//! ZK Circuit definitions for Synor rollups. //! //! Circuits define the constraints that must be satisfied for valid state transitions. //! We use R1CS (Rank-1 Constraint System) representation for Groth16 compatibility. //! //! # Circuit Types //! //! - **TransferCircuit**: Validates token transfers between accounts //! - **DepositCircuit**: Validates deposits from L1 to L2 //! - **WithdrawCircuit**: Validates withdrawals from L2 to L1 //! - **BatchCircuit**: Aggregates multiple transactions into single proof use ark_bn254::Fr as ScalarField; use ark_ff::PrimeField; use ark_relations::r1cs::SynthesisError; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::state::StateRoot; /// Circuit configuration parameters. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CircuitConfig { /// Maximum number of transactions in a batch pub max_batch_size: usize, /// State tree depth pub tree_depth: usize, /// Enable signature verification in circuit pub verify_signatures: bool, } impl Default for CircuitConfig { fn default() -> Self { Self { max_batch_size: crate::constants::MAX_BATCH_SIZE, tree_depth: crate::constants::STATE_TREE_DEPTH, verify_signatures: true, } } } /// Constraint system interface for building circuits. pub trait ConstraintSystem { /// Allocates a public input variable. fn alloc_input(&mut self, name: &str, value: ScalarField) -> Result; /// Allocates a private witness variable. fn alloc_witness(&mut self, name: &str, value: ScalarField) -> Result; /// Adds a constraint: a * b = c fn enforce_constraint( &mut self, a: LinearCombination, b: LinearCombination, c: LinearCombination, ) -> Result<(), CircuitError>; /// Returns the number of constraints. fn num_constraints(&self) -> usize; /// Returns the number of public inputs. fn num_inputs(&self) -> usize; /// Returns the number of witness variables. fn num_witnesses(&self) -> usize; } /// Variable in the constraint system. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Variable(pub(crate) usize); impl Variable { /// Creates a new variable with the given index. pub fn new(index: usize) -> Self { Self(index) } /// Returns the variable index. pub fn index(&self) -> usize { self.0 } } /// Linear combination of variables: sum(coeff_i * var_i). #[derive(Clone, Debug, Default)] pub struct LinearCombination { terms: Vec<(ScalarField, Variable)>, } impl LinearCombination { /// Creates an empty linear combination. pub fn new() -> Self { Self { terms: Vec::new() } } /// Creates a linear combination from a single variable. pub fn from_variable(var: Variable) -> Self { let mut lc = Self::new(); lc.add_term(ScalarField::from(1u64), var); lc } /// Creates a linear combination from a constant. pub fn from_constant(value: ScalarField) -> Self { let mut lc = Self::new(); lc.add_term(value, Variable(0)); // Variable 0 is the constant 1 lc } /// Adds a term (coefficient * variable) to the linear combination. pub fn add_term(&mut self, coeff: ScalarField, var: Variable) { self.terms.push((coeff, var)); } /// Returns the terms of the linear combination. pub fn terms(&self) -> &[(ScalarField, Variable)] { &self.terms } } /// Base trait for all circuits. pub trait Circuit: Clone + Send + Sync { /// Returns the circuit name. fn name(&self) -> &str; /// Returns the circuit configuration. fn config(&self) -> &CircuitConfig; /// Synthesizes the circuit constraints. fn synthesize(&self, cs: &mut CS) -> Result<(), CircuitError>; /// Returns the public inputs for this circuit instance. fn public_inputs(&self) -> Vec; } /// Transfer circuit: validates a token transfer between accounts. #[derive(Clone, Debug)] pub struct TransferCircuit { config: CircuitConfig, /// Previous state root pub old_root: StateRoot, /// New state root after transfer pub new_root: StateRoot, /// Sender account index pub sender_idx: u64, /// Recipient account index pub recipient_idx: u64, /// Transfer amount pub amount: u64, /// Sender's signature (for witness) pub signature: Vec, /// Merkle proof for sender account pub sender_proof: Vec<[u8; 32]>, /// Merkle proof for recipient account pub recipient_proof: Vec<[u8; 32]>, } impl TransferCircuit { /// Creates a new transfer circuit. pub fn new( old_root: StateRoot, new_root: StateRoot, sender_idx: u64, recipient_idx: u64, amount: u64, ) -> Self { Self { config: CircuitConfig::default(), old_root, new_root, sender_idx, recipient_idx, amount, signature: Vec::new(), sender_proof: Vec::new(), recipient_proof: Vec::new(), } } /// Sets the Merkle proofs for verification. pub fn with_proofs( mut self, sender_proof: Vec<[u8; 32]>, recipient_proof: Vec<[u8; 32]>, ) -> Self { self.sender_proof = sender_proof; self.recipient_proof = recipient_proof; self } /// Sets the signature for verification. pub fn with_signature(mut self, signature: Vec) -> Self { self.signature = signature; self } } impl Circuit for TransferCircuit { fn name(&self) -> &str { "TransferCircuit" } fn config(&self) -> &CircuitConfig { &self.config } fn synthesize(&self, cs: &mut CS) -> Result<(), CircuitError> { // Public inputs let old_root_var = cs.alloc_input("old_root", field_from_hash(&self.old_root.0))?; let new_root_var = cs.alloc_input("new_root", field_from_hash(&self.new_root.0))?; // Private witnesses let sender_idx_var = cs.alloc_witness("sender_idx", ScalarField::from(self.sender_idx))?; let recipient_idx_var = cs.alloc_witness("recipient_idx", ScalarField::from(self.recipient_idx))?; let amount_var = cs.alloc_witness("amount", ScalarField::from(self.amount))?; // Constraint 1: Amount > 0 // We don't have a direct "greater than" constraint in R1CS, // but we can enforce that amount is non-zero by requiring amount * amount_inv = 1 // This is a simplified version; real implementation would use range proofs // Constraint 2: Merkle proof verification for sender // Verify that sender account exists at sender_idx in old_root // (Merkle path verification constraints) // Constraint 3: Merkle proof verification for recipient // Verify that recipient account exists at recipient_idx in old_root // Constraint 4: Balance check // sender.balance >= amount // Constraint 5: State transition // new_sender_balance = old_sender_balance - amount // new_recipient_balance = old_recipient_balance + amount // Constraint 6: New root computation // Verify that new_root is correct after state updates // Note: Full implementation would include all Merkle proof verification // and hash constraints. This is a structural placeholder. // Dummy constraint to ensure variables are used let _lc = LinearCombination::from_variable(old_root_var); let _lc2 = LinearCombination::from_variable(new_root_var); let _lc3 = LinearCombination::from_variable(sender_idx_var); let _lc4 = LinearCombination::from_variable(recipient_idx_var); let _lc5 = LinearCombination::from_variable(amount_var); Ok(()) } fn public_inputs(&self) -> Vec { vec![ field_from_hash(&self.old_root.0), field_from_hash(&self.new_root.0), ] } } /// Batch circuit: aggregates multiple transfers into a single proof. #[derive(Clone, Debug)] pub struct BatchCircuit { config: CircuitConfig, /// Initial state root pub initial_root: StateRoot, /// Final state root after all transactions pub final_root: StateRoot, /// Individual transfer circuits pub transfers: Vec, } impl BatchCircuit { /// Creates a new batch circuit. pub fn new(initial_root: StateRoot, final_root: StateRoot) -> Self { Self { config: CircuitConfig::default(), initial_root, final_root, transfers: Vec::new(), } } /// Adds a transfer to the batch. pub fn add_transfer(&mut self, transfer: TransferCircuit) { self.transfers.push(transfer); } /// Returns the number of transfers in the batch. pub fn num_transfers(&self) -> usize { self.transfers.len() } } impl Circuit for BatchCircuit { fn name(&self) -> &str { "BatchCircuit" } fn config(&self) -> &CircuitConfig { &self.config } fn synthesize(&self, cs: &mut CS) -> Result<(), CircuitError> { // Public inputs: initial_root, final_root, batch_hash let initial_root_var = cs.alloc_input("initial_root", field_from_hash(&self.initial_root.0))?; let final_root_var = cs.alloc_input("final_root", field_from_hash(&self.final_root.0))?; // For each transfer, verify: // 1. The intermediate state roots chain correctly // 2. Each individual transfer is valid // In a real implementation, we would: // - Synthesize each transfer circuit // - Chain the state roots: root_i+1 = update(root_i, transfer_i) // - Verify initial_root -> transfer_1 -> ... -> transfer_n -> final_root let _lc = LinearCombination::from_variable(initial_root_var); let _lc2 = LinearCombination::from_variable(final_root_var); Ok(()) } fn public_inputs(&self) -> Vec { vec![ field_from_hash(&self.initial_root.0), field_from_hash(&self.final_root.0), ] } } /// Deposit circuit: validates an L1 -> L2 deposit. #[derive(Clone, Debug)] pub struct DepositCircuit { config: CircuitConfig, /// Previous state root pub old_root: StateRoot, /// New state root after deposit pub new_root: StateRoot, /// L1 deposit transaction hash pub l1_tx_hash: [u8; 32], /// Recipient account index on L2 pub recipient_idx: u64, /// Deposit amount pub amount: u64, } impl Circuit for DepositCircuit { fn name(&self) -> &str { "DepositCircuit" } fn config(&self) -> &CircuitConfig { &self.config } fn synthesize(&self, cs: &mut CS) -> Result<(), CircuitError> { let old_root_var = cs.alloc_input("old_root", field_from_hash(&self.old_root.0))?; let new_root_var = cs.alloc_input("new_root", field_from_hash(&self.new_root.0))?; let l1_tx_hash_var = cs.alloc_input("l1_tx_hash", field_from_hash(&self.l1_tx_hash))?; let _lc = LinearCombination::from_variable(old_root_var); let _lc2 = LinearCombination::from_variable(new_root_var); let _lc3 = LinearCombination::from_variable(l1_tx_hash_var); Ok(()) } fn public_inputs(&self) -> Vec { vec![ field_from_hash(&self.old_root.0), field_from_hash(&self.new_root.0), field_from_hash(&self.l1_tx_hash), ] } } /// Withdrawal circuit: validates an L2 -> L1 withdrawal. #[derive(Clone, Debug)] pub struct WithdrawCircuit { config: CircuitConfig, /// Previous state root pub old_root: StateRoot, /// New state root after withdrawal pub new_root: StateRoot, /// Sender account index on L2 pub sender_idx: u64, /// L1 recipient address pub l1_recipient: [u8; 20], /// Withdrawal amount pub amount: u64, } impl Circuit for WithdrawCircuit { fn name(&self) -> &str { "WithdrawCircuit" } fn config(&self) -> &CircuitConfig { &self.config } fn synthesize(&self, cs: &mut CS) -> Result<(), CircuitError> { let old_root_var = cs.alloc_input("old_root", field_from_hash(&self.old_root.0))?; let new_root_var = cs.alloc_input("new_root", field_from_hash(&self.new_root.0))?; let _lc = LinearCombination::from_variable(old_root_var); let _lc2 = LinearCombination::from_variable(new_root_var); Ok(()) } fn public_inputs(&self) -> Vec { vec![ field_from_hash(&self.old_root.0), field_from_hash(&self.new_root.0), ] } } /// Converts a 32-byte hash to a scalar field element. fn field_from_hash(hash: &[u8; 32]) -> ScalarField { // Take first 31 bytes to ensure it fits in the field let mut bytes = [0u8; 32]; bytes[..31].copy_from_slice(&hash[..31]); ScalarField::from_le_bytes_mod_order(&bytes) } /// Circuit errors. #[derive(Debug, Error)] pub enum CircuitError { #[error("Synthesis error: {0}")] SynthesisError(String), #[error("Invalid witness: {0}")] InvalidWitness(String), #[error("Constraint violation: {0}")] ConstraintViolation(String), #[error("Circuit configuration error: {0}")] ConfigError(String), } impl From for CircuitError { fn from(e: SynthesisError) -> Self { CircuitError::SynthesisError(e.to_string()) } } #[cfg(test)] mod tests { use super::*; // ============================================================================ // Variable Tests // ============================================================================ #[test] fn test_variable_new_creation() { let var = Variable::new(0); assert_eq!(var.index(), 0); } #[test] fn test_variable_new_with_various_indices() { for i in [0, 1, 100, 1000, usize::MAX] { let var = Variable::new(i); assert_eq!(var.index(), i); } } #[test] fn test_variable_equality() { let var1 = Variable::new(5); let var2 = Variable::new(5); let var3 = Variable::new(10); assert_eq!(var1, var2); assert_ne!(var1, var3); } #[test] fn test_variable_clone() { let var1 = Variable::new(42); let var2 = var1; assert_eq!(var1, var2); } #[test] fn test_variable_debug_format() { let var = Variable::new(123); let debug_str = format!("{:?}", var); assert!(debug_str.contains("Variable")); assert!(debug_str.contains("123")); } #[test] fn test_variable_hash() { use std::collections::HashSet; let mut set = HashSet::new(); set.insert(Variable::new(1)); set.insert(Variable::new(2)); set.insert(Variable::new(1)); // Duplicate assert_eq!(set.len(), 2); } // ============================================================================ // LinearCombination Tests // ============================================================================ #[test] fn test_linear_combination_new_empty() { let lc = LinearCombination::new(); assert!(lc.terms().is_empty()); } #[test] fn test_linear_combination_from_variable() { let var = Variable::new(1); let lc = LinearCombination::from_variable(var); assert_eq!(lc.terms().len(), 1); assert_eq!(lc.terms()[0].1, var); assert_eq!(lc.terms()[0].0, ScalarField::from(1u64)); } #[test] fn test_linear_combination_from_constant() { let value = ScalarField::from(42u64); let lc = LinearCombination::from_constant(value); assert_eq!(lc.terms().len(), 1); assert_eq!(lc.terms()[0].0, value); assert_eq!(lc.terms()[0].1, Variable(0)); // Constant is variable 0 } #[test] fn test_linear_combination_add_term() { let mut lc = LinearCombination::new(); let var1 = Variable::new(1); let var2 = Variable::new(2); lc.add_term(ScalarField::from(3u64), var1); assert_eq!(lc.terms().len(), 1); lc.add_term(ScalarField::from(5u64), var2); assert_eq!(lc.terms().len(), 2); } #[test] fn test_linear_combination_multiple_terms() { let mut lc = LinearCombination::new(); for i in 0..10 { lc.add_term(ScalarField::from(i as u64), Variable::new(i)); } assert_eq!(lc.terms().len(), 10); } #[test] fn test_linear_combination_terms_preserved_order() { let mut lc = LinearCombination::new(); let vars: Vec = (0..5).map(Variable::new).collect(); for (i, &var) in vars.iter().enumerate() { lc.add_term(ScalarField::from((i + 1) as u64), var); } for (i, (coeff, var)) in lc.terms().iter().enumerate() { assert_eq!(*coeff, ScalarField::from((i + 1) as u64)); assert_eq!(*var, vars[i]); } } #[test] fn test_linear_combination_default() { let lc = LinearCombination::default(); assert!(lc.terms().is_empty()); } #[test] fn test_linear_combination_clone() { let mut lc1 = LinearCombination::new(); lc1.add_term(ScalarField::from(5u64), Variable::new(1)); let lc2 = lc1.clone(); assert_eq!(lc1.terms().len(), lc2.terms().len()); assert_eq!(lc1.terms()[0].0, lc2.terms()[0].0); assert_eq!(lc1.terms()[0].1, lc2.terms()[0].1); } // ============================================================================ // CircuitConfig Tests // ============================================================================ #[test] fn test_circuit_config_default() { let config = CircuitConfig::default(); assert_eq!(config.max_batch_size, crate::constants::MAX_BATCH_SIZE); assert_eq!(config.tree_depth, crate::constants::STATE_TREE_DEPTH); assert!(config.verify_signatures); } #[test] fn test_circuit_config_custom_values() { let config = CircuitConfig { max_batch_size: 500, tree_depth: 16, verify_signatures: false, }; assert_eq!(config.max_batch_size, 500); assert_eq!(config.tree_depth, 16); assert!(!config.verify_signatures); } #[test] fn test_circuit_config_clone() { let config1 = CircuitConfig::default(); let config2 = config1.clone(); assert_eq!(config1.max_batch_size, config2.max_batch_size); assert_eq!(config1.tree_depth, config2.tree_depth); assert_eq!(config1.verify_signatures, config2.verify_signatures); } #[test] fn test_circuit_config_debug() { let config = CircuitConfig::default(); let debug_str = format!("{:?}", config); assert!(debug_str.contains("CircuitConfig")); assert!(debug_str.contains("max_batch_size")); } // ============================================================================ // TransferCircuit Tests // ============================================================================ #[test] fn test_transfer_circuit_creation() { let old_root = StateRoot([0u8; 32]); let new_root = StateRoot([1u8; 32]); let circuit = TransferCircuit::new(old_root, new_root, 0, 1, 100); assert_eq!(circuit.name(), "TransferCircuit"); assert_eq!(circuit.sender_idx, 0); assert_eq!(circuit.recipient_idx, 1); assert_eq!(circuit.amount, 100); } #[test] fn test_transfer_circuit_with_state_roots() { let old_root = StateRoot([0xaa; 32]); let new_root = StateRoot([0xbb; 32]); let circuit = TransferCircuit::new(old_root, new_root, 10, 20, 500); assert_eq!(circuit.old_root, old_root); assert_eq!(circuit.new_root, new_root); } #[test] fn test_transfer_circuit_with_proofs() { let old_root = StateRoot([0u8; 32]); let new_root = StateRoot([1u8; 32]); let sender_proof = vec![[1u8; 32], [2u8; 32]]; let recipient_proof = vec![[3u8; 32], [4u8; 32]]; let circuit = TransferCircuit::new(old_root, new_root, 0, 1, 100) .with_proofs(sender_proof.clone(), recipient_proof.clone()); assert_eq!(circuit.sender_proof, sender_proof); assert_eq!(circuit.recipient_proof, recipient_proof); } #[test] fn test_transfer_circuit_with_signature() { let old_root = StateRoot([0u8; 32]); let new_root = StateRoot([1u8; 32]); let signature = vec![0xab, 0xcd, 0xef]; let circuit = TransferCircuit::new(old_root, new_root, 0, 1, 100).with_signature(signature.clone()); assert_eq!(circuit.signature, signature); } #[test] fn test_transfer_circuit_public_inputs_extraction() { let old_root = StateRoot([0xaa; 32]); let new_root = StateRoot([0xbb; 32]); let circuit = TransferCircuit::new(old_root, new_root, 0, 1, 100); let public_inputs = circuit.public_inputs(); assert_eq!(public_inputs.len(), 2); assert_eq!(public_inputs[0], field_from_hash(&old_root.0)); assert_eq!(public_inputs[1], field_from_hash(&new_root.0)); } #[test] fn test_transfer_circuit_name() { let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100); assert_eq!(circuit.name(), "TransferCircuit"); } #[test] fn test_transfer_circuit_config_default() { let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100); let config = circuit.config(); assert_eq!(config.max_batch_size, crate::constants::MAX_BATCH_SIZE); } #[test] fn test_transfer_circuit_builder_chain() { let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100) .with_proofs(vec![[1u8; 32]], vec![[2u8; 32]]) .with_signature(vec![1, 2, 3]); assert_eq!(circuit.sender_proof.len(), 1); assert_eq!(circuit.recipient_proof.len(), 1); assert_eq!(circuit.signature.len(), 3); } #[test] fn test_transfer_circuit_clone() { let circuit = TransferCircuit::new(StateRoot([0xaa; 32]), StateRoot([0xbb; 32]), 5, 10, 500); let cloned = circuit.clone(); assert_eq!(circuit.old_root, cloned.old_root); assert_eq!(circuit.new_root, cloned.new_root); assert_eq!(circuit.sender_idx, cloned.sender_idx); assert_eq!(circuit.recipient_idx, cloned.recipient_idx); assert_eq!(circuit.amount, cloned.amount); } #[test] fn test_transfer_circuit_large_amount() { let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, u64::MAX); assert_eq!(circuit.amount, u64::MAX); } #[test] fn test_transfer_circuit_zero_amount() { let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 0); assert_eq!(circuit.amount, 0); } // ============================================================================ // BatchCircuit Tests // ============================================================================ #[test] fn test_batch_circuit_empty_creation() { let initial_root = StateRoot([0u8; 32]); let final_root = StateRoot([2u8; 32]); let batch = BatchCircuit::new(initial_root, final_root); assert_eq!(batch.num_transfers(), 0); assert_eq!(batch.initial_root, initial_root); assert_eq!(batch.final_root, final_root); } #[test] fn test_batch_circuit_add_transfer() { let mut batch = BatchCircuit::new(StateRoot([0u8; 32]), StateRoot([2u8; 32])); assert_eq!(batch.num_transfers(), 0); let transfer = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100); batch.add_transfer(transfer); assert_eq!(batch.num_transfers(), 1); } #[test] fn test_batch_circuit_add_multiple_transfers() { let mut batch = BatchCircuit::new(StateRoot([0u8; 32]), StateRoot([10u8; 32])); for i in 0..5 { let transfer = TransferCircuit::new( StateRoot([i as u8; 32]), StateRoot([(i + 1) as u8; 32]), i as u64, (i + 1) as u64, 100 * (i as u64 + 1), ); batch.add_transfer(transfer); } assert_eq!(batch.num_transfers(), 5); } #[test] fn test_batch_circuit_num_transfers_accuracy() { let mut batch = BatchCircuit::new(StateRoot([0u8; 32]), StateRoot([10u8; 32])); for i in 0..100 { batch.add_transfer(TransferCircuit::new( StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, i, )); assert_eq!(batch.num_transfers(), (i + 1) as usize); } } #[test] fn test_batch_circuit_public_inputs() { let initial_root = StateRoot([0xaa; 32]); let final_root = StateRoot([0xbb; 32]); let batch = BatchCircuit::new(initial_root, final_root); let public_inputs = batch.public_inputs(); assert_eq!(public_inputs.len(), 2); assert_eq!(public_inputs[0], field_from_hash(&initial_root.0)); assert_eq!(public_inputs[1], field_from_hash(&final_root.0)); } #[test] fn test_batch_circuit_name() { let batch = BatchCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32])); assert_eq!(batch.name(), "BatchCircuit"); } #[test] fn test_batch_circuit_config() { let batch = BatchCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32])); let config = batch.config(); assert_eq!(config.max_batch_size, crate::constants::MAX_BATCH_SIZE); } #[test] fn test_batch_circuit_clone() { let mut batch = BatchCircuit::new(StateRoot([0xaa; 32]), StateRoot([0xbb; 32])); batch.add_transfer(TransferCircuit::new( StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100, )); let cloned = batch.clone(); assert_eq!(batch.initial_root, cloned.initial_root); assert_eq!(batch.final_root, cloned.final_root); assert_eq!(batch.num_transfers(), cloned.num_transfers()); } // ============================================================================ // DepositCircuit Tests // ============================================================================ #[test] fn test_deposit_circuit_structure() { let circuit = DepositCircuit { config: CircuitConfig::default(), old_root: StateRoot([0xaa; 32]), new_root: StateRoot([0xbb; 32]), l1_tx_hash: [0xcc; 32], recipient_idx: 42, amount: 1000, }; assert_eq!(circuit.old_root, StateRoot([0xaa; 32])); assert_eq!(circuit.new_root, StateRoot([0xbb; 32])); assert_eq!(circuit.l1_tx_hash, [0xcc; 32]); assert_eq!(circuit.recipient_idx, 42); assert_eq!(circuit.amount, 1000); } #[test] fn test_deposit_circuit_name() { let circuit = DepositCircuit { config: CircuitConfig::default(), old_root: StateRoot([0u8; 32]), new_root: StateRoot([1u8; 32]), l1_tx_hash: [0u8; 32], recipient_idx: 0, amount: 0, }; assert_eq!(circuit.name(), "DepositCircuit"); } #[test] fn test_deposit_circuit_public_inputs() { let circuit = DepositCircuit { config: CircuitConfig::default(), old_root: StateRoot([0xaa; 32]), new_root: StateRoot([0xbb; 32]), l1_tx_hash: [0xcc; 32], recipient_idx: 0, amount: 100, }; let public_inputs = circuit.public_inputs(); assert_eq!(public_inputs.len(), 3); assert_eq!(public_inputs[0], field_from_hash(&circuit.old_root.0)); assert_eq!(public_inputs[1], field_from_hash(&circuit.new_root.0)); assert_eq!(public_inputs[2], field_from_hash(&circuit.l1_tx_hash)); } #[test] fn test_deposit_circuit_clone() { let circuit = DepositCircuit { config: CircuitConfig::default(), old_root: StateRoot([0xaa; 32]), new_root: StateRoot([0xbb; 32]), l1_tx_hash: [0xcc; 32], recipient_idx: 42, amount: 1000, }; let cloned = circuit.clone(); assert_eq!(circuit.old_root, cloned.old_root); assert_eq!(circuit.l1_tx_hash, cloned.l1_tx_hash); assert_eq!(circuit.amount, cloned.amount); } #[test] fn test_deposit_circuit_config() { let circuit = DepositCircuit { config: CircuitConfig::default(), old_root: StateRoot([0u8; 32]), new_root: StateRoot([1u8; 32]), l1_tx_hash: [0u8; 32], recipient_idx: 0, amount: 0, }; let config = circuit.config(); assert_eq!(config.max_batch_size, crate::constants::MAX_BATCH_SIZE); } // ============================================================================ // WithdrawCircuit Tests // ============================================================================ #[test] fn test_withdraw_circuit_structure() { let circuit = WithdrawCircuit { config: CircuitConfig::default(), old_root: StateRoot([0xaa; 32]), new_root: StateRoot([0xbb; 32]), sender_idx: 10, l1_recipient: [0xcc; 20], amount: 500, }; assert_eq!(circuit.old_root, StateRoot([0xaa; 32])); assert_eq!(circuit.new_root, StateRoot([0xbb; 32])); assert_eq!(circuit.sender_idx, 10); assert_eq!(circuit.l1_recipient, [0xcc; 20]); assert_eq!(circuit.amount, 500); } #[test] fn test_withdraw_circuit_name() { let circuit = WithdrawCircuit { config: CircuitConfig::default(), old_root: StateRoot([0u8; 32]), new_root: StateRoot([1u8; 32]), sender_idx: 0, l1_recipient: [0u8; 20], amount: 0, }; assert_eq!(circuit.name(), "WithdrawCircuit"); } #[test] fn test_withdraw_circuit_public_inputs() { let circuit = WithdrawCircuit { config: CircuitConfig::default(), old_root: StateRoot([0xaa; 32]), new_root: StateRoot([0xbb; 32]), sender_idx: 0, l1_recipient: [0u8; 20], amount: 100, }; let public_inputs = circuit.public_inputs(); assert_eq!(public_inputs.len(), 2); assert_eq!(public_inputs[0], field_from_hash(&circuit.old_root.0)); assert_eq!(public_inputs[1], field_from_hash(&circuit.new_root.0)); } #[test] fn test_withdraw_circuit_clone() { let circuit = WithdrawCircuit { config: CircuitConfig::default(), old_root: StateRoot([0xaa; 32]), new_root: StateRoot([0xbb; 32]), sender_idx: 10, l1_recipient: [0xcc; 20], amount: 500, }; let cloned = circuit.clone(); assert_eq!(circuit.old_root, cloned.old_root); assert_eq!(circuit.sender_idx, cloned.sender_idx); assert_eq!(circuit.l1_recipient, cloned.l1_recipient); assert_eq!(circuit.amount, cloned.amount); } #[test] fn test_withdraw_circuit_config() { let circuit = WithdrawCircuit { config: CircuitConfig::default(), old_root: StateRoot([0u8; 32]), new_root: StateRoot([1u8; 32]), sender_idx: 0, l1_recipient: [0u8; 20], amount: 0, }; let config = circuit.config(); assert!(config.verify_signatures); } // ============================================================================ // CircuitError Tests // ============================================================================ #[test] fn test_circuit_error_synthesis_display() { let error = CircuitError::SynthesisError("test synthesis error".to_string()); let display = format!("{}", error); assert!(display.contains("Synthesis error")); assert!(display.contains("test synthesis error")); } #[test] fn test_circuit_error_invalid_witness_display() { let error = CircuitError::InvalidWitness("bad witness data".to_string()); let display = format!("{}", error); assert!(display.contains("Invalid witness")); assert!(display.contains("bad witness data")); } #[test] fn test_circuit_error_constraint_violation_display() { let error = CircuitError::ConstraintViolation("constraint failed".to_string()); let display = format!("{}", error); assert!(display.contains("Constraint violation")); assert!(display.contains("constraint failed")); } #[test] fn test_circuit_error_config_display() { let error = CircuitError::ConfigError("invalid config".to_string()); let display = format!("{}", error); assert!(display.contains("Circuit configuration error")); assert!(display.contains("invalid config")); } #[test] fn test_circuit_error_debug_format() { let error = CircuitError::SynthesisError("test".to_string()); let debug_str = format!("{:?}", error); assert!(debug_str.contains("SynthesisError")); } // ============================================================================ // field_from_hash Tests // ============================================================================ #[test] fn test_field_from_hash_zero() { let hash = [0u8; 32]; let field = field_from_hash(&hash); assert_eq!(field, ScalarField::from(0u64)); } #[test] fn test_field_from_hash_all_ones() { let hash = [0xffu8; 32]; let field = field_from_hash(&hash); let _ = field; } #[test] fn test_field_from_hash_consistency() { let hash = [0xab; 32]; let field1 = field_from_hash(&hash); let field2 = field_from_hash(&hash); assert_eq!(field1, field2); } #[test] fn test_field_from_hash_different_inputs() { let hash1 = [0xaa; 32]; let hash2 = [0xbb; 32]; let field1 = field_from_hash(&hash1); let field2 = field_from_hash(&hash2); assert_ne!(field1, field2); } #[test] fn test_field_from_hash_truncation() { let mut hash1 = [0xaa; 32]; let mut hash2 = [0xaa; 32]; hash1[31] = 0x00; hash2[31] = 0xff; let field1 = field_from_hash(&hash1); let field2 = field_from_hash(&hash2); assert_eq!(field1, field2); } // ============================================================================ // Circuit Trait Implementation Tests // ============================================================================ #[test] fn test_transfer_circuit_trait_clone() { let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100); fn assert_clone(_: &T) {} assert_clone(&circuit); } #[test] fn test_batch_circuit_trait_send_sync() { let batch = BatchCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32])); fn assert_send_sync(_: &T) {} assert_send_sync(&batch); } // ============================================================================ // Edge Cases and Boundary Tests // ============================================================================ #[test] fn test_transfer_circuit_max_indices() { let circuit = TransferCircuit::new( StateRoot([0u8; 32]), StateRoot([1u8; 32]), u64::MAX, u64::MAX - 1, u64::MAX, ); assert_eq!(circuit.sender_idx, u64::MAX); assert_eq!(circuit.recipient_idx, u64::MAX - 1); assert_eq!(circuit.amount, u64::MAX); } #[test] fn test_batch_circuit_with_zero_transfers() { let batch = BatchCircuit::new(StateRoot([0u8; 32]), StateRoot([0u8; 32])); assert_eq!(batch.num_transfers(), 0); assert_eq!(batch.initial_root, batch.final_root); } #[test] fn test_empty_merkle_proofs() { let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100) .with_proofs(vec![], vec![]); assert!(circuit.sender_proof.is_empty()); assert!(circuit.recipient_proof.is_empty()); } #[test] fn test_empty_signature() { let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100) .with_signature(vec![]); assert!(circuit.signature.is_empty()); } #[test] fn test_large_merkle_proof() { let large_proof: Vec<[u8; 32]> = (0..32).map(|i| [i as u8; 32]).collect(); let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100) .with_proofs(large_proof.clone(), large_proof.clone()); assert_eq!(circuit.sender_proof.len(), 32); assert_eq!(circuit.recipient_proof.len(), 32); } }