Skip to content

feat(transcript): positional, word-aligned Jolt transcript (J-1 to J-22, R9) - #2140

Draft
quangvdao wants to merge 13 commits into
transcript/j7-sponge-portfrom
transcript/j9-positional-word-aligned
Draft

quangvdao wants to merge 13 commits into
transcript/j7-sponge-portfrom
transcript/j9-positional-word-aligned

Conversation

@quangvdao

@quangvdao quangvdao commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Jolt's half of the positional, word-aligned transcript: spec rows J-1 to J-22, rules R1 to R9, the Jolt rows of §6, in the §8 commit order. Every absorb and squeeze on a Jolt path is a whole number of 8-byte words, and nothing is absorbed that the verifier can derive from the statement, the schedule or the message's position. The wire format does not change.

Stacked on transcript/j7-sponge-port (13e149195). Versions, one bump for the stack: a16z/jolt-transcript/v2 (32-byte tag), jolt-akita/group-batch/v5, jolt-akita/batch/v2. Keccak stays the default sponge.

Commits

# Commit Rows
1 47bc90099 codec and tags J-3, J-4, J-5, and J-1/J-2 in setup.rs (see below)
2 95256bfea nonce and check J-6
3 b08ca3cc3 widths J-7, J-8, R5 short-scalar mask
4 dc9d16962 statement and session tags J-1, J-2, J-11 at the call sites
5 43c4719be label sites J-9, J-10 (σ/ν check), J-12 to J-22
6 d486b3be2 Keccak digests R9
7 86e5ed7ce known-answer regeneration inventories, seeds, fuzz bundles, ZK fixture cache keys
8 03cd79146 one padded absorb in append_values R6 (review)
9 02b08e30c ProverTranscript::send UFCS footgun (review)
10 49d192e6f BlindFold witness-opening absorb dropped prover/verifier symmetry (review)
11 d15c3d137 one shared encoding for compressed sumcheck rounds R6 (review)
12 5061178f7 padding-test comment narrowed test comment (review)
13 0f41e8d56 BlindFold prover encodes compressed rounds through append_compressed_round R6, one owner (review)

What changes

Row Site Before (J7) After
J-1 PROTOCOL_ID a16z/jolt-transcript/v1, 64 bytes a16z/jolt-transcript/v2, zero-padded to 32 bytes
J-2 Instance::encode length + session, length + statement 32-byte session tag, then u64 length + statement + padding
J-3 BytesMsg absorbed u64 length + body absorbed length + body + zero padding to a word; the NARG form is unchanged (length + body)
J-4 Label, LabelWithCount, U64Word 32-byte words deleted; u64 is the word type (see notes)
J-5 append_labeled, append_values label or label + count; one absorb per value append_labeled deleted; append_values takes no label and absorbs the concatenated encodings, zero-padded to a word, as one message (R6)
J-6 grind and check LEB128 absorbed; check ⌈bits/8⌉ bytes LEB128 on the wire, one u64 LE word absorbed; check reads the low bits of one 8-byte squeeze
J-7 BN254 challenge width 52 to 54 bytes per element 56 bytes per element
J-8 Fp32 pools (len + k) · 4 candidate count rounded up to even
R5 short scalars ⌈b/8⌉ = 17 bytes, one-byte mask 24 bytes; bytes at index ≥ ⌈b/8⌉ zeroed, then the top partial byte masked
J-9 Dory adapter label + count words around every payload byte strings: length word + body + padding; group, serde and field payloads bare
J-10 Dory short widths by label (beta 2, alpha 4, d 3) unchanged, 24-byte squeeze; verifier rejects any proof with (nu, sigma) ≠ (⌊n/2⌋, ⌈n/2⌉)
J-11 Jolt statement 19 × (label + 32-byte word), labelled digest, inputs, outputs 19 bare words, the advice presence word, the bare 32-byte digest, inputs and outputs as length + body + padding
J-12 bound-count sites in openings, poly, sumcheck, verifier-derive, stage 8, hyperkzg, spartan, blindfold, kernels label + values values only
J-13 spartan-clear-v1, hyperkzg-binary-bn254-v1, BlindFold mid-protocol version labels removed; relation_digest, policy_id, setup_id absorbed as 32 bare bytes
J-14 Jolt-to-Akita bridge session jolt-akita/precommitted-group-batch/v4 (38 bytes), jolt-akita/batch jolt-akita/group-batch/v5, jolt-akita/batch/v2, each a 32-byte tag, then the 16-byte challenge (48 bytes)
J-15 Akita setup binding labels, "akita/fp128", flavor string flavor word (0 dense, 1 one-hot), four bare words, chunk-profile words only for one-hot, two bare digests
J-16 append_batch_statement labels removed
J-17 AkitaCommitment::encode labels and BytesMsgs flavor word, bare digest, 4 words, backend bytes with the length word (kept, O-3)
J-18 AkitaHidingCommitment::encode label + count + BytesMsg length + body + padding
J-19 grouped statement labels, role strings, index and presence words bare row digest, group count word, per group a role code word (0 main, 1 untrusted advice, 2 trusted advice, 3 field_inc, 4 bytecode, 5 program image), commitment, point, evaluations. Unknown or indexed roles are rejected with OpeningsError
J-20 verifier commitments append_labeled per commitment bare; presence comes from the J-11 word
J-21 sumcheck rounds label + count + coefficients one count word + coefficients
J-22 ram_val_check_gamma, spartan-outer zero constant absorbs removed
R9 program-preprocessing, Spartan relation, lattice packing and strategy digests Blake2b-256 Keccak-256 (sha3 0.12, the hash jolt-inlines-keccak256 is tested against). Prefixes and inputs unchanged; the opt-in Blake2b sponge leaves them on Keccak. blake2 dropped from jolt-verifier, jolt-spartan-verifier, jolt-claims

