【问题标题】:Corda - Designing Secure ContractsCorda - 设计安全合约
【发布时间】:2019-09-02 08:41:36
【问题描述】:

最近我一直在研究 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 发起了这个交易,但包含了一个属于 aliceBankruptcyDeclarationState 类型的输出。他不应该被允许这样做,因为应该只允许所有者发出BankruptcyDeclarationState,但在这种情况下,alice 会因为要求签署ObligationState 而在不知情的情况下签名。

这里有一个论点是,流可以这样编写,alice 在签名之前会检查交易以确保不包括某些状态,但我不觉得这样就足够了。这需要开发人员和节点管理员对流程进行尽职调查,以确保其安全性。

相比之下,严格的合约命令约束会以我认为更安全的方式防止这些漏洞 - 因此只需要在合约级别进行尽职调查,而不是每个开发人员编写使用合约的流程。

在这方面,我正在寻找一些明确的指南,说明合同命令约束是否应该严格、宽松,或者是否还有其他我错过的考虑因素。谢谢。

【问题讨论】:

    标签: corda


    【解决方案1】:

    正如您正确指出的那样,相同的合约代码在所有交易方之间共享。这是他们之间唯一的约定。 但是每一方都通过开发他/她自己的安全流程来为他/她的行为(签名)负责。书面流程的基础是在签署之前根据合同代码验证交易。谁会在不阅读/检查合同的情况下以数字或其他方式签署任何东西? 我错过了什么吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-23
      相关资源
      最近更新 更多