feat(plonky3): Phase 0 recursion feasibility spike — gate is GO (carrier tables) - #212
Merged
Merged
Conversation
Add spikes/plonky3-recursion-spike, an isolated probe (its own workspace, excluded from the root workspace so the heavy Plonky3 git deps never enter the node/shared build or CI) that empirically proves Plonky3/Plonky3-recursion can express the three composition patterns the zkCoins circuit depends on, in Goldilocks, on the pinned revs: - Probe A (IVC/cyclic with base case): 4-layer chain; the verifier-circuit shape reaches a fixed point (true IVC, no growth) — the analogue of Plonky2 common_data_for_recursion. witness_count [25567, 104630, 107957, 107957]. - Probe B (fan-in-8, variable active count): 2-to-1 aggregation composes into a fixed-shape tree (fan-in-4 probed; fan-in-8 is one more level). No native conditional-verify primitive; inactive slots are padded with real proofs and masked downstream via an active bit. - Probe C (vk/PI binding): an inner proof's public inputs are bound in the verifier circuit; a mismatched claim is rejected in-circuit. Record P0-T5 cost (~4.65 s per stabilized layer, ~1 GB peak RSS) and the P0-T6 Go/No-Go memo (MIGRATION_PLONKY3_SPIKE_RESULT.md). Gate decision: GO. Pins: Plonky3-recursion 524665d0c2e1d294722c064786ae11dff8d9f33b, Plonky3 56952503e1401a62982ceaf952c5e4a829b61803.
…a exactly Address logic-review findings — scope the spike's claims precisely against MIGRATION_PLONKY3.md §5 so the GO gate is not overstated: - Probe A: strengthen the fixed-point assertion (require the shape to have GROWN before stabilising, not just last-two-equal). Document that cross-layer counter PI THREADING (P0-T2 crit. 2) is not exercised — into_recursion_input carries empty table_public_inputs; the enabling primitive is proven in Probe C; explicit threading is Phase-5 work. - Probe B: relabel honestly — the probe proves 2-to-1 fan-in TREE COMPOSITION (4 identical real leaves), NOT variable active count / per-leaf PIs / active-bit masking. The masking strategy (§7.17) is Phase-5 construction on proven primitives. - Probe C: relabel as PUBLIC-INPUT binding (proven), with vk-equality connect-back as Phase-5 construction (the literal "wrong-vk proof" is not fed). Memo gate decision restated: the three mechanisms are proven; the three deferred items are in-repo Phase-5 construction, not upstream gaps. Decision stays GO. All 4 probes still green; fmt + clippy clean.
…/E/F) The Phase-0 gate previously proved the three mechanisms but DEFERRED the three real §5 PASS items to Phase 5. This adds probes that exercise them end-to-end with real proving and positive+negative (+control) assertions: - Probe D (probe_d_pi_threading): cross-layer PI threading binding. An outer circuit verifying an inner uni-stark proof exposes air_public_targets and threads a value (next_start = inner.last + 1) bound to an outer public input; a wrong threaded value is rejected; a control without the bind accepts it. - Probe D part 2 (probe_d_multilayer_carry): the escalated finding. Verifying an inner BATCH proof of a CircuitBuilder circuit exposes NO inner public inputs (air_public_targets = [0,0,0]); the high-level chain does not propagate public inputs across layers (differs from Plonky2 cyclic recursion). Pinned by assert. - Probe E (probe_e_active_masking): variable-active-count masking (§7.17). An 8-slot fixed-shape consumer circuit, batch-stark-proved for real: active+correct with inactive-garbage accepted (masked); active-wrong rejected; flipping a garbage slot's active bit flips the verdict; flipping back re-masks. - Probe F (probe_f_vk_binding): vk-equality connect-back. Two ConstPrepAir instances (k=42/99) have different preprocessed commitments (= vks). A proof from vk_99 (internally valid against vk_99) is rejected SOLELY by the connect to vk_42; a control accepts it unbound. Rewrite MIGRATION_PLONKY3_SPIKE_RESULT.md: the §5 items are now exercised (not deferred); the gate is GO with ONE escalated finding (cross-layer public-input propagation), surfaced for operator judgment per §16. All 8 probes green; fmt + clippy clean.
2 tasks
TaprootFreak
marked this pull request as ready for review
June 6, 2026 12:04
… G/H/I) Three integrated probes that resolve the previously-escalated open questions into hard, pre-Phase-1 constraints: - Probe H (probe_h_option1_air_public_values): Option 1 (carry the threaded value as an AIR public value) is DEAD. The honest empty-PI layer builds+proves; injecting a non-empty RecursionInput::BatchStark.table_public_inputs is rejected. Combined with probe_d_multilayer_carry (air_public_targets = [0,0,0]), both Option-1 avenues fail. - Probe G (probe_g_fanin_pi_passthrough): a real 2-to-1 aggregation's per-leaf values are NOT surfaced to the outer (air_public_targets = 0). The integrated fan-in-8 per-leaf-PI passthrough is blocked at the first cross-layer hop. => Together G+H decide the Phase-1-authorize choice: Option 2 (commit + hash re-bind) is MANDATORY for IVC threading AND aggregator per-leaf surfacing. - Probe I (probe_i_cost_projection): recursion-layer cost at real inner-proof scale. Sub-linear scaling; a layer over a ~2^16-gate proof is ~3.2s / ~1.4GB. Combined with the mandatory Option-2 overhead and the base prove, the 5s warm budget is at material risk -> measure on the real circuit early in Phase 5. Rewrite MIGRATION_PLONKY3_SPIKE_RESULT.md: status is now CONDITIONAL GO (Option 2 mandatory; warm-prove budget at material risk). The pinned probes (G/H/multilayer_carry) catch a future upstream rev that restores native public-input propagation. All 11 probes green; fmt + clippy clean.
…cannot compose The in-circuit Poseidon2 hash-bind primitive (add_hash_slice + connect) is real and binding: hash(V) binds to hash(V); a mismatched preimage is rejected. So Option 2's per-layer commit+rebind building block is expressible. But it needs layer N+1 to read layer N's committed digest, which is structurally impossible across a batch layer (probe_d_multilayer_carry / probe_g / probe_h: no per-instance value exposed, only whole-trace Merkle commitments). So multi-layer Option-2 threading is NOT achievable — confirming both Option 1 and Option 2 are dead and the cross-layer state IVC is unbuildable on this rev.
…ayers (Probe J) Probe J + an adversarial review of all six escape routes confirm that neither Option 1 nor Option 2 can thread a value across a batch-recursion layer. The per-layer commit+rebind primitive works (in-circuit Poseidon2 hash-bind), but layer N+1 cannot read layer N's committed digest (batch proofs expose only whole-trace Merkle commitments; FRI openings are FS-random; vk is per-circuit-static; NPO public_values are hardcoded empty with no public registration path). So zkCoins' cross-layer state IVC is structurally unbuildable on this rev. Memo status: CONDITIONAL GO -> NO-GO, with escape routes (upstream feature / protocol redesign / fork-excluded).
…ndness - Probe L (probe_l_multi_air): two heterogeneous AIRs (CounterAir state-transition-like + ConstPrepAir aggregator-like) co-verify in one verifier circuit, public inputs kept distinct + individually bound; cross-wiring A's PI to B's value is rejected. - Probe N (probe_n_concurrent): 4 independent prove+recurse+verify workloads on separate threads all succeed; peak RSS ~1.38 GB. Prover is concurrency-safe. - Probe O (probe_o_soundness): soundness spot-check — mismatched FRI private data (a different proof's Merkle paths) is rejected by the in-circuit verification, and a tampered public-input claim is rejected. Confirms the negatives in C/D/F/J/L are genuine rejections, not vacuous acceptances. - Probe M (probe_m_long_chain) added (50-layer IVC chain, fixed-point-holds-at-depth); runs slow, result folded into the memo separately. These validate recursion-mechanism robustness (multi-AIR, concurrency, soundness, depth) for a future re-evaluation; they do not change the NO-GO (cross-layer state threading is still unbuildable).
…ence) A recursion proof bincode-serializes to ~363 KB, round-trips byte-stable, and the deserialized proof still verifies; a truncated blob is rejected. Adds a verify_batch_proof helper. Relevant to Phase 6 proof-blob storage. Does not change the NO-GO.
…th-50, concurrency, soundness, serialization)
This was referenced Jun 7, 2026
Merged
TaprootFreak
added a commit
that referenced
this pull request
Jun 7, 2026
The Plonky3 recursion spike, its migration write-ups, and its benchmark results were merged to staging only (PRs #211/#212/#214) and must not be promoted to develop. Remove them here so the next staging -> develop auto-promote carries no Plonky3-migration artifacts. Everything removed is archived verbatim in zk-coins/research. - delete the plonky3-recursion-spike crate (36 files) - delete MIGRATION_PLONKY3.md / _SOLUTIONS_RESEARCH / _SPIKE_RESULT - delete docs/migration/PLONKY3_*.md (5 files) - delete scripts/bench/results/plonky3-*.md (5 files) - restore the workspace Cargo.toml to develop's form (drop the now-unused `exclude = ["spikes/plonky3-recursion-spike"]`)
TaprootFreak
added a commit
that referenced
this pull request
Jun 7, 2026
* docs: Plonky3 migration plan + Phase-0 GO (carrier-table direction) (#211) * docs: add Plonky3 migration plan with phased, executable task spec Adds MIGRATION_PLONKY3.md: a phase-by-phase, execution-ready work plan for the Plonky2 -> Plonky3 backend swap. Phase 0 is a hard recursion feasibility gate against p3-recursion (probed in Goldilocks to isolate the recursion/API risk from the field-migration risk). Subsequent phases port types/hash, Merkle gadgets, the state-transition circuit, recursion + aggregator, node integration, and parity/coverage/bench, each with exact files, acceptance criteria, and local verification commands. Field swap to KoalaBear/BabyBear is sequenced as a separate optional follow-up. Companion to ROADMAP.md and MIGRATION_RESEARCH.md. * docs: record Phase 0 result + defer cross-layer PI-threading choice to Phase-1-authorize The Phase 0 spike returned GO with one escalated finding: the high-level batch recursion does not propagate public inputs across layers (probe_d_multilayer_carry: air_public_targets = [0,0,0]), unlike Plonky2 cyclic recursion. This is a construction problem, not a p3-recursion capability gap, so it does not flip the gate to NO-GO. Record this in the Phase 0 gate section, and mark the cross-layer PI-threading construction as TBD — the choice between Option 1 (threaded outputs as AIR public values, fast), Option 2 (commit + Merkle/hash re-bind each layer, sound), and Option 3 (pinned probe catches a future upstream rev that propagates natively) is made at Phase-1-authorize time, not now. P5-T1 is written against the chosen option and its acceptance now requires the threaded prev_account value to be carried across transitions. * docs: resolve cross-layer threading to Option 2 + record Phase-5 budget risk Phase 0 closed the open threading question empirically, so the plan no longer defers it to Phase-1-authorize: - Cross-layer PI threading is RESOLVED to Option 2 (commit + hash/Merkle re-bind each layer). Option 1 (carry the value as an AIR public value) is dead — proven by probe_h_option1_air_public_values (injecting a non-existent public input is rejected) and probe_g_fanin_pi_passthrough (a real aggregation surfaces 0 per-leaf values to the outer); CircuitBuilder public inputs live in the committed Public table, never as AIR public values. Reflected in the §5 recorded result, the §6 Phase-1-authorize block (now "RESOLVED: Option 2"), P5-T1, and P5-T2 (per-leaf ProofData also needs Option-2 commit+re-bind, then §7.17 masking). - Record the Phase-5 warm-prove budget risk from probe_i_cost_projection: a recursion layer over a ~2^16-gate inner proof is ~3.2 s / ~1.4 GB — a single-layer lower bound on an arithmetic toy; the mandatory Option-2 re-bind adds Poseidon gates per layer, and the real constraints are Poseidon-heavy. Measure the real circuit + Option-2 at the START of Phase 5; a >5 s warm-prove is a NO-GO trigger. Option 3 (pinned probes catch a future upstream rev that restores native propagation) stays armed. * docs: gate is NO-GO — neither Option 1 nor Option 2 threads state across a batch layer Phase 0 closed the cross-layer threading question definitively: Option 1 (AIR public values) is dead (probe_h/g/d_multilayer_carry) and Option 2 (commit + hash re-bind) is also dead (probe_j + adversarial review — layer N+1 cannot read layer N's committed digest). There is no per-instance value channel across a batch-recursion layer, so zkCoins' prev_account/ProofData IVC carry is structurally unbuildable on this rev. Record NO-GO in the §5 result, the §6 Phase-1-authorize block ("DO NOT START Phase 1"; §6-§14 kept as a would-be plan for if an escape route opens), P5-T1 (BLOCKED), P5-T2 (BLOCKED), and the §10 budget note (moot under NO-GO). Escape routes: an upstream rev that exposes cross-layer public inputs (pinned probes auto-detect), a protocol redesign, or a fork (excluded by §16). Decision is the operator's per §16. * docs(plonky3): gate is GO via Path 1+5 (carrier tables) — overturn NO-GO, unblock Phases 4-5 Phase 0 reassessment: probe_q + probe_r_carrier_chain prove a custom AIR's public value (carrier table) threads state across a batch layer end-to-end (V_3==V_0+3, sound negatives, no fork). probe_r_cost: within ≤5s warm budget. Update P0-T6 memo block, §6 Phase-0 outcome, and P5-T1/P5-T2/budget-note to the carrier-table direction. Rationale: MIGRATION_PLONKY3_SOLUTIONS_RESEARCH.md; proof: PR #214. * feat(plonky3): Phase 0 recursion feasibility spike — gate is GO (carrier tables) (#212) * feat(plonky3): Phase 0 recursion feasibility spike — GO gate Add spikes/plonky3-recursion-spike, an isolated probe (its own workspace, excluded from the root workspace so the heavy Plonky3 git deps never enter the node/shared build or CI) that empirically proves Plonky3/Plonky3-recursion can express the three composition patterns the zkCoins circuit depends on, in Goldilocks, on the pinned revs: - Probe A (IVC/cyclic with base case): 4-layer chain; the verifier-circuit shape reaches a fixed point (true IVC, no growth) — the analogue of Plonky2 common_data_for_recursion. witness_count [25567, 104630, 107957, 107957]. - Probe B (fan-in-8, variable active count): 2-to-1 aggregation composes into a fixed-shape tree (fan-in-4 probed; fan-in-8 is one more level). No native conditional-verify primitive; inactive slots are padded with real proofs and masked downstream via an active bit. - Probe C (vk/PI binding): an inner proof's public inputs are bound in the verifier circuit; a mismatched claim is rejected in-circuit. Record P0-T5 cost (~4.65 s per stabilized layer, ~1 GB peak RSS) and the P0-T6 Go/No-Go memo (MIGRATION_PLONKY3_SPIKE_RESULT.md). Gate decision: GO. Pins: Plonky3-recursion 524665d0c2e1d294722c064786ae11dff8d9f33b, Plonky3 56952503e1401a62982ceaf952c5e4a829b61803. * docs(plonky3): tighten Phase 0 memo + probes to match §5 PASS criteria exactly Address logic-review findings — scope the spike's claims precisely against MIGRATION_PLONKY3.md §5 so the GO gate is not overstated: - Probe A: strengthen the fixed-point assertion (require the shape to have GROWN before stabilising, not just last-two-equal). Document that cross-layer counter PI THREADING (P0-T2 crit. 2) is not exercised — into_recursion_input carries empty table_public_inputs; the enabling primitive is proven in Probe C; explicit threading is Phase-5 work. - Probe B: relabel honestly — the probe proves 2-to-1 fan-in TREE COMPOSITION (4 identical real leaves), NOT variable active count / per-leaf PIs / active-bit masking. The masking strategy (§7.17) is Phase-5 construction on proven primitives. - Probe C: relabel as PUBLIC-INPUT binding (proven), with vk-equality connect-back as Phase-5 construction (the literal "wrong-vk proof" is not fed). Memo gate decision restated: the three mechanisms are proven; the three deferred items are in-repo Phase-5 construction, not upstream gaps. Decision stays GO. All 4 probes still green; fmt + clippy clean. * feat(plonky3): exercise the three §5 PASS items empirically (Probes D/E/F) The Phase-0 gate previously proved the three mechanisms but DEFERRED the three real §5 PASS items to Phase 5. This adds probes that exercise them end-to-end with real proving and positive+negative (+control) assertions: - Probe D (probe_d_pi_threading): cross-layer PI threading binding. An outer circuit verifying an inner uni-stark proof exposes air_public_targets and threads a value (next_start = inner.last + 1) bound to an outer public input; a wrong threaded value is rejected; a control without the bind accepts it. - Probe D part 2 (probe_d_multilayer_carry): the escalated finding. Verifying an inner BATCH proof of a CircuitBuilder circuit exposes NO inner public inputs (air_public_targets = [0,0,0]); the high-level chain does not propagate public inputs across layers (differs from Plonky2 cyclic recursion). Pinned by assert. - Probe E (probe_e_active_masking): variable-active-count masking (§7.17). An 8-slot fixed-shape consumer circuit, batch-stark-proved for real: active+correct with inactive-garbage accepted (masked); active-wrong rejected; flipping a garbage slot's active bit flips the verdict; flipping back re-masks. - Probe F (probe_f_vk_binding): vk-equality connect-back. Two ConstPrepAir instances (k=42/99) have different preprocessed commitments (= vks). A proof from vk_99 (internally valid against vk_99) is rejected SOLELY by the connect to vk_42; a control accepts it unbound. Rewrite MIGRATION_PLONKY3_SPIKE_RESULT.md: the §5 items are now exercised (not deferred); the gate is GO with ONE escalated finding (cross-layer public-input propagation), surfaced for operator judgment per §16. All 8 probes green; fmt + clippy clean. * docs(plonky3): mark probe_a row as P0-T2 crit. 1 (not PI threading) * feat(plonky3): close the cross-layer/cost gaps before Phase 1 (Probes G/H/I) Three integrated probes that resolve the previously-escalated open questions into hard, pre-Phase-1 constraints: - Probe H (probe_h_option1_air_public_values): Option 1 (carry the threaded value as an AIR public value) is DEAD. The honest empty-PI layer builds+proves; injecting a non-empty RecursionInput::BatchStark.table_public_inputs is rejected. Combined with probe_d_multilayer_carry (air_public_targets = [0,0,0]), both Option-1 avenues fail. - Probe G (probe_g_fanin_pi_passthrough): a real 2-to-1 aggregation's per-leaf values are NOT surfaced to the outer (air_public_targets = 0). The integrated fan-in-8 per-leaf-PI passthrough is blocked at the first cross-layer hop. => Together G+H decide the Phase-1-authorize choice: Option 2 (commit + hash re-bind) is MANDATORY for IVC threading AND aggregator per-leaf surfacing. - Probe I (probe_i_cost_projection): recursion-layer cost at real inner-proof scale. Sub-linear scaling; a layer over a ~2^16-gate proof is ~3.2s / ~1.4GB. Combined with the mandatory Option-2 overhead and the base prove, the 5s warm budget is at material risk -> measure on the real circuit early in Phase 5. Rewrite MIGRATION_PLONKY3_SPIKE_RESULT.md: status is now CONDITIONAL GO (Option 2 mandatory; warm-prove budget at material risk). The pinned probes (G/H/multilayer_carry) catch a future upstream rev that restores native public-input propagation. All 11 probes green; fmt + clippy clean. * docs(plonky3): scope Option-1 'impossible' to 'not achievable on this rev' * feat(plonky3): Probe J — Option 2 commit+rebind primitive works, but cannot compose The in-circuit Poseidon2 hash-bind primitive (add_hash_slice + connect) is real and binding: hash(V) binds to hash(V); a mismatched preimage is rejected. So Option 2's per-layer commit+rebind building block is expressible. But it needs layer N+1 to read layer N's committed digest, which is structurally impossible across a batch layer (probe_d_multilayer_carry / probe_g / probe_h: no per-instance value exposed, only whole-trace Merkle commitments). So multi-layer Option-2 threading is NOT achievable — confirming both Option 1 and Option 2 are dead and the cross-layer state IVC is unbuildable on this rev. * docs(plonky3): gate is NO-GO — Option 2 cannot compose across batch layers (Probe J) Probe J + an adversarial review of all six escape routes confirm that neither Option 1 nor Option 2 can thread a value across a batch-recursion layer. The per-layer commit+rebind primitive works (in-circuit Poseidon2 hash-bind), but layer N+1 cannot read layer N's committed digest (batch proofs expose only whole-trace Merkle commitments; FRI openings are FS-random; vk is per-circuit-static; NPO public_values are hardcoded empty with no public registration path). So zkCoins' cross-layer state IVC is structurally unbuildable on this rev. Memo status: CONDITIONAL GO -> NO-GO, with escape routes (upstream feature / protocol redesign / fork-excluded). * feat(plonky3): Probes L/N/O — multi-AIR coexistence, concurrency, soundness - Probe L (probe_l_multi_air): two heterogeneous AIRs (CounterAir state-transition-like + ConstPrepAir aggregator-like) co-verify in one verifier circuit, public inputs kept distinct + individually bound; cross-wiring A's PI to B's value is rejected. - Probe N (probe_n_concurrent): 4 independent prove+recurse+verify workloads on separate threads all succeed; peak RSS ~1.38 GB. Prover is concurrency-safe. - Probe O (probe_o_soundness): soundness spot-check — mismatched FRI private data (a different proof's Merkle paths) is rejected by the in-circuit verification, and a tampered public-input claim is rejected. Confirms the negatives in C/D/F/J/L are genuine rejections, not vacuous acceptances. - Probe M (probe_m_long_chain) added (50-layer IVC chain, fixed-point-holds-at-depth); runs slow, result folded into the memo separately. These validate recursion-mechanism robustness (multi-AIR, concurrency, soundness, depth) for a future re-evaluation; they do not change the NO-GO (cross-layer state threading is still unbuildable). * feat(plonky3): Probe P — proof serialization round-trip (node persistence) A recursion proof bincode-serializes to ~363 KB, round-trips byte-stable, and the deserialized proof still verifies; a truncated blob is rejected. Adds a verify_batch_proof helper. Relevant to Phase 6 proof-blob storage. Does not change the NO-GO. * docs(plonky3): record mechanism-robustness probes L-P (multi-AIR, depth-50, concurrency, soundness, serialization) * feat(plonky3): carrier-table IVC (Path 1+5) + full migration audit + recursion-reduction — GO, /api/send recovers w/o UX regression, port HOLD (#214) * feat(plonky3): Probe Q — a custom AIR's public value DOES cross a batch layer (overturns NO-GO) Replicates upstream test_batch_verifier_with_public_values (PR #407, in our pinned rev) in our crate: a custom PublicValueAir (num_public_values=1) proved with prove_batch and verified in-circuit via verify_batch_circuit surfaces its public value as a non-empty air_public_target (NOT [0,0,0]) and binds it soundly across the batch layer — correct value accepted, wrong value rejected. This overturns the scoped NO-GO: the [0,0,0] finding (probes D/G/H) held only for the PRIMITIVE tables and CircuitBuilder public inputs (which route to the committed Public table). A public-value-emitting AIR provides exactly the per-instance cross-layer value channel the IVC needs. The full IVC chaining via a custom carrier table is a public-API construction (~400-650 LOC), not an impossibility. * docs(plonky3): solution-space research — NO-GO overturned, 9 paths assessed Probe Q empirically overturns the scoped NO-GO: a custom AIR's public value DOES cross a batch-recursion layer (PR #407, in our pinned rev). Enumerate + assess all 9 solution paths with links/repo-pointers: (1+5) Plonky3 + custom public-value-emitting tables — viable, channel proven, IVC chaining is a public-API construction; (3) folding/Sonobe — native IVC, strong alternative; (2) self-authored upstream PR; (4) hybrid; (6) protocol redesign via off-circuit continuity (trusted node, §7.22 posture); (7) zkVMs; (8) fork (excluded §16); (9) Stwo/Triton/Halo2-accumulation. Recommend Path 1+5 behind a carrier-table IVC-chain spike (Probe R), with Sonobe benchmarked in parallel. * docs(plonky3): address review — add ProtoStar/Boojum/Lasso, gate-memo forward-pointer - Solutions doc: add ProtoStar/ProtoGalaxy + SuperNova (folding sub-schemes), Lasso (a component, not an IVC framework), and Boojum (Goldilocks STARK, EraVM-specific) — the three systems the brief named that were missing. - MIGRATION_PLONKY3_SPIKE_RESULT.md: add a top-of-file PARTIALLY-SUPERSEDED banner and correct escape-route #1 (the cross-layer capability was present all along via PR #407, not a missing upstream feature) — so a reader landing on the gate memo is pointed to the overturning result. - probe_q: simplify the self-referential air_public_targets shape assertion. * feat(plonky3): Probe R — carrier-table IVC chain threads a counter across 4 layers (GO) A real depth-4 IVC chain: each layer is a prove_batch proof of a custom CarrierAir with two public values [v_in, v_out] (AIR enforces v_out == v_in + 1, both bound to committed trace cells); each IVC link verifies both adjacent carrier proofs in-circuit via verify_batch_circuit (their public values surface as non-empty air_public_targets, not [0,0,0]) and connects prev.v_out == cur.v_in. The counter is provably carried layer-0 -> layer-3 (V_3 == V_0 + 3). Negatives: a wrong forwarded value is rejected (WitnessConflict on the thread bind; a control with the bind removed accepts it, isolating the cause); a carrier claiming an uncommitted public value is rejected (OodEvaluationMismatch). Does NOT use build_and_prove_next_layer, so upstream #436 is not hit. Public API only, no fork. This is the end-to-end empirical confirmation of Path 1+5: the cross-layer state IVC the original NO-GO deemed impossible is buildable via custom public-value-emitting tables. * docs(plonky3): gate memo banner — GO via Path 1+5 (carrier tables), Probe R confirms end-to-end * test(plonky3): Probe R-cost — carrier chain per-transition cost at 2^16 inner scale (within budget) * docs(plonky3): record Probe R-cost in gate memo — carrier chain within warm budget, add probe_q/r/r_cost rows * docs(plonky3): review polish — gate probe_r_cost verdict on STARK-prove class (not witness-gen floor), tag superseded Gate-decision heading * test(spike): add Probe S fair BabyBear vs Plonky2 prover bench * docs(spike): review polish — honest S-box degree-3-vs-7 magnitude (~1.5-2.5x, verdict robust), bump test count 20->21 + Probe S table row * test(spike): add Probe V degree-7 S-box bench on working HidingFriPcs recipe * test(spike): add Probe W real HidingFriPcs vs blowup-2 zk-proxy delta * docs(plonky3): add cutover playbook (Doc 1) + upstream maintenance plan (Doc 4) * docs(plonky3): correct Probe S optimism with Probe V/W — degree-7 1.67x + true-hiding 3x (~5x combined), production config slower at 2^16, net verdict pending Probe T * test(spike): add Probe T real-circuit Plonky3 prove-cost estimate Cost-faithful representative workload for the real zkCoins state-transition circuit under TRUE production crypto (degree-7 Poseidon2 + Keccak-hiding MMCS + HidingFriPcs, num_random_codewords=4). Models the real cost drivers (~4500 Poseidon2 hashes + ~50k non-hash gates) as a two-table batch, NOT the business logic. Sweeps the non-hash table height over 2^13..2^16 to bracket the unknown real layout. Finding: real multi-table prove_batch (p3-batch-stark) WORKS with HidingFriPcs + mixed degree-7/degree-3 instances; verify_batch succeeds. At the realistic layout (~2^13-2^14) Plonky3+BabyBear proves in ~312-449 ms warm p50 vs Plonky2 4350 ms = ~10-14x faster, ~2-3x lower RSS, near-zero circuit build (0.07 ms vs 8.2 s). Faster across the entire sweep including the 2^16 ceiling. * docs(plonky3): add wire/storage format migration (Doc 2) + carrier-table crypto-audit spec (Doc 3) * docs(plonky3): integrate Probe T — real circuit 10-14x faster under production crypto; V/W 2^16 was hash-saturation; full-prove verdict pending X+U * test(spike): add Probes X (aggregator recursion overhead), Y (cold-start), Z (verifier), AA (sustained-load soak) * docs(plonky3): Probe U e2e projection + integrate X/Y/Z/AA net verdict — send is wash/slower (recursion-dominated), mint ~2x, cold-start 38.7x, no leak * docs(plonky3): full migration audit summary — honest mixed verdict, decisive X-prime lever, operator decisions * docs(plonky3): redact internal host names from cutover playbook — role language only (review blocker) * test(spike): Probe X' batched-aggregator lever — same-vk verifier amortization Measure whether batching the 8 same-vk source proofs cuts the flat 8+1 aggregator cost Probe X reported (4.0s non-zk / 6.7s zk). Two framings, real STARK-prove via prove_all_tables: X'-a proves the 8 sources as one multi-instance BatchProof verified in-circuit once (lower bound: 0.98s non-zk / 1.66s zk, 4.1x reduction); X'-b proves 8 independent same-vk proofs as in the real protocol (3.97s non-zk / 6.69s zk, ~1.0x = flat). The recursion API verifies one BatchProof per verify_batch_circuit, so independent same-vk proofs cannot share the verifier — the batched floor is unreachable for /api/send. Realistic full send recomposes to 9.9s non-zk / 12.6s zk, a wash-or-loss vs Plonky2. Batching does not rescue the send case; MAX_IN_COINS reduction is the lever. * docs(plonky3): resolve batching lever via Probe X-prime — not reachable in-protocol, send case rests on MAX_IN_COINS; test count 29 * docs(plonky3): update Fair-Performance lead to the resolved mixed verdict (T/X/X-prime/U) * test(spike): Probe AB recursion-friendly levers — cheaper-inner-FRI 2.4x (64-bit), Poseidon2-MMCS already baseline, ZK-only-outer ~0 * test(spike): Probe AC MAX_IN_COINS sweep — aggregation ~linear in fan-in (~448ms/coin); N=4+cheaper-FRI cuts prove ~4x; e2e capped by node overhead * test(spike): Probe AD KoalaBear-vs-BabyBear field comparison — split verdict; KoalaBear transition ~1.26x faster (degree-3 leaf S-box) but dominant 8+1 aggregation ~2.1x SLOWER (20 vs 13 partial rounds in recursion verifier); recommend STAY on BabyBear * test(spike): probe AE — composed best-config full send-prove measurement * docs(plonky3): recursion-reduction research (AB-AE) — send speed case recoverable: MAX_IN_COINS=4 alone 1.9x, +64-bit inner FRI 3.32x; KoalaBear ruled out; 33 tests * docs(plonky3): apply resolutions — keep MAX_IN_COINS=8 (no UX regression), 64-bit inner FRI as port-phase auditor gate, port HOLD; recommended N=8+q48 = 2.25x send-prove * chore: remove Plonky3 migration content from staging (#217) The Plonky3 recursion spike, its migration write-ups, and its benchmark results were merged to staging only (PRs #211/#212/#214) and must not be promoted to develop. Remove them here so the next staging -> develop auto-promote carries no Plonky3-migration artifacts. Everything removed is archived verbatim in zk-coins/research. - delete the plonky3-recursion-spike crate (36 files) - delete MIGRATION_PLONKY3.md / _SOLUTIONS_RESEARCH / _SPIKE_RESULT - delete docs/migration/PLONKY3_*.md (5 files) - delete scripts/bench/results/plonky3-*.md (5 files) - restore the workspace Cargo.toml to develop's form (drop the now-unused `exclude = ["spikes/plonky3-recursion-spike"]`) * feat(api): GET /api/history/{id} — per-transaction detail endpoint (TxDetail) (#218) The wallet's transaction-detail page needs more than the lean /api/history list row. Add a scoped detail endpoint that returns everything the node can derive for one account_history row without a schema change: - All HistoryItem core fields (txid/timestamp/direction/amount/status/ block_height/...), via the same history_row_to_item mapping so the two endpoints cannot drift. - The decoded account-state snapshot of the mutation: usable balance before/after (settled + coin_queue, mirroring balance_from_account_blob), the post-mutation num_sends (the wallet's authoritative BIP-32 child index), and the commitment public key (33-byte compressed hex). - The verifier circuit digest (proof-system identity) from circuit_digest_meta; a read failure degrades the field to null. - pending_inscriptions.commit_output_value when an inscription row exists (detail-only; the list query stays lean). Scoping: the row must match (id, address) AND have a user-facing source (mint/send/receive) — wrong-address or internal rows 404 identically, so ids cannot be enumerated across accounts. Malformed address or a non-integer/non-positive id is 422 (id parsed from the path as a string so the read surface keeps one validation contract; axum 0.7 would otherwise 400). Tests: handler-level unit tests for every branch (422 x5, 404 x2, 500 x2 incl corrupt-blob, 200 happy + digest), db-level tests for the scoped item query incl the inscription join, pure-fn tests for the decoders, api_remote live round-trip (mint -> list -> detail) + validation contract, openapi smoke (path + TxDetail schema). * chore: move benchmark output to research (#219) Per the project model the node repo carries code/build/standard files only - no benchmark output. Delete scripts/bench/results/ (README + m5-max HTTP-mint-sweep CSV, probe_r2 JSON, m5-max-vs-m3-ultra write-up). The bench harness (node/src/bin/probe_r2.rs) stays; only the output moves. Archived verbatim in zk-coins/research benchmarks/node-runtime/. --------- Co-authored-by: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Executes Phase 0 of the Plonky2 → Plonky3 migration (#211,
MIGRATION_PLONKY3.md§5) — the hard recursion-feasibility gate. An isolated throwaway spike crate (real proving + pos/neg assertions) exhaustively validates the recursion model on the pinned revs.🟢 Gate result: GO via Path 1+5 (carrier tables)
The cross-layer state channel — zkCoins'
prev_account/ProofData carry — is buildable on thisPlonky3-recursionrev via carrier tables: a custom AIR withnum_public_values() > 0exposes a soundly-bound per-instance value across a batch-recursion layer (upstream PR #407, in our pinned rev). Proven end-to-end in #214 (probe_q+probe_r_carrier_chain: depth-4 IVC chain threadingV_3 == V_0+3, sound negatives, no fork).What this PR's probes show
p3-recursionis a layered batch-stark model. Primitive tables and rawCircuitBuilderpublic inputs do NOT carry state across a layer — these probes pin that, and they remain as regression guards so the port routes state through carrier tables, not raw public inputs:probe_d_multilayer_carry(air_public_targets = [0,0,0]),probe_h_option1_air_public_values(injectingtable_public_inputsrejected),probe_g_fanin_pi_passthrough(stock aggregation surfaces 0 per-leaf values).probe_j_option2_rebind: the in-circuit Poseidon2 hash-bind primitive works (but a stock layer can't read a predecessor's committed digest — which is exactly why the carrier-table public-value channel, not a digest re-read, is the right construction).Probes (17 here; +3 in #214 = 20 total)
Binding primitives (proven in isolation):
probe_a/b/c/d_pi_threading/e/f— IVC fixed point, fan-in tree, PI binding, single-hop threading, §7.17 active-bit masking, vk-equality connect-back.Primitive-table guards (pinned
= 0):probe_d_multilayer_carry,probe_h,probe_g,probe_j.Cost:
probe_i(≈3.2 s/layer at 2^16-gate scale).Robustness:
probe_l(multi-AIR),probe_m(depth-50 fixed point, flat 1.4 GB),probe_n(concurrency),probe_o(soundness — verifier non-vacuous),probe_p(proof serialization, ~363 KB byte-stable).Overturning + chosen direction (in #214):
probe_q_custom_public_value,probe_r_carrier_chain,probe_r_cost.Pins
Plonky3/Plonky3-recursion@524665d0c2e1d294722c064786ae11dff8d9f33bPlonky3/Plonky3@56952503e1401a62982ceaf952c5e4a829b61803Notes
node/sharedCI;fmt+clippy -D warningsclean.