Snip 36: in protocol proof verification


snip: 36
title: In protocol S-Two verification
author: Ohad Barta
description: Introduce capability of verifying S-Two proofs within Starknet
discussions-to: https://community.starknet.io/t/snip-36-in-protocol-proof-verification
status: Draft
type: Standards Track
category: Core
created: 2026-02-15

Simple Summary

This SNIP proposes adding support for S-Two proof verification within the Starknet protocol. The document outlines the motivation and specifies a concrete mechanism to facilitate applications that use client-side proving on top of Starknet.

Motivation

Enabling applications to execute logic off-chain and prove it to Starknet unlocks two major domains that are impractical with naĂŻve on-chain execution:

Infinite scaling with ZKthreads and sharding - Starknet, despite being cost-effective, still has finite resources (storage, execution power, etc.). Transactions from one app must compete with others. Even ignoring congestion, networking, database, and sequencing expenses are not always aligned with application preferences. Enabling multiple Shards for application-specific logic anchored to Starknet would empower applications with more control over their environment while seamlessly leveraging Starknet’s liquidity and ecosystem.

Privacy: Proof-verification allows transactions to modify hidden/encrypted parts of a state in a secure way, which is still unknown to Starknet’s users and fullnodes.

Currently, Starknet lacks native proof verification. While creating an S-Two Verifier as a smart contract and submitting an S-Two proof in transaction calldata is possible, this approach is insufficient. Including the full proof (often tens of thousands of felts) in calldata exceeds the present transaction limit (5K felts). This configurable cap limits block usage to prevent excess burden on the Starknet p2p network. Further, large proofs are expensive for the feeder gateway to relay.

Although proofs can be split across multiple transactions (as Starknet does on Ethereum), this remains prohibitively expensive and degrades the usability of L3 and privacy-focused use cases.

​

Rationale

What is proven?

While any claim can be theoretically proven to Starknet, this SNIP suggests focusing on proving one particular claim: An execution of a Starknet transaction.

Reasons for proving a Starknet transaction:

  • Significantly simpler:
    1. For protocols: developers can write standard Starknet contracts and rely on SNOS functionality without implementing custom Cairo proof systems.
    2. For Starknet: The set of acceptable programs to prove is constrained (e.g., tied to known Starknet OS program hashes), simplifying verification logic and bounding proof size variability.
  • Sufficient for the main motivations:
    1. ZKThreads can execute large transactions off-chain and prove them via a cheaper on-chain transaction.
    2. Privacy applications can implement business logic in Cairo, use Starknet accounts and wallets, and prove a virtual block to Starknet.

Mindset behind the design - this is Phase 1

The following is knowingly omitted from the scope of this SNIP. Depending on adoption and feedback of this feature, these follow-up features will get prioritized and implemented:

  • Trustlessness - In Phase 1, proofs are verified solely by Starknet consensus. A future phase will integrate SHARP so proofs are ultimately verified on Ethereum, ensuring correctness beyond Starknet consensus.
  • Better support for ZKThreads - Future versions may allow a single proof to attest to multiple transactions or entire blocks.
  • Better support for privacy -Currently, privacy is de facto (data is computationally infeasible to extract). A future phase may support fully zero-knowledge proofs.
  • Allowing the full builtin set - To facilitate more efficient provers and verifiers, the builtins add_mod, mul_lod and Keccak are currently excluded from the claims that are proven. They can be easily added given use-case that needs them

High-level design and the rationale behind it

The aim is to enable transactions to reference off-chain execution via proofs, minimizing costs and avoiding issues seen in purely contract-based approaches.

Initially, proof verification is performed by Starknet consensus. Users need only rely on the assertion that “Starknet consensus has verified it.”

​

Therefore, a new optional field, proof, is introduced for an invoke tx*.* This field will be sent with the transaction to the gateway and will be echoed within the Starknet mempool network - but it will not be available for consumption through the feeder gateway, and will not appear explicitly in the block, and thus its networking footprint will be reduced.

​

However, some elements should reach Starknet itself to correlate the execution with the proof. Thus, another field that will be added to the transactions is proof_facts. Think of it as a TLDR of the proof that Starknet needs to care about. The exact structure is discussed in the “implementation” section, but it roughly includes:

  • Program hash - the actual program that proof proved
  • Block hash - the real Starknet block that the “virtual Starknet block proven” continued.
  • MessageHashes - a list of instructions to perform on Starknet as a result of these proofs.

