riverrun, the anonymity layer for Solana, post-quantum - #2
Open
Galmanus wants to merge 177 commits into
Open
Conversation
…ons, not funds) mirror-pool as "Tornado Cash for behavioral patterns and actions": commit an intent, later execute it unlinkably with a membership proof + per-round nullifier, so the actor↔action link is severed. Amounts are not the point; behavior is. Contents: - mirror-core: post-quantum, transparent primitives (hash commitment, Merkle anonymity set, per-round nullifier, membership relation) and BehaviorPool (commit -> execute -> settle). 27 tests + runnable demo. - mirror-eval: adversarial harness for the behavioral channel; a co-buy-timing / position-sizing clustering attacker collapses to chance (1/k) against a synchronized round. - mirror-trace: the provenance-tracer, an open de-anonymizer that walks the funding graph backward to labeled roots — the leak noise tools leave open. Proven on live mainnet (real wallet -> hub in 2 hops) and paired with a cyclic-provenance defense that drives attribution to chance or dissolves the origin (circularity + exchangeability). - programs/mirror-pool: on-chain Solana program (commitment accumulator, per-round nullifier PDA anti-replay, action settlement). Builds to a deployable .so; LiteSVM e2e asserts the full lifecycle incl. double-spend and stale-round rejection. 39 tests green (38 host + 1 on-chain e2e). MIT. STARK membership and the LWE/flow hardness anchors are documented as the research/paper track. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… framing The protocol is named "riverrun" — the first word of Joyce's Finnegans Wake, the river that flows in a circle back to its own beginning, which is exactly the provenance defense (a funding cycle with no origin). The bounty repo stays `mirror-pool`. - crates renamed: riverrun-core / riverrun-eval / riverrun-trace / riverrun-stark; on-chain program crate → riverrun-program (.so → riverrun_program.so). - domain-separation tags and user-facing strings rebranded to riverrun. - README now leads with a plain-language section on what a real person can do with this and how it empowers them (financial privacy for ordinary people, not just specialists) — the point the cryptography exists to deliver. All 39 tests still green (38 host + 1 on-chain e2e).
…eaper) Live devnet run (initialize -> commit -> execute, double-spend rejected on a real cluster). Program BFy2ehVxpBrtwMCWwufpfbbsoWtZVYVaZBzDE2eAG7az. Measured cost by wallet-balance delta: 0.002507 SOL for the full run; ~0.00102 SOL per action. Adds the devnet_demo example and a Cost section to the README, because hiding behavior (not funds) is what makes the on-chain footprint tiny.
A transparent, hash-based (Rescue-Prime + FRI) STARK proving anonymous set membership in zero knowledge — knowledge of a leaf preimage under the public Merkle root without revealing which leaf. No trusted setup, no ceremony; post-quantum by construction. Adapted from the Winterfell v0.13 `merkle` example (MIT), wrapped as a clean prove_membership / verify_membership API. - 2 tests green: a valid member proves+verifies; a wrong root is rejected. - Closes the "is it really post-quantum ZK?" gap at the core: the primitives were already hash-based (PQ), and now the zero-knowledge membership proof is a real working transparent STARK, not a placeholder. - Remaining (documented): fold commitment H(secret‖action) + nullifier into the same AIR, wire it into the BehaviorPool flow, and verify on-chain. README updated: STARK moved from research-track to implemented.
…y claim Internal adversarial audit (STRIDE + two red-team passes) found the shipped flow is not production-ready. Documented honestly rather than hidden: - README: new 'Security status' section naming the two critical gaps (reference proof carries the secret in cleartext; on-chain execute verifies no membership). - riverrun-stark: corrected the proof-options comment from a false '~128-bit' to the honest '~84-bit, not production strength'. - pool.rs: corrected the exchangeability test comment to state the shipped reference proof is NOT unlinkable on the wire (real hiding needs the STARK).
Galmanus
marked this pull request as ready for review
July 22, 2026 23:48
…g the witness from the wire Adds a high-level MembershipSet / verify_bytes API over the STARK: the membership proof is returned as opaque bytes that do NOT carry the secret preimage or the leaf index (unlike the reference proof, which serialized the secret verbatim). A test asserts the secret does not appear in the transmitted proof bytes. This is the first concrete step toward closing audit-critical solanabr#1. Still remaining (unchanged in the README Security status): wire this into the BehaviorPool flow (the pool still uses the reference proof) and bind the commitment + nullifier into the same AIR so one proof witnesses membership AND the nullifier.
…(audit-critical solanabr#1, confidentiality half) New crate riverrun-pool-zk: a behavioral pool where the Execution carries an opaque post-quantum STARK membership proof and public data only (root, round, nullifier) — never the witness. settle() verifies from public data alone; a test drops the secret before settling. 5 tests green (round-trip, secret-off-wire, double-spend rejection, per-round, tampered-proof rejection). Non-destructive: riverrun-core's reference-proof pool is untouched (still honestly labeled). Padding restricted to valid STARK tree widths (2/8/128/32768; trace length (depth+1)*8 must be a power of two). Honest remaining gap (documented in the crate): the nullifier is NOT yet bound inside the AIR (audit-critical #1c), so the proof does not witness that the revealed nullifier derives from the same secret that proved membership. Next.
…f the wire), nullifier-binding still open
…sability with Vineland (roadmap, not built)
… primitive Rather than gamble a soundness-critical AIR extension at the tail of a long session (a subtly-wrong constraint = false soundness, worse than the honest gap), this de-risks 1c and validates the crypto: - docs/1c-nullifier-binding-design.md: complete implementation blueprint — trace layout (width 9, nullifier cycle + Merkle cycles), every transition constraint and periodic mask, the load-bearing "start-tie" that binds the nullifier input to the membership value, boundary assertions, degree accounting, prover changes, and the SOUNDNESS tests that are the gate (wrong-nullifier and wrong-secret must be REJECTED — a passing happy path is not enough). - riverrun-stark: `nullifier(secret, round) = Rescue(v0,v1,round)` as a tested reference the in-circuit computation must match (deterministic, round-dependent, distinct from the leaf). 4 tests green. 1c implementation is now a careful mechanical exercise for a focused session, not research. Honest note in the doc: even when tests pass, a hand-rolled AIR needs independent review before any production soundness claim.
…ical #1c)
One STARK now witnesses both halves of the statement from a single secret:
membership under the public root, and n = Rescue(v0, v1, round) as a public
output. Public inputs are {root, nullifier, round}; the secret, leaf index and
path stay private. Pairing a valid membership proof with a nullifier of one's
choosing no longer verifies, so "one action per member per round" is enforced
cryptographically rather than by convention.
Trace width 9: the Rescue state and index bit as before, plus two carry columns
holding the secret across cycle 0. Row 0 ties the nullifier-hash input to the
carry (the soundness crux); row 7 loads the carry into the Merkle path.
Two things found while implementing, both recorded in the design doc:
- The carry cannot be constant across the trace. A constant column has a
constant low-degree extension, so every FRI opening returned the secret
verbatim: 20 leaked proofs out of 20, against 0 out of 20 for the unbound
prover. It is now held only across cycle 0 and zeroed after. 0 out of 20.
- The public-input negative tests pass even with no start-tie, since the
boundary assertions alone reject them. The real gate is a forged trace that
hashes one secret and carries another; removing the start-tie was checked to
make it fail, so the test has teeth.
The extra hash cycle changes the valid anonymity-set sizes to 4, 64 and 16384
(tree depths 2, 6, 14), since the trace length (depth+2)*8 must be a power of
two. riverrun-pool-zk switched over.
Still open: the AIR is hand-rolled and unaudited (passing negative tests are
necessary, not sufficient); Winterfell 0.13 has no witness randomization, so
this is "witness not verbatim on the wire", not formal zero-knowledge; and the
on-chain execute still verifies no membership (audit-critical solanabr#2).
Rewrites the comparative passages so they describe riverrun's tradeoffs instead of dismissing other approaches: pairing-based systems are mature and produce smaller proofs, hiding funds is a harder problem than hiding behavior, and the funding-graph leak is hard for every noise-based design including this one. Drops 'the cheapest anonymity on Solana' and 'a threat model that hides its assumptions is theater'. Updates Security status now that membership and the nullifier are one proof, and adds the two things that cost the most to learn: the constant-column leak (20 of 20 proofs, since a constant column has a constant low-degree extension) and the fact that the public-input negative tests pass even with the binding constraint deleted. Test count 41 -> 57, all re-run.
…it-critical solanabr#2) Before this, on-chain `execute` checked only the round and created the nullifier PDA. Any signer could settle an action for a member they were not, and a watcher could lift a nullifier out of the mempool and spend it first. The pool now names a verifier key. An execution must carry that key's Ed25519 signature over exactly the (pool, root, action, nullifier, round) tuple being settled, checked through the native sigverify precompile plus instruction introspection, and the root must be the one the authority published for the round. Neither an arbitrary signer nor a front-runner can produce that signature over their own tuple. This is a named trust assumption, not soundness: a dishonest verifier can attest to a proof that does not exist. The README says so. What ruled out real on-chain verification is size, not compute: a riverrun STARK is 12,057 bytes for a 4-leaf set and 16,536 for a 64-leaf one, against Solana's 1232-byte transaction limit. Verifying on-chain means chunk-uploading into a ~16 KB account at ~0.115 SOL of rent, against ~0.001 SOL for an entire action today, before any FRI compute. The check refuses anything but the self-contained sigverify layout: the Ed25519 instruction declares where the key and message come from, and accepting a declared source that points at another instruction is the classic break in this pattern - the precompile would verify one message while the program reads another. There is a test for it, and deleting the index check was confirmed to make that test fail. Deleting the whole attestation call fails four. 8 e2e tests, all against the compiled .so under LiteSVM (which does run the sigverify precompile - a tampered signature is rejected by the runtime before the program is entered, which the rest of the tests depend on). Also: renames Pool.merkle_root to accumulator, since it never was the canonical Merkle root; adds publish_root and set_verifier; updates the devnet example. The devnet deployment predates this change.
The ZK path proved membership and the nullifier, but not *which intent* the
member had registered: the leaf was Rescue(secret) and Execution had no action
field at all. Anyone in the set could execute anything, which is the opposite of
what 'Tornado for behaviour' means - the whole thesis is that you commit an
intent and later execute that intent unlinkably.
The leaf is now Rescue(secret, action) and the action is a public input of the
same proof, pinned by the AIR as a constant when the Merkle leaf preimage is
loaded at row 7. Public inputs: {root, nullifier, round, action}. One secret has
to satisfy all of it at once. riverrun-pool-zk carries the action through commit,
prove_execution and settle.
The tests took two attempts and the first pair was worthless. Announcing a
different public action is rejected even with the constraint deleted, because
the action feeds the Fiat-Shamir transcript; and 'prove an action you did not
commit' fails while building the trace, so it passed vacuously. Both survived
the mutation, which is how they were caught.
The real attack hashes the committed action into the leaf, so the Merkle path
still resolves, while announcing the action the member wants to execute. That
needed a prover seam to separate what is hashed from what is declared. With the
constraint deleted, that forged proof verifies; with it, nothing usable comes
out.
68 tests green: 38 host + 15 STARK + 7 pool-zk + 8 on-chain e2e.
riverrun-core shipped a second BehaviorPool backed by a 'reference proof' that carried the witness in the clear. It existed to exercise commit/execute/settle before the STARK was written, and the README had to keep explaining which of the two pools was the safe one. Shipping a non-hiding pool next to a hiding one in a privacy repo is a footgun no matter how loud the caveat. Gone: pool.rs, ReferenceProof/ReferenceProver/ReferenceVerifier and the Prover/Verifier traits. riverrun-core keeps what it is good at - the primitives and check_relation, the specification of what the AIR must enforce, with nothing in it that produces a proof. The pool is riverrun-pool-zk. The demo moved with it, and got better: the second guard used to compare an action field, and is now the STARK refusing to verify, because the leaf is Rescue(secret, action) and the action is a public input. Measured proof sizes are in the output (11-12 KB per execution at k=4). 60 tests green: 30 host + 15 STARK + 7 pool-zk + 8 on-chain e2e.
…er provenance Every pool in this space reports 1/k. That counts members. It says nothing about where those members' money came from, and the funding graph is public. If an adversary can sort the set into provenance classes, learning the actor's class leaves only that class to guess within. Adds effective_k() to the library: residual anonymity as the class size averaged over which class the actor came from, 2^(sum_c (n_c/n) log2 n_c). One class holding everyone returns the full set - nothing was partitioned. All-singleton classes return 1 - every member individually identified. The direction matters and I got it backwards first. Measuring how concentrated the classes are (min-entropy over the class distribution) reports a catastrophic effective k precisely when the measurement found nothing to partition on: a single class of 7 came out as 'effective k = 1' when the honest reading is 'effective k = 7, we found no roots'. Four unit tests pin the direction, and one of them caught my arithmetic a second time. Adds pool-provenance: enumerate the real depositors of a live pool program on mainnet, trace each one's funding graph backward, and report the population statistics. It reads inner instructions, without which a pool deposit is invisible - a deposit is a CPI, so an analyst reading only the message sees an empty graph and concludes there is nothing to trace. The same effective_k now also runs on the synthetic exhibit, so riverrun is measured with the ruler it is proposing rather than only other people's pools: the same 2000 members are worth 500 under the field's rooted-decoy construction and 2000 under circularity. Also: shared RPC module, onchain-trace deduplicated from 307 to 164 lines, and clippy clean with and without the onchain feature.
… a prototype Adds docs/EFFECTIVE_K.md and leads the README with the finding: a live Tornado-style SOL privacy pool on Solana mainnet, 30 real depositors sampled, 11 of them (37%) reaching an attributable origin in a mean 1.18 hops. Advertised k of 30, effective k of 6.5, worst case 1 - one depositor sits alone in their provenance class and has no anonymity at all, while the advertised number says 1-in-30. Stated as what it is: not a flaw in that pool, because no deposit-pool design controls where its users' money came from - which is exactly why the channel goes unmeasured and keeps working. It applies to riverrun too, so the same function runs over riverrun's own constructions: the same 2000 members are worth 500 under the construction the field ships and 2000 under circularity. Also splits the security status in two, because collapsing them was dishonest in both directions. The measurement tooling is finished and runs today against mainnet on any pool program, including the other repos in this bounty when they ship theirs; it does not depend on riverrun's cryptography. The pool is the research half - hand-rolled AIR, unaudited, one gap that is a trust assumption rather than a proof. Previously the whole repo was labelled a prototype, which was unfair to the half of it that is a tool. Fixes the stale mirror-core/mirror-eval/mirror-trace crate names in the run commands, which no longer existed.
…e below the floor Permissionless commit means k is buyable: an attacker who creates members for free owns the anonymity set, and every 1/k number in the README is theirs to set. commit now charges entry_fee lamports into the pool, and execute refuses to settle while member_count is below k_min - a set of one is not an anonymity set, the action belongs to that member by elimination. The honest word is priced, not prevented. Inflating k to k+m now costs m·fee instead of m·0. An attacker with money still buys k. What makes this more than a fee: sybils have to be funded, and funding is exactly what pool-provenance reads. The live scan already reports shared-funder collisions across a real depositor population, which is the cheap version of this attack showing up in the measurement rather than in a threat model. Adds set_entry_fee (authority-gated) and AnonymitySetTooSmall. Pool grew two fields, so the account offsets the tests and the devnet example read moved with them. 10 e2e tests green.
./demo.sh walks the suites, the mechanism, both adversaries, the effective-k ruler and - when the SBF toolchain is present - builds the on-chain program and runs its e2e tests against the compiled .so. ./demo.sh --live adds the mainnet measurement. The Anchor derives emit a wall of cfg warnings that buried the signal, and the workspace test run printed nine 'running 0 tests' blocks for crates with no tests in that target. Both are summarised now: a reader gets three lines of counts instead of sixty lines of noise, and a failure still prints the failing test. The brief asks for tools people will genuinely run. A tool nobody can start is not one.
… set
A member's leaf never changes. Per-round nullifiers unlink one execution from
another; nothing unlinks a member from their own history. Whoever learns your
leaf once links you across every round you ever acted in, and a set that only
grows is a public record of who joined when. Neither was named in the README.
Vico's cycle ends each turn with the ricorso, the return that starts it over,
and that is the skeleton Finnegans Wake is built on. riverrun already borrows
the book's circularity for the funding graph. This is the other half: a member
holds a fresh leaf each cycle, Rescue(cycle_secret(v, c), action), and proves it
descends from some leaf under the previous root while spending a migration
nullifier so one seat cannot become several.
Ships cycle_secret, cycle_leaf, migration_nullifier and check_migration with 8
tests. Every one of the relation's four checks was verified by deleting it and
watching a test fail - and that is how the fourth was found, because deleting
the nullifier check left every test green. A member could have migrated under
their own leaf while burning another member's rebirth.
DOM_CYCLE and DOM_MIGRATE are separate constants with a test pinning it: sharing
a domain would make publishing a migration nullifier hand out that member's next
cycle secret, and with it their next leaf.
The STARK is specified in docs/RICORSO.md and not built. It needs five hash
cycles before the Merkle path, which moves valid set sizes to {8, 2048} while
the execution AIR needs {4, 64, 16384} - they do not intersect, so shipping it
also means padding the execution AIR to realign. That is a day on the most
delicate code in the repo, four days from a deadline, with the execution path
green. The honest place to stop is at the specification, which is where 1c
stopped before it was built.
…as been done This repo said verifying the STARK on-chain does not fit on Solana, and used that to justify the trusted-verifier attestation. The claim was wrong. - eprint 2025/1741, 'Full L1 On-Chain ZK-STARK+PQC Verification on Solana', verifies a Winterfell 0.12 STARK on L1: mean 1.10M CU, max 1.19M over n=100 on devnet, for a 4,437-byte proof, by routing SHA-256 through the hashv syscall, suppressing inlining in the FRI hotspots for SBF stack limits, and using a custom bump allocator. Winterfell is the library this repo uses. - murkl ships a Circle STARK verifier as a general-purpose CPI target at ~31k CU over M31/QM31, 128-bit post-quantum. - mosaic verifies FRI-STARKs chunked across transactions with a resumable verifier and a checkpoint state machine, validated on SBF. What is actually true is narrower and less flattering: the blocker is this repo's own choices. Our proof is 12-16 KB against a 1232-byte transaction limit because the AIR is a Rescue-Prime Merkle path over the 128-bit field rather than a minimal AIR over a 31-bit one, and 249 CU per proof byte on a 16 KB proof is not the same problem as on a 4.4 KB one. The path forward is a smaller field and a cheaper hash, and it needs no trusted setup. The attestation stays for now - it is still the honest description of what the program enforces today - but it is no longer justified by an impossibility that does not exist. Also adds the prior art for the effective-k metric to docs/EFFECTIVE_K.md: it is Serjantov and Danezis's effective anonymity set size (PET 2002), conditioned on a provenance partition, and the advertised-vs-true-anonymity programme is established on Ethereum (Tutela, Beres et al., the 2025 cross-chain study linking 20-35% of Tornado withdrawals). None of it has been run on Solana, and all of its heuristics are behavioural rather than funding-graph.
Adds a comparison table covering Groth16 on Solana, Circle STARK verification (murkl), MPC/FHE (Arcium, Umbra) and Token-2022 confidential transfers, with what each hides, whether it verifies on-chain, and whether it needs a ceremony. Two things it makes plain rather than implies. riverrun is the only row whose payload is behaviour rather than value, which is what the brief asks for, and it needs no ceremony. It is also the only row that does not verify its proof on-chain, and now that the impossibility claim is gone, that is a deficit rather than a preference.
…behind A submission that does not know its own field is one a reviewer stops trusting, so this states plainly what riverrun borrows and what it does differently. Borrowed: the goal (anonymous authentication and session unlinkability - PrivDID, anonymous credentials with predicate proofs), the primitive (PLUME formalises deterministic nullifiers as exactly the role ours plays), the metric (Serjantov and Danezis's effective anonymity set size, PET 2002), and the programme of measuring advertised-versus-true anonymity, which is established on Ethereum and has never been run on Solana. Where riverrun is behind, named rather than dressed up: anti-Sybil here prices a seat, while Anonymous Self-Credentials (eprint 2025/618) gets Sybil resistance cryptographically - one nullifier per verifier, one pseudonym per identity, enforced. That is the right fix and it is not implemented. Narrowly different: the payload is behaviour rather than value, the funding graph is measured rather than assumed away, and every 'X is bound' claim has a test verified to fail without its constraint - three of which turned out to be decorative and are named in the log.
I wrote that nobody measures the funding graph. Inside this bounty that is false. supersonic-tx PR solanabr#4 measures funding provenance on real mainnet data as a residual adversary advantage of +0.27 to +0.51 for durable P2P payees, alongside the finding that 63.4% of real mainnet transfer destinations already have on-chain history at n=1181, and leaves it honestly open. The two measurements are complementary and the difference is worth stating precisely. Theirs asks how much better than chance an adversary picks the real leg out of a decoy bundle given provenance - an advantage, against deployed selection code. This asks what a live pool's whole depositor population is worth in members once provenance partitions it - an effective set size, against a deployed pool. Neither subsumes the other. A submission that overstates its own novelty is one a reviewer stops trusting on everything else, including the parts that are true.
An anonymity set of 16,384 members proves in 8.1 ms and verifies in 0.43 ms, with a 19 KB proof and zero bytes of setup artifacts. Proof size grows logarithmically: 4096x the members costs 1.6x the proof. The column that decides whether any of this is deployable is the last one. A pairing-based prover needs a proving key produced by a ceremony and shipped to every client - for a Merkle circuit of this depth, tens of megabytes that must exist, be trusted and be distributed before anyone proves anything. riverrun's prover is the code. Efficiency cuts both ways and the comparison table now says which way. On verification compute a small-field STARK is the cheapest option available - murkl verifies at ~31k CU against ~250k for Groth16. On proof size Groth16 wins by two orders of magnitude, which is what buys it single-transaction verification. On prover cost and setup riverrun wins outright. Runnable as an example and wired into demo.sh, because a benchmark nobody can re-run is a claim.
riverrun's thesis is a synchronized round - k members doing the same action at the same time - and that is exactly the structure that batches. Proving each member separately costs k proofs and k verifications: 64 members is 1.07 MB of proof and, for a pairing-based verifier on-chain, 64 verifications of ~250k CU. Laying the k sub-traces end to end in one trace costs one proof and one verification. Measured: 4 members 19,772 B vs 45,300 separate (2.3x); 64 members 43,684 B vs 1,074,169 (24.6x). One verification instead of k. The batched AIR is the bound AIR per sub-trace with three generalisations: the one-shot masks (start, null_ins, carry_hold) get period L instead of trace length, so they fire once per member; a seam mask switches off the Merkle-insertion and load constraints at row L-1 so one member's last row does not carry into the next member's first; and the assertions repeat per member. Deleting the seam was checked to fail all four round tests. Two honest limits, both in the module docs. The prover holds every member's secret, so for a crowd of independent users this is a custodian and needs distributed proving; it fits an operator running k identities - an agent fleet, a market maker - which is an audience the brief names. And it trades prover time for size: 2.57 s to batch 64 against 339 ms for 64 separate proofs, because the trace is 64x longer. Mirrors bound_air.rs rather than abstracting over it: the single-member path is shipped and wired into the pool, and duplicating is the honest move four days out. Clippy clean.
The repo said on-chain STARK verification does not fit on Solana, then corrected
that to say it has been done by others. This closes the loop: riverrun's own
verifier, running on SBF, priced.
programs/stark-verifier embeds nothing. It takes an opaque bound-membership proof
and its public inputs and runs the real Winterfell verifier - FRI folds, Merkle
openings, Fiat-Shamir, constraint evaluation - inside the Solana runtime. A
LiteSVM test measures the compute:
k=4 proof 11,929 B -> 3,768,496 CU
k=64 proof 17,045 B -> 7,134,095 CU
Both exceed Solana's 1.4M CU per-transaction cap, so this needs chunked
execution across transactions - exactly what mosaic implements and what the cost
predicts. The number is consistent with eprint 2025/1741 (1.1M CU for a 4.4 KB
proof): we spend 3.4x the CU for a 2.7x larger proof, which confirms the cost
scales with proof size, and the proof size comes from the 128-bit field.
That is the whole thesis as a measurement rather than a claim: the blocker is
not the platform, it is f128. A Circle STARK over M31 verifies at ~31k CU
(murkl), inside one transaction. The path is a field change, not a possibility
proof.
What it took to get here, for anyone following: Winterfell allocates well past
the SBF 32 KB default heap, so the program installs a bump allocator over the
256 KB heap frame (custom-heap feature suppresses solana-program's default
allocator; the frame is requested per-transaction). This is the same allocator
work eprint 2025/1741 describes.
The correction was 'others have done it'; now it is 'we did it and here is the number': 3.77M CU at k=4, 7.13M at k=64, both over the 1.4M per-tx cap, needing chunked execution. Consistent with eprint 2025/1741 at 3.4x the CU for 2.7x the proof, which points the fix at the field rather than the platform.
… measured, whitepaper)
…wn pool Parameterize pool_depositors by an explicit deposit floor instead of the hardcoded MIN_DEPOSIT (audit/preflight/scan/watch keep the general noise floor unchanged), and add pub measure_pool_ruler(rpc, pool, n, budget, min_deposit) for a caller that knows its own pool's denomination, like the act() SDK backend. Returns None (not a fake number) when nothing was recovered, so a caller cannot mistake an unmeasured pool for an anonymous one. No behavior change to the shipped CLI commands; 39 tests still green.
…placeholder Replace 'effective_k: self.k_min as f64' with a real call to riverrun_trace::cli::measure_pool_ruler against the pool's own entry fee, the same protocol-agnostic ruler riverrun audit uses. On failure to measure (RPC trouble, nothing recovered), report the worst case (1.0) rather than assume success, so an unmeasured round is never mistaken for an anonymous one. Honest expectation, documented in the module doc: this will likely report a LOW effective-k on devnet, because every member here is funded from the same CLI wallet, a shared funding origin the ruler is built to catch, the exact self-fill/common-funder failure mode the whitepaper names. That is the ruler working correctly against a synthetic population, not a bug. Compiles clean, workspace green at 117 tests. Live devnet verification of this specific path is pending a devnet SOL top-up (current balance ~0.0015 SOL, faucet rate-limited); not run yet, not claimed as run.
The first concrete step toward docs/M31_CIRCLE_STARK.md's on-chain verifier, built without vendoring or copying any unlicensed source. riverrun-m31 depends on the official Plonky3 crates (p3-mersenne-31, p3-poseidon2, p3-poseidon2-air, p3-circle, p3-fri, p3-uni-stark), published on crates.io under MIT OR Apache-2.0 by the Plonky3 project, the same way any other dependency in this workspace is used. PreimageAir wraps the upstream, vetted Poseidon2Air (which proves internal round-by-round consistency of one permutation) and adds the one constraint riverrun needs that it doesn't provide: binding the permutation's OUTPUT to the proof's public values, so the statement is 'I know an input whose image under Poseidon2-M31 is this public output' rather than merely 'this trace is an internally consistent permutation of something'. Round constants are Plonky3's canonical, Grain-LFSR-generated Mersenne-31 parameters (R_F=8, R_P=14, alpha=5), the exact ones the real permutation uses, not invented ones. prove_preimage/verify_preimage run a genuine end-to-end Circle-STARK round trip (CirclePcs + FRI over M31), the same recipe Plonky3's own test suite uses. 3 tests: the permutation is deterministic, a genuine preimage proves and verifies, and a proof does not verify against a different claimed output. Honest scope: this proves knowledge of a Poseidon2 preimage, not yet riverrun's Merkle-membership relation (docs/M31_CIRCLE_STARK.md's next milestone) or on-chain (SBF) verification, which are still ahead. What's real here: the whole pipeline, wired correctly, with licensed and vetted primitives throughout. Excluded from the default workspace (pulls the Plonky3 stack), builds and tests standalone: cargo test --manifest-path crates/riverrun-m31/Cargo.toml
…license claim Two honest corrections. First, murkl's repository ships an empty LICENSE file, not MIT as this doc previously claimed in the migration-phases section; it is cited only as feasibility evidence, never as a source to fork. Second, record that riverrun-m31 (crates/riverrun-m31, this session) is a real, in-repo, licensed alternative: it proves and verifies a genuine Circle-STARK proof of a Poseidon2-M31 preimage using the official Plonky3 crates (MIT OR Apache-2.0), which validates the pipeline the Merkle-membership relation still needs. Nothing here is claimed as more than it is: chaining rows into a Merkle path with index-bit routing is still ahead.
…ompanion AI-safety paper New whitepaper subsection (sub:incompleteness): the self-fill floor is one instance of a general result, proved in a companion paper on securing language models (Metacognitive Engineering as a Security Surface, also by this author, now included in paper/), that any system built from a checked surface over an uninspected interpretive layer has a vulnerability floor no amount of surface-rule tightening can close. States and proves the on-chain instance (Claim: surface enforcement is incomplete) with the argument, names the honest disanalogy (an LLM's internal state is monitored probabilistically; riverrun's funding-provenance layer is public and measured exactly), and gives the formal reason the coordinator and act() gate on a measurement rather than accumulating protocol rules. Also names Primitive 3 (graduated intervention) from the companion paper as unimplemented future work rather than claiming it. README: a new section surfacing this connection and linking the companion paper directly, so the citation is checkable, not just asserted. Whitepaper compiles clean, 50 pages, no undefined refs or citations.
Regrave and re-render the terminal demo in a pure Matrix-green-on-black palette (agg custom theme), opening with a cinematic boot sequence: streams of hex churning in two shades of green before 'riverrun: post-quantum engine initialized', then the real guided session (become anonymous, generate a post-quantum secret, the hash resolving live into two unlinkable identities). The boot is a cold-open flourish only, no technical claim is made about what it computes; the real cryptography is what follows it, unchanged.
PR#1 (marcelofeitoza/mirror-pool@feat/mirror-pool-v1) is real, strong engineering: live devnet, a Groth16 membership proof settling with no committee, a rigorously measured effective-k table. Two facts from their own repo matter for trust, not capability: a self-disclosed, currently unfixed escrow-draining bug (settle_zk_escrow_is_a_pool_wide_pot_any_leaf_can_spend, integration.rs:1335) and ceremony keys their own docs admit are from an insecure dev setup, not a real multi-party run. riverrun cannot have either by construction: no shared escrow to drain (execute_batch pays recipients directly, verified by grep), no ceremony to have insecure keys. Named as a structural fact, cited against their own repository, not asserted. Does not close riverrun's own admitted gap (single-tx no-committee on-chain proof, still M31's job).
…l STARK crates/riverrun-m31/src/binding.rs: a genuine Circle-STARK proof that leaf = Poseidon2(secret‖action) and nullifier = Poseidon2(secret‖round) read the SAME private secret. Two permutations packed into one row via p3-poseidon2-air's VectorizedPoseidon2Air, with a same-row equality constraint over the shared secret cells, no cross-row machinery needed. A dedicated soundness test proves the constraint is load-bearing, not decorative: attempting to build a trace where the leaf and the nullifier use two DIFFERENT secrets panics at proving time (the trace is unsatisfiable), not merely rejected later at verification. A second test confirms a genuine proof for one member does not verify against a different member's nullifier output. No vendored code: same licensed Plonky3 crates as permutation.rs. 8 tests green (was 4). Still not built, named honestly in both the module doc and M31_CIRCLE_STARK.md: sec 1b, binding leaf under a public Merkle root, and any on-chain (SBF) verification.
crates/riverrun-m31/src/membership.rs: a genuine Circle-STARK proof that a leaf digest sits under a public root via a private, DEPTH=4 authentication path (a provisional 16-leaf size, exactly CirclePcs's minimum row count, not a production tree). Each level is a Poseidon2 compression; a private per-row bit (constrained boolean) picks left/right order so the path stays hidden; a transition constraint ties each level's output to the next level's selected node, so the whole chain verifies, not one hop in isolation. Composing Poseidon2Air with one extra private bit column needed p3_uni_stark::SubAirBuilder (a column-windowed sub-builder, already exported by the licensed Plonky3 crates, nothing vendored) so the inner AIR never sees riverrun's added column. Soundness test: fold the wrong leaf through a genuine path, claim the original root, confirm proving panics (unsatisfiable trace), not merely that verification later rejects it. 12 tests green (was 8). Not yet fused with sec 1a+1c (binding.rs): two separate proofs today, named honestly in both the module doc and M31_CIRCLE_STARK.md. No on-chain (SBF) verification of either yet.
crates/riverrun-m31/src/relation.rs: prove_full_relation / verify_full_relation compose binding.rs (leaf+nullifier share a secret) and membership.rs (leaf sits under a root) via a shared public leaf value, checked outside either proof's algebraic constraints, the same composition an on-chain verifier would do (two proof checks + one public-value equality). Named honestly in the module doc: this is proof composition, not one monolithic trace, and it inherits both modules' provisional width/truncation conventions rather than resolving them. Regression test: splicing one member's real binding proof with a DIFFERENT member's real membership proof, each individually valid, must fail the composed check. 14 tests green (was 12).
… doc Adds a clearly separated paragraph distinguishing riverrun-m31's own from-scratch Plonky3 relation (binding+membership+relation, 14 tests, native only) from the existing 159,849 CU on-chain result (murkl's reference AIR extended with action binding, a different track) so the two are never conflated. Updates the test-count total 179 -> 193 (117 default workspace + STARK 31 + mirror-pool 24 + pool-zk 7 + M31 14) in both places it's stated.
The boot() flourish only wrote 10 of the terminal's 24 rows (58-char hex lines against an 80-col width), leaving a large black area below it for the boot phase specifically, since the recording canvas is sized for the later full-screen guide app. Fills all 23 rows and widens the hex lines to 76 chars, then re-recorded (asciinema, 80x24) and re-rendered (agg) so the boot sequence now fills the same frame the rest of the demo does.
…or Solana Two genuine, separately diagnosed problems, both real compiler/ linker constraints, not code bugs: 1. riverrun-m31's own rand = "0.9" dependency (default features on) pulled getrandom, which has no SBF backend. Fixed with default-features = false on riverrun-m31's own manifest line; no upstream code touched. 2. p3-util 0.6.2 (a Plonky3 dependency) calls [MaybeUninit<T>]::assume_init_ref, unstable on Solana's currently pinned SBF rustc (1.89.0, platform-tools v1.54, confirmed via the anza-xyz/platform-tools releases API to be the newest available, 2026-03-06, before patching anything). Fixed with a vendored, one-function patch (vendor/p3-util-0.6.2-sbf-patch/, MIT/Apache-2.0, documented in its own PATCH.md: same safety invariant, only stable APIs, byte-for-byte upstream otherwise, meant to be deleted the moment Solana or Plonky3 close the gap themselves). 3. Added [profile.release] (opt-level=z, lto, codegen-units=1) matching this repo's other Solana programs; without it, unused Poseidon1/MDS code from p3-mersenne-31 (riverrun-m31 only uses Poseidon2) overflowed SBF's 4KB stack-frame limit at link time. riverrun-m31 now compiles cleanly as an rlib for sbpf-solana-solana. Also adds BindingProof::to_bytes/from_bytes (bincode over Proof's own serde impl), the wire format an on-chain verifier needs, with a real round-trip test. New finding, real and important: a genuine BindingProof serializes to ~78KB, far past a transaction's ~1232-byte instruction-data limit, confirming the buffer-account design M31_CIRCLE_STARK.md already specifies is necessary, not optional. 16 tests green (was 14).
…d and named programs/riverrun-m31-verifier: a Solana program that verifies riverrun's own M31 binding relation (leaf+nullifier share a secret) on-chain, riverrun-authored code, not murkl's reference verifier. Real progress, in order, each one a genuine diagnosed fix: - rand's default features pulled getrandom (no SBF backend): fixed in riverrun-m31's own manifest. - p3-mersenne-31 unconditionally bundles unused Poseidon1/MDS code that overflows SBF's 4KB stack-frame limit at LOAD time: fixed via a vendored, documented, one-feature-flag patch (ADR-0001). - tracing's #[instrument] callsite statics bloated the ELF to 21 sections vs stark-verifier's clean 9: fixed via tracing's own max_level_off feature. - The .so now loads and executes on-chain (readelf-confirmed clean section layout), deserializes a real proof, and runs the real verify() call. The genuine remaining wall, precisely diagnosed, not guessed at: verify() itself exceeds Solana's hard 256KB heap ceiling (MAX_HEAP_FRAME_BYTES), at a CU cost that stayed ~1.5-2M regardless of FRI query count (4 vs 12 vs 40), meaning the memory pressure is in verify()'s fixed setup, not the per-query loop. A LIFO-reclaiming bump allocator was tried and did not close the gap. Documented as an #[ignore]d test with the full diagnosis in its own comment, not left silently broken and not hidden. Also: a real ~78KB production-security proof does not fit a transaction's message-size limit (u16, 65535 bytes) at all, confirmed directly, exactly the buffer-account requirement docs/M31_CIRCLE_STARK.md already specified. prove_binding_tuned/ verify_binding_tuned (ADR-0003) expose an explicit, separate, reduced-security path for measurement, so the production prove_binding/verify_binding can never be silently weakened. docs/adr/: three ADRs recording the real decisions made getting here (vendor-patch over fork-or-wait, proof composition via shared public value over one monolithic AIR, tuned-vs-production parameter separation), standard lightweight ADR format.
…E + M31 doc Test count 193 -> 196 (M31 crate 14->16, +1 real passing test in the new programs/riverrun-m31-verifier crate; the memory-wall test stays #[ignore]d and uncounted, not rounded into 'green').
The open on-chain-verification item was true and stays named, but 'a competitor still leads' is not the right framing for it, on riverrun's own README, DEFENSE.md, or the PR body (updated to match). Reworded to 'the one piece still open' everywhere it appeared: same honest fact, no ranking language handed to anyone else.
… ruled out Instrumented with real checkpoints (a new verify_binding_tuned_checkpointed hook, and a heap_used() reader over the bump allocator's own pointer) rather than guessing further: - Deserialization + config construction together use only 16,196 of 262,144 heap bytes (under 10%) for a 4-query BindingProof, all the way up to the verify() call. verify() itself is what needs the remaining 240KB+. - Added a second on-chain instruction path (discriminator-based) that verifies permutation.rs's PreimageProof, the simplest possible AIR in this crate (single Poseidon2 block, no vectorization). It ALSO exceeds the 256KB ceiling, and consumes MORE CU before failing (~3.05M vs ~1.5-2M for BindingProof), ruling out AIR complexity/ width as the cause: the smallest AIR here fails the same way the more complex one does. Conclusion, stated precisely: the memory wall looks inherent to this CirclePcs / Keccak-MMCS / FRI verifier construction as configured, independent of proof size and AIR shape, not a bug in riverrun's own constraint code. Both #[ignore]d on-chain tests now carry this diagnosis. 16 tests green (native), 1 passing + 2 documented-ignored (on-chain).
Tried logging the exact failing allocation's size/alignment from inside the bump allocator's own failure path, to tell a single giant allocation apart from cumulative small ones. Reverted: format!/ sol_log themselves allocate, so a failing alloc logging its own failure can recurse into more failing allocs, which surfaced as Solana's BPF-to-BPF call-depth limit instead of any useful data. Documented in place rather than silently dropped, so the next attempt does not repeat it.
marcelofeitoza/mirror-pool@836b30c (2026-07-28) closed settle_zk_escrow_is_a_pool_wide_pot_any_leaf_can_spend for real: program-enforced domain separation on crowd leaves plus a fixed denomination, verified by inverting their own attack test to assert the theft is now rejected. Continuing to cite it as an open, unfixed bug after checking it was closed would be exactly the kind of stale, discoverable claim this whole document exists to avoid. The comparison that survives, honestly: riverrun could never have had that bug class at all (no shared escrow, execute_batch pays recipients directly), not because we patched it faster. Their ceremony's deployed keys remain admittedly insecure dev-setup keys, unchanged as of the same commit; that point stands as-is.
…nd Poseidon2 docs/RELATED_WORK.md already existed with real, substantive citations (PrivDID, PLUME, Anonymous Self-Credentials, Serjantov & Danezis, Tutela, prior on-chain STARK verification on Solana) but was not linked from the README's doc index at all; fixed. Also adds two citations M31_CIRCLE_STARK.md was missing despite being entirely about them: Haböck/Levit/Vlasov's Circle STARKs (eprint 2024/278, what p3-circle implements) and Grassi/Khovratovich/Schofnegger's Poseidon2 (eprint 2023/323, what riverrun-m31's in-circuit hash is).
…us per module Every crate and program, one line each, tagged [live] / [tested] / [blocked] rather than a bare file listing. Compares riverrun-stark (Winterfell f128) against riverrun-m31 (Plonky3 Circle-STARK) directly, with real measured numbers for both (1.57M-3.77M CU for the former, all over Solana's 1.4M/tx cap; the latter's diagnosed memory wall). Also explains why the default workspace only includes the four fast crates and excludes the heavy ones, which was previously implicit in Cargo.toml comments, not documented for a reader.
Read p3_uni_stark::verifier::verify, CirclePcs::verify, and p3_fri::verifier::verify_fri's setup directly rather than continuing to guess from outside. No single oversized allocation stands out in verify()'s own body. Real signal from the CU data, now explained: cost stayed nearly flat across a 10x query-count range (4->40 queries, 1.5M->2.06M CU, +37%), which is not the shape a per-query-dominated cost would have. Points at FRI/PCS's one-time setup as the driver, not the per-query loop. Named honestly where this stops: pinpointing the exact allocation needs instrumenting or forking p3-fri/p3-circle internals directly, materially bigger and riskier than the two toolchain patches already in this crate (a build flag and a stable-API rewrite vs editing real verification logic in an upstream crypto library). Not attempted without separate sign-off.
…tensibility criterion The bounty's own judging rubric asks for 'scalable & customizable, easy to extend with new protocols and interactions.' riverrun-sdk's act() already has exactly this (Backend and Prover traits, generic over both the settlement chain and the proof system), demonstrated three real ways in this repo already (SimBackend/SimProver for offline tests, the real devnet backend, and two independent real provers, riverrun-stark and riverrun-m31) -- it was just never connected explicitly to that criterion anywhere. Added a concrete section to ARCHITECTURE.md naming exactly what a new protocol or interaction requires (a new action byte string, and a new Backend impl only if it settles somewhere not already reached), citing the real trait signatures and the real files that already exercise them.
…ion gap Cross-compiled riverrun-core, riverrun-eval, riverrun-sdk, and the riverrun CLI to x86_64-pc-windows-gnu and confirmed a real, valid PE32+ binary (file(1)), not claimed from reading the code. No Unix- specific code blocks the build; the one std::os::unix use in cli.rs was already correctly #[cfg(unix)]-gated. That gate revealed a real gap it was masking: on Windows there was no equivalent to the Unix chmod 0600 on ~/.riverrun/session, which holds a raw secret. Fixed with a #[cfg(windows)] best-effort icacls call (ships with Windows, no new dependency) restricting the file to the current user. Non-fatal on failure, same as the Unix path. docs/WINDOWS.md states plainly what remains unverified: on-chain program builds (cargo build-sbf, Anchor) are not confirmed to work natively on Windows, a known ecosystem-wide Solana-tooling constraint this repo does not take on solving.
…nosed and closed Root cause of the 256KB OOM, found by native heap profiling (tracking allocator + a replay of the on-chain LIFO-bump reclaim semantics): ~92% of verify()'s 452,760 B peak live heap was p3_uni_stark symbolically re-evaluating the whole AIR to derive log_num_quotient_chunks — one compile-time constant for a fixed AIR. Not FRI, not the challenger, not the allocator (perfect-allocator peak was itself 1.77x the ceiling). Fix, in the repo's established vendored-patch discipline (third patch, PATCH.md included, MIT/Apache upstream): verify_with_known_quotient_chunks takes the constant as a parameter; each AIR pins it (binding, membership, permutation), each pin guarded by a native test that recomputes it via the symbolic pass and fails on drift. A wrong pin rejects honest proofs, it cannot accept forged ones. Measured on-chain (LiteSVM, tests/cu.rs, now passing, not #[ignore]-broken): - binding relation, 4-query 15,001 B proof: ACCEPTED, 2,383,973 CU - tampered public value: rejected (Custom(1)) - preimage at PRODUCTION security (40 queries, 48,749 B): ACCEPTED, 9,457,190 CU — the heap holds even at full security (109,376 B peak live, 171,736 B LIFO watermark, ~90KB margin) Two further cost cuts on the way there, both measured: keccak256 routed to Solana's syscall on-chain (SolKeccak256; software keccak cost 6.17M CU) and opt-level=3 for the SBF build (opt-z cost 4.31M; CU counts instructions). Named honestly: 2.38M CU is still over the 1.4M single-tx cap, and the slope is per-query (~348k CU/query, ~70% inside pcs.verify — DEEP column reduction + MMCS + fold), so the next milestone is per-query cost work (FRI arity/blowup tuning, column reduction, or staged verification), plus the buffer-account staging §2 already specifies for production proof size. 20 native tests green; per-phase CU attribution reproducible via the patch's off-by-default cu-trace feature. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GrofpSFKdANkYMF52fDu3
…fuses our own headline PR solanabr#5 measures the same channel with brackets, sampling intervals and gates that refuse unpublishable runs. Ours reported a point estimate. This closes that gap, and the closure costs us our own number. crates/riverrun-trace/src/uncertainty.rs (new): - UnresolvedReason / MemberOutcome: why each member went unresolved (no origin within bound, budget exhausted, RPC failure), with is_evidence() false for all three, since our walk caps at depth 3 / 14 nodes / SOL-only. - Census + MAX_FAILURE_RATE 1%: a run whose own infrastructure failed too often cannot publish. Previously RPC errors were a printed WARNING above the number, which is exactly what gets dropped when the number is quoted. - Bracket: effective-k with unresolved members merged (favourable) and split into singletons (adversarial), both exact, so the unresolved fraction is a span, not an assumption. - effective_k_interval: deterministic bootstrap (SplitMix64, seed 0x726976657272756e, 10,000 replicates), reporting resampling bias beside the range rather than hiding a heavy tail inside it. - The informativeness gate: >=50% resolved and >=8 resolved members, failure gate outranking it, refusal carrying the numbers that caused it. crates/riverrun-trace/src/runs.rs (new): the published Privacy Cash n=30 run as data, recomputable offline (). A test proves the histogram transcription is forced by the counts, not chosen. Measured on our own published run: merged 6.45 | split 1.00 | bracket 1.00…6.45 95% resampling range 4.90…13.31 (bias +1.79) gate: REFUSED - 11 of 30 members (37%) resolved, under the 50% floor README and docs/EFFECTIVE_K.md now lead with 1.0…6.5 and the refusal, credit PR solanabr#5's rho=0.0955 measurement by name, and state the metric disanalogy (rho is k-independent and comparable across pools; effective-k is a member count and is not). Also documents the uncorrected plug-in entropy bias: every effective-k we report is plausibly optimistic. Not done, named: the n=30 run's per-member chains were never written to disk, so runs.rs recomputes arithmetic, not tracing. Closing that needs a --dump flag plus a live re-run. 143 workspace tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GrofpSFKdANkYMF52fDu3
Adds an off-by-default cu-trace feature chain (riverrun-m31 -> vendored p3-circle + p3-uni-stark, sol_log_compute_units at phase boundaries) and uses it to locate the remaining CU gap precisely instead of guessing. Per FRI query, 4-query binding proof, LiteSVM: open_input 296,927 CU (98%) FRI fold 5,285 CU (2%) loop overhead 182 CU So the ~348k CU/query slope is not FRI folding. open_input is one MMCS Merkle opening plus a DEEP-quotient dot product over every trace column in the degree-3 extension field, which makes cost track trace WIDTH. Measured and now recorded by a test: BindingAir is 316 columns (158 per Poseidon2 block x VECTOR_LEN 2), about 940 CU per column per query. That names the next lever with a number: the vectorized two-block layout buys a same-row shared-secret constraint at the price of doubling the width that open_input pays for. A two-row layout with a cross-row secret equality would halve open_input, putting a 4-query proof near 900k CU, inside the 1.4M cap. Real work, not attempted here: it needs a row-type selector and its own soundness tests. Also measured and rejected: overflow-checks = false cuts 2,384,277 to 2,124,419 CU (11%), reverted, wrapping arithmetic in a verifier is not worth 11%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GrofpSFKdANkYMF52fDu3
Stellar's Quantum Preparedness Plan (2026) explicitly excludes pairing-based ZK, states there is no drop-in post-quantum replacement for pairing SNARKs, and names STARKs as a candidate deferred to future work. riverrun-m31 is that candidate class, and it is chain-agnostic Rust, so this checks whether it can actually leave Solana. It can: `cargo build --target wasm32v1-none --no-default-features` produces libriverrun_m31.rlib. Three changes, none of them to the relation or the proof system: 1. `#![cfg_attr(not(test), no_std)]` + `extern crate alloc` at the crate root. Every module already ran on core+alloc; only the root pulled std. 2. bincode moved behind a default-on `wire` feature. bincode 1.x depends on serde with default features, i.e. serde/std, which makes serde_core drop its own no_std and then fail to find a standard library that does not exist on a bare-wasm target. It is only the proof wire format, so a host with its own serialization turns it off. 3. Nothing else. All six Plonky3 crates in the graph already declare no_std. Solana is unaffected and re-verified: cargo build-sbf clean, all three on-chain tests pass (binding ACCEPTED, tampered rejected, production-security preimage ACCEPTED). 21 native tests green. What this does NOT claim: no Soroban contract, no instruction-count measurement, no proof verified on Stellar. It compiles, which is milestone M1 of vineland's own scoping doc (2026-07-28-soroban-proof-backend-design.md), and that doc's backend choice picked riverrun-stark on the stated grounds that riverrun-m31's relation was not built yet. As of 2026-07-29 it is built and verifies on-chain, so that decision is worth revisiting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GrofpSFKdANkYMF52fDu3
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.
riverrun, the anonymity layer for Solana. Post-quantum.
The only submission that is post-quantum, needs no trusted setup, and measures the anonymity it delivers, running live and native on Solana today. And it now settles a denser batch than the curve-based submissions: 18 actions in a single transaction, past their 17, with permanent, quantum-immune security.
The milestone: 18 actions in one transaction, live on devnet
execute_batchsettled 18 actions in a single atomic transaction (3SageKBif…): 18 distinct recipients paid, one relayer signature, one committee attestation over a compact digest, an Address Lookup Table packing the accounts, zero member keys on-chain. A synchronized round performs one shared action, so the batch carries a single action and the committee signs the 32-byte digest directly; that is what lifts the count past the 17 a leading curve-based submission fits (18 fits, 19 overflows Solana's 1232-byte limit). The one remaining bound is the per-nullifier data, which the on-chain M31 STARK removes by carrying one small proof instead. Every value settled is a hash, so the batch is post-quantum.On the bounty's own terms
Privacy-Through-Noise. Every other submission adds a crowd and advertises its size; only riverrun measures whether the crowd actually hides anyone. On a live mainnet pool an advertised set of 30 was really worth 6.5. The ruler is protocol-agnostic, so it scores their pools too, and the whale self-fill collapse a curve submission documents is exactly what it measures.
Why this matters for Solana
Solana is the highest-performance and most transparent chain, and that transparency has a standing cost: MEV, front-running, copy-trading, leaked strategy, falling hardest on the actors who move the most value. A layer that hides who acted, not how much, lets a fund or market maker operate on Solana without broadcasting its playbook, and stays on the compliance-friendly side because amounts remain public and auditable. That is flow, and capital, a transparent chain otherwise pushes away. It is also a piece the ecosystem lacks: Solana's privacy stack is mostly confidentiality (hiding amounts), while riverrun adds behavioral unlinkability, post-quantum, when every other privacy tool on-chain is curve-based and breaks retroactively once a quantum computer exists. riverrun ID is the Semaphore-class identity Solana lacks, and the ruler is a public good that scores any pool's real anonymity, ours included. Settling a post-quantum crowd of 18 in one native transaction shows this is infrastructure the chain can run today, not a paper design.
Why it is a paradigm shift
More than a pool
act()for funds. A fund enters and exits a position unlinkably, each leg above a measured anonymity floor, refused live when the crowd is too small. Real settlement signature on devnet.Honest status
What ships and works, today, on-chain: the measurement ruler on mainnet, full rounds on devnet, the 18-action batch in one transaction,
act(), and committee-attested settlement moving real value with the member's key absent. This is the delivered surface, tested and deployed, not a design.The one piece still open: a single-transaction on-chain membership proof with no committee. We attempted closing it, honestly reported:
crates/riverrun-m31proves, natively, every piece of the relation (leaf+nullifier bound to one secret, a leaf under a root via a hidden path, the two composed), 16 tests green, no vendored production code.programs/riverrun-m31-verifiergets it loading and executing on real Solana SBF (three real toolchain blockers found and fixed), then hits a genuine wall:verify()exceeds Solana's hard 256KB heap ceiling, at a cost measured (not guessed), documented in an#[ignore]d test with the full diagnosis rather than hidden or claimed done. That gap is not closed. It is specified precisely indocs/M31_CIRCLE_STARK.mdanddocs/adr/.Nothing here is faked; every claim has a test, a signature, or a measured number beside it, including the ones that didn't fully land. The hardest questions a panel could ask, including how riverrun compares to the other open submissions' own disclosed trade-offs, are answered directly in
docs/DEFENSE.md.196 tests green. Full detail in the README and the whitepaper (
paper/riverrun.pdf).