Skip to content

feat(prover): persist the derived Lagrange SRS so it isn't re-derived on every start - #1

Draft
coeniebeyers wants to merge 29 commits into
fix/writersto-equal-both-encodingsfrom
feature/3584-persist-derived-lagrange-srs
Draft

feat(prover): persist the derived Lagrange SRS so it isn't re-derived on every start#1
coeniebeyers wants to merge 29 commits into
fix/writersto-equal-both-encodingsfrom
feature/3584-persist-derived-lagrange-srs

Conversation

@coeniebeyers

@coeniebeyers coeniebeyers commented Jul 24, 2026

Copy link
Copy Markdown
Owner

This PR implements LFDT-Lineth#3584.

Submitted upstream as LFDT-Lineth#3733, with the WriterstoEqual fix it depends on as LFDT-Lineth#3732; this PR remains the fork-side review record.

When no Lagrange-form SRS dump is on disk, SRSStore derives it from the canonical SRS in memory and discards it on exit — a multi-hour derivation repeated on every prover start, with a single debug-level log line as the only indication. This PR makes the derivation happen at most once per store directory, by separating reading from provisioning:

  • GetSRS (prove time) remains a pure read. A miss still derives in memory, but at real circuit sizes it now warns before the derivation and names the command that resolves it. It never writes: the SRS directory can be mounted read-only.
  • prover setup provisions. It derives, validates and atomically publishes the dump under the provenance tag derived — locally computed material never claims a ceremony's name — and backfills a missing dump even when circuit assets are current. persist_derived_srs defaults to on; set it to false if the SRS directory must never be written to (for example, a read-only or shared asset store). --force re-validates an existing dump in full and replaces it if it does not load.

Failure modes, each pinned by a test:

failure handled by
partial or interrupted write temp file + fsync + atomic rename — never published
write failure (full disk, read-only directory) probe fails before deriving; setup warns and continues
truncated or unparseable Lagrange dump ReadDump error → re-derive
wrong-size dump point-count check → early re-derive
dump from a different setup verifying-key equality against the canonical → re-derive
corrupted point data on-curve validation → re-derive
corrupt or missing canonical (ceremony) file remains fatal — never re-derived
crash mid-write (e.g. an out-of-memory kill) orphaned temp file deleted at the next prover setup run, once its last write is more than an hour old
concurrent index mutation RWMutex, snapshot reads
different user id on a later run dumps published 0644, matching the ceremony files

Behaviour changes:

  • An unloadable Lagrange dump previously failed GetSRS; it is now logged and re-derived. Canonical (ceremony) load failures remain fatal.
  • A file named kzg_srs_canonical_<n>_<curve>_derived.memdump — a name the code never produces — was previously accepted as trusted ceremony material; it is now warned about and ignored.
  • Orphaned temp files are reclaimed by prover setup rather than at store construction; reads never modify the directory.
  • The copy-prover-assets Makefile target now excludes *_derived*, keeping derived dumps machine-local rather than syncing them into the shared asset bucket. If distributing them through the bucket is preferred, remove the exclusion — we left that as an explicit decision for the maintainers.

Numbers: loading a Lagrange dump from disk is the pre-existing fast path — deployments that ship pre-derived dumps already pay it on every start (~14 s at 2^27 on a real 13 GiB bls12-377 dump, 8 cores: 9.3 s read, 4.8 s parse). What this PR changes is who can take that path: deployments without shipped dumps previously re-derived in memory for hours on every start; now prover setup writes the dump once and later starts load it like any other. The one added cost is ~1.3 s of load-time validation (point count, verifying-key equality, on-curve scan), since the store may now be loading locally-written rather than only ceremony-shipped material. Disk grows by one dump per Lagrange size, the same order of magnitude as the canonical files already present. The feature was also exercised end-to-end through the built CLI: every operator flow (persist, no-op re-run, backfill, --force repair, orphan cleanup, opt-out, read-only directory) behaves as documented, and at 2^21 a fresh process loads the persisted dump in 0.1 s against the 93 s derivation it replaces.

Substitution of validly-encoded points is out of scope, as it is for every file in the store. gofmt, golangci-lint and the test suite (-race, -tags nocorset,fuzzlight) all pass.

