diff --git a/circuits/withdraw/src/main.nr b/circuits/withdraw/src/main.nr index 4836759..ecd3930 100644 --- a/circuits/withdraw/src/main.nr +++ b/circuits/withdraw/src/main.nr @@ -58,6 +58,7 @@ fn main( amount: pub Field, relayer: pub Field, fee: pub Field, + denomination: pub Field, ) { // -- Step 1: Reconstruct commitment -- // commitment = Hash(nullifier, secret) @@ -82,6 +83,17 @@ fn main( // Relayer must be zero if fee is zero validation::validate_relayer(relayer, fee); + // Verify amount matches denomination + // Denomination values: 10=10000000, 100=100000000, 1000=1000000000, 10000=10000000000 + let expected_amount = match denomination { + 10 => 100_000_000, + 100 => 1_000_000_000, + 1000 => 10_000_000_000, + 10000 => 100_000_000_000, + _ => 0, + }; + assert(amount == expected_amount, "amount must match denomination"); + // Bind recipient to prevent front-running // (In Groth16, all pub inputs are automatically bound to the proof) let _ = recipient; @@ -109,13 +121,14 @@ fn test_valid_withdrawal() { let nullifier_hash = hash::compute_nullifier_hash(nullifier, root); let recipient: Field = 0xABCD; - let amount: Field = 100_0000000; // 100 XLM in stroops + let amount: Field = 1_000_000_000; // 100 XLM in stroops let relayer: Field = 0; let fee: Field = 0; + let denomination: Field = 100; // Hundred denomination main( nullifier, secret, 0, hash_path, - root, nullifier_hash, recipient, amount, relayer, fee + root, nullifier_hash, recipient, amount, relayer, fee, denomination ); } @@ -129,13 +142,14 @@ fn test_withdrawal_with_relayer_fee() { let nullifier_hash = hash::compute_nullifier_hash(nullifier, root); let recipient: Field = 0xDEAD; - let amount: Field = 100_0000000; + let amount: Field = 1_000_000_000; let relayer: Field = 0xBEEF; - let fee: Field = 1_0000000; // 1 XLM fee + let fee: Field = 10_000_000; // 1 XLM fee + let denomination: Field = 100; // Hundred denomination main( nullifier, secret, 0, hash_path, - root, nullifier_hash, recipient, amount, relayer, fee + root, nullifier_hash, recipient, amount, relayer, fee, denomination ); } diff --git a/contracts/privacy_pool/src/contract.rs b/contracts/privacy_pool/src/contract.rs index 9de96ee..4db0a8d 100644 --- a/contracts/privacy_pool/src/contract.rs +++ b/contracts/privacy_pool/src/contract.rs @@ -23,30 +23,30 @@ impl PrivacyPool { /// Initialize the privacy pool. /// /// Must be called once before any deposits or withdrawals. - /// Sets the admin, token, denomination, and verifying key. + /// Sets the admin, token, and verifying key. pub fn initialize( env: Env, admin: Address, token: Address, - denomination: Denomination, vk: VerifyingKey, ) -> Result<(), Error> { - initialize::execute(env, admin, token, denomination, vk) + initialize::execute(env, admin, token, vk) } // ────────────────────────────────────────────────────────── // Core Operations // ────────────────────────────────────────────────────────── - /// Deposit into the shielded pool. + /// Deposit into the shielded pool for a specific denomination. /// /// Transfers denomination amount and inserts commitment into Merkle tree. pub fn deposit( env: Env, from: Address, + denomination: Denomination, commitment: BytesN<32>, ) -> Result<(u32, BytesN<32>), Error> { - deposit::execute(env, from, commitment) + deposit::execute(env, from, denomination, commitment) } /// Withdraw from the shielded pool using a ZK proof. @@ -64,19 +64,19 @@ impl PrivacyPool { // View Functions // ────────────────────────────────────────────────────────── - /// Returns the current Merkle root (most recent). - pub fn get_root(env: Env) -> Result, Error> { - view::get_root(env) + /// Returns the current Merkle root for a denomination (most recent). + pub fn get_root_by_denomination(env: Env, denomination: Denomination) -> Result, Error> { + view::get_root_by_denomination(env, denomination) } - /// Returns the total number of deposits. - pub fn deposit_count(env: Env) -> u32 { - view::deposit_count(env) + /// Returns the total number of deposits for a denomination. + pub fn deposit_count_by_denomination(env: Env, denomination: Denomination) -> u32 { + view::deposit_count_by_denomination(env, denomination) } - /// Check if a root is in the historical root buffer. - pub fn is_known_root(env: Env, root: BytesN<32>) -> bool { - view::is_known_root(env, root) + /// Check if a root is in the historical root buffer for a denomination. + pub fn is_known_root_for_denomination(env: Env, root: BytesN<32>, denomination: Denomination) -> bool { + view::is_known_root_for_denomination(env, root, denomination) } /// Check if a nullifier has been spent. @@ -89,6 +89,11 @@ impl PrivacyPool { view::get_config(env) } + /// Returns all supported denominations. + pub fn get_all_denominations(env: Env) -> Vec { + view::get_all_denominations(env) + } + // ────────────────────────────────────────────────────────── // Admin Functions // ────────────────────────────────────────────────────────── diff --git a/contracts/privacy_pool/src/core/deposit.rs b/contracts/privacy_pool/src/core/deposit.rs index da2c6c6..04210ec 100644 --- a/contracts/privacy_pool/src/core/deposit.rs +++ b/contracts/privacy_pool/src/core/deposit.rs @@ -2,19 +2,21 @@ // Deposit Logic // ============================================================ -use soroban_sdk::{token, Address, BytesN, Env}; +use soroban_sdk::{token, Address, BytesN, Env, Vec}; use crate::crypto::merkle; use crate::storage::config; use crate::types::errors::Error; use crate::types::events::emit_deposit; +use crate::types::state::{Denomination, DataKey}; use crate::utils::validation; -/// Execute a deposit into the shielded pool. +/// Execute a deposit into the shielded pool for a specific denomination. /// /// # Arguments -/// - `from` : depositor's Stellar address (must authorize) -/// - `commitment` : 32-byte field element = Hash(nullifier, secret) +/// - `from` : depositor's Stellar address (must authorize) +/// - `denomination`: which fixed amount to deposit +/// - `commitment` : 32-byte field element = Hash(nullifier, secret) /// /// # Returns /// `(leaf_index, merkle_root)` - store leaf_index with your note @@ -24,9 +26,11 @@ use crate::utils::validation; /// - `Error::PoolPaused` if pool is paused /// - `Error::ZeroCommitment` if commitment is all zeros /// - `Error::TreeFull` if pool is full (1,048,576 deposits) +/// - `Error::WrongAmount` if transferred amount doesn't match denomination pub fn execute( env: Env, from: Address, + denomination: Denomination, commitment: BytesN<32>, ) -> Result<(u32, BytesN<32>), Error> { // Require authorization from the depositor @@ -40,7 +44,7 @@ pub fn execute( validation::require_non_zero_commitment(&env, &commitment)?; // Transfer denomination amount from depositor to contract vault - let amount = pool_config.denomination.amount(); + let amount = denomination.amount(); let token_client = token::Client::new(&env, &pool_config.token); token_client.transfer( &from, @@ -48,11 +52,38 @@ pub fn execute( &amount, ); - // Insert commitment into Merkle tree - let (leaf_index, new_root) = merkle::insert(&env, commitment.clone())?; + // Add denomination to list if not already present + add_denomination(&env, &denomination); + + // Insert commitment into Merkle tree for this denomination + let denom_value = denomination.to_u32(); + let (leaf_index, new_root) = merkle::insert(&env, denom_value, commitment.clone())?; // Emit deposit event (no depositor address for privacy) - emit_deposit(&env, commitment, leaf_index, new_root.clone()); + emit_deposit(&env, commitment, leaf_index, new_root.clone(), denomination); Ok((leaf_index, new_root)) } + +/// Add a denomination to the list of supported denominations if not already present +fn add_denomination(env: &Env, denomination: &Denomination) { + let key = DataKey::Denominations; + let mut denominations: Vec = env.storage() + .persistent() + .get(&key) + .unwrap_or_else(|| Vec::new(env)); + + // Check if denomination is already in the list + let mut found = false; + for i in 0..denominations.len() { + if denominations.get(i).unwrap() == *denomination { + found = true; + break; + } + } + + if !found { + denominations.push_back(denomination.clone()); + env.storage().persistent().set(&key, &denominations); + } +} diff --git a/contracts/privacy_pool/src/core/initialize.rs b/contracts/privacy_pool/src/core/initialize.rs index d33e3c0..a51f08a 100644 --- a/contracts/privacy_pool/src/core/initialize.rs +++ b/contracts/privacy_pool/src/core/initialize.rs @@ -7,14 +7,13 @@ use soroban_sdk::{Address, Env}; use crate::crypto::merkle; use crate::storage::config; use crate::types::errors::Error; -use crate::types::state::{Denomination, PoolConfig, VerifyingKey}; +use crate::types::state::{PoolConfig, VerifyingKey}; /// Initialize the privacy pool with configuration. /// /// # Arguments /// - `admin` : address that can pause/update the pool /// - `token` : token contract (use Stellar native XLM or USDC SAC) -/// - `denomination`: fixed deposit/withdrawal amount /// - `vk` : Groth16 verifying key for the withdrawal circuit /// /// # Errors @@ -23,7 +22,6 @@ pub fn execute( env: Env, admin: Address, token: Address, - denomination: Denomination, vk: VerifyingKey, ) -> Result<(), Error> { // Check if already initialized @@ -35,7 +33,6 @@ pub fn execute( let pool_config = PoolConfig { admin, token, - denomination, tree_depth: merkle::TREE_DEPTH, root_history_size: merkle::ROOT_HISTORY_SIZE, paused: false, diff --git a/contracts/privacy_pool/src/core/view.rs b/contracts/privacy_pool/src/core/view.rs index 06e834f..dd20696 100644 --- a/contracts/privacy_pool/src/core/view.rs +++ b/contracts/privacy_pool/src/core/view.rs @@ -2,26 +2,29 @@ // View Functions - Read-only queries // ============================================================ -use soroban_sdk::{BytesN, Env}; +use soroban_sdk::{BytesN, Env, Vec}; use crate::crypto::merkle; use crate::storage::{config, nullifier}; use crate::types::errors::Error; -use crate::types::state::PoolConfig; +use crate::types::state::{PoolConfig, Denomination, DataKey}; -/// Returns the current Merkle root (most recent). -pub fn get_root(env: Env) -> Result, Error> { - merkle::current_root(&env).ok_or(Error::NotInitialized) +/// Returns the current Merkle root for a denomination (most recent). +pub fn get_root_by_denomination(env: Env, denomination: Denomination) -> Result, Error> { + let denom_value = denomination.to_u32(); + merkle::current_root(&env, denom_value).ok_or(Error::NotInitialized) } -/// Returns the total number of deposits (= next leaf index). -pub fn deposit_count(env: Env) -> u32 { - merkle::get_tree_state(&env).next_index +/// Returns the total number of deposits for a denomination (= next leaf index). +pub fn deposit_count_by_denomination(env: Env, denomination: Denomination) -> u32 { + let denom_value = denomination.to_u32(); + merkle::get_tree_state(&env, denom_value).next_index } -/// Check if a root is in the historical root buffer. -pub fn is_known_root(env: Env, root: BytesN<32>) -> bool { - merkle::is_known_root(&env, &root) +/// Check if a root is in the historical root buffer for a denomination. +pub fn is_known_root_for_denomination(env: Env, root: BytesN<32>, denomination: Denomination) -> bool { + let denom_value = denomination.to_u32(); + merkle::is_known_root(&env, denom_value, &root) } /// Check if a nullifier has been spent. @@ -33,3 +36,11 @@ pub fn is_spent(env: Env, nullifier_hash: BytesN<32>) -> bool { pub fn get_config(env: Env) -> Result { config::load(&env) } + +/// Returns all supported denominations. +pub fn get_all_denominations(env: Env) -> Vec { + env.storage() + .persistent() + .get(&DataKey::Denominations) + .unwrap_or_else(|| Vec::new(&env)) +} diff --git a/contracts/privacy_pool/src/core/withdraw.rs b/contracts/privacy_pool/src/core/withdraw.rs index 15197a8..e7828a9 100644 --- a/contracts/privacy_pool/src/core/withdraw.rs +++ b/contracts/privacy_pool/src/core/withdraw.rs @@ -2,13 +2,13 @@ // Withdrawal Logic // ============================================================ -use soroban_sdk::{token, Address, Env}; +use soroban_sdk::{token, Address, Env, BytesN}; -use crate::crypto::verifier; +use crate::crypto::{verifier, merkle}; use crate::storage::{config, nullifier}; use crate::types::errors::Error; use crate::types::events::emit_withdraw; -use crate::types::state::{Proof, PublicInputs}; +use crate::types::state::{Proof, PublicInputs, Denomination, DataKey}; use crate::utils::{address_decoder, validation}; /// Execute a withdrawal from the shielded pool using a ZK proof. @@ -27,6 +27,7 @@ use crate::utils::{address_decoder, validation}; /// - `Error::NullifierAlreadySpent` if nullifier was already used /// - `Error::FeeExceedsAmount` if fee > denomination amount /// - `Error::InvalidProof` if Groth16 verification fails +/// - `Error::WrongAmount` if amount doesn't match denomination pub fn execute( env: Env, proof: Proof, @@ -36,10 +37,15 @@ pub fn execute( let pool_config = config::load(&env)?; validation::require_not_paused(&pool_config)?; - let denomination_amount = pool_config.denomination.amount(); + // Decode denomination from public inputs + let denomination = decode_denomination(&env, &pub_inputs.denomination)?; + let denomination_amount = denomination.amount(); - // Step 1: Validate root is in history - validation::require_known_root(&env, &pub_inputs.root)?; + // Step 1: Validate root is in history for this denomination + let denom_value = denomination.to_u32(); + if !merkle::is_known_root(&env, denom_value, &pub_inputs.root) { + return Err(Error::UnknownRoot); + } // Step 2: Check nullifier not already spent validation::require_nullifier_unspent(&env, &pub_inputs.nullifier_hash)?; @@ -79,11 +85,23 @@ pub fn execute( relayer_opt.clone(), fee, denomination_amount, + denomination, ); Ok(true) } +/// Decode denomination from 32-byte field element +fn decode_denomination(env: &Env, denom_bytes: &BytesN<32>) -> Result { + // Convert to u32 by taking the last 4 bytes + let bytes = denom_bytes.to_array(); + let mut value_bytes = [0u8; 4]; + value_bytes.copy_from_slice(&bytes[28..32]); + let value = u32::from_be_bytes(value_bytes); + + Denomination::from_u32(value).ok_or(Error::WrongAmount) +} + /// Transfer funds to recipient and optionally to relayer. fn transfer_funds( env: &Env, diff --git a/contracts/privacy_pool/src/crypto/merkle.rs b/contracts/privacy_pool/src/crypto/merkle.rs index 778122b..7ce52d9 100644 --- a/contracts/privacy_pool/src/crypto/merkle.rs +++ b/contracts/privacy_pool/src/crypto/merkle.rs @@ -74,49 +74,49 @@ pub fn zero_at_level(env: &Env, level: u32) -> BytesN<32> { // Storage Accessors // ────────────────────────────────────────────────────────────── -pub fn get_tree_state(env: &Env) -> TreeState { +pub fn get_tree_state(env: &Env, denom: u32) -> TreeState { env.storage() .persistent() - .get(&DataKey::TreeState) + .get(&DataKey::TreeState(denom)) .unwrap_or_default() } -pub fn save_tree_state(env: &Env, state: &TreeState) { +pub fn save_tree_state(env: &Env, denom: u32, state: &TreeState) { env.storage() .persistent() - .set(&DataKey::TreeState, state); + .set(&DataKey::TreeState(denom), state); } -pub fn get_root(env: &Env, index: u32) -> Option> { +pub fn get_root(env: &Env, denom: u32, index: u32) -> Option> { env.storage() .persistent() - .get(&DataKey::Root(index % ROOT_HISTORY_SIZE)) + .get(&DataKey::Root(denom, index % ROOT_HISTORY_SIZE)) } -pub fn save_root(env: &Env, index: u32, root: BytesN<32>) { +pub fn save_root(env: &Env, denom: u32, index: u32, root: BytesN<32>) { env.storage() .persistent() - .set(&DataKey::Root(index % ROOT_HISTORY_SIZE), &root); + .set(&DataKey::Root(denom, index % ROOT_HISTORY_SIZE), &root); } -pub fn get_filled_subtree(env: &Env, level: u32) -> BytesN<32> { +pub fn get_filled_subtree(env: &Env, denom: u32, level: u32) -> BytesN<32> { env.storage() .persistent() - .get(&DataKey::FilledSubtree(level)) + .get(&DataKey::FilledSubtree(denom, level)) .unwrap_or_else(|| zero_at_level(env, level)) } -pub fn save_filled_subtree(env: &Env, level: u32, hash: BytesN<32>) { +pub fn save_filled_subtree(env: &Env, denom: u32, level: u32, hash: BytesN<32>) { env.storage() .persistent() - .set(&DataKey::FilledSubtree(level), &hash); + .set(&DataKey::FilledSubtree(denom, level), &hash); } // ────────────────────────────────────────────────────────────── // Merkle Tree Operations // ────────────────────────────────────────────────────────────── -/// Insert a commitment into the incremental Merkle tree. +/// Insert a commitment into the incremental Merkle tree for a specific denomination. /// /// Updates the filled subtrees and computes the new Merkle root. /// O(depth) = O(20) hash operations per insertion. @@ -126,8 +126,8 @@ pub fn save_filled_subtree(env: &Env, level: u32, hash: BytesN<32>) { /// /// # Errors /// - `Error::TreeFull` if all 2^20 leaf slots are used -pub fn insert(env: &Env, commitment: BytesN<32>) -> Result<(u32, BytesN<32>), Error> { - let mut state = get_tree_state(env); +pub fn insert(env: &Env, denom: u32, commitment: BytesN<32>) -> Result<(u32, BytesN<32>), Error> { + let mut state = get_tree_state(env, denom); let max_leaves = 1u32 << TREE_DEPTH; if state.next_index >= max_leaves { @@ -146,10 +146,10 @@ pub fn insert(env: &Env, commitment: BytesN<32>) -> Result<(u32, BytesN<32>), Er // Left child → save as filled subtree, right = zero left = current_hash.clone(); right = zero_at_level(env, level); - save_filled_subtree(env, level, current_hash.clone()); + save_filled_subtree(env, denom, level, current_hash.clone()); } else { // Right child → left = previously saved filled subtree - left = get_filled_subtree(env, level); + left = get_filled_subtree(env, denom, level); right = current_hash.clone(); } @@ -161,11 +161,11 @@ pub fn insert(env: &Env, commitment: BytesN<32>) -> Result<(u32, BytesN<32>), Er // Save root to circular history buffer let new_root_index = state.current_root_index.wrapping_add(1) % ROOT_HISTORY_SIZE; - save_root(env, new_root_index, new_root.clone()); + save_root(env, denom, new_root_index, new_root.clone()); state.current_root_index = new_root_index; state.next_index = leaf_index + 1; - save_tree_state(env, &state); + save_tree_state(env, denom, &state); Ok((leaf_index, new_root)) } @@ -174,9 +174,9 @@ pub fn insert(env: &Env, commitment: BytesN<32>) -> Result<(u32, BytesN<32>), Er // Root History // ────────────────────────────────────────────────────────────── -/// Check if a given root is in the historical root buffer. -pub fn is_known_root(env: &Env, root: &BytesN<32>) -> bool { - let state = get_tree_state(env); +/// Check if a given root is in the historical root buffer for a denomination. +pub fn is_known_root(env: &Env, denom: u32, root: &BytesN<32>) -> bool { + let state = get_tree_state(env, denom); if state.next_index == 0 { return false; @@ -184,7 +184,7 @@ pub fn is_known_root(env: &Env, root: &BytesN<32>) -> bool { let mut index = state.current_root_index; for _ in 0..ROOT_HISTORY_SIZE { - if let Some(stored_root) = get_root(env, index) { + if let Some(stored_root) = get_root(env, denom, index) { if stored_root == *root { return true; } @@ -199,11 +199,11 @@ pub fn is_known_root(env: &Env, root: &BytesN<32>) -> bool { false } -/// Returns the current (most recent) Merkle root. -pub fn current_root(env: &Env) -> Option> { - let state = get_tree_state(env); +/// Returns the current (most recent) Merkle root for a denomination. +pub fn current_root(env: &Env, denom: u32) -> Option> { + let state = get_tree_state(env, denom); if state.next_index == 0 { return None; } - get_root(env, state.current_root_index) + get_root(env, denom, state.current_root_index) } diff --git a/contracts/privacy_pool/src/types/events.rs b/contracts/privacy_pool/src/types/events.rs index c9a04a3..e888ffc 100644 --- a/contracts/privacy_pool/src/types/events.rs +++ b/contracts/privacy_pool/src/types/events.rs @@ -21,6 +21,7 @@ pub struct DepositEvent { pub commitment: BytesN<32>, pub leaf_index: u32, pub root: BytesN<32>, + pub denomination: Denomination, } #[contractevent] @@ -31,6 +32,7 @@ pub struct WithdrawEvent { pub relayer: Option
, pub fee: i128, pub amount: i128, + pub denomination: Denomination, } #[contractevent] @@ -65,11 +67,13 @@ pub fn emit_deposit( commitment: BytesN<32>, leaf_index: u32, root: BytesN<32>, + denomination: Denomination, ) { DepositEvent { commitment, leaf_index, root, + denomination, }.publish(env); } @@ -92,6 +96,7 @@ pub fn emit_withdraw( relayer: Option
, fee: i128, amount: i128, + denomination: Denomination, ) { WithdrawEvent { nullifier_hash, @@ -99,6 +104,7 @@ pub fn emit_withdraw( relayer, fee, amount, + denomination, }.publish(env); } diff --git a/contracts/privacy_pool/src/types/state.rs b/contracts/privacy_pool/src/types/state.rs index 2381aa3..9e13c90 100644 --- a/contracts/privacy_pool/src/types/state.rs +++ b/contracts/privacy_pool/src/types/state.rs @@ -18,18 +18,20 @@ use soroban_sdk::{contracttype, Address, BytesN}; #[contracttype] #[derive(Clone, Debug, PartialEq)] pub enum DataKey { - /// Contract configuration (admin, denomination, etc.) + /// Contract configuration (admin, etc.) Config, - /// Current Merkle tree state (root index, next leaf index) - TreeState, - /// Historical Merkle roots — DataKey::Root(index) → BytesN<32> - Root(u32), - /// Merkle tree filled subtree hashes at each level — DataKey::FilledSubtree(level) → BytesN<32> - FilledSubtree(u32), + /// Current Merkle tree state per denomination — DataKey::TreeState(denom) → TreeState + TreeState(u32), + /// Historical Merkle roots per denomination — DataKey::Root(denom, index) → BytesN<32> + Root(u32, u32), + /// Merkle tree filled subtree hashes at each level per denomination — DataKey::FilledSubtree(denom, level) → BytesN<32> + FilledSubtree(u32, u32), /// Spent nullifier hashes — DataKey::Nullifier(hash) → bool Nullifier(BytesN<32>), /// Verification key for the Groth16 proof system VerifyingKey, + /// List of supported denominations + Denominations, } // ────────────────────────────────────────────────────────────── @@ -39,29 +41,47 @@ pub enum DataKey { /// Fixed denomination amounts supported by the pool. /// Using fixed denominations prevents amount-based correlation attacks. #[contracttype] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Denomination { /// 10 XLM (in stroops: 10 * 10_000_000) - Xlm10, + Ten, /// 100 XLM - Xlm100, + Hundred, /// 1000 XLM - Xlm1000, - /// 100 USDC (6 decimal places: 100 * 1_000_000) - Usdc100, - /// 1000 USDC - Usdc1000, + Thousand, + /// 10000 XLM + TenThousand, } impl Denomination { /// Returns the stroop/microunit amount for this denomination. pub fn amount(&self) -> i128 { match self { - Denomination::Xlm10 => 100_000_000, // 10 XLM - Denomination::Xlm100 => 1_000_000_000, // 100 XLM - Denomination::Xlm1000 => 10_000_000_000, // 1000 XLM - Denomination::Usdc100 => 100_000_000, // 100 USDC (6 dec) - Denomination::Usdc1000 => 1_000_000_000, // 1000 USDC + Denomination::Ten => 100_000_000, // 10 XLM + Denomination::Hundred => 1_000_000_000, // 100 XLM + Denomination::Thousand => 10_000_000_000, // 1000 XLM + Denomination::TenThousand => 100_000_000_000, // 10000 XLM + } + } + + /// Convert to u32 for storage key + pub fn to_u32(&self) -> u32 { + match self { + Denomination::Ten => 10, + Denomination::Hundred => 100, + Denomination::Thousand => 1000, + Denomination::TenThousand => 10000, + } + } + + /// Convert from u32 + pub fn from_u32(value: u32) -> Option { + match value { + 10 => Some(Denomination::Ten), + 100 => Some(Denomination::Hundred), + 1000 => Some(Denomination::Thousand), + 10000 => Some(Denomination::TenThousand), + _ => None, } } } @@ -74,8 +94,6 @@ pub struct PoolConfig { pub admin: Address, /// Token contract address (XLM native or USDC) pub token: Address, - /// Fixed deposit denomination enforced by the pool - pub denomination: Denomination, /// Merkle tree depth (always 20) pub tree_depth: u32, /// Maximum number of historical roots to keep @@ -143,6 +161,8 @@ pub struct PublicInputs { pub relayer: BytesN<32>, /// Relayer fee (zero if none) pub fee: BytesN<32>, + /// Denomination identifier + pub denomination: BytesN<32>, } /// Groth16 proof — three elliptic curve points on BN254. diff --git a/contracts/privacy_pool/src/utils/validation.rs b/contracts/privacy_pool/src/utils/validation.rs index 4a36921..86ca398 100644 --- a/contracts/privacy_pool/src/utils/validation.rs +++ b/contracts/privacy_pool/src/utils/validation.rs @@ -39,9 +39,9 @@ pub fn require_non_zero_commitment(env: &Env, commitment: &BytesN<32>) -> Result } } -/// Validate that the root is in the historical root buffer. -pub fn require_known_root(env: &Env, root: &BytesN<32>) -> Result<(), Error> { - if !merkle::is_known_root(env, root) { +/// Validate that the root is in the historical root buffer for a denomination. +pub fn require_known_root_for_denomination(env: &Env, denom: u32, root: &BytesN<32>) -> Result<(), Error> { + if !merkle::is_known_root(env, denom, root) { Err(Error::UnknownRoot) } else { Ok(())