$npx -y skills add keep-starknet-strange/starknet-agentic --skill snip-36SNIP-36 virtual block proving on Starknet. Trigger on \"virtual block\", \"SNIP-36\", \"off-chain proof\", \"anonymous vote\", \"heavy computation off-chain\", \"prove a transaction\". Covers Cairo virtual contract, proof server, starknet.js integration, and on-chain verification
| 1 | # SNIP-36 |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | SNIP-36 allows executing a single `INVOKE_TXN_V3` off-chain against a reference Starknet block's state, then submitting a stwo-cairo proof on-chain. The proof extends the standard v3 transaction hash by appending a `proof_facts_hash`. Contracts verify this via `get_execution_info_v3_syscall`. |
| 6 | |
| 7 | **Core value:** Run arbitrary Cairo logic off-chain (heavy computation, privacy checks, game outcomes, attribute proofs) and commit only the verified result on-chain — without revealing private inputs. |
| 8 | |
| 9 | **Spec:** https://community.starknet.io/t/snip-36-in-protocol-proof-verification/116123 |
| 10 | |
| 11 | **Reference implementation:** https://github.com/starknet-innovation/snip-36-prover-backend |
| 12 | |
| 13 | --- |
| 14 | |
| 15 | ## Quick Start |
| 16 | |
| 17 | 1. Review the [operator checklist](references/operator-checklist.md). |
| 18 | 2. Add a virtual `create_proof` function that emits one L2->L1 message. |
| 19 | 3. Prove an unsigned virtual tx with `snip36 prove virtual-os`. |
| 20 | 4. Submit `verify_result` with `{ proof, proofFacts }` and the decoded message. |
| 21 | |
| 22 | ## When to Use |
| 23 | |
| 24 | - The user asks for SNIP-36, virtual block proving, off-chain Starknet proof generation, or proof-backed on-chain verification. |
| 25 | - The workflow needs heavy Cairo computation, privacy-preserving inputs, anonymous voting, secret whitelist checks, or replay-safe nullifier patterns. |
| 26 | - The implementation needs a Cairo virtual function, proof server, starknet.js signing flow, and on-chain `verify_result` contract pattern. |
| 27 | |
| 28 | ## When NOT to Use |
| 29 | |
| 30 | - The user needs a normal Starknet transaction that should be broadcast and fee-estimated through standard RPC. |
| 31 | - The proof cannot run on a backend with native binaries, disk, and about 18 GB RAM. |
| 32 | - The security model requires SNOS-native verification instead of Phase 1 sequencer-side proof verification. |
| 33 | |
| 34 | --- |
| 35 | |
| 36 | ## Use Cases |
| 37 | |
| 38 | - Heavy computation: prove a large hash or algorithm result, then store only the verified output. |
| 39 | - Private attributes: prove age, whitelist membership, or voting weight with a nullifier and public boolean/result. |
| 40 | - Provable games: commit coin flips, seeds, bets, and outcomes for on-chain settlement. |
| 41 | - ZKThread or shard transitions: prove `{ old_root, new_root, ... }` before updating L2 state. |
| 42 | |
| 43 | --- |
| 44 | |
| 45 | ## Generic 3-Phase Pattern |
| 46 | |
| 47 | ```text |
| 48 | PHASE 1 — CREATE (off-chain build) |
| 49 | Build a signed INVOKE_TXN_V3 that calls the virtual function. |
| 50 | Never broadcast. Includes public_input + private_input in calldata. |
| 51 | |
| 52 | PHASE 2 — PROVE (proof server) |
| 53 | POST { blockNumber, tx } → snip36 prove virtual-os |
| 54 | Returns { proof, proofFacts, l2ToL1Messages } |
| 55 | Duration: ~40-50s, ~18 GB RAM |
| 56 | |
| 57 | PHASE 3 — VERIFY (on-chain) |
| 58 | execute(verify_call, { proof, proofFacts }) |
| 59 | Contract reads proof_facts, recomputes message hash, applies state change. |
| 60 | ``` |
| 61 | |
| 62 | --- |
| 63 | |
| 64 | ## Part 1 — Cairo Contract |
| 65 | |
| 66 | ### Scarb.toml requirements |
| 67 | |
| 68 | ```toml |
| 69 | [[target.starknet-contract]] |
| 70 | allowed-libfuncs-list.name = "all" # required for get_execution_info_v3_syscall |
| 71 | ``` |
| 72 | |
| 73 | ### Virtual function pattern |
| 74 | |
| 75 | ```cairo |
| 76 | // Called VIRTUALLY (by proof server). Never call directly on-chain. |
| 77 | // public_input → included in L2→L1 message (visible to verifier) |
| 78 | // private_input → used in computation but NEVER revealed on-chain |
| 79 | fn create_proof( |
| 80 | ref self: ContractState, |
| 81 | public_input: PublicInput, |
| 82 | private_input: PrivateInput, |
| 83 | ) { |
| 84 | // 1. Compute result using both inputs |
| 85 | let result = heavy_computation(public_input, private_input); |
| 86 | |
| 87 | // 2. Commit result as L2→L1 message — this becomes the proof output |
| 88 | let mut payload: Array<felt252> = array![]; |
| 89 | // serialize fields the verifier will need: |
| 90 | payload.append(public_input.field1); |
| 91 | payload.append(result); |
| 92 | send_message_to_l1_syscall( |
| 93 | to_address: 0, // unused for SNIP-36 (no L1 delivery) |
| 94 | payload: payload.span() |
| 95 | ).unwrap(); |
| 96 | } |
| 97 | ``` |
| 98 | |
| 99 | ### On-chain verify function pattern |
| 100 | |
| 101 | ```cairo |
| 102 | // Called ON-CHAIN with proof attached via { proof, proofFacts }. |
| 103 | fn verify_result( |
| 104 | ref self: ContractState, |
| 105 | public_message: PublicMessage, // decoded from l2ToL1Messages[0].payload |
| 106 | ) { |
| 107 | // 1. Read proof_facts committed by SNIP-36 |
| 108 | let info = starknet::syscalls::get_execution_info_v3_syscall() |
| 109 | .unwrap_syscall().unbox(); |
| 110 | let proof_facts = info.tx_info.unbox().proof_facts; |
| 111 | |
| 112 | // 2. Recompute message hash from the submitted public_message |
| 113 | let message_hash = compute |