The raw messages to perform appear in the transaction’s calldata, so the on-chain logic can access them freely and check their connection to message hashes that appear within the proof_facts.

​

On-chain, proof facts are retrieved by calling a new version (v3) of the get_execution_info syscall. Notice:

  • The above fields are optional. If they don’t exist, the tx hash will not include them in the calculation. This means that transaction v3, as it is today, will not be changed.
  • Same with get_execution_info: the answer for current contracts that call this syscall will not change. Contracts will need to specify that they call the new version to retrieve the proof.

Implementation

The program to prove:

The program proves the execution of a “virtual/offchain” transaction that occurs on top of a given Starknet block, resulting in a virtual Starknet block to be proven. The OS of the virtual Starknet block is slightly different from the regular Starknet OS and has somewhat reduced functionality. The reason: some OS calls in Starknet are currently trusted. For example, the timestamp. The return values from these calls could have been tampered with by the app if the vanilla Starknet OS had been invoked.

​

The OS program of the virtual block is thus very similar to, but different from, the regular OS. You can notice some files in the SNOS repo have “virtual” in their name. These files replace the equivalent files (w/o “Virtual” in the name) when having the virtual OS.

​

The claim to prove:

Applications that integrate with this feature should deploy to Starknet contracts that include the logic that should be proven to Starknet. The proper execution of these transactions is the claim to be proved.

​

Verification process:

A S-Two Verifier written in Rust is added to the gateway, and the sequencer has the responsibility of verifying the proof , checking the connection between the proof and the proof facts, and propagating the transaction to other sequences only if these checks have passed.

Details and code are available here.

​

How to invoke the prover:

The proving service is a JSON-RPC 2.0 server (implemented in the starknet_os_runner crate within the Starknet sequencer repo) that generates proofs for Starknet transactions.

​

dApp developers that want to create proofs as part of the flow can setup a provng server, and request it to perform transaction proving by calling the prove_transaction method (see implementation here). The method takes a block Id and an invoke transaction, executes the transaction against that block’s state, and returns a proof.

​

Here is an example request:

​

curl -X POST http://localhost:3000 \

-H “Content-Type: application/json” \

-d '{

“jsonrpc”: “2.0”,

“method”: “starknet_proveTransaction”,

“params”: [

 {"block\_number": 800000},

 {

   "type": "INVOKE",

   "version": "0x3",

   "sender\_address": "0x...",

   "calldata": \["0x..."\],

   "signature": \["0x...", "0x..."\],

   "nonce": "0x...",

   "resource\_bounds": {

     "l1\_gas": {"max\_amount": "0x...", "max\_price\_per\_unit": "0x..."},

     "l2\_gas": {"max\_amount": "0x0", "max\_price\_per\_unit": "0x0"},

     "l1\_data\_gas": {"max\_amount": "0x0", "max\_price\_per\_unit": "0x0"}

   },

   "tip": "0x0",

   "paymaster\_data": \[\],

   "account\_deployment\_data": \[\],

   "nonce\_data\_availability\_mode": "L1",

   "fee\_data\_availability\_mode": "L1"

 }

],

“id”: 1

}’

​

The response contains:

​

  • proof — The generated proof (base64-encoded).
  • proof_facts — Proof facts for verification (array of felts).
  • l2_to_l1_messages — Messages sent from L2 to L1 during execution.

New tx - v3 structure and how new fields are treated

The exact suggested transaction structure can be found here. Specifically, it contains the new fields “proof” (array of uint32) and “proof facts” (array of felt252).

​

The “proof fact” array is accessible only through a dedicated syscall, “get execution info,” and only within the context of the transaction that included it.

You can see the new syscall here.

A small example of the new syscall usage:

​

use starknet::SyscallResultTrait;

#[starknet::contract]

mod Example {

#[external(v0)]

#[raw_output]

fn get_proof_facts(self: @ContractState) -> Span<felt252> {

   let info \= starknet::syscalls::get\_execution\_info\_v3\_syscall()

       .unwrap\_syscall()

       .unbox();  

   info.tx\_info.unbox().proof\_facts

}

}

​

Updates for RPC 0.10.1 that are offered as part of this feature

