Skip to content

Withdrawals

The withdrawal program proves that an ordered batch of Zeko withdrawal actions produces both a specific Ethereum sequential withdrawal accumulator and a fixed-depth withdrawal Merkle root.

Proof input

WithdrawTransitionInput contains the chain ID, bridge address, current withdraw accumulator, starting Zeko action state, and an ordered withdrawal list.

FieldMeaning
tokenZeko field encoding an Ethereum token address in its low 160 bits. Zero means native ETH.
recipientZeko field encoding the Ethereum recipient in its low 160 bits.
amountAmount expressed using the token's configured Zeko decimals.

Zeko currently supports only the native token in withdrawal actions. The SP1 program rejects every withdrawal whose token field is not zero.

Withdraw accumulator

For every withdrawal, the guest computes:

text
withdraw_leaf = keccak256(
  keccak256("ZEKO_BRIDGE_WITHDRAW_LEAF_V1"),
  chain_id,
  bridge_address,
  token,
  recipient,
  amount
)

withdraw_state_after = keccak256(
  keccak256("ZEKO_BRIDGE_WITHDRAW_STATE_V1"),
  withdraw_state_before,
  withdraw_leaf
)

It also computes and appends the matching Zeko action:

text
action = Poseidon.hashWithPrefix("Withdrawal_params - qFB3jXP*)", [
  Field(0),
  amount,
  recipient
])

The same ordered withdrawal leaves are committed into a depth-16 Keccak Merkle tree. The tree supports at most 65,536 withdrawals and pads unused leaves with bytes32(0).

text
node = keccak256(
  keccak256("ZEKO_BRIDGE_WITHDRAW_MERKLE_NODE_V1"),
  left,
  right
)

Public values

FieldMeaning
zeko_action_state_beforeZeko action state before the batch.
zeko_action_state_afterZeko action state after the batch.
ethereum_withdraw_state_beforeEthereum withdrawal accumulator before the batch.
ethereum_withdraw_state_afterEthereum withdrawal accumulator after the batch.
withdrawal_rootDepth-16 Merkle root over the same ordered withdrawal leaves.
withdraw_countNumber of withdrawals in the batch.

Accepting a transition

submitWithdrawTransition verifies the SP1 proof and requires:

  • the starting withdrawal accumulator equals currentWithdrawState
  • the final action state has not already been processed
  • both action states are checkpoints recorded by ZekoSettlement
  • the old checkpoint matches currentWithdrawActionStateIndex
  • the new checkpoint index is exactly the old index plus one

For a non-empty batch, the final withdrawal accumulator becomes a valid claim transition and the Merkle root becomes a valid claim root. The bridge stores one withdrawal batch record under the old Zeko action state bound by the SP1 proof. That record contains the Merkle root, sequential states, checkpoint index, and withdrawal count. The same Merkle root may safely appear in different action-state transitions.

Claiming a withdrawal

To claim, a caller supplies:

  • the old Zeko action state bound to the withdrawal batch
  • the clear withdrawal being claimed
  • its index in the batch
  • a fixed 16-sibling Merkle proof

The contract recomputes the leaf and verifies its Merkle proof against the root stored for that old action state. Claims no longer require the root or the full ordered withdrawal batch in calldata.

It then computes a nullifier from the old action-state index, withdrawal index, and leaf. A spent nullifier cannot be claimed again. Finally, the contract validates token and recipient field encodings, converts the Zeko amount back to Ethereum decimals, and transfers the locked ETH or ERC20 tokens.

Proof-powered settlement and bridging between Zeko and Ethereum.