From 9801cdb7e551fedc832c4d8bffce678494856146 Mon Sep 17 00:00:00 2001 From: Xuyang Song Date: Tue, 24 Oct 2023 04:50:10 -0500 Subject: [PATCH] transparent ptx (#230) --- taiga_halo2/src/error.rs | 10 +++ taiga_halo2/src/taiga_api.rs | 14 +-- taiga_halo2/src/transaction.rs | 99 ++++++++++---------- taiga_halo2/src/transparent_ptx.rs | 134 +++++++++++++++++++++------- taiga_halo2/src/value_commitment.rs | 19 ++++ 5 files changed, 185 insertions(+), 91 deletions(-) diff --git a/taiga_halo2/src/error.rs b/taiga_halo2/src/error.rs index ab57bb85..8c18fa03 100644 --- a/taiga_halo2/src/error.rs +++ b/taiga_halo2/src/error.rs @@ -18,6 +18,10 @@ pub enum TransactionError { InconsistentOwnedNotePubID, /// IO error IoError(std::io::Error), + /// Transparent resource nullifier key is missing + MissingTransparentResourceNullifierKey, + /// Transparent resource merkle path is missing + MissingTransparentResourceMerklePath, } impl Display for TransactionError { @@ -37,6 +41,12 @@ impl Display for TransactionError { 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}")), + MissingTransparentResourceNullifierKey => { + f.write_str("Transparent resource nullifier key is missing") + } + MissingTransparentResourceMerklePath => { + f.write_str("Transparent resource merkle path is missing") + } } } } diff --git a/taiga_halo2/src/taiga_api.rs b/taiga_halo2/src/taiga_api.rs index 5e7013d3..bb27fe62 100644 --- a/taiga_halo2/src/taiga_api.rs +++ b/taiga_halo2/src/taiga_api.rs @@ -1,9 +1,7 @@ #[cfg(feature = "borsh")] use crate::{ - action::ActionInfo, - circuit::vp_bytecode::ApplicationByteCode, - error::TransactionError, - transaction::{ShieldedResult, TransparentResult}, + action::ActionInfo, circuit::vp_bytecode::ApplicationByteCode, error::TransactionError, + transaction::TransactionResult, }; use crate::{ note::{Note, RandomSeed}, @@ -203,7 +201,7 @@ pub fn create_transaction( /// Verify a transaction and return the results /// -/// ShieldedResult layout: +/// TransactionResult layout: /// | Parameters | type | size(bytes)| /// | - | - | - | /// | anchor num | u32 | 4 | @@ -213,12 +211,8 @@ pub fn create_transaction( /// | 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> { +pub fn verify_transaction(tx_bytes: Vec) -> Result { // Decode the tx let tx = transaction_deserialize(tx_bytes)?; diff --git a/taiga_halo2/src/transaction.rs b/taiga_halo2/src/transaction.rs index 351903f7..4e5d0c30 100644 --- a/taiga_halo2/src/transaction.rs +++ b/taiga_halo2/src/transaction.rs @@ -6,7 +6,7 @@ use crate::merkle_tree::Anchor; use crate::note::NoteCommitment; use crate::nullifier::Nullifier; use crate::shielded_ptx::ShieldedPartialTransaction; -use crate::transparent_ptx::{OutputResource, TransparentPartialTransaction}; +use crate::transparent_ptx::TransparentPartialTransaction; use crate::value_commitment::ValueCommitment; use blake2b_simd::Params as Blake2bParams; use pasta_curves::{group::Group, pallas}; @@ -32,35 +32,28 @@ pub struct Transaction { signature: BindingSignature, } -#[derive(Debug, Clone, Default)] -#[cfg_attr(feature = "nif", derive(NifRecord))] -#[cfg_attr(feature = "nif", tag = "bundle")] -#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -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 struct TransactionResult { pub anchors: Vec, pub nullifiers: Vec, pub output_cms: Vec, } #[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "nif", derive(NifRecord))] +#[cfg_attr(feature = "nif", tag = "bundle")] #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct TransparentPartialTxBundle(Vec); +pub struct ShieldedPartialTxBundle(Vec); -// TODO: add other outputs if needed. -#[derive(Debug, Clone)] -pub struct TransparentResult { - pub nullifiers: Vec, - pub outputs: Vec, -} +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TransparentPartialTxBundle(Vec); impl Transaction { // Generate the transaction @@ -71,8 +64,7 @@ impl Transaction { ) -> Self { assert!(!(shielded_ptx_bundle.is_empty() && transparent_ptx_bundle.is_empty())); let shielded_sk = shielded_ptx_bundle.get_bindig_sig_r(); - let transparent_sk = transparent_ptx_bundle.get_bindig_sig_r(); - let binding_sk = BindingSigningKey::from(shielded_sk + transparent_sk); + let binding_sk = BindingSigningKey::from(shielded_sk); let sig_hash = Self::digest(&shielded_ptx_bundle, &transparent_ptx_bundle); let signature = binding_sk.sign(rng, &sig_hash); @@ -84,14 +76,15 @@ impl Transaction { } #[allow(clippy::type_complexity)] - pub fn execute(&self) -> Result<(ShieldedResult, TransparentResult), TransactionError> { - let shielded_result = self.shielded_ptx_bundle.execute()?; - let transparent_result = self.transparent_ptx_bundle.execute()?; + pub fn execute(&self) -> Result { + let mut result = self.shielded_ptx_bundle.execute()?; + let mut transparent_result = self.transparent_ptx_bundle.execute()?; + result.append(&mut transparent_result); // check balance self.verify_binding_sig()?; - Ok((shielded_result, transparent_result)) + Ok(result) } fn verify_binding_sig(&self) -> Result<(), TransactionError> { @@ -210,6 +203,14 @@ impl<'a> Decoder<'a> for Transaction { } } +impl TransactionResult { + pub fn append(&mut self, result: &mut TransactionResult) { + self.anchors.append(&mut result.anchors); + self.nullifiers.append(&mut result.nullifiers); + self.output_cms.append(&mut result.output_cms); + } +} + impl ShieldedPartialTxBundle { pub fn is_empty(&self) -> bool { self.0.is_empty() @@ -230,13 +231,13 @@ impl ShieldedPartialTxBundle { } #[allow(clippy::type_complexity)] - pub fn execute(&self) -> Result { + pub fn execute(&self) -> Result { for partial_tx in self.0.iter() { partial_tx.execute()?; } // Return Nullifiers to check double-spent, NoteCommitments to store, anchors to check the root-existence - Ok(ShieldedResult { + Ok(TransactionResult { nullifiers: self.get_nullifiers(), output_cms: self.get_output_cms(), anchors: self.get_anchors(), @@ -261,15 +262,6 @@ impl ShieldedPartialTxBundle { pub fn get_anchors(&self) -> Vec { self.0.iter().flat_map(|ptx| ptx.get_anchors()).collect() } - - fn get_binding_vk(&self) -> BindingVerificationKey { - let vk = self - .get_value_commitments() - .iter() - .fold(pallas::Point::identity(), |acc, cv| acc + cv.inner()); - - BindingVerificationKey::from(vk) - } } impl TransparentPartialTxBundle { @@ -285,20 +277,23 @@ impl TransparentPartialTxBundle { self.0.push(ptx); } - pub fn execute(&self) -> Result { + pub fn execute(&self) -> Result { for partial_tx in self.0.iter() { partial_tx.execute()?; } - Ok(TransparentResult { - nullifiers: vec![], - outputs: vec![], + Ok(TransactionResult { + nullifiers: self.get_nullifiers(), + output_cms: self.get_output_cms(), + anchors: self.get_anchors(), }) } pub fn get_value_commitments(&self) -> Vec { - // TODO: add the real value commitments - vec![] + self.0 + .iter() + .flat_map(|ptx| ptx.get_value_commitments()) + .collect() } pub fn get_nullifiers(&self) -> Vec { @@ -312,17 +307,13 @@ impl TransparentPartialTxBundle { pub fn get_anchors(&self) -> Vec { self.0.iter().flat_map(|ptx| ptx.get_anchors()).collect() } - - pub fn get_bindig_sig_r(&self) -> pallas::Scalar { - // TODO: add the real r - pallas::Scalar::zero() - } } #[cfg(test)] pub mod testing { use crate::shielded_ptx::testing::create_shielded_ptx; - use crate::transaction::ShieldedPartialTxBundle; + use crate::transaction::{ShieldedPartialTxBundle, TransparentPartialTxBundle}; + use crate::transparent_ptx::testing::create_transparent_ptx; pub fn create_shielded_ptx_bundle(num: usize) -> ShieldedPartialTxBundle { let mut bundle = vec![]; @@ -333,6 +324,15 @@ pub mod testing { ShieldedPartialTxBundle::new(bundle) } + pub fn create_transparent_ptx_bundle(num: usize) -> TransparentPartialTxBundle { + let mut bundle = vec![]; + for _ in 0..num { + let ptx = create_transparent_ptx(); + bundle.push(ptx); + } + TransparentPartialTxBundle::new(bundle) + } + #[test] fn test_halo2_transaction() { use super::*; @@ -341,17 +341,16 @@ pub mod testing { let rng = OsRng; let shielded_ptx_bundle = create_shielded_ptx_bundle(1); - // TODO: add transparent_ptx_bundle test - let transparent_ptx_bundle = TransparentPartialTxBundle::default(); + let transparent_ptx_bundle = create_transparent_ptx_bundle(1); let tx = Transaction::build(rng, shielded_ptx_bundle, transparent_ptx_bundle); - let (_shielded_ret, _) = tx.execute().unwrap(); + let _ret = tx.execute().unwrap(); #[cfg(feature = "borsh")] { let borsh = borsh::to_vec(&tx).unwrap(); let de_tx: Transaction = BorshDeserialize::deserialize(&mut borsh.as_ref()).unwrap(); - let (de_shielded_ret, _) = de_tx.execute().unwrap(); - assert_eq!(_shielded_ret, de_shielded_ret); + let de_ret = de_tx.execute().unwrap(); + assert_eq!(_ret, de_ret); } } } diff --git a/taiga_halo2/src/transparent_ptx.rs b/taiga_halo2/src/transparent_ptx.rs index 77308aa1..bb05089c 100644 --- a/taiga_halo2/src/transparent_ptx.rs +++ b/taiga_halo2/src/transparent_ptx.rs @@ -1,6 +1,10 @@ use crate::{ - error::TransactionError, executable::Executable, merkle_tree::Anchor, note::NoteCommitment, - nullifier::Nullifier, value_commitment::ValueCommitment, + error::TransactionError, + executable::Executable, + merkle_tree::{Anchor, MerklePath}, + note::{Note, NoteCommitment}, + nullifier::{Nullifier, NullifierKeyContainer}, + value_commitment::ValueCommitment, }; #[cfg(feature = "serde")] @@ -15,28 +19,61 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub struct TransparentPartialTransaction { pub inputs: Vec, pub outputs: Vec, + pub hints: Vec, } 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 merkle_path is provided + if input.merkle_path.is_none() && input.note.is_merkle_checked { + return Err(TransactionError::MissingTransparentResourceMerklePath); + } + } + // TODO: figure out how transparent ptx executes - unimplemented!() + // VP should be checked here + + Ok(()) } fn get_nullifiers(&self) -> Vec { - unimplemented!() + self.inputs + .iter() + .map(|resource| resource.note.get_nf().unwrap()) + .collect() } fn get_output_cms(&self) -> Vec { - unimplemented!() + self.outputs + .iter() + .map(|resource| resource.note.commitment()) + .collect() } fn get_value_commitments(&self) -> Vec { - unimplemented!() + vec![ValueCommitment::from_tranparent_resources( + &self.inputs, + &self.outputs, + )] } fn get_anchors(&self) -> Vec { - unimplemented!() + 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 } } @@ -44,38 +81,73 @@ impl Executable for TransparentPartialTransaction { #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct InputResource { - pub resource_logic: ResourceLogic, - pub prefix: ContentHash, - pub suffix: Vec, - pub resource_data_static: ResourceDataStatic, - pub resource_data_dynamic: ResourceDataDynamic, + 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 resource_logic: ResourceLogic, - pub resource_data_static: ResourceDataStatic, - pub resource_data_dynamic: ResourceDataDynamic, + pub note: Note, + // 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 ResourceLogic {} +#[cfg(test)] +pub mod testing { + use crate::{ + constant::TAIGA_COMMITMENT_TREE_DEPTH, + merkle_tree::MerklePath, + note::tests::{random_input_note, random_output_note}, + transparent_ptx::*, + }; + use rand::rngs::OsRng; -#[derive(Debug, Clone)] -#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ContentHash {} + // No transparent vp included + pub fn create_transparent_ptx() -> TransparentPartialTransaction { + let mut rng = OsRng; + let input_resource_1 = { + let note = random_input_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 = { + let mut note = random_output_note(&mut rng, input_resource_1.note.rho); + // 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 } + }; -#[derive(Debug, Clone)] -#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ResourceDataStatic {} + let input_resource_2 = { + let mut note = random_input_note(&mut rng); + note.is_merkle_checked = false; + InputResource { + note, + merkle_path: None, + } + }; + let output_resource_2 = { + let mut note = random_output_note(&mut rng, input_resource_2.note.rho); + // 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 } + }; -#[derive(Debug, Clone)] -#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ResourceDataDynamic {} + TransparentPartialTransaction { + inputs: vec![input_resource_1, input_resource_2], + outputs: vec![output_resource_1, output_resource_2], + hints: vec![], + } + } +} diff --git a/taiga_halo2/src/value_commitment.rs b/taiga_halo2/src/value_commitment.rs index 444746b0..f7a3aba7 100644 --- a/taiga_halo2/src/value_commitment.rs +++ b/taiga_halo2/src/value_commitment.rs @@ -1,5 +1,6 @@ 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}; @@ -27,6 +28,24 @@ 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()