Skip to content

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

  • G elliptic curve generator point

Bob (mint)

  • k private key of mint (one for each amount)
  • K public key of mint
  • Q promise (blinded signature)

Alice (user)

  • x random string (secret message), corresponds to point Y on curve
  • r private key (blinding factor)
  • T blinded message
  • Z proof (unblinded 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)

  • Y derived public key
  • DOMAIN_SEPARATOR constant byte string b"Secp256k1_HashToCurve_Cashu_"
  • x message to hash
  • counter uint32 counter(byte order little endian) incremented from 0 until a point is found that lies on the curve

Protocol

  • Mint Bob 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)
  • Alice can take the pair (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.

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 field 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

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.

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