Skip to content

fix: accumulate state-chain withdrawals in an MMR and bind operator proofs to the Gateway - #541

Open
KSlashh wants to merge 1 commit into
devfrom
audit/11-circuit-gateway-binding
Open

KSlashh wants to merge 1 commit into
devfrom
audit/11-circuit-gateway-binding

Conversation

@KSlashh

@KSlashh KSlashh commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

Audit items C6 and M34. Open against dev. Changes the state-chain, operator and watchtower guests: new program ids.

Bounded withdrawal history (M34). The state-chain public values carried every withdrawal since genesis as a list. Each recursive step decoded and re-encoded the whole list, and the operator circuit scanned it. The list is now a Merkle Mountain Range (the MMRGuest the header chain already uses): the public values hold its size and peaks, at most 32 hashes. Appending costs log(n) hashes per withdrawal; the operator proves one leaf with an inclusion path.

Withdrawal source binding (C6). The operator circuit only checked that its graph id appeared somewhere in the list, and the state-chain host could name any storage slot for a withdrawal. Now:

  • A leaf is sha256("bitvm/withdrawal-leaf/v1" || gateway || graph_id), so a withdrawal is a fact about one Gateway contract.

  • The state-chain circuit rejects a withdrawal that is not read from Gateway.withdrawDataMap (WITHDRAW_DATA_BASE_SLOT, slot 16, now a shared constant instead of a host literal).

  • The operator circuit takes the Gateway address and an inclusion proof, and verifies the leaf of (its Gateway, its graph id) against the state chain's accumulator.

  • The Gateway is bound into the graph constant: hash_operator_constant is v4 and hashes the Gateway address after the genesis txid. The node computes it from its own configured Gateway (GOAT_GATEWAY_CONTRACT_ADDRESS), when it builds a graph and when it validates another operator's graph. The prover cannot choose the Gateway.

Proof builder.

  • operator_proof rows record the Gateway the task was authorized for (gateway_address, resolved from the request or the single configured Gateway).

  • New table state_chain_withdrawal: every leaf the proven state chain accumulated, with its block number. The state-chain prover writes a batch's leaves before it marks the batch proven, replacing any leaves from that batch's first block on, so a re-proven batch rebuilds the tail.

  • When an operator task is prepared, the leaves before the end of its state-chain proof are replayed into an MMRHost and the graph's leaf is proven. A graph with no withdrawal from its Gateway in a proven chain that covers its withdrawal block is a permanently invalid task.

  • The operator host checks the inclusion proof against the state-chain public values before proving.

  • The operator CLI takes --gateway-address and --withdrawal-records (a JSON list of {block_number, gateway, graph_id} in accumulator order).

Not changed. The per-block storage check in the state-chain circuit still compares the slot value to 1; the commit chain and header chain are untouched. The genesis block's withdrawals are not accumulated, as before.

Deployment. Three program ids change (state chain, operator, watchtower). The state chain restarts from genesis; existing operator and watchtower proofs and graphs built with the v3 constant are not compatible. The node and the proof builder must be upgraded together. If this lands after #535 (audit/14-2-proof-task-failure), its operator_proof rebuild must carry the new gateway_address column.

Tests: MMR leaves are appended in block order and prove their Gateway (the same graph id under another Gateway is a different leaf); a withdrawal outside withdrawDataMap is rejected; the constant changes with the Gateway; a withdrawal proof built from the records verifies against the guest accumulator and a stale record list does not; stored leaves are numbered in order and rebuilt from a batch start.

The test coverage and validation notes above are from the zkm PR notes. Branch scope and diff formatting were checked locally; builds and tests were not rerun during publication.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant