-
Notifications
You must be signed in to change notification settings - Fork 0
feat: second recursion implementation #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ivokub
wants to merge
50
commits into
main
Choose a base branch
from
feat/recursion-2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
50 commits
Select commit
Hold shift + click to select a range
d5ff026
feat(recursion): scaffold gadget primitives for recursive verifier
ivokub 8f1ce6b
feat(recursion): add FRI fold and batching gadgets
ivokub 5e4ef33
feat(recursion): wire FRI query chain + add bits and binexp gadgets
ivokub f75f57c
feat(recursion): compose bits+binexp into friround and add idxselect
ivokub d91b4e5
feat(recursion): wire end-to-end FRI query verifier (rounds + chain +…
ivokub 212ee04
feat(recursion): wire level-batching gamma-mix into frichain
ivokub 60a6b4f
feat(recursion): pin alpha and gamma constant across module rows
ivokub 8b63d34
feat(recursion): add width-24 Poseidon2 gadget (sponge variant)
ivokub 79a8ae7
feat(recursion): add Merkle leaf-hash and node-hash gadgets
ivokub efba2f3
feat(recursion): close hash-binding gap in merkle path traversal
ivokub 0832219
feat(recursion): bind merkle row 0 to leafhash digest
ivokub 77445a1
test(recursion): end-to-end FRI verifier with Merkle binding
ivokub 55c6687
fix(board): populate Module field in AddExposeIthValueStep ctx
ivokub 29dbce0
feat(recursion): close cross-module gap via exposed values
ivokub a22968c
feat(recursion): add AIR-at-zeta DAG evaluator gadget
ivokub 159a3cc
feat(recursion): full AIR-at-zeta check gadget
ivokub 8f4167a
feat(recursion): add DEEP-quotient bridge gadget primitives
ivokub ddf4468
feat(recursion): add width-24 Fiat-Shamir challenger gadget
ivokub ef95b9b
feat(recursion): Stage-1 buildVerifierCore — AIR-only
ivokub 72d2669
feat(recursion): Stage-2 buildVerifierCore — PublicInput, Exposed, Fi…
ivokub 83692f9
feat(recursion): Stage-3 buildVerifierCore — in-circuit zeta via FS c…
ivokub 4004d75
feat(recursion): Stage-4 buildVerifierCore — DEEP_ALPHA in FS chain
ivokub d12665e
feat(recursion): Stage-5 buildVerifierCore — DEEP_ALPHA bindings as w…
ivokub ca26bf4
feat(recursion): Stage-6 buildVerifierCore — extend FS chain through …
ivokub fc42299
feat(recursion): Stage-7 buildVerifierCore — full FS chain through al…
ivokub 2a74994
test(recursion): non-SkipFRI buildVerifierCore on Fibonacci inner
ivokub 6ee035d
recursion: export BuildVerifierCore + add 5-stage benchmark
ivokub 7bbbed3
perf(recursion): cut BuildVerifierCore by 600x via two fixes
ivokub 2b40710
feat(recursion/bits): RegisterAt — sparse-row bit decomposition
ivokub b9bbbc0
feat(recursion): Stage 8 — in-circuit FRI final-poly match
ivokub 67312ec
feat(recursion): Stage 9 — cross-round FRI fold chain
ivokub 7da410c
feat(recursion): Stage 10 — per-layer Merkle openings for query 0
ivokub e83c1ae
feat(recursion): Stage 11 — DEEP-quotient bridge (single-size)
ivokub 339090d
feat(recursion): Stage 12 — per-column Merkle openings
ivokub 2a54b9f
feat(recursion): multi-block flexible leafhash + Stage 12 covers all …
ivokub a6d2c36
feat(recursion): Stage 13 — multi-degree DEEP bridge
ivokub ce33e7a
feat(recursion): per-column Merkle openings for multi-degree
ivokub b4a0a2b
feat(recursion): Stage 14 — per-level Merkle openings
ivokub 9e4f12f
feat(recursion): Stage 15 — multi-degree FRI fold chain
ivokub 4d49007
feat(recursion): Stage 10 extended to all FRI queries
ivokub 5aae13b
refactor(recursion): generalise finalPoly lookup to arbitrary 2^k
ivokub 47bef00
feat(recursion): aggregation API + tree-aggregation driver
ivokub 29553c0
Merge remote-tracking branch 'origin/main' into feat/recursion-2
ivokub 0276e28
feat: migrate to E6
ivokub 49fbc46
chore: more E6 renaming
ivokub 1d2ab15
chore: use mustsetrandom
ivokub 2e188bb
fix: set all limbs
ivokub 5313ae9
fix: remove tautological check
ivokub acdd278
test: bench with fri
ivokub 3e849e3
fix: use MustSetRandom
ivokub File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| // Copyright Consensys Software Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
| // the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
| // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations under the License. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package recursion | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/consensys/loom/board" | ||
| "github.com/consensys/loom/trace" | ||
| ) | ||
|
|
||
| // BuildAggregationCore compiles a board.Program that verifies BOTH inner | ||
| // proofs in a single outer circuit. The two sub-verifiers are wired into | ||
| // the same builder under disjoint module-name prefixes ("L_" and "R_") so | ||
| // they share no columns or modules, but a single board.Compile + outer | ||
| // prove/verify covers them at once — yielding one aggregated proof for | ||
| // the pair. | ||
| // | ||
| // This is the foundation of tree-aggregation: each non-leaf node of the | ||
| // aggregation tree is a BuildAggregationCore circuit verifying the two | ||
| // child proofs from the level below; AggregateProofs drives that loop. | ||
| // | ||
| // The Left and Right halves are independent — programs may differ in | ||
| // shape, sizes, and hash backends (though both must be Poseidon2, per | ||
| // validateInnerProof). Per-half customisation through cfg.ModulePrefix | ||
| // is ignored here; this function manages the prefixes itself. | ||
| func BuildAggregationCore(input AggregationInput, cfg Config) (board.Program, trace.Trace, error) { | ||
| if err := validateInnerProof(input.Left.Proof, cfg); err != nil { | ||
| return board.Program{}, trace.Trace{}, fmt.Errorf("aggregation: left: %w", err) | ||
| } | ||
| if err := validateInnerProof(input.Right.Proof, cfg); err != nil { | ||
| return board.Program{}, trace.Trace{}, fmt.Errorf("aggregation: right: %w", err) | ||
| } | ||
|
|
||
| leftCfg := cfg | ||
| leftCfg.ModulePrefix = "L_" | ||
| rightCfg := cfg | ||
| rightCfg.ModulePrefix = "R_" | ||
|
|
||
| builder := board.NewBuilder() | ||
| leftTrace, err := buildVerifierCoreInto(&builder, input.Left, leftCfg) | ||
| if err != nil { | ||
| return board.Program{}, trace.Trace{}, fmt.Errorf("aggregation: build left: %w", err) | ||
| } | ||
| rightTrace, err := buildVerifierCoreInto(&builder, input.Right, rightCfg) | ||
| if err != nil { | ||
| return board.Program{}, trace.Trace{}, fmt.Errorf("aggregation: build right: %w", err) | ||
| } | ||
|
|
||
| merged, err := trace.MergeMatching(leftTrace, rightTrace) | ||
| if err != nil { | ||
| return board.Program{}, trace.Trace{}, fmt.Errorf("aggregation: merge traces: %w", err) | ||
| } | ||
|
|
||
| pg, err := board.Compile(&builder) | ||
| if err != nil { | ||
| return board.Program{}, trace.Trace{}, fmt.Errorf("aggregation: compile: %w", err) | ||
| } | ||
| return pg, merged, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| // Copyright Consensys Software Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
| // the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
| // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations under the License. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package recursion | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/consensys/loom/prover" | ||
| "github.com/consensys/loom/setup" | ||
| "github.com/consensys/loom/verifier" | ||
| ) | ||
|
|
||
| // TestBuildAggregationCorePair builds an aggregation circuit verifying | ||
| // two distinct Fibonacci(n=4) inner proofs and confirms the outer | ||
| // proof passes prove + verify end-to-end. Both halves use SkipFRI so | ||
| // the test runs in reasonable wall time. | ||
| func TestBuildAggregationCorePair(t *testing.T) { | ||
| leftProg, leftTrace := makeFibonacciInner(t, 4) | ||
| leftProof, err := prover.Prove(leftTrace, setup.ProvingKey{}, nil, leftProg, prover.SkipFRI()) | ||
| if err != nil { | ||
| t.Fatalf("left inner prove: %v", err) | ||
| } | ||
| rightProg, rightTrace := makeEqualityInner(t, 4) | ||
| rightProof, err := prover.Prove(rightTrace, setup.ProvingKey{}, nil, rightProg, prover.SkipFRI()) | ||
| if err != nil { | ||
| t.Fatalf("right inner prove: %v", err) | ||
| } | ||
|
|
||
| outerProg, outerTrace, err := BuildAggregationCore( | ||
| AggregationInput{ | ||
| Left: RecursionInput{Program: leftProg, Proof: leftProof}, | ||
| Right: RecursionInput{Program: rightProg, Proof: rightProof}, | ||
| }, | ||
| DefaultConfig(), | ||
| ) | ||
| if err != nil { | ||
| t.Fatalf("BuildAggregationCore: %v", err) | ||
| } | ||
| outerProof, err := prover.Prove(outerTrace, setup.ProvingKey{}, nil, outerProg, prover.SkipFRI()) | ||
| if err != nil { | ||
| t.Fatalf("aggregated prove: %v", err) | ||
| } | ||
| if err := verifier.Verify(nil, setup.VerificationKey{}, outerProg, outerProof, verifier.SkipFRI()); err != nil { | ||
| t.Fatalf("aggregated verify: %v", err) | ||
| } | ||
| } | ||
|
|
||
| // TestBuildAggregationCoreRejectsBadLeft tampers the left inner proof | ||
| // (its AT-zeta values) and confirms the aggregated outer verifier | ||
| // rejects. | ||
| func TestBuildAggregationCoreRejectsBadLeft(t *testing.T) { | ||
| leftProg, leftTrace := makeFibonacciInner(t, 4) | ||
| leftProof, err := prover.Prove(leftTrace, setup.ProvingKey{}, nil, leftProg, prover.SkipFRI()) | ||
| if err != nil { | ||
| t.Fatalf("left inner prove: %v", err) | ||
| } | ||
| rightProg, rightTrace := makeEqualityInner(t, 4) | ||
| rightProof, err := prover.Prove(rightTrace, setup.ProvingKey{}, nil, rightProg, prover.SkipFRI()) | ||
| if err != nil { | ||
| t.Fatalf("right inner prove: %v", err) | ||
| } | ||
|
|
||
| // Tamper left's A value at zeta. | ||
| a, ok := leftProof.ValueAtZetaExt("A") | ||
| if !ok { | ||
| t.Fatal("A not in left.ValuesAtZeta") | ||
| } | ||
| a.B0.A0.SetUint64(a.B0.A0.Uint64() + 1) | ||
| leftProof.SetValueAtZetaExt("A", a) | ||
|
|
||
| outerProg, outerTrace, err := BuildAggregationCore( | ||
| AggregationInput{ | ||
| Left: RecursionInput{Program: leftProg, Proof: leftProof}, | ||
| Right: RecursionInput{Program: rightProg, Proof: rightProof}, | ||
| }, | ||
| DefaultConfig(), | ||
| ) | ||
| if err != nil { | ||
| t.Fatalf("BuildAggregationCore: %v", err) | ||
| } | ||
| outerProof, err := prover.Prove(outerTrace, setup.ProvingKey{}, nil, outerProg, prover.SkipFRI()) | ||
| if err != nil { | ||
| return | ||
| } | ||
| if err := verifier.Verify(nil, setup.VerificationKey{}, outerProg, outerProof, verifier.SkipFRI()); err == nil { | ||
| t.Fatalf("aggregated verify accepted tampered left inner proof") | ||
| } | ||
| } | ||
|
|
||
| // TestAggregateInputsTwoLeaves drives the tree-aggregation loop with | ||
| // two leaf proofs (one root aggregation step). | ||
| // | ||
| // Currently skipped under testing.Short() because building the root | ||
| // aggregation circuit requires running BuildVerifierCore on a leaf | ||
| // VERIFIER's airverify program — itself a multi-module program with | ||
| // large constraint expressions. board.Compile times out on the | ||
| // current implementation. Performance work (sharing merkle modules | ||
| // across queries, flattening constraint expressions) is the next | ||
| // stage before tree aggregation becomes test-affordable. | ||
| func TestAggregateInputsTwoLeaves(t *testing.T) { | ||
| if testing.Short() { | ||
| t.Skip("aggregation tree compile is slow; run with -short=false") | ||
| } | ||
| t.Skip("TODO: optimise aggregation circuit compile before re-enabling — see commit message") | ||
| inputs := make([]RecursionInput, 2) | ||
| for i := range inputs { | ||
| prog, tr := makeFibonacciInner(t, 4) | ||
| prf, err := prover.Prove(tr, setup.ProvingKey{}, nil, prog, prover.SkipFRI()) | ||
| if err != nil { | ||
| t.Fatalf("leaf %d inner prove: %v", i, err) | ||
| } | ||
| inputs[i] = RecursionInput{Program: prog, Proof: prf} | ||
| } | ||
|
|
||
| rootProg, rootProof, err := AggregateInputs(inputs, DefaultConfig()) | ||
| if err != nil { | ||
| t.Fatalf("AggregateInputs: %v", err) | ||
| } | ||
| if err := verifier.Verify(nil, setup.VerificationKey{}, rootProg, rootProof, verifier.SkipFRI()); err != nil { | ||
| t.Fatalf("aggregated root verify: %v", err) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| // Copyright Consensys Software Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
| // the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
| // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations under the License. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package recursion | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/consensys/loom/internal/commitment" | ||
| "github.com/consensys/loom/proof" | ||
| ) | ||
|
|
||
| // Config carries optional knobs for verifier-circuit construction. Defaults | ||
| // are exposed via DefaultConfig. | ||
| type Config struct { | ||
| // HashBackendID identifies the algebraic hash the inner proof was produced | ||
| // with. Only Poseidon2 is supported for recursion. | ||
| HashBackendID string | ||
|
|
||
| // ModulePrefix is prepended to every module / column name BuildVerifierCore | ||
| // emits. Use it when composing multiple verifier circuits into the same | ||
| // outer builder (e.g. BuildAggregationCore wires two inner proofs by | ||
| // instantiating BuildVerifierCore twice with distinct prefixes). The empty | ||
| // prefix is the default and matches the historical single-proof layout. | ||
| ModulePrefix string | ||
| } | ||
|
|
||
| // DefaultConfig returns a Config preset for Poseidon2 recursion, which is the | ||
| // only mode currently supported. | ||
| func DefaultConfig() Config { | ||
| return Config{HashBackendID: commitment.HashBackendPoseidon2} | ||
| } | ||
|
|
||
| // validateInnerProof ensures the inner proof is recursion-compatible. SHA-256 | ||
| // proofs cannot be recursed because their hash chain has no efficient | ||
| // in-circuit encoding. | ||
| func validateInnerProof(p proof.Proof, cfg Config) error { | ||
| id := commitment.NormalizeHashBackendID(p.HashBackendID) | ||
| want := commitment.NormalizeHashBackendID(cfg.HashBackendID) | ||
| if id != commitment.HashBackendPoseidon2 { | ||
| return fmt.Errorf("recursion: inner proof uses hash backend %q; only %q is supported", id, commitment.HashBackendPoseidon2) | ||
| } | ||
| if want != commitment.HashBackendPoseidon2 { | ||
| return fmt.Errorf("recursion: config hash backend %q is not supported (only %q)", want, commitment.HashBackendPoseidon2) | ||
| } | ||
| return nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // Copyright Consensys Software Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
| // the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
| // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations under the License. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| // Package recursion builds Loom programs that verify other Loom proofs. | ||
| // | ||
| // Two top-level entry points are planned: | ||
| // | ||
| // - BuildVerifierCore compiles a verifier circuit for a single inner proof | ||
| // (used for proof compression by repeated wrapping). | ||
| // - buildAggregationCore compiles a verifier circuit that verifies two | ||
| // inner proofs at once (used for tree-based aggregation of segmented | ||
| // traces). | ||
| // | ||
| // Recursion is only meaningful with an algebraic hash, so the inner proof | ||
| // must use the Poseidon2 backend. | ||
| // | ||
| // This milestone delivers only the primitive gadget layer: | ||
| // | ||
| // - gadgets/poseidon2 in-circuit Poseidon2 permutation (width-16 MD variant) | ||
| // - gadgets/merkle Merkle-path verification on top of the Poseidon2 gadget | ||
| // - gadgets/challenger Fiat-Shamir sponge layered over the Poseidon2 gadget | ||
| // - extfield E6 arithmetic helpers inlined as expr.Expr trees | ||
| // | ||
| // The BuildVerifierCore / buildAggregationCore wiring is intentionally left as | ||
| // stubs returning an error; subsequent milestones will assemble these gadgets | ||
| // into a complete verifier circuit (AIR-at-zeta check, FRI verification, | ||
| // Merkle-opening verification, DEEP-quotient bridge). | ||
| package recursion |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tamper test exits on prove
Low Severity
TestBuildAggregationCoreRejectsBadLeftreturns without failing whenprover.Proveerrors after tampering the left proof. The test title expects rejection at verify time; a silent pass hides regressions where prove fails for unrelated reasons or where a broken verifier never reachesVerify.Reviewed by Cursor Bugbot for commit 3e849e3. Configure here.