refactor(jolt-field): Land Solinas field from akita-field in jolt-field - #1684
refactor(jolt-field): Land Solinas field from akita-field in jolt-field#1684Acentelles wants to merge 41 commits into
akita-field in jolt-field#1684Conversation
Move the Solinas field stack from Akita revision 3b674abb66186037745af256e6a20d60bdcd7e89 into jolt-field behind a new `solinas` feature: 32/64/128-bit pseudo-Mersenne prime fields, degree 2/4/8 extension fields, packed NEON/AVX2/AVX-512 backends, unreduced accumulators, smooth-domain FFT helpers, and a `parallel` feature gating rayon. Jolt's existing capability traits remain canonical; Solinas-only operations live in narrow capability traits in solinas_traits.rs. FieldError carries field-layer input and size failures only. The temporary `akita` feature, its optional akita-config/akita-field git-pinned dependencies, and src/akita.rs are retained as a bootstrap edge so the current jolt-akita revision keeps building; they are removed in the final migration PR.
Move the Criterion field-arithmetic benchmark from Akita as benches/solinas_field_arith (with plonky3 baselines as dev-only comparisons) and add a Solinas arithmetic fuzz target to the field fuzz workspace.
New field-stack job checks jolt-field with no backend, BN254, Solinas, and combined backends, runs the Solinas test suite, and enforces the shared field package identity: exactly one jolt-field in the workspace graph, with the pre-cutover akita-field allowed only from the immutable bootstrap Git pin. The final migration PR tightens the check to reject every akita-field identity.
|
Warning This PR has more than 500 changed lines and does not include a spec. Large features and architectural changes benefit from a spec-driven workflow. If this PR is a bug fix, refactor, or doesn't warrant a spec, feel free to ignore this message. |
jolt-field
jolt-fieldakita-field in jolt-field
|
Once LayerZero-Labs/akita#307 is merged, we can remove the temporary akita-field compatibility dependency from jolt-field and change |
Plan to shrink jolt-field from 46 public traits to at most 22: delete dead surface, merge single-purpose capability traits, adopt the arkworks/bn254.rs per-type impl style, and move wire serialization of the Solinas types to serde + bincode. Supersedes the granularity of unify-field-hierarchy.md while preserving its layering invariants.
Remove traits with no generic consumer in this workspace or akita: - SignedScalarAccumulator/WithSmallScalarAccumulator and SignedProductAccumulator/WithSignedProductAccumulator families, including the Fr and Naive implementations and the two bn254_ops kernels only they used; the Field umbrella no longer requires them - ExtensionCoeff (single blanket impl; bounds inlined at use sites) - BalancedDigitLookup (replaced by the free fn balanced_digit_lut) - SmoothFftField and fft.rs (no consumer outside their own tests) MontgomeryConstants is retained pending confirmation that no out-of-tree GPU backend consumes it.
…phase 2) AdditiveAccumulator and RingAccumulator were only ever implemented and consumed together (WideAccumulator, NaiveAccumulator); merge them into a single Accumulator trait carrying add/merge/reduce plus the fmadd family. WithAccumulator moves into accumulator.rs and now supertraits RingCore + FromPrimitiveInt, which its associated type already implied. Because WithAccumulator now guarantees Accumulator<Element = Self> by declaration, the 18 'Accumulator: RingAccumulator<Element = F>' where-clauses across jolt-crypto, jolt-blindfold, and jolt-verifier are redundant and removed.
…rmat (spec phase 3) Merge the fine-grained capability traits into cohesive ones: - FieldCore absorbs Invertible (inverse, inv_or_zero) and RandomSampling - FromPrimitiveInt gains a RingCore supertrait and absorbs the MulPow2 and MulPrimitiveInt default-method helpers - new CanonicalRepr (the Fiat-Shamir transcript surface) replaces CanonicalBytes, ReducingBytes, FixedByteSize, FixedBytes<N>, CanonicalU64, CanonicalBitLength, and TranscriptChallenge; per-type challenge derivations (Fr's masked 125-bit path) move over verbatim - the crate root shrinks from 15 one-trait micro-files to algebra.rs, canonical.rs, accumulator.rs, and field.rs Wire serialization of the Solinas types is now serde + bincode, mirroring Fr's existing canonical [u8; N] pattern: prime fields encode as exactly NUM_BYTES bytes with checked canonical deserialization, extension fields as [F; K]. Fiat-Shamir bytes stay on CanonicalRepr's explicit encoding, never bincode. New serde_roundtrip tests pin per-element sizes, single length prefixes on vectors, and rejection of non-canonical encodings, so proof size cannot grow. The mersenne61_compat and Gf2 compatibility tests keep passing with merged bounds and no arkworks dependency.
…phase 4) - ExtField<F> absorbs LiftBase (lift_base), MulBase (mul_base), and FrobeniusExtField (frobenius_pow / frobenius_inv_pow); one trait now carries the full extension contract. The FpExt2/4/8 impls require a pseudo-Mersenne base, which every current base is. - FpExt4MulBackend and FpExt8MulBackend merge into a single ExtMulBackend with the same three implementors; Fp32 keeps its fused delayed-reduction quartic overrides. - The degree-4 mul/square schedule now exists once: the packed PackedField defaults call the shared fp_ext4_mul_coeffs / fp_ext4_square_coeffs (previously copy-pasted verbatim), matching how degree-8 already shares its schedule.
Every concrete type's trait surface now lives in its own file, with three shared macros carrying the mechanical expansions: - impl_native_ring_algebra! / impl_native_additive! (new native_algebra.rs) replace the three per-module native_algebra.rs side-files, including the 226 hand-written lines in ext/; invocations sit in each type's file - impl_prime_ops! collapses the 30 hand-duplicated operator impl blocks across Fp32/Fp64/Fp128 into 3 invocations; the *_raw reduction kernels stay hand-written per type - select_packing! collapses the three near-identical cfg cascades that choose the packed backend per prime width - impl_prime_native_capability! invocations move into the per-type files Also finishes the disposition table: ScaleI32 merges into ReduceTo, PackedValue merges into PackedField, and jolt-field's OptimizedMul is deleted outright (jolt-prover-legacy has its own identical trait; the jolt-field copy had no consumer). jolt-field is now at 22 public traits. Criterion solinas_field_arith before/after shows no regression on any of the 20 benchmarks.
The fuzz crate's target/ directory (630 generated files) was accidentally committed in d8b2830. Remove it from the index and ignore it going forward. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- repair the from_bytes fuzz target's duplicate import (the fuzz crate is its own workspace, so workspace clippy never compiles it) - dedupe mechanical-rename residue bounds in the solinas benches and jolt-sumcheck's SumcheckScalar - move the Solinas prime traits (CanonicalField, HalvingField, PseudoMersenneField, balanced_digit_lut) from the crate root into prime/traits.rs per the spec's layout - amend the spec: OptimizedMul was deleted as a duplicate of jolt-prover-legacy's own trait rather than kept; record the actual Solinas-trait location and the montgomery_constants.rs remainder
8d58e10 to
9aff1cd
Compare
Standard-mode muldiv proofs from this branch and main are identical in all 63,371 bytes when both builds share a Dory URS, and the main-built verifier accepts the branch-built proof. cfg(test) builds randomize the URS per process (DoryGlobals::configure_test_cache_root), so the comparison requires pinning a shared URS cache. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
markosg04
left a comment
There was a problem hiding this comment.
I think there is still hopefully some room to clean up this crate and especially the surface area of the solina-related traits and impls.
You can find a spec and the first few components implemented for such additional cleanup here: https://github.com/a16z/jolt/tree/feat/field-crate-refactor. The tl;dr is that I think this crate can probably be done in half the src loc than what it is now, and we can make the distinction between the backend and impl for the solina stack clearer. I also am generally a fan of using macros for these types of primitive crates because AI are very efficient with them and they seemingly help to catch/prevent some context drift.
Two arithmetic bugs found by the jolt-field-two differential harness (see PR a16z#1684 review), both predating this PR's refactor: - Fp64::reduce_u128 cast the fold's high part to u64. For sub-word primes (40/48/56-bit) inputs >= 2^(64+BITS) reduced incorrectly, which includes essentially all 16-byte Fiat-Shamir challenge derivations. The fold multiply now stays in u128 (bounded by 2^127 since the registry enforces C(C+1) < P). Fp32's fold was already correct. - S160::mul_magnitudes fused two (three) full 64x64 products into one u128 cross-term sum, overflowing for large second limbs: debug panic, release wraparound. Word 1 now uses overflow-tracked accumulation (the true carry into word 2 reaches 2^65 and must include the overflow bit at weight 2^64); word 2 uses wrapping adds since only its low 32 bits survive in the 160-bit result. The N=1 arm was safe (32-bit high limb); the general N>=3 path already accumulated safely. The identical S160 kernel exists in the arkworks fork and needs the same fix there (reported separately). Regression tests: u128-mod oracle over all registered sub-word primes incl. the challenge-bytes path, and a 32-bit-digit schoolbook oracle for S160 with boundary limbs. Both tests fail against the unfixed kernels.
# Conflicts: # Cargo.lock # crates/jolt-field/src/akita.rs # crates/jolt-field/src/transcript_challenge.rs # crates/jolt-verifier-derive/src/lib.rs # crates/jolt-verifier/src/verifier.rs # crates/jolt-witness/src/field_inline/mod.rs
- differential asserts for from_scalar_challenge_bytes on the Solinas words (all lengths) and on Fq (the Fr side already had one) - drop a dead binding left by the un-gating commit - SPEC amendments: impl_group_ops! is a third exported stamping macro; the registry has 9 offsets at this baseline, not 12; note that PseudoMersenne is defined unconditionally in algebra.rs per the file table
… split The baseline's byte surface (NUM_BYTES, to_bytes_le) moved from CanonicalRepr to its new CanonicalBytes supertrait; import both where the oracles compare encodings.
One hand-written module (528 counted LOC vs the 700 budget) porting the
baseline prime/fp128/{core,add_sub,mul,reduce,wide,primes,traits}:
[u64; 2] lo/hi representation, branchless carry-chain add/sub,
schoolbook 2x2 mul/sqr with the AArch64 inline-asm kernels kept
verbatim (1.29x on M4 per the baseline's own measurement; an
AArch64-only unit test cross-checks asm vs portable on all four
offsets), C = 2^a +/- 1 shift specialization, <=10-limb Solinas fold,
Fermat inversion, rejection-sampling random, and the four registered
128-bit primes re-exported under their baseline names.
Every reduction step carries a re-derived bound comment. One sharpening
over the baseline's argument: fold2_canonicalize is exact and canonical
for any u64 third limb given only C < 2^32 (overflow leaves the partial
sum below 2^96, so the single +C correction cannot re-overflow); the
C(C+1) < P assert is kept and documented as implied for the fused
paths.
Dropped with evidence recorded in SPEC.md: the add/sub asm kernels
(~470 lines, no in-tree benchmark, same algorithm as the branchless
portable path), mul_add and its asm, mul_wide_limbs, from_i64_const
(akita-only consumers, drop already approved).
Differential suite: per-offset parity vs the baseline Fp128 (ops
owned+by-ref, algebra identities, bincode wire bytes, canonical and
wrong-length rejection, byte/num_bits/u64 views, challenges at
8/16/32), an independent 4x64-limb schoolbook + long-division oracle,
boundary sets, and all-max limbs at every fold length 0-10. Also fixes
tests/solinas_words_differential.rs, which did not compile under
--all-features since the CanonicalBytes split (the reconcile pass ran
without the solinas feature - the recurring feature-gate lesson).
39/39 tests, clippy all-features clean, no-default-features and
solinas-only builds clean.
Contracts in src/extension.rs (54/60 counted): ExtField (degree, lift,
mul_base, coefficient access, Frobenius) and Ext2Config with the
NegOneNr/TwoNr ZSTs. MulBaseUnreduced is deferred to checkpoint 7 with
its reason recorded: the contract is stated in terms of
Unreduced::Product, which does not exist yet.
Lane-generic deg-4/8 coefficient schedules live in a new unconditional
src/schedules.rs (133/140): the PseudoMersenne kernel-hook defaults in
algebra.rs and the packed lanes (checkpoint 8) share the same formulas,
so they sit in the root layer as backend-neutral algebra; the old
ext.rs budget is re-carved 890 -> 750 + 140 with the component total
unchanged. PseudoMersenne gains the four hooks (ext4/8 mul + square);
an ext8_square hook is new relative to the baseline (which squared
degree 8 via full mul) - value-identical, fewer ops, flagged in SPEC.
Headline: the baseline's fused-accumulation Fp32 deg-4 override LOST
the SPEC's checkpoint-6 bench gate - generic default 12.3 ns/op vs
fused 31.1 ns/op for mul (2.5x), 15.3 vs 28.3 for square, on
aarch64/M4 with the ported override reproducing the baseline's own
timing (31.0) as the sanity anchor. Immediate word-sized reductions
pipeline better than u128 accumulation chains here. The override is
dropped from src with evidence in SPEC.md; the port (with re-derived
column-sum bounds) survives in benches/ext4_kernels.rs for x86-64
re-evaluation, where the verdict could flip.
Impls in src/solinas/ext.rs (617/750): Karatsuba FpExt2 with norm
inverse, FpExt4 subfield-tower inverse, FpExt8 Gaussian-elimination
inverse, Frobenius via base-modulus powers, Moore thetas/solve/
validate, serde as [F; K] coefficient arrays byte-identical to the
baseline.
Differential suite (25 new tests, 64/64 all-features total): five base
fields x {FpExt2 both configs, FpExt4, FpExt8, Moore} against the
baseline plus an independent schoolbook oracle (binary-long-division
modular arithmetic, phi-rule Chebyshev multiply), boundary coefficient
patterns, ring-identity spot checks, fixed seeds. Baseline oddities
recorded: the fused-square bits!=32 guard has no recorded rationale;
NegOneNr is a field on no registered prime (all are 1 mod 4); baseline
ext types implement no canonical-bytes trait, so ext types here are
deliberately not JoltField (parity).
…t 7)
Contracts (src/unreduced.rs, 18/70): one fused Unreduced companion
surface (Product/SmallProduct/Wide, SUM_IS_EXACT, widening muls,
reduce_product/reduce_small_product/reduce_wide, scale_wide) replacing
the baseline's HasUnreducedOps + HasWide + ReduceTo, and Fold
(precompute/fold_one) replacing HasOptimizedFold, documented as the
multilinear bind. The checkpoint-6-deferred MulBaseUnreduced lands in
extension.rs (60/60 exactly).
Impls (src/solinas/unreduced.rs, 501/530): the i32-lane wide types,
u128-slot product accumulators, AccumPair, fold matrices, and the fused
kernels with explicit carry tracking everywhere two full-width products
meet (overflowing adds; the P^2-bias branch tracks add-carry and
sub-borrow jointly, with the impossible underflow guarded by debug
panic). One wrapping-ops policy across product accumulators (the
baseline mixed conventions in Fp128MulU64Accum); FpExt8 gets single
generic impls where the baseline stamped three copies.
Baseline corrections found by re-derivation, recorded in SPEC.md:
- lane headroom is exactly 32768 additions, not the baseline's
"~32,769" (32769 * 0xFFFF > 2^31 - 1); tested at the boundary and
one-past asserts in debug
- FpExt4Fp32ProductAccum docs claimed 7*P^2 ~ 2^65 and 2^63 terms;
correct is < 2^67 and >= 2^61, re-derived per slot
- the fused FpExt2<Fp64> path silently assumed NR in {-1, 2} while
generic over any config; now debug-asserted
- Fp128 accumulator headroom sharpened to 2^64 - 1 terms (the reduce
carry chain binds)
NEON intrinsics dropped with evidence: LLVM auto-vectorizes the
portable [i32; N] ops to the identical add.4s/sub.4s/neg.4s on
aarch64 (checked via --emit asm), plus mul.4s for lane scaling which
the baseline never vectorized; ~120 unsafe lines gone and debug builds
now catch lane overflow.
Tests: 38 new (102/102 all-features): per-accumulator exactness vs
per-term multiplication and an independent limb-schoolbook oracle,
adversarial all-max batches over every carry corner, wrap-through
sequences, 18 fold-parity instantiations vs baseline, MulBaseUnreduced
override parity, SUM_IS_EXACT flag parity.
Budget flag for review: solinas/mod.rs sits at 108 vs 90 (was 102
before this checkpoint; re-exports and registry accumulate across
checkpoints) - needs the budget discussion, not golf.
One packed-algebra source of truth: the entire Solinas fold/ canonicalize algebra (fp32 2-or-3-fold with TWO_FOLD_OK, C = 2^a +/- 1 shift-add offset multiply, N-ary deferred-reduction dot products with the BITS==32 prefold, fused deg-4 kernels; fp64 dual-path add/sub and both reduce128 variants; fp128 SoA carry-chain add/sub) written once in engine.rs/fp128.rs, generic over a SimdWord vocabulary; the genuinely algorithmic per-ISA differences confined to simd.rs (AVX2's missing 64-bit multiply assembled from 32x32 products, AVX-512 mask-register selects and native mullo_epi64, NEON's vqdmulhq_s32 kernel for the 31-bit prime). Note for review: the SPEC pillar says engine *macro*; generic types over SimdWord achieve the same single-source outcome with less expansion (recorded in SPEC.md). Packed ext consumes the shared schedules.rs; select_packing! cfg cascade ported token-identically. Counted LOC 1,067 vs the 1,600 budget (engine 284/550, fp128 99/350, ext 191/230, simd 352/350). Two overages flagged for review: packed.rs 105/90 (the ext4/8 kernel hooks on Packed - overridable defaults are the only stable-Rust route for the fp32 engines' fused kernels) and packed/mod.rs 36/30 (cfg formatting of the cascade). Improvements over baseline: packed Fp128 mul calls the scalar kernel per lane on all ISAs (on aarch64 that is the inline-asm multiply, strictly better than the baseline NEON backend's duplicated portable fold); the baseline's implicit C < 2^(64-BITS) assumption in reduce128_small_k is now debug-asserted. Dropped with SPEC evidence: Mersenne31 C==1 kernels (no registered prime), BITS==31 fold clones, NEON per-C shift-add chains, NEON dot-product carry tracking (unified on prefold), vectorized ext2/4 inverses (inversion is lane-serial in every formulation). Baseline oddity: NEON PackedFp64::fp_ext2_mul override is byte-identical to the trait default (dead specialization). cfg coverage: every introduced region compiled - native NEON (tests RUN), +avx2 and +avx512f,+avx512dq x86_64-apple-darwin checks, plain x86 fallback, aarch64 -neon fallback, no-default-features and solinas-only. 14 new differential tests (116/116 all-features): packed-vs-scalar for all 12 registered primes across widths, packed ext over fp32/fp64/fp128 bases and both NR configs, lane/slice laws, NEON lane-exact differentials vs the baseline packed types. x86 backends are compile-verified only on this machine; performance re-evaluation of dropped specializations is bench-gated per SPEC.
…kpoint 9) src/solinas/parallel.rs (78/80 counted): the baseline rayon helpers ported whole, byte-faithful (expansion-site cfg so consumers dispatch on their own parallel feature). The consumer audit found that none of the seven cfg_*! macros has a single consumer anywhere in the workspace or the rebuild - no crate even enables jolt-field's parallel feature (crates that parallelize carry their own rayon deps) - so the component is recorded in SPEC.md as a deletion candidate at the replacement PR; parity scope names it explicitly, hence the whole port rather than an unevidenced subset. New tests/parallel_macros.rs proves serial/rayon equivalence for every macro (green under both configurations). Crate docs on lib.rs (two-layer architecture, backends, features, byte-compatibility invariants); code lines drop to 43/70 since docs are free under the counting rules. Final audit written into SPEC.md with actuals beside budgets: 5,103 counted LOC vs the 6,240 budget (18% under; 55% below the 11,410-LOC baseline). Six files over their file budgets, all in one consolidated budget-trades note: packed.rs 105/90 (ext kernel hooks), solinas/mod.rs 113/90 (re-exports accumulate), limbs.rs 237/220, bn254/mont.rs 310/300, packed/mod.rs 36/30, packed/simd.rs 352/350. Feature matrix all clean (check + clippy -D warnings): no-default, solinas, bn254, solinas+parallel, solinas+allocative, all-features, plus both x86_64 target-feature cross-checks in two feature configs. 121/121 tests. SPEC status: all nine checkpoints built; remaining before replacement recorded (x86 runtime validation, bench re-evaluations, the replacement PR itself).
|
Per checkpoint, relative to the spec:
Items needing reviewer judgment (consolidated in SPEC.md): six files exceed their per-file budgets (largest overage +23 lines; the recurring cost centers are re-export blocks and the four ext kernel hooks on |
markosg04
left a comment
There was a problem hiding this comment.
Nice work! Can we now delete and replace jolt-field with the new crate (and hence update rest of Jolt / akita)?
There was a problem hiding this comment.
Let's move this to specs/ and rename
…ap adapter Pre-replacement surface work: split the byte surface out of CanonicalEncoding as a bare CanonicalBytes supertrait (same decision as the jolt-field split: transcript absorption must not require the field decode contract, e.g. NoCommitment), and port the temporary akita bootstrap adapter to the new spine so the akita lanes survive the replacement. 121/121 tests, clippy clean on all feature lanes.
…lden byte fixtures) - tests/bn254_differential.rs: jolt-field oracle replaced with num-bigint arithmetic mod r/q (modpow inverses, exact wide-accumulator sums, and the legacy Fr Montgomery-form / Fq plain challenge models); wire bytes now checked structurally, absolute bytes pinned in golden_bytes.rs. - tests/limbs_signed_differential.rs: Limbs/SignedBigInt/SignedBigIntHi32 parity vs jolt-field replaced with exact num-bigint integer oracles (wrapping sign-magnitude model); existing u128/i128 oracles kept. - tests/solinas_words_differential.rs: baseline arms dropped; u128/bigint oracles extended to half, num_bits, challenge decodes, balanced-digit LUT, and the random-stream spec; registry and PRIME_OFFSET_* constants pinned as fixture data. - tests/solinas_fp128_differential.rs: baseline arms dropped; limb oracle extended to wide multiplies, conversions, and decodes; the rejection-sampling stream checked against a test-local reimplementation of its spec. - tests/solinas_ext_differential.rs: baseline parity dropped; schoolbook oracle extended to add/sub/neg and integer embeddings; frobenius_pow checked against x^(q^k) computed with the oracle-verified multiply; Moore checks made intrinsic (basis thetas, solution satisfies system, rejections, must-succeed in genuine fields). - tests/solinas_unreduced_differential.rs: baseline HasUnreducedOps arms dropped; delayed-sum exactness kept vs the schoolbook oracle and per-term ring ops; SUM_IS_EXACT values pinned; fold_one checked against the field identity e + r(o - e). - tests/solinas_packed_differential.rs: aarch64 baseline_diff module removed (packed==scalar plus scalar==oracle in the other suites covers it transitively); expected NEON lane widths pinned. - tests/golden_bytes.rs: NEW. Golden fixtures generated from jolt-field at 5b3e39e pinning bincode and to_bytes_le encodings for every registered prime field, the ext towers over three base widths, BN254 Fr/Fq, and the legacy Fr/Fq challenge derivations (generator deleted after use; regeneration instructions in the file header). - tests/parallel_macros.rs, tests/spine.rs: untouched (no oracle use). - benches/ext4_kernels.rs: jolt-field baseline timing columns removed; the generic-vs-fused-port comparison is unchanged. - Cargo.toml: jolt-field dev-dependency removed; num-bigint (workspace) added as a dev-dependency.
Delete the baseline crates/jolt-field (11,410 counted LOC) and move the rebuild into its place under the package name jolt-field (5,103 counted LOC). MontgomeryConstants dies with the old crate: zero consumers in the workspace, closing the spec OPEN item. The old crate's differential evidence survives via the oracle-free suites and golden byte fixtures generated from it before deletion. Consumers rebind in the follow-up commits; the workspace does not build between this commit and the rebind. 125/125 tests and clippy clean on -p jolt-field, all features/targets.
…pine Scripted, import-guided rename over ~400 files: umbrella Field -> JoltField, FieldCore -> Field, RingCore/FromPrimitiveInt -> Ring, CanonicalRepr -> CanonicalEncoding, from_le_bytes_mod_order -> from_bytes_le_reduced, to_canonical_u64_checked -> to_u64_checked, arkworks::bn254 paths -> root exports. Position-aware for Field: associated types named Field (CommitmentScheme<Field = F>, syn::Field) and all ::-qualified paths keep their names; ark_ff::Field collision files and akita-field's shared trait names handled by hand. Two surface restorations in jolt-field, recorded as deviations in specs/jolt-field-rebuild.md: bn254 From<primitive> impls (94+ call sites relied on the plain arkworks conversions), and JoltField drops its serde bounds while the akita bootstrap edge exists (the foreign pre-cutover type cannot implement serde here; restore at cutover). mersenne61_compat reworked against the new spine, proving the slim hierarchy remains implementable without arkworks. Gates: clippy --all-targets -D warnings green on host, host+zk, akita x3, field-inline, and +avx2/+avx512 cross-checks to x86_64-apple-darwin.
…uild Proof bytes verified unchanged: standard-mode muldiv proofs from the pre-replacement build and the replacement branch are byte-identical in all 63,372 bytes under a pinned Dory URS; ZK sizes equal at 65,947. Test battery: 3,901 tests green across workspace default, prover-legacy host/zk/akita, verifier akita fixtures, and solinas-only field lanes; muldiv e2e passes both modes. All clippy lanes and x86 target-feature cross-checks clean. Remaining-work list updated to post-replacement items (x86 runtime validation, bench re-evals, CI SIMD lane, parallel helpers, akita cutover follow-ups).
…o the rebuilt jolt-field Main's a16z#1690 landed BlindFold ZK support in the modular jolt-prover written against the pre-replacement trait names. Conflicts (7 files, rename-only on our side) resolved by taking main's content; the rebind was then re-applied over all main-side .rs changes. Main-only names fixed by hand: RingAccumulator -> Accumulator (pre-consolidation name that never existed on this branch), and two helpers bounded F: Field that need the JoltField umbrella under the rebuilt hierarchy. Gates on the merge: clippy host and host,zk (--all-targets, -D warnings), jolt-prover suites in both modes, muldiv e2e in both modes, all green.
|
The replacement contemplated in Commit sequence:
Validation (all recorded in
Two surface deviations from the rebuild spec were forced by consumers and are recorded in the spec's "Replacement-time deviations": The spec's "Remaining after replacement" section lists what stays open: x86-64 runtime validation of the packed/fp128 paths (still compile-verified only), the fused-kernel bench re-run on x86, a CI target-feature lane so SIMD is not CI-dark, the zero-consumer parallel helpers (delete or wire a consumer), and the akita cutover follow-ups. |
… rebuilt jolt-field Third rebind-the-delta sync. 20 conflicted files, all rename-only on our side, taken from main and re-rebound; pre-consolidation accumulator names swept proactively this round. Spec correction: the parallel helpers' deletion candidacy is withdrawn — Akita's cutover branch consumes jolt_field::parallel at 32 sites, so the checkpoint 9 zero-consumer verdict was workspace-blind. Gates: clippy host and host,zk (--all-targets, -D warnings), muldiv e2e both modes, green.
- check-shared-field-identity.sh: pass --color never to cargo tree; CI's colorized (*) dedup marker defeated the sed strip, making one identity count as two. The dependency graph was always correct. - typos: exclude the golden byte fixtures (hex substrings false-positive). - ext4_kernels bench: compile to a stub without the solinas feature so the bench workflow's --bench '*' succeeds under default features; the print_stdout expectation moves onto the gated module. - fuzz: restore the five fuzz targets deleted with the old crate, rebound to the rebuilt trait names (the fuzz workspace is invisible to workspace clippy and the fuzz CI job cd's into it). - jolt-verifier: drop an unused import only compiled under the prover-fixtures,zk lane.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
…x lanes The BN254 fixture constants and shared helpers compiled as dead code under solinas-only and backend-free lanes, which build with -Dwarnings in CI.
…-level gate The file-level cfg covers backend-free builds; the BN254 fixture constants additionally need their own gate for the solinas-only lane. All four matrix lanes verified locally with -Dwarnings.
Upgrade from the --color never mitigation to rendering-independent package-ID parsing (requested in Akita a16z#307 review: prefer cargo metadata over cargo tree text). CARGO_TERM_COLOR=always is forced inside the script as a permanent regression guard for the colorized (*) marker bug.
Port the upstream Akita sampling change (LayerZero-Labs/akita@03f29087) to the shared Solinas backend: Field::random on Fp32, Fp64, and Fp128 now samples through sample_uniform_below, which reads exactly ceil(modulus_bits / 8) little-endian bytes per attempt, clears unused high bits, and rejects non-canonical candidates. The byte-consumption contract is deterministic for a fixed RngCore stream and documented on Field::random; extension fields sample base coefficients independently through the same contract. Replaces the previous biased single-reduction paths on the word fields and aligns the differential-test oracle with the new contract.
Summary
This PR does four things toward a single audited field implementation:
akita-fieldinjolt-field: the 32-, 64-, and 128-bit prime fields, theFpExt2/FpExt4/FpExt8extension towers, NEON/AVX2/AVX-512 packed backends, unreduced accumulator paths, tests, fuzz coverage, and benchmarks. One implementation, one Rust type identity: Akita consumesjolt-fielddirectly, with no dependency back except a temporary feature-gated bootstrap adapter (see Changes).specs/consolidate-field-traits.md. The import brought the crate to 46 public traits across 82 files, with 15 one-trait-per-file micro-files at the root. The consolidation reduces this to 23 public traits (see theCanonicalBytesnote under Changes) in four root trait modules (algebra.rs,canonical.rs,accumulator.rs,field.rs) plus the feature-gated backend modules, switches Solinas wire serialization to serde + bincode, and adopts the explicitarkworks/bn254.rsper-type layout. This is a refactor of trait boundaries, not arithmetic.jolt-fieldbugs surfaced by differential testing:Fp64::reduce_u128truncated the fold multiply for sub-word moduli on inputs79944f2with regression tests that fail pre-fix.jolt-field-tworebuild to completion.jolt-field-twois a from-scratch minimal-LOC rebuild of this crate, started infeat/field-crate-refactor(checkpoints 1–4 plus its working SPEC.md, cherry-picked here as5635b98); checkpoints 5–9 (fp128, extensions, unreduced, packed, parallel) complete the spec's build order at 5,103 counted LOC against the 6,240 budget, versus the 11,410-LOC baseline, differential-tested againstjolt-fieldas the oracle. It is intended to replace the implementation from items 1–2 in a follow-up PR; the per-checkpoint summary is in the comments below.jolt-fielddestination (post-consolidation)crates/akita-field/src/traits.rssrc/algebra.rs,src/canonical.rs,src/accumulator.rs,src/field.rs,src/solinas_traits.rscrates/akita-field/src/prime/src/prime/crates/akita-field/src/ext/src/ext/crates/akita-field/src/packed/src/packed/crates/akita-field/src/unreduced/src/unreduced/crates/akita-field/src/fft.rscrates/akita-field/src/parallel.rssrc/parallel.rscrates/akita-pcs/benches/field_arith.rsandfield_arith/benches/solinas_field_arith.rsandbenches/solinas_field_arith/fuzz/fuzz_targets/solinas_field_arith.rsChanges
Solinas import
solinasandparallelfeatures tojolt-fieldwhile retaining the existing BN254 default.AkitaSerialize, no Akita validation policy or protocol framing. AddFieldErrorfor backend-independent input and shape failures; Akita converts it at its repository boundary.src/akita.rsand theakitafeature as a temporary, feature-gated bootstrap so the adapter stays buildable until the Akita cutover; both are removed in the final migration PR.akita-config/akita-fieldare optional dependencies behind that feature only.jolt-akitato the current Akita runtime ring dimension, opening claim, setup, commitment, and batching APIs.jolt-field, add a Solinas arithmetic fuzz target, and record file-level source provenance.scripts/check-shared-field-identity.sh.Trait consolidation (spec phases 1–5)
SignedScalarAccumulator/WithSmallScalarAccumulator,SignedProductAccumulator/WithSignedProductAccumulator),ExtensionCoeff,ScaleI32,BalancedDigitLookup(now a free function), andSmoothFftFieldtogether with the 1,100-linefft.rs.AdditiveAccumulator+RingAccumulatorinto a singleAccumulator.FieldCoreabsorbsInvertibleandRandomSampling;FromPrimitiveIntabsorbsMulPow2andMulPrimitiveInt; a newCanonicalReprreplaces the seven byte/introspection/challenge traits (CanonicalBytes,ReducingBytes,FixedByteSize,FixedBytes<N>,CanonicalU64,CanonicalBitLength,TranscriptChallenge). Solinas wire serialization becomes serde + bincode, matching the existing serde implementations onarkworks/bn254.rs; Fiat-Shamir transcript bytes keep the explicit canonical little-endian encoding and never go through bincode. A later commit (ff5bf9c) re-splits the byte surface back out asCanonicalBytes, now a supertrait ofCanonicalRepr, after feat: Akita lattice PCS integration — verifier path and shared semantics #1675'sNoCommitmentshowed that transcript absorption should not require the full field-decode contract; the final surface is 23 public traits.ExtField<F>absorbsLiftBase,MulBase, andFrobeniusExtField;FpExt4MulBackend+FpExt8MulBackendmerge into oneExtMulBackend;PackedFieldabsorbsPackedValue; the degree-4 mul/square schedule now exists in exactly one place, shared between scalar and packed backends.implor a one-line shared-macro invocation; the threenative_algebra.rsside-files are replaced by one shared macro module.BN254 is untouched by the consolidation:
Fr's serde and byte encodings, the Fiat-Shamir stream, and static dispatch are unchanged (spec invariants 1 and 6).MontgomeryConstantsis retained pending confirmation that no out-of-tree GPU/Metal consumer exists (spec OPEN item; 23 traits become 22 when resolved).Testing
muldive2e passes in--features hostand--features host,zkafter the consolidation.jolt-fieldfeature matrix passes: backend-free (--no-default-features), BN254-only, Solinas-only, combined, and Solinas-plus-parallel.mersenne61_compat(injolt-sumcheck) compiles and passes againstjolt-field --no-default-featureswith bounds updated to the merged traits: the slim hierarchy remains implementable without arkworks.Fp32/Fp64/Fp128/FpExt2/FpExt4/FpExt8; serialized-size tests assert each element encodes to exactlyNUM_BYTESbytes and aVecofCanonicalReprchallenge derivation matches the previousTranscriptChallengebehavior onFr(identical bytes in, identical element out).solinas_field_arithfuzz target and both Criterion benches build; bench results before/after the consolidation are within noise.scripts/check-shared-field-identity.shreports onejolt-fieldidentity and noakita-fieldin the integrated dependency graph.muldivproof against a main-built proof: identical in all 63,371 bytes, proving is deterministic run-to-run, and the main-built verifier accepts the branch-built proof. (Reproduction note:cfg(test)builds randomize the Dory URS per process viaDoryGlobals::configure_test_cache_root, so cross-process comparison requires pinning both builds to a shared URS cache.)Fp64::reduce_u128and S160 fixes fail on the pre-fix code.jolt-field-two: 121 differential tests green across the full feature matrix, plus+avx2and+avx512f,+avx512dqcompile cross-checks againstx86_64-apple-darwin.Security Considerations
This change touches security-sensitive field arithmetic used by the PCS and therefore affects the trusted audit surface for prover and verifier correctness. The intended arithmetic change is zero across both stages: moduli, canonical representations, extension bases, reduction algorithms, and packed kernel behavior are preserved, and architecture-specific arithmetic kernels (NEON, AVX2, AVX-512) are moved without semantic modification.
For the import stage, 22 of the imported files are byte-for-byte copies of their Akita sources and 38 are adapted imports; the main semantic adaptations are removal of Akita serialization bounds and replacement of protocol-level
AkitaErrorwith the narrowerFieldError. The consolidation stage then rewrites trait boundaries only: merges are compile-time renames,#[inline]discipline is carried over onto both trait methods and impls, and all dispatch remains static. The net effect is a smaller audit surface: 46 public traits become 23, and the crate root drops from 15 trait micro-files to 4 trait modules.Breaking Changes
akita-fieldfrom Jolt's dependency graph (jolt-fieldkeeps optionalakita-*dependencies behind the temporaryakitabootstrap feature only).Invertible,RandomSampling,TranscriptChallenge,LiftBase,MulBase,FrobeniusExtField,AdditiveAccumulator,PackedValue, ...) must rebind to the survivors (FieldCore,CanonicalBytes,CanonicalRepr,ExtField,Accumulator,PackedField, ...);CanonicalBytessurvives with a slimmer, bytes-only contract. Akita adapts at its next pin bump.fft.rsandSmoothFftFieldfromjolt-field.Frencodings unchanged).