NUT-00: Notation, Utilization, and Terminology¶
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.
- Sending user:
Alice - Receiving user:
Carol - Mint:
Bob
Blind Diffie-Hellmann key exchange (BDHKE)¶
Variables¶
Gelliptic curve generator point
Bob (mint)¶
kprivate key of mint (one for each amount)Kpublic key corresponding tokC_blind signature (onB_)
Alice (user)¶
xUTF-8-encoded random string (secret message), corresponds to pointY = hash_to_curve(x)on curverblinding factorB_blinded messageCunblinded signature
hash_to_curve(x: bytes) -> curve point Y¶
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)
Yderived public keyDOMAIN_SEPARATORconstant byte stringb"Secp256k1_HashToCurve_Cashu_"xmessage to hashcounteruint32 counter(byte order little endian) incremented from 0 until a point is found that lies on the curve
Protocol¶
- Mint
Bobpublishes public keyK = kG Alicepicks secretxand computesY = hash_to_curve(x)Alicesends toBob:B_ = Y + rGwithrbeing a random blinding factor (blinding)Bobsends back toAliceblinded key:C_ = kB_(these two steps are the DH key exchange) (signing)Alicecan calculate the unblinded key asC_ - rK = kY + krG - krG = kY = C(unblinding)- Alice can take the pair
(x, C)as a token and can send it toCarol. Carolcan send(x, C)toBobwho then checks thatk*hash_to_curve(x) == C(verification), and if so treats it as a valid spend of a token, addingxto the list of spent secrets.
0.1 - Models¶
BlindedMessage¶
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.
{
"amount": int,
"id": hex_str,
"B_": hex_str
}
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¶
A BlindSignature is sent from Bob to Alice after minting tokens or after swapping tokens. A BlindSignature is also called a promise.
{
"amount": int,
"id": hex_str,
"C_": hex_str
}
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¶
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.
{
"amount": int,
"id": hex_str,
"secret": str,
"C": hex_str,
}
amount is the amount of the Proof, secret is the secret message and is a utf-8 encoded string (the use of a 64 character hex string generated from 32 random bytes is recommended to prevent fingerprinting), C is the unblinded signature on secret (hex string), id is the keyset id of the mint public keys that signed the token (hex string).
0.2 - Protocol¶
Errors¶
In case of an error, mints respond with the HTTP status code 400 and include the following data in their response:
{
"detail": "oops",
"code": 1337
}
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.
0.3 - Methods¶
Serialization of tokens¶
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]
cashu is the Cashu token prefix. [version] is a single base64_urlsafe character to denote the token format version.
URI tags¶
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...
V3 tokens¶
V3 tokens are deprecated and the use of the more space-efficient V4 tokens is encouraged.
Version¶
This token format has the [version] value A.
Format¶
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]
[base64_token_json] is the token JSON serialized in base64_urlsafe. [base64_token_json] should be cleared of any whitespace before serializing.
Token format¶
The deserialized base64_token_json is
{
"token": [
{
"mint": str,
"proofs": Proofs
},
...
],
"unit": str <optional>,
"memo": str <optional>
}
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.
Example¶
Below is a TokenV3 JSON before base64_urlsafe serialization.
{
"token": [
{
"mint": "https://8333.space:3338",
"proofs": [
{
"amount": 2,
"id": "009a1f293253e41e",
"secret": "407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837",
"C": "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"
},
{
"amount": 8,
"id": "009a1f293253e41e",
"secret": "fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be",
"C": "029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"
}
]
}
],
"unit": "sat",
"memo": "Thank you."
}
When serialized, this becomes:
cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9
V4 tokens¶
V4 tokens are a space-efficient way of serializing tokens using the CBOR binary format. All keys are single characters and hex strings are encoded in binary. V4 tokens can only hold proofs from a single mint.
Version¶
This token format has the [version] value B.
Format¶
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]
Token format¶
For readability, the structure of a TokenV4 object is shown below in its equivalent JSON form.
{
"m": str, // mint URL
"u": str, // currency unit
"d": str <optional>, // optional memo
"t": [
{
"i": bytes, // keyset ID (short or long form)
"p": [ // proofs with this keyset ID
{
"a": int, // amount
"s": str, // secret
"c": bytes, // signature
"d": { <optional> // optional DLEQ proof
"e": bytes,
"s": bytes,
"r": bytes
},
"w": str <optional> // optional witness
},
...
]
},
...
],
}
m is the mint URL. The mint URL MUST be normalized by stripping any trailing slashes (/). u is the currency unit of the token keysets. Supported units are defined in Keysets. d is an optional, human-readable memo provided by the sender.
Within the t (token) array, i denotes the keyset ID associated with the proofs contained in p which are grouped by i. p is an array of Proof objects with the original keyset ID field id omitted. All proofs in the corresponding p array MUST belong to the same keyset ID.
Unless otherwise stated, fields of type bytes represent byte strings in the CBOR encoding. In the original JSON representation of Proof objects, these values are encoded as hexadecimal strings. Implementations MUST convert between hex strings and raw byte arrays when translating between JSON and CBOR representations.
Optional fields MAY be omitted if not present. Receivers MUST ignore unknown fields to preserve forward compatibility.
Short keyset ID¶
To reduce the size of the i field and the overall Token encoding, wallets MAY use the short keyset ID representation (s_id).
The short keyset ID is defined as the first 8 bytes of the full 33-byte keyset ID:
- Byte form:
s_id = id_bytes[:8] - Hex form:
s_id = id_hex_str[:16]
Wallets receiving a Token MUST support both short and full keyset ID representations. When a short keyset ID is encountered, the wallet MUST resolve it to the corresponding full keyset ID before processing the contained Proof objects.
If a short keyset ID resolves to more than one known full keyset ID, the identifier is considered ambiguous. In this case, the wallet MUST fail token parsing and return an error.
The mint is unaware of the s_id. All API endpoints exposed by the mint use the full keyset ID.
Example¶
Below is a TokenV4 JSON before CBOR and base64_urlsafe serialization.
{
"t": [
{
"i": h'00ffd48b8f5ecf80',
"p": [
{
"a": 1,
"s": "acc12435e7b8484c3cf1850149218af90f716a52bf4a5ed347e48ecc13f77388",
"c": h'0244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cf',
},
],
},
{
"i": h'00ad268c4d1f5826',
"p": [
{
"a": 2,
"s": "1323d3d4707a58ad2e23ada4e9f1f49f5a5b4ac7b708eb0d61f738f48307e8ee",
"c": h'023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94d',
},
{
"a": 1,
"s": "56bcbcbb7cc6406b3fa5d57d2174f4eff8b4402b176926d3a57d3c3dcbb59d57",
"c": h'0273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63',
},
],
},
],
"m": "http://localhost:3338",
"u": "sat",
}
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
Binary Token¶
Token can be transmitted in a binary format when applicable (for example when transmitting via NFC). For this the serialised token is prepended with a prefix and a version byte.
utf8("craw") || utf8(<token_version>) || <serialised_token>
- Binary Encoding V4:
utf8("craw") || utf8("B") || cbor(token_v4_object)