diff --git a/taiga_halo2/src/action.rs b/taiga_halo2/src/action.rs index 56f34217..8305a8d2 100644 --- a/taiga_halo2/src/action.rs +++ b/taiga_halo2/src/action.rs @@ -40,7 +40,9 @@ pub struct ActionPublicInputs { } /// The information to build ActionPublicInputs and ActionCircuit. -#[derive(Clone)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] pub struct ActionInfo { input_note: Note, input_merkle_path: MerklePath, @@ -157,17 +159,35 @@ impl ActionInfo { self.rseed.get_vp_cm_r(PRF_EXPAND_OUTPUT_VP_CM_R) } + // Only used in transparent scenario: the achor is untrusted, recalculate root when executing it transparently. + pub fn calculate_root(&self) -> Anchor { + self.input_note.calculate_root(&self.input_merkle_path) + } + + // Get value commitment + pub fn get_value_commitment(&self, blind_r: &pallas::Scalar) -> ValueCommitment { + ValueCommitment::commit(&self.input_note, &self.output_note, blind_r) + } + + pub fn get_input_note_nullifer(&self) -> Nullifier { + self.input_note.get_nf().unwrap() + } + + pub fn get_output_note_cm(&self) -> NoteCommitment { + self.output_note.commitment() + } + pub fn build(&self) -> (ActionPublicInputs, ActionCircuit) { - let nf = self.input_note.get_nf().unwrap(); + let nf = self.get_input_note_nullifer(); assert_eq!( nf, self.output_note.rho, "The nf of input note should be equal to the rho of output note" ); - let cm = self.output_note.commitment(); + let cm = self.get_output_note_cm(); let rcv = self.get_rcv(); - let cv_net = ValueCommitment::new(&self.input_note, &self.output_note, &rcv); + let cv_net = self.get_value_commitment(&rcv); let input_vp_cm_r = self.get_input_vp_com_r(); let input_vp_commitment = diff --git a/taiga_halo2/src/circuit/mod.rs b/taiga_halo2/src/circuit/mod.rs index 38ad1696..59c5446d 100644 --- a/taiga_halo2/src/circuit/mod.rs +++ b/taiga_halo2/src/circuit/mod.rs @@ -11,6 +11,5 @@ pub mod hash_to_curve; pub mod note_commitment; pub mod note_encryption_circuit; mod vamp_ir_utils; -#[cfg(feature = "borsh")] pub mod vp_bytecode; pub mod vp_examples; diff --git a/taiga_halo2/src/circuit/vp_bytecode.rs b/taiga_halo2/src/circuit/vp_bytecode.rs index afb39866..9753792b 100644 --- a/taiga_halo2/src/circuit/vp_bytecode.rs +++ b/taiga_halo2/src/circuit/vp_bytecode.rs @@ -1,14 +1,29 @@ -use crate::circuit::{ - vp_circuit::{VPVerifyingInfo, ValidityPredicateVerifyingInfo, VampIRValidityPredicateCircuit}, - vp_examples::TrivialValidityPredicateCircuit, -}; +#[cfg(feature = "borsh")] +use crate::circuit::vp_examples::TrivialValidityPredicateCircuit; +use crate::error::TransactionError; use crate::shielded_ptx::NoteVPVerifyingInfoSet; +use crate::{ + circuit::vp_circuit::{ + VPVerifyingInfo, ValidityPredicateVerifyingInfo, VampIRValidityPredicateCircuit, + }, + constant::{ + VP_CIRCUIT_NULLIFIER_ONE_PUBLIC_INPUT_IDX, VP_CIRCUIT_NULLIFIER_TWO_PUBLIC_INPUT_IDX, + VP_CIRCUIT_OUTPUT_CM_ONE_PUBLIC_INPUT_IDX, VP_CIRCUIT_OUTPUT_CM_TWO_PUBLIC_INPUT_IDX, + VP_CIRCUIT_OWNED_NOTE_PUB_ID_PUBLIC_INPUT_IDX, + }, + note::NoteCommitment, + nullifier::Nullifier, +}; + +#[cfg(feature = "borsh")] use borsh::{BorshDeserialize, BorshSerialize}; +use pasta_curves::pallas; #[cfg(feature = "serde")] use serde; use std::path::PathBuf; -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ValidityPredicateRepresentation { // vampir has a unified circuit representation. @@ -19,14 +34,16 @@ pub enum ValidityPredicateRepresentation { // TODO: add other vp types here if needed } -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ValidityPredicateByteCode { circuit: ValidityPredicateRepresentation, inputs: Vec, } -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ApplicationByteCode { app_vp_bytecode: ValidityPredicateByteCode, @@ -38,7 +55,7 @@ impl ValidityPredicateByteCode { Self { circuit, inputs } } - pub fn generate_proof(self) -> VPVerifyingInfo { + pub fn generate_proof(self) -> Result { match self.circuit { ValidityPredicateRepresentation::VampIR(circuit) => { // TDDO: use the file_name api atm, @@ -50,14 +67,74 @@ impl ValidityPredicateByteCode { &vamp_ir_circuit_file, &inputs_file, ); - vp_circuit.get_verifying_info() + Ok(vp_circuit.get_verifying_info()) } + #[cfg(feature = "borsh")] ValidityPredicateRepresentation::Trivial => { - let vp = TrivialValidityPredicateCircuit::from_bytes(self.inputs); - vp.get_verifying_info() + let vp = TrivialValidityPredicateCircuit::from_bytes(&self.inputs); + Ok(vp.get_verifying_info()) } + #[allow(unreachable_patterns)] + _ => Err(TransactionError::InvalidValidityPredicateRepresentation), } } + + // Verify vp circuit transparently and return owned note PubID for further checking + pub fn verify_transparently( + &self, + action_nfs: &[Nullifier], + action_cms: &[NoteCommitment], + ) -> Result { + // check VP transparently + let public_inputs = match &self.circuit { + ValidityPredicateRepresentation::VampIR(circuit) => { + // TDDO: use the file_name api atm, + // request vamp_ir to provide a api to generate circuit from bytes. + let vamp_ir_circuit_file = + PathBuf::from(String::from_utf8_lossy(circuit).to_string()); + let inputs_file = PathBuf::from(String::from_utf8_lossy(&self.inputs).to_string()); + let vp_circuit = VampIRValidityPredicateCircuit::from_vamp_ir_file( + &vamp_ir_circuit_file, + &inputs_file, + ); + vp_circuit.verify_transparently()? + } + #[cfg(feature = "borsh")] + ValidityPredicateRepresentation::Trivial => { + let vp = TrivialValidityPredicateCircuit::from_bytes(&self.inputs); + vp.verify_transparently()? + } + #[allow(unreachable_patterns)] + _ => return Err(TransactionError::InvalidValidityPredicateRepresentation), + }; + + // check nullifiers + // Check the vp actually uses the input notes from action circuits. + let vp_nfs = [ + public_inputs.get_from_index(VP_CIRCUIT_NULLIFIER_ONE_PUBLIC_INPUT_IDX), + public_inputs.get_from_index(VP_CIRCUIT_NULLIFIER_TWO_PUBLIC_INPUT_IDX), + ]; + + if !((action_nfs[0].inner() == vp_nfs[0] && action_nfs[1].inner() == vp_nfs[1]) + || (action_nfs[0].inner() == vp_nfs[1] && action_nfs[1].inner() == vp_nfs[0])) + { + return Err(TransactionError::InconsistentNullifier); + } + + // check note_commitments + // Check the vp actually uses the output notes from action circuits. + let vp_cms = [ + public_inputs.get_from_index(VP_CIRCUIT_OUTPUT_CM_ONE_PUBLIC_INPUT_IDX), + public_inputs.get_from_index(VP_CIRCUIT_OUTPUT_CM_TWO_PUBLIC_INPUT_IDX), + ]; + if !((action_cms[0].inner() == vp_cms[0] && action_cms[1].inner() == vp_cms[1]) + || (action_cms[0].inner() == vp_cms[1] && action_cms[1].inner() == vp_cms[0])) + { + return Err(TransactionError::InconsistentOutputNoteCommitment); + } + + Ok(public_inputs.get_from_index(VP_CIRCUIT_OWNED_NOTE_PUB_ID_PUBLIC_INPUT_IDX)) + } } impl ApplicationByteCode { @@ -71,14 +148,36 @@ impl ApplicationByteCode { } } - pub fn generate_proofs(self) -> NoteVPVerifyingInfoSet { - let app_vp_verifying_info = self.app_vp_bytecode.generate_proof(); + pub fn generate_proofs(self) -> Result { + let app_vp_verifying_info = self.app_vp_bytecode.generate_proof()?; - let app_dynamic_vp_verifying_info = self + let app_dynamic_vp_verifying_info: Result, _> = self .dynamic_vp_bytecode .into_iter() .map(|bytecode| bytecode.generate_proof()) .collect(); - NoteVPVerifyingInfoSet::new(app_vp_verifying_info, app_dynamic_vp_verifying_info) + Ok(NoteVPVerifyingInfoSet::new( + app_vp_verifying_info, + app_dynamic_vp_verifying_info?, + )) + } + + // Verify vp circuits transparently and return owned note PubID for further checking + pub fn verify_transparently( + &self, + action_nfs: &[Nullifier], + action_cms: &[NoteCommitment], + ) -> Result { + let owned_note_id = self + .app_vp_bytecode + .verify_transparently(action_nfs, action_cms)?; + for dynamic_vp in self.dynamic_vp_bytecode.iter() { + let id = dynamic_vp.verify_transparently(action_nfs, action_cms)?; + // check: the app_vp and dynamic_vps belong to the note + if id != owned_note_id { + return Err(TransactionError::InconsistentOwnedNotePubID); + } + } + Ok(owned_note_id) } } diff --git a/taiga_halo2/src/circuit/vp_circuit.rs b/taiga_halo2/src/circuit/vp_circuit.rs index e0a98fb9..932f162a 100644 --- a/taiga_halo2/src/circuit/vp_circuit.rs +++ b/taiga_halo2/src/circuit/vp_circuit.rs @@ -25,6 +25,7 @@ use crate::{ VP_CIRCUIT_OWNED_NOTE_PUB_ID_PUBLIC_INPUT_IDX, VP_CIRCUIT_PARAMS_SIZE, VP_CIRCUIT_PUBLIC_INPUT_NUM, }, + error::TransactionError, note::{Note, NoteCommitment, RandomSeed}, note_encryption::{NoteCiphertext, SecretKey}, proof::Proof, @@ -425,6 +426,7 @@ impl ValidityPredicateConfig { pub trait ValidityPredicateVerifyingInfo: DynClone { fn get_verifying_info(&self) -> VPVerifyingInfo; + fn verify_transparently(&self) -> Result; fn get_vp_vk(&self) -> ValidityPredicateVerifyingKey; } @@ -776,6 +778,19 @@ macro_rules! vp_verifying_info_impl { } } + fn verify_transparently( + &self, + ) -> Result { + use halo2_proofs::dev::MockProver; + let mut rng = OsRng; + let public_inputs = self.get_public_inputs(&mut rng); + let prover = + MockProver::::run(15, self, vec![public_inputs.to_vec()]) + .unwrap(); + prover.verify().unwrap(); + Ok(public_inputs) + } + fn get_vp_vk(&self) -> ValidityPredicateVerifyingKey { let params = SETUP_PARAMS_MAP.get(&15).unwrap(); let vk = keygen_vk(params, self).expect("keygen_vk should not fail"); @@ -912,6 +927,22 @@ impl ValidityPredicateVerifyingInfo for VampIRValidityPredicateCircuit { } } + fn verify_transparently(&self) -> Result { + use halo2_proofs::dev::MockProver; + let mut rng = OsRng; + let mut public_inputs = self.public_inputs.clone(); + let rseed = RandomSeed::random(&mut rng); + public_inputs.extend(ValidityPredicatePublicInputs::get_public_input_padding( + self.public_inputs.len(), + &rseed, + )); + let prover = + MockProver::::run(15, &self.circuit, vec![public_inputs.to_vec()]) + .unwrap(); + prover.verify().unwrap(); + Ok(ValidityPredicatePublicInputs::from(public_inputs)) + } + fn get_vp_vk(&self) -> ValidityPredicateVerifyingKey { let vk = keygen_vk(&self.params, &self.circuit).expect("keygen_vk should not fail"); ValidityPredicateVerifyingKey::from_vk(vk) diff --git a/taiga_halo2/src/circuit/vp_examples.rs b/taiga_halo2/src/circuit/vp_examples.rs index 764591fc..2ed238e6 100644 --- a/taiga_halo2/src/circuit/vp_examples.rs +++ b/taiga_halo2/src/circuit/vp_examples.rs @@ -6,6 +6,7 @@ use crate::{ ValidityPredicatePublicInputs, ValidityPredicateVerifyingInfo, }, constant::{NUM_NOTE, SETUP_PARAMS_MAP, VP_CIRCUIT_PARAMS_SIZE}, + error::TransactionError, note::{Note, RandomSeed}, proof::Proof, vp_commitment::ValidityPredicateCommitment, @@ -100,7 +101,7 @@ impl TrivialValidityPredicateCircuit { // Only for test #[cfg(feature = "borsh")] - pub fn from_bytes(bytes: Vec) -> Self { + pub fn from_bytes(bytes: &Vec) -> Self { BorshDeserialize::deserialize(&mut bytes.as_ref()).unwrap() } @@ -232,6 +233,16 @@ impl ValidityPredicateVerifyingInfo for TrivialValidityPredicateCircuit { } } + fn verify_transparently(&self) -> Result { + use halo2_proofs::dev::MockProver; + let mut rng = OsRng; + let public_inputs = self.get_public_inputs(&mut rng); + let prover = + MockProver::::run(15, self, vec![public_inputs.to_vec()]).unwrap(); + prover.verify().unwrap(); + Ok(public_inputs) + } + fn get_vp_vk(&self) -> ValidityPredicateVerifyingKey { TRIVIAL_VP_VK.clone() } diff --git a/taiga_halo2/src/circuit/vp_examples/cascade_intent.rs b/taiga_halo2/src/circuit/vp_examples/cascade_intent.rs index 686aec0a..5779f0df 100644 --- a/taiga_halo2/src/circuit/vp_examples/cascade_intent.rs +++ b/taiga_halo2/src/circuit/vp_examples/cascade_intent.rs @@ -17,6 +17,7 @@ use crate::{ }, }, constant::{NUM_NOTE, SETUP_PARAMS_MAP}, + error::TransactionError, note::{Note, RandomSeed}, nullifier::Nullifier, proof::Proof, diff --git a/taiga_halo2/src/circuit/vp_examples/field_addition.rs b/taiga_halo2/src/circuit/vp_examples/field_addition.rs index 90b77b2d..4e0d1662 100644 --- a/taiga_halo2/src/circuit/vp_examples/field_addition.rs +++ b/taiga_halo2/src/circuit/vp_examples/field_addition.rs @@ -11,6 +11,7 @@ use crate::{ }, }, constant::{NUM_NOTE, SETUP_PARAMS_MAP, VP_CIRCUIT_CUSTOM_PUBLIC_INPUT_BEGIN_IDX}, + error::TransactionError, note::{Note, RandomSeed}, proof::Proof, vp_commitment::ValidityPredicateCommitment, diff --git a/taiga_halo2/src/circuit/vp_examples/or_relation_intent.rs b/taiga_halo2/src/circuit/vp_examples/or_relation_intent.rs index efbdb533..521758a2 100644 --- a/taiga_halo2/src/circuit/vp_examples/or_relation_intent.rs +++ b/taiga_halo2/src/circuit/vp_examples/or_relation_intent.rs @@ -17,6 +17,7 @@ use crate::{ vp_examples::token::{Token, TOKEN_VK}, }, constant::{NUM_NOTE, SETUP_PARAMS_MAP}, + error::TransactionError, note::{Note, RandomSeed}, nullifier::Nullifier, proof::Proof, 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 b1700a9d..6f48c3ac 100644 --- a/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent.rs +++ b/taiga_halo2/src/circuit/vp_examples/partial_fulfillment_intent.rs @@ -17,6 +17,7 @@ use crate::{ }, }, constant::{NUM_NOTE, SETUP_PARAMS_MAP}, + error::TransactionError, note::{Note, RandomSeed}, proof::Proof, vp_commitment::ValidityPredicateCommitment, diff --git a/taiga_halo2/src/circuit/vp_examples/receiver_vp.rs b/taiga_halo2/src/circuit/vp_examples/receiver_vp.rs index 7990344d..e9585835 100644 --- a/taiga_halo2/src/circuit/vp_examples/receiver_vp.rs +++ b/taiga_halo2/src/circuit/vp_examples/receiver_vp.rs @@ -13,6 +13,7 @@ use crate::{ vp_examples::signature_verification::COMPRESSED_TOKEN_AUTH_VK, }, constant::{GENERATOR, NUM_NOTE, SETUP_PARAMS_MAP}, + error::TransactionError, note::{Note, RandomSeed}, note_encryption::{NoteCiphertext, NotePlaintext, SecretKey}, proof::Proof, diff --git a/taiga_halo2/src/circuit/vp_examples/signature_verification.rs b/taiga_halo2/src/circuit/vp_examples/signature_verification.rs index 004a9509..d4a4e766 100644 --- a/taiga_halo2/src/circuit/vp_examples/signature_verification.rs +++ b/taiga_halo2/src/circuit/vp_examples/signature_verification.rs @@ -11,6 +11,7 @@ use crate::{ }, }, constant::{TaigaFixedBasesFull, NUM_NOTE, SETUP_PARAMS_MAP}, + error::TransactionError, note::{Note, RandomSeed}, proof::Proof, utils::{mod_r_p, poseidon_hash_n}, diff --git a/taiga_halo2/src/circuit/vp_examples/token.rs b/taiga_halo2/src/circuit/vp_examples/token.rs index 59ec34bb..0fd7eb7b 100644 --- a/taiga_halo2/src/circuit/vp_examples/token.rs +++ b/taiga_halo2/src/circuit/vp_examples/token.rs @@ -20,6 +20,7 @@ use crate::{ VP_CIRCUIT_FIRST_DYNAMIC_VP_CM_2, VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_1, VP_CIRCUIT_SECOND_DYNAMIC_VP_CM_2, }, + error::TransactionError, note::{Note, NoteValidityPredicates, RandomSeed}, nullifier::Nullifier, proof::Proof, diff --git a/taiga_halo2/src/error.rs b/taiga_halo2/src/error.rs index e881e1fe..7c5b4c17 100644 --- a/taiga_halo2/src/error.rs +++ b/taiga_halo2/src/error.rs @@ -24,6 +24,8 @@ pub enum TransactionError { MissingTransparentResourceMerklePath, /// Shielded partial Tx binding signature r is missing MissingPartialTxBindingSignatureR, + /// ValidityPredicateRepresentation is not valid + InvalidValidityPredicateRepresentation, } impl Display for TransactionError { @@ -52,6 +54,9 @@ impl Display for TransactionError { MissingPartialTxBindingSignatureR => { f.write_str("Shielded partial Tx binding signature r is missing") } + InvalidValidityPredicateRepresentation => { + f.write_str("ValidityPredicateRepresentation is not valid, add borsh feature if using native vp examples ") + } } } } diff --git a/taiga_halo2/src/note.rs b/taiga_halo2/src/note.rs index 779820cf..b031dca1 100644 --- a/taiga_halo2/src/note.rs +++ b/taiga_halo2/src/note.rs @@ -121,6 +121,8 @@ pub struct NoteType { } #[derive(Copy, Clone, Debug, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] pub struct RandomSeed([u8; 32]); /// NoteValidityPredicates includes one application(static) VP and a few dynamic VPs. diff --git a/taiga_halo2/src/shielded_ptx.rs b/taiga_halo2/src/shielded_ptx.rs index 78ac3f86..71c16af5 100644 --- a/taiga_halo2/src/shielded_ptx.rs +++ b/taiga_halo2/src/shielded_ptx.rs @@ -21,7 +21,6 @@ use rustler::{Decoder, Encoder, Env, NifResult, NifStruct, Term}; #[cfg(feature = "serde")] use serde; -#[cfg(feature = "borsh")] use crate::circuit::vp_bytecode::ApplicationByteCode; #[cfg(feature = "borsh")] use borsh::{BorshDeserialize, BorshSerialize}; @@ -73,19 +72,18 @@ struct ShieldedPartialTransactionProxy { } impl ShieldedPartialTransaction { - #[cfg(feature = "borsh")] pub fn from_bytecode( actions: Vec, input_note_app: Vec, output_note_app: Vec, hints: Vec, mut rng: R, - ) -> Self { - let inputs: Vec = input_note_app + ) -> Result { + let inputs: Result, _> = input_note_app .into_iter() .map(|bytecode| bytecode.generate_proofs()) .collect(); - let outputs: Vec = output_note_app + let outputs: Result, _> = output_note_app .into_iter() .map(|bytecode| bytecode.generate_proofs()) .collect(); @@ -98,13 +96,13 @@ impl ShieldedPartialTransaction { }) .collect(); - Self { + Ok(Self { actions: actions.try_into().unwrap(), - inputs: inputs.try_into().unwrap(), - outputs: outputs.try_into().unwrap(), + inputs: inputs?.try_into().unwrap(), + outputs: outputs?.try_into().unwrap(), binding_sig_r: Some(rcv_sum), hints, - } + }) } pub fn build( diff --git a/taiga_halo2/src/taiga_api.rs b/taiga_halo2/src/taiga_api.rs index b8804e55..6e4084ad 100644 --- a/taiga_halo2/src/taiga_api.rs +++ b/taiga_halo2/src/taiga_api.rs @@ -172,7 +172,7 @@ pub fn create_shielded_partial_transaction( input_note_app: Vec, output_note_app: Vec, hints: Vec, -) -> ShieldedPartialTransaction { +) -> Result { let rng = OsRng; ShieldedPartialTransaction::from_bytecode(actions, input_note_app, output_note_app, hints, rng) } @@ -333,7 +333,8 @@ pub mod tests { vec![input_note_1_app, input_note_2_app], vec![output_note_1_app, output_note_2_app], vec![], - ); + ) + .unwrap(); let ptx_bytes = partial_transaction_serialize(&ptx).unwrap(); verify_shielded_partial_transaction(ptx_bytes).unwrap(); diff --git a/taiga_halo2/src/transaction.rs b/taiga_halo2/src/transaction.rs index 7f76c42e..09930150 100644 --- a/taiga_halo2/src/transaction.rs +++ b/taiga_halo2/src/transaction.rs @@ -325,6 +325,7 @@ impl TransparentPartialTxBundle { pub mod testing { use crate::shielded_ptx::testing::create_shielded_ptx; use crate::transaction::{ShieldedPartialTxBundle, TransparentPartialTxBundle}; + #[cfg(feature = "borsh")] use crate::transparent_ptx::testing::create_transparent_ptx; pub fn create_shielded_ptx_bundle(num: usize) -> ShieldedPartialTxBundle { @@ -336,6 +337,7 @@ pub mod testing { ShieldedPartialTxBundle::new(bundle) } + #[cfg(feature = "borsh")] pub fn create_transparent_ptx_bundle(num: usize) -> TransparentPartialTxBundle { let mut bundle = vec![]; for _ in 0..num { @@ -353,7 +355,12 @@ pub mod testing { let rng = OsRng; let shielded_ptx_bundle = create_shielded_ptx_bundle(1); + + #[cfg(feature = "borsh")] let transparent_ptx_bundle = create_transparent_ptx_bundle(1); + #[cfg(not(feature = "borsh"))] + let transparent_ptx_bundle = TransparentPartialTxBundle::default(); + let tx = Transaction::build(rng, shielded_ptx_bundle, transparent_ptx_bundle).unwrap(); let _ret = tx.execute().unwrap(); diff --git a/taiga_halo2/src/transparent_ptx.rs b/taiga_halo2/src/transparent_ptx.rs index 026daf6e..b298766d 100644 --- a/taiga_halo2/src/transparent_ptx.rs +++ b/taiga_halo2/src/transparent_ptx.rs @@ -1,42 +1,41 @@ use crate::{ - error::TransactionError, - executable::Executable, - merkle_tree::{Anchor, MerklePath}, - note::{Note, NoteCommitment}, - nullifier::{Nullifier, NullifierKeyContainer}, - value_commitment::ValueCommitment, + action::ActionInfo, circuit::vp_bytecode::ApplicationByteCode, constant::NUM_NOTE, + error::TransactionError, executable::Executable, merkle_tree::Anchor, note::NoteCommitment, + nullifier::Nullifier, value_commitment::ValueCommitment, }; +use pasta_curves::pallas; #[cfg(feature = "serde")] use serde; #[cfg(feature = "borsh")] use borsh::{BorshDeserialize, BorshSerialize}; -use rand::RngCore; #[derive(Debug, Clone)] #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TransparentPartialTransaction { - inputs: Vec, - outputs: Vec, + actions: Vec, + input_note_app: Vec, + output_note_app: Vec, hints: Vec, } impl TransparentPartialTransaction { - pub fn new( - inputs: Vec, - mut outputs: Vec, + pub fn new( + actions: Vec, + input_note_app: Vec, + output_note_app: Vec, hints: Vec, - mut rng: R, ) -> Self { - outputs - .iter_mut() - .zip(inputs.iter()) - .for_each(|(output, input)| output.note.set_rho(&input.note, &mut rng)); + assert_eq!(actions.len(), NUM_NOTE); + assert_eq!(input_note_app.len(), NUM_NOTE); + assert_eq!(output_note_app.len(), NUM_NOTE); + Self { - inputs, - outputs, + actions, + input_note_app, + output_note_app, hints, } } @@ -44,129 +43,152 @@ impl TransparentPartialTransaction { impl Executable for TransparentPartialTransaction { fn execute(&self) -> Result<(), TransactionError> { - assert_eq!(self.inputs.len(), self.outputs.len()); - for input in self.inputs.iter() { - // check nullifer_key is provided - if let NullifierKeyContainer::Commitment(_) = input.note.nk_container { - return Err(TransactionError::MissingTransparentResourceNullifierKey); + // check VPs, nullifiers, and note commitments + let action_nfs = self.get_nullifiers(); + let action_cms = self.get_output_cms(); + for (vp, nf) in self.input_note_app.iter().zip(action_nfs.iter()) { + let owned_note_id = vp.verify_transparently(&action_nfs, &action_cms)?; + // Check all notes are checked + if owned_note_id != nf.inner() { + return Err(TransactionError::InconsistentOwnedNotePubID); } + } - // check merkle_path is provided - if input.merkle_path.is_none() && input.note.is_merkle_checked { - return Err(TransactionError::MissingTransparentResourceMerklePath); + for (vp, cm) in self.output_note_app.iter().zip(action_cms.iter()) { + let owned_note_id = vp.verify_transparently(&action_nfs, &action_cms)?; + // Check all notes are checked + if owned_note_id != cm.inner() { + return Err(TransactionError::InconsistentOwnedNotePubID); } } - // TODO: figure out how transparent ptx executes - // VP should be checked here - Ok(()) } + // get nullifiers from actions fn get_nullifiers(&self) -> Vec { - self.inputs + self.actions .iter() - .map(|resource| resource.note.get_nf().unwrap()) + .map(|action| action.get_input_note_nullifer()) .collect() } + // get output cms from actions fn get_output_cms(&self) -> Vec { - self.outputs + self.actions .iter() - .map(|resource| resource.note.commitment()) + .map(|action| action.get_output_note_cm()) .collect() } fn get_value_commitments(&self) -> Vec { - vec![ValueCommitment::from_tranparent_resources( - &self.inputs, - &self.outputs, - )] + self.actions + .iter() + .map(|action| action.get_value_commitment(&pallas::Scalar::zero())) + .collect() } fn get_anchors(&self) -> Vec { - let mut anchors = Vec::new(); - for input in self.inputs.iter() { - if input.note.is_merkle_checked { - if let Some(path) = &input.merkle_path { - anchors.push(input.note.calculate_root(path)); - } - } - } - anchors + // TODO: We have easier way to check the anchor in transparent scenario, but keep consistent with sheilded right now. + // TODO: we can skip the root if the is_merkle_checked flag is false? + self.actions + .iter() + .map(|action| action.calculate_root()) + .collect() } } -#[derive(Debug, Clone)] -#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct InputResource { - pub note: Note, - // Only normal notes need the path, while dummy(intent and padding) notes don't need the path to calculate the anchor. - pub merkle_path: Option, - // TODO: figure out transparent vp reprentation and how to execute it. - // pub static_vp: - // pub dynamic_vps: -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct OutputResource { - pub note: Note, - // TODO: figure out transparent vp reprentation and how to execute it. - // pub static_vp: - // pub dynamic_vps: -} - #[cfg(test)] +#[cfg(feature = "borsh")] pub mod testing { use crate::{ + circuit::vp_examples::TrivialValidityPredicateCircuit, constant::TAIGA_COMMITMENT_TREE_DEPTH, merkle_tree::MerklePath, note::tests::random_note, transparent_ptx::*, }; use rand::rngs::OsRng; - // No transparent vp included pub fn create_transparent_ptx() -> TransparentPartialTransaction { let mut rng = OsRng; - let input_resource_1 = { - let note = random_note(&mut rng); - let merkle_path = MerklePath::random(&mut rng, TAIGA_COMMITMENT_TREE_DEPTH); - InputResource { - note, - merkle_path: Some(merkle_path), - } - }; - let output_resource_1 = { + // construct notes + let input_note_1 = random_note(&mut rng); + let mut output_note_1 = { let mut note = random_note(&mut rng); - // Adjust the random note to keep the balance - note.note_type = input_resource_1.note.note_type; - note.value = input_resource_1.note.value; - OutputResource { note } + note.note_type = input_note_1.note_type; + note.value = input_note_1.value; + note }; + let merkle_path_1 = MerklePath::random(&mut rng, TAIGA_COMMITMENT_TREE_DEPTH); + let action_1 = ActionInfo::new( + input_note_1, + merkle_path_1, + None, + &mut output_note_1, + &mut rng, + ); - let input_resource_2 = { + let input_note_2 = random_note(&mut rng); + let mut output_note_2 = { let mut note = random_note(&mut rng); - note.is_merkle_checked = false; - InputResource { - note, - merkle_path: None, - } + note.note_type = input_note_2.note_type; + note.value = input_note_2.value; + note }; - let output_resource_2 = { - let mut note = random_note(&mut rng); - // Adjust the random note to keep the balance - note.note_type = input_resource_2.note.note_type; - note.value = input_resource_2.note.value; - OutputResource { note } + let merkle_path_2 = MerklePath::random(&mut rng, TAIGA_COMMITMENT_TREE_DEPTH); + let action_2 = ActionInfo::new( + input_note_2, + merkle_path_2, + None, + &mut output_note_2, + &mut rng, + ); + + // construct applications + let input_note_1_app = { + let app_vp = TrivialValidityPredicateCircuit::new( + input_note_1.get_nf().unwrap().inner(), + [input_note_1, input_note_2], + [output_note_1, output_note_2], + ); + + ApplicationByteCode::new(app_vp.to_bytecode(), vec![]) + }; + + let input_note_2_app = { + let app_vp = TrivialValidityPredicateCircuit::new( + input_note_2.get_nf().unwrap().inner(), + [input_note_1, input_note_2], + [output_note_1, output_note_2], + ); + + ApplicationByteCode::new(app_vp.to_bytecode(), vec![]) + }; + + let output_note_1_app = { + let app_vp = TrivialValidityPredicateCircuit::new( + output_note_1.commitment().inner(), + [input_note_1, input_note_2], + [output_note_1, output_note_2], + ); + + ApplicationByteCode::new(app_vp.to_bytecode(), vec![]) + }; + + let output_note_2_app = { + let app_vp = TrivialValidityPredicateCircuit::new( + output_note_2.commitment().inner(), + [input_note_1, input_note_2], + [output_note_1, output_note_2], + ); + + ApplicationByteCode::new(app_vp.to_bytecode(), vec![]) }; TransparentPartialTransaction::new( - vec![input_resource_1, input_resource_2], - vec![output_resource_1, output_resource_2], + vec![action_1, action_2], + vec![input_note_1_app, input_note_2_app], + vec![output_note_1_app, output_note_2_app], vec![], - &mut rng, ) } } diff --git a/taiga_halo2/src/value_commitment.rs b/taiga_halo2/src/value_commitment.rs index f7a3aba7..cdbcea3d 100644 --- a/taiga_halo2/src/value_commitment.rs +++ b/taiga_halo2/src/value_commitment.rs @@ -1,6 +1,5 @@ use crate::constant::NOTE_COMMITMENT_R_GENERATOR; use crate::note::Note; -use crate::transparent_ptx::{InputResource, OutputResource}; use halo2_proofs::arithmetic::CurveAffine; use pasta_curves::group::cofactor::CofactorCurveAffine; use pasta_curves::group::{Curve, Group, GroupEncoding}; @@ -18,7 +17,7 @@ use serde; pub struct ValueCommitment(pallas::Point); impl ValueCommitment { - pub fn new(input_note: &Note, output_note: &Note, blind_r: &pallas::Scalar) -> Self { + pub fn commit(input_note: &Note, output_note: &Note, blind_r: &pallas::Scalar) -> Self { let base_input = input_note.get_note_type(); let base_output = output_note.get_note_type(); ValueCommitment( @@ -28,24 +27,6 @@ impl ValueCommitment { ) } - // The transparent resources are open, so no blind_r is needed in transparent value commitment - pub fn from_tranparent_resources( - input_notes: &[InputResource], - output_notes: &[OutputResource], - ) -> Self { - let base_inputs = input_notes - .iter() - .fold(pallas::Point::identity(), |acc, resource| { - acc + resource.note.get_note_type() * pallas::Scalar::from(resource.note.value) - }); - let base_outputs = output_notes - .iter() - .fold(pallas::Point::identity(), |acc, resource| { - acc + resource.note.get_note_type() * pallas::Scalar::from(resource.note.value) - }); - ValueCommitment(base_inputs - base_outputs) - } - pub fn get_x(&self) -> pallas::Base { if self.0 == pallas::Point::identity() { pallas::Base::zero()