diff --git a/CHANGELOG.md b/CHANGELOG.md index 79762d417..c30b02e64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] ### Added +- [\#295](https://github.com/Manta-Network/manta-rs/pull/295) Transaction data retrieving methods for the signer. - [\#297](https://github.com/Manta-Network/manta-rs/pull/297) Add trusted setup verification tools, update manta-parameters + ### Changed - [\#293](https://github.com/Manta-Network/manta-rs/pull/293) Add decimals argument to AssetMetadata display diff --git a/manta-accounting/src/transfer/canonical.rs b/manta-accounting/src/transfer/canonical.rs index cff2a0a4b..2106c586f 100644 --- a/manta-accounting/src/transfer/canonical.rs +++ b/manta-accounting/src/transfer/canonical.rs @@ -21,11 +21,11 @@ use crate::{ asset::{self, AssetMap, AssetMetadata, MetadataDisplay}, transfer::{ - has_public_participants, internal_pair, requires_authorization, Address, Asset, - AssociatedData, Authorization, AuthorizationContext, Configuration, FullParametersRef, - Parameters, PreSender, ProofSystemError, ProofSystemPublicParameters, ProvingContext, - Receiver, Sender, Transfer, TransferLedger, TransferPost, TransferPostingKeyRef, - VerifyingContext, + has_public_participants, internal_pair, requires_authorization, utxo::UtxoReconstruct, + Address, Asset, AssociatedData, Authorization, AuthorizationContext, Configuration, + FullParametersRef, Identifier, Parameters, PreSender, ProofSystemError, + ProofSystemPublicParameters, ProvingContext, Receiver, Sender, Transfer, TransferLedger, + TransferPost, TransferPostingKeyRef, Utxo, VerifyingContext, }, }; use alloc::{string::String, vec::Vec}; @@ -674,3 +674,84 @@ where }, )) } + +/// Transaction Data +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "Asset: Deserialize<'de>, Identifier: Deserialize<'de>", + serialize = "Asset: Serialize, Identifier: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "Asset: Clone, Identifier: Clone"), + Debug(bound = "Asset: Debug, Identifier: Debug"), + Eq(bound = "Asset: Eq, Identifier: Eq"), + Hash(bound = "Asset: Hash, Identifier: Hash"), + PartialEq(bound = "Asset: PartialEq, Identifier: PartialEq") +)] +pub enum TransactionData +where + C: Configuration, +{ + /// To Private Transaction Data + ToPrivate(Identifier, Asset), + + /// Private Transfer Transaction Data + PrivateTransfer(Vec<(Identifier, Asset)>), + + /// To Public Transaction Data + ToPublic(Identifier, Asset), +} + +impl TransactionData +where + C: Configuration, +{ + /// Returns a vector of [`Identifier`]-[`Asset`] pairs, consuming `self`. + #[inline] + pub fn open(&self) -> Vec<(Identifier, Asset)> { + match self { + TransactionData::ToPrivate(identifier, asset) => { + [(identifier.clone(), asset.clone())].to_vec() + } + TransactionData::PrivateTransfer(identified_assets) => identified_assets.clone(), + TransactionData::ToPublic(identifier, asset) => { + [(identifier.clone(), asset.clone())].to_vec() + } + } + } + + /// Reconstructs the [`Utxo`] from `self` and `address`. + #[inline] + pub fn reconstruct_utxo( + &self, + parameters: &Parameters, + address: &Address, + ) -> Vec> { + self.open() + .into_iter() + .map(|(identifier, asset)| parameters.utxo_reconstruct(&asset, &identifier, address)) + .collect() + } + + /// Checks the correctness of `self` against `utxos`. + #[inline] + pub fn check_transaction_data( + &self, + parameters: &Parameters, + address: &Address, + utxos: &Vec>, + ) -> bool + where + Utxo: PartialEq, + { + self.reconstruct_utxo(parameters, address).eq(utxos) + } +} diff --git a/manta-accounting/src/transfer/utxo/mod.rs b/manta-accounting/src/transfer/utxo/mod.rs index 20b0011f8..b1265cf32 100644 --- a/manta-accounting/src/transfer/utxo/mod.rs +++ b/manta-accounting/src/transfer/utxo/mod.rs @@ -259,8 +259,25 @@ pub trait NoteOpen: AssetType + DeriveDecryptionKey + IdentifierType + NoteType } } +/// Derive Address +pub trait DeriveAddress: AddressType { + /// Secret Key Type + type SecretKey; + + /// Derives the address corresponding to `secret_key`. + fn derive_address(&self, secret_key: &Self::SecretKey) -> Self::Address; +} + /// Utxo Reconstruction -pub trait UtxoReconstruct: NoteOpen { +pub trait UtxoReconstruct: NoteOpen + DeriveAddress { + /// Builds a [`Utxo`] from `asset`, `identifier` and `address`. + fn utxo_reconstruct( + &self, + asset: &Self::Asset, + identifier: &Self::Identifier, + address: &Self::Address, + ) -> Self::Utxo; + /// Checks if `utxo` is consistent with `asset` and `identifier`. fn utxo_check( &self, diff --git a/manta-accounting/src/transfer/utxo/protocol.rs b/manta-accounting/src/transfer/utxo/protocol.rs index 86ca4018f..8780353ba 100644 --- a/manta-accounting/src/transfer/utxo/protocol.rs +++ b/manta-accounting/src/transfer/utxo/protocol.rs @@ -21,6 +21,7 @@ use crate::{ transfer::utxo::{ self, auth::{self, DeriveContext, SpendingKey}, + DeriveAddress, }, }; use alloc::vec::Vec; @@ -1407,6 +1408,23 @@ where } } +impl utxo::DeriveAddress for Parameters +where + C: Configuration, +{ + type SecretKey = C::Scalar; + + #[inline] + fn derive_address(&self, decryption_key: &Self::SecretKey) -> Self::Address { + Address::new( + self.base + .group_generator + .generator() + .scalar_mul(decryption_key, &mut ()), + ) + } +} + impl utxo::UtxoReconstruct for Parameters where C: Configuration, @@ -1415,13 +1433,15 @@ where Asset: Clone + Default, { #[inline] - fn utxo_check( + fn utxo_reconstruct( &self, - utxo: &Self::Utxo, - asset: &Self::Asset, - identifier: &Self::Identifier, - decryption_key: &Self::DecryptionKey, - ) -> bool { + asset: &Asset, + identifier: &Identifier, + address: &Address, + ) -> Utxo + where + Asset: Clone + Default, + { let associated_data = if identifier.is_transparent { Visibility::Transparent } else { @@ -1431,19 +1451,26 @@ where &identifier.utxo_commitment_randomness, &associated_data.secret(asset).id, &associated_data.secret(asset).value, - &self - .base - .group_generator - .generator() - .scalar_mul(decryption_key, &mut ()), + &address.receiving_key, &mut (), ); - let new_utxo = Self::Utxo::new( + Utxo::new( identifier.is_transparent, associated_data.public(asset), new_utxo_commitment, - ); - new_utxo.eq(utxo, &mut ()) + ) + } + + #[inline] + fn utxo_check( + &self, + utxo: &Self::Utxo, + asset: &Self::Asset, + identifier: &Self::Identifier, + decryption_key: &Self::DecryptionKey, + ) -> bool { + self.utxo_reconstruct(asset, identifier, &self.derive_address(decryption_key)) + .eq(utxo, &mut ()) } } diff --git a/manta-accounting/src/wallet/mod.rs b/manta-accounting/src/wallet/mod.rs index 7b5bfff07..8caa5b887 100644 --- a/manta-accounting/src/wallet/mod.rs +++ b/manta-accounting/src/wallet/mod.rs @@ -38,7 +38,7 @@ use crate::{ ledger::ReadResponse, signer::{ BalanceUpdate, SignError, SignRequest, SignResponse, SyncData, SyncError, SyncRequest, - SyncResponse, + SyncResponse, TransactionDataRequest, TransactionDataResponse, }, }, }; @@ -385,6 +385,18 @@ where .map_err(Error::SignError) } + /// Attempts to process TransferPosts and returns the corresponding TransactionData. + #[inline] + pub async fn transaction_data( + &mut self, + transfer_posts: Vec>, + ) -> Result, Error> { + self.signer + .transaction_data(TransactionDataRequest(transfer_posts)) + .await + .map_err(Error::SignerConnectionError) + } + /// Posts a transaction to the ledger, returning a success [`Response`] if the `transaction` /// was successfully posted to the ledger. This method automatically synchronizes with the /// ledger before posting, _but not after_. To amortize the cost of future calls to [`post`], diff --git a/manta-accounting/src/wallet/signer.rs b/manta-accounting/src/wallet/signer.rs index 9ea037caf..c64daf60f 100644 --- a/manta-accounting/src/wallet/signer.rs +++ b/manta-accounting/src/wallet/signer.rs @@ -33,8 +33,9 @@ use crate::{ batch::Join, canonical::{ MultiProvingContext, PrivateTransfer, PrivateTransferShape, Selection, ToPrivate, - ToPublic, Transaction, + ToPublic, Transaction, TransactionData, TransferShape, }, + receiver::ReceiverPost, requires_authorization, utxo::{auth::DeriveContext, DeriveDecryptionKey, DeriveSpend, Spend, UtxoReconstruct}, Address, Asset, AssociatedData, Authorization, AuthorizationContext, FullParametersRef, @@ -52,7 +53,7 @@ use manta_crypto::{ }; use manta_util::{ array_map, cmp::Independence, future::LocalBoxFutureResult, into_array_unchecked, - iter::IteratorExt, persistence::Rollback, + iter::IteratorExt, persistence::Rollback, vec::VecExt, }; #[cfg(feature = "serde")] @@ -89,6 +90,12 @@ where /// Returns the [`Address`] corresponding to `self`. fn address(&mut self) -> LocalBoxFutureResult, Self::Error>; + + /// Returns the [`TransactionData`] of the [`TransferPost`]s in `request` owned by `self`. + fn transaction_data( + &mut self, + request: TransactionDataRequest, + ) -> LocalBoxFutureResult, Self::Error>; } /// Signer Synchronization Data @@ -248,6 +255,58 @@ where pub balance_update: BalanceUpdate, } +/// Transaction Data Request +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "TransferPost: Deserialize<'de>", + serialize = "TransferPost: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "TransferPost: Clone"), + Debug(bound = "TransferPost: Debug"), + Default(bound = "TransferPost: Default"), + Eq(bound = "TransferPost: Eq"), + Hash(bound = "TransferPost: Hash"), + PartialEq(bound = "TransferPost: PartialEq") +)] +pub struct TransactionDataRequest(pub Vec>) +where + C: transfer::Configuration; + +/// Transaction Data Response +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "TransactionData: Deserialize<'de>", + serialize = "TransactionData: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "TransactionData: Clone"), + Debug(bound = "TransactionData: Debug"), + Default(bound = "TransactionData: Default"), + Eq(bound = "TransactionData: Eq"), + Hash(bound = "TransactionData: Hash"), + PartialEq(bound = "TransactionData: PartialEq") +)] +pub struct TransactionDataResponse(pub Vec>>) +where + C: transfer::Configuration; + /// Balance Update #[cfg_attr( feature = "serde", @@ -1336,6 +1395,63 @@ where let account = self.state.accounts.get_default(); account.address(&self.parameters.parameters) } + + /// Returns the associated [`TransactionData`] of `post`, namely the [`Asset`] and the + /// [`Identifier`]. Returns `None` if `post` has an invalid shape, or if `self` doesn't own the + /// underlying assets in `post`. + #[inline] + pub fn transaction_data(&self, post: TransferPost) -> Option> { + let shape = TransferShape::from_post(&post)?; + let parameters = &self.parameters.parameters; + let mut authorization_context = self.state.default_authorization_context(parameters); + let decryption_key = parameters.derive_decryption_key(&mut authorization_context); + match shape { + TransferShape::ToPrivate => { + let ReceiverPost { utxo, note } = post.body.receiver_posts.take_first(); + let (identifier, asset) = + parameters.open_with_check(&decryption_key, &utxo, note)?; + Some(TransactionData::::ToPrivate(identifier, asset)) + } + TransferShape::PrivateTransfer => { + let mut transaction_data = Vec::new(); + let receiver_posts = post.body.receiver_posts; + for receiver_post in receiver_posts.into_iter() { + let ReceiverPost { utxo, note } = receiver_post; + if let Some(identified_asset) = + parameters.open_with_check(&decryption_key, &utxo, note) + { + transaction_data.push(identified_asset); + } + } + if transaction_data.is_empty() { + None + } else { + Some(TransactionData::::PrivateTransfer(transaction_data)) + } + } + TransferShape::ToPublic => { + let ReceiverPost { utxo, note } = post.body.receiver_posts.take_first(); + let (identifier, asset) = + parameters.open_with_check(&decryption_key, &utxo, note)?; + Some(TransactionData::::ToPublic(identifier, asset)) + } + } + } + + /// Returns a vector with the [`TransactionData`] of each well-formed [`TransferPost`] owned by + /// `self`. + #[inline] + pub fn batched_transaction_data( + &self, + posts: Vec>, + ) -> TransactionDataResponse { + TransactionDataResponse( + posts + .into_iter() + .map(|p| self.transaction_data(p)) + .collect(), + ) + } } impl Connection for Signer @@ -1368,4 +1484,12 @@ where fn address(&mut self) -> LocalBoxFutureResult, Self::Error> { Box::pin(async move { Ok(self.address()) }) } + + #[inline] + fn transaction_data( + &mut self, + request: TransactionDataRequest, + ) -> LocalBoxFutureResult, Self::Error> { + Box::pin(async move { Ok(self.batched_transaction_data(request.0)) }) + } } diff --git a/manta-pay/src/signer/client/http.rs b/manta-pay/src/signer/client/http.rs index 57d919501..778a89aa1 100644 --- a/manta-pay/src/signer/client/http.rs +++ b/manta-pay/src/signer/client/http.rs @@ -21,7 +21,7 @@ use crate::{ signer::{ client::network::{Message, Network}, Checkpoint, GetRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, - SyncResponse, + SyncResponse, TransactionDataRequest, TransactionDataResponse, }, }; use alloc::boxed::Box; @@ -105,4 +105,16 @@ impl signer::Connection for Client { .await }) } + + #[inline] + fn transaction_data( + &mut self, + request: TransactionDataRequest, + ) -> LocalBoxFutureResult { + Box::pin(async move { + self.base + .post("transaction_data", &self.wrap_request(request)) + .await + }) + } } diff --git a/manta-pay/src/signer/client/websocket.rs b/manta-pay/src/signer/client/websocket.rs index 674766e94..f46ed90d5 100644 --- a/manta-pay/src/signer/client/websocket.rs +++ b/manta-pay/src/signer/client/websocket.rs @@ -22,7 +22,7 @@ use crate::{ config::{utxo::Address, Config}, signer::{ Checkpoint, GetRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, - SyncResponse, + SyncResponse, TransactionDataRequest, TransactionDataResponse, }, }; use alloc::boxed::Box; @@ -150,4 +150,12 @@ impl signer::Connection for Client { fn address(&mut self) -> LocalBoxFutureResult { Box::pin(async move { self.send("address", GetRequest::Get).await }) } + + #[inline] + fn transaction_data( + &mut self, + request: TransactionDataRequest, + ) -> LocalBoxFutureResult { + Box::pin(async move { self.send("transaction_data", request).await }) + } } diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index c0e8511d2..64f795df1 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -54,6 +54,12 @@ pub type SignError = signer::SignError; /// Signing Result pub type SignResult = signer::SignResult; +/// Transaction Data Request +pub type TransactionDataRequest = signer::TransactionDataRequest; + +/// Transaction Data Response +pub type TransactionDataResponse = signer::TransactionDataResponse; + /// Receiving Key Request #[cfg_attr( feature = "serde",