Skip to content

feat(transcript)!: run Akita inside a caller's transcript (protocol v9) - #254

Open
quangvdao wants to merge 20 commits into
feat/jolt-transcript-j3from
feat/embedded-transcript
Open

quangvdao wants to merge 20 commits into
feat/jolt-transcript-j3from
feat/embedded-transcript

Conversation

@quangvdao

@quangvdao quangvdao commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Restack (latest). This branch now merges A2's head e1df1d07a (#253) as an ordinary merge. A2 derives the session id with SHAKE128 instead of TurboSHAKE128. A2 pins Jolt to J6 (9470792dc), but this branch uses FieldTranscript, which only J7 has, so the Jolt pin is J7 bed472d3d (a16z/jolt#2110), which contains J6's SHAKE128 fix. The proof channel is jolt_transcript::ProverState<TranscriptSponge>, whose private coins also come from SHAKE128. This is FIPS 202 for the primitives only: the Fiat-Shamir duplex itself (spongefish's Keccak-f[1600] sponge or Blake2b512) is not a FIPS-approved mode. Earlier restacks merged A2's dac235470 (the ctutils 0.4.4 bump for RUSTSEC-2026-0385). A2's fuzz library now uses this branch's one-label channel constructors and its field and extension groups.

Version (latest). Versions move once per stack, counted from main. Akita main is at protocol 7 and #253 already sets 8, so this PR no longer bumps: PROTOCOL_VERSION, PROTOCOL_TAG, STANDALONE_SESSION_TAG, COMMITMENT_DIGEST_TAG and CONTEXT_DOMAIN go back from v9 to v8. Four known answers read those tags, and only they were regenerated, on both sponges:

  • transcript_has_a_known_answer (akita-transcript): the channel starts from STANDALONE_SESSION_TAG, and bind_instance absorbs PROTOCOL_TAG.
  • commitment_digest_has_a_known_answer (akita-transcript) and the one in akita-types: the digest is a session under COMMITMENT_DIGEST_TAG.
  • arithmetic_cutover_preserves_proof_and_challenges (akita-sumcheck): its channel starts from STANDALONE_SESSION_TAG.

Every other test in akita-transcript, akita-sumcheck and akita-types passed unchanged with the new tags. CONTEXT_DOMAIN is only stored in diagnostic records and never absorbed.

NOT MERGEABLE: stacked on #253, which pins a16z/jolt to an unmerged commit (J5 #2106, stacked on J3 #2099 and J2 #2097). Retarget to main after #253 lands.

Commits 7 and 9 re-pin the jolt crates from J5 (7e8ce196) to J7 (a16z/jolt#2110). Jolt's J8 (a16z/jolt#2111) pins this branch, and both must share one jolt-transcript. Commit 7 pinned J7 at 6691ec7cb. J7 was later rebased, so that commit is on no ref any more, and commit 9 moves the pin to J7's current head d04df1bb5. Both are build-only commits, so they can be dropped or moved to #253. Before this PR merges, the pin must move to whatever J7 commit lands on a16z/jolt main.

What this does

Akita used to start its own Spongefish session for every proof. Jolt, its main caller, has a session of its own. To link the two, Jolt absorbed Akita's setup, statement and commitments, squeezed a bridge value, and passed that value to Akita as session bytes. Akita then absorbed the same statement and commitments again. Each root commitment went into a sponge three times, and the two transcripts were linked only through one squeezed value.

This PR lets a caller run Akita directly on the caller's own Spongefish state:

  • Embedded entries. These are new: batched_prove_embedded (on akita_prover and AkitaCommitmentScheme) and batched_verify_embedded (on AkitaVerifier and AkitaCommitmentScheme). Akita's messages go into the caller's argument string, and its challenges come from the caller's sponge. The standalone batched_prove and batched_verify build a fresh state and call the embedded entries, so both modes run the same code. On a fresh state, an embedded proof is byte-for-byte the standalone proof.
  • Akita binds its own statement in both modes. The first thing Akita absorbs is the protocol tag, then the instance descriptor, both length-prefixed (bind_instance). The root then absorbs each commitment's digest, the opening points and the claimed values. Akita's soundness does not depend on what the caller absorbed.
  • Commitments are bound by a 32-byte digest. CommitmentDigest is squeezed from a separate Spongefish session, on the selected sponge (Keccak by default), over the canonical bytes of a CommittedGroup (its profile, then its rows). CommittedGroup now has private fields, and it computes the digest when it is built (CommittedGroup::new, now fallible) or decoded. No path accepts a digest from outside, so the verifier always hashes the rows it then uses. A caller binds a commitment early by absorbing the same 32 bytes.
  • Acceptance needs an explicit end-of-proof check. The embedded verifier returns a #[must_use] PendingEof. The caller runs its own remaining messages, then calls PendingEof::check_eof(state). That call consumes the state, checks that every proof byte was read, and is the only way to get ProofAcceptance. On any error, the embedded verifier first poisons the caller's state, so every later read and the final check fail.
  • Two duplicate absorbs are gone. On feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253, the next-level outer payload and the terminal t were absorbed twice: once when the prover sent them, and again as public values (WitnessCommitment, TerminalTFields). The second absorb added no binding, so this PR drops it on both sides.
  • Grouped absorbs. Each atom group and each round polynomial is now one absorb call instead of one per atom. Spongefish absorbs are associative, so the bytes do not change. The first commit makes this change while the bytes are still feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253's, and feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253's known answers still pass after it on both sponges.

Challenges change, but the protocol version stays 8. Versions move once per stack, counted from main: Akita main is at 7, and #253 already sets 8 for this stack.

Design record: specs/akita-embedded-transcript.md. Book: how/transcript.md (new sections "Running inside a caller's transcript" and "Commitment digests") and how/verification.md.

Absorb map

"LP" means a u64 little-endian length prefix. "Public" means absorbed but not sent. "Sent" means a prover message, which goes into the argument string and is absorbed once.

Before (#253, standalone only):

Order What How
session SHAKE128 of the protocol tag Spongefish session id
instance LP(session bytes), LP(descriptor) Spongefish instance
root all commitment coefficients, per group public, one absorb per coefficient
root opening point, per group public, one absorb per element
root all claimed values public, one absorb per element
folds prover messages and challenges sent, one absorb per atom
next level outer payload, already sent public again (WitnessCommitment)
terminal t, already sent public again (TerminalTFields)

After (this PR, still v8):

Order What How
session (standalone only) SHAKE128 of STANDALONE_SESSION_TAG, instance LP(session bytes) Spongefish session
first Akita absorb LP(protocol tag), LP(descriptor) public, in band, both modes
root CommitmentDigest (32 bytes), per group public
root opening point, per group public, one absorb
root all claimed values public, one absorb
folds prover messages and challenges sent, one absorb per group or round polynomial
next level nothing more for the outer payload bound when it was sent
terminal nothing more for t bound when it was sent

When Akita runs embedded, the "session" row is the caller's own session.

Proof size does not change: this PR sends the same messages as #253.

Soundness audits

  • Design (xhigh, before implementation): sound with changes, no blockers. Every required change is in this PR:

    • the digest covers the profile;
    • digests are always recomputed, and the digest and the rows come from one value;
    • empty groups make no absorb call, and merged reads decode only canonical atoms;
    • PendingEof exists;
    • the protocol tag is length-prefixed;
    • the caller contract states where Akita's proof sits in the argument string.

    A second pass confirmed these fixes. It also confirmed that dropping both duplicate absorbs is sound, because the value used later is exactly the value received when it was sent.

  • Implementation (independent read-only audit of the full diff): SOUND, no blockers. It confirmed:

    • prover and verifier absorb the same bytes in the same order;
    • the only changes from feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253 are the digest in place of rows, the caller's session in embedded mode, and the two dropped duplicates;
    • the verifier takes the digest and the rows from one CommittedGroup, and no outside digest can enter;
    • the embedded verifier poisons the state on every error path;
    • no new verifier-reachable panic or unbounded allocation.

    How its non-blocking notes were handled:

    • check_eof is not tied to the state Akita ran on. Now documented: the caller must pass the same state. A live-state check at the end would add nothing, because every Akita proof reads from the argument string and any read on a poisoned state already fails.
    • The prover keeps digests beside the rows. Both come from the same claims.groups() in one function. A mismatch could only break completeness, not soundness. Moving CommittedGroup through the whole prover is listed as a follow-up.
    • The verifier repeats the profile check that CommittedGroup already does. That loop was already there before this PR, and it checks against the configured field bound rather than the field modulus, so it was left alone.
    • A debug cross-check for the sent t was suggested. The old duplicate absorb of t caught only backend bugs, never soundness bugs. Keeping it would need the sent message passed into the terminal suffix, so it is a follow-up.
  • Re-check of the 32-byte digest: SOUND, no blockers. It confirmed that a 256-bit output meets the 2^128 target on both sponges, that every length-dependent site was updated, and, by reading Spongefish's squeeze code, that a 32-byte squeeze is a prefix of a 64-byte one on both Keccak and Blake2b. So the truncated known answers are correct.

Digest strength: changing a bound commitment means finding a collision in the 32-byte digest, which costs about 2^128 hash calls, the transcript target. Known quantum collision searches are no cheaper than a classical parallel search once their memory is counted: Brassard-Hoyer-Tapp needs about 2^85 quantum memory, and Chailloux-Naya-Plasencia-Schrottenloher needs about 2^102 quantum time. So 256-bit outputs are the usual 128-bit collision level. Both sponges have a 512-bit capacity, so the sponge does not lower this.

Caller contract (for Jolt, J8)

  1. Pass a state over Akita's sponge. The entries take Akita's channel types, so a state over a different sponge does not compile.
  2. On the verifier side, build the state so that the unread part of the argument string starts with Akita's proof when Akita starts. Do not read the argument string before Akita runs.
  3. Bind each root commitment before any of your own challenges that depend on it. Absorbing CommittedGroup::digest().as_bytes() is enough. Absorbing the commitment's canonical rows, length-framed, is also enough when public data selects exactly one schedule row, so the profile is fixed. J8 does this: it keeps Jolt main's commitment wire and absorbs the metadata and the rows.
  4. Stop on any error from Akita. After your own last message, call PendingEof::check_eof(state).

Validation

All on a Boat sandbox at this head, unless noted.

  • akita-transcript, akita-sumcheck and akita-types: 343/343 tests pass on Keccak (the default) and 344/344 on Blake2b.
  • New akita-pcs --test embedded_transcript: 7/7 pass with logging-transcript on Keccak and on Blake2b. The tests:
    • an embedded proof on a fresh state equals the standalone proof;
    • an embedded proof round-trips between caller messages;
    • a rejected embedded proof poisons the caller's state;
    • the standalone verifier rejects trailing bytes;
    • changing the session, point, claimed value or commitment rejects the proof, in both modes;
    • a structural check of the logged absorbs: the instance binding comes first, then one 32-byte digest per group, and no sent value is absorbed again.
  • Release --test transcript_hardening: 6/6 pass, and --test transcript_hardening_proptest: 3/3 pass (Keccak, logging-transcript). This includes the check that prover and verifier record identical event logs.
  • Release --test protocol_soundness: 5/5 pass, and --test fold_linf: 3/3 pass (Keccak, logging-transcript).
  • akita-types --features prover,logging-transcript: 361/361 pass. This ran on the head before the digest was shortened to 32 bytes.
  • Builds: cargo check --workspace --all-targets --features logging-transcript passes, and fuzz/ passes cargo check --bins.
  • Clippy: the three configurations from AGENTS.md (Keccak) pass with -D warnings.
  • Local preflight: passes cargo fmt --check, taplo fmt --check, typos, both Rust file-line scripts, the Python script tests, check-crate-deps.sh for verifier, prover, cpu-backend, config, planner and setup, cargo machete, the shared-field identity and external schedule guards, and check-doc-guardrails.sh (mdbook build skipped locally).
  • After rebasing onto feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253 at 2ae02c1 (head 6b61cd1): the proof-input guard passes. cargo check --workspace --all-targets --features logging-transcript passes. The akita-transcript, akita-sumcheck and akita-types tests pass on Keccak and on Blake2b, and embedded_transcript passes 7/7 with logging-transcript. Two of feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253's new sumcheck tests were moved to the two-argument channel constructors.
  • After the J7 re-pin (head de995f497): the akita-transcript, akita-sumcheck and akita-types tests pass on both sponges, embedded_transcript passes 5/5 in the default build, the three AGENTS.md clippy configurations and the default (Keccak) clippy pass, the profile glue tests pass (16/16) and profile/ passes cargo check --release, fuzz/ passes cargo check, and the shared-field identity check passes for all three workspaces. The three lockfiles were regenerated.
  • At head e9d85d1f7 (J7 re-pin plus the merge of feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253's head): cargo check --workspace --all-targets --features logging-transcript passes. The akita-params, akita-transcript and akita-sumcheck tests pass (278/278). embedded_transcript passes in the default build and with logging-transcript. cargo clippy --all --all-targets --release --no-default-features --features transcript-blake2b -- -D warnings passes. check-doc-guardrails.sh passes.
  • At head bfc4c2f1b (merge of feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253's e1df1d07a, Jolt pin bed472d3d), on macpro (M4 Max): cargo metadata --locked for the root, fuzz/ and profile/akita-recursion/; check-shared-field-identity.sh; cargo clippy --all --all-targets --release --no-default-features --features parallel,disk-persistence,transcript-keccak -- -D warnings; the akita-transcript (prover) and akita-sumcheck library tests (29 each) and the akita-types committed-group tests (22) on both sponges. No turboshake in any source or lockfile. Known answers re-pinned for the SHAKE128 session in bfc4c2f1b: the transcript, the commitment digest (akita-transcript and akita-types), and the standard and normalized sumcheck proofs and challenges. No assertion changed, and descriptor digests stay Blake2b-256.
  • Not run here: the Blake2b leg of the release transcript-semantics matrix and the mdbook build (both in CI).

Known answers were regenerated for both sponges: the transcript, the commitment digest (in akita-transcript and akita-types) and the sumcheck cutover. No checked-in fixture or fuzz corpus depends on transcript bytes. The akita-cpu-backend library tests need more than 16 GB, so they run in CI only.

Follow-ups (not in this PR)

  • Descriptor sub-digests are still Blake2b-256 (akita_params::digest_descriptor_bytes), whatever the sponge. The verifier recomputes the grinding-plan, opening-layout and effective-schedule digests on every call. A recursive verifier on Keccak therefore needs a Blake2b-256 circuit too, for one compression per 128 input bytes of each digested structure. Moving them to the sponge's hash also changes the schedule row digests stored in the generated catalog artifacts, so it needs an artifact regeneration.
  • Carry CommittedGroup through the prover's opening data, so the digests and rows are one value there too.
  • Drop the public absorbs of challenge-derived values (TerminalPoint, TerminalOpening), one at a time, each with its own audit.

Commits

  1. refactor(transcript): send each atom group and round polynomial as one message. The bytes stay as on feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253.
  2. The embedded-transcript change: embedded entries, bind_instance, CommitmentDigest, borrowed grinding with PendingEof, the duplicate absorbs dropped, and the known answers.
  3. Docs: the spec and the book.
  4. fix(transcript): the diagnostic event log (logging-transcript only) now restarts when a standalone prover channel is created. It used to restart when prover grinding started. That erased the instance record, so the prover's records no longer matched the verifier's, and in embedded mode it would have erased the caller's records.
  5. docs(spec): link this PR from the spec.
  6. test(types): the commitment decoding tests use only Validate::Yes. The proof-input guard in CI forbids Validate::No in proof-input crates, tests included.
  7. build: pin the jolt crates to J7 (6691ec7cb) in every manifest, the three lockfiles and the book, and use J7's FieldTranscript::challenge_vector in the proof-stream channel.
  8. docs(spec): caller-contract item 5 also allows binding a commitment by its rows when public data fixes its profile.
  9. build: re-pin the jolt crates to J7's rebased head (d04df1bb5). Between the two pins, jolt-field and jolt-poly are unchanged. jolt-transcript changes in two ways. Its default sponge is now Keccak alone, which no Akita crate relies on, because every manifest sets default-features = false. Its proof-of-work search now scans 1,024 nonces on the calling thread before it wakes the thread pool; it still returns the least accepted nonce, so proofs are unchanged. The three lockfiles were regenerated.
  10. Merge feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253's head (f858d7ebf), so this branch again contains all of feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253. This keeps feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253's compile-time check that TRANSCRIPT_SECURITY_BITS equals jolt_transcript::SECURITY_BITS. The pin conflicts were resolved to this branch's J7 pin.
  11. Later: merges of feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253's heads 4a3b8dc8c and dac235470, the J7 13e149195 pin, the fuzz port to the group channel API, and fix(transcript): keep the tags at v8 (c16468ac6).
  12. Merge feat(transcript)!: move the transcript onto jolt-transcript (protocol v8) #253's head e1df1d07a (849bdd4c5, SHAKE128 session id), pin Jolt at J7 bed472d3d in every manifest, lockfile and the book (7e25e59ff), and re-pin the known answers on both sponges (bfc4c2f1b).

🤖 Generated with Claude Code

@github-actions github-actions Bot added the spec PR contains a spec label Oct 21, 2026
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Documentation blast radius (advisory)

These regions may need doc/spec/book updates based on changed paths.
This is not a merge gate. See docs/documentation.md.

Changed files in this PR: 80

workspace-crates

Workspace membership and public crate surface

Code paths touched:

  • crates/akita-algebra/Cargo.toml
  • crates/akita-challenges/Cargo.toml
  • crates/akita-config/Cargo.toml
  • crates/akita-cpu-backend/Cargo.toml
  • crates/akita-params/Cargo.toml
  • crates/akita-pcs/Cargo.toml
  • crates/akita-prover/Cargo.toml
  • crates/akita-serialization/Cargo.toml
  • crates/akita-setup/Cargo.toml
  • crates/akita-sumcheck/Cargo.toml
  • crates/akita-transcript/Cargo.toml
  • crates/akita-types/Cargo.toml
  • crates/akita-verifier/Cargo.toml

Consider updating:

  • README.md
  • docs/crate-graph.md
  • book/src/how/architecture.md

planner-schedule

Schedule tables, DP fallback, proof-size planning

Code paths touched:

  • crates/akita-config/Cargo.toml
  • crates/akita-config/src/lib.rs

Consider updating:

  • book/src/how/configuration.md
  • book/src/usage/profiling.md
  • specs/planner-*.md
  • specs/PRUNING.md

sis-security

SIS floors, norm bounds, challenge sampling

Code paths touched:

  • crates/akita-challenges/Cargo.toml

Consider updating:

  • book/src/how/security.md
  • book/src/foundations/lattices-sis.md
  • specs/archive/2026-Q2/akita-sis-*.md
  • specs/archive/2026-Q2/sis-euclidean-estimator.md
  • specs/fold-linf-rejection.md
  • docs/security-posture.md

prover-protocol

Prove pipeline, folds, sumcheck stages

Code paths touched:

  • crates/akita-prover/src/protocol/mod.rs
  • crates/akita-prover/src/protocol/prove/execution.rs
  • crates/akita-prover/src/protocol/prove/mod.rs
  • crates/akita-prover/src/protocol/prove/registry.rs
  • crates/akita-prover/src/protocol/prove/root.rs
  • crates/akita-prover/src/protocol/prove/suffix.rs
  • crates/akita-sumcheck/Cargo.toml
  • crates/akita-sumcheck/src/proof_stream.rs

Consider updating:

  • book/src/how/proving/**
  • book/src/how/recursion.md
  • book/src/usage/feature-flags.md
  • specs/dynamic-backend-bridges.md
  • specs/packed-sumcheck.md
  • specs/archive/2026-Q3/terminal-fold-cutover.md
  • specs/archive/2026-Q3/protocol-field-geometry-cutover.md

verifier

Verifier replay and no-panic boundary

Code paths touched:

  • crates/akita-verifier/Cargo.toml
  • crates/akita-verifier/src/fold/root.rs
  • crates/akita-verifier/src/fold/suffix.rs
  • crates/akita-verifier/src/fold/terminal.rs
  • crates/akita-verifier/src/fold/verify.rs
  • crates/akita-verifier/src/stages/opening_claims/coefficient_packing.rs
  • crates/akita-verifier/src/stages/opening_claims/extension_claim.rs

Consider updating:

  • book/src/how/verification.md
  • book/src/how/proving/fold-path.md
  • docs/verifier-contract.md
  • docs/verifier-panic-audit.md
  • specs/archive/2026-Q3/security-hardening.md
  • specs/archive/2026-Q3/protocol-field-geometry-cutover.md

transcript

Fiat-Shamir transcript and instance binding

Code paths touched:

  • crates/akita-transcript/Cargo.toml
  • crates/akita-transcript/src/lib.rs
  • crates/akita-transcript/src/logging.rs
  • crates/akita-transcript/src/proof_stream.rs
  • crates/akita-transcript/src/proof_stream/channel.rs
  • crates/akita-transcript/src/proof_stream/tests.rs

Consider updating:

  • book/src/how/transcript.md
  • book/src/usage/commitment-api.md
  • specs/transcript-*.md

commitment-setup

Setup expansion and Ajtai commitment

Code paths touched:

  • crates/akita-cpu-backend/src/commitment/api.rs
  • crates/akita-cpu-backend/src/commitment/executor.rs
  • crates/akita-cpu-backend/src/commitment/imported_outer.rs
  • crates/akita-cpu-backend/src/commitment/tests/api.rs
  • crates/akita-cpu-backend/src/opaque/opening_tests.rs
  • crates/akita-cpu-backend/src/opaque/owned_commit.rs
  • crates/akita-setup/Cargo.toml

Consider updating:

  • book/src/how/commitment.md
  • book/src/usage/commitment-api.md
  • book/src/usage/setup-runtime.md
  • specs/family-agnostic-cpu-backend.md
  • specs/setup-*.md

field-algebra

Field traits, packed SIMD, extension towers

Code paths touched:

  • crates/akita-algebra/Cargo.toml

Consider updating:

  • book/src/foundations/rings-and-fields.md
  • book/src/foundations/ntt-crt.md
  • book/src/how/optimizations.md
  • specs/archive/2026-Q3/akita-field-refactor.md
  • specs/archive/2026-Q3/crt-ntt-*.md
  • specs/archive/2026-Q3/avx-simd-port.md

pcs-umbrella

Public PCS API, examples, integration tests

Code paths touched:

  • crates/akita-pcs/Cargo.toml
  • crates/akita-pcs/src/lib.rs
  • crates/akita-pcs/src/scheme/mod.rs
  • crates/akita-pcs/src/scheme/tests/dense_group.rs
  • crates/akita-pcs/src/scheme/tests/onehot.rs
  • crates/akita-pcs/src/scheme/tests/onehot/multi_group.rs
  • crates/akita-pcs/tests/akita_fp128_e2e/heterogeneous.rs
  • crates/akita-pcs/tests/akita_small_field_e2e.rs
  • crates/akita-pcs/tests/backend_ownership.rs
  • crates/akita-pcs/tests/common/mod.rs
  • crates/akita-pcs/tests/embedded_transcript.rs
  • crates/akita-pcs/tests/matrix_drivers/mod.rs
  • crates/akita-pcs/tests/transcript_hardening/catalog_events.rs

Consider updating:

  • book/src/usage/**
  • README.md

book-tooling

Book structure and guardrails

Code paths touched:

  • book/src/foundations/field-arithmetic.md
  • book/src/how/transcript.md
  • book/src/how/verification.md

Consider updating:

  • book/README.md
  • docs/documentation.md
  • specs/PRUNING.md

Per-PR checklist: spec Status / acceptance criteria; book owning page; AGENTS.md if contracts changed; archive spec after fold.

@github-actions github-actions Bot added the implementation PR contains implementation of a spec label Oct 21, 2026
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

PCS Profile Benchmark

13 of 13 profiles passed.

Times are medians of 3 measured runs after 1 discarded warmup run. Peak RSS is the largest measured value.

Each sample verifies the same proof first with the configured multi-threaded pool and then with one thread. Both timings reuse the same verifier setup.

Merge-base comparisons are available for 13 of 13 profiles. For matching profiles, the head and merge-base binaries ran interleaved on the same runner.

Benchmark shards

CI shard Profiles
1-fp32-base Fp32 dense nv30, direct setup check
Fp32 one-hot nv34, direct setup check
2-fp64-base Fp64 dense nv29, direct setup check
Fp64 one-hot nv35, direct setup check
3-fp128-base Fp128 dense nv28, direct setup check
Fp128 one-hot nv36, direct setup check
Fp128 one-hot nv36, recursive setup check
4-multi-group-direct Fp128 multi-group (final nv34, 4 polys total), direct setup check
5-multi-group-recursive Fp128 multi-group (final nv34, 4 polys total), recursive setup check
6-multi-group-recursive-w8r2 Fp128 multi-group W8R2 (final nv32, 4 polys total), recursive setup check
7-distributed Fp128 one-hot nv32 W2R2, direct setup check
Fp128 one-hot nv32 W4R2, direct setup check
Fp128 one-hot nv32 W8R2, direct setup check

Public opening statements

Public opening statement Profiles
Over Fp32, one committed 30 variable multilinear polynomial with 2^30 coefficients is opened at one 30 coordinate point. Fp32 dense nv30, direct setup check
Over Fp32, one committed 34 variable multilinear polynomial with 2^34 coefficients is opened at one 34 coordinate point. Fp32 one-hot nv34, direct setup check
Over Fp64, one committed 29 variable multilinear polynomial with 2^29 coefficients is opened at one 29 coordinate point. Fp64 dense nv29, direct setup check
Over Fp64, one committed 35 variable multilinear polynomial with 2^35 coefficients is opened at one 35 coordinate point. Fp64 one-hot nv35, direct setup check
Over Fp128, one committed 28 variable multilinear polynomial with 2^28 coefficients is opened at one 28 coordinate point. Fp128 dense nv28, direct setup check
Over Fp128, one committed 36 variable multilinear polynomial with 2^36 coefficients is opened at one 36 coordinate point. Fp128 one-hot nv36, direct setup check
Fp128 one-hot nv36, recursive setup check
Over Fp128, 4 polynomials in 3 groups: one 16 variable polynomial at its own point, one 16 variable polynomial at its own point, and 2 34 variable polynomials at one shared point. Fp128 multi-group (final nv34, 4 polys total), direct setup check
Fp128 multi-group (final nv34, 4 polys total), recursive setup check
Over Fp128, 4 polynomials in 3 groups: one 16 variable polynomial at its own point, one 16 variable polynomial at its own point, and 2 32 variable polynomials at one shared point. Fp128 multi-group W8R2 (final nv32, 4 polys total), recursive setup check
Over Fp128, one committed 32 variable multilinear polynomial with 2^32 coefficients is opened at one 32 coordinate point. Fp128 one-hot nv32 W2R2, direct setup check
Fp128 one-hot nv32 W4R2, direct setup check
Fp128 one-hot nv32 W8R2, direct setup check

One-hot profiles generate deterministic witnesses with one 1 in every consecutive chunk of 256 coefficients. This witness shape is not a separate public claim.

Direct evaluates the public setup contribution during Stage 2. Recursive carries the same check through a Stage 3 setup-product sumcheck. Both modes execute the complete fold schedule and terminal verification.

The chunked profiles W2R2, W4R2, W8R2 divide the witness relation into the stated number of exact chunks for the first two fold levels.

Generated profiles may select different A, B, and D ring dimensions at different fold levels. The short profile names omit those dimensions.

Each sample generates deterministic witnesses and opening points, prepares setup, commits, proves, serializes the proof, checks its size, prepares verifier setup, and verifies the claimed openings. It does not test malformed proofs.

Phase time

Profile Setup Commit Prove Verify, multi-threaded Verify, single-threaded
Fp32 dense nv30, direct setup check 0.015 s
+2.7%
0.782 s
+1.6%
0.876 s
+0.6%
11.4 ms
-3.1%
27.5 ms
-1.6%
Fp32 one-hot nv34, direct setup check 0.039 s
+12.0%
0.504 s
-1.3%
0.882 s
-4.3%
13.2 ms
-5.4%
52.7 ms
-1.7%
Fp64 dense nv29, direct setup check 0.050 s
-2.1%
0.772 s
+0.0%
0.865 s
-0.1%
13.1 ms
-3.9%
35.5 ms
-1.1%
Fp64 one-hot nv35, direct setup check 0.088 s
+0.0%
1.676 s
+1.8%
1.264 s
-10.7%
16.6 ms
-1.2%
85.5 ms
-0.4%
Fp128 dense nv28, direct setup check 0.074 s
+2.0%
0.960 s
-0.9%
1.040 s
-0.1%
13.8 ms
+0.2%
52.2 ms
-1.0%
Fp128 one-hot nv36, direct setup check 0.206 s
+0.4%
4.470 s
-0.0%
2.195 s
+9.5%
27.3 ms
+1.4%
174.9 ms
-0.3%
Fp128 one-hot nv36, recursive setup check 0.759 s
+0.0%
4.937 s
+0.1%
2.221 s
-0.8%
18.3 ms
-1.1%
52.5 ms
-0.9%
Fp128 multi-group (final nv34, 4 polys total), direct setup check 0.129 s
+0.2%
2.267 s
-0.1%
1.209 s
-1.0%
21.7 ms
+3.0%
128.7 ms
-1.2%
Fp128 multi-group (final nv34, 4 polys total), recursive setup check 0.373 s
+0.6%
1.458 s
-1.7%
1.135 s
+1.7%
11.2 ms
-1.3%
22.7 ms
+4.9%
Fp128 multi-group W8R2 (final nv32, 4 polys total), recursive setup check 0.253 s
-0.1%
0.624 s
-0.4%
1.675 s
+0.3%
25.2 ms
-3.0%
73.3 ms
-0.7%
Fp128 one-hot nv32 W2R2, direct setup check 0.048 s
+9.0%
0.356 s
+0.2%
0.596 s
-0.1%
14.9 ms
-9.3%
43.3 ms
-0.7%
Fp128 one-hot nv32 W4R2, direct setup check 0.049 s
+1.3%
0.354 s
-2.3%
0.761 s
+3.0%
17.9 ms
-0.6%
51.7 ms
-0.4%
Fp128 one-hot nv32 W8R2, direct setup check 0.050 s
+3.7%
0.371 s
+2.2%
0.965 s
-2.5%
20.2 ms
-1.6%
62.0 ms
-0.1%

Proof size and protocol shape

Profile Fold A/B/D schedule Total proof Terminal response Terminal response planned maximum Fold levels Grinding retries
Fp32 dense nv30, direct setup check 2048/256/256 → 128/128/128 75,291 bytes
-0.0%
43,728 bytes
+0.0%
45,688 bytes
+0.0%
7
+0.0%
None
Fp32 one-hot nv34, direct setup check 2048/256/256 → 256/128/256 → 128/128/128 75,407 bytes
+0.0%
43,730 bytes
+0.0%
45,688 bytes
+0.0%
7
+0.0%
None
Fp64 dense nv29, direct setup check 2048/128/128 → 1024/128/128 → 128/128/128 → 64/64/64 77,372 bytes
+0.0%
45,224 bytes
+0.1%
47,620 bytes
+0.0%
8
+0.0%
L6: 0 / 0 / 0
Merge base
L6: 1 / 1 / 1
Fp64 one-hot nv35, direct setup check 1024/256/128 → 256/128/128 → 128/128/128 → 64/64/64 77,435 bytes
-0.1%
45,176 bytes
-0.1%
47,620 bytes
+0.0%
8
+0.0%
L6: 0 / 0 / 0
Merge base
L6: 2 / 2 / 2
Fp128 dense nv28, direct setup check 1024/64/64 → 128/64/64 → 64/64/64 80,564 bytes
-0.0%
51,959 bytes
-0.0%
54,262 bytes
+0.0%
7
+0.0%
None
Fp128 one-hot nv36, direct setup check 512/128/64 → 512/64/64 → 64/64/64 81,808 bytes
+0.0%
52,017 bytes
+0.0%
54,262 bytes
+0.0%
8
+0.0%
None
Fp128 one-hot nv36, recursive setup check 512/128/64 → 64/64/64 84,951 bytes
-0.0%
51,976 bytes
-0.0%
54,262 bytes
+0.0%
8
+0.0%
None
Fp128 multi-group (final nv34, 4 polys total), direct setup check 512/128/128 → 512/64/64 → 64/64/64 81,683 bytes
+0.1%
52,003 bytes
+0.1%
54,262 bytes
+0.0%
8
+0.0%
None
Fp128 multi-group (final nv34, 4 polys total), recursive setup check 512/128/128 → 64/64/64 89,779 bytes
+0.0%
51,974 bytes
+0.0%
54,262 bytes
+0.0%
8
+0.0%
None
Fp128 multi-group W8R2 (final nv32, 4 polys total), recursive setup check 512/64/64 → 128/64/64 → 64/64/64 83,722 bytes
-0.0%
51,989 bytes
-0.1%
54,262 bytes
+0.0%
8
+0.0%
None
Fp128 one-hot nv32 W2R2, direct setup check 512/64/64 → 64/64/64 81,532 bytes
-0.0%
51,971 bytes
-0.0%
54,262 bytes
+0.0%
8
+0.0%
None
Fp128 one-hot nv32 W4R2, direct setup check 512/64/64 → 256/64/64 → 64/64/64 81,790 bytes
-0.0%
51,997 bytes
-0.0%
54,262 bytes
+0.0%
8
+0.0%
None
Fp128 one-hot nv32 W8R2, direct setup check 512/64/64 → 256/64/64 → 64/64/64 82,339 bytes
+0.0%
51,983 bytes
+0.0%
54,262 bytes
+0.0%
8
+0.0%
None

Grinding retries are rejected attempts at each fold, listed in measured-run order. Zero means the first sampled nonce was accepted.

Each multi-group profile has two precommitted nv16 singleton polynomials and two polynomials in the displayed final group.

Native proof accounting

Profile Non-nonce proof Nonce messages Nonce maximum
Fp32 dense nv30, direct setup check 74,784 bytes
+0.0%
507 bytes
-0.6%
1,016 bytes
+0.0%
Fp32 one-hot nv34, direct setup check 74,898 bytes
+0.0%
509 bytes
-1.0%
1,020 bytes
+0.0%
Fp64 dense nv29, direct setup check 76,872 bytes
+0.0%
500 bytes
+0.2%
992 bytes
+0.0%
Fp64 one-hot nv35, direct setup check 76,936 bytes
-0.1%
499 bytes
-0.4%
996 bytes
+0.0%
Fp128 dense nv28, direct setup check 80,183 bytes
-0.0%
381 bytes
+0.0%
758 bytes
+0.0%
Fp128 one-hot nv36, direct setup check 81,377 bytes
+0.0%
431 bytes
-0.7%
856 bytes
+0.0%
Fp128 one-hot nv36, recursive setup check 84,424 bytes
-0.0%
527 bytes
+0.0%
1,048 bytes
+0.0%
Fp128 multi-group (final nv34, 4 polys total), direct setup check 81,251 bytes
+0.1%
432 bytes
+0.2%
854 bytes
+0.0%
Fp128 multi-group (final nv34, 4 polys total), recursive setup check 89,174 bytes
+0.0%
605 bytes
-0.2%
1,204 bytes
+0.0%
Fp128 multi-group W8R2 (final nv32, 4 polys total), recursive setup check 83,253 bytes
-0.1%
469 bytes
+0.4%
930 bytes
+0.0%
Fp128 one-hot nv32 W2R2, direct setup check 81,107 bytes
-0.0%
425 bytes
-0.5%
848 bytes
+0.0%
Fp128 one-hot nv32 W4R2, direct setup check 81,357 bytes
-0.0%
433 bytes
+0.0%
856 bytes
+0.0%
Fp128 one-hot nv32 W8R2, direct setup check 81,903 bytes
+0.0%
436 bytes
+0.2%
868 bytes
+0.0%

Memory and setup size

Profile Setup vector Prepared NTT cache Verifier NTT cache Peak RSS
Fp32 dense nv30, direct setup check 20.0 MiB
+0.0%
154.0 MiB
+0.0%
1.2 MiB
+0.0%
4628.0 MiB
-0.4%
Fp32 one-hot nv34, direct setup check 64.0 MiB
+0.0%
354.1 MiB
+0.0%
1.2 MiB
+0.0%
1039.6 MiB
-0.1%
Fp64 dense nv29, direct setup check 32.0 MiB
+0.0%
291.2 MiB
+0.0%
1.8 MiB
+0.0%
4860.1 MiB
-0.2%
Fp64 one-hot nv35, direct setup check 128.0 MiB
+0.0%
917.2 MiB
+0.0%
1.8 MiB
+0.0%
1860.4 MiB
+0.2%
Fp128 dense nv28, direct setup check 64.0 MiB
+0.0%
585.0 MiB
+0.0%
1.5 MiB
+0.0%
5181.3 MiB
-0.0%
Fp128 one-hot nv36, direct setup check 344.0 MiB
+0.0%
2832.0 MiB
+0.0%
3.0 MiB
+0.0%
4413.8 MiB
+0.0%
Fp128 one-hot nv36, recursive setup check 512.0 MiB
+0.0%
3600.0 MiB
+0.0%
1.5 MiB
+0.0%
5723.1 MiB
+0.2%
Fp128 multi-group (final nv34, 4 polys total), direct setup check 256.0 MiB
+0.0%
1319.9 MiB
+0.0%
3.0 MiB
+0.0%
2390.0 MiB
+0.0%
Fp128 multi-group (final nv34, 4 polys total), recursive setup check 256.0 MiB
+0.0%
1899.0 MiB
+0.0%
1.6 MiB
+0.0%
3499.8 MiB
+0.1%
Fp128 multi-group W8R2 (final nv32, 4 polys total), recursive setup check 128.0 MiB
+0.0%
620.5 MiB
+0.0%
1.5 MiB
+0.0%
1738.7 MiB
-0.1%
Fp128 one-hot nv32 W2R2, direct setup check 64.0 MiB
+0.0%
321.0 MiB
+0.0%
1.5 MiB
+0.0%
685.1 MiB
+0.0%
Fp128 one-hot nv32 W4R2, direct setup check 64.0 MiB
+0.0%
345.0 MiB
+0.0%
1.5 MiB
+0.0%
758.0 MiB
-0.1%
Fp128 one-hot nv32 W8R2, direct setup check 64.0 MiB
+0.0%
345.0 MiB
+0.0%
1.5 MiB
+0.0%
841.9 MiB
+0.1%

Deltas are shown only for profiles with a matching merge-base case. Negative is smaller or faster.

The uploaded report.md benchmark artifact contains the detailed fold schedule, proof-size breakdown, terminal response segments, and Golomb diagnostics.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

CI test timing

  • Report generated: 2026-10-27T04:42:55Z.
  • Source: 2768ad8 on feat/embedded-transcript.
  • Workflow run: 40445720416.
  • Main baseline: 55a4027.
  • Previous run: 0475373.

Run summary

Wall s Main wall s Main Δ Ratio Tests Skipped Failed Status
1569.0 1760.0 -10.9% 0.89x 2020 0 0 ok

Wall time spans 2 parallel nextest slice shards.

Slowest tests

Rank Duration s Test
1 405.6 akita-planner::schedule_params::suffix_dp::chunk_ownership_tests::contracting_chunk_search_matches_unpruned_complete_objective
2 157.6 akita-planner::schedule_params::adaptive_search_tests::adaptive_frontier_matches_unpruned_traversal_and_hand_priced_role_optima
3 106.1 akita-planner::planner::setup_prefix_challenge_hook_tests::catalog_binding_revalidates_recursive_setup_prefix_challenge_hook
4 56.2 akita-planner::planner::totality_tests::contractive_winner_remains_selected
5 33.7 akita-planner::planner::totality_tests::noncontractive_multi_chunk_root_can_beat_contractive_candidates
6 32.4 akita-planner::generated_families::tests::generation_uses_requested_geometry_for_preplans_and_grouped_producers
7 26.5 akita-planner::schedule_params::adaptive_search_tests::adaptive_nv36_minimizes_setup_envelope_before_first_direct_setup
8 25.4 akita-planner::schedule_params::adaptive_search_tests::relation_cutover::bounded_suffix_dp_matches_unpruned_fixed_cutover_search
9 23.5 akita-planner::planner::totality_tests::valid_small_grouped_root_has_a_schedule
10 22.2 akita-planner::schedule_params::adaptive_search_tests::production_suffix_selects_l2_with_the_typed_response_model
11 22.1 akita-pcs::dynamic_backends::portable::offloaded_route_uses_only_local_prefix_handles
12 18.1 akita-pcs::akita_fp128_e2e::heterogeneous::bounded_dense_roundtrip_over_u64_coefficients_at_every_catalog_size
13 18.0 akita-pcs::akita_small_field_e2e::combined_recursive::fp32_combined_setup_proves_recursive_and_onehot_families
14 16.8 akita-pcs::scheme::tests::cross_mode::proofs_cannot_replay_across_valid_quotient_and_reduced_schedules
15 15.1 akita-pcs::dynamic_backends::dynamic_backend_failures_cleanup_and_retry
16 11.8 akita-setup::tests::disk_persistence::combined_recursive_prefix_registry_persists_and_serves_each_family
17 11.4 akita-pcs::transcript_hardening_proptest::stream_fuzzes_batch_shapes
18 11.3 akita-pcs::akita_fp128_e2e::fp128_onehot
19 11.3 akita-pcs::akita_small_field_e2e::selective_l2::fp32_ext4_l2_pcs_roundtrip_and_stage2_rejections
20 10.0 akita-pcs::akita_fp128_e2e::fp128_dense

Regressions vs main

Δ s Baseline s Current s Test
52.9 104.7 157.6 akita-planner::schedule_params::adaptive_search_tests::adaptive_frontier_matches_unpruned_traversal_and_hand_priced_role_optima
46.8 59.3 106.1 akita-planner::planner::setup_prefix_challenge_hook_tests::catalog_binding_revalidates_recursive_setup_prefix_challenge_hook
16.9 16.8 33.7 akita-planner::planner::totality_tests::noncontractive_multi_chunk_root_can_beat_contractive_candidates
16.2 16.2 32.4 akita-planner::generated_families::tests::generation_uses_requested_geometry_for_preplans_and_grouped_producers
14.4 12.1 26.5 akita-planner::schedule_params::adaptive_search_tests::adaptive_nv36_minimizes_setup_envelope_before_first_direct_setup
12.3 11.2 23.5 akita-planner::planner::totality_tests::valid_small_grouped_root_has_a_schedule
12.0 10.2 22.2 akita-planner::schedule_params::adaptive_search_tests::production_suffix_selects_l2_with_the_typed_response_model
8.2 3.2 11.4 akita-pcs::transcript_hardening_proptest::stream_fuzzes_batch_shapes
7.3 9.6 16.8 akita-pcs::scheme::tests::cross_mode::proofs_cannot_replay_across_valid_quotient_and_reduced_schedules
6.3 5.6 11.8 akita-setup::tests::disk_persistence::combined_recursive_prefix_registry_persists_and_serves_each_family
5.4 3.1 8.5 akita-sis-estimator::search_mode_parity::full_exhaustive_parallel_matches_certified_search_on_medium_subset

New slow tests

No new tests ≥30s vs main baseline.

…e message

Spongefish absorbs are associative, so a group sent as one message gives
the same argument string and challenges as one message per atom. An empty
group makes no absorb call, matching the per-atom loop it replaces.
Add embedded prover and verifier entries that run on a borrowed Spongefish
state; the standalone entries build a state and call them. Akita binds its
own statement in both modes: bind_instance absorbs the length-prefixed
protocol tag and descriptor, and the root absorbs a 32-byte CommitmentDigest
per group, computed by CommittedGroup from its canonical bytes. Grinding
borrows the state; the verifier returns a must-use PendingEof whose
check_eof yields acceptance, and poisons the borrowed state on any error.
Drop the WitnessCommitment and TerminalTFields re-absorbs of values already
absorbed when sent. Protocol version 9; known answers regenerated for both
sponges.
The proof-stream guardrail forbids Validate::No in proof-input crates,
tests included.
J7 replaces ProverTranscript::field_challenges with
FieldTranscript::challenge_vector, which yields the same bytes. Port the
two channel challenge reads and move every jolt pin, including the fuzz
and recursion-profile workspaces, to the J7 rev so Jolt can patch one
shared jolt-field, jolt-poly and jolt-transcript identity.
@quangvdao
quangvdao force-pushed the feat/embedded-transcript branch from de995f4 to bbba19b Compare October 21, 2026 17:56
quangvdao added a commit to quangvdao/jolt that referenced this pull request Oct 21, 2026
Stage 0 absorbs each Akita commitment's metadata and its group's 32-byte
digest. Stage 8 runs Akita's embedded prover and verifier on Jolt's own
transcript state, and verification ends with Akita's end-of-proof check.

This removes the second Akita transcript (bridged_akita_session), the
setup absorb (append_verifier_setup), the duplicate statement absorbs and
shape_guard.rs. The commitment wire now carries the group's canonical bytes.

Other changes:
- pin Akita to LayerZero-Labs/akita#254 (bbba19bf4)
- add jolt-openings::GroupBatchOpening for the stage-8 batch API
- move the Akita program-preprocessing digest domains to v2 and re-pin
  the goldens
- re-bless the FS inventories; the scanner records transcript hand-offs
- add a full-verifier argument-string tamper test
- update the jolt-akita scheme tests to the new binding
- refresh the fuzz lockfiles
J7 (a16z/jolt#2110) was rebased, so 6691ec7cb is no longer on any ref.
At d04df1bb5, jolt-transcript's default sponge is Keccak alone; no Akita
crate relies on it, because every manifest sets default-features = false.
Keeps this branch's J7 pin (a16z/jolt@d04df1bb5) over #253's J6 pin and
takes #253's compile-time check that TRANSCRIPT_SECURITY_BITS equals
jolt_transcript::SECURITY_BITS.
quangvdao added a commit to quangvdao/jolt that referenced this pull request Oct 21, 2026
a16z#254's new head re-pins Akita's own jolt crates to J7's rebased head and
updates a spec; Akita's code is unchanged.
quangvdao added a commit to quangvdao/jolt that referenced this pull request Oct 21, 2026
That head merges a16z#253's latest commit, which adds a compile-time check
that Akita's transcript security target equals Jolt's.
Takes A2's Jolt pin (J6, efae45b3d) and its fuzz library. The fuzz
targets now use this branch's single-label channel constructors.
J7 contains A2's J6 pin and adds the FieldTranscript surface this branch
uses.
quangvdao added a commit to a16z/jolt that referenced this pull request Oct 24, 2026
send_field, receive_field, send_extension and receive_extension are gone.
Use one-element field groups and the extension exchange instead.
quangvdao added a commit to a16z/jolt that referenced this pull request Oct 24, 2026
Versions move once per stack, counted from main. Akita main is at 7 and
#253 already sets 8, so this branch's tags go back from v9 to v8. The four
known answers that read the tags (transcript, two commitment digests and
the sumcheck cutover) are regenerated on both sponges.
Merge origin/feat/jolt-transcript-j3 into hashfix-emb, preserving embedded transcript binding and J7 integration. Resolve standalone and commitment session derivation with SHAKE128. Keep J7 pins and embedded known answers pending the separate dependency and fixture updates.
Use the J7 head with jolt-transcript SHAKE128 private coins and session identifiers. Refresh all three lockfiles minimally, remove the obsolete spongefish hash dependency, and align the Book source links with the pin.
Record the exact challenge, proof, and commitment digest bytes produced by SHAKE128 session derivation for both transcript sponges. Preserve all assertions and the embedded transcript binding; descriptor digests remain Blake2b-256.
@quangvdao
quangvdao marked this pull request as ready for review October 27, 2026 04:02

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

implementation PR contains implementation of a spec spec PR contains a spec

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant