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 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions arm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -30,8 +31,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"]
compliance_circuit = []
prove = ["risc0-zkvm/prove"]
bonsai = ["risc0-zkvm/bonsai"]
Expand Down
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
}
}
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())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was there a particular reason you took out the error handler here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in particular, just to keep stable the current core types

But if you like the change, I can also handle errors properly

.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()
}
}
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();

Comment on lines +101 to +103

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you've already added the solana feature, would "solana" be clearer than "not evm"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about this and this is basically future-oriented. We may have more chains with more quirks and this one particularly has to be avoided for the evm and not other chains as the recovery ID trick seems to be very EVM specific. So I thought a not would be more suitable here

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense

#[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];
Comment on lines +113 to +114

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto


#[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
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