Skip to content

feat(unpacker): add Merkle commitment unpacker circuit - #612

Draft
ilyalesokhin-starkware wants to merge 1 commit into
graphite-base/612from
ilya/unpacker
Draft

ilyalesokhin-starkware wants to merge 1 commit into
graphite-base/612from
ilya/unpacker

Conversation

@ilyalesokhin-starkware

Copy link
Copy Markdown
Contributor

Add the tree and unpacker modules to the circuits-unpacker crate: a
generic BinaryTree { Leaf, Node } describing an arbitrary, possibly
unbalanced full binary tree, and verify_merkle_commitment, proving a
Merkle root commits to an entire ordered leaf set via a fixed,
depth-independent topology (wiring-by-multiset).

The tree shape lives entirely in the witness: the gate structure is a
fixed L leaf slots + (L-1) node slots, with cost O(L) regardless of n or
depth. Each node resolves its output with a copy-up rule
(out = is_zero(right) ? left : hash_node(left, right)), and a single
strict multiset identity (permute_hash_values) ties every produced hash
to its single consumption.

To make the emitted circuit — gates and variable wiring — byte-identical
for every tree of a given capacity, verify_merkle_commitment is the sole
allocator: it takes leaves as concrete HashValue and guesses every
value internally, including the padding zeros (guessed, not the canonical
zero constant, so nothing constant-folds away). It returns a
VerifiedMerkleTree { root, leaves }, holding the resolved root out of the
multiset so the caller binds it to the claimed commitment.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

@cursor

cursor Bot commented Jun 28, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
New in-circuit Merkle and multiset-permutation logic is soundness-critical, but behavior is heavily tested (including negative cases and fixed-topology guarantees).

Overview
Adds a depth-independent Merkle unpacker in circuits-unpacker: verify_merkle_commitment proves a claimed root commits to a full leaf set (each leaf is a circuit_hash + subtree_hash pair) for an arbitrary witness BinaryTree, with fixed topology L leaf slots + L−1 node slots at capacity L (power of two). Wiring uses a copy-up rule when the right child is an all-zero padding node, Blake2s over four child hashes for real nodes, and a multiset balance via permute_units on whole 16-word nodes; the resolved root is returned for the caller to bind.

permute_hash_values is generalized to permute_units (any equal-width u32 word units); hash permutation remains a thin wrapper. Crate docs, tree module, and dependencies (circuits-stark-verifier, stwo, test rstest) are updated accordingly.

Tests cover valid/invalid commitments (wrong root, shape, tampered leaves, slack capacity, caterpillars, duplicates) and assert the emitted circuit is identical across leaf count n and tree shape at fixed capacity.

Reviewed by Cursor Bugbot for commit 888b225. Bugbot is set up for automated code reviews on this repo. Configure here.

ilyalesokhin-starkware commented Jun 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@reviewable-StarkWare

Copy link
Copy Markdown
Collaborator

This change is Reviewable

@ilyalesokhin-starkware
ilyalesokhin-starkware changed the base branch from ilya/permuate-hash-values to graphite-base/612 June 28, 2026 13:01
Add the tree and unpacker modules to the circuits-unpacker crate: a
generic BinaryTree<T> { Leaf, Node } describing an arbitrary, possibly
unbalanced full binary tree, and verify_merkle_commitment, proving a
Merkle root commits to an entire ordered leaf set via a fixed,
depth-independent topology (wiring-by-multiset).

The tree shape lives entirely in the witness: the gate structure is a
fixed L leaf slots + (L-1) node slots, with cost O(L) regardless of n or
depth. Each node resolves its output with a copy-up rule
(out = is_zero(right) ? left : hash_node(left, right)), and a single
strict multiset identity (permute_hash_values) ties every produced hash
to its single consumption.

To make the emitted circuit — gates and variable wiring — byte-identical
for every tree of a given capacity, verify_merkle_commitment is the sole
allocator: it takes leaves as concrete HashValue<QM31> and guesses every
value internally, including the padding zeros (guessed, not the canonical
zero constant, so nothing constant-folds away). It returns a
VerifiedMerkleTree { root, leaves }, holding the resolved root out of the
multiset so the caller binds it to the claimed commitment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants