From 3da9a640ed8f733dce6a082f1043ef9571ad1bdf Mon Sep 17 00:00:00 2001 From: AHartNtkn Date: Fri, 19 Dec 2025 18:34:11 -0500 Subject: [PATCH 1/5] Add solana feature flag for SHA-256 delta proof hashing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add sha2 dependency (optional) - Add 'solana' feature that enables SHA-256 instead of Keccak-256 - Conditionally select hash function based on feature flag - Default behavior (Keccak-256) unchanged for EVM compatibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- Cargo.lock | 1 + arm/Cargo.toml | 2 ++ arm/src/delta_proof.rs | 16 +++++++++++----- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ed6c2e9..8245c807 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -612,6 +612,7 @@ dependencies = [ "risc0-zkvm", "serde", "serde_with", + "sha2", "sha3", "thiserror 2.0.17", ] diff --git a/arm/Cargo.toml b/arm/Cargo.toml index 66ef6657..437bc3a0 100644 --- a/arm/Cargo.toml +++ b/arm/Cargo.toml @@ -21,6 +21,7 @@ k256 = { version = "=0.13.3", features = [ "ecdsa", "hash2curve", ], default-features = false } +sha2 = { version = "0.10", optional = true } sha3 = { version = "0.10", optional = true } rand = "0.8" bincode = "1.3.3" @@ -32,6 +33,7 @@ thiserror = "2.0.6" [features] default = ["transaction", "prove"] transaction = ["compliance_circuit", "dep:sha3"] +solana = ["transaction", "dep:sha2"] compliance_circuit = [] prove = ["risc0-zkvm/prove"] bonsai = ["risc0-zkvm/bonsai"] diff --git a/arm/src/delta_proof.rs b/arm/src/delta_proof.rs index 465e3126..fcbdd7b5 100644 --- a/arm/src/delta_proof.rs +++ b/arm/src/delta_proof.rs @@ -8,7 +8,13 @@ use k256::{ use serde::{Deserialize, Serialize}; use crate::error::ArmError; -use sha3::{Digest, Keccak256}; + +// Conditionally use SHA-256 for Solana, Keccak-256 for EVM +#[cfg(feature = "solana")] +use sha2::{Digest, Sha256 as HashFunction}; + +#[cfg(not(feature = "solana"))] +use sha3::{Digest, Keccak256 as HashFunction}; /// The delta proof consists of an ECDSA signature and a recovery ID. #[derive(Clone, Debug, PartialEq, Eq)] @@ -36,8 +42,8 @@ pub struct DeltaInstance { impl DeltaProof { /// Generates a delta proof by signing the given message with the provided witness. pub fn prove(message: &[u8], witness: &DeltaWitness) -> Result { - // Hash the message using Keccak256 - let mut digest = Keccak256::new(); + // Hash the message (SHA-256 for Solana, Keccak-256 for EVM) + let mut digest = HashFunction::new(); digest.update(message); // Sign the hashed message using RFC6979 @@ -72,8 +78,8 @@ impl DeltaProof { return Err(ArmError::InvalidDeltaProof); } - // Hash the message using Keccak256 - let mut digest = Keccak256::new(); + // Hash the message (SHA-256 for Solana, Keccak-256 for EVM) + let mut digest = HashFunction::new(); digest.update(message); // Verify the signature From 55c2056dca3aea92ef96cc77108a7d40f12ba84f Mon Sep 17 00:00:00 2001 From: agureev Date: Mon, 12 Jan 2026 19:56:50 +0400 Subject: [PATCH 2/5] Introduce EVM Feature Flag Introduces and EVM feature flag with an optional sha3 dependency Makes it a default for testing --- arm/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arm/Cargo.toml b/arm/Cargo.toml index 437bc3a0..629355cf 100644 --- a/arm/Cargo.toml +++ b/arm/Cargo.toml @@ -31,8 +31,9 @@ bytemuck = { version = "1.12", features = ["derive"] } thiserror = "2.0.6" [features] -default = ["transaction", "prove"] -transaction = ["compliance_circuit", "dep:sha3"] +default = ["transaction", "prove", "evm"] +transaction = ["compliance_circuit"] +evm = ["dep:sha3"] solana = ["transaction", "dep:sha2"] compliance_circuit = [] prove = ["risc0-zkvm/prove"] From 8d26d327831de7bbd44376102770505726bfaad9 Mon Sep 17 00:00:00 2001 From: agureev Date: Mon, 12 Jan 2026 19:57:00 +0400 Subject: [PATCH 3/5] Gate EVM-specific Recovery ID Changes to EVM Feature Flag Now operations that bar recovery IDs above 1 or necessitate translation to 27/28 instead of usual 1, 2 for IDs under a specified flag --- arm/src/delta_proof.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/arm/src/delta_proof.rs b/arm/src/delta_proof.rs index fcbdd7b5..c6f3fbad 100644 --- a/arm/src/delta_proof.rs +++ b/arm/src/delta_proof.rs @@ -13,7 +13,7 @@ use crate::error::ArmError; #[cfg(feature = "solana")] use sha2::{Digest, Sha256 as HashFunction}; -#[cfg(not(feature = "solana"))] +#[cfg(feature = "evm")] use sha3::{Digest, Keccak256 as HashFunction}; /// The delta proof consists of an ECDSA signature and a recovery ID. @@ -52,7 +52,8 @@ impl DeltaProof { .sign_digest_recoverable(digest) .map_err(|_| ArmError::DeltaProofGenerationFailed)?; - // On-chain signatures are not supported when recid is 2 or 3. + // On-chain EVM signatures are not supported when recid is 2 or 3. + #[cfg(feature = "evm")] if recid.to_byte() > 1 { return Err(ArmError::InvalidDeltaProof); } @@ -66,7 +67,8 @@ impl DeltaProof { proof: &DeltaProof, instance: DeltaInstance, ) -> Result<(), ArmError> { - // handle recid + // handle recid for EVM verification + #[cfg(feature = "evm")] if proof.recid.to_byte() > 1 { return Err(ArmError::InvalidDeltaProof); } @@ -95,16 +97,29 @@ impl DeltaProof { pub fn to_bytes(&self) -> [u8; 65] { let mut bytes = [0u8; 65]; bytes[0..64].clone_from_slice(&self.signature.to_bytes()); - bytes[64] = self.recid.to_byte() + 27; + + #[cfg(not(feature = "evm"))] + let recid_byte = self.recid.to_byte(); + + #[cfg(feature = "evm")] + let recid_byte = self.recid.to_byte() + 27; + + bytes[64] = recid_byte; bytes } /// Deserializes the delta proof from bytes. pub fn from_bytes(bytes: &[u8]) -> Result { + #[cfg(not(feature = "evm"))] + let recid_byte = bytes[64]; + + #[cfg(feature = "evm")] + let recid_byte = bytes[64] - 27; + Ok(DeltaProof { signature: Signature::from_bytes((&bytes[0..64]).into()) .map_err(|_| ArmError::InvalidSignature)?, - recid: RecoveryId::from_byte(bytes[64] - 27).ok_or(ArmError::InvalidSignature)?, + recid: RecoveryId::from_byte(recid_byte).ok_or(ArmError::InvalidSignature)?, }) } } From ae981cbc8a09f04dcbde1160055b55540fd99dd2 Mon Sep 17 00:00:00 2001 From: agureev Date: Tue, 13 Jan 2026 14:20:44 +0400 Subject: [PATCH 4/5] Compliance Unit Instance Field Deserialized --- arm/src/action.rs | 16 ++++++---------- arm/src/aggregation/mod.rs | 5 ++++- arm/src/compliance.rs | 9 +++++++++ arm/src/compliance_unit.rs | 18 ++++++++++-------- arm/src/transaction.rs | 4 ++-- 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/arm/src/action.rs b/arm/src/action.rs index fd025263..6d715382 100644 --- a/arm/src/action.rs +++ b/arm/src/action.rs @@ -51,11 +51,11 @@ impl Action { pub(crate) fn get_logic_verifiers(&self) -> Result, ArmError> { let mut logic_verifiers = Vec::new(); - let compliance_intances = self + let compliance_intances: Vec = self .compliance_units .iter() - .map(|unit| unit.get_instance()) - .collect::, ArmError>>()?; + .map(|unit| unit.get_instance().clone()) + .collect(); // Construct the action tree let tags: Vec = compliance_intances @@ -122,15 +122,11 @@ impl Action { /// Constructs the delta message by concatenating the delta messages /// of each compliance unit. - pub fn get_delta_msg(&self) -> Result, ArmError> { + pub fn get_delta_msg(&self) -> Vec { let mut msg = Vec::new(); for unit in &self.compliance_units { - if let Ok(instance) = unit.get_instance() { - msg.extend_from_slice(&instance.delta_msg()); - } else { - return Err(ArmError::InvalidComplianceInstance); - } + msg.extend_from_slice(&unit.instance.delta_msg()); } - Ok(msg) + msg } } diff --git a/arm/src/aggregation/mod.rs b/arm/src/aggregation/mod.rs index 9c94f469..29bf7f59 100644 --- a/arm/src/aggregation/mod.rs +++ b/arm/src/aggregation/mod.rs @@ -57,7 +57,10 @@ impl Transaction { .flat_map(|a| a.get_compliance_units().clone()) .collect(); - let cu_instances: Vec> = cus.iter().map(|cu| cu.instance.clone()).collect(); + let cu_instances: Vec> = cus + .iter() + .map(|cu| cu.instance.to_journal().unwrap()) + .collect(); let inner_receipts: Option> = if self.base_proofs_are_empty() { None diff --git a/arm/src/compliance.rs b/arm/src/compliance.rs index bf24c2e2..b5f9c48e 100644 --- a/arm/src/compliance.rs +++ b/arm/src/compliance.rs @@ -20,6 +20,7 @@ use k256::{ }; use lazy_static::lazy_static; use rand::rngs::OsRng; +use risc0_zkvm::serde::to_vec; use risc0_zkvm::Digest; use serde_with::serde_as; @@ -274,4 +275,12 @@ impl ComplianceInstance { msg.extend_from_slice(self.created_commitment.as_bytes()); msg } + + /// Serializes the instance to a journal format. + pub fn to_journal(&self) -> Result, ArmError> { + Ok( + words_to_bytes(&to_vec(&self).map_err(|_| ArmError::InstanceSerializationFailed)?) + .to_vec(), + ) + } } diff --git a/arm/src/compliance_unit.rs b/arm/src/compliance_unit.rs index c80ca912..31f82f5a 100644 --- a/arm/src/compliance_unit.rs +++ b/arm/src/compliance_unit.rs @@ -22,8 +22,8 @@ use crate::{ pub struct ComplianceUnit { /// The compliance proof (optional, would be absent when aggregation is enabled). pub proof: Option>, - /// The serialized compliance instance. - pub instance: Vec, + /// The compliance instance. + pub instance: ComplianceInstance, } impl ComplianceUnit { @@ -32,7 +32,8 @@ impl ComplianceUnit { /// them as parameters. Instance is generated by proving. #[cfg(feature = "prove")] pub fn create(witness: &ComplianceWitness, proof_type: ProofType) -> Result { - let (proof, instance) = prove(COMPLIANCE_PK, witness, proof_type)?; + let (proof, serialized_instance) = prove(COMPLIANCE_PK, witness, proof_type)?; + let instance = journal_to_instance(&serialized_instance)?; Ok(ComplianceUnit { proof: Some(proof), instance, @@ -42,7 +43,8 @@ impl ComplianceUnit { /// Verifies the compliance proof against the instance using the constant verifying key. pub fn verify(&self) -> Result<(), ArmError> { if let Some(proof) = &self.proof { - verify_proof(&COMPLIANCE_VK, &self.instance, proof) + let journal = &self.instance.to_journal()?; + verify_proof(&COMPLIANCE_VK, journal, proof) } else { Err(ArmError::ProofVerificationFailed( "Missing compliance proof".into(), @@ -52,11 +54,11 @@ impl ComplianceUnit { /// Obtains the delta from the compliance instance. pub fn delta(&self) -> Result { - self.get_instance()?.delta_projective() + self.get_instance().delta_projective() } - /// Retrieves the compliance instance from the serialized instance data. - pub fn get_instance(&self) -> Result { - journal_to_instance(&self.instance) + /// Retrieves the compliance instance. + pub fn get_instance(&self) -> &ComplianceInstance { + &self.instance } } diff --git a/arm/src/transaction.rs b/arm/src/transaction.rs index 52e4b0bc..253a7010 100644 --- a/arm/src/transaction.rs +++ b/arm/src/transaction.rs @@ -105,7 +105,7 @@ impl Transaction { let mut seen_nullifiers = std::collections::HashSet::new(); for action in &self.actions { for cu in action.get_compliance_units() { - let instance = cu.get_instance()?; + let instance = cu.get_instance(); if !seen_nullifiers.insert(instance.consumed_nullifier) { return Err(ArmError::NullifierDuplication); } @@ -128,7 +128,7 @@ impl Transaction { pub fn get_delta_msg(&self) -> Result, ArmError> { let mut msg = Vec::new(); for action in &self.actions { - msg.extend(action.get_delta_msg()?); + msg.extend(action.get_delta_msg()); } Ok(msg) } From ea31dbbd7417e858c73107f6e5cac1bde435d653 Mon Sep 17 00:00:00 2001 From: agureev Date: Tue, 13 Jan 2026 14:25:45 +0400 Subject: [PATCH 5/5] Remove `get_instance` function --- arm/src/action.rs | 2 +- arm/src/compliance_unit.rs | 7 +------ arm/src/transaction.rs | 3 +-- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/arm/src/action.rs b/arm/src/action.rs index 6d715382..3aaa71e9 100644 --- a/arm/src/action.rs +++ b/arm/src/action.rs @@ -54,7 +54,7 @@ impl Action { let compliance_intances: Vec = self .compliance_units .iter() - .map(|unit| unit.get_instance().clone()) + .map(|unit| unit.instance.clone()) .collect(); // Construct the action tree diff --git a/arm/src/compliance_unit.rs b/arm/src/compliance_unit.rs index 31f82f5a..27a736ba 100644 --- a/arm/src/compliance_unit.rs +++ b/arm/src/compliance_unit.rs @@ -54,11 +54,6 @@ impl ComplianceUnit { /// Obtains the delta from the compliance instance. pub fn delta(&self) -> Result { - self.get_instance().delta_projective() - } - - /// Retrieves the compliance instance. - pub fn get_instance(&self) -> &ComplianceInstance { - &self.instance + self.instance.delta_projective() } } diff --git a/arm/src/transaction.rs b/arm/src/transaction.rs index 253a7010..b9219acd 100644 --- a/arm/src/transaction.rs +++ b/arm/src/transaction.rs @@ -105,8 +105,7 @@ impl Transaction { let mut seen_nullifiers = std::collections::HashSet::new(); for action in &self.actions { for cu in action.get_compliance_units() { - let instance = cu.get_instance(); - if !seen_nullifiers.insert(instance.consumed_nullifier) { + if !seen_nullifiers.insert(cu.instance.consumed_nullifier) { return Err(ArmError::NullifierDuplication); } }