New RPC & Transaction Capabilities

  • Added support for proof submission in Invoke V3 transactions (`proof` and `proof_facts`)
  • Added `initial_reads` flag to transaction simulation
  • Added `initial_reads` flag for block tracing
  • Added `proof_facts` flag support to websocket subscriptions and state update requests

Compatibility & Clarifications

Sending the new tx type

All major SDKs (starknet.js/py/rs/go and more) are currently working on being compatible with RPC 0.10.1. Their support is a prerequisite for having this feature on the Starknet testnet.

Proofs are stored within each Apollo.

Proofs will be stored in a new component inside each Apollo, called “proof manager”. Proofs will be saved there, after being verified, for a finite (TBD exactly what, but at least a few weeks) period of time, to optimize verification (i.e., avoid verifying again in the consensus) and to allow syncing of the proofs by new nodes that enter the consensus.

​

It will have the following interface:

  • `set_proof(proof_facts, proof)`,
  • `get_proof(proof_facts)-> Proof`
  • `contains_proof(proof_facts) -> bool`.

Details and code can be found here

​

Pricing

Pricing of these new transactions (on top of the regular L2gas counting for on-chain operations) will be based on three main components:

  • Proof propagation between Apollos - price proof as calldata is priced today. Price will go linearly with the proof length (as the “proof facts” structure would claim), and take the relevant STRKs according to the L2Gas price.
  • Storing the proof sent by users (at least until milestone 2, when these proofs are proven to be correct)
  • Prove the verification of these proofs (after this follow-up will be implemented)

The first two components are linearly dependent on the proof size, and their exact prices might change as we see how small proofs can be. The price per byte of proof is 125 L2gas for proof propagation (reflecting 4,000 L2gas/felt - which is 20% cheaper than calldata as the proof shouldn’t be transmitted to fullnodes), and an additional 5 L2gas/byte for storage. This results in 130 L2gas/byte, or, with proofs of 500K bytes, 65M L2gas.

​

10M additional L2gas will be added to cover future verification costs, resulting in a final price of 75M L2gas.

​

Backwards Compatibility

This SNIP is fully backwards compatible. All it does is introduce new functionality via a new transaction structure.

Security Considerations

In the first phase, all proofs will be verified by the Starknet consensus but not by the SNOS. This means that applications built with this feature will have degraded security compared to native Starknet applications. This will be mitigated in a follow-up release of this feature.

Copyright

Copyright and related rights waived via MIT.

Can you elaborate on this? Specifically privacy is de facto (data is computationally infeasible to extract) vs fully zero-knowledge proofs support. What are the differences and implications in app level development?

There is no strong implications on dapp developers when this is shipped, it will be an internal improvement that will make the S-Two proofs used here mathematically ZK. (ATM while S-Two proofs defenitely obfuscate the claim proven and there is no way we can think of to extract private data from them, they are not mathematically ZK)

Update: A small ammedment was made to the SNIP, the " * Allowing the full builtin set paragraph was added

Great proposal, @Ohad-StarkWare. This resonates deeply with what I’m building.

I started on a beta mode Sigillo (sigillo.tech), a ZK-as-a-Service platform, currently a Go backend that generates and verifies commitments, SNARKs, and STARKs via API. I’m now working on integrating Cairo/Starknet proof verification.

A few thoughts on SNIP 36 from the perspective of a developer who would build on top of this primitive:

  1. The proving service API is key to adoption

The starknet_proveTransaction JSON-RPC endpoint is excellent. The biggest barrier to ZK adoption IMO is the DX. Having a standard proving service that takes a block ID + invoke transaction and returns a proof dramatically lowers the bar.

Question: is there a plan for hosted/managed proving services, or will this be self-hosted only in Phase 1? For many dApp developers, running their own prover is a significant ops burden. A managed option (even paid) would accelerate adoption.

  1. Virtual OS program scope

I understand the rationale for excluding add_mod, mul_mod, and Keccak builtins in Phase 1 to simplify the verifier.
From a ZK-as-a-Service perspective, this is fine for most privacy and sharding use cases.

