# 比特币扩展概念验证：在支持 OP_CAT 的比特币上实现桥契约

By [Starknet 中文](https://paragraph.com/@starknet-zh) · 2024-12-03

---

> _原文：_[_Implementing a Bridge Covenant on OP\_CAT-Enabled Bitcoin: A Proof of Concept_](https://starkware.co/blog/implementing-a-bridge-covenant-on-op-cat-bitcoin/)
> 
> _翻译及校对：_[_Starknet 中文社区_](https://twitter.com/StarkNet_ZH)
> 
> _📑 转载请注明出处 🕹️_ 

精选速览
====

*   深入探讨在比特币上构建 demo 桥契约，为 Starknet 的生产级桥奠定基础
    
*   实施存取款聚合器、桥和取款扩展器四种智能合约
    
*   利用递归契约和默克尔树有效地批量处理存款和取款请求，同时保持用户账户的完整性和安全性
    

引言
==

本文，我们深入探讨了 [sCrypt](https://scrypt.io/) 如何在比特币上构建一个 demo 桥契约。该概念验证实现旨在为 Starknet 二层（L2）网络的生产级桥奠定基础。该桥的设计允许将多个存款或取款请求交易合并为一个根交易，并将其并入主桥契约中，更新其状态，该状态由一组以默克尔树组织的账户组成。

由于桥契约脚本非常复杂，我们在 sCrypt 利用了 sCrypt 专属领域语言（DSL）来编写其实现方式。

概览
==

该桥由一个递归契约比特币脚本构成。在这里，「契约」意味着锁定脚本能够对支出交易施加条件，而「递归」则意味着上述规则足够强大，可以在链上实现持久的逻辑和状态（这是任何链上智能合约的基本要求）。

该脚本存在于一系列交易中，每笔交易都对后续交易结构施加约束，而后续交易解锁当前交易的输出。每当一笔新交易添加到这条链中时，就代表了桥状态的更新。因此，这条链的末端保存着当前的桥状态。

![](https://storage.googleapis.com/papyrus_images/144e2f38164d1a38e6a6771b7c250222bff85305adefec9b4a4e040a76e5d263.png)

契约状态 — 具体来说，就是其哈希值 — 存储在一个不可消耗的 OP\_RETURN 输出中。虽然我们不会花费这个 UTXO，但在执行契约脚本时可以检查其数据。具体来说，状态保存了包含账户数据的默克尔树的根哈希值，如下所示：

![](https://storage.googleapis.com/papyrus_images/c1742a567290c393a8fb4ac26611c0908b0bd1d775ba45d4da61a2150c6aab3c.png)

该默克尔树保存了一组固定账户槽的数据。叶节点包含各自账户数据的哈希值，其中包括地址和余额。为了表示空的账户槽，这些槽被标记为零字节。

每次桥的更新都会导致账户树发生变化。为了方便这种更新，我们依赖于默克尔证明，其验证在比特币脚本中非常高效。更新主要包含两个步骤。首先，我们验证一个默克尔证明，以证明证明默克尔树包含了特定账户的当前状态。然后，在计算该账户的新状态后，我们使用前述默克尔证明中的相同辅助节点来推导出新的根哈希值。

![](https://storage.googleapis.com/papyrus_images/2134d39c5f2d9a366e6d0eede779afa3f60c6c554ad6570671a0fb036d21ff56.png)

更新可以是存款，也可以是取款。桥可在单笔交易中执行这些更新的批量操作。

存款
==

我们的目标是让用户能够独立提交存款或取款请求。为此，用户分别创建交易，分别支付给存款或取款聚合契约。该契约将这些请求汇总成一棵默克尔树。该树的根哈希值可以合并到主桥契约中，主桥契约随后处理每笔存款或取款。

![](https://storage.googleapis.com/papyrus_images/2111d2f96f0ab26235ad4946bfc2fff340ccffc86e878cefc565cfa14aaf7526.png)

在存款交易中，除了对存款数据进行哈希并构建默克尔树之外，契约还确保锁定在契约输出中的存款 satoshis 按正确的方式累积至树的根节点。聚合契约确保只有正确的链上智能合约才能使用这些资金。(当然，在生产环境中，我们也会允许用户取消其存款交易）。

这种树形结构的设计源于契约脚本构建的限制，即不允许包含过多输入和输出的交易。树形结构使我们能够扩展到潜在的任意吞吐量。

取款请求
====

取款请求的聚合与存款类似，但有几处不同。首先，我们需要一种认证方法，以便用户可以从自己的账户取款。这与存款不同，存款是任何人可以向任何账户存款，这与比特币地址的使用方式类似。认证在聚合树的叶节点层完成。取款请求聚合契约会检查提款地址是否与叶交易中第一个输入的 P2WPKH 地址匹配。

![](https://storage.googleapis.com/papyrus_images/01c4927a61a6d94fa288561d7d2011defab601c04d1575d97c5f454ab151eb86.png)

这确保了地址的所有者批准取款，因为他们已经签署了请求取款的交易。与存款聚合相比，另一个细微的不同之处在于，我们还会将中间的累计金额进行哈希，向上传递到树结构中。这是因为在扩展取款时，我们需要这些数据，稍后会详细说明。

敏锐的读者可能会注意到这种取款请求认证模型的潜在问题。假如操作员决定作弊，创建一个聚合树的根交易，而聚合树的数据是通过未经认证的虚假取款请求在本地伪造的，那该怎么办？我们需要一种有效的方法来验证根交易是否来自有效的叶交易。

为了解决这个问题，我们执行了所谓的「创世检查（genesis check）」。本质上，我们让聚合契约检查其前一笔交易以及前两笔交易，即其「祖先交易」。契约验证这些交易是否包含相同的契约脚本，并执行相同的检查。通过这种方式，我们实现了一个归纳式的交易历史检查。由于前两笔交易与当前契约一样，执行了相同的检查，我们可以确认这些交易的「祖先」也执行了相同的检查，一直追溯到叶节点（即创世交易）。

![](https://storage.googleapis.com/papyrus_images/9b7bbfb21897f6d8ebd94e9929ac9468b579b6bc0ecc9ba1929c0bb06e78e15f.png)

当然，我们对树的两个分支都执行了验证。因此，每个聚合节点交易总共检查最多六笔交易。

取款扩展
====

现在让我们进入解决方案的最后部分：取款扩展。在处理一批取款请求后，主桥契约会强制执行一个输出，将总取款金额支付给扩展契约。我们可以将这个契约视为执行取款请求聚合契约所做操作的逆向过程。其从取款树的根节点开始，将其扩展为两个分支，每个分支包含应支付到该分支的相应取款金额。这个过程一直延续到取款树的叶节点。叶交易强制执行一个简单的支付输出，向账户所有者的地址支付他们要求提取的金额。

![](https://storage.googleapis.com/papyrus_images/3f44a14a9cabde61cf207ffb0f42aba3f90c55b2df48df621b2f00a856e0db07.png)

实现方式
====

为了实现我们的桥契约，我们开发了四个 sCrypt 智能合约，分别处理系统的不同方面。本节，我们将简要概述每个合约的功能。

存款聚合器合约
-------

**存款聚合器**（DepositAggregator）合约将单个存款聚合成一棵默克尔树，然后将其合并到主桥契约中。这种聚合可实现批量存款处理，减少需要由桥单独处理的交易数量。此外，它还允许用户独立提交存款，稍后由操作员进行处理。

    class DepositAggregator extends SmartContract {
      @prop()
      operator: PubKey
    
      @prop()
      bridgeSPK: ByteString
      /**
        * Covenant used for the aggregation of deposits.
        *
        * @param operator - Public key of bridge operator.
        * @param bridgeSPK - P2TR script of the bridge state covenant. Includes length prefix!
        */
      constructor(operator: PubKey, bridgeSPK: ByteString) {
          super(...arguments)
          this.operator = operator
          this.bridgeSPK = bridgeSPK
      }
      @method()
      public aggregate(
          shPreimage: SHPreimage,
          isPrevTxLeaf:boolean,
          sigOperator: Sig,
          prevTx0: AggregatorTransaction,
          prevTx1: AggregatorTransaction,
          // Additional parameters...
      ) {
          // Validation steps...
      }
      @method()
      public finalize(
          shPreimage: SHPreimage,
          sigOperator: Sig,
          prevTx: AggregatorTransaction,
          ancestorTx0: AggregatorTransaction,
          ancestorTx1: AggregatorTransaction,
          bridgeTxId: Sha256,
          fundingPrevout: ByteString
      ) {
          // Finalization steps...
      }
    }
    

合约构建函数有两个参数：

*   operator：桥操作员的公钥，该操作员有权聚合存款。
    
*   bridgeSPK：主桥契约的脚本公钥（SPK），确保聚合存款正确合并。
    

存款聚合器的核心功能封装在「聚合（aggregate）」方法中。该方法执行以下步骤：

**验证 Sighash 原像和操作员签名**：确保交易经过桥操作员授权，并且 sighash 原像格式正确且属于正在执行的交易。通过[这篇文章](https://scryptplatform.medium.com/trustless-ordinal-sales-using-op-cat-enabled-covenants-on-bitcoin-0318052f02b2)了解关于 sighash 原像验证的更多信息。

    // Check sighash preimage.
    const s = SigHashUtils.checkSHPreimage(shPreimage)
    assert(this.checkSig(s, SigHashUtils.Gx))
    
    // Check operator signature.
    assert(this.checkSig(sigOperator, this.operator))
    

**构建和验证前置交易 ID**：检查已聚合的前置交易是否有效并正确引用。

    // Construct previous transaction ID.
    const prevTxId = AggregatorUtils.getTxId(prevTx, false)
    
    // Verify that the transaction unlocks the specified outputs.
    const hashPrevouts = AggregatorUtils.getHashPrevouts(
      bridgeTxId,
      prevTxId,
      fundingPrevout
    )
    assert(hashPrevouts == shPreimage.hashPrevouts)
    

**默克尔树聚合**：验证作为见证哈希值传递的存款数据是否与前置交易中存储的状态匹配。

    const hashData0 = DepositAggregator.hashDepositData(depositData0)
    const hashData1 = DepositAggregator.hashDepositData(depositData1)
    
    assert(hashData0 == prevTx0.hashData)
    assert(hashData1 == prevTx1.hashData)
    

**金额验证**：确认前置输出中的金额与指定的存款金额匹配，确保资金在聚合中正确计算。

    // Check that the prev outputs actually carry the specified amount
    // of satoshis. The amount values can also carry aggregated amounts,
    // in case we're not aggregating leaves anymore.
    assert(
      GeneralUtils.padAmt(depositData0.amount) ==
      prevTx0.outputContractAmt
    )
    assert(
      GeneralUtils.padAmt(depositData1.amount) ==
      prevTx1.outputContractAmt
    )
    

**状态更新**：通过连接前置交易的哈希值计算新的哈希值，并更新 OP\_RETURN 输出中的状态。

    // Concatinate hashes from previous aggregation txns (or leaves)
    // and compute new hash. Store this new hash in the state OP_RETURN
    // output.
    const newHash = hash256(prevTx0.hashData + prevTx1.hashData)
    const stateOut = GeneralUtils.getStateOutput(newHash)
    

**重入攻击防范**：强制执行严格的输出脚本和金额，以防止未经授权的修改或双花。

    // Sum up aggregated amounts and construct contract output.
    const contractOut = GeneralUtils.getContractOutput(
      depositData0.amount + depositData1.amount,
      prevTx0.outputContractSPK
    )
    
    // Recurse. Send to aggregator with updated hash.
    const outputs = contractOut + stateOut
    assert(
      sha256(outputs) == shPreimage.hashOutputs
    )
    

一旦存款被聚合，它们必须合并到主桥契约中。这一过程由「最终确认（finalize）」方法处理，其步骤包括：

*   **验证前置交易**：与「聚合（aggregate）」方法类似，验证前置交易，以确保合并数据的完整性。
    
*   **与桥契约的集成**：通过引用桥的交易 ID 和脚本公钥，检查聚合后的存款是否正确地合并至主桥契约中。
    

存款聚合合约的完整源代码可查看 [GitHub](https://github.com/Bitcoin-Wildlife-Sanctuary/scrypt-poc-bridge/blob/main/src/contracts/depositAggregator.ts)。

取款聚合器合约
-------

**取款聚合器**（WithdrawalAggregator ）合约旨在将单个取款请求聚合成一棵默克尔树，与存款聚合器处理存款的方式类似。不过，取款操作需要额外的认证，以确保只有合法的账户所有者才能从其账户中提取资金。

    class WithdrawalAggregator extends SmartContract {
      @prop()
      operator: PubKey
    
      @prop()
      bridgeSPK: ByteString
    
      /**
        * Covenant used for the aggregation of withdrawal requests.
        *
        * @param operator - Public key of bridge operator.
        * @param bridgeSPK - P2TR script of the bridge state covenant. Includes length prefix!
        */
      constructor(operator: PubKey, bridgeSPK: ByteString) {
          super(...arguments)
          this.operator = operator
          this.bridgeSPK = bridgeSPK
      }
    
      @method()
      public aggregate(
          shPreimage: SHPreimage,
          isPrevTxLeaf:boolean,
          sigOperator: Sig,
          prevTx0: AggregatorTransaction,
          prevTx1: AggregatorTransaction,
          // Additional parameters...
      ) {
          // Validation and aggregation logic...
      }
    
      @method()
      public finalize(
          shPreimage: SHPreimage,
          sigOperator: Sig,
          prevTx: AggregatorTransaction,
          ancestorTx0: AggregatorTransaction,
          ancestorTx1: AggregatorTransaction,
          bridgeTxId: Sha256,
          fundingPrevout: ByteString
      ) {
          // Validation logic...
      }
    }
    

取款聚合器的核心功能封装在 「聚合（aggregate）」方法中，该方法执行以下步骤：

    // Check sighash preimage.
    const s = SigHashUtils.checkSHPreimage(shPreimage)
    assert(this.checkSig(s, SigHashUtils.Gx))
    
    // Check operator signature.
    assert(this.checkSig(sigOperator, this.operator))
    

**构建和验证前置交易 ID**：该过程验证已聚合的前置交易是否有效并正确引用。

    // Construct previous transaction IDs.
    const prevTxId0 = AggregatorUtils.getTxId(prevTx0, isPrevTxLeaf)
    const prevTxId1 = AggregatorUtils.getTxId(prevTx1, isPrevTxLeaf)
    
    // Verify that the previous transactions are unlocked by the current transaction.
    const hashPrevouts = AggregatorUtils.getHashPrevouts(
      prevTxId0,
      prevTxId1,
      fundingPrevout
    )
    assert(hashPrevouts == shPreimage.hashPrevouts)
    

**所有权证明验证**：验证所有权证明交易确保只有合法的所有者才能从账户中提取资金。

*   **所有权证明交易**：一种证明控制取款地址的交易。合约检查取款请求中的地址是否与所有权证明交易中的地址匹配。
    

    if (isPrevTxLeaf) {
      // Construct ownership proof transaction IDs.
      const ownershipProofTxId0 = WithdrawalAggregator.getOwnershipProofTxId(ownProofTx0)
      const ownershipProofTxId1 = WithdrawalAggregator.getOwnershipProofTxId(ownProofTx1)
    
      // Check that the leaf transactions unlock the ownership proof transactions.
      assert(ownershipProofTxId0 + toByteString('0000000000ffffffff') == prevTx0.inputContract0)
      assert(ownershipProofTxId1 + toByteString('0000000000ffffffff') == prevTx1.inputContract0)
    
      // Verify that the withdrawal addresses match the addresses in the ownership proof transactions.
      assert(withdrawalData0.address == ownProofTx0.outputAddrP2WPKH)
      assert(withdrawalData1.address == ownProofTx1.outputAddrP2WPKH)
    }
    

**通过「祖先交易」进行创世检查**：与存款聚合器类似，合约通过验证祖先交易执行归纳检查。这确保了交易历史的完整性，并防止操作员插入未经授权的取款请求。

    if (!isPrevTxLeaf) {
      // Construct ancestor transaction IDs.
      const ancestorTxId0 = AggregatorUtils.getTxId(ancestorTx0, isAncestorLeaf)
      const ancestorTxId1 = AggregatorUtils.getTxId(ancestorTx1, isAncestorLeaf)
      const ancestorTxId2 = AggregatorUtils.getTxId(ancestorTx2, isAncestorLeaf)
      const ancestorTxId3 = AggregatorUtils.getTxId(ancestorTx3, isAncestorLeaf)
    
      // Verify that previous transactions unlock the ancestor transactions.
      assert(prevTx0.inputContract0 == ancestorTxId0 + toByteString('0000000000ffffffff'))
      assert(prevTx0.inputContract1 == ancestorTxId1 + toByteString('0000000000ffffffff'))
      assert(prevTx1.inputContract0 == ancestorTxId2 + toByteString('0000000000ffffffff'))
      assert(prevTx1.inputContract1 == ancestorTxId3 + toByteString('0000000000ffffffff'))
    
      // Ensure that the ancestor transactions have the same contract SPK.
      assert(prevTx0.outputContractSPK == ancestorTx0.outputContractSPK)
      assert(prevTx0.outputContractSPK == ancestorTx1.outputContractSPK)
      assert(prevTx0.outputContractSPK == ancestorTx2.outputContractSPK)
      assert(prevTx0.outputContractSPK == ancestorTx3.outputContractSPK)
    }
    

**金额验证和总金额计算**：该方法通过将取款请求或之前的聚合进行金额相加，计算出要提取的总金额。

    let sumAmt = 0n
    if (isPrevTxLeaf) {
      sumAmt = withdrawalData0.amount + withdrawalData1.amount
    } else {
      sumAmt = aggregationData0.sumAmt + aggregationData1.sumAmt
    }
    

**状态更新**：计算一个新的哈希值，其中包含前置交易的哈希值和取款金额的总和。这个哈希值存储在 OP\_RETURN 输出中，以更新状态。

    // Create new aggregation data.
    const newAggregationData: AggregationData = {
      prevH0: prevTx0.hashData,
      prevH1: prevTx1.hashData,
      sumAmt
    }
    const newHash = WithdrawalAggregator.hashAggregationData(newAggregationData)
    const stateOut = GeneralUtils.getStateOutput(newHash)
    

**重入攻击防范和输出强制**：确保严格定义输出，以防止未经授权的修改或重入攻击。

    // Construct contract output with the minimum dust amount.
    const contractOut = GeneralUtils.getContractOutput(
      546n,
      prevTx0.outputContractSPK
    )
    
    // Ensure outputs match the expected format.
    const outputs = contractOut + stateOut
    assert(
      sha256(outputs) == shPreimage.hashOutputs,
    )
    

取款聚合合约的完整源代码可查看 [GitHub](https://github.com/Bitcoin-Wildlife-Sanctuary/scrypt-poc-bridge/blob/main/src/contracts/withdrawalAggregator.ts)。

桥合约
---

**桥**（Bridge）合约是我们系统的核心组件，是维护桥状态的主要契约，包括以默克尔树组织的账户及其余额。其通过与我们之前讨论的聚合器合约集成，处理存款和取款操作。

    class Bridge extends SmartContract {
      @prop()
      operator: PubKey
    
      @prop()
      expanderSPK: ByteString
    
      constructor(
          operator: PubKey,
          expanderSPK: ByteString
      ) {
          super(...arguments)
          this.operator = operator
          this.expanderSPK = expanderSPK
      }
    
      @method()
      public deposit(
          shPreimage: SHPreimage,
          sigOperator: Sig,
          prevTx: BridgeTransaction,           // Previous bridge update transaction.
          aggregatorTx: AggregatorTransaction, // Root aggregator transaction.
          fundingPrevout: ByteString,
    
          deposits: FixedArray<DepositData, typeof MAX_NODES_AGGREGATED>,
          accounts: FixedArray<AccountData, typeof MAX_NODES_AGGREGATED>,
    
          depositProofs: FixedArray<MerkleProof, typeof MAX_NODES_AGGREGATED>,
          accountProofs: FixedArray<MerkleProof, typeof MAX_NODES_AGGREGATED>
      ) {
          // Method implementation...
      }
    
      @method()
      public withdrawal(
          shPreimage: SHPreimage,
          sigOperator: Sig,
          prevTx: BridgeTransaction,           // Previous bridge update transaction.
          aggregatorTx: AggregatorTransaction, // Root aggregator transaction.
          fundingPrevout: ByteString,
    
          withdrawals: FixedArray<WithdrawalData, typeof MAX_NODES_AGGREGATED>,
          accounts: FixedArray<AccountData, typeof MAX_NODES_AGGREGATED>,
    
          intermediateSumsArr: FixedArray<IntermediateValues, typeof MAX_NODES_AGGREGATED>,
    
          withdrawalProofs: FixedArray<MerkleProof, typeof MAX_NODES_AGGREGATED>,
          accountProofs: FixedArray<MerkleProof, typeof MAX_NODES_AGGREGATED>
      ) {
          // Method implementation...
      }
    
    }
    

合约构建函数有两个参数：

*   \*operator：\*桥操作员的公钥，该操作员有权更新桥状态。
    
*   _expanderSPK_\*：_取款扩展器_\*（WithdrawalExpander）合约的脚本公钥（SPK），在取款过程中使用。
    

_存款_方法负责处理聚合的存款交易，并相应更新账户余额。

    @method()
    public deposit(
      shPreimage: SHPreimage,
      sigOperator: Sig,
      prevTx: BridgeTransaction,           // Previous bridge update transaction.
      aggregatorTx: AggregatorTransaction, // Root aggregator transaction.
      fundingPrevout: ByteString,
    
      deposits: FixedArray<DepositData, typeof MAX_NODES_AGGREGATED>,
      accounts: FixedArray<AccountData, typeof MAX_NODES_AGGREGATED>,
    
      depositProofs: FixedArray<MerkleProof, typeof MAX_NODES_AGGREGATED>,
      accountProofs: FixedArray<MerkleProof, typeof MAX_NODES_AGGREGATED>
    ) {
      // Common validation steps...
      // (Same as in previous contracts: sighash preimage check, operator signature verification, prevouts verification)
    
      // Ensure this method is called from the first input.
      assert(shPreimage.inputNumber == toByteString('00000000'))
    
      // Verify that the second input unlocks the correct aggregator script.
      assert(prevTx.depositAggregatorSPK == aggregatorTx.outputContractSPK)
    
      // Process deposits and update accounts.
      let accountsRootNew: Sha256 = prevTx.accountsRoot
      let totalAmtDeposited = 0n
      for (let i = 0; i < MAX_NODES_AGGREGATED; i++) {
          const deposit = deposits[i]
          if (deposit.address != toByteString('')) {
              accountsRootNew = this.applyDeposit(
                  deposits[i],
                  depositProofs[i],
                  aggregatorTx.hashData,
                  accounts[i],
                  accountProofs[i],
                  accountsRootNew
              )
          }
          totalAmtDeposited += deposit.amount
      }
    
      // Update the bridge state and outputs.
      // (Compute new state hash, construct contract output, enforce outputs)
    }
    

_存款_方法执行的步骤包括：

**处理存款并更新账户**：

*   遍历存款，并使用「应用存款（applyDeposit）」方法将每笔存款应用到相应的账户。
    

**更新桥状态和输出**：

*   处理存款后，计算新的账户默克尔根。
    
*   创建新的状态哈希值，表示更新后的桥状态。
    
*   构建合约输出，将总存款金额添加至桥余额中。
    
*   保证输出符合预期格式，以维护数据完整性。
    

取款方法处理聚合的取款交易，更新账户余额，并通过取款扩展器准备分配的资金。

    @method()
    public withdrawal(
      shPreimage: SHPreimage,
      sigOperator: Sig,
      prevTx: BridgeTransaction,           // Previous bridge update transaction.
      aggregatorTx: AggregatorTransaction, // Root aggregator transaction.
      fundingPrevout: ByteString,
    
      withdrawals: FixedArray<WithdrawalData, typeof MAX_NODES_AGGREGATED>,
      accounts: FixedArray<AccountData, typeof MAX_NODES_AGGREGATED>,
    
      intermediateSumsArr: FixedArray<IntermediateValues, typeof MAX_NODES_AGGREGATED>,
    
      withdrawalProofs: FixedArray<MerkleProof, typeof MAX_NODES_AGGREGATED>,
      accountProofs: FixedArray<MerkleProof, typeof MAX_NODES_AGGREGATED>
    ) {
      // Common validation steps...
      // (Same as in previous contracts: sighash preimage check, operator signature verification, prevouts verification)
    
      // Ensure this method is called from the first input.
      assert(shPreimage.inputNumber == toByteString('00000000'))
    
      // Verify that the second input unlocks the correct aggregator script.
      assert(prevTx.withdrawalAggregatorSPK == aggregatorTx.outputContractSPK)
    
      // Process withdrawals and update accounts.
      let accountsRootNew: Sha256 = prevTx.accountsRoot
      let totalAmtWithdrawn = 0n
      for (let i = 0; i < MAX_NODES_AGGREGATED; i++) {
          const withdrawal = withdrawals[i]
          if (withdrawal.address != toByteString('')) {
              accountsRootNew = this.applyWithdrawal(
                  withdrawal,
                  withdrawalProofs[i],
                  intermediateSumsArr[i],
                  aggregatorTx.hashData,
                  accounts[i],
                  accountProofs[i],
                  accountsRootNew
              )
          }
          totalAmtWithdrawn += withdrawal.amount
      }
    
      // Update the bridge state and outputs.
      // (Compute new state hash, construct contract output, create expander output, enforce outputs)
    }
    

_取款_方法执行的步骤包括：

**处理取款请求并更新账户**：

*   遍历取款请求，并使用「应用存款（applyDeposit）」方法将每笔取款应用到相应的账户。
    

**更新桥状态和输出**：

*   处理取款后，计算新的账户默克尔根。
    
*   创建新的状态哈希值，表示更新后的桥状态。
    
*   构建合约输出，将总取款金额从桥余额中扣除。
    
*   为取款扩展器合约创建一个扩展输出，其中包含总取款金额。
    
*   保证输出符合预期格式，以维护数据完整性。
    

完整源代码可查看 [GitHub](https://github.com/Bitcoin-Wildlife-Sanctuary/scrypt-poc-bridge/blob/main/src/contracts/bridge.ts)。

取款扩展器合约
-------

**取款扩展器**（WithdrawalExpander）是我们桥系统的最终组件，负责根据用户的取款请求将聚合的取款金额分发回各个用户。它逆转了取款聚合器执行的聚合过程，将聚合的取款数据扩展回单个用户的支付。

    class WithdrawalExpander extends SmartContract {
      @prop()
      operator: PubKey
    
      constructor(
          operator: PubKey
      ) {
          super(...arguments)
          this.operator = operator
      }
    
      @method()
      public expand(
          shPreimage: SHPreimage,
          sigOperator: Sig,
          // Additional parameters...
      ) {
          // Expansion logic...
      }
    
    }
    

取款扩展器的核心功能封装在「扩展（expand）」方法中。该方法接受聚合的取款数据，并通过递归的方式将其扩展为单独的取款交易，向用户支付相应的金额。

**扩展到叶节点**：如果方法扩展到叶节点（单个取款），它会验证取款数据，并构建直接支付到用户地址的输出。

    if (isExpandingLeaves) {
      // If expanding to leaves, verify the withdrawal data.
      if (isExpandingPrevTxFirstOutput) {
          const hashWithdrawalData = WithdrawalAggregator.hashWithdrawalData(withdrawalData0)
          assert(hashWithdrawalData == prevAggregationData.prevH0)
          hashOutputs = sha256(
              WithdrawalExpander.getP2WPKHOut(
                  GeneralUtils.padAmt(withdrawalData0.amount),
                  withdrawalData0.address
              )
          )
      } else {
          const hashWithdrawalData = WithdrawalAggregator.hashWithdrawalData(withdrawalData1)
          assert(hashWithdrawalData == prevAggregationData.prevH1)
          hashOutputs = sha256(
              WithdrawalExpander.getP2WPKHOut(
                  GeneralUtils.padAmt(withdrawalData1.amount),
                  withdrawalData1.address
              )
          )
      }
    }
    

**进一步扩展**：如果该方法尚未达到叶节点层，则会继续扩展，将聚合数据分成两个分支，并创建输出，以供进一步扩展的交易消费。

    else {
      // Verify current aggregation data matches previous aggregation data.
      const hashCurrentAggregationData = WithdrawalAggregator.hashAggregationData(currentAggregationData)
      if (isPrevTxBridge) {
          assert(hashCurrentAggregationData == prevTxBridge.expanderRoot)
      } else if (isExpandingPrevTxFirstOutput) {
          assert(hashCurrentAggregationData == prevAggregationData.prevH0)
      } else {
          assert(hashCurrentAggregationData == prevAggregationData.prevH1)
      }
    
      // Prepare outputs for the next level of expansion.
      let outAmt0 = 0n
      let outAmt1 = 0n
      if (isLastAggregationLevel) {
          const hashWithdrawalData0 = WithdrawalAggregator.hashWithdrawalData(withdrawalData0)
          const hashWithdrawalData1 = WithdrawalAggregator.hashWithdrawalData(withdrawalData1)
          assert(hashWithdrawalData0 == currentAggregationData.prevH0)
          assert(hashWithdrawalData1 == currentAggregationData.prevH1)
          outAmt0 = withdrawalData0.amount
          outAmt1 = withdrawalData1.amount
      } else {
          const hashNextAggregationData0 = WithdrawalAggregator.hashAggregationData(nextAggregationData0)
          const hashNextAggregationData1 = WithdrawalAggregator.hashAggregationData(nextAggregationData1)
          assert(hashNextAggregationData0 == currentAggregationData.prevH0)
          assert(hashNextAggregationData1 == currentAggregationData.prevH1)
          outAmt0 = nextAggregationData0.sumAmt
          outAmt1 = nextAggregationData1.sumAmt
      }
    
      // Construct outputs for further expansion.
      let expanderSPK = prevTxExpander.contractSPK
      if (isPrevTxBridge) {
          expanderSPK = prevTxBridge.expanderSPK
      }
    
      hashOutputs = sha256(
          GeneralUtils.getContractOutput(outAmt0, expanderSPK) +
          GeneralUtils.getContractOutput(outAmt1, expanderSPK) +
          GeneralUtils.getStateOutput(hashCurrentAggregationData)
      )
    }
    

结论
==

在这个概念验证实现中，我们使用 [sCrypt](https://docs.scrypt.io/) 嵌入式领域专属语言（DSL）开发了一个基于 OP\_CAT 支持的比特币的桥契约。该桥利用递归契约和默克尔树有效地批量处理存款和取款请求，同时保持用户账户的完整性和安全性。通过设计和实施**存款聚合器**（DepositAggregator）、**取款聚合器**（WithdrawalAggregator）、**桥**（Bridge）**和取款扩展器**（WithdrawalExpander）这四种智能合约，我们提供了一种在比特币上管理有状态交互的方法，促进了与像 Starknet 这样的二层网络的互操作性。这项工作为构建生产级桥提供了技术基础，可能增强比特币生态系统中的可扩展性和功能性。

所有代码实现以及端到端测试均[可在 GitHub 上获取](https://github.com/Bitcoin-Wildlife-Sanctuary/scrypt-poc-bridge)。

---

*Originally published on [Starknet 中文](https://paragraph.com/@starknet-zh/op-cat-2)*
