From 1f8bbcf6d5e8302aa1090e5e3f2cb0b58844b178 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Tue, 20 Oct 2026 22:13:08 -0400 Subject: [PATCH 01/16] refactor(transcript): one sealed transcript over spongefish states; delete the legacy facade --- crates/jolt-field/Cargo.toml | 5 + crates/jolt-field/src/algebra.rs | 65 ++- crates/jolt-field/src/binary/f128.rs | 5 - crates/jolt-field/src/binary/f192.rs | 5 - crates/jolt-field/src/binary/f64.rs | 5 - crates/jolt-field/src/binary/f8.rs | 5 - crates/jolt-field/src/bn254/mod.rs | 3 +- crates/jolt-field/src/encoding.rs | 91 ++++ crates/jolt-field/src/lib.rs | 5 +- crates/jolt-field/tests/binary_contract.rs | 1 - crates/jolt-field/tests/binary_f8.rs | 1 - crates/jolt-field/tests/bn254_differential.rs | 17 - .../tests/solinas_fp128_differential.rs | 5 - .../tests/solinas_words_differential.rs | 5 - crates/jolt-field/tests/spine.rs | 14 +- crates/jolt-transcript/Cargo.toml | 28 +- .../jolt-transcript/benches/transcript_ops.rs | 82 ++-- crates/jolt-transcript/src/challenge.rs | 17 +- crates/jolt-transcript/src/codec.rs | 111 ++++- crates/jolt-transcript/src/digest.rs | 194 -------- crates/jolt-transcript/src/grind_bits.rs | 81 +--- crates/jolt-transcript/src/legacy.rs | 423 ------------------ crates/jolt-transcript/src/lib.rs | 109 +---- crates/jolt-transcript/src/pow.rs | 42 +- crates/jolt-transcript/src/prover.rs | 123 ++--- crates/jolt-transcript/src/setup.rs | 107 +++-- crates/jolt-transcript/src/transcript.rs | 247 ++++++++++ crates/jolt-transcript/src/unpriced.rs | 69 +-- crates/jolt-transcript/src/verifier.rs | 96 +--- crates/jolt-transcript/tests/blake2b_tests.rs | 37 +- .../jolt-transcript/tests/challenge_tests.rs | 101 ++--- crates/jolt-transcript/tests/common/mod.rs | 162 ++++--- .../tests/digest_transcript_tests.rs | 99 ---- crates/jolt-transcript/tests/keccak_tests.rs | 44 +- .../tests/legacy_digest_tests.rs | 189 -------- .../jolt-transcript/tests/narg_eof_tests.rs | 4 +- .../tests/native_traits_tests.rs | 13 +- .../jolt-transcript/tests/poseidon_tests.rs | 77 +++- crates/jolt-transcript/tests/pow_tests.rs | 21 +- 39 files changed, 997 insertions(+), 1711 deletions(-) create mode 100644 crates/jolt-field/src/encoding.rs delete mode 100644 crates/jolt-transcript/src/digest.rs delete mode 100644 crates/jolt-transcript/src/legacy.rs create mode 100644 crates/jolt-transcript/src/transcript.rs delete mode 100644 crates/jolt-transcript/tests/digest_transcript_tests.rs delete mode 100644 crates/jolt-transcript/tests/legacy_digest_tests.rs diff --git a/crates/jolt-field/Cargo.toml b/crates/jolt-field/Cargo.toml index 39c1e77367..0d9be80529 100644 --- a/crates/jolt-field/Cargo.toml +++ b/crates/jolt-field/Cargo.toml @@ -24,6 +24,7 @@ allocative = { workspace = true, optional = true } bytemuck = { workspace = true, optional = true } rand_core = { workspace = true } rayon = { workspace = true, optional = true } +spongefish = { workspace = true, optional = true } thiserror = { workspace = true } [features] @@ -35,6 +36,8 @@ asm = [] fuzzing = ["asm"] parallel = ["dep:rayon"] allocative = ["dep:allocative"] +# spongefish `Encoding` for every field, so `F: JoltField` can be absorbed. +spongefish = ["dep:spongefish"] # Byte views of `Fp128` for device buffers (`jolt-metal`). bytemuck = ["dep:bytemuck"] fp128-proof-linkage = ["asm"] @@ -45,6 +48,8 @@ fp64-proof-linkage = [] # `Allocative` under the `allocative` feature; integration tests do not see # the optional dependency above. allocative = { workspace = true } +# Likewise `spongefish::Encoding` under the `spongefish` feature. +spongefish = { workspace = true } bincode = { workspace = true } criterion = { workspace = true } num-bigint = { workspace = true } diff --git a/crates/jolt-field/src/algebra.rs b/crates/jolt-field/src/algebra.rs index 47c77c6f1d..926d529a3f 100644 --- a/crates/jolt-field/src/algebra.rs +++ b/crates/jolt-field/src/algebra.rs @@ -286,13 +286,9 @@ pub trait CanonicalEncoding: /// Bit length of the field order `|F|` (for prime fields, the modulus). const MODULUS_BITS: u32; - /// Size of the set [`from_challenge_bytes`](Self::from_challenge_bytes) - /// draws from, as little-endian 64-bit limbs. Either no challenge has - /// probability above one over this size, or the draws that follow one - /// absorbed message are jointly within total variation `2^-161` of - /// uniform on a set of this size, as the field order is for the default - /// [`challenge_width`](Self::challenge_width). Proof-of-work difficulty - /// is priced from it; that distance is charged separately. + /// The field order, as little-endian 64-bit limbs: the size of the set + /// a challenge codec draws from. Proof-of-work difficulty is priced from + /// it. const CHALLENGE_SET_LIMBS: [u64; 4]; /// Decodes little-endian bytes of any length by reducing into the field. @@ -344,27 +340,6 @@ pub trait CanonicalEncoding: /// /// Zero is considered to have zero significant bits. fn num_bits(&self) -> u32; - - /// Number of transcript bytes squeezed for the `draw`-th challenge since - /// the transcript last absorbed, counting from 1, and decoded by - /// [`from_challenge_bytes`](Self::from_challenge_bytes). - /// - /// The default reduces [`reduction_width`]`(MODULUS_BITS, 1 + 2⌈log₂ draw⌉)` - /// bytes modulo the field order. Draw `k` is then within total variation - /// `2^-162 / k²` of uniform, so any number of draws after one message are - /// jointly within `2^-162 · π²/6 < 2^-161`, with no bound on their count. - #[inline] - fn challenge_width(draw: u64) -> usize { - let log_draw = u64::BITS - draw.saturating_sub(1).leading_zeros(); - reduction_width(Self::MODULUS_BITS, 1 + 2 * log_draw) - } - - /// Decodes one challenge from the bytes [`challenge_width`](Self::challenge_width) - /// gives. Every challenge, scalar or not, uses this decoder. - #[inline] - fn from_challenge_bytes(bytes: &[u8]) -> Self { - Self::from_bytes_le_reduced(bytes) - } } /// Bytes per element so that `2^share_bits` elements, each reduced modulo a @@ -542,14 +517,37 @@ pub trait MaybeAllocative {} #[cfg(not(feature = "allocative"))] impl MaybeAllocative for T {} +/// spongefish's [`Encoding`](spongefish::Encoding) when the `spongefish` +/// feature is on, vacuous otherwise. +/// +/// Every field in this crate encodes as its canonical little-endian bytes, so +/// `F: JoltField` can be absorbed into a transcript without a bound of its own. +#[cfg(feature = "spongefish")] +pub trait MaybeEncoding: spongefish::Encoding {} +#[cfg(feature = "spongefish")] +impl MaybeEncoding for T {} +/// spongefish's `Encoding` when the `spongefish` feature is on, vacuous +/// otherwise. +#[cfg(not(feature = "spongefish"))] +pub trait MaybeEncoding {} +#[cfg(not(feature = "spongefish"))] +impl MaybeEncoding for T {} + /// Everything Jolt's protocol stack requires of a scalar field: field -/// algebra, a canonical transcript encoding, an accumulator, serde wire -/// serialization (canonical-checked, via [`impl_serde_bytes!`]), and heap -/// visitation under the `allocative` feature. +/// algebra, a canonical encoding, an accumulator, serde wire serialization +/// (canonical-checked, via [`impl_serde_bytes!`]), the transcript encoding +/// under the `spongefish` feature, and heap visitation under the `allocative` +/// feature. /// /// Blanket-implemented — implement the component traits and this follows. pub trait JoltField: - Field + CanonicalEncoding + WithAccumulator + Serialize + DeserializeOwned + MaybeAllocative + Field + + CanonicalEncoding + + WithAccumulator + + Serialize + + DeserializeOwned + + MaybeAllocative + + MaybeEncoding { } @@ -559,7 +557,8 @@ impl< + WithAccumulator + Serialize + DeserializeOwned - + MaybeAllocative, + + MaybeAllocative + + MaybeEncoding, > JoltField for T { } diff --git a/crates/jolt-field/src/binary/f128.rs b/crates/jolt-field/src/binary/f128.rs index 1411ae7c83..60a9b1b71e 100644 --- a/crates/jolt-field/src/binary/f128.rs +++ b/crates/jolt-field/src/binary/f128.rs @@ -117,11 +117,6 @@ impl CanonicalEncoding for F128 { fn num_bits(&self) -> u32 { u128::BITS - self.0.leading_zeros() } - - #[inline] - fn challenge_width(_draw: u64) -> usize { - Self::NUM_BYTES - } } impl WithAccumulator for F128 { diff --git a/crates/jolt-field/src/binary/f192.rs b/crates/jolt-field/src/binary/f192.rs index 881497c158..f5c50e6984 100644 --- a/crates/jolt-field/src/binary/f192.rs +++ b/crates/jolt-field/src/binary/f192.rs @@ -156,11 +156,6 @@ impl CanonicalEncoding for F192 { .find_map(|(i, c)| (!c.is_zero()).then(|| i as u32 * 64 + c.num_bits())) .unwrap_or(0) } - - #[inline] - fn challenge_width(_draw: u64) -> usize { - Self::NUM_BYTES - } } impl WithAccumulator for F192 { diff --git a/crates/jolt-field/src/binary/f64.rs b/crates/jolt-field/src/binary/f64.rs index 30e5aeea32..dde0cf2281 100644 --- a/crates/jolt-field/src/binary/f64.rs +++ b/crates/jolt-field/src/binary/f64.rs @@ -117,11 +117,6 @@ impl CanonicalEncoding for F64 { fn num_bits(&self) -> u32 { u64::BITS - self.0.leading_zeros() } - - #[inline] - fn challenge_width(_draw: u64) -> usize { - Self::NUM_BYTES - } } impl WithAccumulator for F64 { diff --git a/crates/jolt-field/src/binary/f8.rs b/crates/jolt-field/src/binary/f8.rs index fd8b3dc2b5..99c1bd8eb8 100644 --- a/crates/jolt-field/src/binary/f8.rs +++ b/crates/jolt-field/src/binary/f8.rs @@ -131,11 +131,6 @@ impl CanonicalEncoding for F8 { fn num_bits(&self) -> u32 { u8::BITS - self.0.leading_zeros() } - - #[inline] - fn challenge_width(_draw: u64) -> usize { - Self::NUM_BYTES - } } impl WithAccumulator for F8 { diff --git a/crates/jolt-field/src/bn254/mod.rs b/crates/jolt-field/src/bn254/mod.rs index f0c801519d..28cd3c47db 100644 --- a/crates/jolt-field/src/bn254/mod.rs +++ b/crates/jolt-field/src/bn254/mod.rs @@ -2,8 +2,7 @@ //! the public API from the arkworks types. //! //! Serde and transcript encodings are the 32-byte little-endian canonical -//! form (identical to jolt-field). Challenges are full-width: the default -//! wide reduction of [`CanonicalEncoding::from_challenge_bytes`]. +//! form (identical to jolt-field). mod mont; diff --git a/crates/jolt-field/src/encoding.rs b/crates/jolt-field/src/encoding.rs new file mode 100644 index 0000000000..8c2acae7b8 --- /dev/null +++ b/crates/jolt-field/src/encoding.rs @@ -0,0 +1,91 @@ +//! spongefish [`Encoding`] for the field elements: their canonical +//! little-endian bytes, as [`CanonicalBytes::to_bytes_le`] writes them. +//! +//! Each encoding is fixed-width and canonical, so it is injective and a run +//! of elements of one field is self-delimiting. + +use spongefish::Encoding; + +use crate::CanonicalBytes; + +/// The `N` canonical bytes of `value`; `N` is its `NUM_BYTES`. +fn fixed(value: &T) -> [u8; N] { + let mut out = [0u8; N]; + value.to_bytes_le(&mut out); + out +} + +macro_rules! fixed_width { + ($([$($generics:tt)*] $field:ty => $bytes:expr),+ $(,)?) => {$( + impl<$($generics)*> Encoding for $field { + fn encode(&self) -> impl AsRef<[u8]> { + fixed::<{ $bytes }, Self>(self) + } + } + )+}; +} + +#[cfg(feature = "bn254")] +fixed_width!([] crate::Fr => 32, [] crate::Fq => 32); + +#[cfg(feature = "solinas")] +fixed_width!( + [const P: u32] crate::Fp32

=> 4, + [const P: u64] crate::Fp64

=> 8, + [const P: u128] crate::Fp128

=> 16, +); + +#[cfg(feature = "binary")] +fixed_width!( + [] crate::F8 => 1, + [] crate::F64 => 8, + [] crate::F128 => 16, + [] crate::F192 => 24, +); + +#[cfg(feature = "solinas")] +impl> Encoding for crate::FpExt2 { + fn encode(&self) -> impl AsRef<[u8]> { + self.to_bytes_le_vec() + } +} + +#[cfg(feature = "solinas")] +impl Encoding for crate::FpExt4 { + fn encode(&self) -> impl AsRef<[u8]> { + self.to_bytes_le_vec() + } +} + +#[cfg(test)] +mod tests { + use super::Encoding; + use crate::CanonicalBytes; + + fn matches_canonical_bytes(value: &T) { + assert_eq!(value.encode().as_ref(), value.to_bytes_le_vec()); + } + + #[test] + fn encodings_are_the_canonical_bytes() { + #[cfg(feature = "bn254")] + { + use crate::Ring; + matches_canonical_bytes(&crate::Fr::from_u64(0x0102_0304)); + matches_canonical_bytes(&crate::Fq::from_u64(7)); + } + #[cfg(feature = "solinas")] + { + use crate::Ring; + matches_canonical_bytes(&crate::Prime31Offset19::from_u64(5)); + matches_canonical_bytes(&crate::Prime64Offset59::from_u64(5)); + matches_canonical_bytes(&crate::Prime128OffsetA7F7::from_u64(5)); + } + #[cfg(feature = "binary")] + { + use crate::Ring; + matches_canonical_bytes(&crate::F8::from_u64(3)); + matches_canonical_bytes(&crate::F192::from_u64(3)); + } + } +} diff --git a/crates/jolt-field/src/lib.rs b/crates/jolt-field/src/lib.rs index febcbd79b7..43ef766a45 100644 --- a/crates/jolt-field/src/lib.rs +++ b/crates/jolt-field/src/lib.rs @@ -103,6 +103,8 @@ mod algebra; mod binary; #[cfg(feature = "bn254")] mod bn254; +#[cfg(feature = "spongefish")] +mod encoding; mod extension; #[cfg(feature = "solinas")] mod fp128_accumulators; @@ -117,7 +119,8 @@ mod unreduced; pub use algebra::{ reduction_width, Accumulator, AdditiveGroup, CanonicalBytes, CanonicalEncoding, Field, - JoltField, MaybeAllocative, NaiveAccumulator, PseudoMersenne, Ring, WithAccumulator, + JoltField, MaybeAllocative, MaybeEncoding, NaiveAccumulator, PseudoMersenne, Ring, + WithAccumulator, }; #[cfg(feature = "binary")] pub use binary::{F128, F192, F64, F8}; diff --git a/crates/jolt-field/tests/binary_contract.rs b/crates/jolt-field/tests/binary_contract.rs index 93125d715b..29b4a582b5 100644 --- a/crates/jolt-field/tests/binary_contract.rs +++ b/crates/jolt-field/tests/binary_contract.rs @@ -273,7 +273,6 @@ fn check_encoding(value: F, frozen: &[u8], short: F, display: &str (long.as_slice(), value), ] { assert_eq!(F::from_bytes_le_reduced(bytes), expected); - assert_eq!(F::from_challenge_bytes(bytes), expected); } } diff --git a/crates/jolt-field/tests/binary_f8.rs b/crates/jolt-field/tests/binary_f8.rs index 08b9ef0c64..8de7f2b043 100644 --- a/crates/jolt-field/tests/binary_f8.rs +++ b/crates/jolt-field/tests/binary_f8.rs @@ -103,7 +103,6 @@ fn spine_contract() { } for (input, expected) in [(&[][..], F8::zero()), (&[0xab, 0xcd][..], value)] { assert_eq!(F8::from_bytes_le_reduced(input), expected); - assert_eq!(F8::from_challenge_bytes(input), expected); } assert_eq!(F8::from_bytes_le_checked(&[]), None); assert_eq!(F8::from_bytes_le_checked(&[0xab]), Some(value)); diff --git a/crates/jolt-field/tests/bn254_differential.rs b/crates/jolt-field/tests/bn254_differential.rs index 940064e298..d6ab81571e 100644 --- a/crates/jolt-field/tests/bn254_differential.rs +++ b/crates/jolt-field/tests/bn254_differential.rs @@ -175,14 +175,6 @@ fn transcript_surface_matches() { (v.bits() <= 64).then(|| v.to_u64_digits().first().copied().unwrap_or(0)); assert_eq!(t.to_u64_checked(), expected_u64); - let challenge: Vec = - (0..::challenge_width(rng.gen_range(1..=64))) - .map(|_| rng.gen()) - .collect(); - assert_val( - &::from_challenge_bytes(&challenge), - &(BigUint::from_bytes_le(&challenge) % &p), - ); let wide: [u8; 48] = std::array::from_fn(|_| rng.gen()); assert_val( &::from_bytes_le_reduced(&wide), @@ -264,15 +256,6 @@ fn fq_matches() { let v: u64 = rng.gen(); assert_val(&two::Fq::from_u64(v), &BigUint::from(v)); - let challenge: Vec = - (0..::challenge_width(rng.gen_range(1..=64))) - .map(|_| rng.gen()) - .collect(); - assert_val( - &::from_challenge_bytes(&challenge), - &(BigUint::from_bytes_le(&challenge) % &p), - ); - assert_eq!( bincode::serde::encode_to_vec(t1, cfg).unwrap(), t1.to_bytes_le_vec(), diff --git a/crates/jolt-field/tests/solinas_fp128_differential.rs b/crates/jolt-field/tests/solinas_fp128_differential.rs index b2a9a97708..c2c063c3d3 100644 --- a/crates/jolt-field/tests/solinas_fp128_differential.rs +++ b/crates/jolt-field/tests/solinas_fp128_differential.rs @@ -184,11 +184,6 @@ macro_rules! check_prime128 { let challenge: [u8; 32] = $rng.gen(); for len in [8usize, 16, 32] { let ours = <$two as CanonicalEncoding>::from_bytes_le_reduced(&challenge[..len]); - assert_eq!( - <$two as CanonicalEncoding>::from_challenge_bytes(&challenge[..len]), - ours, - "challenge derivation defaults to the reducing decode" - ); let mut padded = [0u8; 32]; padded[..len].copy_from_slice(&challenge[..len]); let limbs: [u64; 4] = std::array::from_fn(|i| { diff --git a/crates/jolt-field/tests/solinas_words_differential.rs b/crates/jolt-field/tests/solinas_words_differential.rs index f8712cf9ea..e85d949e89 100644 --- a/crates/jolt-field/tests/solinas_words_differential.rs +++ b/crates/jolt-field/tests/solinas_words_differential.rs @@ -107,11 +107,6 @@ macro_rules! check_prime { Some(bytes_mod(&challenge[..len], p)), "decode vs oracle" ); - assert_eq!( - <$two as CanonicalEncoding>::from_challenge_bytes(&challenge[..len]), - ours, - "challenge derivation defaults to the reducing decode" - ); } let t_bytes = bincode::serde::encode_to_vec(ta, cfg).unwrap(); diff --git a/crates/jolt-field/tests/spine.rs b/crates/jolt-field/tests/spine.rs index d166db1891..a8a5eb9200 100644 --- a/crates/jolt-field/tests/spine.rs +++ b/crates/jolt-field/tests/spine.rs @@ -19,6 +19,13 @@ const P: u64 = (1 << 61) - 1; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] struct M61(u64); +#[cfg(feature = "spongefish")] +impl spongefish::Encoding for M61 { + fn encode(&self) -> impl AsRef<[u8]> { + self.0.to_le_bytes() + } +} + fn reduce128(v: u128) -> u64 { (v % P as u128) as u64 } @@ -227,10 +234,6 @@ fn canonical_surface() { M61::from_bytes_le_reduced(&wide), M61::from_u128_reduced(u128::from_le_bytes(wide)) ); - assert_eq!( - M61::from_challenge_bytes(&wide), - M61::from_bytes_le_reduced(&wide) - ); assert_eq!(M61::zero().num_bits(), 0); assert_eq!(M61(0b1011).num_bits(), 4); assert_eq!(M61(u64::MAX % P).to_u64_checked(), Some(u64::MAX % P)); @@ -303,7 +306,8 @@ fn inner_product(xs: &[F], ys: &[F]) -> F { #[test] fn jolt_field_blanket() { // M61 satisfies the JoltField bundle from the component traits alone — - // the derive above is the `allocative` feature's supertrait, not a JoltField impl. + // the derive and the `Encoding` impl above are the `allocative` and + // `spongefish` features' supertraits, not a JoltField impl. let xs = [M61(2), M61(3)]; let ys = [M61(5), M61(7)]; assert_eq!(inner_product(&xs, &ys), M61(31)); diff --git a/crates/jolt-transcript/Cargo.toml b/crates/jolt-transcript/Cargo.toml index 7e58bcb7e9..10fc88f5ff 100644 --- a/crates/jolt-transcript/Cargo.toml +++ b/crates/jolt-transcript/Cargo.toml @@ -17,24 +17,17 @@ workspace = true [features] default = ["transcript-blake2b", "transcript-keccak", "transcript-poseidon"] -spongefish = [ - "dep:spongefish", - "dep:getrandom_v04", - "spongefish/getrandom", - "spongefish/turboshake128", -] -digest = ["dep:digest"] # The prover's proof-of-work nonce search. It copies the sponge state, which # spongefish 0.8.0 exposes only under `yolocrypto`; verifier builds leave it off. -prover-grinding = ["spongefish", "spongefish/yolocrypto"] +prover-grinding = ["spongefish/yolocrypto"] # Searches proof-of-work nonces on all threads; the nonce found is the same. parallel = ["prover-grinding", "dep:rayon"] solinas = ["jolt-field/solinas"] binary = ["jolt-field/binary"] bn254 = ["dep:ark-bn254", "dep:ark-ff", "jolt-field/bn254"] -transcript-blake2b = ["spongefish", "digest", "dep:blake2"] -transcript-keccak = ["spongefish", "spongefish/keccak"] -transcript-poseidon = ["spongefish", "bn254", "dep:light-poseidon"] +transcript-blake2b = ["dep:blake2"] +transcript-keccak = ["spongefish/keccak"] +transcript-poseidon = ["bn254", "dep:light-poseidon"] # `UnpricedTranscript`, a wrapper that declares no proof of work. Tests only. test-utils = [] @@ -42,16 +35,17 @@ test-utils = [] ark-bn254 = { workspace = true, optional = true } ark-ff = { workspace = true, optional = true } light-poseidon = { workspace = true, optional = true } -spongefish = { workspace = true, optional = true } +spongefish = { workspace = true, features = ["getrandom", "turboshake128"] } blake2 = { workspace = true, optional = true } -digest = { workspace = true, optional = true } -jolt-field = { path = "../jolt-field", default-features = false } +jolt-field = { path = "../jolt-field", default-features = false, features = [ + "spongefish", +] } rayon = { workspace = true, optional = true } # spongefish seeds the prover's private RNG from getrandom 0.4, which needs # an explicit backend on wasm32-unknown-unknown. [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom_v04 = { workspace = true, features = ["wasm_js"], optional = true } +getrandom_v04 = { workspace = true, features = ["wasm_js"] } [dev-dependencies] criterion = { workspace = true } @@ -70,10 +64,6 @@ required-features = [ name = "blake2b_tests" required-features = ["bn254", "transcript-blake2b"] -[[test]] -name = "legacy_digest_tests" -required-features = ["bn254", "transcript-blake2b"] - [[test]] name = "keccak_tests" required-features = ["bn254", "transcript-keccak"] diff --git a/crates/jolt-transcript/benches/transcript_ops.rs b/crates/jolt-transcript/benches/transcript_ops.rs index 2cc82cbd65..f2b4939d3f 100644 --- a/crates/jolt-transcript/benches/transcript_ops.rs +++ b/crates/jolt-transcript/benches/transcript_ops.rs @@ -4,7 +4,34 @@ use std::hint::black_box; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use jolt_field::Fr; -use jolt_transcript::{Blake2bTranscript, KeccakTranscript, PoseidonTranscript, Transcript}; +use jolt_transcript::{ + prover_transcript, Blake2b512, FieldTranscript, Keccak, PoseidonSponge, ProverState, Transcript, +}; +use spongefish::DuplexSpongeInterface; + +fn fresh + spongefish::DuplexSpongeInit>() -> ProverState { + prover_transcript::(b"bench", b"bench") +} + +fn bench_append_bytes_with( + group: &mut criterion::BenchmarkGroup<'_, criterion::measurement::WallTime>, + name: &str, + label: &str, + data: &[u8], +) where + H: DuplexSpongeInterface + spongefish::DuplexSpongeInit, +{ + group.bench_with_input(BenchmarkId::new(name, label), data, |bench, data| { + bench.iter_batched( + fresh::, + |mut t| { + t.append_bytes(black_box(data)); + t + }, + criterion::BatchSize::SmallInput, + ); + }); +} fn bench_append_bytes(c: &mut Criterion) { let mut group = c.benchmark_group("append_bytes"); @@ -12,36 +39,9 @@ fn bench_append_bytes(c: &mut Criterion) { let data_256 = [0xCDu8; 256]; for (label, data) in [("32B", &data_32[..]), ("256B", &data_256[..])] { - group.bench_with_input(BenchmarkId::new("Blake2b", label), data, |bench, data| { - bench.iter_batched( - || Blake2bTranscript::::new(b"bench"), - |mut t| { - t.append_bytes(black_box(data)); - t - }, - criterion::BatchSize::SmallInput, - ); - }); - group.bench_with_input(BenchmarkId::new("Keccak", label), data, |bench, data| { - bench.iter_batched( - || KeccakTranscript::::new(b"bench"), - |mut t| { - t.append_bytes(black_box(data)); - t - }, - criterion::BatchSize::SmallInput, - ); - }); - group.bench_with_input(BenchmarkId::new("Poseidon", label), data, |bench, data| { - bench.iter_batched( - || PoseidonTranscript::::new(b"bench"), - |mut t| { - t.append_bytes(black_box(data)); - t - }, - criterion::BatchSize::SmallInput, - ); - }); + bench_append_bytes_with::(&mut group, "Blake2b", label, data); + bench_append_bytes_with::(&mut group, "Keccak", label, data); + bench_append_bytes_with::(&mut group, "Poseidon", label, data); } group.finish(); } @@ -52,11 +52,11 @@ fn bench_challenge(c: &mut Criterion) { group.bench_function("Blake2b", |bench| { bench.iter_batched( || { - let mut t = Blake2bTranscript::::new(b"bench"); + let mut t = fresh::(); t.append_bytes(&[42u8; 32]); t }, - |mut t| t.challenge(), + |mut t| FieldTranscript::::challenge(&mut t), criterion::BatchSize::SmallInput, ); }); @@ -64,23 +64,11 @@ fn bench_challenge(c: &mut Criterion) { group.bench_function("Keccak", |bench| { bench.iter_batched( || { - let mut t = KeccakTranscript::::new(b"bench"); - t.append_bytes(&[42u8; 32]); - t - }, - |mut t| t.challenge(), - criterion::BatchSize::SmallInput, - ); - }); - - group.bench_function("Poseidon", |bench| { - bench.iter_batched( - || { - let mut t = PoseidonTranscript::::new(b"bench"); + let mut t = fresh::(); t.append_bytes(&[42u8; 32]); t }, - |mut t| t.challenge(), + |mut t| FieldTranscript::::challenge(&mut t), criterion::BatchSize::SmallInput, ); }); diff --git a/crates/jolt-transcript/src/challenge.rs b/crates/jolt-transcript/src/challenge.rs index 4cc547746b..b618bf568c 100644 --- a/crates/jolt-transcript/src/challenge.rs +++ b/crates/jolt-transcript/src/challenge.rs @@ -29,7 +29,6 @@ use jolt_field::{Fq, Fr}; use jolt_field::{F128, F192, F64, F8}; #[cfg(feature = "transcript-keccak")] use spongefish::instantiations::Keccak; -#[cfg(feature = "spongefish")] use spongefish::{DuplexSpongeInterface, VerificationError}; #[cfg(feature = "transcript-blake2b")] @@ -114,7 +113,6 @@ pub trait ChallengeCodec: Sized + Sealed { /// /// `PoseidonSponge` is not one: each block it squeezes is the little-endian /// encoding of a BN254 scalar, whose top bits are far from uniform. -#[cfg(feature = "spongefish")] pub trait UniformBytes: DuplexSpongeInterface + Sealed {} #[cfg(feature = "transcript-keccak")] @@ -343,7 +341,6 @@ binary_codec!(F8, F64, F128, F192); /// # Errors /// Returns `VerificationError` when `F` does not support `len` or the message /// would exceed [`MAX_CHALLENGE_BYTES`]. -#[cfg(feature = "spongefish")] pub(crate) fn message_bytes(len: usize) -> Result { F::challenge_bytes(len) .filter(|&bytes| bytes <= MAX_CHALLENGE_BYTES) @@ -353,9 +350,7 @@ pub(crate) fn message_bytes(len: usize) -> Result(SESSION, INSTANCE); - assert!(ProverTranscript::field_challenges::(&mut prover, 3).is_err()); + let mut prover = prover_transcript::(SESSION, &INSTANCE); + assert!(FieldTranscript::::challenge_vector(&mut prover, 3).is_err()); - let mut prover = prover_transcript::(SESSION, INSTANCE); + let mut prover = prover_transcript::(SESSION, &INSTANCE); prover.prover_message(&BytesMsg(b"after".to_vec())); let mut verifier = - verifier_transcript::(SESSION, INSTANCE, prover.narg_string()); - assert!(VerifierTranscript::field_challenges::(&mut verifier, 3).is_err()); + verifier_transcript::(SESSION, &INSTANCE, prover.narg_string()); + assert!(FieldTranscript::::challenge_vector(&mut verifier, 3).is_err()); assert!(verifier.prover_message::().is_err()); assert!(verifier.check_eof().is_err()); } diff --git a/crates/jolt-transcript/src/codec.rs b/crates/jolt-transcript/src/codec.rs index 9e38105d12..f9d74253ab 100644 --- a/crates/jolt-transcript/src/codec.rs +++ b/crates/jolt-transcript/src/codec.rs @@ -1,19 +1,24 @@ -//! Length-prefixed byte messages for byte-oriented spongefish transcripts. +//! Message encodings for byte-oriented spongefish transcripts. //! -//! `BytesMsg` uses an eight-byte little-endian length, followed by the body. -//! Challenges use spongefish's built-in `u128` codec. +//! [`BytesMsg`] is a byte string with an eight-byte little-endian length, +//! followed by the body. [`Label`], [`LabelWithCount`] and [`U64Word`] are the +//! 32-byte words that name what the next messages are. use spongefish::{Encoding, FromNarg, NargReader, VerificationError}; -/// Length-prefixed byte string. 8-byte LE length keeps `BytesMsg(a) ; BytesMsg(b)` -/// distinguishable from `BytesMsg(a||b)`. +/// Maximum label length in bytes accepted by [`Label`]. +pub const MAX_LABEL_LEN: usize = 32; + +/// Length-prefixed byte string, owned (`Vec`, the default) or borrowed. +/// The 8-byte LE length keeps `BytesMsg(a) ; BytesMsg(b)` distinguishable +/// from `BytesMsg(a||b)`. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct BytesMsg(pub Vec); +pub struct BytesMsg>(pub B); -impl BytesMsg { +impl> BytesMsg { /// Returns the inner bytes. pub fn as_slice(&self) -> &[u8] { - &self.0 + self.0.as_ref() } } @@ -23,11 +28,12 @@ impl From> for BytesMsg { } } -impl Encoding for BytesMsg { +impl> Encoding for BytesMsg { fn encode(&self) -> impl AsRef<[u8]> { - let mut out = Vec::with_capacity(8 + self.0.len()); - out.extend_from_slice(&(self.0.len() as u64).to_le_bytes()); - out.extend_from_slice(&self.0); + let body = self.0.as_ref(); + let mut out = Vec::with_capacity(8 + body.len()); + out.extend_from_slice(&(body.len() as u64).to_le_bytes()); + out.extend_from_slice(body); out } } @@ -44,6 +50,62 @@ impl FromNarg for BytesMsg { } } +/// 32-byte zero-padded label word. +/// +/// Labels are protocol constants; encoding one longer than +/// [`MAX_LABEL_LEN`] bytes panics. +#[derive(Clone, Copy, Debug)] +pub struct Label(pub &'static [u8]); + +impl Encoding for Label { + fn encode(&self) -> impl AsRef<[u8]> { + assert!( + self.0.len() <= MAX_LABEL_LEN, + "label {:?} exceeds 32 bytes", + core::str::from_utf8(self.0) + ); + let mut padded = [0u8; 32]; + let (head, _) = padded.split_at_mut(self.0.len()); + head.copy_from_slice(self.0); + padded + } +} + +/// Packed label (24 bytes) + count (8-byte big-endian) in one 32-byte word. +/// +/// Labels are protocol constants; encoding one longer than 24 bytes panics. +#[derive(Clone, Copy, Debug)] +pub struct LabelWithCount(pub &'static [u8], pub u64); + +impl Encoding for LabelWithCount { + fn encode(&self) -> impl AsRef<[u8]> { + assert!( + self.0.len() <= 24, + "label {:?} exceeds 24 bytes", + core::str::from_utf8(self.0) + ); + let mut packed = [0u8; 32]; + let (head, tail) = packed.split_at_mut(24); + let (label, _) = head.split_at_mut(self.0.len()); + label.copy_from_slice(self.0); + tail.copy_from_slice(&self.1.to_be_bytes()); + packed + } +} + +/// EVM-compatible left-padded u64: 24 zero bytes + 8-byte BE value. +#[derive(Clone, Copy, Debug)] +pub struct U64Word(pub u64); + +impl Encoding for U64Word { + fn encode(&self) -> impl AsRef<[u8]> { + let mut packed = [0u8; 32]; + let (_, value) = packed.split_at_mut(24); + value.copy_from_slice(&self.0.to_be_bytes()); + packed + } +} + #[cfg(test)] #[expect(clippy::indexing_slicing, reason = "tests index fixture data")] mod tests { @@ -59,6 +121,31 @@ mod tests { assert_eq!(&bytes[8..], &[1, 2, 3, 4]); } + #[test] + fn borrowed_and_owned_bytes_encode_alike() { + let owned = BytesMsg(vec![5u8, 6, 7]); + let borrowed = BytesMsg(&[5u8, 6, 7][..]); + assert_eq!(owned.encode().as_ref(), borrowed.encode().as_ref()); + } + + #[test] + fn label_words_pad_and_pack() { + let label = Label(b"abc"); + let encoded = label.encode(); + assert_eq!(&encoded.as_ref()[..3], b"abc"); + assert!(encoded.as_ref()[3..].iter().all(|&byte| byte == 0)); + + let counted = LabelWithCount(b"xy", 0x0102); + let encoded = counted.encode(); + assert_eq!(&encoded.as_ref()[..2], b"xy"); + assert_eq!(&encoded.as_ref()[24..], &0x0102u64.to_be_bytes()); + + let word = U64Word(7); + let encoded = word.encode(); + assert!(encoded.as_ref()[..31].iter().all(|&byte| byte == 0)); + assert_eq!(encoded.as_ref()[31], 7); + } + #[test] fn bytes_msg_narg_rejects_truncation() { let m = BytesMsg(vec![9, 8, 7]); diff --git a/crates/jolt-transcript/src/digest.rs b/crates/jolt-transcript/src/digest.rs deleted file mode 100644 index c79b91147e..0000000000 --- a/crates/jolt-transcript/src/digest.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! Generic digest-based Fiat-Shamir transcript for the deployed proof format. -//! -//! Appends hash `state || round || payload`, squeezes hash `state || round`, -//! and preserves the established challenge decoding. The spongefish-backed -//! [`crate::SpongeTranscript`] is the native transcript; this one exists for -//! the proof-format compatibility boundary. - -use digest::{consts::U32, Digest}; - -use crate::legacy::{Transcript, MAX_LABEL_LEN}; -use crate::ChallengeSetSize; - -#[cfg(test)] -#[derive(Clone, Default)] -struct TestState { - state_history: Vec<[u8; 32]>, - expected_state_history: Option>, -} - -/// Fiat-Shamir transcript backed by a 256-bit digest. -/// -/// Generic over the hash function `D` and field type `F`. Challenges are -/// produced as field elements through `F::from_challenge_bytes`. -pub struct DigestTranscript + 'static, F> { - state: [u8; 32], - n_rounds: u32, - /// Challenges drawn since the last absorb; sets the next one's width. - draws: u64, - #[cfg(test)] - test_state: TestState, - _marker: std::marker::PhantomData<(fn() -> D, F)>, -} - -impl Clone for DigestTranscript -where - D: Digest, - F: jolt_field::CanonicalEncoding, -{ - fn clone(&self) -> Self { - Self { - state: self.state, - n_rounds: self.n_rounds, - draws: self.draws, - #[cfg(test)] - test_state: self.test_state.clone(), - _marker: std::marker::PhantomData, - } - } -} - -/// Exists only because [`Transcript`] requires `Default`. -/// -/// WARNING: `Default` does not produce the zero-state transcript used by the -/// deployed proof format; use [`Transcript::new`] for compatible transcripts. -impl Default for DigestTranscript -where - D: Digest, - F: jolt_field::CanonicalEncoding, -{ - fn default() -> Self { - Self::new(b"") - } -} - -impl std::fmt::Debug for DigestTranscript -where - D: Digest, - F: jolt_field::CanonicalEncoding, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("DigestTranscript") - .field("state", &format_args!("{:02x?}", self.state)) - .field("n_rounds", &self.n_rounds) - .finish_non_exhaustive() - } -} - -impl DigestTranscript -where - D: Digest, - F: jolt_field::CanonicalEncoding, -{ - #[inline] - fn hasher(&self) -> D { - let mut round_bytes = [0u8; 32]; - round_bytes[28..].copy_from_slice(&self.n_rounds.to_be_bytes()); - D::new().chain_update(self.state).chain_update(round_bytes) - } - - fn squeeze(&mut self, out: &mut [u8]) { - let mut out = &mut *out; - - while out.len() > 32 { - let mut chunk = [0u8; 32]; - self.challenge_bytes32(&mut chunk); - let (head, tail) = std::mem::take(&mut out).split_at_mut(32); - head.copy_from_slice(&chunk); - out = tail; - } - - let mut final_chunk = [0u8; 32]; - self.challenge_bytes32(&mut final_chunk); - let (src, _) = final_chunk.split_at(out.len()); - out.copy_from_slice(src); - } - - #[inline] - fn challenge_bytes32(&mut self, out: &mut [u8; 32]) { - let hash: [u8; 32] = self.hasher().finalize().into(); - out.copy_from_slice(&hash); - self.update_state(hash); - } - - fn update_state(&mut self, new_state: [u8; 32]) { - self.state = new_state; - self.n_rounds += 1; - - #[cfg(test)] - { - if let Some(ref expected) = self.test_state.expected_state_history { - #[expect(clippy::indexing_slicing, reason = "tests index fixture data")] - let expected_state = expected[self.n_rounds as usize]; - assert_eq!( - new_state, expected_state, - "Fiat-Shamir transcript mismatch at round {}", - self.n_rounds - ); - } - self.test_state.state_history.push(new_state); - } - } -} - -impl Transcript for DigestTranscript -where - D: Digest, - F: jolt_field::CanonicalEncoding, -{ - type Challenge = F; - - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::from_limbs(F::CHALLENGE_SET_LIMBS); - - fn new(label: &'static [u8]) -> Self { - assert!( - label.len() <= MAX_LABEL_LEN, - "label must be at most {MAX_LABEL_LEN} bytes", - ); - - let mut padded = [0u8; MAX_LABEL_LEN]; - let (head, _) = padded.split_at_mut(label.len()); - head.copy_from_slice(label); - - let hash: [u8; 32] = D::new().chain_update(padded).finalize().into(); - - Self { - state: hash, - n_rounds: 0, - draws: 0, - #[cfg(test)] - test_state: TestState { - state_history: vec![hash], - expected_state_history: None, - }, - _marker: std::marker::PhantomData, - } - } - - fn append_bytes(&mut self, bytes: &[u8]) { - let hash: [u8; 32] = self.hasher().chain_update(bytes).finalize().into(); - self.update_state(hash); - self.draws = 0; - } - - fn challenge(&mut self) -> F { - self.draws = self.draws.saturating_add(1); - let mut buf = vec![0u8; F::challenge_width(self.draws)]; - self.squeeze(&mut buf); - F::from_challenge_bytes(&buf) - } - - fn challenge_bytes(&mut self, out: &mut [u8]) { - self.squeeze(out); - } - - #[inline] - fn state(&self) -> [u8; 32] { - self.state - } - - #[cfg(test)] - fn compare_to(&mut self, other: &Self) { - self.test_state.expected_state_history = Some(other.test_state.state_history.clone()); - } -} diff --git a/crates/jolt-transcript/src/grind_bits.rs b/crates/jolt-transcript/src/grind_bits.rs index 1e43b3c628..4ea3d97be7 100644 --- a/crates/jolt-transcript/src/grind_bits.rs +++ b/crates/jolt-transcript/src/grind_bits.rs @@ -401,85 +401,18 @@ mod tests { } } - /// The legacy facades reduce `Fr::challenge_width` squeezed bytes, so - /// they draw from all of `Fr`, as the codec does. A site's loss factor is - /// a `u64`, so `u64::MAX` bounds every site: none needs proof of work. - #[cfg(all( - feature = "bn254", - feature = "transcript-blake2b", - feature = "transcript-keccak" - ))] - #[test] - fn bn254_facades_need_no_proof_of_work() { - use crate::{ - Blake2bTranscript, ChallengeCodec, KeccakTranscript, LegacyBlake2bTranscript, - Transcript, SECURITY_BITS, - }; - use jolt_field::Fr; - - for set in [ - LegacyBlake2bTranscript::::CHALLENGE_SET, - Blake2bTranscript::::CHALLENGE_SET, - KeccakTranscript::::CHALLENGE_SET, - ] { - assert_eq!(set, ::CHALLENGE_SET_SIZE); - assert_eq!(grind_bits_for_loss(u64::MAX, set, SECURITY_BITS), Ok(0)); - } - } - - /// Draw `k` after an absorb reduces `8w ≥ MODULUS_BITS + 160 + 2⌈log₂ k⌉` - /// bits, so it is within `2^-162 / k²` of uniform and any number of draws - /// after one message stay within `2^-161`. The first BN254 draw matches - /// the codec's one-element width. - #[cfg(all(feature = "bn254", feature = "solinas"))] - #[test] - fn challenge_width_keeps_every_message_within_budget() { - use crate::ChallengeCodec; - use jolt_field::{CanonicalEncoding, Fq, Fr, Prime128Offset275, Prime128OffsetA7F7}; - - fn check() { - let draws = (1..=1 << 12).chain([u64::from(u32::MAX), u64::MAX - 1, u64::MAX]); - let mut previous = 0; - for draw in draws { - let width = F::challenge_width(draw); - let log_draw = u64::BITS - (draw - 1).leading_zeros(); - assert!( - 8 * width as u32 >= F::MODULUS_BITS + 160 + 2 * log_draw, - "draw {draw}" - ); - assert!(width >= previous, "draw {draw}"); - previous = width; - } - } - check::(); - check::(); - check::(); - check::(); - assert_eq!(Fr::challenge_width(1), 52); - assert_eq!(::challenge_bytes(1), Some(52)); - assert_eq!(Prime128OffsetA7F7::challenge_width(1), 36); - } - /// fp128 challenges are priced at the full order `p = 2^128 − 275`: a /// loss factor that is not a power of two needs `⌈log₂ L⌉` bits. - #[cfg(all(feature = "solinas", feature = "transcript-blake2b"))] + #[cfg(feature = "solinas")] #[test] - fn fp128_facades_use_the_full_order() { - use crate::{ChallengeCodec, LegacyBlake2bTranscript, Transcript, SECURITY_BITS}; + fn fp128_codecs_use_the_full_order() { + use crate::{ChallengeCodec, SECURITY_BITS}; use jolt_field::{Prime128Offset275, Prime128OffsetA7F7}; - let sets = [ - ( - LegacyBlake2bTranscript::::CHALLENGE_SET, - ::CHALLENGE_SET_SIZE, - ), - ( - LegacyBlake2bTranscript::::CHALLENGE_SET, - ::CHALLENGE_SET_SIZE, - ), - ]; - for (set, codec_set) in sets { - assert_eq!(set, codec_set); + for set in [ + ::CHALLENGE_SET_SIZE, + ::CHALLENGE_SET_SIZE, + ] { for (loss, bits) in [(1, 1), (2, 2), (3, 2), (6, 3), (10, 4), (27, 5), (42, 6)] { assert_eq!( grind_bits_for_loss(loss, set, SECURITY_BITS), diff --git a/crates/jolt-transcript/src/legacy.rs b/crates/jolt-transcript/src/legacy.rs deleted file mode 100644 index eecf162fd6..0000000000 --- a/crates/jolt-transcript/src/legacy.rs +++ /dev/null @@ -1,423 +0,0 @@ -//! Source-compatible facade for `jolt-sumcheck`, `jolt-openings`, and -//! `jolt-crypto`. -//! -//! Wraps a duplex sponge over each of the three backends and re-exposes -//! the compatibility `Transcript` / `AppendToTranscript` API. It can be -//! removed once all consumers migrate to the split-trait surface. - -#[cfg(feature = "spongefish")] -use std::marker::PhantomData; - -#[cfg(feature = "spongefish")] -use jolt_field::CanonicalEncoding; -use jolt_field::{CanonicalBytes, Ring}; -#[cfg(feature = "spongefish")] -use spongefish::{DuplexSpongeInit, DuplexSpongeInterface, Encoding}; - -#[cfg(feature = "spongefish")] -use crate::codec::BytesMsg; -use crate::pow::{Leb128, Probe, ProofOfWorkError, SpongePow}; -#[cfg(feature = "spongefish")] -use crate::setup::session_id; -use crate::ChallengeSetSize; - -/// Maximum label length in bytes accepted by [`Transcript::new`] and the -/// label helpers below. -pub const MAX_LABEL_LEN: usize = 32; - -/// Fiat-Shamir transcript for non-interactive proofs. -/// -/// A transcript absorbs data and produces deterministic challenges. Both -/// prover and verifier maintain identical transcripts to derive the same -/// challenges. -/// -/// # Security -/// -/// The label passed to [`new`](Transcript::new) binds the transcript to -/// a protocol domain. -/// -/// `Clone` lets the prover search a proof-of-work nonce on copies of the -/// transcript ([`grind`](Transcript::grind)). -pub trait Transcript: Clone + Default + Sync + Send + 'static { - /// The challenge type produced by this transcript. Concrete transcript - /// implementations state any decoding or canonical-encoding capability - /// they need; custom transcripts may return an algebra-only field type. - type Challenge; - - /// Size of the set every challenge is drawn from, near-uniformly: the - /// field's [`CHALLENGE_SET_LIMBS`](jolt_field::CanonicalEncoding::CHALLENGE_SET_LIMBS) - /// for the field transcripts. Prices the proof of work before a challenge - /// ([`grind_bits_for_loss`]). - /// - /// [`grind_bits_for_loss`]: crate::grind_bits_for_loss - const CHALLENGE_SET: ChallengeSetSize; - - /// Creates a new transcript with the given domain separation label. - /// - /// # Panics - /// - /// Panics if `label.len() > MAX_LABEL_LEN`. - fn new(label: &'static [u8]) -> Self; - - /// Absorbs raw bytes. - fn append_bytes(&mut self, bytes: &[u8]); - - /// Absorbs a value via [`AppendToTranscript`]. - fn append(&mut self, value: &A) { - value.append_to_transcript(self); - } - - /// Absorbs a domain label followed by a value. - fn append_labeled(&mut self, label: &'static [u8], value: &A) { - self.append(&Label(label)); - self.append(value); - } - - /// Absorbs a domain label with a count followed by each value in order. - fn append_values(&mut self, label: &'static [u8], values: &[A]) { - self.append(&LabelWithCount(label, values.len() as u64)); - for value in values { - self.append(value); - } - } - - /// Squeezes a challenge. - #[must_use] - fn challenge(&mut self) -> Self::Challenge; - - /// Squeezes a scalar challenge, such as a batching coefficient. It is - /// decoded exactly as [`challenge`](Self::challenge) is. - #[must_use] - fn challenge_scalar(&mut self) -> Self::Challenge { - self.challenge() - } - - /// Squeezes `len` challenges. - #[must_use] - fn challenge_vector(&mut self, len: usize) -> Vec { - (0..len).map(|_| self.challenge()).collect() - } - - /// Squeezes one scalar challenge and returns its powers `[1, gamma, gamma^2, ...]`. - #[must_use] - fn challenge_scalar_powers(&mut self, len: usize) -> Vec - where - Self::Challenge: Ring, - { - let gamma = self.challenge_scalar(); - let one = Self::Challenge::from_u64(1); - std::iter::successors(Some(one), |power| Some(*power * gamma)) - .take(len) - .collect() - } - - /// Squeezes `out.len()` raw bytes. - /// - /// Protocol challenges come from the decoding methods above; this one - /// backs the proof-of-work check of [`check_grind`](Self::check_grind). - fn challenge_bytes(&mut self, out: &mut [u8]); - - /// A proof of work of `bits` bits, for the prover. Returns the nonce the - /// proof carries, or `None` for `bits == 0`, which leaves the transcript - /// unchanged. - /// - /// The nonce is the least one below `2^(bits + 7)` that passes - /// [`check_grind`](Self::check_grind), which is then applied to this - /// transcript. - /// - /// # Errors - /// [`ProofOfWorkError`], before touching the transcript, if - /// `bits > MAX_POW_BITS` or the search exhausts the nonce space - /// (probability at most `e^-128` for an ideal hash). - fn grind(&mut self, bits: u32) -> Result, ProofOfWorkError> { - if bits == 0 { - return Ok(None); - } - let pow = SpongePow::new(bits).ok_or(ProofOfWorkError)?; - let nonce = pow - .search(&TranscriptProbe(&*self)) - .ok_or(ProofOfWorkError)?; - self.check_grind(bits, Some(nonce))?; - Ok(Some(nonce)) - } - - /// Checks a proof of work of `bits` bits whose nonce the proof carries. - /// - /// For `bits == 0` the proof carries no nonce and the transcript is - /// unchanged. For `1 <= bits <= MAX_POW_BITS` the nonce must be below - /// `2^(bits + 7)`; it is absorbed as one [`append_bytes`] call of its - /// canonical LEB128 encoding, then `ceil(bits / 8)` bytes are squeezed - /// with [`challenge_bytes`] and their low `bits` bits must be zero. - /// - /// The step protects the challenges drawn after it, up to the next - /// absorbed prover message. The difficulty and the position of the step - /// must be fixed by the protocol. - /// - /// # Security - /// - /// With an ideal hash, each attempt passes with probability `2^-bits`, - /// and the protected challenges come from squeezes after the check. - /// - /// # Errors - /// [`ProofOfWorkError`] if `bits > MAX_POW_BITS`, if a nonce is missing, - /// present at `bits == 0` or out of range, or if it fails the check. The - /// transcript may have advanced; a rejected proof must not be continued. - /// - /// [`append_bytes`]: Self::append_bytes - /// [`challenge_bytes`]: Self::challenge_bytes - fn check_grind(&mut self, bits: u32, nonce: Option) -> Result<(), ProofOfWorkError> { - let Some(pow) = SpongePow::new(bits) else { - return if bits == 0 && nonce.is_none() { - Ok(()) - } else { - Err(ProofOfWorkError) - }; - }; - let nonce = nonce - .filter(|&nonce| nonce <= pow.last_nonce()) - .ok_or(ProofOfWorkError)?; - self.append_bytes(Leb128::new(nonce).as_bytes()); - let mut word = [0u8; 4]; - let check = word.get_mut(..pow.word_len()).ok_or(ProofOfWorkError)?; - self.challenge_bytes(check); - if pow.accepts(check) { - Ok(()) - } else { - Err(ProofOfWorkError) - } - } - - /// Current 256-bit transcript state. Peeked non-destructively by - /// squeezing 32 bytes from a sponge clone, so callers can read it - /// without advancing the real state. Useful for debug-only - /// cross-verifier comparison. - #[must_use] - fn state(&self) -> [u8; 32]; - - /// Enables transcript comparison for tests; mirrors upstream's signature. - /// Spongefish sponges have no replayable state history, so this is a - /// no-op on the legacy facade — call sites already only use it under - /// `#[cfg(test)]` for debugging digest-based transcripts. - #[cfg(test)] - fn compare_to(&mut self, _other: &Self) {} -} - -/// A copy of a transcript, for the nonce search of [`Transcript::grind`]: -/// a candidate is absorbed exactly as [`Transcript::check_grind`] absorbs it. -struct TranscriptProbe<'a, T>(&'a T); - -impl Probe for TranscriptProbe<'_, T> { - fn probe(&self, candidate: &[u8], out: &mut [u8]) { - let mut transcript = self.0.clone(); - transcript.append_bytes(candidate); - transcript.challenge_bytes(out); - } -} - -/// Implement on types that absorb themselves into a [`Transcript`]. -pub trait AppendToTranscript { - /// Absorbs this value into the transcript. - fn append_to_transcript(&self, transcript: &mut T); - - /// Byte length of the payload absorbed by [`Self::append_to_transcript`], when - /// the type participates in variable-length labeled appends. - fn transcript_payload_len(&self) -> Option { - None - } -} - -/// Big-endian field element absorption used by the deployed proof format. -impl AppendToTranscript for F { - fn append_to_transcript(&self, transcript: &mut T) { - let mut buf = vec![0u8; F::NUM_BYTES]; - self.to_bytes_le(&mut buf); - buf.reverse(); - transcript.append_bytes(&buf); - } -} - -/// Absorbs `label` (with the payload's transcript length when it exposes -/// one) followed by the payload itself. The single home of the labeled-append -/// convention shared by the prover's and verifier's commitment absorption — -/// order and labels are consensus-critical. -pub fn append_length_prefixed(transcript: &mut T, label: &'static [u8], payload: &A) -where - T: Transcript, - A: AppendToTranscript, -{ - if let Some(len) = payload.transcript_payload_len() { - transcript.append(&LabelWithCount(label, len)); - } else { - transcript.append(&Label(label)); - } - transcript.append(payload); -} - -/// 32-byte zero-padded label word used by the deployed proof format. -pub struct Label(pub &'static [u8]); - -impl AppendToTranscript for Label { - fn append_to_transcript(&self, transcript: &mut T) { - assert!( - self.0.len() <= 32, - "label {:?} exceeds 32 bytes", - core::str::from_utf8(self.0) - ); - let mut padded = [0u8; 32]; - let (head, _) = padded.split_at_mut(self.0.len()); - head.copy_from_slice(self.0); - transcript.append_bytes(&padded); - } -} - -/// Packed label (24 bytes) + count (8-byte big-endian) in one 32-byte word -/// used by the deployed proof format. -pub struct LabelWithCount(pub &'static [u8], pub u64); - -impl AppendToTranscript for LabelWithCount { - fn append_to_transcript(&self, transcript: &mut T) { - assert!( - self.0.len() <= 24, - "label {:?} exceeds 24 bytes", - core::str::from_utf8(self.0) - ); - let mut packed = [0u8; 32]; - let (head, _) = packed.split_at_mut(self.0.len()); - head.copy_from_slice(self.0); - packed[24..32].copy_from_slice(&self.1.to_be_bytes()); - transcript.append_bytes(&packed); - } -} - -/// EVM-compatible left-padded u64: 24 zero bytes + 8-byte BE value. -pub struct U64Word(pub u64); - -impl AppendToTranscript for U64Word { - fn append_to_transcript(&self, transcript: &mut T) { - let mut packed = [0u8; 32]; - packed[24..].copy_from_slice(&self.0.to_be_bytes()); - transcript.append_bytes(&packed); - } -} - -/// Sponge-backed transcript driving a duplex sponge directly. -/// -/// The legacy facade does not produce or consume a NARG byte string — -/// existing modular consumers only call `append_bytes` / `challenge` / -/// `state`. New code should use [`crate::ProverTranscript`] / -/// [`crate::VerifierTranscript`] instead. -/// -/// Construction initializes the protocol session and absorbs the -/// length-prefixed label as a non-empty instance. -#[cfg(feature = "spongefish")] -pub struct SpongeTranscript -where - H: DuplexSpongeInit + Clone + Default + Send + Sync + 'static, - F: CanonicalEncoding, -{ - sponge: H, - /// Challenges drawn since the last absorb; sets the next one's width. - draws: u64, - _field: PhantomData, -} - -#[cfg(feature = "spongefish")] -impl Clone for SpongeTranscript -where - H: DuplexSpongeInit + Clone + Default + Send + Sync + 'static, - F: CanonicalEncoding, -{ - fn clone(&self) -> Self { - Self { - sponge: self.sponge.clone(), - draws: self.draws, - _field: PhantomData, - } - } -} - -#[cfg(feature = "spongefish")] -impl Default for SpongeTranscript -where - H: DuplexSpongeInit + Clone + Default + Send + Sync + 'static, - F: CanonicalEncoding, -{ - fn default() -> Self { - Self::new(b"") - } -} - -/// Peeks 32 bytes from a clone of the sponge so the real state stays put. -#[cfg(feature = "spongefish")] -fn peek_state + Clone>(sponge: &H) -> [u8; 32] { - let mut clone = sponge.clone(); - let mut buf = [0u8; 32]; - let _ = clone.squeeze(&mut buf); - buf -} - -#[cfg(feature = "spongefish")] -impl Transcript for SpongeTranscript -where - H: DuplexSpongeInit + Clone + Default + Send + Sync + 'static, - F: CanonicalEncoding, -{ - type Challenge = F; - - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::from_limbs(F::CHALLENGE_SET_LIMBS); - - fn new(label: &'static [u8]) -> Self { - assert!( - label.len() <= MAX_LABEL_LEN, - "label must be at most {MAX_LABEL_LEN} bytes", - ); - let mut sponge = H::init(session_id().as_bytes()); - let _ = sponge.absorb(BytesMsg(label.to_vec()).encode().as_ref()); - Self { - sponge, - draws: 0, - _field: PhantomData, - } - } - - fn append_bytes(&mut self, bytes: &[u8]) { - // 1-byte non-zero domain marker + 8-byte LE length + body. - // - // The marker sub-domain-separates legacy-facade `append_bytes` calls - // from spongefish-native `public_message` / `prover_message` calls on - // the same sponge type, so a future protocol that mixes both paths - // can't have a legacy append collide with a spongefish-native - // BytesMsg of the same body. The length prefix keeps - // `append_bytes(a) ; append_bytes(b)` distinct from - // `append_bytes(a || b)`. - const APPEND_MARKER: u8 = 0x9B; - let mut buf = Vec::with_capacity(9 + bytes.len()); - buf.push(APPEND_MARKER); - buf.extend_from_slice(&(bytes.len() as u64).to_le_bytes()); - buf.extend_from_slice(bytes); - let _ = self.sponge.absorb(&buf); - self.draws = 0; - } - - fn challenge(&mut self) -> F { - // WARNING: this decodes squeezed bytes for every sponge — including - // Poseidon, whose byte output is not uniform — while the split-trait - // surface (`ChallengeCodec`) admits only uniform-byte sponges. The - // legacy facade keeps byte challenges for in-flight consumers - // (jolt-sumcheck, jolt-openings, jolt-crypto). Once those migrate to - // the split-trait surface this facade goes away and the - // inconsistency with it. - self.draws = self.draws.saturating_add(1); - let mut buf = vec![0u8; F::challenge_width(self.draws)]; - let _ = self.sponge.squeeze(&mut buf); - F::from_challenge_bytes(&buf) - } - - fn challenge_bytes(&mut self, out: &mut [u8]) { - let _ = self.sponge.squeeze(out); - } - - fn state(&self) -> [u8; 32] { - peek_state(&self.sponge) - } -} diff --git a/crates/jolt-transcript/src/lib.rs b/crates/jolt-transcript/src/lib.rs index cc452b549b..a3047be05a 100644 --- a/crates/jolt-transcript/src/lib.rs +++ b/crates/jolt-transcript/src/lib.rs @@ -1,19 +1,18 @@ //! Fiat-Shamir transcripts for Jolt, backed by spongefish. //! -//! Two surfaces: +//! The prover runs spongefish's `ProverState` and the verifier its +//! `VerifierState`. [`Transcript`] and [`FieldTranscript`] are the operations +//! both sides share: public messages, field challenges through each field's +//! [`ChallengeCodec`], and proof-of-work steps whose nonce the proof carries. +//! [`ProverTranscript`] and [`VerifierTranscript`] add the NARG string. +//! [`prover_transcript`] and [`verifier_transcript`] bind the session and the +//! public statement before the first message. //! -//! - **Split spongefish-native traits** ([`ProverTranscript`], -//! [`VerifierTranscript`]) — implemented directly -//! on `spongefish::ProverState` / `spongefish::VerifierState`. Use these -//! for new code. -//! - **Source-compatible facade** ([`Transcript`], [`AppendToTranscript`], -//! [`Blake2bTranscript`], [`KeccakTranscript`], [`PoseidonTranscript`]) — -//! preserved for `jolt-sumcheck`, `jolt-openings`, and `jolt-crypto` while -//! those crates migrate to the split-trait surface. -//! -//! Three sponges feature-gated: `transcript-blake2b` (spongefish -//! `Blake2b512`), `transcript-keccak` (spongefish `Keccak`), -//! `transcript-poseidon` (local Circom-compatible BN254 [`PoseidonSponge`]). +//! Three sponges are feature-gated: `transcript-blake2b` (spongefish +//! `Blake2b512`), `transcript-keccak` (spongefish `Keccak`) and +//! `transcript-poseidon` (a Circom-compatible BN254 [`PoseidonSponge`]). +//! Field challenges need uniform squeezed bytes ([`UniformBytes`]), which +//! Poseidon does not give, so a Poseidon transcript cannot draw them. #![deny(missing_docs)] // In the jolt-verifier runtime closure: stricter panic and unsafe discipline @@ -33,104 +32,44 @@ )] mod challenge; -#[cfg(feature = "spongefish")] mod codec; -#[cfg(feature = "digest")] -mod digest; mod grind_bits; -mod legacy; #[cfg(feature = "transcript-poseidon")] mod poseidon; mod pow; -#[cfg(feature = "spongefish")] mod prover; -#[cfg(feature = "spongefish")] mod setup; +mod transcript; #[cfg(feature = "test-utils")] mod unpriced; -#[cfg(feature = "spongefish")] mod verifier; -#[cfg(feature = "spongefish")] -pub use challenge::UniformBytes; -pub use challenge::{ChallengeCodec, MAX_CHALLENGE_BYTES}; -#[cfg(feature = "spongefish")] -pub use codec::BytesMsg; -#[cfg(feature = "digest")] -pub use digest::DigestTranscript; +pub use challenge::{ChallengeCodec, UniformBytes, MAX_CHALLENGE_BYTES}; +pub use codec::{BytesMsg, Label, LabelWithCount, U64Word, MAX_LABEL_LEN}; pub use grind_bits::{ grind_bits_for_loss, ChallengeSetSize, GrindBitsError, MAX_POW_BITS, SECURITY_BITS, }; -#[cfg(feature = "spongefish")] -pub use legacy::SpongeTranscript; -pub use legacy::{ - append_length_prefixed, AppendToTranscript, Label, LabelWithCount, Transcript, U64Word, - MAX_LABEL_LEN, -}; -pub use pow::ProofOfWorkError; +#[cfg(feature = "transcript-poseidon")] +pub use poseidon::PoseidonSponge; #[cfg(feature = "prover-grinding")] pub use pow::{MessageSearch, Probe}; -#[cfg(feature = "spongefish")] +pub use prover::ProverTranscript; pub use setup::{prover_transcript, session_id, verifier_transcript, PROTOCOL_ID}; +pub use spongefish::{Encoding, ProverState, VerificationError, VerifierState}; +pub use transcript::{FieldTranscript, Transcript}; #[cfg(feature = "test-utils")] pub use unpriced::UnpricedTranscript; - -/// Source-compatible re-exports of legacy label / count / word helpers -/// under their `jolt_transcript::domain::*` path (matches the path used -/// by jolt-dory and earlier modular consumers). -pub mod domain { - pub use crate::legacy::{Label, LabelWithCount, U64Word}; -} - -#[cfg(feature = "transcript-poseidon")] -pub use poseidon::PoseidonSponge; -#[cfg(feature = "spongefish")] -pub use prover::ProverTranscript; -#[cfg(feature = "spongefish")] pub use verifier::VerifierTranscript; #[cfg(feature = "transcript-blake2b")] -use blake2::{digest::consts::U32, Blake2b, Blake2b512 as Blake2b512Digest}; -#[cfg(all( - feature = "bn254", - any( - feature = "transcript-blake2b", - feature = "transcript-keccak", - feature = "transcript-poseidon" - ) -))] -use jolt_field::Fr; +use blake2::Blake2b512 as Blake2b512Digest; #[cfg(feature = "transcript-blake2b")] use spongefish::instantiations::Hash; -#[cfg(feature = "transcript-keccak")] -use spongefish::instantiations::Keccak; /// Spongefish's fixed-output hash bridge instantiated with Blake2b-512. #[cfg(feature = "transcript-blake2b")] pub type Blake2b512 = Hash; -/// Fiat-Shamir transcript backed by Blake2b-512 (spongefish duplex sponge). -#[cfg(all(feature = "transcript-blake2b", feature = "bn254"))] -pub type Blake2bTranscript = SpongeTranscript; -/// Fiat-Shamir transcript backed by Blake2b-512 for an explicitly selected field. -#[cfg(all(feature = "transcript-blake2b", not(feature = "bn254")))] -pub type Blake2bTranscript = SpongeTranscript; - -/// Blake2b-256 chained-digest transcript used by the deployed proof format. -/// New protocols should use [`Blake2bTranscript`] instead. -#[cfg(all(feature = "transcript-blake2b", feature = "bn254"))] -pub type LegacyBlake2bTranscript = DigestTranscript, F>; -/// Legacy Blake2b-256 transcript for an explicitly selected field. -#[cfg(all(feature = "transcript-blake2b", not(feature = "bn254")))] -pub type LegacyBlake2bTranscript = DigestTranscript, F>; - -/// Fiat-Shamir transcript backed by Keccak-f1600 (spongefish duplex sponge). -#[cfg(all(feature = "transcript-keccak", feature = "bn254"))] -pub type KeccakTranscript = SpongeTranscript; -/// Fiat-Shamir transcript backed by Keccak-f1600 for an explicitly selected field. -#[cfg(all(feature = "transcript-keccak", not(feature = "bn254")))] -pub type KeccakTranscript = SpongeTranscript; - -/// Fiat-Shamir transcript backed by Circom-compatible BN254 Poseidon. -#[cfg(feature = "transcript-poseidon")] -pub type PoseidonTranscript = SpongeTranscript; +/// Spongefish's Keccak-f[1600] duplex sponge. +#[cfg(feature = "transcript-keccak")] +pub use spongefish::instantiations::Keccak; diff --git a/crates/jolt-transcript/src/pow.rs b/crates/jolt-transcript/src/pow.rs index cc80e5fad7..6589d70105 100644 --- a/crates/jolt-transcript/src/pow.rs +++ b/crates/jolt-transcript/src/pow.rs @@ -19,16 +19,14 @@ //! binary that links a grinding prover compiles its verifier with //! `yolocrypto` too; in 0.8.0 the feature changes visibility only, not bytes. //! -//! The legacy [`Transcript`](crate::Transcript) facade carries the same step -//! with the nonce outside the transcript ([`Transcript::grind`] and -//! [`Transcript::check_grind`]). It copies its own state, so it needs neither -//! feature. +//! A proof that carries its nonces outside the NARG string runs the same step +//! with the nonce absorbed as a public message +//! ([`Transcript::check_public_grind`] and [`ProverTranscript::public_grind`]). //! -//! [`Transcript::grind`]: crate::Transcript::grind -//! [`Transcript::check_grind`]: crate::Transcript::check_grind +//! [`Transcript::check_public_grind`]: crate::Transcript::check_public_grind +//! [`ProverTranscript::public_grind`]: crate::ProverTranscript::public_grind -use std::error::Error; -use std::fmt::{Display, Formatter, Result as FmtResult}; +#[cfg(feature = "prover-grinding")] use std::sync::atomic::{AtomicU64, Ordering}; #[cfg(feature = "prover-grinding")] @@ -36,20 +34,7 @@ use spongefish::DuplexSpongeInterface; use crate::MAX_POW_BITS; -/// A proof of work was rejected: its difficulty is above [`MAX_POW_BITS`], its -/// nonce is missing, unexpected or out of range, or the nonce fails the check. -/// On the prover side, the nonce search exhausted the nonce space. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ProofOfWorkError; - -impl Display for ProofOfWorkError { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - f.write_str("proof of work rejected") - } -} - -impl Error for ProofOfWorkError {} - +#[cfg(feature = "prover-grinding")] /// Evaluates the transcript on a candidate prover message without sending it. /// /// A probe is [`Sync`], so a search may run on @@ -128,7 +113,6 @@ impl SpongePow { } /// `ceil((bits + 7) / 7)` bytes encode every nonce, at most 5. - #[cfg(feature = "spongefish")] pub(crate) const fn max_nonce_len(self) -> usize { (self.bits as usize + 13) / 7 } @@ -152,7 +136,6 @@ impl SpongePow { /// Reads a nonce byte by byte through `next`. Returns `None` unless its /// encoding is canonical LEB128 and it is at most `last_nonce`; read /// errors pass through. - #[cfg(feature = "spongefish")] pub(crate) fn read_nonce( self, mut next: impl FnMut() -> Result, @@ -173,6 +156,7 @@ impl SpongePow { } /// Whether sending `nonce` makes the next squeezed bytes pass the check. + #[cfg(feature = "prover-grinding")] fn accepts_nonce(self, probe: &impl Probe, nonce: u32) -> bool { let mut check = [0u8; 4]; let Some(out) = check.get_mut(..self.word_len()) else { @@ -187,6 +171,7 @@ impl SpongePow { /// pool scans the rest. Builds without `parallel` scan everything on the /// calling thread. Either way the result is the least accepted nonce, so /// the proof does not depend on the thread count. + #[cfg(feature = "prover-grinding")] pub(crate) fn search(self, probe: &impl Probe) -> Option { let last = self.last_nonce(); // `u64::MAX` is no nonce, so it marks "not found". @@ -211,6 +196,7 @@ impl SpongePow { /// Scans the nonces `first, first + stride, ...` up to `last` in /// increasing order and lowers `least` to the first one accepted. Stops /// once it cannot beat `least`, which other scans may lower meanwhile. + #[cfg(feature = "prover-grinding")] fn scan(self, probe: &impl Probe, first: u64, stride: usize, last: u32, least: &AtomicU64) { let nonces = (first..=u64::from(last)) .step_by(stride) @@ -268,7 +254,6 @@ mod tests { assert!(SpongePow::new(MAX_POW_BITS + 1).is_none()); } - #[cfg(feature = "spongefish")] #[test] fn layout() { let layout = |bits| { @@ -281,7 +266,6 @@ mod tests { assert_eq!(layout(25), (u32::MAX, 5, 4)); } - #[cfg(feature = "spongefish")] #[test] fn read_nonce_accepts_only_canonical_nonces_in_range() { let read = |bits, bytes: &[u8]| { @@ -308,10 +292,9 @@ mod tests { assert_eq!(read(8, &[0x80]), Err("short")); } + #[cfg(feature = "prover-grinding")] mod search { - #[cfg(feature = "spongefish")] - use super::super::MAX_POW_BITS; - use super::super::{Leb128, Probe, SpongePow}; + use super::super::{Leb128, Probe, SpongePow, MAX_POW_BITS}; #[test] fn leb128_is_canonical() { @@ -321,7 +304,6 @@ mod tests { assert_eq!(encode(128), [0x80, 0x01]); assert_eq!(encode(300), [0xac, 0x02]); assert_eq!(encode(u32::MAX), [0xff, 0xff, 0xff, 0xff, 0x0f]); - #[cfg(feature = "spongefish")] for bits in 1..=MAX_POW_BITS { let pow = SpongePow::new(bits).unwrap(); let longest = Leb128::new(pow.last_nonce()); diff --git a/crates/jolt-transcript/src/prover.rs b/crates/jolt-transcript/src/prover.rs index 1d0e109f60..ddc875f347 100644 --- a/crates/jolt-transcript/src/prover.rs +++ b/crates/jolt-transcript/src/prover.rs @@ -1,46 +1,23 @@ -//! Spongefish-native [`ProverTranscript`] surface. -//! -//! Implemented directly on `spongefish::ProverState` via the orphan -//! rule. Methods are positional, matching spongefish-native usage -//! (WhiR, sigma-rs). +//! Prover-only operations on `spongefish::ProverState`. #[cfg(feature = "prover-grinding")] use crate::pow::{Leb128, MessageSearch, SpongePow}; -use crate::{challenge::message_bytes, ChallengeCodec, UniformBytes}; -use spongefish::{ - DuplexSpongeInit, DuplexSpongeInterface, Encoding, FromUniform, ProverState, VerificationError, -}; +use crate::Transcript; +#[cfg(feature = "prover-grinding")] +use spongefish::VerificationError; +use spongefish::{DuplexSpongeInit, DuplexSpongeInterface, Encoding, ProverState}; -/// Prover-side spongefish transcript. -/// -/// `H::U` is the sponge alphabet (`u8` for every sponge in this crate). -pub trait ProverTranscript { - /// Absorbs `msg` symmetrically with the verifier; emits no NARG bytes. - fn public_message + ?Sized>(&mut self, msg: &T); +/// The prover's side of a [`Transcript`]: messages written to the NARG string +/// and the proof-of-work nonce searches. +pub trait ProverTranscript: Transcript { + /// The duplex sponge behind the transcript. + type Sponge: DuplexSpongeInterface; /// Absorbs `msg` and appends its NARG-serialized form for verifier replay. - fn prover_message + Encoding + ?Sized>(&mut self, msg: &T); - - /// Squeezes a verifier challenge. - fn verifier_message>(&mut self) -> T; + fn prover_message(&mut self, msg: &M); - /// Draws one message with conventional TV ≤ 2^-161 across all `len` elements. - /// The protocol session/version must bind the codec, field, and length. - /// `len == 0` returns an empty vector without touching the sponge. Only - /// sponges with uniform output qualify ([`UniformBytes`]); `PoseidonSponge` - /// does not. - /// - /// # Errors - /// Returns `VerificationError`, before any sponge operation, when the codec - /// does not support `len` or the message would exceed - /// [`MAX_CHALLENGE_BYTES`](crate::MAX_CHALLENGE_BYTES) bytes. Otherwise on - /// rejection-pool exhaustion (probability at most 2^-161). - fn field_challenges( - &mut self, - len: usize, - ) -> Result, VerificationError> - where - H: UniformBytes; + /// Bytes accumulated in the NARG so far. + fn narg_string(&self) -> &[u8]; /// A copy of the public sponge, for searching over the next prover /// message: a proof-of-work nonce, or a retry counter whose following @@ -50,9 +27,9 @@ pub trait ProverTranscript { /// Needs the `prover-grinding` feature, which enables spongefish's /// `yolocrypto` to copy the sponge state. #[cfg(feature = "prover-grinding")] - fn message_search(&self) -> MessageSearch; + fn message_search(&self) -> MessageSearch; - /// A proof of work of `bits` bits on this transcript's own sponge. + /// A proof of work of `bits` bits whose nonce goes into the NARG string. /// /// For `bits == 0` this does nothing. For `1 <= bits <= MAX_POW_BITS`, the /// prover sends the least nonce below `2^(bits + 7)` for which the next @@ -65,9 +42,6 @@ pub trait ProverTranscript { /// directly after this step: a prover message in between would let the /// prover change them without redoing the work. /// - /// Needs the `prover-grinding` feature, which enables spongefish's - /// `yolocrypto` to copy the sponge state for the nonce search. - /// /// # Security /// /// With an ideal sponge, each attempt passes the check with probability @@ -85,44 +59,35 @@ pub trait ProverTranscript { /// after the nonce is sent, if the real check disagrees with the search; /// that means the sponge's absorb is not what the probe replays, a bug. #[cfg(feature = "prover-grinding")] - fn grind(&mut self, bits: u32) -> Result<(), VerificationError> - where - H: DuplexSpongeInterface + Sync; + fn grind(&mut self, bits: u32) -> Result<(), VerificationError>; - /// Bytes accumulated in the NARG so far. - fn narg_string(&self) -> &[u8]; + /// A proof of work of `bits` bits whose nonce the proof carries outside the + /// NARG string. Returns that nonce, or `None` for `bits == 0`, which leaves + /// the transcript unchanged. + /// + /// The nonce is the least one below `2^(bits + 7)` that passes + /// [`check_public_grind`](Transcript::check_public_grind), which is then + /// applied to this transcript. + /// + /// # Errors + /// As [`grind`](Self::grind). + #[cfg(feature = "prover-grinding")] + fn public_grind(&mut self, bits: u32) -> Result, VerificationError>; } -impl ProverTranscript for ProverState +impl ProverTranscript for ProverState where - H: DuplexSpongeInterface, + H: DuplexSpongeInterface + Sync, R: DuplexSpongeInit, { - fn public_message + ?Sized>(&mut self, msg: &T) { - ProverState::public_message(self, msg); - } + type Sponge = H; - fn prover_message + Encoding + ?Sized>(&mut self, msg: &T) { + fn prover_message(&mut self, msg: &M) { ProverState::prover_message(self, msg); } - fn verifier_message>(&mut self) -> T { - ProverState::verifier_message::(self) - } - - fn field_challenges( - &mut self, - len: usize, - ) -> Result, VerificationError> - where - H: UniformBytes, - { - let n = message_bytes::(len)?; - if len == 0 { - return Ok(Vec::new()); - } - ProverState::verifier_message_as(self, n, |bytes| F::decode_challenges(bytes, len)) - .ok_or(VerificationError) + fn narg_string(&self) -> &[u8] { + ProverState::narg_string(self) } #[cfg(feature = "prover-grinding")] @@ -131,16 +96,13 @@ where } #[cfg(feature = "prover-grinding")] - fn grind(&mut self, bits: u32) -> Result<(), VerificationError> - where - H: DuplexSpongeInterface + Sync, - { + fn grind(&mut self, bits: u32) -> Result<(), VerificationError> { if bits == 0 { return Ok(()); } let pow = SpongePow::new(bits).ok_or(VerificationError)?; let nonce = pow - .search(&ProverTranscript::message_search(self)) + .search(&self.message_search()) .ok_or(VerificationError)?; for byte in Leb128::new(nonce).as_bytes() { ProverState::prover_message(self, byte); @@ -152,7 +114,16 @@ where } } - fn narg_string(&self) -> &[u8] { - ProverState::narg_string(self) + #[cfg(feature = "prover-grinding")] + fn public_grind(&mut self, bits: u32) -> Result, VerificationError> { + if bits == 0 { + return Ok(None); + } + let pow = SpongePow::new(bits).ok_or(VerificationError)?; + let nonce = pow + .search(&self.message_search()) + .ok_or(VerificationError)?; + self.check_public_grind(bits, Some(nonce))?; + Ok(Some(nonce)) } } diff --git a/crates/jolt-transcript/src/setup.rs b/crates/jolt-transcript/src/setup.rs index 8e6f1466f2..5bc77eeecf 100644 --- a/crates/jolt-transcript/src/setup.rs +++ b/crates/jolt-transcript/src/setup.rs @@ -1,13 +1,11 @@ //! Native spongefish transcript initialization. //! //! The session identifier is derived from [`PROTOCOL_ID`]. Each transcript -//! absorbs one instance encoding: the length-prefixed caller session followed -//! by the 32-byte public-statement digest. +//! absorbs one instance: the length-prefixed caller session followed by the +//! length-prefixed encoding of the public statement. use spongefish::instantiations::TurboShake128; -use spongefish::{DuplexSpongeInit, ProverState, SessionId, VerifierState}; - -use crate::codec::BytesMsg; +use spongefish::{DuplexSpongeInit, Encoding, ProverState, SessionId, VerifierState}; /// Crate-wide protocol identifier (ASCII left, zero-padded to 64 bytes). /// [`session_id`] derives the spongefish session identifier from it. @@ -30,63 +28,77 @@ const fn pad_id(src: &[u8]) -> [u8; 64] { buf } -fn transcript_instance(session: &[u8], digest: [u8; 32]) -> (BytesMsg, [u8; 32]) { - (BytesMsg(session.to_vec()), digest) +/// The spongefish instance: the session and the statement, each with an +/// eight-byte little-endian length. Never empty, so spongefish's constructors +/// accept it, and self-delimiting, so the statement cannot run into the first +/// message. +struct Instance<'a, S: ?Sized> { + session: &'a [u8], + statement: &'a S, +} + +impl Encoding for Instance<'_, S> { + fn encode(&self) -> impl AsRef<[u8]> { + let statement = self.statement.encode(); + let statement = statement.as_ref(); + let mut out = Vec::with_capacity(16 + self.session.len() + statement.len()); + out.extend_from_slice(&(self.session.len() as u64).to_le_bytes()); + out.extend_from_slice(self.session); + out.extend_from_slice(&(statement.len() as u64).to_le_bytes()); + out.extend_from_slice(statement); + out + } } -/// Build a prover transcript bound to `session` and `instance`. +/// Build a prover transcript bound to `session` and `statement`. /// /// `session` is protocol-version bytes (e.g. `b"jolt-rv64imac/v1"`). -/// `instance` is the 32-byte digest of the public statement. Together they -/// encode the single spongefish instance, which is non-empty even when -/// `session` is empty. Spongefish initializes a fresh state of backend `H` -/// with [`DuplexSpongeInit::init`]. +/// `statement` is the public statement, which the encoding must determine +/// completely: everything the verifier is given rather than reads from the +/// proof. Spongefish initializes a fresh state of backend `H` with +/// [`DuplexSpongeInit::init`]. /// /// # Example /// /// ``` -/// use jolt_transcript::{prover_transcript, verifier_transcript, BytesMsg, ProverTranscript, VerifierTranscript}; -/// use spongefish::instantiations::Hash; -/// use blake2::Blake2b512; +/// use jolt_transcript::{ +/// prover_transcript, verifier_transcript, Blake2b512, BytesMsg, ProverTranscript, +/// VerifierTranscript, +/// }; /// -/// type Sponge = Hash; /// const SESSION: &[u8] = b"jolt-rv64imac/v1"; -/// let instance: [u8; 32] = [0xAB; 32]; +/// let statement: [u8; 32] = [0xAB; 32]; /// -/// let mut prover = prover_transcript::(SESSION, instance); -/// ProverTranscript::::prover_message( -/// &mut prover, -/// &BytesMsg(b"commitment-a".to_vec()), -/// ); -/// let narg = ProverTranscript::::narg_string(&prover).to_vec(); +/// let mut prover = prover_transcript::(SESSION, &statement); +/// prover.prover_message(&BytesMsg(b"commitment-a".to_vec())); +/// let narg = prover.narg_string().to_vec(); /// -/// let mut verifier = verifier_transcript::(SESSION, instance, &narg); -/// let _: BytesMsg = -/// VerifierTranscript::::prover_message(&mut verifier).unwrap(); -/// VerifierTranscript::::check_eof(verifier).unwrap(); +/// let mut verifier = verifier_transcript::(SESSION, &statement, &narg); +/// let _: BytesMsg = verifier.prover_message().unwrap(); +/// verifier.check_eof().unwrap(); /// ``` #[must_use] -pub fn prover_transcript(session: &[u8], instance: [u8; 32]) -> ProverState +pub fn prover_transcript(session: &[u8], statement: &(impl Encoding + ?Sized)) -> ProverState where H: DuplexSpongeInit, { - ProverState::new(&session_id(), &transcript_instance(session, instance)) + ProverState::new(&session_id(), &Instance { session, statement }) } -/// Build a verifier transcript bound to `session` and `instance` over -/// `narg`. The verifier MUST pass the same `session`/`instance` as the +/// Build a verifier transcript bound to `session` and `statement` over +/// `narg`. The verifier MUST pass the same `session`/`statement` as the /// prover or the transcript states diverge. Spongefish initializes a fresh /// state of backend `H` with [`DuplexSpongeInit::init`]. #[must_use] pub fn verifier_transcript<'a, H>( session: &[u8], - instance: [u8; 32], + statement: &(impl Encoding + ?Sized), narg: &'a [u8], ) -> VerifierState<'a, H> where H: DuplexSpongeInit, { - VerifierState::new(&session_id(), &transcript_instance(session, instance), narg) + VerifierState::new(&session_id(), &Instance { session, statement }, narg) } /// Derive the spongefish session identifier from [`PROTOCOL_ID`] using @@ -116,21 +128,30 @@ mod tests { } #[test] - fn instance_encoding_frames_session_and_preserves_digest() { - use spongefish::Encoding; - + fn instance_encoding_frames_session_and_statement() { let digest = [0xA5u8; 32]; - let instance = transcript_instance(b"", digest); - let encoded = instance.encode(); + let encoded = Instance { + session: b"", + statement: &digest, + } + .encode() + .as_ref() + .to_vec(); let mut expected = vec![0; 8]; + expected.extend_from_slice(&32u64.to_le_bytes()); expected.extend_from_slice(&digest); - assert_eq!(encoded.as_ref(), expected); + assert_eq!(encoded, expected); - let instance = transcript_instance(b"session", digest); - let encoded = instance.encode(); + let encoded = Instance { + session: b"session", + statement: &[0u8; 0], + } + .encode() + .as_ref() + .to_vec(); let mut expected = 7u64.to_le_bytes().to_vec(); expected.extend_from_slice(b"session"); - expected.extend_from_slice(&digest); - assert_eq!(encoded.as_ref(), expected); + expected.extend_from_slice(&0u64.to_le_bytes()); + assert_eq!(encoded, expected); } } diff --git a/crates/jolt-transcript/src/transcript.rs b/crates/jolt-transcript/src/transcript.rs new file mode 100644 index 0000000000..ed549882bc --- /dev/null +++ b/crates/jolt-transcript/src/transcript.rs @@ -0,0 +1,247 @@ +//! The operations the prover's and verifier's spongefish states share. +//! +//! [`Transcript`] absorbs public messages and checks proof-of-work nonces; +//! [`FieldTranscript`] draws field challenges. Both are implemented on +//! spongefish's [`ProverState`] and [`VerifierState`] from three sealed +//! sponge operations, so every method has one implementation that the two +//! sides run alike. The traits are sealed: a transcript that drew challenges +//! some other way would void the codecs' bounds. +//! +//! A public message is absorbed by both sides and writes no proof bytes. A +//! protocol whose proof is a serialized structure absorbs each field it reads +//! from that structure as a public message; spongefish absorbs the same bytes +//! for a public message and for a prover message, so the challenges are those +//! of the same protocol with the field sent through the NARG string. + +use jolt_field::Ring; +use spongefish::{ + DuplexSpongeInit, DuplexSpongeInterface, Encoding, ProverState, Transcript as _, + VerificationError, VerifierState, +}; + +use crate::challenge::message_bytes; +use crate::codec::{BytesMsg, Label, LabelWithCount}; +use crate::pow::{Leb128, SpongePow}; +use crate::{ChallengeCodec, ChallengeSetSize, UniformBytes}; + +pub(crate) mod sealed { + use spongefish::{Encoding, VerificationError}; + + /// The three sponge operations every transcript method is built from. + pub trait Sealed { + /// Absorbs `message` as a public message. + fn absorb(&mut self, message: &M); + + /// Squeezes `len` bytes and decodes them with `decode`. + fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[u8]) -> T) -> T; + + /// `Ok` when `ok` holds. On failure the verifier also poisons its + /// state, so later reads and `check_eof` fail. + fn require(&mut self, ok: bool) -> Result<(), VerificationError>; + } +} + +/// A Fiat-Shamir transcript: spongefish's prover or verifier state. +/// +/// Every method is provided once and runs the same sponge operations on both +/// sides. +pub trait Transcript: sealed::Sealed { + /// Absorbs `message` as a public message. + fn append(&mut self, message: &M) { + self.absorb(message); + } + + /// Absorbs a [`Label`] word, then `message`. + fn append_labeled(&mut self, label: &'static [u8], message: &M) { + self.absorb(&Label(label)); + self.absorb(message); + } + + /// Absorbs a [`LabelWithCount`] word carrying `values.len()`, then each + /// value in order. + fn append_values(&mut self, label: &'static [u8], values: &[M]) { + self.absorb(&LabelWithCount(label, values.len() as u64)); + for value in values { + self.absorb(value); + } + } + + /// Absorbs `bytes` as a length-prefixed [`BytesMsg`]. + fn append_bytes(&mut self, bytes: &[u8]) { + self.absorb(&BytesMsg(bytes)); + } + + /// Checks a proof of work of `bits` bits whose nonce the proof carries + /// outside the NARG string. + /// + /// For `bits == 0` the proof carries no nonce and the transcript is + /// unchanged. For `1 <= bits <= MAX_POW_BITS` the nonce must be at most + /// `2^(bits + 7) - 1`; its canonical LEB128 encoding is absorbed one byte + /// per public message, then `ceil(bits / 8)` bytes are squeezed and their + /// low `bits` bits must be zero. This is the layout of + /// [`VerifierTranscript::check_grind`](crate::VerifierTranscript::check_grind) + /// with the nonce absorbed as a public message instead of read from the + /// NARG string; the sponge absorbs the same bytes. + /// + /// The step protects the challenges drawn after it, up to the next + /// absorbed message. The difficulty and the position of the step must be + /// fixed by the protocol. + /// + /// # Errors + /// `VerificationError` if `bits > MAX_POW_BITS`, if a nonce is missing, + /// present at `bits == 0` or out of range, or if it fails the check. The + /// verifier's state is poisoned then, so `check_eof` fails too. + fn check_public_grind( + &mut self, + bits: u32, + nonce: Option, + ) -> Result<(), VerificationError> { + let Some(pow) = SpongePow::new(bits) else { + return self.require(bits == 0 && nonce.is_none()); + }; + let Some(nonce) = nonce.filter(|&nonce| nonce <= pow.last_nonce()) else { + return self.require(false); + }; + for byte in Leb128::new(nonce).as_bytes() { + self.absorb(byte); + } + let passes = self.squeeze(pow.word_len(), |check| pow.accepts(check)); + self.require(passes) + } +} + +impl Transcript for T {} + +/// A transcript that draws challenges in `F`. +pub trait FieldTranscript: Transcript { + /// Size of the set every challenge is drawn from: the codec's + /// [`CHALLENGE_SET_SIZE`](ChallengeCodec::CHALLENGE_SET_SIZE). Prices the + /// proof of work before a challenge ([`grind_bits_for_loss`]). + /// + /// [`grind_bits_for_loss`]: crate::grind_bits_for_loss + const CHALLENGE_SET: ChallengeSetSize; + + /// Draws `len` challenges as one message of `F`'s [`ChallengeCodec`]. + /// + /// `len == 0` returns an empty vector without touching the sponge. + /// + /// # Errors + /// `VerificationError`, before any sponge operation, when the codec does + /// not support `len` or the message would exceed + /// [`MAX_CHALLENGE_BYTES`](crate::MAX_CHALLENGE_BYTES) bytes; otherwise on + /// rejection-pool exhaustion (probability at most 2^-161). The verifier's + /// state is poisoned then, so `check_eof` fails too. + fn challenge_vector(&mut self, len: usize) -> Result, VerificationError>; + + /// Draws one challenge, as a message of length one. + /// + /// # Errors + /// As [`challenge_vector`](Self::challenge_vector). + fn challenge(&mut self) -> Result { + self.challenge_vector(1)?.pop().ok_or(VerificationError) + } + + /// Draws one challenge `gamma` and returns `[1, gamma, ..., gamma^(len-1)]`. + /// `gamma` is drawn even for `len == 0`. + /// + /// # Errors + /// As [`challenge_vector`](Self::challenge_vector). + fn challenge_powers(&mut self, len: usize) -> Result, VerificationError> + where + F: Ring, + { + let gamma = self.challenge()?; + Ok( + std::iter::successors(Some(F::from_u64(1)), |power| Some(*power * gamma)) + .take(len) + .collect(), + ) + } +} + +/// One codec message of `len` challenges: the single implementation behind +/// every [`FieldTranscript`]. +pub(crate) fn draw_challenges( + transcript: &mut T, + len: usize, +) -> Result, VerificationError> +where + T: sealed::Sealed + ?Sized, + F: ChallengeCodec, +{ + // A length the codec rejects poisons the verifier too, so a caller that + // drops the error still fails `check_eof`. + let bytes = message_bytes::(len); + transcript.require(bytes.is_ok())?; + let bytes = bytes?; + if len == 0 { + return Ok(Vec::new()); + } + let challenges = transcript.squeeze(bytes, |tape| F::decode_challenges(tape, len)); + transcript.require(challenges.is_some())?; + challenges.ok_or(VerificationError) +} + +impl sealed::Sealed for ProverState +where + H: DuplexSpongeInterface, + R: DuplexSpongeInit, +{ + fn absorb(&mut self, message: &M) { + ProverState::public_message(self, message); + } + + fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[u8]) -> T) -> T { + ProverState::verifier_message_as(self, len, decode) + } + + fn require(&mut self, ok: bool) -> Result<(), VerificationError> { + if ok { + Ok(()) + } else { + Err(VerificationError) + } + } +} + +impl sealed::Sealed for VerifierState<'_, H> +where + H: DuplexSpongeInterface, +{ + fn absorb(&mut self, message: &M) { + VerifierState::public_message(self, message); + } + + fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[u8]) -> T) -> T { + VerifierState::verifier_message_as(self, len, decode) + } + + fn require(&mut self, ok: bool) -> Result<(), VerificationError> { + self.check(|| ok) + } +} + +impl FieldTranscript for ProverState +where + H: UniformBytes, + R: DuplexSpongeInit, + F: ChallengeCodec, +{ + const CHALLENGE_SET: ChallengeSetSize = F::CHALLENGE_SET_SIZE; + + fn challenge_vector(&mut self, len: usize) -> Result, VerificationError> { + draw_challenges(self, len) + } +} + +impl FieldTranscript for VerifierState<'_, H> +where + H: UniformBytes, + F: ChallengeCodec, +{ + const CHALLENGE_SET: ChallengeSetSize = F::CHALLENGE_SET_SIZE; + + fn challenge_vector(&mut self, len: usize) -> Result, VerificationError> { + draw_challenges(self, len) + } +} diff --git a/crates/jolt-transcript/src/unpriced.rs b/crates/jolt-transcript/src/unpriced.rs index a5163b4231..cb7506b312 100644 --- a/crates/jolt-transcript/src/unpriced.rs +++ b/crates/jolt-transcript/src/unpriced.rs @@ -1,8 +1,10 @@ //! A transcript wrapper that declares no proof of work, for tests only. -use jolt_field::Ring; - -use crate::{ChallengeSetSize, Transcript}; +#[cfg(feature = "prover-grinding")] +use crate::pow::MessageSearch; +use crate::transcript::{draw_challenges, sealed::Sealed}; +use crate::{ChallengeCodec, ChallengeSetSize, FieldTranscript, ProverTranscript}; +use spongefish::{Encoding, VerificationError}; /// `T` with its challenge set declared as `2^256`, so every difficulty from /// [`grind_bits_for_loss`](crate::grind_bits_for_loss) is 0: proofs carry no @@ -10,49 +12,56 @@ use crate::{ChallengeSetSize, Transcript}; /// without proof of work. /// /// Tests use it to reach checks that a hand-built proof would otherwise fail -/// at its first proof-of-work step, and to run fields whose challenge sets -/// cannot reach the target. It overstates `T`'s challenge set, so it gives -/// no soundness and must never back a real proof; the `test-utils` feature -/// that exports it is for dev-dependencies. -#[derive(Clone, Default)] +/// at its first proof-of-work step. It overstates `T`'s challenge set, so it +/// gives no soundness and must never back a real proof; the `test-utils` +/// feature that exports it is for dev-dependencies. pub struct UnpricedTranscript(pub T); -impl Transcript for UnpricedTranscript { - type Challenge = T::Challenge; - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); +impl Sealed for UnpricedTranscript { + fn absorb(&mut self, message: &M) { + self.0.absorb(message); + } - fn new(label: &'static [u8]) -> Self { - Self(T::new(label)) + fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[u8]) -> U) -> U { + self.0.squeeze(len, decode) } - fn append_bytes(&mut self, bytes: &[u8]) { - self.0.append_bytes(bytes); + fn require(&mut self, ok: bool) -> Result<(), VerificationError> { + self.0.require(ok) } +} - fn challenge(&mut self) -> Self::Challenge { - self.0.challenge() +impl, F: ChallengeCodec> FieldTranscript for UnpricedTranscript { + const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); + + fn challenge_vector(&mut self, len: usize) -> Result, VerificationError> { + draw_challenges(self, len) } +} + +impl ProverTranscript for UnpricedTranscript { + type Sponge = T::Sponge; - fn challenge_scalar(&mut self) -> Self::Challenge { - self.0.challenge_scalar() + fn prover_message(&mut self, msg: &M) { + self.0.prover_message(msg); } - fn challenge_vector(&mut self, len: usize) -> Vec { - self.0.challenge_vector(len) + fn narg_string(&self) -> &[u8] { + self.0.narg_string() } - fn challenge_scalar_powers(&mut self, len: usize) -> Vec - where - Self::Challenge: Ring, - { - self.0.challenge_scalar_powers(len) + #[cfg(feature = "prover-grinding")] + fn message_search(&self) -> MessageSearch { + self.0.message_search() } - fn challenge_bytes(&mut self, out: &mut [u8]) { - self.0.challenge_bytes(out); + #[cfg(feature = "prover-grinding")] + fn grind(&mut self, bits: u32) -> Result<(), VerificationError> { + self.0.grind(bits) } - fn state(&self) -> [u8; 32] { - self.0.state() + #[cfg(feature = "prover-grinding")] + fn public_grind(&mut self, bits: u32) -> Result, VerificationError> { + self.0.public_grind(bits) } } diff --git a/crates/jolt-transcript/src/verifier.rs b/crates/jolt-transcript/src/verifier.rs index e81910f249..aae3e1d2d1 100644 --- a/crates/jolt-transcript/src/verifier.rs +++ b/crates/jolt-transcript/src/verifier.rs @@ -1,42 +1,21 @@ -//! Spongefish-native [`VerifierTranscript`] surface. +//! Verifier-only operations on `spongefish::VerifierState`. -use crate::{challenge::message_bytes, pow::SpongePow, ChallengeCodec, UniformBytes}; +use crate::{pow::SpongePow, Transcript}; use spongefish::{ - DuplexSpongeInterface, Encoding, FromNarg, FromUniform, Transcript, VerificationError, - VerifierState, + DuplexSpongeInterface, Encoding, FromNarg, Transcript as _, VerificationError, VerifierState, }; -/// Verifier-side spongefish transcript. -pub trait VerifierTranscript { - /// Absorbs `msg` symmetrically with the prover. - fn public_message + ?Sized>(&mut self, msg: &T); - +/// The verifier's side of a [`Transcript`]: messages read from the NARG +/// string, and its end. +pub trait VerifierTranscript: Transcript { /// Reads a prover message from the NARG, absorbing it into the sponge. - fn prover_message + FromNarg>(&mut self) -> Result; - - /// Squeezes a verifier challenge. - fn verifier_message>(&mut self) -> T; - - /// Draws one message with conventional TV ≤ 2^-161 across all `len` elements. - /// The protocol session/version must bind the codec, field, and length. - /// `len == 0` returns an empty vector without touching the sponge. Only - /// sponges with uniform output qualify ([`UniformBytes`]); `PoseidonSponge` - /// does not. /// /// # Errors - /// Returns `VerificationError`, before any sponge operation, when the codec - /// does not support `len` or the message would exceed - /// [`MAX_CHALLENGE_BYTES`](crate::MAX_CHALLENGE_BYTES) bytes. Otherwise on - /// rejection-pool exhaustion (probability at most 2^-161). Every failure - /// poisons the state, so later reads and `check_eof` fail. - fn field_challenges( - &mut self, - len: usize, - ) -> Result, VerificationError> - where - H: UniformBytes; + /// `VerificationError` on a truncated or malformed message; the state is + /// poisoned then. + fn prover_message(&mut self) -> Result; - /// Checks a proof of work of `bits` bits on this transcript's own sponge. + /// Checks a proof of work of `bits` bits whose nonce is in the NARG string. /// /// For `bits == 0` this does nothing. For `1 <= bits <= MAX_POW_BITS`, it /// reads a nonce below `2^(bits + 7)` as canonical LEB128, one byte per @@ -67,9 +46,7 @@ pub trait VerifierTranscript { /// `bits > MAX_POW_BITS`. Otherwise on a truncated, non-canonical or /// out-of-range nonce, or one whose check bytes are not zero. Every /// failure poisons the state, so later reads and `check_eof` fail. - fn check_grind(&mut self, bits: u32) -> Result<(), VerificationError> - where - H: DuplexSpongeInterface; + fn check_grind(&mut self, bits: u32) -> Result<(), VerificationError>; /// Asserts the NARG was fully consumed. /// @@ -84,65 +61,32 @@ pub trait VerifierTranscript { /// /// # Errors /// - /// Returns `Err` if the NARG has unread bytes. + /// Returns `Err` if the NARG has unread bytes or the state is poisoned. fn check_eof(self) -> Result<(), VerificationError>; } -impl VerifierTranscript for VerifierState<'_, H> +impl VerifierTranscript for VerifierState<'_, H> where - H: DuplexSpongeInterface, + H: DuplexSpongeInterface, { - fn public_message + ?Sized>(&mut self, msg: &T) { - VerifierState::public_message(self, msg); - } - - fn prover_message + FromNarg>(&mut self) -> Result { + fn prover_message(&mut self) -> Result { VerifierState::prover_message::(self) } - fn verifier_message>(&mut self) -> T { - VerifierState::verifier_message::(self) - } - - fn field_challenges( - &mut self, - len: usize, - ) -> Result, VerificationError> - where - H: UniformBytes, - { - // A length the codec rejects poisons the state too, so a caller that - // drops the error still fails `check_eof`. - let n = message_bytes::(len); - Transcript::check(self, || n.is_ok())?; - let n = n?; - if len == 0 { - return Ok(Vec::new()); - } - let challenges = - VerifierState::verifier_message_as(self, n, |bytes| F::decode_challenges(bytes, len)); - // Exhaustion poisons the state, so later reads and `check_eof` fail too. - Transcript::check(self, || challenges.is_some())?; - challenges.ok_or(VerificationError) - } - - fn check_grind(&mut self, bits: u32) -> Result<(), VerificationError> - where - H: DuplexSpongeInterface, - { + fn check_grind(&mut self, bits: u32) -> Result<(), VerificationError> { if bits == 0 { return Ok(()); } let Some(pow) = SpongePow::new(bits) else { - return Transcript::check(self, || false); + return self.check(|| false); }; // Failed reads poison the state themselves; failed checks poison it - // through `Transcript::check`, so `check_eof` fails afterwards too. + // through `check`, so `check_eof` fails afterwards too. let nonce = pow.read_nonce(|| VerifierState::prover_message::(self))?; - Transcript::check(self, || nonce.is_some())?; + self.check(|| nonce.is_some())?; let passes = VerifierState::verifier_message_as(self, pow.word_len(), |check| pow.accepts(check)); - Transcript::check(self, || passes) + self.check(|| passes) } fn check_eof(self) -> Result<(), VerificationError> { diff --git a/crates/jolt-transcript/tests/blake2b_tests.rs b/crates/jolt-transcript/tests/blake2b_tests.rs index 17cb504343..62bfd9b6d9 100644 --- a/crates/jolt-transcript/tests/blake2b_tests.rs +++ b/crates/jolt-transcript/tests/blake2b_tests.rs @@ -1,31 +1,20 @@ -//! Tests for Blake2bTranscript implementation. +//! Tests for the Blake2b-512 transcript. -mod common; - -use jolt_field::Fr; -use jolt_transcript::Blake2bTranscript; +#![expect(clippy::unwrap_used, reason = "tests")] -type B2b = Blake2bTranscript; +mod common; -transcript_tests!(B2b); +transcript_tests!(jolt_transcript::Blake2b512); #[test] fn test_blake2b_known_vector() { - use ark_ff::PrimeField; - use jolt_transcript::Transcript; - - let mut transcript = Blake2bTranscript::::new(b"Jolt"); - transcript.append_bytes(&12345u64.to_be_bytes()); - let challenge: Fr = transcript.challenge(); - - // Pins the v0.8 session derived from PROTOCOL_ID and the single - // length-prefixed label instance absorb, then one 52-byte challenge - // squeeze reduced mod r. - let expected: ark_bn254::Fr = ark_bn254::Fr::from_le_bytes_mod_order(&[ - 0x72, 0x35, 0x16, 0x7B, 0x7C, 0x6A, 0xDE, 0x91, 0x4D, 0xE7, 0xBD, 0x04, 0x4B, 0x91, 0x30, - 0xF4, 0xAE, 0x68, 0xB7, 0x74, 0x86, 0xC4, 0xEE, 0xBB, 0x67, 0xDD, 0xDF, 0x66, 0x0C, 0x96, - 0x82, 0x1B, - ]); - let got: ark_bn254::Fr = challenge.into(); - assert_eq!(got, expected, "Blake2b known-vector regression"); + let mut transcript = transcript(b"Jolt"); + transcript.append(&12345u64); + let challenge: Fr = transcript.challenge().unwrap(); + // Pins the session derived from PROTOCOL_ID, the instance absorb, one + // `u64` public message and one 52-byte challenge squeeze reduced mod r. + assert_eq!( + format!("{challenge:?}"), + "14920194216516765013403414582981910349784849177391089841728389970502116847106" + ); } diff --git a/crates/jolt-transcript/tests/challenge_tests.rs b/crates/jolt-transcript/tests/challenge_tests.rs index ace81abf5a..a6538d5696 100644 --- a/crates/jolt-transcript/tests/challenge_tests.rs +++ b/crates/jolt-transcript/tests/challenge_tests.rs @@ -8,8 +8,7 @@ use jolt_field::CanonicalBytes; use jolt_transcript::{ - prover_transcript, verifier_transcript, BytesMsg, ChallengeCodec, ProverTranscript, - VerifierTranscript, + prover_transcript, verifier_transcript, BytesMsg, ChallengeCodec, FieldTranscript, }; use spongefish::instantiations::Keccak; use std::fmt::{Debug, Write}; @@ -32,17 +31,17 @@ const INSTANCE: [u8; 32] = [0x42; 32]; fn round_trip() { for len in [1, 17, 28] { - let mut prover = prover_transcript::(SESSION, INSTANCE); + let mut prover = prover_transcript::(SESSION, &INSTANCE); prover.prover_message(&BytesMsg(b"before".to_vec())); - let expected = ProverTranscript::field_challenges::(&mut prover, len).unwrap(); + let expected = FieldTranscript::::challenge_vector(&mut prover, len).unwrap(); assert_eq!(expected.len(), len); prover.prover_message(&BytesMsg(b"after".to_vec())); let tail: [u8; 32] = prover.verifier_message(); - let mut verifier = verifier_transcript::(SESSION, INSTANCE, prover.narg_string()); + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, prover.narg_string()); assert_eq!(verifier.prover_message::().unwrap().0, b"before"); assert_eq!( - VerifierTranscript::field_challenges::(&mut verifier, len).unwrap(), + FieldTranscript::::challenge_vector(&mut verifier, len).unwrap(), expected ); assert_eq!(verifier.prover_message::().unwrap().0, b"after"); @@ -146,12 +145,10 @@ fn pooled_message_sizes_meet_owner_bounds() { fn unsupported_lengths_return_transcript_errors() { for len in [1 << 22, usize::MAX] { assert_eq!(Prime24Offset3::challenge_bytes(len), None); - let mut prover = prover_transcript::(SESSION, INSTANCE); - assert!(ProverTranscript::field_challenges::(&mut prover, len).is_err()); - let mut verifier = verifier_transcript::(SESSION, INSTANCE, &[]); - assert!( - VerifierTranscript::field_challenges::(&mut verifier, len).is_err() - ); + let mut prover = prover_transcript::(SESSION, &INSTANCE); + assert!(FieldTranscript::::challenge_vector(&mut prover, len).is_err()); + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, &[]); + assert!(FieldTranscript::::challenge_vector(&mut verifier, len).is_err()); assert!(verifier.check_eof().is_err()); } } @@ -159,25 +156,25 @@ fn unsupported_lengths_return_transcript_errors() { #[cfg(feature = "binary")] #[test] fn messages_above_the_byte_cap_are_rejected_before_touching_the_sponge() { - let mut prover = prover_transcript::(SESSION, INSTANCE); - let full = ProverTranscript::field_challenges::(&mut prover, MAX_CHALLENGE_BYTES).unwrap(); + let mut prover = prover_transcript::(SESSION, &INSTANCE); + let full = FieldTranscript::::challenge_vector(&mut prover, MAX_CHALLENGE_BYTES).unwrap(); assert_eq!(full.len(), MAX_CHALLENGE_BYTES); - let mut verifier = verifier_transcript::(SESSION, INSTANCE, &[]); + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, &[]); assert_eq!( - VerifierTranscript::field_challenges::(&mut verifier, MAX_CHALLENGE_BYTES).unwrap(), + FieldTranscript::::challenge_vector(&mut verifier, MAX_CHALLENGE_BYTES).unwrap(), full ); for len in [MAX_CHALLENGE_BYTES + 1, usize::MAX] { - let mut prover = prover_transcript::(SESSION, INSTANCE); - assert!(ProverTranscript::field_challenges::(&mut prover, len).is_err()); - let mut fresh = prover_transcript::(SESSION, INSTANCE); + let mut prover = prover_transcript::(SESSION, &INSTANCE); + assert!(FieldTranscript::::challenge_vector(&mut prover, len).is_err()); + let mut fresh = prover_transcript::(SESSION, &INSTANCE); assert_eq!( prover.verifier_message::<[u8; 16]>(), fresh.verifier_message::<[u8; 16]>() ); - let mut verifier = verifier_transcript::(SESSION, INSTANCE, &[]); - assert!(VerifierTranscript::field_challenges::(&mut verifier, len).is_err()); + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, &[]); + assert!(FieldTranscript::::challenge_vector(&mut verifier, len).is_err()); assert!(verifier.check_eof().is_err()); } } @@ -192,20 +189,20 @@ fn empty_messages_leave_the_sponge_untouched() { use spongefish::DuplexSpongeInit; fn check + UniformBytes>() { - let mut prover = prover_transcript::(SESSION, INSTANCE); - assert!(ProverTranscript::field_challenges::(&mut prover, 0) + let mut prover = prover_transcript::(SESSION, &INSTANCE); + assert!(FieldTranscript::::challenge_vector(&mut prover, 0) .unwrap() .is_empty()); - let mut fresh = prover_transcript::(SESSION, INSTANCE); + let mut fresh = prover_transcript::(SESSION, &INSTANCE); assert_eq!( prover.verifier_message::<[u8; 16]>(), fresh.verifier_message::<[u8; 16]>() ); - let mut verifier = verifier_transcript::(SESSION, INSTANCE, &[]); - assert!(VerifierTranscript::field_challenges::(&mut verifier, 0) + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, &[]); + assert!(FieldTranscript::::challenge_vector(&mut verifier, 0) .unwrap() .is_empty()); - let mut fresh = verifier_transcript::(SESSION, INSTANCE, &[]); + let mut fresh = verifier_transcript::(SESSION, &INSTANCE, &[]); assert_eq!( verifier.verifier_message::<[u8; 16]>(), fresh.verifier_message::<[u8; 16]>() @@ -331,15 +328,15 @@ fn known_answer( expected: &str, continuation: &str, ) { - let mut prover = prover_transcript::(SESSION, INSTANCE); + let mut prover = prover_transcript::(SESSION, &INSTANCE); prover.prover_message(&BytesMsg(b"before".to_vec())); - let values = ProverTranscript::field_challenges::(&mut prover, len).unwrap(); + let values = FieldTranscript::::challenge_vector(&mut prover, len).unwrap(); let tail: [u8; 16] = prover.verifier_message(); prover.prover_message(&BytesMsg(b"after".to_vec())); - let mut verifier = verifier_transcript::(SESSION, INSTANCE, prover.narg_string()); + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, prover.narg_string()); assert_eq!(verifier.prover_message::().unwrap().0, b"before"); assert_eq!( - VerifierTranscript::field_challenges::(&mut verifier, len).unwrap(), + FieldTranscript::::challenge_vector(&mut verifier, len).unwrap(), values ); assert_eq!(verifier.verifier_message::<[u8; 16]>(), tail); @@ -354,14 +351,14 @@ fn known_answer( fn solinas_known_answers() { known_answer::( 1, - "03a0e231957e175cad1c4652986665f6", - "cf233d36ef269b469d2c9f0e05bbf256", + "054c2dd95aac3fe41030fd854e23bb1c", + "f94c4bb61fd8fb32d1caa4e81a9d2411", ); - known_answer::(1, "03a0e231957e175c", "9ab77b02494dd39fcf233d36ef269b46"); + known_answer::(1, "054c2dd95aac3fe4", "66d94962948e5d1bf94c4bb61fd8fb32"); known_answer::>( 1, - "03a0e231957e175cad1c4652986665f6", - "05bbf2567392ecb9d780c781ffd908e3", + "054c2dd95aac3fe41030fd854e23bb1c", + "1a9d24117be65f8a42263c77b5726f3a", ); } @@ -370,8 +367,8 @@ fn solinas_known_answers() { fn bn254_known_answer() { known_answer::( 1, - "613f26dcbbf8587df01a46ce094e04d1601b84a15c2c1759fd02b13bbbb52430", - "d780c781ffd908e35322f4c213862958", + "7102fb019592516789657a0711ec2e8e295f8e3aff0c618914af0a9dca4da608", + "42263c77b5726f3a3984d3c5788dd66a", ); } @@ -380,8 +377,8 @@ fn bn254_known_answer() { fn binary_known_answer() { known_answer::( 1, - "03a0e231957e175cad1c4652986665f6", - "cc638e1acaa5c8429ab77b02494dd39f", + "054c2dd95aac3fe41030fd854e23bb1c", + "6f7ccba5b9646bd566d94962948e5d1b", ); } @@ -393,23 +390,23 @@ fn keccak_rate_boundary_known_answers() { known_answer::( 136, concat!( - "03a0e231957e175cad1c4652986665f6cc638e1acaa5c8429ab77b02494dd39f", - "cf233d36ef269b469d2c9f0e05bbf2567392ecb9d780c781ffd908e35322f4c2", - "1386295823f9bebdb077934826eeaa88df7668d2edcca3d139887290f644bf396", - "5ef5d78c221223a45823663c3d8d52749fea1c0058d43d503581e4d9544d2633", - "114fdfde8526f70", + "054c2dd95aac3fe41030fd854e23bb1c6f7ccba5b9646bd566d94962948e5d1b", + "f94c4bb61fd8fb32d1caa4e81a9d24117be65f8a42263c77b5726f3a3984d3c5", + "788dd66a184950ffa84ae77ce7a81690fe05c2cb5b858f72244dd62aa3a09b38", + "bf264e5d9dff511d05316450c2f222c41bd9e3a7450eb7f77027d30891c2c44d", + "e39e7b3ab5d41e40", ), - "da8dad6d5c627aed4e52368123c6e50a", + "60ef1af29559e3b43b9fdaed924be4d0", ); known_answer::( 137, concat!( - "03a0e231957e175cad1c4652986665f6cc638e1acaa5c8429ab77b02494dd39f", - "cf233d36ef269b469d2c9f0e05bbf2567392ecb9d780c781ffd908e35322f4c2", - "1386295823f9bebdb077934826eeaa88df7668d2edcca3d139887290f644bf396", - "5ef5d78c221223a45823663c3d8d52749fea1c0058d43d503581e4d9544d2633", - "114fdfde8526f70da", + "054c2dd95aac3fe41030fd854e23bb1c6f7ccba5b9646bd566d94962948e5d1b", + "f94c4bb61fd8fb32d1caa4e81a9d24117be65f8a42263c77b5726f3a3984d3c5", + "788dd66a184950ffa84ae77ce7a81690fe05c2cb5b858f72244dd62aa3a09b38", + "bf264e5d9dff511d05316450c2f222c41bd9e3a7450eb7f77027d30891c2c44d", + "e39e7b3ab5d41e4060", ), - "8dad6d5c627aed4e52368123c6e50a7d", + "ef1af29559e3b43b9fdaed924be4d02b", ); } diff --git a/crates/jolt-transcript/tests/common/mod.rs b/crates/jolt-transcript/tests/common/mod.rs index e6572d9711..a05272301a 100644 --- a/crates/jolt-transcript/tests/common/mod.rs +++ b/crates/jolt-transcript/tests/common/mod.rs @@ -1,32 +1,37 @@ -//! Common test utilities and standardized test suite for transcript implementations. +//! Common test utilities and standardized test suite for transcript sponges. -/// Standardized test suite macro for any `Transcript` implementation. -/// -/// All comparisons are done through `challenge()` outputs since the -/// underlying spongefish duplex sponges do not expose internal state. +/// Standardized test suite for a sponge `H` with uniform output: the +/// prover's and verifier's spongefish states over `H`, compared through the +/// `Fr` challenges they draw. #[macro_export] macro_rules! transcript_tests { - ($transcript_type:ty) => { - use jolt_transcript::Transcript; + ($sponge:ty) => { + use jolt_field::Fr; + use jolt_transcript::{ + prover_transcript, verifier_transcript, FieldTranscript, Label, ProverState, Transcript, + }; use std::collections::HashSet; - fn challenge_after( - label: &'static [u8], - f: F, - ) -> <$transcript_type as Transcript>::Challenge { - let mut t = <$transcript_type>::new(label); + const SESSION: &[u8] = b"jolt-transcript/tests"; + + fn transcript(statement: &[u8; 4]) -> ProverState<$sponge> { + prover_transcript::<$sponge>(SESSION, statement) + } + + fn challenge_after)>(statement: &[u8; 4], f: F) -> Fr { + let mut t = transcript(statement); f(&mut t); - t.challenge() + t.challenge().unwrap() } #[test] fn test_determinism() { - let c1 = challenge_after(b"determinism_test", |t| { - t.append_bytes(&42u64.to_be_bytes()); + let c1 = challenge_after(b"same", |t| { + t.append(&42u64); t.append_bytes(b"hello world"); }); - let c2 = challenge_after(b"determinism_test", |t| { - t.append_bytes(&42u64.to_be_bytes()); + let c2 = challenge_after(b"same", |t| { + t.append(&42u64); t.append_bytes(b"hello world"); }); assert_eq!( @@ -36,19 +41,39 @@ macro_rules! transcript_tests { } #[test] - fn test_domain_separation() { - let c1 = challenge_after(b"protocol_a", |_| {}); - let c2 = challenge_after(b"protocol_b", |_| {}); - assert_ne!(c1, c2, "Different labels must produce different challenges"); + fn test_session_and_statement_separation() { + let base = challenge_after(b"stmt", |_| {}); + assert_ne!(base, challenge_after(b"STMT", |_| {})); + let mut other = prover_transcript::<$sponge>(b"another session", b"stmt"); + assert_ne!(base, other.challenge().unwrap()); + } + + #[test] + fn test_prover_and_verifier_agree() { + let mut prover = transcript(b"pair"); + let mut verifier = verifier_transcript::<$sponge>(SESSION, b"pair", &[]); + schedule(&mut prover); + schedule(&mut verifier); + let p: Vec = prover.challenge_vector(3).unwrap(); + let v: Vec = verifier.challenge_vector(3).unwrap(); + assert_eq!(p, v); + verifier.check_eof().unwrap(); + } + + /// The same message schedule, on either side. + fn schedule>(t: &mut T) { + t.append_labeled(b"value", &Fr::from(7u64)); + t.append_values(b"values", &[Fr::from(1u64), Fr::from(2u64)]); + let _: Fr = t.challenge().unwrap(); + t.append_bytes(b"tail"); } #[test] fn test_challenge_uniqueness() { - let mut transcript = <$transcript_type>::new(b"uniqueness_test"); + let mut transcript = transcript(b"uniq"); let mut challenges = HashSet::new(); - for i in 0..10_000 { - let c = transcript.challenge(); + let c: Fr = transcript.challenge().unwrap(); assert!( challenges.insert(c), "Duplicate challenge found at iteration {i}" @@ -58,79 +83,82 @@ macro_rules! transcript_tests { #[test] fn test_order_matters() { - let c1 = challenge_after(b"order_test", |t| { - t.append_bytes(&1u64.to_be_bytes()); - t.append_bytes(&2u64.to_be_bytes()); + let c1 = challenge_after(b"ordr", |t| { + t.append(&1u64); + t.append(&2u64); }); - let c2 = challenge_after(b"order_test", |t| { - t.append_bytes(&2u64.to_be_bytes()); - t.append_bytes(&1u64.to_be_bytes()); + let c2 = challenge_after(b"ordr", |t| { + t.append(&2u64); + t.append(&1u64); }); assert_ne!(c1, c2, "Order of appends must affect challenge"); } #[test] fn test_data_sensitivity() { - let c1 = challenge_after(b"data_test", |t| { - t.append_bytes(&0u64.to_be_bytes()); - }); - let c2 = challenge_after(b"data_test", |t| { - t.append_bytes(&1u64.to_be_bytes()); - }); + let c1 = challenge_after(b"data", |t| t.append(&0u64)); + let c2 = challenge_after(b"data", |t| t.append(&1u64)); assert_ne!(c1, c2, "Different data must produce different challenges"); } #[test] - fn test_empty_bytes() { - let baseline = challenge_after(b"empty_test", |_| {}); - let with_empty = challenge_after(b"empty_test", |t| { + fn test_bytes_framing() { + // Each byte string carries its length, so splitting or extending + // the bytes changes the absorbed words. + let empty = challenge_after(b"empt", |t| t.append_bytes(&[])); + assert_ne!(empty, challenge_after(b"empt", |t| t.append_bytes(&[0]))); + let split = challenge_after(b"empt", |t| { + t.append_bytes(b"a"); + t.append_bytes(b"b"); + }); + assert_ne!(split, challenge_after(b"empt", |t| t.append_bytes(b"ab"))); + let leading_empty = challenge_after(b"empt", |t| { t.append_bytes(&[]); + t.append_bytes(b"x"); }); assert_ne!( - baseline, with_empty, - "append_bytes(&[]) must observably change challenge" + leading_empty, + challenge_after(b"empt", |t| t.append_bytes(b"x")) ); - let with_empty_again = challenge_after(b"empty_test", |t| { - t.append_bytes(&[]); - }); - assert_eq!(with_empty, with_empty_again); } #[test] - fn test_default_delegates_to_new() { - let mut default_transcript = <$transcript_type>::default(); - let mut new_transcript = <$transcript_type>::new(b""); - assert_eq!( - default_transcript.challenge(), - new_transcript.challenge(), - "Default must delegate to new(b\"\")" + fn test_label_framing() { + let labeled = challenge_after(b"labl", |t| t.append_labeled(b"x", &1u64)); + let explicit = challenge_after(b"labl", |t| { + t.append(&Label(b"x")); + t.append(&1u64); + }); + assert_eq!(labeled, explicit); + assert_ne!( + labeled, + challenge_after(b"labl", |t| t.append_labeled(b"y", &1u64)) ); } #[test] - #[should_panic(expected = "label must be at most 32 bytes")] + #[should_panic(expected = "exceeds 32 bytes")] fn test_label_too_long() { - let long_label: &[u8; 33] = &[b'x'; 33]; - let _ = <$transcript_type>::new(long_label); - } - - #[test] - fn test_max_valid_label() { - let max_label: &[u8; 32] = &[b'L'; 32]; - let mut t1 = <$transcript_type>::new(max_label); - let mut t2 = <$transcript_type>::new(max_label); - assert_eq!(t1.challenge(), t2.challenge()); + let long_label: &'static [u8; 33] = &[b'x'; 33]; + transcript(b"long").append(&Label(long_label)); } #[test] fn test_challenge_vector() { - let mut transcript = <$transcript_type>::new(b"vector_test"); - let challenges = transcript.challenge_vector(5); - + let challenges: Vec = transcript(b"vect").challenge_vector(5).unwrap(); assert_eq!(challenges.len(), 5); - let unique: HashSet<_> = challenges.iter().collect(); assert_eq!(unique.len(), 5, "All challenges in vector should be unique"); } + + #[test] + fn test_challenge_powers() { + let powers: Vec = transcript(b"powr").challenge_powers(4).unwrap(); + let gamma: Fr = transcript(b"powr").challenge().unwrap(); + assert_eq!( + powers, + vec![Fr::from(1u64), gamma, gamma * gamma, gamma * gamma * gamma] + ); + } }; } diff --git a/crates/jolt-transcript/tests/digest_transcript_tests.rs b/crates/jolt-transcript/tests/digest_transcript_tests.rs deleted file mode 100644 index 0e6322626d..0000000000 --- a/crates/jolt-transcript/tests/digest_transcript_tests.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! Tests for the legacy digest-chained transcript (`DigestTranscript`). -//! -//! `LegacyBlake2bTranscript` exists solely for byte compatibility with -//! `jolt-prover-legacy`'s chained-digest transcript, so beyond the shared -//! `transcript_tests!` behavioral suite, every state transition is checked -//! against an independently recomputed Blake2b-256 hash chain: -//! `state0 = H(label || 0^pad)`, append is `H(state || round || payload)`, -//! squeeze is `H(state || round)` with the 4-byte big-endian round counter -//! right-aligned in a 32-byte word. - -#![cfg(feature = "transcript-blake2b")] - -mod common; - -use blake2::{digest::consts::U32, Blake2b, Digest}; -use jolt_field::{CanonicalEncoding, Fr}; -use jolt_transcript::LegacyBlake2bTranscript; - -type LegacyB2b = LegacyBlake2bTranscript; -type Blake2b256 = Blake2b; - -transcript_tests!(LegacyB2b); - -fn manual_step(state: &[u8; 32], round: u32, payload: &[u8]) -> [u8; 32] { - let mut round_word = [0u8; 32]; - round_word[28..].copy_from_slice(&round.to_be_bytes()); - let mut hasher = Blake2b256::new(); - hasher.update(state); - hasher.update(round_word); - hasher.update(payload); - hasher.finalize().into() -} - -fn manual_label_state(label: &[u8]) -> [u8; 32] { - let mut padded = [0u8; 32]; - padded[..label.len()].copy_from_slice(label); - Blake2b256::digest(padded).into() -} - -#[test] -fn appends_and_challenges_follow_the_legacy_hash_chain() { - let mut transcript = LegacyB2b::new(b"legacy-chain"); - let mut expected = manual_label_state(b"legacy-chain"); - - transcript.append_bytes(b"first message"); - expected = manual_step(&expected, 0, b"first message"); - assert_eq!( - transcript.state(), - expected, - "append must chain the payload" - ); - - transcript.append_bytes(&[]); - expected = manual_step(&expected, 1, &[]); - assert_eq!( - transcript.state(), - expected, - "an empty append must still advance the round counter" - ); - - // The first two challenges after an absorb squeeze - // `Fr::challenge_width(1) = Fr::challenge_width(2) = 52` bytes: two chunks. - for (draw, round) in [ - (LegacyB2b::challenge as fn(&mut LegacyB2b) -> Fr, 2), - (LegacyB2b::challenge_scalar, 4), - ] { - let challenge = draw(&mut transcript); - let first = manual_step(&expected, round, &[]); - expected = manual_step(&first, round + 1, &[]); - assert_eq!(transcript.state(), expected, "challenge must chain state"); - let squeezed = [first.as_slice(), &expected[..20]].concat(); - assert_eq!( - challenge, - Fr::from_bytes_le_reduced(&squeezed), - "challenge must reduce the 52 squeezed bytes" - ); - } -} - -#[test] -fn clone_replays_identically_until_inputs_diverge() { - let mut original = LegacyB2b::new(b"legacy-clone"); - original.append_bytes(b"shared prefix"); - let mut cloned = original.clone(); - assert_eq!(original.state(), cloned.state()); - assert_eq!( - original.challenge(), - cloned.challenge(), - "clones must squeeze identical challenges" - ); - - original.append_bytes(b"left"); - cloned.append_bytes(b"right"); - assert_ne!( - original.state(), - cloned.state(), - "divergent appends must yield divergent states" - ); -} diff --git a/crates/jolt-transcript/tests/keccak_tests.rs b/crates/jolt-transcript/tests/keccak_tests.rs index a3839e0564..a5c81fe51d 100644 --- a/crates/jolt-transcript/tests/keccak_tests.rs +++ b/crates/jolt-transcript/tests/keccak_tests.rs @@ -1,44 +1,30 @@ -//! Tests for KeccakTranscript implementation. +//! Tests for the Keccak transcript. #![expect(clippy::unwrap_used, reason = "tests")] mod common; -use jolt_field::Fr; -use jolt_transcript::KeccakTranscript; - -type Kec = KeccakTranscript; - -transcript_tests!(Kec); +transcript_tests!(jolt_transcript::Keccak); #[test] fn test_keccak_known_vector() { - use ark_ff::PrimeField; - use jolt_transcript::Transcript; - - let mut transcript = KeccakTranscript::::new(b"Jolt"); - transcript.append_bytes(&12345u64.to_be_bytes()); - let challenge: Fr = transcript.challenge(); - - // Pins the v0.8 session derived from PROTOCOL_ID and the single - // length-prefixed label instance absorb, then one 52-byte challenge - // squeeze reduced mod r. - let expected: ark_bn254::Fr = ark_bn254::Fr::from_le_bytes_mod_order(&[ - 0x4B, 0xA1, 0xF1, 0x31, 0x86, 0x42, 0xF4, 0x21, 0xC9, 0xD6, 0xD3, 0xA9, 0x90, 0x02, 0xCB, - 0x21, 0x9C, 0x5B, 0x3E, 0x1B, 0xAA, 0xF7, 0x9A, 0xB2, 0x04, 0xAE, 0xD0, 0x83, 0xD2, 0x8A, - 0xC2, 0x2E, - ]); - let got: ark_bn254::Fr = challenge.into(); - assert_eq!(got, expected, "Keccak known-vector regression"); + let mut transcript = transcript(b"Jolt"); + transcript.append(&12345u64); + let challenge: Fr = transcript.challenge().unwrap(); + // Pins the session derived from PROTOCOL_ID, the instance absorb, one + // `u64` public message and one 52-byte challenge squeeze reduced mod r. + assert_eq!( + format!("{challenge:?}"), + "1766718655065384849885355460798243624214294877158123096419747417635580052889" + ); } #[test] fn public_message_after_challenge_agrees_including_empty() { - use jolt_transcript::{prover_transcript, verifier_transcript, BytesMsg}; - use spongefish::instantiations::Keccak; + use jolt_transcript::{BytesMsg, Keccak}; for public in [b"public after squeeze".as_slice(), &[]] { - let mut prover = prover_transcript::(b"keccak-public", [0; 32]); + let mut prover = prover_transcript::(b"keccak-public", &[0u8; 32]); let first: [u8; 17] = prover.verifier_message(); prover.public_message_as(public, |bytes| bytes); let second: [u8; 32] = prover.verifier_message(); @@ -46,7 +32,7 @@ fn public_message_after_challenge_agrees_including_empty() { let third: [u8; 32] = prover.verifier_message(); let mut verifier = - verifier_transcript::(b"keccak-public", [0; 32], prover.narg_string()); + verifier_transcript::(b"keccak-public", &[0u8; 32], prover.narg_string()); assert_eq!(verifier.verifier_message::<[u8; 17]>(), first); verifier.public_message_as(public, |bytes| bytes); assert_eq!(verifier.verifier_message::<[u8; 32]>(), second); @@ -57,7 +43,7 @@ fn public_message_after_challenge_agrees_including_empty() { assert_eq!(verifier.verifier_message::<[u8; 32]>(), third); verifier.check_eof().unwrap(); - let mut uninterrupted = prover_transcript::(b"keccak-public", [0; 32]); + let mut uninterrupted = prover_transcript::(b"keccak-public", &[0u8; 32]); let stream: [u8; 49] = uninterrupted.verifier_message(); assert_eq!(&stream[..17], &first); if public.is_empty() { diff --git a/crates/jolt-transcript/tests/legacy_digest_tests.rs b/crates/jolt-transcript/tests/legacy_digest_tests.rs deleted file mode 100644 index 8cd53e653e..0000000000 --- a/crates/jolt-transcript/tests/legacy_digest_tests.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! Known-answer tests for `LegacyBlake2bTranscript` (`DigestTranscript`). -//! -//! This transcript exists solely for byte-compatibility with the -//! `jolt-prover-legacy` Fiat-Shamir transcript, so prover/verifier round-trips -//! cannot catch a chaining regression — both sides would drift together. The -//! hex vectors below were generated with an independent BLAKE2b-256 -//! implementation (Python `hashlib.blake2b(digest_size=32)`) from the -//! documented formula: -//! -//! ```text -//! state_0 = H(label zero-padded to 32 bytes) -//! round_word(n) = 28 zero bytes || n as u32 big-endian -//! append(bytes) : state' = H(state || round_word(n) || bytes), n += 1 -//! squeeze : out = H(state || round_word(n)), state' = out, n += 1 -//! ``` - -use jolt_field::{CanonicalBytes, CanonicalEncoding, Fr}; -use jolt_transcript::{Label, LabelWithCount, LegacyBlake2bTranscript, Transcript, U64Word}; - -type T = LegacyBlake2bTranscript; - -const KAT_LABEL: &[u8] = b"jolt transcript kat"; - -fn hex(bytes: &[u8]) -> String { - use std::fmt::Write; - bytes.iter().fold(String::new(), |mut out, byte| { - // writing to a String is infallible - let _ = write!(out, "{byte:02x}"); - out - }) -} - -#[test] -fn initial_state_is_hash_of_zero_padded_label() { - let transcript = T::new(KAT_LABEL); - assert_eq!( - hex(&transcript.state()), - "b57f1b2110a7b1720489cf99300d3a5641498671160090254ef89dc6f926b19e", - ); -} - -#[test] -fn append_bytes_chains_state_with_round_counter() { - let mut transcript = T::new(KAT_LABEL); - transcript.append_bytes(b"jolt says hello"); - assert_eq!( - hex(&transcript.state()), - "e0581c34f7f60080a2b1cac4282e5420a88034e11a876c47f94374122fa1f905", - ); -} - -#[test] -fn challenge_draw_rotates_state_and_truncates_to_sixteen_bytes() { - const EXPECTED_CHUNK: &str = "25a496912b5b75d6504cbc204a25b13d6121e6266ac056589689c43dc06f9142"; - - let mut transcript = T::new(KAT_LABEL); - transcript.append_bytes(b"jolt says hello"); - let mut buf = [0u8; 16]; - transcript.challenge_bytes(&mut buf); - - assert_eq!( - hex(&buf), - &EXPECTED_CHUNK[..32], - "low 16 bytes of the chunk" - ); - assert_eq!( - hex(&transcript.state()), - EXPECTED_CHUNK, - "state becomes the squeezed chunk" - ); -} - -#[test] -fn multi_chunk_draw_concatenates_successive_squeezes() { - let mut transcript = T::new(KAT_LABEL); - let mut out = [0u8; 48]; - transcript.challenge_bytes(&mut out); - assert_eq!( - hex(&out), - "0db99faadc348c2ecdd3faf44d5f94cf27b2a8b93e757d60eae8a2bc1c057d95\ - b6d6e19661c63658f73f61b9f25d4e70", - ); -} - -/// The first challenge after an absorb squeezes `Fr::challenge_width(1) = 52` -/// bytes, two chunks, and reduces them modulo `r` as a little-endian integer. -/// The value was computed independently (Python -/// `int.from_bytes(.., "little") % r`). -#[test] -fn challenge_reduces_fifty_two_squeezed_bytes() { - const SQUEEZED: &str = "25a496912b5b75d6504cbc204a25b13d6121e6266ac056589689c43dc06f9142\ - 66bf5655d23fb7f4e92e96ea1f0df9727d7f0e9a"; - const REDUCED: &str = "71854c4909f4c3e785ab8e719f895fdb6a0ea3df7952cabbe4595baa5d355d1f"; - assert_eq!(Fr::challenge_width(1), 52); - - let mut probe = T::new(KAT_LABEL); - probe.append_bytes(b"jolt says hello"); - let mut squeezed = [0u8; 52]; - probe.challenge_bytes(&mut squeezed); - assert_eq!(hex(&squeezed), SQUEEZED.replace(' ', "")); - - for draw in [T::challenge, T::challenge_scalar] { - let mut transcript = T::new(KAT_LABEL); - transcript.append_bytes(b"jolt says hello"); - let challenge: Fr = draw(&mut transcript); - assert_eq!(hex(&challenge.to_bytes_le_vec()), REDUCED); - assert_eq!(transcript.state(), probe.state()); - } -} - -/// Each draw since the last absorb is wider than the one before it may be -/// (52 bytes for draws 1–2, 53 for draws 3–32), and an absorb starts over. -#[test] -fn challenge_width_grows_with_draws_and_resets_on_absorb() { - let mut transcript = T::new(KAT_LABEL); - let mut probe = T::new(KAT_LABEL); - let check = |transcript: &mut T, probe: &mut T, widths: &[usize]| { - for (draw, &width) in widths.iter().enumerate() { - assert_eq!(Fr::challenge_width(draw as u64 + 1), width); - let mut bytes = vec![0u8; width]; - probe.challenge_bytes(&mut bytes); - let challenge: Fr = transcript.challenge(); - assert_eq!(challenge, Fr::from_bytes_le_reduced(&bytes), "draw {draw}"); - } - }; - check(&mut transcript, &mut probe, &[52, 52, 53, 53, 53]); - transcript.append_bytes(b"next message"); - probe.append_bytes(b"next message"); - check(&mut transcript, &mut probe, &[52, 52, 53]); -} - -#[test] -fn challenge_scalar_powers_are_consecutive_powers_of_one_draw() { - let mut probe = T::new(KAT_LABEL); - let gamma: Fr = probe.challenge_scalar(); - - let mut transcript = T::new(KAT_LABEL); - let powers = transcript.challenge_scalar_powers(4); - - assert_eq!(powers[0], Fr::from(1u64)); - assert_eq!(powers[1], gamma); - assert_eq!(powers[2], gamma * gamma); - assert_eq!(powers[3], gamma * gamma * gamma); - assert_eq!( - transcript.state(), - probe.state(), - "powers consume exactly one draw" - ); -} - -#[test] -fn label_helper_packs_zero_padded_thirty_two_byte_word() { - let mut via_helper = T::new(KAT_LABEL); - via_helper.append(&Label(b"opening claim")); - - let mut explicit = T::new(KAT_LABEL); - let mut word = [0u8; 32]; - word[..13].copy_from_slice(b"opening claim"); - explicit.append_bytes(&word); - - assert_eq!(via_helper.state(), explicit.state()); -} - -#[test] -fn label_with_count_packs_label_and_big_endian_count() { - let mut via_helper = T::new(KAT_LABEL); - via_helper.append(&LabelWithCount(b"round polys", 7)); - - let mut explicit = T::new(KAT_LABEL); - let mut word = [0u8; 32]; - word[..11].copy_from_slice(b"round polys"); - word[24..].copy_from_slice(&7u64.to_be_bytes()); - explicit.append_bytes(&word); - - assert_eq!(via_helper.state(), explicit.state()); -} - -#[test] -fn u64_word_packs_left_padded_big_endian_value() { - let mut via_helper = T::new(KAT_LABEL); - via_helper.append(&U64Word(0xDEAD_BEEF)); - - let mut explicit = T::new(KAT_LABEL); - let mut word = [0u8; 32]; - word[24..].copy_from_slice(&0xDEAD_BEEFu64.to_be_bytes()); - explicit.append_bytes(&word); - - assert_eq!(via_helper.state(), explicit.state()); -} diff --git a/crates/jolt-transcript/tests/narg_eof_tests.rs b/crates/jolt-transcript/tests/narg_eof_tests.rs index 09acbf950c..f29752b5f2 100644 --- a/crates/jolt-transcript/tests/narg_eof_tests.rs +++ b/crates/jolt-transcript/tests/narg_eof_tests.rs @@ -17,7 +17,7 @@ const SESSION: &[u8] = b"narg-eof-test"; const INSTANCE: [u8; 32] = [0x42; 32]; fn build_valid_narg(messages: &[&[u8]]) -> Vec { - let mut prover = prover_transcript::>(SESSION, INSTANCE); + let mut prover = prover_transcript::>(SESSION, &INSTANCE); for m in messages { prover.prover_message(&BytesMsg(m.to_vec())); } @@ -25,7 +25,7 @@ fn build_valid_narg(messages: &[&[u8]]) -> Vec { } fn build_verifier(narg: &[u8]) -> VerifierState<'_, Hash> { - verifier_transcript::>(SESSION, INSTANCE, narg) + verifier_transcript::>(SESSION, &INSTANCE, narg) } #[test] diff --git a/crates/jolt-transcript/tests/native_traits_tests.rs b/crates/jolt-transcript/tests/native_traits_tests.rs index e0cc70804f..edf8b70084 100644 --- a/crates/jolt-transcript/tests/native_traits_tests.rs +++ b/crates/jolt-transcript/tests/native_traits_tests.rs @@ -5,22 +5,21 @@ use blake2::Blake2b512; use jolt_field::Fr; -use jolt_transcript::{ - prover_transcript, verifier_transcript, ProverTranscript, VerifierTranscript, -}; +use jolt_transcript::FieldTranscript; +use jolt_transcript::{prover_transcript, verifier_transcript}; use spongefish::instantiations::Hash; const SESSION: &[u8] = b"native-traits"; const INSTANCE: [u8; 32] = [0x77; 32]; fn prover_challenge(session: &[u8], instance: [u8; 32]) -> Vec { - let mut prover = prover_transcript::>(session, instance); - ProverTranscript::field_challenges::(&mut prover, 1).unwrap() + let mut prover = prover_transcript::>(session, &instance); + FieldTranscript::::challenge_vector(&mut prover, 1).unwrap() } fn verifier_challenge(session: &[u8], instance: [u8; 32]) -> Vec { - let mut verifier = verifier_transcript::>(session, instance, &[]); - VerifierTranscript::field_challenges::(&mut verifier, 1).unwrap() + let mut verifier = verifier_transcript::>(session, &instance, &[]); + FieldTranscript::::challenge_vector(&mut verifier, 1).unwrap() } #[test] diff --git a/crates/jolt-transcript/tests/poseidon_tests.rs b/crates/jolt-transcript/tests/poseidon_tests.rs index ecc3a57c46..a3dbb4403b 100644 --- a/crates/jolt-transcript/tests/poseidon_tests.rs +++ b/crates/jolt-transcript/tests/poseidon_tests.rs @@ -1,31 +1,64 @@ -//! Tests for PoseidonTranscript implementation. +//! Tests for the Poseidon sponge. +//! +//! Poseidon's squeezed bytes are not uniform, so it draws no field +//! challenges; these tests exercise it as a byte sponge. -mod common; +use jolt_transcript::{prover_transcript, verifier_transcript, PoseidonSponge, Transcript}; -use jolt_field::Fr; -use jolt_transcript::PoseidonTranscript; +const SESSION: &[u8] = b"jolt-transcript/tests"; -type Pos = PoseidonTranscript; +fn squeeze_after)>(f: F) -> [u8; 32] { + let mut transcript = prover_transcript::(SESSION, b"Jolt"); + f(&mut transcript); + transcript.verifier_message::<[u8; 32]>() +} -transcript_tests!(Pos); +#[test] +fn test_poseidon_determinism_and_order() { + let a = squeeze_after(|t| { + t.append(&1u64); + t.append(&2u64); + }); + let b = squeeze_after(|t| { + t.append(&1u64); + t.append(&2u64); + }); + let c = squeeze_after(|t| { + t.append(&2u64); + t.append(&1u64); + }); + assert_eq!(a, b); + assert_ne!(a, c); +} #[test] -fn test_poseidon_known_vector() { - use ark_ff::PrimeField; - use jolt_transcript::Transcript; +fn test_poseidon_prover_and_verifier_agree() { + let mut prover = prover_transcript::(SESSION, b"Jolt"); + let mut verifier = verifier_transcript::(SESSION, b"Jolt", &[]); + prover.append_bytes(b"msg"); + verifier.append_bytes(b"msg"); + assert_eq!( + prover.verifier_message::<[u8; 32]>(), + verifier.verifier_message::<[u8; 32]>() + ); +} - let mut transcript = PoseidonTranscript::::new(b"Jolt"); - transcript.append_bytes(&12345u64.to_be_bytes()); - let challenge: Fr = transcript.challenge(); +#[test] +fn test_poseidon_known_vector() { + let got = squeeze_after(|t| t.append(&12345u64)); + // Pins the session derived from PROTOCOL_ID, the instance absorb, one + // `u64` public message and a 32-byte squeeze. + assert_eq!( + hex(&got), + "788647c07eed8851fb17d6eeb2682d72ccca468c7ebd9f49a071712305eb8817", + "Poseidon known-vector regression" + ); +} - // Pins the v0.8 session derived from PROTOCOL_ID and the single - // length-prefixed label instance absorb, then one 52-byte challenge - // squeeze reduced mod r. - let expected: ark_bn254::Fr = ark_bn254::Fr::from_le_bytes_mod_order(&[ - 0x52, 0x31, 0x0C, 0x87, 0xD8, 0x93, 0x5C, 0x65, 0x2E, 0x83, 0x71, 0x19, 0xF2, 0x81, 0x1E, - 0x79, 0xC0, 0xAE, 0x1A, 0xDB, 0xE6, 0xC5, 0xED, 0xD2, 0x12, 0xDC, 0x69, 0x7D, 0xBD, 0xF4, - 0x8E, 0x27, - ]); - let got: ark_bn254::Fr = challenge.into(); - assert_eq!(got, expected, "Poseidon known-vector regression"); +fn hex(bytes: &[u8]) -> String { + use std::fmt::Write; + bytes.iter().fold(String::new(), |mut hex, byte| { + let _ = write!(hex, "{byte:02x}"); + hex + }) } diff --git a/crates/jolt-transcript/tests/pow_tests.rs b/crates/jolt-transcript/tests/pow_tests.rs index 1e54302391..7446e29256 100644 --- a/crates/jolt-transcript/tests/pow_tests.rs +++ b/crates/jolt-transcript/tests/pow_tests.rs @@ -3,6 +3,7 @@ #![expect(clippy::unwrap_used, reason = "tests")] use jolt_field::{CanonicalBytes, Fr, Prime128Offset275, F8}; +use jolt_transcript::FieldTranscript; use jolt_transcript::{ prover_transcript, verifier_transcript, Blake2b512, BytesMsg, ChallengeCodec, PoseidonSponge, Probe, ProverTranscript, VerifierTranscript, MAX_POW_BITS, @@ -16,13 +17,13 @@ const SESSION: &[u8] = b"jolt/pow/v1"; const INSTANCE: [u8; 32] = [0x24; 32]; fn prover() -> ProverState { - let mut prover = prover_transcript::(SESSION, INSTANCE); + let mut prover = prover_transcript::(SESSION, &INSTANCE); prover.prover_message(&BytesMsg(b"before".to_vec())); prover } fn verifier(narg: &[u8]) -> VerifierState<'_, Keccak> { - let mut verifier = verifier_transcript::(SESSION, INSTANCE, narg); + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, narg); assert_eq!(verifier.prover_message::().unwrap().0, b"before"); verifier } @@ -76,7 +77,7 @@ fn manual_check(bits: u32, n: usize) { let start = jolt.narg_string().len(); ProverTranscript::grind(&mut jolt, bits).unwrap(); let nonce = jolt.narg_string()[start..].to_vec(); - let values = ProverTranscript::field_challenges::(&mut jolt, n).unwrap(); + let values = FieldTranscript::::challenge_vector(&mut jolt, n).unwrap(); let tail: [u8; 8] = jolt.verifier_message(); let value = decode(&nonce); assert_eq!(nonce, leb128(value), "bits {bits}"); @@ -112,7 +113,7 @@ fn manual_check(bits: u32, n: usize) { let mut verifier = verifier(jolt.narg_string()); VerifierTranscript::check_grind(&mut verifier, bits).unwrap(); - let read = VerifierTranscript::field_challenges::(&mut verifier, n).unwrap(); + let read = FieldTranscript::::challenge_vector(&mut verifier, n).unwrap(); assert_eq!(read, values); assert_eq!(verifier.verifier_message::<[u8; 8]>(), tail); verifier.check_eof().unwrap(); @@ -132,18 +133,18 @@ fn round_trip(&mut prover, 17).unwrap(); - let second = ProverTranscript::field_challenges::(&mut prover, 3).unwrap(); + let first = FieldTranscript::::challenge_vector(&mut prover, 17).unwrap(); + let second = FieldTranscript::::challenge_vector(&mut prover, 3).unwrap(); prover.prover_message(&BytesMsg(b"after".to_vec())); let mut verifier = verifier(prover.narg_string()); VerifierTranscript::check_grind(&mut verifier, bits).unwrap(); assert_eq!( - VerifierTranscript::field_challenges::(&mut verifier, 17).unwrap(), + FieldTranscript::::challenge_vector(&mut verifier, 17).unwrap(), first ); assert_eq!( - VerifierTranscript::field_challenges::(&mut verifier, 3).unwrap(), + FieldTranscript::::challenge_vector(&mut verifier, 3).unwrap(), second ); assert_eq!(verifier.prover_message::().unwrap().0, b"after"); @@ -158,13 +159,13 @@ fn round_trips() { } fn sponge_prover>() -> ProverState { - let mut prover = prover_transcript::(SESSION, INSTANCE); + let mut prover = prover_transcript::(SESSION, &INSTANCE); prover.prover_message(&BytesMsg(b"before".to_vec())); prover } fn sponge_verifier>(narg: &[u8]) -> VerifierState<'_, H> { - let mut verifier = verifier_transcript::(SESSION, INSTANCE, narg); + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, narg); assert_eq!(verifier.prover_message::().unwrap().0, b"before"); verifier } From 848af6591263cc92b553b6f9d27dcf9398ef7870 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Tue, 20 Oct 2026 22:30:14 -0400 Subject: [PATCH 02/16] refactor(sumcheck): run over the sealed spongefish transcript --- Cargo.lock | 2 +- crates/jolt-crypto/src/commitment.rs | 11 +- crates/jolt-crypto/src/ec/bn254/gt.rs | 15 +- crates/jolt-crypto/src/ec/bn254/mod.rs | 9 +- crates/jolt-crypto/src/ec/group.rs | 6 +- crates/jolt-field/src/lib.rs | 5 +- crates/jolt-poly/src/evaluation_claim.rs | 26 +- crates/jolt-sumcheck/Cargo.toml | 9 +- ...custom_transcript.rs => clear_sumcheck.rs} | 92 ++-- crates/jolt-sumcheck/fuzz/Cargo.lock | 2 +- .../fuzz_targets/clear_vs_compressed_diff.rs | 18 +- .../fuzz/fuzz_targets/honest_then_corrupt.rs | 17 +- .../fuzz/fuzz_targets/sumcheck_verifier.rs | 4 +- .../fuzz/fuzz_targets/valid_prefix_proof.rs | 23 +- crates/jolt-sumcheck/src/committed.rs | 31 +- crates/jolt-sumcheck/src/error.rs | 11 + crates/jolt-sumcheck/src/lib.rs | 4 +- crates/jolt-sumcheck/src/pow.rs | 21 +- crates/jolt-sumcheck/src/proof.rs | 18 +- crates/jolt-sumcheck/src/prover.rs | 14 +- crates/jolt-sumcheck/src/recorder.rs | 24 +- crates/jolt-sumcheck/src/round_proof.rs | 22 +- .../src/round_scheduler_tests.rs | 16 +- crates/jolt-sumcheck/src/tests.rs | 426 ++++++++++-------- crates/jolt-sumcheck/src/verifier.rs | 30 +- crates/jolt-sumcheck/tests/binary_fields.rs | 180 +++----- crates/jolt-sumcheck/tests/committed.rs | 46 +- .../jolt-sumcheck/tests/extension_fields.rs | 179 ++------ .../tests/external-consumer/Cargo.lock | 233 +++++++++- .../tests/external-consumer/Cargo.toml | 7 +- .../external-consumer/tests/external_field.rs | 312 ------------- .../external-consumer/tests/roundtrip.rs | 2 +- .../jolt-sumcheck/tests/mersenne61_compat.rs | 373 --------------- crates/jolt-sumcheck/tests/roundtrip.rs | 30 +- crates/jolt-sumcheck/tests/soundness.rs | 33 +- crates/jolt-transcript/Cargo.toml | 2 +- crates/jolt-transcript/src/lib.rs | 8 +- crates/jolt-transcript/src/scripted.rs | 143 ++++++ crates/jolt-transcript/src/unpriced.rs | 67 --- scripts/check-external-sumcheck.sh | 4 +- 40 files changed, 1017 insertions(+), 1458 deletions(-) rename crates/jolt-sumcheck/examples/{clear_custom_transcript.rs => clear_sumcheck.rs} (52%) delete mode 100644 crates/jolt-sumcheck/tests/external-consumer/tests/external_field.rs delete mode 100644 crates/jolt-sumcheck/tests/mersenne61_compat.rs create mode 100644 crates/jolt-transcript/src/scripted.rs delete mode 100644 crates/jolt-transcript/src/unpriced.rs diff --git a/Cargo.lock b/Cargo.lock index 4d33bfa453..85f182d629 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3387,6 +3387,7 @@ dependencies = [ "rand_core 0.6.4", "rayon", "serde", + "spongefish 0.8.0", "thiserror 2.0.20", ] @@ -3895,7 +3896,6 @@ dependencies = [ "ark-ff 0.5.0", "blake2 0.11.0", "criterion", - "digest 0.11.3", "getrandom 0.4.3", "jolt-field", "light-poseidon", diff --git a/crates/jolt-crypto/src/commitment.rs b/crates/jolt-crypto/src/commitment.rs index fe0064cf91..e94c24fe94 100644 --- a/crates/jolt-crypto/src/commitment.rs +++ b/crates/jolt-crypto/src/commitment.rs @@ -6,7 +6,7 @@ use std::{ use jolt_field::{Accumulator, CanonicalBytes, JoltField, WithAccumulator}; use jolt_poly::EqPolynomial; -use jolt_transcript::AppendToTranscript; +use jolt_transcript::Encoding; use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[cfg(feature = "parallel")] @@ -55,6 +55,13 @@ impl CanonicalBytes for NoCommitment { fn to_bytes_le(&self, _out: &mut [u8]) {} } +/// Absorbs nothing: a transparent protocol never sends one. +impl Encoding for NoCommitment { + fn encode(&self) -> impl AsRef<[u8]> { + [] + } +} + impl HomomorphicCommitment for NoCommitment { fn add(_c1: &Self, _c2: &Self) -> Self { Self @@ -105,7 +112,7 @@ impl VectorCommitment for NoVectorCommitment { /// elements with a blinding factor. Uses `Self::Output` from the supertrait /// as the commitment value type. pub trait VectorCommitment: - Commitment + Commitment { type Field: JoltField; diff --git a/crates/jolt-crypto/src/ec/bn254/gt.rs b/crates/jolt-crypto/src/ec/bn254/gt.rs index 41a7792536..03ebada63a 100644 --- a/crates/jolt-crypto/src/ec/bn254/gt.rs +++ b/crates/jolt-crypto/src/ec/bn254/gt.rs @@ -6,7 +6,7 @@ use ark_ff::{AdditiveGroup, Field as ArkField, PrimeField}; use ark_serialize::{CanonicalSerialize, Compress, SerializationError, Write}; use jolt_field::JoltField; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::Encoding; use crate::JoltGroup; @@ -150,21 +150,16 @@ impl MulAssign for Bn254GT { } } +/// The 384-byte uncompressed serialization: fixed-width. #[expect(clippy::expect_used)] -impl AppendToTranscript for Bn254GT { - fn append_to_transcript(&self, transcript: &mut T) { +impl Encoding for Bn254GT { + fn encode(&self) -> impl AsRef<[u8]> { use ark_serialize::CanonicalSerialize; let mut buf = Vec::with_capacity(self.0.uncompressed_size()); self.0 .serialize_uncompressed(&mut buf) .expect("GT serialization cannot fail"); - buf.reverse(); - transcript.append_bytes(&buf); - } - - fn transcript_payload_len(&self) -> Option { - use ark_serialize::CanonicalSerialize; - Some(self.0.uncompressed_size() as u64) + buf } } diff --git a/crates/jolt-crypto/src/ec/bn254/mod.rs b/crates/jolt-crypto/src/ec/bn254/mod.rs index 9ac7ec37ce..c72214bb77 100644 --- a/crates/jolt-crypto/src/ec/bn254/mod.rs +++ b/crates/jolt-crypto/src/ec/bn254/mod.rs @@ -1,5 +1,5 @@ /// Generates a `#[repr(transparent)]` wrapper over an arkworks projective curve type, -/// with all operator impls, serde, `AppendToTranscript`, `JoltGroup`, compile-time +/// with all operator impls, serde, `Encoding`, `JoltGroup`, compile-time /// size assertions, and a safe `into_inner` accessor. /// /// Paths are fully qualified so the macro does not inject `use` items into the caller's @@ -161,14 +161,15 @@ macro_rules! impl_jolt_group_wrapper { } } - impl ::jolt_transcript::AppendToTranscript for $wrapper { - fn append_to_transcript(&self, transcript: &mut T) { + /// The compressed serialization: fixed-width. + impl ::jolt_transcript::Encoding for $wrapper { + fn encode(&self) -> impl AsRef<[u8]> { use ::ark_serialize::CanonicalSerialize; let mut buf = Vec::with_capacity(self.0.compressed_size()); self.0 .serialize_compressed(&mut buf) .expect(concat!(stringify!($wrapper), " serialization cannot fail")); - transcript.append_bytes(&buf); + buf } } diff --git a/crates/jolt-crypto/src/ec/group.rs b/crates/jolt-crypto/src/ec/group.rs index 4d155629c4..8e528ed81f 100644 --- a/crates/jolt-crypto/src/ec/group.rs +++ b/crates/jolt-crypto/src/ec/group.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; use jolt_field::JoltField; -use jolt_transcript::AppendToTranscript; +use jolt_transcript::Encoding; use serde::{Deserialize, Serialize}; /// Cryptographic group suitable for commitments. @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; /// seeds accumulators with `Default::default()`; a non-identity default would /// silently offset every aggregate. /// -/// Requires [`AppendToTranscript`] so group elements can be absorbed into +/// Requires a fixed-width [`Encoding`] so group elements can be absorbed into /// Fiat-Shamir transcripts (e.g., Pedersen commitments in ZK sumcheck). pub trait JoltGroup: Clone @@ -42,7 +42,7 @@ pub trait JoltGroup: + SubAssign + Serialize + for<'de> Deserialize<'de> - + AppendToTranscript + + Encoding { #[must_use] fn identity() -> Self; diff --git a/crates/jolt-field/src/lib.rs b/crates/jolt-field/src/lib.rs index 43ef766a45..e1551ed93b 100644 --- a/crates/jolt-field/src/lib.rs +++ b/crates/jolt-field/src/lib.rs @@ -103,7 +103,10 @@ mod algebra; mod binary; #[cfg(feature = "bn254")] mod bn254; -#[cfg(feature = "spongefish")] +#[cfg(all( + feature = "spongefish", + any(feature = "bn254", feature = "solinas", feature = "binary") +))] mod encoding; mod extension; #[cfg(feature = "solinas")] diff --git a/crates/jolt-poly/src/evaluation_claim.rs b/crates/jolt-poly/src/evaluation_claim.rs index 76032b5098..4610586d4e 100644 --- a/crates/jolt-poly/src/evaluation_claim.rs +++ b/crates/jolt-poly/src/evaluation_claim.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::{Point, HIGH_TO_LOW}; #[cfg(feature = "transcript")] -use jolt_transcript::{AppendToTranscript, Label, LabelWithCount, Transcript}; +use jolt_transcript::{Encoding, Label, LabelWithCount}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct EvaluationClaim { @@ -20,17 +20,23 @@ impl EvaluationClaim { } } +/// A [`LabelWithCount`] word carrying the point's length, the coordinates, +/// a [`Label`] word and the value: self-delimiting when `F`'s encoding is +/// fixed-width. #[cfg(feature = "transcript")] -impl AppendToTranscript for EvaluationClaim -where - F: AppendToTranscript, -{ - fn append_to_transcript(&self, transcript: &mut T) { - transcript.append(&LabelWithCount(b"opening_point", self.point.len() as u64)); +impl Encoding for EvaluationClaim { + fn encode(&self) -> impl AsRef<[u8]> { + let mut out = Vec::new(); + out.extend_from_slice( + LabelWithCount(b"opening_point", self.point.len() as u64) + .encode() + .as_ref(), + ); for coordinate in self.point.as_slice() { - coordinate.append_to_transcript(transcript); + out.extend_from_slice(coordinate.encode().as_ref()); } - transcript.append(&Label(b"opening_eval")); - self.value.append_to_transcript(transcript); + out.extend_from_slice(Label(b"opening_eval").encode().as_ref()); + out.extend_from_slice(self.value.encode().as_ref()); + out } } diff --git a/crates/jolt-sumcheck/Cargo.toml b/crates/jolt-sumcheck/Cargo.toml index 5ce4ba7d35..152c984d23 100644 --- a/crates/jolt-sumcheck/Cargo.toml +++ b/crates/jolt-sumcheck/Cargo.toml @@ -26,7 +26,10 @@ jolt-crypto = { path = "../jolt-crypto", default-features = false, optional = tr jolt-field = { path = "../jolt-field", default-features = false } jolt-poly = { path = "../jolt-poly", default-features = false } jolt-r1cs = { path = "../jolt-r1cs", default-features = false, optional = true } -jolt-transcript = { path = "../jolt-transcript", default-features = false } +# The prover's per-round nonce search copies the sponge state. +jolt-transcript = { path = "../jolt-transcript", default-features = false, features = [ + "prover-grinding", +] } rand_core = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } tracing.workspace = true @@ -38,6 +41,8 @@ jolt-crypto = { workspace = true, features = ["bn254"] } jolt-field = { workspace = true, features = ["bn254", "solinas", "binary"] } jolt-transcript = { workspace = true, features = [ "bn254", + "solinas", + "binary", "transcript-blake2b", "transcript-keccak", "test-utils", @@ -50,4 +55,4 @@ name = "committed" required-features = ["committed"] [[example]] -name = "clear_custom_transcript" +name = "clear_sumcheck" diff --git a/crates/jolt-sumcheck/examples/clear_custom_transcript.rs b/crates/jolt-sumcheck/examples/clear_sumcheck.rs similarity index 52% rename from crates/jolt-sumcheck/examples/clear_custom_transcript.rs rename to crates/jolt-sumcheck/examples/clear_sumcheck.rs index da0a47e519..e244513cde 100644 --- a/crates/jolt-sumcheck/examples/clear_custom_transcript.rs +++ b/crates/jolt-sumcheck/examples/clear_sumcheck.rs @@ -1,4 +1,9 @@ -use jolt_field::{Prime64Offset59, Ring}; +// One clear batched sumcheck, proved and verified over spongefish transcripts. +// +// Every message here is public: the prover and the verifier absorb the same +// values, so the verifier's NARG string is empty. + +use jolt_field::{Prime128Offset275, Ring}; use jolt_poly::UnivariatePoly; use jolt_sumcheck::{ prove_batch, BatchMember, BatchPrelude, BooleanHypercube, ClearSumcheckRecorder, ProveRounds, @@ -6,53 +11,12 @@ use jolt_sumcheck::{ OPENING_CLAIM_TRANSCRIPT_LABEL, SUMCHECK_CLAIM_TRANSCRIPT_LABEL, SUMCHECK_ROUND_TRANSCRIPT_LABEL, }; -use jolt_transcript::{ChallengeSetSize, Transcript}; - -type F = Prime64Offset59; - -#[derive(Clone, Default)] -struct FixedTranscript { - state: [u8; 32], - cursor: usize, -} - -impl Transcript for FixedTranscript { - type Challenge = F; - // A scripted toy with no soundness: it declares the largest set so that - // no proof of work runs. A real transcript declares the size of the set - // its challenges are drawn from. - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); - - fn new(label: &'static [u8]) -> Self { - let mut transcript = Self::default(); - transcript.append_bytes(label); - transcript - } - - fn append_bytes(&mut self, bytes: &[u8]) { - for byte in bytes { - let index = self.cursor % self.state.len(); - self.state[index] = self.state[index] - .wrapping_mul(31) - .wrapping_add(*byte) - .wrapping_add(1); - self.cursor += 1; - } - } - - fn challenge(&mut self) -> F { - self.append_bytes(b"challenge"); - F::from_u64(7) - } - - fn challenge_bytes(&mut self, out: &mut [u8]) { - out.fill(0); - } +use jolt_transcript::{ + prover_transcript, verifier_transcript, FieldTranscript, Keccak, Transcript, +}; - fn state(&self) -> [u8; 32] { - self.state - } -} +type F = Prime128Offset275; +const SESSION: &[u8] = b"external-sumcheck-example"; struct LinearRound; @@ -73,18 +37,17 @@ impl ProveRounds for LinearRound { Ok(UnivariatePoly::new(vec![F::from_u64(3), F::from_u64(2)])) } - fn finish_rounds(&mut self, bind: F) -> Result<(), SumcheckError> { - assert_eq!(bind, F::from_u64(7)); + fn finish_rounds(&mut self, _bind: F) -> Result<(), SumcheckError> { Ok(()) } } fn main() -> Result<(), SumcheckError> { let claim = SumcheckClaim::new(1, 1, F::from_u64(8)); - let mut prover_transcript = FixedTranscript::new(b"external-sumcheck-example"); + let mut prover_state = prover_transcript::(SESSION, &claim.claimed_sum); let mut recorder = ClearSumcheckRecorder::::new(); - recorder.absorb_input_claims(&[claim.claimed_sum], &mut prover_transcript); - let coefficient = prover_transcript.challenge_scalar(); + recorder.absorb_input_claims(&[claim.claimed_sum], &mut prover_state); + let coefficient = prover_state.challenge()?; let prelude = BatchPrelude::try_new( vec![BatchMember { input_claim: claim.claimed_sum, @@ -102,14 +65,15 @@ fn main() -> Result<(), SumcheckError> { &mut members, &mut SequentialRounds, &mut recorder, - &mut prover_transcript, + &mut prover_state, )?; - let recorded = recorder.finish(&proved.member_claims, &mut prover_transcript)?; + let recorded = recorder.finish(&proved.member_claims, &mut prover_state)?; let proof = recorded.proof; - let mut verifier_transcript = FixedTranscript::new(b"external-sumcheck-example"); + let narg = prover_state.narg_string().to_vec(); + let mut verifier_transcript = verifier_transcript::(SESSION, &claim.claimed_sum, &narg); verifier_transcript.append_labeled(SUMCHECK_CLAIM_TRANSCRIPT_LABEL, &claim.claimed_sum); - let verifier_coefficient = verifier_transcript.challenge_scalar(); + let verifier_coefficient: F = verifier_transcript.challenge()?; let combined_claim = SumcheckClaim::new( claim.num_vars, claim.degree, @@ -126,10 +90,18 @@ fn main() -> Result<(), SumcheckError> { verifier_transcript.append_labeled(OPENING_CLAIM_TRANSCRIPT_LABEL, opening_claim); } - assert_eq!(reduced.point.as_slice(), &[F::from_u64(7)]); - assert_eq!(proved.member_claims, vec![F::from_u64(17)]); - assert_eq!(reduced.value, coefficient * F::from_u64(17)); + let challenge = proved.challenges[0]; + let round = UnivariatePoly::new(vec![F::from_u64(3), F::from_u64(2)]); + assert_eq!(reduced.point.as_slice(), &[challenge]); + assert_eq!(proved.member_claims, vec![round.evaluate(challenge)]); + assert_eq!(reduced.value, coefficient * round.evaluate(challenge)); assert_eq!(proved.final_claim, reduced.value); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); + verifier_transcript + .check_eof() + .map_err(|_| SumcheckError::TranscriptRejected)?; Ok(()) } diff --git a/crates/jolt-sumcheck/fuzz/Cargo.lock b/crates/jolt-sumcheck/fuzz/Cargo.lock index 7feeb012b6..30572c15e1 100644 --- a/crates/jolt-sumcheck/fuzz/Cargo.lock +++ b/crates/jolt-sumcheck/fuzz/Cargo.lock @@ -438,6 +438,7 @@ dependencies = [ "num-traits", "rand_core", "serde", + "spongefish", "thiserror 2.0.19", ] @@ -486,7 +487,6 @@ dependencies = [ "ark-bn254", "ark-ff", "blake2", - "digest 0.11.3", "getrandom", "jolt-field", "light-poseidon", diff --git a/crates/jolt-sumcheck/fuzz/fuzz_targets/clear_vs_compressed_diff.rs b/crates/jolt-sumcheck/fuzz/fuzz_targets/clear_vs_compressed_diff.rs index 00d2d5e4d4..fc61ccad51 100644 --- a/crates/jolt-sumcheck/fuzz/fuzz_targets/clear_vs_compressed_diff.rs +++ b/crates/jolt-sumcheck/fuzz/fuzz_targets/clear_vs_compressed_diff.rs @@ -16,7 +16,10 @@ use jolt_sumcheck::{ round_pow_bits, BooleanHypercube, CompressedSumcheckProof, EvaluationClaim, SumcheckClaim, SumcheckVerifier, SUMCHECK_ROUND_TRANSCRIPT_LABEL, }; -use jolt_transcript::{AppendToTranscript, Blake2bTranscript, LabelWithCount, Transcript}; +use jolt_transcript::{ + prover_transcript, Blake2b512, FieldTranscript, LabelWithCount, ProverState, ProverTranscript, + Transcript, +}; use libfuzzer_sys::fuzz_target; const SCALAR_BYTES: usize = 32; @@ -50,8 +53,9 @@ fuzz_target!(|data: &[u8]| { // Reference: decompress each round with the running-sum hint, evaluate // the full polynomial, and mirror the wire transcript framing exactly, // grinding each round's proof of work the way an honest prover does. - let bits = round_pow_bits::(degree).expect("difficulty for degree <= 4"); - let mut transcript = Blake2bTranscript::new(b"jolt-sumcheck-diff-fuzz"); + let bits = + round_pow_bits::>(degree).expect("difficulty for degree <= 4"); + let mut transcript = prover_transcript::(b"jolt-sumcheck-diff-fuzz", b""); let mut running_sum = claimed_sum; let mut challenges: Vec = Vec::with_capacity(num_vars); let mut pow_nonces = Vec::with_capacity(num_vars); @@ -62,10 +66,10 @@ fuzz_target!(|data: &[u8]| { stored.len() as u64, )); for coefficient in stored { - coefficient.append_to_transcript(&mut transcript); + transcript.append(coefficient); } - pow_nonces.extend(transcript.grind(bits).expect("proof-of-work search")); - let r: Fr = transcript.challenge(); + pow_nonces.extend(transcript.public_grind(bits).expect("proof-of-work search")); + let r: Fr = transcript.challenge().unwrap(); let full = round.decompress(running_sum); running_sum = full.evaluate(r); challenges.push(r); @@ -76,7 +80,7 @@ fuzz_target!(|data: &[u8]| { round_polynomials: wire_rounds, pow_nonces, }; - let mut transcript = Blake2bTranscript::new(b"jolt-sumcheck-diff-fuzz"); + let mut transcript = prover_transcript::(b"jolt-sumcheck-diff-fuzz", b""); let production = SumcheckVerifier::verify_compressed( &claim, &proof, diff --git a/crates/jolt-sumcheck/fuzz/fuzz_targets/honest_then_corrupt.rs b/crates/jolt-sumcheck/fuzz/fuzz_targets/honest_then_corrupt.rs index 51ec8219cd..77d9706972 100644 --- a/crates/jolt-sumcheck/fuzz/fuzz_targets/honest_then_corrupt.rs +++ b/crates/jolt-sumcheck/fuzz/fuzz_targets/honest_then_corrupt.rs @@ -16,7 +16,9 @@ use jolt_field::{CanonicalEncoding, Field, Fr, Ring}; use jolt_poly::UnivariatePoly; use jolt_sumcheck::{round_pow_bits, BooleanHypercube, SumcheckClaim, SumcheckVerifier}; -use jolt_transcript::{AppendToTranscript, Blake2bTranscript, Transcript}; +use jolt_transcript::{ + prover_transcript, Blake2b512, FieldTranscript, ProverState, ProverTranscript, Transcript, +}; use libfuzzer_sys::fuzz_target; use num_traits::Zero; @@ -62,11 +64,11 @@ fuzz_target!(|data: &[u8]| { // Honest degree-2 prover, binding the low variable each round and // mirroring the verifier's transcript exactly, proof of work included. - let bits = round_pow_bits::(2).expect("difficulty for degree 2"); + let bits = round_pow_bits::>(2).expect("difficulty for degree 2"); let two_inverse = Fr::from_u64(2).inverse().expect("2 is invertible"); let mut a = evals_a.clone(); let mut b = evals_b.clone(); - let mut transcript = Blake2bTranscript::new(b"jolt-sumcheck-corrupt-fuzz"); + let mut transcript = prover_transcript::(b"jolt-sumcheck-corrupt-fuzz", b""); let mut rounds: Vec> = Vec::with_capacity(num_vars); let mut pow_nonces = Vec::with_capacity(num_vars); for _ in 0..num_vars { @@ -87,10 +89,10 @@ fuzz_target!(|data: &[u8]| { let poly = UnivariatePoly::new(vec![c0, c1, c2]); for coefficient in poly.coefficients() { - coefficient.append_to_transcript(&mut transcript); + transcript.append(coefficient); } - pow_nonces.extend(transcript.grind(bits).expect("proof-of-work search")); - let r: Fr = transcript.challenge(); + pow_nonces.extend(transcript.public_grind(bits).expect("proof-of-work search")); + let r: Fr = transcript.challenge().unwrap(); for j in 0..half { a[j] = a[2 * j] + r * (a[2 * j + 1] - a[2 * j]); b[j] = b[2 * j] + r * (b[2 * j + 1] - b[2 * j]); @@ -136,7 +138,8 @@ fuzz_target!(|data: &[u8]| { } let claim = SumcheckClaim::new(num_vars, 2, claimed_sum); - let mut verifier_transcript = Blake2bTranscript::new(b"jolt-sumcheck-corrupt-fuzz"); + let mut verifier_transcript = + prover_transcript::(b"jolt-sumcheck-corrupt-fuzz", b""); let result = SumcheckVerifier::verify::, _>( &claim, &rounds, diff --git a/crates/jolt-sumcheck/fuzz/fuzz_targets/sumcheck_verifier.rs b/crates/jolt-sumcheck/fuzz/fuzz_targets/sumcheck_verifier.rs index c2ca55e438..0789dfce96 100644 --- a/crates/jolt-sumcheck/fuzz/fuzz_targets/sumcheck_verifier.rs +++ b/crates/jolt-sumcheck/fuzz/fuzz_targets/sumcheck_verifier.rs @@ -16,7 +16,7 @@ use jolt_sumcheck::{ BooleanHypercube, CompressedSumcheckProof, SumcheckClaim, SumcheckVerifier, SUMCHECK_ROUND_TRANSCRIPT_LABEL, }; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; use libfuzzer_sys::fuzz_target; const SCALAR_BYTES: usize = 32; @@ -69,7 +69,7 @@ fuzz_target!(|data: &[u8]| { }; // The verifier must terminate without panicking on any input. - let mut transcript = Blake2bTranscript::new(b"jolt-sumcheck-fuzz"); + let mut transcript = prover_transcript::(b"jolt-sumcheck-fuzz", b""); let _ = SumcheckVerifier::verify_compressed( &claim, &proof, diff --git a/crates/jolt-sumcheck/fuzz/fuzz_targets/valid_prefix_proof.rs b/crates/jolt-sumcheck/fuzz/fuzz_targets/valid_prefix_proof.rs index aac1770bcc..cb01318c27 100644 --- a/crates/jolt-sumcheck/fuzz/fuzz_targets/valid_prefix_proof.rs +++ b/crates/jolt-sumcheck/fuzz/fuzz_targets/valid_prefix_proof.rs @@ -22,10 +22,12 @@ #![no_main] -use jolt_field::{Fr, CanonicalEncoding}; +use jolt_field::{CanonicalEncoding, Fr}; use jolt_poly::UnivariatePoly; use jolt_sumcheck::{round_pow_bits, BooleanHypercube, SumcheckClaim, SumcheckVerifier}; -use jolt_transcript::{AppendToTranscript, Blake2bTranscript, Transcript}; +use jolt_transcript::{ + prover_transcript, Blake2b512, FieldTranscript, ProverState, ProverTranscript, Transcript, +}; use libfuzzer_sys::fuzz_target; const SCALAR_BYTES: usize = 32; @@ -49,10 +51,11 @@ fuzz_target!(|data: &[u8]| { // `valid_rounds` rounds, so the proof is valid by construction up to // round `valid_rounds - 1` and the verifier's running sum after // ingesting those rounds matches what we compute below. - let mut prover_transcript = Blake2bTranscript::new(b"jolt-sumcheck-valid-fuzz"); + let mut prover_state = prover_transcript::(b"jolt-sumcheck-valid-fuzz", b""); let mut running_sum = claimed_sum; let mut round_proofs: Vec> = Vec::with_capacity(num_vars); - let bits = round_pow_bits::(degree).expect("difficulty for degree <= 6"); + let bits = + round_pow_bits::>(degree).expect("difficulty for degree <= 6"); let mut pow_nonces: Vec = Vec::with_capacity(num_vars); for round in 0..num_vars { @@ -85,10 +88,14 @@ fuzz_target!(|data: &[u8]| { // Mirror the verifier's transcript / proof-of-work / challenge / // evaluate sequence. for c in poly.coefficients() { - c.append_to_transcript(&mut prover_transcript); + prover_state.append(c); } - pow_nonces.extend(prover_transcript.grind(bits).expect("proof-of-work search")); - let r: Fr = prover_transcript.challenge(); + pow_nonces.extend( + prover_state + .public_grind(bits) + .expect("proof-of-work search"), + ); + let r: Fr = prover_state.challenge().unwrap(); running_sum = poly.evaluate(r); round_proofs.push(poly); } else { @@ -119,7 +126,7 @@ fuzz_target!(|data: &[u8]| { } } - let mut verifier_transcript = Blake2bTranscript::new(b"jolt-sumcheck-valid-fuzz"); + let mut verifier_transcript = prover_transcript::(b"jolt-sumcheck-valid-fuzz", b""); let result = SumcheckVerifier::verify::, _>( &claim, &round_proofs, diff --git a/crates/jolt-sumcheck/src/committed.rs b/crates/jolt-sumcheck/src/committed.rs index 0316dd8915..1885a6f204 100644 --- a/crates/jolt-sumcheck/src/committed.rs +++ b/crates/jolt-sumcheck/src/committed.rs @@ -3,7 +3,7 @@ use jolt_crypto::VectorCommitment; use jolt_field::{Field, JoltField}; #[cfg(feature = "committed")] use jolt_poly::UnivariatePoly; -use jolt_transcript::{AppendToTranscript, LabelWithCount, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, LabelWithCount, Transcript}; #[cfg(feature = "committed")] use rand_core::RngCore; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -25,7 +25,7 @@ pub struct CommittedRound { pub degree: usize, } -impl RoundMessage for CommittedRound { +impl RoundMessage for CommittedRound { fn degree(&self) -> usize { self.degree } @@ -38,7 +38,7 @@ impl RoundMessage for CommittedRound { SUMCHECK_COMMITMENT_LABEL, self.degree as u64, )); - self.commitment.append_to_transcript(transcript); + transcript.append(&self.commitment); } } @@ -47,15 +47,18 @@ pub struct CommittedOutputClaims { pub commitments: Vec, } -impl AppendToTranscript for CommittedOutputClaims { - fn append_to_transcript(&self, transcript: &mut T) { - transcript.append(&LabelWithCount( - OUTPUT_CLAIMS_LABEL, - self.commitments.len() as u64, - )); +/// A [`LabelWithCount`] word carrying the number of commitments, then each +/// commitment. +impl Encoding for CommittedOutputClaims { + fn encode(&self) -> impl AsRef<[u8]> { + let mut out = LabelWithCount(OUTPUT_CLAIMS_LABEL, self.commitments.len() as u64) + .encode() + .as_ref() + .to_vec(); for commitment in &self.commitments { - commitment.append_to_transcript(transcript); + out.extend_from_slice(commitment.encode().as_ref()); } + out } } @@ -280,7 +283,7 @@ where transcript: &mut T, ) -> Result> where - T: Transcript, + T: FieldTranscript + jolt_transcript::ProverTranscript, { let coefficients = round_poly.coefficients().to_vec(); if coefficients.len() > VC::capacity(self.setup) { @@ -303,7 +306,7 @@ where &mut self.pow_nonces, transcript, )?; - let challenge = transcript.challenge(); + let challenge = transcript.challenge()?; self.rounds.push(round); self.witness.round_coefficients.push(coefficients); @@ -324,7 +327,7 @@ where transcript: &mut T, ) -> Result<(SumcheckProof, CommittedSumcheckWitness), SumcheckError> where - T: Transcript, + T: FieldTranscript + jolt_transcript::ProverTranscript, { let capacity = VC::capacity(self.setup); let mut commitments = Vec::with_capacity(output_claim_values.len().div_ceil(capacity)); @@ -337,7 +340,7 @@ where } let output_claims = CommittedOutputClaims { commitments }; - output_claims.append_to_transcript(transcript); + transcript.append(&output_claims); Ok(( SumcheckProof::Committed(CommittedSumcheckProof { rounds: self.rounds, diff --git a/crates/jolt-sumcheck/src/error.rs b/crates/jolt-sumcheck/src/error.rs index b8fdf20824..62061830bf 100644 --- a/crates/jolt-sumcheck/src/error.rs +++ b/crates/jolt-sumcheck/src/error.rs @@ -247,4 +247,15 @@ pub enum SumcheckError { /// Consecutive integer domain points have colliding images in the field. #[error("integer sumcheck domain of size {domain_size} is not distinct in the field")] IntegerDomainNotDistinct { domain_size: usize }, + + /// The transcript refused a challenge draw: the challenge codec does not + /// support the request, or its rejection pool ran out. + #[error("transcript rejected a challenge draw")] + TranscriptRejected, +} + +impl From for SumcheckError { + fn from(_: jolt_transcript::VerificationError) -> Self { + Self::TranscriptRejected + } } diff --git a/crates/jolt-sumcheck/src/lib.rs b/crates/jolt-sumcheck/src/lib.rs index 5db80f2d1c..d78b3303b6 100644 --- a/crates/jolt-sumcheck/src/lib.rs +++ b/crates/jolt-sumcheck/src/lib.rs @@ -81,7 +81,7 @@ //! //! Polynomial and clear sumcheck arithmetic is generic over //! [`Field`](jolt_field::Field). Stock clear transcript adapters additionally -//! require [`AppendToTranscript`](jolt_transcript::AppendToTranscript) where +//! require [`Encoding`](jolt_transcript::Encoding) where //! field values are absorbed. Optimized Jolt kernels and commitment backends //! retain their stronger capability bounds at their own integration points. //! @@ -133,7 +133,7 @@ pub const OPENING_CLAIM_TRANSCRIPT_LABEL: &[u8] = b"opening_claim"; /// Absorbs a sumcheck claim scalar using Jolt's canonical transcript label. pub fn append_sumcheck_claim(transcript: &mut T, claim: &A) where - A: jolt_transcript::AppendToTranscript, + A: jolt_transcript::Encoding, T: jolt_transcript::Transcript, { transcript.append_labeled(SUMCHECK_CLAIM_TRANSCRIPT_LABEL, claim); diff --git a/crates/jolt-sumcheck/src/pow.rs b/crates/jolt-sumcheck/src/pow.rs index dee4b4827f..dafc478523 100644 --- a/crates/jolt-sumcheck/src/pow.rs +++ b/crates/jolt-sumcheck/src/pow.rs @@ -5,7 +5,8 @@ //! A false claim survives a round only when the challenge is a root of a //! nonzero polynomial of degree at most `d`, so the round loses a factor `d` //! (Schwartz-Zippel) and its difficulty is -//! `grind_bits_for_loss(d, T::CHALLENGE_SET, SECURITY_BITS)`. +//! `grind_bits_for_loss(d, T::CHALLENGE_SET, SECURITY_BITS)`, where +//! `T::CHALLENGE_SET` is the [`FieldTranscript`] challenge set for `F`. //! //! `d` is always the verifier's degree bound for the claim or statement, never //! a degree the prover declares, so the difficulty is fixed by the protocol @@ -18,7 +19,7 @@ use std::fmt::{Formatter, Result as FmtResult}; use std::marker::PhantomData; use jolt_field::Field; -use jolt_transcript::{grind_bits_for_loss, Transcript, SECURITY_BITS}; +use jolt_transcript::{grind_bits_for_loss, FieldTranscript, ProverTranscript, SECURITY_BITS}; use serde::de::{Error as _, SeqAccess, Visitor}; use serde::ser::{Error as _, SerializeTuple}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -26,12 +27,14 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::error::SumcheckError; /// Proof-of-work difficulty for a round whose polynomial has degree at most -/// `degree`, with challenges drawn by `T::challenge`. +/// `degree`, with challenges drawn by [`FieldTranscript::challenge`]. /// /// # Errors /// [`SumcheckError::PowDifficulty`] if no supported difficulty reaches the /// target for this degree and challenge set. -pub fn round_pow_bits(degree: usize) -> Result> { +pub fn round_pow_bits>( + degree: usize, +) -> Result> { let loss = u64::try_from(degree.max(1)).map_err(|_| SumcheckError::PowDifficulty { degree })?; grind_bits_for_loss(loss, T::CHALLENGE_SET, SECURITY_BITS) .map_err(|_| SumcheckError::PowDifficulty { degree }) @@ -46,7 +49,7 @@ pub(crate) struct RoundNonces<'a> { impl<'a> RoundNonces<'a> { /// Accepts `nonces` for `rounds` rounds of degree at most `degree`: one /// nonce per round when the difficulty is positive, none otherwise. - pub(crate) fn new( + pub(crate) fn new>( degree: usize, rounds: usize, nonces: &'a [u32], @@ -64,13 +67,13 @@ impl<'a> RoundNonces<'a> { /// Checks round `round`'s nonce against the transcript, after the round /// message is absorbed and before the round challenge is drawn. - pub(crate) fn check( + pub(crate) fn check>( &self, round: usize, transcript: &mut T, ) -> Result<(), SumcheckError> { transcript - .check_grind(self.bits, self.nonces.get(round).copied()) + .check_public_grind(self.bits, self.nonces.get(round).copied()) .map_err(|_| SumcheckError::ProofOfWorkRejected { round }) } } @@ -78,7 +81,7 @@ impl<'a> RoundNonces<'a> { /// Grinds round `round`'s nonce after the round message is absorbed and before /// the round challenge is drawn, appending it to `nonces` when the difficulty /// is positive. -pub fn grind_round( +pub fn grind_round + ProverTranscript>( degree: usize, round: usize, nonces: &mut Vec, @@ -86,7 +89,7 @@ pub fn grind_round( ) -> Result<(), SumcheckError> { let bits = round_pow_bits::(degree)?; let nonce = transcript - .grind(bits) + .public_grind(bits) .map_err(|_| SumcheckError::ProofOfWorkRejected { round })?; nonces.extend(nonce); Ok(()) diff --git a/crates/jolt-sumcheck/src/proof.rs b/crates/jolt-sumcheck/src/proof.rs index 5bef9ff2b8..6b60df8241 100644 --- a/crates/jolt-sumcheck/src/proof.rs +++ b/crates/jolt-sumcheck/src/proof.rs @@ -9,7 +9,7 @@ use crate::{ }; use jolt_field::Field; use jolt_poly::{CompressedPoly, UnivariatePoly}; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// A sumcheck proof consisting of one univariate round polynomial per variable. @@ -138,8 +138,8 @@ impl SumcheckProof { transcript: &mut T, ) -> Result, SumcheckError> where - F: AppendToTranscript, - T: Transcript, + F: Encoding, + T: FieldTranscript, D: SumcheckDomain, { match self { @@ -176,8 +176,8 @@ impl SumcheckProof { transcript: &mut T, ) -> Result, SumcheckError> where - F: AppendToTranscript, - T: Transcript, + F: Encoding, + T: FieldTranscript, { match self { Self::Clear(ClearProof::Compressed(proof)) => SumcheckVerifier::verify_compressed( @@ -210,8 +210,8 @@ impl SumcheckProof { transcript: &mut T, ) -> Result, SumcheckError> where - T: Transcript, - C: Clone + AppendToTranscript, + T: FieldTranscript, + C: Clone + Encoding, { match self { Self::Committed(proof) => proof.verify_committed_consistency(statement, transcript), @@ -237,8 +237,8 @@ impl SumcheckProof { transcript: &mut T, ) -> Result, SumcheckError> where - T: Transcript, - C: Clone + AppendToTranscript, + T: FieldTranscript, + C: Clone + Encoding, { self.verify_committed_consistency(SumcheckStatement::new(num_vars, degree), transcript) } diff --git a/crates/jolt-sumcheck/src/prover.rs b/crates/jolt-sumcheck/src/prover.rs index 4eb7522300..2f36ff8c1b 100644 --- a/crates/jolt-sumcheck/src/prover.rs +++ b/crates/jolt-sumcheck/src/prover.rs @@ -23,7 +23,7 @@ use jolt_field::Field; #[cfg(feature = "committed")] use jolt_field::JoltField; use jolt_poly::{UnivariatePoly, UnivariatePolynomial}; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, ProverTranscript}; #[cfg(feature = "committed")] use rand_core::RngCore; @@ -207,7 +207,7 @@ pub fn prove_batch( where F: Field, R: SumcheckRecorder, - T: Transcript, + T: FieldTranscript + ProverTranscript, { if members.len() != prelude.members.len() { return Err(SumcheckError::BatchMemberCountMismatch { @@ -400,7 +400,7 @@ pub struct ProvedUniskipCommitted { /// Self-check the uni-skip round polynomial against the verifier's round /// checks before anything reaches the transcript: degree bound and /// centered-integer-domain round sum. -fn check_uniskip_round( +fn check_uniskip_round( round_poly: &UnivariatePoly, input_claim: F, degree: usize, @@ -435,15 +435,15 @@ pub fn prove_uniskip_clear( transcript: &mut T, ) -> Result, SumcheckError> where - F: Field + AppendToTranscript, - T: Transcript, + F: Field + Encoding, + T: FieldTranscript + ProverTranscript, { check_uniskip_round(&round_poly, input_claim, degree, domain_size)?; LabeledRoundPoly::uniskip(&round_poly).append_to_transcript(transcript); let mut pow_nonces = Vec::new(); grind_round(degree, 0, &mut pow_nonces, transcript)?; - let challenge = transcript.challenge(); + let challenge = transcript.challenge()?; let output_claim = round_poly.evaluate(challenge); transcript.append_labeled(OPENING_CLAIM_TRANSCRIPT_LABEL, &output_claim); @@ -476,7 +476,7 @@ pub fn prove_uniskip_committed( where F: JoltField, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, R: RngCore, { check_uniskip_round(&round_poly, input_claim, degree, domain_size)?; diff --git a/crates/jolt-sumcheck/src/recorder.rs b/crates/jolt-sumcheck/src/recorder.rs index c8125d4887..ffc24074eb 100644 --- a/crates/jolt-sumcheck/src/recorder.rs +++ b/crates/jolt-sumcheck/src/recorder.rs @@ -21,7 +21,7 @@ use jolt_field::Field; #[cfg(feature = "committed")] use jolt_field::JoltField; use jolt_poly::{CompressedPoly, UnivariatePoly}; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, ProverTranscript}; #[cfg(feature = "committed")] use rand_core::RngCore; @@ -51,7 +51,7 @@ pub trait SumcheckRecorder { /// stage that produced them, so the transcript never sees the scalars. fn absorb_input_claims(&mut self, input_claims: &[F], transcript: &mut T) where - T: Transcript; + T: FieldTranscript + ProverTranscript; /// Record one round polynomial, grind the round's proof of work at the /// difficulty for `degree_bound` (the batch's degree bound, which the @@ -65,7 +65,7 @@ pub trait SumcheckRecorder { transcript: &mut T, ) -> Result> where - T: Transcript; + T: FieldTranscript + ProverTranscript; /// Record the flattened output-claim values (canonical absorb order) and /// assemble the proof. Clear: each value appended under @@ -77,7 +77,7 @@ pub trait SumcheckRecorder { transcript: &mut T, ) -> Result, SumcheckError> where - T: Transcript; + T: FieldTranscript + ProverTranscript; } /// A recorded sumcheck: the wire proof, plus (for a committed recorder) the @@ -113,12 +113,12 @@ impl ClearSumcheckRecorder { } } -impl SumcheckRecorder for ClearSumcheckRecorder { +impl SumcheckRecorder for ClearSumcheckRecorder { type Proof = CompressedSumcheckProof; fn absorb_input_claims(&mut self, input_claims: &[F], transcript: &mut T) where - T: Transcript, + T: FieldTranscript + ProverTranscript, { for input_claim in input_claims { append_sumcheck_claim(transcript, input_claim); @@ -132,7 +132,7 @@ impl SumcheckRecorder for ClearSumcheckRecorde transcript: &mut T, ) -> Result> where - T: Transcript, + T: FieldTranscript + ProverTranscript, { CompressedLabeledRoundPoly::sumcheck(round_poly).append_to_transcript(transcript); grind_round( @@ -141,7 +141,7 @@ impl SumcheckRecorder for ClearSumcheckRecorde &mut self.pow_nonces, transcript, )?; - let challenge = transcript.challenge(); + let challenge = transcript.challenge()?; self.round_polynomials.push(round_poly.compress()); Ok(challenge) } @@ -152,7 +152,7 @@ impl SumcheckRecorder for ClearSumcheckRecorde transcript: &mut T, ) -> Result, SumcheckError> where - T: Transcript, + T: FieldTranscript + ProverTranscript, { for opening_claim in output_claim_values { transcript.append_labeled(OPENING_CLAIM_TRANSCRIPT_LABEL, opening_claim); @@ -208,7 +208,7 @@ where fn absorb_input_claims(&mut self, _input_claims: &[F], _transcript: &mut T) where - T: Transcript, + T: FieldTranscript + ProverTranscript, { } @@ -219,7 +219,7 @@ where transcript: &mut T, ) -> Result> where - T: Transcript, + T: FieldTranscript + ProverTranscript, { self.builder .commit_round(round_poly, degree_bound, transcript) @@ -231,7 +231,7 @@ where transcript: &mut T, ) -> Result, SumcheckError> where - T: Transcript, + T: FieldTranscript + ProverTranscript, { let (proof, witness) = self.builder.finish(output_claim_values, transcript)?; Ok(RecordedSumcheck { diff --git a/crates/jolt-sumcheck/src/round_proof.rs b/crates/jolt-sumcheck/src/round_proof.rs index 028d344781..f1e55bbbbb 100644 --- a/crates/jolt-sumcheck/src/round_proof.rs +++ b/crates/jolt-sumcheck/src/round_proof.rs @@ -1,6 +1,6 @@ use jolt_field::Field; use jolt_poly::{UnivariatePoly, UnivariatePolynomial}; -use jolt_transcript::{AppendToTranscript, LabelWithCount, Transcript}; +use jolt_transcript::{Encoding, LabelWithCount, Transcript}; use crate::error::SumcheckError; use crate::{SUMCHECK_ROUND_TRANSCRIPT_LABEL, UNISKIP_ROUND_TRANSCRIPT_LABEL}; @@ -22,19 +22,19 @@ pub trait ClearRound: RoundMessage { } } -impl RoundMessage for UnivariatePoly { +impl RoundMessage for UnivariatePoly { fn degree(&self) -> usize { UnivariatePolynomial::degree(self) } fn append_to_transcript(&self, transcript: &mut T) { for coeff in self.coefficients() { - coeff.append_to_transcript(transcript); + transcript.append(coeff); } } } -impl ClearRound for UnivariatePoly { +impl ClearRound for UnivariatePoly { fn evaluate(&self, challenge: F) -> F { UnivariatePoly::evaluate(self, challenge) } @@ -68,7 +68,7 @@ impl<'a, F: Field> LabeledRoundPoly<'a, F> { } } -impl RoundMessage for LabeledRoundPoly<'_, F> { +impl RoundMessage for LabeledRoundPoly<'_, F> { fn degree(&self) -> usize { as RoundMessage>::degree(self.poly) } @@ -77,12 +77,12 @@ impl RoundMessage for LabeledRoundPoly<'_, F> { let coeffs = self.poly.coefficients(); transcript.append(&LabelWithCount(self.label, coeffs.len() as u64)); for coeff in coeffs { - coeff.append_to_transcript(transcript); + transcript.append(coeff); } } } -impl ClearRound for LabeledRoundPoly<'_, F> { +impl ClearRound for LabeledRoundPoly<'_, F> { fn evaluate(&self, challenge: F) -> F { as ClearRound>::evaluate(self.poly, challenge) } @@ -117,7 +117,7 @@ impl<'a, F: Field> CompressedLabeledRoundPoly<'a, F> { } } -impl RoundMessage for CompressedLabeledRoundPoly<'_, F> { +impl RoundMessage for CompressedLabeledRoundPoly<'_, F> { fn degree(&self) -> usize { as RoundMessage>::degree(self.poly) } @@ -132,14 +132,14 @@ impl RoundMessage for CompressedLabeledRoundPoly< return; }; transcript.append(&LabelWithCount(self.label, (coeffs.len() - 1) as u64)); - constant.append_to_transcript(transcript); + transcript.append(constant); for c in rest.iter().skip(1) { - c.append_to_transcript(transcript); + transcript.append(c); } } } -impl ClearRound for CompressedLabeledRoundPoly<'_, F> { +impl ClearRound for CompressedLabeledRoundPoly<'_, F> { fn evaluate(&self, challenge: F) -> F { as ClearRound>::evaluate(self.poly, challenge) } diff --git a/crates/jolt-sumcheck/src/round_scheduler_tests.rs b/crates/jolt-sumcheck/src/round_scheduler_tests.rs index 44abce3a9c..7dccf86cee 100644 --- a/crates/jolt-sumcheck/src/round_scheduler_tests.rs +++ b/crates/jolt-sumcheck/src/round_scheduler_tests.rs @@ -7,7 +7,7 @@ )] use jolt_field::{Fr, Ring}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, FieldTranscript, ProverState}; use crate::batch::{BatchMember, BatchPrelude}; use crate::error::SumcheckError; @@ -52,7 +52,7 @@ fn traversal_fixture() -> ( DenseMember, DenseMember, BatchPrelude, - Blake2bTranscript, + ProverState, ClearSumcheckRecorder, ) { let sum_long = F::from_u64(4242); @@ -60,11 +60,11 @@ fn traversal_fixture() -> ( let long = DenseMember::with_sum(3, sum_long, 7); let short = DenseMember::with_sum(1, sum_short, 13); - let mut transcript = Blake2bTranscript::new(b"traversal-seam"); + let mut transcript = prover_transcript::(b"traversal-seam", b""); let mut recorder = ClearSumcheckRecorder::::new(); recorder.absorb_input_claims(&[sum_long, sum_short], &mut transcript); - let coeff_long: F = transcript.challenge_scalar(); - let coeff_short: F = transcript.challenge_scalar(); + let coeff_long: F = transcript.challenge().unwrap(); + let coeff_short: F = transcript.challenge().unwrap(); let prelude = BatchPrelude::new( vec![ BatchMember { @@ -106,7 +106,11 @@ fn proof_is_invariant_under_a_reordering_traversal() { let recorded = recorder .finish(&proved.member_claims, &mut transcript) .unwrap(); - (proved, recorded.proof, transcript.state()) + ( + proved, + recorded.proof, + transcript.verifier_message::<[u8; 32]>(), + ) }; let (sequential, sequential_proof, sequential_state) = prove(false); diff --git a/crates/jolt-sumcheck/src/tests.rs b/crates/jolt-sumcheck/src/tests.rs index 03c5684dc1..e549c71e53 100644 --- a/crates/jolt-sumcheck/src/tests.rs +++ b/crates/jolt-sumcheck/src/tests.rs @@ -10,7 +10,10 @@ use jolt_crypto::{Bn254, Bn254G1, JoltGroup, Pedersen, PedersenSetup, VectorCommitment}; use jolt_field::{Field, Fr, Prime128OffsetA7F7, Ring}; use jolt_poly::{CompressedPoly, UnivariatePoly}; -use jolt_transcript::{AppendToTranscript, Blake2bTranscript, LabelWithCount, Transcript}; +use jolt_transcript::{ + prover_transcript, Blake2b512, Encoding, FieldTranscript, LabelWithCount, ProverState, + ProverTranscript, Transcript, +}; use crate::claim::{EvaluationClaim, SumcheckClaim, SumcheckStatement}; use crate::committed::{ @@ -30,10 +33,10 @@ type F = Fr; /// BN254 challenges need no proof of work, so the proof-of-work tests run /// over Akita's field, whose rounds grind. type P = Prime128OffsetA7F7; -type PT = Blake2bTranscript

; +type PT = ProverState; fn pow_bits(degree: usize) -> u32 { - round_pow_bits::(degree).unwrap() + round_pow_bits::>(degree).unwrap() } /// Replays the verifier's round loop on `transcript` — absorb, proof of work, @@ -41,20 +44,20 @@ fn pow_bits(degree: usize) -> u32 { fn grind_rounds( rounds: &[R], degree: usize, - transcript: &mut Blake2bTranscript, + transcript: &mut ProverState, ) -> Vec { rounds .iter() .filter_map(|round| { round.append_to_transcript(transcript); - let nonce = transcript.grind(pow_bits(degree)).unwrap(); - let _challenge: F = transcript.challenge(); + let nonce = transcript.public_grind(pow_bits(degree)).unwrap(); + let _challenge: F = transcript.challenge().unwrap(); nonce }) .collect() } -fn honest_prove>( +fn honest_prove + ProverTranscript>( evals: &[E], num_vars: usize, transcript: &mut T, @@ -81,11 +84,11 @@ fn honest_prove>( as RoundMessage>::append_to_transcript(&round_poly, transcript); pow_nonces.extend( transcript - .grind(round_pow_bits::(1).unwrap()) + .public_grind(round_pow_bits::(1).unwrap()) .unwrap(), ); - let r: E = transcript.challenge(); + let r: E = transcript.challenge().unwrap(); round_polys.push(round_poly); for i in 0..half { @@ -100,7 +103,10 @@ fn honest_prove>( } } -fn honest_prove_compressed_labeled>( +fn honest_prove_compressed_labeled< + E: Field + Encoding, + T: FieldTranscript + ProverTranscript, +>( evals: &[E], num_vars: usize, label: &'static [u8], @@ -129,11 +135,11 @@ fn honest_prove_compressed_labeled(1).unwrap()) + .public_grind(round_pow_bits::(1).unwrap()) .unwrap(), ); - let r: E = transcript.challenge(); + let r: E = transcript.challenge().unwrap(); compressed_round_polys.push(round_poly.compress()); round_polys.push(round_poly); @@ -165,8 +171,8 @@ fn verify_valid_degree1_proof() { let sum = compute_sum(&evals); let num_vars = 3; - let mut prover_transcript = Blake2bTranscript::new(b"sumcheck-test"); - let proof = honest_prove(&evals, num_vars, &mut prover_transcript); + let mut prover_state = prover_transcript::(b"sumcheck-test", b""); + let proof = honest_prove(&evals, num_vars, &mut prover_state); let claim = SumcheckClaim { num_vars, @@ -174,7 +180,7 @@ fn verify_valid_degree1_proof() { claimed_sum: sum, }; - let mut verifier_transcript = Blake2bTranscript::new(b"sumcheck-test"); + let mut verifier_transcript = prover_transcript::(b"sumcheck-test", b""); let result = SumcheckVerifier::verify( &claim, &proof.round_polynomials, @@ -209,9 +215,9 @@ fn centered_integer_domain_verifies_round_sum() { let pow_nonces = grind_rounds( std::slice::from_ref(&round_poly), 2, - &mut Blake2bTranscript::new(b"sumcheck-integer-domain-test"), + &mut prover_transcript::(b"sumcheck-integer-domain-test", b""), ); - let mut transcript = Blake2bTranscript::new(b"sumcheck-integer-domain-test"); + let mut transcript = prover_transcript::(b"sumcheck-integer-domain-test", b""); let result = SumcheckVerifier::verify( &claim, std::slice::from_ref(&round_poly), @@ -237,9 +243,9 @@ fn centered_integer_domain_uses_core_even_window_convention() { let pow_nonces = grind_rounds( std::slice::from_ref(&round_poly), 1, - &mut Blake2bTranscript::new(b"sumcheck-integer-domain-test"), + &mut prover_transcript::(b"sumcheck-integer-domain-test", b""), ); - let mut transcript = Blake2bTranscript::new(b"sumcheck-integer-domain-test"); + let mut transcript = prover_transcript::(b"sumcheck-integer-domain-test", b""); let result = SumcheckVerifier::verify( &claim, &[round_poly], @@ -260,7 +266,7 @@ fn centered_integer_domain_rejects_wrong_sum() { claimed_sum: F::from_u64(3), }; - let mut transcript = Blake2bTranscript::new(b"sumcheck-integer-domain-test"); + let mut transcript = prover_transcript::(b"sumcheck-integer-domain-test", b""); let result = SumcheckVerifier::verify( &claim, &[round_poly], @@ -288,7 +294,7 @@ fn centered_integer_domain_rejects_empty_domain() { claimed_sum: F::from_u64(0), }; - let mut transcript = Blake2bTranscript::new(b"sumcheck-integer-domain-test"); + let mut transcript = prover_transcript::(b"sumcheck-integer-domain-test", b""); let result = SumcheckVerifier::verify( &claim, &[round_poly], @@ -336,7 +342,7 @@ fn verify_round_check_failure() { let evals: Vec = (1..=8).map(F::from_u64).collect(); let sum = compute_sum(&evals); - let mut pt = Blake2bTranscript::new(b"sumcheck-test"); + let mut pt = prover_transcript::(b"sumcheck-test", b""); let mut proof = honest_prove(&evals, 3, &mut pt); let bad_coeffs = vec![F::from_u64(999), F::from_u64(1)]; @@ -348,7 +354,7 @@ fn verify_round_check_failure() { claimed_sum: sum, }; - let mut vt = Blake2bTranscript::new(b"sumcheck-test"); + let mut vt = prover_transcript::(b"sumcheck-test", b""); let result = SumcheckVerifier::verify( &claim, &proof.round_polynomials, @@ -368,7 +374,7 @@ fn verify_wrong_num_rounds() { let evals: Vec = (1..=8).map(F::from_u64).collect(); let sum = compute_sum(&evals); - let mut pt = Blake2bTranscript::new(b"sumcheck-test"); + let mut pt = prover_transcript::(b"sumcheck-test", b""); let mut proof = honest_prove(&evals, 3, &mut pt); let _ = proof.round_polynomials.pop(); @@ -379,7 +385,7 @@ fn verify_wrong_num_rounds() { claimed_sum: sum, }; - let mut vt = Blake2bTranscript::new(b"sumcheck-test"); + let mut vt = prover_transcript::(b"sumcheck-test", b""); let result = SumcheckVerifier::verify( &claim, &proof.round_polynomials, @@ -401,7 +407,7 @@ fn verify_degree_exceeded() { let evals: Vec = (1..=4).map(F::from_u64).collect(); let sum = compute_sum(&evals); - let mut pt = Blake2bTranscript::new(b"sumcheck-test"); + let mut pt = prover_transcript::(b"sumcheck-test", b""); let mut proof = honest_prove(&evals, 2, &mut pt); proof.round_polynomials[0] = @@ -413,7 +419,7 @@ fn verify_degree_exceeded() { claimed_sum: sum, }; - let mut vt = Blake2bTranscript::new(b"sumcheck-test"); + let mut vt = prover_transcript::(b"sumcheck-test", b""); let result = SumcheckVerifier::verify( &claim, &proof.round_polynomials, @@ -436,16 +442,16 @@ fn clear_round_verifier_with_label_absorbs_label() { let label: &[u8; 10] = b"test_label"; let labeled = LabeledRoundPoly::new(&poly, label); - let mut t1 = Blake2bTranscript::::new(b"sumcheck-test"); + let mut t1 = prover_transcript::(b"sumcheck-test", b""); as RoundMessage>::append_to_transcript(&labeled, &mut t1); - let c1: F = t1.challenge(); + let c1: F = t1.challenge().unwrap(); - let mut t2 = Blake2bTranscript::new(b"sumcheck-test"); + let mut t2 = prover_transcript::(b"sumcheck-test", b""); t2.append(&LabelWithCount(label, 2)); for coeff in poly.coefficients() { - coeff.append_to_transcript(&mut t2); + t2.append(&coeff); } - let c2: F = t2.challenge(); + let c2: F = t2.challenge().unwrap(); assert_eq!(c1, c2, "labeled absorption must match manual absorption"); } @@ -454,15 +460,15 @@ fn clear_round_verifier_with_label_absorbs_label() { fn clear_round_verifier_no_label() { let poly = UnivariatePoly::new(vec![F::from_u64(5), F::from_u64(3)]); - let mut t1 = Blake2bTranscript::::new(b"sumcheck-test"); + let mut t1 = prover_transcript::(b"sumcheck-test", b""); as RoundMessage>::append_to_transcript(&poly, &mut t1); - let c1: F = t1.challenge(); + let c1: F = t1.challenge().unwrap(); - let mut t2 = Blake2bTranscript::new(b"sumcheck-test"); + let mut t2 = prover_transcript::(b"sumcheck-test", b""); for coeff in poly.coefficients() { - coeff.append_to_transcript(&mut t2); + t2.append(&coeff); } - let c2: F = t2.challenge(); + let c2: F = t2.challenge().unwrap(); assert_eq!(c1, c2, "unlabeled absorption must match manual absorption"); } @@ -473,19 +479,19 @@ fn clear_round_verifier_compressed_matches_manual_absorption() { let label: &[u8; 15] = b"compressed_test"; let compressed = CompressedLabeledRoundPoly::new(&poly, label); - let mut t1 = Blake2bTranscript::::new(b"sumcheck-test"); + let mut t1 = prover_transcript::(b"sumcheck-test", b""); as RoundMessage>::append_to_transcript(&compressed, &mut t1); - let ch1: F = t1.challenge(); + let ch1: F = t1.challenge().unwrap(); // Manual absorb matching the compressed wire format: label_with_count(d), c0, c2..cd. - let mut t2 = Blake2bTranscript::new(b"sumcheck-test"); + let mut t2 = prover_transcript::(b"sumcheck-test", b""); let coeffs = poly.coefficients(); t2.append(&LabelWithCount(label, (coeffs.len() - 1) as u64)); - coeffs[0].append_to_transcript(&mut t2); + t2.append(&coeffs[0]); for c in coeffs.iter().skip(2) { - c.append_to_transcript(&mut t2); + t2.append(&c); } - let ch2: F = t2.challenge(); + let ch2: F = t2.challenge().unwrap(); assert_eq!( ch1, ch2, @@ -535,9 +541,9 @@ fn owned_compressed_verify_matches_borrowed_compressed_rounds() { let sum = compute_sum(&evals); let label = SUMCHECK_ROUND_TRANSCRIPT_LABEL; - let mut prover_transcript = Blake2bTranscript::new(b"sumcheck-test"); + let mut prover_state = prover_transcript::(b"sumcheck-test", b""); let (clear_proof, compressed_proof) = - honest_prove_compressed_labeled(&evals, num_vars, label, &mut prover_transcript); + honest_prove_compressed_labeled(&evals, num_vars, label, &mut prover_state); let claim = SumcheckClaim { num_vars, degree: 1, @@ -549,7 +555,7 @@ fn owned_compressed_verify_matches_borrowed_compressed_rounds() { .iter() .map(|poly| CompressedLabeledRoundPoly::new(poly, label)) .collect::>(); - let mut borrowed_transcript = Blake2bTranscript::new(b"sumcheck-test"); + let mut borrowed_transcript = prover_transcript::(b"sumcheck-test", b""); let borrowed = SumcheckVerifier::verify( &claim, &wrapped, @@ -559,13 +565,16 @@ fn owned_compressed_verify_matches_borrowed_compressed_rounds() { ) .unwrap(); - let mut owned_transcript = Blake2bTranscript::new(b"sumcheck-test"); + let mut owned_transcript = prover_transcript::(b"sumcheck-test", b""); let owned = compressed_proof .verify(&claim, BooleanHypercube, label, &mut owned_transcript) .unwrap(); assert_eq!(owned, borrowed); - assert_eq!(owned_transcript.state(), borrowed_transcript.state()); + assert_eq!( + owned_transcript.verifier_message::<[u8; 32]>(), + borrowed_transcript.verifier_message::<[u8; 32]>() + ); let poly = jolt_poly::Polynomial::new(evals); assert_eq!(owned.value, poly.evaluate_and_consume(&owned.point)); @@ -583,7 +592,7 @@ fn owned_compressed_verify_rejects_wrong_round_count() { claimed_sum: F::from_u64(0), }; - let mut transcript = Blake2bTranscript::::new(b"sumcheck-test"); + let mut transcript = prover_transcript::(b"sumcheck-test", b""); let result = proof.verify( &claim, BooleanHypercube, @@ -612,7 +621,7 @@ fn owned_compressed_verify_rejects_degree_bound_exceeded() { claimed_sum: F::from_u64(3), }; - let mut transcript = Blake2bTranscript::::new(b"sumcheck-test"); + let mut transcript = prover_transcript::(b"sumcheck-test", b""); let result = proof.verify( &claim, BooleanHypercube, @@ -638,7 +647,7 @@ fn owned_compressed_verify_rejects_empty_round_polynomial() { claimed_sum: F::from_u64(0), }; - let mut transcript = Blake2bTranscript::::new(b"sumcheck-test"); + let mut transcript = prover_transcript::(b"sumcheck-test", b""); let result = proof.verify( &claim, BooleanHypercube, @@ -658,7 +667,7 @@ fn sumcheck_proof_verify_dispatches_full_clear() { let pow_nonces = grind_rounds( &[LabeledRoundPoly::sumcheck(&round_poly)], 1, - &mut Blake2bTranscript::new(b"sumcheck-proof-dispatch"), + &mut prover_transcript::(b"sumcheck-proof-dispatch", b""), ); let proof = SumcheckProof::::Clear(ClearProof::Full(ClearSumcheckProof { round_polynomials: vec![round_poly], @@ -666,7 +675,7 @@ fn sumcheck_proof_verify_dispatches_full_clear() { })); let claim = SumcheckClaim::new(1, 1, F::from_u64(7)); - let mut transcript = Blake2bTranscript::::new(b"sumcheck-proof-dispatch"); + let mut transcript = prover_transcript::(b"sumcheck-proof-dispatch", b""); let reduction = proof .verify( &claim, @@ -687,7 +696,7 @@ fn sumcheck_proof_verify_rejects_wrong_clear_encoding() { })); let claim = SumcheckClaim::new(1, 1, F::from_u64(0)); - let mut transcript = Blake2bTranscript::::new(b"sumcheck-proof-dispatch"); + let mut transcript = prover_transcript::(b"sumcheck-proof-dispatch", b""); let result = proof.verify( &claim, BooleanHypercube, @@ -718,7 +727,7 @@ fn sumcheck_proof_verify_rejects_committed_encoding() { }); let claim = SumcheckClaim::new(1, 1, F::from_u64(0)); - let mut transcript = Blake2bTranscript::::new(b"sumcheck-proof-dispatch"); + let mut transcript = prover_transcript::(b"sumcheck-proof-dispatch", b""); let result = proof.verify( &claim, BooleanHypercube, @@ -872,19 +881,19 @@ fn committed_rounds_check_transcript_and_return_public_data() { }, ]; - let mut manual = Blake2bTranscript::::new(b"committed-sumcheck"); - let mut expected_challenges = Vec::new(); + let mut manual = prover_transcript::(b"committed-sumcheck", b""); + let mut expected_challenges: Vec = Vec::new(); let mut pow_nonces = Vec::new(); for round in &rounds { manual.append(&LabelWithCount(b"sumcheck_commitment", round.degree as u64)); - round.commitment.append_to_transcript(&mut manual); + manual.append(&round.commitment); // Priced at the statement's degree bound (2), not the round's own degree. - pow_nonces.extend(manual.grind(pow_bits(2)).unwrap()); - expected_challenges.push(manual.challenge()); + pow_nonces.extend(manual.public_grind(pow_bits(2)).unwrap()); + expected_challenges.push(manual.challenge().unwrap()); } - let mut verifier = Blake2bTranscript::::new(b"committed-sumcheck"); - let consistency = SumcheckVerifier::verify_committed_round_consistency( + let mut verifier = prover_transcript::(b"committed-sumcheck", b""); + let consistency = SumcheckVerifier::verify_committed_round_consistency::( SumcheckStatement::new(3, 2), &rounds, &pow_nonces, @@ -901,7 +910,10 @@ fn committed_rounds_check_transcript_and_return_public_data() { .map(|round| round.commitment) .collect::>() ); - assert_eq!(verifier.state(), manual.state()); + assert_eq!( + verifier.verifier_message::<[u8; 32]>(), + manual.verifier_message::<[u8; 32]>() + ); } #[test] @@ -910,9 +922,9 @@ fn committed_rounds_reject_wrong_round_count() { commitment: F::from_u64(11), degree: 1, }]; - let mut transcript = Blake2bTranscript::::new(b"committed-sumcheck"); + let mut transcript = prover_transcript::(b"committed-sumcheck", b""); - let result = SumcheckVerifier::verify_committed_round_consistency( + let result = SumcheckVerifier::verify_committed_round_consistency::( SumcheckStatement::new(2, 1), &rounds, &[], @@ -934,10 +946,10 @@ fn committed_rounds_reject_degree_bound_before_absorbing() { commitment: F::from_u64(11), degree: 3, }]; - let mut transcript = Blake2bTranscript::::new(b"committed-sumcheck"); - let before = transcript.state(); + let mut transcript = prover_transcript::(b"committed-sumcheck", b""); + let mut untouched = prover_transcript::(b"committed-sumcheck", b""); - let result = SumcheckVerifier::verify_committed_round_consistency( + let result = SumcheckVerifier::verify_committed_round_consistency::( SumcheckStatement::new(1, 2), &rounds, &[], @@ -948,7 +960,10 @@ fn committed_rounds_reject_degree_bound_before_absorbing() { result, Err(SumcheckError::DegreeBoundExceeded { got: 3, max: 2 }) )); - assert_eq!(transcript.state(), before); + assert_eq!( + transcript.verifier_message::<[u8; 32]>(), + untouched.verifier_message::<[u8; 32]>() + ); } #[test] @@ -957,17 +972,22 @@ fn committed_output_claims_absorb_length_and_order() { commitments: vec![F::from_u64(3), F::from_u64(5), F::from_u64(8)], }; - let mut actual = Blake2bTranscript::::new(b"committed-output"); - output_claims.append_to_transcript(&mut actual); + let mut actual = prover_transcript::(b"committed-output", b""); + actual.append(&output_claims); - let mut expected = Blake2bTranscript::::new(b"committed-output"); + let mut expected = prover_transcript::(b"committed-output", b""); expected.append(&LabelWithCount(b"output_claims_coms", 3)); for commitment in &output_claims.commitments { - commitment.append_to_transcript(&mut expected); + expected.append(&commitment); } - assert_eq!(actual.state(), expected.state()); - assert_eq!(actual.challenge(), expected.challenge()); + assert_eq!( + actual.verifier_message::<[u8; 32]>(), + expected.verifier_message::<[u8; 32]>() + ); + let actual: F = actual.challenge().unwrap(); + let expected: F = expected.challenge().unwrap(); + assert_eq!(actual, expected); } #[test] @@ -989,27 +1009,32 @@ fn committed_proof_checks_rounds_then_output_claims() { }, }; - let mut manual = Blake2bTranscript::::new(b"committed-proof"); - let mut expected_challenges = Vec::new(); + let mut manual = prover_transcript::(b"committed-proof", b""); + let mut expected_challenges: Vec = Vec::new(); for round in &proof.rounds { manual.append(&LabelWithCount(b"sumcheck_commitment", round.degree as u64)); - round.commitment.append_to_transcript(&mut manual); - proof.pow_nonces.extend(manual.grind(pow_bits(2)).unwrap()); - expected_challenges.push(manual.challenge()); + manual.append(&round.commitment); + proof + .pow_nonces + .extend(manual.public_grind(pow_bits(2)).unwrap()); + expected_challenges.push(manual.challenge().unwrap()); } manual.append(&LabelWithCount(b"output_claims_coms", 2)); for commitment in &proof.output_claims.commitments { - commitment.append_to_transcript(&mut manual); + manual.append(&commitment); } - let mut verifier = Blake2bTranscript::::new(b"committed-proof"); - let consistency = proof + let mut verifier = prover_transcript::(b"committed-proof", b""); + let consistency: CommittedSumcheckConsistency = proof .verify_committed_consistency(SumcheckStatement::new(2, 2), &mut verifier) .unwrap(); assert_eq!(consistency.challenges(), expected_challenges); assert_eq!(consistency.round_degrees(), vec![1, 2]); - assert_eq!(verifier.state(), manual.state()); + assert_eq!( + verifier.verifier_message::<[u8; 32]>(), + manual.verifier_message::<[u8; 32]>() + ); } #[test] @@ -1073,7 +1098,7 @@ impl RoundMessage for MockClearRound { } fn append_to_transcript(&self, transcript: &mut T) { - F::from_u64(42).append_to_transcript(transcript); + transcript.append(&F::from_u64(42)); } } @@ -1105,9 +1130,9 @@ fn verify_accepts_custom_clear_round_messages() { let pow_nonces = grind_rounds( &round_proofs, 1, - &mut Blake2bTranscript::new(b"sumcheck-test"), + &mut prover_transcript::(b"sumcheck-test", b""), ); - let mut vt = Blake2bTranscript::new(b"sumcheck-test"); + let mut vt = prover_transcript::(b"sumcheck-test", b""); let result = SumcheckVerifier::verify( &claim, &round_proofs, @@ -1157,9 +1182,9 @@ fn clear_recorder_roundtrip_matches_compressed_verifier() { .collect(); let claimed_sum = compute_sum(&evals); - let mut prover_transcript = Blake2bTranscript::new(b"recorder-test"); + let mut prover_state = prover_transcript::(b"recorder-test", b""); let mut recorder = ClearSumcheckRecorder::::new(); - recorder.absorb_input_claims(&[claimed_sum], &mut prover_transcript); + recorder.absorb_input_claims(&[claimed_sum], &mut prover_state); let mut buf = evals; for _round in 0..num_vars { @@ -1169,7 +1194,7 @@ fn clear_recorder_roundtrip_matches_compressed_verifier() { let round_poly = UnivariatePoly::new(vec![eval_0, eval_1 - eval_0]); let r = recorder - .absorb_round(&round_poly, 1, &mut prover_transcript) + .absorb_round(&round_poly, 1, &mut prover_state) .unwrap(); for i in 0..half { @@ -1178,11 +1203,9 @@ fn clear_recorder_roundtrip_matches_compressed_verifier() { buf.truncate(half); } let final_eval = buf[0]; - let recorded = recorder - .finish(&[final_eval], &mut prover_transcript) - .unwrap(); + let recorded = recorder.finish(&[final_eval], &mut prover_state).unwrap(); - let mut verifier_transcript = Blake2bTranscript::new(b"recorder-test"); + let mut verifier_transcript = prover_transcript::(b"recorder-test", b""); append_sumcheck_claim(&mut verifier_transcript, &claimed_sum); let reduction = SumcheckProof::::from(recorded.proof) .verify_compressed_boolean(num_vars, 1, claimed_sum, &mut verifier_transcript) @@ -1190,7 +1213,10 @@ fn clear_recorder_roundtrip_matches_compressed_verifier() { verifier_transcript.append_labeled(OPENING_CLAIM_TRANSCRIPT_LABEL, &reduction.value); assert_eq!(reduction.value, final_eval); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } /// A minimal dense multilinear [`ProveRounds`](crate::prover::ProveRounds) @@ -1283,7 +1309,7 @@ fn prove_batch_rejects_zero_max_degree() { max_degree: 0, }; let mut members: Vec<&mut dyn ProveRounds> = vec![&mut member]; - let mut transcript = Blake2bTranscript::new(b"zero-degree-batch"); + let mut transcript = prover_transcript::(b"zero-degree-batch", b""); let mut recorder = ClearSumcheckRecorder::::new(); let result = prove_batch( &prelude, @@ -1316,11 +1342,11 @@ fn prove_batch_clear_twin_matches_compressed_verifier_with_padding() { let mut long = DenseMember::with_sum(3, sum_long, 5); let mut short = DenseMember::with_sum(1, sum_short, 91); - let mut prover_transcript = Blake2bTranscript::new(b"prove-batch-twin"); + let mut prover_state = prover_transcript::(b"prove-batch-twin", b""); let mut recorder = ClearSumcheckRecorder::::new(); - recorder.absorb_input_claims(&[sum_long, sum_short], &mut prover_transcript); - let coeff_long: F = prover_transcript.challenge_scalar(); - let coeff_short: F = prover_transcript.challenge_scalar(); + recorder.absorb_input_claims(&[sum_long, sum_short], &mut prover_state); + let coeff_long: F = prover_state.challenge().unwrap(); + let coeff_short: F = prover_state.challenge().unwrap(); let prelude = BatchPrelude::new( vec![ BatchMember { @@ -1345,7 +1371,7 @@ fn prove_batch_clear_twin_matches_compressed_verifier_with_padding() { &mut members, &mut SequentialRounds, &mut recorder, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); assert_eq!( @@ -1353,15 +1379,15 @@ fn prove_batch_clear_twin_matches_compressed_verifier_with_padding() { vec![long.final_eval(), short.final_eval()] ); let recorded = recorder - .finish(&proved.member_claims, &mut prover_transcript) + .finish(&proved.member_claims, &mut prover_state) .unwrap(); assert!(recorded.committed_witness.is_none()); - let mut verifier_transcript = Blake2bTranscript::new(b"prove-batch-twin"); + let mut verifier_transcript = prover_transcript::(b"prove-batch-twin", b""); append_sumcheck_claim(&mut verifier_transcript, &sum_long); append_sumcheck_claim(&mut verifier_transcript, &sum_short); - let verifier_coeff_long: F = verifier_transcript.challenge_scalar(); - let verifier_coeff_short: F = verifier_transcript.challenge_scalar(); + let verifier_coeff_long: F = verifier_transcript.challenge().unwrap(); + let verifier_coeff_short: F = verifier_transcript.challenge().unwrap(); assert_eq!( (verifier_coeff_long, verifier_coeff_short), (coeff_long, coeff_short) @@ -1379,7 +1405,10 @@ fn prove_batch_clear_twin_matches_compressed_verifier_with_padding() { assert_eq!(reduction.value, proved.final_claim); assert_eq!(reduction.point.as_slice(), proved.challenges.as_slice()); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } /// Twin-transcript lock for a head-aligned member: a 1-round member at @@ -1401,11 +1430,11 @@ fn prove_batch_clear_twin_head_aligned_member() { // The head-aligned kernel's table carries the 2^(3 - 1) padding scale. let mut short = DenseMember::with_sum(1, sum_short.mul_pow_2(2), 91); - let mut prover_transcript = Blake2bTranscript::new(b"prove-batch-head-twin"); + let mut prover_state = prover_transcript::(b"prove-batch-head-twin", b""); let mut recorder = ClearSumcheckRecorder::::new(); - recorder.absorb_input_claims(&[sum_long, sum_short], &mut prover_transcript); - let coeff_long: F = prover_transcript.challenge_scalar(); - let coeff_short: F = prover_transcript.challenge_scalar(); + recorder.absorb_input_claims(&[sum_long, sum_short], &mut prover_state); + let coeff_long: F = prover_state.challenge().unwrap(); + let coeff_short: F = prover_state.challenge().unwrap(); let prelude = BatchPrelude::new( vec![ BatchMember { @@ -1430,7 +1459,7 @@ fn prove_batch_clear_twin_head_aligned_member() { &mut members, &mut SequentialRounds, &mut recorder, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); let quarter = F::from_u64(4).inverse().unwrap(); @@ -1439,16 +1468,16 @@ fn prove_batch_clear_twin_head_aligned_member() { vec![long.final_eval(), short.final_eval() * quarter] ); let recorded = recorder - .finish(&proved.member_claims, &mut prover_transcript) + .finish(&proved.member_claims, &mut prover_state) .unwrap(); // Verifier twin: the claimed sum is position-independent — identical to // the tail-aligned layout's. - let mut verifier_transcript = Blake2bTranscript::new(b"prove-batch-head-twin"); + let mut verifier_transcript = prover_transcript::(b"prove-batch-head-twin", b""); append_sumcheck_claim(&mut verifier_transcript, &sum_long); append_sumcheck_claim(&mut verifier_transcript, &sum_short); - let verifier_coeff_long: F = verifier_transcript.challenge_scalar(); - let verifier_coeff_short: F = verifier_transcript.challenge_scalar(); + let verifier_coeff_long: F = verifier_transcript.challenge().unwrap(); + let verifier_coeff_short: F = verifier_transcript.challenge().unwrap(); let claimed_sum = verifier_coeff_long * sum_long + verifier_coeff_short * sum_short.mul_pow_2(2); assert_eq!(claimed_sum, prelude.claimed_sum); @@ -1461,7 +1490,10 @@ fn prove_batch_clear_twin_head_aligned_member() { assert_eq!(reduction.value, proved.final_claim); assert_eq!(reduction.point.as_slice(), proved.challenges.as_slice()); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } /// The committed twin of the batched engine: the same members proved through @@ -1483,12 +1515,12 @@ fn prove_batch_committed_twin_matches_committed_consistency() { let mut long = DenseMember::with_sum(3, sum_long, 23); let mut short = DenseMember::with_sum(1, sum_short, 57); - let mut prover_transcript = Blake2bTranscript::new(b"prove-batch-zk-twin"); + let mut prover_state = prover_transcript::(b"prove-batch-zk-twin", b""); let mut recorder = CommittedSumcheckRecorder::::new(&setup, rand_core::OsRng).unwrap(); - recorder.absorb_input_claims(&[sum_long, sum_short], &mut prover_transcript); - let coeff_long: F = prover_transcript.challenge_scalar(); - let coeff_short: F = prover_transcript.challenge_scalar(); + recorder.absorb_input_claims(&[sum_long, sum_short], &mut prover_state); + let coeff_long: F = prover_state.challenge().unwrap(); + let coeff_short: F = prover_state.challenge().unwrap(); let prelude = BatchPrelude::new( vec![ BatchMember { @@ -1513,25 +1545,28 @@ fn prove_batch_committed_twin_matches_committed_consistency() { &mut members, &mut SequentialRounds, &mut recorder, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); let recorded = recorder - .finish(&proved.member_claims, &mut prover_transcript) + .finish(&proved.member_claims, &mut prover_state) .unwrap(); // Verifier twin: the committed path absorbs no claim scalars — only the // coefficient squeezes and the commitments the proof carries. - let mut verifier_transcript = Blake2bTranscript::new(b"prove-batch-zk-twin"); - let _coeff_long: F = verifier_transcript.challenge_scalar(); - let _coeff_short: F = verifier_transcript.challenge_scalar(); + let mut verifier_transcript = prover_transcript::(b"prove-batch-zk-twin", b""); + let _coeff_long: F = verifier_transcript.challenge().unwrap(); + let _coeff_short: F = verifier_transcript.challenge().unwrap(); let consistency = recorded .proof .verify_committed_consistency_dims(3, 1, &mut verifier_transcript) .unwrap(); assert_eq!(consistency.challenges(), proved.challenges); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); let witness = recorded.committed_witness.unwrap(); let committed = recorded.proof.as_committed().unwrap(); @@ -1583,17 +1618,12 @@ fn prove_uniskip_clear_twin_matches_uniskip_verify() { let input_claim = as ClearRound>::coefficient_linear_combination(&poly, &coefficients); - let mut prover_transcript = Blake2bTranscript::new(b"uniskip-twin"); - let proved = prove_uniskip_clear::( - poly, - input_claim, - degree, - domain_size, - &mut prover_transcript, - ) - .unwrap(); + let mut prover_state = prover_transcript::(b"uniskip-twin", b""); + let proved = + prove_uniskip_clear::(poly, input_claim, degree, domain_size, &mut prover_state) + .unwrap(); - let mut verifier_transcript = Blake2bTranscript::new(b"uniskip-twin"); + let mut verifier_transcript = prover_transcript::(b"uniskip-twin", b""); let reduction = SumcheckProof::::from(proved.proof) .verify( &SumcheckClaim::new(1, degree, input_claim), @@ -1606,7 +1636,10 @@ fn prove_uniskip_clear_twin_matches_uniskip_verify() { verifier_transcript.append_labeled(OPENING_CLAIM_TRANSCRIPT_LABEL, &proved.output_claim); assert_eq!(reduction.point.as_slice(), &[proved.challenge]); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } /// The committed uni-skip twin: `prove_uniskip_committed` must be @@ -1634,7 +1667,7 @@ fn prove_uniskip_committed_twin_matches_committed_consistency() { let input_claim = as ClearRound>::coefficient_linear_combination(&poly, &coefficients); - let mut prover_transcript = Blake2bTranscript::new(b"uniskip-zk-twin"); + let mut prover_state = prover_transcript::(b"uniskip-zk-twin", b""); let proved = prove_uniskip_committed::( poly, input_claim, @@ -1642,18 +1675,21 @@ fn prove_uniskip_committed_twin_matches_committed_consistency() { domain_size, &setup, rand_core::OsRng, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let mut verifier_transcript = Blake2bTranscript::new(b"uniskip-zk-twin"); + let mut verifier_transcript = prover_transcript::(b"uniskip-zk-twin", b""); let consistency = proved .proof .verify_committed_consistency(SumcheckStatement::new(1, degree), &mut verifier_transcript) .unwrap(); assert_eq!(consistency.challenges(), vec![proved.challenge]); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); assert_eq!( proved.witness.output_claim_rows.concat(), vec![proved.output_claim], @@ -1663,7 +1699,7 @@ fn prove_uniskip_committed_twin_matches_committed_consistency() { #[test] fn bn254_round_proofs_carry_no_nonce_bytes() { let evals: Vec = (1..=8).map(F::from_u64).collect(); - let mut transcript = Blake2bTranscript::new(b"sumcheck-test"); + let mut transcript = prover_transcript::(b"sumcheck-test", b""); let proof = honest_prove(&evals, 3, &mut transcript); assert!(proof.pow_nonces.is_empty()); @@ -1709,24 +1745,29 @@ fn nonced_round_trips_and_rejects_malformed_input() { assert!(decode(&[3, 7, 0]).is_err()); } -/// Replays rounds `0..round` with their honest nonces, absorbs `rounds[round]`, -/// and returns the first nonce that fails that round's proof of work. +/// Replays rounds `0..round` with their honest nonces on a transcript for +/// `session`, absorbs `rounds[round]`, and returns the first nonce that fails +/// that round's proof of work. fn failing_nonce( rounds: &[R], nonces: &[u32], round: usize, degree: usize, - mut transcript: PT, + session: &[u8], ) -> u32 { let bits = round_pow_bits::(degree).unwrap(); - for (message, &nonce) in rounds[..round].iter().zip(nonces) { - message.append_to_transcript(&mut transcript); - transcript.check_grind(bits, Some(nonce)).unwrap(); - let _challenge: P = transcript.challenge(); - } - rounds[round].append_to_transcript(&mut transcript); + let probe = || { + let mut transcript = prover_transcript::(session, b""); + for (message, &nonce) in rounds[..round].iter().zip(nonces) { + message.append_to_transcript(&mut transcript); + transcript.check_public_grind(bits, Some(nonce)).unwrap(); + let _challenge: P = transcript.challenge().unwrap(); + } + rounds[round].append_to_transcript(&mut transcript); + transcript + }; (0..u32::MAX) - .find(|&nonce| transcript.clone().check_grind(bits, Some(nonce)).is_err()) + .find(|&nonce| probe().check_public_grind(bits, Some(nonce)).is_err()) .unwrap() } @@ -1734,17 +1775,17 @@ fn failing_nonce( fn clear_verify_rejects_wrong_pow_nonce() { let evals: Vec

= (1..=8).map(P::from_u64).collect(); let claim = SumcheckClaim::new(3, 1, compute_sum(&evals)); - let mut prover_transcript = PT::new(b"sumcheck-test"); - let mut proof = honest_prove(&evals, 3, &mut prover_transcript); + let mut prover_state = prover_transcript::(b"sumcheck-test", b""); + let mut proof = honest_prove(&evals, 3, &mut prover_state); proof.pow_nonces[1] = failing_nonce( &proof.round_polynomials, &proof.pow_nonces, 1, 1, - PT::new(b"sumcheck-test"), + b"sumcheck-test", ); - let mut transcript = PT::new(b"sumcheck-test"); + let mut transcript = prover_transcript::(b"sumcheck-test", b""); let result = SumcheckVerifier::verify( &claim, &proof.round_polynomials, @@ -1763,14 +1804,14 @@ fn clear_verify_rejects_wrong_pow_nonce() { fn clear_verify_rejects_dropped_and_extra_pow_nonces() { let evals: Vec

= (1..=8).map(P::from_u64).collect(); let claim = SumcheckClaim::new(3, 1, compute_sum(&evals)); - let mut prover_transcript = PT::new(b"sumcheck-test"); - let proof = honest_prove(&evals, 3, &mut prover_transcript); + let mut prover_state = prover_transcript::(b"sumcheck-test", b""); + let proof = honest_prove(&evals, 3, &mut prover_state); let dropped = &proof.pow_nonces[..2]; let extra = [proof.pow_nonces.as_slice(), &[0]].concat(); for (nonces, got) in [(dropped, 2), (extra.as_slice(), 4)] { - let mut transcript = PT::new(b"sumcheck-test"); - let initial_state = transcript.state(); + let mut transcript = prover_transcript::(b"sumcheck-test", b""); + let mut untouched = prover_transcript::(b"sumcheck-test", b""); let result = SumcheckVerifier::verify( &claim, &proof.round_polynomials, @@ -1783,7 +1824,10 @@ fn clear_verify_rejects_dropped_and_extra_pow_nonces() { result, Err(SumcheckError::WrongNumberOfPowNonces { expected: 3, got: actual }) if actual == got )); - assert_eq!(transcript.state(), initial_state); + assert_eq!( + transcript.verifier_message::<[u8; 32]>(), + untouched.verifier_message::<[u8; 32]>() + ); } } @@ -1792,9 +1836,9 @@ fn compressed_verify_rejects_wrong_and_dropped_pow_nonces() { let evals: Vec

= (1..=8).map(P::from_u64).collect(); let claim = SumcheckClaim::new(3, 1, compute_sum(&evals)); let label = SUMCHECK_ROUND_TRANSCRIPT_LABEL; - let mut prover_transcript = PT::new(b"sumcheck-test"); + let mut prover_state = prover_transcript::(b"sumcheck-test", b""); let (clear_proof, compressed_proof) = - honest_prove_compressed_labeled(&evals, 3, label, &mut prover_transcript); + honest_prove_compressed_labeled(&evals, 3, label, &mut prover_state); let wrapped = clear_proof .round_polynomials .iter() @@ -1807,9 +1851,9 @@ fn compressed_verify_rejects_wrong_and_dropped_pow_nonces() { &compressed_proof.pow_nonces, 2, 1, - PT::new(b"sumcheck-test"), + b"sumcheck-test", ); - let mut transcript = PT::new(b"sumcheck-test"); + let mut transcript = prover_transcript::(b"sumcheck-test", b""); assert!(matches!( wrong.verify(&claim, BooleanHypercube, label, &mut transcript), Err(SumcheckError::ProofOfWorkRejected { round: 2 }) @@ -1817,7 +1861,7 @@ fn compressed_verify_rejects_wrong_and_dropped_pow_nonces() { let mut dropped = compressed_proof; dropped.pow_nonces.clear(); - let mut transcript = PT::new(b"sumcheck-test"); + let mut transcript = prover_transcript::(b"sumcheck-test", b""); assert!(matches!( dropped.verify(&claim, BooleanHypercube, label, &mut transcript), Err(SumcheckError::WrongNumberOfPowNonces { @@ -1840,30 +1884,32 @@ fn committed_rounds_reject_wrong_and_dropped_pow_nonces() { }, ]; let statement = SumcheckStatement::new(2, 2); - let mut prover = PT::new(b"committed-sumcheck"); + let mut prover = prover_transcript::(b"committed-sumcheck", b""); let mut pow_nonces = Vec::new(); for round in &rounds { prover.append(&LabelWithCount(b"sumcheck_commitment", round.degree as u64)); - round.commitment.append_to_transcript(&mut prover); - pow_nonces.extend(prover.grind(round_pow_bits::(2).unwrap()).unwrap()); - let _challenge: P = prover.challenge(); + prover.append(&round.commitment); + pow_nonces.extend( + prover + .public_grind(round_pow_bits::(2).unwrap()) + .unwrap(), + ); + let _challenge: P = prover.challenge().unwrap(); } - let mut probe = PT::new(b"committed-sumcheck"); - probe.append(&LabelWithCount(b"sumcheck_commitment", 2)); - rounds[0].commitment.append_to_transcript(&mut probe); + let bits = round_pow_bits::(2).unwrap(); let mut wrong = pow_nonces.clone(); wrong[0] = (0..u32::MAX) .find(|&nonce| { - probe - .clone() - .check_grind(round_pow_bits::(2).unwrap(), Some(nonce)) - .is_err() + let mut probe = prover_transcript::(b"committed-sumcheck", b""); + probe.append(&LabelWithCount(b"sumcheck_commitment", 2)); + probe.append(&rounds[0].commitment); + probe.check_public_grind(bits, Some(nonce)).is_err() }) .unwrap(); - let mut transcript = PT::new(b"committed-sumcheck"); + let mut transcript = prover_transcript::(b"committed-sumcheck", b""); assert!(matches!( - SumcheckVerifier::verify_committed_round_consistency( + SumcheckVerifier::verify_committed_round_consistency::( statement, &rounds, &wrong, @@ -1872,9 +1918,9 @@ fn committed_rounds_reject_wrong_and_dropped_pow_nonces() { Err(SumcheckError::ProofOfWorkRejected { round: 0 }) )); - let mut transcript = PT::new(b"committed-sumcheck"); + let mut transcript = prover_transcript::(b"committed-sumcheck", b""); assert!(matches!( - SumcheckVerifier::verify_committed_round_consistency( + SumcheckVerifier::verify_committed_round_consistency::( statement, &rounds, &pow_nonces[..1], @@ -1886,12 +1932,14 @@ fn committed_rounds_reject_wrong_and_dropped_pow_nonces() { }) )); - let mut transcript = PT::new(b"committed-sumcheck"); - assert!(SumcheckVerifier::verify_committed_round_consistency( - statement, - &rounds, - &pow_nonces, - &mut transcript, - ) - .is_ok()); + let mut transcript = prover_transcript::(b"committed-sumcheck", b""); + assert!( + SumcheckVerifier::verify_committed_round_consistency::( + statement, + &rounds, + &pow_nonces, + &mut transcript, + ) + .is_ok() + ); } diff --git a/crates/jolt-sumcheck/src/verifier.rs b/crates/jolt-sumcheck/src/verifier.rs index 48cec51346..95fd3831f7 100644 --- a/crates/jolt-sumcheck/src/verifier.rs +++ b/crates/jolt-sumcheck/src/verifier.rs @@ -1,6 +1,6 @@ use jolt_field::Field; use jolt_poly::UnivariatePolynomial; -use jolt_transcript::{AppendToTranscript, LabelWithCount, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, LabelWithCount}; use crate::claim::{EvaluationClaim, SumcheckClaim, SumcheckStatement}; use crate::committed::{ @@ -55,7 +55,7 @@ impl SumcheckVerifier { ) -> Result, SumcheckError> where F: Field, - T: Transcript, + T: FieldTranscript, R: ClearRound, D: SumcheckDomain, { @@ -80,7 +80,7 @@ impl SumcheckVerifier { domain.check_round_sum(round, running_sum, round_proof)?; round_proof.append_to_transcript(transcript); pow.check(round, transcript)?; - let r: F = transcript.challenge(); + let r: F = transcript.challenge()?; running_sum = round_proof.evaluate(r); challenges.push(r); } @@ -97,8 +97,8 @@ impl SumcheckVerifier { transcript: &mut T, ) -> Result, SumcheckError> where - F: Field + AppendToTranscript, - T: Transcript, + F: Field + Encoding, + T: FieldTranscript, { if proof.round_polynomials.len() != claim.num_vars { return Err(SumcheckError::WrongNumberOfRounds { @@ -126,10 +126,10 @@ impl SumcheckVerifier { transcript.append(&LabelWithCount(round_label, coeffs.len() as u64)); for coeff in coeffs { - coeff.append_to_transcript(transcript); + transcript.append(coeff); } pow.check(round, transcript)?; - let r: F = transcript.challenge(); + let r: F = transcript.challenge()?; running_sum = round_proof.evaluate_with_hint(running_sum, r); challenges.push(r); } @@ -150,8 +150,8 @@ impl SumcheckVerifier { ) -> Result, SumcheckError> where F: Field, - T: Transcript, - C: Clone + AppendToTranscript, + T: FieldTranscript, + C: Clone + Encoding, { if round_proofs.len() != statement.num_vars { return Err(SumcheckError::WrongNumberOfRounds { @@ -177,7 +177,7 @@ impl SumcheckVerifier { rounds.push(VerifiedCommittedRound { commitment: round_proof.commitment.clone(), degree: round_proof.degree, - challenge: transcript.challenge(), + challenge: transcript.challenge()?, }); } @@ -187,7 +187,7 @@ impl SumcheckVerifier { impl CompressedSumcheckProof where - F: Field + AppendToTranscript, + F: Field + Encoding, { pub fn verify( &self, @@ -197,7 +197,7 @@ where transcript: &mut T, ) -> Result, SumcheckError> where - T: Transcript, + T: FieldTranscript, { SumcheckVerifier::verify_compressed(claim, self, domain, round_label, transcript) } @@ -216,8 +216,8 @@ impl CommittedSumcheckProof { ) -> Result, SumcheckError> where F: Field, - T: Transcript, - C: Clone + AppendToTranscript, + T: FieldTranscript, + C: Clone + Encoding, { let consistency = SumcheckVerifier::verify_committed_round_consistency( statement, @@ -225,7 +225,7 @@ impl CommittedSumcheckProof { &self.pow_nonces, transcript, )?; - self.output_claims.append_to_transcript(transcript); + transcript.append(&self.output_claims); Ok(consistency) } } diff --git a/crates/jolt-sumcheck/tests/binary_fields.rs b/crates/jolt-sumcheck/tests/binary_fields.rs index 046e9770b7..f30f932b68 100644 --- a/crates/jolt-sumcheck/tests/binary_fields.rs +++ b/crates/jolt-sumcheck/tests/binary_fields.rs @@ -15,15 +15,17 @@ use jolt_sumcheck::{ SUMCHECK_ROUND_TRANSCRIPT_LABEL, }; use jolt_transcript::{ - AppendToTranscript, Blake2bTranscript, ChallengeSetSize, LabelWithCount, Transcript, - UnpricedTranscript, + prover_transcript, Blake2b512, ChallengeCodec, Encoding, FieldTranscript, LabelWithCount, + ProverState, ProverTranscript, ScriptedTranscript, Transcript, }; const LABEL: &[u8] = b"binary-sumcheck"; -trait BinaryField: JoltField + AppendToTranscript { +trait BinaryField: JoltField + Encoding + ChallengeCodec { /// The hashing transcript these tests run over this field. - type HashTranscript: Transcript; + type HashTranscript: FieldTranscript + ProverTranscript; + + fn hash_transcript() -> Self::HashTranscript; fn sample(seed: u64) -> Self; } @@ -36,7 +38,11 @@ fn sample_word(seed: u64) -> u64 { } impl BinaryField for F128 { - type HashTranscript = Blake2bTranscript; + type HashTranscript = ProverState; + + fn hash_transcript() -> Self::HashTranscript { + prover_transcript::(LABEL, b"") + } fn sample(seed: u64) -> Self { Self::from_raw( @@ -49,7 +55,11 @@ impl BinaryField for F128 { // the 128-bit target, so a priced transcript rejects every round; the `F64` // tests check sumcheck algebra, not Fiat-Shamir security. impl BinaryField for F64 { - type HashTranscript = UnpricedTranscript>; + type HashTranscript = ScriptedTranscript, Self>; + + fn hash_transcript() -> Self::HashTranscript { + ScriptedTranscript::new(prover_transcript::(LABEL, b"")) + } fn sample(seed: u64) -> Self { Self::from_raw(sample_word(seed)) @@ -162,11 +172,11 @@ struct BatchFixture { prelude: BatchPrelude, proved: ProvedBatch, proof: SumcheckProof, - transcript_state: [u8; 32], + transcript_state: F, } -impl BatchFixture { - fn prove>( +impl BatchFixture { + fn prove + ProverTranscript>( original: &[DenseProduct], offsets: &[usize], max_num_vars: usize, @@ -182,7 +192,7 @@ impl BatchFixture { .zip(claims) .map(|((member, &offset), input_claim)| BatchMember { input_claim, - coefficient: transcript.challenge_scalar(), + coefficient: transcript.challenge().unwrap(), rounds: member.rounds, offset, }) @@ -214,20 +224,20 @@ impl BatchFixture { prelude, proved, proof: recorded.proof.into(), - transcript_state: transcript.state(), + transcript_state: transcript.challenge().unwrap(), } } - fn replay_head>(&self, transcript: &mut T) { + fn replay_head>(&self, transcript: &mut T) { for member in &self.prelude.members { transcript.append_labeled(SUMCHECK_CLAIM_TRANSCRIPT_LABEL, &member.input_claim); } for member in &self.prelude.members { - assert_eq!(transcript.challenge_scalar(), member.coefficient); + assert_eq!(transcript.challenge().unwrap(), member.coefficient); } } - fn verify>(&self, mut transcript: T) -> EvaluationClaim { + fn verify>(&self, mut transcript: T) -> EvaluationClaim { self.replay_head(&mut transcript); let reduced = self .proof @@ -243,7 +253,7 @@ impl BatchFixture { } assert_eq!(reduced.point.as_slice(), self.proved.challenges); assert_eq!(reduced.value, self.proved.final_claim); - assert_eq!(transcript.state(), self.transcript_state); + assert_eq!(transcript.challenge().unwrap(), self.transcript_state); reduced } @@ -283,9 +293,15 @@ fn single_members() { &[0], 4, degree, - ScriptedTranscript::with_script(script.clone()), + ScriptedTranscript::with_script( + prover_transcript::(LABEL, b""), + script.clone(), + ), ); - let reduced = fixture.verify(ScriptedTranscript::with_script(script)); + let reduced = fixture.verify(ScriptedTranscript::with_script( + prover_transcript::(LABEL, b""), + script, + )); assert_eq!( fixture.prelude.members[0].input_claim, products[0].claimed_sum() @@ -305,14 +321,8 @@ fn mixed_linear_members() { DenseProduct::::sampled(2, 1, 300), DenseProduct::::sampled(2, 1, 400), ]; - let fixture = BatchFixture::prove( - &products, - &[0, 2, 0, 1], - 4, - 1, - F::HashTranscript::new(LABEL), - ); - let _ = fixture.verify(F::HashTranscript::new(LABEL)); + let fixture = BatchFixture::prove(&products, &[0, 2, 0, 1], 4, 1, F::hash_transcript()); + let _ = fixture.verify(F::hash_transcript()); for (member, indices) in [(1, [0, 1, 2, 3]), (2, [0, 4, 8, 12]), (3, [0, 2, 4, 6])] { let mut extended = vec![F::zero(); 16]; for (&value, index) in products[member].factors[0].iter().zip(indices) { @@ -332,8 +342,8 @@ fn mixed_quadratic_members() { DenseProduct::::sampled(2, 2, 200), DenseProduct::::sampled(2, 2, 300), ]; - let fixture = BatchFixture::prove(&products, &[0, 2, 1], 4, 2, F::HashTranscript::new(LABEL)); - let _ = fixture.verify(F::HashTranscript::new(LABEL)); + let fixture = BatchFixture::prove(&products, &[0, 2, 1], 4, 2, F::hash_transcript()); + let _ = fixture.verify(F::hash_transcript()); let r = &fixture.proved.challenges; assert_eq!( fixture.proved.member_claims[1], @@ -356,75 +366,17 @@ fn zero_native_claim() { rounds: 1, }, ]; - let fixture = BatchFixture::prove(&products, &[0, 1], 2, 1, F::HashTranscript::new(LABEL)); + let fixture = BatchFixture::prove(&products, &[0, 1], 2, 1, F::hash_transcript()); assert_eq!(fixture.prelude.members[1].input_claim, F::zero()); assert_eq!( fixture.proved.member_claims[1], u * (F::one() - fixture.proved.challenges[0]) ); assert!(!fixture.proved.member_claims[1].is_zero()); - let _ = fixture.verify(F::HashTranscript::new(LABEL)); + let _ = fixture.verify(F::hash_transcript()); fixture.check_output_scales(&products); } -#[derive(Clone, Default)] -struct ScriptedTranscript { - script: Vec, - next: usize, - state: [u8; 32], - cursor: usize, -} - -impl ScriptedTranscript { - fn with_script(script: Vec) -> Self { - let mut transcript = Self { - script, - ..Self::default() - }; - transcript.append_bytes(LABEL); - transcript - } -} - -impl Transcript for ScriptedTranscript { - type Challenge = F; - // Scripted challenges: no proof of work. - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); - - fn new(label: &'static [u8]) -> Self { - let mut transcript = Self::default(); - transcript.append_bytes(label); - transcript - } - - fn append_bytes(&mut self, bytes: &[u8]) { - for &byte in bytes { - let index = self.cursor % self.state.len(); - self.state[index] = self.state[index] - .wrapping_mul(31) - .wrapping_add(byte) - .wrapping_add(1); - self.cursor += 1; - } - } - - fn challenge(&mut self) -> F { - let challenge = self.script[self.next]; - self.next += 1; - self.append_bytes(b"scripted_challenge"); - challenge.append_to_transcript(self); - challenge - } - - fn challenge_bytes(&mut self, out: &mut [u8]) { - out.fill(0); - } - - fn state(&self) -> [u8; 32] { - self.state - } -} - fn zero_padding_factor() { for (offset, challenges) in [(1, [F::one(), F::sample(6)]), (0, [F::sample(6), F::one()])] { let products = [DenseProduct::::sampled(1, 2, 200)]; @@ -434,7 +386,10 @@ fn zero_padding_factor() { &[offset], 2, 2, - ScriptedTranscript::with_script(script.clone()), + ScriptedTranscript::with_script( + prover_transcript::(LABEL, b""), + script.clone(), + ), ); assert_eq!(fixture.proved.challenges, challenges); assert_eq!( @@ -443,7 +398,10 @@ fn zero_padding_factor() { ); assert_eq!(fixture.proved.member_claims, vec![F::zero()]); assert_eq!(fixture.proved.final_claim, F::zero()); - let _ = fixture.verify(ScriptedTranscript::with_script(script)); + let _ = fixture.verify(ScriptedTranscript::with_script( + prover_transcript::(LABEL, b""), + script, + )); fixture.check_output_scales(&products); } } @@ -453,7 +411,7 @@ fn rejection() { DenseProduct::::sampled(4, 2, 31), DenseProduct::::sampled(2, 2, 97), ]; - let fixture = BatchFixture::prove(&products, &[0, 1], 4, 2, F::HashTranscript::new(LABEL)); + let fixture = BatchFixture::prove(&products, &[0, 1], 4, 2, F::hash_transcript()); let mut changed_prelude = fixture.prelude.clone(); changed_prelude.claimed_sum += F::one(); let mut members = products.clone(); @@ -461,7 +419,7 @@ fn rejection() { .iter_mut() .map(|member| member as &mut dyn ProveRounds) .collect(); - let mut transcript = F::HashTranscript::new(LABEL); + let mut transcript = F::hash_transcript(); fixture.replay_head(&mut transcript); assert!(matches!( prove_batch( @@ -484,7 +442,7 @@ fn rejection() { *round = CompressedPoly::new(coefficients); // The honest nonces were ground against the untampered rounds; regrind so // the round loop reaches the final oracle check under test. - let mut transcript = F::HashTranscript::new(LABEL); + let mut transcript = F::hash_transcript(); fixture.replay_head(&mut transcript); proof.pow_nonces = proof .round_polynomials @@ -496,15 +454,15 @@ fn rejection() { coefficients.len() as u64, )); for coefficient in coefficients { - coefficient.append_to_transcript(&mut transcript); + transcript.append(coefficient); } let bits = round_pow_bits::(2).unwrap(); - let nonce = transcript.grind(bits).unwrap(); - let _ = transcript.challenge(); + let nonce = transcript.public_grind(bits).unwrap(); + let _ = transcript.challenge().unwrap(); nonce }) .collect(); - let mut transcript = F::HashTranscript::new(LABEL); + let mut transcript = F::hash_transcript(); fixture.replay_head(&mut transcript); let reduced = altered .verify_compressed_boolean(4, 2, fixture.prelude.claimed_sum, &mut transcript) @@ -533,9 +491,10 @@ fn rejection() { panic!("expected a compressed proof") }; let _ = proof.round_polynomials.pop().unwrap(); - let mut transcript = F::HashTranscript::new(LABEL); + let mut transcript = F::hash_transcript(); fixture.replay_head(&mut transcript); - let state = transcript.state(); + let mut untouched = F::hash_transcript(); + fixture.replay_head(&mut untouched); assert!(matches!( missing_round.verify_compressed_boolean(4, 2, fixture.prelude.claimed_sum, &mut transcript), Err(SumcheckError::WrongNumberOfRounds { @@ -543,14 +502,17 @@ fn rejection() { got: 3 }) )); - assert_eq!(transcript.state(), state); + assert_eq!( + transcript.challenge().unwrap(), + untouched.challenge().unwrap() + ); } fn uncompressed() { let original = DenseProduct::::sampled(4, 3, 27); let input_claim = original.claimed_sum(); let mut member = original.clone(); - let mut prover = F::HashTranscript::new(LABEL); + let mut prover = F::hash_transcript(); let mut previous_claim = input_claim; let mut bind = None; let mut rounds = Vec::new(); @@ -561,8 +523,8 @@ fn uncompressed() { LabeledRoundPoly::new(&polynomial, SUMCHECK_ROUND_TRANSCRIPT_LABEL) .append_to_transcript(&mut prover); let bits = round_pow_bits::(3).unwrap(); - pow_nonces.extend(prover.grind(bits).unwrap()); - let challenge = prover.challenge(); + pow_nonces.extend(prover.public_grind(bits).unwrap()); + let challenge = prover.challenge().unwrap(); previous_claim = polynomial.evaluate(challenge); bind = Some(challenge); challenges.push(challenge); @@ -573,7 +535,7 @@ fn uncompressed() { .iter() .map(|polynomial| LabeledRoundPoly::new(polynomial, SUMCHECK_ROUND_TRANSCRIPT_LABEL)) .collect(); - let mut verifier = F::HashTranscript::new(LABEL); + let mut verifier = F::hash_transcript(); let reduced = SumcheckVerifier::verify( &SumcheckClaim::new(4, 3, input_claim), &labeled, @@ -585,7 +547,7 @@ fn uncompressed() { assert_eq!(reduced.point.as_slice(), challenges); assert_eq!(reduced.value, original.evaluate(&challenges)); assert_eq!(reduced.value, previous_claim); - assert_eq!(prover.state(), verifier.state()); + assert_eq!(prover.challenge().unwrap(), verifier.challenge().unwrap()); } fn output_scale_errors() { @@ -673,18 +635,20 @@ fn integer_domains() { CenteredIntegerDomain::new(3).check_round_sum(0, F::sample(4), &polynomial), Err(SumcheckError::IntegerDomainNotDistinct { domain_size: 3 }) )); - let mut transcript = F::HashTranscript::new(LABEL); - let state = transcript.state(); + let mut transcript = F::hash_transcript(); + let mut untouched = F::hash_transcript(); assert!(matches!( prove_uniskip_clear::(polynomial.clone(), F::sample(4), 2, 3, &mut transcript), Err(SumcheckError::IntegerDomainNotDistinct { domain_size: 3 }) )); - assert_eq!(transcript.state(), state); assert!(matches!( prove_uniskip_clear::(polynomial, F::sample(4), 1, 3, &mut transcript), Err(SumcheckError::DegreeBoundExceeded { got: 2, max: 1 }) )); - assert_eq!(transcript.state(), state); + assert_eq!( + transcript.challenge().unwrap(), + untouched.challenge().unwrap() + ); } macro_rules! binary_tests { diff --git a/crates/jolt-sumcheck/tests/committed.rs b/crates/jolt-sumcheck/tests/committed.rs index 3a18efe2be..b4281350c9 100644 --- a/crates/jolt-sumcheck/tests/committed.rs +++ b/crates/jolt-sumcheck/tests/committed.rs @@ -6,7 +6,9 @@ use jolt_sumcheck::round_proof::RoundMessage; use jolt_sumcheck::{ round_pow_bits, CommittedRound, CommittedRoundWitness, SumcheckStatement, SumcheckVerifier, }; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{ + prover_transcript, Blake2b512, FieldTranscript, ProverState, ProverTranscript, +}; type F = Fr; type VC = Pedersen; @@ -44,14 +46,14 @@ fn grind_nonces( degree: usize, label: &'static [u8], ) -> Vec { - let bits = round_pow_bits::>(degree).unwrap(); - let mut transcript = Blake2bTranscript::::new(label); + let bits = round_pow_bits::>(degree).unwrap(); + let mut transcript = prover_transcript::(label, b""); rounds .iter() .filter_map(|round| { round.append_to_transcript(&mut transcript); - let nonce = transcript.grind(bits).unwrap(); - let _challenge: F = transcript.challenge(); + let nonce = transcript.public_grind(bits).unwrap(); + let _challenge: F = transcript.challenge().unwrap(); nonce }) .collect() @@ -69,18 +71,18 @@ fn committed_rounds_complete_with_pedersen_commitments() { ], ); - let bits = round_pow_bits::>(2).unwrap(); - let mut prover_transcript = Blake2bTranscript::::new(b"committed-roundtrip"); - let mut expected_challenges = Vec::new(); + let bits = round_pow_bits::>(2).unwrap(); + let mut prover_state = prover_transcript::(b"committed-roundtrip", b""); + let mut expected_challenges: Vec = Vec::new(); let mut pow_nonces = Vec::new(); for round in &rounds { - round.append_to_transcript(&mut prover_transcript); - pow_nonces.extend(prover_transcript.grind(bits).unwrap()); - expected_challenges.push(prover_transcript.challenge()); + round.append_to_transcript(&mut prover_state); + pow_nonces.extend(prover_state.public_grind(bits).unwrap()); + expected_challenges.push(prover_state.challenge().unwrap()); } - let mut verifier_transcript = Blake2bTranscript::::new(b"committed-roundtrip"); - let consistency = SumcheckVerifier::verify_committed_round_consistency( + let mut verifier_transcript = prover_transcript::(b"committed-roundtrip", b""); + let consistency = SumcheckVerifier::verify_committed_round_consistency::( SumcheckStatement::new(rounds.len(), 2), &rounds, &pow_nonces, @@ -90,7 +92,10 @@ fn committed_rounds_complete_with_pedersen_commitments() { assert_eq!(consistency.challenges(), expected_challenges); assert_eq!(consistency.round_degrees(), vec![2, 1, 2]); - assert_eq!(verifier_transcript.state(), prover_transcript.state()); + assert_eq!( + verifier_transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } #[test] @@ -110,8 +115,8 @@ fn tampered_committed_round_changes_challenges() { ) .remove(0); - let mut original_transcript = Blake2bTranscript::::new(b"committed-roundtrip"); - let original = SumcheckVerifier::verify_committed_round_consistency( + let mut original_transcript = prover_transcript::(b"committed-roundtrip", b""); + let original = SumcheckVerifier::verify_committed_round_consistency::( SumcheckStatement::new(2, 2), &rounds, &grind_nonces(&rounds, 2, b"committed-roundtrip"), @@ -119,8 +124,8 @@ fn tampered_committed_round_changes_challenges() { ) .unwrap(); - let mut tampered_transcript = Blake2bTranscript::::new(b"committed-roundtrip"); - let tampered = SumcheckVerifier::verify_committed_round_consistency( + let mut tampered_transcript = prover_transcript::(b"committed-roundtrip", b""); + let tampered = SumcheckVerifier::verify_committed_round_consistency::( SumcheckStatement::new(2, 2), &tampered, &grind_nonces(&tampered, 2, b"committed-roundtrip"), @@ -129,5 +134,8 @@ fn tampered_committed_round_changes_challenges() { .unwrap(); assert_ne!(tampered.challenges(), original.challenges()); - assert_ne!(tampered_transcript.state(), original_transcript.state()); + assert_ne!( + tampered_transcript.verifier_message::<[u8; 32]>(), + original_transcript.verifier_message::<[u8; 32]>() + ); } diff --git a/crates/jolt-sumcheck/tests/extension_fields.rs b/crates/jolt-sumcheck/tests/extension_fields.rs index 936ebe287e..80e5e614f7 100644 --- a/crates/jolt-sumcheck/tests/extension_fields.rs +++ b/crates/jolt-sumcheck/tests/extension_fields.rs @@ -1,8 +1,6 @@ #![expect(clippy::unwrap_used, reason = "tests may panic on assertion failures")] -use std::marker::PhantomData; - -use jolt_field::{Ext2, Field, FpExt4, Prime128Offset275, Prime32Offset99, Prime64Offset59, Ring}; +use jolt_field::{Ext2, Field, FpExt4, Prime32Offset99, Prime64Offset59, Ring}; use jolt_poly::UnivariatePoly; use jolt_sumcheck::{ prove_batch, prove_uniskip_clear, BatchMember, BatchPrelude, BooleanHypercube, @@ -11,131 +9,20 @@ use jolt_sumcheck::{ SumcheckVerifier, OPENING_CLAIM_TRANSCRIPT_LABEL, SUMCHECK_CLAIM_TRANSCRIPT_LABEL, SUMCHECK_ROUND_TRANSCRIPT_LABEL, UNISKIP_ROUND_TRANSCRIPT_LABEL, }; -use jolt_transcript::{AppendToTranscript, ChallengeSetSize, Transcript}; -use num_traits::Zero; +use jolt_transcript::{ + prover_transcript, verifier_transcript, Blake2b512, ChallengeCodec, Encoding, FieldTranscript, + Transcript, +}; use serde::{Deserialize, Serialize}; -trait ChallengeField: Field + AppendToTranscript + 'static { - fn challenge_from_seed(seed: u64) -> Self; -} +/// A field the challenge codecs cover, absorbed through its encoding. +trait ChallengeField: Field + Encoding + ChallengeCodec + 'static {} + +impl ChallengeField for F {} type Quadratic = Ext2; type Quartic = FpExt4; -impl ChallengeField for Quadratic { - fn challenge_from_seed(seed: u64) -> Self { - Self::new( - Prime64Offset59::from_u64(seed + 1), - Prime64Offset59::from_u64(seed + 11), - ) - } -} - -impl ChallengeField for Quartic { - fn challenge_from_seed(seed: u64) -> Self { - Self::new(std::array::from_fn(|index| { - Prime32Offset99::from_u64(seed + 3 * index as u64 + 1) - })) - } -} - -impl ChallengeField for Prime128Offset275 { - fn challenge_from_seed(seed: u64) -> Self { - Self::from_u64(seed + 1) - } -} - -#[derive(Clone)] -struct ExtensionTranscript { - state: [u8; 32], - cursor: usize, - challenge_index: u64, - _field: PhantomData, -} - -impl Default for ExtensionTranscript { - fn default() -> Self { - Self { - state: [0; 32], - cursor: 0, - challenge_index: 0, - _field: PhantomData, - } - } -} - -impl Transcript for ExtensionTranscript { - type Challenge = F; - // Scripted challenges: no proof of work. - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); - - fn new(label: &'static [u8]) -> Self { - let mut transcript = Self::default(); - transcript.append_bytes(label); - transcript - } - - fn append_bytes(&mut self, bytes: &[u8]) { - for byte in bytes { - let index = self.cursor % self.state.len(); - self.state[index] = self.state[index] - .wrapping_mul(31) - .wrapping_add(*byte) - .wrapping_add(1); - self.cursor += 1; - } - } - - fn challenge(&mut self) -> F { - self.append_bytes(b"extension_challenge"); - let state_seed = u64::from_le_bytes(self.state[..8].try_into().unwrap()); - let seed = state_seed.wrapping_add(self.challenge_index); - self.challenge_index = self.challenge_index.wrapping_add(1); - F::challenge_from_seed(seed) - } - - fn challenge_bytes(&mut self, out: &mut [u8]) { - out.fill(0); - } - - fn state(&self) -> [u8; 32] { - self.state - } -} - -#[derive(Clone, Default)] -struct CapturingTranscript { - absorbed: Vec, -} - -impl Transcript for CapturingTranscript { - type Challenge = Quadratic; - // Scripted challenges: no proof of work. - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); - - fn new(label: &'static [u8]) -> Self { - Self { - absorbed: label.to_vec(), - } - } - - fn append_bytes(&mut self, bytes: &[u8]) { - self.absorbed.extend_from_slice(bytes); - } - - fn challenge(&mut self) -> Self::Challenge { - Quadratic::zero() - } - - fn challenge_bytes(&mut self, out: &mut [u8]) { - out.fill(0); - } - - fn state(&self) -> [u8; 32] { - [0; 32] - } -} - struct ProductOfAffines { num_rounds: usize, left: Vec, @@ -226,10 +113,10 @@ where let right = affine_table(right_coefficients); let input_claim: F = left.iter().zip(&right).map(|(&a, &b)| a * b).sum(); - let mut prover_transcript = ExtensionTranscript::::new(b"extension-sumcheck"); + let mut prover_state = prover_transcript::(b"extension-sumcheck", b""); let mut recorder = ClearSumcheckRecorder::::new(); - recorder.absorb_input_claims(&[input_claim], &mut prover_transcript); - let coefficient = prover_transcript.challenge_scalar(); + recorder.absorb_input_claims(&[input_claim], &mut prover_state); + let coefficient = prover_state.challenge().unwrap(); let prelude = BatchPrelude::try_new( vec![BatchMember { input_claim, @@ -248,11 +135,11 @@ where &mut members, &mut SequentialRounds, &mut recorder, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); let recorded = recorder - .finish(&proved.member_claims, &mut prover_transcript) + .finish(&proved.member_claims, &mut prover_state) .unwrap(); let proof = recorded.proof; @@ -261,9 +148,10 @@ where bincode::serde::decode_from_slice(&wire, bincode::config::standard()).unwrap(); assert_eq!(read, wire.len()); - let mut verifier_transcript = ExtensionTranscript::::new(b"extension-sumcheck"); + let mut verifier_transcript = + verifier_transcript::(b"extension-sumcheck", b"", &[]); verifier_transcript.append_labeled(SUMCHECK_CLAIM_TRANSCRIPT_LABEL, &input_claim); - let verifier_coefficient = verifier_transcript.challenge_scalar(); + let verifier_coefficient: F = verifier_transcript.challenge().unwrap(); assert_eq!(verifier_coefficient, coefficient); let reduced = SumcheckVerifier::verify_compressed( &SumcheckClaim::new(2, 2, coefficient * input_claim), @@ -282,7 +170,10 @@ where assert_eq!(proved.member_claims, vec![expected]); assert_eq!(reduced.value, coefficient * expected); assert_eq!(proved.final_claim, reduced.value); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } fn run_uniskip_roundtrip(coefficients: [F; 3]) @@ -294,17 +185,12 @@ where .into_iter() .map(|point| polynomial.evaluate(F::from_i64(point))) .sum(); - let mut prover_transcript = ExtensionTranscript::::new(b"extension-uniskip"); - let proved = prove_uniskip_clear::( - polynomial.clone(), - input_claim, - 2, - 3, - &mut prover_transcript, - ) - .unwrap(); + let mut prover_state = prover_transcript::(b"extension-uniskip", b""); + let proved = + prove_uniskip_clear::(polynomial.clone(), input_claim, 2, 3, &mut prover_state) + .unwrap(); - let mut verifier_transcript = ExtensionTranscript::::new(b"extension-uniskip"); + let mut verifier_transcript = verifier_transcript::(b"extension-uniskip", b"", &[]); let reduced = SumcheckProof::::from(proved.proof) .verify( &SumcheckClaim::new(1, 2, input_claim), @@ -318,17 +204,18 @@ where assert_eq!(reduced.point.as_slice(), &[proved.challenge]); assert_eq!(reduced.value, polynomial.evaluate(proved.challenge)); assert_eq!(reduced.value, proved.output_claim); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } #[test] -fn quadratic_transcript_absorption_reverses_the_complete_payload() { +fn quadratic_encoding_is_the_canonical_little_endian_bytes() { let value = Quadratic::new(Prime64Offset59::from_u64(1), Prime64Offset59::from_u64(2)); - let mut transcript = CapturingTranscript::default(); - value.append_to_transcript(&mut transcript); assert_eq!( - transcript.absorbed, - [0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1] + value.encode().as_ref(), + [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0] ); } diff --git a/crates/jolt-sumcheck/tests/external-consumer/Cargo.lock b/crates/jolt-sumcheck/tests/external-consumer/Cargo.lock index 8040d45eb7..3b8e178a55 100644 --- a/crates/jolt-sumcheck/tests/external-consumer/Cargo.lock +++ b/crates/jolt-sumcheck/tests/external-consumer/Cargo.lock @@ -8,6 +8,21 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "cc" version = "1.4.5" @@ -18,12 +33,68 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "find-msvc-tools" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasm-bindgen", +] + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + [[package]] name = "jolt-field" version = "0.1.0" @@ -32,6 +103,7 @@ dependencies = [ "num-traits", "rand_core", "serde", + "spongefish", "thiserror", ] @@ -68,15 +140,15 @@ dependencies = [ "jolt-poly", "jolt-sumcheck", "jolt-transcript", - "num-traits", - "rand_core", ] [[package]] name = "jolt-transcript" version = "0.1.0" dependencies = [ + "getrandom", "jolt-field", + "spongefish", ] [[package]] @@ -86,6 +158,32 @@ dependencies = [ "num-traits", ] +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" +dependencies = [ + "cfg-if", + "cpufeatures", +] + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + [[package]] name = "num-traits" version = "0.2.19" @@ -95,6 +193,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -119,12 +223,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "serde" version = "1.0.229" @@ -155,12 +271,43 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest", + "keccak", + "sponge-cursor", +] + [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + +[[package]] +name = "spongefish" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62560b5029264a655fc461a0ecadc4d24862799190897d3225b3272b7a3f13b7" +dependencies = [ + "digest", + "getrandom", + "keccak", + "shake", + "turboshake", + "zeroize", +] + [[package]] name = "syn" version = "2.0.119" @@ -231,8 +378,90 @@ version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +[[package]] +name = "turboshake" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f892c6904b0bd5a9241eac848347abcbbbd2b9f3892bad23ac3e6efab8d6f06a" +dependencies = [ + "digest", + "keccak", + "sponge-cursor", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.5", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] diff --git a/crates/jolt-sumcheck/tests/external-consumer/Cargo.toml b/crates/jolt-sumcheck/tests/external-consumer/Cargo.toml index e8c6cf5ec8..5d092b2d68 100644 --- a/crates/jolt-sumcheck/tests/external-consumer/Cargo.toml +++ b/crates/jolt-sumcheck/tests/external-consumer/Cargo.toml @@ -16,6 +16,7 @@ jolt-field = { path = "../../../jolt-field", default-features = false, features ] } jolt-poly = { path = "../../../jolt-poly", default-features = false } jolt-sumcheck = { path = "../..", default-features = false } -jolt-transcript = { path = "../../../jolt-transcript", default-features = false } -num-traits = { version = "0.2.19", default-features = false } -rand_core = { version = "0.6.4", default-features = false } +jolt-transcript = { path = "../../../jolt-transcript", default-features = false, features = [ + "solinas", + "transcript-keccak", +] } diff --git a/crates/jolt-sumcheck/tests/external-consumer/tests/external_field.rs b/crates/jolt-sumcheck/tests/external-consumer/tests/external_field.rs deleted file mode 100644 index 77e06bf89e..0000000000 --- a/crates/jolt-sumcheck/tests/external-consumer/tests/external_field.rs +++ /dev/null @@ -1,312 +0,0 @@ -use std::{ - fmt::{Display, Formatter, Result as FmtResult}, - iter::{Product, Sum}, - ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, -}; - -use jolt_field::{AdditiveGroup, Field, Prime64Offset59, Ring}; -use jolt_poly::UnivariatePoly; -use jolt_sumcheck::{ - prove_batch, BatchMember, BatchPrelude, BooleanHypercube, ClearSumcheckRecorder, ProveRounds, - SequentialRounds, SumcheckClaim, SumcheckError, SumcheckRecorder, SumcheckVerifier, - OPENING_CLAIM_TRANSCRIPT_LABEL, SUMCHECK_CLAIM_TRANSCRIPT_LABEL, - SUMCHECK_ROUND_TRANSCRIPT_LABEL, -}; -use jolt_transcript::{AppendToTranscript, ChallengeSetSize, Transcript}; -use num_traits::{One, Zero}; -use rand_core::RngCore; - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -struct ExternalField(Prime64Offset59); - -impl Display for ExternalField { - fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult { - Display::fmt(&self.0, formatter) - } -} - -impl Zero for ExternalField { - fn zero() -> Self { - Self(Prime64Offset59::zero()) - } - - fn is_zero(&self) -> bool { - self.0.is_zero() - } -} - -impl One for ExternalField { - fn one() -> Self { - Self(Prime64Offset59::one()) - } - - fn is_one(&self) -> bool { - self.0.is_one() - } -} - -impl Add for ExternalField { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl Add<&Self> for ExternalField { - type Output = Self; - - fn add(self, rhs: &Self) -> Self::Output { - self + *rhs - } -} - -impl AddAssign for ExternalField { - fn add_assign(&mut self, rhs: Self) { - *self = *self + rhs; - } -} - -impl Sub for ExternalField { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl Sub<&Self> for ExternalField { - type Output = Self; - - fn sub(self, rhs: &Self) -> Self::Output { - self - *rhs - } -} - -impl SubAssign for ExternalField { - fn sub_assign(&mut self, rhs: Self) { - *self = *self - rhs; - } -} - -impl Neg for ExternalField { - type Output = Self; - - fn neg(self) -> Self::Output { - Self(-self.0) - } -} - -impl Mul for ExternalField { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - Self(self.0 * rhs.0) - } -} - -impl Mul<&Self> for ExternalField { - type Output = Self; - - fn mul(self, rhs: &Self) -> Self::Output { - self * *rhs - } -} - -impl MulAssign for ExternalField { - fn mul_assign(&mut self, rhs: Self) { - *self = *self * rhs; - } -} - -impl Sum for ExternalField { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |sum, value| sum + value) - } -} - -impl<'a> Sum<&'a ExternalField> for ExternalField { - fn sum>(iter: I) -> Self { - iter.copied().sum() - } -} - -impl Product for ExternalField { - fn product>(iter: I) -> Self { - iter.fold(Self::one(), |product, value| product * value) - } -} - -impl<'a> Product<&'a ExternalField> for ExternalField { - fn product>(iter: I) -> Self { - iter.copied().product() - } -} - -impl AdditiveGroup for ExternalField {} - -impl Ring for ExternalField { - fn from_u64(value: u64) -> Self { - Self(Prime64Offset59::from_u64(value)) - } - - fn from_i64(value: i64) -> Self { - Self(Prime64Offset59::from_i64(value)) - } - - fn from_u128(value: u128) -> Self { - Self(Prime64Offset59::from_u128(value)) - } - - fn from_i128(value: i128) -> Self { - Self(Prime64Offset59::from_i128(value)) - } -} - -impl Field for ExternalField { - fn inverse(&self) -> Option { - self.0.inverse().map(Self) - } - - fn random(rng: &mut R) -> Self { - Self(Prime64Offset59::random(rng)) - } -} - -impl AppendToTranscript for ExternalField { - fn append_to_transcript(&self, transcript: &mut T) { - self.0.append_to_transcript(transcript); - } -} - -#[derive(Clone, Default)] -struct ExternalTranscript { - state: [u8; 32], - cursor: usize, -} - -impl Transcript for ExternalTranscript { - type Challenge = ExternalField; - // Scripted challenges: no proof of work. - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); - - fn new(label: &'static [u8]) -> Self { - let mut transcript = Self::default(); - transcript.append_bytes(label); - transcript - } - - fn append_bytes(&mut self, bytes: &[u8]) { - for byte in bytes { - let index = self.cursor % self.state.len(); - self.state[index] = self.state[index] - .wrapping_mul(31) - .wrapping_add(*byte) - .wrapping_add(1); - self.cursor += 1; - } - } - - fn challenge(&mut self) -> ExternalField { - self.append_bytes(b"challenge"); - ExternalField::from_u64(u64::from_le_bytes(self.state[..8].try_into().unwrap())) - } - - fn challenge_bytes(&mut self, out: &mut [u8]) { - out.fill(0); - } - - fn state(&self) -> [u8; 32] { - self.state - } -} - -struct LinearRound; - -impl ProveRounds for LinearRound { - fn num_rounds(&self) -> usize { - 1 - } - - fn prove_round( - &mut self, - bind: Option, - round: usize, - previous_claim: ExternalField, - ) -> Result, SumcheckError> { - assert!(bind.is_none()); - assert_eq!(round, 0); - let polynomial = - field_only_polynomial([ExternalField::from_u64(3), ExternalField::from_u64(2)]); - assert_eq!( - polynomial.evaluate(ExternalField::zero()) + polynomial.evaluate(ExternalField::one()), - previous_claim - ); - Ok(polynomial) - } - - fn finish_rounds(&mut self, _bind: ExternalField) -> Result<(), SumcheckError> { - Ok(()) - } -} - -fn field_only_polynomial(coefficients: [F; 2]) -> UnivariatePoly { - let polynomial = UnivariatePoly::new(coefficients.to_vec()); - let _compressed = polynomial.compress(); - polynomial -} - -#[test] -fn external_field_runs_stock_clear_prover_and_verifier() { - let input_claim = ExternalField::from_u64(8); - let mut prover_transcript = ExternalTranscript::new(b"external-field"); - let mut recorder = ClearSumcheckRecorder::::new(); - recorder.absorb_input_claims(&[input_claim], &mut prover_transcript); - let coefficient = prover_transcript.challenge_scalar(); - let prelude = BatchPrelude::try_new( - vec![BatchMember { - input_claim, - coefficient, - rounds: 1, - offset: 0, - }], - 1, - 1, - ) - .unwrap(); - let mut round = LinearRound; - let mut members: [&mut dyn ProveRounds; 1] = [&mut round]; - let proved = prove_batch( - &prelude, - &mut members, - &mut SequentialRounds, - &mut recorder, - &mut prover_transcript, - ) - .unwrap(); - let recorded = recorder - .finish(&proved.member_claims, &mut prover_transcript) - .unwrap(); - let proof = recorded.proof; - - let mut verifier_transcript = ExternalTranscript::new(b"external-field"); - verifier_transcript.append_labeled(SUMCHECK_CLAIM_TRANSCRIPT_LABEL, &input_claim); - let verifier_coefficient = verifier_transcript.challenge_scalar(); - let reduced = SumcheckVerifier::verify_compressed( - &SumcheckClaim::new(1, 1, verifier_coefficient * input_claim), - &proof, - BooleanHypercube, - SUMCHECK_ROUND_TRANSCRIPT_LABEL, - &mut verifier_transcript, - ) - .unwrap(); - for claim in &proved.member_claims { - verifier_transcript.append_labeled(OPENING_CLAIM_TRANSCRIPT_LABEL, claim); - } - - let point = reduced.point.as_slice()[0]; - let expected = ExternalField::from_u64(3) + ExternalField::from_u64(2) * point; - assert_eq!(proved.member_claims, vec![expected]); - assert_eq!(reduced.value, verifier_coefficient * expected); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); -} diff --git a/crates/jolt-sumcheck/tests/external-consumer/tests/roundtrip.rs b/crates/jolt-sumcheck/tests/external-consumer/tests/roundtrip.rs index 2b1be08ff0..b4b33f139e 100644 --- a/crates/jolt-sumcheck/tests/external-consumer/tests/roundtrip.rs +++ b/crates/jolt-sumcheck/tests/external-consumer/tests/roundtrip.rs @@ -1,7 +1,7 @@ mod external_example { include!(concat!( env!("CARGO_MANIFEST_DIR"), - "/../../examples/clear_custom_transcript.rs" + "/../../examples/clear_sumcheck.rs" )); #[test] diff --git a/crates/jolt-sumcheck/tests/mersenne61_compat.rs b/crates/jolt-sumcheck/tests/mersenne61_compat.rs deleted file mode 100644 index 303fcd90c5..0000000000 --- a/crates/jolt-sumcheck/tests/mersenne61_compat.rs +++ /dev/null @@ -1,373 +0,0 @@ -//! Compatibility test for non-BN254 sumcheck verifier plumbing. -//! -//! `Mersenne61` is intentionally small and exists here only to prove that the -//! transcript and verifier APIs no longer depend on BN254-specific helper -//! surface. It is not a production proving field. Real sumcheck soundness with -//! this base field would require an adequately large extension field. - -use std::{ - fmt::{Debug, Display}, - hash::Hash, - iter::{Product, Sum}, - ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, -}; - -use jolt_field::{ - AdditiveGroup, CanonicalBytes, CanonicalEncoding, Field, NaiveAccumulator, Ring, - WithAccumulator, -}; -use jolt_sumcheck::{ - BooleanHypercube, ClearRound, RoundMessage, SumcheckClaim, SumcheckError, SumcheckVerifier, -}; -use jolt_transcript::{AppendToTranscript, Blake2bTranscript, KeccakTranscript, Transcript}; -use num_traits::{One, Zero}; - -const MODULUS: u64 = (1u64 << 61) - 1; - -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)] -struct Mersenne61(u64); - -impl Mersenne61 { - fn reduce_u128(x: u128) -> Self { - let p = MODULUS as u128; - let mut y = (x & p) + (x >> 61); - y = (y & p) + (y >> 61); - if y >= p { - y -= p; - } - Self(y as u64) - } - - fn pow(self, mut exp: u64) -> Self { - let mut base = self; - let mut acc = Self::one(); - while exp > 0 { - if exp & 1 == 1 { - acc *= base; - } - base *= base; - exp >>= 1; - } - acc - } -} - -impl Debug for Mersenne61 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&self.0, f) - } -} - -impl Display for Mersenne61 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl Zero for Mersenne61 { - fn zero() -> Self { - Self(0) - } - - fn is_zero(&self) -> bool { - self.0 == 0 - } -} - -impl One for Mersenne61 { - fn one() -> Self { - Self(1) - } - - fn is_one(&self) -> bool { - self.0 == 1 - } -} - -impl Add for Mersenne61 { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - let mut sum = self.0 + rhs.0; - if sum >= MODULUS { - sum -= MODULUS; - } - Self(sum) - } -} - -impl Add<&Self> for Mersenne61 { - type Output = Self; - - fn add(self, rhs: &Self) -> Self::Output { - self + *rhs - } -} - -impl AddAssign for Mersenne61 { - fn add_assign(&mut self, rhs: Self) { - *self = *self + rhs; - } -} - -impl Sub for Mersenne61 { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - if self.0 >= rhs.0 { - Self(self.0 - rhs.0) - } else { - Self(MODULUS - (rhs.0 - self.0)) - } - } -} - -impl Sub<&Self> for Mersenne61 { - type Output = Self; - - fn sub(self, rhs: &Self) -> Self::Output { - self - *rhs - } -} - -impl SubAssign for Mersenne61 { - fn sub_assign(&mut self, rhs: Self) { - *self = *self - rhs; - } -} - -impl Neg for Mersenne61 { - type Output = Self; - - fn neg(self) -> Self::Output { - if self.is_zero() { - self - } else { - Self(MODULUS - self.0) - } - } -} - -impl Mul for Mersenne61 { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - Self::reduce_u128(self.0 as u128 * rhs.0 as u128) - } -} - -impl Mul<&Self> for Mersenne61 { - type Output = Self; - - fn mul(self, rhs: &Self) -> Self::Output { - self * *rhs - } -} - -impl MulAssign for Mersenne61 { - fn mul_assign(&mut self, rhs: Self) { - *self = *self * rhs; - } -} - -impl Sum for Mersenne61 { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |acc, x| acc + x) - } -} - -impl<'a> Sum<&'a Mersenne61> for Mersenne61 { - fn sum>(iter: I) -> Self { - iter.copied().sum() - } -} - -impl Product for Mersenne61 { - fn product>(iter: I) -> Self { - iter.fold(Self::one(), |acc, x| acc * x) - } -} - -impl<'a> Product<&'a Mersenne61> for Mersenne61 { - fn product>(iter: I) -> Self { - iter.copied().product() - } -} - -impl AdditiveGroup for Mersenne61 {} - -impl Field for Mersenne61 { - fn inverse(&self) -> Option { - if self.is_zero() { - None - } else { - Some(self.pow(MODULUS - 2)) - } - } - - fn random(rng: &mut R) -> Self { - Self::from_u64(rng.next_u64()) - } -} - -impl Ring for Mersenne61 { - fn from_u64(v: u64) -> Self { - Self::reduce_u128(v as u128) - } - - fn from_i64(v: i64) -> Self { - if v >= 0 { - Self::from_u64(v as u64) - } else { - -Self::from_u64(v.unsigned_abs()) - } - } - - fn from_u128(v: u128) -> Self { - Self::reduce_u128(v) - } - - fn from_i128(v: i128) -> Self { - if v >= 0 { - Self::from_u128(v as u128) - } else { - -Self::from_u128(v.unsigned_abs()) - } - } -} - -impl CanonicalBytes for Mersenne61 { - const NUM_BYTES: usize = 8; - - fn to_bytes_le(&self, out: &mut [u8]) { - assert_eq!(out.len(), 8); - out.copy_from_slice(&self.0.to_le_bytes()); - } -} - -impl CanonicalEncoding for Mersenne61 { - const MODULUS_BITS: u32 = 61; - const CHALLENGE_SET_LIMBS: [u64; 4] = [1 << 60, 0, 0, 0]; - - /// 16 bytes reduced modulo `p = 2^61 − 1` give each value probability at - /// most `1/p + 2^-128 ≤ 2^-60`. - fn challenge_width(_draw: u64) -> usize { - 16 - } - - fn from_bytes_le_reduced(bytes: &[u8]) -> Self { - let mut buf = [0u8; 16]; - let len = bytes.len().min(16); - buf[..len].copy_from_slice(&bytes[..len]); - Self::from_u128(u128::from_le_bytes(buf)) - } - - fn from_bytes_le_checked(bytes: &[u8]) -> Option { - let arr: [u8; 8] = bytes.try_into().ok()?; - Self::from_u128_checked(u64::from_le_bytes(arr) as u128) - } - - fn to_u128_checked(&self) -> Option { - Some(self.0 as u128) - } - - fn to_u64_checked(&self) -> Option { - Some(self.0) - } - - fn from_u128_checked(v: u128) -> Option { - (v < MODULUS as u128).then_some(Self(v as u64)) - } - - fn from_u128_reduced(v: u128) -> Self { - Self::reduce_u128(v) - } - - fn num_bits(&self) -> u32 { - u64::BITS - self.0.leading_zeros() - } -} - -impl WithAccumulator for Mersenne61 { - type Accumulator = NaiveAccumulator; - type SmallScalarAccumulator = NaiveAccumulator; - type SignedProductAccumulator = NaiveAccumulator; -} - -#[derive(Clone, Debug)] -struct LinearRound { - coeffs: [Mersenne61; 2], -} - -impl RoundMessage for LinearRound { - fn degree(&self) -> usize { - 1 - } - - fn append_to_transcript(&self, transcript: &mut T) { - self.coeffs[0].append_to_transcript(transcript); - self.coeffs[1].append_to_transcript(transcript); - } -} - -impl ClearRound for LinearRound { - fn evaluate(&self, challenge: Mersenne61) -> Mersenne61 { - self.coeffs[0] + self.coeffs[1] * challenge - } - - fn coefficient_linear_combination(&self, coefficients: &[Mersenne61]) -> Mersenne61 { - self.coeffs - .iter() - .zip(coefficients) - .map(|(&coefficient, &scale)| coefficient * scale) - .sum() - } -} - -fn build_rounds() -> (SumcheckClaim, Vec) { - let mut running_sum = Mersenne61::from_u64(10); - let mut rounds = Vec::new(); - - for _ in 0..4 { - let c0 = running_sum * Mersenne61::from_u64(3); - let c1 = running_sum - c0 - c0; - let round = LinearRound { coeffs: [c0, c1] }; - running_sum = round.evaluate(Mersenne61::from_u64(5)); - rounds.push(round); - } - - (SumcheckClaim::new(4, 1, Mersenne61::from_u64(10)), rounds) -} - -#[test] -fn hash_transcripts_accept_mersenne61_without_bn254_field_surface() { - let mut blake = Blake2bTranscript::::new(b"compat"); - let mut keccak = KeccakTranscript::::new(b"compat"); - Mersenne61::from_u64(42).append_to_transcript(&mut blake); - Mersenne61::from_u64(42).append_to_transcript(&mut keccak); - - let _: Mersenne61 = blake.challenge(); - let _: Mersenne61 = keccak.challenge(); -} - -/// 60-bit challenges cannot reach the 128-bit Fiat-Shamir target with any -/// supported proof of work, so the verifier refuses the proof before touching -/// the transcript rather than accepting it at reduced security. -#[test] -fn sumcheck_verifier_refuses_mersenne61_challenges_below_target() { - let (claim, rounds) = build_rounds(); - let mut verifier_transcript = Blake2bTranscript::::new(b"mersenne61"); - let before = verifier_transcript.state(); - let result = SumcheckVerifier::verify( - &claim, - &rounds, - &[], - BooleanHypercube, - &mut verifier_transcript, - ); - assert!(matches!( - result, - Err(SumcheckError::PowDifficulty { degree: 1 }) - )); - assert_eq!(verifier_transcript.state(), before); -} diff --git a/crates/jolt-sumcheck/tests/roundtrip.rs b/crates/jolt-sumcheck/tests/roundtrip.rs index 225d2c806e..e0bd7adee3 100644 --- a/crates/jolt-sumcheck/tests/roundtrip.rs +++ b/crates/jolt-sumcheck/tests/roundtrip.rs @@ -8,15 +8,17 @@ use jolt_sumcheck::round_proof::{CompressedLabeledRoundPoly, LabeledRoundPoly, R use jolt_sumcheck::{ round_pow_bits, BooleanHypercube, SumcheckVerifier, SUMCHECK_ROUND_TRANSCRIPT_LABEL, }; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{ + prover_transcript, Blake2b512, FieldTranscript, ProverState, ProverTranscript, +}; type F = Fr; /// Grinds one round's proof of work the way the verifier checks it: after the /// round message is absorbed, at the difficulty for the claim's `degree`. -fn grind_round(degree: usize, transcript: &mut Blake2bTranscript, nonces: &mut Vec) { - let bits = round_pow_bits::>(degree).unwrap(); - nonces.extend(transcript.grind(bits).unwrap()); +fn grind_round(degree: usize, transcript: &mut ProverState, nonces: &mut Vec) { + let bits = round_pow_bits::>(degree).unwrap(); + nonces.extend(transcript.public_grind(bits).unwrap()); } /// Prove a sumcheck for the product of `polys` multilinear polynomials. @@ -29,7 +31,7 @@ fn grind_round(degree: usize, transcript: &mut Blake2bTranscript, nonces: &mu fn prove_product( polys: &[Vec], num_vars: usize, - transcript: &mut Blake2bTranscript, + transcript: &mut ProverState, ) -> (ClearSumcheckProof, F) { let degree = polys.len(); let n = 1 << num_vars; @@ -74,7 +76,7 @@ fn prove_product( as RoundMessage>::append_to_transcript(&round_poly, transcript); grind_round(degree, transcript, &mut pow_nonces); - let r: F = transcript.challenge(); + let r: F = transcript.challenge().unwrap(); round_polys.push(round_poly); // Bind all polynomials (HighToLow) @@ -104,7 +106,7 @@ fn degree3_final_eval_correct() { let g_evals: Vec = (0..n).map(|i| F::from_u64((i * 5 + 2) as u64)).collect(); let h_evals: Vec = (0..n).map(|i| F::from_u64((i + 7) as u64)).collect(); - let mut pt = Blake2bTranscript::new(b"sumcheck-roundtrip"); + let mut pt = prover_transcript::(b"sumcheck-roundtrip", b""); let (proof, claimed_sum) = prove_product( &[f_evals.clone(), g_evals.clone(), h_evals.clone()], num_vars, @@ -117,7 +119,7 @@ fn degree3_final_eval_correct() { claimed_sum, }; - let mut vt = Blake2bTranscript::new(b"sumcheck-roundtrip"); + let mut vt = prover_transcript::(b"sumcheck-roundtrip", b""); let EvaluationClaim { point: challenges, value: final_eval, @@ -149,7 +151,7 @@ fn compressed_round_verifier_roundtrip() { let f: Vec = (0..n).map(|i| F::from_u64(i as u64 + 1)).collect(); let g: Vec = (0..n).map(|i| F::from_u64(i as u64 * 2 + 3)).collect(); - let mut pt = Blake2bTranscript::new(b"sumcheck-roundtrip"); + let mut pt = prover_transcript::(b"sumcheck-roundtrip", b""); let mut bufs = vec![f.clone(), g.clone()]; let claimed_sum: F = (0..n).map(|i| bufs[0][i] * bufs[1][i]).sum(); let mut round_polys = Vec::with_capacity(num_vars); @@ -188,7 +190,7 @@ fn compressed_round_verifier_roundtrip() { ); grind_round(degree, &mut pt, &mut pow_nonces); - let r: F = pt.challenge(); + let r: F = pt.challenge().unwrap(); round_polys.push(round_poly); for buf in &mut bufs { @@ -215,7 +217,7 @@ fn compressed_round_verifier_roundtrip() { .map(|p| CompressedLabeledRoundPoly::new(p, label)) .collect(); - let mut vt = Blake2bTranscript::new(b"sumcheck-roundtrip"); + let mut vt = prover_transcript::(b"sumcheck-roundtrip", b""); let result = SumcheckVerifier::verify( &claim, &wrapped, @@ -241,7 +243,7 @@ fn labeled_round_verifier_roundtrip() { let label = SUMCHECK_ROUND_TRANSCRIPT_LABEL; - let mut pt = Blake2bTranscript::new(b"sumcheck-roundtrip"); + let mut pt = prover_transcript::(b"sumcheck-roundtrip", b""); let degree = 2; let mut bufs = vec![f.clone(), g.clone()]; let claimed_sum: F = (0..n).map(|i| bufs[0][i] * bufs[1][i]).sum(); @@ -278,7 +280,7 @@ fn labeled_round_verifier_roundtrip() { as RoundMessage>::append_to_transcript(&labeled, &mut pt); grind_round(degree, &mut pt, &mut pow_nonces); - let r: F = pt.challenge(); + let r: F = pt.challenge().unwrap(); round_polys.push(round_poly); for buf in &mut bufs { @@ -306,7 +308,7 @@ fn labeled_round_verifier_roundtrip() { .map(|p| LabeledRoundPoly::new(p, label)) .collect(); - let mut vt = Blake2bTranscript::new(b"sumcheck-roundtrip"); + let mut vt = prover_transcript::(b"sumcheck-roundtrip", b""); let result = SumcheckVerifier::verify( &claim, &wrapped, diff --git a/crates/jolt-sumcheck/tests/soundness.rs b/crates/jolt-sumcheck/tests/soundness.rs index 917c9c6264..7c69fcdcf3 100644 --- a/crates/jolt-sumcheck/tests/soundness.rs +++ b/crates/jolt-sumcheck/tests/soundness.rs @@ -9,7 +9,10 @@ use jolt_sumcheck::round_proof::RoundMessage; use jolt_sumcheck::{ round_pow_bits, BooleanHypercube, SumcheckVerifier, SUMCHECK_ROUND_TRANSCRIPT_LABEL, }; -use jolt_transcript::{AppendToTranscript, Blake2bTranscript, LabelWithCount, Transcript}; +use jolt_transcript::{ + prover_transcript, Blake2b512, FieldTranscript, LabelWithCount, ProverState, ProverTranscript, + Transcript, +}; type F = Fr; @@ -29,18 +32,18 @@ impl From> for OracleCheckError { } } -fn new_transcript() -> Blake2bTranscript { - Blake2bTranscript::new(b"soundness-test") +fn new_transcript() -> ProverState { + prover_transcript::(b"soundness-test", b"") } fn pow_bits(degree: usize) -> u32 { - round_pow_bits::>(degree).unwrap() + round_pow_bits::>(degree).unwrap() } fn honest_prove( evals: &[F], num_vars: usize, - transcript: &mut Blake2bTranscript, + transcript: &mut ProverState, ) -> ClearSumcheckProof { let mut buf = evals.to_vec(); let mut round_polys = Vec::with_capacity(num_vars); @@ -56,8 +59,8 @@ fn honest_prove( } let round_poly = UnivariatePoly::new(vec![eval_0, eval_1 - eval_0]); as RoundMessage>::append_to_transcript(&round_poly, transcript); - pow_nonces.extend(transcript.grind(pow_bits(1)).unwrap()); - let r: F = transcript.challenge(); + pow_nonces.extend(transcript.public_grind(pow_bits(1)).unwrap()); + let r: F = transcript.challenge().unwrap(); round_polys.push(round_poly); for i in 0..half { buf[i] = buf[i] + r * (buf[i + half] - buf[i]); @@ -250,7 +253,7 @@ fn verifier_transcript_desync_rejected() { }; let mut vt = new_transcript(); - F::from_u64(0xdead).append_to_transcript(&mut vt); + vt.append(&F::from_u64(0xdead)); let result = SumcheckVerifier::verify( &claim, @@ -290,14 +293,14 @@ fn num_vars_zero_accepts_any_claimed_sum() { /// Absorbs one compressed round exactly the way `verify_compressed` replays /// it: `LabelWithCount(label, degree)` then the stored coefficients `[c0, c2]`. -fn absorb_compressed_round(round: &CompressedPoly, transcript: &mut Blake2bTranscript) { +fn absorb_compressed_round(round: &CompressedPoly, transcript: &mut ProverState) { let coeffs = round.coeffs_except_linear_term(); transcript.append(&LabelWithCount( SUMCHECK_ROUND_TRANSCRIPT_LABEL, coeffs.len() as u64, )); for coeff in coeffs { - coeff.append_to_transcript(transcript); + transcript.append(coeff); } } @@ -308,8 +311,8 @@ fn grind_compressed_nonces(rounds: &[CompressedPoly], degree: usize) -> Vec, ) -> CompressedSumcheckProof { let mut g = g_evals.to_vec(); let mut h = h_evals.to_vec(); @@ -344,8 +347,8 @@ fn honest_prove_product_compressed( let compressed = UnivariatePoly::new(vec![c0, c1, c2]).compress(); absorb_compressed_round(&compressed, transcript); - pow_nonces.extend(transcript.grind(pow_bits(2)).unwrap()); - let r: F = transcript.challenge(); + pow_nonces.extend(transcript.public_grind(pow_bits(2)).unwrap()); + let r: F = transcript.challenge().unwrap(); for i in 0..half { g[i] = g[i] + r * (g[i + half] - g[i]); diff --git a/crates/jolt-transcript/Cargo.toml b/crates/jolt-transcript/Cargo.toml index 10fc88f5ff..a48b183481 100644 --- a/crates/jolt-transcript/Cargo.toml +++ b/crates/jolt-transcript/Cargo.toml @@ -28,7 +28,7 @@ bn254 = ["dep:ark-bn254", "dep:ark-ff", "jolt-field/bn254"] transcript-blake2b = ["dep:blake2"] transcript-keccak = ["spongefish/keccak"] transcript-poseidon = ["bn254", "dep:light-poseidon"] -# `UnpricedTranscript`, a wrapper that declares no proof of work. Tests only. +# `ScriptedTranscript`: scripted challenges, an event log, no proof of work. Tests only. test-utils = [] [dependencies] diff --git a/crates/jolt-transcript/src/lib.rs b/crates/jolt-transcript/src/lib.rs index a3047be05a..6d717599cf 100644 --- a/crates/jolt-transcript/src/lib.rs +++ b/crates/jolt-transcript/src/lib.rs @@ -38,10 +38,10 @@ mod grind_bits; mod poseidon; mod pow; mod prover; +#[cfg(feature = "test-utils")] +mod scripted; mod setup; mod transcript; -#[cfg(feature = "test-utils")] -mod unpriced; mod verifier; pub use challenge::{ChallengeCodec, UniformBytes, MAX_CHALLENGE_BYTES}; @@ -54,11 +54,11 @@ pub use poseidon::PoseidonSponge; #[cfg(feature = "prover-grinding")] pub use pow::{MessageSearch, Probe}; pub use prover::ProverTranscript; +#[cfg(feature = "test-utils")] +pub use scripted::{ScriptedTranscript, TranscriptEvent}; pub use setup::{prover_transcript, session_id, verifier_transcript, PROTOCOL_ID}; pub use spongefish::{Encoding, ProverState, VerificationError, VerifierState}; pub use transcript::{FieldTranscript, Transcript}; -#[cfg(feature = "test-utils")] -pub use unpriced::UnpricedTranscript; pub use verifier::VerifierTranscript; #[cfg(feature = "transcript-blake2b")] diff --git a/crates/jolt-transcript/src/scripted.rs b/crates/jolt-transcript/src/scripted.rs new file mode 100644 index 0000000000..71e010a556 --- /dev/null +++ b/crates/jolt-transcript/src/scripted.rs @@ -0,0 +1,143 @@ +//! A transcript wrapper for tests: scripted challenges, an event log, and no +//! proof of work. + +use std::collections::VecDeque; + +#[cfg(feature = "prover-grinding")] +use crate::pow::MessageSearch; +use crate::transcript::sealed::Sealed; +use crate::{ChallengeSetSize, FieldTranscript, ProverTranscript}; +use spongefish::{Encoding, VerificationError}; + +/// One sponge operation a [`ScriptedTranscript`] saw, in order. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TranscriptEvent { + /// A public or prover message, as its encoded bytes. + Absorb(Vec), + /// A squeeze of this many bytes outside a challenge draw, such as a + /// proof-of-work check. + Squeeze(usize), + /// One challenge draw and the values it returned. + Challenges(Vec), +} + +/// `T` with its challenge set declared as `2^256` and its challenges +/// optionally replaced by a script. +/// +/// Every operation runs on `T` and is logged. A draw of `len` challenges draws +/// them from `T`, keeping its sponge in step, then returns the next scripted +/// values in their place while the script lasts. The declared challenge set +/// makes every difficulty from +/// [`grind_bits_for_loss`](crate::grind_bits_for_loss) 0, so proofs carry no +/// nonces. +/// +/// Tests use it to choose challenges, to compare absorb orders, and to reach +/// checks that a hand-built proof would otherwise fail at its first +/// proof-of-work step. It overstates `T`'s challenge set and lets the caller +/// pick challenges, so it gives no soundness and must never back a real proof; +/// the `test-utils` feature that exports it is for dev-dependencies. +pub struct ScriptedTranscript { + inner: T, + script: VecDeque, + events: Vec>, +} + +impl ScriptedTranscript { + /// `inner` with no script: its own challenges, unpriced. + pub fn new(inner: T) -> Self { + Self::with_script(inner, Vec::new()) + } + + /// `inner` whose next challenges are `script`, in order. + pub fn with_script(inner: T, script: Vec) -> Self { + Self { + inner, + script: script.into(), + events: Vec::new(), + } + } + + /// The operations so far, in order. + pub fn events(&self) -> &[TranscriptEvent] { + &self.events + } + + /// The bytes of each absorbed message so far, in order. + pub fn absorbed(&self) -> Vec<&[u8]> { + self.events + .iter() + .filter_map(|event| match event { + TranscriptEvent::Absorb(bytes) => Some(bytes.as_slice()), + TranscriptEvent::Squeeze(_) | TranscriptEvent::Challenges(_) => None, + }) + .collect() + } + + /// The wrapped transcript. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl Sealed for ScriptedTranscript { + fn absorb(&mut self, message: &M) { + self.events + .push(TranscriptEvent::Absorb(message.encode().as_ref().to_vec())); + self.inner.absorb(message); + } + + fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[u8]) -> U) -> U { + self.events.push(TranscriptEvent::Squeeze(len)); + self.inner.squeeze(len, decode) + } + + fn require(&mut self, ok: bool) -> Result<(), VerificationError> { + self.inner.require(ok) + } +} + +impl, F: Clone> FieldTranscript for ScriptedTranscript { + const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); + + fn challenge_vector(&mut self, len: usize) -> Result, VerificationError> { + let mut challenges = self.inner.challenge_vector(len)?; + for challenge in &mut challenges { + match self.script.pop_front() { + Some(scripted) => *challenge = scripted, + None => break, + } + } + self.events + .push(TranscriptEvent::Challenges(challenges.clone())); + Ok(challenges) + } +} + +impl ProverTranscript for ScriptedTranscript { + type Sponge = T::Sponge; + + fn prover_message(&mut self, msg: &M) { + self.events + .push(TranscriptEvent::Absorb(msg.encode().as_ref().to_vec())); + self.inner.prover_message(msg); + } + + fn narg_string(&self) -> &[u8] { + self.inner.narg_string() + } + + #[cfg(feature = "prover-grinding")] + fn message_search(&self) -> MessageSearch { + self.inner.message_search() + } + + #[cfg(feature = "prover-grinding")] + fn grind(&mut self, bits: u32) -> Result<(), VerificationError> { + self.inner.grind(bits) + } + + #[cfg(feature = "prover-grinding")] + fn public_grind(&mut self, bits: u32) -> Result, VerificationError> { + self.inner.public_grind(bits) + } +} diff --git a/crates/jolt-transcript/src/unpriced.rs b/crates/jolt-transcript/src/unpriced.rs deleted file mode 100644 index cb7506b312..0000000000 --- a/crates/jolt-transcript/src/unpriced.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! A transcript wrapper that declares no proof of work, for tests only. - -#[cfg(feature = "prover-grinding")] -use crate::pow::MessageSearch; -use crate::transcript::{draw_challenges, sealed::Sealed}; -use crate::{ChallengeCodec, ChallengeSetSize, FieldTranscript, ProverTranscript}; -use spongefish::{Encoding, VerificationError}; - -/// `T` with its challenge set declared as `2^256`, so every difficulty from -/// [`grind_bits_for_loss`](crate::grind_bits_for_loss) is 0: proofs carry no -/// nonces and the transcript absorbs and squeezes exactly what `T` does -/// without proof of work. -/// -/// Tests use it to reach checks that a hand-built proof would otherwise fail -/// at its first proof-of-work step. It overstates `T`'s challenge set, so it -/// gives no soundness and must never back a real proof; the `test-utils` -/// feature that exports it is for dev-dependencies. -pub struct UnpricedTranscript(pub T); - -impl Sealed for UnpricedTranscript { - fn absorb(&mut self, message: &M) { - self.0.absorb(message); - } - - fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[u8]) -> U) -> U { - self.0.squeeze(len, decode) - } - - fn require(&mut self, ok: bool) -> Result<(), VerificationError> { - self.0.require(ok) - } -} - -impl, F: ChallengeCodec> FieldTranscript for UnpricedTranscript { - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); - - fn challenge_vector(&mut self, len: usize) -> Result, VerificationError> { - draw_challenges(self, len) - } -} - -impl ProverTranscript for UnpricedTranscript { - type Sponge = T::Sponge; - - fn prover_message(&mut self, msg: &M) { - self.0.prover_message(msg); - } - - fn narg_string(&self) -> &[u8] { - self.0.narg_string() - } - - #[cfg(feature = "prover-grinding")] - fn message_search(&self) -> MessageSearch { - self.0.message_search() - } - - #[cfg(feature = "prover-grinding")] - fn grind(&mut self, bits: u32) -> Result<(), VerificationError> { - self.0.grind(bits) - } - - #[cfg(feature = "prover-grinding")] - fn public_grind(&mut self, bits: u32) -> Result, VerificationError> { - self.0.public_grind(bits) - } -} diff --git a/scripts/check-external-sumcheck.sh b/scripts/check-external-sumcheck.sh index 5f0ceebb9c..78052c086f 100755 --- a/scripts/check-external-sumcheck.sh +++ b/scripts/check-external-sumcheck.sh @@ -5,7 +5,9 @@ fixture="crates/jolt-sumcheck/tests/external-consumer/Cargo.toml" cargo check -p jolt-transcript --no-default-features --features bn254 --locked --offline -for forbidden in ark-bn254 ark-ec ark-ff getrandom jolt-crypto jolt-openings jolt-r1cs rayon spongefish; do +# The transcript is spongefish, which seeds the prover's private randomness +# from getrandom; both belong to the minimal graph. +for forbidden in ark-bn254 ark-ec ark-ff jolt-crypto jolt-openings jolt-r1cs rayon; do if cargo tree --manifest-path "$fixture" --edges normal,build --offline --prefix none | rg -q "^${forbidden} "; then echo "unexpected dependency in minimal sumcheck graph: ${forbidden}" >&2 exit 1 From 382bdc81962395c2dfbfd797d845ae263241b3d5 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Tue, 20 Oct 2026 22:58:08 -0400 Subject: [PATCH 03/16] refactor(pcs, blindfold): run openings, HyperKZG, Dory, Akita and BlindFold over the spongefish transcript --- crates/jolt-akita/Cargo.toml | 4 +- crates/jolt-akita/src/adapters.rs | 80 +++++++---- crates/jolt-akita/src/native_batching.rs | 29 ++-- crates/jolt-akita/src/scheme.rs | 109 +++++++------- crates/jolt-akita/tests/e2e.rs | 25 ++-- crates/jolt-akita/tests/external_catalog.rs | 10 +- crates/jolt-akita/tests/fold_paths.rs | 17 ++- crates/jolt-akita/tests/grouped_capacity.rs | 16 ++- crates/jolt-akita/tests/native_batching.rs | 64 +++++---- crates/jolt-akita/tests/one_hot_groups.rs | 17 ++- crates/jolt-akita/tests/pathologies.rs | 29 ++-- crates/jolt-akita/tests/prefix_packing.rs | 26 ++-- crates/jolt-akita/tests/runtime_chunking.rs | 10 +- crates/jolt-akita/tests/scheme_surface.rs | 59 ++++---- crates/jolt-blindfold/Cargo.toml | 3 +- crates/jolt-blindfold/src/error.rs | 20 +++ crates/jolt-blindfold/src/pow.rs | 46 +++--- crates/jolt-blindfold/src/protocol.rs | 18 +-- crates/jolt-blindfold/src/prove.rs | 86 +++++------ crates/jolt-blindfold/src/verify.rs | 96 ++++++------- .../tests/assignment_pipeline.rs | 32 ++--- crates/jolt-blindfold/tests/proof_pipeline.rs | 6 +- crates/jolt-blindfold/tests/r1cs_pipeline.rs | 8 +- crates/jolt-blindfold/tests/real_prover.rs | 6 +- .../jolt-blindfold/tests/sumcheck_pipeline.rs | 6 +- crates/jolt-blindfold/tests/support/mod.rs | 87 ++++++------ crates/jolt-claims-derive/src/lib.rs | 4 +- crates/jolt-claims-derive/src/tests.rs | 2 +- .../src/protocols/jolt/geometry/bytecode.rs | 2 +- .../bytecode/read_raf_address_phase.rs | 2 +- crates/jolt-dory/benches/akita_paths.rs | 10 +- crates/jolt-dory/benches/dory.rs | 23 +-- .../fuzz/fuzz_targets/verify_tampered.rs | 8 +- .../fuzz/fuzz_targets/zk_mode_confusion.rs | 14 +- crates/jolt-dory/fuzz/tests/generate_seeds.rs | 4 +- crates/jolt-dory/src/scheme.rs | 16 ++- crates/jolt-dory/src/streaming.rs | 26 ++-- crates/jolt-dory/src/transcript.rs | 134 ++++++++++++------ crates/jolt-dory/src/types.rs | 31 ++-- crates/jolt-dory/tests/commit_open_verify.rs | 118 ++++++++++----- crates/jolt-hyperkzg/Cargo.toml | 1 + crates/jolt-hyperkzg/src/kzg.rs | 29 ++-- crates/jolt-hyperkzg/src/scheme.rs | 14 +- crates/jolt-hyperkzg/src/types.rs | 18 ++- crates/jolt-hyperkzg/tests/openings.rs | 48 +++---- .../dory_homomorphic_batch_must_reject.rs | 46 +++--- crates/jolt-openings/src/claims.rs | 35 +++-- crates/jolt-openings/src/error.rs | 11 ++ crates/jolt-openings/src/prefix.rs | 6 +- crates/jolt-openings/src/schemes.rs | 66 +++++---- .../jolt-openings/tests/homomorphic_dory.rs | 36 +++-- .../tests/prefix_packed_layout.rs | 6 +- crates/jolt-openings/tests/schemes.rs | 10 +- crates/jolt-openings/tests/support/mock.rs | 27 ++-- 54 files changed, 906 insertions(+), 750 deletions(-) diff --git a/crates/jolt-akita/Cargo.toml b/crates/jolt-akita/Cargo.toml index 5812200f87..1769297909 100644 --- a/crates/jolt-akita/Cargo.toml +++ b/crates/jolt-akita/Cargo.toml @@ -37,7 +37,9 @@ jolt-openings = { path = "../jolt-openings", default-features = false, features "parallel", ] } jolt-poly.workspace = true -jolt-transcript = { path = "../jolt-transcript", default-features = false } +jolt-transcript = { path = "../jolt-transcript", default-features = false, features = [ + "solinas", +] } rayon.workspace = true serde = { workspace = true, features = ["derive"] } tracing.workspace = true diff --git a/crates/jolt-akita/src/adapters.rs b/crates/jolt-akita/src/adapters.rs index b95d62d2ce..5282878a2c 100644 --- a/crates/jolt-akita/src/adapters.rs +++ b/crates/jolt-akita/src/adapters.rs @@ -24,7 +24,9 @@ use akita_types::{ use jolt_field::{CanonicalBytes, Zero}; use jolt_openings::{OpeningsError, VerifierOpeningClaim}; use jolt_poly::{MultilinearPoly, OneHotIndexOrder, OneHotPolynomial, Polynomial}; -use jolt_transcript::{AppendToTranscript, Label, LabelWithCount, Transcript, U64Word}; +use jolt_transcript::{ + BytesMsg, Encoding, FieldTranscript, Label, LabelWithCount, Transcript, U64Word, +}; use rayon::{ThreadPool, ThreadPoolBuilder}; use serde::{Deserialize, Serialize}; @@ -712,12 +714,12 @@ pub(crate) fn append_batch_statement( point: &[AkitaField], ) { transcript.append(&Label(b"akita_batch_statement")); - commitment.append_to_transcript(transcript); + transcript.append(&commitment); transcript.append_values(b"akita_pcs_point", point); transcript.append(&LabelWithCount(b"akita_claims", statement.len() as u64)); for claim in statement { - claim.commitment.append_to_transcript(transcript); - claim.evaluation.value.append_to_transcript(transcript); + transcript.append(&claim.commitment); + transcript.append(&claim.evaluation.value); } } @@ -856,20 +858,33 @@ impl AkitaCommitment { } } -impl AppendToTranscript for AkitaCommitment { - fn append_to_transcript(&self, transcript: &mut T) { - transcript.append(&Label(b"akita_commitment")); - transcript.append_bytes(self.backend_flavor.transcript_label()); - transcript.append_bytes(&self.layout_digest); - transcript.append(&U64Word(self.num_vars as u64)); - transcript.append(&U64Word(self.poly_count as u64)); - transcript.append(&U64Word(self.one_hot_k as u64)); - transcript.append(&U64Word(self.backend_coeff_len as u64)); - transcript.append(&LabelWithCount( - b"akita_commitment_bytes", - self.serialized_backend_bytes.len() as u64, - )); - transcript.append_bytes(&self.serialized_backend_bytes); +impl Encoding for AkitaCommitment { + fn encode(&self) -> impl AsRef<[u8]> { + let mut out = Label(b"akita_commitment").encode().as_ref().to_vec(); + out.extend_from_slice( + BytesMsg(self.backend_flavor.transcript_label()) + .encode() + .as_ref(), + ); + out.extend_from_slice(BytesMsg(&self.layout_digest).encode().as_ref()); + for word in [ + self.num_vars, + self.poly_count, + self.one_hot_k, + self.backend_coeff_len, + ] { + out.extend_from_slice(U64Word(word as u64).encode().as_ref()); + } + out.extend_from_slice( + LabelWithCount( + b"akita_commitment_bytes", + self.serialized_backend_bytes.len() as u64, + ) + .encode() + .as_ref(), + ); + out.extend_from_slice(BytesMsg(&self.serialized_backend_bytes).encode().as_ref()); + out } } @@ -921,14 +936,16 @@ impl AkitaHidingCommitment { } } -impl AppendToTranscript for AkitaHidingCommitment { - fn append_to_transcript(&self, transcript: &mut T) { - transcript.append(&Label(b"akita_hiding_commitment")); - transcript.append(&LabelWithCount( - b"akita_hiding_eval", - self.eval.len() as u64, - )); - transcript.append_bytes(&self.eval); +impl Encoding for AkitaHidingCommitment { + fn encode(&self) -> impl AsRef<[u8]> { + let mut out = Label(b"akita_hiding_commitment").encode().as_ref().to_vec(); + out.extend_from_slice( + LabelWithCount(b"akita_hiding_eval", self.eval.len() as u64) + .encode() + .as_ref(), + ); + out.extend_from_slice(BytesMsg(&self.eval).encode().as_ref()); + out } } @@ -1209,11 +1226,14 @@ pub(crate) fn transparent_zk_error() -> OpeningsError { /// subsequent Jolt challenge consumes the terminal opening proof, so /// reabsorbing that proof into the outer transcript could not affect /// acceptance. -pub(crate) fn bridged_akita_session(jolt_transcript: &mut T, session_label: &[u8]) -> Vec +pub(crate) fn bridged_akita_session( + jolt_transcript: &mut T, + session_label: &[u8], +) -> Result, OpeningsError> where - T: Transcript, + T: FieldTranscript, { - let bridge = jolt_transcript.challenge_scalar(); + let bridge = jolt_transcript.challenge()?; let bridge_bytes = bridge.to_bytes_le_vec(); // Akita binds the concrete instance into its own Fiat-Shamir state but // keeps the session bytes as domain separator, so the cross-protocol @@ -1221,7 +1241,7 @@ where let mut bridged_session = Vec::with_capacity(session_label.len() + bridge_bytes.len()); bridged_session.extend_from_slice(session_label); bridged_session.extend_from_slice(&bridge_bytes); - bridged_session + Ok(bridged_session) } #[cfg(test)] diff --git a/crates/jolt-akita/src/native_batching.rs b/crates/jolt-akita/src/native_batching.rs index 4e03bc9bae..98924ab7c6 100644 --- a/crates/jolt-akita/src/native_batching.rs +++ b/crates/jolt-akita/src/native_batching.rs @@ -24,7 +24,7 @@ use jolt_openings::{ TaggedGroupOpeningClaim, VerifierOpeningClaim, }; use jolt_poly::MultilinearPoly; -use jolt_transcript::{AppendToTranscript, Label, LabelWithCount, Transcript, U64Word}; +use jolt_transcript::{FieldTranscript, Label, LabelWithCount, U64Word}; use tracing::info_span; use crate::adapters::{ @@ -157,7 +157,7 @@ fn bind_grouped_statement_transcripts( main: &GroupOpeningClaim, ) -> Result, OpeningsError> where - T: Transcript, + T: FieldTranscript, { append_verifier_setup(transcript, setup, AkitaBackendFlavor::OneHot)?; transcript.append(&Label(b"akita_precommit_batch_v4")); @@ -182,20 +182,17 @@ where transcript.append_bytes(b"main_trace"); } transcript.append(&U64Word(u64::from(role.is_some()))); - claim.commitment.append_to_transcript(transcript); + transcript.append(&claim.commitment); transcript.append_values(b"akita_group_point", &claim.point); transcript.append(&LabelWithCount( b"akita_group_evaluations", claim.evaluations.len() as u64, )); for evaluation in &claim.evaluations { - evaluation.append_to_transcript(transcript); + transcript.append(&evaluation); } } - Ok(bridged_akita_session( - transcript, - b"jolt-akita/precommitted-group-batch/v4", - )) + bridged_akita_session(transcript, b"jolt-akita/precommitted-group-batch/v4") } fn prove_one_hot_opening( @@ -247,7 +244,7 @@ impl AkitaNativeBatching { transcript: &mut T, ) -> Result where - T: Transcript, + T: FieldTranscript, { let auxiliary_claims = auxiliary_groups .iter() @@ -350,7 +347,7 @@ impl AkitaNativeBatching { transcript: &mut T, ) -> Result<(), OpeningsError> where - T: Transcript, + T: FieldTranscript, { validate_trace_batch_statement(setup, auxiliary_groups, main)?; let backend_main_point = reverse_point(&main.point); @@ -528,7 +525,7 @@ fn bind_statement_transcripts( point: &[AkitaField], ) -> Result, OpeningsError> where - T: Transcript, + T: FieldTranscript, { { let _span = info_span!("AkitaNativeBatching::append_setup_and_statement").entered(); @@ -536,7 +533,7 @@ where append_batch_statement(transcript, statement, commitment, point); } let _span = info_span!("AkitaNativeBatching::bridge_transcripts").entered(); - Ok(bridged_akita_session(transcript, b"jolt-akita/batch")) + bridged_akita_session(transcript, b"jolt-akita/batch") } fn single_group_batch<'a, Cfg>( @@ -602,7 +599,7 @@ impl BatchOpeningScheme for AkitaNativeBatching { ) -> Result where Self: 'a, - T: Transcript, + T: FieldTranscript, { let ValidatedStatement { commitment, point } = validate_statement( &statement, @@ -677,7 +674,7 @@ impl BatchOpeningScheme for AkitaNativeBatching { transcript: &mut T, ) -> Result<(), OpeningsError> where - T: Transcript, + T: FieldTranscript, { let ValidatedStatement { commitment, point } = validate_statement( statement, @@ -755,7 +752,7 @@ mod tests { use super::*; use jolt_field::Zero; use jolt_openings::{CommitmentGroupRole, EvaluationClaim}; - use jolt_transcript::Blake2bTranscript; + use jolt_transcript::{prover_transcript, Blake2b512}; use crate::adapters::AkitaVerifierScheduleArtifacts; use crate::configs::AkitaChunkProfile; @@ -810,7 +807,7 @@ mod tests { schedule_selection: [0; 32], backend_proof: Vec::new(), }; - let mut transcript = Blake2bTranscript::new(b"invalid-transported-setup"); + let mut transcript = prover_transcript::(b"invalid-transported-setup", b""); let error = ::verify_batch( &transported, &statement, diff --git a/crates/jolt-akita/src/scheme.rs b/crates/jolt-akita/src/scheme.rs index 4f686069a4..8902d6ad0e 100644 --- a/crates/jolt-akita/src/scheme.rs +++ b/crates/jolt-akita/src/scheme.rs @@ -10,7 +10,7 @@ use jolt_openings::{ VerifierOpeningClaim, ZkBatchOpeningScheme, ZkOpeningScheme, }; use jolt_poly::{MultilinearPoly, OneHotPolynomial, Polynomial}; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use serde::{Deserialize, Serialize}; use std::{ collections::{btree_map::Entry, BTreeMap}, @@ -621,7 +621,7 @@ impl CommitmentScheme for AkitaScheme { eval: Self::Field, setup: &Self::ProverSetup, hint: Option, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result { let hint = match hint { Some(hint) => hint, @@ -648,7 +648,7 @@ impl CommitmentScheme for AkitaScheme { eval: Self::Field, proof: &Self::Proof, setup: &Self::VerifierSetup, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(), OpeningsError> { let statement = vec![VerifierOpeningClaim { commitment: commitment.clone(), @@ -712,7 +712,7 @@ impl CommitmentScheme for AkitaScheme { auxiliary_groups: Vec>, final_group: GroupOpeningClaim, final_hint: Self::OpeningHint, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result { AkitaNativeBatching::prove_trace_batch( setup, @@ -728,7 +728,7 @@ impl CommitmentScheme for AkitaScheme { auxiliary_groups: &[TaggedGroupOpeningClaim], final_group: &GroupOpeningClaim, proof: &Self::Proof, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(), OpeningsError> { AkitaNativeBatching::verify_trace_batch( setup, @@ -831,7 +831,7 @@ impl ZkOpeningScheme for AkitaScheme { eval: Self::Field, setup: &Self::ProverSetup, hint: Self::OpeningHint, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(Self::Proof, Self::HidingCommitment, Self::Blind), OpeningsError> { let proof = Self::open(poly, point, eval, setup, Some(hint), transcript)?; Ok(( @@ -846,7 +846,7 @@ impl ZkOpeningScheme for AkitaScheme { _point: &[Self::Field], _proof: &Self::Proof, _setup: &Self::VerifierSetup, - _transcript: &mut impl Transcript, + _transcript: &mut impl FieldTranscript, ) -> Result { Err(transparent_zk_error()) } @@ -868,7 +868,7 @@ impl ZkBatchOpeningScheme for AkitaNativeBatching { ) -> Result, OpeningsError> where Self: 'a, - T: Transcript, + T: FieldTranscript, { Err(transparent_zk_error()) } @@ -881,7 +881,7 @@ impl ZkBatchOpeningScheme for AkitaNativeBatching { _transcript: &mut T, ) -> Result where - T: Transcript, + T: FieldTranscript, { Err(transparent_zk_error()) } @@ -903,7 +903,7 @@ mod tests { use akita_config::CommitmentConfig; use akita_schedules::ValidatedScheduleCatalog; use jolt_field::Ring; - use jolt_transcript::Blake2bTranscript; + use jolt_transcript::{prover_transcript, Blake2b512}; #[test] fn full_width_objects_do_not_widen_bounded_dense_commitments() { @@ -952,47 +952,34 @@ mod tests { }, backend_cache: Default::default(), }; - let mut baseline = Blake2bTranscript::::new(b"akita-setup-key-test"); - let initial_state = baseline.state(); - - append_verifier_setup(&mut baseline, &setup, AkitaBackendFlavor::Dense).unwrap(); - assert_ne!(baseline.state(), initial_state); - - let mut same = Blake2bTranscript::::new(b"akita-setup-key-test"); - append_verifier_setup(&mut same, &setup, AkitaBackendFlavor::Dense).unwrap(); - assert_eq!(baseline.state(), same.state()); - - let mut flavor_transcript = Blake2bTranscript::::new(b"akita-setup-key-test"); - append_verifier_setup(&mut flavor_transcript, &setup, AkitaBackendFlavor::OneHot).unwrap(); - assert_ne!(baseline.state(), flavor_transcript.state()); + // The next squeeze after binding `setup` under `flavor`: equal for + // two bindings exactly when they absorbed the same bytes. + let bound = |setup: &AkitaVerifierSetup, flavor| { + let mut transcript = prover_transcript::(b"akita-setup-key-test", b""); + append_verifier_setup(&mut transcript, setup, flavor).unwrap(); + transcript.verifier_message::<[u8; 32]>() + }; + let baseline = bound(&setup, AkitaBackendFlavor::Dense); + assert_ne!( + baseline, + prover_transcript::(b"akita-setup-key-test", b"") + .verifier_message::<[u8; 32]>() + ); + assert_eq!(baseline, bound(&setup, AkitaBackendFlavor::Dense)); + assert_ne!(baseline, bound(&setup, AkitaBackendFlavor::OneHot)); let mut changed_shape = setup.clone(); changed_shape.max_num_vars = 5; - let mut shape_transcript = Blake2bTranscript::::new(b"akita-setup-key-test"); - append_verifier_setup( - &mut shape_transcript, - &changed_shape, - AkitaBackendFlavor::Dense, - ) - .unwrap(); - assert_ne!(baseline.state(), shape_transcript.state()); + assert_ne!(baseline, bound(&changed_shape, AkitaBackendFlavor::Dense)); let mut changed_digest = setup; changed_digest.default_layout_digest = [8; 32]; - let mut digest_transcript = Blake2bTranscript::::new(b"akita-setup-key-test"); - append_verifier_setup( - &mut digest_transcript, - &changed_digest, - AkitaBackendFlavor::Dense, - ) - .unwrap(); - assert_ne!(baseline.state(), digest_transcript.state()); + let digest = bound(&changed_digest, AkitaBackendFlavor::Dense); + assert_ne!(baseline, digest); let mut changed_k = changed_digest; changed_k.one_hot_k = AKITA_ONE_HOT_K16; - let mut k_transcript = Blake2bTranscript::::new(b"akita-setup-key-test"); - append_verifier_setup(&mut k_transcript, &changed_k, AkitaBackendFlavor::Dense).unwrap(); - assert_ne!(digest_transcript.state(), k_transcript.state()); + assert_ne!(digest, bound(&changed_k, AkitaBackendFlavor::Dense)); } #[test] @@ -1058,7 +1045,7 @@ mod tests { evaluation: EvaluationClaim::new(point.clone(), polynomial.evaluate(&point)), }) .collect::>(); - let mut prover_transcript = Blake2bTranscript::::new(b"akita-one-hot-k"); + let mut prover_state = prover_transcript::(b"akita-one-hot-k", b""); let proof = ::prove_batch( &prover_setup, statement.clone(), @@ -1067,10 +1054,10 @@ mod tests { .map(|poly| poly as &dyn MultilinearPoly) .collect(), hint, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let mut verifier_transcript = Blake2bTranscript::::new(b"akita-one-hot-k"); + let mut verifier_transcript = prover_transcript::(b"akita-one-hot-k", b""); ::verify_batch( &verifier_setup, &statement, @@ -1078,7 +1065,10 @@ mod tests { &mut verifier_transcript, ) .unwrap(); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); if num_vars == 16 && num_polys == 1 { let one_hot = match &verifier_setup.schedule_artifacts { @@ -1111,7 +1101,7 @@ mod tests { // Transport drops the honest setup's primed backend caches. let bytes = serde_json::to_vec(&tampered).unwrap(); let transported: AkitaVerifierSetup = serde_json::from_slice(&bytes).unwrap(); - let mut transcript = Blake2bTranscript::::new(b"akita-one-hot-k"); + let mut transcript = prover_transcript::(b"akita-one-hot-k", b""); let error = ::verify_batch( &transported, &statement, @@ -1129,7 +1119,7 @@ mod tests { } else { AKITA_ONE_HOT_K16 }; - let mut verifier_transcript = Blake2bTranscript::::new(b"akita-one-hot-k"); + let mut verifier_transcript = prover_transcript::(b"akita-one-hot-k", b""); let _ = ::verify_batch( &verifier_setup, &wrong_k_statement, @@ -1289,13 +1279,13 @@ mod tests { commitment, evaluation: EvaluationClaim::new(point, value), }]; - let mut prover_transcript = Blake2bTranscript::::new(b"catalog-replay"); + let mut prover_state = prover_transcript::(b"catalog-replay", b""); let proof = ::prove_batch( &prover_setup, statement.clone(), vec![&polynomial], hint, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); @@ -1352,7 +1342,7 @@ mod tests { alternate_artifacts, )) .unwrap(); - let mut verifier_transcript = Blake2bTranscript::::new(b"catalog-replay"); + let mut verifier_transcript = prover_transcript::(b"catalog-replay", b""); let _ = ::verify_batch( &alternate_verifier_setup, &statement, @@ -1361,7 +1351,7 @@ mod tests { ) .expect_err("a proof must not replay across catalogs with different digests"); - let mut original_transcript = Blake2bTranscript::::new(b"catalog-replay"); + let mut original_transcript = prover_transcript::(b"catalog-replay", b""); ::verify_batch( &verifier_setup, &statement, @@ -1398,17 +1388,17 @@ mod tests { evaluation: EvaluationClaim::new(point.clone(), claim), }]; - let mut prover_transcript = Blake2bTranscript::::new(b"akita-direct-layout"); + let mut prover_state = prover_transcript::(b"akita-direct-layout", b""); let proof = ::prove_batch( &prover_setup, statement.clone(), vec![&polynomial], hint, - &mut prover_transcript, + &mut prover_state, ) .expect("direct proof should prove"); - let mut verifier_transcript = Blake2bTranscript::::new(b"akita-direct-layout"); + let mut verifier_transcript = prover_transcript::(b"akita-direct-layout", b""); ::verify_batch( &verifier_setup, &statement, @@ -1416,11 +1406,14 @@ mod tests { &mut verifier_transcript, ) .expect("direct proof should verify"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); let mut changed_commitment_statement = statement.clone(); changed_commitment_statement[0].commitment.layout_digest = [15; 32]; - let mut verifier_transcript = Blake2bTranscript::::new(b"akita-direct-layout"); + let mut verifier_transcript = prover_transcript::(b"akita-direct-layout", b""); let _error = ::verify_batch( &verifier_setup, &changed_commitment_statement, @@ -1431,7 +1424,7 @@ mod tests { let mut changed_setup = verifier_setup; changed_setup.default_layout_digest = commitment_digest; - let mut verifier_transcript = Blake2bTranscript::::new(b"akita-direct-layout"); + let mut verifier_transcript = prover_transcript::(b"akita-direct-layout", b""); let _error = ::verify_batch( &changed_setup, &statement, diff --git a/crates/jolt-akita/tests/e2e.rs b/crates/jolt-akita/tests/e2e.rs index 4c5789844f..2c0dabd8e8 100644 --- a/crates/jolt-akita/tests/e2e.rs +++ b/crates/jolt-akita/tests/e2e.rs @@ -9,7 +9,7 @@ pub mod support; use jolt_akita::{AkitaChunkProfile, AkitaScheduleArtifacts, AkitaScheme, AkitaSetupParams}; use jolt_openings::{CommitmentScheme, OpeningsError}; use jolt_poly::Polynomial; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; use support::{f, layout, polynomial, setup_for}; #[test] @@ -27,18 +27,18 @@ fn akita_single_opening_rejects_wrong_eval_point_setup_and_transcript() { let eval = poly.evaluate(&point); let (commitment, hint) = AkitaScheme::commit(&poly, &prover_setup).unwrap(); - let mut prover_transcript = Blake2bTranscript::new(b"akita-e2e-rejects"); + let mut prover_state = prover_transcript::(b"akita-e2e-rejects", b""); let proof = AkitaScheme::open( &poly, &point, eval, &prover_setup, Some(hint), - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let mut transcript = Blake2bTranscript::new(b"akita-e2e-rejects"); + let mut transcript = prover_transcript::(b"akita-e2e-rejects", b""); assert!( AkitaScheme::verify( &commitment, @@ -54,7 +54,7 @@ fn akita_single_opening_rejects_wrong_eval_point_setup_and_transcript() { let mut wrong_point = point.clone(); wrong_point[2] += f(1); - let mut transcript = Blake2bTranscript::new(b"akita-e2e-rejects"); + let mut transcript = prover_transcript::(b"akita-e2e-rejects", b""); assert!( AkitaScheme::verify( &commitment, @@ -68,7 +68,7 @@ fn akita_single_opening_rejects_wrong_eval_point_setup_and_transcript() { "wrong opening point should reject" ); - let mut wrong_transcript = Blake2bTranscript::new(b"akita-e2e-wrong-domain"); + let mut wrong_transcript = prover_transcript::(b"akita-e2e-wrong-domain", b""); assert!( AkitaScheme::verify( &commitment, @@ -83,7 +83,7 @@ fn akita_single_opening_rejects_wrong_eval_point_setup_and_transcript() { ); let (_, wrong_layout_setup) = setup_for(14, 1, layout(8)); - let mut transcript = Blake2bTranscript::new(b"akita-e2e-rejects"); + let mut transcript = prover_transcript::(b"akita-e2e-rejects", b""); assert!( AkitaScheme::verify( &commitment, @@ -160,18 +160,18 @@ fn single_opening_roundtrip( let eval = poly.evaluate(&point); let (commitment, hint) = AkitaScheme::commit(&poly, &prover_setup).unwrap(); - let mut prover_transcript = Blake2bTranscript::new(label); + let mut prover_state = prover_transcript::(label, b""); let proof = AkitaScheme::open( &poly, &point, eval, &prover_setup, Some(hint), - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let mut verifier_transcript = Blake2bTranscript::new(label); + let mut verifier_transcript = prover_transcript::(label, b""); AkitaScheme::verify( &commitment, &point, @@ -181,5 +181,8 @@ fn single_opening_roundtrip( &mut verifier_transcript, ) .expect("single proof should verify"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } diff --git a/crates/jolt-akita/tests/external_catalog.rs b/crates/jolt-akita/tests/external_catalog.rs index fe94a9727d..0a8a0d0a09 100644 --- a/crates/jolt-akita/tests/external_catalog.rs +++ b/crates/jolt-akita/tests/external_catalog.rs @@ -17,7 +17,7 @@ use jolt_akita::{ }; use jolt_openings::{BatchOpeningScheme, CommitmentScheme}; use jolt_poly::{MultilinearPoly, OneHotPolynomial}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; #[expect( dead_code, @@ -44,7 +44,7 @@ fn external_multichunk_catalog_survives_transport() { .expect("restore verifier without source catalogs"); let mut statement = support::single_statement(commitment, &point, evaluation); assert_eq!(setup.akita_chunk_profile(), AkitaChunkProfile::new(2, 4)); - let mut transcript = Blake2bTranscript::new(DOMAIN); + let mut transcript = prover_transcript::(DOMAIN, b""); AkitaNativeBatching::verify_batch(&setup, &statement, &proof, &mut transcript) .expect("upstream verifier accepts the transported multi-chunk proof"); let mut wrong = serde_json::to_value(&setup).expect("encode trusted setup"); @@ -55,11 +55,11 @@ fn external_multichunk_catalog_survives_transport() { &wrong, &statement, &proof, - &mut Blake2bTranscript::new(DOMAIN) + &mut prover_transcript::(DOMAIN, b"") ) .is_err()); statement[0].evaluation.value += support::f(1); - let mut transcript = Blake2bTranscript::new(DOMAIN); + let mut transcript = prover_transcript::(DOMAIN, b""); assert!( AkitaNativeBatching::verify_batch(&setup, &statement, &proof, &mut transcript).is_err() ); @@ -141,7 +141,7 @@ fn external_multichunk_catalog_survives_transport() { AkitaScheme::commit_one_hot_group(&prover, support::layout(3), &polynomials) .expect("commit one-hot source"); let statement = support::single_statement(commitment.clone(), &point, evaluation); - let mut transcript = Blake2bTranscript::new(DOMAIN); + let mut transcript = prover_transcript::(DOMAIN, b""); let proof = AkitaNativeBatching::prove_batch( &prover, statement.clone(), diff --git a/crates/jolt-akita/tests/fold_paths.rs b/crates/jolt-akita/tests/fold_paths.rs index b7c695369d..ddf6725141 100644 --- a/crates/jolt-akita/tests/fold_paths.rs +++ b/crates/jolt-akita/tests/fold_paths.rs @@ -16,7 +16,7 @@ use jolt_akita::{ AkitaScheme, }; use jolt_openings::{CommitmentScheme, OpeningsError}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; use support::{f, layout, polynomial, setup_for}; struct ProofFixture { @@ -30,7 +30,7 @@ struct ProofFixture { impl ProofFixture { fn verify(&self, proof: &AkitaBatchProof) -> Result<(), OpeningsError> { - let mut transcript = Blake2bTranscript::new(self.label); + let mut transcript = prover_transcript::(self.label, b""); AkitaScheme::verify( &self.commitment, &self.point, @@ -50,18 +50,18 @@ fn fold_roundtrip(num_vars: usize, label: &'static [u8]) -> ProofFixture { let (commitment, hint) = AkitaScheme::commit(&poly, &prover_setup).expect("dense commit should succeed"); - let mut prover_transcript = Blake2bTranscript::new(label); + let mut prover_state = prover_transcript::(label, b""); let proof = AkitaScheme::open( &poly, &point, eval, &prover_setup, Some(hint), - &mut prover_transcript, + &mut prover_state, ) .expect("fold-schedule proof should be produced"); - let mut verifier_transcript = Blake2bTranscript::new(label); + let mut verifier_transcript = prover_transcript::(label, b""); AkitaScheme::verify( &commitment, &point, @@ -71,7 +71,10 @@ fn fold_roundtrip(num_vars: usize, label: &'static [u8]) -> ProofFixture { &mut verifier_transcript, ) .expect("fold-schedule proof should verify"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); ProofFixture { verifier_setup, @@ -104,7 +107,7 @@ fn deep_recursive_fold_schedule_roundtrips() { let mut tampered_eval = fixture.eval; tampered_eval += f(1); - let mut transcript = Blake2bTranscript::new(fixture.label); + let mut transcript = prover_transcript::(fixture.label, b""); assert!( AkitaScheme::verify( &fixture.commitment, diff --git a/crates/jolt-akita/tests/grouped_capacity.rs b/crates/jolt-akita/tests/grouped_capacity.rs index 490b32f3cc..7289c9afac 100644 --- a/crates/jolt-akita/tests/grouped_capacity.rs +++ b/crates/jolt-akita/tests/grouped_capacity.rs @@ -22,7 +22,7 @@ use jolt_openings::{ TransparentObjectSetup, }; use jolt_poly::{MultilinearPoly, OneHotPolynomial}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, Transcript}; use support::{f, layout, polynomial}; const FINAL_NUM_VARS: usize = 16; @@ -106,13 +106,13 @@ fn grouped_opening_proves_advice_larger_than_the_trace_group() { .expect("trace group should commit against the larger advice object"); let main = GroupOpeningClaim::new(trace_commitment, trace_point, vec![trace_evaluation]); - let mut prover_transcript = Blake2bTranscript::new(b"akita-grouped-capacity"); + let mut prover_state = prover_transcript::(b"akita-grouped-capacity", b""); let proof = AkitaScheme::prove_batch( &prover_setup, vec![(advice_claim.clone(), advice_hint)], main.clone(), trace_hint, - &mut prover_transcript, + &mut prover_state, ) .expect("grouped opening should prove"); @@ -121,7 +121,8 @@ fn grouped_opening_proves_advice_larger_than_the_trace_group() { ) .expect("verifier setup should deserialize"); for setup in [&verifier_setup, &transported] { - let mut verifier_transcript = Blake2bTranscript::new(b"akita-grouped-capacity"); + let mut verifier_transcript = + prover_transcript::(b"akita-grouped-capacity", b""); AkitaScheme::verify_batch( setup, std::slice::from_ref(&advice_claim), @@ -130,10 +131,13 @@ fn grouped_opening_proves_advice_larger_than_the_trace_group() { &mut verifier_transcript, ) .expect("grouped opening should verify"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } - let mut other_context = Blake2bTranscript::new(b"akita-grouped-capacity"); + let mut other_context = prover_transcript::(b"akita-grouped-capacity", b""); other_context.append_bytes(b"different preceding Jolt messages"); assert!(AkitaScheme::verify_batch( &transported, diff --git a/crates/jolt-akita/tests/native_batching.rs b/crates/jolt-akita/tests/native_batching.rs index 2cd9f1c72c..c5b1b48322 100644 --- a/crates/jolt-akita/tests/native_batching.rs +++ b/crates/jolt-akita/tests/native_batching.rs @@ -15,7 +15,7 @@ use jolt_openings::{ VerifierOpeningClaim, }; use jolt_poly::{MultilinearPoly, OneHotPolynomial}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; use support::{ batch_polynomials, f, layout, native_setup, native_statement, polynomial, setup_for, single_statement, @@ -87,16 +87,16 @@ fn akita_streamed_trace_batch_rejects_swapped_evaluations() { ) .expect("streamed trace group should commit"); let mut statement = GroupOpeningClaim::new(commitment, point, evaluations); - let mut prover_transcript = Blake2bTranscript::new(b"akita-streamed-tamper"); + let mut prover_state = prover_transcript::(b"akita-streamed-tamper", b""); let proof = AkitaScheme::prove_batch( &prover_setup, Vec::new(), statement.clone(), hint, - &mut prover_transcript, + &mut prover_state, ) .expect("streamed trace batch should prove"); - let mut verifier_transcript = Blake2bTranscript::new(b"akita-streamed-tamper"); + let mut verifier_transcript = prover_transcript::(b"akita-streamed-tamper", b""); AkitaScheme::verify_batch( &verifier_setup, &[], @@ -105,10 +105,13 @@ fn akita_streamed_trace_batch_rejects_swapped_evaluations() { &mut verifier_transcript, ) .expect("original streamed trace evaluations must verify"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); statement.evaluations.swap(0, 1); - let mut verifier_transcript = Blake2bTranscript::new(b"akita-streamed-tamper"); + let mut verifier_transcript = prover_transcript::(b"akita-streamed-tamper", b""); assert!(AkitaScheme::verify_batch( &verifier_setup, &[], @@ -132,17 +135,17 @@ fn akita_native_batching_roundtrips_grouped_commitment() { .expect("grouped commit should succeed"); let statement = native_statement(commitment, &point, [eval_a, eval_b]); - let mut prover_transcript = Blake2bTranscript::new(b"akita-bb-roundtrip"); + let mut prover_state = prover_transcript::(b"akita-bb-roundtrip", b""); let proof = ::prove_batch( &prover_setup, statement.clone(), batch_polynomials([&poly_a, &poly_b]), hint, - &mut prover_transcript, + &mut prover_state, ) .expect("black-box proof should be produced"); - let mut verifier_transcript = Blake2bTranscript::new(b"akita-bb-roundtrip"); + let mut verifier_transcript = prover_transcript::(b"akita-bb-roundtrip", b""); ::verify_batch( &verifier_setup, &statement, @@ -150,7 +153,10 @@ fn akita_native_batching_roundtrips_grouped_commitment() { &mut verifier_transcript, ) .expect("black-box proof should verify"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } #[test] @@ -168,7 +174,7 @@ fn akita_native_batching_rejects_malformed_statements() { AkitaScheme::commit_group(&prover_setup, layout(7), &[polynomial(16, 80)]) .expect("other commit should succeed"); - let mut transcript = Blake2bTranscript::new(b"akita-bb-empty"); + let mut transcript = prover_transcript::(b"akita-bb-empty", b""); assert!(matches!( ::prove_batch( &prover_setup, @@ -190,7 +196,7 @@ fn akita_native_batching_rejects_malformed_statements() { evaluation: EvaluationClaim::new(point.clone(), eval_b), }, ]; - let mut transcript = Blake2bTranscript::new(b"akita-bb-mixed-commit"); + let mut transcript = prover_transcript::(b"akita-bb-mixed-commit", b""); assert!(matches!( ::prove_batch( &prover_setup, @@ -206,7 +212,7 @@ fn akita_native_batching_rejects_malformed_statements() { let mut shifted_point = point.clone(); shifted_point[0] += f(1); mixed_points[1].evaluation = EvaluationClaim::new(shifted_point, eval_b); - let mut transcript = Blake2bTranscript::new(b"akita-bb-mixed-points"); + let mut transcript = prover_transcript::(b"akita-bb-mixed-points", b""); assert!(matches!( ::prove_batch( &prover_setup, @@ -219,7 +225,7 @@ fn akita_native_batching_rejects_malformed_statements() { )); let one_claim_for_two_slots = single_statement(group_commitment, &point, eval_a); - let mut transcript = Blake2bTranscript::new(b"akita-bb-claim-count"); + let mut transcript = prover_transcript::(b"akita-bb-claim-count", b""); assert!(matches!( ::prove_batch( &prover_setup, @@ -251,7 +257,7 @@ fn akita_native_batching_rejects_bad_prover_witnesses() { .expect("other grouped commit should succeed"); let statement = native_statement(commitment, &point, [eval_a, eval_b]); - let mut transcript = Blake2bTranscript::new(b"akita-bb-wrong-hint"); + let mut transcript = prover_transcript::(b"akita-bb-wrong-hint", b""); assert!( matches!( ::prove_batch( @@ -266,7 +272,7 @@ fn akita_native_batching_rejects_bad_prover_witnesses() { "mismatched prover hint should reject" ); - let mut transcript = Blake2bTranscript::new(b"akita-bb-wrong-count"); + let mut transcript = prover_transcript::(b"akita-bb-wrong-count", b""); assert!(matches!( ::prove_batch( &prover_setup, @@ -279,7 +285,7 @@ fn akita_native_batching_rejects_bad_prover_witnesses() { )); let wrong_dimension = polynomial(12, 200); - let mut transcript = Blake2bTranscript::new(b"akita-bb-wrong-dim"); + let mut transcript = prover_transcript::(b"akita-bb-wrong-dim", b""); assert!(matches!( ::prove_batch( &prover_setup, @@ -305,17 +311,17 @@ fn akita_native_batching_rejects_tampered_verifier_inputs() { .expect("grouped commit should succeed"); let statement = native_statement(commitment.clone(), &point, [eval_a, eval_b]); - let mut prover_transcript = Blake2bTranscript::new(b"akita-bb-tamper"); + let mut prover_state = prover_transcript::(b"akita-bb-tamper", b""); let proof = ::prove_batch( &prover_setup, statement.clone(), batch_polynomials([&poly_a, &poly_b]), hint, - &mut prover_transcript, + &mut prover_state, ) .expect("black-box proof should be produced"); - let mut verifier_transcript = Blake2bTranscript::new(b"akita-bb-tamper"); + let mut verifier_transcript = prover_transcript::(b"akita-bb-tamper", b""); ::verify_batch( &verifier_setup, &statement, @@ -355,7 +361,7 @@ fn assert_native_verify_rejects( statement: jolt_akita::AkitaNativeBatchStatement, proof: &jolt_akita::AkitaBatchProof, ) { - let mut transcript = Blake2bTranscript::new(b"akita-bb-tamper"); + let mut transcript = prover_transcript::(b"akita-bb-tamper", b""); assert!(::verify_batch( setup, &statement, @@ -386,7 +392,7 @@ fn akita_native_batching_rejects_point_commitment_dimension_mismatch() { .expect("commit should succeed"); let statement = single_statement(commitment, &short_point, f(9)); - let mut transcript = Blake2bTranscript::new(b"akita-bb-short-point"); + let mut transcript = prover_transcript::(b"akita-bb-short-point", b""); expect_invalid_batch( ::prove_batch( &prover_setup, @@ -411,7 +417,7 @@ fn akita_native_batching_rejects_statements_outside_the_verifier_setup() { AkitaScheme::commit_group(&small_setup, layout(7), std::slice::from_ref(&small_poly)) .expect("commit should succeed"); let small_statement = single_statement(small_commitment, &small_point, small_eval); - let mut transcript = Blake2bTranscript::new(b"akita-bb-cross-setup"); + let mut transcript = prover_transcript::(b"akita-bb-cross-setup", b""); let small_proof = ::prove_batch( &small_setup, small_statement.clone(), @@ -422,7 +428,7 @@ fn akita_native_batching_rejects_statements_outside_the_verifier_setup() { .expect("proof should be produced"); let (_, wider_verifier) = setup_for(15, 2, layout(7)); - let mut transcript = Blake2bTranscript::new(b"akita-bb-cross-setup"); + let mut transcript = prover_transcript::(b"akita-bb-cross-setup", b""); expect_invalid_batch( ::verify_batch( &wider_verifier, @@ -448,7 +454,7 @@ fn akita_native_batching_rejects_statements_outside_the_verifier_setup() { &point, [poly_a.evaluate(&point), poly_b.evaluate(&point)], ); - let mut transcript = Blake2bTranscript::new(b"akita-bb-cross-setup"); + let mut transcript = prover_transcript::(b"akita-bb-cross-setup", b""); let group_proof = ::prove_batch( &two_slot_setup, group_statement.clone(), @@ -458,7 +464,7 @@ fn akita_native_batching_rejects_statements_outside_the_verifier_setup() { ) .expect("group proof should be produced"); let (_, one_slot_verifier) = setup_for(16, 1, layout(7)); - let mut transcript = Blake2bTranscript::new(b"akita-bb-cross-setup"); + let mut transcript = prover_transcript::(b"akita-bb-cross-setup", b""); expect_invalid_batch( ::verify_batch( &one_slot_verifier, @@ -480,7 +486,7 @@ fn akita_native_batching_rejects_dense_commitment_with_chunk_size() { AkitaScheme::commit_group(&prover_setup, layout(7), std::slice::from_ref(&poly)) .expect("commit should succeed"); let statement = single_statement(commitment.clone(), &point, eval); - let mut transcript = Blake2bTranscript::new(b"akita-bb-full-chunk"); + let mut transcript = prover_transcript::(b"akita-bb-full-chunk", b""); let proof = ::prove_batch( &prover_setup, statement.clone(), @@ -498,7 +504,7 @@ fn akita_native_batching_rejects_dense_commitment_with_chunk_size() { serde_json::from_value(forged).expect("forged commitment deserializes"); let forged_statement = single_statement(forged, &point, eval); - let mut transcript = Blake2bTranscript::new(b"akita-bb-full-chunk"); + let mut transcript = prover_transcript::(b"akita-bb-full-chunk", b""); expect_invalid_batch( ::verify_batch( &verifier_setup, @@ -541,7 +547,7 @@ fn akita_native_batching_rejects_dense_witnesses_for_one_hot_hints() { let dense_12 = polynomial(12, 1); let point_12: Vec<_> = (0..12).map(|index| f(index as u64 + 2)).collect(); let statement = single_statement(commitment, &point_12, dense_12.evaluate(&point_12)); - let mut transcript = Blake2bTranscript::new(b"akita-bb-dense-for-onehot"); + let mut transcript = prover_transcript::(b"akita-bb-dense-for-onehot", b""); expect_invalid_batch( ::prove_batch( &one_hot_setup, diff --git a/crates/jolt-akita/tests/one_hot_groups.rs b/crates/jolt-akita/tests/one_hot_groups.rs index d43ca6d40a..fd44dede86 100644 --- a/crates/jolt-akita/tests/one_hot_groups.rs +++ b/crates/jolt-akita/tests/one_hot_groups.rs @@ -12,7 +12,7 @@ use jolt_akita::{ }; use jolt_openings::{BatchOpeningScheme, CommitmentScheme, OpeningsError}; use jolt_poly::{MultilinearPoly, OneHotIndexOrder, OneHotPolynomial}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; use support::{f, layout, native_statement}; /// `log2(K) + 8`: the smallest K=16 one-hot dimension the folded-only @@ -91,17 +91,17 @@ fn owned_group_opens_through_native_batching_and_verifies() { .collect(); let statement = native_statement(commitment, &point, evaluations.iter().copied()); - let mut prover_transcript = Blake2bTranscript::new(b"akita-owned-one-hot"); + let mut prover_state = prover_transcript::(b"akita-owned-one-hot", b""); let proof = ::prove_batch( &prover_setup, statement.clone(), witnesses.clone(), hint.clone(), - &mut prover_transcript, + &mut prover_state, ) .expect("owned one-hot group should prove"); - let mut verifier_transcript = Blake2bTranscript::new(b"akita-owned-one-hot"); + let mut verifier_transcript = prover_transcript::(b"akita-owned-one-hot", b""); ::verify_batch( &verifier_setup, &statement, @@ -109,11 +109,14 @@ fn owned_group_opens_through_native_batching_and_verifies() { &mut verifier_transcript, ) .expect("owned one-hot group proof should verify"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); let mut tampered = statement.clone(); tampered[0].evaluation.value += f(1); - let mut verifier_transcript = Blake2bTranscript::new(b"akita-owned-one-hot"); + let mut verifier_transcript = prover_transcript::(b"akita-owned-one-hot", b""); assert!( ::verify_batch( &verifier_setup, @@ -125,7 +128,7 @@ fn owned_group_opens_through_native_batching_and_verifies() { "tampered evaluation must reject" ); - let mut transcript = Blake2bTranscript::new(b"akita-owned-one-hot"); + let mut transcript = prover_transcript::(b"akita-owned-one-hot", b""); let err = ::prove_batch( &prover_setup, statement[..1].to_vec(), diff --git a/crates/jolt-akita/tests/pathologies.rs b/crates/jolt-akita/tests/pathologies.rs index dbda70ab95..e0b62396ef 100644 --- a/crates/jolt-akita/tests/pathologies.rs +++ b/crates/jolt-akita/tests/pathologies.rs @@ -17,7 +17,7 @@ use jolt_openings::{ BatchOpeningScheme, CommitmentScheme, OpeningsError, ZkBatchOpeningScheme, ZkOpeningScheme, }; use jolt_poly::{MultilinearPoly, OneHotPolynomial, Point, Polynomial, HIGH_TO_LOW}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; use serde_json::{json, Value}; use support::{batch_polynomials, f, layout, native_setup, polynomial, setup_for}; @@ -83,18 +83,18 @@ fn akita_public_commit_open_uses_upstream_one_hot_path_for_k256() { let eval = one_hot.evaluate(&point); assert_eq!(eval, dense.evaluate(&point)); - let mut prover_transcript = Blake2bTranscript::new(b"akita-native-one-hot"); + let mut prover_state = prover_transcript::(b"akita-native-one-hot", b""); let proof = AkitaScheme::open( &one_hot, &point, eval, &prover_setup, Some(one_hot_hint), - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let mut verifier_transcript = Blake2bTranscript::new(b"akita-native-one-hot"); + let mut verifier_transcript = prover_transcript::(b"akita-native-one-hot", b""); AkitaScheme::verify( &one_hot_commitment, &point, @@ -104,7 +104,10 @@ fn akita_public_commit_open_uses_upstream_one_hot_path_for_k256() { &mut verifier_transcript, ) .expect("native Akita one-hot proof should verify"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } #[test] @@ -149,7 +152,7 @@ fn akita_forged_commitment_metadata_rejects_before_shape_backed_allocation() { evaluation: claim.evaluation.clone(), }) .collect(); - let mut transcript = Blake2bTranscript::new(b"akita-forged-metadata"); + let mut transcript = prover_transcript::(b"akita-forged-metadata", b""); let err = ::verify_batch( &verifier_setup, &forged_statement, @@ -169,7 +172,7 @@ fn akita_native_batching_rejects_corrupted_proof_payloads() { for field in ["schedule_selection", "backend_proof"] { let tampered = mutate_byte_array_field(&proof, field); - let mut transcript = Blake2bTranscript::new(b"akita-corrupt-proof"); + let mut transcript = prover_transcript::(b"akita-corrupt-proof", b""); assert!( ::verify_batch( &verifier_setup, @@ -191,18 +194,18 @@ fn akita_zk_interfaces_are_explicitly_unsupported() { let eval = poly.evaluate(&point); let (commitment, hint) = AkitaScheme::commit_zk(&poly, &prover_setup).unwrap(); - let mut prover_transcript = Blake2bTranscript::new(b"akita-zk-unsupported"); + let mut prover_state = prover_transcript::(b"akita-zk-unsupported", b""); let (proof, _, ()) = AkitaScheme::open_zk( &poly, &point, eval, &prover_setup, hint.clone(), - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let mut verifier_transcript = Blake2bTranscript::new(b"akita-zk-unsupported"); + let mut verifier_transcript = prover_transcript::(b"akita-zk-unsupported", b""); assert_transparent_zk_error(AkitaScheme::verify_zk( &commitment, &point, @@ -212,7 +215,7 @@ fn akita_zk_interfaces_are_explicitly_unsupported() { )); let zk_point = Point::::high_to_low(point); - let mut transcript = Blake2bTranscript::new(b"akita-zk-batch-prove-unsup"); + let mut transcript = prover_transcript::(b"akita-zk-batch-prove-unsup", b""); assert_transparent_zk_error( ::prove_batch_zk( &prover_setup, @@ -225,7 +228,7 @@ fn akita_zk_interfaces_are_explicitly_unsupported() { ), ); - let mut transcript = Blake2bTranscript::new(b"akita-zk-batch-verify-unsup"); + let mut transcript = prover_transcript::(b"akita-zk-batch-verify-unsup", b""); assert_transparent_zk_error( ::verify_batch_zk( &verifier_setup, @@ -251,7 +254,7 @@ fn native_proof_fixture( .expect("grouped commit should succeed"); let statement = support::native_statement(commitment, &point, [eval_a, eval_b]); - let mut transcript = Blake2bTranscript::new(label); + let mut transcript = prover_transcript::(label, b""); let proof = ::prove_batch( &prover_setup, statement.clone(), diff --git a/crates/jolt-akita/tests/prefix_packing.rs b/crates/jolt-akita/tests/prefix_packing.rs index 453b7ea4d1..4b2d145d99 100644 --- a/crates/jolt-akita/tests/prefix_packing.rs +++ b/crates/jolt-akita/tests/prefix_packing.rs @@ -9,7 +9,7 @@ use jolt_akita::{ use jolt_field::Ring; use jolt_openings::{CommitmentScheme, PrefixPackedClaims, PrefixPackedLayout}; use jolt_poly::Polynomial; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; fn f(value: u64) -> AkitaField { AkitaField::from_u64(value) @@ -57,21 +57,19 @@ fn fixed_prefix_claim_opens_the_materialized_akita_polynomial() { )) .unwrap(); let (commitment, hint) = AkitaScheme::commit(&physical, &prover_setup).unwrap(); - let mut prover_transcript = Blake2bTranscript::new(b"akita/fixed-prefix"); - let physical_claim = layout - .reduce_claims(&claims, &mut prover_transcript) - .unwrap(); + let mut prover_state = prover_transcript::(b"akita/fixed-prefix", b""); + let physical_claim = layout.reduce_claims(&claims, &mut prover_state).unwrap(); let proof = AkitaScheme::open( &physical, physical_claim.point.as_slice(), physical_claim.value, &prover_setup, Some(hint), - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let mut verifier_transcript = Blake2bTranscript::new(b"akita/fixed-prefix"); + let mut verifier_transcript = prover_transcript::(b"akita/fixed-prefix", b""); let verifier_claim = layout .reduce_claims(&claims, &mut verifier_transcript) .unwrap(); @@ -117,21 +115,20 @@ fn changed_fixed_prefix_statement_rejects_the_original_proof() { )) .unwrap(); let (commitment, hint) = AkitaScheme::commit(&physical, &prover_setup).unwrap(); - let mut prover_transcript = Blake2bTranscript::new(b"akita/fixed-prefix-tamper"); - let physical_claim = layout - .reduce_claims(&claims, &mut prover_transcript) - .unwrap(); + let mut prover_state = prover_transcript::(b"akita/fixed-prefix-tamper", b""); + let physical_claim = layout.reduce_claims(&claims, &mut prover_state).unwrap(); let proof = AkitaScheme::open( &physical, physical_claim.point.as_slice(), physical_claim.value, &prover_setup, Some(hint), - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let mut honest_verifier_transcript = Blake2bTranscript::new(b"akita/fixed-prefix-tamper"); + let mut honest_verifier_transcript = + prover_transcript::(b"akita/fixed-prefix-tamper", b""); let honest_claim = layout .reduce_claims(&claims, &mut honest_verifier_transcript) .unwrap(); @@ -147,7 +144,8 @@ fn changed_fixed_prefix_statement_rejects_the_original_proof() { evaluations[1] += f(1); let changed = PrefixPackedClaims::new(digest, logical_point, evaluations); - let mut verifier_transcript = Blake2bTranscript::new(b"akita/fixed-prefix-tamper"); + let mut verifier_transcript = + prover_transcript::(b"akita/fixed-prefix-tamper", b""); let changed_claim = layout .reduce_claims(&changed, &mut verifier_transcript) .unwrap(); diff --git a/crates/jolt-akita/tests/runtime_chunking.rs b/crates/jolt-akita/tests/runtime_chunking.rs index 970d3322d0..328c200f18 100644 --- a/crates/jolt-akita/tests/runtime_chunking.rs +++ b/crates/jolt-akita/tests/runtime_chunking.rs @@ -16,7 +16,7 @@ use jolt_akita::{ AkitaSetupParams, AkitaVerifierSetup, }; use jolt_openings::CommitmentScheme; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; use serde_json::Value; use std::{fs, sync::Arc}; @@ -284,7 +284,7 @@ fn dense_geometry_survives_verifier_transport() { evaluation, &prover, Some(hint), - &mut Blake2bTranscript::new(DOMAIN), + &mut prover_transcript::(DOMAIN, b""), ) .unwrap(); let mut encoded = serde_json::to_value(&verifier).unwrap(); @@ -297,7 +297,7 @@ fn dense_geometry_survives_verifier_transport() { evaluation, &proof, &transported, - &mut Blake2bTranscript::new(DOMAIN), + &mut prover_transcript::(DOMAIN, b""), ) .unwrap(); let mut tampered = encoded.clone(); @@ -315,7 +315,7 @@ fn dense_geometry_survives_verifier_transport() { evaluation, &proof, &tampered, - &mut Blake2bTranscript::new(DOMAIN), + &mut prover_transcript::(DOMAIN, b""), ) .unwrap_err(); assert!( @@ -331,7 +331,7 @@ fn dense_geometry_survives_verifier_transport() { evaluation, &proof, &wrong, - &mut Blake2bTranscript::new(DOMAIN) + &mut prover_transcript::(DOMAIN, b"") ) .is_err()); } diff --git a/crates/jolt-akita/tests/scheme_surface.rs b/crates/jolt-akita/tests/scheme_surface.rs index c835dbd52c..27bd06be43 100644 --- a/crates/jolt-akita/tests/scheme_surface.rs +++ b/crates/jolt-akita/tests/scheme_surface.rs @@ -7,12 +7,12 @@ mod support; use jolt_akita::{ - AkitaBackendFlavor, AkitaChunkProfile, AkitaField, AkitaScheduleArtifacts, AkitaScheme, - AkitaSetupParams, AKITA_ONE_HOT_K16, + AkitaBackendFlavor, AkitaChunkProfile, AkitaHidingCommitment, AkitaScheduleArtifacts, + AkitaScheme, AkitaSetupParams, AKITA_ONE_HOT_K16, }; use jolt_openings::{CommitmentScheme, OpeningsError, ZkOpeningScheme}; use jolt_poly::{MultilinearPoly, OneHotPolynomial}; -use jolt_transcript::{AppendToTranscript, Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, Transcript}; use support::{f, layout, polynomial, setup_for}; /// The smallest dense dimension the checked-in catalog schedules. @@ -90,14 +90,14 @@ fn single_k16_one_hot_commit_roundtrips_with_transported_verifier_setup() { let point: Vec<_> = (0..ONE_HOT_VARS).map(|index| f(index as u64 + 5)).collect(); let eval = MultilinearPoly::evaluate(&one_hot, &point); - let mut prover_transcript = Blake2bTranscript::new(b"akita-k16-transported"); + let mut prover_state = prover_transcript::(b"akita-k16-transported", b""); let proof = AkitaScheme::open( &one_hot, &point, eval, &prover_setup, Some(hint), - &mut prover_transcript, + &mut prover_state, ) .expect("one-hot opening should prove"); @@ -106,7 +106,7 @@ fn single_k16_one_hot_commit_roundtrips_with_transported_verifier_setup() { serde_json::from_str(&json).expect("verifier setup deserializes"); assert_eq!(transported, verifier_setup); - let mut verifier_transcript = Blake2bTranscript::new(b"akita-k16-transported"); + let mut verifier_transcript = prover_transcript::(b"akita-k16-transported", b""); AkitaScheme::verify( &commitment, &point, @@ -116,7 +116,10 @@ fn single_k16_one_hot_commit_roundtrips_with_transported_verifier_setup() { &mut verifier_transcript, ) .expect("transported setup must re-derive the one-hot backend key"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } #[test] @@ -127,18 +130,11 @@ fn open_without_hint_recommits_deterministically() { let eval = poly.evaluate(&point); let (commitment, _) = AkitaScheme::commit(&poly, &prover_setup).expect("commit succeeds"); - let mut prover_transcript = Blake2bTranscript::new(b"akita-no-hint"); - let proof = AkitaScheme::open( - &poly, - &point, - eval, - &prover_setup, - None, - &mut prover_transcript, - ) - .expect("hint-free opening should prove"); + let mut prover_state = prover_transcript::(b"akita-no-hint", b""); + let proof = AkitaScheme::open(&poly, &point, eval, &prover_setup, None, &mut prover_state) + .expect("hint-free opening should prove"); - let mut verifier_transcript = Blake2bTranscript::new(b"akita-no-hint"); + let mut verifier_transcript = prover_transcript::(b"akita-no-hint", b""); AkitaScheme::verify( &commitment, &point, @@ -148,7 +144,10 @@ fn open_without_hint_recommits_deterministically() { &mut verifier_transcript, ) .expect("hint-free proof should verify against the original commitment"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } #[test] @@ -159,7 +158,7 @@ fn hiding_commitment_transcript_binding_tracks_the_evaluation() { let eval = poly.evaluate(&point); let (_, hint) = AkitaScheme::commit_zk(&poly, &prover_setup).expect("commit_zk succeeds"); - let mut transcript = Blake2bTranscript::new(b"akita-hiding"); + let mut transcript = prover_transcript::(b"akita-hiding", b""); let (_, hiding, ()) = AkitaScheme::open_zk( &poly, &point, @@ -170,17 +169,19 @@ fn hiding_commitment_transcript_binding_tracks_the_evaluation() { ) .expect("open_zk should produce a hiding commitment"); - let mut first = Blake2bTranscript::::new(b"akita-hiding-bind"); - hiding.append_to_transcript(&mut first); - let mut second = Blake2bTranscript::::new(b"akita-hiding-bind"); - hiding.append_to_transcript(&mut second); - assert_eq!(first.state(), second.state(), "binding is deterministic"); + // The next squeeze after binding a hiding commitment. + let bound = |hiding: &AkitaHidingCommitment| { + let mut transcript = prover_transcript::(b"akita-hiding-bind", b""); + transcript.append(hiding); + transcript.verifier_message::<[u8; 32]>() + }; + assert_eq!(bound(&hiding), bound(&hiding), "binding is deterministic"); let mut other_point = point.clone(); other_point[DENSE_VARS - 1] += f(1); let other_eval = poly.evaluate(&other_point); assert_ne!(eval, other_eval, "fixture needs distinct evaluations"); - let mut transcript = Blake2bTranscript::new(b"akita-hiding"); + let mut transcript = prover_transcript::(b"akita-hiding", b""); let (_, other_hiding, ()) = AkitaScheme::open_zk( &poly, &other_point, @@ -190,11 +191,9 @@ fn hiding_commitment_transcript_binding_tracks_the_evaluation() { &mut transcript, ) .expect("open_zk should produce a hiding commitment"); - let mut third = Blake2bTranscript::::new(b"akita-hiding-bind"); - other_hiding.append_to_transcript(&mut third); assert_ne!( - first.state(), - third.state(), + bound(&hiding), + bound(&other_hiding), "distinct evaluations must bind distinct transcript states" ); } diff --git a/crates/jolt-blindfold/Cargo.toml b/crates/jolt-blindfold/Cargo.toml index b4e9e617cd..49e028e9c6 100644 --- a/crates/jolt-blindfold/Cargo.toml +++ b/crates/jolt-blindfold/Cargo.toml @@ -15,7 +15,8 @@ jolt-field.workspace = true jolt-poly.workspace = true jolt-r1cs.workspace = true jolt-sumcheck = { workspace = true, features = ["committed", "r1cs"] } -jolt-transcript.workspace = true +# The prover grinds its public proof-of-work nonces on a copy of the sponge. +jolt-transcript = { workspace = true, features = ["prover-grinding"] } jolt-utils.workspace = true rand_core.workspace = true rayon.workspace = true diff --git a/crates/jolt-blindfold/src/error.rs b/crates/jolt-blindfold/src/error.rs index 0554958137..11069e27c2 100644 --- a/crates/jolt-blindfold/src/error.rs +++ b/crates/jolt-blindfold/src/error.rs @@ -153,6 +153,16 @@ pub enum ProverError { WitnessVariableReassigned { index: usize }, #[error("product constraint {constraint} references unassigned operands")] UnsolvableProduct { constraint: usize }, + /// The transcript refused a challenge draw: the challenge codec does not + /// support the request, or its rejection pool ran out. + #[error("transcript rejected a challenge draw")] + TranscriptRejected, +} + +impl From for ProverError { + fn from(_: jolt_transcript::VerificationError) -> Self { + Self::TranscriptRejected + } } #[derive(Debug, ThisError)] @@ -197,4 +207,14 @@ pub enum VerificationError { OuterFinalClaimMismatch { expected: F, actual: F }, #[error("inner final claim mismatch: expected {expected}, got {actual}")] InnerFinalClaimMismatch { expected: F, actual: F }, + /// The transcript refused a challenge draw: the challenge codec does not + /// support the request, or its rejection pool ran out. + #[error("transcript rejected a challenge draw")] + TranscriptRejected, +} + +impl From for VerificationError { + fn from(_: jolt_transcript::VerificationError) -> Self { + Self::TranscriptRejected + } } diff --git a/crates/jolt-blindfold/src/pow.rs b/crates/jolt-blindfold/src/pow.rs index 62682a7abe..125f0f16f1 100644 --- a/crates/jolt-blindfold/src/pow.rs +++ b/crates/jolt-blindfold/src/pow.rs @@ -1,12 +1,12 @@ //! Proof of work before BlindFold's Fiat-Shamir draws outside its sumchecks. //! -//! Each draw is a full `Transcript::challenge` draw, so it is priced against -//! `T::CHALLENGE_SET`. The loss of a draw is the most values that can make a +//! Each draw is a full `FieldTranscript::challenge` draw, so it is priced +//! against `T::CHALLENGE_SET`. The loss of a draw is the most values that can make a //! false statement pass it, by Schwartz-Zippel over the draw. The folded //! sumcheck rounds grind inside `jolt_sumcheck` instead. use jolt_field::JoltField; -use jolt_transcript::{grind_bits_for_loss, Transcript, SECURITY_BITS}; +use jolt_transcript::{grind_bits_for_loss, FieldTranscript, SECURITY_BITS}; use crate::error::VerificationError; @@ -36,7 +36,7 @@ impl PowSite { /// Grinding bits for this draw, or `None` when the transcript's challenge /// set cannot reach the target within the proof-of-work bound. - pub(crate) fn bits(self) -> Option { + pub(crate) fn bits>(self) -> Option { let loss = match self { Self::Fold => 2, Self::OuterPoint { num_vars } => u64::try_from(num_vars).ok()?, @@ -64,13 +64,13 @@ impl<'a> PowNonces<'a> { /// # Errors /// [`VerificationError::ProofOfWorkRejected`] if the difficulty is out of /// reach, the nonce is missing, or it fails the check. - pub(crate) fn check( + pub(crate) fn check>( &mut self, site: PowSite, transcript: &mut T, ) -> Result<(), VerificationError> { let rejected = || VerificationError::ProofOfWorkRejected { site: site.name() }; - let bits = site.bits::().ok_or_else(rejected)?; + let bits = site.bits::().ok_or_else(rejected)?; let nonce = if bits == 0 { None } else { @@ -78,7 +78,9 @@ impl<'a> PowNonces<'a> { self.next += 1; Some(nonce) }; - transcript.check_grind(bits, nonce).map_err(|_| rejected()) + transcript + .check_public_grind(bits, nonce) + .map_err(|_| rejected()) } /// Rejects nonces left over after the last site. @@ -103,7 +105,7 @@ impl<'a> PowNonces<'a> { mod tests { use super::*; use jolt_field::{Fr, Prime128OffsetA7F7}; - use jolt_transcript::Blake2bTranscript; + use jolt_transcript::{prover_transcript, Blake2b512, ProverState, ProverTranscript}; const SITES: [PowSite; 3] = [ PowSite::Fold, @@ -113,7 +115,7 @@ mod tests { #[test] fn bn254_sites_read_no_nonces() { - let mut transcript = Blake2bTranscript::::new(b"blindfold-pow"); + let mut transcript = prover_transcript::(b"blindfold-pow", b""); let mut nonces = PowNonces::new(&[]); for site in SITES { nonces @@ -140,36 +142,42 @@ mod tests { #[test] fn akita_field_sites_read_one_nonce_each_in_order() { type P = Prime128OffsetA7F7; - type T = Blake2bTranscript

; - assert_eq!(SITES.map(PowSite::bits::), [Some(2), Some(4), Some(1)]); + type T = ProverState; + assert_eq!( + SITES.map(PowSite::bits::), + [Some(2), Some(4), Some(1)] + ); - let mut prover = T::new(b"blindfold-pow"); + let mut prover = prover_transcript::(b"blindfold-pow", b""); let ground = SITES.map(|site| { let nonce = prover - .grind(site.bits::().expect("difficulty in reach")) + .public_grind(site.bits::().expect("difficulty in reach")) .expect("grinding succeeds") .expect("positive difficulty takes a nonce"); - let _challenge: P = prover.challenge(); + let _challenge: P = prover.challenge().expect("P supports one challenge"); nonce }); - let mut verifier = T::new(b"blindfold-pow"); + let mut verifier = prover_transcript::(b"blindfold-pow", b""); let mut nonces = PowNonces::new(&ground); for site in SITES { nonces .check::(site, &mut verifier) .expect("site check passes"); - let _challenge: P = verifier.challenge(); + let _challenge: P = verifier.challenge().expect("P supports one challenge"); } assert!(nonces.finish::

().is_ok()); - assert_eq!(verifier.state(), prover.state()); + assert_eq!( + verifier.verifier_message::<[u8; 32]>(), + prover.verifier_message::<[u8; 32]>() + ); - let mut verifier = T::new(b"blindfold-pow"); + let mut verifier = prover_transcript::(b"blindfold-pow", b""); let mut nonces = PowNonces::new(&ground[..1]); nonces .check::(SITES[0], &mut verifier) .expect("site check passes"); - let _challenge: P = verifier.challenge(); + let _challenge: P = verifier.challenge().expect("P supports one challenge"); assert!(matches!( nonces.check::(SITES[1], &mut verifier), Err(VerificationError::ProofOfWorkRejected { diff --git a/crates/jolt-blindfold/src/protocol.rs b/crates/jolt-blindfold/src/protocol.rs index 95ec6b8a98..a50900df10 100644 --- a/crates/jolt-blindfold/src/protocol.rs +++ b/crates/jolt-blindfold/src/protocol.rs @@ -619,11 +619,7 @@ mod tests { CommittedOutputClaims, CommittedRound, CommittedSumcheckProof, SumcheckDomainSpec, SumcheckStatement, }; - use jolt_transcript::{AppendToTranscript, Blake2bTranscript, Transcript, UnpricedTranscript}; - - /// Hand-built committed proofs carry no proof of work, so these tests - /// replay them on a transcript that declares none. - type Unpriced = UnpricedTranscript>; + use jolt_transcript::{prover_transcript, Blake2b512, Encoding}; #[derive(Clone, Debug)] struct TestStage { @@ -703,7 +699,7 @@ mod tests { final_openings: Vec>, ) -> Result, VerificationError> where - Com: Clone + AppendToTranscript, + Com: Clone + Encoding, { if stages.len() != proofs.len() { return Err(VerificationError::StageCountMismatch { @@ -712,7 +708,7 @@ mod tests { }); } - let mut transcript = Unpriced::new(b"blindfold"); + let mut transcript = prover_transcript::(b"blindfold", b""); let mut next_opening_id = 0usize; let stages = stages .iter() @@ -751,7 +747,7 @@ mod tests { final_openings: Vec>, ) -> BlindFoldProtocol where - Com: Clone + AppendToTranscript, + Com: Clone + Encoding, { let statement = try_statement_from_proofs(stages, proofs, final_openings) .expect("statement builds from committed proofs"); @@ -801,7 +797,7 @@ mod tests { let statement = SumcheckStatement::new(1, 1); let proof = proof(&[(11, 1)], &[21]); - let mut transcript = Unpriced::new(b"blindfold-alias"); + let mut transcript = prover_transcript::(b"blindfold-alias", b""); let consistency = proof .verify_committed_consistency(statement, &mut transcript) .expect("committed proof is consistent"); @@ -833,7 +829,7 @@ mod tests { let statement = SumcheckStatement::new(1, 1); let proof = proof(&[(11, 1)], &[21]); - let mut transcript = Unpriced::new(b"blindfold-alias"); + let mut transcript = prover_transcript::(b"blindfold-alias", b""); let consistency = proof .verify_committed_consistency(statement, &mut transcript) .expect("committed proof is consistent"); @@ -938,7 +934,7 @@ mod tests { fn rejects_extra_output_claim_rows_without_typed_openings() { let statement = SumcheckStatement::new(1, 1); let proof = proof(&[(11, 1)], &[21, 22]); - let mut transcript = Unpriced::new(b"blindfold-output-row-count"); + let mut transcript = prover_transcript::(b"blindfold-output-row-count", b""); let consistency = proof .verify_committed_consistency(statement, &mut transcript) .expect("committed proof is consistent"); diff --git a/crates/jolt-blindfold/src/prove.rs b/crates/jolt-blindfold/src/prove.rs index 9e52d57a79..7b21c10c33 100644 --- a/crates/jolt-blindfold/src/prove.rs +++ b/crates/jolt-blindfold/src/prove.rs @@ -3,7 +3,7 @@ use jolt_field::JoltField; use jolt_poly::{BindingOrder, EqPolynomial, Polynomial, UnivariatePoly}; use jolt_r1cs::{ConstraintMatrices, ConstraintMatrixEvalError, SparseRow}; use jolt_sumcheck::{grind_round, CompressedSumcheckProof, SUMCHECK_ROUND_TRANSCRIPT_LABEL}; -use jolt_transcript::{AppendToTranscript, Label, LabelWithCount, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, Label, ProverTranscript, Transcript}; use rand_core::RngCore; use rayon::prelude::*; @@ -159,10 +159,10 @@ pub fn prove( rng: &mut R, ) -> Result, ProverError> where - F: JoltField + AppendToTranscript, + F: JoltField + Encoding, VC: VectorCommitment, - VC::Output: HomomorphicCommitment + AppendToTranscript, - T: Transcript, + VC::Output: HomomorphicCommitment + Encoding, + T: FieldTranscript + ProverTranscript, R: RngCore, { let mut row_committer = DirectBlindFoldRowCommitter; @@ -185,10 +185,10 @@ pub fn prove_with_row_committer( row_committer: &mut C, ) -> Result, ProverError> where - F: JoltField + AppendToTranscript, + F: JoltField + Encoding, VC: VectorCommitment, - VC::Output: HomomorphicCommitment + AppendToTranscript, - T: Transcript, + VC::Output: HomomorphicCommitment + Encoding, + T: FieldTranscript + ProverTranscript, R: RngCore, C: BlindFoldRowCommitter, { @@ -434,9 +434,9 @@ where &random_instance.error_row_commitments, &random_instance.eval_commitments, ); - append_values(transcript, b"bf_cross_e", &cross_term_error_row_commitments); + transcript.append_values(b"bf_cross_e", &cross_term_error_row_commitments); let fold_pow_nonce = grind(PowSite::Fold, transcript)?; - let folding_challenge = transcript.challenge(); + let folding_challenge = transcript.challenge()?; let folded_u = F::one() + folding_challenge * random_u; let folded_witness_rows = row_committer.fold_rows( @@ -557,7 +557,7 @@ where }, transcript, )?; - let tau = transcript.challenge_vector(outer_num_vars); + let tau = transcript.challenge_vector(outer_num_vars)?; let flattened_folded_witness = flatten(&folded_witness_rows); let flattened_folded_error = flatten(&folded_error_rows); let outer_trace = prove_outer_sumcheck( @@ -586,7 +586,7 @@ where "folded error row opening", )?; - append_values(transcript, b"bf_az_bz_cz", &[az_rx, bz_rx, cz_rx]); + transcript.append_values(b"bf_az_bz_cz", &[az_rx, bz_rx, cz_rx]); append_vector_opening( transcript, b"bf_error_opening", @@ -595,9 +595,9 @@ where ); let inner_combination_pow_nonce = grind(PowSite::InnerCombination, transcript)?; - let ra = transcript.challenge(); - let rb = transcript.challenge(); - let rc = transcript.challenge(); + let ra = transcript.challenge()?; + let rb = transcript.challenge()?; + let rc = transcript.challenge()?; let inner_num_vars = log2_power_of_two("witness row count", protocol.dimensions.witness.row_count)? + log2_power_of_two("witness row length", protocol.dimensions.witness.row_len)?; @@ -835,8 +835,8 @@ fn prove_outer_sumcheck( transcript: &mut T, ) -> Result, ProverError> where - F: JoltField + AppendToTranscript, - T: Transcript, + F: JoltField + Encoding, + T: FieldTranscript + ProverTranscript, { let num_vars = log2_power_of_two("outer folded R1CS sumcheck", error_values.len())?; ensure_len("outer challenge vector", num_vars, tau.len())?; @@ -907,13 +907,12 @@ where }); } let compressed = round_poly.compress(); - append_values( - transcript, + transcript.append_values( SUMCHECK_ROUND_TRANSCRIPT_LABEL, compressed.coeffs_except_linear_term(), ); grind_round(OUTER_SUMCHECK_DEGREE, round, &mut pow_nonces, transcript)?; - let challenge = transcript.challenge(); + let challenge = transcript.challenge()?; running_sum = round_poly.evaluate(challenge); az.bind_with_order(challenge, BindingOrder::HighToLow); bz.bind_with_order(challenge, BindingOrder::HighToLow); @@ -948,8 +947,8 @@ fn prove_inner_sumcheck( transcript: &mut T, ) -> Result, ProverError> where - F: JoltField + AppendToTranscript, - T: Transcript, + F: JoltField + Encoding, + T: FieldTranscript + ProverTranscript, { let witness_values = flatten(witness_rows); let num_vars = log2_power_of_two("inner folded R1CS sumcheck", witness_values.len())?; @@ -992,13 +991,9 @@ where }); } let compressed = round_poly.compress(); - append_values( - transcript, - INNER_SUMCHECK_LABEL, - compressed.coeffs_except_linear_term(), - ); + transcript.append_values(INNER_SUMCHECK_LABEL, compressed.coeffs_except_linear_term()); grind_round(INNER_SUMCHECK_DEGREE, round, &mut pow_nonces, transcript)?; - let challenge = transcript.challenge(); + let challenge = transcript.challenge()?; running_sum = round_poly.evaluate(challenge); l_w.bind_with_order(challenge, BindingOrder::HighToLow); witness.bind_with_order(challenge, BindingOrder::HighToLow); @@ -1142,37 +1137,25 @@ fn append_relaxed_instance( error_commitments: &[C], eval_commitments: &[C], ) where - F: AppendToTranscript, - C: AppendToTranscript, + F: Encoding, + C: Encoding, T: Transcript, { - transcript.append(&Label(labels.u)); - u.append_to_transcript(transcript); - append_values(transcript, labels.witness, witness_commitments); - append_values(transcript, labels.error, error_commitments); - append_values(transcript, labels.eval, eval_commitments); + transcript.append_labeled(labels.u, &u); + transcript.append_values(labels.witness, witness_commitments); + transcript.append_values(labels.error, error_commitments); + transcript.append_values(labels.eval, eval_commitments); } /// Grinds the proof-of-work nonce for `site` right before its challenge draw. fn grind(site: PowSite, transcript: &mut T) -> Result, ProverError> where F: JoltField, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let failed = || ProverError::ProofOfWork { site: site.name() }; - let bits = site.bits::().ok_or_else(failed)?; - transcript.grind(bits).map_err(|_| failed()) -} - -fn append_values(transcript: &mut T, label: &'static [u8], values: &[A]) -where - A: AppendToTranscript, - T: Transcript, -{ - transcript.append(&LabelWithCount(label, values.len() as u64)); - for value in values { - value.append_to_transcript(transcript); - } + let bits = site.bits::().ok_or_else(failed)?; + transcript.public_grind(bits).map_err(|_| failed()) } fn append_vector_opening( @@ -1181,12 +1164,11 @@ fn append_vector_opening( blinding_label: &'static [u8], opening: &VectorCommitmentOpening, ) where - F: AppendToTranscript, + F: Encoding, T: Transcript, { - append_values(transcript, row_label, &opening.combined_vector); - transcript.append(&Label(blinding_label)); - opening.combined_blinding.append_to_transcript(transcript); + transcript.append_values(row_label, &opening.combined_vector); + transcript.append_labeled(blinding_label, &opening.combined_blinding); } fn random_rows(row_count: usize, row_len: usize, rng: &mut R) -> Vec> diff --git a/crates/jolt-blindfold/src/verify.rs b/crates/jolt-blindfold/src/verify.rs index 8d7c8a95d2..3cbb55e1ab 100644 --- a/crates/jolt-blindfold/src/verify.rs +++ b/crates/jolt-blindfold/src/verify.rs @@ -3,7 +3,7 @@ use jolt_field::JoltField; use jolt_poly::EqPolynomial; use jolt_r1cs::{ConstraintMatrices, MatrixColumnContributions}; use jolt_sumcheck::{BooleanHypercube, SumcheckClaim, SUMCHECK_ROUND_TRANSCRIPT_LABEL}; -use jolt_transcript::{AppendToTranscript, Label, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, Label, Transcript}; use crate::pow::{PowNonces, PowSite}; use crate::{ @@ -17,8 +17,8 @@ const INNER_SUMCHECK_LABEL: &[u8] = b"inner_sumcheck_poly"; impl BlindFoldProtocol where - F: JoltField + AppendToTranscript, - Com: Copy + HomomorphicCommitment + AppendToTranscript, + F: JoltField + Encoding, + Com: Copy + HomomorphicCommitment + Encoding, { pub fn verify( &self, @@ -28,7 +28,7 @@ where ) -> Result<(), VerificationError> where VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { let mut nonces = PowNonces::new(&proof.pow_nonces); let folded = self.folded_instance_from_proof(proof, &mut nonces, transcript)?; @@ -65,8 +65,8 @@ where impl BlindFoldProtocol where - F: JoltField + AppendToTranscript, - Com: Clone + HomomorphicCommitment + AppendToTranscript, + F: JoltField + Encoding, + Com: Clone + HomomorphicCommitment + Encoding, { fn folded_instance_from_proof( &self, @@ -75,7 +75,7 @@ where transcript: &mut T, ) -> Result, VerificationError> where - T: Transcript, + T: FieldTranscript, { let committed = self.committed_relaxed_instance(&proof.auxiliary_row_commitments)?; committed.append_to_transcript( @@ -106,7 +106,7 @@ where transcript.append_values(b"bf_cross_e", &proof.cross_term_error_row_commitments); nonces.check(PowSite::Fold, transcript)?; - let folding_challenge = transcript.challenge(); + let folding_challenge = transcript.challenge()?; Ok(committed.fold( &random, &proof.cross_term_error_row_commitments, @@ -117,8 +117,8 @@ where impl RelaxedInstance where - F: AppendToTranscript, - Com: AppendToTranscript, + F: Encoding, + Com: Encoding, { fn append_to_transcript( &self, @@ -130,8 +130,7 @@ where ) where T: Transcript, { - transcript.append(&Label(u_label)); - self.u.append_to_transcript(transcript); + transcript.append_labeled(u_label, &self.u); transcript.append_values(witness_label, &self.witness_row_commitments); transcript.append_values(error_label, &self.error_row_commitments); transcript.append_values(eval_label, &self.eval_commitments); @@ -140,8 +139,8 @@ where impl BlindFoldProtocol where - F: JoltField + AppendToTranscript, - Com: Copy + HomomorphicCommitment + AppendToTranscript, + F: JoltField + Encoding, + Com: Copy + HomomorphicCommitment + Encoding, { fn verify_outer_folded_r1cs( &self, @@ -153,7 +152,7 @@ where ) -> Result, VerificationError> where VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { let error_row_count = self.dimensions.error.row_count; if error_row_count == 0 || !error_row_count.is_power_of_two() { @@ -187,7 +186,7 @@ where transcript.append(&Label(b"bf_spartan")); nonces.check(PowSite::OuterPoint { num_vars }, transcript)?; - let tau = transcript.challenge_vector(num_vars); + let tau = transcript.challenge_vector(num_vars)?; let claim = SumcheckClaim::new(num_vars, OUTER_SUMCHECK_DEGREE, F::zero()); let outer = proof .outer_sumcheck @@ -234,7 +233,7 @@ where impl BlindFoldProof where F: JoltField, - Com: Copy + AppendToTranscript, + Com: Copy + Encoding, { fn verify_folded_eval_commitments( &self, @@ -262,8 +261,8 @@ where impl BlindFoldProtocol where - F: JoltField + AppendToTranscript, - Com: Copy + HomomorphicCommitment + AppendToTranscript, + F: JoltField + Encoding, + Com: Copy + HomomorphicCommitment + Encoding, { fn verify_folded_eval_witness_bindings( &self, @@ -432,8 +431,8 @@ impl WitnessCoordinate { impl BlindFoldProtocol where - F: JoltField + AppendToTranscript, - Com: Copy + HomomorphicCommitment + AppendToTranscript, + F: JoltField + Encoding, + Com: Copy + HomomorphicCommitment + Encoding, { fn verify_inner_folded_r1cs( &self, @@ -446,12 +445,12 @@ where ) -> Result<(), VerificationError> where VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { nonces.check(PowSite::InnerCombination, transcript)?; - let ra = transcript.challenge(); - let rb = transcript.challenge(); - let rc = transcript.challenge(); + let ra = transcript.challenge()?; + let rb = transcript.challenge()?; + let rc = transcript.challenge()?; let public = public_contributions(&self.r1cs, &outer.point, folded.u)?; let claim = ra * (proof.az_rx - public.a) + rb * (proof.bz_rx - public.b) @@ -537,12 +536,11 @@ fn append_vector_opening( blinding_label: &'static [u8], opening: &VectorCommitmentOpening, ) where - F: AppendToTranscript, + F: Encoding, T: Transcript, { transcript.append_values(row_label, &opening.combined_vector); - transcript.append(&Label(blinding_label)); - opening.combined_blinding.append_to_transcript(transcript); + transcript.append_labeled(blinding_label, &opening.combined_blinding); } fn public_contributions( @@ -637,11 +635,7 @@ mod tests { use jolt_poly::CompressedPoly; use jolt_r1cs::ConstraintMatrices; use jolt_sumcheck::CompressedSumcheckProof; - use jolt_transcript::{Blake2bTranscript, UnpricedTranscript}; - - /// Declares no proof of work, so hand-built proofs reach the checks these - /// tests target; the proof-of-work tests use [`Blake2bTranscript`]. - type Unpriced = UnpricedTranscript>; + use jolt_transcript::{prover_transcript, Blake2b512, ProverState}; fn f(value: u64) -> Fr { Fr::from_u64(value) @@ -858,7 +852,7 @@ mod tests { fn fold_transcript( protocol: &BlindFoldProtocol, proof: &BlindFoldProof, - ) -> Blake2bTranscript { + ) -> ProverState { let committed = protocol .committed_relaxed_instance(&proof.auxiliary_row_commitments) .expect("committed instance builds"); @@ -872,7 +866,7 @@ mod tests { proof.random_u, ) .expect("random instance builds"); - let mut transcript = Blake2bTranscript::::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); committed.append_to_transcript( &mut transcript, b"bf_committed_u", @@ -891,12 +885,14 @@ mod tests { transcript } - /// The folding challenge under [`Unpriced`], which grinds nothing. + /// The folding challenge. Over `Fr` the folding draw grinds nothing. fn folding_challenge( protocol: &BlindFoldProtocol, proof: &BlindFoldProof, ) -> Fr { - fold_transcript(protocol, proof).challenge() + fold_transcript(protocol, proof) + .challenge() + .expect("Fr supports one challenge") } fn proof_with_valid_eval_opening( @@ -915,7 +911,7 @@ mod tests { let setup = setup(); let protocol = protocol(&setup); let proof = proof(&setup, &protocol); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -939,7 +935,7 @@ mod tests { let protocol = protocol_with_eval(&setup); let mut proof = proof_with_valid_eval_opening(&setup, &protocol); let _ = proof.folded_eval_outputs.pop(); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -960,7 +956,7 @@ mod tests { let protocol = coefficient_row_protocol(); let mut proof = proof(&setup, &protocol); let _ = proof.random_round_commitments.pop(); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -981,7 +977,7 @@ mod tests { let protocol = protocol(&setup); let mut proof = proof(&setup, &protocol); proof.folded_eval_outputs.push(f(7)); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -998,7 +994,7 @@ mod tests { let setup = setup(); let protocol = protocol_with_eval(&setup); let proof = proof_with_valid_eval_opening(&setup, &protocol); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let folded = protocol .folded_instance_from_proof(&proof, &mut PowNonces::new(&[]), &mut transcript) .expect("folded instance builds"); @@ -1014,7 +1010,7 @@ mod tests { let protocol = protocol_with_eval(&setup); let mut proof = proof_with_valid_eval_opening(&setup, &protocol); proof.folded_eval_outputs[0] += f(1); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -1032,7 +1028,7 @@ mod tests { let protocol = outer_round_protocol(); let mut proof = proof(&setup, &protocol); let _ = proof.outer_sumcheck.round_polynomials.pop(); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -1053,7 +1049,7 @@ mod tests { let mut proof = proof(&setup, &protocol); proof.outer_sumcheck.round_polynomials[0] = CompressedPoly::new(vec![f(0), f(0), f(0), f(0)]); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -1073,7 +1069,7 @@ mod tests { let protocol = outer_round_protocol(); let mut proof = proof(&setup, &protocol); proof.error_opening.combined_blinding = f(1); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -1092,7 +1088,7 @@ mod tests { let mut proof = proof(&setup, &protocol); proof.az_rx = f(1); proof.bz_rx = f(1); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -1109,7 +1105,7 @@ mod tests { let setup = setup(); let protocol = inner_round_protocol(); let proof = proof(&setup, &protocol); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -1133,7 +1129,7 @@ mod tests { let mut proof = proof(&setup, &protocol); add_zero_inner_round(&mut proof); proof.witness_opening.combined_blinding = f(1); - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) @@ -1159,7 +1155,7 @@ mod tests { combined_vector: vec![f(5)], combined_blinding: f(50), }; - let mut transcript = Unpriced::new(b"blindfold-verify"); + let mut transcript = prover_transcript::(b"blindfold-verify", b""); let error = protocol .verify::, _>(&proof, &setup, &mut transcript) diff --git a/crates/jolt-blindfold/tests/assignment_pipeline.rs b/crates/jolt-blindfold/tests/assignment_pipeline.rs index 88b07741f7..d0241070af 100644 --- a/crates/jolt-blindfold/tests/assignment_pipeline.rs +++ b/crates/jolt-blindfold/tests/assignment_pipeline.rs @@ -6,7 +6,7 @@ use jolt_blindfold::{BlindFoldProtocol, BlindFoldWitness, ProverError}; use jolt_claims::{constant, opening}; use jolt_crypto::{Bn254G1, JoltGroup, PedersenSetup, VectorCommitment}; use jolt_sumcheck::{CommittedSumcheckWitness, SumcheckDomainSpec, SumcheckStatement}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; use rand_chacha::ChaCha20Rng; use rand_core::{RngCore, SeedableRng}; use support::*; @@ -44,7 +44,7 @@ fn assignment_fixture(rng: &mut impl RngCore) -> AssignmentFixture { let (stage1, stage2) = { let mut prover = SumcheckTestProver::new(&mut *rng); - let mut transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); + let mut transcript = prover_transcript::(TRANSCRIPT_LABEL, b""); let stage1 = prover.prove_stage_with_output_claims(&setup, &mut transcript, statement1, input1, 2); let stage2 = @@ -57,7 +57,7 @@ fn assignment_fixture(rng: &mut impl RngCore) -> AssignmentFixture { let eval_blindings = vec![rng_field(rng)]; let eval_commitment = VC::commit(&setup, &[eval_outputs[0]], &eval_blindings[0]); - let mut transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); + let mut transcript = prover_transcript::(TRANSCRIPT_LABEL, b""); let stage1_consistency = stage1 .proof .verify_committed_consistency(statement1, &mut transcript) @@ -121,7 +121,7 @@ fn product_assignment_fixture(rng: &mut impl RngCore) -> AssignmentFixture { let (stage1, stage2) = { let mut prover = SumcheckTestProver::new(&mut *rng); - let mut transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); + let mut transcript = prover_transcript::(TRANSCRIPT_LABEL, b""); let stage1 = prover.prove_stage_with_output_claims(&setup, &mut transcript, statement1, input1, 2); // Stage 2 opens on the product of two stage-1 output-claim entries — @@ -137,7 +137,7 @@ fn product_assignment_fixture(rng: &mut impl RngCore) -> AssignmentFixture { let eval_blindings = vec![rng_field(rng)]; let eval_commitment = VC::commit(&setup, &[eval_outputs[0]], &eval_blindings[0]); - let mut transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); + let mut transcript = prover_transcript::(TRANSCRIPT_LABEL, b""); let stage1_consistency = stage1 .proof .verify_committed_consistency(statement1, &mut transcript) @@ -212,12 +212,12 @@ fn assigned_witness_proves_and_verifies_through_the_real_prover() { .iter() .all(|row| row.len() == dimensions.witness.row_len)); - let mut prover_transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); - append_protocol_transcript_prefix(&fixture.protocol, &mut prover_transcript); + let mut prover_state = prover_transcript::(TRANSCRIPT_LABEL, b""); + append_protocol_transcript_prefix(&fixture.protocol, &mut prover_state); let proof = jolt_blindfold::prove::( &fixture.setup, &fixture.protocol, - &mut prover_transcript, + &mut prover_state, BlindFoldWitness { rows: &assigned.rows, blindings: &assigned.blindings, @@ -228,7 +228,7 @@ fn assigned_witness_proves_and_verifies_through_the_real_prover() { ) .expect("assigned witness proves"); - let mut verifier_transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); + let mut verifier_transcript = prover_transcript::(TRANSCRIPT_LABEL, b""); append_protocol_transcript_prefix(&fixture.protocol, &mut verifier_transcript); fixture .protocol @@ -257,12 +257,12 @@ fn product_auxiliaries_solve_and_prove_through_the_real_prover() { ) .expect("product witness assigns"); - let mut prover_transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); - append_protocol_transcript_prefix(&fixture.protocol, &mut prover_transcript); + let mut prover_state = prover_transcript::(TRANSCRIPT_LABEL, b""); + append_protocol_transcript_prefix(&fixture.protocol, &mut prover_state); let proof = jolt_blindfold::prove::( &fixture.setup, &fixture.protocol, - &mut prover_transcript, + &mut prover_state, BlindFoldWitness { rows: &assigned.rows, blindings: &assigned.blindings, @@ -273,7 +273,7 @@ fn product_auxiliaries_solve_and_prove_through_the_real_prover() { ) .expect("product witness proves"); - let mut verifier_transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); + let mut verifier_transcript = prover_transcript::(TRANSCRIPT_LABEL, b""); append_protocol_transcript_prefix(&fixture.protocol, &mut verifier_transcript); fixture .protocol @@ -407,12 +407,12 @@ fn final_opening_rows_stay_hidden_from_public_proof_data() { ) .expect("witness assigns"); - let mut prover_transcript = Blake2bTranscript::::new(TRANSCRIPT_LABEL); - append_protocol_transcript_prefix(&fixture.protocol, &mut prover_transcript); + let mut prover_state = prover_transcript::(TRANSCRIPT_LABEL, b""); + append_protocol_transcript_prefix(&fixture.protocol, &mut prover_state); let proof = jolt_blindfold::prove::( &fixture.setup, &fixture.protocol, - &mut prover_transcript, + &mut prover_state, BlindFoldWitness { rows: &assigned.rows, blindings: &assigned.blindings, diff --git a/crates/jolt-blindfold/tests/proof_pipeline.rs b/crates/jolt-blindfold/tests/proof_pipeline.rs index af1e3e6058..f891599dec 100644 --- a/crates/jolt-blindfold/tests/proof_pipeline.rs +++ b/crates/jolt-blindfold/tests/proof_pipeline.rs @@ -4,7 +4,7 @@ mod support; use jolt_blindfold::VerificationError; use jolt_poly::CompressedPoly; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; use support::*; @@ -12,7 +12,7 @@ use support::*; fn verify_blindfold_protocol_pipeline( full: &BlindFoldTestProof, ) -> Result<(), VerificationError> { - let mut transcript = Blake2bTranscript::::new(b"protocol-backed-blindfold-proof"); + let mut transcript = prover_transcript::(b"protocol-backed-blindfold-proof", b""); append_protocol_transcript_prefix(&full.protocol, &mut transcript); full.protocol .verify::(&full.proof, &full.setup, &mut transcript) @@ -144,7 +144,7 @@ fn blindfold_protocol_pipeline_rejects_tampered_error_opening_blinding() { fn blindfold_protocol_pipeline_rejects_wrong_transcript() { let mut rng = ChaCha20Rng::from_seed([76; 32]); let full = prove_blindfold_protocol_pipeline(&mut rng); - let mut transcript = Blake2bTranscript::::new(b"wrong-transcript"); + let mut transcript = prover_transcript::(b"wrong-transcript", b""); append_protocol_transcript_prefix(&full.protocol, &mut transcript); assert!(full diff --git a/crates/jolt-blindfold/tests/r1cs_pipeline.rs b/crates/jolt-blindfold/tests/r1cs_pipeline.rs index 37d31e20c7..65602a44ca 100644 --- a/crates/jolt-blindfold/tests/r1cs_pipeline.rs +++ b/crates/jolt-blindfold/tests/r1cs_pipeline.rs @@ -3,7 +3,7 @@ mod support; use jolt_sumcheck::{grind_round, RoundMessage}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, FieldTranscript}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; use support::*; @@ -17,11 +17,11 @@ fn r1cs_pipeline_rejects_late_stage_mismatch() { // Regrind the swapped rounds' proof of work at stage 3's transcript // position, so the mismatch reaches the R1CS check under test. - let mut transcript = Blake2bTranscript::::new(b"blindfold-r1cs-e2e"); + let mut transcript = prover_transcript::(b"blindfold-r1cs-e2e", b""); for stage in [&stage1, &stage2] { let _consistency = stage .proof - .verify_committed_consistency(stage.statement, &mut transcript) + .verify_committed_consistency::(stage.statement, &mut transcript) .expect("honest stage verifies"); } let mut pow_nonces = Vec::new(); @@ -34,7 +34,7 @@ fn r1cs_pipeline_rejects_late_stage_mismatch() { &mut transcript, ) .expect("proof of work"); - let _challenge: F = transcript.challenge(); + let _challenge: F = transcript.challenge().expect("challenge draw"); } tampered_stage3.proof.pow_nonces = pow_nonces; diff --git a/crates/jolt-blindfold/tests/real_prover.rs b/crates/jolt-blindfold/tests/real_prover.rs index 52a1649794..0db05d0340 100644 --- a/crates/jolt-blindfold/tests/real_prover.rs +++ b/crates/jolt-blindfold/tests/real_prover.rs @@ -9,7 +9,7 @@ mod support; use jolt_blindfold::{prove, BlindFoldProof, BlindFoldWitness, ProverError, VerificationError}; use jolt_crypto::Bn254G1; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; use support::*; @@ -18,7 +18,7 @@ fn prove_real( instance: &ProtocolBackedInstance, rng: &mut ChaCha20Rng, ) -> Result, ProverError> { - let mut transcript = Blake2bTranscript::::new(PROTOCOL_BACKED_TRANSCRIPT_LABEL); + let mut transcript = prover_transcript::(PROTOCOL_BACKED_TRANSCRIPT_LABEL, b""); append_protocol_transcript_prefix(&instance.protocol, &mut transcript); prove::( &instance.setup, @@ -38,7 +38,7 @@ fn verify_real( instance: &ProtocolBackedInstance, proof: &BlindFoldProof, ) -> Result<(), VerificationError> { - let mut transcript = Blake2bTranscript::::new(PROTOCOL_BACKED_TRANSCRIPT_LABEL); + let mut transcript = prover_transcript::(PROTOCOL_BACKED_TRANSCRIPT_LABEL, b""); append_protocol_transcript_prefix(&instance.protocol, &mut transcript); instance .protocol diff --git a/crates/jolt-blindfold/tests/sumcheck_pipeline.rs b/crates/jolt-blindfold/tests/sumcheck_pipeline.rs index 5baa60f38f..6b6b34a534 100644 --- a/crates/jolt-blindfold/tests/sumcheck_pipeline.rs +++ b/crates/jolt-blindfold/tests/sumcheck_pipeline.rs @@ -4,7 +4,7 @@ mod support; use jolt_crypto::VectorCommitment; use jolt_sumcheck::RoundMessage; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, FieldTranscript}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; use support::*; @@ -80,9 +80,9 @@ fn committed_round_blinding_is_empirically_independent_from_commitments_and_chal for _ in 0..SAMPLES { let blinding = rng_field(&mut rng); let round = commit_round_with_blinding(&setup, coefficients.clone(), blinding); - let mut transcript = Blake2bTranscript::::new(b"blindfold-r1cs-independence"); + let mut transcript = prover_transcript::(b"blindfold-r1cs-independence", b""); round.append_to_transcript(&mut transcript); - let challenge = transcript.challenge(); + let challenge = transcript.challenge().expect("challenge draw"); assert!(VC::verify( &setup, diff --git a/crates/jolt-blindfold/tests/support/mod.rs b/crates/jolt-blindfold/tests/support/mod.rs index dac4548908..e0c8666f7e 100644 --- a/crates/jolt-blindfold/tests/support/mod.rs +++ b/crates/jolt-blindfold/tests/support/mod.rs @@ -22,7 +22,8 @@ use jolt_sumcheck::{ SumcheckDomainSpec, SumcheckR1csLayout, SumcheckStatement, SUMCHECK_ROUND_TRANSCRIPT_LABEL, }; use jolt_transcript::{ - grind_bits_for_loss, AppendToTranscript, Blake2bTranscript, Label, Transcript, SECURITY_BITS, + grind_bits_for_loss, prover_transcript, Blake2b512, ChallengeCodec, Encoding, FieldTranscript, + Label, ProverState, ProverTranscript, Transcript, SECURITY_BITS, }; use rand_core::RngCore; @@ -154,17 +155,16 @@ pub fn field_low_u64(value: F) -> u64 { ]) } -pub fn transcript_projection(label: &'static [u8], value: &A) -> u64 { - let mut transcript = Blake2bTranscript::::new(b"blindfold-statistical-projection"); - transcript.append(&Label(label)); - value.append_to_transcript(&mut transcript); - field_low_u64(transcript.challenge()) +pub fn transcript_projection(label: &'static [u8], value: &A) -> u64 { + let mut transcript = prover_transcript::(b"blindfold-statistical-projection", b""); + transcript.append_labeled(label, &value); + field_low_u64(transcript.challenge().expect("challenge draw")) } pub fn field_slice_projection(label: &'static [u8], values: &[F]) -> u64 { - let mut transcript = Blake2bTranscript::::new(b"blindfold-statistical-projection"); + let mut transcript = prover_transcript::(b"blindfold-statistical-projection", b""); transcript.append_values(label, values); - field_low_u64(transcript.challenge()) + field_low_u64(transcript.challenge().expect("challenge draw")) } pub fn compressed_sumcheck_projection( @@ -386,7 +386,7 @@ impl SumcheckTestProver { pub fn prove_stage( &mut self, setup: &PedersenSetup, - transcript: &mut Blake2bTranscript, + transcript: &mut ProverState, statement: SumcheckStatement, input_claim: F, ) -> GeneratedStage { @@ -396,7 +396,7 @@ impl SumcheckTestProver { pub fn prove_stage_with_output_claims( &mut self, setup: &PedersenSetup, - transcript: &mut Blake2bTranscript, + transcript: &mut ProverState, statement: SumcheckStatement, input_claim: F, output_claim_count: usize, @@ -416,7 +416,7 @@ impl SumcheckTestProver { round.append_to_transcript(transcript); grind_round::(statement.degree, round_index, &mut pow_nonces, transcript) .expect("proof of work"); - let challenge = transcript.challenge(); + let challenge = transcript.challenge().expect("challenge draw"); claim = eval_poly(&round_coefficients, challenge); rounds.push(round); @@ -439,7 +439,7 @@ impl SumcheckTestProver { let output_claims = CommittedOutputClaims { commitments: output_commitments, }; - output_claims.append_to_transcript(transcript); + transcript.append(&output_claims); GeneratedStage { statement, @@ -464,7 +464,7 @@ impl SumcheckTestProver { statement: SumcheckStatement, input_claim: F, ) -> GeneratedStage { - let mut transcript = Blake2bTranscript::::new(transcript_label); + let mut transcript = prover_transcript::(transcript_label, b""); self.prove_stage(setup, &mut transcript, statement, input_claim) } } @@ -474,13 +474,13 @@ pub fn generate_zero_stage(setup: &PedersenSetup, num_vars: usize) -> G .map(|round| commit_round(setup, vec![f(0)], round)) .collect(); // Ground on the transcript `stage_consistency` replays. - let mut transcript = Blake2bTranscript::::new(b"blindfold-r1cs-e2e"); + let mut transcript = prover_transcript::(b"blindfold-r1cs-e2e", b""); let mut pow_nonces = Vec::with_capacity(num_vars); for (round_index, round) in rounds.iter().enumerate() { round.append_to_transcript(&mut transcript); grind_round::(1, round_index, &mut pow_nonces, &mut transcript) .expect("proof of work"); - let _challenge: F = transcript.challenge(); + let _challenge: F = transcript.challenge().expect("challenge draw"); } GeneratedStage { statement: SumcheckStatement::new(num_vars, 1), @@ -502,7 +502,7 @@ pub fn stage_consistency( statement: SumcheckStatement, proof: &CommittedSumcheckProof, ) -> CommittedSumcheckConsistency { - let mut transcript = Blake2bTranscript::::new(b"blindfold-r1cs-e2e"); + let mut transcript = prover_transcript::(b"blindfold-r1cs-e2e", b""); proof .verify_committed_consistency(statement, &mut transcript) .expect("committed proof transcript verifies") @@ -518,7 +518,7 @@ pub fn stage_consistency_for_transcript_label( transcript_label: &'static [u8], stages: &[&GeneratedStage], ) -> Vec> { - let mut transcript = Blake2bTranscript::::new(transcript_label); + let mut transcript = prover_transcript::(transcript_label, b""); stages .iter() .map(|stage| { @@ -546,7 +546,7 @@ where stages.len(), "relations and generated stages must align" ); - let mut transcript = Blake2bTranscript::::new(transcript_label); + let mut transcript = prover_transcript::(transcript_label, b""); let stages = relations .iter() .zip(stages) @@ -729,7 +729,7 @@ pub fn generated_deep_triple( let setup = pedersen_setup(4); let statement = SumcheckStatement::new(4, 3); let mut values = deep_values_without_links(); - let mut transcript = Blake2bTranscript::::new(b"blindfold-r1cs-e2e"); + let mut transcript = prover_transcript::(b"blindfold-r1cs-e2e", b""); let stage1 = prover.prove_stage( &setup, &mut transcript, @@ -808,7 +808,7 @@ pub fn build_protocol_backed_instance_with_bindings( let (stage1, stage2) = { let mut prover = SumcheckTestProver::new(&mut *rng); - let mut transcript = Blake2bTranscript::::new(transcript_label); + let mut transcript = prover_transcript::(transcript_label, b""); let stage1 = prover.prove_stage_with_output_claims(&setup, &mut transcript, statement1, input1, 2); let stage2 = @@ -837,7 +837,7 @@ pub fn build_protocol_backed_instance_with_bindings( .map(|(&output, blinding)| VC::commit(&setup, &[output], blinding)) .collect::>(); - let mut transcript = Blake2bTranscript::::new(transcript_label); + let mut transcript = prover_transcript::(transcript_label, b""); let stage1_consistency = stage1 .proof .verify_committed_consistency(stage1.statement, &mut transcript) @@ -908,7 +908,7 @@ pub fn build_protocol_backed_instance_with_bindings( pub fn prove_blindfold_protocol_pipeline(rng: &mut R) -> BlindFoldTestProof { let instance = build_protocol_backed_instance(rng); - let mut transcript = Blake2bTranscript::::new(PROTOCOL_BACKED_TRANSCRIPT_LABEL); + let mut transcript = prover_transcript::(PROTOCOL_BACKED_TRANSCRIPT_LABEL, b""); append_protocol_transcript_prefix(&instance.protocol, &mut transcript); let witness = ProtocolWitness { rows: &instance.rows, @@ -968,7 +968,7 @@ where pub fn append_protocol_transcript_prefix( protocol: &BlindFoldProtocol, - transcript: &mut Blake2bTranscript, + transcript: &mut ProverState, ) { for (stage, output_claims) in protocol .sumcheck_consistency @@ -981,9 +981,9 @@ pub fn append_protocol_transcript_prefix( degree: round.degree, } .append_to_transcript(transcript); - let _ = transcript.challenge(); + let _: F = transcript.challenge().expect("challenge draw"); } - output_claims.append_to_transcript(transcript); + transcript.append(&output_claims); } } @@ -1119,7 +1119,7 @@ struct ProtocolWitness<'a> { fn prove_from_protocol_witness( setup: &PedersenSetup, protocol: &BlindFoldProtocol, - transcript: &mut Blake2bTranscript, + transcript: &mut ProverState, witness: ProtocolWitness<'_>, rng: &mut R, ) -> BlindFoldProof { @@ -1282,7 +1282,7 @@ fn prove_from_protocol_witness( transcript.append_values(b"bf_cross_e", &cross_term_error_row_commitments); // A bad fold survives at most 2 values of r: E₁ + r·T + r²·E₂. let fold_pow_nonce = grind_draw(transcript, 2); - let folding_challenge = transcript.challenge(); + let folding_challenge = transcript.challenge().expect("challenge draw"); let folded_u = f(1) + folding_challenge * random_u; let folded_witness_rows = fold_rows(witness.rows, &random_witness_rows, folding_challenge); @@ -1363,7 +1363,9 @@ fn prove_from_protocol_witness( log2(protocol.dimensions.error.row_count) + log2(protocol.dimensions.error.row_len); // The outer zero-check sum is multilinear in τ. let outer_point_pow_nonce = grind_draw(transcript, outer_num_vars as u64); - let tau = transcript.challenge_vector(outer_num_vars); + let tau = transcript + .challenge_vector(outer_num_vars) + .expect("challenge draw"); let outer_trace = prove_slow_sumcheck( outer_num_vars, 3, @@ -1409,9 +1411,9 @@ fn prove_from_protocol_witness( ); let inner_combination_pow_nonce = grind_draw(transcript, 1); - let ra = transcript.challenge(); - let rb = transcript.challenge(); - let rc = transcript.challenge(); + let ra = transcript.challenge().expect("challenge draw"); + let rb = transcript.challenge().expect("challenge draw"); + let rc = transcript.challenge().expect("challenge draw"); let inner_num_vars = log2(protocol.dimensions.witness.row_count) + log2(protocol.dimensions.witness.row_len); let row_weights = EqPolynomial::::evals(&outer_trace.point, None); @@ -1523,29 +1525,27 @@ struct RelaxedInstanceLabels { } fn append_relaxed_instance_from_parts( - transcript: &mut Blake2bTranscript, + transcript: &mut ProverState, labels: RelaxedInstanceLabels, u: F, witness_commitments: &[Bn254G1], error_commitments: &[Bn254G1], eval_commitments: &[Bn254G1], ) { - transcript.append(&Label(labels.u)); - u.append_to_transcript(transcript); + transcript.append_labeled(labels.u, &u); transcript.append_values(labels.witness, witness_commitments); transcript.append_values(labels.error, error_commitments); transcript.append_values(labels.eval, eval_commitments); } fn append_vector_opening( - transcript: &mut Blake2bTranscript, + transcript: &mut ProverState, row_label: &'static [u8], blinding_label: &'static [u8], opening: &jolt_crypto::VectorCommitmentOpening, ) { transcript.append_values(row_label, &opening.combined_vector); - transcript.append(&Label(blinding_label)); - opening.combined_blinding.append_to_transcript(transcript); + transcript.append_labeled(blinding_label, &opening.combined_blinding); } fn zero_rows(row_count: usize, row_len: usize) -> Vec> { @@ -1736,7 +1736,7 @@ fn prove_slow_sumcheck( degree: usize, claim: F, label: &'static [u8], - transcript: &mut Blake2bTranscript, + transcript: &mut ProverState, eval: impl Fn(&[F]) -> F, ) -> SumcheckTrace { let mut running_sum = claim; @@ -1768,7 +1768,7 @@ fn prove_slow_sumcheck( compressed.extend_from_slice(&coefficients[2..]); transcript.append_values(label, &compressed); grind_round::(degree, round, &mut pow_nonces, transcript).expect("proof of work"); - let challenge = transcript.challenge(); + let challenge = transcript.challenge().expect("challenge draw"); running_sum = eval_poly(&coefficients, challenge); prefix.push(challenge); rounds.push(CompressedPoly::new(compressed)); @@ -1784,11 +1784,10 @@ fn prove_slow_sumcheck( } /// Grinds the nonce an honest prover sends before a challenge draw of loss -/// `loss` over `Transcript::challenge`. -fn grind_draw(transcript: &mut Blake2bTranscript, loss: u64) -> Option { - let bits = grind_bits_for_loss(loss, Blake2bTranscript::::CHALLENGE_SET, SECURITY_BITS) - .expect("difficulty"); - transcript.grind(bits).expect("proof of work") +/// `loss` over `FieldTranscript::challenge`. +fn grind_draw(transcript: &mut ProverState, loss: u64) -> Option { + let bits = grind_bits_for_loss(loss, F::CHALLENGE_SET_SIZE, SECURITY_BITS).expect("difficulty"); + transcript.public_grind(bits).expect("proof of work") } fn interpolate_zero_to_degree(values: &[F]) -> Vec { diff --git a/crates/jolt-claims-derive/src/lib.rs b/crates/jolt-claims-derive/src/lib.rs index a900a516ba..831c557caf 100644 --- a/crates/jolt-claims-derive/src/lib.rs +++ b/crates/jolt-claims-derive/src/lib.rs @@ -70,7 +70,7 @@ //! (one drawn Fiat-Shamir scalar). A `Vec` field is rejected (challenge sub-enum //! variants are unit, so there is no indexed id), and an `Option` field is //! rejected (no relation draws a conditional challenge, and the `draw_challenges` -//! default treats every field as one unconditional `challenge_scalar`). +//! default treats every field as one unconditional `challenge`). //! //! Generates both halves of [`SumcheckChallenges`]: `resolve_challenge` (id → //! value) and `from_transcript_values` (consume one drawn scalar per field in @@ -766,7 +766,7 @@ fn plan_challenge_field(field: &Field) -> Result { &field.ty, "challenge fields are an unconditional scalar `F`; a conditional \ `Option` challenge is not supported (no relation draws one, and the \ - `draw_challenges` default treats every field as one `challenge_scalar`)", + `draw_challenges` default treats every field as one `challenge`)", )); } Ok(ChallengeFieldPlan { ident, path }) diff --git a/crates/jolt-claims-derive/src/tests.rs b/crates/jolt-claims-derive/src/tests.rs index 7e227fd457..c64e394bb8 100644 --- a/crates/jolt-claims-derive/src/tests.rs +++ b/crates/jolt-claims-derive/src/tests.rs @@ -249,7 +249,7 @@ fn challenges_reject_option_fields() { tau: Option, } }, - "challenge fields are an unconditional scalar `F`; a conditional `Option` challenge is not supported (no relation draws one, and the `draw_challenges` default treats every field as one `challenge_scalar`)", + "challenge fields are an unconditional scalar `F`; a conditional `Option` challenge is not supported (no relation draws one, and the `draw_challenges` default treats every field as one `challenge`)", ); } diff --git a/crates/jolt-claims/src/protocols/jolt/geometry/bytecode.rs b/crates/jolt-claims/src/protocols/jolt/geometry/bytecode.rs index 0af4c37864..8db32b44d0 100644 --- a/crates/jolt-claims/src/protocols/jolt/geometry/bytecode.rs +++ b/crates/jolt-claims/src/protocols/jolt/geometry/bytecode.rs @@ -24,7 +24,7 @@ use super::registers::{ use super::spartan::{pc_shift, unexpanded_pc_shift}; /// Per-stage (1..=5) gamma-power vector lengths for the bytecode read-RAF stage -/// folds — the arities of the prover's `challenge_scalar_powers` draws. The +/// folds — the arities of the prover's `challenge_powers` draws. The /// verifier stores each stage's single drawn scalar and expands it with /// [`stage_gamma_powers`], so these lengths are single-sourced with the /// fold-side `require_len` guards. diff --git a/crates/jolt-claims/src/protocols/jolt/relations/bytecode/read_raf_address_phase.rs b/crates/jolt-claims/src/protocols/jolt/relations/bytecode/read_raf_address_phase.rs index 701067c5d0..7fa4dd3d53 100644 --- a/crates/jolt-claims/src/protocols/jolt/relations/bytecode/read_raf_address_phase.rs +++ b/crates/jolt-claims/src/protocols/jolt/relations/bytecode/read_raf_address_phase.rs @@ -167,7 +167,7 @@ pub struct BytecodeReadRafAddressPhaseChallenges { impl BytecodeReadRafAddressPhaseChallenges { /// Expand the five drawn per-stage scalars into the gamma-power vectors the /// bytecode folds consume (`[1, γ, γ², …]` — the recurrence the prover's - /// `challenge_scalar_powers` applies to its single squeezed scalar), sized + /// `challenge_powers` applies to its single squeezed scalar), sized /// by [`BYTECODE_STAGE_GAMMA_COUNTS`]. pub fn stage_gamma_powers(&self) -> [Vec; 5] { let stage_gammas = [ diff --git a/crates/jolt-dory/benches/akita_paths.rs b/crates/jolt-dory/benches/akita_paths.rs index eb815de8aa..9fe44bdc20 100644 --- a/crates/jolt-dory/benches/akita_paths.rs +++ b/crates/jolt-dory/benches/akita_paths.rs @@ -55,7 +55,7 @@ use jolt_openings::{ VerifierOpeningClaim, }; use jolt_poly::{MultilinearPoly, OneHotPolynomial, Polynomial}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; const LAYOUT_DIGEST: [u8; 32] = [0xA5; 32]; const NUM_POLYS: usize = 1; @@ -494,7 +494,7 @@ fn native_batch_open( commitment: jolt_akita::AkitaCommitment, hint: AkitaProverHint, ) -> jolt_akita::AkitaBatchProof { - let mut transcript = Blake2bTranscript::new(b"jolt-akita/black-box-batch-bench"); + let mut transcript = prover_transcript::(b"jolt-akita/black-box-batch-bench", b""); ::prove_batch( &case.native_setup, native_batch_statement(case, commitment), @@ -513,7 +513,7 @@ fn packed_batch_open( case: &AkitaBatchCase, hint: AkitaProverHint, ) -> ::Proof { - let mut transcript = Blake2bTranscript::new(b"jolt-akita/packed-batch-bench"); + let mut transcript = prover_transcript::(b"jolt-akita/packed-batch-bench", b""); let physical = case .packing .reduce_claims(&case.packed_claims, &mut transcript) @@ -554,7 +554,7 @@ fn wrapper_open( path: DataPath, hint: AkitaProverHint, ) -> jolt_akita::AkitaBatchProof { - let mut transcript = Blake2bTranscript::new(b"jolt-akita/bench"); + let mut transcript = prover_transcript::(b"jolt-akita/bench", b""); match path { DataPath::DenseData => AkitaScheme::open( &case.dense_poly, @@ -699,7 +699,7 @@ fn dory_commit(case: &DoryCase, path: DataPath) -> (DoryCommitment, DoryHint) { } fn dory_open(case: &DoryCase, path: DataPath, hint: DoryHint) -> jolt_dory::DoryProof { - let mut transcript = Blake2bTranscript::new(b"jolt-akita/dory-baseline-bench"); + let mut transcript = prover_transcript::(b"jolt-akita/dory-baseline-bench", b""); match path { DataPath::DenseData => DoryScheme::open( &case.dense_poly, diff --git a/crates/jolt-dory/benches/dory.rs b/crates/jolt-dory/benches/dory.rs index d4e474f01b..6d0c4c21e0 100644 --- a/crates/jolt-dory/benches/dory.rs +++ b/crates/jolt-dory/benches/dory.rs @@ -10,7 +10,7 @@ use jolt_dory::{DoryScheme, DoryVerifierSetup}; use jolt_field::{Field, Fr}; use jolt_openings::{CommitmentScheme, StreamingCommitment, ZkOpeningScheme}; use jolt_poly::{OneHotPolynomial, Polynomial}; -use jolt_transcript::Transcript; +use jolt_transcript::Blake2b512; use rand_chacha::ChaCha20Rng; use rand_core::{RngCore, SeedableRng}; @@ -68,7 +68,8 @@ fn bench_open(c: &mut Criterion) { (poly, point, eval) }, |(poly, point, eval)| { - let mut transcript = jolt_transcript::Blake2bTranscript::new(b"bench-open"); + let mut transcript = + jolt_transcript::prover_transcript::(b"bench-open", b""); DoryScheme::open(&poly, &point, eval, &setup, None, &mut transcript) }, criterion::BatchSize::SmallInput, @@ -98,7 +99,7 @@ fn bench_verify(c: &mut Criterion) { let (commitment, _) = DoryScheme::commit(poly.evaluations(), &setup).unwrap(); let mut transcript = - jolt_transcript::Blake2bTranscript::new(b"bench-verify"); + jolt_transcript::prover_transcript::(b"bench-verify", b""); let proof = DoryScheme::open(&poly, &point, eval, &setup, None, &mut transcript) .unwrap(); @@ -106,7 +107,7 @@ fn bench_verify(c: &mut Criterion) { }, |(commitment, point, eval, proof)| { let mut transcript = - jolt_transcript::Blake2bTranscript::new(b"bench-verify"); + jolt_transcript::prover_transcript::(b"bench-verify", b""); DoryScheme::verify( &commitment, &point, @@ -237,7 +238,7 @@ fn bench_open_zk(c: &mut Criterion) { }, |(poly, point, eval, hint)| { let mut transcript = - jolt_transcript::Blake2bTranscript::new(b"bench-open-zk"); + jolt_transcript::prover_transcript::(b"bench-open-zk", b""); DoryScheme::open_zk(&poly, &point, eval, &setup, hint, &mut transcript) }, criterion::BatchSize::SmallInput, @@ -267,16 +268,20 @@ fn bench_verify_zk(c: &mut Criterion) { let (commitment, hint) = ::commit_zk(poly.evaluations(), &setup) .unwrap(); - let mut transcript = - jolt_transcript::Blake2bTranscript::new(b"bench-verify-zk"); + let mut transcript = jolt_transcript::prover_transcript::( + b"bench-verify-zk", + b"", + ); let (proof, _eval_com, _blind) = DoryScheme::open_zk(&poly, &point, eval, &setup, hint, &mut transcript) .unwrap(); (commitment, point, proof) }, |(commitment, point, proof)| { - let mut transcript = - jolt_transcript::Blake2bTranscript::new(b"bench-verify-zk"); + let mut transcript = jolt_transcript::prover_transcript::( + b"bench-verify-zk", + b"", + ); DoryScheme::verify_zk( &commitment, &point, diff --git a/crates/jolt-dory/fuzz/fuzz_targets/verify_tampered.rs b/crates/jolt-dory/fuzz/fuzz_targets/verify_tampered.rs index f38851c1ac..c63b86193d 100644 --- a/crates/jolt-dory/fuzz/fuzz_targets/verify_tampered.rs +++ b/crates/jolt-dory/fuzz/fuzz_targets/verify_tampered.rs @@ -16,7 +16,7 @@ use jolt_dory::{DoryCommitment, DoryProof, DoryScheme, DoryVerifierSetup}; use jolt_field::{Field, Fr}; use jolt_openings::CommitmentScheme; use jolt_poly::Polynomial; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, Transcript}; use libfuzzer_sys::fuzz_target; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; @@ -44,11 +44,11 @@ fn fixture() -> &'static Fixture { let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup).expect("fixture commit"); - let mut pt = Blake2bTranscript::new(TRANSCRIPT_LABEL); + let mut pt = prover_transcript::(TRANSCRIPT_LABEL, b""); let proof = DoryScheme::open(&poly, &point, eval, &prover_setup, Some(hint), &mut pt) .expect("fixture open"); - let mut vt = Blake2bTranscript::new(TRANSCRIPT_LABEL); + let mut vt = prover_transcript::(TRANSCRIPT_LABEL, b""); DoryScheme::verify(&commitment, &point, eval, &proof, &verifier_setup, &mut vt) .expect("fixture proof must verify before tampering"); @@ -146,7 +146,7 @@ fuzz_target!(|data: &[u8]| { return; } - let mut transcript = Blake2bTranscript::new(TRANSCRIPT_LABEL); + let mut transcript = prover_transcript::(TRANSCRIPT_LABEL, b""); let result = DoryScheme::verify( &fix.commitment, &fix.point, diff --git a/crates/jolt-dory/fuzz/fuzz_targets/zk_mode_confusion.rs b/crates/jolt-dory/fuzz/fuzz_targets/zk_mode_confusion.rs index efc5b88c48..c045c743df 100644 --- a/crates/jolt-dory/fuzz/fuzz_targets/zk_mode_confusion.rs +++ b/crates/jolt-dory/fuzz/fuzz_targets/zk_mode_confusion.rs @@ -14,7 +14,7 @@ use jolt_dory::{DoryProverSetup, DoryScheme, DoryVerifierSetup}; use jolt_field::{CanonicalEncoding, Fr}; use jolt_openings::{CommitmentScheme, ZkOpeningScheme}; use jolt_poly::Polynomial; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, Transcript}; use libfuzzer_sys::fuzz_target; const SCALAR_BYTES: usize = 32; @@ -50,24 +50,24 @@ fuzz_target!(|data: &[u8]| { // ZK completeness. let (zk_commitment, zk_hint) = DoryScheme::commit_zk(poly.evaluations(), prover_setup).expect("commit_zk"); - let mut pt = Blake2bTranscript::new(TRANSCRIPT_LABEL); + let mut pt = prover_transcript::(TRANSCRIPT_LABEL, b""); let (zk_proof, _y_com, _blind) = DoryScheme::open_zk(&poly, &point, eval, prover_setup, zk_hint, &mut pt).expect("open_zk"); - let mut vt = Blake2bTranscript::new(TRANSCRIPT_LABEL); + let mut vt = prover_transcript::(TRANSCRIPT_LABEL, b""); DoryScheme::verify_zk(&zk_commitment, &point, &zk_proof, verifier_setup, &mut vt) .expect("honest ZK opening must verify in ZK mode"); // Transparent completeness. let (commitment, hint) = DoryScheme::commit(poly.evaluations(), prover_setup).expect("commit"); - let mut pt = Blake2bTranscript::new(TRANSCRIPT_LABEL); + let mut pt = prover_transcript::(TRANSCRIPT_LABEL, b""); let proof = DoryScheme::open(&poly, &point, eval, prover_setup, Some(hint), &mut pt).expect("open"); - let mut vt = Blake2bTranscript::new(TRANSCRIPT_LABEL); + let mut vt = prover_transcript::(TRANSCRIPT_LABEL, b""); DoryScheme::verify(&commitment, &point, eval, &proof, verifier_setup, &mut vt) .expect("honest transparent opening must verify"); // Mode confusion must be rejected in both directions. - let mut vt = Blake2bTranscript::new(TRANSCRIPT_LABEL); + let mut vt = prover_transcript::(TRANSCRIPT_LABEL, b""); assert!( DoryScheme::verify( &zk_commitment, @@ -80,7 +80,7 @@ fuzz_target!(|data: &[u8]| { .is_err(), "transparent verifier accepted a ZK proof" ); - let mut vt = Blake2bTranscript::new(TRANSCRIPT_LABEL); + let mut vt = prover_transcript::(TRANSCRIPT_LABEL, b""); assert!( DoryScheme::verify_zk(&commitment, &point, &proof, verifier_setup, &mut vt).is_err(), "ZK verifier accepted a transparent proof" diff --git a/crates/jolt-dory/fuzz/tests/generate_seeds.rs b/crates/jolt-dory/fuzz/tests/generate_seeds.rs index 625f08d588..fce4dd4abe 100644 --- a/crates/jolt-dory/fuzz/tests/generate_seeds.rs +++ b/crates/jolt-dory/fuzz/tests/generate_seeds.rs @@ -12,7 +12,7 @@ use jolt_dory::DoryScheme; use jolt_field::{Field, Fr}; use jolt_openings::CommitmentScheme; use jolt_poly::Polynomial; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, Transcript}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; @@ -30,7 +30,7 @@ fn generate_deser_commitment_seeds() { let point: Vec = (0..num_vars).map(|_| Fr::random(&mut rng)).collect(); let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup).expect("commit"); - let mut transcript = Blake2bTranscript::new(b"seed-gen"); + let mut transcript = prover_transcript::(b"seed-gen", b""); let proof = DoryScheme::open( &poly, &point, diff --git a/crates/jolt-dory/src/scheme.rs b/crates/jolt-dory/src/scheme.rs index a4915a1a3a..83da41ebbb 100644 --- a/crates/jolt-dory/src/scheme.rs +++ b/crates/jolt-dory/src/scheme.rs @@ -14,7 +14,7 @@ use jolt_crypto::{Bn254G1, Bn254GT, Commitment, DeriveSetup, JoltGroup, Pedersen use jolt_field::Fr; use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, OpeningsError, ZkOpeningScheme}; use jolt_poly::MultilinearPoly; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use rayon::prelude::*; use crate::routines::{JoltG1Routines, JoltG2Routines}; @@ -233,7 +233,7 @@ impl CommitmentScheme for DoryScheme { _eval: Fr, setup: &Self::ProverSetup, hint: Option, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result { let num_vars = point.len(); let adapter = DorySourceAdapter::new(poly); @@ -267,6 +267,7 @@ impl CommitmentScheme for DoryScheme { &mut dory_transcript, ) .map_err(|e| OpeningsError::ProveFailed(format!("dory::prove failed: {e:?}")))?; + dory_transcript.finish()?; Ok(DoryProof(proof)) } @@ -278,7 +279,7 @@ impl CommitmentScheme for DoryScheme { eval: Fr, proof: &Self::Proof, setup: &Self::VerifierSetup, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(), OpeningsError> { let ark_point: Vec = point.iter().rev().map(jolt_fr_to_ark).collect(); let ark_eval = jolt_fr_to_ark(&eval); @@ -302,7 +303,8 @@ impl CommitmentScheme for DoryScheme { setup.0.clone().into_inner(), &mut dory_transcript, ) - .map_err(|_| OpeningsError::VerificationFailed) + .map_err(|_| OpeningsError::VerificationFailed)?; + dory_transcript.finish() } } @@ -380,7 +382,7 @@ impl ZkOpeningScheme for DoryScheme { _eval: Fr, setup: &Self::ProverSetup, hint: Self::OpeningHint, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(Self::Proof, Self::HidingCommitment, Self::Blind), OpeningsError> { let num_vars = point.len(); let adapter = DorySourceAdapter::new(poly); @@ -403,6 +405,7 @@ impl ZkOpeningScheme for DoryScheme { &mut dory_transcript, ) .map_err(|e| OpeningsError::ProveFailed(format!("dory::prove (ZK) failed: {e:?}")))?; + dory_transcript.finish()?; let y_com = proof .y_com @@ -421,7 +424,7 @@ impl ZkOpeningScheme for DoryScheme { point: &[Fr], proof: &Self::Proof, setup: &Self::VerifierSetup, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result { let ark_point: Vec = point.iter().rev().map(jolt_fr_to_ark).collect(); // In ZK mode dory::verify reads the evaluation commitment from `proof.y_com`, @@ -444,6 +447,7 @@ impl ZkOpeningScheme for DoryScheme { &mut dory_transcript, ) .map_err(|_| OpeningsError::VerificationFailed)?; + dory_transcript.finish()?; Ok(hiding_commitment) } diff --git a/crates/jolt-dory/src/streaming.rs b/crates/jolt-dory/src/streaming.rs index 39fea02bdd..a4966ebbbf 100644 --- a/crates/jolt-dory/src/streaming.rs +++ b/crates/jolt-dory/src/streaming.rs @@ -463,7 +463,7 @@ mod tests { CommitmentScheme, StreamingCommitment, ZkOpeningScheme, ZkStreamingCommitment, }; use jolt_poly::{MultilinearPoly, OneHotIndexOrder, OneHotPolynomial}; - use jolt_transcript::Transcript; + use jolt_transcript::Blake2b512; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; @@ -502,7 +502,8 @@ mod tests { .map(|_| ::random(&mut rng)) .collect(); let eval = poly.evaluate(&point); - let mut prove_transcript = jolt_transcript::Blake2bTranscript::new(b"stream-open"); + let mut prove_transcript = + jolt_transcript::prover_transcript::(b"stream-open", b""); let proof = DoryScheme::open( &poly, &point, @@ -513,7 +514,8 @@ mod tests { ) .unwrap(); let verifier_setup = DoryScheme::verifier_setup(&prover_setup); - let mut verify_transcript = jolt_transcript::Blake2bTranscript::new(b"stream-open"); + let mut verify_transcript = + jolt_transcript::prover_transcript::(b"stream-open", b""); let result = DoryScheme::verify( &streamed, &point, @@ -559,7 +561,8 @@ mod tests { .map(|_| ::random(&mut rng)) .collect(); let eval = poly.evaluate(&point); - let mut prove_transcript = jolt_transcript::Blake2bTranscript::new(b"u64-stream-open"); + let mut prove_transcript = + jolt_transcript::prover_transcript::(b"u64-stream-open", b""); let proof = DoryScheme::open( &poly, &point, @@ -570,7 +573,8 @@ mod tests { ) .unwrap(); let verifier_setup = DoryScheme::verifier_setup(&prover_setup); - let mut verify_transcript = jolt_transcript::Blake2bTranscript::new(b"u64-stream-open"); + let mut verify_transcript = + jolt_transcript::prover_transcript::(b"u64-stream-open", b""); let result = DoryScheme::verify( &streamed, &point, @@ -691,7 +695,8 @@ mod tests { .map(|_| ::random(&mut rng)) .collect::>(); let eval = poly.evaluate(&point); - let mut prove_transcript = jolt_transcript::Blake2bTranscript::new(b"one-hot-zk-open"); + let mut prove_transcript = + jolt_transcript::prover_transcript::(b"one-hot-zk-open", b""); let (proof, _, _) = DoryScheme::open_zk( &poly, &point, @@ -701,7 +706,8 @@ mod tests { &mut prove_transcript, ) .unwrap(); - let mut verify_transcript = jolt_transcript::Blake2bTranscript::new(b"one-hot-zk-open"); + let mut verify_transcript = + jolt_transcript::prover_transcript::(b"one-hot-zk-open", b""); let result = DoryScheme::verify_zk( &commitment, &point, @@ -754,7 +760,8 @@ mod tests { .map(|_| ::random(&mut rng)) .collect(); let eval = poly.evaluate(&point); - let mut prove_transcript = jolt_transcript::Blake2bTranscript::new(b"i128-stream-open"); + let mut prove_transcript = + jolt_transcript::prover_transcript::(b"i128-stream-open", b""); let proof = DoryScheme::open( &poly, &point, @@ -765,7 +772,8 @@ mod tests { ) .unwrap(); let verifier_setup = DoryScheme::verifier_setup(&prover_setup); - let mut verify_transcript = jolt_transcript::Blake2bTranscript::new(b"i128-stream-open"); + let mut verify_transcript = + jolt_transcript::prover_transcript::(b"i128-stream-open", b""); let result = DoryScheme::verify( &streamed, &point, diff --git a/crates/jolt-dory/src/transcript.rs b/crates/jolt-dory/src/transcript.rs index 6e39ff04a1..2adc982729 100644 --- a/crates/jolt-dory/src/transcript.rs +++ b/crates/jolt-dory/src/transcript.rs @@ -3,28 +3,50 @@ //! Prover/verifier parity within `jolt-dory` is by construction: both sides //! traverse this adapter. The surrounding Jolt transcript is responsible for //! matching the core Fiat-Shamir byte layout before this adapter is entered. +//! +//! Dory's transcript trait draws challenges infallibly. A draw the Jolt +//! transcript refuses is recorded and reported by +//! [`JoltToDoryTranscript::finish`], which every caller checks once Dory +//! returns. use dory::backends::arkworks::BN254; use dory::primitives::arithmetic::Group as DoryGroup; use dory::primitives::transcript::Transcript as DoryTranscript; use dory::primitives::DorySerialize; -use jolt_field::Fr; -use jolt_transcript::domain::{Label, LabelWithCount}; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_field::{Fr, Ring}; +use jolt_openings::OpeningsError; +use jolt_transcript::{FieldTranscript, Label, LabelWithCount}; use crate::scheme::{ark_to_jolt_fr, jolt_fr_to_ark, ArkFr}; -pub struct JoltToDoryTranscript<'a, T: Transcript> { +pub struct JoltToDoryTranscript<'a, T: FieldTranscript> { transcript: &'a mut T, + rejected: bool, } -impl<'a, T: Transcript> JoltToDoryTranscript<'a, T> { +impl<'a, T: FieldTranscript> JoltToDoryTranscript<'a, T> { pub fn new(transcript: &'a mut T) -> Self { - Self { transcript } + Self { + transcript, + rejected: false, + } + } + + /// Ends the adapter's use. + /// + /// # Errors + /// [`OpeningsError::TranscriptRejected`] if the transcript refused one of + /// Dory's challenge draws; the challenges Dory used after it are void. + pub fn finish(self) -> Result<(), OpeningsError> { + if self.rejected { + Err(OpeningsError::TranscriptRejected) + } else { + Ok(()) + } } } -impl> DoryTranscript for JoltToDoryTranscript<'_, T> { +impl> DoryTranscript for JoltToDoryTranscript<'_, T> { type Curve = BN254; fn append_bytes(&mut self, _label: &[u8], bytes: &[u8]) { @@ -36,7 +58,7 @@ impl> DoryTranscript for JoltToDoryTranscript<'_, fn append_field(&mut self, _label: &[u8], x: &ArkFr) { let jolt_scalar = ark_to_jolt_fr(x); self.transcript.append(&Label(b"dory_field")); - jolt_scalar.append_to_transcript(self.transcript); + self.transcript.append(&jolt_scalar); } #[expect( @@ -66,7 +88,12 @@ impl> DoryTranscript for JoltToDoryTranscript<'_, } fn challenge_scalar(&mut self, _label: &[u8]) -> ArkFr { - let challenge: Fr = self.transcript.challenge_scalar(); + // Dory cannot take an error here: a refused draw yields zero and + // fails `finish`. + let challenge: Fr = self.transcript.challenge().unwrap_or_else(|_| { + self.rejected = true; + Fr::from_u64(0) + }); jolt_fr_to_ark(&challenge) } @@ -89,14 +116,14 @@ mod tests { use ark_ec::PrimeGroup; use ark_serialize::CanonicalSerialize; use dory::backends::arkworks::{ArkFr as DoryArkFr, ArkG1}; - use jolt_field::Ring; - use jolt_transcript::Blake2bTranscript; + use jolt_transcript::{prover_transcript, Blake2b512, ProverState, Transcript}; // The framing words below are reconstructed from the documented layout - // (legacy.rs `Label` / `LabelWithCount`), NOT by calling those helpers: - // a `Label` is one 32-byte zero-padded word; a `LabelWithCount` packs the - // label into bytes 0..24 and the count big-endian into bytes 24..32. Each - // word and each payload is absorbed as its own `append_bytes` call. + // (`Label` / `LabelWithCount` in jolt-transcript's codec), NOT by calling + // those helpers: a `Label` is one 32-byte zero-padded word; a + // `LabelWithCount` packs the label into bytes 0..24 and the count + // big-endian into bytes 24..32. A word is absorbed as its 32 raw bytes, a + // byte payload through `append_bytes`, which prefixes its length. fn label_word(label: &[u8]) -> [u8; 32] { assert!(label.len() <= 32); @@ -113,8 +140,8 @@ mod tests { word } - fn transcript() -> Blake2bTranscript { - Blake2bTranscript::new(b"dory-adapter-framing") + fn transcript() -> ProverState { + prover_transcript::(b"dory-adapter-framing", b"") } /// `ArkFr` in scope is a type alias, which cannot be used in constructor @@ -131,24 +158,25 @@ mod tests { JoltToDoryTranscript::new(&mut actual).append_bytes(b"caller-label", &payload); let mut expected = transcript(); - expected.append_bytes(&label_with_count_word(b"dory_bytes", 5)); + expected.append(&label_with_count_word(b"dory_bytes", 5)); expected.append_bytes(&payload); - assert_eq!(actual.state(), expected.state()); + let actual = actual.verifier_message::<[u8; 32]>(); + assert_eq!(actual, expected.verifier_message::<[u8; 32]>()); // The count is load-bearing: the same payload under a wrong count // must diverge, otherwise the golden comparison proves nothing. let mut wrong_count = transcript(); - wrong_count.append_bytes(&label_with_count_word(b"dory_bytes", 4)); + wrong_count.append(&label_with_count_word(b"dory_bytes", 4)); wrong_count.append_bytes(&payload); - assert_ne!(actual.state(), wrong_count.state()); + assert_ne!(actual, wrong_count.verifier_message::<[u8; 32]>()); - // Word and payload are separate absorptions, not one concatenated - // buffer (the sponge length-prefixes each `append_bytes` call). + // The payload is a length-prefixed message of its own, not part of a + // concatenated buffer with the word. let mut merged = transcript(); let mut buffer = label_with_count_word(b"dory_bytes", 5).to_vec(); buffer.extend_from_slice(&payload); merged.append_bytes(&buffer); - assert_ne!(actual.state(), merged.state()); + assert_ne!(actual, merged.verifier_message::<[u8; 32]>()); } #[test] @@ -159,23 +187,24 @@ mod tests { &dory_ark_fr(ArkBn254Fr::from(0xdead_beefu64)), ); - // Fr absorbs as its 32-byte big-endian canonical form: 24 zero bytes - // then the value, reconstructed here without CanonicalBytes. - let mut scalar_be = [0u8; 32]; - scalar_be[24..].copy_from_slice(&0xdead_beefu64.to_be_bytes()); + // Fr absorbs as its 32-byte little-endian canonical form: the value + // then 24 zero bytes, reconstructed here without CanonicalBytes. + let mut scalar_le = [0u8; 32]; + scalar_le[..8].copy_from_slice(&0xdead_beefu64.to_le_bytes()); let mut expected = transcript(); - expected.append_bytes(&label_word(b"dory_field")); - expected.append_bytes(&scalar_be); - assert_eq!(actual.state(), expected.state()); + expected.append(&label_word(b"dory_field")); + expected.append(&scalar_le); + let actual = actual.verifier_message::<[u8; 32]>(); + assert_eq!(actual, expected.verifier_message::<[u8; 32]>()); - // Little-endian absorption would be an invisible-to-roundtrip bug. - let mut little_endian = transcript(); - let mut scalar_le = [0u8; 32]; - scalar_le[..8].copy_from_slice(&0xdead_beefu64.to_le_bytes()); - little_endian.append_bytes(&label_word(b"dory_field")); - little_endian.append_bytes(&scalar_le); - assert_ne!(actual.state(), little_endian.state()); + // Big-endian absorption would be an invisible-to-roundtrip bug. + let mut big_endian = transcript(); + let mut scalar_be = [0u8; 32]; + scalar_be[24..].copy_from_slice(&0xdead_beefu64.to_be_bytes()); + big_endian.append(&label_word(b"dory_field")); + big_endian.append(&scalar_be); + assert_ne!(actual, big_endian.verifier_message::<[u8; 32]>()); } #[test] @@ -195,9 +224,12 @@ mod tests { assert_eq!(payload.len(), 32); let mut expected = transcript(); - expected.append_bytes(&label_with_count_word(b"dory_group", payload.len() as u64)); + expected.append(&label_with_count_word(b"dory_group", payload.len() as u64)); expected.append_bytes(&payload); - assert_eq!(actual.state(), expected.state()); + assert_eq!( + actual.verifier_message::<[u8; 32]>(), + expected.verifier_message::<[u8; 32]>() + ); } #[test] @@ -215,9 +247,12 @@ mod tests { assert_eq!(payload.len(), 32); let mut expected = transcript(); - expected.append_bytes(&label_with_count_word(b"dory_serde", payload.len() as u64)); + expected.append(&label_with_count_word(b"dory_serde", payload.len() as u64)); expected.append_bytes(&payload); - assert_eq!(actual.state(), expected.state()); + assert_eq!( + actual.verifier_message::<[u8; 32]>(), + expected.verifier_message::<[u8; 32]>() + ); } /// Domain separation comes from the fixed `dory_*` labels; the caller's @@ -232,8 +267,9 @@ mod tests { let mut second = transcript(); JoltToDoryTranscript::new(&mut second).append_bytes(b"label-two", &payload); - assert_eq!(first.state(), second.state()); - assert_ne!(first.state(), transcript().state()); + let first = first.verifier_message::<[u8; 32]>(); + assert_eq!(first, second.verifier_message::<[u8; 32]>()); + assert_ne!(first, transcript().verifier_message::<[u8; 32]>()); } /// The adapter's challenge is the Jolt transcript's scalar challenge, @@ -246,15 +282,19 @@ mod tests { let mut adapter = JoltToDoryTranscript::new(&mut adapted); adapter.append_bytes(b"caller-label", b"shared-absorption"); let adapter_challenge = adapter.challenge_scalar(b"caller-label"); + adapter.finish().expect("the adapter draws a supported challenge"); let mut direct = transcript(); - direct.append_bytes(&label_with_count_word(b"dory_bytes", 17)); + direct.append(&label_with_count_word(b"dory_bytes", 17)); direct.append_bytes(b"shared-absorption"); - let direct_challenge: Fr = direct.challenge_scalar(); + let direct_challenge: Fr = direct.challenge().expect("Fr supports one challenge"); assert_eq!(adapter_challenge, jolt_fr_to_ark(&direct_challenge)); assert_ne!(adapter_challenge, dory_ark_fr(ArkBn254Fr::from(0u64))); - assert_eq!(adapted.state(), direct.state()); + assert_eq!( + adapted.verifier_message::<[u8; 32]>(), + direct.verifier_message::<[u8; 32]>() + ); // Fr conversion sanity: the transmute-based bridge is the identity on // canonical values. diff --git a/crates/jolt-dory/src/types.rs b/crates/jolt-dory/src/types.rs index 9c6e8f0d06..f4e960e8b5 100644 --- a/crates/jolt-dory/src/types.rs +++ b/crates/jolt-dory/src/types.rs @@ -6,7 +6,7 @@ use dory::backends::arkworks::{ }; use jolt_crypto::{Bn254G1, Bn254GT, HomomorphicCommitment}; use jolt_field::Fr; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::Encoding; use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; /// Caps the upstream `Vec::with_capacity(num_rounds)` allocation against @@ -46,13 +46,9 @@ impl<'de> Deserialize<'de> for DoryCommitment { } } -impl AppendToTranscript for DoryCommitment { - fn append_to_transcript(&self, transcript: &mut T) { - self.0.append_to_transcript(transcript); - } - - fn transcript_payload_len(&self) -> Option { - self.0.transcript_payload_len() +impl Encoding for DoryCommitment { + fn encode(&self) -> impl AsRef<[u8]> { + self.0.encode() } } @@ -266,7 +262,7 @@ mod tests { use jolt_field::Field; use jolt_openings::CommitmentScheme; use jolt_poly::Polynomial; - use jolt_transcript::Transcript; + use jolt_transcript::Blake2b512; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; @@ -308,7 +304,8 @@ mod tests { let (commitment, hint) = crate::DoryScheme::commit(poly.evaluations(), &prover_setup).unwrap(); - let mut prove_transcript = jolt_transcript::Blake2bTranscript::new(b"serde-vs"); + let mut prove_transcript = + jolt_transcript::prover_transcript::(b"serde-vs", b""); let proof = crate::DoryScheme::open( &poly, &point, @@ -319,7 +316,8 @@ mod tests { ) .unwrap(); - let mut verify_transcript = jolt_transcript::Blake2bTranscript::new(b"serde-vs"); + let mut verify_transcript = + jolt_transcript::prover_transcript::(b"serde-vs", b""); let result = crate::DoryScheme::verify( &commitment, &point, @@ -379,7 +377,7 @@ mod tests { .collect(); let eval = poly.evaluate(&point); - let mut transcript = jolt_transcript::Blake2bTranscript::new(b"serde-bp"); + let mut transcript = jolt_transcript::prover_transcript::(b"serde-bp", b""); let proof = crate::DoryScheme::open(&poly, &point, eval, &prover_setup, None, &mut transcript) .unwrap(); @@ -391,7 +389,8 @@ mod tests { let verifier_setup = DoryVerifierSetup(prover_setup.0.to_verifier_setup()); let (commitment, _) = crate::DoryScheme::commit(poly.evaluations(), &prover_setup).unwrap(); - let mut verify_transcript = jolt_transcript::Blake2bTranscript::new(b"serde-bp"); + let mut verify_transcript = + jolt_transcript::prover_transcript::(b"serde-bp", b""); let result = crate::DoryScheme::verify( &commitment, &point, @@ -421,7 +420,8 @@ mod tests { .collect(); let eval = poly.evaluate(&point); - let mut transcript = jolt_transcript::Blake2bTranscript::new(b"serde-trailing"); + let mut transcript = + jolt_transcript::prover_transcript::(b"serde-trailing", b""); let proof = crate::DoryScheme::open(&poly, &point, eval, &prover_setup, None, &mut transcript) .unwrap(); @@ -447,7 +447,8 @@ mod tests { .collect(); let eval = poly.evaluate(&point); - let mut transcript = jolt_transcript::Blake2bTranscript::new(b"serde-oversized"); + let mut transcript = + jolt_transcript::prover_transcript::(b"serde-oversized", b""); let proof = crate::DoryScheme::open(&poly, &point, eval, &prover_setup, None, &mut transcript) .unwrap(); diff --git a/crates/jolt-dory/tests/commit_open_verify.rs b/crates/jolt-dory/tests/commit_open_verify.rs index 58957d95d4..f1a44def94 100644 --- a/crates/jolt-dory/tests/commit_open_verify.rs +++ b/crates/jolt-dory/tests/commit_open_verify.rs @@ -11,11 +11,16 @@ use jolt_openings::{ AdditivelyHomomorphic, CommitmentScheme, StreamingCommitment, ZkOpeningScheme, }; use jolt_poly::{OneHotPolynomial, Polynomial}; -use jolt_transcript::{Blake2bTranscript, KeccakTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, FieldTranscript, Keccak}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; -fn round_trip>(num_vars: usize, seed: u64, label: &'static [u8]) { +fn round_trip>( + new_transcript: impl Fn(&[u8]) -> T, + num_vars: usize, + seed: u64, + label: &'static [u8], +) { let mut rng = ChaCha20Rng::seed_from_u64(seed); let prover_setup = DoryScheme::setup_prover(num_vars); let verifier_setup = DoryScheme::setup_verifier(num_vars); @@ -26,17 +31,17 @@ fn round_trip>(num_vars: usize, seed: u64, label: let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = T::new(label); + let mut pt = new_transcript(label); let proof = DoryScheme::open(&poly, &point, eval, &prover_setup, Some(hint), &mut pt).unwrap(); - let mut vt = T::new(label); + let mut vt = new_transcript(label); DoryScheme::verify(&commitment, &point, eval, &proof, &verifier_setup, &mut vt) .expect("round-trip verification (with hint) must succeed"); - let mut pt2 = T::new(label); + let mut pt2 = new_transcript(label); let proof2 = DoryScheme::open(&poly, &point, eval, &prover_setup, None, &mut pt2).unwrap(); - let mut vt2 = T::new(label); + let mut vt2 = new_transcript(label); DoryScheme::verify( &commitment, &point, @@ -51,15 +56,30 @@ fn round_trip>(num_vars: usize, seed: u64, label: #[test] fn commit_open_verify_various_sizes() { for num_vars in [2, 3, 4, 6] { - round_trip::(num_vars, 100 + num_vars as u64, b"cov-sizes"); + round_trip( + |label| prover_transcript::(label, b""), + num_vars, + 100 + num_vars as u64, + b"cov-sizes", + ); } } #[test] fn commit_open_verify_both_transcripts() { let num_vars = 4; - round_trip::(num_vars, 200, b"blake2b-rt"); - round_trip::(num_vars, 200, b"keccak-rt"); + round_trip( + |label| prover_transcript::(label, b""), + num_vars, + 200, + b"blake2b-rt", + ); + round_trip( + |label| prover_transcript::(label, b""), + num_vars, + 200, + b"keccak-rt", + ); } #[test] @@ -143,11 +163,11 @@ fn streaming_zk_commitment_is_blinded_and_verifies() { "streaming ZK commitments must use fresh blinding" ); - let mut pt = Blake2bTranscript::new(b"stream-zk"); + let mut pt = prover_transcript::(b"stream-zk", b""); let (proof, eval_com, _blind) = DoryScheme::open_zk(&poly, &point, eval, &prover_setup, hint, &mut pt).unwrap(); - let mut vt = Blake2bTranscript::new(b"stream-zk"); + let mut vt = prover_transcript::(b"stream-zk", b""); let verified_eval_com = DoryScheme::verify_zk(&commitment, &point, &proof, &verifier_setup, &mut vt) .expect("streaming ZK commitment must verify"); @@ -168,11 +188,11 @@ fn wrong_eval_rejected() { let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = Blake2bTranscript::new(b"wrong-eval"); + let mut pt = prover_transcript::(b"wrong-eval", b""); let proof = DoryScheme::open(&poly, &point, eval, &prover_setup, Some(hint), &mut pt).unwrap(); let tampered_eval = eval + Fr::from_u64(1); - let mut vt = Blake2bTranscript::new(b"wrong-eval"); + let mut vt = prover_transcript::(b"wrong-eval", b""); let result = DoryScheme::verify( &commitment, &point, @@ -198,12 +218,12 @@ fn wrong_point_rejected() { let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = Blake2bTranscript::new(b"wrong-point"); + let mut pt = prover_transcript::(b"wrong-point", b""); let proof = DoryScheme::open(&poly, &point, eval, &prover_setup, Some(hint), &mut pt).unwrap(); let mut tampered_point = point.clone(); tampered_point[0] += Fr::from_u64(1); - let mut vt = Blake2bTranscript::new(b"wrong-point"); + let mut vt = prover_transcript::(b"wrong-point", b""); let result = DoryScheme::verify( &commitment, &tampered_point, @@ -292,7 +312,7 @@ fn wrong_commitment_rejected() { let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = Blake2bTranscript::new(b"wrong-commit"); + let mut pt = prover_transcript::(b"wrong-commit", b""); let proof = DoryScheme::open(&poly, &point, eval, &prover_setup, Some(hint), &mut pt).unwrap(); let wrong_poly = Polynomial::::random(num_vars, &mut rng); @@ -300,7 +320,7 @@ fn wrong_commitment_rejected() { DoryScheme::commit(wrong_poly.evaluations(), &prover_setup).unwrap(); assert_ne!(commitment, wrong_commitment); - let mut vt = Blake2bTranscript::new(b"wrong-commit"); + let mut vt = prover_transcript::(b"wrong-commit", b""); let result = DoryScheme::verify( &wrong_commitment, &point, @@ -326,15 +346,20 @@ fn wrong_transcript_domain_rejected() { let eval = poly.evaluate(&point); let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = Blake2bTranscript::new(b"correct-domain"); + let mut pt = prover_transcript::(b"correct-domain", b""); let proof = DoryScheme::open(&poly, &point, eval, &prover_setup, Some(hint), &mut pt).unwrap(); - let mut vt = Blake2bTranscript::new(b"wrong-domain"); + let mut vt = prover_transcript::(b"wrong-domain", b""); let result = DoryScheme::verify(&commitment, &point, eval, &proof, &verifier_setup, &mut vt); assert!(result.is_err(), "wrong transcript domain must be rejected"); } -fn zk_round_trip>(num_vars: usize, seed: u64, label: &'static [u8]) { +fn zk_round_trip>( + new_transcript: impl Fn(&[u8]) -> T, + num_vars: usize, + seed: u64, + label: &'static [u8], +) { let mut rng = ChaCha20Rng::seed_from_u64(seed); let prover_setup = DoryScheme::setup_prover(num_vars); let verifier_setup = DoryScheme::setup_verifier(num_vars); @@ -346,11 +371,11 @@ fn zk_round_trip>(num_vars: usize, seed: u64, labe let (commitment, hint) = ::commit_zk(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = T::new(label); + let mut pt = new_transcript(label); let (proof, eval_com, _blind) = DoryScheme::open_zk(&poly, &point, eval, &prover_setup, hint, &mut pt).unwrap(); - let mut vt = T::new(label); + let mut vt = new_transcript(label); let verified_eval_com = DoryScheme::verify_zk(&commitment, &point, &proof, &verifier_setup, &mut vt) .expect("ZK round-trip verification must succeed"); @@ -360,15 +385,30 @@ fn zk_round_trip>(num_vars: usize, seed: u64, labe #[test] fn zk_round_trip_various_sizes() { for num_vars in [2, 3, 4, 6] { - zk_round_trip::(num_vars, 1100 + num_vars as u64, b"zk-cov-sizes"); + zk_round_trip( + |label| prover_transcript::(label, b""), + num_vars, + 1100 + num_vars as u64, + b"zk-cov-sizes", + ); } } #[test] fn zk_round_trip_both_transcripts() { let num_vars = 4; - zk_round_trip::(num_vars, 1200, b"zk-blake2b-rt"); - zk_round_trip::(num_vars, 1200, b"zk-keccak-rt"); + zk_round_trip( + |label| prover_transcript::(label, b""), + num_vars, + 1200, + b"zk-blake2b-rt", + ); + zk_round_trip( + |label| prover_transcript::(label, b""), + num_vars, + 1200, + b"zk-keccak-rt", + ); } #[test] @@ -386,11 +426,11 @@ fn transparent_verify_rejects_zk_opening_proof() { let (commitment, hint) = ::commit_zk(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = Blake2bTranscript::new(b"zk-proof-transparent-verify"); + let mut pt = prover_transcript::(b"zk-proof-transparent-verify", b""); let (proof, _eval_com, _blind) = DoryScheme::open_zk(&poly, &point, eval, &prover_setup, hint, &mut pt).unwrap(); - let mut vt = Blake2bTranscript::new(b"zk-proof-transparent-verify"); + let mut vt = prover_transcript::(b"zk-proof-transparent-verify", b""); let result = DoryScheme::verify(&commitment, &point, eval, &proof, &verifier_setup, &mut vt); assert!( result.is_err(), @@ -414,7 +454,7 @@ fn zk_wrong_commitment_rejected() { let (commitment, hint) = ::commit_zk(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = Blake2bTranscript::new(b"zk-wrong-commit"); + let mut pt = prover_transcript::(b"zk-wrong-commit", b""); let (proof, _eval_com, _blind) = DoryScheme::open_zk(&poly, &point, eval, &prover_setup, hint, &mut pt).unwrap(); @@ -424,7 +464,7 @@ fn zk_wrong_commitment_rejected() { .unwrap(); assert_ne!(commitment, wrong_commitment); - let mut vt = Blake2bTranscript::new(b"zk-wrong-commit"); + let mut vt = prover_transcript::(b"zk-wrong-commit", b""); let result = DoryScheme::verify_zk(&wrong_commitment, &point, &proof, &verifier_setup, &mut vt); assert!(result.is_err(), "ZK: wrong commitment must be rejected"); } @@ -447,11 +487,11 @@ fn transparent_commitment_rejected_for_zk_blinded_proof() { let (_zk_commitment, hint) = ::commit_zk(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = Blake2bTranscript::new(b"zk-transparent-reject"); + let mut pt = prover_transcript::(b"zk-transparent-reject", b""); let (proof, _eval_com, _blind) = DoryScheme::open_zk(&poly, &point, eval, &prover_setup, hint, &mut pt).unwrap(); - let mut vt = Blake2bTranscript::new(b"zk-transparent-reject"); + let mut vt = prover_transcript::(b"zk-transparent-reject", b""); let result = DoryScheme::verify_zk( &transparent_commitment, &point, @@ -497,7 +537,7 @@ fn zk_combined_commitment_and_hint_verify() { .collect(); let eval = weighted_poly.evaluate(&point); - let mut pt = Blake2bTranscript::new(b"zk-combined"); + let mut pt = prover_transcript::(b"zk-combined", b""); let (proof, eval_com, _blind) = DoryScheme::open_zk( &weighted_poly, &point, @@ -508,7 +548,7 @@ fn zk_combined_commitment_and_hint_verify() { ) .unwrap(); - let mut vt = Blake2bTranscript::new(b"zk-combined"); + let mut vt = prover_transcript::(b"zk-combined", b""); let verified_eval_com = DoryScheme::verify_zk( &combined_commitment, &point, @@ -535,7 +575,7 @@ fn wrong_eval_commitment_rejected_zk() { let (commitment, hint) = ::commit_zk(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = Blake2bTranscript::new(b"zk-tampered-y-com"); + let mut pt = prover_transcript::(b"zk-tampered-y-com", b""); let (mut proof, _eval_com, _blind) = DoryScheme::open_zk(&poly, &point, eval, &prover_setup, hint, &mut pt).unwrap(); @@ -544,7 +584,7 @@ fn wrong_eval_commitment_rejected_zk() { // bind y_com cryptographically to the rest of the proof. proof.0.y_com = Some(ArkG1::default()); - let mut vt = Blake2bTranscript::new(b"zk-tampered-y-com"); + let mut vt = prover_transcript::(b"zk-tampered-y-com", b""); let result = DoryScheme::verify_zk(&commitment, &point, &proof, &verifier_setup, &mut vt); assert!(result.is_err(), "tampered proof.y_com must be rejected"); } @@ -564,11 +604,11 @@ fn zk_wrong_transcript_domain_rejected() { let (commitment, hint) = ::commit_zk(poly.evaluations(), &prover_setup).unwrap(); - let mut pt = Blake2bTranscript::new(b"zk-correct-domain"); + let mut pt = prover_transcript::(b"zk-correct-domain", b""); let (proof, _eval_com, _blind) = DoryScheme::open_zk(&poly, &point, eval, &prover_setup, hint, &mut pt).unwrap(); - let mut vt = Blake2bTranscript::new(b"zk-wrong-domain"); + let mut vt = prover_transcript::(b"zk-wrong-domain", b""); let result = DoryScheme::verify_zk(&commitment, &point, &proof, &verifier_setup, &mut vt); assert!( result.is_err(), @@ -622,7 +662,7 @@ fn ragged_hint_combination_verifies() { .collect(); let eval = joint.evaluate(&point); - let mut pt = Blake2bTranscript::new(b"ragged-hints"); + let mut pt = prover_transcript::(b"ragged-hints", b""); let proof = DoryScheme::open( &joint, &point, @@ -633,7 +673,7 @@ fn ragged_hint_combination_verifies() { ) .unwrap(); - let mut vt = Blake2bTranscript::new(b"ragged-hints"); + let mut vt = prover_transcript::(b"ragged-hints", b""); DoryScheme::verify( &combined_commitment, &point, diff --git a/crates/jolt-hyperkzg/Cargo.toml b/crates/jolt-hyperkzg/Cargo.toml index 7c5378ad04..22af9b8c3c 100644 --- a/crates/jolt-hyperkzg/Cargo.toml +++ b/crates/jolt-hyperkzg/Cargo.toml @@ -20,6 +20,7 @@ thiserror.workspace = true [dev-dependencies] jolt-transcript = { workspace = true, features = [ "bn254", + "test-utils", "transcript-blake2b", ] } bincode.workspace = true diff --git a/crates/jolt-hyperkzg/src/kzg.rs b/crates/jolt-hyperkzg/src/kzg.rs index 94757b6925..60a28f7adc 100644 --- a/crates/jolt-hyperkzg/src/kzg.rs +++ b/crates/jolt-hyperkzg/src/kzg.rs @@ -1,6 +1,6 @@ use jolt_crypto::{Bn254, Bn254G1, JoltGroup, PairingGroup}; use jolt_field::{Fr, One, Zero}; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use crate::{HyperKZGError, HyperKZGProverSetup, HyperKZGVerifierSetup}; @@ -20,7 +20,7 @@ impl HyperKZGProverSetup { &self, polynomials: &[Vec], points: [Fr; 3], - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<([Bn254G1; 3], [Vec; 3]), HyperKZGError> { let evaluations = points.map(|point| { polynomials @@ -28,7 +28,7 @@ impl HyperKZGProverSetup { .map(|coefficients| evaluate(coefficients, point)) .collect::>() }); - let powers = absorb_evaluations(&evaluations, transcript); + let powers = absorb_evaluations(&evaluations, transcript)?; let first = polynomials.first().ok_or(HyperKZGError::PolynomialShape)?; let mut combined = vec![Fr::zero(); first.len()]; for (polynomial, weight) in polynomials.iter().zip(powers) { @@ -38,7 +38,7 @@ impl HyperKZGProverSetup { } let [a, b, c] = points.map(|point| self.commit_coefficients("ient(&combined, point))); let witnesses = [a?, b?, c?]; - let _ = absorb_witnesses(&witnesses, transcript); + let _ = absorb_witnesses(&witnesses, transcript)?; Ok((witnesses, evaluations)) } } @@ -50,10 +50,10 @@ impl HyperKZGVerifierSetup { points: [Fr; 3], evaluations: &[Vec; 3], witnesses: &[Bn254G1; 3], - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(), HyperKZGError> { - let powers = absorb_evaluations(evaluations, transcript); - let d = absorb_witnesses(witnesses, transcript); + let powers = absorb_evaluations(evaluations, transcript)?; + let d = absorb_witnesses(witnesses, transcript)?; let weights = [Fr::one(), d, d * d]; let scale = weights.iter().copied().sum::(); let mut lhs = Bn254G1::msm(commitments, &powers).scalar_mul(&scale); @@ -81,28 +81,25 @@ impl HyperKZGVerifierSetup { fn absorb_evaluations( evaluations: &[Vec; 3], - transcript: &mut impl Transcript, -) -> Vec { + transcript: &mut impl FieldTranscript, +) -> Result, HyperKZGError> { for row in evaluations { for value in row { transcript.append(value); } } - let q = transcript.challenge(); let [first, _, _] = evaluations; - std::iter::successors(Some(Fr::one()), |power| Some(*power * q)) - .take(first.len()) - .collect() + Ok(transcript.challenge_powers(first.len())?) } fn absorb_witnesses( witnesses: &[Bn254G1; 3], - transcript: &mut impl Transcript, -) -> Fr { + transcript: &mut impl FieldTranscript, +) -> Result { for witness in witnesses { transcript.append(witness); } - transcript.challenge() + Ok(transcript.challenge()?) } fn evaluate(coefficients: &[Fr], point: Fr) -> Fr { diff --git a/crates/jolt-hyperkzg/src/scheme.rs b/crates/jolt-hyperkzg/src/scheme.rs index 907beeedee..64a7f0c982 100644 --- a/crates/jolt-hyperkzg/src/scheme.rs +++ b/crates/jolt-hyperkzg/src/scheme.rs @@ -2,7 +2,7 @@ use jolt_crypto::{Bn254G1, Commitment, JoltGroup}; use jolt_field::{Fr, One, Zero}; use jolt_openings::{CommitmentScheme, OpeningsError}; use jolt_poly::MultilinearPoly; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use crate::{ HyperKZGError, HyperKZGProof, HyperKZGProverSetup, HyperKZGSetupParams, HyperKZGVerifierSetup, @@ -51,7 +51,7 @@ impl HyperKZGScheme { evaluation: Fr, setup: &HyperKZGProverSetup, hint: Option, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result { let len = setup.verifier.check_arity(point.len())?; if evaluations.len() != len { @@ -87,7 +87,7 @@ impl HyperKZGScheme { for commitment in &com { transcript.append(commitment); } - let r = transcript.challenge(); + let r = transcript.challenge()?; if r.is_zero() { return Err(HyperKZGError::DegenerateChallenge); } @@ -102,7 +102,7 @@ impl HyperKZGScheme { evaluation: Fr, proof: &HyperKZGProof, setup: &HyperKZGVerifierSetup, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(), HyperKZGError> { let _ = setup.check_arity(point.len())?; if proof.com.len() != point.len() - 1 || proof.v.iter().any(|row| row.len() != point.len()) @@ -113,7 +113,7 @@ impl HyperKZGScheme { for commitment in &proof.com { transcript.append(commitment); } - let r = transcript.challenge(); + let r = transcript.challenge()?; if r.is_zero() { return Err(HyperKZGError::DegenerateChallenge); } @@ -195,7 +195,7 @@ impl CommitmentScheme for HyperKZGScheme { evaluation: Fr, setup: &Self::ProverSetup, hint: Option, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result { if poly.num_vars() != point.len() { return Err(OpeningsError::ProveFailed( @@ -216,7 +216,7 @@ impl CommitmentScheme for HyperKZGScheme { evaluation: Fr, proof: &Self::Proof, setup: &Self::VerifierSetup, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(), OpeningsError> { Self::verify_opening(commitment, point, evaluation, proof, setup, transcript) .map_err(|_| OpeningsError::VerificationFailed) diff --git a/crates/jolt-hyperkzg/src/types.rs b/crates/jolt-hyperkzg/src/types.rs index f8c89f825f..ed5349fbcc 100644 --- a/crates/jolt-hyperkzg/src/types.rs +++ b/crates/jolt-hyperkzg/src/types.rs @@ -1,6 +1,6 @@ use jolt_crypto::{Bn254G1, Bn254G2, JoltGroup}; use jolt_field::Fr; -use jolt_transcript::{Transcript, U64Word}; +use jolt_transcript::{FieldTranscript, U64Word}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -66,7 +66,7 @@ impl HyperKZGVerifierSetup { commitment: &Bn254G1, point: &[Fr], evaluation: Fr, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) { transcript.append_labeled(b"hyperkzg-binary-bn254-v1", &U64Word(self.num_powers)); transcript.append_bytes(&self.setup_id); @@ -105,6 +105,16 @@ pub enum HyperKZGError { Folding, #[error("KZG pairing equation failed")] Pairing, + /// The transcript refused a challenge draw: the codec does not support the + /// request, or its rejection pool ran out. + #[error("transcript rejected a challenge draw")] + TranscriptRejected, +} + +impl From for HyperKZGError { + fn from(_: jolt_transcript::VerificationError) -> Self { + Self::TranscriptRejected + } } #[cfg(test)] @@ -115,7 +125,7 @@ pub enum HyperKZGError { mod tests { use jolt_crypto::Bn254; use jolt_field::{One, Ring, Zero}; - use jolt_transcript::Blake2bTranscript; + use jolt_transcript::{prover_transcript, Blake2b512}; use super::*; use crate::HyperKZGScheme; @@ -141,7 +151,7 @@ mod tests { Fr::zero(), &proof, key, - &mut Blake2bTranscript::new(b"decoded-key"), + &mut prover_transcript::(b"decoded-key", b""), ) }; verify(&valid).unwrap(); diff --git a/crates/jolt-hyperkzg/tests/openings.rs b/crates/jolt-hyperkzg/tests/openings.rs index db7ec43620..7e028f3706 100644 --- a/crates/jolt-hyperkzg/tests/openings.rs +++ b/crates/jolt-hyperkzg/tests/openings.rs @@ -12,7 +12,7 @@ use jolt_hyperkzg::{ }; use jolt_openings::CommitmentScheme; use jolt_poly::Polynomial; -use jolt_transcript::{Blake2bTranscript, ChallengeSetSize, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, ProverState, ScriptedTranscript}; fn setup_params(beta: u64, capacity: usize) -> HyperKZGSetupParams { let beta = Fr::from_u64(beta); @@ -31,8 +31,8 @@ fn setup(beta: u64, capacity: usize) -> (HyperKZGProverSetup, HyperKZGVerifierSe HyperKZGScheme::setup(setup_params(beta, capacity)).unwrap() } -fn transcript() -> Blake2bTranscript { - Blake2bTranscript::new(b"hyperkzg-test") +fn transcript() -> ProverState { + prover_transcript::(b"hyperkzg-test", b"") } fn multilinear_oracle(table: &[Fr], point: &[Fr]) -> Fr { @@ -78,7 +78,10 @@ fn independent_evaluations_and_transcripts_match() { assert_eq!(consumed, bytes.len()); let mut vt = transcript(); HyperKZGScheme::verify(&commitment, &point, evaluation, &proof, &vk, &mut vt).unwrap(); - assert_eq!(pt.challenge(), vt.challenge()); + assert_eq!( + pt.verifier_message::<[u8; 32]>(), + vt.verifier_message::<[u8; 32]>() + ); } } @@ -225,7 +228,10 @@ fn malformed_shape_and_arity_return_errors_before_transcript_mutation() { ), Err(HyperKZGError::InvalidArity) ); - assert_eq!(actual.challenge(), transcript().challenge()); + assert_eq!( + actual.verifier_message::<[u8; 32]>(), + transcript().verifier_message::<[u8; 32]>() + ); } } @@ -282,31 +288,9 @@ fn bad_imports_and_false_prover_claims_reject() { .unwrap(); } -#[derive(Clone, Default)] -struct ZeroTranscript; - -impl Transcript for ZeroTranscript { - type Challenge = Fr; - // Scripted challenges: no proof of work. - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); - - fn new(_: &'static [u8]) -> Self { - Self - } - - fn append_bytes(&mut self, _: &[u8]) {} - - fn challenge(&mut self) -> Fr { - Fr::zero() - } - - fn challenge_bytes(&mut self, out: &mut [u8]) { - out.fill(0); - } - - fn state(&self) -> [u8; 32] { - [0; 32] - } +/// A transcript whose first challenge, the fold challenge, is zero. +fn zero_fold_challenge() -> ScriptedTranscript, Fr> { + ScriptedTranscript::with_script(transcript(), vec![Fr::zero()]) } #[test] @@ -319,7 +303,7 @@ fn zero_fold_challenge_rejects_on_both_paths() { f.evaluation, &f.proof, &f.vk, - &mut ZeroTranscript, + &mut zero_fold_challenge(), ), Err(HyperKZGError::DegenerateChallenge), ); @@ -331,7 +315,7 @@ fn zero_fold_challenge_rejects_on_both_paths() { Fr::one(), &pk, None, - &mut ZeroTranscript, + &mut zero_fold_challenge(), ) .is_err()); } diff --git a/crates/jolt-openings/fuzz/fuzz_targets/dory_homomorphic_batch_must_reject.rs b/crates/jolt-openings/fuzz/fuzz_targets/dory_homomorphic_batch_must_reject.rs index 694d2684ec..cbb84b2a4f 100644 --- a/crates/jolt-openings/fuzz/fuzz_targets/dory_homomorphic_batch_must_reject.rs +++ b/crates/jolt-openings/fuzz/fuzz_targets/dory_homomorphic_batch_must_reject.rs @@ -16,7 +16,7 @@ use jolt_openings::{ ZkBatchOpeningScheme, ZkOpeningScheme, }; use jolt_poly::{MultilinearPoly, Point, Polynomial, HIGH_TO_LOW}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, Transcript}; use libfuzzer_sys::fuzz_target; const MAX_NUM_VARS: usize = 4; @@ -103,7 +103,7 @@ fn prove_clear( polynomials: &[Polynomial], hints: Vec, ) -> ::Proof { - let mut transcript = Blake2bTranscript::new(CLEAR_LABEL); + let mut transcript = prover_transcript::(CLEAR_LABEL, b""); ::prove_batch( setup, claims, @@ -148,17 +148,17 @@ fuzz_target!(|data: &[u8]| { match data[2] % 10 { 0 => { let (claims, hints) = commit_clear(&polynomials, &point, prover_setup); - let mut prover_transcript = Blake2bTranscript::new(CLEAR_LABEL); + let mut prover_state = prover_transcript::(CLEAR_LABEL, b""); let proof = ::prove_batch( prover_setup, claims.clone(), sources(&polynomials), hints, - &mut prover_transcript, + &mut prover_state, ) .unwrap_or_else(|error| panic!("Dory clear batch proof failed: {error}")); - let mut verifier_transcript = Blake2bTranscript::new(CLEAR_LABEL); + let mut verifier_transcript = prover_transcript::(CLEAR_LABEL, b""); ::verify_batch( verifier_setup, &claims, @@ -166,14 +166,17 @@ fuzz_target!(|data: &[u8]| { &mut verifier_transcript, ) .unwrap_or_else(|error| panic!("honest clear batch verification failed: {error}")); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } 1 => { let (mut claims, hints) = commit_clear(&polynomials, &point, prover_setup); let proof = prove_clear(prover_setup, claims.clone(), &polynomials, hints); claims[0].evaluation.value += Fr::from_u64(1); - let mut transcript = Blake2bTranscript::new(CLEAR_LABEL); + let mut transcript = prover_transcript::(CLEAR_LABEL, b""); assert!( ::verify_batch( verifier_setup, @@ -197,7 +200,7 @@ fuzz_target!(|data: &[u8]| { let mut tampered = claims; tampered[0].commitment = wrong_commitment; - let mut transcript = Blake2bTranscript::new(CLEAR_LABEL); + let mut transcript = prover_transcript::(CLEAR_LABEL, b""); assert!( ::verify_batch( verifier_setup, @@ -212,7 +215,7 @@ fuzz_target!(|data: &[u8]| { 3 => { let (mut claims, hints) = commit_clear(&polynomials, &point, prover_setup); claims[1].evaluation.point = shifted_point(&point); - let mut transcript = Blake2bTranscript::new(CLEAR_LABEL); + let mut transcript = prover_transcript::(CLEAR_LABEL, b""); assert!( ::prove_batch( prover_setup, @@ -230,7 +233,7 @@ fuzz_target!(|data: &[u8]| { let wrong = Polynomial::new(vec![Fr::from_u64(1); 1usize << (num_vars + 1)]); let mut polynomial_sources = sources(&polynomials); polynomial_sources[0] = &wrong; - let mut transcript = Blake2bTranscript::new(CLEAR_LABEL); + let mut transcript = prover_transcript::(CLEAR_LABEL, b""); assert!( ::prove_batch( prover_setup, @@ -248,7 +251,7 @@ fuzz_target!(|data: &[u8]| { let mut polynomial_sources = sources(&polynomials); polynomial_sources.pop(); hints.pop(); - let mut transcript = Blake2bTranscript::new(CLEAR_LABEL); + let mut transcript = prover_transcript::(CLEAR_LABEL, b""); assert!( ::prove_batch( prover_setup, @@ -264,7 +267,7 @@ fuzz_target!(|data: &[u8]| { 6 => { let (claims, hints) = commit_clear(&polynomials, &point, prover_setup); let proof = prove_clear(prover_setup, claims.clone(), &polynomials, hints); - let mut transcript = Blake2bTranscript::new(b"fuzz-openings-clear-alt"); + let mut transcript = prover_transcript::(b"fuzz-openings-clear-alt", b""); assert!( ::verify_batch( verifier_setup, @@ -278,7 +281,7 @@ fuzz_target!(|data: &[u8]| { } 7 => { let (commitments, hints, evaluations) = commit_zk(&polynomials, &point, prover_setup); - let mut prover_transcript = Blake2bTranscript::new(ZK_LABEL); + let mut prover_state = prover_transcript::(ZK_LABEL, b""); let opening = ::prove_batch_zk( prover_setup, point.clone(), @@ -286,11 +289,11 @@ fuzz_target!(|data: &[u8]| { sources(&polynomials), hints, evaluations, - &mut prover_transcript, + &mut prover_state, ) .unwrap_or_else(|error| panic!("Dory ZK batch proof failed: {error}")); - let mut verifier_transcript = Blake2bTranscript::new(ZK_LABEL); + let mut verifier_transcript = prover_transcript::(ZK_LABEL, b""); let verifier_hiding = ::verify_batch_zk( verifier_setup, point, @@ -300,7 +303,10 @@ fuzz_target!(|data: &[u8]| { ) .unwrap_or_else(|error| panic!("honest ZK batch verification failed: {error}")); assert_eq!(opening.hiding_commitment, verifier_hiding); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } 8 => { let (commitments, mut hints, mut evaluations) = @@ -309,7 +315,7 @@ fuzz_target!(|data: &[u8]| { polynomial_sources.pop(); hints.pop(); evaluations.pop(); - let mut transcript = Blake2bTranscript::new(ZK_LABEL); + let mut transcript = prover_transcript::(ZK_LABEL, b""); assert!( ::prove_batch_zk( prover_setup, @@ -327,7 +333,7 @@ fuzz_target!(|data: &[u8]| { _ => { let (mut commitments, hints, evaluations) = commit_zk(&polynomials, &point, prover_setup); - let mut prover_transcript = Blake2bTranscript::new(ZK_LABEL); + let mut prover_state = prover_transcript::(ZK_LABEL, b""); let opening = ::prove_batch_zk( prover_setup, point.clone(), @@ -335,7 +341,7 @@ fuzz_target!(|data: &[u8]| { sources(&polynomials), hints, evaluations, - &mut prover_transcript, + &mut prover_state, ) .unwrap_or_else(|error| panic!("Dory ZK batch proof failed: {error}")); @@ -348,7 +354,7 @@ fuzz_target!(|data: &[u8]| { } commitments[0] = wrong_commitment; - let mut verifier_transcript = Blake2bTranscript::new(ZK_LABEL); + let mut verifier_transcript = prover_transcript::(ZK_LABEL, b""); assert!( ::verify_batch_zk( verifier_setup, diff --git a/crates/jolt-openings/src/claims.rs b/crates/jolt-openings/src/claims.rs index 3e02d7389f..ae88646375 100644 --- a/crates/jolt-openings/src/claims.rs +++ b/crates/jolt-openings/src/claims.rs @@ -1,6 +1,6 @@ use jolt_field::JoltField; use jolt_poly::EvaluationClaim; -use jolt_transcript::{AppendToTranscript, Label, LabelWithCount, Transcript}; +use jolt_transcript::{Encoding, Label, LabelWithCount}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ZkEvaluationClaim<'a, F, C> { @@ -17,21 +17,22 @@ impl<'a, F, C> ZkEvaluationClaim<'a, F, C> { } } -impl AppendToTranscript for ZkEvaluationClaim<'_, F, C> +impl Encoding for ZkEvaluationClaim<'_, F, C> where F: JoltField, - C: AppendToTranscript, + C: Encoding, { - fn append_to_transcript(&self, transcript: &mut T) { - transcript.append(&LabelWithCount( - b"zk_opening_point", - self.point.len() as u64, - )); + fn encode(&self) -> impl AsRef<[u8]> { + let mut out = LabelWithCount(b"zk_opening_point", self.point.len() as u64) + .encode() + .as_ref() + .to_vec(); for coordinate in self.point { - coordinate.append_to_transcript(transcript); + out.extend_from_slice(coordinate.encode().as_ref()); } - transcript.append(&Label(b"zk_eval_commitment")); - self.hiding_commitment.append_to_transcript(transcript); + out.extend_from_slice(Label(b"zk_eval_commitment").encode().as_ref()); + out.extend_from_slice(self.hiding_commitment.encode().as_ref()); + out } } @@ -43,14 +44,18 @@ pub struct VerifierOpeningClaim { pub(crate) struct VerifierRlcClaims<'a, F: JoltField, C>(pub &'a [VerifierOpeningClaim]); -impl AppendToTranscript for VerifierRlcClaims<'_, F, C> +impl Encoding for VerifierRlcClaims<'_, F, C> where F: JoltField, { - fn append_to_transcript(&self, transcript: &mut T) { - transcript.append(&LabelWithCount(b"rlc_claims", self.0.len() as u64)); + fn encode(&self) -> impl AsRef<[u8]> { + let mut out = LabelWithCount(b"rlc_claims", self.0.len() as u64) + .encode() + .as_ref() + .to_vec(); for claim in self.0 { - claim.evaluation.value.append_to_transcript(transcript); + out.extend_from_slice(claim.evaluation.value.encode().as_ref()); } + out } } diff --git a/crates/jolt-openings/src/error.rs b/crates/jolt-openings/src/error.rs index 7d51a24b1d..ef287c12df 100644 --- a/crates/jolt-openings/src/error.rs +++ b/crates/jolt-openings/src/error.rs @@ -17,4 +17,15 @@ pub enum OpeningsError { #[error("polynomial size {poly_size} exceeds setup max {setup_max}")] PolynomialTooLarge { poly_size: usize, setup_max: usize }, + + /// The transcript refused a challenge draw: the codec does not support the + /// request, or its rejection pool ran out. + #[error("transcript rejected a challenge draw")] + TranscriptRejected, +} + +impl From for OpeningsError { + fn from(_: jolt_transcript::VerificationError) -> Self { + Self::TranscriptRejected + } } diff --git a/crates/jolt-openings/src/prefix.rs b/crates/jolt-openings/src/prefix.rs index 93bfc5860e..21f4d69643 100644 --- a/crates/jolt-openings/src/prefix.rs +++ b/crates/jolt-openings/src/prefix.rs @@ -17,7 +17,7 @@ use std::collections::BTreeMap; use jolt_field::JoltField; use jolt_poly::{eq_index_msb, Point, HIGH_TO_LOW}; -use jolt_transcript::{Label, Transcript, U64Word}; +use jolt_transcript::{FieldTranscript, Label, U64Word}; use crate::{EvaluationClaim, OpeningsError}; @@ -188,7 +188,7 @@ where ) -> Result, OpeningsError> where F: JoltField, - T: Transcript, + T: FieldTranscript, { if claims.point.len() != self.logical_num_vars { return Err(OpeningsError::InvalidBatch(format!( @@ -213,7 +213,7 @@ where transcript.append_values(b"prefix_pack_point", claims.point.as_slice()); transcript.append_values(b"prefix_pack_evals", &claims.evaluations); - let selector = transcript.challenge_vector(self.selector_num_vars); + let selector = transcript.challenge_vector(self.selector_num_vars)?; let point = self.pack_point(&selector, claims.point.as_slice())?; let evaluation = self.reduce_evaluations(&selector, &claims.evaluations)?; Ok(EvaluationClaim::new(point, evaluation)) diff --git a/crates/jolt-openings/src/schemes.rs b/crates/jolt-openings/src/schemes.rs index 612694e672..903fe5da0b 100644 --- a/crates/jolt-openings/src/schemes.rs +++ b/crates/jolt-openings/src/schemes.rs @@ -11,7 +11,7 @@ use std::{fmt::Debug, marker::PhantomData}; use jolt_crypto::{Commitment, HomomorphicCommitment}; use jolt_field::{JoltField, Ring}; use jolt_poly::{MultilinearPoly, Point, RlcSource, HIGH_TO_LOW}; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript}; use serde::{de::DeserializeOwned, Serialize}; use crate::claims::{VerifierOpeningClaim, VerifierRlcClaims, ZkEvaluationClaim}; @@ -79,7 +79,7 @@ pub trait CommitmentScheme: Commitment { eval: Self::Field, setup: &Self::ProverSetup, hint: Option, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result; fn verify( @@ -88,7 +88,7 @@ pub trait CommitmentScheme: Commitment { eval: Self::Field, proof: &Self::Proof, setup: &Self::VerifierSetup, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(), OpeningsError>; /// Commits a group of polynomials as one commitment object whose members @@ -117,7 +117,7 @@ pub trait CommitmentScheme: Commitment { _groups: Vec>, _final_group: GroupOpeningClaim, _final_hint: Self::OpeningHint, - _transcript: &mut impl Transcript, + _transcript: &mut impl FieldTranscript, ) -> Result { Err(OpeningsError::InvalidBatch( "this commitment scheme has no native group-batch opening".to_owned(), @@ -131,7 +131,7 @@ pub trait CommitmentScheme: Commitment { _groups: &[TaggedGroupOpeningClaim], _final_group: &GroupOpeningClaim, _proof: &Self::Proof, - _transcript: &mut impl Transcript, + _transcript: &mut impl FieldTranscript, ) -> Result<(), OpeningsError> { Err(OpeningsError::InvalidBatch( "this commitment scheme has no native group-batch opening".to_owned(), @@ -334,7 +334,7 @@ pub trait ZkOpeningScheme: CommitmentScheme { + 'static + Serialize + DeserializeOwned - + AppendToTranscript; + + Encoding; type Blind: Clone + Send + Sync; @@ -356,7 +356,7 @@ pub trait ZkOpeningScheme: CommitmentScheme { eval: Self::Field, setup: &Self::ProverSetup, hint: Self::OpeningHint, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(Self::Proof, Self::HidingCommitment, Self::Blind), OpeningsError>; /// Verify a ZK opening proof and return the hiding commitment to the @@ -366,7 +366,7 @@ pub trait ZkOpeningScheme: CommitmentScheme { point: &[Self::Field], proof: &Self::Proof, setup: &Self::VerifierSetup, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result; } @@ -430,7 +430,7 @@ pub trait BatchOpeningScheme { ) -> Result where Self: 'a, - T: Transcript; + T: FieldTranscript; fn verify_batch( setup: &Self::VerifierSetup, @@ -439,7 +439,7 @@ pub trait BatchOpeningScheme { transcript: &mut T, ) -> Result<(), OpeningsError> where - T: Transcript; + T: FieldTranscript; } /// The prover-side outputs of a hiding batch opening: the native proof, the @@ -476,7 +476,7 @@ pub trait ZkBatchOpeningScheme: BatchOpeningScheme { ) -> Result, OpeningsError> where Self: 'a, - T: Transcript; + T: FieldTranscript; fn verify_batch_zk( setup: &Self::VerifierSetup, @@ -486,7 +486,7 @@ pub trait ZkBatchOpeningScheme: BatchOpeningScheme { transcript: &mut T, ) -> Result where - T: Transcript; + T: FieldTranscript; } // Batching strategies are zero-sized marker types rather than impls on the @@ -527,7 +527,7 @@ where ) -> Result where Self: 'a, - T: Transcript, + T: FieldTranscript, { let statement = HomomorphicBatchStatement::new(&claims)?; if polynomials.len() != statement.claims.len() || hints.len() != statement.claims.len() { @@ -539,8 +539,8 @@ where ))); } - statement.append_to_transcript(transcript); - let scalars = transcript.challenge_scalar_powers(statement.claims.len()); + transcript.append(&statement); + let scalars = transcript.challenge_powers(statement.claims.len())?; let joint_eval = statement.joint_eval(&scalars); let joint_polynomial = Self::combine_polynomials(polynomials, &scalars, statement.point.len())?; @@ -553,7 +553,7 @@ where Some(combined_hint), transcript, )?; - EvaluationClaim::new(statement.point.clone(), joint_eval).append_to_transcript(transcript); + transcript.append(&EvaluationClaim::new(statement.point.clone(), joint_eval)); Ok(proof) } @@ -564,11 +564,11 @@ where transcript: &mut T, ) -> Result<(), OpeningsError> where - T: Transcript, + T: FieldTranscript, { let statement = HomomorphicBatchStatement::new(claims)?; - statement.append_to_transcript(transcript); - let scalars = transcript.challenge_scalar_powers(statement.claims.len()); + transcript.append(&statement); + let scalars = transcript.challenge_powers(statement.claims.len())?; let joint_eval = statement.joint_eval(&scalars); let commitments = statement.commitments(); let joint_commitment = PCS::combine(&commitments, &scalars); @@ -580,7 +580,7 @@ where setup, transcript, )?; - EvaluationClaim::new(statement.point.clone(), joint_eval).append_to_transcript(transcript); + transcript.append(&EvaluationClaim::new(statement.point.clone(), joint_eval)); Ok(()) } } @@ -650,12 +650,12 @@ where } } -impl AppendToTranscript for HomomorphicBatchStatement<'_, F, C> +impl Encoding for HomomorphicBatchStatement<'_, F, C> where F: JoltField, { - fn append_to_transcript(&self, transcript: &mut T) { - VerifierRlcClaims(self.claims).append_to_transcript(transcript); + fn encode(&self) -> impl AsRef<[u8]> { + VerifierRlcClaims(self.claims).encode().as_ref().to_vec() } } @@ -684,7 +684,7 @@ where ) -> Result, OpeningsError> where Self: 'a, - T: Transcript, + T: FieldTranscript, { if commitments.is_empty() { return Err(OpeningsError::InvalidBatch( @@ -703,7 +703,7 @@ where commitments.len() ))); } - let scalars = transcript.challenge_scalar_powers(commitments.len()); + let scalars = transcript.challenge_powers(commitments.len())?; let joint_eval = evaluations .iter() @@ -721,8 +721,10 @@ where combined_hint, transcript, )?; - ZkEvaluationClaim::new(point.as_slice(), &hiding_commitment) - .append_to_transcript(transcript); + transcript.append(&ZkEvaluationClaim::new( + point.as_slice(), + &hiding_commitment, + )); Ok(ZkBatchOpening { proof, hiding_commitment, @@ -739,14 +741,14 @@ where transcript: &mut T, ) -> Result where - T: Transcript, + T: FieldTranscript, { if commitments.is_empty() { return Err(OpeningsError::InvalidBatch( "batch opening requires at least one commitment".to_owned(), )); } - let scalars = transcript.challenge_scalar_powers(commitments.len()); + let scalars = transcript.challenge_powers(commitments.len())?; let joint_commitment = PCS::combine(&commitments, &scalars); let hiding_commitment = PCS::verify_zk( &joint_commitment, @@ -755,8 +757,10 @@ where setup, transcript, )?; - ZkEvaluationClaim::new(point.as_slice(), &hiding_commitment) - .append_to_transcript(transcript); + transcript.append(&ZkEvaluationClaim::new( + point.as_slice(), + &hiding_commitment, + )); Ok(hiding_commitment) } } diff --git a/crates/jolt-openings/tests/homomorphic_dory.rs b/crates/jolt-openings/tests/homomorphic_dory.rs index 658d7906b8..d4b25684b4 100644 --- a/crates/jolt-openings/tests/homomorphic_dory.rs +++ b/crates/jolt-openings/tests/homomorphic_dory.rs @@ -10,7 +10,7 @@ use jolt_openings::{ BatchOpeningScheme, HomomorphicBatch, OpeningsError, ZkBatchOpeningScheme, ZkOpeningScheme, }; use jolt_poly::{Point, Polynomial, HIGH_TO_LOW}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; #[path = "support/common.rs"] pub mod common; @@ -26,17 +26,17 @@ fn dory_homomorphic_batch_roundtrip_clear_many_polynomials() { let verifier_setup = DoryScheme::setup_verifier(point.len()); let (claims, hints) = clear_claims::(&polynomials, &point, &prover_setup); - let mut prover_transcript = Blake2bTranscript::new(b"dory-batch"); + let mut prover_state = prover_transcript::(b"dory-batch", b""); let proof = ::prove_batch( &prover_setup, claims.clone(), sources(&polynomials), hints, - &mut prover_transcript, + &mut prover_state, ) .expect("Dory homomorphic batch proof should be produced"); - let mut verifier_transcript = Blake2bTranscript::new(b"dory-batch"); + let mut verifier_transcript = prover_transcript::(b"dory-batch", b""); ::verify_batch( &verifier_setup, &claims, @@ -45,7 +45,10 @@ fn dory_homomorphic_batch_roundtrip_clear_many_polynomials() { ) .expect("Dory homomorphic batch proof should verify"); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } #[test] @@ -55,20 +58,20 @@ fn dory_homomorphic_batch_rejects_tampered_value() { let verifier_setup = DoryScheme::setup_verifier(point.len()); let (claims, hints) = clear_claims::(&polynomials, &point, &prover_setup); - let mut prover_transcript = Blake2bTranscript::new(b"dory-batch-tamper"); + let mut prover_state = prover_transcript::(b"dory-batch-tamper", b""); let proof = ::prove_batch( &prover_setup, claims.clone(), sources(&polynomials), hints, - &mut prover_transcript, + &mut prover_state, ) .expect("Dory homomorphic batch proof should be produced"); let mut tampered = claims; tampered[1].evaluation.value += fr(1); - let mut verifier_transcript = Blake2bTranscript::new(b"dory-batch-tamper"); + let mut verifier_transcript = prover_transcript::(b"dory-batch-tamper", b""); let result = ::verify_batch( &verifier_setup, &tampered, @@ -87,7 +90,7 @@ fn dory_homomorphic_batch_rejects_wrong_witness_dimension() { let mut polynomial_sources = sources(&polynomials); polynomial_sources[0] = &wrong_witness; - let mut transcript = Blake2bTranscript::new(b"dory-batch-witness-dim"); + let mut transcript = prover_transcript::(b"dory-batch-witness-dim", b""); let result = ::prove_batch( &prover_setup, claims, @@ -114,7 +117,7 @@ fn dory_homomorphic_zk_batch_roundtrip() { evaluations.push(polynomial.evaluate(&point)); } - let mut prover_transcript = Blake2bTranscript::new(b"dory-batch-zk"); + let mut prover_state = prover_transcript::(b"dory-batch-zk", b""); let opening = ::prove_batch_zk( &prover_setup, point.clone(), @@ -122,11 +125,11 @@ fn dory_homomorphic_zk_batch_roundtrip() { sources(&polynomials), hints, evaluations, - &mut prover_transcript, + &mut prover_state, ) .expect("Dory ZK homomorphic batch proof should be produced"); - let mut verifier_transcript = Blake2bTranscript::new(b"dory-batch-zk"); + let mut verifier_transcript = prover_transcript::(b"dory-batch-zk", b""); let verifier_hiding = ::verify_batch_zk( &verifier_setup, point, @@ -137,7 +140,10 @@ fn dory_homomorphic_zk_batch_roundtrip() { .expect("Dory ZK homomorphic batch proof should verify"); assert_eq!(opening.hiding_commitment, verifier_hiding); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } struct ZkBatchFixture { @@ -162,7 +168,7 @@ fn zk_batch_fixture(seed: u64, label: &'static [u8]) -> ZkBatchFixture { evaluations.push(polynomial.evaluate(&point)); } - let mut transcript = Blake2bTranscript::new(label); + let mut transcript = prover_transcript::(label, b""); let opening = ::prove_batch_zk( &prover_setup, point.clone(), @@ -189,7 +195,7 @@ fn verify_zk_fixture( commitments: Vec, proof: &DoryProof, ) -> Result<::HidingCommitment, OpeningsError> { - let mut transcript = Blake2bTranscript::new(label); + let mut transcript = prover_transcript::(label, b""); ::verify_batch_zk( &fixture.verifier_setup, point, diff --git a/crates/jolt-openings/tests/prefix_packed_layout.rs b/crates/jolt-openings/tests/prefix_packed_layout.rs index b01cf37b89..df77f3fcce 100644 --- a/crates/jolt-openings/tests/prefix_packed_layout.rs +++ b/crates/jolt-openings/tests/prefix_packed_layout.rs @@ -3,7 +3,7 @@ use jolt_field::{Fr, Ring}; use jolt_openings::{OpeningsError, PrefixPackedClaims, PrefixPackedLayout}; use jolt_poly::Polynomial; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; fn fr(value: u64) -> Fr { Fr::from_u64(value) @@ -82,7 +82,7 @@ fn claim_reduction_binds_statement_before_drawing_selector() { let layout = PrefixPackedLayout::new(2, 4, [0_u64, 1, 2]).unwrap(); let claims = PrefixPackedClaims::new([9_u8; 32], vec![fr(3), fr(5)], vec![fr(7), fr(11), fr(13)]); - let mut transcript = Blake2bTranscript::new(b"prefix-packed-claim"); + let mut transcript = prover_transcript::(b"prefix-packed-claim", b""); let reduced = layout.reduce_claims(&claims, &mut transcript).unwrap(); assert_eq!(reduced.point.len(), layout.packed_num_vars()); @@ -100,7 +100,7 @@ fn claim_reduction_binds_statement_before_drawing_selector() { .unwrap() ); - let mut changed_transcript = Blake2bTranscript::new(b"prefix-packed-claim"); + let mut changed_transcript = prover_transcript::(b"prefix-packed-claim", b""); let changed = PrefixPackedClaims::new([9_u8; 32], vec![fr(3), fr(5)], vec![fr(7), fr(11), fr(17)]); let changed_reduced = layout diff --git a/crates/jolt-openings/tests/schemes.rs b/crates/jolt-openings/tests/schemes.rs index c17727a815..86beb4815a 100644 --- a/crates/jolt-openings/tests/schemes.rs +++ b/crates/jolt-openings/tests/schemes.rs @@ -9,7 +9,7 @@ use jolt_openings::{ BatchOpeningScheme, HomomorphicBatch, OpeningsError, ZkBatchOpeningScheme, ZkOpeningScheme, }; use jolt_poly::{Point, Polynomial, HIGH_TO_LOW}; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512}; #[path = "support/common.rs"] pub mod common; @@ -37,7 +37,7 @@ fn unit_hints(count: usize) -> Vec<()> { #[test] fn homomorphic_batch_opening_rejects_empty_claims() { - let mut transcript = Blake2bTranscript::new(b"batch-empty"); + let mut transcript = prover_transcript::(b"batch-empty", b""); let result = ::prove_batch( &(), Vec::new(), @@ -54,7 +54,7 @@ fn homomorphic_batch_opening_rejects_mismatched_points() { let (mut claims, _) = clear_claims::(&polynomials, &point, &()); claims[1].evaluation.point = Point::new(vec![fr(8), fr(13), fr(21)]); - let mut transcript = Blake2bTranscript::new(b"batch-point-mismatch"); + let mut transcript = prover_transcript::(b"batch-point-mismatch", b""); let result = ::prove_batch( &(), claims, @@ -70,7 +70,7 @@ fn homomorphic_batch_opening_rejects_mismatched_witness_count() { let (polynomials, point) = batch_polynomials(); let (claims, _) = clear_claims::(&polynomials, &point, &()); - let mut transcript = Blake2bTranscript::new(b"batch-mismatch"); + let mut transcript = prover_transcript::(b"batch-mismatch", b""); let result = ::prove_batch( &(), claims, @@ -104,7 +104,7 @@ fn homomorphic_zk_batch_opening_rejects_witness_count_mismatch() { let (polynomials, point) = batch_polynomials(); let commitments = zk_commitments(&polynomials); - let mut transcript = Blake2bTranscript::new(b"batch-zk-mismatch"); + let mut transcript = prover_transcript::(b"batch-zk-mismatch", b""); let result = ::prove_batch_zk( &(), point.clone(), diff --git a/crates/jolt-openings/tests/support/mock.rs b/crates/jolt-openings/tests/support/mock.rs index 832bb72271..5d3fedd74b 100644 --- a/crates/jolt-openings/tests/support/mock.rs +++ b/crates/jolt-openings/tests/support/mock.rs @@ -4,7 +4,7 @@ use jolt_crypto::{Commitment, HomomorphicCommitment}; use jolt_field::JoltField; use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, OpeningsError, ZkOpeningScheme}; use jolt_poly::{MultilinearPoly, Polynomial}; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -31,11 +31,12 @@ pub struct MockProof { evaluations: Vec, } -impl AppendToTranscript for MockCommitment { - fn append_to_transcript(&self, transcript: &mut T) { - for evaluation in &self.evaluations { - evaluation.append_to_transcript(transcript); - } +impl Encoding for MockCommitment { + fn encode(&self) -> impl AsRef<[u8]> { + self.evaluations + .iter() + .flat_map(|evaluation| evaluation.encode().as_ref().to_vec()) + .collect::>() } } @@ -77,7 +78,7 @@ where _eval: Self::Field, _setup: &Self::ProverSetup, _hint: Option<()>, - _transcript: &mut impl Transcript, + _transcript: &mut impl FieldTranscript, ) -> Result { let evaluations = poly.to_dense().into_owned(); Ok(MockProof { evaluations }) @@ -89,7 +90,7 @@ where eval: Self::Field, proof: &Self::Proof, _setup: &Self::VerifierSetup, - _transcript: &mut impl Transcript, + _transcript: &mut impl FieldTranscript, ) -> Result<(), OpeningsError> { if commitment.evaluations != proof.evaluations { return Err(OpeningsError::VerificationFailed); @@ -146,9 +147,9 @@ pub struct MockHidingCommitment { pub eval: F, } -impl AppendToTranscript for MockHidingCommitment { - fn append_to_transcript(&self, transcript: &mut T) { - self.eval.append_to_transcript(transcript); +impl Encoding for MockHidingCommitment { + fn encode(&self) -> impl AsRef<[u8]> { + self.eval.encode() } } @@ -172,7 +173,7 @@ where eval: Self::Field, _setup: &Self::ProverSetup, _hint: Self::OpeningHint, - _transcript: &mut impl Transcript, + _transcript: &mut impl FieldTranscript, ) -> Result<(Self::Proof, Self::HidingCommitment, Self::Blind), OpeningsError> { let evaluations = poly.to_dense().into_owned(); Ok((MockProof { evaluations }, MockHidingCommitment { eval }, ())) @@ -183,7 +184,7 @@ where point: &[Self::Field], proof: &Self::Proof, _setup: &Self::VerifierSetup, - _transcript: &mut impl Transcript, + _transcript: &mut impl FieldTranscript, ) -> Result { if commitment.evaluations != proof.evaluations { return Err(OpeningsError::VerificationFailed); From b307a5f5f953e2fd6036010f5aa69dc5d25f2a22 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Tue, 20 Oct 2026 23:21:49 -0400 Subject: [PATCH 04/16] refactor(jolt): port verifier and prover libs to the spongefish transcript Bind the full statement as the spongefish instance, absorb commitments as labeled messages, carry the NARG string in JoltProof and check its end, and move per-round grinding onto the shared SpongePow core. --- .../src/optimized/inc_claim_reduction.rs | 2 +- .../src/optimized/registers_val_evaluation.rs | 2 +- crates/jolt-kernels/src/reference/naive.rs | 27 +- crates/jolt-prover/Cargo.toml | 3 +- crates/jolt-prover/src/akita/preprocessing.rs | 2 - crates/jolt-prover/src/akita/prover.rs | 11 +- crates/jolt-prover/src/akita/stage0.rs | 27 +- crates/jolt-prover/src/akita/stage8.rs | 14 +- crates/jolt-prover/src/blindfold.rs | 43 +- crates/jolt-prover/src/dory/prover.rs | 16 +- crates/jolt-prover/src/dory/stages/stage0.rs | 44 +- crates/jolt-prover/src/dory/stages/stage8.rs | 4 +- crates/jolt-prover/src/driver.rs | 6 +- crates/jolt-prover/src/profile.rs | 13 +- crates/jolt-prover/src/recorder.rs | 6 +- crates/jolt-prover/src/stages/drivers.rs | 30 +- .../src/stages/field_inline_fixtures.rs | 42 +- crates/jolt-prover/src/stages/mod.rs | 22 + crates/jolt-prover/src/stages/stage1.rs | 44 +- crates/jolt-prover/src/stages/stage2.rs | 52 +- crates/jolt-prover/src/stages/stage3.rs | 10 +- crates/jolt-prover/src/stages/stage4.rs | 33 +- crates/jolt-prover/src/stages/stage5.rs | 27 +- crates/jolt-prover/src/stages/stage6a.rs | 27 +- crates/jolt-prover/src/stages/stage6b.rs | 35 +- crates/jolt-prover/src/stages/stage7.rs | 10 +- crates/jolt-prover/tests/e2e_matrix.rs | 6 +- crates/jolt-prover/tests/engine_twins.rs | 47 +- .../tests/support/field_inline/dory.rs | 6 +- crates/jolt-prover/tests/zk_e2e.rs | 10 +- crates/jolt-spartan-prover/src/lib.rs | 12 +- crates/jolt-spartan-prover/tests/spartan.rs | 10 +- crates/jolt-spartan-verifier/src/key.rs | 20 +- crates/jolt-spartan-verifier/src/lib.rs | 18 +- crates/jolt-sumcheck/src/recorder.rs | 16 +- crates/jolt-transcript/src/lib.rs | 6 +- crates/jolt-transcript/src/prover.rs | 21 +- crates/jolt-transcript/src/scripted.rs | 120 ++++- crates/jolt-transcript/src/verifier.rs | 19 +- crates/jolt-verifier-derive/src/lib.rs | 25 +- crates/jolt-verifier/Cargo.toml | 1 + crates/jolt-verifier/src/error.rs | 16 + crates/jolt-verifier/src/lib.rs | 5 +- crates/jolt-verifier/src/proof.rs | 7 + crates/jolt-verifier/src/stages/pow.rs | 26 +- crates/jolt-verifier/src/stages/relations.rs | 170 ++----- .../jolt-verifier/src/stages/stage1/verify.rs | 6 +- .../src/stages/stage2/outputs.rs | 10 +- .../src/stages/stage2/ram_output_check.rs | 6 +- .../jolt-verifier/src/stages/stage2/verify.rs | 8 +- .../jolt-verifier/src/stages/stage3/verify.rs | 4 +- .../src/stages/stage4/outputs.rs | 12 +- .../src/stages/stage4/ram_val_check.rs | 66 +-- .../jolt-verifier/src/stages/stage4/verify.rs | 4 +- .../src/stages/stage5/outputs.rs | 6 +- .../jolt-verifier/src/stages/stage5/verify.rs | 4 +- .../src/stages/stage6a/booleanity.rs | 8 +- .../src/stages/stage6a/verify.rs | 61 +-- .../jolt-verifier/src/stages/stage6b/batch.rs | 43 +- .../src/stages/stage6b/verify.rs | 16 +- .../jolt-verifier/src/stages/stage7/verify.rs | 4 +- .../jolt-verifier/src/stages/stage8/akita.rs | 8 +- .../jolt-verifier/src/stages/stage8/verify.rs | 24 +- crates/jolt-verifier/src/stages/uniskip.rs | 25 +- crates/jolt-verifier/src/verifier.rs | 467 +++++++----------- crates/jolt-verifier/tests/completeness/zk.rs | 6 +- .../tests/statistical_independence/zk.rs | 18 +- .../tests/support/fs_transcript.rs | 8 +- .../tests/support/verifier_fixtures.rs | 14 +- .../jolt-verifier/tests/support/zk_audit.rs | 10 +- 70 files changed, 972 insertions(+), 979 deletions(-) diff --git a/crates/jolt-kernels/src/optimized/inc_claim_reduction.rs b/crates/jolt-kernels/src/optimized/inc_claim_reduction.rs index e96b9a84a3..73da27764d 100644 --- a/crates/jolt-kernels/src/optimized/inc_claim_reduction.rs +++ b/crates/jolt-kernels/src/optimized/inc_claim_reduction.rs @@ -322,7 +322,7 @@ impl ProveRounds for IncKernel { let group = group(y)?; acc[0] += group[0]; acc[1] += group[1]; - Ok(acc) + Ok::<_, SumcheckError>(acc) }, ) .try_reduce(|| vec![F::zero(); 2], |a, b| Ok(merge_evals(a, b)))?; diff --git a/crates/jolt-kernels/src/optimized/registers_val_evaluation.rs b/crates/jolt-kernels/src/optimized/registers_val_evaluation.rs index d455d7cb2e..5422c3e3dd 100644 --- a/crates/jolt-kernels/src/optimized/registers_val_evaluation.rs +++ b/crates/jolt-kernels/src/optimized/registers_val_evaluation.rs @@ -265,7 +265,7 @@ impl ProveRounds for ValEvaluationKernel { || [F::Accumulator::default(); 3], |mut acc, y| { group(y, &mut acc)?; - Ok(acc) + Ok::<_, SumcheckError>(acc) }, ) .map(|acc| acc.map(|acc| acc.map(F::Accumulator::reduce))) diff --git a/crates/jolt-kernels/src/reference/naive.rs b/crates/jolt-kernels/src/reference/naive.rs index 979a339b94..c62deaed30 100644 --- a/crates/jolt-kernels/src/reference/naive.rs +++ b/crates/jolt-kernels/src/reference/naive.rs @@ -453,7 +453,7 @@ mod tests { ProveRounds, SequentialRounds, SumcheckProof, SumcheckRecorder, OPENING_CLAIM_TRANSCRIPT_LABEL, }; - use jolt_transcript::{Blake2bTranscript, Transcript}; + use jolt_transcript::{prover_transcript, Blake2b512, Transcript}; use jolt_verifier::stages::relations::ConcreteSumcheck; use jolt_verifier::VerifierError; @@ -647,8 +647,8 @@ mod tests { symbolic: ToySymbolic::new(ROUNDS), reference_point: reference_point(), }; - let mut prover_transcript = Blake2bTranscript::new(b"naive-toy"); - let challenges = relation.draw_challenges(&mut prover_transcript).unwrap(); + let mut prover_state = prover_transcript::(b"naive-toy", b""); + let challenges = relation.draw_challenges(&mut prover_state).unwrap(); let gamma = challenges.gamma; let opening_tables = opening_tables(); @@ -667,8 +667,8 @@ mod tests { let input_claim = relation.input_claim(&inputs, &challenges).unwrap(); assert_eq!(input_claim, claimed_sum); let mut recorder = ClearSumcheckRecorder::::new(); - recorder.absorb_input_claims(&[input_claim], &mut prover_transcript); - let coefficient: Fr = prover_transcript.challenge_scalar(); + recorder.absorb_input_claims(&[input_claim], &mut prover_state); + let coefficient: Fr = prover_state.challenge()?; let prelude = BatchPrelude::new( vec![BatchMember { input_claim, @@ -698,7 +698,7 @@ mod tests { &mut members, &mut SequentialRounds, &mut recorder, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); @@ -730,13 +730,13 @@ mod tests { assert_eq!(proved.member_claims, vec![expected]); let recorded = recorder - .finish(&output_claims.opening_values(), &mut prover_transcript) + .finish(&output_claims.opening_values(), &mut prover_state) .unwrap(); let verifier_relation = ToyRelation { symbolic: ToySymbolic::new(ROUNDS), reference_point: reference_point(), }; - let mut verifier_transcript = Blake2bTranscript::new(b"naive-toy"); + let mut verifier_transcript = prover_transcript::(b"naive-toy", b""); let verifier_challenges = verifier_relation .draw_challenges(&mut verifier_transcript) .unwrap(); @@ -744,7 +744,7 @@ mod tests { .input_claim(&inputs, &verifier_challenges) .unwrap(); append_sumcheck_claim(&mut verifier_transcript, &verifier_input_claim); - let verifier_coefficient: Fr = verifier_transcript.challenge_scalar(); + let verifier_coefficient: Fr = verifier_transcript.challenge()?; let reduction = SumcheckProof::::from(recorded.proof) .verify_compressed_boolean( ROUNDS, @@ -759,7 +759,10 @@ mod tests { assert_eq!(reduction.value, proved.final_claim); assert_eq!(reduction.point.as_slice(), proved.challenges.as_slice()); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } /// A derived table materialized at the wrong point survives the sumcheck @@ -772,7 +775,7 @@ mod tests { symbolic: ToySymbolic::new(ROUNDS), reference_point: reference_point(), }; - let mut transcript = Blake2bTranscript::new(b"naive-drift"); + let mut transcript = prover_transcript::(b"naive-drift", b""); let challenges = relation.draw_challenges(&mut transcript).unwrap(); let gamma = challenges.gamma; @@ -781,7 +784,7 @@ mod tests { let derived_tables = derived_tables(&drifted_point); let claimed_sum = brute_force_sum(&opening_tables, &derived_tables, gamma); - let coefficient: Fr = transcript.challenge_scalar(); + let coefficient: Fr = transcript.challenge()?; let prelude = BatchPrelude::new( vec![BatchMember { input_claim: claimed_sum, diff --git a/crates/jolt-prover/Cargo.toml b/crates/jolt-prover/Cargo.toml index 2226777fa3..d10bc3d508 100644 --- a/crates/jolt-prover/Cargo.toml +++ b/crates/jolt-prover/Cargo.toml @@ -18,6 +18,7 @@ parallel = [ "jolt-crypto/parallel", "jolt-kernels/parallel", "jolt-sumcheck/parallel", + "jolt-transcript/parallel", "dep:rayon", ] prover-fixtures = ["dep:jolt-host"] @@ -112,7 +113,7 @@ jolt-poly = { workspace = true, default-features = false } jolt-program.workspace = true jolt-riscv.workspace = true jolt-sumcheck.workspace = true -jolt-transcript.workspace = true +jolt-transcript = { workspace = true, features = ["prover-grinding", "transcript-keccak"] } jolt-verifier.workspace = true jolt-witness.workspace = true rand_core = { workspace = true, features = ["getrandom"], optional = true } diff --git a/crates/jolt-prover/src/akita/preprocessing.rs b/crates/jolt-prover/src/akita/preprocessing.rs index e29273c159..5d2585eedb 100644 --- a/crates/jolt-prover/src/akita/preprocessing.rs +++ b/crates/jolt-prover/src/akita/preprocessing.rs @@ -12,7 +12,6 @@ use jolt_claims::protocols::jolt::{JoltAdviceKind, TracePolynomialOrder}; use jolt_crypto::NoVectorCommitment; use jolt_openings::{CommitmentScheme, TransparentObjectSetup}; use jolt_program::preprocess::JoltProgramPreprocessing; -use jolt_transcript::LegacyBlake2bTranscript; use jolt_verifier::{ CommittedProgramPreprocessing, JoltVerifierPreprocessing, ProgramPreprocessing, }; @@ -25,7 +24,6 @@ use super::one_hot_trace_setup_shape; use super::witness::{commit_advice, commit_direct_program, AdviceObject}; pub type AkitaVc = NoVectorCommitment; -pub type AkitaTranscript = LegacyBlake2bTranscript; pub type AkitaProverPreprocessing = JoltProverPreprocessing; pub type AkitaVerifierPreprocessing = JoltVerifierPreprocessing; diff --git a/crates/jolt-prover/src/akita/prover.rs b/crates/jolt-prover/src/akita/prover.rs index d19e56a1f6..5deb7b0103 100644 --- a/crates/jolt-prover/src/akita/prover.rs +++ b/crates/jolt-prover/src/akita/prover.rs @@ -8,7 +8,7 @@ use jolt_field::{CanonicalBytes, JoltField}; use jolt_openings::{ CommitmentScheme, GroupCommitmentMetadata, GroupSetupMetadata, TransparentObjectSetup, }; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, ProverInit}; use jolt_verifier::config::JoltProtocolConfig; use jolt_verifier::proof::{ClearProofClaims, JoltProof, JoltProofClaims, JoltStageProofs}; use jolt_verifier::stages::pow::{PowSite, StagePowNonces}; @@ -38,14 +38,14 @@ pub fn prove( public_io: &JoltDevice, ) -> Result, ProverError> where - F: JoltField + CanonicalBytes + AppendToTranscript, + F: JoltField + CanonicalBytes + Encoding, PCS: CommitmentScheme + TransparentObjectSetup, PCS::ProverSetup: GroupSetupMetadata, PCS::VerifierSetup: GroupSetupMetadata, - PCS::Output: Clone + PartialEq + AppendToTranscript + GroupCommitmentMetadata, + PCS::Output: Clone + PartialEq + Encoding + GroupCommitmentMetadata, VC: VectorCommitment, - VC::Output: Clone + AppendToTranscript, - T: Transcript, + VC::Output: Clone + Encoding, + T: FieldTranscript + ProverInit, { // The Akita path is transparent-only (`akita` and `zk` are mutually // exclusive), so the mode context carries nothing; the shared stage @@ -221,5 +221,6 @@ where rw_config: config.rw_config, one_hot_config: config.one_hot_config, trace_polynomial_order: config.trace_polynomial_order, + narg: transcript.narg_string().to_vec(), }) } diff --git a/crates/jolt-prover/src/akita/stage0.rs b/crates/jolt-prover/src/akita/stage0.rs index 950137c816..0029505a38 100644 --- a/crates/jolt-prover/src/akita/stage0.rs +++ b/crates/jolt-prover/src/akita/stage0.rs @@ -7,10 +7,10 @@ use jolt_claims::protocols::jolt::{JoltAdviceKind, JoltRelationId, TracePolynomi use jolt_crypto::VectorCommitment; use jolt_field::JoltField; use jolt_openings::{CommitmentScheme, GroupSetupMetadata, TransparentObjectSetup}; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, ProverInit}; use jolt_verifier::{ - absorb_akita_commitments, absorb_transcript_preamble, validate_inputs_from_parts, - CheckedInputs, ProofTranscriptConfig, + absorb_akita_commitments, validate_inputs_from_parts, CheckedInputs, JoltStatement, + ProofTranscriptConfig, JOLT_SESSION, }; use super::witness::{commit_advice, AdviceObject}; @@ -46,9 +46,9 @@ where PCS: CommitmentScheme + TransparentObjectSetup, PCS::ProverSetup: GroupSetupMetadata, PCS::VerifierSetup: GroupSetupMetadata, - PCS::Output: Clone + AppendToTranscript, + PCS::Output: Clone + Encoding, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverInit, { let setup = &preprocessing.verifier.pcs_setup; if config.akita_chunk_profile.num_chunks() != setup.one_hot_num_chunks() @@ -106,15 +106,16 @@ where false, )?; - let mut transcript = T::new(b"Jolt"); - absorb_transcript_preamble( - &checked, - ProofTranscriptConfig { - rw_config: config.rw_config, - one_hot_config: config.one_hot_config, - trace_polynomial_order: config.trace_polynomial_order, + let mut transcript = T::init( + JOLT_SESSION, + &JoltStatement { + checked: &checked, + config: ProofTranscriptConfig { + rw_config: config.rw_config, + one_hot_config: config.one_hot_config, + trace_polynomial_order: config.trace_polynomial_order, + }, }, - &mut transcript, ); let log_t = config.trace_length.ilog2() as usize; diff --git a/crates/jolt-prover/src/akita/stage8.rs b/crates/jolt-prover/src/akita/stage8.rs index 7b3de752c8..59c486b98a 100644 --- a/crates/jolt-prover/src/akita/stage8.rs +++ b/crates/jolt-prover/src/akita/stage8.rs @@ -13,8 +13,10 @@ use jolt_field::JoltField; use jolt_openings::{ CommitmentScheme, EvaluationClaim, GroupOpeningClaim, TaggedGroupOpeningClaim, }; -use jolt_transcript::{AppendToTranscript, Transcript}; -use jolt_verifier::stages::pow::{grind_pow, PowSite}; +use jolt_transcript::{Encoding, FieldTranscript, ProverTranscript}; +use jolt_verifier::stages::pow::PowSite; + +use crate::stages::grind_pow; use jolt_verifier::stages::stage4::outputs::Stage4ClearOutput; use jolt_verifier::stages::stage6b::outputs::Stage6bClearOutput; use jolt_verifier::stages::stage7::outputs::Stage7ClearOutput; @@ -43,7 +45,7 @@ fn reduce_precommitted( ) -> Result, ProverError> where F: JoltField, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let claims = object_leaf_claims(plan, leaves).map_err(ProverError::Verifier)?; let semantic = plan.packed_claims(&claims).map_err(batch_failed::)?; @@ -73,9 +75,9 @@ pub fn prove_stage8( where F: JoltField, PCS: CommitmentScheme, - PCS::Output: Clone + AppendToTranscript, + PCS::Output: Clone + Encoding, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let log_t = checked.trace_length.ilog2() as usize; let chunk_width = config.one_hot_config.committed_chunk_bits(); @@ -98,7 +100,7 @@ where let main_group = one_hot_trace_claim(&plan, chunk_width, &leaves, one_hot_trace_commitment) .map_err(ProverError::Verifier)?; - let pow_nonce = grind_pow( + let pow_nonce = grind_pow::( PowSite::Stage8, selector_loss( [untrusted_advice, trusted_advice] diff --git a/crates/jolt-prover/src/blindfold.rs b/crates/jolt-prover/src/blindfold.rs index ef2bc86ed2..689ca06099 100644 --- a/crates/jolt-prover/src/blindfold.rs +++ b/crates/jolt-prover/src/blindfold.rs @@ -27,13 +27,13 @@ use jolt_crypto::{HomomorphicCommitment, VectorCommitment}; use jolt_field::{Accumulator, JoltField, WithAccumulator}; use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, ZkOpeningScheme}; use jolt_sumcheck::{CommittedSumcheckWitness, SumcheckDomainSpec}; -use jolt_transcript::{AppendToTranscript, Label, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, Label, Probe, ProverInit}; use jolt_verifier::proof::JoltProof; use jolt_verifier::stages::zk::{blindfold, inputs::BlindFoldInputs}; use jolt_verifier::stages::{ stage1, stage2, stage3, stage4, stage5, stage6a, stage6b, stage7, stage8, }; -use jolt_verifier::VerifierError; +use jolt_verifier::{VerifierError, JOLT_SESSION}; use crate::{JoltProverPreprocessing, ProverError}; @@ -98,8 +98,8 @@ pub(crate) struct ZkFinalOpening { } /// Prove the BlindFold tail for `shell` (the assembled proof with a unit -/// claims placeholder). `forward_state` is the prover's own transcript state -/// at the stage-8 boundary — the replay must land on the same bytes. +/// claims placeholder). `forward` is the prover's own transcript at the +/// stage-8 boundary — the replay must land on the same sponge state. pub(crate) fn prove_blindfold( preprocessing: &JoltProverPreprocessing, public_io: &JoltDevice, @@ -107,17 +107,17 @@ pub(crate) fn prove_blindfold( shell: &JoltProof, witnesses: &ZkStageWitnesses, final_opening: &ZkFinalOpening, - forward_state: [u8; 32], + forward: &T, ) -> Result, ProverError> where - F: JoltField + AppendToTranscript, + F: JoltField + Encoding, PCS: CommitmentScheme + AdditivelyHomomorphic + ZkOpeningScheme, - PCS::Output: AppendToTranscript + HomomorphicCommitment, + PCS::Output: Encoding + HomomorphicCommitment, VC: VectorCommitment, - VC::Output: Copy + HomomorphicCommitment + AppendToTranscript, - T: Transcript, + VC::Output: Copy + HomomorphicCommitment + Encoding, + T: FieldTranscript + ProverInit, ::Accumulator: Accumulator, { let (protocol, mut transcript) = replay_stages::( @@ -127,10 +127,16 @@ where trusted_advice_commitment, )?; // A 32-byte compare guarding the exact seam the design rests on: the - // replay landing on the prover's forward transcript bytes. Hard error - // (not debug-only) so release provers diagnose drift here rather than - // as a downstream BlindFold verification failure. - if transcript.state() != forward_state { + // replay landing on the prover's forward sponge state, compared by what + // copies of the two sponges squeeze next. Hard error (not debug-only) so + // release provers diagnose drift here rather than as a downstream + // BlindFold verification failure. + let next_bytes = |transcript: &T| { + let mut out = [0u8; 32]; + transcript.message_search().probe(&[], &mut out); + out + }; + if next_bytes(&transcript) != next_bytes(forward) { return Err(ProverError::InvariantViolation { reason: "the verifier replay diverged from the prover's forward transcript", }); @@ -182,20 +188,21 @@ fn replay_stages( trusted_advice_commitment: Option<&PCS::Output>, ) -> Result<(BlindFoldProtocol, T), ProverError> where - F: JoltField + AppendToTranscript, + F: JoltField + Encoding, PCS: CommitmentScheme + AdditivelyHomomorphic + ZkOpeningScheme, - PCS::Output: AppendToTranscript + HomomorphicCommitment, + PCS::Output: Encoding + HomomorphicCommitment, VC: VectorCommitment, - VC::Output: Copy + HomomorphicCommitment + AppendToTranscript, - T: Transcript, + VC::Output: Copy + HomomorphicCommitment + Encoding, + T: FieldTranscript + ProverInit, { - let (checked, mut transcript) = jolt_verifier::validate_and_seed_transcript::( + let (checked, mut transcript) = jolt_verifier::validate_and_seed_transcript( preprocessing, public_io, shell, trusted_advice_commitment, + |statement| T::init(JOLT_SESSION, statement), )?; let formula_dimensions = jolt_verifier::stages::build_formula_dimensions( shell, diff --git a/crates/jolt-prover/src/dory/prover.rs b/crates/jolt-prover/src/dory/prover.rs index 07720ba9c2..f104e45c3a 100644 --- a/crates/jolt-prover/src/dory/prover.rs +++ b/crates/jolt-prover/src/dory/prover.rs @@ -7,7 +7,7 @@ use jolt_crypto::{HomomorphicCommitment, VectorCommitment}; use jolt_field::{Accumulator, JoltField, WithAccumulator}; use jolt_kernels::{JoltBackend, KernelContext}; use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, ZkOpeningScheme}; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, ProverInit}; use jolt_verifier::config::JoltProtocolConfig; #[cfg(not(feature = "zk"))] use jolt_verifier::proof::ClearProofClaims; @@ -57,17 +57,17 @@ pub fn prove( public_io: &JoltDevice, ) -> Result, ProverError> where - F: JoltField + AppendToTranscript, + F: JoltField + Encoding, PCS: CommitmentScheme + AdditivelyHomomorphic + ZkOpeningScheme, - PCS::Output: AppendToTranscript + HomomorphicCommitment, + PCS::Output: Encoding + HomomorphicCommitment, VC: VectorCommitment, - VC::Output: Copy + HomomorphicCommitment + AppendToTranscript, - T: Transcript, + VC::Output: Copy + HomomorphicCommitment + Encoding, + T: FieldTranscript + ProverInit, ::Accumulator: Accumulator, { - jolt_verifier::stages::pow::require_no_pow::()?; + jolt_verifier::stages::pow::require_no_pow::()?; let mode = ProofMode::::new(preprocessing.verifier.vc_setup.as_ref())?; let stage0 = prove_stage0::( &mut backend, @@ -217,6 +217,7 @@ where rw_config: config.rw_config, one_hot_config: config.one_hot_config, trace_polynomial_order: config.trace_polynomial_order, + narg: transcript.narg_string().to_vec(), }) } #[cfg(feature = "zk")] @@ -240,6 +241,7 @@ where rw_config: config.rw_config, one_hot_config: config.one_hot_config, trace_polynomial_order: config.trace_polynomial_order, + narg: transcript.narg_string().to_vec(), }; let witnesses = ZkStageWitnesses { stage1_uniskip: stage1.uniskip_witness, @@ -264,7 +266,7 @@ where &shell, &witnesses, &final_opening, - transcript.state(), + &transcript, )?; Ok(shell.with_claims(JoltProofClaims::Zk { blindfold_proof })) diff --git a/crates/jolt-prover/src/dory/stages/stage0.rs b/crates/jolt-prover/src/dory/stages/stage0.rs index 00d7021892..a53e95918e 100644 --- a/crates/jolt-prover/src/dory/stages/stage0.rs +++ b/crates/jolt-prover/src/dory/stages/stage0.rs @@ -20,13 +20,14 @@ use jolt_kernels::reference::bytecode_read_raf::BytecodeReadRafWitness; use jolt_kernels::reference::instruction_read_raf::InstructionReadRafWitness; use jolt_kernels::{CommitmentGrid, JoltBackend, KernelContext, WitnessCommitment}; use jolt_openings::CommitmentScheme; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, ProverInit}; use jolt_verifier::proof::JoltCommitments; #[cfg(feature = "field-inline")] use jolt_verifier::proof::{FieldInlineCommitments, FieldRegistersCommitments}; use jolt_verifier::{ absorb_committed_program_commitments, absorb_transcript_commitments, - absorb_transcript_preamble, validate_inputs_from_parts, CheckedInputs, ProofTranscriptConfig, + validate_inputs_from_parts, CheckedInputs, JoltStatement, ProofTranscriptConfig, + JOLT_SESSION, }; use jolt_witness::WitnessBundle; @@ -78,9 +79,9 @@ pub fn prove_stage0( where F: JoltField, PCS: CommitmentScheme, - PCS::Output: AppendToTranscript, + PCS::Output: Encoding, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverInit, { // Committed-program mode needs the prover-retained full program + hints; // require presence to agree with the verifier preprocessing's mode. @@ -156,15 +157,16 @@ where } } - let mut transcript = T::new(b"Jolt"); - absorb_transcript_preamble( - &checked, - ProofTranscriptConfig { - rw_config: config.rw_config, - one_hot_config: config.one_hot_config, - trace_polynomial_order: config.trace_polynomial_order, + let mut transcript = T::init( + JOLT_SESSION, + &JoltStatement { + checked: &checked, + config: ProofTranscriptConfig { + rw_config: config.rw_config, + one_hot_config: config.one_hot_config, + trace_polynomial_order: config.trace_polynomial_order, + }, }, - &mut transcript, ); let dimensions = crate::stages::formula_dimensions( @@ -407,7 +409,7 @@ mod field_inline_tests { use jolt_kernels::finish_streamed; use jolt_kernels::KernelError; use jolt_openings::{CommitmentScheme, StreamingCommitment}; - use jolt_transcript::LegacyBlake2bTranscript; + use jolt_transcript::{prover_transcript, Keccak, LegacyBlake2bTranscript}; fn grid() -> CommitmentGrid { CommitmentGrid { @@ -483,9 +485,9 @@ mod field_inline_tests { direct_dense_commitment(&column, &setup) ); - let mut prover_transcript = LegacyBlake2bTranscript::::new(b"Jolt"); - absorb_transcript_commitments(&commitments, None, None, &mut prover_transcript); - let mut verifier_transcript = LegacyBlake2bTranscript::::new(b"Jolt"); + let mut prover_state = Legacyprover_transcript::(b"Jolt", b""); + absorb_transcript_commitments(&commitments, None, None, &mut prover_state); + let mut verifier_transcript = Legacyprover_transcript::(b"Jolt", b""); jolt_verifier::absorb_transcript_commitments( &commitments, None, @@ -493,17 +495,17 @@ mod field_inline_tests { &mut verifier_transcript, ); assert_eq!( - prover_transcript.challenge(), - verifier_transcript.challenge() + prover_state.challenge()?, + verifier_transcript.challenge()? ); let mut stripped = commitments.clone(); stripped.field_inline = None; - let mut stripped_transcript = LegacyBlake2bTranscript::::new(b"Jolt"); + let mut stripped_transcript = Legacyprover_transcript::(b"Jolt", b""); absorb_transcript_commitments(&stripped, None, None, &mut stripped_transcript); assert_ne!( - prover_transcript.challenge(), - stripped_transcript.challenge(), + prover_state.challenge()?, + stripped_transcript.challenge()?, "the field-inline payload must be Fiat-Shamir-bound" ); } diff --git a/crates/jolt-prover/src/dory/stages/stage8.rs b/crates/jolt-prover/src/dory/stages/stage8.rs index a6ca7dff7c..63c3a55eca 100644 --- a/crates/jolt-prover/src/dory/stages/stage8.rs +++ b/crates/jolt-prover/src/dory/stages/stage8.rs @@ -44,7 +44,7 @@ use jolt_openings::{ VerifierOpeningClaim, ZkOpeningScheme, }; use jolt_poly::Point; -use jolt_transcript::Transcript; +use jolt_transcript::{FieldTranscript, ProverTranscript}; use jolt_verifier::proof::JoltCommitments; use jolt_verifier::stages::stage6b::outputs::Stage6bClearOutput; use jolt_verifier::stages::stage7::outputs::Stage7ClearOutput; @@ -90,7 +90,7 @@ where PCS: CommitmentScheme + AdditivelyHomomorphic + ZkOpeningScheme, PCS::Output: HomomorphicCommitment, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let log_t = checked.trace_length.ilog2() as usize; let precommitted = &checked.precommitted; diff --git a/crates/jolt-prover/src/driver.rs b/crates/jolt-prover/src/driver.rs index c3280ac8ca..87dceffadb 100644 --- a/crates/jolt-prover/src/driver.rs +++ b/crates/jolt-prover/src/driver.rs @@ -34,7 +34,7 @@ use jolt_poly::UnivariatePoly; use jolt_sumcheck::{ ProveRounds, RecordedSumcheck, RoundScheduler, SumcheckError, SumcheckRecorder, }; -use jolt_transcript::Transcript; +use jolt_transcript::{FieldTranscript, ProverTranscript}; use jolt_verifier::stages::relations::{ ConcreteSumcheck, ConcreteSumcheckChallenges, SumcheckInputClaims, SumcheckInputPoints, SumcheckOutputClaims, SumcheckOutputPoints, @@ -79,7 +79,7 @@ pub trait StageProver: Sized { B: ?Sized, for<'a> KernelContext<'a, F, B>: KernelSource, Rec: SumcheckRecorder, - T: Transcript; + T: FieldTranscript + ProverTranscript; /// The stage's absorbed opening scalars, in the stage's curated order /// (stage 6b's runtime point dedup reorders the returned values). The @@ -480,7 +480,7 @@ macro_rules! impl_stage_prover { B: ?Sized, for<'a> ::jolt_kernels::KernelContext<'a, F, B>: $crate::driver::KernelSource, Rec: ::jolt_sumcheck::SumcheckRecorder, - T: ::jolt_transcript::Transcript, + T: ::jolt_transcript::FieldTranscript + ::jolt_transcript::ProverTranscript, { let __stage_span = ::tracing::info_span!(concat!($label, "::prove")).entered(); diff --git a/crates/jolt-prover/src/profile.rs b/crates/jolt-prover/src/profile.rs index 0d5cea62ec..8c92fca080 100644 --- a/crates/jolt-prover/src/profile.rs +++ b/crates/jolt-prover/src/profile.rs @@ -70,7 +70,7 @@ type FieldInlineField = Fr; #[cfg(all(feature = "field-inline", feature = "akita"))] type FieldInlineField = AkitaField; #[cfg(not(feature = "akita"))] -use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; +use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; #[cfg(not(feature = "akita"))] use rayon::ThreadPoolBuilder; @@ -875,7 +875,7 @@ fn prove_workload( // `jolt_prover::prove` root span covers exactly this interval; the // Instant is the `--format none` no-subscriber baseline. let now = Instant::now(); - let proof = crate::dory::prove::, Blake2bTranscript>( + let proof = crate::dory::prove::, ProverState>( backend.with_witness(&witness), &prover_preprocessing, &config, @@ -901,7 +901,7 @@ fn prove_workload( .build() .expect("single-threaded verifier pool must build"); let verify = || { - jolt_verifier::verify::, Blake2bTranscript>( + jolt_verifier::verify::, VerifierState<'_, Keccak>>( &prover_preprocessing.verifier, &public_io, &proof, @@ -931,7 +931,8 @@ fn prove_workload( backend: BackendKind, chunk_profile: AkitaChunkProfile, ) -> ProvenRun { - use crate::akita::preprocessing::{AkitaTranscript, AkitaVc}; + use crate::akita::preprocessing::AkitaVc; + use jolt_transcript::{Keccak, ProverState, VerifierState}; use crate::JoltProverPreprocessing; use jolt_akita::{AkitaField, AkitaScheduleArtifacts, AkitaScheme}; use jolt_openings::CommitmentScheme; @@ -1000,7 +1001,7 @@ fn prove_workload( // The `jolt_prover::prove` root span covers exactly // this interval; the Instant is the `--format none` baseline. let now = Instant::now(); - let proof = crate::akita::prove::( + let proof = crate::akita::prove::>( backend.with_witness(&witness), &prover_preprocessing, &config, @@ -1026,7 +1027,7 @@ fn prove_workload( ); let verify = || { - jolt_verifier::verify::( + jolt_verifier::verify::>( &prover_preprocessing.verifier, &public_io, &proof, diff --git a/crates/jolt-prover/src/recorder.rs b/crates/jolt-prover/src/recorder.rs index 9743423cff..f215f593a8 100644 --- a/crates/jolt-prover/src/recorder.rs +++ b/crates/jolt-prover/src/recorder.rs @@ -21,7 +21,7 @@ use jolt_sumcheck::{prove_uniskip_clear, ClearSumcheckRecorder}; use jolt_sumcheck::{ prove_uniskip_committed, CommittedSumcheckRecorder, CommittedSumcheckWitness, RecordedSumcheck, }; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, ProverTranscript}; #[cfg(feature = "zk")] use rand_core::OsRng; @@ -103,8 +103,8 @@ impl<'a, VC: VectorCommitment> ProofMode<'a, VC> { transcript: &mut T, ) -> Result, ProverError> where - VC::Output: Clone + AppendToTranscript, - T: Transcript, + VC::Output: Clone + Encoding, + T: FieldTranscript + ProverTranscript, { #[cfg(feature = "zk")] { diff --git a/crates/jolt-prover/src/stages/drivers.rs b/crates/jolt-prover/src/stages/drivers.rs index 46ad442b4f..9c4c1d1de6 100644 --- a/crates/jolt-prover/src/stages/drivers.rs +++ b/crates/jolt-prover/src/stages/drivers.rs @@ -196,7 +196,7 @@ mod twin_tests { ClearSumcheckRecorder, CompressedSumcheckProof, ProveRounds, SequentialRounds, SumcheckError, SumcheckProof, }; - use jolt_transcript::{Blake2bTranscript, Transcript}; + use jolt_transcript::{prover_transcript, Keccak, Transcript}; use jolt_verifier::stages::relations::{ConcreteSumcheck, SumcheckBatch, SumcheckOutputClaims}; use jolt_verifier::VerifierError; use jolt_witness::{ @@ -762,8 +762,8 @@ mod twin_tests { ))); let mut context = KernelContext::new(&kernels, NoWitness, session); - let mut prover_transcript = Blake2bTranscript::new(b"prove-driver-twin"); - let challenges = sumchecks.draw_challenges(&mut prover_transcript).unwrap(); + let mut prover_state = prover_transcript::(b"prove-driver-twin", b""); + let challenges = sumchecks.draw_challenges(&mut prover_state).unwrap(); let input_points = sumchecks.empty_input_points(); let proved = sumchecks .prove( @@ -773,14 +773,14 @@ mod twin_tests { &input_points, &challenges, ClearSumcheckRecorder::::new(), - &mut prover_transcript, + &mut prover_state, ) .unwrap(); // Verifier twin: generated draw + composed verify_clear (which runs the // derive-opening-points and expected-final-claim checks internally) + // output-claim absorbs. - let mut verifier_transcript = Blake2bTranscript::new(b"prove-driver-twin"); + let mut verifier_transcript = prover_transcript::(b"prove-driver-twin", b""); let verifier_challenges = sumchecks.draw_challenges(&mut verifier_transcript).unwrap(); let _ = sumchecks .verify_clear( @@ -795,7 +795,10 @@ mod twin_tests { .unwrap(); sumchecks.append_output_claims(&mut verifier_transcript, &proved.output_claims); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); let calls = context .session() @@ -873,8 +876,8 @@ mod twin_tests { let session = ProofSession::default(); let mut context = KernelContext::new(&kernels, NoWitness, session); - let mut prover_transcript = Blake2bTranscript::new(b"prove-driver-head-twin"); - let challenges = sumchecks.draw_challenges(&mut prover_transcript).unwrap(); + let mut prover_state = prover_transcript::(b"prove-driver-head-twin", b""); + let challenges = sumchecks.draw_challenges(&mut prover_state).unwrap(); let input_points = sumchecks.empty_input_points(); let proved = sumchecks .prove( @@ -884,11 +887,11 @@ mod twin_tests { &input_points, &challenges, ClearSumcheckRecorder::::new(), - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let mut verifier_transcript = Blake2bTranscript::new(b"prove-driver-head-twin"); + let mut verifier_transcript = prover_transcript::(b"prove-driver-head-twin", b""); let verifier_challenges = sumchecks.draw_challenges(&mut verifier_transcript).unwrap(); let verified_points = sumchecks .verify_clear( @@ -903,7 +906,10 @@ mod twin_tests { .unwrap(); sumchecks.append_output_claims(&mut verifier_transcript, &proved.output_claims); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); assert_eq!(verified_points, proved.output_points); assert_eq!( proved.output_points.delta.value.as_slice(), @@ -938,7 +944,7 @@ mod twin_tests { let sumchecks = fixture(false); let inputs = inputs(false); let kernels = toy_kernels(); - let mut transcript = Blake2bTranscript::new(b"prove-driver-twin"); + let mut transcript = prover_transcript::(b"prove-driver-twin", b""); let challenges = sumchecks.draw_challenges(&mut transcript).unwrap(); let input_points = sumchecks.empty_input_points(); let result = sumchecks.prove( diff --git a/crates/jolt-prover/src/stages/field_inline_fixtures.rs b/crates/jolt-prover/src/stages/field_inline_fixtures.rs index 5218762e45..7eff9ea070 100644 --- a/crates/jolt-prover/src/stages/field_inline_fixtures.rs +++ b/crates/jolt-prover/src/stages/field_inline_fixtures.rs @@ -484,7 +484,7 @@ pub(crate) mod twins { use jolt_claims::NoChallenges; use jolt_field::Ring; use jolt_program::preprocess::PublicIoMemory; - use jolt_transcript::{AppendToTranscript, LegacyBlake2bTranscript as Blake2bTranscript}; + use jolt_transcript::{Keccak, Encoding, ProverState}; use jolt_verifier::config::JOLT_VERIFIER_CONFIG; use jolt_verifier::stages::relations::ConcreteSumcheck; use jolt_verifier::stages::stage1::outer_remainder::{ @@ -554,8 +554,8 @@ pub(crate) mod twins { /// Stage 1's twin (already round-tripped by stage 1's own tests): /// positions the transcript at the stage-2 boundary. - pub(crate) fn replay_stage1( - transcript: &mut Blake2bTranscript, + pub(crate) fn replay_stage1( + transcript: &mut ProverState, stage1: &Stage1ProverOutput, ) { let tau = draw_spartan_outer_tau(transcript, LOG_T); @@ -597,8 +597,8 @@ pub(crate) mod twins { /// Stage 2's twin (already round-tripped by stage 2's own tests): /// positions the transcript at the stage-3 boundary. - pub(crate) fn replay_stage2( - transcript: &mut Blake2bTranscript, + pub(crate) fn replay_stage2( + transcript: &mut ProverState, config: &ProverConfig, public_io: &JoltDevice, stage1: &Stage1ProverOutput, @@ -683,8 +683,8 @@ pub(crate) mod twins { /// Stage 3's twin (`stage3::verify`'s clear body — the stage has no field-inline /// member): positions the transcript at the stage-4 boundary. - pub(crate) fn replay_stage3( - transcript: &mut Blake2bTranscript, + pub(crate) fn replay_stage3( + transcript: &mut ProverState, stage1: &Stage1ProverOutput, stage2: &Stage2ProverOutput, stage3: &Stage3ProverOutput, @@ -727,8 +727,8 @@ pub(crate) mod twins { /// stage-5 boundary. The fixtures carry no advice and no committed /// program image, so the attached-claims step degenerates to the public /// initial-RAM evaluation alone. - pub(crate) fn replay_stage4( - transcript: &mut Blake2bTranscript, + pub(crate) fn replay_stage4( + transcript: &mut ProverState, config: &ProverConfig, checked: &CheckedInputs, preprocessing: &FixturePreprocessing, @@ -791,8 +791,8 @@ pub(crate) mod twins { /// Stage 5's twin (`stage5::verify`'s clear body): positions the /// transcript at the stage-6a boundary. - pub(crate) fn replay_stage5( - transcript: &mut Blake2bTranscript, + pub(crate) fn replay_stage5( + transcript: &mut ProverState, config: &ProverConfig, checked: &CheckedInputs, preprocessing: &FixturePreprocessing, @@ -843,8 +843,8 @@ pub(crate) mod twins { /// Stage 6a's twin (`stage6a::verify`'s clear body): positions the /// transcript at the stage-6b boundary. #[expect(clippy::too_many_arguments, reason = "the stage's upstream carriers")] - pub(crate) fn replay_stage6a( - transcript: &mut Blake2bTranscript, + pub(crate) fn replay_stage6a( + transcript: &mut ProverState, config: &ProverConfig, checked: &CheckedInputs, preprocessing: &FixturePreprocessing, @@ -942,7 +942,7 @@ pub(crate) mod proving { use crate::{JoltBackend, ProofMode}; use jolt_field::Fr; use jolt_kernels::KernelContext; - use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; + use jolt_transcript::{Keccak, ProverState}; type Commitment = Bn254G1; type Stages3 = ( Stage1ProverOutput, @@ -960,19 +960,19 @@ pub(crate) mod proving { pub(crate) public_io: &'a JoltDevice, pub(crate) checked: &'a CheckedInputs, pub(crate) preprocessing: &'a JoltProverPreprocessing>, - pub(crate) transcript: &'a mut Blake2bTranscript, + pub(crate) transcript: &'a mut ProverState, } impl FixtureProver<'_, '_> { pub(crate) fn through_stage3(&mut self) -> Stages3 { - let stage1 = prove_stage1::, Blake2bTranscript>( + let stage1 = prove_stage1::, ProverState>( self.backend, self.mode, LOG_T, self.transcript, ) .unwrap(); - let stage2 = prove_stage2::, Blake2bTranscript>( + let stage2 = prove_stage2::, ProverState>( self.backend, self.mode, self.config, @@ -981,7 +981,7 @@ pub(crate) mod proving { self.transcript, ) .unwrap(); - let stage3 = prove_stage3::, Blake2bTranscript>( + let stage3 = prove_stage3::, ProverState>( self.backend, self.mode, self.config, @@ -994,7 +994,7 @@ pub(crate) mod proving { } pub(crate) fn through_stage4(&mut self) -> Stages4 { let (stage1, stage2, stage3) = self.through_stage3(); - let stage4 = prove_stage4::, Blake2bTranscript>( + let stage4 = prove_stage4::, ProverState>( self.backend, self.mode, self.checked, @@ -1009,7 +1009,7 @@ pub(crate) mod proving { } pub(crate) fn through_stage5(&mut self) -> Stages5 { let ((stage1, stage2, stage3), stage4) = self.through_stage4(); - let stage5 = prove_stage5::, Blake2bTranscript>( + let stage5 = prove_stage5::, ProverState>( self.backend, self.mode, self.checked, @@ -1024,7 +1024,7 @@ pub(crate) mod proving { } pub(crate) fn through_stage6a(&mut self) -> Stages6a { let (((stage1, stage2, stage3), stage4), stage5) = self.through_stage5(); - let stage6a = prove_stage6a::, Blake2bTranscript>( + let stage6a = prove_stage6a::, ProverState>( self.backend, self.mode, self.checked, diff --git a/crates/jolt-prover/src/stages/mod.rs b/crates/jolt-prover/src/stages/mod.rs index 9cb8d7cd52..eefc584c72 100644 --- a/crates/jolt-prover/src/stages/mod.rs +++ b/crates/jolt-prover/src/stages/mod.rs @@ -9,7 +9,9 @@ use jolt_claims::protocols::jolt::geometry::dimensions::JoltFormulaDimensions; use jolt_claims::protocols::jolt::JoltRelationId; +use jolt_transcript::{FieldTranscript, ProverTranscript}; use jolt_verifier::stages::formula_dimensions_from_parts; +use jolt_verifier::stages::pow::{pow_bits, PowSite}; use jolt_verifier::{CheckedInputs, VerifierError}; use crate::ProverConfig; @@ -26,6 +28,26 @@ pub mod stage6a; pub mod stage6b; pub mod stage7; +/// Grinds the nonce of the burst at `site` right before its first draw, the +/// prover's side of `jolt_verifier::stages::pow::check_pow`. Returns `None` +/// when the difficulty is zero. +/// +/// # Errors +/// [`VerifierError::PowDifficulty`] as for [`pow_bits`], and +/// [`VerifierError::ProofOfWorkRejected`] if the nonce search fails. +pub(crate) fn grind_pow( + site: PowSite, + loss: u64, + transcript: &mut T, +) -> Result, VerifierError> +where + T: FieldTranscript + ProverTranscript, +{ + transcript + .public_grind(pow_bits::(site, loss)?) + .map_err(|_| VerifierError::ProofOfWorkRejected { site }) +} + /// The one-hot formula dimensions, built by the same core constructor as the /// verifier's `build_formula_dimensions` (which reads the one-hot config off /// the proof; the prover reads it off its own derived config — stage 0 wrote diff --git a/crates/jolt-prover/src/stages/stage1.rs b/crates/jolt-prover/src/stages/stage1.rs index 0dde8d7b43..04a705bd43 100644 --- a/crates/jolt-prover/src/stages/stage1.rs +++ b/crates/jolt-prover/src/stages/stage1.rs @@ -18,8 +18,10 @@ use jolt_openings::CommitmentScheme; #[cfg(feature = "zk")] use jolt_sumcheck::CommittedSumcheckWitness; use jolt_sumcheck::SumcheckProof; -use jolt_transcript::Transcript; -use jolt_verifier::stages::pow::{grind_pow, PowSite}; +use jolt_transcript::{FieldTranscript, ProverTranscript}; +use jolt_verifier::stages::pow::PowSite; + +use crate::stages::grind_pow; use jolt_verifier::stages::stage1::outer_remainder::{ outer_remainder_input_values_from_uniskip_output, OuterRemainder, }; @@ -60,14 +62,14 @@ where F: JoltField, PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, { - let tau_pow_nonce = grind_pow( + let tau_pow_nonce = grind_pow::( PowSite::Stage1Tau, spartan_outer_tau_loss(log_t), transcript, )?; - let tau = draw_spartan_outer_tau(transcript, log_t); + let tau = draw_spartan_outer_tau(transcript, log_t)?; // Backend-neutral kernel-seam spans at the call boundary, so every // `UniskipKernel` implementation inherits them — see the taxonomy's // kernel-seam contract. @@ -92,7 +94,7 @@ where uniskip_challenge, ), }; - let pow_nonce = grind_pow(PowSite::Stage1, sumchecks.challenge_loss(), transcript)?; + let pow_nonce = grind_pow::(PowSite::Stage1, sumchecks.challenge_loss(), transcript)?; let challenges = sumchecks.draw_challenges(transcript)?; let input_points = sumchecks.empty_input_points(); let inputs = Stage1BatchInputClaims { @@ -151,7 +153,7 @@ mod field_inline_round_trip { use jolt_dory::DoryScheme; use jolt_field::Fr; use jolt_poly::Polynomial; - use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; + use jolt_transcript::{prover_transcript, Keccak, ProverState}; use jolt_verifier::stages::stage2::product_tau_low; use jolt_witness::{JoltWitnessOracle as _, TraceBackend}; @@ -163,12 +165,12 @@ mod field_inline_round_trip { let backend = crate::stages::field_inline_fixtures::reference_backend(); let mut context = backend.with_witness(&witness); let mode = ProofMode::>::new(None).unwrap(); - let mut prover_transcript = Blake2bTranscript::new(b"stage1-field-inline"); - let out = prove_stage1::, Blake2bTranscript>( + let mut prover_state = prover_transcript::(b"stage1-field-inline", b""); + let out = prove_stage1::, ProverState>( &mut context, &mode, LOG_T, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); @@ -189,10 +191,13 @@ mod field_inline_round_trip { assert_eq!(Polynomial::::new(table).evaluate(&tau_low), value); } - let mut transcript = Blake2bTranscript::new(b"stage1-field-inline"); + let mut transcript = prover_transcript::(b"stage1-field-inline", b""); twins::replay_stage1(&mut transcript, &out); - assert_eq!(transcript.state(), prover_transcript.state()); + assert_eq!( + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } #[test] @@ -214,7 +219,7 @@ mod field_inline_zk { use jolt_crypto::{Bn254G1, Pedersen, PedersenSetup}; use jolt_dory::DoryScheme; use jolt_field::Fr; - use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; + use jolt_transcript::{prover_transcript, Keccak, ProverState}; use jolt_verifier::stages::uniskip::{self, UniskipParams}; use jolt_verifier::stages::PrecommittedSchedule; use jolt_verifier::CheckedInputs; @@ -231,12 +236,12 @@ mod field_inline_zk { let mut context = backend.with_witness(&witness); let setup = PedersenSetup::new(vec![Bn254G1::default(); CAPACITY], Bn254G1::default()); let mode = ProofMode::>::new(Some(&setup)).unwrap(); - let mut prover_transcript = Blake2bTranscript::new(b"stage1-field-inline-zk"); - let out = prove_stage1::, Blake2bTranscript>( + let mut prover_state = prover_transcript::(b"stage1-field-inline-zk", b""); + let out = prove_stage1::, ProverState>( &mut context, &mode, LOG_T, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); @@ -290,7 +295,7 @@ mod field_inline_zk { program_image: None, }, }; - let mut transcript = Blake2bTranscript::new(b"stage1-field-inline-zk"); + let mut transcript = prover_transcript::(b"stage1-field-inline-zk", b""); let tau = draw_spartan_outer_tau(&mut transcript, LOG_T); let uniskip_step = uniskip::verify_zk( &checked, @@ -310,6 +315,9 @@ mod field_inline_zk { .verify_zk(&out.sumcheck_proof, &mut transcript) .unwrap(); - assert_eq!(transcript.state(), prover_transcript.state()); + assert_eq!( + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } } diff --git a/crates/jolt-prover/src/stages/stage2.rs b/crates/jolt-prover/src/stages/stage2.rs index 6416bb2d1c..99f249f153 100644 --- a/crates/jolt-prover/src/stages/stage2.rs +++ b/crates/jolt-prover/src/stages/stage2.rs @@ -27,8 +27,10 @@ use jolt_program::preprocess::PublicIoMemory; #[cfg(feature = "zk")] use jolt_sumcheck::CommittedSumcheckWitness; use jolt_sumcheck::SumcheckProof; -use jolt_transcript::Transcript; -use jolt_verifier::stages::pow::{grind_pow, PowSite}; +use jolt_transcript::{FieldTranscript, ProverTranscript}; +use jolt_verifier::stages::pow::PowSite; + +use crate::stages::grind_pow; use jolt_verifier::stages::relations::ConcreteSumcheck; use jolt_verifier::stages::stage1::Stage1ClearOutput; #[cfg(feature = "field-inline")] @@ -84,7 +86,7 @@ where F: JoltField, PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let log_t = config.trace_length.ilog2() as usize; let log_k = config.ram_K.ilog2() as usize; @@ -107,12 +109,12 @@ where tracing::info_span!("SpartanProductUniskip::prepare") .in_scope(|| backend.prepare_product_uniskip(log_t, &tau_low))?; - let tau_high_pow_nonce = grind_pow( + let tau_high_pow_nonce = grind_pow::( PowSite::Stage2TauHigh, spartan_product_tau_high_loss(), transcript, )?; - let tau_high: F = draw_spartan_product_tau_high(transcript); + let tau_high: F = draw_spartan_product_tau_high(transcript)?; let uniskip_relation = ProductUniskip::new(product_dimensions, tau_high); // The field-inline lane inputs enter the composed input claim exactly as on the // verifier — composed through the shared seam, before `input_claim`. @@ -166,7 +168,7 @@ where }; // Both batch gammas, then the RAM output-check address reference point (the // last member's `draw_challenges` override) — the verifier's exact schedule. - let pow_nonce = grind_pow(PowSite::Stage2, sumchecks.challenge_loss(), transcript)?; + let pow_nonce = grind_pow::(PowSite::Stage2, sumchecks.challenge_loss(), transcript)?; let challenges = sumchecks.draw_challenges(transcript)?; let input_points = sumchecks.empty_input_points(); @@ -225,7 +227,7 @@ mod field_inline_round_trip { use jolt_crypto::{Bn254G1, Pedersen}; use jolt_dory::DoryScheme; use jolt_field::Fr; - use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; + use jolt_transcript::{prover_transcript, Keccak, ProverState}; use super::*; use crate::stages::field_inline_fixtures::{ @@ -242,21 +244,21 @@ mod field_inline_round_trip { let config = test_prover_config(); let public_io = test_public_io(); - let mut prover_transcript = Blake2bTranscript::new(b"stage2-field-inline"); - let stage1 = prove_stage1::, Blake2bTranscript>( + let mut prover_state = prover_transcript::(b"stage2-field-inline", b""); + let stage1 = prove_stage1::, ProverState>( &mut context, &mode, LOG_T, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let out = prove_stage2::, Blake2bTranscript>( + let out = prove_stage2::, ProverState>( &mut context, &mode, &config, &public_io, &stage1.clear_output, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); @@ -271,11 +273,14 @@ mod field_inline_round_trip { assert_eq!(reduction.rs2_value, appendage.rs2_value); assert_eq!(reduction.rd_value, appendage.rd_value); - let mut transcript = Blake2bTranscript::new(b"stage2-field-inline"); + let mut transcript = prover_transcript::(b"stage2-field-inline", b""); twins::replay_stage1(&mut transcript, &stage1); twins::replay_stage2(&mut transcript, &config, &public_io, &stage1, &out); - assert_eq!(transcript.state(), prover_transcript.state()); + assert_eq!( + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } } @@ -287,7 +292,7 @@ mod field_inline_zk { use jolt_crypto::{Bn254G1, Pedersen, PedersenSetup}; use jolt_dory::DoryScheme; use jolt_field::Fr; - use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; + use jolt_transcript::{prover_transcript, Keccak, ProverState}; use jolt_verifier::stages::stage1::outer_remainder::OuterRemainder; use jolt_verifier::stages::stage1::outputs::Stage1BatchSumchecks; use jolt_verifier::stages::uniskip::{self, UniskipParams}; @@ -312,21 +317,21 @@ mod field_inline_zk { let config = test_prover_config(); let public_io = test_public_io(); - let mut prover_transcript = Blake2bTranscript::new(b"stage2-field-inline-zk"); - let stage1 = prove_stage1::, Blake2bTranscript>( + let mut prover_state = prover_transcript::(b"stage2-field-inline-zk", b""); + let stage1 = prove_stage1::, ProverState>( &mut context, &mode, LOG_T, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let out = prove_stage2::, Blake2bTranscript>( + let out = prove_stage2::, ProverState>( &mut context, &mode, &config, &public_io, &stage1.clear_output, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); @@ -357,7 +362,7 @@ mod field_inline_zk { program_image: None, }, }; - let mut transcript = Blake2bTranscript::new(b"stage2-field-inline-zk"); + let mut transcript = prover_transcript::(b"stage2-field-inline-zk", b""); { let tau = uniskip::draw_spartan_outer_tau(&mut transcript, LOG_T); let uniskip_step = uniskip::verify_zk( @@ -430,6 +435,9 @@ mod field_inline_zk { .verify_zk(&out.sumcheck_proof, &mut transcript) .unwrap(); - assert_eq!(transcript.state(), prover_transcript.state()); + assert_eq!( + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } } diff --git a/crates/jolt-prover/src/stages/stage3.rs b/crates/jolt-prover/src/stages/stage3.rs index 2c45a753d0..a584c181d2 100644 --- a/crates/jolt-prover/src/stages/stage3.rs +++ b/crates/jolt-prover/src/stages/stage3.rs @@ -16,8 +16,10 @@ use jolt_openings::CommitmentScheme; #[cfg(feature = "zk")] use jolt_sumcheck::CommittedSumcheckWitness; use jolt_sumcheck::SumcheckProof; -use jolt_transcript::Transcript; -use jolt_verifier::stages::pow::{grind_pow, PowSite}; +use jolt_transcript::{FieldTranscript, ProverTranscript}; +use jolt_verifier::stages::pow::PowSite; + +use crate::stages::grind_pow; use jolt_verifier::stages::stage1::Stage1ClearOutput; use jolt_verifier::stages::stage2::outputs::Stage2ClearOutput; use jolt_verifier::stages::stage3::outputs::{ @@ -56,7 +58,7 @@ where F: JoltField, PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let log_t = config.trace_length.ilog2() as usize; let trace_dimensions = TraceDimensions::new(log_t); @@ -72,7 +74,7 @@ where instruction_input: InstructionInput::new(trace_dimensions, product_remainder_point), registers_claim_reduction: RegistersClaimReduction::new(trace_dimensions, product_tau_low), }; - let pow_nonce = grind_pow(PowSite::Stage3, sumchecks.challenge_loss(), transcript)?; + let pow_nonce = grind_pow::(PowSite::Stage3, sumchecks.challenge_loss(), transcript)?; let challenges = sumchecks.draw_challenges(transcript)?; let input_points = sumchecks.empty_input_points(); let inputs = stage3_input_values_from_upstream(&stage1.output_values, &stage2.output_values); diff --git a/crates/jolt-prover/src/stages/stage4.rs b/crates/jolt-prover/src/stages/stage4.rs index d2beb424e8..8bbecd7a0c 100644 --- a/crates/jolt-prover/src/stages/stage4.rs +++ b/crates/jolt-prover/src/stages/stage4.rs @@ -21,10 +21,12 @@ use jolt_openings::CommitmentScheme; #[cfg(feature = "zk")] use jolt_sumcheck::CommittedSumcheckWitness; use jolt_sumcheck::SumcheckProof; -use jolt_transcript::Transcript; +use jolt_transcript::{FieldTranscript, ProverTranscript}; #[cfg(feature = "field-inline")] use jolt_verifier::config::JOLT_VERIFIER_CONFIG; -use jolt_verifier::stages::pow::{grind_pow, PowSite}; +use jolt_verifier::stages::pow::PowSite; + +use crate::stages::grind_pow; use jolt_verifier::stages::stage2::outputs::Stage2ClearOutput; use jolt_verifier::stages::stage3::outputs::Stage3ClearOutput; #[cfg(feature = "field-inline")] @@ -73,7 +75,7 @@ where F: JoltField, PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let log_t = checked.trace_length.ilog2() as usize; let log_k = checked.ram_K.ilog2() as usize; @@ -184,7 +186,7 @@ where // Draws the registers gamma, under `field-inline` the field-register read-write gamma, // then the RAM value-check gamma behind its `b"ram_val_check_gamma"` domain // separator (replayed by the relation's `draw_challenges` override). - let pow_nonce = grind_pow(PowSite::Stage4, sumchecks.challenge_loss(), transcript)?; + let pow_nonce = grind_pow::(PowSite::Stage4, sumchecks.challenge_loss(), transcript)?; let challenges = sumchecks.draw_challenges(transcript)?; let inputs = stage4_input_values_from_upstream( @@ -256,7 +258,7 @@ mod field_inline_round_trip { use jolt_field::{Fr, Ring}; use jolt_kernels::ProverInputs; use jolt_poly::EqPolynomial; - use jolt_transcript::{LegacyBlake2bTranscript as Blake2bTranscript, Transcript}; + use jolt_transcript::{prover_transcript, Keccak, ProverState, Transcript}; use jolt_verifier::stages::relations::ConcreteSumcheck as _; use jolt_witness::JoltWitnessOracle as _; @@ -277,7 +279,7 @@ mod field_inline_round_trip { let checked = test_checked_inputs(); let preprocessing = field_arithmetic_preprocessing(); - let mut prover_transcript = Blake2bTranscript::new(b"stage4-field-inline"); + let mut prover_state = prover_transcript::(b"stage4-field-inline", b""); let (stage1, stage2, stage3) = FixtureProver { backend: &mut context, mode: &mode, @@ -285,10 +287,10 @@ mod field_inline_round_trip { public_io: &public_io, checked: &checked, preprocessing: &preprocessing, - transcript: &mut prover_transcript, + transcript: &mut prover_state, } .through_stage3(); - let out = prove_stage4::, Blake2bTranscript>( + let out = prove_stage4::, ProverState>( &mut context, &mode, &checked, @@ -296,13 +298,13 @@ mod field_inline_round_trip { &preprocessing, &stage2.clear_output, &stage3.clear_output, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); // The verifier twin (stage4::verify's clear body, shared as the // stage-5+ twins' replay), positioned by the upstream replays. - let mut transcript = Blake2bTranscript::new(b"stage4-field-inline"); + let mut transcript = prover_transcript::(b"stage4-field-inline", b""); twins::replay_stage1(&mut transcript, &stage1); twins::replay_stage2(&mut transcript, &config, &public_io, &stage1, &stage2); twins::replay_stage3(&mut transcript, &stage1, &stage2, &stage3); @@ -316,7 +318,10 @@ mod field_inline_round_trip { &out, ); - assert_eq!(transcript.state(), prover_transcript.state()); + assert_eq!( + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } fn fr(value: u64) -> Fr { @@ -456,7 +461,7 @@ mod field_inline_zk { use jolt_crypto::{Bn254G1, Pedersen, PedersenSetup}; use jolt_dory::DoryScheme; use jolt_field::Fr; - use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; + use jolt_transcript::{prover_transcript, Keccak, ProverState}; use super::*; use crate::stages::field_inline_fixtures::{ @@ -478,7 +483,7 @@ mod field_inline_zk { let checked = test_checked_inputs(); let preprocessing = field_arithmetic_preprocessing(); - let mut transcript = Blake2bTranscript::new(b"stage4-field-inline-zk"); + let mut transcript = prover_transcript::(b"stage4-field-inline-zk", b""); let (_stage1, stage2, stage3) = FixtureProver { backend: &mut context, mode: &mode, @@ -489,7 +494,7 @@ mod field_inline_zk { transcript: &mut transcript, } .through_stage3(); - let out = prove_stage4::, Blake2bTranscript>( + let out = prove_stage4::, ProverState>( &mut context, &mode, &checked, diff --git a/crates/jolt-prover/src/stages/stage5.rs b/crates/jolt-prover/src/stages/stage5.rs index ad792925db..6200c4a60e 100644 --- a/crates/jolt-prover/src/stages/stage5.rs +++ b/crates/jolt-prover/src/stages/stage5.rs @@ -21,8 +21,10 @@ use jolt_openings::CommitmentScheme; #[cfg(feature = "zk")] use jolt_sumcheck::CommittedSumcheckWitness; use jolt_sumcheck::SumcheckProof; -use jolt_transcript::Transcript; -use jolt_verifier::stages::pow::{grind_pow, PowSite}; +use jolt_transcript::{FieldTranscript, ProverTranscript}; +use jolt_verifier::stages::pow::PowSite; + +use crate::stages::grind_pow; use jolt_verifier::stages::stage2::outputs::Stage2ClearOutput; use jolt_verifier::stages::stage4::outputs::Stage4ClearOutput; #[cfg(feature = "field-inline")] @@ -70,7 +72,7 @@ where F: JoltField, PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let log_k = checked.ram_K.ilog2() as usize; let formula_dimensions = super::formula_dimensions( @@ -93,7 +95,7 @@ where // Draws the instruction gamma, then the RAM gamma (registers draws // nothing, and so does the `field-inline` field value-evaluation member) — // the generated declaration-order draw. - let pow_nonce = grind_pow(PowSite::Stage5, sumchecks.challenge_loss(), transcript)?; + let pow_nonce = grind_pow::(PowSite::Stage5, sumchecks.challenge_loss(), transcript)?; let challenges = sumchecks.draw_challenges(transcript)?; let inputs = stage5_input_values_from_upstream(&stage2.output_values, &stage4.output_values); @@ -154,7 +156,7 @@ mod field_inline_round_trip { use jolt_field::{Fr, Ring}; use jolt_kernels::ProverInputs; use jolt_poly::EqPolynomial; - use jolt_transcript::{LegacyBlake2bTranscript as Blake2bTranscript, Transcript}; + use jolt_transcript::{prover_transcript, Keccak, ProverState, Transcript}; use jolt_verifier::stages::relations::ConcreteSumcheck as _; use jolt_witness::JoltWitnessOracle as _; @@ -176,7 +178,7 @@ mod field_inline_round_trip { let checked = test_checked_inputs(); let preprocessing = field_arithmetic_preprocessing(); - let mut prover_transcript = Blake2bTranscript::new(b"stage5-field-inline"); + let mut prover_state = prover_transcript::(b"stage5-field-inline", b""); let ((stage1, stage2, stage3), stage4) = FixtureProver { backend: &mut context, mode: &mode, @@ -184,10 +186,10 @@ mod field_inline_round_trip { public_io: &public_io, checked: &checked, preprocessing: &preprocessing, - transcript: &mut prover_transcript, + transcript: &mut prover_state, } .through_stage4(); - let out = prove_stage5::, Blake2bTranscript>( + let out = prove_stage5::, ProverState>( &mut context, &mode, &checked, @@ -195,13 +197,13 @@ mod field_inline_round_trip { &preprocessing, &stage2.clear_output, &stage4.clear_output, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); // The verifier twin (stage5::verify's clear body), positioned by the // upstream replays. - let mut transcript = Blake2bTranscript::new(b"stage5-field-inline"); + let mut transcript = prover_transcript::(b"stage5-field-inline", b""); twins::replay_stage1(&mut transcript, &stage1); twins::replay_stage2(&mut transcript, &config, &public_io, &stage1, &stage2); twins::replay_stage3(&mut transcript, &stage1, &stage2, &stage3); @@ -224,7 +226,10 @@ mod field_inline_round_trip { &out, ); - assert_eq!(transcript.state(), prover_transcript.state()); + assert_eq!( + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } fn fr(value: u64) -> Fr { diff --git a/crates/jolt-prover/src/stages/stage6a.rs b/crates/jolt-prover/src/stages/stage6a.rs index dfe29ceb58..27d9318fbe 100644 --- a/crates/jolt-prover/src/stages/stage6a.rs +++ b/crates/jolt-prover/src/stages/stage6a.rs @@ -25,8 +25,10 @@ use jolt_openings::CommitmentScheme; #[cfg(feature = "zk")] use jolt_sumcheck::CommittedSumcheckWitness; use jolt_sumcheck::SumcheckProof; -use jolt_transcript::Transcript; -use jolt_verifier::stages::pow::{grind_pow, PowSite}; +use jolt_transcript::{FieldTranscript, ProverTranscript}; +use jolt_verifier::stages::pow::PowSite; + +use crate::stages::grind_pow; use jolt_verifier::stages::stage1::Stage1ClearOutput; use jolt_verifier::stages::stage2::outputs::Stage2ClearOutput; use jolt_verifier::stages::stage3::outputs::Stage3ClearOutput; @@ -78,7 +80,7 @@ where F: JoltField, PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let formula_dimensions = super::formula_dimensions( checked, @@ -114,7 +116,7 @@ where // then the booleanity member's override (the reference-address pad draw // and the gamma). The 6a verifier only carries the booleanity values; this // prover's booleanity kernel consumes them off the challenge aggregate. - let pow_nonce = grind_pow(PowSite::Stage6a, sumchecks.challenge_loss(), transcript)?; + let pow_nonce = grind_pow::(PowSite::Stage6a, sumchecks.challenge_loss(), transcript)?; let address_challenges = sumchecks.draw_challenges(transcript)?; let carried = Stage6aCarriedChallenges::from(&address_challenges); @@ -196,7 +198,7 @@ mod field_inline_round_trip { use jolt_crypto::{Bn254G1, Pedersen}; use jolt_dory::DoryScheme; use jolt_field::{Fr, Ring}; - use jolt_transcript::{LegacyBlake2bTranscript as Blake2bTranscript, Transcript}; + use jolt_transcript::{prover_transcript, Keccak, ProverState, Transcript}; use super::*; use crate::recorder::ProofMode; @@ -216,7 +218,7 @@ mod field_inline_round_trip { let checked = test_checked_inputs(); let preprocessing = field_arithmetic_preprocessing(); - let mut prover_transcript = Blake2bTranscript::new(b"stage6a-field-inline"); + let mut prover_state = prover_transcript::(b"stage6a-field-inline", b""); let (((stage1, stage2, stage3), stage4), stage5) = FixtureProver { backend: &mut context, mode: &mode, @@ -224,10 +226,10 @@ mod field_inline_round_trip { public_io: &public_io, checked: &checked, preprocessing: &preprocessing, - transcript: &mut prover_transcript, + transcript: &mut prover_state, } .through_stage5(); - let out = prove_stage6a::, Blake2bTranscript>( + let out = prove_stage6a::, ProverState>( &mut context, &mode, &checked, @@ -238,7 +240,7 @@ mod field_inline_round_trip { &stage3.clear_output, &stage4.clear_output, &stage5.clear_output, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); @@ -253,7 +255,7 @@ mod field_inline_round_trip { let zero = Fr::from_u64(0); assert!(appendage.rd_wa_read_write != zero); - let mut transcript = Blake2bTranscript::new(b"stage6a-field-inline"); + let mut transcript = prover_transcript::(b"stage6a-field-inline", b""); twins::replay_stage1(&mut transcript, &stage1); twins::replay_stage2(&mut transcript, &config, &public_io, &stage1, &stage2); twins::replay_stage3(&mut transcript, &stage1, &stage2, &stage3); @@ -288,6 +290,9 @@ mod field_inline_round_trip { &out, ); - assert_eq!(transcript.state(), prover_transcript.state()); + assert_eq!( + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } } diff --git a/crates/jolt-prover/src/stages/stage6b.rs b/crates/jolt-prover/src/stages/stage6b.rs index 521a76e05d..f7a89c4295 100644 --- a/crates/jolt-prover/src/stages/stage6b.rs +++ b/crates/jolt-prover/src/stages/stage6b.rs @@ -30,8 +30,10 @@ use jolt_openings::CommitmentScheme; #[cfg(feature = "zk")] use jolt_sumcheck::CommittedSumcheckWitness; use jolt_sumcheck::SumcheckProof; -use jolt_transcript::Transcript; -use jolt_verifier::stages::pow::{grind_pow, PowSite}; +use jolt_transcript::{FieldTranscript, ProverTranscript}; +use jolt_verifier::stages::pow::PowSite; + +use crate::stages::grind_pow; use jolt_verifier::stages::stage1::Stage1ClearOutput; use jolt_verifier::stages::stage2::outputs::Stage2ClearOutput; use jolt_verifier::stages::stage3::outputs::Stage3ClearOutput; @@ -87,7 +89,7 @@ where F: JoltField, PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let log_k = checked.ram_K.ilog2() as usize; let precommitted = &checked.precommitted; @@ -103,12 +105,12 @@ where // The bytecode gamma shares stage 6a's squeeze; the post-6a draws and the // challenges aggregate are the verifier's promoted two-front helpers. let carried = &stage6a.challenges; - let pow_nonce = grind_pow( + let pow_nonce = grind_pow::( PowSite::Stage6b, Stage6bDraws::::challenge_loss(&formula_dimensions, committed_program), transcript, )?; - let draws = Stage6bDraws::draw(transcript, &formula_dimensions, committed_program); + let draws = Stage6bDraws::draw(transcript, &formula_dimensions, committed_program)?; // The batch, through the verifier's own promoted constructor over the // clear carriers. Full-mode bytecode rows come from program preprocessing. @@ -242,7 +244,7 @@ mod field_inline_round_trip { use jolt_field::{Fr, Ring}; use jolt_kernels::ProverInputs; use jolt_poly::EqPolynomial; - use jolt_transcript::{LegacyBlake2bTranscript as Blake2bTranscript, Transcript}; + use jolt_transcript::{prover_transcript, Keccak, ProverState, Transcript}; use jolt_verifier::stages::relations::ConcreteSumcheck as _; use jolt_verifier::stages::stage6b::field_registers_inc_claim_reduction::FieldRegistersIncClaimReduction; use jolt_witness::{JoltWitnessOracle as _, TraceBackend}; @@ -286,7 +288,7 @@ mod field_inline_round_trip { let public_io = test_public_io(); let checked = test_checked_inputs(); - let mut prover_transcript = Blake2bTranscript::new(label); + let mut prover_state = prover_transcript::(label, b""); let ((((stage1, stage2, stage3), stage4), stage5), stage6a) = FixtureProver { backend: &mut context, mode: &mode, @@ -294,10 +296,10 @@ mod field_inline_round_trip { public_io: &public_io, checked: &checked, preprocessing: &preprocessing, - transcript: &mut prover_transcript, + transcript: &mut prover_state, } .through_stage6a(); - let out = prove_stage6b::, Blake2bTranscript>( + let out = prove_stage6b::, ProverState>( &mut context, &mode, &checked, @@ -309,7 +311,7 @@ mod field_inline_round_trip { &stage4.clear_output, &stage5.clear_output, &stage6a.clear_output, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); @@ -317,7 +319,7 @@ mod field_inline_round_trip { // the upstream replays. The private wire-shape validator is // transcript-free and elided; `verify_clear`'s hard checks and the // final state equality pin the protocol content. - let mut transcript = Blake2bTranscript::new(label); + let mut transcript = prover_transcript::(label, b""); twins::replay_stage1(&mut transcript, &stage1); twins::replay_stage2(&mut transcript, &config, &public_io, &stage1, &stage2); twins::replay_stage3(&mut transcript, &stage1, &stage2, &stage3); @@ -442,7 +444,10 @@ mod field_inline_round_trip { transcript.append_labeled(b"opening_claim", &value); } - assert_eq!(transcript.state(), prover_transcript.state()); + assert_eq!( + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } fn fr(value: u64) -> Fr { @@ -542,7 +547,7 @@ mod field_inline_zk { use jolt_crypto::{Bn254G1, Pedersen, PedersenSetup}; use jolt_dory::DoryScheme; use jolt_field::Fr; - use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; + use jolt_transcript::{prover_transcript, Keccak, ProverState}; use super::*; use crate::stages::field_inline_fixtures::{ @@ -564,7 +569,7 @@ mod field_inline_zk { let checked = test_checked_inputs(); let preprocessing = addi_only_preprocessing(); - let mut transcript = Blake2bTranscript::new(b"stage6b-field-inline-zk"); + let mut transcript = prover_transcript::(b"stage6b-field-inline-zk", b""); let ((((stage1, stage2, stage3), stage4), stage5), stage6a) = FixtureProver { backend: &mut context, mode: &mode, @@ -575,7 +580,7 @@ mod field_inline_zk { transcript: &mut transcript, } .through_stage6a(); - let out = prove_stage6b::, Blake2bTranscript>( + let out = prove_stage6b::, ProverState>( &mut context, &mode, &checked, diff --git a/crates/jolt-prover/src/stages/stage7.rs b/crates/jolt-prover/src/stages/stage7.rs index cb6d2e1ba1..5691ee8b8f 100644 --- a/crates/jolt-prover/src/stages/stage7.rs +++ b/crates/jolt-prover/src/stages/stage7.rs @@ -19,8 +19,10 @@ use jolt_openings::CommitmentScheme; #[cfg(feature = "zk")] use jolt_sumcheck::CommittedSumcheckWitness; use jolt_sumcheck::SumcheckProof; -use jolt_transcript::Transcript; -use jolt_verifier::stages::pow::{grind_pow, PowSite}; +use jolt_transcript::{FieldTranscript, ProverTranscript}; +use jolt_verifier::stages::pow::PowSite; + +use crate::stages::grind_pow; use jolt_verifier::stages::stage4::Stage4ClearOutput; use jolt_verifier::stages::stage6b::outputs::Stage6bClearOutput; use jolt_verifier::stages::stage7::hamming_weight_claim_reduction::hamming_weight_claim_reduction_dimensions; @@ -60,7 +62,7 @@ where F: JoltField, PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript + ProverTranscript, { let precommitted = &checked.precommitted; let formula_dimensions = super::formula_dimensions( @@ -80,7 +82,7 @@ where &stage6b.output_points, Some((stage4, stage6b)), )?; - let pow_nonce = grind_pow(PowSite::Stage7, sumchecks.challenge_loss(), transcript)?; + let pow_nonce = grind_pow::(PowSite::Stage7, sumchecks.challenge_loss(), transcript)?; let challenges = sumchecks.draw_challenges(transcript)?; let inputs = stage7_input_values_from_upstream(&sumchecks, stage6b)?; diff --git a/crates/jolt-prover/tests/e2e_matrix.rs b/crates/jolt-prover/tests/e2e_matrix.rs index d25409886e..36518c160a 100644 --- a/crates/jolt-prover/tests/e2e_matrix.rs +++ b/crates/jolt-prover/tests/e2e_matrix.rs @@ -251,7 +251,7 @@ mod matrix { use jolt_dory::DoryScheme; use jolt_field::Fr; use jolt_prover::{dory, JoltBackend, JoltSharedPreprocessing, ProverConfig}; - use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; + use jolt_transcript::{Keccak, LegacyBlake2bTranscript as ProverState, ProverState}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; use crate::support::{self, GuestCase}; @@ -300,7 +300,7 @@ mod matrix { .include_trusted_advice(trusted.is_some()), JoltVmWitnessInputs::new(&prepared.program, &program_preprocessing, prepared.trace), ); - let proof = dory::prove::, Blake2bTranscript>( + let proof = dory::prove::, ProverState>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -308,7 +308,7 @@ mod matrix { &public_io, ) .expect("Dory proof"); - jolt_verifier::verify::, Blake2bTranscript>( + jolt_verifier::verify::, ProverState>( &preprocessing.verifier, &public_io, &proof, diff --git a/crates/jolt-prover/tests/engine_twins.rs b/crates/jolt-prover/tests/engine_twins.rs index f10c82b52d..ea8d2807d7 100644 --- a/crates/jolt-prover/tests/engine_twins.rs +++ b/crates/jolt-prover/tests/engine_twins.rs @@ -20,7 +20,7 @@ use jolt_sumcheck::{ CommittedSumcheckRecorder, ProveRounds, SequentialRounds, SumcheckDomain, SumcheckError, SumcheckProof, SumcheckRecorder, OPENING_CLAIM_TRANSCRIPT_LABEL, }; -use jolt_transcript::{Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Keccak, Transcript}; use jolt_verifier::stages::relations::{ConcreteSumcheck as _, SumcheckBatch}; use jolt_verifier::stages::stage5::{InstructionReadRaf, RegistersValEvaluation}; use jolt_verifier::stages::uniskip::{self, UniskipParams}; @@ -128,8 +128,8 @@ fn synthetic_output_values() -> Vec { fn clear_engine_twin_matches_generated_verify_clear() { let sumchecks = fixture(); let inputs = inputs(); - let mut prover_transcript = Blake2bTranscript::new(b"engine-twin"); - let prover_challenges = sumchecks.draw_challenges(&mut prover_transcript).unwrap(); + let mut prover_state = prover_transcript::(b"engine-twin", b""); + let prover_challenges = sumchecks.draw_challenges(&mut prover_state).unwrap(); let instruction_sum = sumchecks .instruction_read_raf @@ -159,7 +159,7 @@ fn clear_engine_twin_matches_generated_verify_clear() { &inputs, &prover_challenges, &mut recorder, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); let mut members: Vec<&mut dyn ProveRounds> = @@ -169,17 +169,17 @@ fn clear_engine_twin_matches_generated_verify_clear() { &mut members, &mut SequentialRounds, &mut recorder, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); let output_values = synthetic_output_values(); let recorded = recorder - .finish(&output_values, &mut prover_transcript) + .finish(&output_values, &mut prover_state) .unwrap(); // Verifier: draw → begin_batch → verify_compressed_boolean → output-claim // absorbs (the low-level clear path the composed `verify_clear` wraps). - let mut verifier_transcript = Blake2bTranscript::new(b"engine-twin"); + let mut verifier_transcript = prover_transcript::(b"engine-twin", b""); let verifier_challenges = sumchecks.draw_challenges(&mut verifier_transcript).unwrap(); let mut verifier_recorder = ClearSumcheckRecorder::::new(); let (verifier_batch, verifier_coefficients) = sumchecks @@ -205,7 +205,10 @@ fn clear_engine_twin_matches_generated_verify_clear() { assert_eq!(reduction.value, proved.final_claim); assert_eq!(reduction.point.as_slice(), proved.challenges.as_slice()); assert_eq!(verifier_coefficients, prover_coefficients); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } #[test] @@ -217,8 +220,8 @@ fn committed_engine_twin_matches_generated_verify_zk() { // prove_batch → finish (output-claim row commitments absorbed). let sumchecks = fixture(); let inputs = inputs(); - let mut prover_transcript = Blake2bTranscript::new(b"engine-zk-twin"); - let prover_challenges = sumchecks.draw_challenges(&mut prover_transcript).unwrap(); + let mut prover_state = prover_transcript::(b"engine-zk-twin", b""); + let prover_challenges = sumchecks.draw_challenges(&mut prover_state).unwrap(); let instruction_sum = sumchecks .instruction_read_raf @@ -249,7 +252,7 @@ fn committed_engine_twin_matches_generated_verify_zk() { &inputs, &prover_challenges, &mut recorder, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); let mut members: Vec<&mut dyn ProveRounds> = @@ -259,15 +262,15 @@ fn committed_engine_twin_matches_generated_verify_zk() { &mut members, &mut SequentialRounds, &mut recorder, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); let recorded = recorder - .finish(&synthetic_output_values(), &mut prover_transcript) + .finish(&synthetic_output_values(), &mut prover_state) .unwrap(); assert!(recorded.committed_witness.is_some()); - let mut verifier_transcript = Blake2bTranscript::new(b"engine-zk-twin"); + let mut verifier_transcript = prover_transcript::(b"engine-zk-twin", b""); let _verifier_challenges = sumchecks.draw_challenges(&mut verifier_transcript).unwrap(); let consistency = sumchecks .verify_zk(&recorded.proof, &mut verifier_transcript) @@ -283,7 +286,10 @@ fn committed_engine_twin_matches_generated_verify_zk() { ); assert_eq!(consistency.max_num_vars, batch.max_num_vars); assert_eq!(consistency.max_degree, batch.max_degree); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } /// Twin-transcript lock for the shared uni-skip verification core: a clear @@ -308,17 +314,17 @@ fn uniskip_prover_twin_matches_uniskip_verify_clear() { &coefficients, ); - let mut prover_transcript = Blake2bTranscript::new(b"uniskip-stage-twin"); + let mut prover_state = prover_transcript::(b"uniskip-stage-twin", b""); let proved = prove_uniskip_clear::( poly, input_claim, degree, domain_size, - &mut prover_transcript, + &mut prover_state, ) .unwrap(); - let mut verifier_transcript = Blake2bTranscript::new(b"uniskip-stage-twin"); + let mut verifier_transcript = prover_transcript::(b"uniskip-stage-twin", b""); let challenge = uniskip::verify_clear( &SumcheckProof::::from(proved.proof), ¶ms, @@ -329,5 +335,8 @@ fn uniskip_prover_twin_matches_uniskip_verify_clear() { .unwrap(); assert_eq!(challenge, proved.challenge); - assert_eq!(prover_transcript.state(), verifier_transcript.state()); + assert_eq!( + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } diff --git a/crates/jolt-prover/tests/support/field_inline/dory.rs b/crates/jolt-prover/tests/support/field_inline/dory.rs index a674c8d9a3..9e2bfe5241 100644 --- a/crates/jolt-prover/tests/support/field_inline/dory.rs +++ b/crates/jolt-prover/tests/support/field_inline/dory.rs @@ -3,7 +3,7 @@ use jolt_crypto::{Bn254G1, Pedersen}; use jolt_dory::DoryScheme; use jolt_field::Fr; use jolt_prover::{dory, JoltBackend, JoltSharedPreprocessing, ProverConfig, ProverError}; -use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; +use jolt_transcript::{Keccak, LegacyBlake2bTranscript as ProverState, ProverState}; use jolt_verifier::proof::JoltProof; use jolt_verifier::{JoltVerifierPreprocessing, VerifierError}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; @@ -56,7 +56,7 @@ pub fn try_prove( ) .with_field_inline() .expect("field-inline witness view"); - let proof = dory::prove::, Blake2bTranscript>( + let proof = dory::prove::, ProverState>( backend.with_witness(&witness), &preprocessing, &config, @@ -71,7 +71,7 @@ pub fn verify_full( public_io: &JoltDevice, proof: &Proof, ) -> Result<(), VerifierError> { - jolt_verifier::verify::, Blake2bTranscript>( + jolt_verifier::verify::, ProverState>( preprocessing, public_io, proof, diff --git a/crates/jolt-prover/tests/zk_e2e.rs b/crates/jolt-prover/tests/zk_e2e.rs index d8a0f55ae0..24603623d2 100644 --- a/crates/jolt-prover/tests/zk_e2e.rs +++ b/crates/jolt-prover/tests/zk_e2e.rs @@ -30,7 +30,7 @@ mod zk { use jolt_prover::dory::DoryProverPreprocessing; use jolt_prover::{JoltBackend, JoltSharedPreprocessing, ProverConfig}; use jolt_riscv::JoltTraceRow; - use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; + use jolt_transcript::{Keccak, LegacyBlake2bTranscript as ProverState, ProverState}; use jolt_verifier::proof::{JoltProof, JoltProofClaims}; use jolt_verifier::VerifierError; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; @@ -97,7 +97,7 @@ mod zk { .expect("trusted advice commitment") }); let proof = - jolt_prover::dory::prove::, Blake2bTranscript>( + jolt_prover::dory::prove::, ProverState>( backend.with_witness(&witness), &preprocessing, &config, @@ -118,7 +118,7 @@ mod zk { } fn verify(proved: &ProvedGuest) -> Result<(), VerifierError> { - jolt_verifier::verify::, Blake2bTranscript>( + jolt_verifier::verify::, ProverState>( &proved.preprocessing.verifier, &proved.public_io, &proved.proof, @@ -167,7 +167,7 @@ mod zk { JoltVmWitnessInputs::new(&run.program, &program_preprocessing, run.trace), ); let proof = - jolt_prover::dory::prove::, Blake2bTranscript>( + jolt_prover::dory::prove::, ProverState>( JoltBackend::reference().with_witness(&witness), &preprocessing, &config, @@ -175,7 +175,7 @@ mod zk { &public_io, ) .expect("committed ZK prove"); - jolt_verifier::verify::, Blake2bTranscript>( + jolt_verifier::verify::, ProverState>( &preprocessing.verifier, &public_io, &proof, diff --git a/crates/jolt-spartan-prover/src/lib.rs b/crates/jolt-spartan-prover/src/lib.rs index 764f31238f..4c32f50120 100644 --- a/crates/jolt-spartan-prover/src/lib.rs +++ b/crates/jolt-spartan-prover/src/lib.rs @@ -15,7 +15,7 @@ use jolt_sumcheck::{ prove_batch, BatchMember, BatchPrelude, ClearSumcheckRecorder, CompressedSumcheckProof, ProveRounds, SequentialRounds, SumcheckRecorder, }; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, ProverTranscript}; use rounds::{InnerRounds, OuterRounds}; @@ -32,11 +32,11 @@ pub fn prove( public_inputs: &[PCS::Field], witness: &[PCS::Field], pcs_setup: &PCS::ProverSetup, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result, SpartanError> where - PCS::Field: AppendToTranscript, - PCS::Output: AppendToTranscript, + PCS::Field: Encoding, + PCS::Output: Encoding, { key.validate_public_inputs(public_inputs)?; if witness.len() != key.witness_len() { @@ -96,11 +96,11 @@ where }) } -fn prove_rounds( +fn prove_rounds( member: &mut dyn ProveRounds, degree: usize, claim: F, - transcript: &mut impl Transcript, + transcript: &mut (impl FieldTranscript + ProverTranscript), ) -> Result<(CompressedSumcheckProof, Vec, F), SpartanError> { let prelude = BatchPrelude::try_new( vec![BatchMember { diff --git a/crates/jolt-spartan-prover/tests/spartan.rs b/crates/jolt-spartan-prover/tests/spartan.rs index ef7a64dabf..34aac1a439 100644 --- a/crates/jolt-spartan-prover/tests/spartan.rs +++ b/crates/jolt-spartan-prover/tests/spartan.rs @@ -14,7 +14,7 @@ use jolt_r1cs::ConstraintMatrices; use jolt_spartan_prover::prove; use jolt_spartan_verifier::{SpartanError, SpartanKey}; use jolt_sumcheck::SumcheckError; -use jolt_transcript::{AppendToTranscript, Blake2bTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, Encoding, ProverState, Transcript}; fn matrices() -> ConstraintMatrices { let one = Fr::one(); @@ -40,8 +40,8 @@ fn public() -> [Fr; 1] { fn witness() -> [Fr; 3] { [3, 9, 27].map(Fr::from_u64) } -fn transcript() -> Blake2bTranscript { - Blake2bTranscript::new(b"spartan-test") +fn transcript() -> ProverState { + prover_transcript::(b"spartan-test", b"") } fn hyperkzg_setup() -> ( @@ -63,14 +63,14 @@ fn hyperkzg_setup() -> ( fn check_backend>(pk: &PCS::ProverSetup, vk: &PCS::VerifierSetup) where - PCS::Output: AppendToTranscript, + PCS::Output: Encoding, { let key = key(); let mut pt = transcript(); let proof = prove::(&key, &public(), &witness(), pk, &mut pt).unwrap(); let mut vt = transcript(); key.verify::(&public(), &proof, vk, &mut vt).unwrap(); - assert_eq!(pt.challenge(), vt.challenge()); + assert_eq!(pt.challenge().unwrap(), vt.challenge().unwrap()); let bytes = bincode::serde::encode_to_vec(&proof, bincode::config::standard()).unwrap(); let (decoded, used) = bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap(); diff --git a/crates/jolt-spartan-verifier/src/key.rs b/crates/jolt-spartan-verifier/src/key.rs index 147f6e0682..8a9f037016 100644 --- a/crates/jolt-spartan-verifier/src/key.rs +++ b/crates/jolt-spartan-verifier/src/key.rs @@ -2,7 +2,7 @@ use blake2::{digest::consts::U32, Blake2b, Digest}; use jolt_field::JoltField; use jolt_poly::EqPolynomial; use jolt_r1cs::ConstraintMatrices; -use jolt_transcript::{AppendToTranscript, Label, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, Label}; use crate::{outer_relation, SpartanError}; @@ -147,13 +147,13 @@ impl SpartanKey { } } -impl SpartanKey { +impl SpartanKey { /// Bind the authenticated relation, public input, and commitment before tau. - pub fn begin( + pub fn begin( &self, public_inputs: &[F], commitment: &C, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result, SpartanError> { self.validate_public_inputs(public_inputs)?; transcript.append(&Label(b"spartan-clear-v1")); @@ -161,7 +161,7 @@ impl SpartanKey { transcript.append_bytes(&self.policy_id); transcript.append_values(b"public-inputs", public_inputs); transcript.append_labeled(b"witness-commitment", commitment); - let tau = transcript.challenge_vector(self.row_vars()); + let tau = transcript.challenge_vector(self.row_vars())?; transcript.append_labeled(b"spartan-outer", &F::zero()); Ok(tau) } @@ -172,14 +172,14 @@ impl SpartanKey { row_weights: &[F], public_inputs: &[F], evaluations: [F; 3], - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<([F; 3], F), SpartanError> { self.validate_public_inputs(public_inputs)?; transcript.append_values(b"outer-evaluations", &evaluations); let weights = [ - transcript.challenge(), - transcript.challenge(), - transcript.challenge(), + transcript.challenge()?, + transcript.challenge()?, + transcript.challenge()?, ]; let public = std::iter::once(F::one()) .chain(public_inputs.iter().copied()) @@ -204,7 +204,7 @@ impl SpartanKey { /// Binds the terminal witness claim before the PCS opening transcript. pub fn append_witness_evaluation( evaluation: F, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) { transcript.append_labeled(b"witness-evaluation", &evaluation); } diff --git a/crates/jolt-spartan-verifier/src/lib.rs b/crates/jolt-spartan-verifier/src/lib.rs index b3c0a31a52..486e379f87 100644 --- a/crates/jolt-spartan-verifier/src/lib.rs +++ b/crates/jolt-spartan-verifier/src/lib.rs @@ -29,7 +29,7 @@ use jolt_sumcheck::{ BooleanHypercube, CompressedSumcheckProof, SumcheckClaim, SumcheckError, SUMCHECK_ROUND_TRANSCRIPT_LABEL, }; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -86,9 +86,19 @@ pub enum SpartanError { Matrix(#[from] ConstraintMatrixEvalError), #[error(transparent)] Opening(#[from] OpeningsError), + /// The transcript refused a challenge draw: the challenge codec does not + /// support the request, or its rejection pool ran out. + #[error("transcript rejected a challenge draw")] + TranscriptRejected, } -impl SpartanKey { +impl From for SpartanError { + fn from(_: jolt_transcript::VerificationError) -> Self { + Self::TranscriptRejected + } +} + +impl SpartanKey { /// Verify against this authenticated relation and the application's PCS key. /// /// The application's key policy must fix PCS setup and transcript choices. @@ -98,10 +108,10 @@ impl SpartanKey { public_inputs: &[F], proof: &SpartanProof, pcs_setup: &PCS::VerifierSetup, - transcript: &mut impl Transcript, + transcript: &mut impl FieldTranscript, ) -> Result<(), SpartanError> where - PCS::Output: AppendToTranscript, + PCS::Output: Encoding, { let tau = self.begin(public_inputs, &proof.witness_commitment, transcript)?; let outer = proof.outer.verify( diff --git a/crates/jolt-sumcheck/src/recorder.rs b/crates/jolt-sumcheck/src/recorder.rs index ffc24074eb..cd0a785daa 100644 --- a/crates/jolt-sumcheck/src/recorder.rs +++ b/crates/jolt-sumcheck/src/recorder.rs @@ -21,7 +21,7 @@ use jolt_field::Field; #[cfg(feature = "committed")] use jolt_field::JoltField; use jolt_poly::{CompressedPoly, UnivariatePoly}; -use jolt_transcript::{Encoding, FieldTranscript, ProverTranscript}; +use jolt_transcript::{Encoding, FieldTranscript, ProverTranscript, Transcript}; #[cfg(feature = "committed")] use rand_core::RngCore; @@ -49,9 +49,7 @@ pub trait SumcheckRecorder { /// declaration order). Clear: each appended under `b"sumcheck_claim"`. /// Committed: no-op — the claims' commitments were already absorbed by the /// stage that produced them, so the transcript never sees the scalars. - fn absorb_input_claims(&mut self, input_claims: &[F], transcript: &mut T) - where - T: FieldTranscript + ProverTranscript; + fn absorb_input_claims(&mut self, input_claims: &[F], transcript: &mut T); /// Record one round polynomial, grind the round's proof of work at the /// difficulty for `degree_bound` (the batch's degree bound, which the @@ -116,10 +114,7 @@ impl ClearSumcheckRecorder { impl SumcheckRecorder for ClearSumcheckRecorder { type Proof = CompressedSumcheckProof; - fn absorb_input_claims(&mut self, input_claims: &[F], transcript: &mut T) - where - T: FieldTranscript + ProverTranscript, - { + fn absorb_input_claims(&mut self, input_claims: &[F], transcript: &mut T) { for input_claim in input_claims { append_sumcheck_claim(transcript, input_claim); } @@ -206,10 +201,7 @@ where { type Proof = SumcheckProof; - fn absorb_input_claims(&mut self, _input_claims: &[F], _transcript: &mut T) - where - T: FieldTranscript + ProverTranscript, - { + fn absorb_input_claims(&mut self, _input_claims: &[F], _transcript: &mut T) { } fn absorb_round( diff --git a/crates/jolt-transcript/src/lib.rs b/crates/jolt-transcript/src/lib.rs index 6d717599cf..8f6dc8f675 100644 --- a/crates/jolt-transcript/src/lib.rs +++ b/crates/jolt-transcript/src/lib.rs @@ -53,13 +53,13 @@ pub use grind_bits::{ pub use poseidon::PoseidonSponge; #[cfg(feature = "prover-grinding")] pub use pow::{MessageSearch, Probe}; -pub use prover::ProverTranscript; +pub use prover::{ProverInit, ProverTranscript}; #[cfg(feature = "test-utils")] -pub use scripted::{ScriptedTranscript, TranscriptEvent}; +pub use scripted::{Script, ScriptedTranscript, TranscriptEvent}; pub use setup::{prover_transcript, session_id, verifier_transcript, PROTOCOL_ID}; pub use spongefish::{Encoding, ProverState, VerificationError, VerifierState}; pub use transcript::{FieldTranscript, Transcript}; -pub use verifier::VerifierTranscript; +pub use verifier::{VerifierInit, VerifierTranscript}; #[cfg(feature = "transcript-blake2b")] use blake2::Blake2b512 as Blake2b512Digest; diff --git a/crates/jolt-transcript/src/prover.rs b/crates/jolt-transcript/src/prover.rs index ddc875f347..991bce7aeb 100644 --- a/crates/jolt-transcript/src/prover.rs +++ b/crates/jolt-transcript/src/prover.rs @@ -2,7 +2,7 @@ #[cfg(feature = "prover-grinding")] use crate::pow::{Leb128, MessageSearch, SpongePow}; -use crate::Transcript; +use crate::{prover_transcript, Transcript}; #[cfg(feature = "prover-grinding")] use spongefish::VerificationError; use spongefish::{DuplexSpongeInit, DuplexSpongeInterface, Encoding, ProverState}; @@ -11,7 +11,7 @@ use spongefish::{DuplexSpongeInit, DuplexSpongeInterface, Encoding, ProverState} /// and the proof-of-work nonce searches. pub trait ProverTranscript: Transcript { /// The duplex sponge behind the transcript. - type Sponge: DuplexSpongeInterface; + type Sponge: DuplexSpongeInterface + Sync; /// Absorbs `msg` and appends its NARG-serialized form for verifier replay. fn prover_message(&mut self, msg: &M); @@ -75,6 +75,23 @@ pub trait ProverTranscript: Transcript { fn public_grind(&mut self, bits: u32) -> Result, VerificationError>; } +/// A prover transcript that a prover builds itself, bound to the same +/// instance its verifier binds: from the session and the public statement, as +/// [`prover_transcript`] does. +pub trait ProverInit: ProverTranscript + Sized { + /// The transcript bound to `session` and `statement`. + fn init(session: &[u8], statement: &(impl Encoding + ?Sized)) -> Self; +} + +impl ProverInit for ProverState +where + H: DuplexSpongeInit + Sync, +{ + fn init(session: &[u8], statement: &(impl Encoding + ?Sized)) -> Self { + prover_transcript::(session, statement) + } +} + impl ProverTranscript for ProverState where H: DuplexSpongeInterface + Sync, diff --git a/crates/jolt-transcript/src/scripted.rs b/crates/jolt-transcript/src/scripted.rs index 71e010a556..be41ff64f1 100644 --- a/crates/jolt-transcript/src/scripted.rs +++ b/crates/jolt-transcript/src/scripted.rs @@ -6,8 +6,11 @@ use std::collections::VecDeque; #[cfg(feature = "prover-grinding")] use crate::pow::MessageSearch; use crate::transcript::sealed::Sealed; -use crate::{ChallengeSetSize, FieldTranscript, ProverTranscript}; -use spongefish::{Encoding, VerificationError}; +use crate::{ + ChallengeSetSize, FieldTranscript, ProverInit, ProverTranscript, VerifierInit, + VerifierTranscript, +}; +use spongefish::{Encoding, FromNarg, VerificationError}; /// One sponge operation a [`ScriptedTranscript`] saw, in order. #[derive(Clone, Debug, PartialEq, Eq)] @@ -21,24 +24,53 @@ pub enum TranscriptEvent { Challenges(Vec), } -/// `T` with its challenge set declared as `2^256` and its challenges -/// optionally replaced by a script. +/// What a [`ScriptedTranscript`] does with the challenges of each draw. +pub trait Script { + /// Whether the transcript declares its challenge set as `2^256`, which + /// makes every difficulty from + /// [`grind_bits_for_loss`](crate::grind_bits_for_loss) 0 so proofs carry + /// no nonces. Otherwise it declares the wrapped transcript's set. + const UNPRICED: bool; + + /// Sees the challenges the wrapped transcript drew for one draw, and may + /// replace them. + fn draw(&mut self, challenges: &mut [F]); +} + +/// A queue of challenges that replace the drawn ones, in order, while it +/// lasts. Unpriced. +impl Script for VecDeque { + const UNPRICED: bool = true; + + fn draw(&mut self, challenges: &mut [F]) { + for challenge in challenges { + match self.pop_front() { + Some(scripted) => *challenge = scripted, + None => break, + } + } + } +} + +/// `T` whose challenges pass through a [`Script`]: by default a queue of +/// replacement values, with the challenge set declared as `2^256`. /// /// Every operation runs on `T` and is logged. A draw of `len` challenges draws -/// them from `T`, keeping its sponge in step, then returns the next scripted -/// values in their place while the script lasts. The declared challenge set +/// them from `T`, keeping its sponge in step, then hands them to the script, +/// which may replace them. With the default script the declared challenge set /// makes every difficulty from /// [`grind_bits_for_loss`](crate::grind_bits_for_loss) 0, so proofs carry no /// nonces. /// -/// Tests use it to choose challenges, to compare absorb orders, and to reach -/// checks that a hand-built proof would otherwise fail at its first -/// proof-of-work step. It overstates `T`'s challenge set and lets the caller -/// pick challenges, so it gives no soundness and must never back a real proof; -/// the `test-utils` feature that exports it is for dev-dependencies. -pub struct ScriptedTranscript { +/// Tests use it to choose challenges, to compare absorb orders, to record and +/// replay challenge tapes, and to reach checks that a hand-built proof would +/// otherwise fail at its first proof-of-work step. It can overstate `T`'s +/// challenge set and lets the caller pick challenges, so it gives no soundness +/// and must never back a real proof; the `test-utils` feature that exports it +/// is for dev-dependencies. +pub struct ScriptedTranscript> { inner: T, - script: VecDeque, + script: S, events: Vec>, } @@ -50,9 +82,16 @@ impl ScriptedTranscript { /// `inner` whose next challenges are `script`, in order. pub fn with_script(inner: T, script: Vec) -> Self { + Self::scripted_by(inner, script.into()) + } +} + +impl ScriptedTranscript { + /// `inner` whose draws pass through `script`. + pub fn scripted_by(inner: T, script: S) -> Self { Self { inner, - script: script.into(), + script, events: Vec::new(), } } @@ -79,7 +118,7 @@ impl ScriptedTranscript { } } -impl Sealed for ScriptedTranscript { +impl Sealed for ScriptedTranscript { fn absorb(&mut self, message: &M) { self.events .push(TranscriptEvent::Absorb(message.encode().as_ref().to_vec())); @@ -96,24 +135,28 @@ impl Sealed for ScriptedTranscript { } } -impl, F: Clone> FieldTranscript for ScriptedTranscript { - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); +impl FieldTranscript for ScriptedTranscript +where + T: FieldTranscript, + F: Clone, + S: Script, +{ + const CHALLENGE_SET: ChallengeSetSize = if S::UNPRICED { + ChallengeSetSize::power_of_two(256) + } else { + T::CHALLENGE_SET + }; fn challenge_vector(&mut self, len: usize) -> Result, VerificationError> { let mut challenges = self.inner.challenge_vector(len)?; - for challenge in &mut challenges { - match self.script.pop_front() { - Some(scripted) => *challenge = scripted, - None => break, - } - } + self.script.draw(&mut challenges); self.events .push(TranscriptEvent::Challenges(challenges.clone())); Ok(challenges) } } -impl ProverTranscript for ScriptedTranscript { +impl ProverTranscript for ScriptedTranscript { type Sponge = T::Sponge; fn prover_message(&mut self, msg: &M) { @@ -141,3 +184,32 @@ impl ProverTranscript for ScriptedTranscript { self.inner.public_grind(bits) } } + +impl VerifierTranscript for ScriptedTranscript { + fn prover_message(&mut self) -> Result { + let message = self.inner.prover_message::()?; + self.events + .push(TranscriptEvent::Absorb(message.encode().as_ref().to_vec())); + Ok(message) + } + + fn check_grind(&mut self, bits: u32) -> Result<(), VerificationError> { + self.inner.check_grind(bits) + } + + fn check_eof(self) -> Result<(), VerificationError> { + self.inner.check_eof() + } +} + +impl<'a, T: VerifierInit<'a>, F, S: Default> VerifierInit<'a> for ScriptedTranscript { + fn init(session: &[u8], statement: &(impl Encoding + ?Sized), narg: &'a [u8]) -> Self { + Self::scripted_by(T::init(session, statement, narg), S::default()) + } +} + +impl ProverInit for ScriptedTranscript { + fn init(session: &[u8], statement: &(impl Encoding + ?Sized)) -> Self { + Self::scripted_by(T::init(session, statement), S::default()) + } +} diff --git a/crates/jolt-transcript/src/verifier.rs b/crates/jolt-transcript/src/verifier.rs index aae3e1d2d1..c5700acca3 100644 --- a/crates/jolt-transcript/src/verifier.rs +++ b/crates/jolt-transcript/src/verifier.rs @@ -1,8 +1,9 @@ //! Verifier-only operations on `spongefish::VerifierState`. -use crate::{pow::SpongePow, Transcript}; +use crate::{pow::SpongePow, verifier_transcript, Transcript}; use spongefish::{ - DuplexSpongeInterface, Encoding, FromNarg, Transcript as _, VerificationError, VerifierState, + DuplexSpongeInit, DuplexSpongeInterface, Encoding, FromNarg, Transcript as _, + VerificationError, VerifierState, }; /// The verifier's side of a [`Transcript`]: messages read from the NARG @@ -65,6 +66,20 @@ pub trait VerifierTranscript: Transcript { fn check_eof(self) -> Result<(), VerificationError>; } +/// A verifier transcript that a verifier builds itself, so that it binds the +/// instance it checks: from the session, the public statement and the +/// proof's NARG string, as [`verifier_transcript`] does. +pub trait VerifierInit<'a>: VerifierTranscript + Sized { + /// The transcript bound to `session` and `statement` over `narg`. + fn init(session: &[u8], statement: &(impl Encoding + ?Sized), narg: &'a [u8]) -> Self; +} + +impl<'a, H: DuplexSpongeInit> VerifierInit<'a> for VerifierState<'a, H> { + fn init(session: &[u8], statement: &(impl Encoding + ?Sized), narg: &'a [u8]) -> Self { + verifier_transcript::(session, statement, narg) + } +} + impl VerifierTranscript for VerifierState<'_, H> where H: DuplexSpongeInterface, diff --git a/crates/jolt-verifier-derive/src/lib.rs b/crates/jolt-verifier-derive/src/lib.rs index 7f2785a8f4..6167fb0fbf 100644 --- a/crates/jolt-verifier-derive/src/lib.rs +++ b/crates/jolt-verifier-derive/src/lib.rs @@ -497,10 +497,13 @@ fn expand(input: DeriveInput) -> syn::Result { .map(|(plan, (sum, coeff))| { if plan.is_option { quote! { - let #coeff = #sum.as_ref().map(|_| transcript.challenge_scalar()); + let #coeff = #sum + .as_ref() + .map(|_| transcript.challenge()) + .transpose()?; } } else { - quote!(let #coeff = transcript.challenge_scalar();) + quote!(let #coeff = transcript.challenge()?;) } }); @@ -569,7 +572,7 @@ fn expand(input: DeriveInput) -> syn::Result { > where __R: ::jolt_sumcheck::SumcheckRecorder<#f>, - __T: ::jolt_transcript::Transcript, + __T: ::jolt_transcript::FieldTranscript<#f>, { use #relations::ConcreteSumcheck as _; @@ -628,8 +631,8 @@ fn expand(input: DeriveInput) -> syn::Result { stage: usize, ) -> ::core::result::Result<#output_points_name<#f>, #krate::VerifierError> where - __C: ::core::clone::Clone + ::jolt_transcript::AppendToTranscript, - __T: ::jolt_transcript::Transcript, + __C: ::core::clone::Clone + ::jolt_transcript::Encoding, + __T: ::jolt_transcript::FieldTranscript<#f>, { use #relations::ConcreteSumcheck as _; @@ -688,11 +691,11 @@ fn expand(input: DeriveInput) -> syn::Result { if plan.is_option { quote! { if self.#id.is_some() { - __batching_coefficients.push(transcript.challenge_scalar()); + __batching_coefficients.push(transcript.challenge()?); } } } else { - quote!(__batching_coefficients.push(transcript.challenge_scalar());) + quote!(__batching_coefficients.push(transcript.challenge()?);) } }); @@ -706,8 +709,8 @@ fn expand(input: DeriveInput) -> syn::Result { #krate::VerifierError, > where - __C: ::core::clone::Clone + ::jolt_transcript::AppendToTranscript, - __T: ::jolt_transcript::Transcript, + __C: ::core::clone::Clone + ::jolt_transcript::Encoding, + __T: ::jolt_transcript::FieldTranscript<#f>, { use #relations::ConcreteSumcheck as _; @@ -1073,7 +1076,7 @@ fn expand(input: DeriveInput) -> syn::Result { /// challenges draw nothing; `Option` members draw only when present. /// This single-sources the stage's inline per-instance draw, so its /// Fiat-Shamir order follows member declaration order. - pub fn draw_challenges<__T: ::jolt_transcript::Transcript>( + pub fn draw_challenges<__T: ::jolt_transcript::FieldTranscript<#f>>( &self, transcript: &mut __T, ) -> ::core::result::Result<#challenges_name<#f>, #krate::VerifierError> { @@ -1109,7 +1112,7 @@ fn expand(input: DeriveInput) -> syn::Result { /// Append every absorbed opening to the transcript in canonical order, /// each under the `b"opening_claim"` label, matching the prover's /// commitment order. - pub fn append_output_claims<__T: ::jolt_transcript::Transcript>( + pub fn append_output_claims<__T: ::jolt_transcript::Transcript>( &self, transcript: &mut __T, claims: &#output_claims_name<#f>, diff --git a/crates/jolt-verifier/Cargo.toml b/crates/jolt-verifier/Cargo.toml index 59a33b3a15..b2654c59ec 100644 --- a/crates/jolt-verifier/Cargo.toml +++ b/crates/jolt-verifier/Cargo.toml @@ -68,6 +68,7 @@ serde.workspace = true thiserror.workspace = true [dev-dependencies] +jolt-transcript = { workspace = true, features = ["test-utils", "transcript-keccak"] } jolt-host.workspace = true jolt-prover.workspace = true jolt-witness.workspace = true diff --git a/crates/jolt-verifier/src/error.rs b/crates/jolt-verifier/src/error.rs index 5c4cf5dc0e..68fb32a81c 100644 --- a/crates/jolt-verifier/src/error.rs +++ b/crates/jolt-verifier/src/error.rs @@ -142,4 +142,20 @@ pub enum VerifierError { #[error("the challenge set needs proof of work, which this build's proofs do not carry")] ChallengeSetNeedsProofOfWork, + + /// The transcript refused a challenge draw: the challenge codec does not + /// support the request, or its rejection pool ran out. + #[error("transcript rejected a challenge draw")] + TranscriptRejected, + + /// The proof's NARG string has unread bytes, or a transcript check + /// failed earlier. + #[error("the transcript did not end cleanly")] + NargNotConsumed, +} + +impl From for VerifierError { + fn from(_: jolt_transcript::VerificationError) -> Self { + Self::TranscriptRejected + } } diff --git a/crates/jolt-verifier/src/lib.rs b/crates/jolt-verifier/src/lib.rs index f7d879efe8..4ad91b2f0b 100644 --- a/crates/jolt-verifier/src/lib.rs +++ b/crates/jolt-verifier/src/lib.rs @@ -65,7 +65,6 @@ pub use verifier::absorb_akita_commitments; #[cfg(not(feature = "akita"))] pub use verifier::absorb_transcript_commitments; pub use verifier::{ - absorb_committed_program_commitments, absorb_transcript_preamble, validate_and_seed_transcript, - validate_inputs_from_parts, verify, verify_until_stage1, CheckedInputs, PreStage1VerifierState, - ProofTranscriptConfig, + absorb_committed_program_commitments, validate_and_seed_transcript, validate_inputs_from_parts, + verify, CheckedInputs, JoltStatement, ProofTranscriptConfig, JOLT_SESSION, }; diff --git a/crates/jolt-verifier/src/proof.rs b/crates/jolt-verifier/src/proof.rs index 8e38ea8b03..15d21ba707 100644 --- a/crates/jolt-verifier/src/proof.rs +++ b/crates/jolt-verifier/src/proof.rs @@ -66,6 +66,11 @@ pub struct JoltProof< pub rw_config: JoltReadWriteConfig, pub one_hot_config: JoltOneHotConfig, pub trace_polynomial_order: TracePolynomialOrder, + /// The spongefish NARG string: the prover messages the verifier reads + /// from its transcript. Every message of the current protocol is a field + /// of this struct absorbed as a public message, so the prover leaves it + /// empty; the verifier still requires it to be read to its end. + pub narg: Vec, } impl JoltProof @@ -108,6 +113,7 @@ where rw_config, one_hot_config, trace_polynomial_order, + narg: Vec::new(), } } @@ -156,6 +162,7 @@ where rw_config: self.rw_config, one_hot_config: self.one_hot_config, trace_polynomial_order: self.trace_polynomial_order, + narg: self.narg, } } } diff --git a/crates/jolt-verifier/src/stages/pow.rs b/crates/jolt-verifier/src/stages/pow.rs index 5f6f1c9d5e..fa8b7b471c 100644 --- a/crates/jolt-verifier/src/stages/pow.rs +++ b/crates/jolt-verifier/src/stages/pow.rs @@ -15,7 +15,7 @@ //! A slot holds a nonce exactly when its difficulty is positive. use jolt_claims::{Expr, Source}; -use jolt_transcript::{grind_bits_for_loss, Transcript, SECURITY_BITS}; +use jolt_transcript::{grind_bits_for_loss, FieldTranscript, SECURITY_BITS}; use serde::{Deserialize, Serialize}; use crate::num::u64_from_usize; @@ -178,7 +178,7 @@ impl TryFrom for StagePowNonces { /// # Errors /// [`VerifierError::PowDifficulty`] if no supported difficulty reaches /// `SECURITY_BITS` for this loss and challenge set. -pub fn pow_bits(site: PowSite, loss: u64) -> Result { +pub fn pow_bits>(site: PowSite, loss: u64) -> Result { if loss == 0 { return Ok(0); } @@ -192,30 +192,14 @@ pub fn pow_bits(site: PowSite, loss: u64) -> Result( +pub fn check_pow>( site: PowSite, loss: u64, nonces: &StagePowNonces, transcript: &mut T, ) -> Result<(), VerifierError> { transcript - .check_grind(pow_bits::(site, loss)?, nonces.get(site)) - .map_err(|_| VerifierError::ProofOfWorkRejected { site }) -} - -/// Grinds the nonce of the burst at `site` right before its first draw, for -/// the prover. Returns `None` when the difficulty is zero. -/// -/// # Errors -/// [`VerifierError::PowDifficulty`] as for [`pow_bits`], and -/// [`VerifierError::ProofOfWorkRejected`] if the nonce search fails. -pub fn grind_pow( - site: PowSite, - loss: u64, - transcript: &mut T, -) -> Result, VerifierError> { - transcript - .grind(pow_bits::(site, loss)?) + .check_public_grind(pow_bits::(site, loss)?, nonces.get(site)) .map_err(|_| VerifierError::ProofOfWorkRejected { site }) } @@ -227,7 +211,7 @@ pub fn grind_pow( /// # Errors /// [`VerifierError::ChallengeSetNeedsProofOfWork`] if a burst of the largest /// loss needs a positive difficulty. -pub fn require_no_pow() -> Result<(), VerifierError> { +pub fn require_no_pow>() -> Result<(), VerifierError> { match grind_bits_for_loss(u64::MAX, T::CHALLENGE_SET, SECURITY_BITS) { Ok(0) => Ok(()), _ => Err(VerifierError::ChallengeSetNeedsProofOfWork), diff --git a/crates/jolt-verifier/src/stages/relations.rs b/crates/jolt-verifier/src/stages/relations.rs index 070e923413..302f1b0df1 100644 --- a/crates/jolt-verifier/src/stages/relations.rs +++ b/crates/jolt-verifier/src/stages/relations.rs @@ -25,7 +25,7 @@ use std::collections::BTreeSet; use jolt_claims::SymbolicSumcheck; use jolt_field::JoltField; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use crate::stages::ids::{VerifierChallengeId, VerifierDerivedId}; use crate::VerifierError; @@ -47,7 +47,7 @@ pub trait OutputAppend: OutputClaims { /// ([`OutputClaims::opening_values`]) order, each under the `b"opening_claim"` /// label. This is the Fiat-Shamir order and MUST match the order in which the /// prover commits the openings. - fn append_openings>(&self, transcript: &mut T) { + fn append_openings>(&self, transcript: &mut T) { for value in self.opening_values() { transcript.append_labeled(b"opening_claim", &value); } @@ -150,16 +150,23 @@ where /// /// The bound is `SumcheckChallenges` — which every `Challenges` already /// implements — so the default needs no separate `Default` derive. It errors - /// only if the per-field draw cannot populate the struct, which cannot happen for - /// the infinite `challenge_scalar` stream the default supplies. - fn draw_challenges>( + /// only if the transcript refuses a draw: the stream of single challenges it + /// supplies is otherwise unbounded. + fn draw_challenges>( &self, transcript: &mut T, ) -> Result, VerifierError> { - SumcheckChallenges::from_transcript_values(::core::iter::repeat_with(|| { - transcript.challenge_scalar() - })) - .map_err(VerifierError::from) + // The stream stops at the first refused draw, which is reported as + // such rather than as a missing challenge. + let mut rejected = false; + let challenges = SumcheckChallenges::from_transcript_values( + ::core::iter::repeat_with(|| transcript.challenge()) + .map_while(|draw| draw.map_err(|_| rejected = true).ok()), + ); + if rejected { + return Err(VerifierError::TranscriptRejected); + } + challenges.map_err(VerifierError::from) } /// Proof-of-work loss of what [`draw_challenges`](Self::draw_challenges) @@ -537,125 +544,46 @@ pub fn stage_claim_failed(stage: impl Debug, reason: impl ToString) -> VerifierE } /// Test-only transcript double for asserting [`ConcreteSumcheck::draw_challenges`] -/// reproduces a stage's inline Fiat-Shamir draw exactly. +/// reproduces a stage's inline Fiat-Shamir draw exactly, and that +/// `append_openings` reproduces the reference absorb order. /// -/// Unlike the `append_openings` recorder, challenge *squeezes* -/// (`challenge`/`challenge_scalar`/`challenge_scalar_powers`) append no bytes, so a -/// byte-chunk recorder cannot observe them. This double instead records an ordered -/// event log that distinguishes a squeeze from a byte-append (e.g. the -/// `ram_val_check` gamma domain separator), and returns a *distinct sequential* -/// scalar from each squeeze so a relation's stored challenge can be checked against -/// the squeeze that produced it. +/// It logs every absorb and every challenge draw in order, and its `n`-th +/// challenge is `Fr(n)`, so a relation's stored challenge can be matched to the +/// draw that produced it. #[cfg(test)] -#[expect( - clippy::arithmetic_side_effects, - reason = "tests use plain arithmetic on fixture data" -)] pub(crate) mod draw_recording { use jolt_field::{Fr, Ring}; - use jolt_transcript::{ChallengeSetSize, Transcript}; - - #[derive(Clone, Debug, PartialEq, Eq)] - pub(crate) enum DrawEvent { - /// A challenge squeeze (`challenge`/`challenge_scalar`/the single squeeze - /// inside `challenge_scalar_powers`). Carries the 1-based squeeze index so - /// the value a relation kept can be matched to its squeeze. - Squeeze(u64), - /// A raw byte append (a domain separator preceding a squeeze). - Append(Vec), - } - - /// A `Transcript` that logs every squeeze and byte-append in order. Each - /// squeeze returns `Fr(index)` for the 1-based squeeze counter, so the powers - /// `challenge_scalar_powers` derives are distinct and a stored `gamma` can be - /// asserted to equal the squeezed value. - #[derive(Clone, Default)] - pub(crate) struct DrawRecordingTranscript { - pub(crate) events: Vec, - squeezes: u64, - } + use jolt_transcript::{prover_transcript, Keccak, ProverState, ScriptedTranscript}; + pub(crate) use jolt_transcript::TranscriptEvent; - impl Transcript for DrawRecordingTranscript { - type Challenge = Fr; - // Scripted challenges: no proof of work. - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); - - fn new(_label: &'static [u8]) -> Self { - Self::default() - } - - fn append_bytes(&mut self, bytes: &[u8]) { - self.events.push(DrawEvent::Append(bytes.to_vec())); - } + /// A logging transcript whose `n`-th challenge is `Fr(n)`. + pub(crate) type DrawRecordingTranscript = ScriptedTranscript, Fr>; - fn challenge(&mut self) -> Self::Challenge { - self.squeezes += 1; - self.events.push(DrawEvent::Squeeze(self.squeezes)); - Fr::from_u64(self.squeezes) - } - - fn challenge_bytes(&mut self, out: &mut [u8]) { - out.fill(0); - } + /// A fresh [`DrawRecordingTranscript`]. + pub(crate) fn recorder() -> DrawRecordingTranscript { + ScriptedTranscript::with_script( + prover_transcript::(b"draw-recording", b""), + (1..=4096).map(Fr::from_u64).collect(), + ) + } - fn state(&self) -> [u8; 32] { - [0u8; 32] - } + /// The event of a one-challenge draw that returned `Fr(index)`. + pub(crate) fn squeeze(index: u64) -> TranscriptEvent { + TranscriptEvent::Challenges(vec![Fr::from_u64(index)]) } - /// Run `draw` against a fresh recorder, returning its ordered event log and the - /// draw's result. A `draw_challenges` and a hand-written replica of the inline - /// draw, each passed through this, are directly comparable: equal event logs - /// prove the same squeeze/append sequence, and the recorder's distinct - /// sequential squeeze values let the returned challenge be checked against the + /// Run `draw` against a fresh recorder, returning its ordered event log and + /// the draw's result. A `draw_challenges` and a hand-written replica of the + /// inline draw, each passed through this, are directly comparable: equal + /// event logs prove the same absorb and draw sequence, and the sequential + /// challenge values let the returned challenge be checked against the /// replica's captured value. pub(crate) fn record( draw: impl FnOnce(&mut DrawRecordingTranscript) -> R, - ) -> (Vec, R) { - let mut transcript = DrawRecordingTranscript::default(); + ) -> (Vec>, R) { + let mut transcript = recorder(); let result = draw(&mut transcript); - (transcript.events, result) - } -} - -/// The append-order recorder shared by the stage `append_output_claims` ordering -/// locks: unlike the challenge recorder, it observes only byte appends. -#[cfg(test)] -pub(crate) mod append_recording { - use jolt_field::{Fr, Ring}; - use jolt_transcript::{ChallengeSetSize, Transcript}; - - /// A minimal `Transcript` double that records each appended byte chunk, so - /// that append order can be compared without depending on the digest. - #[derive(Clone, Default)] - pub(crate) struct RecordingTranscript { - pub(crate) chunks: Vec>, - } - - impl Transcript for RecordingTranscript { - type Challenge = Fr; - // Scripted challenges: no proof of work. - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); - - fn new(_label: &'static [u8]) -> Self { - Self::default() - } - - fn append_bytes(&mut self, bytes: &[u8]) { - self.chunks.push(bytes.to_vec()); - } - - fn challenge(&mut self) -> Self::Challenge { - Fr::from_u64(0) - } - - fn challenge_bytes(&mut self, out: &mut [u8]) { - out.fill(0); - } - - fn state(&self) -> [u8; 32] { - [0u8; 32] - } + (transcript.events().to_vec(), result) } } @@ -683,20 +611,20 @@ mod tests { JoltOpeningId::committed(polynomial, relation) } - use super::append_recording::RecordingTranscript; + use super::draw_recording::recorder; - /// The chunk stream produced by appending `opening_values()` one-by-one is + /// The absorb stream produced by appending `opening_values()` one-by-one is /// the reference Fiat-Shamir order; `append_openings` must reproduce it. fn assert_append_matches_values>(claims: &C) { - let mut via_append = RecordingTranscript::default(); + let mut via_append = recorder(); claims.append_openings(&mut via_append); - let mut via_values = RecordingTranscript::default(); + let mut via_values = recorder(); for value in claims.opening_values() { via_values.append_labeled(b"opening_claim", &value); } - assert_eq!(via_append.chunks, via_values.chunks); + assert_eq!(via_append.absorbed(), via_values.absorbed()); } #[derive(OutputClaims)] @@ -1387,7 +1315,7 @@ mod begin_batch_tests { let (replica_events, (instruction_coeff, registers_coeff)) = record(|t| { append_sumcheck_claim(t, &instruction_sum); append_sumcheck_claim(t, ®isters_sum); - (t.challenge_scalar(), t.challenge_scalar()) + (t.challenge()?, t.challenge()?) }); assert_eq!(events, replica_events); diff --git a/crates/jolt-verifier/src/stages/stage1/verify.rs b/crates/jolt-verifier/src/stages/stage1/verify.rs index 7c27936b68..7e8a7ffbf3 100644 --- a/crates/jolt-verifier/src/stages/stage1/verify.rs +++ b/crates/jolt-verifier/src/stages/stage1/verify.rs @@ -2,7 +2,7 @@ use jolt_claims::protocols::jolt::{geometry::spartan::SpartanOuterDimensions, Jo use jolt_crypto::VectorCommitment; use jolt_field::Ring; use jolt_openings::CommitmentScheme; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use super::outer_remainder::{outer_remainder_input_values_from_uniskip_output, OuterRemainder}; use super::outputs::{ @@ -29,7 +29,7 @@ pub fn verify( where PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { let uniskip_params = uniskip::UniskipParams::spartan_outer(); let log_t = crate::num::ilog2(checked.trace_length); @@ -41,7 +41,7 @@ where &pow_nonces, transcript, )?; - let tau = uniskip::draw_spartan_outer_tau(transcript, log_t); + let tau = uniskip::draw_spartan_outer_tau(transcript, log_t)?; if !checked.zk { let claims = &proof.clear_claims()?.stage1; diff --git a/crates/jolt-verifier/src/stages/stage2/outputs.rs b/crates/jolt-verifier/src/stages/stage2/outputs.rs index c9c87906f7..7b5a53a18e 100644 --- a/crates/jolt-verifier/src/stages/stage2/outputs.rs +++ b/crates/jolt-verifier/src/stages/stage2/outputs.rs @@ -227,7 +227,7 @@ impl Stage2Output { )] mod tests { use super::*; - use crate::stages::relations::draw_recording::{record, DrawEvent}; + use crate::stages::relations::draw_recording::{record, squeeze}; use crate::stages::relations::ConcreteSumcheck; use common::jolt_device::{JoltDevice, MemoryConfig}; #[cfg(feature = "field-inline")] @@ -301,9 +301,11 @@ mod tests { let (inline_events, (inline_gammas, inline_output_address)) = record(|t| { ( (0..gamma_draws) - .map(|_| t.challenge_scalar()) + .map(|_| t.challenge().unwrap()) + .collect::>(), + (0..log_k) + .map(|_| t.challenge().unwrap()) .collect::>(), - (0..log_k).map(|_| t.challenge()).collect::>(), ) }); let (draw_events, challenges) = record(|t| sumchecks.draw_challenges(t).unwrap()); @@ -312,7 +314,7 @@ mod tests { assert_eq!( draw_events, (1..=(gamma_draws + log_k) as u64) - .map(DrawEvent::Squeeze) + .map(squeeze) .collect::>() ); #[cfg(not(feature = "field-inline"))] diff --git a/crates/jolt-verifier/src/stages/stage2/ram_output_check.rs b/crates/jolt-verifier/src/stages/stage2/ram_output_check.rs index ae3916278c..268fdfd858 100644 --- a/crates/jolt-verifier/src/stages/stage2/ram_output_check.rs +++ b/crates/jolt-verifier/src/stages/stage2/ram_output_check.rs @@ -18,7 +18,7 @@ use jolt_claims::SymbolicSumcheck; use jolt_field::JoltField; use jolt_poly::{range_mask_mle_msb, sparse_segments_mle_msb, try_eq_mle}; use jolt_program::preprocess::PublicIoMemory; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use crate::stages::relations::ConcreteSumcheck; use crate::VerifierError; @@ -123,14 +123,14 @@ impl ConcreteSumcheck for RamOutputCheck { /// MUST stay `challenge()` (not `challenge_scalar()`): both decode the same /// 16-byte squeeze, but differently, so switching would silently change the /// address point values without changing the transcript bytes. - fn draw_challenges>( + fn draw_challenges>( &self, transcript: &mut T, ) -> Result, VerifierError> { Ok(RamOutputCheckChallenges { output_address: (0..self.read_write_dimensions.log_k()) .map(|_| transcript.challenge()) - .collect(), + .collect::>()?, }) } diff --git a/crates/jolt-verifier/src/stages/stage2/verify.rs b/crates/jolt-verifier/src/stages/stage2/verify.rs index 3a787c715f..c7ed808a84 100644 --- a/crates/jolt-verifier/src/stages/stage2/verify.rs +++ b/crates/jolt-verifier/src/stages/stage2/verify.rs @@ -12,7 +12,7 @@ use jolt_crypto::VectorCommitment; use jolt_field::JoltField; use jolt_openings::CommitmentScheme; use jolt_program::preprocess::PublicIoMemory; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; #[cfg(feature = "field-inline")] use super::field_registers_claim_reduction::{ @@ -95,7 +95,7 @@ pub fn verify( where PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { match (checked.zk, stage1) { (true, Stage1Output::Clear(_)) => { @@ -279,7 +279,7 @@ fn verify_product_uniskip( where PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { let log_t = crate::num::ilog2(checked.trace_length); let product_dimensions = SpartanProductDimensions::new(log_t); @@ -291,7 +291,7 @@ where &proof.stages.pow_nonces(), transcript, )?; - let tau_high = uniskip::draw_spartan_product_tau_high(transcript); + let tau_high = uniskip::draw_spartan_product_tau_high(transcript)?; let uniskip_params = uniskip::UniskipParams::spartan_product(); match stage1 { Stage1Output::Clear(stage1) => { diff --git a/crates/jolt-verifier/src/stages/stage3/verify.rs b/crates/jolt-verifier/src/stages/stage3/verify.rs index 525e8f8914..0bb036bd7a 100644 --- a/crates/jolt-verifier/src/stages/stage3/verify.rs +++ b/crates/jolt-verifier/src/stages/stage3/verify.rs @@ -2,7 +2,7 @@ use jolt_claims::protocols::jolt::{geometry::dimensions::TraceDimensions, JoltRe use jolt_crypto::VectorCommitment; use jolt_field::JoltField; use jolt_openings::CommitmentScheme; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use super::{ instruction_input::{instruction_input_input_values_from_upstream, InstructionInput}, @@ -52,7 +52,7 @@ pub fn verify( where PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { let log_t = crate::num::ilog2(checked.trace_length); let dimensions = TraceDimensions::new(log_t); diff --git a/crates/jolt-verifier/src/stages/stage4/outputs.rs b/crates/jolt-verifier/src/stages/stage4/outputs.rs index 4c26834861..9d52afd92c 100644 --- a/crates/jolt-verifier/src/stages/stage4/outputs.rs +++ b/crates/jolt-verifier/src/stages/stage4/outputs.rs @@ -1,6 +1,6 @@ use jolt_field::JoltField; use jolt_sumcheck::BatchedCommittedSumcheckConsistency; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use crate::stages::relations::{OutputClaims, SumcheckBatch}; use crate::stages::zk::outputs::CommittedOutputClaimOutput; @@ -105,7 +105,7 @@ impl Stage4OutputClaims { /// Append every produced opening to the transcript in canonical order, each /// under the `b"opening_claim"` label, matching the prover's commitment order. - pub fn append_to_transcript>(&self, transcript: &mut T) { + pub fn append_to_transcript>(&self, transcript: &mut T) { for value in self.opening_values() { transcript.append_labeled(b"opening_claim", &value); } @@ -193,7 +193,7 @@ impl Stage4Output { #[expect(clippy::unwrap_used)] mod tests { use super::*; - use crate::stages::relations::draw_recording::{record, DrawEvent}; + use crate::stages::relations::draw_recording::{record, squeeze, TranscriptEvent}; #[cfg(feature = "field-inline")] use jolt_claims::protocols::field_inline::FieldInlineConfig; use jolt_claims::protocols::jolt::geometry::dimensions::{ @@ -307,7 +307,7 @@ mod tests { let leading_gamma_draws = 2usize; let (inline_events, (inline_gammas, inline_ram_gamma)) = record(|t| { let gammas = (0..leading_gamma_draws) - .map(|_| t.challenge_scalar()) + .map(|_| t.challenge().unwrap()) .collect::>(); let ram = sumchecks.ram_val_check.draw_challenges(t).unwrap(); (gammas, ram.gamma) @@ -315,10 +315,10 @@ mod tests { let (draw_events, challenges) = record(|t| sumchecks.draw_challenges(t).unwrap()); assert_eq!(draw_events, inline_events); - assert!(matches!(draw_events.first(), Some(DrawEvent::Squeeze(1)))); + assert_eq!(draw_events.first(), Some(&squeeze(1))); assert!(draw_events .iter() - .any(|event| matches!(event, DrawEvent::Append(_)))); + .any(|event| matches!(event, TranscriptEvent::Absorb(_)))); #[cfg(not(feature = "field-inline"))] let drawn_gammas = vec![challenges.registers_read_write.gamma]; #[cfg(feature = "field-inline")] diff --git a/crates/jolt-verifier/src/stages/stage4/ram_val_check.rs b/crates/jolt-verifier/src/stages/stage4/ram_val_check.rs index 53c0707122..26d76d09fd 100644 --- a/crates/jolt-verifier/src/stages/stage4/ram_val_check.rs +++ b/crates/jolt-verifier/src/stages/stage4/ram_val_check.rs @@ -29,7 +29,7 @@ use jolt_claims::protocols::jolt::{ use jolt_claims::SymbolicSumcheck; use jolt_field::JoltField; use jolt_poly::{block_selector_mle_msb, LtPolynomial}; -use jolt_transcript::{LabelWithCount, Transcript}; +use jolt_transcript::{FieldTranscript, Label}; use crate::stages::relations::ConcreteSumcheck; use crate::stages::stage2::{Stage2BatchOutputClaims, Stage2BatchOutputPoints}; @@ -183,17 +183,15 @@ impl ConcreteSumcheck for RamValCheck { openings } - /// Reproduces the stage-4 inline RAM value-check gamma draw: the - /// `b"ram_val_check_gamma"` domain separator (an empty labeled append) followed - /// by `ram_val_check_gamma = challenge_scalar()`. The separator's empty append - /// is part of the soundness-critical byte stream, so it is replayed here too. - fn draw_challenges>( + /// Draws the RAM value-check gamma behind its `b"ram_val_check_gamma"` + /// domain-separator label. + fn draw_challenges>( &self, transcript: &mut T, ) -> Result, VerifierError> { - append_ram_val_check_gamma_domain_separator(transcript); + transcript.append(&Label(b"ram_val_check_gamma")); Ok(RamValCheckChallenges { - gamma: transcript.challenge_scalar(), + gamma: transcript.challenge()?, }) } @@ -553,32 +551,19 @@ fn ram_val_check_advice_block( }) } -/// Absorb the Fiat-Shamir domain separator for the RAM value-check gamma: an empty -/// message labeled `b"ram_val_check_gamma"`. The prover appends this empty labeled -/// chunk before sampling the gamma, so [`RamValCheck::draw_challenges`] must -/// reproduce it byte-for-byte (label chunk + empty payload) or every challenge from -/// here on diverges. -fn append_ram_val_check_gamma_domain_separator(transcript: &mut T) { - transcript.append(&LabelWithCount(b"ram_val_check_gamma", 0)); - transcript.append_bytes(&[]); -} - #[cfg(test)] -#[expect( - clippy::unwrap_used, - clippy::indexing_slicing, - reason = "test code indexes its own fixed-size fixtures" -)] +#[expect(clippy::unwrap_used, reason = "test code unwraps its own fixtures")] mod tests { use super::*; - use crate::stages::relations::draw_recording::{record, DrawEvent}; + use crate::stages::relations::draw_recording::{record, TranscriptEvent}; use jolt_field::Fr; + use jolt_transcript::Encoding; - // Overrides the default to prepend the `b"ram_val_check_gamma"` domain separator - // (an empty labeled append) before the gamma squeeze; the append is part of the + // Overrides the default to absorb the `b"ram_val_check_gamma"` domain + // separator label before the gamma draw; the label is part of the // soundness-critical byte stream, so it must appear in `draw_challenges` too. #[test] - fn draw_challenges_appends_domain_separator_then_draws_gamma() { + fn draw_challenges_absorbs_domain_separator_then_draws_gamma() { let relation = RamValCheck::::new( TraceDimensions::new(4), 3, @@ -586,30 +571,19 @@ mod tests { ); let (inline_events, inline_gamma) = record(|t| { - append_ram_val_check_gamma_domain_separator(t); - t.challenge_scalar() + t.append(&Label(b"ram_val_check_gamma")); + t.challenge().unwrap() }); let (draw_events, challenges) = record(|t| relation.draw_challenges(t).unwrap()); assert_eq!(draw_events, inline_events); - assert!(draw_events.len() >= 2); - let (separator, last) = draw_events.split_at(draw_events.len() - 1); - assert_eq!(last, [DrawEvent::Squeeze(1)]); - assert!(separator - .iter() - .all(|event| matches!(event, DrawEvent::Append(_)))); - assert_eq!(challenges.gamma, inline_gamma); - } - - #[test] - fn ram_val_check_gamma_domain_separator_matches_core_empty_bytes_append() { - let (events, ()) = record(append_ram_val_check_gamma_domain_separator); - - let mut packed = vec![0; 32]; - packed[..b"ram_val_check_gamma".len()].copy_from_slice(b"ram_val_check_gamma"); assert_eq!( - events, - [DrawEvent::Append(packed), DrawEvent::Append(Vec::new())] + draw_events, + [ + TranscriptEvent::Absorb(Label(b"ram_val_check_gamma").encode().as_ref().to_vec()), + TranscriptEvent::Challenges(vec![inline_gamma]), + ] ); + assert_eq!(challenges.gamma, inline_gamma); } } diff --git a/crates/jolt-verifier/src/stages/stage4/verify.rs b/crates/jolt-verifier/src/stages/stage4/verify.rs index 32914f5ca9..559758572b 100644 --- a/crates/jolt-verifier/src/stages/stage4/verify.rs +++ b/crates/jolt-verifier/src/stages/stage4/verify.rs @@ -10,7 +10,7 @@ use jolt_field::JoltField; use jolt_openings::CommitmentScheme; use jolt_poly::sparse_segments_mle_msb; use jolt_program::preprocess::PublicInitialRam; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; #[cfg(feature = "field-inline")] use super::field_registers_read_write_checking::{ @@ -95,7 +95,7 @@ pub fn verify( where PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { let log_t = crate::num::ilog2(checked.trace_length); let log_k = crate::num::ilog2(checked.ram_K); diff --git a/crates/jolt-verifier/src/stages/stage5/outputs.rs b/crates/jolt-verifier/src/stages/stage5/outputs.rs index 54aace3241..0de5e01835 100644 --- a/crates/jolt-verifier/src/stages/stage5/outputs.rs +++ b/crates/jolt-verifier/src/stages/stage5/outputs.rs @@ -166,7 +166,7 @@ impl Stage5Output { #[expect(clippy::unwrap_used)] mod tests { use super::*; - use crate::stages::relations::draw_recording::{record, DrawEvent}; + use crate::stages::relations::draw_recording::{record, squeeze}; #[cfg(feature = "field-inline")] use jolt_claims::protocols::field_inline::FieldRegistersTraceDimensions; use jolt_claims::protocols::jolt::geometry::dimensions::TraceDimensions; @@ -239,13 +239,13 @@ mod tests { fn draw_challenges_matches_inline_draw_sequence() { let sumchecks = sumchecks(); let (inline_events, inline_gammas) = - record(|t| (0..2).map(|_| t.challenge_scalar()).collect::>()); + record(|t| (0..2).map(|_| t.challenge().unwrap()).collect::>()); let (draw_events, challenges) = record(|t| sumchecks.draw_challenges(t).unwrap()); assert_eq!(draw_events, inline_events); assert_eq!( draw_events, - vec![DrawEvent::Squeeze(1), DrawEvent::Squeeze(2)] + vec![squeeze(1), squeeze(2)] ); assert_eq!( vec![ diff --git a/crates/jolt-verifier/src/stages/stage5/verify.rs b/crates/jolt-verifier/src/stages/stage5/verify.rs index f911e22fd6..cbe07842f4 100644 --- a/crates/jolt-verifier/src/stages/stage5/verify.rs +++ b/crates/jolt-verifier/src/stages/stage5/verify.rs @@ -4,7 +4,7 @@ use jolt_claims::protocols::jolt::{geometry::dimensions::JoltFormulaDimensions, use jolt_crypto::VectorCommitment; use jolt_field::JoltField; use jolt_openings::CommitmentScheme; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; #[cfg(feature = "field-inline")] use super::field_registers_val_evaluation::{ @@ -91,7 +91,7 @@ pub fn verify( where PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { let log_k = crate::num::ilog2(checked.ram_K); let trace_dimensions = formula_dimensions.trace; diff --git a/crates/jolt-verifier/src/stages/stage6a/booleanity.rs b/crates/jolt-verifier/src/stages/stage6a/booleanity.rs index 9e59a08fc5..8bc616fc2a 100644 --- a/crates/jolt-verifier/src/stages/stage6a/booleanity.rs +++ b/crates/jolt-verifier/src/stages/stage6a/booleanity.rs @@ -13,7 +13,7 @@ pub use jolt_claims::protocols::jolt::relations::booleanity::{ }; use jolt_claims::SymbolicSumcheck; use jolt_field::JoltField; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use crate::stages::relations::ConcreteSumcheck; use crate::VerifierError; @@ -110,7 +110,7 @@ impl ConcreteSumcheck for BooleanityAddressPhase { /// both decode the same 16-byte squeeze, but differently, so switching /// would silently change the reference/gamma values without changing the /// transcript bytes. - fn draw_challenges>( + fn draw_challenges>( &self, transcript: &mut T, ) -> Result, VerifierError> { @@ -121,14 +121,14 @@ impl ConcreteSumcheck for BooleanityAddressPhase { // only settles the (unreachable) underflow for the arithmetic lint. if reference_address.len() < chunk_bits { let missing = chunk_bits.saturating_sub(reference_address.len()); - reference_address.extend(transcript.challenge_vector(missing)); + reference_address.extend(transcript.challenge_vector(missing)?); } else { let excess = reference_address.len().saturating_sub(chunk_bits); reference_address = reference_address.split_off(excess); } Ok(BooleanityAddressPhaseChallenges { reference_address, - gamma: transcript.challenge(), + gamma: transcript.challenge()?, }) } diff --git a/crates/jolt-verifier/src/stages/stage6a/verify.rs b/crates/jolt-verifier/src/stages/stage6a/verify.rs index f2518fa59d..f1fbbe6759 100644 --- a/crates/jolt-verifier/src/stages/stage6a/verify.rs +++ b/crates/jolt-verifier/src/stages/stage6a/verify.rs @@ -4,7 +4,7 @@ use jolt_claims::protocols::composed::ComposedClaims; use jolt_claims::protocols::jolt::{geometry::dimensions::JoltFormulaDimensions, JoltRelationId}; use jolt_crypto::VectorCommitment; use jolt_openings::CommitmentScheme; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; #[cfg(feature = "field-inline")] use super::field_inline::field_inline_bytecode_read_raf_address_phase_input_values_from_upstream; @@ -53,7 +53,7 @@ pub fn verify( where PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { // The upstream cycle/register points and entry index ride on the relation // (full geometry at construction) for the prover's address-phase kernel; @@ -197,13 +197,12 @@ mod tests { }; use super::super::outputs::Stage6aOutputClaims; use super::*; - use crate::stages::relations::append_recording::RecordingTranscript; - use crate::stages::relations::draw_recording::{record, DrawEvent}; + use crate::stages::relations::draw_recording::{record, recorder, squeeze}; use jolt_claims::protocols::jolt::geometry::booleanity::BooleanityDimensions; use jolt_claims::protocols::jolt::geometry::bytecode::BytecodeReadRafDimensions; use jolt_claims::protocols::jolt::geometry::ra::JoltRaPolynomialLayout; use jolt_field::{Fr, Ring}; - use jolt_transcript::ChallengeSetSize; + use jolt_transcript::{prover_transcript, Keccak, ScriptedTranscript}; fn fr(value: u64) -> Fr { Fr::from_u64(value) @@ -262,17 +261,17 @@ mod tests { let (inline_events, (inline_gammas, inline_reference_address, inline_gamma)) = record(|t| { - let gammas: Vec = (0..6).map(|_| t.challenge_scalar()).collect(); + let gammas: Vec = (0..6).map(|_| t.challenge().unwrap()).collect(); let mut reference_address: Vec = address.iter().rev().copied().collect(); - reference_address.extend(t.challenge_vector(1)); - (gammas, reference_address, t.challenge()) + reference_address.extend(t.challenge_vector(1).unwrap()); + (gammas, reference_address, t.challenge().unwrap()) }); let (draw_events, challenges) = record(|t| sumchecks.draw_challenges(t).unwrap()); assert_eq!(draw_events, inline_events); assert_eq!( draw_events, - (1..=8u64).map(DrawEvent::Squeeze).collect::>() + (1..=8u64).map(squeeze).collect::>() ); assert_eq!( vec![ @@ -313,7 +312,7 @@ mod tests { assert_eq!( draw_events, - (1..=7u64).map(DrawEvent::Squeeze).collect::>() + (1..=7u64).map(squeeze).collect::>() ); let reversed: Vec = address.iter().rev().copied().collect(); assert_eq!(challenges.booleanity.reference_address, reversed[1..]); @@ -331,49 +330,24 @@ mod tests { let mut claims = sample_claims(); claims.bytecode_read_raf.val_stages = (903..908).map(fr).collect(); - let mut got = RecordingTranscript::default(); + let mut got = recorder(); sumchecks.append_output_claims(&mut got, &claims); - let mut want = RecordingTranscript::default(); + let mut want = recorder(); for value in [901, 903, 904, 905, 906, 907, 902].map(fr) { want.append_labeled(b"opening_claim", &value); } - assert_eq!(got.chunks, want.chunks); - } - - /// A transcript double whose every squeeze returns the same nontrivial - /// scalar, so `challenge_scalar_powers`' output is a genuine power vector. - #[derive(Clone, Default)] - struct ConstantChallengeTranscript; - - impl Transcript for ConstantChallengeTranscript { - type Challenge = Fr; - // Scripted challenges: no proof of work. - const CHALLENGE_SET: ChallengeSetSize = ChallengeSetSize::power_of_two(256); - fn new(_label: &'static [u8]) -> Self { - Self - } - fn append_bytes(&mut self, _bytes: &[u8]) {} - fn challenge(&mut self) -> Self::Challenge { - Fr::from_u64(7) - } - fn challenge_bytes(&mut self, out: &mut [u8]) { - out.fill(0); - } - - fn state(&self) -> [u8; 32] { - [0u8; 32] - } + assert_eq!(got.absorbed(), want.absorbed()); } /// The `stage_gamma_powers` expansion of a drawn scalar must equal - /// `challenge_scalar_powers`' output for the same squeezed scalar — the + /// `challenge_powers`' output for the same drawn scalar — the /// value-identity the stage-6a generated draw substitution relies on /// (the prover draws each stage's power vector inline; the verifier stores /// the scalar and expands at the stage-6b folds). #[test] - fn stage_gamma_powers_matches_challenge_scalar_powers() { + fn stage_gamma_powers_matches_challenge_powers() { use jolt_claims::protocols::jolt::geometry::bytecode::BYTECODE_STAGE_GAMMA_COUNTS; use jolt_claims::protocols::jolt::relations::bytecode::BytecodeReadRafAddressPhaseChallenges; @@ -386,13 +360,16 @@ mod tests { stage4_gamma: gamma, stage5_gamma: gamma, }; - let mut transcript = ConstantChallengeTranscript; + let mut transcript = ScriptedTranscript::with_script( + prover_transcript::(b"stage-gamma-powers", b""), + vec![gamma; BYTECODE_STAGE_GAMMA_COUNTS.len()], + ); for (powers, len) in challenges .stage_gamma_powers() .into_iter() .zip(BYTECODE_STAGE_GAMMA_COUNTS) { - assert_eq!(powers, transcript.challenge_scalar_powers(len)); + assert_eq!(powers, transcript.challenge_powers(len).unwrap()); } } } diff --git a/crates/jolt-verifier/src/stages/stage6b/batch.rs b/crates/jolt-verifier/src/stages/stage6b/batch.rs index 535f6a1e0e..20cd8692b8 100644 --- a/crates/jolt-verifier/src/stages/stage6b/batch.rs +++ b/crates/jolt-verifier/src/stages/stage6b/batch.rs @@ -10,6 +10,8 @@ //! exactly when their precommitted layout needs a cycle-phase reduction, so the //! batch's instance count matches the prover's. +use std::convert::Infallible; + #[cfg(feature = "field-inline")] use jolt_claims::protocols::field_inline::FieldRegistersTraceDimensions; #[cfg(not(feature = "akita"))] @@ -27,7 +29,7 @@ use jolt_crypto::VectorCommitment; use jolt_field::JoltField; use jolt_openings::CommitmentScheme; use jolt_riscv::JoltInstructionRow; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; use super::booleanity::{Booleanity, BooleanityCyclePhaseChallenges}; use super::bytecode_read_raf::{ @@ -124,13 +126,13 @@ pub struct Stage6bDraws { } impl Stage6bDraws { - pub fn draw>( + pub fn draw>( transcript: &mut T, formula_dimensions: &JoltFormulaDimensions, committed_bytecode: bool, - ) -> Self { + ) -> Result { Self::sequence(formula_dimensions, committed_bytecode, |_| { - transcript.challenge_scalar() + transcript.challenge().map_err(VerifierError::from) }) } @@ -145,37 +147,38 @@ impl Stage6bDraws { let mut loss = BATCH_COEFFICIENT_LOSS; let _ = Self::sequence(formula_dimensions, committed_bytecode, |draw_loss| { loss = loss.saturating_add(draw_loss); - F::from_u64(0) + Ok::(F::from_u64(0)) }); loss } /// The burst's draws in squeeze order. `scalar` receives each draw's loss - /// and returns its value. Each draw folds its member's input claims with + /// and returns its value, or an error that stops the burst. Each draw folds its member's input claims with /// powers up to one less than the claim count: the instruction RA claims, /// the four base increment claims, the two field-register increment /// claims, and the staged bytecode Val claims. - fn sequence( + fn sequence( formula_dimensions: &JoltFormulaDimensions, committed_bytecode: bool, - mut scalar: impl FnMut(u64) -> F, - ) -> Self { + mut scalar: impl FnMut(u64) -> Result, + ) -> Result { // Field order is draw order: the struct literal evaluates in // declaration order. - Self { + Ok(Self { instruction_ra_gamma: scalar(u64_from_usize( formula_dimensions .instruction_ra_virtualization .num_virtual_ra_polys() .saturating_sub(1), - )), + ))?, #[cfg(not(feature = "akita"))] - inc_gamma: scalar(3), + inc_gamma: scalar(3)?, #[cfg(feature = "field-inline")] - field_registers_inc_gamma: scalar(1), + field_registers_inc_gamma: scalar(1)?, eta: committed_bytecode - .then(|| scalar(u64_from_usize(NUM_BYTECODE_VAL_STAGES.saturating_sub(1)))), - } + .then(|| scalar(u64_from_usize(NUM_BYTECODE_VAL_STAGES.saturating_sub(1)))) + .transpose()?, + }) } } @@ -609,7 +612,7 @@ impl Stage6bSumchecks { )] mod tests { use super::*; - use crate::stages::relations::draw_recording::{record, DrawEvent}; + use crate::stages::relations::draw_recording::{record, squeeze}; use jolt_field::Fr; /// Pins the post-6a draw schedule to member declaration order: the instruction-RA gamma, @@ -643,17 +646,19 @@ mod tests { let (inline_events, inline_values) = record(|t| { (0..expected_squeezes) - .map(|_| t.challenge_scalar()) + .map(|_| t.challenge().unwrap()) .collect::>() }); let (draw_events, draws) = - record(|t| Stage6bDraws::::draw(t, &formula_dimensions, committed_bytecode)); + record(|t| { + Stage6bDraws::::draw(t, &formula_dimensions, committed_bytecode).unwrap() + }); assert_eq!(draw_events, inline_events); assert_eq!( draw_events, (1..=expected_squeezes as u64) - .map(DrawEvent::Squeeze) + .map(squeeze) .collect::>() ); let mut ordered = vec![draws.instruction_ra_gamma]; diff --git a/crates/jolt-verifier/src/stages/stage6b/verify.rs b/crates/jolt-verifier/src/stages/stage6b/verify.rs index 640c7c3ca6..ec355362ba 100644 --- a/crates/jolt-verifier/src/stages/stage6b/verify.rs +++ b/crates/jolt-verifier/src/stages/stage6b/verify.rs @@ -7,7 +7,7 @@ use jolt_claims::OutputClaims; use jolt_crypto::VectorCommitment; use jolt_field::JoltField; use jolt_openings::CommitmentScheme; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; #[cfg(not(feature = "akita"))] use super::committed_reduction_cycle_phase::{ @@ -87,7 +87,7 @@ pub fn verify( where PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { // The bytecode fold gamma shares stage 6a's squeeze; it and the booleanity // gamma ride on the stage-6a output as typed upstream values. The post-6a @@ -107,7 +107,7 @@ where transcript, formula_dimensions, bytecode_reduction_layout.is_some(), - ); + )?; let sumchecks = Stage6bSumchecks::build( checked, @@ -582,7 +582,7 @@ fn append_opening_claims( booleanity_point: &[F], ) where F: JoltField, - T: Transcript, + T: FieldTranscript, { // Single-sourced with the prover-curation order: both fronts absorb the // `stage6b_opening_values` sequence, so the two transcripts cannot drift. @@ -608,7 +608,7 @@ mod tests { use super::super::ram_hamming_booleanity::RamHammingBooleanityOutputClaims; use super::super::ram_ra_virtualization::RamRaVirtualizationOutputClaims; use super::*; - use crate::stages::relations::append_recording::RecordingTranscript; + use crate::stages::relations::draw_recording::recorder; use jolt_field::{Fr, Ring}; fn fr(value: u64) -> Fr { @@ -870,15 +870,15 @@ mod tests { fn append_opening_claims_follows_canonical_order() { let (claims, last) = sample_claims(); - let mut got = RecordingTranscript::default(); + let mut got = recorder(); append_opening_claims(&mut got, &claims, &[], &[]); - let mut want = RecordingTranscript::default(); + let mut want = recorder(); for value in (1..=last).map(fr) { want.append_labeled(b"opening_claim", &value); } - assert_eq!(got.chunks, want.chunks); + assert_eq!(got.absorbed(), want.absorbed()); } #[test] diff --git a/crates/jolt-verifier/src/stages/stage7/verify.rs b/crates/jolt-verifier/src/stages/stage7/verify.rs index 1439c65bf8..d120975362 100644 --- a/crates/jolt-verifier/src/stages/stage7/verify.rs +++ b/crates/jolt-verifier/src/stages/stage7/verify.rs @@ -11,7 +11,7 @@ use jolt_claims::protocols::jolt::{ use jolt_crypto::VectorCommitment; use jolt_field::JoltField; use jolt_openings::CommitmentScheme; -use jolt_transcript::Transcript; +use jolt_transcript::FieldTranscript; #[cfg(not(feature = "akita"))] use super::advice_address_phase::{ @@ -61,7 +61,7 @@ pub fn verify( where PCS: CommitmentScheme, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { let hamming_dimensions = hamming_weight_claim_reduction_dimensions( formula_dimensions.ra_layout, diff --git a/crates/jolt-verifier/src/stages/stage8/akita.rs b/crates/jolt-verifier/src/stages/stage8/akita.rs index 0d189525b3..1be166cddb 100644 --- a/crates/jolt-verifier/src/stages/stage8/akita.rs +++ b/crates/jolt-verifier/src/stages/stage8/akita.rs @@ -20,7 +20,7 @@ use jolt_openings::{ CommitmentScheme, EvaluationClaim, GroupOpeningClaim, TaggedGroupOpeningClaim, }; use jolt_poly::Point; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript}; use super::precommitted::precommitted_final_openings; use crate::stages::pow::{check_pow, PowSite, StagePowNonces}; @@ -150,7 +150,7 @@ fn reduce_object( ) -> Result, VerifierError> where PCS: CommitmentScheme, - T: Transcript, + T: FieldTranscript, { let claims = object_leaf_claims(&object.plan, leaves)?; let semantic = object.plan.packed_claims(&claims).map_err(batch_failed)?; @@ -245,10 +245,10 @@ pub fn verify( ) -> Result<(), VerifierError> where PCS: CommitmentScheme, - PCS::Output: Clone + AppendToTranscript + OneHotTraceCommitmentMetadata, + PCS::Output: Clone + Encoding + OneHotTraceCommitmentMetadata, PCS::VerifierSetup: OneHotTraceSetupMetadata, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { // Auxiliary objects precede the OneHotTrace group in canonical role order: advice, // (field-inline) the always-present field-increment commitment, then the direct diff --git a/crates/jolt-verifier/src/stages/stage8/verify.rs b/crates/jolt-verifier/src/stages/stage8/verify.rs index 595fdbc483..4201038773 100644 --- a/crates/jolt-verifier/src/stages/stage8/verify.rs +++ b/crates/jolt-verifier/src/stages/stage8/verify.rs @@ -46,7 +46,9 @@ use jolt_openings::{ use jolt_poly::Point; #[cfg(not(feature = "akita"))] use jolt_transcript::LabelWithCount; -use jolt_transcript::{AppendToTranscript, Transcript}; +#[cfg(feature = "akita")] +use jolt_transcript::Encoding; +use jolt_transcript::FieldTranscript; #[cfg(not(feature = "akita"))] /// One assembled final-opening batch entry. Public because the prover's @@ -87,7 +89,7 @@ where + ZkOpeningScheme, PCS::Output: Clone + HomomorphicCommitment, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { let log_t = formula_dimensions.trace.log_t(); let layout = formula_dimensions.ra_layout; @@ -171,7 +173,7 @@ where let opening_ids: Vec = entries.iter().map(|entry| entry.id).collect(); if checked.zk { - let gamma_powers = transcript.challenge_scalar_powers(entries.len()); + let gamma_powers = transcript.challenge_powers(entries.len())?; let commitments: Vec = entries .iter() .map(|entry| entry.commitment.clone()) @@ -193,8 +195,10 @@ where .map_err(|error| VerifierError::FinalOpeningVerificationFailed { reason: error.to_string(), })?; - ZkEvaluationClaim::new(pcs_opening_point.as_slice(), &hiding_evaluation_commitment) - .append_to_transcript(transcript); + transcript.append(&ZkEvaluationClaim::new( + pcs_opening_point.as_slice(), + &hiding_evaluation_commitment, + )); return Ok(Stage8Output::Zk(Stage8ZkOutput { opening_ids, @@ -229,9 +233,9 @@ where crate::num::u64_from_usize(opening_claims.len()), )); for claim in &opening_claims { - claim.evaluation.value.append_to_transcript(transcript); + transcript.append(&claim.evaluation.value); } - let gamma_powers = transcript.challenge_scalar_powers(opening_claims.len()); + let gamma_powers = transcript.challenge_powers(opening_claims.len())?; let joint_claim = gamma_powers .iter() @@ -261,7 +265,7 @@ where .map_err(|error| VerifierError::FinalOpeningVerificationFailed { reason: error.to_string(), })?; - EvaluationClaim::new(pcs_opening_point.clone(), joint_claim).append_to_transcript(transcript); + transcript.append(&EvaluationClaim::new(pcs_opening_point.clone(), joint_claim)); Ok(Stage8Output::Clear(Stage8ClearOutput { opening_claims, @@ -680,10 +684,10 @@ pub fn verify( where F: JoltField, PCS: CommitmentScheme, - PCS::Output: Clone + AppendToTranscript + super::OneHotTraceCommitmentMetadata, + PCS::Output: Clone + Encoding + super::OneHotTraceCommitmentMetadata, PCS::VerifierSetup: super::OneHotTraceSetupMetadata, VC: VectorCommitment, - T: Transcript, + T: FieldTranscript, { super::akita::verify( formula_dimensions, diff --git a/crates/jolt-verifier/src/stages/uniskip.rs b/crates/jolt-verifier/src/stages/uniskip.rs index 942960fdf4..e1386dacac 100644 --- a/crates/jolt-verifier/src/stages/uniskip.rs +++ b/crates/jolt-verifier/src/stages/uniskip.rs @@ -21,7 +21,7 @@ use jolt_sumcheck::{ CenteredIntegerDomain, CommittedSumcheckConsistency, SumcheckClaim, SumcheckProof, SumcheckStatement, UNISKIP_ROUND_TRANSCRIPT_LABEL, }; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript}; use crate::num::u64_from_usize; use crate::stages::zk::committed::{self, CommittedOutputClaimOutput}; @@ -90,16 +90,16 @@ impl UniskipParams { /// uni-skip round (the Spartan outer relation's cycle variables plus the two /// uni-skip-collapsed row variables). Both fronts call this, so the draw is /// single-sourced. -pub fn draw_spartan_outer_tau(transcript: &mut T, log_t: usize) -> Vec +pub fn draw_spartan_outer_tau(transcript: &mut T, log_t: usize) -> Result, VerifierError> where F: JoltField, - T: Transcript, + T: FieldTranscript, { #[expect( clippy::arithmetic_side_effects, reason = "log_t is an ilog2 result (< 64); log_t + 2 cannot overflow usize" )] - transcript.challenge_vector(log_t + 2) + Ok(transcript.challenge_vector(log_t + 2)?) } /// Proof-of-work loss of [`draw_spartan_outer_tau`]. A false outer claim @@ -120,16 +120,13 @@ pub fn spartan_product_tau_high_loss() -> u64 { } /// The stage-2 product tau_high, drawn before the product uni-skip round. -/// Both fronts call this, so the draw is single-sourced. MUST stay the raw -/// `challenge()` (not `challenge_scalar()`): both decode the same 16-byte -/// squeeze, but differently, so switching would silently change the value -/// without changing the transcript bytes. -pub fn draw_spartan_product_tau_high(transcript: &mut T) -> F +/// Both fronts call this, so the draw is single-sourced. +pub fn draw_spartan_product_tau_high(transcript: &mut T) -> Result where F: JoltField, - T: Transcript, + T: FieldTranscript, { - transcript.challenge() + Ok(transcript.challenge()?) } /// The ZK uni-skip step's outputs: the committed round consistency and output @@ -160,7 +157,7 @@ pub fn verify_clear( ) -> Result where F: JoltField, - T: Transcript, + T: FieldTranscript, { let reduction = proof .verify( @@ -198,8 +195,8 @@ pub fn verify_zk( ) -> Result, VerifierError> where F: JoltField, - C: Clone + AppendToTranscript, - T: Transcript, + C: Clone + Encoding, + T: FieldTranscript, { let consistency = proof .verify_committed_consistency( diff --git a/crates/jolt-verifier/src/verifier.rs b/crates/jolt-verifier/src/verifier.rs index 3232b4f43e..4a15552406 100644 --- a/crates/jolt-verifier/src/verifier.rs +++ b/crates/jolt-verifier/src/verifier.rs @@ -2,6 +2,7 @@ use common::constants::MAX_BLINDFOLD_GENERATORS; use common::jolt_device::JoltDevice; +use jolt_claims::protocols::field_inline::FieldInlineRepresentation; use jolt_claims::protocols::jolt::JoltRelationId; use jolt_claims::protocols::jolt::{JoltOneHotConfig, JoltReadWriteConfig}; #[cfg(not(feature = "akita"))] @@ -13,15 +14,16 @@ use jolt_openings::CommitmentScheme; use jolt_openings::{AdditivelyHomomorphic, ZkOpeningScheme}; use jolt_program::preprocess::{compute_max_ram_k, compute_min_ram_k}; use jolt_sumcheck::SumcheckProof; -#[cfg(feature = "akita")] -use jolt_transcript::append_length_prefixed; -use jolt_transcript::{AppendToTranscript, Label, LabelWithCount, Transcript, U64Word}; +use jolt_transcript::{ + Encoding, FieldTranscript, Label, LabelWithCount, Transcript, U64Word, VerifierInit, +}; #[cfg(not(feature = "akita"))] use crate::proof::JoltCommitments; use crate::{ config::{ - validate_proof_config, ZkConfig, JOLT_VERIFIER_CONFIG, JOLT_VERIFIER_INSTRUCTION_PROFILE, + validate_proof_config, CommitmentConfig, JoltProtocolConfig, ZkConfig, + JOLT_VERIFIER_CONFIG, JOLT_VERIFIER_INSTRUCTION_PROFILE, }, num, preprocessing::JoltVerifierPreprocessing, @@ -34,30 +36,31 @@ use crate::{ }; #[cfg(not(feature = "akita"))] -pub fn verify( +pub fn verify<'a, F, PCS, VC, T>( preprocessing: &JoltVerifierPreprocessing, public_io: &JoltDevice, - proof: &JoltProof, + proof: &'a JoltProof, trusted_advice_commitment: Option<&PCS::Output>, ) -> Result<(), VerifierError> where - F: JoltField + AppendToTranscript, + F: JoltField + Encoding, PCS: CommitmentScheme + AdditivelyHomomorphic + ZkOpeningScheme, - PCS::Output: AppendToTranscript + HomomorphicCommitment, + PCS::Output: Encoding + HomomorphicCommitment, VC: VectorCommitment, - VC::Output: Copy + HomomorphicCommitment + AppendToTranscript, - T: Transcript, + VC::Output: Copy + HomomorphicCommitment + Encoding, + T: FieldTranscript + VerifierInit<'a>, { use crate::stages::zk::{blindfold, inputs::BlindFoldInputs}; - crate::stages::pow::require_no_pow::()?; - let (checked, mut transcript) = validate_and_seed_transcript::( + crate::stages::pow::require_no_pow::()?; + let (checked, mut transcript) = validate_and_seed_transcript( preprocessing, public_io, proof, trusted_advice_commitment, + |statement| T::init(JOLT_SESSION, statement, &proof.narg), )?; // Built once for the whole verification and shared by the stages that read @@ -160,40 +163,42 @@ where .map_err(|error| VerifierError::BlindFoldVerificationFailed { reason: error.to_string(), })?; - return Ok(()); + } else { + let stage8::Stage8Output::Clear(_) = stage8 else { + return Err(VerifierError::ExpectedClearProof { field: "stage8" }); + }; } - let stage8::Stage8Output::Clear(_) = stage8 else { - return Err(VerifierError::ExpectedClearProof { field: "stage8" }); - }; - - Ok(()) + transcript + .check_eof() + .map_err(|_| VerifierError::NargNotConsumed) } /// The Akita verification path: the same stage spine, with ordered native /// trace-column claims and one batch opening for the trace, advice, /// and committed-program objects. No homomorphism bounds and no ZK tail. #[cfg(feature = "akita")] -pub fn verify( +pub fn verify<'a, F, PCS, VC, T>( preprocessing: &JoltVerifierPreprocessing, public_io: &JoltDevice, - proof: &JoltProof, + proof: &'a JoltProof, trusted_advice_commitment: Option<&PCS::Output>, ) -> Result<(), VerifierError> where - F: JoltField + AppendToTranscript, + F: JoltField + Encoding, PCS: CommitmentScheme, - PCS::Output: Clone + AppendToTranscript + stage8::OneHotTraceCommitmentMetadata, + PCS::Output: Clone + Encoding + stage8::OneHotTraceCommitmentMetadata, PCS::VerifierSetup: stage8::OneHotTraceSetupMetadata, VC: VectorCommitment, - VC::Output: Copy + AppendToTranscript, - T: Transcript, + VC::Output: Copy + Encoding, + T: FieldTranscript + VerifierInit<'a>, { - let (checked, mut transcript) = validate_and_seed_transcript::( + let (checked, mut transcript) = validate_and_seed_transcript( preprocessing, public_io, proof, trusted_advice_commitment, + |statement| T::init(JOLT_SESSION, statement, &proof.narg), )?; // Built once for the whole verification and shared by the stages that read @@ -274,25 +279,136 @@ where return Err(VerifierError::ExpectedClearProof { field: "stage8" }); }; - Ok(()) + transcript + .check_eof() + .map_err(|_| VerifierError::NargNotConsumed) +} + +/// The session every Jolt transcript is bound to. +pub const JOLT_SESSION: &[u8] = b"Jolt"; + +/// The statement every Jolt transcript is bound to before its first message: +/// the protocol configuration, the preprocessing digest, the public I/O, and +/// the proof's structural parameters, all validated before the transcript is +/// built. Prover and verifier encode it through this one type. +#[derive(Clone, Copy, Debug)] +pub struct JoltStatement<'a> { + pub checked: &'a CheckedInputs, + pub config: ProofTranscriptConfig, +} + +impl Encoding for JoltStatement<'_> { + fn encode(&self) -> impl AsRef<[u8]> { + let checked = self.checked; + let public_io = &checked.public_io; + let protocol = JoltProtocolConfig::for_zk(checked.zk); + let mut out = Vec::new(); + let mut word = |label: &'static [u8], value: u64| { + out.extend_from_slice(Label(label).encode().as_ref()); + out.extend_from_slice(U64Word(value).encode().as_ref()); + }; + word( + b"zk", + match protocol.zk { + ZkConfig::Transparent => 0, + ZkConfig::BlindFold => 1, + }, + ); + word( + b"commitment", + match protocol.commitment { + CommitmentConfig::Homomorphic => 0, + CommitmentConfig::Akita => 1, + }, + ); + word( + b"field_inline", + u64::from(protocol.field_inline.enabled), + ); + word( + b"field_register_log_k", + num::u64_from_usize(protocol.field_inline.field_register_log_k), + ); + word( + b"field_inline_representation", + match protocol.field_inline.representation { + FieldInlineRepresentation::NativeFieldElement => 0, + }, + ); + word( + b"max_input_size", + public_io.memory_layout.max_input_size, + ); + word( + b"max_output_size", + public_io.memory_layout.max_output_size, + ); + word(b"heap_size", public_io.memory_layout.heap_size); + word(b"panic", u64::from(public_io.panic)); + word(b"ram_K", num::u64_from_usize(checked.ram_K)); + word(b"trace_length", num::u64_from_usize(checked.trace_length)); + word(b"entry_address", checked.entry_address); + let rw = self.config.rw_config; + word( + b"ram_rw_phase1_num_rounds", + u64::from(rw.ram_rw_phase1_num_rounds), + ); + word( + b"ram_rw_phase2_num_rounds", + u64::from(rw.ram_rw_phase2_num_rounds), + ); + word( + b"registers_rw_phase1_num_rounds", + u64::from(rw.registers_rw_phase1_num_rounds), + ); + word( + b"registers_rw_phase2_num_rounds", + u64::from(rw.registers_rw_phase2_num_rounds), + ); + let one_hot = self.config.one_hot_config; + word(b"log_k_chunk", u64::from(one_hot.log_k_chunk)); + word( + b"lookups_ra_virtual_log_k_chunk", + u64::from(one_hot.lookups_ra_virtual_log_k_chunk), + ); + word( + b"dory_layout", + self.config.trace_polynomial_order.transcript_scalar(), + ); + for (label, bytes) in [ + (&b"preprocessing_digest"[..], &checked.preprocessing_digest[..]), + (b"inputs", &public_io.inputs), + (b"outputs", &public_io.outputs), + ] { + out.extend_from_slice( + LabelWithCount(label, num::u64_from_usize(bytes.len())) + .encode() + .as_ref(), + ); + out.extend_from_slice(bytes); + } + out + } } -/// Validates the inputs and proof shape, then seeds the Fiat-Shamir transcript -/// with the preamble and commitment absorptions. Every consumer of the staged -/// verification — [`verify`] on both builds, and the zk audit harness — starts -/// here, so there is exactly one absorption order. +/// Validates the inputs and proof shape, builds the Fiat-Shamir transcript +/// with `new_transcript` from the [`JoltStatement`], then absorbs the +/// commitments. Every consumer of the staged verification — [`verify`] on +/// both builds, the ZK prover's replay and the zk audit harness — starts here, +/// so there is exactly one absorption order. pub fn validate_and_seed_transcript( preprocessing: &JoltVerifierPreprocessing, public_io: &JoltDevice, proof: &JoltProof, trusted_advice_commitment: Option<&PCS::Output>, + new_transcript: impl FnOnce(&JoltStatement<'_>) -> T, ) -> Result<(CheckedInputs, T), VerifierError> where PCS: CommitmentScheme, - PCS::Output: AppendToTranscript, + PCS::Output: Encoding, VC: VectorCommitment, - VC::Output: AppendToTranscript, - T: Transcript, + VC::Output: Encoding, + T: Transcript, { validate_proof_config(&JOLT_VERIFIER_CONFIG, proof.protocol)?; validate_proof_consistency(proof, JOLT_VERIFIER_CONFIG.zk == ZkConfig::BlindFold)?; @@ -303,8 +419,14 @@ where trusted_advice_commitment.is_some(), )?; - let mut transcript = T::new(b"Jolt"); - absorb_preamble(&checked, proof, &mut transcript); + let mut transcript = new_transcript(&JoltStatement { + checked: &checked, + config: ProofTranscriptConfig { + rw_config: proof.rw_config, + one_hot_config: proof.one_hot_config, + trace_polynomial_order: proof.trace_polynomial_order, + }, + }); absorb_commitments( preprocessing, proof, @@ -626,84 +748,6 @@ where } } -/// Absorbs the Jolt Fiat-Shamir preamble: the preprocessing digest, public I/O -/// metadata, and the proof-derived structural parameters. WARNING: the byte -/// order here is consensus-critical — the prover's preamble must absorb -/// identically or the transcripts diverge. -#[jolt_verifier_derive::fs_scope(Preamble)] -pub(crate) fn absorb_preamble( - checked: &CheckedInputs, - proof: &JoltProof, - transcript: &mut T, -) where - PCS: CommitmentScheme, - VC: VectorCommitment, - T: Transcript, -{ - let public_io = &checked.public_io; - absorb_labeled_bytes( - transcript, - b"preprocessing_digest", - &checked.preprocessing_digest, - ); - absorb_labeled_u64( - transcript, - b"max_input_size", - public_io.memory_layout.max_input_size, - ); - absorb_labeled_u64( - transcript, - b"max_output_size", - public_io.memory_layout.max_output_size, - ); - absorb_labeled_u64(transcript, b"heap_size", public_io.memory_layout.heap_size); - absorb_labeled_bytes(transcript, b"inputs", &public_io.inputs); - absorb_labeled_bytes(transcript, b"outputs", &public_io.outputs); - absorb_labeled_u64(transcript, b"panic", u64::from(public_io.panic)); - absorb_labeled_u64(transcript, b"ram_K", num::u64_from_usize(checked.ram_K)); - absorb_labeled_u64( - transcript, - b"trace_length", - num::u64_from_usize(checked.trace_length), - ); - absorb_labeled_u64(transcript, b"entry_address", checked.entry_address); - absorb_labeled_u64( - transcript, - b"ram_rw_phase1_num_rounds", - u64::from(proof.rw_config.ram_rw_phase1_num_rounds), - ); - absorb_labeled_u64( - transcript, - b"ram_rw_phase2_num_rounds", - u64::from(proof.rw_config.ram_rw_phase2_num_rounds), - ); - absorb_labeled_u64( - transcript, - b"registers_rw_phase1_num_rounds", - u64::from(proof.rw_config.registers_rw_phase1_num_rounds), - ); - absorb_labeled_u64( - transcript, - b"registers_rw_phase2_num_rounds", - u64::from(proof.rw_config.registers_rw_phase2_num_rounds), - ); - absorb_labeled_u64( - transcript, - b"log_k_chunk", - u64::from(proof.one_hot_config.log_k_chunk), - ); - absorb_labeled_u64( - transcript, - b"lookups_ra_virtual_log_k_chunk", - u64::from(proof.one_hot_config.lookups_ra_virtual_log_k_chunk), - ); - absorb_labeled_u64( - transcript, - b"dory_layout", - proof.trace_polynomial_order.transcript_scalar(), - ); -} - /// Absorbs the commitments in the consensus-critical order — the proof-carried /// polynomial commitments, the optional advice commitments, then the committed /// program's preprocessing-held commitments. WARNING: the prover must absorb @@ -718,9 +762,9 @@ pub(crate) fn absorb_commitments( transcript: &mut T, ) where PCS: CommitmentScheme, - PCS::Output: AppendToTranscript, + PCS::Output: Encoding, VC: VectorCommitment, - T: Transcript, + T: Transcript, { #[cfg(not(feature = "akita"))] { @@ -767,19 +811,19 @@ pub fn absorb_akita_commitments( program_commitments: Option<(&C, &C)>, transcript: &mut T, ) where - C: AppendToTranscript, + C: Encoding, T: Transcript, { - append_length_prefixed(transcript, b"commitment", one_hot_trace); + transcript.append_labeled(b"commitment", one_hot_trace); if let Some(commitment) = untrusted_advice_commitment { - append_length_prefixed(transcript, b"untrusted_advice", commitment); + transcript.append_labeled(b"untrusted_advice", commitment); } if let Some(commitment) = trusted_advice_commitment { - append_length_prefixed(transcript, b"trusted_advice", commitment); + transcript.append_labeled(b"trusted_advice", commitment); } #[cfg(feature = "field-inline")] if let Some(commitment) = field_inc_commitment { - append_length_prefixed(transcript, b"field_inc", commitment); + transcript.append_labeled(b"field_inc", commitment); } if let Some((bytecode, image)) = program_commitments { absorb_akita_program_commitments(bytecode, image, transcript); @@ -789,11 +833,11 @@ pub fn absorb_akita_commitments( #[cfg(feature = "akita")] pub fn absorb_akita_program_commitments(bytecode: &C, image: &C, transcript: &mut T) where - C: AppendToTranscript, + C: Encoding, T: Transcript, { - append_length_prefixed(transcript, b"program_bytecode_commitment", bytecode); - append_length_prefixed(transcript, b"program_image_init_commitment", image); + transcript.append_labeled(b"program_bytecode_commitment", bytecode); + transcript.append_labeled(b"program_image_init_commitment", image); } /// Absorbs the preprocessing-held committed-program commitments (whole @@ -804,17 +848,11 @@ pub fn absorb_committed_program_commitments( program_image_commitment: &C, transcript: &mut T, ) where - C: AppendToTranscript, + C: Encoding, T: Transcript, { - append_payload_label(transcript, b"program_bytecode_commit", bytecode_commitment); - transcript.append(bytecode_commitment); - append_payload_label( - transcript, - b"program_image_commitment", - program_image_commitment, - ); - transcript.append(program_image_commitment); + transcript.append_labeled(b"program_bytecode_commit", bytecode_commitment); + transcript.append_labeled(b"program_image_commitment", program_image_commitment); } /// Absorbs the proof-derived polynomial commitments (increment, one-hot `ra`, @@ -829,13 +867,10 @@ pub fn absorb_transcript_commitments( trusted_advice_commitment: Option<&C>, transcript: &mut T, ) where - C: AppendToTranscript, + C: Encoding, T: Transcript, { - let mut absorb_commitment = |commitment: &C| { - append_payload_label(transcript, b"commitment", commitment); - transcript.append(commitment); - }; + let mut absorb_commitment = |commitment: &C| transcript.append_labeled(b"commitment", commitment); absorb_commitment(&commitments.rd_inc); absorb_commitment(&commitments.ram_inc); for commitment in &commitments.instruction_ra { @@ -852,38 +887,21 @@ pub fn absorb_transcript_commitments( // protocol. The prover's stage 0 must commit in this same order. #[cfg(feature = "field-inline")] if let Some(field_inline) = &commitments.field_inline { - append_payload_label( - transcript, + transcript.append_labeled( b"field_rd_inc_commitment", &field_inline.field_registers.rd_inc, ); - transcript.append(&field_inline.field_registers.rd_inc); } if let Some(untrusted_advice_commitment) = untrusted_advice_commitment { - append_payload_label(transcript, b"untrusted_advice", untrusted_advice_commitment); - transcript.append(untrusted_advice_commitment); + transcript.append_labeled(b"untrusted_advice", untrusted_advice_commitment); } if let Some(trusted_advice_commitment) = trusted_advice_commitment { - append_payload_label(transcript, b"trusted_advice", trusted_advice_commitment); - transcript.append(trusted_advice_commitment); + transcript.append_labeled(b"trusted_advice", trusted_advice_commitment); } } -fn append_payload_label(transcript: &mut T, label: &'static [u8], payload: &A) -where - T: Transcript, - A: AppendToTranscript, -{ - if let Some(len) = payload.transcript_payload_len() { - transcript.append(&LabelWithCount(label, len)); - } else { - transcript.append(&Label(label)); - } -} - -/// Proof-derived configuration that participates in the Fiat-Shamir preamble. -/// Bundles the parameters [`absorb_transcript_preamble`] reads from the proof so -/// the modular prover can seed an identical transcript before it has a finished +/// The proof's structural parameters in the [`JoltStatement`]. The modular +/// prover builds it from its configuration, before it has a finished /// [`JoltProof`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ProofTranscriptConfig { @@ -892,92 +910,6 @@ pub struct ProofTranscriptConfig { pub trace_polynomial_order: TracePolynomialOrder, } -/// Verifier state captured immediately before stage 1, after input validation -/// and the preamble/commitment absorption. The modular prover drives the staged -/// verification itself, so it reuses this entry point to obtain the checked -/// inputs and a transcript seeded identically to [`verify`]. -#[derive(Debug)] -pub struct PreStage1VerifierState { - pub checked: CheckedInputs, - pub transcript: T, -} - -/// Absorbs the Jolt Fiat-Shamir preamble: the preprocessing digest, public I/O -/// metadata, and the proof-derived structural parameters carried by -/// [`ProofTranscriptConfig`]. WARNING: the byte order here is consensus-critical. -/// It must stay identical to the order [`verify`] uses (via `absorb_preamble`) -/// or prover and verifier transcripts diverge. -pub fn absorb_transcript_preamble( - checked: &CheckedInputs, - config: ProofTranscriptConfig, - transcript: &mut T, -) where - T: Transcript, -{ - let public_io = &checked.public_io; - absorb_labeled_bytes( - transcript, - b"preprocessing_digest", - &checked.preprocessing_digest, - ); - absorb_labeled_u64( - transcript, - b"max_input_size", - public_io.memory_layout.max_input_size, - ); - absorb_labeled_u64( - transcript, - b"max_output_size", - public_io.memory_layout.max_output_size, - ); - absorb_labeled_u64(transcript, b"heap_size", public_io.memory_layout.heap_size); - absorb_labeled_bytes(transcript, b"inputs", &public_io.inputs); - absorb_labeled_bytes(transcript, b"outputs", &public_io.outputs); - absorb_labeled_u64(transcript, b"panic", u64::from(public_io.panic)); - absorb_labeled_u64(transcript, b"ram_K", num::u64_from_usize(checked.ram_K)); - absorb_labeled_u64( - transcript, - b"trace_length", - num::u64_from_usize(checked.trace_length), - ); - absorb_labeled_u64(transcript, b"entry_address", checked.entry_address); - absorb_labeled_u64( - transcript, - b"ram_rw_phase1_num_rounds", - u64::from(config.rw_config.ram_rw_phase1_num_rounds), - ); - absorb_labeled_u64( - transcript, - b"ram_rw_phase2_num_rounds", - u64::from(config.rw_config.ram_rw_phase2_num_rounds), - ); - absorb_labeled_u64( - transcript, - b"registers_rw_phase1_num_rounds", - u64::from(config.rw_config.registers_rw_phase1_num_rounds), - ); - absorb_labeled_u64( - transcript, - b"registers_rw_phase2_num_rounds", - u64::from(config.rw_config.registers_rw_phase2_num_rounds), - ); - absorb_labeled_u64( - transcript, - b"log_k_chunk", - u64::from(config.one_hot_config.log_k_chunk), - ); - absorb_labeled_u64( - transcript, - b"lookups_ra_virtual_log_k_chunk", - u64::from(config.one_hot_config.lookups_ra_virtual_log_k_chunk), - ); - absorb_labeled_u64( - transcript, - b"dory_layout", - config.trace_polynomial_order.transcript_scalar(), - ); -} - /// Fail closed on a zero-based RAM remap. Stage 2's RAF-evaluation unmap is /// `8k + lowest_address`, and the lattice digit-zero reconstruction relies on /// `unmap(0) = lowest_address ≠ 0` to distinguish "no RAM access" from an @@ -1157,42 +1089,6 @@ where }) } -/// Runs the [`verify`] preamble — input validation, proof-consistency and -/// config checks, then preamble + commitment absorption — and returns the -/// pre-stage-1 state. [`verify`] delegates to this function, so both paths share -/// one absorption order and cannot diverge. -pub fn verify_until_stage1( - preprocessing: &JoltVerifierPreprocessing, - public_io: &JoltDevice, - proof: &JoltProof, - trusted_advice_commitment: Option<&PCS::Output>, -) -> Result, VerifierError> -where - PCS: CommitmentScheme, - PCS::Output: AppendToTranscript, - VC: VectorCommitment, - VC::Output: AppendToTranscript, - T: Transcript, -{ - let (checked, transcript) = - validate_and_seed_transcript(preprocessing, public_io, proof, trusted_advice_commitment)?; - - Ok(PreStage1VerifierState { - checked, - transcript, - }) -} - -fn absorb_labeled_bytes(transcript: &mut T, label: &'static [u8], bytes: &[u8]) { - transcript.append(&LabelWithCount(label, num::u64_from_usize(bytes.len()))); - transcript.append_bytes(bytes); -} - -fn absorb_labeled_u64(transcript: &mut T, label: &'static [u8], value: u64) { - transcript.append(&Label(label)); - transcript.append(&U64Word(value)); -} - #[cfg(test)] #[cfg_attr( not(feature = "field-inline"), @@ -1238,7 +1134,7 @@ mod tests { use jolt_sumcheck::{ ClearProof, ClearSumcheckProof, CommittedSumcheckProof, CompressedSumcheckProof, }; - use jolt_transcript::Transcript; + use jolt_transcript::{FieldTranscript, Transcript}; use num_traits::Zero; use crate::preprocessing::ProgramPreprocessing; @@ -1282,7 +1178,7 @@ mod tests { _eval: Self::Field, _setup: &Self::ProverSetup, _hint: Option, - _transcript: &mut impl Transcript, + _transcript: &mut impl FieldTranscript, ) -> Result { Ok(()) } @@ -1293,13 +1189,13 @@ mod tests { _eval: Self::Field, _proof: &Self::Proof, _setup: &Self::VerifierSetup, - _transcript: &mut impl Transcript, + _transcript: &mut impl FieldTranscript, ) -> Result<(), OpeningsError> { Ok(()) } } - impl jolt_transcript::AppendToTranscript for TestCommitment { + impl jolt_transcript::Encoding for TestCommitment { fn append_to_transcript(&self, _transcript: &mut T) {} } @@ -1563,6 +1459,7 @@ mod tests { lookups_ra_virtual_log_k_chunk: 0, }, trace_polynomial_order: crate::proof::TracePolynomialOrder::CycleMajor, + narg: Vec::new(), } } diff --git a/crates/jolt-verifier/tests/completeness/zk.rs b/crates/jolt-verifier/tests/completeness/zk.rs index e1066f1d1c..ddef57ce70 100644 --- a/crates/jolt-verifier/tests/completeness/zk.rs +++ b/crates/jolt-verifier/tests/completeness/zk.rs @@ -18,7 +18,7 @@ use jolt_field::Fr; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] use jolt_sumcheck::SumcheckProof; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] -use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; +use jolt_transcript::{Blake2b512, LegacyBlake2bTranscript as ProverState, ProverState}; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] use jolt_verifier::JoltProofClaims; @@ -44,7 +44,7 @@ fn zk_committed_muldiv_blindfold_shape_audit_matches_modular_protocol() { Fr, DoryScheme, Pedersen, - Blake2bTranscript, + ProverState, _, >(&case.preprocessing, &case.public_io, &case.proof, None) .expect("build modular BlindFold protocol shape"); @@ -79,7 +79,7 @@ fn zk_muldiv_blindfold_shape_audit_matches_modular_protocol() { Fr, DoryScheme, Pedersen, - Blake2bTranscript, + ProverState, _, >(&case.preprocessing, &case.public_io, &case.proof, None) .expect("build modular BlindFold protocol shape"); diff --git a/crates/jolt-verifier/tests/statistical_independence/zk.rs b/crates/jolt-verifier/tests/statistical_independence/zk.rs index 24651614a0..c0483404ed 100644 --- a/crates/jolt-verifier/tests/statistical_independence/zk.rs +++ b/crates/jolt-verifier/tests/statistical_independence/zk.rs @@ -18,9 +18,7 @@ use jolt_field::{CanonicalBytes, Fr}; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] use jolt_sumcheck::SumcheckProof; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] -use jolt_transcript::{ - AppendToTranscript, LegacyBlake2bTranscript as Blake2bTranscript, Transcript, -}; +use jolt_transcript::{prover_transcript, Blake2b512, Encoding, LegacyBlake2bTranscript as ProverState, Transcript}; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] use jolt_verifier::JoltProofClaims; @@ -353,7 +351,7 @@ fn collect_sumcheck_statistics( proof: &SumcheckProof, tracker: &mut BucketTracker, ) where - C: AppendToTranscript, + C: Encoding, { let proof = proof .as_committed() @@ -506,15 +504,15 @@ impl BucketTracker { Self::default() } - fn record_append(&mut self, name: impl Into, value: &A) { + fn record_append(&mut self, name: impl Into, value: &A) { let name = name.into(); - let mut transcript = Blake2bTranscript::::new(b"jolt-zk-stat"); + let mut transcript = prover_transcript::(b"jolt-zk-stat", b""); transcript.append_bytes(name.as_bytes()); value.append_to_transcript(&mut transcript); - self.record_projected(name, field_low_u64(transcript.challenge())); + self.record_projected(name, field_low_u64(transcript.challenge().unwrap())); } - fn record_append_positions(&mut self, prefix: &str, values: &[A]) { + fn record_append_positions(&mut self, prefix: &str, values: &[A]) { for index in selected_positions(values.len()) { self.record_append(format!("{prefix}.{index}"), &values[index]); } @@ -557,10 +555,10 @@ impl BucketTracker { } fn record_bytes(&mut self, name: String, bytes: &[u8]) { - let mut transcript = Blake2bTranscript::::new(b"jolt-zk-stat"); + let mut transcript = prover_transcript::(b"jolt-zk-stat", b""); transcript.append_bytes(name.as_bytes()); transcript.append_bytes(bytes); - self.record_projected(name, field_low_u64(transcript.challenge())); + self.record_projected(name, field_low_u64(transcript.challenge().unwrap())); } fn record_projected(&mut self, name: String, value: u64) { diff --git a/crates/jolt-verifier/tests/support/fs_transcript.rs b/crates/jolt-verifier/tests/support/fs_transcript.rs index 9249e8e8e8..b2b2988278 100644 --- a/crates/jolt-verifier/tests/support/fs_transcript.rs +++ b/crates/jolt-verifier/tests/support/fs_transcript.rs @@ -221,12 +221,12 @@ where } fn challenge(&mut self) -> Self::Challenge { - draw(ChallengeKind::Challenge, || vec![self.inner.challenge()])[0] + draw(ChallengeKind::Challenge, || vec![self.inner.challenge().unwrap()])[0] } fn challenge_scalar(&mut self) -> Self::Challenge { draw(ChallengeKind::Scalar, || { - vec![self.inner.challenge_scalar()] + vec![self.inner.challenge().unwrap()] })[0] } @@ -237,7 +237,7 @@ where (0..len) .map(|index| { draw(ChallengeKind::VectorElement { len, index }, || { - vec![self.inner.challenge()] + vec![self.inner.challenge().unwrap()] })[0] }) .collect() @@ -245,7 +245,7 @@ where fn challenge_scalar_powers(&mut self, len: usize) -> Vec { let base = draw(ChallengeKind::PowersBase { len }, || { - vec![self.inner.challenge_scalar()] + vec![self.inner.challenge().unwrap()] })[0]; let mut powers = vec![Self::Challenge::from_u64(1); len]; for index in 1..len { diff --git a/crates/jolt-verifier/tests/support/verifier_fixtures.rs b/crates/jolt-verifier/tests/support/verifier_fixtures.rs index a94ff73b9b..e134bf4c4a 100644 --- a/crates/jolt-verifier/tests/support/verifier_fixtures.rs +++ b/crates/jolt-verifier/tests/support/verifier_fixtures.rs @@ -23,7 +23,7 @@ use jolt_host::Program; use jolt_program::execution::{JoltProgram, TraceOutput}; use jolt_prover::dory::DoryProverPreprocessing; use jolt_prover::{JoltBackend, JoltSharedPreprocessing, ProverConfig}; -use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; +use jolt_transcript::{Blake2b512, LegacyBlake2bTranscript as ProverState, ProverState}; use jolt_verifier::{verify, JoltProof, JoltVerifierPreprocessing, VerifierError}; use super::guest_fixtures::{fixture_witness, prepare_guest, PreparedGuest}; @@ -129,7 +129,7 @@ pub struct VerifierFixtureCase { #[cfg(not(feature = "zk"))] impl VerifierFixtureCase { pub fn verify(&self) -> Result<(), VerifierError> { - verify::, Blake2bTranscript>( + verify::, ProverState>( &self.preprocessing, &self.public_io, &self.proof, @@ -150,7 +150,7 @@ pub struct ZkVerifierFixtureCase { #[cfg(feature = "zk")] impl ZkVerifierFixtureCase { pub fn verify(&self) -> Result<(), VerifierError> { - verify::, Blake2bTranscript>( + verify::, ProverState>( &self.preprocessing, &self.public_io, &self.proof, @@ -530,7 +530,7 @@ fn assert_verifier_accepts( proof: VerifierFixtureProof, public_io: JoltDevice, ) { - let result = verify::, Blake2bTranscript>( + let result = verify::, ProverState>( &fixture.preprocessing, &public_io, &proof, @@ -725,7 +725,7 @@ fn prove_prepared( jolt_prover::dory::commit_trusted_advice(&preprocessing, trusted_advice) .expect("trusted advice commitment") }); - let proof = jolt_prover::dory::prove::, Blake2bTranscript>( + let proof = jolt_prover::dory::prove::, ProverState>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -751,7 +751,7 @@ mod field_inline { use jolt_field::Fr; use jolt_program::execution::{ExecutionBackend, JoltProgram, TraceInputs, TraceOutput}; use jolt_prover::{JoltBackend, ProverConfig}; - use jolt_transcript::LegacyBlake2bTranscript as Blake2bTranscript; + use jolt_transcript::{Blake2b512, LegacyBlake2bTranscript as ProverState, ProverState}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; use tracer::execution_backend::TracerBackend; @@ -829,7 +829,7 @@ mod field_inline { let witness = Arc::new(witness); let backend = JoltBackend::::reference(); - let proof = jolt_prover::prove::, Blake2bTranscript>( + let proof = jolt_prover::prove::, ProverState>( backend.with_witness(&witness), &prover_preprocessing, &config, diff --git a/crates/jolt-verifier/tests/support/zk_audit.rs b/crates/jolt-verifier/tests/support/zk_audit.rs index fbc2028fc8..ee18e06c77 100644 --- a/crates/jolt-verifier/tests/support/zk_audit.rs +++ b/crates/jolt-verifier/tests/support/zk_audit.rs @@ -4,7 +4,7 @@ use jolt_claims::protocols::jolt::JoltRelationId; use jolt_crypto::{HomomorphicCommitment, VectorCommitment}; use jolt_field::JoltField; use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, ZkOpeningScheme}; -use jolt_transcript::{AppendToTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, Transcript}; use jolt_verifier::{ preprocessing::JoltVerifierPreprocessing, @@ -53,14 +53,14 @@ pub fn audit_zk_blindfold_protocol_shape( trusted_advice_commitment: Option<&PCS::Output>, ) -> Result where - F: JoltField + AppendToTranscript, + F: JoltField + Encoding, PCS: CommitmentScheme + AdditivelyHomomorphic + ZkOpeningScheme, - PCS::Output: AppendToTranscript + HomomorphicCommitment, + PCS::Output: Encoding + HomomorphicCommitment, VC: VectorCommitment, - VC::Output: Copy + HomomorphicCommitment + AppendToTranscript, - T: Transcript, + VC::Output: Copy + HomomorphicCommitment + Encoding, + T: FieldTranscript, { let (checked, mut transcript) = jolt_verifier::validate_and_seed_transcript::( From a5b0ada4d4a6bcc6f4ba517a5ff01c7e41110673 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Tue, 20 Oct 2026 23:32:38 -0400 Subject: [PATCH 05/16] test(jolt): port verifier and prover tests to the spongefish transcript The fs-audit transcript is the production verifier state with a recording script that also replays proof-of-work check bytes, so frozen replays over changed inputs model a verifier whose every squeeze is fixed. --- crates/jolt-prover/src/dory/stages/stage0.rs | 19 +- crates/jolt-prover/src/stages/drivers.rs | 2 +- .../src/stages/field_inline_fixtures.rs | 4 +- crates/jolt-prover/src/stages/stage1.rs | 2 +- crates/jolt-prover/src/stages/stage2.rs | 4 +- crates/jolt-prover/src/stages/stage4.rs | 2 +- crates/jolt-prover/src/stages/stage5.rs | 2 +- crates/jolt-prover/src/stages/stage6a.rs | 2 +- crates/jolt-prover/src/stages/stage6b.rs | 2 +- crates/jolt-prover/tests/akita_e2e.rs | 23 +- .../tests/akita_field_inline_e2e.rs | 5 +- crates/jolt-prover/tests/backend_contract.rs | 6 +- crates/jolt-prover/tests/e2e_matrix.rs | 11 +- .../tests/support/akita_backend.rs | 7 +- .../tests/support/field_inline/akita.rs | 7 +- .../tests/support/field_inline/dory.rs | 4 +- crates/jolt-prover/tests/zk_e2e.rs | 6 +- crates/jolt-transcript/src/scripted.rs | 26 +- .../fuzz_targets/proof_tamper_must_reject.rs | 6 +- .../zk_proof_tamper_must_reject.rs | 6 +- crates/jolt-verifier/src/stages/relations.rs | 9 +- .../src/stages/stage2/outputs.rs | 2 +- .../src/stages/stage4/outputs.rs | 2 +- .../src/stages/stage4/ram_val_check.rs | 2 +- .../src/stages/stage5/outputs.rs | 2 +- .../src/stages/stage6a/verify.rs | 2 +- .../src/stages/stage6b/verify.rs | 1 + crates/jolt-verifier/src/verifier.rs | 26 +- crates/jolt-verifier/tests/completeness/zk.rs | 6 +- crates/jolt-verifier/tests/fs_attacks.rs | 48 ++- .../tests/generate_fuzz_fixture.rs | 4 +- .../tests/soundness/tampering/akita.rs | 13 +- .../tests/statistical_independence/zk.rs | 8 +- .../tests/support/akita_fixtures.rs | 12 +- .../tests/support/fs_mutations.rs | 39 +- .../tests/support/fs_transcript.rs | 338 ++++++------------ .../tests/support/tamper_manifest.rs | 3 + .../tests/support/verifier_fixtures.rs | 14 +- .../jolt-verifier/tests/support/zk_audit.rs | 22 +- 39 files changed, 303 insertions(+), 396 deletions(-) diff --git a/crates/jolt-prover/src/dory/stages/stage0.rs b/crates/jolt-prover/src/dory/stages/stage0.rs index a53e95918e..256fa30d97 100644 --- a/crates/jolt-prover/src/dory/stages/stage0.rs +++ b/crates/jolt-prover/src/dory/stages/stage0.rs @@ -409,7 +409,7 @@ mod field_inline_tests { use jolt_kernels::finish_streamed; use jolt_kernels::KernelError; use jolt_openings::{CommitmentScheme, StreamingCommitment}; - use jolt_transcript::{prover_transcript, Keccak, LegacyBlake2bTranscript}; + use jolt_transcript::{prover_transcript, FieldTranscript, Keccak, ProverState}; fn grid() -> CommitmentGrid { CommitmentGrid { @@ -485,27 +485,26 @@ mod field_inline_tests { direct_dense_commitment(&column, &setup) ); - let mut prover_state = Legacyprover_transcript::(b"Jolt", b""); + let mut prover_state = prover_transcript::(b"Jolt", b""); absorb_transcript_commitments(&commitments, None, None, &mut prover_state); - let mut verifier_transcript = Legacyprover_transcript::(b"Jolt", b""); + let mut verifier_transcript = prover_transcript::(b"Jolt", b""); jolt_verifier::absorb_transcript_commitments( &commitments, None, None, &mut verifier_transcript, ); - assert_eq!( - prover_state.challenge()?, - verifier_transcript.challenge()? - ); + let first = |transcript: &mut ProverState| -> Fr { transcript.challenge().unwrap() }; + let expected = first(&mut prover_state); + assert_eq!(expected, first(&mut verifier_transcript)); let mut stripped = commitments.clone(); stripped.field_inline = None; - let mut stripped_transcript = Legacyprover_transcript::(b"Jolt", b""); + let mut stripped_transcript = prover_transcript::(b"Jolt", b""); absorb_transcript_commitments(&stripped, None, None, &mut stripped_transcript); assert_ne!( - prover_state.challenge()?, - stripped_transcript.challenge()?, + expected, + first(&mut stripped_transcript), "the field-inline payload must be Fiat-Shamir-bound" ); } diff --git a/crates/jolt-prover/src/stages/drivers.rs b/crates/jolt-prover/src/stages/drivers.rs index 9c4c1d1de6..cbf1808efd 100644 --- a/crates/jolt-prover/src/stages/drivers.rs +++ b/crates/jolt-prover/src/stages/drivers.rs @@ -196,7 +196,7 @@ mod twin_tests { ClearSumcheckRecorder, CompressedSumcheckProof, ProveRounds, SequentialRounds, SumcheckError, SumcheckProof, }; - use jolt_transcript::{prover_transcript, Keccak, Transcript}; + use jolt_transcript::{prover_transcript, Keccak}; use jolt_verifier::stages::relations::{ConcreteSumcheck, SumcheckBatch, SumcheckOutputClaims}; use jolt_verifier::VerifierError; use jolt_witness::{ diff --git a/crates/jolt-prover/src/stages/field_inline_fixtures.rs b/crates/jolt-prover/src/stages/field_inline_fixtures.rs index 7eff9ea070..2f8df790dc 100644 --- a/crates/jolt-prover/src/stages/field_inline_fixtures.rs +++ b/crates/jolt-prover/src/stages/field_inline_fixtures.rs @@ -558,7 +558,7 @@ pub(crate) mod twins { transcript: &mut ProverState, stage1: &Stage1ProverOutput, ) { - let tau = draw_spartan_outer_tau(transcript, LOG_T); + let tau = draw_spartan_outer_tau(transcript, LOG_T).unwrap(); let uniskip_challenge = uniskip::verify_clear( &stage1.uniskip_proof, &UniskipParams::spartan_outer(), @@ -612,7 +612,7 @@ pub(crate) mod twins { let raf_dimensions = RamRafEvaluationDimensions::try_from(read_write_dimensions).unwrap(); let tau_low = product_tau_low(&stage1.clear_output.remainder_point(), log_t).unwrap(); - let tau_high: Fr = draw_spartan_product_tau_high(transcript); + let tau_high: Fr = draw_spartan_product_tau_high(transcript).unwrap(); let uniskip_relation = ProductUniskip::new(product_dimensions, tau_high); let uniskip_inputs = product_uniskip_input_values_from_stage1(&stage1.clear_output); let uniskip_input_claim = uniskip_relation diff --git a/crates/jolt-prover/src/stages/stage1.rs b/crates/jolt-prover/src/stages/stage1.rs index 04a705bd43..b17ebed54a 100644 --- a/crates/jolt-prover/src/stages/stage1.rs +++ b/crates/jolt-prover/src/stages/stage1.rs @@ -296,7 +296,7 @@ mod field_inline_zk { }, }; let mut transcript = prover_transcript::(b"stage1-field-inline-zk", b""); - let tau = draw_spartan_outer_tau(&mut transcript, LOG_T); + let tau = draw_spartan_outer_tau(&mut transcript, LOG_T).unwrap(); let uniskip_step = uniskip::verify_zk( &checked, &out.uniskip_proof, diff --git a/crates/jolt-prover/src/stages/stage2.rs b/crates/jolt-prover/src/stages/stage2.rs index 99f249f153..35f8e38ee8 100644 --- a/crates/jolt-prover/src/stages/stage2.rs +++ b/crates/jolt-prover/src/stages/stage2.rs @@ -364,7 +364,7 @@ mod field_inline_zk { }; let mut transcript = prover_transcript::(b"stage2-field-inline-zk", b""); { - let tau = uniskip::draw_spartan_outer_tau(&mut transcript, LOG_T); + let tau = uniskip::draw_spartan_outer_tau(&mut transcript, LOG_T).unwrap(); let uniskip_step = uniskip::verify_zk( &checked, &stage1.uniskip_proof, @@ -390,7 +390,7 @@ mod field_inline_zk { let product_dimensions = SpartanProductDimensions::new(log_t); let raf_dimensions = RamRafEvaluationDimensions::try_from(read_write_dimensions).unwrap(); let tau_low = product_tau_low(&stage1.clear_output.remainder_point(), log_t).unwrap(); - let tau_high: Fr = draw_spartan_product_tau_high(&mut transcript); + let tau_high: Fr = draw_spartan_product_tau_high(&mut transcript).unwrap(); let product_uniskip_step = uniskip::verify_zk( &checked, &out.uniskip_proof, diff --git a/crates/jolt-prover/src/stages/stage4.rs b/crates/jolt-prover/src/stages/stage4.rs index 8bbecd7a0c..34868b3f51 100644 --- a/crates/jolt-prover/src/stages/stage4.rs +++ b/crates/jolt-prover/src/stages/stage4.rs @@ -258,7 +258,7 @@ mod field_inline_round_trip { use jolt_field::{Fr, Ring}; use jolt_kernels::ProverInputs; use jolt_poly::EqPolynomial; - use jolt_transcript::{prover_transcript, Keccak, ProverState, Transcript}; + use jolt_transcript::{prover_transcript, Keccak, ProverState}; use jolt_verifier::stages::relations::ConcreteSumcheck as _; use jolt_witness::JoltWitnessOracle as _; diff --git a/crates/jolt-prover/src/stages/stage5.rs b/crates/jolt-prover/src/stages/stage5.rs index 6200c4a60e..8d2b122ade 100644 --- a/crates/jolt-prover/src/stages/stage5.rs +++ b/crates/jolt-prover/src/stages/stage5.rs @@ -156,7 +156,7 @@ mod field_inline_round_trip { use jolt_field::{Fr, Ring}; use jolt_kernels::ProverInputs; use jolt_poly::EqPolynomial; - use jolt_transcript::{prover_transcript, Keccak, ProverState, Transcript}; + use jolt_transcript::{prover_transcript, Keccak, ProverState}; use jolt_verifier::stages::relations::ConcreteSumcheck as _; use jolt_witness::JoltWitnessOracle as _; diff --git a/crates/jolt-prover/src/stages/stage6a.rs b/crates/jolt-prover/src/stages/stage6a.rs index 27d9318fbe..829260f28a 100644 --- a/crates/jolt-prover/src/stages/stage6a.rs +++ b/crates/jolt-prover/src/stages/stage6a.rs @@ -198,7 +198,7 @@ mod field_inline_round_trip { use jolt_crypto::{Bn254G1, Pedersen}; use jolt_dory::DoryScheme; use jolt_field::{Fr, Ring}; - use jolt_transcript::{prover_transcript, Keccak, ProverState, Transcript}; + use jolt_transcript::{prover_transcript, Keccak, ProverState}; use super::*; use crate::recorder::ProofMode; diff --git a/crates/jolt-prover/src/stages/stage6b.rs b/crates/jolt-prover/src/stages/stage6b.rs index f7a89c4295..f9810056d6 100644 --- a/crates/jolt-prover/src/stages/stage6b.rs +++ b/crates/jolt-prover/src/stages/stage6b.rs @@ -362,7 +362,7 @@ mod field_inline_round_trip { ) .unwrap(); let carried = &stage6a.clear_output.challenges; - let draws = Stage6bDraws::draw(&mut transcript, &formula_dimensions, false); + let draws = Stage6bDraws::draw(&mut transcript, &formula_dimensions, false).unwrap(); let program = preprocessing.program().unwrap(); let entry_bytecode_index = preprocessing .verifier diff --git a/crates/jolt-prover/tests/akita_e2e.rs b/crates/jolt-prover/tests/akita_e2e.rs index 673375a03d..b8c53e4197 100644 --- a/crates/jolt-prover/tests/akita_e2e.rs +++ b/crates/jolt-prover/tests/akita_e2e.rs @@ -39,8 +39,9 @@ mod akita_tests { use jolt_field::Ring; use jolt_prover::akita; use jolt_prover::akita::preprocessing::{ - self, AkitaProverPreprocessing, AkitaTranscript, AkitaVc, + self, AkitaProverPreprocessing, AkitaVc, }; + use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_prover::akita::witness::commit_advice; use jolt_prover::JoltBackend; use jolt_prover::{PreprocessingError, ProverConfig, ProverError}; @@ -127,7 +128,7 @@ mod akita_tests { witness_config(&config, untrusted_advice, has_trusted_advice), JoltVmWitnessInputs::new(&run.program, &program_preprocessing, run.trace), ); - let proof = akita::prove::( + let proof = akita::prove::>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -144,7 +145,7 @@ mod akita_tests { } fn verify(proved: &ProvedGuest) -> Result<(), VerifierError> { - jolt_verifier::verify::( + jolt_verifier::verify::>( &proved.preprocessing.verifier, &proved.public_io, &proved.proof, @@ -361,7 +362,7 @@ mod akita_tests { witness_config(&config, false, false), JoltVmWitnessInputs::new(&run.program, &program_preprocessing, run.trace), ); - let result = akita::prove::( + let result = akita::prove::>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -404,7 +405,7 @@ mod akita_tests { continue; } config.akita_chunk_profile = requested; - let result = akita::prove::( + let result = akita::prove::>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -499,7 +500,7 @@ mod akita_tests { witness_config(&config, true, true), JoltVmWitnessInputs::new(&run.program, &program, run.trace), ); - let proof = akita::prove::( + let proof = akita::prove::>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -507,7 +508,7 @@ mod akita_tests { &public_io, ) .expect("reuse the original advice commitment and opening hint"); - jolt_verifier::verify::( + jolt_verifier::verify::>( &preprocessing.verifier, &public_io, &proof, @@ -579,7 +580,7 @@ mod akita_tests { witness_config(&config, false, false), JoltVmWitnessInputs::new(&run.program, &program_preprocessing, run.trace), ); - let proof = akita::prove::( + let proof = akita::prove::>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -601,7 +602,7 @@ mod akita_tests { .expect("transport proof"); assert_eq!(consumed, encoded.len()); let verify = |proof: &Proof| { - jolt_verifier::verify::( + jolt_verifier::verify::>( &verifier, &public_io, proof, None, ) }; @@ -650,7 +651,7 @@ mod akita_tests { witness_config(&config, true, true), JoltVmWitnessInputs::new(&run.program, &program_preprocessing, run.trace), ); - let proof = akita::prove::( + let proof = akita::prove::>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -660,7 +661,7 @@ mod akita_tests { .expect("committed advice Akita proof"); assert!(proof.untrusted_advice_commitment.is_some()); - jolt_verifier::verify::( + jolt_verifier::verify::>( &preprocessing.verifier, &public_io, &proof, diff --git a/crates/jolt-prover/tests/akita_field_inline_e2e.rs b/crates/jolt-prover/tests/akita_field_inline_e2e.rs index a99cf295b1..46c1fd85e4 100644 --- a/crates/jolt-prover/tests/akita_field_inline_e2e.rs +++ b/crates/jolt-prover/tests/akita_field_inline_e2e.rs @@ -259,8 +259,7 @@ mod clear { fn akita_field_inline_duplicate_inc_group_is_rejected() { use jolt_claims::protocols::field_inline::lattice::field_inc_group_role; use jolt_openings::{GroupOpeningClaim, TaggedGroupOpeningClaim}; - use jolt_prover::akita::preprocessing::AkitaTranscript; - use jolt_transcript::Transcript; + use jolt_transcript::{prover_transcript, Keccak}; let (output, ()) = akita::prove(&field_ops(), JoltBackend::optimized(), |_, _, _| ()); let commitment = output @@ -281,7 +280,7 @@ mod clear { ], vec![AkitaField::from_u64(0)], ); - let mut transcript = AkitaTranscript::new(b"spurious-field_inline-group"); + let mut transcript = prover_transcript::(b"spurious-field_inline-group", b""); let error = ::verify_batch( &output.verifier_preprocessing.pcs_setup, &[field_claim.clone(), field_claim], diff --git a/crates/jolt-prover/tests/backend_contract.rs b/crates/jolt-prover/tests/backend_contract.rs index d8a24598e2..fd80d638b1 100644 --- a/crates/jolt-prover/tests/backend_contract.rs +++ b/crates/jolt-prover/tests/backend_contract.rs @@ -34,7 +34,7 @@ mod tests { dory, JoltBackend, JoltProverPreprocessing, JoltSharedPreprocessing, ProverConfig, ProverError, }; - use jolt_transcript::LegacyBlake2bTranscript; + use jolt_transcript::{Keccak, ProverState}; use jolt_witness::{ ChunkVisitor, JoltVmWitnessConfig, JoltVmWitnessInputs, JoltWitnessOracle, JoltWitnessPlane, ProgramSource, RowSource, Shape, TraceBackend, WitnessError, @@ -188,7 +188,7 @@ mod tests { Fr, DoryScheme, Pedersen, - LegacyBlake2bTranscript, + ProverState, >(&mut context, &preprocessing, &config, None, &public_io); assert!( matches!(result, Err(ProverError::Witness(WitnessError::InvalidWitnessData { label: "stage-0 validation", reason })) @@ -220,7 +220,7 @@ mod tests { trace_length: config.trace_length, }); let context = KernelContext::new(®istry, &witness, session); - let result = dory::prove::, LegacyBlake2bTranscript>( + let result = dory::prove::, ProverState>( context, &preprocessing, &proof_config, diff --git a/crates/jolt-prover/tests/e2e_matrix.rs b/crates/jolt-prover/tests/e2e_matrix.rs index 36518c160a..fc321c9a03 100644 --- a/crates/jolt-prover/tests/e2e_matrix.rs +++ b/crates/jolt-prover/tests/e2e_matrix.rs @@ -251,7 +251,7 @@ mod matrix { use jolt_dory::DoryScheme; use jolt_field::Fr; use jolt_prover::{dory, JoltBackend, JoltSharedPreprocessing, ProverConfig}; - use jolt_transcript::{Keccak, LegacyBlake2bTranscript as ProverState, ProverState}; + use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; use crate::support::{self, GuestCase}; @@ -308,7 +308,7 @@ mod matrix { &public_io, ) .expect("Dory proof"); - jolt_verifier::verify::, ProverState>( + jolt_verifier::verify::, VerifierState<'_, Keccak>>( &preprocessing.verifier, &public_io, &proof, @@ -322,7 +322,8 @@ mod matrix { mod mode { use jolt_akita::{AkitaChunkProfile, AkitaField, AkitaScheduleArtifacts, AkitaScheme}; use jolt_prover::akita; - use jolt_prover::akita::preprocessing::{self, AkitaTranscript, AkitaVc}; + use jolt_prover::akita::preprocessing::{self, AkitaVc}; + use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_prover::JoltBackend; use jolt_prover::ProverConfig; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; @@ -371,7 +372,7 @@ mod matrix { .include_trusted_advice(trusted_advice), JoltVmWitnessInputs::new(&prepared.program, &program_preprocessing, prepared.trace), ); - let proof = akita::prove::( + let proof = akita::prove::>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -379,7 +380,7 @@ mod matrix { &public_io, ) .expect("Akita proof"); - jolt_verifier::verify::( + jolt_verifier::verify::>( &preprocessing.verifier, &public_io, &proof, diff --git a/crates/jolt-prover/tests/support/akita_backend.rs b/crates/jolt-prover/tests/support/akita_backend.rs index 32a697ae95..f578f5ccbc 100644 --- a/crates/jolt-prover/tests/support/akita_backend.rs +++ b/crates/jolt-prover/tests/support/akita_backend.rs @@ -23,7 +23,8 @@ use jolt_prover::ProverError; use common::jolt_device::JoltDevice; use jolt_akita::{AkitaChunkProfile, AkitaField, AkitaScheduleArtifacts, AkitaScheme}; use jolt_prover::akita; -use jolt_prover::akita::preprocessing::{AkitaProverPreprocessing, AkitaTranscript, AkitaVc}; +use jolt_prover::akita::preprocessing::{AkitaProverPreprocessing, AkitaVc}; +use jolt_transcript::{Keccak, ProverState}; use jolt_prover::ProverConfig; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; @@ -130,7 +131,7 @@ fn registry_reuses_short_lived_witnesses_and_releases_each_session() { trace_length: fixture.config.trace_length, }); let backend = KernelContext::new(®istry, &witness, session); - let result = akita::prove::( + let result = akita::prove::>( backend, &fixture.preprocessing, &config, @@ -176,7 +177,7 @@ fn successful_proof_releases_the_bound_witness_and_session() { trace_length: fixture.config.trace_length, }); let context = KernelContext::new(®istry, witness, session); - let _proof = akita::prove::( + let _proof = akita::prove::>( context, &fixture.preprocessing, &fixture.config, diff --git a/crates/jolt-prover/tests/support/field_inline/akita.rs b/crates/jolt-prover/tests/support/field_inline/akita.rs index 032c119968..16e2b26876 100644 --- a/crates/jolt-prover/tests/support/field_inline/akita.rs +++ b/crates/jolt-prover/tests/support/field_inline/akita.rs @@ -4,7 +4,8 @@ use common::jolt_device::JoltDevice; use jolt_akita::AkitaProverSetup; use jolt_akita::{AkitaChunkProfile, AkitaField, AkitaScheduleArtifacts, AkitaScheme}; -use jolt_prover::akita::preprocessing::{AkitaTranscript, AkitaVc}; +use jolt_prover::akita::preprocessing::{AkitaVc}; +use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_prover::JoltBackend; use jolt_prover::{akita, ProverConfig}; use jolt_verifier::proof::JoltProof; @@ -78,7 +79,7 @@ pub fn prove( &prover_preprocessing.pcs_setup, ); - let proof = akita::prove::( + let proof = akita::prove::>( backend.with_witness(&witness), &prover_preprocessing, &config, @@ -101,7 +102,7 @@ pub fn verify_full( public_io: &JoltDevice, proof: &Proof, ) -> Result<(), VerifierError> { - jolt_verifier::verify::( + jolt_verifier::verify::>( preprocessing, public_io, proof, diff --git a/crates/jolt-prover/tests/support/field_inline/dory.rs b/crates/jolt-prover/tests/support/field_inline/dory.rs index 9e2bfe5241..fdca552d72 100644 --- a/crates/jolt-prover/tests/support/field_inline/dory.rs +++ b/crates/jolt-prover/tests/support/field_inline/dory.rs @@ -3,7 +3,7 @@ use jolt_crypto::{Bn254G1, Pedersen}; use jolt_dory::DoryScheme; use jolt_field::Fr; use jolt_prover::{dory, JoltBackend, JoltSharedPreprocessing, ProverConfig, ProverError}; -use jolt_transcript::{Keccak, LegacyBlake2bTranscript as ProverState, ProverState}; +use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_verifier::proof::JoltProof; use jolt_verifier::{JoltVerifierPreprocessing, VerifierError}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; @@ -71,7 +71,7 @@ pub fn verify_full( public_io: &JoltDevice, proof: &Proof, ) -> Result<(), VerifierError> { - jolt_verifier::verify::, ProverState>( + jolt_verifier::verify::, VerifierState<'_, Keccak>>( preprocessing, public_io, proof, diff --git a/crates/jolt-prover/tests/zk_e2e.rs b/crates/jolt-prover/tests/zk_e2e.rs index 24603623d2..6c726dbf6f 100644 --- a/crates/jolt-prover/tests/zk_e2e.rs +++ b/crates/jolt-prover/tests/zk_e2e.rs @@ -30,7 +30,7 @@ mod zk { use jolt_prover::dory::DoryProverPreprocessing; use jolt_prover::{JoltBackend, JoltSharedPreprocessing, ProverConfig}; use jolt_riscv::JoltTraceRow; - use jolt_transcript::{Keccak, LegacyBlake2bTranscript as ProverState, ProverState}; + use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_verifier::proof::{JoltProof, JoltProofClaims}; use jolt_verifier::VerifierError; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; @@ -118,7 +118,7 @@ mod zk { } fn verify(proved: &ProvedGuest) -> Result<(), VerifierError> { - jolt_verifier::verify::, ProverState>( + jolt_verifier::verify::, VerifierState<'_, Keccak>>( &proved.preprocessing.verifier, &proved.public_io, &proved.proof, @@ -175,7 +175,7 @@ mod zk { &public_io, ) .expect("committed ZK prove"); - jolt_verifier::verify::, ProverState>( + jolt_verifier::verify::, VerifierState<'_, Keccak>>( &preprocessing.verifier, &public_io, &proof, diff --git a/crates/jolt-transcript/src/scripted.rs b/crates/jolt-transcript/src/scripted.rs index be41ff64f1..6e4eabc92b 100644 --- a/crates/jolt-transcript/src/scripted.rs +++ b/crates/jolt-transcript/src/scripted.rs @@ -35,6 +35,10 @@ pub trait Script { /// Sees the challenges the wrapped transcript drew for one draw, and may /// replace them. fn draw(&mut self, challenges: &mut [F]); + + /// Sees the bytes of a squeeze outside a challenge draw, such as a + /// proof-of-work check, and may replace them. By default it keeps them. + fn check_bytes(&mut self, _bytes: &mut [u8]) {} } /// A queue of challenges that replace the drawn ones, in order, while it @@ -62,6 +66,9 @@ impl Script for VecDeque { /// [`grind_bits_for_loss`](crate::grind_bits_for_loss) 0, so proofs carry no /// nonces. /// +/// The script also sees the bytes of every other squeeze, such as a +/// proof-of-work check, and may replace them. +/// /// Tests use it to choose challenges, to compare absorb orders, to record and /// replay challenge tapes, and to reach checks that a hand-built proof would /// otherwise fail at its first proof-of-work step. It can overstate `T`'s @@ -118,7 +125,7 @@ impl ScriptedTranscript { } } -impl Sealed for ScriptedTranscript { +impl> Sealed for ScriptedTranscript { fn absorb(&mut self, message: &M) { self.events .push(TranscriptEvent::Absorb(message.encode().as_ref().to_vec())); @@ -127,7 +134,12 @@ impl Sealed for ScriptedTranscript { fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[u8]) -> U) -> U { self.events.push(TranscriptEvent::Squeeze(len)); - self.inner.squeeze(len, decode) + let script = &mut self.script; + self.inner.squeeze(len, |bytes| { + let mut bytes = bytes.to_vec(); + script.check_bytes(&mut bytes); + decode(&bytes) + }) } fn require(&mut self, ok: bool) -> Result<(), VerificationError> { @@ -156,7 +168,7 @@ where } } -impl ProverTranscript for ScriptedTranscript { +impl> ProverTranscript for ScriptedTranscript { type Sponge = T::Sponge; fn prover_message(&mut self, msg: &M) { @@ -185,7 +197,7 @@ impl ProverTranscript for ScriptedTranscript } } -impl VerifierTranscript for ScriptedTranscript { +impl> VerifierTranscript for ScriptedTranscript { fn prover_message(&mut self) -> Result { let message = self.inner.prover_message::()?; self.events @@ -202,13 +214,15 @@ impl VerifierTranscript for ScriptedTranscript, F, S: Default> VerifierInit<'a> for ScriptedTranscript { +impl<'a, T: VerifierInit<'a>, F, S: Script + Default> VerifierInit<'a> + for ScriptedTranscript +{ fn init(session: &[u8], statement: &(impl Encoding + ?Sized), narg: &'a [u8]) -> Self { Self::scripted_by(T::init(session, statement, narg), S::default()) } } -impl ProverInit for ScriptedTranscript { +impl + Default> ProverInit for ScriptedTranscript { fn init(session: &[u8], statement: &(impl Encoding + ?Sized)) -> Self { Self::scripted_by(T::init(session, statement), S::default()) } diff --git a/crates/jolt-verifier/fuzz/fuzz_targets/proof_tamper_must_reject.rs b/crates/jolt-verifier/fuzz/fuzz_targets/proof_tamper_must_reject.rs index b409e2da5e..72a6d0c92d 100644 --- a/crates/jolt-verifier/fuzz/fuzz_targets/proof_tamper_must_reject.rs +++ b/crates/jolt-verifier/fuzz/fuzz_targets/proof_tamper_must_reject.rs @@ -14,7 +14,7 @@ use jolt_dory::{DoryCommitment, DoryScheme}; use jolt_field::{CanonicalEncoding, Fr, Ring}; use jolt_poly::{CompressedPoly, UnivariatePoly}; use jolt_sumcheck::{ClearProof, SumcheckProof}; -use jolt_transcript::LegacyBlake2bTranscript; +use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_verifier::{verify, JoltProof, JoltProofClaims, JoltVerifierPreprocessing, ZkConfig}; use libfuzzer_sys::fuzz_target; @@ -40,7 +40,7 @@ fn bundles() -> &'static [Bundle] { .expect("fixture decodes"); assert_eq!(consumed, bytes.len(), "fixture has trailing bytes"); let (preprocessing, public_io, proof, advice) = &bundle; - verify::, LegacyBlake2bTranscript>( + verify::, VerifierState<'_, Keccak>>( preprocessing, public_io, proof, @@ -257,7 +257,7 @@ fuzz_target!(|data: &[u8]| { return; } - let result = verify::, LegacyBlake2bTranscript>( + let result = verify::, VerifierState<'_, Keccak>>( &preprocessing, &public_io, &proof, diff --git a/crates/jolt-verifier/fuzz/fuzz_targets/zk_proof_tamper_must_reject.rs b/crates/jolt-verifier/fuzz/fuzz_targets/zk_proof_tamper_must_reject.rs index 77cfe63428..89206332d3 100644 --- a/crates/jolt-verifier/fuzz/fuzz_targets/zk_proof_tamper_must_reject.rs +++ b/crates/jolt-verifier/fuzz/fuzz_targets/zk_proof_tamper_must_reject.rs @@ -15,7 +15,7 @@ use jolt_dory::{DoryCommitment, DoryScheme}; use jolt_field::{CanonicalEncoding, Fr, Ring}; use jolt_poly::CompressedPoly; use jolt_sumcheck::{CompressedSumcheckProof, SumcheckProof}; -use jolt_transcript::LegacyBlake2bTranscript; +use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_verifier::{verify, JoltProof, JoltProofClaims, JoltVerifierPreprocessing, ZkConfig}; use libfuzzer_sys::fuzz_target; @@ -41,7 +41,7 @@ fn bundles() -> &'static [Bundle] { .expect("fixture decodes"); assert_eq!(consumed, bytes.len(), "fixture has trailing bytes"); let (preprocessing, public_io, proof, advice) = &bundle; - verify::, LegacyBlake2bTranscript>( + verify::, VerifierState<'_, Keccak>>( preprocessing, public_io, proof, @@ -261,7 +261,7 @@ fuzz_target!(|data: &[u8]| { return; } - let result = verify::, LegacyBlake2bTranscript>( + let result = verify::, VerifierState<'_, Keccak>>( &preprocessing, &public_io, &proof, diff --git a/crates/jolt-verifier/src/stages/relations.rs b/crates/jolt-verifier/src/stages/relations.rs index 302f1b0df1..ce6dba2a65 100644 --- a/crates/jolt-verifier/src/stages/relations.rs +++ b/crates/jolt-verifier/src/stages/relations.rs @@ -612,6 +612,7 @@ mod tests { } use super::draw_recording::recorder; + use jolt_transcript::Transcript; /// The absorb stream produced by appending `opening_values()` one-by-one is /// the reference Fiat-Shamir order; `append_openings` must reproduce it. @@ -1234,7 +1235,7 @@ mod sumcheck_batch_derive_tests { #[cfg(test)] #[expect(clippy::unwrap_used)] mod begin_batch_tests { - use super::draw_recording::{record, DrawEvent}; + use super::draw_recording::{record, TranscriptEvent}; use super::ConcreteSumcheck as _; use crate::stages::stage5::{InstructionReadRaf, RegistersValEvaluation}; use jolt_claims::protocols::jolt::geometry::dimensions::TraceDimensions; @@ -1243,7 +1244,7 @@ mod begin_batch_tests { use jolt_claims::protocols::jolt::relations::registers::RegistersValEvaluationInputClaims; use jolt_field::{Fr, JoltField, Ring}; use jolt_sumcheck::{append_sumcheck_claim, BatchMember, ClearSumcheckRecorder}; - use jolt_transcript::Transcript; + use jolt_transcript::FieldTranscript; #[derive(super::SumcheckBatch)] #[sumcheck_batch(crate = "crate")] @@ -1315,7 +1316,7 @@ mod begin_batch_tests { let (replica_events, (instruction_coeff, registers_coeff)) = record(|t| { append_sumcheck_claim(t, &instruction_sum); append_sumcheck_claim(t, ®isters_sum); - (t.challenge()?, t.challenge()?) + (t.challenge().unwrap(), t.challenge().unwrap()) }); assert_eq!(events, replica_events); @@ -1372,7 +1373,7 @@ mod begin_batch_tests { assert_eq!( events .iter() - .filter(|event| matches!(event, DrawEvent::Squeeze(_))) + .filter(|event| matches!(event, TranscriptEvent::Squeeze(_))) .count(), 1, ); diff --git a/crates/jolt-verifier/src/stages/stage2/outputs.rs b/crates/jolt-verifier/src/stages/stage2/outputs.rs index 7b5a53a18e..823acbe6e9 100644 --- a/crates/jolt-verifier/src/stages/stage2/outputs.rs +++ b/crates/jolt-verifier/src/stages/stage2/outputs.rs @@ -239,7 +239,7 @@ mod tests { }; use jolt_field::{Fr, Ring}; use jolt_program::preprocess::PublicIoMemory; - use jolt_transcript::Transcript; + use jolt_transcript::FieldTranscript; fn fr(value: u64) -> Fr { Fr::from_u64(value) diff --git a/crates/jolt-verifier/src/stages/stage4/outputs.rs b/crates/jolt-verifier/src/stages/stage4/outputs.rs index 9d52afd92c..b729da8131 100644 --- a/crates/jolt-verifier/src/stages/stage4/outputs.rs +++ b/crates/jolt-verifier/src/stages/stage4/outputs.rs @@ -203,7 +203,7 @@ mod tests { use jolt_claims::protocols::jolt::relations::ram::RamValCheckOutputClaims; use jolt_claims::protocols::jolt::relations::registers::RegistersReadWriteOutputClaims; use jolt_field::{Fr, Ring}; - use jolt_transcript::Transcript; + use jolt_transcript::FieldTranscript; fn fr(value: u64) -> Fr { Fr::from_u64(value) diff --git a/crates/jolt-verifier/src/stages/stage4/ram_val_check.rs b/crates/jolt-verifier/src/stages/stage4/ram_val_check.rs index 26d76d09fd..932d8b13a6 100644 --- a/crates/jolt-verifier/src/stages/stage4/ram_val_check.rs +++ b/crates/jolt-verifier/src/stages/stage4/ram_val_check.rs @@ -557,7 +557,7 @@ mod tests { use super::*; use crate::stages::relations::draw_recording::{record, TranscriptEvent}; use jolt_field::Fr; - use jolt_transcript::Encoding; + use jolt_transcript::{Encoding, Transcript}; // Overrides the default to absorb the `b"ram_val_check_gamma"` domain // separator label before the gamma draw; the label is part of the diff --git a/crates/jolt-verifier/src/stages/stage5/outputs.rs b/crates/jolt-verifier/src/stages/stage5/outputs.rs index 0de5e01835..2ebbedcfdb 100644 --- a/crates/jolt-verifier/src/stages/stage5/outputs.rs +++ b/crates/jolt-verifier/src/stages/stage5/outputs.rs @@ -175,7 +175,7 @@ mod tests { use jolt_claims::protocols::jolt::relations::ram::RamRaClaimReductionOutputClaims; use jolt_claims::protocols::jolt::relations::registers::RegistersValEvaluationOutputClaims; use jolt_field::{Fr, Ring}; - use jolt_transcript::Transcript; + use jolt_transcript::FieldTranscript; fn fr(value: u64) -> Fr { Fr::from_u64(value) diff --git a/crates/jolt-verifier/src/stages/stage6a/verify.rs b/crates/jolt-verifier/src/stages/stage6a/verify.rs index f1fbbe6759..d05b154cd0 100644 --- a/crates/jolt-verifier/src/stages/stage6a/verify.rs +++ b/crates/jolt-verifier/src/stages/stage6a/verify.rs @@ -202,7 +202,7 @@ mod tests { use jolt_claims::protocols::jolt::geometry::bytecode::BytecodeReadRafDimensions; use jolt_claims::protocols::jolt::geometry::ra::JoltRaPolynomialLayout; use jolt_field::{Fr, Ring}; - use jolt_transcript::{prover_transcript, Keccak, ScriptedTranscript}; + use jolt_transcript::{prover_transcript, Keccak, ScriptedTranscript, Transcript}; fn fr(value: u64) -> Fr { Fr::from_u64(value) diff --git a/crates/jolt-verifier/src/stages/stage6b/verify.rs b/crates/jolt-verifier/src/stages/stage6b/verify.rs index ec355362ba..941b78e7d5 100644 --- a/crates/jolt-verifier/src/stages/stage6b/verify.rs +++ b/crates/jolt-verifier/src/stages/stage6b/verify.rs @@ -594,6 +594,7 @@ fn append_opening_claims( #[cfg(test)] #[expect(clippy::unwrap_used, clippy::expect_used)] mod tests { + use jolt_transcript::Transcript; #[cfg(not(feature = "akita"))] use super::super::booleanity::BooleanityOutputClaims; #[cfg(not(feature = "akita"))] diff --git a/crates/jolt-verifier/src/verifier.rs b/crates/jolt-verifier/src/verifier.rs index 4a15552406..5a2359c5c6 100644 --- a/crates/jolt-verifier/src/verifier.rs +++ b/crates/jolt-verifier/src/verifier.rs @@ -1134,7 +1134,7 @@ mod tests { use jolt_sumcheck::{ ClearProof, ClearSumcheckProof, CommittedSumcheckProof, CompressedSumcheckProof, }; - use jolt_transcript::{FieldTranscript, Transcript}; + use jolt_transcript::FieldTranscript; use num_traits::Zero; use crate::preprocessing::ProgramPreprocessing; @@ -1196,7 +1196,9 @@ mod tests { } impl jolt_transcript::Encoding for TestCommitment { - fn append_to_transcript(&self, _transcript: &mut T) {} + fn encode(&self) -> impl AsRef<[u8]> { + [0u8; 0] + } } type TestProof = JoltProof>; @@ -1237,7 +1239,7 @@ mod tests { fn protocol_and_payload_reject_before_preprocessing_validation() { #[cfg(feature = "field-inline")] use jolt_riscv::JoltInstructionKind; - use jolt_transcript::LegacyBlake2bTranscript; + use jolt_transcript::{verifier_transcript, Keccak}; #[cfg_attr(not(feature = "field-inline"), expect(unused_mut))] let mut preprocessing = test_preprocessing(); #[cfg(feature = "field-inline")] @@ -1257,23 +1259,17 @@ mod tests { }; let public_io = JoltDevice::default(); assert!(matches!( - validate_and_seed_transcript::<_, _, LegacyBlake2bTranscript, _>( - &preprocessing, - &public_io, - &proof, - None - ), + validate_and_seed_transcript(&preprocessing, &public_io, &proof, None, |statement| { + verifier_transcript::(JOLT_SESSION, statement, &[]) + }), Err(VerifierError::ProtocolConfigMismatch { .. }) )); proof.protocol = JOLT_VERIFIER_CONFIG; proof.stages.stage1_sumcheck_proof = sumcheck_proof(!is_zk); assert!(matches!( - validate_and_seed_transcript::<_, _, LegacyBlake2bTranscript, _>( - &preprocessing, - &public_io, - &proof, - None - ), + validate_and_seed_transcript(&preprocessing, &public_io, &proof, None, |statement| { + verifier_transcript::(JOLT_SESSION, statement, &[]) + }), Err(VerifierError::ExpectedClearProof { field: "stage1_sumcheck_proof" } | VerifierError::ExpectedCommittedProof { diff --git a/crates/jolt-verifier/tests/completeness/zk.rs b/crates/jolt-verifier/tests/completeness/zk.rs index ddef57ce70..ce5de9cceb 100644 --- a/crates/jolt-verifier/tests/completeness/zk.rs +++ b/crates/jolt-verifier/tests/completeness/zk.rs @@ -18,7 +18,7 @@ use jolt_field::Fr; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] use jolt_sumcheck::SumcheckProof; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] -use jolt_transcript::{Blake2b512, LegacyBlake2bTranscript as ProverState, ProverState}; +use jolt_transcript::{Keccak, VerifierState}; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] use jolt_verifier::JoltProofClaims; @@ -44,7 +44,7 @@ fn zk_committed_muldiv_blindfold_shape_audit_matches_modular_protocol() { Fr, DoryScheme, Pedersen, - ProverState, + VerifierState<'_, Keccak>, _, >(&case.preprocessing, &case.public_io, &case.proof, None) .expect("build modular BlindFold protocol shape"); @@ -79,7 +79,7 @@ fn zk_muldiv_blindfold_shape_audit_matches_modular_protocol() { Fr, DoryScheme, Pedersen, - ProverState, + VerifierState<'_, Keccak>, _, >(&case.preprocessing, &case.public_io, &case.proof, None) .expect("build modular BlindFold protocol shape"); diff --git a/crates/jolt-verifier/tests/fs_attacks.rs b/crates/jolt-verifier/tests/fs_attacks.rs index 0847c7aafd..8af707ca4f 100644 --- a/crates/jolt-verifier/tests/fs_attacks.rs +++ b/crates/jolt-verifier/tests/fs_attacks.rs @@ -20,8 +20,6 @@ use jolt_dory::DoryScheme; use jolt_field::Fr; #[cfg(all(not(feature = "akita"), not(feature = "zk")))] use jolt_field::Ring; -#[cfg(not(feature = "akita"))] -use jolt_transcript::LegacyBlake2bTranscript; #[cfg(all(not(feature = "akita"), not(feature = "zk")))] #[test] @@ -34,7 +32,7 @@ fn dory_clear_preprocessing_digest_requires_fiat_shamir_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >( &case.preprocessing, &case.public_io, @@ -54,7 +52,7 @@ fn dory_clear_preprocessing_digest_requires_fiat_shamir_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >( &preprocessing, &case.public_io, @@ -74,7 +72,7 @@ fn dory_clear_preprocessing_digest_requires_fiat_shamir_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >( &preprocessing, &case.public_io, @@ -103,7 +101,7 @@ fn dory_clear_stage1_sumcheck_requires_fiat_shamir_challenges() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >( &case.preprocessing, &case.public_io, @@ -123,7 +121,7 @@ fn dory_clear_stage1_sumcheck_requires_fiat_shamir_challenges() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >( &case.preprocessing, &case.public_io, @@ -143,7 +141,7 @@ fn dory_clear_stage1_sumcheck_requires_fiat_shamir_challenges() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >( &case.preprocessing, &case.public_io, @@ -172,7 +170,7 @@ fn dory_clear_final_opening_batch_requires_commitment_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >( &case.preprocessing, &case.public_io, @@ -192,7 +190,7 @@ fn dory_clear_final_opening_batch_requires_commitment_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >( &case.preprocessing, &case.public_io, @@ -212,7 +210,7 @@ fn dory_clear_final_opening_batch_requires_commitment_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >( &case.preprocessing, &case.public_io, @@ -241,7 +239,7 @@ fn dory_zk_preprocessing_digest_requires_fiat_shamir_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >(&case.preprocessing, &case.public_io, &case.proof, None) }); assert!( @@ -256,7 +254,7 @@ fn dory_zk_preprocessing_digest_requires_fiat_shamir_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >(&preprocessing, &case.public_io, &case.proof, None) }); assert!( @@ -271,7 +269,7 @@ fn dory_zk_preprocessing_digest_requires_fiat_shamir_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >(&preprocessing, &case.public_io, &case.proof, None) }); assert!( @@ -295,7 +293,7 @@ fn dory_zk_final_opening_batch_requires_commitment_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >(&case.preprocessing, &case.public_io, &case.proof, None) }); assert!( @@ -310,7 +308,7 @@ fn dory_zk_final_opening_batch_requires_commitment_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >(&case.preprocessing, &case.public_io, &proof, None) }); assert!( @@ -325,7 +323,7 @@ fn dory_zk_final_opening_batch_requires_commitment_binding() { Fr, DoryScheme, Pedersen, - AuditTranscript>, + AuditTranscript<'_, Fr>, >(&case.preprocessing, &case.public_io, &proof, None) }); assert!( @@ -342,12 +340,12 @@ fn dory_zk_final_opening_batch_requires_commitment_binding() { #[test] fn akita_clear_preprocessing_digest_requires_fiat_shamir_binding() { use jolt_akita::{AkitaField, AkitaScheme}; - use jolt_prover::akita::preprocessing::{AkitaTranscript, AkitaVc}; + use jolt_prover::akita::preprocessing::AkitaVc; use support::akita_fixtures::akita_muldiv_case; let case = akita_muldiv_case(); let (honest, tape) = record_challenges::(|| { - jolt_verifier::verify::>( + jolt_verifier::verify::>( &case.preprocessing, &case.public_io, &case.proof, @@ -359,7 +357,7 @@ fn akita_clear_preprocessing_digest_requires_fiat_shamir_binding() { let mut preprocessing = case.preprocessing.clone(); preprocessing.preprocessing_digest[0] ^= 1; let frozen = replay_challenges(&tape, || { - jolt_verifier::verify::>( + jolt_verifier::verify::>( &preprocessing, &case.public_io, &case.proof, @@ -374,7 +372,7 @@ fn akita_clear_preprocessing_digest_requires_fiat_shamir_binding() { assert_eq!(frozen.consumed, frozen.expected); let (production, mutated_tape) = record_challenges::(|| { - jolt_verifier::verify::>( + jolt_verifier::verify::>( &preprocessing, &case.public_io, &case.proof, @@ -396,12 +394,12 @@ fn akita_clear_preprocessing_digest_requires_fiat_shamir_binding() { fn akita_clear_stage1_sumcheck_requires_fiat_shamir_challenges() { use jolt_akita::{AkitaField, AkitaScheme}; use jolt_field::Ring; - use jolt_prover::akita::preprocessing::{AkitaTranscript, AkitaVc}; + use jolt_prover::akita::preprocessing::AkitaVc; use support::akita_fixtures::akita_muldiv_case; let case = akita_muldiv_case(); let (honest, tape) = record_challenges::(|| { - jolt_verifier::verify::>( + jolt_verifier::verify::>( &case.preprocessing, &case.public_io, &case.proof, @@ -413,7 +411,7 @@ fn akita_clear_stage1_sumcheck_requires_fiat_shamir_challenges() { let mut proof = case.proof.clone(); equivocate_stage1_clear(&mut proof, &tape, AkitaField::from_u64(1)); let frozen = replay_challenges(&tape, || { - jolt_verifier::verify::>( + jolt_verifier::verify::>( &case.preprocessing, &case.public_io, &proof, @@ -428,7 +426,7 @@ fn akita_clear_stage1_sumcheck_requires_fiat_shamir_challenges() { assert_eq!(frozen.consumed, frozen.expected); let (production, mutated_tape) = record_challenges::(|| { - jolt_verifier::verify::>( + jolt_verifier::verify::>( &case.preprocessing, &case.public_io, &proof, diff --git a/crates/jolt-verifier/tests/generate_fuzz_fixture.rs b/crates/jolt-verifier/tests/generate_fuzz_fixture.rs index 13872d774d..f3c81a68da 100644 --- a/crates/jolt-verifier/tests/generate_fuzz_fixture.rs +++ b/crates/jolt-verifier/tests/generate_fuzz_fixture.rs @@ -24,7 +24,7 @@ use common::jolt_device::JoltDevice; use jolt_crypto::{Bn254G1, Pedersen}; use jolt_dory::{DoryCommitment, DoryScheme}; use jolt_field::Fr; -use jolt_transcript::LegacyBlake2bTranscript; +use jolt_transcript::{Keccak, VerifierState}; use jolt_verifier::{JoltProof, JoltVerifierPreprocessing}; mod support; @@ -134,7 +134,7 @@ fn write_bundle(filename: &str, bundle: FuzzBundle, zk: bool) { assert_eq!(consumed, bytes.len(), "fuzz bundle has trailing bytes"); support::assert_accepts_mode( zk, - jolt_verifier::verify::, LegacyBlake2bTranscript>( + jolt_verifier::verify::, VerifierState<'_, Keccak>>( &decoded.0, &decoded.1, &decoded.2, diff --git a/crates/jolt-verifier/tests/soundness/tampering/akita.rs b/crates/jolt-verifier/tests/soundness/tampering/akita.rs index c24664d661..8cacf8a0cc 100644 --- a/crates/jolt-verifier/tests/soundness/tampering/akita.rs +++ b/crates/jolt-verifier/tests/soundness/tampering/akita.rs @@ -31,7 +31,8 @@ use jolt_claims::protocols::jolt::lattice::relations::{ use jolt_claims::protocols::jolt::relations::claim_reductions::bytecode::BytecodeReductionIntermediateClaims; use jolt_claims::protocols::jolt::TracePolynomialOrder; use jolt_field::{JoltField, Ring}; -use jolt_prover::akita::preprocessing::{AkitaTranscript, AkitaVc}; +use jolt_prover::akita::preprocessing::{AkitaVc}; +use jolt_transcript::{Keccak, VerifierState}; use jolt_verifier::preprocessing::{CommittedProgramPreprocessing, ProgramPreprocessing}; use jolt_verifier::proof::{ClearProofClaims, JoltProofClaims}; use jolt_verifier::stages::{ @@ -641,7 +642,7 @@ fn every_commitment_wire_rejects_perturbation() { } if let Some(trusted) = &advice.trusted_advice_commitment { sweep_commitment(trusted, 6, |commitment| { - jolt_verifier::verify::( + jolt_verifier::verify::>( &advice.preprocessing, &advice.public_io, &advice.proof, @@ -662,7 +663,7 @@ fn every_commitment_wire_rejects_perturbation() { panic!("committed fixture must carry committed preprocessing"); }; replace(program, commitment); - jolt_verifier::verify::( + jolt_verifier::verify::>( &preprocessing, &committed.public_io, &committed.proof, @@ -710,7 +711,7 @@ fn akita_proof_shape_tampers_reject() { AkitaField, AkitaScheme, AkitaVc, - AkitaTranscript, + VerifierState<'_, Keccak>, >( &preprocessing, &committed.public_io, @@ -730,7 +731,7 @@ fn akita_proof_shape_tampers_reject() { AkitaField, AkitaScheme, AkitaVc, - AkitaTranscript, + VerifierState<'_, Keccak>, >( &preprocessing, &committed.public_io, @@ -744,7 +745,7 @@ fn akita_proof_shape_tampers_reject() { #[test] fn akita_advice_commitment_presence_rejects() { let advice = akita_advice_case(); - let result = jolt_verifier::verify::( + let result = jolt_verifier::verify::>( &advice.preprocessing, &advice.public_io, &advice.proof, diff --git a/crates/jolt-verifier/tests/statistical_independence/zk.rs b/crates/jolt-verifier/tests/statistical_independence/zk.rs index c0483404ed..ce84cd8acd 100644 --- a/crates/jolt-verifier/tests/statistical_independence/zk.rs +++ b/crates/jolt-verifier/tests/statistical_independence/zk.rs @@ -18,7 +18,7 @@ use jolt_field::{CanonicalBytes, Fr}; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] use jolt_sumcheck::SumcheckProof; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] -use jolt_transcript::{prover_transcript, Blake2b512, Encoding, LegacyBlake2bTranscript as ProverState, Transcript}; +use jolt_transcript::{prover_transcript, Encoding, FieldTranscript, Keccak, Transcript}; #[cfg(all(feature = "prover-fixtures", feature = "zk"))] use jolt_verifier::JoltProofClaims; @@ -506,9 +506,9 @@ impl BucketTracker { fn record_append(&mut self, name: impl Into, value: &A) { let name = name.into(); - let mut transcript = prover_transcript::(b"jolt-zk-stat", b""); + let mut transcript = prover_transcript::(b"jolt-zk-stat", b""); transcript.append_bytes(name.as_bytes()); - value.append_to_transcript(&mut transcript); + transcript.append(value); self.record_projected(name, field_low_u64(transcript.challenge().unwrap())); } @@ -555,7 +555,7 @@ impl BucketTracker { } fn record_bytes(&mut self, name: String, bytes: &[u8]) { - let mut transcript = prover_transcript::(b"jolt-zk-stat", b""); + let mut transcript = prover_transcript::(b"jolt-zk-stat", b""); transcript.append_bytes(name.as_bytes()); transcript.append_bytes(bytes); self.record_projected(name, field_low_u64(transcript.challenge().unwrap())); diff --git a/crates/jolt-verifier/tests/support/akita_fixtures.rs b/crates/jolt-verifier/tests/support/akita_fixtures.rs index 1cf8dc04f3..39e9cacb92 100644 --- a/crates/jolt-verifier/tests/support/akita_fixtures.rs +++ b/crates/jolt-verifier/tests/support/akita_fixtures.rs @@ -11,7 +11,8 @@ use jolt_akita::{ }; use jolt_host::Program; use jolt_prover::akita; -use jolt_prover::akita::preprocessing::{self, AkitaProverPreprocessing, AkitaTranscript, AkitaVc}; +use jolt_prover::akita::preprocessing::{self, AkitaProverPreprocessing, AkitaVc}; +use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_prover::JoltBackend; use jolt_prover::ProverConfig; use jolt_verifier::proof::JoltProof; @@ -36,7 +37,7 @@ impl AkitaFixtureCase { } pub fn verify_proof(&self, proof: &AkitaJoltProof) -> Result<(), VerifierError> { - verify::( + verify::>( &self.preprocessing, &self.public_io, proof, @@ -146,7 +147,7 @@ fn prove_prepared( preprocessing::commit_trusted_advice(&preprocessing, trusted_advice) .expect("trusted advice commitment") }); - let proof = akita::prove::( + let proof = akita::prove::>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -183,7 +184,8 @@ mod field_inline { use jolt_host::{JoltProgramSource, Program}; use jolt_program::execution::{ExecutionBackend, JoltProgram, TraceInputs, TraceOutput}; use jolt_program::preprocess::JoltProgramPreprocessing; - use jolt_prover::akita::preprocessing::{AkitaTranscript, AkitaVc}; + use jolt_prover::akita::preprocessing::{AkitaVc}; + use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_prover::JoltBackend; use jolt_prover::{akita, ProverConfig}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; @@ -285,7 +287,7 @@ mod field_inline { .expect("trace witness") .with_field_inline() .expect("field-inline witness view"); - let proof = akita::prove::( + let proof = akita::prove::>( JoltBackend::optimized().with_witness(&witness), &prover_preprocessing, &config, diff --git a/crates/jolt-verifier/tests/support/fs_mutations.rs b/crates/jolt-verifier/tests/support/fs_mutations.rs index 4b5e5adae4..27d19f39e2 100644 --- a/crates/jolt-verifier/tests/support/fs_mutations.rs +++ b/crates/jolt-verifier/tests/support/fs_mutations.rs @@ -15,7 +15,7 @@ use jolt_sumcheck::{CenteredIntegerDomain, ClearProof, SumcheckProof}; use jolt_verifier::{fs_audit::FsScope, JoltProof, JoltProofClaims}; use num_traits::One; -use super::fs_transcript::{ChallengeKind, ChallengeTape}; +use super::fs_transcript::ChallengeTape; /// Changes two Dory commitments in the kernel of the frozen final-opening RLC. #[cfg(not(feature = "akita"))] @@ -27,14 +27,13 @@ pub fn cancel_dory_final_opening_commitments( PCS::Output: HomomorphicCommitment, VC: VectorCommitment, { + // The final-opening RLC base is stage 8's first draw. let gamma_base = tape .records .iter() - .find(|record| { - record.id.scope == FsScope::Stage8 - && matches!(record.id.kind, ChallengeKind::PowersBase { len } if len >= 2) - }) + .find(|record| record.id.scope == FsScope::Stage8) .expect("stage-8 final-opening batching challenge is missing"); + assert_eq!(gamma_base.id.len, 1); let gamma_0 = PCS::Field::one(); let gamma_1 = gamma_base.values[0]; let direction = proof @@ -68,24 +67,20 @@ pub fn equivocate_stage1_clear( .iter() .filter(|record| record.id.scope == FsScope::Stage1); + // Stage 1 draws the `tau` vector, the uni-skip challenge, the batching + // coefficient, then the remainder round challenges, one record each. let tau = stage1.next().expect("stage 1 tau challenge is missing"); - assert!(matches!( - tau.id.kind, - ChallengeKind::VectorElement { index: 0, .. } - )); - - let uniskip_challenge = stage1 - .find(|record| record.id.kind == ChallengeKind::Challenge) - .expect("stage 1 uni-skip challenge is missing") - .values[0]; - let batching_coefficient = stage1 - .find(|record| record.id.kind == ChallengeKind::Scalar) - .expect("stage 1 batching coefficient is missing") - .values[0]; - let remainder_challenge = stage1 - .find(|record| record.id.kind == ChallengeKind::Challenge) - .expect("stage 1 remainder challenge is missing") - .values[0]; + assert!(tau.id.len > 1, "stage 1 opens with the tau vector"); + let mut single = |name: &str| { + let record = stage1 + .next() + .unwrap_or_else(|| panic!("stage 1 {name} is missing")); + assert_eq!(record.id.len, 1, "stage 1 {name} is one challenge"); + record.values[0] + }; + let uniskip_challenge = single("uni-skip challenge"); + let batching_coefficient = single("batching coefficient"); + let remainder_challenge = single("remainder challenge"); let JoltProofClaims::Clear(claims) = &mut proof.claims else { panic!("stage-1 clear equivocation requires a clear proof"); diff --git a/crates/jolt-verifier/tests/support/fs_transcript.rs b/crates/jolt-verifier/tests/support/fs_transcript.rs index b2b2988278..ed5abd0853 100644 --- a/crates/jolt-verifier/tests/support/fs_transcript.rs +++ b/crates/jolt-verifier/tests/support/fs_transcript.rs @@ -6,51 +6,37 @@ use std::{any::Any, cell::RefCell, collections::BTreeMap, sync::Arc}; -use jolt_field::{Field, Ring}; -use jolt_transcript::{ChallengeSetSize, Transcript}; +use jolt_field::Field; +use jolt_transcript::{Keccak, Script, ScriptedTranscript, VerifierState}; use jolt_verifier::fs_audit::{self, FsScope}; -/// The transcript API used to derive a challenge. +/// A challenge draw's verifier scope, ordinal within that scope, and length. /// -/// Multi-value APIs are recorded at draw granularity, mirroring the -/// production trait defaults: `challenge_vector(len)` is `len` independent -/// squeezes (one [`ChallengeKind::VectorElement`] record each), and -/// `challenge_scalar_powers(len)` squeezes only its base scalar (one -/// [`ChallengeKind::PowersBase`] record) with the powers derived locally. -/// A verifier refactor that drops or short-consumes an element therefore -/// registers as a per-element tape divergence instead of hiding inside one -/// atomic `Vec` record. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ChallengeKind { - Challenge, - Scalar, - VectorElement { len: usize, index: usize }, - PowersBase { len: usize }, -} - -/// A challenge's verifier scope and ordinal within that scope. +/// One record is one codec message: `challenge_vector(len)` is one record of +/// `len` values, and `challenge_powers(len)` one record of its single base. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ChallengeId { pub scope: FsScope, pub index: usize, - pub kind: ChallengeKind, + pub len: usize, } -/// One typed challenge call and all values it returned. +/// One challenge draw and the values it returned. #[derive(Clone, Debug, PartialEq, Eq)] pub struct ChallengeRecord { pub id: ChallengeId, pub values: Vec, } -/// Challenges produced while verifying one fixture. +/// Challenges produced while verifying one fixture, and the proof-of-work +/// check bytes squeezed between them. #[derive(Clone, Debug, PartialEq, Eq)] pub struct ChallengeTape { pub records: Vec>, /// Proof-of-work check bytes, in squeeze order. They are not challenges, /// but a replay must freeze them too: otherwise a mutated transcript /// fails the proof of work before reaching the binding under test. - pub pow_checks: Vec>, + pub checks: Vec>, } impl ChallengeTape { @@ -89,15 +75,15 @@ struct ErasedRecord { enum Session { Record { records: Vec, + checks: Vec>, counters: BTreeMap, - pow_checks: Vec>, }, Replay { records: Vec, + checks: Vec>, counters: BTreeMap, cursor: usize, - pow_checks: Vec>, - pow_cursor: usize, + check_cursor: usize, }, } @@ -105,165 +91,100 @@ thread_local! { static SESSION: RefCell> = const { RefCell::new(None) }; } -fn next_id(counters: &mut BTreeMap, kind: ChallengeKind) -> ChallengeId { +fn next_id(counters: &mut BTreeMap, len: usize) -> ChallengeId { let scope = fs_audit::current(); let index = counters.entry(scope).or_default(); let id = ChallengeId { scope, index: *index, - kind, + len, }; *index += 1; id } -fn draw(kind: ChallengeKind, produce: impl FnOnce() -> Vec) -> Vec -where - F: Field + 'static, -{ - let actual = produce(); - SESSION.with(|slot| { - let mut slot = slot.borrow_mut(); - let Some(session) = slot.as_mut() else { - return actual; - }; - match session { - Session::Record { - records, counters, .. - } => { - let id = next_id(counters, kind); - records.push(ErasedRecord { - id, - values: Arc::new(actual.clone()), - }); - actual - } - Session::Replay { - records, - counters, - cursor, - .. - } => { - let id = next_id(counters, kind); - let expected = records - .get(*cursor) - .unwrap_or_else(|| panic!("challenge replay exhausted at {id:?}")); - assert_eq!( - expected.id, id, - "challenge replay kind or scope diverged at tape index {cursor}" - ); - *cursor += 1; - expected - .values - .downcast_ref::>() - .expect("challenge replay field type changed") - .clone() - } - } - }) -} - -/// Records or replays one proof-of-work check squeeze. `out` already holds -/// the live squeeze; a replay overwrites it with the recorded bytes. -fn pow_check(out: &mut [u8]) { - SESSION.with(|slot| match slot.borrow_mut().as_mut() { - None => {} - Some(Session::Record { pow_checks, .. }) => pow_checks.push(out.to_vec()), - Some(Session::Replay { - pow_checks, - pow_cursor, - .. - }) => { - let expected = pow_checks - .get(*pow_cursor) - .unwrap_or_else(|| panic!("proof-of-work replay exhausted at {pow_cursor}")); - assert_eq!( - expected.len(), - out.len(), - "proof-of-work replay width diverged at check {pow_cursor}" - ); - out.copy_from_slice(expected); - *pow_cursor += 1; - } - }); -} +/// The [`Script`] of [`AuditTranscript`]: records every draw and check, or +/// replaces them with the active replay tape. It keeps the real challenge set, +/// so proofs carry their real nonces. +#[derive(Clone, Copy, Debug, Default)] +pub struct AuditScript; -/// Transcript wrapper that records or replays challenge calls for the active session. -#[derive(Clone)] -pub struct AuditTranscript { - inner: T, -} +impl Script for AuditScript { + const UNPRICED: bool = false; -impl Default for AuditTranscript { - fn default() -> Self { - Self { - inner: T::default(), - } - } -} - -impl Transcript for AuditTranscript -where - T: Transcript, - T::Challenge: Field + 'static, -{ - type Challenge = T::Challenge; - const CHALLENGE_SET: ChallengeSetSize = T::CHALLENGE_SET; - - fn new(label: &'static [u8]) -> Self { - Self { - inner: T::new(label), - } - } - - fn append_bytes(&mut self, bytes: &[u8]) { - self.inner.append_bytes(bytes); - } - - fn challenge(&mut self) -> Self::Challenge { - draw(ChallengeKind::Challenge, || vec![self.inner.challenge().unwrap()])[0] - } - - fn challenge_scalar(&mut self) -> Self::Challenge { - draw(ChallengeKind::Scalar, || { - vec![self.inner.challenge().unwrap()] - })[0] - } - - // WARNING: both bodies must mirror the `Transcript` trait defaults - // (`legacy.rs`), which no production transcript overrides — the audit - // records at the same granularity the sponge is actually squeezed. - fn challenge_vector(&mut self, len: usize) -> Vec { - (0..len) - .map(|index| { - draw(ChallengeKind::VectorElement { len, index }, || { - vec![self.inner.challenge().unwrap()] - })[0] - }) - .collect() + fn draw(&mut self, challenges: &mut [F]) { + SESSION.with(|slot| { + let mut slot = slot.borrow_mut(); + match slot.as_mut() { + None => {} + Some(Session::Record { + records, counters, .. + }) => { + let id = next_id(counters, challenges.len()); + records.push(ErasedRecord { + id, + values: Arc::new(challenges.to_vec()), + }); + } + Some(Session::Replay { + records, + counters, + cursor, + .. + }) => { + let id = next_id(counters, challenges.len()); + let expected = records + .get(*cursor) + .unwrap_or_else(|| panic!("challenge replay exhausted at {id:?}")); + assert_eq!( + expected.id, id, + "challenge replay length or scope diverged at tape index {cursor}" + ); + *cursor += 1; + challenges.clone_from_slice( + expected + .values + .downcast_ref::>() + .expect("challenge replay field type changed"), + ); + } + } + }); } - fn challenge_scalar_powers(&mut self, len: usize) -> Vec { - let base = draw(ChallengeKind::PowersBase { len }, || { - vec![self.inner.challenge().unwrap()] - })[0]; - let mut powers = vec![Self::Challenge::from_u64(1); len]; - for index in 1..len { - powers[index] = powers[index - 1] * base; - } - powers + // A frozen verifier takes every squeeze from the tape, so a replay over + // changed inputs passes the proof-of-work checks the honest run passed. + fn check_bytes(&mut self, bytes: &mut [u8]) { + SESSION.with(|slot| match slot.borrow_mut().as_mut() { + None => {} + Some(Session::Record { checks, .. }) => checks.push(bytes.to_vec()), + Some(Session::Replay { + checks, + check_cursor, + .. + }) => { + let expected = checks + .get(*check_cursor) + .unwrap_or_else(|| panic!("check replay exhausted at {check_cursor}")); + bytes.copy_from_slice(expected); + *check_cursor += 1; + } + }); } +} - // Only the provided `check_grind` squeezes raw bytes, so these are - // proof-of-work check words. - fn challenge_bytes(&mut self, out: &mut [u8]) { - self.inner.challenge_bytes(out); - pow_check(out); - } +/// The production verifier transcript with its draws and checks recorded or +/// replayed for the active session. +pub type AuditTranscript<'a, F> = ScriptedTranscript, F, AuditScript>; - fn state(&self) -> [u8; 32] { - self.inner.state() - } +fn start(session: Session) { + SESSION.with(|slot| { + assert!( + slot.borrow().is_none(), + "nested Fiat-Shamir audit sessions are unsupported" + ); + *slot.borrow_mut() = Some(session); + }); } /// Runs `verify` with challenge recording enabled. @@ -271,25 +192,16 @@ pub fn record_challenges(verify: impl FnOnce() -> R) -> (R, ChallengeTape< where F: Field + 'static, { - SESSION.with(|slot| { - assert!( - slot.borrow().is_none(), - "nested Fiat-Shamir audit sessions are unsupported" - ); - *slot.borrow_mut() = Some(Session::Record { - records: Vec::new(), - counters: BTreeMap::new(), - pow_checks: Vec::new(), - }); + start(Session::Record { + records: Vec::new(), + checks: Vec::new(), + counters: BTreeMap::new(), }); - let result = verify(); - let (records, pow_checks) = SESSION.with(|slot| match slot.borrow_mut().take() { + let (records, checks) = SESSION.with(|slot| match slot.borrow_mut().take() { Some(Session::Record { - records, - pow_checks, - .. - }) => (records, pow_checks), + records, checks, .. + }) => (records, checks), Some(Session::Replay { .. }) => panic!("recording session changed to replay"), None => panic!("recording session disappeared"), }); @@ -304,24 +216,18 @@ where .clone(), }) .collect(); - ( - result, - ChallengeTape { - records, - pow_checks, - }, - ) + (result, ChallengeTape { records, checks }) } -/// Result of verifying with a frozen challenge tape. The counts cover -/// challenge records and proof-of-work checks together. +/// Result of verifying with a frozen challenge tape. pub struct ReplayResult { pub output: R, pub consumed: usize, pub expected: usize, } -/// Runs `verify` while returning the recorded values for every challenge call. +/// Runs `verify` while returning the recorded values for every challenge +/// draw and proof-of-work check. pub fn replay_challenges( tape: &ChallengeTape, verify: impl FnOnce() -> R, @@ -329,37 +235,25 @@ pub fn replay_challenges( where F: Field + 'static, { - let records = tape - .records - .iter() - .map(|record| ErasedRecord { - id: record.id, - values: Arc::new(record.values.clone()), - }) - .collect(); - SESSION.with(|slot| { - assert!( - slot.borrow().is_none(), - "nested Fiat-Shamir audit sessions are unsupported" - ); - *slot.borrow_mut() = Some(Session::Replay { - records, - counters: BTreeMap::new(), - cursor: 0, - pow_checks: tape.pow_checks.clone(), - pow_cursor: 0, - }); + start(Session::Replay { + records: tape + .records + .iter() + .map(|record| ErasedRecord { + id: record.id, + values: Arc::new(record.values.clone()), + }) + .collect(), + checks: tape.checks.clone(), + counters: BTreeMap::new(), + cursor: 0, + check_cursor: 0, }); - let output = verify(); let (consumed, expected) = SESSION.with(|slot| match slot.borrow_mut().take() { Some(Session::Replay { - records, - cursor, - pow_checks, - pow_cursor, - .. - }) => (cursor + pow_cursor, records.len() + pow_checks.len()), + records, cursor, .. + }) => (cursor, records.len()), Some(Session::Record { .. }) => panic!("replay session changed to recording"), None => panic!("replay session disappeared"), }); diff --git a/crates/jolt-verifier/tests/support/tamper_manifest.rs b/crates/jolt-verifier/tests/support/tamper_manifest.rs index ff870ebc78..5dd4f51094 100644 --- a/crates/jolt-verifier/tests/support/tamper_manifest.rs +++ b/crates/jolt-verifier/tests/support/tamper_manifest.rs @@ -1420,6 +1420,9 @@ pub fn observed_rejection_phase(error: &VerifierError) -> Option PowSite::Stage8 => VerifierPhase::Stage8Openings, }) } + // A refused draw or unread NARG bytes are observed wherever the + // transcript stops; neither names a phase. + VerifierError::TranscriptRejected | VerifierError::NargNotConsumed => None, VerifierError::MissingProofPayload { field } => match *field { "commitments.field_inline" | "field_inc_commitment" => Some(VerifierPhase::Preamble), _ => None, diff --git a/crates/jolt-verifier/tests/support/verifier_fixtures.rs b/crates/jolt-verifier/tests/support/verifier_fixtures.rs index e134bf4c4a..7901b11750 100644 --- a/crates/jolt-verifier/tests/support/verifier_fixtures.rs +++ b/crates/jolt-verifier/tests/support/verifier_fixtures.rs @@ -23,7 +23,7 @@ use jolt_host::Program; use jolt_program::execution::{JoltProgram, TraceOutput}; use jolt_prover::dory::DoryProverPreprocessing; use jolt_prover::{JoltBackend, JoltSharedPreprocessing, ProverConfig}; -use jolt_transcript::{Blake2b512, LegacyBlake2bTranscript as ProverState, ProverState}; +use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_verifier::{verify, JoltProof, JoltVerifierPreprocessing, VerifierError}; use super::guest_fixtures::{fixture_witness, prepare_guest, PreparedGuest}; @@ -129,7 +129,7 @@ pub struct VerifierFixtureCase { #[cfg(not(feature = "zk"))] impl VerifierFixtureCase { pub fn verify(&self) -> Result<(), VerifierError> { - verify::, ProverState>( + verify::, VerifierState<'_, Keccak>>( &self.preprocessing, &self.public_io, &self.proof, @@ -150,7 +150,7 @@ pub struct ZkVerifierFixtureCase { #[cfg(feature = "zk")] impl ZkVerifierFixtureCase { pub fn verify(&self) -> Result<(), VerifierError> { - verify::, ProverState>( + verify::, VerifierState<'_, Keccak>>( &self.preprocessing, &self.public_io, &self.proof, @@ -530,7 +530,7 @@ fn assert_verifier_accepts( proof: VerifierFixtureProof, public_io: JoltDevice, ) { - let result = verify::, ProverState>( + let result = verify::, VerifierState<'_, Keccak>>( &fixture.preprocessing, &public_io, &proof, @@ -725,7 +725,7 @@ fn prove_prepared( jolt_prover::dory::commit_trusted_advice(&preprocessing, trusted_advice) .expect("trusted advice commitment") }); - let proof = jolt_prover::dory::prove::, ProverState>( + let proof = jolt_prover::dory::prove::, ProverState>( JoltBackend::optimized().with_witness(&witness), &preprocessing, &config, @@ -751,7 +751,7 @@ mod field_inline { use jolt_field::Fr; use jolt_program::execution::{ExecutionBackend, JoltProgram, TraceInputs, TraceOutput}; use jolt_prover::{JoltBackend, ProverConfig}; - use jolt_transcript::{Blake2b512, LegacyBlake2bTranscript as ProverState, ProverState}; + use jolt_transcript::{Keccak, ProverState}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; use tracer::execution_backend::TracerBackend; @@ -829,7 +829,7 @@ mod field_inline { let witness = Arc::new(witness); let backend = JoltBackend::::reference(); - let proof = jolt_prover::prove::, ProverState>( + let proof = jolt_prover::prove::, ProverState>( backend.with_witness(&witness), &prover_preprocessing, &config, diff --git a/crates/jolt-verifier/tests/support/zk_audit.rs b/crates/jolt-verifier/tests/support/zk_audit.rs index ee18e06c77..34d96b05b6 100644 --- a/crates/jolt-verifier/tests/support/zk_audit.rs +++ b/crates/jolt-verifier/tests/support/zk_audit.rs @@ -4,7 +4,7 @@ use jolt_claims::protocols::jolt::JoltRelationId; use jolt_crypto::{HomomorphicCommitment, VectorCommitment}; use jolt_field::JoltField; use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, ZkOpeningScheme}; -use jolt_transcript::{Encoding, FieldTranscript, Transcript}; +use jolt_transcript::{Encoding, FieldTranscript, VerifierInit}; use jolt_verifier::{ preprocessing::JoltVerifierPreprocessing, @@ -46,10 +46,10 @@ impl ZkBlindFoldProtocolShape { } } -pub fn audit_zk_blindfold_protocol_shape( +pub fn audit_zk_blindfold_protocol_shape<'a, F, PCS, VC, T, ZkProof>( preprocessing: &JoltVerifierPreprocessing, public_io: &JoltDevice, - proof: &JoltProof, + proof: &'a JoltProof, trusted_advice_commitment: Option<&PCS::Output>, ) -> Result where @@ -60,15 +60,15 @@ where PCS::Output: Encoding + HomomorphicCommitment, VC: VectorCommitment, VC::Output: Copy + HomomorphicCommitment + Encoding, - T: FieldTranscript, + T: FieldTranscript + VerifierInit<'a>, { - let (checked, mut transcript) = - jolt_verifier::validate_and_seed_transcript::( - preprocessing, - public_io, - proof, - trusted_advice_commitment, - )?; + let (checked, mut transcript) = jolt_verifier::validate_and_seed_transcript( + preprocessing, + public_io, + proof, + trusted_advice_commitment, + |statement| T::init(jolt_verifier::JOLT_SESSION, statement, &proof.narg), + )?; let formula_dimensions = jolt_verifier::stages::build_formula_dimensions( proof, From 18530be3b64519e8e0df780799072cd8ea685faa Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Tue, 20 Oct 2026 23:43:33 -0400 Subject: [PATCH 06/16] test(jolt-verifier): bind each statement field and re-bless the Fiat-Shamir census --- crates/jolt-verifier/src/verifier.rs | 131 +++++++++++ .../tests/fs_inventory/absorb-sites.inventory | 222 ++++++------------ .../fs_inventory/challenge-sites.inventory | 35 ++- .../tests/fs_inventory/scope-sites.inventory | 1 - .../fs_inventory/source-schema.inventory | 1 + crates/jolt-verifier/tests/fs_obligations.rs | 15 +- 6 files changed, 239 insertions(+), 166 deletions(-) diff --git a/crates/jolt-verifier/src/verifier.rs b/crates/jolt-verifier/src/verifier.rs index 5a2359c5c6..9fa517d883 100644 --- a/crates/jolt-verifier/src/verifier.rs +++ b/crates/jolt-verifier/src/verifier.rs @@ -1278,6 +1278,137 @@ mod tests { )); } + /// The statement the transcript is bound to changes with every field of + /// it the verifier reads, except the fields the verifier binds another + /// way. Proof-body fields are absorbed as messages where they are read. + #[test] + #[expect(clippy::unwrap_used)] + fn statement_binds_each_field_the_verifier_reads() { + type Edit = fn(&mut CheckedInputs, &mut ProofTranscriptConfig); + + let preprocessing = test_preprocessing(); + let public_io = JoltDevice { + memory_layout: preprocessing.program.memory_layout().clone(), + inputs: vec![1, 2], + outputs: vec![3], + ..JoltDevice::default() + }; + let is_zk = JOLT_VERIFIER_CONFIG.zk == ZkConfig::BlindFold; + let proof = proof_with_zk(is_zk, if is_zk { zk_claims() } else { clear_claims() }); + let checked = validate_inputs(&preprocessing, &public_io, &proof, false).unwrap(); + let config = ProofTranscriptConfig { + rw_config: proof.rw_config, + one_hot_config: proof.one_hot_config, + trace_polynomial_order: proof.trace_polynomial_order, + }; + let encode = |edit: Edit| { + let (mut checked, mut config) = (checked.clone(), config); + edit(&mut checked, &mut config); + let statement = JoltStatement { + checked: &checked, + config, + }; + let bytes = statement.encode().as_ref().to_vec(); + bytes + }; + let honest = encode(|_, _| {}); + + let bound: &[(&str, Edit)] = &[ + ("zk", |c, _| c.zk = !c.zk), + ("max_input_size", |c, _| { + c.public_io.memory_layout.max_input_size += 1; + }), + ("max_output_size", |c, _| { + c.public_io.memory_layout.max_output_size += 1; + }), + ("heap_size", |c, _| c.public_io.memory_layout.heap_size += 1), + ("panic", |c, _| c.public_io.panic = !c.public_io.panic), + ("inputs", |c, _| c.public_io.inputs.push(0)), + ("outputs", |c, _| c.public_io.outputs.push(1)), + ("trace_length", |c, _| c.trace_length *= 2), + ("ram_K", |c, _| c.ram_K *= 2), + ("entry_address", |c, _| c.entry_address += 4), + ("preprocessing_digest", |c, _| c.preprocessing_digest[0] ^= 1), + ("ram_rw_phase1_num_rounds", |_, k| { + k.rw_config.ram_rw_phase1_num_rounds += 1; + }), + ("ram_rw_phase2_num_rounds", |_, k| { + k.rw_config.ram_rw_phase2_num_rounds += 1; + }), + ("registers_rw_phase1_num_rounds", |_, k| { + k.rw_config.registers_rw_phase1_num_rounds += 1; + }), + ("registers_rw_phase2_num_rounds", |_, k| { + k.rw_config.registers_rw_phase2_num_rounds += 1; + }), + ("log_k_chunk", |_, k| k.one_hot_config.log_k_chunk += 1), + ("lookups_ra_virtual_log_k_chunk", |_, k| { + k.one_hot_config.lookups_ra_virtual_log_k_chunk += 1; + }), + ("trace_polynomial_order", |_, k| { + k.trace_polynomial_order = match k.trace_polynomial_order { + TracePolynomialOrder::CycleMajor => TracePolynomialOrder::AddressMajor, + TracePolynomialOrder::AddressMajor => TracePolynomialOrder::CycleMajor, + }; + }), + ]; + for (field, edit) in bound { + assert_ne!(encode(*edit), honest, "the statement does not bind {field}"); + } + + let unbound: &[(&str, Edit)] = &[ + // `validate_inputs` rejects a memory layout other than the + // preprocessing's, and the preprocessing digest binds that one. + ("program_size", |c, _| c.public_io.memory_layout.program_size += 1), + ("max_trusted_advice_size", |c, _| { + c.public_io.memory_layout.max_trusted_advice_size += 1; + }), + ("trusted_advice_start", |c, _| { + c.public_io.memory_layout.trusted_advice_start += 1; + }), + ("trusted_advice_end", |c, _| { + c.public_io.memory_layout.trusted_advice_end += 1; + }), + ("max_untrusted_advice_size", |c, _| { + c.public_io.memory_layout.max_untrusted_advice_size += 1; + }), + ("untrusted_advice_start", |c, _| { + c.public_io.memory_layout.untrusted_advice_start += 1; + }), + ("untrusted_advice_end", |c, _| { + c.public_io.memory_layout.untrusted_advice_end += 1; + }), + ("input_start", |c, _| c.public_io.memory_layout.input_start += 1), + ("input_end", |c, _| c.public_io.memory_layout.input_end += 1), + ("output_start", |c, _| c.public_io.memory_layout.output_start += 1), + ("output_end", |c, _| c.public_io.memory_layout.output_end += 1), + ("stack_size", |c, _| c.public_io.memory_layout.stack_size += 1), + ("stack_end", |c, _| c.public_io.memory_layout.stack_end += 1), + ("heap_end", |c, _| c.public_io.memory_layout.heap_end += 1), + ("panic address", |c, _| c.public_io.memory_layout.panic += 1), + ("termination", |c, _| c.public_io.memory_layout.termination += 1), + ("io_end", |c, _| c.public_io.memory_layout.io_end += 1), + // The trusted-advice commitment is absorbed under its own label + // right after the statement, so its presence is bound there. + ("trusted_advice_commitment_present", |c, _| { + c.trusted_advice_commitment_present = !c.trusted_advice_commitment_present; + }), + // The capacity of the verifier's own vector-commitment setup. + ("vc_capacity", |c, _| c.vc_capacity = Some(c.vc_capacity.unwrap_or(0) + 1)), + // The advice bytes themselves: the verifier reads only their + // commitments. + ("trusted_advice", |c, _| c.public_io.trusted_advice.push(1)), + ("untrusted_advice", |c, _| c.public_io.untrusted_advice.push(1)), + ]; + for (field, edit) in unbound { + assert_eq!( + encode(*edit), + honest, + "the statement now binds {field}; move it to the bound list" + ); + } + } + #[test] #[expect(clippy::unwrap_used)] fn validate_inputs_normalizes_public_output() { diff --git a/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory b/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory index eb21f834ab..016c08bd0e 100644 --- a/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory +++ b/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory @@ -1,23 +1,11 @@ # Generated by `JOLT_FS_BLESS=1 cargo nextest run -p jolt-verifier \ # --test fs_obligations --features fs-audit`. # Review changes; this inventories identities, not transcript values. -jolt-akita::adapters.rs::AkitaCommitment::append_to_transcript::append#0::transcript.append(&Label(b"akita_commitment")) -jolt-akita::adapters.rs::AkitaCommitment::append_to_transcript::append#3::transcript.append(&U64Word(self.num_varsasu64)) -jolt-akita::adapters.rs::AkitaCommitment::append_to_transcript::append#4::transcript.append(&U64Word(self.poly_countasu64)) -jolt-akita::adapters.rs::AkitaCommitment::append_to_transcript::append#5::transcript.append(&U64Word(self.one_hot_kasu64)) -jolt-akita::adapters.rs::AkitaCommitment::append_to_transcript::append#6::transcript.append(&U64Word(self.backend_coeff_lenasu64)) -jolt-akita::adapters.rs::AkitaCommitment::append_to_transcript::append#7::transcript.append(&LabelWithCount(b"akita_commitment_bytes",self.serialized_backend_bytes.len()asu64,)) -jolt-akita::adapters.rs::AkitaCommitment::append_to_transcript::append_bytes#1::transcript.append_bytes(self.backend_flavor.transcript_label()) -jolt-akita::adapters.rs::AkitaCommitment::append_to_transcript::append_bytes#2::transcript.append_bytes(&self.layout_digest) -jolt-akita::adapters.rs::AkitaCommitment::append_to_transcript::append_bytes#8::transcript.append_bytes(&self.serialized_backend_bytes) -jolt-akita::adapters.rs::AkitaHidingCommitment::append_to_transcript::append#0::transcript.append(&Label(b"akita_hiding_commitment")) -jolt-akita::adapters.rs::AkitaHidingCommitment::append_to_transcript::append#1::transcript.append(&LabelWithCount(b"akita_hiding_eval",self.eval.len()asu64,)) -jolt-akita::adapters.rs::AkitaHidingCommitment::append_to_transcript::append_bytes#2::transcript.append_bytes(&self.eval) jolt-akita::adapters.rs::append_batch_statement::append#0::transcript.append(&Label(b"akita_batch_statement")) +jolt-akita::adapters.rs::append_batch_statement::append#1::transcript.append(&commitment) jolt-akita::adapters.rs::append_batch_statement::append#3::transcript.append(&LabelWithCount(b"akita_claims",statement.len()asu64)) -jolt-akita::adapters.rs::append_batch_statement::append_to_transcript#1::commitment.append_to_transcript(transcript) -jolt-akita::adapters.rs::append_batch_statement::append_to_transcript#4::claim.commitment.append_to_transcript(transcript) -jolt-akita::adapters.rs::append_batch_statement::append_to_transcript#5::claim.evaluation.value.append_to_transcript(transcript) +jolt-akita::adapters.rs::append_batch_statement::append#4::transcript.append(&claim.commitment) +jolt-akita::adapters.rs::append_batch_statement::append#5::transcript.append(&claim.evaluation.value) jolt-akita::adapters.rs::append_batch_statement::append_values#2::transcript.append_values(b"akita_pcs_point",point) jolt-akita::adapters.rs::append_verifier_setup::append#0::transcript.append(&Label(b"akita_setup_key")) jolt-akita::adapters.rs::append_verifier_setup::append#10::transcript.append(&U64Word(akita_chunk_profile.levelsasu64)) @@ -35,20 +23,21 @@ jolt-akita::adapters.rs::append_verifier_setup::append_bytes#2::transcript.appen jolt-akita::native_batching.rs::AkitaNativeBatching::verify_batch::bind_statement_transcripts#0::bind_statement_transcripts(transcript,setup,statement,commitment,point) jolt-akita::native_batching.rs::AkitaNativeBatching::verify_trace_batch::bind_grouped_statement_transcripts#0::bind_grouped_statement_transcripts(transcript,setup,selection,auxiliary_groups,main,) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append#11::transcript.append(&LabelWithCount(b"akita_group_evaluations",claim.evaluations.len()asu64,)) +jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append#12::transcript.append(&evaluation) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append#1::transcript.append(&Label(b"akita_precommit_batch_v4")) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append#3::transcript.append(&LabelWithCount(b"akita_groups",group_countasu64)) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append#4::transcript.append(&U64Word(indexasu64)) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append#6::transcript.append(&U64Word(role_index)) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append#8::transcript.append(&U64Word(u64::from(role.is_some()))) +jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append#9::transcript.append(&claim.commitment) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append_bytes#2::transcript.append_bytes(&serialize_akita(&selection)?) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append_bytes#5::transcript.append_bytes(role.transcript_label()) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append_bytes#7::transcript.append_bytes(b"main_trace") -jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append_to_transcript#12::evaluation.append_to_transcript(transcript) -jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append_to_transcript#9::claim.commitment.append_to_transcript(transcript) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append_values#10::transcript.append_values(b"akita_group_point",&claim.point) jolt-akita::native_batching.rs::bind_grouped_statement_transcripts::append_verifier_setup#0::append_verifier_setup(transcript,setup,AkitaBackendFlavor::OneHot) jolt-akita::native_batching.rs::bind_statement_transcripts::append_batch_statement#1::append_batch_statement(transcript,statement,commitment,point) jolt-akita::native_batching.rs::bind_statement_transcripts::append_verifier_setup#0::append_verifier_setup(transcript,verifier_setup,commitment.backend_flavor) +jolt-blindfold::pow.rs::PowNonces<'a>::check::check_public_grind#0::transcript.check_public_grind(bits,nonce) jolt-blindfold::verify.rs::BlindFoldProtocol::folded_instance_from_proof::append_to_transcript#0::committed.append_to_transcript(transcript,b"bf_committed_u",b"bf_committed_w",b"bf_committed_e",b"bf_committed_eval",) jolt-blindfold::verify.rs::BlindFoldProtocol::folded_instance_from_proof::append_to_transcript#1::random.append_to_transcript(transcript,b"bf_random_u",b"bf_random_w",b"bf_random_e",b"bf_random_eval",) jolt-blindfold::verify.rs::BlindFoldProtocol::folded_instance_from_proof::append_values#2::transcript.append_values(b"bf_cross_e",&proof.cross_term_error_row_commitments) @@ -58,24 +47,20 @@ jolt-blindfold::verify.rs::BlindFoldProtocol::verify_inner_folded_r1cs::a jolt-blindfold::verify.rs::BlindFoldProtocol::verify_outer_folded_r1cs::append#0::transcript.append(&Label(b"bf_spartan")) jolt-blindfold::verify.rs::BlindFoldProtocol::verify_outer_folded_r1cs::append_values#2::transcript.append_values(b"bf_az_bz_cz",&[proof.az_rx,proof.bz_rx,proof.cz_rx]) jolt-blindfold::verify.rs::BlindFoldProtocol::verify_outer_folded_r1cs::append_vector_opening#3::append_vector_opening(transcript,b"bf_error_opening",b"bf_error_blind",&proof.error_opening,) -jolt-blindfold::verify.rs::RelaxedInstance::append_to_transcript::append#0::transcript.append(&Label(u_label)) -jolt-blindfold::verify.rs::RelaxedInstance::append_to_transcript::append_to_transcript#1::self.u.append_to_transcript(transcript) -jolt-blindfold::verify.rs::RelaxedInstance::append_to_transcript::append_values#2::transcript.append_values(witness_label,&self.witness_row_commitments) -jolt-blindfold::verify.rs::RelaxedInstance::append_to_transcript::append_values#3::transcript.append_values(error_label,&self.error_row_commitments) -jolt-blindfold::verify.rs::RelaxedInstance::append_to_transcript::append_values#4::transcript.append_values(eval_label,&self.eval_commitments) -jolt-blindfold::verify.rs::append_vector_opening::append#1::transcript.append(&Label(blinding_label)) -jolt-blindfold::verify.rs::append_vector_opening::append_to_transcript#2::opening.combined_blinding.append_to_transcript(transcript) +jolt-blindfold::verify.rs::RelaxedInstance::append_to_transcript::append_labeled#0::transcript.append_labeled(u_label,&self.u) +jolt-blindfold::verify.rs::RelaxedInstance::append_to_transcript::append_values#1::transcript.append_values(witness_label,&self.witness_row_commitments) +jolt-blindfold::verify.rs::RelaxedInstance::append_to_transcript::append_values#2::transcript.append_values(error_label,&self.error_row_commitments) +jolt-blindfold::verify.rs::RelaxedInstance::append_to_transcript::append_values#3::transcript.append_values(eval_label,&self.eval_commitments) +jolt-blindfold::verify.rs::append_vector_opening::append_labeled#1::transcript.append_labeled(blinding_label,&opening.combined_blinding) jolt-blindfold::verify.rs::append_vector_opening::append_values#0::transcript.append_values(row_label,&opening.combined_vector) -jolt-crypto::ec/bn254/gt.rs::Bn254GT::append_to_transcript::append_bytes#0::transcript.append_bytes(&buf) jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::append_bytes::append#0::self.transcript.append(&LabelWithCount(b"dory_bytes",bytes.len()asu64)) jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::append_bytes::append_bytes#1::self.transcript.append_bytes(bytes) jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::append_field::append#0::self.transcript.append(&Label(b"dory_field")) -jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::append_field::append_to_transcript#1::jolt_scalar.append_to_transcript(self.transcript) +jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::append_field::append#1::self.transcript.append(&jolt_scalar) jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::append_group::append#0::self.transcript.append(&LabelWithCount(b"dory_group",buffer.len()asu64)) jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::append_group::append_bytes#1::self.transcript.append_bytes(&buffer) jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::append_serde::append#0::self.transcript.append(&LabelWithCount(b"dory_serde",buffer.len()asu64)) jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::append_serde::append_bytes#1::self.transcript.append_bytes(&buffer) -jolt-dory::types.rs::DoryCommitment::append_to_transcript::append_to_transcript#0::self.0.append_to_transcript(transcript) jolt-hyperkzg::kzg.rs::HyperKZGProverSetup::open_batch::absorb_evaluations#0::absorb_evaluations(&evaluations,transcript) jolt-hyperkzg::kzg.rs::HyperKZGProverSetup::open_batch::absorb_witnesses#1::absorb_witnesses(&witnesses,transcript) jolt-hyperkzg::kzg.rs::HyperKZGVerifierSetup::verify_batch::absorb_evaluations#0::absorb_evaluations(evaluations,transcript) @@ -84,19 +69,14 @@ jolt-hyperkzg::kzg.rs::absorb_evaluations::append#0::transcript.append(value) jolt-hyperkzg::kzg.rs::absorb_witnesses::append#0::transcript.append(witness) jolt-hyperkzg::scheme.rs::HyperKZGScheme::open_table::append#0::transcript.append(commitment) jolt-hyperkzg::scheme.rs::HyperKZGScheme::verify_opening::append#0::transcript.append(commitment) -jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append#1::transcript.append(&self.g1) -jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append#2::transcript.append(&self.g2) -jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append#3::transcript.append(&self.beta_g2) -jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append#4::transcript.append(commitment) -jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append#6::transcript.append(&evaluation) -jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append_bytes#0::transcript.append_bytes(&self.setup_id) -jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append_values#5::transcript.append_values(b"opening-point",point) -jolt-openings::claims.rs::VerifierRlcClaims<'_,F,C>::append_to_transcript::append#0::transcript.append(&LabelWithCount(b"rlc_claims",self.0.len()asu64)) -jolt-openings::claims.rs::VerifierRlcClaims<'_,F,C>::append_to_transcript::append_to_transcript#1::claim.evaluation.value.append_to_transcript(transcript) -jolt-openings::claims.rs::ZkEvaluationClaim<'_,F,C>::append_to_transcript::append#0::transcript.append(&LabelWithCount(b"zk_opening_point",self.point.len()asu64,)) -jolt-openings::claims.rs::ZkEvaluationClaim<'_,F,C>::append_to_transcript::append#2::transcript.append(&Label(b"zk_eval_commitment")) -jolt-openings::claims.rs::ZkEvaluationClaim<'_,F,C>::append_to_transcript::append_to_transcript#1::coordinate.append_to_transcript(transcript) -jolt-openings::claims.rs::ZkEvaluationClaim<'_,F,C>::append_to_transcript::append_to_transcript#3::self.hiding_commitment.append_to_transcript(transcript) +jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append#2::transcript.append(&self.g1) +jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append#3::transcript.append(&self.g2) +jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append#4::transcript.append(&self.beta_g2) +jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append#5::transcript.append(commitment) +jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append#7::transcript.append(&evaluation) +jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append_bytes#1::transcript.append_bytes(&self.setup_id) +jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append_labeled#0::transcript.append_labeled(b"hyperkzg-binary-bn254-v1",&U64Word(self.num_powers)) +jolt-hyperkzg::types.rs::HyperKZGVerifierSetup::append_statement::append_values#6::transcript.append_values(b"opening-point",point) jolt-openings::prefix.rs::PrefixPackedLayout::reduce_claims::append#0::transcript.append(&Label(b"prefix_packed_claim")) jolt-openings::prefix.rs::PrefixPackedLayout::reduce_claims::append#1::transcript.append(&U64Word(self.logical_num_varsasu64)) jolt-openings::prefix.rs::PrefixPackedLayout::reduce_claims::append#2::transcript.append(&U64Word(self.slot_capacityasu64)) @@ -104,134 +84,88 @@ jolt-openings::prefix.rs::PrefixPackedLayout::reduce_claims::append#3::trans jolt-openings::prefix.rs::PrefixPackedLayout::reduce_claims::append_bytes#4::transcript.append_bytes(&claims.layout_digest) jolt-openings::prefix.rs::PrefixPackedLayout::reduce_claims::append_values#5::transcript.append_values(b"prefix_pack_point",claims.point.as_slice()) jolt-openings::prefix.rs::PrefixPackedLayout::reduce_claims::append_values#6::transcript.append_values(b"prefix_pack_evals",&claims.evaluations) -jolt-openings::schemes.rs::HomomorphicBatch::verify_batch::append_to_transcript#0::statement.append_to_transcript(transcript) -jolt-openings::schemes.rs::HomomorphicBatch::verify_batch::append_to_transcript#2::EvaluationClaim::new(statement.point.clone(),joint_eval).append_to_transcript(transcript) -jolt-openings::schemes.rs::HomomorphicBatch::verify_batch_zk::append_to_transcript#1::ZkEvaluationClaim::new(point.as_slice(),&hiding_commitment).append_to_transcript(transcript) -jolt-openings::schemes.rs::HomomorphicBatchStatement<'_,F,C>::append_to_transcript::append_to_transcript#0::VerifierRlcClaims(self.claims).append_to_transcript(transcript) -jolt-poly::evaluation_claim.rs::EvaluationClaim::append_to_transcript::append#0::transcript.append(&LabelWithCount(b"opening_point",self.point.len()asu64)) -jolt-poly::evaluation_claim.rs::EvaluationClaim::append_to_transcript::append#2::transcript.append(&Label(b"opening_eval")) -jolt-poly::evaluation_claim.rs::EvaluationClaim::append_to_transcript::append_to_transcript#1::coordinate.append_to_transcript(transcript) -jolt-poly::evaluation_claim.rs::EvaluationClaim::append_to_transcript::append_to_transcript#3::self.value.append_to_transcript(transcript) +jolt-openings::schemes.rs::HomomorphicBatch::verify_batch::append#0::transcript.append(&statement) +jolt-openings::schemes.rs::HomomorphicBatch::verify_batch::append#2::transcript.append(&EvaluationClaim::new(statement.point.clone(),joint_eval)) +jolt-openings::schemes.rs::HomomorphicBatch::verify_batch_zk::append#1::transcript.append(&ZkEvaluationClaim::new(point.as_slice(),&hiding_commitment,)) +jolt-spartan-verifier::key.rs::SpartanKey::append_witness_evaluation::append_labeled#0::transcript.append_labeled(b"witness-evaluation",&evaluation) jolt-spartan-verifier::key.rs::SpartanKey::begin::append#0::transcript.append(&Label(b"spartan-clear-v1")) jolt-spartan-verifier::key.rs::SpartanKey::begin::append_bytes#1::transcript.append_bytes(&self.relation_digest) jolt-spartan-verifier::key.rs::SpartanKey::begin::append_bytes#2::transcript.append_bytes(&self.policy_id) +jolt-spartan-verifier::key.rs::SpartanKey::begin::append_labeled#4::transcript.append_labeled(b"witness-commitment",commitment) +jolt-spartan-verifier::key.rs::SpartanKey::begin::append_labeled#6::transcript.append_labeled(b"spartan-outer",&F::zero()) jolt-spartan-verifier::key.rs::SpartanKey::begin::append_values#3::transcript.append_values(b"public-inputs",public_inputs) +jolt-spartan-verifier::key.rs::SpartanKey::begin_inner::append_labeled#4::transcript.append_labeled(b"spartan-inner",&claim) jolt-spartan-verifier::key.rs::SpartanKey::begin_inner::append_values#0::transcript.append_values(b"outer-evaluations",&evaluations) jolt-spartan-verifier::lib.rs::SpartanKey::verify::append_witness_evaluation#0::Self::append_witness_evaluation(proof.witness_evaluation,transcript) -jolt-sumcheck::committed.rs::CommittedOutputClaims::append_to_transcript::append#0::transcript.append(&LabelWithCount(OUTPUT_CLAIMS_LABEL,self.commitments.len()asu64,)) -jolt-sumcheck::committed.rs::CommittedOutputClaims::append_to_transcript::append_to_transcript#1::commitment.append_to_transcript(transcript) jolt-sumcheck::committed.rs::CommittedRound::append_to_transcript::append#0::transcript.append(&LabelWithCount(SUMCHECK_COMMITMENT_LABEL,self.degreeasu64,)) -jolt-sumcheck::committed.rs::CommittedRound::append_to_transcript::append_to_transcript#1::self.commitment.append_to_transcript(transcript) -jolt-sumcheck::committed.rs::CommittedSumcheckBuilder<'a,F,VC,R>::finish::append_to_transcript#0::output_claims.append_to_transcript(transcript) +jolt-sumcheck::committed.rs::CommittedRound::append_to_transcript::append#1::transcript.append(&self.commitment) +jolt-sumcheck::committed.rs::CommittedSumcheckBuilder<'a,F,VC,R>::finish::append#0::transcript.append(&output_claims) +jolt-sumcheck::lib.rs::append_sumcheck_claim::append_labeled#0::transcript.append_labeled(SUMCHECK_CLAIM_TRANSCRIPT_LABEL,claim) +jolt-sumcheck::pow.rs::RoundNonces<'a>::check::check_public_grind#0::transcript.check_public_grind(self.bits,self.nonces.get(round).copied()) jolt-sumcheck::recorder.rs::ClearSumcheckRecorder::absorb_input_claims::append_sumcheck_claim#0::append_sumcheck_claim(transcript,input_claim) jolt-sumcheck::recorder.rs::ClearSumcheckRecorder::absorb_round::append_to_transcript#0::CompressedLabeledRoundPoly::sumcheck(round_poly).append_to_transcript(transcript) +jolt-sumcheck::recorder.rs::ClearSumcheckRecorder::finish::append_labeled#0::transcript.append_labeled(OPENING_CLAIM_TRANSCRIPT_LABEL,opening_claim) jolt-sumcheck::round_proof.rs::CompressedLabeledRoundPoly<'_,F>::append_to_transcript::append#0::transcript.append(&LabelWithCount(self.label,(coeffs.len()-1)asu64)) -jolt-sumcheck::round_proof.rs::CompressedLabeledRoundPoly<'_,F>::append_to_transcript::append_to_transcript#1::constant.append_to_transcript(transcript) -jolt-sumcheck::round_proof.rs::CompressedLabeledRoundPoly<'_,F>::append_to_transcript::append_to_transcript#2::c.append_to_transcript(transcript) +jolt-sumcheck::round_proof.rs::CompressedLabeledRoundPoly<'_,F>::append_to_transcript::append#1::transcript.append(constant) +jolt-sumcheck::round_proof.rs::CompressedLabeledRoundPoly<'_,F>::append_to_transcript::append#2::transcript.append(c) jolt-sumcheck::round_proof.rs::LabeledRoundPoly<'_,F>::append_to_transcript::append#0::transcript.append(&LabelWithCount(self.label,coeffs.len()asu64)) -jolt-sumcheck::round_proof.rs::LabeledRoundPoly<'_,F>::append_to_transcript::append_to_transcript#1::coeff.append_to_transcript(transcript) -jolt-sumcheck::round_proof.rs::UnivariatePoly::append_to_transcript::append_to_transcript#0::coeff.append_to_transcript(transcript) -jolt-sumcheck::verifier.rs::CommittedSumcheckProof::verify_committed_consistency::append_to_transcript#0::self.output_claims.append_to_transcript(transcript) +jolt-sumcheck::round_proof.rs::LabeledRoundPoly<'_,F>::append_to_transcript::append#1::transcript.append(coeff) +jolt-sumcheck::round_proof.rs::UnivariatePoly::append_to_transcript::append#0::transcript.append(coeff) +jolt-sumcheck::verifier.rs::CommittedSumcheckProof::verify_committed_consistency::append#0::transcript.append(&self.output_claims) jolt-sumcheck::verifier.rs::SumcheckVerifier::verify::append_to_transcript#0::round_proof.append_to_transcript(transcript) jolt-sumcheck::verifier.rs::SumcheckVerifier::verify_committed_round_consistency::append_to_transcript#0::round_proof.append_to_transcript(transcript) jolt-sumcheck::verifier.rs::SumcheckVerifier::verify_compressed::append#0::transcript.append(&LabelWithCount(round_label,coeffs.len()asu64)) -jolt-sumcheck::verifier.rs::SumcheckVerifier::verify_compressed::append_to_transcript#1::coeff.append_to_transcript(transcript) -jolt-transcript::legacy.rs::F::append_to_transcript::append_bytes#0::transcript.append_bytes(&buf) -jolt-transcript::legacy.rs::Label::append_to_transcript::append_bytes#0::transcript.append_bytes(&padded) -jolt-transcript::legacy.rs::LabelWithCount::append_to_transcript::append_bytes#0::transcript.append_bytes(&packed) -jolt-transcript::legacy.rs::SpongeTranscript::append_bytes::absorb#0::self.sponge.absorb(&buf) -jolt-transcript::legacy.rs::SpongeTranscript::new::absorb#0::sponge.absorb(BytesMsg(label.to_vec()).encode().as_ref()) -jolt-transcript::legacy.rs::Transcript::append::append_to_transcript#0::value.append_to_transcript(self) -jolt-transcript::legacy.rs::Transcript::append_labeled::append#0::self.append(&Label(label)) -jolt-transcript::legacy.rs::Transcript::append_labeled::append#1::self.append(value) -jolt-transcript::legacy.rs::Transcript::append_values::append#0::self.append(&LabelWithCount(label,values.len()asu64)) -jolt-transcript::legacy.rs::Transcript::append_values::append#1::self.append(value) -jolt-transcript::legacy.rs::Transcript::check_grind::append_bytes#0::self.append_bytes(Leb128::new(nonce).as_bytes()) -jolt-transcript::legacy.rs::TranscriptProbe<'_,T>::probe::append_bytes#0::transcript.append_bytes(candidate) -jolt-transcript::legacy.rs::U64Word::append_to_transcript::append_bytes#0::transcript.append_bytes(&packed) -jolt-transcript::legacy.rs::append_length_prefixed::append#0::transcript.append(&LabelWithCount(label,len)) -jolt-transcript::legacy.rs::append_length_prefixed::append#1::transcript.append(&Label(label)) -jolt-transcript::legacy.rs::append_length_prefixed::append#2::transcript.append(payload) +jolt-sumcheck::verifier.rs::SumcheckVerifier::verify_compressed::append#1::transcript.append(coeff) jolt-transcript::poseidon.rs::PoseidonSponge::absorb::absorb_fr_pair#0::self.absorb_fr_pair(len_fr,Fr::zero()) jolt-transcript::poseidon.rs::PoseidonSponge::absorb::absorb_fr_pair#1::self.absorb_fr_pair(a,b) jolt-transcript::poseidon.rs::PoseidonSponge::init::absorb#0::sponge.absorb(session_id) jolt-transcript::pow.rs::MessageSearch::probe::absorb#0::sponge.absorb(core::slice::from_ref(byte)) -jolt-verifier::stages/stage4/ram_val_check.rs::RamValCheck::draw_challenges::append_ram_val_check_gamma_domain_separator#0::append_ram_val_check_gamma_domain_separator(transcript) -jolt-verifier::stages/stage4/ram_val_check.rs::append_ram_val_check_gamma_domain_separator::append#0::transcript.append(&LabelWithCount(b"ram_val_check_gamma",0)) -jolt-verifier::stages/stage4/ram_val_check.rs::append_ram_val_check_gamma_domain_separator::append_bytes#1::transcript.append_bytes(&[]) +jolt-transcript::scripted.rs::ScriptedTranscript::absorb::absorb#0::self.inner.absorb(message) +jolt-transcript::scripted.rs::ScriptedTranscript::init::init#0::T::init(session,statement,narg) +jolt-transcript::scripted.rs::ScriptedTranscript::init::init#1::T::init(session,statement) +jolt-transcript::transcript.rs::Transcript::append::absorb#0::self.absorb(message) +jolt-transcript::transcript.rs::Transcript::append_bytes::absorb#0::self.absorb(&BytesMsg(bytes)) +jolt-transcript::transcript.rs::Transcript::append_labeled::absorb#0::self.absorb(&Label(label)) +jolt-transcript::transcript.rs::Transcript::append_labeled::absorb#1::self.absorb(message) +jolt-transcript::transcript.rs::Transcript::append_values::absorb#0::self.absorb(&LabelWithCount(label,values.len()asu64)) +jolt-transcript::transcript.rs::Transcript::append_values::absorb#1::self.absorb(value) +jolt-transcript::transcript.rs::Transcript::check_public_grind::absorb#0::self.absorb(byte) +jolt-verifier::stages/pow.rs::check_pow::check_public_grind#0::transcript.check_public_grind(pow_bits::(site,loss)?,nonces.get(site)) +jolt-verifier::stages/relations.rs::OutputAppend::append_openings::append_labeled#0::transcript.append_labeled(b"opening_claim",&value) +jolt-verifier::stages/stage4/outputs.rs::Stage4OutputClaims::append_to_transcript::append_labeled#0::transcript.append_labeled(b"opening_claim",&value) +jolt-verifier::stages/stage4/ram_val_check.rs::RamValCheck::draw_challenges::append#0::transcript.append(&Label(b"ram_val_check_gamma")) jolt-verifier::stages/stage4/verify.rs::verify::append_to_transcript#0::claims.append_to_transcript(transcript) +jolt-verifier::stages/stage6b/verify.rs::append_opening_claims::append_labeled#0::transcript.append_labeled(b"opening_claim",&value) jolt-verifier::stages/stage6b/verify.rs::verify::append_opening_claims#0::append_opening_claims(transcript,claims,&cycle_points.bytecode_read_raf.bytecode_ra,&booleanity_opening_point,) +jolt-verifier::stages/stage8/verify.rs::verify::append#1::transcript.append(&ZkEvaluationClaim::new(pcs_opening_point.as_slice(),&hiding_evaluation_commitment,)) jolt-verifier::stages/stage8/verify.rs::verify::append#2::transcript.append(&LabelWithCount(b"rlc_claims",crate::num::u64_from_usize(opening_claims.len()),)) -jolt-verifier::stages/stage8/verify.rs::verify::append_to_transcript#1::ZkEvaluationClaim::new(pcs_opening_point.as_slice(),&hiding_evaluation_commitment).append_to_transcript(transcript) -jolt-verifier::stages/stage8/verify.rs::verify::append_to_transcript#3::claim.evaluation.value.append_to_transcript(transcript) -jolt-verifier::stages/stage8/verify.rs::verify::append_to_transcript#5::EvaluationClaim::new(pcs_opening_point.clone(),joint_claim).append_to_transcript(transcript) +jolt-verifier::stages/stage8/verify.rs::verify::append#3::transcript.append(&claim.evaluation.value) +jolt-verifier::stages/stage8/verify.rs::verify::append#5::transcript.append(&EvaluationClaim::new(pcs_opening_point.clone(),joint_claim)) +jolt-verifier::stages/uniskip.rs::verify_clear::append_labeled#0::transcript.append_labeled(b"opening_claim",&output_claim) jolt-verifier::verifier.rs::absorb_akita_commitments::absorb_akita_program_commitments#4::absorb_akita_program_commitments(bytecode,image,transcript) -jolt-verifier::verifier.rs::absorb_akita_commitments::append_length_prefixed#0::append_length_prefixed(transcript,b"commitment",one_hot_trace) -jolt-verifier::verifier.rs::absorb_akita_commitments::append_length_prefixed#1::append_length_prefixed(transcript,b"untrusted_advice",commitment) -jolt-verifier::verifier.rs::absorb_akita_commitments::append_length_prefixed#2::append_length_prefixed(transcript,b"trusted_advice",commitment) -jolt-verifier::verifier.rs::absorb_akita_commitments::append_length_prefixed#3::append_length_prefixed(transcript,b"field_inc",commitment) -jolt-verifier::verifier.rs::absorb_akita_program_commitments::append_length_prefixed#0::append_length_prefixed(transcript,b"program_bytecode_commitment",bytecode) -jolt-verifier::verifier.rs::absorb_akita_program_commitments::append_length_prefixed#1::append_length_prefixed(transcript,b"program_image_init_commitment",image) +jolt-verifier::verifier.rs::absorb_akita_commitments::append_labeled#0::transcript.append_labeled(b"commitment",one_hot_trace) +jolt-verifier::verifier.rs::absorb_akita_commitments::append_labeled#1::transcript.append_labeled(b"untrusted_advice",commitment) +jolt-verifier::verifier.rs::absorb_akita_commitments::append_labeled#2::transcript.append_labeled(b"trusted_advice",commitment) +jolt-verifier::verifier.rs::absorb_akita_commitments::append_labeled#3::transcript.append_labeled(b"field_inc",commitment) +jolt-verifier::verifier.rs::absorb_akita_program_commitments::append_labeled#0::transcript.append_labeled(b"program_bytecode_commitment",bytecode) +jolt-verifier::verifier.rs::absorb_akita_program_commitments::append_labeled#1::transcript.append_labeled(b"program_image_init_commitment",image) jolt-verifier::verifier.rs::absorb_commitments::absorb_akita_commitments#2::#[cfg(feature="akita")]absorb_akita_commitments(&proof.commitments,proof.untrusted_advice_commitment.as_ref(),trusted_advice_commitment,#[cfg(feature="field-inline")]proof.field_inc_commitment.as_ref(),preprocessing.program.committed().map(|committed|{(&committed.bytecode_commitment,&committed.program_image_commitment,)}),transcript,) jolt-verifier::verifier.rs::absorb_commitments::absorb_committed_program_commitments#1::absorb_committed_program_commitments(&committed.bytecode_commitment,&committed.program_image_commitment,transcript,) jolt-verifier::verifier.rs::absorb_commitments::absorb_transcript_commitments#0::absorb_transcript_commitments(&proof.commitments,proof.untrusted_advice_commitment.as_ref(),trusted_advice_commitment,transcript,) -jolt-verifier::verifier.rs::absorb_committed_program_commitments::append#1::transcript.append(bytecode_commitment) -jolt-verifier::verifier.rs::absorb_committed_program_commitments::append#3::transcript.append(program_image_commitment) -jolt-verifier::verifier.rs::absorb_committed_program_commitments::append_payload_label#0::append_payload_label(transcript,b"program_bytecode_commit",bytecode_commitment) -jolt-verifier::verifier.rs::absorb_committed_program_commitments::append_payload_label#2::append_payload_label(transcript,b"program_image_commitment",program_image_commitment,) -jolt-verifier::verifier.rs::absorb_labeled_bytes::append#0::transcript.append(&LabelWithCount(label,num::u64_from_usize(bytes.len()))) -jolt-verifier::verifier.rs::absorb_labeled_bytes::append_bytes#1::transcript.append_bytes(bytes) -jolt-verifier::verifier.rs::absorb_labeled_u64::append#0::transcript.append(&Label(label)) -jolt-verifier::verifier.rs::absorb_labeled_u64::append#1::transcript.append(&U64Word(value)) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_bytes#0::absorb_labeled_bytes(transcript,b"preprocessing_digest",&checked.preprocessing_digest,) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_bytes#4::absorb_labeled_bytes(transcript,b"inputs",&public_io.inputs) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_bytes#5::absorb_labeled_bytes(transcript,b"outputs",&public_io.outputs) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#10::absorb_labeled_u64(transcript,b"ram_rw_phase1_num_rounds",u64::from(proof.rw_config.ram_rw_phase1_num_rounds),) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#11::absorb_labeled_u64(transcript,b"ram_rw_phase2_num_rounds",u64::from(proof.rw_config.ram_rw_phase2_num_rounds),) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#12::absorb_labeled_u64(transcript,b"registers_rw_phase1_num_rounds",u64::from(proof.rw_config.registers_rw_phase1_num_rounds),) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#13::absorb_labeled_u64(transcript,b"registers_rw_phase2_num_rounds",u64::from(proof.rw_config.registers_rw_phase2_num_rounds),) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#14::absorb_labeled_u64(transcript,b"log_k_chunk",u64::from(proof.one_hot_config.log_k_chunk),) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#15::absorb_labeled_u64(transcript,b"lookups_ra_virtual_log_k_chunk",u64::from(proof.one_hot_config.lookups_ra_virtual_log_k_chunk),) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#16::absorb_labeled_u64(transcript,b"dory_layout",proof.trace_polynomial_order.transcript_scalar(),) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#1::absorb_labeled_u64(transcript,b"max_input_size",public_io.memory_layout.max_input_size,) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#2::absorb_labeled_u64(transcript,b"max_output_size",public_io.memory_layout.max_output_size,) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#3::absorb_labeled_u64(transcript,b"heap_size",public_io.memory_layout.heap_size) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#6::absorb_labeled_u64(transcript,b"panic",u64::from(public_io.panic)) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#7::absorb_labeled_u64(transcript,b"ram_K",num::u64_from_usize(checked.ram_K)) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#8::absorb_labeled_u64(transcript,b"trace_length",num::u64_from_usize(checked.trace_length),) -jolt-verifier::verifier.rs::absorb_preamble::absorb_labeled_u64#9::absorb_labeled_u64(transcript,b"entry_address",checked.entry_address) -jolt-verifier::verifier.rs::absorb_transcript_commitments::absorb_commitment#2::absorb_commitment(&commitments.rd_inc) -jolt-verifier::verifier.rs::absorb_transcript_commitments::absorb_commitment#3::absorb_commitment(&commitments.ram_inc) +jolt-verifier::verifier.rs::absorb_committed_program_commitments::append_labeled#0::transcript.append_labeled(b"program_bytecode_commit",bytecode_commitment) +jolt-verifier::verifier.rs::absorb_committed_program_commitments::append_labeled#1::transcript.append_labeled(b"program_image_commitment",program_image_commitment) +jolt-verifier::verifier.rs::absorb_transcript_commitments::absorb_commitment#1::absorb_commitment(&commitments.rd_inc) +jolt-verifier::verifier.rs::absorb_transcript_commitments::absorb_commitment#2::absorb_commitment(&commitments.ram_inc) +jolt-verifier::verifier.rs::absorb_transcript_commitments::absorb_commitment#3::absorb_commitment(commitment) jolt-verifier::verifier.rs::absorb_transcript_commitments::absorb_commitment#4::absorb_commitment(commitment) jolt-verifier::verifier.rs::absorb_transcript_commitments::absorb_commitment#5::absorb_commitment(commitment) -jolt-verifier::verifier.rs::absorb_transcript_commitments::absorb_commitment#6::absorb_commitment(commitment) -jolt-verifier::verifier.rs::absorb_transcript_commitments::append#10::transcript.append(untrusted_advice_commitment) -jolt-verifier::verifier.rs::absorb_transcript_commitments::append#12::transcript.append(trusted_advice_commitment) -jolt-verifier::verifier.rs::absorb_transcript_commitments::append#1::transcript.append(commitment) -jolt-verifier::verifier.rs::absorb_transcript_commitments::append#8::transcript.append(&field_inline.field_registers.rd_inc) -jolt-verifier::verifier.rs::absorb_transcript_commitments::append_payload_label#0::append_payload_label(transcript,b"commitment",commitment) -jolt-verifier::verifier.rs::absorb_transcript_commitments::append_payload_label#11::append_payload_label(transcript,b"trusted_advice",trusted_advice_commitment) -jolt-verifier::verifier.rs::absorb_transcript_commitments::append_payload_label#7::append_payload_label(transcript,b"field_rd_inc_commitment",&field_inline.field_registers.rd_inc,) -jolt-verifier::verifier.rs::absorb_transcript_commitments::append_payload_label#9::append_payload_label(transcript,b"untrusted_advice",untrusted_advice_commitment) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_bytes#0::absorb_labeled_bytes(transcript,b"preprocessing_digest",&checked.preprocessing_digest,) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_bytes#4::absorb_labeled_bytes(transcript,b"inputs",&public_io.inputs) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_bytes#5::absorb_labeled_bytes(transcript,b"outputs",&public_io.outputs) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#10::absorb_labeled_u64(transcript,b"ram_rw_phase1_num_rounds",u64::from(config.rw_config.ram_rw_phase1_num_rounds),) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#11::absorb_labeled_u64(transcript,b"ram_rw_phase2_num_rounds",u64::from(config.rw_config.ram_rw_phase2_num_rounds),) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#12::absorb_labeled_u64(transcript,b"registers_rw_phase1_num_rounds",u64::from(config.rw_config.registers_rw_phase1_num_rounds),) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#13::absorb_labeled_u64(transcript,b"registers_rw_phase2_num_rounds",u64::from(config.rw_config.registers_rw_phase2_num_rounds),) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#14::absorb_labeled_u64(transcript,b"log_k_chunk",u64::from(config.one_hot_config.log_k_chunk),) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#15::absorb_labeled_u64(transcript,b"lookups_ra_virtual_log_k_chunk",u64::from(config.one_hot_config.lookups_ra_virtual_log_k_chunk),) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#16::absorb_labeled_u64(transcript,b"dory_layout",config.trace_polynomial_order.transcript_scalar(),) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#1::absorb_labeled_u64(transcript,b"max_input_size",public_io.memory_layout.max_input_size,) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#2::absorb_labeled_u64(transcript,b"max_output_size",public_io.memory_layout.max_output_size,) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#3::absorb_labeled_u64(transcript,b"heap_size",public_io.memory_layout.heap_size) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#6::absorb_labeled_u64(transcript,b"panic",u64::from(public_io.panic)) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#7::absorb_labeled_u64(transcript,b"ram_K",num::u64_from_usize(checked.ram_K)) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#8::absorb_labeled_u64(transcript,b"trace_length",num::u64_from_usize(checked.trace_length),) -jolt-verifier::verifier.rs::absorb_transcript_preamble::absorb_labeled_u64#9::absorb_labeled_u64(transcript,b"entry_address",checked.entry_address) -jolt-verifier::verifier.rs::append_payload_label::append#0::transcript.append(&LabelWithCount(label,len)) -jolt-verifier::verifier.rs::append_payload_label::append#1::transcript.append(&Label(label)) -jolt-verifier::verifier.rs::validate_and_seed_transcript::absorb_commitments#1::absorb_commitments(preprocessing,proof,trusted_advice_commitment,&muttranscript,) -jolt-verifier::verifier.rs::validate_and_seed_transcript::absorb_preamble#0::absorb_preamble(&checked,proof,&muttranscript) -jolt-verifier::verifier.rs::verify::append#0::transcript.append(&Label(b"BlindFold")) +jolt-verifier::verifier.rs::absorb_transcript_commitments::append_labeled#0::transcript.append_labeled(b"commitment",commitment) +jolt-verifier::verifier.rs::absorb_transcript_commitments::append_labeled#6::transcript.append_labeled(b"field_rd_inc_commitment",&field_inline.field_registers.rd_inc,) +jolt-verifier::verifier.rs::absorb_transcript_commitments::append_labeled#7::transcript.append_labeled(b"untrusted_advice",untrusted_advice_commitment) +jolt-verifier::verifier.rs::absorb_transcript_commitments::append_labeled#8::transcript.append_labeled(b"trusted_advice",trusted_advice_commitment) +jolt-verifier::verifier.rs::validate_and_seed_transcript::absorb_commitments#0::absorb_commitments(preprocessing,proof,trusted_advice_commitment,&muttranscript,) +jolt-verifier::verifier.rs::verify::append#2::transcript.append(&Label(b"BlindFold")) +jolt-verifier::verifier.rs::verify::init#1::T::init(JOLT_SESSION,statement,&proof.narg) +jolt-verifier::verifier.rs::verify::init#4::T::init(JOLT_SESSION,statement,&proof.narg) +jolt-verifier::verifier.rs::verify::validate_and_seed_transcript#0::validate_and_seed_transcript(preprocessing,public_io,proof,trusted_advice_commitment,|statement|T::init(JOLT_SESSION,statement,&proof.narg),) +jolt-verifier::verifier.rs::verify::validate_and_seed_transcript#3::validate_and_seed_transcript(preprocessing,public_io,proof,trusted_advice_commitment,|statement|T::init(JOLT_SESSION,statement,&proof.narg),) diff --git a/crates/jolt-verifier/tests/fs_inventory/challenge-sites.inventory b/crates/jolt-verifier/tests/fs_inventory/challenge-sites.inventory index 5b2bc95caf..a9dc9c5d6e 100644 --- a/crates/jolt-verifier/tests/fs_inventory/challenge-sites.inventory +++ b/crates/jolt-verifier/tests/fs_inventory/challenge-sites.inventory @@ -1,22 +1,22 @@ # Generated by `JOLT_FS_BLESS=1 cargo nextest run -p jolt-verifier \ # --test fs_obligations --features fs-audit`. # Review changes; this inventories identities, not transcript values. -jolt-akita::adapters.rs::bridged_akita_session::challenge_scalar#0 +jolt-akita::adapters.rs::bridged_akita_session::challenge#0 jolt-blindfold::verify.rs::BlindFoldProtocol::folded_instance_from_proof::challenge#3 jolt-blindfold::verify.rs::BlindFoldProtocol::verify_inner_folded_r1cs::challenge#0 jolt-blindfold::verify.rs::BlindFoldProtocol::verify_inner_folded_r1cs::challenge#1 jolt-blindfold::verify.rs::BlindFoldProtocol::verify_inner_folded_r1cs::challenge#2 jolt-blindfold::verify.rs::BlindFoldProtocol::verify_outer_folded_r1cs::challenge_vector#1 jolt-claims::r1cs.rs::lower_claim_expr::challenge#0 -jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::challenge_scalar::challenge_scalar#0 -jolt-hyperkzg::kzg.rs::absorb_evaluations::challenge#1 +jolt-dory::transcript.rs::JoltToDoryTranscript<'_,T>::challenge_scalar::challenge#0 +jolt-hyperkzg::kzg.rs::absorb_evaluations::challenge_powers#1 jolt-hyperkzg::kzg.rs::absorb_witnesses::challenge#1 jolt-hyperkzg::scheme.rs::HyperKZGScheme::open_table::challenge#1 jolt-hyperkzg::scheme.rs::HyperKZGScheme::verify_opening::challenge#1 jolt-openings::prefix.rs::PrefixPackedLayout::reduce_claims::challenge_vector#7 -jolt-openings::schemes.rs::HomomorphicBatch::verify_batch::challenge_scalar_powers#1 -jolt-openings::schemes.rs::HomomorphicBatch::verify_batch_zk::challenge_scalar_powers#0 -jolt-spartan-verifier::key.rs::SpartanKey::begin::challenge_vector#4 +jolt-openings::schemes.rs::HomomorphicBatch::verify_batch::challenge_powers#1 +jolt-openings::schemes.rs::HomomorphicBatch::verify_batch_zk::challenge_powers#0 +jolt-spartan-verifier::key.rs::SpartanKey::begin::challenge_vector#5 jolt-spartan-verifier::key.rs::SpartanKey::begin_inner::challenge#1 jolt-spartan-verifier::key.rs::SpartanKey::begin_inner::challenge#2 jolt-spartan-verifier::key.rs::SpartanKey::begin_inner::challenge#3 @@ -25,28 +25,27 @@ jolt-sumcheck::recorder.rs::ClearSumcheckRecorder::absorb_round::challenge#1 jolt-sumcheck::verifier.rs::SumcheckVerifier::verify::challenge#1 jolt-sumcheck::verifier.rs::SumcheckVerifier::verify_committed_round_consistency::challenge#1 jolt-sumcheck::verifier.rs::SumcheckVerifier::verify_compressed::challenge#2 -jolt-transcript::legacy.rs::Transcript::challenge_scalar::challenge#0 -jolt-transcript::legacy.rs::Transcript::challenge_scalar_powers::challenge_scalar#0 -jolt-transcript::legacy.rs::Transcript::challenge_vector::challenge#0 -jolt-transcript::unpriced.rs::UnpricedTranscript::challenge::challenge#0 -jolt-transcript::unpriced.rs::UnpricedTranscript::challenge_scalar::challenge_scalar#0 -jolt-transcript::unpriced.rs::UnpricedTranscript::challenge_scalar_powers::challenge_scalar_powers#0 -jolt-transcript::unpriced.rs::UnpricedTranscript::challenge_vector::challenge_vector#0 -jolt-verifier::stages/relations.rs::ConcreteSumcheck::draw_challenges::challenge_scalar#0 +jolt-transcript::scripted.rs::ScriptedTranscript::challenge_vector::challenge_vector#0 +jolt-transcript::transcript.rs::FieldTranscript::challenge::challenge_vector#0 +jolt-transcript::transcript.rs::FieldTranscript::challenge_powers::challenge#0 +jolt-verifier::stages/relations.rs::ConcreteSumcheck::draw_challenges::challenge#0 jolt-verifier::stages/stage1/outputs.rs::Stage1BatchSumchecks::::batching_coefficient[*] jolt-verifier::stages/stage2/outputs.rs::Stage2BatchSumchecks::::batching_coefficient[*] jolt-verifier::stages/stage2/ram_output_check.rs::RamOutputCheck::draw_challenges::challenge#0 jolt-verifier::stages/stage3/outputs.rs::Stage3Sumchecks::::batching_coefficient[*] jolt-verifier::stages/stage4/outputs.rs::Stage4Sumchecks::::batching_coefficient[*] -jolt-verifier::stages/stage4/ram_val_check.rs::RamValCheck::draw_challenges::challenge_scalar#1 +jolt-verifier::stages/stage4/ram_val_check.rs::RamValCheck::draw_challenges::challenge#1 jolt-verifier::stages/stage5/outputs.rs::Stage5Sumchecks::::batching_coefficient[*] jolt-verifier::stages/stage6a/booleanity.rs::BooleanityAddressPhase::draw_challenges::challenge#1 jolt-verifier::stages/stage6a/booleanity.rs::BooleanityAddressPhase::draw_challenges::challenge_vector#0 jolt-verifier::stages/stage6a/outputs.rs::Stage6aSumchecks::::batching_coefficient[*] -jolt-verifier::stages/stage6b/batch.rs::Stage6bDraws::draw::challenge_scalar#0 +jolt-verifier::stages/stage6b/batch.rs::Stage6bDraws::draw::challenge#0 +jolt-verifier::stages/stage6b/batch.rs::Stage6bDraws::draw::challenge#1 +jolt-verifier::stages/stage6b/batch.rs::Stage6bDraws::draw::challenge#2 +jolt-verifier::stages/stage6b/batch.rs::Stage6bDraws::draw::challenge#3 jolt-verifier::stages/stage6b/outputs.rs::Stage6bSumchecks::::batching_coefficient[*] jolt-verifier::stages/stage7/outputs.rs::Stage7Sumchecks::::batching_coefficient[*] -jolt-verifier::stages/stage8/verify.rs::verify::challenge_scalar_powers#0 -jolt-verifier::stages/stage8/verify.rs::verify::challenge_scalar_powers#4 +jolt-verifier::stages/stage8/verify.rs::verify::challenge_powers#0 +jolt-verifier::stages/stage8/verify.rs::verify::challenge_powers#4 jolt-verifier::stages/uniskip.rs::draw_spartan_outer_tau::challenge_vector#0 jolt-verifier::stages/uniskip.rs::draw_spartan_product_tau_high::challenge#0 diff --git a/crates/jolt-verifier/tests/fs_inventory/scope-sites.inventory b/crates/jolt-verifier/tests/fs_inventory/scope-sites.inventory index 3200cf7b3b..94dec3175e 100644 --- a/crates/jolt-verifier/tests/fs_inventory/scope-sites.inventory +++ b/crates/jolt-verifier/tests/fs_inventory/scope-sites.inventory @@ -11,5 +11,4 @@ jolt-verifier::stages/stage6b/verify.rs::verify::jolt_verifier_derive::fs_scope( jolt-verifier::stages/stage7/verify.rs::verify::jolt_verifier_derive::fs_scope(Stage7) jolt-verifier::stages/stage8/verify.rs::verify::jolt_verifier_derive::fs_scope(Stage8) jolt-verifier::verifier.rs::absorb_commitments::jolt_verifier_derive::fs_scope(Commitments) -jolt-verifier::verifier.rs::absorb_preamble::jolt_verifier_derive::fs_scope(Preamble) jolt-verifier::verifier.rs::verify::fs_scope_guard(BlindFold) diff --git a/crates/jolt-verifier/tests/fs_inventory/source-schema.inventory b/crates/jolt-verifier/tests/fs_inventory/source-schema.inventory index 2a3a16ff8c..7ca3b9efb7 100644 --- a/crates/jolt-verifier/tests/fs_inventory/source-schema.inventory +++ b/crates/jolt-verifier/tests/fs_inventory/source-schema.inventory @@ -71,6 +71,7 @@ jolt-verifier::proof.rs::JoltProof.claims jolt-verifier::proof.rs::JoltProof.commitments jolt-verifier::proof.rs::JoltProof.field_inc_commitment jolt-verifier::proof.rs::JoltProof.joint_opening_proof +jolt-verifier::proof.rs::JoltProof.narg jolt-verifier::proof.rs::JoltProof.one_hot_config jolt-verifier::proof.rs::JoltProof.protocol jolt-verifier::proof.rs::JoltProof.ram_K diff --git a/crates/jolt-verifier/tests/fs_obligations.rs b/crates/jolt-verifier/tests/fs_obligations.rs index 5a20f88498..44be194ffa 100644 --- a/crates/jolt-verifier/tests/fs_obligations.rs +++ b/crates/jolt-verifier/tests/fs_obligations.rs @@ -468,7 +468,11 @@ impl<'ast> Visit<'ast> for InventoryVisitor { .to_token_stream() .to_string() .replace(' ', ""); - if name == "append_to_transcript" + // Building a transcript from the statement binds the instance. + let binds_statement = + (name == "init" || name.ends_with("_transcript")) && arguments.contains("statement"); + if binds_statement + || name == "append_to_transcript" || name.starts_with("absorb") || ((name.starts_with("append_") || name.starts_with("bind_")) && arguments.to_ascii_lowercase().contains("transcript")) @@ -554,14 +558,19 @@ fn prover_only_name(name: &str) -> bool { fn is_challenge_call(name: &str) -> bool { matches!( name, - "challenge" | "challenge_scalar" | "challenge_vector" | "challenge_scalar_powers" + "challenge" | "challenge_vector" | "challenge_powers" ) } fn is_absorb_method(name: &str) -> bool { matches!( name, - "append" | "append_bytes" | "append_to_transcript" | "append_values" + "append" + | "append_bytes" + | "append_labeled" + | "append_to_transcript" + | "append_values" + | "check_public_grind" ) } From b6c5f2bd976e6aeb2cd26202a88d6936219e56db Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Tue, 20 Oct 2026 23:51:58 -0400 Subject: [PATCH 07/16] refactor(jolt): port the SDK, examples, jolt-eval and fuzz targets to the spongefish transcript Keccak is the protocol sponge, Blake2b512 behind transcript-blake2b; the Poseidon protocol selection is removed until the transcript absorbs native Fr. --- crates/jolt-kernels/src/reference/naive.rs | 8 +- crates/jolt-spartan-prover/src/lib.rs | 2 +- crates/jolt-spartan-prover/tests/spartan.rs | 5 +- crates/jolt-sumcheck/README.md | 8 +- crates/jolt-transcript/fuzz/Cargo.toml | 1 + .../fuzz/fuzz_targets/encoding_injectivity.rs | 33 +++-- .../fuzz_targets/transcript_determinism.rs | 47 +++--- crates/jolt-verifier/Cargo.toml | 16 +-- crates/jolt-verifier/src/stages/relations.rs | 8 +- .../src/stages/stage2/outputs.rs | 2 +- .../src/stages/stage2/ram_output_check.rs | 5 +- .../jolt-verifier/src/stages/stage2/verify.rs | 2 +- .../jolt-verifier/src/stages/stage3/verify.rs | 2 +- .../src/stages/stage4/outputs.rs | 2 +- .../jolt-verifier/src/stages/stage4/verify.rs | 2 +- .../src/stages/stage5/outputs.rs | 5 +- .../src/stages/stage6a/booleanity.rs | 6 +- .../src/stages/stage6a/verify.rs | 13 +- .../src/stages/stage6b/verify.rs | 2 +- .../jolt-verifier/src/stages/stage7/verify.rs | 2 +- .../jolt-verifier/src/stages/stage8/verify.rs | 9 +- crates/jolt-verifier/src/stages/uniskip.rs | 5 +- crates/jolt-verifier/src/verifier.rs | 59 +++++--- crates/jolt-verifier/tests/fs_attacks.rs | 135 ++++++------------ crates/jolt-verifier/tests/fs_obligations.rs | 5 +- .../tests/soundness/tampering/akita.rs | 2 +- .../tests/support/akita_fixtures.rs | 6 +- examples/fibonacci/Cargo.toml | 2 - examples/field-ops/src/main.rs | 8 +- examples/merkle-tree-save/Cargo.toml | 2 - examples/merkle-tree-save/src/main.rs | 2 +- examples/recursion/guest/src/lib.rs | 2 +- examples/recursion/src/main.rs | 4 +- jolt-eval/Cargo.toml | 1 + jolt-eval/src/guests/mod.rs | 6 +- .../src/invariant/transcript_symmetry.rs | 4 +- jolt-sdk/Cargo.toml | 7 +- jolt-sdk/macros/src/lib.rs | 4 +- jolt-sdk/src/host_utils.rs | 54 ++----- jolt-sdk/tests/verifier_api.rs | 2 +- src/build_wasm.rs | 2 +- zklean-extractor/Cargo.toml | 3 +- zklean-extractor/src/mle_ast.rs | 9 ++ 43 files changed, 219 insertions(+), 285 deletions(-) diff --git a/crates/jolt-kernels/src/reference/naive.rs b/crates/jolt-kernels/src/reference/naive.rs index c62deaed30..4bf1cd16e1 100644 --- a/crates/jolt-kernels/src/reference/naive.rs +++ b/crates/jolt-kernels/src/reference/naive.rs @@ -453,7 +453,7 @@ mod tests { ProveRounds, SequentialRounds, SumcheckProof, SumcheckRecorder, OPENING_CLAIM_TRANSCRIPT_LABEL, }; - use jolt_transcript::{prover_transcript, Blake2b512, Transcript}; + use jolt_transcript::{prover_transcript, Blake2b512, FieldTranscript, Transcript}; use jolt_verifier::stages::relations::ConcreteSumcheck; use jolt_verifier::VerifierError; @@ -668,7 +668,7 @@ mod tests { assert_eq!(input_claim, claimed_sum); let mut recorder = ClearSumcheckRecorder::::new(); recorder.absorb_input_claims(&[input_claim], &mut prover_state); - let coefficient: Fr = prover_state.challenge()?; + let coefficient: Fr = prover_state.challenge().unwrap(); let prelude = BatchPrelude::new( vec![BatchMember { input_claim, @@ -744,7 +744,7 @@ mod tests { .input_claim(&inputs, &verifier_challenges) .unwrap(); append_sumcheck_claim(&mut verifier_transcript, &verifier_input_claim); - let verifier_coefficient: Fr = verifier_transcript.challenge()?; + let verifier_coefficient: Fr = verifier_transcript.challenge().unwrap(); let reduction = SumcheckProof::::from(recorded.proof) .verify_compressed_boolean( ROUNDS, @@ -784,7 +784,7 @@ mod tests { let derived_tables = derived_tables(&drifted_point); let claimed_sum = brute_force_sum(&opening_tables, &derived_tables, gamma); - let coefficient: Fr = transcript.challenge()?; + let coefficient: Fr = transcript.challenge().unwrap(); let prelude = BatchPrelude::new( vec![BatchMember { input_claim: claimed_sum, diff --git a/crates/jolt-spartan-prover/src/lib.rs b/crates/jolt-spartan-prover/src/lib.rs index 4c32f50120..c45422badc 100644 --- a/crates/jolt-spartan-prover/src/lib.rs +++ b/crates/jolt-spartan-prover/src/lib.rs @@ -32,7 +32,7 @@ pub fn prove( public_inputs: &[PCS::Field], witness: &[PCS::Field], pcs_setup: &PCS::ProverSetup, - transcript: &mut impl FieldTranscript, + transcript: &mut (impl FieldTranscript + ProverTranscript), ) -> Result, SpartanError> where PCS::Field: Encoding, diff --git a/crates/jolt-spartan-prover/tests/spartan.rs b/crates/jolt-spartan-prover/tests/spartan.rs index 34aac1a439..b1738e3148 100644 --- a/crates/jolt-spartan-prover/tests/spartan.rs +++ b/crates/jolt-spartan-prover/tests/spartan.rs @@ -14,7 +14,7 @@ use jolt_r1cs::ConstraintMatrices; use jolt_spartan_prover::prove; use jolt_spartan_verifier::{SpartanError, SpartanKey}; use jolt_sumcheck::SumcheckError; -use jolt_transcript::{prover_transcript, Blake2b512, Encoding, ProverState, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, Encoding, FieldTranscript, ProverState}; fn matrices() -> ConstraintMatrices { let one = Fr::one(); @@ -70,7 +70,8 @@ where let proof = prove::(&key, &public(), &witness(), pk, &mut pt).unwrap(); let mut vt = transcript(); key.verify::(&public(), &proof, vk, &mut vt).unwrap(); - assert_eq!(pt.challenge().unwrap(), vt.challenge().unwrap()); + let (prover_next, verifier_next): (Fr, Fr) = (pt.challenge().unwrap(), vt.challenge().unwrap()); + assert_eq!(prover_next, verifier_next); let bytes = bincode::serde::encode_to_vec(&proof, bincode::config::standard()).unwrap(); let (decoded, used) = bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap(); diff --git a/crates/jolt-sumcheck/README.md b/crates/jolt-sumcheck/README.md index 859f3f7fe8..e6811807ef 100644 --- a/crates/jolt-sumcheck/README.md +++ b/crates/jolt-sumcheck/README.md @@ -17,10 +17,10 @@ pool. Its default feature set is empty. Polynomial storage, compression, round generation, and the shared prover and verifier engines require `jolt_field::Field`. The stock clear recorder and -proof-verification conveniences additionally require -`jolt_transcript::AppendToTranscript`, because they absorb field elements. -Implement `jolt_transcript::Transcript` to choose challenge generation; stock -hash transcripts retain their `CanonicalEncoding` requirement. Jolt's own +proof-verification conveniences additionally require spongefish's +`Encoding`, because they absorb field elements. Challenges come from +`jolt_transcript::FieldTranscript`, which spongefish's prover and verifier +states implement through the field's `ChallengeCodec`. Jolt's own optimized kernels and commitment-backed modes may require the broader `JoltField` bundle at their integration boundary. diff --git a/crates/jolt-transcript/fuzz/Cargo.toml b/crates/jolt-transcript/fuzz/Cargo.toml index fb0d5a0d17..3d08f71db5 100644 --- a/crates/jolt-transcript/fuzz/Cargo.toml +++ b/crates/jolt-transcript/fuzz/Cargo.toml @@ -38,6 +38,7 @@ jolt-transcript = { path = "..", features = [ "transcript-keccak", "transcript-poseidon", ] } +spongefish = "=0.8.0" [[bin]] name = "transcript_determinism" diff --git a/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs b/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs index 31194d819e..17600e1fd6 100644 --- a/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs +++ b/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs @@ -5,15 +5,15 @@ //! chunk boundaries must reach different transcript states. //! //! Each chunk is absorbed the way production code absorbs variable-length -//! payloads — a `LabelWithCount` frame followed by the payload — so a -//! boundary move changes the framing and MUST change the state. A matching -//! state would mean the framing convention fails to separate `[ab]` from -//! `[a][b]`, the classic transcript-malleability footgun. +//! payloads — `append_bytes`, a length-prefixed `BytesMsg` — so a boundary +//! move changes the framing and MUST change the state. A matching state would +//! mean the framing convention fails to separate `[ab]` from `[a][b]`, the +//! classic transcript-malleability footgun. The state is observed as the next +//! 32 squeezed bytes. -use jolt_transcript::{ - Blake2bTranscript, KeccakTranscript, LabelWithCount, PoseidonTranscript, Transcript, -}; +use jolt_transcript::{prover_transcript, Blake2b512, Keccak, PoseidonSponge, Transcript}; use libfuzzer_sys::fuzz_target; +use spongefish::DuplexSpongeInit; const MAX_CHUNKS: usize = 16; @@ -32,13 +32,12 @@ fn parse_chunks(data: &[u8]) -> Vec<&[u8]> { chunks } -fn framed_state(chunks: &[Vec]) -> [u8; 32] { - let mut transcript = T::new(b"fuzz-injectivity"); +fn framed_state>(chunks: &[Vec]) -> [u8; 32] { + let mut transcript = prover_transcript::(b"fuzz-injectivity", b""); for chunk in chunks { - transcript.append(&LabelWithCount(b"chunk", chunk.len() as u64)); transcript.append_bytes(chunk); } - transcript.state() + transcript.verifier_messages::() } fuzz_target!(|data: &[u8]| { @@ -75,18 +74,18 @@ fuzz_target!(|data: &[u8]| { debug_assert_eq!(original.concat(), morphed.concat()); assert_ne!( - framed_state::(&original), - framed_state::(&morphed), + framed_state::(&original), + framed_state::(&morphed), "Blake2b framed absorption is not boundary-injective" ); assert_ne!( - framed_state::(&original), - framed_state::(&morphed), + framed_state::(&original), + framed_state::(&morphed), "Keccak framed absorption is not boundary-injective" ); assert_ne!( - framed_state::(&original), - framed_state::(&morphed), + framed_state::(&original), + framed_state::(&morphed), "Poseidon framed absorption is not boundary-injective" ); }); diff --git a/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs b/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs index 80f17ae436..b580ba53d2 100644 --- a/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs +++ b/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs @@ -1,16 +1,16 @@ #![no_main] -//! Determinism and absorption oracles over the digest transcripts. +//! Determinism and absorption oracles over the transcript sponges. //! -//! The old `transcript_no_panic` target asserted nothing on a surface with no -//! panic conditions. Instead, twin instances driven by the same fuzzer-chosen -//! op sequence must agree on every challenge and on the final `state()`, and -//! replaying the sequence with one absorbed byte flipped must reach a -//! different final state — a matching state would mean absorbed bytes are +//! Twin instances driven by the same fuzzer-chosen op sequence must agree on +//! every squeeze and on the final state, observed as the next 32 squeezed +//! bytes, and replaying the sequence with one absorbed byte flipped must reach +//! a different final state — a matching state would mean absorbed bytes are //! ignored or collide. -use jolt_transcript::{Blake2bTranscript, KeccakTranscript, PoseidonTranscript, Transcript}; +use jolt_transcript::{prover_transcript, Blake2b512, Keccak, PoseidonSponge, Transcript}; use libfuzzer_sys::fuzz_target; +use spongefish::DuplexSpongeInit; const MAX_OPS: usize = 32; @@ -41,8 +41,11 @@ fn parse_ops(data: &[u8]) -> Vec> { /// Replays `ops`, optionally XOR-ing `mask` into the absorbed byte at global /// offset `position`. Returns the challenge stream and the final state. -fn run(ops: &[Op<'_>], flip: Option<(usize, u8)>) -> (Vec, [u8; 32]) { - let mut transcript = T::new(b"fuzz-determinism"); +fn run>( + ops: &[Op<'_>], + flip: Option<(usize, u8)>, +) -> (Vec<[u8; 16]>, [u8; 32]) { + let mut transcript = prover_transcript::(b"fuzz-determinism", b""); let mut challenges = Vec::new(); let mut absorbed = 0usize; for op in ops { @@ -60,25 +63,27 @@ fn run(ops: &[Op<'_>], flip: Option<(usize, u8)>) -> (Vec challenges.push(transcript.challenge()), + Op::Challenge => challenges.push(transcript.verifier_messages::()), } } - (challenges, transcript.state()) + (challenges, transcript.verifier_messages::()) } -fn check(ops: &[Op<'_>], total_absorbed: usize, position: usize, mask: u8) -where - T::Challenge: PartialEq, -{ - let (challenges_a, state_a) = run::(ops, None); - let (challenges_b, state_b) = run::(ops, None); +fn check>( + ops: &[Op<'_>], + total_absorbed: usize, + position: usize, + mask: u8, +) { + let (challenges_a, state_a) = run::(ops, None); + let (challenges_b, state_b) = run::(ops, None); assert!( challenges_a == challenges_b && state_a == state_b, "transcript replay is nondeterministic" ); if total_absorbed > 0 && mask != 0 { - let (_, state_flipped) = run::(ops, Some((position % total_absorbed, mask))); + let (_, state_flipped) = run::(ops, Some((position % total_absorbed, mask))); assert_ne!( state_a, state_flipped, "flipping an absorbed byte did not change the transcript state" @@ -101,7 +106,7 @@ fuzz_target!(|data: &[u8]| { }) .sum(); - check::(&ops, total_absorbed, position, mask); - check::(&ops, total_absorbed, position, mask); - check::(&ops, total_absorbed, position, mask); + check::(&ops, total_absorbed, position, mask); + check::(&ops, total_absorbed, position, mask); + check::(&ops, total_absorbed, position, mask); }); diff --git a/crates/jolt-verifier/Cargo.toml b/crates/jolt-verifier/Cargo.toml index b2654c59ec..dde1e8e066 100644 --- a/crates/jolt-verifier/Cargo.toml +++ b/crates/jolt-verifier/Cargo.toml @@ -12,7 +12,6 @@ categories = ["cryptography"] workspace = true [features] -default = ["transcript-blake2b"] # Heap flamegraph visitation of the stage clear-output carriers (profile # harness's per-stage SVGs). allocative = ["dep:allocative", "jolt-claims/allocative"] @@ -34,15 +33,6 @@ zk = ["jolt-prover/zk"] # objects share one batch opening. Mutually exclusive with # `zk` — one PIOP per compiled verifier. akita = ["jolt-prover/akita", "jolt-claims/akita", "tracer/fp128-field-inline"] -transcript-blake2b = [ - "jolt-transcript/bn254", - "jolt-transcript/transcript-blake2b", -] -transcript-keccak = [ - "jolt-transcript/bn254", - "jolt-transcript/transcript-keccak", -] -transcript-poseidon = ["jolt-transcript/transcript-poseidon"] [dependencies] allocative = { workspace = true, optional = true } @@ -68,7 +58,11 @@ serde.workspace = true thiserror.workspace = true [dev-dependencies] -jolt-transcript = { workspace = true, features = ["test-utils", "transcript-keccak"] } +jolt-transcript = { workspace = true, features = [ + "bn254", + "test-utils", + "transcript-keccak", +] } jolt-host.workspace = true jolt-prover.workspace = true jolt-witness.workspace = true diff --git a/crates/jolt-verifier/src/stages/relations.rs b/crates/jolt-verifier/src/stages/relations.rs index ce6dba2a65..57adda838a 100644 --- a/crates/jolt-verifier/src/stages/relations.rs +++ b/crates/jolt-verifier/src/stages/relations.rs @@ -137,11 +137,11 @@ where /// transcript, in the exact order the stage's inline draw uses. Batch-level /// coefficients and the shared binding vector are NOT drawn here. /// - /// The default draws one `challenge_scalar` per `Challenges` field, in + /// The default draws one `challenge()` per `Challenges` field, in /// declaration order, via [`SumcheckChallenges::from_transcript_values`]. This is /// the correct draw for the common case — a relation whose challenges are each a - /// single `challenge_scalar` (and for [`NoChallenges`](::jolt_claims::NoChallenges), - /// which has no fields, it draws nothing). A `challenge_scalar_powers(n)` draw + /// single `challenge()` (and for [`NoChallenges`](::jolt_claims::NoChallenges), + /// which has no fields, it draws nothing). A `challenge_powers(n)` draw /// reduces to this case: it performs exactly one squeeze and the relation keeps /// the degree-1 power, which equals that squeezed scalar. Only relations whose /// draw is genuinely different — an extra transcript append (a domain @@ -553,8 +553,8 @@ pub fn stage_claim_failed(stage: impl Debug, reason: impl ToString) -> VerifierE #[cfg(test)] pub(crate) mod draw_recording { use jolt_field::{Fr, Ring}; - use jolt_transcript::{prover_transcript, Keccak, ProverState, ScriptedTranscript}; pub(crate) use jolt_transcript::TranscriptEvent; + use jolt_transcript::{prover_transcript, Keccak, ProverState, ScriptedTranscript}; /// A logging transcript whose `n`-th challenge is `Fr(n)`. pub(crate) type DrawRecordingTranscript = ScriptedTranscript, Fr>; diff --git a/crates/jolt-verifier/src/stages/stage2/outputs.rs b/crates/jolt-verifier/src/stages/stage2/outputs.rs index 823acbe6e9..ab6275ccfc 100644 --- a/crates/jolt-verifier/src/stages/stage2/outputs.rs +++ b/crates/jolt-verifier/src/stages/stage2/outputs.rs @@ -284,7 +284,7 @@ mod tests { } /// Pins the batch's `draw_challenges` to the pre-port inline draw: the RAM read-write - /// gamma, the instruction claim-reduction gamma (each a single `challenge_scalar`), under + /// gamma, the instruction claim-reduction gamma (each a single `challenge()`), under /// `field-inline` the field-inline claim-reduction gamma (the spec's draw slot: after the /// instruction claim-reduction gamma, before the RAM output address challenges), then the /// RAM output-check address reference point — one raw `challenge()` per RAM address diff --git a/crates/jolt-verifier/src/stages/stage2/ram_output_check.rs b/crates/jolt-verifier/src/stages/stage2/ram_output_check.rs index 268fdfd858..3efe165852 100644 --- a/crates/jolt-verifier/src/stages/stage2/ram_output_check.rs +++ b/crates/jolt-verifier/src/stages/stage2/ram_output_check.rs @@ -120,9 +120,8 @@ impl ConcreteSumcheck for RamOutputCheck { /// so the generated aggregate draw lands the vector exactly where the legacy /// stage front drew it — after both batch gammas. /// - /// MUST stay `challenge()` (not `challenge_scalar()`): both decode the same - /// 16-byte squeeze, but differently, so switching would silently change the - /// address point values without changing the transcript bytes. + /// Each coordinate is its own one-challenge codec message; one + /// `challenge_vector` message would squeeze different bytes. fn draw_challenges>( &self, transcript: &mut T, diff --git a/crates/jolt-verifier/src/stages/stage2/verify.rs b/crates/jolt-verifier/src/stages/stage2/verify.rs index c7ed808a84..931347f076 100644 --- a/crates/jolt-verifier/src/stages/stage2/verify.rs +++ b/crates/jolt-verifier/src/stages/stage2/verify.rs @@ -166,7 +166,7 @@ where // Draw each relation's challenges in declaration order: the RAM read-write gamma, the // instruction claim-reduction gamma, under `field-inline` the field-inline claim-reduction - // gamma (each a single `challenge_scalar`), then the RAM output-check address reference + // gamma (each a single `challenge()`), then the RAM output-check address reference // point (the last member's `draw_challenges` override — one raw `challenge()` per RAM // address variable, landing after the gammas as the inline draw did). The drawn challenges // feed the input/output claims and populate the stage aggregate carried downstream. diff --git a/crates/jolt-verifier/src/stages/stage3/verify.rs b/crates/jolt-verifier/src/stages/stage3/verify.rs index 0bb036bd7a..3bcd3e1d06 100644 --- a/crates/jolt-verifier/src/stages/stage3/verify.rs +++ b/crates/jolt-verifier/src/stages/stage3/verify.rs @@ -73,7 +73,7 @@ where }; // Draw each relation's batching gamma in declaration order (shift, instruction - // input, register reduction); each is a single `challenge_scalar`. The drawn + // input, register reduction); each is a single `challenge()`. The drawn // challenges feed the input/output claims and populate the stage aggregate // carried downstream. check_pow( diff --git a/crates/jolt-verifier/src/stages/stage4/outputs.rs b/crates/jolt-verifier/src/stages/stage4/outputs.rs index b729da8131..8df45366d8 100644 --- a/crates/jolt-verifier/src/stages/stage4/outputs.rs +++ b/crates/jolt-verifier/src/stages/stage4/outputs.rs @@ -290,7 +290,7 @@ mod tests { } } - /// Pins the batch's `draw_challenges` to the inline draw order: one `challenge_scalar` per + /// Pins the batch's `draw_challenges` to the inline draw order: one `challenge()` per /// leading member — the registers gamma, under `field-inline` the field-register /// read-write gamma (the spec's draw slot: after the registers gamma, before the RAM /// value-check draw) — then the RAM value-check draw (its domain separator + gamma; that diff --git a/crates/jolt-verifier/src/stages/stage4/verify.rs b/crates/jolt-verifier/src/stages/stage4/verify.rs index 559758572b..3a52a79716 100644 --- a/crates/jolt-verifier/src/stages/stage4/verify.rs +++ b/crates/jolt-verifier/src/stages/stage4/verify.rs @@ -163,7 +163,7 @@ where }; // Draw the batching gammas in declaration order: the registers gamma, under `field-inline` - // the field-register read-write gamma (each a single `challenge_scalar`), then the RAM + // the field-register read-write gamma (each a single `challenge()`), then the RAM // value-check gamma behind its `b"ram_val_check_gamma"` domain separator (the relation's // `draw_challenges` override replays the separator at its exact transcript position). check_pow( diff --git a/crates/jolt-verifier/src/stages/stage5/outputs.rs b/crates/jolt-verifier/src/stages/stage5/outputs.rs index 2ebbedcfdb..da59c3cf76 100644 --- a/crates/jolt-verifier/src/stages/stage5/outputs.rs +++ b/crates/jolt-verifier/src/stages/stage5/outputs.rs @@ -243,10 +243,7 @@ mod tests { let (draw_events, challenges) = record(|t| sumchecks.draw_challenges(t).unwrap()); assert_eq!(draw_events, inline_events); - assert_eq!( - draw_events, - vec![squeeze(1), squeeze(2)] - ); + assert_eq!(draw_events, vec![squeeze(1), squeeze(2)]); assert_eq!( vec![ challenges.instruction_read_raf.gamma, diff --git a/crates/jolt-verifier/src/stages/stage6a/booleanity.rs b/crates/jolt-verifier/src/stages/stage6a/booleanity.rs index 8bc616fc2a..1c42226658 100644 --- a/crates/jolt-verifier/src/stages/stage6a/booleanity.rs +++ b/crates/jolt-verifier/src/stages/stage6a/booleanity.rs @@ -106,10 +106,8 @@ impl ConcreteSumcheck for BooleanityAddressPhase { /// squeezes exactly where the hand pre-batch block drew them — after the /// bytecode member's six gammas. /// - /// MUST stay `challenge_vector` + `challenge()` (not `challenge_scalar`): - /// both decode the same 16-byte squeeze, but differently, so switching - /// would silently change the reference/gamma values without changing the - /// transcript bytes. + /// The pad is one `challenge_vector` message and the gamma one + /// `challenge()` message; merging them would squeeze different bytes. fn draw_challenges>( &self, transcript: &mut T, diff --git a/crates/jolt-verifier/src/stages/stage6a/verify.rs b/crates/jolt-verifier/src/stages/stage6a/verify.rs index d05b154cd0..829d820525 100644 --- a/crates/jolt-verifier/src/stages/stage6a/verify.rs +++ b/crates/jolt-verifier/src/stages/stage6a/verify.rs @@ -76,7 +76,7 @@ where // The generated per-member draw: the bytecode member's six squeezes (the // fold gamma plus the five per-stage folding gammas, each formerly an - // inline `challenge_scalar_powers(..)` whose single squeeze's degree-1 + // inline `challenge_powers(..)` whose single squeeze's degree-1 // power equals the squeezed scalar; byte- and value-equal, test-locked in // `bytecode_read_raf.rs` — stage 6b's folds expand the power vectors via // `stage_gamma_powers`, test-locked below), then the booleanity member's @@ -269,10 +269,7 @@ mod tests { let (draw_events, challenges) = record(|t| sumchecks.draw_challenges(t).unwrap()); assert_eq!(draw_events, inline_events); - assert_eq!( - draw_events, - (1..=8u64).map(squeeze).collect::>() - ); + assert_eq!(draw_events, (1..=8u64).map(squeeze).collect::>()); assert_eq!( vec![ challenges.bytecode_read_raf.gamma, @@ -310,10 +307,7 @@ mod tests { let (draw_events, challenges) = record(|t| sumchecks.draw_challenges(t).unwrap()); - assert_eq!( - draw_events, - (1..=7u64).map(squeeze).collect::>() - ); + assert_eq!(draw_events, (1..=7u64).map(squeeze).collect::>()); let reversed: Vec = address.iter().rev().copied().collect(); assert_eq!(challenges.booleanity.reference_address, reversed[1..]); assert_eq!(sumchecks.booleanity.reference_cycle(), vec![fr(21)]); @@ -347,6 +341,7 @@ mod tests { /// (the prover draws each stage's power vector inline; the verifier stores /// the scalar and expands at the stage-6b folds). #[test] + #[expect(clippy::unwrap_used)] fn stage_gamma_powers_matches_challenge_powers() { use jolt_claims::protocols::jolt::geometry::bytecode::BYTECODE_STAGE_GAMMA_COUNTS; use jolt_claims::protocols::jolt::relations::bytecode::BytecodeReadRafAddressPhaseChallenges; diff --git a/crates/jolt-verifier/src/stages/stage6b/verify.rs b/crates/jolt-verifier/src/stages/stage6b/verify.rs index 941b78e7d5..b8e52a6023 100644 --- a/crates/jolt-verifier/src/stages/stage6b/verify.rs +++ b/crates/jolt-verifier/src/stages/stage6b/verify.rs @@ -594,7 +594,6 @@ fn append_opening_claims( #[cfg(test)] #[expect(clippy::unwrap_used, clippy::expect_used)] mod tests { - use jolt_transcript::Transcript; #[cfg(not(feature = "akita"))] use super::super::booleanity::BooleanityOutputClaims; #[cfg(not(feature = "akita"))] @@ -611,6 +610,7 @@ mod tests { use super::*; use crate::stages::relations::draw_recording::recorder; use jolt_field::{Fr, Ring}; + use jolt_transcript::Transcript; fn fr(value: u64) -> Fr { Fr::from_u64(value) diff --git a/crates/jolt-verifier/src/stages/stage7/verify.rs b/crates/jolt-verifier/src/stages/stage7/verify.rs index d120975362..94d4e61a13 100644 --- a/crates/jolt-verifier/src/stages/stage7/verify.rs +++ b/crates/jolt-verifier/src/stages/stage7/verify.rs @@ -91,7 +91,7 @@ where clear, )?; - // Draw the hamming-weight reduction's batching gamma (a single `challenge_scalar`, + // Draw the hamming-weight reduction's batching gamma (a single `challenge()`, // matching the relation's default `draw_challenges`) path-agnostically before the // ZK/clear branch; the advice and committed-program address phases draw nothing // (`NoChallenges`). BlindFold sources the gamma from diff --git a/crates/jolt-verifier/src/stages/stage8/verify.rs b/crates/jolt-verifier/src/stages/stage8/verify.rs index 4201038773..4d4dc043f9 100644 --- a/crates/jolt-verifier/src/stages/stage8/verify.rs +++ b/crates/jolt-verifier/src/stages/stage8/verify.rs @@ -44,11 +44,11 @@ use jolt_openings::{ }; #[cfg(not(feature = "akita"))] use jolt_poly::Point; -#[cfg(not(feature = "akita"))] -use jolt_transcript::LabelWithCount; #[cfg(feature = "akita")] use jolt_transcript::Encoding; use jolt_transcript::FieldTranscript; +#[cfg(not(feature = "akita"))] +use jolt_transcript::LabelWithCount; #[cfg(not(feature = "akita"))] /// One assembled final-opening batch entry. Public because the prover's @@ -265,7 +265,10 @@ where .map_err(|error| VerifierError::FinalOpeningVerificationFailed { reason: error.to_string(), })?; - transcript.append(&EvaluationClaim::new(pcs_opening_point.clone(), joint_claim)); + transcript.append(&EvaluationClaim::new( + pcs_opening_point.clone(), + joint_claim, + )); Ok(Stage8Output::Clear(Stage8ClearOutput { opening_claims, diff --git a/crates/jolt-verifier/src/stages/uniskip.rs b/crates/jolt-verifier/src/stages/uniskip.rs index e1386dacac..8038102b86 100644 --- a/crates/jolt-verifier/src/stages/uniskip.rs +++ b/crates/jolt-verifier/src/stages/uniskip.rs @@ -90,7 +90,10 @@ impl UniskipParams { /// uni-skip round (the Spartan outer relation's cycle variables plus the two /// uni-skip-collapsed row variables). Both fronts call this, so the draw is /// single-sourced. -pub fn draw_spartan_outer_tau(transcript: &mut T, log_t: usize) -> Result, VerifierError> +pub fn draw_spartan_outer_tau( + transcript: &mut T, + log_t: usize, +) -> Result, VerifierError> where F: JoltField, T: FieldTranscript, diff --git a/crates/jolt-verifier/src/verifier.rs b/crates/jolt-verifier/src/verifier.rs index 9fa517d883..f783b0da36 100644 --- a/crates/jolt-verifier/src/verifier.rs +++ b/crates/jolt-verifier/src/verifier.rs @@ -321,10 +321,7 @@ impl Encoding for JoltStatement<'_> { CommitmentConfig::Akita => 1, }, ); - word( - b"field_inline", - u64::from(protocol.field_inline.enabled), - ); + word(b"field_inline", u64::from(protocol.field_inline.enabled)); word( b"field_register_log_k", num::u64_from_usize(protocol.field_inline.field_register_log_k), @@ -335,14 +332,8 @@ impl Encoding for JoltStatement<'_> { FieldInlineRepresentation::NativeFieldElement => 0, }, ); - word( - b"max_input_size", - public_io.memory_layout.max_input_size, - ); - word( - b"max_output_size", - public_io.memory_layout.max_output_size, - ); + word(b"max_input_size", public_io.memory_layout.max_input_size); + word(b"max_output_size", public_io.memory_layout.max_output_size); word(b"heap_size", public_io.memory_layout.heap_size); word(b"panic", u64::from(public_io.panic)); word(b"ram_K", num::u64_from_usize(checked.ram_K)); @@ -376,7 +367,10 @@ impl Encoding for JoltStatement<'_> { self.config.trace_polynomial_order.transcript_scalar(), ); for (label, bytes) in [ - (&b"preprocessing_digest"[..], &checked.preprocessing_digest[..]), + ( + &b"preprocessing_digest"[..], + &checked.preprocessing_digest[..], + ), (b"inputs", &public_io.inputs), (b"outputs", &public_io.outputs), ] { @@ -870,7 +864,8 @@ pub fn absorb_transcript_commitments( C: Encoding, T: Transcript, { - let mut absorb_commitment = |commitment: &C| transcript.append_labeled(b"commitment", commitment); + let mut absorb_commitment = + |commitment: &C| transcript.append_labeled(b"commitment", commitment); absorb_commitment(&commitments.rd_inc); absorb_commitment(&commitments.ram_inc); for commitment in &commitments.instruction_ra { @@ -1328,7 +1323,9 @@ mod tests { ("trace_length", |c, _| c.trace_length *= 2), ("ram_K", |c, _| c.ram_K *= 2), ("entry_address", |c, _| c.entry_address += 4), - ("preprocessing_digest", |c, _| c.preprocessing_digest[0] ^= 1), + ("preprocessing_digest", |c, _| { + c.preprocessing_digest[0] ^= 1; + }), ("ram_rw_phase1_num_rounds", |_, k| { k.rw_config.ram_rw_phase1_num_rounds += 1; }), @@ -1359,7 +1356,9 @@ mod tests { let unbound: &[(&str, Edit)] = &[ // `validate_inputs` rejects a memory layout other than the // preprocessing's, and the preprocessing digest binds that one. - ("program_size", |c, _| c.public_io.memory_layout.program_size += 1), + ("program_size", |c, _| { + c.public_io.memory_layout.program_size += 1; + }), ("max_trusted_advice_size", |c, _| { c.public_io.memory_layout.max_trusted_advice_size += 1; }), @@ -1378,15 +1377,25 @@ mod tests { ("untrusted_advice_end", |c, _| { c.public_io.memory_layout.untrusted_advice_end += 1; }), - ("input_start", |c, _| c.public_io.memory_layout.input_start += 1), + ("input_start", |c, _| { + c.public_io.memory_layout.input_start += 1; + }), ("input_end", |c, _| c.public_io.memory_layout.input_end += 1), - ("output_start", |c, _| c.public_io.memory_layout.output_start += 1), - ("output_end", |c, _| c.public_io.memory_layout.output_end += 1), - ("stack_size", |c, _| c.public_io.memory_layout.stack_size += 1), + ("output_start", |c, _| { + c.public_io.memory_layout.output_start += 1; + }), + ("output_end", |c, _| { + c.public_io.memory_layout.output_end += 1; + }), + ("stack_size", |c, _| { + c.public_io.memory_layout.stack_size += 1; + }), ("stack_end", |c, _| c.public_io.memory_layout.stack_end += 1), ("heap_end", |c, _| c.public_io.memory_layout.heap_end += 1), ("panic address", |c, _| c.public_io.memory_layout.panic += 1), - ("termination", |c, _| c.public_io.memory_layout.termination += 1), + ("termination", |c, _| { + c.public_io.memory_layout.termination += 1; + }), ("io_end", |c, _| c.public_io.memory_layout.io_end += 1), // The trusted-advice commitment is absorbed under its own label // right after the statement, so its presence is bound there. @@ -1394,11 +1403,15 @@ mod tests { c.trusted_advice_commitment_present = !c.trusted_advice_commitment_present; }), // The capacity of the verifier's own vector-commitment setup. - ("vc_capacity", |c, _| c.vc_capacity = Some(c.vc_capacity.unwrap_or(0) + 1)), + ("vc_capacity", |c, _| { + c.vc_capacity = Some(c.vc_capacity.unwrap_or(0) + 1); + }), // The advice bytes themselves: the verifier reads only their // commitments. ("trusted_advice", |c, _| c.public_io.trusted_advice.push(1)), - ("untrusted_advice", |c, _| c.public_io.untrusted_advice.push(1)), + ("untrusted_advice", |c, _| { + c.public_io.untrusted_advice.push(1); + }), ]; for (field, edit) in unbound { assert_eq!( diff --git a/crates/jolt-verifier/tests/fs_attacks.rs b/crates/jolt-verifier/tests/fs_attacks.rs index 8af707ca4f..515c386f0d 100644 --- a/crates/jolt-verifier/tests/fs_attacks.rs +++ b/crates/jolt-verifier/tests/fs_attacks.rs @@ -28,12 +28,7 @@ fn dory_clear_preprocessing_digest_requires_fiat_shamir_binding() { let case = standard_muldiv_case(); let (honest, tape) = record_challenges::(|| { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >( + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( &case.preprocessing, &case.public_io, &case.proof, @@ -48,12 +43,7 @@ fn dory_clear_preprocessing_digest_requires_fiat_shamir_binding() { let mut preprocessing = case.preprocessing.clone(); preprocessing.preprocessing_digest[0] ^= 1; let frozen = replay_challenges(&tape, || { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >( + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( &preprocessing, &case.public_io, &case.proof, @@ -68,12 +58,7 @@ fn dory_clear_preprocessing_digest_requires_fiat_shamir_binding() { assert_eq!(frozen.consumed, frozen.expected); let (production, mutated_tape) = record_challenges::(|| { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >( + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( &preprocessing, &case.public_io, &case.proof, @@ -97,12 +82,7 @@ fn dory_clear_stage1_sumcheck_requires_fiat_shamir_challenges() { let case = standard_muldiv_case(); let (honest, tape) = record_challenges::(|| { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >( + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( &case.preprocessing, &case.public_io, &case.proof, @@ -117,12 +97,7 @@ fn dory_clear_stage1_sumcheck_requires_fiat_shamir_challenges() { let mut proof = case.proof.clone(); equivocate_stage1_clear(&mut proof, &tape, Fr::from_u64(1)); let frozen = replay_challenges(&tape, || { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >( + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( &case.preprocessing, &case.public_io, &proof, @@ -137,12 +112,7 @@ fn dory_clear_stage1_sumcheck_requires_fiat_shamir_challenges() { assert_eq!(frozen.consumed, frozen.expected); let (production, mutated_tape) = record_challenges::(|| { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >( + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( &case.preprocessing, &case.public_io, &proof, @@ -166,12 +136,7 @@ fn dory_clear_final_opening_batch_requires_commitment_binding() { let case = standard_muldiv_case(); let (honest, tape) = record_challenges::(|| { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >( + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( &case.preprocessing, &case.public_io, &case.proof, @@ -186,12 +151,7 @@ fn dory_clear_final_opening_batch_requires_commitment_binding() { let mut proof = case.proof.clone(); cancel_dory_final_opening_commitments(&mut proof, &tape); let frozen = replay_challenges(&tape, || { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >( + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( &case.preprocessing, &case.public_io, &proof, @@ -206,12 +166,7 @@ fn dory_clear_final_opening_batch_requires_commitment_binding() { assert_eq!(frozen.consumed, frozen.expected); let (production, mutated_tape) = record_challenges::(|| { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >( + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( &case.preprocessing, &case.public_io, &proof, @@ -235,12 +190,12 @@ fn dory_zk_preprocessing_digest_requires_fiat_shamir_binding() { let case = zk_muldiv_case(); let (honest, tape) = record_challenges::(|| { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >(&case.preprocessing, &case.public_io, &case.proof, None) + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( + &case.preprocessing, + &case.public_io, + &case.proof, + None, + ) }); assert!( honest.is_ok(), @@ -250,12 +205,12 @@ fn dory_zk_preprocessing_digest_requires_fiat_shamir_binding() { let mut preprocessing = case.preprocessing.clone(); preprocessing.preprocessing_digest[0] ^= 1; let frozen = replay_challenges(&tape, || { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >(&preprocessing, &case.public_io, &case.proof, None) + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( + &preprocessing, + &case.public_io, + &case.proof, + None, + ) }); assert!( frozen.output.is_ok(), @@ -265,12 +220,12 @@ fn dory_zk_preprocessing_digest_requires_fiat_shamir_binding() { assert_eq!(frozen.consumed, frozen.expected); let (production, mutated_tape) = record_challenges::(|| { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >(&preprocessing, &case.public_io, &case.proof, None) + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( + &preprocessing, + &case.public_io, + &case.proof, + None, + ) }); assert!( production.is_err(), @@ -289,12 +244,12 @@ fn dory_zk_final_opening_batch_requires_commitment_binding() { let case = zk_muldiv_case(); let (honest, tape) = record_challenges::(|| { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >(&case.preprocessing, &case.public_io, &case.proof, None) + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( + &case.preprocessing, + &case.public_io, + &case.proof, + None, + ) }); assert!( honest.is_ok(), @@ -304,12 +259,12 @@ fn dory_zk_final_opening_batch_requires_commitment_binding() { let mut proof = case.proof.clone(); cancel_dory_final_opening_commitments(&mut proof, &tape); let frozen = replay_challenges(&tape, || { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >(&case.preprocessing, &case.public_io, &proof, None) + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( + &case.preprocessing, + &case.public_io, + &proof, + None, + ) }); assert!( frozen.output.is_ok(), @@ -319,12 +274,12 @@ fn dory_zk_final_opening_batch_requires_commitment_binding() { assert_eq!(frozen.consumed, frozen.expected); let (production, mutated_tape) = record_challenges::(|| { - jolt_verifier::verify::< - Fr, - DoryScheme, - Pedersen, - AuditTranscript<'_, Fr>, - >(&case.preprocessing, &case.public_io, &proof, None) + jolt_verifier::verify::, AuditTranscript<'_, Fr>>( + &case.preprocessing, + &case.public_io, + &proof, + None, + ) }); assert!( production.is_err(), diff --git a/crates/jolt-verifier/tests/fs_obligations.rs b/crates/jolt-verifier/tests/fs_obligations.rs index 44be194ffa..1b6d06630f 100644 --- a/crates/jolt-verifier/tests/fs_obligations.rs +++ b/crates/jolt-verifier/tests/fs_obligations.rs @@ -556,10 +556,7 @@ fn prover_only_name(name: &str) -> bool { } fn is_challenge_call(name: &str) -> bool { - matches!( - name, - "challenge" | "challenge_vector" | "challenge_powers" - ) + matches!(name, "challenge" | "challenge_vector" | "challenge_powers") } fn is_absorb_method(name: &str) -> bool { diff --git a/crates/jolt-verifier/tests/soundness/tampering/akita.rs b/crates/jolt-verifier/tests/soundness/tampering/akita.rs index 8cacf8a0cc..3aa72906a1 100644 --- a/crates/jolt-verifier/tests/soundness/tampering/akita.rs +++ b/crates/jolt-verifier/tests/soundness/tampering/akita.rs @@ -31,7 +31,7 @@ use jolt_claims::protocols::jolt::lattice::relations::{ use jolt_claims::protocols::jolt::relations::claim_reductions::bytecode::BytecodeReductionIntermediateClaims; use jolt_claims::protocols::jolt::TracePolynomialOrder; use jolt_field::{JoltField, Ring}; -use jolt_prover::akita::preprocessing::{AkitaVc}; +use jolt_prover::akita::preprocessing::AkitaVc; use jolt_transcript::{Keccak, VerifierState}; use jolt_verifier::preprocessing::{CommittedProgramPreprocessing, ProgramPreprocessing}; use jolt_verifier::proof::{ClearProofClaims, JoltProofClaims}; diff --git a/crates/jolt-verifier/tests/support/akita_fixtures.rs b/crates/jolt-verifier/tests/support/akita_fixtures.rs index 39e9cacb92..792d353d51 100644 --- a/crates/jolt-verifier/tests/support/akita_fixtures.rs +++ b/crates/jolt-verifier/tests/support/akita_fixtures.rs @@ -12,9 +12,9 @@ use jolt_akita::{ use jolt_host::Program; use jolt_prover::akita; use jolt_prover::akita::preprocessing::{self, AkitaProverPreprocessing, AkitaVc}; -use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_prover::JoltBackend; use jolt_prover::ProverConfig; +use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_verifier::proof::JoltProof; use jolt_verifier::{verify, JoltVerifierPreprocessing, VerifierError}; @@ -184,10 +184,10 @@ mod field_inline { use jolt_host::{JoltProgramSource, Program}; use jolt_program::execution::{ExecutionBackend, JoltProgram, TraceInputs, TraceOutput}; use jolt_program::preprocess::JoltProgramPreprocessing; - use jolt_prover::akita::preprocessing::{AkitaVc}; - use jolt_transcript::{Keccak, ProverState, VerifierState}; + use jolt_prover::akita::preprocessing::AkitaVc; use jolt_prover::JoltBackend; use jolt_prover::{akita, ProverConfig}; + use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; use tracer::execution_backend::TracerBackend; diff --git a/examples/fibonacci/Cargo.toml b/examples/fibonacci/Cargo.toml index 4e35116afe..e1d57f0824 100644 --- a/examples/fibonacci/Cargo.toml +++ b/examples/fibonacci/Cargo.toml @@ -4,8 +4,6 @@ version = "0.1.0" edition = "2021" [features] -transcript-poseidon = ["jolt-sdk/transcript-poseidon"] -transcript-keccak = ["jolt-sdk/transcript-keccak"] transcript-blake2b = ["jolt-sdk/transcript-blake2b"] [dependencies] diff --git a/examples/field-ops/src/main.rs b/examples/field-ops/src/main.rs index 909fb193bd..e72a1a3a7f 100644 --- a/examples/field-ops/src/main.rs +++ b/examples/field-ops/src/main.rs @@ -12,8 +12,8 @@ mod pipeline { use jolt::host::JoltProgramSource; use jolt::{ JoltProgramPreprocessing, JoltProverPreprocessing, JoltSharedPreprocessing, MemoryConfig, - TraceInputs, TraceOutput, TracerBackend, VerifierField, VerifierPCS, VerifierTranscript, - VerifierVC, + ProofTranscript, TraceInputs, TraceOutput, TracerBackend, VerifierField, VerifierPCS, + VerifierTranscript, VerifierVC, }; use jolt_field::{CanonicalBytes, Ring}; use jolt_program::execution::{ExecutionBackend, JoltProgram, TraceData}; @@ -164,7 +164,7 @@ mod pipeline { let prover_preprocessing = preprocessing; let backend = JoltBackend::::reference(); - let proof = jolt_prover::prove::( + let proof = jolt_prover::prove::( backend.with_witness(&witness), &prover_preprocessing, &config, @@ -173,7 +173,7 @@ mod pipeline { ) .expect("modular field-inline prove"); - jolt::jolt_verifier::verify::( + jolt::jolt_verifier::verify::>( &prover_preprocessing.verifier, &public_io, &proof, diff --git a/examples/merkle-tree-save/Cargo.toml b/examples/merkle-tree-save/Cargo.toml index 4880b52c00..bf87593b8d 100644 --- a/examples/merkle-tree-save/Cargo.toml +++ b/examples/merkle-tree-save/Cargo.toml @@ -4,8 +4,6 @@ version = "0.1.0" edition = "2021" [features] -transcript-poseidon = ["jolt-sdk/transcript-poseidon"] -transcript-keccak = ["jolt-sdk/transcript-keccak"] transcript-blake2b = ["jolt-sdk/transcript-blake2b"] [dependencies] diff --git a/examples/merkle-tree-save/src/main.rs b/examples/merkle-tree-save/src/main.rs index b446372ad3..9959108280 100644 --- a/examples/merkle-tree-save/src/main.rs +++ b/examples/merkle-tree-save/src/main.rs @@ -1,6 +1,6 @@ // This is a variant of examples/merkle-tree that adds: // 1. `--save` flag to serialize verifier objects to /tmp/ -// 2. Transcript feature flags (transcript-poseidon, etc.) in Cargo.toml +// 2. The `transcript-blake2b` feature flag in Cargo.toml // // The upstream merkle-tree example is left unmodified. This crate reuses its guest. diff --git a/examples/recursion/guest/src/lib.rs b/examples/recursion/guest/src/lib.rs index 64a79c1e0c..1c2b865eb4 100644 --- a/examples/recursion/guest/src/lib.rs +++ b/examples/recursion/guest/src/lib.rs @@ -70,7 +70,7 @@ fn verify(bytes: &[u8]) -> u32 { jolt::VerifierField, jolt::VerifierPCS, jolt::VerifierVC, - jolt::VerifierTranscript, + jolt::VerifierTranscript<'_>, >(&verifier_preprocessing, &device, &proof, None) .is_ok(); end_cycle_tracking("verification"); diff --git a/examples/recursion/src/main.rs b/examples/recursion/src/main.rs index bef163fbbd..2d7ca71ea5 100644 --- a/examples/recursion/src/main.rs +++ b/examples/recursion/src/main.rs @@ -386,7 +386,7 @@ fn collect_guest_proofs( jolt_sdk::VerifierField, jolt_sdk::VerifierPCS, jolt_sdk::VerifierVC, - jolt_sdk::VerifierTranscript, + jolt_sdk::VerifierTranscript<'_>, >(&guest_verifier_preprocessing, &io_device, &proof, None) .is_ok(); info!(" Verification result: {is_valid}"); @@ -533,7 +533,7 @@ fn run_recursion_proof( jolt_sdk::VerifierField, jolt_sdk::VerifierPCS, jolt_sdk::VerifierVC, - jolt_sdk::VerifierTranscript, + jolt_sdk::VerifierTranscript<'_>, >(&recursion_verifier_preprocessing, &io_device, &proof, None) .is_ok(); let rv = postcard::from_bytes::(&io_device.outputs).unwrap(); diff --git a/jolt-eval/Cargo.toml b/jolt-eval/Cargo.toml index 8e37a24b31..70b78e0032 100644 --- a/jolt-eval/Cargo.toml +++ b/jolt-eval/Cargo.toml @@ -16,6 +16,7 @@ jolt-riscv = { workspace = true, features = ["serialization"] } jolt-field = { workspace = true } jolt-poly = { workspace = true } jolt-transcript = { workspace = true, features = [ + "bn254", "transcript-blake2b", "transcript-keccak", "transcript-poseidon", diff --git a/jolt-eval/src/guests/mod.rs b/jolt-eval/src/guests/mod.rs index 5844d578fd..8c2a17d362 100644 --- a/jolt-eval/src/guests/mod.rs +++ b/jolt-eval/src/guests/mod.rs @@ -26,7 +26,7 @@ pub use tracer::JoltDevice; pub type VerifierField = jolt_field::Fr; pub type VerifierPCS = jolt_dory::DoryScheme; pub type VerifierVC = jolt_crypto::Pedersen; -pub type VerifierTranscript = jolt_transcript::LegacyBlake2bTranscript; +pub type VerifierTranscript<'a> = jolt_transcript::VerifierState<'a, jolt_transcript::Keccak>; pub type Proof = jolt_verifier::JoltProof; pub type ProverPreprocessing = DoryProverPreprocessing; @@ -64,7 +64,7 @@ pub fn verify( proof: Proof, io_device: &JoltDevice, ) -> Result<(), VerifierError> { - jolt_verifier::verify::( + jolt_verifier::verify::>( verifier_pp, io_device, &proof, @@ -95,7 +95,7 @@ pub fn verify_with_claims( io_device.outputs = claimed_outputs.to_vec(); io_device.panic = claimed_panic; - jolt_verifier::verify::( + jolt_verifier::verify::>( verifier_pp, &io_device, &proof, diff --git a/jolt-eval/src/invariant/transcript_symmetry.rs b/jolt-eval/src/invariant/transcript_symmetry.rs index 0ef643f46f..ba2717d0e5 100644 --- a/jolt-eval/src/invariant/transcript_symmetry.rs +++ b/jolt-eval/src/invariant/transcript_symmetry.rs @@ -71,7 +71,7 @@ fn run_check(input: &Input) -> Result<(), CheckError> where H: DuplexSpongeInit, { - let mut prover = prover_transcript::(SESSION, INSTANCE_DIGEST); + let mut prover = prover_transcript::(SESSION, &INSTANCE_DIGEST); let mut prover_challenges: Vec<[u8; 32]> = Vec::new(); for op in &input.ops { @@ -88,7 +88,7 @@ where } let narg: Vec = prover.narg_string().to_vec(); - let mut verifier = verifier_transcript::(SESSION, INSTANCE_DIGEST, &narg); + let mut verifier = verifier_transcript::(SESSION, &INSTANCE_DIGEST, &narg); let mut challenge_idx = 0usize; for (op_idx, op) in input.ops.iter().enumerate() { diff --git a/jolt-sdk/Cargo.toml b/jolt-sdk/Cargo.toml index 2c30f933b5..1e1c3414f8 100644 --- a/jolt-sdk/Cargo.toml +++ b/jolt-sdk/Cargo.toml @@ -69,8 +69,6 @@ guest-verifier = [ ] malloc-shim = ["jolt-platform/malloc-shim"] -transcript-poseidon = ["jolt-transcript/transcript-poseidon"] -transcript-keccak = ["jolt-transcript/transcript-keccak"] transcript-blake2b = ["jolt-transcript/transcript-blake2b"] # Jolt-SDK native features (optional capabilities) @@ -115,7 +113,10 @@ jolt-host = { workspace = true, optional = true } jolt-openings = { workspace = true, optional = true } jolt-program = { workspace = true, optional = true } jolt-prover = { workspace = true, optional = true } -jolt-transcript = { workspace = true, optional = true } +jolt-transcript = { workspace = true, optional = true, features = [ + "bn254", + "transcript-keccak", +] } jolt-verifier = { workspace = true, optional = true } jolt-witness = { workspace = true, optional = true } tracer = { workspace = true, default-features = false, features = [ diff --git a/jolt-sdk/macros/src/lib.rs b/jolt-sdk/macros/src/lib.rs index 0a0f973549..cd08dc4a8e 100644 --- a/jolt-sdk/macros/src/lib.rs +++ b/jolt-sdk/macros/src/lib.rs @@ -278,7 +278,7 @@ impl MacroBuilder { jolt::VerifierField, jolt::VerifierPCS, jolt::VerifierVC, - jolt::VerifierTranscript, + jolt::VerifierTranscript<'_>, >( &__jolt_preprocessing, &__jolt_io_device, @@ -1155,7 +1155,7 @@ impl MacroBuilder { jolt::VerifierField, jolt::VerifierPCS, jolt::VerifierVC, - jolt::VerifierTranscript, + jolt::VerifierTranscript<'_>, >( &preprocessing, &io_device, diff --git a/jolt-sdk/src/host_utils.rs b/jolt-sdk/src/host_utils.rs index f00393ae29..f6a97c10f2 100644 --- a/jolt-sdk/src/host_utils.rs +++ b/jolt-sdk/src/host_utils.rs @@ -15,34 +15,12 @@ use jolt_crypto::{Bn254G1, Pedersen}; use jolt_dory::DoryHint; #[cfg(any(feature = "host", feature = "guest-verifier"))] use jolt_dory::{DoryCommitment, DoryScheme}; -#[cfg(any(feature = "host", feature = "guest-verifier"))] -use jolt_field::Fr; #[cfg(feature = "host")] use jolt_prover::dory::stages::stage0::TrustedAdviceCommitment; #[cfg(feature = "host")] use jolt_prover::{JoltProverPreprocessing as GenericJoltProverPreprocessing, ProverConfig}; -#[cfg(all( - any(feature = "host", feature = "guest-verifier"), - feature = "transcript-keccak" -))] -use jolt_transcript::KeccakTranscript; -#[cfg(all( - any(feature = "host", feature = "guest-verifier"), - any( - feature = "transcript-blake2b", - not(any( - feature = "transcript-poseidon", - feature = "transcript-keccak", - feature = "transcript-blake2b" - )) - ) -))] -use jolt_transcript::LegacyBlake2bTranscript; -#[cfg(all( - any(feature = "host", feature = "guest-verifier"), - feature = "transcript-poseidon" -))] -use jolt_transcript::PoseidonTranscript; +#[cfg(any(feature = "host", feature = "guest-verifier"))] +use jolt_transcript::{ProverState, VerifierState}; #[cfg(feature = "host")] use jolt_verifier::ProgramPreprocessing as GenericProgramPreprocessing; #[cfg(feature = "host")] @@ -52,32 +30,20 @@ use tracer::TracerInlineExpansionProvider; #[cfg(feature = "host")] pub use jolt_host as host; +/// The duplex sponge of the Fiat-Shamir transcript: Keccak, or Blake2b512 with +/// the `transcript-blake2b` feature. #[cfg(all( any(feature = "host", feature = "guest-verifier"), - feature = "transcript-poseidon" + not(feature = "transcript-blake2b") ))] -pub type ProtocolTranscript = PoseidonTranscript; -#[cfg(all( - any(feature = "host", feature = "guest-verifier"), - feature = "transcript-keccak" -))] -pub type ProtocolTranscript = KeccakTranscript; +pub type ProtocolSponge = jolt_transcript::Keccak; #[cfg(all( any(feature = "host", feature = "guest-verifier"), feature = "transcript-blake2b" ))] -pub type ProtocolTranscript = LegacyBlake2bTranscript; -#[cfg(all( - any(feature = "host", feature = "guest-verifier"), - not(any( - feature = "transcript-poseidon", - feature = "transcript-keccak", - feature = "transcript-blake2b" - )) -))] -pub type ProtocolTranscript = LegacyBlake2bTranscript; +pub type ProtocolSponge = jolt_transcript::Blake2b512; #[cfg(feature = "host")] -pub type ProofTranscript = ProtocolTranscript; +pub type ProofTranscript = ProverState; #[cfg(feature = "host")] pub use jolt_program::execution::{ExecutionBackend, TraceError, TraceInputs, TraceOutput}; #[cfg(feature = "host")] @@ -108,7 +74,7 @@ pub type VerifierVC = Pedersen; #[cfg(feature = "host")] pub type VerifierField = jolt_field::Fr; #[cfg(feature = "host")] -pub type VerifierTranscript = ProtocolTranscript; +pub type VerifierTranscript<'a> = VerifierState<'a, ProtocolSponge>; #[cfg(feature = "host")] pub type JoltVerifierPreprocessing = jolt_verifier::JoltVerifierPreprocessing; @@ -193,7 +159,7 @@ pub type VerifierVC = Pedersen; #[cfg(all(feature = "guest-verifier", not(feature = "host")))] pub type VerifierField = jolt_field::Fr; #[cfg(all(feature = "guest-verifier", not(feature = "host")))] -pub type VerifierTranscript = ProtocolTranscript; +pub type VerifierTranscript<'a> = VerifierState<'a, ProtocolSponge>; #[cfg(all(feature = "guest-verifier", not(feature = "host")))] pub type JoltVerifierPreprocessing = jolt_verifier::JoltVerifierPreprocessing; diff --git a/jolt-sdk/tests/verifier_api.rs b/jolt-sdk/tests/verifier_api.rs index 8111799c0b..60f34cb477 100644 --- a/jolt-sdk/tests/verifier_api.rs +++ b/jolt-sdk/tests/verifier_api.rs @@ -40,7 +40,7 @@ mod tests { jolt_sdk::VerifierField, jolt_sdk::VerifierPCS, jolt_sdk::VerifierVC, - jolt_sdk::VerifierTranscript, + jolt_sdk::VerifierTranscript<'_>, >(&preprocessing, &device, &proof, None); let duration = start.elapsed(); println!("Verification took: {} ms", duration.as_millis()); diff --git a/src/build_wasm.rs b/src/build_wasm.rs index 386cb33fdc..51c17f8d23 100644 --- a/src/build_wasm.rs +++ b/src/build_wasm.rs @@ -175,7 +175,7 @@ pub fn verify_{func_name}( jolt_sdk::VerifierField, jolt_sdk::VerifierPCS, jolt_sdk::VerifierVC, - jolt_sdk::VerifierTranscript, + jolt_sdk::VerifierTranscript<'_>, >(&preprocessing, &program_io, &proof, trusted_advice_commitment.as_ref()) .map_err(JsError::from) }} diff --git a/zklean-extractor/Cargo.toml b/zklean-extractor/Cargo.toml index 80cb84ba0b..50d46a5bce 100644 --- a/zklean-extractor/Cargo.toml +++ b/zklean-extractor/Cargo.toml @@ -20,12 +20,13 @@ rand_core = { workspace = true, features = ["getrandom"] } regex = "1.11.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +spongefish.workspace = true strum.workspace = true build-fs-tree = "0.8.1" allocative.workspace = true num-bigint = "0.4" -jolt-field.workspace = true +jolt-field = { workspace = true, features = ["spongefish"] } jolt-claims.workspace = true jolt-lookup-tables.workspace = true jolt-r1cs.workspace = true diff --git a/zklean-extractor/src/mle_ast.rs b/zklean-extractor/src/mle_ast.rs index 4d784aa804..60f8a7ad02 100644 --- a/zklean-extractor/src/mle_ast.rs +++ b/zklean-extractor/src/mle_ast.rs @@ -1272,6 +1272,15 @@ impl CanonicalBytes for MleAst { } } +/// The canonical bytes, so a transcript absorbs the AST as it absorbs `Fr`. +impl spongefish::Encoding for MleAst { + fn encode(&self) -> impl AsRef<[u8]> { + let mut bytes = [0u8; 32]; + self.to_bytes_le(&mut bytes); + bytes + } +} + impl CanonicalEncoding for MleAst { const MODULUS_BITS: u32 = 254; /// Mirrors BN254 `Fr`, which this AST models. From c17ff02fd014ee1cadaaf9d3a69f8b02e67f7a79 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Tue, 20 Oct 2026 23:58:36 -0400 Subject: [PATCH 08/16] fix(transcript): per-crate clippy configs after the sponge port jolt-prover names Fr challenges, so its transcript dependency enables bn254 itself instead of relying on feature unification. Drop an unused import in the field-inline Akita fixtures and the SDK, and avoid unwrap in the zk statistical test. --- crates/jolt-prover/Cargo.toml | 6 +++++- .../tests/statistical_independence/zk.rs | 18 ++++++++++++++++-- .../tests/support/akita_fixtures.rs | 2 +- jolt-sdk/src/host_utils.rs | 8 +++----- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/crates/jolt-prover/Cargo.toml b/crates/jolt-prover/Cargo.toml index d10bc3d508..73b8dab11f 100644 --- a/crates/jolt-prover/Cargo.toml +++ b/crates/jolt-prover/Cargo.toml @@ -113,7 +113,11 @@ jolt-poly = { workspace = true, default-features = false } jolt-program.workspace = true jolt-riscv.workspace = true jolt-sumcheck.workspace = true -jolt-transcript = { workspace = true, features = ["prover-grinding", "transcript-keccak"] } +jolt-transcript = { workspace = true, features = [ + "bn254", + "prover-grinding", + "transcript-keccak", +] } jolt-verifier.workspace = true jolt-witness.workspace = true rand_core = { workspace = true, features = ["getrandom"], optional = true } diff --git a/crates/jolt-verifier/tests/statistical_independence/zk.rs b/crates/jolt-verifier/tests/statistical_independence/zk.rs index ce84cd8acd..05f4441328 100644 --- a/crates/jolt-verifier/tests/statistical_independence/zk.rs +++ b/crates/jolt-verifier/tests/statistical_independence/zk.rs @@ -509,7 +509,14 @@ impl BucketTracker { let mut transcript = prover_transcript::(b"jolt-zk-stat", b""); transcript.append_bytes(name.as_bytes()); transcript.append(value); - self.record_projected(name, field_low_u64(transcript.challenge().unwrap())); + self.record_projected( + name, + field_low_u64( + transcript + .challenge() + .expect("one Fr challenge is within the codec bound"), + ), + ); } fn record_append_positions(&mut self, prefix: &str, values: &[A]) { @@ -558,7 +565,14 @@ impl BucketTracker { let mut transcript = prover_transcript::(b"jolt-zk-stat", b""); transcript.append_bytes(name.as_bytes()); transcript.append_bytes(bytes); - self.record_projected(name, field_low_u64(transcript.challenge().unwrap())); + self.record_projected( + name, + field_low_u64( + transcript + .challenge() + .expect("one Fr challenge is within the codec bound"), + ), + ); } fn record_projected(&mut self, name: String, value: u64) { diff --git a/crates/jolt-verifier/tests/support/akita_fixtures.rs b/crates/jolt-verifier/tests/support/akita_fixtures.rs index 792d353d51..5807cccddf 100644 --- a/crates/jolt-verifier/tests/support/akita_fixtures.rs +++ b/crates/jolt-verifier/tests/support/akita_fixtures.rs @@ -187,7 +187,7 @@ mod field_inline { use jolt_prover::akita::preprocessing::AkitaVc; use jolt_prover::JoltBackend; use jolt_prover::{akita, ProverConfig}; - use jolt_transcript::{Keccak, ProverState, VerifierState}; + use jolt_transcript::{Keccak, ProverState}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; use tracer::execution_backend::TracerBackend; diff --git a/jolt-sdk/src/host_utils.rs b/jolt-sdk/src/host_utils.rs index f6a97c10f2..0e53237d36 100644 --- a/jolt-sdk/src/host_utils.rs +++ b/jolt-sdk/src/host_utils.rs @@ -19,8 +19,6 @@ use jolt_dory::{DoryCommitment, DoryScheme}; use jolt_prover::dory::stages::stage0::TrustedAdviceCommitment; #[cfg(feature = "host")] use jolt_prover::{JoltProverPreprocessing as GenericJoltProverPreprocessing, ProverConfig}; -#[cfg(any(feature = "host", feature = "guest-verifier"))] -use jolt_transcript::{ProverState, VerifierState}; #[cfg(feature = "host")] use jolt_verifier::ProgramPreprocessing as GenericProgramPreprocessing; #[cfg(feature = "host")] @@ -43,7 +41,7 @@ pub type ProtocolSponge = jolt_transcript::Keccak; ))] pub type ProtocolSponge = jolt_transcript::Blake2b512; #[cfg(feature = "host")] -pub type ProofTranscript = ProverState; +pub type ProofTranscript = jolt_transcript::ProverState; #[cfg(feature = "host")] pub use jolt_program::execution::{ExecutionBackend, TraceError, TraceInputs, TraceOutput}; #[cfg(feature = "host")] @@ -74,7 +72,7 @@ pub type VerifierVC = Pedersen; #[cfg(feature = "host")] pub type VerifierField = jolt_field::Fr; #[cfg(feature = "host")] -pub type VerifierTranscript<'a> = VerifierState<'a, ProtocolSponge>; +pub type VerifierTranscript<'a> = jolt_transcript::VerifierState<'a, ProtocolSponge>; #[cfg(feature = "host")] pub type JoltVerifierPreprocessing = jolt_verifier::JoltVerifierPreprocessing; @@ -159,7 +157,7 @@ pub type VerifierVC = Pedersen; #[cfg(all(feature = "guest-verifier", not(feature = "host")))] pub type VerifierField = jolt_field::Fr; #[cfg(all(feature = "guest-verifier", not(feature = "host")))] -pub type VerifierTranscript<'a> = VerifierState<'a, ProtocolSponge>; +pub type VerifierTranscript<'a> = jolt_transcript::VerifierState<'a, ProtocolSponge>; #[cfg(all(feature = "guest-verifier", not(feature = "host")))] pub type JoltVerifierPreprocessing = jolt_verifier::JoltVerifierPreprocessing; From 7db4652eef3cf84d04146073bf86e1c686b1cad2 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Wed, 21 Oct 2026 01:49:36 -0400 Subject: [PATCH 09/16] fix(transcript): address the soundness audit's notes - An unlabelled UnivariatePoly round absorbs as a labelled sum-check round with its coefficient count, so a longer polynomial cannot absorb like a shorter one. - Document that every absorbed Encoding must decode uniquely, since the sponge absorbs bytes without framing. - Correct the prover-grinding comment: crates holding both sides enable yolocrypto in verifier builds; it changes visibility, not bytes. - List precommitted as unbound in the statement test, and check that every unbound edit changes the inputs. - Fix two test ports: count challenge draws rather than PoW squeezes, and squeeze the prover's tail once in the grouped-capacity loop. - Re-bless the absorb-site inventory. --- crates/jolt-akita/tests/grouped_capacity.rs | 3 +- crates/jolt-sumcheck/src/round_proof.rs | 6 ++-- crates/jolt-sumcheck/src/tests.rs | 21 ++++++++++-- crates/jolt-transcript/Cargo.toml | 4 ++- crates/jolt-transcript/src/transcript.rs | 17 +++++++--- crates/jolt-verifier/src/stages/relations.rs | 2 +- crates/jolt-verifier/src/verifier.rs | 34 ++++++++++++++++--- .../tests/fs_inventory/absorb-sites.inventory | 4 +-- 8 files changed, 72 insertions(+), 19 deletions(-) diff --git a/crates/jolt-akita/tests/grouped_capacity.rs b/crates/jolt-akita/tests/grouped_capacity.rs index 7289c9afac..5e331bd3f0 100644 --- a/crates/jolt-akita/tests/grouped_capacity.rs +++ b/crates/jolt-akita/tests/grouped_capacity.rs @@ -120,6 +120,7 @@ fn grouped_opening_proves_advice_larger_than_the_trace_group() { &serde_json::to_string(&verifier_setup).expect("verifier setup should serialize"), ) .expect("verifier setup should deserialize"); + let prover_tail = prover_state.verifier_message::<[u8; 32]>(); for setup in [&verifier_setup, &transported] { let mut verifier_transcript = prover_transcript::(b"akita-grouped-capacity", b""); @@ -132,7 +133,7 @@ fn grouped_opening_proves_advice_larger_than_the_trace_group() { ) .expect("grouped opening should verify"); assert_eq!( - prover_state.verifier_message::<[u8; 32]>(), + prover_tail, verifier_transcript.verifier_message::<[u8; 32]>() ); } diff --git a/crates/jolt-sumcheck/src/round_proof.rs b/crates/jolt-sumcheck/src/round_proof.rs index f1e55bbbbb..4df81381fa 100644 --- a/crates/jolt-sumcheck/src/round_proof.rs +++ b/crates/jolt-sumcheck/src/round_proof.rs @@ -22,15 +22,15 @@ pub trait ClearRound: RoundMessage { } } +/// An unlabelled round polynomial absorbs as a sum-check round: the count word +/// keeps a longer polynomial from absorbing like a shorter one. impl RoundMessage for UnivariatePoly { fn degree(&self) -> usize { UnivariatePolynomial::degree(self) } fn append_to_transcript(&self, transcript: &mut T) { - for coeff in self.coefficients() { - transcript.append(coeff); - } + LabeledRoundPoly::sumcheck(self).append_to_transcript(transcript); } } diff --git a/crates/jolt-sumcheck/src/tests.rs b/crates/jolt-sumcheck/src/tests.rs index e549c71e53..48478592bd 100644 --- a/crates/jolt-sumcheck/src/tests.rs +++ b/crates/jolt-sumcheck/src/tests.rs @@ -457,7 +457,7 @@ fn clear_round_verifier_with_label_absorbs_label() { } #[test] -fn clear_round_verifier_no_label() { +fn clear_round_verifier_no_label_absorbs_a_sumcheck_round() { let poly = UnivariatePoly::new(vec![F::from_u64(5), F::from_u64(3)]); let mut t1 = prover_transcript::(b"sumcheck-test", b""); @@ -465,12 +465,29 @@ fn clear_round_verifier_no_label() { let c1: F = t1.challenge().unwrap(); let mut t2 = prover_transcript::(b"sumcheck-test", b""); + t2.append(&LabelWithCount(SUMCHECK_ROUND_TRANSCRIPT_LABEL, 2)); for coeff in poly.coefficients() { t2.append(&coeff); } let c2: F = t2.challenge().unwrap(); - assert_eq!(c1, c2, "unlabeled absorption must match manual absorption"); + assert_eq!( + c1, c2, + "an unlabelled round must absorb as a sum-check round" + ); +} + +#[test] +fn clear_round_verifier_no_label_binds_the_coefficient_count() { + let short = UnivariatePoly::new(vec![F::from_u64(5), F::from_u64(3)]); + let long = UnivariatePoly::new(vec![F::from_u64(5), F::from_u64(3), F::from_u64(0)]); + let draw = |poly: &UnivariatePoly| -> F { + let mut transcript = prover_transcript::(b"sumcheck-test", b""); + as RoundMessage>::append_to_transcript(poly, &mut transcript); + transcript.challenge().unwrap() + }; + + assert_ne!(draw(&short), draw(&long)); } #[test] diff --git a/crates/jolt-transcript/Cargo.toml b/crates/jolt-transcript/Cargo.toml index a48b183481..4c771a6857 100644 --- a/crates/jolt-transcript/Cargo.toml +++ b/crates/jolt-transcript/Cargo.toml @@ -18,7 +18,9 @@ workspace = true [features] default = ["transcript-blake2b", "transcript-keccak", "transcript-poseidon"] # The prover's proof-of-work nonce search. It copies the sponge state, which -# spongefish 0.8.0 exposes only under `yolocrypto`; verifier builds leave it off. +# spongefish 0.8.0 exposes only under `yolocrypto`. Crates that hold both sides +# enable it in verifier builds too; `yolocrypto` changes visibility only, not +# the bytes absorbed or squeezed. prover-grinding = ["spongefish/yolocrypto"] # Searches proof-of-work nonces on all threads; the nonce found is the same. parallel = ["prover-grinding", "dep:rayon"] diff --git a/crates/jolt-transcript/src/transcript.rs b/crates/jolt-transcript/src/transcript.rs index ed549882bc..25c6b1933d 100644 --- a/crates/jolt-transcript/src/transcript.rs +++ b/crates/jolt-transcript/src/transcript.rs @@ -12,6 +12,12 @@ //! from that structure as a public message; spongefish absorbs the same bytes //! for a public message and for a prover message, so the challenges are those //! of the same protocol with the field sent through the NARG string. +//! +//! The sponge absorbs a message's bytes with no length or separator, so the +//! messages absorbed between two squeezes must decode uniquely from their +//! concatenation. Every [`Encoding`] a protocol absorbs is therefore of fixed +//! width, or of a length fixed by bound data, or carries its own count, as +//! [`append_values`](Transcript::append_values) and [`BytesMsg`] do. use jolt_field::Ring; use spongefish::{ @@ -23,6 +29,7 @@ use crate::challenge::message_bytes; use crate::codec::{BytesMsg, Label, LabelWithCount}; use crate::pow::{Leb128, SpongePow}; use crate::{ChallengeCodec, ChallengeSetSize, UniformBytes}; +use sealed::Sealed; pub(crate) mod sealed { use spongefish::{Encoding, VerificationError}; @@ -45,7 +52,7 @@ pub(crate) mod sealed { /// /// Every method is provided once and runs the same sponge operations on both /// sides. -pub trait Transcript: sealed::Sealed { +pub trait Transcript: Sealed { /// Absorbs `message` as a public message. fn append(&mut self, message: &M) { self.absorb(message); @@ -110,7 +117,7 @@ pub trait Transcript: sealed::Sealed { } } -impl Transcript for T {} +impl Transcript for T {} /// A transcript that draws challenges in `F`. pub trait FieldTranscript: Transcript { @@ -166,7 +173,7 @@ pub(crate) fn draw_challenges( len: usize, ) -> Result, VerificationError> where - T: sealed::Sealed + ?Sized, + T: Sealed + ?Sized, F: ChallengeCodec, { // A length the codec rejects poisons the verifier too, so a caller that @@ -182,7 +189,7 @@ where challenges.ok_or(VerificationError) } -impl sealed::Sealed for ProverState +impl Sealed for ProverState where H: DuplexSpongeInterface, R: DuplexSpongeInit, @@ -204,7 +211,7 @@ where } } -impl sealed::Sealed for VerifierState<'_, H> +impl Sealed for VerifierState<'_, H> where H: DuplexSpongeInterface, { diff --git a/crates/jolt-verifier/src/stages/relations.rs b/crates/jolt-verifier/src/stages/relations.rs index 57adda838a..2793fcd57d 100644 --- a/crates/jolt-verifier/src/stages/relations.rs +++ b/crates/jolt-verifier/src/stages/relations.rs @@ -1373,7 +1373,7 @@ mod begin_batch_tests { assert_eq!( events .iter() - .filter(|event| matches!(event, TranscriptEvent::Squeeze(_))) + .filter(|event| matches!(event, TranscriptEvent::Challenges(_))) .count(), 1, ); diff --git a/crates/jolt-verifier/src/verifier.rs b/crates/jolt-verifier/src/verifier.rs index f783b0da36..eaa91ea7cd 100644 --- a/crates/jolt-verifier/src/verifier.rs +++ b/crates/jolt-verifier/src/verifier.rs @@ -20,6 +20,9 @@ use jolt_transcript::{ #[cfg(not(feature = "akita"))] use crate::proof::JoltCommitments; +#[cfg(feature = "akita")] +use crate::stages::stage8::OneHotTraceCommitmentMetadata; +use crate::stages::stage8::Stage8Output; use crate::{ config::{ validate_proof_config, CommitmentConfig, JoltProtocolConfig, ZkConfig, @@ -164,7 +167,7 @@ where reason: error.to_string(), })?; } else { - let stage8::Stage8Output::Clear(_) = stage8 else { + let Stage8Output::Clear(_) = stage8 else { return Err(VerifierError::ExpectedClearProof { field: "stage8" }); }; } @@ -187,7 +190,7 @@ pub fn verify<'a, F, PCS, VC, T>( where F: JoltField + Encoding, PCS: CommitmentScheme, - PCS::Output: Clone + Encoding + stage8::OneHotTraceCommitmentMetadata, + PCS::Output: Clone + Encoding + OneHotTraceCommitmentMetadata, PCS::VerifierSetup: stage8::OneHotTraceSetupMetadata, VC: VectorCommitment, VC::Output: Copy + Encoding, @@ -275,7 +278,7 @@ where &stage7, )?; - let stage8::Stage8Output::Clear = stage8 else { + let Stage8Output::Clear = stage8 else { return Err(VerifierError::ExpectedClearProof { field: "stage8" }); }; @@ -1190,7 +1193,7 @@ mod tests { } } - impl jolt_transcript::Encoding for TestCommitment { + impl Encoding for TestCommitment { fn encode(&self) -> impl AsRef<[u8]> { [0u8; 0] } @@ -1412,8 +1415,31 @@ mod tests { ("untrusted_advice", |c, _| { c.public_io.untrusted_advice.push(1); }), + // Derived during validation from the trace order, trace length, + // chunk bits and advice presence, which are bound, and from the + // committed program, which the preprocessing digest binds. + ("precommitted", |c, _| { + c.precommitted = PrecommittedSchedule::new( + TracePolynomialOrder::CycleMajor, + 4, + 4, + #[cfg(not(feature = "akita"))] + None, + #[cfg(not(feature = "akita"))] + None, + Some(CommittedProgramSchedule { + bytecode_len: 16, + program_image_len_words: 16, + program_image_start_index: 0, + }), + ) + .unwrap(); + }), ]; for (field, edit) in unbound { + let (mut edited, mut edited_config) = (checked.clone(), config); + edit(&mut edited, &mut edited_config); + assert_ne!(edited, checked, "the {field} edit changes nothing"); assert_eq!( encode(*edit), honest, diff --git a/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory b/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory index 016c08bd0e..9fc33fc51f 100644 --- a/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory +++ b/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory @@ -110,7 +110,7 @@ jolt-sumcheck::round_proof.rs::CompressedLabeledRoundPoly<'_,F>::append_to_trans jolt-sumcheck::round_proof.rs::CompressedLabeledRoundPoly<'_,F>::append_to_transcript::append#2::transcript.append(c) jolt-sumcheck::round_proof.rs::LabeledRoundPoly<'_,F>::append_to_transcript::append#0::transcript.append(&LabelWithCount(self.label,coeffs.len()asu64)) jolt-sumcheck::round_proof.rs::LabeledRoundPoly<'_,F>::append_to_transcript::append#1::transcript.append(coeff) -jolt-sumcheck::round_proof.rs::UnivariatePoly::append_to_transcript::append#0::transcript.append(coeff) +jolt-sumcheck::round_proof.rs::UnivariatePoly::append_to_transcript::append_to_transcript#0::LabeledRoundPoly::sumcheck(self).append_to_transcript(transcript) jolt-sumcheck::verifier.rs::CommittedSumcheckProof::verify_committed_consistency::append#0::transcript.append(&self.output_claims) jolt-sumcheck::verifier.rs::SumcheckVerifier::verify::append_to_transcript#0::round_proof.append_to_transcript(transcript) jolt-sumcheck::verifier.rs::SumcheckVerifier::verify_committed_round_consistency::append_to_transcript#0::round_proof.append_to_transcript(transcript) @@ -140,7 +140,7 @@ jolt-verifier::stages/stage6b/verify.rs::verify::append_opening_claims#0::append jolt-verifier::stages/stage8/verify.rs::verify::append#1::transcript.append(&ZkEvaluationClaim::new(pcs_opening_point.as_slice(),&hiding_evaluation_commitment,)) jolt-verifier::stages/stage8/verify.rs::verify::append#2::transcript.append(&LabelWithCount(b"rlc_claims",crate::num::u64_from_usize(opening_claims.len()),)) jolt-verifier::stages/stage8/verify.rs::verify::append#3::transcript.append(&claim.evaluation.value) -jolt-verifier::stages/stage8/verify.rs::verify::append#5::transcript.append(&EvaluationClaim::new(pcs_opening_point.clone(),joint_claim)) +jolt-verifier::stages/stage8/verify.rs::verify::append#5::transcript.append(&EvaluationClaim::new(pcs_opening_point.clone(),joint_claim,)) jolt-verifier::stages/uniskip.rs::verify_clear::append_labeled#0::transcript.append_labeled(b"opening_claim",&output_claim) jolt-verifier::verifier.rs::absorb_akita_commitments::absorb_akita_program_commitments#4::absorb_akita_program_commitments(bytecode,image,transcript) jolt-verifier::verifier.rs::absorb_akita_commitments::append_labeled#0::transcript.append_labeled(b"commitment",one_hot_trace) From f2f4b01592a417e35bacd764be440199cf159cae Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Wed, 21 Oct 2026 01:49:36 -0400 Subject: [PATCH 10/16] chore(transcript): imported short names, rustfmt, regenerated fixtures Import types and traits by short name on lines this branch adds, as check_style_invariants requires, apply rustfmt, and commit the lockfiles, verifier fixtures and fuzz seeds regenerated for the new transcript bytes. --- Cargo.lock | 1 + crates/jolt-blindfold/src/error.rs | 9 +- crates/jolt-crypto/fuzz/Cargo.lock | 175 +++++++++++++++++- crates/jolt-dory/fuzz/Cargo.lock | 2 +- .../deser_commitment/valid-commitment-bincode | Bin 387 -> 387 bytes .../deser_commitment/valid-proof-bincode | Bin 3509 -> 3509 bytes crates/jolt-dory/src/transcript.rs | 4 +- crates/jolt-field/src/algebra.rs | 6 +- crates/jolt-field/src/encoding.rs | 46 +++-- crates/jolt-field/tests/spine.rs | 4 +- crates/jolt-hyperkzg/src/types.rs | 6 +- crates/jolt-kernels/src/reference/naive.rs | 6 +- crates/jolt-openings/fuzz/Cargo.lock | 2 +- crates/jolt-openings/src/error.rs | 6 +- crates/jolt-prover/src/dory/stages/stage0.rs | 6 +- crates/jolt-prover/src/profile.rs | 2 +- crates/jolt-prover/src/stages/drivers.rs | 12 +- .../src/stages/field_inline_fixtures.rs | 2 +- crates/jolt-prover/src/stages/stage1.rs | 12 +- crates/jolt-prover/src/stages/stage2.rs | 12 +- crates/jolt-prover/src/stages/stage4.rs | 6 +- crates/jolt-prover/src/stages/stage5.rs | 6 +- crates/jolt-prover/src/stages/stage6a.rs | 6 +- crates/jolt-prover/src/stages/stage6b.rs | 6 +- crates/jolt-prover/tests/akita_e2e.rs | 6 +- crates/jolt-prover/tests/e2e_matrix.rs | 2 +- crates/jolt-prover/tests/engine_twins.rs | 15 +- .../tests/support/akita_backend.rs | 2 +- .../tests/support/field_inline/akita.rs | 4 +- crates/jolt-spartan-verifier/src/key.rs | 5 +- crates/jolt-spartan-verifier/src/lib.rs | 6 +- crates/jolt-sumcheck/src/committed.rs | 8 +- crates/jolt-sumcheck/src/error.rs | 5 +- crates/jolt-sumcheck/src/lib.rs | 4 +- crates/jolt-sumcheck/src/recorder.rs | 3 +- .../jolt-transcript/benches/transcript_ops.rs | 19 +- crates/jolt-transcript/fuzz/Cargo.lock | 8 +- crates/jolt-transcript/tests/blake2b_tests.rs | 4 +- crates/jolt-transcript/tests/keccak_tests.rs | 6 +- .../jolt-transcript/tests/poseidon_tests.rs | 6 +- crates/jolt-verifier/fuzz/Cargo.lock | 2 +- .../fuzz/fixtures/advice-consumer-bundle.bin | Bin 173777 -> 173779 bytes .../fuzz/fixtures/committed-muldiv-bundle.bin | Bin 100603 -> 100605 bytes .../fuzz/fixtures/muldiv-bundle.bin | Bin 168802 -> 168804 bytes .../fixtures/zk-advice-consumer-bundle.bin | Bin 176599 -> 176601 bytes .../fixtures/zk-committed-muldiv-bundle.bin | Bin 103310 -> 103312 bytes .../fuzz/fixtures/zk-muldiv-bundle.bin | Bin 171816 -> 171818 bytes crates/jolt-verifier/src/error.rs | 5 +- .../jolt-verifier/src/stages/stage8/verify.rs | 4 +- .../jolt-verifier/tests/support/zk_audit.rs | 4 +- examples/recursion/src/main.rs | 5 +- jolt-eval/fuzz/Cargo.lock | 3 +- jolt-eval/src/guests/mod.rs | 3 +- jolt-sdk/src/host_utils.rs | 24 ++- jolt-sdk/tests/verifier_api.rs | 3 +- zklean-extractor/src/mle_ast.rs | 3 +- 56 files changed, 355 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85f182d629..53a851fc2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8087,6 +8087,7 @@ dependencies = [ "regex", "serde", "serde_json", + "spongefish 0.8.0", "strum 0.28.0", "tracer", ] diff --git a/crates/jolt-blindfold/src/error.rs b/crates/jolt-blindfold/src/error.rs index 11069e27c2..caa7dff744 100644 --- a/crates/jolt-blindfold/src/error.rs +++ b/crates/jolt-blindfold/src/error.rs @@ -3,6 +3,7 @@ use jolt_crypto::VectorOpeningError; use jolt_field::JoltField; use jolt_r1cs::ConstraintMatrixEvalError; use jolt_sumcheck::{SumcheckError, SumcheckR1csError}; +use jolt_transcript::VerificationError as TranscriptVerificationError; use thiserror::Error as ThisError; #[derive(Clone, Debug, ThisError, PartialEq, Eq)] @@ -159,8 +160,8 @@ pub enum ProverError { TranscriptRejected, } -impl From for ProverError { - fn from(_: jolt_transcript::VerificationError) -> Self { +impl From for ProverError { + fn from(_: TranscriptVerificationError) -> Self { Self::TranscriptRejected } } @@ -213,8 +214,8 @@ pub enum VerificationError { TranscriptRejected, } -impl From for VerificationError { - fn from(_: jolt_transcript::VerificationError) -> Self { +impl From for VerificationError { + fn from(_: TranscriptVerificationError) -> Self { Self::TranscriptRejected } } diff --git a/crates/jolt-crypto/fuzz/Cargo.lock b/crates/jolt-crypto/fuzz/Cargo.lock index 23bc4c85ae..3f0938ed6e 100644 --- a/crates/jolt-crypto/fuzz/Cargo.lock +++ b/crates/jolt-crypto/fuzz/Cargo.lock @@ -67,7 +67,7 @@ dependencies = [ "ark-serialize", "ark-std", "arrayvec", - "digest", + "digest 0.10.7", "educe", "itertools", "num-bigint", @@ -119,7 +119,7 @@ dependencies = [ "ark-serialize-derive", "ark-std", "arrayvec", - "digest", + "digest 0.10.7", "num-bigint", ] @@ -175,6 +175,21 @@ dependencies = [ "virtue", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "cc" version = "1.3.0" @@ -193,6 +208,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-deque" version = "0.8.7" @@ -228,13 +252,32 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "crypto-common 0.2.2", ] [[package]] @@ -304,8 +347,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", + "wasm-bindgen", ] [[package]] @@ -317,6 +362,15 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + [[package]] name = "itertools" version = "0.13.0" @@ -386,6 +440,7 @@ dependencies = [ "num-traits", "rand_core", "serde", + "spongefish", "thiserror", ] @@ -407,7 +462,9 @@ dependencies = [ name = "jolt-transcript" version = "0.1.0" dependencies = [ + "getrandom", "jolt-field", + "spongefish", ] [[package]] @@ -417,6 +474,26 @@ dependencies = [ "num-traits", ] +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" +dependencies = [ + "cfg-if", + "cpufeatures", +] + [[package]] name = "libc" version = "0.2.189" @@ -564,6 +641,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "serde" version = "1.0.229" @@ -607,12 +690,42 @@ dependencies = [ "zmij", ] +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest 0.11.3", + "keccak", + "sponge-cursor", +] + [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + +[[package]] +name = "spongefish" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62560b5029264a655fc461a0ecadc4d24862799190897d3225b3272b7a3f13b7" +dependencies = [ + "digest 0.11.3", + "getrandom", + "shake", + "turboshake", + "zeroize", +] + [[package]] name = "syn" version = "2.0.119" @@ -683,6 +796,17 @@ version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +[[package]] +name = "turboshake" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f892c6904b0bd5a9241eac848347abcbbbd2b9f3892bad23ac3e6efab8d6f06a" +dependencies = [ + "digest 0.11.3", + "keccak", + "sponge-cursor", +] + [[package]] name = "typenum" version = "1.20.1" @@ -713,6 +837,51 @@ version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.3", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + [[package]] name = "zerocopy" version = "0.8.55" diff --git a/crates/jolt-dory/fuzz/Cargo.lock b/crates/jolt-dory/fuzz/Cargo.lock index a4935d3be8..1278634d2f 100644 --- a/crates/jolt-dory/fuzz/Cargo.lock +++ b/crates/jolt-dory/fuzz/Cargo.lock @@ -597,6 +597,7 @@ dependencies = [ "num-traits", "rand_core", "serde", + "spongefish", "thiserror 2.0.19", ] @@ -653,7 +654,6 @@ dependencies = [ "ark-bn254", "ark-ff", "blake2 0.11.0-rc.6", - "digest 0.11.3", "getrandom 0.4.3", "jolt-field", "light-poseidon", diff --git a/crates/jolt-dory/fuzz/seeds/deser_commitment/valid-commitment-bincode b/crates/jolt-dory/fuzz/seeds/deser_commitment/valid-commitment-bincode index ddf7b592c91c6153136ee40d114e10e46b53f6fa..e5216b3a0198e21d24ea10c9addcbb70b3dd11ca 100644 GIT binary patch literal 387 zcmV-}0et@ZfB|WQPAo9Vayw2IU(T_<`jvZ1rZ$1<)DvVWES)6Z^#TCudvcNBJEU?T zUH}~sYt08(r^01wl8=Y=r@+T?mmI4+My*E;igY^X?TQ&QPfE$2j7ykSe|qfIA^NTU zz!%#Tes^C?Kb_&FcK@9!d)HRKh0S7IX_)_o=Qk>ExrP7$ literal 387 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 diff --git a/crates/jolt-dory/src/transcript.rs b/crates/jolt-dory/src/transcript.rs index 2adc982729..4f76a66528 100644 --- a/crates/jolt-dory/src/transcript.rs +++ b/crates/jolt-dory/src/transcript.rs @@ -282,7 +282,9 @@ mod tests { let mut adapter = JoltToDoryTranscript::new(&mut adapted); adapter.append_bytes(b"caller-label", b"shared-absorption"); let adapter_challenge = adapter.challenge_scalar(b"caller-label"); - adapter.finish().expect("the adapter draws a supported challenge"); + adapter + .finish() + .expect("the adapter draws a supported challenge"); let mut direct = transcript(); direct.append(&label_with_count_word(b"dory_bytes", 17)); diff --git a/crates/jolt-field/src/algebra.rs b/crates/jolt-field/src/algebra.rs index 926d529a3f..f123cc457f 100644 --- a/crates/jolt-field/src/algebra.rs +++ b/crates/jolt-field/src/algebra.rs @@ -14,6 +14,8 @@ use allocative::Allocative; use num_traits::{One, Zero}; use rand_core::RngCore; use serde::{de::DeserializeOwned, Serialize}; +#[cfg(feature = "spongefish")] +use spongefish::Encoding; use std::fmt::{Debug, Display}; use std::hash::Hash; use std::iter::{Product, Sum}; @@ -523,9 +525,9 @@ impl MaybeAllocative for T {} /// Every field in this crate encodes as its canonical little-endian bytes, so /// `F: JoltField` can be absorbed into a transcript without a bound of its own. #[cfg(feature = "spongefish")] -pub trait MaybeEncoding: spongefish::Encoding {} +pub trait MaybeEncoding: Encoding {} #[cfg(feature = "spongefish")] -impl MaybeEncoding for T {} +impl MaybeEncoding for T {} /// spongefish's `Encoding` when the `spongefish` feature is on, vacuous /// otherwise. #[cfg(not(feature = "spongefish"))] diff --git a/crates/jolt-field/src/encoding.rs b/crates/jolt-field/src/encoding.rs index 8c2acae7b8..4a4d393960 100644 --- a/crates/jolt-field/src/encoding.rs +++ b/crates/jolt-field/src/encoding.rs @@ -7,6 +7,12 @@ use spongefish::Encoding; use crate::CanonicalBytes; +#[cfg(feature = "solinas")] +use crate::{Ext2Config, Field, Fp128, Fp32, Fp64, FpExt2, FpExt4, PseudoMersenne}; +#[cfg(feature = "bn254")] +use crate::{Fq, Fr}; +#[cfg(feature = "binary")] +use crate::{F128, F192, F64, F8}; /// The `N` canonical bytes of `value`; `N` is its `NUM_BYTES`. fn fixed(value: &T) -> [u8; N] { @@ -26,32 +32,32 @@ macro_rules! fixed_width { } #[cfg(feature = "bn254")] -fixed_width!([] crate::Fr => 32, [] crate::Fq => 32); +fixed_width!([] Fr => 32, [] Fq => 32); #[cfg(feature = "solinas")] fixed_width!( - [const P: u32] crate::Fp32

=> 4, - [const P: u64] crate::Fp64

=> 8, - [const P: u128] crate::Fp128

=> 16, + [const P: u32] Fp32

=> 4, + [const P: u64] Fp64

=> 8, + [const P: u128] Fp128

=> 16, ); #[cfg(feature = "binary")] fixed_width!( - [] crate::F8 => 1, - [] crate::F64 => 8, - [] crate::F128 => 16, - [] crate::F192 => 24, + [] F8 => 1, + [] F64 => 8, + [] F128 => 16, + [] F192 => 24, ); #[cfg(feature = "solinas")] -impl> Encoding for crate::FpExt2 { +impl> Encoding for FpExt2 { fn encode(&self) -> impl AsRef<[u8]> { self.to_bytes_le_vec() } } #[cfg(feature = "solinas")] -impl Encoding for crate::FpExt4 { +impl Encoding for FpExt4 { fn encode(&self) -> impl AsRef<[u8]> { self.to_bytes_le_vec() } @@ -70,22 +76,22 @@ mod tests { fn encodings_are_the_canonical_bytes() { #[cfg(feature = "bn254")] { - use crate::Ring; - matches_canonical_bytes(&crate::Fr::from_u64(0x0102_0304)); - matches_canonical_bytes(&crate::Fq::from_u64(7)); + use crate::{Fq, Fr, Ring}; + matches_canonical_bytes(&Fr::from_u64(0x0102_0304)); + matches_canonical_bytes(&Fq::from_u64(7)); } #[cfg(feature = "solinas")] { - use crate::Ring; - matches_canonical_bytes(&crate::Prime31Offset19::from_u64(5)); - matches_canonical_bytes(&crate::Prime64Offset59::from_u64(5)); - matches_canonical_bytes(&crate::Prime128OffsetA7F7::from_u64(5)); + use crate::{Prime128OffsetA7F7, Prime31Offset19, Prime64Offset59, Ring}; + matches_canonical_bytes(&Prime31Offset19::from_u64(5)); + matches_canonical_bytes(&Prime64Offset59::from_u64(5)); + matches_canonical_bytes(&Prime128OffsetA7F7::from_u64(5)); } #[cfg(feature = "binary")] { - use crate::Ring; - matches_canonical_bytes(&crate::F8::from_u64(3)); - matches_canonical_bytes(&crate::F192::from_u64(3)); + use crate::{Ring, F192, F8}; + matches_canonical_bytes(&F8::from_u64(3)); + matches_canonical_bytes(&F192::from_u64(3)); } } } diff --git a/crates/jolt-field/tests/spine.rs b/crates/jolt-field/tests/spine.rs index a8a5eb9200..7293a6a40e 100644 --- a/crates/jolt-field/tests/spine.rs +++ b/crates/jolt-field/tests/spine.rs @@ -12,6 +12,8 @@ use jolt_field::{ }; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; +#[cfg(feature = "spongefish")] +use spongefish::Encoding; const P: u64 = (1 << 61) - 1; @@ -20,7 +22,7 @@ const P: u64 = (1 << 61) - 1; struct M61(u64); #[cfg(feature = "spongefish")] -impl spongefish::Encoding for M61 { +impl Encoding for M61 { fn encode(&self) -> impl AsRef<[u8]> { self.0.to_le_bytes() } diff --git a/crates/jolt-hyperkzg/src/types.rs b/crates/jolt-hyperkzg/src/types.rs index ed5349fbcc..2ca14e1a4f 100644 --- a/crates/jolt-hyperkzg/src/types.rs +++ b/crates/jolt-hyperkzg/src/types.rs @@ -1,6 +1,6 @@ use jolt_crypto::{Bn254G1, Bn254G2, JoltGroup}; use jolt_field::Fr; -use jolt_transcript::{FieldTranscript, U64Word}; +use jolt_transcript::{FieldTranscript, U64Word, VerificationError}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -111,8 +111,8 @@ pub enum HyperKZGError { TranscriptRejected, } -impl From for HyperKZGError { - fn from(_: jolt_transcript::VerificationError) -> Self { +impl From for HyperKZGError { + fn from(_: VerificationError) -> Self { Self::TranscriptRejected } } diff --git a/crates/jolt-kernels/src/reference/naive.rs b/crates/jolt-kernels/src/reference/naive.rs index 4bf1cd16e1..f22f372e93 100644 --- a/crates/jolt-kernels/src/reference/naive.rs +++ b/crates/jolt-kernels/src/reference/naive.rs @@ -760,9 +760,9 @@ mod tests { assert_eq!(reduction.value, proved.final_claim); assert_eq!(reduction.point.as_slice(), proved.challenges.as_slice()); assert_eq!( - prover_state.verifier_message::<[u8; 32]>(), - verifier_transcript.verifier_message::<[u8; 32]>() - ); + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); } /// A derived table materialized at the wrong point survives the sumcheck diff --git a/crates/jolt-openings/fuzz/Cargo.lock b/crates/jolt-openings/fuzz/Cargo.lock index 71b977cad2..9ba7cb3d6c 100644 --- a/crates/jolt-openings/fuzz/Cargo.lock +++ b/crates/jolt-openings/fuzz/Cargo.lock @@ -554,6 +554,7 @@ dependencies = [ "num-traits", "rand_core", "serde", + "spongefish", "thiserror 2.0.19", ] @@ -622,7 +623,6 @@ dependencies = [ "ark-bn254", "ark-ff", "blake2 0.11.0-rc.6", - "digest 0.11.3", "getrandom 0.4.3", "jolt-field", "light-poseidon", diff --git a/crates/jolt-openings/src/error.rs b/crates/jolt-openings/src/error.rs index ef287c12df..b3518889a3 100644 --- a/crates/jolt-openings/src/error.rs +++ b/crates/jolt-openings/src/error.rs @@ -1,3 +1,5 @@ +use jolt_transcript::VerificationError; + #[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] pub enum OpeningsError { #[error("opening proof verification failed")] @@ -24,8 +26,8 @@ pub enum OpeningsError { TranscriptRejected, } -impl From for OpeningsError { - fn from(_: jolt_transcript::VerificationError) -> Self { +impl From for OpeningsError { + fn from(_: VerificationError) -> Self { Self::TranscriptRejected } } diff --git a/crates/jolt-prover/src/dory/stages/stage0.rs b/crates/jolt-prover/src/dory/stages/stage0.rs index 256fa30d97..ca0cdb20ae 100644 --- a/crates/jolt-prover/src/dory/stages/stage0.rs +++ b/crates/jolt-prover/src/dory/stages/stage0.rs @@ -26,8 +26,7 @@ use jolt_verifier::proof::JoltCommitments; use jolt_verifier::proof::{FieldInlineCommitments, FieldRegistersCommitments}; use jolt_verifier::{ absorb_committed_program_commitments, absorb_transcript_commitments, - validate_inputs_from_parts, CheckedInputs, JoltStatement, ProofTranscriptConfig, - JOLT_SESSION, + validate_inputs_from_parts, CheckedInputs, JoltStatement, ProofTranscriptConfig, JOLT_SESSION, }; use jolt_witness::WitnessBundle; @@ -494,7 +493,8 @@ mod field_inline_tests { None, &mut verifier_transcript, ); - let first = |transcript: &mut ProverState| -> Fr { transcript.challenge().unwrap() }; + let first = + |transcript: &mut ProverState| -> Fr { transcript.challenge().unwrap() }; let expected = first(&mut prover_state); assert_eq!(expected, first(&mut verifier_transcript)); diff --git a/crates/jolt-prover/src/profile.rs b/crates/jolt-prover/src/profile.rs index 8c92fca080..0788f57242 100644 --- a/crates/jolt-prover/src/profile.rs +++ b/crates/jolt-prover/src/profile.rs @@ -932,10 +932,10 @@ fn prove_workload( chunk_profile: AkitaChunkProfile, ) -> ProvenRun { use crate::akita::preprocessing::AkitaVc; - use jolt_transcript::{Keccak, ProverState, VerifierState}; use crate::JoltProverPreprocessing; use jolt_akita::{AkitaField, AkitaScheduleArtifacts, AkitaScheme}; use jolt_openings::CommitmentScheme; + use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_verifier::{JoltVerifierPreprocessing, ProgramPreprocessing}; let backend = match backend { diff --git a/crates/jolt-prover/src/stages/drivers.rs b/crates/jolt-prover/src/stages/drivers.rs index cbf1808efd..587adb62a9 100644 --- a/crates/jolt-prover/src/stages/drivers.rs +++ b/crates/jolt-prover/src/stages/drivers.rs @@ -796,9 +796,9 @@ mod twin_tests { sumchecks.append_output_claims(&mut verifier_transcript, &proved.output_claims); assert_eq!( - prover_state.verifier_message::<[u8; 32]>(), - verifier_transcript.verifier_message::<[u8; 32]>() - ); + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); let calls = context .session() @@ -907,9 +907,9 @@ mod twin_tests { sumchecks.append_output_claims(&mut verifier_transcript, &proved.output_claims); assert_eq!( - prover_state.verifier_message::<[u8; 32]>(), - verifier_transcript.verifier_message::<[u8; 32]>() - ); + prover_state.verifier_message::<[u8; 32]>(), + verifier_transcript.verifier_message::<[u8; 32]>() + ); assert_eq!(verified_points, proved.output_points); assert_eq!( proved.output_points.delta.value.as_slice(), diff --git a/crates/jolt-prover/src/stages/field_inline_fixtures.rs b/crates/jolt-prover/src/stages/field_inline_fixtures.rs index 2f8df790dc..90f9d8f2d1 100644 --- a/crates/jolt-prover/src/stages/field_inline_fixtures.rs +++ b/crates/jolt-prover/src/stages/field_inline_fixtures.rs @@ -484,7 +484,7 @@ pub(crate) mod twins { use jolt_claims::NoChallenges; use jolt_field::Ring; use jolt_program::preprocess::PublicIoMemory; - use jolt_transcript::{Keccak, Encoding, ProverState}; + use jolt_transcript::{Encoding, Keccak, ProverState}; use jolt_verifier::config::JOLT_VERIFIER_CONFIG; use jolt_verifier::stages::relations::ConcreteSumcheck; use jolt_verifier::stages::stage1::outer_remainder::{ diff --git a/crates/jolt-prover/src/stages/stage1.rs b/crates/jolt-prover/src/stages/stage1.rs index b17ebed54a..2a5f00dc13 100644 --- a/crates/jolt-prover/src/stages/stage1.rs +++ b/crates/jolt-prover/src/stages/stage1.rs @@ -195,9 +195,9 @@ mod field_inline_round_trip { twins::replay_stage1(&mut transcript, &out); assert_eq!( - transcript.verifier_message::<[u8; 32]>(), - prover_state.verifier_message::<[u8; 32]>() - ); + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } #[test] @@ -316,8 +316,8 @@ mod field_inline_zk { .unwrap(); assert_eq!( - transcript.verifier_message::<[u8; 32]>(), - prover_state.verifier_message::<[u8; 32]>() - ); + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } } diff --git a/crates/jolt-prover/src/stages/stage2.rs b/crates/jolt-prover/src/stages/stage2.rs index 35f8e38ee8..96a970a727 100644 --- a/crates/jolt-prover/src/stages/stage2.rs +++ b/crates/jolt-prover/src/stages/stage2.rs @@ -278,9 +278,9 @@ mod field_inline_round_trip { twins::replay_stage2(&mut transcript, &config, &public_io, &stage1, &out); assert_eq!( - transcript.verifier_message::<[u8; 32]>(), - prover_state.verifier_message::<[u8; 32]>() - ); + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } } @@ -436,8 +436,8 @@ mod field_inline_zk { .unwrap(); assert_eq!( - transcript.verifier_message::<[u8; 32]>(), - prover_state.verifier_message::<[u8; 32]>() - ); + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } } diff --git a/crates/jolt-prover/src/stages/stage4.rs b/crates/jolt-prover/src/stages/stage4.rs index 34868b3f51..e7efee5a38 100644 --- a/crates/jolt-prover/src/stages/stage4.rs +++ b/crates/jolt-prover/src/stages/stage4.rs @@ -319,9 +319,9 @@ mod field_inline_round_trip { ); assert_eq!( - transcript.verifier_message::<[u8; 32]>(), - prover_state.verifier_message::<[u8; 32]>() - ); + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } fn fr(value: u64) -> Fr { diff --git a/crates/jolt-prover/src/stages/stage5.rs b/crates/jolt-prover/src/stages/stage5.rs index 8d2b122ade..ba113af26c 100644 --- a/crates/jolt-prover/src/stages/stage5.rs +++ b/crates/jolt-prover/src/stages/stage5.rs @@ -227,9 +227,9 @@ mod field_inline_round_trip { ); assert_eq!( - transcript.verifier_message::<[u8; 32]>(), - prover_state.verifier_message::<[u8; 32]>() - ); + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } fn fr(value: u64) -> Fr { diff --git a/crates/jolt-prover/src/stages/stage6a.rs b/crates/jolt-prover/src/stages/stage6a.rs index 829260f28a..d78ad56810 100644 --- a/crates/jolt-prover/src/stages/stage6a.rs +++ b/crates/jolt-prover/src/stages/stage6a.rs @@ -291,8 +291,8 @@ mod field_inline_round_trip { ); assert_eq!( - transcript.verifier_message::<[u8; 32]>(), - prover_state.verifier_message::<[u8; 32]>() - ); + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } } diff --git a/crates/jolt-prover/src/stages/stage6b.rs b/crates/jolt-prover/src/stages/stage6b.rs index f9810056d6..34ea402866 100644 --- a/crates/jolt-prover/src/stages/stage6b.rs +++ b/crates/jolt-prover/src/stages/stage6b.rs @@ -445,9 +445,9 @@ mod field_inline_round_trip { } assert_eq!( - transcript.verifier_message::<[u8; 32]>(), - prover_state.verifier_message::<[u8; 32]>() - ); + transcript.verifier_message::<[u8; 32]>(), + prover_state.verifier_message::<[u8; 32]>() + ); } fn fr(value: u64) -> Fr { diff --git a/crates/jolt-prover/tests/akita_e2e.rs b/crates/jolt-prover/tests/akita_e2e.rs index b8c53e4197..16e6ef370c 100644 --- a/crates/jolt-prover/tests/akita_e2e.rs +++ b/crates/jolt-prover/tests/akita_e2e.rs @@ -38,13 +38,11 @@ mod akita_tests { }; use jolt_field::Ring; use jolt_prover::akita; - use jolt_prover::akita::preprocessing::{ - self, AkitaProverPreprocessing, AkitaVc, - }; - use jolt_transcript::{Keccak, ProverState, VerifierState}; + use jolt_prover::akita::preprocessing::{self, AkitaProverPreprocessing, AkitaVc}; use jolt_prover::akita::witness::commit_advice; use jolt_prover::JoltBackend; use jolt_prover::{PreprocessingError, ProverConfig, ProverError}; + use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_verifier::proof::{ClearProofClaims, JoltProof, JoltProofClaims}; use jolt_verifier::stages::pow::PowSite; use jolt_verifier::{JoltVerifierPreprocessing, VerifierError}; diff --git a/crates/jolt-prover/tests/e2e_matrix.rs b/crates/jolt-prover/tests/e2e_matrix.rs index fc321c9a03..7162e12b2c 100644 --- a/crates/jolt-prover/tests/e2e_matrix.rs +++ b/crates/jolt-prover/tests/e2e_matrix.rs @@ -323,9 +323,9 @@ mod matrix { use jolt_akita::{AkitaChunkProfile, AkitaField, AkitaScheduleArtifacts, AkitaScheme}; use jolt_prover::akita; use jolt_prover::akita::preprocessing::{self, AkitaVc}; - use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_prover::JoltBackend; use jolt_prover::ProverConfig; + use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; use crate::support::{self, GuestCase}; diff --git a/crates/jolt-prover/tests/engine_twins.rs b/crates/jolt-prover/tests/engine_twins.rs index ea8d2807d7..b9bcb8f142 100644 --- a/crates/jolt-prover/tests/engine_twins.rs +++ b/crates/jolt-prover/tests/engine_twins.rs @@ -173,9 +173,7 @@ fn clear_engine_twin_matches_generated_verify_clear() { ) .unwrap(); let output_values = synthetic_output_values(); - let recorded = recorder - .finish(&output_values, &mut prover_state) - .unwrap(); + let recorded = recorder.finish(&output_values, &mut prover_state).unwrap(); // Verifier: draw → begin_batch → verify_compressed_boolean → output-claim // absorbs (the low-level clear path the composed `verify_clear` wraps). @@ -315,14 +313,9 @@ fn uniskip_prover_twin_matches_uniskip_verify_clear() { ); let mut prover_state = prover_transcript::(b"uniskip-stage-twin", b""); - let proved = prove_uniskip_clear::( - poly, - input_claim, - degree, - domain_size, - &mut prover_state, - ) - .unwrap(); + let proved = + prove_uniskip_clear::(poly, input_claim, degree, domain_size, &mut prover_state) + .unwrap(); let mut verifier_transcript = prover_transcript::(b"uniskip-stage-twin", b""); let challenge = uniskip::verify_clear( diff --git a/crates/jolt-prover/tests/support/akita_backend.rs b/crates/jolt-prover/tests/support/akita_backend.rs index f578f5ccbc..b9319a5f99 100644 --- a/crates/jolt-prover/tests/support/akita_backend.rs +++ b/crates/jolt-prover/tests/support/akita_backend.rs @@ -24,8 +24,8 @@ use common::jolt_device::JoltDevice; use jolt_akita::{AkitaChunkProfile, AkitaField, AkitaScheduleArtifacts, AkitaScheme}; use jolt_prover::akita; use jolt_prover::akita::preprocessing::{AkitaProverPreprocessing, AkitaVc}; -use jolt_transcript::{Keccak, ProverState}; use jolt_prover::ProverConfig; +use jolt_transcript::{Keccak, ProverState}; use jolt_witness::{JoltVmWitnessConfig, JoltVmWitnessInputs, TraceBackend}; struct UnreadableWitness; diff --git a/crates/jolt-prover/tests/support/field_inline/akita.rs b/crates/jolt-prover/tests/support/field_inline/akita.rs index 16e2b26876..5dff493e10 100644 --- a/crates/jolt-prover/tests/support/field_inline/akita.rs +++ b/crates/jolt-prover/tests/support/field_inline/akita.rs @@ -4,10 +4,10 @@ use common::jolt_device::JoltDevice; use jolt_akita::AkitaProverSetup; use jolt_akita::{AkitaChunkProfile, AkitaField, AkitaScheduleArtifacts, AkitaScheme}; -use jolt_prover::akita::preprocessing::{AkitaVc}; -use jolt_transcript::{Keccak, ProverState, VerifierState}; +use jolt_prover::akita::preprocessing::AkitaVc; use jolt_prover::JoltBackend; use jolt_prover::{akita, ProverConfig}; +use jolt_transcript::{Keccak, ProverState, VerifierState}; use jolt_verifier::proof::JoltProof; use jolt_verifier::{JoltVerifierPreprocessing, VerifierError}; use jolt_witness::field_inline::FieldInlineWitnessOracle; diff --git a/crates/jolt-spartan-verifier/src/key.rs b/crates/jolt-spartan-verifier/src/key.rs index 8a9f037016..d5089a66c4 100644 --- a/crates/jolt-spartan-verifier/src/key.rs +++ b/crates/jolt-spartan-verifier/src/key.rs @@ -202,10 +202,7 @@ impl SpartanKey { } /// Binds the terminal witness claim before the PCS opening transcript. - pub fn append_witness_evaluation( - evaluation: F, - transcript: &mut impl FieldTranscript, - ) { + pub fn append_witness_evaluation(evaluation: F, transcript: &mut impl FieldTranscript) { transcript.append_labeled(b"witness-evaluation", &evaluation); } } diff --git a/crates/jolt-spartan-verifier/src/lib.rs b/crates/jolt-spartan-verifier/src/lib.rs index 486e379f87..bc268b05dd 100644 --- a/crates/jolt-spartan-verifier/src/lib.rs +++ b/crates/jolt-spartan-verifier/src/lib.rs @@ -29,7 +29,7 @@ use jolt_sumcheck::{ BooleanHypercube, CompressedSumcheckProof, SumcheckClaim, SumcheckError, SUMCHECK_ROUND_TRANSCRIPT_LABEL, }; -use jolt_transcript::{Encoding, FieldTranscript}; +use jolt_transcript::{Encoding, FieldTranscript, VerificationError}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -92,8 +92,8 @@ pub enum SpartanError { TranscriptRejected, } -impl From for SpartanError { - fn from(_: jolt_transcript::VerificationError) -> Self { +impl From for SpartanError { + fn from(_: VerificationError) -> Self { Self::TranscriptRejected } } diff --git a/crates/jolt-sumcheck/src/committed.rs b/crates/jolt-sumcheck/src/committed.rs index 1885a6f204..48bdced2ac 100644 --- a/crates/jolt-sumcheck/src/committed.rs +++ b/crates/jolt-sumcheck/src/committed.rs @@ -3,7 +3,9 @@ use jolt_crypto::VectorCommitment; use jolt_field::{Field, JoltField}; #[cfg(feature = "committed")] use jolt_poly::UnivariatePoly; -use jolt_transcript::{Encoding, FieldTranscript, LabelWithCount, Transcript}; +use jolt_transcript::{Encoding, LabelWithCount, Transcript}; +#[cfg(feature = "committed")] +use jolt_transcript::{FieldTranscript, ProverTranscript}; #[cfg(feature = "committed")] use rand_core::RngCore; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -283,7 +285,7 @@ where transcript: &mut T, ) -> Result> where - T: FieldTranscript + jolt_transcript::ProverTranscript, + T: FieldTranscript + ProverTranscript, { let coefficients = round_poly.coefficients().to_vec(); if coefficients.len() > VC::capacity(self.setup) { @@ -327,7 +329,7 @@ where transcript: &mut T, ) -> Result<(SumcheckProof, CommittedSumcheckWitness), SumcheckError> where - T: FieldTranscript + jolt_transcript::ProverTranscript, + T: FieldTranscript + ProverTranscript, { let capacity = VC::capacity(self.setup); let mut commitments = Vec::with_capacity(output_claim_values.len().div_ceil(capacity)); diff --git a/crates/jolt-sumcheck/src/error.rs b/crates/jolt-sumcheck/src/error.rs index 62061830bf..76bf23413c 100644 --- a/crates/jolt-sumcheck/src/error.rs +++ b/crates/jolt-sumcheck/src/error.rs @@ -1,4 +1,5 @@ use jolt_field::Field; +use jolt_transcript::VerificationError; #[derive(Debug, thiserror::Error)] #[non_exhaustive] @@ -254,8 +255,8 @@ pub enum SumcheckError { TranscriptRejected, } -impl From for SumcheckError { - fn from(_: jolt_transcript::VerificationError) -> Self { +impl From for SumcheckError { + fn from(_: VerificationError) -> Self { Self::TranscriptRejected } } diff --git a/crates/jolt-sumcheck/src/lib.rs b/crates/jolt-sumcheck/src/lib.rs index d78b3303b6..fa400472e7 100644 --- a/crates/jolt-sumcheck/src/lib.rs +++ b/crates/jolt-sumcheck/src/lib.rs @@ -121,6 +121,8 @@ mod round_scheduler_tests; #[cfg(all(test, feature = "committed"))] mod tests; +use jolt_transcript::Encoding; + /// Transcript label used for ordinary sumcheck round polynomials. pub const SUMCHECK_ROUND_TRANSCRIPT_LABEL: &[u8] = b"sumcheck_poly"; /// Transcript label used for univariate-skip round polynomials. @@ -133,7 +135,7 @@ pub const OPENING_CLAIM_TRANSCRIPT_LABEL: &[u8] = b"opening_claim"; /// Absorbs a sumcheck claim scalar using Jolt's canonical transcript label. pub fn append_sumcheck_claim(transcript: &mut T, claim: &A) where - A: jolt_transcript::Encoding, + A: Encoding, T: jolt_transcript::Transcript, { transcript.append_labeled(SUMCHECK_CLAIM_TRANSCRIPT_LABEL, claim); diff --git a/crates/jolt-sumcheck/src/recorder.rs b/crates/jolt-sumcheck/src/recorder.rs index cd0a785daa..8688911b64 100644 --- a/crates/jolt-sumcheck/src/recorder.rs +++ b/crates/jolt-sumcheck/src/recorder.rs @@ -201,8 +201,7 @@ where { type Proof = SumcheckProof; - fn absorb_input_claims(&mut self, _input_claims: &[F], _transcript: &mut T) { - } + fn absorb_input_claims(&mut self, _input_claims: &[F], _transcript: &mut T) {} fn absorb_round( &mut self, diff --git a/crates/jolt-transcript/benches/transcript_ops.rs b/crates/jolt-transcript/benches/transcript_ops.rs index f2b4939d3f..e1391c3fa3 100644 --- a/crates/jolt-transcript/benches/transcript_ops.rs +++ b/crates/jolt-transcript/benches/transcript_ops.rs @@ -2,24 +2,27 @@ use std::hint::black_box; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use criterion::measurement::WallTime; +use criterion::{ + criterion_group, criterion_main, BatchSize, BenchmarkGroup, BenchmarkId, Criterion, +}; use jolt_field::Fr; use jolt_transcript::{ prover_transcript, Blake2b512, FieldTranscript, Keccak, PoseidonSponge, ProverState, Transcript, }; -use spongefish::DuplexSpongeInterface; +use spongefish::{DuplexSpongeInit, DuplexSpongeInterface}; -fn fresh + spongefish::DuplexSpongeInit>() -> ProverState { +fn fresh + DuplexSpongeInit>() -> ProverState { prover_transcript::(b"bench", b"bench") } fn bench_append_bytes_with( - group: &mut criterion::BenchmarkGroup<'_, criterion::measurement::WallTime>, + group: &mut BenchmarkGroup<'_, WallTime>, name: &str, label: &str, data: &[u8], ) where - H: DuplexSpongeInterface + spongefish::DuplexSpongeInit, + H: DuplexSpongeInterface + DuplexSpongeInit, { group.bench_with_input(BenchmarkId::new(name, label), data, |bench, data| { bench.iter_batched( @@ -28,7 +31,7 @@ fn bench_append_bytes_with( t.append_bytes(black_box(data)); t }, - criterion::BatchSize::SmallInput, + BatchSize::SmallInput, ); }); } @@ -57,7 +60,7 @@ fn bench_challenge(c: &mut Criterion) { t }, |mut t| FieldTranscript::::challenge(&mut t), - criterion::BatchSize::SmallInput, + BatchSize::SmallInput, ); }); @@ -69,7 +72,7 @@ fn bench_challenge(c: &mut Criterion) { t }, |mut t| FieldTranscript::::challenge(&mut t), - criterion::BatchSize::SmallInput, + BatchSize::SmallInput, ); }); diff --git a/crates/jolt-transcript/fuzz/Cargo.lock b/crates/jolt-transcript/fuzz/Cargo.lock index 4d2df0902b..0e1f3723e3 100644 --- a/crates/jolt-transcript/fuzz/Cargo.lock +++ b/crates/jolt-transcript/fuzz/Cargo.lock @@ -193,6 +193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", + "zeroize", ] [[package]] @@ -290,6 +291,7 @@ dependencies = [ "block-buffer", "crypto-common 0.2.2", "ctutils", + "zeroize", ] [[package]] @@ -413,6 +415,7 @@ dependencies = [ "num-traits", "rand_core", "serde", + "spongefish", "thiserror 2.0.20", ] @@ -423,7 +426,6 @@ dependencies = [ "ark-bn254", "ark-ff", "blake2", - "digest 0.11.3", "getrandom", "jolt-field", "light-poseidon", @@ -436,6 +438,7 @@ version = "0.0.0" dependencies = [ "jolt-transcript", "libfuzzer-sys", + "spongefish", ] [[package]] @@ -655,6 +658,9 @@ name = "sponge-cursor" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" +dependencies = [ + "zeroize", +] [[package]] name = "spongefish" diff --git a/crates/jolt-transcript/tests/blake2b_tests.rs b/crates/jolt-transcript/tests/blake2b_tests.rs index 62bfd9b6d9..b236e28659 100644 --- a/crates/jolt-transcript/tests/blake2b_tests.rs +++ b/crates/jolt-transcript/tests/blake2b_tests.rs @@ -4,7 +4,9 @@ mod common; -transcript_tests!(jolt_transcript::Blake2b512); +use jolt_transcript::Blake2b512; + +transcript_tests!(Blake2b512); #[test] fn test_blake2b_known_vector() { diff --git a/crates/jolt-transcript/tests/keccak_tests.rs b/crates/jolt-transcript/tests/keccak_tests.rs index a5c81fe51d..16b6d2548b 100644 --- a/crates/jolt-transcript/tests/keccak_tests.rs +++ b/crates/jolt-transcript/tests/keccak_tests.rs @@ -4,7 +4,9 @@ mod common; -transcript_tests!(jolt_transcript::Keccak); +use jolt_transcript::Keccak; + +transcript_tests!(Keccak); #[test] fn test_keccak_known_vector() { @@ -21,7 +23,7 @@ fn test_keccak_known_vector() { #[test] fn public_message_after_challenge_agrees_including_empty() { - use jolt_transcript::{BytesMsg, Keccak}; + use jolt_transcript::BytesMsg; for public in [b"public after squeeze".as_slice(), &[]] { let mut prover = prover_transcript::(b"keccak-public", &[0u8; 32]); diff --git a/crates/jolt-transcript/tests/poseidon_tests.rs b/crates/jolt-transcript/tests/poseidon_tests.rs index a3dbb4403b..fb591915c8 100644 --- a/crates/jolt-transcript/tests/poseidon_tests.rs +++ b/crates/jolt-transcript/tests/poseidon_tests.rs @@ -3,11 +3,13 @@ //! Poseidon's squeezed bytes are not uniform, so it draws no field //! challenges; these tests exercise it as a byte sponge. -use jolt_transcript::{prover_transcript, verifier_transcript, PoseidonSponge, Transcript}; +use jolt_transcript::{ + prover_transcript, verifier_transcript, PoseidonSponge, ProverState, Transcript, +}; const SESSION: &[u8] = b"jolt-transcript/tests"; -fn squeeze_after)>(f: F) -> [u8; 32] { +fn squeeze_after)>(f: F) -> [u8; 32] { let mut transcript = prover_transcript::(SESSION, b"Jolt"); f(&mut transcript); transcript.verifier_message::<[u8; 32]>() diff --git a/crates/jolt-verifier/fuzz/Cargo.lock b/crates/jolt-verifier/fuzz/Cargo.lock index 3d65d2f161..0136e814bf 100644 --- a/crates/jolt-verifier/fuzz/Cargo.lock +++ b/crates/jolt-verifier/fuzz/Cargo.lock @@ -657,6 +657,7 @@ dependencies = [ "num-traits", "rand_core", "serde", + "spongefish", "thiserror 2.0.19", ] @@ -768,7 +769,6 @@ dependencies = [ "ark-bn254", "ark-ff", "blake2 0.11.0-rc.6", - "digest 0.11.3", "getrandom 0.4.3", "jolt-field", "light-poseidon", diff --git a/crates/jolt-verifier/fuzz/fixtures/advice-consumer-bundle.bin b/crates/jolt-verifier/fuzz/fixtures/advice-consumer-bundle.bin index ad711395c17e2a7891d2766e3959487969730b4e..ca1639673e7a353e82d694307adfac1c2e04e69f 100644 GIT binary patch delta 85382 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 Bw2uG) delta 85378 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 Z%Apqsj;mQcV4zg7 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

    {|-wC3I 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 diff --git a/crates/jolt-verifier/fuzz/fixtures/muldiv-bundle.bin b/crates/jolt-verifier/fuzz/fixtures/muldiv-bundle.bin index aa0dbd932b53ba44349332acc06d14e56c281307..2033c9737248844073beb40ef17a6b1ae36a5835 100644 GIT binary patch delta 84851 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 delta 84849 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�XaU>1}-b+rH?7x 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

    W4$=fCJ_B^U}ec zt37SZkAa6~QkhA+=(0BgCf+a=D%!eXd6wa}AD(iLNTV z-IoUVZi-Km5;`KH{{S@S`t|?- diff --git a/crates/jolt-verifier/fuzz/fixtures/zk-advice-consumer-bundle.bin b/crates/jolt-verifier/fuzz/fixtures/zk-advice-consumer-bundle.bin index 078348adac73be412ad9ed8b34deefb912538761..2a66f14ef35be03dbf2e6c150066a6966e476c41 100644 GIT binary patch delta 87485 zcmeF&Q+HoqAL#Aaw)1Oj8;#xAwwt7}vtrw}(HM=L#*LlEP8u8gKgP3|e)s<{PY1bYp)Ha!Fm;%)1}Z@{A~7ofK*6akSyrx|;!-T$Rmpj{YhoHkJvub!(YL;~}>?xA@c+ zLQk*cPo~oT3^AT^ia5Wq_6xGX^Z$iybJN#yO<;8Q{@}scUR@Czo zdFqLU3G-BS#F7f&V*vpJP>JpYx|mtLDcw7#T4nA%Lq?bGYW63|ERVzozOI=pShg?J z1lThLAH1_m`--n!&}#ZK9Iuidj>KGdqIG>H;L&@pI8dGtixp>fdd9P9E8g#SSL@p8 zEfQUa3(*w%M7~#W;;CFQ<%7e`jiAd7R;s32afys!rie){Z!3Vkp2B9sC+YvG}~m)OMeG|NMa`r$)#f-E6o0>8m2Wr()~rSd6X__rB7 z2vk`+3KGoe~9DkV*dh>KB|ESt@OGc(8IU$k4bw!21*;}UiXhQM8~EcPR|I{JZpF~hgz}am6YZ2!Gh(9 zojzS#w7qjXL&~#EGsus`(0p97*MA1CzurnT^z#FY{7-rkxSwKJyML)tTgzaywQyMC zCh6W+J;yt4zD`-l#(K&?^fU5PNy5QK7bNH>yUVk?sHEJ#2Uf}DFa^d%((+n}pHpc= zkc1j@M#vZb@ThnV<`xnQ^;%$@YFm9f+CF6rD&!<;&C(w(xm)^o)Z2SY;O(YDd$UGqJ#JTta_ zfJ&%T*<^l++~OTIO|0n(rb~p^oj(wou=PnjXBgYpBr+a{Q#!pwOZ;wXfry4uMJr=) z+kAw1-ae&6RTj4TnN%gEO-VN^;L%@hSVjQUTiYAqtVs%^!VI|nz}byws}vrm9&$qw z+KiNjEcYZ`q^Yvi%RE*ke~Ml^__W|voku?a(GnYTd~QQNv6WaFn)giNY`pCTmYdg5 z1Z@C`LyFjnRpKW0*wPP4Z!e7yHnL2Lo=lMs5#6ZI`EKR*

    aSXDAD{=v)CnG}r^# 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�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 z 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 delta 87483 zcmeF(Q+HoqAL#Aaw)2Z^r%97EHX7S%+!$T4?WAF2+qRv?c4Hg+KgP3g86_>T=PK*K4&V0+7 zPxI#qPSP~Q{=Ajrf@B?VhH07GQIz_XFRG};*$B^Xpds;mdyxRHmCzbA`GY{K3*3(Y z7n^;eTRMfm5#l5K^oMi}#xg156a%V!3G1fhF4d}+wWzkNkepiV{A_7YaJb|p{?A1r zX42mhofcg`)(MWC@xr5C0meP|*e?j^Yc^3oxAzPk`c)2zj=5EVZx-Fi*M5lJ`nQ`ydV_n5=cN_|1!?)5yzg>~xF%dm=t& zU=JUk`SlMgwpZ5`6(97_;_;Is>3Yw~DoP;hnYs9S>6a}e0A33v8C+E7jR0hVwMJ18 zllJ8`gF1*&2!_-wGYS)HK`y?48veAdKCso?5LO^m(VCVm7~%vm!v)KY-#P0Ty$1?` z4=3mG^DYFT-n&_5ty$I{+jOI;6?=Fl(u0R+=}m9xJROG?I_;JUr8gCEs?UOkie|Bi zyr6ck?ZWzZAelnx*E#_P2FFsX9P`$Xb$^wNIBQ|Y9oN?H1N7m@rg7r{Vk&aw*a{+V zvv-i#%b@S^=~rmYj;N~vaKTP~zaswY+qFPG*`gl7t4LDrd|u;^_PQS-sdFIhdI z^}~kjk)BmTNgpi>XV0`iz(hBGl>4E{eh`l9iSVx(OcLs2Qtn%e4o(oYZyv@%_I_!nCe07GQ3E@ zxt5EDV~0H2kxBNHkd450L_n8tUw!*+k-x8*q>R$|4g2LMiHKPn|Mr)ivBxaa4O4KU zpWPDsN5{=)#Wya5Fb>r+#wM-$`e4=*rKlP*Zw=&HT~7xU^|=?kmWCx{Uj5z$p1R## zYJeVM+j5|{&Ub29hT6OHoXvxtL!;rG(2ZiP0dK~ylR6ATGRj|5>X0nGb*3O zClT(&R~)3JxU3^tx^M;xi@u)k2S%e{s>~WWa_S$oINj7;qmRGt&_bU4Ifed8i^;QxrPi2{%XxORzUX!KK}r=4UL01P?TH^3K1APceN>oUMT&sw0_fj{tcN?DGlS9)i3%! z_H*C{gG7=NLy+(6B2gsuQ7yEO`zu*7k+w+?$+(7e;gMe6;LoP$#=5FsKHGBimXzMi ztJE)u}-J=S8ItM&|G6tI^!VU9mFn^u@mhfHFg2kobvoetxr*n4qFuP_|J0P z%ZWG#ROOL_CeN67r3Y54+UtZw6eP8?`M06O&hvRB&kp9$d+e>%gseO$uvWlYat@Oq zonr?7_?ze%Eio6h(R{d;5k@BM6Gf;=%CXM6(1~X)Q7MlJJqHFmV&OL9uX?Y-QjM}F zBCGE`ao^Ff81Lcc;;2?ueJ$lDQL=Ap@tYh)KM#MnnJ3`1@VSz~Fht839Zaaw!l;hD zVUBNe)<;(}b_v%Dld5>!LT>_3BvSnHi{%6l&G{_WmH2)4UAbMVc=yCEyB)YbrjSNd z=c7rGF~}?q>6}RbVs!Kc0V2e?9V<9VPWqe>Zy=@sCago8isL;z7Wt5 zJawKm&FLLR<-ofu^cw0u4Y5Jpi1p~Y0{$5D%dPicjvmRTUXuSS?e1G*(rAb857m zCe6}sVGR+17$v`LzSi#aE~aB?P@&JVjgrGYZ*lS1f(*ki1z^VO)>lX{uS-(5TwbJRC5 zmMhtY`99@YINT_swT>s$G!|;&h-6IbG&DNH2LHMSK3N1s=eT1m%e>RjrEiT7sTal< zl|9IvuUj-D#YQ~lZlRZ6Fiz&4xHc@%qhcf&ZBg`~D9L$Bvl|0??>g6v<8hjK$(WZ& z#tf=UEE5_j423l4w3C|PqDw1S?cI`Lm1gW{3;9^=XG*se;1;}P_e7es?SeI%Rq+lX z8tH^Wk}Qh-?Y~|wWi8@_u|8er3=&NJ+3jYs5Cj50w=aVeJz6mKqA3Kd}0nQBiDl64fZ1H&t+vP1h{p%+l)*O z0u!sPdvT25>FL{d-$uDCio3As8TkCn;Z;=_?qp=ReBDgoiuttr_sCHep_hM8A8FSw z&y!Y`ch+niaKed2$CP)59c)!R^MMP_)_dsU#co;zkZ=QnN?`F_W0j5pLSut8&@Iz~ zokLV03iy+C_EnkQxs(mTbh)>-GaY$MNNva03K@w#t}7Z0Jl!y(DM4lpk-ptE6{R+( zbo=ah5%CNeJZ9K8XqOYr4msnEu_0r$|lw+vl7_aWnTKOy+D{m^vbt2NFwM zo0v@4Ne>`;2$~^kd=}%h_o96zGo5Wux9K`6=KlUMo@@fv@6mzWTeww$r_(y7%q)~{SKoXDB;w&>#1lk*WINK)W_}(&`Ac-&7E`s10 z=uZCvyX0aRGVhR40S?~|ZLJXL0&xq-3Hl<*$ONFM&lmB~`=HM-*@iQ$Am5mtcWurh zi^PZs=bBQZ<_bp_MIU2QreU7n^wV5829C`lZnAbl%j+~gEjDg?PT$%o`5b1PM-6;X zH+gjuVwfbYzprVMAksTh%PY!G^{q>mhua%(yOwg=w!P~QWQ#sq=SdJ!yxCw?58L^a z^#Vl^Zyrh5u*?CJ>YwUn;Gb%KqE-=H;E!AzpMppMl`$h*d`gK6h=@fVdmZXde8_1ke##dypzUkI(qQ#|wdEVqDAdma%e z`%PD(#gGfmo|FtOtU7-7V>W7yU*ij9`(84+Gm+n)x5fa(@1dPAw4hS{?SwxoqCjoj z46g5BnBHFGh4d4b((BZ)OC8G`R~jS6Dt=XE0i1o(w-&|8C>*tHf>ojU#fOkF^_@yl zURA0x8CqX%aLIj0!|&GEH017B#&N7^H?SM*f2=7uiLB6T@}@%&+M;G)=Z;|2nb$sob?C#KGu zH2=?hlOvrp*j~HIW zI1OU)1?U5uZm-Pt`Hs@+VbZ!Km`{JQjtUD?m0JJIQ*PWOVt4fi>&NjrJhzV&28`@+&{abI`F4lTD=6NE6m0esk9LC*6Cnl5`L z*B&;5^JDYbDVV{OcO+y#(MjU?%yGDe9pfwM_)`TJkIFd5QW(c!Dfw3~x)?pp{AmPi zq3j!=_BV?Ig{?47wl0DBdOa_3<+|q-FQ<$|ADWls%z_C9+Ot_(dy3A+S}_2eVEYx+ zrT?Sh8xfy-15H#Fspdh)SVksLH&CJ|Nj361+HU)zbuA&&j2E(H#;&TuUR9O$Y7 z)KAVQiB&0OOt@lLOw8=d0^(=v(M4}^Crxzn5kX~KR`*$R(N>x)fZD!E7#kA?_v}G39-7YHU8F9BnM8c%=y;Tmdfpbn z38(3c0As61`c)<^DgqVhA2-vWR!Eao-oX83g2rV*JxW{FV4Rb@*q-{pYqc95j4M0w z1wjq=2829lj7a}VaR>uvTL_cW%lSb;K0(?`b31FAcT`PiLGr1>TFzuApqGXgfqeGR z!>w9(M=~sX1-)@E71wF2ceiaZJAP3aLytKI`v{gf%fWX_@yza;r)rd+aRmki28buM@vF7Wgfl(= z`<3+E4Uy;QyI(elaW1^|K$I6PnH_4WSJZZp@p-K}NrZPC(&!T(qU`pewwF@6n9CKR zP_6*7ZB18##Gf1SNz0nyw-`4;KhSkOi4L*^*lPs13BP}*@FQ)K@W>F#jlpz5A(1z! z+VrVaTe=8@!zex7Ab(^NLULfn+PFOrJ zg{ehI>MfE_#qD364`Wx%aC0sqHSkY&pT84#jhF~dk^>Ix$W4T(SbCeH)t>@5R3#HW zNi)*y;3OlaAexX_0>);)Hu2L4RVS)H7m?T)qk}zprD(iET1Kf`TBFmYL;Kz~bCYH@ zvU5o4r(vq+><4>jVi(2ogm8}^7ab<7`4IVbCF7BYl>6Gj*JTo1HKex6xASYoIO{x-S=Hy@eA*qa4M?_k{ge5zflwl9q2b?$Z*WKQR_MIs&?#=jAS#)tHRcf zYcjpBNG)a<#gh{e-8R4s-#xF-wqTaM0(*438zjaTprnyybLRmQJpPwOXpF2T^4P8= z`W;xM9XMKjQC<2ePNV6PM|xfvA(5+2Q|0Np!trb(4gHCBju7p!onH^St7T#kR(f*@ z8s~K=_0EL&_nARvGpz^-gKE9(G2kA1W!Ej_7?iCSoqetHih4F#bo6#p_g$U_Gk64BL9eUA%EiF3{ z5+mknx_hvo}rA6;5i#U=5eq)mQZ;P5%5nh9;$$4VXodTSVlPVrs zK-1B+UU8{c$etgp{EJpg=0Z+<7)+@5qqX>LRUOK2%CW{%64#B4?+HR+=+pJhz`ZN! zmH|B-(KbCpTdhYMt0WUiEan*5ESw8c)~?X#F|G&z`U}h z{iE1ZT4}c+BiL*hsIIjJ!B4$aYKja3reHHvdHvcoL{D+lwN-0_fyzfuu@db3`>t*! z3(_O$aa#-ICwGgi+lu_xiPd`LMO>jc_Gh~HOL3I3zOr<3DreSV+6OPN9MUT=mX|2^ zL)r|Ke#h!^e>U7dspBGC#y7l$0I2wlNtsez@iK}5g_*`ba^;A;7%GCwhv%0sp^pC@ z#GoZMFPwshfZe7`3qJghX9;Ss;J1K3wgG{A(pwH+e?_xITnp|ohFXfmaf{%Fvus-9 zJP=tFDO8XrG(?7uk7!qPYj(myYJ^zgwOWyQCqy3o8Pm)~05hokZXM0<2Eg=1EgQ|S zSQim%r*KH1V2(k$!K1J+jw`VrliCOrxP}Im;A_e|S2cAn9+%7$4pRJreiu?RLRZZ2RR_6;+mYX;u8Rht=V#=0tmYZMpN z(Bpgy$Iqf3sul%+`DXoVSvXs&3hc2Mo(6(VUKi_?qC1FES)jv;KYB0$>g)8jc$~$W zTOVzJ;hV0`?_M(TJVgFpA(}f3#lYny8W^!5ZJo{QA{q2>YKCG2fV_0MtTTq1QYhM5 z;=aL$fWoR2YVOF4z>u||!l%bA1K$i8iz?J!PU827Y7U6RFzk?{XjZ)dLZ3O`wnY1`Uq( zY3R793aoCjcxEROSR)ki?;2_RT~Ct|lzMKbu}83c|AsIWRol5LWtk9jJ)DeojBz+; z(B_d|V43jBzlU1PT#<;~ouQvB-d0h#OWkqcFl2w0Cw2VIaX%F?Qkwc}Y4e%j3c>BXY&>TIc+L0V zD~1$x&tYIo*dPO^fvh&1>GF;|XGWJn;Zz~|#G0S09__pNb(9w4f@SjTiG2CYAf(U% zqq>JFPW`+^Abjqi_DjrX@Y%qZ@WS=IYBh-m=8c)XPh8d=7O;l3!A`oXETG{YPW}R6 zwQu#FmkGjkh|XHad6@b+q0Z?_d!ko4Kg^qa>XhQnievm}PR_RTiR|#f1k^x6X^wy^ffe@YGF)Ew0(r*G9l=@j?`YBBbVo@!VfT~GK=kNEyeFjcS6y|;EVyww;y^ZAXj6VXN}-$$_mufP~5)NMw*4xvYj823JuE>z*=;c+y;)S@L{+@(AkaSjObDRbWImI&H zQ30w4c6|g-2Vl&!c^X$rofmJzUNU!7gnI~2HHcgx3G`(lJs3Yzi90iq!;rF*X7E$` zTh_d^o5OMa5RxZ4N2y(70vmcCt9p- zp^RxFKOE?K#iqc+=DYbe2n4ynOf%K~+a$^&%io)X*d(d+D_*Es5_uvO??Ij%3BgdO zB5ZxDvc<6fH18d^_C7PsZU2>J(yCyu+Tn@3RJ@g#vn_EmHt6Upm>QP%+RL* zVe67J-K=9)1Zmg+cYgY%&SX3bSrGBJR^Zt;5`)Suv=LnX`U{LGaD9^wJa3YKnF!0z z1iH&r@L@xld6(oPK$2kA$!47R>Lx~jBM*;Fw|tsBG8nr>9969gZ8kby;50sm-I^sm z`Wi&^QX6In6PIEU9J6v1u>Ns5iVZNb?McU*+36Le$9yH=m2gRIp6ZTvCD;kD^WSUt z;NsEW_w~nsBmC%`^-w$4a&+P)iF4IZAakQU$g$3?*7NjY^gsIsllti+S;PwnwU55+ zHCd~l!+P>$v^=uiLNc*fm{j}ua&=Xv+a}NR6Eb*I8jp&wlU72KR2&TbCr|w(0x==^ zHR!jzW|k80y#3G>G%V5L+sg8PH_87i$^R?K|0~J=E6M*W$^R?K|0~J=E6M*W$^R?K z|0~J=E6M*W$^R?K|0~J=cayx1fjhrIEa0T$iZM~cVWpsQFHB#9HPm1u#l29_TTMK- zC(d7PmDq>H?p2x@W?{|k7;3ECpFgzgf9UXXEr_Q`nMd$ZtQsS`^&b2_rf9=4hS-7P zKDKdnYvu46MNhqm?*Q_DE*!N8vD>v!C^uMgNM*PL!Fi(T-ExQN!J!e$t_4`t%7?vLE zL12`>6LQR3nQ+w?4g*dQ)MXd?I(>_+gi0Aug*ZKE+w4-F7(_uB)X*k+YMCSd1xoGW zZ>noX8LI0k7=8-jV^?#tIs?$1x!4gDxJWdV@6xEcq9^lN^Mqh*aN!|HKXMGgGv7!h zl>aNI-Ro&sW{XO0A`;aytWB9jlG6+o3Urw{M)8eEb{h8=FMxaxm}$F$X9z1Vif3cz z9``3_Po<1VqFZE}$riKX0R@LS+BcO?MFahCA$UeXDlv#DjmwV?N7d()#sNg|4S^G4 ztWet_A3=tLqI3iL1?tJ3W8N_t-Q6mu_1+PbZeEy~Ojiau@LtYakzY~#r*qoEZ1H&1 z3o^XiO+-JZ9s#SPNtEtXr;B~m{LvLZVmI(CV^s3uurQ*0_4EZhSPPHEc))Xb8fIu3 zJ~>a*u)vt)V6JVNg@YkP8R31TZV$|1>*0d$+70PUv*|0$)y_B_-yr22rw!AQrCJe3r#83Ld@U?DOL)lC;lY{@(F7)Q5q9Xc^LoUapg-8#QElnX ze`FtB10qo7Ez?xf4Sgyw$li!315ZR2s!W3WoCWJy7%oTiUmf(SndKb)$WFfO#Fq4a(Y2_RglHWQF@;MvIW)I| z>Q=H#{svTWQ(o1;Tyt+OuRWa}ho~JPv<=u15q|cRXXN~GnAq@ig2v;H80wi}TI0MD zY*+sYfcrFmd>MeBly$_Ki347q!a}f%^6p_$ciW)}WLJN6{Kb0q8jUF15!tpGPZw5A zySAauP$zIodWlD`{vb{nv!7aG4hI7sVpr#;M+49|ul3W<;QX+~86FeTb(k)U7HKN_ zHVZlXo*ay?Yr|{laJh2UMy38dwt9TZpjeJ2RYBDX{+VRhs{_rr``N!Aob@VFY z*othc7T5t~f+!6B$tJ#a^G_4}oIYQ4ds{SOe9(97<+W^MSIsl#;`?|T$CBM}hg*sT zN!Eb11Wk{#B@`loP_!oK&Og)d^O<5DLp8?z-*hkXyPKWwvnwT3-+Rg4u0tGCjvu9o zf6}AK*jKlwp$^(WM}_sR8xx@H7A@f$J97orN5Fp%F~64C>hK+c{ybqQ?M_@BD5^Xw z%xOR`v?nIe^xLxjjQBhmp0~R2Bmc91P0Joo?g!^cAb-I@ja8K8?X-ODDC}hvB#X}n zW78=FV-eYp_(w93e7gH99n%*cG;bTZS8Z#luhgCv-^b%Wu-9^L;`YzT&ez;j;c0x~ z_~c!EF%i!w1)x*zvPo7xPurnlgVr@60Lm~rl=a)Nbeiv#{X^kqjvjAs%)%TY(uqXC z5K;tta15Kb5H*`~JB=?=FQx%S_d}8)ln1S@9QmUg{7l>BvooK}A}z7Kq~j>*=2~B) z-xP968A@n`n3RRj`paNgEf#4v(F_f8m6^@-<# z?+g=NwR=B*H0UCilcfn%tdKoGJWWCaZ+e1U3qN;Max_4YiRD-Whnf-$`6FePM71T( z9w7l0Renyr3;1Lay0iqMHZ2y8dBNz}Fs-|cmFLn5s6FoL%AXXgS$m$*hD z@HwwzQ0CCsN!uq1$J-uKBRfFdietVZm`<|wT9~fxZyiT!{Xbsdd#a~`Y)ocEvgh}#3zy? zzH+sOw(l7}x77wtk*+y;^s&8l2v;XWe{aHG`Oag5g&6P+`t&meAObBWg*HEWUP=s8 zaTieMkqL#)EP6OE>SmVt!Wl0z-E&edOCpT_2)2U3u3=DX0$cSomG4XqAdff*-7(0Y zkWWV!{AT(Ld8RM0^_bTAgiVjfIKsT4tFQ1ldrb$~gbhaQR|z|XJg1;BD@pE4>ysf2 z_@$+X;T;pIaDN#g>Mh%9Hib;k(O0<83#BA-npEGoytbPQjU`*c6{U?2VXB#P>PJ%Or6JrWDrtKs+mOly+q1H%7O}BD zomqYD?Rdk3yY}R(sg#jK;uXF|(O8p2<=M$*Q>^1S5q65ew%Ju7NEQ`2ym-T4yDhwF|h-w~q;mBXyxOZ&`{EZC!nH}PWPqRhhX%Ph373s}qjA`PDn(g1G z31}7?A=oBNAou1mgLU$X8slPdS6DiOszHG)hRkwiF_tLB)<-WAEZ5`hS3+jAv1*Yl z=;}3U4&-iBDRcq)rS|WFHvvZ=FqjBI>EyDM8O*>ESDF?8H3@~6RloVz<{a`gr0W1O zn`RpJRC4`V4f$ZP*mQpbb*nRNOP?G5Dgdo#klx`;mW(GYFZTYdp8Ik@3(KgRB9^%J zPKRkDvVvYJ!V>x}8A1jA9=g5h*`sfQ(={u_?&Hz>WP;$FgX^SGCKNwEiWD=7;RcTl zp-PIIB(n%u8%sZeBw|fpDP63UK$sg0v_(9Bp1fBYrMGp9KAx@*PQP^p=rh;Vc9X+sAnXBR)z# z@()EzjFflwLTSZO&0DHHSR0yYPJ1^0N(~`*Rw5SQlRH&ICRtkd!HABe;;?5%HBE-M z&pV?db)-UL+lE$@k2yRB{fS4Tb%(KqKHDah=llx0Edw zVd_>cRWUIk;&vExz9|xro|WwL za8S;(4?;`VZ^#9b##U5Fj20=9*mgY~mdaD$S(EE0sjcLaAA0VvwAV0|%owzR3GG=c z-W#32f#gY_=gFlVUSOtWXdj1fYy?Mk4BeGD0i@j*LQX`rM3x(8RXi`;eIk|+HEzUj z1dNzot3tZ4zbPlr2OwG5V}8z#l(o++8T@yqbDDaijmT-5pWZP~dy9HAFk>^x_Z2z{ z$XSGqTTBSAVzZUM zW^w zxSwTZ=~46eZvN|APqE`nkd9tM3!Q8NC=tX54YE-Wj?3BH6vVh@psc8lx{XUCf$klh z>QN!0jCCENP+W%f`+`Mh>v8;jf52l2TaD7Ofrjhy{ye z=N3Hlt%+fuJn;_UTuSgMN^PJDD_aw}>77-8x5S&UKD1JZ7H>^G5(&0~VlGw)@*BG#}^ zN2pEc3$>^*r_OGAsRZn@W%deso`xf`jO079>J=_|+WN;Rqx<1j`i zw~c!`)-x&Q1d`kp_NR;VQK0Kh2wTw%b2Vd4N7q>Di9$;^UfO^2^2Aqn4?c9M*8SsMp=ZnKpctQT&nH!mmeL&8?Oxuk%VA1MF6a<)8~)$C&7>J zq9?cM`_1Nr&&k-r2@;SDsQx0*5E4BUSu+DFuwu`;Z$3D895(T9{I(fEzzlq|^L)e*7#mAR$55+ws|G zMV;~$=4JYJ+DV=)nFa90@K$=3o^aAel@~Eh+qHa=G!mT)dHJ4I3W{2WL^C>i)WmHf|8GVpgQ`JbU=;O|uO zKSRmD->Kw(hLVB5Q_24fB?Et_lK&Y>2L4VZ|1*>f{GCevXDAu?JC*#;P%`j$D*2zG zWZ>^q@;^h#z~8Cle}mtW{DpCx(>=}`?`e+HBkG=KLx+jMoKkv!KHpaOd=)Zed848a#WA8gXZEd zoMnPYNwg?wNM(W%UIWO?UFBhS=#SOD?CNNFtXpV^?&=YQD_=^A)(^NH-!%3kdAeW( zC2&`NgvpVc;hF;j>~j7%&N4d3@{|L;NEroQ*q%s(&uouFRy^ls1i8VQ6-qRPk)6|W zX)-%6Jtp&;3TdU90)Y(0k1}}Z&G(;YZ4Xhy zHsN3m7)^cr8|GRDlInq`GO}ZmKHNXxt(5Kk9QAzb{3FbaNU&Rex)Tq%t2+dglw;4aUglimWK4n7VMoU{U&J+L^pN8aT|~K~TsZSUT}3rJPnf@ij7W>kixG1g7no z14r%C)D5e1Lx5VeY64HEK(N?W3P}9|4_Q2<90u)5xi{c>_^47WfX~f;bd_}y9h*At zvrH6rTd=9@9OJFoaT*u1k=2~QH@b}pBAymTW3Cumh&>=TETGofE``cIMtB_zzm9_| z==B}6RDxoc(L&3S2-7=m5KI{{1eciRmq#B{NltH25q0XlbaLNTHCitm_B~7*b}BOt zxKCw+?4ExJ*jDC7K};)Mk)dy6h9MA?#f>9!XI(zK79wD@qv?kFl7k%qJMB{VgQ*~T z`FSTzxEbgT0Q*#ipTiZI$EN$=PpY?5{1OxMUeKBOcg+4Cx?OEcL$T|f+I~=Nzg(2xDEKId~m-Fc%zRm%%A^oF7D2rhx z6%l*t5;V*8E*^STH?B!W)QrJXdWP5gNY()mIlB!>#a7V=F4zu_^?(0%frCK*ulEV` zol1U}An}eYKx(+U^nk{Kc_8rOB%Ur%JGfHFR);hmL~V6gn(y*-Bl>zzH=yOA-$;;y zt-5@L_ED1va*MzX0QEE+4lx6go;%z>YgQf>6eMc{JfwXDoyrD)m#7-s4KUERoD$6O z`ek?fYE@ij{MQBUS4*yMT$R5HDj4{`7ZE~()U<~!l&SfHAL46r7zX)se>xupeb96C z_B(;;Vx%`Ffp9QLs+7No7)IEep8c+1%ms6Y+a1{5QvS&46NARtO9cUw8KcSX7gX0b zM4|cIrc7QsNNF~rF`bIiJI*IV{AfX-WVi+9Z|XaN7%kIhjtF*>B!0O#aMQC!abCr( zdNhdvAn8Lgd2a8TJlL5hHW#8MJKGN)_cRr5LZb}{q8jO~DA1o*o=4eUXrIU;On;4aeG<0uc?(0|GFH8o4Tur%PRza<$8 zbbsk)g^!lwe>)m4sz?UZSclrE^_h9%T=EW?5h%OEg6xiZU8tK(HP`Qpu+8$5b?J%i z)v4eDtRw18)?Dm+eu32T#D*sGVtvUp2#)ziloa?9!wS;TrT|oGk8Ldc7H)iywp}O# zYkO|=Wm+g5!|yLE5Wk{wVZu_d1$?uENq)Feg9_%+IntJbTP$CV7T;W$xgO|5Q)iQ? z_#kYgBZ$yjXh37i5+O-=1M2us7n=vjG!6OuLW+4A1uVx(fz;o73+O;!ykdj8Ej1ii zYPLmw)c117kpV4{rwD^blsT>%s-{Y;AUPo2NeJ=8%6ls`I(yaRrCagh9XuP2u+T0( zjfV0o35b3pdv`^<@F+^O*pVw^WIkeanW&5E1RKNOI5*`r?)PAUn+&4i<)F zniYI?tC{Q3_uE^XT zIeYjLpxo(gn_6={#DqIFbJA1WtR0U1Cvatm0^;XTBQ+AOKeC3)z$m<3>Dfe`8!+p4 z+0akD(Uh2BivTHh$0NsI6C<{K{n$Zep)Y>fJ@)#3`_tP0)$^Os!!`n_iI+D*ZFp>S z`yj(-p{ImrX7~-=r-+{mH3e&X)VvBG6p!`UV312LCl5NW5bG;>QA5af0bo#I zFTUX6YWesQ$k$g4jdxiY&K$_R<;a&EG_rz8e@Z<3 zRkhIOpQ?bD#*FFJWixI6Se|=2Ld;88W`U5Hx!j+Ky~Po0qj9tAT+a+#c6%xe%3@A8 z#kc|F5ShvRRNP$0%;Wp(h3RNfKqnhZ)xM6HM`p2<-Q9t(rCZ$FrC#Vkp<92O*omNE zX73h?NxTIbT*~%z+U=chHqU~Zab;VI&bnL=K9-Zx`@g$_0xq^;Y%=6`dy&Vo5)atH zvLxRI1l%KPKa^-uz}$c+F697>WTC^nF=J`6;ZG8T>yIqF!KhN^SmoBMz=c{ZPlXCH z&bZo2|28hl0(WFnsR058d>ir_-_EbVBD;F|IeGIxhzy5Jj?X0p9Ib0G!yjF6C3c@S z?ltLC#RC~55iL7riQVe8`twyByCQ;-z%?9wTExv6)hIbi`?+5W!M}NkV(k!baFulY zOssyD#Tgaz6jP^)HX24L14Lf$jcg6^f)M_j0`HOeMQN<-Ff0Aoa>+Qi6zQ1qV}@u; z^^$#~etwV+^+~wtbh#&@8ArnIFIBjTE)p(0o%W=ck733Y-DT)7DE8%cJ)-_DfDfM% z)(5(W=7)>gnBFAPdN1R3&vT+Is-ILGjZ2L19(Va4cvvL`_w{K#Kq|)|;yjH0_Xb|C zI`+IwWD1EbS@PQCZ+mU%&ql>qh2q^q<6%bDJzS5*5RlVYM9??8e&pRsMs-iMt#D!G^;q1MoEtS@T!;4Vi{-@R*D!P~9sA ze~0(ZA=J@?;S)|&08R(t#FwkipBFBwUOma;Nnn>r41AQpK#kc9H_@=U;&xv(waOjy zeNZpet|XD#q^UZEOmuZ|-*zZyOt1o*lCn=74Eg+JgBW6xt})0{sRZv7Ij~1l4Y4S< za}esD?9@(Y_uP#YwB%(DS|N~#un1Vuym{=w*D0f~mH(vw0H)ZxB>ECMOy9mefT_?h zfc_hUUKF;~>Zwm;Qr)s4P|&*{7-ICDAM_-tD8W9pWsZRxH7E4LL}Ff)zHmQgW8YN6 zs;2mGK{Z4p9#?{wq`?$9o&=&d?T6O&BhymK7(ym9uM`*2YI z^lE}@SYORylD&DEaE?|Vt?3SH*zswC2eikt36%1&W^0LIp#%;>Uw$h?yt>oz)>Y$7 z9Jtw~kQA~e?cgeGS?*|j_ULJ+=M*BS4g>JypY>t@5#H>jrRjWx8>?@{1|73P$p_|b z@NIAzE}C{@<=%Gb7LM#VjtHfT?YXp;MLj`nT|9l0jH-`9N0J{6xn)_rIXX5SbTj2n zRtT)}>mAC*GI4iG#J8XYXo2Wymb+sv46jo6pTZ#$cS7z>odRqV=6~28kO^P}z3&f) zrf5h3$}1@~zWoqhxbb=`pWJ!cfboD$Qi}hka5LX(*@&*FRFmRt%eMOvr^<`(6p2u^ z{AJy&AiJsCBHYY;GHa0{aLZy3YvdMy)_yZ+XbEvY1|y$ zjs6vik|CWiORg5ZPjU%1I^9+A+hV#4){Wi*1E#SdH9cAf$-KI-3=K|gqqBnsFQee@ z>$FCqFHq>b%2kXA8ZUM$VTc-H;Nz7t3dZo{(DK$IKH6pQ5oZ!$ zj5*&*SMC3-_%<8k;Kqj#Ary3EM}b`Rvy9~~?W{Ke*{4(waqD}_FC!-*+cXkHM<&G& zH?7}=A;mD#@E*U#$5ICIg1zez z|C@rBDqNK>t#33ByGWTRSW#kyVG^W*!axy*7$#2YQXlnj&vtxiFZJrTMVJ}_wJH}j z2*2312qp#op>8%q!7KEoPPUkx-zZA*8alDidm)ZxDj8$fK06Ci+rQw?<|MEI@?l6B zZ_?vwv(C?uQ4S`s?P+yVcHj>|%VU#yGYrmnl!^!1vah(sCcG!-3wH8M24kfCzAf1` ze~=}Sa7ry1M4*)q<7yfqu)Np(+vExua5At2AAkdolo1KHy(w=>NGAJJ;|I5*#cyy2 z>TF6;vZZbnDm{QK{IC%u`86d#0z1mKF=dyZE`juwA$o5pP=BRYFD2ML+Ezd_#tHWq z1zQYp=|RnD)R*$tJa)h8OZvG5LlGHoc;$y$$sI&#MyzSh#7ms6>HjWt%COx=x4nr^ z{Fv0!P)S{W_g~vNhSpMg0QDe}_Zb_w87a44X8tJ{3IB^h3PV?xgb_y`9yneMV#v}bV1p%uq*P3hIc&XOq!hFesC&YRJ_aIX< zHk%{si<{RK06|T&%C$ne2-5ROb__h==Z*^u(b5_j6{oFmCuRZO z8>A=cx(4N)3_}gV8;>wWF7W_Ui?c3ea z-#X9TqXO2l`M75dCU#IJgS zy>tUAd;uziQ4S;$5U5Pt&D`f9nA>x+ z_IJAll_RMB$!IyaUX~&5>4F%2W?+6HxV=rAP?59N$#YjwMH~z+)uy;$If5D47lkj| z6?tIOafLBIg*04&i}mkm%-GVbz9D@PL03chIAJ+v%0UYlyiyv-mv$5ct~gnA z|4l(cxdUF?%Sy5X{y*`5%{*Z!{xzmka@9&_6XW^F!4Tzh{?eT4pVXXJ3T&Az=+ z^FTIPKD=?QlsnX}GbMRSjTsD|)~k9LLPF!*@nCILE;y z&_AtipY6Oar<(h%Ouv{6IESJ*ASiRozq&2CxNnvpFkNf(3LK7Pyrr!5t2m%T^)ZzC zI)J6YT)3BFy`v}?{~rLNKwZBA$c8@Nw&=&Y0uu`C09dGpP8w1h zEd+KB>zxQB$uX&_wM<^;yWIi6+tw! zUmv+;RzY;pwBu8uRPxWyVk`^wg)S-%XDKF>6-_{C{VWDM>u0VwRNcBUP%EtuOL#M) z;Jd0>-QZKb0PvhJy9=cTS1ZYdngOTay7ivLxP*a-x?Q zXV`e2FBE^8T8})Mg3_J#AvE!sGWgx0f}_*(S5VDxfm(7mzJmHpRs>+3A#JTF`gZmsf3*5ZaaZTy~kdK zi`En5HoT!dth$lmm!`L1)bjD`YWKrputYbQzUa`B_H---A@}K*ur@*G6pNbg)P~(y zskprn($6~90gcXoNAwOhesiy2LETzkd)=pg4D!TG)rrv42GxyH^jFtrY(EQh95JsY zAmx8!M9o0>mHcLb1z0gKU#a?QwC%Zkw<0_wuGX9-6%U+BOf2eVy0P&}c~lzft2HlF zRwSZ!M;)0Ic8#CqzRYoOm4suXtonF}Bi3;=|6s%om374yjm7~FrOZM%w9||LDV5dfT17zRV z1pjnQ6*Yg9SXQff7^X80>9KmXMUW4<8qDexa(qJwCH1o(ae0{`pJyv4u;vgZmoa|; zg(=Zw9TR{Fj$6T;1JN#}dI1tvr}$quZQaNN2{@heajsJ!yg`xdGmuewP~@NMndO1Ca7LNw@x2=T`wPd_U(imKAU z_>HaR*qR%e@aTFJ${f4yvuo~F5LAD;l<77xb##_^NuLr>PX$GHBP+aUBA?mh2A!Rf6)i5%kG(Zv=)=?j!tA`eMMr+(=f^uzQYJlAzrmU;ld8a&F8!kAFCKqA z5x-&OcX!&kd_}pXBrX&%wvz5BorRAQ`Z_Xfu@V)8_9GRLsi$E^p)hBO3=MxYCR|Io z8XFFJyeaF~$C$d1*gjqJ)?qkmC(qg4j%^YjLWyM05K84#p@u7>;whp=|L_8_^4LWi z$)Z)4$3-XmfB`!fPk3B%1`Y2a>7Eo*g7#E2&nZCS0aV6LaKy9*KUBy0&|u^IjWnk z(#k2yzAdZHu`I__ZcqGbgMO6L;1=r%3WSrS22k9%t(_zZJi*64?c0hO^mLHW?X^{z{bw*!FyN}S;xNeaJpUQ2A? z>k&csLbmrsN~}Y2g6MxFKy^3-5q!TD00e){Kxf$o`Nybxm@XJUslJ* zJ;8|U{b|Nq2PqQh5)G1Aa9kCtkLxXRzwAk9CQwUL!Lg5s_~qr26BQqd&HWvM6-O3Z zfx7Dt=@0*IzlML1sH%rpNz6=-ODgEU``MKt8~eHFPMnm^@2B=<*3bw!{z{6U?RMXE zJRXEIU-dAkb<*4b(zp~-2)HKPLYXjDQ@Y9&k7gH5FbomQQU<5_9b$quyZbiW*2Gz> zmxYsoI9yqc6QxNXq%Fi@q~&>KET+>c9KALZxtDI(nRS1wzsZd%|NK*(4Z#XJcc8hT zWGamMceaR72yxbCEEB4VdL-lBFkeNyen1Q@<)Ey#P;6Nme=P=LB}>_`PY>^tJfR&r z@0j|TPjn&sfB_=!}x(9R93EL_4Zloow0embY z*nAqG_1M`Q2>>5xWC-cLvQ(V5in~l(EeMNl8ZXGw?%ebCotiK8H=v9 zTQPqrOWlvh4`#mV#o&b;zfEZ71&xs;f5IexGnt@de>_<=WZO$9O2x8MLxBBcoNFfh z0HKyX>Jga6kv_U4J`T?^!Z#lI?t_j3H4HYgB?L~iEc<`~D29M|{sZhvAz9`r5gq|$ zhWUKQ=?G?vJzB7)?U?=@!szM?kN#(F{l+V$P!|Wq%-96j%ltqxHON+k1H* zWJ4NpGkH{>35ho*xl7*rV&K^eEDiycNB@&NBw|%-von9HZQ6=^6phezYoaLet08}E z{gV80Qs7yln^^%% zFa?JsH}1%X&P0LY^m@c*bFd}n-QQd@?@f0@X=Juu9Rm>qe!uB}#+yo%tOK!+qjnAB zUlE|I>k9={oGYC;oDO07FyU;Vl5T(8d;#UauZmf=puDP|wCMLX`wagE zDj?X+Fa8AkfC0+LZU=b=gx&{|*~pS;#v?m-T7fGLD{WFER1KBust=%VE~~a*Jl*CO%y7aziHP( zCK?Nw0#0F133DbvL&ty@d~6*^&OyYt%totMv!#6-73v_*>huO~f_cY+D!8Ynb#5wr zbEH#{>Sr2edkEKWyaFxw`K^B|Y&IeEKTvXAZgXK(Rg2a5sVq@v94nB|)r8&;3K!CS za2xbyQYu(0not7nYjx!c9b<%f9CR$V;ZYtF+7=EWpzWlhr39j2%tF_?0G;>C^A~^s z{AI5~l)S_wX?FMddTw94bKR%2R6IxIZj%}r>FyEqK}*2==U}HheIV? z+EBLJB@7KGBd41|=kE^f8k7WJ&YjCq?k8DY{z6{O5hr}8vs8cEEDy)Yx`JA5=0VBE5gwhph$4*tH(4!4FW>WjDqIF+s|JLevYe*_r97o`M`C4uJ z3~(Ix=*%knfB{d4sVqHo@I+o5KEU+fh5RI;iD~oAT-$#|%&t$JLly^?P3ZYg;AZ@| z#xt2VC7(Xx-_UP*f=ZhyVCg`={sXQ3{Ia@Ye!?21T+-%7s${&& z%}68p)2NTaF`)zJlziM9B;2Z%XzMGx-7BLoo)>!V5C|_a=0DvSco88iw?K_R3Iipi zpQrPPPMd#OFR95E8G2*{g=BKf%je4}*D+gvQ8c&rw zb`O(k<%~rqvbduzKetLT;9$G-B@ndT@S!;CGFg9+P1feEYUL<~75g91#om#w-IY+8 z?Xo2U%!u$Ovc61NxO?7wK$Rr|DW{w`k$U&h5=dtJ*;>e_JyADZvwB651lFN|C!IE2=~DbVxgk3U2=ORO%P|r2#o4mi zw!@+|GL8e@5RI6v&h|$O{ef#m4rBI20^WZ*>_)kqxVGd`OB8{jxFX3op#)rCpCGfP z@ET%U6L-)Eh%5R%vw#C)^F?+ZlPlAqrnPALmB-xYiGqpX5=`TyEn1lm`Q(tLAd5%E{Cok?enA@*!pVxdcig2U6k|uwp z8TLsvWSIu2wT&BzoyP>48?E@wV5@Z}>oa@nTZ9kIHo>3-qh)7$EtVYyK}#+j>o}K7 zR`!6R@RL>4lLc6r}-Rw}cTAE`S~b!rzLL#m6*AxecoV20SaUMMeBt z8l8OXXW1IpN`b%g!z9d_RCkre6HdCiw4HdMGFso{&*hQaSlxcp8cI(Z-DF7>E-jb)i}Hi$AvPLrE|#W|RvAb}ytnTFUTh$Smo! z=i(&BqS(J1j%b6!X5 z)LFF>%(UGF*bSdrJT}~e=sPRi=)3CXzo~H+v*i3e;wv1gxTM+ukH^y(sv@lO3ZBZN zeBI7ZRACSSeIsfGm|9knWc`0&TT7Ku+QYxG@3RTB$506Us!j<7E9nJblL%|*>gHj3e^=ywP;TnPE{6Q$l1r%#16#5 z8ZmMc6BJazxu))EsuLU$bQJL>3{>5Edx=_M;D)73{rD>mH8mE9m z>_mFoKN-CP5=F!l+)Cj&-ObWh@B0OVh4fPt0Ha=dR%hf<%_6yEeSR3`AX7LX#6xAW~0 z1L4)MHOck7NT%H>fi_f~MbVnk>I!GPcarnhV;@_Ot`gltE7g#@cK<}^yA&~?`l)~T z-UE$styy_9dI$U+;mH+i<+o2@ZlYVT(0?)Vz3@U((+q$80E2%floTdVfS;$H!$@22 zaLk8o;a z!SpL+_)xUCfX`<)GwJ&vZeGAog(MNee&rcIkQzS3zqq}%o&hjpZ# zk_f$Y1bZ!1LP+d|Ud-rxIfr@yYm(w>=sH9Uk^k`?>myc=07$s&7;rMZ@ZeJ|xMuqE zahf}*^TmGwE~7&5q@FlQ2%Niy&;2zQW#l z9w{z7QK9cNqHLdhkqS;Fb28%^v>+$_{9I#BKirOi*rltvaZC?N-)v81X9!Le& z1z;3@-;=DG4?|W|;xx7+win7icCak}K{OhD2LP3;&KR8C)q8UmK=qozmv(kt@V0HG z?5%&}RiWOi$1f_~yLMI)5pTeAePgmJT#KRr-|IxU^b~jCFCkl58yyw5d-rH8Ck4ib zRp;4_l3gArU&VUj!+g`2jE*grdnN9NJl$%l8!Trp)#EBvntCt?wk>xBS+4t2zWMg2 z;Un2ZGHZ7WMDxjaDaSvzVRNjRN~T1^&G~<-&S@&uYHtjRR7_7n$zhENR%)aV3k*n& zA?Q|B_?#lv!=7KY9VY_d^nNGKj2S|r%2Pm1>13o(gt(e&&l>-zC$P$iu)8Q8tGF#3 z*@Q@-`@dP7(^sId!KCEL;0=#Z!++lF+i?=$0%d@fprN3dQGZ4p;XDK!T3;(_h%JA| zh~cMrv)51*EGn_A}#r$7=}82EFK|}nnmpPb!-{{ z@xB;^g)NHbIP@@9N=2v=%8>Jfbua~wpUKjwD<{ltp&rlIERj%M_mdSe+&P6cxYX2$ zI5iT+Tlq{$dT^|iyvNT^*`#rTL!no@!UrJtOShaDoM5*I1KNDb}u#V3D~;eYES zSroQs1jT`77^QFfUuA#n!JKA^BeTHfHf4nJ@UR>- z(CD|wDgO$_|I~rOVK!T6p^Yvb8@>7l1mJXO24mbK(i#_ndOe6VQD>jS!;HiVShWhF z8VlNtPW4Wn#koj_Q%{>8$G?I`zE&cEd%j9YunhJ=Q4!AN0yOe& zLxNb$slwh0&FeJH;8$q{;j4e(Bg-5fO6So; zO4{8AAUL+C!UGS$Xcf02Mf|bsm`Fa^FBq*h5ne$6rwYr}bj1|0XQbF2iSOsA9b z!#$Erhb^w!e5vd@IwMnoe=q<)QZWA`|C)k?V@BthHr?j9Am`Y8p@cPT3F*N-S@i8f z&KV0zxBR91HVt?HQ#*f8?qK?*Dw4;7kjX&SAU`jr89)pB!r3hncHUj67!j#GJd=~I zQH)Mb*Rh;`mppb4UNtI6!sF!NthzC1XACBAN!7F80H!*R^s8>j;#){l-6AJuhWQ*m za|@hdpF}{|V+w<^ZM5-kIZOV@TeWS=IUxY|_O4)mUXpvU9{_)!VZM`RA7l$|X^Co0 zsK#ORJw>q<`+xxguWi!#$ADEdW{Ck&VYVP{YMLzdvc*liVTXK~)qxdEQodT!9g1#I zCNtI(Xy8QrFb_=<+=40Z5R>GLMUN8;6x?f1BF1-{fCKPy`^w0GU%?QGIZcu-~VA@<_Dd@-4U5zeVoO0Frw=RWIG!xVZdT(j4RArWuhL%u}wq^8Y z5xY7?2w_Wfs@sv5z6~7#DZor8E~S)PheAQx|mQ6fVML z<5Z#xh^rF99P826k3KHM@Uj)_w?udk_yB@B`Ts?DlvD}cMz}rcFB}5H$KU8+h;%?Q zA;8)2huEM2?y^rO*DD_Xs4uizCc2e7Q-A=>K#O54$sR%%rb`^E$ggAVcTnHq5^|(% z4q3$c#~FXUTRDx){OWc@9zMdiVC|`M>&3l013slI@E`kt0jgTtA*mX{Lb!FfF2?!7 zJ8mP+fQhBJ=l6o5?yQ!267z)mW$Ip>hx4il<;H6zXWnQMB;T*UaYj73zCScQ7t+FW z=y*Iip-Sj{!B+=oqIH`ZA+U~eHQBW%nE9P3CKZ2;D#`6^_~~bbSSYU6VkRNoE(2(= zc8z9=U}blumJJ7b;NZ(~_FlWBTEU`G&!ufx%zP21{YqEk;R25x%@1@PY-u*F0-yo$ z1xg58x-K)bUTrE6Eu1`Fig5h;rY`BYwex3jNAFqD`c7G9PiAShh6#`<@HD3CMuZkR zW(t4qnU%*@pK-W18$5-ZU9^3}5%VDk{Ux16^>n95GvX?Zc%#x8CRS%J(y|Z=T$IwH z^Vl_dnRLRHLkP#_a#RtF^jy2StGkZAr#7CZ6cVM6w3UXKBu6cQ*sjDH58@oG9!3BV z56%;sk4N}!Ig8`yg#jY9DL`H<(z=@RFra@A*vECHEiNczNm3QjGV$RP<}Po-ks|I3 zD9%^dtQUa}`+x!MWku*+>tW0^BaAGNbX@!WE4NFcdUI?f6Vy5fJ7)~C_t=#|YzuO= z(@Q2j*73q(m?omcO+jhpZp#&O{BJ6A!tLv-hS9uGVp>s@c)rd<90DeHS#6e@ArOCR zzI+rRbrZ|iR|!{U>2jQ9_R%f=2#h!2MHd^R+@M%j5ygxlF`Z3+q97hsDEdsgH=95Bi7LLQfu9YI9SFO9(3$x5x%+?D0Sln! zwI=SBRw9JTPM~rDU<<|BRnQ6(;+D0p52j zBbgkbof9Us=>X%Euudab`u-sC=WsU1Z-i}%T5mdz)&~|BiA%%+wj(_98VQGQhu^FE z`z{X*FBIHw5p9uxRHFLE4ZS_df+~Bh)LY$nJEi9#fIVN{JO@27$OV6rQaV|HKTyQj z)t?4(S%PXRyZPO592u5nTfQXb^u0nD6uoNq90=Vks>=Z|b#FA>M1~DXv-=)zp%rkj zd3N!rNaor5Ae3>m4%!Z9a_c0m$c**bULR0&K#`A=YU@695 zt`PgO+`z0D`+xzNI&S>xesi6PQ$xs2kPWu%l|`a^7{@+H$W`v?`*svW-|v_=vB;od zZBv){4*|x7FucQ4k{^#OVuR>K%5w>)$)@0|bnBL86q#vAA}xR57`>*@rsXC!DyuWf z82Lpf8a6y$jMVdK*$Opxd~t5o`ZrsJXkyykVmXu}&$-qFnZ{$(ubuvsi zqd}#ffx=HfQ27!M)xJ|EI=tcjH=L?pW9+0Hi5PZvV7x74->x=*4gLv#(SXXew+Ia+Wy4< z0M`R2xfZ-65zk4_r`Qs$B^BeUfuUExdMd&r+#5}TdOCpzxI1h+4}`$-=X!B51!<7{ zzQXw)(+3UQF&0)wUbiud1k2OLA6cXyLq8bHoYRD*i_m|!Kt-YWSqOwU*6Q>=h91#@ z6damk32%ysq}uMiAq@v1KXRf+P4#%*Qn+EA{3WMzs2Tf!0ki7wQirGjy-^K;rE+-k zzr9DsIx{9lV5LZNT~aE~C1XO8-CU@3Yn-5MAC))OlHE`Gw&0vaKo_@8kEwB z-4bBs3>tq*<13gE%~2Fd_0aBl7hku+TrCmpcnJOc05D}}TdLxF&><MgnwimYgf!VLX?ka5L8A$>H@*EmF?vj_@<1z@DdxxIhe>hyrueZRM;wf=dlIXy{15qo0r z>ANGx;2YeZm8|N)gOCC~5O#%sT0Bu)REW#scFOH{aEly9Gage$&UPIviZnW-8vRWC zLy1Xm09PvqQSdXnAG00tA-ZFrZ}w1Gxkp&-0I(U-|FWB2=Pi~l2m*ynu38$M`+xx#HcI7HP`*75ch_V_r&Y%jqGvKjzhR&l(yXe4yh<>II#UU00I-s7ss<;z z<7w;{`j4>c_;w*Uu7t?0j-vNRMnq=3ki-n}O$+9dc0u|qY1nmWVgy(0bbwWT<@5UrV`M%buN@@Rz34uiGevN+z=vqCwIcKb5s;_p8kFc9iXnd> z!L%J%FBCtyU*9C<&drJI;^35aoIsmqu_7tz2k#FTXYKQf%JduvB~+U>f>;M9CL?Yt zM-rOIRxeo?9%oavaVZ;EFh1aUjHGFp9ye2lpVi^#EEfBK0TY1|w1jX0R-@Lq-pKnUBY^kPoaMpDk~vx z1TfyY9E^t5-%rznizoLiq{o)KFgv&;oH?c#uP4ke<*l*gdt5q5*;^)`-$&9UQQw(d zF%3AoMJ*=L=r5nE^&+st&?{pN?%ig^SrfnGG`Ii!eHT!NLHUsMxC&PZERYiGKL`0W zP~BS}&=I1%6Gu|8MG%GpI*EVCiV6j7cBgY{5lxAs!PbF*S81%ChHaE(+xJnNN*^YO z5!?qP%gnC~uWq;YulC8bYuHSlQ2ZaNnpilv@KV%_?eHy+Gixwck&}$0XVhqM~A(z0-SMj7@;AOGPeA=`&q7yS3rM&a@7?Q1jSbI zD>m>KD0y~@eld0N+S{Ab+UN8QlRCZj!pOcOK&~t+F2iMT&5$Z8{5xmU5=kws?^-Eu zgn6xvWZfVJ`+xxp*vvL-Z)kZshlszoVu6EXhb>Op<>(*#An?ibY+o=YoIv-C_Ye+v z)VD(lP2A9j4~@fAoJ@agK>^<#y{I1q$7B+^{ti>UR@N5Hg%HsCzMLzgDj6@w3FY3l z6#5|qW9EBX3yJ^fbo2fE%idCf3_`%~s%uweT~XN|5aQtp!YVjHhkTglTiGO@rD^nb zz~098g_A2>!l(o-8?WvkcV8kBa)HM6ajiFBZi2gHywOf?n$v$u%JwtW+|+bo9eIp@ z2PVaHWh&zFeqg8YXFaDhdMoF~yx&;W?rrPbEPZ;?%KBn*F??5_y={v@TWD8C7GlvL z-2cSs=9{966hb$-Pq)UPI9zvwtSZjVh$ev@4^Si9jWS)&0ZQ%_42pph!zN3mZ|I9HSISZz@02bL0V*CANQiWN9tyS~?Gp^GmKe^&nbI z)Qsk#8s@84C$DSEi)>X4Bc8>RqWH%5d()D7jUnrKg!xhkf3qqmwh9cP5`~aVIftW0 zQ7>4<-Uwo>IS5?QZ1?7^REfhXv^l(wyEl6bMnjTD3^-JAT?{|H@zcBLh4KNW`{e4>Mzt4B*0a&T@tL_8q{;Bhq=-P=47>APN~-OzV> zC1D7w;kUdn@cl}t^D#|jawZb$+n_1=JlSEduAdpIG*u|U8S^fLV{j#;P8 zhzM!PjkHY(1p9yisKq;*&ujOuCPa=>>euqy=-%9*eqCvCxkO?}J-~GiN-z;ghQ57K zD#?Gl?BN*b=gO@MvsRbfP7z~Jfg$4r0gK#X6!sS1Vf5AqHbqM(Mvqi&iZK*MM8as1 z)7?*RA)BjEI`3I9ffIvIeMxgDGHLi3*MhX9db*R+yJBo{2U&`H!UbBJi1|d@o?Ns6 zZ2M~5s=y15-gZ1f6q6Dw3ejhK*suwQ#QuMY-?>c6S*&u~_jE0lz8m9gHlTW3<{Uo z7b94HIHE`ZOgRALw`&HyECm4TV}yTlKmBr2Zx_R~y(M1PY3doROO-y?4Kl_oNk%S< z`II3f=W#Tw<6PHVm4=aM-|ht&7i4AY7>S5p!vzYK4aNtx@{IUWh9@*PXq%yf?*ZE6 zw0DyKkk1aG>L?b#ri<$J-W|zCFV4W1GSwjuZ(Wx_hY_T^*v2eNn8YCCTMK_o8+E8H ztY!=&3(Ir1TbY;FPW&77P0TP$vz>qvcgnxlT^ok^9&jN1bczbw){(#F$=iv-F3D6^ zMXbRjx2aT3Nw66&n3>nrZW5hDzcVlzR=vDBQ?2SgWAgb1k#5(;(>bTUQdZJkOZ#u} z+NJ@E!4nHMusVM%Xx6+GLZN>fRcRn+%FF5hSIR!2-)m zns~DW!%aVa9{Yd+lwRDA9CSw$o@v<+?&+ zY^`Fr3l*rkUN6B`?f1{|Oa2iqfES4HfQ}0nfZARc8fUqt7UXnrkeun=r}RspRs|bY zZ>|zn*G-Hi1(l+@fy#f*9vnJjG^z0Fwp$!3dDlb48vd)S0r`DTK%U(a0mDRBV^^_v>7b^9NhW@sfaf$RE^4D++0r(eUd*3 zcn1P&y@DLVs-&v0S9&^&iL z{}G(Adt+%92T1gBHvG{0SE=(yLb(EHS2tjc%avn65UDVz#tsLHCLOl6(IGwTj%8@D zTa^m}fEujZi(FJUe*g-iABkzuo`D8zKs#?V?F^!4f!PE5fB}%H!Su1?a^4E!C_Y}9 z?d@3UXISBBYqEa^USvrA^&lJta!$AJ4XwGoNI%VN=OprhXIL%{d;S@8!+kEcJpmaN z`$luA1GQ|*8Vj+%BT4F9bvhBRMD|At@P7mXU2F)ZDpY@E1i@nh4BSa$-P$qO#F2J_ zd@tb{6}N%hby5aUf%FvIpUAx`1sAt>F^>$VD63e3+jxHo5ypZC|ue*@WYJdoN4 zGgckQ>SBMO0xI}o={hN%4w#QM*-eOXD*027It$*z?8b!>X^RKl1s*Pt&edPU0pKy$ zPDhtEJZ>($mcl3&Dp)N}#>x;BG6K!PhROos3#jMb1fm78+@eFWhp)pS<>~dc)rh&y z;ZhSA^Hc4tAp`q>0cvyoH4syPd;L{yV%eJltDk=$j*Qz~M@>QP;G}>Xw;IcY3(Fjq zu`;Mrf2MgRM69J`18_wH4n)ddLCs^8w-?2?gV~Gq+V7$aBEOhYMH2VsaFQ$v0R@P1 z`y>~NQ7Iy0Mq_^SV~)UNyDvUU-JREar2{_@v4eJxOBa{8^DjppJ1uYt;UjEr?px-U ztdxK4y(yg4;#!}l4rt>MbrvusaaSVkw*_=r-*AEo(_nQIw00MdNbz-{=kc^ds+1Gm z5-u(#I=Hw?p~;Fj{&I+|ELyoY&;#MTTxUOYwQDA+t}wdgvMyi87cEK?mvqJKnkvYS z$(7{)gP*%(5+NTY+IgFt)Vv^1`EcF6Z%KdmXN3Abo>IONZD|&~*ChfHdvbKSTb)z| zCvxz*+ggH|4S7(BN(S$Zdo0JmhXxKMm>J(~iR)4(ffxQ}k+FjHSI0b*Ue5aGpq1)| zh;f`4qp@u9;_c_@x8D@@M0Y$ zNBCbA;GU{}>^Cc5)44HQn+`ze>(Vj-)PaMJfF`H_YLByDN!s5(p%TGtakzHFZ&DCj z3!>OW`n4sse~w24Z=cQ_H{sb2p!0ulU=Sn%fJg#M29E)xh|^vM9dg4M3W^KYk0E=q zQm#EyA-^xR>e_`!vO@pckg3ZQ0+#co6ZR$sLT^FLlMTjT5hB@FuR-rm0#@XtqaD-2 z;Mju#0&=A*+KfxaiIcgcC$SuRGf+}9IyPa5I3gViXBGm|PZXXTbp?DJ|LCe2~#8+m&gU5|2Mloo5v zlRgNcAcxQq>4@L21+_&V2W998fi210A#INRUm6pM@Q;hN44)v7ckWjspobP@Tm_a; z8jRaflZ!=jkQ@7e0Z+3E2Y`QUWKs$siTtXb45}2Zxhyd@k`0)jAiePZgB)~y?lmM= zRM)8Q9ZpwDV`!wb#U(N1-P|Ky?I*uBbp${p--q2h#g<|*cD3DiMu+|Hj-Mb@;93AV zB44o&_5-7Q6UEO2xEx%lMi4FUNZdCUb~&b%y);$=85t~mZ5;KrA~JugJ(f45_W)-A zG{$BYf}1m-W0>mhVDo!7_QL_cBM8ZI)By87NP00Cl-1DG%ID-(AR(q@SLaF*jFla0 zK3+46VPgC6x1LJ`=22q21Zjkbf80At`jj>tByJfa9Cn%~)2?rx*Wgw8AvSLJp&&XK zPKhj0Q|(Dr$1n*W?f`${i?KN?pj{QJ`xRC7NWvdgBZtHs=-2mVlIc(tBNH1zv<;In z>_0jtieTRltJ3YDv`S--Grbi^@v8#}syTR6-JJ@}buYazb23l4;WYiQqQBB55^vMz z+LA5|@En~>g1-Rfo75U*Zy0t0V(n2Hc^y&5vGG12X>lkV0Rev?the#Ns0;!yxL=J4 z45k~MAhiD((wB-%XN6kI=`$o78v!5}hrS&WyA|6+c}uEwK1W~3H!lH{);oKZP=MxL z%ib#i3m_Haou6}G)y7g9l)44;gnOL0udbcxr3KSv)_Tflv>5{+&Fk%y1X)bkZwgHv z+)0J6!fhZ*zv6#$w1n1Km;)fUzi8Opq3q#T=UwW5QuDhyx%R7r-W;rNm<%5ztNx zsdCT%$TLg1i6}Te;&a+wq<|g+AnzR~Z-pS29LT!%@=t$7G#KwLuu!GVZ5N9)K8*1S zOcnzm2<;E;@z;LPYI0COmK1V>+!GIoqXY>E$tS@*%a11j10b8!vDkQK${tLY?4nd@ z+WbPEFsD{7^CzS&coOeX02Bitw*@r@1e4JK1C^Y5W(Tmhj;qfy-QJr4yH<$%`%jC5 z10d!21oD69_An)5YNpfO^)MFgr?O?LmV)_H2vnlycy5LRAQ22xXnQlXHjzQ9xvS8Q zB4t_m!xb6P!u}yM$2NB<8v_C$b@TL74<*Ur#wlGGyWH!@ohOb1AnkX34SxSEk>Xn4CRJ1I zxpIGe9yIQW9A0VXQ$&r^YY+ndajoB!e)X1~lt#l0~PO5vD z10aK^IjjNkJT~m-CbGldhm6*DHNT~?b5-G{yj=ChixUGNO~!OQe5L{HhB1^htZGU` zU~7#Rr@gCW8R`+tN9DJz10aMqVD^T76eE8e3%C!wlFG?1fLT=4s)6P$+VZUM`@D(+ zAbQ(J3aU!YS0&VA;w!#y4I~O)GJj?8?Hw!>$nl<7rvo5s;Qyj2UAG+CeBQNkcOH`% zS%^Mx;4jLQVOhVnF+q?6Ah(}%K_0)__x>3P1YuI28v*(8J$yOuG4-Rb0F3Up90PwK zrQ5w=XuWc4G`HXKBU@gGFQwv;#RnRiTE?%eVvNHc10d!EG%lT`@-t;Aj?^oPw#lCm zuAe#e>UIO1$~x{9%q;>S?WSsXfVX5xx1jKT1td;-2AOZgD-#P!>!0{`iqYOAkA`vx6`1|U3Sg-r{Eia%_o`t zk`4>i>MhqMqTPT4F9RSZWraSWWK{h>EkR`GZWqWMG9VMEWvAN_SE0}~h_)01AhRoG zDN`e<4)5kh;7c1_29-d{D&6u~DlLH`%#q@3^B>o&vK5FteqECO@P3X3Z zt-_o^qd zs7wO$b40t9qyJ@NWyLQHUTxY3wJ( z+Yb~R4c=U$c7X#R!N*^B>>7XRPuP}*ho;wPRsZ*zTua&sh{h7Hch1pCAp;+u;(Jip(Eh4vx6L)Cwb1$L4HARwv1n6Lo^9a2Bz!`+wVZ7oLxYr`Tmz%-w;D8mDb10buiT9Fn3*TwOOU{OEER|4%I1Rij& z=w?I*aKF!B>Hz~F+ytUkU7RaV0%#`fpzoI0ZGXVff(ZiYS5Mpq_~v|+10a7b;#MDS zN2Vxbr*hD(FgWF9?|FZGz?>aj5VG&CrauJ(Aiu@Zp!^?IzHN@zXv|Px<}SByBU=^P z=}2D1S+*zEkpmzrl!tBbFkcw#(EtEfd=|6^<0T`}&oH$qKR|G{UeYQ9AWFeB3jilK z0YIr?8^AX4fL*ng9fUDE`W`)&-*Gs3B?BN^wD{$P5{HfIQCNTaiCFuC>}capz_ftr zA*pR)RPKW#10dA}RC4PO-lh;1Ou~CUL3|P(t?yc)+lNz+D$T;9jjID7n_K%4Bl-T& z%UTW>kAf!ki#Z@Vpa?s(*pR;$pq(uR0|6k_dca9cCHB1|vRfiHb$!TCu~{=;b7XK* z>IMBJ$r_yj5g>m}_^0;f-eDCAL(aWvEd|(97@EHYB4C~6kLox z?_ly$8BLm@Yp^JWNYm~fyC7Tn)~*Z_C<7q8HYN6;4}kjxMI@FvQUWvTV82$Ief-pb z?t@as+NqcWARq*X5&0NDz!^-(nLa~U(Z>lLc+XkC31feZgjBRm43YyN#*hG8aYVQn zPWsWJdOtmD#@)WZv__M}lVrVk>CR1&10V~g+pcCP@Kolx_(}vVj`|qs7YMf^M+F&- zzaCoK#)ShQqD6_}#^;G=63=@_cSBq=B?Q2z)imB@RPL(mU~FH3GBDh(6%H{5`Hb--3J}TZ^_n zm9BpSAfHgo0P$}Gp8+`%LjeAH8}G{E12ROs@}mCMLZP^1E(0L3-Gry@=Canmq?_72 zTgwh9f;bP*P0r-BGdD#;mK}czCSs(Sb0YQbteyfOC>%(m3X zJm5eVR4^0>>i>HPw%wR_TA%>}AW1TaN#p}z5X{8J!t8&k!n}cnR^5|-=2>^`{|4rx zDFPrQA`p&J^1J`qEjH}5LyL}QG>JyB#z(h5G6i&!k~}X0AaF>xxd=wS?vyXE5-9K& zw?DB`b%{#8#~e-gDwAVDDFPrlgrt8c;RdbQ_i^Dyr5Adj{tPm<2&R{oNFadjSF<74^lV7b&oJ=ox>bv{KA3gfAD8 zYs=SOKc4LqtnQDQ6apZG5{nswU(QtPf_#|*Dq!fh;P7S^%!Ah>3h43f^q>X;Aj1?T zUvYlT!2VxYQy?|QgwsU#sr#Sp9W7Ow)+N*9Dgq$+PVh29x7*W0M8F}FTv^7D#{c#S zqW;4lT6Q>R4e>7mAZ>rLg~UF()tdfFou2zYs{q|VBEJoFRnjex;5gf|9!KIf&>tKE zAPZz-Vx$Wq<%TOGgW8nQoB|*TwY={H7LXUGn_RfJ0I-G= zEMSKAG4K%Ey`N9eL`bmwS(GTjA)HGcufue)Zi4L=rm(0w7T9 z7}-lj^t|PBF_ti&ye>cgVgUkp5AmomCb%`4{z=q@C4>xGY;W9_1 zplIi8J=oH3hWuglgaROjSTg^3`6`NpFminb=*8~73j%{k@5c%9Tx4IBesqxnAktv8 z#d)Qfc=1RK9=a&qO$glxsu;yEo#rSX>yPodKcS zE^eOz;yrZONfHSK0wCQ5VL`9qv4nF`DZnZxrIQ7jb8(WXpN2&wQHr3rM2-R==gIo& z=gV&=OFm(c#89v_1p) zkW@rtK~Lco)1Gp=%9-H_IXL?(iKEmY;5Qyn+Bc9YR>RT;~-G50yu8O!Az;)ls% zZKfEKBY3r!_XV2or1oQlb=>#x>LdanrMR~*__RrHgEtK-WiXg=Ma)^O6~<709I%K* zZ4>B!6apaROMjCICD_)cBD5q!!#oO=5-Aav!6yKmbKwZ!vg@w`AUDH#`bhq7KL zmw&GUAYMy6LCl2;X43h9hDk2wsee3&)5Mp5tSbGf%kj99>4O3w-iBeePeGY|GXngd zQ#TGB9V;^%yE*sKjw(medhTD70w6AYtK=pxQO7s06H&+nM33HB`2=z98DX?{OxLGG zqbUL)s<*^U6)uTpnv~R_Z8nk7&$(0P#j;?*ZrY{86w@k!0wAk(Zx57Gg+O>79El-+ zZns|qvci0ZrIDdL4q*lXj$?%aAganIoA#nAc0jFN4m5x*PNSQ}l*as4AslCN)dQ>{-$i7b-<%1ytAW;dMk2{hz3u(S9a7Ni# zICph=)|vu#@UWtfr=nlc83G_P3VL&YWUP!D?;)-iFe6&iu7N@yqG|WvDSWMQdm$MX z0w7;iHPu-L5CaE-8KP7SfVA=pNeu@fw5_8h42GCs_ZR{ov4ex~bqJP-Qb* z+~0y6IIRHDDHo*($>yq^0wC4^vcmFU`cCCaE5OKg9(C zAbF=DiEGgRfP)B55gnB+cobgC5fr*UD-^-otNNY!Dgq!(2ha!Y)e%=f=?D*^Srbqo zl9~-Xp-@l{ZosRHHQt;8Aji~WR&hkVZHwdfk_nD}y55WvMBE)?{#5fy105vgF9IOb zA-=@A)%5Q|>$J~z9d=*l0H82`k?aAzCOM`o5ze)i0wDYZU^g<*PiOtwA8@BhwUobn zq#RHU>av$BjPpIrH3tG9*&I*dx4Uv^MLH?TcdcX7LEV09w0w7L3 zBWZgRCM6uANP8j~D534shd{K*o<6J;ERl6zs;vSb*qBP9A(7ncTq77m4Lc4Y7Q%+5dfA zo`?jaR|zu;^d+7GApPlor}=?)zM5Vpf_~scBn6k)wUiDO4K5G8`^u4dT`U41gOfLk zTEijVyvjn5Wx$ztB~ys-iA7y=;g2C$mH z=ciTR4wi5((}{G>%6v0L09i6vpM9pZc0#2BAoqRml-W{(Jx8cSyQ4Hr7bCd&|NAB0 zYc#G$Laqv;fC3gALnew_ z;A`T{1bbzS0w5Q`k)K&r=*oe$MY>0{{($xZH5Y$gH5sIo9i%#^7#jj0I*;I6y9GzI ziJ@F!F2|R+=T4V=ef^ZfSF+Bvk>!4f0w5qf?Er^8bNy$3A*iUAEoHAh11COS1X<=H zrgld5@~)@?Adb>qm53f6Ne^ze$O&OfCPIJ{^jgv^9s(eA_NrfIu~zQP zgC#Cg-iBDWqXn+wA$vw2eq`P$2H7nFAj*_$bMmq=HJ68eBi#eV{F{JyPBjNO<|3jyu0wBYZhP}^=wfLVc zv;}t}U@B~~PJ*tI&9kKU9ZkNNkBTk&@RBgd(=LrJ zr7#c<0w5l2PJy*wZgn}Cp8uZx^>g#Utf0&CjxS4p%>X@#e}a+%AbDv47WuuTm&&>pD76KqCAxDwtv!>FcWVc2sH-E%?Zj)4>d(2Ut+%8^= z=x~q%AkJ?ve(y6&=R0^Mx1V&qt*?T29-alJZRo?XcsYjMn*tySK!eAcT2aN<{99L` zbj{O$*m+Wk6`si1*3gha$N<`^0wBr2UC_+!{(+j*CvDFqDbM4ZVUsgR46QTT_Rw%c zFDwEek&+qEHqU8@Q8W*x+~5vk#E<#Fc}ceTKR2tN>hNhK0w9yh2(ifBQe^hCtU+e6 zMqFyUQHZE(eqNw5k=46%1&jh9d;3!m#-KHSc`pBizLPIa4>coo+>EYpT5(RefS;X8t9~ZmYpp_4CV`8_KAa6ixJqDDJ$stBel#cs0 zW1Ga_L}uwO4r>`Ayog`z4gw%2vrmpbmZ<;fu8_u11;qUsz#-yKyvs9UYhCR3>ida* z0w5+^T%4p8ggZ&QSnwzox>XG1$0wA+^P!-*%4r^9y+Y;PbaM@X6Drqv9$CB4M4WKf^V){kCjbP0Ty$?Y_SP%{AW)lkmLGPf22|@CxqX>`@vDII zM%?yO6fI_FR`Pk3wt)g5x}c}>_9Bu#P)y+L5Q`o&uBj%4#Sj~1ihhr_6eubS0wD4; z{>7(ff$t?jnC|2&vOm$L4u((*vf*$*g$lP8Q!4@>DKF*7=MsRDo`*@^2gU=7=T(J@ zk`Ebf+I`?NUpaIF0w6*Nen3Hg8k1IuL#nJpNm3Y0hTWRjNrG7)G&uGAmY)IwAQ9C) zZmDrU{XJ9v5jvLJGhMDX{Vmy;xsCw&8E1#o9ReV2&@)~{jzze?Xu5a_DfeMtJ)24M ze}K;w7(TB@;{6>0Ao}4^&wo&&Ot9rD$zDTIpbv?){E^xw;)0l=#W9(G{E7k~9Aw&w z2(TC(Y(!}f^QMSI*L}WR4vhC-`Fp9ZL#hc70w978a`t5<6tx?bH*z~O9p~c}@&1@b zrC)q(J%%v!3>yL<6lfdjD1$qDGYK2{suH=Wd{>9Hm-z-kt5NXnciRc20wBIxN0ulU z;?a77jb+BwHk^|TSH8S|JklclpvnRXtX&BLAXuaA(H8GE0SZ01RG-iXH(eImUuSI^NmYF3`J zq}Q@Z0}2Bm!fyPG&bj;e)8Eld=#YJRGQ5|!>OI~@4EBD1s+UNegCMHzW;d~2;0=CB zM8CdU5wR2AnN*=n+Ug&@DnSSP6_Fr9XF)ld`JtlIB(g%mSyj+-MqI2EFJtl~`X(AF zRv-!i3?SW!SNNF-)=JfQ-;R#{NjC1rLnY3qTSLe^AC&2q6a)ewN*^xR{)%Nf;wn@f zz(1nRg2eiNE{!&^dad4}Yn?(OssbPafq@G>d)v0bd6}2n7*%nUd9Q9)B;Y}7z?nVY zTl@zCAa`l%L)o;5Au!GjBiTy$!_&$X443m~9t<+Sed3Q*5CR}uN7aop-;>lfMh>SY zT{lzspe=z+^Z#cf%_mTc7(EOEAhbTvr?cMY#R-vy0w6~rD|tf(j%afic~1*T zyxHBhwc%k$2T>xWb5l}!uv;NN3D+3@9oY@w?I(yp-{5~b=en2tsOCWV3`uieQzkfsOoxT|Z z0U)PnQhl9G9hJer)w^D8CI2h(3b3B%*A;(%^9$(`y6O%A3Lr{faPaIPr6Ml>SEyF% zo!+B4j^HMEasd%%=o4tPB8mkdv{h1Osj7OixoRr0x@S8zy5*_cbH3peoKeoki;iR% z1t5@+GFtZ>YFXpDDL{=w4yNE}GJKgIV5LQ!WfUhRXoLN(luZ z8CFxPHNh84H;3uWuOFESq?e`I6$(8w*QF;!iqYV#1t5i}Etq}9uw@t6P4@f8kx_!* z6*WOK=FBeXK6p0{T z5TyF1&F+WZOO#$9xp;8BPYk@nMa8OyLAWe@g3R+0iBe^{R7AgNQ%iihlu?fYcf{wM3J z*wL~>?!H78B|p9n0w6-}@8G|J@QoD(O&inzfA2@f3SnJKT{d#bDd~hQZleMMAeYv% zDH|p3bYvL7HD|^FN%}IR=1#mCvo3Dg${a}19U!+6}B%<&Klsioc;szRW{hWk5waI6CGv|JHxi4 z87fEJ(T@FeIfaa|YbI@5Izh7p>GaU|Z6dpe*P64;DIY{OeN97qI7q=yeqWN=hkL;7KD0m+bRZV+UlQ6^9At ze%f}O#qEH5$C73eX1xpK54#`e!<&T2mZkkiN>qV}n4e+r*C3s_xh$ruYhdf9jxP|t zY$swgrDQ`jQnE|UAf%FWTwM{E{zJ}$MJ)Nt;noL54uQ5`8WmfA7F({!A|JnCvzUrB z(^7(lNN6xj{SE62(G~f-?W|2S4DWQzmD6oz;@4yr!onoj8I=6Cl(O6{Idl%-;g!BO z=N!u2$*1K8hSk6nLjwx;-M!98FR={~jABR=@Ubw;tR0rb2LHQJ7A<7LxbPm8h<`-@ zd@PCz=V?O_bnB&mI)Ih`tBcFQ*+Wk9N&aEZ(M8~HD$%WdE$foHsV06uua3>QK0{8x zE*m$oH;j*U1v?SEV3=1JB!hf8VhdDCghdY0E~H~|9GcltDMuVZg0ufq7B@t31S6ay z%b6GCXIxj7JN^c~IT|Z^>6FV|^K;TjeqWbV0n`?JK>Id-Nk5N!vyLHG8G+W1uA4*j zZ4kY2aJmk&_$$hug||7~zJKB zWqvi4sVuE03l11KCAk*#HK~Xl)fh|Kl7>U;VtwQac~a}sX}kI>55dSi)87E#B~H-u zZD%rDQsq75MF?secsIHSQ(mb*Q8Q{(ladfJhoUBb;7|p%4>gfUdu4fO*aBqC8E#tn zf+l;J4SmEYF_Dl|o)cq;HKj?8NQ*_1Nm@$ht|)BTN5w+Lq+~B12LJ#7#A5DdT^^&- zn4>ld;;9O`G|eTQdE9TAsUSeG{@I}t72u0Z4D(~&QL4vknSCm$0*23}!sLzP7$W4*Fy7)Ofe zJqF^9*6Ah{1PK9SEYlWYbwFccShDMV-jqQ z0Ye_)(#W>QV?1@6-5_yKYwX?I{tC7_ABnBO`ds7)R3ZgWc)}77-SBV{!-huehVh$! zt*#1>ZaX%BPg<3Q8G{a?s2{f=Kz;N=ny>D%)%4xp$Pp4|rU0$cf-uhmGMvVSJyx4WcURYKja3M?$OE4d$k|9RCjiY+noOb*tD>^O`$rW7jTTQ= zQOV~q^4;oh>V@RJr-YxB{tv)kIRY z2Yqu1)!?E&5W{NYg+ZF?`D&;?DQ@g+`ip&=twX6(ZOpwX6?awkXSW;)L@T6mU{DL~^_*pp`Tk<24MeoLLf9%@Y!eoyAuzYdqI9q4#V+hK$ag zGZ=TUy6p`RBpefVD=MJxG#QGPK$l6=7pGfz`SW$t2EzEjV9tFYz6=@Po-Sy8JaeCj z^rby0L=QdZMmv>q@fgE@mtjFQnF8Ma-0ESY8A!Nyix zVT~%}AuXLemzJBw-~p@%R0GBMyi`>Ni77w$#ZvPbK3l0W%b}8gF1Od8ewDIq4}IJs zC2w_e!ph=9&cZZAGF$h>{#U8Yai95isf3%c63BjyS6GS5=E=2bpZZ8|l>~l@atvZh zH8_Ou>mP3T3fIA1%l_|4fBx_o~8Fj9bo z7!+X<6_0H&Pz-gvEGZgMbi`5o0Oc0V`vh$79(7Z^qYhWV+&M^9U&5%FF5eQEw(5{7 zekoa!GfYDPS^-2%kQ@uERbkw$V|VW4ixN`k&r+FlEK=crV8@1at!aazQ>_{=0VFoT z7`hWubZL6w?Y@JJJ9W^=-W|7>hM2;QP?RS~o2dfH2)MH(X{IA^hxP0}mf&RK3$17B zE#uSf2|)#mr*5R+yMkLKZoVyi{via&{@PCLR+hzy=oDvNYnK55QrZEgH0>Tqq~$H& ze7%W{WvWJh2#;m9G+Eq061$`zJKS%Ght(;DL6`k&N@hmyStdlOQ7k_?@_?&xn9DpG zi4OtA%QvH-6@p+N6>!Ra-x%lBXvY*}tdyU`FW-b9R_R}ycv@E*|B&&^*1stqZfRSb zK4{!Ywm+HCqMx;d6dXKMl2EF(QvVAh(qvYRTiITJv_a1?j^BoZVce_D5Xp)}A842u zC(9abUfTk_cV!{H1{m?#cS($goMk(Vs>L2JEvRtVNRtcdK=EzQ8Y$!F?|$*9ZcVw2 z-5EdRF5J+-@m)^|U2)Y+EV*{O`T2uS2IbnDNhLIgA0M#uBne-Yxwb)*dlbTR^qZS@ zdl$EV|M%Aa1EBLogdr>0SPxpml&$h6+-QCYuMfiO*?LMZ?o)67>vt-3^>*Hh{tKY&IK_7;hSEvk%{(WhdMV$KSr{NGdHe>Wg`E`vFc?T`86_P40w$Lp&rj5*ruh&{8yoeS-kGIiFL*2W1g+HKi%P#2gXeo_D-8* za_n~UVwXVi=E4*`2@XUt<~KURYroI2)lIGg4dmW>n-VhVMxr0NUlEUFDViWr2=sRd zu+-IOA3L)ZAU?mX4=dWMz_EC~s}c@>yVSB^ney4wEui;0?a+aH8@YINA}lgt4PYE9 z4J=dS6-xzgW?NXndne?ssIl%E4`?h zY-w?>p`@nE%cMXwvO|6@2NBFzSsj+pi5i3Ks_OVzD4kj@lHpqr_I4NFckHnR&xH9v zOP8KP;1s(7Q#Wjd+WqaVDWwU2_2Ms!6NJ)d9BFAC*bd`Ze|iF@etf1ZYbPEi=bCKX3iUaoDL~X`rtVE=+jV=6q+V!Q5Ne8h+gwO3FdlY(x)TO z!1zc{6T5gOg>mVT)aH5YK!Ocimpz5v0dT(v)tUn+Y|xGa^$+1I^@PrU!T8~DR@u-9 zBT4Kvww`q_dVQN6(@2#f!)v-J5yN%feZ!4}ym_kJ$@eIJH^^WDu9PSgyuB03y>y9r zo*qf=w&Skd%6ONzhEU1jU92QJf)DZ=s-T`khr~=q3fMnHT;vi63p?uEb`9LLr5dm2 z@g_+wUZ=N+0F&p8x;^!OC#(|?(YQyoOf>5@YtcSXEWtSk1gzT3dkrI5wPyCELupFDS zp1Fp{*?~89EoOVz0?&XCU)f9=akfbFA5**zF85us6S`3j?% z){h0hnD#sgHt`KC2bFmWcNeoL-orVa*-N)-H+%c4&d@8IgDlUa+<0kF5__4WQL!`9 zhWY$AE-^Z4Wn%02)@Lf13+%0O+~lzBA#DH_zP&&IGDo}=*n32~F_rzarQ&VUVaS0C#!hZ?n%Yw9}djji`>LaGTeY(jHylnV? zW=$Rr-Z9F9K3vZ9C)e{*&%tlWltDqvmA~hl2*rWz!N+Um!=}!la7ctz6q=s#o?25f zq;GdC0B~G_8p;9(4w$AdsDQ++E+Tmxs(pA;TM41fqq1YxT}q_ciejvpQ$z2X}@fxFJuoV~U(y=!C#o z>I6DMiFXu|y}b(~=Xun1N%yLN^G#yY>6%aB;JoBgyd`0>1i-V9CuC9VF>0nR!&r^3 zYT&&W*rRHqJK)Oq9WZVp?k!}_tOWhQ3!4op4w^Stl5OFwY0A zpDBL%Fs!-H$@9#*vE-FtKl3e1sB=$sL)`^x&*SZyXGp?wd*!{^pFzIUW@hJFg+4K$ zq${RS5^x^S3Y*lO074>MlhFkv3ZLe37h{btNF0IQNXmiP=f)FD|A}k^CvEPZBfY0J zXAwtWO@|OIli{e#x5O zzqQRw^{nUl%V=s6d=Cj^kF@*GXXZrz)3X+JGh>MU-xLkj=at~)7_$q12;7F^mYuX+ zW~S{D=t#ARP7xvP%RfU^p(Vp}c4c@xpeiBL6U zTtjrsF9ox$iqmsOKggMG2`yH>Lz<|9V|`#wr$Ca;&YH2#69%~7WWfQ;;z-ZukzK_u z>QN)Y!XvoVN{|MHj2=aQzZqJV>u~6b*3zIK`4V8y`$*pMI!L^*Go6He5f^*byb*%9RW3 zUL+ok2}U!MNSRKox`-C}SO@9W7-!;3e9dzAd14Y0WR@wqQyXI8i>5YpsWcQP>;ayf zAH-IoGoYHDfo*huobE{^Lq;Z0d_4*8#9|u-gY@kd(%n(+X|%MkCYf*s`K7KG>+Ji^ zs2i0jdQDr$QYEfTB-i?F;{ILs*!6eZNscrsnr|bcN=>M+dDE@;S+8#)!N={3Ul9}X z;;leimT83&JvU~RrKu9EwhqOCgdHYHh`n8=#HUW!j9%`4J;AV^CH8kxD7FBBS&J42 z?a+38U$RMo%I@Osx_;T3SK+FrA;+H#wg>5s^(eR}cQe2EfAk34zKivt3QR{+WK2x; z1=YUK*Nr}d9q%%p1r;$ZNtu)u#0ILp=|J9M6h1E02`(W&ru}BecuP-QsNnNR8GomN zZSvz}m2O$nmkiFDQOprAeyt-u}t?@q!3h)+KZxrV9U}k=v zk^ycrbGo!lc^Tl~){xJthStKp6Q*IVoSwygN1F}GBR`=E8A3#VeJCdbZq@>Y5zvhF zN;U?mUo2j<{A7>^pL{(R~gL-qCqQ1 z5>VCUdPF8a+cSF9Kh;GABGvFX0wu;y33A5AOlb14u-j=np?{cl7A;iQh}P(gsL(i8s2<+ukgr9p)@1V z0k*+^0@b&f_?>YM5y5L5B@*!8j_IWlkuoXDR6Y#=v&k4t0fkGoo}uRu)^;kqFLiE> zsJG-=rfXGp5nL%38OxX3_ioMafaER>tLWM7%)eQvQvdavP-8ri7KRD0OP03p|843& z;1_iUH9Y`@jKO^onxa;B3Bi6ow}~}OAyX57)Gd%}F>p3L2C}HI8o?oaPnYkncP0+8Ggr8T06X__#RWGBWV)V*)F}t`gkWq{Dr-&5#+n^%sg5d47m&YR zG+`UX+5!q~@wyp-1*Uo%M08}{WuFX`d$PZGS|$Q>lD85N{EANE!b=OpgD(Yu*XrkQQ>1 z=RV9<_S>Q{V!V>qOS+4^W*2I%cXe**d{`Qm$z9i8s3u(WtPQrWe1CO#qblrwbHL5Y z3fgEJYEG&kYB+?MqkP_=j)xzXa|8N&XUQQFp*H0Vw#x^wI>yryg4D$QJ%7Z>C%Rx% z5$aG*`Y?!kLJ>^HN#K6Ero;Rba+diboGQ@^YX58T+#vu}vEMsONZCo^*j>z0&cM9` zY4NFBcevpXuWpdbl3}7%$=!^9kue_%|7S0n{M+o`0?{X%euH|6^)-y)rq>`z0S3^+ zjPLUOtQ{U1J0#mD4{J&Xo5XpI7B^Z~lxC6%BBRn8xkI7+XUz#bySdI61KTIec9y)e zi$9P|0CdFcD+EJW-yeET@Gu9yqae0O zBmS>3wf5S_>98&bf+6tlw z&oqD_8I4OJ(whvGD6zMCDk>xo+$NKw_D=I#TZ9UB@iqYqrO6Y+5@NI{9;9lIYR6{$ zYXi2W-d{px{fVxOg{k&0SHb*pW~-GwC=^hr7+IN6=)uwmsj5;SC{-ZXV$LcP5>0Vo zENvAN%0x&yOxU`AvIOa15CzEmK>+=payKF)7#N~Aj{SP(zBWQ&OA1SUXO4hZObs`^ zD;-4O*f<^b5NB2hGj6a*{{P-KGD&k1wJog;hF_hFw0DA{{V8P(2Ie;MO9FVl`e08N zjCyqYgWEIvJ|Ax6UyK4uIk4Yl91#WCCB}z(^cQZB*x-_X=eiJ?{U-r(ATnr+-j|=) zYa^!hd8!A{-FUU8NYm=&qT*g?Y%bgX}ED>Xo;s38t!e*!kpSjyP$ z;_2(Ki{>cuh&gbiRJABYl1aPp`LnkDBTu8?{yo5%!H z{CwsR0Rr~ccq-$c_h*2-k5DGN#MrA4yAcUk81|*x=@x2=K(8$mgabBe&akZ5D;@lQ zUzQ^v9pf`rGz7?4j)SKL|$>9%mhVY{i$3m#d=N% z&+zrHY*~7eRQE*!3%ZEwq9;U6yAc{=49!&=cfIEs;5#3hG2BZjB#k~@lCWQ&fCY(& z5QUA4mV>iNj;`*&IOt565CSaQZ58_?;G)7-q8Y_g)cL92TM_^1EO)5C*^uSuObnl@KY1n&u|5G-wR3g58QHFAt zhY3vWc)Z_4MFJGpqmtfl`^~gm&e6TYHV-_fx}-b~+AVt1rMnFww_r6C%ZK6Ue9jPV z#O=Ni&=7M(fGC9?IgPf)>Cd8Ej?gF5Ep^oe_Zs|*e{jF@Q3TX~-gLl!C6J)N5& zuMU%9s_-BAppVb`+IdS9S|9E_8xe4ElmWB&Q+S@hk^` z@wX#`hycB^lyPcy+TY2sw3mXtJ+uD@onpc!f!hOV$dNQS1MoC|d54on&+*jrN-0Y5 zvgwK-{L&cp8Eqrr`C>GGY5SJ-*E^cCy791LNBc|!ykal*`>J!IY?_t`B&Q2wJJ@US zRC{t3Ui3GDy6qZL4>5&9ir=W(zrwZ+W-4gxHlWIceZ|rrbmjh|s!{t$3!63U^FR5J z*5FtSYzpaP+5DXQhoaLbc%;#gJyvd~zOd_)D-9rVk1t~s`SmA%_Q~vTej#?wyA*g$ zxle;&RUej}D5?ljWfgt?6a=b?6ngF;WG%PCn(ze%tV&RkUD@&Sw8KN!o-5&39xZyX zdb^tpmL8&>ur*rFez7MYGJ+OAb(MKP3`|<`4&P#{bzKmgxwhnIdkZOwCsyDLC5 z5e&e=qiNpPDZ870N(C93s;MuK4qyoEXtWv7E7$gi284wq5I58hqZ^sGtkl{5YuC+T zcgsc%6Ay#*pot_{3%_10Xbl16lMXMq_jgqH*;PaWld~XUnOl~_TP*m7r=FF`jrhF5 zVG~&(!`%KiwnwY}T!v44EIXp$<&(Tj9R$!UM`NC_e<=Wex5z82s8ac)T`)mdAV0VF z%|Qjdvk~-^ghb&PHjxk^n;9AZOxP`iC%|Jmwl8DWm(7Ue$CPKnjUa5=Oriq){g*gF zm0+pR8C0uv={M?~(OM1ysK!m0BNi7VP7ohoZ$CH-o4o}RmNuKskxl5p1+0Qi-z4TN z9{7ENtZy8DmIPRR`}sz$qCb*i*LA(`Pfb#dy~V=&3dG9p1vf#Sbe?QEU1Mt&f+j53 zB;|~lxW+Ur#f^w|e1;=}x08_)>;6MA6Q4s2SeQRoecKoK!Sp*u5#|A=7>C^J^e5+# zX@F8=BT~0b7+WvCy7zWQlHl#tuBc2*?qZ~3!^kK za6?X(3!FynsJ{xE4}^Jk7Fp4(Ig@34xcw)PRuWvP@_Q)QFH(Pl9>?uAAda`c(D~3Q z8?td$t^fA6srut=Klps04v<2zts#lhD@WMtX;K(0gw}+LGup1%^`3@XtJ|ub!@-3D zu5v4Xmk7=LiMUULYWclX(C=JQwpyCO>-PxEBSpEyYCs-%xhNn7X`al1kRgzgHvIC_ z<-;P%GTH~~{(qb<5xtQ8_AKIPf@^(o)3^VZ?Ols1famw!X}VC+SmKhC)<@(o))T$z zSR~H>7<>IGJb6->b2)yppb$_A`mDkCS`I#cR74K;M|xXV48hA!#l!CkH(*NRio4Q; zRP%8fYpww^Fv}`xvQtcyPNNNQ(}JfEg?|02UoP1wBpnnk)MS-a(vb>Vcc)i(BXT4V zEkF~AZBaPuzbyZ&JRjqZg@w%}6=EtLd zL+AS%#|yp6n3Mo1q9gWi7eT%Uqk1IeQ`6Wfyqr^0;O!G5oG7g)h)mwm4v7DfCmnLzQ?eYV?)AYd=ja8O{?3UzxkjS;_bj1X2|b+ zN(V=KLr7~GKQ-`OA*yXi5QoAq@Dy!-NQ^0m4hp7UCYW+co$L=%ddk^k)92Jsm0DiY z?rE>}bOOq$7j)meY;s>8TLSQuD?AD4SUXMpBQD@+c+g8&n4y0)BV15g>OJ~pvf#E~ z*M0F>4zq%`n*I6(SksEigys#P1>Yaq$=HDuW|8Jn&so$RJgM2$XB()$~6v?9>MS}@#+Xe!=!W-O#De~bZiPVszyZ`a8ZupK z<-_ft#DvokvGN6+1SKxgNK6zr!wU*6@eVVNA|p@1uvT16+dv7CFl72Y@(<|z{zdJ{ zwsr-ecC{?SRz81Zv(p zYm)r% z5!vAVF3SoFlD-}=x$>9|f?*`5j;Ovp*yL@eLIvX>sE|g`e-@xBik(nE2Z;Peq&XS0T)I zG54GYl@&NYJ4M2l=`TmE z99~_`I~B9u!!KdDw>OV{b5y$ljZn-7mj336NC|FT8mqxJwv3&XVMop_4`><6oGPI( z`8Q_gbd2vH4=h>^Oy+0_D+uLynEiMsURvMGJCaUxQYG3RBYE`AJ}T?qP7A_ADEeiP z-4{PxX%xYIweAmpz;nKMMF38MKH3X#Rw{g*W?@%{RO5*Qq)8gy5?g@c68Rg{e+O;I zQMnG$6;l1{mfoCtHC4c=*gSl`+9D2P9f1?dhGHhWon z9g=4+QiB<|Gq=wRI;C!>Lz@S+Lx3=}V}qj=QT@guKpk1iRyLOZCKK>bMmxitRlh2U zW-uz3Myf^uwYI}0iOwsD3_^vL+IsrPc_#pf)eBuBY1Y!HmdL3Z>s$?XgZJr`Wl~{# zdTF_jJp2BpMK(i!&yY;W z$&TVVkVx3pj)uF1wVb1oAuDQqgd|Sx697{Q4rd^6-=Jfzn`D0zB+*M05rV5qAFIXG zR>AZf`Wl)ID8;UlQps~3O-Xl2r-u&`k{T&dUFt&r>K87sQZ3Y&(ag2sF)R4&y*Q-! zyDu?+WDsI6^o0{7GkNpmwc;-ht~2Kb&pcxpmRk4wR1;)0akMnw3Q`66BREwHPH7*; z_Cp<1m{0Chd{OIJQ(qbb7PAk>+@yR-KFu{eBb_b1z87{$vh*@Ub--2Kn$wU*?K7Y_ zj{qP{L8!MF>5wQuj?h|YQs)%i{S67Wf@>{*BH8S!wbJKBh7Cb4&6i~hjIK+k=^XEj zL?nHwmK9=LVKyS~i&z@#7Ic#P2o<0rGE!ReGIZ-BA>-;Q#Sg{?Ddds6RbdWqn;>iR zNU%}~f!GgtwI%)3kXOFPXsr5^0sPZz0i9)7;9;&284a2K!)!li5&KGt&Wdh- z{J?0fId>ft;sP1k5|vhz2!ze6>*$jbi*El6&Uxjoqi(m)#C#?{;?vI_$`|ts16nVY zVVhlk;bcfTNgto!?SbY{s-5c&1;j2NxBqwWCH+<|a#%&@dSO|dIl<e!Xaa;^eeJ zp5R&2`8Em^7^|fWf&~aPWEgXceK;1u5~+c>R^*{RENu;5ZKJ}H83zK)o$M2R{?@aN zcj)j2dJB|R?h6C?(3(tpPmET`t_&wJp?NG~DB}rSWp$*NodEzFCDEU_S-XvAEz)2B zML@d0_e7hy>Uw<aF-f|3PtJG`yHq*l2#h@N*L?_Bt3skerkZ8s}hKK zKHe!VPtLl?^5Z}!$uyGBzS@n3-foMG75bw=&=h!e{tfJ&yHQx5mC9DnvXY-Iae5G( zJVk9;P5~G~!3b-b4G>#)z|EtUe;~oOQLP?iL8W7%JRWxT9K$Q;vd0TJ;QlLBn=q4L z@%)YemYtoyUkT;Qu!IKb5(({A3WNmMH2}OV6QPIsG=2WQA{+iberv@tbgJ((%0CCeQ#fQKCI<>n;`E#@VkA) z0i?ecquO`W6pq8>3IOIfzP-x25>!2&b?@9O#u--z}xU;o1_RM*}ZJP*k`0t^%q-hs*4 za_?mZw97D4k>k2z=l>Ln*q*FfMEN9`VhzHH^C~)O zgVff-js|*ZD8Gq$e{W_NL|S^ZQqAi4F1f0hQw>j32%KvMJ$^!M%#>eV$zoZ$<3KtpA4*LVyw=B$_${%WPyZG(HV z-CEKT8`Oh#e|FaR(_zf*+jQgfr@Udq&wa1ijDk(-UAWGWCO%TrkfazFRJ(;a#0Hh| z%jP8jdmeJF!WK+G2Y|O46ZeO6J^E;)M%p}m3zd{Tg2HXYgy6R-E?PD?hsqyn6jLv0 zN21%1iwbAURpHt?!K+pHVf$)77wU^s!h>V`E~GxmeG<|16GF}qC~ zpe}_fBU3Tp6`|3Uq^|9-TjgQ08Y+Wi~a%!F?@wl&+lK-HN%~G## z;R?gXf3Q6n29Ji`jqA9kI(KziuQCtazVa^H1HtP?{d52X5n*Om{+R|xZ*qPYhBQ1w zv={wLvA%U+y#E6%$&x2DQJ7kj9hvClu`m-F(WS8b8_)% z?BUR=L|pgE#YNj8J%?8njlf>9e+^n*p-CxYLjbA;-A_I3_Jx#*-28XjXLGzVy37sNUGh8oBScIYum(143(#*MOV53uX2TKg}>oO?;}|4 zP+W2Dd8~7nMSDFt=lVo9Rn6HE$ttG>uiKBEUU{C87XYce9*u??j)=`G^8vAm*Ncq< zf2pJK)W*bl)Z@I8?Oi9*zB+<-> zxU=E8#P5Kl--L6aC1&bRzLG_6bKKM0*YwzjAkPQyK$^HvRNC=i6#ZzV;WK^64}e-xgG5Tzmpm;KwrdgvqNH0HMWO@CxX!e_Fz49A`lx&avle zWK0e)H~W+-xOXhSRpH%tQUWZxT!ZNYdU;jyI_LOlINCPsvQ-m^2gNzJL}eQ!f7>C| zX^;57UG(enxDApkcq|)OD$zSZRM1P!!_ABJzqJx%y;o=9N2F96B%*A?*h4*!escR7 z0~s>IO$kKWn!76{=6Sl}PwpRtoLP>zu}6hhKy~UK!TY({r~&h0zH=u63q>h*jl7WZ zR_E-fKlOsKJwWaTdUqeqR{;2Re_oFoSC-er){!lFBX>t_-g}_Av3uoF_Vd|;#iiJe z4e%%~{|=w)k4`(z?jq+FfpDK)OK6>E49w@(h){*=bj1ZAunH-Lli^s`A5{=*o-4j* zhS~L+Xof3}5J2i2oUAe-S<=p<+VDWr@p!!fN(>eT5e#8{KRElQEM4)#f5n~09;PJo z7ePq>B*GCDR)xyz;NUY(qTt14gVeN7%2(->G@bfjAM#Vp0oc?;;k)MGJUJdZe$Tzbez zx8C-ah4&JNLNEby*Chg)XKkMK!@5>Xx$icTD&m&hpDN=R68Z?Pe~Zqus;?e>6YfXX zgOtCXi5$0_dp^kU`m*R7sxzTQ^syMX?iXO zn`HxlUtuV}PpN&FIg&0=|0n1LTtfu1VH0slLA{t6BLvPu$onCWZi%<^ptqkH%^4il z*SZolP>NBGNSJfuf6f?D`-@QvfWcYaG|O2>*R78=lGl3ij7MIBIU?qztC@f{J~5aH zohz?IcZI-AlK@x z{Un{!xWESVg4Eg$rR-W?|-XpuCHmP2AmfpO>rHe}tgGi*Xoo&LJnf(>SJz8o4!N zZjPG2ZY&kX$^$(Q&ij7$WyVJqUe_ww{D%roJDZ@xNC%FHEr<^mLX{q*VP<66!RM{l zjDQxa=IL+!#7}*-&Wfh}*f5xaXYU@3h}gq@DggZMSgkKR(s#}Rr597nb^b}t48;k$ zP+(HDf1tPyzR_zkhZw^QrO=ADxWO{=&;Z$O-?=RH50#O(4~)csxTt4zb6_$s+g>!Z z7*3r;Mp=>p{;>j~KOuNV!zZBDYkr?iq0YMrDS{#;1dof;r#+&A?rG9!or2y1h_~h_ z+a@;#Dia$)najSE|H$z;;#z;SrVZSO z;~M^uxER?F=@T$)<&ZMaT_YBlmk9Q-CMn5RtkjlyavJ<*vq_VyGXwd|uk?As`+ zwrXoX0%5mBZ)E6x6kRc2>XjB2@G-6VUuzAwCbAjafXsh+gArD@Wb#;+LKWtGe`OT^ z@@v_v7Mw3P02@LlcdWt!ojZd_QT`97FLAuR1^=vN8bi5lFss}dVCqr%sIcbDCa z9dfg}wQMB?B!EQ=s`o=GETkd)yQ96%$I?o$O2vVS`@1N3Pz`#*wJM!_VFl80hHB>9 zcB(iId|nrAz$`d{r)^jryzlrQEKDrcnW-O|9b2DpVt;(FyDmQ+xvFR!e#$)b`yfS#S&_P$qp%iEs`MJEIvcmROIU3ohKZkDXb~4L4I&1szT?~NX~jmp zstwdoJ%t9cCt-k?&cRwcj}%51{|k_9I|t)+XGcK^3Uc+cNs7B7i^AE|%Mxdm*06waRQe>!)H+XwC+CLVpkK-BJeW40w(5W={3P9e=m^HI;qSl+{=b~ zOlFe-@U$TSE+IC``{M1S&rWUFVmNaW@%4Lej9Q-I%9Tj4ZY8kJ6BPWwH1)u-gFo$!SOkmKkMn@P?t<@5@y`V+ zc(vaw<^<@H*prbo89>^V%w%gJssa?*A({Mg**aXz2Y>la3IF*$g>A`?qN*hr4((4K z&2`UJe>BKv47ti!T1X*D;u=X0_}tpUsAFkcp(1{!chB>Ew$P5GtbYQlo>xj3NxP9^ zNT?LXcK)607etf`V-U%ej)xn*F5c|N@X{mNcxwq4RoIzYc?*0lkb{;NWou-Q$yj0PkJ8{X$EwWMdHjm92UC^C*0^VOeDtE z^~=r#G?8xc+t#rGJ|8v-U_hF>(Qe+02dlgV9S48I|5z;AfsJi8nBT^n(I1k8w6RBo%}}`rj~16M zbCQ#qw=W32)ivo@I(&bE2T4oW;efHGiU5!>wWc#7qr|*@XNd-LWM{=DQ1fsQe}ZJ&YV3g(;gj7sxkz}j;j z!BRnhR>q2i1y6R>E8!Bc0U)C8r*? z7aXsm_n$9!R=XDgeyIH4Rr5LSvk_mL3+4ix_`#6I{jr#)sK;9>ul=hLGEUAJe;%Db zMv|^ShCVn|(GnYse3LfQ$O8^(3v->*9kJUGTN$`X3^c#ky8yxeJd;iz=SEIx`nCeb zUy$8)6>|5S$BfSna?YTkVl7#uzI3Uwe2^+>m_GP)J^5w20A95e25V!uecZc;kA{&_M@eUMzd!`A^3?8{1%k$ zi+n)F#~cn4T_G_XqKWxNLrXQC6VBoc{_iVaLIZlZ2cfXcn!9DDJy0?U+!CJsf0U&) z1jwf}{3+A$7y}EIYKm-zqO>@GQB+LoSI}S9I>l)|>b_pVtAzKHPXguoe}&zvCmc`9 z-GFdH6FGg1i|xmN@#W@@j{s$VkTMMn?~T*82>foIql!R^45Ry^a1(gkX$IlUz2~_T z^u;AB8F#h@PwAJ#V;%jRA2^v^yLrFA;ACVwd=zLEstF-=t521`M@KYy1RFM6{~0Uf zfsg9+F?Z9>7++ut;wfzPZFgU5L2oJUxNo%&Ql`(+hn%?^#i(EBn{L`saCglKJ z8Vax&53P;8tJ$M!c z>T@JJyI*NldV>Bge-~x1aAxOk$?RVfwp&D`WizpN!LTn56rxsVVSiI#0UZhMm8zeC zpvB7l(+lY)N;M7NBl!(M&g?A*hwg=#P!NY0QloMpy`sj z7|M3ZoeX)OeS5{N-bTJ>qJ8b4t|$AykK$}iN#_3De#ycne`6DF^3wkTe~8eW>nOR6 znp$0Xi!}q7Aw92`xjZswHNzt?{L4(KydW#NVU@|B5 zaho62{gN&HT8Nb0C@$+C5BP&oj18Se^MhJ&8+|gr=kSbF)lH7vex$IjTMM#+pID%c= z^MfY!aRCfNRbe15cdH9q3SmH{CPTNN4jyY5aOg%MVn2% z?knLY<~8L@@aiS6|40ccgk|W#+O z^;a6i90!+gT7i~n4}50S2$%Lk+yXZvBi}4tjU5GdF(1_z0Tkf5?~4QK2z45(yW~KpHj*l9tuzRJ5uNb#6YSq0hHrhGlsO5jf)f3qr~D5$IV)sp#nC5x$soeQ|=l8 z6C1tnye>13boDOI5oxUnIE+><5kftwC|NAF!hBHsHpLq2OD+Ih4-7rTdbfZZZLQD< zL!M~GEZ?s{kHY^9P~0B!w5qjY>}vl>e-(04s%jy85$#~+#V+02hr&3buMP}@`71YL^thkN?r`=e5jZ~{4tM?j?UV^-{Jb4X>)MKcX5E&rV6Z;i+ ze_d(RpGi7YWxxv^U9#ngRF4AdhND{I3phWf;Ukivj-Re*Ccty>Q?Tl(3ckUgfAlVJ zhdD`APzwOEkvT%|q1$OPWj9Jy;QMiE3>E%{NzaR-mtVw%`V?SwiIPo{D{bfBh-q~f zpE2(x`C@l5sm?LE+kd_24H~?$t}?cig|K$~Ntjb=39wYq;ovD>(5R*yLf0v4$p_wx z*9vNPGCTip)rRPm%N}!hyC2DBe@Q*o{}UD+QyeE)E1|eaBC5lyqKouXeq~!Ab-|yP zmUP9f{SA>Wy%GoN`?)c&m}-h`x9O_g@Lga})*sv8JRpIdzl6Zczv~g`crOZYE}CEx z0onZH9x;qXfL3>7U~2vzQ5F4frZo+aP<>#9!M_o0e`LnWJ{R4hz!QR3e-O7rSOcW% z2f1Agr1SX*%peDv>Io7Ce9T0My>Mt2tB6}gjAqMF=Yt?Hp>GFQdl z$U;BBM*p2Cp?snI8UcbP$c-y$Li#-@v>WyJBHVKU^7+o&NfkGskym|J$x8bch(8!haC-t8Nq?HYTA^o00#g700#g700Do`F3R*j?nHB= z!72&QcrY6y6nk2cid)O)J-K-!7c8owE%;eD$s4#qV;ztvZe_K(jzR}}vmIR8^zagZ zU916jowh2ae}|Q9W*lj-p-QtA>-lDEd{-cj&A5H?lKmI30iUjABicr!8O3Z(KLly5 ze$7;t#V;+28W#)}+pBh-Bz(K;PNg9njT-x{Ld)7YY0HUsUxu(SD*N{3x52z_uJpfk zDK4F2DghEBJv8WINLR^4TNyxP@@9PYqUFQ=?R+R!0#; zF;-64%tuQ)S7KE=W{;8;LGFriGA98eBx@ey|3k&?kEi)6Rs6^zDT7EOm=u<@m&4?F z^@|6Rf2MEKD8Jo+;CKB1%aqy)aQ(oUSUgA034vI&>Sb^cV|m-c1g27Ka@~YxA4+|z zP~6n)Q~5~jeP7z)ipZ26%bQP`3L$i?2vLBY4jk#EH)(3M9Em(8dS3feZGP+`s;5j= zUeS_#i${vk?dXy^bkT3L0c%NnpR-;=%b<22e>o3peF2uDsTwOEjvUXn!SbwW<>(GU z*aka70a(-zBQy=qok{Cp*VG|A$Y5Zbv>ir))CDMh+!El!k_+i^DdoGjb-M5?J;dIh zhAD@%hKU`8jWKt$)%}Ym ze=GKjprjG>bXT?$H&w$)!d6fF6XMWV($?y=LaZJzSROK{mYjqI`!WD{&BaWmB^f!qV7=5;Q)QjR11ew z6IA!vG)yU8bN??M!%2@A`A_fl;*S+%f7%MBG<3rOsP{qpbn9nk^u%D#^` zLAKVc&|5*PY>R8L0H{-I%NDT=7yzZ->eO2(#Z6{I>8WxNDL&9^0h~ScjaP+ne+5#~ z4!p4A-5X*+GS;+K1OmobRDBFPiB_|$AVRYdEwGIHFGPYc8XY_{(bs-3@z+VXV7E)q zCh{3M^3P$o0v5QLFO}JnYULc;DQZo=HXVB;Vub@)(A~^xB z=GVd(7y8Q?-bRXYM?}D(Co)R7f7S!JrY^SzLONr-7aXu+E&t2!1iSnVmT)tBT3>FY z^uDW--y8|Te$4xKJ1If3G0%oxmnH z84(9XXkc4#AquJNIVtRTPbI5#Z;^Bnqc=tIG!Wr1%8OOw^erVBxBPlxr$e>B3 zjU=^j$q%$}=??M*ozkWvsI%65?^tBb{Rv5pPTc+z;M zcui871bf)7RAT(f&=o4Gk2340)iO<3lE>=UDf4eq0UR>^fBG-NF@Pm{MyV+z-~Q-j zubt0ZP#}hv(WUm+7c1jBymoD2{=i8PNJI{rN)kvYM4HO-Aqey{YH1j~ zaakBj*cBtZe?)sp$)Bc1XDg*2jLq?AEp(EUday#aAvLi#`Kf4f*_mPIPk~acWij*~ zI1wB9h%Sklku~!$BtFHC`r_c@u@4uBZc}Rlcau8@C-WMn1`%7Kq`b#Gp$Jm~dHYP^ z#h&+jY*2xdUITI*QQ!E4<#wo1rR}V1j@Asip)k53e>yaJ)zM43{p-O-6c50ztTQt} zZS-2ua!4(IW-K4}w153oxz+%@_70`p|BW+8*C7yvsT)0w@}$jy+#A@X8`Z^71&Gd5G}+ zf8zk}?%&{58&2b_Dw+Wd0=`g%!g&}Qd=@Jw;apR95erQk82Q2qAz>#*K1{DDc(vQ8 zJ-7iuU(BicHyM|88FI3P0KD_b+uTA$P>WTRPs;v}uOeWXn)wsh1lG%+@JDJr<%?5T zWtMO-lB}Z`i^p@$jZu*%Y^_ggEx^4Se}_=k_?Xi;!UdZz7;JIqt8=zr6Grg#`v@~l zE!$s%fc=73Q|BGnYMdwR_fkN%BQv5<8vsU#jdyN~^r=i7T>G>GiW#Y45H!UJC-f4j z6RgTYSVfsizqCCmTryU)g?T=T(Me`e+%|BML_hIyg#cf1W$QeaTrO zdfJaBa(iUJ3)wV0pfxlt{3s;8WtJ6~yXNS;hgFxpaXU!-o~9uT8-k^+qP=q=)MDVS zuwlG6P=3;tpk!-Wz4K*oIt8mV0Nt)F2bMW)DJUiJ%IJ|;MJ`jgS|Q3=FNoY z!CmknobQ$l0VN>oPCQ#Ef2Hr1{e~{BqRXmO5^&F)a6|1VDZL60Z=b;uAn-^{_6F&nTMfiRe z_r5?(S|K1@rN1CpDIlk+{2~Wy8244HJwV`=ZuUKOCm;Etsck7Bf3$A~(Wy5~N!F`7 zqc#qQ5SH)o^0*E_70LES%nztguprM?(Gl6b;PT|dD?Ufy>JvN>=X^DCsW+7x3$)b^ zG>sq&Ja1IJp_QNh<+MC=y_2XQEa9VlNEoZgY4wr&4`ZhwM?0J3uB)m$u7Fr}>UK$% zx%Lb9!Vq$r&&RX7e>4EG1|SF}7>{*G0EUqO&clJ?c9fpz><&3N&z}9`xYXO?RgNGF znq<@#VM-x}$J#?!k+KbZJ>h`f_)Q4%U7^N*zcZ{Lyj-+^N#P0&F0Eh0fQπdDFy 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

    EIcSk-G-BDwu`|07F>bz7??&flYe#* zhUrVt>zqA`ux4K<@|~t-B6>_DkdWr~l`AfeK>6GT-;gw9<}RqOmG?Z`zIlaQZ$ltu zgxda?>%E;#e79HR=D)|~ff&filSdzS7W?PLDifO7t3C@FrqzP}iEGVQfuCfj5mRKI zaxdw_x^PO&qTlczP=_~GC3xj+d1Nco)yKazH9;wxP2{3GHiPs z4kh{n)fG+686PpS+%Xk0#PYDDg??`Y!A6_#@g*h_6{q1}`6yg@^q~I~!jO}8UR;>4 zod$8YMLYf;iFx6r0uL{^AimzT5@+>(K1cy}x(Lna%sJSVyEEmWTqsd@VKUFmYxs;y z2+&}@@oXS&P#Kr4nYiy;kx1qfWkD`0eecS;mX)1DZ#R3JMS#;8x+FtiekYnc`7)c3 z643mC^hu8im70f%XKrxii0mR@pdZ?Q*l{kE?jf)Ns&!_(x+kGn0;K z_eoazT&i=u9KS4>vfQtx5DLT4;m9>laZ<7m>sb9E#Isdc+8c3|OI9mv<)u|*a-dY$ z(bI1V1Wyi{rKKF8-C>#+d4m_Y=c64X**g@?qI?RtNMJ6mVE@~{c^er>%}~?eDJCq= z9AjpL6M>-;yZv6=ra8ZzMKLBoYngMbOmwa^? zZ2fia(pA$==~JJ}_or_VlIMrp-N!%DH2uN=nv99TFp^jH+2s}ENge9t8VZa_;5OfA8wx?BzPp0X83uJ&w^a8 zJthg$$O>Z_J>RA|6qN#A48Ar6HlK}Y+tryx`Lhzz;5WA{dPM{ySwfUwIHHSE?LB|$ zv<(7*O!JqNGW02AP0U$g%EYmaiLT6o%u3-kKbg9QOiw#QX-LS?Q6ii1Td$3f%!m-2 z^0nulJCYG7qCBYb!XR*JGOWPVN&I>SLyq~{ z_s3VF{PX@>2v$WhQD>;J&&QcM4$XHWBeAKK6p2?+vo)t5H|TyTCD!#pQeY~n!bHn9 zrTpW*U19|8KHFGm76VTJ7dJs1+gN}i)_tD0>_%``%tLtA>LS_ib8Oc=PA8* zfwRyJgF&UXiel$+&f^eobiCFPFA|Rm!=0l5@47NaL&l@3I0~MGJcyg_>ov<|-Q^Si zjO8V?#~fa*9X0=LK%{b_^Bw15F(7_6+c)AFz}3iAGL9Dk$*`26%j}g305~mN3shBg zh6L|srmJOVMU}R|f?Y5?ys5?YTlodGQinY~c@7f&Uh8Q;Y-R#ytZITNPTI;cjnE1p zrf_zNz(3W#3!!OLEDPDp`bAdZqy(O5k7R@~O(U&UZF34Nz(&;r{6e(;Ponojn+g2( zhzz!yq5s_2>3jbdm}y69VeZCUplydy*?1ScwbBi*Yt)T`JYQV$|d`^x$`!t0vF{>|TA#mTB zd-#%a*V0{JH4IEt?EDq_D}V;groaj_f8D%m-@79I3Wlh9mTm>S>=#ZUt_`2%8D{p472Zr?k7FJ zdoFQg4djrvsL{bLn&y@`Hd(ZL>PV0Z1bPnP?DCaWD$kHHNU2EUA&ol_nG+!G@0q=H zEuMZx7?_8jDMhvWyRU$Y3|U6YG5;ivGeR?Ad6?wS%~yB<_$&D#2I><>hMo|!O3fI0 z#fl+ofipD{6VW*9c&&RhY;bQFhbfw>dUSI`VL%)-9EO75XrtxQv*7W!CjQz_r6PsR zK<(lxu6Ess;o&`>b*3&VdWnXEraX z4f_wO3RBPw;za<;5|Q8-L8k~mJ(VMfh32b=yoX^;o%0sw$yj8^#qFXdeMc9(=R&g= zG3BzRX}->%5B!iw3MnN@!CRD&^(xuz(GCMn9r7@X>T7LnjR;=}jkV!7?LN_+8Kq}? zaLzFm14{2vyz{i_pi}BA^Tb$>M7rp9mAv#P_qZ;>iXM?UUX=-FLPxz2ll1!5fCmBm z)G?SRmjE_6^16WIU^UG(#L{)&ddUSjwK22lpza^pL7vFCq$-CC0mN@(Ft?$bp3Lm; z|Mmtd}eh3WF;S&j6XDqd`fWuEbaVL1>{0vEk`zD&I^@DI; zffcSdTE&jB>p^Cn+$A#0FntY`4J4k-6>)&VGY`G3w*_eh%(Z9+^A(HVhbaVLf4Zsn z)!6kEdINfqu;(UL*^C>s9Z5kGy-sKsRj1y_UB6h@;=?V%n`k^mTcni z1cbb|T7mo*|bHVXyxA)aLy=) zGy+6J1kM0bUUe|%>MG$jJLfR-oOA}{>kDH(f!{Z1RMtKzn}@{hd?Sp7=i_gVC{zxL zOSiXIGTGRCdj{cfuckeC0e2`?$t*2-_m#$}QlR1!=XPy)`Szod~_ANavqq7)e)DZjGKawX~g9n$W+ z^vJGP+KOzMHj0(TLazZ@_apKOy&bEzU0KMhLPsN4BG0QiqrHX{m!p1GV50N{FJP=o zr7=JAB?I5fsoeBoc%Zx{eUdsyOmrp8rwTE%TZR*mIDg4m57EybcfG9;b2&N6TMkZ+ zeMi@oUx^^mdP;5l=pbB3(sWaWzn+FBq=h6E9ay4uShEX0#uCPYh}49ODHWy)Ey1Zn z-c+Ux_+HPhxK_kPOV`i7lVPEp_VAssEhy8jRRAm^B~QzZlmFK452M%@Xb|xqG)=)n z|KgmUh8QKnn`B+)FVyz9TkyW`j01z-(b`)^ChJ8m3iB5Ug3CUF4REs!d<-+xw}F~! zMhHF30Y*tHn*Cz}4>T@!woIGU%Vt$u@@+et-U4JAYyPpad?VUT5-cZQk8XKJV6)(x zM5Um!LS$%*!5WKFRgj8*D;zf5>6?Q6M33sg;@5hxS|{^FmUa35#b>snY~7!~AELUF z6>vu(iDLefvv`+T=+b(In44h}_09sxgFr^T5^kW%gdLeAzp)3Q0XOU$ZC1#nnW-fo zxd~B$sib7nqMW(V!C3=Wrh3BKMzoZgd{Gmt!JavUChLggk~& zzHrtzUH`C6o3*`^I!hxIs+*nlip_0&$8^*R{U=;8wzS@Y+k*oSW*;lEJIVClk@Tbv zvDwx$FE*-_stU)+?M>tEXeIwCQ;VIr%-G#c*TNcsYlkvaO@@sVbM((aPDI3Pe-J)5 zit6iG-sR88CsOr7t>C&6id&QrnR-gFyoB46NG>_y#!?$27yXnN(d0xzkC)t(x6b_) zyDQ5|`s%YivsaR5g|^hFGVN#qP4p}f&My;UbC8#$ia9?FiSk?G8PV~KVgq)$Gr2?l zA`~GPp6~Fb`Y+hPDyhCafsD*J0A)jb@~r;$t|fEwfGH;@&KQjE?_@$UE}ruue&A9X zTKiogYs^8T&guAe5~{j1s(+;bTYWt*u+317MtS}xz5jwDpO?mQ9OEE&42*u~@wJ~0 znXPs^5pjmn2Gx@bx}1HYz9+ljw-N8b-Kl^Y5^Erb_I3{HP;&6PfI?&f`?6KcYcmMrA1UOH|o9!)BRS z^WhnW$nlGbFCUUO7)RMNQS2VmgIQyG2L7a@ED-K)vRDU!B#}Q_FoQ|fE}skT3QEGC zRg-@(X2#^tS}?i(={y~rpPL2e?bZ43C$&hy zz328yHa1)2MzYm7El0a#ykaOzYo@dQY~ZKI6{gn7-1o#9QuUDz`7(QZMJ6wcedC?1 zl4yf(C1rKb3Eb1hT{S*QgN_GPw3#1j(;CdUwWM!9Lo76W`j;&&9<6jE52fP1-|njO zzVG17Z3ln4@^Kwao;zoo=_LX{kz)D!-2kiCh?T6LQ&vjQcH!1?RBU7vS`{1P@E3|@ zG5l|UR2z9Rc(SkUIBC|WLNJ3^giMQ03###<`ngWw7Uf+g3?wA>e@`ar&dO(NuF?2| zpM$7J{<()&ni-|wqLf+~UvaDnH+(Q@pd){XGRJDP+&kz#*g$yu|AfxBe>3u8BtA)> zLYEq7W(48E>Pd(o8Gd#pw=I03E9hv=MWLD}K8&$YWvGeJ9#bMyj|Vzb6Y>-J)TKwJ zf0OG_M#3)4PHhmB-Pvl`DJ?d?dkzRe1p8QyW7&~`tun=F+rs0JRQw7+qYv=CzVY;A z=slZx%Y)Cdp_5b{cr!FleF3EY4?RG_zX|@*T|(ai&_1A}{(%z^jiA4e(O+L29BUGM zljHaqI5g+syrUR*B$h!1C^r3K@Usi-wQe)w$$+9PQ&^n~(z)XEw1#(=C4PU~S!2O7 zcx(~Rk28W>>9>wRGlpF%n2N6o6AeP^&DihP;}f}-kMAlM0f_e5;+6MLIMr_k{@7tm zE59MQ0ZzQW+E0p{wF2xmFyQ~v8M;ZE4lydlUm+yUCH5|*#th_h+5ZLLRur!kqC+Di z5llJ=62wyuou1(TGVx!S37SF&t)SmC?5~J-VdQ%QJT=OR=ujp2lQO7qOn9H`wzV15 zT@&3zXVgn--a2@DV%-c3pZnkoef8f~B&SYw zVvt4>=IdO7aDJ1K^(swl$6qS}__93}{ap9yYD5|ER2?v_=PGl0_Y{Ibxgq`8`VOxD zXFS9x5b>j;BShf<-b00*wpXojgsybIDU;X9jAn&O0Utxfjb`$;It0(vKSuvUFGa<<$b!yqDf$LYSe1V69C{{xOTX za3Z6AegzE$<}zt;Rd=Xv8r zNCFh~B&@n4x2|{6x4HzZQjISYBTW-K;CFMn5xCs&dz<>l@G;&?FLjo(7QvV7;hnk} zc&YT2-An}!`ppqp@)RXb+h?gTw>u2FPj&xcz^?|@6jU$bpL(BGSqp;8g#=3lr`}I@iA!{_fZ)~^4EMr zVdz>!iSVaY_79@6PP;&a`>|!fT9!cqe9Ptv0)M0!Ic@;0murTl)aS+maxpiXQOjjO zUB7M$eewW9Bg9H4bskYf{`ao=T+ME)Gg`SP9WrEEuMin66v zK>>Iq+04z;$-$}}F)>FcYr(or#RYl?^+t82miYNc@ad9Ho^T!|j9WoUuCXH=$e0~_ zGHuL4_ER3{CwbS1KkwBlDP9)UZF|j*Xp}M$e{O2&BbF`i4Zu5xlHn~|DH~!*e9|f*0sCvqlB7UrCl)4>6*n`OM06}kCtu|ZWx@6((tnw9z-UbrSj!0VD1Pw zHvyT(OXTPk)UQJ=l>mErOnZn8Yk;U;VYvfq*av#w#kw8lRtkc2ZhsAVA_ch0g0*P_ zN#%8m$q^S#?5o0_x&Q`#@HYStCz(=y_?>$pkZCYZ~r_7Asj? zB+hyq3EV9{1R^JH=U}R6SGE`!x}}fwpf|y5d6M<%2)#Wkc(dN10Sw<^sz-cImWO{M zSpnx~1|c}kL_)md>*rh#izR*A)A0piLGh6CZ?Pr69$Q^62uha)#k$`cClb3fja;ZvD1*q^ zPFMOw^N^jd7t=G)s$|9>5z>X>z(Oc2cYg=G+O{7V9?Wv29Sp$Pd3)-2#CE?dNk-@= z{1vkm-P-Ah-MqX`s>~`UnuRT>1t+MT<52C4_LAL?9a!n6Eoi>HinF)zX-vc;eP8;3y=cifrrUDebCRI}AbK=$kIR%N6;9H;C-GMzXS-jeO{U6nPZW6SY;FUw=@a%Li8$2cUuAI z1j^(Tg1R5==zg*>Al%%BemYb^!O_jYNC50@{N7mt48~ppto8*^`~$s4jQzN3E+tN( zwY2wrG8K^onTGMs8*Aq$0eUU$D@aJVy!eOammzdWuU*yYRh$Sek@em93^6+>L=A>% z;rgS4a;0H#%>#kna0}j$APM(J9ntL3t#nfjf%TSDEl>w9gH0dY`qZMT)ScP|1@n>cyTPr?nTxvnyyrN>v)i^9 zDVMx%vcrrPf27&YYDLv-dWAcOVCZys+``<_KZF4*b;<1HZ~CmP=k1+NL@6k3o;(9S zT7SC@6rJ3JAt2!`h~m1&g=psdoAv#)DoeGIEzCZic~|X`cIGR^PtVZ|AnReLMr*C{ zeKVxV@OmA=3!222RiWNPKhpuNz8{|*oe zOaBB&2xn1jWw$E=$P)e?R|wqhsAl((C;P?67Mf>UuI>dJ&v|34$5Xv(Qw-7!AO?g7 z{+G~aO)f9G1H}bAYAXXU+e^+|fFWjlgs;dh2m1qlilM*LhHXhVSA1v?4)^#4bYjJ($h|J`U5JkVtn6oBZ0cXSXZfzBw})wkFE~ z)p;n5-t#@Xaw>Ct{H(+#b`cyNjK=8^F=Qy?6j-VFukgXMg{UZv!m@8Y7qtK=_T>_C zA@=USAQ7G9O5aB1I(WAqd)XnYzW$9}2w>j~S-%~`1|BZUDtgaoCbLK|t? z&eXEcTXqd|06WC{W~``RnBEgGLk7Y{hD$C;Hk2(3qRQ@{kYoB!nO>Ody<7V4bB|3O z%Q3a9R!aV}WgI_~Fp45fxf_mD+=-rl_x0q~_@}lFCMPsgM45CQ?%6nf!G$pOo*8^BAmBCw3VQC>TIwcB7MxX`ZK%cc9tc$1?)BXS zw~Dq2kaLagjMqqCT4K&d&euhc>U%f@(azSi?a8vk_gTsgo6Es0T}&dgu@QPZJDD%k zOu?}hkl;G}fB6?SCTcJMQd>a~I&~%glep4i3S(}P%RM9YktMyeHRyyrJU-$kyB7Ah zc6knBoVL6qt;fI#5ua!bu;G@>b=W!lVobaRyw(9yEKI-qw8;Wk`vDucW&k9EDHHT_c2W zV)AW`OUESBB;!1+0O?>Zn`7b~b$io=#m+|Q-2^j$U)5d8vH9Y$x4KV3oGp=Bka1lp z8()S4(<`RS+yapNb2lGuwUkUAdyNg+;28zWwDZyMm6VBPabk%HKysHe`6rdVej zgdbcaRU!v@piPgsf0L~gShtp;w?;b#0an;-%L3ISHOo*AsQSd_OI{A5y9|yeUc_$- zY?JJ=i*GDu^lAt_OOBCGtwMt+z3vY60N|??n6RzJif@~9o-m4Sc_UvlM8Uk&!+}55 zPn`ABN=5JsT2hj~$=~8?nMaSvTXU02`zfsRUHOX?8n6h_!IC1>){@S z-Wdm?rRsnONi9wA&@5y~aE$mG9R2Cl7b6S_D1V>etQ8Af5eVlRT|Ckn=sfSdMJ__a zw79I&LdZ(1kzi+`a+f6KzJlTv(n%%Sl<nW3A;P>+`SnqYq zdVzrUlt-#nnL{n~zVe{*Cz_EBYyf;o2x9T9}c z)}BLtH6;Bg7=}CrDaz7P6=(l>U<@b%<&Mg_k4@GV@-d>ydhiJyR{OgukE}O_V$7Dj zgtCSn_&Jdb$yiUN%rc3KxNOQ2j@d4Mid!dgmri&8g)z~7f&@8tN2DezupLg|_bP-f z1h4sYd~D2Ayb8widaEJDCFg0J|HlCarYfRtvh$GzLCzo$aYq*Pz4q0-R~1;zq#%KV zSKc~81s~mP^~FCdRALc-FZrORq&gXPi!nj6^=csGH~tC?k7~fDd$4T~8f%n=njWmi zRa4KnSqm9K+-#$MO>@eAIj`)6y);!7q&rU*0~1Frt*A71@0;rka`DG9>=f6%*p@#l zyb=r(o|D-07&=pRvP>9MZF~0J2IW{iXCK;Jpi0M_Oe_N*9#n}mIhne+Bk9A;P@PWw zS2yr%PmuE-^d)T1#`<9gjI}sHQ75pN&ZDP+$2r*4U^!h8<%e7fw3NZw51Q{T4Frmz zMrRQLS^v=i39NFdC0#ghWOfcMQrz>8Ww-4j3h(PC)S_`sg2c9cH#P`XxL1YHwB1&M zq)A8p%pKDOUp$GdlPf?KG4#S&Ho+;fPYp-5nkRWY560x`DRR~;%WCG{=ZB>ngFm-^ zZ;#YUCb&Y!mIfa${Nb{NNce<`f zZm!i(64Di)rGq$?l0A<0eo{_lIj@o8Xd? zS+lWWdlBxYc*ZNcw9lRWq=y??GajcWkx>iY*3Nm8$^O59zwEVD>sj57wX|?gxJfLu zGYWTu-E8nr;Fl-!;6Ry6v^GC>YKccIcc!Z!fC?@t>Fqic;}-mcRDU-pDk8W43IqPB zi@c-#^@7~1Sj252`#=yUxc2Qr{sBpS%rGV>DdQ1++v2AUbsrc1EdC*}c0Rl$HzE3z zROR6b{|3^PMt6;X+$NRukT<#G?~;>+n74}@KlKFIdBYozAAyopQlGTp9rN#&?3Icb zP@*h35vWEKr8#bP+~u>Ad7@(Ork9-0d=h;)0d`$s^F85+#zhzkc{&Ra@P0+Z?ds#X z%-k3@E29TMcBA3uc2MUr!(XvN+YAcmE#D`m`^cY*33Ib1A3hCIsz|fV>5*e+t z_$oj86~op`)|S^P#Lk>ddTB-Lf&#~?&7DWVl_{tMRP*37Tv;z;0-2@&X))3a+QxFf z^+`-37@tq}eTn2JR%ZkhRb;*L*Er}Er=rLRC}iI0mVr5n2;=lzJyVn~o+{MgSk#R( zeJmy*3pAJ1Jur$q7|T4-trr5@am8H!^C0Gi3UtzP%tLS@aprTQPLZaZWA89uWSyFV zv?lq13mXvfMOzGiFt2zQdR~<{CY7`eiDv)+&}yx*b5BGzAk}ha?4FEwYu(Tsq0BKN zE$ilxO9gJPQ%CK?^?P<9x>79hrT10|&^>}BCFRw2q*I^rvG$qf+)4d0)6R7ja;_VY zGJ+{#5-j}y8*#Uq+lIQ!c??2PX@+ph;}C}@ z<|YzoX@j54lfHaH1`_PsU9VE-)(9KS;q-qC^t)z%&2od?7Z(mrcOFrobw^)B zT&AtBz(6$JFgfZa!Ng-=Q*nt>=AYs2g*WdsB8vYUj6rqRq4(5m&GsE%i}{uZNTvhG%JZN?>5|#-W(504F}av~bMH#T zNH)}(@@4~41)h?GD5i}a3{AO+od_5X{3Ve3W*}tY`72=MF9-&P9y9mTlp(bt!(B-l zDi1cj3zj@mJ0$@?GFHq1_x%o??R_GoIldPjv(zo=iE`+6k^&7rDp^PR*|B+vf^oME zxP@rL;gH}24?8l~BOJrs{&_@TYUuS!N73)zILZpzdw#5atImNdAiWa|rXt|99ceL% z$Y&DKoA0AwozAkL&FfrwfL93$CHDDKEq*V;7j;n;JXs6Wir$kCtwei8iX*;%Z};Q| zZ9%^VF>OdRi2cgG*K|{lj{Co3oKy$CBu5Dhz?1Ne~=G^`RYQRz+QgZb5<{GPm)nE!lPD zy)<%6`}hbJ`US2cBsXSgH*l-jY~WurLIof8vP+5L4;G> ze=b@Py_*8aVO9Lhw5s!GFvFQx1Kz`VuxEcUJLpcZ!KP7=5`g|Dt3s z2G}~;#ir>NmOz#{;}?v7RKf8A_wSdOJ8jOuI>|u2Fa#I1zhAa45q_@rUAY8Ewz%X% zyymooV-=1HVBw8ebj5L96umtOx0O5Vo3aQ4`o@cIxRg!7b2Y#>Wvu@qS)qhx2WbBT zXW?+kPe#YcW0az#*hDUX-(2rd;H#rZ+s&mHtmFbF+v%Z%%b{u1LEDvRG)2k*)Zza5 z+0HR7*vShRNZRubkBdMy4dLLH1j%ywT{Po}5ejGiWC$PmBFQ@+3@ zG(UwA7`Bl?3^VtD=4I;Ogn~=+vtqr2jsHkVV9IV&LCCWKTSTbs0u8zXlEyrPv{e4`qNn5`@fg@rdN&I6+FGV zo-`|?i**z&;8@znpOWMTMZR=LGov-9Mnb}3JuUS$2O0Xy8~H>TDtC3kVU!3@q-2>q zq9IV8L-4m}z0JNVMxl~G6Zu^gm`$2RpGQl{64^khBPm2ZF+C}}Wansa>1ejN#bk#f z7VK#7uni9CL52nZ9Q^gE4O$C>hyUoM{Ik^i{lr!PBCR0tJNjk1k#-~VI-po2e&-# z6}<@nD=m(rJm$(*KOjgi9-)vmjk=Ou%}B)k7Tx;p1m2M<%&B=Y!7AiQGAnTqg@1>r zuin<^%cM!3V@xlf%(XDu60`zXw}uXtI`=yboBZ+(D99hSlfYEFH1T#Ea^f>NdIfJf zejfbw5maafwaF3gBLwGsu2`$QYuB?)Y?1zaa|F{oVmVi-Hm~>=dwRK@(GY#;?#PR9 z9Wf;|eCYo%p$E?7+`S!4u8oZX@PHK0@H#TV-_9BP?2nY2o0}bkU1wrUCTc)(d353~ z{CDLlWvR-z@j^`1am(X?7p9ep_)S`uUu{xL4e3-~)VoB0{wAJdR7gIw!QO4XW&cO8WVAt+)gAfoYD z&`jYIxlivrGee^3;)|}6_pMq1j^i{J%oF(*rv486P}c!5nWJ||&ZVNEOzk^dFE`z! zVQL>9SqJ?q@_KaigDC%7%??nua%F6voLJ1$xQUKxZ}N~-K~0;T!4o;uS4Ez zW3o)#sg0H&!210xTKEQlB{&W6;q3Z{dxoPvrLr6dN?R=3FG@D=o z&#*-6WTVji)~lUiAX9EXk~tc$#z5u>r5h}CzmN3?D>=&`@Az+`D{72zeMtT2OAPG) z>ysbG)RSqIz6~oFv7>w8H+_brAVxWVbYF-5-j-}gw-gO30_E0r&*dC|#NiZR_4$i7 z@2sK(ASpSeIkaG+KSMgC^nbqcyc)(Yj{1_-r1=xv;dmN5BQiwuuz*}G>OK+&N{XN2 z=&j=t@!dC(>FY+gP>I^1bMoS=y2y+Jsm{3pa9gu&&2XtKar#u0Mj4cjUvQanVl75M zf*ZTn;boV6xe7;zOmV&(rA6C@eycy~8D+*1JaksAGX`-Q2C_T{tJj^< z%6j}~HdG%N$0k|DN7La}@QqirjEU!+$GbQyW{D9C^Cp0s#+@L%ZQ4HHzJ+E|Gcl!6 zz2d5YBz0Xcg9p1J!Uni*vXEgXY%g0iJpXkg&TL}VX;)*nl45>H!q$U zs_wL!*6t&;Dn_iIxvo+dQ3h2#0-rv){S3rA5E{%ZX5aNSM>-nH&|wb;8?_wMyjJPL5)#nE)Gos`P4yEt99}s zuprKyOy)uAn!;mPJ1aI^v#%jFNzRTS@adMgdkd>j;zcMxUV7ICoX|dwfkb?_qY=MN zbeEG9$|)Usb@b+~V}w2B!SbkiH!J;$!}lAqBIR&8-i(SS25^un7Bi~a7Dy?8*{K!H z(iq?%5b0(NU6j%>GS<|JXk>X37Et<-UL;^gN!B=oVCFOw_J2U0JIQ7s9dqIYJ?kEO zr*RB@sV+QJz;iIC!{F(*^s70CmCsKTbQ{7WQ1i^X;yrSV0&ENGCAl`+gG?0>4tc3BMjr9kieMN)udZ zWLpy(ggcC-8e4yzb`59U0FT$k*NH$fDn>+oZBu%J$N~Zl_^iPaz@w4fs3seCJgL^Z zaEf{=2~$h)4#EFY2v3nInRxmT?dr-<_QCoEy=6x>8Cm{EUWZdy|LPL(kr%fef$X_s z)ki?Tk@4>~QuL|A&1Zd#!$};LA47+Pq--Yx;{@1>U|BG@W)J`5SGRc>GI%`t-$1#r zR1DA{DQ6fM_ptC6;qS%TlLXD!BFU3Dr_ygD! z7}l~FN7E)=8XL(wS3~bmB-Sn<EH~6| zZuqs=3D>viKB%n z*2RZ}bS7G!;-{eM8fpuCQd!L|^ZTE>9F7P8BnCOK=(B{@yVwq(SHe74OV zh#f3`9{=C=*x76k4mu_47(13IBdrG9<}-z+^XCh*&X2hs70)HDny4%kJ9<@$O>+RK z2u&dk)KHa$tv_HMt~%YP!K(eX_bC6@%0Qi&#f4od!X!qPXZt$@>5J-|rKv zT=L!LgbkdL%|dJz{Az(7(*Ti}wqvB22NLr(;^N>ebm<|CDm(fZfk;G1b)_0CZx%{7 z$8>>FZrYPev5QitvU%PN^tEO?u;kGYWzP}{0oI~o*2q+hKHvq+VZbwkhpsH4H}fQO z(L*r$DR(9fNjb2sMYiWK|V&G(0SJ(guz_FYbbHXHD4ilcEN zP?kpkG z5PtX++=NcETXL}e9?2zCF7Xwvt45o4Uo&DS57~6|(3`%|Y`@|@j~KXQl0f<4j*f)af)yroPoRMOqn+c+d;gY|`ue;38s{&Pt|z{;GE zuHp$EF}CXv?=!Z^&G^_X^(;9ODl|v0;THnp#+4Hd@>3R%)#h$Bjtt2aX;CLZ6h5uF+AAeJnM( zYF|hg-q>u(aC4~N3Pj|G8#h&My$w_ca5?VVU9XC(M6Bp6{KYk_<;$=FTQxb_Au z-Ny97n7#UU$<#dNYzsjNMsk1Wz+EhU20aN8Sg#lHp}O61PV9!!F&2Q4vj_4F}sk5`s>P}aGF{@l8(W^Bz3|}As z=z_R(83<9HV931_!ohNK2lGm-kphdlw*KyPzl{JU@ZZV0*4{5+{hAdRK*2v?G?)8e%jvz(AKpUEnOq!> zpiNrPA%4PpkORN|zH50L=iXl?K9%CXB#*SE-Uo5vM`(xs-!`EP3@6m1|g{MTVJul66EH|1FQ+-A(%wL{}_ zaXZdSoOK)ke`n%rUmPzQX<&vnz3Hp<@hXqwYghq5Jvijt(4SQxDS+eA*IX6ka}w7b zX0~P}ami&%1g}7zF)t}l-{S!jINjcwj+y3YOvLp9XbQGXlA0#atJh^H+X4)d5O6s= zd}_!*e8Q_}b;|;$pUtU)2h6mRK_J3T9LAN<`AH^<7J5opF&*4x>Y&6UbkViS^*3tx z@H>C$(9R{%gQ)yr6)2^lr>3j#M_3*M(6>ULyp)z`s+_`Jl4K_`IiD!C66*-U#iNhr zEv66|li54qkLlr^c=NQM+vy_Ob;qcjAM55GK_$%D-Jz_6Q3B&8yqiAccIRBAk zbL^Mxkc0JGy!3x*u4sD+=;~UsQvm1~l87uKhmjFW#*+ym$NhHUkwp8^_^Bc?wf`A_`N|UW9haH znoTXP{R3|tY?JOElzhthtB4h2``)!dS`^IH+AX(7iDT9xoKyV=9p-E@)iE3|2uj^> z+ePD>t=4D$z7_-Sa|0&oj!EAeOF1u`GCR1+U$=w$LIz*mrJHv6Zh92#CsEmELdNg| z94N;{QQ_0=|LH(tC+o(}P#sSs)E0&<5ZPKY`);NR9pit_l>WwK(`dSZ@j4cJ;KhTf z>5N!G6lM^f)~%r;=6S@;YJ~m6oeNs|l7AG24D+Ehyds{@s+q$}t-53qXXAgzGW-2- z{-_Kf)&H$A#KL=k`R!!Kp{?I`W4^&5U+Hib9>5Q}6}ItWdSOK6pm0ni`J!@6m<#AK zm1pxPbLQdna2)c#ZX^bIY8p0omi_Y%Ckz@Qs?iyx*rLS&U}EC%sy-||P^0B)PF>4Z z0|)8H4J^vZ)t3Ni>T;@Q;xSeaZt(`3hbRl0M#G$X#m~=k?kM@ zDf*0RmR<3Jm1E=)s`C%i%_5tHbOT_bYdv)ME@M)!s~RBqKa?1R_uqZkRMp# z#*r7muE^^nC2jAsT$yCV2U^MVJcSu$1PodTI8@n}!DN`iG(aQ;#sVq@cRU)X*-tr& zI*Stu@b8BMBKCd;71l-MMv!7K*26=~k1$EyiV;+0*}10|X~;In!A6kydA!}h)b%hh zVw3Y-bj~&vmv(okL_(faKKl6s)3Nq_JGv}Fajatr!hB&y(8GM2%rc>BLOP0+Kx?mh z1$2|;r}P1+yVUCqMi3|pfEADCi%t8QKD z-k(4S*)H*}a$7LK?<+_$TAc=~%DLta{QMN1N>)8hKtY2|aSS~df{d)mskiqJj@RCC zKCncn&FX2xLLk7UYf~z`D;&m64#+#1 zt+i=|kW-&eCat#s5(E4fvZ6}6SJ9{3WcRLH!J*`F&mefSJ?Vh2PvhVhauqdL!Mk|6b7WeH`fd8j=F2NiNA)&4 zIfV5=YAb~qm$%*i5m)}5!os47z8g2ESS5zp4Sv6}Ee8)V#~ceE)t~>sQ~UvecHHMz zwa-dCgpodN(*&Wa|Gs1c>%Z|GN~7vK*8rHGu4~cqha?>8V2LKA@0zki%y3gV?%kIF z3o_CcQCL8K`I(a_s0Xscxgyx&-?=at7DKtDoNF6oqo%sSjsR8lqbTfm8k?@ zfT0BP6+Z_-QjplBQWtuso0T^O-9e`i6ALMxvdP_qm7DH5O(_-xg~;ymXHu?JI2c6# z-`LX@0#i%(e*l$oVyl23VRxK&64uUr54Be1!AjOJMB&g0x9AqBQ&**gGBP$|JNsaW z5w#lE)*>uhz)0xuNLAhtF6yL)RPGRsvaQ4C^0f?1Qhx?e5RtVHNgEpb=@z32XJVPh zI0~Su)9{#@hV|x{lN8{KT*G`6c7C&R18!{s%$P|oSUJ`pOXkiL03V1g$lS>@6>?=k zc8z;vC4Yq)SCZC(k2?Q5Xusi}-AL ziEZQr1ulDflmtocwsG^rQms9xAdZy=A^&QCHR z=kR>TUea7UQ3*bEvGjH&fuu%FRFmW$J^6wL$rJ)37puE#G?#&&q#IsQ$%U z!p}Efv0ByMLd!o?e}yYGcZBd!dP0OJ4(`)G=!vP3295u|n?3=idR|mMAx}LfyBw6F zPi{g5KaG#4ZE_@Y#org-*>dl|j{*Rr$l;KHWK3RFCa@3^E()$ghJpDo?7Ror%l@!i zib~#mHdD8@F8{9^%(wh2YPqJkw{}Mk3k>S}LvAXvhO-_d_Ri=Fezj753Q7PUNM|vE z2$Vs9%Pm<;KL4{|w*D#NI6(Fk@(lDgLZd7!`E-8fcO;2-2nmolFpfIa`po-C=puwS-K>oH}Lzn(55@wOe%w9;` zL8Hge_d49ZBD;f6KCAB2vDS9E)g!+P2>P*qU}zl(pM3dB<0TZ$&VHQ8CKbbf71<`b zMAvsC9JbcC!gf;j8-SBDMd!Ibj<7*~5-xH1p@TfPFJoUMOCB2X2L-1*S;m$pPr;2aET%9N7|q!O5QiA%q;sKKZ&VhaXZOVIuBkWSo`n=9 zgq4mi8YPhx{!^;H35_N3Qcq>SJK7&nluS)|QW}F>gLOe&P#%yXD0m$mw$7FDFA)Mk zGLZ~@7n=_&SQQsbU9@)ez=2sRoKH;Fop)_MUDXvhL(-Rk1Hc{@m>9<+1yon4tkYI3 z*20Yth5(6HtzukI%|DP^n|qH7iKh}~@NU2_=dD*RAw|k%g?M>Sfl&2C%I>CS71c1< z6ot`yq|#RQyG-T@2_-Q#{C)j(q2n54jhGlr&2U^Dvmn>_I-V1`fJ;-|1}w}gg<;Vj)g?KA+G$c*YaC!xnE zU^1enj4#*x55|Vx6KV=I(nImVlzEYV^l_<32pLZ)8GoM;J-O219kgxYbV(2GenHKl zS^`RJR>jj%uxSx#*IxR!TcoApLjm0GS2@;i*N8ZjBJIdMO zn$qYAuIP?GHiIPcOm;OW^vB?bsw~NZBC-0H~qfnu&d$yuMOo@cXb|RLbJ62 zws0h6nfjNqHr=^d%Zi7c1jAcek-Seu(x*jJry(jQ{YQ>p{vhl4W>a;anLyrVn&Z`V z_|BsG$_ZUMUhXds7IP-W+3nAx|J{0W-qD{$i zj>f!K@a%vrHzE6s-w5V2IBI+|s`mC4v`d{6N~D%JNqK7S#lqizmdPj8Xr}#qjpml3 zNZ~!#fz5>(AKHtAw4PFd6V9hJk5>kdUiI?4`__BMF~mh13|7uK?uvFdxjf8w0$T<3Z28=nMQIPWHVJsVZM91Y4px`E1J zIz)z@OM9DfGC}t*C13F~iSw-kdp5yGdcvVIgH^(NkQ01RgSJw&>Rw?v3t}-cwD@o z@XjWYmwVrXTl$4i7O_}IRU!`sMcg~93x)Xi)=WIfT?wsFD9s#=AH#a)N^w2(el|qc?_nRcm6z1c#mxJ=HiWx(~Rk>{vfg`ICzaC^}SLuA@gnm=OWgm^*7|7-oW{ zyA3HJ;`M%0L`trlMzZe~xNJq92f^PaNv@LFQknIS@~PrQ2Ve{GlNKbnbk$%g~I<~IzUi#99QNNA?ztWz>rd$o5%}%-id5# zq)w7jkubWVL+F$-!Tq!+Fiv+?jx6B@<7$|sq*AE!A&A1XiBN*REIj>LYc+d55j&0v z5AVt};XPL}>x3QGYY#t0qjn%U?%t3vEVPA|pM;hl7sjLSuY%lrOEny^RYNVe#_W?k zNh}B>`%=&zzY^a-%-B+?qXF%PhIO0HMlf21#CTZ#t2x-z-XlR@_9UB*3cFPGz9Z6HQ|4Y^Owz&fkfLyVOY*m z#5q*<0z^l6Uq`Ja1k@m5zhHdNIf^o_Pn+=!FU~}WZVMtoX`-&L=B!@4?_eS9@2vB+ zk2osy*epphY#A%VQczC+Z2)#D(Ovn?$og3cnVs$l^15^a!%SQ`U^LoJKl6St)&c>4 zXMR4GnePf!Lvrs6g52bj`kJ#|Q@X3C^{3x|_n8h4Y_j`0gc|o0!wM8kEha5lmpdqd z`eZD&ciYnkLG+u~pT+Q_B4yT2HF(S@4~AQR%=S+qLOIZcwA>|!(n;8g&bO*yeQv!4@!J`Pyd)5tVB~|Jll^6dp`M|xalVfcN9e6 z9TI(cQ6aCv3sub3floqTcr`5cj({;)tM7m=;qI40>|#B^SfLs0>4{#pNWOU+Xv4JGf_>`<@8SEcA? z<1W{18&9gIo#>;im%b)tEDbmeR2Sj{6Gq(21!RdqXnoG5Q*Gj_uAiJc>?Y>jx3#rOIjXS zG4V_4Z1Edk0-0!MuKE_Sq9b!XN6z10Nc$Q68Fon1#8xMsbr<~qK*Mz+!<2UTwid5) z;JW>avV0q6v?Kja;IKprY+ZmxvRd1s$qeeFrDZ~KoW?}Cg?craiYuPVBK;4lFMQWi zx7cB(YO_BdQ)KYg*s%;_vNZ>z@_Y#VW_)*QQ>nSvz3Bda3g&H2+b+p+h#)N5C!ve* zj`~f5@QY9N2?3U$a(7uZWIzS#P6B37ox+iShB`zo8E=n9{oC3UEc@Hoe`Y!IEh7sD znztxk@QMLSLhcO6(}snV6&?-iWV=XnVVoh*H>*e zQjBdTps-YgbN7`_vqD7UQ7SRC7jgSeS))E;8=nj#qiL5jJV2iuvhb$0@o}mAODI)E z_PCENQtHWum>;8gKZnr%#`QHIxFLx=`|Th)(AFTfT%z zS#C}HiVpmJ5cr6}5-Vd=-Mv@hLHiI4nNo>rvorqklan`w5*OYJPEAe z*r>20ulQ7@RyZX=IcoZo(j^yWKdbnyn^8<7tCU4wTCNIdmCIT=U$|G;y3J`vgJOJn z;gwI_b%*#r%-3~rgVq(Vlg+9%E2j=wV-8&+y6(wM~7_#w~03rBpiPf&%lV!z6i7K;510~)eU~eEQabk zobTVRB@Xd7p`rjsUjB~>1IIbsSklyWFjnz)-_?tBA2V>93LITJy!3Ri#1B~5+|<2u z!+U9)XkQqOhQDx!1$i98~p+$Wz+>C^n@4m!X%KNy5hzZ9X z%)-f=7dlt_sDG5M$wFC`IP$}wlrU^XW1$p_*~ zi_}F@mdgd9dONALen>$%x-L7}+X*dCEkRhD=cc-^nC9#Ar#g|p;+B7}?5e4(Ic@sCNF5V(mMY@>-6i>R;%J_!3Yw zMdbGq2w`eAO%@x4qL(j+9YvIkAM^NXS*87}*Ose@i0eF1Y?pSf)cDo@5=gg4si9z*NhbX-vq3s#thc9fke|<32B{ z6<;{#p1o@L)vAP{_z~S}IKK;@-eecwa)*xjMkhQ2L+zUOKVO`!S8i#T#>u_1MxnV) zB?QuclD+?n_8`l{uFGEiGZEsI?Y)@6G5weL1UWk5W3o!tdOw3>a+)&f+>l2l;h|>I zYUhheY?=;nu}FLqLE;(r1!%rG?v(%1RcEP5KKFawIz{2s;c_w(lXY2`rU>m4cdBO2 zolM1$G7?I|pVg~=!fdz7=p+2dXDXBmvYS$5Pu#pd)Y>-M#Mc@Wk9y)#CrzAT6hq=wGEWu< z$*q+1j(Jm8h`-e;JlnE4T5~~F7Gel`nyG!hiWhf{BZpK>Og(aM;=`gNM6gUjX18{LNBU0VEHUYn9zhC8H|!eP?m9BuKexj1&C3zdnN!P zhFvCl8y|XFshee5Oh|$9Ptos3Pu_+cT9Y`0G{bw9QlA_uKZtB%FmRZ$9iio%>A;CN zA%D)YPC?PlHQ3f(>c`Sj9rNEVO`}RAjAPaHf@|AcYdQ44`dw`QH7a0+0F7n-gIp7j zuHL()ss}j@NPPE#2LE!bj#gs1eyx5D)2Le;j#9flhLqX@0;_>%p9qDJ;H3KK5yT^Nv#(lXux{jHF;ZJ zAMgLT^tsCc<{kx2^Xo~1jmg;aX4E2mW7;6z0t9QuM@Sji>kr>8qIn2rPXlr3F03qn zq9^8*Hy%KdFi=Nc02Z#BOINjH(>20NY7N;8MJ$g_dG*` z>s#-V(Wn;^{7S*AirKdxkXLH={m)SwK&@BChthEK$}pmOnHO&Olt(7I{xrnR%2|7{ zxgy4h#!%W#m6&rB0TsNC*mNoo9HTJz-t4vbEx@3Vm-gl3k?So1qfLea+TBy+Kir@} z(-J9ta;+kx05ppzMe>K0K0@Pq(5znYj`A16e)_j~MZ%9)>q}t+!9-R6iTxHM-j6OZ zWFN@1rBWtBkhZS2Wy7A2#^WQ^^ckm(jJf-@Gp_=y38U-B7v8<3r2P$*4cB4OJe%7Z ztEPS#OGh70HJD?%kf1Wo7@hHzJWFlfhg($?k;Ksm{fK>oadWTz94yABjK$%zR0I=D zE_5@-Z25g;`vU`%0|EiUR1&gscS>mlDG4lQ!F{^|oZwTSYEE2fy;*a@ZvQx5s}bdb zqqd4352~);#|%=P^*Vvf`YKd+A3-0ElWzyW{UEiA_|8XIPK*n79_U}ueJNf*)&N=2|qELYg@FD+0P z;NnP~javsfYLjU{v{lh<)2g1&?t8fF27Q^YQN$RgrXWGx-d|I?F+?c)xQ_~1U|8z^ zlIl4AE?d`@#$;bAO^oQL`bx*Y)@%E#887!OXC<$TSjk^8h^y(n6g#uYm-_4gPC&@$gl!ytEqC5E7qFN&$Hw0e~3 z=GJHyRx1blNf&V+o?NNJ7acTLzhs&kXC2up6&H|;JG*w#G!J7^R!uUWM{dII_jE7) zIDvwP8{J-Bm@wmDcxz!pzTnQN=+`ArIMG>n`xfIo%V_rTa98rT>WJPKLOB)g%(kcF zoYQHzXba-GGTPBG&&lChgmAwG@Wn=+-|g3&v0--iM58jnGjoRtqR->f#5dJKe?oYa~6*>3c=IYT*?l1FlRJjna)&5gs2A^(pl4B z)BT=Y>|?*6J;d3>ptLijOPYtZVc$6o^)1SJ?7i<}HayzoAbhviU*HN;@d~1Jz#B`_ zWdG@B$x0j;v*7WJIY%BT_PsCQVjAL+r#bd-OkEWI3pK%g<{N$fH`q5vc#mXTmH2bZ z{RSfLv>>SONIsCZjjgP$6@x=!i?Mn|w67>X&`xl8-(|#l(#u1Ks@R4%+)h1qPOU$o z^1%)-G9GXbn3U{II_kmynwgPybBq)CC-T9s-XjdIlH(t<9C^Jo8^N5JCy~8T$IwWm zTD4(C^hTA+kM-ZY%xNrt4(b0k{!u+51(I?y6K5PLn#e-QRiiR*)0vCCqCt|8Kj@{g z&n->1)wNjOV*I&Bs>3n6lP4PDY90=0hlGC4eWZ(KG^!|zeCnotwv|}b7B~`n5Y`{y zb*Fx8FLJHWzQwj~Y+n@AER5-rf_8Ju-%Vpghjq~o;t0)jUP853z+!`dqye}p*F@QT zkk^syU!joshl_MWE#4JaPyWd)8;fkmKf(d)s#1Ps@iRAb%5v#zOJ)b@eRk4MNTC9y z`Q|b+^W?$6aCRp38drA_k3=pw>ITcV`eMyHYo#KPoJRpFDHk#YC`xy3l>DnWr@Q-C zV*59M)n4w8aGQ@=E`(CZqCu7$7NGkPowjH5Y+dN&*th$@29Yy-l2k^Hp!RS=@MCCU zh8)m4o7Pe_NgvTGn3Fl91*1N)N|?$yU8XHpN0qmv&te0<%V0^p$r3k;ak3NG9pcCd zranj;iZ>0xM_E@p?6xuxiX<&l34=$Ll38oEw)^wXwM&~%i?yk-`G}U`TQz1nC^)sw z&4a&H5J1ya>L{tO&%7FAQ=QU|+e7rK4+>dB1^H69OBMe)4XV%1N|lJmR~1EH$qOLw zFD%yO1d6i%l7d4_sfF5Q$Bf%Fy#qTao~wW>(Rp@71T`T$PAaa7iwF=Z{vf!bNo7fB zIX8O&MJcM6s+)NcXeRWFA0de2V>`HX^C43zsn3CK&FaBQFoDOZf2 z&T+Eh@x4*#iN)Rv7W_INVg)2-X@5*OGrLGX_P!=gl)|^>d!?PQ8C!yljAZQ(7z`l$ z0DfR0Z1p^bf|hgb_^Uw%n^0n^g;}^)Rs&gaIxhwgJ>vbky3sl>sxWqlZGC2uHCHY$ z5&*lCi&=)+;8zL*{5{uh5h409U4AJ^9J+}PscD$uNNmpt{G{XZHIbA@0VD-5FQuX2 z%Yo(lRlAHtM1)PJec(TKiq)ci(NG<|bf7zQ-OWP4ym{;XVqJLqGeraY?bBmze>C@w zAt#K?95bM@XD~oYxc_!|r`B7l{Ry+roL4&HGI%GbkUO|KxCR3_`fSGfrZ`7q=53IF z`%qqSyQ$|^SyM5i`NxCM3oABSTtYj znfh$8em}uve=3~)6iy?FGWhIAlMd16jz)+JC>~Mj&{!qBs<=`b}c&hEbFok7T z+7PsS-WHVq0kEiA6^>nqm**2i)C_Dr*MMVgzjE+Z28J)R2#Vq#L~v2o;K2Y1hts|F zfKrL7aTgcAPKSPQCNKx;W491Mh>wLrJ z(5%D-B|KI50cifH7c5XE(=U18KHyjL>1M_dG@rkC(q!>Dl;kSzqkxqjEO~eEdvkbHV*a8rrwSuJZNyQJX2-1Avw0o#h6Y;nX z@;McWISOi60y^&m1hl)1F8(%iNN;NM$eHZ33Y^n03*q+M9_vZYpjzz z5x0~~HK0W7fb6>#TxrMd>n%i({=L^?kE{ODevr~PdN`5Q`2(Ol@nLmP5pmv89Uul& zdf&^FGL_425>@EMjrWf%3YUN}9-iH_ONAyi0=wWo+${CIDb}6X%kxttVII>(iA`3( z-AXc!6->uK+})qGypG~cW-6=PgZVIy^fF3}$h}<%{4?Z+nkb}$xUJmO8nw2Qu^_xB zev=HY`Pno4g2>K?0h7Kf{yxSzyPav4R!{*`1eJ+`=gF={$EzEQ=Ii#|*UdC1(`IGI$ zCRyH2w@`w-I943yins;MAB=dSwcG5;)A8k)FPl-#x2=#H`AFB?u=#j&nM(&{0JSxM)URS?M@@sv~w)DwU1T2 zI0zz__nZ7LHAdt_P$GUU6nd7P{JvB##r}i|Zo=O@8!OPP@CpleSUh`8hVa z{1|VUO2{g`C;CHPpznxr9SyCX~&`g@b{@wBlE77?75W;{#W>Q^!6> zWPP=n2+i|Dr{LU;MzK6ah`@|nkQn6Y2)e63(x%(#MVfmkn!Zi5Q?eRl+_$(-`*>I? zJs^VZE*AMpTt}GP5O@Jmhr}fl3hJ18UX0#*ld3Cii$Fy16eK$KAm(<^B9Z-($${UqftvaqZWi{e*vZ0%|bbHbw1xIu3eAXnW zDODD70mbn4?)Nq&S?DH9Gywbk-OV{tg3a78XnI|CqLq-7;CO-w4xbw?{n^3HH4EIe z={TJ%1)C~K(DS2|k!P)}F;n)u=*k%zNtg4Knb(4!(@lusL%rhb1v<)xM<-hG$gFng~>~-=Nop&Mf5daI)3YMe(lB4V|=@$m1^=67`f!E@tUoQo?;bYKSgG^ut|0J;4 zfVDrhR&Y0zgu-+(poZBCZzOxIsBvY4A2l6BKW^-g|CDGzI4}K)*6mm1&y=%|b|cvw zq=!@N<@%!n^b#(NQHyKnv7fNDx#}t4r_Wf zmJz_axo#BmI@AWHMIy~RjKmsFHNa?t8_Cg^L7*$%I)omy{~}M%e^40QWTgpqmrl7# z{eq9cb9&$+(6m4`o$3?=Dm>%v8}a7Gp2i{CReMBl)FyAL!nK>?jEO~Hvbo`KfQ}Pc zVp%B*h2{cEL3)bPyCcD?{f-_YTAx5WAJw2I6)>+FSj*t3)JUR9exP_tndv}tBNF=b z3&!U0XzO?SX&lfZEIS^(_mU<;KnpdOfW=D;A(nUc^z)r&*3c9yY)A!9byD|wCRIf2 zNX1ik-p^z@g@*s}^-8H1?Lj^DLq9Wc{?qo`_H4@^qIVYd?n){OZS^MD=-Qr{G(vV+ zR7EZuY>Ki}vnh7(QB{TMCI9^{7xVEnLMDZv$RoBxUCK~50$AV`!*ST$2dRQs_8(aW zvpyro)`S?EBPfBQ&Xx{kg+Q8%V7E+i1^Kg6r(98bZ)cX2sM+EJhZ)S93V+#h1r12o z_N?Sr{`%JVNV@o6uhI7gO|M*FAHHae0dNV${^gevR>?tUj=0+52>lNmmmF6!GAI06 z4@ELZiuhlvas(PM_QoXCBvwo{VzoVQH$YG-Sz1vTM97{TJ-~vbLX{3p>ztFYsPq!P z2-z3Of4cX{-oN~a$`tBLlO$6Cm>~Ch=Ip|{9?gQML*7Tx)P5>eeLRaCHjU6MQBr^a zFg_N#&vq54=+|xU?+q=9N$vB$R(qU!OWnK_%kc@}0nN_p+nwv!Mzm^L(hqVS2xgq> zJ{#k>de&lER`*;4WN;ahz$O388Tb7ju^m!`uSI4$aC>Pro!#BKR=}m9GlgLAX1&oV>^61g}An2vWjv+J*pMv8hhmhWg8c)C6vW_{sv|FRT~l)j0{Y z)F`2BlkG@=nJ2Pvah}5Y#;IknLUpqBoSS$UpB%<2)RmKIA(QO~*r~FizHqb3EayF0 zHRoog*R4PQlkC|1{LRAOP;g~E7U2Bl|2itlLU+dxK$J z?!vd3sfdHr>s=|-*1`rCk2v~k&^1^l_hM^=hwYwQuJTnfCW3nvxZhqvw4y43Af_jt zVCZCZ%@QbHt)Eeqv zEU{sgp<5*zB{g)J#o1-r7S{5d?Me`muOU3iEFS54nu0(D8)5VI)-%`yDcC71${yP=}SQ;v(!Cz|L@Q!Z4(pSUk z_T%5to>;Og_mA0|GFv~(0pnxUuA08N4;AYrn3E(W1z6-+xGPe;WhTAtz1b*mT1-wc4pCy|_M&?BH&gi@jfk-VFj!OCNA zQfvtc8~W2CTjh69#6K$rpPDP2NYK2<%;8BhKUf-~9doBM-_I#x=QkS&QZr>jTXm|? zI(RHfT)9!!J8RZLHOjm?bPyaSdIBc^aAU)bDSj+}rM~ATJxFO08rQ2z3Ns}=B`W>q zlz?DI4w$Xoj0mXF$vcbLr3v(Vl-ier79feWYdszmOcj<+ivjL`)YLZ7Iuq;tt>t_Q zmKR6kaUI^F9Zk(#B+Dn^RT|(jr{iR}jde#6;o9~Tl36&LaPT|)M`EFKjjVSdb6P%F zxDau{ z4Oc~&(1o)G0x;GBw_T(UgSJ?&#tr#dy{?ewy-E`N-RzLlMm#-Z7ay3)fLPvm72yjO zF?N5D%ig`LvP&Vc0PpXQTIqM3`z#kmWgrM|ZE{7y0F_vuLAy~vu&L-Sb1^b%f{r;p}X)j?e zSmM$gwA#)6{;O{Cdqi)I_AaFP-BK$jqK(Pv(lTyiJczZujt%*WBtuF2X}Znj_JY_M zN)}`D@R<;iR7I?YH3Ae**SX0VQKEi3weeXWw101xksr7TR=18TuVA?jd}Rh`m>6uB zj>;=J_@emGisjPiFY7ijtx|;YE}yiVWWK}znEJ7c^ItLWY`&hof9EgtwsmzOtZ&s6 z5wf~R?p3v`fGedisZ8j#^4D8}(f8*25GZlqH)$6@UPR2^aBjVw0U~EBzOA`s;JIbj zJsoAH`-@LF$yP)fMBVR3k4Ba(q=Li+g&GRH#(M_L!A|q?x{l(+6X6=6+u=S%BU=H| z6$G%wn1=8tSGu-wzWv9l0xqR=f=B*rdNGC%6|cTRyJ%<|T%aN=Izqb(1E z0o}tlu>3AIPhmUxJd886qst|9`@8B9v)wP>5cSpA!dO<{BL?#=`S-MRWGGQ-W5XkqCI}Jazo6^YihFF044~0^$cTE6LyXlI@29u{G6&&m%F8_#f+MyN}#T&{lljOW+|a|Vut-SOC3|3+0g;Sze(iY@A)jYg>Z{j|hALjy*Z2@2Pd&3<*#?2DH14#N4# z2jd>D1ao|Ty*N7`I^aPs8!IHFwJ{P6N?l*7+aOQLg}9&Mnbs)F6Ask|gZ2l2m#|*- zThYSZNi6odJh$I6ey*!#`uoTf(s@vrtH(n%INuA<#lXm@+UPqZIGeNWy2;Js-aSW{ zEw5g{%(@dV2MtOsL4g7UhkxtvVNLG~>C7O`#uh)^DEw9)sRA-bj~>ATm8n4n zmhYIf9}C^*eDR|k>Xj6FHGP6m^8P!k%VGUB?DxczlwaMuX<2h(v+|@{4rbr~5Ub$9 z5eLD52m=$hM79NO#h<0I`2+BFiedD3kb|spDye=no4j+;KwB?l`jYtbPYrc;I8Cxs zIUO2_;*c9T%Lf^bhV-CE8UT-d#(+XGV{(T1=Xnq@bxE72kWbM&fGa5cxcy8fk^w|K z9&kMm!%EUM?+2s@;bp?)|E{Ft$$dmC3&#i*Nh}93dK@1l{B&zhOyFK}Ip^8VL|M_U z`nG(C)jP0!X~)UH⪼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 Hf`a-VFF-yJ diff --git a/crates/jolt-verifier/fuzz/fixtures/zk-muldiv-bundle.bin b/crates/jolt-verifier/fuzz/fixtures/zk-muldiv-bundle.bin index 5b77019d94298705c3684517fc9f09e9824627ea..9c6d13c573c968f3699dbd78b846c0c0f0e76941 100644 GIT binary patch delta 86705 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


    8nLJf01OO3h5R?(qbX!x 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 delta 86703 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%0A;*?N~4=i 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 diff --git a/crates/jolt-verifier/src/error.rs b/crates/jolt-verifier/src/error.rs index 68fb32a81c..090e2b8b4d 100644 --- a/crates/jolt-verifier/src/error.rs +++ b/crates/jolt-verifier/src/error.rs @@ -1,5 +1,6 @@ use jolt_claims::protocols::jolt::{JoltCommittedPolynomial, JoltRelationId}; use jolt_riscv::JoltInstructionKind; +use jolt_transcript::VerificationError; use crate::config::JoltProtocolConfig; use crate::stages::ids::{VerifierChallengeId, VerifierDerivedId}; @@ -154,8 +155,8 @@ pub enum VerifierError { NargNotConsumed, } -impl From for VerifierError { - fn from(_: jolt_transcript::VerificationError) -> Self { +impl From for VerifierError { + fn from(_: VerificationError) -> Self { Self::TranscriptRejected } } diff --git a/crates/jolt-verifier/src/stages/stage8/verify.rs b/crates/jolt-verifier/src/stages/stage8/verify.rs index 4d4dc043f9..d21ea1dffe 100644 --- a/crates/jolt-verifier/src/stages/stage8/verify.rs +++ b/crates/jolt-verifier/src/stages/stage8/verify.rs @@ -3,6 +3,8 @@ use super::outputs::Stage8Output; use super::outputs::{Stage8ClearOutput, Stage8ZkOutput}; #[cfg(not(feature = "akita"))] use super::precommitted::{precommitted_final_openings, PrecommittedFinalOpening}; +#[cfg(feature = "akita")] +use super::OneHotTraceCommitmentMetadata; #[cfg(not(feature = "akita"))] use crate::proof::JoltCommitments; #[cfg(feature = "akita")] @@ -687,7 +689,7 @@ pub fn verify( where F: JoltField, PCS: CommitmentScheme, - PCS::Output: Clone + Encoding + super::OneHotTraceCommitmentMetadata, + PCS::Output: Clone + Encoding + OneHotTraceCommitmentMetadata, PCS::VerifierSetup: super::OneHotTraceSetupMetadata, VC: VectorCommitment, T: FieldTranscript, diff --git a/crates/jolt-verifier/tests/support/zk_audit.rs b/crates/jolt-verifier/tests/support/zk_audit.rs index 34d96b05b6..9e6158d0ce 100644 --- a/crates/jolt-verifier/tests/support/zk_audit.rs +++ b/crates/jolt-verifier/tests/support/zk_audit.rs @@ -13,7 +13,7 @@ use jolt_verifier::{ stage1, stage2, stage3, stage4, stage5, stage6a, stage6b, stage7, stage8, zk::{blindfold, inputs::BlindFoldInputs}, }, - VerifierError, + VerifierError, JOLT_SESSION, }; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -67,7 +67,7 @@ where public_io, proof, trusted_advice_commitment, - |statement| T::init(jolt_verifier::JOLT_SESSION, statement, &proof.narg), + |statement| T::init(JOLT_SESSION, statement, &proof.narg), )?; let formula_dimensions = jolt_verifier::stages::build_formula_dimensions( diff --git a/examples/recursion/src/main.rs b/examples/recursion/src/main.rs index 2d7ca71ea5..67336b6815 100644 --- a/examples/recursion/src/main.rs +++ b/examples/recursion/src/main.rs @@ -3,6 +3,7 @@ use jolt_sdk::host::Program; use jolt_sdk::ProgramPreprocessingMode; use jolt_sdk::{ JoltDevice, JoltProverPreprocessing, JoltVerifierPreprocessing, MemoryConfig, RV64IMACProof, + VerifierTranscript, }; use serde::{de::DeserializeOwned, Serialize}; use std::cmp::PartialEq; @@ -386,7 +387,7 @@ fn collect_guest_proofs( jolt_sdk::VerifierField, jolt_sdk::VerifierPCS, jolt_sdk::VerifierVC, - jolt_sdk::VerifierTranscript<'_>, + VerifierTranscript<'_>, >(&guest_verifier_preprocessing, &io_device, &proof, None) .is_ok(); info!(" Verification result: {is_valid}"); @@ -533,7 +534,7 @@ fn run_recursion_proof( jolt_sdk::VerifierField, jolt_sdk::VerifierPCS, jolt_sdk::VerifierVC, - jolt_sdk::VerifierTranscript<'_>, + VerifierTranscript<'_>, >(&recursion_verifier_preprocessing, &io_device, &proof, None) .is_ok(); let rv = postcard::from_bytes::(&io_device.outputs).unwrap(); diff --git a/jolt-eval/fuzz/Cargo.lock b/jolt-eval/fuzz/Cargo.lock index d76760d101..415bad5ada 100644 --- a/jolt-eval/fuzz/Cargo.lock +++ b/jolt-eval/fuzz/Cargo.lock @@ -1278,6 +1278,7 @@ dependencies = [ "rand_core", "rayon", "serde", + "spongefish", "thiserror 2.0.18", ] @@ -1559,10 +1560,10 @@ dependencies = [ "ark-bn254", "ark-ff", "blake2 0.11.0-rc.6", - "digest 0.11.2", "getrandom 0.4.2", "jolt-field", "light-poseidon", + "rayon", "spongefish", ] diff --git a/jolt-eval/src/guests/mod.rs b/jolt-eval/src/guests/mod.rs index 8c2a17d362..2ce7fc26b3 100644 --- a/jolt-eval/src/guests/mod.rs +++ b/jolt-eval/src/guests/mod.rs @@ -12,6 +12,7 @@ pub use jolt_verifier::VerifierError; use common::constants::{DEFAULT_MAX_TRUSTED_ADVICE_SIZE, DEFAULT_MAX_UNTRUSTED_ADVICE_SIZE}; use common::jolt_device::MemoryConfig; use jolt_prover::dory::DoryProverPreprocessing; +use jolt_transcript::{Keccak, VerifierState}; pub use btreemap::BTreeMapOps; pub use fibonacci::Fibonacci; @@ -26,7 +27,7 @@ pub use tracer::JoltDevice; pub type VerifierField = jolt_field::Fr; pub type VerifierPCS = jolt_dory::DoryScheme; pub type VerifierVC = jolt_crypto::Pedersen; -pub type VerifierTranscript<'a> = jolt_transcript::VerifierState<'a, jolt_transcript::Keccak>; +pub type VerifierTranscript<'a> = VerifierState<'a, Keccak>; pub type Proof = jolt_verifier::JoltProof; pub type ProverPreprocessing = DoryProverPreprocessing; diff --git a/jolt-sdk/src/host_utils.rs b/jolt-sdk/src/host_utils.rs index 0e53237d36..7bac463967 100644 --- a/jolt-sdk/src/host_utils.rs +++ b/jolt-sdk/src/host_utils.rs @@ -19,6 +19,20 @@ use jolt_dory::{DoryCommitment, DoryScheme}; use jolt_prover::dory::stages::stage0::TrustedAdviceCommitment; #[cfg(feature = "host")] use jolt_prover::{JoltProverPreprocessing as GenericJoltProverPreprocessing, ProverConfig}; +#[cfg(all( + any(feature = "host", feature = "guest-verifier"), + feature = "transcript-blake2b" +))] +use jolt_transcript::Blake2b512; +#[cfg(all( + any(feature = "host", feature = "guest-verifier"), + not(feature = "transcript-blake2b") +))] +use jolt_transcript::Keccak; +#[cfg(feature = "host")] +use jolt_transcript::ProverState; +#[cfg(any(feature = "host", feature = "guest-verifier"))] +use jolt_transcript::VerifierState; #[cfg(feature = "host")] use jolt_verifier::ProgramPreprocessing as GenericProgramPreprocessing; #[cfg(feature = "host")] @@ -34,14 +48,14 @@ pub use jolt_host as host; any(feature = "host", feature = "guest-verifier"), not(feature = "transcript-blake2b") ))] -pub type ProtocolSponge = jolt_transcript::Keccak; +pub type ProtocolSponge = Keccak; #[cfg(all( any(feature = "host", feature = "guest-verifier"), feature = "transcript-blake2b" ))] -pub type ProtocolSponge = jolt_transcript::Blake2b512; +pub type ProtocolSponge = Blake2b512; #[cfg(feature = "host")] -pub type ProofTranscript = jolt_transcript::ProverState; +pub type ProofTranscript = ProverState; #[cfg(feature = "host")] pub use jolt_program::execution::{ExecutionBackend, TraceError, TraceInputs, TraceOutput}; #[cfg(feature = "host")] @@ -72,7 +86,7 @@ pub type VerifierVC = Pedersen; #[cfg(feature = "host")] pub type VerifierField = jolt_field::Fr; #[cfg(feature = "host")] -pub type VerifierTranscript<'a> = jolt_transcript::VerifierState<'a, ProtocolSponge>; +pub type VerifierTranscript<'a> = VerifierState<'a, ProtocolSponge>; #[cfg(feature = "host")] pub type JoltVerifierPreprocessing = jolt_verifier::JoltVerifierPreprocessing; @@ -157,7 +171,7 @@ pub type VerifierVC = Pedersen; #[cfg(all(feature = "guest-verifier", not(feature = "host")))] pub type VerifierField = jolt_field::Fr; #[cfg(all(feature = "guest-verifier", not(feature = "host")))] -pub type VerifierTranscript<'a> = jolt_transcript::VerifierState<'a, ProtocolSponge>; +pub type VerifierTranscript<'a> = VerifierState<'a, ProtocolSponge>; #[cfg(all(feature = "guest-verifier", not(feature = "host")))] pub type JoltVerifierPreprocessing = jolt_verifier::JoltVerifierPreprocessing; diff --git a/jolt-sdk/tests/verifier_api.rs b/jolt-sdk/tests/verifier_api.rs index 60f34cb477..756742e237 100644 --- a/jolt-sdk/tests/verifier_api.rs +++ b/jolt-sdk/tests/verifier_api.rs @@ -3,6 +3,7 @@ mod tests { use jolt_sdk::{ deserialize_verifier_object, JoltDevice, JoltVerifierPreprocessing, RV64IMACProof, + VerifierTranscript, }; #[test] @@ -40,7 +41,7 @@ mod tests { jolt_sdk::VerifierField, jolt_sdk::VerifierPCS, jolt_sdk::VerifierVC, - jolt_sdk::VerifierTranscript<'_>, + VerifierTranscript<'_>, >(&preprocessing, &device, &proof, None); let duration = start.elapsed(); println!("Verification took: {} ms", duration.as_millis()); diff --git a/zklean-extractor/src/mle_ast.rs b/zklean-extractor/src/mle_ast.rs index 60f8a7ad02..0e7f44dfd5 100644 --- a/zklean-extractor/src/mle_ast.rs +++ b/zklean-extractor/src/mle_ast.rs @@ -10,6 +10,7 @@ use ark_std::{One, Zero}; use num_bigint::BigUint; use rand_core::RngCore; use serde::{Deserialize, Serialize}; +use spongefish::Encoding; #[cfg(test)] use jolt_field::JoltField; @@ -1273,7 +1274,7 @@ impl CanonicalBytes for MleAst { } /// The canonical bytes, so a transcript absorbs the AST as it absorbs `Fr`. -impl spongefish::Encoding for MleAst { +impl Encoding for MleAst { fn encode(&self) -> impl AsRef<[u8]> { let mut bytes = [0u8; 32]; self.to_bytes_le(&mut bytes); From 0bf9cd14783cbf2a121564afebd33c4ca7db919e Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Wed, 21 Oct 2026 02:56:52 -0400 Subject: [PATCH 11/16] chore(transcript): typos and an unused dev-dependency Rename the order test's label to a dictionary word of the same length, and drop jolt-sumcheck's num-traits dev-dependency, whose only user was the deleted mersenne61 compatibility test. --- Cargo.lock | 1 - crates/jolt-sumcheck/Cargo.toml | 1 - crates/jolt-transcript/tests/common/mod.rs | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53a851fc2d..858851ee5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3863,7 +3863,6 @@ dependencies = [ "jolt-poly", "jolt-r1cs", "jolt-transcript", - "num-traits", "rand_core 0.6.4", "serde", "thiserror 2.0.20", diff --git a/crates/jolt-sumcheck/Cargo.toml b/crates/jolt-sumcheck/Cargo.toml index 152c984d23..7ec71951d3 100644 --- a/crates/jolt-sumcheck/Cargo.toml +++ b/crates/jolt-sumcheck/Cargo.toml @@ -47,7 +47,6 @@ jolt-transcript = { workspace = true, features = [ "transcript-keccak", "test-utils", ] } -num-traits = { workspace = true } rand_core = { workspace = true, features = ["getrandom"] } [[test]] diff --git a/crates/jolt-transcript/tests/common/mod.rs b/crates/jolt-transcript/tests/common/mod.rs index a05272301a..11c0c5bf86 100644 --- a/crates/jolt-transcript/tests/common/mod.rs +++ b/crates/jolt-transcript/tests/common/mod.rs @@ -83,11 +83,11 @@ macro_rules! transcript_tests { #[test] fn test_order_matters() { - let c1 = challenge_after(b"ordr", |t| { + let c1 = challenge_after(b"swap", |t| { t.append(&1u64); t.append(&2u64); }); - let c2 = challenge_after(b"ordr", |t| { + let c2 = challenge_after(b"swap", |t| { t.append(&2u64); t.append(&1u64); }); From bfb85f096f64b068445182e60ddd6dd3863cf21b Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Wed, 21 Oct 2026 12:26:34 -0400 Subject: [PATCH 12/16] fix(transcript): keep the prover's grinding out of verifier builds The proof-of-work nonce search copies the sponge state, which spongefish 0.8.0 exposes only under `yolocrypto`. jolt-sumcheck and jolt-blindfold turned `prover-grinding` on in every build, so jolt-verifier compiled the search and `yolocrypto` too. - jolt-sumcheck and jolt-blindfold gain a `prover` feature holding their prove side: the round loop, `grind_round`, the recorders' rounds, the committed builder and BlindFold's `prove`. jolt-prover, jolt-kernels and jolt-spartan-prover enable it; jolt-verifier's graph has neither the search nor `yolocrypto`. - jolt-transcript's default features are Keccak only. Blake2b and Poseidon are opt-in; the fuzz workspaces that use them, or BN254 challenges, now name those features. - CI lints the verifier-only graph, checks jolt-verifier's feature tree, and tests jolt-sumcheck and jolt-blindfold with their prover. --- .github/workflows/rust.yml | 22 ++- crates/jolt-blindfold/Cargo.toml | 35 ++++- crates/jolt-blindfold/src/lib.rs | 2 + crates/jolt-claims/fuzz/Cargo.lock | 132 ++++++++++++++++++ crates/jolt-dory/fuzz/Cargo.lock | 43 +----- crates/jolt-dory/fuzz/Cargo.toml | 5 +- crates/jolt-kernels/Cargo.toml | 2 +- crates/jolt-openings/fuzz/Cargo.lock | 43 +----- crates/jolt-openings/fuzz/Cargo.toml | 5 +- crates/jolt-prover/Cargo.toml | 4 +- crates/jolt-spartan-prover/Cargo.toml | 2 +- crates/jolt-sumcheck/Cargo.toml | 19 ++- crates/jolt-sumcheck/README.md | 23 +-- crates/jolt-sumcheck/fuzz/Cargo.lock | 85 +---------- crates/jolt-sumcheck/fuzz/Cargo.toml | 1 + crates/jolt-sumcheck/src/batch.rs | 8 +- crates/jolt-sumcheck/src/committed.rs | 16 +-- crates/jolt-sumcheck/src/lib.rs | 16 ++- crates/jolt-sumcheck/src/pow.rs | 5 +- crates/jolt-sumcheck/src/recorder.rs | 48 +++++-- .../tests/external-consumer/Cargo.toml | 4 +- crates/jolt-transcript/Cargo.toml | 8 +- crates/jolt-transcript/src/lib.rs | 4 +- crates/jolt-transcript/src/setup.rs | 6 +- crates/jolt-verifier/fuzz/Cargo.lock | 59 ++------ crates/jolt-verifier/fuzz/Cargo.toml | 2 +- jolt-eval/fuzz/Cargo.lock | 1 - 27 files changed, 328 insertions(+), 272 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index dd2eb5eafb..a412b2f056 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -108,6 +108,21 @@ jobs: # --all-features turns on parallel; lint the sequential nonce search too. - name: cargo clippy -p jolt-transcript --features bn254,solinas,binary,prover-grinding run: cargo clippy -p jolt-transcript --all-targets --features bn254,solinas,binary,prover-grinding + # The workspace-wide invocations above unify jolt-prover's `prover` + # features into every crate, so lint the verifier-only graph on its own: + # jolt-sumcheck and jolt-blindfold without `prover`, and jolt-verifier's + # library without the dev-dependency on jolt-prover. + - name: cargo clippy verifier-only sumcheck and BlindFold + run: cargo clippy -p jolt-sumcheck -p jolt-blindfold --all-targets + - name: cargo clippy -p jolt-verifier --lib + run: cargo clippy -p jolt-verifier --lib + - name: Check the verifier graph leaves out the prover's grinding + run: | + if cargo tree -p jolt-verifier -e normal,features --prefix none \ + | grep -E 'feature "(prover|prover-grinding|yolocrypto)"'; then + echo "jolt-verifier enables the prover's proof-of-work search" >&2 + exit 1 + fi # jolt-verifier's prover-fixtures feature gates live prover artifact # generation for verifier tests. It is not enabled by the workspace-wide # invocations above, so build it explicitly in both modes. @@ -795,7 +810,12 @@ jobs: run: cargo nextest run -p jolt-utils --features parallel --cargo-quiet - name: Test jolt-sumcheck with committed proofs if: matrix.shard == 0 - run: cargo nextest run -p jolt-sumcheck --features committed --cargo-quiet + run: cargo nextest run -p jolt-sumcheck --features committed,prover --cargo-quiet + # The shard loop above tests jolt-blindfold without its prover, which + # skips the proof pipelines. + - name: Test jolt-blindfold with its prover + if: matrix.shard == 0 + run: cargo nextest run -p jolt-blindfold --features prover --cargo-quiet - name: Test isolated external sumcheck consumer if: matrix.shard == 0 run: cargo nextest run --manifest-path crates/jolt-sumcheck/tests/external-consumer/Cargo.toml --locked --cargo-quiet diff --git a/crates/jolt-blindfold/Cargo.toml b/crates/jolt-blindfold/Cargo.toml index 49e028e9c6..abee9aa7cb 100644 --- a/crates/jolt-blindfold/Cargo.toml +++ b/crates/jolt-blindfold/Cargo.toml @@ -5,6 +5,15 @@ edition = "2021" license = "MIT" description = "Generic BlindFold verifier infrastructure for the Jolt zkVM" +[features] +# The prove side: `prove` and the proof-of-work search before its challenges, +# which copies the sponge state. Verifier builds leave it off. +prover = [ + "jolt-sumcheck/prover", + "jolt-transcript/prover-grinding", + "dep:rayon", +] + [lints] workspace = true @@ -15,11 +24,10 @@ jolt-field.workspace = true jolt-poly.workspace = true jolt-r1cs.workspace = true jolt-sumcheck = { workspace = true, features = ["committed", "r1cs"] } -# The prover grinds its public proof-of-work nonces on a copy of the sponge. -jolt-transcript = { workspace = true, features = ["prover-grinding"] } +jolt-transcript.workspace = true jolt-utils.workspace = true rand_core.workspace = true -rayon.workspace = true +rayon = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } thiserror.workspace = true @@ -29,7 +37,28 @@ jolt-transcript = { workspace = true, features = [ "bn254", "solinas", "transcript-blake2b", + "prover-grinding", "test-utils", ] } rand_chacha.workspace = true rand_core.workspace = true + +[[test]] +name = "assignment_pipeline" +required-features = ["prover"] + +[[test]] +name = "proof_pipeline" +required-features = ["prover"] + +[[test]] +name = "r1cs_pipeline" +required-features = ["prover"] + +[[test]] +name = "real_prover" +required-features = ["prover"] + +[[test]] +name = "sumcheck_pipeline" +required-features = ["prover"] diff --git a/crates/jolt-blindfold/src/lib.rs b/crates/jolt-blindfold/src/lib.rs index 406fdef6bd..e396e3752c 100644 --- a/crates/jolt-blindfold/src/lib.rs +++ b/crates/jolt-blindfold/src/lib.rs @@ -22,6 +22,7 @@ mod error; mod pow; mod proof; pub mod protocol; +#[cfg(feature = "prover")] mod prove; pub mod r1cs; mod relaxed; @@ -36,6 +37,7 @@ pub use protocol::{ BlindFoldDimensions, BlindFoldProtocol, FinalOpeningWitnessCoordinates, RowDimensions, WitnessCoordinate, WitnessRowLayout, }; +#[cfg(feature = "prover")] pub use prove::{ prove, prove_with_row_committer, BlindFoldRowCommitter, BlindFoldWitness, DirectBlindFoldRowCommitter, diff --git a/crates/jolt-claims/fuzz/Cargo.lock b/crates/jolt-claims/fuzz/Cargo.lock index 4626bd5eb2..ac18314687 100644 --- a/crates/jolt-claims/fuzz/Cargo.lock +++ b/crates/jolt-claims/fuzz/Cargo.lock @@ -173,6 +173,12 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "cc" version = "1.3.0" @@ -197,6 +203,15 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-deque" version = "0.8.7" @@ -358,8 +373,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", + "wasm-bindgen", ] [[package]] @@ -474,6 +491,7 @@ dependencies = [ "num-traits", "rand_core", "serde", + "spongefish", "thiserror", ] @@ -538,7 +556,9 @@ dependencies = [ name = "jolt-transcript" version = "0.1.0" dependencies = [ + "getrandom", "jolt-field", + "spongefish", ] [[package]] @@ -548,6 +568,26 @@ dependencies = [ "num-traits", ] +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" +dependencies = [ + "cfg-if", + "cpufeatures", +] + [[package]] name = "libc" version = "0.2.189" @@ -698,6 +738,12 @@ dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "semver" version = "1.0.28" @@ -734,12 +780,42 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest 0.11.3", + "keccak", + "sponge-cursor", +] + [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + +[[package]] +name = "spongefish" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62560b5029264a655fc461a0ecadc4d24862799190897d3225b3272b7a3f13b7" +dependencies = [ + "digest 0.11.3", + "getrandom", + "shake", + "turboshake", + "zeroize", +] + [[package]] name = "strum" version = "0.28.0" @@ -831,6 +907,17 @@ version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +[[package]] +name = "turboshake" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f892c6904b0bd5a9241eac848347abcbbbd2b9f3892bad23ac3e6efab8d6f06a" +dependencies = [ + "digest 0.11.3", + "keccak", + "sponge-cursor", +] + [[package]] name = "typenum" version = "1.20.1" @@ -849,6 +936,51 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.3", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + [[package]] name = "zerocopy" version = "0.8.55" diff --git a/crates/jolt-dory/fuzz/Cargo.lock b/crates/jolt-dory/fuzz/Cargo.lock index 1278634d2f..86699c6bb3 100644 --- a/crates/jolt-dory/fuzz/Cargo.lock +++ b/crates/jolt-dory/fuzz/Cargo.lock @@ -399,7 +399,7 @@ dependencies = [ "rand_core", "rayon", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -598,7 +598,7 @@ dependencies = [ "rand_core", "serde", "spongefish", - "thiserror 2.0.19", + "thiserror", ] [[package]] @@ -610,7 +610,7 @@ dependencies = [ "jolt-poly", "jolt-transcript", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -643,7 +643,7 @@ dependencies = [ "rand_core", "rayon", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -656,7 +656,6 @@ dependencies = [ "blake2 0.11.0-rc.6", "getrandom 0.4.3", "jolt-field", - "light-poseidon", "spongefish", ] @@ -703,18 +702,6 @@ dependencies = [ "cc", ] -[[package]] -name = "light-poseidon" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a1ccadd0bb5a32c196da536fd72c59183de24a055f6bf0513bf845fefab862" -dependencies = [ - "ark-bn254", - "ark-ff", - "num-bigint", - "thiserror 1.0.69", -] - [[package]] name = "link-section" version = "0.19.1" @@ -985,33 +972,13 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.19", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror-impl", ] [[package]] diff --git a/crates/jolt-dory/fuzz/Cargo.toml b/crates/jolt-dory/fuzz/Cargo.toml index 6c9466a487..5655b271f0 100644 --- a/crates/jolt-dory/fuzz/Cargo.toml +++ b/crates/jolt-dory/fuzz/Cargo.toml @@ -44,7 +44,10 @@ jolt-dory = { path = ".." } jolt-field = { path = "../../jolt-field" } jolt-openings = { path = "../../jolt-openings" } jolt-poly = { path = "../../jolt-poly" } -jolt-transcript = { path = "../../jolt-transcript" } +jolt-transcript = { path = "../../jolt-transcript", features = [ + "bn254", + "transcript-blake2b", +] } serde_json = "1" bincode = { version = "2", features = ["serde"] } rand_core = "0.6" diff --git a/crates/jolt-kernels/Cargo.toml b/crates/jolt-kernels/Cargo.toml index 3f8efee558..cb36891c8a 100644 --- a/crates/jolt-kernels/Cargo.toml +++ b/crates/jolt-kernels/Cargo.toml @@ -53,7 +53,7 @@ jolt-poly = { workspace = true, default-features = false } jolt-program.workspace = true jolt-riscv.workspace = true jolt-r1cs.workspace = true -jolt-sumcheck.workspace = true +jolt-sumcheck = { workspace = true, features = ["prover"] } jolt-utils.workspace = true jolt-verifier.workspace = true jolt-witness.workspace = true diff --git a/crates/jolt-openings/fuzz/Cargo.lock b/crates/jolt-openings/fuzz/Cargo.lock index 9ba7cb3d6c..e8b5626822 100644 --- a/crates/jolt-openings/fuzz/Cargo.lock +++ b/crates/jolt-openings/fuzz/Cargo.lock @@ -379,7 +379,7 @@ dependencies = [ "rand_core", "rayon", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -555,7 +555,7 @@ dependencies = [ "rand_core", "serde", "spongefish", - "thiserror 2.0.19", + "thiserror", ] [[package]] @@ -567,7 +567,7 @@ dependencies = [ "jolt-poly", "jolt-transcript", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -612,7 +612,7 @@ dependencies = [ "rand_core", "rayon", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -625,7 +625,6 @@ dependencies = [ "blake2 0.11.0-rc.6", "getrandom 0.4.3", "jolt-field", - "light-poseidon", "spongefish", ] @@ -672,18 +671,6 @@ dependencies = [ "cc", ] -[[package]] -name = "light-poseidon" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a1ccadd0bb5a32c196da536fd72c59183de24a055f6bf0513bf845fefab862" -dependencies = [ - "ark-bn254", - "ark-ff", - "num-bigint", - "thiserror 1.0.69", -] - [[package]] name = "link-section" version = "0.19.1" @@ -935,33 +922,13 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.19", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror-impl", ] [[package]] diff --git a/crates/jolt-openings/fuzz/Cargo.toml b/crates/jolt-openings/fuzz/Cargo.toml index 44f8184ed3..d7107abf29 100644 --- a/crates/jolt-openings/fuzz/Cargo.toml +++ b/crates/jolt-openings/fuzz/Cargo.toml @@ -29,7 +29,10 @@ jolt-dory = { path = "../../jolt-dory" } jolt-field = { path = "../../jolt-field", features = ["bn254"] } jolt-openings = { path = ".." } jolt-poly = { path = "../../jolt-poly" } -jolt-transcript = { path = "../../jolt-transcript" } +jolt-transcript = { path = "../../jolt-transcript", features = [ + "bn254", + "transcript-blake2b", +] } [[bin]] name = "dory_homomorphic_batch_must_reject" diff --git a/crates/jolt-prover/Cargo.toml b/crates/jolt-prover/Cargo.toml index 73b8dab11f..0ee8102650 100644 --- a/crates/jolt-prover/Cargo.toml +++ b/crates/jolt-prover/Cargo.toml @@ -101,7 +101,7 @@ allocative = [ common.workspace = true jolt-akita = { workspace = true, optional = true } akita-params = { workspace = true, optional = true } -jolt-blindfold = { workspace = true, optional = true } +jolt-blindfold = { workspace = true, optional = true, features = ["prover"] } jolt-claims.workspace = true jolt-crypto.workspace = true jolt-dory.workspace = true @@ -112,7 +112,7 @@ jolt-openings.workspace = true jolt-poly = { workspace = true, default-features = false } jolt-program.workspace = true jolt-riscv.workspace = true -jolt-sumcheck.workspace = true +jolt-sumcheck = { workspace = true, features = ["prover"] } jolt-transcript = { workspace = true, features = [ "bn254", "prover-grinding", diff --git a/crates/jolt-spartan-prover/Cargo.toml b/crates/jolt-spartan-prover/Cargo.toml index d70f44503e..c997228715 100644 --- a/crates/jolt-spartan-prover/Cargo.toml +++ b/crates/jolt-spartan-prover/Cargo.toml @@ -14,7 +14,7 @@ jolt-openings.workspace = true jolt-poly.workspace = true jolt-r1cs = { path = "../jolt-r1cs", default-features = false } jolt-spartan-verifier.workspace = true -jolt-sumcheck.workspace = true +jolt-sumcheck = { workspace = true, features = ["prover"] } jolt-transcript.workspace = true [dev-dependencies] diff --git a/crates/jolt-sumcheck/Cargo.toml b/crates/jolt-sumcheck/Cargo.toml index 7ec71951d3..b8560a3558 100644 --- a/crates/jolt-sumcheck/Cargo.toml +++ b/crates/jolt-sumcheck/Cargo.toml @@ -16,6 +16,10 @@ transcript-blake2b = ["jolt-transcript/transcript-blake2b"] transcript-keccak = ["jolt-transcript/transcript-keccak"] transcript-poseidon = ["jolt-transcript/transcript-poseidon"] committed = ["dep:jolt-crypto", "dep:rand_core"] +# The prove side: the round loop, the recorders' rounds and the per-round +# proof-of-work search, which copies the sponge state. Verifier builds leave it +# off. +prover = ["jolt-transcript/prover-grinding"] r1cs = ["dep:jolt-r1cs"] [lints] @@ -26,10 +30,7 @@ jolt-crypto = { path = "../jolt-crypto", default-features = false, optional = tr jolt-field = { path = "../jolt-field", default-features = false } jolt-poly = { path = "../jolt-poly", default-features = false } jolt-r1cs = { path = "../jolt-r1cs", default-features = false, optional = true } -# The prover's per-round nonce search copies the sponge state. -jolt-transcript = { path = "../jolt-transcript", default-features = false, features = [ - "prover-grinding", -] } +jolt-transcript = { path = "../jolt-transcript", default-features = false } rand_core = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } tracing.workspace = true @@ -45,6 +46,7 @@ jolt-transcript = { workspace = true, features = [ "binary", "transcript-blake2b", "transcript-keccak", + "prover-grinding", "test-utils", ] } rand_core = { workspace = true, features = ["getrandom"] } @@ -53,5 +55,14 @@ rand_core = { workspace = true, features = ["getrandom"] } name = "committed" required-features = ["committed"] +[[test]] +name = "binary_fields" +required-features = ["prover"] + +[[test]] +name = "extension_fields" +required-features = ["prover"] + [[example]] name = "clear_sumcheck" +required-features = ["prover"] diff --git a/crates/jolt-sumcheck/README.md b/crates/jolt-sumcheck/README.md index e6811807ef..ccb89bf9c4 100644 --- a/crates/jolt-sumcheck/README.md +++ b/crates/jolt-sumcheck/README.md @@ -7,9 +7,12 @@ pool. Its default feature set is empty. ## Features - `parallel`: parallel polynomial operations. -- `transcript-blake2b`, `transcript-keccak`, `transcript-poseidon`: stock - transcript implementations. Poseidon selects BN254 because that sponge is - field-specific. +- `prover`: the prove side — the batched round loop, the recorders' rounds + and the per-round proof-of-work search. The search copies the sponge state, + which needs spongefish's `yolocrypto` feature, so verifier builds leave + `prover` off. +- `transcript-blake2b`, `transcript-keccak`: the sponges a transcript can draw + field challenges from. Keccak is jolt-transcript's default. - `committed`: commitment-backed round proving. It exposes the generic `VectorCommitment` boundary without selecting a curve backend. - `r1cs`: lowering of sumcheck verifier equations into the dependency-light @@ -30,13 +33,11 @@ against its polynomial oracle or commitment. This includes zero-variable claims. Run the minimal example with: ```bash -cargo run -p jolt-sumcheck --example clear_custom_transcript --no-default-features +cargo run -p jolt-sumcheck --example clear_sumcheck --features prover ``` -The example transcript tracks every absorb but returns fixed challenges, which -makes the Fiat–Shamir schedule and algebraic API easy to inspect. It is not a -production transcript; applications must use a cryptographic transcript with -adequate challenge entropy. +The example proves and verifies a one-round clear sumcheck over a Keccak +transcript. The isolated fixture runs outside Cargo's Jolt workspace feature unification: @@ -56,13 +57,13 @@ An external project should pin all Jolt packages to the same full Git revision: ```toml [dependencies] -jolt-sumcheck = { git = "https://github.com/a16z/jolt", rev = "FULL_COMMIT_SHA", default-features = false } +jolt-sumcheck = { git = "https://github.com/a16z/jolt", rev = "FULL_COMMIT_SHA", default-features = false, features = ["prover"] } jolt-field = { git = "https://github.com/a16z/jolt", rev = "FULL_COMMIT_SHA", default-features = false, features = ["solinas"] } jolt-poly = { git = "https://github.com/a16z/jolt", rev = "FULL_COMMIT_SHA", default-features = false } -jolt-transcript = { git = "https://github.com/a16z/jolt", rev = "FULL_COMMIT_SHA", default-features = false } +jolt-transcript = { git = "https://github.com/a16z/jolt", rev = "FULL_COMMIT_SHA", default-features = false, features = ["solinas", "transcript-keccak"] } ``` Cargo only honors `[patch]` tables in the consuming workspace root. The minimal -configuration above does not require Jolt's Arkworks patches. BN254 and Poseidon +configuration above does not require Jolt's Arkworks patches. BN254 consumers must copy the exact Arkworks patch/source requirements documented by the pinned Jolt revision into their root manifest. diff --git a/crates/jolt-sumcheck/fuzz/Cargo.lock b/crates/jolt-sumcheck/fuzz/Cargo.lock index 30572c15e1..79f36b73e4 100644 --- a/crates/jolt-sumcheck/fuzz/Cargo.lock +++ b/crates/jolt-sumcheck/fuzz/Cargo.lock @@ -14,27 +14,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "allocative" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cf9afc79c83d514444b55df3935d317da54b1ce3b17a133c646889cc260de8" -dependencies = [ - "allocative_derive", - "ctor", -] - -[[package]] -name = "allocative_derive" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614043c56c1173b800acb007b81fd0cbc0a0d7d717b71ba705fc2230d0760a23" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -83,7 +62,6 @@ name = "ark-ff" version = "0.5.0" source = "git+https://github.com/a16z/arkworks-algebra?branch=dev%2Ftwist-shout#b8f0f5129501c42c8a934f4832746d335c8f8ca1" dependencies = [ - "allocative", "ark-ff-asm", "ark-ff-macros", "ark-serialize", @@ -278,16 +256,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "ctor" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e30e509674ef0ec91e21a7735766db37d163d46151b6a361d8b83dd79116bd" -dependencies = [ - "link-section", - "linktime-proc-macro", -] - [[package]] name = "ctutils" version = "0.4.2" @@ -439,7 +407,7 @@ dependencies = [ "rand_core", "serde", "spongefish", - "thiserror 2.0.19", + "thiserror", ] [[package]] @@ -452,7 +420,7 @@ dependencies = [ "rand_core", "rayon", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -464,7 +432,7 @@ dependencies = [ "jolt-poly", "jolt-transcript", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -489,7 +457,6 @@ dependencies = [ "blake2", "getrandom", "jolt-field", - "light-poseidon", "spongefish", ] @@ -536,30 +503,6 @@ dependencies = [ "cc", ] -[[package]] -name = "light-poseidon" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a1ccadd0bb5a32c196da536fd72c59183de24a055f6bf0513bf845fefab862" -dependencies = [ - "ark-bn254", - "ark-ff", - "num-bigint", - "thiserror 1.0.69", -] - -[[package]] -name = "link-section" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc98458dfe90986c5e2f6ddcf68360c7e5c4252600153e06aa4ee8176c0f8d1" - -[[package]] -name = "linktime-proc-macro" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7b0a3383c2a1002d11349c92c85a666a5fb679e96c79d782cf0dbe557fd6ee" - [[package]] name = "num-bigint" version = "0.4.8" @@ -780,33 +723,13 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.19", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror-impl", ] [[package]] diff --git a/crates/jolt-sumcheck/fuzz/Cargo.toml b/crates/jolt-sumcheck/fuzz/Cargo.toml index 22e2af52d8..5ecb836333 100644 --- a/crates/jolt-sumcheck/fuzz/Cargo.toml +++ b/crates/jolt-sumcheck/fuzz/Cargo.toml @@ -52,6 +52,7 @@ jolt-sumcheck = { path = ".." } jolt-transcript = { path = "../../jolt-transcript", features = [ "bn254", "transcript-blake2b", + "prover-grinding", ] } [[bin]] diff --git a/crates/jolt-sumcheck/src/batch.rs b/crates/jolt-sumcheck/src/batch.rs index 5b6628217c..70f4efe217 100644 --- a/crates/jolt-sumcheck/src/batch.rs +++ b/crates/jolt-sumcheck/src/batch.rs @@ -163,7 +163,13 @@ impl BatchPrelude { } pub(crate) enum PaddingRule { - ConstantExtension { two_inv: F }, + ConstantExtension { + #[cfg_attr( + not(feature = "prover"), + expect(dead_code, reason = "only the prover halves inactive rounds") + )] + two_inv: F, + }, ZeroExtension, } diff --git a/crates/jolt-sumcheck/src/committed.rs b/crates/jolt-sumcheck/src/committed.rs index 48bdced2ac..ad73cfa7fa 100644 --- a/crates/jolt-sumcheck/src/committed.rs +++ b/crates/jolt-sumcheck/src/committed.rs @@ -1,20 +1,20 @@ #[cfg(feature = "committed")] use jolt_crypto::VectorCommitment; use jolt_field::{Field, JoltField}; -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] use jolt_poly::UnivariatePoly; use jolt_transcript::{Encoding, LabelWithCount, Transcript}; -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] use jolt_transcript::{FieldTranscript, ProverTranscript}; -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] use rand_core::RngCore; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::error::SumcheckError; -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] use crate::pow::grind_round; use crate::pow::{Nonced, NoncedRef}; -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] use crate::proof::SumcheckProof; use crate::round_proof::RoundMessage; @@ -224,7 +224,7 @@ pub struct CommittedSumcheckWitness { } impl CommittedSumcheckWitness { - #[cfg(feature = "committed")] + #[cfg(all(feature = "committed", feature = "prover"))] fn new() -> Self { Self { round_coefficients: Vec::new(), @@ -241,7 +241,7 @@ impl CommittedSumcheckWitness { /// flattened output-claim values and absorb those commitments. Blindings are /// drawn from the caller-supplied `rng` and retained in the witness — the /// caller owns the randomness source, so a fixed seed reproduces the proof. -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] pub struct CommittedSumcheckBuilder<'a, F, VC, R> where F: JoltField, @@ -255,7 +255,7 @@ where witness: CommittedSumcheckWitness, } -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] impl<'a, F, VC, R> CommittedSumcheckBuilder<'a, F, VC, R> where F: JoltField, diff --git a/crates/jolt-sumcheck/src/lib.rs b/crates/jolt-sumcheck/src/lib.rs index fa400472e7..8cf443eda7 100644 --- a/crates/jolt-sumcheck/src/lib.rs +++ b/crates/jolt-sumcheck/src/lib.rs @@ -109,6 +109,7 @@ pub mod domain; pub mod error; mod pow; pub mod proof; +#[cfg(feature = "prover")] pub mod prover; #[cfg(feature = "r1cs")] pub mod r1cs; @@ -116,9 +117,9 @@ pub mod recorder; pub mod round_proof; pub mod verifier; -#[cfg(all(test, feature = "committed"))] +#[cfg(all(test, feature = "committed", feature = "prover"))] mod round_scheduler_tests; -#[cfg(all(test, feature = "committed"))] +#[cfg(all(test, feature = "committed", feature = "prover"))] mod tests; use jolt_transcript::Encoding; @@ -143,7 +144,7 @@ where pub use batch::{BatchMember, BatchPrelude}; pub use claim::{EvaluationClaim, SumcheckClaim, SumcheckStatement}; -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] pub use committed::CommittedSumcheckBuilder; pub use committed::{ BatchedCommittedSumcheckConsistency, CommittedOutputClaims, CommittedRound, @@ -152,10 +153,13 @@ pub use committed::{ }; pub use domain::{BooleanHypercube, CenteredIntegerDomain, SumcheckDomain, SumcheckDomainSpec}; pub use error::SumcheckError; -pub use pow::{grind_round, round_pow_bits, Nonced, NoncedRef}; +#[cfg(feature = "prover")] +pub use pow::grind_round; +pub use pow::{round_pow_bits, Nonced, NoncedRef}; pub use proof::{ClearProof, ClearSumcheckProof, CompressedSumcheckProof, SumcheckProof}; -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] pub use prover::prove_uniskip_committed; +#[cfg(feature = "prover")] pub use prover::{ prove_batch, prove_uniskip_clear, MemberFinish, MemberRound, ProveRounds, ProvedBatch, ProvedUniskip, ProvedUniskipCommitted, RoundScheduler, SequentialRounds, @@ -166,7 +170,7 @@ pub use r1cs::{ append_sumcheck_r1cs_constraints_for_domain, SumcheckR1csError, SumcheckR1csLayout, SumcheckR1csRound, SumcheckR1csRoundLayout, }; -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] pub use recorder::CommittedSumcheckRecorder; pub use recorder::{ClearSumcheckRecorder, RecordedSumcheck, SumcheckRecorder}; pub use round_proof::{ClearRound, CompressedLabeledRoundPoly, LabeledRoundPoly, RoundMessage}; diff --git a/crates/jolt-sumcheck/src/pow.rs b/crates/jolt-sumcheck/src/pow.rs index dafc478523..887d6c1538 100644 --- a/crates/jolt-sumcheck/src/pow.rs +++ b/crates/jolt-sumcheck/src/pow.rs @@ -19,7 +19,9 @@ use std::fmt::{Formatter, Result as FmtResult}; use std::marker::PhantomData; use jolt_field::Field; -use jolt_transcript::{grind_bits_for_loss, FieldTranscript, ProverTranscript, SECURITY_BITS}; +#[cfg(feature = "prover")] +use jolt_transcript::ProverTranscript; +use jolt_transcript::{grind_bits_for_loss, FieldTranscript, SECURITY_BITS}; use serde::de::{Error as _, SeqAccess, Visitor}; use serde::ser::{Error as _, SerializeTuple}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -81,6 +83,7 @@ impl<'a> RoundNonces<'a> { /// Grinds round `round`'s nonce after the round message is absorbed and before /// the round challenge is drawn, appending it to `nonces` when the difficulty /// is positive. +#[cfg(feature = "prover")] pub fn grind_round + ProverTranscript>( degree: usize, round: usize, diff --git a/crates/jolt-sumcheck/src/recorder.rs b/crates/jolt-sumcheck/src/recorder.rs index 8688911b64..652409c2c5 100644 --- a/crates/jolt-sumcheck/src/recorder.rs +++ b/crates/jolt-sumcheck/src/recorder.rs @@ -10,31 +10,41 @@ //! //! The generated `begin_batch` drivers (`#[derive(SumcheckBatch)]` in //! `jolt-verifier`) call [`absorb_input_claims`](SumcheckRecorder::absorb_input_claims); -//! the prove-side round loop calls [`absorb_round`](SumcheckRecorder::absorb_round) -//! per round and [`finish`](SumcheckRecorder::finish) once. The clear verifier -//! also runs `begin_batch` (with [`ClearSumcheckRecorder`]) so the two sides -//! share the head's Fiat-Shamir sequence structurally. +//! the prove-side round loop calls `absorb_round` per round and `finish` once. +//! The clear verifier also runs `begin_batch` (with [`ClearSumcheckRecorder`]) +//! so the two sides share the head's Fiat-Shamir sequence structurally. +//! Without the `prover` feature only that head remains: `absorb_round`, +//! `finish` and the committed recorder belong to the prover. -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] use jolt_crypto::VectorCommitment; use jolt_field::Field; -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] use jolt_field::JoltField; -use jolt_poly::{CompressedPoly, UnivariatePoly}; -use jolt_transcript::{Encoding, FieldTranscript, ProverTranscript, Transcript}; -#[cfg(feature = "committed")] +use jolt_poly::CompressedPoly; +#[cfg(feature = "prover")] +use jolt_poly::UnivariatePoly; +use jolt_transcript::{Encoding, Transcript}; +#[cfg(feature = "prover")] +use jolt_transcript::{FieldTranscript, ProverTranscript}; +#[cfg(all(feature = "committed", feature = "prover"))] use rand_core::RngCore; -#[cfg(feature = "committed")] +use crate::append_sumcheck_claim; +#[cfg(all(feature = "committed", feature = "prover"))] use crate::committed::CommittedSumcheckBuilder; use crate::committed::CommittedSumcheckWitness; +#[cfg(feature = "prover")] use crate::error::SumcheckError; +#[cfg(feature = "prover")] use crate::pow::grind_round; use crate::proof::CompressedSumcheckProof; -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] use crate::proof::SumcheckProof; +#[cfg(feature = "prover")] use crate::round_proof::{CompressedLabeledRoundPoly, RoundMessage}; -use crate::{append_sumcheck_claim, OPENING_CLAIM_TRANSCRIPT_LABEL}; +#[cfg(feature = "prover")] +use crate::OPENING_CLAIM_TRANSCRIPT_LABEL; /// Records one sumcheck's proof material, abstracting over clear vs. committed /// (ZK) recording: `absorb_input_claims` once (from `begin_batch`), @@ -56,6 +66,7 @@ pub trait SumcheckRecorder { /// verifier prices the round at), and squeeze the round challenge. Clear: /// appended compressed under `b"sumcheck_poly"`. Committed: Pedersen /// commitment appended. + #[cfg(feature = "prover")] fn absorb_round( &mut self, round_poly: &UnivariatePoly, @@ -69,6 +80,7 @@ pub trait SumcheckRecorder { /// assemble the proof. Clear: each value appended under /// `b"opening_claim"`. Committed: values are row-committed and only the /// commitments are absorbed. + #[cfg(feature = "prover")] fn finish( self, output_claim_values: &[F], @@ -91,6 +103,10 @@ pub struct RecordedSumcheck { /// transcript in the clear and collects the rounds into a /// [`CompressedSumcheckProof`]. Its transcript writes are byte-identical to /// what the clear verifier reads back. +#[cfg_attr( + not(feature = "prover"), + expect(dead_code, reason = "a verifier build absorbs only the input claims") +)] pub struct ClearSumcheckRecorder { round_polynomials: Vec>, pow_nonces: Vec, @@ -120,6 +136,7 @@ impl SumcheckRecorder for ClearSumcheckRecorder { } } + #[cfg(feature = "prover")] fn absorb_round( &mut self, round_poly: &UnivariatePoly, @@ -141,6 +158,7 @@ impl SumcheckRecorder for ClearSumcheckRecorder { Ok(challenge) } + #[cfg(feature = "prover")] fn finish( self, output_claim_values: &[F], @@ -168,7 +186,7 @@ impl SumcheckRecorder for ClearSumcheckRecorder { /// claims' commitments were already absorbed by the stage that produced them. /// The retained witness (coefficients, rows, blindings) is returned by /// [`finish`](SumcheckRecorder::finish) for BlindFold. -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] pub struct CommittedSumcheckRecorder<'a, F, VC, R> where F: JoltField, @@ -178,7 +196,7 @@ where builder: CommittedSumcheckBuilder<'a, F, VC, R>, } -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] impl<'a, F, VC, R> CommittedSumcheckRecorder<'a, F, VC, R> where F: JoltField, @@ -192,7 +210,7 @@ where } } -#[cfg(feature = "committed")] +#[cfg(all(feature = "committed", feature = "prover"))] impl SumcheckRecorder for CommittedSumcheckRecorder<'_, F, VC, R> where F: JoltField, diff --git a/crates/jolt-sumcheck/tests/external-consumer/Cargo.toml b/crates/jolt-sumcheck/tests/external-consumer/Cargo.toml index 5d092b2d68..a0414fc998 100644 --- a/crates/jolt-sumcheck/tests/external-consumer/Cargo.toml +++ b/crates/jolt-sumcheck/tests/external-consumer/Cargo.toml @@ -15,7 +15,9 @@ jolt-field = { path = "../../../jolt-field", default-features = false, features "solinas", ] } jolt-poly = { path = "../../../jolt-poly", default-features = false } -jolt-sumcheck = { path = "../..", default-features = false } +jolt-sumcheck = { path = "../..", default-features = false, features = [ + "prover", +] } jolt-transcript = { path = "../../../jolt-transcript", default-features = false, features = [ "solinas", "transcript-keccak", diff --git a/crates/jolt-transcript/Cargo.toml b/crates/jolt-transcript/Cargo.toml index 4c771a6857..e29dc5c363 100644 --- a/crates/jolt-transcript/Cargo.toml +++ b/crates/jolt-transcript/Cargo.toml @@ -16,11 +16,11 @@ ignored = ["getrandom_v04"] workspace = true [features] -default = ["transcript-blake2b", "transcript-keccak", "transcript-poseidon"] +# Keccak is the stock sponge; Blake2b and Poseidon are opt-in. +default = ["transcript-keccak"] # The prover's proof-of-work nonce search. It copies the sponge state, which -# spongefish 0.8.0 exposes only under `yolocrypto`. Crates that hold both sides -# enable it in verifier builds too; `yolocrypto` changes visibility only, not -# the bytes absorbed or squeezed. +# spongefish 0.8.0 exposes only under `yolocrypto`, so verifier builds leave it +# off. `yolocrypto` changes visibility only, not the bytes absorbed or squeezed. prover-grinding = ["spongefish/yolocrypto"] # Searches proof-of-work nonces on all threads; the nonce found is the same. parallel = ["prover-grinding", "dep:rayon"] diff --git a/crates/jolt-transcript/src/lib.rs b/crates/jolt-transcript/src/lib.rs index 8f6dc8f675..0c6a03808c 100644 --- a/crates/jolt-transcript/src/lib.rs +++ b/crates/jolt-transcript/src/lib.rs @@ -8,8 +8,8 @@ //! [`prover_transcript`] and [`verifier_transcript`] bind the session and the //! public statement before the first message. //! -//! Three sponges are feature-gated: `transcript-blake2b` (spongefish -//! `Blake2b512`), `transcript-keccak` (spongefish `Keccak`) and +//! Three sponges are feature-gated: `transcript-keccak` (spongefish `Keccak`, +//! the default), `transcript-blake2b` (spongefish `Blake2b512`) and //! `transcript-poseidon` (a Circom-compatible BN254 [`PoseidonSponge`]). //! Field challenges need uniform squeezed bytes ([`UniformBytes`]), which //! Poseidon does not give, so a Poseidon transcript cannot draw them. diff --git a/crates/jolt-transcript/src/setup.rs b/crates/jolt-transcript/src/setup.rs index 5bc77eeecf..d9b9ddc440 100644 --- a/crates/jolt-transcript/src/setup.rs +++ b/crates/jolt-transcript/src/setup.rs @@ -62,18 +62,18 @@ impl Encoding for Instance<'_, S> { /// /// ``` /// use jolt_transcript::{ -/// prover_transcript, verifier_transcript, Blake2b512, BytesMsg, ProverTranscript, +/// prover_transcript, verifier_transcript, BytesMsg, Keccak, ProverTranscript, /// VerifierTranscript, /// }; /// /// const SESSION: &[u8] = b"jolt-rv64imac/v1"; /// let statement: [u8; 32] = [0xAB; 32]; /// -/// let mut prover = prover_transcript::(SESSION, &statement); +/// let mut prover = prover_transcript::(SESSION, &statement); /// prover.prover_message(&BytesMsg(b"commitment-a".to_vec())); /// let narg = prover.narg_string().to_vec(); /// -/// let mut verifier = verifier_transcript::(SESSION, &statement, &narg); +/// let mut verifier = verifier_transcript::(SESSION, &statement, &narg); /// let _: BytesMsg = verifier.prover_message().unwrap(); /// verifier.check_eof().unwrap(); /// ``` diff --git a/crates/jolt-verifier/fuzz/Cargo.lock b/crates/jolt-verifier/fuzz/Cargo.lock index 0136e814bf..d792dd9f4b 100644 --- a/crates/jolt-verifier/fuzz/Cargo.lock +++ b/crates/jolt-verifier/fuzz/Cargo.lock @@ -430,7 +430,7 @@ dependencies = [ "rand_core", "rayon", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -574,9 +574,8 @@ dependencies = [ "jolt-transcript", "jolt-utils", "rand_core", - "rayon", "serde", - "thiserror 2.0.19", + "thiserror", ] [[package]] @@ -595,7 +594,7 @@ dependencies = [ "jolt-utils", "rayon", "serde", - "thiserror 2.0.19", + "thiserror", ] [[package]] @@ -658,7 +657,7 @@ dependencies = [ "rand_core", "serde", "spongefish", - "thiserror 2.0.19", + "thiserror", ] [[package]] @@ -680,7 +679,7 @@ dependencies = [ "jolt-poly", "jolt-transcript", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -713,7 +712,7 @@ dependencies = [ "rand_core", "rayon", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -725,7 +724,7 @@ dependencies = [ "common", "jolt-riscv", "serde", - "thiserror 2.0.19", + "thiserror", ] [[package]] @@ -734,7 +733,7 @@ version = "0.1.0" dependencies = [ "jolt-field", "serde", - "thiserror 2.0.19", + "thiserror", ] [[package]] @@ -744,7 +743,7 @@ dependencies = [ "ark-serialize", "serde", "strum", - "thiserror 2.0.19", + "thiserror", ] [[package]] @@ -758,7 +757,7 @@ dependencies = [ "jolt-transcript", "rand_core", "serde", - "thiserror 2.0.19", + "thiserror", "tracing", ] @@ -768,10 +767,8 @@ version = "0.1.0" dependencies = [ "ark-bn254", "ark-ff", - "blake2 0.11.0-rc.6", "getrandom 0.4.3", "jolt-field", - "light-poseidon", "spongefish", ] @@ -805,7 +802,7 @@ dependencies = [ "jolt-verifier-derive", "num-traits", "serde", - "thiserror 2.0.19", + "thiserror", ] [[package]] @@ -870,18 +867,6 @@ dependencies = [ "cc", ] -[[package]] -name = "light-poseidon" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a1ccadd0bb5a32c196da536fd72c59183de24a055f6bf0513bf845fefab862" -dependencies = [ - "ark-bn254", - "ark-ff", - "num-bigint", - "thiserror 1.0.69", -] - [[package]] name = "link-section" version = "0.19.1" @@ -1169,33 +1154,13 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.19", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror-impl", ] [[package]] diff --git a/crates/jolt-verifier/fuzz/Cargo.toml b/crates/jolt-verifier/fuzz/Cargo.toml index d866c35201..63c7bb9ace 100644 --- a/crates/jolt-verifier/fuzz/Cargo.toml +++ b/crates/jolt-verifier/fuzz/Cargo.toml @@ -60,7 +60,7 @@ jolt-dory = { path = "../../jolt-dory" } jolt-field = { path = "../../jolt-field", features = ["bn254"] } jolt-poly = { path = "../../jolt-poly" } jolt-sumcheck = { path = "../../jolt-sumcheck" } -jolt-transcript = { path = "../../jolt-transcript" } +jolt-transcript = { path = "../../jolt-transcript", features = ["bn254"] } common = { path = "../../../common" } bincode = { version = "2", features = ["serde"] } diff --git a/jolt-eval/fuzz/Cargo.lock b/jolt-eval/fuzz/Cargo.lock index 415bad5ada..04a1b9393e 100644 --- a/jolt-eval/fuzz/Cargo.lock +++ b/jolt-eval/fuzz/Cargo.lock @@ -1136,7 +1136,6 @@ dependencies = [ "jolt-transcript", "jolt-utils", "rand_core", - "rayon", "serde", "thiserror 2.0.18", ] From d04df1bb53fff0f9a3bfb530aab3c249be5d89a9 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Wed, 21 Oct 2026 13:32:05 -0400 Subject: [PATCH 13/16] test(jolt-verifier): re-bless the challenge census for the single stage-6b draw site Stage 6b now draws through J6's one `sequence`, so its three further `challenge` sites fold into one. Also rustfmt the stage-6b draw test. --- crates/jolt-verifier/src/stages/stage6b/batch.rs | 7 +++---- .../tests/fs_inventory/challenge-sites.inventory | 3 --- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/jolt-verifier/src/stages/stage6b/batch.rs b/crates/jolt-verifier/src/stages/stage6b/batch.rs index 20cd8692b8..65dce809f2 100644 --- a/crates/jolt-verifier/src/stages/stage6b/batch.rs +++ b/crates/jolt-verifier/src/stages/stage6b/batch.rs @@ -649,10 +649,9 @@ mod tests { .map(|_| t.challenge().unwrap()) .collect::>() }); - let (draw_events, draws) = - record(|t| { - Stage6bDraws::::draw(t, &formula_dimensions, committed_bytecode).unwrap() - }); + let (draw_events, draws) = record(|t| { + Stage6bDraws::::draw(t, &formula_dimensions, committed_bytecode).unwrap() + }); assert_eq!(draw_events, inline_events); assert_eq!( diff --git a/crates/jolt-verifier/tests/fs_inventory/challenge-sites.inventory b/crates/jolt-verifier/tests/fs_inventory/challenge-sites.inventory index a9dc9c5d6e..9f37db8ccf 100644 --- a/crates/jolt-verifier/tests/fs_inventory/challenge-sites.inventory +++ b/crates/jolt-verifier/tests/fs_inventory/challenge-sites.inventory @@ -40,9 +40,6 @@ jolt-verifier::stages/stage6a/booleanity.rs::BooleanityAddressPhase::draw_cha jolt-verifier::stages/stage6a/booleanity.rs::BooleanityAddressPhase::draw_challenges::challenge_vector#0 jolt-verifier::stages/stage6a/outputs.rs::Stage6aSumchecks::::batching_coefficient[*] jolt-verifier::stages/stage6b/batch.rs::Stage6bDraws::draw::challenge#0 -jolt-verifier::stages/stage6b/batch.rs::Stage6bDraws::draw::challenge#1 -jolt-verifier::stages/stage6b/batch.rs::Stage6bDraws::draw::challenge#2 -jolt-verifier::stages/stage6b/batch.rs::Stage6bDraws::draw::challenge#3 jolt-verifier::stages/stage6b/outputs.rs::Stage6bSumchecks::::batching_coefficient[*] jolt-verifier::stages/stage7/outputs.rs::Stage7Sumchecks::::batching_coefficient[*] jolt-verifier::stages/stage8/verify.rs::verify::challenge_powers#0 From f57ca65e2b0ebc8e690814628ff9673ded6c8ab4 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Wed, 21 Oct 2026 12:29:04 -0400 Subject: [PATCH 14/16] feat(transcript): width-5 native-Fr Poseidon sponge on spongefish ProverState/VerifierState Replace the byte-based Poseidon sponge (capacity about 2^127) with the width-5 circom BN254 Poseidon over FrUnit: rate 3, capacity 2. - SpongeUnit maps each message's byte encoding to sponge units: the identity for u8 (Keccak and Blake2b bytes are unchanged), and for FrUnit a length element followed by 31-byte little-endian chunks. - ChallengeSponge is the single challenge path; Poseidon draws Fr challenges as squeezed elements and implements it only for Fr. - Proof of work on Poseidon tests the low bits of one squeezed element; the MessageSearch probe absorbs exactly the packed nonce bytes. - Verifier rejections poison the state through a message that never parses, since spongefish's check exists only for byte sponges. - Re-enable transcript-poseidon in jolt-sdk and the examples; Keccak stays the default, and Blake2b with Poseidon is a compile error. - Re-bless the Fiat-Shamir absorb-site inventory. --- crates/jolt-prover/src/blindfold.rs | 10 +- .../jolt-transcript/benches/transcript_ops.rs | 21 +- .../fuzz/fuzz_targets/encoding_injectivity.rs | 12 +- .../fuzz_targets/transcript_determinism.rs | 24 +- crates/jolt-transcript/src/challenge.rs | 67 ++- crates/jolt-transcript/src/codec.rs | 71 ++- crates/jolt-transcript/src/lib.rs | 15 +- crates/jolt-transcript/src/poseidon.rs | 527 +++++++++++------- crates/jolt-transcript/src/pow.rs | 80 +-- crates/jolt-transcript/src/prover.rs | 27 +- crates/jolt-transcript/src/scripted.rs | 23 +- crates/jolt-transcript/src/setup.rs | 12 +- crates/jolt-transcript/src/transcript.rs | 121 ++-- crates/jolt-transcript/src/verifier.rs | 36 +- .../jolt-transcript/tests/poseidon_tests.rs | 250 +++++++-- crates/jolt-transcript/tests/pow_tests.rs | 63 ++- .../tests/fs_inventory/absorb-sites.inventory | 5 +- examples/fibonacci/Cargo.toml | 1 + examples/merkle-tree-save/Cargo.toml | 1 + .../src/invariant/transcript_symmetry.rs | 38 +- jolt-sdk/Cargo.toml | 1 + jolt-sdk/src/host_utils.rs | 28 +- 22 files changed, 966 insertions(+), 467 deletions(-) diff --git a/crates/jolt-prover/src/blindfold.rs b/crates/jolt-prover/src/blindfold.rs index 689ca06099..6e3d817210 100644 --- a/crates/jolt-prover/src/blindfold.rs +++ b/crates/jolt-prover/src/blindfold.rs @@ -27,7 +27,7 @@ use jolt_crypto::{HomomorphicCommitment, VectorCommitment}; use jolt_field::{Accumulator, JoltField, WithAccumulator}; use jolt_openings::{AdditivelyHomomorphic, CommitmentScheme, ZkOpeningScheme}; use jolt_sumcheck::{CommittedSumcheckWitness, SumcheckDomainSpec}; -use jolt_transcript::{Encoding, FieldTranscript, Label, Probe, ProverInit}; +use jolt_transcript::{Encoding, FieldTranscript, Label, Probe, ProverInit, Unit}; use jolt_verifier::proof::JoltProof; use jolt_verifier::stages::zk::{blindfold, inputs::BlindFoldInputs}; use jolt_verifier::stages::{ @@ -126,17 +126,17 @@ where shell, trusted_advice_commitment, )?; - // A 32-byte compare guarding the exact seam the design rests on: the + // A 32-unit compare guarding the exact seam the design rests on: the // replay landing on the prover's forward sponge state, compared by what // copies of the two sponges squeeze next. Hard error (not debug-only) so // release provers diagnose drift here rather than as a downstream // BlindFold verification failure. - let next_bytes = |transcript: &T| { - let mut out = [0u8; 32]; + let next_units = |transcript: &T| { + let mut out = [::ZERO; 32]; transcript.message_search().probe(&[], &mut out); out }; - if next_bytes(&transcript) != next_bytes(forward) { + if next_units(&transcript) != next_units(forward) { return Err(ProverError::InvariantViolation { reason: "the verifier replay diverged from the prover's forward transcript", }); diff --git a/crates/jolt-transcript/benches/transcript_ops.rs b/crates/jolt-transcript/benches/transcript_ops.rs index e1391c3fa3..71c9b87435 100644 --- a/crates/jolt-transcript/benches/transcript_ops.rs +++ b/crates/jolt-transcript/benches/transcript_ops.rs @@ -8,11 +8,12 @@ use criterion::{ }; use jolt_field::Fr; use jolt_transcript::{ - prover_transcript, Blake2b512, FieldTranscript, Keccak, PoseidonSponge, ProverState, Transcript, + prover_transcript, Blake2b512, FieldTranscript, Keccak, PoseidonSponge, ProverState, + SpongeUnit, Transcript, }; -use spongefish::{DuplexSpongeInit, DuplexSpongeInterface}; +use spongefish::DuplexSpongeInit; -fn fresh + DuplexSpongeInit>() -> ProverState { +fn fresh>() -> ProverState { prover_transcript::(b"bench", b"bench") } @@ -22,7 +23,7 @@ fn bench_append_bytes_with( label: &str, data: &[u8], ) where - H: DuplexSpongeInterface + DuplexSpongeInit, + H: DuplexSpongeInit, { group.bench_with_input(BenchmarkId::new(name, label), data, |bench, data| { bench.iter_batched( @@ -76,6 +77,18 @@ fn bench_challenge(c: &mut Criterion) { ); }); + group.bench_function("Poseidon", |bench| { + bench.iter_batched( + || { + let mut t = fresh::(); + t.append_bytes(&[42u8; 32]); + t + }, + |mut t| FieldTranscript::::challenge(&mut t), + BatchSize::SmallInput, + ); + }); + group.finish(); } diff --git a/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs b/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs index 17600e1fd6..c500a31ea5 100644 --- a/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs +++ b/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs @@ -9,9 +9,13 @@ //! move changes the framing and MUST change the state. A matching state would //! mean the framing convention fails to separate `[ab]` from `[a][b]`, the //! classic transcript-malleability footgun. The state is observed as the next -//! 32 squeezed bytes. +//! four squeezed units: 4 bytes, or 4 `Fr` elements on Poseidon. -use jolt_transcript::{prover_transcript, Blake2b512, Keccak, PoseidonSponge, Transcript}; +use std::fmt::Debug; + +use jolt_transcript::{ + prover_transcript, Blake2b512, Keccak, PoseidonSponge, SpongeUnit, Transcript, +}; use libfuzzer_sys::fuzz_target; use spongefish::DuplexSpongeInit; @@ -32,12 +36,12 @@ fn parse_chunks(data: &[u8]) -> Vec<&[u8]> { chunks } -fn framed_state>(chunks: &[Vec]) -> [u8; 32] { +fn framed_state>(chunks: &[Vec]) -> Vec { let mut transcript = prover_transcript::(b"fuzz-injectivity", b""); for chunk in chunks { transcript.append_bytes(chunk); } - transcript.verifier_messages::() + transcript.verifier_message_as(32, <[H::U]>::to_vec) } fuzz_target!(|data: &[u8]| { diff --git a/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs b/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs index b580ba53d2..b302d7831d 100644 --- a/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs +++ b/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs @@ -3,12 +3,16 @@ //! Determinism and absorption oracles over the transcript sponges. //! //! Twin instances driven by the same fuzzer-chosen op sequence must agree on -//! every squeeze and on the final state, observed as the next 32 squeezed -//! bytes, and replaying the sequence with one absorbed byte flipped must reach -//! a different final state — a matching state would mean absorbed bytes are -//! ignored or collide. +//! every squeeze and on the final state, observed as the next four squeezed +//! units (bytes, or `Fr` elements on Poseidon), and replaying the sequence +//! with one absorbed byte flipped must reach a different final state — a +//! matching state would mean absorbed bytes are ignored or collide. -use jolt_transcript::{prover_transcript, Blake2b512, Keccak, PoseidonSponge, Transcript}; +use std::fmt::Debug; + +use jolt_transcript::{ + prover_transcript, Blake2b512, Keccak, PoseidonSponge, SpongeUnit, Transcript, +}; use libfuzzer_sys::fuzz_target; use spongefish::DuplexSpongeInit; @@ -41,10 +45,10 @@ fn parse_ops(data: &[u8]) -> Vec> { /// Replays `ops`, optionally XOR-ing `mask` into the absorbed byte at global /// offset `position`. Returns the challenge stream and the final state. -fn run>( +fn run>( ops: &[Op<'_>], flip: Option<(usize, u8)>, -) -> (Vec<[u8; 16]>, [u8; 32]) { +) -> (Vec>, Vec) { let mut transcript = prover_transcript::(b"fuzz-determinism", b""); let mut challenges = Vec::new(); let mut absorbed = 0usize; @@ -63,13 +67,13 @@ fn run>( } absorbed += chunk.len(); } - Op::Challenge => challenges.push(transcript.verifier_messages::()), + Op::Challenge => challenges.push(transcript.verifier_message_as(16, <[H::U]>::to_vec)), } } - (challenges, transcript.verifier_messages::()) + (challenges, transcript.verifier_message_as(32, <[H::U]>::to_vec)) } -fn check>( +fn check>( ops: &[Op<'_>], total_absorbed: usize, position: usize, diff --git a/crates/jolt-transcript/src/challenge.rs b/crates/jolt-transcript/src/challenge.rs index b618bf568c..777275d5ec 100644 --- a/crates/jolt-transcript/src/challenge.rs +++ b/crates/jolt-transcript/src/challenge.rs @@ -29,15 +29,15 @@ use jolt_field::{Fq, Fr}; use jolt_field::{F128, F192, F64, F8}; #[cfg(feature = "transcript-keccak")] use spongefish::instantiations::Keccak; -use spongefish::{DuplexSpongeInterface, VerificationError}; +use spongefish::DuplexSpongeInterface; #[cfg(feature = "transcript-blake2b")] use crate::Blake2b512; -use crate::ChallengeSetSize; +use crate::{ChallengeSetSize, SpongeUnit}; use sealed::Sealed; -mod sealed { +pub(crate) mod sealed { pub trait Sealed {} } @@ -111,10 +111,49 @@ pub trait ChallengeCodec: Sized + Sealed { /// security. Field challenges are decoded from raw squeezed bytes, so the /// codecs' TV bounds hold only on such a sponge. /// -/// `PoseidonSponge` is not one: each block it squeezes is the little-endian -/// encoding of a BN254 scalar, whose top bits are far from uniform. +/// `PoseidonSponge` squeezes field elements, not bytes, and is not one; it +/// draws `Fr` challenges natively through [`ChallengeSponge`]. pub trait UniformBytes: DuplexSpongeInterface + Sealed {} +/// A sponge that draws one message of `len` challenges in `F`, with +/// conventional TV at most 2^-161 for the whole message. +/// +/// Every [`UniformBytes`] sponge draws every [`ChallengeCodec`] field by +/// decoding squeezed bytes. `PoseidonSponge` draws only BN254 `Fr`, one +/// squeezed element per challenge with no decoding; challenges in other +/// fields have no implementation on it, so asking for them does not compile. +/// +/// The trait is sealed. +pub trait ChallengeSponge: DuplexSpongeInterface + Sealed { + /// `|F|` as this sponge draws it: the number of values one challenge can + /// take. Proof-of-work difficulties are priced from it + /// ([`grind_bits_for_loss`]). + /// + /// [`grind_bits_for_loss`]: crate::grind_bits_for_loss + const CHALLENGE_SET: ChallengeSetSize; + + /// Units squeezed for `len` elements; depends only on the sponge, `F` and + /// `len`. `None` when `F` does not support `len` or the message would + /// exceed [`MAX_CHALLENGE_BYTES`] bytes of squeezed output. + fn challenge_units(len: usize) -> Option; + + /// Decodes exactly `challenge_units(len)` squeezed units. `None` on a + /// wrong length or rejection-pool exhaustion. + fn decode_challenges(units: &[Self::U], len: usize) -> Option>; +} + +impl ChallengeSponge for H { + const CHALLENGE_SET: ChallengeSetSize = F::CHALLENGE_SET_SIZE; + + fn challenge_units(len: usize) -> Option { + F::challenge_bytes(len).filter(|&bytes| bytes <= MAX_CHALLENGE_BYTES) + } + + fn decode_challenges(units: &[u8], len: usize) -> Option> { + F::decode_challenges(units, len) + } +} + #[cfg(feature = "transcript-keccak")] impl Sealed for Keccak {} #[cfg(feature = "transcript-keccak")] @@ -124,9 +163,10 @@ impl Sealed for Blake2b512 {} #[cfg(feature = "transcript-blake2b")] impl UniformBytes for Blake2b512 {} -/// Largest challenge message, in squeezed bytes. Larger requests are rejected -/// before touching the sponge, so a length taken from the input cannot make -/// the verifier allocate without bound. +/// Largest challenge message, in squeezed bytes; a Poseidon element counts as +/// 32 bytes. Larger requests are rejected before touching the sponge, so a +/// length taken from the input cannot make the verifier allocate without +/// bound. pub const MAX_CHALLENGE_BYTES: usize = 1 << 24; #[cfg(any(feature = "solinas", feature = "bn254"))] @@ -336,17 +376,6 @@ macro_rules! binary_codec { #[cfg(feature = "binary")] binary_codec!(F8, F64, F128, F192); -/// Bytes squeezed for a message of `len` elements of `F`. -/// -/// # Errors -/// Returns `VerificationError` when `F` does not support `len` or the message -/// would exceed [`MAX_CHALLENGE_BYTES`]. -pub(crate) fn message_bytes(len: usize) -> Result { - F::challenge_bytes(len) - .filter(|&bytes| bytes <= MAX_CHALLENGE_BYTES) - .ok_or(VerificationError) -} - #[cfg(all(test, feature = "solinas"))] mod tests { use super::{Prime64Offset59, Sealed, UniformBytes}; diff --git a/crates/jolt-transcript/src/codec.rs b/crates/jolt-transcript/src/codec.rs index f9d74253ab..1a7da0b4b0 100644 --- a/crates/jolt-transcript/src/codec.rs +++ b/crates/jolt-transcript/src/codec.rs @@ -1,10 +1,77 @@ -//! Message encodings for byte-oriented spongefish transcripts. +//! Message encodings, and how a sponge absorbs them. //! //! [`BytesMsg`] is a byte string with an eight-byte little-endian length, //! followed by the body. [`Label`], [`LabelWithCount`] and [`U64Word`] are the //! 32-byte words that name what the next messages are. +//! +//! Every message is a byte [`Encoding`]. [`SpongeUnit`] maps it to the units +//! the sponge absorbs: a byte sponge absorbs the bytes as they are, and +//! `PoseidonSponge` packs them into BN254 `Fr` elements. + +use spongefish::{Encoding, FromNarg, NargReader, Unit, VerificationError}; + +use sealed::Sealed; + +pub(crate) mod sealed { + pub trait Sealed {} +} + +/// The alphabet of a transcript's sponge: how the sponge absorbs a message, +/// and how it reads a proof-of-work check from squeezed units. +/// +/// The trait is sealed. +pub trait SpongeUnit: Unit + Copy + Eq + Sealed { + /// The units the sponge absorbs for `message`. The map is injective and + /// prefix-free on byte strings, so messages absorbed back to back decode + /// uniquely whenever their byte encodings do. + fn encode_message(message: &M) -> impl AsRef<[Self]>; + + /// Units squeezed for a proof-of-work check of `bits` bits, for + /// `1 <= bits <= MAX_POW_BITS`; at most 4. + /// + /// [`MAX_POW_BITS`]: crate::MAX_POW_BITS + fn check_len(bits: u32) -> usize; + + /// The low 32 bits of a check, or `None` unless `check` has the length of + /// one check. + fn low_word(check: &[Self]) -> Option; +} + +impl Sealed for u8 {} + +/// A byte sponge absorbs a message's bytes as they are, and checks the low +/// bits of `ceil(bits / 8)` little-endian bytes. +impl SpongeUnit for u8 { + fn encode_message(message: &M) -> impl AsRef<[u8]> { + message.encode() + } -use spongefish::{Encoding, FromNarg, NargReader, VerificationError}; + fn check_len(bits: u32) -> usize { + (bits as usize).div_ceil(8) + } + + fn low_word(check: &[u8]) -> Option { + let mut word = [0u8; 4]; + word.get_mut(..check.len())?.copy_from_slice(check); + Some(u32::from_le_bytes(word)) + } +} + +/// A message as its sponge absorbs it: [`SpongeUnit::encode_message`] of its +/// byte encoding. The proof carries the byte encoding itself. +pub(crate) struct Units(pub(crate) M); + +impl Encoding for Units { + fn encode(&self) -> impl AsRef<[U]> { + U::encode_message(&self.0) + } +} + +impl FromNarg for Units { + fn from_narg(reader: &mut NargReader<'_>) -> Result { + M::from_narg(reader).map(Self) + } +} /// Maximum label length in bytes accepted by [`Label`]. pub const MAX_LABEL_LEN: usize = 32; diff --git a/crates/jolt-transcript/src/lib.rs b/crates/jolt-transcript/src/lib.rs index 0c6a03808c..dce4d0d462 100644 --- a/crates/jolt-transcript/src/lib.rs +++ b/crates/jolt-transcript/src/lib.rs @@ -11,8 +11,11 @@ //! Three sponges are feature-gated: `transcript-keccak` (spongefish `Keccak`, //! the default), `transcript-blake2b` (spongefish `Blake2b512`) and //! `transcript-poseidon` (a Circom-compatible BN254 [`PoseidonSponge`]). -//! Field challenges need uniform squeezed bytes ([`UniformBytes`]), which -//! Poseidon does not give, so a Poseidon transcript cannot draw them. +//! Keccak and Blake2b absorb messages as bytes and decode field challenges +//! from uniform squeezed bytes ([`UniformBytes`]). Poseidon works over BN254 +//! `Fr`: it packs each message's bytes into field elements and draws `Fr` +//! challenges as squeezed elements, with no decoding; it draws challenges in +//! no other field ([`ChallengeSponge`]). #![deny(missing_docs)] // In the jolt-verifier runtime closure: stricter panic and unsafe discipline @@ -44,20 +47,20 @@ mod setup; mod transcript; mod verifier; -pub use challenge::{ChallengeCodec, UniformBytes, MAX_CHALLENGE_BYTES}; -pub use codec::{BytesMsg, Label, LabelWithCount, U64Word, MAX_LABEL_LEN}; +pub use challenge::{ChallengeCodec, ChallengeSponge, UniformBytes, MAX_CHALLENGE_BYTES}; +pub use codec::{BytesMsg, Label, LabelWithCount, SpongeUnit, U64Word, MAX_LABEL_LEN}; pub use grind_bits::{ grind_bits_for_loss, ChallengeSetSize, GrindBitsError, MAX_POW_BITS, SECURITY_BITS, }; #[cfg(feature = "transcript-poseidon")] -pub use poseidon::PoseidonSponge; +pub use poseidon::{FrUnit, PoseidonPermutation, PoseidonSponge}; #[cfg(feature = "prover-grinding")] pub use pow::{MessageSearch, Probe}; pub use prover::{ProverInit, ProverTranscript}; #[cfg(feature = "test-utils")] pub use scripted::{Script, ScriptedTranscript, TranscriptEvent}; pub use setup::{prover_transcript, session_id, verifier_transcript, PROTOCOL_ID}; -pub use spongefish::{Encoding, ProverState, VerificationError, VerifierState}; +pub use spongefish::{Encoding, ProverState, Unit, VerificationError, VerifierState}; pub use transcript::{FieldTranscript, Transcript}; pub use verifier::{VerifierInit, VerifierTranscript}; diff --git a/crates/jolt-transcript/src/poseidon.rs b/crates/jolt-transcript/src/poseidon.rs index ddb6673485..2f4dbf6c4d 100644 --- a/crates/jolt-transcript/src/poseidon.rs +++ b/crates/jolt-transcript/src/poseidon.rs @@ -1,168 +1,227 @@ -//! `PoseidonSponge` — Circom-compatible BN254 Poseidon adapter exposed -//! through `spongefish::DuplexSpongeInterface`. +//! `PoseidonSponge` — a duplex sponge over BN254 `Fr`, on the +//! Circom-compatible Poseidon permutation. //! -//! Sponge layout: one `Fr` capacity element (`self.state`) plus two `Fr` -//! rate inputs per `permute` call, fed through light-poseidon's width-4 -//! compression function (`Poseidon::new_circom(3)` — width minus one -//! inputs). Each call replaces capacity with the compression output. +//! # Sponge //! -//! Byte traffic is mapped to `Fr` via 31-byte little-endian chunks -//! (`Fr::from_le_bytes_mod_order` is injective on chunks ≤ 31 bytes since -//! 248 bits < BN254 modulus). Squeezed bytes come from -//! `into_bigint().to_bytes_le()` of the running state. +//! The sponge is spongefish's overwrite-mode [`DuplexSponge`] over the unit +//! [`FrUnit`], with the width-5 Poseidon permutation of circomlib (x^5 S-box, +//! 8 full and 60 partial rounds, the constants light-poseidon ships for five +//! state elements). Rate is 3 units (state slots 0–2) and capacity is 2 units +//! (slots 3–4). The generic bound is a birthday collision on the capacity, +//! about `2^253` permutation calls, so security rests on the permutation's +//! own 128-bit design target; one unit of capacity would give only about +//! `2^127`. +//! Absorb and squeeze are associative: absorbing in several calls equals one +//! absorb of the concatenation. //! -//! Round constants are built once per `PoseidonSponge` construction; the -//! same `Poseidon` is reused for every `permute` call in this sponge's -//! lifetime (`light_poseidon::Poseidon::hash` clears its scratch state on -//! exit, so reuse is safe). +//! Challenges are squeezed natively, one unit each. A squeezed unit is a +//! uniform `Fr` up to the sponge's security, so `Fr` challenges need no +//! decoding and have no reduction bias. Challenges in any other field are not +//! offered on this sponge. +//! +//! # Byte packing +//! +//! Every message, the session identifier and the transcript instance enter +//! the sponge as byte strings, each packed on its own: a byte string of +//! length `n` becomes the unit `n`, then `ceil(n / 31)` units, each the +//! little-endian integer of the next 31 bytes (the last chunk may be +//! shorter). Every chunk is below `2^248 < p`, so it is a canonical element, +//! and the leading length fixes the number of chunks and the size of the last +//! one: the map is injective and prefix-free, so the messages absorbed +//! between two squeezes decode uniquely from their concatenation. +//! +//! A message is packed from its byte [`Encoding`]. A one-byte message, such +//! as each byte of a proof-of-work nonce, is two units; a 32-byte field +//! element is three. -use ark_bn254::Fr; -use ark_ff::{BigInteger, PrimeField, Zero}; -use light_poseidon::{Poseidon, PoseidonHasher}; -use spongefish::{DuplexSpongeInit, DuplexSpongeInterface}; +use std::sync::OnceLock; -const SQUEEZE_BYTES: usize = 32; -const ABSORB_CHUNK_BYTES: usize = 31; +use ark_bn254::Fr as InnerFr; +use ark_ff::{AdditiveGroup, Field, PrimeField}; +use jolt_field::Fr; +use light_poseidon::parameters::bn254_x5::get_poseidon_parameters; +use spongefish::{DuplexSponge, EncodedSessionId, Encoding, Permutation, Unit}; -#[expect( - clippy::expect_used, - reason = "width 4 (NR_INPUTS=3) is supported by light-poseidon's Circom params" -)] -fn fresh_hasher() -> Poseidon { - Poseidon::::new_circom(3).expect("light-poseidon: width-4 init") +use crate::challenge::sealed::Sealed as SpongeSealed; +use crate::codec::sealed::Sealed as UnitSealed; +use crate::{ChallengeCodec, ChallengeSetSize, ChallengeSponge, SpongeUnit, MAX_CHALLENGE_BYTES}; + +/// State width of the permutation, in field elements. +const WIDTH: usize = 5; +/// Units absorbed or squeezed per permutation call. +const RATE: usize = 3; +/// Bytes per packed chunk: `2^248 < p`, so every chunk is canonical. +const CHUNK_BYTES: usize = 31; + +/// Duplex sponge over BN254 `Fr` with the Circom-compatible width-5 Poseidon +/// permutation. See the [module documentation](self) for the layout and the +/// byte packing. +pub type PoseidonSponge = DuplexSponge; + +/// One BN254 `Fr` element: the alphabet of [`PoseidonSponge`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct FrUnit(InnerFr); + +impl Unit for FrUnit { + const ZERO: Self = Self(InnerFr::ZERO); } -/// Width-4 Poseidon duplex sponge over BN254 `Fr`, byte-driven. -pub struct PoseidonSponge { - hasher: Poseidon, - state: Fr, - pending_squeeze: [u8; SQUEEZE_BYTES], - squeeze_offset: usize, +impl From for FrUnit { + fn from(value: Fr) -> Self { + Self(value.into()) + } } -impl PoseidonSponge { - /// Construct a fresh sponge with zero state. - pub fn new() -> Self { - Self { - hasher: fresh_hasher(), - state: Fr::zero(), - pending_squeeze: [0u8; SQUEEZE_BYTES], - squeeze_offset: SQUEEZE_BYTES, - } +impl From for Fr { + fn from(unit: FrUnit) -> Self { + unit.0.into() } +} + +/// Packs a byte string as its length, then 31-byte little-endian chunks. +fn pack_bytes(bytes: &[u8]) -> Vec { + let mut units = Vec::with_capacity(1 + bytes.len().div_ceil(CHUNK_BYTES)); + units.push(FrUnit(InnerFr::from(bytes.len() as u64))); + units.extend( + bytes + .chunks(CHUNK_BYTES) + .map(|chunk| FrUnit(InnerFr::from_le_bytes_mod_order(chunk))), + ); + units +} - /// Advance the state and discard any pending squeezed bytes. - pub fn ratchet(&mut self) -> &mut Self { - self.permute(Fr::zero(), Fr::zero()); - self.squeeze_offset = SQUEEZE_BYTES; - self +impl EncodedSessionId for FrUnit { + fn encode_bytes(bytes: &[u8]) -> impl AsRef<[Self]> { + pack_bytes(bytes) } +} + +impl UnitSealed for FrUnit {} - /// One Poseidon application over the current state plus two `Fr` rate - /// inputs. - fn permute(&mut self, a: Fr, b: Fr) { - #[expect( - clippy::expect_used, - reason = "input length matches hasher width-1; failure is unreachable" - )] - let next = self - .hasher - .hash(&[self.state, a, b]) - .expect("light-poseidon hash"); - self.state = next; +/// A message is its packed byte encoding. The proof-of-work check is one +/// squeezed element, tested on the low bits of its canonical integer: for a +/// uniform element and `bits <= MAX_POW_BITS`, those bits are within +/// `2^bits / p < 2^-228` of uniform. +/// +/// [`MAX_POW_BITS`]: crate::MAX_POW_BITS +impl SpongeUnit for FrUnit { + fn encode_message(message: &M) -> impl AsRef<[Self]> { + pack_bytes(message.encode().as_ref()) } - fn refill_squeeze(&mut self) { - self.permute(Fr::zero(), Fr::zero()); - let bytes = self.state.into_bigint().to_bytes_le(); - self.pending_squeeze.fill(0); - let n = bytes.len().min(SQUEEZE_BYTES); - let (dst, _) = self.pending_squeeze.split_at_mut(n); - let (src, _) = bytes.split_at(n); - dst.copy_from_slice(src); - self.squeeze_offset = 0; + fn check_len(_bits: u32) -> usize { + 1 } - fn absorb_fr_pair(&mut self, a: Fr, b: Fr) { - // Any pending squeeze is invalidated by a new absorb; spongefish's - // DuplexSpongeInterface contract is associative within a phase and - // the squeeze cache is just a buffer over fresh permutations. - self.squeeze_offset = SQUEEZE_BYTES; - self.permute(a, b); + fn low_word(check: &[Self]) -> Option { + let [unit] = check else { + return None; + }; + let low_limb = unit.0.into_bigint().0.first().copied()?; + Some(low_limb as u32) } } -impl Default for PoseidonSponge { - fn default() -> Self { - Self::new() - } +/// Round constants and MDS matrix of the width-5 Circom permutation. +struct Constants { + /// One row of `WIDTH` constants per round. + ark: Vec<[InnerFr; WIDTH]>, + mds: [[InnerFr; WIDTH]; WIDTH], + full_rounds: usize, + partial_rounds: usize, } -impl Clone for PoseidonSponge { - fn clone(&self) -> Self { - Self { - hasher: fresh_hasher(), - state: self.state, - pending_squeeze: self.pending_squeeze, - squeeze_offset: self.squeeze_offset, +#[expect( + clippy::expect_used, + reason = "light-poseidon ships width-5 Circom parameters of this shape; a mismatch is a build defect caught by the first test" +)] +fn constants() -> &'static Constants { + static CONSTANTS: OnceLock = OnceLock::new(); + CONSTANTS.get_or_init(|| { + let params = get_poseidon_parameters::(WIDTH as u8) + .expect("light-poseidon: width-5 Circom parameters"); + assert_eq!(params.width, WIDTH, "Poseidon width"); + assert_eq!(params.alpha, 5, "Poseidon S-box exponent"); + let rounds = params.full_rounds + params.partial_rounds; + assert_eq!(params.ark.len(), rounds * WIDTH, "Poseidon round constants"); + let ark = params + .ark + .chunks_exact(WIDTH) + .map(|row| <[InnerFr; WIDTH]>::try_from(row).expect("row of WIDTH constants")) + .collect(); + let rows: Vec<[InnerFr; WIDTH]> = params + .mds + .iter() + .map(|row| <[InnerFr; WIDTH]>::try_from(row.as_slice()).expect("MDS row")) + .collect(); + let mds = <[[InnerFr; WIDTH]; WIDTH]>::try_from(rows).expect("WIDTH MDS rows"); + Constants { + ark, + mds, + full_rounds: params.full_rounds, + partial_rounds: params.partial_rounds, } - } + }) } -impl std::fmt::Debug for PoseidonSponge { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PoseidonSponge") - .field("state", &self.state) - .field("squeeze_offset", &self.squeeze_offset) - .finish_non_exhaustive() - } +fn sbox(x: InnerFr) -> InnerFr { + x.square().square() * x } -impl DuplexSpongeInit for PoseidonSponge { - fn init(session_id: &[u8; 32]) -> Self { - let mut sponge = Self::new(); - let _ = sponge.absorb(session_id); - sponge +/// The width-5 Poseidon permutation with circomlib's constants: the same +/// rounds as light-poseidon's `Poseidon::new_circom(4)`, which applies it to +/// `[0, a, b, c, d]` and returns slot 0, and as circomlib's `PoseidonEx` with +/// four inputs, an initial state and all five outputs. +#[derive(Clone, Copy, Debug, Default)] +pub struct PoseidonPermutation; + +impl Permutation for PoseidonPermutation { + type U = FrUnit; + + fn permute_mut(&self, state: &mut [FrUnit; WIDTH]) { + let constants = constants(); + let half_full = constants.full_rounds / 2; + let partial = half_full..half_full + constants.partial_rounds; + let mut s = state.map(|unit| unit.0); + for (round, ark) in constants.ark.iter().enumerate() { + for (x, c) in s.iter_mut().zip(ark) { + *x += c; + } + if partial.contains(&round) { + if let Some(first) = s.first_mut() { + *first = sbox(*first); + } + } else { + for x in &mut s { + *x = sbox(*x); + } + } + let mut mixed = [InnerFr::ZERO; WIDTH]; + for (out, row) in mixed.iter_mut().zip(&constants.mds) { + *out = row + .iter() + .zip(&s) + .fold(InnerFr::ZERO, |acc, (m, x)| acc + *m * x); + } + s = mixed; + } + *state = s.map(FrUnit); } } -impl DuplexSpongeInterface for PoseidonSponge { - type U = u8; - - fn absorb(&mut self, input: &[u8]) -> &mut Self { - // Length-binding permutation up front: without it, absorb(&[]), - // absorb(&[0]), absorb(&[0; 31]) all collapse to Fr::zero() at the - // chunk level and would alias. - let len_fr = Fr::from(input.len() as u64); - self.absorb_fr_pair(len_fr, Fr::zero()); - - let mut iter = input.chunks(ABSORB_CHUNK_BYTES); - while let Some(first) = iter.next() { - let a = Fr::from_le_bytes_mod_order(first); - let b = iter - .next() - .map_or_else(Fr::zero, Fr::from_le_bytes_mod_order); - self.absorb_fr_pair(a, b); - } - self +impl SpongeSealed for PoseidonSponge {} + +/// `Fr` challenges are squeezed units, one per element, from the whole field. +/// Messages are capped at [`MAX_CHALLENGE_BYTES`] of 32-byte elements. +impl ChallengeSponge for PoseidonSponge { + const CHALLENGE_SET: ChallengeSetSize = ::CHALLENGE_SET_SIZE; + + fn challenge_units(len: usize) -> Option { + (len <= MAX_CHALLENGE_BYTES / 32).then_some(len) } - fn squeeze(&mut self, output: &mut [u8]) -> &mut Self { - let mut remaining = &mut *output; - while !remaining.is_empty() { - if self.squeeze_offset >= SQUEEZE_BYTES { - self.refill_squeeze(); - } - let avail = SQUEEZE_BYTES - self.squeeze_offset; - let take = avail.min(remaining.len()); - let (head, tail) = std::mem::take(&mut remaining).split_at_mut(take); - let (_, pending) = self.pending_squeeze.split_at(self.squeeze_offset); - let (src, _) = pending.split_at(take); - head.copy_from_slice(src); - self.squeeze_offset += take; - remaining = tail; - } - self + fn decode_challenges(units: &[FrUnit], len: usize) -> Option> { + (units.len() == len).then(|| units.iter().copied().map(Fr::from).collect()) } } @@ -171,86 +230,158 @@ impl DuplexSpongeInterface for PoseidonSponge { unused_results, reason = "DuplexSpongeInterface methods return &mut Self for chaining" )] +#[expect(clippy::unwrap_used, clippy::indexing_slicing, reason = "tests")] mod tests { use super::*; + use std::str::FromStr; + + use ark_ff::BigInteger; + use light_poseidon::{Poseidon, PoseidonHasher}; + use spongefish::{DuplexSpongeInit, DuplexSpongeInterface}; + + fn unit(value: u64) -> FrUnit { + FrUnit(InnerFr::from(value)) + } + /// The permutation agrees with light-poseidon's Circom hash, which puts a + /// zero domain tag in slot 0 and returns slot 0. #[test] - fn empty_distinct_from_zero_absorb() { - let mut a = PoseidonSponge::new(); - let mut b = PoseidonSponge::new(); - a.absorb(&[]); - b.absorb(&[0u8]); - let mut x = [0u8; 32]; - let mut y = [0u8; 32]; - a.squeeze(&mut x); - b.squeeze(&mut y); - assert_ne!(x, y); + fn permutation_matches_the_circom_hash() { + let mut hasher = Poseidon::::new_circom(4).unwrap(); + for inputs in [[0u64, 0, 0, 0], [1, 2, 3, 4], [u64::MAX, 7, 1 << 40, 9]] { + let [a, b, c, d] = inputs.map(InnerFr::from); + let mut state = [FrUnit::ZERO, FrUnit(a), FrUnit(b), FrUnit(c), FrUnit(d)]; + PoseidonPermutation.permute_mut(&mut state); + assert_eq!(state[0].0, hasher.hash(&[a, b, c, d]).unwrap()); + } } - /// Clone rebuilds the (non-Clone) hasher but must preserve the duplex - /// state, including a partially consumed squeeze block. + /// Full input and output states from circomlibjs 0.1.7: `poseidon(inputs, + /// init, 5)` permutes `[init, inputs..]` and returns all five slots. This + /// pins the slots the sponge writes and reads, not only slot 0. #[test] - fn clone_resumes_a_partially_consumed_squeeze_block() { - let mut original = PoseidonSponge::new(); - original.absorb(b"clone me"); - let mut prefix = [0u8; 11]; - original.squeeze(&mut prefix); + fn permutation_matches_circomlibjs_on_the_full_state() { + let vectors: [([&str; WIDTH], [&str; WIDTH]); 3] = [ + ( + ["0", "1", "2", "3", "4"], + [ + "18821383157269793795438455681495246036402687001665670618754263018637548127333", + "7817711165059374331357136443537800893307845083525445872661165200086166013245", + "16733335996448830230979566039396561240864200624113062088822991822580465420551", + "6644334865470350789317807668685953492649391266180911382577082600917830417726", + "3372108894677221197912083238087960099443657816445944159266857514496320565191", + ], + ), + ( + ["5", "6", "7", "8", "9"], + [ + "18579283463335299535026317327209621916637121867533110620302664491185669653790", + "4839293927659934187583387626954013301637664044190093833281890908670902790478", + "3533800487131377481301584166615267513872620666740963507842462725216737178402", + "5940255035692576900005793592585174844567995327116752015751005272509148573319", + "20404954242403156198400108044020687119358875604606616587752779986720487318498", + ], + ), + ( + [ + "1606938044258990275541962092341162602522202993782792835301387", + "515377520732011331036461129765621272702107522001", + "0", + "1", + "1809251394333065553493296640760748560207343510400633813116524750123642650624", + ], + [ + "10283392003478364169555601049796176772009100738157568382521096879459620708057", + "20080835164471701059339308672369966090281333042336046927504392591494914030429", + "16354268304952869921319451770559219642228841318443983767906418135619628518778", + "10967621804834771612841019768306877278929285037226631454369680010856400098637", + "9874277098345336612192428837053126420843492328588816763190316741189242710163", + ], + ), + ]; + let parse = |digits: [&str; WIDTH]| digits.map(|d| FrUnit(InnerFr::from_str(d).unwrap())); + for (input, output) in vectors { + let mut state = parse(input); + PoseidonPermutation.permute_mut(&mut state); + assert_eq!(state, parse(output)); + } + } - let mut cloned = original.clone(); - let mut rest_original = [0u8; 40]; - let mut rest_cloned = [0u8; 40]; - original.squeeze(&mut rest_original); - cloned.squeeze(&mut rest_cloned); - assert_eq!(rest_original, rest_cloned); + #[test] + fn byte_packing_is_length_then_31_byte_chunks() { + assert_eq!(pack_bytes(&[]), [unit(0)]); + assert_eq!(pack_bytes(&[0]), [unit(1), unit(0)]); + let bytes: Vec = (1..=32).collect(); + let packed = pack_bytes(&bytes); + assert_eq!(packed.len(), 3); + assert_eq!(packed[0], unit(32)); + assert_eq!(packed[1].0, InnerFr::from_le_bytes_mod_order(&bytes[..31])); + assert_eq!(packed[2], unit(32)); + // A full chunk of 0xff is 2^248 - 1, below the modulus. + let top = pack_bytes(&[0xff; 31]); + assert_eq!(top[1].0.into_bigint().to_bytes_le()[..31], [0xff; 31]); } + /// Byte strings that alias at the chunk level stay distinct through the + /// length, and the packing never reads one string as a prefix of another. #[test] - fn clones_diverge_after_independent_absorbs() { - let mut left = PoseidonSponge::new(); - left.absorb(b"shared"); - let mut right = left.clone(); - left.absorb(b"left"); - right.absorb(b"right"); - let mut x = [0u8; 32]; - let mut y = [0u8; 32]; - left.squeeze(&mut x); - right.squeeze(&mut y); - assert_ne!(x, y); + fn byte_packing_separates_lengths() { + let strings: [&[u8]; 6] = [&[], &[0], &[0, 0], &[0; 31], &[0; 32], &[0; 62]]; + for (i, a) in strings.iter().enumerate() { + for b in &strings[i + 1..] { + let (a, b) = (pack_bytes(a), pack_bytes(b)); + assert_ne!(a, b); + assert!(!b.starts_with(&a) && !a.starts_with(&b)); + } + } + } + + fn squeeze_after(input: &[FrUnit]) -> [FrUnit; 4] { + let mut sponge = PoseidonSponge::init(&[7u8; 32]); + sponge.absorb(input); + sponge.squeeze_array::<4>() + } + + #[test] + fn absorb_and_squeeze_are_associative() { + let input: Vec = (0..8).map(unit).collect(); + let mut split = PoseidonSponge::init(&[7u8; 32]); + split + .absorb(&input[..1]) + .absorb(&input[1..5]) + .absorb(&input[5..]); + let mut first = [FrUnit::ZERO; 1]; + let mut rest = [FrUnit::ZERO; 3]; + split.squeeze(&mut first).squeeze(&mut rest); + assert_eq!([first.as_slice(), &rest].concat(), squeeze_after(&input)); + } + + #[test] + fn distinct_inputs_and_sessions_diverge() { + assert_ne!(squeeze_after(&[unit(0)]), squeeze_after(&[unit(1)])); + assert_ne!(squeeze_after(&[unit(0)]), squeeze_after(&[unit(0); 2])); + let mut other = PoseidonSponge::init(&[8u8; 32]); + other.absorb(&[unit(0)]); + assert_ne!(other.squeeze_array::<4>(), squeeze_after(&[unit(0)])); + } + + #[test] + fn clones_resume_a_partially_consumed_rate_block() { + let mut original = PoseidonSponge::init(&[1u8; 32]); + original.absorb(&[unit(5)]); + let mut prefix = [FrUnit::ZERO; 2]; + original.squeeze(&mut prefix); + let mut cloned = original.clone(); + assert_eq!(original.squeeze_array::<5>(), cloned.squeeze_array::<5>()); } #[test] - fn ratchet_advances_the_state_and_discards_pending_output() { - let mut baseline_sponge = PoseidonSponge::new(); - baseline_sponge.absorb(b"m"); - let mut baseline = [0u8; 32]; - baseline_sponge.squeeze(&mut baseline); - - let mut a = PoseidonSponge::new(); - let mut b = PoseidonSponge::new(); - a.absorb(b"m").ratchet(); - b.absorb(b"m").ratchet(); - let mut x = [0u8; 32]; - let mut y = [0u8; 32]; - a.squeeze(&mut x); - b.squeeze(&mut y); - assert_eq!(x, y, "ratchet must be deterministic"); - assert_ne!(x, baseline, "ratchet must advance the state"); - - // A ratchet after a partial squeeze must not resume the pending - // block: the remaining 27 bytes of the pre-ratchet block would be - // `baseline[5..]`. - let mut partial = PoseidonSponge::new(); - partial.absorb(b"m"); - let mut consumed = [0u8; 5]; - partial.squeeze(&mut consumed); - assert_eq!(consumed, baseline[..5], "fixture must share the block"); - partial.ratchet(); - let mut post_ratchet = [0u8; 27]; - partial.squeeze(&mut post_ratchet); - assert_ne!( - post_ratchet[..], - baseline[5..], - "pending squeeze bytes must be discarded by ratchet" + fn pow_check_reads_the_low_word_of_one_element() { + assert_eq!( + FrUnit::low_word(&[unit(0x1234_5678_9abc)]), + Some(0x5678_9abc) ); + assert_eq!(FrUnit::low_word(&[]), None); + assert_eq!(FrUnit::low_word(&[unit(0); 2]), None); } } diff --git a/crates/jolt-transcript/src/pow.rs b/crates/jolt-transcript/src/pow.rs index 6589d70105..23ae8a528d 100644 --- a/crates/jolt-transcript/src/pow.rs +++ b/crates/jolt-transcript/src/pow.rs @@ -2,11 +2,15 @@ //! //! The prover //! sends a nonce below `2^(bits + 7)` as canonical LEB128, one byte per prover -//! message, and both parties squeeze `ceil(bits / 8)` bytes and accept iff -//! their low `bits` bits are zero. The step protects the verifier messages +//! message, and both parties squeeze a check and accept iff its low `bits` +//! bits are zero. On a byte sponge the check is `ceil(bits / 8)` +//! little-endian bytes; on `PoseidonSponge` it is one element, read as its +//! canonical integer ([`SpongeUnit`]). The step protects the verifier messages //! that follow it, up to the next prover message. The check is squeezed in one -//! call; on every sponge here that gives the same bytes as squeezing it one -//! byte at a time, because each buffers its squeeze output. +//! call; on every sponge here that gives the same units as squeezing it one +//! unit at a time, because each buffers its squeeze output. +//! +//! [`SpongeUnit`]: crate::SpongeUnit //! //! The same layout, search primitive and probe are proposed for spongefish in //! arkworks-rs/spongefish#301 (`PowTranscriptExt::sponge_pow`, @@ -32,27 +36,28 @@ use std::sync::atomic::{AtomicU64, Ordering}; #[cfg(feature = "prover-grinding")] use spongefish::DuplexSpongeInterface; -use crate::MAX_POW_BITS; +use crate::{SpongeUnit, MAX_POW_BITS}; #[cfg(feature = "prover-grinding")] /// Evaluates the transcript on a candidate prover message without sending it. /// /// A probe is [`Sync`], so a search may run on /// several threads at once. -pub trait Probe: Sync { - /// Writes into `out` the bytes the transcript would squeeze right after +pub trait Probe: Sync { + /// Writes into `out` the units the transcript would squeeze right after /// absorbing `candidate`. The transcript itself is unchanged. - fn probe(&self, candidate: &[u8], out: &mut [u8]); + fn probe(&self, candidate: &[u8], out: &mut [U]); } /// A copy of the prover's public sponge, for searching over a prover message: /// a proof-of-work nonce, or a retry counter whose following challenges must /// satisfy a prover-side condition. /// -/// A probe absorbs the candidate one byte per absorb call, exactly as sending -/// it as single-byte prover messages does. For the Keccak and Blake2b sponges -/// this equals one absorb of the whole candidate; [`PoseidonSponge`] absorbs a -/// length with every call, so for it only the byte-by-byte form matches. +/// A probe absorbs the candidate one byte at a time, each byte as a one-byte +/// message, exactly as sending it as single-byte prover messages does. For the +/// Keccak and Blake2b sponges this equals one absorb of the whole candidate; +/// [`PoseidonSponge`] packs a length with every message, so for it only the +/// byte-by-byte form matches. /// /// It holds no private randomness, writes no proof bytes and cannot restore /// or advance the live prover state. @@ -72,11 +77,14 @@ impl MessageSearch { } #[cfg(feature = "prover-grinding")] -impl + Sync> Probe for MessageSearch { - fn probe(&self, candidate: &[u8], out: &mut [u8]) { +impl Probe for MessageSearch +where + H: DuplexSpongeInterface + Sync, +{ + fn probe(&self, candidate: &[u8], out: &mut [H::U]) { let mut sponge = self.sponge.clone(); for byte in candidate { - let _ = sponge.absorb(core::slice::from_ref(byte)); + let _ = sponge.absorb(H::U::encode_message(byte).as_ref()); } let _ = sponge.squeeze(out); } @@ -117,20 +125,11 @@ impl SpongePow { (self.bits as usize + 13) / 7 } - /// `ceil(bits / 8)` squeezed bytes carry the checked bits, at most 4. - pub(crate) const fn word_len(self) -> usize { - (self.bits as usize).div_ceil(8) - } - - /// The low `bits` bits of the little-endian check bytes are zero. - /// Rejects check bytes of the wrong length. - pub(crate) fn accepts(self, check: &[u8]) -> bool { - let mut word = [0u8; 4]; - match word.get_mut(..self.word_len()) { - Some(prefix) if prefix.len() == check.len() => prefix.copy_from_slice(check), - _ => return false, - } - u32::from_le_bytes(word) & ((1u32 << self.bits) - 1) == 0 + /// The low `bits` bits of the check are zero ([`SpongeUnit::low_word`]). + /// Rejects a check of the wrong length. + pub(crate) fn accepts(self, check: &[U]) -> bool { + check.len() == U::check_len(self.bits) + && U::low_word(check).is_some_and(|word| word & ((1u32 << self.bits) - 1) == 0) } /// Reads a nonce byte by byte through `next`. Returns `None` unless its @@ -155,11 +154,11 @@ impl SpongePow { Ok(None) } - /// Whether sending `nonce` makes the next squeezed bytes pass the check. + /// Whether sending `nonce` makes the next squeezed check pass. #[cfg(feature = "prover-grinding")] - fn accepts_nonce(self, probe: &impl Probe, nonce: u32) -> bool { - let mut check = [0u8; 4]; - let Some(out) = check.get_mut(..self.word_len()) else { + fn accepts_nonce(self, probe: &impl Probe, nonce: u32) -> bool { + let mut check = [U::ZERO; 4]; + let Some(out) = check.get_mut(..U::check_len(self.bits)) else { return false; }; probe.probe(Leb128::new(nonce).as_bytes(), out); @@ -172,7 +171,7 @@ impl SpongePow { /// calling thread. Either way the result is the least accepted nonce, so /// the proof does not depend on the thread count. #[cfg(feature = "prover-grinding")] - pub(crate) fn search(self, probe: &impl Probe) -> Option { + pub(crate) fn search(self, probe: &impl Probe) -> Option { let last = self.last_nonce(); // `u64::MAX` is no nonce, so it marks "not found". let least = AtomicU64::new(u64::MAX); @@ -197,7 +196,14 @@ impl SpongePow { /// increasing order and lowers `least` to the first one accepted. Stops /// once it cannot beat `least`, which other scans may lower meanwhile. #[cfg(feature = "prover-grinding")] - fn scan(self, probe: &impl Probe, first: u64, stride: usize, last: u32, least: &AtomicU64) { + fn scan( + self, + probe: &impl Probe, + first: u64, + stride: usize, + last: u32, + least: &AtomicU64, + ) { let nonces = (first..=u64::from(last)) .step_by(stride) .filter_map(|nonce| u32::try_from(nonce).ok()); @@ -245,7 +251,7 @@ impl Leb128 { #[cfg(test)] #[expect(clippy::unwrap_used, reason = "tests")] mod tests { - use super::{SpongePow, MAX_POW_BITS}; + use super::{SpongePow, SpongeUnit, MAX_POW_BITS}; #[test] fn difficulty_bounds() { @@ -258,7 +264,7 @@ mod tests { fn layout() { let layout = |bits| { let pow = SpongePow::new(bits).unwrap(); - (pow.last_nonce(), pow.max_nonce_len(), pow.word_len()) + (pow.last_nonce(), pow.max_nonce_len(), u8::check_len(bits)) }; assert_eq!(layout(1), (255, 2, 1)); assert_eq!(layout(8), ((1 << 15) - 1, 3, 1)); diff --git a/crates/jolt-transcript/src/prover.rs b/crates/jolt-transcript/src/prover.rs index 991bce7aeb..9ec27381ea 100644 --- a/crates/jolt-transcript/src/prover.rs +++ b/crates/jolt-transcript/src/prover.rs @@ -1,8 +1,9 @@ //! Prover-only operations on `spongefish::ProverState`. +use crate::codec::Units; #[cfg(feature = "prover-grinding")] use crate::pow::{Leb128, MessageSearch, SpongePow}; -use crate::{prover_transcript, Transcript}; +use crate::{prover_transcript, SpongeUnit, Transcript}; #[cfg(feature = "prover-grinding")] use spongefish::VerificationError; use spongefish::{DuplexSpongeInit, DuplexSpongeInterface, Encoding, ProverState}; @@ -11,9 +12,10 @@ use spongefish::{DuplexSpongeInit, DuplexSpongeInterface, Encoding, ProverState} /// and the proof-of-work nonce searches. pub trait ProverTranscript: Transcript { /// The duplex sponge behind the transcript. - type Sponge: DuplexSpongeInterface + Sync; + type Sponge: DuplexSpongeInterface + Sync; - /// Absorbs `msg` and appends its NARG-serialized form for verifier replay. + /// Absorbs `msg` and appends its byte encoding to the NARG string for + /// verifier replay. fn prover_message(&mut self, msg: &M); /// Bytes accumulated in the NARG so far. @@ -33,9 +35,11 @@ pub trait ProverTranscript: Transcript { /// /// For `bits == 0` this does nothing. For `1 <= bits <= MAX_POW_BITS`, the /// prover sends the least nonce below `2^(bits + 7)` for which the next - /// `ceil(bits / 8)` squeezed bytes have their low `bits` bits zero, as - /// canonical LEB128 with one byte per prover message, and squeezes those - /// bytes. The layout is the one proposed in arkworks-rs/spongefish#301. + /// squeezed check has its low `bits` bits zero, as canonical LEB128 with + /// one byte per prover message, and squeezes the check. On a byte sponge + /// the check is `ceil(bits / 8)` little-endian bytes, the layout proposed + /// in arkworks-rs/spongefish#301; on `PoseidonSponge` it is one element's + /// canonical integer. /// /// The step protects the verifier messages drawn after it, up to the next /// prover message. The protocol must draw the challenges it protects @@ -85,7 +89,7 @@ pub trait ProverInit: ProverTranscript + Sized { impl ProverInit for ProverState where - H: DuplexSpongeInit + Sync, + H: DuplexSpongeInit + Sync, { fn init(session: &[u8], statement: &(impl Encoding + ?Sized)) -> Self { prover_transcript::(session, statement) @@ -94,13 +98,13 @@ where impl ProverTranscript for ProverState where - H: DuplexSpongeInterface + Sync, + H: DuplexSpongeInterface + Sync, R: DuplexSpongeInit, { type Sponge = H; fn prover_message(&mut self, msg: &M) { - ProverState::prover_message(self, msg); + ProverState::prover_message(self, &Units(msg)); } fn narg_string(&self) -> &[u8] { @@ -122,9 +126,10 @@ where .search(&self.message_search()) .ok_or(VerificationError)?; for byte in Leb128::new(nonce).as_bytes() { - ProverState::prover_message(self, byte); + ProverTranscript::prover_message(self, byte); } - if ProverState::verifier_message_as(self, pow.word_len(), |check| pow.accepts(check)) { + if ProverState::verifier_message_as(self, H::U::check_len(bits), |check| pow.accepts(check)) + { Ok(()) } else { Err(VerificationError) diff --git a/crates/jolt-transcript/src/scripted.rs b/crates/jolt-transcript/src/scripted.rs index 6e4eabc92b..bd0de892f6 100644 --- a/crates/jolt-transcript/src/scripted.rs +++ b/crates/jolt-transcript/src/scripted.rs @@ -74,7 +74,8 @@ impl Script for VecDeque { /// otherwise fail at its first proof-of-work step. It can overstate `T`'s /// challenge set and lets the caller pick challenges, so it gives no soundness /// and must never back a real proof; the `test-utils` feature that exports it -/// is for dev-dependencies. +/// is for dev-dependencies. It wraps byte-sponge transcripts only, since its +/// script sees check bytes. pub struct ScriptedTranscript> { inner: T, script: S, @@ -125,7 +126,9 @@ impl ScriptedTranscript { } } -impl> Sealed for ScriptedTranscript { +impl, F, S: Script> Sealed for ScriptedTranscript { + type Unit = u8; + fn absorb(&mut self, message: &M) { self.events .push(TranscriptEvent::Absorb(message.encode().as_ref().to_vec())); @@ -149,7 +152,7 @@ impl> Sealed for ScriptedTranscript { impl FieldTranscript for ScriptedTranscript where - T: FieldTranscript, + T: FieldTranscript, F: Clone, S: Script, { @@ -168,7 +171,9 @@ where } } -impl> ProverTranscript for ScriptedTranscript { +impl, F, S: Script> ProverTranscript + for ScriptedTranscript +{ type Sponge = T::Sponge; fn prover_message(&mut self, msg: &M) { @@ -197,7 +202,9 @@ impl> ProverTranscript for ScriptedTranscri } } -impl> VerifierTranscript for ScriptedTranscript { +impl, F, S: Script> VerifierTranscript + for ScriptedTranscript +{ fn prover_message(&mut self) -> Result { let message = self.inner.prover_message::()?; self.events @@ -214,7 +221,7 @@ impl> VerifierTranscript for ScriptedTran } } -impl<'a, T: VerifierInit<'a>, F, S: Script + Default> VerifierInit<'a> +impl<'a, T: VerifierInit<'a, Unit = u8>, F, S: Script + Default> VerifierInit<'a> for ScriptedTranscript { fn init(session: &[u8], statement: &(impl Encoding + ?Sized), narg: &'a [u8]) -> Self { @@ -222,7 +229,9 @@ impl<'a, T: VerifierInit<'a>, F, S: Script + Default> VerifierInit<'a> } } -impl + Default> ProverInit for ScriptedTranscript { +impl, F, S: Script + Default> ProverInit + for ScriptedTranscript +{ fn init(session: &[u8], statement: &(impl Encoding + ?Sized)) -> Self { Self::scripted_by(T::init(session, statement), S::default()) } diff --git a/crates/jolt-transcript/src/setup.rs b/crates/jolt-transcript/src/setup.rs index d9b9ddc440..4319eb3338 100644 --- a/crates/jolt-transcript/src/setup.rs +++ b/crates/jolt-transcript/src/setup.rs @@ -7,6 +7,8 @@ use spongefish::instantiations::TurboShake128; use spongefish::{DuplexSpongeInit, Encoding, ProverState, SessionId, VerifierState}; +use crate::codec::{SpongeUnit, Units}; + /// Crate-wide protocol identifier (ASCII left, zero-padded to 64 bytes). /// [`session_id`] derives the spongefish session identifier from it. pub const PROTOCOL_ID: [u8; 64] = pad_id(b"a16z/jolt-transcript/v1"); @@ -31,7 +33,7 @@ const fn pad_id(src: &[u8]) -> [u8; 64] { /// The spongefish instance: the session and the statement, each with an /// eight-byte little-endian length. Never empty, so spongefish's constructors /// accept it, and self-delimiting, so the statement cannot run into the first -/// message. +/// message. The sponge absorbs it as one message ([`SpongeUnit`]). struct Instance<'a, S: ?Sized> { session: &'a [u8], statement: &'a S, @@ -80,9 +82,9 @@ impl Encoding for Instance<'_, S> { #[must_use] pub fn prover_transcript(session: &[u8], statement: &(impl Encoding + ?Sized)) -> ProverState where - H: DuplexSpongeInit, + H: DuplexSpongeInit, { - ProverState::new(&session_id(), &Instance { session, statement }) + ProverState::new(&session_id(), &Units(Instance { session, statement })) } /// Build a verifier transcript bound to `session` and `statement` over @@ -96,9 +98,9 @@ pub fn verifier_transcript<'a, H>( narg: &'a [u8], ) -> VerifierState<'a, H> where - H: DuplexSpongeInit, + H: DuplexSpongeInit, { - VerifierState::new(&session_id(), &Instance { session, statement }, narg) + VerifierState::new(&session_id(), &Units(Instance { session, statement }), narg) } /// Derive the spongefish session identifier from [`PROTOCOL_ID`] using diff --git a/crates/jolt-transcript/src/transcript.rs b/crates/jolt-transcript/src/transcript.rs index 25c6b1933d..9532b554ad 100644 --- a/crates/jolt-transcript/src/transcript.rs +++ b/crates/jolt-transcript/src/transcript.rs @@ -13,34 +13,39 @@ //! for a public message and for a prover message, so the challenges are those //! of the same protocol with the field sent through the NARG string. //! -//! The sponge absorbs a message's bytes with no length or separator, so the -//! messages absorbed between two squeezes must decode uniquely from their +//! A byte sponge absorbs a message's bytes with no length or separator, so +//! the messages absorbed between two squeezes must decode uniquely from their //! concatenation. Every [`Encoding`] a protocol absorbs is therefore of fixed //! width, or of a length fixed by bound data, or carries its own count, as //! [`append_values`](Transcript::append_values) and [`BytesMsg`] do. +//! `PoseidonSponge` frames each message itself ([`SpongeUnit`]). use jolt_field::Ring; use spongefish::{ - DuplexSpongeInit, DuplexSpongeInterface, Encoding, ProverState, Transcript as _, + DuplexSpongeInit, DuplexSpongeInterface, Encoding, FromNarg, NargReader, ProverState, Unit, VerificationError, VerifierState, }; -use crate::challenge::message_bytes; -use crate::codec::{BytesMsg, Label, LabelWithCount}; +use crate::codec::{BytesMsg, Label, LabelWithCount, SpongeUnit, Units}; use crate::pow::{Leb128, SpongePow}; -use crate::{ChallengeCodec, ChallengeSetSize, UniformBytes}; +use crate::{ChallengeSetSize, ChallengeSponge}; use sealed::Sealed; pub(crate) mod sealed { use spongefish::{Encoding, VerificationError}; + use crate::SpongeUnit; + /// The three sponge operations every transcript method is built from. pub trait Sealed { + /// The sponge's alphabet. + type Unit: SpongeUnit; + /// Absorbs `message` as a public message. fn absorb(&mut self, message: &M); - /// Squeezes `len` bytes and decodes them with `decode`. - fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[u8]) -> T) -> T; + /// Squeezes `len` units and decodes them with `decode`. + fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[Self::Unit]) -> T) -> T; /// `Ok` when `ok` holds. On failure the verifier also poisons its /// state, so later reads and `check_eof` fail. @@ -84,8 +89,9 @@ pub trait Transcript: Sealed { /// For `bits == 0` the proof carries no nonce and the transcript is /// unchanged. For `1 <= bits <= MAX_POW_BITS` the nonce must be at most /// `2^(bits + 7) - 1`; its canonical LEB128 encoding is absorbed one byte - /// per public message, then `ceil(bits / 8)` bytes are squeezed and their - /// low `bits` bits must be zero. This is the layout of + /// per public message, then the check is squeezed and its low `bits` bits + /// must be zero: `ceil(bits / 8)` little-endian bytes on a byte sponge, one + /// element's canonical integer on `PoseidonSponge`. This is the layout of /// [`VerifierTranscript::check_grind`](crate::VerifierTranscript::check_grind) /// with the nonce absorbed as a public message instead of read from the /// NARG string; the sponge absorbs the same bytes. @@ -112,7 +118,7 @@ pub trait Transcript: Sealed { for byte in Leb128::new(nonce).as_bytes() { self.absorb(byte); } - let passes = self.squeeze(pow.word_len(), |check| pow.accepts(check)); + let passes = self.squeeze(Self::Unit::check_len(bits), |check| pow.accepts(check)); self.require(passes) } } @@ -121,23 +127,27 @@ impl Transcript for T {} /// A transcript that draws challenges in `F`. pub trait FieldTranscript: Transcript { - /// Size of the set every challenge is drawn from: the codec's - /// [`CHALLENGE_SET_SIZE`](ChallengeCodec::CHALLENGE_SET_SIZE). Prices the - /// proof of work before a challenge ([`grind_bits_for_loss`]). + /// Size of the set every challenge is drawn from + /// ([`ChallengeSponge::CHALLENGE_SET`]). Prices the proof of work before a + /// challenge ([`grind_bits_for_loss`]). /// /// [`grind_bits_for_loss`]: crate::grind_bits_for_loss const CHALLENGE_SET: ChallengeSetSize; - /// Draws `len` challenges as one message of `F`'s [`ChallengeCodec`]. + /// Draws `len` challenges as one message of the sponge's + /// [`ChallengeSponge`] map: `F`'s [`ChallengeCodec`] on a byte sponge, + /// squeezed elements on `PoseidonSponge`. /// /// `len == 0` returns an empty vector without touching the sponge. /// /// # Errors - /// `VerificationError`, before any sponge operation, when the codec does + /// `VerificationError`, before any sponge operation, when the map does /// not support `len` or the message would exceed /// [`MAX_CHALLENGE_BYTES`](crate::MAX_CHALLENGE_BYTES) bytes; otherwise on /// rejection-pool exhaustion (probability at most 2^-161). The verifier's /// state is poisoned then, so `check_eof` fails too. + /// + /// [`ChallengeCodec`]: crate::ChallengeCodec fn challenge_vector(&mut self, len: usize) -> Result, VerificationError>; /// Draws one challenge, as a message of length one. @@ -166,39 +176,38 @@ pub trait FieldTranscript: Transcript { } } -/// One codec message of `len` challenges: the single implementation behind -/// every [`FieldTranscript`]. -pub(crate) fn draw_challenges( - transcript: &mut T, - len: usize, -) -> Result, VerificationError> +/// One message of `len` challenges from sponge `H`: the single +/// implementation behind every [`FieldTranscript`]. +fn draw_challenges(transcript: &mut T, len: usize) -> Result, VerificationError> where - T: Sealed + ?Sized, - F: ChallengeCodec, + T: Sealed + ?Sized, + H: ChallengeSponge, { - // A length the codec rejects poisons the verifier too, so a caller that + // A length the map rejects poisons the verifier too, so a caller that // drops the error still fails `check_eof`. - let bytes = message_bytes::(len); - transcript.require(bytes.is_ok())?; - let bytes = bytes?; + let units = H::challenge_units(len); + transcript.require(units.is_some())?; + let units = units.ok_or(VerificationError)?; if len == 0 { return Ok(Vec::new()); } - let challenges = transcript.squeeze(bytes, |tape| F::decode_challenges(tape, len)); + let challenges = transcript.squeeze(units, |tape| H::decode_challenges(tape, len)); transcript.require(challenges.is_some())?; challenges.ok_or(VerificationError) } impl Sealed for ProverState where - H: DuplexSpongeInterface, + H: DuplexSpongeInterface, R: DuplexSpongeInit, { + type Unit = H::U; + fn absorb(&mut self, message: &M) { - ProverState::public_message(self, message); + ProverState::public_message(self, &Units(message)); } - fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[u8]) -> T) -> T { + fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[H::U]) -> T) -> T { ProverState::verifier_message_as(self, len, decode) } @@ -211,44 +220,68 @@ where } } +/// A prover message that never parses and absorbs nothing: reading it is how +/// [`Sealed::require`] poisons a verifier over any alphabet. Spongefish's +/// `Transcript::check` does the same, but only over bytes. +struct Rejection; + +impl Encoding for Rejection { + fn encode(&self) -> impl AsRef<[U]> { + let empty: [U; 0] = []; + empty + } +} + +impl FromNarg for Rejection { + fn from_narg(_: &mut NargReader<'_>) -> Result { + Err(VerificationError) + } +} + impl Sealed for VerifierState<'_, H> where - H: DuplexSpongeInterface, + H: DuplexSpongeInterface, { + type Unit = H::U; + fn absorb(&mut self, message: &M) { - VerifierState::public_message(self, message); + VerifierState::public_message(self, &Units(message)); } - fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[u8]) -> T) -> T { + fn squeeze(&mut self, len: usize, decode: impl FnOnce(&[H::U]) -> T) -> T { VerifierState::verifier_message_as(self, len, decode) } + /// Neither branch absorbs or squeezes. Reading no messages fails only on a + /// poisoned state; reading a [`Rejection`] always fails and poisons it. fn require(&mut self, ok: bool) -> Result<(), VerificationError> { - self.check(|| ok) + if ok { + self.prover_messages_vec::(0).map(drop) + } else { + self.prover_message::().map(drop) + } } } impl FieldTranscript for ProverState where - H: UniformBytes, + H: ChallengeSponge, R: DuplexSpongeInit, - F: ChallengeCodec, { - const CHALLENGE_SET: ChallengeSetSize = F::CHALLENGE_SET_SIZE; + const CHALLENGE_SET: ChallengeSetSize = H::CHALLENGE_SET; fn challenge_vector(&mut self, len: usize) -> Result, VerificationError> { - draw_challenges(self, len) + draw_challenges::<_, H, F>(self, len) } } impl FieldTranscript for VerifierState<'_, H> where - H: UniformBytes, - F: ChallengeCodec, + H: ChallengeSponge, { - const CHALLENGE_SET: ChallengeSetSize = F::CHALLENGE_SET_SIZE; + const CHALLENGE_SET: ChallengeSetSize = H::CHALLENGE_SET; fn challenge_vector(&mut self, len: usize) -> Result, VerificationError> { - draw_challenges(self, len) + draw_challenges::<_, H, F>(self, len) } } diff --git a/crates/jolt-transcript/src/verifier.rs b/crates/jolt-transcript/src/verifier.rs index c5700acca3..9281f83c0c 100644 --- a/crates/jolt-transcript/src/verifier.rs +++ b/crates/jolt-transcript/src/verifier.rs @@ -1,9 +1,10 @@ //! Verifier-only operations on `spongefish::VerifierState`. -use crate::{pow::SpongePow, verifier_transcript, Transcript}; +use crate::codec::Units; +use crate::transcript::sealed::Sealed as _; +use crate::{pow::SpongePow, verifier_transcript, SpongeUnit, Transcript}; use spongefish::{ - DuplexSpongeInit, DuplexSpongeInterface, Encoding, FromNarg, Transcript as _, - VerificationError, VerifierState, + DuplexSpongeInit, DuplexSpongeInterface, Encoding, FromNarg, VerificationError, VerifierState, }; /// The verifier's side of a [`Transcript`]: messages read from the NARG @@ -20,9 +21,11 @@ pub trait VerifierTranscript: Transcript { /// /// For `bits == 0` this does nothing. For `1 <= bits <= MAX_POW_BITS`, it /// reads a nonce below `2^(bits + 7)` as canonical LEB128, one byte per - /// prover message, then squeezes `ceil(bits / 8)` bytes and requires their - /// low `bits` bits to be zero. The layout is the one proposed in - /// arkworks-rs/spongefish#301. + /// prover message, then squeezes the check and requires its low `bits` + /// bits to be zero. On a byte sponge the check is `ceil(bits / 8)` + /// little-endian bytes, the layout proposed in + /// arkworks-rs/spongefish#301; on `PoseidonSponge` it is one element's + /// canonical integer. /// /// The step protects the verifier messages drawn after it, up to the next /// prover message. The protocol must draw the challenges it protects @@ -74,7 +77,7 @@ pub trait VerifierInit<'a>: VerifierTranscript + Sized { fn init(session: &[u8], statement: &(impl Encoding + ?Sized), narg: &'a [u8]) -> Self; } -impl<'a, H: DuplexSpongeInit> VerifierInit<'a> for VerifierState<'a, H> { +impl<'a, H: DuplexSpongeInit> VerifierInit<'a> for VerifierState<'a, H> { fn init(session: &[u8], statement: &(impl Encoding + ?Sized), narg: &'a [u8]) -> Self { verifier_transcript::(session, statement, narg) } @@ -82,10 +85,10 @@ impl<'a, H: DuplexSpongeInit> VerifierInit<'a> for VerifierState<'a, H> impl VerifierTranscript for VerifierState<'_, H> where - H: DuplexSpongeInterface, + H: DuplexSpongeInterface, { fn prover_message(&mut self) -> Result { - VerifierState::prover_message::(self) + VerifierState::prover_message::>(self).map(|units| units.0) } fn check_grind(&mut self, bits: u32) -> Result<(), VerificationError> { @@ -93,15 +96,16 @@ where return Ok(()); } let Some(pow) = SpongePow::new(bits) else { - return self.check(|| false); + return self.require(false); }; // Failed reads poison the state themselves; failed checks poison it - // through `check`, so `check_eof` fails afterwards too. - let nonce = pow.read_nonce(|| VerifierState::prover_message::(self))?; - self.check(|| nonce.is_some())?; - let passes = - VerifierState::verifier_message_as(self, pow.word_len(), |check| pow.accepts(check)); - self.check(|| passes) + // through `require`, so `check_eof` fails afterwards too. + let nonce = pow.read_nonce(|| VerifierTranscript::prover_message::(self))?; + self.require(nonce.is_some())?; + let passes = VerifierState::verifier_message_as(self, H::U::check_len(bits), |check| { + pow.accepts(check) + }); + self.require(passes) } fn check_eof(self) -> Result<(), VerificationError> { diff --git a/crates/jolt-transcript/tests/poseidon_tests.rs b/crates/jolt-transcript/tests/poseidon_tests.rs index fb591915c8..ac1739d255 100644 --- a/crates/jolt-transcript/tests/poseidon_tests.rs +++ b/crates/jolt-transcript/tests/poseidon_tests.rs @@ -1,66 +1,224 @@ -//! Tests for the Poseidon sponge. -//! -//! Poseidon's squeezed bytes are not uniform, so it draws no field -//! challenges; these tests exercise it as a byte sponge. +//! Tests for the Poseidon transcript, which packs each message's bytes into +//! BN254 `Fr` elements and draws `Fr` challenges as squeezed elements. +#![expect(clippy::unwrap_used, reason = "tests")] + +use ark_bn254::Fr as BnFr; +use ark_ff::PrimeField; +use jolt_field::{CanonicalBytes, Fr}; use jolt_transcript::{ - prover_transcript, verifier_transcript, PoseidonSponge, ProverState, Transcript, + prover_transcript, session_id, verifier_transcript, BytesMsg, ChallengeSponge, FieldTranscript, + FrUnit, PoseidonSponge, ProverTranscript, Transcript, VerifierTranscript, MAX_CHALLENGE_BYTES, }; +use spongefish::{DuplexSpongeInit, DuplexSpongeInterface, ProverState}; + +const SESSION: &[u8] = b"jolt/poseidon/v1"; +const INSTANCE: [u8; 32] = [0x5a; 32]; -const SESSION: &[u8] = b"jolt-transcript/tests"; +const NATIVE_VECTOR: [[u8; 32]; 2] = [ + [ + 0x3f, 0x85, 0x29, 0x7b, 0xe8, 0x4d, 0x5f, 0xe5, 0xc0, 0x9d, 0x7a, 0xc0, 0x12, 0x57, 0xd6, + 0x90, 0xe4, 0x59, 0xfd, 0xf6, 0x1b, 0x92, 0x1e, 0x94, 0x7f, 0x03, 0x1d, 0xe0, 0xc5, 0xdf, + 0x29, 0x08, + ], + [ + 0xfa, 0x74, 0x0d, 0x13, 0x9c, 0x87, 0xb9, 0xf1, 0xd8, 0x42, 0xe8, 0xc5, 0xa0, 0x6a, 0x63, + 0xcc, 0xd7, 0xc5, 0xb4, 0xeb, 0x85, 0xf0, 0x54, 0xf7, 0x4e, 0x24, 0x6d, 0xed, 0x8c, 0x6e, + 0xeb, 0x27, + ], +]; -fn squeeze_after)>(f: F) -> [u8; 32] { - let mut transcript = prover_transcript::(SESSION, b"Jolt"); - f(&mut transcript); - transcript.verifier_message::<[u8; 32]>() +fn le_bytes(value: Fr) -> [u8; 32] { + let mut out = [0u8; 32]; + value.to_bytes_le(&mut out); + out } -#[test] -fn test_poseidon_determinism_and_order() { - let a = squeeze_after(|t| { - t.append(&1u64); - t.append(&2u64); - }); - let b = squeeze_after(|t| { - t.append(&1u64); - t.append(&2u64); - }); - let c = squeeze_after(|t| { - t.append(&2u64); - t.append(&1u64); - }); - assert_eq!(a, b); - assert_ne!(a, c); +fn fr(value: u64) -> Fr { + Fr::from(value) +} + +fn challenges(transcript: &mut impl FieldTranscript, len: usize) -> Vec { + transcript.challenge_vector(len).unwrap() } +/// A public message and a prover message. +fn sample_prover() -> ProverState { + let mut prover = prover_transcript::(SESSION, &INSTANCE); + prover.append(&BytesMsg(b"public".to_vec())); + ProverTranscript::prover_message(&mut prover, &BytesMsg(b"jolt".to_vec())); + prover +} + +/// Pins the session and instance packing, the message packing, the NARG +/// bytes and two native challenges. #[test] -fn test_poseidon_prover_and_verifier_agree() { - let mut prover = prover_transcript::(SESSION, b"Jolt"); - let mut verifier = verifier_transcript::(SESSION, b"Jolt", &[]); - prover.append_bytes(b"msg"); - verifier.append_bytes(b"msg"); +fn native_known_vector() { + let mut prover = sample_prover(); + let drawn = challenges(&mut prover, 2); + let mut narg = 4u64.to_le_bytes().to_vec(); + narg.extend_from_slice(b"jolt"); + assert_eq!(prover.narg_string(), narg); assert_eq!( - prover.verifier_message::<[u8; 32]>(), - verifier.verifier_message::<[u8; 32]>() + drawn.iter().copied().map(le_bytes).collect::>(), + NATIVE_VECTOR ); } +/// A byte string packed as the module documents: the length, then 31-byte +/// little-endian chunks. +fn packed(bytes: &[u8]) -> Vec { + let mut units = vec![FrUnit::from(fr(bytes.len() as u64))]; + for chunk in bytes.chunks(31) { + units.push(FrUnit::from(Fr::from(BnFr::from_le_bytes_mod_order(chunk)))); + } + units +} + +/// `bytes` with an eight-byte little-endian length in front. +fn framed(bytes: &[u8]) -> Vec { + [&(bytes.len() as u64).to_le_bytes()[..], bytes].concat() +} + +/// Replays the sample transcript on a bare sponge from the documented +/// packing: the instance is one packed message, each message is packed on its +/// own, and challenges are the squeezed elements themselves. +#[test] +fn challenges_are_squeezed_elements_after_the_documented_packing() { + let instance = [framed(SESSION), framed(&INSTANCE)].concat(); + let mut sponge = PoseidonSponge::init(session_id().as_bytes()); + let _ = sponge + .absorb(&packed(&instance)) + .absorb(&packed(&framed(b"public"))) + .absorb(&packed(&framed(b"jolt"))); + let expected: Vec = sponge.squeeze_array::<3>().map(Fr::from).to_vec(); + + let mut prover = sample_prover(); + assert_eq!(challenges(&mut prover, 3), expected); +} + #[test] -fn test_poseidon_known_vector() { - let got = squeeze_after(|t| t.append(&12345u64)); - // Pins the session derived from PROTOCOL_ID, the instance absorb, one - // `u64` public message and a 32-byte squeeze. +fn round_trips_byte_and_scalar_messages() { + let bodies: [&[u8]; 5] = [b"", b"x", &[0xab; 23], &[0xcd; 24], &[0xef; 100]]; + let scalars = [fr(0), fr(1), -fr(1), fr(u64::MAX)]; + + let mut prover = prover_transcript::(SESSION, &INSTANCE); + prover.append(&le_bytes(fr(3))); + let mut sent = Vec::new(); + for body in bodies { + ProverTranscript::prover_message(&mut prover, &BytesMsg(body.to_vec())); + sent.push(challenges(&mut prover, 1)); + } + for scalar in scalars { + ProverTranscript::prover_message(&mut prover, &le_bytes(scalar)); + } + sent.push(challenges(&mut prover, 5)); + assert!(challenges(&mut prover, 0).is_empty()); + + let mut verifier = + verifier_transcript::(SESSION, &INSTANCE, prover.narg_string()); + verifier.append(&le_bytes(fr(3))); + let mut read = Vec::new(); + for body in bodies { + let message = VerifierTranscript::prover_message::(&mut verifier).unwrap(); + assert_eq!(message.0, body); + read.push(challenges(&mut verifier, 1)); + } + for scalar in scalars { + let message = VerifierTranscript::prover_message::<[u8; 32]>(&mut verifier).unwrap(); + assert_eq!(message, le_bytes(scalar)); + } + read.push(challenges(&mut verifier, 5)); + assert_eq!(read, sent); + verifier.check_eof().unwrap(); +} + +fn draw_after( + session: &[u8], + instance: [u8; 32], + absorb: impl FnOnce(&mut ProverState), +) -> Vec { + let mut prover = prover_transcript::(session, &instance); + absorb(&mut prover); + challenges(&mut prover, 1) +} + +/// A different message, session or instance changes the challenges. Each +/// message is packed on its own, so splitting raw bytes across messages, or +/// appending a zero byte, does too, even without a length in the encoding. +#[test] +fn challenges_bind_messages_session_and_instance() { + let base = draw_after(SESSION, INSTANCE, |t| t.append(&[1u8, 2])); + let others = [ + draw_after(SESSION, INSTANCE, |t| { + t.append(&[1u8]); + t.append(&[2u8]); + }), + draw_after(SESSION, INSTANCE, |t| t.append(&[1u8, 2, 0])), + draw_after(SESSION, INSTANCE, |t| t.append(&[1u8, 3])), + draw_after(b"jolt/poseidon/v2", INSTANCE, |t| t.append(&[1u8, 2])), + draw_after(SESSION, [0x5b; 32], |t| t.append(&[1u8, 2])), + ]; + for other in others { + assert_ne!(base, other); + } +} + +/// A truncated message is rejected and poisons the verifier, so later draws +/// and `check_eof` fail. Trailing bytes fail `check_eof`. +#[test] +fn rejects_truncated_and_trailing_input() { + let mut prover = prover_transcript::(SESSION, &INSTANCE); + ProverTranscript::prover_message(&mut prover, &BytesMsg(b"payload".to_vec())); + let narg = prover.narg_string().to_vec(); + + for cut in 1..=narg.len() { + let truncated = &narg[..narg.len() - cut]; + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, truncated); + assert!(VerifierTranscript::prover_message::(&mut verifier).is_err()); + assert!(FieldTranscript::::challenge_vector(&mut verifier, 1).is_err()); + assert!(verifier.check_eof().is_err()); + } + + let trailing = [narg.as_slice(), &[0]].concat(); + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, &trailing); + let message = VerifierTranscript::prover_message::(&mut verifier).unwrap(); + assert_eq!(message.0, b"payload"); + assert!(verifier.check_eof().is_err()); +} + +/// The cap counts 32 bytes per element; a larger request fails before +/// touching the sponge and poisons the verifier. +#[test] +fn oversized_challenge_messages_fail_and_poison() { + let cap = MAX_CHALLENGE_BYTES / 32; assert_eq!( - hex(&got), - "788647c07eed8851fb17d6eeb2682d72ccca468c7ebd9f49a071712305eb8817", - "Poseidon known-vector regression" + >::challenge_units(cap), + Some(cap) ); + for len in [cap + 1, usize::MAX] { + assert_eq!( + >::challenge_units(len), + None + ); + + let mut prover = prover_transcript::(SESSION, &INSTANCE); + assert!(FieldTranscript::::challenge_vector(&mut prover, len).is_err()); + let mut fresh = prover_transcript::(SESSION, &INSTANCE); + assert_eq!(challenges(&mut prover, 1), challenges(&mut fresh, 1)); + + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, &[]); + assert!(FieldTranscript::::challenge_vector(&mut verifier, len).is_err()); + assert!(verifier.check_eof().is_err()); + } } -fn hex(bytes: &[u8]) -> String { - use std::fmt::Write; - bytes.iter().fold(String::new(), |mut hex, byte| { - let _ = write!(hex, "{byte:02x}"); - hex - }) +/// Native challenges are full-width: across many draws the top half takes +/// values a 128-bit challenge never would. +#[test] +fn challenges_are_full_width() { + let mut prover = prover_transcript::(SESSION, &INSTANCE); + assert!(challenges(&mut prover, 64) + .iter() + .any(|&c| le_bytes(c)[16..].iter().any(|&b| b != 0))); } diff --git a/crates/jolt-transcript/tests/pow_tests.rs b/crates/jolt-transcript/tests/pow_tests.rs index 7446e29256..1831e761a9 100644 --- a/crates/jolt-transcript/tests/pow_tests.rs +++ b/crates/jolt-transcript/tests/pow_tests.rs @@ -6,10 +6,11 @@ use jolt_field::{CanonicalBytes, Fr, Prime128Offset275, F8}; use jolt_transcript::FieldTranscript; use jolt_transcript::{ prover_transcript, verifier_transcript, Blake2b512, BytesMsg, ChallengeCodec, PoseidonSponge, - Probe, ProverTranscript, VerifierTranscript, MAX_POW_BITS, + Probe, ProverTranscript, SpongeUnit, VerifierTranscript, MAX_POW_BITS, }; use spongefish::{ - instantiations::Keccak, DuplexSpongeInit, DuplexSpongeInterface, ProverState, VerifierState, + instantiations::Keccak, DuplexSpongeInit, DuplexSpongeInterface, ProverState, Unit, + VerifierState, }; use std::fmt::Debug; @@ -158,53 +159,63 @@ fn round_trips() { round_trip::(); } -fn sponge_prover>() -> ProverState { +fn sponge_prover + Sync>() -> ProverState { let mut prover = prover_transcript::(SESSION, &INSTANCE); - prover.prover_message(&BytesMsg(b"before".to_vec())); + ProverTranscript::prover_message(&mut prover, &BytesMsg(b"before".to_vec())); prover } -fn sponge_verifier>(narg: &[u8]) -> VerifierState<'_, H> { +fn sponge_verifier>(narg: &[u8]) -> VerifierState<'_, H> { let mut verifier = verifier_transcript::(SESSION, &INSTANCE, narg); - assert_eq!(verifier.prover_message::().unwrap().0, b"before"); + assert_eq!( + VerifierTranscript::prover_message::(&mut verifier) + .unwrap() + .0, + b"before" + ); verifier } +/// Whether `check` is one check whose low `bits` bits are zero. +fn check_passes(bits: u32, check: &[U]) -> bool { + check.len() == U::check_len(bits) && U::low_word(check).unwrap() & ((1u32 << bits) - 1) == 0 +} + /// Runs `check_grind` on `before ‖ nonce ‖ after` and returns the verifier's -/// result, the bytes it squeezes next and whether the rest still verifies. -fn verify_nonce>( +/// result, the units it squeezes next and whether the rest still verifies. +fn verify_nonce + Sync>( prefix: &[u8], nonce: &[u8], bits: u32, -) -> (bool, [u8; 16], bool) { +) -> (bool, Vec, bool) { let mut after = sponge_prover::(); - after.prover_message(&BytesMsg(b"after".to_vec())); + ProverTranscript::prover_message(&mut after, &BytesMsg(b"after".to_vec())); let suffix = &after.narg_string()[prefix.len()..]; let narg = [prefix, nonce, suffix].concat(); let mut verifier = sponge_verifier::(&narg); let passed = VerifierTranscript::check_grind(&mut verifier, bits).is_ok(); - let next = verifier.verifier_message::<[u8; 16]>(); - let rest = verifier - .prover_message::() + let next = verifier.verifier_message_as(4, <[H::U]>::to_vec); + let rest = VerifierTranscript::prover_message::(&mut verifier) .is_ok_and(|message| message.0 == b"after") && verifier.check_eof().is_ok(); (passed, next, rest) } -/// For each sponge, including Poseidon, which absorbs a length with every -/// call: the search replays the real sends, the honest nonce round-trips and -/// is the least one, a larger passing nonce in range is accepted (proof bytes -/// are not unique), and a passing nonce out of range is rejected and poisons. -fn sponge_checks + Sync>() { +/// For each sponge, including Poseidon, which packs a length with every +/// message and checks the low bits of one squeezed element: the search +/// replays the real sends, the honest nonce round-trips and is the least one, +/// a larger passing nonce in range is accepted (proof bytes are not unique), +/// and a passing nonce out of range is rejected and poisons. +fn sponge_checks + Sync>() { for bits in [1, 2, 9] { - let word_len = (bits as usize).div_ceil(8); + let check_len = H::U::check_len(bits); let base = sponge_prover::(); let prefix = base.narg_string().to_vec(); let search = ProverTranscript::message_search(&base); let probe_passes = |value: u32| { - let mut check = vec![0u8; word_len]; + let mut check = vec![H::U::ZERO; check_len]; search.probe(&leb128(value), &mut check); - passes(bits, &check) + check_passes(bits, &check) }; let mut prover = sponge_prover::(); @@ -212,15 +223,17 @@ fn sponge_checks + Sync>() { let nonce = prover.narg_string()[prefix.len()..].to_vec(); let value = decode(&nonce); assert_eq!(nonce, leb128(value)); - let challenge = prover.verifier_message::<[u8; 16]>(); + let challenge = prover.verifier_message_as(4, <[H::U]>::to_vec); let mut sent = sponge_prover::(); for byte in &nonce { - sent.prover_message(byte); + ProverTranscript::prover_message(&mut sent, byte); } - let mut probed = [0u8; 40]; + let mut probed = vec![H::U::ZERO; 40]; search.probe(&nonce, &mut probed); - assert_eq!(sent.verifier_message::<[u8; 40]>(), probed); + assert_eq!(sent.verifier_message_as(40, <[H::U]>::to_vec), probed); + assert!(check_passes(bits, &probed[..check_len])); + assert_eq!(probed[check_len..check_len + 4], challenge[..]); assert_eq!( verify_nonce::(&prefix, &nonce, bits), diff --git a/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory b/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory index 9fc33fc51f..003d45bd89 100644 --- a/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory +++ b/crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory @@ -116,10 +116,7 @@ jolt-sumcheck::verifier.rs::SumcheckVerifier::verify::append_to_transcript#0::ro jolt-sumcheck::verifier.rs::SumcheckVerifier::verify_committed_round_consistency::append_to_transcript#0::round_proof.append_to_transcript(transcript) jolt-sumcheck::verifier.rs::SumcheckVerifier::verify_compressed::append#0::transcript.append(&LabelWithCount(round_label,coeffs.len()asu64)) jolt-sumcheck::verifier.rs::SumcheckVerifier::verify_compressed::append#1::transcript.append(coeff) -jolt-transcript::poseidon.rs::PoseidonSponge::absorb::absorb_fr_pair#0::self.absorb_fr_pair(len_fr,Fr::zero()) -jolt-transcript::poseidon.rs::PoseidonSponge::absorb::absorb_fr_pair#1::self.absorb_fr_pair(a,b) -jolt-transcript::poseidon.rs::PoseidonSponge::init::absorb#0::sponge.absorb(session_id) -jolt-transcript::pow.rs::MessageSearch::probe::absorb#0::sponge.absorb(core::slice::from_ref(byte)) +jolt-transcript::pow.rs::MessageSearch::probe::absorb#0::sponge.absorb(H::U::encode_message(byte).as_ref()) jolt-transcript::scripted.rs::ScriptedTranscript::absorb::absorb#0::self.inner.absorb(message) jolt-transcript::scripted.rs::ScriptedTranscript::init::init#0::T::init(session,statement,narg) jolt-transcript::scripted.rs::ScriptedTranscript::init::init#1::T::init(session,statement) diff --git a/examples/fibonacci/Cargo.toml b/examples/fibonacci/Cargo.toml index e1d57f0824..5ba746b983 100644 --- a/examples/fibonacci/Cargo.toml +++ b/examples/fibonacci/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [features] transcript-blake2b = ["jolt-sdk/transcript-blake2b"] +transcript-poseidon = ["jolt-sdk/transcript-poseidon"] [dependencies] jolt-sdk = { workspace = true, features = ["host"] } diff --git a/examples/merkle-tree-save/Cargo.toml b/examples/merkle-tree-save/Cargo.toml index bf87593b8d..5764d6860d 100644 --- a/examples/merkle-tree-save/Cargo.toml +++ b/examples/merkle-tree-save/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [features] transcript-blake2b = ["jolt-sdk/transcript-blake2b"] +transcript-poseidon = ["jolt-sdk/transcript-poseidon"] [dependencies] jolt-sdk = { workspace = true, features = ["host"] } diff --git a/jolt-eval/src/invariant/transcript_symmetry.rs b/jolt-eval/src/invariant/transcript_symmetry.rs index ba2717d0e5..a2aca3cf2b 100644 --- a/jolt-eval/src/invariant/transcript_symmetry.rs +++ b/jolt-eval/src/invariant/transcript_symmetry.rs @@ -9,7 +9,8 @@ use spongefish::instantiations::Keccak; use spongefish::{DuplexSpongeInit, VerificationError}; use jolt_transcript::{ - prover_transcript, verifier_transcript, Blake2b512, BytesMsg, PoseidonSponge, + prover_transcript, verifier_transcript, Blake2b512, BytesMsg, ChallengeSponge, FieldTranscript, + PoseidonSponge, ProverTranscript, Transcript, VerifierTranscript, }; use crate::invariant::{CheckError, Invariant, InvariantViolation}; @@ -69,19 +70,22 @@ fn arb_scalar(u: &mut Unstructured<'_>) -> ArbitraryResult { fn run_check(input: &Input) -> Result<(), CheckError> where - H: DuplexSpongeInit, + H: DuplexSpongeInit + ChallengeSponge + Sync, { let mut prover = prover_transcript::(SESSION, &INSTANCE_DIGEST); - let mut prover_challenges: Vec<[u8; 32]> = Vec::new(); + let mut prover_challenges: Vec = Vec::new(); - for op in &input.ops { + for (op_idx, op) in input.ops.iter().enumerate() { match op { - Op::PublicBytes(b) => prover.public_message(&BytesMsg(b.clone())), - Op::PublicScalar(f) => prover.public_message(&scalar_bytes(*f)), - Op::ProverBytes(b) => prover.prover_message(&BytesMsg(b.clone())), - Op::ProverScalar(f) => prover.prover_message(&scalar_bytes(*f)), + Op::PublicBytes(b) => prover.append(&BytesMsg(b.clone())), + Op::PublicScalar(f) => prover.append(&scalar_bytes(*f)), + Op::ProverBytes(b) => { + ProverTranscript::prover_message(&mut prover, &BytesMsg(b.clone())) + } + Op::ProverScalar(f) => ProverTranscript::prover_message(&mut prover, &scalar_bytes(*f)), Op::Challenge => { - let c: [u8; 32] = prover.verifier_message(); + let c = FieldTranscript::::challenge(&mut prover) + .map_err(|e| violation("prover challenge", op_idx, e))?; prover_challenges.push(c); } } @@ -93,26 +97,25 @@ where for (op_idx, op) in input.ops.iter().enumerate() { match op { - Op::PublicBytes(b) => verifier.public_message(&BytesMsg(b.clone())), - Op::PublicScalar(f) => verifier.public_message(&scalar_bytes(*f)), + Op::PublicBytes(b) => verifier.append(&BytesMsg(b.clone())), + Op::PublicScalar(f) => verifier.append(&scalar_bytes(*f)), Op::ProverBytes(expected) => { - let got: BytesMsg = verifier - .prover_message() + let got: BytesMsg = VerifierTranscript::prover_message(&mut verifier) .map_err(|e| violation("prover_message", op_idx, e))?; if got.as_slice() != expected.as_slice() { return Err(mismatch("ProverBytes round-trip", op_idx)); } } Op::ProverScalar(expected) => { - let got: [u8; 32] = verifier - .prover_message() + let got: [u8; 32] = VerifierTranscript::prover_message(&mut verifier) .map_err(|e| violation("prover_message<[u8; 32]>", op_idx, e))?; if got != scalar_bytes(*expected) { return Err(mismatch("ProverScalar round-trip", op_idx)); } } Op::Challenge => { - let verifier_c: [u8; 32] = verifier.verifier_message(); + let verifier_c = FieldTranscript::::challenge(&mut verifier) + .map_err(|e| violation("verifier challenge", op_idx, e))?; if verifier_c != prover_challenges[challenge_idx] { return Err(mismatch("Challenge", op_idx)); } @@ -121,8 +124,7 @@ where } } - verifier - .check_eof() + VerifierTranscript::check_eof(verifier) .map_err(|e| violation("check_eof", input.ops.len(), e))?; Ok(()) } diff --git a/jolt-sdk/Cargo.toml b/jolt-sdk/Cargo.toml index 1e1c3414f8..0b390def03 100644 --- a/jolt-sdk/Cargo.toml +++ b/jolt-sdk/Cargo.toml @@ -70,6 +70,7 @@ guest-verifier = [ malloc-shim = ["jolt-platform/malloc-shim"] transcript-blake2b = ["jolt-transcript/transcript-blake2b"] +transcript-poseidon = ["jolt-transcript/transcript-poseidon"] # Jolt-SDK native features (optional capabilities) # diff --git a/jolt-sdk/src/host_utils.rs b/jolt-sdk/src/host_utils.rs index 7bac463967..897d662244 100644 --- a/jolt-sdk/src/host_utils.rs +++ b/jolt-sdk/src/host_utils.rs @@ -21,14 +21,20 @@ use jolt_prover::dory::stages::stage0::TrustedAdviceCommitment; use jolt_prover::{JoltProverPreprocessing as GenericJoltProverPreprocessing, ProverConfig}; #[cfg(all( any(feature = "host", feature = "guest-verifier"), - feature = "transcript-blake2b" + feature = "transcript-blake2b", + not(feature = "transcript-poseidon") ))] use jolt_transcript::Blake2b512; #[cfg(all( any(feature = "host", feature = "guest-verifier"), - not(feature = "transcript-blake2b") + not(any(feature = "transcript-blake2b", feature = "transcript-poseidon")) ))] use jolt_transcript::Keccak; +#[cfg(all( + any(feature = "host", feature = "guest-verifier"), + feature = "transcript-poseidon" +))] +use jolt_transcript::PoseidonSponge; #[cfg(feature = "host")] use jolt_transcript::ProverState; #[cfg(any(feature = "host", feature = "guest-verifier"))] @@ -42,18 +48,28 @@ use tracer::TracerInlineExpansionProvider; #[cfg(feature = "host")] pub use jolt_host as host; -/// The duplex sponge of the Fiat-Shamir transcript: Keccak, or Blake2b512 with -/// the `transcript-blake2b` feature. +#[cfg(all(feature = "transcript-blake2b", feature = "transcript-poseidon"))] +compile_error!("enable at most one of `transcript-blake2b` and `transcript-poseidon`"); +/// The duplex sponge of the Fiat-Shamir transcript: Keccak by default, +/// Blake2b512 with the `transcript-blake2b` feature, or the BN254 Poseidon +/// sponge with the `transcript-poseidon` feature. #[cfg(all( any(feature = "host", feature = "guest-verifier"), - not(feature = "transcript-blake2b") + not(any(feature = "transcript-blake2b", feature = "transcript-poseidon")) ))] pub type ProtocolSponge = Keccak; #[cfg(all( any(feature = "host", feature = "guest-verifier"), - feature = "transcript-blake2b" + feature = "transcript-blake2b", + not(feature = "transcript-poseidon") ))] pub type ProtocolSponge = Blake2b512; +#[cfg(all( + any(feature = "host", feature = "guest-verifier"), + feature = "transcript-poseidon", + not(feature = "transcript-blake2b") +))] +pub type ProtocolSponge = PoseidonSponge; #[cfg(feature = "host")] pub type ProofTranscript = ProverState; #[cfg(feature = "host")] From 3cfbd1077cbc61a373135a9ba7740f35d4103e47 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Wed, 21 Oct 2026 14:32:30 -0400 Subject: [PATCH 15/16] test(transcript): restore 16-unit grind comparisons and Poseidon message-order checks --- crates/jolt-transcript/tests/poseidon_tests.rs | 11 +++++++++++ crates/jolt-transcript/tests/pow_tests.rs | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/jolt-transcript/tests/poseidon_tests.rs b/crates/jolt-transcript/tests/poseidon_tests.rs index ac1739d255..e4a1a5a01c 100644 --- a/crates/jolt-transcript/tests/poseidon_tests.rs +++ b/crates/jolt-transcript/tests/poseidon_tests.rs @@ -146,6 +146,8 @@ fn draw_after( /// A different message, session or instance changes the challenges. Each /// message is packed on its own, so splitting raw bytes across messages, or /// appending a zero byte, does too, even without a length in the encoding. +/// The same messages give the same challenges, and swapping two messages +/// changes them. #[test] fn challenges_bind_messages_session_and_instance() { let base = draw_after(SESSION, INSTANCE, |t| t.append(&[1u8, 2])); @@ -162,6 +164,15 @@ fn challenges_bind_messages_session_and_instance() { for other in others { assert_ne!(base, other); } + + let in_order = |first: u8, second: u8| { + draw_after(SESSION, INSTANCE, |t| { + t.append(&[first]); + t.append(&[second]); + }) + }; + assert_eq!(in_order(1, 2), in_order(1, 2)); + assert_ne!(in_order(1, 2), in_order(2, 1)); } /// A truncated message is rejected and poisons the verifier, so later draws diff --git a/crates/jolt-transcript/tests/pow_tests.rs b/crates/jolt-transcript/tests/pow_tests.rs index 1831e761a9..ac71661193 100644 --- a/crates/jolt-transcript/tests/pow_tests.rs +++ b/crates/jolt-transcript/tests/pow_tests.rs @@ -194,7 +194,7 @@ fn verify_nonce + Sync>( let narg = [prefix, nonce, suffix].concat(); let mut verifier = sponge_verifier::(&narg); let passed = VerifierTranscript::check_grind(&mut verifier, bits).is_ok(); - let next = verifier.verifier_message_as(4, <[H::U]>::to_vec); + let next = verifier.verifier_message_as(16, <[H::U]>::to_vec); let rest = VerifierTranscript::prover_message::(&mut verifier) .is_ok_and(|message| message.0 == b"after") && verifier.check_eof().is_ok(); @@ -223,7 +223,7 @@ fn sponge_checks + Sync>() { let nonce = prover.narg_string()[prefix.len()..].to_vec(); let value = decode(&nonce); assert_eq!(nonce, leb128(value)); - let challenge = prover.verifier_message_as(4, <[H::U]>::to_vec); + let challenge = prover.verifier_message_as(16, <[H::U]>::to_vec); let mut sent = sponge_prover::(); for byte in &nonce { @@ -233,7 +233,7 @@ fn sponge_checks + Sync>() { search.probe(&nonce, &mut probed); assert_eq!(sent.verifier_message_as(40, <[H::U]>::to_vec), probed); assert!(check_passes(bits, &probed[..check_len])); - assert_eq!(probed[check_len..check_len + 4], challenge[..]); + assert_eq!(probed[check_len..check_len + 16], challenge[..]); assert_eq!( verify_nonce::(&prefix, &nonce, bits), From ebefa58faf178e30ae7afd419f094322ef49a7f8 Mon Sep 17 00:00:00 2001 From: Quang Dao Date: Fri, 23 Oct 2026 18:45:19 -0400 Subject: [PATCH 16/16] fix(transcript): drop the short-word carry explicitly, test Poseidon short challenges against num-bigint, refresh docs The carry out of x0 + p*x1 is discarded on purpose (the word is taken mod 2^256); binding it keeps -D warnings green. The Poseidon short challenge test now checks b = 128, 130 and 192 against unbounded integers, and a new test checks that a zero loss factor is refused and poisons the verifier. Docs in challenge.rs, poseidon.rs, lib.rs, transcript.rs and the fuzz targets now match the ChallengeSponge and short-challenge paths. --- .../fuzz/fuzz_targets/encoding_injectivity.rs | 2 +- .../fuzz_targets/transcript_determinism.rs | 2 +- crates/jolt-transcript/src/challenge.rs | 7 ++-- crates/jolt-transcript/src/lib.rs | 5 +-- crates/jolt-transcript/src/poseidon.rs | 11 ++++--- crates/jolt-transcript/src/transcript.rs | 5 +-- .../jolt-transcript/tests/poseidon_tests.rs | 33 +++++++++++++------ 7 files changed, 42 insertions(+), 23 deletions(-) diff --git a/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs b/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs index c500a31ea5..7bec8ec74d 100644 --- a/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs +++ b/crates/jolt-transcript/fuzz/fuzz_targets/encoding_injectivity.rs @@ -9,7 +9,7 @@ //! move changes the framing and MUST change the state. A matching state would //! mean the framing convention fails to separate `[ab]` from `[a][b]`, the //! classic transcript-malleability footgun. The state is observed as the next -//! four squeezed units: 4 bytes, or 4 `Fr` elements on Poseidon. +//! 32 squeezed units: 32 bytes, or 32 `Fr` elements on Poseidon. use std::fmt::Debug; diff --git a/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs b/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs index b302d7831d..09e2b5fc99 100644 --- a/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs +++ b/crates/jolt-transcript/fuzz/fuzz_targets/transcript_determinism.rs @@ -3,7 +3,7 @@ //! Determinism and absorption oracles over the transcript sponges. //! //! Twin instances driven by the same fuzzer-chosen op sequence must agree on -//! every squeeze and on the final state, observed as the next four squeezed +//! every squeeze and on the final state, observed as the next 32 squeezed //! units (bytes, or `Fr` elements on Poseidon), and replaying the sequence //! with one absorbed byte flipped must reach a different final state — a //! matching state would mean absorbed bytes are ignored or collide. diff --git a/crates/jolt-transcript/src/challenge.rs b/crates/jolt-transcript/src/challenge.rs index 610449542e..331e9cb719 100644 --- a/crates/jolt-transcript/src/challenge.rs +++ b/crates/jolt-transcript/src/challenge.rs @@ -93,9 +93,10 @@ pub(crate) mod sealed { /// /// # Use outside this crate /// -/// `ChallengeCodec` bounds the field of every `FieldTranscript` -/// implementation on spongefish's prover and verifier states, so a protocol -/// that draws field challenges from them names it as a bound. Callers cannot +/// `ChallengeCodec` bounds the field of the byte sponges' [`ChallengeSponge`] +/// implementation, behind the `FieldTranscript` implementations on +/// spongefish's prover and verifier states, so a protocol that draws field +/// challenges from a byte sponge can name it as a bound. Callers cannot /// implement it. The widths and decodings above are part of the transcript's /// wire format: changing one changes every proof that draws challenges in /// that field. diff --git a/crates/jolt-transcript/src/lib.rs b/crates/jolt-transcript/src/lib.rs index fbdca2d5fa..14cbb76b10 100644 --- a/crates/jolt-transcript/src/lib.rs +++ b/crates/jolt-transcript/src/lib.rs @@ -2,8 +2,9 @@ //! //! The prover runs spongefish's `ProverState` and the verifier its //! `VerifierState`. [`Transcript`] and [`FieldTranscript`] are the operations -//! both sides share: public messages, field challenges through each field's -//! [`ChallengeCodec`], and proof-of-work steps whose nonce the proof carries. +//! both sides share: public messages, field challenges through the sponge's +//! [`ChallengeSponge`] map, and proof-of-work steps whose nonce the proof +//! carries. //! [`ProverTranscript`] and [`VerifierTranscript`] add the NARG string. //! [`prover_transcript`] and [`verifier_transcript`] bind the session and the //! public statement before the first message. diff --git a/crates/jolt-transcript/src/poseidon.rs b/crates/jolt-transcript/src/poseidon.rs index 2ab9d106e0..0fbcf112db 100644 --- a/crates/jolt-transcript/src/poseidon.rs +++ b/crates/jolt-transcript/src/poseidon.rs @@ -16,7 +16,9 @@ //! //! Challenges are squeezed natively, one unit each. A squeezed unit is a //! uniform `Fr` up to the sponge's security, so `Fr` challenges need no -//! decoding and have no reduction bias. Challenges in any other field are not +//! decoding and have no reduction bias. A short challenge of `b` bits takes +//! two units `x0` and `x1` and keeps the low `b` bits of `x0 + p * x1` +//! ([`SpongeUnit::short_word`]). Challenges in any other field are not //! offered on this sponge. //! //! # Byte packing @@ -54,8 +56,8 @@ const RATE: usize = 3; const CHUNK_BYTES: usize = 31; /// Duplex sponge over BN254 `Fr` with the Circom-compatible width-5 Poseidon -/// permutation. See the [module documentation](self) for the layout and the -/// byte packing. +/// permutation: rate 3 elements, capacity 2. Each message is packed on its +/// own as its byte length, then its bytes in 31-byte little-endian chunks. pub type PoseidonSponge = DuplexSponge; /// One BN254 `Fr` element: the alphabet of [`PoseidonSponge`]. @@ -133,7 +135,8 @@ impl SpongeUnit for FrUnit { return None; }; let mut word = high.0.into_bigint().mul_low(&InnerFr::MODULUS); - word.add_with_carry(&low.0.into_bigint()); + // The carry out of bit 256 is dropped: the word is the sum mod 2^256. + let _carry = word.add_with_carry(&low.0.into_bigint()); word.to_bytes_le().try_into().ok() } } diff --git a/crates/jolt-transcript/src/transcript.rs b/crates/jolt-transcript/src/transcript.rs index a723fa2299..80dc394909 100644 --- a/crates/jolt-transcript/src/transcript.rs +++ b/crates/jolt-transcript/src/transcript.rs @@ -175,8 +175,9 @@ pub trait FieldTranscript: Transcript { /// # Errors /// `VerificationError`, before any sponge operation, when /// [`challenge_bits_for_loss`] rejects `loss_factor` or `b` is not below - /// the bit length of the field order. The verifier's state is poisoned - /// then, so `check_eof` fails too. + /// the bit length of the field order, or if the squeezed units do not fit + /// [`SpongeUnit::short_word`], which no sponge allows. The verifier's state + /// is poisoned then, so `check_eof` fails too. /// /// [`UniformBytes`]: crate::UniformBytes fn challenge_short_scalar(&mut self, loss_factor: u64) -> Result diff --git a/crates/jolt-transcript/tests/poseidon_tests.rs b/crates/jolt-transcript/tests/poseidon_tests.rs index 0cf78fbad4..ae0d60d734 100644 --- a/crates/jolt-transcript/tests/poseidon_tests.rs +++ b/crates/jolt-transcript/tests/poseidon_tests.rs @@ -236,13 +236,15 @@ fn challenges_are_full_width() { /// A short challenge squeezes two elements `x0` and `x1`, keeps the low `b` /// bits of `x0 + p * x1`, and squeezes nothing more. The verifier draws the -/// same value. +/// same value. The expected value is computed on unbounded integers. #[test] fn short_challenge_keeps_the_low_bits_of_two_elements() { - use ark_ff::BigInteger; use jolt_field::CanonicalEncoding; + use num_bigint::BigUint; - for (loss, bits) in [(1u64, 128u32), (4, 130)] { + let int = |x: Fr| BigUint::from_bytes_le(&le_bytes(x)); + let modulus = int(-fr(1)) + 1u32; + for (loss, bits) in [(1u64, 128u32), (4, 130), (u64::MAX, 192)] { let mut widest = 0; for round in 0..64u32 { let statement = round.to_be_bytes(); @@ -251,13 +253,10 @@ fn short_challenge_keeps_the_low_bits_of_two_elements() { let value: Fr = short.challenge_short_scalar(loss).unwrap(); let drawn = challenges(&mut manual, 3); - let [x0, x1] = - [drawn[0], drawn[1]].map(|x| BnFr::from_le_bytes_mod_order(&le_bytes(x))); - let mut sum = x1.into_bigint().mul_low(&BnFr::MODULUS); - sum.add_with_carry(&x0.into_bigint()); - let len = bits.div_ceil(8) as usize; - let mut bytes = sum.to_bytes_le()[..len].to_vec(); - *bytes.last_mut().unwrap() &= u8::MAX >> (8 * len as u32 - bits); + let expected = + (int(drawn[0]) + &modulus * int(drawn[1])) % (BigUint::from(1u32) << bits); + let mut bytes = expected.to_bytes_le(); + bytes.resize(bits.div_ceil(8) as usize, 0); assert_eq!(value, Fr::from_bytes_le_reduced(&bytes)); assert_eq!(challenges(&mut short, 1), [drawn[2]]); @@ -270,3 +269,17 @@ fn short_challenge_keeps_the_low_bits_of_two_elements() { assert_eq!(widest, bits, "loss={loss}"); } } + +/// A zero loss factor is refused before anything is squeezed, and poisons the +/// verifier. +#[test] +fn short_challenge_refuses_zero_loss_and_poisons() { + let mut prover = prover_transcript::(SESSION, &INSTANCE); + assert!(FieldTranscript::::challenge_short_scalar(&mut prover, 0).is_err()); + let mut fresh = prover_transcript::(SESSION, &INSTANCE); + assert_eq!(challenges(&mut prover, 1), challenges(&mut fresh, 1)); + + let mut verifier = verifier_transcript::(SESSION, &INSTANCE, &[]); + assert!(FieldTranscript::::challenge_short_scalar(&mut verifier, 0).is_err()); + assert!(verifier.check_eof().is_err()); +}