Notes for reviewers

  • u64 instead of a U64Word type (J-4). spongefish 0.8 already encodes u64 as its 8 little-endian bytes, which is exactly the word R1 asks for. A new wrapper would only forward to that impl, so every count, flag and configuration value is absorbed as a plain u64.
  • NargMessage and send. BytesMsg has two encodings: padded in the sponge, unpadded on the wire (NargMessage::write_narg). spongefish's inherent ProverState::prover_message writes the sponge encoding to the NARG, so on a concrete ProverState it shadowed a trait method of the same name and wrote the padded form; the verifier then rejected (a completeness bug, never a soundness one, and the setup.rs doc example hit it). The trait method is now ProverTranscript::send, which nothing shadows; every caller and the doc example use it, and cargo test --doc -p jolt-transcript passes.
  • Intermediate commits do not build downstream. Commit 1 changes prover_transcript, Instance and BytesMsg, so crates above jolt-transcript compile again only at commit 5. J-1 and J-2 land in commit 1 because protocol_tag and the new Instance are part of the codec; commit 4 updates their call sites. verifier.rs is in commit 4 for J-11 but also carries the J-13 and J-20 removals in the same functions.
  • J-10 lands in commit 5. The R5 mask and the 24-byte squeeze are in commit 3; the Dory adapter's use of them and the σ/ν check (check_matrix_shape, tested by non_canonical_matrix_split_rejected in both modes) are in commit 5 with the other Dory changes.
  • Akita presence word. J-20 asks the akita path to read the same presence the statement binds. Bit 0 of the presence word is computed on every build from proof.untrusted_advice_commitment.is_some() (CheckedInputs), and absorb_akita_commitments absorbs exactly that Option, so the two cannot disagree. The stage-4 schedule keeps reading the same field.
  • append_values is one absorb (commit 8). For every production width (BN254 Fr/Fq, Fp64, Fp128, their extensions) and Dory's 32-, 64- and 384-byte compressed groups the encodings are whole words, so the Keccak and Blake2b streams are byte-identical to the old per-value absorbs: proof sizes and permutation counts below are unchanged by it, and clear-Dory absorb calls fall from 20290 to 909 per verify. A group of 4-byte Fp32 or 1-byte F8 values is now padded to a word. Poseidon binds each absorb call's length, so its transcript for a multi-value append_values changes (fewer permutations); it is not a default sponge.
  • BlindFold (commit 10). The verifier absorbed the folded witness opening after the inner sumcheck; the prover never did. Only check_eof followed, so no challenge depended on it. The verifier absorb is gone, which saves 7 permutations in Dory ZK.
  • ZK fixture cache keys renamed. ZK verifier fixtures are cached in the temp dir under their name; a stale J7 proof there fails verification instead of regenerating. The names now end in -word-aligned.

Test-diff audit (against 13e149195)

No assertion is loosened. Every changed expectation follows from a spec rule.

