From a065b2fdc49e7d3803865df0e27217f40e577342 Mon Sep 17 00:00:00 2001 From: callebtc Date: Tue, 29 Oct 2024 23:55:52 +0000 Subject: [PATCH] deploy: d69242ff97c6f53849350c724c036c66a8425276 --- 00/index.html | 2 +- 01/index.html | 2 +- 02/index.html | 2 +- 03/index.html | 2 +- 04/index.html | 2 +- 05/index.html | 2 +- 06/index.html | 2 +- 07/index.html | 9 +++--- 08/index.html | 2 +- 09/index.html | 2 +- 10/index.html | 3 +- 11/index.html | 4 +-- 12/index.html | 2 +- 13/index.html | 2 +- 14/index.html | 4 ++- 15/index.html | 2 +- 16/index.html | 2 +- 17/index.html | 2 +- 18/index.html | 2 +- 404.html | 2 +- error_codes/index.html | 2 +- index.html | 2 +- search/search_index.json | 2 +- sitemap.xml | 56 +++++++++++++++++++------------------- sitemap.xml.gz | Bin 292 -> 292 bytes tests/00-tests/index.html | 2 +- tests/01-tests/index.html | 2 +- tests/02-tests/index.html | 2 +- tests/11-test/index.html | 2 +- tests/12-tests/index.html | 2 +- tests/13-tests/index.html | 2 +- tests/index.html | 2 +- 32 files changed, 66 insertions(+), 62 deletions(-) diff --git a/00/index.html b/00/index.html index 3286023..2ef4bfa 100644 --- a/00/index.html +++ b/00/index.html @@ -18,7 +18,7 @@ - + diff --git a/01/index.html b/01/index.html index 1622861..8cf1578 100644 --- a/01/index.html +++ b/01/index.html @@ -18,7 +18,7 @@ - + diff --git a/02/index.html b/02/index.html index 902fd43..9af5f8f 100644 --- a/02/index.html +++ b/02/index.html @@ -18,7 +18,7 @@ - + diff --git a/03/index.html b/03/index.html index df6d629..6f88ce3 100644 --- a/03/index.html +++ b/03/index.html @@ -18,7 +18,7 @@ - + diff --git a/04/index.html b/04/index.html index eccf104..9af71b8 100644 --- a/04/index.html +++ b/04/index.html @@ -18,7 +18,7 @@ - + diff --git a/05/index.html b/05/index.html index de223e0..37514fb 100644 --- a/05/index.html +++ b/05/index.html @@ -18,7 +18,7 @@ - + diff --git a/06/index.html b/06/index.html index 97c5916..486c0d5 100644 --- a/06/index.html +++ b/06/index.html @@ -18,7 +18,7 @@ - + diff --git a/07/index.html b/07/index.html index 5bac1d5..84b4ef4 100644 --- a/07/index.html +++ b/07/index.html @@ -18,7 +18,7 @@ - + @@ -915,7 +915,7 @@

NUT-07: Token state check

optional

-

used in: NUT-17

+

used in: NUT-17, NUT-11, NUT-14


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

@@ -945,7 +945,7 @@

ExampleNUT-00).

Response of Bob:

-

Bob will respond with a PostCheckStateResponse

+

Bob responds with a PostCheckStateResponse:

