{"id":"acd2ed874770b5cdc45b7fcd0084c447bf2c8fe676e4881ec5570cf60e875c60","pubkey":"d1678e7ef965374bbea308a1215609a78376dc158277a7d657680f9d5efd5c38","created_at":1781802270,"kind":30817,"tags":[["d","nut-12"],["title","NUT-12: Offline ecash signature validation"],["summary","DLEQ proofs, which let anyone verify a mint's signature offline with nothing but its public keys."],["s","optional"],["t","cashu"],["t","ecash"],["t","nut"],["alt","A specification: NUT-12: Offline ecash signature validation"],["client","openspecs-import"],["published_at","1699230502"],["proxy","https://github.com/cashubtc/nuts/blob/a845dfc998abae501fc3419592d53dc995d34b12/12.md","web"],["x","99925214be110cb7299739754cf6373652dc1eed7d27c316944c2023a96f2efd"]],"content":"# NUT-12: Offline ecash signature validation\n\n`optional`\n\n---\n\nIn 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]).\n\n# The DLEQ proof\n\nThe 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.\n\nThe complete DLEQ proof reads\n\n```\n# DLEQ Proof\n\n(These steps occur when Bob returns C')\n\nBob:\nr = HMAC-SHA256(key=a, data=\"Cashu_DLEQ_R_v1\" || A || B' || C' || ctr) # Deterministic nonce\nR1 = r*G\nR2 = r*B'\ne = hash(R1,R2,A,C')\ns = (r + e*a) mod n\nreturn e, s\n\nAlice:\nR1 = s*G - e*A\nR2 = s*B' - e*C'\ne == hash(R1,R2,A,C')\n\nIf true, a in A = a*G must be equal to a in C' = a*B'\n\nwhere:\n  G, n = secp256k1 generator point, and curve order\n  a = mint's amount-i private key, as 32-byte big-endian (HMAC key)\n  A, B', C' = uncompressed (65 byte) SEC1 public points as raw bytes\n  ctr = nonce counter byte; set to 0x00 initially, increment and retry if r == 0 or r >= n (max 256 tries)\n```\n\n> [!CAUTION]\n>\n> Mints **SHOULD** use a rejection-sampled deterministic nonce (`r`) to avoid RNG failures. See the [test vectors][tests].\n>\n> Reusing a nonce across different challenges leaks the private key immediately (`a = (s₁ - s₂) · (e₁ - e₂)⁻¹ mod n`). Mints that use random nonces **MUST** source them from a cryptographically secure RNG.\n\n### Hash function\n\nThe hash function `hash(x: <Array<[PublicKey]>) -> bytes` generates a deterministic SHA256 hash for a given input list of `PublicKey`. The uncompressed (32+32+1)-byte hexadecimal representations (130 characters) of each `PublicKey` is concatenated before taking the SHA256 hash.\n\n```python\ndef hash_e(*publickeys: PublicKey) -> bytes:\n    e_ = \"\"\n    for p in publickeys:\n        _p = p.serialize(compressed=False).hex()\n        e_ += str(_p)\n    e = hashlib.sha256(e_.encode(\"utf-8\")).digest()\n    return e\n\n```\n\n> [!NOTE]\n> For examples of valid DLEQ proofs, see the [test vectors][tests].\n\n### Mint to user: DLEQ in `BlindSignature`\n\nThe 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:\n\n```json\n{\n  \"id\": <str>,\n  \"amount\": <int>,\n  \"C_\": <str>,\n  \"dleq\": { <-- New: DLEQ proof\n    \"e\": <str>,\n    \"s\": <str>\n  }\n}\n\n```\n\n`e` and `s` are the DLEQ proof.\n\n### User to user: DLEQ in `Proof`\n\nIn 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`.\n\n```json\n{\n  \"id\": <str>,\n  \"amount\": <int>,\n  \"secret\": <str>,\n  \"C\": <str>,\n  \"dleq\": { <-- New: DLEQ proof\n    \"e\": <str>,\n    \"s\": <str>,\n    \"r\": <str>\n  }\n}\n```\n\n`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`.\n\n> [!IMPORTANT]\n>\n> **Privacy:** The blinding factor `r` should not be shared with the mint or otherwise, the mint will be able to associate the `BlindSignature` with the `Proof`.\n\n## Alice (minting user) verifies DLEQ proof\n\nWhen 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:\n\n```\nR1 = s*G - e*A\nR2 = s*B' - e*C'\ne == hash(R1,R2,A,C') # must be True\n```\n\nHere, the variables are\n\n- `A` – the public key `Bob` used to sign this Proof\n- `(e, s)` – the DLEQ proof returned by `Bob`\n- `B'` – `Alice`'s `BlindedMessage`\n- `C'` – `Bob`'s `BlindSignature` on `B'`\n\nIn 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.\n\nIf a DLEQ proof is included in the mint's `BlindSignature` response, wallets **MUST** verify the DLEQ proof.\n\n## Carol (another user) verifies DLEQ proof\n\n`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):\n\n- `(x, C)` – the ecash `Proof`\n- `(e, s)` – the DLEQ proof revealed by `Alice`\n- `r` – `Alice`'s blinding factor\n\nHere, `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`.\n\nTo 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:\n\n```\nY = hash_to_curve(x)\nC' = C + r*A\nB' = Y + r*G\n\nR1 = ... (same as Alice)\n```\n\nIf a DLEQ proof is included in a received token, wallets **MUST** verify the proof.\n\n## Mint info setting\n\nThe [NUT-06][06] `MintMethodSetting` indicates support for this feature:\n\n```json\n{\n  \"12\": {\n    \"supported\": true\n  }\n}\n```\n\n[00]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crqqpd6d9\n[01]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crznyve7g\n[02]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cry0t0uzl\n[03]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crxuwwl3j\n[04]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crg74fknc\n[05]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cr2dsg4q4\n[06]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crv3ltsuz\n[07]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crwz62n00\n[08]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crs4q9zck\n[09]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crjx9yptm\n[10]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnqx5tye7\n[11]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnz43282n\n[12]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnyf7fzky\n[tests]: https://github.com/cashubtc/nuts/blob/a845dfc998abae501fc3419592d53dc995d34b12/tests/12-tests.md\n","sig":"302aaed4c3576c927c55b43c577523d181b7753ac4ef250cd226ec0e51831edd5e47faddba6c3bd1c7ad41ad487ecabab28ca7c19d95408b15edc0d6801736aa"}