Skip to content

Functions

DISCLAIMER // NFA // DYOR

This analysis is based on observations of the contract behavior. We are not smart contract security experts. This document aims to explain what the contract appears to do based on the code. It should not be considered a comprehensive security audit or financial advice. Always verify critical information independently and consult with blockchain security professionals for important decisions.

⊙ generated by robots | curated by humans

METADATA
Contract Address 0x82eb1246...90cC37 (etherscan)
Network Ethereum Mainnet
Analysis Date 2026-06-02

Function Selectors

SELECTOR FUNCTION SIGNATURE CATEGORY
0x1de26e16 deposit(bytes32,uint256) User
0xa4b135c1 batchDeposit(bytes32[],uint256[]) User
0x5ea99d00 withdrawFromBurn(bytes,bytes,uint256,bytes32[],uint256) User
0xee59a615 getPoolRoot(bytes32) View
0xe5f99a21 isKnownDepositRoot(bytes32,bytes32) View
0x999d3653 isBurnNullifierSpent(bytes32,bytes32) View
0x8d284655 getPoolDenomination(bytes32) View
0xde5ddc78 getNextLeafIndex(bytes32) View
0x7b3be964 getRootAccumulator(bytes32) View
0x50b7e101 everKnownRoot(bytes32,bytes32) View
0x097d5787 poolVerifiers(bytes32) View
0xdd31ce13 rootAccumulator(bytes32) View
0x69883b4e poolIds(uint256) View
0xe8295588 zeros(uint256) View
0xad7a672f totalBalance() View
0x099eef6c HEADER_RELAY() View
0x481cc7ba BURN_VERIFIER() View
0x82bfefc8 TOKEN() View
0xb158e39a UNIT_SCALE() View
0x9ceb21ae ASSET_ID() View
0xd47bbbb3 CONFIRMATION_DEPTH() View
0xa8e50978 NETWORK_TAG() View
0xa7a06b46 TACIT_DECIMALS() View
0x2a894a73 TREE_LEVELS() View
0x082febf0 MAX_LEAVES() View
0x584aaf55 POOL_TREE_RESERVE() View

A receive() fallback also exists but has no selector; it reverts unconditionally.


Summary

CATEGORY COUNT
Total Functions 26 (+ receive)
User Functions 3
Admin Functions 0
View Functions 23

User Functions

Function: deposit(bytes32 commitment, uint256 denomination)

Locks one fixed denomination of ETH and appends the user's note commitment as a leaf in that denomination's Poseidon Merkle tree. This is the "into Tacit" direction — once the leaf is on-chain, the depositor can mint the matching confidential asset on Bitcoin.

ATTRIBUTE VALUE
Selector 0x1de26e16
Parameters commitment (Poseidon note commitment), denomination (must equal a configured pool denom, in wei)
Access External, payable, nonReentrant, anyone
FLAG OBSERVATION
msg.value must equal denomination exactly for the ETH path; mismatch reverts
Duplicate commitments are rejected per pool (DuplicateCommitment)
A second capacity gate queries the SP1 verifier's lastProvenPoolIndex and refuses deposits within POOL_TREE_RESERVE (1024) leaves of capacity, to avoid locking ETH the Bitcoin-side tree can no longer accept
commitment must be a valid bn254 field element (< _FIELD_SIZE)
CONDITION REQUIREMENT
Pool exists & matches p.denomination == denomination (else InvalidDenomination)
Deposit tree not full p.nextLeafIndex < MAX_LEAVES (else MerkleTreeFull)
SP1 pool tree headroom lastProvenPoolIndex + 1024 < MAX_LEAVES (else MerkleTreeFull)
Valid field element uint256(commitment) < _FIELD_SIZE (else InvalidFieldElement)
Unique commitment !p.commitments[commitment] (else DuplicateCommitment)
Exact ETH msg.value == denomination (else InvalidDenomination)
STEP ACTION
1 Derive pid = keccak256(ASSET_ID, denomination) and load the pool
2 Run the precondition checks above
3 Mark commitment used; walk 20 tree levels updating filledSubtrees, compute new root
4 Store new root, set everKnownRoot[pid][root] = true, fold root into rootAccumulator (running SHA-256)
5 Increment nextLeafIndex and totalBalance += denomination
6 Emit Deposit(pid, commitment, leafIndex, timestamp)
7 Pull ETH: require msg.value == denomination
VARIABLE CHANGE
_pools[pid].commitments[commitment] set true
_pools[pid].filledSubtrees[*] updated along the insertion path
_pools[pid].currentRoot new Merkle root
everKnownRoot[pid][root] set true
rootAccumulator[pid] sha256(prev ‖ newRoot)
_pools[pid].nextLeafIndex +1
totalBalance +denomination
CONDITION REVERT
Wrong / unknown denomination InvalidDenomination()
Deposit tree or pool tree full MerkleTreeFull()
Commitment ≥ field size InvalidFieldElement()
Commitment already used DuplicateCommitment()
msg.value != denomination InvalidDenomination()
function deposit(bytes32 commitment, uint256 denomination) external payable nonReentrant {
    _insertDeposit(commitment, denomination);
    if (TOKEN == address(0)) {
        if (msg.value != denomination) revert InvalidDenomination();
    } else {
        if (msg.value != 0) revert InvalidDenomination();
        _safeTransferFromExact(TOKEN, msg.sender, denomination);
    }
}

