原文:Implementing a Bridge Covenant on OP_CAT-Enabled Bitcoin: A Proof of Concept
翻譯及校對:Starknet 中文社區
📑 轉載請註明出處 🕹️
精選速覽
-
深入探討在比特幣上構建 demo 橋契約,為 Starknet 的生產級橋奠定基礎
-
實施存取款聚合器、橋和取款擴展器四種智能合約
-
利用遞歸契約和默克爾樹有效地批量處理存款和取款請求,同時保持用戶賬戶的完整性和安全性
引言
本文,我們深入探討了 sCrypt 如何在比特幣上構建一個 demo 橋契約。該概念驗證實現旨在為 Starknet 二層(L2)網絡的生產級橋奠定基礎。該橋的設計允許將多個存款或取款請求交易合併為一個根交易,並將其併入主橋契約中,更新其狀態,該狀態由一組以默克爾樹組織的賬戶組成。
由於橋契約腳本非常複雜,我們在 sCrypt 利用了 sCrypt 專屬領域語言(DSL)來編寫其實現方式。
概覽
該橋由一個遞歸契約比特幣腳本構成。在這裡,「契約」意味著鎖定腳本能夠對支出交易施加條件,而「遞歸」則意味著上述規則足夠強大,可以在鏈上實現持久的邏輯和狀態(這是任何鏈上智能合約的基本要求)。
該腳本存在於一系列交易中,每筆交易都對後續交易結構施加約束,而後續交易解鎖當前交易的輸出。每當一筆新交易添加到這條鏈中時,就代表了橋狀態的更新。因此,這條鏈的末端保存著當前的橋狀態。
契約狀態 — 具體來說,就是其哈希值 — 存儲在一個不可消耗的 OP_RETURN 輸出中。雖然我們不會花費這個 UTXO,但在執行契約腳本時可以檢查其數據。具體來說,狀態保存了包含賬戶數據的默克爾樹的根哈希值,如下所示:
該默克爾樹保存了一組固定賬戶槽的數據。葉節點包含各自賬戶數據的哈希值,其中包括地址和餘額。為了表示空的賬戶槽,這些槽被標記為零字節。
每次橋的更新都會導致賬戶樹發生變化。為了方便這種更新,我們依賴於默克爾證明,其驗證在比特幣腳本中非常高效。更新主要包含兩個步驟。首先,我們驗證一個默克爾證明,以證明證明默克爾樹包含了特定賬戶的當前狀態。然後,在計算該賬戶的新狀態後,我們使用前述默克爾證明中的相同輔助節點來推導出新的根哈希值。
更新可以是存款,也可以是取款。橋可在單筆交易中執行這些更新的批量操作。
存款
我們的目標是讓用戶能夠獨立提交存款或取款請求。為此,用戶分別創建交易,分別支付給存款或取款聚合契約。該契約將這些請求彙總成一棵默克爾樹。該樹的根哈希值可以合併到主橋契約中,主橋契約隨後處理每筆存款或取款。
在存款交易中,除了對存款數據進行哈希並構建默克爾樹之外,契約還確保鎖定在契約輸出中的存款 satoshis 按正確的方式累積至樹的根節點。聚合契約確保只有正確的鏈上智能合約才能使用這些資金。(當然,在生產環境中,我們也會允許用戶取消其存款交易)。
這種樹形結構的設計源於契約腳本構建的限制,即不允許包含過多輸入和輸出的交易。樹形結構使我們能夠擴展到潛在的任意吞吐量。
取款請求
取款請求的聚合與存款類似,但有幾處不同。首先,我們需要一種認證方法,以便用戶可以從自己的賬戶取款。這與存款不同,存款是任何人可以向任何賬戶存款,這與比特幣地址的使用方式類似。認證在聚合樹的葉節點層完成。取款請求聚合契約會檢查提款地址是否與葉交易中第一個輸入的 P2WPKH 地址匹配。
這確保了地址的所有者批准取款,因為他們已經簽署了請求取款的交易。與存款聚合相比,另一個細微的不同之處在於,我們還會將中間的累計金額進行哈希,向上傳遞到樹結構中。這是因為在擴展取款時,我們需要這些數據,稍後會詳細說明。
敏銳的讀者可能會注意到這種取款請求認證模型的潛在問題。假如操作員決定作弊,創建一個聚合樹的根交易,而聚合樹的數據是通過未經認證的虛假取款請求在本地偽造的,那該怎麼辦?我們需要一種有效的方法來驗證根交易是否來自有效的葉交易。
為了解決這個問題,我們執行了所謂的「創世檢查(genesis check)」。本質上,我們讓聚合契約檢查其前一筆交易以及前兩筆交易,即其「祖先交易」。契約驗證這些交易是否包含相同的契約腳本,並執行相同的檢查。通過這種方式,我們實現了一個歸納式的交易歷史檢查。由於前兩筆交易與當前契約一樣,執行了相同的檢查,我們可以確認這些交易的「祖先」也執行了相同的檢查,一直追溯到葉節點(即創世交易)。
當然,我們對樹的兩個分支都執行了驗證。因此,每個聚合節點交易總共檢查最多六筆交易。
取款擴展
現在讓我們進入解決方案的最後部分:取款擴展。在處理一批取款請求後,主橋契約會強制執行一個輸出,將總取款金額支付給擴展契約。我們可以將這個契約視為執行取款請求聚合契約所做操作的逆向過程。其從取款樹的根節點開始,將其擴展為兩個分支,每個分支包含應支付到該分支的相應取款金額。這個過程一直延續到取款樹的葉節點。葉交易強制執行一個簡單的支付輸出,向賬戶所有者的地址支付他們要求提取的金額。
實現方式
為了實現我們的橋契約,我們開發了四個 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 原像格式正確且屬於正在執行的交易。通過這篇文章瞭解關於 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。
取款聚合器合約
取款聚合器(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。
橋合約
橋(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。
取款擴展器合約
取款擴展器(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 嵌入式領域專屬語言(DSL)開發了一個基於 OP_CAT 支持的比特幣的橋契約。該橋利用遞歸契約和默克爾樹有效地批量處理存款和取款請求,同時保持用戶賬戶的完整性和安全性。通過設計和實施存款聚合器(DepositAggregator)、取款聚合器(WithdrawalAggregator)、橋(Bridge)和取款擴展器(WithdrawalExpander)這四種智能合約,我們提供了一種在比特幣上管理有狀態交互的方法,促進了與像 Starknet 這樣的二層網絡的互操作性。這項工作為構建生產級橋提供了技術基礎,可能增強比特幣生態系統中的可擴展性和功能性。
所有代碼實現以及端到端測試均可在 GitHub 上獲取。