From fe2e9208ca081fb1628685b1eebacb4a2d2f0cd8 Mon Sep 17 00:00:00 2001 From: Xuyang Song Date: Wed, 11 Oct 2023 04:50:31 -0500 Subject: [PATCH] taiga data format and api (#222) * basic taiga data format and layout * simplified Taiga APIs for external * add hints to the partial transaction * add a new partial transaction creation API to support concrete circuit presentation instead of dynamic one. --- .../cascaded_partial_transactions.rs | 4 +- .../partial_fulfillment_token_swap.rs | 4 +- taiga_halo2/examples/tx_examples/token.rs | 1 + .../tx_examples/token_swap_with_intent.rs | 4 +- .../tx_examples/token_swap_without_intent.rs | 2 +- taiga_halo2/src/circuit/mod.rs | 2 + taiga_halo2/src/circuit/vp_bytecode.rs | 84 ++++ taiga_halo2/src/circuit/vp_examples.rs | 64 +++ taiga_halo2/src/error.rs | 9 + taiga_halo2/src/lib.rs | 1 + taiga_halo2/src/merkle_tree.rs | 20 + taiga_halo2/src/shielded_ptx.rs | 53 ++- taiga_halo2/src/taiga_api.rs | 368 ++++++++++++++++++ taiga_halo2/src/transaction.rs | 8 +- 14 files changed, 616 insertions(+), 8 deletions(-) create mode 100644 taiga_halo2/src/circuit/vp_bytecode.rs create mode 100644 taiga_halo2/src/taiga_api.rs diff --git a/taiga_halo2/examples/tx_examples/cascaded_partial_transactions.rs b/taiga_halo2/examples/tx_examples/cascaded_partial_transactions.rs index 30063018..9b0bcb2b 100644 --- a/taiga_halo2/examples/tx_examples/cascaded_partial_transactions.rs +++ b/taiga_halo2/examples/tx_examples/cascaded_partial_transactions.rs @@ -124,6 +124,7 @@ pub fn create_transaction(mut rng: R) -> Transaction { ShieldedPartialTransaction::build( [input_note_1_proving_info, input_note_2_proving_info], [output_note_1_proving_info, intent_note_proving_info], + vec![], &mut rng, ) }; @@ -183,12 +184,13 @@ pub fn create_transaction(mut rng: R) -> Transaction { ShieldedPartialTransaction::build( [intent_note_proving_info, input_note_3_proving_info], [output_note_2_proving_info, output_note_3_proving_info], + vec![], &mut rng, ) }; // Create the final transaction - let shielded_tx_bundle = ShieldedPartialTxBundle::build(vec![ptx_1, ptx_2]); + let shielded_tx_bundle = ShieldedPartialTxBundle::new(vec![ptx_1, ptx_2]); let transparent_ptx_bundle = TransparentPartialTxBundle::default(); Transaction::build(&mut rng, shielded_tx_bundle, transparent_ptx_bundle) } 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 d34818c1..461f9f18 100644 --- a/taiga_halo2/examples/tx_examples/partial_fulfillment_token_swap.rs +++ b/taiga_halo2/examples/tx_examples/partial_fulfillment_token_swap.rs @@ -120,6 +120,7 @@ pub fn create_token_intent_ptx( let ptx = ShieldedPartialTransaction::build( [input_note_proving_info, padding_input_note_proving_info], [intent_note_proving_info, padding_output_note_proving_info], + vec![], &mut rng, ); @@ -243,6 +244,7 @@ pub fn consume_token_intent_ptx( ShieldedPartialTransaction::build( [intent_note_proving_info, padding_input_note_proving_info], [bought_note_proving_info, returned_note_proving_info], + vec![], &mut rng, ) } @@ -298,7 +300,7 @@ pub fn create_token_swap_transaction(mut rng: R) -> Tran ); // Solver creates the final transaction - let shielded_tx_bundle = ShieldedPartialTxBundle::build(vec![alice_ptx, bob_ptx, solver_ptx]); + let shielded_tx_bundle = ShieldedPartialTxBundle::new(vec![alice_ptx, bob_ptx, solver_ptx]); let transparent_ptx_bundle = TransparentPartialTxBundle::default(); Transaction::build(&mut rng, shielded_tx_bundle, transparent_ptx_bundle) } diff --git a/taiga_halo2/examples/tx_examples/token.rs b/taiga_halo2/examples/tx_examples/token.rs index 44e6ba16..64135ccf 100644 --- a/taiga_halo2/examples/tx_examples/token.rs +++ b/taiga_halo2/examples/tx_examples/token.rs @@ -134,6 +134,7 @@ pub fn create_token_swap_ptx( ShieldedPartialTransaction::build( [input_note_proving_info, padding_input_note_proving_info], [output_note_proving_info, padding_output_note_proving_info], + vec![], &mut rng, ) } diff --git a/taiga_halo2/examples/tx_examples/token_swap_with_intent.rs b/taiga_halo2/examples/tx_examples/token_swap_with_intent.rs index d93e4843..bda7acd9 100644 --- a/taiga_halo2/examples/tx_examples/token_swap_with_intent.rs +++ b/taiga_halo2/examples/tx_examples/token_swap_with_intent.rs @@ -127,6 +127,7 @@ pub fn create_token_intent_ptx( let ptx = ShieldedPartialTransaction::build( [input_note_proving_info, padding_input_note_proving_info], [intent_note_proving_info, padding_output_note_proving_info], + vec![], &mut rng, ); @@ -239,6 +240,7 @@ pub fn consume_token_intent_ptx( ShieldedPartialTransaction::build( [intent_note_proving_info, padding_input_note_proving_info], [output_note_proving_info, padding_output_note_proving_info], + vec![], &mut rng, ) } @@ -302,7 +304,7 @@ pub fn create_token_swap_intent_transaction(mut rng: R) ); // Solver creates the final transaction - let shielded_tx_bundle = ShieldedPartialTxBundle::build(vec![alice_ptx, bob_ptx, solver_ptx]); + let shielded_tx_bundle = ShieldedPartialTxBundle::new(vec![alice_ptx, bob_ptx, solver_ptx]); let transparent_ptx_bundle = TransparentPartialTxBundle::default(); Transaction::build(&mut rng, shielded_tx_bundle, transparent_ptx_bundle) } diff --git a/taiga_halo2/examples/tx_examples/token_swap_without_intent.rs b/taiga_halo2/examples/tx_examples/token_swap_without_intent.rs index 0c3cd672..2e35d258 100644 --- a/taiga_halo2/examples/tx_examples/token_swap_without_intent.rs +++ b/taiga_halo2/examples/tx_examples/token_swap_without_intent.rs @@ -68,7 +68,7 @@ pub fn create_token_swap_transaction(mut rng: R) -> Tran ); // Solver creates the final transaction - let shielded_tx_bundle = ShieldedPartialTxBundle::build(vec![alice_ptx, bob_ptx, carol_ptx]); + let shielded_tx_bundle = ShieldedPartialTxBundle::new(vec![alice_ptx, bob_ptx, carol_ptx]); let transparent_ptx_bundle = TransparentPartialTxBundle::default(); Transaction::build(&mut rng, shielded_tx_bundle, transparent_ptx_bundle) } diff --git a/taiga_halo2/src/circuit/mod.rs b/taiga_halo2/src/circuit/mod.rs index bf45e402..83e4c5bd 100644 --- a/taiga_halo2/src/circuit/mod.rs +++ b/taiga_halo2/src/circuit/mod.rs @@ -10,4 +10,6 @@ pub mod curve; pub mod hash_to_curve; 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 new file mode 100644 index 00000000..afb39866 --- /dev/null +++ b/taiga_halo2/src/circuit/vp_bytecode.rs @@ -0,0 +1,84 @@ +use crate::circuit::{ + vp_circuit::{VPVerifyingInfo, ValidityPredicateVerifyingInfo, VampIRValidityPredicateCircuit}, + vp_examples::TrivialValidityPredicateCircuit, +}; +use crate::shielded_ptx::NoteVPVerifyingInfoSet; +use borsh::{BorshDeserialize, BorshSerialize}; +#[cfg(feature = "serde")] +use serde; +use std::path::PathBuf; + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ValidityPredicateRepresentation { + // vampir has a unified circuit representation. + VampIR(Vec), + // Native halo2 circuits don't have a unified representatioin, enumerate the vp circuit examples for the moment. + // TODO: figure out if we can have a unified circuit presentation. In theory, it's possible to separate the circuit system and proving system. + Trivial, + // TODO: add other vp types here if needed +} + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ValidityPredicateByteCode { + circuit: ValidityPredicateRepresentation, + inputs: Vec, +} + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ApplicationByteCode { + app_vp_bytecode: ValidityPredicateByteCode, + dynamic_vp_bytecode: Vec, +} + +impl ValidityPredicateByteCode { + pub fn new(circuit: ValidityPredicateRepresentation, inputs: Vec) -> Self { + Self { circuit, inputs } + } + + pub fn generate_proof(self) -> VPVerifyingInfo { + 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.get_verifying_info() + } + ValidityPredicateRepresentation::Trivial => { + let vp = TrivialValidityPredicateCircuit::from_bytes(self.inputs); + vp.get_verifying_info() + } + } + } +} + +impl ApplicationByteCode { + pub fn new( + app_vp_bytecode: ValidityPredicateByteCode, + dynamic_vp_bytecode: Vec, + ) -> Self { + Self { + app_vp_bytecode, + dynamic_vp_bytecode, + } + } + + pub fn generate_proofs(self) -> NoteVPVerifyingInfoSet { + let app_vp_verifying_info = self.app_vp_bytecode.generate_proof(); + + let app_dynamic_vp_verifying_info = self + .dynamic_vp_bytecode + .into_iter() + .map(|bytecode| bytecode.generate_proof()) + .collect(); + NoteVPVerifyingInfoSet::new(app_vp_verifying_info, app_dynamic_vp_verifying_info) + } +} diff --git a/taiga_halo2/src/circuit/vp_examples.rs b/taiga_halo2/src/circuit/vp_examples.rs index 1b9dbaaa..b63dadcb 100644 --- a/taiga_halo2/src/circuit/vp_examples.rs +++ b/taiga_halo2/src/circuit/vp_examples.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "borsh")] +use crate::circuit::vp_bytecode::{ValidityPredicateByteCode, ValidityPredicateRepresentation}; use crate::{ circuit::vp_circuit::{ VPVerifyingInfo, ValidityPredicateCircuit, ValidityPredicateConfig, @@ -9,6 +11,8 @@ use crate::{ vp_commitment::ValidityPredicateCommitment, vp_vk::ValidityPredicateVerifyingKey, }; +#[cfg(feature = "borsh")] +use borsh::{BorshDeserialize, BorshSerialize}; use halo2_proofs::plonk::{keygen_pk, keygen_vk, ProvingKey}; use halo2_proofs::{ circuit::{floor_planner, Layouter}, @@ -82,6 +86,24 @@ impl TrivialValidityPredicateCircuit { } } + // Only for test + #[cfg(feature = "borsh")] + pub fn to_bytecode(&self) -> ValidityPredicateByteCode { + ValidityPredicateByteCode::new(ValidityPredicateRepresentation::Trivial, self.to_bytes()) + } + + // Only for test + #[cfg(feature = "borsh")] + pub fn to_bytes(&self) -> Vec { + borsh::to_vec(&self).unwrap() + } + + // Only for test + #[cfg(feature = "borsh")] + pub fn from_bytes(bytes: Vec) -> Self { + BorshDeserialize::deserialize(&mut bytes.as_ref()).unwrap() + } + fn to_proxy(&self) -> TrivialValidtyPredicateCircuitProxy { TrivialValidtyPredicateCircuitProxy { owned_note_pub_id: self.owned_note_pub_id, @@ -91,6 +113,48 @@ impl TrivialValidityPredicateCircuit { } } +#[cfg(feature = "borsh")] +impl BorshSerialize for TrivialValidityPredicateCircuit { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + use ff::PrimeField; + writer.write_all(&self.owned_note_pub_id.to_repr())?; + for input in self.input_notes.iter() { + input.serialize(writer)?; + } + + for output in self.output_notes.iter() { + output.serialize(writer)?; + } + Ok(()) + } +} + +#[cfg(feature = "borsh")] +impl BorshDeserialize for TrivialValidityPredicateCircuit { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + use ff::PrimeField; + let owned_note_pub_id_bytes = <[u8; 32]>::deserialize_reader(reader)?; + let owned_note_pub_id = Option::from(pallas::Base::from_repr(owned_note_pub_id_bytes)) + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "owned_note_pub_id not in field", + ) + })?; + let input_notes: Vec<_> = (0..NUM_NOTE) + .map(|_| Note::deserialize_reader(reader)) + .collect::>()?; + let output_notes: Vec<_> = (0..NUM_NOTE) + .map(|_| Note::deserialize_reader(reader)) + .collect::>()?; + Ok(Self { + owned_note_pub_id, + input_notes: input_notes.try_into().unwrap(), + output_notes: output_notes.try_into().unwrap(), + }) + } +} + impl TrivialValidtyPredicateCircuitProxy { fn to_concrete(&self) -> Option { let input_notes = self.input_notes.clone().try_into().ok()?; diff --git a/taiga_halo2/src/error.rs b/taiga_halo2/src/error.rs index 6e479874..ab57bb85 100644 --- a/taiga_halo2/src/error.rs +++ b/taiga_halo2/src/error.rs @@ -16,6 +16,8 @@ pub enum TransactionError { InconsistentOutputNoteCommitment, /// Owned note public id is not consistent between the action and the vp. InconsistentOwnedNotePubID, + /// IO error + IoError(std::io::Error), } impl Display for TransactionError { @@ -34,6 +36,7 @@ impl Display for TransactionError { InconsistentOwnedNotePubID => { f.write_str("Owned note public id is not consistent between the action and the vp") } + IoError(e) => f.write_str(&format!("IoError error: {e}")), } } } @@ -43,3 +46,9 @@ impl From for TransactionError { TransactionError::Proof(e) } } + +impl From for TransactionError { + fn from(e: std::io::Error) -> Self { + TransactionError::IoError(e) + } +} diff --git a/taiga_halo2/src/lib.rs b/taiga_halo2/src/lib.rs index 2164730c..166bf969 100644 --- a/taiga_halo2/src/lib.rs +++ b/taiga_halo2/src/lib.rs @@ -13,6 +13,7 @@ pub mod note_encryption; pub mod nullifier; pub mod proof; pub mod shielded_ptx; +pub mod taiga_api; pub mod transaction; pub mod transparent_ptx; pub mod utils; diff --git a/taiga_halo2/src/merkle_tree.rs b/taiga_halo2/src/merkle_tree.rs index bf6d72eb..a23d1ba4 100644 --- a/taiga_halo2/src/merkle_tree.rs +++ b/taiga_halo2/src/merkle_tree.rs @@ -57,6 +57,26 @@ impl Hash for Anchor { } } +#[cfg(feature = "borsh")] +impl BorshSerialize for Anchor { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + writer.write_all(&self.0.to_repr())?; + Ok(()) + } +} + +#[cfg(feature = "borsh")] +impl BorshDeserialize for Anchor { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + let mut repr = [0u8; 32]; + reader.read_exact(&mut repr)?; + let value = Option::from(pallas::Base::from_repr(repr)).ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "Anchor not in field") + })?; + Ok(Self(value)) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Copy, Hash, Default)] #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/taiga_halo2/src/shielded_ptx.rs b/taiga_halo2/src/shielded_ptx.rs index 8e0d53a3..1027b01d 100644 --- a/taiga_halo2/src/shielded_ptx.rs +++ b/taiga_halo2/src/shielded_ptx.rs @@ -21,6 +21,8 @@ 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}; #[cfg(feature = "borsh")] @@ -33,6 +35,7 @@ pub struct ShieldedPartialTransaction { inputs: [NoteVPVerifyingInfoSet; NUM_NOTE], outputs: [NoteVPVerifyingInfoSet; NUM_NOTE], binding_sig_r: pallas::Scalar, + hints: Vec, } #[derive(Debug, Clone)] @@ -66,12 +69,48 @@ struct ShieldedPartialTransactionProxy { inputs: Vec, outputs: Vec, binding_sig_r: pallas::Scalar, + hints: Vec, } 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 + .into_iter() + .map(|bytecode| bytecode.generate_proofs()) + .collect(); + let outputs: Vec = output_note_app + .into_iter() + .map(|bytecode| bytecode.generate_proofs()) + .collect(); + let mut rcv_sum = pallas::Scalar::zero(); + let actions: Vec = actions + .into_iter() + .map(|action_info| { + rcv_sum += action_info.get_rcv(); + ActionVerifyingInfo::create(action_info, &mut rng).unwrap() + }) + .collect(); + + Self { + actions: actions.try_into().unwrap(), + inputs: inputs.try_into().unwrap(), + outputs: outputs.try_into().unwrap(), + binding_sig_r: rcv_sum, + hints, + } + } + pub fn build( input_info: [InputNoteProvingInfo; NUM_NOTE], output_info: [OutputNoteProvingInfo; NUM_NOTE], + hints: Vec, mut rng: R, ) -> Self { let inputs: Vec = input_info @@ -108,11 +147,12 @@ impl ShieldedPartialTransaction { inputs: inputs.try_into().unwrap(), outputs: outputs.try_into().unwrap(), binding_sig_r: rcv_sum, + hints, } } // verify zk proof - fn verify_proof(&self) -> Result<(), Error> { + pub fn verify_proof(&self) -> Result<(), TransactionError> { // Verify action proofs for verifying_info in self.actions.iter() { verifying_info.verify()?; @@ -201,12 +241,17 @@ impl ShieldedPartialTransaction { inputs: self.inputs.to_vec(), outputs: self.outputs.to_vec(), binding_sig_r: self.binding_sig_r, + hints: self.hints.clone(), } } pub fn get_binding_sig_r(&self) -> pallas::Scalar { self.binding_sig_r } + + pub fn get_hints(&self) -> Vec { + self.hints.clone() + } } impl ShieldedPartialTransactionProxy { @@ -219,6 +264,7 @@ impl ShieldedPartialTransactionProxy { inputs, outputs, binding_sig_r: self.binding_sig_r, + hints: self.hints.clone(), }) } } @@ -277,6 +323,8 @@ impl BorshSerialize for ShieldedPartialTransaction { writer.write_all(&self.binding_sig_r.to_repr())?; + self.hints.serialize(writer)?; + Ok(()) } } @@ -301,11 +349,13 @@ impl BorshDeserialize for ShieldedPartialTransaction { "binding_sig_r not in field", ) })?; + let hints = Vec::::deserialize_reader(reader)?; Ok(ShieldedPartialTransaction { actions: actions.try_into().unwrap(), inputs: inputs.try_into().unwrap(), outputs: outputs.try_into().unwrap(), binding_sig_r, + hints, }) } } @@ -572,6 +622,7 @@ pub mod testing { ShieldedPartialTransaction::build( [input_note_proving_info_1, input_note_proving_info_2], [output_note_proving_info_1, output_note_proving_info_2], + vec![], &mut rng, ) } diff --git a/taiga_halo2/src/taiga_api.rs b/taiga_halo2/src/taiga_api.rs new file mode 100644 index 00000000..3597a75c --- /dev/null +++ b/taiga_halo2/src/taiga_api.rs @@ -0,0 +1,368 @@ +#[cfg(feature = "borsh")] +use crate::{ + action::ActionInfo, + circuit::vp_bytecode::ApplicationByteCode, + error::TransactionError, + transaction::{ShieldedResult, TransparentResult}, +}; +use crate::{ + note::{Note, RandomSeed}, + nullifier::{Nullifier, NullifierKeyContainer}, + shielded_ptx::ShieldedPartialTransaction, + transaction::{ShieldedPartialTxBundle, Transaction, TransparentPartialTxBundle}, +}; +use pasta_curves::pallas; +use rand::rngs::OsRng; + +pub const NOTE_SIZE: usize = 234; + +#[cfg(feature = "borsh")] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Create a note +/// app_vk is the compressed verifying key of application(static) VP +/// app_data_static is the encoded data that is defined in application vp +/// app_data_dynamic is the data defined in application vp and will NOT be used to derive type +/// value is the quantity of notes +/// nk is the nullifier key +/// rho is the old nullifier +/// is_merkle_checked is true for normal notes, false for intent(ephemeral) notes +/// +/// In practice, input notes are fetched and decrypted from blockchain storage. +/// The create_input_note API is only for test. +pub fn create_input_note( + app_vk: pallas::Base, + app_data_static: pallas::Base, + app_data_dynamic: pallas::Base, + value: u64, + nk: pallas::Base, + is_merkle_checked: bool, +) -> Note { + let rng = OsRng; + let nk_container = NullifierKeyContainer::from_key(nk); + let rho = Nullifier::default(); + let rseed = RandomSeed::random(rng); + Note::new( + app_vk, + app_data_static, + app_data_dynamic, + value, + nk_container, + rho, + is_merkle_checked, + rseed, + ) +} + +/// +pub fn create_output_note( + app_vk: pallas::Base, + app_data_static: pallas::Base, + app_data_dynamic: pallas::Base, + value: u64, + // The owner of output note has the nullifer key and exposes the nullifier_key commitment to output creator. + nk_com: pallas::Base, + // TODO: remove the input_nf, and get it at run time. + input_nf: Nullifier, + is_merkle_checked: bool, +) -> Note { + let rng = OsRng; + let nk_container = NullifierKeyContainer::from_commitment(nk_com); + let rseed = RandomSeed::random(rng); + Note::new( + app_vk, + app_data_static, + app_data_dynamic, + value, + nk_container, + input_nf, + is_merkle_checked, + rseed, + ) +} + +/// Note borsh serialization +/// +/// Note size: 234 bytes +/// +/// Note layout: +/// | Parameters | type |size(bytes)| +/// | - | - | - | +/// | app_vk | pallas::Base | 32 | +/// | app_data_static | pallas::Base | 32 | +/// | app_data_dynamic | pallas::Base | 32 | +/// | value(quantity) | u64 | 8 | +/// | nk_container type | u8 | 1 | +/// | nk_com/nk | pallas::Base | 32 | +/// | rho | pallas::Base | 32 | +/// | psi | pallas::Base | 32 | +/// | rcm | pallas::Base | 32 | +/// | is_merkle_checked | u8 | 1 | +#[cfg(feature = "borsh")] +pub fn note_serialize(note: &Note) -> std::io::Result> { + let mut result = Vec::with_capacity(NOTE_SIZE); + note.serialize(&mut result)?; + Ok(result) +} + +/// Note borsh deserialization +#[cfg(feature = "borsh")] +pub fn note_deserialize(bytes: Vec) -> std::io::Result { + if bytes.len() != NOTE_SIZE { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "incorrect note size", + )); + } + BorshDeserialize::deserialize(&mut bytes.as_ref()) +} + +/// Shielded Partial Transaction borsh serialization +/// +/// Shielded Partial Transaction layout: +/// | Parameters | type | size(bytes) | +/// | - | - | - | +/// | 2 action proofs | ActionVerifyingInfo | 4676 * 2 | +/// | input1 static vp proof | VPVerifyingInfo | 158216 | +/// | input1 dynamic vp num(by borsh) | u32 | 4 | +/// | input1 dynamic vp proof | VPVerifyingInfo | 158216 * num | +/// | input2 static vp proof | VPVerifyingInfo | 158216 | +/// | input2 dynamic vp num(by borsh) | u32 | 4 | +/// | input2 dynamic vp proof | VPVerifyingInfo | 158216 * num | +/// | output1 static vp proof | VPVerifyingInfo | 158216 | +/// | output1 dynamic vp num(by borsh) | u32 | 4 | +/// | output1 dynamic vp proofs | VPVerifyingInfo | 158216 * num | +/// | output2 static vp proof | VPVerifyingInfo | 158216 | +/// | output2 dynamic vp num(by borsh) | u32 | 4 | +/// | output2 dynamic vp proofs | VPVerifyingInfo | 158216 * num | +/// | binding_sig_r | pallas::Scalar | 32 | +/// | hints | Vec | - | +/// +/// Note: Ultimately, vp proofs won't go to the ptx. It's verifier proofs instead. +/// The verifier proof may have a much smaller size since the verifier verifying-key +/// is a constant and can be cached. +#[cfg(feature = "borsh")] +pub fn partial_transaction_serialize(ptx: &ShieldedPartialTransaction) -> std::io::Result> { + borsh::to_vec(&ptx) +} + +/// Shielded Partial Transaction borsh deserialization +#[cfg(feature = "borsh")] +pub fn partial_transaction_deserialize( + bytes: Vec, +) -> std::io::Result { + BorshDeserialize::deserialize(&mut bytes.as_ref()) +} + +/// Transaction borsh serialization +/// +/// Transaction layout: +/// | Parameters | type | size(bytes)| +/// | - | - | - | +/// | shielded_ptx_bundle(a list of shielded ptx) | ShieldedPartialTxBundle | - | +/// | TODO: transparent_ptx_bundle(a list of transparent ptx) | TransparentPartialTxBundle | - | +/// | signature | BindingSignature | 32 | +/// +#[cfg(feature = "borsh")] +pub fn transaction_serialize(tx: &Transaction) -> std::io::Result> { + borsh::to_vec(&tx) +} + +/// Transaction borsh deserialization +/// +#[cfg(feature = "borsh")] +pub fn transaction_deserialize(bytes: Vec) -> std::io::Result { + BorshDeserialize::deserialize(&mut bytes.as_ref()) +} + +/// Create a shielded partial transaction from vp bytecode +#[cfg(feature = "borsh")] +pub fn create_shielded_partial_transaction( + actions: Vec, + input_note_app: Vec, + output_note_app: Vec, + hints: Vec, +) -> ShieldedPartialTransaction { + let rng = OsRng; + ShieldedPartialTransaction::from_bytecode(actions, input_note_app, output_note_app, hints, rng) +} + +/// Create a transaction from partial transactions +/// +pub fn create_transaction( + shielded_ptxs: Vec, + // TODO: add transparent_ptxs + // transparent_ptxs: Vec, +) -> Transaction { + let rng = OsRng; + let shielded_ptx_bundle = ShieldedPartialTxBundle::new(shielded_ptxs); + // empty transparent_ptx_bundle + let transparent_ptx_bundle = TransparentPartialTxBundle::default(); + Transaction::build(rng, shielded_ptx_bundle, transparent_ptx_bundle) +} + +/// Verify a transaction and return the results +/// +/// ShieldedResult layout: +/// | Parameters | type | size(bytes)| +/// | - | - | - | +/// | anchor num | u32 | 4 | +/// | anchors | pallas::Base | 32 * num | +/// | nullifier num | u32 | 4 | +/// | nullifiers | pallas::Base | 32 * num | +/// | output cm num | u32 | 4 | +/// | output cms | pallas::Base | 32 * num | +/// +/// Note: TransparentResult is empty +/// +#[cfg(feature = "borsh")] +pub fn verify_transaction( + tx_bytes: Vec, +) -> Result<(ShieldedResult, TransparentResult), TransactionError> { + // Decode the tx + let tx = transaction_deserialize(tx_bytes)?; + + // Verify the tx + tx.execute() +} + +/// Verify a shielded transaction +/// +#[cfg(feature = "borsh")] +pub fn verify_shielded_partial_transaction(ptx_bytes: Vec) -> Result<(), TransactionError> { + // Decode the ptx + let ptx = partial_transaction_deserialize(ptx_bytes)?; + + // Verify the ptx + ptx.verify_proof() +} + +#[cfg(test)] +#[cfg(feature = "borsh")] +pub mod tests { + use crate::{ + note::tests::{random_input_note, random_output_note}, + taiga_api::*, + }; + use rand::rngs::OsRng; + + #[test] + fn note_borsh_serialization_api_test() { + let mut rng = OsRng; + let input_note = random_input_note(&mut rng); + { + let bytes = note_serialize(&input_note).unwrap(); + let de_input_note = note_deserialize(bytes).unwrap(); + assert_eq!(input_note, de_input_note); + } + + { + let output_note = random_output_note(&mut rng, input_note.rho); + let bytes = note_serialize(&output_note).unwrap(); + let de_output_note = note_deserialize(bytes).unwrap(); + assert_eq!(output_note, de_output_note); + } + } + + #[ignore] + #[test] + fn ptx_example_test() { + use crate::action::ActionInfo; + use crate::circuit::vp_examples::TrivialValidityPredicateCircuit; + use crate::constant::TAIGA_COMMITMENT_TREE_DEPTH; + use crate::merkle_tree::{MerklePath, Node}; + use crate::note::{ + tests::{random_input_note, random_output_note}, + RandomSeed, + }; + + let mut rng = OsRng; + + // construct notes + let input_note_1 = random_input_note(&mut rng); + let input_note_1_nf = input_note_1.get_nf().unwrap(); + let output_note_1 = random_output_note(&mut rng, input_note_1_nf); + let merkle_path_1 = MerklePath::random(&mut rng, TAIGA_COMMITMENT_TREE_DEPTH); + let anchor_1 = { + let cm_note = Node::from(&input_note_1); + merkle_path_1.root(cm_note) + }; + let rseed_1 = RandomSeed::random(&mut rng); + let action_1 = ActionInfo::new( + input_note_1, + merkle_path_1, + anchor_1, + output_note_1, + rseed_1, + ); + + let input_note_2 = random_input_note(&mut rng); + let input_note_2_nf = input_note_2.get_nf().unwrap(); + let output_note_2 = random_output_note(&mut rng, input_note_2_nf); + let merkle_path_2 = MerklePath::random(&mut rng, TAIGA_COMMITMENT_TREE_DEPTH); + let anchor_2 = { + let cm_note = Node::from(&input_note_2); + merkle_path_2.root(cm_note) + }; + let rseed_2 = RandomSeed::random(&mut rng); + let action_2 = ActionInfo::new( + input_note_2, + merkle_path_2, + anchor_2, + output_note_2, + rseed_2, + ); + + // construct applications + let input_note_1_app = { + let app_vp = TrivialValidityPredicateCircuit::new( + input_note_1_nf.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_nf.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![]) + }; + + // construct ptx + let ptx = create_shielded_partial_transaction( + vec![action_1, action_2], + vec![input_note_1_app, input_note_2_app], + vec![output_note_1_app, output_note_2_app], + vec![], + ); + + 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 7d8d91be..351903f7 100644 --- a/taiga_halo2/src/transaction.rs +++ b/taiga_halo2/src/transaction.rs @@ -42,6 +42,8 @@ pub struct ShieldedPartialTxBundle(Vec); #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "nif", derive(NifStruct))] #[cfg_attr(feature = "nif", module = "Taiga.Transaction.Result")] +#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ShieldedResult { pub anchors: Vec, pub nullifiers: Vec, @@ -219,7 +221,7 @@ impl ShieldedPartialTxBundle { }) } - pub fn build(partial_txs: Vec) -> Self { + pub fn new(partial_txs: Vec) -> Self { Self(partial_txs) } @@ -275,7 +277,7 @@ impl TransparentPartialTxBundle { self.0.is_empty() } - pub fn build(partial_txs: Vec) -> Self { + pub fn new(partial_txs: Vec) -> Self { Self(partial_txs) } @@ -328,7 +330,7 @@ pub mod testing { let ptx = create_shielded_ptx(); bundle.push(ptx); } - ShieldedPartialTxBundle::build(bundle) + ShieldedPartialTxBundle::new(bundle) } #[test]