Skip to content

Constant-size proofs + stateless verification (v0.3.0)#50

Open
adamkrellenstein wants to merge 11 commits into
mainfrom
crypto/constant-size-stateless
Open

Constant-size proofs + stateless verification (v0.3.0)#50
adamkrellenstein wants to merge 11 commits into
mainfrom
crypto/constant-size-stateless

Conversation

@adamkrellenstein

@adamkrellenstein adamkrellenstein commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Reconciles the two open PoR PRs — #44 (constant-size proof) and #49 (stateless verify) — into one coherent model. Supersedes both.

Why one PR

#44 and #49 branched independently off main and conflicted at the design level, not just textually: #44 makes the proof constant-size by dropping challenge_ids/ledger_indices and moves the ledger to stable append-only slots; #49 (written against the old format) still read those fields and assumed lexicographic ordering. Those index models are mutually exclusive, so this lands them together.

The reconciled model

  • Proof is constant-size (wire v3): {compressed_snark, ledger_root, aggregated_tree_depth}. The challenge set is supplied out-of-band; the proof carries no per-file data, so its size is independent of challenge/file count.
  • plan.rs: make_plan takes AggInputs; per-file ledger indices are resolved from a LedgerView on both prove and verify paths (never read from the proof). AggInputs::Proof carries only root + depth.
  • verify.rs: LedgerView trait + registry-backed StatelessLedger { valid_roots, files: file_id → (slot, rc) } + verify_stateless, sharing verify_with with the ledger path.
  • ledger.rs: aggregate_root/aggregate_root_from_files take (rc, slot) / (root, depth, slot) and mirror rebuild_tree exactly (append-only slots, zero-filled gaps, sparsity guard, power-of-two padding) — byte-identical to FileLedger::root.
  • Bump to 0.3.0 (breaking wire + API). Carries the Soundness: non-strict to_bits_le on challenge/ledger index lets a prover open leaf idx+1 #46 strict-bit soundness fix from Constant size proof #44.

