Skip to content

refactor(jolt-field): Land Solinas field from akita-field in jolt-field#1684

Open
Acentelles wants to merge 13 commits into
a16z:mainfrom
Acentelles:feat/solinas-field-stack
Open

refactor(jolt-field): Land Solinas field from akita-field in jolt-field#1684
Acentelles wants to merge 13 commits into
a16z:mainfrom
Acentelles:feat/solinas-field-stack

Conversation

@Acentelles

@Acentelles Acentelles commented Jul 17, 2026

Copy link
Copy Markdown

Summary

This PR does two things toward a single audited field implementation:

  1. Lands the Solinas field stack from akita-field in jolt-field: the 32-, 64-, and 128-bit prime fields, the FpExt2/FpExt4/FpExt8 extension towers, NEON/AVX2/AVX-512 packed backends, unreduced accumulator paths, tests, fuzz coverage, and benchmarks. One implementation, one Rust type identity: Akita consumes jolt-field directly, with no dependency back except a temporary feature-gated bootstrap adapter (see Changes).
  2. Consolidates the resulting trait surface per 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 22 public traits 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 explicit arkworks/bn254.rs per-type layout. This is a refactor of trait boundaries, not arithmetic.
Akita source jolt-field destination (post-consolidation)
crates/akita-field/src/traits.rs src/algebra.rs, src/canonical.rs, src/accumulator.rs, src/field.rs, src/solinas_traits.rs
crates/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.rs Not imported in final form: removed in the consolidation (zero consumers in this workspace)
crates/akita-field/src/parallel.rs src/parallel.rs
crates/akita-pcs/benches/field_arith.rs and field_arith/ benches/solinas_field_arith.rs and benches/solinas_field_arith/
Akita field arithmetic fuzz coverage fuzz/fuzz_targets/solinas_field_arith.rs

Changes

Solinas import

  • Add solinas and parallel features to jolt-field while retaining the existing BN254 default.
  • Import the complete Akita Solinas implementation (prime fields, extension towers, packed backends, unreduced/wide accumulators, parallel helpers).
  • Keep Akita proof and field serialization concerns out of the shared crate: no AkitaSerialize, no Akita validation policy or protocol framing. Add FieldError for backend-independent input and shape failures; Akita converts it at its repository boundary.
  • Keep src/akita.rs and the akita feature 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-field are optional dependencies behind that feature only.
  • Update jolt-akita to the current Akita runtime ring dimension, opening claim, setup, commitment, and batching APIs.
  • Move the Solinas Criterion benchmark into jolt-field, add a Solinas arithmetic fuzz target, and record file-level source provenance.
  • Add a CI feature matrix for backend-free, BN254-only, Solinas-only, and combined builds, plus scripts/check-shared-field-identity.sh.

Trait consolidation (spec phases 1–5)

  • Phase 1 — delete dead surface. Remove traits with no generic consumer in this workspace or Akita: both signed-accumulator families (SignedScalarAccumulator/WithSmallScalarAccumulator, SignedProductAccumulator/WithSignedProductAccumulator), ExtensionCoeff, ScaleI32, BalancedDigitLookup (now a free function), and SmoothFftField together with the 1,100-line fft.rs.
  • Phase 2 — one accumulator trait. Merge AdditiveAccumulator + RingAccumulator into a single Accumulator.
  • Phase 3 — root traits and wire format. FieldCore absorbs Invertible and RandomSampling; FromPrimitiveInt absorbs MulPow2 and MulPrimitiveInt; a new CanonicalRepr replaces 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 on arkworks/bn254.rs; Fiat-Shamir transcript bytes keep the explicit canonical little-endian encoding and never go through bincode.
  • Phase 4 — extension cluster. ExtField<F> absorbs LiftBase, MulBase, and FrobeniusExtField; FpExt4MulBackend + FpExt8MulBackend merge into one ExtMulBackend; PackedField absorbs PackedValue; the degree-4 mul/square schedule now exists in exactly one place, shared between scalar and packed backends.
  • Phase 5 — per-type layout. Everything a concrete type implements is visible in that type's file, as an explicit impl or a one-line shared-macro invocation; the three native_algebra.rs side-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). MontgomeryConstants is retained pending confirmation that no out-of-tree GPU/Metal consumer exists (spec OPEN item; 22 traits become 21 when resolved).

