diff --git a/00.md b/00.md index d7184a73f..08f3f7256 100644 --- a/00.md +++ b/00.md @@ -38,7 +38,7 @@ Deterministically maps a message to a public key point on the secp256k1 curve, u - `Y` derived public key - `DOMAIN_SEPARATOR` constant byte string `b"Secp256k1_HashToCurve_Cashu_"` - `x` message to hash -- `counter` uint32 counter(byte order little endian) incremented from 0 until a point is found that lies on the curve +- `counter` uint32 counter (byte order little endian) incremented from 0 until a point is found that lies on the curve ## Protocol @@ -56,7 +56,7 @@ These specifications use elliptic curve public key cryptography over the field a ### Types -Values in these specification are one of: +Values in these specifications are one of: - Scalars: integers modulo the curve order `n` - Curve points: elements of the elliptic curve group (including the identity point at infinity) @@ -65,8 +65,8 @@ Values in these specification are one of: ### Byte Operations And Encodings - Concatenation `||` appends one byte sequence to another. -- All operations such as point addition, scalar multiplication, hashing, are performed on the underlying mathematical values, not on their serialised byte encodings. -- When a curve point is serialised as `hex_str`, it is the hex encoding of the SEC1 compressed public key bytes, unless a NUT specifies a different encoding. +- All operations such as point addition, scalar multiplication, hashing, are performed on the underlying mathematical values, not on their serialized byte encodings. +- When a curve point is serialized as `hex_str`, it is the hex encoding of the SEC1 compressed public key bytes, unless a NUT specifies a different encoding. - Integer byte order and fixed widths are defined by each NUT where relevant, if a NUT is silent, it must not be assumed. ### Hashing @@ -78,7 +78,7 @@ Values in these specification are one of: ### `BlindedMessage` -An encrypted ("blinded") secret and an amount is sent from `Alice` to `Bob` for [minting tokens][04] or for [swapping tokens][03]. A `BlindedMessage` is also called an _output_. +An encrypted ("blinded") secret and an amount are sent from `Alice` to `Bob` for [minting tokens][04] or for [swapping tokens][03]. A `BlindedMessage` is also called an _output_. ```json { @@ -113,11 +113,11 @@ A `Proof` is also called an _input_ and is generated by `Alice` from a `BlindSig "amount": int, "id": hex_str, "secret": str, - "C": hex_str, + "C": hex_str } ``` -`amount` is the amount of the `Proof`, `secret` is the secret message and is a utf-8 encoded string (the use of a 64 character hex string generated from 32 random bytes is recommended to prevent fingerprinting), `C` is the unblinded signature on `secret` (hex string), `id` is the [keyset id][02] of the mint public keys that signed the token (hex string). +`amount` is the amount of the `Proof`, `secret` is the secret message and is a UTF-8-encoded string (the use of a 64 character hex string generated from 32 random bytes is recommended to prevent fingerprinting), `C` is the unblinded signature on `secret` (hex string), `id` is the [keyset id][02] of the mint public keys that signed the token (hex string). ## 0.2 - Protocol @@ -274,7 +274,7 @@ For readability, the structure of a TokenV4 object is shown below in its equival ] }, ... - ], + ] } ``` @@ -349,11 +349,11 @@ cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMT #### Binary Token -Token can be transmitted in a binary format when applicable (for example when transmitting via NFC). -For this the serialised token is prepended with a prefix and a version byte. +Tokens can be transmitted in a binary format when applicable (for example when transmitting via NFC). +For this the serialized token is prepended with a prefix and a version byte. ``` -utf8("craw") || utf8() || +utf8("craw") || utf8() || ``` - Binary Encoding V4: `utf8("craw") || utf8("B") || cbor(token_v4_object)` diff --git a/01.md b/01.md index 5a85bd2d6..57039a0fa 100644 --- a/01.md +++ b/01.md @@ -16,7 +16,7 @@ Note that a mint can support multiple keysets at the same time but will only res ## Supported Currency Units -A mint may support any currency unit(s) they can mint ([NUT-04][04]) and melt([NUT-05][05]), either directly or indirectly, including: +A mint may support any currency unit(s) they can mint ([NUT-04][04]) and melt ([NUT-05][05]), either directly or indirectly, including: - `btc`: Bitcoin (Minor Unit: 8) - though use of the `sat` unit is generally preferred - `sat`: Bitcoin's Minor Unit (ie: `100,000,000 sat = 1 bitcoin`) @@ -39,7 +39,7 @@ A mint may support any currency unit(s) they can mint ([NUT-04][04]) and melt([N Keysets are generated by the mint. The mint is free to use any key generation method they like. Each keyset is identified by its keyset `id` which can be computed by anyone from its public keys (see [NUT-02][02]). -Keys in Keysets are maps of the form `{ : , : , ...}` for each `` of the amounts the mint `Bob` supports and the corresponding public key ``, that is `K_i` (see [NUT-00][00]). The mint **MUST** use the [compressed Secp256k1 public key format](https://learnmeabitcoin.com/technical/public-key#public-key-format) to represent its public keys. +Keys in Keysets are maps of the form `{ : , : , ...}` for each `` of the amounts the mint `Bob` supports and the corresponding public key ``, that is `K_i` (see [NUT-00][00]). The mint **MUST** use the [compressed Secp256k1 public key format](https://learnmeabitcoin.com/technical/public-key#public-key-format) to represent its public keys. ## Example @@ -65,7 +65,7 @@ Response `GetKeysResponse` of `Bob`: "unit": , "active": , "input_fee_ppk": , - "final_expiry": + "final_expiry": , "keys": { : , ... diff --git a/02.md b/02.md index 7a7d02b5b..5f5e83e08 100644 --- a/02.md +++ b/02.md @@ -28,13 +28,13 @@ Wallets **SHOULD** prioritize swaps with `Proofs` from inactive keysets (see [NU ### Fees -Keysets indicate the fee `input_fee_ppk` that is charged when a `Proof` of that keyset is spent as an input to a transaction. The fee is given in parts per thousand (ppk) per input measured in the `unit` of the keyset. The total fee for a transaction is the sum of all fees per input rounded up to the next larger integer (that that can be represented with the keyset). +Keysets indicate the fee `input_fee_ppk` that is charged when a `Proof` of that keyset is spent as an input to a transaction. The fee is given in parts per thousand (ppk) per input measured in the `unit` of the keyset. The total fee for a transaction is the sum of all fees per input rounded up to the next larger integer (that can be represented with the keyset). -As an example, we construct a transaction spending 3 inputs (`Proofs`) from a keyset with unit `sat` and `input_fee_ppk` of `100`. A fee of `100 ppk` means `0.1 sat` per input. The sum of the individual fees are 300 ppk for this transaction. Rounded up to the next smallest denomination, the mint charges `1 sat` in total fees, i.e. `fees = ceil(0.3) == 1`. In this case, the fees for spending 1-10 inputs is 1 sat, 11-20 inputs is 2 sat and so on. +As an example, we construct a transaction spending 3 inputs (`Proofs`) from a keyset with unit `sat` and `input_fee_ppk` of `100`. A fee of `100 ppk` means `0.1 sat` per input. The sum of the individual fees is 300 ppk for this transaction. Rounded up to the next smallest denomination, the mint charges `1 sat` in total fees, i.e. `fees = ceil(0.3) == 1`. In this case, the fees for spending 1-10 inputs is 1 sat, 11-20 inputs is 2 sat and so on. #### Wallet transaction construction -When constructing a transaction with ecash `inputs` (example: `/v1/swap` or `/v1/melt`), wallets **MUST** add fees to the inputs or, vice versa, subtract from the outputs. The mint checks the following equation: +When constructing a transaction with ecash `inputs` (example: `/v1/swap` or `/v1/melt/{method}`), wallets **MUST** add fees to the inputs or, vice versa, subtract from the outputs. The mint checks the following equation: ```python sum(inputs) - fees == sum(outputs) @@ -109,8 +109,8 @@ def derive_keyset_id_v1(keys: Dict[int, PublicKey]) -> str: return "00" + hashlib.sha256(pubkeys_concat).hexdigest()[:14] ``` -> [!CRITICAL] -> Wallet implementations should reject any attempt at importing new keysets which IDs +> [!CAUTION] +> Wallet implementations should reject any attempt at importing new keysets whose IDs > collide with any of the previously added keysets. ### Keyset Final Expiry @@ -228,7 +228,7 @@ A useful flow is: - If we don't have any keys from this mint yet, get all keys: `GET /v1/keys` and store them - Get all keysets with `GET /v1/keysets` -- For all new keyset returned here which we don't have yet, get it using `GET /v1/keys/{keyset_id}` and store it +- For each new keyset returned here which we don't have yet, get it using `GET /v1/keys/{keyset_id}` and store it - If any of the keysets has changed its `active` flag, update it in the db and use the keyset accordingly [00]: 00.md @@ -244,4 +244,4 @@ A useful flow is: [10]: 10.md [11]: 11.md [12]: 12.md -[TokenV3]: https://github.com/cashuBTC/nuts/blob/main/00.md#023---v3-tokens +[TokenV3]: https://github.com/cashubtc/nuts/blob/main/00.md#v3-tokens diff --git a/03.md b/03.md index 923db1cb2..460634a47 100644 --- a/03.md +++ b/03.md @@ -6,15 +6,15 @@ The swap operation is the most important component of the Cashu system. A swap operation consists of multiple inputs (`Proofs`) and outputs (`BlindedMessages`). Mints verify and invalidate the inputs and issue new promises (`BlindSignatures`). These are then used by the wallet to generate new `Proofs` (see [NUT-00][00]). -The swap operation can serve multiple use cases. The first use case is that `Alice` can use it to split her tokens to a target amount she needs to send to `Carol`, if she does not have the necessary amounts to compose the target amount in her wallet already. The second one is that `Carols`'s wallet can use it to receive tokens from `Alice` by sending them as inputs to the mint and receive new outputs in return. +The swap operation can serve multiple use cases. The first use case is that `Alice` can use it to split her tokens to a target amount she needs to send to `Carol`, if she does not have the necessary amounts to compose the target amount in her wallet already. The second one is that `Carol`'s wallet can use it to receive tokens from `Alice` by sending them as inputs to the mint and receive new outputs in return. ## Swap to send To make this more clear, we present an example of a typical case of sending tokens from `Alice` to `Carol`. -`Alice` has 64 sat in her wallet, composed of three `Proofs`, one worth 32 sat and another two worth 16 sat. She wants to send `Carol` 40 sat but does not have the necessary `Proofs` to compose the target amount of 40 sat. For that, `Alice` requests a swap from the mint and uses `Proofs` worth `[16, 16, 32]` as inputs and asks for new outputs worth `[8, 32, 8, 16]` totalling 64 sat. Notice that the first two tokens can now be combined to 40 sat. The `Proofs` that `Alice` sent `Bob` as inputs of the swap operation are now invalidated. +`Alice` has 64 sat in her wallet, composed of three `Proofs`, one worth 32 sat and another two worth 16 sat. She wants to send `Carol` 40 sat but does not have the necessary `Proofs` to compose the target amount of 40 sat. For that, `Alice` requests a swap from the mint and uses `Proofs` worth `[16, 16, 32]` as inputs and asks for new outputs worth `[8, 32, 8, 16]` totaling 64 sat. Notice that the first two tokens can now be combined to 40 sat. The `Proofs` that `Alice` sent `Bob` as inputs of the swap operation are now invalidated. -Note: In order to preserve privacy around the amount that a client might want to send to another user and keep the rest as change, the client **SHOULD** ensure that the list requested outputs is ordered by amount in ascending order. As an example of what to avoid, a request for outputs expressed like so: `[16, 8, 2, 64, 8]` might imply the client is preparing a payment for 26 sat; the client should instead order the list like so: `[2, 8, 8, 16, 64]` to mitigate this privacy leak to the mint. +Note: In order to preserve privacy around the amount that a client might want to send to another user and keep the rest as change, the client **SHOULD** ensure that the list of requested outputs is ordered by amount in ascending order. As an example of what to avoid, a request for outputs expressed like so: `[16, 8, 2, 64, 8]` might imply the client is preparing a payment for 26 sat; the client should instead order the list like so: `[2, 8, 8, 16, 64]` to mitigate this privacy leak to the mint. ## Swap to receive @@ -33,7 +33,7 @@ With the data being of the form `PostSwapRequest`: ```json { "inputs": , - "outputs": , + "outputs": } ``` @@ -78,6 +78,149 @@ If successful, `Bob` will respond with a `PostSwapResponse` } ``` +## Mint-rerandomized outputs (V2) + +An opt-in swap can authorize original blinded outputs while having the mint +reserve and sign fresh, derived points. This allows a pre-signed multi-party +swap to execute even if an original blinded output has already been signed +separately. This section specifies V2 secp256k1 swaps only; ordinary swaps, +melts, and V3 are unchanged. + +A wallet opts in by adding `"rerandomize": true` to `PostSwapRequest`: + +```json +{ + "inputs": "", + "outputs": "", + "rerandomize": true +} +``` + +The arrays above indicate the existing NUT-03 types; the string placeholders +are not valid proof or output data. An omitted `rerandomize` or an explicit +`false` requests an ordinary swap. An opted-in request **MUST** have inputs +locked with [NUT-11][11] `SIG_ALL` (including [NUT-14][14] HTLC conditions) +and **MUST** use the distinct authorization digest defined below. A mint +**MUST NOT** accept an ordinary `SIG_ALL` signature for an opted-in swap, or +silently execute it as an ordinary swap. + +### Authorization of original outputs + +For `rerandomize: true`, the `SIG_ALL` message commits to each original +blinded point through its SHA-256 hash. Define +`frame(x) = uint32_be(len(x)) || x`, where `len(x)` is the number of **bytes** +in `x`. The message starts with `frame(empty)` as a swap-context marker, then +contains for each input `frame(UTF8(secret))` and `frame(C_bytes)`, followed +by `frame(amount_bytes)` and the **unframed** 32-byte +`SHA256(B_bytes)` for each **original** output, in request order: + +```text +message = frame(empty) + || frame(UTF8(secret_0)) || frame(C_0) || ... + || frame(amount_0) || SHA256(B_0) || ... +digest = TaggedSHA256("Cashu_SigAllRerandomized_v1", message) +``` + +`C_bytes` and `B_bytes` are raw 33-byte compressed secp256k1 points decoded +from their hex fields (not the UTF-8 hex strings). `SHA256(B_bytes)` hashes +exactly those original 33 bytes: it is neither a hash of the hexadecimal +text nor a hash of the mint's derived `B'_i`. As it is always 32 bytes, no +length prefix precedes the output hash. `amount_bytes` is the +minimal unsigned big-endian representation of the integer amount, with no +leading zero bytes; the integer zero encodes as the empty byte string. +`TaggedSHA256(tag, message)` means +`SHA256(SHA256(tag) || SHA256(tag) || message)`. The signer **MUST** sign the +32-byte `digest` directly as a BIP-340 Schnorr message; the verifier +**MUST NOT** hash it a second time. The frame for the empty context contributes +four zero bytes. The domain tag identifies the opted-in mode; the `rerandomize` +field itself is not another frame. Changing an input or the original output +order changes the authorization. The existing ordinary swap/melt `SIG_ALL` +encoding and `SIG_INPUTS` rules remain unchanged. Mints **MUST** verify this +exact transcript and **MUST NOT** accept an alternate output encoding as a +fallback. + +See the [test vectors][03-tests] for the exact bytes and derived points. + +### Derivation and issuance + +The mint **MUST** generate an unpredictable fresh 32-byte `R` for this swap. +Let `tag` be the UTF-8 bytes of +`Cashu_OutputRerandomization_secp256k1_v1`. Starting at counter zero, compute +`candidate = TaggedSHA256(tag, R || uint32_be(counter))`; accept the first +candidate interpreted as a big-endian integer in `[1, n - 1]` as scalar `t`, +where `n` is the secp256k1 group order. Invalid or zero candidates **MUST** +be skipped. The same `t` **MUST** be used for every output in the swap. + +For each original output point `B_i = Y_i + r_i G`, the mint computes: + +```text +B'_i = B_i + t G +C'_i = a_i B'_i +``` + +Here `G` is the secp256k1 generator, `Y_i` is the proof's hash-to-curve +point, `r_i` is the wallet's original blinding factor, and `a_i` is the mint +key for that output's keyset and amount (`A_i = a_i G`). The mint **MUST** +reject duplicate original output points within the request, but **MUST** +check, reserve, and sign only the corresponding `B'_i` for output uniqueness. +A signature previously issued on an original `B_i` **MUST NOT** alone cause +the swap to fail. If addition produces the point at infinity, or a derived +point is already reserved or signed, the mint **MUST** abandon that `R` and +retry with fresh `R` or reject the swap without spending its inputs. Output +amounts, keyset IDs, and order remain the same; the returned signatures and +[NUT-09][09] records correspond to the derived points. + +### Commit and lost-response recovery + +The mint **MUST** commit the signatures and spent-input recovery value `R` +atomically, associating the same `R` with every spent input in the swap. It +**MUST NOT** reveal `R` while the swap is pending or if it fails. Only after +commit, `PostSwapResponse` includes `rerandomization_r`, the 32-byte `R` +encoded as exactly 64 lowercase hexadecimal characters: + +```json +{ + "signatures": "", + "rerandomization_r": "4242424242424242424242424242424242424242424242424242424242" +} +``` + +An ordinary swap response **MUST** omit `rerandomization_r`. A mint +**MUST** retain the committed `R` for as long as it supports recovery of the +associated signatures, and expose it through spent-input [NUT-07][07] state +and [NUT-17][17] proof-state notifications. After a lost response, the wallet +**SHOULD** check that all input states are `SPENT` and return the same `R` +before recovery; if any is still `PENDING`, it cannot infer completion. With +committed `R`, it can reconstruct each `B'_i` from its saved original output, +use [NUT-09][09] restore for the corresponding signatures, and unblind +normally: + +```text +C_i = C'_i - t A_i +P_i = C_i - r_i A_i = C'_i - (r_i + t) A_i +``` + +`P_i` is an ordinary, transferable V2 proof. If the wallet needs a +transferable [NUT-12][12] DLEQ, its effective blinding factor is +`(r_i + t) mod n`; the mint's DLEQ on `(B'_i, C'_i)` does not change. The +wallet needs to retain its original output points and blinding factors to +perform lost-response recovery; this is not seed-only [NUT-13][13] recovery. +With independently sampled nonzero `r_i` and `t`, the chance that +`(r_i + t) mod n = 0` is approximately **2⁻²⁵⁶ per output** (at most about +`m / 2²⁵⁶` for `m` outputs). This is negligible in practice; the rare +representation case for an optional transferable DLEQ is addressed in +[NUT-12][12]. + +### Mint info setting + +The mint **MUST** advertise `nuts["3"] = {"rerandomize": true}` through +[NUT-06][06] only when its active backend can atomically persist `R` for all +spent inputs and durably return it after a restart, together with the +associated [NUT-09][09] signatures. Absent or `false` means wallets +**MUST NOT** assume support or silently downgrade an opted-in pre-signed swap. +The setting denotes the complete V2 authorization, issuance, and recovery +behavior defined above, not merely ordinary NUT-03 support. + [00]: 00.md [01]: 01.md [02]: 02.md @@ -91,4 +234,8 @@ If successful, `Bob` will respond with a `PostSwapResponse` [10]: 10.md [11]: 11.md [12]: 12.md +[13]: 13.md +[14]: 14.md +[17]: 17.md +[03-tests]: tests/03-tests.md [errors]: error_codes.md diff --git a/04.md b/04.md index 9d3b29825..e4636e3b0 100644 --- a/04.md +++ b/04.md @@ -10,7 +10,7 @@ Minting tokens is a two-step process: requesting a mint quote and minting new to ## Supported methods -Method-specific NUTs describe how to handle different payment methods. The currently specified models are: +Method-specific NUTs describe how to handle different payment methods. The currently specified methods are: - [NUT-23][23] for bolt11 Lightning invoices - [NUT-25][25] for bolt12 Lightning offers diff --git a/05.md b/05.md index 1bf20ae81..e2f99c9a6 100644 --- a/05.md +++ b/05.md @@ -10,7 +10,7 @@ Melting tokens is the opposite of minting tokens (see [NUT-04][04]). Like mintin ## Supported methods -Method-specific NUTs describe how to handle different payment methods. The currently specified models are: +Method-specific NUTs describe how to handle different payment methods. The currently specified methods are: - [NUT-23][23] for bolt11 Lightning invoices - [NUT-25][25] for bolt12 Lightning offers @@ -74,7 +74,7 @@ Where - `quote` is the quote ID string in UUID v7 format with all 74 variable bits generated by a CSPRNG - `request` is the method-specific payment routing target -- `amount` and `unit` the amount and unit that need to be provided +- `amount` and `unit` are the amount and unit that need to be provided - `fee_reserve` is the additional fee reserve for using the method (the wallet provides proofs covering at least `amount + fee_reserve + fee`, where `fee` is the keyset input fee per [NUT-02][02]) - `method` is the payment method of the quote - `expiry` is the Unix timestamp until which the melt quote is valid. diff --git a/06.md b/06.md index 9732ca42c..90d508521 100644 --- a/06.md +++ b/06.md @@ -47,6 +47,9 @@ With the mint's response being of the form `GetInfoResponse`: "tos_url": "https://mint.host/tos", "max_array_length": 1000, "nuts": { + "3": { + "rerandomize": true + }, "4": { "methods": [ { @@ -102,6 +105,10 @@ With the mint's response being of the form `GetInfoResponse`: - (optional) `max_array_length` is the maximum length the mint accepts for any array in a request, such as `inputs`, `outputs` or `Ys`. Wallets **SHOULD** split larger requests into batches no bigger than this. - (optional) `nuts` indicates each NUT specification that the mint supports and its settings. The settings are defined in each NUT separately. +The optional `nuts["3"].rerandomize` setting is defined by [NUT-03][03]. +Its presence with value `true` advertises complete mint-rerandomized V2 +swap and recovery support, not merely support for ordinary swaps. + With curl: ```bash diff --git a/07.md b/07.md index 810469449..0bbf0e773 100644 --- a/07.md +++ b/07.md @@ -8,25 +8,25 @@ With the token state check, wallets can ask the mint whether a specific proof is already spent and whether it is in-flight in a transaction. Wallets can also request the witness data that was used to spend a proof. -### Token states +## Token states A proof can be in one of the following states -- A proof is `PENDING` if it is being processed in a transaction (in an ongoing payment). A `PENDING` proof cannot be used in another transaction until it is `live` again. +- A proof is `PENDING` if it is being processed in a transaction (in an ongoing payment). A `PENDING` proof cannot be used in another transaction until it is `UNSPENT` again. - A proof is `SPENT` if it has been redeemed and its secret is in the list of spent secrets of the mint. - A proof is `UNSPENT` if the mint has no record of it being pending or spent **Note:** Before deleting spent proofs from their database, wallets can check if the proof is `SPENT` to make sure that they don't accidentally delete an unspent proof. Beware that this behavior can make it easier for the mint to correlate the sender to the receiver. -**Important:** Mints **MUST** remember which proofs are currently `PENDING` to avoid reuse of the same token in multiple concurrent transactions. This can be achieved with for example mutex lock whose key is the `Proof`'s `Y`. +**Important:** Mints **MUST** remember which proofs are currently `PENDING` to avoid reuse of the same token in multiple concurrent transactions. This can be achieved with, for example, a mutex lock whose key is the `Proof`'s `Y`. ## Use cases -#### Example 1: Ecash transaction +### Example 1: Ecash transaction When `Alice` prepares a token to be sent to `Carol`, she can mark these tokens in her database as _pending_. She can then, periodically or upon user input, check with the mint if the token is `UNSPENT` or whether it has been redeemed by `Carol` already, i.e., is `SPENT`. If the proof is not spendable anymore (and, thus, has been redeemed by `Carol`), she can safely delete the proof from her database. -#### Example 2: Lightning payments +### Example 2: Lightning payments If `Alice`'s melt operation takes a long time to complete (for example if she requests a very slow Lightning payment) and she closes her wallet in the meantime, the next time she comes online, she can check all proofs marked as _pending_ in her database to determine whether the payment is still in flight (mint returns `PENDING`), it has succeeded (mint returns `SPENT`), or it has failed (mint returns `UNSPENT`). @@ -42,7 +42,7 @@ With the data being of the form `PostCheckStateRequest`: ```json { - "Ys": , + "Ys": } ``` @@ -59,6 +59,7 @@ Where the elements of the array in `Ys` are the hexadecimal representation of th "Y": , "state": , "witness": , + "rerandomization_r": }, ... ] @@ -70,6 +71,12 @@ The elements of the `states` array MUST be returned in the same order as the cor - `Y` corresponds to the `Proof` checked in the request. - `state` is an enum string field with possible values `"UNSPENT"`, `"PENDING"`, `"SPENT"` - `witness` is the serialized witness data that was used to spend the `Proof` if the token has a [NUT-10][10] spending condition that requires a witness such as in the case of P2PK ([NUT-11][11]) or HTLCs ([NUT-14][14]). +- `rerandomization_r`, when present, is the 32-byte mint randomness `R` encoded + as exactly 64 lowercase hexadecimal characters. For a committed + [NUT-03][03] mint-rerandomized swap, the mint **MUST** provide the same value + for every spent input in that swap, including after a restart. For `PENDING` + and `UNSPENT` states, and for spent proofs without this recovery data, it + **MUST** be omitted. Revealing a shared `R` links the swap's spent inputs. With curl: @@ -101,7 +108,7 @@ Where `Y` belongs to the provided `Proof` to check in the request, `state` indic ## Mint info setting -The [NUT-06][06] `MintMethodSetting` indicates support for this feature: +The [NUT-06][06] `nuts` object indicates support for this feature: ```json { diff --git a/08.md b/08.md index 98fa34fbb..0f56d5901 100644 --- a/08.md +++ b/08.md @@ -12,7 +12,7 @@ The problem is also described in [this gist](https://gist.github.com/callebtc/a6 ## Description -Before requesting a Lightning payment as described in [NUT-05][05], `Alice` produces a number of `BlindedMessage` which are similar to ordinary blinded messages but their value is yet to be determined by the mint `Bob` and are thus called _blank outputs_. The number of necessary blank outputs is `max(ceil(log2(fee_reserve)), 1)` which ensures that there is at least one output if there is any fee. If the `fee_reserve` is `0`, then the number of blank outputs is `0` as well. The blank outputs will contain the overpaid fees that will be returned by the mint to the wallet. +Before requesting a Lightning payment as described in [NUT-05][05], `Alice` produces a number of `BlindedMessage`s which are similar to ordinary blinded messages but their value is yet to be determined by the mint `Bob` and are thus called _blank outputs_. The number of necessary blank outputs is `max(ceil(log2(fee_reserve)), 1)` which ensures that there is at least one output if there is any fee. If the `fee_reserve` is `0`, then the number of blank outputs is `0` as well. The blank outputs will contain the overpaid fees that will be returned by the mint to the wallet. This code calculates the number of necessary blank outputs in Python: @@ -26,21 +26,21 @@ def calculate_number_of_blank_outputs(fee_reserve_sat: int) -> int: ## Example -The wallet wants to pay an invoice with `amount := 100 000 sat` and determines by asking the mint that `fee_reserve` is `1000 sats`. The wallet then provides `101 000 sat` worth of proofs and 10 blank `outputs` to make the payment (since `ceil(log2(1000))=ceil(9.96..)=10`). The mint pays the invoice and determines that the actual fee was `100 sat`, i.e, the overpaid fee to return is `fee_return = 900 sat`. The mint splits the amount `900` into summands of `2^n` which is `4, 128, 256, 512`. The mint inserts these amounts into the blank `outputs` it received form the wallet and generates 4 new promises. The mint then returns these `BlindSignature`s to the wallet together with the successful payment status. +The wallet wants to pay an invoice with `amount := 100 000 sat` and determines by asking the mint that `fee_reserve` is `1000 sats`. The wallet then provides `101 000 sat` worth of proofs and 10 blank `outputs` to make the payment (since `ceil(log2(1000))=ceil(9.96..)=10`). The mint pays the invoice and determines that the actual fee was `100 sat`, i.e., the overpaid fee to return is `fee_return = 900 sat`. The mint splits the amount `900` into summands of `2^n` which is `4, 128, 256, 512`. The mint inserts these amounts into the blank `outputs` it received from the wallet and generates 4 new promises. The mint then returns these `BlindSignature`s to the wallet together with the successful payment status. ## Wallet flow -The wallet asks the mint for the `fee_reserve` for paying a specific bolt11 invoice of value `amount` by calling `POST /v1/melt/quote` as described in [NUT-05][05]. The wallet then provides a `PostMeltBolt11Request` to `POST /v1/melt/bolt11` that has (1) proofs of the value `amount+fee+fee_reserve`, (2) the bolt11 invoice to be paid, and finally, as a new entry, (3) a field `outputs` that has `n_blank_outputs` blinded messages that are generated before the payment attempt to receive potential overpaid fees back to her. +The wallet asks the mint for the `fee_reserve` for paying a specific bolt11 invoice of value `amount` by calling `POST /v1/melt/quote/bolt11` as described in [NUT-05][05]. The wallet then provides a `PostMeltBolt11Request` to `POST /v1/melt/bolt11` that has (1) proofs of the value `amount+fee+fee_reserve`, (2) the bolt11 invoice to be paid, and finally, as a new entry, (3) a field `outputs` that has `n_blank_outputs` blinded messages that are generated before the payment attempt to receive potential overpaid fees back to her. ## Mint flow -Here we describe how the mint generates `BlindSignature`s for the overpaid fees. The mint `Bob` returns in `PostMeltQuoteBolt11Response` the field `change` **ONLY IF** `Alice` has previously provided `outputs` for the change **AND** if the the `inputs` provided were greater then the `total_amount_paid - fees`. +Here we describe how the mint generates `BlindSignature`s for the overpaid fees. The mint `Bob` returns in `PostMeltQuoteBolt11Response` the field `change` **ONLY IF** `Alice` has previously provided `outputs` for the change **AND** if the `inputs` provided were greater than the `total_amount_paid - fees`. If the `overpaid_fees = input_amount - fees - total_paid` is positive, `Bob` decomposes it to amounts supported by the keyset, typically `2^n`, and imprints them into the `blank_outputs` provided by `Alice`. `Bob` then signs these blank outputs (now with the imprinted amounts) and thus generates `BlindSignature`s. `Bob` then returns a payment status to the wallet, and, in addition, all blind signatures it generated for the overpaid fees. -Importantly, while `Bob` does not necessarily return the same number of blind signatures as it received blank outputs from `Alice` (since some of them may be of value 0), `Bob` **MUST** return the all blank signatures with a value greater than 0 in the same order as the blank outputs were received and should omit all blind signatures with value 0. For example, if `Bob` receives 10 blank outputs but the overpaid fees only occupy 4 blind signatures, `Bob` will only return these 4 blind signatures with the appropriate imprinted amounts and omit the remaining 6 blind signatures with value 0. Due to the well-defined order of the returned blind signatures, `Alice` can map the blind signatures returned from `Bob` to the blank outputs it provided so that she can further apply the correct unblinding operations on them. +Importantly, while `Bob` does not necessarily return the same number of blind signatures as it received blank outputs from `Alice` (since some of them may be of value 0), `Bob` **MUST** return all blind signatures with a value greater than 0 in the same order as the blank outputs were received and should omit all blind signatures with value 0. For example, if `Bob` receives 10 blank outputs but the overpaid fees only occupy 4 blind signatures, `Bob` will only return these 4 blind signatures with the appropriate imprinted amounts and omit the remaining 6 blind signatures with value 0. Due to the well-defined order of the returned blind signatures, `Alice` can map the blind signatures returned from `Bob` to the blank outputs it provided so that she can further apply the correct unblinding operations on them. ## Example @@ -60,7 +60,7 @@ With the data being of the form `PostMeltBolt11Request`: } ``` -where the new `output` field carries the `BlindMessages`. +where the new `outputs` field carries the `BlindedMessages`. The mint `Bob` then responds with a `PostMeltQuoteBolt11Response`: @@ -93,13 +93,13 @@ curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \ "amount": 4, "id": "009a1f293253e41e", "secret": "429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5", - "C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011", + "C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011" }, { "amount": 8, "id": "009a1f293253e41e", "secret": "4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad", - "C": "0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9", + "C": "0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9" } ], "outputs": [ @@ -136,7 +136,7 @@ The field `change` is an array of `BlindSignatures` that account for the overpai ## Mint info setting -The [NUT-06][06] `MintMethodSetting` indicates support for this feature: +The [NUT-06][06] `nuts` object indicates support for this feature: ```json { diff --git a/09.md b/09.md index 73eee23a5..014b88acc 100644 --- a/09.md +++ b/09.md @@ -20,7 +20,7 @@ With the data being of the form `PostRestoreRequest`: ```json { - "outputs": + "outputs": } ``` @@ -30,16 +30,28 @@ The mint `Bob` then responds with a `PostRestoreResponse`. ```json { - "outputs": , + "outputs": , "signatures": } ``` The returned arrays `outputs` and `signatures` are of the same length and for every entry `outputs[i]`, there is a corresponding entry `signatures[i]`. +### Restoring mint-rerandomized swap outputs + +For a [NUT-03][03] swap with `rerandomize: true`, the mint stores and returns +the **derived** output points `B'_i`, not the original `B_i`. A wallet whose +swap response was lost **SHOULD** obtain `rerandomization_r` from the spent +inputs through [NUT-07][07], derive the shared `t`, and submit its saved +original outputs with `B_i` replaced by `B'_i = B_i + tG` to this endpoint. +The returned `outputs[i]` and `signatures[i]` **MUST** correspond to each +other as above; the wallet matches those returned pairs to its saved outputs +before unblinding. The returned mint [NUT-12][12] DLEQ, if present, is +verified against `(B'_i, C'_i)` without modifying it. + ## Mint info setting -The [NUT-06][06] `MintMethodSetting` indicates support for this feature: +The [NUT-06][06] `nuts` object indicates support for this feature: ```json { @@ -52,7 +64,9 @@ The [NUT-06][06] `MintMethodSetting` indicates support for this feature: [00]: 00.md [02]: 02.md [03]: 03.md +[06]: 06.md [07]: 07.md [09]: 09.md [11]: 11.md +[12]: 12.md [13]: 13.md diff --git a/10.md b/10.md index 8c2c436c1..347f2fb38 100644 --- a/10.md +++ b/10.md @@ -12,7 +12,7 @@ In this NUT, we define a well-known format of `secret` that can be used to expre Spending conditions are enforced by the mint which means that, upon encountering a `Proof` where `Proof.secret` can be parsed into the well-known format, the mint can require additional conditions to be met. -Caution: If the mint does not support spending conditions or a specific `kind` of spending condition, proofs may be treated as a regular anyone-can-spend tokens. Applications need to make sure to check whether the mint supports a specific `kind` of spending condition by checking the mint's [info][06] endpoint. +Caution: If the mint does not support spending conditions or a specific `kind` of spending condition, proofs may be treated as regular anyone-can-spend tokens. Applications need to make sure to check whether the mint supports a specific `kind` of spending condition by checking the mint's [info][06] endpoint. ## Basic components @@ -21,11 +21,11 @@ An ecash transaction, i.e., a [swap][03] or a [melt][05] operation, with a spend - Inputs referring to the `Proofs` being spent - `Secret` containing the rules for unlocking a `Proof` - Additional witness data satisfying the unlock conditions such as signatures -- Outputs referring to the `BlindMessages` with new unlock conditions to which the `Proofs` are spent to +- Outputs referring to the `BlindedMessages` with new unlock conditions to which the `Proofs` are spent Spending conditions are defined for each individual `Proof` and not on a transaction level that can consist of multiple `Proofs`. Similarly, spending conditions must be satisfied by providing signatures or additional witness data for each `Proof` separately. For a transaction to be valid, all `Proofs` in that transaction must be unlocked successfully. -New `Secret`s of the outputs to which the inputs are spent to are provided as `BlindMessages` which means that they are blind-signed and not visible to the mint until they are actually spent. +New `Secret`s of the outputs to which the inputs are spent are provided as `BlindedMessages` which means that they are blind-signed and not visible to the mint until they are actually spent. ## Well-known Secret @@ -39,7 +39,7 @@ kind , { "nonce": , "data": , - "tags": [[ "key", "value1", "value2", ...], ... ], // (optional) + "tags": [[ "key", "value1", "value2", ...], ... ] // (optional) } ] ``` @@ -51,7 +51,7 @@ kind , ### Tag format -The optional `tags` field, is an array of arrays of non-empty strings. +The optional `tags` field is an array of arrays of non-empty strings. Each individual tag is an array of **ONE or more strings**. The first element of the tag array is known as the tag _name_ or _key_ and the subsequent string(s) are the _tag value(s)_. @@ -76,7 +76,7 @@ Example use cases of this secret format are ## Mint info setting -The [NUT-06][06] `MintMethodSetting` indicates support for this feature: +The [NUT-06][06] `nuts` object indicates support for this feature: ```json { diff --git a/11.md b/11.md index 77394e66d..733e913e1 100644 --- a/11.md +++ b/11.md @@ -143,10 +143,20 @@ A swap contains `inputs` and `outputs` (see [NUT-03][03]). To provide a valid si If a swap transaction has `n` inputs and `m` outputs, the message to sign becomes: ``` -msg = secret_0 || C_0 || ... || secret_n || C_n || amount_0 || B_0 || ... || amount_m || B_m +msg = secret_0 || C_0 || ... || secret_n || C_n || amount_0 || B_0 || ... || amount_m || B_m ``` -Here, `||` denotes string concatenation. The `C` of each input and `B_` of each output are **hex strings** and `amount` is a UTF-encoded string. +Here, `||` denotes string concatenation. The `C` of each input and `B_` of each output are **hex strings** and `amount` is a UTF-8-encoded string. + +For a [NUT-03][03] swap that sets `rerandomize: true`, this ordinary swap +aggregation **MUST NOT** be used. That swap uses the distinct +`Cashu_SigAllRerandomized_v1` digest: input fields and output amounts are +length-framed, while each **original** compressed output point contributes +its unframed 32-byte SHA-256 hash, as specified in [NUT-03][03]. Every input +**MUST** satisfy the existing +`SIG_ALL` requirements, and its required signatures **MUST** verify under +that separate digest. Ordinary swap and melt aggregation, `SIG_INPUTS`, +signature thresholds, and permitted locktime/refund pathways are unchanged. ##### Aggregation for `melt` @@ -158,7 +168,7 @@ If a melt transaction has `n` inputs, `m` blank outputs, and a quote ID `quote_i 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. +Here, `||` denotes string concatenation. The `C` of each input and `B_` of each output are **hex strings** and `amount` is a UTF-8-encoded string. ## Locktime Tag @@ -200,7 +210,7 @@ If the `pubkeys` tag is present, the `Proof` is spendable only if a valid signat If the `n_sigs` tag is a positive integer, the mint will require at least `n_sigs` of those public keys to provide a valid signature. -If the number of public keys with valid signatures is greater or equal to the number specified in `n_sigs` (or `1` if `n_sigs` is not present), the transaction is valid. The signatures are provided in an array of strings in the `P2PKWitness` object. +If the number of public keys with valid signatures is greater than or equal to the number specified in `n_sigs` (or `1` if `n_sigs` is not present), the transaction is valid. The signatures are provided in an array of strings in the `P2PKWitness` object. Expressed as an "n-of-m" scheme, `n = n_sigs` is the number of required signatures and `m = 1 (data field) + count(pubkeys tag keys)` is the total number of public keys that _could_ sign. @@ -218,7 +228,7 @@ Refund Multisig allows proofs to be _additionally spendable_ by a separate set o If the `n_sigs_refund` tag is a positive integer, the mint will require at least `n_sigs_refund` of those `refund` public keys to provide a valid signature. -If the number of `refund` public keys with valid signatures is greater or equal to the number specified in `n_sigs_refund` (or `1` if `n_sigs_refund` is not present), the transaction is valid. The signatures are provided in an array of strings in the `P2PKWitness` object. +If the number of `refund` public keys with valid signatures is greater than or equal to the number specified in `n_sigs_refund` (or `1` if `n_sigs_refund` is not present), the transaction is valid. The signatures are provided in an array of strings in the `P2PKWitness` object. Expressed as an "n-of-m" scheme, `n = n_sigs_refund` is the number of required signatures and `m = count(refund tag keys)` is the total number of `refund` keys that _could_ sign. @@ -263,7 +273,7 @@ The signature flag `sigflag` indicates that signatures are necessary on the `inp Public keys **MUST** use the [compressed Secp256k1 public key format](https://learnmeabitcoin.com/technical/public-key#public-key-format). -Each key **MUST** appear at most **ONCE** per [multi-signature](#Multisig) pathway. The same key **MAY** appear in both pathways. +Each key **MUST** appear at most **ONCE** per [multi-signature](#multisig) pathway. The same key **MAY** appear in both pathways. Keys are compared using their lowercase x-coordinate (`02` or `03` y-parity prefix ignored). @@ -306,7 +316,7 @@ The following use cases are unlocked using P2PK: ## Mint info setting -The [NUT-06][06] `MintMethodSetting` indicates support for this feature: +The [NUT-06][06] `nuts` object indicates support for this feature: ```json { diff --git a/12.md b/12.md index a4f17866d..79ec20602 100644 --- a/12.md +++ b/12.md @@ -8,7 +8,7 @@ In this document, we present an extension of Cashu's crypto system to allow a us # The DLEQ proof -The purpose of this DLEQ is to prove that the mint has used the same private key `a` for creating its public key `A` ([NUT-01][01]) and for signing the BlindedMessage `B'`. `Bob` returns the DLEQ proof additional to the blind signature `C'` for a mint or swap operation. +The purpose of this DLEQ is to prove that the mint has used the same private key `a` for creating its public key `A` ([NUT-01][01]) and for signing the BlindedMessage `B'`. `Bob` returns the DLEQ proof in addition to the blind signature `C'` for a mint or swap operation. The complete DLEQ proof reads @@ -47,7 +47,7 @@ where: ### Hash function -The hash function `hash(x: ) -> bytes` generates a deterministic SHA256 hash for a given input list of `PublicKey`. The uncompressed (32+32+1)-byte hexadecimal representations (130 characters) of each `PublicKey` is concatenated before taking the SHA256 hash. +The hash function `hash(x: Array[PublicKey]) -> bytes` generates a deterministic SHA256 hash for a given input list of `PublicKey`. The uncompressed (32+32+1)-byte hexadecimal representations (130 characters) of each `PublicKey` are concatenated before taking the SHA256 hash. ```python def hash_e(*publickeys: PublicKey) -> bytes: @@ -65,7 +65,7 @@ def hash_e(*publickeys: PublicKey) -> bytes: ### Mint to user: DLEQ in `BlindSignature` -The mint produces these DLEQ proofs when returning `BlindSignature`'s in the responses for minting ([NUT-04][04]) and swapping ([NUT-03][03]) tokens. The `BlindSignature` object is extended in the following way to include the DLEQ proof: +The mint produces these DLEQ proofs when returning `BlindSignature`s in the responses for minting ([NUT-04][04]) and swapping ([NUT-03][03]) tokens. The `BlindSignature` object is extended in the following way to include the DLEQ proof: ```json { @@ -149,9 +149,28 @@ R1 = ... (same as Alice) If a DLEQ proof is included in a received token, wallets **MUST** verify the proof. +### Mint-rerandomized V2 swaps + +For a [NUT-03][03] mint-rerandomized swap, the mint's unchanged DLEQ proves +the relation between `A = aG` and the **derived** output pair +`(B'_i, C'_i)`, where `B'_i = B_i + tG`. The receiving wallet **MUST** +verify that DLEQ against the derived pair if one is included in the mint's +response or [NUT-09][09] restore result. The ordinary proof signature is +`C_i = C'_i - (r_i + t)A_i`. + +To carry the unchanged DLEQ into an ordinary transferable `Proof`, the +wallet **MUST** record `(r_i + t) mod n` in `Proof.dleq.r` instead of its +original `r_i`. A recipient then reconstructs the same `(B'_i, C'_i)` +using the equations above. In the exceptional case `(r_i + t) mod n = 0`, +the wallet **MUST NOT** encode an invalid nonzero blinding scalar; it +**MAY** transfer the otherwise ordinary proof without a DLEQ instead. +For independently sampled nonzero `r_i` and `t`, this case has probability +about **2⁻²⁵⁶ per output**, so it is not a practical obstacle to using +transferable DLEQs. + ## Mint info setting -The [NUT-06][06] `MintMethodSetting` indicates support for this feature: +The [NUT-06][06] `nuts` object indicates support for this feature: ```json { diff --git a/13.md b/13.md index 6f03eeb24..56e717c99 100644 --- a/13.md +++ b/13.md @@ -12,7 +12,7 @@ In this document, we describe the process that allows wallets to recover their e An ecash token, or a `Proof`, consists of a `secret` generated by the wallet, and a signature `C` generated by the wallet and the mint in collaboration. Here, we describe how wallets can deterministically generate the `secrets` and blinding factors `r` necessary to generate the signatures `C`. -The wallet generates a `seed` derived from a 12-word [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) `mnemonic` seed phrase that the user stores in a secure place. The wallet uses the `seed`, to derive deterministic values for the `secret` and the blinding factors `r` for every new ecash token that it generates. +The wallet generates a `seed` derived from a 12-word [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) `mnemonic` seed phrase that the user stores in a secure place. The wallet uses the `seed` to derive deterministic values for the `secret` and the blinding factors `r` for every new ecash token that it generates. In order to do this, the wallet keeps track of a `counter_k` for each `keyset_k` it uses. The index `k` indicates that the wallet **MUST** keep track of a separate counter for each keyset `k` it uses. The wallet **MUST** keep track of multiple keysets for every mint it interacts with. @@ -21,11 +21,11 @@ In order to do this, the wallet keeps track of a `counter_k` for each `keyset_k` The secret derivation method depends on the keyset ID version the wallet derives secrets and blinding factors for. - **Keyset V2** (keyset IDs starting with `01`): Use [HMAC-SHA256 Derivation](#hmac-sha256-derivation) -- **Keyset V1 (deprecated)** (keyset IDs starting with `00`): Use [BIP32 Legacy Derivation](#legacy-derivation-deprecated) +- **Keyset V1 (deprecated)** (keyset IDs starting with `00`): Use [BIP32 Legacy Derivation](#legacy-derivation-for-keyset-version-00) ### HMAC-SHA256 Derivation -For keysets with version byte `01`, the wallet uses `counter_k`, `seed` and `keyset_id` as inputs to a Key Derivation Function (KDF) which output is used to derive `secret` and `r`. +For keysets with version byte `01`, the wallet uses `counter_k`, `seed` and `keyset_id` as inputs to a Key Derivation Function (KDF) whose output is used to derive `secret` and `r`. The HMAC-SHA256 KDF is built as the following: @@ -49,7 +49,7 @@ The following BIP32 derivation path for derivation of the key: `m/129373'/10'/0' - 10': Account for generating private keys for usage in P2PK. - `{counter}` is an incrementing, non-hardened BIP32 child index. -This will allow wallets to swap proof that are still locked to a public key during a restore process. +This will allow wallets to swap proofs that are still locked to a public key during a restore process. ### Code Examples @@ -238,10 +238,10 @@ This results in the following derivation paths: ``` secret_derivation_path = `m/129372'/0'/{keyset_k_int}'/{counter_k}'/0` -r_derivation_path = `m/129372'/0'/{keyset_id_k_int}'/{counter_k}'/1` +r_derivation_path = `m/129372'/0'/{keyset_k_int}'/{counter_k}'/1` ``` -Here, `{keyset_k_int}` and `{counter_k}` are the only variables that can change. `keyset_id_k_int` is an integer representation (see below) of the keyset ID the token is generated with. This means that the derivation path is unique for each keyset. Note that the coin type is always `0'`, independent of the unit of the ecash. +Here, `{keyset_k_int}` and `{counter_k}` are the only variables that can change. `keyset_k_int` is an integer representation (see below) of the keyset ID the token is generated with. This means that the derivation path is unique for each keyset. Note that the coin type is always `0'`, independent of the unit of the ecash. > [!NOTE] > For examples, see the [test vectors][tests]. @@ -273,7 +273,7 @@ keysetIdInt = BigInt(`0x${keysetIdHex}`) % BigInt(2 ** 31 - 1); ## Restore from seed phrase -Using deterministic secret derivation, a user's wallet can regenerate the same `BlindedMessages` in case of loss of a previous wallet state. To also restore the corresponding `BlindSignatures` to fully recover the ecash, the wallet can either requests the mint to re-issue past `BlindSignatures` on the regenerated `BlindedMessages` (see [NUT-09][09]) or by downloading the entire database of the mint (TBD). +Using deterministic secret derivation, a user's wallet can regenerate the same `BlindedMessages` in case of loss of a previous wallet state. To also restore the corresponding `BlindSignatures` to fully recover the ecash, the wallet can either request the mint to re-issue past `BlindSignatures` on the regenerated `BlindedMessages` (see [NUT-09][09]) or download the entire database of the mint (TBD). The wallet takes the following steps during recovery: @@ -309,7 +309,7 @@ secret, r = derive_secret_and_r_hmac(seed, keyset_id, counter) Using the `secret` string and the private key `r`, the wallet generates a `BlindedMessage`. The wallet then increases the `counter` by `1` and repeats the same process for a given batch size. It is recommended to use a batch size of 100. -The user's wallet can now request the corresponding `BlindSignatures` for theses `BlindedMessages` from the mint using the [NUT-09][09] restore endpoint or by downloading the entire mint's database. +The user's wallet can now request the corresponding `BlindSignatures` for these `BlindedMessages` from the mint using the [NUT-09][09] restore endpoint or by downloading the entire mint's database. #### Generate `Proofs` diff --git a/14.md b/14.md index b72c10e80..07705ba4a 100644 --- a/14.md +++ b/14.md @@ -49,6 +49,14 @@ The hash lock in `Secret.data` and the preimage in `Proof.witness.preimage` are See [NUT-11][11] for a description of the signature scheme, the additional use of signature flags, and how to require signatures from multiple public keys (Multisig). +For a [NUT-03][03] swap with `rerandomize: true`, an HTLC input with +`SIG_ALL` uses that swap's separately tagged authorization digest over the +original outputs, including when the locktime/refund pathway applies. The +preimage requirement, permitted signing keys, and signature thresholds +remain those of this NUT and [NUT-11][11]. A valid preimage does not waive +signatures when the receiver pathway requires them; the existing zero-signature +and expired anyone-can-spend pathways retain their usual semantics. + ## Spending HTLC Proofs A `Proof` with a `Secret` of kind `HTLC` can be spent in two ways. @@ -153,7 +161,7 @@ The signature flag `sigflag` indicates that signatures are necessary on the `inp ## Mint info setting -The [NUT-06][06] `MintMethodSetting` indicates support for this feature: +The [NUT-06][06] `nuts` object indicates support for this feature: ```json { diff --git a/15.md b/15.md index e1e186f1a..d012bfb4e 100644 --- a/15.md +++ b/15.md @@ -12,7 +12,7 @@ The Lightning backend of the mint must support paying a partial amount of an Inv ## Multimint payment execution -`Alice`'s wallet coordinates multiple MPPs on different mints that support the feature (see below for the indicated setting). For a given Lightning invoice of `amount_total`, `Alice` splits the total amount into several partial amounts `amount_total = amount_1 + amount_2 + ...` that each must be covered by her individual balances on the mints she wants to use for the payment. She constructs multiple `PostMeltQuoteBolt11Request`'s that each include the corresponding partial amount in the payment option (see below) that she sends to all mints she wants to use for the payment. The mints then respond with a normal `PostMeltQuoteBolt11Response` (see [NUT-05][05]). `Alice` proceeds to pay the melt requests at each mint simultaneously. When all mints have sent out the partial Lightning payment, she receives a successful response from all mints individually. +`Alice`'s wallet coordinates multiple MPPs on different mints that support the feature (see below for the indicated setting). For a given Lightning invoice of `amount_total`, `Alice` splits the total amount into several partial amounts `amount_total = amount_1 + amount_2 + ...` that each must be covered by her individual balances on the mints she wants to use for the payment. She constructs multiple `PostMeltQuoteBolt11Request`s that each include the corresponding partial amount in the payment option (see below) that she sends to all mints she wants to use for the payment. The mints then respond with a normal `PostMeltQuoteBolt11Response` (see [NUT-05][05]). `Alice` proceeds to pay the melt requests at each mint simultaneously. When all mints have sent out the partial Lightning payment, she receives a successful response from all mints individually. ## Melt quote @@ -46,7 +46,7 @@ The settings returned in the info endpoint ([NUT-06][06]) indicate that a mint s ```json { - [ + "methods": [ { "method": , "unit": diff --git a/16.md b/16.md index 366817c93..98c75a19e 100644 --- a/16.md +++ b/16.md @@ -16,7 +16,7 @@ If the serialized token is not too large (i.e. includes less than or equal to 2 ### Animated QR codes -If a token is too large to be displayed as a single QR code, we use animated QR codes are based on the [UR](https://developer.blockchaincommons.com/ur/) protocol. The sender produces an animated QR code from a serialized Cashu token. The receiver scans the animated QR code until the UR decoder is able to decode the token. +If a token is too large to be displayed as a single QR code, we use animated QR codes based on the [UR](https://developer.blockchaincommons.com/ur/) protocol. The sender produces an animated QR code from a serialized Cashu token. The receiver scans the animated QR code until the UR decoder is able to decode the token. ## Resources diff --git a/17.md b/17.md index 5210183e4..32b21291c 100644 --- a/17.md +++ b/17.md @@ -42,7 +42,7 @@ All requests from the wallet to the mint are of the form of a `WsRequest`: } ``` -`WsRequestMethod` is a enum of strings with the supported commands `"subscribe"` and `"unsubscribe"`: +`WsRequestMethod` is an enum of strings with the supported commands `"subscribe"` and `"unsubscribe"`: ```ts enum WsRequestMethod { @@ -97,7 +97,7 @@ For example, if the wallet subscribes to a `Proof.Y` of a `Proof` that has not b #### Command: Unsubscribe -The wallet should always unsubscribe any subscriptions that is isn't interested in anymore. The parameters for the `"unsubscribe"` command is only the subscription ID: +The wallet should always unsubscribe any subscriptions that it isn't interested in anymore. The parameters for the `"unsubscribe"` command are only the subscription ID: ```json { @@ -124,7 +124,7 @@ Here, the `id` corresponds to the `id` in the request (as part of the JSON-RPC s ### Notifications -`WsNotification`'s are sent from the mint to the wallet and contain subscription data in the following format +`WsNotification`s are sent from the mint to the wallet and contain subscription data in the following format ```ts { @@ -139,6 +139,12 @@ Here, the `id` corresponds to the `id` in the request (as part of the JSON-RPC s `subId` is the subscription ID (previously generated by the wallet) this notification corresponds to. `NotificationPayload` carries the subscription data which is a `MintQuoteResponse` ([NUT-04][04]), a `MeltQuoteResponse` ([NUT-05][05]), or a `CheckStateResponse` ([NUT-07][07]), depending on what the corresponding `SubscriptionKind` was. +For a `proof_state` subscription, a notification or current-state snapshot +for an input spent in a mint-rerandomized [NUT-03][03] swap **MUST** contain +the committed `rerandomization_r` defined in [NUT-07][07]. It **MUST NOT** +include this field for a `PENDING` state; the mint **MUST NOT** disclose the +randomness before the swap commits. An ordinary proof state omits the field. + ### Errors `WsErrors` for a given `WsRequest` are returned in the following format @@ -150,13 +156,13 @@ Here, the `id` corresponds to the `id` in the request (as part of the JSON-RPC s "code": -32601, "message": "Human readable error message" }, - "id": "1" + "id": 1 } ``` ### Example: `ProofState` subscription -To subscribe to the `ProofState` of a `Proof`, the wallet establishes a websocket connection to `https://mint.com/v1/ws` and sends a `WsRequest` with a `filters` chosen to be the a `Proof.Y` value of the `Proof` (see [NUT-00][00]). Note that `filters` is an array meaning multiple subscriptions of the same `kind` can be made in the same request. +To subscribe to the `ProofState` of a `Proof`, the wallet establishes a websocket connection to `https://mint.com/v1/ws` and sends a `WsRequest` with a `filters` chosen to be the `Proof.Y` value of the `Proof` (see [NUT-00][00]). Note that `filters` is an array meaning multiple subscriptions of the same `kind` can be made in the same request. Wallet: @@ -266,8 +272,8 @@ Example: "bolt11_mint_quote", "bolt11_melt_quote", "proof_state" - ] - }, + ] + } ] } } diff --git a/18.md b/18.md index 06f67927d..32ce41c69 100644 --- a/18.md +++ b/18.md @@ -28,7 +28,7 @@ A Payment Request is defined as follows "sm": Array[SupportedMethod] , "d": str , "t": Array[Transport] , - "nut10": NUT10Option , + "nut10": NUT10Option } ``` @@ -142,7 +142,7 @@ The `n` tag specifies the NIPs the receiver supports. At least one tag value MUS - type: `post` - target: `` -The execute the payment, the sender makes a `POST` request to the specified endpoint URL with the `PaymentRequestPayload` as the body. +To execute the payment, the sender makes a `POST` request to the specified endpoint URL with the `PaymentRequestPayload` as the body. ## Payment payload diff --git a/19.md b/19.md index 66c46d455..2aeff514c 100644 --- a/19.md +++ b/19.md @@ -4,7 +4,7 @@ --- -To minimize the risk of loss of funds to due network errors during critical operations such as minting, swapping, and melting, we introduce a caching mechanism for successful responses. This allows wallet to replay requests on cached endpoints and allow it to recover the desired state after a network interruption. +To minimize the risk of loss of funds due to network errors during critical operations such as minting, swapping, and melting, we introduce a caching mechanism for successful responses. This allows wallets to replay requests on cached endpoints and allows them to recover the desired state after a network interruption. ## Requests & Responses @@ -41,11 +41,11 @@ The entry is structured as follows: "cached_endpoints": [ { "method": "POST", - "path": "/v1/mint/bolt11", + "path": "/v1/mint/bolt11" }, { "method": "POST", - "path": "/v1/swap", + "path": "/v1/swap" }, ... ] diff --git a/20.md b/20.md index f8a12fd8d..84fa712d2 100644 --- a/20.md +++ b/20.md @@ -31,9 +31,9 @@ The wallet of `Alice` includes the following `PostMintQuoteBolt11Request` data i } ``` -with the requested `amount`,`unit`, and `description` according to [NUT-04][04]. +with the requested `amount`, `unit`, and `description` according to [NUT-04][04]. -`pubkey` is the compressed secp256k1 public key (33 bytes, hex-encoded) that will be required for signature verification during the minting operation. The mint will only mint ecash after receiving a valid signature from the corresponding private key in the subsequent `PostMintRequest`. +`pubkey` is the compressed secp256k1 public key (33 bytes, hex-encoded) that will be required for signature verification during the minting operation. The mint will only mint ecash after receiving a valid signature from the corresponding private key in the subsequent `PostMintBolt11Request`. > [!IMPORTANT] > @@ -65,12 +65,14 @@ The mint `Bob` then responds with a `PostMintQuoteBolt11Response`: { "quote": , "request": , + "amount": , "unit": , + "method": "bolt11", "amount_paid": , "amount_issued": , "updated_at": , "state": , // Deprecated, optional - "expiry": , + "expiry": , "pubkey": // Optional <-- New } ``` @@ -91,6 +93,9 @@ Response of `Bob`: { "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", "request": "lnbc100n1pj4apw9...", + "amount": 10, + "unit": "sat", + "method": "bolt11", "amount_paid": 0, "amount_issued": 0, "updated_at": 1701704657, @@ -171,7 +176,6 @@ curl -X POST https://mint.host:3338/v1/mint/bolt11 -H "Content-Type: application } ], "signature": "d9be080b33179387e504bb6991ea41ae0dd715e28b01ce9f63d57198a095bccc776874914288e6989e97ac9d255ac667c205fa8d90a211184b417b4ffdd24092" - }' ``` @@ -196,7 +200,7 @@ Response of `Bob`: ## Errors -If the wallet user `Alice` does not include a signature on the `PostMintBolt11Request` but did include a `pubkey` in the `PostMintBolt11QuoteRequest` then `Bob` **MUST** respond with an error. `Alice` **CAN** repeat the request with a valid signature. +If the wallet user `Alice` does not include a signature on the `PostMintBolt11Request` but did include a `pubkey` in the `PostMintQuoteBolt11Request` then `Bob` **MUST** respond with an error. `Alice` **CAN** repeat the request with a valid signature. See [Error Codes][errors]: @@ -210,7 +214,7 @@ The settings for this NUT indicate the support for requiring a signature before ```json { "20": { - "supported": , + "supported": } } ``` diff --git a/21.md b/21.md index 3d706efe7..2472f8051 100644 --- a/21.md +++ b/21.md @@ -6,7 +6,7 @@ --- -This NUT defines a clear authentication scheme that allows operators to limit the use of their mint to registered users using the OAuth 2.0 and OpenID Connect protocols. The mint operator can protect chosen endpoints from access by requiring user authentication. Only users that provide a clear authentication token (CAT) from the specified OpenID Connect (OIDC) service can use the protected endpoints. The CAT is an Oauth 2.0 Access token (also known as the `access_token`) commonly in the form of a JWT that contains user information, a signature from the OIDC service, and an expiry time. To access protected endpoints, the wallet includes the CAT in the HTTP request header. +This NUT defines a clear authentication scheme that allows operators to limit the use of their mint to registered users using the OAuth 2.0 and OpenID Connect protocols. The mint operator can protect chosen endpoints from access by requiring user authentication. Only users that provide a clear authentication token (CAT) from the specified OpenID Connect (OIDC) service can use the protected endpoints. The CAT is an OAuth 2.0 access token (also known as the `access_token`) commonly in the form of a JWT that contains user information, a signature from the OIDC service, and an expiry time. To access protected endpoints, the wallet includes the CAT in the HTTP request header. **Note:** The primary purpose of this NUT is to restrict access to a mint by allowing registered users to obtain Blind Authentication Tokens as specified in [NUT-22][22]. @@ -55,10 +55,10 @@ The `*` wildcard, if present, MUST be the final character only. For example: -- `/v1/*` matches any path starting `/v1/` (all endpoints) +- `/v1/*` matches any path starting with `/v1/` (all endpoints) - `/v1/auth/*` matches any path that starts with `/v1/auth/` (all auth endpoints) - `/v1/mint/*` matches any path that starts with `/v1/mint/` (all minting endpoints) -- `/v1/mint/bolt*` matches any path starting `/v1/mint/bolt` (bolt11/12 minting endpoints) +- `/v1/mint/bolt*` matches any path starting with `/v1/mint/bolt` (bolt11/12 minting endpoints) In the example above, the `/v1/auth/blind/mint` path is the **exact match** [NUT-22][22] endpoint for obtaining blind authentication tokens (BATs). diff --git a/22.md b/22.md index 2389f01dc..ca6787219 100644 --- a/22.md +++ b/22.md @@ -108,7 +108,7 @@ Notice that the unit is `auth` and only a single amount of `1` is supported. Like in [NUT-02][02] the mint also offers the endpoints returning the keysets: ``` -GET v1/auth/blind/keysets +GET /v1/auth/blind/keysets ``` The mint returns the same `GetKeysetsResponse` response types as described in [NUT-02][02]. @@ -178,7 +178,7 @@ authA[base64url_authproof_json] This string is a BAT. -Note that `base64_url` strings may have padding characters (usually `=`) at the end, which can be omitted. Mints **MUST** accept and decode both padded and unpadded forms. +Note that `base64url` strings may have padding characters (usually `=`) at the end, which can be omitted. Mints **MUST** accept and decode both padded and unpadded forms. > [!CAUTION] > @@ -194,7 +194,7 @@ Blind-auth: and make the request as we usually would. -`AuthProofs` are single-use. The wallet MUST delete the `AuthProof` after a successful request, and SHOULD delete it even if request results in an error. If the wallet runs out of `AuthProofs`, it can [mint new ones](#minting-blind-authentication-tokens) using its clear authentication token (CAT). +`AuthProofs` are single-use. The wallet MUST delete the `AuthProof` after a successful request, and SHOULD delete it even if the request results in an error. If the wallet runs out of `AuthProofs`, it can [mint new ones](#minting-blind-authentication-tokens) using its clear authentication token (CAT). ## Mint @@ -233,9 +233,9 @@ The `*` wildcard, if present, MUST be the final character only. For example: -- `/v1/*` matches any path starting `/v1/` (all endpoints) +- `/v1/*` matches any path starting with `/v1/` (all endpoints) - `/v1/mint/*` matches any path that starts with `/v1/mint/` (all minting endpoints) -- `/v1/mint/bolt*` matches any path starting `/v1/mint/bolt` (bolt11/12 minting endpoints) +- `/v1/mint/bolt*` matches any path starting with `/v1/mint/bolt` (bolt11/12 minting endpoints) > [!CAUTION] > Wallets **MUST** treat mint provided `path` values as untrusted input and use exact or prefix matching only. Never use regex matching on untrusted input. diff --git a/23.md b/23.md index 64fea4634..9ceff174f 100644 --- a/23.md +++ b/23.md @@ -2,7 +2,7 @@ `optional` -`depends on: NUT-04 NUT-05` +`depends on: NUT-04, NUT-05` --- @@ -237,7 +237,7 @@ Melt request: ```bash curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \ -'{"quote": "019e6d5a-2347-7000-813e-a11ea6e419e7", "inputs": [...]}' +'{"quote": "019e6d5a-2347-7000-8449-370fefb42fed", "inputs": [...]}' ``` Successful melt response: @@ -270,5 +270,7 @@ Successful melt response: } ``` +[02]: 02.md [04]: 04.md [05]: 05.md +[08]: 08.md diff --git a/24.md b/24.md index 8fd245812..4d493ee04 100644 --- a/24.md +++ b/24.md @@ -10,7 +10,7 @@ This NUT describes how Cashu tokens can be used with HTTP 402 Payment Required responses to enable payments for HTTP resources. -### Server response +## Server response HTTP servers may respond with the HTTP status code 402 and an `X-Cashu` header containing a payment request, encoded in either [NUT-18][18] `creqA` or [NUT-26][26] `creqB` formats. Wallets **MUST** support both formats. @@ -32,15 +32,15 @@ The decoded payment request has the following fields: Note: there is no transport field since we expect an in-band payment. -### Client payment +## Client payment -After receiving a 402 response with a `X-Cashu` header, the client may retry the request with a `cashuB` token in the `X-Cashu` header as payment. +After receiving a 402 response with an `X-Cashu` header, the client may retry the request with a `cashuB` token in the `X-Cashu` header as payment. -The token MUST be from one of the mints listed in the `mints` array, and MUST be of the same `unit` and greater than or equal to the `amount`. +The token MUST be from one of the mints listed in the `m` array, and MUST be of the same `u` unit and greater than or equal to the `a` amount. ## Payment response -If the server receives tokens from a mint that is not in the `mints` array, an incorrect `unit`, an insufficient amount of tokens, or with insufficient locking conditions, it should respond with a HTTP `400` status code. +If the server receives tokens from a mint that is not in the `m` array, an incorrect `u` unit, an insufficient amount of tokens, or with insufficient locking conditions, it should respond with an HTTP `400` status code. [10]: 10.md [12]: 12.md diff --git a/25.md b/25.md index 34ab9304c..e0af915f1 100644 --- a/25.md +++ b/25.md @@ -2,7 +2,7 @@ `optional` -`depends on: NUT-04 NUT-05 NUT-20` +`depends on: NUT-04, NUT-05, NUT-20` --- diff --git a/26.md b/26.md index 773cee410..e0e95453b 100644 --- a/26.md +++ b/26.md @@ -215,7 +215,7 @@ bitcoin:?lightning=lnbc210n1p56amv8sp5v5gvxh0swyje66pcxtqtqh3qmzxd74fkxhjmzgzw7n #### BOLT12 + Cashu -A unified QR code supporting both LightningOffers (BOLT12) and Cashu: +A unified QR code supporting both Lightning Offers (BOLT12) and Cashu: ``` bitcoin:?lno=lno1pgzkcctzv4kpvggzu2th0tw73fx2ygyd7gyuul490zkhkmz75ncz6q9nkyp9m78932tq&creq=CREQB1QYQQWER9D4HNZV3NQGQQSQQQQQQQQQQRAQPSQQGQQSQQZQG9QQVXSAR5WPEN5TE0D45KUAPWV4UXZMTSD3JJUCM0D5RQQRJRDANXVET9YPCXZ7TDV4H8GXHR3TQ diff --git a/27.md b/27.md index db3a489b9..d19d41378 100644 --- a/27.md +++ b/27.md @@ -22,13 +22,16 @@ The private key for Nostr mint backup is derived from the wallet's mnemonic seed ### Example Implementation -```javascript +```typescript import { mnemonicToSeedSync } from "@scure/bip39"; import { sha256 } from "@noble/hashes/sha256"; import { bytesToHex } from "@noble/hashes/utils"; import { getPublicKey } from "nostr-tools"; -function deriveMintBackupKeys(mnemonic: string): { privateKeyHex: string; publicKeyHex: string } { +function deriveMintBackupKeys(mnemonic: string): { + privateKeyHex: string; + publicKeyHex: string; +} { // Derive seed from mnemonic const seed: Uint8Array = mnemonicToSeedSync(mnemonic); const domainSeparator = new TextEncoder().encode("cashu-mint-backup"); diff --git a/29.md b/29.md index f2734c5fe..a00157e8b 100644 --- a/29.md +++ b/29.md @@ -6,10 +6,10 @@ `uses: NUT-20` -This spec describes how a wallet can mint multiple quotes in one batched operation by requesting blind signatures for multiple quotes in a single atomic request. - --- +This spec describes how a wallet can mint multiple quotes in one batched operation by requesting blind signatures for multiple quotes in a single atomic request. + ## 1. Batch Checking Mint Quotes Before minting, the wallet SHOULD verify each mint quote's current accounting state. It does this by sending: @@ -131,7 +131,7 @@ Below is an example for minting two NUT-20 locked bolt11 mint quotes. ##### Request ```http -POST https://mint.host:3338/v1/mint/bolt12/batch +POST https://mint.host:3338/v1/mint/bolt11/batch Content-Type: application/json { diff --git a/30.md b/30.md index 753f16a2d..0511fbbc0 100644 --- a/30.md +++ b/30.md @@ -2,7 +2,7 @@ `optional` -`depends on: NUT-04 NUT-05 NUT-20` +`depends on: NUT-04, NUT-05, NUT-20` --- @@ -278,19 +278,23 @@ curl -X POST https://mint.host:3338/v1/melt/quote/onchain -d \ "quote": "019e6d5a-2347-7000-83ad-72a9a83f3105", "amount": 100000, "unit": "sat", + "method": "onchain", "state": "UNPAID", "expiry": 1701704757, "request": "bc1q...", "fee_options": [ { + "fee_index": 0, "fee_reserve": 5000, "estimated_blocks": 1 }, { + "fee_index": 1, "fee_reserve": 2000, "estimated_blocks": 6 }, { + "fee_index": 2, "fee_reserve": 800, "estimated_blocks": 144 } @@ -331,6 +335,7 @@ curl -X POST https://mint.host:3338/v1/melt/onchain -d \ "quote": "019e6d5a-2347-7000-83ad-72a9a83f3105", "amount": 100000, "unit": "sat", + "method": "onchain", "state": "PENDING", "expiry": 1701704757, "request": "bc1q...", @@ -365,6 +370,7 @@ The wallet selects one of the returned `fee_options` by including that option's "quote": "019e6d5a-2347-7000-83ad-72a9a83f3105", "amount": 100000, "unit": "sat", + "method": "onchain", "state": "PAID", "expiry": 1701704757, "request": "bc1q...", diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..93af1019a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,163 @@ +# AGENTS.md + +## Overview + +This repository contains the **Cashu protocol specifications**, called **NUTs** (Notation, Usage, and Terminology). Cashu is an open-source ecash protocol for Bitcoin. Each NUT specifies a part of the protocol that wallets and mints implement to interoperate. + +This is a **markdown-only** specs repo. There is no application code, no build system, and no test runner. The only CI check is **Prettier** formatting on every push and pull request. + +## Repository Layout + +``` +. +├── 00.md – 30.md # NUT specifications (zero-padded two-digit numbers) +├── README.md # Spec index with mandatory/optional tables and implementation support matrix +├── CONTRIBUTING.md # Contribution guide, PR naming, review and merge process +├── error_codes.md # Protocol error code registry +├── SECURITY.md # Vulnerability reporting +├── LICENSE # MIT +├── .gitignore +├── tests/ +│ ├── NN-tests.md # Test vectors for NUTs involving crypto or serialization +│ └── README.md +├── suppl/ +│ ├── NN.md # Supplementary material for specific NUTs +│ └── README.md +└── .github/ + └── workflows/ + ├── prettier.yml # Formatting CI (runs on every push/PR) + └── pages.yml # MkDocs deployment to GitHub Pages +``` + +## NUT Spec Conventions + +Every NUT file follows these conventions: + +- **Filename**: Zero-padded two-digit number matching the NUT number (e.g. `30.md` for NUT-30). +- **Title**: `# NUT-NN: Title` as the first line. +- **Status badge**: `` `mandatory` `` or `` `optional` `` on its own line after the title. +- **Dependencies** (if any): `` `depends on: NUT-NN` `` and/or `` `uses: NUT-NN` `` on separate lines in the header block. +- **Used-by** (if any): `` `used in: NUT-NN` `` declares the inverse relationship, naming the NUTs that build on this one (e.g. NUT-04, NUT-05, NUT-07, NUT-09, NUT-10, NUT-21). +- **Horizontal rule**: `---` separating the header block from the body. +- **RFC 2119 language**: Use `MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, `MAY`, and `CAN` (capitalized) per RFC 2119 when specifying protocol requirements. +- **API endpoints**: All REST endpoints use the `/v1/` prefix. Show the HTTP method and URL in a ` ```http ` code block, followed by request/response bodies in ` ```json ` blocks. +- **Type annotations in JSON**: Use `` `` `` placeholders to annotate the type of a field instead of writing a concrete value (e.g. ``, ``, ``). The types used across the existing NUTs are loose and not a fixed enumeration, so pick whatever placeholder is clearest rather than treating these as a closed set. +- **Curl examples**: Include a `bash` code block with a representative `curl` command when documenting an endpoint. +- **Mint info settings**: If the NUT is discoverable via NUT-06, include a `## Mint info setting` section showing the JSON structure under `"nuts"`. +- **Reference links**: Every NUT file ends with a block of reference-style Markdown links. At minimum include all NUTs referenced in the document. The full block looks like: + +``` +[00]: 00.md +[01]: 01.md +[02]: 02.md +... +``` + +## NUT Template + +When creating a new NUT, use this skeleton: + +````markdown +# NUT-NN: Title + +`optional` + +`depends on: NUT-XX` + +`used in: NUT-YY` + +--- + +Description of what this NUT specifies and why it exists. + +## Section + +Details, protocol flow, and requirements using RFC 2119 language. + +## Example + +**Request** of `Alice`: + +```http +POST https://mint.host:3338/v1/endpoint +``` + +With the data being of the form `PostExampleRequest`: + +```json +{ + "field": +} +``` + +If successful, `Bob` will respond with a `PostExampleResponse`: + +```json +{ + "field": +} +``` + +## Mint info setting + +```json +"nuts": { + "NN": { + ... + } +} +``` + +[00]: 00.md +[01]: 01.md + +... +```` + +## Formatting + +Prettier is enforced via CI on every push and PR (Node.js 20, default Prettier config). + +- **Check**: `npx prettier@3.9.1 --check .` +- **Auto-fix**: `npx prettier@3.9.1 --write .` + +Always run the check before committing. If Prettier reformats your changes, that is the canonical style — do not override it. + +## Review and Merge + +NUT changes require at least two approving reviews from core maintainers/contributors before merge, and new NUTs are merged only after at least two implementation PRs across different projects are marked "Ready to Merge". Follow the process in [`CONTRIBUTING.md`](CONTRIBUTING.md); do not shortcut it. + +## Test Vectors + +Test vector files live in `tests/` and are named `NN-tests.md` (matching the NUT number); a few older files use the singular `NN-test.md`. Add or update test vectors when a NUT involves: + +- Cryptographic operations (hashing, signing, blinding, DLEQ) +- Serialization or encoding formats (token encoding, bech32m, CBOR) +- Deterministic derivation (secret generation, keyset IDs) + +Test vectors use `shell` code blocks with labeled hex-encoded values. + +## Error Codes + +The file `error_codes.md` is the central registry of protocol error codes. When a new NUT introduces error conditions: + +- Assign codes within the appropriate range (see existing ranges in the file). +- Add rows to the table with the code, description, and relevant NUT references. +- Use the same reference-link style as the NUT files. + +## README Updates + +`README.md` contains the spec index and an implementation support matrix. When adding a new NUT: + +1. Add the NUT to the **Optional** (or **Mandatory**) table in the correct numeric position. +2. The support matrix columns (Wallets, Mints) should initially be empty (`-`) until implementations adopt the spec. +3. Add the corresponding `[NN]: NN.md` reference link at the bottom of the file. + +## Common Pitfalls + +- **Do not renumber existing NUTs.** NUT numbers are permanent identifiers referenced by implementations. +- **Do not alter the reference link block format.** Keep one `[NN]: NN.md` per line, sorted numerically. +- **Do not change existing API endpoint paths** unless the NUT is explicitly being revised with a breaking change. +- **Preserve the role names.** The protocol uses `Alice` (wallet), `Bob` (mint), and `Carol` (receiver) consistently across all specs. +- **Keep JSON examples valid** (except for `` placeholders and `...` ellipses). +- **Do not add supplementary material to the NUT file itself.** Extended material (e.g. redirect URL flows) belongs in `suppl/NN.md`. diff --git a/tests/00-tests.md b/tests/00-tests.md index b1bfc342c..77be11077 100644 --- a/tests/00-tests.md +++ b/tests/00-tests.md @@ -21,7 +21,7 @@ Point: 026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f ### Blinded messages -These are test vectors for the the blinded secret (public key) `B_` Alice sends to the mint Bob given a secret `x` and a random blinding factor `r`. +These are test vectors for the blinded secret (public key) `B_` Alice sends to the mint Bob given a secret `x` and a random blinding factor `r`. ```shell # Test 1 @@ -53,7 +53,7 @@ C_: 0398bc70ce8184d27ba89834d19f5199c84443c31131e48d3c1214db24247d005d ## Serialization of TokenV3 -The following are JSON-formatted v3 tokens and their serialized counterparts. +The following is a JSON-formatted v3 token and its serialized counterpart. ```json { @@ -141,7 +141,7 @@ Token from a single keyset and including a memo. } ``` -Encoded: +Serialized: ``` cashuBpGF0gaJhaUgArSaMTR9YJmFwgaNhYQFhc3hAOWE2ZGJiODQ3YmQyMzJiYTc2ZGIwZGYxOTcyMTZiMjlkM2I4Y2MxNDU1M2NkMjc4MjdmYzFjYzk0MmZlZGI0ZWFjWCEDhhhUP_trhpXfStS6vN6So0qWvc2X3O4NfM-Y1HISZ5JhZGlUaGFuayB5b3VhbXVodHRwOi8vbG9jYWxob3N0OjMzMzhhdWNzYXQ= diff --git a/tests/02-tests.md b/tests/02-tests.md index 0b3e7657e..614607d96 100644 --- a/tests/02-tests.md +++ b/tests/02-tests.md @@ -3,6 +3,7 @@ ## Version 1 The following keysets and corresponding keyset IDs are correct: + Keyset id: `00456a94ab4e1c46` ```json diff --git a/tests/03-tests.md b/tests/03-tests.md new file mode 100644 index 000000000..53444f057 --- /dev/null +++ b/tests/03-tests.md @@ -0,0 +1,189 @@ +# NUT-03 mint-rerandomized V2 swap vectors + +These vectors specify the optional [mint-rerandomized swap](../03.md) +encoding. All hex strings are lowercase; point encodings are compressed +secp256k1 public keys. Signing vectors test transcript bytes and Schnorr +verification only; the illustrative secret `example-input` is **not** a +NUT-10 spending condition, and the examples are not executable swap requests. + +## Shared mint tweak and additive points + +For `R = 0x42` repeated 32 times, counter zero is accepted: + +```text +R = 4242424242424242424242424242424242424242424242424242424242424242 +tag = Cashu_OutputRerandomization_secp256k1_v1 +counter = 00000000 +t = ea8bfdd0711f5a0cdd1377930ed729ca779722a8a5f97156242fcef09965eeb5 + +G = 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +2G = 02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 +G + tG = 020f78ce253e2f82c3f92f2e2c529f0c2320d628f0456e360f14e7005565f35ee6 +2G + tG = 03b0da7d80f73fdece3947e68b62c6cea972ba798600de31ce7a754d934e7d7c26 +-tG = 037c6797ffee9183650748f55b3e90fcabfa9e0d26b068c86ee72f8c25f15b1146 +``` + +Adding `tG` to `-tG` yields infinity and requires a fresh `R` or a failed +swap without spending inputs. As a signing/unblinding example, take +`Y = G`, original blinding factor `r = 1`, original `B = 2G`, and mint key +`a = 3` (`A = 3G`). This is a point-level algebraic example, not a claim +that a particular secret hashes to `G`. Then: + +```text +original B = 02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 +derived B' = 03b0da7d80f73fdece3947e68b62c6cea972ba798600de31ce7a754d934e7d7c26 +A = 3G = 02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9 +C' = 3B' = 033a0b1d9624a63e568496edc17143458e6fd1b73b829792087f6af6f4692cd728 +r + t = ea8bfdd0711f5a0cdd1377930ed729ca779722a8a5f97156242fcef09965eeb6 +P = C'-(r+t)A = 02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9 +``` + +The mint's ordinary DLEQ for `(B', C')`, if present, is reusable on the +proof `P` with the effective blinding factor `r + t`; it is not recomputed +using the original point `B`. + +## Tagged `SIG_ALL` transcript + +Frame the context, each input field, and each output amount with a four-byte +unsigned big-endian byte length. Append each **unframed** 32-byte +`SHA256(original B_)` immediately after its framed amount. Here `B_` is +the original 33-byte compressed point, not the hex text or tweaked `B'`: + +```text +SHA256(G) = 0f715baf5d4c2ed329785cef29e562f73488c8a2bb9dbc5700b361d54b9b0554 +SHA256(2G) = b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7 +``` + +For an empty input list and one output with amount `1` and original `B_ = G`: + +```text +message = 00000000 00000001 01 0f715baf5d4c2ed329785cef29e562f73488c8a2bb9dbc5700b361d54b9b0554 +digest = 12ba73147194f4528cf2e06b4a557daff57a1f2153938106af49d51c399bbe2a +``` + +Incorrectly prefixing the 32-byte hash with `00000020` would instead produce +`407b56261c9d046da7e2e5617bf07cacc64413894b2ccb81e3324c41a9cd8359`; +the mint **MUST NOT** accept a signature on that alternate digest. + +The digest is `TaggedSHA256("Cashu_SigAllRerandomized_v1", message)`, signed +**directly** as a BIP-340 32-byte message (no extra SHA-256). For a +non-empty **transcript-only** example, use one input with secret string +`example-input`, input `C = G`, and two outputs in order: +`(amount=1, B_=G)` and `(amount=2, B_=2G)`. The message is: + +```text +00000000 +0000000d 6578616d706c652d696e707574 +00000021 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 +00000001 01 +0f715baf5d4c2ed329785cef29e562f73488c8a2bb9dbc5700b361d54b9b0554 +00000001 02 +b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7 +``` + +The spaces and newlines in this display are not part of the message. The +tagged digest is +`1cf1d9ebb188c3bef26728eb68650d00bef181e24c4e6dcd5f102808183ec9fc`. +Signing that digest with secp256k1 private key `1` and zero BIP-340 +auxiliary randomness yields this valid Schnorr signature: + +```text +d4d6e5d561a3e6430dfea89f9f786312c29aa88db0456f3828663c15d468aa7d71650eaa4718eca7e0db0b4217e728471ecd3d2b9e23feba50a57ee68b9bfe4e +``` + +Reversing the two **output pairs** (amount and original `B_` together) +changes the tagged digest to +`c89da53d5da23ea64167e0dc3ff0d8c7b824fa8d6ad5727dddd35076d47417c2`. +The signature above cannot authorize that reordered request or an ordinary +NUT-11 `SIG_ALL` swap. Incorrectly framing the raw compressed point instead +of hashing it would produce +`2e96fda9957222af5e1a8e8c581ab12ed8b5180108a554bdb934d661d9bc2c71` +for the one-output example; that digest is not valid for this mode. + +## Two-input P2PK and HTLC authorization vectors + +These vectors pin the existing `SIG_ALL` spending-condition behavior using +two inputs and two original outputs. They exercise the authorization digest +and witness rules, **not** mint issuance: `C = G` and `C = 2G` below are +illustrative valid points, not signatures issued on these proof secrets. +For both vectors use keyset `009a1f293253e41e`, two input proofs in order +`(amount=1, C=G)` and `(amount=2, C=2G)`, and two original outputs in order +`(amount=1, B_=G)` and `(amount=2, B_=2G)`. Use the point encodings above. +The mint's derived outputs `B'_i` are **not** part of the signing transcript. + +For **P2PK 2-of-2**, use the following exact UTF-8 string as the first +input's `Proof.secret` (all spaces inside this code block are significant): + +```text +["P2PK", {"data": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "nonce": "00000000000000000000000000000000", "tags": [["sigflag", "SIG_ALL"], ["pubkeys", "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"], ["n_sigs", "2"]]}] +``` + +The second input's secret is identical except its 32-character nonce is +`11111111111111111111111111111111`. **Do not** normalize or reserialize +these strings before framing them. Each secret is 267 UTF-8 bytes, so each +input-secret frame starts with `0000010b`; each input-`C` frame starts with +`00000021`. The exact tail after the input frames is: + +```text +00000001 01 0f715baf5d4c2ed329785cef29e562f73488c8a2bb9dbc5700b361d54b9b0554 +00000001 02 b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7 +``` + +Prefix the first input with the empty context frame `00000000` and frame +the two input secrets and compressed `C` values in request order. With the +tag `Cashu_SigAllRerandomized_v1`, the resulting digest is +`fd3ade51140ee7316f1f95b3f2e55c61ddb82a8ce8b645be6ae6d221a88b3c9f`. +Schnorr signatures with private keys `1` (`G`) and `2` (`2G`), each using +32 zero BIP-340 auxiliary bytes, are: + +```text +b13d0056645a8eda768a397979e6763540e0749b15b7acb2d2e73ba60e68aa86d9704f080103c62d09b842bb689f0ce6537aecc26cd429825ae3af3b3da0f8c9 +ef0977a09c4df9b3af8f34e2374f53e9f359ba4238d787b1ddba0f35d0aaf48b3a60f9db1f1ec74d6a272a5ff5238edfbafab0dd635aa3fda4abc841d679825f +``` + +Put both hex signatures in a `P2PKWitness.signatures` array on the first +input; the second input requires no witness. Reordering the two **output +pairs** changes the digest to +`88c0190df9997b266fd4290cc507eb6ae9712661f6fff76af10bdbf194f606e1`. +Replacing the second original `B_` with `3G` instead produces +`5377787396552d37556800f60a8b1e90803dc14b92876a6a51b70694d2fb9a70`; +changing its amount from `2` to `3` produces +`4e486157d56c88cedfae5ddb346b2d10a1b4b5fc039abb1463e2af2306b21cab`. +All these changes invalidate the signatures. Framing the raw compressed +points instead of appending their 32-byte hashes produces the obsolete +digest `c1c3ae247a01b3e2c83646e4f9f12d3c16da5d6198145ee6bed831fb84d478f8`; +it **MUST NOT** authorize the updated mode. + +For **HTLC receiver and refund**, use this exact string as the first +input's `Proof.secret`: + +```text +["HTLC", {"data": "425ed4e4a36b30ea21b90e21c712c649e8214c29b7eaf68089d1039c6e55384c", "nonce": "00000000000000000000000000000000", "tags": [["sigflag", "SIG_ALL"], ["pubkeys", "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"], ["refund", "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"], ["locktime", "1"]]}] +``` + +As for P2PK, the second secret changes only its nonce to 32 `1` characters. +Each secret is 349 UTF-8 bytes, with frame length `0000015d`; use the same +input points, original outputs and output hashes as above. The `data` field +is the SHA-256 hash of 32 bytes of `0x42`; its preimage is +`4242424242424242424242424242424242424242424242424242424242`. +The digest is +`be691a010c111bd4689c18241cfe5a79893b443558db806d3000ec4268ec2670`. +With a matching `HTLCWitness.preimage`, a receiver using key `2` signs: + +```text +e14f243196f4857b290589a0326a1433107ccff557372d9f398b5bdf3bbb045193b8a1f9271b26603068f000140dc702a9cbf7c765e50812f1e4ce3e86a93a46 +``` + +After locktime `1` expires, an empty preimage and refund-key `3` signature +provide the alternative spending pathway: + +```text +c6a2b4c82137bac2118b37d0d086c18aa893893aa674b41304039dd2359cb503512d360b963970b4480a90b10f3846a1995d5d5f6ce9bda26ea1bb82323090e9 +``` + +Both signatures were produced with 32 zero BIP-340 auxiliary bytes and go +in the first input's HTLC witness. Reordering the outputs changes the digest +to `f1392f8a4083843c1875a43593675b97524a462469526d78462f1d8c321f0289`; +framing the raw compressed points instead of hashing them would yield +`67003e2d948944565c26d5a31de6874da42bab8c2558dcb980fa97b75c162de5`. +Neither alternate digest authorizes the original request in the current mode. diff --git a/tests/11-test.md b/tests/11-test.md index 96468410b..97180812b 100644 --- a/tests/11-test.md +++ b/tests/11-test.md @@ -117,7 +117,7 @@ The following is a `Proof` with a signature from the refund key that is **not** ### SIG_ALL Test Vectors -Example `SwapRequest`: +Example `PostSwapRequest`: ```json { @@ -140,7 +140,7 @@ Example `SwapRequest`: } ``` -The following is the `msg_to_sign` on the above `SwapRequest`. +The following is the `msg_to_sign` on the above `PostSwapRequest`. ``` ["P2PK",{"nonce":"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303","data":"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1","tags":[["sigflag","SIG_ALL"]]}]02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd2038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39 @@ -148,7 +148,7 @@ The following is the `msg_to_sign` on the above `SwapRequest`. The hex representation of sha256sum(msg_to_sign) should look like this: `de7f9e3ca0fcc5ed3258fcf83dbf1be7fa78a5ed6da7bf2aa60d61e9dc6eb09a` -The following is a `SwapRequest` with a valid sig_all signature. +The following is a `PostSwapRequest` with a valid `SIG_ALL` signature. ```json { @@ -171,7 +171,7 @@ The following is a `SwapRequest` with a valid sig_all signature. } ``` -The following is a `SwapRequest` that is invalid as the spending conditions are not identical across inputs (secret tags are different). +The following is a `PostSwapRequest` that is invalid as the spending conditions are not identical across inputs (secret tags are different). ```json { @@ -210,7 +210,7 @@ The following is a `SwapRequest` that is invalid as the spending conditions are } ``` -The following is a `SwapRequest` where multiple valid signatures are required and provided. +The following is a `PostSwapRequest` where multiple valid signatures are required and provided. ```json { @@ -233,7 +233,7 @@ The following is a `SwapRequest` where multiple valid signatures are required an } ``` -The following is a `SwapRequest` where the locktime has passed and the refund key signatures are valid. +The following is a `PostSwapRequest` where the locktime has passed and the refund key signatures are valid. ```json { @@ -256,7 +256,7 @@ The following is a `SwapRequest` where the locktime has passed and the refund ke } ``` -The following is a valid `SwapRequest` with an HTLC also locked to a public key +The following is a valid `PostSwapRequest` with an HTLC also locked to a public key ```json { @@ -279,7 +279,7 @@ The following is a valid `SwapRequest` with an HTLC also locked to a public key } ``` -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. +The following is an invalid `PostSwapRequest` 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 { @@ -307,7 +307,7 @@ The following is an invalid `SwapRequest` with an HTLC also locked to a public k } ``` -The following is a valid `SwapRequest` with a multisig HTLC also locked to locktime and refund keys. +The following is a valid `PostSwapRequest` with a multisig HTLC also locked to locktime and refund keys. ```json { @@ -330,7 +330,7 @@ The following is a valid `SwapRequest` with a multisig HTLC also locked to lockt } ``` -Example `MeltRequest`: +Example `PostMeltRequest`: ```json { @@ -354,7 +354,7 @@ Example `MeltRequest`: } ``` -The following is the valid `msg_to_sign` on the above `MeltRequest`. +The following is the valid `msg_to_sign` on the above `PostMeltRequest`. ``` ["P2PK",{"nonce":"bbf9edf441d17097e39f5095a3313ba24d3055ab8a32f758ff41c10d45c4f3de","data":"029116d32e7da635c8feeb9f1f4559eb3d9b42d400f9d22a64834d89cde0eb6835","tags":[["sigflag","SIG_ALL"]]}]02a9d461ff36448469dccf828fa143833ae71c689886ac51b62c8d61ddaa10028b0038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39cF8911fzT88aEi1d-6boZZkq5lYxbUSVs-HbJxK0 @@ -362,7 +362,7 @@ The following is the valid `msg_to_sign` on the above `MeltRequest`. The hex representation of sha256sum(msg_to_sign) should look like this: `9efa1067cc7dc870f4074f695115829c3cd817a6866c3b84e9814adf3c3cf262` -The following is a valid `SIG_ALL` `MeltRequest`. +The following is a valid `SIG_ALL` `PostMeltRequest`. ```json { @@ -386,7 +386,7 @@ The following is a valid `SIG_ALL` `MeltRequest`. } ``` -The following is a valid multi-sig `SIG_ALL` `MeltRequest`. +The following is a valid multi-sig `SIG_ALL` `PostMeltRequest`. ```json { diff --git a/tests/13-tests.md b/tests/13-tests.md index be3b9dcbf..bf0dfbaa3 100644 --- a/tests/13-tests.md +++ b/tests/13-tests.md @@ -7,7 +7,7 @@ The integer representation of a keyset with an ID `009a1f293253e41e` and its cor ```json { "keyset_id": "009a1f293253e41e", - "keyest_id_int": 864559728, + "keyset_id_int": 864559728, "derivation_path": "m/129372'/0'/864559728'/{counter}'" } ``` @@ -60,7 +60,7 @@ The corresponding derivation paths are ## Version 2: Secret derivation -Using [NUT-13](13.md) derivation procedure for V2 with keyset ID `015ba18a8adcd02e715a58358eb618da4a4b3791151a4bee5e968bb88406ccf76a`, we derive values starting from the following BIP39 mnemonic: +Using [NUT-13](../13.md) derivation procedure for V2 with keyset ID `015ba18a8adcd02e715a58358eb618da4a4b3791151a4bee5e968bb88406ccf76a`, we derive values starting from the following BIP39 mnemonic: ```json { @@ -94,7 +94,7 @@ The corresponding blinding factors `r` are: ## P2PK Derivation -Using [NUT-13](13.md) derivation procedure for P2PK, we derive values starting from the following BIP39 mnemonic: +Using [NUT-13](../13.md) derivation procedure for P2PK, we derive values starting from the following BIP39 mnemonic: ```json { diff --git a/tests/28-tests.md b/tests/28-tests.md index 8c0a4279f..54a274f5c 100644 --- a/tests/28-tests.md +++ b/tests/28-tests.md @@ -19,7 +19,7 @@ Slot 0 is the `data` tag. Slots 1-10 can be any combination of `pubkeys` and `re ### Example P2BK proof -The following P2BK proof shows the receiver's public key (P) blinded in the `data` tag (slot `0`), and the ephemeral public key (E) in the `p2pk_e`metadata field. +The following P2BK proof shows the receiver's public key (P) blinded in the `data` tag (slot `0`), and the ephemeral public key (E) in the `p2pk_e` metadata field. ```json { @@ -59,7 +59,7 @@ r6: "c39fa43b707215c163593fb8cadc0eddb4fe2f82c0c79c82a6fc2e3b6b051a7e" # scalar r7: "b17d6a51396eb926f4a901e20ff760a852563f90fd4b85e193888f34fd2ee523" # scalar as hex padded 64 r8: "4d4af85ea296457155b7ce328cf9accbe232e8ac23a1dfe901a36ab1b72ea04d" # scalar as hex padded 64 r9: "ce311248ea9f42a73fc874b3ce351d55964652840d695382f0018b36bb089dd1" # scalar as hex padded 64 -r10 "9de35112d62e6343d02301d8f58fef87958e99bb68cfdfa855e04fe18b95b114" # scalar as hex padded 64 +r10: "9de35112d62e6343d02301d8f58fef87958e99bb68cfdfa855e04fe18b95b114" # scalar as hex padded 64 ``` ### Blinded Public Keys (P') @@ -85,7 +85,7 @@ The following are valid blinded public keys for receiver pubkey (P), derived by The following are valid derived secret keys for the receiver secret key (p), by locking slot. ```shell -# skStd: standard derivation, (p + r0) mod n +# skStd: standard derivation, (p + rᵢ) mod n 0: "a174e77b25459f4809a187415af14065b49140c1408860f543444ed59261a605" # hex encoded private key 1: "f78fcf5891dbd772cd68146ae9d740107f96b43ea7d3f34850ee7d71faa6084f" # hex encoded private key 2: "81e0a0f6f9f36eebb3d7ffd733630270967150344203a2d2fb66bfd0466fe1a8" # hex encoded private key @@ -98,7 +98,7 @@ The following are valid derived secret keys for the receiver secret key (p), by 9: "7b68faf4c2a000e5c23b25f413bc5c9a2ec9f48b4990deba0dfb8904cac76f2c" # hex encoded private key 10: "4b1b39beae2f21825295b3193b172ecc2e123bc2a4f76adf73da4daf9b54826f" # hex encoded private key -# skNeg: negated derivation, (-p + r0) mod n +# skNeg: negated derivation, (-p + rᵢ) mod n 0: "47051623754422cb04bc24c0cfe2c1ddc8db1fcc18f0aa4b477df4aca2adc20e" # hex encoded private key 1: "9d1ffe00e1da5af5c882b1ea5ec8c18893e09349803c3c9e552823490af22458" # hex encoded private key 2: "2770cf9f49f1f26eaef29d56a85483e8aabb2f3f1a6bec28ffa065a756bbfdb1" # hex encoded private key