Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dfd4d4a
feat: Run the MultiStark verifier natively — codegen + Rust-built IO …
arthurpaulino Jul 19, 2026
f9ad960
perf: Native Goldilocks arithmetic in the recursive verifier (execute…
arthurpaulino Jul 19, 2026
2730861
perf: Read the proof stream by index, with unconstrained byte fetches
arthurpaulino Jul 19, 2026
bad7a5d
perf: Hash Merkle 2-to-1 compressions as one direct blake3 block
arthurpaulino Jul 19, 2026
156afc9
perf: Hash MMCS leaf rows at lane granularity
arthurpaulino Jul 19, 2026
3f4c0d6
perf: Make challenger observation linear in transcript size
arthurpaulino Jul 19, 2026
9388550
perf: Segmented, hugepage-backed, hash-caching QueryMap storage
arthurpaulino Jul 19, 2026
6e02f21
perf: Prune production Aiur toplevels to their entrypoint closures
arthurpaulino Jul 19, 2026
8d4c39f
perf: Ingest the verifying key by IO slices and indexed reads
arthurpaulino Jul 19, 2026
b612f70
perf: Native extension-field representation (execute -40%, FFT -27%)
arthurpaulino Jul 19, 2026
976cb7a
perf: Hash MMCS leaves by walking the selected rows directly
arthurpaulino Jul 19, 2026
9d26eb9
feat: Sparse kernel proofs via multi-stark circuit activation
arthurpaulino Jul 20, 2026
491c65f
refactor: Inline the trivial Goldilocks constant helpers
arthurpaulino Jul 20, 2026
6fd0ab1
perf: Split-streams vk encoding (7.8x smaller, outer prove now fits)
arthurpaulino Jul 20, 2026
f63844b
chore: rustfmt + clippy fixes in vk_codec
arthurpaulino Jul 20, 2026
186654a
fix: Cover the hint-op terms in the @fn inlining passes
arthurpaulino Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Benchmarks/IxVM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,11 @@ def friParameters : Aiur.FriParameters := {
}

def main : IO Unit := do
let .ok toplevel := IxVM.ixVM
-- `ixon_serde_blake3_bench` is a bench-only entrypoint, present only in
-- the FULL toplevel (production prunes it), so this prove goes through
-- the generic interpreter — the codegen'd kernel mirrors the pruned
-- toplevel and has no code (or index) for bench entries.
let .ok toplevel := IxVM.ixVMFull
| throw (IO.userError "Merging failed")
let .ok compiled := toplevel.compile
| throw (IO.userError "Compilation failed")
Expand All@@ -34,9 +38,7 @@ def main : IO Unit := do

let _ ← bgroup "IxVM benchmarks" { oneShot := true } do
throughput (.Elements n.toUInt64 "consts")
-- IxVM-native prove: routes execution through the codegen'd Rust
-- kernel (`execute_generated`) instead of the bytecode interpreter.
bench "serde/blake3 Nat.add_comm"
(aiurSystem.proveIxVM funIdx #[.ofNat n])
(aiurSystem.prove funIdx #[.ofNat n])
ioBuffer
return
23 changes: 17 additions & 6 deletions Benchmarks/RecursiveVerifier.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,10 +136,13 @@ def main (args : List String) : IO UInt32 := do
IO.eprintln "inner proof failed to verify"
return 1
-- Proof (advice, channel 0), vk (channel 1), claims (channel 2), plus the
-- Blake3-bound vk/claims digests and FRI params as public input.
-- Blake3-bound vk/claims digests and FRI params as public input. The
-- advice buffer is built natively in Rust from the raw byte blobs
-- (`executeMultiStark` / `proveMultiStark`).
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes facSystem.vkBytes
claimBytes recCommitParams innerFri
let vkBytes := facSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
recCommitParams innerFri
-- Compile the verifier toplevel and run it over the proof.
let vTop ← match MultiStark.multiStark with
| .ok t => pure t
Expand All@@ -149,10 +152,17 @@ def main (args : List String) : IO UInt32 := do
| .error e => IO.eprintln s!"verifier compile failed: {e}"; return 1
let vIdx := vCompiled.getFuncIdx `verify_multi_stark_proof |>.get!
IO.println "executing verify_multi_stark_proof…"
-- `--use-bytecode`: route through the generic Aiur interpreter instead of
-- the codegen'd verifier (same escape hatch as `ix check --use-bytecode`;
-- useful for iterating on `Ix/MultiStark/*.lean` without regenerating
-- `crates/ixvm-codegen/src/aiur_multi_stark.rs`, and for measuring the
-- interpreter ↔ codegen gap).
let useBytecode := args.contains "--use-bytecode"
let e0 ← IO.monoNanosNow
match vCompiled.bytecode.execute vIdx pubInput io with
match vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes vkBytes
claimBytes useBytecode with
| .error e => IO.eprintln s!"verifier execution REJECTED: {e}"; return 1
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let e1 ← IO.monoNanosNow
let stats := Aiur.computeStats vCompiled qc
IO.println s!"verifier accepted, execute {secs e0 e1} s"
Expand All@@ -178,7 +188,8 @@ def main (args : List String) : IO UInt32 := do
let vSystem := AiurSystem.build vCompiled.bytecode recCommitParams innerFri
TracingTexray.resetPeakTreeRss
let t0 ← IO.monoNanosNow
let (vclaim, vproof, _) := vSystem.prove vIdx pubInput io
let (vclaim, vproof) := vSystem.proveMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
let nbytes := vproof.toBytes.size -- force the (lazy, pure) prove to run
let t1 ← IO.monoNanosNow
let outerPeak ← TracingTexray.peakTreeRssBytes
Expand Down
21 changes: 15 additions & 6 deletions Benchmarks/Typecheck.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -498,17 +498,25 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] recursively verifying {r.name} …"
(← IO.getStdout).flush
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes
aiurSystem.vkBytes claimBytes commitParams friParams
let vkBytes := aiurSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
commitParams friParams
-- Native path: the advice buffer is built in Rust from the raw
-- byte blobs and execution routes through the codegen'd verifier.
let (rvRes, rvSec) ← timed fun _ =>
vCompiled.bytecode.execute vIdx pubInput io
vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
match rvRes with
| .error e =>
IO.eprintln s!" ❌ recursive verifier REJECTED {r.name}'s proof: {e}"
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let rvStats := Aiur.computeStats vCompiled qc
IO.println s!" {r.name}: recursive={rvSec}s \
recursive-fft-cost={rvStats.totalFftCost}"
-- The per-circuit breakdown names where the verifier's cost
-- lives (deserialization vs blake3 vs FRI); texray-gated like
-- the other detailed diagnostics.
if useTexray then Aiur.printStats rvStats
let (row, _) := ordered[i]!
ordered := ordered.set! i
({ row with recursiveExecuteSec := some rvSec
Expand All@@ -520,8 +528,9 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] proving the verifier over {r.name} …"
(← IO.getStdout).flush
TracingTexray.resetPeakTreeRss
let ((rvClaim, rvProof, _), rvProveSec) ← timed fun _ =>
vSystem.prove vIdx pubInput io
let ((rvClaim, rvProof), rvProveSec) ← timed fun _ =>
vSystem.proveMultiStark vIdx pubInput proofBytes vkBytes
claimBytes
let rvPeak ← TracingTexray.peakTreeRssBytes
let rvProofBytes := Aiur.Proof.toBytes rvProof
let (rvVerifyRes, rvVerifySec) ← timed fun _ =>
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,10 +47,11 @@ dashmap = "6.1.0"
hashbrown = "0.15"
indexmap = "2"
itertools = "0.14.0"
libc = "0.2"
log = "0.4"
memmap2 = "0.9"
mimalloc = { version = "0.1", default-features = false }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "2c019220cb43ce946efec6b9caf507f5a6bccd1c" }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "6382036e970ab508e69b2db2b8ae5ad64672e34b" }
num-bigint = "0.4.6"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Compiler/Check.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,14 @@ def inferTerm (t : Term) : CheckM Typed.Term := match t with
let a' ← inferNoEscape a
let b' ← checkNoEscape b a'.typ
pure (Typed.Term.unconstrainedBigUintDivMod (.tuple #[a'.typ, a'.typ]) false a' b')
| .unconstrainedGToBytes a => do
-- The bytes are UNCONSTRAINED advice typed `u8`; the caller must
-- range-check them (see `Source.Term.unconstrainedGToBytes`).
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGToBytes (.array .u8 8) false a')
| .unconstrainedGInverse a => do
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGInverse .field false a')
| .toField a => do
let a' ← checkNoEscape a .u8
pure (Typed.Term.toField .field false a')
Expand DownExpand Up@@ -981,6 +989,10 @@ def zonkTypedTerm (t : Typed.Term) : CheckM Typed.Term := match t with
pure (.u8RangeCheck (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← zonkTyp τ) e (← zonkTypedTerm a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← zonkTyp τ) e (← zonkTypedTerm a))
| .toField τ e a => do pure (.toField (← zonkTyp τ) e (← zonkTypedTerm a))
| .u8FromFieldUnsafe τ e a => do
pure (.u8FromFieldUnsafe (← zonkTyp τ) e (← zonkTypedTerm a))
Expand Down
14 changes: 14 additions & 0 deletions Ix/Aiur/Compiler/Concretize.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -366,6 +366,10 @@ def termToConcrete
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← typToConcrete mono τ) e
(← termToConcrete mono a) (← termToConcrete mono b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← typToConcrete mono τ) e (← termToConcrete mono a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← typToConcrete mono τ) e (← termToConcrete mono a))
-- `toField` / `u8FromFieldUnsafe` are erased coercions: `u8` and `field`
-- share a representation, so we drop the wrapper and keep the inner term.
| .toField _ _ a | .u8FromFieldUnsafe _ _ a => termToConcrete mono a
Expand DownExpand Up@@ -575,6 +579,10 @@ def rewriteTypedTerm (decls : Typed.Decls)
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .toField τ e a => .toField (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe (rewriteTyp subst mono τ) e
Expand DownExpand Up@@ -653,6 +661,7 @@ def collectInTypedTerm (seen : Std.HashSet (Global × Array Typ)) :
collectInTypedTerm (collectInTypedTerm (collectInTyp seen τ) a) b
| .eqZero τ _ a | .store τ _ a | .load τ _ a | .ptrVal τ _ a | .toField τ _ a
| .u8FromFieldUnsafe τ _ a
| .unconstrainedGToBytes τ _ a | .unconstrainedGInverse τ _ a
| .u8BitDecomposition τ _ a | .u8ShiftLeft τ _ a | .u8ShiftRight τ _ a =>
collectInTypedTerm (collectInTyp seen τ) a
| .ioGetInfo τ _ c k =>
Expand DownExpand Up@@ -724,6 +733,7 @@ def collectCalls (decls : Typed.Decls)
collectCalls decls (collectCalls decls seen a) b
| .eqZero _ _ a | .store _ _ a | .load _ _ a | .ptrVal _ _ a | .toField _ _ a
| .u8FromFieldUnsafe _ _ a
| .unconstrainedGToBytes _ _ a | .unconstrainedGInverse _ _ a
| .u8BitDecomposition _ _ a | .u8ShiftLeft _ _ a | .u8ShiftRight _ _ a =>
collectCalls decls seen a
| .ioGetInfo _ _ c k =>
Expand DownExpand Up@@ -839,6 +849,10 @@ def substInTypedTerm (subst : Global → Option Typ) : Typed.Term → Typed.Term
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (Typ.instantiate subst τ) e
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedGToBytes τ e a =>
.unconstrainedGToBytes (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .unconstrainedGInverse τ e a =>
.unconstrainedGInverse (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .toField τ e a => .toField (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .u8FromFieldUnsafe τ e a =>
.u8FromFieldUnsafe (Typ.instantiate subst τ) e (substInTypedTerm subst a)
Expand Down
4 changes: 4 additions & 0 deletions Ix/Aiur/Compiler/Layout.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -207,6 +207,10 @@ def opLayout : Bytecode.Op → LayoutM Unit
-- No constraint relation, no lookup. Mirrors `IORead` (aux only) — the Rust
-- `constraints.rs` pushes exactly 2 auxiliaries and emits no relation.
| .unconstrainedBigUintDivMod .. => do pushDegrees #[1, 1]; bumpAuxiliaries 2
-- Unconstrained hints: fresh auxiliary witness columns only, no relation,
-- no lookup — same shape as `IORead`. The caller pins the values.
| .unconstrainedGToBytes _ => do pushDegrees $ .replicate 8 1; bumpAuxiliaries 8
| .unconstrainedGInverse _ => do pushDegree 1; bumpAuxiliaries
| .debug .. => pure ()

/-- Termination helper for blockLayout's Block/Ctrl traversal. -/
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Lower.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -333,6 +333,12 @@ def toIndex
let a ← expectIdx layoutMap bindings a
let b ← expectIdx layoutMap bindings b
pushOp (.unconstrainedBigUintDivMod a b) 2
| .unconstrainedGToBytes _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGToBytes a) 8
| .unconstrainedGInverse _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGInverse a)
| .debug _ _ label term ret => do
let term ← match term with
| none => pure none
Expand Down
2 changes: 2 additions & 0 deletions Ix/Aiur/Compiler/Match.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -391,6 +391,8 @@ def typedToSimple : Term → Simple.Term
| .u32LessThan τ e a b => .u32LessThan τ e (typedToSimple a) (typedToSimple b)
| .u8RangeCheck τ e a b => .u8RangeCheck τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes τ e (typedToSimple a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse τ e (typedToSimple a)
| .toField τ e a => .toField τ e (typedToSimple a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe τ e (typedToSimple a)
| .debug τ e l t r =>
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Simple.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,6 +136,12 @@ def simplifyTypedTerm (decls : Source.Decls) : Term → Except CheckError Term
let a' ← simplifyTypedTerm decls a
let b' ← simplifyTypedTerm decls b
pure (.unconstrainedBigUintDivMod τ e a' b')
| .unconstrainedGToBytes τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGToBytes τ e a')
| .unconstrainedGInverse τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGInverse τ e a')
| .toField τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.toField τ e a')
Expand Down
21 changes: 21 additions & 0 deletions Ix/Aiur/Goldilocks.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,27 @@ def G.u8BitDecomposition (a : G) : Fin 8 → G :=

def G.u32LessThan (a b : G) : G := if a.n < b.n then 1 else 0

/-- The 8 little-endian bytes of the canonical `u64` value. Semantic model of
the `unconstrained_g_to_bytes` hint. -/
def G.toLeBytes (a : G) : Fin 8 → G :=
fun i => G.ofUInt8 (a.val >>> (8 * i.val).toUInt64).toUInt8

/-- Exponentiation by squaring. Fuel-structural (64 bits covers any `n < 2⁶⁴`
exponent, in particular `p − 2`). -/
def G.pow (x : G) (n : Nat) : G := go n 64 where
go (n fuel : Nat) : G := match fuel with
| 0 => 1
| fuel + 1 =>
if n == 0 then 1
else
let h := go (n / 2) fuel
let sq := h * h
if n % 2 == 0 then sq else sq * x

/-- Fermat inverse `x^(p−2)`, with `0 ↦ 0`. Semantic model of the
`unconstrained_g_inverse` hint. -/
def G.inverse (x : G) : G := G.pow x (gSize.toNat - 2)

theorem G.one_ne_zero : ¬(1 : G) = (0 : G) := by decide

theorem G.add_comm (a b : G) : a + b = b + a := by
Expand Down
8 changes: 8 additions & 0 deletions Ix/Aiur/Interpret.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,6 +417,14 @@ partial def interp (decls : Decls) (bindings : Bindings) : Term → InterpM Valu
let _ ← interp decls bindings t1
let _ ← interp decls bindings t2
throwErr "unconstrainedBigUintDivMod: not implemented in debug interpreter"
| .unconstrainedGToBytes t => do
match ← interp decls bindings t with
| .field g => return .array (Array.ofFn fun i => .field (g.toLeBytes i))
| _ => throwErr "unconstrainedGToBytes: expected field value"
| .unconstrainedGInverse t => do
match ← interp decls bindings t with
| .field g => return .field g.inverse
| _ => throwErr "unconstrainedGInverse: expected field value"
-- `toField` / `u8FromFieldUnsafe` are erased coercions: value unchanged.
| .toField t | .u8FromFieldUnsafe t => interp decls bindings t
| .u8Lit n => return .field (G.ofNat n)
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Meta.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -205,6 +205,8 @@ syntax "u8_less_than" "(" aiur_trm ", " aiur_trm ")" : ai
syntax "u32_less_than" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "u8_range_check" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_big_uint_div_mod" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_g_to_bytes" "(" aiur_trm ")" : aiur_trm
syntax "unconstrained_g_inverse" "(" aiur_trm ")" : aiur_trm
syntax "to_field" "(" aiur_trm ")" : aiur_trm
syntax "u8_from_field_unsafe" "(" aiur_trm ")" : aiur_trm
syntax:max num "u8" : aiur_trm
Expand DownExpand Up@@ -351,6 +353,10 @@ partial def elabTrm : ElabStxCat `aiur_trm
mkAppM ``Source.Term.u8RangeCheck #[← elabTrm i, ← elabTrm j]
| `(aiur_trm| unconstrained_big_uint_div_mod($a:aiur_trm, $b:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedBigUintDivMod #[← elabTrm a, ← elabTrm b]
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGToBytes #[← elabTrm a]
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGInverse #[← elabTrm a]
| `(aiur_trm| to_field($a:aiur_trm)) => do
mkAppM ``Source.Term.toField #[← elabTrm a]
| `(aiur_trm| u8_from_field_unsafe($a:aiur_trm)) => do
Expand DownExpand Up@@ -579,6 +585,12 @@ where
let a ← replaceToken old new a
let b ← replaceToken old new b
`(aiur_trm| unconstrained_big_uint_div_mod($a, $b))
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_to_bytes($a))
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_inverse($a))
| `(aiur_trm| to_field($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| to_field($a))
Expand Down
23 changes: 23 additions & 0 deletions Ix/Aiur/Protocol.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,29 @@ def proveIxVM (system : @& AiurSystem)
let ioMap := ioMap.foldl (fun acc (k, v) => acc.insert k v) ∅
(claim, proof, ⟨ioData, ioMap⟩)

@[extern "rs_aiur_multi_stark_prove"]
private opaque proveMultiStark' : @& AiurSystem →
@& Bytecode.FunIdx → @& Array G →
(proofBytes : @& ByteArray) → (vkBytes : @& ByteArray) →
(claimBytes : @& ByteArray) → Bool →
Array G × Proof

/-- Prove the MultiStark recursive verifier over raw proof/vk/claims
byte blobs. The IO advice buffer is built natively in Rust (see
`Bytecode.Toplevel.executeMultiStark`); the execute step inside
the prove routes through the codegen'd verifier
(`crates/ixvm-codegen/src/aiur_multi_stark.rs`) unless
`useBytecode` is set. Only valid when `system` was built from the
production `MultiStark.multiStark` bytecode. Returns the claim
(`#[functionChannel, funIdx] ++ pubInput ++ output`) and the
`Proof`; the final buffer is not returned. -/
def proveMultiStark (system : @& AiurSystem)
(funIdx : @& Bytecode.FunIdx) (pubInput : @& Array G)
(proofBytes vkBytes claimBytes : @& ByteArray) (useBytecode : Bool := false) :
Array G × Proof :=
proveMultiStark' system funIdx pubInput proofBytes vkBytes claimBytes
useBytecode

@[extern "rs_aiur_system_prove_addr_with_env"]
private opaque proveAddrWithEnv' : @& AiurSystem →
@& Bytecode.FunIdx → @& EnvHandle → @& ByteArray →
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dfd4d4a
feat: Run the MultiStark verifier natively — codegen + Rust-built IO …
arthurpaulino Jul 19, 2026
f9ad960
perf: Native Goldilocks arithmetic in the recursive verifier (execute…
arthurpaulino Jul 19, 2026
2730861
perf: Read the proof stream by index, with unconstrained byte fetches
arthurpaulino Jul 19, 2026
bad7a5d
perf: Hash Merkle 2-to-1 compressions as one direct blake3 block
arthurpaulino Jul 19, 2026
156afc9
perf: Hash MMCS leaf rows at lane granularity
arthurpaulino Jul 19, 2026
3f4c0d6
perf: Make challenger observation linear in transcript size
arthurpaulino Jul 19, 2026
9388550
perf: Segmented, hugepage-backed, hash-caching QueryMap storage
arthurpaulino Jul 19, 2026
6e02f21
perf: Prune production Aiur toplevels to their entrypoint closures
arthurpaulino Jul 19, 2026
8d4c39f
perf: Ingest the verifying key by IO slices and indexed reads
arthurpaulino Jul 19, 2026
b612f70
perf: Native extension-field representation (execute -40%, FFT -27%)
arthurpaulino Jul 19, 2026
976cb7a
perf: Hash MMCS leaves by walking the selected rows directly
arthurpaulino Jul 19, 2026
9d26eb9
feat: Sparse kernel proofs via multi-stark circuit activation
arthurpaulino Jul 20, 2026
491c65f
refactor: Inline the trivial Goldilocks constant helpers
arthurpaulino Jul 20, 2026
6fd0ab1
perf: Split-streams vk encoding (7.8x smaller, outer prove now fits)
arthurpaulino Jul 20, 2026
f63844b
chore: rustfmt + clippy fixes in vk_codec
arthurpaulino Jul 20, 2026
186654a
fix: Cover the hint-op terms in the @fn inlining passes
arthurpaulino Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Benchmarks/IxVM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,11 @@ def friParameters : Aiur.FriParameters := {
}

def main : IO Unit := do
let .ok toplevel := IxVM.ixVM
-- `ixon_serde_blake3_bench` is a bench-only entrypoint, present only in
-- the FULL toplevel (production prunes it), so this prove goes through
-- the generic interpreter — the codegen'd kernel mirrors the pruned
-- toplevel and has no code (or index) for bench entries.
let .ok toplevel := IxVM.ixVMFull
| throw (IO.userError "Merging failed")
let .ok compiled := toplevel.compile
| throw (IO.userError "Compilation failed")
Expand All@@ -34,9 +38,7 @@ def main : IO Unit := do

let _ ← bgroup "IxVM benchmarks" { oneShot := true } do
throughput (.Elements n.toUInt64 "consts")
-- IxVM-native prove: routes execution through the codegen'd Rust
-- kernel (`execute_generated`) instead of the bytecode interpreter.
bench "serde/blake3 Nat.add_comm"
(aiurSystem.proveIxVM funIdx #[.ofNat n])
(aiurSystem.prove funIdx #[.ofNat n])
ioBuffer
return
23 changes: 17 additions & 6 deletions Benchmarks/RecursiveVerifier.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,10 +136,13 @@ def main (args : List String) : IO UInt32 := do
IO.eprintln "inner proof failed to verify"
return 1
-- Proof (advice, channel 0), vk (channel 1), claims (channel 2), plus the
-- Blake3-bound vk/claims digests and FRI params as public input.
-- Blake3-bound vk/claims digests and FRI params as public input. The
-- advice buffer is built natively in Rust from the raw byte blobs
-- (`executeMultiStark` / `proveMultiStark`).
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes facSystem.vkBytes
claimBytes recCommitParams innerFri
let vkBytes := facSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
recCommitParams innerFri
-- Compile the verifier toplevel and run it over the proof.
let vTop ← match MultiStark.multiStark with
| .ok t => pure t
Expand All@@ -149,10 +152,17 @@ def main (args : List String) : IO UInt32 := do
| .error e => IO.eprintln s!"verifier compile failed: {e}"; return 1
let vIdx := vCompiled.getFuncIdx `verify_multi_stark_proof |>.get!
IO.println "executing verify_multi_stark_proof…"
-- `--use-bytecode`: route through the generic Aiur interpreter instead of
-- the codegen'd verifier (same escape hatch as `ix check --use-bytecode`;
-- useful for iterating on `Ix/MultiStark/*.lean` without regenerating
-- `crates/ixvm-codegen/src/aiur_multi_stark.rs`, and for measuring the
-- interpreter ↔ codegen gap).
let useBytecode := args.contains "--use-bytecode"
let e0 ← IO.monoNanosNow
match vCompiled.bytecode.execute vIdx pubInput io with
match vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes vkBytes
claimBytes useBytecode with
| .error e => IO.eprintln s!"verifier execution REJECTED: {e}"; return 1
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let e1 ← IO.monoNanosNow
let stats := Aiur.computeStats vCompiled qc
IO.println s!"verifier accepted, execute {secs e0 e1} s"
Expand All@@ -178,7 +188,8 @@ def main (args : List String) : IO UInt32 := do
let vSystem := AiurSystem.build vCompiled.bytecode recCommitParams innerFri
TracingTexray.resetPeakTreeRss
let t0 ← IO.monoNanosNow
let (vclaim, vproof, _) := vSystem.prove vIdx pubInput io
let (vclaim, vproof) := vSystem.proveMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
let nbytes := vproof.toBytes.size -- force the (lazy, pure) prove to run
let t1 ← IO.monoNanosNow
let outerPeak ← TracingTexray.peakTreeRssBytes
Expand Down
21 changes: 15 additions & 6 deletions Benchmarks/Typecheck.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -498,17 +498,25 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] recursively verifying {r.name} …"
(← IO.getStdout).flush
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes
aiurSystem.vkBytes claimBytes commitParams friParams
let vkBytes := aiurSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
commitParams friParams
-- Native path: the advice buffer is built in Rust from the raw
-- byte blobs and execution routes through the codegen'd verifier.
let (rvRes, rvSec) ← timed fun _ =>
vCompiled.bytecode.execute vIdx pubInput io
vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
match rvRes with
| .error e =>
IO.eprintln s!" ❌ recursive verifier REJECTED {r.name}'s proof: {e}"
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let rvStats := Aiur.computeStats vCompiled qc
IO.println s!" {r.name}: recursive={rvSec}s \
recursive-fft-cost={rvStats.totalFftCost}"
-- The per-circuit breakdown names where the verifier's cost
-- lives (deserialization vs blake3 vs FRI); texray-gated like
-- the other detailed diagnostics.
if useTexray then Aiur.printStats rvStats
let (row, _) := ordered[i]!
ordered := ordered.set! i
({ row with recursiveExecuteSec := some rvSec
Expand All@@ -520,8 +528,9 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] proving the verifier over {r.name} …"
(← IO.getStdout).flush
TracingTexray.resetPeakTreeRss
let ((rvClaim, rvProof, _), rvProveSec) ← timed fun _ =>
vSystem.prove vIdx pubInput io
let ((rvClaim, rvProof), rvProveSec) ← timed fun _ =>
vSystem.proveMultiStark vIdx pubInput proofBytes vkBytes
claimBytes
let rvPeak ← TracingTexray.peakTreeRssBytes
let rvProofBytes := Aiur.Proof.toBytes rvProof
let (rvVerifyRes, rvVerifySec) ← timed fun _ =>
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,10 +47,11 @@ dashmap = "6.1.0"
hashbrown = "0.15"
indexmap = "2"
itertools = "0.14.0"
libc = "0.2"
log = "0.4"
memmap2 = "0.9"
mimalloc = { version = "0.1", default-features = false }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "2c019220cb43ce946efec6b9caf507f5a6bccd1c" }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "6382036e970ab508e69b2db2b8ae5ad64672e34b" }
num-bigint = "0.4.6"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Compiler/Check.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,14 @@ def inferTerm (t : Term) : CheckM Typed.Term := match t with
let a' ← inferNoEscape a
let b' ← checkNoEscape b a'.typ
pure (Typed.Term.unconstrainedBigUintDivMod (.tuple #[a'.typ, a'.typ]) false a' b')
| .unconstrainedGToBytes a => do
-- The bytes are UNCONSTRAINED advice typed `u8`; the caller must
-- range-check them (see `Source.Term.unconstrainedGToBytes`).
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGToBytes (.array .u8 8) false a')
| .unconstrainedGInverse a => do
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGInverse .field false a')
| .toField a => do
let a' ← checkNoEscape a .u8
pure (Typed.Term.toField .field false a')
Expand DownExpand Up@@ -981,6 +989,10 @@ def zonkTypedTerm (t : Typed.Term) : CheckM Typed.Term := match t with
pure (.u8RangeCheck (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← zonkTyp τ) e (← zonkTypedTerm a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← zonkTyp τ) e (← zonkTypedTerm a))
| .toField τ e a => do pure (.toField (← zonkTyp τ) e (← zonkTypedTerm a))
| .u8FromFieldUnsafe τ e a => do
pure (.u8FromFieldUnsafe (← zonkTyp τ) e (← zonkTypedTerm a))
Expand Down
14 changes: 14 additions & 0 deletions Ix/Aiur/Compiler/Concretize.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -366,6 +366,10 @@ def termToConcrete
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← typToConcrete mono τ) e
(← termToConcrete mono a) (← termToConcrete mono b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← typToConcrete mono τ) e (← termToConcrete mono a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← typToConcrete mono τ) e (← termToConcrete mono a))
-- `toField` / `u8FromFieldUnsafe` are erased coercions: `u8` and `field`
-- share a representation, so we drop the wrapper and keep the inner term.
| .toField _ _ a | .u8FromFieldUnsafe _ _ a => termToConcrete mono a
Expand DownExpand Up@@ -575,6 +579,10 @@ def rewriteTypedTerm (decls : Typed.Decls)
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .toField τ e a => .toField (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe (rewriteTyp subst mono τ) e
Expand DownExpand Up@@ -653,6 +661,7 @@ def collectInTypedTerm (seen : Std.HashSet (Global × Array Typ)) :
collectInTypedTerm (collectInTypedTerm (collectInTyp seen τ) a) b
| .eqZero τ _ a | .store τ _ a | .load τ _ a | .ptrVal τ _ a | .toField τ _ a
| .u8FromFieldUnsafe τ _ a
| .unconstrainedGToBytes τ _ a | .unconstrainedGInverse τ _ a
| .u8BitDecomposition τ _ a | .u8ShiftLeft τ _ a | .u8ShiftRight τ _ a =>
collectInTypedTerm (collectInTyp seen τ) a
| .ioGetInfo τ _ c k =>
Expand DownExpand Up@@ -724,6 +733,7 @@ def collectCalls (decls : Typed.Decls)
collectCalls decls (collectCalls decls seen a) b
| .eqZero _ _ a | .store _ _ a | .load _ _ a | .ptrVal _ _ a | .toField _ _ a
| .u8FromFieldUnsafe _ _ a
| .unconstrainedGToBytes _ _ a | .unconstrainedGInverse _ _ a
| .u8BitDecomposition _ _ a | .u8ShiftLeft _ _ a | .u8ShiftRight _ _ a =>
collectCalls decls seen a
| .ioGetInfo _ _ c k =>
Expand DownExpand Up@@ -839,6 +849,10 @@ def substInTypedTerm (subst : Global → Option Typ) : Typed.Term → Typed.Term
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (Typ.instantiate subst τ) e
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedGToBytes τ e a =>
.unconstrainedGToBytes (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .unconstrainedGInverse τ e a =>
.unconstrainedGInverse (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .toField τ e a => .toField (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .u8FromFieldUnsafe τ e a =>
.u8FromFieldUnsafe (Typ.instantiate subst τ) e (substInTypedTerm subst a)
Expand Down
4 changes: 4 additions & 0 deletions Ix/Aiur/Compiler/Layout.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -207,6 +207,10 @@ def opLayout : Bytecode.Op → LayoutM Unit
-- No constraint relation, no lookup. Mirrors `IORead` (aux only) — the Rust
-- `constraints.rs` pushes exactly 2 auxiliaries and emits no relation.
| .unconstrainedBigUintDivMod .. => do pushDegrees #[1, 1]; bumpAuxiliaries 2
-- Unconstrained hints: fresh auxiliary witness columns only, no relation,
-- no lookup — same shape as `IORead`. The caller pins the values.
| .unconstrainedGToBytes _ => do pushDegrees $ .replicate 8 1; bumpAuxiliaries 8
| .unconstrainedGInverse _ => do pushDegree 1; bumpAuxiliaries
| .debug .. => pure ()

/-- Termination helper for blockLayout's Block/Ctrl traversal. -/
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Lower.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -333,6 +333,12 @@ def toIndex
let a ← expectIdx layoutMap bindings a
let b ← expectIdx layoutMap bindings b
pushOp (.unconstrainedBigUintDivMod a b) 2
| .unconstrainedGToBytes _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGToBytes a) 8
| .unconstrainedGInverse _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGInverse a)
| .debug _ _ label term ret => do
let term ← match term with
| none => pure none
Expand Down
2 changes: 2 additions & 0 deletions Ix/Aiur/Compiler/Match.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -391,6 +391,8 @@ def typedToSimple : Term → Simple.Term
| .u32LessThan τ e a b => .u32LessThan τ e (typedToSimple a) (typedToSimple b)
| .u8RangeCheck τ e a b => .u8RangeCheck τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes τ e (typedToSimple a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse τ e (typedToSimple a)
| .toField τ e a => .toField τ e (typedToSimple a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe τ e (typedToSimple a)
| .debug τ e l t r =>
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Simple.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,6 +136,12 @@ def simplifyTypedTerm (decls : Source.Decls) : Term → Except CheckError Term
let a' ← simplifyTypedTerm decls a
let b' ← simplifyTypedTerm decls b
pure (.unconstrainedBigUintDivMod τ e a' b')
| .unconstrainedGToBytes τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGToBytes τ e a')
| .unconstrainedGInverse τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGInverse τ e a')
| .toField τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.toField τ e a')
Expand Down
21 changes: 21 additions & 0 deletions Ix/Aiur/Goldilocks.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,27 @@ def G.u8BitDecomposition (a : G) : Fin 8 → G :=

def G.u32LessThan (a b : G) : G := if a.n < b.n then 1 else 0

/-- The 8 little-endian bytes of the canonical `u64` value. Semantic model of
the `unconstrained_g_to_bytes` hint. -/
def G.toLeBytes (a : G) : Fin 8 → G :=
fun i => G.ofUInt8 (a.val >>> (8 * i.val).toUInt64).toUInt8

/-- Exponentiation by squaring. Fuel-structural (64 bits covers any `n < 2⁶⁴`
exponent, in particular `p − 2`). -/
def G.pow (x : G) (n : Nat) : G := go n 64 where
go (n fuel : Nat) : G := match fuel with
| 0 => 1
| fuel + 1 =>
if n == 0 then 1
else
let h := go (n / 2) fuel
let sq := h * h
if n % 2 == 0 then sq else sq * x

/-- Fermat inverse `x^(p−2)`, with `0 ↦ 0`. Semantic model of the
`unconstrained_g_inverse` hint. -/
def G.inverse (x : G) : G := G.pow x (gSize.toNat - 2)

theorem G.one_ne_zero : ¬(1 : G) = (0 : G) := by decide

theorem G.add_comm (a b : G) : a + b = b + a := by
Expand Down
8 changes: 8 additions & 0 deletions Ix/Aiur/Interpret.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,6 +417,14 @@ partial def interp (decls : Decls) (bindings : Bindings) : Term → InterpM Valu
let _ ← interp decls bindings t1
let _ ← interp decls bindings t2
throwErr "unconstrainedBigUintDivMod: not implemented in debug interpreter"
| .unconstrainedGToBytes t => do
match ← interp decls bindings t with
| .field g => return .array (Array.ofFn fun i => .field (g.toLeBytes i))
| _ => throwErr "unconstrainedGToBytes: expected field value"
| .unconstrainedGInverse t => do
match ← interp decls bindings t with
| .field g => return .field g.inverse
| _ => throwErr "unconstrainedGInverse: expected field value"
-- `toField` / `u8FromFieldUnsafe` are erased coercions: value unchanged.
| .toField t | .u8FromFieldUnsafe t => interp decls bindings t
| .u8Lit n => return .field (G.ofNat n)
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Meta.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -205,6 +205,8 @@ syntax "u8_less_than" "(" aiur_trm ", " aiur_trm ")" : ai
syntax "u32_less_than" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "u8_range_check" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_big_uint_div_mod" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_g_to_bytes" "(" aiur_trm ")" : aiur_trm
syntax "unconstrained_g_inverse" "(" aiur_trm ")" : aiur_trm
syntax "to_field" "(" aiur_trm ")" : aiur_trm
syntax "u8_from_field_unsafe" "(" aiur_trm ")" : aiur_trm
syntax:max num "u8" : aiur_trm
Expand DownExpand Up@@ -351,6 +353,10 @@ partial def elabTrm : ElabStxCat `aiur_trm
mkAppM ``Source.Term.u8RangeCheck #[← elabTrm i, ← elabTrm j]
| `(aiur_trm| unconstrained_big_uint_div_mod($a:aiur_trm, $b:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedBigUintDivMod #[← elabTrm a, ← elabTrm b]
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGToBytes #[← elabTrm a]
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGInverse #[← elabTrm a]
| `(aiur_trm| to_field($a:aiur_trm)) => do
mkAppM ``Source.Term.toField #[← elabTrm a]
| `(aiur_trm| u8_from_field_unsafe($a:aiur_trm)) => do
Expand DownExpand Up@@ -579,6 +585,12 @@ where
let a ← replaceToken old new a
let b ← replaceToken old new b
`(aiur_trm| unconstrained_big_uint_div_mod($a, $b))
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_to_bytes($a))
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_inverse($a))
| `(aiur_trm| to_field($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| to_field($a))
Expand Down
23 changes: 23 additions & 0 deletions Ix/Aiur/Protocol.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,29 @@ def proveIxVM (system : @& AiurSystem)
let ioMap := ioMap.foldl (fun acc (k, v) => acc.insert k v) ∅
(claim, proof, ⟨ioData, ioMap⟩)

@[extern "rs_aiur_multi_stark_prove"]
private opaque proveMultiStark' : @& AiurSystem →
@& Bytecode.FunIdx → @& Array G →
(proofBytes : @& ByteArray) → (vkBytes : @& ByteArray) →
(claimBytes : @& ByteArray) → Bool →
Array G × Proof

/-- Prove the MultiStark recursive verifier over raw proof/vk/claims
byte blobs. The IO advice buffer is built natively in Rust (see
`Bytecode.Toplevel.executeMultiStark`); the execute step inside
the prove routes through the codegen'd verifier
(`crates/ixvm-codegen/src/aiur_multi_stark.rs`) unless
`useBytecode` is set. Only valid when `system` was built from the
production `MultiStark.multiStark` bytecode. Returns the claim
(`#[functionChannel, funIdx] ++ pubInput ++ output`) and the
`Proof`; the final buffer is not returned. -/
def proveMultiStark (system : @& AiurSystem)
(funIdx : @& Bytecode.FunIdx) (pubInput : @& Array G)
(proofBytes vkBytes claimBytes : @& ByteArray) (useBytecode : Bool := false) :
Array G × Proof :=
proveMultiStark' system funIdx pubInput proofBytes vkBytes claimBytes
useBytecode

@[extern "rs_aiur_system_prove_addr_with_env"]
private opaque proveAddrWithEnv' : @& AiurSystem →
@& Bytecode.FunIdx → @& EnvHandle → @& ByteArray →
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dfd4d4a
feat: Run the MultiStark verifier natively — codegen + Rust-built IO …
arthurpaulino Jul 19, 2026
f9ad960
perf: Native Goldilocks arithmetic in the recursive verifier (execute…
arthurpaulino Jul 19, 2026
2730861
perf: Read the proof stream by index, with unconstrained byte fetches
arthurpaulino Jul 19, 2026
bad7a5d
perf: Hash Merkle 2-to-1 compressions as one direct blake3 block
arthurpaulino Jul 19, 2026
156afc9
perf: Hash MMCS leaf rows at lane granularity
arthurpaulino Jul 19, 2026
3f4c0d6
perf: Make challenger observation linear in transcript size
arthurpaulino Jul 19, 2026
9388550
perf: Segmented, hugepage-backed, hash-caching QueryMap storage
arthurpaulino Jul 19, 2026
6e02f21
perf: Prune production Aiur toplevels to their entrypoint closures
arthurpaulino Jul 19, 2026
8d4c39f
perf: Ingest the verifying key by IO slices and indexed reads
arthurpaulino Jul 19, 2026
b612f70
perf: Native extension-field representation (execute -40%, FFT -27%)
arthurpaulino Jul 19, 2026
976cb7a
perf: Hash MMCS leaves by walking the selected rows directly
arthurpaulino Jul 19, 2026
9d26eb9
feat: Sparse kernel proofs via multi-stark circuit activation
arthurpaulino Jul 20, 2026
491c65f
refactor: Inline the trivial Goldilocks constant helpers
arthurpaulino Jul 20, 2026
6fd0ab1
perf: Split-streams vk encoding (7.8x smaller, outer prove now fits)
arthurpaulino Jul 20, 2026
f63844b
chore: rustfmt + clippy fixes in vk_codec
arthurpaulino Jul 20, 2026
186654a
fix: Cover the hint-op terms in the @fn inlining passes
arthurpaulino Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Benchmarks/IxVM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,11 @@ def friParameters : Aiur.FriParameters := {
}

def main : IO Unit := do
let .ok toplevel := IxVM.ixVM
-- `ixon_serde_blake3_bench` is a bench-only entrypoint, present only in
-- the FULL toplevel (production prunes it), so this prove goes through
-- the generic interpreter — the codegen'd kernel mirrors the pruned
-- toplevel and has no code (or index) for bench entries.
let .ok toplevel := IxVM.ixVMFull
| throw (IO.userError "Merging failed")
let .ok compiled := toplevel.compile
| throw (IO.userError "Compilation failed")
Expand All@@ -34,9 +38,7 @@ def main : IO Unit := do

let _ ← bgroup "IxVM benchmarks" { oneShot := true } do
throughput (.Elements n.toUInt64 "consts")
-- IxVM-native prove: routes execution through the codegen'd Rust
-- kernel (`execute_generated`) instead of the bytecode interpreter.
bench "serde/blake3 Nat.add_comm"
(aiurSystem.proveIxVM funIdx #[.ofNat n])
(aiurSystem.prove funIdx #[.ofNat n])
ioBuffer
return
23 changes: 17 additions & 6 deletions Benchmarks/RecursiveVerifier.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,10 +136,13 @@ def main (args : List String) : IO UInt32 := do
IO.eprintln "inner proof failed to verify"
return 1
-- Proof (advice, channel 0), vk (channel 1), claims (channel 2), plus the
-- Blake3-bound vk/claims digests and FRI params as public input.
-- Blake3-bound vk/claims digests and FRI params as public input. The
-- advice buffer is built natively in Rust from the raw byte blobs
-- (`executeMultiStark` / `proveMultiStark`).
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes facSystem.vkBytes
claimBytes recCommitParams innerFri
let vkBytes := facSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
recCommitParams innerFri
-- Compile the verifier toplevel and run it over the proof.
let vTop ← match MultiStark.multiStark with
| .ok t => pure t
Expand All@@ -149,10 +152,17 @@ def main (args : List String) : IO UInt32 := do
| .error e => IO.eprintln s!"verifier compile failed: {e}"; return 1
let vIdx := vCompiled.getFuncIdx `verify_multi_stark_proof |>.get!
IO.println "executing verify_multi_stark_proof…"
-- `--use-bytecode`: route through the generic Aiur interpreter instead of
-- the codegen'd verifier (same escape hatch as `ix check --use-bytecode`;
-- useful for iterating on `Ix/MultiStark/*.lean` without regenerating
-- `crates/ixvm-codegen/src/aiur_multi_stark.rs`, and for measuring the
-- interpreter ↔ codegen gap).
let useBytecode := args.contains "--use-bytecode"
let e0 ← IO.monoNanosNow
match vCompiled.bytecode.execute vIdx pubInput io with
match vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes vkBytes
claimBytes useBytecode with
| .error e => IO.eprintln s!"verifier execution REJECTED: {e}"; return 1
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let e1 ← IO.monoNanosNow
let stats := Aiur.computeStats vCompiled qc
IO.println s!"verifier accepted, execute {secs e0 e1} s"
Expand All@@ -178,7 +188,8 @@ def main (args : List String) : IO UInt32 := do
let vSystem := AiurSystem.build vCompiled.bytecode recCommitParams innerFri
TracingTexray.resetPeakTreeRss
let t0 ← IO.monoNanosNow
let (vclaim, vproof, _) := vSystem.prove vIdx pubInput io
let (vclaim, vproof) := vSystem.proveMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
let nbytes := vproof.toBytes.size -- force the (lazy, pure) prove to run
let t1 ← IO.monoNanosNow
let outerPeak ← TracingTexray.peakTreeRssBytes
Expand Down
21 changes: 15 additions & 6 deletions Benchmarks/Typecheck.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -498,17 +498,25 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] recursively verifying {r.name} …"
(← IO.getStdout).flush
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes
aiurSystem.vkBytes claimBytes commitParams friParams
let vkBytes := aiurSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
commitParams friParams
-- Native path: the advice buffer is built in Rust from the raw
-- byte blobs and execution routes through the codegen'd verifier.
let (rvRes, rvSec) ← timed fun _ =>
vCompiled.bytecode.execute vIdx pubInput io
vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
match rvRes with
| .error e =>
IO.eprintln s!" ❌ recursive verifier REJECTED {r.name}'s proof: {e}"
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let rvStats := Aiur.computeStats vCompiled qc
IO.println s!" {r.name}: recursive={rvSec}s \
recursive-fft-cost={rvStats.totalFftCost}"
-- The per-circuit breakdown names where the verifier's cost
-- lives (deserialization vs blake3 vs FRI); texray-gated like
-- the other detailed diagnostics.
if useTexray then Aiur.printStats rvStats
let (row, _) := ordered[i]!
ordered := ordered.set! i
({ row with recursiveExecuteSec := some rvSec
Expand All@@ -520,8 +528,9 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] proving the verifier over {r.name} …"
(← IO.getStdout).flush
TracingTexray.resetPeakTreeRss
let ((rvClaim, rvProof, _), rvProveSec) ← timed fun _ =>
vSystem.prove vIdx pubInput io
let ((rvClaim, rvProof), rvProveSec) ← timed fun _ =>
vSystem.proveMultiStark vIdx pubInput proofBytes vkBytes
claimBytes
let rvPeak ← TracingTexray.peakTreeRssBytes
let rvProofBytes := Aiur.Proof.toBytes rvProof
let (rvVerifyRes, rvVerifySec) ← timed fun _ =>
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,10 +47,11 @@ dashmap = "6.1.0"
hashbrown = "0.15"
indexmap = "2"
itertools = "0.14.0"
libc = "0.2"
log = "0.4"
memmap2 = "0.9"
mimalloc = { version = "0.1", default-features = false }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "2c019220cb43ce946efec6b9caf507f5a6bccd1c" }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "6382036e970ab508e69b2db2b8ae5ad64672e34b" }
num-bigint = "0.4.6"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Compiler/Check.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,14 @@ def inferTerm (t : Term) : CheckM Typed.Term := match t with
let a' ← inferNoEscape a
let b' ← checkNoEscape b a'.typ
pure (Typed.Term.unconstrainedBigUintDivMod (.tuple #[a'.typ, a'.typ]) false a' b')
| .unconstrainedGToBytes a => do
-- The bytes are UNCONSTRAINED advice typed `u8`; the caller must
-- range-check them (see `Source.Term.unconstrainedGToBytes`).
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGToBytes (.array .u8 8) false a')
| .unconstrainedGInverse a => do
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGInverse .field false a')
| .toField a => do
let a' ← checkNoEscape a .u8
pure (Typed.Term.toField .field false a')
Expand DownExpand Up@@ -981,6 +989,10 @@ def zonkTypedTerm (t : Typed.Term) : CheckM Typed.Term := match t with
pure (.u8RangeCheck (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← zonkTyp τ) e (← zonkTypedTerm a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← zonkTyp τ) e (← zonkTypedTerm a))
| .toField τ e a => do pure (.toField (← zonkTyp τ) e (← zonkTypedTerm a))
| .u8FromFieldUnsafe τ e a => do
pure (.u8FromFieldUnsafe (← zonkTyp τ) e (← zonkTypedTerm a))
Expand Down
14 changes: 14 additions & 0 deletions Ix/Aiur/Compiler/Concretize.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -366,6 +366,10 @@ def termToConcrete
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← typToConcrete mono τ) e
(← termToConcrete mono a) (← termToConcrete mono b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← typToConcrete mono τ) e (← termToConcrete mono a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← typToConcrete mono τ) e (← termToConcrete mono a))
-- `toField` / `u8FromFieldUnsafe` are erased coercions: `u8` and `field`
-- share a representation, so we drop the wrapper and keep the inner term.
| .toField _ _ a | .u8FromFieldUnsafe _ _ a => termToConcrete mono a
Expand DownExpand Up@@ -575,6 +579,10 @@ def rewriteTypedTerm (decls : Typed.Decls)
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .toField τ e a => .toField (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe (rewriteTyp subst mono τ) e
Expand DownExpand Up@@ -653,6 +661,7 @@ def collectInTypedTerm (seen : Std.HashSet (Global × Array Typ)) :
collectInTypedTerm (collectInTypedTerm (collectInTyp seen τ) a) b
| .eqZero τ _ a | .store τ _ a | .load τ _ a | .ptrVal τ _ a | .toField τ _ a
| .u8FromFieldUnsafe τ _ a
| .unconstrainedGToBytes τ _ a | .unconstrainedGInverse τ _ a
| .u8BitDecomposition τ _ a | .u8ShiftLeft τ _ a | .u8ShiftRight τ _ a =>
collectInTypedTerm (collectInTyp seen τ) a
| .ioGetInfo τ _ c k =>
Expand DownExpand Up@@ -724,6 +733,7 @@ def collectCalls (decls : Typed.Decls)
collectCalls decls (collectCalls decls seen a) b
| .eqZero _ _ a | .store _ _ a | .load _ _ a | .ptrVal _ _ a | .toField _ _ a
| .u8FromFieldUnsafe _ _ a
| .unconstrainedGToBytes _ _ a | .unconstrainedGInverse _ _ a
| .u8BitDecomposition _ _ a | .u8ShiftLeft _ _ a | .u8ShiftRight _ _ a =>
collectCalls decls seen a
| .ioGetInfo _ _ c k =>
Expand DownExpand Up@@ -839,6 +849,10 @@ def substInTypedTerm (subst : Global → Option Typ) : Typed.Term → Typed.Term
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (Typ.instantiate subst τ) e
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedGToBytes τ e a =>
.unconstrainedGToBytes (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .unconstrainedGInverse τ e a =>
.unconstrainedGInverse (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .toField τ e a => .toField (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .u8FromFieldUnsafe τ e a =>
.u8FromFieldUnsafe (Typ.instantiate subst τ) e (substInTypedTerm subst a)
Expand Down
4 changes: 4 additions & 0 deletions Ix/Aiur/Compiler/Layout.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -207,6 +207,10 @@ def opLayout : Bytecode.Op → LayoutM Unit
-- No constraint relation, no lookup. Mirrors `IORead` (aux only) — the Rust
-- `constraints.rs` pushes exactly 2 auxiliaries and emits no relation.
| .unconstrainedBigUintDivMod .. => do pushDegrees #[1, 1]; bumpAuxiliaries 2
-- Unconstrained hints: fresh auxiliary witness columns only, no relation,
-- no lookup — same shape as `IORead`. The caller pins the values.
| .unconstrainedGToBytes _ => do pushDegrees $ .replicate 8 1; bumpAuxiliaries 8
| .unconstrainedGInverse _ => do pushDegree 1; bumpAuxiliaries
| .debug .. => pure ()

/-- Termination helper for blockLayout's Block/Ctrl traversal. -/
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Lower.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -333,6 +333,12 @@ def toIndex
let a ← expectIdx layoutMap bindings a
let b ← expectIdx layoutMap bindings b
pushOp (.unconstrainedBigUintDivMod a b) 2
| .unconstrainedGToBytes _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGToBytes a) 8
| .unconstrainedGInverse _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGInverse a)
| .debug _ _ label term ret => do
let term ← match term with
| none => pure none
Expand Down
2 changes: 2 additions & 0 deletions Ix/Aiur/Compiler/Match.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -391,6 +391,8 @@ def typedToSimple : Term → Simple.Term
| .u32LessThan τ e a b => .u32LessThan τ e (typedToSimple a) (typedToSimple b)
| .u8RangeCheck τ e a b => .u8RangeCheck τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes τ e (typedToSimple a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse τ e (typedToSimple a)
| .toField τ e a => .toField τ e (typedToSimple a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe τ e (typedToSimple a)
| .debug τ e l t r =>
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Simple.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,6 +136,12 @@ def simplifyTypedTerm (decls : Source.Decls) : Term → Except CheckError Term
let a' ← simplifyTypedTerm decls a
let b' ← simplifyTypedTerm decls b
pure (.unconstrainedBigUintDivMod τ e a' b')
| .unconstrainedGToBytes τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGToBytes τ e a')
| .unconstrainedGInverse τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGInverse τ e a')
| .toField τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.toField τ e a')
Expand Down
21 changes: 21 additions & 0 deletions Ix/Aiur/Goldilocks.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,27 @@ def G.u8BitDecomposition (a : G) : Fin 8 → G :=

def G.u32LessThan (a b : G) : G := if a.n < b.n then 1 else 0

/-- The 8 little-endian bytes of the canonical `u64` value. Semantic model of
the `unconstrained_g_to_bytes` hint. -/
def G.toLeBytes (a : G) : Fin 8 → G :=
fun i => G.ofUInt8 (a.val >>> (8 * i.val).toUInt64).toUInt8

/-- Exponentiation by squaring. Fuel-structural (64 bits covers any `n < 2⁶⁴`
exponent, in particular `p − 2`). -/
def G.pow (x : G) (n : Nat) : G := go n 64 where
go (n fuel : Nat) : G := match fuel with
| 0 => 1
| fuel + 1 =>
if n == 0 then 1
else
let h := go (n / 2) fuel
let sq := h * h
if n % 2 == 0 then sq else sq * x

/-- Fermat inverse `x^(p−2)`, with `0 ↦ 0`. Semantic model of the
`unconstrained_g_inverse` hint. -/
def G.inverse (x : G) : G := G.pow x (gSize.toNat - 2)

theorem G.one_ne_zero : ¬(1 : G) = (0 : G) := by decide

theorem G.add_comm (a b : G) : a + b = b + a := by
Expand Down
8 changes: 8 additions & 0 deletions Ix/Aiur/Interpret.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,6 +417,14 @@ partial def interp (decls : Decls) (bindings : Bindings) : Term → InterpM Valu
let _ ← interp decls bindings t1
let _ ← interp decls bindings t2
throwErr "unconstrainedBigUintDivMod: not implemented in debug interpreter"
| .unconstrainedGToBytes t => do
match ← interp decls bindings t with
| .field g => return .array (Array.ofFn fun i => .field (g.toLeBytes i))
| _ => throwErr "unconstrainedGToBytes: expected field value"
| .unconstrainedGInverse t => do
match ← interp decls bindings t with
| .field g => return .field g.inverse
| _ => throwErr "unconstrainedGInverse: expected field value"
-- `toField` / `u8FromFieldUnsafe` are erased coercions: value unchanged.
| .toField t | .u8FromFieldUnsafe t => interp decls bindings t
| .u8Lit n => return .field (G.ofNat n)
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Meta.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -205,6 +205,8 @@ syntax "u8_less_than" "(" aiur_trm ", " aiur_trm ")" : ai
syntax "u32_less_than" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "u8_range_check" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_big_uint_div_mod" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_g_to_bytes" "(" aiur_trm ")" : aiur_trm
syntax "unconstrained_g_inverse" "(" aiur_trm ")" : aiur_trm
syntax "to_field" "(" aiur_trm ")" : aiur_trm
syntax "u8_from_field_unsafe" "(" aiur_trm ")" : aiur_trm
syntax:max num "u8" : aiur_trm
Expand DownExpand Up@@ -351,6 +353,10 @@ partial def elabTrm : ElabStxCat `aiur_trm
mkAppM ``Source.Term.u8RangeCheck #[← elabTrm i, ← elabTrm j]
| `(aiur_trm| unconstrained_big_uint_div_mod($a:aiur_trm, $b:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedBigUintDivMod #[← elabTrm a, ← elabTrm b]
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGToBytes #[← elabTrm a]
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGInverse #[← elabTrm a]
| `(aiur_trm| to_field($a:aiur_trm)) => do
mkAppM ``Source.Term.toField #[← elabTrm a]
| `(aiur_trm| u8_from_field_unsafe($a:aiur_trm)) => do
Expand DownExpand Up@@ -579,6 +585,12 @@ where
let a ← replaceToken old new a
let b ← replaceToken old new b
`(aiur_trm| unconstrained_big_uint_div_mod($a, $b))
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_to_bytes($a))
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_inverse($a))
| `(aiur_trm| to_field($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| to_field($a))
Expand Down
23 changes: 23 additions & 0 deletions Ix/Aiur/Protocol.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,29 @@ def proveIxVM (system : @& AiurSystem)
let ioMap := ioMap.foldl (fun acc (k, v) => acc.insert k v) ∅
(claim, proof, ⟨ioData, ioMap⟩)

@[extern "rs_aiur_multi_stark_prove"]
private opaque proveMultiStark' : @& AiurSystem →
@& Bytecode.FunIdx → @& Array G →
(proofBytes : @& ByteArray) → (vkBytes : @& ByteArray) →
(claimBytes : @& ByteArray) → Bool →
Array G × Proof

/-- Prove the MultiStark recursive verifier over raw proof/vk/claims
byte blobs. The IO advice buffer is built natively in Rust (see
`Bytecode.Toplevel.executeMultiStark`); the execute step inside
the prove routes through the codegen'd verifier
(`crates/ixvm-codegen/src/aiur_multi_stark.rs`) unless
`useBytecode` is set. Only valid when `system` was built from the
production `MultiStark.multiStark` bytecode. Returns the claim
(`#[functionChannel, funIdx] ++ pubInput ++ output`) and the
`Proof`; the final buffer is not returned. -/
def proveMultiStark (system : @& AiurSystem)
(funIdx : @& Bytecode.FunIdx) (pubInput : @& Array G)
(proofBytes vkBytes claimBytes : @& ByteArray) (useBytecode : Bool := false) :
Array G × Proof :=
proveMultiStark' system funIdx pubInput proofBytes vkBytes claimBytes
useBytecode

@[extern "rs_aiur_system_prove_addr_with_env"]
private opaque proveAddrWithEnv' : @& AiurSystem →
@& Bytecode.FunIdx → @& EnvHandle → @& ByteArray →
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dfd4d4a
feat: Run the MultiStark verifier natively — codegen + Rust-built IO …
arthurpaulino Jul 19, 2026
f9ad960
perf: Native Goldilocks arithmetic in the recursive verifier (execute…
arthurpaulino Jul 19, 2026
2730861
perf: Read the proof stream by index, with unconstrained byte fetches
arthurpaulino Jul 19, 2026
bad7a5d
perf: Hash Merkle 2-to-1 compressions as one direct blake3 block
arthurpaulino Jul 19, 2026
156afc9
perf: Hash MMCS leaf rows at lane granularity
arthurpaulino Jul 19, 2026
3f4c0d6
perf: Make challenger observation linear in transcript size
arthurpaulino Jul 19, 2026
9388550
perf: Segmented, hugepage-backed, hash-caching QueryMap storage
arthurpaulino Jul 19, 2026
6e02f21
perf: Prune production Aiur toplevels to their entrypoint closures
arthurpaulino Jul 19, 2026
8d4c39f
perf: Ingest the verifying key by IO slices and indexed reads
arthurpaulino Jul 19, 2026
b612f70
perf: Native extension-field representation (execute -40%, FFT -27%)
arthurpaulino Jul 19, 2026
976cb7a
perf: Hash MMCS leaves by walking the selected rows directly
arthurpaulino Jul 19, 2026
9d26eb9
feat: Sparse kernel proofs via multi-stark circuit activation
arthurpaulino Jul 20, 2026
491c65f
refactor: Inline the trivial Goldilocks constant helpers
arthurpaulino Jul 20, 2026
6fd0ab1
perf: Split-streams vk encoding (7.8x smaller, outer prove now fits)
arthurpaulino Jul 20, 2026
f63844b
chore: rustfmt + clippy fixes in vk_codec
arthurpaulino Jul 20, 2026
186654a
fix: Cover the hint-op terms in the @fn inlining passes
arthurpaulino Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Benchmarks/IxVM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,11 @@ def friParameters : Aiur.FriParameters := {
}

def main : IO Unit := do
let .ok toplevel := IxVM.ixVM
-- `ixon_serde_blake3_bench` is a bench-only entrypoint, present only in
-- the FULL toplevel (production prunes it), so this prove goes through
-- the generic interpreter — the codegen'd kernel mirrors the pruned
-- toplevel and has no code (or index) for bench entries.
let .ok toplevel := IxVM.ixVMFull
| throw (IO.userError "Merging failed")
let .ok compiled := toplevel.compile
| throw (IO.userError "Compilation failed")
Expand All@@ -34,9 +38,7 @@ def main : IO Unit := do

let _ ← bgroup "IxVM benchmarks" { oneShot := true } do
throughput (.Elements n.toUInt64 "consts")
-- IxVM-native prove: routes execution through the codegen'd Rust
-- kernel (`execute_generated`) instead of the bytecode interpreter.
bench "serde/blake3 Nat.add_comm"
(aiurSystem.proveIxVM funIdx #[.ofNat n])
(aiurSystem.prove funIdx #[.ofNat n])
ioBuffer
return
23 changes: 17 additions & 6 deletions Benchmarks/RecursiveVerifier.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,10 +136,13 @@ def main (args : List String) : IO UInt32 := do
IO.eprintln "inner proof failed to verify"
return 1
-- Proof (advice, channel 0), vk (channel 1), claims (channel 2), plus the
-- Blake3-bound vk/claims digests and FRI params as public input.
-- Blake3-bound vk/claims digests and FRI params as public input. The
-- advice buffer is built natively in Rust from the raw byte blobs
-- (`executeMultiStark` / `proveMultiStark`).
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes facSystem.vkBytes
claimBytes recCommitParams innerFri
let vkBytes := facSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
recCommitParams innerFri
-- Compile the verifier toplevel and run it over the proof.
let vTop ← match MultiStark.multiStark with
| .ok t => pure t
Expand All@@ -149,10 +152,17 @@ def main (args : List String) : IO UInt32 := do
| .error e => IO.eprintln s!"verifier compile failed: {e}"; return 1
let vIdx := vCompiled.getFuncIdx `verify_multi_stark_proof |>.get!
IO.println "executing verify_multi_stark_proof…"
-- `--use-bytecode`: route through the generic Aiur interpreter instead of
-- the codegen'd verifier (same escape hatch as `ix check --use-bytecode`;
-- useful for iterating on `Ix/MultiStark/*.lean` without regenerating
-- `crates/ixvm-codegen/src/aiur_multi_stark.rs`, and for measuring the
-- interpreter ↔ codegen gap).
let useBytecode := args.contains "--use-bytecode"
let e0 ← IO.monoNanosNow
match vCompiled.bytecode.execute vIdx pubInput io with
match vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes vkBytes
claimBytes useBytecode with
| .error e => IO.eprintln s!"verifier execution REJECTED: {e}"; return 1
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let e1 ← IO.monoNanosNow
let stats := Aiur.computeStats vCompiled qc
IO.println s!"verifier accepted, execute {secs e0 e1} s"
Expand All@@ -178,7 +188,8 @@ def main (args : List String) : IO UInt32 := do
let vSystem := AiurSystem.build vCompiled.bytecode recCommitParams innerFri
TracingTexray.resetPeakTreeRss
let t0 ← IO.monoNanosNow
let (vclaim, vproof, _) := vSystem.prove vIdx pubInput io
let (vclaim, vproof) := vSystem.proveMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
let nbytes := vproof.toBytes.size -- force the (lazy, pure) prove to run
let t1 ← IO.monoNanosNow
let outerPeak ← TracingTexray.peakTreeRssBytes
Expand Down
21 changes: 15 additions & 6 deletions Benchmarks/Typecheck.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -498,17 +498,25 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] recursively verifying {r.name} …"
(← IO.getStdout).flush
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes
aiurSystem.vkBytes claimBytes commitParams friParams
let vkBytes := aiurSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
commitParams friParams
-- Native path: the advice buffer is built in Rust from the raw
-- byte blobs and execution routes through the codegen'd verifier.
let (rvRes, rvSec) ← timed fun _ =>
vCompiled.bytecode.execute vIdx pubInput io
vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
match rvRes with
| .error e =>
IO.eprintln s!" ❌ recursive verifier REJECTED {r.name}'s proof: {e}"
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let rvStats := Aiur.computeStats vCompiled qc
IO.println s!" {r.name}: recursive={rvSec}s \
recursive-fft-cost={rvStats.totalFftCost}"
-- The per-circuit breakdown names where the verifier's cost
-- lives (deserialization vs blake3 vs FRI); texray-gated like
-- the other detailed diagnostics.
if useTexray then Aiur.printStats rvStats
let (row, _) := ordered[i]!
ordered := ordered.set! i
({ row with recursiveExecuteSec := some rvSec
Expand All@@ -520,8 +528,9 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] proving the verifier over {r.name} …"
(← IO.getStdout).flush
TracingTexray.resetPeakTreeRss
let ((rvClaim, rvProof, _), rvProveSec) ← timed fun _ =>
vSystem.prove vIdx pubInput io
let ((rvClaim, rvProof), rvProveSec) ← timed fun _ =>
vSystem.proveMultiStark vIdx pubInput proofBytes vkBytes
claimBytes
let rvPeak ← TracingTexray.peakTreeRssBytes
let rvProofBytes := Aiur.Proof.toBytes rvProof
let (rvVerifyRes, rvVerifySec) ← timed fun _ =>
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,10 +47,11 @@ dashmap = "6.1.0"
hashbrown = "0.15"
indexmap = "2"
itertools = "0.14.0"
libc = "0.2"
log = "0.4"
memmap2 = "0.9"
mimalloc = { version = "0.1", default-features = false }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "2c019220cb43ce946efec6b9caf507f5a6bccd1c" }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "6382036e970ab508e69b2db2b8ae5ad64672e34b" }
num-bigint = "0.4.6"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Compiler/Check.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,14 @@ def inferTerm (t : Term) : CheckM Typed.Term := match t with
let a' ← inferNoEscape a
let b' ← checkNoEscape b a'.typ
pure (Typed.Term.unconstrainedBigUintDivMod (.tuple #[a'.typ, a'.typ]) false a' b')
| .unconstrainedGToBytes a => do
-- The bytes are UNCONSTRAINED advice typed `u8`; the caller must
-- range-check them (see `Source.Term.unconstrainedGToBytes`).
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGToBytes (.array .u8 8) false a')
| .unconstrainedGInverse a => do
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGInverse .field false a')
| .toField a => do
let a' ← checkNoEscape a .u8
pure (Typed.Term.toField .field false a')
Expand DownExpand Up@@ -981,6 +989,10 @@ def zonkTypedTerm (t : Typed.Term) : CheckM Typed.Term := match t with
pure (.u8RangeCheck (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← zonkTyp τ) e (← zonkTypedTerm a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← zonkTyp τ) e (← zonkTypedTerm a))
| .toField τ e a => do pure (.toField (← zonkTyp τ) e (← zonkTypedTerm a))
| .u8FromFieldUnsafe τ e a => do
pure (.u8FromFieldUnsafe (← zonkTyp τ) e (← zonkTypedTerm a))
Expand Down
14 changes: 14 additions & 0 deletions Ix/Aiur/Compiler/Concretize.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -366,6 +366,10 @@ def termToConcrete
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← typToConcrete mono τ) e
(← termToConcrete mono a) (← termToConcrete mono b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← typToConcrete mono τ) e (← termToConcrete mono a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← typToConcrete mono τ) e (← termToConcrete mono a))
-- `toField` / `u8FromFieldUnsafe` are erased coercions: `u8` and `field`
-- share a representation, so we drop the wrapper and keep the inner term.
| .toField _ _ a | .u8FromFieldUnsafe _ _ a => termToConcrete mono a
Expand DownExpand Up@@ -575,6 +579,10 @@ def rewriteTypedTerm (decls : Typed.Decls)
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .toField τ e a => .toField (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe (rewriteTyp subst mono τ) e
Expand DownExpand Up@@ -653,6 +661,7 @@ def collectInTypedTerm (seen : Std.HashSet (Global × Array Typ)) :
collectInTypedTerm (collectInTypedTerm (collectInTyp seen τ) a) b
| .eqZero τ _ a | .store τ _ a | .load τ _ a | .ptrVal τ _ a | .toField τ _ a
| .u8FromFieldUnsafe τ _ a
| .unconstrainedGToBytes τ _ a | .unconstrainedGInverse τ _ a
| .u8BitDecomposition τ _ a | .u8ShiftLeft τ _ a | .u8ShiftRight τ _ a =>
collectInTypedTerm (collectInTyp seen τ) a
| .ioGetInfo τ _ c k =>
Expand DownExpand Up@@ -724,6 +733,7 @@ def collectCalls (decls : Typed.Decls)
collectCalls decls (collectCalls decls seen a) b
| .eqZero _ _ a | .store _ _ a | .load _ _ a | .ptrVal _ _ a | .toField _ _ a
| .u8FromFieldUnsafe _ _ a
| .unconstrainedGToBytes _ _ a | .unconstrainedGInverse _ _ a
| .u8BitDecomposition _ _ a | .u8ShiftLeft _ _ a | .u8ShiftRight _ _ a =>
collectCalls decls seen a
| .ioGetInfo _ _ c k =>
Expand DownExpand Up@@ -839,6 +849,10 @@ def substInTypedTerm (subst : Global → Option Typ) : Typed.Term → Typed.Term
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (Typ.instantiate subst τ) e
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedGToBytes τ e a =>
.unconstrainedGToBytes (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .unconstrainedGInverse τ e a =>
.unconstrainedGInverse (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .toField τ e a => .toField (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .u8FromFieldUnsafe τ e a =>
.u8FromFieldUnsafe (Typ.instantiate subst τ) e (substInTypedTerm subst a)
Expand Down
4 changes: 4 additions & 0 deletions Ix/Aiur/Compiler/Layout.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -207,6 +207,10 @@ def opLayout : Bytecode.Op → LayoutM Unit
-- No constraint relation, no lookup. Mirrors `IORead` (aux only) — the Rust
-- `constraints.rs` pushes exactly 2 auxiliaries and emits no relation.
| .unconstrainedBigUintDivMod .. => do pushDegrees #[1, 1]; bumpAuxiliaries 2
-- Unconstrained hints: fresh auxiliary witness columns only, no relation,
-- no lookup — same shape as `IORead`. The caller pins the values.
| .unconstrainedGToBytes _ => do pushDegrees $ .replicate 8 1; bumpAuxiliaries 8
| .unconstrainedGInverse _ => do pushDegree 1; bumpAuxiliaries
| .debug .. => pure ()

/-- Termination helper for blockLayout's Block/Ctrl traversal. -/
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Lower.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -333,6 +333,12 @@ def toIndex
let a ← expectIdx layoutMap bindings a
let b ← expectIdx layoutMap bindings b
pushOp (.unconstrainedBigUintDivMod a b) 2
| .unconstrainedGToBytes _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGToBytes a) 8
| .unconstrainedGInverse _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGInverse a)
| .debug _ _ label term ret => do
let term ← match term with
| none => pure none
Expand Down
2 changes: 2 additions & 0 deletions Ix/Aiur/Compiler/Match.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -391,6 +391,8 @@ def typedToSimple : Term → Simple.Term
| .u32LessThan τ e a b => .u32LessThan τ e (typedToSimple a) (typedToSimple b)
| .u8RangeCheck τ e a b => .u8RangeCheck τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes τ e (typedToSimple a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse τ e (typedToSimple a)
| .toField τ e a => .toField τ e (typedToSimple a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe τ e (typedToSimple a)
| .debug τ e l t r =>
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Simple.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,6 +136,12 @@ def simplifyTypedTerm (decls : Source.Decls) : Term → Except CheckError Term
let a' ← simplifyTypedTerm decls a
let b' ← simplifyTypedTerm decls b
pure (.unconstrainedBigUintDivMod τ e a' b')
| .unconstrainedGToBytes τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGToBytes τ e a')
| .unconstrainedGInverse τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGInverse τ e a')
| .toField τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.toField τ e a')
Expand Down
21 changes: 21 additions & 0 deletions Ix/Aiur/Goldilocks.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,27 @@ def G.u8BitDecomposition (a : G) : Fin 8 → G :=

def G.u32LessThan (a b : G) : G := if a.n < b.n then 1 else 0

/-- The 8 little-endian bytes of the canonical `u64` value. Semantic model of
the `unconstrained_g_to_bytes` hint. -/
def G.toLeBytes (a : G) : Fin 8 → G :=
fun i => G.ofUInt8 (a.val >>> (8 * i.val).toUInt64).toUInt8

/-- Exponentiation by squaring. Fuel-structural (64 bits covers any `n < 2⁶⁴`
exponent, in particular `p − 2`). -/
def G.pow (x : G) (n : Nat) : G := go n 64 where
go (n fuel : Nat) : G := match fuel with
| 0 => 1
| fuel + 1 =>
if n == 0 then 1
else
let h := go (n / 2) fuel
let sq := h * h
if n % 2 == 0 then sq else sq * x

/-- Fermat inverse `x^(p−2)`, with `0 ↦ 0`. Semantic model of the
`unconstrained_g_inverse` hint. -/
def G.inverse (x : G) : G := G.pow x (gSize.toNat - 2)

theorem G.one_ne_zero : ¬(1 : G) = (0 : G) := by decide

theorem G.add_comm (a b : G) : a + b = b + a := by
Expand Down
8 changes: 8 additions & 0 deletions Ix/Aiur/Interpret.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,6 +417,14 @@ partial def interp (decls : Decls) (bindings : Bindings) : Term → InterpM Valu
let _ ← interp decls bindings t1
let _ ← interp decls bindings t2
throwErr "unconstrainedBigUintDivMod: not implemented in debug interpreter"
| .unconstrainedGToBytes t => do
match ← interp decls bindings t with
| .field g => return .array (Array.ofFn fun i => .field (g.toLeBytes i))
| _ => throwErr "unconstrainedGToBytes: expected field value"
| .unconstrainedGInverse t => do
match ← interp decls bindings t with
| .field g => return .field g.inverse
| _ => throwErr "unconstrainedGInverse: expected field value"
-- `toField` / `u8FromFieldUnsafe` are erased coercions: value unchanged.
| .toField t | .u8FromFieldUnsafe t => interp decls bindings t
| .u8Lit n => return .field (G.ofNat n)
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Meta.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -205,6 +205,8 @@ syntax "u8_less_than" "(" aiur_trm ", " aiur_trm ")" : ai
syntax "u32_less_than" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "u8_range_check" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_big_uint_div_mod" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_g_to_bytes" "(" aiur_trm ")" : aiur_trm
syntax "unconstrained_g_inverse" "(" aiur_trm ")" : aiur_trm
syntax "to_field" "(" aiur_trm ")" : aiur_trm
syntax "u8_from_field_unsafe" "(" aiur_trm ")" : aiur_trm
syntax:max num "u8" : aiur_trm
Expand DownExpand Up@@ -351,6 +353,10 @@ partial def elabTrm : ElabStxCat `aiur_trm
mkAppM ``Source.Term.u8RangeCheck #[← elabTrm i, ← elabTrm j]
| `(aiur_trm| unconstrained_big_uint_div_mod($a:aiur_trm, $b:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedBigUintDivMod #[← elabTrm a, ← elabTrm b]
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGToBytes #[← elabTrm a]
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGInverse #[← elabTrm a]
| `(aiur_trm| to_field($a:aiur_trm)) => do
mkAppM ``Source.Term.toField #[← elabTrm a]
| `(aiur_trm| u8_from_field_unsafe($a:aiur_trm)) => do
Expand DownExpand Up@@ -579,6 +585,12 @@ where
let a ← replaceToken old new a
let b ← replaceToken old new b
`(aiur_trm| unconstrained_big_uint_div_mod($a, $b))
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_to_bytes($a))
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_inverse($a))
| `(aiur_trm| to_field($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| to_field($a))
Expand Down
23 changes: 23 additions & 0 deletions Ix/Aiur/Protocol.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,29 @@ def proveIxVM (system : @& AiurSystem)
let ioMap := ioMap.foldl (fun acc (k, v) => acc.insert k v) ∅
(claim, proof, ⟨ioData, ioMap⟩)

@[extern "rs_aiur_multi_stark_prove"]
private opaque proveMultiStark' : @& AiurSystem →
@& Bytecode.FunIdx → @& Array G →
(proofBytes : @& ByteArray) → (vkBytes : @& ByteArray) →
(claimBytes : @& ByteArray) → Bool →
Array G × Proof

/-- Prove the MultiStark recursive verifier over raw proof/vk/claims
byte blobs. The IO advice buffer is built natively in Rust (see
`Bytecode.Toplevel.executeMultiStark`); the execute step inside
the prove routes through the codegen'd verifier
(`crates/ixvm-codegen/src/aiur_multi_stark.rs`) unless
`useBytecode` is set. Only valid when `system` was built from the
production `MultiStark.multiStark` bytecode. Returns the claim
(`#[functionChannel, funIdx] ++ pubInput ++ output`) and the
`Proof`; the final buffer is not returned. -/
def proveMultiStark (system : @& AiurSystem)
(funIdx : @& Bytecode.FunIdx) (pubInput : @& Array G)
(proofBytes vkBytes claimBytes : @& ByteArray) (useBytecode : Bool := false) :
Array G × Proof :=
proveMultiStark' system funIdx pubInput proofBytes vkBytes claimBytes
useBytecode

@[extern "rs_aiur_system_prove_addr_with_env"]
private opaque proveAddrWithEnv' : @& AiurSystem →
@& Bytecode.FunIdx → @& EnvHandle → @& ByteArray →
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dfd4d4a
feat: Run the MultiStark verifier natively — codegen + Rust-built IO …
arthurpaulino Jul 19, 2026
f9ad960
perf: Native Goldilocks arithmetic in the recursive verifier (execute…
arthurpaulino Jul 19, 2026
2730861
perf: Read the proof stream by index, with unconstrained byte fetches
arthurpaulino Jul 19, 2026
bad7a5d
perf: Hash Merkle 2-to-1 compressions as one direct blake3 block
arthurpaulino Jul 19, 2026
156afc9
perf: Hash MMCS leaf rows at lane granularity
arthurpaulino Jul 19, 2026
3f4c0d6
perf: Make challenger observation linear in transcript size
arthurpaulino Jul 19, 2026
9388550
perf: Segmented, hugepage-backed, hash-caching QueryMap storage
arthurpaulino Jul 19, 2026
6e02f21
perf: Prune production Aiur toplevels to their entrypoint closures
arthurpaulino Jul 19, 2026
8d4c39f
perf: Ingest the verifying key by IO slices and indexed reads
arthurpaulino Jul 19, 2026
b612f70
perf: Native extension-field representation (execute -40%, FFT -27%)
arthurpaulino Jul 19, 2026
976cb7a
perf: Hash MMCS leaves by walking the selected rows directly
arthurpaulino Jul 19, 2026
9d26eb9
feat: Sparse kernel proofs via multi-stark circuit activation
arthurpaulino Jul 20, 2026
491c65f
refactor: Inline the trivial Goldilocks constant helpers
arthurpaulino Jul 20, 2026
6fd0ab1
perf: Split-streams vk encoding (7.8x smaller, outer prove now fits)
arthurpaulino Jul 20, 2026
f63844b
chore: rustfmt + clippy fixes in vk_codec
arthurpaulino Jul 20, 2026
186654a
fix: Cover the hint-op terms in the @fn inlining passes
arthurpaulino Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Benchmarks/IxVM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,11 @@ def friParameters : Aiur.FriParameters := {
}

def main : IO Unit := do
let .ok toplevel := IxVM.ixVM
-- `ixon_serde_blake3_bench` is a bench-only entrypoint, present only in
-- the FULL toplevel (production prunes it), so this prove goes through
-- the generic interpreter — the codegen'd kernel mirrors the pruned
-- toplevel and has no code (or index) for bench entries.
let .ok toplevel := IxVM.ixVMFull
| throw (IO.userError "Merging failed")
let .ok compiled := toplevel.compile
| throw (IO.userError "Compilation failed")
Expand All@@ -34,9 +38,7 @@ def main : IO Unit := do

let _ ← bgroup "IxVM benchmarks" { oneShot := true } do
throughput (.Elements n.toUInt64 "consts")
-- IxVM-native prove: routes execution through the codegen'd Rust
-- kernel (`execute_generated`) instead of the bytecode interpreter.
bench "serde/blake3 Nat.add_comm"
(aiurSystem.proveIxVM funIdx #[.ofNat n])
(aiurSystem.prove funIdx #[.ofNat n])
ioBuffer
return
23 changes: 17 additions & 6 deletions Benchmarks/RecursiveVerifier.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,10 +136,13 @@ def main (args : List String) : IO UInt32 := do
IO.eprintln "inner proof failed to verify"
return 1
-- Proof (advice, channel 0), vk (channel 1), claims (channel 2), plus the
-- Blake3-bound vk/claims digests and FRI params as public input.
-- Blake3-bound vk/claims digests and FRI params as public input. The
-- advice buffer is built natively in Rust from the raw byte blobs
-- (`executeMultiStark` / `proveMultiStark`).
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes facSystem.vkBytes
claimBytes recCommitParams innerFri
let vkBytes := facSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
recCommitParams innerFri
-- Compile the verifier toplevel and run it over the proof.
let vTop ← match MultiStark.multiStark with
| .ok t => pure t
Expand All@@ -149,10 +152,17 @@ def main (args : List String) : IO UInt32 := do
| .error e => IO.eprintln s!"verifier compile failed: {e}"; return 1
let vIdx := vCompiled.getFuncIdx `verify_multi_stark_proof |>.get!
IO.println "executing verify_multi_stark_proof…"
-- `--use-bytecode`: route through the generic Aiur interpreter instead of
-- the codegen'd verifier (same escape hatch as `ix check --use-bytecode`;
-- useful for iterating on `Ix/MultiStark/*.lean` without regenerating
-- `crates/ixvm-codegen/src/aiur_multi_stark.rs`, and for measuring the
-- interpreter ↔ codegen gap).
let useBytecode := args.contains "--use-bytecode"
let e0 ← IO.monoNanosNow
match vCompiled.bytecode.execute vIdx pubInput io with
match vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes vkBytes
claimBytes useBytecode with
| .error e => IO.eprintln s!"verifier execution REJECTED: {e}"; return 1
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let e1 ← IO.monoNanosNow
let stats := Aiur.computeStats vCompiled qc
IO.println s!"verifier accepted, execute {secs e0 e1} s"
Expand All@@ -178,7 +188,8 @@ def main (args : List String) : IO UInt32 := do
let vSystem := AiurSystem.build vCompiled.bytecode recCommitParams innerFri
TracingTexray.resetPeakTreeRss
let t0 ← IO.monoNanosNow
let (vclaim, vproof, _) := vSystem.prove vIdx pubInput io
let (vclaim, vproof) := vSystem.proveMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
let nbytes := vproof.toBytes.size -- force the (lazy, pure) prove to run
let t1 ← IO.monoNanosNow
let outerPeak ← TracingTexray.peakTreeRssBytes
Expand Down
21 changes: 15 additions & 6 deletions Benchmarks/Typecheck.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -498,17 +498,25 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] recursively verifying {r.name} …"
(← IO.getStdout).flush
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes
aiurSystem.vkBytes claimBytes commitParams friParams
let vkBytes := aiurSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
commitParams friParams
-- Native path: the advice buffer is built in Rust from the raw
-- byte blobs and execution routes through the codegen'd verifier.
let (rvRes, rvSec) ← timed fun _ =>
vCompiled.bytecode.execute vIdx pubInput io
vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
match rvRes with
| .error e =>
IO.eprintln s!" ❌ recursive verifier REJECTED {r.name}'s proof: {e}"
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let rvStats := Aiur.computeStats vCompiled qc
IO.println s!" {r.name}: recursive={rvSec}s \
recursive-fft-cost={rvStats.totalFftCost}"
-- The per-circuit breakdown names where the verifier's cost
-- lives (deserialization vs blake3 vs FRI); texray-gated like
-- the other detailed diagnostics.
if useTexray then Aiur.printStats rvStats
let (row, _) := ordered[i]!
ordered := ordered.set! i
({ row with recursiveExecuteSec := some rvSec
Expand All@@ -520,8 +528,9 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] proving the verifier over {r.name} …"
(← IO.getStdout).flush
TracingTexray.resetPeakTreeRss
let ((rvClaim, rvProof, _), rvProveSec) ← timed fun _ =>
vSystem.prove vIdx pubInput io
let ((rvClaim, rvProof), rvProveSec) ← timed fun _ =>
vSystem.proveMultiStark vIdx pubInput proofBytes vkBytes
claimBytes
let rvPeak ← TracingTexray.peakTreeRssBytes
let rvProofBytes := Aiur.Proof.toBytes rvProof
let (rvVerifyRes, rvVerifySec) ← timed fun _ =>
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,10 +47,11 @@ dashmap = "6.1.0"
hashbrown = "0.15"
indexmap = "2"
itertools = "0.14.0"
libc = "0.2"
log = "0.4"
memmap2 = "0.9"
mimalloc = { version = "0.1", default-features = false }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "2c019220cb43ce946efec6b9caf507f5a6bccd1c" }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "6382036e970ab508e69b2db2b8ae5ad64672e34b" }
num-bigint = "0.4.6"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Compiler/Check.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,14 @@ def inferTerm (t : Term) : CheckM Typed.Term := match t with
let a' ← inferNoEscape a
let b' ← checkNoEscape b a'.typ
pure (Typed.Term.unconstrainedBigUintDivMod (.tuple #[a'.typ, a'.typ]) false a' b')
| .unconstrainedGToBytes a => do
-- The bytes are UNCONSTRAINED advice typed `u8`; the caller must
-- range-check them (see `Source.Term.unconstrainedGToBytes`).
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGToBytes (.array .u8 8) false a')
| .unconstrainedGInverse a => do
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGInverse .field false a')
| .toField a => do
let a' ← checkNoEscape a .u8
pure (Typed.Term.toField .field false a')
Expand DownExpand Up@@ -981,6 +989,10 @@ def zonkTypedTerm (t : Typed.Term) : CheckM Typed.Term := match t with
pure (.u8RangeCheck (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← zonkTyp τ) e (← zonkTypedTerm a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← zonkTyp τ) e (← zonkTypedTerm a))
| .toField τ e a => do pure (.toField (← zonkTyp τ) e (← zonkTypedTerm a))
| .u8FromFieldUnsafe τ e a => do
pure (.u8FromFieldUnsafe (← zonkTyp τ) e (← zonkTypedTerm a))
Expand Down
14 changes: 14 additions & 0 deletions Ix/Aiur/Compiler/Concretize.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -366,6 +366,10 @@ def termToConcrete
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← typToConcrete mono τ) e
(← termToConcrete mono a) (← termToConcrete mono b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← typToConcrete mono τ) e (← termToConcrete mono a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← typToConcrete mono τ) e (← termToConcrete mono a))
-- `toField` / `u8FromFieldUnsafe` are erased coercions: `u8` and `field`
-- share a representation, so we drop the wrapper and keep the inner term.
| .toField _ _ a | .u8FromFieldUnsafe _ _ a => termToConcrete mono a
Expand DownExpand Up@@ -575,6 +579,10 @@ def rewriteTypedTerm (decls : Typed.Decls)
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .toField τ e a => .toField (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe (rewriteTyp subst mono τ) e
Expand DownExpand Up@@ -653,6 +661,7 @@ def collectInTypedTerm (seen : Std.HashSet (Global × Array Typ)) :
collectInTypedTerm (collectInTypedTerm (collectInTyp seen τ) a) b
| .eqZero τ _ a | .store τ _ a | .load τ _ a | .ptrVal τ _ a | .toField τ _ a
| .u8FromFieldUnsafe τ _ a
| .unconstrainedGToBytes τ _ a | .unconstrainedGInverse τ _ a
| .u8BitDecomposition τ _ a | .u8ShiftLeft τ _ a | .u8ShiftRight τ _ a =>
collectInTypedTerm (collectInTyp seen τ) a
| .ioGetInfo τ _ c k =>
Expand DownExpand Up@@ -724,6 +733,7 @@ def collectCalls (decls : Typed.Decls)
collectCalls decls (collectCalls decls seen a) b
| .eqZero _ _ a | .store _ _ a | .load _ _ a | .ptrVal _ _ a | .toField _ _ a
| .u8FromFieldUnsafe _ _ a
| .unconstrainedGToBytes _ _ a | .unconstrainedGInverse _ _ a
| .u8BitDecomposition _ _ a | .u8ShiftLeft _ _ a | .u8ShiftRight _ _ a =>
collectCalls decls seen a
| .ioGetInfo _ _ c k =>
Expand DownExpand Up@@ -839,6 +849,10 @@ def substInTypedTerm (subst : Global → Option Typ) : Typed.Term → Typed.Term
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (Typ.instantiate subst τ) e
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedGToBytes τ e a =>
.unconstrainedGToBytes (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .unconstrainedGInverse τ e a =>
.unconstrainedGInverse (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .toField τ e a => .toField (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .u8FromFieldUnsafe τ e a =>
.u8FromFieldUnsafe (Typ.instantiate subst τ) e (substInTypedTerm subst a)
Expand Down
4 changes: 4 additions & 0 deletions Ix/Aiur/Compiler/Layout.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -207,6 +207,10 @@ def opLayout : Bytecode.Op → LayoutM Unit
-- No constraint relation, no lookup. Mirrors `IORead` (aux only) — the Rust
-- `constraints.rs` pushes exactly 2 auxiliaries and emits no relation.
| .unconstrainedBigUintDivMod .. => do pushDegrees #[1, 1]; bumpAuxiliaries 2
-- Unconstrained hints: fresh auxiliary witness columns only, no relation,
-- no lookup — same shape as `IORead`. The caller pins the values.
| .unconstrainedGToBytes _ => do pushDegrees $ .replicate 8 1; bumpAuxiliaries 8
| .unconstrainedGInverse _ => do pushDegree 1; bumpAuxiliaries
| .debug .. => pure ()

/-- Termination helper for blockLayout's Block/Ctrl traversal. -/
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Lower.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -333,6 +333,12 @@ def toIndex
let a ← expectIdx layoutMap bindings a
let b ← expectIdx layoutMap bindings b
pushOp (.unconstrainedBigUintDivMod a b) 2
| .unconstrainedGToBytes _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGToBytes a) 8
| .unconstrainedGInverse _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGInverse a)
| .debug _ _ label term ret => do
let term ← match term with
| none => pure none
Expand Down
2 changes: 2 additions & 0 deletions Ix/Aiur/Compiler/Match.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -391,6 +391,8 @@ def typedToSimple : Term → Simple.Term
| .u32LessThan τ e a b => .u32LessThan τ e (typedToSimple a) (typedToSimple b)
| .u8RangeCheck τ e a b => .u8RangeCheck τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes τ e (typedToSimple a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse τ e (typedToSimple a)
| .toField τ e a => .toField τ e (typedToSimple a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe τ e (typedToSimple a)
| .debug τ e l t r =>
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Simple.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,6 +136,12 @@ def simplifyTypedTerm (decls : Source.Decls) : Term → Except CheckError Term
let a' ← simplifyTypedTerm decls a
let b' ← simplifyTypedTerm decls b
pure (.unconstrainedBigUintDivMod τ e a' b')
| .unconstrainedGToBytes τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGToBytes τ e a')
| .unconstrainedGInverse τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGInverse τ e a')
| .toField τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.toField τ e a')
Expand Down
21 changes: 21 additions & 0 deletions Ix/Aiur/Goldilocks.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,27 @@ def G.u8BitDecomposition (a : G) : Fin 8 → G :=

def G.u32LessThan (a b : G) : G := if a.n < b.n then 1 else 0

/-- The 8 little-endian bytes of the canonical `u64` value. Semantic model of
the `unconstrained_g_to_bytes` hint. -/
def G.toLeBytes (a : G) : Fin 8 → G :=
fun i => G.ofUInt8 (a.val >>> (8 * i.val).toUInt64).toUInt8

/-- Exponentiation by squaring. Fuel-structural (64 bits covers any `n < 2⁶⁴`
exponent, in particular `p − 2`). -/
def G.pow (x : G) (n : Nat) : G := go n 64 where
go (n fuel : Nat) : G := match fuel with
| 0 => 1
| fuel + 1 =>
if n == 0 then 1
else
let h := go (n / 2) fuel
let sq := h * h
if n % 2 == 0 then sq else sq * x

/-- Fermat inverse `x^(p−2)`, with `0 ↦ 0`. Semantic model of the
`unconstrained_g_inverse` hint. -/
def G.inverse (x : G) : G := G.pow x (gSize.toNat - 2)

theorem G.one_ne_zero : ¬(1 : G) = (0 : G) := by decide

theorem G.add_comm (a b : G) : a + b = b + a := by
Expand Down
8 changes: 8 additions & 0 deletions Ix/Aiur/Interpret.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,6 +417,14 @@ partial def interp (decls : Decls) (bindings : Bindings) : Term → InterpM Valu
let _ ← interp decls bindings t1
let _ ← interp decls bindings t2
throwErr "unconstrainedBigUintDivMod: not implemented in debug interpreter"
| .unconstrainedGToBytes t => do
match ← interp decls bindings t with
| .field g => return .array (Array.ofFn fun i => .field (g.toLeBytes i))
| _ => throwErr "unconstrainedGToBytes: expected field value"
| .unconstrainedGInverse t => do
match ← interp decls bindings t with
| .field g => return .field g.inverse
| _ => throwErr "unconstrainedGInverse: expected field value"
-- `toField` / `u8FromFieldUnsafe` are erased coercions: value unchanged.
| .toField t | .u8FromFieldUnsafe t => interp decls bindings t
| .u8Lit n => return .field (G.ofNat n)
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Meta.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -205,6 +205,8 @@ syntax "u8_less_than" "(" aiur_trm ", " aiur_trm ")" : ai
syntax "u32_less_than" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "u8_range_check" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_big_uint_div_mod" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_g_to_bytes" "(" aiur_trm ")" : aiur_trm
syntax "unconstrained_g_inverse" "(" aiur_trm ")" : aiur_trm
syntax "to_field" "(" aiur_trm ")" : aiur_trm
syntax "u8_from_field_unsafe" "(" aiur_trm ")" : aiur_trm
syntax:max num "u8" : aiur_trm
Expand DownExpand Up@@ -351,6 +353,10 @@ partial def elabTrm : ElabStxCat `aiur_trm
mkAppM ``Source.Term.u8RangeCheck #[← elabTrm i, ← elabTrm j]
| `(aiur_trm| unconstrained_big_uint_div_mod($a:aiur_trm, $b:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedBigUintDivMod #[← elabTrm a, ← elabTrm b]
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGToBytes #[← elabTrm a]
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGInverse #[← elabTrm a]
| `(aiur_trm| to_field($a:aiur_trm)) => do
mkAppM ``Source.Term.toField #[← elabTrm a]
| `(aiur_trm| u8_from_field_unsafe($a:aiur_trm)) => do
Expand DownExpand Up@@ -579,6 +585,12 @@ where
let a ← replaceToken old new a
let b ← replaceToken old new b
`(aiur_trm| unconstrained_big_uint_div_mod($a, $b))
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_to_bytes($a))
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_inverse($a))
| `(aiur_trm| to_field($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| to_field($a))
Expand Down
23 changes: 23 additions & 0 deletions Ix/Aiur/Protocol.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,29 @@ def proveIxVM (system : @& AiurSystem)
let ioMap := ioMap.foldl (fun acc (k, v) => acc.insert k v) ∅
(claim, proof, ⟨ioData, ioMap⟩)

@[extern "rs_aiur_multi_stark_prove"]
private opaque proveMultiStark' : @& AiurSystem →
@& Bytecode.FunIdx → @& Array G →
(proofBytes : @& ByteArray) → (vkBytes : @& ByteArray) →
(claimBytes : @& ByteArray) → Bool →
Array G × Proof

/-- Prove the MultiStark recursive verifier over raw proof/vk/claims
byte blobs. The IO advice buffer is built natively in Rust (see
`Bytecode.Toplevel.executeMultiStark`); the execute step inside
the prove routes through the codegen'd verifier
(`crates/ixvm-codegen/src/aiur_multi_stark.rs`) unless
`useBytecode` is set. Only valid when `system` was built from the
production `MultiStark.multiStark` bytecode. Returns the claim
(`#[functionChannel, funIdx] ++ pubInput ++ output`) and the
`Proof`; the final buffer is not returned. -/
def proveMultiStark (system : @& AiurSystem)
(funIdx : @& Bytecode.FunIdx) (pubInput : @& Array G)
(proofBytes vkBytes claimBytes : @& ByteArray) (useBytecode : Bool := false) :
Array G × Proof :=
proveMultiStark' system funIdx pubInput proofBytes vkBytes claimBytes
useBytecode

@[extern "rs_aiur_system_prove_addr_with_env"]
private opaque proveAddrWithEnv' : @& AiurSystem →
@& Bytecode.FunIdx → @& EnvHandle → @& ByteArray →
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dfd4d4a
feat: Run the MultiStark verifier natively — codegen + Rust-built IO …
arthurpaulino Jul 19, 2026
f9ad960
perf: Native Goldilocks arithmetic in the recursive verifier (execute…
arthurpaulino Jul 19, 2026
2730861
perf: Read the proof stream by index, with unconstrained byte fetches
arthurpaulino Jul 19, 2026
bad7a5d
perf: Hash Merkle 2-to-1 compressions as one direct blake3 block
arthurpaulino Jul 19, 2026
156afc9
perf: Hash MMCS leaf rows at lane granularity
arthurpaulino Jul 19, 2026
3f4c0d6
perf: Make challenger observation linear in transcript size
arthurpaulino Jul 19, 2026
9388550
perf: Segmented, hugepage-backed, hash-caching QueryMap storage
arthurpaulino Jul 19, 2026
6e02f21
perf: Prune production Aiur toplevels to their entrypoint closures
arthurpaulino Jul 19, 2026
8d4c39f
perf: Ingest the verifying key by IO slices and indexed reads
arthurpaulino Jul 19, 2026
b612f70
perf: Native extension-field representation (execute -40%, FFT -27%)
arthurpaulino Jul 19, 2026
976cb7a
perf: Hash MMCS leaves by walking the selected rows directly
arthurpaulino Jul 19, 2026
9d26eb9
feat: Sparse kernel proofs via multi-stark circuit activation
arthurpaulino Jul 20, 2026
491c65f
refactor: Inline the trivial Goldilocks constant helpers
arthurpaulino Jul 20, 2026
6fd0ab1
perf: Split-streams vk encoding (7.8x smaller, outer prove now fits)
arthurpaulino Jul 20, 2026
f63844b
chore: rustfmt + clippy fixes in vk_codec
arthurpaulino Jul 20, 2026
186654a
fix: Cover the hint-op terms in the @fn inlining passes
arthurpaulino Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Benchmarks/IxVM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,11 @@ def friParameters : Aiur.FriParameters := {
}

def main : IO Unit := do
let .ok toplevel := IxVM.ixVM
-- `ixon_serde_blake3_bench` is a bench-only entrypoint, present only in
-- the FULL toplevel (production prunes it), so this prove goes through
-- the generic interpreter — the codegen'd kernel mirrors the pruned
-- toplevel and has no code (or index) for bench entries.
let .ok toplevel := IxVM.ixVMFull
| throw (IO.userError "Merging failed")
let .ok compiled := toplevel.compile
| throw (IO.userError "Compilation failed")
Expand All@@ -34,9 +38,7 @@ def main : IO Unit := do

let _ ← bgroup "IxVM benchmarks" { oneShot := true } do
throughput (.Elements n.toUInt64 "consts")
-- IxVM-native prove: routes execution through the codegen'd Rust
-- kernel (`execute_generated`) instead of the bytecode interpreter.
bench "serde/blake3 Nat.add_comm"
(aiurSystem.proveIxVM funIdx #[.ofNat n])
(aiurSystem.prove funIdx #[.ofNat n])
ioBuffer
return
23 changes: 17 additions & 6 deletions Benchmarks/RecursiveVerifier.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,10 +136,13 @@ def main (args : List String) : IO UInt32 := do
IO.eprintln "inner proof failed to verify"
return 1
-- Proof (advice, channel 0), vk (channel 1), claims (channel 2), plus the
-- Blake3-bound vk/claims digests and FRI params as public input.
-- Blake3-bound vk/claims digests and FRI params as public input. The
-- advice buffer is built natively in Rust from the raw byte blobs
-- (`executeMultiStark` / `proveMultiStark`).
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes facSystem.vkBytes
claimBytes recCommitParams innerFri
let vkBytes := facSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
recCommitParams innerFri
-- Compile the verifier toplevel and run it over the proof.
let vTop ← match MultiStark.multiStark with
| .ok t => pure t
Expand All@@ -149,10 +152,17 @@ def main (args : List String) : IO UInt32 := do
| .error e => IO.eprintln s!"verifier compile failed: {e}"; return 1
let vIdx := vCompiled.getFuncIdx `verify_multi_stark_proof |>.get!
IO.println "executing verify_multi_stark_proof…"
-- `--use-bytecode`: route through the generic Aiur interpreter instead of
-- the codegen'd verifier (same escape hatch as `ix check --use-bytecode`;
-- useful for iterating on `Ix/MultiStark/*.lean` without regenerating
-- `crates/ixvm-codegen/src/aiur_multi_stark.rs`, and for measuring the
-- interpreter ↔ codegen gap).
let useBytecode := args.contains "--use-bytecode"
let e0 ← IO.monoNanosNow
match vCompiled.bytecode.execute vIdx pubInput io with
match vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes vkBytes
claimBytes useBytecode with
| .error e => IO.eprintln s!"verifier execution REJECTED: {e}"; return 1
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let e1 ← IO.monoNanosNow
let stats := Aiur.computeStats vCompiled qc
IO.println s!"verifier accepted, execute {secs e0 e1} s"
Expand All@@ -178,7 +188,8 @@ def main (args : List String) : IO UInt32 := do
let vSystem := AiurSystem.build vCompiled.bytecode recCommitParams innerFri
TracingTexray.resetPeakTreeRss
let t0 ← IO.monoNanosNow
let (vclaim, vproof, _) := vSystem.prove vIdx pubInput io
let (vclaim, vproof) := vSystem.proveMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
let nbytes := vproof.toBytes.size -- force the (lazy, pure) prove to run
let t1 ← IO.monoNanosNow
let outerPeak ← TracingTexray.peakTreeRssBytes
Expand Down
21 changes: 15 additions & 6 deletions Benchmarks/Typecheck.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -498,17 +498,25 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] recursively verifying {r.name} …"
(← IO.getStdout).flush
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes
aiurSystem.vkBytes claimBytes commitParams friParams
let vkBytes := aiurSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
commitParams friParams
-- Native path: the advice buffer is built in Rust from the raw
-- byte blobs and execution routes through the codegen'd verifier.
let (rvRes, rvSec) ← timed fun _ =>
vCompiled.bytecode.execute vIdx pubInput io
vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
match rvRes with
| .error e =>
IO.eprintln s!" ❌ recursive verifier REJECTED {r.name}'s proof: {e}"
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let rvStats := Aiur.computeStats vCompiled qc
IO.println s!" {r.name}: recursive={rvSec}s \
recursive-fft-cost={rvStats.totalFftCost}"
-- The per-circuit breakdown names where the verifier's cost
-- lives (deserialization vs blake3 vs FRI); texray-gated like
-- the other detailed diagnostics.
if useTexray then Aiur.printStats rvStats
let (row, _) := ordered[i]!
ordered := ordered.set! i
({ row with recursiveExecuteSec := some rvSec
Expand All@@ -520,8 +528,9 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] proving the verifier over {r.name} …"
(← IO.getStdout).flush
TracingTexray.resetPeakTreeRss
let ((rvClaim, rvProof, _), rvProveSec) ← timed fun _ =>
vSystem.prove vIdx pubInput io
let ((rvClaim, rvProof), rvProveSec) ← timed fun _ =>
vSystem.proveMultiStark vIdx pubInput proofBytes vkBytes
claimBytes
let rvPeak ← TracingTexray.peakTreeRssBytes
let rvProofBytes := Aiur.Proof.toBytes rvProof
let (rvVerifyRes, rvVerifySec) ← timed fun _ =>
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,10 +47,11 @@ dashmap = "6.1.0"
hashbrown = "0.15"
indexmap = "2"
itertools = "0.14.0"
libc = "0.2"
log = "0.4"
memmap2 = "0.9"
mimalloc = { version = "0.1", default-features = false }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "2c019220cb43ce946efec6b9caf507f5a6bccd1c" }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "6382036e970ab508e69b2db2b8ae5ad64672e34b" }
num-bigint = "0.4.6"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Compiler/Check.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,14 @@ def inferTerm (t : Term) : CheckM Typed.Term := match t with
let a' ← inferNoEscape a
let b' ← checkNoEscape b a'.typ
pure (Typed.Term.unconstrainedBigUintDivMod (.tuple #[a'.typ, a'.typ]) false a' b')
| .unconstrainedGToBytes a => do
-- The bytes are UNCONSTRAINED advice typed `u8`; the caller must
-- range-check them (see `Source.Term.unconstrainedGToBytes`).
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGToBytes (.array .u8 8) false a')
| .unconstrainedGInverse a => do
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGInverse .field false a')
| .toField a => do
let a' ← checkNoEscape a .u8
pure (Typed.Term.toField .field false a')
Expand DownExpand Up@@ -981,6 +989,10 @@ def zonkTypedTerm (t : Typed.Term) : CheckM Typed.Term := match t with
pure (.u8RangeCheck (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← zonkTyp τ) e (← zonkTypedTerm a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← zonkTyp τ) e (← zonkTypedTerm a))
| .toField τ e a => do pure (.toField (← zonkTyp τ) e (← zonkTypedTerm a))
| .u8FromFieldUnsafe τ e a => do
pure (.u8FromFieldUnsafe (← zonkTyp τ) e (← zonkTypedTerm a))
Expand Down
14 changes: 14 additions & 0 deletions Ix/Aiur/Compiler/Concretize.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -366,6 +366,10 @@ def termToConcrete
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← typToConcrete mono τ) e
(← termToConcrete mono a) (← termToConcrete mono b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← typToConcrete mono τ) e (← termToConcrete mono a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← typToConcrete mono τ) e (← termToConcrete mono a))
-- `toField` / `u8FromFieldUnsafe` are erased coercions: `u8` and `field`
-- share a representation, so we drop the wrapper and keep the inner term.
| .toField _ _ a | .u8FromFieldUnsafe _ _ a => termToConcrete mono a
Expand DownExpand Up@@ -575,6 +579,10 @@ def rewriteTypedTerm (decls : Typed.Decls)
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .toField τ e a => .toField (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe (rewriteTyp subst mono τ) e
Expand DownExpand Up@@ -653,6 +661,7 @@ def collectInTypedTerm (seen : Std.HashSet (Global × Array Typ)) :
collectInTypedTerm (collectInTypedTerm (collectInTyp seen τ) a) b
| .eqZero τ _ a | .store τ _ a | .load τ _ a | .ptrVal τ _ a | .toField τ _ a
| .u8FromFieldUnsafe τ _ a
| .unconstrainedGToBytes τ _ a | .unconstrainedGInverse τ _ a
| .u8BitDecomposition τ _ a | .u8ShiftLeft τ _ a | .u8ShiftRight τ _ a =>
collectInTypedTerm (collectInTyp seen τ) a
| .ioGetInfo τ _ c k =>
Expand DownExpand Up@@ -724,6 +733,7 @@ def collectCalls (decls : Typed.Decls)
collectCalls decls (collectCalls decls seen a) b
| .eqZero _ _ a | .store _ _ a | .load _ _ a | .ptrVal _ _ a | .toField _ _ a
| .u8FromFieldUnsafe _ _ a
| .unconstrainedGToBytes _ _ a | .unconstrainedGInverse _ _ a
| .u8BitDecomposition _ _ a | .u8ShiftLeft _ _ a | .u8ShiftRight _ _ a =>
collectCalls decls seen a
| .ioGetInfo _ _ c k =>
Expand DownExpand Up@@ -839,6 +849,10 @@ def substInTypedTerm (subst : Global → Option Typ) : Typed.Term → Typed.Term
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (Typ.instantiate subst τ) e
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedGToBytes τ e a =>
.unconstrainedGToBytes (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .unconstrainedGInverse τ e a =>
.unconstrainedGInverse (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .toField τ e a => .toField (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .u8FromFieldUnsafe τ e a =>
.u8FromFieldUnsafe (Typ.instantiate subst τ) e (substInTypedTerm subst a)
Expand Down
4 changes: 4 additions & 0 deletions Ix/Aiur/Compiler/Layout.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -207,6 +207,10 @@ def opLayout : Bytecode.Op → LayoutM Unit
-- No constraint relation, no lookup. Mirrors `IORead` (aux only) — the Rust
-- `constraints.rs` pushes exactly 2 auxiliaries and emits no relation.
| .unconstrainedBigUintDivMod .. => do pushDegrees #[1, 1]; bumpAuxiliaries 2
-- Unconstrained hints: fresh auxiliary witness columns only, no relation,
-- no lookup — same shape as `IORead`. The caller pins the values.
| .unconstrainedGToBytes _ => do pushDegrees $ .replicate 8 1; bumpAuxiliaries 8
| .unconstrainedGInverse _ => do pushDegree 1; bumpAuxiliaries
| .debug .. => pure ()

/-- Termination helper for blockLayout's Block/Ctrl traversal. -/
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Lower.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -333,6 +333,12 @@ def toIndex
let a ← expectIdx layoutMap bindings a
let b ← expectIdx layoutMap bindings b
pushOp (.unconstrainedBigUintDivMod a b) 2
| .unconstrainedGToBytes _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGToBytes a) 8
| .unconstrainedGInverse _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGInverse a)
| .debug _ _ label term ret => do
let term ← match term with
| none => pure none
Expand Down
2 changes: 2 additions & 0 deletions Ix/Aiur/Compiler/Match.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -391,6 +391,8 @@ def typedToSimple : Term → Simple.Term
| .u32LessThan τ e a b => .u32LessThan τ e (typedToSimple a) (typedToSimple b)
| .u8RangeCheck τ e a b => .u8RangeCheck τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes τ e (typedToSimple a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse τ e (typedToSimple a)
| .toField τ e a => .toField τ e (typedToSimple a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe τ e (typedToSimple a)
| .debug τ e l t r =>
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Simple.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,6 +136,12 @@ def simplifyTypedTerm (decls : Source.Decls) : Term → Except CheckError Term
let a' ← simplifyTypedTerm decls a
let b' ← simplifyTypedTerm decls b
pure (.unconstrainedBigUintDivMod τ e a' b')
| .unconstrainedGToBytes τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGToBytes τ e a')
| .unconstrainedGInverse τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGInverse τ e a')
| .toField τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.toField τ e a')
Expand Down
21 changes: 21 additions & 0 deletions Ix/Aiur/Goldilocks.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,27 @@ def G.u8BitDecomposition (a : G) : Fin 8 → G :=

def G.u32LessThan (a b : G) : G := if a.n < b.n then 1 else 0

/-- The 8 little-endian bytes of the canonical `u64` value. Semantic model of
the `unconstrained_g_to_bytes` hint. -/
def G.toLeBytes (a : G) : Fin 8 → G :=
fun i => G.ofUInt8 (a.val >>> (8 * i.val).toUInt64).toUInt8

/-- Exponentiation by squaring. Fuel-structural (64 bits covers any `n < 2⁶⁴`
exponent, in particular `p − 2`). -/
def G.pow (x : G) (n : Nat) : G := go n 64 where
go (n fuel : Nat) : G := match fuel with
| 0 => 1
| fuel + 1 =>
if n == 0 then 1
else
let h := go (n / 2) fuel
let sq := h * h
if n % 2 == 0 then sq else sq * x

/-- Fermat inverse `x^(p−2)`, with `0 ↦ 0`. Semantic model of the
`unconstrained_g_inverse` hint. -/
def G.inverse (x : G) : G := G.pow x (gSize.toNat - 2)

theorem G.one_ne_zero : ¬(1 : G) = (0 : G) := by decide

theorem G.add_comm (a b : G) : a + b = b + a := by
Expand Down
8 changes: 8 additions & 0 deletions Ix/Aiur/Interpret.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,6 +417,14 @@ partial def interp (decls : Decls) (bindings : Bindings) : Term → InterpM Valu
let _ ← interp decls bindings t1
let _ ← interp decls bindings t2
throwErr "unconstrainedBigUintDivMod: not implemented in debug interpreter"
| .unconstrainedGToBytes t => do
match ← interp decls bindings t with
| .field g => return .array (Array.ofFn fun i => .field (g.toLeBytes i))
| _ => throwErr "unconstrainedGToBytes: expected field value"
| .unconstrainedGInverse t => do
match ← interp decls bindings t with
| .field g => return .field g.inverse
| _ => throwErr "unconstrainedGInverse: expected field value"
-- `toField` / `u8FromFieldUnsafe` are erased coercions: value unchanged.
| .toField t | .u8FromFieldUnsafe t => interp decls bindings t
| .u8Lit n => return .field (G.ofNat n)
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Meta.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -205,6 +205,8 @@ syntax "u8_less_than" "(" aiur_trm ", " aiur_trm ")" : ai
syntax "u32_less_than" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "u8_range_check" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_big_uint_div_mod" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_g_to_bytes" "(" aiur_trm ")" : aiur_trm
syntax "unconstrained_g_inverse" "(" aiur_trm ")" : aiur_trm
syntax "to_field" "(" aiur_trm ")" : aiur_trm
syntax "u8_from_field_unsafe" "(" aiur_trm ")" : aiur_trm
syntax:max num "u8" : aiur_trm
Expand DownExpand Up@@ -351,6 +353,10 @@ partial def elabTrm : ElabStxCat `aiur_trm
mkAppM ``Source.Term.u8RangeCheck #[← elabTrm i, ← elabTrm j]
| `(aiur_trm| unconstrained_big_uint_div_mod($a:aiur_trm, $b:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedBigUintDivMod #[← elabTrm a, ← elabTrm b]
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGToBytes #[← elabTrm a]
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGInverse #[← elabTrm a]
| `(aiur_trm| to_field($a:aiur_trm)) => do
mkAppM ``Source.Term.toField #[← elabTrm a]
| `(aiur_trm| u8_from_field_unsafe($a:aiur_trm)) => do
Expand DownExpand Up@@ -579,6 +585,12 @@ where
let a ← replaceToken old new a
let b ← replaceToken old new b
`(aiur_trm| unconstrained_big_uint_div_mod($a, $b))
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_to_bytes($a))
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_inverse($a))
| `(aiur_trm| to_field($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| to_field($a))
Expand Down
23 changes: 23 additions & 0 deletions Ix/Aiur/Protocol.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,29 @@ def proveIxVM (system : @& AiurSystem)
let ioMap := ioMap.foldl (fun acc (k, v) => acc.insert k v) ∅
(claim, proof, ⟨ioData, ioMap⟩)

@[extern "rs_aiur_multi_stark_prove"]
private opaque proveMultiStark' : @& AiurSystem →
@& Bytecode.FunIdx → @& Array G →
(proofBytes : @& ByteArray) → (vkBytes : @& ByteArray) →
(claimBytes : @& ByteArray) → Bool →
Array G × Proof

/-- Prove the MultiStark recursive verifier over raw proof/vk/claims
byte blobs. The IO advice buffer is built natively in Rust (see
`Bytecode.Toplevel.executeMultiStark`); the execute step inside
the prove routes through the codegen'd verifier
(`crates/ixvm-codegen/src/aiur_multi_stark.rs`) unless
`useBytecode` is set. Only valid when `system` was built from the
production `MultiStark.multiStark` bytecode. Returns the claim
(`#[functionChannel, funIdx] ++ pubInput ++ output`) and the
`Proof`; the final buffer is not returned. -/
def proveMultiStark (system : @& AiurSystem)
(funIdx : @& Bytecode.FunIdx) (pubInput : @& Array G)
(proofBytes vkBytes claimBytes : @& ByteArray) (useBytecode : Bool := false) :
Array G × Proof :=
proveMultiStark' system funIdx pubInput proofBytes vkBytes claimBytes
useBytecode

@[extern "rs_aiur_system_prove_addr_with_env"]
private opaque proveAddrWithEnv' : @& AiurSystem →
@& Bytecode.FunIdx → @& EnvHandle → @& ByteArray →
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dfd4d4a
feat: Run the MultiStark verifier natively — codegen + Rust-built IO …
arthurpaulino Jul 19, 2026
f9ad960
perf: Native Goldilocks arithmetic in the recursive verifier (execute…
arthurpaulino Jul 19, 2026
2730861
perf: Read the proof stream by index, with unconstrained byte fetches
arthurpaulino Jul 19, 2026
bad7a5d
perf: Hash Merkle 2-to-1 compressions as one direct blake3 block
arthurpaulino Jul 19, 2026
156afc9
perf: Hash MMCS leaf rows at lane granularity
arthurpaulino Jul 19, 2026
3f4c0d6
perf: Make challenger observation linear in transcript size
arthurpaulino Jul 19, 2026
9388550
perf: Segmented, hugepage-backed, hash-caching QueryMap storage
arthurpaulino Jul 19, 2026
6e02f21
perf: Prune production Aiur toplevels to their entrypoint closures
arthurpaulino Jul 19, 2026
8d4c39f
perf: Ingest the verifying key by IO slices and indexed reads
arthurpaulino Jul 19, 2026
b612f70
perf: Native extension-field representation (execute -40%, FFT -27%)
arthurpaulino Jul 19, 2026
976cb7a
perf: Hash MMCS leaves by walking the selected rows directly
arthurpaulino Jul 19, 2026
9d26eb9
feat: Sparse kernel proofs via multi-stark circuit activation
arthurpaulino Jul 20, 2026
491c65f
refactor: Inline the trivial Goldilocks constant helpers
arthurpaulino Jul 20, 2026
6fd0ab1
perf: Split-streams vk encoding (7.8x smaller, outer prove now fits)
arthurpaulino Jul 20, 2026
f63844b
chore: rustfmt + clippy fixes in vk_codec
arthurpaulino Jul 20, 2026
186654a
fix: Cover the hint-op terms in the @fn inlining passes
arthurpaulino Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Benchmarks/IxVM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,11 @@ def friParameters : Aiur.FriParameters := {
}

def main : IO Unit := do
let .ok toplevel := IxVM.ixVM
-- `ixon_serde_blake3_bench` is a bench-only entrypoint, present only in
-- the FULL toplevel (production prunes it), so this prove goes through
-- the generic interpreter — the codegen'd kernel mirrors the pruned
-- toplevel and has no code (or index) for bench entries.
let .ok toplevel := IxVM.ixVMFull
| throw (IO.userError "Merging failed")
let .ok compiled := toplevel.compile
| throw (IO.userError "Compilation failed")
Expand All@@ -34,9 +38,7 @@ def main : IO Unit := do

let _ ← bgroup "IxVM benchmarks" { oneShot := true } do
throughput (.Elements n.toUInt64 "consts")
-- IxVM-native prove: routes execution through the codegen'd Rust
-- kernel (`execute_generated`) instead of the bytecode interpreter.
bench "serde/blake3 Nat.add_comm"
(aiurSystem.proveIxVM funIdx #[.ofNat n])
(aiurSystem.prove funIdx #[.ofNat n])
ioBuffer
return
23 changes: 17 additions & 6 deletions Benchmarks/RecursiveVerifier.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,10 +136,13 @@ def main (args : List String) : IO UInt32 := do
IO.eprintln "inner proof failed to verify"
return 1
-- Proof (advice, channel 0), vk (channel 1), claims (channel 2), plus the
-- Blake3-bound vk/claims digests and FRI params as public input.
-- Blake3-bound vk/claims digests and FRI params as public input. The
-- advice buffer is built natively in Rust from the raw byte blobs
-- (`executeMultiStark` / `proveMultiStark`).
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes facSystem.vkBytes
claimBytes recCommitParams innerFri
let vkBytes := facSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
recCommitParams innerFri
-- Compile the verifier toplevel and run it over the proof.
let vTop ← match MultiStark.multiStark with
| .ok t => pure t
Expand All@@ -149,10 +152,17 @@ def main (args : List String) : IO UInt32 := do
| .error e => IO.eprintln s!"verifier compile failed: {e}"; return 1
let vIdx := vCompiled.getFuncIdx `verify_multi_stark_proof |>.get!
IO.println "executing verify_multi_stark_proof…"
-- `--use-bytecode`: route through the generic Aiur interpreter instead of
-- the codegen'd verifier (same escape hatch as `ix check --use-bytecode`;
-- useful for iterating on `Ix/MultiStark/*.lean` without regenerating
-- `crates/ixvm-codegen/src/aiur_multi_stark.rs`, and for measuring the
-- interpreter ↔ codegen gap).
let useBytecode := args.contains "--use-bytecode"
let e0 ← IO.monoNanosNow
match vCompiled.bytecode.execute vIdx pubInput io with
match vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes vkBytes
claimBytes useBytecode with
| .error e => IO.eprintln s!"verifier execution REJECTED: {e}"; return 1
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let e1 ← IO.monoNanosNow
let stats := Aiur.computeStats vCompiled qc
IO.println s!"verifier accepted, execute {secs e0 e1} s"
Expand All@@ -178,7 +188,8 @@ def main (args : List String) : IO UInt32 := do
let vSystem := AiurSystem.build vCompiled.bytecode recCommitParams innerFri
TracingTexray.resetPeakTreeRss
let t0 ← IO.monoNanosNow
let (vclaim, vproof, _) := vSystem.prove vIdx pubInput io
let (vclaim, vproof) := vSystem.proveMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
let nbytes := vproof.toBytes.size -- force the (lazy, pure) prove to run
let t1 ← IO.monoNanosNow
let outerPeak ← TracingTexray.peakTreeRssBytes
Expand Down
21 changes: 15 additions & 6 deletions Benchmarks/Typecheck.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -498,17 +498,25 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] recursively verifying {r.name} …"
(← IO.getStdout).flush
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes
aiurSystem.vkBytes claimBytes commitParams friParams
let vkBytes := aiurSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
commitParams friParams
-- Native path: the advice buffer is built in Rust from the raw
-- byte blobs and execution routes through the codegen'd verifier.
let (rvRes, rvSec) ← timed fun _ =>
vCompiled.bytecode.execute vIdx pubInput io
vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
match rvRes with
| .error e =>
IO.eprintln s!" ❌ recursive verifier REJECTED {r.name}'s proof: {e}"
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let rvStats := Aiur.computeStats vCompiled qc
IO.println s!" {r.name}: recursive={rvSec}s \
recursive-fft-cost={rvStats.totalFftCost}"
-- The per-circuit breakdown names where the verifier's cost
-- lives (deserialization vs blake3 vs FRI); texray-gated like
-- the other detailed diagnostics.
if useTexray then Aiur.printStats rvStats
let (row, _) := ordered[i]!
ordered := ordered.set! i
({ row with recursiveExecuteSec := some rvSec
Expand All@@ -520,8 +528,9 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] proving the verifier over {r.name} …"
(← IO.getStdout).flush
TracingTexray.resetPeakTreeRss
let ((rvClaim, rvProof, _), rvProveSec) ← timed fun _ =>
vSystem.prove vIdx pubInput io
let ((rvClaim, rvProof), rvProveSec) ← timed fun _ =>
vSystem.proveMultiStark vIdx pubInput proofBytes vkBytes
claimBytes
let rvPeak ← TracingTexray.peakTreeRssBytes
let rvProofBytes := Aiur.Proof.toBytes rvProof
let (rvVerifyRes, rvVerifySec) ← timed fun _ =>
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,10 +47,11 @@ dashmap = "6.1.0"
hashbrown = "0.15"
indexmap = "2"
itertools = "0.14.0"
libc = "0.2"
log = "0.4"
memmap2 = "0.9"
mimalloc = { version = "0.1", default-features = false }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "2c019220cb43ce946efec6b9caf507f5a6bccd1c" }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "6382036e970ab508e69b2db2b8ae5ad64672e34b" }
num-bigint = "0.4.6"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Compiler/Check.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,14 @@ def inferTerm (t : Term) : CheckM Typed.Term := match t with
let a' ← inferNoEscape a
let b' ← checkNoEscape b a'.typ
pure (Typed.Term.unconstrainedBigUintDivMod (.tuple #[a'.typ, a'.typ]) false a' b')
| .unconstrainedGToBytes a => do
-- The bytes are UNCONSTRAINED advice typed `u8`; the caller must
-- range-check them (see `Source.Term.unconstrainedGToBytes`).
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGToBytes (.array .u8 8) false a')
| .unconstrainedGInverse a => do
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGInverse .field false a')
| .toField a => do
let a' ← checkNoEscape a .u8
pure (Typed.Term.toField .field false a')
Expand DownExpand Up@@ -981,6 +989,10 @@ def zonkTypedTerm (t : Typed.Term) : CheckM Typed.Term := match t with
pure (.u8RangeCheck (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← zonkTyp τ) e (← zonkTypedTerm a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← zonkTyp τ) e (← zonkTypedTerm a))
| .toField τ e a => do pure (.toField (← zonkTyp τ) e (← zonkTypedTerm a))
| .u8FromFieldUnsafe τ e a => do
pure (.u8FromFieldUnsafe (← zonkTyp τ) e (← zonkTypedTerm a))
Expand Down
14 changes: 14 additions & 0 deletions Ix/Aiur/Compiler/Concretize.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -366,6 +366,10 @@ def termToConcrete
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← typToConcrete mono τ) e
(← termToConcrete mono a) (← termToConcrete mono b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← typToConcrete mono τ) e (← termToConcrete mono a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← typToConcrete mono τ) e (← termToConcrete mono a))
-- `toField` / `u8FromFieldUnsafe` are erased coercions: `u8` and `field`
-- share a representation, so we drop the wrapper and keep the inner term.
| .toField _ _ a | .u8FromFieldUnsafe _ _ a => termToConcrete mono a
Expand DownExpand Up@@ -575,6 +579,10 @@ def rewriteTypedTerm (decls : Typed.Decls)
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .toField τ e a => .toField (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe (rewriteTyp subst mono τ) e
Expand DownExpand Up@@ -653,6 +661,7 @@ def collectInTypedTerm (seen : Std.HashSet (Global × Array Typ)) :
collectInTypedTerm (collectInTypedTerm (collectInTyp seen τ) a) b
| .eqZero τ _ a | .store τ _ a | .load τ _ a | .ptrVal τ _ a | .toField τ _ a
| .u8FromFieldUnsafe τ _ a
| .unconstrainedGToBytes τ _ a | .unconstrainedGInverse τ _ a
| .u8BitDecomposition τ _ a | .u8ShiftLeft τ _ a | .u8ShiftRight τ _ a =>
collectInTypedTerm (collectInTyp seen τ) a
| .ioGetInfo τ _ c k =>
Expand DownExpand Up@@ -724,6 +733,7 @@ def collectCalls (decls : Typed.Decls)
collectCalls decls (collectCalls decls seen a) b
| .eqZero _ _ a | .store _ _ a | .load _ _ a | .ptrVal _ _ a | .toField _ _ a
| .u8FromFieldUnsafe _ _ a
| .unconstrainedGToBytes _ _ a | .unconstrainedGInverse _ _ a
| .u8BitDecomposition _ _ a | .u8ShiftLeft _ _ a | .u8ShiftRight _ _ a =>
collectCalls decls seen a
| .ioGetInfo _ _ c k =>
Expand DownExpand Up@@ -839,6 +849,10 @@ def substInTypedTerm (subst : Global → Option Typ) : Typed.Term → Typed.Term
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (Typ.instantiate subst τ) e
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedGToBytes τ e a =>
.unconstrainedGToBytes (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .unconstrainedGInverse τ e a =>
.unconstrainedGInverse (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .toField τ e a => .toField (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .u8FromFieldUnsafe τ e a =>
.u8FromFieldUnsafe (Typ.instantiate subst τ) e (substInTypedTerm subst a)
Expand Down
4 changes: 4 additions & 0 deletions Ix/Aiur/Compiler/Layout.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -207,6 +207,10 @@ def opLayout : Bytecode.Op → LayoutM Unit
-- No constraint relation, no lookup. Mirrors `IORead` (aux only) — the Rust
-- `constraints.rs` pushes exactly 2 auxiliaries and emits no relation.
| .unconstrainedBigUintDivMod .. => do pushDegrees #[1, 1]; bumpAuxiliaries 2
-- Unconstrained hints: fresh auxiliary witness columns only, no relation,
-- no lookup — same shape as `IORead`. The caller pins the values.
| .unconstrainedGToBytes _ => do pushDegrees $ .replicate 8 1; bumpAuxiliaries 8
| .unconstrainedGInverse _ => do pushDegree 1; bumpAuxiliaries
| .debug .. => pure ()

/-- Termination helper for blockLayout's Block/Ctrl traversal. -/
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Lower.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -333,6 +333,12 @@ def toIndex
let a ← expectIdx layoutMap bindings a
let b ← expectIdx layoutMap bindings b
pushOp (.unconstrainedBigUintDivMod a b) 2
| .unconstrainedGToBytes _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGToBytes a) 8
| .unconstrainedGInverse _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGInverse a)
| .debug _ _ label term ret => do
let term ← match term with
| none => pure none
Expand Down
2 changes: 2 additions & 0 deletions Ix/Aiur/Compiler/Match.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -391,6 +391,8 @@ def typedToSimple : Term → Simple.Term
| .u32LessThan τ e a b => .u32LessThan τ e (typedToSimple a) (typedToSimple b)
| .u8RangeCheck τ e a b => .u8RangeCheck τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes τ e (typedToSimple a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse τ e (typedToSimple a)
| .toField τ e a => .toField τ e (typedToSimple a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe τ e (typedToSimple a)
| .debug τ e l t r =>
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Simple.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,6 +136,12 @@ def simplifyTypedTerm (decls : Source.Decls) : Term → Except CheckError Term
let a' ← simplifyTypedTerm decls a
let b' ← simplifyTypedTerm decls b
pure (.unconstrainedBigUintDivMod τ e a' b')
| .unconstrainedGToBytes τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGToBytes τ e a')
| .unconstrainedGInverse τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGInverse τ e a')
| .toField τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.toField τ e a')
Expand Down
21 changes: 21 additions & 0 deletions Ix/Aiur/Goldilocks.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,27 @@ def G.u8BitDecomposition (a : G) : Fin 8 → G :=

def G.u32LessThan (a b : G) : G := if a.n < b.n then 1 else 0

/-- The 8 little-endian bytes of the canonical `u64` value. Semantic model of
the `unconstrained_g_to_bytes` hint. -/
def G.toLeBytes (a : G) : Fin 8 → G :=
fun i => G.ofUInt8 (a.val >>> (8 * i.val).toUInt64).toUInt8

/-- Exponentiation by squaring. Fuel-structural (64 bits covers any `n < 2⁶⁴`
exponent, in particular `p − 2`). -/
def G.pow (x : G) (n : Nat) : G := go n 64 where
go (n fuel : Nat) : G := match fuel with
| 0 => 1
| fuel + 1 =>
if n == 0 then 1
else
let h := go (n / 2) fuel
let sq := h * h
if n % 2 == 0 then sq else sq * x

/-- Fermat inverse `x^(p−2)`, with `0 ↦ 0`. Semantic model of the
`unconstrained_g_inverse` hint. -/
def G.inverse (x : G) : G := G.pow x (gSize.toNat - 2)

theorem G.one_ne_zero : ¬(1 : G) = (0 : G) := by decide

theorem G.add_comm (a b : G) : a + b = b + a := by
Expand Down
8 changes: 8 additions & 0 deletions Ix/Aiur/Interpret.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,6 +417,14 @@ partial def interp (decls : Decls) (bindings : Bindings) : Term → InterpM Valu
let _ ← interp decls bindings t1
let _ ← interp decls bindings t2
throwErr "unconstrainedBigUintDivMod: not implemented in debug interpreter"
| .unconstrainedGToBytes t => do
match ← interp decls bindings t with
| .field g => return .array (Array.ofFn fun i => .field (g.toLeBytes i))
| _ => throwErr "unconstrainedGToBytes: expected field value"
| .unconstrainedGInverse t => do
match ← interp decls bindings t with
| .field g => return .field g.inverse
| _ => throwErr "unconstrainedGInverse: expected field value"
-- `toField` / `u8FromFieldUnsafe` are erased coercions: value unchanged.
| .toField t | .u8FromFieldUnsafe t => interp decls bindings t
| .u8Lit n => return .field (G.ofNat n)
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Meta.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -205,6 +205,8 @@ syntax "u8_less_than" "(" aiur_trm ", " aiur_trm ")" : ai
syntax "u32_less_than" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "u8_range_check" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_big_uint_div_mod" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_g_to_bytes" "(" aiur_trm ")" : aiur_trm
syntax "unconstrained_g_inverse" "(" aiur_trm ")" : aiur_trm
syntax "to_field" "(" aiur_trm ")" : aiur_trm
syntax "u8_from_field_unsafe" "(" aiur_trm ")" : aiur_trm
syntax:max num "u8" : aiur_trm
Expand DownExpand Up@@ -351,6 +353,10 @@ partial def elabTrm : ElabStxCat `aiur_trm
mkAppM ``Source.Term.u8RangeCheck #[← elabTrm i, ← elabTrm j]
| `(aiur_trm| unconstrained_big_uint_div_mod($a:aiur_trm, $b:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedBigUintDivMod #[← elabTrm a, ← elabTrm b]
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGToBytes #[← elabTrm a]
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGInverse #[← elabTrm a]
| `(aiur_trm| to_field($a:aiur_trm)) => do
mkAppM ``Source.Term.toField #[← elabTrm a]
| `(aiur_trm| u8_from_field_unsafe($a:aiur_trm)) => do
Expand DownExpand Up@@ -579,6 +585,12 @@ where
let a ← replaceToken old new a
let b ← replaceToken old new b
`(aiur_trm| unconstrained_big_uint_div_mod($a, $b))
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_to_bytes($a))
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_inverse($a))
| `(aiur_trm| to_field($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| to_field($a))
Expand Down
23 changes: 23 additions & 0 deletions Ix/Aiur/Protocol.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,29 @@ def proveIxVM (system : @& AiurSystem)
let ioMap := ioMap.foldl (fun acc (k, v) => acc.insert k v) ∅
(claim, proof, ⟨ioData, ioMap⟩)

@[extern "rs_aiur_multi_stark_prove"]
private opaque proveMultiStark' : @& AiurSystem →
@& Bytecode.FunIdx → @& Array G →
(proofBytes : @& ByteArray) → (vkBytes : @& ByteArray) →
(claimBytes : @& ByteArray) → Bool →
Array G × Proof

/-- Prove the MultiStark recursive verifier over raw proof/vk/claims
byte blobs. The IO advice buffer is built natively in Rust (see
`Bytecode.Toplevel.executeMultiStark`); the execute step inside
the prove routes through the codegen'd verifier
(`crates/ixvm-codegen/src/aiur_multi_stark.rs`) unless
`useBytecode` is set. Only valid when `system` was built from the
production `MultiStark.multiStark` bytecode. Returns the claim
(`#[functionChannel, funIdx] ++ pubInput ++ output`) and the
`Proof`; the final buffer is not returned. -/
def proveMultiStark (system : @& AiurSystem)
(funIdx : @& Bytecode.FunIdx) (pubInput : @& Array G)
(proofBytes vkBytes claimBytes : @& ByteArray) (useBytecode : Bool := false) :
Array G × Proof :=
proveMultiStark' system funIdx pubInput proofBytes vkBytes claimBytes
useBytecode

@[extern "rs_aiur_system_prove_addr_with_env"]
private opaque proveAddrWithEnv' : @& AiurSystem →
@& Bytecode.FunIdx → @& EnvHandle → @& ByteArray →
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dfd4d4a
feat: Run the MultiStark verifier natively — codegen + Rust-built IO …
arthurpaulino Jul 19, 2026
f9ad960
perf: Native Goldilocks arithmetic in the recursive verifier (execute…
arthurpaulino Jul 19, 2026
2730861
perf: Read the proof stream by index, with unconstrained byte fetches
arthurpaulino Jul 19, 2026
bad7a5d
perf: Hash Merkle 2-to-1 compressions as one direct blake3 block
arthurpaulino Jul 19, 2026
156afc9
perf: Hash MMCS leaf rows at lane granularity
arthurpaulino Jul 19, 2026
3f4c0d6
perf: Make challenger observation linear in transcript size
arthurpaulino Jul 19, 2026
9388550
perf: Segmented, hugepage-backed, hash-caching QueryMap storage
arthurpaulino Jul 19, 2026
6e02f21
perf: Prune production Aiur toplevels to their entrypoint closures
arthurpaulino Jul 19, 2026
8d4c39f
perf: Ingest the verifying key by IO slices and indexed reads
arthurpaulino Jul 19, 2026
b612f70
perf: Native extension-field representation (execute -40%, FFT -27%)
arthurpaulino Jul 19, 2026
976cb7a
perf: Hash MMCS leaves by walking the selected rows directly
arthurpaulino Jul 19, 2026
9d26eb9
feat: Sparse kernel proofs via multi-stark circuit activation
arthurpaulino Jul 20, 2026
491c65f
refactor: Inline the trivial Goldilocks constant helpers
arthurpaulino Jul 20, 2026
6fd0ab1
perf: Split-streams vk encoding (7.8x smaller, outer prove now fits)
arthurpaulino Jul 20, 2026
f63844b
chore: rustfmt + clippy fixes in vk_codec
arthurpaulino Jul 20, 2026
186654a
fix: Cover the hint-op terms in the @fn inlining passes
arthurpaulino Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Benchmarks/IxVM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,11 @@ def friParameters : Aiur.FriParameters := {
}

def main : IO Unit := do
let .ok toplevel := IxVM.ixVM
-- `ixon_serde_blake3_bench` is a bench-only entrypoint, present only in
-- the FULL toplevel (production prunes it), so this prove goes through
-- the generic interpreter — the codegen'd kernel mirrors the pruned
-- toplevel and has no code (or index) for bench entries.
let .ok toplevel := IxVM.ixVMFull
| throw (IO.userError "Merging failed")
let .ok compiled := toplevel.compile
| throw (IO.userError "Compilation failed")
Expand All@@ -34,9 +38,7 @@ def main : IO Unit := do

let _ ← bgroup "IxVM benchmarks" { oneShot := true } do
throughput (.Elements n.toUInt64 "consts")
-- IxVM-native prove: routes execution through the codegen'd Rust
-- kernel (`execute_generated`) instead of the bytecode interpreter.
bench "serde/blake3 Nat.add_comm"
(aiurSystem.proveIxVM funIdx #[.ofNat n])
(aiurSystem.prove funIdx #[.ofNat n])
ioBuffer
return
23 changes: 17 additions & 6 deletions Benchmarks/RecursiveVerifier.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,10 +136,13 @@ def main (args : List String) : IO UInt32 := do
IO.eprintln "inner proof failed to verify"
return 1
-- Proof (advice, channel 0), vk (channel 1), claims (channel 2), plus the
-- Blake3-bound vk/claims digests and FRI params as public input.
-- Blake3-bound vk/claims digests and FRI params as public input. The
-- advice buffer is built natively in Rust from the raw byte blobs
-- (`executeMultiStark` / `proveMultiStark`).
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes facSystem.vkBytes
claimBytes recCommitParams innerFri
let vkBytes := facSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
recCommitParams innerFri
-- Compile the verifier toplevel and run it over the proof.
let vTop ← match MultiStark.multiStark with
| .ok t => pure t
Expand All@@ -149,10 +152,17 @@ def main (args : List String) : IO UInt32 := do
| .error e => IO.eprintln s!"verifier compile failed: {e}"; return 1
let vIdx := vCompiled.getFuncIdx `verify_multi_stark_proof |>.get!
IO.println "executing verify_multi_stark_proof…"
-- `--use-bytecode`: route through the generic Aiur interpreter instead of
-- the codegen'd verifier (same escape hatch as `ix check --use-bytecode`;
-- useful for iterating on `Ix/MultiStark/*.lean` without regenerating
-- `crates/ixvm-codegen/src/aiur_multi_stark.rs`, and for measuring the
-- interpreter ↔ codegen gap).
let useBytecode := args.contains "--use-bytecode"
let e0 ← IO.monoNanosNow
match vCompiled.bytecode.execute vIdx pubInput io with
match vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes vkBytes
claimBytes useBytecode with
| .error e => IO.eprintln s!"verifier execution REJECTED: {e}"; return 1
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let e1 ← IO.monoNanosNow
let stats := Aiur.computeStats vCompiled qc
IO.println s!"verifier accepted, execute {secs e0 e1} s"
Expand All@@ -178,7 +188,8 @@ def main (args : List String) : IO UInt32 := do
let vSystem := AiurSystem.build vCompiled.bytecode recCommitParams innerFri
TracingTexray.resetPeakTreeRss
let t0 ← IO.monoNanosNow
let (vclaim, vproof, _) := vSystem.prove vIdx pubInput io
let (vclaim, vproof) := vSystem.proveMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
let nbytes := vproof.toBytes.size -- force the (lazy, pure) prove to run
let t1 ← IO.monoNanosNow
let outerPeak ← TracingTexray.peakTreeRssBytes
Expand Down
21 changes: 15 additions & 6 deletions Benchmarks/Typecheck.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -498,17 +498,25 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] recursively verifying {r.name} …"
(← IO.getStdout).flush
let claimBytes := MultiStark.serializeClaims #[claim]
let (pubInput, io) := MultiStark.verifierInput proofBytes
aiurSystem.vkBytes claimBytes commitParams friParams
let vkBytes := aiurSystem.vkBytes
let pubInput := MultiStark.verifierPubInput vkBytes claimBytes
commitParams friParams
-- Native path: the advice buffer is built in Rust from the raw
-- byte blobs and execution routes through the codegen'd verifier.
let (rvRes, rvSec) ← timed fun _ =>
vCompiled.bytecode.execute vIdx pubInput io
vCompiled.bytecode.executeMultiStark vIdx pubInput proofBytes
vkBytes claimBytes
match rvRes with
| .error e =>
IO.eprintln s!" ❌ recursive verifier REJECTED {r.name}'s proof: {e}"
| .ok (_, _, qc) =>
| .ok (_, qc) =>
let rvStats := Aiur.computeStats vCompiled qc
IO.println s!" {r.name}: recursive={rvSec}s \
recursive-fft-cost={rvStats.totalFftCost}"
-- The per-circuit breakdown names where the verifier's cost
-- lives (deserialization vs blake3 vs FRI); texray-gated like
-- the other detailed diagnostics.
if useTexray then Aiur.printStats rvStats
let (row, _) := ordered[i]!
ordered := ordered.set! i
({ row with recursiveExecuteSec := some rvSec
Expand All@@ -520,8 +528,9 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do
IO.println s!" [{i + 1}/{ordered.size}] proving the verifier over {r.name} …"
(← IO.getStdout).flush
TracingTexray.resetPeakTreeRss
let ((rvClaim, rvProof, _), rvProveSec) ← timed fun _ =>
vSystem.prove vIdx pubInput io
let ((rvClaim, rvProof), rvProveSec) ← timed fun _ =>
vSystem.proveMultiStark vIdx pubInput proofBytes vkBytes
claimBytes
let rvPeak ← TracingTexray.peakTreeRssBytes
let rvProofBytes := Aiur.Proof.toBytes rvProof
let (rvVerifyRes, rvVerifySec) ← timed fun _ =>
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,10 +47,11 @@ dashmap = "6.1.0"
hashbrown = "0.15"
indexmap = "2"
itertools = "0.14.0"
libc = "0.2"
log = "0.4"
memmap2 = "0.9"
mimalloc = { version = "0.1", default-features = false }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "2c019220cb43ce946efec6b9caf507f5a6bccd1c" }
multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "6382036e970ab508e69b2db2b8ae5ad64672e34b" }
num-bigint = "0.4.6"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Compiler/Check.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,14 @@ def inferTerm (t : Term) : CheckM Typed.Term := match t with
let a' ← inferNoEscape a
let b' ← checkNoEscape b a'.typ
pure (Typed.Term.unconstrainedBigUintDivMod (.tuple #[a'.typ, a'.typ]) false a' b')
| .unconstrainedGToBytes a => do
-- The bytes are UNCONSTRAINED advice typed `u8`; the caller must
-- range-check them (see `Source.Term.unconstrainedGToBytes`).
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGToBytes (.array .u8 8) false a')
| .unconstrainedGInverse a => do
let a' ← checkNoEscape a .field
pure (Typed.Term.unconstrainedGInverse .field false a')
| .toField a => do
let a' ← checkNoEscape a .u8
pure (Typed.Term.toField .field false a')
Expand DownExpand Up@@ -981,6 +989,10 @@ def zonkTypedTerm (t : Typed.Term) : CheckM Typed.Term := match t with
pure (.u8RangeCheck (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← zonkTyp τ) e (← zonkTypedTerm a) (← zonkTypedTerm b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← zonkTyp τ) e (← zonkTypedTerm a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← zonkTyp τ) e (← zonkTypedTerm a))
| .toField τ e a => do pure (.toField (← zonkTyp τ) e (← zonkTypedTerm a))
| .u8FromFieldUnsafe τ e a => do
pure (.u8FromFieldUnsafe (← zonkTyp τ) e (← zonkTypedTerm a))
Expand Down
14 changes: 14 additions & 0 deletions Ix/Aiur/Compiler/Concretize.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -366,6 +366,10 @@ def termToConcrete
| .unconstrainedBigUintDivMod τ e a b => do
pure (.unconstrainedBigUintDivMod (← typToConcrete mono τ) e
(← termToConcrete mono a) (← termToConcrete mono b))
| .unconstrainedGToBytes τ e a => do
pure (.unconstrainedGToBytes (← typToConcrete mono τ) e (← termToConcrete mono a))
| .unconstrainedGInverse τ e a => do
pure (.unconstrainedGInverse (← typToConcrete mono τ) e (← termToConcrete mono a))
-- `toField` / `u8FromFieldUnsafe` are erased coercions: `u8` and `field`
-- share a representation, so we drop the wrapper and keep the inner term.
| .toField _ _ a | .u8FromFieldUnsafe _ _ a => termToConcrete mono a
Expand DownExpand Up@@ -575,6 +579,10 @@ def rewriteTypedTerm (decls : Typed.Decls)
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a) (rewriteTypedTerm decls subst mono b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .toField τ e a => .toField (rewriteTyp subst mono τ) e
(rewriteTypedTerm decls subst mono a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe (rewriteTyp subst mono τ) e
Expand DownExpand Up@@ -653,6 +661,7 @@ def collectInTypedTerm (seen : Std.HashSet (Global × Array Typ)) :
collectInTypedTerm (collectInTypedTerm (collectInTyp seen τ) a) b
| .eqZero τ _ a | .store τ _ a | .load τ _ a | .ptrVal τ _ a | .toField τ _ a
| .u8FromFieldUnsafe τ _ a
| .unconstrainedGToBytes τ _ a | .unconstrainedGInverse τ _ a
| .u8BitDecomposition τ _ a | .u8ShiftLeft τ _ a | .u8ShiftRight τ _ a =>
collectInTypedTerm (collectInTyp seen τ) a
| .ioGetInfo τ _ c k =>
Expand DownExpand Up@@ -724,6 +733,7 @@ def collectCalls (decls : Typed.Decls)
collectCalls decls (collectCalls decls seen a) b
| .eqZero _ _ a | .store _ _ a | .load _ _ a | .ptrVal _ _ a | .toField _ _ a
| .u8FromFieldUnsafe _ _ a
| .unconstrainedGToBytes _ _ a | .unconstrainedGInverse _ _ a
| .u8BitDecomposition _ _ a | .u8ShiftLeft _ _ a | .u8ShiftRight _ _ a =>
collectCalls decls seen a
| .ioGetInfo _ _ c k =>
Expand DownExpand Up@@ -839,6 +849,10 @@ def substInTypedTerm (subst : Global → Option Typ) : Typed.Term → Typed.Term
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod (Typ.instantiate subst τ) e
(substInTypedTerm subst a) (substInTypedTerm subst b)
| .unconstrainedGToBytes τ e a =>
.unconstrainedGToBytes (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .unconstrainedGInverse τ e a =>
.unconstrainedGInverse (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .toField τ e a => .toField (Typ.instantiate subst τ) e (substInTypedTerm subst a)
| .u8FromFieldUnsafe τ e a =>
.u8FromFieldUnsafe (Typ.instantiate subst τ) e (substInTypedTerm subst a)
Expand Down
4 changes: 4 additions & 0 deletions Ix/Aiur/Compiler/Layout.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -207,6 +207,10 @@ def opLayout : Bytecode.Op → LayoutM Unit
-- No constraint relation, no lookup. Mirrors `IORead` (aux only) — the Rust
-- `constraints.rs` pushes exactly 2 auxiliaries and emits no relation.
| .unconstrainedBigUintDivMod .. => do pushDegrees #[1, 1]; bumpAuxiliaries 2
-- Unconstrained hints: fresh auxiliary witness columns only, no relation,
-- no lookup — same shape as `IORead`. The caller pins the values.
| .unconstrainedGToBytes _ => do pushDegrees $ .replicate 8 1; bumpAuxiliaries 8
| .unconstrainedGInverse _ => do pushDegree 1; bumpAuxiliaries
| .debug .. => pure ()

/-- Termination helper for blockLayout's Block/Ctrl traversal. -/
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Lower.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -333,6 +333,12 @@ def toIndex
let a ← expectIdx layoutMap bindings a
let b ← expectIdx layoutMap bindings b
pushOp (.unconstrainedBigUintDivMod a b) 2
| .unconstrainedGToBytes _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGToBytes a) 8
| .unconstrainedGInverse _ _ a => do
let a ← expectIdx layoutMap bindings a
pushOp (.unconstrainedGInverse a)
| .debug _ _ label term ret => do
let term ← match term with
| none => pure none
Expand Down
2 changes: 2 additions & 0 deletions Ix/Aiur/Compiler/Match.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -391,6 +391,8 @@ def typedToSimple : Term → Simple.Term
| .u32LessThan τ e a b => .u32LessThan τ e (typedToSimple a) (typedToSimple b)
| .u8RangeCheck τ e a b => .u8RangeCheck τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedBigUintDivMod τ e a b => .unconstrainedBigUintDivMod τ e (typedToSimple a) (typedToSimple b)
| .unconstrainedGToBytes τ e a => .unconstrainedGToBytes τ e (typedToSimple a)
| .unconstrainedGInverse τ e a => .unconstrainedGInverse τ e (typedToSimple a)
| .toField τ e a => .toField τ e (typedToSimple a)
| .u8FromFieldUnsafe τ e a => .u8FromFieldUnsafe τ e (typedToSimple a)
| .debug τ e l t r =>
Expand Down
6 changes: 6 additions & 0 deletions Ix/Aiur/Compiler/Simple.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,6 +136,12 @@ def simplifyTypedTerm (decls : Source.Decls) : Term → Except CheckError Term
let a' ← simplifyTypedTerm decls a
let b' ← simplifyTypedTerm decls b
pure (.unconstrainedBigUintDivMod τ e a' b')
| .unconstrainedGToBytes τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGToBytes τ e a')
| .unconstrainedGInverse τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.unconstrainedGInverse τ e a')
| .toField τ e a => do
let a' ← simplifyTypedTerm decls a
pure (.toField τ e a')
Expand Down
21 changes: 21 additions & 0 deletions Ix/Aiur/Goldilocks.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,27 @@ def G.u8BitDecomposition (a : G) : Fin 8 → G :=

def G.u32LessThan (a b : G) : G := if a.n < b.n then 1 else 0

/-- The 8 little-endian bytes of the canonical `u64` value. Semantic model of
the `unconstrained_g_to_bytes` hint. -/
def G.toLeBytes (a : G) : Fin 8 → G :=
fun i => G.ofUInt8 (a.val >>> (8 * i.val).toUInt64).toUInt8

/-- Exponentiation by squaring. Fuel-structural (64 bits covers any `n < 2⁶⁴`
exponent, in particular `p − 2`). -/
def G.pow (x : G) (n : Nat) : G := go n 64 where
go (n fuel : Nat) : G := match fuel with
| 0 => 1
| fuel + 1 =>
if n == 0 then 1
else
let h := go (n / 2) fuel
let sq := h * h
if n % 2 == 0 then sq else sq * x

/-- Fermat inverse `x^(p−2)`, with `0 ↦ 0`. Semantic model of the
`unconstrained_g_inverse` hint. -/
def G.inverse (x : G) : G := G.pow x (gSize.toNat - 2)

theorem G.one_ne_zero : ¬(1 : G) = (0 : G) := by decide

theorem G.add_comm (a b : G) : a + b = b + a := by
Expand Down
8 changes: 8 additions & 0 deletions Ix/Aiur/Interpret.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,6 +417,14 @@ partial def interp (decls : Decls) (bindings : Bindings) : Term → InterpM Valu
let _ ← interp decls bindings t1
let _ ← interp decls bindings t2
throwErr "unconstrainedBigUintDivMod: not implemented in debug interpreter"
| .unconstrainedGToBytes t => do
match ← interp decls bindings t with
| .field g => return .array (Array.ofFn fun i => .field (g.toLeBytes i))
| _ => throwErr "unconstrainedGToBytes: expected field value"
| .unconstrainedGInverse t => do
match ← interp decls bindings t with
| .field g => return .field g.inverse
| _ => throwErr "unconstrainedGInverse: expected field value"
-- `toField` / `u8FromFieldUnsafe` are erased coercions: value unchanged.
| .toField t | .u8FromFieldUnsafe t => interp decls bindings t
| .u8Lit n => return .field (G.ofNat n)
Expand Down
12 changes: 12 additions & 0 deletions Ix/Aiur/Meta.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -205,6 +205,8 @@ syntax "u8_less_than" "(" aiur_trm ", " aiur_trm ")" : ai
syntax "u32_less_than" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "u8_range_check" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_big_uint_div_mod" "(" aiur_trm ", " aiur_trm ")" : aiur_trm
syntax "unconstrained_g_to_bytes" "(" aiur_trm ")" : aiur_trm
syntax "unconstrained_g_inverse" "(" aiur_trm ")" : aiur_trm
syntax "to_field" "(" aiur_trm ")" : aiur_trm
syntax "u8_from_field_unsafe" "(" aiur_trm ")" : aiur_trm
syntax:max num "u8" : aiur_trm
Expand DownExpand Up@@ -351,6 +353,10 @@ partial def elabTrm : ElabStxCat `aiur_trm
mkAppM ``Source.Term.u8RangeCheck #[← elabTrm i, ← elabTrm j]
| `(aiur_trm| unconstrained_big_uint_div_mod($a:aiur_trm, $b:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedBigUintDivMod #[← elabTrm a, ← elabTrm b]
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGToBytes #[← elabTrm a]
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
mkAppM ``Source.Term.unconstrainedGInverse #[← elabTrm a]
| `(aiur_trm| to_field($a:aiur_trm)) => do
mkAppM ``Source.Term.toField #[← elabTrm a]
| `(aiur_trm| u8_from_field_unsafe($a:aiur_trm)) => do
Expand DownExpand Up@@ -579,6 +585,12 @@ where
let a ← replaceToken old new a
let b ← replaceToken old new b
`(aiur_trm| unconstrained_big_uint_div_mod($a, $b))
| `(aiur_trm| unconstrained_g_to_bytes($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_to_bytes($a))
| `(aiur_trm| unconstrained_g_inverse($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| unconstrained_g_inverse($a))
| `(aiur_trm| to_field($a:aiur_trm)) => do
let a ← replaceToken old new a
`(aiur_trm| to_field($a))
Expand Down
23 changes: 23 additions & 0 deletions Ix/Aiur/Protocol.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,29 @@ def proveIxVM (system : @& AiurSystem)
let ioMap := ioMap.foldl (fun acc (k, v) => acc.insert k v) ∅
(claim, proof, ⟨ioData, ioMap⟩)

@[extern "rs_aiur_multi_stark_prove"]
private opaque proveMultiStark' : @& AiurSystem →
@& Bytecode.FunIdx → @& Array G →
(proofBytes : @& ByteArray) → (vkBytes : @& ByteArray) →
(claimBytes : @& ByteArray) → Bool →
Array G × Proof

/-- Prove the MultiStark recursive verifier over raw proof/vk/claims
byte blobs. The IO advice buffer is built natively in Rust (see
`Bytecode.Toplevel.executeMultiStark`); the execute step inside
the prove routes through the codegen'd verifier
(`crates/ixvm-codegen/src/aiur_multi_stark.rs`) unless
`useBytecode` is set. Only valid when `system` was built from the
production `MultiStark.multiStark` bytecode. Returns the claim
(`#[functionChannel, funIdx] ++ pubInput ++ output`) and the
`Proof`; the final buffer is not returned. -/
def proveMultiStark (system : @& AiurSystem)
(funIdx : @& Bytecode.FunIdx) (pubInput : @& Array G)
(proofBytes vkBytes claimBytes : @& ByteArray) (useBytecode : Bool := false) :
Array G × Proof :=
proveMultiStark' system funIdx pubInput proofBytes vkBytes claimBytes
useBytecode

@[extern "rs_aiur_system_prove_addr_with_env"]
private opaque proveAddrWithEnv' : @& AiurSystem →
@& Bytecode.FunIdx → @& EnvHandle → @& ByteArray →
Expand Down
Loading