However, what about a clear timeline or criteria for adding them (Keccak in particular is common in cross-chain verification patterns. Even a signal like “will add in Phase 2 if N projects request it” helps developers plan.

  1. The proof_facts TLDR pattern is clever

Separating the full proof (mempool only, not in blocks) from the proof_facts (on-chain, via get_execution_info v3) is a smart design. It keeps block sizes manageable while giving contracts the essential verification data.

One consideration: for developer tooling like Sigillo, having a clear way to reconstruct the relationship between a proof and its facts after the fact (e.g., for debugging, auditing) would be valuable. Is there a plan for proof archival or indexing outside the block?

  1. Privacy use case — “de facto” vs “fully zero-knowledge”

The note that Phase 1 privacy is “de facto” (computationally infeasible to extract) rather than fully ZK is important to communicate clearly. When building Sigillo where developers trust with proof generation, we need to set clear expectations about the privacy guarantees at each phase.

Would it be useful to publish a threat model document for Phase 1 privacy? This would help projects like ours make informed decisions about what to build on top of this.


TY!

In phase1 the proving services are indeed self hosted, but given business use-case hosting them as a service could be considered! Would you like to use such a primitive for Sigillo or are you merely pointing it out on behalf of other potential users?

Adding more builtins will come with a tradeoff os slower proving process (we worked hard to keep proving in the few-seconds area for the privacy app). But technically its not involved, and given interesting use-cases addinfg these later this year is very feasible!

Currently there are no plans to explicitly support it (the fact that proofs can’t be accessed later and shouldn’t be saved for eternity in Starknet archive nodes is what allows them to be cheaper than calldata). However de-facto Starknet Apollo nodes will store these proofs for quite some time, so participants in the Starknet staking protocol (starting version 0.15) will have access to the proofs.

WDYM by “threat model document”? Do you have an example?

All in all, we do view this SNIP as a first step and looking forward to enhance the support based on applicative demands - feel free to reach me on TG (@ohadStarkWare) if you have more feature requests, would love to continue this disucssion

Thanks for the thoughtful responses, @Ohad-StarkWare. A few follow-ups:

On managed proving: That’s great to hear it could be considered. I think this would significantly lower the barrier for adoption. Even developers who understand the proving pipeline well would benefit from not having to operate the infrastructure (it’s a different skillset). To your question: yes, I’d use it directly for building tooling on top of SNIP 36, but I suspect many dApp teams would too. The gap between “I understand starknet_proveTransaction” and “I can reliably run a proving service in production” is real.

On Keccak and builtins: Makes total sense to keep proving fast. One thought: would there be room for a “heavy proving” option with longer timeouts for use cases that need excluded builtins? Cross-chain verification patterns rely heavily on Keccak, and some of those flows are async by nature – a 30-60s proof would be acceptable there. Even a signal like “we’ll consider it when N projects need it” helps developers plan their architectures.

On proof archival: The economics make sense — proofs being cheaper than calldata because they’re not stored permanently is a feature, not a limitation. One area that might be worth exploring for the community: lightweight proof indexing as a separate layer. Something that caches proofs from Apollo nodes while available and maintains a proof_facts → full_proof mapping, so developers can debug and audit without depending on Starknet’s archive. Not
necessarily part of the protocol, could be community tooling.

On threat model: Here’s the structure I was thinking about:

What’s protected Private data in proven transactions — values, addresses, business logic
Who can attack Sequencers (see proofs in mempool), fullnodes, external observers
Phase 1 guarantee Computationally infeasible to extract data from S-Two proofs
What’s still visible Submitter address, timing, proof size, and anything in calldata
Known limitations S-Two not formally ZK yet; proofs retained in Apollo nodes temporarily
Future upgrade Formally ZK proofs + SHARP integration → Ethereum-level verification

Zcash publishes circuit-level security analysis, Semaphore has anonymity set documentation, both are good references for the format. I think something like this would help developers building privacy applications on top of SNIP 36 make informed decisions about what guarantees they can offer their users in Phase 1 vs what to wait for. Happy to help draft it if that’s useful.

One more thought on privacy patterns: I’ve been thinking about what developers will actually try to build first once SNIP 36 ships. The obvious ones:

  • Private transfers (commitment-based balances + nullifiers — classic Zcash pattern adapted to Cairo)
  • Anonymous voting (merkle eligibility proofs with hidden votes — relevant given governance is a big topic here)
  • Selective disclosure for compliance (prove a property about your identity without revealing the identity itself)

For all of these, the “de facto privacy” in Phase 1 is probably sufficient, the real blocker is DX, not the privacy guarantees. Would be curious if the team has a sense of which pattern they expect to see adopted first.

Will reach out on Telegram. Looking forward to continuing the conversation.