Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 89 additions & 36 deletions 11.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

`optional`

`depends on: NUT-10`
`depends on: NUT-10, NUT-08`

---

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.
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 [NUT-06][06] info endpoint.

## Pay-to-Pubkey

Expand Down Expand Up @@ -45,38 +45,43 @@ The recipient who owns the private key of the public key `Secret.data` can spend
}
```

#### Signature scheme
### 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]).
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]).

### 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", ...]`.
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

Supported tags are:

- `sigflag: <str>` determines whether outputs have to be signed as well
- `n_sigs: <int>` specifies the minimum number of valid signatures expected
- `pubkeys: <hex_str>` are additional public keys that can provide signatures (_allows multiple entries_)
- `sigflag: <str_enum[SIG_FLAG]>` sets the signature flag
- `pubkeys: <hex_str>` are additional public keys (together with the one in the `data` field of the secret) that can provide signatures (_allows multiple entries_)
- `n_sigs: <int>` specifies the minimum number of public keys providing valid signatures
- `locktime: <int>` is the Unix timestamp of when the lock expires
- `refund: <hex_str>` are optional refund public keys that can exclusively spend after `locktime` (_allows multiple entries_)
- `n_sigs_refund: <int>` specifies the minimum number of refund public keys providing valid signatures

**Note:** The tag serialization type is `[<str>, <str>, ...]` but some tag values are `int`. Wallets and mints must cast types appropriately for de/serialization.
> [!NOTE]
>
> The tag serialization type is `[<str>, <str>, ...]` 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.
- `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.

The signature flag `SIG_ALL` is enforced if at least one of the `Proofs` have the flag `SIG_ALL`. Otherwise, `SIG_INPUTS` is enforced.
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.

#### Signature
`SIG_INPUTS` is only enforced if no input is `SIG_ALL`.

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`.
#### Signature flag `SIG_INPUTS`

`SIG_INPUTS` means that each `Proof` (input) requires it's own signature. The signature is provided in the `Proof.witness` field of each input separately. The format of the [witness](#witness-format) is defined later on.

##### Signed inputs

Expand All @@ -92,51 +97,98 @@ A `Proof` (an input) with a signature `P2PKWitness.signatures` on `secret` is th
}
```

The `secret` of each input is **signed as a string**.
The `secret` field is **signed as a string**.

##### Signed outputs
##### Witness format

A `BlindedMessage` (an output) with a signature `P2PKWitness.signatures` on `B_` is the JSON (see [NUT-00][00]):
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
{
"amount": <int>,
"B_": <hex_str>,
"witness": <P2PKWitness | str> // Signatures on "B_"
"signatures": <Array[<hex_str>]>
}
```

The `B_` of each output is **signed as bytes** which comes from the original hex string.
The `signatures` are an array of signatures in hex and correspond to the signatures by one or more signing public keys.

##### Witness format
#### Signature flag `SIG_ALL`

`P2PKWitness` is a serialized JSON string of the form
`SIG_ALL` is enforced only if the following conditions are met:

```json
{
"signatures": <Array[<hex_str>]>
}
- 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` fields of all `Proofs` (inputs), and the `B_` fields of all `BlindedMessages` (outputs, see [NUT-00][00]) to a single message string in the order they appear in the transaction. This string concatenated 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 || ... || secret_n || B_0 || ... || B_m
```

The `signatures` are an array of signatures in hex. The witness for a spent proof can be obtained with a `Proof` state check (see [NUT-07][07]).
Here, `||` denotes string concatenation. The `B_` of each output is **a hex 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 || ... || secret_n || B_0 || ... || B_m || quote_id
```

Here, `||` denotes string concatenation. The `B_` of each output is **a hex string**.

### 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.
Cashu offers two levels of multi-signature protection: `Locktime MultiSig` and `Refund MultiSig`, which are activated depending on the status of the proof's `locktime` tag.

#### Locktime MultiSig

> [!NOTE]
> Locktime Multisig conditions only apply if the `locktime` tag is not present, or is a timestamp in the future.

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`, 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) + len(pubkeys tag)` is the number of public keys that could sign.

> [!CAUTION]
>
> Because Schnorr signatures are non-deterministic, we expect a minimum number of unique public keys with valid signatures instead of expecting a minimum number of signatures.

#### 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.

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.
> [!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).

### Locktime
#### Refund MultiSig

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).
If the `locktime` tag is in the past and the `refund` tag is present, the `Proof` is spendable only if a valid signature is given by at least ONE of the `refund` pubkeys.

#### Refund public keys
If the `n_sigs_refund` tag is present, the mint will require at least`n_sigs_refund` of the `refund` pubkeys to provide a valid signature.

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`.
> [!CAUTION]
>
> Because Schnorr signatures are non-deterministic, we expect a minimum number of unique public keys with valid signatures instead of expecting a minimum number of signatures.

This comment was marked as resolved.

#### 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.
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 ONE of the two 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
[
Expand All @@ -150,7 +202,8 @@ This is an example `secret` that locks a `Proof` with a Pay-to-Pubkey (P2PK) con
["locktime", "1689418329"],
[
"refund",
"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e"
"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
"02e2aeb97f47690e3c418592a5bcda77282d1339a3017f5558928c2441b7731d50"
],
[
"pubkeys",
Expand Down
Loading