← index

Bitcoin OP_CAT Use Cases Series #5: Drivechain

An archive of delvingbitcoin.org · view original topic →

sCrypt · #1 ·

Bitcoin OP_CAT Use Cases Series #5: Drivechain

Hashrate Escrows Without BIP300

We have created a smart contract that operates similarly to the hashrate escrow mechanism in Bitcoin’s Drivechain proposal. However, instead of requiring a substantial protocol upgrade like BIP300, it leverages general smart contract functionality to establish a sidechain covenant using OP_CAT.

Drivechain

Drivechain in Bitcoin are a mechanism for implementing sidechains, which are independent blockchains pegged to Bitcoin. They allow users to transfer BTC between the Bitcoin mainchain and sidechains, enabling new features or experimental technologies without altering the mainchain. Drivechain use a two-way peg system and rely on Bitcoin miners to enforce transfers.

Peg-In: Transferring to a Sidechain

Peg-Out: Returning to the Mainchain

BIP300: Hashrate Escrow

The Drivechain proposal for Bitcoin is encapsulated in two Bitcoin Improvement Proposals: BIP300 for Hashrate Escrows and BIP301 for Blind Merged Mining. We focus on the former since involves opcode change.

In the Drivechain proposal BIP300, miner voting is a critical mechanism that governs the approval of withdrawals (peg-outs) from the sidechain back to the main Bitcoin blockchain. Transactions are not signed using cryptographic keys. Instead, they are “signed/voted” by the collective hashpower over time: hashrate-secured escrow. This process functions like a large multisignature arrangement, requiring something akin to 1500 out of 3000 “signatures,” with each mined block representing one signature.

Here’s how miner voting operates in our hashrate escrow contract:

1. Withdrawal Request Creation

A user on the sidechain burns their sidechain tokens to initiate a withdrawal request to the main Bitcoin chain.

2. Miner Voting Period

3. Voting Process

4. Consensus Threshold

Overview

Our hashrate escrow of the drivechain implementation features:

The contract includes four primary methods: lock, initWithdrawal, vote, and finishWithdrawal.

Operator-Controlled State Management

Miner Voting

Dynamic Contract State

The contract operates through the following phases:

  1. Lock: Funds are bridged into the drive chain.
  2. InitWithdrawal: Operators propose withdrawals.
  3. Vote: Miners vote to approve or reject proposals.
  4. FinishWithdrawal: Approved withdrawals are executed or state resets for unapproved proposals.

Transaction diagram depicting a call to the lock() method

Transaction diagram depicting a call to the vote() method

Implementation

State Representation

The contract state includes:

These values are hashed and enforced to be stored within an unspendable OP_RETURN output by the contract, using recursive covenants.

Locking Extra Funds

The lock method validates and bridges BTC to the covenant. It ensures the bridged amount exceeds 10,000 satoshis and verifies state consistency.

@method()
public lock(
    shPreimage: SHPreimage,
    prevTx: DriveChainTx,
    amtBridged: bigint,
    spentAmountsSuffix: ByteString,
    feePrevout: ByteString
) {
    this.checkContractContext(shPreimage, prevTx, feePrevout)

    // Check passed spent amounts info is valid.
    assert(
        shPreimage.hashSpentAmounts ==
        sha256(
            DriveChain.padAmt(prevTx.contractOutputAmount) +
            DriveChain.padAmt(amtBridged) +
            spentAmountsSuffix
        )
    )

    // Check bridged amount is sufficient.
    assert(amtBridged > 10000n)

    // Ensure state transition is reflected in outputs.
    const hashOutputs = sha256(
        DriveChain.padAmt(prevTx.contractOutputAmount + amtBridged) +
        prevTx.contractOutputSPK +
        DriveChain.getStateOut(prevTx.stateHash)
    )
    assert(hashOutputs == shPreimage.hashOutputs)
}

Initializing a Withdrawal

Operators propose withdrawals by providing signatures and specifying payout details. The contract validates these signatures, ensures sufficient time has passed since the last proposal, and updates the state.

