Skip to content

Add septic curve precompiles (SEPTIC_ADD, SEPTIC_DOUBLE, SEPTIC_SCALAR_MUL, SEPTIC_VERIFY)#2719

Open
sameepsi wants to merge 5 commits into
succinctlabs:mainfrom
kalqix:feat/septic-precompiles
Open

Add septic curve precompiles (SEPTIC_ADD, SEPTIC_DOUBLE, SEPTIC_SCALAR_MUL, SEPTIC_VERIFY)#2719
sameepsi wants to merge 5 commits into
succinctlabs:mainfrom
kalqix:feat/septic-precompiles

Conversation

@sameepsi
Copy link
Copy Markdown

@sameepsi sameepsi commented Apr 14, 2026

Summary

This PR adds four precompiles for elliptic curve operations on SP1's own septic curve (y² = x³ + 45x + 41z³ over KoalaBear Fp7). These enable Schnorr signature verification using field-native arithmetic, eliminating the need for non-native 256-bit curve operations in signature-heavy applications.

All four precompiles have working executors (both minimal and full VM paths), guest-side APIs, and have been benchmarked extensively. AIR constraints are not implemented — this PR is submitted for review and collaboration on that final layer.

Motivation

We're building KalqiX, a CLOB-based DEX using SP1 for validity proofs. Each proof batch verifies 2,000 order signatures. With secp256k1 ecrecover, signature verification consumed 109M cycles (22% of the 500M budget) and 146.5M gas.

The septic curve is already used internally by SP1's memory argument (Hypercube). Its field arithmetic is native to the proof system — no limb decomposition, no non-native range checks, no carry propagation. We hypothesized that exposing it as a precompile would make signature verification dramatically cheaper.

Results

Benchmark: 2,000 Schnorr signature verifications in a single SP1 execution.