Checklist

  • I wrote new tests for my new core changes.
  • I have successfully ran tests, style checker and build against my new changes locally.
  • If this change is deployed to any environment (including Devnet), E2E test coverage exists or is included in this PR.
  • I have informed the team of any breaking changes if there are any.

No breaking API changes.

Potential follow-up issues

Found while implementing this change and exploring the codebase; none are addressed in this PR, and we are happy to file each as its own issue:

  • protocol/serde/decoder.go — an integer overflow can bypass the deserializer's length bounds check, so a corrupted or crafted asset can be accepted (the same pattern appears at two more sites in the file).
  • cmd/controller/controller.go — a crashed large-mode job is recovered under a file name no input pattern matches, silently dropping it from the queue.
  • lib/compressor/blob/v2/test_utils — buffer aliasing causes the multi-blob test to run on duplicated data rather than two distinct blobs.
  • symbolic/constructor_ext_test.go — the parent-structure assertion compares the expected value to itself, so its fourteen fixtures verify nothing.
  • circuits/aggregation/circuit_test.go — a memoized test SRS makes every "different" verifying key byte-identical, so per-key selection is never exercised.

Thanks

Thanks @mkrielza for your help shaping this PR and for finding the WriterstoEqual issue.

@coeniebeyers
coeniebeyers force-pushed the feature/3584-persist-derived-lagrange-srs branch 6 times, most recently from 3b2977c to bf013d0 Compare July 24, 2026 15:22
Comment thread prover/circuits/srs_store.go Outdated
Comment thread prover/circuits/srs_store.go
Comment thread prover/circuits/srs_store.go Outdated
Comment thread prover/circuits/srs_store.go
Comment thread prover/circuits/srs_store.go
Comment thread prover/circuits/srs_store_test.go
Comment thread prover/circuits/srs_store.go
@mkrielza

mkrielza commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What is confirmed not at risk:

  • No confidentiality exposure. SRS material is public by design, so persisting it and the 0644 widening leak nothing.
  • No path traversal. Every filename component comes from a constrained set: an integer size, a fixed curve-name map, and a regex-restricted ceremony tag. No / or .. can enter.
  • Canonical files are never touched. The writer hardcodes lagrange in the filename it constructs.
  • Canonical load failures stay fatal. The trust root's own integrity failure remains loud. Deliberate and correct.
  • A corrupt or substituted Lagrange basis does not yield forgeable proofs. Commitments stop matching the on-chain verifying key, so the L1 verifier rejects them. This is bounded to liveness and availability, not theft. The exception is the provenance path where the tau is known to someone and the on-chain key derives from that same setup.
  • No symlink-redirected write. rename(2) does not follow a symlink at the destination, it replaces it, so a planted symlink cannot divert the published dump elsewhere. Worth noting the asymmetry: the read path uses os.ReadFile, which does follow symlinks. Both only matter to an attacker who already has write access to the directory.

@mkrielza

mkrielza commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

See this PR for a proposal to make derivation an explicit provisioning step rather than an implicit side effect of GetSRS. E.g, a prover command that an operator runs deliberately.

Then:

  • The write is operator-initiated, so its output has known provenance and can be labelled honestly.
  • The trust directory stays read-only at prove time.
  • There is no runtime mutation to monitor or to attest around.
  • Version skew is handled naturally by re-running it after a dependency upgrade.

Same performance benefit, without the category change. All violations of good principles, mentioned in the comments of the PR, are symptoms of putting a write in a read path.

coeniebeyers and others added 14 commits August 6, 2026 10:17
Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
… on every start

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…ename

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…mment

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
Signed-off-by: Marelize <marelize.kriel@adhara.io>
…eremony material

Signed-off-by: Marelize <marelize.kriel@adhara.io>
Signed-off-by: Marelize <marelize.kriel@adhara.io>
…ault off

Signed-off-by: Marelize <marelize.kriel@adhara.io>
…g even when circuits are current

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…emory derivation

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
… when unwritable, and quiet at dummy sizes

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
… setup section

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…r at prove time

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…ling setup

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…of trusting it

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…after it

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…ence after setup

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…ation, not after

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…ts cleanup

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…rveFileNames

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
…ap over-limit lines

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
… not-found

Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
Signed-off-by: Coenie Beyers <coenie.beyers@adhara.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants