Skip to content

feat(circuits): add variable-length BLAKE3 gadget and wire it into the example#1743

Open
gopikannappan wants to merge 3 commits into
binius-zk:mainfrom
gopikannappan:blake3-variable-length
Open

feat(circuits): add variable-length BLAKE3 gadget and wire it into the example#1743
gopikannappan wants to merge 3 commits into
binius-zk:mainfrom
gopikannappan:blake3-variable-length

Conversation

@gopikannappan

Copy link
Copy Markdown

What. Adds binius_circuits::blake3::blake3_variable, a BLAKE3 hash gadget whose message length is a runtime witness bounded by a compile-time capacity — the BLAKE3 analog of Sha256::new. Wires it into the blake3 example so --max-message-len is accepted (previously rejected), and removes the example's single-chunk restriction.

Note one thing I found while scoping this: the blake3_fixed TODO about multi-chunk support is stale — blake3_fixed/blake3_tree_root already handle multi-chunk messages. The genuine remaining gap is a runtime-variable-length gadget (the Sha256::new analog), which is what this adds.

Why. BLAKE3 only had blake3_fixed (compile-time length). Applications that verify variable-length inputs bounded by a max had no BLAKE3 option, unlike SHA-256/SHA-512/Keccak.

How. For a length-len message the digest is fixed by the final-block index bd, its byte length fbl, and the content; all blocks before bd are full, so the chunk/tree shape follows from bd. Following the SHA-256 variable circuit's approach: mask the message tail to zero against len_bytes; build one candidate digest per possible final-block index (reusing the exact blake3_fixed chunk/tree machinery, sharing full interior chunks, with only the final block's block_len as a runtime wire); then multi_wire_multiplex the candidate at bd. len_bytes <= max_len_bytes is enforced so the multiplex selector stays in range.

Testing. New unit tests compare the circuit digest against the blake3 crate across empty, single-block, single-chunk, multi-chunk, and non-aligned capacities, plus an overlong-length rejection test. cargo test -p binius-circuits -p binius-examples green; fmt/clippy clean; example proves+verifies end-to-end for variable messages.

Known limitation (flagging honestly). This builds max_blocks candidate digests, each rebuilding its chunk + parent tree, so circuit size is roughly quadratic in max_blocks. I chose the clearly-correct construction over the optimal one for a first pass; a production version likely wants an incremental CV-stack (à la BLAKE3 streaming) or shared sub-tree nodes across candidates to reach ~O(max_blocks). Happy to follow up with that if you'd prefer it before merge.

Also flagging unrelated pre-existing drift I noticed: blake3 check-snapshot fails on the default fixed circuit on pristine HEAD too (verified via git stash) — the committed snapshot looks stale relative to current main. This PR leaves the default fixed-length circuit shape untouched, so I did not re-bless it.

Authored with Claude Code.

…e example

Adds binius_circuits::blake3::blake3_variable, a BLAKE3 hash gadget whose
message length is a runtime witness bounded by a compile-time capacity — the
BLAKE3 analog of Sha256::new. Wires it into the blake3 example so
--max-message-len is accepted (previously rejected), removing the single-chunk
restriction.

For a length-len message the digest is fixed by the final-block index bd, its
byte length fbl, and the content; all blocks before bd are full, so the
chunk/tree shape follows from bd. Following the SHA-256 variable circuit's
approach: mask the message tail to zero against len_bytes; build one candidate
digest per possible final-block index (reusing the exact blake3_fixed chunk/tree
machinery, sharing full interior chunks, with only the final block's block_len as
a runtime wire); then multi_wire_multiplex the candidate at bd. len_bytes <=
max_len_bytes is enforced so the multiplex selector stays in range.

New unit tests compare the circuit digest against the blake3 crate across empty,
single-block, single-chunk, multi-chunk, and non-aligned capacities, plus an
overlong-length rejection test. Example proves+verifies end-to-end for variable
messages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gopikannappan
gopikannappan requested a review from jimpo as a code owner July 8, 2026 14:25
@jimpo

jimpo commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Awesome, thanks! Will review soon

gopikannappan and others added 2 commits July 10, 2026 10:11
… + add negative tests

blake3_variable hashes the prover-claimed length: it enforces
digest == blake3(message[..len_bytes]) and len_bytes <= max_len_bytes, but does
not constrain the len_bytes value itself. With a free len_bytes and an
internally-consumed digest, a prover can substitute the digest of any prefix.
This mirrors the keccak len_bytes contract documented in binius-zk#1758.

- document the len_bytes caller obligation on blake3_variable (safe patterns:
  pin to a public input / derived wire, or use blake3_fixed for constant length)
- add variable_unconstrained_len_admits_prefix_hash: honest-length control,
  the prefix-hash characterization, and a positive-binding control showing
  message[..len_bytes] is bound (full-message digest rejected under a short len)

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