Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add spending condition trees to multiplex NUT-10 #127

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions sct.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
NUT-SCT: Spending Condition Trees (SCT)
==========================

`optional`, `depends on: NUT-10`

---

This NUT describes a [NUT-10] spending condition called a Spending Condition Tree (SCT). An ecash token locked with an SCT is spendable under a set of conditions, but at spend-time only a single condition must be revealed to the mint.

# Definitions

The following section describes some functions which wallets and mints need to compose and verify Spending Condition Trees. Reference code in python is included for clarity.

### `SHA256(data: bytes) -> bytes`

The SHA256 hash function.

```python
import hashlib

def SHA256(data: bytes) -> bytes:
h = hashlib.sha256()
h.update(data)
return h.digest()
```

### `sorted_merkle_hash(left: bytes, right: bytes) -> bytes`

Hashes two internal merkle node hashes, sorting them lexicographically so that left/right position in the tree is irrelevant.

```python
def sorted_merkle_hash(left: bytes, right: bytes) -> bytes:
if right < left:
left, right = right, left
return SHA256(left + right)
```

### `merkle_root(leaf_hashes: List[bytes]) -> bytes`

Compute the merkle root of a set of leaf hashes. Each branch hash is derived using `sorted_merkle_hash()`, so that left/right position in the tree is irrelevant.

```python
def merkle_root(leaf_hashes: List[bytes]) -> bytes:
if len(leaf_hashes) == 0:
return b""
elif len(leaf_hashes) == 1:
return leaf_hashes[0]
else:
split = len(leaf_hashes) // 2
left = merkle_root(leaf_hashes[:split])
right = merkle_root(leaf_hashes[split:])
return sorted_merkle_hash(left, right)
```

### `merkle_verify(root: bytes, leaf_hash: bytes, proof: List[bytes]) -> bool`

Verify a proof of membership in the given merkle root hash. Membership proofs are represented as a list of internal node hashes, in order from deepest to shallowest in the tree.

To match the behavior of `merkle_root()`, each branch hash is derived using `sorted_merkle_hash()`, so that left/right position in the tree is irrelevant.

```python
def merkle_verify(root: bytes, leaf_hash: bytes, proof: List[bytes]) -> bool:
h = leaf_hash
for branch_hash in proof:
h = sorted_merkle_hash(h, branch_hash)
return h == root
```

# Spending Condition Trees

In its _expanded_ form, a Spending Condition Tree (SCT) is an ordered list of [NUT-00] secrets, `[x1, x2, ... xn]`.

Each secret in the SCT is a UTF8-encoded string. Each may be a serialized JSON [NUT-10] secret, or a plain [NUT-00] bearer secret. Example:

```json
[
"[\"P2PK\",{\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]],\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\"}]",
"[\"P2PK\",{\"tags\":[[\"sigflag\",\"SIG_ALL\"]],\"nonce\":\"ad4481ae666d97c347e2d737aaae159b30ac6d6fcef93cdca4395bb49d581f0e\",\"data\":\"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\"}]",
"[\"P2PK\",{\"nonce\":\"f7325999fee4aacfcd7e6e8d54f651e4b518724c486178b6587ebce107119596\",\"data\":\"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\"}]",
"9becd3a8ce24b53beaf8ffb20a497b683b55f87ef87e3814be43a5768bcfe69f"
]
```

Each secret in the SCT is one possible spending condition for an ecash token. Fulfilling _any_ of the spending conditions in the SCT is deemed sufficient to spend the token.

However, only the wallet which creates the SCT will ever see this expanded form of the SCT. To avoid exposing the whole SCT structure to the mint, a wallet must compute the SCT root hash.

```python
secrets: List[str] = [x1, x2, ... xn]
leaf_hashes = [SHA256(x.encode('utf8')) for x in secrets]
sct_root = merkle_root(leaf_hashes)
```

The `sct_root` is then used as a commitment to the list of secrets.

## SCT

