-
Notifications
You must be signed in to change notification settings - Fork 84
NUT-11: length-frame the SIG_ALL message aggregation #404
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,6 +69,10 @@ Signatures are stored in `P2PKWitness` objects and are provided in either each ` | |
|
|
||
| The `signatures` are an array of signatures in hex and correspond to the signatures by one or more signing public keys. | ||
|
|
||
| ### Signature validation | ||
|
|
||
| When validating a witness, the mint **MUST** ignore signatures that do not verify (e.g. signatures produced over a superseded message format) rather than rejecting the transaction, and **MUST** count the number of _unique_ public keys with at least one valid signature towards any threshold. This allows wallets to include signatures over multiple message formats during protocol upgrades. | ||
|
|
||
| ## Tags | ||
|
|
||
| More complex spending conditions can be defined in the tags in `Secret.tags`. All tags are optional. Tags are arrays with two or more strings being `["key", "value1", "value2", ...]`. We denote a specific tag in a proof by its `key`. | ||
|
|
@@ -134,31 +138,34 @@ If this condition is met, the `SIG_ALL` flag is enforced and only **the first in | |
|
|
||
| #### Message aggregation for `SIG_ALL` | ||
|
|
||
| The message to be signed depends on the type of transaction containing an input with signature flag `SIG_ALL`. | ||
|
|
||
| ##### Aggregation for `swap` | ||
|
|
||
| A swap contains `inputs` and `outputs` (see [NUT-03][03]). To provide a valid signature, the owner (or owners) of the signing public keys must concatenate the `secret` and `C` fields of all `Proofs` (inputs) with the `amount` and `B_` fields of all `BlindedMessages` (outputs, see [NUT-00][00]) to a single message string in the order they appear in the transaction. This concatenated string is then hashed and signed (see [Signature scheme](#signature-scheme)). | ||
|
|
||
| If a swap transaction has `n` inputs and `m` outputs, the message to sign becomes: | ||
| To provide a valid signature, the owner (or owners) of the signing public keys commit to the quote ID (for a melt), all `Proofs` (the inputs) and all `BlindedMessages` (the outputs, see [NUT-00][00]) of the transaction, in the order they appear in the request. The message is built over raw bytes: | ||
|
|
||
| ``` | ||
| msg = secret_0 || C_0 || ... || secret_n || C_n || amount_0 || B_0 || ... || amount_m || B_m | ||
| msg_to_sign = b"Cashu_SigAllSig_v1" | ||
| || len32(quote) || quote | ||
| || for each input i (in request order): | ||
| len32(secret_i) || secret_i | ||
| || len32(C_i) || C_i | ||
| || for each output j (in request order): | ||
| len32(amount_j) || amount_j | ||
| || len32(B_j) || B_j | ||
| ``` | ||
|
|
||
| Here, `||` denotes string concatenation. The `C` of each input and `B_` of each output are **hex strings** and `amount` is a UTF-encoded string. | ||
|
|
||
| ##### Aggregation for `melt` | ||
| Where: | ||
|
|
||
| For a melt transaction, the message to sign is composed of all the inputs, the quote ID being paid, and the [NUT-08][08] blank `outputs`. | ||
| - `b"Cashu_SigAllSig_v1"` is the domain-separation tag as raw ASCII bytes, not length-prefixed. | ||
| - `||` denotes byte concatenation and `len32(x)` is the 32-bit (4-byte) big-endian length of the byte array `x` in bytes. | ||
| - `quote` is the UTF-8 quote ID being paid by a melt transaction ([NUT-05][05]), or the empty byte array for a swap ([NUT-03][03]); thus `len32(quote)` is `0` for a swap. | ||
| - `secret_i` is the UTF-8 bytes of the input's **unescaped** `secret` string. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here we say i think we can add one canonical vector whose secret contains a character that differs between escaped/unescaped JSON (a /, a unicode escape, a quote), so CTS, CDK and Nutshell can't quietly disagree.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test vectors show the canonical MTS bytes and the escaped proof structs. A mismatch with escaping would produce different bytes and would be obvious? eg: "secret": "[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",contains backslash characters, from escaping secret for wire. |
||
| - `C_i` is the raw byte representation of the input's signature point (e.g. 33-byte compressed secp256k1 point), decoded from the proof's hex string. | ||
| - `amount_j` is the output amount as canonical minimal big-endian bytes (e.g. `0` → empty byte array, `1` → `0x01`, `256` → `0x0100`); thus `len32(amount_j)` is its length in bytes as a 32-bit integer (e.g. `0` for amount `0`, `1` for amount `1`, `2` for amount `256`). | ||
| - `B_j` is the raw byte representation of the blinded message (e.g. 33-byte compressed secp256k1 point), decoded from the request's hex string. | ||
|
|
||
| If a melt transaction has `n` inputs, `m` blank outputs, and a quote ID `quote_id`, the message to sign becomes: | ||
| The inputs are the `Proofs` in the transaction's `inputs` field. The outputs are the `BlindedMessages` in its `outputs` field: for a swap all requested outputs, for a melt the [NUT-08][08] blank outputs (zero output entries if absent). The message to sign is hashed and signed as described in [Signature scheme](#signature-scheme). | ||
|
|
||
| ``` | ||
| msg = secret_0 || C_0 || ... || secret_n || C_n || amount_0 || B_0 || ... || amount_m || B_m || quote_id | ||
| ``` | ||
|
|
||
| Here, `||` denotes string concatenation. The `C` of each input and `B_` of each output are **hex strings** and `amount` is a UTF-encoded string. | ||
| > [!NOTE] | ||
| > | ||
| > The output keyset `id` is deliberately not part of the message. Output keysets are already constrained at the mint: new outputs **MUST** be from an active keyset ([NUT-02][02]), and the inputs and outputs of a transaction must share one unit (error codes `11009`/`11010`). Leaving the `id` unbound lets pre-signed transactions remain valid across keyset rotations. | ||
|
|
||
| ## Locktime Tag | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.