Corda - 设计安全合约
Corda - Designing Secure Contracts
最近我一直在研究 Corda 中合约命令的安全性和漏洞方面。关于一些合约命令约束是否应该严格或是否应该放宽以允许不同输入、输出和命令的交易组合的争论出现了。
问题是,虽然我可以看到允许交易组合的好处,但我觉得宽松的合约命令约束实际上会带来安全漏洞,在我看来,最好在合约级别防范这些漏洞,使得命令的签署参与者通过整体合约验证达成共识,而不是依赖流量级别检查,可能被开发人员忽略或被恶意节点规避.
示例 - 破产声明
这个例子允许网络上的节点宣布破产。假设在这种情况下,破产宣告状态只是宣告破产的节点的身份,以及一个原因。
@BelongsToContract(BankruptcyDeclarationContract::class)
data class BankruptcyDeclarationState(
override val owner: AbstractParty,
val reason: String
) : OwnableState { ... }
严格验证
严格的验证要求,在签发时...
- 必须消耗零输入状态。
- 必须创建一个输出状态。
- 只有所有者必须签名。
fun verifyIssue(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
"Zero input states must be consumed." using (tx.inputs.isEmpty())
"One output state must be created." using (tx.outputs.size == 1)
val state = tx.outputsOfType<BankruptcyDeclarationState>().single()
"Only the owner must sign." using (state.owner.owningKey == signers.single())
}
宽松验证
宽松的验证要求,在发行时...
- 必须消耗
BankruptcyDeclarationState
类型的零输入状态。
- 必须创建一个
BankruptcyDeclarationState
类型的输出状态。
- 只有所有者必须签名。
fun verifyIssue(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
val inputs = tx.inputsOfType<BankruptcyDeclarationState>()
val outputs = tx.outputsOfType<BankruptcyDeclarationState>()
"Zero input states of type BankruptcyDeclarationState must be consumed." using
(inputs.isEmpty())
"One output state of type BankruptcyDeclarationState must be created." using
(outputs.size == 1)
"Only the owner must sign." using (outputs.single().owner.owningKey == signers.single())
}
观察结果
- 严格验证确保全局检查输入和输出,而不是检查特定的输入和输出类型,但是这有一个缺点,即输入和输出的交易组合是不可能的。
- 宽松的验证确保只检查所需状态类型的输入和输出,这将允许不同输入和输出类型的交易组合。
- 这里的关键是只有宣布破产的节点必须签名,这意味着
BankruptcyDeclarationState
的发行只能从该节点发生。 不允许任何其他人代表网络上的另一个节点宣布破产。
识别漏洞
假设我们选择对我们的合约命令约束进行建模以放宽,以便我们可以编写交易。另外,假设我们有一些 ObligationState
的合约命令,在发布时要求:
- 必须消耗
ObligationState
类型的零输入状态。
- 必须创建一个
ObligationState
类型的输出状态。
- 债务人和债权人必须签字。
现在我们有两个状态类型和两个合约命令,我们可以编写一个同时使用这两者的交易,并确定漏洞。假设这里 bob 正在发起这个交易。
val transaction = with(TransactionBuilder(notary)) {
addOutputState(ObligationState(alice, bob), ObligationContract.ID)
addCommand(ObligationContract.Issue(), aliceKey, bobKey)
addOutputState(BankruptcyDeclarationState(alice, "..."), BankruptcyDeclarationContract.ID)
addCommand(BankruptcyDeclarationContract.Issue(), aliceKey)
}
请记住,只有 BankruptcyDeclarationState
的所有者必须签名,但 ObligationState
的义务人和债权人必须签名,因此此发起流程将从所需的对方收集签名。这里的漏洞是 bob 发起了这个交易,但是包含了 BankruptcyDeclarationState
类型的输出,它属于 alice。不应该允许他这样做,因为只允许所有者发布 BankruptcyDeclarationState
但在这种情况下 alice 会无意中签名,因为需要签署 ObligationState
.
这里有一个论点,即流程可以这样写,alice 会在签名前检查交易以确保不包括某些状态,但我觉得这还不够。这要求开发人员和节点管理员对流程进行尽职调查以确保其安全性。
相比之下,严格的合约命令约束将以我认为更安全的方式防止这些漏洞——因此只需要在合约级别进行尽职调查,而不是每个开发人员编写使用合约的流程.
我在这方面寻找的是关于合同命令约束是否应该严格、放松,或者是否有其他我遗漏的考虑因素的权威指南。谢谢。
正如您所指出的,所有交易方共享相同的合约代码。这是他们之间唯一的约定。
但是每一方通过开发 his/her 自己的安全流程来负责 his/her 行动(签名)。编写流程的基础是在签署之前根据合约代码验证交易。如果没有 reading/checking 合同,谁会以数字方式或其他方式签署任何东西?
我错过了什么吗?
最近我一直在研究 Corda 中合约命令的安全性和漏洞方面。关于一些合约命令约束是否应该严格或是否应该放宽以允许不同输入、输出和命令的交易组合的争论出现了。
问题是,虽然我可以看到允许交易组合的好处,但我觉得宽松的合约命令约束实际上会带来安全漏洞,在我看来,最好在合约级别防范这些漏洞,使得命令的签署参与者通过整体合约验证达成共识,而不是依赖流量级别检查,可能被开发人员忽略或被恶意节点规避.
示例 - 破产声明
这个例子允许网络上的节点宣布破产。假设在这种情况下,破产宣告状态只是宣告破产的节点的身份,以及一个原因。
@BelongsToContract(BankruptcyDeclarationContract::class)
data class BankruptcyDeclarationState(
override val owner: AbstractParty,
val reason: String
) : OwnableState { ... }
严格验证
严格的验证要求,在签发时...
- 必须消耗零输入状态。
- 必须创建一个输出状态。
- 只有所有者必须签名。
fun verifyIssue(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
"Zero input states must be consumed." using (tx.inputs.isEmpty())
"One output state must be created." using (tx.outputs.size == 1)
val state = tx.outputsOfType<BankruptcyDeclarationState>().single()
"Only the owner must sign." using (state.owner.owningKey == signers.single())
}
宽松验证
宽松的验证要求,在发行时...
- 必须消耗
BankruptcyDeclarationState
类型的零输入状态。 - 必须创建一个
BankruptcyDeclarationState
类型的输出状态。 - 只有所有者必须签名。
fun verifyIssue(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
val inputs = tx.inputsOfType<BankruptcyDeclarationState>()
val outputs = tx.outputsOfType<BankruptcyDeclarationState>()
"Zero input states of type BankruptcyDeclarationState must be consumed." using
(inputs.isEmpty())
"One output state of type BankruptcyDeclarationState must be created." using
(outputs.size == 1)
"Only the owner must sign." using (outputs.single().owner.owningKey == signers.single())
}
观察结果
- 严格验证确保全局检查输入和输出,而不是检查特定的输入和输出类型,但是这有一个缺点,即输入和输出的交易组合是不可能的。
- 宽松的验证确保只检查所需状态类型的输入和输出,这将允许不同输入和输出类型的交易组合。
- 这里的关键是只有宣布破产的节点必须签名,这意味着
BankruptcyDeclarationState
的发行只能从该节点发生。 不允许任何其他人代表网络上的另一个节点宣布破产。
识别漏洞
假设我们选择对我们的合约命令约束进行建模以放宽,以便我们可以编写交易。另外,假设我们有一些 ObligationState
的合约命令,在发布时要求:
- 必须消耗
ObligationState
类型的零输入状态。 - 必须创建一个
ObligationState
类型的输出状态。 - 债务人和债权人必须签字。
现在我们有两个状态类型和两个合约命令,我们可以编写一个同时使用这两者的交易,并确定漏洞。假设这里 bob 正在发起这个交易。
val transaction = with(TransactionBuilder(notary)) {
addOutputState(ObligationState(alice, bob), ObligationContract.ID)
addCommand(ObligationContract.Issue(), aliceKey, bobKey)
addOutputState(BankruptcyDeclarationState(alice, "..."), BankruptcyDeclarationContract.ID)
addCommand(BankruptcyDeclarationContract.Issue(), aliceKey)
}
请记住,只有 BankruptcyDeclarationState
的所有者必须签名,但 ObligationState
的义务人和债权人必须签名,因此此发起流程将从所需的对方收集签名。这里的漏洞是 bob 发起了这个交易,但是包含了 BankruptcyDeclarationState
类型的输出,它属于 alice。不应该允许他这样做,因为只允许所有者发布 BankruptcyDeclarationState
但在这种情况下 alice 会无意中签名,因为需要签署 ObligationState
.
这里有一个论点,即流程可以这样写,alice 会在签名前检查交易以确保不包括某些状态,但我觉得这还不够。这要求开发人员和节点管理员对流程进行尽职调查以确保其安全性。
相比之下,严格的合约命令约束将以我认为更安全的方式防止这些漏洞——因此只需要在合约级别进行尽职调查,而不是每个开发人员编写使用合约的流程.
我在这方面寻找的是关于合同命令约束是否应该严格、放松,或者是否有其他我遗漏的考虑因素的权威指南。谢谢。
正如您所指出的,所有交易方共享相同的合约代码。这是他们之间唯一的约定。 但是每一方通过开发 his/her 自己的安全流程来负责 his/her 行动(签名)。编写流程的基础是在签署之前根据合约代码验证交易。如果没有 reading/checking 合同,谁会以数字方式或其他方式签署任何东西? 我错过了什么吗?