Function: batchDeposit(bytes32[] commitments, uint256[] denominations)

Splits a larger amount across several denominations in one transaction — e.g. deposit 0.11 ETH as one 0.1 + one 0.01 leaf. Each (commitment, denomination) pair is inserted independently; for ETH, msg.value must equal the sum of all denominations.

ATTRIBUTE VALUE
Selector 0xa4b135c1
Parameters commitments[], denominations[] (equal length)
Access External, payable, nonReentrant, anyone
FLAG OBSERVATION
Each entry runs the full _insertDeposit precondition set independently
msg.value must equal the summed denominations exactly
Splitting across denominations is a common mixer technique for breaking round-number amount correlation
CONDITION REQUIREMENT
Equal-length arrays commitments.length == denominations.length
Per-entry checks every entry satisfies _insertDeposit preconditions
Exact ETH msg.value == Σ denominations (else InvalidDenomination)
STEP ACTION
1 Loop each pair, call _insertDeposit, accumulate totalValue
2 Require msg.value == totalValue (ETH path)
function batchDeposit(bytes32[] calldata commitments, uint256[] calldata denominations) external payable nonReentrant {
    require(commitments.length == denominations.length);
    uint256 totalValue;
    for (uint256 i; i < commitments.length; ++i) {
        _insertDeposit(commitments[i], denominations[i]);
        totalValue += denominations[i];
    }
    if (TOKEN == address(0)) {
        if (msg.value != totalValue) revert InvalidDenomination();
    } else {
        if (msg.value != 0) revert InvalidDenomination();
        _safeTransferFromExact(TOKEN, msg.sender, totalValue);
    }
}

Function: withdrawFromBurn(bytes rawBtcTx, bytes proofHeaders, uint256 burnBlockHeight, bytes32[] txMerkleProof, uint256 txIndex)

Releases one denomination of escrowed ETH by proving that a corresponding T_BRIDGE_BURN happened on Bitcoin. The single call performs Bitcoin Simplified Payment Verification (SPV), Tacit envelope parsing, an SP1 accepted-burn lookup, and a Groth16 proof check before transferring funds. This is the "out of Tacit" direction and the core of the bridge.

