该文档(NUT-11)描述了Pay-to-Public-Key(P2PK)方案,它是一种基于NUT-10的Secret的支付条件。
optional
depends on: NUT-10, NUT-08
此NUT描述了支付到公钥(Pay-to-Public-Key,P2PK),这是一种基于NUT-10的知名Secret
的支付条件。使用P2PK,我们可以将ecash代币锁定到接收者的ECC公钥,并需要使用相应的私钥进行Schnorr签名来解锁ecash。支付条件由mint强制执行。
警告:如果mint不支持这种类型的支付条件,证明可能会被视为常规的任何人都可以花费的代币。应用程序需要通过检查mint的NUT-06信息端点来确保mint是否支持特定类型的支付条件。
NUT-10 Secret kind: P2PK
如果对于一个Proof
,Proof.secret
是一种kind: P2PK
的Secret
,则必须通过提供见证Proof.witness
以及数组Proof.witness.signatures
中的一个或多个有效签名来解锁该证明。
在基本情况下,当花费锁定的代币时,mint需要Proof.witness.signatures
中关于Proof.secret
的,由Proof.Secret.data
中的公钥进行的有效 Schnorr 签名。
为了给出基本情况的具体示例,要mint一个锁定的代币,我们首先创建一个P2PK Secret
,内容如下:
[
"P2PK",
{
"nonce": "859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f",
"data": "0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7",
"tags": [["sigflag", "SIG_INPUTS"]]
}
]
这里,Secret.data
是锁定ecash的接收者的公钥。我们将此Secret
序列化为Proof.secret
中的字符串,并获得mint的盲签名,该签名存储在Proof.C
中(参见NUT-03)。
拥有公钥Secret.data
的私钥的接收者可以通过提供Proof.secret
字符串的签名来花费此证明,然后将其添加到Proof.witness.signatures
中:
{
"amount": 1,
"secret": "[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]",
"C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
"id": "009a1f293253e41e",
"witness": "{\"signatures\":[\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\"]}"
}
要花费锁定有P2PK
的代币,花费者需要在花费的证明中包含签名。我们使用libsecp256k1
的序列化64字节Schnorr签名,对要签名的消息的SHA256哈希进行签名。要签名的消息是输入中的Proof.secret
字段。如果输入中的Secret.tags.sigflag
指示,则输出可能还需要对消息BlindedMessage.B_
进行签名。
像swap和melt这样的ecash花费操作可以有多个输入和输出。如果我们有多个锁定的输入,我们要么在每个输入中单独提供签名(对于SIG_INPUTS
),要么仅在第一个输入中提供整个交易的签名(对于SIG_ALL
)。输入是在inputs
字段中提供的Proofs
,输出是请求正文的outputs
字段中的BlindedMessages
(请参见NUT-05中的PostMeltRequest
和NUT-03中的PostSwapRequest
)。
更复杂的支出条件可以在Secret.tags
中的标签中定义。所有标签都是可选的。标签是具有两个或多个字符串的数组,格式为["key", "value1", "value2", ...]
。我们将证明中的特定标签表示为
支持的标签有:
sigflag: <str_enum[SIG_FLAG]>
设置签名标志pubkeys: <hex_str>
是其他的公钥(和secret里data
字段里的公钥一起),可以提供签名(允许多个条目)n_sigs: <int>
指定提供有效签名的公钥的最小数量locktime: <int>
是锁过期的 Unix 时间戳refund: <hex_str>
是可选的退款公钥,只能在 locktime
之后花费(允许多个条目)n_sigs_refund: <int>
指定提供有效签名的退款公钥的最小数量[!NOTE]
标签序列化类型是
[<str>, <str>, ...]
,但某些标签值是int
。钱包和mint必须为反/序列化适当地转换类型。
签名标志在Secret.tags['sigflag']
标签中定义。当前,有两个签名标志。
SIG_INPUTS
要求所有输入都具有独立的有效签名。它是默认的签名标志,如果缺少sigflag
标签,则将应用该标志。SIG_ALL
要求对交易的所有输入和所有输出都进行有效签名。如果任何一个输入具有签名标志SIG_ALL
,则要求所有输入都具有相同的类型,标志SIG_ALL
和相同的Secret.data
和Secret.tags
,否则将返回错误。
仅当没有输入为SIG_ALL
时,才强制执行SIG_INPUTS
。
SIG_INPUTS
SIG_INPUTS
表示每个Proof
(输入)都需要自己的签名。该签名在每个输入的Proof.witness
字段中单独提供。见证的格式稍后定义。
在secret
上具有签名P2PKWitness.signatures
的Proof
(输入)是JSON(请参见NUT-00):
{
"amount": <int>,
"secret": <str>,
"C": <hex_str>,
"id": <str>,
"witness": <P2PKWitness | str> // "secret"上的签名
}
secret
字段被签名成一个字符串。
签名存储在P2PKWitness
对象中,并且分别在所有输入的每个Proof.witness
中(对于SIG_INPUTS
)或仅在交易的第一个输入中(对于SIG_ALL
)中提供。P2PKWitness
是以下形式的序列化JSON字符串
{
"signatures": <Array[<hex_str>]>
}
signatures
是十六进制签名数组,对应于一个或多个签名公钥的签名。
SIG_ALL
仅当满足以下条件时,才强制执行SIG_ALL
:
SIG_ALL
,则所有其他输入必须具有相同的Secret.data
和Secret.tags
,并且扩展到也必须是SIG_ALL
。如果满足此条件,则强制执行SIG_ALL
标志,并且只有交易的第一个输入需要一个见证,该见证涵盖该交易所有其他的输入和输出。签名公钥的所有签名必须在交易的第一个输入的Proof.witness
中提供。
SIG_ALL
的消息聚合要签名的消息取决于包含具有签名标志SIG_ALL
的输入的交易类型。
swap
的聚合交换包含inputs
和outputs
(请参见NUT-03)。为了提供有效的签名,签名公钥的所有者必须将所有Proofs
(输入)的secret
字段和所有BlindedMessages
(输出,请参见NUT-00)的B_
字段连接到单个消息字符串中,按它们在交易出现的顺序进行连接。然后,将此字符串连接起来进行哈希和签名(请参见签名方案)。
如果交换交易具有n
个输入和m
个输出,则要签名的消息变为:
msg = secret_0 || ... || secret_n || B_0 || ... || B_m
这里,||
表示字符串连接。每个输出的B_
是 一个十六进制字符串。
melt
的聚合对于熔化交易(melt transaction),要签名的消息由所有输入,要支付的报价ID和NUT-08空白outputs
组成。
如果熔化交易(melt transaction)具有n
个输入,m
个空白输出和一个报价ID quote_id
,则要签名的消息变为:
msg = secret_0 || ... || secret_n || B_0 || ... || B_m || quote_id
这里,||
表示字符串连接。每个输出的B_
是 一个十六进制字符串。
Cashu 提供两种级别的多重签名保护:Locktime MultiSig
和 Refund MultiSig
,它们根据证明的 locktime
标签的状态激活。
[!NOTE] 只有当
locktime
标签不存在,或者是一个未来的时间戳时,Locktime 多重签名条件才适用。
如果存在 pubkeys
标签,则仅当 Secret.data
字段或 pubkeys
标签中包含的至少一个公钥给出有效签名时,该 Proof
才可以花费。
如果 n_sigs
标签是一个正整数,则 mint 将要求至少 n_sigs
个公钥提供有效签名。
如果具有有效签名的公钥数量大于或等于 n_sigs
中指定的数量,则交易有效。签名在 P2PKWitness
对象中的字符串数组中提供。
表示为“n-of-m”方案,n = n_sigs
是所需签名的数量,m = 1 (data field) + len(pubkeys tag)
是可以签名的公钥的数量。
[!CAUTION]
由于 Schnorr 签名是不确定的,因此我们期望最少数量的具有有效签名的唯一公钥,而不是期望最少数量的签名。
如果标签 locktime
是 unix 时间,并且 mint 的本地时钟大于 locktime
,则除了以下条件也为真以外,该 Proof
可以被任何人花费。
[!NOTE] 如果一个
Proof
只需要一个secret
和一个有效的签名C
就可以花费(这是默认情况),那么它被认为是任何人都可以花费的。
如果 locktime
标签在过去,并且存在 refund
标签,则只有当至少一个 refund
公钥给出有效签名时,该 Proof
才可以花费。
如果存在 n_sigs_refund
标签,则 mint 将要求至少 n_sigs_refund
个 refund
公钥提供有效签名。
[!CAUTION]
由于 Schnorr 签名是不确定的,因此我们期望最少数量的具有有效签名的唯一公钥,而不是期望最少数量的签名。
这是一个 Secret
示例,它使用 Pay-to-Pubkey (P2PK) 条件锁定 Proof
,该条件要求来自 data
字段和 pubkeys
标签中的公钥的 2-of-3 签名。如果 timelock
过去,则 Proof
可以仅使用来自 refund
标签中的两个公钥之一的签名来花费。签名标志 sigflag
指示签名对于此 Proof
花费的交易的 inputs
和 outputs
都是必要的。
[
"P2PK",
{
"nonce": "da62796403af76c80cd6ce9153ed3746",
"data": "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
"tags": [
["sigflag", "SIG_ALL"],
["n_sigs", "2"],
["locktime", "1689418329"],
[
"refund",
"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
"02e2aeb97f47690e3c418592a5bcda77282d1339a3017f5558928c2441b7731d50"
],
[
"pubkeys",
"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
"023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54"
]
]
}
]
使用P2PK可以解锁以下用例:
NUT-06 MintMethodSetting
指示对此功能的支持:
{
"11": {
"supported": true
}
}
- 原文链接: github.com/cashubtc/nuts...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!