diff --git a/00/index.html b/00/index.html index 9c9a251..1fbacdb 100644 --- a/00/index.html +++ b/00/index.html @@ -126,7 +126,7 @@ @@ -136,7 +136,7 @@ @@ -146,7 +146,7 @@ @@ -208,7 +208,7 @@
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 status paid
and a proof
of payment. If the request included outputs
, the mint may respond with change
for the overpaid fees (see NUT-08).
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.
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
).
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.
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.
Alice
Carol
Bob
G
elliptic curve generator pointk
private key of mint (one for each amount)K
public key of mintQ
promise (blinded signature)x
random string (secret message), corresponds to point Y
on curver
private key (blinding factor)T
blinded messageZ
proof (unblinded signature)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)
Y
derived public keyDOMAIN_SEPARATOR
constant byte string b\"Secp256k1_HashToCurve_Cashu_\"
x
message to hashcounter
uint32 counter(byte order little endian) incremented from 0 until a point is found that lies on the curveBob
publishes public key K = kG
Alice
picks secret x
and computes Y = hash_to_curve(x)
Alice
sends to Bob
: B_ = Y + rG
with r
being a random blinding factor (blinding)Bob
sends back to Alice
blinded key: C_ = kB_
(these two steps are the DH key exchange) (signing)Alice
can calculate the unblinded key as C_ - rK = kY + krG - krG = kY = C
(unblinding)(x, C)
as a token and can send it to Carol
.Carol
can send (x, C)
to Bob
who then checks that k*hash_to_curve(x) == C
(verification), and if so treats it as a valid spend of a token, adding x
to the list of spent secrets.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
.
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.
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).
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.
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.
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
.
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.
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.
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
.
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.
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).
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).
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.
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.
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).
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).
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.
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.
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
.
{\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.
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:
GET /v1/keys
and store themGET /v1/keysets
GET /v1/keys/{keyset_id}
and store itactive
flag, update it in the db and use the keyset accordinglymandatory
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.
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.
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.
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.
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\"
:
\"UNPAID\"
means that the quote's request has not been paid yet.\"PAID\"
means that the request has been paid.\"ISSUED\"
means that the quote has already been 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.
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.
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.
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.
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 status paid
and 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.
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\"
:
\"UNPAID\"
means that the request has not been paid yet.\"PENDING\"
means that the request is currently being paid.\"PAID\"
means that the request has been paid successfully.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.
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
name
is the name of the mint and should be recognizable.pubkey
is the hex pubkey of the mint.version
is the implementation name and the version of the software running on this mint separated with a slash \"/\".description
is a short description of the mint that can be shown in the wallet next to the mint's name.description_long
is a long description that can be shown in an additional field.contact
is an array of contact objects to reach the mint operator. A contact object consists of two fields. The method
field denotes the contact method (like \"email\"), the info
field denotes the identifier (like \"contact@me.com\").motd
is the message of the day that the wallet must display to the user. It should only be used to display important announcements to users, such as scheduled maintenances.icon_url
is the URL pointing to an image to be used as an icon for the mint. Recommended to be squared in shape.time
is the current time set on the server. The value is passed as a Unix timestamp integer.nuts
indicates each NUT specification that the mint supports and its settings. The settings are defined in each NUT separately.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
UNSPENT
if it has not been spent yetPENDING
if it is being processed in a transaction (in an ongoing payment). A PENDING
proof cannot be used in another transaction until it is live
again.SPENT
if it has been redeemed and its secret is in the list of spent secrets of the mint.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
.
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.
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
).
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
Y
corresponds to the Proof
checked in the request.state
is an enum string field with possible values \"UNSPENT\"
, \"PENDING\"
, \"SPENT\"
witness
is the serialized witness data that was used to spend the Proof
if the token required it such as in the case of P2PK (see NUT-11).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).
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 BlindSignature
s to the wallet together with the successful payment status.
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.
Here we describe how the mint generates BlindSignature
s for the overpaid fees. The mint Bob
returns in PostMeltQuoteBolt11Response
the field change
ONLY IF Alice
has previously provided outputs
for the change AND if the 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 BlindSignature
s. Bob
then returns a payment status to the wallet, and, in addition, all blind signatures it generated for the overpaid fees.
Importantly, while Bob
does not necessarily return the same number of blind signatures as it received blank outputs from Alice
(since some of them may be of value 0), Bob
MUST return the all blank signatures with a value greater than 0 in the same order as the blank outputs were received and should omit all blind signatures with value 0. For example, if Bob
receives 10 blank outputs but the overpaid fees only occupy 4 blind signatures, Bob
will only return these 4 blind signatures with the appropriate imprinted amounts and omit the remaining 6 blind signatures with value 0. Due to the well-defined order of the returned blind signatures, Alice
can map the blind signatures returned from Bob
to the blank outputs it provided so that she can further apply the correct unblinding operations on them.
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 BlindSignature
s due to overpaid fees.
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 BlindedMessage
s 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.
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-09) 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]
.
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.
An ecash transaction, i.e., a swap or a melt operation, with a spending condition consists of the following components:
Proofs
being spentSecret
containing the rules for unlocking a Proof
BlindMessages
with new unlock conditions to which the Proofs
are spent toSpending conditions are defined for each individual Proof
and not on a transaction level that can consist of multiple Proofs
. Similarly, spending conditions must be satisfied by providing signatures or additional witness data for each Proof
separately. For a transaction to be valid, all Proofs
in that transaction must be unlocked successfully.
New Secret
s of the outputs to which the inputs are spent to are provided as BlindMessages
which means that they are blind-signed and not visible to the mint until they are actually spent.
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
kind
is the kind of the spending conditionnonce
is a unique random stringdata
expresses the spending condition specific to each kindtags
hold additional data committed to and can be used for feature extensionsExample use cases of this secret format are
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).
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:
sigflag: <str>
determines whether outputs have to be signed as welln_sigs: <int>
specifies the minimum number of valid signatures expectedpubkeys: <hex_str>
are additional public keys that can provide signatures (allows multiple entries)locktime: <int>
is the Unix timestamp of when the lock expiresrefund: <hex_str>
are optional refund public keys that can exclusively spend after locktime
(allows multiple entries)Note: The tag serialization type is [<str>, <str>, ...]
but some tag values are int
. Wallets and mints must cast types appropriately for de/serialization.
Signature flags are defined in the tag Secret.tags['sigflag']
. Currently, there are two signature flags.
SIG_INPUTS
requires valid signatures on all inputs. It is the default signature flag and will be applied even if the sigflag
tag is absent.SIG_ALL
requires valid signatures on all inputs and on all outputs.The signature flag SIG_ALL
is enforced if at least one of the Proofs
have the flag SIG_ALL
. Otherwise, SIG_INPUTS
is enforced.
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
.
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.
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.
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.
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.
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).
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
.
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:
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).
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.
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
.
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
A
\u2013\u00a0the public key Bob
used to sign this Proof(e, s)
\u2013\u00a0the DLEQ proof returned by Bob
B'
\u2013 Alice
's BlindedMessage
C'
\u2013 Bob
's BlindSignature
on B'
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.
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):
(x, C)
\u2013\u00a0the ecash Proof
(e, s)
\u2013\u00a0the DLEQ proof revealed by Alice
r
\u2013 Alice
's blinding factorHere, 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.
129372'
(UTF-8 for \ud83e\udd5c)0'
keyset_k_int
)counter'
(this value is incremented)secret
or r
= 0
or 1
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.
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:
secret
and r
from counter
and keyset
BlindedMessage
from secret
BlindSignature
for secret
from the mintBlindSignature
to C
using r
Proof = (secret, C)
Proof
is already spentBlindedMessages
","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.
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
.
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.
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:
Proofs
in batches of 100 and increment counter
counter
to the value at the last successful restore + 1Wallets 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.
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.
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.
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":"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:
WsRequest
with the subscribe
command.WsResponse
containing an ok or an error.WsNotification
of the current state of the subscribed objects and whenever there is an update for the wallet's subscriptions.WsRequest
with the unsubscribe
command.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.
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.
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.
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.
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.
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":"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.
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.
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.
Alice
Carol
Bob
G
elliptic curve generator pointk
private key of mint (one for each amount)K
public key of mintQ
promise (blinded signature)x
random string (secret message), corresponds to point Y
on curver
private key (blinding factor)T
blinded messageZ
proof (unblinded signature)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)
Y
derived public keyDOMAIN_SEPARATOR
constant byte string b\"Secp256k1_HashToCurve_Cashu_\"
x
message to hashcounter
uint32 counter(byte order little endian) incremented from 0 until a point is found that lies on the curveBob
publishes public key K = kG
Alice
picks secret x
and computes Y = hash_to_curve(x)
Alice
sends to Bob
: B_ = Y + rG
with r
being a random blinding factor (blinding)Bob
sends back to Alice
blinded key: C_ = kB_
(these two steps are the DH key exchange) (signing)Alice
can calculate the unblinded key as C_ - rK = kY + krG - krG = kY = C
(unblinding)(x, C)
as a token and can send it to Carol
.Carol
can send (x, C)
to Bob
who then checks that k*hash_to_curve(x) == C
(verification), and if so treats it as a valid spend of a token, adding x
to the list of spent secrets.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
.
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.
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).
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.
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.
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
.
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.
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.
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
.
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.
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).
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).
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.
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.
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).
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).
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.
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.
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
.
{\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.
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:
GET /v1/keys
and store themGET /v1/keysets
GET /v1/keys/{keyset_id}
and store itactive
flag, update it in the db and use the keyset accordinglymandatory
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.
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.
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.
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.
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\"
:
\"UNPAID\"
means that the quote's request has not been paid yet.\"PAID\"
means that the request has been paid.\"ISSUED\"
means that the quote has already been 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.
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.
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.
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.
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.
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\"
:
\"UNPAID\"
means that the request has not been paid yet.\"PENDING\"
means that the request is currently being paid.\"PAID\"
means that the request has been paid successfully.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.
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
name
is the name of the mint and should be recognizable.pubkey
is the hex pubkey of the mint.version
is the implementation name and the version of the software running on this mint separated with a slash \"/\".description
is a short description of the mint that can be shown in the wallet next to the mint's name.description_long
is a long description that can be shown in an additional field.contact
is an array of contact objects to reach the mint operator. A contact object consists of two fields. The method
field denotes the contact method (like \"email\"), the info
field denotes the identifier (like \"contact@me.com\").motd
is the message of the day that the wallet must display to the user. It should only be used to display important announcements to users, such as scheduled maintenances.icon_url
is the URL pointing to an image to be used as an icon for the mint. Recommended to be squared in shape.time
is the current time set on the server. The value is passed as a Unix timestamp integer.nuts
indicates each NUT specification that the mint supports and its settings. The settings are defined in each NUT separately.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
UNSPENT
if it has not been spent yetPENDING
if it is being processed in a transaction (in an ongoing payment). A PENDING
proof cannot be used in another transaction until it is live
again.SPENT
if it has been redeemed and its secret is in the list of spent secrets of the mint.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
.
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.
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
).
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
Y
corresponds to the Proof
checked in the request.state
is an enum string field with possible values \"UNSPENT\"
, \"PENDING\"
, \"SPENT\"
witness
is the serialized witness data that was used to spend the Proof
if the token required it such as in the case of P2PK (see NUT-11).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).
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 BlindSignature
s to the wallet together with the successful payment status.
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.
Here we describe how the mint generates BlindSignature
s for the overpaid fees. The mint Bob
returns in PostMeltQuoteBolt11Response
the field change
ONLY IF Alice
has previously provided outputs
for the change AND if the 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 BlindSignature
s. Bob
then returns a payment status to the wallet, and, in addition, all blind signatures it generated for the overpaid fees.
Importantly, while Bob
does not necessarily return the same number of blind signatures as it received blank outputs from Alice
(since some of them may be of value 0), Bob
MUST return the all blank signatures with a value greater than 0 in the same order as the blank outputs were received and should omit all blind signatures with value 0. For example, if Bob
receives 10 blank outputs but the overpaid fees only occupy 4 blind signatures, Bob
will only return these 4 blind signatures with the appropriate imprinted amounts and omit the remaining 6 blind signatures with value 0. Due to the well-defined order of the returned blind signatures, Alice
can map the blind signatures returned from Bob
to the blank outputs it provided so that she can further apply the correct unblinding operations on them.
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 BlindSignature
s due to overpaid fees.
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 BlindedMessage
s 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.
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-09) 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]
.
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.
An ecash transaction, i.e., a swap or a melt operation, with a spending condition consists of the following components:
Proofs
being spentSecret
containing the rules for unlocking a Proof
BlindMessages
with new unlock conditions to which the Proofs
are spent toSpending conditions are defined for each individual Proof
and not on a transaction level that can consist of multiple Proofs
. Similarly, spending conditions must be satisfied by providing signatures or additional witness data for each Proof
separately. For a transaction to be valid, all Proofs
in that transaction must be unlocked successfully.
New Secret
s of the outputs to which the inputs are spent to are provided as BlindMessages
which means that they are blind-signed and not visible to the mint until they are actually spent.
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
kind
is the kind of the spending conditionnonce
is a unique random stringdata
expresses the spending condition specific to each kindtags
hold additional data committed to and can be used for feature extensionsExample use cases of this secret format are
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).
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:
sigflag: <str>
determines whether outputs have to be signed as welln_sigs: <int>
specifies the minimum number of valid signatures expectedpubkeys: <hex_str>
are additional public keys that can provide signatures (allows multiple entries)locktime: <int>
is the Unix timestamp of when the lock expiresrefund: <hex_str>
are optional refund public keys that can exclusively spend after locktime
(allows multiple entries)Note: The tag serialization type is [<str>, <str>, ...]
but some tag values are int
. Wallets and mints must cast types appropriately for de/serialization.
Signature flags are defined in the tag Secret.tags['sigflag']
. Currently, there are two signature flags.
SIG_INPUTS
requires valid signatures on all inputs. It is the default signature flag and will be applied even if the sigflag
tag is absent.SIG_ALL
requires valid signatures on all inputs and on all outputs.The signature flag SIG_ALL
is enforced if at least one of the Proofs
have the flag SIG_ALL
. Otherwise, SIG_INPUTS
is enforced.
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
.
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.
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.
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.
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.
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).
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
.
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:
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).
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.
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
.
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
A
\u2013\u00a0the public key Bob
used to sign this Proof(e, s)
\u2013\u00a0the DLEQ proof returned by Bob
B'
\u2013 Alice
's BlindedMessage
C'
\u2013 Bob
's BlindSignature
on B'
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.
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):
(x, C)
\u2013\u00a0the ecash Proof
(e, s)
\u2013\u00a0the DLEQ proof revealed by Alice
r
\u2013 Alice
's blinding factorHere, 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.
129372'
(UTF-8 for \ud83e\udd5c)0'
keyset_k_int
)counter'
(this value is incremented)secret
or r
= 0
or 1
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.
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:
secret
and r
from counter
and keyset
BlindedMessage
from secret
BlindSignature
for secret
from the mintBlindSignature
to C
using r
Proof = (secret, C)
Proof
is already spentBlindedMessages
","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.
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
.
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.
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:
Proofs
in batches of 100 and increment counter
counter
to the value at the last successful restore + 1Wallets 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.
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.
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.
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":"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:
WsRequest
with the subscribe
command.WsResponse
containing an ok or an error.WsNotification
of the current state of the subscribed objects and whenever there is an update for the wallet's subscriptions.WsRequest
with the unsubscribe
command.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.
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.
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.
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.
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.
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":"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.
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/tests/00-tests/index.html b/tests/00-tests/index.html
index e360e56..14ad58f 100644
--- a/tests/00-tests/index.html
+++ b/tests/00-tests/index.html
@@ -122,7 +122,7 @@
@@ -132,7 +132,7 @@
@@ -142,7 +142,7 @@
@@ -204,7 +204,7 @@