[NUT-10] Secret `kind: SCT`

If for a `Proof`, `Proof.secret` is a `Secret` of kind `SCT`. The hex-encoded merkle root hash of a Spending Condition Tree is in `Proof.secret.data`.

The proof is unlocked by fulfilling ALL of the following conditions:

1. `Proof.witness.leaf_secret` must be a UTF8 string, treated as a secret (possibly a [NUT-10] well-known secret).
1. `Proof.witness.merkle_proof` must be a valid proof (i.e. a list of hashes) demonstrating that `SHA256(Proof.witness.leaf_secret)` is a leaf hash of the SCT root (specified in `Proof.secret.data`).
1. (optional) if `Proof.witness.leaf_secret` encodes a [NUT-10] spending condition which requires a witness, then `Proof.witness.witness` must provide witness data which satisfies those conditions.[^1]

[^1]: Note that the `SCT` well-known secret rules allow for nested SCTs, where a leaf node is itself another SCT. In this case, the `Proof.witness.witness` will itself be another `SCT` witness object. Recursion and self-referencing inside an SCT is not permitted.

Example of a [NUT-10] `SCT` secret object:

```json
[
"SCT",
{
"nonce": "d426a2750847d5775f06560d973b484a5b6315e17efffecb1d8d518876c01615",
"data": "18065b939dbbb648749bd5532c740078bb757c3b9f81e0309350a1277fa9a39c"
}
]
```

We serialize this object to a JSON string, and get a blind signature on it, by the mint which is stored in `Proof.C` (see [NUT-03](03.md)). This commits the token created with this secret to knowing and (if applicable) passing [NUT-10] checks for at least one of the SCT's leaf secrets.

### Spending

To spend this ecash, the wallet must know a `leaf_secret` and a `merkle_proof` of inclusion in the SCT root hash. Here is an example `Proof` which spends a token locked with a well-known secret of kind `SCT`. The `leaf_secret` is a simple [NUT-00] bearer secret with no spending conditions.

```json
{
"amount": 1,
"secret": "[\"SCT\",{\"nonce\":\"d426a2750847d5775f06560d973b484a5b6315e17efffecb1d8d518876c01615\",\"data\":\"18065b939dbbb648749bd5532c740078bb757c3b9f81e0309350a1277fa9a39c\"}]",
"C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
"id": "009a1f293253e41e",
"witness": "{\"leaf_secret\":\"9becd3a8ce24b53beaf8ffb20a497b683b55f87ef87e3814be43a5768bcfe69f\",\"merkle_proof\":[\"8da10ed117cad5e89c6131198ffe271166d68dff9ce961ff117bd84297133b77\",\"2397636f1aff968e9f8177b8deaaf9514415126e45aa7755841f966f4eb2279f\"]}"
}
```

Here is a different input, which references the same SCT root hash but uses a different leaf secret - this time a [NUT-10] `P2PK` secret. As `P2PK` requires signature validation, we must provide a `P2PKWitness` stored in `Proof.witness.witness` (See [NUT-11] for details).

```json
{
"amount": 1,
"secret": "[\"SCT\",{\"nonce\":\"d426a2750847d5775f06560d973b484a5b6315e17efffecb1d8d518876c01615\",\"data\":\"18065b939dbbb648749bd5532c740078bb757c3b9f81e0309350a1277fa9a39c\"}]",
"C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
"id": "009a1f293253e41e",
"witness": "{\"leaf_secret\":\"[\\\"P2PK\\\",{\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]],\\\"nonce\\\":\\\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\"}]\",\"merkle_proof\":[\"6bad0d7d596cb9048754ee75daf13ee7e204c6e408b83ee67514369e3f8f3f96\",\"4ac38d0dffb307a4d704c5c7cc28324fd3c151cfaaeddeaa695b890f1a24050b\"],\"witness\":\"{\\\"signatures\\\":[\\\"9ef66b39609fe4b5653ee8cc1d4f7133ca16c6cf1862eca7df626c63d90f19f257241ebae3939baa837e1be25e2996b7062e16ba58877aa8318db20729184ff4\\\"]}\"}"
}
```

