fix: make Hash duplex sponge portable across pointer widths#171
Merged
mmaker merged 5 commits intoJun 10, 2026
Merged
Conversation
`Hash<D>::squeeze` and `squeeze_end` fold the squeeze block-counter and the read-length into the hash state via `usize::to_be_bytes()`. `usize` is 8 bytes on 64-bit targets and 4 bytes on 32-bit (e.g. wasm32), so the absorbed bytes — and therefore the squeezed output — differ by target. A 64-bit prover and a 32-bit verifier then derive different Fiat-Shamir challenges, breaking verification (this surfaced verifying a 64-bit-produced transcript inside a wasm32 verifier). Encode both counters as fixed-width `u64`. On 64-bit this is byte-identical to the previous output (`i as u64 == i`), so existing transcripts and the spec vectors are unchanged; only 32-bit output changes, now matching 64-bit. The permutation `DuplexSponge` is unaffected — its `usize` fields are buffer indices, never absorbed.
14c4e66 to
09b9910
Compare
…lity Add a `wasm32-wasip1` job to the PR workflow that builds and runs the test suite under wasmtime, providing a 32-bit lane. This catches pointer-width divergence in the `Hash` duplex sponge for free via the existing vector KATs (`test_shosha`, `duplexSpongeVectors.json`). - Fix arkworks-rs#171: encode the squeeze index and squeeze_end byte count as fixed-width `u64` instead of pointer-width `usize`, so a 64-bit prover and a 32-bit verifier (e.g. wasm32) derive identical outputs. Byte-identical on 64-bit. - Remove the unused Cardano `pallas` dev-dependency: it is referenced nowhere (only `ark_pallas`/`ark_vesta` are) and transitively pulls `tokio`/`socket2`, which do not compile on wasm. Drops a large transitive tree from Cargo.lock. - Add a codec KAT locking the `str` length prefix to a fixed-width LE `u32`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mmaker
approved these changes
Jun 10, 2026
mmaker
left a comment
Member
There was a problem hiding this comment.
approving, adding more regression guards
shreyas-londhe
added a commit
to shreyas-londhe/jolt
that referenced
this pull request
Jun 11, 2026
…lity 0.6.2 carries the upstream fix (arkworks-rs/spongefish#171) making the `Hash<D>` duplex sponge encode its squeeze counter at fixed `u64` width instead of native `usize`. Under 0.6.1 a 64-bit prover and a 32-bit (wasm32) verifier derived different Fiat-Shamir challenges, so the WASM Verifier E2E rejected valid proofs (UniSkipVerificationError). The change is a no-op on 64-bit, so native muldiv (host + zk) is unaffected. Verified locally: muldiv host + host,zk pass; a wasm32 verifier built against 0.6.2 verifies a 64-bit-produced fibonacci proof end-to-end (was failing on 0.6.1).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Hash<D>(theDigest-bridge duplex sponge backingBlake2b512,Blake2s256,SHA256,SHA512) folds two counters into the hash state at native pointer width:squeeze,Mode::Squeeze(i)branch —Digest::update(&mut output_hasher_prefix, i.to_be_bytes()),i: usizesqueeze_end—Digest::update(&mut squeeze_hasher, byte_count.to_be_bytes()),byte_count: usizeusize::to_be_bytes()is 8 bytes on 64-bit targets and 4 bytes on 32-bit (e.g.wasm32). The first squeeze hitsMode::Squeeze(0)→0usize.to_be_bytes()is 8 zero bytes vs 4, so the squeezed output diverges from the first byte.Impact
Any protocol where a 64-bit prover produces a transcript/NARG that a 32-bit verifier replays (in-browser/on-chain
wasm32verifiers, 32-bit embedded) derives different Fiat-Shamir challenges, so verification fails. Found verifying a 64-bit-produced transcript inside awasm32verifier: a 7-byte-absorb-then-squeeze probe diverged between native and wasm; with this patch the probe, all derived challenges, and full proof verification match native exactly.The permutation-based
DuplexSponge<P, …>(Keccak, Ascon) is not affected — itsusizefields (absorb_pos/squeeze_pos) are buffer indices and are never absorbed.Fix
Encode both counters as fixed-width
u64:On 64-bit this is byte-identical to the current output (
i as u64 == iforusize), so existing transcripts and the spec vectors (duplexSpongeVectors.json,test_shosha) are unchanged — confirmed by the existing suite passing. Only 32-bit output changes, now matching 64-bit. Since 32-bit verification is currently broken, there is no compatible 32-bit consumer to break.On testing
I deliberately did not add a unit test, because a regression here is undetectable on a 64-bit-only CI: the buggy
usizeand the fixedu64encodings are byte-identical on 64-bit (same value, same type), so no runtime assertion can distinguish them. The property is only observable on a 32-bit target.The right guard is a 32-bit CI lane (e.g.
i686-unknown-linux-gnu, orwasm32with a test runner) running the existing suite — the current multi-block-squeeze vectors (test_shosha,duplexSpongeVectors.json) already exercise theMode::Squeeze(i)counter and would catch any pointer-width divergence for free. The CI currently has no such lane (wasm32-unknown-unknownalso needs agetrandombackend for the dev-deps). Happy to add ani686test job in this PR if you'd like that guard.