diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 42fa4b7..1f48281 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,9 +1,6 @@ name: Deploy MkDocs Site to GitHub Pages -on: - push: - branches: - - main +on: [push, pull_request] permissions: write-all @@ -12,6 +9,18 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + ref: gh-pages + + - name: Copy contents to docs/ + run: | + mkdir docs/ + + - name: Checkout main branch + uses: actions/checkout@v3 + with: + ref: main + path: docs - name: Set up Python uses: actions/setup-python@v4 @@ -28,4 +37,5 @@ jobs: uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./site + publish_branch: gh-pages + publish_dir: ./site \ No newline at end of file diff --git a/docs/00.md b/docs/00.md deleted file mode 100644 index c4d714b..0000000 --- a/docs/00.md +++ /dev/null @@ -1,321 +0,0 @@ -# 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][04] or for [swapping tokens][03]. A `BlindedMessage` is also called an `output`. - -```json -{ - "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][04] or after [swapping tokens][03]. A `BlindSignature` is also called a `promise`. - -```json -{ - "amount": int, - "id": hex_str, - "C_": hex_str -} -``` - -`amount` is the value of the blinded token, `id` is the [keyset id][02] 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][05]. [Serialized](#serialization-of-tokens) `Proofs` can also be sent from `Alice` to `Carol`. Upon receiving the token, `Carol` deserializes it and requests a [swap][03] from `Bob` to receive new `Proofs`. - -```json -{ - "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][02] 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: - -```json -{ - "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: - -```sh -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 - -```sh -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. - -```sh -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 - -```json -{ - "token": [ - { - "mint": str, - "proofs": Proofs - }, - ... - ], - "unit": str , - "memo": str -} -``` - -`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][01] for supported units), and `memo` is an optional text memo from the sender. - -##### Example - -Below is a TokenV3 JSON before `base64_urlsafe` serialization. - -```json -{ - "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. - -```sh -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. - -```json -{ - "m": str, // mint URL - "u": str, // unit - "d": str , // memo - "t": [ - { - "i": bytes, // keyset ID - "p": [ // proofs with this keyset ID - { - "a": int, // amount - "s": str, // secret - "c": bytes, // signature - "d": { // DLEQ proof - "e": bytes, - "s": bytes, - "r": bytes - }, - "w": str // 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][01] 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. - -```json -{ - "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](https://cbor.nemo157.com/#type=hex&value=a3617482a261694800ffd48b8f5ecf80617081a36161016173784061636331323433356537623834383463336366313835303134393231386166393066373136613532626634613565643334376534386563633133663737333838616358210244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cfa261694800ad268c4d1f5826617082a3616102617378403133323364336434373037613538616432653233616461346539663166343966356135623461633762373038656230643631663733386634383330376538656561635821023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94da36161016173784035366263626362623763633634303662336661356435376432313734663465666638623434303262313736393236643361353764336333646362623539643537616358210273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63616d75687474703a2f2f6c6f63616c686f73743a33333338617563736174). 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 -``` - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/01.md b/docs/01.md deleted file mode 100644 index 526831d..0000000 --- a/docs/01.md +++ /dev/null @@ -1,86 +0,0 @@ -# NUT-01: Mint public key exchange - -`mandatory` - ---- - -This document outlines the exchange of the public keys of the mint `Bob` with the wallet user `Alice`. `Alice` uses the keys to unblind `Bob`'s blind signatures (see [NUT-00][00]). - -## Description - -Wallet user `Alice` receives public keys from mint `Bob` via `GET /v1/keys`. The set of all public keys for a set of amounts is called a _keyset_. - -The mint responds only with its `active` keysets. Keyset are `active` if the mint will sign promises with it. The mint will accept tokens from inactive keysets as inputs but will not sign with them for new outputs. The `active` keysets can change over time, for example due to key rotation. A list of all keysets, active and inactive, can be requested separately (see [NUT-02][02]). - -Note that a mint can support multiple keysets at the same time but will only respond with the active keysets on the endpoint `GET /v1/keys`. A wallet can ask for the keys of a specific (active or inactive) keyset via the endpoint `GET /v1/keys/{keyset_id}` (see [NUT-02][02]). - -## Keyset generation - -Keysets are generated by the mint. The mint is free to use any key generation method they like. Each keyset is identified by its keyset `id` which can be computed by anyone from its public keys (see [NUT-02][02]). - -Keys in Keysets are maps of the form `{ : , : , ...}` for each `` of the amounts the mint `Bob` supports and the corresponding public key ``, that is `K_i` (see [NUT-00][00]). The mint **MUST** use the [compressed Secp256k1 public key format](https://learnmeabitcoin.com/technical/public-key#public-key-format) to represent its public keys. - -## Example - -Request of `Alice`: - -```http -GET https://mint.host:3338/v1/keys -``` - -With curl: - -```bash -curl -X GET https://mint.host:3338/v1/keys -``` - -Response `GetKeysResponse` of `Bob`: - -```json -{ - "keysets": [ - { - "id": , - "unit": , - "keys": { - : , - ... - } - } - ] -} -``` - -## Example response - -```json -{ - "keysets": [ - { - "id": "009a1f293253e41e", - "unit": "sat", - "keys": { - "1": "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104", - "2": "03b0f36d6d47ce14df8a7be9137712c42bcdd960b19dd02f1d4a9703b1f31d7513", - "4": "0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09", - "8": "0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d", - ... - } - } - ] -} -``` - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/02.md b/docs/02.md deleted file mode 100644 index 64d726a..0000000 --- a/docs/02.md +++ /dev/null @@ -1,206 +0,0 @@ -# NUT-02: Keysets and fees - -`mandatory` - ---- - -A keyset is a set of public keys that the mint `Bob` generates and shares with its users. It refers to the set of public keys that each correspond to the amount values that the mint supports (e.g. `1, 2, 4, 8, ...`) respectively. - -Each keyset indicates its keyset `id`, the currency `unit`, whether the keyset is `active`, and an `input_fee_ppk` that determines the fees for spending ecash from this keyset. - -A mint can have multiple keysets at the same time. For example, it could have one keyset for each currency `unit` that it supports. Wallets should support multiple keysets. They must respect the `active` and the `input_fee_ppk` properties of the keysets they use. - -## Keyset properties - -### Keyset ID - -A keyset `id` is an identifier for a specific keyset. It can be derived by anyone who knows the set of public keys of a mint. Wallets **CAN** compute the keyset `id` for a given keyset by themselves to confirm that the mint is supplying the correct keyset ID (see below). - -The keyset `id` is in each `Proof` so it can be used by wallets to identify which mint and keyset it was generated from. The keyset field `id` is also present in the `BlindedMessages` sent to the mint and `BlindSignatures` returned from the mint (see [NUT-00][00]). - -### Active keysets - -Mints can have multiple keysets at the same time but **MUST** have at least one `active` keyset (see [NUT-01][01]). The `active` property determines whether the mint allows generating new ecash from this keyset. `Proofs` from inactive keysets with `active=false` are still accepted as inputs but new outputs (`BlindedMessages` and `BlindSignatures`) **MUST** be from `active` keysets only. - -To rotate keysets, a mint can generate a new active keyset and inactive an old one. If the `active` flag of an old keyset is set to `false`, no new ecash from this keyset can be generated and the outstanding ecash supply of that keyset can be taken out of circulation as wallets rotate their ecash to active keysets. - -Wallets **SHOULD** prioritize swaps with `Proofs` from inactive keysets (see [NUT-03][03]) so they can quickly get rid of them. Wallets **CAN** swap their entire balance from an inactive keyset to an active one as soon as they detect that the keyset was inactivated. When constructing outputs for a transaction, wallets **MUST** choose only `active` keysets (see [NUT-00][00]). - -### Fees - -Keysets indicate the fee `input_fee_ppk` that is charged when a `Proof` of that keyset is spent as an input to a transaction. The fee is given in parts per thousand (ppk) per input measured in the `unit` of the keyset. The total fee for a transaction is the sum of all fees per input rounded up to the next larger integer (that that can be represented with the keyest). - -As an example, we construct a transaction spending 3 inputs (`Proofs`) from a keyset with unit `sat` and `input_fee_ppk` of `100`. A fee of `100 ppk` means `0.1 sat` per input. The sum of the individual fees are 300 ppk for this transaction. Rounded up to the next smallest denomination, the mint charges `1 sat` in total fees, i.e. `fees = ceil(0.3) == 1`. In this case, the fees for spending 1-10 inputs is 1 sat, 11-20 inputs is 2 sat and so on. - -#### Wallet transaction construction - -When constructing a transaction with ecash `inputs` (example: `/v1/swap` or `/v1/melt`), wallets **MUST** add fees to the inputs or, vice versa, subtract from the outputs. The mint checks the following equation: - -```python -sum(inputs) - fees == sum(outputs) -``` - -Here, `sum(inputs)` and `sum(outputs)` mean the sum of the amounts of the inputs and outputs respectively. `fees` is calculated from the sum of each input's fee and rounded up to the next larger integer: - -```python -def fees(inputs: List[Proof]) -> int: - sum_fees = 0 - for proof in inputs: - sum_fees += keysets[proof.id].input_fee_ppk - return (sum_fees + 999) // 1000 -``` - -Here, the `//` operator in `(sum_fees + 999) // 1000` denotes an integer division operator (aka floor division operator) that rounds down `sum_fees + 999` to the next lower integer. Alternatively, we could round up the sum using a floating point division with `ceil(sum_fees / 1000)` although it is not recommended to do so due to the non-deterministic behavior of floating point division. - -Notice that since transactions can spend inputs from different keysets, the sum considers the fee for each `Proof` indexed by the keyset ID individually. - -### Deriving the keyset ID - -#### Keyset ID version - -Keyset IDs have a version byte (two hexadecimal characters). The currently used version byte is `00`. - -The mint and the wallets of its users can derive a keyset ID from the keyset of the mint. The keyset ID is a lower-case hex string. To derive the keyset ID of a keyset, execute the following steps: - -``` -1 - sort public keys by their amount in ascending order -2 - concatenate all public keys to one byte array -3 - HASH_SHA256 the concatenated public keys -4 - take the first 14 characters of the hex-encoded hash -5 - prefix it with a keyset ID version byte -``` - -An example implementation in Python: - -```python -def derive_keyset_id(keys: Dict[int, PublicKey]) -> str: - sorted_keys = dict(sorted(keys.items())) - pubkeys_concat = b"".join([p.serialize() for p in sorted_keys.values()]) - return "00" + hashlib.sha256(pubkeys_concat).hexdigest()[:14] -``` - -## Example: Get mint keysets - -A wallet can ask the mint for a list of all keysets via the `GET /v1/keysets` endpoint. - -Request of `Alice`: - -```http -GET https://mint.host:3338/v1/keysets -``` - -With curl: - -```bash -curl -X GET https://mint.host:3338/v1/keysets -``` - -Response `GetKeysetsResponse` of `Bob`: - -```json -{ - "keysets": [ - { - "id": , - "unit": , - "active": , - "input_fee_ppk": , - }, - ... - ] -} -``` - -Here, `id` is the keyset ID, `unit` is the unit string (e.g. "sat") of the keyset, `active` indicates whether new ecash can be minted with this keyset, and `input_fee_ppk` is the fee (per thousand units) to spend one input spent from this keyset. If `input_fee_ppk` is not given, we assume it to be `0`. - -### Example response - -```json -{ - "keysets": [ - { - "id": "009a1f293253e41e", - "unit": "sat", - "active": True, - "input_fee_ppk": 100 - }, - { - "id": "0042ade98b2a370a", - "unit": "sat", - "active": False, - "input_fee_ppk": 100 - }, - { - "id": "00c074b96c7e2b0e", - "unit": "usd", - "active": True, - "input_fee_ppk": 100 - } - ] -} -``` - -## Requesting public keys for a specific keyset - -To receive the public keys of a specific keyset, a wallet can call the `GET /v1/keys/{keyset_id}` endpoint where `keyset_id` is the keyset ID. - -### Example - -Request of `Alice`: - -We request the keys for the keyset `009a1f293253e41e`. - -```http -GET https://mint.host:3338/v1/keys/009a1f293253e41e -``` - -With curl: - -```bash -curl -X GET https://mint.host:3338/v1/keys/009a1f293253e41e -``` - -Response of `Bob` (same as [NUT-01][01]): - -```json -{ - "keysets": [{ - "id": "009a1f293253e41e", - "unit": "sat", - "keys": { - "1": "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104", - "2": "03b0f36d6d47ce14df8a7be9137712c42bcdd960b19dd02f1d4a9703b1f31d7513", - "4": "0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09", - "8": "0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d", - ... - }, - }, ... - ] -} -``` - -## Wallet implementation notes - -Wallets can request the list of keyset IDs from the mint upon startup and load only tokens from its database that have a keyset ID supported by the mint it interacts with. This also helps wallets to determine whether the mint has added a new current keyset or whether it has changed the `active` flag of an existing one. - -A useful flow is: - -- If we don't have any keys from this mint yet, get all keys: `GET /v1/keys` and store them -- Get all keysets with `GET /v1/keysets` -- For all new keyset returned here which we don't have yet, get it using `GET /v1/keys/{keyset_id}` and store it -- If any of the keysets has changed its `active` flag, update it in the db and use the keyset accordingly - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md -[TokenV3]: https://github.com/cashuBTC/nuts/blob/main/00.md#023---v3-tokens diff --git a/docs/03.md b/docs/03.md deleted file mode 100644 index 946e33e..0000000 --- a/docs/03.md +++ /dev/null @@ -1,91 +0,0 @@ -# NUT-03: Swap tokens - -`mandatory` - ---- - -The swap operation is the most important component of the Cashu system. A swap operation consists of multiple inputs (`Proofs`) and outputs (`BlindedMessages`). Mints verify and invalidate the inputs and issue new promises (`BlindSignatures`). These are then used by the wallet to generate new `Proofs` (see [NUT-00][00]). - -The swap operation can serve multiple use cases. The first use case is that `Alice` can use it to split her tokens to a target amount she needs to send to `Carol`, if she does not have the necessary amounts to compose the target amount in her wallet already. The second one is that `Carols`'s wallet can use it to receive tokens from `Alice` by sending them as inputs to the mint and receive new outputs in return. - -## Swap to send - -To make this more clear, we present an example of a typical case of sending tokens from `Alice` to `Carol`. - -`Alice` has 64 sat in her wallet, composed of three `Proofs`, one worth 32 sat and another two worth 16 sat. She wants to send `Carol` 40 sat but does not have the necessary `Proofs` to compose the target amount of 40 sat. For that, `Alice` requests a swap from the mint and uses `Proofs` worth `[16, 16, 32]` as inputs and asks for new outputs worth `[8, 32, 8, 16]` totalling 64 sat. Notice that the first two tokens can now be combined to 40 sat. The `Proofs` that `Alice` sent `Bob` as inputs of the swap operation are now invalidated. - -Note: In order to preserve privacy around the amount that a client might want to send to another user and keep the rest as change, the client **SHOULD** ensure that the list requested outputs is ordered by amount in ascending order. As an example of what to avoid, a request for outputs expressed like so: `[16, 8, 2, 64, 8]` might imply the client is preparing a payment for 26 sat; the client should instead order the list like so: `[2, 8, 8, 16, 64]` to mitigate this privacy leak to the mint. - -## Swap to receive - -Another useful case for the swap operation follows up the example above where `Alice` has swapped her `Proofs` ready to be sent to `Carol`. `Carol` can receive these `Proofs` using the same operation by using them as inputs to invalidate them and request new outputs from `Bob`. Only if `Carol` has redeemed new outputs, `Alice` can't double-spend the `Proofs` anymore and the transaction is settled. To continue our example, `Carol` requests a swap with input `Proofs` worth `[32, 8]` to receive new outputs (of an arbitrary distribution) with the same total amount. - -## Example - -**Request** of `Alice`: - -```http -POST https://mint.host:3338/v1/swap -``` - -With the data being of the form `PostSwapRequest`: - -```json -{ - "inputs": , - "outputs": , -} -``` - -With curl: - -```bash -curl -X POST https://mint.host:3338/v1/swap -d \ -{ - "inputs": - [ - { - "amount": 2, - "id": "009a1f293253e41e", - "secret": "407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837", - "C": "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea" - }, - { - ... - } - ], - "outputs": - [ - { - "amount": 2, - "id": "009a1f293253e41e", - "B_": "02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239" - }, - { - ... - } - ], -} -``` - -If successful, `Bob` will respond with a `PostSwapResponse` - -```json -{ - "signatures": -} -``` - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/04.md b/docs/04.md deleted file mode 100644 index e3f7a28..0000000 --- a/docs/04.md +++ /dev/null @@ -1,241 +0,0 @@ -# NUT-04: Mint tokens - -`mandatory` - ---- - -Minting tokens is a two-step process: requesting a mint quote and minting new tokens. Here, we describe both steps. - -In the first request the wallet asks the mint for a quote for a specific `amount` and `unit` to mint, and the payment `method` to pay. The mint responds with a quote that includes a `quote` id and a payment `request`. The user pays the `request` and, if successful, requests minting of new tokens with the mint in a second request. The wallet includes the `quote` id and new `outputs` in the second request. - -We limit this document to mint quotes of `unit="sat"` and `method="bolt11"` which requests a bolt11 Lightning invoice (typically generated by the mint to add Bitcoin to its reserves) to mint ecash denominated in Satoshis. - -# Mint quote - -To request a mint quote, the wallet of `Alice` makes a `POST /v1/mint/quote/{method}` request where `method` is the payment method requested (here `bolt11`). - -```http -POST https://mint.host:3338/v1/mint/quote/bolt11 -``` - -The wallet of `Alice` includes the following `PostMintQuoteBolt11Request` data in its request: - -```json -{ - "amount": , - "unit": , - "description": -} -``` - -with the requested `amount` and the `unit`. An optional `description` can be passed if the mint signals support for it in `MintMethodSetting`. - -The mint `Bob` then responds with a `PostMintQuoteBolt11Response`: - -```json -{ - "quote": , - "request": , - "state": , - "expiry": -} -``` - -Where `quote` is the quote ID and `request` is the payment request to fulfill. `expiry` is the Unix timestamp until which the mint quote is valid. - -`state` is an enum string field with possible values `"UNPAID"`, `"PAID"`, `"ISSUED"`: - -- `"UNPAID"` means that the quote's request has not been paid yet. -- `"PAID"` means that the request has been paid. -- `"ISSUED"` means that the quote has already been issued. - -Note: `quote` is a **unique and random** id generated by the mint to internally look up the payment state. `quote` **MUST** remain a secret between user and mint and **MUST NOT** be derivable from the payment request. A third party who knows the `quote` ID can front-run and steal the tokens that this operation mints. - -## Example - -Request of `Alice` with curl: - -```bash -curl -X POST http://localhost:3338/v1/mint/quote/bolt11 -d '{"amount": 10, "unit": "sat"}' -H "Content-Type: application/json" -``` - -Response of `Bob`: - -```json -{ - "quote": "DSGLX9kevM...", - "request": "lnbc100n1pj4apw9...", - "state": "UNPAID", - "expiry": 1701704757 -} -``` - -The wallet **MUST** store the `amount` in the request and the `quote` id in the response in its database so it can later request the tokens after paying the request. After payment, the wallet continues with the next section. - -## Check mint quote state - -To check whether a mint quote has been paid, `Alice` makes a `GET /v1/mint/quote/bolt11/{quote_id}`. - -```http -GET https://mint.host:3338/v1/mint/quote/bolt11/{quote_id} -``` - -Like before, the mint `Bob` responds with a `PostMintQuoteBolt11Response`. - -Example request of `Alice` with curl: - -```bash -curl -X GET http://localhost:3338/v1/mint/quote/bolt11/DSGLX9kevM... -``` - -# Minting tokens - -After requesting a mint quote and paying the request, the wallet proceeds with minting new tokens by calling the `POST /v1/mint/{method}` endpoint where `method` is the payment method requested (here `bolt11`). - -```http -POST https://mint.host:3338/v1/mint/bolt11 -``` - -The wallet `Alice` includes the following `PostMintBolt11Request` data in its request - -```json -{ - "quote": , - "outputs": -} -``` - -with the `quote` being the quote ID from the previous step and `outputs` being `BlindedMessages` (see [NUT-00][00]) that the wallet requests signatures on whose sum is `amount` as requested in the quote. - -The mint `Bob` then responds with a `PostMintBolt11Response`: - -```json -{ - "signatures": -} -``` - -where `signatures` is an array of blind signatures on the outputs. - -## Example - -Request of `Alice` with curl: - -```bash -curl -X POST https://mint.host:3338/v1/mint/bolt11 -H "Content-Type: application/json" -d \ -'{ - "quote": "DSGLX9kevM...", - "outputs": [ - { - "amount": 8, - "id": "009a1f293253e41e", - "B_": "035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d" - }, - { - "amount": 2, - "id": "009a1f293253e41e", - "B_": "0288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6" - } - ] -}' -``` - -Response of `Bob`: - -```json -{ - "signatures": [ - { - "id": "009a1f293253e41e", - "amount": 2, - "C_": "0224f1c4c564230ad3d96c5033efdc425582397a5a7691d600202732edc6d4b1ec" - }, - { - "id": "009a1f293253e41e", - "amount": 8, - "C_": "0277d1de806ed177007e5b94a8139343b6382e472c752a74e99949d511f7194f6c" - } - ] -} -``` - -If the invoice was not paid yet, `Bob` responds with an error. In that case, `Alice` **CAN** repeat the same request until the Lightning invoice is settled. - -## Unblinding signatures - -Upon receiving the `BlindSignatures` from the mint `Bob`, the wallet of `Alice` unblinds them to generate `Proofs` (using the blinding factor `r` and the mint's public key `K`, see BDHKE [NUT-00][00]). The wallet then stores these `Proofs` in its database: - -```json -[ - { - "id": "009a1f293253e41e", - "amount": 2, - "secret": "407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837", - "C": "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea" - }, - { - "id": "009a1f293253e41e", - "amount": 8, - "secret": "fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be", - "C": "029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059" - } -] -``` - -## Settings - -The settings for this nut indicate the supported method-unit pairs for minting and whether minting is disabled or not. They are part of the info response of the mint ([NUT-06][06]) which in this case reads - -```json -{ - "4": { - "methods": [ - , - ... - ], - "disabled": - } -} -``` - -`MintMethodSetting` indicates supported `method` and `unit` pairs and additional settings of the mint. `disabled` indicates whether this minting is disabled. - -`MintMethodSetting` is of the form: - -```json -{ - "method": , - "unit": , - "min_amount": , - "max_amount": , - "description": -} -``` - -`min_amount` and `max_amount` indicate the minimum and maximum amount for an operation of this method-unit pair. - -Example `MintMethodSetting`: - -```json -{ - "method": "bolt11", - "unit": "sat", - "min_amount": 0, - "max_amount": 10000, - "description": true -} -``` - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/05.md b/docs/05.md deleted file mode 100644 index be55a41..0000000 --- a/docs/05.md +++ /dev/null @@ -1,212 +0,0 @@ -# NUT-05: Melting tokens - -`mandatory` - ---- - -Melting tokens is the opposite of minting tokens (see [NUT-04][04]). Like minting tokens, melting is a two-step process: requesting a melt quote and melting tokens. Here, we describe both steps. - -In the first request the wallet asks the mint for a quote for a `request` it wants paid by the mint and the `unit` the wallet would like to spend as inputs. The mint responds with a quote that includes a `quote` id and an `amount` the mint demands in the requested unit. For the method `bolt11`, the mint includes a `fee_reserve` field indicating the reserve fee for a Lightning payment. - -In the second request, the wallet includes the `quote` id and provides `inputs` that sum up to `amount+fee_reserve` in the first response. For the method `bolt11`, the wallet can also include `outputs` in order for the mint to return overpaid Lightning fees (see [NUT-08][08]). The mint responds with a payment status `paid` and a `proof` of payment. If the request included `outputs`, the mint may respond with `change` for the overpaid fees (see [NUT-08][08]). - -We limit this document to mint quotes of `unit="sat"` and `method="bolt11"` which requests a bolt11 Lightning payment (typically paid by the mint from its Bitcoin reserves) using ecash denominated in Satoshis. - -# Melt quote - -To request a melt quote, the wallet of `Alice` makes a `POST /v1/melt/quote/{method}` request where `method` is the payment method requested (here `bolt11`). - -```http -POST https://mint.host:3338/v1/melt/quote/bolt11 -``` - -The wallet `Alice` includes the following `PostMeltQuoteBolt11Request` data in its request: - -```json -{ - "request": , - "unit": -} -``` - -Here, `request` is the bolt11 Lightning invoice to be paid and `unit` is the unit the wallet would like to pay with. - -The mint `Bob` then responds with a `PostMeltQuoteBolt11Response`: - -```json -{ - "quote": , - "amount": , - "fee_reserve": , - "state": , - "expiry": , - "payment_preimage": -} -``` - -Where `quote` is the quote ID, `amount` the amount that needs to be provided, and `fee_reserve` the additional fee reserve that is required. The mint expects `Alice` to include `Proofs` of _at least_ `total_amount = amount + fee_reserve`. `expiry` is the Unix timestamp until which the melt quote is valid. `payment_preimage` is the bolt11 payment preimage in case of a successful payment. - -`state` is an enum string field with possible values `"UNPAID"`, `"PENDING"`, `"PAID"`: - -- `"UNPAID"` means that the request has not been paid yet. -- `"PENDING"` means that the request is currently being paid. -- `"PAID"` means that the request has been paid successfully. - -## Example - -Request of `Alice` with curl: - -```bash -curl -X POST https://mint.host:3338/v1/melt/quote/bolt11 -d \ -{ - "request": "lnbc100n1p3kdrv5sp5lpdxzghe5j67q...", - "unit": "sat" -} -``` - -Response of `Bob`: - -```json -{ - "quote": "TRmjduhIsPxd...", - "amount": 10, - "fee_reserve": 2, - "state": "UNPAID", - "expiry": 1701704757 -} -``` - -## Check melt quote state - -To check whether a melt quote has been paid, `Alice` makes a `GET /v1/melt/quote/bolt11/{quote_id}`. - -```http -GET https://mint.host:3338/v1/melt/quote/bolt11/{quote_id} -``` - -Like before, the mint `Bob` responds with a `PostMeltQuoteBolt11Response`. - -Example request of `Alice` with curl: - -```bash -curl -X GET http://localhost:3338/v1/melt/quote/bolt11/TRmjduhIsPxd... -``` - -# Melting tokens - -Now that `Alice` knows what the total amount is (`amount + fee_reserve`) in her requested `unit`, she can proceed to melting tokens for which a payment will be executed by the mint. She calls the `POST /v1/melt/{method}` endpoint where `method` is the payment method requested (here `bolt11`). - -```http -POST https://mint.host:3338/v1/melt/bolt11 -``` - -⚠️ **Attention:** This call will block until the Lightning payment either succeeds or fails. This can take quite a long time in case the Lightning payment is slow. Make sure to **use no (or a very long) timeout when making this call**! - -The wallet of `Alice` includes the following `PostMeltBolt11Request` data in its request - -```json -{ - "quote": , - "inputs": -} -``` - -Here, `quote` is the melt quote ID to be paid and `inputs` are the proofs with a total amount of at least `amount + fee_reserve` (see previous melt quote response). - -Like before, the mint `Bob` then responds with a `PostMeltQuoteBolt11Response`. If the payment was successful, the `state` field is set to `"PAID"` and the response includes the `payment_preimage` field containing the payment secret of the bolt11 payment. - -If `state=="PAID"`, `Alice`'s wallet can delete the `inputs` from her database (or move them to a history). If `state=="UNPAID"`, `Alice` can repeat the same request again until the payment is successful. - -## Example - -Request of `Alice` with curl: - -```bash -curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \ -'{ - "quote": "od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP", - "inputs": [ - { - "amount": 4, - "id": "009a1f293253e41e", - "secret": "429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5", - "C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011", - }, - { - "amount": 8, - "id": "009a1f293253e41e", - "secret": "4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad", - "C": "0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9", - } - ] -}' -``` - -Response `PostMeltQuoteBolt11Response` of `Bob`: - -```json -{ - "quote": "TRmjduhIsPxd...", - "amount": 10, - "fee_reserve": 2, - "state": "PAID", - "expiry": 1701704757, - "payment_preimage": "c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b" -} -``` - -## Settings - -The mint's settings for this nut indicate the supported method-unit pairs for melting. They are part of the info response of the mint ([NUT-06][06]) which in this case reads - -```json -{ - "5": { - "methods": [ - , - ... - ], - "disabled": - } -} -``` - -`MeltMethodSetting` indicates supported `method` and `unit` pairs and additional settings of the mint. `disabled` indicates whether melting is disabled. - -`MeltMethodSetting` is of the form: - -```json -{ - "method": , - "unit": , - "min_amount": , - "max_amount": -} -``` - -`min_amount` and `max_amount` indicate the minimum and maximum amount for an operation of this method-unit pair. - -Example `MeltMethodSetting`: - -```json -{ - "method": "bolt11", - "unit": "sat", - "min_amount": 100, - "max_amount": 10000 -} -``` - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/06.md b/docs/06.md deleted file mode 100644 index 8c88220..0000000 --- a/docs/06.md +++ /dev/null @@ -1,114 +0,0 @@ -# NUT-06: Mint information - -`mandatory` - ---- - -This endpoint returns information about the mint that a wallet can show to the user and use to make decisions on how to interact with the mint. - -## Example - -**Request** of `Alice`: - -```http -GET https://mint.host:3338/v1/info -``` - -With the mint's response being of the form `GetInfoResponse`: - -```json -{ - "name": "Bob's Cashu mint", - "pubkey": "0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99", - "version": "Nutshell/0.15.0", - "description": "The short mint description", - "description_long": "A description that can be a long piece of text.", - "contact": [ - { - "method": "email", - "info": "contact@me.com" - }, - { - "method": "twitter", - "info": "@me" - }, - { - "method": "nostr", - "info": "npub..." - } - ], - "motd": "Message to display to users.", - "icon_url": "https://mint.host/icon.jpg", - "time": 1725304480, - "nuts": { - "4": { - "methods": [ - { - "method": "bolt11", - "unit": "sat", - "min_amount": 0, - "max_amount": 10000 - } - ], - "disabled": false - }, - "5": { - "methods": [ - { - "method": "bolt11", - "unit": "sat", - "min_amount": 100, - "max_amount": 10000 - } - ], - "disabled": false - }, - "7": { - "supported": true - }, - "8": { - "supported": true - }, - "9": { - "supported": true - }, - "10": { - "supported": true - }, - "12": { - "supported": true - } - } -} -``` - -- (optional) `name` is the name of the mint and should be recognizable. -- (optional) `pubkey` is the hex pubkey of the mint. -- (optional) `version` is the implementation name and the version of the software running on this mint separated with a slash "/". -- (optional) `description` is a short description of the mint that can be shown in the wallet next to the mint's name. -- (optional) `description_long` is a long description that can be shown in an additional field. -- (optional) `contact` is an array of contact objects to reach the mint operator. A contact object consists of two fields. The `method` field denotes the contact method (like "email"), the `info` field denotes the identifier (like "contact@me.com"). -- (optional) `motd` is the message of the day that the wallet must display to the user. It should only be used to display important announcements to users, such as scheduled maintenances. -- (optional) `icon_url` is the URL pointing to an image to be used as an icon for the mint. Recommended to be squared in shape. -- (optional) `time` is the current time set on the server. The value is passed as a Unix timestamp integer. -- (optional) `nuts` indicates each NUT specification that the mint supports and its settings. The settings are defined in each NUT separately. - -With curl: - -```bash -curl -X GET https://mint.host:3338/v1/info -``` - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/07.md b/docs/07.md deleted file mode 100644 index c2ef444..0000000 --- a/docs/07.md +++ /dev/null @@ -1,110 +0,0 @@ -# NUT-07: Token state check - -`optional` - ---- - -With the token state check, wallets can ask the mint whether a specific proof is already spent and whether it is in-flight in a transaction. Wallets can also request the witness data that was used to spend a proof. - -### Token states - -A proof can be in one of the following states - -- A proof is `UNSPENT` if it has not been spent yet -- A proof is `PENDING` if it is being processed in a transaction (in an ongoing payment). A `PENDING` proof cannot be used in another transaction until it is `live` again. -- A proof is `SPENT` if it has been redeemed and its secret is in the list of spent secrets of the mint. - -**Note:** Before deleting spent proofs from their database, wallets can check if the proof is `SPENT` to make sure that they don't accidentally delete an unspent proof. Beware that this behavior can make it easier for the mint to correlate the sender to the receiver. - -**Important:** Mints **MUST** remember which proofs are currently `PENDING` to avoid reuse of the same token in multiple concurrent transactions. This can be achieved with for example mutex lock whose key is the `Proof`'s `Y`. - -## Use cases - -#### Example 1: Ecash transaction - -When `Alice` prepares a token to be sent to `Carol`, she can mark these tokens in her database as _pending_. She can then, periodically or upon user input, check with the mint if the token is `UNSPENT` or whether it has been redeemed by `Carol` already, i.e., is `SPENT`. If the proof is not spendable anymore (and, thus, has been redeemed by `Carol`), she can safely delete the proof from her database. - -#### Example 2: Lightning payments - -If `Alice`'s melt operation takes a long time to complete (for example if she requests a very slow Lightning payment) and she closes her wallet in the meantime, the next time she comes online, she can check all proofs marked as _pending_ in her database to determine whether the payment is still in flight (mint returns `PENDING`), it has succeeded (mint returns `SPENT`), or it has failed (mint returns `UNSPENT`). - -## Example - -**Request** of `Alice`: - -```http -POST https://mint.host:3338/v1/checkstate -``` - -With the data being of the form `PostCheckStateRequest`: - -```json -{ - "Ys": , -} -``` - -Where the elements of the array in `Ys` are the hexadecimal representation of the compressed point `Y = hash_to_curve(secret)` of the `Proof` to check (see [NUT-00][00]). - -**Response** of `Bob`: - -`Bob` will respond with a `PostCheckStateResponse` - -```json -{ - "states": [ - { - "Y": , - "state": , - "witness": , - }, - ... - ] -} -``` - -- `Y` corresponds to the `Proof` checked in the request. -- `state` is an enum string field with possible values `"UNSPENT"`, `"PENDING"`, `"SPENT"` -- `witness` is the serialized witness data that was used to spend the `Proof` if the token required it such as in the case of P2PK (see [NUT-11][11]). - -With curl: - -**Request** of `Alice`: - -```bash -curl -X POST https://mint.host:3338/v1/checkstate -H 'Content-Type: application/json' -d '{ - "Ys": [ - "02599b9ea0a1ad4143706c2a5a4a568ce442dd4313e1cf1f7f0b58a317c1a355ee" - ] -}' -``` - -**Response** of `Bob`: - -```json -{ - "states": [ - { - "Y": "02599b9ea0a1ad4143706c2a5a4a568ce442dd4313e1cf1f7f0b58a317c1a355ee", - "state": "SPENT", - "witness": "{\"signatures\": [\"b2cf120a49cb1ac3cb32e1bf5ccb6425e0a8372affdc1d41912ca35c13908062f269c0caa53607d4e1ac4c8563246c4c8a869e6ee124ea826fd4746f3515dc1e\"]}" - } - ] -} -``` - -Where `Y` belongs to the provided `Proof` to check in the request, `state` indicates its state, and `witness` is the witness data that was potentially provided in a previous spend operation (can be empty). - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/08.md b/docs/08.md deleted file mode 100644 index abb8d0f..0000000 --- a/docs/08.md +++ /dev/null @@ -1,145 +0,0 @@ -# NUT-08: Lightning fee return - -`optional`, `depends on: NUT-05` - ---- - -This document describes how the overpaid Lightning fees are handled and extends [NUT-05][05] which describes melting tokens (i.e. paying a Lightning invoice). In short, a wallet includes _blank outputs_ when paying a Lightning invoice which can be assigned a value by the mint if the user has overpaid Lightning fees. This can be the case due to the unpredictability of Lightning network fees. To solve this issue, we introduce so-called _blank outputs_ which are blinded messages with an undetermined value. - -The problem is also described in [this gist](https://gist.github.com/callebtc/a6cc0bd2b6f70e081e478147c40fc578). - -## Description - -Before requesting a Lightning payment as described in [NUT-05][05], `Alice` produces a number of `BlindedMessage` which are similar to ordinary blinded messages but their value is yet to be determined by the mint `Bob` and are thus called _blank outputs_. The number of necessary blank outputs is `max(ceil(log2(fee_reserve)), 1)` which ensures that there is at least one output if there is any fee. If the `fee_reserve` is `0`, then the number of blank outputs is `0` as well. The blank outputs will contain the overpaid fees that will be returned by the mint to the wallet. - -This code calculates the number of necessary blank outputs in Python: - -```python -def calculate_number_of_blank_outputs(fee_reserve_sat: int) -> int: - assert fee_reserve_sat >= 0, "Fee reserve can't be negative." - if fee_reserve_sat == 0: - return 0 - return max(math.ceil(math.log2(fee_reserve_sat)), 1) -``` - -## Example - -The wallet wants to pay an invoice with `amount := 100 000 sat` and determines by asking the mint that `fee_reserve` is `1000 sats`. The wallet then provides `101 000 sat` worth of proofs and 10 blank `outputs` to make the payment (since `ceil(log2(1000))=ceil(9.96..)=10`). The mint pays the invoice and determines that the actual fee was `100 sat`, i.e, the overpaid fee to return is `fee_return = 900 sat`. The mint splits the amount `900` into summands of `2^n` which is `4, 128, 256, 512`. The mint inserts these amounts into the blank `outputs` it received form the wallet and generates 4 new promises. The mint then returns these `BlindSignature`s to the wallet together with the successful payment status. - -## Wallet flow - -The wallet asks the mint for the `fee_reserve` for paying a specific bolt11 invoice of value `amount` by calling `POST /v1/melt/quote` as described in [NUT-05][05]. The wallet then provides a `PostMeltBolt11Request` to `POST /v1/melt/bolt11` that has (1) proofs of the value `amount+fee_reserve`, (2) the bolt11 invoice to be paid, and finally, as a new entry, (3) a field `outputs` that has `n_blank_outputs` blinded messages that are generated before the payment attempt to receive potential overpaid fees back to her. - -## Mint flow - -Here we describe how the mint generates `BlindSignature`s for the overpaid fees. The mint `Bob` returns in `PostMeltQuoteBolt11Response` the field `change` **ONLY IF** `Alice` has previously provided `outputs` for the change **AND** if the Lightning `actual_fees` were smaller than the `fee_reserve`. - -If the `overpaid_fees = fee_reserve - actual_fees` is positive, `Bob` decomposes it to values of `2^n` (as in [NUT-00][00]) and then imprints them into the `blank_outputs` provided by `Alice`. - -`Bob` then signs these blank outputs (now with the imprinted amounts) and thus generates `BlindSignature`s. `Bob` then returns a payment status to the wallet, and, in addition, all blind signatures it generated for the overpaid fees. - -Importantly, while `Bob` does not necessarily return the same number of blind signatures as it received blank outputs from `Alice` (since some of them may be of value 0), `Bob` **MUST** return the all blank signatures with a value greater than 0 in the same order as the blank outputs were received and should omit all blind signatures with value 0. For example, if `Bob` receives 10 blank outputs but the overpaid fees only occupy 4 blind signatures, `Bob` will only return these 4 blind signatures with the appropriate imprinted amounts and omit the remaining 6 blind signatures with value 0. Due to the well-defined order of the returned blind signatures, `Alice` can map the blind signatures returned from `Bob` to the blank outputs it provided so that she can further apply the correct unblinding operations on them. - -## Example - -**Request** of `Alice`: - -```http -POST https://mint.host:3338/v1/melt/bolt11 -``` - -With the data being of the form `PostMeltBolt11Request`: - -```json -{ - "quote": , - "inputs": , - "outputs": <-- New -} -``` - -where the new `output` field carries the `BlindMessages`. - -The mint `Bob` then responds with a `PostMeltQuoteBolt11Response`: - -```json -{ - "quote": , - "amount": , - "fee_reserve": , - "state": , - "expiry": , - "payment_preimage": , - "change": <-- New -} -``` - -where the new `change` field carries the returned `BlindSignature`s due to overpaid fees. - -## Example - -Request of `Alice` with curl: - -```bash -curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \ -'{ - "quote": "od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP", - "inputs": [ - { - "amount": 4, - "id": "009a1f293253e41e", - "secret": "429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5", - "C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011", - }, - { - "amount": 8, - "id": "009a1f293253e41e", - "secret": "4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad", - "C": "0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9", - } - ], - "outputs": [ - { - "amount": 1, - "id": "009a1f293253e41e", - "B_": "03327fc4fa333909b70f08759e217ce5c94e6bf1fc2382562f3c560c5580fa69f4" - } - ] -}' -``` - -Everything here is the same as in [NUT-05][05] except for `outputs`. The `amount` field in the `BlindedMessage`s here are ignored by `Bob` so they can be set to any arbitrary value by `Alice` (they should be set to a value, like `1` so potential JSON validations do not error). - -If the mint has made a successful payment, it will respond the following. - -**Response** `PostMeltQuoteBolt11Response` from `Bob`: - -```json -{ - "state": "PAID", - "payment_preimage": "c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b", - "change": [ - { - "id": "009a1f293253e41e", - "amount": 2, - "C_": "03c668f551855ddc792e22ea61d32ddfa6a45b1eb659ce66e915bf5127a8657be0" - } - ] -} -``` - -The field `change` is an array of `BlindSignatures` that account for the overpaid fees. Notice that the amount has been changed by the mint. `Alice` must take these and generate `Proofs` by unblinding them as described in [NUT-00][00] and as she does in [NUT-04][04] when minting new tokens. After generating the `Proofs`, `Alice` stores them in her database. - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/09.md b/docs/09.md deleted file mode 100644 index 04c7f4e..0000000 --- a/docs/09.md +++ /dev/null @@ -1,43 +0,0 @@ -# NUT-09: Restore signatures - -`optional` `used in: NUT-13` - ---- - -In this document, we describe how wallets can recover blind signatures, and with that their corresponding `Proofs`, by requesting from the mint to reissue the blind signatures. This can be used for a backup recovery of a lost wallet (see [NUT-09][09]) or for recovering the response of an interrupted swap request (see [NUT-03][03]). - -Mints must store the `BlindedMessage` and the corresponding `BlindSignature` in their database every time they issue a `BlindSignature`. Wallets provide the `BlindedMessage` for which they request the `BlindSignature`. Mints only respond with a `BlindSignature`, if they have previously signed the `BlindedMessage`. Each returned `BlindSignature` also contains the `amount` and the keyset `id` (see [NUT-00][00]) which is all the necessary information for a wallet to recover a `Proof`. - -**Request** of `Alice`: - -```http -POST https://mint.host:3338/v1/restore -``` - -With the data being of the form `PostRestoreRequest`: - -```json -{ - "outputs": -} -``` - -**Response** of `Bob`: - -The mint `Bob` then responds with a `PostRestoreResponse`. - -```json -{ - "outputs": , - "signatures": -} -``` - -The returned arrays `outputs` and `signatures` are of the same length and for every entry `outputs[i]`, there is a corresponding entry `signatures[i]`. - -[00]: 00.md -[02]: 02.md -[03]: 03.md -[07]: 07.md -[09]: 09.md -[11]: 11.md diff --git a/docs/10.md b/docs/10.md deleted file mode 100644 index 99b131e..0000000 --- a/docs/10.md +++ /dev/null @@ -1,68 +0,0 @@ -# NUT-10: Spending conditions - -`optional` - ---- - -An ordinary ecash token is a set of `Proofs` each with a random string `secret`. To spend such a token in a [swap][03] or a [melt][05] operation, wallets include `proofs` in their request each with a unique `secret`. To autorize a transaction, the mint requires that the `secret` has not been seen before. This is the most fundamental spending condition in Cashu, which ensures that a token can't be double-spent. - -In this NUT, we define a well-known format of `secret` that can be used to express more complex spending conditions. These conditions need to be met before the mint authorizes a transaction. Note that the specific type of spending condition is not part of this document but will be explained in other documents. Here, we describe the structure of `secret` which is expressed as a JSON `Secret` with a specific format. - -Spending conditions are enforced by the mint which means that, upon encountering a `Proof` where `Proof.secret` can be parsed into the well-known format, the mint can require additional conditions to be met. - -Caution: If the mint does not support spending conditions or a specific `kind` of spending condition, proofs may be treated as a regular anyone-can-spend tokens. Applications need to make sure to check whether the mint supports a specific `kind` of spending condition by checking the mint's [info][06] endpoint. - -## Basic components - -An ecash transaction, i.e., a [swap][03] or a [melt][05] operation, with a spending condition consists of the following components: - -- Inputs referring to the `Proofs` being spent -- `Secret` containing the rules for unlocking a `Proof` -- Additional witness data satisfying the unlock conditions such as signatures -- Outputs referring to the `BlindMessages` with new unlock conditions to which the `Proofs` are spent to - -Spending conditions are defined for each individual `Proof` and not on a transaction level that can consist of multiple `Proofs`. Similarly, spending conditions must be satisfied by providing signatures or additional witness data for each `Proof` separately. For a transaction to be valid, all `Proofs` in that transaction must be unlocked successfully. - -New `Secret`s of the outputs to which the inputs are spent to are provided as `BlindMessages` which means that they are blind-signed and not visible to the mint until they are actually spent. - -## Well-known Secret - -Spending conditions are expressed in a well-known secret format that is revealed to the mint when spending (unlocking) a token, not when the token is minted (locked). The mint parses each `Proof`'s `secret`. If it can deserialize it into the following format it executes additional spending conditions that are further specified in additional NUTs. - -The well-known `Secret` stored in `Proof.secret` is a JSON of the format: - -```json -[ -kind , - { - "nonce": , - "data": , - "tags": [[ "key", "value1", "value2", ...], ... ], // (optional) - } -] -``` - -- `kind` is the kind of the spending condition -- `nonce` is a unique random string -- `data` expresses the spending condition specific to each kind -- `tags` hold additional data committed to and can be used for feature extensions - -## Examples - -Example use cases of this secret format are - -- [NUT-11][11]: Pay-to-Public-Key (P2PK) - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/11.md b/docs/11.md deleted file mode 100644 index f600666..0000000 --- a/docs/11.md +++ /dev/null @@ -1,185 +0,0 @@ -# NUT-11: Pay to Public Key (P2PK) - -`optional`, `depends on: NUT-10` - ---- - -This NUT describes Pay-to-Public-Key (P2PK) which is one kind of spending condition based on [NUT-10][10]'s well-known `Secret`. Using P2PK, we can lock ecash tokens to a receiver's ECC public key and require a Schnorr signature with the corresponding private key to unlock the ecash. The spending condition is enforced by the mint. - -Caution: If the mint does not support this type of spending condition, proofs may be treated as a regular anyone-can-spend tokens. Applications need to make sure to check whether the mint supports a specific kind of spending condition by checking the mint's [info][06] endpoint. - -## Pay-to-Pubkey - -[NUT-10][10] Secret `kind: P2PK` - -If for a `Proof`, `Proof.secret` is a `Secret` of kind `P2PK`, the proof must be unlocked by providing a witness `Proof.witness` and one or more valid signatures in the array `Proof.witness.signatures`. - -In the basic case, when spending a locked token, the mint requires one valid Schnorr signature in `Proof.witness.signatures` on `Proof.secret` by the public key in `Proof.Secret.data`. - -To give a concrete example of the basic case, to mint a locked token we first create a P2PK `Secret` that reads: - -```json -[ - "P2PK", - { - "nonce": "859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f", - "data": "0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7", - "tags": [["sigflag", "SIG_INPUTS"]] - } -] -``` - -Here, `Secret.data` is the public key of the recipient of the locked ecash. We serialize this `Secret` to a string in `Proof.secret` and get a blind signature by the mint that is stored in `Proof.C` (see [NUT-03][03]]). - -The recipient who owns the private key of the public key `Secret.data` can spend this proof by providing a signature on the serialized `Proof.secret` string that is then added to `Proof.witness.signatures`: - -```json -{ - "amount": 1, - "secret": "[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]", - "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", - "id": "009a1f293253e41e", - "witness": "{\"signatures\":[\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\"]}" -} -``` - -#### Signature scheme - -To spend a token locked with `P2PK`, the spender needs to include signatures in the spent proofs. We use `libsecp256k1`'s serialized 64 byte Schnorr signatures on the SHA256 hash of the message to sign. The message to sign is the field `Proof.secret` in the inputs. If indicated by `Secret.tags.sigflag` in the inputs, outputs might also require signatures on the message `BlindedMessage.B_`. - -An ecash spending operation like [swap][03] and [melt][05] can have multiple inputs and outputs. If we have more than one input or output, we provide signatures in each `Proof` and `BlindedMessage` individually. The inputs are the `Proofs` provided in the `inputs` field and the outputs are the `BlindedMessages` in the `outputs` field in the request body (see `PostMeltRequest` in [NUT-05][05] and `PostSwapRequest` in [NUT-03][03]). - -### Tags - -More complex spending conditions can be defined in the tags in `Proof.tags`. All tags are optional. Tags are arrays with two or more strings being `["key", "value1", "value2", ...]`. - -Supported tags are: - -- `sigflag: ` determines whether outputs have to be signed as well -- `n_sigs: ` specifies the minimum number of valid signatures expected -- `pubkeys: ` are additional public keys that can provide signatures (_allows multiple entries_) -- `locktime: ` is the Unix timestamp of when the lock expires -- `refund: ` are optional refund public keys that can exclusively spend after `locktime` (_allows multiple entries_) - -**Note:** The tag serialization type is `[, , ...]` but some tag values are `int`. Wallets and mints must cast types appropriately for de/serialization. - -#### Signature flags - -Signature flags are defined in the tag `Secret.tags['sigflag']`. Currently, there are two signature flags. - -- `SIG_INPUTS` requires valid signatures on all inputs. It is the default signature flag and will be applied even if the `sigflag` tag is absent. -- `SIG_ALL` requires valid signatures on all inputs and on all outputs. - -The signature flag `SIG_ALL` is enforced if at least one of the `Proofs` have the flag `SIG_ALL`. Otherwise, `SIG_INPUTS` is enforced. - -#### Signature - -Signatures must be provided in the field `Proof.witness.signatures` for each `Proof` which is an input. If the signature flag `SIG_ALL` is enforced, signatures must also be provided for every output in its field `BlindedMessage.witness.signatures`. - -##### Signed inputs - -A `Proof` (an input) with a signature `P2PKWitness.signatures` on `secret` is the JSON (see [NUT-00][00]): - -```json -{ - "amount": , - "secret": , - "C": , - "id": , - "witness": // Signatures on "secret" -} -``` - -The `secret` of each input is **signed as a string**. - -##### Signed outputs - -A `BlindedMessage` (an output) with a signature `P2PKWitness.signatures` on `B_` is the JSON (see [NUT-00][00]): - -```json -{ - "amount": , - "B_": , - "witness": // Signatures on "B_" -} -``` - -The `B_` of each output is **signed as bytes** which comes from the original hex string. - -##### Witness format - -`P2PKWitness` is a serialized JSON string of the form - -```json -{ - "signatures": ]> -} -``` - -The `signatures` are an array of signatures in hex. - -### Multisig - -If the tag `n_sigs` is a positive integer, the mint will also consider signatures from public keys specified in the `pubkeys` tag additional to the public key in `Secret.data`. If the number of valid signatures is greater or equal to the number specified in `n_sigs`, the transaction is valid. - -Expressed as an "n-of-m" scheme, `n = n_sigs` is the number of required signatures and `m = 1 ("data" field) + len(pubkeys tag)` is the number of public keys that could sign. - -### Locktime - -If the tag `locktime` is the unix time and the mint's local clock is greater than `locktime`, the `Proof` becomes spendable by anyone, except if the following condition is also true. Note: A `Proof` is considered spendable by anyone if it only requires a `secret` and a valid signature `C` to be spent (which is the default case). - -#### Refund public keys - -If the `locktime` is in the past and a tag `refund` is present, the `Proof` is spendable only if a valid signature by one of the the `refund` pubkeys is provided in `Proof.witness.signatures` and, depending on the signature flag, in `BlindedMessage.witness.signatures`. - -#### Complex Example - -This is an example `secret` that locks a `Proof` with a Pay-to-Pubkey (P2PK) condition that requires 2-of-3 signatures from the public keys in the `data` field and the `pubkeys` tag. If the `timelock` has passed, the `Proof` becomes spendable with a single signature from the public key in the `refund` tag. The signature flag `sigflag` indicates that signatures are necessary on the `inputs` and the `outputs` of a transaction. - -```json -[ - "P2PK", - { - "nonce": "da62796403af76c80cd6ce9153ed3746", - "data": "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e", - "tags": [ - ["sigflag", "SIG_ALL"], - ["n_sigs", "2"], - ["locktime", "1689418329"], - [ - "refund", - "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e" - ], - [ - "pubkeys", - "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", - "023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54" - ] - ] - } -] -``` - -## Use cases - -The following use cases are unlocked using P2PK: - -- Publicly post locked ecash that can only be redeemed by the intended receiver -- Final offline-receiver payments that can't be double-spent when combined with an offline signature check mechanism like DLEQ proofs -- Receiver of locked ecash can defer and batch multiple mint round trips for receiving proofs (requires DLEQ) -- Ecash that is owned by multiple people via the multisignature abilities -- Atomic swaps when used in combination with the locktime feature - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/12.md b/docs/12.md deleted file mode 100644 index a78308b..0000000 --- a/docs/12.md +++ /dev/null @@ -1,145 +0,0 @@ -# NUT-12: Offline ecash signature validation - -`optional` - ---- - -In this document, we present an extension of Cashu's crypto system to allow a user `Alice` to verify the mint `Bob`'s signature using only `Bob`'s public keys. We explain how another user `Carol` who receives ecash from `Alice` can execute the DLEQ proof as well. This is achieved using a Discrete Log Equality (DLEQ) proof. Previously, `Bob`'s signature could only be checked by himself using his own private keys ([NUT-00](00)). - -# The DLEQ proof - -The purpose of this DLEQ is to prove that the mint has used the same private key `a` for creating its public key `A` ([NUT-01](01)) and for signing the BlindedMessage `B'`. `Bob` returns the DLEQ proof additional to the blind signature `C'` for a mint or swap operation. - -The complete DLEQ proof reads - -``` -# DLEQ Proof - -(These steps occur when Bob returns C') - -Bob: -r = random nonce -R1 = r*G -R2 = r*B' -e = hash(R1,R2,A,C') -s = r + e*a -return e, s - -Alice: -R1 = s*G - e*A -R2 = s*B' - e*C' -e == hash(R1,R2,A,C') - -If true, a in A = a*G must be equal to a in C' = a*B' -``` - -### `hash(x: ) -> bytes` - -The hash(x) function generates a deterministic Sha256 hash for a given input list of `PublicKey`. The uncompressed hexadecimal representations of each `PublicKey` is concatenated before taking the Sha256 hash. - -```python -def hash_e(*publickeys: PublicKey) -> bytes: - e_ = "" - for p in publickeys: - _p = p.serialize(compressed=False).hex() - e_ += str(_p) - e = hashlib.sha256(e_.encode("utf-8")).digest() - return e - -``` - -### Mint to user: DLEQ in `BlindSignature` - -The mint produces these DLEQ proofs when returning `BlindSignature`'s in the responses for minting ([NUT-04][04]) and swapping ([NUT-03][03]) tokens. The `BlindSignature` object is extended in the following way to include the DLEQ proof: - -```json -{ - "id": , - "amount": , - "C_": , - "dleq": { <-- New: DLEQ proof - "e": , - "s": - } -} - -``` - -`e` and `s` are the DLEQ proof. - -### User to user: DLEQ in `Proof` - -In order for `Alice` to communicate the DLEQ to another user `Carol`, we extend the `Proof` (see [NUT-00](00)) object and include the DLEQ proof. As explained below, we also need to include the blinding factor `r` for the proof to be convincing to another user `Carol`. - -```json -{ - "id": , - "amount": , - "secret": , - "C": , - "dleq": { <-- New: DLEQ proof - "e": , - "s": , - "r": - } -} -``` - -`e` and `s` are the challenge and response of the DLEQ proof returned by `Bob`, `r` is the blinding factor of `Alice` that was used to generate the `Proof`. `Alice` serializes these proofs like any other in a token (see [NUT-00][00]) to send it to another user `Carol`. - -## Alice (minting user) verifies DLEQ proof - -When minting or swapping tokens, `Alice` receives DLEQ proofs in the `BlindSignature` response from the mint `Bob`. `Alice` checks the validity of the DLEQ proofs for each ecash token she receives via the equations: - -``` -R1 = s*G - e*A -R2 = s*B' - e*C' -e == hash(R1,R2,A,C') # must be True -``` - -Here, the variables are - -- `A` – the public key `Bob` used to sign this Proof -- `(e, s)` – the DLEQ proof returned by `Bob` -- `B'` – `Alice`'s `BlindedMessage` -- `C'` – `Bob`'s `BlindSignature` on `B'` - -In order to execute the proof, `Alice` needs `e, s` that are returned in the `BlindSignature` by `Bob`. `Alice` further needs `B'` (the `BlindedMessage` `Alice` created and `Bob` signed) and `C'` (the blind signature in the `BlindSignature` response) from `Bob`, and `A` (the public key of `Bob` with which he signed the BlindedMessage). All these values are available to `Alice` during or after calling the mint and swap operations. - -If a DLEQ proof is included in the mint's `BlindSignature` response, wallets **MUST** verify the DLEQ proof. - -## Carol (another user) verifies DLEQ proof - -`Carol` is a user that receives `Proofs` in a token from another user Alice. When `Alice` sends `Proofs` with DLEQ proofs to `Carol` or when `Alice` posts the `Proofs` publicly, `Carol` can validate the DLEQ proof herself and verify `Bob`'s signature without having to talk to `Bob`. `Alice` includes the following information in the `Proof` (see above): - -- `(x, C)` – the ecash `Proof` -- `(e, s)` – the DLEQ proof revealed by `Alice` -- `r` – `Alice`'s blinding factor - -Here, `x` is the Proof's secret, and `C` is the mint's signature on it. To execute the DLEQ proof like `Alice` did above, `Carol` needs `(B', C')` which she can compute herself using the blinding factor `r` that she receives from `Alice`. - -To verify the DLEQ proof of a received token, `Carol` needs to reconstruct `B'` and `C'` using the blinding factor `r` that `Alice` has included in the `Proof` she sent to `Carol`. Since `Carol` now has all the necessary information, she can execute the same equations to verify the DLEQ proof as `Alice` did: - -``` -Y = hash_to_curve(x) -C' = C + r*A -B' = Y + r*G - -R1 = ... (same as Alice) -``` - -If a DLEQ proof is included in a received token, wallets **MUST** verify the proof. - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/13.md b/docs/13.md deleted file mode 100644 index 0465544..0000000 --- a/docs/13.md +++ /dev/null @@ -1,115 +0,0 @@ -# NUT-13: Deterministic Secrets - -`optional` `depends on: NUT-09` - ---- - -In this document, we describe the process that allows wallets to recover their ecash balance with the help of the mint using a familiar 12 word seed phrase (mnemonic). This allows us to restore the wallet's previous state in case of a device loss or other loss of access to the wallet. The basic idea is that wallets that generate the ecash deterministically can regenerate the same tokens during a recovery process. For this, they ask the mint to reissue previously generated signatures using [NUT-09][09]. - -## Deterministic secret derivation - -An ecash token, or a `Proof`, consists of a `secret` generated by the wallet, and a signature `C` generated by the wallet and the mint in collaboration. Here, we describe how wallets can deterministically generate the `secrets` and blinding factors `r` necessary to generate the signatures `C`. - -The wallet generates a `private_key` derived from a 12-word [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) `mnemonic` seed phrase that the user stores in a secure place. The wallet uses the `private_key`, to derive deterministic values for the `secret` and the blinding factors `r` for every new ecash token that it generates. - -In order to do this, the wallet keeps track of a `counter_k` for each `keyset_k` it uses. The index `k` indicates that the wallet needs to keep track of a separate counter for each keyset `k` it uses. Typically, the wallet will need to keep track of multiple keysets for every mint it interacts with. `counter_k` is used to generate a [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) derivation path which can then be used to derive `secret` and `r`. - -The following BIP32 derivation path is used. The derivation path depends on the [keyset ID][02] of `keyset_k`, and the `counter_k` of that keyset. - -- Purpose' = `129372'` (UTF-8 for 🥜) -- Coin type' = Always `0'` -- Keyset id' = Keyset ID represented as an integer (`keyset_k_int`) -- Coin counter' = `counter'` (this value is incremented) -- `secret` or `r` = `0` or `1` - -`m / 129372' / 0' / keyset_k_int' / counter' / secret||r` - -This results in the following derivation paths: - -``` -secret_derivation_path = `m/129372'/0'/{keyset_k_int}'/{counter_k}'/0` -r_derivation_path = `m/129372'/0'/{keyset_id_k_int}'/{counter_k}'/1` -``` - -Here, `{keyset_k_int}` and `{counter_k}` are the only variables that can change. `keyset_id_k_int` is an integer representation (see below) of the keyset ID the token is generated with. This means that the derivation path is unique for each keyset. Note that the coin type is always `0'`, independent of the unit of the ecash. - -**Note:** For examples, see the [test vectors][tests]. - -#### Counter - -The wallet starts with `counter_k := 0` upon encountering a new keyset and increments it by `1` every time it has successfully minted new ecash with this keyset. The wallet stores the latest `counter_k` in its database for all keysets it uses. Note that we have a `counter` (and therefore a derivation path) for each keyset `k`. We omit the keyset index `k` in the following of this document. - -#### Keyset ID - -The integer representation `keyset_id_int` of a keyset is calculated from its [hexadecimal ID][02] which has a length of 8 bytes or 16 hex characters. First, we convert the hex string to a big-endian sequence of bytes. This value is then modulo reduced by `2^31 - 1` to arrive at an integer that is a unique identifier `keyset_id_int`. - -Example in Python: - -```python -keyset_id_int = int.from_bytes(bytes.fromhex(keyset_id_hex), "big") % (2**31 - 1) -``` - -Example in JavaScript: - -```javascript -keysetIdInt = BigInt(`0x${keysetIdHex}`) % BigInt(2 ** 31 - 1); -``` - -## Restore from seed phrase - -Using deterministic secret derivation, a user's wallet can regenerate the same `BlindedMessages` in case of loss of a previous wallet state. To also restore the corresponding `BlindSignatures` to fully recover the ecash, the wallet can either requests the mint to re-issue past `BlindSignatures` on the regenerated `BlindedMessages` (see [NUT-09][09]) or by downloading the entire database of the mint (TBD). - -The wallet takes the following steps during recovery: - -1. Generate `secret` and `r` from `counter` and `keyset` -2. Generate `BlindedMessage` from `secret` -3. Obtain `BlindSignature` for `secret` from the mint -4. Unblind `BlindSignature` to `C` using `r` -5. Restore `Proof = (secret, C)` -6. Check if `Proof` is already spent - -#### Generate `BlindedMessages` - -To generate the `BlindedMessages`, the wallet starts with a `counter := 0` and , for each increment of the `counter`, generates a `secret` using the BIP32 private key derived from `secret_derivation_path` and converts it to a hex string. - -```python -secret = bip32.get_privkey_from_path(secret_derivation_path).hex() -``` - -The wallet similarly generates a blinding factor `r` from the `r_derivation_path`: - -```python -r = self.bip32.get_privkey_from_path(r_derivation_path) -``` - -**Note:** For examples, see the [test vectors][tests]. - -Using the `secret` string and the private key `r`, the wallet generates a `BlindedMessage`. The wallet then increases the `counter` by `1` and repeats the same process for a given batch size. It is recommended to use a batch size of 100. - -The user's wallet can now request the corresponding `BlindSignatures` for theses `BlindedMessages` from the mint using the [NUT-09][09] restore endpoint or by downloading the entire mint's database. - -#### Generate `Proofs` - -Using the restored `BlindSignatures` and the `r` generated in the previous step, the wallet can [unblind][00] the signature to `C`. The triple `(secret, C, amount)` is a restored `Proof`. - -#### Check `Proofs` states - -If the wallet used the restore endpoint [NUT-09][09] for regenerating the `Proofs`, it additionally needs to check for the `Proofs` spent state using [NUT-07][07]. The wallet deletes all `Proofs` which are already spent and keeps the unspent ones in its database. - -### Restoring batches - -Generally, the user won't remember the last state of `counter` when starting the recovery process. Therefore, wallets need to know how far they need to increment the `counter` during the restore process to be confident to have reached the most recent state. - -In short, following approach is recommended: - -- Restore `Proofs` in batches of 100 and increment `counter` -- Repeat until three consecutive batches are returned empty -- Reset `counter` to the value at the last successful restore + 1 - -Wallets restore `Proofs` in batches of 100. The wallet starts with a `counter=0` and increments it for every `Proof` it generated during one batch. When the wallet begins restoring the first `Proofs`, it is likely that the first few batches will only contain spent `Proofs`. Eventually, the wallet will reach a `counter` that will result in unspent `Proofs` which it stores in its database. The wallet then continues to restore until _three successive batches are returned empty by the mint_. This is to be confident that the restore process did not miss any `Proofs` that might have been generated with larger gaps in the `counter` by the previous wallet that we are restoring. - -[00]: 00.md -[02]: 02.md -[07]: 07.md -[09]: 09.md -[tests]: tests/13-tests.md diff --git a/docs/14.md b/docs/14.md deleted file mode 100644 index f3a0791..0000000 --- a/docs/14.md +++ /dev/null @@ -1,71 +0,0 @@ -# NUT-14: Hashed Timelock Contracts (HTLCs) - -`optional` `depends on: NUT-10` - ---- - -This NUT describes the use of Hashed Timelock Contracts (HTLCs) which defines a spending condition based on [NUT-10][10]'s well-known `Secret` format. Using HTLCs, ecash tokens can be locked to the hash of a preimage or a timelock. This enables use cases such as atomic swaps of ecash between users, and atomic coupling of an ecash spending condition to a Lightning HTLC. - -`HTLC` spending conditions can be thought of as an extension of `P2PK` locks [NUT-11][11] but with a hash lock in `Secret.data` and a new `Proof.witness.preimage` witness in the locked inputs to be spent. The `preimage` that was used to spend a locked token can be retrieved using [NUT-07][07]. Caution: applications that rely on being able to retrieve the witness independent from the spender must check via the mint's [info][06] endpoint that NUT-07 is supported. - -Caution: If the mint does not support this type of spending condition, proofs may be treated as a regular anyone-can-spend tokens. Applications need to make sure to check whether the mint supports a specific kind of spending condition by checking the mint's [info][06] endpoint. - -## HTLC - -[NUT-10][10] Secret `kind: HTLC` - -If for a `Proof`, `Proof.secret` is a `Secret` of kind `HTLC`, the hash of the lock is in `Proof.secret.data`. The preimage for unlocking the HTLC is in the witness `Proof.witness.preimage`. All additional tags from `P2PK` locks are used here as well, allowing us to add a locktime, signature flag, and use multisig (see [NUT-11][11]). - -Here is a concrete example of a `Secret` of kind `HTLC`: - -```json -[ - "HTLC", - { - "nonce": "da62796403af76c80cd6ce9153ed3746", - "data": "023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54", - "tags": [ - [ - "pubkeys", - "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904" - ], - ["locktime", "1689418329"], - [ - "refund", - "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e" - ] - ] - } -] -``` - -A `Proof` with this `Secret` can be spent in two ways. To spend the hash lock, the witness in `Proof.witness` includes the preimage to `Secret.data` and a signature from the key in `Secret.tag.pubkeys`. Additionally, if the current system time is later than `Secret.tag.locktime`, the `Proof` can be spent if `Proof.witness` includes a signature from the key in `Secret.tags.refund`. - -The hash lock in `Secret.data` and the preimage in `Proof.witness.preimage` is treated as 32 byte data encoded as 64 character hex strings. - -See [NUT-11][11] for a description of the signature scheme, the additional use of signature flags, and how to require signature from multiple public keys (multisig). - -##### Witness format - -`HTLCWitness` is a serialized JSON string of the form - -```json -{ - "preimage": , - "signatures": ]> -} -``` - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/15.md b/docs/15.md deleted file mode 100644 index 8418312..0000000 --- a/docs/15.md +++ /dev/null @@ -1,80 +0,0 @@ -# NUT-15: Partial multi-path payments - -`optional` `depends on: NUT-05` - ---- - -In this document, we describe how wallets can instruct multiple mints to each pay a partial amount of a bolt11 Lightning invoice. The full payment is composed of partial payments (MPP) from multiple multi-path payments from different Lightning nodes. This way, wallets can pay a larger Lightning invoice combined from multiple smaller balances on different mints. Due to the atomic nature of MPP, either all payments will be successful or all of them will fail. - -The Lightning backend of the mint must support paying a partial amount of an Invoice and multi-path payments (MPP, see [BOLT 4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md)). For example, the mint's Lightning node must be able to pay 50 sats of a 100 sat bolt11 invoice. The receiving Lightning node must support receiving multi-path payments as well. - -## Multimint payment execution - -`Alice`'s wallet coordinates multiple MPPs on different mints that support the feature (see below for the indicated setting). For a given Lightning invoice of `amount_total`, `Alice` splits the total amount into several partial amounts `amount_total = amount_1 + amount_2 + ...` that each must be covered by her individual balances on the mints she wants to use for the payment. She constructs multiple `PostMeltQuoteBolt11Request`'s that each include the corresponding partial amount in the payment option (see below) that she sends to all mints she wants to use for the payment. The mints then respond with a normal `PostMeltQuoteBolt11Response` (see [NUT-05][05]). `Alice` proceeds to pay the melt requests at each mint simultaneously. When all mints have sent out the partial Lightning payment, she receives a successful response from all mints individually. - -## Melt quote - -To request a melt quote with a partial `amount`, the wallet of `Alice` makes a `POST /v1/melt/quote/bolt11` similar to [NUT-05][05]. - -```http -POST https://mint.host:3338/v1/melt/quote/bolt11 -``` - -The wallet `Alice` includes the following `PostMeltQuoteBolt11Request` data in its request which includes an additional (and optional) `options` object compared to the standard request in [NUT-05][05]: - -```json -{ - "request": , - "unit": , - "options": { - "mpp": { - "amount": - } - } -} -``` - -Here, `request` is the bolt11 Lightning invoice to be paid, `unit` is the unit the wallet would like to pay with, and `amount` is the amount for the requested payment. The wallet then pays the returned melt quote the same way as in [NUT-05][05]. - -## Settings - -The settings returned in the info endpoint ([NUT-06][06]) indicate that a mint supports this NUT. The mint MUST indicate each `method` and `unit` that supports `mpp`. It can indicate this in an array of objects for multiple `method` and `unit` pairs and the boolean flag `mpp` set to `true`. - -`MultipathPaymentSetting` is of the form: - -```json -{ - [ - { - "method": , - "unit": , - "mpp": - }, - ... - ] -} -``` - -Example `MultipathPaymentSetting`: - -```json -{ - "15": { - [ - { - "method": "bolt11", - "unit": "sat", - "mpp": true - }, - { - "method": "bolt11", - "unit": "usd", - "mpp": true - }, - ] - } -} -``` - -[05]: 05.md -[06]: 06.md diff --git a/docs/16.md b/docs/16.md deleted file mode 100644 index 366817c..0000000 --- a/docs/16.md +++ /dev/null @@ -1,26 +0,0 @@ -# NUT-16: Animated QR codes - -`optional` - ---- - -This document outlines how tokens should be displayed as QR codes for sending them between two wallets. - -## Introduction - -QR codes are a great way to send and receive Cashu tokens. Before a token can be shared as a QR code, it needs to be serialized (see [NUT-00][00]). - -### Static QR codes - -If the serialized token is not too large (i.e. includes less than or equal to 2 proofs) it can usually be shared as a static QR code. This might not be the case if the secret includes long scripts or the token has a long memo or mint URL. - -### Animated QR codes - -If a token is too large to be displayed as a single QR code, we use animated QR codes are based on the [UR](https://developer.blockchaincommons.com/ur/) protocol. The sender produces an animated QR code from a serialized Cashu token. The receiver scans the animated QR code until the UR decoder is able to decode the token. - -## Resources - -- Blockchain commons [UR](https://developer.blockchaincommons.com/ur/) -- Typescript library [bc-ur](https://github.com/gandlafbtc/bc-ur) - -[00]: 00.md diff --git a/docs/17.md b/docs/17.md deleted file mode 100644 index 37edaa3..0000000 --- a/docs/17.md +++ /dev/null @@ -1,282 +0,0 @@ -# NUT-17: WebSockets - -`optional`, `depends on: NUT-07` - ---- - -This NUT defines a websocket protocol that enables bidirectional communication between apps and mints using the JSON-RPC format. - -## Subscriptions - -The websocket enables real-time subscriptions that wallets can use to receive notifications for a state change of a `MintQuoteResponse` ([NUT-04][04]), `MeltQuoteResponse` ([NUT-05][05]), `CheckStateResponse` ([NUT-07][07]). - -A summary of the subscription flow is the following: - -1. A wallet connects to the websocket endpoint and sends a `WsRequest` with the `subscribe` command. -2. The mint responds with a `WsResponse` containing an ok or an error. -3. If the subscription was accepted, the mint sends a `WsNotification` of the current state of the subscribed objects and whenever there is an update for the wallet's subscriptions. -4. To close a subscription, the wallet sends `WsRequest` with the `unsubscribe` command. - -## Specifications - -The websocket is reachable via the mint's URL path `/v1/ws`: - -``` -https://mint.com/v1/ws -``` - -`NUT-17` uses the JSON-RPC format for all messages. There are three types of messages defined in this NUT. - -### Requests - -All requests from the wallet to the mint are of the form of a `WsRequest`: - -```json -{ - "jsonrpc": "2.0", - "method": , - "params": , - "id": -} -``` - -`WsRequestMethod` is a enum of strings with the supported commands `"subscribe"` and `"unsubscribe"`: - -```ts -enum WsRequestMethod { - sub = "subscribe", - unsub = "unsubscribe", -} -``` - -`WsRequestParams` is a serialized JSON with the parameters of the corresponding command. - -#### Command: Subscribe - -To subscribe to updates, the wallet sends a `"subscribe"` command with the following `params` parameters: - -```json -{ - "kind": , - "subId": , - "filters": -} -``` - -Here, `subId` is a unique uuid generated by the wallet and allows the client to map its requests to the mint's responses. - -`SubscriptionKind` is an enum with the following possible values: - -```ts -enum SubscriptionKind { - bolt11_melt_quote = "bolt11_melt_quote", - bolt11_mint_quote = "bolt11_mint_quote", - proof_state = "proof_state", -} -``` - -The `filters` are an array of mint quote IDs ([NUT-04][04]), or melt quote IDs ([NUT-05][05]), or `Y`'s ([NUT-07][07]) of the corresponding object to receive updates from. - -As an example, `filters` would be of the following form to subscribe for updates of three different mint quote IDs: - -```json -["20385fc7245...", "d06667cda9b...", "e14d8ca96f..."] -``` - -Note that `id` and `subId` are unrelated. The `subId` is the ID for each subscription, whereas `id` is part of the JSON-RPC spec and is an integer counter that must be incremented for every request sent over the websocket. - -**Important:** If the subscription is accepted by the mint, the mint MUST first respond with the _current_ state of the subscribed object and continue sending any further updates to it. - -For example, if the wallet subscribes to a `Proof.Y` of a `Proof` that has not been spent yet, the mint will first respond with a `ProofState` with `state == "UNSPENT"`. If the wallet then spends this `Proof`, the mint would send a `ProofState` with `state == "PENDING"` and then one with `state == "SPENT"`. In total, the mint would send three notifications to the wallet. - -#### Command: Unsubscribe - -The wallet should always unsubscribe any subscriptions that is isn't interested in anymore. The parameters for the `"unsubscribe"` command is only the subscription ID: - -```json -{ - "subId": -} -``` - -### Responses - -A `WsResponse` is returned by the mint to both the `"subscribe"` and `"unsubscribe"` commands and indicates whether the request was successful: - -```json -{ - "jsonrpc": "2.0", - "result": { - "status": "OK", - "subId": - }, - "id": -} -``` - -Here, the `id` corresponds to the `id` in the request (as part of the JSON-RPC spec) and `subId` corresponds to the subscription ID. - -### Notifications - -`WsNotification`'s are sent from the mint to the wallet and contain subscription data in the following format - -```ts -{ - "jsonrpc": "2.0", - "method": "subscribe", - "params": { - "subId": , - "payload": NotificationPayload - } -} -``` - -`subId` is the subscription ID (previously generated by the wallet) this notification corresponds to. `NotificationPayload` carries the subscription data which is a `MintQuoteResponse` ([NUT-04][04]), a `MeltQuoteResponse` ([NUT-05][05]), or a `CheckStateResponse` ([NUT-07][07]), depending on what the corresponding `SubscriptionKind` was. - -### Errors - -`WsErrors` for a given `WsRequest` are returned in the following format - -```json -{ - "jsonrpc": "2.0", - "error": { - "code": -32601, - "message": "Human readable error message" - }, - "id": "1" -} -``` - -### Example: `ProofState` subscription - -To subscribe to the `ProofState` of a `Proof`, the wallet establishes a websocket connection to `https://mint.com/v1/ws` and sends a `WsRequest` with a `filters` chosen to be the a `Proof.Y` value of the `Proof` (see [NUT-00][00]). Note that `filters` is an array meaning multiple subscriptions of the same `kind` can be made in the same request. - -Wallet: - -```json -{ - "jsonrpc": "2.0", - "id": 0, - "method": "subscribe", - "params": { - "kind": "proof_state", - "filters": [ - "02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d" - ], - "subId": "Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T" - } -} -``` - -The mint first responds with a `WsResponse` confirming that the subscription has been added. - -Mint: - -```json -{ - "jsonrpc": "2.0", - "result": { - "status": "OK", - "subId": "Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T" - }, - "id": 0 -} -``` - -The mint immediately sends the current `ProofState` of the subscription as a `WsNotification`. - -Mint: - -```json -{ - "jsonrpc": "2.0", - "method": "subscribe", - "params": { - "subId": "Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T", - "payload": { - "Y": "02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d", - "state": "UNSPENT", - "witness": null - } - } -} -``` - -While leaving the websocket connection open, the wallet then spends the ecash. The mint sends `WsNotification` updating the wallet about state changes of the `ProofState` accordingly: - -Mint: - -```json -{"jsonrpc": "2.0", "method": "subscribe", "params": {"subId": "Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T", "payload": {"Y": "02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d", "state": "PENDING"}}} - -{"jsonrpc": "2.0", "method": "subscribe", "params": {"subId": "Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T", "payload": {"Y": "02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d", "state": "SPENT"}}} -``` - -The wallet then unsubscribes. - -Wallet: - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "unsubscribe", - "params": { "subId": "Ua_IYvRHoCoF_wsZFlJ1m4gBDB--O0_6_n0zHg2T" } -} -``` - -## Signaling Support via NUT-06 - -Mints signal websocket support via [NUT-06][06] using the following setting: - -```json -"nuts": { - "17": { - "supported": [ - { - "method": , - "unit": , - "commands": - }, - ... - ] - } -} -``` - -Here, `commands` is an array of the commands that the mint supports. A mint that supports all commands would return `["bolt11_mint_quote", "bolt11_melt_quote", "proof_state"]`. Supported commands are given for each method-unit pair. - -Example: - -```json -"nuts": { - "17": { - "supported": [ - { - "method": "bolt11", - "unit": "sat", - "commands": [ - "bolt11_mint_quote", - "bolt11_melt_quote", - "proof_state" - ] - }, - ] - } -} -``` - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index dcf9898..0000000 --- a/docs/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# Cashu NUTs (Notation, Usage, and Terminology) - -These documents each specify parts of the Cashu protocol. Read the specifications for the legacy API [here](https://github.com/cashubtc/nuts/tree/74f26b81b6617db710fa1081eebc0c7203711213). - -## Specifications - -Wallets and mints `MUST` implement all mandatory specs and `CAN` implement optional specs. - -### Mandatory - -| NUT # | Description | -| -------- | ----------------------- | -| [00][00] | Cryptography and Models | -| [01][01] | Mint public keys | -| [02][02] | Keysets and fees | -| [03][03] | Swapping tokens | -| [04][04] | Minting tokens | -| [05][05] | Melting tokens | -| [06][06] | Mint info | - -### Optional - -| # | Description | Wallets | Mints | -| -------- | --------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------- | -| [07][07] | Token state check | [Nutshell][py], [Moksha][moksha], [Nutstash][ns], [cashu-ts][ts], [cdk-cli] | [Nutshell][py], [Moksha][moksha], [cdk-mintd] | -| [08][08] | Overpaid Lightning fees | [Nutshell][py], [Moksha][moksha], [Nutstash][ns], [cashu-ts][ts], [cdk-cli] | [Nutshell][py], [Moksha][moksha], [cdk-mintd] | -| [09][09] | Signature restore | [Nutshell][py], [cdk-cli], [cashu-ts][ts], [gonuts] | [Nutshell][py], [cdk-mintd] | -| [10][10] | Spending conditions | [Nutshell][py], [cdk-cli], [cashu-ts][ts] | [Nutshell][py], [cdk-mintd], [nutmix] | -| [11][11] | Pay-To-Pubkey (P2PK) | [Nutshell][py], [cdk-cli], [cashu-ts][ts] | [Nutshell][py], [cdk-mintd], [nutmix] | -| [12][12] | DLEQ proofs | [Nutshell][py], [cdk-cli] | [Nutshell][py], [cdk-mintd] | -| [13][13] | Deterministic secrets | [Nutshell][py], [Moksha][moksha], [cashu-ts][ts], [cdk-cli], [gonuts] | - | -| [14][14] | Hashed Timelock Contracts (HTLCs) | [Nutshell][py], [cdk-cli] | [Nutshell][py], [cdk-mintd] | -| [15][15] | Partial multi-path payments (MPP) | [Nutshell][py] | [Nutshell][py] | -| [16][16] | Animated QR codes | [Cashu.me][cashume] | - | -| [17][17] | WebSocket subscriptions | [Nutshell][py] | [Nutshell][py] | - -#### Wallets: - -- [Nutshell][py] -- [cdk-cli][cdk-cli] -- [cashu-ts][ts] -- [eNuts][enuts] -- [Minibits][minibits] -- [Moksha][moksha] -- [Nutstash][ns] -- [Cashu.me][cashume] -- [Gonuts][gonuts] -- [Boardwalk Cash][bwc] - -#### Mints: - -- [Nutshell][py] -- [Gonuts][gonuts] -- [Moksha][moksha] -- [cdk-mintd][cdk-mintd] -- [Nutmix][nutmix] - -[py]: https://github.com/cashubtc/nutshell -[lnbits]: https://github.com/lnbits/cashu -[cashume]: https://cashu.me -[ns]: https://nutstash.app/ -[ts]: https://github.com/cashubtc/cashu-ts -[enuts]: https://github.com/cashubtc/eNuts -[minibits]: https://github.com/minibits-cash/minibits_wallet -[moksha]: https://github.com/ngutech21/moksha -[cdk]: https://github.com/cashubtc/cdk -[cdk-cli]: https://github.com/cashubtc/cdk/tree/main/crates/cdk-cli -[cdk-mintd]: https://github.com/cashubtc/cdk/tree/main/crates/cdk-mintd -[gonuts]: https://github.com/elnosh/gonuts -[nutmix]: https://github.com/lescuer97/nutmix -[bwc]: https://github.com/MakePrisms/boardwalkcash -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md -[13]: 13.md -[14]: 14.md -[15]: 15.md -[16]: 16.md -[17]: 17.md diff --git a/docs/error_codes.md b/docs/error_codes.md deleted file mode 100644 index f340330..0000000 --- a/docs/error_codes.md +++ /dev/null @@ -1,32 +0,0 @@ -# NUT Errors - -| Code | Description | Relevant nuts | -| ----- | ----------------------------------------------- | ---------------------------------------- | -| 10002 | Blinded message of output already signed | [NUT-03][03], [NUT-04][04], [NUT-05][05] | -| 10003 | Token could not be verified | [NUT-03][03], [NUT-05][05] | -| 11001 | Token is already spent | [NUT-03][03], [NUT-05][05] | -| 11002 | Transaction is not balanced (inputs != outputs) | [NUT-02][02], [NUT-03][03], [NUT-05][05] | -| 11005 | Unit in request is not supported | [NUT-04][04], [NUT-05][05] | -| 11006 | Amount outside of limit range | [NUT-04][04], [NUT-05][05] | -| 12001 | Keyset is not known | [NUT-02][02], [NUT-04][04] | -| 12002 | Keyset is inactive, cannot sign messages | [NUT-02][02], [NUT-03][03], [NUT-04][04] | -| 20001 | Quote request is not paid | [NUT-04][04] | -| 20002 | Tokens have already been issued for quote | [NUT-04][04] | -| 20003 | Minting is disabled | [NUT-04][04] | -| 20005 | Quote is pending | [NUT-04][04], [NUT-05][05] | -| 20006 | Invoice already paid | [NUT-05][05] | -| 20007 | Quote is expired | [NUT-04][04], [NUT-05][05] | - -[00]: 00.md -[01]: 01.md -[02]: 02.md -[03]: 03.md -[04]: 04.md -[05]: 05.md -[06]: 06.md -[07]: 07.md -[08]: 08.md -[09]: 09.md -[10]: 10.md -[11]: 11.md -[12]: 12.md diff --git a/docs/tests/00-tests.md b/docs/tests/00-tests.md deleted file mode 100644 index d383773..0000000 --- a/docs/tests/00-tests.md +++ /dev/null @@ -1,194 +0,0 @@ -# NUT-00 Test Vectors - -### Hash-to-curve function - -The hash to curve function takes a message of any length and outputs a valid point on the secp256k1 curve. Note that unless you are using complex spend conditions ([NUT-10]), standardized secrets (random 32-bytes-long byte arrays) should be used in order to prevent wallet fingerprinting. - -```shell -# Test 1 (hex encoded) -Message: 0000000000000000000000000000000000000000000000000000000000000000 -Point: 024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725 - -# Test 2 (hex encoded) -Message: 0000000000000000000000000000000000000000000000000000000000000001 -Point: 022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf - -# Test 3 (hex encoded) -# Note that this message will take a few iterations of the loop before finding a valid point -Message: 0000000000000000000000000000000000000000000000000000000000000002 -Point: 026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f -``` - -### Blinded messages - -These are test vectors for the the blinded secret (public key) `B_` Alice sends to the mint Bob given a secret `x` and a random blinding factor `r`. - -```shell -# Test 1 -x: d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6 # hex encoded byte array -r: 99fce58439fc37412ab3468b73db0569322588f62fb3a49182d67e23d877824a # hex encoded private key -B_: 033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d # hex encoded public key - -# Test 2 -x: f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60 # hex encoded byte array -r: f78476ea7cc9ade20f9e05e58a804cf19533f03ea805ece5fee88c8e2874ba50 # hex encoded private key -B_: 029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763 # hex encoded public key -``` - -### Blinded signatures - -These are test vectors for the blinded key `C_` given the mint's private key `k` and Alice's blinded message containing `B_`. - -```shell -# Test 1 -mint private key: 0000000000000000000000000000000000000000000000000000000000000001 -B_: 02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2 -C_: 02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2 - -# Test 2 -mint private key: 7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f -B_: 02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2 -C_: 0398bc70ce8184d27ba89834d19f5199c84443c31131e48d3c1214db24247d005d -``` - -## Serialization of TokenV3 - -The following are JSON-formatted v3 tokens and their serialized counterparts. - -```json -{ - "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." -} -``` - -Serialized: - -``` -cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9 -``` - -## Deserialization of TokenV3 - -The following are incorrectly formatted serialized v3 tokens. - -```shell -# Incorrect prefix (casshuA) -casshuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9 - -# No prefix -eyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9 -``` - -The following is a correctly serialized v3 token. - -```shell -cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9 -``` - -Both of the following v3 tokens are valid, one includes padding characters at the end and the other does not. - -```shell -# Clients should be able to deserialize both -cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91IHZlcnkgbXVjaC4ifQ== -cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91IHZlcnkgbXVjaC4ifQ -``` - -## Serialization of TokenV4 - -The following are JSON-formatted v4 tokens and their serialized counterparts. The `h''` values are `bytes` but displayed as hex strings here. - -### Single keyset - -Token from a single keyset and including a memo. - -```json -{ - "t": [ - { - "i": h'00ad268c4d1f5826', - "p": [ - { - "a": 1, - "s": "9a6dbb847bd232ba76db0df197216b29d3b8cc14553cd27827fc1cc942fedb4e", - "c": h'038618543ffb6b8695df4ad4babcde92a34a96bdcd97dcee0d7ccf98d472126792', - }, - ], - }, - ], - "d": "Thank you", - "m": "http://localhost:3338", - "u": "sat", -} -``` - -Encoded: - -``` -cashuBpGF0gaJhaUgArSaMTR9YJmFwgaNhYQFhc3hAOWE2ZGJiODQ3YmQyMzJiYTc2ZGIwZGYxOTcyMTZiMjlkM2I4Y2MxNDU1M2NkMjc4MjdmYzFjYzk0MmZlZGI0ZWFjWCEDhhhUP_trhpXfStS6vN6So0qWvc2X3O4NfM-Y1HISZ5JhZGlUaGFuayB5b3VhbXVodHRwOi8vbG9jYWxob3N0OjMzMzhhdWNzYXQ= -``` - -### Multiple keysets - -The token below includes proofs from two different keysets. - -```json -{ - "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", -} -``` - -Serialized: - -``` -cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA -``` - -[NUT-10]: ../10.md diff --git a/docs/tests/01-tests.md b/docs/tests/01-tests.md deleted file mode 100644 index a46927d..0000000 --- a/docs/tests/01-tests.md +++ /dev/null @@ -1,105 +0,0 @@ -# NUT-01 Test Vectors - -The following are incorrect keysets that should be rejected by wallets implementing the NUT-01 specification. - -Key 1 is missing a byte - -```json -{ - "1": "03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38", - "2": "03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de", - "4": "02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303", - "8": "02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528" -} -``` - -Key 2 is a valid key but is not in the compressed format. - -```json -{ - "1": "03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc", - "2": "04fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de3625246cb2c27dac965cb7200a5986467eee92eb7d496bbf1453b074e223e481", - "4": "02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303", - "8": "02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528" -} -``` - -The following are correct keysets that should be accepted by wallets implementing the NUT-01 specification. Note that the second (bigger) keyset's biggest amount is `9223372036854775808`, one unit over what can fit into a 64-bit signed integer (often known as a `Long`). To handle this you'll need to use either an unsigned 64-bit integer or a 128-bit signed integer (sometimes known as a `BigInteger`). - -```json -{ - "1": "03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc", - "2": "03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de", - "4": "02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303", - "8": "02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528" -} -``` - -```json -{ - "1": "03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566", - "2": "03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5", - "4": "036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7", - "8": "03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0", - "16": "028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d", - "32": "03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612", - "64": "03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664", - "128": "0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9", - "256": "03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459", - "512": "03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb", - "1024": "031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc", - "2048": "0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b", - "4096": "02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2", - "8192": "0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21", - "16384": "036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50", - "32768": "0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04", - "65536": "02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d", - "131072": "031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41", - "262144": "02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328", - "524288": "038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86", - "1048576": "0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788", - "2097152": "033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c", - "4194304": "02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512", - "8388608": "025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0", - "16777216": "037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21", - "33554432": "03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262", - "67108864": "03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3", - "134217728": "02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020", - "268435456": "0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276", - "536870912": "03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9", - "1073741824": "02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee", - "2147483648": "034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a", - "4294967296": "037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5", - "8589934592": "02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3", - "17179869184": "026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9", - "34359738368": "02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75", - "68719476736": "037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754", - "137438953472": "021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6", - "274877906944": "02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a", - "549755813888": "029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785", - "1099511627776": "03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a", - "2199023255552": "02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258", - "4398046511104": "03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a", - "8796093022208": "02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e", - "17592186044416": "030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310", - "35184372088832": "03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06", - "70368744177664": "03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1", - "140737488355328": "0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed", - "281474976710656": "02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d", - "562949953421312": "02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a", - "1125899906842624": "038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9", - "2251799813685248": "02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f", - "4503599627370496": "02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73", - "9007199254740992": "0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49", - "18014398509481984": "02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6", - "36028797018963968": "0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0", - "72057594037927936": "02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd", - "144115188075855872": "02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a", - "288230376151711744": "03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc", - "576460752303423488": "02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a", - "1152921504606846976": "035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06", - "2305843009213693952": "033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099", - "4611686018427387904": "024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f", - "9223372036854775808": "0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad" -} -``` diff --git a/docs/tests/02-tests.md b/docs/tests/02-tests.md deleted file mode 100644 index c5a8080..0000000 --- a/docs/tests/02-tests.md +++ /dev/null @@ -1,84 +0,0 @@ -# NUT-02 Test Vectors - -The following keysets and corresponding keyset IDs are correct: -Keyset id: `00456a94ab4e1c46` - -```json -{ - "1": "03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc", - "2": "03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de", - "4": "02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303", - "8": "02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528" -} -``` - -Keyset id: `000f01df73ea149a` - -```json -{ - "1": "03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566", - "2": "03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5", - "4": "036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7", - "8": "03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0", - "16": "028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d", - "32": "03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612", - "64": "03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664", - "128": "0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9", - "256": "03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459", - "512": "03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb", - "1024": "031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc", - "2048": "0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b", - "4096": "02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2", - "8192": "0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21", - "16384": "036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50", - "32768": "0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04", - "65536": "02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d", - "131072": "031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41", - "262144": "02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328", - "524288": "038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86", - "1048576": "0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788", - "2097152": "033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c", - "4194304": "02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512", - "8388608": "025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0", - "16777216": "037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21", - "33554432": "03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262", - "67108864": "03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3", - "134217728": "02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020", - "268435456": "0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276", - "536870912": "03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9", - "1073741824": "02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee", - "2147483648": "034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a", - "4294967296": "037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5", - "8589934592": "02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3", - "17179869184": "026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9", - "34359738368": "02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75", - "68719476736": "037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754", - "137438953472": "021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6", - "274877906944": "02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a", - "549755813888": "029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785", - "1099511627776": "03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a", - "2199023255552": "02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258", - "4398046511104": "03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a", - "8796093022208": "02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e", - "17592186044416": "030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310", - "35184372088832": "03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06", - "70368744177664": "03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1", - "140737488355328": "0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed", - "281474976710656": "02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d", - "562949953421312": "02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a", - "1125899906842624": "038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9", - "2251799813685248": "02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f", - "4503599627370496": "02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73", - "9007199254740992": "0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49", - "18014398509481984": "02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6", - "36028797018963968": "0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0", - "72057594037927936": "02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd", - "144115188075855872": "02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a", - "288230376151711744": "03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc", - "576460752303423488": "02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a", - "1152921504606846976": "035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06", - "2305843009213693952": "033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099", - "4611686018427387904": "024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f", - "9223372036854775808": "0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad" -} -``` diff --git a/docs/tests/11-test.md b/docs/tests/11-test.md deleted file mode 100644 index af2922d..0000000 --- a/docs/tests/11-test.md +++ /dev/null @@ -1,73 +0,0 @@ -# NUT-11 Test Vectors - -The following is a `Proof` with a valid signature. - -```json -{ - "amount": 1, - "secret": "[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]", - "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", - "id": "009a1f293253e41e", - "witness": "{\"signatures\":[\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\"]}" -} -``` - -The following is a `Proof` with an invalid signature as it is on a different secret. - -```json -{ - "amount": 1, - "secret": "[\"P2PK\",{\"nonce\":\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_INPUTS\"]]}]", - "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", - "id": "009a1f293253e41e", - "witness": "{\"signatures\":[\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\"]}" -} -``` - -The following is a `Proof` with 2 signatures required to meet the multi-signature spend condition. - -```json -{ - "amount": 1, - "secret": "[\"P2PK\",{\"nonce\":\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_INPUTS\"]]}]", - "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", - "id": "009a1f293253e41e", - "witness": "{\"signatures\":[\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\",\"9a72ca2d4d5075be5b511ee48dbc5e45f259bcf4a4e8bf18587f433098a9cd61ff9737dc6e8022de57c76560214c4568377792d4c2c6432886cc7050487a1f22\"]}" -} -``` - -The following is a `Proof` with one one signature failing the multi-signature spend condition. - -```json -{ - "amount": 1, - "secret": "[\"P2PK\",{\"nonce\":\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_INPUTS\"]]}]", - "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", - "id": "009a1f293253e41e", - "witness": "{\"signatures\":[\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\"]}" -} -``` - -The following is a `Proof` with a signature from the refund key that is spendable because the locktime is in the past. - -```json -{ - "amount": 1, - "id": "009a1f293253e41e", - "secret": "[\"P2PK\",{\"nonce\":\"902685f492ef3bb2ca35a47ddbba484a3365d143b9776d453947dcbf1ddf9689\",\"data\":\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"03142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\"],[\"locktime\",\"21\"],[\"n_sigs\",\"2\"],[\"refund\",\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\"],[\"sigflag\",\"SIG_INPUTS\"]]}]", - "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", - "witness": "{\"signatures\":[\"710507b4bc202355c91ea3c147c0d0189c75e179d995e566336afd759cb342bcad9a593345f559d9b9e108ac2c9b5bd9f0b4b6a295028a98606a0a2e95eb54f7\"]}" -} -``` - -The following is a `Proof` with a signature from the refund key that is **not** spendable because the locktime is in the future. - -```json -{ - "amount": 1, - "id": "009a1f293253e41e", - "secret": "[\"P2PK\",{\"nonce\":\"64c46e5d30df27286166814b71b5d69801704f23a7ad626b05688fbdb48dcc98\",\"data\":\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"03142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\"],[\"locktime\",\"21\"],[\"n_sigs\",\"2\"],[\"refund\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"],[\"sigflag\",\"SIG_INPUTS\"]]}]", - "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", - "witness": "{\"signatures\":[\"f661d3dc046d636d47cb3d06586da42c498f0300373d1c2a4f417a44252cdf3809bce207c8888f934dba0d2b1671f1b8622d526840f2d5883e571b462630c1ff\"]}" -} -``` diff --git a/docs/tests/12-tests.md b/docs/tests/12-tests.md deleted file mode 100644 index a00518d..0000000 --- a/docs/tests/12-tests.md +++ /dev/null @@ -1,57 +0,0 @@ -# NUT-12 Test vectors - -## `hash_e` function - -```shell -R1: "020000000000000000000000000000000000000000000000000000000000000001" -R2: "020000000000000000000000000000000000000000000000000000000000000001" -K: "020000000000000000000000000000000000000000000000000000000000000001" -C_: "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" -``` - -```shell -hash(R1, R2, K, C_): "a4dc034b74338c28c6bc3ea49731f2a24440fc7c4affc08b31a93fc9fbe6401e" -``` - -## DLEQ verification on `BlindSignature` - -The following is a `BlindSignature` with a **valid** DLEQ proof. - -```shell -A: "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" -B_: "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" -``` - -```json -{ - "amount": 8, - "id": "00882760bfa2eb41", - "C_": "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2", - "dleq": { - "e": "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9", - "s": "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" - } -} -``` - -## DLEQ verification on `Proof` - -The following is a `Prood` with a **valid** DLEQ proof. - -```shell -A: "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" -``` - -```json -{ - "amount": 1, - "id": "00882760bfa2eb41", - "secret": "daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9", - "C": "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc", - "dleq": { - "e": "b31e58ac6527f34975ffab13e70a48b6d2b0d35abc4b03f0151f09ee1a9763d4", - "s": "8fbae004c59e754d71df67e392b6ae4e29293113ddc2ec86592a0431d16306d8", - "r": "a6d13fcd7a18442e6076f5e1e7c887ad5de40a019824bdfa9fe740d302e8d861" - } -} -``` diff --git a/docs/tests/13-tests.md b/docs/tests/13-tests.md deleted file mode 100644 index 3408688..0000000 --- a/docs/tests/13-tests.md +++ /dev/null @@ -1,59 +0,0 @@ -# NUT-13 Test vectors - -## Keyset ID integer representation - -The integer representation of a keyset with an ID `009a1f293253e41e` and its corresponding derivation path for a counter of value `{counter}` are - -```json -{ - "keyset_id": "009a1f293253e41e", - "keyest_id_int": 864559728, - "derivation_path": "m/129372'/0'/864559728'/{counter}'" -} -``` - -## Secret derivatoin - -We derive values starting from the following BIP39 mnemonic. - -```json -{ - "mnemonic": "half depart obvious quality work element tank gorilla view sugar picture humble" -} -``` - -The secrets derived for the first five counters from `counter=0` to `counter=4` are - -```json -{ - "secret_0": "485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae", - "secret_1": "8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270", - "secret_2": "bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8", - "secret_3": "59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf", - "secret_4": "576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0" -} -``` - -The corresponding blinding factors `r` are - -```json -{ - "r_0": "ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679", - "r_1": "967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248", - "r_2": "b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899", - "r_3": "fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29", - "r_4": "5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9" -} -``` - -The corresponding derivation paths are - -```json -{ - "derivation_path_0": "m/129372'/0'/864559728'/0'", - "derivation_path_1": "m/129372'/0'/864559728'/1'", - "derivation_path_2": "m/129372'/0'/864559728'/2'", - "derivation_path_3": "m/129372'/0'/864559728'/3'", - "derivation_path_4": "m/129372'/0'/864559728'/4'" -} -``` diff --git a/docs/tests/README.md b/docs/tests/README.md deleted file mode 100644 index 557ec86..0000000 --- a/docs/tests/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Test Vectors - -The files in this directory contain test vectors for NUTs that warrant them.