Repository navigation
Fix/zk verifier hardening - #175
Merged
Merged
Conversation
…s (deny_version, genesis) — a denied key is refused on register, activate and unretire and never verifies
…2 (a snarkjs key without phase 2), with which anyone could forge proofs
…y take that key again — a client that cached what it means is never served another key under it
… vectors — a production key, its stored prepared form and a real proof, plus the refusals and constants v1 depends on
…verifier 3.4.0 — on-chain denied keys, removed versions keep their key, keys without phase 2 refused, host-function golden vectors (spec 20)
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
Hardens how circuit verifying keys are managed, so a Root mistake can no longer reopen an unsound circuit or install a key anyone can forge proofs with, and pins the frozen Groth16 host function to golden vectors. Ships in runtime spec 20 with
pallet-zk-verifier0.19.0 andorbinum-zk-verifier3.4.0.Why
k + i·lspending-key aliases give one note several nullifiers (fixed in v3), so retiring them was the only guard. Nothing enforced it:unretire_versionchecked arity alone, and the same key could be registered again under another version number.deltais±gammaor±the G2 generator — what snarkjs leaves when a circuit's phase 2 never ran. With such a key,(α, β, −L(x))verifies any statement using public key data only.bn254_groth16_verify(v1) is frozen — executed blocks and syncing nodes depend on it answering the same — but its result depended on crate constants and arkworks' serialized layout with nothing pinning them.Changes
pallet-zk-verifier0.19.0DeniedVkHashes: on-chain, append-only record of unsound keys. A denied key is refused byregister_verification_key,batch_register_verification_keys,set_active_versionandunretire_version(DeniedVerificationKey), and a stored one verifies nothing, active or not. No call removes an entry,remove_verification_keyandpurge_circuitincluded.deny_version(circuit_id, version)(Root, call index 8): retires the version and records its key's hash (VerificationKeyDenied). Never the active version; an already-recorded hash is refused (KeyAlreadyDenied).GenesisConfig::denied_vk_hashes(Vec<H256>,0x…in the chain spec): the record a new chain starts with, written before any genesis key.RemovedVkHashes:remove_verification_keyrecords the removed key's hash, and the version can then only take that same key again (VersionReused). Append-only;purge_circuitleaves it.deny_versionestimated fromretire_versionuntil re-benchmarked.orbinum-zk-verifier3.4.0VerifyingKey::to_ark_vkrefusesdelta ∈ {gamma, −gamma, G2, −G2}. Every path that validates a raw key (registration,prepared_bytes,Groth16Verifier,set_active/unretirearity checks) refuses it withInvalidVerifyingKey. Keys already stored in prepared form are not re-checked; the seven on testnet have an independentdelta.tests/host_function_v1.rs: golden vectors — the production unshield v3 key, its prepared form as stored on chain, and a real proof. They pin that the host function andverify_preparedagree and accept it, refuse tampered, non-canonical and malformed input, that preparing the key still yields the stored bytes, and the constants v1 depends on. A failure means a change alters what v1 answers: add a#[version(N)]instead of updating the fixtures.After the upgrade (testnet)
Root records the unsound versions, all already retired:
Testing
pallet-zk-verifier: 173 passed.orbinum-zk-verifier: 94 unit, 10 real-proof integration, 9 golden.pallet-shielded-pool: 384. Runtime: 85. Benchmarks (runtime-benchmarks): 9, includingdeny_version. clippy andcargo fmt --checkclean; release build OK.deniedVkHashes, using real proofs from the production circuit artifacts:deny_version, the v2 spend is refused and every way back — unretire, register as another version, register after removal, batch — fails. v3 transfer and unshields keep working.deltas are refused by register and batch. A forged unshield that snarkjs confirms valid for such a key is refused on chain, with no funds moved and the nullifier unspent.deltacheck disabled, the same forged unshield was included and paid 5 ORB with no note and no secret.verify_proofagainst the active unshield v3 givesProofVerified; a tampered proof or a changed input givesVerificationFailed.