# NUT-11: Pay to Public Key (P2PK) `optional` `depends on: NUT-10, NUT-08` `extended by: NUT-28` --- 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 Proofs (see [NUT-00][00]) 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 regular anyone-can-spend Proofs. Applications need to make sure to check whether the mint supports a specific kind of spending condition by checking the mint's [NUT-06][06] info endpoint. ## Basic Case [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 Proof, 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 Proof 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 Proofs locked with `P2PK`, the spender needs to include signatures in the Proofs used as "inputs" for the spending operation. 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, unless otherwise indicated by the `Secret.tags.sigflag` in the inputs, as detailed below. An ecash spending operation like [swap][03] and [melt][05] can have multiple inputs and outputs. If we have more than one locked input, we either provide signatures in each input individually (for `SIG_INPUTS`) or only in the first input for the entire transaction (for `SIG_ALL`). 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]). > [!NOTE] > > The field `Proof.secret` contains escaped JSON for transport. The message to sign MUST be constructed using the **unescaped** secret string, eg: ["P2PK",{"nonce":"c7f280eb55c1e856... ### Witness format Signatures are stored in `P2PKWitness` objects and are provided in either each `Proof.witness` of all inputs separately (for `SIG_INPUTS`) or only in the first input of the transaction (for `SIG_ALL`). `P2PKWitness` is a serialized JSON string of the form ```json { "signatures": ]> } ``` The `signatures` are an array of signatures in hex and correspond to the signatures by one or more signing public keys. ## Tags More complex spending conditions can be defined in the tags in `Secret.tags`. All tags are optional. Tags are arrays with two or more strings being `["key", "value1", "value2", ...]`. We denote a specific tag in a proof by its `key`. Supported tags are: - `sigflag: ` sets the [signature flag](#signature-flag) - `pubkeys: ` are additional public keys (together with the one in the `data` field of the secret) that can provide signatures (_allows multiple entries_) - `n_sigs: ` specifies the minimum number of [Locktime Multisig](#locktime-multisig) public keys providing valid signatures - `locktime: ` is the Unix timestamp of when the lock expires - `refund: ` are additional public keys that can provide signatures after `locktime` (_allows multiple entries_) - `n_sigs_refund: ` specifies the minimum number of [Refund Multisig](#refund-multisig) public keys providing valid signatures after `locktime` expires Each of the above tags may appear exactly **ONCE** in a P2PK secret. If a tag appears more than once, the P2PK secret is malformed and the Proof **MUST** be rejected as unspendable. If `n_sigs` or `n_sigs_refund` is not a positive integer, or exceeds the total number of keys in its pathway, the P2PK secret is malformed and the Proof **MUST** be rejected as unspendable. > [!NOTE] > > The tag serialization type is `[, , ...]` but some tag values are `int`. Wallets and mints must cast types appropriately for de/serialization. ## Signature flag Signature flags are defined in the tag `Secret.tags['sigflag']`. Currently, there are two signature flags. - `SIG_INPUTS` requires valid signatures on all inputs independently. It is the default signature flag and will be applied if the `sigflag` tag is absent. - `SIG_ALL` requires valid signatures on all inputs and on all outputs of a transaction. If any one input has the signature flag `SIG_ALL`, then all inputs are required to have the same kind, the flag `SIG_ALL` and the same `Secret.data` and `Secret.tags`, otherwise an error is returned. `SIG_INPUTS` is only enforced if no input is `SIG_ALL`. If a P2PK secret has any other signature flag value, the P2PK secret is malformed and the Proof **MUST** be rejected as unspendable. ### Signature flag `SIG_INPUTS` `SIG_INPUTS` means that each `Proof` (input) requires its own signature. The signature is provided in the `Proof.witness` field of each input separately. The format of the [witness](#witness-format) was defined earlier on. #### 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` field is **signed as a string**. ### Signature flag `SIG_ALL` `SIG_ALL` is enforced only if the following conditions are met: - If one input has the signature flag `SIG_ALL`, all other inputs MUST have the same `Secret.data` and `Secret.tags`, and by extension, also be `SIG_ALL`. - If one or more inputs differ from this, an error is returned. If this condition is met, the `SIG_ALL` flag is enforced and only **the first input of a transaction requires a witness** that covers all other inputs and outputs of the transaction. All signatures by the signing public keys MUST be provided in the `Proof.witness` of the first input of the transaction. #### Message aggregation for `SIG_ALL` The message to be signed depends on the type of transaction containing an input with signature flag `SIG_ALL`. ##### Aggregation for `swap` A swap contains `inputs` and `outputs` (see [NUT-03][03]). To provide a valid signature, the owner (or owners) of the signing public keys must concatenate the `secret` and `C` fields of all `Proofs` (inputs) with the `amount` and `B_` fields of all `BlindedMessages` (outputs, see [NUT-00][00]) to a single message string in the order they appear in the transaction. This concatenated string is then hashed and signed (see [Signature scheme](#signature-scheme)). If a swap transaction has `n` inputs and `m` outputs, the message to sign becomes: ``` msg = secret_0 || C_0 || ... || secret_n || C_n || amount_0 || B_0 || ... || amount_m || B_m ``` Here, `||` denotes string concatenation. The `C` of each input and `B_` of each output are **hex strings** and `amount` is a UTF-encoded string. ##### Aggregation for `melt` For a melt transaction, the message to sign is composed of all the inputs, the quote ID being paid, and the [NUT-08][08] blank `outputs`. If a melt transaction has `n` inputs, `m` blank outputs, and a quote ID `quote_id`, the message to sign becomes: ``` msg = secret_0 || C_0 || ... || secret_n || C_n || amount_0 || B_0 || ... || amount_m || B_m || quote_id ``` Here, `||` denotes string concatenation. The `C` of each input and `B_` of each output are **hex strings** and `amount` is a UTF-encoded string. ## Locktime Tag The `locktime` tag signals which set of locking rules the mint should apply. There are three possible states the `locktime` tag can represent: ### Permanent Lock If the `locktime` tag is not present, or is not a valid unix time, the lock is considered "permanent". [Locktime Multisig](#locktime-multisig) conditions apply if the `pubkeys` tag is present, [Basic Case](#basic-case) conditions if not. ### Active Lock If the `locktime` tag is a valid unix time and the mint's local clock is less than `locktime`, the lock is "active". [Locktime Multisig](#locktime-multisig) conditions apply if the `pubkeys` tag is present, [Basic Case](#basic-case) conditions if not. ### Expired Lock If the `locktime` tag is a valid unix time and the mint's local clock is greater than `locktime`, the lock has "expired". Both [Locktime Multisig](#locktime-multisig) and [Refund Multisig](#refund-multisig) conditions apply if the `refund` tag is present, otherwise the proof is considered unlocked and spendable without a witness signature. > [!NOTE] > A `Proof` is considered spendable by anyone if it only requires a `secret` and a valid unblinded signature `C` to be spent (which is the default case in [NUT-00][00]). ## Multisig Cashu offers two multi-signature pathways: `Locktime MultiSig` and `Refund MultiSig`, which are activated depending on the status of the proof's `locktime` tag. > [!NOTE] > Each pathway has a self-contained set of conditions which must be satisfied for that pathway to be valid. You cannot mix/blend conditions between pathways. ### Locktime MultiSig Locktime Multisig extends the [Basic Case](#basic-case) by allowing proofs to be locked to multiple public keys. The additional locking public keys are stored in the `pubkeys` tag. If the `pubkeys` tag is present, the `Proof` is spendable only if a valid signature is given by **at least ONE** of the public keys contained in the `Secret.data` field or the `pubkeys` tag. If the `n_sigs` tag is a positive integer, the mint will require at least `n_sigs` of those public keys to provide a valid signature. If the number of public keys with valid signatures is greater or equal to the number specified in `n_sigs` (or `1` if `n_sigs` is not present), the transaction is valid. The signatures are provided in an array of strings in the `P2PKWitness` object. Expressed as an "n-of-m" scheme, `n = n_sigs` is the number of required signatures and `m = 1 (data field) + count(pubkeys tag keys)` is the total number of public keys that _could_ sign. > [!CAUTION] > > Because Schnorr signatures are non-deterministic (due to auxiliary random data), we expect a minimum number of unique public keys with valid signatures instead of expecting a minimum number of signatures. ### Refund MultiSig Refund Multisig allows proofs to be _additionally spendable_ by a separate set of public keys once the `locktime` has expired. These public keys are stored in the `refund` tag, and can include keys previously listed in `data` or `pubkeys`. [Locktime Multisig](#locktime-multisig) conditions continue to apply, and the proof can continue to be spent according to Locktime Multisig rules. **In addition**, the `Proof` can be spent if a valid signature is given by **at least ONE** of the public keys contained in the `refund` tag. If the `n_sigs_refund` tag is a positive integer, the mint will require at least `n_sigs_refund` of those `refund` public keys to provide a valid signature. If the number of `refund` public keys with valid signatures is greater or equal to the number specified in `n_sigs_refund` (or `1` if `n_sigs_refund` is not present), the transaction is valid. The signatures are provided in an array of strings in the `P2PKWitness` object. Expressed as an "n-of-m" scheme, `n = n_sigs_refund` is the number of required signatures and `m = count(refund tag keys)` is the total number of `refund` keys that _could_ sign. > [!CAUTION] > > Because Schnorr signatures are non-deterministic (due to auxiliary random data), we expect a minimum number of unique public keys with valid signatures instead of expecting a minimum number of 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 `locktime` has passed, the `Proof` continues to be spendable with 2-of-3 signatures from the public keys in the `data` field and the `pubkeys` tag. But now it **ALSO** becomes spendable with a single signature from any ONE of the public keys in the `refund` tag. The signature flag `sigflag` indicates that signatures are necessary on the `inputs` and the `outputs` of the transaction this `Proof` is spent by. ```json [ "P2PK", { "nonce": "da62796403af76c80cd6ce9153ed3746", "data": "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e", "tags": [ ["sigflag", "SIG_ALL"], ["n_sigs", "2"], ["locktime", "1689418329"], [ "refund", "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e", "02e2aeb97f47690e3c418592a5bcda77282d1339a3017f5558928c2441b7731d50" ], [ "pubkeys", "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", "023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54" ] ] } ] ``` ## Public Key Canonicalisation Public keys **MUST** use the [compressed Secp256k1 public key format](https://learnmeabitcoin.com/technical/public-key#public-key-format). Each key **MUST** appear at most **ONCE** per [multi-signature](#Multisig) pathway. The same key **MAY** appear in both pathways. Keys are compared using their lowercase x-coordinate (`02` or `03` y-parity prefix ignored). Example: the following keys are considered equivalent because they all have the same lowercase x-coordinate, so can all be Schnorr signed using the same secret key: ```json 02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904 // 02 lowercase 02698C4E2B5F9534CD0687D87513C759790CF829AA5739184A3E3735471FBDA904 // 02 uppercase 02698C4e2B5f9534cD0687d87513C759790cF829aa5739184a3e3735471fbda904 // 02 mixed case 03698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904 // 03 lowercase 03698C4E2B5F9534CD0687D87513C759790CF829AA5739184A3E3735471FBDA904 // 03 uppercase 03698C4e2B5f9534cD0687d87513C759790cF829aa5739184a3e3735471fbda904 // 03 mixed case ``` Example: key duplicated in main pathway (malformed): ``` "data": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", "pubkeys": "03698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904" ``` Example: key allowed in both pathways: ``` "data": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", "refund": "03698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904" ``` If a pathway contains a duplicate key, the P2PK secret is malformed and the Proof **MUST** be rejected as unspendable. ## 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 ## Mint info setting The [NUT-06][06] `MintMethodSetting` indicates support for this feature: ```json { "11": { "supported": 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