Source: Starknet Chinese Community
Selected Quick FactsIn-depth discussion on building a demo bridge contract on Bitcoin to lay the foundation for Starknet's production-grade bridge
Implement four smart contracts of Deposit and Withdrawal Aggregator, Bridge and Withdrawal Expander
Utilize recursive contracts and Merkle trees to efficiently batch process deposit and withdrawal requests while maintaining the integrity of user accounts Sex and Security
IntroductionIn this article, we explore in depth sCrypt How to build a demo bridge contract on Bitcoin. This proof-of-concept implementation is designed to lay the foundation for a production-grade bridge for the Starknet Layer 2 (L2) network. The design of the bridge allows multiple deposit or withdrawal request transactions to be merged into a single root transaction and incorporated into the main bridge contract, updating its state, which consists of a set of accounts organized in a Merkle tree.
Because the bridge contract script is very complex, we at sCrypt use the sCrypt domain-specific language (DSL) to write its implementation.
OverviewThe bridge consists of a recursive contract Bitcoin script. Here, "contract" means that the locking script is able to impose conditions on the spending transaction, while "recursion" means that the above rules are strong enough to achieve persistent logic and state on the chain (this is a basic requirement for any on-chain smart contract ).
This script exists in a series of transactions, each transaction imposes constraints on the structure of subsequent transactions, and subsequent transactions unlock the output of the current transaction. Whenever a new transaction is added to this chain, it represents an update to the bridge state. Therefore, the end of this chain holds the current bridge state.
The contract state — specifically, its hash value — is stored in a non-consumable OP_RETURN output. Although we will not spend this UTXO, its data can be inspected when executing the contract script. Specifically, the state holds the root hash of the Merkle tree containing the account data, as shown below:
The Merkle The tree holds a fixed set of account slot data. Leaf nodes contain hashes of their respective account data, including addresses and balances. To represent empty account slots, these slots are marked with zero bytes.
Each bridge update will cause the account tree to change. To facilitate this update, we rely on Merkle proofs, whose verification is very efficient in Bitcoin script. The update mainly consists of two steps. First, we verify a Merkle proof to show that the Merkle tree contains the current state of a specific account. Then, after calculating the new state of the account, we use the same auxiliary nodes from the aforementioned Merkle proof to derive the new root hash.
The update can be a deposit or a withdrawal. The bridge can perform batch operations of these updates in a single transaction.
DepositsOur goal is to enable users to submit deposit or withdrawal requests independently. To do this, users create transactions that pay into the deposit or withdrawal aggregation contract respectively. The contract aggregates these requests into a Merkle tree. The root hash of this tree can be merged into the main bridge contract, which then processes every deposit or withdrawal.
In a deposit transaction, in addition to hashing the deposit data and building a Merkle tree, the contract also ensures that the lock in the contract output The deposit satoshis are accumulated in the correct way up to the root node of the tree. Aggregation contracts ensure that only the correct on-chain smart contract can use these funds. (Of course, in a production environment we will also allow users to cancel their deposit transactions).
The design of this tree structure stems from the limitations of contract script construction, which does not allow transactions that contain too many inputs and outputs. The tree structure enables us to scale to potentially arbitrary throughput.
Withdrawal RequestsAggregation of withdrawal requests is similar to deposits, but with a few differences. First, we need an authentication method so that users can withdraw money from their accounts. This is different from deposits, where anyone can deposit money into any account, similar to how Bitcoin addresses are used. Authentication is done at the leaf node level of the aggregation tree. The withdrawal request aggregation contract checks that the withdrawal address matches the first P2WPKH address entered in the leaf transaction.
This ensures that the owner of the address approves the withdrawal because they have signed the transaction requesting the withdrawal. Another subtle difference compared to deposit aggregation is that we also hash the intermediate accumulated amounts and pass them up the tree structure. This is because we need this data when scaling withdrawals, more on that later.
Astute readers may notice potential problems with this authentication model for withdrawal requests. What if an operator decides to cheat and creates a transaction at the root of an aggregation tree whose data is forged locally through unauthenticated fake withdrawal requests? We need an efficient way to verify that the root transaction comes from a valid leaf transaction.
To solve this problem, we perform a so-called "genesis check". Essentially, we have the aggregate contract check its previous transaction and the two previous transactions, its "ancestor transactions". The contract verifies that these transactions contain the same contract script and performs the same checks. In this way, we implement an inductive transaction history check. Since the first two transactions performed the same checks as the current contract, we can confirm that the "ancestors" of these transactions also performed the same checks, all the way back to the leaf node (i.e., the genesis transaction).
Of course, we performed verification on both branches of the tree. Therefore, a total of up to six transactions are checked per aggregation node transaction.
Withdrawal ExtensionNow let’s get to the final part of the solution: Withdrawal Extension. After processing a batch of withdrawal requests, the main bridge contract forces an output to pay the total withdrawal amount to the extension contract. We can think of this contract as the reverse process of what the withdrawal request aggregation contract does. It starts from the root node of the withdrawal tree and expands it into two branches, each branch containing the corresponding withdrawal amount payable to that branch. This process continues to the leaf nodes of the withdrawal tree. Leaf transactions enforce a simple payment output to the account owner's address for the amount they requested to withdraw.
ImplementationTo implement our bridge contract, we developed four sCrypt smart contracts, each handling different aspects of the system. In this section, we will briefly outline the functionality of each contract.
Deposit Aggregator Contract
The Deposit Aggregator contract aggregates individual deposits into a Merkle tree and then merges them into the main bridge contract. This aggregation enables batch deposit processing, reducing the number of transactions that need to be processed individually by the bridge. Additionally, it allows users to independently submit deposits, which are later processed by operators.
The contract construction function has two parameters:
operator: the public key of the bridge operator, which has the authority to Aggregate deposits.
bridgeSPK: The script public key (SPK) of the main bridge contract, ensuring that aggregated deposits are merged correctly.
The core functionality of the deposit aggregator is encapsulated in the "aggregate" method. This method performs the following steps:
Verify Sighash preimage and operator signature: Ensures that the transaction is authorized by the bridge operator and that the sighash preimage is properly formatted and belongs to the transaction being executed. Learn more about sighash preimage verification in this article.
Constructing and validating predecessor transaction IDs: Check that the aggregated predecessor transaction is valid and referenced correctly.
Merkle tree aggregation: Verifies that the deposit data passed as the witness hash matches the state stored in the preceding transaction.
Amount validation: Verify that the amount in the front output matches the specified deposit amount, ensuring that funds are calculated correctly in the aggregation.
State update: Calculate a new hash by concatenating the hashes of the preceding transactions and update the status in the OP_RETURN output.
Reentrancy attack prevention: Enforce strict output scripts and amounts to prevent unauthorized modification or double spending.
Once deposits are aggregated, they must be merged into the main bridge contract. This process is handled by the "finalize" method, whose steps include:
Verify preceding transactions: Similar to the "aggregate" method, verify preceding transactions to ensure the integrity of the merged data.
Integration with the bridge contract: Check that the aggregated deposits are correctly merged into the main bridge contract by referencing the bridge's transaction ID and script public key.
The complete source code of the deposit aggregation contract can be viewed on GitHub.
Withdrawal Aggregator Contract
The Withdrawal Aggregator contract is designed to aggregate individual withdrawal requests into a Merkle tree, similar to how deposit aggregators handle deposits. However, withdrawal operations require additional authentication to ensure that only legitimate account owners can withdraw funds from their accounts.
The core functionality of the withdrawal aggregator is encapsulated in the "aggregate" method, which performs the following steps:
Constructing and validating predecessor transaction IDs: This process verifies that the aggregated predecessor transaction is valid and correctly referenced.
Proof of Ownership Verification: Verifying Proof of Ownership transactions ensures that only the legitimate owner can withdraw funds from the account.
Proof-of-ownership transaction: A transaction that proves control of a withdrawal address. The contract checks that the address in the withdrawal request matches the address in the proof of ownership transaction.
Origination check via "ancestor transactions": Similar to the deposit aggregator, the contract performs an inductive check by validating ancestor transactions. This ensures the integrity of transaction history and prevents operators from inserting unauthorized withdrawal requests.
Amount verification and total amount calculation: This method calculates the total amount to be withdrawn by adding the amounts of withdrawal requests or previous aggregations.
Status update: Calculate a new hash value that contains the sum of the hash value of the preceding transaction and the withdrawal amount. This hash is stored in the OP_RETURN output to update the state.
Reentrancy attack prevention and output enforcement: Ensure output is strictly defined to prevent unauthorized modification or reentrancy attacks.
The complete source code of the withdrawal aggregation contract can be viewed on GitHub.
Bridge ContractThe Bridge contract is the core component of our system and the main contract for maintaining the status of the bridge, including accounts and their balances organized in Merkle trees. It handles deposit and withdrawal operations by integrating with the aggregator contract we discussed earlier.
The contract construction function has two parameters:
operator: the public address of the bridge operator key, this operator has the authority to update the bridge status.
expanderSPK: WithdrawalExpander) the script public key (SPK) of the contract, which is used during the withdrawal process.
The deposit method is responsible for processing aggregated deposit transactions and updating the account balance accordingly.
The steps performed by the deposit method include:
Process the deposit and update the account:
Traverse the deposit , and use the "applyDeposit" method to apply each deposit to the corresponding account.
Update bridge status and output:
After processing the deposit, calculate the new account Merkle root.
Create a new status hash representing the updated bridge status.
Construct the contract output to add the total deposit amount to the bridge balance.
Ensure that the output is in the expected format to maintain data integrity.
The withdrawal method processes aggregated withdrawal transactions, updates the account balance, and prepares the allocated funds via the withdrawal expander.
The steps performed by the withdrawal method include:
Processing the withdrawal request and updating the account:< /p>
Traverse the withdrawal requests and apply each withdrawal to the corresponding account using the "applyDeposit" method.
Update bridge status and output:
After processing a withdrawal, calculate the new account Merkle root.
Create a new status hash representing the updated bridge status.
Construct the contract output to subtract the total withdrawal amount from the bridge balance.
Create an extension output for the withdrawal expander contract that contains the total withdrawal amount.
Ensure that the output is in the expected format to maintain data integrity.
The complete source code can be viewed on GitHub.
Withdrawal Expander ContractThe Withdrawal Expander (WithdrawalExpander) is the final component of our bridge system and is responsible for distributing the aggregated withdrawal amount back to each user based on the user's withdrawal request. It reverses the aggregation process performed by the withdrawal aggregator, extending the aggregated withdrawal data back to individual user payments.
The core functionality of the withdrawal expander is encapsulated in the "expand" method. This method accepts aggregated withdrawal data and recursively expands it into individual withdrawal transactions, paying the corresponding amount to the user.
Expand to leaf nodes: If the method expands to a leaf node (single withdrawal), it validates the withdrawal data and builds an output that pays directly to the user's address.
Further expansion: If the method has not reached the leaf node level, it will continue to expand, split the aggregate data into two branches, and create outputs for further expansion of transaction consumption .
ConclusionIn this proof-of-concept implementation, we use the sCrypt embedded realmDedicated Language (DSL) has developed a bridge contract for Bitcoin based on OP_CAT support. The bridge utilizes recursive contracts and Merkle trees to efficiently batch deposit and withdrawal requests while maintaining the integrity and security of user accounts. By designing and implementing four smart contracts: Deposit Aggregator, Withdrawal Aggregator, Bridge and Withdrawal Expander, we provide a way to manage stateful interactions on Bitcoin , facilitating interoperability with layer-2 networks like Starknet. This work provides a technical foundation for building production-grade bridges, potentially enhancing scalability and functionality in the Bitcoin ecosystem.
All code implementations and end-to-end tests are available on GitHub.
[Full text ends]
Original link: https://starkware.co/blog /implementing-a-bridge-covenant-on-op-cat-bitcoin/
Mirror:https://mirror.xyz/starknet-zh.eth/zFbhQB7gfmSTV4CcTv5MRqJBUnKuwMLTNOgZ6jUgDK8