Test Change Rule
jolt-transcript codec.rs: bytes_msg_is_length_prefixed → bytes_msg_is_length_prefixed_and_word_padded; new bytes_msg_narg_form_has_no_padding_and_round_trips asserts length + body + zeros to a word, and the unpadded NARG round trip J-3, R6
codec.rs: label_words_pad_and_pack → protocol_tags_pad_to_four_words; U64Word big-endian assertions removed with the type tag is 32 bytes, zero-padded, over-long names rejected at compile time J-4, R2
setup.rs: new protocol_id_is_the_padded_v2_tag, instance_encoding_is_the_tag_then_the_statement_as_bytes (replace the 64-byte id tests) exact bytes of the v2 tag and of the instance J-1, J-2
tests/common/mod.rs: test_label_framing, test_label_too_long → test_protocol_tag_too_long label API is gone; tag length still bounded J-4, J-5
pow.rs: new check_reads_the_low_bits_of_one_le_word; pow_tests.rs nonce helper absorbs a word check is the low bits of one LE u64 J-6, R4
transcript.rs: new short_scalars_squeeze_words_and_keep_only_the_low_bits all-ones tape for each short width decodes below 2^b, and bytes past ⌈b/8⌉ do not affect the value R5
challenge_tests.rs: BN254 width assertions 52/53 → 56; new pooled_messages_are_whole_words; known-answer vectors for Prime128OffsetA7F7, Prime64Offset59, FpExt4<Prime32Offset99>, BN254 Fr, F128 and F8 regenerated every vector moves with the v2 tag (J-1); Fr also with J-7, the Fp32 extension with J-8, F8 with the R5 word padding
keccak_tests.rs, blake2b_tests.rs, poseidon_tests.rs challenge vectors regenerated J-1, J-7
jolt-dory transcript.rs: four framing tests renamed and rewritten (append_bytes_absorbs_length_word_payload_and_padding, append_field_absorbs_the_little_endian_scalar, append_group_absorbs_the_bare_compressed_point, append_serde_absorbs_the_bare_compressed_value); caller_labels_do_not_reach_the_transcript kept each still asserts exact equality with a hand-built transcript and inequality with the merged/wrong-count variant J-9
jolt-dory/tests/challenge_bursts.rs kept as on J7, tag call sites only §3.1
jolt-dory/tests/commit_open_verify.rs: new non_canonical_matrix_split_rejected splits (1, 4) and (0, 5) rejected, clear and ZK J-10
jolt-sumcheck tests.rs: clear_round_verifier_with_label_absorbs_label, …_no_label_absorbs_a_sumcheck_round, …_no_label_binds_the_coefficient_count → clear_round_absorbs_count_word_then_coefficients, clear_round_binds_the_coefficient_count; committed_output_claims_absorb_length_and_order → …_absorb_commitments_in_order exact layout: one count word, then the coefficients; count still bound; committed output count is fixed by the verifier J-21, J-12
jolt-sumcheck/tests/roundtrip.rs: labeled_round_verifier_roundtrip → full_round_verifier_roundtrip same round trip, no label J-21
ram_val_check.rs: draw_challenges_absorbs_domain_separator_then_draws_gamma → draw_challenges_draws_gamma_with_no_absorb asserts the draw has no absorb before it J-22
stage4/outputs.rs draw-events test was: an absorb appears among the draw events; now: every draw event is a challenge (stricter) J-22
jolt-verifier verifier.rs: statement_binds_each_field_the_verifier_reads rewritten for the positional layout; new presence-word checks (1, 2, 3); validate_inputs_rejects_trace_length_below_pcs_floor takes the presence flag on every build each statement field still flips the statement bytes J-11
jolt-akita native_batching.rs: new group_role_codes_are_distinct_and_unknown_roles_reject, bridge_tags_are_distinct_from_each_other_and_the_old_tags role table 0 to 5, unknown and indexed roles rejected; new tags differ from each other and from both J7 tags J-14, J-19, O-6
preprocessing.rs FULL_PROGRAM_DIGEST, COMMITTED_PROGRAM_DIGEST, all four feature configurations regenerated R9
fs_inventory/absorb-sites.inventory, challenge-sites.inventory re-blessed: labelled absorb sites become bare; challenge ordinals shift where a label absorb before them was removed (verify_outer_folded_r1cs, reduce_claims, SpartanKey::begin, stage-8 challenge_powers); RamValCheck::draw_challenges::challenge#1 goes with its separator J-9, J-12 to J-22
Dory deser_commitment seeds, verifier fuzz bundles (6) regenerated transcript change (all rows)
challenge_tests.rs: new append_values_is_unchanged_for_whole_word_encodings, append_values_pads_an_odd_count_of_four_byte_values one padded absorb equals per-value absorbs for 8-, 16-, 32-byte fields and 32/64/384 group bytes; three Fp32 values absorb as 12 bytes + 4 zeros, so the next word aligns R6
jolt-dory transcript.rs test helper bare absorbs its bytes one by one instead of through append_values, so it stays an unframed, unpadded reference R6
jolt-transcript tests, jolt-eval transcript_symmetry.rs: ProverTranscript::prover_message(&mut p, m) → p.send(m); jolt-eval run_check gains the Sync bound the trait impl needs rename only UFCS fix
jolt-blindfold real_prover.rs: new prover_and_verifier_transcripts_agree_after_blindfold one challenge drawn on each side after BlindFold must match symmetry
fs_inventory/absorb-sites.inventory (commits 8, 10) append_values site reads self.absorb(&WordAligned(bytes)); verify_inner_folded_r1cs::append_vector_opening#3 removed R6, symmetry
jolt-sumcheck tests.rs: new compressed_round_absorbs_like_the_verifier_for_four_byte_and_bn254_fields the prover's CompressedRoundPoly, the verifier's encoding and a hand-built layout (count word, then c0, c2, c3 zero-padded to a word) give the same next challenge, over Prime32Offset99 (4-byte) and Fr R6
challenge_tests.rs append_values_pads_an_odd_count_of_four_byte_values (commit 12) comment only: padding alone leaves the squeeze unchanged in this fixture, not in general —
fs_inventory/absorb-sites.inventory, challenge-sites.inventory (commit 11) the per-coefficient CompressedRoundPoly absorbs and the verifier's two absorbs become calls to append_compressed_round, which holds the count word and the one append_values; verify_compressed::challenge#2 → #1 R6
everything else prover_transcript(b"...") → prover_transcript(&protocol_tag(b"...")) J-1, R2