{
   "states": [
     {
@@ -958,10 +958,11 @@ 

Example}

+

The elements of the states array MUST be returned in the same order as the corresponding Ys checked in the request.

With curl:

Request of Alice:

diff --git a/08/index.html b/08/index.html index adb612b..f127af7 100644 --- a/08/index.html +++ b/08/index.html @@ -18,7 +18,7 @@ - + diff --git a/09/index.html b/09/index.html index 6776907..570b894 100644 --- a/09/index.html +++ b/09/index.html @@ -18,7 +18,7 @@ - + diff --git a/10/index.html b/10/index.html index 7b2c7d4..a3317a0 100644 --- a/10/index.html +++ b/10/index.html @@ -18,7 +18,7 @@ - + @@ -930,6 +930,7 @@

ExamplesNUT-11: Pay-to-Public-Key (P2PK) +
  • [NUT-14][14]: Hashed Timelock Contracts (HTLCs)
  • diff --git a/11/index.html b/11/index.html index 53730fe..9bbb4c9 100644 --- a/11/index.html +++ b/11/index.html @@ -18,7 +18,7 @@ - + @@ -1087,7 +1087,7 @@

    Witness format} -

    The signatures are an array of signatures in hex.

    +

    The signatures are an array of signatures in hex. The witness for a spent proof can be obtained with a Proof state check (see NUT-07).

    Multisig

    If the tag n_sigs is a positive integer, the mint will also consider signatures from public keys specified in the pubkeys tag additional to the public key in Secret.data. If the number of valid signatures is greater or equal to the number specified in n_sigs, the transaction is valid.

    Expressed as an "n-of-m" scheme, n = n_sigs is the number of required signatures and m = 1 ("data" field) + len(pubkeys tag) is the number of public keys that could sign.

    diff --git a/12/index.html b/12/index.html index fac23e7..4da2507 100644 --- a/12/index.html +++ b/12/index.html @@ -18,7 +18,7 @@ - + diff --git a/13/index.html b/13/index.html index 76875d4..b80e360 100644 --- a/13/index.html +++ b/13/index.html @@ -18,7 +18,7 @@ - + diff --git a/14/index.html b/14/index.html index 71a656a..3cc2d49 100644 --- a/14/index.html +++ b/14/index.html @@ -18,7 +18,7 @@ - + @@ -928,6 +928,8 @@
    Witness format} +

    The witness for a spent proof can be obtained with a Proof state check (see NUT-07).

    + diff --git a/15/index.html b/15/index.html index 045686f..9e29689 100644 --- a/15/index.html +++ b/15/index.html @@ -18,7 +18,7 @@ - + diff --git a/16/index.html b/16/index.html index bc5587e..9a41ab4 100644 --- a/16/index.html +++ b/16/index.html @@ -18,7 +18,7 @@ - + diff --git a/17/index.html b/17/index.html index 73a0614..be62af0 100644 --- a/17/index.html +++ b/17/index.html @@ -16,7 +16,7 @@ - + diff --git a/18/index.html b/18/index.html index 8aff7ba..aa73fc2 100644 --- a/18/index.html +++ b/18/index.html @@ -14,7 +14,7 @@ - + diff --git a/404.html b/404.html index dfcea60..ffccc75 100644 --- a/404.html +++ b/404.html @@ -12,7 +12,7 @@ - + diff --git a/error_codes/index.html b/error_codes/index.html index 5e7990a..83cdaa7 100644 --- a/error_codes/index.html +++ b/error_codes/index.html @@ -14,7 +14,7 @@ - + diff --git a/index.html b/index.html index 3f7f894..c0135c6 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@ - + diff --git a/search/search_index.json b/search/search_index.json index 3e52adf..321062a 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Cashu NUTs (Notation, Usage, and Terminology)","text":"

    These documents each specify parts of the Cashu protocol. Read the specifications for the legacy API here.

    "},{"location":"#specifications","title":"Specifications","text":"

    Wallets and mints MUST implement all mandatory specs and CAN implement optional specs.

    "},{"location":"#mandatory","title":"Mandatory","text":"NUT # Description 00 Cryptography and Models 01 Mint public keys 02 Keysets and fees 03 Swapping tokens 04 Minting tokens 05 Melting tokens 06 Mint info"},{"location":"#optional","title":"Optional","text":"# Description Wallets Mints 07 Token state check Nutshell, Moksha, Nutstash, cashu-ts, cdk-cli Nutshell, Moksha, cdk-mintd, nutmix 08 Overpaid Lightning fees Nutshell, Moksha, Nutstash, cashu-ts, cdk-cli Nutshell, Moksha, cdk-mintd, nutmix 09 Signature restore Nutshell, cdk-cli, cashu-ts, gonuts Nutshell, cdk-mintd, nutmix 10 Spending conditions Nutshell, cdk-cli, cashu-ts Nutshell, cdk-mintd, nutmix 11 Pay-To-Pubkey (P2PK) Nutshell, cdk-cli, cashu-ts Nutshell, cdk-mintd, nutmix 12 DLEQ proofs Nutshell, cdk-cli Nutshell, cdk-mintd, nutmix 13 Deterministic secrets Nutshell, Moksha, cashu-ts, cdk-cli, gonuts - 14 Hashed Timelock Contracts (HTLCs) Nutshell, cdk-cli Nutshell, cdk-mintd, nutmix 15 Partial multi-path payments (MPP) Nutshell Nutshell 16 Animated QR codes Cashu.me - 17 WebSocket subscriptions Nutshell Nutshell 18 Payment requests Cashu.me, Boardwalk -"},{"location":"#wallets","title":"Wallets:","text":""},{"location":"#mints","title":"Mints:","text":""},{"location":"00/","title":"NUT-00: Notation, Utilization, and Terminology","text":"

    mandatory

    This document details the notation and models used throughout the specification and lays the groundwork for understanding the basic cryptography used in the Cashu protocol.

    "},{"location":"00/#blind-diffie-hellmann-key-exchange-bdhke","title":"Blind Diffie-Hellmann key exchange (BDHKE)","text":""},{"location":"00/#variables","title":"Variables","text":""},{"location":"00/#bob-mint","title":"Bob (mint)","text":""},{"location":"00/#alice-user","title":"Alice (user)","text":""},{"location":"00/#hash_to_curvex-bytes-curve-point-y","title":"hash_to_curve(x: bytes) -> curve point Y","text":"

    Deterministically maps a message to a public key point on the secp256k1 curve, utilizing a domain separator to ensure uniqueness.

    Y = PublicKey('02' || SHA256(msg_hash || counter)) where msg_hash is SHA256(DOMAIN_SEPARATOR || x)

    "},{"location":"00/#protocol","title":"Protocol","text":""},{"location":"00/#01-models","title":"0.1 - Models","text":""},{"location":"00/#blindedmessage","title":"BlindedMessage","text":"

    An encrypted (\"blinded\") secret and an amount is sent from Alice to Bob for minting tokens or for swapping tokens. A BlindedMessage is also called an output.

    {\n  \"amount\": int,\n  \"id\": hex_str,\n  \"B_\": hex_str\n}\n

    amount is the value for the requested BlindSignature, id is the requested keyset ID from which we expect a signature, and B_ is the blinded secret message generated by Alice. An array [BlindedMessage] is also referred to as BlindedMessages.

    "},{"location":"00/#blindsignature","title":"BlindSignature","text":"

    A BlindSignature is sent from Bob to Alice after minting tokens or after swapping tokens. A BlindSignature is also called a promise.

    {\n  \"amount\": int,\n  \"id\": hex_str,\n  \"C_\": hex_str\n}\n

    amount is the value of the blinded token, id is the keyset id of the mint keys that signed the token, and C_ is the blinded signature on the secret message B_ sent in the previous step.

    "},{"location":"00/#proof","title":"Proof","text":"

    A Proof is also called an input and is generated by Alice from a BlindSignature it received. An array [Proof] is called Proofs. Alice sends Proofs to Bob for melting tokens. Serialized Proofs can also be sent from Alice to Carol. Upon receiving the token, Carol deserializes it and requests a swap from Bob to receive new Proofs.

    {\n  \"amount\": int,\n  \"id\": hex_str,\n  \"secret\": str,\n  \"C\": hex_str,\n}\n

    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 of the mint public keys that signed the token (hex string).

    "},{"location":"00/#02-protocol","title":"0.2 - Protocol","text":""},{"location":"00/#errors","title":"Errors","text":"

    In case of an error, mints respond with the HTTP status code 400 and include the following data in their response:

    {\n  \"detail\": \"oops\",\n  \"code\": 1337\n}\n

    Here, detail is the error message, and code is the error code. Error codes are to be defined in the documents concerning the use of a certain API endpoint.

    "},{"location":"00/#03-methods","title":"0.3 - Methods","text":""},{"location":"00/#serialization-of-tokens","title":"Serialization of tokens","text":"

    Tokens can be serialized to send them between users Alice and Carol. Serialized tokens have a Cashu token prefix, a versioning flag, and the token. Optionally, a URI prefix for making tokens clickable on the web.

    We use the following format for token serialization:

    cashu[version][token]\n

    cashu is the Cashu token prefix. [version] is a single base64_urlsafe character to denote the token format version.

    "},{"location":"00/#uri-tags","title":"URI tags","text":"

    To make Cashu tokens clickable on the web, we use the URI scheme cashu:. An example of a serialized token with URI tag is

    cashu:cashuAeyJwcm9vZn...\n
    "},{"location":"00/#v3-tokens","title":"V3 tokens","text":"

    V3 tokens are deprecated and the use of the more space-efficient V4 tokens is encouraged.

    "},{"location":"00/#version","title":"Version","text":"

    This token format has the [version] value A.

    "},{"location":"00/#format","title":"Format","text":"

    V3 tokens are base64-encoded JSON objects. The token format supports tokens from multiple mints. The JSON is serialized with a base64_urlsafe (base64 encoding with / replaced by _ and + by -). base64_urlsafe strings may have padding characters (usually =) at the end which can be omitted. Clients need to be able to decode both cases.

    cashuA[base64_token_json]\n

    [base64_token_json] is the token JSON serialized in base64_urlsafe. [base64_token_json] should be cleared of any whitespace before serializing.

    "},{"location":"00/#token-format","title":"Token format","text":"

    The deserialized base64_token_json is

    {\n  \"token\": [\n    {\n      \"mint\": str,\n      \"proofs\": Proofs\n    },\n    ...\n  ],\n  \"unit\": str <optional>,\n  \"memo\": str <optional>\n}\n

    mint is the mint URL. The mint URL must be stripped of any trailing slashes (/). Proofs is an array of Proof objects. The next two elements are only for displaying the receiving user appropriate information: unit is the currency unit of the token keysets (see Keysets for supported units), and memo is an optional text memo from the sender.

    "},{"location":"00/#example","title":"Example","text":"

    Below is a TokenV3 JSON before base64_urlsafe serialization.

    {\n  \"token\": [\n    {\n      \"mint\": \"https://8333.space:3338\",\n      \"proofs\": [\n        {\n          \"amount\": 2,\n          \"id\": \"009a1f293253e41e\",\n          \"secret\": \"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\n          \"C\": \"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"\n        },\n        {\n          \"amount\": 8,\n          \"id\": \"009a1f293253e41e\",\n          \"secret\": \"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\n          \"C\": \"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"\n        }\n      ]\n    }\n  ],\n  \"unit\": \"sat\",\n  \"memo\": \"Thank you.\"\n}\n

    When serialized, this becomes:

    cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9\n
    "},{"location":"00/#v4-tokens","title":"V4 tokens","text":"

    V4 tokens are a space-efficient way of serializing tokens using the CBOR binary format. All field are single characters and hex strings are encoded in binary. V4 tokens can only hold proofs from a single mint.

    "},{"location":"00/#version_1","title":"Version","text":"

    This token format has the [version] value B.

    "},{"location":"00/#format_1","title":"Format","text":"

    Wallets serialize tokens in a base64_urlsafe format (base64 encoding with / replaced by _ and + by -). base64_urlsafe strings may have padding characters (usually =) at the end which can be omitted. Clients need to be able to decode both cases.

    cashuB[base64_token_cbor]\n
    "},{"location":"00/#token-format_1","title":"Token format","text":"

    The deserialized base64_token_cbor is a JSON of the same form as a TokenV4 but with shorter keys and data represented as binary data (bytes) instead of hex strings (hex_str). Note that we have expanded what is called Proofs in TokenV3 (called p here with TokenV4) showing that its values are also different from the TokenV3 serialization.

    {\n  \"m\": str, // mint URL\n  \"u\": str, // unit\n  \"d\": str <optional>, // memo\n  \"t\": [\n    {\n      \"i\": bytes, // keyset ID\n      \"p\": [ // proofs with this keyset ID\n        {\n          \"a\": int, // amount\n          \"s\": str, // secret\n          \"c\": bytes, // signature\n          \"d\": { <optional> // DLEQ proof\n            \"e\": bytes,\n            \"s\": bytes,\n            \"r\": bytes\n          },\n          \"w\": str <optional> // witness\n        },\n        ...\n      ]\n    },\n    ...\n  ],\n}\n

    m is the mint URL. The mint URL must be stripped of any trailing slashes (/). u is the currency unit of the token keysets (see Keysets for supported units), and d is an optional text memo from the sender.

    i is the keyset ID of the profs in p, which is an array of Proof objects without the id field. We extracted the keyset ID id from each proof and grouped all proofs by their keyset ID i one level above (in p).

    Note that all fields of the bytes type encode hex strings in the original representation of Proof's.

    "},{"location":"00/#example_1","title":"Example","text":"

    Below is a TokenV4 JSON before CBOR and base64_urlsafe serialization.

    {\n    \"t\": [\n        {\n            \"i\": h'00ffd48b8f5ecf80',\n            \"p\": [\n                {\n                    \"a\": 1,\n                    \"s\": \"acc12435e7b8484c3cf1850149218af90f716a52bf4a5ed347e48ecc13f77388\",\n                    \"c\": h'0244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cf',\n                },\n            ],\n        },\n        {\n            \"i\": h'00ad268c4d1f5826',\n            \"p\": [\n                {\n                    \"a\": 2,\n                    \"s\": \"1323d3d4707a58ad2e23ada4e9f1f49f5a5b4ac7b708eb0d61f738f48307e8ee\",\n                    \"c\": h'023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94d',\n                },\n                {\n                    \"a\": 1,\n                    \"s\": \"56bcbcbb7cc6406b3fa5d57d2174f4eff8b4402b176926d3a57d3c3dcbb59d57\",\n                    \"c\": h'0273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63',\n                },\n            ],\n        },\n    ],\n    \"m\": \"http://localhost:3338\",\n    \"u\": \"sat\",\n}\n

    The h'' values are bytes but displayed as hex strings here.

    We serialize this JSON using CBOR which can be seen here. The resulting bytes are then serialized to a string using base64_urlsafe and the prefix cashuB is added. This leaves us with the following serialized TokenV4:

    cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA\n
    "},{"location":"01/","title":"NUT-01: Mint public key exchange","text":"

    mandatory

    This document outlines the exchange of the public keys of the mint Bob with the wallet user Alice. Alice uses the keys to unblind Bob's blind signatures (see NUT-00).

    "},{"location":"01/#description","title":"Description","text":"

    Wallet user Alice receives public keys from mint Bob via GET /v1/keys. The set of all public keys for a set of amounts is called a keyset.

    The mint responds only with its active keysets. Keyset are active if the mint will sign promises with it. The mint will accept tokens from inactive keysets as inputs but will not sign with them for new outputs. The active keysets can change over time, for example due to key rotation. A list of all keysets, active and inactive, can be requested separately (see NUT-02).

    Note that a mint can support multiple keysets at the same time but will only respond with the active keysets on the endpoint GET /v1/keys. A wallet can ask for the keys of a specific (active or inactive) keyset via the endpoint GET /v1/keys/{keyset_id} (see NUT-02).

    "},{"location":"01/#keyset-generation","title":"Keyset generation","text":"

    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).

    Keys in Keysets are maps of the form {<amount_1> : <mint_pubkey_1>, <amount_2> : <mint_pubkey_2>, ...} for each <amount_i> of the amounts the mint Bob supports and the corresponding public key <mint_pubkey_1>, that is K_i (see NUT-00). The mint MUST use the compressed Secp256k1 public key format to represent its public keys.

    "},{"location":"01/#example","title":"Example","text":"

    Request of Alice:

    GET https://mint.host:3338/v1/keys\n

    With curl:

    curl -X GET https://mint.host:3338/v1/keys\n

    Response GetKeysResponse of Bob:

    {\n  \"keysets\": [\n    {\n      \"id\": <keyset_id_hex_str>,\n      \"unit\": <currency_unit_str>,\n      \"keys\": {\n        <amount_int>: <public_key_str>,\n        ...\n      }\n    }\n  ]\n}\n
    "},{"location":"01/#example-response","title":"Example response","text":"
    {\n  \"keysets\": [\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"unit\": \"sat\",\n      \"keys\": {\n          \"1\": \"02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104\",\n          \"2\": \"03b0f36d6d47ce14df8a7be9137712c42bcdd960b19dd02f1d4a9703b1f31d7513\",\n          \"4\": \"0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09\",\n          \"8\": \"0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d\",\n          ...\n      }\n    }\n  ]\n}\n
    "},{"location":"02/","title":"NUT-02: Keysets and fees","text":"

    mandatory

    A keyset is a set of public keys that the mint Bob generates and shares with its users. It refers to the set of public keys that each correspond to the amount values that the mint supports (e.g. 1, 2, 4, 8, ...) respectively.

    Each keyset indicates its keyset id, the currency unit, whether the keyset is active, and an input_fee_ppk that determines the fees for spending ecash from this keyset.

    A mint can have multiple keysets at the same time. For example, it could have one keyset for each currency unit that it supports. Wallets should support multiple keysets. They must respect the active and the input_fee_ppk properties of the keysets they use.

    "},{"location":"02/#keyset-properties","title":"Keyset properties","text":""},{"location":"02/#keyset-id","title":"Keyset ID","text":"

    A keyset id is an identifier for a specific keyset. It can be derived by anyone who knows the set of public keys of a mint. Wallets CAN compute the keyset id for a given keyset by themselves to confirm that the mint is supplying the correct keyset ID (see below).

    The keyset id is in each Proof so it can be used by wallets to identify which mint and keyset it was generated from. The keyset field id is also present in the BlindedMessages sent to the mint and BlindSignatures returned from the mint (see NUT-00).

    "},{"location":"02/#active-keysets","title":"Active keysets","text":"

    Mints can have multiple keysets at the same time but MUST have at least one active keyset (see NUT-01). The active property determines whether the mint allows generating new ecash from this keyset. Proofs from inactive keysets with active=false are still accepted as inputs but new outputs (BlindedMessages and BlindSignatures) MUST be from active keysets only.

    To rotate keysets, a mint can generate a new active keyset and inactive an old one. If the active flag of an old keyset is set to false, no new ecash from this keyset can be generated and the outstanding ecash supply of that keyset can be taken out of circulation as wallets rotate their ecash to active keysets.

    Wallets SHOULD prioritize swaps with Proofs from inactive keysets (see NUT-03) so they can quickly get rid of them. Wallets CAN swap their entire balance from an inactive keyset to an active one as soon as they detect that the keyset was inactivated. When constructing outputs for a transaction, wallets MUST choose only active keysets (see NUT-00).

    "},{"location":"02/#fees","title":"Fees","text":"

    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 keyest).

    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.

    "},{"location":"02/#wallet-transaction-construction","title":"Wallet transaction construction","text":"

    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:

    sum(inputs) - fees == sum(outputs)\n

    Here, sum(inputs) and sum(outputs) mean the sum of the amounts of the inputs and outputs respectively. fees is calculated from the sum of each input's fee and rounded up to the next larger integer:

    def fees(inputs: List[Proof]) -> int:\n  sum_fees = 0\n  for proof in inputs:\n    sum_fees += keysets[proof.id].input_fee_ppk\n  return (sum_fees + 999) // 1000\n

    Here, the // operator in (sum_fees + 999) // 1000 denotes an integer division operator (aka floor division operator) that rounds down sum_fees + 999 to the next lower integer. Alternatively, we could round up the sum using a floating point division with ceil(sum_fees / 1000) although it is not recommended to do so due to the non-deterministic behavior of floating point division.

    Notice that since transactions can spend inputs from different keysets, the sum considers the fee for each Proof indexed by the keyset ID individually.

    "},{"location":"02/#deriving-the-keyset-id","title":"Deriving the keyset ID","text":""},{"location":"02/#keyset-id-version","title":"Keyset ID version","text":"

    Keyset IDs have a version byte (two hexadecimal characters). The currently used version byte is 00.

    The mint and the wallets of its users can derive a keyset ID from the keyset of the mint. The keyset ID is a lower-case hex string. To derive the keyset ID of a keyset, execute the following steps:

    1 - sort public keys by their amount in ascending order\n2 - concatenate all public keys to one byte array\n3 - HASH_SHA256 the concatenated public keys\n4 - take the first 14 characters of the hex-encoded hash\n5 - prefix it with a keyset ID version byte\n

    An example implementation in Python:

    def derive_keyset_id(keys: Dict[int, PublicKey]) -> str:\n    sorted_keys = dict(sorted(keys.items()))\n    pubkeys_concat = b\"\".join([p.serialize() for p in sorted_keys.values()])\n    return \"00\" + hashlib.sha256(pubkeys_concat).hexdigest()[:14]\n
    "},{"location":"02/#example-get-mint-keysets","title":"Example: Get mint keysets","text":"

    A wallet can ask the mint for a list of all keysets via the GET /v1/keysets endpoint.

    Request of Alice:

    GET https://mint.host:3338/v1/keysets\n

    With curl:

    curl -X GET https://mint.host:3338/v1/keysets\n

    Response GetKeysetsResponse of Bob:

    {\n  \"keysets\": [\n    {\n      \"id\": <hex_str>,\n      \"unit\": <str>,\n      \"active\": <bool>,\n      \"input_fee_ppk\": <int|null>,\n    },\n    ...\n  ]\n}\n

    Here, id is the keyset ID, unit is the unit string (e.g. \"sat\") of the keyset, active indicates whether new ecash can be minted with this keyset, and input_fee_ppk is the fee (per thousand units) to spend one input spent from this keyset. If input_fee_ppk is not given, we assume it to be 0.

    "},{"location":"02/#example-response","title":"Example response","text":"
    {\n  \"keysets\": [\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"unit\": \"sat\",\n      \"active\": True,\n      \"input_fee_ppk\": 100\n    },\n    {\n      \"id\": \"0042ade98b2a370a\",\n      \"unit\": \"sat\",\n      \"active\": False,\n      \"input_fee_ppk\": 100\n    },\n    {\n      \"id\": \"00c074b96c7e2b0e\",\n      \"unit\": \"usd\",\n      \"active\": True,\n      \"input_fee_ppk\": 100\n    }\n  ]\n}\n
    "},{"location":"02/#requesting-public-keys-for-a-specific-keyset","title":"Requesting public keys for a specific keyset","text":"

    To receive the public keys of a specific keyset, a wallet can call the GET /v1/keys/{keyset_id} endpoint where keyset_id is the keyset ID.

    "},{"location":"02/#example","title":"Example","text":"

    Request of Alice:

    We request the keys for the keyset 009a1f293253e41e.

    GET https://mint.host:3338/v1/keys/009a1f293253e41e\n

    With curl:

    curl -X GET https://mint.host:3338/v1/keys/009a1f293253e41e\n

    Response of Bob (same as NUT-01):

    {\n  \"keysets\": [{\n    \"id\": \"009a1f293253e41e\",\n    \"unit\": \"sat\",\n    \"keys\": {\n        \"1\": \"02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104\",\n        \"2\": \"03b0f36d6d47ce14df8a7be9137712c42bcdd960b19dd02f1d4a9703b1f31d7513\",\n        \"4\": \"0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09\",\n        \"8\": \"0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d\",\n        ...\n    },\n  }, ...\n  ]\n}\n
    "},{"location":"02/#wallet-implementation-notes","title":"Wallet implementation notes","text":"

    Wallets can request the list of keyset IDs from the mint upon startup and load only tokens from its database that have a keyset ID supported by the mint it interacts with. This also helps wallets to determine whether the mint has added a new current keyset or whether it has changed the active flag of an existing one.

    A useful flow is:

    "},{"location":"03/","title":"NUT-03: Swap tokens","text":"

    mandatory

    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).

    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.

    "},{"location":"03/#swap-to-send","title":"Swap to send","text":"

    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.

    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.

    "},{"location":"03/#swap-to-receive","title":"Swap to receive","text":"

    Another useful case for the swap operation follows up the example above where Alice has swapped her Proofs ready to be sent to Carol. Carol can receive these Proofs using the same operation by using them as inputs to invalidate them and request new outputs from Bob. Only if Carol has redeemed new outputs, Alice can't double-spend the Proofs anymore and the transaction is settled. To continue our example, Carol requests a swap with input Proofs worth [32, 8] to receive new outputs (of an arbitrary distribution) with the same total amount.

    "},{"location":"03/#example","title":"Example","text":"

    Request of Alice:

    POST https://mint.host:3338/v1/swap\n

    With the data being of the form PostSwapRequest:

    {\n  \"inputs\": <Array[Proof]>,\n  \"outputs\": <Array[BlindedMessage]>,\n}\n

    With curl:

    curl -X POST https://mint.host:3338/v1/swap -d \\\n{\n  \"inputs\":\n    [\n      {\n        \"amount\": 2,\n        \"id\": \"009a1f293253e41e\",\n        \"secret\": \"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\n        \"C\": \"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"\n      },\n      {\n      ...\n      }\n    ],\n  \"outputs\":\n    [\n      {\n        \"amount\": 2,\n        \"id\": \"009a1f293253e41e\",\n        \"B_\": \"02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239\"\n      },\n      {\n      ...\n      }\n    ],\n}\n

    If successful, Bob will respond with a PostSwapResponse

    {\n  \"signatures\": <Array[BlindSignature]>\n}\n
    "},{"location":"04/","title":"NUT-04: Mint tokens","text":"

    mandatory

    Minting tokens is a two-step process: requesting a mint quote and minting new tokens. Here, we describe both steps.

    In the first request the wallet asks the mint for a quote for a specific amount and unit to mint, and the payment method to pay. The mint responds with a quote that includes a quote id and a payment request. The user pays the request and, if successful, requests minting of new tokens with the mint in a second request. The wallet includes the quote id and new outputs in the second request.

    We limit this document to mint quotes of unit=\"sat\" and method=\"bolt11\" which requests a bolt11 Lightning invoice (typically generated by the mint to add Bitcoin to its reserves) to mint ecash denominated in Satoshis.

    "},{"location":"04/#mint-quote","title":"Mint quote","text":"

    To request a mint quote, the wallet of Alice makes a POST /v1/mint/quote/{method} request where method is the payment method requested (here bolt11).

    POST https://mint.host:3338/v1/mint/quote/bolt11\n

    The wallet of Alice includes the following PostMintQuoteBolt11Request data in its request:

    {\n  \"amount\": <int>,\n  \"unit\": <str_enum[\"sat\"]>,\n  \"description\": <str|null>\n}\n

    with the requested amount and the unit. An optional description can be passed if the mint signals support for it in MintMethodSetting.

    The mint Bob then responds with a PostMintQuoteBolt11Response:

    {\n  \"quote\": <str>,\n  \"request\": <str>,\n  \"state\": <str_enum[STATE]>,\n  \"expiry\": <int>\n}\n

    Where quote is the quote ID and request is the payment request to fulfill. expiry is the Unix timestamp until which the mint quote is valid.

    state is an enum string field with possible values \"UNPAID\", \"PAID\", \"ISSUED\":

    Note: quote is a unique and random id generated by the mint to internally look up the payment state. quote MUST remain a secret between user and mint and MUST NOT be derivable from the payment request. A third party who knows the quote ID can front-run and steal the tokens that this operation mints.

    "},{"location":"04/#example","title":"Example","text":"

    Request of Alice with curl:

    curl -X POST http://localhost:3338/v1/mint/quote/bolt11 -d '{\"amount\": 10, \"unit\": \"sat\"}' -H \"Content-Type: application/json\"\n

    Response of Bob:

    {\n  \"quote\": \"DSGLX9kevM...\",\n  \"request\": \"lnbc100n1pj4apw9...\",\n  \"state\": \"UNPAID\",\n  \"expiry\": 1701704757\n}\n

    The wallet MUST store the amount in the request and the quote id in the response in its database so it can later request the tokens after paying the request. After payment, the wallet continues with the next section.

    "},{"location":"04/#check-mint-quote-state","title":"Check mint quote state","text":"

    To check whether a mint quote has been paid, Alice makes a GET /v1/mint/quote/bolt11/{quote_id}.

    GET https://mint.host:3338/v1/mint/quote/bolt11/{quote_id}\n

    Like before, the mint Bob responds with a PostMintQuoteBolt11Response.

    Example request of Alice with curl:

    curl -X GET http://localhost:3338/v1/mint/quote/bolt11/DSGLX9kevM...\n
    "},{"location":"04/#minting-tokens","title":"Minting tokens","text":"

    After requesting a mint quote and paying the request, the wallet proceeds with minting new tokens by calling the POST /v1/mint/{method} endpoint where method is the payment method requested (here bolt11).

    POST https://mint.host:3338/v1/mint/bolt11\n

    The wallet Alice includes the following PostMintBolt11Request data in its request

    {\n  \"quote\": <str>,\n  \"outputs\": <Array[BlindedMessage]>\n}\n

    with the quote being the quote ID from the previous step and outputs being BlindedMessages (see NUT-00) that the wallet requests signatures on whose sum is amount as requested in the quote.

    The mint Bob then responds with a PostMintBolt11Response:

    {\n  \"signatures\": <Array[BlindSignature]>\n}\n

    where signatures is an array of blind signatures on the outputs.

    "},{"location":"04/#example_1","title":"Example","text":"

    Request of Alice with curl:

    curl -X POST https://mint.host:3338/v1/mint/bolt11 -H \"Content-Type: application/json\" -d \\\n'{\n  \"quote\": \"DSGLX9kevM...\",\n  \"outputs\": [\n    {\n      \"amount\": 8,\n      \"id\": \"009a1f293253e41e\",\n      \"B_\": \"035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d\"\n    },\n    {\n      \"amount\": 2,\n      \"id\": \"009a1f293253e41e\",\n      \"B_\": \"0288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6\"\n    }\n  ]\n}'\n

    Response of Bob:

    {\n  \"signatures\": [\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"amount\": 2,\n      \"C_\": \"0224f1c4c564230ad3d96c5033efdc425582397a5a7691d600202732edc6d4b1ec\"\n    },\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"amount\": 8,\n      \"C_\": \"0277d1de806ed177007e5b94a8139343b6382e472c752a74e99949d511f7194f6c\"\n    }\n  ]\n}\n

    If the invoice was not paid yet, Bob responds with an error. In that case, Alice CAN repeat the same request until the Lightning invoice is settled.

    "},{"location":"04/#unblinding-signatures","title":"Unblinding signatures","text":"

    Upon receiving the BlindSignatures from the mint Bob, the wallet of Alice unblinds them to generate Proofs (using the blinding factor r and the mint's public key K, see BDHKE NUT-00). The wallet then stores these Proofs in its database:

    [\n  {\n    \"id\": \"009a1f293253e41e\",\n    \"amount\": 2,\n    \"secret\": \"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\n    \"C\": \"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"\n  },\n  {\n    \"id\": \"009a1f293253e41e\",\n    \"amount\": 8,\n    \"secret\": \"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\n    \"C\": \"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"\n  }\n]\n
    "},{"location":"04/#settings","title":"Settings","text":"

    The settings for this nut indicate the supported method-unit pairs for minting and whether minting is disabled or not. They are part of the info response of the mint (NUT-06) which in this case reads

    {\n  \"4\": {\n    \"methods\": [\n      <MintMethodSetting>,\n      ...\n    ],\n    \"disabled\": <bool>\n  }\n}\n

    MintMethodSetting indicates supported method and unit pairs and additional settings of the mint. disabled indicates whether this minting is disabled.

    MintMethodSetting is of the form:

    {\n  \"method\": <str>,\n  \"unit\": <str>,\n  \"min_amount\": <int|null>,\n  \"max_amount\": <int|null>,\n  \"description\": <bool|null>\n}\n

    min_amount and max_amount indicate the minimum and maximum amount for an operation of this method-unit pair.

    Example MintMethodSetting:

    {\n  \"method\": \"bolt11\",\n  \"unit\": \"sat\",\n  \"min_amount\": 0,\n  \"max_amount\": 10000,\n  \"description\": true\n}\n
    "},{"location":"05/","title":"NUT-05: Melting tokens","text":"

    mandatory

    used in: NUT-08, NUT-15

    Melting tokens is the opposite of minting tokens (see NUT-04). Like minting tokens, melting is a two-step process: requesting a melt quote and melting tokens. Here, we describe both steps.

    In the first request the wallet asks the mint for a quote for a request it wants paid by the mint and the unit the wallet would like to spend as inputs. The mint responds with a quote that includes a quote id and an amount the mint demands in the requested unit. For the method bolt11, the mint includes a fee_reserve field indicating the reserve fee for a Lightning payment.

    In the second request, the wallet includes the quote id and provides inputs that sum up to amount+fee_reserve in the first response. For the method bolt11, the wallet can also include outputs in order for the mint to return overpaid Lightning fees (see NUT-08). The mint responds with a payment state. If the state is \"PAID\" the response includes a payment_preimage as a proof of payment. If the request included outputs, the mint may respond with change for the overpaid fees (see NUT-08).

    We limit this document to mint quotes of unit=\"sat\" and method=\"bolt11\" which requests a bolt11 Lightning payment (typically paid by the mint from its Bitcoin reserves) using ecash denominated in Satoshis.

    "},{"location":"05/#melt-quote","title":"Melt quote","text":"

    To request a melt quote, the wallet of Alice makes a POST /v1/melt/quote/{method} request where method is the payment method requested (here bolt11).

    POST https://mint.host:3338/v1/melt/quote/bolt11\n

    The wallet Alice includes the following PostMeltQuoteBolt11Request data in its request:

    {\n  \"request\": <str>,\n  \"unit\": <str_enum[\"sat\"]>\n}\n

    Here, request is the bolt11 Lightning invoice to be paid and unit is the unit the wallet would like to pay with.

    The mint Bob then responds with a PostMeltQuoteBolt11Response:

    {\n  \"quote\": <str>,\n  \"amount\": <int>,\n  \"fee_reserve\": <int>,\n  \"state\": <str_enum[STATE]>,\n  \"expiry\": <int>,\n  \"payment_preimage\": <str|null>\n}\n

    Where quote is the quote ID, amount the amount that needs to be provided, and fee_reserve the additional fee reserve that is required. The mint expects Alice to include Proofs of at least total_amount = amount + fee_reserve. expiry is the Unix timestamp until which the melt quote is valid. payment_preimage is the bolt11 payment preimage in case of a successful payment.

    state is an enum string field with possible values \"UNPAID\", \"PENDING\", \"PAID\":

    "},{"location":"05/#example","title":"Example","text":"

    Request of Alice with curl:

    curl -X POST https://mint.host:3338/v1/melt/quote/bolt11 -d \\\n{\n  \"request\": \"lnbc100n1p3kdrv5sp5lpdxzghe5j67q...\",\n  \"unit\": \"sat\"\n}\n

    Response of Bob:

    {\n  \"quote\": \"TRmjduhIsPxd...\",\n  \"amount\": 10,\n  \"fee_reserve\": 2,\n  \"state\": \"UNPAID\",\n  \"expiry\": 1701704757\n}\n
    "},{"location":"05/#check-melt-quote-state","title":"Check melt quote state","text":"

    To check whether a melt quote has been paid, Alice makes a GET /v1/melt/quote/bolt11/{quote_id}.

    GET https://mint.host:3338/v1/melt/quote/bolt11/{quote_id}\n

    Like before, the mint Bob responds with a PostMeltQuoteBolt11Response.

    Example request of Alice with curl:

    curl -X GET http://localhost:3338/v1/melt/quote/bolt11/TRmjduhIsPxd...\n
    "},{"location":"05/#melting-tokens","title":"Melting tokens","text":"

    Now that Alice knows what the total amount is (amount + fee_reserve) in her requested unit, she can proceed to melting tokens for which a payment will be executed by the mint. She calls the POST /v1/melt/{method} endpoint where method is the payment method requested (here bolt11).

    POST https://mint.host:3338/v1/melt/bolt11\n

    \u26a0\ufe0f Attention: This call will block until the Lightning payment either succeeds or fails. This can take quite a long time in case the Lightning payment is slow. Make sure to use no (or a very long) timeout when making this call!

    The wallet of Alice includes the following PostMeltBolt11Request data in its request

    {\n  \"quote\": <str>,\n  \"inputs\": <Array[Proof]>\n}\n

    Here, quote is the melt quote ID to be paid and inputs are the proofs with a total amount of at least amount + fee_reserve (see previous melt quote response).

    Like before, the mint Bob then responds with a PostMeltQuoteBolt11Response. If the payment was successful, the state field is set to \"PAID\" and the response includes the payment_preimage field containing the payment secret of the bolt11 payment.

    If state==\"PAID\", Alice's wallet can delete the inputs from her database (or move them to a history). If state==\"UNPAID\", Alice can repeat the same request again until the payment is successful.

    "},{"location":"05/#example_1","title":"Example","text":"

    Request of Alice with curl:

    curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \\\n'{\n  \"quote\": \"od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP\",\n  \"inputs\": [\n    {\n      \"amount\": 4,\n      \"id\": \"009a1f293253e41e\",\n      \"secret\": \"429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5\",\n      \"C\": \"03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011\",\n    },\n    {\n      \"amount\": 8,\n      \"id\": \"009a1f293253e41e\",\n      \"secret\": \"4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad\",\n      \"C\": \"0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9\",\n    }\n  ]\n}'\n

    Response PostMeltQuoteBolt11Response of Bob:

    {\n  \"quote\": \"TRmjduhIsPxd...\",\n  \"amount\": 10,\n  \"fee_reserve\": 2,\n  \"state\": \"PAID\",\n  \"expiry\": 1701704757,\n  \"payment_preimage\": \"c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b\"\n}\n
    "},{"location":"05/#settings","title":"Settings","text":"

    The mint's settings for this nut indicate the supported method-unit pairs for melting. They are part of the info response of the mint (NUT-06) which in this case reads

    {\n  \"5\": {\n    \"methods\": [\n      <MeltMethodSetting>,\n      ...\n    ],\n    \"disabled\": <bool>\n  }\n}\n

    MeltMethodSetting indicates supported method and unit pairs and additional settings of the mint. disabled indicates whether melting is disabled.

    MeltMethodSetting is of the form:

    {\n  \"method\": <str>,\n  \"unit\": <str>,\n  \"min_amount\": <int|null>,\n  \"max_amount\": <int|null>\n}\n

    min_amount and max_amount indicate the minimum and maximum amount for an operation of this method-unit pair.

    Example MeltMethodSetting:

    {\n  \"method\": \"bolt11\",\n  \"unit\": \"sat\",\n  \"min_amount\": 100,\n  \"max_amount\": 10000\n}\n
    "},{"location":"06/","title":"NUT-06: Mint information","text":"

    mandatory

    This endpoint returns information about the mint that a wallet can show to the user and use to make decisions on how to interact with the mint.

    "},{"location":"06/#example","title":"Example","text":"

    Request of Alice:

    GET https://mint.host:3338/v1/info\n

    With the mint's response being of the form GetInfoResponse:

    {\n  \"name\": \"Bob's Cashu mint\",\n  \"pubkey\": \"0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99\",\n  \"version\": \"Nutshell/0.15.0\",\n  \"description\": \"The short mint description\",\n  \"description_long\": \"A description that can be a long piece of text.\",\n  \"contact\": [\n    {\n      \"method\": \"email\",\n      \"info\": \"contact@me.com\"\n    },\n    {\n      \"method\": \"twitter\",\n      \"info\": \"@me\"\n    },\n    {\n      \"method\": \"nostr\",\n      \"info\": \"npub...\"\n    }\n  ],\n  \"motd\": \"Message to display to users.\",\n  \"icon_url\": \"https://mint.host/icon.jpg\",\n  \"time\": 1725304480,\n  \"nuts\": {\n    \"4\": {\n      \"methods\": [\n        {\n          \"method\": \"bolt11\",\n          \"unit\": \"sat\",\n          \"min_amount\": 0,\n          \"max_amount\": 10000\n        }\n      ],\n      \"disabled\": false\n    },\n    \"5\": {\n      \"methods\": [\n        {\n          \"method\": \"bolt11\",\n          \"unit\": \"sat\",\n          \"min_amount\": 100,\n          \"max_amount\": 10000\n        }\n      ],\n      \"disabled\": false\n    },\n    \"7\": {\n      \"supported\": true\n    },\n    \"8\": {\n      \"supported\": true\n    },\n    \"9\": {\n      \"supported\": true\n    },\n    \"10\": {\n      \"supported\": true\n    },\n    \"12\": {\n      \"supported\": true\n    }\n  }\n}\n

    With curl:

    curl -X GET https://mint.host:3338/v1/info\n
    "},{"location":"07/","title":"NUT-07: Token state check","text":"

    optional

    used in: NUT-17

    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.

    "},{"location":"07/#token-states","title":"Token states","text":"

    A proof can be in one of the following states

    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.

    "},{"location":"07/#use-cases","title":"Use cases","text":""},{"location":"07/#example-1-ecash-transaction","title":"Example 1: Ecash transaction","text":"

    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.

    "},{"location":"07/#example-2-lightning-payments","title":"Example 2: Lightning payments","text":"

    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).

    "},{"location":"07/#example","title":"Example","text":"

    Request of Alice:

    POST https://mint.host:3338/v1/checkstate\n

    With the data being of the form PostCheckStateRequest:

    {\n  \"Ys\": <Array[hex_str]>,\n}\n

    Where the elements of the array in Ys are the hexadecimal representation of the compressed point Y = hash_to_curve(secret) of the Proof to check (see NUT-00).

    Response of Bob:

    Bob will respond with a PostCheckStateResponse

    {\n  \"states\": [\n    {\n      \"Y\": <hex_str>,\n      \"state\": <str_enum[STATE]>,\n      \"witness\": <str|null>,\n    },\n    ...\n  ]\n}\n

    With curl:

    Request of Alice:

    curl -X POST https://mint.host:3338/v1/checkstate -H 'Content-Type: application/json' -d '{\n  \"Ys\": [\n    \"02599b9ea0a1ad4143706c2a5a4a568ce442dd4313e1cf1f7f0b58a317c1a355ee\"\n  ]\n}'\n

    Response of Bob:

    {\n  \"states\": [\n    {\n      \"Y\": \"02599b9ea0a1ad4143706c2a5a4a568ce442dd4313e1cf1f7f0b58a317c1a355ee\",\n      \"state\": \"SPENT\",\n      \"witness\": \"{\\\"signatures\\\": [\\\"b2cf120a49cb1ac3cb32e1bf5ccb6425e0a8372affdc1d41912ca35c13908062f269c0caa53607d4e1ac4c8563246c4c8a869e6ee124ea826fd4746f3515dc1e\\\"]}\"\n    }\n  ]\n}\n

    Where Y belongs to the provided Proof to check in the request, state indicates its state, and witness is the witness data that was potentially provided in a previous spend operation (can be empty).

    "},{"location":"08/","title":"NUT-08: Lightning fee return","text":"

    optional

    depends on: NUT-05

    This document describes how the overpaid Lightning fees are handled and extends NUT-05 which describes melting tokens (i.e. paying a Lightning invoice). In short, a wallet includes blank outputs when paying a Lightning invoice which can be assigned a value by the mint if the user has overpaid Lightning fees. This can be the case due to the unpredictability of Lightning network fees. To solve this issue, we introduce so-called blank outputs which are blinded messages with an undetermined value.

    The problem is also described in this gist.

    "},{"location":"08/#description","title":"Description","text":"

    Before requesting a Lightning payment as described in NUT-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.

    This code calculates the number of necessary blank outputs in Python:

    def calculate_number_of_blank_outputs(fee_reserve_sat: int) -> int:\n    assert fee_reserve_sat >= 0, \"Fee reserve can't be negative.\"\n    if fee_reserve_sat == 0:\n        return 0\n    return max(math.ceil(math.log2(fee_reserve_sat)), 1)\n
    "},{"location":"08/#example","title":"Example","text":"

    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 BlindSignatures to the wallet together with the successful payment status.

    "},{"location":"08/#wallet-flow","title":"Wallet flow","text":"

    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. The wallet then provides a PostMeltBolt11Request to POST /v1/melt/bolt11 that has (1) proofs of the value amount+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.

    "},{"location":"08/#mint-flow","title":"Mint flow","text":"

    Here we describe how the mint generates BlindSignatures 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 Lightning actual_fees were smaller than the fee_reserve.

    If the overpaid_fees = fee_reserve - actual_fees is positive, Bob decomposes it to values of 2^n (as in NUT-00) and then imprints them into the blank_outputs provided by Alice.

    Bob then signs these blank outputs (now with the imprinted amounts) and thus generates BlindSignatures. 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.

    "},{"location":"08/#example_1","title":"Example","text":"

    Request of Alice:

    POST https://mint.host:3338/v1/melt/bolt11\n

    With the data being of the form PostMeltBolt11Request:

    {\n  \"quote\": <str>,\n  \"inputs\": <Array[Proof]>,\n  \"outputs\": <Array[BlindedMessage]> <-- New\n}\n

    where the new output field carries the BlindMessages.

    The mint Bob then responds with a PostMeltQuoteBolt11Response:

    {\n  \"quote\": <str>,\n  \"amount\": <int>,\n  \"fee_reserve\": <int>,\n  \"state\": <str_enum[STATE]>,\n  \"expiry\": <int>,\n  \"payment_preimage\": <str|null>,\n  \"change\": <Array[BlindSignature]> <-- New\n}\n

    where the new change field carries the returned BlindSignatures due to overpaid fees.

    "},{"location":"08/#example_2","title":"Example","text":"

    Request of Alice with curl:

    curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \\\n'{\n  \"quote\": \"od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP\",\n  \"inputs\": [\n    {\n      \"amount\": 4,\n      \"id\": \"009a1f293253e41e\",\n      \"secret\": \"429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5\",\n      \"C\": \"03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011\",\n    },\n    {\n      \"amount\": 8,\n      \"id\": \"009a1f293253e41e\",\n      \"secret\": \"4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad\",\n      \"C\": \"0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9\",\n    }\n  ],\n  \"outputs\": [\n    {\n      \"amount\": 1,\n      \"id\": \"009a1f293253e41e\",\n      \"B_\": \"03327fc4fa333909b70f08759e217ce5c94e6bf1fc2382562f3c560c5580fa69f4\"\n    }\n  ]\n}'\n

    Everything here is the same as in NUT-05 except for outputs. The amount field in the BlindedMessages here are ignored by Bob so they can be set to any arbitrary value by Alice (they should be set to a value, like 1 so potential JSON validations do not error).

    If the mint has made a successful payment, it will respond the following.

    Response PostMeltQuoteBolt11Response from Bob:

    {\n  \"state\": \"PAID\",\n  \"payment_preimage\": \"c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b\",\n  \"change\": [\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"amount\": 2,\n      \"C_\": \"03c668f551855ddc792e22ea61d32ddfa6a45b1eb659ce66e915bf5127a8657be0\"\n    }\n  ]\n}\n

    The field change is an array of BlindSignatures that account for the overpaid fees. Notice that the amount has been changed by the mint. Alice must take these and generate Proofs by unblinding them as described in NUT-00 and as she does in NUT-04 when minting new tokens. After generating the Proofs, Alice stores them in her database.

    "},{"location":"09/","title":"NUT-09: Restore signatures","text":"

    optional

    used in: NUT-13

    In this document, we describe how wallets can recover blind signatures, and with that their corresponding Proofs, by requesting from the mint to reissue the blind signatures. This can be used for a backup recovery of a lost wallet (see NUT-13) or for recovering the response of an interrupted swap request (see NUT-03).

    Mints must store the BlindedMessage and the corresponding BlindSignature in their database every time they issue a BlindSignature. Wallets provide the BlindedMessage for which they request the BlindSignature. Mints only respond with a BlindSignature, if they have previously signed the BlindedMessage. Each returned BlindSignature also contains the amount and the keyset id (see NUT-00) which is all the necessary information for a wallet to recover a Proof.

    Request of Alice:

    POST https://mint.host:3338/v1/restore\n

    With the data being of the form PostRestoreRequest:

    {\n  \"outputs\": <Array[BlindedMessages]>\n}\n

    Response of Bob:

    The mint Bob then responds with a PostRestoreResponse.

    {\n  \"outputs\": <Array[BlindedMessages]>,\n  \"signatures\": <Array[BlindSignature]>\n}\n

    The returned arrays outputs and signatures are of the same length and for every entry outputs[i], there is a corresponding entry signatures[i].

    "},{"location":"10/","title":"NUT-10: Spending conditions","text":"

    optional

    used in: NUT-11, NUT-14

    An ordinary ecash token is a set of Proofs each with a random string secret. To spend such a token in a swap or a melt operation, wallets include proofs in their request each with a unique secret. To autorize a transaction, the mint requires that the secret has not been seen before. This is the most fundamental spending condition in Cashu, which ensures that a token can't be double-spent.

    In this NUT, we define a well-known format of secret that can be used to express more complex spending conditions. These conditions need to be met before the mint authorizes a transaction. Note that the specific type of spending condition is not part of this document but will be explained in other documents. Here, we describe the structure of secret which is expressed as a JSON Secret with a specific format.

    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 endpoint.

    "},{"location":"10/#basic-components","title":"Basic components","text":"

    An ecash transaction, i.e., a swap or a melt operation, with a spending condition consists of the following components:

    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 Secrets 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.

    "},{"location":"10/#well-known-secret","title":"Well-known Secret","text":"

    Spending conditions are expressed in a well-known secret format that is revealed to the mint when spending (unlocking) a token, not when the token is minted (locked). The mint parses each Proof's secret. If it can deserialize it into the following format it executes additional spending conditions that are further specified in additional NUTs.

    The well-known Secret stored in Proof.secret is a JSON of the format:

    [\nkind <str>,\n  {\n    \"nonce\": <str>,\n    \"data\": <str>,\n    \"tags\": [[ \"key\", \"value1\", \"value2\", ...],  ... ], // (optional)\n  }\n]\n
    "},{"location":"10/#examples","title":"Examples","text":"

    Example use cases of this secret format are

    "},{"location":"11/","title":"NUT-11: Pay to Public Key (P2PK)","text":"

    optional

    depends on: NUT-10

    This NUT describes Pay-to-Public-Key (P2PK) which is one kind of spending condition based on NUT-10's well-known Secret. Using P2PK, we can lock ecash tokens to a receiver's ECC public key and require a Schnorr signature with the corresponding private key to unlock the ecash. The spending condition is enforced by the mint.

    Caution: If the mint does not support this type 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 endpoint.

    "},{"location":"11/#pay-to-pubkey","title":"Pay-to-Pubkey","text":"

    NUT-10 Secret kind: P2PK

    If for a Proof, Proof.secret is a Secret of kind P2PK, the proof must be unlocked by providing a witness Proof.witness and one or more valid signatures in the array Proof.witness.signatures.

    In the basic case, when spending a locked token, the mint requires one valid Schnorr signature in Proof.witness.signatures on Proof.secret by the public key in Proof.Secret.data.

    To give a concrete example of the basic case, to mint a locked token we first create a P2PK Secret that reads:

    [\n  \"P2PK\",\n  {\n    \"nonce\": \"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\n    \"data\": \"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\n    \"tags\": [[\"sigflag\", \"SIG_INPUTS\"]]\n  }\n]\n

    Here, Secret.data is the public key of the recipient of the locked ecash. We serialize this Secret to a string in Proof.secret and get a blind signature by the mint that is stored in Proof.C (see NUT-03]).

    The recipient who owns the private key of the public key Secret.data can spend this proof by providing a signature on the serialized Proof.secret string that is then added to Proof.witness.signatures:

    {\n  \"amount\": 1,\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"id\": \"009a1f293253e41e\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\\\"]}\"\n}\n
    "},{"location":"11/#signature-scheme","title":"Signature scheme","text":"

    To spend a token locked with P2PK, the spender needs to include signatures in the spent proofs. We use libsecp256k1's serialized 64 byte Schnorr signatures on the SHA256 hash of the message to sign. The message to sign is the field Proof.secret in the inputs. If indicated by Secret.tags.sigflag in the inputs, outputs might also require signatures on the message BlindedMessage.B_.

    An ecash spending operation like swap and melt can have multiple inputs and outputs. If we have more than one input or output, we provide signatures in each Proof and BlindedMessage individually. The inputs are the Proofs provided in the inputs field and the outputs are the BlindedMessages in the outputs field in the request body (see PostMeltRequest in NUT-05 and PostSwapRequest in NUT-03).

    "},{"location":"11/#tags","title":"Tags","text":"

    More complex spending conditions can be defined in the tags in Proof.tags. All tags are optional. Tags are arrays with two or more strings being [\"key\", \"value1\", \"value2\", ...].

    Supported tags are:

    Note: The tag serialization type is [<str>, <str>, ...] but some tag values are int. Wallets and mints must cast types appropriately for de/serialization.

    "},{"location":"11/#signature-flags","title":"Signature flags","text":"

    Signature flags are defined in the tag Secret.tags['sigflag']. Currently, there are two signature flags.

    The signature flag SIG_ALL is enforced if at least one of the Proofs have the flag SIG_ALL. Otherwise, SIG_INPUTS is enforced.

    "},{"location":"11/#signature","title":"Signature","text":"

    Signatures must be provided in the field Proof.witness.signatures for each Proof which is an input. If the signature flag SIG_ALL is enforced, signatures must also be provided for every output in its field BlindedMessage.witness.signatures.

    "},{"location":"11/#signed-inputs","title":"Signed inputs","text":"

    A Proof (an input) with a signature P2PKWitness.signatures on secret is the JSON (see NUT-00):

    {\n  \"amount\": <int>,\n  \"secret\": <str>,\n  \"C\": <hex_str>,\n  \"id\": <str>,\n  \"witness\": <P2PKWitness | str> // Signatures on \"secret\"\n}\n

    The secret of each input is signed as a string.

    "},{"location":"11/#signed-outputs","title":"Signed outputs","text":"

    A BlindedMessage (an output) with a signature P2PKWitness.signatures on B_ is the JSON (see NUT-00):

    {\n  \"amount\": <int>,\n  \"B_\": <hex_str>,\n  \"witness\": <P2PKWitness | str> // Signatures on \"B_\"\n}\n

    The B_ of each output is signed as bytes which comes from the original hex string.

    "},{"location":"11/#witness-format","title":"Witness format","text":"

    P2PKWitness is a serialized JSON string of the form

    {\n  \"signatures\": <Array[<hex_str>]>\n}\n

    The signatures are an array of signatures in hex.

    "},{"location":"11/#multisig","title":"Multisig","text":"

    If the tag n_sigs is a positive integer, the mint will also consider signatures from public keys specified in the pubkeys tag additional to the public key in Secret.data. If the number of valid signatures is greater or equal to the number specified in n_sigs, the transaction is valid.

    Expressed as an \"n-of-m\" scheme, n = n_sigs is the number of required signatures and m = 1 (\"data\" field) + len(pubkeys tag) is the number of public keys that could sign.

    "},{"location":"11/#locktime","title":"Locktime","text":"

    If the tag locktime is the unix time and the mint's local clock is greater than locktime, the Proof becomes spendable by anyone, except if the following condition is also true. Note: A Proof is considered spendable by anyone if it only requires a secret and a valid signature C to be spent (which is the default case).

    "},{"location":"11/#refund-public-keys","title":"Refund public keys","text":"

    If the locktime is in the past and a tag refund is present, the Proof is spendable only if a valid signature by one of the the refund pubkeys is provided in Proof.witness.signatures and, depending on the signature flag, in BlindedMessage.witness.signatures.

    "},{"location":"11/#complex-example","title":"Complex Example","text":"

    This is an example secret that locks a Proof with a Pay-to-Pubkey (P2PK) condition that requires 2-of-3 signatures from the public keys in the data field and the pubkeys tag. If the timelock has passed, the Proof becomes spendable with a single signature from the public key in the refund tag. The signature flag sigflag indicates that signatures are necessary on the inputs and the outputs of a transaction.

    [\n  \"P2PK\",\n  {\n    \"nonce\": \"da62796403af76c80cd6ce9153ed3746\",\n    \"data\": \"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e\",\n    \"tags\": [\n      [\"sigflag\", \"SIG_ALL\"],\n      [\"n_sigs\", \"2\"],\n      [\"locktime\", \"1689418329\"],\n      [\n        \"refund\",\n        \"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e\"\n      ],\n      [\n        \"pubkeys\",\n        \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n        \"023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54\"\n      ]\n    ]\n  }\n]\n
    "},{"location":"11/#use-cases","title":"Use cases","text":"

    The following use cases are unlocked using P2PK:

    "},{"location":"12/","title":"NUT-12: Offline ecash signature validation","text":"

    optional

    In this document, we present an extension of Cashu's crypto system to allow a user Alice to verify the mint Bob's signature using only Bob's public keys. We explain how another user Carol who receives ecash from Alice can execute the DLEQ proof as well. This is achieved using a Discrete Log Equality (DLEQ) proof. Previously, Bob's signature could only be checked by himself using his own private keys (NUT-00).

    "},{"location":"12/#the-dleq-proof","title":"The DLEQ proof","text":"

    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) and for signing the BlindedMessage B'. Bob returns the DLEQ proof additional to the blind signature C' for a mint or swap operation.

    The complete DLEQ proof reads

    # DLEQ Proof\n\n(These steps occur when Bob returns C')\n\nBob:\nr = random nonce\nR1 = r*G\nR2 = r*B'\ne = hash(R1,R2,A,C')\ns = r + e*a\nreturn e, s\n\nAlice:\nR1 = s*G - e*A\nR2 = s*B' - e*C'\ne == hash(R1,R2,A,C')\n\nIf true, a in A = a*G must be equal to a in C' = a*B'\n
    "},{"location":"12/#hashx-arraypublickey-bytes","title":"hash(x: <Array<[PublicKey]>) -> bytes","text":"

    The hash(x) function generates a deterministic Sha256 hash for a given input list of PublicKey. The uncompressed hexadecimal representations of each PublicKey is concatenated before taking the Sha256 hash.

    def hash_e(*publickeys: PublicKey) -> bytes:\n    e_ = \"\"\n    for p in publickeys:\n        _p = p.serialize(compressed=False).hex()\n        e_ += str(_p)\n    e = hashlib.sha256(e_.encode(\"utf-8\")).digest()\n    return e\n
    "},{"location":"12/#mint-to-user-dleq-in-blindsignature","title":"Mint to user: DLEQ in BlindSignature","text":"

    The mint produces these DLEQ proofs when returning BlindSignature's in the responses for minting (NUT-04) and swapping (NUT-03) tokens. The BlindSignature object is extended in the following way to include the DLEQ proof:

    {\n  \"id\": <str>,\n  \"amount\": <int>,\n  \"C_\": <str>,\n  \"dleq\": { <-- New: DLEQ proof\n    \"e\": <str>,\n    \"s\": <str>\n  }\n}\n

    e and s are the DLEQ proof.

    "},{"location":"12/#user-to-user-dleq-in-proof","title":"User to user: DLEQ in Proof","text":"

    In order for Alice to communicate the DLEQ to another user Carol, we extend the Proof (see NUT-00) object and include the DLEQ proof. As explained below, we also need to include the blinding factor r for the proof to be convincing to another user Carol.

    {\n  \"id\": <str>,\n  \"amount\": <int>,\n  \"secret\": <str>,\n  \"C\": <str>,\n  \"dleq\": { <-- New: DLEQ proof\n    \"e\": <str>,\n    \"s\": <str>,\n    \"r\": <str>\n  }\n}\n

    e and s are the challenge and response of the DLEQ proof returned by Bob, r is the blinding factor of Alice that was used to generate the Proof. Alice serializes these proofs like any other in a token (see NUT-00) to send it to another user Carol.

    "},{"location":"12/#alice-minting-user-verifies-dleq-proof","title":"Alice (minting user) verifies DLEQ proof","text":"

    When minting or swapping tokens, Alice receives DLEQ proofs in the BlindSignature response from the mint Bob. Alice checks the validity of the DLEQ proofs for each ecash token she receives via the equations:

    R1 = s*G - e*A\nR2 = s*B' - e*C'\ne == hash(R1,R2,A,C') # must be True\n

    Here, the variables are

    In order to execute the proof, Alice needs e, s that are returned in the BlindSignature by Bob. Alice further needs B' (the BlindedMessage Alice created and Bob signed) and C' (the blind signature in the BlindSignature response) from Bob, and A (the public key of Bob with which he signed the BlindedMessage). All these values are available to Alice during or after calling the mint and swap operations.

    If a DLEQ proof is included in the mint's BlindSignature response, wallets MUST verify the DLEQ proof.

    "},{"location":"12/#carol-another-user-verifies-dleq-proof","title":"Carol (another user) verifies DLEQ proof","text":"

    Carol is a user that receives Proofs in a token from another user Alice. When Alice sends Proofs with DLEQ proofs to Carol or when Alice posts the Proofs publicly, Carol can validate the DLEQ proof herself and verify Bob's signature without having to talk to Bob. Alice includes the following information in the Proof (see above):

    Here, x is the Proof's secret, and C is the mint's signature on it. To execute the DLEQ proof like Alice did above, Carol needs (B', C') which she can compute herself using the blinding factor r that she receives from Alice.

    To verify the DLEQ proof of a received token, Carol needs to reconstruct B' and C' using the blinding factor r that Alice has included in the Proof she sent to Carol. Since Carol now has all the necessary information, she can execute the same equations to verify the DLEQ proof as Alice did:

    Y = hash_to_curve(x)\nC' = C + r*A\nB' = Y + r*G\n\nR1 = ... (same as Alice)\n

    If a DLEQ proof is included in a received token, wallets MUST verify the proof.

    "},{"location":"13/","title":"NUT-13: Deterministic Secrets","text":"

    optional

    depends on: NUT-09

    In this document, we describe the process that allows wallets to recover their ecash balance with the help of the mint using a familiar 12 word seed phrase (mnemonic). This allows us to restore the wallet's previous state in case of a device loss or other loss of access to the wallet. The basic idea is that wallets that generate the ecash deterministically can regenerate the same tokens during a recovery process. For this, they ask the mint to reissue previously generated signatures using NUT-09.

    "},{"location":"13/#deterministic-secret-derivation","title":"Deterministic secret derivation","text":"

    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 private_key derived from a 12-word BIP39 mnemonic seed phrase that the user stores in a secure place. The wallet uses the private_key, 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 needs to keep track of a separate counter for each keyset k it uses. Typically, the wallet will need to keep track of multiple keysets for every mint it interacts with. counter_k is used to generate a BIP32 derivation path which can then be used to derive secret and r.

    The following BIP32 derivation path is used. The derivation path depends on the keyset ID of keyset_k, and the counter_k of that keyset.

    m / 129372' / 0' / keyset_k_int' / counter' / secret||r

    This results in the following derivation paths:

    secret_derivation_path = `m/129372'/0'/{keyset_k_int}'/{counter_k}'/0`\nr_derivation_path = `m/129372'/0'/{keyset_id_k_int}'/{counter_k}'/1`\n

    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.

    Note: For examples, see the test vectors.

    "},{"location":"13/#counter","title":"Counter","text":"

    The wallet starts with counter_k := 0 upon encountering a new keyset and increments it by 1 every time it has successfully minted new ecash with this keyset. The wallet stores the latest counter_k in its database for all keysets it uses. Note that we have a counter (and therefore a derivation path) for each keyset k. We omit the keyset index k in the following of this document.

    "},{"location":"13/#keyset-id","title":"Keyset ID","text":"

    The integer representation keyset_id_int of a keyset is calculated from its hexadecimal ID which has a length of 8 bytes or 16 hex characters. First, we convert the hex string to a big-endian sequence of bytes. This value is then modulo reduced by 2^31 - 1 to arrive at an integer that is a unique identifier keyset_id_int.

    Example in Python:

    keyset_id_int = int.from_bytes(bytes.fromhex(keyset_id_hex), \"big\") % (2**31 - 1)\n

    Example in JavaScript:

    keysetIdInt = BigInt(`0x${keysetIdHex}`) % BigInt(2 ** 31 - 1);\n
    "},{"location":"13/#restore-from-seed-phrase","title":"Restore from seed phrase","text":"

    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) or by downloading the entire database of the mint (TBD).

    The wallet takes the following steps during recovery:

    1. Generate secret and r from counter and keyset
    2. Generate BlindedMessage from secret
    3. Obtain BlindSignature for secret from the mint
    4. Unblind BlindSignature to C using r
    5. Restore Proof = (secret, C)
    6. Check if Proof is already spent
    "},{"location":"13/#generate-blindedmessages","title":"Generate BlindedMessages","text":"

    To generate the BlindedMessages, the wallet starts with a counter := 0 and , for each increment of the counter, generates a secret using the BIP32 private key derived from secret_derivation_path and converts it to a hex string.

    secret = bip32.get_privkey_from_path(secret_derivation_path).hex()\n

    The wallet similarly generates a blinding factor r from the r_derivation_path:

    r = self.bip32.get_privkey_from_path(r_derivation_path)\n

    Note: For examples, see the test vectors.

    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 restore endpoint or by downloading the entire mint's database.

    "},{"location":"13/#generate-proofs","title":"Generate Proofs","text":"

    Using the restored BlindSignatures and the r generated in the previous step, the wallet can unblind the signature to C. The triple (secret, C, amount) is a restored Proof.

    "},{"location":"13/#check-proofs-states","title":"Check Proofs states","text":"

    If the wallet used the restore endpoint NUT-09 for regenerating the Proofs, it additionally needs to check for the Proofs spent state using NUT-07. The wallet deletes all Proofs which are already spent and keeps the unspent ones in its database.

    "},{"location":"13/#restoring-batches","title":"Restoring batches","text":"

    Generally, the user won't remember the last state of counter when starting the recovery process. Therefore, wallets need to know how far they need to increment the counter during the restore process to be confident to have reached the most recent state.

    In short, following approach is recommended:

    Wallets restore Proofs in batches of 100. The wallet starts with a counter=0 and increments it for every Proof it generated during one batch. When the wallet begins restoring the first Proofs, it is likely that the first few batches will only contain spent Proofs. Eventually, the wallet will reach a counter that will result in unspent Proofs which it stores in its database. The wallet then continues to restore until three successive batches are returned empty by the mint. This is to be confident that the restore process did not miss any Proofs that might have been generated with larger gaps in the counter by the previous wallet that we are restoring.

    "},{"location":"14/","title":"NUT-14: Hashed Timelock Contracts (HTLCs)","text":"

    optional

    depends on: NUT-10

    This NUT describes the use of Hashed Timelock Contracts (HTLCs) which defines a spending condition based on NUT-10's well-known Secret format. Using HTLCs, ecash tokens can be locked to the hash of a preimage or a timelock. This enables use cases such as atomic swaps of ecash between users, and atomic coupling of an ecash spending condition to a Lightning HTLC.

    HTLC spending conditions can be thought of as an extension of P2PK locks NUT-11 but with a hash lock in Secret.data and a new Proof.witness.preimage witness in the locked inputs to be spent. The preimage that was used to spend a locked token can be retrieved using NUT-07. Caution: applications that rely on being able to retrieve the witness independent from the spender must check via the mint's info endpoint that NUT-07 is supported.

    Caution: If the mint does not support this type 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 endpoint.

    "},{"location":"14/#htlc","title":"HTLC","text":"

    NUT-10 Secret kind: HTLC

    If for a Proof, Proof.secret is a Secret of kind HTLC, the hash of the lock is in Proof.secret.data. The preimage for unlocking the HTLC is in the witness Proof.witness.preimage. All additional tags from P2PK locks are used here as well, allowing us to add a locktime, signature flag, and use multisig (see NUT-11).

    Here is a concrete example of a Secret of kind HTLC:

    [\n  \"HTLC\",\n  {\n    \"nonce\": \"da62796403af76c80cd6ce9153ed3746\",\n    \"data\": \"023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54\",\n    \"tags\": [\n      [\n        \"pubkeys\",\n        \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\"\n      ],\n      [\"locktime\", \"1689418329\"],\n      [\n        \"refund\",\n        \"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e\"\n      ]\n    ]\n  }\n]\n

    A Proof with this Secret can be spent in two ways. To spend the hash lock, the witness in Proof.witness includes the preimage to Secret.data and a signature from the key in Secret.tag.pubkeys. Additionally, if the current system time is later than Secret.tag.locktime, the Proof can be spent if Proof.witness includes a signature from the key in Secret.tags.refund.

    The hash lock in Secret.data and the preimage in Proof.witness.preimage is treated as 32 byte data encoded as 64 character hex strings.

    See NUT-11 for a description of the signature scheme, the additional use of signature flags, and how to require signature from multiple public keys (multisig).

    "},{"location":"14/#witness-format","title":"Witness format","text":"

    HTLCWitness is a serialized JSON string of the form

    {\n  \"preimage\": <hex_str>,\n  \"signatures\": <Array[<hex_str>]>\n}\n
    "},{"location":"15/","title":"NUT-15: Partial multi-path payments","text":"

    optional

    depends on: NUT-05

    In this document, we describe how wallets can instruct multiple mints to each pay a partial amount of a bolt11 Lightning invoice. The full payment is composed of partial payments (MPP) from multiple multi-path payments from different Lightning nodes. This way, wallets can pay a larger Lightning invoice combined from multiple smaller balances on different mints. Due to the atomic nature of MPP, either all payments will be successful or all of them will fail.

    The Lightning backend of the mint must support paying a partial amount of an Invoice and multi-path payments (MPP, see BOLT 4). For example, the mint's Lightning node must be able to pay 50 sats of a 100 sat bolt11 invoice. The receiving Lightning node must support receiving multi-path payments as well.

    "},{"location":"15/#multimint-payment-execution","title":"Multimint payment execution","text":"

    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). 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.

    "},{"location":"15/#melt-quote","title":"Melt quote","text":"

    To request a melt quote with a partial amount, the wallet of Alice makes a POST /v1/melt/quote/bolt11 similar to NUT-05.

    POST https://mint.host:3338/v1/melt/quote/bolt11\n

    The wallet Alice includes the following PostMeltQuoteBolt11Request data in its request which includes an additional (and optional) options object compared to the standard request in NUT-05:

    {\n  \"request\": <str>,\n  \"unit\": <str_enum[\"sat\"]>,\n  \"options\": {\n    \"mpp\": {\n      \"amount\": <int>\n    }\n  }\n}\n

    Here, request is the bolt11 Lightning invoice to be paid, unit is the unit the wallet would like to pay with, and amount is the amount for the requested payment. The wallet then pays the returned melt quote the same way as in NUT-05.

    "},{"location":"15/#settings","title":"Settings","text":"

    The settings returned in the info endpoint (NUT-06) indicate that a mint supports this NUT. The mint MUST indicate each method and unit that supports mpp. It can indicate this in an array of objects for multiple method and unit pairs and the boolean flag mpp set to true.

    MultipathPaymentSetting is of the form:

    {\n  [\n    {\n      \"method\": <str>,\n      \"unit\": <str>,\n      \"mpp\": <bool>\n    },\n    ...\n  ]\n}\n

    Example MultipathPaymentSetting:

    {\n  \"15\": {\n    [\n      {\n        \"method\": \"bolt11\",\n        \"unit\": \"sat\",\n        \"mpp\": true\n      },\n      {\n        \"method\": \"bolt11\",\n        \"unit\": \"usd\",\n        \"mpp\": true\n      },\n    ]\n  }\n}\n
    "},{"location":"16/","title":"NUT-16: Animated QR codes","text":"

    optional

    This document outlines how tokens should be displayed as QR codes for sending them between two wallets.

    "},{"location":"16/#introduction","title":"Introduction","text":"

    QR codes are a great way to send and receive Cashu tokens. Before a token can be shared as a QR code, it needs to be serialized (see NUT-00).

    "},{"location":"16/#static-qr-codes","title":"Static QR codes","text":"

    If the serialized token is not too large (i.e. includes less than or equal to 2 proofs) it can usually be shared as a static QR code. This might not be the case if the secret includes long scripts or the token has a long memo or mint URL.

    "},{"location":"16/#animated-qr-codes","title":"Animated QR codes","text":"

    If a token is too large to be displayed as a single QR code, we use animated QR codes are based on the 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.

    "},{"location":"16/#resources","title":"Resources","text":""},{"location":"17/","title":"NUT-17: WebSockets","text":"

    optional

    depends on: NUT-07

    This NUT defines a websocket protocol that enables bidirectional communication between apps and mints using the JSON-RPC format.

    "},{"location":"17/#subscriptions","title":"Subscriptions","text":"

    The websocket enables real-time subscriptions that wallets can use to receive notifications for a state change of a MintQuoteResponse (NUT-04), MeltQuoteResponse (NUT-05), CheckStateResponse (NUT-07).

    A summary of the subscription flow is the following:

    1. A wallet connects to the websocket endpoint and sends a WsRequest with the subscribe command.
    2. The mint responds with a WsResponse containing an ok or an error.
    3. If the subscription was accepted, the mint sends a WsNotification of the current state of the subscribed objects and whenever there is an update for the wallet's subscriptions.
    4. To close a subscription, the wallet sends WsRequest with the unsubscribe command.
    "},{"location":"17/#specifications","title":"Specifications","text":"

    The websocket is reachable via the mint's URL path /v1/ws:

    https://mint.com/v1/ws\n

    NUT-17 uses the JSON-RPC format for all messages. There are three types of messages defined in this NUT.

    "},{"location":"17/#requests","title":"Requests","text":"

    All requests from the wallet to the mint are of the form of a WsRequest:

    {\n  \"jsonrpc\": \"2.0\",\n  \"method\": <str_enum[WsRequestMethod]>,\n  \"params\": <str_WsRequestParams>,\n  \"id\": <int>\n}\n

    WsRequestMethod is a enum of strings with the supported commands \"subscribe\" and \"unsubscribe\":

    enum WsRequestMethod {\n  sub = \"subscribe\",\n  unsub = \"unsubscribe\",\n}\n

    WsRequestParams is a serialized JSON with the parameters of the corresponding command.

    "},{"location":"17/#command-subscribe","title":"Command: Subscribe","text":"

    To subscribe to updates, the wallet sends a \"subscribe\" command with the following params parameters:

    {\n  \"kind\": <str_enum[SubscriptionKind]>,\n  \"subId\": <string>,\n  \"filters\": <string[]>\n}\n

    Here, subId is a unique uuid generated by the wallet and allows the client to map its requests to the mint's responses.

    SubscriptionKind is an enum with the following possible values:

    enum SubscriptionKind {\n  bolt11_melt_quote = \"bolt11_melt_quote\",\n  bolt11_mint_quote = \"bolt11_mint_quote\",\n  proof_state = \"proof_state\",\n}\n

    The filters are an array of mint quote IDs (NUT-04), or melt quote IDs (NUT-05), or Y's (NUT-07) of the corresponding object to receive updates from.

    As an example, filters would be of the following form to subscribe for updates of three different mint quote IDs:

    [\"20385fc7245...\", \"d06667cda9b...\", \"e14d8ca96f...\"]\n

    Note that id and subId are unrelated. The subId is the ID for each subscription, whereas id is part of the JSON-RPC spec and is an integer counter that must be incremented for every request sent over the websocket.

    Important: If the subscription is accepted by the mint, the mint MUST first respond with the current state of the subscribed object and continue sending any further updates to it.

    For example, if the wallet subscribes to a Proof.Y of a Proof that has not been spent yet, the mint will first respond with a ProofState with state == \"UNSPENT\". If the wallet then spends this Proof, the mint would send a ProofState with state == \"PENDING\" and then one with state == \"SPENT\". In total, the mint would send three notifications to the wallet.

    "},{"location":"17/#command-unsubscribe","title":"Command: Unsubscribe","text":"

    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:

    {\n  \"subId\": <string>\n}\n
    "},{"location":"17/#responses","title":"Responses","text":"

    A WsResponse is returned by the mint to both the \"subscribe\" and \"unsubscribe\" commands and indicates whether the request was successful:

    {\n  \"jsonrpc\": \"2.0\",\n  \"result\": {\n    \"status\": \"OK\",\n    \"subId\": <str>\n  },\n  \"id\": <int>\n}\n

    Here, the id corresponds to the id in the request (as part of the JSON-RPC spec) and subId corresponds to the subscription ID.

    "},{"location":"17/#notifications","title":"Notifications","text":"

    WsNotification's are sent from the mint to the wallet and contain subscription data in the following format

    {\n  \"jsonrpc\": \"2.0\",\n  \"method\": \"subscribe\",\n  \"params\": {\n    \"subId\": <str>,\n    \"payload\": NotificationPayload\n  }\n}\n

    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), a MeltQuoteResponse (NUT-05), or a CheckStateResponse (NUT-07), depending on what the corresponding SubscriptionKind was.

    "},{"location":"17/#errors","title":"Errors","text":"

    WsErrors for a given WsRequest are returned in the following format

    {\n  \"jsonrpc\": \"2.0\",\n  \"error\": {\n    \"code\": -32601,\n    \"message\": \"Human readable error message\"\n  },\n  \"id\": \"1\"\n}\n
    "},{"location":"17/#example-proofstate-subscription","title":"Example: ProofState subscription","text":"

    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). Note that filters is an array meaning multiple subscriptions of the same kind can be made in the same request.

    Wallet:

    {\n  \"jsonrpc\": \"2.0\",\n  \"id\": 0,\n  \"method\": \"subscribe\",\n  \"params\": {\n    \"kind\": \"proof_state\",\n    \"filters\": [\n      \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\"\n    ],\n    \"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\"\n  }\n}\n

    The mint first responds with a WsResponse confirming that the subscription has been added.

    Mint:

    {\n  \"jsonrpc\": \"2.0\",\n  \"result\": {\n    \"status\": \"OK\",\n    \"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\"\n  },\n  \"id\": 0\n}\n

    The mint immediately sends the current ProofState of the subscription as a WsNotification.

    Mint:

    {\n  \"jsonrpc\": \"2.0\",\n  \"method\": \"subscribe\",\n  \"params\": {\n    \"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\",\n    \"payload\": {\n      \"Y\": \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\",\n      \"state\": \"UNSPENT\",\n      \"witness\": null\n    }\n  }\n}\n

    While leaving the websocket connection open, the wallet then spends the ecash. The mint sends WsNotification updating the wallet about state changes of the ProofState accordingly:

    Mint:

    {\"jsonrpc\": \"2.0\", \"method\": \"subscribe\", \"params\": {\"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\", \"payload\": {\"Y\": \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\", \"state\": \"PENDING\"}}}\n\n{\"jsonrpc\": \"2.0\", \"method\": \"subscribe\", \"params\": {\"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\", \"payload\": {\"Y\": \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\", \"state\": \"SPENT\"}}}\n

    The wallet then unsubscribes.

    Wallet:

    {\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"unsubscribe\",\n  \"params\": { \"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\" }\n}\n
    "},{"location":"17/#signaling-support-via-nut-06","title":"Signaling Support via NUT-06","text":"

    Mints signal websocket support via NUT-06 using the following setting:

    \"nuts\": {\n    \"17\": {\n      \"supported\": [\n        {\n          \"method\": <str>,\n          \"unit\": <str>,\n          \"commands\": <str[]>\n        },\n        ...\n      ]\n    }\n}\n

    Here, commands is an array of the commands that the mint supports. A mint that supports all commands would return [\"bolt11_mint_quote\", \"bolt11_melt_quote\", \"proof_state\"]. Supported commands are given for each method-unit pair.

    Example:

    \"nuts\": {\n    \"17\": {\n      \"supported\": [\n        {\n          \"method\": \"bolt11\",\n          \"unit\": \"sat\",\n          \"commands\": [\n            \"bolt11_mint_quote\",\n            \"bolt11_melt_quote\",\n            \"proof_state\"\n            ]\n        },\n      ]\n    }\n}\n
    "},{"location":"18/","title":"NUT-18: Payment Requests","text":"

    optional

    This NUT introduces a standardised format for payment requests, that supply a sending wallet with all information necessary to complete the transaction. This enables many use-cases where a transaction is better initiated by the receiver (e.g. point of sale).

    "},{"location":"18/#flow","title":"Flow","text":"
    1. Receiver creates a payment request, encodes it and displays it to the sender
    2. Sender scans the request and constructs a matching token
    3. Sender sends the token according to the transport specified in the payment request
    4. Receiver receives the token and finalises the transaction
    "},{"location":"18/#payment-request","title":"Payment Request","text":"

    A Payment Request is defined as follows

    {\n  \"i\": str <optional>,\n  \"a\": int <optional>,\n  \"u\": str <optional>,\n  \"s\": bool <optional>,\n  \"m\": Array[str] <optional>,\n  \"d\": str <optional>,\n  \"t\": Array[Transport]\n}\n

    Here, the fields are

    "},{"location":"18/#transport","title":"Transport","text":"

    Transport specifies methods for sending the ecash to the receiver. A transport consists of a type and a target.

    {\n  \"t\": str,\n  \"a\": str,\n  \"g\": Array[Array[str, str]] <optional>\n}\n
    "},{"location":"18/#tags","title":"Tags","text":"

    Tags are an optional array of [tag, value, value, ...] tuples that can be used to specify additional features about the transport. A single tag can have multiple values.

    "},{"location":"18/#transport-types","title":"Transport types","text":"

    The supported transport types are described below.

    "},{"location":"18/#nostr","title":"Nostr","text":"

    The n tag specifies the NIPs the receiver supports. At least one tag value MUST be specified. For NIP-17 direct messages, the sender sends a PaymentRequestPayload as the message content.

    "},{"location":"18/#http-post","title":"HTTP POST","text":"

    The execute the payment, the sender makes a POST request to the specified endpoint URL with the PaymentRequestPayload as the body.

    "},{"location":"18/#payment-payload","title":"Payment payload","text":"

    If not specified otherwise, the payload sent to the receiver is a PaymentRequestPayload JSON serialized object as follows:

    {\n  \"id\": str <optional>,\n  \"memo\": str <optional>,\n  \"mint\": str,\n  \"unit\": <str_enum>,\n  \"proofs\": Array<Proof>\n}\n

    Here, id is the payment id (corresponding to i in request), memo is an optional memo to be sent to the receiver with the payment, mint is the mint URL from which the ecash is from, unit is the unit of the payment, and proofs is an array of proofs (see NUT-00, can also include DLEQ proofs).

    "},{"location":"18/#encoded-request","title":"Encoded Request","text":"

    The payment request is serialized using CBOR, encoded in base64_urlsafe, together with a prefix creq and a version A:

    \"creq\" + \"A\" + base64(CBOR(PaymentRequest))

    "},{"location":"18/#example","title":"Example","text":"

    This is an example payment request expressed as JSON:

    {\n  \"i\": \"b7a90176\",\n  \"a\": 10,\n  \"u\": \"sat\",\n  \"m\": [\"https://nofees.testnut.cashu.space\"],\n  \"t\": [\n    {\n      \"t\": \"nostr\",\n      \"a\": \"nprofile1qy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9mhwden5te0wfjkccte9curxven9eehqctrv5hszrthwden5te0dehhxtnvdakqqgydaqy7curk439ykptkysv7udhdhu68sucm295akqefdehkf0d495cwunl5\",\n      \"g\": [[\"n\", \"17\"]]\n    }\n  ]\n}\n

    This payment request serializes to (see here):

    creqApWF0gaNhdGVub3N0cmFheKlucHJvZmlsZTFxeTI4d3VtbjhnaGo3dW45ZDNzaGp0bnl2OWtoMnVld2Q5aHN6OW1od2RlbjV0ZTB3ZmprY2N0ZTljdXJ4dmVuOWVlaHFjdHJ2NWhzenJ0aHdkZW41dGUwZGVoaHh0bnZkYWtxcWd5ZGFxeTdjdXJrNDM5eWtwdGt5c3Y3dWRoZGh1NjhzdWNtMjk1YWtxZWZkZWhrZjBkNDk1Y3d1bmw1YWeBgmFuYjE3YWloYjdhOTAxNzZhYQphdWNzYXRhbYF4Imh0dHBzOi8vbm9mZWVzLnRlc3RudXQuY2FzaHUuc3BhY2U=\n
    "},{"location":"error_codes/","title":"NUT Errors","text":"Code Description Relevant nuts 10002 Blinded message of output already signed NUT-03, NUT-04, NUT-05 10003 Token could not be verified NUT-03, NUT-05 11001 Token is already spent NUT-03, NUT-05 11002 Transaction is not balanced (inputs != outputs) NUT-02, NUT-03, NUT-05 11005 Unit in request is not supported NUT-04, NUT-05 11006 Amount outside of limit range NUT-04, NUT-05 12001 Keyset is not known NUT-02, NUT-04 12002 Keyset is inactive, cannot sign messages NUT-02, NUT-03, NUT-04 20001 Quote request is not paid NUT-04 20002 Tokens have already been issued for quote NUT-04 20003 Minting is disabled NUT-04 20005 Quote is pending NUT-04, NUT-05 20006 Invoice already paid NUT-05 20007 Quote is expired NUT-04, NUT-05"},{"location":"tests/","title":"Test Vectors","text":"

    The files in this directory contain test vectors for NUTs that warrant them.

    "},{"location":"tests/00-tests/","title":"NUT-00 Test Vectors","text":""},{"location":"tests/00-tests/#hash-to-curve-function","title":"Hash-to-curve function","text":"

    The hash to curve function takes a message of any length and outputs a valid point on the secp256k1 curve. Note that unless you are using complex spend conditions (NUT-10), standardized secrets (random 32-bytes-long byte arrays) should be used in order to prevent wallet fingerprinting.

    # Test 1 (hex encoded)\nMessage: 0000000000000000000000000000000000000000000000000000000000000000\nPoint:   024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725\n\n# Test 2 (hex encoded)\nMessage: 0000000000000000000000000000000000000000000000000000000000000001\nPoint:   022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf\n\n# Test 3 (hex encoded)\n# Note that this message will take a few iterations of the loop before finding a valid point\nMessage: 0000000000000000000000000000000000000000000000000000000000000002\nPoint:   026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f\n
    "},{"location":"tests/00-tests/#blinded-messages","title":"Blinded messages","text":"

    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.

    # Test 1\nx:  d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6   # hex encoded byte array\nr:  99fce58439fc37412ab3468b73db0569322588f62fb3a49182d67e23d877824a   # hex encoded private key\nB_: 033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d # hex encoded public key\n\n# Test 2\nx:  f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60   # hex encoded byte array\nr:  f78476ea7cc9ade20f9e05e58a804cf19533f03ea805ece5fee88c8e2874ba50   # hex encoded private key\nB_: 029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763 # hex encoded public key\n
    "},{"location":"tests/00-tests/#blinded-signatures","title":"Blinded signatures","text":"

    These are test vectors for the blinded key C_ given the mint's private key k and Alice's blinded message containing B_.

    # Test 1\nmint private key: 0000000000000000000000000000000000000000000000000000000000000001\nB_: 02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\nC_: 02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\n\n# Test 2\nmint private key: 7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f\nB_: 02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\nC_: 0398bc70ce8184d27ba89834d19f5199c84443c31131e48d3c1214db24247d005d\n
    "},{"location":"tests/00-tests/#serialization-of-tokenv3","title":"Serialization of TokenV3","text":"

    The following are JSON-formatted v3 tokens and their serialized counterparts.

    {\n  \"token\": [\n    {\n      \"mint\": \"https://8333.space:3338\",\n      \"proofs\": [\n        {\n          \"amount\": 2,\n          \"id\": \"009a1f293253e41e\",\n          \"secret\": \"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\n          \"C\": \"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"\n        },\n        {\n          \"amount\": 8,\n          \"id\": \"009a1f293253e41e\",\n          \"secret\": \"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\n          \"C\": \"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"\n        }\n      ]\n    }\n  ],\n  \"unit\": \"sat\",\n  \"memo\": \"Thank you.\"\n}\n

    Serialized:

    cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9\n
    "},{"location":"tests/00-tests/#deserialization-of-tokenv3","title":"Deserialization of TokenV3","text":"

    The following are incorrectly formatted serialized v3 tokens.

    # Incorrect prefix (casshuA)\ncasshuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9\n\n# No prefix\neyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9\n

    The following is a correctly serialized v3 token.

    cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9\n

    Both of the following v3 tokens are valid, one includes padding characters at the end and the other does not.

    # Clients should be able to deserialize both\ncashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91IHZlcnkgbXVjaC4ifQ==\ncashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91IHZlcnkgbXVjaC4ifQ\n
    "},{"location":"tests/00-tests/#serialization-of-tokenv4","title":"Serialization of TokenV4","text":"

    The following are JSON-formatted v4 tokens and their serialized counterparts. The h'' values are bytes but displayed as hex strings here.

    "},{"location":"tests/00-tests/#single-keyset","title":"Single keyset","text":"

    Token from a single keyset and including a memo.

    {\n    \"t\": [\n        {\n            \"i\": h'00ad268c4d1f5826',\n            \"p\": [\n                {\n                    \"a\": 1,\n                    \"s\": \"9a6dbb847bd232ba76db0df197216b29d3b8cc14553cd27827fc1cc942fedb4e\",\n                    \"c\": h'038618543ffb6b8695df4ad4babcde92a34a96bdcd97dcee0d7ccf98d472126792',\n                },\n            ],\n        },\n    ],\n    \"d\": \"Thank you\",\n    \"m\": \"http://localhost:3338\",\n    \"u\": \"sat\",\n}\n

    Encoded:

    cashuBpGF0gaJhaUgArSaMTR9YJmFwgaNhYQFhc3hAOWE2ZGJiODQ3YmQyMzJiYTc2ZGIwZGYxOTcyMTZiMjlkM2I4Y2MxNDU1M2NkMjc4MjdmYzFjYzk0MmZlZGI0ZWFjWCEDhhhUP_trhpXfStS6vN6So0qWvc2X3O4NfM-Y1HISZ5JhZGlUaGFuayB5b3VhbXVodHRwOi8vbG9jYWxob3N0OjMzMzhhdWNzYXQ=\n
    "},{"location":"tests/00-tests/#multiple-keysets","title":"Multiple keysets","text":"

    The token below includes proofs from two different keysets.

    {\n    \"t\": [\n        {\n            \"i\": h'00ffd48b8f5ecf80',\n            \"p\": [\n                {\n                    \"a\": 1,\n                    \"s\": \"acc12435e7b8484c3cf1850149218af90f716a52bf4a5ed347e48ecc13f77388\",\n                    \"c\": h'0244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cf',\n                },\n            ],\n        },\n        {\n            \"i\": h'00ad268c4d1f5826',\n            \"p\": [\n                {\n                    \"a\": 2,\n                    \"s\": \"1323d3d4707a58ad2e23ada4e9f1f49f5a5b4ac7b708eb0d61f738f48307e8ee\",\n                    \"c\": h'023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94d',\n                },\n                {\n                    \"a\": 1,\n                    \"s\": \"56bcbcbb7cc6406b3fa5d57d2174f4eff8b4402b176926d3a57d3c3dcbb59d57\",\n                    \"c\": h'0273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63',\n                },\n            ],\n        },\n    ],\n    \"m\": \"http://localhost:3338\",\n    \"u\": \"sat\",\n}\n

    Serialized:

    cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA\n
    "},{"location":"tests/01-tests/","title":"NUT-01 Test Vectors","text":"

    The following are incorrect keysets that should be rejected by wallets implementing the NUT-01 specification.

    Key 1 is missing a byte

    {\n  \"1\": \"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38\",\n  \"2\": \"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\n  \"4\": \"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\n  \"8\": \"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}\n

    Key 2 is a valid key but is not in the compressed format.

    {\n  \"1\": \"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\n  \"2\": \"04fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de3625246cb2c27dac965cb7200a5986467eee92eb7d496bbf1453b074e223e481\",\n  \"4\": \"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\n  \"8\": \"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}\n

    The following are correct keysets that should be accepted by wallets implementing the NUT-01 specification. Note that the second (bigger) keyset's biggest amount is 9223372036854775808, one unit over what can fit into a 64-bit signed integer (often known as a Long). To handle this you'll need to use either an unsigned 64-bit integer or a 128-bit signed integer (sometimes known as a BigInteger).

    {\n  \"1\": \"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\n  \"2\": \"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\n  \"4\": \"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\n  \"8\": \"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}\n
    {\n  \"1\": \"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\n  \"2\": \"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\n  \"4\": \"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\n  \"8\": \"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\n  \"16\": \"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\n  \"32\": \"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\n  \"64\": \"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\n  \"128\": \"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\n  \"256\": \"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\n  \"512\": \"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\n  \"1024\": \"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\n  \"2048\": \"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\n  \"4096\": \"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\n  \"8192\": \"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\n  \"16384\": \"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\n  \"32768\": \"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\n  \"65536\": \"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\n  \"131072\": \"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\n  \"262144\": \"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\n  \"524288\": \"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\n  \"1048576\": \"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\n  \"2097152\": \"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\n  \"4194304\": \"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\n  \"8388608\": \"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\n  \"16777216\": \"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\n  \"33554432\": \"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\n  \"67108864\": \"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\n  \"134217728\": \"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\n  \"268435456\": \"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\n  \"536870912\": \"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\n  \"1073741824\": \"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\n  \"2147483648\": \"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\n  \"4294967296\": \"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\n  \"8589934592\": \"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\n  \"17179869184\": \"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\n  \"34359738368\": \"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\n  \"68719476736\": \"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\n  \"137438953472\": \"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\n  \"274877906944\": \"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\n  \"549755813888\": \"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\n  \"1099511627776\": \"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\n  \"2199023255552\": \"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\n  \"4398046511104\": \"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\n  \"8796093022208\": \"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\n  \"17592186044416\": \"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\n  \"35184372088832\": \"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\n  \"70368744177664\": \"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\n  \"140737488355328\": \"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\n  \"281474976710656\": \"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\n  \"562949953421312\": \"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\n  \"1125899906842624\": \"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\n  \"2251799813685248\": \"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\n  \"4503599627370496\": \"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\n  \"9007199254740992\": \"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\n  \"18014398509481984\": \"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\n  \"36028797018963968\": \"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\n  \"72057594037927936\": \"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\n  \"144115188075855872\": \"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\n  \"288230376151711744\": \"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\n  \"576460752303423488\": \"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\n  \"1152921504606846976\": \"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\n  \"2305843009213693952\": \"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\n  \"4611686018427387904\": \"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\n  \"9223372036854775808\": \"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}\n
    "},{"location":"tests/02-tests/","title":"NUT-02 Test Vectors","text":"

    The following keysets and corresponding keyset IDs are correct: Keyset id: 00456a94ab4e1c46

    {\n  \"1\": \"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\n  \"2\": \"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\n  \"4\": \"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\n  \"8\": \"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}\n

    Keyset id: 000f01df73ea149a

    {\n  \"1\": \"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\n  \"2\": \"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\n  \"4\": \"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\n  \"8\": \"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\n  \"16\": \"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\n  \"32\": \"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\n  \"64\": \"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\n  \"128\": \"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\n  \"256\": \"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\n  \"512\": \"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\n  \"1024\": \"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\n  \"2048\": \"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\n  \"4096\": \"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\n  \"8192\": \"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\n  \"16384\": \"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\n  \"32768\": \"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\n  \"65536\": \"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\n  \"131072\": \"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\n  \"262144\": \"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\n  \"524288\": \"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\n  \"1048576\": \"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\n  \"2097152\": \"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\n  \"4194304\": \"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\n  \"8388608\": \"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\n  \"16777216\": \"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\n  \"33554432\": \"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\n  \"67108864\": \"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\n  \"134217728\": \"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\n  \"268435456\": \"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\n  \"536870912\": \"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\n  \"1073741824\": \"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\n  \"2147483648\": \"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\n  \"4294967296\": \"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\n  \"8589934592\": \"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\n  \"17179869184\": \"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\n  \"34359738368\": \"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\n  \"68719476736\": \"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\n  \"137438953472\": \"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\n  \"274877906944\": \"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\n  \"549755813888\": \"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\n  \"1099511627776\": \"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\n  \"2199023255552\": \"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\n  \"4398046511104\": \"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\n  \"8796093022208\": \"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\n  \"17592186044416\": \"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\n  \"35184372088832\": \"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\n  \"70368744177664\": \"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\n  \"140737488355328\": \"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\n  \"281474976710656\": \"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\n  \"562949953421312\": \"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\n  \"1125899906842624\": \"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\n  \"2251799813685248\": \"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\n  \"4503599627370496\": \"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\n  \"9007199254740992\": \"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\n  \"18014398509481984\": \"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\n  \"36028797018963968\": \"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\n  \"72057594037927936\": \"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\n  \"144115188075855872\": \"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\n  \"288230376151711744\": \"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\n  \"576460752303423488\": \"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\n  \"1152921504606846976\": \"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\n  \"2305843009213693952\": \"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\n  \"4611686018427387904\": \"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\n  \"9223372036854775808\": \"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}\n
    "},{"location":"tests/11-test/","title":"NUT-11 Test Vectors","text":"

    The following is a Proof with a valid signature.

    {\n  \"amount\": 1,\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"id\": \"009a1f293253e41e\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\\\"]}\"\n}\n

    The following is a Proof with an invalid signature as it is on a different secret.

    {\n  \"amount\": 1,\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"id\": \"009a1f293253e41e\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\\\"]}\"\n}\n

    The following is a Proof with 2 signatures required to meet the multi-signature spend condition.

    {\n  \"amount\": 1,\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"id\": \"009a1f293253e41e\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\\\",\\\"9a72ca2d4d5075be5b511ee48dbc5e45f259bcf4a4e8bf18587f433098a9cd61ff9737dc6e8022de57c76560214c4568377792d4c2c6432886cc7050487a1f22\\\"]}\"\n}\n

    The following is a Proof with one one signature failing the multi-signature spend condition.

    {\n  \"amount\": 1,\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"id\": \"009a1f293253e41e\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\\\"]}\"\n}\n

    The following is a Proof with a signature from the refund key that is spendable because the locktime is in the past.

    {\n  \"amount\": 1,\n  \"id\": \"009a1f293253e41e\",\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"902685f492ef3bb2ca35a47ddbba484a3365d143b9776d453947dcbf1ddf9689\\\",\\\"data\\\":\\\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"03142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"locktime\\\",\\\"21\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"refund\\\",\\\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"710507b4bc202355c91ea3c147c0d0189c75e179d995e566336afd759cb342bcad9a593345f559d9b9e108ac2c9b5bd9f0b4b6a295028a98606a0a2e95eb54f7\\\"]}\"\n}\n

    The following is a Proof with a signature from the refund key that is not spendable because the locktime is in the future.

    {\n  \"amount\": 1,\n  \"id\": \"009a1f293253e41e\",\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"64c46e5d30df27286166814b71b5d69801704f23a7ad626b05688fbdb48dcc98\\\",\\\"data\\\":\\\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"03142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"locktime\\\",\\\"21\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"refund\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"f661d3dc046d636d47cb3d06586da42c498f0300373d1c2a4f417a44252cdf3809bce207c8888f934dba0d2b1671f1b8622d526840f2d5883e571b462630c1ff\\\"]}\"\n}\n
    "},{"location":"tests/12-tests/","title":"NUT-12 Test vectors","text":""},{"location":"tests/12-tests/#hash_e-function","title":"hash_e function","text":"
    R1: \"020000000000000000000000000000000000000000000000000000000000000001\"\nR2: \"020000000000000000000000000000000000000000000000000000000000000001\"\nK: \"020000000000000000000000000000000000000000000000000000000000000001\"\nC_: \"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\"\n
    hash(R1, R2, K, C_): \"a4dc034b74338c28c6bc3ea49731f2a24440fc7c4affc08b31a93fc9fbe6401e\"\n
    "},{"location":"tests/12-tests/#dleq-verification-on-blindsignature","title":"DLEQ verification on BlindSignature","text":"

    The following is a BlindSignature with a valid DLEQ proof.

    A: \"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"\nB_: \"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\"\n
    {\n  \"amount\": 8,\n  \"id\": \"00882760bfa2eb41\",\n  \"C_\": \"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\",\n  \"dleq\": {\n    \"e\": \"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9\",\n    \"s\": \"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da\"\n  }\n}\n
    "},{"location":"tests/12-tests/#dleq-verification-on-proof","title":"DLEQ verification on Proof","text":"

    The following is a Proof with a valid DLEQ proof.

    A: \"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"\n
    {\n  \"amount\": 1,\n  \"id\": \"00882760bfa2eb41\",\n  \"secret\": \"daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9\",\n  \"C\": \"024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc\",\n  \"dleq\": {\n    \"e\": \"b31e58ac6527f34975ffab13e70a48b6d2b0d35abc4b03f0151f09ee1a9763d4\",\n    \"s\": \"8fbae004c59e754d71df67e392b6ae4e29293113ddc2ec86592a0431d16306d8\",\n    \"r\": \"a6d13fcd7a18442e6076f5e1e7c887ad5de40a019824bdfa9fe740d302e8d861\"\n  }\n}\n
    "},{"location":"tests/13-tests/","title":"NUT-13 Test vectors","text":""},{"location":"tests/13-tests/#keyset-id-integer-representation","title":"Keyset ID integer representation","text":"

    The integer representation of a keyset with an ID 009a1f293253e41e and its corresponding derivation path for a counter of value {counter} are

    {\n  \"keyset_id\": \"009a1f293253e41e\",\n  \"keyest_id_int\": 864559728,\n  \"derivation_path\": \"m/129372'/0'/864559728'/{counter}'\"\n}\n
    "},{"location":"tests/13-tests/#secret-derivatoin","title":"Secret derivatoin","text":"

    We derive values starting from the following BIP39 mnemonic.

    {\n  \"mnemonic\": \"half depart obvious quality work element tank gorilla view sugar picture humble\"\n}\n

    The secrets derived for the first five counters from counter=0 to counter=4 are

    {\n  \"secret_0\": \"485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae\",\n  \"secret_1\": \"8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270\",\n  \"secret_2\": \"bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8\",\n  \"secret_3\": \"59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf\",\n  \"secret_4\": \"576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0\"\n}\n

    The corresponding blinding factors r are

    {\n  \"r_0\": \"ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679\",\n  \"r_1\": \"967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248\",\n  \"r_2\": \"b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899\",\n  \"r_3\": \"fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29\",\n  \"r_4\": \"5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9\"\n}\n

    The corresponding derivation paths are

    {\n  \"derivation_path_0\": \"m/129372'/0'/864559728'/0'\",\n  \"derivation_path_1\": \"m/129372'/0'/864559728'/1'\",\n  \"derivation_path_2\": \"m/129372'/0'/864559728'/2'\",\n  \"derivation_path_3\": \"m/129372'/0'/864559728'/3'\",\n  \"derivation_path_4\": \"m/129372'/0'/864559728'/4'\"\n}\n
    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Cashu NUTs (Notation, Usage, and Terminology)","text":"

    These documents each specify parts of the Cashu protocol. Read the specifications for the legacy API here.

    "},{"location":"#specifications","title":"Specifications","text":"

    Wallets and mints MUST implement all mandatory specs and CAN implement optional specs.

    "},{"location":"#mandatory","title":"Mandatory","text":"NUT # Description 00 Cryptography and Models 01 Mint public keys 02 Keysets and fees 03 Swapping tokens 04 Minting tokens 05 Melting tokens 06 Mint info"},{"location":"#optional","title":"Optional","text":"# Description Wallets Mints 07 Token state check Nutshell, Moksha, Nutstash, cashu-ts, cdk-cli Nutshell, Moksha, cdk-mintd, nutmix 08 Overpaid Lightning fees Nutshell, Moksha, Nutstash, cashu-ts, cdk-cli Nutshell, Moksha, cdk-mintd, nutmix 09 Signature restore Nutshell, cdk-cli, cashu-ts, gonuts Nutshell, cdk-mintd, nutmix 10 Spending conditions Nutshell, cdk-cli, cashu-ts Nutshell, cdk-mintd, nutmix 11 Pay-To-Pubkey (P2PK) Nutshell, cdk-cli, cashu-ts Nutshell, cdk-mintd, nutmix 12 DLEQ proofs Nutshell, cdk-cli Nutshell, cdk-mintd, nutmix 13 Deterministic secrets Nutshell, Moksha, cashu-ts, cdk-cli, gonuts - 14 Hashed Timelock Contracts (HTLCs) Nutshell, cdk-cli Nutshell, cdk-mintd, nutmix 15 Partial multi-path payments (MPP) Nutshell Nutshell 16 Animated QR codes Cashu.me - 17 WebSocket subscriptions Nutshell Nutshell 18 Payment requests Cashu.me, Boardwalk -"},{"location":"#wallets","title":"Wallets:","text":""},{"location":"#mints","title":"Mints:","text":""},{"location":"00/","title":"NUT-00: Notation, Utilization, and Terminology","text":"

    mandatory

    This document details the notation and models used throughout the specification and lays the groundwork for understanding the basic cryptography used in the Cashu protocol.

    "},{"location":"00/#blind-diffie-hellmann-key-exchange-bdhke","title":"Blind Diffie-Hellmann key exchange (BDHKE)","text":""},{"location":"00/#variables","title":"Variables","text":""},{"location":"00/#bob-mint","title":"Bob (mint)","text":""},{"location":"00/#alice-user","title":"Alice (user)","text":""},{"location":"00/#hash_to_curvex-bytes-curve-point-y","title":"hash_to_curve(x: bytes) -> curve point Y","text":"

    Deterministically maps a message to a public key point on the secp256k1 curve, utilizing a domain separator to ensure uniqueness.

    Y = PublicKey('02' || SHA256(msg_hash || counter)) where msg_hash is SHA256(DOMAIN_SEPARATOR || x)

    "},{"location":"00/#protocol","title":"Protocol","text":""},{"location":"00/#01-models","title":"0.1 - Models","text":""},{"location":"00/#blindedmessage","title":"BlindedMessage","text":"

    An encrypted (\"blinded\") secret and an amount is sent from Alice to Bob for minting tokens or for swapping tokens. A BlindedMessage is also called an output.

    {\n  \"amount\": int,\n  \"id\": hex_str,\n  \"B_\": hex_str\n}\n

    amount is the value for the requested BlindSignature, id is the requested keyset ID from which we expect a signature, and B_ is the blinded secret message generated by Alice. An array [BlindedMessage] is also referred to as BlindedMessages.

    "},{"location":"00/#blindsignature","title":"BlindSignature","text":"

    A BlindSignature is sent from Bob to Alice after minting tokens or after swapping tokens. A BlindSignature is also called a promise.

    {\n  \"amount\": int,\n  \"id\": hex_str,\n  \"C_\": hex_str\n}\n

    amount is the value of the blinded token, id is the keyset id of the mint keys that signed the token, and C_ is the blinded signature on the secret message B_ sent in the previous step.

    "},{"location":"00/#proof","title":"Proof","text":"

    A Proof is also called an input and is generated by Alice from a BlindSignature it received. An array [Proof] is called Proofs. Alice sends Proofs to Bob for melting tokens. Serialized Proofs can also be sent from Alice to Carol. Upon receiving the token, Carol deserializes it and requests a swap from Bob to receive new Proofs.

    {\n  \"amount\": int,\n  \"id\": hex_str,\n  \"secret\": str,\n  \"C\": hex_str,\n}\n

    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 of the mint public keys that signed the token (hex string).

    "},{"location":"00/#02-protocol","title":"0.2 - Protocol","text":""},{"location":"00/#errors","title":"Errors","text":"

    In case of an error, mints respond with the HTTP status code 400 and include the following data in their response:

    {\n  \"detail\": \"oops\",\n  \"code\": 1337\n}\n

    Here, detail is the error message, and code is the error code. Error codes are to be defined in the documents concerning the use of a certain API endpoint.

    "},{"location":"00/#03-methods","title":"0.3 - Methods","text":""},{"location":"00/#serialization-of-tokens","title":"Serialization of tokens","text":"

    Tokens can be serialized to send them between users Alice and Carol. Serialized tokens have a Cashu token prefix, a versioning flag, and the token. Optionally, a URI prefix for making tokens clickable on the web.

    We use the following format for token serialization:

    cashu[version][token]\n

    cashu is the Cashu token prefix. [version] is a single base64_urlsafe character to denote the token format version.

    "},{"location":"00/#uri-tags","title":"URI tags","text":"

    To make Cashu tokens clickable on the web, we use the URI scheme cashu:. An example of a serialized token with URI tag is

    cashu:cashuAeyJwcm9vZn...\n
    "},{"location":"00/#v3-tokens","title":"V3 tokens","text":"

    V3 tokens are deprecated and the use of the more space-efficient V4 tokens is encouraged.

    "},{"location":"00/#version","title":"Version","text":"

    This token format has the [version] value A.

    "},{"location":"00/#format","title":"Format","text":"

    V3 tokens are base64-encoded JSON objects. The token format supports tokens from multiple mints. The JSON is serialized with a base64_urlsafe (base64 encoding with / replaced by _ and + by -). base64_urlsafe strings may have padding characters (usually =) at the end which can be omitted. Clients need to be able to decode both cases.

    cashuA[base64_token_json]\n

    [base64_token_json] is the token JSON serialized in base64_urlsafe. [base64_token_json] should be cleared of any whitespace before serializing.

    "},{"location":"00/#token-format","title":"Token format","text":"

    The deserialized base64_token_json is

    {\n  \"token\": [\n    {\n      \"mint\": str,\n      \"proofs\": Proofs\n    },\n    ...\n  ],\n  \"unit\": str <optional>,\n  \"memo\": str <optional>\n}\n

    mint is the mint URL. The mint URL must be stripped of any trailing slashes (/). Proofs is an array of Proof objects. The next two elements are only for displaying the receiving user appropriate information: unit is the currency unit of the token keysets (see Keysets for supported units), and memo is an optional text memo from the sender.

    "},{"location":"00/#example","title":"Example","text":"

    Below is a TokenV3 JSON before base64_urlsafe serialization.

    {\n  \"token\": [\n    {\n      \"mint\": \"https://8333.space:3338\",\n      \"proofs\": [\n        {\n          \"amount\": 2,\n          \"id\": \"009a1f293253e41e\",\n          \"secret\": \"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\n          \"C\": \"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"\n        },\n        {\n          \"amount\": 8,\n          \"id\": \"009a1f293253e41e\",\n          \"secret\": \"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\n          \"C\": \"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"\n        }\n      ]\n    }\n  ],\n  \"unit\": \"sat\",\n  \"memo\": \"Thank you.\"\n}\n

    When serialized, this becomes:

    cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9\n
    "},{"location":"00/#v4-tokens","title":"V4 tokens","text":"

    V4 tokens are a space-efficient way of serializing tokens using the CBOR binary format. All field are single characters and hex strings are encoded in binary. V4 tokens can only hold proofs from a single mint.

    "},{"location":"00/#version_1","title":"Version","text":"

    This token format has the [version] value B.

    "},{"location":"00/#format_1","title":"Format","text":"

    Wallets serialize tokens in a base64_urlsafe format (base64 encoding with / replaced by _ and + by -). base64_urlsafe strings may have padding characters (usually =) at the end which can be omitted. Clients need to be able to decode both cases.

    cashuB[base64_token_cbor]\n
    "},{"location":"00/#token-format_1","title":"Token format","text":"

    The deserialized base64_token_cbor is a JSON of the same form as a TokenV4 but with shorter keys and data represented as binary data (bytes) instead of hex strings (hex_str). Note that we have expanded what is called Proofs in TokenV3 (called p here with TokenV4) showing that its values are also different from the TokenV3 serialization.

    {\n  \"m\": str, // mint URL\n  \"u\": str, // unit\n  \"d\": str <optional>, // memo\n  \"t\": [\n    {\n      \"i\": bytes, // keyset ID\n      \"p\": [ // proofs with this keyset ID\n        {\n          \"a\": int, // amount\n          \"s\": str, // secret\n          \"c\": bytes, // signature\n          \"d\": { <optional> // DLEQ proof\n            \"e\": bytes,\n            \"s\": bytes,\n            \"r\": bytes\n          },\n          \"w\": str <optional> // witness\n        },\n        ...\n      ]\n    },\n    ...\n  ],\n}\n

    m is the mint URL. The mint URL must be stripped of any trailing slashes (/). u is the currency unit of the token keysets (see Keysets for supported units), and d is an optional text memo from the sender.

    i is the keyset ID of the profs in p, which is an array of Proof objects without the id field. We extracted the keyset ID id from each proof and grouped all proofs by their keyset ID i one level above (in p).

    Note that all fields of the bytes type encode hex strings in the original representation of Proof's.

    "},{"location":"00/#example_1","title":"Example","text":"

    Below is a TokenV4 JSON before CBOR and base64_urlsafe serialization.

    {\n    \"t\": [\n        {\n            \"i\": h'00ffd48b8f5ecf80',\n            \"p\": [\n                {\n                    \"a\": 1,\n                    \"s\": \"acc12435e7b8484c3cf1850149218af90f716a52bf4a5ed347e48ecc13f77388\",\n                    \"c\": h'0244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cf',\n                },\n            ],\n        },\n        {\n            \"i\": h'00ad268c4d1f5826',\n            \"p\": [\n                {\n                    \"a\": 2,\n                    \"s\": \"1323d3d4707a58ad2e23ada4e9f1f49f5a5b4ac7b708eb0d61f738f48307e8ee\",\n                    \"c\": h'023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94d',\n                },\n                {\n                    \"a\": 1,\n                    \"s\": \"56bcbcbb7cc6406b3fa5d57d2174f4eff8b4402b176926d3a57d3c3dcbb59d57\",\n                    \"c\": h'0273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63',\n                },\n            ],\n        },\n    ],\n    \"m\": \"http://localhost:3338\",\n    \"u\": \"sat\",\n}\n

    The h'' values are bytes but displayed as hex strings here.

    We serialize this JSON using CBOR which can be seen here. The resulting bytes are then serialized to a string using base64_urlsafe and the prefix cashuB is added. This leaves us with the following serialized TokenV4:

    cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA\n
    "},{"location":"01/","title":"NUT-01: Mint public key exchange","text":"

    mandatory

    This document outlines the exchange of the public keys of the mint Bob with the wallet user Alice. Alice uses the keys to unblind Bob's blind signatures (see NUT-00).

    "},{"location":"01/#description","title":"Description","text":"

    Wallet user Alice receives public keys from mint Bob via GET /v1/keys. The set of all public keys for a set of amounts is called a keyset.

    The mint responds only with its active keysets. Keyset are active if the mint will sign promises with it. The mint will accept tokens from inactive keysets as inputs but will not sign with them for new outputs. The active keysets can change over time, for example due to key rotation. A list of all keysets, active and inactive, can be requested separately (see NUT-02).

    Note that a mint can support multiple keysets at the same time but will only respond with the active keysets on the endpoint GET /v1/keys. A wallet can ask for the keys of a specific (active or inactive) keyset via the endpoint GET /v1/keys/{keyset_id} (see NUT-02).

    "},{"location":"01/#keyset-generation","title":"Keyset generation","text":"

    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).

    Keys in Keysets are maps of the form {<amount_1> : <mint_pubkey_1>, <amount_2> : <mint_pubkey_2>, ...} for each <amount_i> of the amounts the mint Bob supports and the corresponding public key <mint_pubkey_1>, that is K_i (see NUT-00). The mint MUST use the compressed Secp256k1 public key format to represent its public keys.

    "},{"location":"01/#example","title":"Example","text":"

    Request of Alice:

    GET https://mint.host:3338/v1/keys\n

    With curl:

    curl -X GET https://mint.host:3338/v1/keys\n

    Response GetKeysResponse of Bob:

    {\n  \"keysets\": [\n    {\n      \"id\": <keyset_id_hex_str>,\n      \"unit\": <currency_unit_str>,\n      \"keys\": {\n        <amount_int>: <public_key_str>,\n        ...\n      }\n    }\n  ]\n}\n
    "},{"location":"01/#example-response","title":"Example response","text":"
    {\n  \"keysets\": [\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"unit\": \"sat\",\n      \"keys\": {\n          \"1\": \"02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104\",\n          \"2\": \"03b0f36d6d47ce14df8a7be9137712c42bcdd960b19dd02f1d4a9703b1f31d7513\",\n          \"4\": \"0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09\",\n          \"8\": \"0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d\",\n          ...\n      }\n    }\n  ]\n}\n
    "},{"location":"02/","title":"NUT-02: Keysets and fees","text":"

    mandatory

    A keyset is a set of public keys that the mint Bob generates and shares with its users. It refers to the set of public keys that each correspond to the amount values that the mint supports (e.g. 1, 2, 4, 8, ...) respectively.

    Each keyset indicates its keyset id, the currency unit, whether the keyset is active, and an input_fee_ppk that determines the fees for spending ecash from this keyset.

    A mint can have multiple keysets at the same time. For example, it could have one keyset for each currency unit that it supports. Wallets should support multiple keysets. They must respect the active and the input_fee_ppk properties of the keysets they use.

    "},{"location":"02/#keyset-properties","title":"Keyset properties","text":""},{"location":"02/#keyset-id","title":"Keyset ID","text":"

    A keyset id is an identifier for a specific keyset. It can be derived by anyone who knows the set of public keys of a mint. Wallets CAN compute the keyset id for a given keyset by themselves to confirm that the mint is supplying the correct keyset ID (see below).

    The keyset id is in each Proof so it can be used by wallets to identify which mint and keyset it was generated from. The keyset field id is also present in the BlindedMessages sent to the mint and BlindSignatures returned from the mint (see NUT-00).

    "},{"location":"02/#active-keysets","title":"Active keysets","text":"

    Mints can have multiple keysets at the same time but MUST have at least one active keyset (see NUT-01). The active property determines whether the mint allows generating new ecash from this keyset. Proofs from inactive keysets with active=false are still accepted as inputs but new outputs (BlindedMessages and BlindSignatures) MUST be from active keysets only.

    To rotate keysets, a mint can generate a new active keyset and inactive an old one. If the active flag of an old keyset is set to false, no new ecash from this keyset can be generated and the outstanding ecash supply of that keyset can be taken out of circulation as wallets rotate their ecash to active keysets.

    Wallets SHOULD prioritize swaps with Proofs from inactive keysets (see NUT-03) so they can quickly get rid of them. Wallets CAN swap their entire balance from an inactive keyset to an active one as soon as they detect that the keyset was inactivated. When constructing outputs for a transaction, wallets MUST choose only active keysets (see NUT-00).

    "},{"location":"02/#fees","title":"Fees","text":"

    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 keyest).

    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.

    "},{"location":"02/#wallet-transaction-construction","title":"Wallet transaction construction","text":"

    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:

    sum(inputs) - fees == sum(outputs)\n

    Here, sum(inputs) and sum(outputs) mean the sum of the amounts of the inputs and outputs respectively. fees is calculated from the sum of each input's fee and rounded up to the next larger integer:

    def fees(inputs: List[Proof]) -> int:\n  sum_fees = 0\n  for proof in inputs:\n    sum_fees += keysets[proof.id].input_fee_ppk\n  return (sum_fees + 999) // 1000\n

    Here, the // operator in (sum_fees + 999) // 1000 denotes an integer division operator (aka floor division operator) that rounds down sum_fees + 999 to the next lower integer. Alternatively, we could round up the sum using a floating point division with ceil(sum_fees / 1000) although it is not recommended to do so due to the non-deterministic behavior of floating point division.

    Notice that since transactions can spend inputs from different keysets, the sum considers the fee for each Proof indexed by the keyset ID individually.

    "},{"location":"02/#deriving-the-keyset-id","title":"Deriving the keyset ID","text":""},{"location":"02/#keyset-id-version","title":"Keyset ID version","text":"

    Keyset IDs have a version byte (two hexadecimal characters). The currently used version byte is 00.

    The mint and the wallets of its users can derive a keyset ID from the keyset of the mint. The keyset ID is a lower-case hex string. To derive the keyset ID of a keyset, execute the following steps:

    1 - sort public keys by their amount in ascending order\n2 - concatenate all public keys to one byte array\n3 - HASH_SHA256 the concatenated public keys\n4 - take the first 14 characters of the hex-encoded hash\n5 - prefix it with a keyset ID version byte\n

    An example implementation in Python:

    def derive_keyset_id(keys: Dict[int, PublicKey]) -> str:\n    sorted_keys = dict(sorted(keys.items()))\n    pubkeys_concat = b\"\".join([p.serialize() for p in sorted_keys.values()])\n    return \"00\" + hashlib.sha256(pubkeys_concat).hexdigest()[:14]\n
    "},{"location":"02/#example-get-mint-keysets","title":"Example: Get mint keysets","text":"

    A wallet can ask the mint for a list of all keysets via the GET /v1/keysets endpoint.

    Request of Alice:

    GET https://mint.host:3338/v1/keysets\n

    With curl:

    curl -X GET https://mint.host:3338/v1/keysets\n

    Response GetKeysetsResponse of Bob:

    {\n  \"keysets\": [\n    {\n      \"id\": <hex_str>,\n      \"unit\": <str>,\n      \"active\": <bool>,\n      \"input_fee_ppk\": <int|null>,\n    },\n    ...\n  ]\n}\n

    Here, id is the keyset ID, unit is the unit string (e.g. \"sat\") of the keyset, active indicates whether new ecash can be minted with this keyset, and input_fee_ppk is the fee (per thousand units) to spend one input spent from this keyset. If input_fee_ppk is not given, we assume it to be 0.

    "},{"location":"02/#example-response","title":"Example response","text":"
    {\n  \"keysets\": [\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"unit\": \"sat\",\n      \"active\": True,\n      \"input_fee_ppk\": 100\n    },\n    {\n      \"id\": \"0042ade98b2a370a\",\n      \"unit\": \"sat\",\n      \"active\": False,\n      \"input_fee_ppk\": 100\n    },\n    {\n      \"id\": \"00c074b96c7e2b0e\",\n      \"unit\": \"usd\",\n      \"active\": True,\n      \"input_fee_ppk\": 100\n    }\n  ]\n}\n
    "},{"location":"02/#requesting-public-keys-for-a-specific-keyset","title":"Requesting public keys for a specific keyset","text":"

    To receive the public keys of a specific keyset, a wallet can call the GET /v1/keys/{keyset_id} endpoint where keyset_id is the keyset ID.

    "},{"location":"02/#example","title":"Example","text":"

    Request of Alice:

    We request the keys for the keyset 009a1f293253e41e.

    GET https://mint.host:3338/v1/keys/009a1f293253e41e\n

    With curl:

    curl -X GET https://mint.host:3338/v1/keys/009a1f293253e41e\n

    Response of Bob (same as NUT-01):

    {\n  \"keysets\": [{\n    \"id\": \"009a1f293253e41e\",\n    \"unit\": \"sat\",\n    \"keys\": {\n        \"1\": \"02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104\",\n        \"2\": \"03b0f36d6d47ce14df8a7be9137712c42bcdd960b19dd02f1d4a9703b1f31d7513\",\n        \"4\": \"0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09\",\n        \"8\": \"0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d\",\n        ...\n    },\n  }, ...\n  ]\n}\n
    "},{"location":"02/#wallet-implementation-notes","title":"Wallet implementation notes","text":"

    Wallets can request the list of keyset IDs from the mint upon startup and load only tokens from its database that have a keyset ID supported by the mint it interacts with. This also helps wallets to determine whether the mint has added a new current keyset or whether it has changed the active flag of an existing one.

    A useful flow is:

    "},{"location":"03/","title":"NUT-03: Swap tokens","text":"

    mandatory

    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).

    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.

    "},{"location":"03/#swap-to-send","title":"Swap to send","text":"

    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.

    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.

    "},{"location":"03/#swap-to-receive","title":"Swap to receive","text":"

    Another useful case for the swap operation follows up the example above where Alice has swapped her Proofs ready to be sent to Carol. Carol can receive these Proofs using the same operation by using them as inputs to invalidate them and request new outputs from Bob. Only if Carol has redeemed new outputs, Alice can't double-spend the Proofs anymore and the transaction is settled. To continue our example, Carol requests a swap with input Proofs worth [32, 8] to receive new outputs (of an arbitrary distribution) with the same total amount.

    "},{"location":"03/#example","title":"Example","text":"

    Request of Alice:

    POST https://mint.host:3338/v1/swap\n

    With the data being of the form PostSwapRequest:

    {\n  \"inputs\": <Array[Proof]>,\n  \"outputs\": <Array[BlindedMessage]>,\n}\n

    With curl:

    curl -X POST https://mint.host:3338/v1/swap -d \\\n{\n  \"inputs\":\n    [\n      {\n        \"amount\": 2,\n        \"id\": \"009a1f293253e41e\",\n        \"secret\": \"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\n        \"C\": \"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"\n      },\n      {\n      ...\n      }\n    ],\n  \"outputs\":\n    [\n      {\n        \"amount\": 2,\n        \"id\": \"009a1f293253e41e\",\n        \"B_\": \"02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239\"\n      },\n      {\n      ...\n      }\n    ],\n}\n

    If successful, Bob will respond with a PostSwapResponse

    {\n  \"signatures\": <Array[BlindSignature]>\n}\n
    "},{"location":"04/","title":"NUT-04: Mint tokens","text":"

    mandatory

    Minting tokens is a two-step process: requesting a mint quote and minting new tokens. Here, we describe both steps.

    In the first request the wallet asks the mint for a quote for a specific amount and unit to mint, and the payment method to pay. The mint responds with a quote that includes a quote id and a payment request. The user pays the request and, if successful, requests minting of new tokens with the mint in a second request. The wallet includes the quote id and new outputs in the second request.

    We limit this document to mint quotes of unit=\"sat\" and method=\"bolt11\" which requests a bolt11 Lightning invoice (typically generated by the mint to add Bitcoin to its reserves) to mint ecash denominated in Satoshis.

    "},{"location":"04/#mint-quote","title":"Mint quote","text":"

    To request a mint quote, the wallet of Alice makes a POST /v1/mint/quote/{method} request where method is the payment method requested (here bolt11).

    POST https://mint.host:3338/v1/mint/quote/bolt11\n

    The wallet of Alice includes the following PostMintQuoteBolt11Request data in its request:

    {\n  \"amount\": <int>,\n  \"unit\": <str_enum[\"sat\"]>,\n  \"description\": <str|null>\n}\n

    with the requested amount and the unit. An optional description can be passed if the mint signals support for it in MintMethodSetting.

    The mint Bob then responds with a PostMintQuoteBolt11Response:

    {\n  \"quote\": <str>,\n  \"request\": <str>,\n  \"state\": <str_enum[STATE]>,\n  \"expiry\": <int>\n}\n

    Where quote is the quote ID and request is the payment request to fulfill. expiry is the Unix timestamp until which the mint quote is valid.

    state is an enum string field with possible values \"UNPAID\", \"PAID\", \"ISSUED\":

    Note: quote is a unique and random id generated by the mint to internally look up the payment state. quote MUST remain a secret between user and mint and MUST NOT be derivable from the payment request. A third party who knows the quote ID can front-run and steal the tokens that this operation mints.

    "},{"location":"04/#example","title":"Example","text":"

    Request of Alice with curl:

    curl -X POST http://localhost:3338/v1/mint/quote/bolt11 -d '{\"amount\": 10, \"unit\": \"sat\"}' -H \"Content-Type: application/json\"\n

    Response of Bob:

    {\n  \"quote\": \"DSGLX9kevM...\",\n  \"request\": \"lnbc100n1pj4apw9...\",\n  \"state\": \"UNPAID\",\n  \"expiry\": 1701704757\n}\n

    The wallet MUST store the amount in the request and the quote id in the response in its database so it can later request the tokens after paying the request. After payment, the wallet continues with the next section.

    "},{"location":"04/#check-mint-quote-state","title":"Check mint quote state","text":"

    To check whether a mint quote has been paid, Alice makes a GET /v1/mint/quote/bolt11/{quote_id}.

    GET https://mint.host:3338/v1/mint/quote/bolt11/{quote_id}\n

    Like before, the mint Bob responds with a PostMintQuoteBolt11Response.

    Example request of Alice with curl:

    curl -X GET http://localhost:3338/v1/mint/quote/bolt11/DSGLX9kevM...\n
    "},{"location":"04/#minting-tokens","title":"Minting tokens","text":"

    After requesting a mint quote and paying the request, the wallet proceeds with minting new tokens by calling the POST /v1/mint/{method} endpoint where method is the payment method requested (here bolt11).

    POST https://mint.host:3338/v1/mint/bolt11\n

    The wallet Alice includes the following PostMintBolt11Request data in its request

    {\n  \"quote\": <str>,\n  \"outputs\": <Array[BlindedMessage]>\n}\n

    with the quote being the quote ID from the previous step and outputs being BlindedMessages (see NUT-00) that the wallet requests signatures on whose sum is amount as requested in the quote.

    The mint Bob then responds with a PostMintBolt11Response:

    {\n  \"signatures\": <Array[BlindSignature]>\n}\n

    where signatures is an array of blind signatures on the outputs.

    "},{"location":"04/#example_1","title":"Example","text":"

    Request of Alice with curl:

    curl -X POST https://mint.host:3338/v1/mint/bolt11 -H \"Content-Type: application/json\" -d \\\n'{\n  \"quote\": \"DSGLX9kevM...\",\n  \"outputs\": [\n    {\n      \"amount\": 8,\n      \"id\": \"009a1f293253e41e\",\n      \"B_\": \"035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d\"\n    },\n    {\n      \"amount\": 2,\n      \"id\": \"009a1f293253e41e\",\n      \"B_\": \"0288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6\"\n    }\n  ]\n}'\n

    Response of Bob:

    {\n  \"signatures\": [\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"amount\": 2,\n      \"C_\": \"0224f1c4c564230ad3d96c5033efdc425582397a5a7691d600202732edc6d4b1ec\"\n    },\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"amount\": 8,\n      \"C_\": \"0277d1de806ed177007e5b94a8139343b6382e472c752a74e99949d511f7194f6c\"\n    }\n  ]\n}\n

    If the invoice was not paid yet, Bob responds with an error. In that case, Alice CAN repeat the same request until the Lightning invoice is settled.

    "},{"location":"04/#unblinding-signatures","title":"Unblinding signatures","text":"

    Upon receiving the BlindSignatures from the mint Bob, the wallet of Alice unblinds them to generate Proofs (using the blinding factor r and the mint's public key K, see BDHKE NUT-00). The wallet then stores these Proofs in its database:

    [\n  {\n    \"id\": \"009a1f293253e41e\",\n    \"amount\": 2,\n    \"secret\": \"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\n    \"C\": \"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"\n  },\n  {\n    \"id\": \"009a1f293253e41e\",\n    \"amount\": 8,\n    \"secret\": \"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\n    \"C\": \"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"\n  }\n]\n
    "},{"location":"04/#settings","title":"Settings","text":"

    The settings for this nut indicate the supported method-unit pairs for minting and whether minting is disabled or not. They are part of the info response of the mint (NUT-06) which in this case reads

    {\n  \"4\": {\n    \"methods\": [\n      <MintMethodSetting>,\n      ...\n    ],\n    \"disabled\": <bool>\n  }\n}\n

    MintMethodSetting indicates supported method and unit pairs and additional settings of the mint. disabled indicates whether this minting is disabled.

    MintMethodSetting is of the form:

    {\n  \"method\": <str>,\n  \"unit\": <str>,\n  \"min_amount\": <int|null>,\n  \"max_amount\": <int|null>,\n  \"description\": <bool|null>\n}\n

    min_amount and max_amount indicate the minimum and maximum amount for an operation of this method-unit pair.

    Example MintMethodSetting:

    {\n  \"method\": \"bolt11\",\n  \"unit\": \"sat\",\n  \"min_amount\": 0,\n  \"max_amount\": 10000,\n  \"description\": true\n}\n
    "},{"location":"05/","title":"NUT-05: Melting tokens","text":"

    mandatory

    used in: NUT-08, NUT-15

    Melting tokens is the opposite of minting tokens (see NUT-04). Like minting tokens, melting is a two-step process: requesting a melt quote and melting tokens. Here, we describe both steps.

    In the first request the wallet asks the mint for a quote for a request it wants paid by the mint and the unit the wallet would like to spend as inputs. The mint responds with a quote that includes a quote id and an amount the mint demands in the requested unit. For the method bolt11, the mint includes a fee_reserve field indicating the reserve fee for a Lightning payment.

    In the second request, the wallet includes the quote id and provides inputs that sum up to amount+fee_reserve in the first response. For the method bolt11, the wallet can also include outputs in order for the mint to return overpaid Lightning fees (see NUT-08). The mint responds with a payment state. If the state is \"PAID\" the response includes a payment_preimage as a proof of payment. If the request included outputs, the mint may respond with change for the overpaid fees (see NUT-08).

    We limit this document to mint quotes of unit=\"sat\" and method=\"bolt11\" which requests a bolt11 Lightning payment (typically paid by the mint from its Bitcoin reserves) using ecash denominated in Satoshis.

    "},{"location":"05/#melt-quote","title":"Melt quote","text":"

    To request a melt quote, the wallet of Alice makes a POST /v1/melt/quote/{method} request where method is the payment method requested (here bolt11).

    POST https://mint.host:3338/v1/melt/quote/bolt11\n

    The wallet Alice includes the following PostMeltQuoteBolt11Request data in its request:

    {\n  \"request\": <str>,\n  \"unit\": <str_enum[\"sat\"]>\n}\n

    Here, request is the bolt11 Lightning invoice to be paid and unit is the unit the wallet would like to pay with.

    The mint Bob then responds with a PostMeltQuoteBolt11Response:

    {\n  \"quote\": <str>,\n  \"amount\": <int>,\n  \"fee_reserve\": <int>,\n  \"state\": <str_enum[STATE]>,\n  \"expiry\": <int>,\n  \"payment_preimage\": <str|null>\n}\n

    Where quote is the quote ID, amount the amount that needs to be provided, and fee_reserve the additional fee reserve that is required. The mint expects Alice to include Proofs of at least total_amount = amount + fee_reserve. expiry is the Unix timestamp until which the melt quote is valid. payment_preimage is the bolt11 payment preimage in case of a successful payment.

    state is an enum string field with possible values \"UNPAID\", \"PENDING\", \"PAID\":

    "},{"location":"05/#example","title":"Example","text":"

    Request of Alice with curl:

    curl -X POST https://mint.host:3338/v1/melt/quote/bolt11 -d \\\n{\n  \"request\": \"lnbc100n1p3kdrv5sp5lpdxzghe5j67q...\",\n  \"unit\": \"sat\"\n}\n

    Response of Bob:

    {\n  \"quote\": \"TRmjduhIsPxd...\",\n  \"amount\": 10,\n  \"fee_reserve\": 2,\n  \"state\": \"UNPAID\",\n  \"expiry\": 1701704757\n}\n
    "},{"location":"05/#check-melt-quote-state","title":"Check melt quote state","text":"

    To check whether a melt quote has been paid, Alice makes a GET /v1/melt/quote/bolt11/{quote_id}.

    GET https://mint.host:3338/v1/melt/quote/bolt11/{quote_id}\n

    Like before, the mint Bob responds with a PostMeltQuoteBolt11Response.

    Example request of Alice with curl:

    curl -X GET http://localhost:3338/v1/melt/quote/bolt11/TRmjduhIsPxd...\n
    "},{"location":"05/#melting-tokens","title":"Melting tokens","text":"

    Now that Alice knows what the total amount is (amount + fee_reserve) in her requested unit, she can proceed to melting tokens for which a payment will be executed by the mint. She calls the POST /v1/melt/{method} endpoint where method is the payment method requested (here bolt11).

    POST https://mint.host:3338/v1/melt/bolt11\n

    \u26a0\ufe0f Attention: This call will block until the Lightning payment either succeeds or fails. This can take quite a long time in case the Lightning payment is slow. Make sure to use no (or a very long) timeout when making this call!

    The wallet of Alice includes the following PostMeltBolt11Request data in its request

    {\n  \"quote\": <str>,\n  \"inputs\": <Array[Proof]>\n}\n

    Here, quote is the melt quote ID to be paid and inputs are the proofs with a total amount of at least amount + fee_reserve (see previous melt quote response).

    Like before, the mint Bob then responds with a PostMeltQuoteBolt11Response. If the payment was successful, the state field is set to \"PAID\" and the response includes the payment_preimage field containing the payment secret of the bolt11 payment.

    If state==\"PAID\", Alice's wallet can delete the inputs from her database (or move them to a history). If state==\"UNPAID\", Alice can repeat the same request again until the payment is successful.

    "},{"location":"05/#example_1","title":"Example","text":"

    Request of Alice with curl:

    curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \\\n'{\n  \"quote\": \"od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP\",\n  \"inputs\": [\n    {\n      \"amount\": 4,\n      \"id\": \"009a1f293253e41e\",\n      \"secret\": \"429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5\",\n      \"C\": \"03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011\",\n    },\n    {\n      \"amount\": 8,\n      \"id\": \"009a1f293253e41e\",\n      \"secret\": \"4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad\",\n      \"C\": \"0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9\",\n    }\n  ]\n}'\n

    Response PostMeltQuoteBolt11Response of Bob:

    {\n  \"quote\": \"TRmjduhIsPxd...\",\n  \"amount\": 10,\n  \"fee_reserve\": 2,\n  \"state\": \"PAID\",\n  \"expiry\": 1701704757,\n  \"payment_preimage\": \"c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b\"\n}\n
    "},{"location":"05/#settings","title":"Settings","text":"

    The mint's settings for this nut indicate the supported method-unit pairs for melting. They are part of the info response of the mint (NUT-06) which in this case reads

    {\n  \"5\": {\n    \"methods\": [\n      <MeltMethodSetting>,\n      ...\n    ],\n    \"disabled\": <bool>\n  }\n}\n

    MeltMethodSetting indicates supported method and unit pairs and additional settings of the mint. disabled indicates whether melting is disabled.

    MeltMethodSetting is of the form:

    {\n  \"method\": <str>,\n  \"unit\": <str>,\n  \"min_amount\": <int|null>,\n  \"max_amount\": <int|null>\n}\n

    min_amount and max_amount indicate the minimum and maximum amount for an operation of this method-unit pair.

    Example MeltMethodSetting:

    {\n  \"method\": \"bolt11\",\n  \"unit\": \"sat\",\n  \"min_amount\": 100,\n  \"max_amount\": 10000\n}\n
    "},{"location":"06/","title":"NUT-06: Mint information","text":"

    mandatory

    This endpoint returns information about the mint that a wallet can show to the user and use to make decisions on how to interact with the mint.

    "},{"location":"06/#example","title":"Example","text":"

    Request of Alice:

    GET https://mint.host:3338/v1/info\n

    With the mint's response being of the form GetInfoResponse:

    {\n  \"name\": \"Bob's Cashu mint\",\n  \"pubkey\": \"0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99\",\n  \"version\": \"Nutshell/0.15.0\",\n  \"description\": \"The short mint description\",\n  \"description_long\": \"A description that can be a long piece of text.\",\n  \"contact\": [\n    {\n      \"method\": \"email\",\n      \"info\": \"contact@me.com\"\n    },\n    {\n      \"method\": \"twitter\",\n      \"info\": \"@me\"\n    },\n    {\n      \"method\": \"nostr\",\n      \"info\": \"npub...\"\n    }\n  ],\n  \"motd\": \"Message to display to users.\",\n  \"icon_url\": \"https://mint.host/icon.jpg\",\n  \"time\": 1725304480,\n  \"nuts\": {\n    \"4\": {\n      \"methods\": [\n        {\n          \"method\": \"bolt11\",\n          \"unit\": \"sat\",\n          \"min_amount\": 0,\n          \"max_amount\": 10000\n        }\n      ],\n      \"disabled\": false\n    },\n    \"5\": {\n      \"methods\": [\n        {\n          \"method\": \"bolt11\",\n          \"unit\": \"sat\",\n          \"min_amount\": 100,\n          \"max_amount\": 10000\n        }\n      ],\n      \"disabled\": false\n    },\n    \"7\": {\n      \"supported\": true\n    },\n    \"8\": {\n      \"supported\": true\n    },\n    \"9\": {\n      \"supported\": true\n    },\n    \"10\": {\n      \"supported\": true\n    },\n    \"12\": {\n      \"supported\": true\n    }\n  }\n}\n

    With curl:

    curl -X GET https://mint.host:3338/v1/info\n
    "},{"location":"07/","title":"NUT-07: Token state check","text":"

    optional

    used in: NUT-17, NUT-11, NUT-14

    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.

    "},{"location":"07/#token-states","title":"Token states","text":"

    A proof can be in one of the following states

    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.

    "},{"location":"07/#use-cases","title":"Use cases","text":""},{"location":"07/#example-1-ecash-transaction","title":"Example 1: Ecash transaction","text":"

    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.

    "},{"location":"07/#example-2-lightning-payments","title":"Example 2: Lightning payments","text":"

    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).

    "},{"location":"07/#example","title":"Example","text":"

    Request of Alice:

    POST https://mint.host:3338/v1/checkstate\n

    With the data being of the form PostCheckStateRequest:

    {\n  \"Ys\": <Array[hex_str]>,\n}\n

    Where the elements of the array in Ys are the hexadecimal representation of the compressed point Y = hash_to_curve(secret) of the Proof to check (see NUT-00).

    Response of Bob:

    Bob responds with a PostCheckStateResponse:

    {\n  \"states\": [\n    {\n      \"Y\": <hex_str>,\n      \"state\": <str_enum[STATE]>,\n      \"witness\": <str|null>,\n    },\n    ...\n  ]\n}\n

    The elements of the states array MUST be returned in the same order as the corresponding Ys checked in the request.

    With curl:

    Request of Alice:

    curl -X POST https://mint.host:3338/v1/checkstate -H 'Content-Type: application/json' -d '{\n  \"Ys\": [\n    \"02599b9ea0a1ad4143706c2a5a4a568ce442dd4313e1cf1f7f0b58a317c1a355ee\"\n  ]\n}'\n

    Response of Bob:

    {\n  \"states\": [\n    {\n      \"Y\": \"02599b9ea0a1ad4143706c2a5a4a568ce442dd4313e1cf1f7f0b58a317c1a355ee\",\n      \"state\": \"SPENT\",\n      \"witness\": \"{\\\"signatures\\\": [\\\"b2cf120a49cb1ac3cb32e1bf5ccb6425e0a8372affdc1d41912ca35c13908062f269c0caa53607d4e1ac4c8563246c4c8a869e6ee124ea826fd4746f3515dc1e\\\"]}\"\n    }\n  ]\n}\n

    Where Y belongs to the provided Proof to check in the request, state indicates its state, and witness is the witness data that was potentially provided in a previous spend operation (can be empty).

    "},{"location":"08/","title":"NUT-08: Lightning fee return","text":"

    optional

    depends on: NUT-05

    This document describes how the overpaid Lightning fees are handled and extends NUT-05 which describes melting tokens (i.e. paying a Lightning invoice). In short, a wallet includes blank outputs when paying a Lightning invoice which can be assigned a value by the mint if the user has overpaid Lightning fees. This can be the case due to the unpredictability of Lightning network fees. To solve this issue, we introduce so-called blank outputs which are blinded messages with an undetermined value.

    The problem is also described in this gist.

    "},{"location":"08/#description","title":"Description","text":"

    Before requesting a Lightning payment as described in NUT-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.

    This code calculates the number of necessary blank outputs in Python:

    def calculate_number_of_blank_outputs(fee_reserve_sat: int) -> int:\n    assert fee_reserve_sat >= 0, \"Fee reserve can't be negative.\"\n    if fee_reserve_sat == 0:\n        return 0\n    return max(math.ceil(math.log2(fee_reserve_sat)), 1)\n
    "},{"location":"08/#example","title":"Example","text":"

    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 BlindSignatures to the wallet together with the successful payment status.

    "},{"location":"08/#wallet-flow","title":"Wallet flow","text":"

    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. The wallet then provides a PostMeltBolt11Request to POST /v1/melt/bolt11 that has (1) proofs of the value amount+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.

    "},{"location":"08/#mint-flow","title":"Mint flow","text":"

    Here we describe how the mint generates BlindSignatures 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 Lightning actual_fees were smaller than the fee_reserve.

    If the overpaid_fees = fee_reserve - actual_fees is positive, Bob decomposes it to values of 2^n (as in NUT-00) and then imprints them into the blank_outputs provided by Alice.

    Bob then signs these blank outputs (now with the imprinted amounts) and thus generates BlindSignatures. 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.

    "},{"location":"08/#example_1","title":"Example","text":"

    Request of Alice:

    POST https://mint.host:3338/v1/melt/bolt11\n

    With the data being of the form PostMeltBolt11Request:

    {\n  \"quote\": <str>,\n  \"inputs\": <Array[Proof]>,\n  \"outputs\": <Array[BlindedMessage]> <-- New\n}\n

    where the new output field carries the BlindMessages.

    The mint Bob then responds with a PostMeltQuoteBolt11Response:

    {\n  \"quote\": <str>,\n  \"amount\": <int>,\n  \"fee_reserve\": <int>,\n  \"state\": <str_enum[STATE]>,\n  \"expiry\": <int>,\n  \"payment_preimage\": <str|null>,\n  \"change\": <Array[BlindSignature]> <-- New\n}\n

    where the new change field carries the returned BlindSignatures due to overpaid fees.

    "},{"location":"08/#example_2","title":"Example","text":"

    Request of Alice with curl:

    curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \\\n'{\n  \"quote\": \"od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP\",\n  \"inputs\": [\n    {\n      \"amount\": 4,\n      \"id\": \"009a1f293253e41e\",\n      \"secret\": \"429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5\",\n      \"C\": \"03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011\",\n    },\n    {\n      \"amount\": 8,\n      \"id\": \"009a1f293253e41e\",\n      \"secret\": \"4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad\",\n      \"C\": \"0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9\",\n    }\n  ],\n  \"outputs\": [\n    {\n      \"amount\": 1,\n      \"id\": \"009a1f293253e41e\",\n      \"B_\": \"03327fc4fa333909b70f08759e217ce5c94e6bf1fc2382562f3c560c5580fa69f4\"\n    }\n  ]\n}'\n

    Everything here is the same as in NUT-05 except for outputs. The amount field in the BlindedMessages here are ignored by Bob so they can be set to any arbitrary value by Alice (they should be set to a value, like 1 so potential JSON validations do not error).

    If the mint has made a successful payment, it will respond the following.

    Response PostMeltQuoteBolt11Response from Bob:

    {\n  \"state\": \"PAID\",\n  \"payment_preimage\": \"c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b\",\n  \"change\": [\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"amount\": 2,\n      \"C_\": \"03c668f551855ddc792e22ea61d32ddfa6a45b1eb659ce66e915bf5127a8657be0\"\n    }\n  ]\n}\n

    The field change is an array of BlindSignatures that account for the overpaid fees. Notice that the amount has been changed by the mint. Alice must take these and generate Proofs by unblinding them as described in NUT-00 and as she does in NUT-04 when minting new tokens. After generating the Proofs, Alice stores them in her database.

    "},{"location":"09/","title":"NUT-09: Restore signatures","text":"

    optional

    used in: NUT-13

    In this document, we describe how wallets can recover blind signatures, and with that their corresponding Proofs, by requesting from the mint to reissue the blind signatures. This can be used for a backup recovery of a lost wallet (see NUT-13) or for recovering the response of an interrupted swap request (see NUT-03).

    Mints must store the BlindedMessage and the corresponding BlindSignature in their database every time they issue a BlindSignature. Wallets provide the BlindedMessage for which they request the BlindSignature. Mints only respond with a BlindSignature, if they have previously signed the BlindedMessage. Each returned BlindSignature also contains the amount and the keyset id (see NUT-00) which is all the necessary information for a wallet to recover a Proof.

    Request of Alice:

    POST https://mint.host:3338/v1/restore\n

    With the data being of the form PostRestoreRequest:

    {\n  \"outputs\": <Array[BlindedMessages]>\n}\n

    Response of Bob:

    The mint Bob then responds with a PostRestoreResponse.

    {\n  \"outputs\": <Array[BlindedMessages]>,\n  \"signatures\": <Array[BlindSignature]>\n}\n

    The returned arrays outputs and signatures are of the same length and for every entry outputs[i], there is a corresponding entry signatures[i].

    "},{"location":"10/","title":"NUT-10: Spending conditions","text":"

    optional

    used in: NUT-11, NUT-14

    An ordinary ecash token is a set of Proofs each with a random string secret. To spend such a token in a swap or a melt operation, wallets include proofs in their request each with a unique secret. To autorize a transaction, the mint requires that the secret has not been seen before. This is the most fundamental spending condition in Cashu, which ensures that a token can't be double-spent.

    In this NUT, we define a well-known format of secret that can be used to express more complex spending conditions. These conditions need to be met before the mint authorizes a transaction. Note that the specific type of spending condition is not part of this document but will be explained in other documents. Here, we describe the structure of secret which is expressed as a JSON Secret with a specific format.

    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 endpoint.

    "},{"location":"10/#basic-components","title":"Basic components","text":"

    An ecash transaction, i.e., a swap or a melt operation, with a spending condition consists of the following components:

    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 Secrets 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.

    "},{"location":"10/#well-known-secret","title":"Well-known Secret","text":"

    Spending conditions are expressed in a well-known secret format that is revealed to the mint when spending (unlocking) a token, not when the token is minted (locked). The mint parses each Proof's secret. If it can deserialize it into the following format it executes additional spending conditions that are further specified in additional NUTs.

    The well-known Secret stored in Proof.secret is a JSON of the format:

    [\nkind <str>,\n  {\n    \"nonce\": <str>,\n    \"data\": <str>,\n    \"tags\": [[ \"key\", \"value1\", \"value2\", ...],  ... ], // (optional)\n  }\n]\n
    "},{"location":"10/#examples","title":"Examples","text":"

    Example use cases of this secret format are

    "},{"location":"11/","title":"NUT-11: Pay to Public Key (P2PK)","text":"

    optional

    depends on: NUT-10

    This NUT describes Pay-to-Public-Key (P2PK) which is one kind of spending condition based on NUT-10's well-known Secret. Using P2PK, we can lock ecash tokens to a receiver's ECC public key and require a Schnorr signature with the corresponding private key to unlock the ecash. The spending condition is enforced by the mint.

    Caution: If the mint does not support this type 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 endpoint.

    "},{"location":"11/#pay-to-pubkey","title":"Pay-to-Pubkey","text":"

    NUT-10 Secret kind: P2PK

    If for a Proof, Proof.secret is a Secret of kind P2PK, the proof must be unlocked by providing a witness Proof.witness and one or more valid signatures in the array Proof.witness.signatures.

    In the basic case, when spending a locked token, the mint requires one valid Schnorr signature in Proof.witness.signatures on Proof.secret by the public key in Proof.Secret.data.

    To give a concrete example of the basic case, to mint a locked token we first create a P2PK Secret that reads:

    [\n  \"P2PK\",\n  {\n    \"nonce\": \"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\n    \"data\": \"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\n    \"tags\": [[\"sigflag\", \"SIG_INPUTS\"]]\n  }\n]\n

    Here, Secret.data is the public key of the recipient of the locked ecash. We serialize this Secret to a string in Proof.secret and get a blind signature by the mint that is stored in Proof.C (see NUT-03]).

    The recipient who owns the private key of the public key Secret.data can spend this proof by providing a signature on the serialized Proof.secret string that is then added to Proof.witness.signatures:

    {\n  \"amount\": 1,\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"id\": \"009a1f293253e41e\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\\\"]}\"\n}\n
    "},{"location":"11/#signature-scheme","title":"Signature scheme","text":"

    To spend a token locked with P2PK, the spender needs to include signatures in the spent proofs. We use libsecp256k1's serialized 64 byte Schnorr signatures on the SHA256 hash of the message to sign. The message to sign is the field Proof.secret in the inputs. If indicated by Secret.tags.sigflag in the inputs, outputs might also require signatures on the message BlindedMessage.B_.

    An ecash spending operation like swap and melt can have multiple inputs and outputs. If we have more than one input or output, we provide signatures in each Proof and BlindedMessage individually. The inputs are the Proofs provided in the inputs field and the outputs are the BlindedMessages in the outputs field in the request body (see PostMeltRequest in NUT-05 and PostSwapRequest in NUT-03).

    "},{"location":"11/#tags","title":"Tags","text":"

    More complex spending conditions can be defined in the tags in Proof.tags. All tags are optional. Tags are arrays with two or more strings being [\"key\", \"value1\", \"value2\", ...].

    Supported tags are:

    Note: The tag serialization type is [<str>, <str>, ...] but some tag values are int. Wallets and mints must cast types appropriately for de/serialization.

    "},{"location":"11/#signature-flags","title":"Signature flags","text":"

    Signature flags are defined in the tag Secret.tags['sigflag']. Currently, there are two signature flags.

    The signature flag SIG_ALL is enforced if at least one of the Proofs have the flag SIG_ALL. Otherwise, SIG_INPUTS is enforced.

    "},{"location":"11/#signature","title":"Signature","text":"

    Signatures must be provided in the field Proof.witness.signatures for each Proof which is an input. If the signature flag SIG_ALL is enforced, signatures must also be provided for every output in its field BlindedMessage.witness.signatures.

    "},{"location":"11/#signed-inputs","title":"Signed inputs","text":"

    A Proof (an input) with a signature P2PKWitness.signatures on secret is the JSON (see NUT-00):

    {\n  \"amount\": <int>,\n  \"secret\": <str>,\n  \"C\": <hex_str>,\n  \"id\": <str>,\n  \"witness\": <P2PKWitness | str> // Signatures on \"secret\"\n}\n

    The secret of each input is signed as a string.

    "},{"location":"11/#signed-outputs","title":"Signed outputs","text":"

    A BlindedMessage (an output) with a signature P2PKWitness.signatures on B_ is the JSON (see NUT-00):

    {\n  \"amount\": <int>,\n  \"B_\": <hex_str>,\n  \"witness\": <P2PKWitness | str> // Signatures on \"B_\"\n}\n

    The B_ of each output is signed as bytes which comes from the original hex string.

    "},{"location":"11/#witness-format","title":"Witness format","text":"

    P2PKWitness is a serialized JSON string of the form

    {\n  \"signatures\": <Array[<hex_str>]>\n}\n

    The signatures are an array of signatures in hex. The witness for a spent proof can be obtained with a Proof state check (see NUT-07).

    "},{"location":"11/#multisig","title":"Multisig","text":"

    If the tag n_sigs is a positive integer, the mint will also consider signatures from public keys specified in the pubkeys tag additional to the public key in Secret.data. If the number of valid signatures is greater or equal to the number specified in n_sigs, the transaction is valid.

    Expressed as an \"n-of-m\" scheme, n = n_sigs is the number of required signatures and m = 1 (\"data\" field) + len(pubkeys tag) is the number of public keys that could sign.

    "},{"location":"11/#locktime","title":"Locktime","text":"

    If the tag locktime is the unix time and the mint's local clock is greater than locktime, the Proof becomes spendable by anyone, except if the following condition is also true. Note: A Proof is considered spendable by anyone if it only requires a secret and a valid signature C to be spent (which is the default case).

    "},{"location":"11/#refund-public-keys","title":"Refund public keys","text":"

    If the locktime is in the past and a tag refund is present, the Proof is spendable only if a valid signature by one of the the refund pubkeys is provided in Proof.witness.signatures and, depending on the signature flag, in BlindedMessage.witness.signatures.

    "},{"location":"11/#complex-example","title":"Complex Example","text":"

    This is an example secret that locks a Proof with a Pay-to-Pubkey (P2PK) condition that requires 2-of-3 signatures from the public keys in the data field and the pubkeys tag. If the timelock has passed, the Proof becomes spendable with a single signature from the public key in the refund tag. The signature flag sigflag indicates that signatures are necessary on the inputs and the outputs of a transaction.

    [\n  \"P2PK\",\n  {\n    \"nonce\": \"da62796403af76c80cd6ce9153ed3746\",\n    \"data\": \"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e\",\n    \"tags\": [\n      [\"sigflag\", \"SIG_ALL\"],\n      [\"n_sigs\", \"2\"],\n      [\"locktime\", \"1689418329\"],\n      [\n        \"refund\",\n        \"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e\"\n      ],\n      [\n        \"pubkeys\",\n        \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n        \"023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54\"\n      ]\n    ]\n  }\n]\n
    "},{"location":"11/#use-cases","title":"Use cases","text":"

    The following use cases are unlocked using P2PK:

    "},{"location":"12/","title":"NUT-12: Offline ecash signature validation","text":"

    optional

    In this document, we present an extension of Cashu's crypto system to allow a user Alice to verify the mint Bob's signature using only Bob's public keys. We explain how another user Carol who receives ecash from Alice can execute the DLEQ proof as well. This is achieved using a Discrete Log Equality (DLEQ) proof. Previously, Bob's signature could only be checked by himself using his own private keys (NUT-00).

    "},{"location":"12/#the-dleq-proof","title":"The DLEQ proof","text":"

    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) and for signing the BlindedMessage B'. Bob returns the DLEQ proof additional to the blind signature C' for a mint or swap operation.

    The complete DLEQ proof reads

    # DLEQ Proof\n\n(These steps occur when Bob returns C')\n\nBob:\nr = random nonce\nR1 = r*G\nR2 = r*B'\ne = hash(R1,R2,A,C')\ns = r + e*a\nreturn e, s\n\nAlice:\nR1 = s*G - e*A\nR2 = s*B' - e*C'\ne == hash(R1,R2,A,C')\n\nIf true, a in A = a*G must be equal to a in C' = a*B'\n
    "},{"location":"12/#hashx-arraypublickey-bytes","title":"hash(x: <Array<[PublicKey]>) -> bytes","text":"

    The hash(x) function generates a deterministic Sha256 hash for a given input list of PublicKey. The uncompressed hexadecimal representations of each PublicKey is concatenated before taking the Sha256 hash.

    def hash_e(*publickeys: PublicKey) -> bytes:\n    e_ = \"\"\n    for p in publickeys:\n        _p = p.serialize(compressed=False).hex()\n        e_ += str(_p)\n    e = hashlib.sha256(e_.encode(\"utf-8\")).digest()\n    return e\n
    "},{"location":"12/#mint-to-user-dleq-in-blindsignature","title":"Mint to user: DLEQ in BlindSignature","text":"

    The mint produces these DLEQ proofs when returning BlindSignature's in the responses for minting (NUT-04) and swapping (NUT-03) tokens. The BlindSignature object is extended in the following way to include the DLEQ proof:

    {\n  \"id\": <str>,\n  \"amount\": <int>,\n  \"C_\": <str>,\n  \"dleq\": { <-- New: DLEQ proof\n    \"e\": <str>,\n    \"s\": <str>\n  }\n}\n

    e and s are the DLEQ proof.

    "},{"location":"12/#user-to-user-dleq-in-proof","title":"User to user: DLEQ in Proof","text":"

    In order for Alice to communicate the DLEQ to another user Carol, we extend the Proof (see NUT-00) object and include the DLEQ proof. As explained below, we also need to include the blinding factor r for the proof to be convincing to another user Carol.

    {\n  \"id\": <str>,\n  \"amount\": <int>,\n  \"secret\": <str>,\n  \"C\": <str>,\n  \"dleq\": { <-- New: DLEQ proof\n    \"e\": <str>,\n    \"s\": <str>,\n    \"r\": <str>\n  }\n}\n

    e and s are the challenge and response of the DLEQ proof returned by Bob, r is the blinding factor of Alice that was used to generate the Proof. Alice serializes these proofs like any other in a token (see NUT-00) to send it to another user Carol.

    "},{"location":"12/#alice-minting-user-verifies-dleq-proof","title":"Alice (minting user) verifies DLEQ proof","text":"

    When minting or swapping tokens, Alice receives DLEQ proofs in the BlindSignature response from the mint Bob. Alice checks the validity of the DLEQ proofs for each ecash token she receives via the equations:

    R1 = s*G - e*A\nR2 = s*B' - e*C'\ne == hash(R1,R2,A,C') # must be True\n

    Here, the variables are

    In order to execute the proof, Alice needs e, s that are returned in the BlindSignature by Bob. Alice further needs B' (the BlindedMessage Alice created and Bob signed) and C' (the blind signature in the BlindSignature response) from Bob, and A (the public key of Bob with which he signed the BlindedMessage). All these values are available to Alice during or after calling the mint and swap operations.

    If a DLEQ proof is included in the mint's BlindSignature response, wallets MUST verify the DLEQ proof.

    "},{"location":"12/#carol-another-user-verifies-dleq-proof","title":"Carol (another user) verifies DLEQ proof","text":"

    Carol is a user that receives Proofs in a token from another user Alice. When Alice sends Proofs with DLEQ proofs to Carol or when Alice posts the Proofs publicly, Carol can validate the DLEQ proof herself and verify Bob's signature without having to talk to Bob. Alice includes the following information in the Proof (see above):

    Here, x is the Proof's secret, and C is the mint's signature on it. To execute the DLEQ proof like Alice did above, Carol needs (B', C') which she can compute herself using the blinding factor r that she receives from Alice.

    To verify the DLEQ proof of a received token, Carol needs to reconstruct B' and C' using the blinding factor r that Alice has included in the Proof she sent to Carol. Since Carol now has all the necessary information, she can execute the same equations to verify the DLEQ proof as Alice did:

    Y = hash_to_curve(x)\nC' = C + r*A\nB' = Y + r*G\n\nR1 = ... (same as Alice)\n

    If a DLEQ proof is included in a received token, wallets MUST verify the proof.

    "},{"location":"13/","title":"NUT-13: Deterministic Secrets","text":"

    optional

    depends on: NUT-09

    In this document, we describe the process that allows wallets to recover their ecash balance with the help of the mint using a familiar 12 word seed phrase (mnemonic). This allows us to restore the wallet's previous state in case of a device loss or other loss of access to the wallet. The basic idea is that wallets that generate the ecash deterministically can regenerate the same tokens during a recovery process. For this, they ask the mint to reissue previously generated signatures using NUT-09.

    "},{"location":"13/#deterministic-secret-derivation","title":"Deterministic secret derivation","text":"

    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 private_key derived from a 12-word BIP39 mnemonic seed phrase that the user stores in a secure place. The wallet uses the private_key, 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 needs to keep track of a separate counter for each keyset k it uses. Typically, the wallet will need to keep track of multiple keysets for every mint it interacts with. counter_k is used to generate a BIP32 derivation path which can then be used to derive secret and r.

    The following BIP32 derivation path is used. The derivation path depends on the keyset ID of keyset_k, and the counter_k of that keyset.

    m / 129372' / 0' / keyset_k_int' / counter' / secret||r

    This results in the following derivation paths:

    secret_derivation_path = `m/129372'/0'/{keyset_k_int}'/{counter_k}'/0`\nr_derivation_path = `m/129372'/0'/{keyset_id_k_int}'/{counter_k}'/1`\n

    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.

    Note: For examples, see the test vectors.

    "},{"location":"13/#counter","title":"Counter","text":"

    The wallet starts with counter_k := 0 upon encountering a new keyset and increments it by 1 every time it has successfully minted new ecash with this keyset. The wallet stores the latest counter_k in its database for all keysets it uses. Note that we have a counter (and therefore a derivation path) for each keyset k. We omit the keyset index k in the following of this document.

    "},{"location":"13/#keyset-id","title":"Keyset ID","text":"

    The integer representation keyset_id_int of a keyset is calculated from its hexadecimal ID which has a length of 8 bytes or 16 hex characters. First, we convert the hex string to a big-endian sequence of bytes. This value is then modulo reduced by 2^31 - 1 to arrive at an integer that is a unique identifier keyset_id_int.

    Example in Python:

    keyset_id_int = int.from_bytes(bytes.fromhex(keyset_id_hex), \"big\") % (2**31 - 1)\n

    Example in JavaScript:

    keysetIdInt = BigInt(`0x${keysetIdHex}`) % BigInt(2 ** 31 - 1);\n
    "},{"location":"13/#restore-from-seed-phrase","title":"Restore from seed phrase","text":"

    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) or by downloading the entire database of the mint (TBD).

    The wallet takes the following steps during recovery:

    1. Generate secret and r from counter and keyset
    2. Generate BlindedMessage from secret
    3. Obtain BlindSignature for secret from the mint
    4. Unblind BlindSignature to C using r
    5. Restore Proof = (secret, C)
    6. Check if Proof is already spent
    "},{"location":"13/#generate-blindedmessages","title":"Generate BlindedMessages","text":"

    To generate the BlindedMessages, the wallet starts with a counter := 0 and , for each increment of the counter, generates a secret using the BIP32 private key derived from secret_derivation_path and converts it to a hex string.

    secret = bip32.get_privkey_from_path(secret_derivation_path).hex()\n

    The wallet similarly generates a blinding factor r from the r_derivation_path:

    r = self.bip32.get_privkey_from_path(r_derivation_path)\n

    Note: For examples, see the test vectors.

    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 restore endpoint or by downloading the entire mint's database.

    "},{"location":"13/#generate-proofs","title":"Generate Proofs","text":"

    Using the restored BlindSignatures and the r generated in the previous step, the wallet can unblind the signature to C. The triple (secret, C, amount) is a restored Proof.

    "},{"location":"13/#check-proofs-states","title":"Check Proofs states","text":"

    If the wallet used the restore endpoint NUT-09 for regenerating the Proofs, it additionally needs to check for the Proofs spent state using NUT-07. The wallet deletes all Proofs which are already spent and keeps the unspent ones in its database.

    "},{"location":"13/#restoring-batches","title":"Restoring batches","text":"

    Generally, the user won't remember the last state of counter when starting the recovery process. Therefore, wallets need to know how far they need to increment the counter during the restore process to be confident to have reached the most recent state.

    In short, following approach is recommended:

    Wallets restore Proofs in batches of 100. The wallet starts with a counter=0 and increments it for every Proof it generated during one batch. When the wallet begins restoring the first Proofs, it is likely that the first few batches will only contain spent Proofs. Eventually, the wallet will reach a counter that will result in unspent Proofs which it stores in its database. The wallet then continues to restore until three successive batches are returned empty by the mint. This is to be confident that the restore process did not miss any Proofs that might have been generated with larger gaps in the counter by the previous wallet that we are restoring.

    "},{"location":"14/","title":"NUT-14: Hashed Timelock Contracts (HTLCs)","text":"

    optional

    depends on: NUT-10

    This NUT describes the use of Hashed Timelock Contracts (HTLCs) which defines a spending condition based on NUT-10's well-known Secret format. Using HTLCs, ecash tokens can be locked to the hash of a preimage or a timelock. This enables use cases such as atomic swaps of ecash between users, and atomic coupling of an ecash spending condition to a Lightning HTLC.

    HTLC spending conditions can be thought of as an extension of P2PK locks NUT-11 but with a hash lock in Secret.data and a new Proof.witness.preimage witness in the locked inputs to be spent. The preimage that was used to spend a locked token can be retrieved using NUT-07. Caution: applications that rely on being able to retrieve the witness independent from the spender must check via the mint's info endpoint that NUT-07 is supported.

    Caution: If the mint does not support this type 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 endpoint.

    "},{"location":"14/#htlc","title":"HTLC","text":"

    NUT-10 Secret kind: HTLC

    If for a Proof, Proof.secret is a Secret of kind HTLC, the hash of the lock is in Proof.secret.data. The preimage for unlocking the HTLC is in the witness Proof.witness.preimage. All additional tags from P2PK locks are used here as well, allowing us to add a locktime, signature flag, and use multisig (see NUT-11).

    Here is a concrete example of a Secret of kind HTLC:

    [\n  \"HTLC\",\n  {\n    \"nonce\": \"da62796403af76c80cd6ce9153ed3746\",\n    \"data\": \"023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54\",\n    \"tags\": [\n      [\n        \"pubkeys\",\n        \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\"\n      ],\n      [\"locktime\", \"1689418329\"],\n      [\n        \"refund\",\n        \"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e\"\n      ]\n    ]\n  }\n]\n

    A Proof with this Secret can be spent in two ways. To spend the hash lock, the witness in Proof.witness includes the preimage to Secret.data and a signature from the key in Secret.tag.pubkeys. Additionally, if the current system time is later than Secret.tag.locktime, the Proof can be spent if Proof.witness includes a signature from the key in Secret.tags.refund.

    The hash lock in Secret.data and the preimage in Proof.witness.preimage is treated as 32 byte data encoded as 64 character hex strings.

    See NUT-11 for a description of the signature scheme, the additional use of signature flags, and how to require signature from multiple public keys (multisig).

    "},{"location":"14/#witness-format","title":"Witness format","text":"

    HTLCWitness is a serialized JSON string of the form

    {\n  \"preimage\": <hex_str>,\n  \"signatures\": <Array[<hex_str>]>\n}\n

    The witness for a spent proof can be obtained with a Proof state check (see NUT-07).

    "},{"location":"15/","title":"NUT-15: Partial multi-path payments","text":"

    optional

    depends on: NUT-05

    In this document, we describe how wallets can instruct multiple mints to each pay a partial amount of a bolt11 Lightning invoice. The full payment is composed of partial payments (MPP) from multiple multi-path payments from different Lightning nodes. This way, wallets can pay a larger Lightning invoice combined from multiple smaller balances on different mints. Due to the atomic nature of MPP, either all payments will be successful or all of them will fail.

    The Lightning backend of the mint must support paying a partial amount of an Invoice and multi-path payments (MPP, see BOLT 4). For example, the mint's Lightning node must be able to pay 50 sats of a 100 sat bolt11 invoice. The receiving Lightning node must support receiving multi-path payments as well.

    "},{"location":"15/#multimint-payment-execution","title":"Multimint payment execution","text":"

    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). 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.

    "},{"location":"15/#melt-quote","title":"Melt quote","text":"

    To request a melt quote with a partial amount, the wallet of Alice makes a POST /v1/melt/quote/bolt11 similar to NUT-05.

    POST https://mint.host:3338/v1/melt/quote/bolt11\n

    The wallet Alice includes the following PostMeltQuoteBolt11Request data in its request which includes an additional (and optional) options object compared to the standard request in NUT-05:

    {\n  \"request\": <str>,\n  \"unit\": <str_enum[\"sat\"]>,\n  \"options\": {\n    \"mpp\": {\n      \"amount\": <int>\n    }\n  }\n}\n

    Here, request is the bolt11 Lightning invoice to be paid, unit is the unit the wallet would like to pay with, and amount is the amount for the requested payment. The wallet then pays the returned melt quote the same way as in NUT-05.

    "},{"location":"15/#settings","title":"Settings","text":"

    The settings returned in the info endpoint (NUT-06) indicate that a mint supports this NUT. The mint MUST indicate each method and unit that supports mpp. It can indicate this in an array of objects for multiple method and unit pairs and the boolean flag mpp set to true.

    MultipathPaymentSetting is of the form:

    {\n  [\n    {\n      \"method\": <str>,\n      \"unit\": <str>,\n      \"mpp\": <bool>\n    },\n    ...\n  ]\n}\n

    Example MultipathPaymentSetting:

    {\n  \"15\": {\n    [\n      {\n        \"method\": \"bolt11\",\n        \"unit\": \"sat\",\n        \"mpp\": true\n      },\n      {\n        \"method\": \"bolt11\",\n        \"unit\": \"usd\",\n        \"mpp\": true\n      },\n    ]\n  }\n}\n
    "},{"location":"16/","title":"NUT-16: Animated QR codes","text":"

    optional

    This document outlines how tokens should be displayed as QR codes for sending them between two wallets.

    "},{"location":"16/#introduction","title":"Introduction","text":"

    QR codes are a great way to send and receive Cashu tokens. Before a token can be shared as a QR code, it needs to be serialized (see NUT-00).

    "},{"location":"16/#static-qr-codes","title":"Static QR codes","text":"

    If the serialized token is not too large (i.e. includes less than or equal to 2 proofs) it can usually be shared as a static QR code. This might not be the case if the secret includes long scripts or the token has a long memo or mint URL.

    "},{"location":"16/#animated-qr-codes","title":"Animated QR codes","text":"

    If a token is too large to be displayed as a single QR code, we use animated QR codes are based on the 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.

    "},{"location":"16/#resources","title":"Resources","text":""},{"location":"17/","title":"NUT-17: WebSockets","text":"

    optional

    depends on: NUT-07

    This NUT defines a websocket protocol that enables bidirectional communication between apps and mints using the JSON-RPC format.

    "},{"location":"17/#subscriptions","title":"Subscriptions","text":"

    The websocket enables real-time subscriptions that wallets can use to receive notifications for a state change of a MintQuoteResponse (NUT-04), MeltQuoteResponse (NUT-05), CheckStateResponse (NUT-07).

    A summary of the subscription flow is the following:

    1. A wallet connects to the websocket endpoint and sends a WsRequest with the subscribe command.
    2. The mint responds with a WsResponse containing an ok or an error.
    3. If the subscription was accepted, the mint sends a WsNotification of the current state of the subscribed objects and whenever there is an update for the wallet's subscriptions.
    4. To close a subscription, the wallet sends WsRequest with the unsubscribe command.
    "},{"location":"17/#specifications","title":"Specifications","text":"

    The websocket is reachable via the mint's URL path /v1/ws:

    https://mint.com/v1/ws\n

    NUT-17 uses the JSON-RPC format for all messages. There are three types of messages defined in this NUT.

    "},{"location":"17/#requests","title":"Requests","text":"

    All requests from the wallet to the mint are of the form of a WsRequest:

    {\n  \"jsonrpc\": \"2.0\",\n  \"method\": <str_enum[WsRequestMethod]>,\n  \"params\": <str_WsRequestParams>,\n  \"id\": <int>\n}\n

    WsRequestMethod is a enum of strings with the supported commands \"subscribe\" and \"unsubscribe\":

    enum WsRequestMethod {\n  sub = \"subscribe\",\n  unsub = \"unsubscribe\",\n}\n

    WsRequestParams is a serialized JSON with the parameters of the corresponding command.

    "},{"location":"17/#command-subscribe","title":"Command: Subscribe","text":"

    To subscribe to updates, the wallet sends a \"subscribe\" command with the following params parameters:

    {\n  \"kind\": <str_enum[SubscriptionKind]>,\n  \"subId\": <string>,\n  \"filters\": <string[]>\n}\n

    Here, subId is a unique uuid generated by the wallet and allows the client to map its requests to the mint's responses.

    SubscriptionKind is an enum with the following possible values:

    enum SubscriptionKind {\n  bolt11_melt_quote = \"bolt11_melt_quote\",\n  bolt11_mint_quote = \"bolt11_mint_quote\",\n  proof_state = \"proof_state\",\n}\n

    The filters are an array of mint quote IDs (NUT-04), or melt quote IDs (NUT-05), or Y's (NUT-07) of the corresponding object to receive updates from.

    As an example, filters would be of the following form to subscribe for updates of three different mint quote IDs:

    [\"20385fc7245...\", \"d06667cda9b...\", \"e14d8ca96f...\"]\n

    Note that id and subId are unrelated. The subId is the ID for each subscription, whereas id is part of the JSON-RPC spec and is an integer counter that must be incremented for every request sent over the websocket.

    Important: If the subscription is accepted by the mint, the mint MUST first respond with the current state of the subscribed object and continue sending any further updates to it.

    For example, if the wallet subscribes to a Proof.Y of a Proof that has not been spent yet, the mint will first respond with a ProofState with state == \"UNSPENT\". If the wallet then spends this Proof, the mint would send a ProofState with state == \"PENDING\" and then one with state == \"SPENT\". In total, the mint would send three notifications to the wallet.

    "},{"location":"17/#command-unsubscribe","title":"Command: Unsubscribe","text":"

    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:

    {\n  \"subId\": <string>\n}\n
    "},{"location":"17/#responses","title":"Responses","text":"

    A WsResponse is returned by the mint to both the \"subscribe\" and \"unsubscribe\" commands and indicates whether the request was successful:

    {\n  \"jsonrpc\": \"2.0\",\n  \"result\": {\n    \"status\": \"OK\",\n    \"subId\": <str>\n  },\n  \"id\": <int>\n}\n

    Here, the id corresponds to the id in the request (as part of the JSON-RPC spec) and subId corresponds to the subscription ID.

    "},{"location":"17/#notifications","title":"Notifications","text":"

    WsNotification's are sent from the mint to the wallet and contain subscription data in the following format

    {\n  \"jsonrpc\": \"2.0\",\n  \"method\": \"subscribe\",\n  \"params\": {\n    \"subId\": <str>,\n    \"payload\": NotificationPayload\n  }\n}\n

    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), a MeltQuoteResponse (NUT-05), or a CheckStateResponse (NUT-07), depending on what the corresponding SubscriptionKind was.

    "},{"location":"17/#errors","title":"Errors","text":"

    WsErrors for a given WsRequest are returned in the following format

    {\n  \"jsonrpc\": \"2.0\",\n  \"error\": {\n    \"code\": -32601,\n    \"message\": \"Human readable error message\"\n  },\n  \"id\": \"1\"\n}\n
    "},{"location":"17/#example-proofstate-subscription","title":"Example: ProofState subscription","text":"

    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). Note that filters is an array meaning multiple subscriptions of the same kind can be made in the same request.

    Wallet:

    {\n  \"jsonrpc\": \"2.0\",\n  \"id\": 0,\n  \"method\": \"subscribe\",\n  \"params\": {\n    \"kind\": \"proof_state\",\n    \"filters\": [\n      \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\"\n    ],\n    \"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\"\n  }\n}\n

    The mint first responds with a WsResponse confirming that the subscription has been added.

    Mint:

    {\n  \"jsonrpc\": \"2.0\",\n  \"result\": {\n    \"status\": \"OK\",\n    \"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\"\n  },\n  \"id\": 0\n}\n

    The mint immediately sends the current ProofState of the subscription as a WsNotification.

    Mint:

    {\n  \"jsonrpc\": \"2.0\",\n  \"method\": \"subscribe\",\n  \"params\": {\n    \"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\",\n    \"payload\": {\n      \"Y\": \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\",\n      \"state\": \"UNSPENT\",\n      \"witness\": null\n    }\n  }\n}\n

    While leaving the websocket connection open, the wallet then spends the ecash. The mint sends WsNotification updating the wallet about state changes of the ProofState accordingly:

    Mint:

    {\"jsonrpc\": \"2.0\", \"method\": \"subscribe\", \"params\": {\"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\", \"payload\": {\"Y\": \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\", \"state\": \"PENDING\"}}}\n\n{\"jsonrpc\": \"2.0\", \"method\": \"subscribe\", \"params\": {\"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\", \"payload\": {\"Y\": \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\", \"state\": \"SPENT\"}}}\n

    The wallet then unsubscribes.

    Wallet:

    {\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"unsubscribe\",\n  \"params\": { \"subId\": \"Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T\" }\n}\n
    "},{"location":"17/#signaling-support-via-nut-06","title":"Signaling Support via NUT-06","text":"

    Mints signal websocket support via NUT-06 using the following setting:

    \"nuts\": {\n    \"17\": {\n      \"supported\": [\n        {\n          \"method\": <str>,\n          \"unit\": <str>,\n          \"commands\": <str[]>\n        },\n        ...\n      ]\n    }\n}\n

    Here, commands is an array of the commands that the mint supports. A mint that supports all commands would return [\"bolt11_mint_quote\", \"bolt11_melt_quote\", \"proof_state\"]. Supported commands are given for each method-unit pair.

    Example:

    \"nuts\": {\n    \"17\": {\n      \"supported\": [\n        {\n          \"method\": \"bolt11\",\n          \"unit\": \"sat\",\n          \"commands\": [\n            \"bolt11_mint_quote\",\n            \"bolt11_melt_quote\",\n            \"proof_state\"\n            ]\n        },\n      ]\n    }\n}\n
    "},{"location":"18/","title":"NUT-18: Payment Requests","text":"

    optional

    This NUT introduces a standardised format for payment requests, that supply a sending wallet with all information necessary to complete the transaction. This enables many use-cases where a transaction is better initiated by the receiver (e.g. point of sale).

    "},{"location":"18/#flow","title":"Flow","text":"
    1. Receiver creates a payment request, encodes it and displays it to the sender
    2. Sender scans the request and constructs a matching token
    3. Sender sends the token according to the transport specified in the payment request
    4. Receiver receives the token and finalises the transaction
    "},{"location":"18/#payment-request","title":"Payment Request","text":"

    A Payment Request is defined as follows

    {\n  \"i\": str <optional>,\n  \"a\": int <optional>,\n  \"u\": str <optional>,\n  \"s\": bool <optional>,\n  \"m\": Array[str] <optional>,\n  \"d\": str <optional>,\n  \"t\": Array[Transport]\n}\n

    Here, the fields are

    "},{"location":"18/#transport","title":"Transport","text":"

    Transport specifies methods for sending the ecash to the receiver. A transport consists of a type and a target.

    {\n  \"t\": str,\n  \"a\": str,\n  \"g\": Array[Array[str, str]] <optional>\n}\n
    "},{"location":"18/#tags","title":"Tags","text":"

    Tags are an optional array of [tag, value, value, ...] tuples that can be used to specify additional features about the transport. A single tag can have multiple values.

    "},{"location":"18/#transport-types","title":"Transport types","text":"

    The supported transport types are described below.

    "},{"location":"18/#nostr","title":"Nostr","text":"

    The n tag specifies the NIPs the receiver supports. At least one tag value MUST be specified. For NIP-17 direct messages, the sender sends a PaymentRequestPayload as the message content.

    "},{"location":"18/#http-post","title":"HTTP POST","text":"

    The execute the payment, the sender makes a POST request to the specified endpoint URL with the PaymentRequestPayload as the body.

    "},{"location":"18/#payment-payload","title":"Payment payload","text":"

    If not specified otherwise, the payload sent to the receiver is a PaymentRequestPayload JSON serialized object as follows:

    {\n  \"id\": str <optional>,\n  \"memo\": str <optional>,\n  \"mint\": str,\n  \"unit\": <str_enum>,\n  \"proofs\": Array<Proof>\n}\n

    Here, id is the payment id (corresponding to i in request), memo is an optional memo to be sent to the receiver with the payment, mint is the mint URL from which the ecash is from, unit is the unit of the payment, and proofs is an array of proofs (see NUT-00, can also include DLEQ proofs).

    "},{"location":"18/#encoded-request","title":"Encoded Request","text":"

    The payment request is serialized using CBOR, encoded in base64_urlsafe, together with a prefix creq and a version A:

    \"creq\" + \"A\" + base64(CBOR(PaymentRequest))

    "},{"location":"18/#example","title":"Example","text":"

    This is an example payment request expressed as JSON:

    {\n  \"i\": \"b7a90176\",\n  \"a\": 10,\n  \"u\": \"sat\",\n  \"m\": [\"https://nofees.testnut.cashu.space\"],\n  \"t\": [\n    {\n      \"t\": \"nostr\",\n      \"a\": \"nprofile1qy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9mhwden5te0wfjkccte9curxven9eehqctrv5hszrthwden5te0dehhxtnvdakqqgydaqy7curk439ykptkysv7udhdhu68sucm295akqefdehkf0d495cwunl5\",\n      \"g\": [[\"n\", \"17\"]]\n    }\n  ]\n}\n

    This payment request serializes to (see here):

    creqApWF0gaNhdGVub3N0cmFheKlucHJvZmlsZTFxeTI4d3VtbjhnaGo3dW45ZDNzaGp0bnl2OWtoMnVld2Q5aHN6OW1od2RlbjV0ZTB3ZmprY2N0ZTljdXJ4dmVuOWVlaHFjdHJ2NWhzenJ0aHdkZW41dGUwZGVoaHh0bnZkYWtxcWd5ZGFxeTdjdXJrNDM5eWtwdGt5c3Y3dWRoZGh1NjhzdWNtMjk1YWtxZWZkZWhrZjBkNDk1Y3d1bmw1YWeBgmFuYjE3YWloYjdhOTAxNzZhYQphdWNzYXRhbYF4Imh0dHBzOi8vbm9mZWVzLnRlc3RudXQuY2FzaHUuc3BhY2U=\n
    "},{"location":"error_codes/","title":"NUT Errors","text":"Code Description Relevant nuts 10002 Blinded message of output already signed NUT-03, NUT-04, NUT-05 10003 Token could not be verified NUT-03, NUT-05 11001 Token is already spent NUT-03, NUT-05 11002 Transaction is not balanced (inputs != outputs) NUT-02, NUT-03, NUT-05 11005 Unit in request is not supported NUT-04, NUT-05 11006 Amount outside of limit range NUT-04, NUT-05 12001 Keyset is not known NUT-02, NUT-04 12002 Keyset is inactive, cannot sign messages NUT-02, NUT-03, NUT-04 20001 Quote request is not paid NUT-04 20002 Tokens have already been issued for quote NUT-04 20003 Minting is disabled NUT-04 20005 Quote is pending NUT-04, NUT-05 20006 Invoice already paid NUT-05 20007 Quote is expired NUT-04, NUT-05"},{"location":"tests/","title":"Test Vectors","text":"

    The files in this directory contain test vectors for NUTs that warrant them.

    "},{"location":"tests/00-tests/","title":"NUT-00 Test Vectors","text":""},{"location":"tests/00-tests/#hash-to-curve-function","title":"Hash-to-curve function","text":"

    The hash to curve function takes a message of any length and outputs a valid point on the secp256k1 curve. Note that unless you are using complex spend conditions (NUT-10), standardized secrets (random 32-bytes-long byte arrays) should be used in order to prevent wallet fingerprinting.

    # Test 1 (hex encoded)\nMessage: 0000000000000000000000000000000000000000000000000000000000000000\nPoint:   024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725\n\n# Test 2 (hex encoded)\nMessage: 0000000000000000000000000000000000000000000000000000000000000001\nPoint:   022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf\n\n# Test 3 (hex encoded)\n# Note that this message will take a few iterations of the loop before finding a valid point\nMessage: 0000000000000000000000000000000000000000000000000000000000000002\nPoint:   026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f\n
    "},{"location":"tests/00-tests/#blinded-messages","title":"Blinded messages","text":"

    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.

    # Test 1\nx:  d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6   # hex encoded byte array\nr:  99fce58439fc37412ab3468b73db0569322588f62fb3a49182d67e23d877824a   # hex encoded private key\nB_: 033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d # hex encoded public key\n\n# Test 2\nx:  f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60   # hex encoded byte array\nr:  f78476ea7cc9ade20f9e05e58a804cf19533f03ea805ece5fee88c8e2874ba50   # hex encoded private key\nB_: 029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763 # hex encoded public key\n
    "},{"location":"tests/00-tests/#blinded-signatures","title":"Blinded signatures","text":"

    These are test vectors for the blinded key C_ given the mint's private key k and Alice's blinded message containing B_.

    # Test 1\nmint private key: 0000000000000000000000000000000000000000000000000000000000000001\nB_: 02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\nC_: 02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\n\n# Test 2\nmint private key: 7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f\nB_: 02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\nC_: 0398bc70ce8184d27ba89834d19f5199c84443c31131e48d3c1214db24247d005d\n
    "},{"location":"tests/00-tests/#serialization-of-tokenv3","title":"Serialization of TokenV3","text":"

    The following are JSON-formatted v3 tokens and their serialized counterparts.

    {\n  \"token\": [\n    {\n      \"mint\": \"https://8333.space:3338\",\n      \"proofs\": [\n        {\n          \"amount\": 2,\n          \"id\": \"009a1f293253e41e\",\n          \"secret\": \"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\n          \"C\": \"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"\n        },\n        {\n          \"amount\": 8,\n          \"id\": \"009a1f293253e41e\",\n          \"secret\": \"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\n          \"C\": \"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"\n        }\n      ]\n    }\n  ],\n  \"unit\": \"sat\",\n  \"memo\": \"Thank you.\"\n}\n

    Serialized:

    cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9\n
    "},{"location":"tests/00-tests/#deserialization-of-tokenv3","title":"Deserialization of TokenV3","text":"

    The following are incorrectly formatted serialized v3 tokens.

    # Incorrect prefix (casshuA)\ncasshuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9\n\n# No prefix\neyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9\n

    The following is a correctly serialized v3 token.

    cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9\n

    Both of the following v3 tokens are valid, one includes padding characters at the end and the other does not.

    # Clients should be able to deserialize both\ncashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91IHZlcnkgbXVjaC4ifQ==\ncashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91IHZlcnkgbXVjaC4ifQ\n
    "},{"location":"tests/00-tests/#serialization-of-tokenv4","title":"Serialization of TokenV4","text":"

    The following are JSON-formatted v4 tokens and their serialized counterparts. The h'' values are bytes but displayed as hex strings here.

    "},{"location":"tests/00-tests/#single-keyset","title":"Single keyset","text":"

    Token from a single keyset and including a memo.

    {\n    \"t\": [\n        {\n            \"i\": h'00ad268c4d1f5826',\n            \"p\": [\n                {\n                    \"a\": 1,\n                    \"s\": \"9a6dbb847bd232ba76db0df197216b29d3b8cc14553cd27827fc1cc942fedb4e\",\n                    \"c\": h'038618543ffb6b8695df4ad4babcde92a34a96bdcd97dcee0d7ccf98d472126792',\n                },\n            ],\n        },\n    ],\n    \"d\": \"Thank you\",\n    \"m\": \"http://localhost:3338\",\n    \"u\": \"sat\",\n}\n

    Encoded:

    cashuBpGF0gaJhaUgArSaMTR9YJmFwgaNhYQFhc3hAOWE2ZGJiODQ3YmQyMzJiYTc2ZGIwZGYxOTcyMTZiMjlkM2I4Y2MxNDU1M2NkMjc4MjdmYzFjYzk0MmZlZGI0ZWFjWCEDhhhUP_trhpXfStS6vN6So0qWvc2X3O4NfM-Y1HISZ5JhZGlUaGFuayB5b3VhbXVodHRwOi8vbG9jYWxob3N0OjMzMzhhdWNzYXQ=\n
    "},{"location":"tests/00-tests/#multiple-keysets","title":"Multiple keysets","text":"

    The token below includes proofs from two different keysets.

    {\n    \"t\": [\n        {\n            \"i\": h'00ffd48b8f5ecf80',\n            \"p\": [\n                {\n                    \"a\": 1,\n                    \"s\": \"acc12435e7b8484c3cf1850149218af90f716a52bf4a5ed347e48ecc13f77388\",\n                    \"c\": h'0244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cf',\n                },\n            ],\n        },\n        {\n            \"i\": h'00ad268c4d1f5826',\n            \"p\": [\n                {\n                    \"a\": 2,\n                    \"s\": \"1323d3d4707a58ad2e23ada4e9f1f49f5a5b4ac7b708eb0d61f738f48307e8ee\",\n                    \"c\": h'023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94d',\n                },\n                {\n                    \"a\": 1,\n                    \"s\": \"56bcbcbb7cc6406b3fa5d57d2174f4eff8b4402b176926d3a57d3c3dcbb59d57\",\n                    \"c\": h'0273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63',\n                },\n            ],\n        },\n    ],\n    \"m\": \"http://localhost:3338\",\n    \"u\": \"sat\",\n}\n

    Serialized:

    cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA\n
    "},{"location":"tests/01-tests/","title":"NUT-01 Test Vectors","text":"

    The following are incorrect keysets that should be rejected by wallets implementing the NUT-01 specification.

    Key 1 is missing a byte

    {\n  \"1\": \"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38\",\n  \"2\": \"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\n  \"4\": \"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\n  \"8\": \"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}\n

    Key 2 is a valid key but is not in the compressed format.

    {\n  \"1\": \"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\n  \"2\": \"04fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de3625246cb2c27dac965cb7200a5986467eee92eb7d496bbf1453b074e223e481\",\n  \"4\": \"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\n  \"8\": \"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}\n

    The following are correct keysets that should be accepted by wallets implementing the NUT-01 specification. Note that the second (bigger) keyset's biggest amount is 9223372036854775808, one unit over what can fit into a 64-bit signed integer (often known as a Long). To handle this you'll need to use either an unsigned 64-bit integer or a 128-bit signed integer (sometimes known as a BigInteger).

    {\n  \"1\": \"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\n  \"2\": \"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\n  \"4\": \"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\n  \"8\": \"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}\n
    {\n  \"1\": \"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\n  \"2\": \"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\n  \"4\": \"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\n  \"8\": \"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\n  \"16\": \"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\n  \"32\": \"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\n  \"64\": \"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\n  \"128\": \"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\n  \"256\": \"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\n  \"512\": \"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\n  \"1024\": \"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\n  \"2048\": \"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\n  \"4096\": \"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\n  \"8192\": \"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\n  \"16384\": \"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\n  \"32768\": \"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\n  \"65536\": \"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\n  \"131072\": \"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\n  \"262144\": \"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\n  \"524288\": \"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\n  \"1048576\": \"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\n  \"2097152\": \"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\n  \"4194304\": \"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\n  \"8388608\": \"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\n  \"16777216\": \"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\n  \"33554432\": \"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\n  \"67108864\": \"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\n  \"134217728\": \"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\n  \"268435456\": \"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\n  \"536870912\": \"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\n  \"1073741824\": \"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\n  \"2147483648\": \"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\n  \"4294967296\": \"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\n  \"8589934592\": \"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\n  \"17179869184\": \"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\n  \"34359738368\": \"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\n  \"68719476736\": \"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\n  \"137438953472\": \"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\n  \"274877906944\": \"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\n  \"549755813888\": \"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\n  \"1099511627776\": \"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\n  \"2199023255552\": \"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\n  \"4398046511104\": \"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\n  \"8796093022208\": \"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\n  \"17592186044416\": \"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\n  \"35184372088832\": \"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\n  \"70368744177664\": \"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\n  \"140737488355328\": \"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\n  \"281474976710656\": \"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\n  \"562949953421312\": \"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\n  \"1125899906842624\": \"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\n  \"2251799813685248\": \"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\n  \"4503599627370496\": \"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\n  \"9007199254740992\": \"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\n  \"18014398509481984\": \"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\n  \"36028797018963968\": \"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\n  \"72057594037927936\": \"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\n  \"144115188075855872\": \"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\n  \"288230376151711744\": \"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\n  \"576460752303423488\": \"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\n  \"1152921504606846976\": \"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\n  \"2305843009213693952\": \"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\n  \"4611686018427387904\": \"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\n  \"9223372036854775808\": \"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}\n
    "},{"location":"tests/02-tests/","title":"NUT-02 Test Vectors","text":"

    The following keysets and corresponding keyset IDs are correct: Keyset id: 00456a94ab4e1c46

    {\n  \"1\": \"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc\",\n  \"2\": \"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de\",\n  \"4\": \"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303\",\n  \"8\": \"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528\"\n}\n

    Keyset id: 000f01df73ea149a

    {\n  \"1\": \"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566\",\n  \"2\": \"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\",\n  \"4\": \"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7\",\n  \"8\": \"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0\",\n  \"16\": \"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d\",\n  \"32\": \"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612\",\n  \"64\": \"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664\",\n  \"128\": \"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9\",\n  \"256\": \"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459\",\n  \"512\": \"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb\",\n  \"1024\": \"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc\",\n  \"2048\": \"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b\",\n  \"4096\": \"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2\",\n  \"8192\": \"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21\",\n  \"16384\": \"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50\",\n  \"32768\": \"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04\",\n  \"65536\": \"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d\",\n  \"131072\": \"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41\",\n  \"262144\": \"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328\",\n  \"524288\": \"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86\",\n  \"1048576\": \"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788\",\n  \"2097152\": \"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c\",\n  \"4194304\": \"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512\",\n  \"8388608\": \"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0\",\n  \"16777216\": \"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21\",\n  \"33554432\": \"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262\",\n  \"67108864\": \"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3\",\n  \"134217728\": \"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020\",\n  \"268435456\": \"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276\",\n  \"536870912\": \"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9\",\n  \"1073741824\": \"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee\",\n  \"2147483648\": \"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a\",\n  \"4294967296\": \"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5\",\n  \"8589934592\": \"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3\",\n  \"17179869184\": \"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9\",\n  \"34359738368\": \"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75\",\n  \"68719476736\": \"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754\",\n  \"137438953472\": \"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6\",\n  \"274877906944\": \"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a\",\n  \"549755813888\": \"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785\",\n  \"1099511627776\": \"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a\",\n  \"2199023255552\": \"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258\",\n  \"4398046511104\": \"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a\",\n  \"8796093022208\": \"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e\",\n  \"17592186044416\": \"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310\",\n  \"35184372088832\": \"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06\",\n  \"70368744177664\": \"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1\",\n  \"140737488355328\": \"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed\",\n  \"281474976710656\": \"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d\",\n  \"562949953421312\": \"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a\",\n  \"1125899906842624\": \"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9\",\n  \"2251799813685248\": \"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f\",\n  \"4503599627370496\": \"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73\",\n  \"9007199254740992\": \"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49\",\n  \"18014398509481984\": \"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6\",\n  \"36028797018963968\": \"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0\",\n  \"72057594037927936\": \"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd\",\n  \"144115188075855872\": \"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a\",\n  \"288230376151711744\": \"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc\",\n  \"576460752303423488\": \"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a\",\n  \"1152921504606846976\": \"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06\",\n  \"2305843009213693952\": \"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099\",\n  \"4611686018427387904\": \"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f\",\n  \"9223372036854775808\": \"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad\"\n}\n
    "},{"location":"tests/11-test/","title":"NUT-11 Test Vectors","text":"

    The following is a Proof with a valid signature.

    {\n  \"amount\": 1,\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"id\": \"009a1f293253e41e\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\\\"]}\"\n}\n

    The following is a Proof with an invalid signature as it is on a different secret.

    {\n  \"amount\": 1,\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"id\": \"009a1f293253e41e\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\\\"]}\"\n}\n

    The following is a Proof with 2 signatures required to meet the multi-signature spend condition.

    {\n  \"amount\": 1,\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"id\": \"009a1f293253e41e\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\\\",\\\"9a72ca2d4d5075be5b511ee48dbc5e45f259bcf4a4e8bf18587f433098a9cd61ff9737dc6e8022de57c76560214c4568377792d4c2c6432886cc7050487a1f22\\\"]}\"\n}\n

    The following is a Proof with one one signature failing the multi-signature spend condition.

    {\n  \"amount\": 1,\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\\\",\\\"data\\\":\\\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"id\": \"009a1f293253e41e\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\\\"]}\"\n}\n

    The following is a Proof with a signature from the refund key that is spendable because the locktime is in the past.

    {\n  \"amount\": 1,\n  \"id\": \"009a1f293253e41e\",\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"902685f492ef3bb2ca35a47ddbba484a3365d143b9776d453947dcbf1ddf9689\\\",\\\"data\\\":\\\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"03142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"locktime\\\",\\\"21\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"refund\\\",\\\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"710507b4bc202355c91ea3c147c0d0189c75e179d995e566336afd759cb342bcad9a593345f559d9b9e108ac2c9b5bd9f0b4b6a295028a98606a0a2e95eb54f7\\\"]}\"\n}\n

    The following is a Proof with a signature from the refund key that is not spendable because the locktime is in the future.

    {\n  \"amount\": 1,\n  \"id\": \"009a1f293253e41e\",\n  \"secret\": \"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"64c46e5d30df27286166814b71b5d69801704f23a7ad626b05688fbdb48dcc98\\\",\\\"data\\\":\\\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\\\",\\\"tags\\\":[[\\\"pubkeys\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\",\\\"03142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\\\"],[\\\"locktime\\\",\\\"21\\\"],[\\\"n_sigs\\\",\\\"2\\\"],[\\\"refund\\\",\\\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\\\"],[\\\"sigflag\\\",\\\"SIG_INPUTS\\\"]]}]\",\n  \"C\": \"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904\",\n  \"witness\": \"{\\\"signatures\\\":[\\\"f661d3dc046d636d47cb3d06586da42c498f0300373d1c2a4f417a44252cdf3809bce207c8888f934dba0d2b1671f1b8622d526840f2d5883e571b462630c1ff\\\"]}\"\n}\n
    "},{"location":"tests/12-tests/","title":"NUT-12 Test vectors","text":""},{"location":"tests/12-tests/#hash_e-function","title":"hash_e function","text":"
    R1: \"020000000000000000000000000000000000000000000000000000000000000001\"\nR2: \"020000000000000000000000000000000000000000000000000000000000000001\"\nK: \"020000000000000000000000000000000000000000000000000000000000000001\"\nC_: \"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\"\n
    hash(R1, R2, K, C_): \"a4dc034b74338c28c6bc3ea49731f2a24440fc7c4affc08b31a93fc9fbe6401e\"\n
    "},{"location":"tests/12-tests/#dleq-verification-on-blindsignature","title":"DLEQ verification on BlindSignature","text":"

    The following is a BlindSignature with a valid DLEQ proof.

    A: \"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"\nB_: \"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\"\n
    {\n  \"amount\": 8,\n  \"id\": \"00882760bfa2eb41\",\n  \"C_\": \"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2\",\n  \"dleq\": {\n    \"e\": \"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9\",\n    \"s\": \"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da\"\n  }\n}\n
    "},{"location":"tests/12-tests/#dleq-verification-on-proof","title":"DLEQ verification on Proof","text":"

    The following is a Proof with a valid DLEQ proof.

    A: \"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"\n
    {\n  \"amount\": 1,\n  \"id\": \"00882760bfa2eb41\",\n  \"secret\": \"daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9\",\n  \"C\": \"024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc\",\n  \"dleq\": {\n    \"e\": \"b31e58ac6527f34975ffab13e70a48b6d2b0d35abc4b03f0151f09ee1a9763d4\",\n    \"s\": \"8fbae004c59e754d71df67e392b6ae4e29293113ddc2ec86592a0431d16306d8\",\n    \"r\": \"a6d13fcd7a18442e6076f5e1e7c887ad5de40a019824bdfa9fe740d302e8d861\"\n  }\n}\n
    "},{"location":"tests/13-tests/","title":"NUT-13 Test vectors","text":""},{"location":"tests/13-tests/#keyset-id-integer-representation","title":"Keyset ID integer representation","text":"

    The integer representation of a keyset with an ID 009a1f293253e41e and its corresponding derivation path for a counter of value {counter} are

    {\n  \"keyset_id\": \"009a1f293253e41e\",\n  \"keyest_id_int\": 864559728,\n  \"derivation_path\": \"m/129372'/0'/864559728'/{counter}'\"\n}\n
    "},{"location":"tests/13-tests/#secret-derivatoin","title":"Secret derivatoin","text":"

    We derive values starting from the following BIP39 mnemonic.

    {\n  \"mnemonic\": \"half depart obvious quality work element tank gorilla view sugar picture humble\"\n}\n

    The secrets derived for the first five counters from counter=0 to counter=4 are

    {\n  \"secret_0\": \"485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae\",\n  \"secret_1\": \"8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270\",\n  \"secret_2\": \"bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8\",\n  \"secret_3\": \"59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf\",\n  \"secret_4\": \"576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0\"\n}\n

    The corresponding blinding factors r are

    {\n  \"r_0\": \"ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679\",\n  \"r_1\": \"967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248\",\n  \"r_2\": \"b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899\",\n  \"r_3\": \"fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29\",\n  \"r_4\": \"5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9\"\n}\n

    The corresponding derivation paths are

    {\n  \"derivation_path_0\": \"m/129372'/0'/864559728'/0'\",\n  \"derivation_path_1\": \"m/129372'/0'/864559728'/1'\",\n  \"derivation_path_2\": \"m/129372'/0'/864559728'/2'\",\n  \"derivation_path_3\": \"m/129372'/0'/864559728'/3'\",\n  \"derivation_path_4\": \"m/129372'/0'/864559728'/4'\"\n}\n
    "}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index ba03fcf..92b03ab 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,114 +2,114 @@ https://cashubtc.github.io/nuts/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/00/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/01/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/02/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/03/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/04/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/05/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/06/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/07/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/08/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/09/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/10/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/11/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/12/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/13/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/14/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/15/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/16/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/17/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/18/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/error_codes/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/tests/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/tests/00-tests/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/tests/01-tests/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/tests/02-tests/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/tests/11-test/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/tests/12-tests/ - 2024-10-18 + 2024-10-29 https://cashubtc.github.io/nuts/tests/13-tests/ - 2024-10-18 + 2024-10-29 \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 71698b995b5440ad98c4f567af878f6d94fd45b1..a527e322a960ceecaf3f5d487aa8f3cbd5a8120f 100644 GIT binary patch delta 270 zcmV+p0rCE%0;B>5ABzYGfF&T22Oxi4H_G1n1ns%8E>RPO4TH6hUtBj$&u!A5i%5p~ z!sQQC=g(=(K5XE@yIPRcBD1dbm%(+lczt=!kK$~0)f`5)F-ygcYthGeJt?&;i{v5N zskyTBp;O%U>4(*QQE6EUv&+(08*H=Ns7BwKwF{>HKU_JrNBfu>G-y}c| z5g>;MkV6E>Ap+zO0dj}{IYfXQB0vt=5PWz`7c&dE7)8qw#%i5oeFFOh)~D_KFdlvU U{a^jtC%vA`7uE-x%WVw+06R8(7XSbN delta 270 zcmV+p0rCE%0;B>5ABzYG0HqO;2OxiSH_G1n1ns%8EKw7c4TH6hUtBj$&u!A5i%5p~ z!sQQC=g)D-K3w45PqiSaMdl{!Fa6Zk;`QY@FU8sHsyPhoVwQ?0u0hgPxe(hsZqqSEpx%q~k~ZLrO5qZ+(5YZp#!Y{T9B9<6Nqm?pCKYMOo{t4gg^ zKQ}cTM}IMeE)F^AoXS*kU)vP*&HM(`8ekH@ECA*nU>*SG1HgO)m`?z+1enMFHwlnK z1jr!*zfNc!`07Q^}#Q*>R diff --git a/tests/00-tests/index.html b/tests/00-tests/index.html index 5117c6e..d432099 100644 --- a/tests/00-tests/index.html +++ b/tests/00-tests/index.html @@ -14,7 +14,7 @@ - + diff --git a/tests/01-tests/index.html b/tests/01-tests/index.html index 5e668b9..4f27cb1 100644 --- a/tests/01-tests/index.html +++ b/tests/01-tests/index.html @@ -14,7 +14,7 @@ - + diff --git a/tests/02-tests/index.html b/tests/02-tests/index.html index 454ef2b..bd2e7ba 100644 --- a/tests/02-tests/index.html +++ b/tests/02-tests/index.html @@ -14,7 +14,7 @@ - + diff --git a/tests/11-test/index.html b/tests/11-test/index.html index 65e79cb..c7445df 100644 --- a/tests/11-test/index.html +++ b/tests/11-test/index.html @@ -14,7 +14,7 @@ - + diff --git a/tests/12-tests/index.html b/tests/12-tests/index.html index ef99472..07c761b 100644 --- a/tests/12-tests/index.html +++ b/tests/12-tests/index.html @@ -14,7 +14,7 @@ - + diff --git a/tests/13-tests/index.html b/tests/13-tests/index.html index fc25b5e..6497b65 100644 --- a/tests/13-tests/index.html +++ b/tests/13-tests/index.html @@ -14,7 +14,7 @@ - + diff --git a/tests/index.html b/tests/index.html index b3e60f3..b64ddd9 100644 --- a/tests/index.html +++ b/tests/index.html @@ -14,7 +14,7 @@ - +