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 factor (scalar)B_blinded message (curve point)Cunblinded signature (curve point)
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.
Notation And Conventions
These specifications use elliptic curve public key cryptography over the field and curve parameters defined by secp256k1.
Types
Values in these specifications are one of:
- Scalars: integers modulo the curve order
n - Curve points: elements of the elliptic curve group (including the identity point at infinity)
- Byte sequences
Byte Operations And Encodings
- Concatenation
||appends one byte sequence to another. - All operations such as point addition, scalar multiplication, hashing, are performed on the underlying mathematical values, not on their serialized byte encodings.
- When a curve point is serialized as
hex_str, it is the hex encoding of the SEC1 compressed public key bytes, unless a NUT specifies a different encoding. - Integer byte order and fixed widths are defined by each NUT where relevant, if a NUT is silent, it must not be assumed.
Hashing
SHA256(m)denotes the SHA 256 hash of byte sequencem.- Any hash to curve function must specify its domain separator and exact procedure, including how counters are encoded and incremented.
0.1 - Models
BlindedMessage
An encrypted (“blinded”) secret and an amount are 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. Map key order is not significant: encoders MAY emit keys in any order and receivers MUST accept any order, so encoded tokens are not byte-comparable across implementations.
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
Tokens can be transmitted in a binary format when applicable (for example when transmitting via NFC). For this the serialized token is prepended with a prefix and a version byte.
utf8("craw") || utf8(<token_version>) || <serialized_token>
- Binary Encoding V4:
utf8("craw") || utf8("B") || cbor(token_v4_object)