feat: BSB22 commitment verification for gnark logderivlookup proofs - #31
Merged
Conversation
…k deps - Bump solana-bn254 from "2" to "3" — v3 uses ark-ff 0.5, eliminating the duplicate ark-ff 0.4 compilation from the dep graph - Set default-features = false for thiserror, ark-serialize, ark-ec, ark-ff, ark-bn254, num-bigint, serde, serde_json — avoids pulling in std/alloc features that downstream Solana programs don't need - Keep features = ["curve"] on ark-bn254 (required for BN254 curve ops)
Adds a `bsb22` cargo feature that extends the existing Groth16
verifier to accept gnark proofs carrying one BSB22 Pedersen
commitment plus its knowledge proof. Every circuit that touches
`std/lookup/logderivlookup` or any emulated-field range-check
helper ends up with exactly one commitment via gnark's
multicommit merging, so this unlocks on-chain verification of
those circuits without any changes to the vanilla verifier path.
New public API (gated on `bsb22`):
- `Groth16Verifier::new_with_commitment` — mirrors `new` plus the
commitment and PoK G1 points; rejects vks that lack the
Pedersen commitment key.
- `Groth16Verifyingkey::vk_commitment_g2` / `vk_commitment_g_sigma_neg_g2`
as `Option<[u8; 128]>` fields; vanilla `new` refuses keys with
these set.
- `groth16::negate_g1_be` — public helper that replaces five
inlined copies of the gnark BE negate-and-reserialize dance.
- `hash_to_field::hash_to_field_bn254_fr` — RFC 9380
expand_message_xmd with SHA-256, byte-exact with
gnark-crypto's `fr.Hash`. Uses the `sol_sha256` syscall on
chain via a raw extern binding; zero heap allocations (stack
scratch buffer).
- `gnark_vk_parser::parse_gnark_vk_bytes` — reads gnark's
`VerifyingKey.WriteRawTo` binary including the trailing
`PublicAndCommitmentCommitted` / `CommitmentKeys` sections.
Rejects multi-commitment and trailing garbage with
`Bsb22InvalidVerifyingKeyBinary` / `Bsb22UnsupportedMultiCommitment`.
- `gnark_vk_parser::{bsb22_vk_to_rust_const, generate_bsb22_vk_file}`
for downstream build scripts that want to bake a vk into
`.rodata`.
Verification algorithm mirrors gnark's
`backend/groth16/bn254/verify.go:47-133` (single-commitment path):
hash-to-field → extended prepare_inputs with commitment MSM + raw
G1 addition → standard 4-pair Groth16 pairing → 2-pair Pedersen
PoK pairing. For single-commitment the gnark fold collapses to
identity so no Fiat-Shamir challenge is needed.
Test infrastructure:
- `tests/bsb22/gnark-fixture/` — single-file Go module exposing
Setup/Prove/NativeVerify via cgo for Lookups1/2/3Circuit
(1, 2, 3 logderivlookup queries, all merged into one
commitment by gnark's multicommit).
- `tests/bsb22/` — integration test crate that compiles the Go
fixture to a C archive via bindgen and runs the verifier
against real proofs from each variant; 7 positive+negative
tests.
- `tests/bsb22-program/` — tiny cdylib Solana program + litesvm
test that measures empirical on-chain CU.
- `tests/fixtures/bsb22/` — committed binary snapshot (vk +
proof + public input) shared between the lib-internal
end-to-end test, the SBF program's build.rs, and the parser
unit tests.
Empirical on-chain CU: ~223,558 per BSB22 verify (vanilla
Groth16 is ~170k; BSB22 adds one hash-to-field, one G1 mul, two
G1 additions, and one 2-pair pairing).
The vanilla Groth16 path is byte-identical to before — 6/6 existing
tests pass unchanged with default features, all new code is gated.
Transitive dep pin required for the SBF toolchain (rustc 1.84):
blake3 is pinned to =1.8.2 in `[workspace.dependencies]` to keep
`constant_time_eq` on 0.3.x, which doesn't require edition2024.
alt_bn128_addition -> alt_bn128_g1_addition_be alt_bn128_multiplication -> alt_bn128_g1_multiplication_be alt_bn128_pairing -> alt_bn128_pairing_be alt_bn128_g1_decompress -> alt_bn128_g1_decompress_be alt_bn128_g2_decompress -> alt_bn128_g2_decompress_be
- Add src/syscalls.rs re-exporting g1_addition_be / g1_multiplication_be / pairing_be from pinocchio when targeting Solana and from solana-bn254 v3 on host. Lets the crate build under pinocchio without pulling solana-bn254 into SBF builds. - Replace .concat() input assembly in groth16.rs with stack-allocated fixed buffers (96 / 128 / 384 / 768 bytes), removing per-call Vec allocations. - is_less_than_bn254_field_size_be uses ark BigInt::cmp directly; num-bigint becomes optional and is gated behind the existing 'vk' feature. - Bump thiserror 1 -> 2. Test and VK-parser updates carry the corresponding API and feature-gate changes.
A bare extern "C" sol_sha256 symbol does not resolve under the static-syscalls SBF ABI (platform-tools v1.54+); the call silently leaves the output buffer zeroed. That zeroes the BSB22 commitment-derived public input, so every committed (merge / P256) on-chain proof verification fails while host verification passes. Use pinocchio's define_syscall!-bound sol_sha256, matching how the alt_bn128 ops are already bound.
- Merge the two cfg-gated commitment-key fields into a single ungated vk_commitment: Option<CommitmentVerifyingKey>; the type now enforces that both G2 points are set together, and vk consts no longer need feature-conditional fields. Standard new() rejects commitment vks in all builds. Breaking for baked consts: add vk_commitment: None. - Move vk generators to src/vk/ as vk::gnark and vk::circom; codegen is now private, host-only (cfg(not(target_os = "solana"))), and emits the merged field. circom codegen returns meaningful errors instead of underflow panics on malformed JSON (new integration tests). - Extract g1_mul_add/g1_add and verify_commitment_pok helpers; delete statically dead length checks; specialize expand_message_xmd to L=48; unify Cursor bounds checks with checked_add. - Target-gate sha2 to host builds; syscalls and hash_to_field are now pub(crate). - Collapse the three Go fixture circuits into one parameterized LookupsCircuit; drop go mod tidy from the build script; delete superseded TestDumpVkBytes. - Naming pass: rename generic/abbreviated locals, replace vanilla with standard Groth16, migrate deprecated alt_bn128 compress calls to _be. - Docs: fix stale field names, CU figures (~212k measured), dangling fixture-README pointer, machine-local path in README.
- Add UnexpectedCommitmentKey (ungated), Bsb22CommittedPublicInputsUnsupported, Bsb22VkFileIoFailed, and Bsb22InvalidCommitmentPoint; delete the never- constructed IncompatibleVerifyingKeyWithNrPublicInputs, InvalidG1Length, and InvalidG2Length (codes 0/4/5 stay unassigned). - Check vk_commitment before the vk_ic length check in both constructors so the commitment mismatch is reported instead of being shadowed by InvalidPublicInputsLength. - Label an off-curve prover-supplied commitment as Bsb22InvalidCommitmentPoint instead of PreparingInputsG1AdditionFailed; tighten the mutated-commitment FFI assertion from four accepted variants to three. - Split Bsb22UnsupportedMultiCommitment: committed public inputs now report Bsb22CommittedPublicInputsUnsupported, lockstep mismatch reports Bsb22InvalidVerifyingKeyBinary; generate_bsb22_vk_file IO failures report Bsb22VkFileIoFailed instead of invalid-binary. - Prose pass over new docs/comments: fix the wrong fail-closed claim on verify_commitment_pok, name concrete error variants in comments, drop filler wording.
The bsb22-integration-program litesvm test loads a prebuilt bsb22_integration_program.so from target/deploy, produced by cargo build-sbf. cargo test --workspace does not build it, so the test panicked with a missing-.so error. Install the Solana toolchain and run cargo build-sbf for the program before the workspace test so the .so exists at the path the test expects.
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
Adds BSB22 Pedersen-commitment verification behind a new
bsb22feature, so this crate can verify gnark Groth16 proofs from circuits that usestd/lookup/logderivlookuporapi.Commit(single commitment). Also upgrades to solana-bn254 v3, moves SBF builds onto pinocchio syscalls, and reorganizes the vk parsers undersrc/vk/.What the verifier does
For a proof with one BSB22 commitment,
Groth16Verifier::new_with_commitmentmirrors gnarkbackend/groth16/bn254/verify.go:expand_message_xmdover SHA-256, DSTbsb22-commitment, byte-exact with gnark-cryptofr.Hashand pinned by golden vectors).e(commitment, g_sigma_neg) * e(pok, g) == 1. For a single commitment, gnark'sBatchVerifyMultiVkfold reduces to exactly this (the fold scalar for index 0 is 1), so no Fiat-Shamir challenge is needed.Circuits that commit to public inputs (
PublicAndCommitmentCommittednon-empty) and multi-commitment vks are rejected at parse time with dedicated errors.API
Groth16Verifyingkeygains an un-gatedvk_commitment: Option<CommitmentVerifyingKey>field. Breaking: existing baked vk consts must addvk_commitment: None. The standardnew()rejects commitment vks withUnexpectedCommitmentKeyin all builds.vk::gnark::parse_gnark_vk_bytesparses gnarkVerifyingKey.WriteRawTobinaries including the trailing commitment sections;vk::gnark::generate_bsb22_vk_filebakes a vk into apub constfrom build.rs (host-only, compiled out on SBF).vk::circom(renamed fromvk_parser) emits the new field and returns field-naming errors instead of underflow panics on malformed JSON.IncompatibleVerifyingKeyWithNrPublicInputs,InvalidG1Length,InvalidG2Length); new variants label commitment-specific failures (Bsb22InvalidCommitmentPoint,Bsb22CommittedPublicInputsUnsupported,Bsb22VkFileIoFailed).Cost
~212k CU per BSB22 verify on Solana, measured by
tests/bsb22-program/tests/litesvm_cu.rs(asserts < 350k).Testing
Three layers, all anchored on gnark itself:
cargo test -p groth16-solana --features bsb22): committed fixture e2e, hash-to-field golden vectors from gnark-crypto v0.19.0, parser accept/reject cases.tests/bsb22, requires Go): an in-repo cgo gnark fixture runs Setup/Prove/Verify per test run with fresh randomized setups across three lookup-count variants, plus negative tests for mutated public input, commitment, and PoK. gnark's own verifier is run on the same bytes as the chain-of-trust anchor.cargo test-sbf -p bsb22-integration-program): litesvm end-to-end verify with the vk baked via the codegen path, plus a mutated-input rejection.The port was additionally reviewed line-by-line against gnark v0.14.0 / gnark-crypto v0.19.0 sources and RFC 9380.