Approach Total cycles Gas Per-sig Speedup vs secp256k1
secp256k1 ecrecover (baseline) 108.9M 146.5M 54,446 1.0×
Septic Schnorr (SEPTIC_ADD/DOUBLE, per-bit) 69.9M 104.7M 34,964 1.6×
Septic Schnorr (SEPTIC_SCALAR_MUL) 10.5M 48.8M 5,263 10.4×
Septic Schnorr (SEPTIC_VERIFY, Shamir's trick) 10.3M 32.5M 5,163 10.6×

Gas reduction: 78% lower than secp256k1 (32.5M vs 146.5M). The gas metric reflects actual STARK trace area, which is the primary cost driver for proof generation.

Single-signature benchmarks:

Scheme Cycles Syscalls
Septic Schnorr (SEPTIC_VERIFY) 5,163 1
Septic Schnorr (SEPTIC_SCALAR_MUL) 5,263 2
secp256k1 ecrecover 74,515 783
Ed25519 verify 124,975 807
P-256 ECDSA 151,237 787

Cryptographic Parameters

All parameters independently verified via SageMath and cross-checked against three points from the SP1 codebase (CURVE_CUMULATIVE_SUM_START, DIGEST_SUM_START, DUMMY_POINT).

  • Base field: KoalaBear, p = 2,130,706,433
  • Extension: Fp7 = Fp[z]/(z⁷ - 3z - 5)
  • Curve: y² = x³ + 45x + 41z³
  • Group order: 199372529839252601278447397890876471698671718266839763841250021879 (217 bits, prime)
  • Twist order: Also prime
  • Security: ≥100 bits (following Pornin's EcGFp5 analysis, SafeCurves criteria)
  • Signature scheme: Schnorr (sign: k random, R = kG, e = H(R‖A‖m), s = k - ea mod r)

What's Implemented

Four precompiles

Syscall Code Operation
SEPTIC_ADD 0x134 Point addition (P₁ + P₂)
SEPTIC_DOUBLE 0x135 Point doubling (2P)
SEPTIC_SCALAR_MUL 0x136 Scalar multiplication (sP)
SEPTIC_VERIFY 0x137 Schnorr verify helper: sG + eA via Shamir's trick

Files changed

Syscall registration:

  • crates/core/executor/src/syscall_code.rs — four new SyscallCode variants
  • crates/core/executor/src/air.rs — four new RiscvAirId variants
  • crates/core/executor/src/artifacts/rv64im_costs.json — gas cost entries
  • crates/core/executor/src/vm/gas.rs — complexity mappings

Minimal executor (functional — used by execute() and mock prover):

  • crates/core/executor/src/minimal/precompiles/septic.rs — full implementations using SepticCurve from sp1-hypercube
  • crates/core/executor/src/minimal/ecall.rs — dispatch

Full VM executor (compiles, no event recording):

  • crates/core/executor/src/vm/syscall/precompiles/septic.rs — stubs that drive memory access patterns
  • crates/core/executor/src/vm/syscall.rs — dispatch

Guest-side API:

  • crates/zkvm/entrypoint/src/syscalls/septic.rs — asm ecall wrappers
  • crates/zkvm/entrypoint/src/syscalls/mod.rs — syscall constants
  • crates/zkvm/lib/src/septic.rsSepticPoint (add, double, scalar_mul, scalar_mul_single) + schnorr_compute
  • crates/zkvm/lib/src/lib.rs — extern declarations

Design notes

  • Memory layout: Points are [u64; 7] (14 KoalaBear u32 limbs packed two-per-u64 LE, 8-byte aligned). Scalars are [u64; 4] (8 u32 limbs, 256-bit LE). Matches existing precompile alignment conventions.
  • No identity point handling: SepticCurve has no native identity representation. All executors use a result_set flag (same pattern as the guest-side scalar_mul in sp1-lib). Scalar = 0 writes a zero sentinel.
  • SEPTIC_VERIFY hardcodes the generator point from sp1_hypercube::septic_digest::CURVE_CUMULATIVE_SUM_START_{X,Y} — no constant duplication.
  • Shamir's trick in SEPTIC_VERIFY processes bits MSB-first with one shared doubling per bit position. Precomputes G+A (1 addition). ~381 EC operations vs ~651 for two separate scalar muls.

What's NOT Implemented

AIR constraints. The precompile executors compute correct results, but there are no AIR chips to prove these computations in the STARK. This means:

  • execute() works ✓
  • Mock prover works ✓
  • Benchmarking works ✓
  • prove() will not work for programs that use these precompiles

We're submitting this as an RFC because:

  1. The algebraic identity checks already exist. septic_curve.rs has sum_checker_x and sum_checker_y — the polynomial constraints that verify point operations. These are the core of what an AIR chip would prove.

  2. The curve arithmetic is field-native. Unlike secp256k1/Ed25519/P-256 (which require non-native limb decomposition, range checks, and carry propagation), the septic curve operates directly over KoalaBear. Each field element is a single trace column. This should make the AIR chip significantly simpler than existing EC precompile chips.

  3. The Succinct team has deep expertise in SP1's AIR framework and has built all existing precompile chips. We believe collaboration on the AIR layer would produce a better result than an external implementation.

We're happy to collaborate on building the AIR constraints if there's interest.

How to Test

# Build
cargo build --workspace

# Run existing executor tests
cargo test -p sp1-core-executor

# Benchmark (requires the kalqix-poc project)
# Single signature:
cargo run --release --bin profile -- verify-order-septic

# Batch 2,000 signatures:
cargo run --release --bin profile -- batch-septic-verify-2000

# Comparison with secp256k1:
cargo run --release --bin profile -- batch-eth-2000

POC repository with full benchmarking harness: kalqix-poc

Broader Impact

Any SP1 application that verifies signatures can benefit from these precompiles — not just DEXs. Use cases include:

  • Rollup signature aggregation — batch-verify user transaction signatures
  • Oracle networks — verify data feed signatures inside proofs
  • Governance — prove vote signature validity
  • Any ECDSA-heavy workload — replace secp256k1/Ed25519 with field-native Schnorr

The Schnorr scheme on this curve provides ≥100 bits of security with prime group order and prime twist order. Session key registration (linking an ETH wallet to a septic Schnorr key) can be done via a single secp256k1 sign-message, verified once per batch using the existing SECP256K1_ADD precompile.

Add precompiles for elliptic curve point addition and doubling on SP1's
septic curve (y^2 = x^3 + 45x + 41z^3 over KoalaBear Fp7), using the
existing SepticCurve implementation in sp1-hypercube.

Memory layout is 7 u64 words (8-byte aligned) packing 14 KoalaBear u32
limbs (x0..x6, y0..y6). The minimal executor performs the real curve
arithmetic via SepticCurve::add_incomplete/double. The full VM executor
path is wired for compilation only — a Septic AIR and event recording
will follow.
Performs a full 256-bit double-and-add scalar multiplication in a single
syscall (~325x reduction vs. composing SEPTIC_ADD + SEPTIC_DOUBLE) by
running the loop inside the executor. Adds the executor implementation,
full-VM stub, gas/cost entries, the `SEPTIC_SCALAR_MUL = 0x136` syscall
constant + asm wrapper, and a `SepticPoint::scalar_mul_single` guest API.
Computes `s*G + e*A` on the septic curve in a single syscall using
Shamir's trick (~381 EC ops vs. ~651 for two independent scalar muls),
reducing per-Schnorr-verify cost from ~5.2k to ~3k cycles. The generator
`G` is pulled from `CURVE_CUMULATIVE_SUM_START` so the executor stays
in sync with the rest of the codebase. Adds the executor implementation,
full-VM stub, gas/cost entries, the `SEPTIC_VERIFY = 0x137` syscall
constant + asm wrapper, and a `schnorr_compute(A, s, e)` guest API.
@sameepsi sameepsi changed the title feat: Add septic curve precompiles (SEPTIC_ADD, SEPTIC_DOUBLE, SEPTIC_SCALAR_MUL, SEPTIC_VERIFY) Add septic curve precompiles (SEPTIC_ADD, SEPTIC_DOUBLE, SEPTIC_SCALAR_MUL, SEPTIC_VERIFY) Apr 14, 2026
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.

1 participant