@method()
public initWithdrawal(
    shPreimage: SHPreimage,
    prevTx: DriveChainTx,
    operatorSigs: FixedArray<Sig, 3>,
    operatorPubKeys: FixedArray<PubKey, 5>,
    currentState: DriveChainState,
    nLockTimeInt: bigint,
    payoutAmt: bigint,
    payoutSPK: ByteString,
    feePrevout: ByteString
) {
    this.checkContractContext(shPreimage, prevTx, feePrevout);

    // Verify the provided state matches the stored hash
    assert(DriveChain.getStateHash(currentState) == prevTx.stateHash);

    // Validate operator signatures and keys
    assert(this.checkMultiSig(operatorSigs, operatorPubKeys));

    // Ensure withdrawal period requirements are met
    assert(nLockTimeInt >= currentState.startPeriod + 2088n);

    // Update state with new payout details
    const newState: DriveChainState = {
        startPeriod: nLockTimeInt,
        voteCnt: 0n,
        payoutAmt: payoutAmt,
        payoutSPK: payoutSPK,
    };

    // Ensure state transition is reflected in outputs
    const hashOutputs = sha256(
        DriveChain.padAmt(prevTx.contractOutputAmount) +
        prevTx.contractOutputSPK +
        DriveChain.getStateOut(DriveChain.getStateHash(newState))
    );
    assert(hashOutputs == shPreimage.hashOutputs);
}

Voting on Withdrawal

Miners vote by using a coinbase transaction. Their votes affect the voteCnt positively or negatively, and the contract enforces single-use votes.

@method()
public vote(
    shPreimage: SHPreimage,
    prevTx: DriveChainTx,
    coinbaseTx: CoinbaseTx,
    currentState: DriveChainState,
    agree: boolean,
    feePrevout: ByteString,
    changeOut: ByteString
) {
    this.checkContractContextVote(
        shPreimage,
        prevTx,
        coinbaseTx,
        feePrevout
    )

    // Check passed state.
    assert(DriveChain.getStateHash(currentState) == prevTx.stateHash)

    // Check block height in coinbase tx is greater than start period.
    assert(coinbaseTx.blockHeight > currentState.startPeriod)

    // Adjust vote count.
    // Implements 66% agree-voting threshold.
    if (agree) {
        currentState.voteCnt += 1n
    } else {
        currentState.voteCnt -= 2n
    }

    // Ensure state transition is reflected in outputs.
    const hashOutputs = sha256(
        DriveChain.padAmt(prevTx.contractOutputAmount) +
        prevTx.contractOutputSPK +
        DriveChain.getStateOut(DriveChain.getStateHash(currentState)) +
        changeOut
    )
    assert(hashOutputs == shPreimage.hashOutputs)
}

Finalizing a Withdrawal

Once the voting period ends, withdrawals are finalized if the voteCnt reaches the threshold. The payout amount is sent, and the state is reset for future proposals.

@method()
public finishWithdrawal(
    shPreimage: SHPreimage,
    prevTx: DriveChainTx,
    nLockTimeInt: bigint,
    currentState: DriveChainState,
    feePrevout: ByteString
) {
    this.checkContractContext(shPreimage, prevTx, feePrevout)
    
    // Check passed state.
    assert(DriveChain.getStateHash(currentState) == prevTx.stateHash)
    // Check passed nLockTime int value.
    assert(shPreimage.nLockTime == DriveChain.padTime(nLockTimeInt))
    // This method can only be called at least 2016 blocks after startPeriod.
    assert(nLockTimeInt >= currentState.startPeriod + 2016n)
    // Check votes were made.
    assert(currentState.voteCnt > 0n)
    // Construct payout output.
    const payoutOut =
        DriveChain.padAmt(currentState.payoutAmt) + currentState.payoutSPK
    // Update state; reset vote count, payout amt and set new start preiod.
    const newState: DriveChainState = {
        startPeriod: nLockTimeInt,
        voteCnt: 0n,
        payoutAmt: 0n,
        payoutSPK: toByteString(''),
    }
    // Ensure state transition is reflected in outputs.
    const hashOutputs = sha256(
        DriveChain.padAmt(prevTx.contractOutputAmount) +
        prevTx.contractOutputSPK +
        DriveChain.getStateOut(DriveChain.getStateHash(newState)) +
        payoutOut
    )
    assert(hashOutputs == shPreimage.hashOutputs)
}

The full code of the smart contract can be found on GitHub.

Acknowledgement

Our implementation draws inspiration from the SHA-gate contract designed for Bitcoin Cash. Our work adapts the mechanics of the SHA-gate contract to BTC with OP_CAT re-enabled.

/dev/fd0 · #2 ·

Thank you for writing this post. I think this is the best use case for OP_CAT.

light · #3 ·

Why is this m-of-n group of operators needed, rather than allowing any user to initiate a withdrawal?