ATTRIBUTE VALUE
Selector 0x5ea99d00
Parameters rawBtcTx (full Bitcoin tx), proofHeaders (header chain to relay tip), burnBlockHeight, txMerkleProof (tx inclusion proof), txIndex
Access External, nonReentrant, anyone (recipient is fixed inside the burn envelope, not by msg.sender)
FLAG OBSERVATION
Funds go to the recipient embedded in the burn envelope, never to msg.sender — so anyone can relay another user's withdrawal
Six independent checks must all pass before funds move — enumerated in the Preconditions table below
bindHash mixes block.chainid and address(this), preventing a burn proof from being replayed against a different chain or a different bridge deployment
The G2 element b is half-swapped on read (snarkjs (c0,c1) → precompile (c1,c0)); an ordering the code comments flag as test-pinned
No current-root equality check — an accepted burn claim remains redeemable even after the Bitcoin pool root advances. Intended (see code comment), but widens the validity window
CONDITION REQUIREMENT
Bitcoin inclusion verifyBlock confirms the header chain reaches the relay tip with ≥ 6 confirmations, and the tx Merkle proof matches the block's merkle root
Envelope shape env.length ≥ 281, env[0] == 0x61, env[1] == NETWORK_TAG, asset id matches ASSET_ID
Pool match envelope denom × UNIT_SCALE maps to a configured pool
bindHash recomputed bindHash equals the envelope's bindHash field
SP1 acceptance verifier.isAcceptedBurn(burnClaimId) is true (else UnprovenRoot)
Groth16 BURN_VERIFIER.verifyProof(a,b,c,pub) is true (else InvalidGroth16Proof)
Nullifier unused !p.burnNullifiers[nullifierHash] (else NullifierAlreadySpent)
Solvency totalBalance >= p.denomination (else InsufficientBalance)
STEP ACTION
1 Recompute Bitcoin txid from rawBtcTx; call HEADER_RELAY.verifyBlock for the block merkle root; verify tx inclusion via txMerkleProof
2 Extract the Taproot script-path envelope (_extractTaprootEnvelope), stripping the 6-byte "TACIT"‖0x01 frame
3 _validateBurn: parse fields, recompute bindHash, derive burnClaimId = sha256(nullifier ‖ denom ‖ poolRoot ‖ recipient ‖ bindHash), check isAcceptedBurn, then _verifyProof (Groth16)
4 Check nullifier unused, recipient non-zero, totalBalance sufficient
5 Mark nullifier spent, totalBalance -= denomination, emit Withdrawal
6 Transfer the denomination to recipient (forceSafeTransferETH for ETH)
VARIABLE CHANGE
_pools[pid].burnNullifiers[nullifierHash] set true
totalBalance -denomination
CONDITION REVERT
Header chain / inclusion invalid InvalidBurnProof() (or relay errors: ChainNotAnchored, InvalidPoW, …)
Wrong opcode / network / asset InvalidBurnProof / InvalidNetworkTag / InvalidAssetId
bindHash mismatch InvalidBurnProof()
Burn not SP1-accepted UnprovenRoot()
Groth16 fails / wrong size InvalidGroth16Proof()
Public input ≥ field size InvalidFieldElement()
Nullifier reused NullifierAlreadySpent()
Backing insufficient InsufficientBalance()
function withdrawFromBurn(
    bytes calldata rawBtcTx, bytes calldata proofHeaders, uint256 burnBlockHeight,
    bytes32[] calldata txMerkleProof, uint256 txIndex
) external nonReentrant {
    {
        bytes32 txid = _computeTxid(rawBtcTx);
        bytes32 blockMR = HEADER_RELAY.verifyBlock(proofHeaders, burnBlockHeight, CONFIRMATION_DEPTH);
        if (!_verifyTxInclusion(txid, blockMR, txMerkleProof, txIndex)) revert InvalidBurnProof();
    }
    bytes memory env = _extractTaprootEnvelope(rawBtcTx);
    (bytes32 nullifierHash,, address payable recipient, bytes32 pid) = _validateBurn(env);

    Pool storage p = _pools[pid];
    if (p.burnNullifiers[nullifierHash]) revert NullifierAlreadySpent();
    if (recipient == address(0)) revert InvalidBurnProof();
    if (totalBalance < p.denomination) revert InsufficientBalance();

    p.burnNullifiers[nullifierHash] = true;
    totalBalance -= p.denomination;
    emit Withdrawal(pid, nullifierHash, recipient, p.denomination);
    if (TOKEN == address(0)) {
        SafeTransferLib.forceSafeTransferETH(recipient, p.denomination);
    } else {
        SafeTransferLib.safeTransfer(TOKEN, recipient, p.denomination);
    }
}

Function: receive()

The plain-ETH fallback reverts. ETH can only enter the contract through deposit / batchDeposit, never by a bare transfer. This blocks accidental sends and keeps totalBalance accounting exact.

ATTRIBUTE VALUE
Selector none (receive)
Access External, payable
CONDITION REVERT
Any bare ETH transfer InvalidDenomination()
receive() external payable { revert InvalidDenomination(); }

View Functions

The contract exposes its immutable configuration and per-pool state read-only. None modify state; all are callable by anyone.

Function: pool & tree readers

Per-pool and per-tree inspection used by the dApp/indexer to build deposits and withdrawals.

FUNCTION RETURNS
getPoolRoot(pid) current Merkle root of a pool
isKnownDepositRoot(pid, root) whether root was ever a pool root (everKnownRoot)
isBurnNullifierSpent(pid, n) whether burn nullifier n is consumed
getPoolDenomination(pid) the pool's wei denomination
getNextLeafIndex(pid) number of leaves inserted so far
getRootAccumulator(pid) running SHA-256 over all historical roots (read by the SP1 verifier)
everKnownRoot(pid, root) / rootAccumulator(pid) / poolVerifiers(pid) raw mapping getters
poolIds(i) / zeros(i) the 8 pool ids and the 20 precomputed empty-subtree hashes
totalBalance() aggregate escrowed ETH (solvency gate)
FLAG OBSERVATION
getRootAccumulator is the hook the SP1 verifier uses to tie its proof's deposit view to the actual on-chain deposits

Function: immutable configuration getters

Compile-time constants and constructor-pinned immutables.

FUNCTION VALUE (THIS DEPLOYMENT)
HEADER_RELAY() 0x36358295...8Ca6D0 (etherscan)
BURN_VERIFIER() 0x031b22ba...7Ab2ca (etherscan)
TOKEN() address(0) (native ETH)
UNIT_SCALE() 10^10
ASSET_ID() 0x3cba71e1...126f34 (tacitscan)
CONFIRMATION_DEPTH() 6
NETWORK_TAG() 0 (Bitcoin mainnet)
TACIT_DECIMALS() 8
TREE_LEVELS() 20
MAX_LEAVES() 1048576
POOL_TREE_RESERVE() 1024