Design decisions

  • Drop the challenge_ids equality check (from Add stateless PoR verification (verify_stateless + aggregate_root) #49). Safe: the verifier rebuilds every SNARK public input from the supplied challenges (incl. block_height/prover_id/num_challenges via Challenge::id()initial_state), so a mismatched proof fails SNARK verification regardless. Confirmed by the audit below.
  • Append-only slots over lexicographic (from Constant size proof #44). Stable slots keep a file's index identical across historical roots, which is what makes cross-block aggregation and stateless verification work; lexicographic order shifts indices on every insert.
  • Stateless registry carries slots. StatelessLedger holds file_id → (slot, rc) (not just valid roots) so a contract keeping its file registry on-chain can resolve indices exactly as the live ledger does — enabling the host to drop its mutable in-memory FileLedger later.

Verification

  • Full suite 294 passed / 0 failed (47 binaries, all security suites incl. security_ledger, security_malicious_prover, stateless_verify). New stateless_verify.rs asserts stateless verify ≡ ledger verify (single + multi-file) and aggregate_rootFileLedger::root (incl. sparse slots).
  • Adversarial LLM soundness re-audit (the same kind that found Soundness: non-strict to_bits_le on challenge/ledger index lets a prover open leaf idx+1 #46): no high/medium findings across the challenge-binding, stateless-registry, cross-block-depth, aggregate_root, and single-file attack classes. One doc-hardening note added to StatelessLedger.
  • fmt + clippy clean; cargo audit passed (pre-push hook).

Supersedes


Note

High Risk
Changes proof serialization, verifier trust assumptions (out-of-band challenges + caller-supplied stateless registry/roots), ledger index semantics, and circuit constraints—core soundness and cross-network compatibility surfaces.

Overview
Breaking release (0.3.0) that reconciles constant-size proofs and stateless verification around one ledger model: append-only stable slots instead of lexicographic file_id ordering.

Proof wire format v3Proof now serializes only compressed_snark, ledger_root, and aggregated_tree_depth (no challenge_ids or ledger_indices). Callers pass the intended Challenge set out-of-band; PorSystem::verify no longer compares proof-embedded challenge IDs. Binding comes from canonical challenge sorting, initial_state derived from Challenge::id(), and verifier-rebuilt public inputs.

Prove/verify planningPlan::make_plan takes AggInputs (ledger root from live FileLedger on prove, from the proof on verify). Per-file ledger indices are always resolved through LedgerView::lookup, never read from the proof.

Stateless pathLedgerView, registry snapshot StatelessLedger (valid_roots + file_id → (slot, rc)), shared verify_with, and public verify_stateless. aggregate_root / aggregate_root_from_files and LedgerFrontier (incremental O(log n) root updates) match FileLedger::root byte-for-byte for contiguous append-only slots.

Ledger semanticsIndexedFileLedgerEntry, immutable file_id (duplicates rejected), add_files requires explicit indices for reconstruction; aggregated tree built by ledger_index with zero-filled gaps and a sparsity cap. Ledger on-disk format bumps to v2.

Circuit (#46) — Merkle and aggregation path selection use to_bits_le_strict instead of to_bits_le so non-canonical field decompositions cannot steer paths.

Docs (PROTOCOL.md, SECURITY.md), security/integration tests, and Picus expected constraint counts are updated for the new model.

Reviewed by Cursor Bugbot for commit 137bb9e. Bugbot is set up for automated code reviews on this repo. Configure here.


Closes #46 (strict bit-decomposition on both index sites).
Issue #19 (incremental tree-update) is addressed by the LedgerFrontier added here.

@codspeed-hq

codspeed-hq Bot commented Jun 16, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

🎉 Hooray! codspeed-rust just leveled up to 5.0.0!

A heads-up, this is a breaking change and it might affect your current performance baseline a bit. But here's the exciting part - it's packed with new, cool features and promises improved result stability 🥳!
Curious about what's new? Visit our releases page to delve into all the awesome details about this new version.

✅ 13 untouched benchmarks
⏩ 13 skipped benchmarks1


Comparing crypto/constant-size-stateless (137bb9e) with main (fe9f977)

Open in CodSpeed

Footnotes

  1. 13 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

wilfreddenton
wilfreddenton previously approved these changes Jun 18, 2026
adamkrellenstein and others added 11 commits June 18, 2026 14:08
…file-merkle-path); fixes Component + Monolithic matrix Errors
Carries the issue #46 soundness fix into the constant-size circuit. The Merkle
path-selection indices were derived with non-strict `to_bits_le`, which only
enforces Σ bitᵢ·2ⁱ ≡ x (mod p), not canonicity. Since the Pallas modulus is just
above 2^254 (NUM_BITS=255, p ≡ 1 mod 2^32), a prover can witness the (x + p)
decomposition whose low bits encode index+1 and open a leaf other than the
challenged one (file tree) or steer the aggregation path off the public index.

Switches the four index-derivation sites — challenge_with_idx and
ledger_index_public in both synth.rs and formal_components.rs — to
to_bits_le_strict. Completeness preserved; the idx+1 opening is closed.

Verified: build + api_functionality, depth_zero_cheat_regression,
security_malicious_prover, security_ledger all pass.
…e-identical to aggregate_root; removal out of scope)
Unify the constant-size proof work (wire v3: Proof carries only
compressed_snark + ledger_root + aggregated_tree_depth) with stateless
verification on one model.

- plan.rs: make_plan takes AggInputs; per-file ledger indices are always
  resolved from a LedgerView (live FileLedger or stateless registry), never
  read from the proof. AggInputs::Proof drops ledger_indices.
- verify.rs: LedgerView trait + registry-backed StatelessLedger
  {valid_roots, files: file_id -> (slot, rc)} + verify_stateless, sharing
  verify_with. The challenge_ids equality check is removed: public inputs
  are rebuilt from the supplied challenges, so a mismatched proof fails the
  SNARK regardless.
- ledger.rs: aggregate_root / aggregate_root_from_files take (rc, slot)
  pairs and mirror rebuild_tree (append-only slots, zero-filled gaps,
  sparsity guard, power-of-two padding), byte-identical to FileLedger::root.

Bump to 0.3.0 (breaking wire + API). Full suite green (294 passed);
stateless verify matches ledger verify for single- and multi-file proofs.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c35891f. Configure here.

Comment thread crates/kontor-crypto/src/ledger.rs Outdated
"Failed to deserialize ledger: {}; legacy v2 load failed: {}",
current_err, legacy_err
))
})?,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing v1 ledger load path

High Severity

FileLedger::load only accepts wire format version 3 or a v2 legacy shape with indexed entries. Prior releases persisted ledgers as format version 1 with unindexed FileLedgerEntry values, and there is no v1 loader or migration. Upgrading from 0.2.x leaves existing on-disk ledgers unloadable.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c35891f. Configure here.

Comment thread crates/kontor-crypto/src/ledger.rs
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.

Soundness: non-strict to_bits_le on challenge/ledger index lets a prover open leaf idx+1

4 participants