test(math): pin stream_bytes byte parity with as_bytes#832
Open
MauroToscano wants to merge 1 commit into
Open
Conversation
#828 rewired the Merkle backends and DefaultTranscript::append_field_element from as_bytes()/to_bytes_be() to the new AsBytes::stream_bytes. That swap is only sound because stream_bytes emits byte-identical output, but nothing enforces it: stream_bytes is a defaulted trait method, so an override that disagrees with as_bytes compiles cleanly and then silently changes Merkle roots and Fiat-Shamir challenges rather than failing a test. Adds a parity sweep over both Goldilocks fields, including non-canonical u64s around the modulus where a raw-value override would diverge from as_bytes, and pins the ext3 byte layout that math-cuda's keccak_leaves_ext3 kernel mirrors component-by-component. The GPU parity tests only run on a CUDA host, so this keeps the CPU half of that contract honest on a GPU-less runner. Documents the invariant on the trait method itself, where an implementor would look for it. Also corrects the stream_bytes comment in extensions_goldilocks.rs: it described cutting three sink calls down to one, but as_bytes() already produced a single 24-byte Vec and a single Digest::update. The per-element allocation, not the call count, is what the override removes.
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.
Summary
Follow-up to #828, which rewired the two Merkle backends and
DefaultTranscript::append_field_elementfromas_bytes()/to_bytes_be()to the newAsBytes::stream_bytes. That rewire is only sound becausestream_bytesemits byte-identical output — it does, I checked both fields — but nothing enforces it, and #828 shipped without tests.stream_bytesis a defaulted trait method. An override that disagrees withas_bytescompiles cleanly and then silently changes every Merkle root and Fiat-Shamir challenge that flows through it. The failure mode is proofs that no longer verify against previously committed roots, discovered at integration time — not a red test.crypto/math/tests/stream_bytes_parity.rs: assertsstream_bytes == as_bytes == to_bytes_beforFieldElement<GoldilocksField>andFieldElement<Degree3GoldilocksExtensionField>over ~2k pseudorandom values plus edge cases. The edge cases deliberately include non-canonicalu64s (p,p+1,u64::MAX), since both encodings reduce throughcanonical_u64and that is exactly where a raw-value override would diverge.math-cuda'skeccak_leaves_ext3kernel mirrors (canonical u64 per component, big-endian, order 0,1,2 — matchingwrite_bytes_be). The GPU parity tests inmath-cuda/tests/keccak_leaves.rsonly run on a CUDA host, andgpu-testswas SKIPPED on perf(crypto): stream field-element bytes into hashers and transcript #828, so this keeps the CPU half of that contract honest on a GPU-less runner.stream_bytes.traits.rs, where an implementor would actually look for it.Comment fix
The
stream_bytescomment inextensions_goldilocks.rssaid it makes "one sink call over a stack buffer instead of three (one per limb)" and that "call count — not indirection — is the cost being cut". The previous code never made three calls:as_bytes()returned a single 24-byteVec, which became a singleDigest::update. The three-call version was a considered alternative, never what shipped. What the override actually removes is the per-element heap allocation. Corrected, and noted that the layout is load-bearing for CPU/GPU leaf parity.No behavior change — docs and tests only.
Test plan
cargo test -p math --test stream_bytes_parity— 5/5stream_bytesto little-endian fails 1 test; swapping the ext3 component order inwrite_bytes_befails 3, including the GPU-contract test.cargo test -p math -p crypto -p stark --lib— 418/418make lint— clean (exit 0; all four clippy configs including the cuda-feature pass)