From aeaaed1610bf2c845ffa3d89809020a3b75da25d Mon Sep 17 00:00:00 2001 From: Yonatan Iluz Date: Sun, 10 May 2026 17:37:55 +0300 Subject: [PATCH 1/2] starknet_api: add PROOF_VERSION_V1 and accept either marker in ProofFactsVariant --- .../transaction/account_transactions_test.rs | 2 +- crates/starknet_api/src/transaction/fields.rs | 72 ++++++++++++++++--- .../src/transaction/fields_test.rs | 42 +++++++++++ 3 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 crates/starknet_api/src/transaction/fields_test.rs diff --git a/crates/blockifier/src/transaction/account_transactions_test.rs b/crates/blockifier/src/transaction/account_transactions_test.rs index 68344f74002..bf27025f82e 100644 --- a/crates/blockifier/src/transaction/account_transactions_test.rs +++ b/crates/blockifier/src/transaction/account_transactions_test.rs @@ -2222,7 +2222,7 @@ fn test_missing_validate_entrypoint_rejects( /// Converts SnosProofFacts to ProofFacts for testing. fn snos_to_proof_facts(snos: SnosProofFacts) -> ProofFacts { vec![ - snos.proof_version, + snos.proof_version.as_felt(), VIRTUAL_SNOS, snos.program_hash, VIRTUAL_OS_OUTPUT_VERSION, diff --git a/crates/starknet_api/src/transaction/fields.rs b/crates/starknet_api/src/transaction/fields.rs index c5970bbb706..a8cd4f55013 100644 --- a/crates/starknet_api/src/transaction/fields.rs +++ b/crates/starknet_api/src/transaction/fields.rs @@ -14,6 +14,10 @@ use crate::hash::StarkHash; use crate::serde_utils::PrefixedBytesAsHex; use crate::{StarknetApiError, StarknetApiResult}; +#[cfg(test)] +#[path = "fields_test.rs"] +mod fields_test; + pub const HIGH_GAS_AMOUNT: u64 = 10000000000; // A high gas amount that should be enough for execution. /// A fee. @@ -633,6 +637,54 @@ pub const VIRTUAL_SNOS: Felt = Felt::from_hex_unchecked("0x5649525455414c5f534e4 // Represent the `PROOF_VERSION_V0` marker as a Felt ('PROOF0'). pub const PROOF_VERSION_V0: Felt = Felt::from_hex_unchecked("0x50524f4f4630"); +// Represent the `PROOF_VERSION_V1` marker as a Felt ('PROOF1'). +pub const PROOF_VERSION_V1: Felt = Felt::from_hex_unchecked("0x50524f4f4631"); + +/// Supported proof-facts version markers. +#[cfg_attr(any(test, feature = "testing"), derive(EnumIter))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ProofVersion { + V0, + V1, +} + +impl ProofVersion { + /// Felt (Cairo short-string) representation written into proof facts. + pub const fn as_felt(self) -> Felt { + match self { + ProofVersion::V0 => PROOF_VERSION_V0, + ProofVersion::V1 => PROOF_VERSION_V1, + } + } + + /// Human-readable short-string label (matches the Cairo constant value). + pub const fn as_str(self) -> &'static str { + match self { + ProofVersion::V0 => "PROOF0", + ProofVersion::V1 => "PROOF1", + } + } +} + +impl TryFrom for ProofVersion { + type Error = (); + fn try_from(value: Felt) -> Result { + if value == PROOF_VERSION_V0 { + Ok(ProofVersion::V0) + } else if value == PROOF_VERSION_V1 { + Ok(ProofVersion::V1) + } else { + Err(()) + } + } +} + +impl fmt::Display for ProofVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} ({})", self.as_felt(), self.as_str()) + } +} + /// The version of the virtual OS output (short string 'VIRTUAL_SNOS0'). /// This must match the Cairo constant `VIRTUAL_OS_OUTPUT_VERSION` in `virtual_os_output.cairo`. pub const VIRTUAL_OS_OUTPUT_VERSION: Felt = @@ -681,13 +733,15 @@ impl TryFrom<&ProofFacts> for ProofFactsVariant { ))); }; - // Validate that the first element is PROOF_VERSION_V0. - if *proof_version != PROOF_VERSION_V0 { - return Err(StarknetApiError::InvalidProofFacts(format!( - "Expected first field to be {} (PROOF_VERSION_V0), but got {}", - PROOF_VERSION_V0, proof_version - ))); - } + // Validate that the first element is a supported proof version marker. + let proof_version = ProofVersion::try_from(*proof_version).map_err(|()| { + StarknetApiError::InvalidProofFacts(format!( + "Expected first field to be {} or {}, but got {}", + ProofVersion::V0, + ProofVersion::V1, + proof_version, + )) + })?; // Validate that the second element is VIRTUAL_SNOS. if *variant_marker != VIRTUAL_SNOS { @@ -725,7 +779,7 @@ impl TryFrom<&ProofFacts> for ProofFactsVariant { })?); Ok(ProofFactsVariant::Snos(SnosProofFacts { - proof_version: *proof_version, + proof_version, program_hash: *program_hash, block_number, block_hash: BlockHash(*block_hash), @@ -738,7 +792,7 @@ impl TryFrom<&ProofFacts> for ProofFactsVariant { /// /// A valid SNOS proof facts structure must include these fields as its first five entries. pub struct SnosProofFacts { - pub proof_version: Felt, + pub proof_version: ProofVersion, pub program_hash: StarkHash, pub block_number: BlockNumber, pub block_hash: BlockHash, diff --git a/crates/starknet_api/src/transaction/fields_test.rs b/crates/starknet_api/src/transaction/fields_test.rs new file mode 100644 index 00000000000..db49c9ef780 --- /dev/null +++ b/crates/starknet_api/src/transaction/fields_test.rs @@ -0,0 +1,42 @@ +use starknet_types_core::short_string::ShortString; +use strum::IntoEnumIterator; + +use super::*; + +/// Returns SNOS-shaped `ProofFacts` whose first felt is the given proof version. +fn proof_facts_given_proof_version(proof_version: Felt) -> ProofFacts { + let mut facts = ProofFacts::snos_proof_facts_for_testing(); + Arc::make_mut(&mut facts.0)[0] = proof_version; + facts +} + +#[test] +fn proof_facts_variant_accepts_supported_versions() { + for version in ProofVersion::iter() { + let variant = + ProofFactsVariant::try_from(&proof_facts_given_proof_version(version.as_felt())) + .expect("supported version should parse"); + match variant { + ProofFactsVariant::Snos(snos) => assert_eq!(snos.proof_version, version), + ProofFactsVariant::Empty => panic!("expected Snos variant"), + } + } +} + +#[test] +fn proof_facts_variant_rejects_unknown_version() { + let facts = proof_facts_given_proof_version(Felt::from_hex_unchecked("0xDEAD")); + assert!(matches!( + ProofFactsVariant::try_from(&facts), + Err(StarknetApiError::InvalidProofFacts(_)) + )); +} + +#[test] +fn proof_version_str_encodes_to_felt() { + for version in ProofVersion::iter() { + let from_short_string = + Felt::from(ShortString::try_from(version.as_str()).expect("valid short string")); + assert_eq!(from_short_string, version.as_felt()); + } +} From 19501d4023abeddbba66ccb06d8f85d8af48d33f Mon Sep 17 00:00:00 2001 From: Yonatan Iluz Date: Sun, 10 May 2026 17:42:09 +0300 Subject: [PATCH 2/2] starknet_proof_verifier: accept either PROOF_VERSION_V0 or PROOF_VERSION_V1 in verify_proof --- .../src/proof_verifier.rs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/crates/starknet_proof_verifier/src/proof_verifier.rs b/crates/starknet_proof_verifier/src/proof_verifier.rs index 6d159c1fe47..fdaae28036b 100644 --- a/crates/starknet_proof_verifier/src/proof_verifier.rs +++ b/crates/starknet_proof_verifier/src/proof_verifier.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use apollo_sizeof::SizeOf; use privacy_circuit_verify::{verify_recursive_circuit, PrivacyProofOutput}; use serde::{Deserialize, Serialize}; -use starknet_api::transaction::fields::{Proof, ProofFacts, PROOF_VERSION_V0}; +use starknet_api::transaction::fields::{Proof, ProofFacts, ProofVersion}; use starknet_types_core::felt::Felt; use thiserror::Error; @@ -15,8 +15,12 @@ pub enum VerifyProofError { EmptyProof, #[error(transparent)] ProgramOutputError(#[from] ProgramOutputError), - #[error("Invalid proof version: expected {expected}, got {actual}.")] - InvalidProofVersion { expected: Felt, actual: Felt }, + #[error( + "Unsupported proof version: got {actual}, expected {v0} or {v1}.", + v0 = ProofVersion::V0, + v1 = ProofVersion::V1, + )] + InvalidProofVersion { actual: Felt }, #[error("Proof facts too short: expected at least 3 elements, got {length}.")] ProofFactsTooShort { length: usize }, #[error("Proof verification failed: {0}")] @@ -29,9 +33,9 @@ impl PartialEq for VerifyProofError { (Self::EmptyProof, Self::EmptyProof) => true, (Self::ProgramOutputError(lhs), Self::ProgramOutputError(rhs)) => lhs == rhs, ( - Self::InvalidProofVersion { expected: exp_l, actual: act_l }, - Self::InvalidProofVersion { expected: exp_r, actual: act_r }, - ) => exp_l == exp_r && act_l == act_r, + Self::InvalidProofVersion { actual: act_l }, + Self::InvalidProofVersion { actual: act_r }, + ) => act_l == act_r, (Self::Verification(lhs), Self::Verification(rhs)) => lhs == rhs, (Self::ProofFactsTooShort { length: l }, Self::ProofFactsTooShort { length: r }) => { l == r @@ -83,7 +87,7 @@ impl ProgramOutput { return Err(ProgramOutputError::TooShort(self.0.len())); } // Add the proof version and variant markers in place of num_tasks. - let mut facts = vec![PROOF_VERSION_V0]; + let mut facts = vec![ProofVersion::V0.as_felt()]; facts.push(program_variant); // Skip num_tasks (index 0) and output_size (index 1); add the task output // (program_hash followed by the virtual OS output). @@ -119,21 +123,19 @@ pub fn reconstruct_output_preimage( } /// Verifies a submitted proof against the proof facts using the circuit verifier. +/// +/// Accepts either V0 (legacy) or V1 (current) proof versions. Both currently resolve to the same +/// upstream circuit revision. When the V1 circuit revision is bumped, V0 verification should be +/// routed to a `privacy-circuit-verify-legacy` alias pinned to the old revision. pub fn verify_proof(proof_facts: ProofFacts, proof: Proof) -> Result<(), VerifyProofError> { // Reject empty proof payloads before running the verifier. if proof.is_empty() { return Err(VerifyProofError::EmptyProof); } - // Validate that the first element of proof facts is PROOF_VERSION_V0. - let expected_proof_version = PROOF_VERSION_V0; - let actual_first = proof_facts.0.first().copied().unwrap_or_default(); - if actual_first != expected_proof_version { - return Err(VerifyProofError::InvalidProofVersion { - expected: expected_proof_version, - actual: actual_first, - }); - } + let proof_version_felt = proof_facts.0.first().copied().unwrap_or_default(); + let _proof_version = ProofVersion::try_from(proof_version_felt) + .map_err(|()| VerifyProofError::InvalidProofVersion { actual: proof_version_felt })?; // Reconstruct the output preimage from proof facts and verify the proof. let output_preimage = reconstruct_output_preimage(&proof_facts)?;