Cashu Book of NUTs

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.

Blind Diffie-Hellmann key exchange (BDHKE)

Variables

Bob (mint)

Alice (user)

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)

Protocol

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:

Byte Operations And Encodings

Hashing

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:

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>