diff --git a/taiga_halo2/examples/tx_examples/partial_fulfillment_token_swap.rs b/taiga_halo2/examples/tx_examples/partial_fulfillment_token_swap.rs index 1fd1c42a..0bfbc74e 100644 --- a/taiga_halo2/examples/tx_examples/partial_fulfillment_token_swap.rs +++ b/taiga_halo2/examples/tx_examples/partial_fulfillment_token_swap.rs @@ -10,16 +10,14 @@ use pasta_curves::{group::Curve, pallas}; use rand::{CryptoRng, RngCore}; use taiga_halo2::{ circuit::vp_examples::{ - partial_fulfillment_intent::{ - create_intent_note, PartialFulfillmentIntentValidityPredicateCircuit, - }, + partial_fulfillment_intent::{PartialFulfillmentIntentValidityPredicateCircuit, Swap}, signature_verification::COMPRESSED_TOKEN_AUTH_VK, - token::{Token, TokenAuthorization}, + token::{Token, TokenAuthorization, TokenNote}, }, constant::TAIGA_COMMITMENT_TREE_DEPTH, merkle_tree::{Anchor, MerklePath}, note::{InputNoteProvingInfo, Note, OutputNoteProvingInfo}, - nullifier::{Nullifier, NullifierKeyContainer}, + nullifier::NullifierKeyContainer, shielded_ptx::ShieldedPartialTransaction, transaction::{ShieldedPartialTxBundle, Transaction, TransparentPartialTxBundle}, }; @@ -29,39 +27,17 @@ pub fn create_token_intent_ptx( sell: Token, buy: Token, input_auth_sk: pallas::Scalar, - input_nk: NullifierKeyContainer, // NullifierKeyContainer::Key -) -> ( - ShieldedPartialTransaction, - NullifierKeyContainer, - pallas::Base, - pallas::Base, - Nullifier, -) { +) -> (ShieldedPartialTransaction, Swap, Note) { let input_auth = TokenAuthorization::from_sk_vk(&input_auth_sk, &COMPRESSED_TOKEN_AUTH_VK); - - // input note - let rho = Nullifier::from(pallas::Base::random(&mut rng)); - let input_note = sell.create_random_token_note(&mut rng, rho, input_nk, &input_auth); - - // output intent note - let input_note_nk_com = input_note.get_nk_commitment(); - let input_note_nf = input_note.get_nf().unwrap(); - let intent_note = create_intent_note( - &mut rng, - &sell, - &buy, - input_note_nk_com, - input_note.app_data_dynamic, - input_note_nf, - input_nk, - ); + let swap = Swap::random(&mut rng, sell, buy, input_auth); + let intent_note = swap.create_intent_note(&mut rng); // padding the zero notes let padding_input_note = Note::random_padding_input_note(&mut rng); let padding_input_note_nf = padding_input_note.get_nf().unwrap(); let padding_output_note = Note::random_padding_output_note(&mut rng, padding_input_note_nf); - let input_notes = [*input_note.note(), padding_input_note]; + let input_notes = [*swap.sell.note(), padding_input_note]; let output_notes = [intent_note, padding_output_note]; let merkle_path = MerklePath::random(&mut rng, TAIGA_COMMITMENT_TREE_DEPTH); @@ -70,7 +46,7 @@ pub fn create_token_intent_ptx( let anchor = Anchor::from(pallas::Base::random(&mut rng)); // Create the input note proving info - let input_note_proving_info = input_note.generate_input_token_note_proving_info( + let input_note_proving_info = swap.sell.generate_input_token_note_proving_info( &mut rng, input_auth, input_auth_sk, @@ -85,10 +61,7 @@ pub fn create_token_intent_ptx( owned_note_pub_id: intent_note.commitment().inner(), input_notes, output_notes, - sell: sell.clone(), - buy, - receiver_nk_com: input_note_nk_com, - receiver_app_data_dynamic: input_note.app_data_dynamic, + swap: swap.clone(), }; OutputNoteProvingInfo::new(intent_note, Box::new(intent_vp), vec![]) @@ -118,61 +91,23 @@ pub fn create_token_intent_ptx( &mut rng, ); - ( - ptx, - input_nk, - input_note_nk_com, - input_note.app_data_dynamic, - rho, - ) + (ptx, swap, intent_note) } #[allow(clippy::too_many_arguments)] pub fn consume_token_intent_ptx( mut rng: R, - sell: Token, - buy: Token, - bought_note_value: u64, - returned_note_value: u64, - input_rho: Nullifier, - input_nk: NullifierKeyContainer, // NullifierKeyContainer::Key - receiver_nk_com: pallas::Base, - receiver_app_data_dynamic: pallas::Base, + swap: Swap, + intent_note: Note, + offer: Token, output_auth_pk: pallas::Point, ) -> ShieldedPartialTransaction { - // input intent note - let intent_note = create_intent_note( - &mut rng, - &sell, - &buy, - receiver_nk_com, - receiver_app_data_dynamic, - input_rho, - input_nk, - ); + let (input_notes, output_notes) = swap.fill(&mut rng, intent_note, offer); + let [intent_note, padding_input_note] = input_notes; + let [bought_note, returned_note] = output_notes; // output notes - let input_note_nf = intent_note.get_nf().unwrap(); let output_auth = TokenAuthorization::new(output_auth_pk, *COMPRESSED_TOKEN_AUTH_VK); - let bought_token = Token::new(buy.name().inner(), bought_note_value); - let bought_note = - bought_token.create_random_token_note(&mut rng, input_note_nf, input_nk, &output_auth); - - // padding the zero note - let padding_input_note = Note::random_padding_input_note(&mut rng); - let padding_input_note_nf = padding_input_note.get_nf().unwrap(); - let returned_token = Token::new(sell.name().inner(), returned_note_value); - let returned_note = returned_token.create_random_token_note( - &mut rng, - padding_input_note_nf, - input_nk, - &output_auth, - ); - // let padding_output_note = Note::random_padding_input_note(&mut rng, padding_input_note_nf); - - let input_notes = [intent_note, padding_input_note]; - let output_notes = [*bought_note.note(), *returned_note.note()]; - let merkle_path = MerklePath::random(&mut rng, TAIGA_COMMITMENT_TREE_DEPTH); // Fetch a valid anchor for dummy notes @@ -181,13 +116,10 @@ pub fn consume_token_intent_ptx( // Create the intent note proving info let intent_note_proving_info = { let intent_vp = PartialFulfillmentIntentValidityPredicateCircuit { - owned_note_pub_id: input_note_nf.inner(), + owned_note_pub_id: intent_note.get_nf().unwrap().inner(), input_notes, output_notes, - sell: sell.clone(), - buy: buy.clone(), - receiver_nk_com, - receiver_app_data_dynamic, + swap: swap.clone(), }; InputNoteProvingInfo::new( @@ -200,12 +132,11 @@ pub fn consume_token_intent_ptx( }; // Create the output note proving info - let bought_note_proving_info = bought_note.generate_output_token_note_proving_info( - &mut rng, - output_auth, - input_notes, - output_notes, - ); + let bought_note_proving_info = TokenNote { + token_name: swap.buy.name().clone(), + note: bought_note, + } + .generate_output_token_note_proving_info(&mut rng, output_auth, input_notes, output_notes); // Create the padding input note proving info let padding_input_note_proving_info = InputNoteProvingInfo::create_padding_note_proving_info( @@ -217,12 +148,11 @@ pub fn consume_token_intent_ptx( ); // Create the returned note proving info - let returned_note_proving_info = returned_note.generate_output_token_note_proving_info( - &mut rng, - output_auth, - input_notes, - output_notes, - ); + let returned_note_proving_info = TokenNote { + token_name: swap.sell.token_name().clone(), + note: returned_note, + } + .generate_output_token_note_proving_info(&mut rng, output_auth, input_notes, output_notes); // Create shielded partial tx ShieldedPartialTransaction::build( @@ -236,46 +166,36 @@ pub fn consume_token_intent_ptx( pub fn create_token_swap_transaction(mut rng: R) -> Transaction { let generator = pallas::Point::generator().to_affine(); - // Alice creates the partial transaction with 5 BTC input and intent output + // Alice creates the partial transaction with: + // - 2 BTC sell + // - intent output encoding 10 ETH ask let alice_auth_sk = pallas::Scalar::random(&mut rng); let alice_auth_pk = generator * alice_auth_sk; - let alice_nk = NullifierKeyContainer::random_key(&mut rng); let sell = Token::new("btc".to_string(), 2u64); let buy = Token::new("eth".to_string(), 10u64); - let (alice_ptx, intent_nk, receiver_nk_com, receiver_app_data_dynamic, intent_rho) = - create_token_intent_ptx(&mut rng, sell.clone(), buy.clone(), alice_auth_sk, alice_nk); + let (alice_ptx, swap, intent_note) = + create_token_intent_ptx(&mut rng, sell.clone(), buy.clone(), alice_auth_sk); // Bob creates the partial transaction with 1 DOLPHIN input and 5 BTC output let bob_auth_sk = pallas::Scalar::random(&mut rng); let bob_auth_pk = generator * bob_auth_sk; let bob_nk = NullifierKeyContainer::random_key(&mut rng); - let eth_token = Token::new("eth".to_string(), 5); - let btc_token = Token::new("btc".to_string(), 1); + let offer = Token::new("eth".to_string(), 5); + let returned = Token::new("btc".to_string(), 1); let bob_ptx = create_token_swap_ptx( &mut rng, - eth_token, + offer.clone(), bob_auth_sk, bob_nk, - btc_token, + returned, bob_auth_pk, bob_nk.to_commitment(), ); // Solver/Bob creates the partial transaction to consume the intent note // The bob_ptx and solver_ptx can be merged to one ptx. - let solver_ptx = consume_token_intent_ptx( - &mut rng, - sell, - buy, - 5u64, - 1u64, - intent_rho, - intent_nk, - receiver_nk_com, - receiver_app_data_dynamic, - alice_auth_pk, - ); + let solver_ptx = consume_token_intent_ptx(&mut rng, swap, intent_note, offer, alice_auth_pk); // Solver creates the final transaction let shielded_tx_bundle = ShieldedPartialTxBundle::new(vec![alice_ptx, bob_ptx, solver_ptx]); diff --git a/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent.rs b/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent.rs index 1a721416..682c0bcf 100644 --- a/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent.rs +++ b/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent.rs @@ -6,9 +6,8 @@ use crate::{ circuit::{ blake2s::publicize_default_dynamic_vp_commitments, gadgets::{ - assign_free_advice, assign_free_constant, - mul::{MulChip, MulInstructions}, - poseidon_hash::poseidon_hash_gadget, + assign_free_constant, + mul::MulChip, sub::{SubChip, SubInstructions}, target_note_variable::{get_is_input_note_flag, get_owned_note_variable}, }, @@ -16,18 +15,15 @@ use crate::{ BasicValidityPredicateVariables, VPVerifyingInfo, ValidityPredicateCircuit, ValidityPredicateConfig, ValidityPredicatePublicInputs, ValidityPredicateVerifyingInfo, }, - vp_examples::token::{Token, TOKEN_VK}, }, constant::{NUM_NOTE, SETUP_PARAMS_MAP}, note::{Note, RandomSeed}, - nullifier::{Nullifier, NullifierKeyContainer}, proof::Proof, - utils::poseidon_hash_n, vp_commitment::ValidityPredicateCommitment, vp_vk::ValidityPredicateVerifyingKey, }; use halo2_proofs::{ - circuit::{floor_planner, Layouter, Value}, + circuit::{floor_planner, Layouter}, plonk::{keygen_pk, keygen_vk, Circuit, ConstraintSystem, Error}, }; use lazy_static::lazy_static; @@ -35,46 +31,26 @@ use pasta_curves::pallas; use rand::rngs::OsRng; use rand::RngCore; +pub mod swap; +pub use swap::Swap; + +mod data_static; +use data_static::PartialFulfillmentIntentDataStatic; + lazy_static! { pub static ref PARTIAL_FULFILLMENT_INTENT_VK: ValidityPredicateVerifyingKey = PartialFulfillmentIntentValidityPredicateCircuit::default().get_vp_vk(); pub static ref COMPRESSED_PARTIAL_FULFILLMENT_INTENT_VK: pallas::Base = PARTIAL_FULFILLMENT_INTENT_VK.get_compressed(); } + // PartialFulfillmentIntentValidityPredicateCircuit #[derive(Clone, Debug, Default)] pub struct PartialFulfillmentIntentValidityPredicateCircuit { pub owned_note_pub_id: pallas::Base, pub input_notes: [Note; NUM_NOTE], pub output_notes: [Note; NUM_NOTE], - pub sell: Token, - pub buy: Token, - pub receiver_nk_com: pallas::Base, - pub receiver_app_data_dynamic: pallas::Base, -} - -impl PartialFulfillmentIntentValidityPredicateCircuit { - pub fn encode_app_data_static( - sell: &Token, - buy: &Token, - receiver_nk_com: pallas::Base, - receiver_app_data_dynamic: pallas::Base, - ) -> pallas::Base { - let sold_token = sell.encode_name(); - let sold_token_value = sell.encode_value(); - let bought_token = buy.encode_name(); - let bought_token_value = buy.encode_value(); - poseidon_hash_n([ - sold_token, - sold_token_value, - bought_token, - bought_token_value, - // Assuming the sold_token and bought_token have the same TOKEN_VK - TOKEN_VK.get_compressed(), - receiver_nk_com, - receiver_app_data_dynamic, - ]) - } + pub swap: Swap, } impl ValidityPredicateCircuit for PartialFulfillmentIntentValidityPredicateCircuit { @@ -85,333 +61,87 @@ impl ValidityPredicateCircuit for PartialFulfillmentIntentValidityPredicateCircu mut layouter: impl Layouter, basic_variables: BasicValidityPredicateVariables, ) -> Result<(), Error> { - let owned_note_pub_id = basic_variables.get_owned_note_pub_id(); - let is_input_note = get_is_input_note_flag( - config.get_is_input_note_flag_config, - layouter.namespace(|| "get is_input_note_flag"), - &owned_note_pub_id, - &basic_variables.get_input_note_nfs(), - &basic_variables.get_output_note_cms(), - )?; - - let token_vp_vk = assign_free_advice( - layouter.namespace(|| "witness token vp vk"), - config.advices[0], - Value::known(TOKEN_VK.get_compressed()), - )?; - - let sold_token = assign_free_advice( - layouter.namespace(|| "witness sold_token"), - config.advices[0], - Value::known(self.sell.encode_name()), - )?; - - let sold_token_value = assign_free_advice( - layouter.namespace(|| "witness sold_token_value"), - config.advices[0], - Value::known(self.sell.encode_value()), - )?; + let sub_chip = SubChip::construct(config.sub_config.clone(), ()); + let mul_chip = MulChip::construct(config.mul_config.clone()); - let bought_token = assign_free_advice( - layouter.namespace(|| "witness bought_token"), - config.advices[0], - Value::known(self.buy.encode_name()), - )?; - - let bought_token_value = assign_free_advice( - layouter.namespace(|| "witness bought_token_value"), - config.advices[0], - Value::known(self.buy.encode_value()), - )?; - - let receiver_nk_com = assign_free_advice( - layouter.namespace(|| "witness receiver nk_com"), - config.advices[0], - Value::known(self.receiver_nk_com), - )?; + let owned_note_pub_id = basic_variables.get_owned_note_pub_id(); - let receiver_app_data_dynamic = assign_free_advice( - layouter.namespace(|| "witness receiver app_data_dynamic"), + let app_data_static = self.swap.assign_app_data_static( config.advices[0], - Value::known(self.receiver_app_data_dynamic), + layouter.namespace(|| "assign app_data_static"), )?; - - // Encode the app_data_static of intent note - let encoded_app_data_static = poseidon_hash_gadget( - config.poseidon_config, - layouter.namespace(|| "app_data_static encoding"), - [ - sold_token.clone(), - sold_token_value.clone(), - bought_token.clone(), - bought_token_value.clone(), - token_vp_vk.clone(), - receiver_nk_com.clone(), - receiver_app_data_dynamic.clone(), - ], + let encoded_app_data_static = app_data_static.encode( + config.poseidon_config.clone(), + layouter.namespace(|| "encode app_data_static"), )?; // search target note and get the intent app_static_data - let app_data_static = get_owned_note_variable( + let owned_note_app_data_static = get_owned_note_variable( config.get_owned_note_variable_config, layouter.namespace(|| "get owned note app_data_static"), &owned_note_pub_id, &basic_variables.get_app_data_static_searchable_pairs(), )?; - // check the app_data_static of intent note + // Enforce consistency of app_data_static: + // - as witnessed in the swap, and + // - as encoded in the intent note layouter.assign_region( || "check app_data_static", |mut region| { - region.constrain_equal(encoded_app_data_static.cell(), app_data_static.cell()) + region.constrain_equal( + encoded_app_data_static.cell(), + owned_note_app_data_static.cell(), + ) }, )?; - // Create the intent note - { - // TODO: use a nor gate to replace the sub gate. - let sub_chip = SubChip::construct(config.sub_config.clone(), ()); + let is_input_note = get_is_input_note_flag( + config.get_is_input_note_flag_config, + layouter.namespace(|| "get is_input_note_flag"), + &owned_note_pub_id, + &basic_variables.get_input_note_nfs(), + &basic_variables.get_output_note_cms(), + )?; + // Conditional checks if is_input_note == 1 + app_data_static.is_input_note_checks( + &is_input_note, + &basic_variables, + &config.conditional_equal_config, + layouter.namespace(|| "is_input_note checks"), + )?; + + let is_output_note = { let constant_one = assign_free_constant( layouter.namespace(|| "one"), config.advices[0], pallas::Base::one(), )?; - let is_output_note = SubInstructions::sub( + // TODO: use a nor gate to replace the sub gate. + SubInstructions::sub( &sub_chip, layouter.namespace(|| "expected_sold_value - returned_value"), &is_input_note, &constant_one, - )?; - layouter.assign_region( - || "conditional equal: check sold token vp_vk", - |mut region| { - config.conditional_equal_config.assign_region( - &is_output_note, - &token_vp_vk, - &basic_variables.input_note_variables[0] - .note_variables - .app_vk, - 0, - &mut region, - ) - }, - )?; - - layouter.assign_region( - || "conditional equal: check sold token app_data_static", - |mut region| { - config.conditional_equal_config.assign_region( - &is_output_note, - &sold_token, - &basic_variables.input_note_variables[0] - .note_variables - .app_data_static, - 0, - &mut region, - ) - }, - )?; - - layouter.assign_region( - || "conditional equal: check sold token value", - |mut region| { - config.conditional_equal_config.assign_region( - &is_output_note, - &sold_token_value, - &basic_variables.input_note_variables[0].note_variables.value, - 0, - &mut region, - ) - }, - )?; - } - - // Consume the intent note - { - layouter.assign_region( - || "conditional equal: check bought token vk", - |mut region| { - config.conditional_equal_config.assign_region( - &is_input_note, - &token_vp_vk, - &basic_variables.output_note_variables[0] - .note_variables - .app_vk, - 0, - &mut region, - ) - }, - )?; - - layouter.assign_region( - || "conditional equal: check bought token vk", - |mut region| { - config.conditional_equal_config.assign_region( - &is_input_note, - &bought_token, - &basic_variables.output_note_variables[0] - .note_variables - .app_data_static, - 0, - &mut region, - ) - }, - )?; - - // check nk_com - layouter.assign_region( - || "conditional equal: check bought token nk_com", - |mut region| { - config.conditional_equal_config.assign_region( - &is_input_note, - &receiver_nk_com, - &basic_variables.output_note_variables[0] - .note_variables - .nk_com, - 0, - &mut region, - ) - }, - )?; - - // check app_data_dynamic - layouter.assign_region( - || "conditional equal: check bought token app_data_dynamic", - |mut region| { - config.conditional_equal_config.assign_region( - &is_input_note, - &receiver_app_data_dynamic, - &basic_variables.output_note_variables[0] - .note_variables - .app_data_dynamic, - 0, - &mut region, - ) - }, - )?; - - let sub_chip = SubChip::construct(config.sub_config, ()); - let mul_chip = MulChip::construct(config.mul_config); - - let is_partial_fulfillment = SubInstructions::sub( - &sub_chip, - layouter.namespace(|| "expected_bought_token_value - actual_bought_token_value"), - &bought_token_value, - &basic_variables.output_note_variables[0] - .note_variables - .value, - )?; - let is_partial_fulfillment = MulInstructions::mul( - &mul_chip, - layouter.namespace(|| "is_input * is_partial_fulfillment"), - &is_input_note, - &is_partial_fulfillment, - )?; - - // check returned token vk if it's partially fulfilled - layouter.assign_region( - || "conditional equal: check returned token vk", - |mut region| { - config.conditional_equal_config.assign_region( - &is_partial_fulfillment, - &token_vp_vk, - &basic_variables.output_note_variables[1] - .note_variables - .app_vk, - 0, - &mut region, - ) - }, - )?; - - // check return token app_data_static if it's partially fulfilled - layouter.assign_region( - || "conditional equal: check returned token app_data_static", - |mut region| { - config.conditional_equal_config.assign_region( - &is_partial_fulfillment, - &sold_token, - &basic_variables.output_note_variables[1] - .note_variables - .app_data_static, - 0, - &mut region, - ) - }, - )?; - - layouter.assign_region( - || "conditional equal: check returned token nk_com", - |mut region| { - config.conditional_equal_config.assign_region( - &is_partial_fulfillment, - &receiver_nk_com, - &basic_variables.output_note_variables[1] - .note_variables - .nk_com, - 0, - &mut region, - ) - }, - )?; - - layouter.assign_region( - || "conditional equal: check returned token app_data_dynamic", - |mut region| { - config.conditional_equal_config.assign_region( - &is_partial_fulfillment, - &receiver_app_data_dynamic, - &basic_variables.output_note_variables[1] - .note_variables - .app_data_dynamic, - 0, - &mut region, - ) - }, - )?; + )? + }; + // Conditional checks if is_output_note == 1 + app_data_static.is_output_note_checks( + &is_output_note, + &basic_variables, + &config.conditional_equal_config, + layouter.namespace(|| "is_output_note checks"), + )?; - // value check - { - let actual_sold_value = SubInstructions::sub( - &sub_chip, - layouter.namespace(|| "expected_sold_value - returned_value"), - &sold_token_value, - &basic_variables.output_note_variables[1] - .note_variables - .value, - )?; - - // check (expected_bought_value * actual_sold_value) == (expected_sold_value * actual_bought_value) - // if it's partially fulfilled - let expected_bought_mul_actual_sold_value = MulInstructions::mul( - &mul_chip, - layouter.namespace(|| "expected_bought_value * actual_sold_value"), - &bought_token_value, - &actual_sold_value, - )?; - let expected_sold_mul_actual_bought_value = MulInstructions::mul( - &mul_chip, - layouter.namespace(|| "expected_sold_value * actual_bought_value"), - &sold_token_value, - &basic_variables.output_note_variables[0] - .note_variables - .value, - )?; - - layouter.assign_region( - || "conditional equal: expected_bought_value * actual_sold_value == expected_sold_value * actual_bought_value", - |mut region| { - config.conditional_equal_config.assign_region( - &is_partial_fulfillment, - &expected_bought_mul_actual_sold_value, - &expected_sold_mul_actual_bought_value, - 0, - &mut region, - ) - }, - )?; - } - } + // Conditional checks if is_partial_fulfillment == 1 + app_data_static.is_partial_fulfillment_checks( + &is_input_note, + &basic_variables, + &config.conditional_equal_config, + &sub_chip, + &mul_chip, + layouter.namespace(|| "is_partial_fulfillment checks"), + )?; // Publicize the dynamic vp commitments with default value publicize_default_dynamic_vp_commitments( @@ -453,79 +183,50 @@ impl ValidityPredicateCircuit for PartialFulfillmentIntentValidityPredicateCircu vp_circuit_impl!(PartialFulfillmentIntentValidityPredicateCircuit); vp_verifying_info_impl!(PartialFulfillmentIntentValidityPredicateCircuit); -pub fn create_intent_note( - mut rng: R, - sell: &Token, - buy: &Token, - receiver_nk_com: pallas::Base, - receiver_app_data_dynamic: pallas::Base, - rho: Nullifier, - nk: NullifierKeyContainer, -) -> Note { - let app_data_static = PartialFulfillmentIntentValidityPredicateCircuit::encode_app_data_static( - sell, - buy, - receiver_nk_com, - receiver_app_data_dynamic, - ); - let rseed = RandomSeed::random(&mut rng); - Note::new( - *COMPRESSED_PARTIAL_FULFILLMENT_INTENT_VK, - app_data_static, - pallas::Base::zero(), - 1u64, - nk, - rho, - false, - rseed, - ) -} - -#[test] -fn test_halo2_partial_fulfillment_intent_vp_circuit() { +#[cfg(test)] +mod tests { + use super::*; + use crate::circuit::vp_examples::{ + signature_verification::COMPRESSED_TOKEN_AUTH_VK, + token::{Token, TokenAuthorization}, + }; use crate::constant::VP_CIRCUIT_PARAMS_SIZE; - use crate::{circuit::vp_examples::token::COMPRESSED_TOKEN_VK, note::tests::random_input_note}; use halo2_proofs::arithmetic::Field; use halo2_proofs::dev::MockProver; use rand::rngs::OsRng; + use rand::RngCore; + + // Generate a swap, along with its corresponding intent note and authorisation + fn swap(mut rng: impl RngCore, sell: Token, buy: Token) -> Swap { + let sk = pallas::Scalar::random(&mut rng); + let auth = TokenAuthorization::from_sk_vk(&sk, &COMPRESSED_TOKEN_AUTH_VK); + + Swap::random(&mut rng, sell, buy, auth) + } + + #[test] + fn create_intent() { + use crate::nullifier::Nullifier; + + let mut rng = OsRng; + let sell = Token::new("token1".to_string(), 2u64); + let buy = Token::new("token2".to_string(), 4u64); + + let swap = swap(&mut rng, sell, buy); + let intent_note = swap.create_intent_note(&mut rng); - let mut rng = OsRng; - - let sell = Token::new("token1".to_string(), 2u64); - let buy = Token::new("token2".to_string(), 4u64); - - let mut sold_note = random_input_note(&mut rng); - sold_note.note_type.app_vk = *COMPRESSED_TOKEN_VK; - sold_note.note_type.app_data_static = sell.encode_name(); - sold_note.value = sell.value(); - let receiver_nk_com = sold_note.get_nk_commitment(); - let rho = Nullifier::from(pallas::Base::random(&mut rng)); - let nk = NullifierKeyContainer::random_key(&mut rng); - let intent_note = create_intent_note( - &mut rng, - &sell, - &buy, - receiver_nk_com, - sold_note.app_data_dynamic, - rho, - nk, - ); - // Creating intent test - { let input_padding_note = Note::random_padding_input_note(&mut rng); - let input_notes = [sold_note, input_padding_note]; - let output_padding_note = - Note::random_padding_output_note(&mut rng, input_padding_note.get_nf().unwrap()); + let nf = Nullifier::random(&mut rng); + let output_padding_note = Note::random_padding_output_note(&mut rng, nf); + + let input_notes = [*swap.sell.note(), input_padding_note]; let output_notes = [intent_note, output_padding_note]; let circuit = PartialFulfillmentIntentValidityPredicateCircuit { owned_note_pub_id: intent_note.commitment().inner(), input_notes, output_notes, - sell: sell.clone(), - buy: buy.clone(), - receiver_nk_com, - receiver_app_data_dynamic: sold_note.app_data_dynamic, + swap, }; let public_inputs = circuit.get_public_inputs(&mut rng); @@ -535,75 +236,64 @@ fn test_halo2_partial_fulfillment_intent_vp_circuit() { vec![public_inputs.to_vec()], ) .unwrap(); - assert_eq!(prover.verify(), Ok(())); + prover.assert_satisfied(); } - // Consuming intent test - { - { - let input_padding_note = Note::random_padding_input_note(&mut rng); - let input_notes = [intent_note, input_padding_note]; - let mut bought_note = sold_note; - bought_note.note_type.app_data_static = buy.encode_name(); - bought_note.app_data_dynamic = sold_note.app_data_dynamic; - bought_note.nk_container = sold_note.nk_container; - - // full fulfillment - { - bought_note.value = buy.value(); - let output_padding_note = Note::random_padding_output_note( - &mut rng, - input_padding_note.get_nf().unwrap(), - ); - let output_notes = [bought_note, output_padding_note]; - - let circuit = PartialFulfillmentIntentValidityPredicateCircuit { - owned_note_pub_id: intent_note.get_nf().unwrap().inner(), - input_notes, - output_notes, - sell: sell.clone(), - buy: buy.clone(), - receiver_nk_com, - receiver_app_data_dynamic: sold_note.app_data_dynamic, - }; - let public_inputs = circuit.get_public_inputs(&mut rng); - - let prover = MockProver::::run( - VP_CIRCUIT_PARAMS_SIZE, - &circuit, - vec![public_inputs.to_vec()], - ) - .unwrap(); - assert_eq!(prover.verify(), Ok(())); - } - - // partial fulfillment - { - bought_note.value = 2u64; - let mut returned_note = bought_note; - returned_note.note_type.app_data_static = sell.encode_name(); - returned_note.value = 1u64; - let output_notes = [bought_note, returned_note]; - - let circuit = PartialFulfillmentIntentValidityPredicateCircuit { - owned_note_pub_id: intent_note.get_nf().unwrap().inner(), - input_notes, - output_notes, - sell, - buy, - receiver_nk_com, - receiver_app_data_dynamic: sold_note.app_data_dynamic, - }; - let public_inputs = circuit.get_public_inputs(&mut rng); - - let prover = MockProver::::run( - VP_CIRCUIT_PARAMS_SIZE, - &circuit, - vec![public_inputs.to_vec()], - ) - .unwrap(); - assert_eq!(prover.verify(), Ok(())); - } - } + #[test] + fn full_fulfillment() { + let mut rng = OsRng; + let sell = Token::new("token1".to_string(), 2u64); + let buy = Token::new("token2".to_string(), 4u64); + + let swap = swap(&mut rng, sell, buy); + let intent_note = swap.create_intent_note(&mut rng); + + let bob_sell = swap.buy.clone(); + let (input_notes, output_notes) = swap.fill(&mut rng, intent_note, bob_sell); + + let circuit = PartialFulfillmentIntentValidityPredicateCircuit { + owned_note_pub_id: intent_note.get_nf().unwrap().inner(), + input_notes, + output_notes, + swap, + }; + let public_inputs = circuit.get_public_inputs(&mut rng); + + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); + prover.assert_satisfied(); + } + + #[test] + fn partial_fulfillment() { + let mut rng = OsRng; + let sell = Token::new("token1".to_string(), 2u64); + let buy = Token::new("token2".to_string(), 4u64); + + let swap = swap(&mut rng, sell, buy); + let intent_note = swap.create_intent_note(&mut rng); + + let bob_sell = Token::new(swap.buy.name().inner().to_string(), 2u64); + let (input_notes, output_notes) = swap.fill(&mut rng, intent_note, bob_sell); + + let circuit = PartialFulfillmentIntentValidityPredicateCircuit { + owned_note_pub_id: intent_note.get_nf().unwrap().inner(), + input_notes, + output_notes, + swap, + }; + let public_inputs = circuit.get_public_inputs(&mut rng); + + let prover = MockProver::::run( + VP_CIRCUIT_PARAMS_SIZE, + &circuit, + vec![public_inputs.to_vec()], + ) + .unwrap(); + prover.assert_satisfied(); } } diff --git a/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent/data_static.rs b/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent/data_static.rs new file mode 100644 index 00000000..f3b7e2bc --- /dev/null +++ b/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent/data_static.rs @@ -0,0 +1,310 @@ +use crate::circuit::{ + gadgets::{ + conditional_equal::ConditionalEqualConfig, + mul::{MulChip, MulInstructions}, + poseidon_hash::poseidon_hash_gadget, + sub::{SubChip, SubInstructions}, + }, + vp_circuit::BasicValidityPredicateVariables, +}; +use halo2_gadgets::poseidon::Pow5Config as PoseidonConfig; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter}, + plonk::Error, +}; +use pasta_curves::pallas; + +#[derive(Clone, Debug)] +pub struct PartialFulfillmentIntentDataStatic { + pub token_vp_vk: AssignedCell, + pub sold_token: AssignedCell, + pub sold_token_value: AssignedCell, + pub bought_token: AssignedCell, + pub bought_token_value: AssignedCell, + pub receiver_nk_com: AssignedCell, + pub receiver_app_data_dynamic: AssignedCell, +} + +impl PartialFulfillmentIntentDataStatic { + pub fn encode( + &self, + config: PoseidonConfig, + mut layouter: impl Layouter, + ) -> Result, Error> { + // Encode the app_data_static of intent note + poseidon_hash_gadget( + config.clone(), + layouter.namespace(|| "app_data_static encoding"), + [ + self.sold_token.clone(), + self.sold_token_value.clone(), + self.bought_token.clone(), + self.bought_token_value.clone(), + self.token_vp_vk.clone(), + self.receiver_nk_com.clone(), + self.receiver_app_data_dynamic.clone(), + ], + ) + } + + /// Checks to be enforced if `is_input_note == 1` + pub fn is_input_note_checks( + &self, + is_input_note: &AssignedCell, + basic_variables: &BasicValidityPredicateVariables, + config: &ConditionalEqualConfig, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "conditional equal: check bought token vk", + |mut region| { + config.assign_region( + is_input_note, + &self.token_vp_vk, + &basic_variables.output_note_variables[0] + .note_variables + .app_vk, + 0, + &mut region, + ) + }, + )?; + + layouter.assign_region( + || "conditional equal: check bought token vk", + |mut region| { + config.assign_region( + is_input_note, + &self.bought_token, + &basic_variables.output_note_variables[0] + .note_variables + .app_data_static, + 0, + &mut region, + ) + }, + )?; + + // check nk_com + layouter.assign_region( + || "conditional equal: check bought token nk_com", + |mut region| { + config.assign_region( + is_input_note, + &self.receiver_nk_com, + &basic_variables.output_note_variables[0] + .note_variables + .nk_com, + 0, + &mut region, + ) + }, + )?; + + // check app_data_dynamic + layouter.assign_region( + || "conditional equal: check bought token app_data_dynamic", + |mut region| { + config.assign_region( + is_input_note, + &self.receiver_app_data_dynamic, + &basic_variables.output_note_variables[0] + .note_variables + .app_data_dynamic, + 0, + &mut region, + ) + }, + )?; + + Ok(()) + } + + /// Checks to be enforced if `is_output_note == 1` + pub fn is_output_note_checks( + &self, + is_output_note: &AssignedCell, + basic_variables: &BasicValidityPredicateVariables, + config: &ConditionalEqualConfig, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "conditional equal: check sold token vp_vk", + |mut region| { + config.assign_region( + is_output_note, + &self.token_vp_vk, + &basic_variables.input_note_variables[0] + .note_variables + .app_vk, + 0, + &mut region, + ) + }, + )?; + + layouter.assign_region( + || "conditional equal: check sold token app_data_static", + |mut region| { + config.assign_region( + is_output_note, + &self.sold_token, + &basic_variables.input_note_variables[0] + .note_variables + .app_data_static, + 0, + &mut region, + ) + }, + )?; + + layouter.assign_region( + || "conditional equal: check sold token value", + |mut region| { + config.assign_region( + is_output_note, + &self.sold_token_value, + &basic_variables.input_note_variables[0].note_variables.value, + 0, + &mut region, + ) + }, + )?; + + Ok(()) + } + + /// Checks to be enforced if `is_partial_fulfillment == 1` + pub fn is_partial_fulfillment_checks( + &self, + is_input_note: &AssignedCell, + basic_variables: &BasicValidityPredicateVariables, + config: &ConditionalEqualConfig, + sub_chip: &SubChip, + mul_chip: &MulChip, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let is_partial_fulfillment = { + let is_partial_fulfillment = SubInstructions::sub( + sub_chip, + layouter.namespace(|| "expected_bought_token_value - actual_bought_token_value"), + &self.bought_token_value, + &basic_variables.output_note_variables[0] + .note_variables + .value, + )?; + MulInstructions::mul( + mul_chip, + layouter.namespace(|| "is_input * is_partial_fulfillment"), + is_input_note, + &is_partial_fulfillment, + )? + }; + + // check returned token vk if it's partially fulfilled + layouter.assign_region( + || "conditional equal: check returned token vk", + |mut region| { + config.assign_region( + &is_partial_fulfillment, + &self.token_vp_vk, + &basic_variables.output_note_variables[1] + .note_variables + .app_vk, + 0, + &mut region, + ) + }, + )?; + + // check return token app_data_static if it's partially fulfilled + layouter.assign_region( + || "conditional equal: check returned token app_data_static", + |mut region| { + config.assign_region( + &is_partial_fulfillment, + &self.sold_token, + &basic_variables.output_note_variables[1] + .note_variables + .app_data_static, + 0, + &mut region, + ) + }, + )?; + + layouter.assign_region( + || "conditional equal: check returned token nk_com", + |mut region| { + config.assign_region( + &is_partial_fulfillment, + &self.receiver_nk_com, + &basic_variables.output_note_variables[1] + .note_variables + .nk_com, + 0, + &mut region, + ) + }, + )?; + + layouter.assign_region( + || "conditional equal: check returned token app_data_dynamic", + |mut region| { + config.assign_region( + &is_partial_fulfillment, + &self.receiver_app_data_dynamic, + &basic_variables.output_note_variables[1] + .note_variables + .app_data_dynamic, + 0, + &mut region, + ) + }, + )?; + + // value check + { + let actual_sold_value = SubInstructions::sub( + sub_chip, + layouter.namespace(|| "expected_sold_value - returned_value"), + &self.sold_token_value, + &basic_variables.output_note_variables[1] + .note_variables + .value, + )?; + + // check (expected_bought_value * actual_sold_value) == (expected_sold_value * actual_bought_value) + // if it's partially fulfilled + let expected_bought_mul_actual_sold_value = MulInstructions::mul( + mul_chip, + layouter.namespace(|| "expected_bought_value * actual_sold_value"), + &self.bought_token_value, + &actual_sold_value, + )?; + let expected_sold_mul_actual_bought_value = MulInstructions::mul( + mul_chip, + layouter.namespace(|| "expected_sold_value * actual_bought_value"), + &self.sold_token_value, + &basic_variables.output_note_variables[0] + .note_variables + .value, + )?; + + layouter.assign_region( + || "conditional equal: expected_bought_value * actual_sold_value == expected_sold_value * actual_bought_value", + |mut region| { + config.assign_region( + &is_partial_fulfillment, + &expected_bought_mul_actual_sold_value, + &expected_sold_mul_actual_bought_value, + 0, + &mut region, + ) + }, + )?; + } + + Ok(()) + } +} diff --git a/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent/swap.rs b/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent/swap.rs new file mode 100644 index 00000000..1f8e82ae --- /dev/null +++ b/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent/swap.rs @@ -0,0 +1,177 @@ +use super::{PartialFulfillmentIntentDataStatic, COMPRESSED_PARTIAL_FULFILLMENT_INTENT_VK}; +use crate::{ + circuit::{ + gadgets::assign_free_advice, + vp_examples::token::{Token, TokenAuthorization, TokenNote, TOKEN_VK}, + }, + constant::NUM_NOTE, + note::{Note, RandomSeed}, + nullifier::{Nullifier, NullifierKeyContainer}, + utils::poseidon_hash_n, +}; +use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::{Advice, Column, Error}, +}; +use pasta_curves::pallas; +use rand::RngCore; + +#[derive(Clone, Debug, Default)] +pub struct Swap { + pub sell: TokenNote, + pub buy: Token, + pub auth: TokenAuthorization, +} + +impl Swap { + pub fn random( + mut rng: impl RngCore, + sell: Token, + buy: Token, + auth: TokenAuthorization, + ) -> Self { + assert_eq!(buy.value() % sell.value(), 0); + + let sell = { + let rho = Nullifier::random(&mut rng); + let nk = NullifierKeyContainer::random_key(&mut rng); + sell.create_random_token_note(&mut rng, rho, nk, &auth) + }; + + Swap { sell, buy, auth } + } + + /// Either: + /// - completely fills the swap using a single `TokenNote`, or + /// - partially fills the swap, producing a `TokenNote` and a + /// returned note. + pub fn fill( + &self, + mut rng: impl RngCore, + intent_note: Note, + offer: Token, + ) -> ([Note; NUM_NOTE], [Note; NUM_NOTE]) { + assert_eq!(offer.name(), self.buy.name()); + + let ratio = self.buy.value() / self.sell.value; + assert_eq!(offer.value() % ratio, 0); + + let offer_note = offer.create_random_token_note( + &mut rng, + intent_note.get_nf().unwrap(), + self.sell.note().nk_container, + &self.auth, + ); + + let input_padding_note = Note::random_padding_input_note(&mut rng); + + let returned_note = if offer.value() < self.buy.value() { + let filled_value = offer.value() / ratio; + let returned_value = self.sell.value - filled_value; + let returned_token = + Token::new(self.sell.token_name().inner().to_string(), returned_value); + *returned_token + .create_random_token_note( + &mut rng, + input_padding_note.get_nf().unwrap(), + self.sell.note().nk_container, + &self.auth, + ) + .note() + } else { + Note::random_padding_output_note(&mut rng, input_padding_note.get_nf().unwrap()) + }; + + let input_notes = [intent_note, input_padding_note]; + let output_notes = [*offer_note.note(), returned_note]; + + (input_notes, output_notes) + } + + pub fn encode_app_data_static(&self) -> pallas::Base { + poseidon_hash_n([ + self.sell.encode_name(), + self.sell.encode_value(), + self.buy.encode_name(), + self.buy.encode_value(), + // Assuming the sold_token and bought_token have the same TOKEN_VK + TOKEN_VK.get_compressed(), + self.sell.note().get_nk_commitment(), + self.sell.note().app_data_dynamic, + ]) + } + + pub fn create_intent_note(&self, mut rng: R) -> Note { + let rseed = RandomSeed::random(&mut rng); + + Note::new( + *COMPRESSED_PARTIAL_FULFILLMENT_INTENT_VK, + self.encode_app_data_static(), + pallas::Base::zero(), + 1u64, + self.sell.note().nk_container, + self.sell.note().get_nf().unwrap(), + false, + rseed, + ) + } + + /// Assign variables encoded in app_static_data + pub fn assign_app_data_static( + &self, + column: Column, + mut layouter: impl Layouter, + ) -> Result { + let token_vp_vk = assign_free_advice( + layouter.namespace(|| "witness token vp vk"), + column, + Value::known(TOKEN_VK.get_compressed()), + )?; + + let sold_token = assign_free_advice( + layouter.namespace(|| "witness sold_token"), + column, + Value::known(self.sell.encode_name()), + )?; + + let sold_token_value = assign_free_advice( + layouter.namespace(|| "witness sold_token_value"), + column, + Value::known(self.sell.encode_value()), + )?; + + let bought_token = assign_free_advice( + layouter.namespace(|| "witness bought_token"), + column, + Value::known(self.buy.encode_name()), + )?; + + let bought_token_value = assign_free_advice( + layouter.namespace(|| "witness bought_token_value"), + column, + Value::known(self.buy.encode_value()), + )?; + + let receiver_nk_com = assign_free_advice( + layouter.namespace(|| "witness receiver nk_com"), + column, + Value::known(self.sell.note().get_nk_commitment()), + )?; + + let receiver_app_data_dynamic = assign_free_advice( + layouter.namespace(|| "witness receiver app_data_dynamic"), + column, + Value::known(self.sell.note().app_data_dynamic), + )?; + + Ok(PartialFulfillmentIntentDataStatic { + token_vp_vk, + sold_token, + sold_token_value, + bought_token, + bought_token_value, + receiver_nk_com, + receiver_app_data_dynamic, + }) + } +} diff --git a/taiga_halo2/src/circuit/vp_examples/token.rs b/taiga_halo2/src/circuit/vp_examples/token.rs index 87d97e2c..dead3b84 100644 --- a/taiga_halo2/src/circuit/vp_examples/token.rs +++ b/taiga_halo2/src/circuit/vp_examples/token.rs @@ -46,7 +46,7 @@ lazy_static! { pub static ref COMPRESSED_TOKEN_VK: pallas::Base = TOKEN_VK.get_compressed(); } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct TokenName(String); impl TokenName { @@ -122,8 +122,8 @@ impl Token { #[derive(Clone, Debug, Default)] pub struct TokenNote { - token_name: TokenName, - note: Note, + pub token_name: TokenName, + pub note: Note, } impl std::ops::Deref for TokenNote { @@ -139,6 +139,14 @@ impl TokenNote { &self.token_name } + pub fn encode_name(&self) -> pallas::Base { + self.token_name.encode() + } + + pub fn encode_value(&self) -> pallas::Base { + pallas::Base::from(self.note().value) + } + pub fn note(&self) -> &Note { &self.note } diff --git a/taiga_halo2/src/nullifier.rs b/taiga_halo2/src/nullifier.rs index 7bc33cb4..260bee0b 100644 --- a/taiga_halo2/src/nullifier.rs +++ b/taiga_halo2/src/nullifier.rs @@ -62,6 +62,10 @@ impl Nullifier { pub fn from_bytes(bytes: [u8; 32]) -> CtOption { pallas::Base::from_repr(bytes).map(Nullifier) } + + pub fn random(mut rng: impl RngCore) -> Self { + Self(pallas::Base::random(&mut rng)) + } } impl From for Nullifier {