Proof size (same seed, bincode)

Mode Case J7 13e149195 (bytes) This PR (bytes) Joint opening proof, J7 → here
Dory clear muldiv 64802 64802 19061 → 19061
Dory clear advice 65381 65381 19061 → 19061
Dory ZK muldiv 66726 66726 21429 → 21429
Dory ZK advice 67113 67113 21429 → 21429
Akita muldiv 91973 91970 74891 → 74890
Akita advice 98155 98121 80746 → 80712

Sizes are re-measured at 5061178f7 and match 86e5ed7ce exactly in every row. Dory proofs are byte-identical in size in both modes, as expected: the wire format does not change. The Akita differences are not a format change. Akita codes the terminal tail of its opening proof with Golomb-Rice (akita-params/src/golomb_rice.rs), so its length depends on the challenge values, and every challenge changes with the v2 transcript. In the advice case all 34 bytes are in the joint opening proof.

Muldiv, per component (J7 → here): the joint opening proof is 74891 → 74890 (the Golomb-Rice tail above), and the stages are 11944 → 11942. Every other field is the same size: commitments 296, claims 4822, each of the ten sumcheck proofs, and the small config fields. The 2 stage bytes are all in pow_nonces, 16 → 14. The nonces are bincode varints: a value below 251 takes 1 byte, a value up to 65535 takes 3. Grinding searches from nonce 0, so each nonce depends on the transcript state at its site. At J7 the stage-6a nonce is 419 (3 bytes); here it is 73 (1 byte). The other nine nonces are below 251 in both builds, and stage 8 has none. Base nonces: 5, 0, 1, 48, 19, 23, 4, 419, 3, 15; here: 43, 0, 1, 25, 10, 9, 42, 73, 4, 75. So the muldiv change is 1 byte of Golomb-Rice tail plus 2 bytes of nonce varint, and neither is a format change.

Keccak permutations (one recorded honest verify, rate 136)

Counted from a trace of every absorb, squeeze, challenge draw, short scalar and grind in ScriptedTranscript, replayed through a rate-136 sponge model. This includes the short scalars, grinds and verifier-side prover messages that J7's published counts missed, so the J7 column is re-measured rather than copied.

Mode Case J7 This PR Delta Absorbed bytes, J7 → here Squeezed bytes, J7 → here
Dory clear muldiv 732 634 −98 (−13.4%) 87976 → 68472 15711 → 16992
Dory clear advice 745 645 −100 (−13.4%) 89512 → 69624 15919 → 17216
Dory ZK muldiv 936 888 −48 (−5.1%) 105176 → 91608 17958 → 19400
Dory ZK advice 943 896 −47 (−5.0%) 106008 → 92376 18166 → 19624
Akita (Fp128) muldiv 404 324 −80 (−19.8%) 37316 → 22344 9897 → 11640
Akita (Fp128) advice 433 343 −90 (−20.8%) 41079 → 24640 10090 → 11840