Note that for the purpose of [NUT-11] input signature verification, the signature must be made over the **top-level secret**, which is of kind `SCT`.

## Previous Work

The design of SCTs was inspired by the Bitcoin TapRoot upgrade, specifically [BIP-0341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki). Much like TapScript Trees, Cashu SCTs use merkle trees to prove that a spending condition was committed to when an ecash token was first created.

# Appendix

## Merkle Proof Generation

Below is an example of a function which generates a proof that a specific leaf hash at the given position in the tree is a member of the SCT's merkle root hash. It requires knowledge of the full set of leaf hashes.

This is only an example of a merkle-proof generation algorithm. Wallet implementations are free to implement proof construction in any way which passes the `merkle_verify` function.

```python
def merkle_prove(leaf_hashes: List[bytes], position: int) -> List[bytes]:
if len(leaf_hashes) <= 1:
return []
split = len(leaf_hashes) // 2
if position < split:
proof = merkle_prove(leaf_hashes[:split], position)
proof.append(merkle_root(leaf_hashes[split:]))
return proof
else:
proof = merkle_prove(leaf_hashes[split:], position - split)
proof.append(merkle_root(leaf_hashes[:split]))
return proof
```

[NUT-00]: 00.md
[NUT-10]: 10.md
[NUT-11]: 11.md
158 changes: 158 additions & 0 deletions tests/sct-tests.md.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# NUT-XX Test Vectors

An SCT tree built with the following leaf secrets:

```json
[
"[\"P2PK\",{\"nonce\":\"ffd73b9125cc07cdbf2a750222e601200452316bf9a2365a071dd38322a098f0\",\"data\":\"028fab76e686161cc6daf78fea08ba29ce8895e34d20322796f35fec8e689854aa\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]",
"[\"P2PK\",{\"tags\":[[\"sigflag\",\"SIG_ALL\"]],\"nonce\":\"ad4481ae666d97c347e2d737aaae159b30ac6d6fcef93cdca4395bb49d581f0e\",\"data\":\"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\"}]",
"[\"P2PK\",{\"nonce\":\"f7325999fee4aacfcd7e6e8d54f651e4b518724c486178b6587ebce107119596\",\"data\":\"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\"}]",
"[\"HTLC\",{\"nonce\":\"83a2010affde58f99848e6c33906a0fc04b8fb4fd195e1467c5cb4dbfff43438\",\"data\":\"023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54\",\"tags\":[[\"pubkeys\",\"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\"],[\"locktime\",\"1689418329\"],[\"refund\",\"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e\"]]}]",
"[\"HTLC\",{\"nonce\":\"99fc46253939ba6f057760b8b29b00c6f876050e32bd8ee95b5b223f8aa0ec90\",\"data\":\"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\"tags\":[[\"pubkeys\",\"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\"],[\"locktime\",\"1689418329\"]]}]",
"9becd3a8ce24b53beaf8ffb20a497b683b55f87ef87e3814be43a5768bcfe69f",
"3838b0454a81511d33b608984c7f01a082f3a80830168f1f3e7d47d21420e8f9",
]
```

Will result in the following leaf hashes:

```json
[
"b43b79ed408d4cc0aa75ad0a97ab21e357ff7ee027300fb573833c568431e808",
"6bad0d7d596cb9048754ee75daf13ee7e204c6e408b83ee67514369e3f8f3f96",
"8da10ed117cad5e89c6131198ffe271166d68dff9ce961ff117bd84297133b77",
"7ec5a236d308d2c2bf800d81d3e3df89cc98f4f937d0788c302d2754ba28166a",
"e19353a94d1aaf56b150b1399b33cd4ef4096b086665945fbe96bd72c22097a7",
"cc655b7103c8b999b3fc292484bcb5a526e2d0cbf951f17fd7670fc05b1ff947",
"009ea9fae527f7914096da1f1ce2480d2e4cfea62480afb88da9219f1c09767f"
]
```

