Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,618 changes: 1,516 additions & 102 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions arm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ risc0-zkvm = { version = "3.0.3", features = [
"std",
"unstable",
], default-features = false }
solana_groth16_verifier = { git = "https://github.com/boundless-xyz/risc0-solana", package = "groth_16_verifier", tag = "v3.0.0", optional = true}
solana_verifier_router = { git = "https://github.com/boundless-xyz/risc0-solana", package = "verifier_router", tag = "v3.0.0", optional = true}
anchor-lang = {version = "0.31.1", optional = true}

serde = { version = "1.0.197", default-features = false }
serde_with = "3.14.1"
k256 = { version = "=0.13.3", features = [
Expand All @@ -21,6 +25,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"
Expand All @@ -30,8 +35,10 @@ 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", "dep:solana_groth16_verifier", "dep:solana_verifier_router", "dep:anchor-lang"]
compliance_circuit = []
prove = ["risc0-zkvm/prove"]
bonsai = ["risc0-zkvm/bonsai"]
Expand Down
Binary file modified arm/elfs/batch_aggregation.bin
Binary file not shown.
Binary file modified arm/elfs/compliance-guest.bin
Binary file not shown.
Binary file modified arm/elfs/sequential_aggregation.bin
Binary file not shown.
16 changes: 6 additions & 10 deletions arm/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ impl Action {
pub(crate) fn get_logic_verifiers(&self) -> Result<Vec<LogicVerifier>, ArmError> {
let mut logic_verifiers = Vec::new();

let compliance_intances = self
let compliance_intances: Vec<ComplianceInstance> = self
.compliance_units
.iter()
.map(|unit| unit.get_instance())
.collect::<Result<Vec<ComplianceInstance>, ArmError>>()?;
.map(|unit| unit.instance.clone())
.collect();

// Construct the action tree
let tags: Vec<Digest> = compliance_intances
Expand Down Expand Up @@ -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<Vec<u8>, ArmError> {
pub fn get_delta_msg(&self) -> Vec<u8> {
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
}
}
4 changes: 2 additions & 2 deletions arm/src/aggregation/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ pub const BATCH_AGGREGATION_PK: &[u8] = include_bytes!("../../elfs/batch_aggrega
lazy_static! {
/// Sequential aggregation verification key / sequential aggregation image id.
pub static ref SEQUENTIAL_AGGREGATION_VK: Digest =
Digest::from_hex("c5e69eb269d73d061c6ceeffe8da6eadfc3e3b48a5130979ee97a5b9aced6e4c").unwrap();
Digest::from_hex("25782c2bbe597a2db31fcce28f6cd3e1aa423f2cb987aab0e881ed87b9369d13").unwrap();

/// Batch aggregation verification key / Batch aggregation image id.
pub static ref BATCH_AGGREGATION_VK: Digest = Digest::from_hex("6bd39fd7673afba6dbde9e024875ebce86b93fb49a28b7d0fde61abdf2b5f2bf").unwrap();
pub static ref BATCH_AGGREGATION_VK: Digest = Digest::from_hex("4297db7ea74c296acd49ca55e160a6e930478da2605931ea0654d466c0f95099").unwrap();
}
5 changes: 4 additions & 1 deletion arm/src/aggregation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ impl Transaction {
.flat_map(|a| a.get_compliance_units().clone())
.collect();

let cu_instances: Vec<Vec<u8>> = cus.iter().map(|cu| cu.instance.clone()).collect();
let cu_instances: Vec<Vec<u8>> = cus
.iter()
.map(|cu| cu.instance.to_journal().unwrap())
.collect();

let inner_receipts: Option<Vec<InnerReceipt>> = if self.base_proofs_are_empty() {
None
Expand Down
9 changes: 9 additions & 0 deletions arm/src/compliance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<Vec<u8>, ArmError> {
Ok(
words_to_bytes(&to_vec(&self).map_err(|_| ArmError::InstanceSerializationFailed)?)
.to_vec(),
)
}
}
17 changes: 7 additions & 10 deletions arm/src/compliance_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ use crate::{
pub struct ComplianceUnit {
/// The compliance proof (optional, would be absent when aggregation is enabled).
pub proof: Option<Vec<u8>>,
/// The serialized compliance instance.
pub instance: Vec<u8>,
/// The compliance instance.
pub instance: ComplianceInstance,
}

impl ComplianceUnit {
Expand All @@ -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<Self, ArmError> {
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,
Expand All @@ -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(),
Expand All @@ -52,11 +54,6 @@ impl ComplianceUnit {

/// Obtains the delta from the compliance instance.
pub fn delta(&self) -> Result<ProjectivePoint, ArmError> {
self.get_instance()?.delta_projective()
}

/// Retrieves the compliance instance from the serialized instance data.
pub fn get_instance(&self) -> Result<ComplianceInstance, ArmError> {
journal_to_instance(&self.instance)
self.instance.delta_projective()
}
}
2 changes: 1 addition & 1 deletion arm/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub const PADDING_LOGIC_PK: &[u8] = include_bytes!("../elfs/trivial-logic-guest.
lazy_static! {
/// compliance verification key / compliance image id
pub static ref COMPLIANCE_VK: Digest =
Digest::from_hex("0bccec1401e3bfc1b024f0c3abf520939d0166deba454b54a505582448cc97dd")
Digest::from_hex("1176e7f038c55009f369e2eafd1dd9bc5b51a6f5fc6369cc9f54779258f898fc")
.unwrap();

/// padding logic verification key / compliance image id
Expand Down
39 changes: 30 additions & 9 deletions arm/src/delta_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(feature = "evm")]
use sha3::{Digest, Keccak256 as HashFunction};

/// The delta proof consists of an ECDSA signature and a recovery ID.
#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -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<DeltaProof, ArmError> {
// 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
Expand All @@ -46,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);
}
Expand All @@ -60,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);
}
Expand All @@ -72,8 +80,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
Expand All @@ -89,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<DeltaProof, ArmError> {
#[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)?,
})
}
}
Expand Down
37 changes: 31 additions & 6 deletions arm/src/proving_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ use crate::error::ArmError;
use risc0_zkvm::{sha::Digest, InnerReceipt, Receipt};
use serde::de::DeserializeOwned;

#[cfg(feature = "solana")]
use anchor_lang::prelude::AnchorSerialize;
#[cfg(feature = "prove")]
use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext};
#[cfg(feature = "prove")]
use serde::Serialize;
#[cfg(feature = "solana")]
use solana_groth16_verifier::{negate_g1, Proof};
#[cfg(feature = "solana")]
use solana_verifier_router::{Seal, Selector};

/// Types of proofs supported.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -51,18 +57,37 @@ pub fn journal_to_instance<T: DeserializeOwned>(journal: &[u8]) -> Result<T, Arm
journal.decode().map_err(|_| ArmError::JournalDecodingError)
}

/// Encode the seal of the given proof for use with EVM smart contract verifiers.
/// Encode the seal of the given proof for use with on-chain Groth16 verifiers.
pub fn encode_seal(proof: &[u8]) -> Result<Vec<u8>, ArmError> {
let inner: InnerReceipt =
bincode::deserialize(proof).map_err(|_| ArmError::InnerReceiptDeserializationError)?;
let seal = match inner {
InnerReceipt::Groth16(receipt) => {
let selector = &receipt.verifier_parameters.as_bytes()[..4];
// Create a new vector with the capacity to hold both selector and seal
let mut selector_seal = Vec::with_capacity(selector.len() + receipt.seal.len());
selector_seal.extend_from_slice(selector);
selector_seal.extend_from_slice(receipt.seal.as_ref());
selector_seal
#[cfg(feature = "evm")]
{
// Create a new vector with the capacity to hold both selector and seal
let mut selector_seal = Vec::with_capacity(selector.len() + receipt.seal.len());
selector_seal.extend_from_slice(selector);
selector_seal.extend_from_slice(receipt.seal.as_ref());
selector_seal
}

#[cfg(feature = "solana")]
{
let proof_raw = receipt.seal;
let mut proof = Proof {
pi_a: proof_raw[0..64].try_into().unwrap(),
pi_b: proof_raw[64..192].try_into().unwrap(),
pi_c: proof_raw[192..256].try_into().unwrap(),
};
proof.pi_a = negate_g1(&proof.pi_a);
let seal = Seal {
selector: selector.try_into().unwrap(),
proof: proof,
};
seal.try_to_vec().unwrap()
}
}
_ => Err(ArmError::UnsupportedProofType)?,
};
Expand Down
5 changes: 2 additions & 3 deletions arm/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -128,7 +127,7 @@ impl Transaction {
pub fn get_delta_msg(&self) -> Result<Vec<u8>, 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)
}
Expand Down