最近,我一直在研究 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
,但在这种情况下,由于需要为 ObligationState
签名,Alice 将无意中签署。在这里可以提出这样的论点:可以编写流程使 Alice 在签署之前检查事务以确保某些状态未被包含,但我认为这还不够。这需要开发人员和节点管理员进行流程尽职调查以确保其安全性。
相比之下,严格的合约命令限制将以我认为更加安全的方式防止这些漏洞-因此,只需要在合约级别上执行尽职调查,而不是每个编写使用合约的开发人员都需要尽职调查。
在这方面,我正在寻找的是关于合约命令限制应该是严格的,宽松的,还是是否存在其他需要考虑但我错过了的问题的一些明确指南。谢谢。