From fd55e283128da1e60d534fce463972631cc9f43d Mon Sep 17 00:00:00 2001 From: Rob Woodgate Date: Fri, 17 Jul 2026 22:43:00 +0100 Subject: [PATCH 1/3] NUT-11: length-frame the SIG_ALL message aggregation Re-encodes the SIG_ALL message to the domain-separated, length-framed byte format used by NUT-20/29 (tag "Cashu_SigAllSig_v1"). The quote id is framed directly after the tag and is empty for swaps, so one layout serves both transaction types. Adds a normative signature validation rule (ignore signatures that do not verify, count unique public keys) and canonical message vectors in tests/11-test.md. --- 11.md | 41 ++++++++++++++++-------------- tests/11-test.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 19 deletions(-) diff --git a/11.md b/11.md index c1630c456..fa940ba38 100644 --- a/11.md +++ b/11.md @@ -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,30 @@ 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. +Where: -##### Aggregation for `melt` - -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`. - -If a melt transaction has `n` inputs, `m` blank outputs, and a quote ID `quote_id`, the message to sign becomes: - -``` -msg = secret_0 || C_0 || ... || secret_n || C_n || amount_0 || B_0 || ... || amount_m || B_m || quote_id -``` +- `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. +- `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. -Here, `||` denotes string concatenation. The `C` of each input and `B_` of each output are **hex strings** and `amount` is a UTF-encoded string. +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). ## Locktime Tag diff --git a/tests/11-test.md b/tests/11-test.md index 96468410b..662188a09 100644 --- a/tests/11-test.md +++ b/tests/11-test.md @@ -409,3 +409,68 @@ The following is a valid multi-sig `SIG_ALL` `MeltRequest`. ] } ``` + +### SIG_ALL v1 Message Vectors + +The signing key for these vectors is the well-known test key: + +``` +privkey = 0000000000000000000000000000000000000000000000000000000000000001 +pubkey = 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +``` + +The following `SwapRequest` is signed over the v1 message. The witness carries one signature per message format (v1, then the superseded formats); a mint counts ONE unique public key with a valid signature and ignores the signatures it cannot verify. + +```json +{ + "inputs": [ + { + "amount": 8, + "id": "009a1f293253e41e", + "secret": "[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]", + "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", + "witness": "{\"signatures\":[\"b2b821f819f12ab61d261971187d19772aaad11422f9d5f3ffda6f97de03349e0e44976b5d44e3c850a99b621045167915caf3ef5b102abe69439e36b68f89d5\",\"947864cac6d9369f358257eece81c4aa9deb2e63899f2d868e7c243ff334ade5db1d8db010daa2b9f4407610458d9ffa3250eb778d1a0980f4de671627c8718e\",\"ea0cdab0d0895bbf212d6d301ea4180171a1e2af309fcee57fa16998b9c234f2f8722953f9703f561866f45672b2a499109722dfeb3c3e7a2a2d51cda9684652\"]}" + }, + { + "amount": 2, + "id": "009a1f293253e41e", + "secret": "[\"P2PK\",{\"nonce\":\"16d937a29ae4e5d4a6e9f9959c4d4b9a8d6f2f7b2f0a1b3c4d5e6f708192a3b4\",\"data\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]", + "C": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + ], + "outputs": [ + { + "amount": 8, + "id": "009a1f293253e41e", + "B_": "035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d" + }, + { + "amount": 2, + "id": "009a1f293253e41e", + "B_": "0288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6" + } + ] +} +``` + +The corresponding v1 `msg_to_sign` (572 bytes, hex) and its SHA-256 hash are: + +``` +msg_to_sign = 43617368755f536967416c6c5369675f763100000000000000c05b225032504b222c7b226e6f6e6365223a2238353964343933356334393037303632613632393763663465363633653238333564393064393765636464353130373435643332663638313633323361343166222c2264617461223a22303237396265363637656639646362626163353561303632393563653837306230373032396266636462326463653238643935396632383135623136663831373938222c2274616773223a5b5b22736967666c6167222c225349475f414c4c225d5d7d5d0000002102698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904000000c05b225032504b222c7b226e6f6e6365223a2231366439333761323961653465356434613665396639393539633464346239613864366632663762326630613162336334643565366637303831393261336234222c2264617461223a22303237396265363637656639646362626163353561303632393563653837306230373032396266636462326463653238643935396632383135623136663831373938222c2274616773223a5b5b22736967666c6167222c225349475f414c4c225d5d7d5d0000002102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5000000010800000021035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d0000000102000000210288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6 +sha256(msg_to_sign) = 3fd05c896ff0d5a058f9180e577dced83539ea885f9bf6adf71f7ed084590dc2 +``` + +Note the empty quote field for a swap: the four zero bytes immediately after the domain tag are `len32(quote) = 0`, and no quote bytes follow. + +For a melt of quote `9d745270-1405-46de-b5c5-e2762b4f5e00` with the same inputs and outputs, the v1 `msg_to_sign` (608 bytes, hex) and its SHA-256 hash are: + +``` +msg_to_sign = 43617368755f536967416c6c5369675f76310000002439643734353237302d313430352d343664652d623563352d653237363262346635653030000000c05b225032504b222c7b226e6f6e6365223a2238353964343933356334393037303632613632393763663465363633653238333564393064393765636464353130373435643332663638313633323361343166222c2264617461223a22303237396265363637656639646362626163353561303632393563653837306230373032396266636462326463653238643935396632383135623136663831373938222c2274616773223a5b5b22736967666c6167222c225349475f414c4c225d5d7d5d0000002102698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904000000c05b225032504b222c7b226e6f6e6365223a2231366439333761323961653465356434613665396639393539633464346239613864366632663762326630613162336334643565366637303831393261336234222c2264617461223a22303237396265363637656639646362626163353561303632393563653837306230373032396266636462326463653238643935396632383135623136663831373938222c2274616773223a5b5b22736967666c6167222c225349475f414c4c225d5d7d5d0000002102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5000000010800000021035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d0000000102000000210288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6 +sha256(msg_to_sign) = 0ebae3a8dbe1107a7b6392a53b6fb3dfc18b38c4ab02b4b9a460ad8829e20ce1 +``` + +A valid signature on the melt message by the test key is: + +``` +1493200b3f52cd67bdda888f67d80cf8862b4e7bd48a800828f5482218305c367f17dc3409cf89e6c795741605ccce2501f5e2b0f0e3ec54edcc7001a5863e3d +``` From 7620cac149cac2cd3d716531500c2ace31a43238 Mon Sep 17 00:00:00 2001 From: Rob Woodgate Date: Fri, 17 Jul 2026 22:54:14 +0100 Subject: [PATCH 2/3] NUT-11: keep a single canonical SIG_ALL vector set Removes the superseded string-concatenation SIG_ALL vectors so the spec carries one canonical message aggregation, retitles the length-framed vector set, and notes why the output keyset id is not part of the message. --- 11.md | 4 + tests/11-test.md | 295 ----------------------------------------------- 2 files changed, 4 insertions(+), 295 deletions(-) diff --git a/11.md b/11.md index fa940ba38..04226d8f4 100644 --- a/11.md +++ b/11.md @@ -163,6 +163,10 @@ Where: 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). +> [!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 The `locktime` tag signals which set of locking rules the mint should apply. There are three possible states the `locktime` tag can represent: diff --git a/tests/11-test.md b/tests/11-test.md index 662188a09..eb827aada 100644 --- a/tests/11-test.md +++ b/tests/11-test.md @@ -117,301 +117,6 @@ The following is a `Proof` with a signature from the refund key that is **not** ### SIG_ALL Test Vectors -Example `SwapRequest`: - -```json -{ - "inputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"P2PK\",{\"nonce\":\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd", - "witness": "{\"signatures\":[\"ce017ca25b1b97df2f72e4b49f69ac26a240ce14b3690a8fe619d41ccc42d3c1282e073f85acd36dc50011638906f35b56615f24e4d03e8effe8257f6a808538\"]}" - } - ], - "outputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - } - ] -} -``` - -The following is the `msg_to_sign` on the above `SwapRequest`. - -``` -["P2PK",{"nonce":"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303","data":"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1","tags":[["sigflag","SIG_ALL"]]}]02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd2038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39 -``` - -The hex representation of sha256sum(msg_to_sign) should look like this: `de7f9e3ca0fcc5ed3258fcf83dbf1be7fa78a5ed6da7bf2aa60d61e9dc6eb09a` - -The following is a `SwapRequest` with a valid sig_all signature. - -```json -{ - "inputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"P2PK\",{\"nonce\":\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd", - "witness": "{\"signatures\":[\"ce017ca25b1b97df2f72e4b49f69ac26a240ce14b3690a8fe619d41ccc42d3c1282e073f85acd36dc50011638906f35b56615f24e4d03e8effe8257f6a808538\"]}" - } - ], - "outputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - } - ] -} -``` - -The following is a `SwapRequest` that is invalid as the spending conditions are not identical across inputs (secret tags are different). - -```json -{ - "inputs": [ - { - "amount": 1, - "id": "00bfa73302d12ffd", - "secret": "[\"P2PK\",{\"nonce\":\"fa6dd3fac9086c153878dec90b9e37163d38ff2ecf8b37db6470e9d185abbbae\",\"data\":\"033b42b04e659fed13b669f8b16cdaffc3ee5738608810cf97a7631d09bd01399d\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "024d232312bab25af2e73f41d56864d378edca9109ae8f76e1030e02e585847786", - "witness": "{\"signatures\":[\"27b4d260a1186e3b62a26c0d14ffeab3b9f7c3889e78707b8fd3836b473a00601afbd53a2288ad20a624a8bbe3344453215ea075fc0ce479dd8666fd3d9162cc\"]}" - }, - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"P2PK\",{\"nonce\":\"4007b21fc5f5b1d4920bc0a08b158d98fd0fb2b0b0262b57ff53c6c5d6c2ae8c\",\"data\":\"033b42b04e659fed13b669f8b16cdaffc3ee5738608810cf97a7631d09bd01399d\",\"tags\":[[\"locktime\",\"122222222222222\"],[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "02417400f2af09772219c831501afcbab4efb3b2e75175635d5474069608deb641" - } - ], - "outputs": [ - { - "amount": 1, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - }, - { - "amount": 1, - "id": "00bfa73302d12ffd", - "B_": "03afe7c87e32d436f0957f1d70a2bca025822a84a8623e3a33aed0a167016e0ca5" - }, - { - "amount": 1, - "id": "00bfa73302d12ffd", - "B_": "02c0d4fce02a7a0f09e3f1bca952db910b17e81a7ebcbce62cd8dcfb127d21e37b" - } - ] -} -``` - -The following is a `SwapRequest` where multiple valid signatures are required and provided. - -```json -{ - "inputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"P2PK\",{\"nonce\":\"04bfd885fc982d553711092d037fdceb7320fd8f96b0d4fd6d31a65b83b94272\",\"data\":\"0275e78025b558dbe6cb8fdd032a2e7613ca14fda5c1f4c4e3427f5077a7bd90e4\",\"tags\":[[\"pubkeys\",\"035163650bbd5ed4be7693f40f340346ba548b941074e9138b67ef6c42755f3449\",\"02817d22a8edc44c4141e192995a7976647c335092199f9e076a170c7336e2f5cc\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "03866a09946562482c576ca989d06371e412b221890804c7da8887d321380755be", - "witness": "{\"signatures\":[\"be1d72c5ca16a93c5a34f25ec63ce632ddc3176787dac363321af3fd0f55d1927e07451bc451ffe5c682d76688ea9925d7977dffbb15bd79763b527f474734b0\",\"669d6d10d7ed35395009f222f6c7bdc28a378a1ebb72ee43117be5754648501da3bedf2fd6ff0c7849ac92683538c60af0af504102e40f2d8daca8e08b1ca16b\"]}" - } - ], - "outputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - } - ] -} -``` - -The following is a `SwapRequest` where the locktime has passed and the refund key signatures are valid. - -```json -{ - "inputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"P2PK\",{\"nonce\":\"9ea35553beb18d553d0a53120d0175a0991ca6109370338406eed007b26eacd1\",\"data\":\"02af21e09300af92e7b48c48afdb12e22933738cfb9bba67b27c00c679aae3ec25\",\"tags\":[[\"locktime\",\"1\"],[\"refund\",\"02637c19143c58b2c58bd378400a7b82bdc91d6dedaeb803b28640ef7d28a887ac\",\"0345c7fdf7ec7c8e746cca264bf27509eb4edb9ac421f8fbfab1dec64945a4d797\"],[\"n_sigs_refund\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "03dd83536fbbcbb74ccb3c87147df26753fd499cc2c095f74367fff0fb459c312e", - "witness": "{\"signatures\":[\"23b58ef28cd22f3dff421121240ddd621deee83a3bc229fd67019c2e338d91e2c61577e081e1375dbab369307bba265e887857110ca3b4bd949211a0a298805f\",\"7e75948ef1513564fdcecfcbd389deac67c730f7004f8631ba90c0844d3e8c0cf470b656306877df5141f65fd3b7e85445a8452c3323ab273e6d0d44843817ed\"]}" - } - ], - "outputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - } - ] -} -``` - -The following is a valid `SwapRequest` with an HTLC also locked to a public key - -```json -{ - "inputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"HTLC\",{\"nonce\":\"d730dd70cd7ec6e687829857de8e70aab2b970712f4dbe288343eca20e63c28c\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\",\"tags\":[[\"pubkeys\",\"0350cda8a1d5257dbd6ba8401a9a27384b9ab699e636e986101172167799469b14\"],[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "03ff6567e2e6c31db5cb7189dab2b5121930086791c93899e4eff3dda61cb57273", - "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"signatures\":[\"a4c00a9ad07f9936e404494fda99a9b935c82d7c053173b304b8663124c81d4b00f64a225f5acf41043ca52b06382722bd04ded0fbeb0fcc404eed3b24778b88\"]}" - } - ], - "outputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - } - ] -} -``` - -The following is an invalid `SwapRequest` with an HTLC also locked to a public key, a locktime and a refund key. The locktime has not expired, but the proof is signed with the refund key. - -```json -{ - "inputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"HTLC\",{\"nonce\":\"512c4045f12fdfd6f55059669c189e040c37c1ce2f8be104ed6aec296acce4e9\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\",\"tags\":[[\"pubkeys\",\"03ba83defd31c63f8841d188f0d41b5bb3af1bb3c08d0ba46f8f1d26a4d45e8cad\"],[\"locktime\",\"4854185133\"],[\"refund\",\"032f1008a79c722e93a1b4b853f85f38283f9ef74ee4c5c91293eb1cc3c5e46e34\"],[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "02207abeff828146f1fc3909c74613d5605bd057f16791994b3c91f045b39a6939", - "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"signatures\":[\"7816d57871bde5be2e4281065dbe5b15f641d8f1ed9437a3ae556464d6f9b8a0a2e6660337a915f2c26dce1453a416daf682b8fb593b67a0750fce071e0759b9\"]}" - } - ], - "outputs": [ - { - "amount": 1, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - }, - { - "amount": 1, - "id": "00bfa73302d12ffd", - "B_": "03afe7c87e32d436f0957f1d70a2bca025822a84a8623e3a33aed0a167016e0ca5" - } - ] -} -``` - -The following is a valid `SwapRequest` with a multisig HTLC also locked to locktime and refund keys. - -```json -{ - "inputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"HTLC\",{\"nonce\":\"c9b0fabb8007c0db4bef64d5d128cdcf3c79e8bb780c3294adf4c88e96c32647\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\",\"tags\":[[\"pubkeys\",\"039e6ec7e922abb4162235b3a42965eb11510b07b7461f6b1a17478b1c9c64d100\"],[\"locktime\",\"1\"],[\"refund\",\"02ce1bbd2c9a4be8029c9a6435ad601c45677f5cde81f8a7f0ed535e0039d0eb6c\",\"03c43c00ff57f63cfa9e732f0520c342123e21331d0121139f1b636921eeec095f\"],[\"n_sigs_refund\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "0344b6f1471cf18a8cbae0e624018c816be5e3a9b04dcb7689f64173c1ae90a3a5", - "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"signatures\":[\"98e21672d409cc782c720f203d8284f0af0c8713f18167499f9f101b7050c3e657fb0e57478ebd8bd561c31aa6c30f4cd20ec38c73f5755b7b4ddee693bca5a5\",\"693f40129dbf905ed9c8008081c694f72a36de354f9f4fa7a61b389cf781f62a0ae0586612fb2eb504faaf897fefb6742309186117f4743bcebcb8e350e975e2\"]}" - } - ], - "outputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - } - ] -} -``` - -Example `MeltRequest`: - -```json -{ - "quote": "cF8911fzT88aEi1d-6boZZkq5lYxbUSVs-HbJxK0", - "inputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"P2PK\",{\"nonce\":\"bbf9edf441d17097e39f5095a3313ba24d3055ab8a32f758ff41c10d45c4f3de\",\"data\":\"029116d32e7da635c8feeb9f1f4559eb3d9b42d400f9d22a64834d89cde0eb6835\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "02a9d461ff36448469dccf828fa143833ae71c689886ac51b62c8d61ddaa10028b", - "witness": "{\"signatures\":[\"478224fbe715e34f78cb33451db6fcf8ab948afb8bd04ff1a952c92e562ac0f7c1cb5e61809410635be0aa94d0448f7f7959bd5762cc3802b0a00ff58b2da747\"]}" - } - ], - "outputs": [ - { - "amount": 0, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - } - ] -} -``` - -The following is the valid `msg_to_sign` on the above `MeltRequest`. - -``` -["P2PK",{"nonce":"bbf9edf441d17097e39f5095a3313ba24d3055ab8a32f758ff41c10d45c4f3de","data":"029116d32e7da635c8feeb9f1f4559eb3d9b42d400f9d22a64834d89cde0eb6835","tags":[["sigflag","SIG_ALL"]]}]02a9d461ff36448469dccf828fa143833ae71c689886ac51b62c8d61ddaa10028b0038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39cF8911fzT88aEi1d-6boZZkq5lYxbUSVs-HbJxK0 -``` - -The hex representation of sha256sum(msg_to_sign) should look like this: `9efa1067cc7dc870f4074f695115829c3cd817a6866c3b84e9814adf3c3cf262` - -The following is a valid `SIG_ALL` `MeltRequest`. - -```json -{ - "quote": "cF8911fzT88aEi1d-6boZZkq5lYxbUSVs-HbJxK0", - "inputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"P2PK\",{\"nonce\":\"bbf9edf441d17097e39f5095a3313ba24d3055ab8a32f758ff41c10d45c4f3de\",\"data\":\"029116d32e7da635c8feeb9f1f4559eb3d9b42d400f9d22a64834d89cde0eb6835\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "02a9d461ff36448469dccf828fa143833ae71c689886ac51b62c8d61ddaa10028b", - "witness": "{\"signatures\":[\"478224fbe715e34f78cb33451db6fcf8ab948afb8bd04ff1a952c92e562ac0f7c1cb5e61809410635be0aa94d0448f7f7959bd5762cc3802b0a00ff58b2da747\"]}" - } - ], - "outputs": [ - { - "amount": 0, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - } - ] -} -``` - -The following is a valid multi-sig `SIG_ALL` `MeltRequest`. - -```json -{ - "quote": "Db3qEMVwFN2tf_1JxbZp29aL5cVXpSMIwpYfyOVF", - "inputs": [ - { - "amount": 2, - "id": "00bfa73302d12ffd", - "secret": "[\"P2PK\",{\"nonce\":\"68d7822538740e4f9c9ebf5183ef6c4501c7a9bca4e509ce2e41e1d62e7b8a99\",\"data\":\"0394e841bd59aeadce16380df6174cb29c9fea83b0b65b226575e6d73cc5a1bd59\",\"tags\":[[\"pubkeys\",\"033d892d7ad2a7d53708b7a5a2af101cbcef69522bd368eacf55fcb4f1b0494058\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]", - "C": "03a70c42ec9d7192422c7f7a3ad017deda309fb4a2453fcf9357795ea706cc87a9", - "witness": "{\"signatures\":[\"ed739970d003f703da2f101a51767b63858f4894468cc334be04aa3befab1617a81e3eef093441afb499974152d279e59d9582a31dc68adbc17ffc22a2516086\",\"f9efe1c70eb61e7ad8bd615c50ff850410a4135ea73ba5fd8e12a734743ad045e575e9e76ea5c52c8e7908d3ad5c0eaae93337e5c11109e52848dc328d6757a2\"]}" - } - ], - "outputs": [ - { - "amount": 0, - "id": "00bfa73302d12ffd", - "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39" - } - ] -} -``` - -### SIG_ALL v1 Message Vectors - The signing key for these vectors is the well-known test key: ``` From 9591e8b2aaa43700827b3bbb05f6625303af6a5e Mon Sep 17 00:00:00 2001 From: Rob Woodgate Date: Fri, 17 Jul 2026 23:54:19 +0100 Subject: [PATCH 3/3] NUT-14: add HTLC test vectors Preimage, keyless hashlock and SIG_ALL cases; the SIG_ALL vector uses the length-framed NUT-11 aggregation. --- tests/14-test.md | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/14-test.md diff --git a/tests/14-test.md b/tests/14-test.md new file mode 100644 index 000000000..b805371d8 --- /dev/null +++ b/tests/14-test.md @@ -0,0 +1,94 @@ +# NUT-14 Test Vectors + +The signing key for these vectors is the well-known test key: + +``` +privkey = 0000000000000000000000000000000000000000000000000000000000000001 +pubkey = 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +``` + +All hashlocks commit to the same preimage: + +``` +preimage = 0000000000000000000000000000000000000000000000000000000000000001 +hashlock = sha256(preimage) = ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5 +``` + +### Preimage and Signature Test Vectors + +The following `Proof` has a valid preimage and a valid signature on `secret` by the `pubkeys` key, so it is spendable. + +```json +{ + "amount": 8, + "id": "009a1f293253e41e", + "secret": "[\"HTLC\",{\"nonce\":\"5d11913ee0f92fefdc82a6764fd2457a1585d418f0265b5575eb14cd3be76d94\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"],[\"sigflag\",\"SIG_INPUTS\"]]}]", + "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", + "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"signatures\":[\"8d6da34f529edccdb6a5d2122f16293b01b38263e58733acca0ff6595515224f69b80c0933f96a729899249bc7c1a5f34efcb7cf4347f1135625449bae51f86b\"]}" +} +``` + +The same `Proof` with a witness whose preimage does not hash to `Secret.data` is **NOT** spendable, regardless of the signature: + +```json +{ + "amount": 8, + "id": "009a1f293253e41e", + "secret": "[\"HTLC\",{\"nonce\":\"5d11913ee0f92fefdc82a6764fd2457a1585d418f0265b5575eb14cd3be76d94\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"],[\"sigflag\",\"SIG_INPUTS\"]]}]", + "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", + "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000002\",\"signatures\":[\"8d6da34f529edccdb6a5d2122f16293b01b38263e58733acca0ff6595515224f69b80c0933f96a729899249bc7c1a5f34efcb7cf4347f1135625449bae51f86b\"]}" +} +``` + +### Keyless HTLC Test Vector + +A hashlock with no `pubkeys` tag requires no signature: the following `Proof` is spendable with the preimage alone. + +```json +{ + "amount": 8, + "id": "009a1f293253e41e", + "secret": "[\"HTLC\",{\"nonce\":\"09ef07c284bcda9a413723b8bb5d1a4bbee0e9564ba91e0d5e2b2a1071ab5c53\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\"}]", + "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", + "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000001\"}" +} +``` + +### SIG_ALL Test Vector + +HTLC shares the [NUT-11][11] SIG_ALL message aggregation. The following `SwapRequest` has a valid preimage and a valid signature over the aggregated message. + +```json +{ + "inputs": [ + { + "amount": 8, + "id": "009a1f293253e41e", + "secret": "[\"HTLC\",{\"nonce\":\"da62796403af76c80cd6ce9153ed3746\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"],[\"sigflag\",\"SIG_ALL\"]]}]", + "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", + "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"signatures\":[\"5df34ba9ea8097b5c89c475d24e2feb5dd816c7486ad1a4f2f3afeef808f82a469859bc9075ab1bc1735e47b87f600301172f4ed5ba3feca80e13771d6f6fe6f\"]}" + } + ], + "outputs": [ + { + "amount": 8, + "id": "009a1f293253e41e", + "B_": "035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d" + }, + { + "amount": 2, + "id": "009a1f293253e41e", + "B_": "0288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6" + } + ] +} +``` + +The corresponding `msg_to_sign` (386 bytes, hex) and its SHA-256 hash are: + +``` +msg_to_sign = 43617368755f536967416c6c5369675f763100000000000000ef5b2248544c43222c7b226e6f6e6365223a226461363237393634303361663736633830636436636539313533656433373436222c2264617461223a2265633439313664643238666334633130643738653238376361356439636335316565316165373363626664653038633662333733323463626661616338626335222c2274616773223a5b5b227075626b657973222c22303237396265363637656639646362626163353561303632393563653837306230373032396266636462326463653238643935396632383135623136663831373938225d2c5b22736967666c6167222c225349475f414c4c225d5d7d5d0000002102698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904000000010800000021035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d0000000102000000210288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6 +sha256(msg_to_sign) = cd1a10eadc41f679104b542aee828ba22390fff80ac29747504c51a118792a58 +``` + +[11]: ../11.md