Which results in the following SCT merkle root hash:

```
71655cac0c83c6949169bcd6c82b309810138895f83b967089ffd9f64d109306
```

Each leaf hash has the following proofs of inclusion:
```json
[
[
"7a56977edf9c299c1cfb14dfbeb2ab28d7b3d44b3c9cc6b7854f8a58acb3407d",
"7de4c7c75c8082ed9a2124ce8f027ed9a60f2236b6f50c62748a220086ed367b"
],
[
"8da10ed117cad5e89c6131198ffe271166d68dff9ce961ff117bd84297133b77",
"b43b79ed408d4cc0aa75ad0a97ab21e357ff7ee027300fb573833c568431e808",
"7de4c7c75c8082ed9a2124ce8f027ed9a60f2236b6f50c62748a220086ed367b"
],
[
"6bad0d7d596cb9048754ee75daf13ee7e204c6e408b83ee67514369e3f8f3f96",
"b43b79ed408d4cc0aa75ad0a97ab21e357ff7ee027300fb573833c568431e808",
"7de4c7c75c8082ed9a2124ce8f027ed9a60f2236b6f50c62748a220086ed367b"
],
[
"e19353a94d1aaf56b150b1399b33cd4ef4096b086665945fbe96bd72c22097a7",
"f583288c32937865b0c5c7d4a9262f65b7275f59c8796eb3e79de9e0b217d5e0",
"7ea48b9a4ad58f92c4cfa8e006afa98b2b05ac1b4de481e13088d26f672d8edc"
],
[
"7ec5a236d308d2c2bf800d81d3e3df89cc98f4f937d0788c302d2754ba28166a",
"f583288c32937865b0c5c7d4a9262f65b7275f59c8796eb3e79de9e0b217d5e0",
"7ea48b9a4ad58f92c4cfa8e006afa98b2b05ac1b4de481e13088d26f672d8edc"
],
[
"009ea9fae527f7914096da1f1ce2480d2e4cfea62480afb88da9219f1c09767f",
"2628c9759f0cecbb43b297b6eb0c268573d265730c2c9f6e194b4948f43d669d",
"7ea48b9a4ad58f92c4cfa8e006afa98b2b05ac1b4de481e13088d26f672d8edc"
],
[
"cc655b7103c8b999b3fc292484bcb5a526e2d0cbf951f17fd7670fc05b1ff947",
"2628c9759f0cecbb43b297b6eb0c268573d265730c2c9f6e194b4948f43d669d",
"7ea48b9a4ad58f92c4cfa8e006afa98b2b05ac1b4de481e13088d26f672d8edc"
]
]
```


## Proofs

The following is a valid `Proof` object spending an `SCT` secret with a bearer leaf secret.

```json
{
"amount": 1,
"secret": "[\"SCT\",{\"nonce\":\"d426a2750847d5775f06560d973b484a5b6315e17efffecb1d8d518876c01615\",\"data\":\"71655cac0c83c6949169bcd6c82b309810138895f83b967089ffd9f64d109306\"}]",
"C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
"id": "009a1f293253e41e",
"witness": {
"leaf_secret": "9becd3a8ce24b53beaf8ffb20a497b683b55f87ef87e3814be43a5768bcfe69f",
"merkle_proof": [
"009ea9fae527f7914096da1f1ce2480d2e4cfea62480afb88da9219f1c09767f",
"2628c9759f0cecbb43b297b6eb0c268573d265730c2c9f6e194b4948f43d669d",
"7ea48b9a4ad58f92c4cfa8e006afa98b2b05ac1b4de481e13088d26f672d8edc"
]
}
}
```

The following is a valid `Proof` object spending an `SCT` secret with a NUT-11 P2PK leaf secret.

