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
G
elliptic curve generator point
k
private key of mint (one for each amount)K
public key of mintQ
promise (blinded signature)
x
random string (secret message), corresponds to pointY
on curver
private key (blinding factor)T
blinded messageZ
proof (unblinded signature)
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 stringb"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 curve
- Mint
Bob
publishes public keyK = kG
Alice
picks secretx
and computesY = hash_to_curve(x)
Alice
sends toBob
:B_ = Y + rG
withr
being a random blinding factor (blinding)Bob
sends back toAlice
blinded key:C_ = kB_
(these two steps are the DH key exchange) (signing)Alice
can 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
. Carol
can send(x, C)
toBob
who then checks thatk*hash_to_curve(x) == C
(verification), and if so treats it as a valid spend of a token, addingx
to the list of spent secrets.
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
.
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.
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).
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.
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.
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 are deprecated and the use of the more space-efficient V4 tokens is encouraged.
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]
[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
{
"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.
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 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.
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]
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.
{
"m": str, // mint URL
"u": str, // unit
"d": str <optional>, // memo
"t": [
{
"i": bytes, // keyset ID
"p": [ // proofs with this keyset ID
{
"a": int, // amount
"s": str, // secret
"c": bytes, // signature
"d": { <optional> // DLEQ proof
"e": bytes,
"s": bytes,
"r": bytes
},
"w": str <optional> // witness
},
...
]
},
...
],
}
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.
{
"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