transcript

the Fiat-Shamir transcript converts zheng's interactive proof into a non-interactive one. the prover maintains a running hemera hash of every message exchanged. each verifier challenge is derived by hashing the transcript so far. the verifier recomputes the same transcript and checks consistency.

~3 hemera calls total per proof: (1) domain separation initialization, (2) Fiat-Shamir seed from commitment, (3) Brakedown binding hash. remaining challenges use the algebraic Fiat-Shamir path — field arithmetic derived from the sponge state without additional hash invocations.

construction

transcript state: H = hemera_init(DOMAIN_SEP)

DOMAIN_SEP = hemera(0x01 | "zheng-transcript-v1")

absorb(message):
  H = hemera_absorb(H, message)

squeeze(n_challenges):
  challenges = hemera_squeeze(H, n_challenges)
  H = hemera_absorb(H, challenges)
  return challenges

the transcript is a sponge: absorb prover messages, squeeze verifier challenges. hemera's sponge construction (Poseidon2 with 1024-bit state, 512-bit capacity) provides 256-bit security against transcript manipulation.

domain separation

each proof phase uses a distinct domain separator to prevent cross-phase attacks:

phase separator purpose
commitment 0x02 | "commit" binds Brakedown commitment to transcript
sumcheck round i 0x03 | i each round gets a unique challenge domain
evaluation 0x04 | "eval" separates evaluation point from constraint checks
PCS opening 0x05 | "pcs-open" prevents reuse of sumcheck challenges in PCS
recursive 0x06 | "recurse" inner proof transcripts isolated from outer

domain separators are absorbed before the corresponding message. this ensures that identical messages in different phases produce different challenges.

transcript format

a serialized proof contains the full sequence of prover messages. the verifier reconstructs the transcript by absorbing each message in order and checking that derived challenges match.

proof = [
  commitment: [u8; 32],           // Brakedown commitment (hemera digest)
  sumcheck_polynomials: [         // one per round
    [GoldilocksElement; deg+1],   // coefficients of univariate g_i
  ],
  evaluation_value: GoldilocksElement,  // f(r) at sumcheck output point
  pcs_opening: BrakedownProof,    // recursive Brakedown opening proof
]

the verifier processes this sequentially: absorb commitment -> squeeze sumcheck challenges -> absorb each sumcheck polynomial -> squeeze next challenge -> ... -> absorb evaluation -> verify Brakedown opening.

binary encoding

all multi-byte integers are little-endian. all field elements are in canonical form (value < p where p = 2^64 - 2^32 + 1).

GoldilocksElement

8 bytes. the canonical u64 representation in little-endian byte order. the value must satisfy 0 <= v < p. any encoding with v >= p is rejected by the verifier.

GoldilocksElement := u64_le(v)    // 8 bytes, v < 2^64 - 2^32 + 1

commitment

32 bytes. a hemera digest consists of 4 GoldilocksElements concatenated in order, each encoded as 8 bytes LE.

Commitment := GoldilocksElement[0] || GoldilocksElement[1] || ... || GoldilocksElement[3]
           // 4 * 8 = 32 bytes

sumcheck polynomial (per round)

each round emits one univariate polynomial g_i of degree d. the encoding is:

SumcheckPoly :=
  degree: u8                          // 1 byte, value d
  coefficients: GoldilocksElement[d+1] // (d+1) * 8 bytes

coefficients are in ascending order: [c_0, c_1, ..., c_d] where g_i(X) = c_0 + c_1X + c_2X^2 + ... + c_d*X^d. each coefficient is an 8-byte LE u64 in canonical form.

total per round: 1 + 8*(d+1) bytes.

evaluation value

8 bytes. a single GoldilocksElement encoding f(r) at the sumcheck output point.

EvaluationValue := GoldilocksElement    // 8 bytes LE

BrakedownProof

the Brakedown opening proof encodes recursive tensor reductions:

BrakedownProof :=
  num_levels: u8                        // log log N recursion levels
  for each level:
    commitment: Commitment              // 32 bytes (Brakedown commitment of opening vector)
    tensor_response: [GoldilocksElement] // tensor reduction at this level
  final_elements: [GoldilocksElement; lambda]  // <= lambda direct field elements

the number of recursion levels and tensor dimensions are determined by the Brakedown parameters. the verifier knows these from the public configuration.

full proof wire format

Proof :=
  commitment: Commitment               // 32 bytes
  num_rounds: u16_le                   // 2 bytes
  sumcheck_polys: SumcheckPoly[num_rounds]
  evaluation: EvaluationValue          // 8 bytes
  pcs_proof: BrakedownProof

the prover writes fields in this exact order. the verifier reads them sequentially, absorbing each into the Fiat-Shamir transcript as it goes.

proof size

for N = 2^20 (typical nox trace), 128-bit security:

  • commitment: 32 bytes
  • num_rounds: 2 bytes
  • sumcheck_polys: ~660 bytes (20 rounds * ~33 bytes each)
  • evaluation: 8 bytes
  • pcs_proof: ~1,300 bytes (log log N levels of recursive tensor openings + lambda final elements)
  • total: ~2 KiB

proof size is constant regardless of original computation size.

properties

property value
hash function hemera (Poseidon2 over Goldilocks field)
hemera calls ~3 per proof
state size 1024 bits (16 field elements)
capacity 512 bits (8 field elements)
security 256-bit classical, 170+ bit post-quantum
challenge type native Goldilocks field elements (no truncation)
domain separation per-phase prefix absorb

soundness

if hemera behaves as a random oracle, the Fiat-Shamir transcript is as sound as the interactive protocol. the soundness error per sumcheck round is at most d/p, where d is the polynomial degree and p = 2^64 - 2^32 + 1. across k rounds, total soundness error <= kd/p — negligible for any practical parameters.

the critical property: challenges are native Goldilocks field elements. no reduction, no truncation, no modular bias. hemera outputs field elements directly.

see sumcheck for the protocol that generates transcript messages, Brakedown for the PCS opening proof format, hemera for the hash construction

Dimensions

transcript

Local Graph