```json
{
"amount": 1,
"secret": "[\"SCT\",{\"nonce\":\"d426a2750847d5775f06560d973b484a5b6315e17efffecb1d8d518876c01615\",\"data\":\"71655cac0c83c6949169bcd6c82b309810138895f83b967089ffd9f64d109306\"}]",
"C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
"id": "009a1f293253e41e",
"witness": {
"leaf_secret": "[\"P2PK\",{\"nonce\":\"ffd73b9125cc07cdbf2a750222e601200452316bf9a2365a071dd38322a098f0\",\"data\":\"028fab76e686161cc6daf78fea08ba29ce8895e34d20322796f35fec8e689854aa\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]",
"merkle_proof": [
"7a56977edf9c299c1cfb14dfbeb2ab28d7b3d44b3c9cc6b7854f8a58acb3407d",
"7de4c7c75c8082ed9a2124ce8f027ed9a60f2236b6f50c62748a220086ed367b"
],
"witness": "{\"signatures\":[\"9ef66b39609fe4b5653ee8cc1d4f7133ca16c6cf1862eca7df626c63d90f19f257241ebae3939baa837e1be25e2996b7062e16ba58877aa8318db20729184ff4\"]}"
}
}
```

<sub>The secret key for the above pubkey is `8e935aec5668312be8f960a5ecc3c5dd290e39985970bfd093047df7f05cc9ec`</sub>

### Invalid

The following is an *invalid* `Proof` object which attempts to spend an `SCT` secret with a bearer leaf secret. The proof is invalid becase the `merkle_proof`'s first (deepest) hash is incorrect.

```json
{
"amount": 1,
"secret": "[\"SCT\",{\"nonce\":\"d426a2750847d5775f06560d973b484a5b6315e17efffecb1d8d518876c01615\",\"data\":\"71655cac0c83c6949169bcd6c82b309810138895f83b967089ffd9f64d109306\"}]",
"C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
"id": "009a1f293253e41e",
"witness": {
"leaf_secret": "9becd3a8ce24b53beaf8ffb20a497b683b55f87ef87e3814be43a5768bcfe69f",
"merkle_proof": [
"db7a191c4f3c112d7eb3ae9ee8fa9bd940fc4fed6ada9ba9ab2f102c3e3bbe80",
"2628c9759f0cecbb43b297b6eb0c268573d265730c2c9f6e194b4948f43d669d",
"7ea48b9a4ad58f92c4cfa8e006afa98b2b05ac1b4de481e13088d26f672d8edc"
]
}
}
```

The following is an *invalid* `Proof` object spending an `SCT` secret with a NUT-11 P2PK leaf secret. The proof is invalid because the P2PK signature is made on the `leaf_secret` instead of the top-level `Proof.secret`.

```json
{
"amount": 1,
"secret": "[\"SCT\",{\"nonce\":\"d426a2750847d5775f06560d973b484a5b6315e17efffecb1d8d518876c01615\",\"data\":\"71655cac0c83c6949169bcd6c82b309810138895f83b967089ffd9f64d109306\"}]",
"C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
"id": "009a1f293253e41e",
"witness": {
"leaf_secret": "[\"P2PK\",{\"nonce\":\"ffd73b9125cc07cdbf2a750222e601200452316bf9a2365a071dd38322a098f0\",\"data\":\"028fab76e686161cc6daf78fea08ba29ce8895e34d20322796f35fec8e689854aa\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]",
"merkle_proof": [
"7a56977edf9c299c1cfb14dfbeb2ab28d7b3d44b3c9cc6b7854f8a58acb3407d",
"7de4c7c75c8082ed9a2124ce8f027ed9a60f2236b6f50c62748a220086ed367b"
],
"witness": "{\"signatures\":[\"106b3df8cbe1b9e867ec5717f5018b42e388e8fce7de3b09da1da7c6ab1eaaa19ab7ab95a3bcb8af8d627214f339a594efa8aefa9db7f34de2ca0587f5693e46\"]}"
}
}
```