Version history of the Opaque protocol specifications, newest first. Each spec carries its own status badge; this file records the cross-cutting decisions and normative changes from CSAP v1 forward.
- starknet-integration.md added. Design document (pre-implementation) for
extending Opaque to Starknet with STRK20 as the integration target, based on
adversarially verified research of 2026-07-18. Core positions: PSR as the
eligibility layer STRK20 lacks (three permission tiers: permissionless
in-pool gates, the
ScreeningAttestationseam, an in-circuit SNIP); a Garaga-generated Groth16-BN254 verifier plus u256-limb wrapper as the only on-chain Cairo footprint; counterfactual secp256k1 account-class custody keeping the 98-byte meta-address; native L1→L2 messaging + Hyperlane for mirroring (Wormhole has no Starknet support); and an explicit do-not-port list (privacy pool, Shamir escrow backstop, StealthTokenSweep forwarder). Normative constants (account class hash, salt rule, chain id) land in CSAP.md / UAB.md / payload-format.md when implementation starts.
- Circuit-gated findings documented (OPQ-018, OPQ-024). The reputation leaf
commits no revocation/expiry, so a revoked/expired credential stays provable
within the root TTL (PSR.md §5); the pool and disclosure nullifier hashes omit
commitment-specific data, so two notes sharing a
nullifiercollide (privacy-pool.md §7, conditional-disclosure.md — the "per (commitment, context)" claim is corrected to per (nullifier, context)). The complete fixes are in-circuit and gated on a new production trusted-setup ceremony; the deployed circuits carry SECURITY NOTE comments. Interim mitigations are now normative: the SDK draws a fresh(nullifier, secret)per deposit, and the indexer re-registers reputation roots excluding revoked/expired leaves on every revocation. - Off-chain / implementation hardening (no protocol change): relayer submit
now rejects post-deadline execution and reclaims job-account rent on slash/cancel
(OPQ-023/036); the pool rejects self-recipient payouts and enforces a
rent-exemption floor (OPQ-028); the reputation verifier's nullifier flag is made
load-bearing (OPQ-040); attestations can be re-issued after revoke/expiry
(OPQ-026); disclosure policy group keys are range-checked to
< n, resolvers must be executable, native announce payloads are size-bounded (OPQ-042); the scanner reduces the hashed secret mod n (OPQ-025); the relayer gateway verifies bid signatures before caching (OPQ-037); the PSR prover pins proving artifacts by digest and requires a real leaf preimage (OPQ-030/038); the app clears sensitive browser storage on disconnect and ships a CSP (OPQ-014/015/031); EVM contracts gained ASP-root history + validation, age-based root eviction, UAB source-chain + zero-emitter checks, submit deadline, and fee-without-recipient guards (OPQ-016/017/022/023/027/029/033); ONS fixed the revoke CEI order, zero-authority overwrite, out-of-order sequences, and added an emitter-change timelock (OPQ-019/020/021/043); the ASP separated its ENS pointer key from the authority (OPQ-035). CI workflows gained least-privilegepermissions:blocks.
- PSR.md §4–§5 — PSR public signals are field-reduced and carried as 32-byte
field elements (security fix OPQ-008).
external_nullifier = keccak256(scope)and any SHA-256-derivedattestation_idare now reduced modulo the BN254 scalar fieldrat a single source of truth, so the witness signal, the on-chain public input, and the Poseidon preimage are the identical reduced value — the un-reduced 256-bit value made the EVM verifier'scheckFieldrevert. The Solanareputation-verifiernow takesattestation_id/external_nullifieras[u8; 32]big-endian (mirroring the EVMuint256) instead ofu64, which had silently capped the domain at 2^64 and made spec/EVM-encoded proofs unrepresentable on Solana. - PSR.md §5 — reputation verifier schema binding + initializer authorization
(security fixes OPQ-006, OPQ-007).
OpaqueReputationVerifierV2gains an admin-set schema-registry binding: when configured, a proof'sattestation_idmust reference a live registered schema, so a proof cannot claim reputation under a never-registered schema. (Binding the issuer still requires the in-circuit commitment to the schema authority — the tracked remediation.) The Solana privacy-pool and reputation-verifierinitializehandlers are now gated to the program's upgrade authority, closing the unprotected-initializer front-run that could seize the ASP authority or record a rogue Groth16 verifier; the verifier's Groth16 program is additionally pinned by address. - Off-chain hardening (no spec change): OPQ-003/005/009/010/011/012/013. Relayer
nodes re-derive the payload hash and simulate the inner call before bonding (and
submit atomically on Solana); the ASP never advances its Solana cursor past an
undecoded deposit and recomputes each
label = Poseidon(scope, leafIndex)rather than trusting the RPC; the Solana relayer validates the job account before slicing; the V2 attestation scanner drops rogue-issuer traits by default; the relayer client binds bids to the user's own jobId/chain; and the frontend caches the setup signature under a non-extractable IndexedDB key.
- ONS.md §3 — ons-mirror revoke tombstones instead of closing (security fix
OPQ-004). Closing the mirror PDA on revoke destroyed the stored
wormhole_sequence, so a later-delivered but lower-sequence upsert VAA could re-create the record fresh (the monotonic floor is only skipped for a brand-newname_hash == 0PDA) and resurrect a revoked name at stale keys. A revoke now updates the record in place: it advanceswormhole_sequence, keepsname_hash, zeroes the key material, and sets a newrevokedflag.OnsRecordgains a trailingrevoked: bool(account +1 byte); resolvers treat a revoked record as unresolved.
- CSAP.md §2.1–§2.3, §2.8 — native ed25519 Solana stealth accounts
(security fix OPQ-002). The Solana stealth account is now a native ed25519
key derived by a DKSAP tweak on the ed25519 curve: the meta-address gains a
third 32-byte half
S_ed = reduce(s_ed)·B(so it is 98 bytes,V‖S‖S_ed;s_edcomes from a third HKDF block, leavingv/sunchanged), the address isP_ed = S_ed + h_ed·Bwithh_ed = SHA-512("opaque-solana-stealth-v2" ‖ sec) mod L, and the one-time spend scalara = (reduce(s_ed) + h_ed) mod Lis reconstructable only withs_ed. The withdrawn construction seeded the account fromSHA-256("opaque-solana-stealth-v1" ‖ uncompressed(P_stealth)), which the payer knows — letting the payer recompute the account key and sweep the funds. Sender funding and view-only balance scanning still work from public material; only the spend side is recipient-bound. Legacy 66-byte meta-addresses stay valid for Ethereum and fail closed on Solana. The Solanastealth_registrynow stores 98-byte meta-addresses. - CSAP.md §2.7 (Solana registry) —
register_keys_on_behalfnow verifies an Ed25519 signature (security fix OPQ-001). The instruction previously performed no authorization check, so anyone could overwrite any account's meta-address. It now requires an Ed25519SigVerifyinstruction (introspected via the Instructions sysvar) proving theregistrantsigned a canonical message bound to the program id, registrant, scheme, current nonce, and meta-address; the nonce is consumed to prevent replay.
- relayer-market.md §9.4 added (sweep submission interface); §9.2 and §3.4
amended. The fee-in-token sweep gains a normative HTTP interface on the node
gateway:
POST /v1/sweep(chain-tagged EVM forwarder calldata or partially signed Solana transaction; synchronous, not re-gossiped) andGET /v1/sweep/info(per-chain operator + EVM forwarder). §9.2 now requires co-signing over the owner's original blockhash and recommends the fee-payer and token-program-whitelist guards; §3.4 lists the new routes. The previously optional Solana in-token fee transfer is implemented end-to-end (SDK builds theamount - fee/feesplit; the reference node serves the endpoint).
- relayer-market.md §9 added (fee-in-token sweep). Escrow-free, gasless
withdrawal of an ERC-20/SPL balance from a one-time stealth address that holds
the token but no native gas. Ethereum uses a stateless
StealthTokenSweepforwarder: the owner signs an EIP-712Sweepauthorization (token,owner,destination,value,fee,nonce,deadline, domainOpaqueStealthTokenSweep/1) plus an EIP-2612permit; the relayer submits, pays gas, and is reimbursedfeein-token while the destination receivesvalue - fee. Solana needs no program: the relayer is the fee payer while the reconstructed stealth keypair signs the SPL transfer as authority. No bond or slashing (no escrow at risk); destination, amount, and fee are owner-signed, so a relayer can only execute the signed sweep or nothing. Complements, and is independent of, the native-fee job escrow (§5).
- conditional-disclosure.md Draft v1. Threshold viewing keys: selective
M-of-N disclosure without full viewing-key exposure. Active path is a
pool-scoped Groth16 proof (
conditional_disclosure.circom: state-tree membership +value > thresholdqualification + disclosedvalue/label) gated by a FROST(secp256k1) / BIP-340 custodian quorum signature verified on-chain (ecrecover trick /secp256k1_recover) over the proof'scontext; Shamir splitting of the viewing key is the documented recovery backstop (reconstruction reveals the full key — normative warning). Closes the conditional-disclosure nullifier reservation:Poseidon(nullifier, context, DOMAIN_DISCLOSURE)withDOMAIN_DISCLOSURE = keccak256("opaque/disclosure/v1") mod r. - nullifier-registry.md updated: privacy-pool row marked live
(
Poseidon(nullifier)), disclosure row specified; arity-based layout separation noted. - privacy-pool.md Draft v1. Amount privacy via the Privacy Pools
(Buterin/Soleimani association-set) construction:
commitment = Poseidon(value, label, Poseidon(nullifier, secret)), an append-only state tree of commitments and an ASP-curated association tree of labels, a partial-withdrawal Groth16 proof (withdrawal.circom) that folds state-membership + association-membership + nullifier + value-accounting +contextbinding into one proof, and a standaloneassociation.circomASP statement. Verified ERC-8065 is the unrelated ZK Token Wrapper, not an association-set pool — no ERC-8065 compatibility is claimed. Mainnet gated on circuit + contract audits, a production trusted-setup ceremony, and a legal opinion.
- relayer-market.md Draft v1. Gas-private submission market: combined
stake-registry + job-escrow contract per chain so submit-or-slash is
on-chain-verifiable (create/accept-bond/submit/slash/cancel), GossipSub
opaque/jobs/v1wire formats (advert, stake-backed bid, NaCl-box payload delivery), HTTP gateway intake, bond = fee, native-asset pre-funded fees (fee-in-proof deferred), and UAB/ONS VAA delivery as a node duty replacing the Phase-1 central relay. - ONS.md Draft v1. Opaque Name Service:
*.opq.ethsubnames over an ENSIP-10 wildcard resolver on Ethereum (canonical), Wormhole-mirrored read-only PDAs on Solana, claims from either chain. Normative consistency model is eventually consistent, canonical-chain-wins — no atomic cross-chain claim exists or is claimed. Defines the 164-byte mirror payload (Ethereum → Solana), the variable-length claim payload (Solana → Ethereum), the provisional-claim reconciliation states (pending/confirmed/lost/expiredwith a 24 h pending window), and the four SDK resolution paths. Meta-addresses are sourced from the deployed ERC-6538 registry, not a parallel store.
- V1
stealth_attestationretired. Circuit removed fromcircuits/(history retains it), V1 entry point and verification key removed from the Solanagroth16-verifier(redeployed), V1 artifacts removed from the app. PSR.md is V2-only; the V1↔V2 mismatch warnings are resolved. - TypeScript prover migrated to V2.
@opaquecash/psr-proverbuilds the V2 leaf (Poseidon(stealth_pk, schema_id, issuer_pk_x, trait_data_hash, nonce)) with deterministic dev-mode defaults, parses the four V2 signals, and defaults to thecircuits/v2artifacts. A freshly client-generated proof was verified on devnet through the SDK submit path — the full pipeline (prove → root registration → on-chain verify → nullifier consumption) is V2 end to end.
- Solana on-chain PSR V2 complete. Devnet
groth16-verifierandreputation-verifierupgraded: real V2 verifying key, fixed field constants,verify_reputationrewritten to the V2 signal layout (nullifier_hashas input viaverify_proof_v2CPI). First real proof verified on devnet with nullifier consumption and replay rejection. Client gap: the TypeScript prover still targets V1 and is the remaining migration.
- V2 verification key pinned. A real Groth16 proof generated with the
production V2 proving key is now committed as a fixture in
circuits/test/fixtures/v2/and verified in CI against the snarkjs vkey, the EthereumGroth16VerifierV2, and the Solanagroth16-verifierprogram. The vkey transcribed in both on-chain verifiers is thereby locked to the circuit. - Solana verifier corrections (implementation, no spec change). The
groth16-verifierprogram had its BN254 scalar/base field constants swapped (negation usedrinstead ofq, so no real proof could pass) and carried placeholder V2 IC points. Both fixed; the real V2 verification key installed. The deployed devnet program predates the fix and requires an upgrade. nullifier-registry.mdDraft v1. Canonical 32-byte BE field-element encoding, consume-once registry semantics, domain separation, chain-local replay model withexternal_nullifierchain scoping.- CSAP §2.9 added (ONS seed).
com.opaque.metaENSIP-5-style text-record key for resolving meta-addresses through existing name services; read path only until ONS ships.
- UAB live on testnet, both directions (Ethereum Sepolia ⇄ Solana devnet):
UABSender/UABReceiveron Sepolia,announce_with_relay+uab-receiveron devnet, central delivery relayer as a documented stop-gap. UAB.md and payload-format.md describe the deployed behaviour. payload-format.mdDraft v1. 96-byte chain-neutral announcement payload; correctedsource_chain_idto Wormhole chain ids (earlier draft used an ASCII tag for Solana).
- Execution decisions D1–D5 resolved, fixing spec-relevant ground rules:
V2 (
stealth_reputation) is the canonical circuit and V1 (stealth_attestation) is deprecated and frozen (D3); the Stellar implementation is archived and out of spec scope (D5). - PSR.md draft (canonical V2). Schema registry, attestation engine, V2
circuit with public signals
[merkle_root, attestation_id, external_nullifier, nullifier_hash]. The V1↔V2 signal-layout incompatibility is documented as normative. - CSAP.md Draft v1. DKSAP over secp256k1 + Keccak-256 with scheme id
1, HKDF domain"opaque-cash-v1", 66-byteV‖Smeta-address (EIP-5564 byte-order deviation recorded as an open decision), 8-bit view tags, ERC-5564/6538-compatible announcement and registry interfaces, and the cross-validated test vectors incircuits/test/test_vectors.json.
Specs are individually versioned by their status line (Draft v1, …). A breaking change to deployed behaviour — key derivation, payload layout, circuit public signals, registry interfaces — requires either a new scheme id (CSAP), a new payload version (UAB), or a new circuit version (PSR), plus an entry here. Editorial changes are not recorded.