The challenge draws (286 to 323 per verify) are the same in both columns, so the schedule is unchanged.

Against §7:

  • J-7 (BN254 56-byte challenges). §7 predicts about +6 to 8% of squeeze permutations. Squeezed bytes rise 8.2% in clear Dory, but replaying the head trace with J7's challenge widths gives 632 permutations against 634. The widening therefore costs only +2 permutations: most squeezes stay inside a block that was already being produced.
  • J-9 and J-11 (Dory framing, statement). These, with the other label removals, make up the rest of the clear-Dory delta: about −100 permutations, against roughly −3.5 per Dory round plus −8 for the statement.
  • Dory ZK. The ZK delta is smaller because more of its absorbs are BlindFold and Pedersen payloads that were never framed. 7 of the 48 and 47 come from dropping BlindFold's verifier-only absorb (895 → 888, 903 → 896 at 86e5ed7ce); clear Dory and Akita are unchanged by the review fixes.
  • Akita. The 249 or 250 grind checks each read 8 bytes instead of 1 and cost 0 extra permutations, as §7 predicts. The −80 and −90 come from removing labels, the bridge and setup framing, and the statement.

Validation

All on a remote Boat sandbox; nothing was built locally.

Run on sandbox bx_ecgwkhfn with -j4, per crate. The round-2 fixes were checked at tree 6ecac648 (git diff --binary 13e149195 of 5061178f7; the sumcheck and transcript suites ran on the same tree before the inventory re-bless). The round-1 fixes were checked at tree e31418f3 (49d192e6f); everything else at 195c0876 (86e5ed7ce). Commit 8 changes the shared append_values, so the other crates' suites were not re-run on it; the honest Dory clear, Dory ZK and Akita verifies of the size and permutation runs did pass on the fixed code, and CI covers the rest at the new head.

Check Result
clippy --all-targets --all-features -D warnings at 0f41e8d56 clean: jolt-sumcheck, jolt-blindfold
nextest (all features) at 0f41e8d56 jolt-sumcheck 112, jolt-blindfold 76
fs_obligations (fs-audit) at 0f41e8d56 pass, plain and akita; re-blessing changes no inventory line
clippy --all-targets --all-features -D warnings at 5061178f7 clean: jolt-sumcheck, jolt-transcript
nextest (all features) at 5061178f7 jolt-sumcheck 112, jolt-transcript 92
fs_obligations (fs-audit) at 5061178f7 pass, plain and akita
clippy --all-targets -D warnings at 49d192e6f clean: jolt-transcript, jolt-dory, jolt-blindfold (all features), jolt-eval
nextest at 49d192e6f jolt-transcript 92 (all features), jolt-dory 52, jolt-blindfold 76, jolt-eval transcript invariants 6
doctests at 49d192e6f cargo test --doc -p jolt-transcript, default and all features: pass
fs_obligations (fs-audit) at 03cd79146+ pass, plain and akita
clippy --all-targets -D warnings at 86e5ed7ce clean: jolt-verifier (plain and akita), jolt-spartan-verifier, jolt-akita, jolt-claims, jolt-hyperkzg, jolt-openings, jolt-sumcheck
nextest at 86e5ed7ce jolt-verifier lib 79 plain / 72 akita, jolt-akita lib 46, jolt-claims 150, jolt-hyperkzg 9, jolt-openings 15, jolt-poly 142, jolt-sumcheck 111
preprocessing digest vectors (R9) pass in all four configurations (plain, akita, each with and without field-inline)
fs_attacks clear 3, ZK 2, akita 2: all pass
fuzz crates cargo check of all ten fuzz crates; seeds and bundles regenerated by their generators
cargo machete clean after dropping blake2
cargo fmt --check, taplo fmt --check, typos clean

Not run here: jolt-prover's own tests (its library builds through prover-fixtures) and workspace-wide jobs. CI covers those.

Follow-ups (not in this PR)

  • Pre-existing unused-import warnings in jolt-dory/fuzz (Transcript), jolt-openings/fuzz (Transcript) and jolt-verifier/fuzz (ProverState).

🤖 Generated with Claude Code

…, J-4, J-5)

