Hekate v0.34.0
On this page
Two protocol rewrites, each closing a class rather than an instance.
Every proof published the raw column evaluations of every committed
column of every table, the ZeroCheck round polynomials above their
linear coefficient, the LogUp claimed_sums and h_evals, the
eval-sumcheck rounds and two bare tensor folds. Those are linear
functionals of the witness at a point any observer recomputes from the
transcript: an academic leak on a wide dense trace, exact recovery on
a witness of a few secrets pinned to known rows, passive, from one
proof, by every party that ever sees it. Separately, every bus
selector in the workspace was a witness Bit. One write cleared a row
from the bus and switched off every root gated on it, and the row
stayed committed, opened and free; the verifier meanwhile evaluated
whichever Program the caller handed it and walked the author's own
declarations before doing algebra, accepting every shape it did not
recognise.
0.34.0 one-time-pads every exposed scalar and re-proves the verifier's
whole accept predicate in an outer zk-Ligero argument over GF(2^128),
pins liveness to fixed columns on every shipped table and deletes the
structural validators from the verify path, and makes verify take
the audited program_id as its first argument. Proof wire v4 to v5,
bundle v2 to v4, every program_id in the workspace changes. First
AGPL-3.0-only release. Pair with the signed prover cdylib 0.12.0.
Upgrade is mandatory and every proof must be re-proved.
Full analysis of the three gaps, what each one admitted, and the cost accounting: postmortem.
§ Zero knowledge (#26)
Masking the claims alone cannot work. The verifier's consistency check
evaluates F(c) = Σ_k α^k · Constraint_k(c) at the AIR's degree from
plaintext, and any scheme that lets it evaluate F on the true c
reveals c; an additive mask published on the wire is public-coin
arithmetic, subtracted by the verifier and by every observer alike.
The nonlinearity is discharged into an argument instead.
- The recovery is arithmetic, not a side channel. For a witness
constant over a known block of rows and zero elsewhere,
W̃_j(r_final) = Σ_slot secret_slot · c_slotwith everyc_slotpublic,r_finalre-derived from the transcript, and the row map fixed program structure. A secret at two known rows divides out of one equation; four candidate slots give two equations per column, and the active-slot mask is usually a public input. Values, keys and per-record randomness are all in that regime. - Every exposed scalar is one-time padded.
Pad::deriveexpands a 32-byte seed in counter mode, and the seed is bound to the statement rather than taken from the caller verbatim, hence a reused blinding seed still yields an independent pad. The pad is committed as a Ligero row oracle, andpad_rootis absorbed after the main header and every chiplet header, before any challenge that depends on it. Pad indices are derived from the statement on both sides and never read from the wire. The statement size is a pure function of program and config, and a cursor that does not land exactly onmasked_scalarsis rejected. - The verifier's predicate is re-proved in outer zk-Ligero. Over an additive-FFT coset of GF(2^128), with geometry derived on both sides from the statement size alone and never read from the proof, because a prover-chosen geometry is a free parameter that voids the query bound. Three tests over the two stacked oracles: interleaved, linear, quadratic.
- The mask rows reach the whole response space. A plain mask row
cannot cover the high half of a product-code response. Each masked
test uses
u_a + Z · u_bwithZthe novel-basis message vanisher at indexk; the pair spans all of degree< 2kwhile every committed row stays insideRS[n, k]. Responses ship as novel-basis coefficients, which makes the degree bound structural rather than scanned and removes3 · n · 16bytes from the wire. - Every moved check has a row. Fixed-column MLE compare, the AIR
root sum, boundary, the blinding telescope, LogUp consistency and
bus sum,
h · keyas a real product, both eval finals, theMuloperand triples, the ring-unit claims, andΣ claimed_sum = 0perbus_id. Dropping a row silently un-enforces that check under ZK. - Ring-unit claims needed their own gadget. The target there is
F_2-linear rather than F-linear, and no affine row over GF(2^128)
expresses it. A linearized polynomial evaluated by a
Frobenius-Horner chain over the dual basis does, over pad unknowns
only, with no claim and no public value entering the chain.
Construction, completeness, uniqueness and cost are in
docs/zk-ring-switching.pdf, which closes the open problem Diamond left in ring-switching.
§ Zero-trust verify path (#27)
One rule applied throughout: a verify-path check is safe iff its answer is a total function of data the verifier recomputes or compares by equality, with no case analysis over author-chosen structure.
- Liveness is a fixed column. 48 of 57 shipped endpoints selected
on prover-written Bits. Every endpoint is now
Noneor a member of its table'sfixed_columns(), enforced byvalidate_fixed_selectors: exhaustive over the enum, no accepting arm, atChipletDef::from_air,ChipletDef::from_wire,Circuit::compile, and verify entry for main and every chiplet.FirstRow,LastRowandCustomsubstitute a virtual poly the LogUp reader cannot consume and are refused as selector shapes. Two shapes carry the schedules:FixedShape::Cadence { stride, count, origin, values }, a strided block with a per-period pattern at any origin, andFixedShape::Segments, a disjoint union of them sorted by origin, with overlap or disorder rejected rather than normalised. - The verifier pins its circuit.
program_idis the first argument ofverify, recomputed from the verifier's ownProgram, compared, and absorbed as the first transcript message. It never travels in the proof or the bundle: a proof-carried id is a label the adversary picks, and the bundle already carries the program the id derives from. The threat is drift in the operator's build, where a prover and verifier rebuilt from one drifted tree still agree with each other, and the pinned constant is the one value living outside the code that drifts. - The structural validators are gone from verify.
validate_clock_stitching,validate_bus_set,validate_paired_bus_mutexandunconstrained_bit_selectorseach read author-chosen structure and each had an accepting default arm. The verify path now runsvalidate_fixed_selectors,validate_fixed_columns, sumcheck, Brakedown openings, LogUp consistency and cross-bus sum, the fixed-column MLE compare, and the boundary compare. Nothing reads the AST or the specs for shape. Clock waivers remain authoring-time documentation with a citation lint. - Roots came out, not columns. A pinned selector is the same
committed Bit column and the trace layout is unchanged. Deleted:
selector booleanity everywhere, roughly 30 chain-shape roots each on
Keccak and AES, 37 on the ML-DSA control table. ML-DSA also sheds
the never-written
kec_input_bindbus, whose endpoints were both inert, and thirteen dead columns at 33 physical cells per row.
§ Authoring
A host that takes the new crates without pinning its own bus selectors is rejected at circuit construction and again at verify entry.
Circuit.column,schema,columns,expand_bits,reuse_pass_through,fix,boundary,call,bus,mount,mount_unlinked,attach,publish,compile. Column indices, chiplet offsets and inline kernel root offsets are derived, never hand-computed.publish(col, row)is the only public-input slot mint and records theBoundaryConstraint::with_public_inputpin.ServiceandServiceSlot. The serving chiplet declares its bus key schema once;request()andrespond()derive both endpoint specs from it. Slots:Value,Const,RequestIdx,RequestIdxBytes.- Constructors take the count that sizes the schedule. Keccak,
both AES levels and the S-box ROM, RAM, ROM, integer arithmetic,
NTT, twiddle ROM, base multiplication, norm check, high bits, and
both post-quantum control tables.
MlDsaChiplet::newtakesmsg_len, and that length lands inprogram_id: one id per accepted length. Trace generation errors upfront on schedule overflow instead of truncating silently. - Preflight.
BusDiagnostic::clock_collisionsreports emit rows whose row-index clock bits fold to one value, masked tonum_vars;BusDiagnostic::inertreports a bus no endpoint emits on. Witness deviation from a pin reports asfixed_column_violations.
§ Gauges
algebraic_and_evaluation_perfect_hiding was green and proved
nothing: it asserted that a non-constant polynomial takes different
values at different random points, and would have passed with
trace-column hiding entirely absent.
eval_claim_recovery.rs,zk_security.rs: recompute the MLE offline at the replayedr_finaland flip. Raw mode recovers the secret, ZK mode does not.wire_census.rs: every published field element of a real proof maps to one line of the simulator's view inventory, destructuring every wire struct with no.., hence a new field element cannot reach the wire without breaking the build until someone classifies it.hiding_rejections.rs: bumps each absorbed value and each outer segment field in turn, pinning every moved check listed above.generator.rs: 64 fixed-seed samples over geometry, four gate families, bus topology and ZK on one seed in three. Per sample the honest proof verifies, a forged public input is rejected, a drifted id is rejected asProgramIdMismatch. 1.3 s release.- Checked-in floors and parity suites pin the rest: every selector
Noneor fixed, the per-table roots that fire on a zero trace, empty-trace and short-chain forgeries,Circuitoutput against hand-authored twins, the statement compiler againstast.evaluate, and every declared fixed shape against its generated trace at all six post-quantum levels. hekate-scribbleloses its control-language module and both transition-table suites;MutationKind::FlipSelectoris now a fixed-column mismatch on every table.
§ Security accounting
The inner figure is min(ldt_bits, proximity_bits) with
proximity_bits = field_bits - log2(encoded_width), the BCIKS20
proximity-gap term of the random column fold; it was previously capped
at the field width, which credited a term the protocol does not
deliver. At an 8192-wide row code the LDT term is over 160 bits and
the proximity term is 115, and the smaller one governs. The outer
figure takes the minimum of the Ligero query term and its additive
(e + 1) / |F| term. MIN_PRODUCTION_BITS moves from 128 to 110 to
match: nothing became weaker, the number became honest, and it is now
a floor a real geometry can clear rather than a label.
§ Performance
Signed public cdylib 0.12.0, table-math, Apple M3 Max,
-C target-cpu=native, three runs per cell: fastest for time, mean
for proof size, lowest for peak. Peak is the larger of
phys_footprint_peak and ru_maxrss. Cells are ZK / base, and ZK is
the default from this release.
| Workload | Prove | Verify | Proof | Peak mem |
|---|---|---|---|---|
| ML-KEM-768 | 629 / 579 ms | 38.4 / 19.1 ms | 3,942 / 3,335 KiB | 450 / 422 MiB |
| ML-DSA-44 | 857 / 777 ms | 59.5 / 22.5 ms | 4,781 / 4,031 KiB | 451 / 435 MiB |
| ML-DSA-65 | 892 / 820 ms | 59.7 / 22.6 ms | 4,789 / 4,051 KiB | 450 / 401 MiB |
| ML-DSA-87 | 1.35 / 1.25 s | 62.1 / 26.7 ms | 6,253 / 5,371 KiB | 800 / 749 MiB |
| AES-128 | 1.29 / 1.23 s | 20.7 / 16.4 ms | 5,902 / 5,243 KiB | 1,183 / 1,121 MiB |
| AES-256 | 1.41 / 1.34 s | 21.7 / 16.7 ms | 6,241 / 5,598 KiB | 1,478 / 1,421 MiB |
| keccak_inline 2^15 | 211 / 186 ms | 16.4 / 5.1 ms | 1,083 / 782 KiB | 169 / 131 MiB |
| keccak_inline 2^20 | 3.73 / 3.79 s | 24.4 / 12.3 ms | 4,502 / 3,985 KiB | 2,486 / 2,457 MiB |
| fibonacci_raw 2^20 | 336 / 287 ms | 6.1 / 2.5 ms | 1,295 / 738 KiB | 250 / 165 MiB |
| fibonacci_raw 2^24 | 5.13 / 4.47 s | 11.6 / 5.7 ms | 4,558 / 2,841 KiB | 3,508 / 2,215 MiB |
| fibonacci_raw 2^26 | 23.05 / 18.42 s | 18.9 / 9.3 ms | 8,903 / 5,630 KiB | 13,846 / 8,712 MiB |
Against 0.33.0's published means the base arm proves 7 to 17% faster,
and the ZK arm lands between 4% slower and 10% faster. Hiding is what
consumes that window: up to 13% on prove time, 11 to 19% on proof
size, and 1.3 to 3.2 times the verification. keccak_inline 2^15 pays
39% on proof size, being the one workload small enough for a fixed
outer segment to dominate.
The outer argument is 160 to 260 KiB and tracks the statement shape
rather than the trace height. Everything above it is the blind unit,
one committed column per master. fibonacci_raw is five physical
columns wide, and there the blind column pushes the commitment grid to
the next power of two: at 2^26 the tensor vectors go 1,117,517 to
2,231,629 bytes, exactly grid_cols 2^16 to 2^17, which is also where
its 58 to 75% proof-size delta and its peak memory go.
Pinned liveness costs one shape MLE per pinned column at verify: 0.5 to 1 µs per small-stride block, about 11 µs per 107-entry pattern block, height-independent. A full composite's pins land under 0.5 ms against the 38 to 62 ms composite verifies above.
§ License
Hekate moves to AGPL-3.0-only with this release. Every version up to and including 0.33.0 stays Apache-2.0 and that grant is not revoked. The project is dual-licensed: commercial licenses are available from Oumuamua Labs, info@oumuamua.dev.
Linking the open workspace against the proprietary prover shared library is covered by LICENSE-EXCEPTION, an additional permission under AGPL section 7. It covers that library and nothing else, grants no right in it, and does not relieve a network operator of section 13 with respect to the Hekate portion of a combined work.
§ Breaking
- Wire format. Proof v4 to v5, adding
pad_rootand anOuterProofofaux_root, three responses and twoOuterOpeningrecords. Bundle v2 to v4: the config table shedsinv_rateandsumcheck_blinding_factorand gainsouter_queriesandzero_knowledge; the fixed-column table carriesCadenceandSegments, digest tags 6 and 7. Predecessors fail at deserialization. Re-prove. program_id. Changes for every program in the workspace. Fixed columns are part of identity and every table gained some; the domain tag ishekate-program-id-v2. Deployments pin the new constant.verifysignature.verify(program_id: &[u8; 32], program, instance, proof, transcript, config), withError::ProgramIdMismatch { actual }on mismatch.- Transcript.
program_idis absorbed first, beforenum_columns.zero_knowledgeandouter_queriesjoin the config values absorbed before the first challenge.pad_rootfollows the main and chiplet headers; the outer segment runsaux_root,outer_r,outer_w,outer_r_lin,outer_q,outer_r_quad,outer_p0, then oneouter_idxper query. Config.inv_rateandsumcheck_blinding_factorremoved,outer_queriesandzero_knowledgeadded.inv_ratebecomes the constantINV_RATEalongsideOUTER_RATE_LOG2andOUTER_ROWS, because a caller-tunable rate is a divergence waiting to happen. Callers that set a blinding factor usezero_knowledgeand readblind_units().Config::default()isprod()and proves in zero knowledge; the raw protocol is asked for.- Security floor.
MIN_PRODUCTION_BITS128 to 110, with the proximity term now counted. A geometry that passed on the old cap and fails now was never delivering the number it reported. - Admission. A bus selector that is not
Noneor an overlay-shaped fixed column is rejected atChipletDef::from_air,ChipletDef::from_wire,Circuit::compileand verify entry. An AIR whosenum_columnsdisagrees with its virtual layout, or whose sumcheck degree needs more than 16 interpolation points, is rejected at admission instead of panicking later. - Constructors. Every chiplet constructor gains its count or
schedule parameter;
MlDsaChiplet::newgainsmsg_len. - Moves.
hekate_sdk::{program_id, program_id_hex}arehekate_program::digest::{program_id, program_id_hex}, withprogram_id_ofadded. - Deleted public API.
validate_paired_bus_mutex,unconstrained_bit_selectors,assert_paired_bus_mutex,fixed_gated_witness_roots,fix_narrowed,validate_justification, thehekate_scribblecontrol-language module, preflightselector_mutex_violations, andSecurityMetrics::soundness_error, the last reasonhekate-corewould not build as trueno_stdwithout rayon in the graph. - New public API. Modules
hekate_core::{outer, ligero},hekate_program::{circuit, predicate, outer, linearized},hekate_verifier::outer. TypesFixedShape::{Cadence, Segments},CadenceSegment,Service,ServiceSlot,NttSchedule,NttRun,OuterGeometry,Pad,Masking,RowEncoder,OpenedWeights,Opening,ProductMask. Functionsvalidate_fixed_selectors,FixedColumn::{prefix, segments},FixedShape::is_overlay,Config::{outer_geom, outer_security_bits, outer_field_term_bits, check_outer_security, blind_units},verify_outer,weights_at_columns,mlkem::{ntt_forward_traced, ntt_inverse_traced},BusDiagnostic::{clock_collisions, inert},SecurityMetrics::proximity_bits. - Features.
hekate-coregains an optional rayon dependency behind its existingparallelfeature;hekate-verifier'sparallelnow forwards tohekate-coreandhekate-program. - Prover cdylib 0.12.0, signed (SHA-256, Ed25519, ML-DSA-65). 0.11.0 neither produces nor reads v5 proofs.
- License. AGPL-3.0-only, or a commercial license from Oumuamua Labs.
- Version. Workspace 0.33.0 to 0.34.0; hekate-keccak and hekate-aes 0.7.0; hekate-pqc 0.8.0.
§ Not promised
- Circuit semantics. A pinned id binds which circuit is verified, not that it computes what its name says.
- Determination of the published cell by the AIR. The engine gives the construction and does not certify its use. A certifier answering an undecidable question over author-chosen structure maps every shape it fails to recognise to accept, which is the defect this release removed.
- Workload privacy. Fixed liveness makes activity patterns public by construction. Nothing new leaks in the shipped set.
- Data-dependent activity counts. Pin every row and fill with canonical no-ops. No shipped circuit needs the alternative.