fix: add STARK proof verification to unshield (security fix) - #4
Open
zk-sable wants to merge 1 commit into
Open
Conversation
SECURITY FIX: The unshield instruction previously accepted merkle_root and nullifier_hash without any ZK proof verification, allowing anyone to drain the shielded pool by submitting arbitrary values. This commit adds STARK proof verification by reading from Murkl's on-chain verifier buffer (StArKSLbAn43UCcujFMc5gKc8rY2BVfSbguMfyLTMtw). The verifier buffer contains a STARK proof that has been cryptographically verified on-chain, with the following public inputs: - commitment: the note commitment - nullifier: prevents double-spend - merkle_root: proves membership in the current tree Flow: 1. User generates STARK proof off-chain (Murkl WASM prover, ~2s) 2. Upload proof to verifier buffer (init + upload_chunk + finalize) 3. Call unshield with verifier_buffer account 4. Program verifies: finalized=true, nullifier matches, root matches Benefits over previous SNARK approach (circom/Groth16): - No trusted setup ceremony required - Post-quantum secure (128-bit security level) - ~31K CU per verification on Solana - Transparent — all parameters are public New error variants: InvalidProofBuffer, ProofNotVerified, ProofNullifierMismatch, ProofMerkleRootMismatch See: https://github.com/exidz/murkl/blob/main/docs/INTEGRATION.md
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.
🔒 Security Fix: Add STARK Proof Verification to
unshieldThe Problem
The current
unshieldinstruction acceptsmerkle_rootandnullifier_hashas parameters but does not verify any ZK proof. The existing comment even acknowledges this:This means anyone can drain the shielded pool by calling
unshieldwith arbitrary values. The nullifier PDA prevents double-spend of the same nullifier, but an attacker can generate fresh nullifiers at will.The Fix
This PR adds STARK proof verification by integrating with the Murkl STARK Verifier (
StArKSLbAn43UCcujFMc5gKc8rY2BVfSbguMfyLTMtw), which is deployed on devnet.The
unshieldinstruction now requires averifier_bufferaccount that:finalized == true(proof was cryptographically verified on-chain)nullifierthat matches the withdrawal'snullifier_hashmerkle_rootthat matches the withdrawal'snew_rootHow It Works
The verifier buffer layout (from Murkl SDK):
Changes
instructions/unshield.rs: Addedverifier_bufferaccount + proof verification logicerrors.rs: Added 4 new error variants for proof verification failuresWhy STARKs over SNARKs?
Your circuit files use circom (Groth16 SNARKs). STARKs offer advantages for a shielded pool:
The 8.7KB proof size is larger but is uploaded in chunks before verification — the actual
unshieldcall just reads the buffer (minimal CU).Integration with Existing Circuits
Your circom circuits define the correct transfer logic (2-in-2-out, value conservation, range checks). The STARK prover would implement the same constraints in the M31 field. The circuit logic is compatible — only the proof system changes.
Testing
To test on devnet:
verifier_bufferin theunshieldcallLive demo: https://murkl-relayer-production.up.railway.app
Breaking Change
This is a breaking change — existing
unshieldcallers must now provide averifier_bufferaccount. This is intentional since the previous version was insecure.Related: #2 (STARK Integration issue)