Drop labels from absorbed bytes. Byte strings absorb as a u64 length,
the body and zero padding to a word; the NARG keeps length and body.
Sessions are 32-byte zero-padded protocol tags. Protocol id moves to
a16z/jolt-transcript/v2.
The wire nonce stays LEB128. The verifier absorbs it as a u64 LE word
and checks the low bits of an 8-byte squeeze.
BN254 challenges squeeze 56 bytes per element, Fp32 candidate pools
round to an even count, F8 squeezes pad to a word, and 129- and 130-bit
short scalars squeeze 24 bytes.
…-2, J-11)

The Jolt statement absorbs its 19 configuration values as bare u64
words, then one advice-presence word computed on every build (bit 0
untrusted, bit 1 trusted), the bare 32-byte preprocessing digest, and
the inputs and outputs as length word, body and padding. Every
transcript is now opened with a 32-byte protocol tag, including
JOLT_SESSION.

verifier.rs also carries the J-13 and J-20 label removals in the same
functions; they are listed with the label sites in the next commit.
…to J-22)

Dory frames byte strings with a length word and absorbs group, serde
and field payloads bare; the verifier rejects any Dory proof whose
sigma/nu split differs from the one num_vars fixes. Sumcheck rounds
absorb one count word and the coefficients. Mid-protocol version
labels and constant absorbs are gone. The Jolt-to-Akita bridge uses
jolt-akita/group-batch/v5 and jolt-akita/batch/v2 tags, the grouped
statement codes each role as a u64 word (0 to 5), and the Akita
commitment absorbs its flavor as a word.
The program-preprocessing digest, the Spartan relation digest and the
lattice packing and strategy digests switch from Blake2b-256 to
Keccak-256 (sha3 0.12, the implementation jolt-inlines-keccak256 is
tested against). Domain prefixes and input bytes are unchanged. The
opt-in Blake2b sponge leaves these digests on Keccak. blake2 is dropped
from jolt-verifier, jolt-spartan-verifier and jolt-claims. No version
bump beyond a16z/jolt-transcript/v2.
Re-bless the fs_inventory absorb and challenge sites, regenerate the
Dory deser seeds and the verifier fuzz bundles, and rename the ZK
fixture cache keys so proofs cached under the J7 transcript regenerate
instead of failing.
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

📏 PR diff

Note

Actual code changed: 1,430 lines

🟢 +627 added    🔴 −803 removed

Tests, docs, fixtures, and helper scripts excluded.

Category 🟢 Added 🔴 Removed
⌨️ Actual code +627 −803
🧪 Tests +1,924 −1,164
📚 Docs +235 −135
📦 Fixtures +40 −54
🔧 Helper scripts 0 0
Total diff +2,826 −2,156

Every changed line is classified once.

append_values absorbed each value in its own call, so a group of 4-byte
Fp32 or 1-byte F8 values was never padded to a word. Concatenate the
encodings, zero-pad once to a word and absorb one message. For whole-word
encodings (every production field, Dory's compressed groups) the Keccak and
Blake2b streams are byte-identical to the per-value absorbs; a test pins
that, and another pins the padding of an odd count of Fp32 values.
On a concrete ProverState, spongefish's inherent prover_message shadowed
the trait method and wrote the padded sponge encoding of a BytesMsg to the
NARG string, so the verifier rejected it. The setup.rs doc example hit
exactly that. The trait method is now send, which nothing shadows; every
caller and the doc example use it.
The verifier absorbed the folded witness opening after the inner sumcheck,
but the prover never absorbs it, so the two transcripts diverged after
BlindFold. Only check_eof followed, so no challenge depended on it; it cost
Keccak work and would break any protocol composed after BlindFold. A test
now draws one challenge on each side after BlindFold and compares them.
…(R6)

The prover's CompressedRoundPoly absorbed the constant and each later
coefficient as separate messages, while the verifier absorbed them as one
word-padded append_values. The bytes agree for whole-word fields but not
for 4-byte ones (12 bytes against 16). Both sides, and the recorder, now
call append_compressed_round, which owns the encoding: the count word,
then the sent coefficients [c0, c2, ..., cd] as one padded message.

Adds a prover-versus-verifier transcript test over Prime32Offset99 and Fr.
Keccak absorbs by overwriting state bytes, so trailing zeros can change
the next squeeze in general. Say only what holds in this fixture.
The BlindFold outer and inner sumcheck provers copied the compressed-round
encoding (count word, then one append_values). Make
append_compressed_round public, re-export it, and call it from both sites,
so it is the only place that encodes compressed rounds. The bytes do not
change.

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