diff --git a/src/darkpool.cairo b/src/darkpool.cairo index 17f9482f..4c34e856 100644 --- a/src/darkpool.cairo +++ b/src/darkpool.cairo @@ -85,10 +85,10 @@ trait IDarkpool { valid_settle_statement: ValidSettleStatement, valid_settle_witness_commitments: Array, valid_settle_proof: Proof, - verification_job_ids: Array, + verification_job_id: felt252, ); fn poll_process_match( - ref self: TContractState, verification_job_ids: Array, + ref self: TContractState, verification_job_id: felt252, ) -> Option>; } @@ -113,11 +113,11 @@ mod Darkpool { scalar::Scalar, types::{Proof, CircuitParams}, IMultiVerifierLibraryDispatcher, IMultiVerifierDispatcherTrait }, - merkle::{poseidon::poseidon_hash, IMerkleLibraryDispatcher, IMerkleDispatcherTrait}, + merkle::{IMerkleLibraryDispatcher, IMerkleDispatcherTrait}, nullifier_set::{INullifierSetLibraryDispatcher, INullifierSetDispatcherTrait}, utils::{ serde::EcPointSerde, storage::StoreSerdeWrapper, - crypto::{append_statement_commitments, hash_statement, native_poseidon_hash_scalars} + crypto::{append_statement_commitments, hash_statement, compute_poseidon_with_flag} }, oz::erc20::{IERC20DispatcherTrait, IERC20Dispatcher}, }; @@ -300,39 +300,40 @@ mod Darkpool { // Add all of the circuits to the multi-verifier - if self.feature_flags.read().verifier { - let verifier = _get_verifier(@self); - let mut circuits = array![ - Circuit::ValidWalletCreate(()), - Circuit::ValidWalletUpdate(()), - Circuit::ValidCommitments(()), - Circuit::ValidReblind(()), - Circuit::ValidMatchMpc(()), - Circuit::ValidSettle(()), - ]; - - loop { - match circuits.pop_front() { - Option::Some(circuit) => { - verifier.add_circuit(circuit.into()); - }, - Option::None(()) => { - break; - } - }; + let verifier = _get_verifier(@self); + let mut circuits = array![ + Circuit::ValidWalletCreate(()), + Circuit::ValidWalletUpdate(()), + Circuit::ValidCommitments(()), + Circuit::ValidReblind(()), + Circuit::ValidMatchMpc(()), + Circuit::ValidSettle(()), + ]; + + loop { + match circuits.pop_front() { + Option::Some(circuit) => { + verifier.add_circuit(circuit.into()); + }, + Option::None(()) => { + break; + } }; - } + }; + + let feature_flags = self.feature_flags.read(); // Initialize the Merkle tree - _get_merkle_tree(@self) - .initialize(height, self.feature_flags.read().non_native_poseidon); + _get_merkle_tree(@self).initialize(height, feature_flags); + + // Set verification disabled flag in the verifier + _get_verifier(@self).set_feature_flags(feature_flags); } /// Adds a circuit to the verifier /// Parameters: /// - `circuit`: The circuit to add fn add_circuit(ref self: ContractState, circuit: Circuit) { - assert(self.feature_flags.read().verifier, 'verifier disabled'); ownable__assert_only_owner(@self); _get_verifier(@self).add_circuit(circuit.into()); } @@ -344,7 +345,6 @@ mod Darkpool { fn parameterize_circuit( ref self: ContractState, circuit: Circuit, circuit_params: CircuitParams ) { - assert(self.feature_flags.read().verifier, 'verifier disabled'); ownable__assert_only_owner(@self); // Parameterize the circuit _get_verifier(@self).parameterize_circuit(circuit.into(), circuit_params); @@ -405,7 +405,6 @@ mod Darkpool { /// - `verifier_class_hash`: The hash of the implementation class used for verifier operations /// - `circuit`: The circuit for which to upgrade the verifier fn upgrade_verifier(ref self: ContractState, verifier_class_hash: ClassHash) { - assert(self.feature_flags.read().verifier, 'verifier disabled'); ownable__assert_only_owner(@self); // Get existing class hash to emit event @@ -475,7 +474,6 @@ mod Darkpool { fn check_verification_job_status( self: @ContractState, verification_job_id: felt252 ) -> Option { - assert(self.feature_flags.read().verifier, 'verifier disabled'); _get_verifier(self).check_verification_job_status(verification_job_id) } @@ -498,21 +496,19 @@ mod Darkpool { proof: Proof, verification_job_id: felt252, ) { - if self.feature_flags.read().verifier { - let verifier = _get_verifier(@self); - - // Inject witness - append_statement_commitments(@statement, ref witness_commitments); - - // Queue verification - verifier - .queue_verification_job( - Circuit::ValidWalletCreate(()).into(), - proof, - witness_commitments, - verification_job_id - ); - } + let verifier = _get_verifier(@self); + + // Inject witness + append_statement_commitments(@statement, ref witness_commitments); + + // Queue verification + verifier + .queue_verification_job( + Circuit::ValidWalletCreate(()).into(), + proof, + witness_commitments, + verification_job_id + ); // Store callback elements let callback_elems = NewWalletCallbackElems { @@ -535,19 +531,15 @@ mod Darkpool { fn poll_new_wallet( ref self: ContractState, verification_job_id: felt252, ) -> Option> { - let verified = if self.feature_flags.read().verifier { - let verifier = _get_verifier(@self); + let verifier = _get_verifier(@self); - assert( - verifier.check_verification_job_status(verification_job_id).is_none(), - 'polling already complete' - ); + assert( + verifier.check_verification_job_status(verification_job_id).is_none(), + 'polling already complete' + ); - verifier - .step_verification(Circuit::ValidWalletCreate(()).into(), verification_job_id) - } else { - Option::Some(true) - }; + let verified = verifier + .step_verification(Circuit::ValidWalletCreate(()).into(), verification_job_id); match verified { Option::Some(success) => { @@ -563,15 +555,9 @@ mod Darkpool { hash_input.append(callback_elems.private_shares_commitment); hash_input.append_all(ref callback_elems.public_wallet_shares); - let total_shares_commitment = if self - .feature_flags - .read() - .non_native_poseidon { - *poseidon_hash(hash_input.span(), 1 // num_elements - )[0] - } else { - native_poseidon_hash_scalars(hash_input.span()) - }; + let total_shares_commitment = compute_poseidon_with_flag( + hash_input.span(), self.feature_flags.read().use_base_field_poseidon + ); let merkle_tree = _get_merkle_tree(@self); let new_root = merkle_tree.insert(total_shares_commitment); @@ -607,15 +593,6 @@ mod Darkpool { proof: Proof, verification_job_id: felt252, ) { - let verifier = _get_verifier(@self); - - if self.feature_flags.read().verifier { - // Inject witness - // Have to do this in a separate conditional block before any other - // member references on the statement due to compiler issues - append_statement_commitments(@statement, ref witness_commitments); - } - // Assert that the merkle root for which inclusion is proven in `VALID WALLET UPDATE` // is a valid historical root assert( @@ -641,16 +618,17 @@ mod Darkpool { // Mark the `old_shares_nullifier` as in use _get_nullifier_set(@self).mark_nullifier_in_use(statement.old_shares_nullifier); - if self.feature_flags.read().verifier { - // Queue verification - verifier - .queue_verification_job( - Circuit::ValidWalletUpdate(()).into(), - proof, - witness_commitments, - verification_job_id - ); - } + // Inject witness + append_statement_commitments(@statement, ref witness_commitments); + + // Queue verification + _get_verifier(@self) + .queue_verification_job( + Circuit::ValidWalletUpdate(()).into(), + proof, + witness_commitments, + verification_job_id + ); // Store callback elements let external_transfer = if statement.external_transfer == Default::default() { @@ -681,19 +659,15 @@ mod Darkpool { fn poll_update_wallet( ref self: ContractState, verification_job_id: felt252, ) -> Option> { - let verified = if self.feature_flags.read().verifier { - let verifier = _get_verifier(@self); + let verifier = _get_verifier(@self); - assert( - verifier.check_verification_job_status(verification_job_id).is_none(), - 'polling already complete' - ); + assert( + verifier.check_verification_job_status(verification_job_id).is_none(), + 'polling already complete' + ); - verifier - .step_verification(Circuit::ValidWalletUpdate(()).into(), verification_job_id) - } else { - Option::Some(true) - }; + let verified = verifier + .step_verification(Circuit::ValidWalletUpdate(()).into(), verification_job_id); match verified { Option::Some(success) => { @@ -711,15 +685,9 @@ mod Darkpool { hash_input.append(callback_elems.new_private_shares_commitment); hash_input.append_all(ref callback_elems.new_public_shares); - let total_shares_commitment = if self - .feature_flags - .read() - .non_native_poseidon { - *poseidon_hash(hash_input.span(), 1 // num_elements - )[0] - } else { - native_poseidon_hash_scalars(hash_input.span()) - }; + let total_shares_commitment = compute_poseidon_with_flag( + hash_input.span(), self.feature_flags.read().use_base_field_poseidon + ); let merkle_tree = _get_merkle_tree(@self); let new_root = merkle_tree.insert(total_shares_commitment); @@ -755,10 +723,12 @@ mod Darkpool { /// Parameters: /// - `party_0_payload`: The first party's match payload /// - `party_1_payload`: The second party's match payload - /// - `match_proof`: The proof of `VALID_MATCH_MPC` - /// - `match_witness_commitments`: The Pedersen commitments to the match proof witness elements - /// - `settle_proof`: The proof of `VALID_SETTLE` - /// - `settle_witness_commitments`: The Pedersen commitments to the settle proof witness elements + /// - `valid_match_witness_commitments`: The Pedersen commitments to the match proof witness elements + /// - `valid_match_proof`: The proof of `VALID_MATCH_MPC` + /// - `valid_settle_statement`: Public inputs to the `VALID_SETTLE` circuit + /// - `valid_settle_witness_commitments`: The Pedersen commitments to the settle proof witness elements + /// - `valid_settle_proof`: The proof of `VALID_SETTLE` + /// - `verification_job_id`: The ID of the verification job to enqueue fn process_match( ref self: ContractState, mut party_0_payload: MatchPayload, @@ -768,42 +738,8 @@ mod Darkpool { valid_settle_statement: ValidSettleStatement, mut valid_settle_witness_commitments: Array, valid_settle_proof: Proof, - verification_job_ids: Array, + verification_job_id: felt252, ) { - let verifier = _get_verifier(@self); - - if self.feature_flags.read().verifier { - // Inject witnesses - // Have to do this in a separate conditional block before any other - // member references on the statements due to compiler issues - - // Party 0 VALID COMMITMENTS - append_statement_commitments( - @party_0_payload.valid_commitments_statement, - ref party_0_payload.valid_commitments_witness_commitments - ); - // Party 0 VALID REBLIND - append_statement_commitments( - @party_0_payload.valid_reblind_statement, - ref party_0_payload.valid_reblind_witness_commitments - ); - // Party 1 VALID COMMITMENTS - append_statement_commitments( - @party_1_payload.valid_commitments_statement, - ref party_1_payload.valid_commitments_witness_commitments - ); - // Party 1 VALID REBLIND - append_statement_commitments( - @party_1_payload.valid_reblind_statement, - ref party_1_payload.valid_reblind_witness_commitments - ); - // No statement to inject into witness for VALID MATCH MPC - // VALID SETTLE - append_statement_commitments( - @valid_settle_statement, ref valid_settle_witness_commitments - ); - } - // Assert that the merkle roots for which inclusion is proven in `VALID REBLIND` // are valid historical roots let merkle_tree = _get_merkle_tree(@self); @@ -827,58 +763,84 @@ mod Darkpool { party_1_payload.valid_reblind_statement.original_shares_nullifier ); - // Queue verifications + // Inject witnesses & queue verifications + // TODO: This probably won't fit in a transaction... think about how to handle this - if self.feature_flags.read().verifier { - // Party 0 VALID COMMITMENTS - verifier - .queue_verification_job( - Circuit::ValidCommitments(()).into(), - party_0_payload.valid_commitments_proof, - party_0_payload.valid_commitments_witness_commitments, - *verification_job_ids[0] - ); - // Party 0 VALID REBLIND - verifier - .queue_verification_job( - Circuit::ValidReblind(()).into(), - party_0_payload.valid_reblind_proof, - party_0_payload.valid_reblind_witness_commitments, - *verification_job_ids[1] - ); - // Party 1 VALID COMMITMENTS - verifier - .queue_verification_job( - Circuit::ValidCommitments(()).into(), - party_1_payload.valid_commitments_proof, - party_1_payload.valid_commitments_witness_commitments, - *verification_job_ids[2] - ); - // Party 1 VALID REBLIND - verifier - .queue_verification_job( - Circuit::ValidReblind(()).into(), - party_1_payload.valid_reblind_proof, - party_1_payload.valid_reblind_witness_commitments, - *verification_job_ids[3] - ); - // VALID MATCH MPC - verifier - .queue_verification_job( - Circuit::ValidMatchMpc(()).into(), - valid_match_mpc_proof, - valid_match_mpc_witness_commitments, - *verification_job_ids[4] - ); - // VALID SETTLE - verifier - .queue_verification_job( - Circuit::ValidSettle(()).into(), - valid_settle_proof, - valid_settle_witness_commitments, - *verification_job_ids[5] - ); - } + let verifier = _get_verifier(@self); + + // Party 0 VALID COMMITMENTS + append_statement_commitments( + @party_0_payload.valid_commitments_statement, + ref party_0_payload.valid_commitments_witness_commitments + ); + verifier + .queue_verification_job( + Circuit::ValidCommitments(()).into(), + party_0_payload.valid_commitments_proof, + party_0_payload.valid_commitments_witness_commitments, + verification_job_id + ); + + // Party 0 VALID REBLIND + append_statement_commitments( + @party_0_payload.valid_reblind_statement, + ref party_0_payload.valid_reblind_witness_commitments + ); + verifier + .queue_verification_job( + Circuit::ValidReblind(()).into(), + party_0_payload.valid_reblind_proof, + party_0_payload.valid_reblind_witness_commitments, + verification_job_id + 1 + ); + + // Party 1 VALID COMMITMENTS + append_statement_commitments( + @party_1_payload.valid_commitments_statement, + ref party_1_payload.valid_commitments_witness_commitments + ); + verifier + .queue_verification_job( + Circuit::ValidCommitments(()).into(), + party_1_payload.valid_commitments_proof, + party_1_payload.valid_commitments_witness_commitments, + verification_job_id + 2 + ); + + // Party 1 VALID REBLIND + append_statement_commitments( + @party_1_payload.valid_reblind_statement, + ref party_1_payload.valid_reblind_witness_commitments + ); + verifier + .queue_verification_job( + Circuit::ValidReblind(()).into(), + party_1_payload.valid_reblind_proof, + party_1_payload.valid_reblind_witness_commitments, + verification_job_id + 3 + ); + + // VALID MATCH MPC + // No statement to inject into witness + verifier + .queue_verification_job( + Circuit::ValidMatchMpc(()).into(), + valid_match_mpc_proof, + valid_match_mpc_witness_commitments, + verification_job_id + 4 + ); + + // VALID SETTLE + append_statement_commitments( + @valid_settle_statement, ref valid_settle_witness_commitments + ); + verifier + .queue_verification_job( + Circuit::ValidSettle(()).into(), + valid_settle_proof, + valid_settle_witness_commitments, + verification_job_id + 5 + ); // Store callback elements let callback_elems = ProcessMatchCallbackElems { @@ -902,9 +864,7 @@ mod Darkpool { }; self .process_match_callback_elems - .write( // Use the first verification job id as the mapping key for the callback elements - *verification_job_ids[0], StoreSerdeWrapper { inner: callback_elems } - ); + .write(verification_job_id, StoreSerdeWrapper { inner: callback_elems }); } /// Poll the process match verification job, and if it verifies, insert the updated wallet into the @@ -914,161 +874,11 @@ mod Darkpool { /// Returns: /// - The root of the tree after the new commitment is inserted, if the proof verifies fn poll_process_match( - ref self: ContractState, verification_job_ids: Array, + ref self: ContractState, verification_job_id: felt252, ) -> Option> { - let all_verified = if self.feature_flags.read().verifier { - let verifier = _get_verifier(@self); - - let circuits = array![ - Circuit::ValidCommitments(()), - Circuit::ValidReblind(()), - Circuit::ValidCommitments(()), - Circuit::ValidReblind(()), - Circuit::ValidMatchMpc(()), - Circuit::ValidSettle(()) - ] - .span(); - - let verification_job_ids = verification_job_ids.span(); - - assert( - circuits.len() == verification_job_ids.len(), 'wrong # of verification job ids' - ); - - // Assert that no verification jobs have failed, and that not all are complete - let mut i = 0; - let mut should_poll = false; - loop { - if i == verification_job_ids.len() { - break; - } + let poll_result = _check_and_poll_process_match(@self, verification_job_id); - let verified = verifier.check_verification_job_status(*verification_job_ids[i]); - - match verified { - Option::Some(success) => { - if !success { - break; - } - }, - Option::None(()) => { - should_poll = true; - break; - }, - }; - - i += 1; - }; - assert(should_poll, 'polling already complete'); - - let mut all_verified = Option::None(()); - loop { - if i == verification_job_ids.len() { - all_verified = Option::Some(true); - break; - }; - - let verified = verifier - .step_verification((*circuits[i]).into(), *verification_job_ids[i]); - - match verified { - Option::Some(success) => { - if !success { - all_verified = Option::Some(false); - break; - }; - - i += 1; - }, - Option::None(()) => { - break; - }, - }; - }; - - all_verified - } else { - Option::Some(true) - }; - - match all_verified { - Option::Some(success) => { - let nullifier_set = _get_nullifier_set(@self); - let mut callback_elems = self - .process_match_callback_elems - .read(*verification_job_ids[0]) - .inner; - - if success { - // Callback logic - - // Insert both parties' old shares nullifiers to the spent nullifier set - nullifier_set - .mark_nullifier_spent(callback_elems.party_0_original_shares_nullifier); - nullifier_set - .mark_nullifier_spent(callback_elems.party_1_original_shares_nullifier); - - // Insert both partes' updated wallet commitments to the merkle tree - let mut party_0_hash_input = ArrayTrait::new(); - party_0_hash_input - .append(callback_elems.party_0_reblinded_private_shares_commitment); - party_0_hash_input.append_all(ref callback_elems.party_0_modified_shares); - - let mut party_1_hash_input = ArrayTrait::new(); - party_1_hash_input - .append(callback_elems.party_1_reblinded_private_shares_commitment); - party_1_hash_input.append_all(ref callback_elems.party_1_modified_shares); - - let (party_0_total_shares_commitment, party_1_total_shares_commitment) = - if self - .feature_flags - .read() - .non_native_poseidon { - ( - *poseidon_hash(party_0_hash_input.span(), 1 // num_elements - )[0], - *poseidon_hash(party_1_hash_input.span(), 1 // num_elements - )[0], - ) - } else { - ( - native_poseidon_hash_scalars(party_0_hash_input.span()), - native_poseidon_hash_scalars(party_1_hash_input.span()) - ) - }; - - let merkle_tree = _get_merkle_tree(@self); - merkle_tree.insert(party_0_total_shares_commitment); - let new_root = merkle_tree.insert(party_1_total_shares_commitment); - - // Mark wallet as updated - _mark_wallet_updated( - ref self, - callback_elems.party_0_wallet_blinder_share, - callback_elems.tx_hash - ); - _mark_wallet_updated( - ref self, - callback_elems.party_1_wallet_blinder_share, - callback_elems.tx_hash - ); - - Option::Some(Result::Ok(new_root)) - } else { - // Verification failed - nullifier_set - .mark_nullifier_unused( - callback_elems.party_0_original_shares_nullifier - ); - nullifier_set - .mark_nullifier_unused( - callback_elems.party_1_original_shares_nullifier - ); - Option::Some(Result::Err('verification failed')) - } - }, - Option::None(()) => Option::None(()), - } + _handle_process_match_poll_result(ref self, verification_job_id, poll_result) } } @@ -1176,6 +986,144 @@ mod Darkpool { }; } + /// Asserts that verification of the `process_match` proofs associated with the given + /// `verification_job_id` has not already completed, and if not, polls the verification jobs + fn _check_and_poll_process_match( + self: @ContractState, verification_job_id: felt252 + ) -> Option { + let verifier = _get_verifier(self); + + let circuits = array![ + Circuit::ValidCommitments(()), + Circuit::ValidReblind(()), + Circuit::ValidCommitments(()), + Circuit::ValidReblind(()), + Circuit::ValidMatchMpc(()), + Circuit::ValidSettle(()) + ]; + + let mut already_completed = false; + let mut poll_result = Option::None(()); + let mut i = 0; + loop { + if i == circuits.len() { + // We only make it to this branch if the last verification job had just polled successfully + poll_result = Option::Some(true); + break; + } + + let already_verified = verifier + .check_verification_job_status(verification_job_id + i.into()); + + match already_verified { + Option::Some(success) => { + // If any of the verification jobs failed, or if the last verification + // job has already (successfully) completed, the polling must have previously + // completed. + if !success || i == circuits.len() - 1 { + already_completed = true; + break; + } + // Continue to next iteration if verification job had previously succeeded + }, + Option::None(()) => { + let verified = verifier + .step_verification((*circuits[i]).into(), verification_job_id + i.into()); + + match verified { + Option::Some(success) => { + if !success { + poll_result = Option::Some(false); + break; + } + // Continue to next iteration if verification job just succeeded. + // This means that we'll do another `step_verification` call in this + // transaction, which may exceed the gas limit... + }, + Option::None(()) => { + break; + }, + }; + }, + }; + + i += 1; + }; + + assert(!already_completed, 'polling already complete'); + + poll_result + } + + fn _handle_process_match_poll_result( + ref self: ContractState, verification_job_id: felt252, poll_result: Option + ) -> Option> { + match poll_result { + Option::Some(success) => { + let nullifier_set = _get_nullifier_set(@self); + let mut callback_elems = self + .process_match_callback_elems + .read(verification_job_id) + .inner; + + if success { + // Callback logic + + // Insert both parties' old shares nullifiers to the spent nullifier set + nullifier_set + .mark_nullifier_spent(callback_elems.party_0_original_shares_nullifier); + nullifier_set + .mark_nullifier_spent(callback_elems.party_1_original_shares_nullifier); + + // Insert both partes' updated wallet commitments to the merkle tree + let use_base_field_poseidon = self.feature_flags.read().use_base_field_poseidon; + + let mut party_0_hash_input = ArrayTrait::new(); + party_0_hash_input + .append(callback_elems.party_0_reblinded_private_shares_commitment); + party_0_hash_input.append_all(ref callback_elems.party_0_modified_shares); + let party_0_total_shares_commitment = compute_poseidon_with_flag( + party_0_hash_input.span(), use_base_field_poseidon + ); + + let mut party_1_hash_input = ArrayTrait::new(); + party_1_hash_input + .append(callback_elems.party_1_reblinded_private_shares_commitment); + party_1_hash_input.append_all(ref callback_elems.party_1_modified_shares); + let party_1_total_shares_commitment = compute_poseidon_with_flag( + party_1_hash_input.span(), use_base_field_poseidon + ); + + let merkle_tree = _get_merkle_tree(@self); + merkle_tree.insert(party_0_total_shares_commitment); + let new_root = merkle_tree.insert(party_1_total_shares_commitment); + + // Mark wallet as updated + _mark_wallet_updated( + ref self, + callback_elems.party_0_wallet_blinder_share, + callback_elems.tx_hash + ); + _mark_wallet_updated( + ref self, + callback_elems.party_1_wallet_blinder_share, + callback_elems.tx_hash + ); + + Option::Some(Result::Ok(new_root)) + } else { + // Verification failed + nullifier_set + .mark_nullifier_unused(callback_elems.party_0_original_shares_nullifier); + nullifier_set + .mark_nullifier_unused(callback_elems.party_1_original_shares_nullifier); + Option::Some(Result::Err('verification failed')) + } + }, + Option::None(()) => Option::None(()), + } + } + // ----------- // | OWNABLE | // ----------- diff --git a/src/darkpool/types.cairo b/src/darkpool/types.cairo index 9d6c6524..01ee04e0 100644 --- a/src/darkpool/types.cairo +++ b/src/darkpool/types.cairo @@ -96,10 +96,10 @@ struct Signature { /// Represents which optional features to enable in the darkpool #[derive(Drop, Serde, Copy, starknet::Store)] struct FeatureFlags { - /// Whether or not to use Poseidon over the scalar field - non_native_poseidon: bool, + /// Whether or not to use Poseidon over the base field + use_base_field_poseidon: bool, /// Whether or not to verify proofs - verifier: bool, + disable_verification: bool, } // -------------------------- diff --git a/src/merkle.cairo b/src/merkle.cairo index a5adba45..2f091a2d 100644 --- a/src/merkle.cairo +++ b/src/merkle.cairo @@ -1,10 +1,10 @@ mod poseidon; -use renegade_contracts::verifier::scalar::Scalar; +use renegade_contracts::{verifier::scalar::Scalar, darkpool::types::FeatureFlags}; #[starknet::interface] trait IMerkle { - fn initialize(ref self: TContractState, height: u8, non_native_poseidon: bool); + fn initialize(ref self: TContractState, height: u8, feature_flags: FeatureFlags); fn get_root(self: @TContractState) -> Scalar; fn root_in_history(self: @TContractState, root: Scalar) -> bool; fn insert(ref self: TContractState, value: Scalar) -> Scalar; @@ -20,9 +20,9 @@ mod Merkle { use alexandria_math::fast_power::fast_power; use renegade_contracts::{ - utils::{constants::MAX_U128, crypto::native_poseidon_hash_scalars}, verifier::scalar::Scalar + utils::{constants::MAX_U128, crypto::compute_poseidon_with_flag}, verifier::scalar::Scalar, + darkpool::types::FeatureFlags, }; - use super::poseidon::poseidon_hash; // ------------- // | CONSTANTS | @@ -41,6 +41,8 @@ mod Merkle { #[storage] struct Storage { + /// Feature flag settings + feature_flags: FeatureFlags, /// The height of the Merkle tree stored by this contract height: u8, /// Capacity of the Merkle tree, cached in contract storage @@ -63,8 +65,6 @@ mod Merkle { /// at the given height. Used to set the sibling pathway when a subtree is /// filled. zeros: LegacyMap, - /// Indicates whether or not to use Poseidon over the scalar field - non_native_poseidon: bool, } // ---------- @@ -107,8 +107,8 @@ mod Merkle { /// Set up the Merkle tree /// Parameters: /// - `height`: The height of the Merkle tree - fn initialize(ref self: ContractState, height: u8, non_native_poseidon: bool) { - self.non_native_poseidon.write(non_native_poseidon); + fn initialize(ref self: ContractState, height: u8, feature_flags: FeatureFlags) { + self.feature_flags.write(feature_flags); self.height.write(height); // Calculate the capacity @@ -196,12 +196,9 @@ mod Merkle { hash_input.append(current_leaf); hash_input.append(current_leaf); - let next_leaf = if self.non_native_poseidon.read() { - *poseidon_hash(hash_input.span(), 1, // num_elements - )[0] - } else { - native_poseidon_hash_scalars(hash_input.span()) - }; + let next_leaf = compute_poseidon_with_flag( + hash_input.span(), self.feature_flags.read().use_base_field_poseidon + ); setup_empty_tree(ref self, height - 1, next_leaf) } @@ -277,12 +274,9 @@ mod Merkle { new_subtree_filled = subtree_filled; } next_value = - if self.non_native_poseidon.read() { - *poseidon_hash(hash_input.span(), 1, // num_elements - )[0] - } else { - native_poseidon_hash_scalars(hash_input.span()) - }; + compute_poseidon_with_flag( + hash_input.span(), self.feature_flags.read().use_base_field_poseidon + ); // Emit an event indicating that the internal node has changed self diff --git a/src/testing/tests/darkpool_tests.cairo b/src/testing/tests/darkpool_tests.cairo index c6c52fb9..e7c02acc 100644 --- a/src/testing/tests/darkpool_tests.cairo +++ b/src/testing/tests/darkpool_tests.cairo @@ -44,7 +44,7 @@ const DUMMY_CALLER: felt252 = 'DUMMY_CALLER'; fn test_upgrade_darkpool() { let test_caller = contract_address_try_from_felt252(TEST_CALLER).unwrap(); set_contract_address(test_caller); - let mut darkpool = setup_darkpool(FeatureFlags { verifier: false, non_native_poseidon: false }); + let mut darkpool = setup_darkpool(); darkpool.upgrade(DummyUpgradeTarget::TEST_CLASS_HASH.try_into().unwrap()); // The dummy upgrade target has a hardcoded response for the `get_wallet_blinder_transaction` @@ -63,7 +63,7 @@ fn test_upgrade_darkpool() { fn test_upgrade_merkle() { let test_caller = contract_address_try_from_felt252(TEST_CALLER).unwrap(); set_contract_address(test_caller); - let mut darkpool = setup_darkpool(FeatureFlags { verifier: false, non_native_poseidon: false }); + let mut darkpool = setup_darkpool(); let original_root = darkpool.get_root(); @@ -79,7 +79,7 @@ fn test_upgrade_merkle() { fn test_upgrade_nullifier_set() { let test_caller = contract_address_try_from_felt252(TEST_CALLER).unwrap(); set_contract_address(test_caller); - let mut darkpool = setup_darkpool(FeatureFlags { verifier: false, non_native_poseidon: false }); + let mut darkpool = setup_darkpool(); darkpool.upgrade_nullifier_set(DummyUpgradeTarget::TEST_CLASS_HASH.try_into().unwrap()); assert(!darkpool.is_nullifier_available(0.into()), 'upgrade target wrong result'); @@ -96,7 +96,9 @@ fn test_upgrade_nullifier_set() { fn test_upgrade_verifier() { let test_caller = contract_address_try_from_felt252(TEST_CALLER).unwrap(); set_contract_address(test_caller); - let mut darkpool = setup_darkpool(FeatureFlags { verifier: true, non_native_poseidon: false }); + let mut darkpool = setup_darkpool_with_flags( + FeatureFlags { use_base_field_poseidon: true, disable_verification: false } + ); darkpool.upgrade_verifier(DummyUpgradeTarget::TEST_CLASS_HASH.try_into().unwrap()); assert( @@ -119,7 +121,7 @@ fn test_upgrade_verifier() { fn test_transfer_ownership() { let test_caller = contract_address_try_from_felt252(TEST_CALLER).unwrap(); set_contract_address(test_caller); - let mut darkpool = setup_darkpool(FeatureFlags { verifier: false, non_native_poseidon: false }); + let mut darkpool = setup_darkpool(); let dummy_caller = contract_address_try_from_felt252(DUMMY_CALLER).unwrap(); darkpool.transfer_ownership(dummy_caller); @@ -137,7 +139,7 @@ fn test_transfer_ownership() { fn test_initialize_access() { let dummy_caller = contract_address_try_from_felt252(DUMMY_CALLER).unwrap(); set_contract_address(dummy_caller); - setup_darkpool(FeatureFlags { verifier: false, non_native_poseidon: false }); + setup_darkpool(); } #[test] @@ -146,7 +148,7 @@ fn test_initialize_access() { fn test_upgrade_darkpool_access() { let test_caller = contract_address_try_from_felt252(TEST_CALLER).unwrap(); set_contract_address(test_caller); - let mut darkpool = setup_darkpool(FeatureFlags { verifier: false, non_native_poseidon: false }); + let mut darkpool = setup_darkpool(); let dummy_caller = contract_address_try_from_felt252(DUMMY_CALLER).unwrap(); set_contract_address(dummy_caller); @@ -160,7 +162,7 @@ fn test_upgrade_darkpool_access() { fn test_upgrade_merkle_access() { let test_caller = contract_address_try_from_felt252(TEST_CALLER).unwrap(); set_contract_address(test_caller); - let mut darkpool = setup_darkpool(FeatureFlags { verifier: false, non_native_poseidon: false }); + let mut darkpool = setup_darkpool(); let dummy_caller = contract_address_try_from_felt252(DUMMY_CALLER).unwrap(); set_contract_address(dummy_caller); @@ -174,7 +176,7 @@ fn test_upgrade_merkle_access() { fn test_upgrade_nullifier_set_access() { let test_caller = contract_address_try_from_felt252(TEST_CALLER).unwrap(); set_contract_address(test_caller); - let mut darkpool = setup_darkpool(FeatureFlags { verifier: false, non_native_poseidon: false }); + let mut darkpool = setup_darkpool(); let dummy_caller = contract_address_try_from_felt252(DUMMY_CALLER).unwrap(); set_contract_address(dummy_caller); @@ -188,7 +190,9 @@ fn test_upgrade_nullifier_set_access() { fn test_upgrade_verifier_access() { let test_caller = contract_address_try_from_felt252(TEST_CALLER).unwrap(); set_contract_address(test_caller); - let mut darkpool = setup_darkpool(FeatureFlags { verifier: true, non_native_poseidon: false }); + let mut darkpool = setup_darkpool_with_flags( + FeatureFlags { use_base_field_poseidon: true, disable_verification: false } + ); let dummy_caller = contract_address_try_from_felt252(DUMMY_CALLER).unwrap(); set_contract_address(dummy_caller); @@ -202,7 +206,7 @@ fn test_upgrade_verifier_access() { fn test_transfer_ownership_access() { let test_caller = contract_address_try_from_felt252(TEST_CALLER).unwrap(); set_contract_address(test_caller); - let mut darkpool = setup_darkpool(FeatureFlags { verifier: false, non_native_poseidon: false }); + let mut darkpool = setup_darkpool(); let dummy_caller = contract_address_try_from_felt252(DUMMY_CALLER).unwrap(); set_contract_address(dummy_caller); @@ -223,7 +227,7 @@ fn test_initialize_twice() { let mut calldata = ArrayTrait::new(); calldata.append(TEST_CALLER); Serde::::serialize( - @FeatureFlags { verifier: false, non_native_poseidon: false }, ref calldata + @FeatureFlags { use_base_field_poseidon: false, disable_verification: false }, ref calldata ); let (darkpool_address, _) = deploy_syscall( @@ -233,15 +237,23 @@ fn test_initialize_twice() { let mut darkpool = IDarkpoolDispatcher { contract_address: darkpool_address }; - initialize_darkpool(ref darkpool, false); - initialize_darkpool(ref darkpool, false); + initialize_darkpool(ref darkpool); + initialize_darkpool(ref darkpool); } // ----------- // | HELPERS | // ----------- -fn setup_darkpool(feature_flags: FeatureFlags) -> IDarkpoolDispatcher { +fn setup_darkpool() -> IDarkpoolDispatcher { + // Default feature flags used disable the scalar field poseidon hash and the verifier, as these + // are generally not what is being tested here and disabling them speeds up tests. + setup_darkpool_with_flags( + FeatureFlags { use_base_field_poseidon: true, disable_verification: true } + ) +} + +fn setup_darkpool_with_flags(feature_flags: FeatureFlags) -> IDarkpoolDispatcher { let mut calldata = ArrayTrait::new(); calldata.append(TEST_CALLER); Serde::::serialize(@feature_flags, ref calldata); @@ -252,12 +264,12 @@ fn setup_darkpool(feature_flags: FeatureFlags) -> IDarkpoolDispatcher { .unwrap(); let mut darkpool = IDarkpoolDispatcher { contract_address: darkpool_address }; - initialize_darkpool(ref darkpool, feature_flags.verifier); + initialize_darkpool(ref darkpool); darkpool } -fn initialize_darkpool(ref darkpool: IDarkpoolDispatcher, initialize_verifier: bool) { +fn initialize_darkpool(ref darkpool: IDarkpoolDispatcher) { darkpool .initialize( Merkle::TEST_CLASS_HASH.try_into().unwrap(), @@ -266,14 +278,12 @@ fn initialize_darkpool(ref darkpool: IDarkpoolDispatcher, initialize_verifier: b TEST_MERKLE_HEIGHT, ); - if initialize_verifier { - darkpool.parameterize_circuit(Circuit::ValidWalletCreate(()), get_dummy_circuit_params()); - darkpool.parameterize_circuit(Circuit::ValidWalletUpdate(()), get_dummy_circuit_params()); - darkpool.parameterize_circuit(Circuit::ValidCommitments(()), get_dummy_circuit_params()); - darkpool.parameterize_circuit(Circuit::ValidReblind(()), get_dummy_circuit_params()); - darkpool.parameterize_circuit(Circuit::ValidMatchMpc(()), get_dummy_circuit_params()); - darkpool.parameterize_circuit(Circuit::ValidSettle(()), get_dummy_circuit_params()); - } + darkpool.parameterize_circuit(Circuit::ValidWalletCreate(()), get_dummy_circuit_params()); + darkpool.parameterize_circuit(Circuit::ValidWalletUpdate(()), get_dummy_circuit_params()); + darkpool.parameterize_circuit(Circuit::ValidCommitments(()), get_dummy_circuit_params()); + darkpool.parameterize_circuit(Circuit::ValidReblind(()), get_dummy_circuit_params()); + darkpool.parameterize_circuit(Circuit::ValidMatchMpc(()), get_dummy_circuit_params()); + darkpool.parameterize_circuit(Circuit::ValidSettle(()), get_dummy_circuit_params()); } fn assert_not_verified(ref darkpool: IDarkpoolDispatcher, verification_job_id: felt252) { diff --git a/src/utils/crypto.cairo b/src/utils/crypto.cairo index 71a61c2a..d11dbca9 100644 --- a/src/utils/crypto.cairo +++ b/src/utils/crypto.cairo @@ -7,7 +7,9 @@ use alexandria_data_structures::array_ext::ArrayTraitExt; use starknet::{syscalls::keccak_syscall, SyscallResultTrait}; use poseidon::poseidon_hash_span; -use renegade_contracts::verifier::scalar::{Scalar, ScalarSerializable}; +use renegade_contracts::{ + verifier::scalar::{Scalar, ScalarSerializable}, merkle::poseidon::poseidon_hash +}; use super::constants::{BASE_FIELD_ORDER, SHIFT_256_FELT, SHIFT_256_SCALAR}; @@ -107,7 +109,7 @@ fn hash_statement>(statement: keccak_syscall(keccak_input.span()).unwrap_syscall().into() } -fn native_poseidon_hash_scalars(mut scalars: Span) -> Scalar { +fn base_field_poseidon_hash_scalars(mut scalars: Span) -> Scalar { let mut hash_input_felt = ArrayTrait::new(); loop { match scalars.pop_front() { @@ -122,3 +124,12 @@ fn native_poseidon_hash_scalars(mut scalars: Span) -> Scalar { poseidon_hash_span(hash_input_felt.span()).into() } + +fn compute_poseidon_with_flag(mut scalars: Span, use_base_field_poseidon: bool) -> Scalar { + if use_base_field_poseidon { + base_field_poseidon_hash_scalars(scalars) + } else { + *poseidon_hash(scalars, 1 // num_elements + )[0] + } +} diff --git a/src/verifier.cairo b/src/verifier.cairo index e0534ad1..699adf37 100644 --- a/src/verifier.cairo +++ b/src/verifier.cairo @@ -7,12 +7,13 @@ mod utils; // ------------- // TODO: Move to separate file / module when extensibility pattern is stabilized -use renegade_contracts::utils::serde::EcPointSerde; +use renegade_contracts::{utils::serde::EcPointSerde, darkpool::types::FeatureFlags}; use types::{CircuitParams, Proof, VerificationJob}; #[starknet::interface] trait IMultiVerifier { + fn set_feature_flags(ref self: TContractState, feature_flags: FeatureFlags); fn add_circuit(ref self: TContractState, circuit_id: felt252); fn parameterize_circuit( ref self: TContractState, circuit_id: felt252, circuit_params: CircuitParams @@ -45,9 +46,12 @@ mod MultiVerifier { use alexandria_data_structures::array_ext::ArrayTraitExt; use alexandria_math::fast_power::fast_power; - use renegade_contracts::utils::{ - math::get_consecutive_powers, storage::StoreSerdeWrapper, eq::EcPointPartialEq, - serde::EcPointSerde, constants::{MAX_USIZE, G_LABEL, H_LABEL} + use renegade_contracts::{ + utils::{ + math::get_consecutive_powers, storage::StoreSerdeWrapper, eq::EcPointPartialEq, + serde::EcPointSerde, constants::{MAX_USIZE, G_LABEL, H_LABEL} + }, + darkpool::types::FeatureFlags }; use super::{ @@ -75,6 +79,8 @@ mod MultiVerifier { #[storage] struct Storage { + /// Feature flag settings + feature_flags: FeatureFlags, /// Map of in-use circuit IDs circuit_id_in_use: LegacyMap, /// Mapping from verification job ID -> verification job @@ -146,6 +152,15 @@ mod MultiVerifier { #[external(v0)] impl IMultiVerifierImpl of super::IMultiVerifier { + /// Controls whether or not full verification is enabled. If disabled, all + /// smart contract logic remains the same, other than `step_verification`, which + /// will immediately mark the verification job as verified. + /// Parameters: + /// - `disabled`: Whether or not verification should be enabled + fn set_feature_flags(ref self: ContractState, feature_flags: FeatureFlags) { + self.feature_flags.write(feature_flags); + } + /// Adds a new circuit to the contract /// Parameters: /// - `circuit_id`: The ID of the circuit @@ -157,7 +172,7 @@ mod MultiVerifier { self.emit(Event::CircuitAdded(CircuitAdded { circuit_id })); } - /// Initializes the verifier for the given public parameters + /// Parameterizes the verifier with the given public parameters /// Parameters: /// - `circuit_id`: The ID of the circuit /// - `circuit_params`: The public parameters of the circuit @@ -304,7 +319,12 @@ mod MultiVerifier { ref self: ContractState, circuit_id: felt252, verification_job_id: felt252 ) -> Option { let mut verification_job = self.verification_queue.read(verification_job_id).inner; - step_verification_inner(ref self, ref verification_job); + + if !self.feature_flags.read().disable_verification { + step_verification_inner(ref self, ref verification_job); + } else { + verification_job.verified = Option::Some(true); + } let verified = verification_job.verified;