Testing

  • muldiv e2e passes in --features host and --features host,zk after the consolidation.
  • jolt-field feature matrix passes: backend-free (--no-default-features), BN254-only, Solinas-only, combined, and Solinas-plus-parallel.
  • mersenne61_compat (in jolt-sumcheck) compiles and passes against jolt-field --no-default-features with bounds updated to the merged traits: the slim hierarchy remains implementable without arkworks.
  • New bincode round-trip tests for Fp32/Fp64/Fp128/FpExt2/FpExt4/FpExt8; serialized-size tests assert each element encodes to exactly NUM_BYTES bytes and a Vec of $n$ elements to $n \cdot \texttt{NUM BYTES}$ plus one length prefix.
  • A compile test verifies the merged CanonicalRepr challenge derivation matches the previous TranscriptChallenge behavior on Fr (identical bytes in, identical element out).
  • The solinas_field_arith fuzz target and both Criterion benches build; bench results before/after the consolidation are within noise.
  • scripts/check-shared-field-identity.sh reports one jolt-field identity and no akita-field in the integrated dependency graph.
  • Byte-for-byte comparison of a standard-mode muldiv proof 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 via DoryGlobals::configure_test_cache_root, so cross-process comparison requires pinning both builds to a shared URS cache.)

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 AkitaError with the narrower FieldError. 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 22, and the crate root drops from 15 trait micro-files to 4 trait modules.

Breaking Changes

  • Removes akita-field from Jolt's dependency graph (jolt-field keeps optional akita-* dependencies behind the temporary akita bootstrap feature only).
  • Deletes or merges 24 public traits; downstream code generic over the old names (Invertible, RandomSampling, CanonicalBytes, TranscriptChallenge, LiftBase, MulBase, FrobeniusExtField, AdditiveAccumulator, PackedValue, ...) must rebind to the survivors (FieldCore, CanonicalRepr, ExtField, Accumulator, PackedField, ...). Akita adapts at its next pin bump.
  • Removes fft.rs and SmoothFftField from jolt-field.
  • Solinas types' wire format is now serde + bincode (BN254/Fr encodings unchanged).

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.
@github-actions

Copy link
Copy Markdown
Contributor

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.
See CONTRIBUTING.md for details on how to create a spec.

If this PR is a bug fix, refactor, or doesn't warrant a spec, feel free to ignore this message.

@github-actions github-actions Bot added the no-spec PR has no spec file label Jul 17, 2026
@socket-security

socket-security Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedp3-baby-bear@​0.5.310010093100100
Addedp3-mersenne-31@​0.5.310010093100100

View full report

@Acentelles Acentelles changed the title Land the shared Solinas field stack in jolt-field refactor(jolt-field): Land the shared Solinas field stack in jolt-field Jul 17, 2026
@Acentelles Acentelles closed this Jul 17, 2026
@Acentelles Acentelles reopened this Jul 17, 2026
@Acentelles Acentelles changed the title refactor(jolt-field): Land the shared Solinas field stack in jolt-field refactor(jolt-field): Land Solinas field from akita-field in jolt-field Jul 17, 2026
@Acentelles
Acentelles marked this pull request as ready for review July 17, 2026 01:49
@Acentelles

Copy link
Copy Markdown
Author

Once LayerZero-Labs/akita#307 is merged, we can remove the temporary akita-field compatibility dependency from jolt-field and change jolt-akita from the temporary jolt-field/akita feature to jolt-field/solinas.

@markosg04
markosg04 self-requested a review July 17, 2026 01:59
Acentelles and others added 8 commits July 21, 2026 19:01
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>
@github-actions github-actions Bot added spec Tracking issue for a feature spec implementation PR contains implementation of a spec and removed no-spec PR has no spec file labels Jul 22, 2026
- 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
@Acentelles
Acentelles force-pushed the feat/solinas-field-stack branch from 8d58e10 to 9aff1cd Compare July 22, 2026 20:28
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>
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 Tracking issue for a feature spec

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant