Skip to content

opaquecash/starknet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Opaque on Starknet

Starknet implementation of the Opaque protocol, starting with the PSR (Programmable Stealth Reputation) verification stack. Design rationale and the full integration plan live in the spec repo: spec/starknet-integration.md.

Status: P0 proof-of-concept. Nothing here is deployed.

Components

Package Purpose Status
contracts/psr_groth16_verifier Groth16-BN254 verifier for the PSR V2 circuit, code-generated by Garaga from the pinned production verification key Compiles
contracts/reputation_verifier OpaqueReputationVerifierV2 port: root registry (1h TTL, delete-on-evict), u256 nullifier set, schema-liveness seam (OPQ-006) Fork-tested
contracts/stealth_announcer CSAP announcer: EIP-5564-shaped Announcement event, Solana-style input bounds Tested
contracts/stealth_registry CSAP meta-address registry: ERC-6538-shaped storage, 66/98-byte length check, SNIP-12 on-behalf registration through SRC-6 is_valid_signature with consumable nonces Tested
contracts/stealth_account CSAP stealth custody: non-upgradeable OZ EthAccountComponent account, one-time P_stealth signer, counterfactual per-payment address Tested + declared
contracts/psr_gate Tier-1 PSR consumer: credential-gated entry — enter only by proving a valid attestation under a required schema, one-time per nullifier (the integration thesis) Live
contracts/ons_mirror Read-only ONS mirror fed by native L1→L2 messaging (#[l1_handler]): emitter allowlist, monotonic sequence, revoke-tombstone (OPQ-004). Paired with StarknetOnsMirrorSender in opaquecash/ethereum Live

The generated verifier

psr_groth16_verifier is generated code. It is produced from the same verification key pinned by Groth16VerifierV2.sol (Ethereum) and the Solana groth16-verifier program, via the committed proof fixture in circuits/test/fixtures/v2/. Do not edit it by hand; regenerate it:

uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python --prerelease=allow 'garaga==1.1.0'
cd contracts
../.venv/bin/garaga gen --system groth16 \
  --vk ../../circuits/test/fixtures/v2/verification_key.json \
  --project-name psr_groth16_verifier

A new trusted-setup ceremony (the planned mainnet ceremony for the OPQ-018 / OPQ-006 circuit fixes) changes the verification key and therefore requires regenerating and redeclaring this contract — it is not a parameter update.

After regenerating, re-add [lib] to the generated Scarb.toml (above the [[target.starknet-contract]] section): an explicit contract target suppresses the default lib target, and reputation_verifier consumes this package as a dependency for its fork tests.

The verifier delegates multi-scalar multiplication to Garaga's universal ECIP ops class (library_call to a pinned class hash), which is pre-declared on Starknet mainnet and Sepolia by the Garaga project.

Toolchain

Pinned in contracts/psr_groth16_verifier/.tool-versions:

  • scarb 2.16.1
  • starknet-foundry 0.57.0
  • garaga 1.1.0 (Python CLI, Python 3.12)

Build

cd contracts/psr_groth16_verifier && scarb build
cd contracts/reputation_verifier && scarb build

P0 exit criteria (tracking)

  • Garaga verifier generated from the pinned V2 vkey
  • scarb build green
  • Wrapper contract: root registry + nullifier set + schema-liveness seam, fork-tested with the real proof: nullifier consume + replay rejection + TTL expiry + unknown-root rejection + admin gating (5 tests green)
  • Real V2 fixture proof encoded with garaga calldata: 1,977 felts (contracts/psr_groth16_verifier/tests/proof_calldata.txt)
  • Real V2 fixture proof verified via snforge fork test against Sepolia (real ECIP class): ~43.1M L2 gas (snforge test)
  • Live declare/deploy + invoke on Starknet Sepolia: the committed V2 fixture proof verified on-chain and its replay was rejected with 'nullifier already used' (see deployments below)

Measured numbers: a single verification executes at ~43.1M L2 gas in the snforge fork (call execution only; the spec's ~34M order-of-magnitude came from Garaga's smaller benchmark vkey). The live end-to-end invoke — account validation, 1,978 felts of calldata, wrapper + verifier + ECIP library call, events — consumed 155.47M L2 gas / 4.81 STRK on Sepolia.

Sepolia deployments (P0 — testnet, dev-key admin)

Contract Class hash Address
Groth16VerifierBN254 0x003c72da2c846e3304885e59fb3e0dae07243d482adc29ef3c248d60ad99992d 0x01f339dfc3a1509bc3ccd1c7ea1a19c07bc0f89ad7378b505b3edc5f5b13b02e
OpaqueReputationVerifierV2 0x04361bf98499842b39bd103f4ddc890a0adf56e060a75d98fca8143fc45fb576 0x079ada2245bd7f7575c4c67c9ea34edc9c420c57de711762b8ffbb8483823af6
PsrGate 0x0222458c219f5c760436f616862d7a9f2f1df8ab311ebcfc7dd83ac1cef08a6b 0x07103219e851b7bc16b07748e4427d38c2b884b06d524b52dbae9a04386b8331
OpaqueNameMirror 0x01d0821469d516a81cddfd6ee6d88b5073657db561d9de2f6e723649ad4ff14a 0x0355472f6f63510795a34d3a39a62424a1658f1311b9237bc87509811c508328
StealthAccount (class only) 0x04794bab07198e0585d2d7951dbc5860fba47fea2a15d227ca3237b7b9e484ed counterfactual per payment
StealthAnnouncer 0x625f476e46225bf2c050d956201d6be75183dd50d9f20d31c408f09fcbaa4bb 0x003b8258e84e6feec93239b442e6a91f532fda35fed67de4093b1d97150d2aa2
StealthMetaAddressRegistry 0x6203ad71f44b9b4371a9b2f0394cef08045c3c8e14b86f34fee7517acafb819 0x047ff90c491384ecf8dba8b32b1eea7947f850ea92ddc196edcb0f508acff874

The OpaqueReputationVerifierV2 above is the current build (adds verify_and_consume, which PsrGate uses); the earlier instance 0x017a56e5…83eb6 carried the first live proof verification below.

First live announcement: CSAP canonical vector 1 announced through the @opaquecash/stealth-chain-starknet call builder and decoded back by its adapter (tx 0x006efbb9…42d8, block 12158304).

First live stealth payment: OpaqueClient.buildStarknetStealthSend produced an ERC-20 transfer + announce multicall (tx 0x6c7b7905…98fa2, block 12167420) that funded the counterfactual stealth account 0x352ca9…abfd5 with 0.001 STRK; the recipient then scanned Starknet and owned the payment end-to-end.

First live sweep: the recipient reconstructed the stealth account from an owned output (OpaqueClient.buildStarknetSweep), self-deployed it (deploy_account 0x6713f89c…) paid from its own balance, and transferred the funds out (0x3a4cf7f1…) — the full send → scan → own → sweep lifecycle proven on-chain.

First live PSR-gated entry (the integration thesis): PsrGate.enter accepted the committed V2 credential proof (tx 0x0668c1f0…) — has_entered flipped true and entry_count reached 1 — while a replay was rejected on-chain with nullifier already used. A stealth identity enters only by proving, in zero knowledge, a valid attestation under the gate's schema, once per scope.

ONS mirror (L1→L2): OpaqueNameMirror is paired with StarknetOnsMirrorSender on Ethereum Sepolia (0xbdB9D13B8b5f94cBAf924d5AAEA216f66B9131Ef), allowlisted as the mirror's L1 emitter. First live delivery: a name upsert published from the sender (L1 tx 0x74b307bf…) travelled the native L1→L2 path (Ethereum Starknet Core → sequencer → handle_mirror); resolve_meta on the mirror then returned the exact spend/view keys sent from Ethereum, so the two halves are proven end-to-end, not just wire-compatible in tests.

The StealthAccount class hash is a consensus-critical CSAP constant (spec/starknet-integration.md §7.1): every stealth address is a counterfactual deployment of this exact class, so redeclaring a changed class moves all future addresses.

Admin / deployer: sncast account opaque_deployer (0x029db6e717afae61c5693afb65da25fb71974ccfe6705a8cc9282a8c9d725ceb, local dev keystore — rotate to a multisig before anything real).

Live proof verification: tx 0x05586dae…79c56 (block 12156896) — ReputationVerified emitted, nullifier consumed (is_nullifier_usedtrue), replay rejected at estimation.

About

Starknet implementation of the Opaque privacy protocol (PSR + CSAP stealth layer)

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages