corda - Corda - 设计安全合约
问题描述
最近我一直在研究 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
只能从该节点发行a。不应允许其他任何人代表网络上的另一个节点宣布破产。
识别漏洞
假设我们选择将合约命令约束建模为放松,以便我们可以组合交易。另外,假设我们有一个合约命令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发起了这个交易,但包含了aliceBankruptcyDeclarationState
拥有的类型的输出。他不应该被允许这样做,因为应该只允许所有者签发,但在这种情况下,爱丽丝会因为要求签署 .BankruptcyDeclarationState
ObligationState
这里有一个论点是,可以以这样一种方式编写流程,即爱丽丝会在签名之前检查交易以确保不包括某些状态,但我觉得这还不够。这需要开发人员和节点管理员对流程进行尽职调查,以确保其安全性。
相比之下,严格的合约命令约束会以我认为更安全的方式防止这些漏洞 - 因此仅在合约级别需要尽职调查,而不是每个开发人员编写消耗合约的流程。
在这方面,我正在寻找一些明确的指南,说明合同命令约束是否应该严格、放松,或者是否还有其他我错过的考虑因素。谢谢。
解决方案
正如您正确指出的那样,所有交易方共享相同的合同代码。这是他们之间唯一的约定。但是每一方都通过开发他/她自己的安全流程来为他/她的行为(签名)负责。书面流程的基础是在签署之前根据合约代码验证交易。谁会在不阅读/检查合同的情况下以数字或其他方式签署任何东西?我错过了什么吗?
推荐阅读
- system-verilog - const 对象句柄作为函数中的参数
- javascript - 输入带有电子邮件验证的输入,尝试清除错误消息并在单击复选框时使输入变灰
- regex - 如何在 awk 中生成错误消息列表?
- javascript - 在 reduce 函数中使用变量
- twitter-bootstrap - vb-tooltip 的自定义类
- linux-kernel - 如何在 Linux 中分配大的连续内存区域
- reactjs - 子路由不会在 SSR React App 上重新加载
- jquery - 没有从 jQuery 循环中获取值
- javascript - JS 数组中出现最多的元素的问题
- c - 将静态结构添加到作为函数参数的数组