diff --git a/CHANGELOG.md b/CHANGELOG.md index d9021f8a5..f6d3f5ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] ### Added +- [\#286](https://github.com/Manta-Network/manta-rs/pull/286) MantaPay v1.0.0 ### Changed - [\#283](https://github.com/Manta-Network/manta-rs/pull/283) Upgrade asset system. diff --git a/manta-accounting/Cargo.toml b/manta-accounting/Cargo.toml index 8eff93656..42683fd36 100644 --- a/manta-accounting/Cargo.toml +++ b/manta-accounting/Cargo.toml @@ -47,7 +47,9 @@ std = ["manta-crypto/std", "manta-util/std"] test = [ "futures", "indexmap", + "manta-crypto/arkworks", "manta-crypto/rand", + "manta-crypto/test", "parking_lot", "statrs" ] diff --git a/manta-accounting/src/asset.rs b/manta-accounting/src/asset.rs index b8115e829..3e4b54274 100644 --- a/manta-accounting/src/asset.rs +++ b/manta-accounting/src/asset.rs @@ -879,9 +879,8 @@ impl AssetMap for BTreeAssetMap where K: Clone + Ord, I: Clone + Ord, - V: AddAssign + Clone + Default + Ord + Sub, + V: AddAssign + Clone + Default + Ord + Sub + for<'v> AddAssign<&'v V>, for<'v> &'v V: Sub, - for<'v> &'v V: AddAssign<&'v V>, { impl_asset_map_for_maps_body! { K, I, V, BTreeMapEntry } } @@ -897,9 +896,8 @@ impl AssetMap for HashAssetMap where K: Clone + Hash + Eq, I: Clone + Ord, - V: AddAssign + Clone + Default + Ord + Sub, + V: AddAssign + Clone + Default + Ord + Sub + for<'v> AddAssign<&'v V>, for<'v> &'v V: Sub, - for<'v> &'v V: AddAssign<&'v V>, S: BuildHasher + Default, { impl_asset_map_for_maps_body! { K, I, V, HashMapEntry } diff --git a/manta-accounting/src/transfer/batch.rs b/manta-accounting/src/transfer/batch.rs index 949192c2a..b3a4f2169 100644 --- a/manta-accounting/src/transfer/batch.rs +++ b/manta-accounting/src/transfer/batch.rs @@ -18,15 +18,43 @@ // TODO: Move more of the batching algorithm here to improve library interfaces. -use crate::transfer::{Asset, Configuration, Parameters, PreSender, Receiver, SpendingKey, Utxo}; +use crate::transfer::{ + internal_pair, internal_zero_pair, Address, Asset, AuthorizationContext, Configuration, + Parameters, PreSender, Receiver, UtxoAccumulatorItem, UtxoAccumulatorModel, +}; use alloc::vec::Vec; +use core::{fmt::Debug, hash::Hash}; use manta_crypto::{ accumulator::Accumulator, - rand::{CryptoRng, Rand, RngCore}, + rand::{CryptoRng, RngCore}, }; use manta_util::into_array_unchecked; +#[cfg(feature = "serde")] +use manta_util::serde::{Deserialize, Serialize}; + /// Batch Join Structure +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "PreSender: Deserialize<'de>", + serialize = "PreSender: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "PreSender: Clone"), + Debug(bound = "PreSender: Debug"), + Default(bound = "PreSender: Default"), + Eq(bound = "PreSender: Eq"), + Hash(bound = "PreSender: Hash"), + PartialEq(bound = "PreSender: PartialEq") +)] pub struct Join where C: Configuration, @@ -42,12 +70,13 @@ impl Join where C: Configuration, { - /// Builds a new [`Join`] for `asset` using `spending_key` and `zero_key`. + /// Builds a new [`Join`] for `asset` using `address`. #[inline] pub fn new( parameters: &Parameters, + authorization_context: &mut AuthorizationContext, + address: Address, asset: Asset, - spending_key: &SpendingKey, rng: &mut R, ) -> ([Receiver; RECEIVERS], Self) where @@ -55,12 +84,25 @@ where { let mut receivers = Vec::with_capacity(RECEIVERS); let mut zeroes = Vec::with_capacity(RECEIVERS - 1); - let (receiver, pre_sender) = - spending_key.internal_pair(parameters, rng.gen(), asset.clone()); + let asset_id = asset.id.clone(); + let (receiver, pre_sender) = internal_pair::( + parameters, + authorization_context, + address.clone(), + asset, + Default::default(), + rng, + ); receivers.push(receiver); for _ in 1..RECEIVERS { - let (receiver, pre_sender) = - spending_key.internal_zero_pair(parameters, rng.gen(), asset.id.clone()); + let (receiver, pre_sender) = internal_zero_pair::( + parameters, + authorization_context, + address.clone(), + asset_id.clone(), + Default::default(), + rng, + ); receivers.push(receiver); zeroes.push(pre_sender); } @@ -69,13 +111,13 @@ where /// Inserts UTXOs for each sender in `self` into the `utxo_accumulator` for future proof selection. #[inline] - pub fn insert_utxos(&self, utxo_accumulator: &mut A) + pub fn insert_utxos(&self, parameters: &Parameters, utxo_accumulator: &mut A) where - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = UtxoAccumulatorModel>, { - self.pre_sender.insert_utxo(utxo_accumulator); + self.pre_sender.insert_utxo(parameters, utxo_accumulator); for zero in &self.zeroes { - zero.insert_utxo(utxo_accumulator); + zero.insert_utxo(parameters, utxo_accumulator); } } } diff --git a/manta-accounting/src/transfer/canonical.rs b/manta-accounting/src/transfer/canonical.rs index b75928461..cff2a0a4b 100644 --- a/manta-accounting/src/transfer/canonical.rs +++ b/manta-accounting/src/transfer/canonical.rs @@ -19,16 +19,18 @@ // TODO: Add typing for `ProvingContext` and `VerifyingContext` against the canonical shapes. use crate::{ - asset::{self, AssetMap}, + asset::{self, AssetMap, AssetMetadata, MetadataDisplay}, transfer::{ - has_public_participants, Asset, Configuration, FullParameters, Parameters, PreSender, - ProofSystemError, ProofSystemPublicParameters, ProvingContext, Receiver, ReceivingKey, - Sender, SpendingKey, Transfer, TransferPost, VerifyingContext, + 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, }, }; -use alloc::vec::Vec; +use alloc::{string::String, vec::Vec}; use core::{fmt::Debug, hash::Hash}; -use manta_crypto::rand::{CryptoRng, Rand, RngCore}; +use manta_crypto::rand::{CryptoRng, RngCore}; use manta_util::{create_seal, seal}; #[cfg(feature = "serde")] @@ -82,35 +84,36 @@ macro_rules! transfer_alias { }; } -/// [`Mint`] Transfer Shape +/// [`ToPrivate`] Transfer Shape /// /// ```text /// <1, 0, 1, 0> /// ``` #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd)] -pub struct MintShape; +pub struct ToPrivateShape; -impl_shape!(MintShape, 1, 0, 1, 0); +impl_shape!(ToPrivateShape, 1, 0, 1, 0); -/// [`Mint`] Transfer Type -pub type Mint = transfer_alias!(C, MintShape); +/// [`ToPrivate`] Transfer Type +pub type ToPrivate = transfer_alias!(C, ToPrivateShape); -impl Mint +impl ToPrivate where C: Configuration, { - /// Builds a [`Mint`] from `asset` and `receiver`. + /// Builds a [`ToPrivate`] from `asset` and `receiver`. #[inline] pub fn build(asset: Asset, receiver: Receiver) -> Self { - Self::new_unchecked(Some(asset.id), [asset.value], [], [receiver], []) + Self::new_unchecked(None, Some(asset.id), [asset.value], [], [receiver], []) } - /// Builds a new [`Mint`] from a [`SpendingKey`] using [`SpendingKey::receiver`]. + /// Builds a new [`ToPrivate`] from `address` and `asset`. #[inline] - pub fn from_spending_key( + pub fn from_address( parameters: &Parameters, - spending_key: &SpendingKey, + address: Address, asset: Asset, + associated_data: AssociatedData, rng: &mut R, ) -> Self where @@ -118,24 +121,31 @@ where { Self::build( asset.clone(), - spending_key.receiver(parameters, rng.gen(), asset), + Receiver::::sample(parameters, address, asset, associated_data, rng), ) } - /// Builds a new [`Mint`] and [`PreSender`] pair from a [`SpendingKey`] using - /// [`SpendingKey::internal_pair`]. + /// Builds a new [`ToPrivate`] and [`PreSender`] pair from `authorization_context` and `asset`. #[inline] pub fn internal_pair( parameters: &Parameters, - spending_key: &SpendingKey, + authorization_context: &mut AuthorizationContext, + address: Address, asset: Asset, + associated_data: AssociatedData, rng: &mut R, ) -> (Self, PreSender) where R: CryptoRng + RngCore + ?Sized, { - let (receiver, pre_sender) = - spending_key.internal_pair(parameters, rng.gen(), asset.clone()); + let (receiver, pre_sender) = internal_pair::( + parameters, + authorization_context, + address, + asset.clone(), + associated_data, + rng, + ); (Self::build(asset, receiver), pre_sender) } } @@ -160,47 +170,56 @@ where /// Builds a [`PrivateTransfer`] from `senders` and `receivers`. #[inline] pub fn build( + authorization: Authorization, senders: [Sender; PrivateTransferShape::SENDERS], receivers: [Receiver; PrivateTransferShape::RECEIVERS], ) -> Self { - Self::new_unchecked(None, [], senders, receivers, []) + Self::new_unchecked(Some(authorization), None, [], senders, receivers, []) } } -/// [`Reclaim`] Transfer Shape +/// [`ToPublic`] Transfer Shape /// /// ```text /// <0, 2, 1, 1> /// ``` /// -/// The [`ReclaimShape`] is defined in terms of the [`PrivateTransferShape`]. It is defined to +/// The [`ToPublicShape`] is defined in terms of the [`PrivateTransferShape`]. It is defined to /// have the same number of senders and one secret receiver turned into a public sink. #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd)] -pub struct ReclaimShape; +pub struct ToPublicShape; impl_shape!( - ReclaimShape, + ToPublicShape, PrivateTransferShape::SOURCES, PrivateTransferShape::SENDERS, PrivateTransferShape::RECEIVERS - 1, PrivateTransferShape::SINKS + 1 ); -/// [`Reclaim`] Transfer -pub type Reclaim = transfer_alias!(C, ReclaimShape); +/// [`ToPublic`] Transfer +pub type ToPublic = transfer_alias!(C, ToPublicShape); -impl Reclaim +impl ToPublic where C: Configuration, { - /// Builds a [`Reclaim`] from `senders`, `receivers`, and `reclaim`. + /// Builds a [`ToPublic`] from `senders`, `receivers`, and `asset`. #[inline] pub fn build( - senders: [Sender; ReclaimShape::SENDERS], - receivers: [Receiver; ReclaimShape::RECEIVERS], + authorization: Authorization, + senders: [Sender; ToPublicShape::SENDERS], + receivers: [Receiver; ToPublicShape::RECEIVERS], asset: Asset, ) -> Self { - Self::new_unchecked(Some(asset.id), [], senders, receivers, [asset.value]) + Self::new_unchecked( + Some(authorization), + Some(asset.id), + [], + senders, + receivers, + [asset.value], + ) } } @@ -212,54 +231,69 @@ where )] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum TransferShape { - /// [`Mint`] Transfer - Mint, + /// [`ToPrivate`] Transfer + ToPrivate, /// [`PrivateTransfer`] Transfer PrivateTransfer, - /// [`Reclaim`] Transfer - Reclaim, + /// [`ToPublic`] Transfer + ToPublic, } impl TransferShape { /// Selects the [`TransferShape`] for the given shape if it matches a canonical shape. #[inline] pub fn select( - asset_id_is_some: bool, + has_authorization: bool, + has_visible_asset_id: bool, sources: usize, senders: usize, receivers: usize, sinks: usize, ) -> Option { - const MINT_VISIBLE_ASSET_ID: bool = - has_public_participants(MintShape::SOURCES, MintShape::SINKS); - const PRIVATE_TRANSFER_VISIBLE_ASSET_ID: bool = + const TO_PRIVATE_HAS_AUTHORIZATION: bool = requires_authorization(ToPrivateShape::SENDERS); + const TO_PRIVATE_HAS_VISIBLE_ASSET_ID: bool = + has_public_participants(ToPrivateShape::SOURCES, ToPrivateShape::SINKS); + const PRIVATE_TRANSFER_HAS_AUTHORIZATION: bool = + requires_authorization(PrivateTransferShape::SENDERS); + const PRIVATE_TRANSFER_HAS_VISIBLE_ASSET_ID: bool = has_public_participants(PrivateTransferShape::SOURCES, PrivateTransferShape::SINKS); - const RECLAIM_VISIBLE_ASSET_ID: bool = - has_public_participants(ReclaimShape::SOURCES, ReclaimShape::SINKS); - match (asset_id_is_some, sources, senders, receivers, sinks) { + const TO_PUBLIC_HAS_AUTHORIZATION: bool = requires_authorization(ToPublicShape::SENDERS); + const TO_PUBLIC_HAS_VISIBLE_ASSET_ID: bool = + has_public_participants(ToPublicShape::SOURCES, ToPublicShape::SINKS); + match ( + has_authorization, + has_visible_asset_id, + sources, + senders, + receivers, + sinks, + ) { ( - MINT_VISIBLE_ASSET_ID, - MintShape::SOURCES, - MintShape::SENDERS, - MintShape::RECEIVERS, - MintShape::SINKS, - ) => Some(Self::Mint), + TO_PRIVATE_HAS_AUTHORIZATION, + TO_PRIVATE_HAS_VISIBLE_ASSET_ID, + ToPrivateShape::SOURCES, + ToPrivateShape::SENDERS, + ToPrivateShape::RECEIVERS, + ToPrivateShape::SINKS, + ) => Some(Self::ToPrivate), ( - PRIVATE_TRANSFER_VISIBLE_ASSET_ID, + PRIVATE_TRANSFER_HAS_AUTHORIZATION, + PRIVATE_TRANSFER_HAS_VISIBLE_ASSET_ID, PrivateTransferShape::SOURCES, PrivateTransferShape::SENDERS, PrivateTransferShape::RECEIVERS, PrivateTransferShape::SINKS, ) => Some(Self::PrivateTransfer), ( - RECLAIM_VISIBLE_ASSET_ID, - ReclaimShape::SOURCES, - ReclaimShape::SENDERS, - ReclaimShape::RECEIVERS, - ReclaimShape::SINKS, - ) => Some(Self::Reclaim), + TO_PUBLIC_HAS_AUTHORIZATION, + TO_PUBLIC_HAS_VISIBLE_ASSET_ID, + ToPublicShape::SOURCES, + ToPublicShape::SENDERS, + ToPublicShape::RECEIVERS, + ToPublicShape::SINKS, + ) => Some(Self::ToPublic), _ => None, } } @@ -271,11 +305,29 @@ impl TransferShape { C: Configuration, { Self::select( - post.asset_id.is_some(), - post.sources.len(), - post.sender_posts.len(), - post.receiver_posts.len(), - post.sinks.len(), + post.authorization_signature.is_some(), + post.body.asset_id.is_some(), + post.body.sources.len(), + post.body.sender_posts.len(), + post.body.receiver_posts.len(), + post.body.sinks.len(), + ) + } + + /// Selects the [`TransferShape`] from `posting_key`. + #[inline] + pub fn from_posting_key_ref(posting_key: &TransferPostingKeyRef) -> Option + where + C: Configuration, + L: TransferLedger, + { + Self::select( + posting_key.authorization_key.is_some(), + posting_key.asset_id.is_some(), + posting_key.sources.len(), + posting_key.senders.len(), + posting_key.receivers.len(), + posting_key.sinks.len(), ) } } @@ -286,8 +338,8 @@ impl TransferShape { derive(Deserialize, Serialize), serde( bound( - deserialize = "Asset: Deserialize<'de>, ReceivingKey: Deserialize<'de>", - serialize = "Asset: Serialize, ReceivingKey: Serialize" + deserialize = "Asset: Deserialize<'de>, Address: Deserialize<'de>", + serialize = "Asset: Serialize, Address: Serialize" ), crate = "manta_util::serde", deny_unknown_fields @@ -295,25 +347,25 @@ impl TransferShape { )] #[derive(derivative::Derivative)] #[derivative( - Clone(bound = "Asset: Clone, ReceivingKey: Clone"), - Copy(bound = "Asset: Copy, ReceivingKey: Copy"), - Debug(bound = "Asset: Debug, ReceivingKey: Debug"), - Eq(bound = "Asset: Eq, ReceivingKey: Eq"), - Hash(bound = "Asset: Hash, ReceivingKey: Hash"), - PartialEq(bound = "Asset: PartialEq, ReceivingKey: PartialEq") + Clone(bound = "Asset: Clone, Address: Clone"), + Copy(bound = "Asset: Copy, Address: Copy"), + Debug(bound = "Asset: Debug, Address: Debug"), + Eq(bound = "Asset: Eq, Address: Eq"), + Hash(bound = "Asset: Hash, Address: Hash"), + PartialEq(bound = "Asset: PartialEq, Address: PartialEq") )] pub enum Transaction where C: Configuration, { - /// Mint Private Asset - Mint(Asset), + /// Convert Public Asset into Private Asset + ToPrivate(Asset), - /// Private Transfer Asset to Receiver - PrivateTransfer(Asset, ReceivingKey), + /// Private Transfer Asset to Address + PrivateTransfer(Asset, Address), - /// Reclaim Private Asset - Reclaim(Asset), + /// Convert Private Asset into Public Asset + ToPublic(Asset), } impl Transaction @@ -324,17 +376,17 @@ where /// transaction kind if successful, and returning the asset back if the balance was /// insufficient. #[inline] - pub fn check(&self, balance: F) -> Result, Asset> + pub fn check(&self, balance: F) -> Result, &Asset> where - F: FnOnce(Asset) -> bool, + F: FnOnce(&Asset) -> bool, { match self { - Self::Mint(asset) => Ok(TransactionKind::Deposit(asset.clone())), - Self::PrivateTransfer(asset, _) | Self::Reclaim(asset) => { - if balance(asset.clone()) { + Self::ToPrivate(asset) => Ok(TransactionKind::Deposit(asset.clone())), + Self::PrivateTransfer(asset, _) | Self::ToPublic(asset) => { + if balance(asset) { Ok(TransactionKind::Withdraw(asset.clone())) } else { - Err(asset.clone()) + Err(asset) } } } @@ -344,19 +396,44 @@ where #[inline] pub fn shape(&self) -> TransferShape { match self { - Self::Mint(_) => TransferShape::Mint, + Self::ToPrivate(_) => TransferShape::ToPrivate, Self::PrivateTransfer(_, _) => TransferShape::PrivateTransfer, - Self::Reclaim(_) => TransferShape::Reclaim, + Self::ToPublic(_) => TransferShape::ToPublic, + } + } + + /// Returns the amount of value being transfered in `self`. + #[inline] + pub fn value(&self) -> &C::AssetValue { + match self { + Self::ToPrivate(asset) => &asset.value, + Self::PrivateTransfer(asset, _) => &asset.value, + Self::ToPublic(asset) => &asset.value, } } /// Returns `true` if `self` is a [`Transaction`] which transfers zero value. #[inline] - pub fn is_zero(&self) -> bool { + pub fn is_zero(&self) -> bool + where + C::AssetValue: Default + PartialEq, + { + self.value() == &Default::default() + } + + /// Returns a display for the asset and address internal to `self` given the asset `metadata`. + #[inline] + pub fn display(&self, metadata: &AssetMetadata, f: F) -> (String, Option) + where + F: FnOnce(&Address) -> String, + C::AssetValue: MetadataDisplay, + { match self { - Self::Mint(asset) => asset.is_zero(), - Self::PrivateTransfer(asset, _) => asset.is_zero(), - Self::Reclaim(asset) => asset.is_zero(), + Self::ToPrivate(asset) => (asset.value.display(metadata), None), + Self::PrivateTransfer(asset, address) => { + (asset.value.display(metadata), Some(f(address))) + } + Self::ToPublic(asset) => (asset.value.display(metadata), None), } } } @@ -368,7 +445,7 @@ where serde( bound( deserialize = "Asset: Deserialize<'de>", - serialize = "Asset: Serialize" + serialize = "Asset: Serialize", ), crate = "manta_util::serde", deny_unknown_fields @@ -379,9 +456,9 @@ where Clone(bound = "Asset: Clone"), Copy(bound = "Asset: Copy"), Debug(bound = "Asset: Debug"), - Eq(bound = "Asset: Eq"), Hash(bound = "Asset: Hash"), - PartialEq(bound = "Asset: PartialEq") + Eq(bound = "Asset: Eq"), + PartialEq(bound = "Asset: Eq") )] pub enum TransactionKind where @@ -399,6 +476,27 @@ where } /// Transfer Asset Selection +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "C::AssetValue: Deserialize<'de>, PreSender: Deserialize<'de>", + serialize = "C::AssetValue: Serialize, PreSender: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "C::AssetValue: Clone, PreSender: Clone"), + Debug(bound = "C::AssetValue: Debug, PreSender: Debug"), + Default(bound = "C::AssetValue: Default, PreSender: Default"), + Hash(bound = "C::AssetValue: Hash, PreSender: Hash"), + Eq(bound = "C::AssetValue: Eq, PreSender: Eq"), + PartialEq(bound = "C::AssetValue: PartialEq, PreSender: PartialEq") +)] pub struct Selection where C: Configuration, @@ -445,6 +543,18 @@ where } /// Canonical Multi-Proving Contexts +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "ProvingContext: Deserialize<'de>", + serialize = "ProvingContext: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] #[derive(derivative::Derivative)] #[derivative( Clone(bound = "ProvingContext: Clone"), @@ -459,14 +569,14 @@ pub struct MultiProvingContext where C: Configuration + ?Sized, { - /// Mint Proving Context - pub mint: ProvingContext, + /// [`ToPrivate`] Proving Context + pub to_private: ProvingContext, - /// Private Transfer Proving Context + /// [`PrivateTransfer`] Proving Context pub private_transfer: ProvingContext, - /// Reclaim Proving Context - pub reclaim: ProvingContext, + /// [`ToPublic`] Proving Context + pub to_public: ProvingContext, } impl MultiProvingContext @@ -477,14 +587,26 @@ where #[inline] pub fn select(&self, shape: TransferShape) -> &ProvingContext { match shape { - TransferShape::Mint => &self.mint, + TransferShape::ToPrivate => &self.to_private, TransferShape::PrivateTransfer => &self.private_transfer, - TransferShape::Reclaim => &self.reclaim, + TransferShape::ToPublic => &self.to_public, } } } /// Canonical Multi-Verifying Contexts +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "VerifyingContext: Deserialize<'de>", + serialize = "VerifyingContext: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] #[derive(derivative::Derivative)] #[derivative( Clone(bound = "VerifyingContext: Clone"), @@ -499,14 +621,14 @@ pub struct MultiVerifyingContext where C: Configuration + ?Sized, { - /// Mint Verifying Context - pub mint: VerifyingContext, + /// [`ToPrivate`] Verifying Context + pub to_private: VerifyingContext, - /// Private Transfer Verifying Context + /// [`PrivateTransfer`] Verifying Context pub private_transfer: VerifyingContext, - /// Reclaim Verifying Context - pub reclaim: VerifyingContext, + /// [`ToPublic`] Verifying Context + pub to_public: VerifyingContext, } impl MultiVerifyingContext @@ -517,9 +639,9 @@ where #[inline] pub fn select(&self, shape: TransferShape) -> &VerifyingContext { match shape { - TransferShape::Mint => &self.mint, + TransferShape::ToPrivate => &self.to_private, TransferShape::PrivateTransfer => &self.private_transfer, - TransferShape::Reclaim => &self.reclaim, + TransferShape::ToPublic => &self.to_public, } } } @@ -528,26 +650,27 @@ where #[inline] pub fn generate_context( public_parameters: &ProofSystemPublicParameters, - parameters: FullParameters, + parameters: FullParametersRef, rng: &mut R, ) -> Result<(MultiProvingContext, MultiVerifyingContext), ProofSystemError> where C: Configuration, R: CryptoRng + RngCore + ?Sized, { - let mint = Mint::generate_context(public_parameters, parameters, rng)?; - let private_transfer = PrivateTransfer::generate_context(public_parameters, parameters, rng)?; - let reclaim = Reclaim::generate_context(public_parameters, parameters, rng)?; + let to_private = ToPrivate::::generate_context(public_parameters, parameters, rng)?; + let private_transfer = + PrivateTransfer::::generate_context(public_parameters, parameters, rng)?; + let to_public = ToPublic::::generate_context(public_parameters, parameters, rng)?; Ok(( MultiProvingContext { - mint: mint.0, + to_private: to_private.0, private_transfer: private_transfer.0, - reclaim: reclaim.0, + to_public: to_public.0, }, MultiVerifyingContext { - mint: mint.1, + to_private: to_private.1, private_transfer: private_transfer.1, - reclaim: reclaim.1, + to_public: to_public.1, }, )) } diff --git a/manta-accounting/src/transfer/mod.rs b/manta-accounting/src/transfer/mod.rs index c251b78d2..03aa97bf2 100644 --- a/manta-accounting/src/transfer/mod.rs +++ b/manta-accounting/src/transfer/mod.rs @@ -20,8 +20,8 @@ //! following structures: //! //! - Global Configuration: [`Configuration`] -//! - Sender Abstraction: [`Sender`], [`SenderVar`], [`SenderPost`], [`SenderLedger`] -//! - Receiver Abstraction: [`Receiver`], [`ReceiverVar`], [`ReceiverPost`], [`ReceiverLedger`] +//! - Sender Abstraction: [`Sender`], [`SenderPost`], [`SenderLedger`]( +//! - Receiver Abstraction: [`Receiver`], [`ReceiverPost`], [`ReceiverLedger`] //! - Transfer Abstraction: [`Transfer`], [`TransferPost`], [`TransferLedger`] //! - Canonical Transactions: [`canonical`] //! - Batched Transactions: [`batch`] @@ -29,49 +29,51 @@ //! See the [`crate::wallet`] module for more on how this transfer protocol is used in a wallet //! protocol for the keeping of accounts for private assets. -use crate::asset; -use alloc::vec::Vec; -use core::{ - fmt::Debug, - hash::Hash, - iter::Sum, - marker::PhantomData, - ops::{AddAssign, Deref, Sub}, +use crate::{ + asset, + transfer::{ + receiver::{ReceiverLedger, ReceiverPostError}, + sender::{SenderLedger, SenderPostError}, + utxo::{auth, Mint, NullifierIndependence, Spend, UtxoIndependence}, + }, }; +use core::{fmt::Debug, hash::Hash, iter::Sum, ops::AddAssign}; use manta_crypto::{ - accumulator::{self, AssertValidVerification, MembershipProof, Model}, - constraint::{HasInput, ProofSystem}, + accumulator::{self, ItemHashFunction}, + constraint::{HasInput, Input, ProofSystem}, eclair::{ self, alloc::{ mode::{Derived, Public, Secret}, - Allocate, Allocator, Constant, Variable, + Allocate, Allocator, Constant, Var, Variable, }, - bool::{AssertEq, Bool}, + bool::{Assert, AssertEq}, ops::Add, }, - encryption::{self, hybrid::Hybrid, EncryptedMessage}, - key::{self, agreement::Derive}, rand::{CryptoRng, RngCore, Sample}, }; -use manta_util::SizeLimit; +use manta_util::{ + cmp::Independence, + codec::{Encode, Write}, + convert::Field, + vec::{all_unequal, Vec}, +}; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize}; -mod receiver; -mod sender; - pub mod batch; pub mod canonical; +pub mod receiver; +pub mod sender; +pub mod utxo; #[cfg(feature = "test")] #[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] pub mod test; +#[doc(inline)] pub use canonical::Shape; -pub use receiver::*; -pub use sender::*; /// Returns `true` if the [`Transfer`] with this shape would have public participants. #[inline] @@ -79,140 +81,115 @@ pub const fn has_public_participants(sources: usize, sinks: usize) -> bool { (sources + sinks) > 0 } -/// UTXO Commitment Scheme -pub trait UtxoCommitmentScheme { - /// Ephemeral Secret Key Type - type EphemeralSecretKey; - - /// Public Spend Key Type - type PublicSpendKey; - - /// Asset Type - type Asset; - - /// Unspent Transaction Output Type - type Utxo; - - /// Commits to the `ephemeral_secret_key`, `public_spend_key`, and `asset` for a UTXO. - fn commit( - &self, - ephemeral_secret_key: &Self::EphemeralSecretKey, - public_spend_key: &Self::PublicSpendKey, - asset: &Self::Asset, - compiler: &mut COM, - ) -> Self::Utxo; +/// Returns `true` if the [`Transfer`] with this shape would have secret participants. +#[inline] +pub const fn has_secret_participants(senders: usize, receivers: usize) -> bool { + (senders + receivers) > 0 } -/// Void Number Commitment Scheme -pub trait VoidNumberCommitmentScheme { - /// Secret Spend Key Type - type SecretSpendKey; - - /// Unspent Transaction Output Type - type Utxo; - - /// Void Number Type - type VoidNumber; - - /// Commits to the `secret_spend_key` and `utxo` for a Void Number. - fn commit( - &self, - secret_spend_key: &Self::SecretSpendKey, - utxo: &Self::Utxo, - compiler: &mut COM, - ) -> Self::VoidNumber; +/// Returns `true` if the [`Transfer`] with this shape would require an authorization. +#[inline] +pub const fn requires_authorization(senders: usize) -> bool { + senders > 0 } -/// Transfer Configuration +/// Configuration pub trait Configuration { + /// Compiler Type + type Compiler: Assert; + /// Asset Id Type - type AssetId: Clone + Hash + Ord; // Hash + type AssetId: Clone + Ord; /// Asset Value Type - type AssetValue: AddAssign + Clone + Copy + Default + PartialOrd + Sub + Sum; // Sub, Copy - - /// Secret Key Type - type SecretKey: Clone + Sample + SizeLimit; - - /// Public Key Type - type PublicKey: Clone; - - /// Key Agreement Scheme Type - type KeyAgreementScheme: key::agreement::SecretKeyType> - + key::agreement::PublicKeyType> - + key::agreement::EphemeralPublicKeyType> - + key::agreement::EphemeralSecretKeyType> - + key::agreement::ReconstructSecret - + key::agreement::Agree - + key::agreement::Derive - + key::agreement::DeriveEphemeral - + key::agreement::GenerateSecret; - - /// Secret Key Variable Type - type SecretKeyVar: Variable>; - - /// Public Key Variable Type - type PublicKeyVar: Variable> - + eclair::cmp::PartialEq; - - /// Key Agreement Scheme Variable Type - type KeyAgreementSchemeVar: Constant - + key::agreement::SecretKeyType> - + key::agreement::PublicKeyType> - + key::agreement::Agree - + key::agreement::Derive; + type AssetValue: AddAssign + Clone + Default + PartialOrd + Sum; + + /// Associated Data Type + type AssociatedData: Default; /// Unspent Transaction Output Type - type Utxo: PartialEq; - - /// UTXO Commitment Scheme Type - type UtxoCommitmentScheme: UtxoCommitmentScheme< - EphemeralSecretKey = SecretKey, - PublicSpendKey = PublicKey, - Asset = Asset, - Utxo = Utxo, + type Utxo: Independence; + + /// Nullifier Type + type Nullifier: Independence; + + /// Identifier Type + type Identifier: Clone + Sample; + + /// Address Type + type Address: Clone; + + /// Note Type + type Note: AsRef> + Into>; + + /// Mint Secret Type + type MintSecret: utxo::QueryIdentifier, Utxo = Self::Utxo>; + + /// Spend Secret Type + type SpendSecret: utxo::QueryAsset, Utxo = Self::Utxo>; + + /// [`Utxo`] Accumulator Witness Type + type UtxoAccumulatorWitness: Default; + + /// [`Utxo`] Accumulator Output Type + type UtxoAccumulatorOutput: Default; + + /// [`Utxo`] Accumulator Item Hash Type + type UtxoAccumulatorItemHash: ItemHashFunction, Item = UtxoAccumulatorItem>; + + /// Parameters Type + type Parameters: auth::DeriveContext + + auth::ProveAuthorization + + auth::VerifyAuthorization + + auth::DeriveSigningKey + + auth::Sign> + + auth::VerifySignature> + + utxo::AssetType> + + utxo::AssociatedDataType + + utxo::DeriveMint< + Secret = Self::MintSecret, + Utxo = Self::Utxo, + Address = Self::Address, + Note = Self::Note, + > + utxo::DeriveSpend< + UtxoAccumulatorWitness = Self::UtxoAccumulatorWitness, + UtxoAccumulatorOutput = Self::UtxoAccumulatorOutput, + UtxoAccumulatorItemHash = Self::UtxoAccumulatorItemHash, + Secret = Self::SpendSecret, + Nullifier = Self::Nullifier, + Identifier = Self::Identifier, + > + utxo::UtxoReconstruct; + + /// Authorization Context Variable Type + type AuthorizationContextVar: Variable< + Secret, + Self::Compiler, + Type = AuthorizationContext, >; - /// UTXO Variable Type - type UtxoVar: Variable> - + Variable> - + eclair::cmp::PartialEq; + /// Authorization Proof Variable Type + type AuthorizationProofVar: Variable>; - /// UTXO Commitment Scheme Variable Type - type UtxoCommitmentSchemeVar: Constant - + UtxoCommitmentScheme< - Self::Compiler, - EphemeralSecretKey = SecretKeyVar, - PublicSpendKey = PublicKeyVar, - Asset = AssetVar, - Utxo = UtxoVar, - >; - - /// Void Number Type - type VoidNumber: PartialEq; + /// Asset Id Variable Type + type AssetIdVar: Variable + + Variable + + eclair::cmp::PartialEq; - /// Void Number Commitment Scheme Type - type VoidNumberCommitmentScheme: VoidNumberCommitmentScheme< - SecretSpendKey = SecretKey, - Utxo = Utxo, - VoidNumber = VoidNumber, - >; + /// Asset Value Variable Type + type AssetValueVar: Variable + + Variable + + Add + + eclair::cmp::PartialEq; - /// Void Number Variable Type - type VoidNumberVar: Variable - + eclair::cmp::PartialEq; + /// Unspent Transaction Output Variable Type + type UtxoVar: Variable + + Variable; - /// Void Number Commitment Scheme Variable Type - type VoidNumberCommitmentSchemeVar: Constant - + VoidNumberCommitmentScheme< - Self::Compiler, - SecretSpendKey = SecretKeyVar, - Utxo = UtxoVar, - VoidNumber = VoidNumberVar, - >; + /// Note Variable Type + type NoteVar: Variable; - /// UTXO Accumulator Model Type - type UtxoAccumulatorModel: Model; + /// Nullifier Variable Type + type NullifierVar: Variable; /// UTXO Accumulator Witness Variable Type type UtxoAccumulatorWitnessVar: Variable< @@ -229,621 +206,282 @@ pub trait Configuration { >; /// UTXO Accumulator Model Variable Type - type UtxoAccumulatorModelVar: Constant - + AssertValidVerification - + Model< + type UtxoAccumulatorModelVar: Constant> + + accumulator::Model< Self::Compiler, - Item = Self::UtxoVar, Witness = Self::UtxoAccumulatorWitnessVar, Output = Self::UtxoAccumulatorOutputVar, - Verification = Bool, >; - /// Asset Id Variable Type - type AssetIdVar: Variable - + Variable - + eclair::cmp::PartialEq; + /// Mint Secret Variable Type + type MintSecretVar: Variable::Secret>; - /// Asset Value Variable Type - type AssetValueVar: Variable - + Variable - + Add - + eclair::cmp::PartialEq; + /// Spend Secret Variable Type + type SpendSecretVar: Variable< + Secret, + Self::Compiler, + Type = ::Secret, + >; - /// Constraint System Type - type Compiler: AssertEq; + /// Parameters Variable Type + type ParametersVar: Constant + + auth::AssertAuthorized< + Compiler, + AuthorizationContext = Self::AuthorizationContextVar, + AuthorizationProof = Self::AuthorizationProofVar, + > + utxo::AssetType> + + utxo::UtxoType + + Mint + + Spend< + Self::Compiler, + UtxoAccumulatorModel = Self::UtxoAccumulatorModelVar, + Secret = Self::SpendSecretVar, + Nullifier = Self::NullifierVar, + >; /// Proof System Type type ProofSystem: ProofSystem + + HasInput> + HasInput + HasInput + HasInput> + HasInput> - + HasInput> - + HasInput>; - - /// Note Base Encryption Scheme Type - type NoteEncryptionScheme: encryption::Encrypt< - EncryptionKey = SharedSecret, - Randomness = (), - Header = (), - Plaintext = Note, - > + encryption::Decrypt< - DecryptionKey = SharedSecret, - DecryptedPlaintext = Option>, - >; + + HasInput> + + HasInput>; } -/// Asset Type -pub type Asset = asset::Asset<::AssetId, ::AssetValue>; - -/// Asset Variable Type -pub type AssetVar = - asset::Asset<::AssetIdVar, ::AssetValueVar>; - -/// Secret Key Type -pub type SecretKey = ::SecretKey; - -/// Secret Key Variable Type -pub type SecretKeyVar = ::SecretKeyVar; - -/// Public Key Type -pub type PublicKey = ::PublicKey; - -/// Public Key Variable Type -pub type PublicKeyVar = ::PublicKeyVar; - -/// Shared Secret Type -pub type SharedSecret = key::agreement::SharedSecret<::KeyAgreementScheme>; - -/// Unspend Transaction Output Type -pub type Utxo = ::Utxo; - -/// Unspent Transaction Output Variable Type -pub type UtxoVar = ::UtxoVar; - -/// Void Number Type -pub type VoidNumber = ::VoidNumber; - -/// Void Number Variable Type -pub type VoidNumberVar = ::VoidNumberVar; - -/// UTXO Accumulator Witness Type -pub type UtxoAccumulatorWitness = - <::UtxoAccumulatorModel as accumulator::Types>::Witness; - -/// UTXO Accumulator Output Type -pub type UtxoAccumulatorOutput = - <::UtxoAccumulatorModel as accumulator::Types>::Output; - -/// UTXO Membership Proof Type -pub type UtxoMembershipProof = MembershipProof<::UtxoAccumulatorModel>; - -/// UTXO Membership Proof Variable Type -pub type UtxoMembershipProofVar = MembershipProof<::UtxoAccumulatorModelVar>; - -/// Encrypted Note Type -pub type EncryptedNote = EncryptedMessage< - Hybrid<::KeyAgreementScheme, ::NoteEncryptionScheme>, ->; - -/// Transfer Configuration Compiler Type +/// Compiler Type pub type Compiler = ::Compiler; -/// Transfer Proof System Type +/// Proof System Type type ProofSystemType = ::ProofSystem; -/// Transfer Proof System Error Type +/// Proof System Error Type pub type ProofSystemError = as ProofSystem>::Error; -/// Transfer Proof System Public Parameters Type +/// Proof System Public Parameters Type pub type ProofSystemPublicParameters = as ProofSystem>::PublicParameters; -/// Transfer Proving Context Type +/// Proving Context Type pub type ProvingContext = as ProofSystem>::ProvingContext; -/// Transfer Verifying Context Type +/// Verifying Context Type pub type VerifyingContext = as ProofSystem>::VerifyingContext; -/// Transfer Proof System Input Type -pub type ProofInput = <::ProofSystem as ProofSystem>::Input; +/// Proof System Input Type +pub type ProofInput = as ProofSystem>::Input; -/// Transfer Validity Proof Type +/// Validity Proof Type pub type Proof = as ProofSystem>::Proof; -/// Transfer Parameters -#[derive(derivative::Derivative)] -#[derivative( - Clone(bound = r" - C::KeyAgreementScheme: Clone, - C::NoteEncryptionScheme: Clone, - C::UtxoCommitmentScheme: Clone, - C::VoidNumberCommitmentScheme: Clone - "), - Copy(bound = r" - C::KeyAgreementScheme: Copy, - C::NoteEncryptionScheme: Copy, - C::UtxoCommitmentScheme: Copy, - C::VoidNumberCommitmentScheme: Copy - "), - Debug(bound = r" - C::KeyAgreementScheme: Debug, - C::NoteEncryptionScheme: Debug, - C::UtxoCommitmentScheme: Debug, - C::VoidNumberCommitmentScheme: Debug - "), - Default(bound = r" - C::KeyAgreementScheme: Default, - C::NoteEncryptionScheme: Default, - C::UtxoCommitmentScheme: Default, - C::VoidNumberCommitmentScheme: Default - "), - Eq(bound = r" - C::KeyAgreementScheme: Eq, - C::NoteEncryptionScheme: Eq, - C::UtxoCommitmentScheme: Eq, - C::VoidNumberCommitmentScheme: Eq - "), - Hash(bound = r" - C::KeyAgreementScheme: Hash, - C::NoteEncryptionScheme: Hash, - C::UtxoCommitmentScheme: Hash, - C::VoidNumberCommitmentScheme: Hash - "), - PartialEq(bound = r" - C::KeyAgreementScheme: PartialEq, - C::NoteEncryptionScheme: PartialEq, - C::UtxoCommitmentScheme: PartialEq, - C::VoidNumberCommitmentScheme: PartialEq - ") -)] -pub struct Parameters -where - C: Configuration + ?Sized, -{ - /// Note Encryption Scheme - pub note_encryption_scheme: Hybrid, - - /// UTXO Commitment Scheme - pub utxo_commitment: C::UtxoCommitmentScheme, +/// Parameters Type +pub type Parameters = ::Parameters; - /// Void Number Commitment Scheme - pub void_number_commitment: C::VoidNumberCommitmentScheme, -} +/// Parameters Variable Type +pub type ParametersVar = ::ParametersVar; -impl Parameters -where - C: Configuration + ?Sized, -{ - /// Builds a new [`Parameters`] container from `note_encryption_scheme`, `utxo_commitment`, and - /// `void_number_commitment`. - #[inline] - pub fn new( - key_agreement_scheme: C::KeyAgreementScheme, - note_encryption_scheme: C::NoteEncryptionScheme, - utxo_commitment: C::UtxoCommitmentScheme, - void_number_commitment: C::VoidNumberCommitmentScheme, - ) -> Self { - Self { - note_encryption_scheme: Hybrid { - key_agreement_scheme, - encryption_scheme: note_encryption_scheme, - }, - utxo_commitment, - void_number_commitment, - } - } +/// Full Parameters Type +pub type FullParameters<'p, C> = utxo::FullParameters<'p, Parameters>; - /// Returns the [`KeyAgreementScheme`](Configuration::KeyAgreementScheme) associated to `self`. - #[inline] - pub fn key_agreement_scheme(&self) -> &C::KeyAgreementScheme { - &self.note_encryption_scheme.key_agreement_scheme - } +/// Full Parameters Variable Type +pub type FullParametersVar<'p, C> = utxo::FullParameters<'p, ParametersVar, Compiler>; - /// Derives a [`PublicKey`] from a borrowed `secret_key`. - #[inline] - pub fn derive(&self, secret_key: &SecretKey) -> PublicKey { - self.note_encryption_scheme - .key_agreement_scheme - .derive(secret_key, &mut ()) - } +/// Full Parameters Reference Type +pub type FullParametersRef<'p, C> = utxo::FullParametersRef<'p, Parameters>; - /// Computes the [`Utxo`] associated to `ephemeral_secret_key`, `public_spend_key`, and `asset`. - #[inline] - pub fn utxo( - &self, - ephemeral_secret_key: &SecretKey, - public_spend_key: &PublicKey, - asset: &Asset, - ) -> Utxo { - self.utxo_commitment - .commit(ephemeral_secret_key, public_spend_key, asset, &mut ()) - } +/// Full Parameters Reference Variable Type +pub type FullParametersRefVar<'p, C> = utxo::FullParametersRef<'p, ParametersVar, Compiler>; - /// Computes the [`VoidNumber`] associated to `secret_spend_key` and `utxo`. - #[inline] - pub fn void_number(&self, secret_spend_key: &SecretKey, utxo: &Utxo) -> VoidNumber { - self.void_number_commitment - .commit(secret_spend_key, utxo, &mut ()) - } +/// UTXO Accumulator Model Type +pub type UtxoAccumulatorModel = utxo::UtxoAccumulatorModel>; - /// Validates the `utxo` against the `secret_spend_key` and the given `ephemeral_secret_key` - /// and `asset`, returning the void number if the `utxo` is valid. - #[inline] - pub fn check_full_asset( - &self, - secret_spend_key: &SecretKey, - ephemeral_secret_key: &SecretKey, - asset: &Asset, - utxo: &Utxo, - ) -> Option> { - (&self.utxo(ephemeral_secret_key, &self.derive(secret_spend_key), asset) == utxo) - .then(move || self.void_number(secret_spend_key, utxo)) - } -} +/// UTXO Accumulator Model Variable Type +pub type UtxoAccumulatorModelVar = utxo::UtxoAccumulatorModel, Compiler>; -/// Transfer Full Parameters -#[derive(derivative::Derivative)] -#[derivative(Clone(bound = ""), Copy(bound = ""))] -pub struct FullParameters<'p, C> -where - C: Configuration, -{ - /// Base Parameters - pub base: &'p Parameters, +/// UTXO Accumulator Item Type +pub type UtxoAccumulatorItem = utxo::UtxoAccumulatorItem>; - /// UTXO Accumulator Model - pub utxo_accumulator_model: &'p C::UtxoAccumulatorModel, -} - -impl<'p, C> FullParameters<'p, C> -where - C: Configuration, -{ - /// Builds a new [`FullParameters`] from `base` and `utxo_accumulator_model`. - #[inline] - pub fn new( - base: &'p Parameters, - utxo_accumulator_model: &'p C::UtxoAccumulatorModel, - ) -> Self { - Self { - base, - utxo_accumulator_model, - } - } -} +/// UTXO Accumulator Witness Type +pub type UtxoAccumulatorWitness = utxo::UtxoAccumulatorWitness>; -impl<'p, C> AsRef> for FullParameters<'p, C> -where - C: Configuration, -{ - #[inline] - fn as_ref(&self) -> &Parameters { - self.base - } -} +/// UTXO Accumulator Output Type +pub type UtxoAccumulatorOutput = utxo::UtxoAccumulatorOutput>; -impl<'p, C> Deref for FullParameters<'p, C> -where - C: Configuration, -{ - type Target = Parameters; +/// Address Type +pub type Address = utxo::Address>; - #[inline] - fn deref(&self) -> &Self::Target { - self.base - } -} +/// Asset Id Type +pub type AssetId = ::AssetId; -/// Transfer Full Parameters Variables -pub struct FullParametersVar<'p, C> -where - C: Configuration, -{ - /// Key Agreement Scheme - key_agreement: C::KeyAgreementSchemeVar, +/// Asset Value Type +pub type AssetValue = ::AssetValue; - /// UTXO Commitment Scheme - utxo_commitment: C::UtxoCommitmentSchemeVar, +/// Asset Type +pub type Asset = asset::Asset<::AssetId, ::AssetValue>; - /// Void Number Commitment Scheme - void_number_commitment: C::VoidNumberCommitmentSchemeVar, +/// Asset Variable Type +pub type AssetVar = + asset::Asset<::AssetIdVar, ::AssetValueVar>; - /// UTXO Accumulator Model - utxo_accumulator_model: C::UtxoAccumulatorModelVar, +/// Associated Data Type +pub type AssociatedData = utxo::AssociatedData>; - /// Type Parameter Marker - __: PhantomData<&'p ()>, -} +/// Spending Key Type +pub type SpendingKey = auth::SpendingKey>; -impl<'p, C> FullParametersVar<'p, C> -where - C: Configuration, -{ - /// Derives a [`PublicKeyVar`] from `secret_key`. - #[inline] - fn derive(&self, secret_key: &SecretKeyVar, compiler: &mut C::Compiler) -> PublicKeyVar { - self.key_agreement.derive(secret_key, compiler) - } +/// Authorization Context Type +pub type AuthorizationContext = auth::AuthorizationContext>; - /// Computes the [`UtxoVar`] associated to `ephemeral_secret_key`, `public_spend_key`, and - /// `asset`. - #[inline] - fn utxo( - &self, - ephemeral_secret_key: &SecretKeyVar, - public_spend_key: &PublicKeyVar, - asset: &AssetVar, - compiler: &mut C::Compiler, - ) -> UtxoVar { - self.utxo_commitment - .commit(ephemeral_secret_key, public_spend_key, asset, compiler) - } +/// Authorization Context Variable Type +pub type AuthorizationContextVar = auth::AuthorizationContext>; - /// Computes the [`VoidNumberVar`] associated to `secret_spend_key` and `utxo`. - #[inline] - fn void_number( - &self, - secret_spend_key: &SecretKeyVar, - utxo: &UtxoVar, - compiler: &mut C::Compiler, - ) -> VoidNumberVar { - self.void_number_commitment - .commit(secret_spend_key, utxo, compiler) - } -} +/// Authorization Proof Type +pub type AuthorizationProof = auth::AuthorizationProof>; -impl<'p, C> Constant for FullParametersVar<'p, C> -where - C: Configuration, - Parameters: 'p, -{ - type Type = FullParameters<'p, C>; +/// Authorization Proof Variable Type +pub type AuthorizationProofVar = auth::AuthorizationProof>; - #[inline] - fn new_constant(this: &Self::Type, compiler: &mut C::Compiler) -> Self { - Self { - key_agreement: this - .note_encryption_scheme - .key_agreement_scheme - .as_constant(compiler), - utxo_commitment: this.utxo_commitment.as_constant(compiler), - void_number_commitment: this.void_number_commitment.as_constant(compiler), - utxo_accumulator_model: this.utxo_accumulator_model.as_constant(compiler), - __: PhantomData, - } - } -} +/// Authorization Type +pub type Authorization = auth::Authorization>; -/// Spending Key -#[derive(derivative::Derivative)] -#[derivative(Clone(bound = ""))] -pub struct SpendingKey -where - C: Configuration, -{ - /// Spend Part of the Spending Key - spend: SecretKey, +/// Authorization Variable Type +pub type AuthorizationVar = auth::Authorization>; - /// View Part of the Spending Key - view: SecretKey, -} +/// Authorization Key Type +pub type AuthorizationKey = auth::AuthorizationKey>; -impl SpendingKey -where - C: Configuration, -{ - /// Builds a new [`SpendingKey`] from `spend` and `view`. - #[inline] - pub fn new(spend: SecretKey, view: SecretKey) -> Self { - Self { spend, view } - } +/// Authorization Signature Type +pub type AuthorizationSignature = auth::AuthorizationSignature>; - /// Derives the receiving key for `self`. - #[inline] - pub fn derive(&self, parameters: &C::KeyAgreementScheme) -> ReceivingKey { - ReceivingKey { - spend: parameters.derive(&self.spend, &mut ()), - view: parameters.derive(&self.view, &mut ()), - } - } +/// Unspent Transaction Output Type +pub type Utxo = utxo::Utxo>; - /// Validates the `utxo` against `self` and the given `ephemeral_secret_key` and `asset`, - /// returning the void number if the `utxo` is valid. - #[inline] - pub fn check_full_asset( - &self, - parameters: &Parameters, - ephemeral_secret_key: &SecretKey, - asset: &Asset, - utxo: &Utxo, - ) -> Option> { - parameters.check_full_asset(&self.spend, ephemeral_secret_key, asset, utxo) - } +/// Base Note Type +pub type BaseNote = Var<::NoteVar, Public, ::Compiler>; - /// Prepares `self` for spending `asset` with the given `ephemeral_secret_key`. - #[inline] - pub fn sender( - &self, - parameters: &Parameters, - ephemeral_secret_key: SecretKey, - asset: Asset, - ) -> PreSender { - PreSender::new(parameters, self.spend.clone(), ephemeral_secret_key, asset) - } +/// Note Type +pub type Note = utxo::Note>; - /// Prepares `self` for receiving `asset`. - #[inline] - pub fn receiver( - &self, - parameters: &Parameters, - ephemeral_secret_key: SecretKey, - asset: Asset, - ) -> Receiver { - self.derive(parameters.key_agreement_scheme()) - .into_receiver(parameters, ephemeral_secret_key, asset) - } +/// Nullifier Type +pub type Nullifier = utxo::Nullifier>; - /// Returns an receiver-sender pair for internal transactions. - #[inline] - pub fn internal_pair( - &self, - parameters: &Parameters, - ephemeral_secret_key: SecretKey, - asset: Asset, - ) -> (Receiver, PreSender) { - let receiver = self.receiver(parameters, ephemeral_secret_key.clone(), asset.clone()); - let sender = self.sender(parameters, ephemeral_secret_key, asset); - (receiver, sender) - } +/// Identifier Type +pub type Identifier = utxo::Identifier>; - /// Returns an receiver-sender pair of zeroes for internal transactions. - #[inline] - pub fn internal_zero_pair( - &self, - parameters: &Parameters, - ephemeral_secret_key: SecretKey, - asset_id: C::AssetId, - ) -> (Receiver, PreSender) { - self.internal_pair(parameters, ephemeral_secret_key, Asset::::zero(asset_id)) - } -} +/// Identified Asset Type +pub type IdentifiedAsset = utxo::IdentifiedAsset>; -impl Sample for SpendingKey -where - C: Configuration, - D: Clone, - SecretKey: Sample, -{ - #[inline] - fn sample(distribution: D, rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - Self::new( - Sample::sample(distribution.clone(), rng), - Sample::sample(distribution, rng), - ) - } -} +/// Pre-Sender Type +pub type PreSender = sender::PreSender>; -/// Receiving Key -#[cfg_attr( - feature = "serde", - derive(Deserialize, Serialize), - serde( - bound( - deserialize = "PublicKey: Deserialize<'de>", - serialize = "PublicKey: Serialize" - ), - crate = "manta_util::serde", - deny_unknown_fields - ) -)] -#[derive(derivative::Derivative)] -#[derivative( - Clone(bound = ""), - Copy(bound = "PublicKey: Copy"), - Debug(bound = "PublicKey: Debug"), - Eq(bound = "PublicKey: Eq"), - Hash(bound = "PublicKey: Hash"), - PartialEq(bound = "PublicKey: PartialEq") -)] -pub struct ReceivingKey -where - C: Configuration, -{ - /// Spend Part of the Receiving Key - pub spend: PublicKey, +/// Sender Type +pub type Sender = sender::Sender>; - /// View Part of the Receiving Key - pub view: PublicKey, -} +/// Sender Variable Type +pub type SenderVar = sender::Sender, Compiler>; -impl ReceivingKey -where - C: Configuration, -{ - /// Prepares `self` for receiving `asset`. - #[inline] - pub fn into_receiver( - self, - parameters: &Parameters, - ephemeral_secret_key: SecretKey, - asset: Asset, - ) -> Receiver { - Receiver::new( - parameters, - self.spend, - self.view, - ephemeral_secret_key, - asset, - ) - } -} +/// Sender Post Type +pub type SenderPost = sender::SenderPost>; -/// Note -#[derive(derivative::Derivative)] -#[derivative( - Clone(bound = "Asset: Clone, SecretKey: Clone"), - Copy(bound = "Asset: Copy, SecretKey: Copy"), - Debug(bound = "Asset: Debug, SecretKey: Debug"), - Eq(bound = "Asset: Eq, SecretKey: Eq"), - Hash(bound = "Asset: Hash, SecretKey: Hash"), - PartialEq(bound = "Asset: PartialEq, SecretKey: PartialEq") -)] -pub struct Note -where - C: Configuration + ?Sized, -{ - /// Ephemeral Secret Key - pub ephemeral_secret_key: SecretKey, +/// Receiver Type +pub type Receiver = receiver::Receiver>; - /// Asset - pub asset: Asset, -} +/// Receiver Variable Type +pub type ReceiverVar = receiver::Receiver, Compiler>; -impl Note -where - C: Configuration, -{ - /// Builds a new plaintext [`Note`] from `ephemeral_secret_key` and `asset`. - #[inline] - pub fn new(ephemeral_secret_key: SecretKey, asset: Asset) -> Self { - Self { - ephemeral_secret_key, - asset, - } - } -} +/// Receiver Post Type +pub type ReceiverPost = receiver::ReceiverPost>; -impl Sample<(SD, AD)> for Note +/// Generates an internal pair for `asset` against `authorization_context`. +#[inline] +pub fn internal_pair( + parameters: &Parameters, + authorization_context: &mut AuthorizationContext, + address: Address, + asset: Asset, + associated_data: AssociatedData, + rng: &mut R, +) -> (Receiver, PreSender) where C: Configuration, - SecretKey: Sample, - Asset: Sample, + R: CryptoRng + RngCore + ?Sized, { - #[inline] - fn sample(distribution: (SD, AD), rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - Self::new( - Sample::sample(distribution.0, rng), - Sample::sample(distribution.1, rng), - ) - } + let receiver = Receiver::::sample(parameters, address, asset.clone(), associated_data, rng); + let pre_sender = PreSender::::sample( + parameters, + authorization_context, + receiver.identifier(), + asset, + rng, + ); + (receiver, pre_sender) } -impl SizeLimit for Note +/// Generates an internal pair for a zero-asset with the given `asset_id` against +/// `authorization_context`. +#[inline] +pub fn internal_zero_pair( + parameters: &Parameters, + authorization_context: &mut AuthorizationContext, + address: Address, + asset_id: C::AssetId, + associated_data: AssociatedData, + rng: &mut R, +) -> (Receiver, PreSender) where C: Configuration, - Asset: SizeLimit, + R: CryptoRng + RngCore + ?Sized, { - const SIZE: usize = SecretKey::::SIZE + Asset::::SIZE; + internal_pair::( + parameters, + authorization_context, + address, + Asset::::zero(asset_id), + associated_data, + rng, + ) } /// Transfer +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = r" + Authorization: Clone, + C::AssetId: Clone, + C::AssetValue: Clone, + Sender: Clone, + Receiver: Clone"), + Copy(bound = r" + Authorization: Copy, + C::AssetId: Copy, + C::AssetValue: Copy, + Sender: Copy, + Receiver: Copy"), + Debug(bound = r" + Authorization: Debug, + C::AssetId: Debug, + C::AssetValue: Debug, + Sender: Debug, + Receiver: Debug"), + Eq(bound = r" + Authorization: Eq, + C::AssetId: Eq, + C::AssetValue: Eq, + Sender: Eq, + Receiver: Eq"), + Hash(bound = r" + Authorization: Hash, + C::AssetId: Hash, + C::AssetValue: Hash, + Sender: Hash, + Receiver: Hash"), + PartialEq(bound = r" + Authorization: PartialEq, + C::AssetId: PartialEq, + C::AssetValue: PartialEq, + Sender: PartialEq, + Receiver: PartialEq") +)] pub struct Transfer< C, const SOURCES: usize, @@ -853,6 +491,9 @@ pub struct Transfer< > where C: Configuration, { + /// Authorization + authorization: Option>, + /// Asset Id asset_id: Option, @@ -874,53 +515,34 @@ impl>>, asset_id: impl Into>, sources: [C::AssetValue; SOURCES], senders: [Sender; SENDERS], receivers: [Receiver; RECEIVERS], sinks: [C::AssetValue; SINKS], ) -> Self { + let authorization = authorization.into(); let asset_id = asset_id.into(); - Self::check_shape(asset_id.is_some()); - Self::new_unchecked(asset_id, sources, senders, receivers, sinks) - } - - /// Generates the public input for the [`Transfer`] validation proof. - #[inline] - pub fn generate_proof_input(&self) -> ProofInput { - let mut input = Default::default(); - if let Some(asset_id) = &self.asset_id { - C::ProofSystem::extend(&mut input, asset_id); - } - self.sources - .iter() - .for_each(|source| C::ProofSystem::extend(&mut input, source)); - self.senders - .iter() - .for_each(|sender| sender.extend_input(&mut input)); - self.receivers - .iter() - .for_each(|receiver| receiver.extend_input(&mut input)); - self.sinks - .iter() - .for_each(|sink| C::ProofSystem::extend(&mut input, sink)); - input + Self::check_shape(authorization.is_some(), asset_id.is_some()); + Self::new_unchecked(authorization, asset_id, sources, senders, receivers, sinks) } /// Checks that the [`Transfer`] has a valid shape. #[inline] - fn check_shape(has_visible_asset_id: bool) { + pub fn check_shape(has_authorization: bool, has_visible_asset_id: bool) { Self::has_nonempty_input_shape(); Self::has_nonempty_output_shape(); + Self::has_authorization_when_required(has_authorization); Self::has_visible_asset_id_when_required(has_visible_asset_id); } /// Checks that the input side of the transfer is not empty. #[inline] - fn has_nonempty_input_shape() { + pub fn has_nonempty_input_shape() { assert_ne!( SOURCES + SENDERS, 0, @@ -930,7 +552,7 @@ where /// Checks that the output side of the transfer is not empty. #[inline] - fn has_nonempty_output_shape() { + pub fn has_nonempty_output_shape() { assert_ne!( RECEIVERS + SINKS, 0, @@ -938,9 +560,20 @@ where ); } + /// Checks that the given `authorization` for [`Transfer`] building is present exactly when + /// required. + #[inline] + pub fn has_authorization_when_required(has_authorization: bool) { + if requires_authorization(SENDERS) { + assert!(has_authorization, "Missing authorization when required."); + } else { + assert!(!has_authorization, "Given authorization when not required."); + } + } + /// Checks that the given `asset_id` for [`Transfer`] building is visible exactly when required. #[inline] - fn has_visible_asset_id_when_required(has_visible_asset_id: bool) { + pub fn has_visible_asset_id_when_required(has_visible_asset_id: bool) { if has_public_participants(SOURCES, SINKS) { assert!( has_visible_asset_id, @@ -958,6 +591,7 @@ where /// output sides. #[inline] fn new_unchecked( + authorization: Option>, asset_id: Option, sources: [C::AssetValue; SOURCES], senders: [Sender; SENDERS], @@ -965,6 +599,7 @@ where sinks: [C::AssetValue; SINKS], ) -> Self { Self { + authorization, asset_id, sources, senders, @@ -973,9 +608,39 @@ where } } + /// Constructs an [`Asset`] against the `asset_id` of `self` and `value`. + #[inline] + fn construct_asset(&self, value: &C::AssetValue) -> Option> { + Some(Asset::::new(self.asset_id.clone()?, value.clone())) + } + + /// Returns the `k`-th source in the transfer. + #[inline] + pub fn source(&self, k: usize) -> Option> { + self.sources + .get(k) + .and_then(|value| self.construct_asset(value)) + } + + /// Returns the `k`-th sink in the transfer. + #[inline] + pub fn sink(&self, k: usize) -> Option> { + self.sinks + .get(k) + .and_then(|value| self.construct_asset(value)) + } + + /// Generates the public input for the [`Transfer`] validation proof. + #[inline] + pub fn generate_proof_input(&self) -> ProofInput { + let mut input = Default::default(); + self.extend(&mut input); + input + } + /// Builds a constraint system which asserts constraints against unknown variables. #[inline] - pub fn unknown_constraints(parameters: FullParameters) -> C::Compiler { + pub fn unknown_constraints(parameters: FullParametersRef) -> C::Compiler { let mut compiler = C::ProofSystem::context_compiler(); TransferVar::::new_unknown(&mut compiler) .build_validity_constraints(¶meters.as_constant(&mut compiler), &mut compiler); @@ -984,7 +649,7 @@ where /// Builds a constraint system which asserts constraints against known variables. #[inline] - pub fn known_constraints(&self, parameters: FullParameters) -> C::Compiler { + pub fn known_constraints(&self, parameters: FullParametersRef) -> C::Compiler { let mut compiler = C::ProofSystem::proof_compiler(); let transfer: TransferVar = self.as_known(&mut compiler); @@ -996,7 +661,7 @@ where #[inline] pub fn generate_context( public_parameters: &ProofSystemPublicParameters, - parameters: FullParameters, + parameters: FullParametersRef, rng: &mut R, ) -> Result<(ProvingContext, VerifyingContext), ProofSystemError> where @@ -1009,37 +674,156 @@ where ) } - /// Converts `self` into its ledger post. + /// Converts `self` into its [`TransferPostBody`] by building the [`Transfer`] validity proof. + #[allow(clippy::type_complexity)] // FIXME: Use a better abstraction here. + #[inline] + fn into_post_body_with_authorization( + self, + parameters: FullParametersRef, + proving_context: &ProvingContext, + rng: &mut R, + ) -> Result<(TransferPostBody, Option>), ProofSystemError> + where + R: CryptoRng + RngCore + ?Sized, + { + Ok(( + TransferPostBody::build( + C::ProofSystem::prove(proving_context, self.known_constraints(parameters), rng)?, + self.asset_id, + self.sources, + self.senders, + self.receivers, + self.sinks, + ), + self.authorization, + )) + } + + /// Converts `self` into its [`TransferPostBody`] by building the [`Transfer`] validity proof. + #[inline] + pub fn into_post_body( + self, + parameters: FullParametersRef, + proving_context: &ProvingContext, + rng: &mut R, + ) -> Result, ProofSystemError> + where + R: CryptoRng + RngCore + ?Sized, + { + Ok(self + .into_post_body_with_authorization(parameters, proving_context, rng)? + .0) + } + + /// Converts `self` into its [`TransferPost`] by building the [`Transfer`] validity proof and + /// signing the [`TransferPostBody`] payload. + /// + /// Returns `Ok(None)` when the authorization required by this [`Transfer`] is invalid or not + /// provided. Returns `Err` when proof generation fails. #[inline] pub fn into_post( self, - parameters: FullParameters, - context: &ProvingContext, + parameters: FullParametersRef, + proving_context: &ProvingContext, + spending_key: Option<&SpendingKey>, rng: &mut R, - ) -> Result, ProofSystemError> + ) -> Result>, ProofSystemError> where R: CryptoRng + RngCore + ?Sized, { - Ok(TransferPost { - validity_proof: C::ProofSystem::prove( - context, - self.known_constraints(parameters), - rng, - )?, - asset_id: self.asset_id, - sources: self.sources.into(), - sender_posts: self.senders.into_iter().map(Sender::into_post).collect(), - receiver_posts: self - .receivers - .into_iter() - .map(Receiver::into_post) - .collect(), - sinks: self.sinks.into(), - }) + match ( + requires_authorization(SENDERS), + self.authorization.is_some(), + spending_key, + ) { + (true, true, Some(spending_key)) => { + let (body, authorization) = + self.into_post_body_with_authorization(parameters, proving_context, rng)?; + match auth::sign( + parameters.base, + spending_key, + authorization.expect("It is known to be `Some` from the check above."), + &body, + rng, + ) { + Some(authorization_signature) => Ok(Some(TransferPost::new_unchecked( + Some(authorization_signature), + body, + ))), + _ => Ok(None), + } + } + (false, false, None) => Ok(Some(TransferPost::new_unchecked( + None, + self.into_post_body(parameters, proving_context, rng)?, + ))), + _ => Ok(None), + } + } +} + +impl + Input for Transfer +where + C: Configuration, +{ + #[inline] + fn extend(&self, input: &mut ProofInput) { + if let Some(authorization) = &self.authorization { + C::ProofSystem::extend(input, Field::get(authorization)) + } + if let Some(asset_id) = &self.asset_id { + C::ProofSystem::extend(input, asset_id); + } + self.sources + .iter() + .for_each(|source| C::ProofSystem::extend(input, source)); + self.senders + .iter() + .for_each(|sender| C::ProofSystem::extend(input, sender)); + self.receivers + .iter() + .for_each(|receiver| C::ProofSystem::extend(input, receiver)); + self.sinks + .iter() + .for_each(|sink| C::ProofSystem::extend(input, sink)); } } /// Transfer Variable +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = r" + AuthorizationVar: Clone, + C::AssetIdVar: Clone, + C::AssetValueVar: Clone, + SenderVar: Clone, + ReceiverVar: Clone"), + Debug(bound = r" + AuthorizationVar: Debug, + C::AssetIdVar: Debug, + C::AssetValueVar: Debug, + SenderVar: Debug, + ReceiverVar: Debug"), + Eq(bound = r" + AuthorizationVar: Eq, + C::AssetIdVar: Eq, + C::AssetValueVar: Eq, + SenderVar: Eq, + ReceiverVar: Eq"), + Hash(bound = r" + AuthorizationVar: Hash, + C::AssetIdVar: Hash, + C::AssetValueVar: Hash, + SenderVar: Hash, + ReceiverVar: Hash"), + PartialEq(bound = r" + AuthorizationVar: PartialEq, + C::AssetIdVar: PartialEq, + C::AssetValueVar: PartialEq, + SenderVar: PartialEq, + ReceiverVar: PartialEq") +)] struct TransferVar< C, const SOURCES: usize, @@ -1049,6 +833,9 @@ struct TransferVar< > where C: Configuration, { + /// Authorization + authorization: Option>, + /// Asset Id asset_id: Option, @@ -1078,28 +865,19 @@ where compiler: &mut C::Compiler, ) { let mut secret_asset_ids = Vec::with_capacity(SENDERS + RECEIVERS); - let input_sum = Self::value_sum( - self.senders - .into_iter() - .map(|s| { - let asset = s.get_well_formed_asset(parameters, compiler); - secret_asset_ids.push(asset.id); - asset.value - }) - .chain(self.sources) - .collect::>(), + let input_sum = Self::input_sum( + parameters, + &mut secret_asset_ids, + self.authorization, + self.senders, + self.sources, compiler, ); - let output_sum = Self::value_sum( - self.receivers - .into_iter() - .map(|r| { - let asset = r.get_well_formed_asset(parameters, compiler); - secret_asset_ids.push(asset.id); - asset.value - }) - .chain(self.sinks) - .collect::>(), + let output_sum = Self::output_sum( + parameters, + &mut secret_asset_ids, + self.receivers, + self.sinks, compiler, ); compiler.assert_eq(&input_sum, &output_sum); @@ -1109,13 +887,69 @@ where } } + /// Computes the sum over all the input assets, asserting that they are all well-formed. + #[inline] + fn input_sum( + parameters: &FullParametersVar, + secret_asset_ids: &mut Vec, + authorization: Option>, + senders: Vec>, + sources: Vec, + compiler: &mut C::Compiler, + ) -> C::AssetValueVar { + if let Some(mut authorization) = authorization { + authorization.assert_authorized(¶meters.base, compiler); + Self::value_sum( + senders + .into_iter() + .map(|s| { + let asset = s.well_formed_asset( + ¶meters.base, + ¶meters.utxo_accumulator_model, + &mut authorization.context, + compiler, + ); + secret_asset_ids.push(asset.id); + asset.value + }) + .chain(sources) + .collect::>(), + compiler, + ) + } else { + Self::value_sum(sources, compiler) + } + } + + /// Computes the sum over all the output assets, asserting that they are all well-formed. + #[inline] + fn output_sum( + parameters: &FullParametersVar, + secret_asset_ids: &mut Vec, + receivers: Vec>, + sinks: Vec, + compiler: &mut C::Compiler, + ) -> C::AssetValueVar { + Self::value_sum( + receivers + .into_iter() + .map(|r| { + let asset = r.well_formed_asset(¶meters.base, compiler); + secret_asset_ids.push(asset.id); + asset.value + }) + .chain(sinks) + .collect::>(), + compiler, + ) + } + /// Computes the sum of the asset values over `iter`. #[inline] fn value_sum(iter: I, compiler: &mut C::Compiler) -> C::AssetValueVar where I: IntoIterator, { - // TODO: Add a `Sum` trait for `compiler` and just do a sum here. iter.into_iter() .reduce(move |l, r| Add::add(l, r, compiler)) .unwrap() @@ -1129,9 +963,38 @@ where { type Type = Transfer; + #[inline] + fn new_unknown(compiler: &mut C::Compiler) -> Self { + Self { + authorization: requires_authorization(SENDERS).then(|| compiler.allocate_unknown()), + asset_id: has_public_participants(SOURCES, SINKS) + .then(|| compiler.allocate_unknown::()), + sources: (0..SOURCES) + .into_iter() + .map(|_| compiler.allocate_unknown::()) + .collect(), + senders: (0..SENDERS) + .into_iter() + .map(|_| compiler.allocate_unknown()) + .collect(), + receivers: (0..RECEIVERS) + .into_iter() + .map(|_| compiler.allocate_unknown()) + .collect(), + sinks: (0..SINKS) + .into_iter() + .map(|_| compiler.allocate_unknown::()) + .collect(), + } + } + #[inline] fn new_known(this: &Self::Type, compiler: &mut C::Compiler) -> Self { Self { + authorization: this + .authorization + .as_ref() + .map(|authorization| authorization.as_known(compiler)), asset_id: this .asset_id .as_ref() @@ -1158,30 +1021,6 @@ where .collect(), } } - - #[inline] - fn new_unknown(compiler: &mut C::Compiler) -> Self { - Self { - asset_id: has_public_participants(SOURCES, SINKS) - .then(|| compiler.allocate_unknown::()), - sources: (0..SOURCES) - .into_iter() - .map(|_| compiler.allocate_unknown::()) - .collect(), - senders: (0..SENDERS) - .into_iter() - .map(|_| compiler.allocate_unknown()) - .collect(), - receivers: (0..RECEIVERS) - .into_iter() - .map(|_| compiler.allocate_unknown()) - .collect(), - sinks: (0..SINKS) - .into_iter() - .map(|_| compiler.allocate_unknown::()) - .collect(), - } - } } /// Transfer Ledger @@ -1191,11 +1030,22 @@ where /// the [`Transfer`] abstraction. This `trait` inherits from [`SenderLedger`] and [`ReceiverLedger`] /// which validate the [`Sender`] and [`Receiver`] parts of any [`Transfer`]. See their /// documentation for more. -pub trait TransferLedger: SenderLedger)> - + ReceiverLedger)> +pub trait TransferLedger: + SenderLedger< + Parameters, + SuperPostingKey = (Self::ValidProof, TransferLedgerSuperPostingKey), + > + ReceiverLedger< + Parameters, + SuperPostingKey = (Self::ValidProof, TransferLedgerSuperPostingKey), + > where - C: Configuration, + C: Configuration + ?Sized, { + /// Super Posting Key + /// + /// Type that allows super-traits of [`TransferLedger`] to customize posting key behavior. + type SuperPostingKey: Copy; + /// Account Identifier type AccountId; @@ -1236,16 +1086,11 @@ where /// [`check_sink_accounts`](Self::check_sink_accounts) and [`is_valid`](Self::is_valid). type ValidProof: Copy; - /// Super Posting Key - /// - /// Type that allows super-traits of [`TransferLedger`] to customize posting key behavior. - type SuperPostingKey: Copy; - /// Checks that the balances associated to the source accounts are sufficient to withdraw the /// amount given in `sources`. fn check_source_accounts( &self, - asset_id: C::AssetId, + asset_id: &C::AssetId, sources: I, ) -> Result, InvalidSourceAccount> where @@ -1254,37 +1099,32 @@ where /// Checks that the sink accounts exist and balance can be increased by the specified amounts. fn check_sink_accounts( &self, - asset_id: C::AssetId, + asset_id: &C::AssetId, sinks: I, ) -> Result, InvalidSinkAccount> where I: Iterator; - /// Checks that the transfer `proof` is valid. + /// Checks that the transfer proof stored in `posting_key` is valid. fn is_valid( &self, - asset_id: Option, - sources: &[SourcePostingKey], - senders: &[SenderPostingKey], - receivers: &[ReceiverPostingKey], - sinks: &[SinkPostingKey], - proof: Proof, + posting_key: TransferPostingKeyRef, ) -> Option<(Self::ValidProof, Self::Event)>; /// Updates the public balances in the ledger, finishing the transaction. /// - /// # Safety + /// # Crypto Safety /// /// This method can only be called once we check that `proof` is a valid proof and that /// `senders` and `receivers` are valid participants in the transaction. See /// [`is_valid`](Self::is_valid) for more. fn update_public_balances( &mut self, + super_key: &TransferLedgerSuperPostingKey, asset_id: C::AssetId, sources: Vec>, sinks: Vec>, proof: Self::ValidProof, - super_key: &TransferLedgerSuperPostingKey, ) -> Result<(), Self::UpdateError>; } @@ -1294,9 +1134,33 @@ pub type SourcePostingKey = >::ValidSourceAccount; /// Transfer Sink Posting Key Type pub type SinkPostingKey = >::ValidSinkAccount; +/// Transfer Sender Posting Key Type +pub type SenderPostingKey = sender::SenderPostingKey, L>; + +/// Transfer Receiver Posting Key Type +pub type ReceiverPostingKey = receiver::ReceiverPostingKey, L>; + /// Transfer Ledger Super Posting Key Type pub type TransferLedgerSuperPostingKey = >::SuperPostingKey; +/// Invalid Authorization Signature Error +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum InvalidAuthorizationSignature { + /// Invalid Authorization Signature Shape + InvalidShape, + + /// Missing Signature + MissingSignature, + + /// Bad Signature + BadSignature, +} + /// Invalid Source Accounts /// /// This `struct` is the error state of the [`TransferLedger::check_source_accounts`] method. See @@ -1306,10 +1170,18 @@ pub type TransferLedgerSuperPostingKey = >::SuperPo derive(Deserialize, Serialize), serde(crate = "manta_util::serde", deny_unknown_fields) )] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "AccountId: Clone, C::AssetId: Clone, C::AssetValue: Clone"), + Copy(bound = "AccountId: Copy, C::AssetId: Copy, C::AssetValue: Copy"), + Debug(bound = "AccountId: Debug, C::AssetId: Debug, C::AssetValue: Debug"), + Eq(bound = "AccountId: Eq, C::AssetId: Eq, C::AssetValue: Eq"), + Hash(bound = "AccountId: Hash, C::AssetId: Hash, C::AssetValue: Hash"), + PartialEq(bound = "AccountId: PartialEq, C::AssetId: PartialEq, C::AssetValue: PartialEq") +)] pub struct InvalidSourceAccount where - C: Configuration, + C: Configuration + ?Sized, { /// Account Id pub account_id: AccountId, @@ -1330,10 +1202,18 @@ where derive(Deserialize, Serialize), serde(crate = "manta_util::serde", deny_unknown_fields) )] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "AccountId: Clone, C::AssetId: Clone, C::AssetValue: Clone"), + Copy(bound = "AccountId: Copy, C::AssetId: Copy, C::AssetValue: Copy"), + Debug(bound = "AccountId: Debug, C::AssetId: Debug, C::AssetValue: Debug"), + Eq(bound = "AccountId: Eq, C::AssetId: Eq, C::AssetValue: Eq"), + Hash(bound = "AccountId: Hash, C::AssetId: Hash, C::AssetValue: Hash"), + PartialEq(bound = "AccountId: PartialEq, C::AssetId: PartialEq, C::AssetValue: PartialEq") +)] pub struct InvalidSinkAccount where - C: Configuration, + C: Configuration + ?Sized, { /// Account Id pub account_id: AccountId, @@ -1354,8 +1234,16 @@ where derive(Deserialize, Serialize), serde( bound( - deserialize = "InvalidSourceAccount: Deserialize<'de>, InvalidSinkAccount: Deserialize<'de>, UpdateError: Deserialize<'de>", - serialize = "InvalidSourceAccount: Serialize, InvalidSinkAccount: Serialize, UpdateError: Serialize" + deserialize = r" + AccountId: Deserialize<'de>, + UpdateError: Deserialize<'de>, + C::AssetId: Deserialize<'de>, + C::AssetValue: Deserialize<'de>", + serialize = r" + AccountId: Serialize, + UpdateError: Serialize, + C::AssetId: Serialize, + C::AssetValue: Serialize", ), crate = "manta_util::serde", deny_unknown_fields @@ -1363,29 +1251,27 @@ where )] #[derive(derivative::Derivative)] #[derivative( - Clone( - bound = "InvalidSourceAccount: Clone, InvalidSinkAccount: Clone, UpdateError: Clone" - ), - Debug( - bound = "InvalidSourceAccount: Debug, InvalidSinkAccount: Debug, UpdateError: Debug" - ), - Eq( - bound = "InvalidSourceAccount: Eq, InvalidSinkAccount: Eq, UpdateError: Eq" - ), - Hash( - bound = "InvalidSourceAccount: Hash, InvalidSinkAccount: Hash, UpdateError: Hash" - ), + Clone(bound = "AccountId: Clone, UpdateError: Clone, C::AssetId: Clone, C::AssetValue: Clone"), + Copy(bound = "AccountId: Copy, UpdateError: Copy, C::AssetId: Copy, C::AssetValue: Copy"), + Debug(bound = "AccountId: Debug, UpdateError: Debug, C::AssetId: Debug, C::AssetValue: Debug"), + Eq(bound = "AccountId: Eq, UpdateError: Eq, C::AssetId: Eq, C::AssetValue: Eq"), + Hash(bound = "AccountId: Hash, UpdateError: Hash, C::AssetId: Hash, C::AssetValue: Hash"), PartialEq( - bound = "InvalidSourceAccount: PartialEq, InvalidSinkAccount: PartialEq, UpdateError: PartialEq" + bound = "AccountId: PartialEq, UpdateError: PartialEq, C::AssetId: PartialEq, C::AssetValue: PartialEq" ) )] pub enum TransferPostError where - C: Configuration, + C: Configuration + ?Sized, { /// Invalid Transfer Post Shape InvalidShape, + /// Invalid Authorization Signature + /// + /// The authorization signature for the [`TransferPost`] was not valid. + InvalidAuthorizationSignature(InvalidAuthorizationSignature), + /// Invalid Source Accounts InvalidSourceAccount(InvalidSourceAccount), @@ -1401,8 +1287,8 @@ where /// Duplicate Spend Error DuplicateSpend, - /// Duplicate Register Error - DuplicateRegister, + /// Duplicate Mint Error + DuplicateMint, /// Invalid Transfer Proof Error /// @@ -1415,47 +1301,247 @@ where UpdateError(UpdateError), } -impl From> - for TransferPostError +impl From + for TransferPostError +where + C: Configuration + ?Sized, +{ + #[inline] + fn from(err: InvalidAuthorizationSignature) -> Self { + Self::InvalidAuthorizationSignature(err) + } +} + +impl From> + for TransferPostError +where + C: Configuration + ?Sized, +{ + #[inline] + fn from(err: InvalidSourceAccount) -> Self { + Self::InvalidSourceAccount(err) + } +} + +impl From> + for TransferPostError +where + C: Configuration + ?Sized, +{ + #[inline] + fn from(err: InvalidSinkAccount) -> Self { + Self::InvalidSinkAccount(err) + } +} + +impl From + for TransferPostError +where + C: Configuration + ?Sized, +{ + #[inline] + fn from(err: SenderPostError) -> Self { + Self::Sender(err) + } +} + +impl From + for TransferPostError +where + C: Configuration + ?Sized, +{ + #[inline] + fn from(err: ReceiverPostError) -> Self { + Self::Receiver(err) + } +} + +/// Transfer Post Body +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = r" + C::AssetId: Deserialize<'de>, + C::AssetValue: Deserialize<'de>, + SenderPost: Deserialize<'de>, + ReceiverPost: Deserialize<'de>, + Proof: Deserialize<'de>, + ", + serialize = r" + C::AssetId: Serialize, + C::AssetValue: Serialize, + SenderPost: Serialize, + ReceiverPost: Serialize, + Proof: Serialize, + ", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = r" + C::AssetId: Clone, + C::AssetValue: Clone, + SenderPost: Clone, + ReceiverPost: Clone, + Proof: Clone + "), + Debug(bound = r" + C::AssetId: Debug, + C::AssetValue: Debug, + SenderPost: Debug, + ReceiverPost: Debug, + Proof: Debug + "), + Eq(bound = r" + C::AssetId: Eq, + C::AssetValue: Eq, + SenderPost: Eq, + ReceiverPost: Eq, + Proof: Eq + "), + Hash(bound = r" + C::AssetId: Hash, + C::AssetValue: Hash, + SenderPost: Hash, + ReceiverPost: Hash, + Proof: Hash + "), + PartialEq(bound = r" + C::AssetId: PartialEq, + C::AssetValue: PartialEq, + SenderPost: PartialEq, + ReceiverPost: PartialEq, + Proof: PartialEq + ") +)] +pub struct TransferPostBody +where + C: Configuration + ?Sized, +{ + /// Asset Id + pub asset_id: Option, + + /// Sources + pub sources: Vec, + + /// Sender Posts + pub sender_posts: Vec>, + + /// Receiver Posts + pub receiver_posts: Vec>, + + /// Sinks + pub sinks: Vec, + + /// Proof + pub proof: Proof, +} + +impl TransferPostBody where - C: Configuration, + C: Configuration + ?Sized, { + /// Builds a new [`TransferPostBody`]. #[inline] - fn from(err: InvalidSourceAccount) -> Self { - Self::InvalidSourceAccount(err) + fn build< + const SOURCES: usize, + const SENDERS: usize, + const RECEIVERS: usize, + const SINKS: usize, + >( + proof: Proof, + asset_id: Option, + sources: [C::AssetValue; SOURCES], + senders: [Sender; SENDERS], + receivers: [Receiver; RECEIVERS], + sinks: [C::AssetValue; SINKS], + ) -> Self { + Self { + asset_id, + sources: sources.into(), + sender_posts: senders.into_iter().map(Sender::::into_post).collect(), + receiver_posts: receivers + .into_iter() + .map(Receiver::::into_post) + .collect(), + sinks: sinks.into(), + proof, + } } -} -impl From> - for TransferPostError -where - C: Configuration, -{ + /// Constructs an [`Asset`] against the `asset_id` of `self` and `value`. #[inline] - fn from(err: InvalidSinkAccount) -> Self { - Self::InvalidSinkAccount(err) + fn construct_asset(&self, value: &C::AssetValue) -> Option> { + Some(Asset::::new(self.asset_id.clone()?, value.clone())) + } + + /// Returns the `k`-th source in the transfer. + #[inline] + pub fn source(&self, k: usize) -> Option> { + self.sources + .get(k) + .and_then(|value| self.construct_asset(value)) + } + + /// Returns the `k`-th sink in the transfer. + #[inline] + pub fn sink(&self, k: usize) -> Option> { + self.sinks + .get(k) + .and_then(|value| self.construct_asset(value)) } } -impl From - for TransferPostError +impl Encode for TransferPostBody where - C: Configuration, + C: Configuration + ?Sized, + C::AssetId: Encode, + C::AssetValue: Encode, + SenderPost: Encode, + ReceiverPost: Encode, + Proof: Encode, { #[inline] - fn from(err: SenderPostError) -> Self { - Self::Sender(err) + fn encode(&self, mut writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.asset_id.encode(&mut writer)?; + self.sources.encode(&mut writer)?; + self.sender_posts.encode(&mut writer)?; + self.receiver_posts.encode(&mut writer)?; + self.sinks.encode(&mut writer)?; + self.proof.encode(&mut writer)?; + Ok(()) } } -impl From - for TransferPostError +impl Input for TransferPostBody where - C: Configuration, + C: Configuration + ?Sized, { #[inline] - fn from(err: ReceiverPostError) -> Self { - Self::Receiver(err) + fn extend(&self, input: &mut ProofInput) { + if let Some(asset_id) = &self.asset_id { + C::ProofSystem::extend(input, asset_id); + } + self.sources + .iter() + .for_each(|source| C::ProofSystem::extend(input, source)); + self.sender_posts + .iter() + .for_each(|post| C::ProofSystem::extend(input, post)); + self.receiver_posts + .iter() + .for_each(|post| C::ProofSystem::extend(input, post)); + self.sinks + .iter() + .for_each(|sink| C::ProofSystem::extend(input, sink)); } } @@ -1466,18 +1552,12 @@ where serde( bound( deserialize = r" - C::AssetId: Deserialize<'de>, - C::AssetValue: Deserialize<'de>, - SenderPost: Deserialize<'de>, - ReceiverPost: Deserialize<'de>, - Proof: Deserialize<'de>, + AuthorizationSignature: Deserialize<'de>, + TransferPostBody: Deserialize<'de>, ", serialize = r" - C::AssetId: Serialize, - C::AssetValue: Serialize, - SenderPost: Serialize, - ReceiverPost: Serialize, - Proof: Serialize, + AuthorizationSignature: Serialize, + TransferPostBody: Serialize, ", ), crate = "manta_util::serde", @@ -1486,68 +1566,57 @@ where )] #[derive(derivative::Derivative)] #[derivative( - Clone( - bound = "C::AssetId: Clone, C::AssetValue: Clone, SenderPost: Clone, ReceiverPost: Clone, Proof: Clone" - ), - Debug( - bound = "C::AssetId: Debug, C::AssetValue: Debug, SenderPost: Debug, ReceiverPost: Debug, Proof: Debug" - ), - Eq( - bound = "C::AssetId: Eq, C::AssetValue: Eq, SenderPost: Eq, ReceiverPost: Eq, Proof: Eq" - ), - Hash( - bound = "C::AssetId: Hash, C::AssetValue: Hash, SenderPost: Hash, ReceiverPost: Hash, Proof: Hash" - ), - PartialEq( - bound = "C::AssetId: PartialEq, C::AssetValue: PartialEq, SenderPost: PartialEq, ReceiverPost: PartialEq, Proof: PartialEq" - ) + Clone(bound = "AuthorizationSignature: Clone, TransferPostBody: Clone"), + Debug(bound = "AuthorizationSignature: Debug, TransferPostBody: Debug"), + Eq(bound = "AuthorizationSignature: Eq, TransferPostBody: Eq"), + Hash(bound = "AuthorizationSignature: Hash, TransferPostBody: Hash"), + PartialEq(bound = "AuthorizationSignature: PartialEq, TransferPostBody: PartialEq") )] pub struct TransferPost where - C: Configuration, + C: Configuration + ?Sized, { - /// Asset Id - pub asset_id: Option, - - /// Sources - pub sources: Vec, - - /// Sender Posts - pub sender_posts: Vec>, - - /// Receiver Posts - pub receiver_posts: Vec>, - - /// Sinks - pub sinks: Vec, + /// Authorization Signature + pub authorization_signature: Option>, - /// Validity Proof - pub validity_proof: Proof, + /// Transfer Post Body + pub body: TransferPostBody, } impl TransferPost where - C: Configuration, + C: Configuration + ?Sized, { + /// Builds a new [`TransferPost`] without checking the consistency conditions between the `body` + /// and the `authorization_signature`. + #[inline] + fn new_unchecked( + authorization_signature: Option>, + body: TransferPostBody, + ) -> Self { + Self { + authorization_signature, + body, + } + } + + /// Returns the `k`-th source in the transfer. + #[inline] + pub fn source(&self, k: usize) -> Option> { + self.body.source(k) + } + + /// Returns the `k`-th sink in the transfer. + #[inline] + pub fn sink(&self, k: usize) -> Option> { + self.body.sink(k) + } + /// Generates the public input for the [`Transfer`] validation proof. #[inline] pub fn generate_proof_input(&self) -> ProofInput { let mut input = Default::default(); - if let Some(asset_id) = &self.asset_id { - C::ProofSystem::extend(&mut input, asset_id); - } - self.sources - .iter() - .for_each(|source| C::ProofSystem::extend(&mut input, source)); - self.sender_posts - .iter() - .for_each(|post| post.extend_input(&mut input)); - self.receiver_posts - .iter() - .for_each(|post| post.extend_input(&mut input)); - self.sinks - .iter() - .for_each(|sink| C::ProofSystem::extend(&mut input, sink)); + self.extend(&mut input); input } @@ -1560,16 +1629,55 @@ where C::ProofSystem::verify( verifying_context, &self.generate_proof_input(), - &self.validity_proof, + &self.body.proof, ) } + /// Asserts that `self` has a valid proof. See [`has_valid_proof`](Self::has_valid_proof) for + /// more. + #[inline] + pub fn assert_valid_proof(&self, verifying_context: &VerifyingContext) -> &Proof + where + Self: Debug, + ProofSystemError: Debug, + { + assert!( + self.has_valid_proof(verifying_context) + .expect("Unable to verify proof."), + "Invalid TransferPost: {self:?}.", + ); + &self.body.proof + } + + /// Verifies that the authorization signature for `self` is valid under the `parameters`. + #[inline] + pub fn has_valid_authorization_signature( + &self, + parameters: &C::Parameters, + ) -> Result<(), InvalidAuthorizationSignature> { + match ( + &self.authorization_signature, + requires_authorization(self.body.sender_posts.len()), + ) { + (Some(authorization_signature), true) => { + if authorization_signature.verify(parameters, &self.body) { + Ok(()) + } else { + Err(InvalidAuthorizationSignature::BadSignature) + } + } + (Some(_), false) => Err(InvalidAuthorizationSignature::InvalidShape), + (None, true) => Err(InvalidAuthorizationSignature::MissingSignature), + (None, false) => Ok(()), + } + } + /// Checks that the public participant data is well-formed and runs `ledger` validation on /// source and sink accounts. #[allow(clippy::type_complexity)] // FIXME: Use a better abstraction for this. #[inline] fn check_public_participants( - asset_id: Option, + asset_id: &Option, source_accounts: Vec, source_values: Vec, sink_accounts: Vec, @@ -1595,7 +1703,7 @@ where } let sources = if sources > 0 { ledger.check_source_accounts( - asset_id.clone().unwrap(), + asset_id.as_ref().unwrap(), source_accounts.into_iter().zip(source_values), )? } else { @@ -1603,7 +1711,7 @@ where }; let sinks = if sinks > 0 { ledger.check_sink_accounts( - asset_id.unwrap(), + asset_id.as_ref().unwrap(), sink_accounts.into_iter().zip(sink_values), )? } else { @@ -1613,73 +1721,66 @@ where } /// Validates `self` on the transfer `ledger`. - #[allow(clippy::type_complexity)] + #[allow(clippy::type_complexity)] // FIXME: Use a better abstraction for this. #[inline] pub fn validate( self, + parameters: &C::Parameters, + ledger: &L, source_accounts: Vec, sink_accounts: Vec, - ledger: &L, ) -> Result, TransferPostError> where L: TransferLedger, { + self.has_valid_authorization_signature(parameters)?; let (source_posting_keys, sink_posting_keys) = Self::check_public_participants( - self.asset_id.clone(), + &self.body.asset_id, source_accounts, - self.sources, + self.body.sources, sink_accounts, - self.sinks, + self.body.sinks, ledger, )?; - for (i, p) in self.sender_posts.iter().enumerate() { - if self - .sender_posts - .iter() - .skip(i + 1) - .any(move |q| p.void_number == q.void_number) - { - return Err(TransferPostError::DuplicateSpend); - } + if !all_unequal(&self.body.sender_posts, |p, q| { + p.nullifier.is_related(&q.nullifier) + }) { + return Err(TransferPostError::DuplicateSpend); } - for (i, p) in self.receiver_posts.iter().enumerate() { - if self - .receiver_posts - .iter() - .skip(i + 1) - .any(move |q| p.utxo == q.utxo) - { - return Err(TransferPostError::DuplicateRegister); - } + if !all_unequal(&self.body.receiver_posts, |p, q| p.utxo.is_related(&q.utxo)) { + return Err(TransferPostError::DuplicateMint); } let sender_posting_keys = self + .body .sender_posts .into_iter() .map(move |s| s.validate(ledger)) .collect::, _>>()?; let receiver_posting_keys = self + .body .receiver_posts .into_iter() .map(move |r| r.validate(ledger)) .collect::, _>>()?; - let (validity_proof, event) = match ledger.is_valid( - self.asset_id.clone(), - &source_posting_keys, - &sender_posting_keys, - &receiver_posting_keys, - &sink_posting_keys, - self.validity_proof, - ) { - Some((validity_proof, event)) => (validity_proof, event), + let (proof, event) = match ledger.is_valid(TransferPostingKeyRef { + authorization_key: &self.authorization_signature.map(|s| s.authorization_key), + asset_id: &self.body.asset_id, + sources: &source_posting_keys, + senders: &sender_posting_keys, + receivers: &receiver_posting_keys, + sinks: &sink_posting_keys, + proof: self.body.proof, + }) { + Some((proof, event)) => (proof, event), _ => return Err(TransferPostError::InvalidProof), }; Ok(TransferPostingKey { - asset_id: self.asset_id, + asset_id: self.body.asset_id, source_posting_keys, sender_posting_keys, receiver_posting_keys, sink_posting_keys, - validity_proof, + proof, event, }) } @@ -1689,24 +1790,132 @@ where #[inline] pub fn post( self, + parameters: &C::Parameters, + ledger: &mut L, + super_key: &TransferLedgerSuperPostingKey, source_accounts: Vec, sink_accounts: Vec, - super_key: &TransferLedgerSuperPostingKey, - ledger: &mut L, ) -> Result> where L: TransferLedger, { - self.validate(source_accounts, sink_accounts, ledger)? - .post(super_key, ledger) + self.validate(parameters, ledger, source_accounts, sink_accounts)? + .post(ledger, super_key) .map_err(TransferPostError::UpdateError) } } +impl Encode for TransferPost +where + C: Configuration + ?Sized, + AuthorizationSignature: Encode, + TransferPostBody: Encode, +{ + #[inline] + fn encode(&self, mut writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.authorization_signature.encode(&mut writer)?; + self.body.encode(&mut writer)?; + Ok(()) + } +} + +impl Input for TransferPost +where + C: Configuration + ?Sized, +{ + #[inline] + fn extend(&self, input: &mut ProofInput) { + if let Some(authorization_signature) = &self.authorization_signature { + C::ProofSystem::extend(input, &authorization_signature.authorization_key); + } + self.body.extend(input); + } +} + /// Transfer Posting Key +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = r" + C::AssetId: Deserialize<'de>, + SourcePostingKey: Deserialize<'de>, + SenderPostingKey: Deserialize<'de>, + ReceiverPostingKey: Deserialize<'de>, + SinkPostingKey: Deserialize<'de>, + L::ValidProof: Deserialize<'de>, + L::Event: Deserialize<'de>", + serialize = r" + C::AssetId: Serialize, + SourcePostingKey: Serialize, + SenderPostingKey: Serialize, + ReceiverPostingKey: Serialize, + SinkPostingKey: Serialize, + L::ValidProof: Serialize, + L::Event: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = r" + C::AssetId: Clone, + SourcePostingKey: Clone, + SenderPostingKey: Clone, + ReceiverPostingKey: Clone, + SinkPostingKey: Clone, + L::ValidProof: Clone, + L::Event: Clone"), + Debug(bound = r" + C::AssetId: Debug, + SourcePostingKey: Debug, + SenderPostingKey: Debug, + ReceiverPostingKey: Debug, + SinkPostingKey: Debug, + L::ValidProof: Debug, + L::Event: Debug"), + Default(bound = r" + C::AssetId: Default, + SourcePostingKey: Default, + SenderPostingKey: Default, + ReceiverPostingKey: Default, + SinkPostingKey: Default, + L::ValidProof: Default, + L::Event: Default"), + Eq(bound = r" + C::AssetId: Eq, + SourcePostingKey: Eq, + SenderPostingKey: Eq, + ReceiverPostingKey: Eq, + SinkPostingKey: Eq, + L::ValidProof: Eq, + L::Event: Eq"), + Hash(bound = r" + C::AssetId: Hash, + SourcePostingKey: Hash, + SenderPostingKey: Hash, + ReceiverPostingKey: Hash, + SinkPostingKey: Hash, + L::ValidProof: Hash, + L::Event: Hash"), + PartialEq(bound = r" + C::AssetId: PartialEq, + SourcePostingKey: PartialEq, + SenderPostingKey: PartialEq, + ReceiverPostingKey: PartialEq, + SinkPostingKey: PartialEq, + L::ValidProof: PartialEq, + L::Event: PartialEq") +)] pub struct TransferPostingKey where - C: Configuration, + C: Configuration + ?Sized, L: TransferLedger, { /// Asset Id @@ -1724,8 +1933,8 @@ where /// Sink Posting Keys sink_posting_keys: Vec>, - /// Validity Proof Posting Key - validity_proof: L::ValidProof, + /// Proof Posting Key + proof: L::ValidProof, /// Ledger Event event: L::Event, @@ -1733,40 +1942,12 @@ where impl TransferPostingKey where - C: Configuration, + C: Configuration + ?Sized, L: TransferLedger, { - /// Generates the public input for the [`Transfer`] validation proof. - #[inline] - pub fn generate_proof_input( - asset_id: Option, - sources: &[SourcePostingKey], - senders: &[SenderPostingKey], - receivers: &[ReceiverPostingKey], - sinks: &[SinkPostingKey], - ) -> ProofInput { - let mut input = Default::default(); - if let Some(asset_id) = asset_id { - C::ProofSystem::extend(&mut input, &asset_id); - } - sources - .iter() - .for_each(|source| C::ProofSystem::extend(&mut input, source.as_ref())); - senders - .iter() - .for_each(|post| post.extend_input(&mut input)); - receivers - .iter() - .for_each(|post| post.extend_input(&mut input)); - sinks - .iter() - .for_each(|sink| C::ProofSystem::extend(&mut input, sink.as_ref())); - input - } - /// Posts `self` to the transfer `ledger`. /// - /// # Safety + /// # Crypto Safety /// /// This method assumes that posting `self` to `ledger` is atomic and cannot fail. See /// [`SenderLedger::spend`] and [`ReceiverLedger::register`] for more information on the @@ -1774,21 +1955,139 @@ where #[inline] pub fn post( self, - super_key: &TransferLedgerSuperPostingKey, ledger: &mut L, + super_key: &TransferLedgerSuperPostingKey, ) -> Result { - let proof = self.validity_proof; - SenderPostingKey::post_all(self.sender_posting_keys, &(proof, *super_key), ledger); - ReceiverPostingKey::post_all(self.receiver_posting_keys, &(proof, *super_key), ledger); + let proof = self.proof; + SenderPostingKey::::post_all(self.sender_posting_keys, ledger, &(proof, *super_key)); + ReceiverPostingKey::::post_all( + self.receiver_posting_keys, + ledger, + &(proof, *super_key), + ); if let Some(asset_id) = self.asset_id { ledger.update_public_balances( + super_key, asset_id, self.source_posting_keys, self.sink_posting_keys, proof, - super_key, )?; } Ok(self.event) } } + +/// Transfer Posting Key Reference +#[derive(derivative::Derivative)] +#[derivative( + Debug(bound = r" + AuthorizationKey: Debug, + C::AssetId: Debug, + SourcePostingKey: Debug, + SenderPostingKey: Debug, + ReceiverPostingKey: Debug, + SinkPostingKey: Debug, + Proof: Debug"), + Eq(bound = r" + AuthorizationKey: Eq, + C::AssetId: Eq, + SourcePostingKey: Eq, + SenderPostingKey: Eq, + ReceiverPostingKey: Eq, + SinkPostingKey: Eq, + Proof: Eq"), + Hash(bound = r" + AuthorizationKey: Hash, + C::AssetId: Hash, + SourcePostingKey: Hash, + SenderPostingKey: Hash, + ReceiverPostingKey: Hash, + SinkPostingKey: Hash, + Proof: Hash"), + PartialEq(bound = r" + AuthorizationKey: PartialEq, + C::AssetId: PartialEq, + SourcePostingKey: PartialEq, + SenderPostingKey: PartialEq, + ReceiverPostingKey: PartialEq, + SinkPostingKey: PartialEq, + Proof: PartialEq") +)] +pub struct TransferPostingKeyRef<'k, C, L> +where + C: Configuration + ?Sized, + L: TransferLedger + ?Sized, +{ + /// Authorization Key + pub authorization_key: &'k Option>, + + /// Asset Id + pub asset_id: &'k Option, + + /// Sources + pub sources: &'k [SourcePostingKey], + + /// Senders + pub senders: &'k [SenderPostingKey], + + /// Receivers + pub receivers: &'k [ReceiverPostingKey], + + /// Sinks + pub sinks: &'k [SinkPostingKey], + + /// Proof + pub proof: Proof, +} + +impl<'k, C, L> TransferPostingKeyRef<'k, C, L> +where + C: Configuration + ?Sized, + L: TransferLedger + ?Sized, +{ + /// Generates the public input for the [`Transfer`] validation proof. + #[inline] + pub fn generate_proof_input(&self) -> ProofInput { + let mut input = Default::default(); + self.extend(&mut input); + input + } + + /// Verifies the validity proof of `self` according to the `verifying_context`. + #[inline] + pub fn has_valid_proof( + &self, + verifying_context: &VerifyingContext, + ) -> Result> { + C::ProofSystem::verify(verifying_context, &self.generate_proof_input(), &self.proof) + } +} + +impl<'k, C, L> Input for TransferPostingKeyRef<'k, C, L> +where + C: Configuration + ?Sized, + L: TransferLedger + ?Sized, +{ + #[inline] + fn extend(&self, input: &mut ProofInput) { + if let Some(authorization_key) = &self.authorization_key { + C::ProofSystem::extend(input, authorization_key); + } + if let Some(asset_id) = &self.asset_id { + C::ProofSystem::extend(input, asset_id); + } + self.sources + .iter() + .for_each(|source| C::ProofSystem::extend(input, source.as_ref())); + self.senders + .iter() + .for_each(|post| C::ProofSystem::extend(input, post)); + self.receivers + .iter() + .for_each(|post| C::ProofSystem::extend(input, post)); + self.sinks + .iter() + .for_each(|sink| C::ProofSystem::extend(input, sink.as_ref())); + } +} diff --git a/manta-accounting/src/transfer/receiver.rs b/manta-accounting/src/transfer/receiver.rs index 875470043..44f85c65d 100644 --- a/manta-accounting/src/transfer/receiver.rs +++ b/manta-accounting/src/transfer/receiver.rs @@ -16,163 +16,165 @@ //! Transfer Receiver -use crate::transfer::{ - Asset, AssetVar, Configuration, EncryptedNote, FullParametersVar, Note, Parameters, ProofInput, - PublicKey, PublicKeyVar, SecretKey, SecretKeyVar, Utxo, UtxoVar, -}; +use crate::transfer::utxo::{DeriveMint, Identifier, Mint, Note, QueryIdentifier}; use core::{fmt::Debug, hash::Hash, iter}; use manta_crypto::{ - constraint::HasInput, - eclair::{ - alloc::{ - mode::{Derived, Public, Secret}, - Allocate, Allocator, Variable, - }, - bool::AssertEq, + accumulator::{Accumulator, ItemHashFunction}, + constraint::{HasInput, Input}, + eclair::alloc::{ + mode::{Derived, Public, Secret}, + Allocate, Allocator, Constant, Var, Variable, }, - encryption::{hybrid, Encrypt}, + rand::RngCore, }; +use manta_util::codec::{Encode, Write}; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize}; /// Receiver -pub struct Receiver +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "M::Secret: Deserialize<'de>, M::Utxo: Deserialize<'de>, M::Note: Deserialize<'de>", + serialize = "M::Secret: Serialize, M::Utxo: Serialize, M::Note: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "M::Secret: Clone, M::Utxo: Clone, M::Note: Clone"), + Copy(bound = "M::Secret: Copy, M::Utxo: Copy, M::Note: Copy"), + Debug(bound = "M::Secret: Debug, M::Utxo: Debug, M::Note: Debug"), + Default(bound = "M::Secret: Default, M::Utxo: Default, M::Note: Default"), + Eq(bound = "M::Secret: Eq, M::Utxo: Eq, M::Note: Eq"), + Hash(bound = "M::Secret: Hash, M::Utxo: Hash, M::Note: Hash"), + PartialEq(bound = "M::Secret: PartialEq, M::Utxo: PartialEq, M::Note: PartialEq") +)] +pub struct Receiver where - C: Configuration, + M: Mint, { - /// Public Spend Key - public_spend_key: PublicKey, - - /// Ephemeral Secret Spend Key - ephemeral_secret_key: SecretKey, - - /// Asset - asset: Asset, + /// Minting Secret + secret: M::Secret, /// Unspent Transaction Output - utxo: Utxo, + utxo: M::Utxo, - /// Encrypted Note - encrypted_note: EncryptedNote, + /// Note + note: M::Note, } -impl Receiver +impl Receiver where - C: Configuration, + M: Mint, { - /// Build a new [`Receiver`] from `ephemeral_secret_key`, to send `asset` to the owners of - /// `public_spend_key` and `public_view_key`. + /// Builds a new [`Receiver`] from `secret`, `utxo`, and `note`. #[inline] - pub fn new( - parameters: &Parameters, - public_spend_key: PublicKey, - public_view_key: PublicKey, - ephemeral_secret_key: SecretKey, - asset: Asset, - ) -> Self { - let randomness = hybrid::Randomness::from_key(ephemeral_secret_key); - Self { - utxo: parameters.utxo(&randomness.ephemeral_secret_key, &public_spend_key, &asset), - encrypted_note: parameters.note_encryption_scheme.encrypt_into( - &public_view_key, - &randomness, - (), - &Note::new(randomness.ephemeral_secret_key.clone(), asset.clone()), - &mut (), - ), - ephemeral_secret_key: randomness.ephemeral_secret_key, - public_spend_key, - asset, - } + pub fn new(secret: M::Secret, utxo: M::Utxo, note: M::Note) -> Self { + Self { secret, utxo, note } } - /// Returns the ephemeral public key associated to `self`. + /// Returns the asset underlying `self`, asserting that `self` is well-formed. #[inline] - pub fn ephemeral_public_key(&self) -> &PublicKey { - self.encrypted_note.ephemeral_public_key() + pub fn well_formed_asset(&self, parameters: &M, compiler: &mut COM) -> M::Asset { + parameters.well_formed_asset(&self.secret, &self.utxo, &self.note, compiler) } +} - /// Extracts the ledger posting data from `self`. +impl Receiver +where + M: Mint, +{ + /// Samples a new [`Receiver`] that will control `asset` at the given `address`. #[inline] - pub fn into_post(self) -> ReceiverPost { - ReceiverPost { - utxo: self.utxo, - encrypted_note: self.encrypted_note, - } + pub fn sample( + parameters: &M, + address: M::Address, + asset: M::Asset, + associated_data: M::AssociatedData, + rng: &mut R, + ) -> Self + where + M: DeriveMint, + R: RngCore + ?Sized, + { + let (secret, utxo, note) = parameters.derive_mint(address, asset, associated_data, rng); + Self::new(secret, utxo, note) } - /// Extends proof public input with `self`. + /// Inserts the [`Utxo`] corresponding to `self` into the `utxo_accumulator` with the intention + /// of returning a proof later. + /// + /// [`Utxo`]: crate::transfer::utxo::UtxoType::Utxo #[inline] - pub fn extend_input(&self, input: &mut ProofInput) { - C::ProofSystem::extend(input, &self.utxo); + pub fn insert_utxo(&self, parameters: &M, utxo_accumulator: &mut A) -> bool + where + M: ItemHashFunction, + A: Accumulator, + { + utxo_accumulator.insert(¶meters.item_hash(&self.utxo, &mut ())) } -} - -/// Receiver Variable -pub struct ReceiverVar -where - C: Configuration, -{ - /// Ephemeral Secret Spend Key - ephemeral_secret_key: SecretKeyVar, - /// Public Spend Key - public_spend_key: PublicKeyVar, - - /// Asset - asset: AssetVar, + /// Returns the identifier for `self`. + #[inline] + pub fn identifier(&self) -> Identifier + where + M::Secret: QueryIdentifier, + { + self.secret.query_identifier(&self.utxo) + } - /// Unspent Transaction Output - utxo: UtxoVar, + /// Extracts the ledger posting data from `self`. + #[inline] + pub fn into_post(self) -> ReceiverPost { + ReceiverPost::new(self.utxo, self.note) + } } -impl ReceiverVar +impl Input

for Receiver where - C: Configuration, + M: Mint, + P: HasInput + HasInput + ?Sized, { - /// Returns the asset for `self`, checking if `self` is well-formed. #[inline] - pub fn get_well_formed_asset( - self, - parameters: &FullParametersVar, - compiler: &mut C::Compiler, - ) -> AssetVar { - let utxo = parameters.utxo( - &self.ephemeral_secret_key, - &self.public_spend_key, - &self.asset, - compiler, - ); - compiler.assert_eq(&self.utxo, &utxo); - self.asset + fn extend(&self, input: &mut P::Input) { + P::extend(input, &self.utxo); + P::extend(input, &self.note); } } -impl Variable for ReceiverVar +impl Variable for Receiver where - C: Configuration, + M: Mint + Constant, + M::Secret: Variable, + M::Utxo: Variable, + M::Note: Variable, + M::Type: Mint, Utxo = Var>, + Note: AsRef>, { - type Type = Receiver; + type Type = Receiver; #[inline] - fn new_known(this: &Self::Type, compiler: &mut C::Compiler) -> Self { - Self { - ephemeral_secret_key: this.ephemeral_secret_key.as_known(compiler), - public_spend_key: this.public_spend_key.as_known(compiler), - asset: this.asset.as_known::>(compiler), - utxo: this.utxo.as_known::(compiler), - } + fn new_unknown(compiler: &mut COM) -> Self { + Self::new( + compiler.allocate_unknown(), + compiler.allocate_unknown(), + compiler.allocate_unknown(), + ) } #[inline] - fn new_unknown(compiler: &mut C::Compiler) -> Self { - Self { - ephemeral_secret_key: compiler.allocate_unknown(), - public_spend_key: compiler.allocate_unknown(), - asset: compiler.allocate_unknown::>(), - utxo: compiler.allocate_unknown::(), - } + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new( + this.secret.as_known(compiler), + this.utxo.as_known(compiler), + this.note.as_ref().as_known(compiler), + ) } } @@ -181,10 +183,15 @@ where /// This is the validation trait for ensuring that a particular instance of [`Receiver`] is valid /// according to the ledger state. These methods are the minimum required for a ledger which accepts /// the [`Receiver`] abstraction. -pub trait ReceiverLedger +pub trait ReceiverLedger where - C: Configuration, + M: Mint, { + /// Super Posting Key + /// + /// Type that allows super-traits of [`ReceiverLedger`] to customize posting key behavior. + type SuperPostingKey: Copy; + /// Valid [`Utxo`] Posting Key /// /// # Safety @@ -192,21 +199,18 @@ where /// This type must be some wrapper around [`Utxo`] which can only be constructed by this /// implementation of [`ReceiverLedger`]. This is to prevent that [`register`](Self::register) /// is called before [`is_not_registered`](Self::is_not_registered). - type ValidUtxo: AsRef>; - - /// Super Posting Key /// - /// Type that allows super-traits of [`ReceiverLedger`] to customize posting key behavior. - type SuperPostingKey: Copy; + /// [`Utxo`]: crate::transfer::utxo::UtxoType::Utxo + type ValidUtxo: AsRef; /// Checks if the ledger already contains the `utxo` in its set of UTXOs. /// /// Existence of such a UTXO could indicate a possible double-spend. - fn is_not_registered(&self, utxo: Utxo) -> Option; + fn is_not_registered(&self, utxo: M::Utxo) -> Option; - /// Posts the `utxo` and `encrypted_note` to the ledger, registering the asset. + /// Posts the `utxo` and `note` to the ledger, registering the asset. /// - /// # Safety + /// # Crypto Safety /// /// This method can only be called once we check that `utxo` is not already stored on the /// ledger. See [`is_not_registered`](Self::is_not_registered) for more. @@ -214,28 +218,27 @@ where /// # Implementation Note /// /// This method, by default, calls the [`register_all`] method on an iterator of length one - /// containing `(utxo, encrypted_note)`. Either [`register`] or [`register_all`] can be - /// implemented depending on which is more efficient. + /// containing `(utxo, note)`. Either [`register`] or [`register_all`] can be implemented + /// depending on which is more efficient. /// /// [`register`]: Self::register /// [`register_all`]: Self::register_all #[inline] fn register( &mut self, - utxo: Self::ValidUtxo, - encrypted_note: EncryptedNote, super_key: &Self::SuperPostingKey, + utxo: Self::ValidUtxo, + note: M::Note, ) { - self.register_all(iter::once((utxo, encrypted_note)), super_key) + self.register_all(super_key, iter::once((utxo, note))) } - /// Posts all of the [`Utxo`] and [`EncryptedNote`] to the ledger, registering the assets. + /// Posts all of the [`Utxo`] and [`Note`] to the ledger, registering the assets. /// - /// # Safety + /// # Crypto Safety /// - /// This method can only be called once we check that all the [`Utxo`] and [`EncryptedNote`] are - /// not already stored on the ledger. See [`is_not_registered`](Self::is_not_registered) for - /// more. + /// This method can only be called once we check that all the [`Utxo`] and [`Note`] are not + /// already stored on the ledger. See [`is_not_registered`](Self::is_not_registered) for more. /// /// # Implementation Note /// @@ -243,15 +246,17 @@ where /// iterates over `iter` calling [`register`] on each item returned. Either [`register`] or /// [`register_all`] can be implemented depending on which is more efficient. /// + /// [`Utxo`]: crate::transfer::utxo::UtxoType::Utxo + /// [`Note`]: crate::transfer::utxo::NoteType::Note /// [`register`]: Self::register /// [`register_all`]: Self::register_all #[inline] - fn register_all(&mut self, iter: I, super_key: &Self::SuperPostingKey) + fn register_all(&mut self, super_key: &Self::SuperPostingKey, iter: I) where - I: IntoIterator)>, + I: IntoIterator, { - for (utxo, encrypted_note) in iter { - self.register(utxo, encrypted_note, super_key) + for (utxo, note) in iter { + self.register(super_key, utxo, note) } } } @@ -262,11 +267,12 @@ where derive(Deserialize, Serialize), serde(crate = "manta_util::serde", deny_unknown_fields) )] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub enum ReceiverPostError { /// Asset Registered Error /// /// The asset has already been registered with the ledger. + #[default] AssetRegistered, } @@ -285,8 +291,8 @@ pub enum ReceiverPostError { derive(Deserialize, Serialize), serde( bound( - deserialize = "Utxo: Deserialize<'de>, EncryptedNote: Deserialize<'de>", - serialize = "Utxo: Serialize, EncryptedNote: Serialize", + deserialize = "M::Utxo: Deserialize<'de>, M::Note: Deserialize<'de>", + serialize = "M::Utxo: Serialize, M::Note: Serialize", ), crate = "manta_util::serde", deny_unknown_fields @@ -294,100 +300,143 @@ pub enum ReceiverPostError { )] #[derive(derivative::Derivative)] #[derivative( - Clone(bound = "Utxo: Clone, EncryptedNote: Clone"), - Copy(bound = "Utxo: Copy, EncryptedNote: Copy"), - Debug(bound = "Utxo: Debug, EncryptedNote: Debug"), - Eq(bound = "Utxo: Eq, EncryptedNote: Eq"), - Hash(bound = "Utxo: Hash, EncryptedNote: Hash"), - PartialEq(bound = "Utxo: PartialEq, EncryptedNote: PartialEq") + Clone(bound = "M::Utxo: Clone, M::Note: Clone"), + Copy(bound = "M::Utxo: Copy, M::Note: Copy"), + Debug(bound = "M::Utxo: Debug, M::Note: Debug"), + Eq(bound = "M::Utxo: Eq, M::Note: Eq"), + Hash(bound = "M::Utxo: Hash, M::Note: Hash"), + PartialEq(bound = "M::Utxo: PartialEq, M::Note: PartialEq") )] -pub struct ReceiverPost +pub struct ReceiverPost where - C: Configuration, + M: Mint, { /// Unspent Transaction Output - pub utxo: Utxo, + pub utxo: M::Utxo, - /// Encrypted Note - pub encrypted_note: EncryptedNote, + /// Note + pub note: M::Note, } -impl ReceiverPost +impl ReceiverPost where - C: Configuration, + M: Mint, { - /// Returns the ephemeral public key associated to `self`. - #[inline] - pub fn ephemeral_public_key(&self) -> &PublicKey { - self.encrypted_note.ephemeral_public_key() - } - - /// Extends proof public input with `self`. + /// Builds a new [`ReceiverPost`] from `utxo` and `note`. #[inline] - pub fn extend_input(&self, input: &mut ProofInput) { - C::ProofSystem::extend(input, &self.utxo); + pub fn new(utxo: M::Utxo, note: M::Note) -> Self { + Self { utxo, note } } /// Validates `self` on the receiver `ledger`. #[inline] - pub fn validate(self, ledger: &L) -> Result, ReceiverPostError> + pub fn validate(self, ledger: &L) -> Result, ReceiverPostError> where - L: ReceiverLedger, + L: ReceiverLedger, { Ok(ReceiverPostingKey { utxo: ledger .is_not_registered(self.utxo) .ok_or(ReceiverPostError::AssetRegistered)?, - encrypted_note: self.encrypted_note, + note: self.note, }) } } -/// Receiver Posting Key -pub struct ReceiverPostingKey +impl Encode for ReceiverPost where - C: Configuration, - L: ReceiverLedger + ?Sized, + M: Mint, + M::Utxo: Encode, + M::Note: Encode, { - /// UTXO Posting Key - utxo: L::ValidUtxo, - - /// Encrypted Note - encrypted_note: EncryptedNote, + #[inline] + fn encode(&self, mut writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.utxo.encode(&mut writer)?; + self.note.encode(&mut writer)?; + Ok(()) + } } -impl ReceiverPostingKey +impl Input

for ReceiverPost where - C: Configuration, - L: ReceiverLedger + ?Sized, + M: Mint, + P: HasInput + HasInput + ?Sized, { - /// Returns the ephemeral public key associated to `self`. #[inline] - pub fn ephemeral_public_key(&self) -> &PublicKey { - self.encrypted_note.ephemeral_public_key() + fn extend(&self, input: &mut P::Input) { + P::extend(input, &self.utxo); + P::extend(input, &self.note); } +} - /// Extends proof public input with `self`. - #[inline] - pub fn extend_input(&self, input: &mut ProofInput) { - C::ProofSystem::extend(input, self.utxo.as_ref()); - } +/// Receiver Posting Key +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "L::ValidUtxo: Deserialize<'de>, M::Note: Deserialize<'de>", + serialize = "L::ValidUtxo: Serialize, M::Note: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "L::ValidUtxo: Clone, M::Note: Clone"), + Copy(bound = "L::ValidUtxo: Copy, M::Note: Copy"), + Debug(bound = "L::ValidUtxo: Debug, M::Note: Debug"), + Default(bound = "L::ValidUtxo: Default, M::Note: Default"), + Eq(bound = "L::ValidUtxo: Eq, M::Note: Eq"), + Hash(bound = "L::ValidUtxo: Hash, M::Note: Hash"), + PartialEq(bound = "L::ValidUtxo: PartialEq, M::Note: PartialEq") +)] +pub struct ReceiverPostingKey +where + M: Mint, + L: ReceiverLedger + ?Sized, +{ + /// Valid UTXO Posting Key + utxo: L::ValidUtxo, + + /// Note + note: M::Note, +} +impl ReceiverPostingKey +where + M: Mint, + L: ReceiverLedger + ?Sized, +{ /// Posts `self` to the receiver `ledger`. #[inline] - pub fn post(self, super_key: &L::SuperPostingKey, ledger: &mut L) { - ledger.register(self.utxo, self.encrypted_note, super_key); + pub fn post(self, ledger: &mut L, super_key: &L::SuperPostingKey) { + ledger.register(super_key, self.utxo, self.note); } - /// Posts all the of the [`ReceiverPostingKey`] in `iter` to the receiver `ledger`. + /// Posts all the of the [`ReceiverPostingKey`]s in `iter` to the receiver `ledger`. #[inline] - pub fn post_all(iter: I, super_key: &L::SuperPostingKey, ledger: &mut L) + pub fn post_all(iter: I, ledger: &mut L, super_key: &L::SuperPostingKey) where I: IntoIterator, { - ledger.register_all( - iter.into_iter().map(move |k| (k.utxo, k.encrypted_note)), - super_key, - ) + ledger.register_all(super_key, iter.into_iter().map(move |k| (k.utxo, k.note))) + } +} + +impl Input

for ReceiverPostingKey +where + M: Mint, + L: ReceiverLedger + ?Sized, + P: HasInput + HasInput + ?Sized, +{ + #[inline] + fn extend(&self, input: &mut P::Input) { + P::extend(input, self.utxo.as_ref()); + P::extend(input, &self.note); } } diff --git a/manta-accounting/src/transfer/sender.rs b/manta-accounting/src/transfer/sender.rs index 1fc941fc7..44a04ff44 100644 --- a/manta-accounting/src/transfer/sender.rs +++ b/manta-accounting/src/transfer/sender.rs @@ -16,128 +16,185 @@ //! Transfer Sender -use crate::transfer::{ - Asset, AssetVar, Configuration, FullParametersVar, Parameters, ProofInput, SecretKey, - SecretKeyVar, Utxo, UtxoAccumulatorOutput, UtxoMembershipProof, UtxoMembershipProofVar, - VoidNumber, VoidNumberVar, +use crate::transfer::utxo::{ + DeriveSpend, QueryAsset, Spend, UtxoAccumulatorItem, UtxoAccumulatorOutput, UtxoMembershipProof, }; use core::{fmt::Debug, hash::Hash, iter}; use manta_crypto::{ - accumulator::Accumulator, - constraint::HasInput, - eclair::{ - alloc::{ - mode::{Derived, Secret}, - Allocate, Allocator, Variable, - }, - bool::AssertEq, + accumulator::{self, Accumulator, ItemHashFunction}, + constraint::{HasInput, Input}, + eclair::alloc::{ + mode::{Derived, Public, Secret}, + Allocate, Allocator, Const, Constant, Var, Variable, }, + rand::RngCore, }; +use manta_util::codec::{Encode, Write}; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize}; /// Pre-Sender -pub struct PreSender +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = r" + S::Secret: Deserialize<'de>, + S::Utxo: Deserialize<'de>, + S::Nullifier: Deserialize<'de>", + serialize = r" + S::Secret: Serialize, + S::Utxo: Serialize, + S::Nullifier: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "S::Secret: Clone, S::Utxo: Clone, S::Nullifier: Clone"), + Copy(bound = "S::Secret: Copy, S::Utxo: Copy, S::Nullifier: Copy"), + Debug(bound = "S::Secret: Debug, S::Utxo: Debug, S::Nullifier: Debug"), + Default(bound = "S::Secret: Default, S::Utxo: Default, S::Nullifier: Default"), + Eq(bound = "S::Secret: Eq, S::Utxo: Eq, S::Nullifier: Eq"), + Hash(bound = "S::Secret: Hash, S::Utxo: Hash, S::Nullifier: Hash"), + PartialEq(bound = "S::Secret: PartialEq, S::Utxo: PartialEq, S::Nullifier: PartialEq") +)] +pub struct PreSender where - C: Configuration, + S: Spend, { - /// Secret Spend Key - secret_spend_key: SecretKey, - - /// Ephemeral Secret Key - ephemeral_secret_key: SecretKey, - - /// Asset - asset: Asset, + /// Spending Secret + secret: S::Secret, /// Unspent Transaction Output - utxo: Utxo, + utxo: S::Utxo, - /// Void Number - void_number: VoidNumber, + /// Nullifier + nullifier: S::Nullifier, } -impl PreSender +impl PreSender where - C: Configuration, + S: Spend, { - /// Builds a new [`PreSender`] from `ephemeral_secret_key` to claim `asset` with - /// `secret_spend_key`. + /// Builds a new [`PreSender`] from `secret`, `utxo`, and `nullifier`. #[inline] - pub fn new( - parameters: &Parameters, - secret_spend_key: SecretKey, - ephemeral_secret_key: SecretKey, - asset: Asset, - ) -> Self { - let utxo = parameters.utxo( - &ephemeral_secret_key, - ¶meters.derive(&secret_spend_key), - &asset, - ); + pub fn new(secret: S::Secret, utxo: S::Utxo, nullifier: S::Nullifier) -> Self { Self { - void_number: parameters.void_number(&secret_spend_key, &utxo), - secret_spend_key, - ephemeral_secret_key, - asset, + secret, utxo, + nullifier, } } + /// Samples a new [`PreSender`] that will control `asset` at the given `identifier`. + #[inline] + pub fn sample( + parameters: &S, + authorization_context: &mut S::AuthorizationContext, + identifier: S::Identifier, + asset: S::Asset, + rng: &mut R, + ) -> Self + where + S: DeriveSpend, + R: RngCore + ?Sized, + { + let (secret, utxo, nullifier) = + parameters.derive_spend(authorization_context, identifier, asset, rng); + Self::new(secret, utxo, nullifier) + } + /// Inserts the [`Utxo`] corresponding to `self` into the `utxo_accumulator` with the intention /// of returning a proof later by a call to [`get_proof`](Self::get_proof). + /// + /// [`Utxo`]: crate::transfer::utxo::UtxoType::Utxo #[inline] - pub fn insert_utxo(&self, utxo_accumulator: &mut A) -> bool + pub fn insert_utxo(&self, parameters: &S, utxo_accumulator: &mut A) -> bool where - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = S::UtxoAccumulatorModel>, { - utxo_accumulator.insert(&self.utxo) + utxo_accumulator.insert( + ¶meters + .utxo_accumulator_item_hash() + .item_hash(&self.utxo, &mut ()), + ) } /// Requests the membership proof of the [`Utxo`] corresponding to `self` from /// `utxo_accumulator` to prepare the conversion from `self` into a [`Sender`]. + /// + /// [`Utxo`]: crate::transfer::utxo::UtxoType::Utxo #[inline] - pub fn get_proof(&self, utxo_accumulator: &A) -> Option> + pub fn get_proof(&self, parameters: &S, utxo_accumulator: &A) -> Option> where - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = S::UtxoAccumulatorModel>, { Some(SenderProof { - utxo_membership_proof: utxo_accumulator.prove(&self.utxo)?, + utxo_membership_proof: utxo_accumulator.prove( + ¶meters + .utxo_accumulator_item_hash() + .item_hash(&self.utxo, &mut ()), + )?, }) } /// Converts `self` into a [`Sender`] by attaching `proof` to it. #[inline] - pub fn upgrade(self, proof: SenderProof) -> Sender { + pub fn upgrade(self, proof: SenderProof) -> Sender { + Self::upgrade_unchecked(self, proof.utxo_membership_proof) + } + + /// Converts `self` into a [`Sender`] by attaching `proof` to it without necessarily checking + /// that it comes from an accumulator or represents a valid proof. + #[inline] + pub fn upgrade_unchecked(self, proof: UtxoMembershipProof) -> Sender { Sender { - secret_spend_key: self.secret_spend_key, - ephemeral_secret_key: self.ephemeral_secret_key, - asset: self.asset, + secret: self.secret, utxo: self.utxo, - utxo_membership_proof: proof.utxo_membership_proof, - void_number: self.void_number, + utxo_membership_proof: proof, + nullifier: self.nullifier, } } + /// Converts `self` into a [`Sender`] by attaching a default [`UtxoMembershipProof`] to it which + /// in general should not be a valid proof. + #[inline] + pub fn assign_default_proof_unchecked(self) -> Sender + where + UtxoMembershipProof: Default, + { + self.upgrade_unchecked(Default::default()) + } + /// Tries to convert `self` into a [`Sender`] by getting a proof from `utxo_accumulator`. #[inline] - pub fn try_upgrade(self, utxo_accumulator: &A) -> Option> + pub fn try_upgrade(self, parameters: &S, utxo_accumulator: &A) -> Option> where - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = S::UtxoAccumulatorModel>, { - Some(self.get_proof(utxo_accumulator)?.upgrade(self)) + Some(self.get_proof(parameters, utxo_accumulator)?.upgrade(self)) } /// Inserts the [`Utxo`] corresponding to `self` into the `utxo_accumulator` and upgrades to a /// full [`Sender`] if the insertion succeeded. + /// + /// [`Utxo`]: crate::transfer::utxo::UtxoType::Utxo #[inline] - pub fn insert_and_upgrade(self, utxo_accumulator: &mut A) -> Option> + pub fn insert_and_upgrade( + self, + parameters: &S, + utxo_accumulator: &mut A, + ) -> Option> where - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = S::UtxoAccumulatorModel>, { - if self.insert_utxo(utxo_accumulator) { - self.try_upgrade(utxo_accumulator) + if self.insert_utxo(parameters, utxo_accumulator) { + self.try_upgrade(parameters, utxo_accumulator) } else { None } @@ -148,167 +205,234 @@ where /// /// This `struct` is created by the [`get_proof`](PreSender::get_proof) method on [`PreSender`]. /// See its documentation for more. -pub struct SenderProof +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = r"UtxoMembershipProof: Deserialize<'de>", + serialize = r"UtxoMembershipProof: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "UtxoMembershipProof: Clone"), + Copy(bound = "UtxoMembershipProof: Copy"), + Debug(bound = "UtxoMembershipProof: Debug"), + Default(bound = "UtxoMembershipProof: Default"), + Eq(bound = "UtxoMembershipProof: Eq"), + Hash(bound = "UtxoMembershipProof: Hash"), + PartialEq(bound = "UtxoMembershipProof: PartialEq") +)] +pub struct SenderProof where - C: Configuration, + S: Spend, { /// UTXO Membership Proof - utxo_membership_proof: UtxoMembershipProof, + utxo_membership_proof: UtxoMembershipProof, } -impl SenderProof +impl SenderProof where - C: Configuration, + S: Spend, { /// Upgrades the `pre_sender` to a [`Sender`] by attaching `self` to it. #[inline] - pub fn upgrade(self, pre_sender: PreSender) -> Sender { + pub fn upgrade(self, pre_sender: PreSender) -> Sender { pre_sender.upgrade(self) } } /// Sender -pub struct Sender +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = r" + S::Secret: Deserialize<'de>, + S::Utxo: Deserialize<'de>, + UtxoMembershipProof: Deserialize<'de>, + S::Nullifier: Deserialize<'de>", + serialize = r" + S::Secret: Serialize, + S::Utxo: Serialize, + UtxoMembershipProof: Serialize, + S::Nullifier: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = r" + S::Secret: Clone, + S::Utxo: Clone, + UtxoMembershipProof: Clone, + S::Nullifier: Clone"), + Copy(bound = r" + S::Secret: Copy, + S::Utxo: Copy, + UtxoMembershipProof: Copy, + S::Nullifier: Copy"), + Debug(bound = r" + S::Secret: Debug, + S::Utxo: Debug, + UtxoMembershipProof: Debug, + S::Nullifier: Debug"), + Default(bound = r" + S::Secret: Default, + S::Utxo: Default, + UtxoMembershipProof: Default, + S::Nullifier: Default"), + Eq(bound = r" + S::Secret: Eq, + S::Utxo: Eq, + UtxoMembershipProof: Eq, + S::Nullifier: Eq"), + Hash(bound = r" + S::Secret: Hash, + S::Utxo: Hash, + UtxoMembershipProof: Hash, + S::Nullifier: Hash"), + PartialEq(bound = r" + S::Secret: PartialEq, + S::Utxo: PartialEq, + UtxoMembershipProof: PartialEq, + S::Nullifier: PartialEq") +)] +pub struct Sender where - C: Configuration, + S: Spend, { - /// Secret Spend Key - secret_spend_key: SecretKey, - - /// Ephemeral Secret Key - ephemeral_secret_key: SecretKey, - - /// Asset - asset: Asset, + /// Spending Secret + secret: S::Secret, /// Unspent Transaction Output - utxo: Utxo, + utxo: S::Utxo, /// UTXO Membership Proof - utxo_membership_proof: UtxoMembershipProof, + utxo_membership_proof: UtxoMembershipProof, - /// Void Number - void_number: VoidNumber, + /// Nullifier + nullifier: S::Nullifier, } -impl Sender +impl Sender where - C: Configuration, + S: Spend, { - /// Returns the asset value sent by `self` in the transaction. + /// Builds a new [`Sender`] from `secret`, `utxo`, and `nullifier`. #[inline] - pub fn asset_value(&self) -> C::AssetValue { - self.asset.value - } - - /// Reverts `self` back into a [`PreSender`]. - /// - /// This method should be called if the [`Utxo`] membership proof attached to `self` was deemed - /// invalid or had expired. - #[inline] - pub fn downgrade(self) -> PreSender { - PreSender { - secret_spend_key: self.secret_spend_key, - ephemeral_secret_key: self.ephemeral_secret_key, - asset: self.asset, - utxo: self.utxo, - void_number: self.void_number, - } - } - - /// Extracts the ledger posting data from `self`. - #[inline] - pub fn into_post(self) -> SenderPost { - SenderPost { - utxo_accumulator_output: self.utxo_membership_proof.into_output(), - void_number: self.void_number, + pub fn new( + secret: S::Secret, + utxo: S::Utxo, + utxo_membership_proof: UtxoMembershipProof, + nullifier: S::Nullifier, + ) -> Self { + Self { + secret, + utxo, + utxo_membership_proof, + nullifier, } } - /// Extends proof public input with `self`. + /// Returns the asset underlying `self`, asserting that `self` is well-formed. #[inline] - pub fn extend_input(&self, input: &mut ProofInput) { - C::ProofSystem::extend(input, self.utxo_membership_proof.output()); - C::ProofSystem::extend(input, &self.void_number); + pub fn well_formed_asset( + &self, + parameters: &S, + utxo_accumulator_model: &S::UtxoAccumulatorModel, + authorization_context: &mut S::AuthorizationContext, + compiler: &mut COM, + ) -> S::Asset { + let (asset, nullifier) = parameters.well_formed_asset( + utxo_accumulator_model, + authorization_context, + &self.secret, + &self.utxo, + &self.utxo_membership_proof, + compiler, + ); + parameters.assert_equal_nullifiers(&self.nullifier, &nullifier, compiler); + asset } } -/// Sender Variable -pub struct SenderVar +impl Sender where - C: Configuration, + S: Spend, { - /// Secret Spend Key - secret_spend_key: SecretKeyVar, - - /// Ephemeral Secret Key - ephemeral_secret_key: SecretKeyVar, - - /// Asset - asset: AssetVar, - - /// UTXO Membership Proof - utxo_membership_proof: UtxoMembershipProofVar, + /// Returns the underlying asset for `self`. + #[inline] + pub fn asset(&self) -> S::Asset + where + S::Secret: QueryAsset, + { + self.secret.query_asset(&self.utxo) + } - /// Void Number - void_number: VoidNumberVar, + /// Extracts the ledger posting data from `self`. + #[inline] + pub fn into_post(self) -> SenderPost { + SenderPost::new(self.utxo_membership_proof.into_output(), self.nullifier) + } } -impl SenderVar +impl Input

for Sender where - C: Configuration, + S: Spend, + P: HasInput> + HasInput + ?Sized, { - /// Returns the asset for `self`, checking if `self` is well-formed. #[inline] - pub fn get_well_formed_asset( - self, - parameters: &FullParametersVar, - compiler: &mut C::Compiler, - ) -> AssetVar { - let public_spend_key = parameters.derive(&self.secret_spend_key, compiler); - let utxo = parameters.utxo( - &self.ephemeral_secret_key, - &public_spend_key, - &self.asset, - compiler, - ); - self.utxo_membership_proof.assert_valid( - ¶meters.utxo_accumulator_model, - &utxo, - compiler, - ); - let void_number = parameters.void_number(&self.secret_spend_key, &utxo, compiler); - compiler.assert_eq(&self.void_number, &void_number); - self.asset + fn extend(&self, input: &mut P::Input) { + P::extend(input, self.utxo_membership_proof.output()); + P::extend(input, &self.nullifier); } } -impl Variable for SenderVar +impl Variable for Sender where - C: Configuration, + S: Spend + Constant, + S::UtxoAccumulatorModel: Constant, + Const: accumulator::Model, + S::Secret: Variable, + S::Utxo: Variable, + UtxoMembershipProof: + Variable, COM, Type = UtxoMembershipProof>, + S::Nullifier: Variable, + S::Type: Spend< + UtxoAccumulatorModel = Const, + Secret = Var, + Utxo = Var, + Nullifier = Var, + >, { - type Type = Sender; + type Type = Sender; #[inline] - fn new_known(this: &Self::Type, compiler: &mut C::Compiler) -> Self { - Self { - secret_spend_key: this.secret_spend_key.as_known(compiler), - ephemeral_secret_key: this.ephemeral_secret_key.as_known(compiler), - asset: this.asset.as_known::>(compiler), - utxo_membership_proof: this.utxo_membership_proof.as_known(compiler), - void_number: this.void_number.as_known(compiler), - } + fn new_unknown(compiler: &mut COM) -> Self { + Self::new( + compiler.allocate_unknown(), + compiler.allocate_unknown(), + compiler.allocate_unknown(), + compiler.allocate_unknown(), + ) } #[inline] - fn new_unknown(compiler: &mut C::Compiler) -> Self { - Self { - secret_spend_key: compiler.allocate_unknown(), - ephemeral_secret_key: compiler.allocate_unknown(), - asset: compiler.allocate_unknown::>(), - utxo_membership_proof: compiler.allocate_unknown(), - void_number: compiler.allocate_unknown(), - } + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new( + this.secret.as_known(compiler), + this.utxo.as_known(compiler), + this.utxo_membership_proof.as_known(compiler), + this.nullifier.as_known(compiler), + ) } } @@ -317,41 +441,48 @@ where /// This is the validation trait for ensuring that a particular instance of [`Sender`] is valid /// according to the ledger state. These methods are the minimum required for a ledger which accepts /// the [`Sender`] abstraction. -pub trait SenderLedger +pub trait SenderLedger where - C: Configuration, + S: Spend, { - /// Valid [`VoidNumber`] Posting Key - /// - /// # Safety + /// Super Posting Key /// - /// This type must be some wrapper around [`VoidNumber`] which can only be constructed by this - /// implementation of [`SenderLedger`]. This is to prevent that [`spend`](Self::spend) is - /// called before [`is_unspent`](Self::is_unspent) and - /// [`has_matching_utxo_accumulator_output`](Self::has_matching_utxo_accumulator_output). - type ValidVoidNumber: AsRef>; + /// Type that allows super-traits of [`SenderLedger`] to customize posting key behavior. + type SuperPostingKey: Copy; /// Valid UTXO Accumulator Output Posting Key /// /// # Safety /// - /// This type must be some wrapper around [`S::Output`] which can only be constructed by this - /// implementation of [`SenderLedger`]. This is to prevent that [`spend`](Self::spend) is - /// called before [`is_unspent`](Self::is_unspent) and - /// [`has_matching_utxo_accumulator_output`](Self::has_matching_utxo_accumulator_output). + /// This type must be some wrapper around [`UtxoAccumulatorOutput`] that can only be + /// constructed by this implementation of [`SenderLedger`]. This is to prevent that [`spend`] is + /// called before [`is_unspent`] and [`has_matching_utxo_accumulator_output`]. /// - /// [`S::Output`]: manta_crypto::accumulator::Types::Output - type ValidUtxoAccumulatorOutput: AsRef>; + /// [`UtxoAccumulatorOutput`]: UtxoAccumulatorOutput + /// [`spend`]: Self::spend + /// [`is_unspent`]: Self::is_unspent + /// [`has_matching_utxo_accumulator_output`]: Self::has_matching_utxo_accumulator_output + type ValidUtxoAccumulatorOutput: AsRef>; - /// Super Posting Key + /// Valid [`Nullifier`] Posting Key /// - /// Type that allows super-traits of [`SenderLedger`] to customize posting key behavior. - type SuperPostingKey: Copy; + /// # Safety + /// + /// This type must be some wrapper around [`Nullifier`] that can only be constructed by this + /// implementation of [`SenderLedger`]. This is to prevent that [`spend`] is called before + /// [`is_unspent`] and [`has_matching_utxo_accumulator_output`]. + /// + /// [`Nullifier`]: crate::transfer::utxo::NullifierType::Nullifier + /// [`spend`]: Self::spend + /// [`is_unspent`]: Self::is_unspent + /// [`has_matching_utxo_accumulator_output`]: Self::has_matching_utxo_accumulator_output + type ValidNullifier: AsRef; - /// Checks if the ledger already contains the `void_number` in its set of void numbers. + /// Checks if the ledger already contains the `nullifier` in its set of nullifiers. /// - /// Existence of such a void number could indicate a possible double-spend. - fn is_unspent(&self, void_number: VoidNumber) -> Option; + /// Existence of such a nullifier could indicate a possible double-spend and so the ledger does + /// not accept duplicates. + fn is_unspent(&self, nullifier: S::Nullifier) -> Option; /// Checks if `output` matches the current accumulated value of the UTXO accumulator that is /// stored on the ledger. @@ -360,42 +491,39 @@ where /// older state of the ledger. fn has_matching_utxo_accumulator_output( &self, - output: UtxoAccumulatorOutput, + output: UtxoAccumulatorOutput, ) -> Option; - /// Posts the `void_number` to the ledger, spending the asset. + /// Posts the `nullifier` to the ledger, spending the asset. /// - /// # Safety + /// # Crypto Safety /// - /// This method can only be called once we check that `void_number` is not already stored on + /// This method can only be called once we check that `nullifier` is not already stored on /// the ledger. See [`is_unspent`](Self::is_unspent) for more. /// /// # Implementation Note /// - /// This method, by defualt, calls the [`spend_all`] method on an iterator of length one - /// containing `(utxo, encrypted_note)`. Either [`spend`] or [`spend_all`] can be implemented - /// depending on which is more efficient. + /// This method, by default, calls the [`spend_all`] method on an iterator of length one + /// containing `(utxo_accumulator_output, nullifier)`. Either [`spend`] or [`spend_all`] can be + /// implemented depending on which is more efficient. /// /// [`spend`]: Self::spend /// [`spend_all`]: Self::spend_all #[inline] fn spend( &mut self, - utxo_accumulator_output: Self::ValidUtxoAccumulatorOutput, - void_number: Self::ValidVoidNumber, super_key: &Self::SuperPostingKey, + utxo_accumulator_output: Self::ValidUtxoAccumulatorOutput, + nullifier: Self::ValidNullifier, ) { - self.spend_all( - iter::once((utxo_accumulator_output, void_number)), - super_key, - ) + self.spend_all(super_key, iter::once((utxo_accumulator_output, nullifier))) } - /// Posts all of the [`VoidNumber`] to the ledger, spending the assets. + /// Posts all of the [`Nullifier`]s to the ledger, spending the assets. /// - /// # Safety + /// # Crypto Safety /// - /// This method can only be called once we check that all the [`VoidNumber`] are not already + /// This method can only be called once we check that all the [`Nullifier`]s are not already /// stored on the ledger. See [`is_unspent`](Self::is_unspent) for more. /// /// # Implementation Note @@ -404,15 +532,16 @@ where /// iterates over `iter` calling [`spend`] on each item returned. Either [`spend`] or /// [`spend_all`] can be implemented depending on which is more efficient. /// + /// [`Nullifier`]: crate::transfer::utxo::NullifierType::Nullifier /// [`spend`]: Self::spend /// [`spend_all`]: Self::spend_all #[inline] - fn spend_all(&mut self, iter: I, super_key: &Self::SuperPostingKey) + fn spend_all(&mut self, super_key: &Self::SuperPostingKey, iter: I) where - I: IntoIterator, + I: IntoIterator, { - for (utxo_accumulator_output, void_number) in iter { - self.spend(utxo_accumulator_output, void_number, super_key) + for (utxo_accumulator_output, nullifier) in iter { + self.spend(super_key, utxo_accumulator_output, nullifier) } } } @@ -425,15 +554,15 @@ where )] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum SenderPostError { - /// Asset Spent Error - /// - /// The asset has already been spent. - AssetSpent, - /// Invalid UTXO Accumulator Output Error /// /// The sender was not constructed under the current state of the UTXO accumulator. InvalidUtxoAccumulatorOutput, + + /// Asset Spent Error + /// + /// The asset has already been spent. + AssetSpent, } /// Sender Post @@ -451,8 +580,12 @@ pub enum SenderPostError { derive(Deserialize, Serialize), serde( bound( - deserialize = "UtxoAccumulatorOutput: Deserialize<'de>, VoidNumber: Deserialize<'de>", - serialize = "UtxoAccumulatorOutput: Serialize, VoidNumber: Serialize", + deserialize = r" + UtxoAccumulatorOutput: Deserialize<'de>, + S::Nullifier: Deserialize<'de>", + serialize = r" + UtxoAccumulatorOutput: Serialize, + S::Nullifier: Serialize", ), crate = "manta_util::serde", deny_unknown_fields @@ -460,93 +593,156 @@ pub enum SenderPostError { )] #[derive(derivative::Derivative)] #[derivative( - Clone(bound = "UtxoAccumulatorOutput: Clone, VoidNumber: Clone"), - Copy(bound = "UtxoAccumulatorOutput: Copy, VoidNumber: Copy"), - Debug(bound = "UtxoAccumulatorOutput: Debug, VoidNumber: Debug"), - Eq(bound = "UtxoAccumulatorOutput: Eq, VoidNumber: Eq"), - Hash(bound = "UtxoAccumulatorOutput: Hash, VoidNumber: Hash"), - PartialEq(bound = "UtxoAccumulatorOutput: PartialEq, VoidNumber: PartialEq") + Clone(bound = "UtxoAccumulatorOutput: Clone, S::Nullifier: Clone"), + Copy(bound = "UtxoAccumulatorOutput: Copy, S::Nullifier: Copy"), + Debug(bound = "UtxoAccumulatorOutput: Debug, S::Nullifier: Debug"), + Eq(bound = "UtxoAccumulatorOutput: Eq, S::Nullifier: Eq"), + Hash(bound = "UtxoAccumulatorOutput: Hash, S::Nullifier: Hash"), + PartialEq(bound = "UtxoAccumulatorOutput: PartialEq, S::Nullifier: PartialEq") )] -pub struct SenderPost +pub struct SenderPost where - C: Configuration, + S: Spend, { /// UTXO Accumulator Output - pub utxo_accumulator_output: UtxoAccumulatorOutput, + pub utxo_accumulator_output: UtxoAccumulatorOutput, - /// Void Number - pub void_number: VoidNumber, + /// Nullifier + pub nullifier: S::Nullifier, } -impl SenderPost +impl SenderPost where - C: Configuration, + S: Spend, { - /// Extends proof public input with `self`. + /// Builds a new [`SenderPost`] from `utxo_accumulator_output` and `nullifier`. #[inline] - pub fn extend_input(&self, input: &mut ProofInput) { - C::ProofSystem::extend(input, &self.utxo_accumulator_output); - C::ProofSystem::extend(input, &self.void_number); + pub fn new(utxo_accumulator_output: UtxoAccumulatorOutput, nullifier: S::Nullifier) -> Self { + Self { + utxo_accumulator_output, + nullifier, + } } /// Validates `self` on the sender `ledger`. #[inline] - pub fn validate(self, ledger: &L) -> Result, SenderPostError> + pub fn validate(self, ledger: &L) -> Result, SenderPostError> where - L: SenderLedger, + L: SenderLedger, { Ok(SenderPostingKey { utxo_accumulator_output: ledger .has_matching_utxo_accumulator_output(self.utxo_accumulator_output) .ok_or(SenderPostError::InvalidUtxoAccumulatorOutput)?, - void_number: ledger - .is_unspent(self.void_number) + nullifier: ledger + .is_unspent(self.nullifier) .ok_or(SenderPostError::AssetSpent)?, }) } } +impl Encode for SenderPost +where + S: Spend, + UtxoAccumulatorOutput: Encode, + S::Nullifier: Encode, +{ + #[inline] + fn encode(&self, mut writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.utxo_accumulator_output.encode(&mut writer)?; + self.nullifier.encode(&mut writer)?; + Ok(()) + } +} + +impl Input

for SenderPost +where + S: Spend, + P: HasInput> + HasInput + ?Sized, +{ + #[inline] + fn extend(&self, input: &mut P::Input) { + P::extend(input, &self.utxo_accumulator_output); + P::extend(input, &self.nullifier); + } +} + /// Sender Posting Key -pub struct SenderPostingKey +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = r" + L::ValidUtxoAccumulatorOutput: Deserialize<'de>, + L::ValidNullifier: Deserialize<'de>", + serialize = r" + L::ValidUtxoAccumulatorOutput: Serialize, + L::ValidNullifier: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "L::ValidUtxoAccumulatorOutput: Clone, L::ValidNullifier: Clone"), + Copy(bound = "L::ValidUtxoAccumulatorOutput: Copy, L::ValidNullifier: Copy"), + Debug(bound = "L::ValidUtxoAccumulatorOutput: Debug, L::ValidNullifier: Debug"), + Default(bound = "L::ValidUtxoAccumulatorOutput: Default, L::ValidNullifier: Default"), + Eq(bound = "L::ValidUtxoAccumulatorOutput: Eq, L::ValidNullifier: Eq"), + Hash(bound = "L::ValidUtxoAccumulatorOutput: Hash, L::ValidNullifier: Hash"), + PartialEq(bound = "L::ValidUtxoAccumulatorOutput: PartialEq, L::ValidNullifier: PartialEq") +)] +pub struct SenderPostingKey where - C: Configuration, - L: SenderLedger + ?Sized, + S: Spend, + L: SenderLedger + ?Sized, { /// UTXO Accumulator Output Posting Key utxo_accumulator_output: L::ValidUtxoAccumulatorOutput, - /// Void Number Posting Key - void_number: L::ValidVoidNumber, + /// Nullifier Posting Key + nullifier: L::ValidNullifier, } -impl SenderPostingKey +impl SenderPostingKey where - C: Configuration, - L: SenderLedger + ?Sized, + S: Spend, + L: SenderLedger + ?Sized, { - /// Extends proof public input with `self`. - #[inline] - pub fn extend_input(&self, input: &mut ProofInput) { - C::ProofSystem::extend(input, self.utxo_accumulator_output.as_ref()); - C::ProofSystem::extend(input, self.void_number.as_ref()); - } - /// Posts `self` to the sender `ledger`. #[inline] - pub fn post(self, super_key: &L::SuperPostingKey, ledger: &mut L) { - ledger.spend(self.utxo_accumulator_output, self.void_number, super_key); + pub fn post(self, ledger: &mut L, super_key: &L::SuperPostingKey) { + ledger.spend(super_key, self.utxo_accumulator_output, self.nullifier); } /// Posts all of the [`SenderPostingKey`] in `iter` to the sender `ledger`. #[inline] - pub fn post_all(iter: I, super_key: &L::SuperPostingKey, ledger: &mut L) + pub fn post_all(iter: I, ledger: &mut L, super_key: &L::SuperPostingKey) where I: IntoIterator, { ledger.spend_all( - iter.into_iter() - .map(move |k| (k.utxo_accumulator_output, k.void_number)), super_key, + iter.into_iter() + .map(move |k| (k.utxo_accumulator_output, k.nullifier)), ) } } + +impl Input

for SenderPostingKey +where + S: Spend, + L: SenderLedger + ?Sized, + P: HasInput> + HasInput + ?Sized, +{ + #[inline] + fn extend(&self, input: &mut P::Input) { + P::extend(input, self.utxo_accumulator_output.as_ref()); + P::extend(input, self.nullifier.as_ref()); + } +} diff --git a/manta-accounting/src/transfer/test.rs b/manta-accounting/src/transfer/test.rs index 73d0a8305..e6c1be5c1 100644 --- a/manta-accounting/src/transfer/test.rs +++ b/manta-accounting/src/transfer/test.rs @@ -16,13 +16,12 @@ //! Transfer Protocol Testing Framework -use crate::{ - asset, - transfer::{ - canonical::Mint, has_public_participants, Asset, Configuration, FullParameters, Parameters, - PreSender, Proof, ProofSystemError, ProofSystemPublicParameters, ProvingContext, Receiver, - Sender, SpendingKey, Transfer, TransferPost, Utxo, VerifyingContext, - }, +use crate::transfer::{ + canonical::ToPrivate, has_public_participants, requires_authorization, Address, Asset, + AssociatedData, Authorization, AuthorizationContext, Configuration, FullParametersRef, + Parameters, PreSender, ProofInput, ProofSystemError, ProofSystemPublicParameters, + ProvingContext, Receiver, Sender, SpendingKey, Transfer, TransferPost, UtxoAccumulatorItem, + UtxoAccumulatorModel, VerifyingContext, }; use alloc::vec::Vec; use core::{ @@ -31,41 +30,35 @@ use core::{ }; use manta_crypto::{ accumulator::Accumulator, - constraint::ProofSystem, - rand::{CryptoRng, Rand, RngCore, Sample}, + constraint::{test::verify_fuzz_public_input, ProofSystem}, + rand::{fuzz::Fuzz, CryptoRng, Rand, RngCore, Sample}, }; use manta_util::into_array_unchecked; -use super::ProofInput; - /// Samples a distribution over `count`-many values summing to `total`. /// /// # Warning /// /// This is a naive algorithm and should only be used for testing purposes. #[inline] -pub fn value_distribution( - count: usize, - total: C::AssetValue, - rng: &mut R, -) -> Vec +pub fn value_distribution(count: usize, total: V, rng: &mut R) -> Vec where - C: Configuration, - C::AssetValue: Ord + Rem + Sample + Sub, + V: Default + Ord + Sample, + for<'v> &'v V: Rem + Sub, R: RngCore + ?Sized, { if count == 0 { return Vec::default(); } let mut result = Vec::with_capacity(count + 1); - result.push(C::AssetValue::default()); + result.push(V::default()); for _ in 1..count { - result.push(::gen(rng) % total); + result.push(&rng.gen::<_, V>() % &total); } result.push(total); result.sort_unstable(); for i in 0..count { - result[i] = result[i + 1] - result[i]; + result[i] = &result[i + 1] - &result[i]; } result .pop() @@ -79,74 +72,79 @@ where /// /// This is a naive algorithm and should only be used for testing purposes. #[inline] -pub fn sample_asset_values( - total: C::AssetValue, - rng: &mut R, -) -> [C::AssetValue; N] +pub fn sample_asset_values(total: V, rng: &mut R) -> [V; N] where - C: Configuration, - C::AssetValue: Ord + Rem + Sample + Sub, + V: Default + Ord + Sample, + for<'v> &'v V: Rem + Sub, R: RngCore + ?Sized, { - into_array_unchecked(value_distribution::(N, total, rng)) + into_array_unchecked(value_distribution(N, total, rng)) } -/// Parameters Distribution -#[derive(derivative::Derivative)] -#[derivative(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] -pub struct ParametersDistribution { - /// Key Agreement Scheme Distribution - pub key_agreement_scheme: K, - - /// Note Encryption Scheme Distribution - pub note_encryption_scheme: E, +/// Transfer Distribution +pub struct TransferDistribution<'p, C, A> +where + C: Configuration, + A: Accumulator, Model = UtxoAccumulatorModel>, +{ + /// Parameters + pub parameters: &'p Parameters, - /// UTXO Commitment Scheme Distribution - pub utxo_commitment: U, + /// UTXO Accumulator + pub utxo_accumulator: &'p mut A, - /// Void Number Commitment Scheme Distribution - pub void_number_commitment_scheme: V, + /// Authorization + pub authorization: Option>, } -impl Sample> for Parameters +impl<'p, C, A> TransferDistribution<'p, C, A> where C: Configuration, - C::KeyAgreementScheme: Sample, - C::NoteEncryptionScheme: Sample, - C::UtxoCommitmentScheme: Sample, - C::VoidNumberCommitmentScheme: Sample, + A: Accumulator, Model = UtxoAccumulatorModel>, { + /// Builds a new [`TransferDistribution`] from `parameters`, `utxo_accumulator` + /// and `authorization`. + #[inline] + pub fn new( + parameters: &'p Parameters, + utxo_accumulator: &'p mut A, + authorization: Option>, + ) -> Self { + Self { + parameters, + utxo_accumulator, + authorization, + } + } + + /// Builds a new [`TransferDistribution`] from `parameters`, `utxo_accumulator` + /// and `spending_key`. #[inline] - fn sample(distribution: ParametersDistribution, rng: &mut R) -> Self + pub fn from_spending_key( + parameters: &'p Parameters, + utxo_accumulator: &'p mut A, + spending_key: &SpendingKey, + rng: &mut R, + ) -> Self where R: RngCore + ?Sized, { - Parameters::new( - rng.sample(distribution.key_agreement_scheme), - rng.sample(distribution.note_encryption_scheme), - rng.sample(distribution.utxo_commitment), - rng.sample(distribution.void_number_commitment_scheme), + Self::new( + parameters, + utxo_accumulator, + Some(Authorization::::from_spending_key( + parameters, + spending_key, + rng, + )), ) } } -/// Transfer Distribution -pub struct TransferDistribution<'p, C, A> -where - C: Configuration, - A: Accumulator, Model = C::UtxoAccumulatorModel>, -{ - /// Parameters - pub parameters: &'p Parameters, - - /// UTXO Accumulator - pub utxo_accumulator: &'p mut A, -} - impl<'p, C, A> From> for TransferDistribution<'p, C, A> where C: Configuration, - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = UtxoAccumulatorModel>, { #[inline] fn from(distribution: FixedTransferDistribution<'p, C, A>) -> Self { @@ -163,7 +161,7 @@ where pub struct FixedTransferDistribution<'p, C, A> where C: Configuration, - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = UtxoAccumulatorModel>, { /// Base Transfer Distribution pub base: TransferDistribution<'p, C, A>, @@ -188,9 +186,38 @@ impl where C: Configuration, - C::AssetId: Sample, - C::AssetValue: Rem + Sample + Sub, { + /// Generates a new [`TransferDistribution`] from `parameters`, `utxo_accumulator`, and + /// `spending_key`. + #[inline] + fn generate_distribution<'s, 'p, A, R>( + parameters: &'p Parameters, + utxo_accumulator: &'p mut A, + spending_key: Option<&'s SpendingKey>, + rng: &mut R, + ) -> (Option<&'s SpendingKey>, TransferDistribution<'p, C, A>) + where + A: Accumulator, Model = UtxoAccumulatorModel>, + R: RngCore + ?Sized, + { + match (spending_key, requires_authorization(SENDERS)) { + (Some(spending_key), true) => ( + Some(spending_key), + TransferDistribution::::from_spending_key( + parameters, + utxo_accumulator, + spending_key, + rng, + ), + ), + (None, false) => ( + None, + TransferDistribution::new(parameters, utxo_accumulator, None), + ), + _ => unreachable!("Authorization shape mismatch."), + } + } + /// Samples a [`TransferPost`] from `parameters` and `utxo_accumulator` using `proving_context` /// and `rng`. #[inline] @@ -198,23 +225,20 @@ where proving_context: &ProvingContext, parameters: &Parameters, utxo_accumulator: &mut A, + spending_key: Option<&SpendingKey>, rng: &mut R, - ) -> Result, ProofSystemError> + ) -> Result>, ProofSystemError> where - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = UtxoAccumulatorModel>, + for<'s> Self: Sample>, R: CryptoRng + RngCore + ?Sized, - C::AssetValue: Ord + Rem + Sample + Sub, { - Self::sample( - TransferDistribution { - parameters, - utxo_accumulator, - }, - rng, - ) - .into_post( - FullParameters::new(parameters, utxo_accumulator.model()), + let (spending_key, distribution) = + Self::generate_distribution(parameters, utxo_accumulator, spending_key, rng); + Self::sample(distribution, rng).into_post( + FullParametersRef::::new(parameters, utxo_accumulator.model()), proving_context, + spending_key, rng, ) } @@ -226,16 +250,17 @@ where public_parameters: &ProofSystemPublicParameters, parameters: &Parameters, utxo_accumulator: &mut A, + spending_key: Option<&SpendingKey>, rng: &mut R, ) -> Result> where - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = UtxoAccumulatorModel>, + for<'s> Self: Sample>, R: CryptoRng + RngCore + ?Sized, - C::AssetValue: Ord + Rem + Sample + Sub, { let (proving_context, verifying_context) = Self::generate_context( public_parameters, - FullParameters::new(parameters, utxo_accumulator.model()), + FullParametersRef::::new(parameters, utxo_accumulator.model()), rng, )?; Self::sample_and_check_proof_with_context( @@ -243,6 +268,7 @@ where &verifying_context, parameters, utxo_accumulator, + spending_key, rng, ) } @@ -255,116 +281,106 @@ where verifying_context: &VerifyingContext, parameters: &Parameters, utxo_accumulator: &mut A, + spending_key: Option<&SpendingKey>, rng: &mut R, ) -> Result> where - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = UtxoAccumulatorModel>, + for<'s> Self: Sample>, R: CryptoRng + RngCore + ?Sized, - C::AssetValue: Ord + Rem + Sample + Sub, { - let post = Self::sample_post(proving_context, parameters, utxo_accumulator, rng)?; - C::ProofSystem::verify( - verifying_context, - &post.generate_proof_input(), - &post.validity_proof, - ) + Self::sample_post( + proving_context, + parameters, + utxo_accumulator, + spending_key, + rng, + )? + .expect("Sample post cannot return None.") + .has_valid_proof(verifying_context) } - /// Checks if `generate_proof_input` from [`Transfer`] and [`TransferPost`] gives the same [`ProofInput`]. + /// Checks if `generate_proof_input` from [`Transfer`] and [`TransferPost`] gives the same + /// [`ProofInput`]. #[inline] pub fn sample_and_check_generate_proof_input_compatibility( public_parameters: &ProofSystemPublicParameters, parameters: &Parameters, utxo_accumulator: &mut A, + spending_key: Option<&SpendingKey>, rng: &mut R, ) -> Result> where - A: Accumulator, Model = C::UtxoAccumulatorModel>, + A: Accumulator, Model = UtxoAccumulatorModel>, + for<'s> Self: Sample>, R: CryptoRng + RngCore + ?Sized, - ProofInput: PartialEq, + ProofInput: PartialEq + Debug, ProofSystemError: Debug, - C::AssetValue: Ord + Rem + Sample + Sub, { - let transfer = Self::sample( - TransferDistribution { - parameters, - utxo_accumulator, - }, - rng, - ); - let full_parameters = FullParameters::new(parameters, utxo_accumulator.model()); + let (spending_key, distribution) = + Self::generate_distribution(parameters, utxo_accumulator, spending_key, rng); + let transfer = Self::sample(distribution, rng); + let full_parameters = FullParametersRef::::new(parameters, utxo_accumulator.model()); let (proving_context, _) = Self::generate_context(public_parameters, full_parameters, rng)?; Ok(transfer.generate_proof_input() == transfer - .into_post(full_parameters, &proving_context, rng)? + .into_post(full_parameters, &proving_context, spending_key, rng)? + .expect("TransferPost should have been constructed correctly.") .generate_proof_input()) } } -impl TransferPost -where - C: Configuration, -{ - /// Asserts that `self` contains a valid proof according to the `verifying_context`, returning a - /// reference to the proof. - #[inline] - pub fn assert_valid_proof(&self, verifying_context: &VerifyingContext) -> &Proof - where - ProofSystemError: Debug, - { - assert!( - self.has_valid_proof(verifying_context) - .expect("Unable to verify proof."), - "The proof should have been valid." - ); - &self.validity_proof - } -} - -/// Samples a set of senders and receivers. +/// Samples a set of [`Sender`]s and [`Receiver`]s. #[inline] fn sample_senders_and_receivers( parameters: &Parameters, + mut authorization_context: Option<&mut AuthorizationContext>, asset_id: C::AssetId, - senders: &[C::AssetValue], - receivers: &[C::AssetValue], + senders: Vec, + receivers: Vec, utxo_accumulator: &mut A, rng: &mut R, ) -> (Vec>, Vec>) where C: Configuration, - A: Accumulator, Model = C::UtxoAccumulatorModel>, + Address: Sample, + AssociatedData: Sample, + A: Accumulator, Model = UtxoAccumulatorModel>, R: RngCore + ?Sized, { - ( - senders - .iter() + let senders = match ( + authorization_context.take(), + requires_authorization(senders.len()), + ) { + (Some(authorization_context), true) => senders + .into_iter() .map(|v| { - let sender = PreSender::new( + let pre_sender = PreSender::::sample( parameters, + authorization_context, rng.gen(), - rng.gen(), - asset::Asset { - id: asset_id.clone(), - value: *v, - }, + Asset::::new(asset_id.clone(), v), + rng, ); - sender.insert_utxo(utxo_accumulator); - sender.try_upgrade(utxo_accumulator).unwrap() + pre_sender + .insert_and_upgrade(parameters, utxo_accumulator) + .expect("Insertion and upgrading should not fail.") }) .collect(), + (None, false) => Vec::new(), + _ => unreachable!("Badly shaped transaction."), + }; + ( + senders, receivers - .iter() + .into_iter() .map(|v| { - Receiver::new( + Receiver::::sample( parameters, - parameters.derive(&rng.gen()), - parameters.derive(&rng.gen()), rng.gen(), - asset::Asset { - id: asset_id.clone(), - value: *v, - }, + Asset::::new(asset_id.clone(), v), + rng.gen(), + rng, ) }) .collect(), @@ -382,28 +398,38 @@ impl< where C: Configuration, C::AssetId: Sample, - C::AssetValue: Ord + Rem + Sample + Sub, - A: Accumulator, Model = C::UtxoAccumulatorModel>, + C::AssetValue: Default + Ord + Sample, + for<'v> &'v C::AssetValue: Rem + Sub, + Address: Sample, + AssociatedData: Sample, + A: Accumulator, Model = UtxoAccumulatorModel>, { #[inline] - fn sample(distribution: TransferDistribution<'_, C, A>, rng: &mut R) -> Self + fn sample(mut distribution: TransferDistribution<'_, C, A>, rng: &mut R) -> Self where R: RngCore + ?Sized, { + let authorization_context = distribution.authorization.as_mut().map(|k| &mut k.context); let asset = Asset::::gen(rng); - let mut input = value_distribution::(SOURCES + SENDERS, asset.value, rng); - let mut output = value_distribution::(RECEIVERS + SINKS, asset.value, rng); + let mut input = value_distribution(SOURCES + SENDERS, asset.value.clone(), rng); + let mut output = value_distribution(RECEIVERS + SINKS, asset.value, rng); let secret_input = input.split_off(SOURCES); let public_output = output.split_off(RECEIVERS); - let (senders, receivers) = sample_senders_and_receivers( + let (senders, receivers) = sample_senders_and_receivers::( distribution.parameters, + authorization_context, asset.id.clone(), - &secret_input, - &output, + secret_input, + output, distribution.utxo_accumulator, rng, ); Self::new( + requires_authorization(SENDERS).then(|| { + distribution + .authorization + .expect("The authorization exists whenever we require authorization.") + }), has_public_participants(SOURCES, SINKS).then_some(asset.id), into_array_unchecked(input), into_array_unchecked(senders), @@ -424,69 +450,108 @@ impl< for Transfer where C: Configuration, - C::AssetValue: Ord + Rem + Sample + Sub, - A: Accumulator, Model = C::UtxoAccumulatorModel>, + C::AssetId: Sample, + C::AssetValue: Default + Ord + Sample, + for<'v> &'v C::AssetValue: Rem + Sub, + Address: Sample, + AssociatedData: Sample, + A: Accumulator, Model = UtxoAccumulatorModel>, { #[inline] - fn sample(distribution: FixedTransferDistribution<'_, C, A>, rng: &mut R) -> Self + fn sample(mut distribution: FixedTransferDistribution<'_, C, A>, rng: &mut R) -> Self where R: RngCore + ?Sized, { - let (senders, receivers) = sample_senders_and_receivers( + let authorization_context = distribution + .base + .authorization + .as_mut() + .map(|k| &mut k.context); + let (senders, receivers) = sample_senders_and_receivers::( distribution.base.parameters, + authorization_context, distribution.asset_id.clone(), - &value_distribution::(SENDERS, distribution.sender_sum, rng), - &value_distribution::(RECEIVERS, distribution.receiver_sum, rng), + value_distribution(SENDERS, distribution.sender_sum, rng), + value_distribution(RECEIVERS, distribution.receiver_sum, rng), distribution.base.utxo_accumulator, rng, ); Self::new( + requires_authorization(SENDERS).then(|| { + distribution + .base + .authorization + .expect("The authorization proof exists whenever we require authorization.") + }), has_public_participants(SOURCES, SINKS).then_some(distribution.asset_id), - sample_asset_values::(distribution.source_sum, rng), + sample_asset_values(distribution.source_sum, rng), into_array_unchecked(senders), into_array_unchecked(receivers), - sample_asset_values::(distribution.sink_sum, rng), + sample_asset_values(distribution.sink_sum, rng), ) } } -/// Samples a [`Mint`] transaction against `spending_key` and `asset` returning a [`TransferPost`] +/// Samples a [`ToPrivate`] transfers and returns the corresponding [`TransferPost`] /// and [`PreSender`]. #[inline] -pub fn sample_mint( +pub fn sample_to_private( + parameters: FullParametersRef, proving_context: &ProvingContext, - full_parameters: FullParameters, - spending_key: &SpendingKey, + authorization_context: &mut AuthorizationContext, + address: Address, asset: Asset, + associated_data: AssociatedData, rng: &mut R, ) -> Result<(TransferPost, PreSender), ProofSystemError> where C: Configuration, R: CryptoRng + RngCore + ?Sized, { - let (mint, pre_sender) = Mint::internal_pair(full_parameters.base, spending_key, asset, rng); + let (transaction, pre_sender) = ToPrivate::internal_pair( + parameters.base, + authorization_context, + address, + asset, + associated_data, + rng, + ); Ok(( - mint.into_post(full_parameters, proving_context, rng)?, + transaction + .into_post(parameters, proving_context, None, rng)? + .expect("The `ToPrivate` transaction does not require authorization."), pre_sender, )) } -/// Asserts that `post` represents a valid `Transfer` verifying against `verifying_context`. +/// Checks that a [`TransferPost`] is valid, and that its proof cannot be verified when tested against a fuzzed +/// or randomized `public_input`. #[inline] -pub fn assert_valid_proof(verifying_context: &VerifyingContext, post: &TransferPost) -where +pub fn validity_check_with_fuzzing( + verifying_context: &VerifyingContext, + post: &TransferPost, + rng: &mut R, +) where + A: Clone + Sample + Fuzz, C: Configuration, - ::Error: Debug, + C::ProofSystem: ProofSystem>, + ProofSystemError: Debug, + R: RngCore + ?Sized, TransferPost: Debug, { - assert!( - C::ProofSystem::verify( - verifying_context, - &post.generate_proof_input(), - &post.validity_proof, - ) - .expect("Unable to verify proof."), - "Invalid proof: {:?}.", - post, + let public_input = post.generate_proof_input(); + let proof = &post.body.proof; + post.assert_valid_proof(verifying_context); + verify_fuzz_public_input::( + verifying_context, + &public_input, + proof, + |input| input.fuzz(rng), + ); + verify_fuzz_public_input::( + verifying_context, + &public_input, + proof, + |input| (0..input.len()).map(|_| rng.gen()).collect(), ); } diff --git a/manta-accounting/src/transfer/utxo/auth.rs b/manta-accounting/src/transfer/utxo/auth.rs new file mode 100644 index 000000000..9c7f79641 --- /dev/null +++ b/manta-accounting/src/transfer/utxo/auth.rs @@ -0,0 +1,451 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Authorization + +use core::{fmt::Debug, hash::Hash}; +use manta_crypto::{ + eclair::alloc::{mode::Derived, Allocate, Allocator, Constant, Variable}, + rand::RngCore, +}; +use manta_util::{ + codec::{Encode, Write}, + convert::Field, +}; + +#[cfg(feature = "serde")] +use manta_util::serde::{Deserialize, Serialize}; + +/// Spending Key +pub trait SpendingKeyType { + /// Spending Key Type + type SpendingKey; +} + +/// Spending Key Type +pub type SpendingKey = ::SpendingKey; + +/// Authorization Context +pub trait AuthorizationContextType { + /// Authorization Context Type + type AuthorizationContext; +} + +/// Authorization Context Type +pub type AuthorizationContext = ::AuthorizationContext; + +/// Authorization Key +pub trait AuthorizationKeyType { + /// Authorization Key Type + type AuthorizationKey; +} + +/// Authorization Key Type +pub type AuthorizationKey = ::AuthorizationKey; + +/// Authorization Proof +pub trait AuthorizationProofType: AuthorizationKeyType { + /// Authorization Proof Type + type AuthorizationProof: Field; +} + +/// Authorization Proof Type +pub type AuthorizationProof = ::AuthorizationProof; + +/// Signing Key +pub trait SigningKeyType { + /// Signing Key Type + type SigningKey; +} + +/// Signing Key Type +pub type SigningKey = ::SigningKey; + +/// Signature +pub trait SignatureType { + /// Signature Type + type Signature; +} + +/// Signature Type +pub type Signature = ::Signature; + +/// Authorization Context Derivation +pub trait DeriveContext: AuthorizationContextType + SpendingKeyType { + /// Derives the authorization context from the `spending_key`. + fn derive_context(&self, spending_key: &Self::SpendingKey) -> Self::AuthorizationContext; +} + +/// Authorization Context Proving +pub trait ProveAuthorization: + AuthorizationContextType + AuthorizationProofType + SpendingKeyType +{ + /// Generates a proof that `authorization_context` is derived from `spending_key` correctly. + fn prove( + &self, + spending_key: &Self::SpendingKey, + authorization_context: &Self::AuthorizationContext, + rng: &mut R, + ) -> Self::AuthorizationProof + where + R: RngCore + ?Sized; +} + +/// Authorization Context Verification +pub trait VerifyAuthorization: + AuthorizationContextType + AuthorizationProofType + SpendingKeyType +{ + /// Verifies that `authorization_context` is derived from `spending_key` using + /// `authorization_proof`. + fn verify( + &self, + spending_key: &Self::SpendingKey, + authorization_context: &Self::AuthorizationContext, + authorization_proof: &Self::AuthorizationProof, + ) -> bool; + + /// Verifies that `authorization` is derived from `spending_key` using its inner authorization + /// proof. + #[inline] + fn verify_from( + &self, + spending_key: &Self::SpendingKey, + authorization: &Authorization, + ) -> bool { + authorization.verify(self, spending_key) + } +} + +/// Authorization Assertion +pub trait AssertAuthorized: AuthorizationContextType + AuthorizationProofType { + /// Asserts that `authorization_context` corresponds to `authorization_proof`. + fn assert_authorized( + &self, + authorization_context: &Self::AuthorizationContext, + authorization_proof: &Self::AuthorizationProof, + compiler: &mut COM, + ); +} + +/// Signing Key Derivation +pub trait DeriveSigningKey: + AuthorizationContextType + AuthorizationProofType + SigningKeyType + SpendingKeyType +{ + /// Derives the signing key from `spending_key`, `authorization_context`, and the + /// `authorization_proof`. + fn derive_signing_key( + &self, + spending_key: &Self::SpendingKey, + authorization_context: &Self::AuthorizationContext, + authorization_proof: &Self::AuthorizationProof, + ) -> Self::SigningKey; +} + +/// Signing +pub trait Sign: SignatureType + SigningKeyType { + /// Signs `message` with the `signing_key`. + fn sign(&self, signing_key: &Self::SigningKey, message: &M, rng: &mut R) -> Self::Signature + where + R: RngCore + ?Sized; +} + +/// Signature Verification +pub trait VerifySignature: AuthorizationKeyType + SignatureType { + /// Verifies that `signature` is a valid signature of `message` under `authorization_key`. + fn verify( + &self, + authorization_key: &Self::AuthorizationKey, + message: &M, + signature: &Self::Signature, + ) -> bool; +} + +/// Authorization +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "T::AuthorizationContext: Deserialize<'de>, T::AuthorizationProof: Deserialize<'de>", + serialize = "T::AuthorizationContext: Serialize, T::AuthorizationProof: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "T::AuthorizationContext: Clone, T::AuthorizationProof: Clone"), + Copy(bound = "T::AuthorizationContext: Copy, T::AuthorizationProof: Copy"), + Debug(bound = "T::AuthorizationContext: Debug, T::AuthorizationProof: Debug"), + Default(bound = "T::AuthorizationContext: Default, T::AuthorizationProof: Default"), + Eq(bound = "T::AuthorizationContext: Eq, T::AuthorizationProof: Eq"), + Hash(bound = "T::AuthorizationContext: Hash, T::AuthorizationProof: Hash"), + PartialEq(bound = "T::AuthorizationContext: PartialEq, T::AuthorizationProof: PartialEq") +)] +pub struct Authorization +where + T: AuthorizationContextType + AuthorizationProofType + ?Sized, +{ + /// Authorization Context + pub context: T::AuthorizationContext, + + /// Authorization Proof + pub proof: T::AuthorizationProof, +} + +impl Authorization +where + T: AuthorizationContextType + AuthorizationProofType + ?Sized, +{ + /// Builds a new [`Authorization`] from `context` and `proof`. + #[inline] + pub fn new(context: T::AuthorizationContext, proof: T::AuthorizationProof) -> Self { + Self { context, proof } + } + + /// Builds a new [`Authorization`] from `parameters` and `spending_key`. + #[inline] + pub fn from_spending_key(parameters: &T, spending_key: &T::SpendingKey, rng: &mut R) -> Self + where + T: DeriveContext + ProveAuthorization, + R: RngCore + ?Sized, + { + let context = parameters.derive_context(spending_key); + let proof = parameters.prove(spending_key, &context, rng); + Self::new(context, proof) + } + + /// Verifies that `self` is derived from `spending_key`. + #[inline] + pub fn verify(&self, parameters: &T, spending_key: &T::SpendingKey) -> bool + where + T: VerifyAuthorization, + { + parameters.verify(spending_key, &self.context, &self.proof) + } + + /// Asserts that `self.context` corresponds to `self.proof`. + #[inline] + pub fn assert_authorized(&self, parameters: &T, compiler: &mut COM) + where + T: AssertAuthorized, + { + parameters.assert_authorized(&self.context, &self.proof, compiler) + } +} + +impl Field for Authorization +where + T: AuthorizationContextType + AuthorizationProofType + ?Sized, +{ + #[inline] + fn get(&self) -> &T::AuthorizationKey { + Field::get(&self.proof) + } + + #[inline] + fn get_mut(&mut self) -> &mut T::AuthorizationKey { + Field::get_mut(&mut self.proof) + } + + #[inline] + fn into(self) -> T::AuthorizationKey { + Field::into(self.proof) + } +} + +impl Variable, COM> for Authorization +where + T: AuthorizationContextType + AuthorizationProofType + Constant + ?Sized, + T::Type: AuthorizationContextType + AuthorizationProofType, + AuthorizationContext: Variable>, + AuthorizationProof: Variable>, +{ + type Type = Authorization; + + #[inline] + fn new_unknown(compiler: &mut COM) -> Self { + Self::new(compiler.allocate_unknown(), compiler.allocate_unknown()) + } + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new( + this.context.as_known(compiler), + this.proof.as_known(compiler), + ) + } +} + +/// Signs `message` with the signing key generated by `spending_key` and `authorization`, checking +/// if `authorization` is valid for the `spending_key`. +#[inline] +pub fn sign( + parameters: &T, + spending_key: &T::SpendingKey, + authorization: Authorization, + message: &M, + rng: &mut R, +) -> Option> +where + T: VerifyAuthorization + DeriveSigningKey + Sign, + R: RngCore + ?Sized, +{ + if authorization.verify(parameters, spending_key) { + Some(AuthorizationSignature::generate( + parameters, + spending_key, + authorization, + message, + rng, + )) + } else { + None + } +} + +/// Authorization Signature +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "T::AuthorizationKey: Deserialize<'de>, T::Signature: Deserialize<'de>", + serialize = "T::AuthorizationKey: Serialize, T::Signature: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "T::AuthorizationKey: Clone, T::Signature: Clone"), + Copy(bound = "T::AuthorizationKey: Copy, T::Signature: Copy"), + Debug(bound = "T::AuthorizationKey: Debug, T::Signature: Debug"), + Default(bound = "T::AuthorizationKey: Default, T::Signature: Default"), + Eq(bound = "T::AuthorizationKey: Eq, T::Signature: Eq"), + Hash(bound = "T::AuthorizationKey: Hash, T::Signature: Hash"), + PartialEq(bound = "T::AuthorizationKey: PartialEq, T::Signature: PartialEq") +)] +pub struct AuthorizationSignature +where + T: AuthorizationKeyType + SignatureType, +{ + /// Authorization Key + pub authorization_key: T::AuthorizationKey, + + /// Signature + pub signature: T::Signature, +} + +impl AuthorizationSignature +where + T: AuthorizationKeyType + SignatureType, +{ + /// Builds a new [`AuthorizationSignature`] from `authorization_key` and `signature` without + /// checking that the `authorization_key` is the correct key for `signature`. + #[inline] + pub fn new_unchecked(authorization_key: T::AuthorizationKey, signature: T::Signature) -> Self { + Self { + authorization_key, + signature, + } + } + + /// Generates a new [`AuthorizationSignature`] by signing `message` with `spending_key` and + /// `authorization`. + #[inline] + pub fn generate( + parameters: &T, + spending_key: &T::SpendingKey, + authorization: Authorization, + message: &M, + rng: &mut R, + ) -> Self + where + T: DeriveSigningKey + Sign, + R: RngCore + ?Sized, + { + let signature = parameters.sign( + ¶meters.derive_signing_key( + spending_key, + &authorization.context, + &authorization.proof, + ), + message, + rng, + ); + Self::new_unchecked(Field::into(authorization), signature) + } + + /// Verifies that `message` is commited to with `self` as the [`AuthorizationSignature`]. + #[inline] + pub fn verify(&self, parameters: &T, message: &M) -> bool + where + T: VerifySignature, + { + parameters.verify(&self.authorization_key, message, &self.signature) + } +} + +impl Encode for AuthorizationSignature +where + T: AuthorizationKeyType + SignatureType, + T::AuthorizationKey: Encode, + T::Signature: Encode, +{ + #[inline] + fn encode(&self, mut writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.authorization_key.encode(&mut writer)?; + self.signature.encode(&mut writer)?; + Ok(()) + } +} + +/// Testing Framework +#[cfg(feature = "test")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] +pub mod test { + use super::*; + + /// Verifies that the signature generated by `spending_key` for `message` is correct and + /// corresponds to a valid authorization. + #[inline] + pub fn signature_correctness( + parameters: &T, + spending_key: &T::SpendingKey, + message: &M, + rng: &mut R, + ) -> bool + where + T: DeriveContext + + DeriveSigningKey + + ProveAuthorization + + Sign + + VerifyAuthorization + + VerifySignature, + R: RngCore + ?Sized, + { + let authorization = Authorization::from_spending_key(parameters, spending_key, rng); + let signature = sign(parameters, spending_key, authorization, message, rng) + .expect("Unable to sign message."); + signature.verify(parameters, message) + } +} diff --git a/manta-accounting/src/transfer/utxo/mod.rs b/manta-accounting/src/transfer/utxo/mod.rs new file mode 100644 index 000000000..20b0011f8 --- /dev/null +++ b/manta-accounting/src/transfer/utxo/mod.rs @@ -0,0 +1,544 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! UTXO Protocols +//! +//! The current protocol is referred to by [`protocol`] and older protocols are marked by their +//! version number. The [`VERSION`] number can be queried for the current protocol and can be used +//! to select the protocol version. The transfer protocol is built up from a given [`Mint`] and +//! [`Spend`] implementation. + +use crate::transfer::utxo::auth::AuthorizationContextType; +use core::{fmt::Debug, hash::Hash, marker::PhantomData, ops::Deref}; +use manta_crypto::{ + accumulator::{self, ItemHashFunction, MembershipProof}, + eclair::alloc::{Allocate, Constant}, + rand::RngCore, +}; +use manta_util::cmp::IndependenceContext; + +#[cfg(feature = "serde")] +use manta_util::serde::{Deserialize, Serialize}; + +pub mod auth; +pub mod protocol; + +/// Current UTXO Protocol Version +pub const VERSION: u8 = protocol::VERSION; + +/// Asset +pub trait AssetType { + /// Asset Type + type Asset; +} + +/// Asset Type +pub type Asset = ::Asset; + +/// Unspent Transaction Output +pub trait UtxoType { + /// Unspent Transaction Output Type + type Utxo; +} + +/// Unspent Transaction Output Type +pub type Utxo = ::Utxo; + +/// UTXO Independence +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct UtxoIndependence; + +impl IndependenceContext for UtxoIndependence { + const DEFAULT: bool = false; +} + +/// Note +pub trait NoteType { + /// Note Type + type Note; +} + +/// Note Type +pub type Note = ::Note; + +/// Nullifier +pub trait NullifierType { + /// Nullifier Type + type Nullifier; +} + +/// Nullifier Type +pub type Nullifier = ::Nullifier; + +/// Nullifier Independence +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct NullifierIndependence; + +impl IndependenceContext for NullifierIndependence { + const DEFAULT: bool = false; +} + +/// Identifier +pub trait IdentifierType { + /// Identifier Type + type Identifier; +} + +/// Identifier Type +pub type Identifier = ::Identifier; + +/// Identified Asset +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "T::Identifier: Deserialize<'de>, T::Asset: Deserialize<'de>", + serialize = "T::Identifier: Serialize, T::Asset: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "T::Identifier: Clone, T::Asset: Clone"), + Copy(bound = "T::Identifier: Copy, T::Asset: Copy"), + Debug(bound = "T::Identifier: Debug, T::Asset: Debug"), + Default(bound = "T::Identifier: Default, T::Asset: Default"), + Eq(bound = "T::Identifier: Eq, T::Asset: Eq"), + Hash(bound = "T::Identifier: Hash, T::Asset: Hash"), + PartialEq(bound = "T::Identifier: PartialEq, T::Asset: PartialEq") +)] +pub struct IdentifiedAsset +where + T: AssetType + IdentifierType + ?Sized, +{ + /// Identifier + pub identifier: T::Identifier, + + /// Asset + pub asset: T::Asset, +} + +impl IdentifiedAsset +where + T: AssetType + IdentifierType + ?Sized, +{ + /// Builds a new [`IdentifiedAsset`] from `identifier` and `asset`. + #[inline] + pub fn new(identifier: T::Identifier, asset: T::Asset) -> Self { + Self { identifier, asset } + } +} + +/// Address +pub trait AddressType { + /// Address Type + type Address; +} + +/// Address Type +pub type Address = ::Address; + +/// Associated Data +pub trait AssociatedDataType { + /// Associated Data Type + type AssociatedData; +} + +/// Associated Data Type +pub type AssociatedData = ::AssociatedData; + +/// Full Asset +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "T::Asset: Deserialize<'de>, T::AssociatedData: Deserialize<'de>", + serialize = "T::Asset: Serialize, T::AssociatedData: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "T::Asset: Clone, T::AssociatedData: Clone"), + Copy(bound = "T::Asset: Copy, T::AssociatedData: Copy"), + Debug(bound = "T::Asset: Debug, T::AssociatedData: Debug"), + Default(bound = "T::Asset: Default, T::AssociatedData: Default"), + Eq(bound = "T::Asset: Eq, T::AssociatedData: Eq"), + Hash(bound = "T::Asset: Hash, T::AssociatedData: Hash"), + PartialEq(bound = "T::Asset: PartialEq, T::AssociatedData: PartialEq") +)] +pub struct FullAsset +where + T: AssetType + AssociatedDataType + ?Sized, +{ + /// Asset + pub asset: T::Asset, + + /// Associated Data + pub associated_data: T::AssociatedData, +} + +impl FullAsset +where + T: AssetType + AssociatedDataType + ?Sized, +{ + /// Builds a new [`FullAsset`] from `asset` and `associated_data`. + #[inline] + pub fn new(asset: T::Asset, associated_data: T::AssociatedData) -> Self { + Self { + asset, + associated_data, + } + } + + /// Lifts an `asset` into a [`FullAsset`] by attaching the default associated data. + #[inline] + pub fn from_asset(asset: T::Asset) -> Self + where + T::AssociatedData: Default, + { + Self::new(asset, Default::default()) + } +} + +/// Derive Decryption Key +pub trait DeriveDecryptionKey: AuthorizationContextType { + /// Decryption Key Type + type DecryptionKey; + + /// Derives the decryption key for notes from `authorization_context`. + fn derive_decryption_key( + &self, + authorization_context: &mut Self::AuthorizationContext, + ) -> Self::DecryptionKey; +} + +/// Note Opening +pub trait NoteOpen: AssetType + DeriveDecryptionKey + IdentifierType + NoteType + UtxoType { + /// Tries to open `note` with `decryption_key`, returning a note [`Identifier`] and its stored + /// [`Asset`]. + /// + /// [`Identifier`]: IdentifierType::Identifier + /// [`Asset`]: AssetType::Asset + fn open( + &self, + decryption_key: &Self::DecryptionKey, + utxo: &Self::Utxo, + note: Self::Note, + ) -> Option<(Self::Identifier, Self::Asset)>; + + /// Tries to open `note` with `decryption_key`, returning an [`IdentifiedAsset`]. + #[inline] + fn open_into( + &self, + decryption_key: &Self::DecryptionKey, + utxo: &Self::Utxo, + note: Self::Note, + ) -> Option> { + self.open(decryption_key, utxo, note) + .map(|(identifier, asset)| IdentifiedAsset::new(identifier, asset)) + } +} + +/// Utxo Reconstruction +pub trait UtxoReconstruct: NoteOpen { + /// Checks if `utxo` is consistent with `asset` and `identifier`. + fn utxo_check( + &self, + utxo: &Self::Utxo, + asset: &Self::Asset, + identifier: &Self::Identifier, + decryption_key: &Self::DecryptionKey, + ) -> bool; + + /// Opens `note` and checks if `utxo` is consistent with it. Returns `None` + /// when it fails to open the `note` or when the `note` is inconsistent with the `utxo`. + #[inline] + fn open_with_check( + &self, + decryption_key: &Self::DecryptionKey, + utxo: &Self::Utxo, + note: Self::Note, + ) -> Option<(Self::Identifier, Self::Asset)> { + let (identifier, asset) = self.open(decryption_key, utxo, note)?; + + if self.utxo_check(utxo, &asset, &identifier, decryption_key) { + Some((identifier, asset)) + } else { + None + } + } +} + +/// Query Identifier Value +pub trait QueryIdentifier: IdentifierType + UtxoType { + /// Queries the underlying identifier from `self` and `utxo`. + fn query_identifier(&self, utxo: &Self::Utxo) -> Self::Identifier; +} + +/// UTXO Minting +pub trait Mint: AssetType + NoteType + UtxoType { + /// Secret Type + type Secret; + + /// Returns the asset inside of `utxo` asserting that `secret`, `utxo`, and `note` are + /// well-formed. + fn well_formed_asset( + &self, + secret: &Self::Secret, + utxo: &Self::Utxo, + note: &Self::Note, + compiler: &mut COM, + ) -> Self::Asset; +} + +/// Derive Minting Data +pub trait DeriveMint: AddressType + AssociatedDataType + Mint { + /// Derives the data required to mint to a target `address`, the `asset` to mint and + /// `associated_data`. + fn derive_mint( + &self, + address: Self::Address, + asset: Self::Asset, + associated_data: Self::AssociatedData, + rng: &mut R, + ) -> (Self::Secret, Self::Utxo, Self::Note) + where + R: RngCore + ?Sized; +} + +/// Query Asset Value +pub trait QueryAsset: AssetType + UtxoType { + /// Queries the underlying asset from `self` and `utxo`. + fn query_asset(&self, utxo: &Self::Utxo) -> Self::Asset; +} + +/// UTXO Spending +pub trait Spend: AuthorizationContextType + AssetType + UtxoType + NullifierType { + /// UTXO Accumulator Witness Type + type UtxoAccumulatorWitness; + + /// UTXO Accumulator Output Type + type UtxoAccumulatorOutput; + + /// UTXO Accumulator Model Type + type UtxoAccumulatorModel: accumulator::Model< + COM, + Witness = Self::UtxoAccumulatorWitness, + Output = Self::UtxoAccumulatorOutput, + >; + + /// UTXO Accumulator Item Hash Type + type UtxoAccumulatorItemHash: ItemHashFunction< + Self::Utxo, + COM, + Item = UtxoAccumulatorItem, + >; + + /// Spend Secret Type + type Secret; + + /// Returns the [`UtxoAccumulatorItemHash`](Self::UtxoAccumulatorItemHash) + fn utxo_accumulator_item_hash(&self) -> &Self::UtxoAccumulatorItemHash; + + /// Returns the asset and its nullifier inside of `utxo` asserting that `secret` and `utxo` are + /// well-formed and that `utxo_membership_proof` is a valid proof. + fn well_formed_asset( + &self, + utxo_accumulator_model: &Self::UtxoAccumulatorModel, + authorization_context: &mut Self::AuthorizationContext, + secret: &Self::Secret, + utxo: &Self::Utxo, + utxo_membership_proof: &UtxoMembershipProof, + compiler: &mut COM, + ) -> (Self::Asset, Self::Nullifier); + + /// Asserts that `lhs` and `rhs` are exactly equal. + fn assert_equal_nullifiers( + &self, + lhs: &Self::Nullifier, + rhs: &Self::Nullifier, + compiler: &mut COM, + ); +} + +/// Derive Spending Data +pub trait DeriveSpend: Spend + IdentifierType { + /// Derives the data required to spend with an `authorization_context`, the `asset` to spend and + /// its `identifier`. + fn derive_spend( + &self, + authorization_context: &mut Self::AuthorizationContext, + identifier: Self::Identifier, + asset: Self::Asset, + rng: &mut R, + ) -> (Self::Secret, Self::Utxo, Self::Nullifier) + where + R: RngCore + ?Sized; +} + +/// UTXO Accumulator Model Type +pub type UtxoAccumulatorModel = >::UtxoAccumulatorModel; + +/// UTXO Accumulator Item Type +pub type UtxoAccumulatorItem = + as accumulator::Types>::Item; + +/// UTXO Accumulator Witness Type +pub type UtxoAccumulatorWitness = + as accumulator::Types>::Witness; + +/// UTXO Accumulator Output Type +pub type UtxoAccumulatorOutput = + as accumulator::Types>::Output; + +/// UTXO Membership Proof Type +pub type UtxoMembershipProof = MembershipProof>; + +/// Full Parameters Owned +/// +/// This `struct` uses a lifetime marker to tie it down to a particular instance of +/// [`FullParametersRef`] during allocation. +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "P: Deserialize<'de>, P::UtxoAccumulatorModel: Deserialize<'de>", + serialize = "P: Serialize, P::UtxoAccumulatorModel: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "P: Clone, P::UtxoAccumulatorModel: Clone"), + Copy(bound = "P: Copy, P::UtxoAccumulatorModel: Copy"), + Debug(bound = "P: Debug, P::UtxoAccumulatorModel: Debug"), + Eq(bound = "P: Eq, P::UtxoAccumulatorModel: Eq"), + Hash(bound = "P: Hash, P::UtxoAccumulatorModel: Hash"), + PartialEq(bound = "P: PartialEq, P::UtxoAccumulatorModel: PartialEq") +)] +pub struct FullParameters<'p, P, COM = ()> +where + P: Mint + Spend, +{ + /// Base Parameters + pub base: P, + + /// UTXO Accumulator Model + pub utxo_accumulator_model: P::UtxoAccumulatorModel, + + /// Type Parameter Marker + __: PhantomData<&'p ()>, +} + +impl<'p, P, COM> FullParameters<'p, P, COM> +where + P: Mint + Spend, +{ + /// Builds a new [`FullParameters`] from `base` and `utxo_accumulator_model`. + #[inline] + pub fn new(base: P, utxo_accumulator_model: P::UtxoAccumulatorModel) -> Self { + Self { + base, + utxo_accumulator_model, + __: PhantomData, + } + } +} + +impl<'p, P, COM> Constant for FullParameters<'p, P, COM> +where + P: Mint + Spend + Constant, + P::UtxoAccumulatorModel: Constant>, + P::Type: 'p + Mint + Spend, + UtxoAccumulatorModel: 'p, +{ + type Type = FullParametersRef<'p, P::Type>; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new( + this.base.as_constant(compiler), + this.utxo_accumulator_model.as_constant(compiler), + ) + } +} + +/// Full Parameters Reference +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = ""), + Copy(bound = ""), + Debug(bound = "P: Debug, P::UtxoAccumulatorModel: Debug"), + Eq(bound = "P: Eq, P::UtxoAccumulatorModel: Eq"), + Hash(bound = "P: Hash, P::UtxoAccumulatorModel: Hash"), + PartialEq(bound = "P: PartialEq, P::UtxoAccumulatorModel: PartialEq") +)] +pub struct FullParametersRef<'p, P, COM = ()> +where + P: Mint + Spend, +{ + /// Base Parameters + pub base: &'p P, + + /// UTXO Accumulator Model + pub utxo_accumulator_model: &'p P::UtxoAccumulatorModel, +} + +impl<'p, P, COM> FullParametersRef<'p, P, COM> +where + P: Mint + Spend, +{ + /// Builds a new [`FullParametersRef`] from `base` and `utxo_accumulator_model`. + #[inline] + pub fn new(base: &'p P, utxo_accumulator_model: &'p P::UtxoAccumulatorModel) -> Self { + Self { + base, + utxo_accumulator_model, + } + } +} + +impl<'p, P, COM> AsRef

for FullParametersRef<'p, P, COM> +where + P: Mint + Spend, +{ + #[inline] + fn as_ref(&self) -> &P { + self.base + } +} + +impl<'p, P, COM> Deref for FullParametersRef<'p, P, COM> +where + P: Mint + Spend, +{ + type Target = P; + + #[inline] + fn deref(&self) -> &Self::Target { + self.base + } +} diff --git a/manta-accounting/src/transfer/utxo/protocol.rs b/manta-accounting/src/transfer/utxo/protocol.rs new file mode 100644 index 000000000..86ca4018f --- /dev/null +++ b/manta-accounting/src/transfer/utxo/protocol.rs @@ -0,0 +1,2829 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! UTXO Version 1 Protocol + +use crate::{ + asset, + transfer::utxo::{ + self, + auth::{self, DeriveContext, SpendingKey}, + }, +}; +use alloc::vec::Vec; +use core::{cmp, fmt::Debug, hash::Hash}; +use manta_crypto::{ + accumulator::{self, ItemHashFunction, MembershipProof}, + algebra::{ + diffie_hellman::StandardDiffieHellman, security::ComputationalDiffieHellmanHardness, + HasGenerator, Ring, ScalarMul, ScalarMulGroup, + }, + constraint::{HasInput, Input}, + eclair::{ + alloc::{ + mode::{Derived, Public, Secret}, + Allocate, Allocator, Const, Constant, Var, Variable, + }, + bool::{Assert, AssertEq, Bool, ConditionalSelect}, + cmp::PartialEq, + num::Zero, + ops::{BitAnd, BitOr}, + Has, + }, + encryption::{ + self, + hybrid::{Hybrid, Randomness}, + Decrypt, Encrypt, EncryptedMessage, + }, + rand::{Rand, RngCore, Sample}, + signature::{self, schnorr, Sign, Verify}, +}; +use manta_util::{ + cmp::Independence, + codec::{Encode, Write}, + convert::Field, +}; + +#[cfg(feature = "serde")] +use manta_util::serde::{Deserialize, Serialize}; + +/// UTXO Version Number +pub const VERSION: u8 = 1; + +/// UTXO Visibility +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub enum Visibility { + /// Opaque UTXO + #[default] + Opaque, + + /// Transparent UTXO + Transparent, +} + +impl Visibility { + /// Returns `true` if `self` represents the opaque visibility mode. + #[inline] + pub const fn is_opaque(self) -> bool { + matches!(self, Self::Opaque) + } + + /// Returns `true` if `self` represents the transparent visibility mode. + #[inline] + pub const fn is_transparent(self) -> bool { + matches!(self, Self::Transparent) + } + + /// Returns `value` if `self` is [`Opaque`](Self::Opaque) and the default value otherwise. + #[inline] + pub fn secret(self, value: &T) -> T + where + T: Clone + Default, + { + match self { + Self::Opaque => value.clone(), + _ => Default::default(), + } + } + + /// Returns `value` if `self` is [`Transparent`](Self::Transparent) and the default value + /// otherwise. + #[inline] + pub fn public(self, value: &T) -> T + where + T: Clone + Default, + { + match self { + Self::Transparent => value.clone(), + _ => Default::default(), + } + } +} + +impl Sample for Visibility { + #[inline] + fn sample(distribution: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + if bool::sample(distribution, rng) { + Self::Opaque + } else { + Self::Transparent + } + } +} + +/// UTXO Commitment Scheme +pub trait UtxoCommitmentScheme +where + COM: Has, +{ + /// Asset Id + type AssetId; + + /// Asset Value + type AssetValue; + + /// Receiving Key + type ReceivingKey; + + /// UTXO Commitment Randomness Type + type Randomness: Clone; + + /// UTXO Commitment Type + type Commitment: PartialEq; + + /// Commits to the UTXO data `asset_id`, `asset_value`, and `receiving_key`. + fn commit( + &self, + randomness: &Self::Randomness, + asset_id: &Self::AssetId, + asset_value: &Self::AssetValue, + receiving_key: &Self::ReceivingKey, + compiler: &mut COM, + ) -> Self::Commitment; +} + +/// Viewing Key Derivation Function +pub trait ViewingKeyDerivationFunction { + /// Proof Authorization Key + type ProofAuthorizationKey; + + /// Viewing Key + type ViewingKey; + + /// Computes the [`ViewingKey`](Self::ViewingKey) from `proof_authorization_key`. + fn viewing_key( + &self, + proof_authorization_key: &Self::ProofAuthorizationKey, + compiler: &mut COM, + ) -> Self::ViewingKey; +} + +/// Nullifier Commitment Scheme +pub trait NullifierCommitmentScheme +where + COM: Has, +{ + /// Proof Authorization Key + type ProofAuthorizationKey; + + /// UTXO Accumulator Item + type UtxoAccumulatorItem; + + /// Nullifier Commitment + type Commitment: PartialEq; + + /// Commits to the `item` using `proof_authorization_key`. + fn commit( + &self, + proof_authorization_key: &Self::ProofAuthorizationKey, + item: &Self::UtxoAccumulatorItem, + compiler: &mut COM, + ) -> Self::Commitment; +} + +/// UTXO Configuration +pub trait BaseConfiguration +where + COM: Has, +{ + /// Boolean Type + type Bool: Constant + + BitAnd + + BitOr + + PartialEq; + + /// Asset Id Type + type AssetId: ConditionalSelect + + PartialEq + + Zero; + + /// Asset Value Type + type AssetValue: ConditionalSelect + + PartialEq + + Zero; + + /// Scalar Type + type Scalar: Clone + PartialEq; + + /// Group Type + type Group: Clone + + ComputationalDiffieHellmanHardness + + ScalarMulGroup + + PartialEq; + + /// Group Generator + type GroupGenerator: HasGenerator; + + /// UTXO Commitment Scheme + type UtxoCommitmentScheme: UtxoCommitmentScheme< + COM, + AssetId = Self::AssetId, + AssetValue = Self::AssetValue, + ReceivingKey = Self::Group, + >; + + /// Viewing Key Derivation Function + type ViewingKeyDerivationFunction: ViewingKeyDerivationFunction< + COM, + ProofAuthorizationKey = Self::Group, + ViewingKey = Self::Scalar, + >; + + /// Light Incoming Ciphertext Type + type LightIncomingCiphertext: PartialEq; + + /// Light Incoming Header + type LightIncomingHeader: Default + PartialEq; + + /// Base Encryption Scheme for [`LightIncomingNote`] + type LightIncomingBaseEncryptionScheme: Clone + + Encrypt< + COM, + EncryptionKey = Self::Group, + Header = Self::LightIncomingHeader, + Plaintext = IncomingPlaintext, + Ciphertext = Self::LightIncomingCiphertext, + Randomness = encryption::Randomness, + >; + + /// Incoming Ciphertext Type + type IncomingCiphertext: PartialEq; + + /// Incoming Header + type IncomingHeader: Default + PartialEq; + + /// Base Encryption Scheme for [`IncomingNote`] + type IncomingBaseEncryptionScheme: Clone + + Encrypt< + COM, + EncryptionKey = Self::Group, + Header = Self::IncomingHeader, + Plaintext = IncomingPlaintext, + Ciphertext = Self::IncomingCiphertext, + >; + + /// UTXO Accumulator Item Hash + type UtxoAccumulatorItemHash: ItemHashFunction, COM>; + + /// UTXO Accumulator Model + type UtxoAccumulatorModel: accumulator::Model< + COM, + Item = UtxoAccumulatorItem, + Verification = Self::Bool, + >; + + /// Nullifier Commitment Scheme Type + type NullifierCommitmentScheme: NullifierCommitmentScheme< + COM, + ProofAuthorizationKey = Self::Group, + UtxoAccumulatorItem = UtxoAccumulatorItem, + >; + + /// Outgoing Header + type OutgoingHeader: Default + PartialEq; + + /// Outgoing Ciphertext Type + type OutgoingCiphertext: PartialEq; + + /// Base Encryption Scheme for [`OutgoingNote`] + type OutgoingBaseEncryptionScheme: Clone + + Encrypt< + COM, + EncryptionKey = Self::Group, + Header = Self::OutgoingHeader, + Plaintext = Asset, + Ciphertext = Self::OutgoingCiphertext, + >; +} + +/// Address Partition Function +pub trait AddressPartitionFunction { + /// Address Type + type Address; + + /// Partition Type + type Partition: core::cmp::PartialEq; + + /// Returns the partition for the `address`. + fn partition(&self, address: &Self::Address) -> Self::Partition; +} + +/// UTXO Configuration +pub trait Configuration: BaseConfiguration { + /// Address Partition Function Type + type AddressPartitionFunction: AddressPartitionFunction

>; + + /// Schnorr Hash Function + type SchnorrHashFunction: Clone + + schnorr::HashFunction>; +} + +/// Asset Type +pub type Asset = + asset::Asset<>::AssetId, >::AssetValue>; + +/// UTXO Commitment +pub type UtxoCommitment = + <>::UtxoCommitmentScheme as UtxoCommitmentScheme>::Commitment; + +/// UTXO Commitment Randomness +pub type UtxoCommitmentRandomness = + <>::UtxoCommitmentScheme as UtxoCommitmentScheme>::Randomness; + +/// Incoming Base Randomness +pub type IncomingBaseRandomness = + encryption::Randomness<>::IncomingBaseEncryptionScheme>; + +/// Incoming Encryption Scheme +pub type IncomingEncryptionScheme = Hybrid< + StandardDiffieHellman< + >::Scalar, + >::Group, + >, + >::IncomingBaseEncryptionScheme, +>; + +/// Incoming Randomness +pub type IncomingRandomness = encryption::Randomness>; + +/// Incoming Encrypted Note +pub type IncomingNote = EncryptedMessage>; + +/// Incoming Encryption Scheme +pub type LightIncomingEncryptionScheme = Hybrid< + StandardDiffieHellman< + >::Scalar, + >::Group, + >, + >::LightIncomingBaseEncryptionScheme, +>; + +/// Light Incoming Randomness +pub type LightIncomingRandomness = + encryption::Randomness>; + +/// Light Incoming Encrypted Note +pub type LightIncomingNote = EncryptedMessage>; + +/// UTXO Accumulator Item +pub type UtxoAccumulatorItem = + <>::UtxoAccumulatorItemHash as ItemHashFunction< + Utxo, + COM, + >>::Item; + +/// UTXO Accumulator Item Hash Type +pub type UtxoAccumulatorItemHash = + >::UtxoAccumulatorItemHash; + +/// UTXO Membership Proof +pub type UtxoMembershipProof = + MembershipProof<>::UtxoAccumulatorModel>; + +/// Nullifier Commitment +pub type NullifierCommitment = + <>::NullifierCommitmentScheme as NullifierCommitmentScheme>::Commitment; + +/// Outgoing Base Randomness +pub type OutgoingBaseRandomness = + encryption::Randomness<>::OutgoingBaseEncryptionScheme>; + +/// Outgoing Encryption Scheme +pub type OutgoingEncryptionScheme = Hybrid< + StandardDiffieHellman< + >::Scalar, + >::Group, + >, + >::OutgoingBaseEncryptionScheme, +>; + +/// Outgoing Randomness +pub type OutgoingRandomness = encryption::Randomness>; + +/// Outgoing Note +pub type OutgoingNote = EncryptedMessage>; + +/// Address Partition +pub type AddressPartition = + <::AddressPartitionFunction as AddressPartitionFunction>::Partition; + +/// Signature Scheme +pub type SignatureScheme = schnorr::Schnorr<::SchnorrHashFunction>; + +/// UTXO Model Base Parameters +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "C::GroupGenerator: Deserialize<'de>, + C::UtxoCommitmentScheme: Deserialize<'de>, + C::IncomingBaseEncryptionScheme: Deserialize<'de>, + C::LightIncomingBaseEncryptionScheme: Deserialize<'de>, + C::ViewingKeyDerivationFunction: Deserialize<'de>, + C::UtxoAccumulatorItemHash: Deserialize<'de>, + C::NullifierCommitmentScheme: Deserialize<'de>, + C::OutgoingBaseEncryptionScheme: Deserialize<'de>,", + serialize = "C::GroupGenerator: Serialize, + C::UtxoCommitmentScheme: Serialize, + C::IncomingBaseEncryptionScheme: Serialize, + C::LightIncomingBaseEncryptionScheme: Serialize, + C::ViewingKeyDerivationFunction: Serialize, + C::UtxoAccumulatorItemHash: Serialize, + C::NullifierCommitmentScheme: Serialize, + C::OutgoingBaseEncryptionScheme: Serialize,", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = r" + C::GroupGenerator: Clone, + C::UtxoCommitmentScheme: Clone, + C::IncomingBaseEncryptionScheme: Clone, + C::LightIncomingBaseEncryptionScheme: Clone, + C::ViewingKeyDerivationFunction: Clone, + C::UtxoAccumulatorItemHash: Clone, + C::NullifierCommitmentScheme: Clone, + C::OutgoingBaseEncryptionScheme: Clone, + "), + Copy(bound = r" + C::GroupGenerator: Copy, + C::UtxoCommitmentScheme: Copy, + C::IncomingBaseEncryptionScheme: Copy, + C::LightIncomingBaseEncryptionScheme: Copy, + C::ViewingKeyDerivationFunction: Copy, + C::UtxoAccumulatorItemHash: Copy, + C::NullifierCommitmentScheme: Copy, + C::OutgoingBaseEncryptionScheme: Copy, + "), + Debug(bound = r" + C::GroupGenerator: Debug, + C::UtxoCommitmentScheme: Debug, + C::IncomingBaseEncryptionScheme: Debug, + C::LightIncomingBaseEncryptionScheme: Debug, + C::ViewingKeyDerivationFunction: Debug, + C::UtxoAccumulatorItemHash: Debug, + C::NullifierCommitmentScheme: Debug, + C::OutgoingBaseEncryptionScheme: Debug, + "), + Default(bound = r" + C::GroupGenerator: Default, + C::UtxoCommitmentScheme: Default, + C::IncomingBaseEncryptionScheme: Default, + C::LightIncomingBaseEncryptionScheme: Default, + C::ViewingKeyDerivationFunction: Default, + C::UtxoAccumulatorItemHash: Default, + C::NullifierCommitmentScheme: Default, + C::OutgoingBaseEncryptionScheme: Default, + "), + Eq(bound = r" + C::GroupGenerator: Eq, + C::UtxoCommitmentScheme: Eq, + C::IncomingBaseEncryptionScheme: Eq, + C::LightIncomingBaseEncryptionScheme: Eq, + C::ViewingKeyDerivationFunction: Eq, + C::UtxoAccumulatorItemHash: Eq, + C::NullifierCommitmentScheme: Eq, + C::OutgoingBaseEncryptionScheme: Eq, + "), + Hash(bound = r" + C::GroupGenerator: Hash, + C::UtxoCommitmentScheme: Hash, + C::IncomingBaseEncryptionScheme: Hash, + C::LightIncomingBaseEncryptionScheme: Hash, + C::ViewingKeyDerivationFunction: Hash, + C::UtxoAccumulatorItemHash: Hash, + C::NullifierCommitmentScheme: Hash, + C::OutgoingBaseEncryptionScheme: Hash, + "), + PartialEq(bound = r" + C::GroupGenerator: cmp::PartialEq, + C::UtxoCommitmentScheme: cmp::PartialEq, + C::IncomingBaseEncryptionScheme: cmp::PartialEq, + C::LightIncomingBaseEncryptionScheme: cmp::PartialEq, + C::ViewingKeyDerivationFunction: cmp::PartialEq, + C::UtxoAccumulatorItemHash: cmp::PartialEq, + C::NullifierCommitmentScheme: cmp::PartialEq, + C::OutgoingBaseEncryptionScheme: cmp::PartialEq, + ") +)] +pub struct BaseParameters +where + C: BaseConfiguration, + COM: Has, +{ + /// Group Generator + pub group_generator: C::GroupGenerator, + + /// UTXO Commitment Scheme + pub utxo_commitment_scheme: C::UtxoCommitmentScheme, + + /// Incoming Base Encryption Scheme + pub incoming_base_encryption_scheme: C::IncomingBaseEncryptionScheme, + + /// Light Incoming Base Encryption Scheme + pub light_incoming_base_encryption_scheme: C::LightIncomingBaseEncryptionScheme, + + /// Viewing Key Derivation Function + pub viewing_key_derivation_function: C::ViewingKeyDerivationFunction, + + /// UTXO Accumulator Item Hash + pub utxo_accumulator_item_hash: C::UtxoAccumulatorItemHash, + + /// Nullifier Commitment Scheme + pub nullifier_commitment_scheme: C::NullifierCommitmentScheme, + + /// Outgoing Base Encryption Scheme + pub outgoing_base_encryption_scheme: C::OutgoingBaseEncryptionScheme, +} + +impl auth::AuthorizationContextType for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type AuthorizationContext = AuthorizationContext; +} + +impl auth::AuthorizationKeyType for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type AuthorizationKey = C::Group; +} + +impl auth::AuthorizationProofType for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type AuthorizationProof = AuthorizationProof; +} + +impl utxo::AssetType for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type Asset = Asset; +} + +impl utxo::AddressType for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type Address = Address; +} + +impl utxo::NoteType for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type Note = IncomingNote; +} + +impl utxo::UtxoType for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type Utxo = Utxo; +} + +impl utxo::NullifierType for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type Nullifier = Nullifier; +} + +impl utxo::IdentifierType for BaseParameters +where + C: BaseConfiguration, +{ + type Identifier = Identifier; +} + +impl auth::AssertAuthorized for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + #[inline] + fn assert_authorized( + &self, + authorization_context: &Self::AuthorizationContext, + authorization_proof: &Self::AuthorizationProof, + compiler: &mut COM, + ) { + let randomized_proof_authorization_key = authorization_context + .proof_authorization_key + .scalar_mul(&authorization_proof.randomness, compiler); + compiler.assert_eq( + &randomized_proof_authorization_key, + &authorization_proof.randomized_proof_authorization_key, + ); + } +} + +impl utxo::Mint for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type Secret = MintSecret; + + #[inline] + fn well_formed_asset( + &self, + secret: &Self::Secret, + utxo: &Self::Utxo, + note: &Self::Note, + compiler: &mut COM, + ) -> Self::Asset { + secret.well_formed_asset( + self.group_generator.generator(), + &self.utxo_commitment_scheme, + &self.incoming_base_encryption_scheme, + utxo, + note, + compiler, + ) + } +} + +impl accumulator::ItemHashFunction, COM> for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type Item = UtxoAccumulatorItem; + + #[inline] + fn item_hash(&self, utxo: &Utxo, compiler: &mut COM) -> Self::Item { + self.utxo_accumulator_item_hash.item_hash(utxo, compiler) + } +} + +impl utxo::Spend for BaseParameters +where + C: BaseConfiguration, + COM: Assert + Has, +{ + type UtxoAccumulatorWitness = utxo::UtxoAccumulatorWitness; + type UtxoAccumulatorOutput = utxo::UtxoAccumulatorOutput; + type UtxoAccumulatorModel = C::UtxoAccumulatorModel; + type Secret = SpendSecret; + type UtxoAccumulatorItemHash = C::UtxoAccumulatorItemHash; + + #[inline] + fn utxo_accumulator_item_hash(&self) -> &Self::UtxoAccumulatorItemHash { + &self.utxo_accumulator_item_hash + } + + #[inline] + fn well_formed_asset( + &self, + utxo_accumulator_model: &Self::UtxoAccumulatorModel, + authorization_context: &mut Self::AuthorizationContext, + secret: &Self::Secret, + utxo: &Self::Utxo, + utxo_membership_proof: &UtxoMembershipProof, + compiler: &mut COM, + ) -> (Self::Asset, Self::Nullifier) { + secret.well_formed_asset( + self, + utxo_accumulator_model, + authorization_context, + utxo, + utxo_membership_proof, + compiler, + ) + } + + #[inline] + fn assert_equal_nullifiers( + &self, + lhs: &Self::Nullifier, + rhs: &Self::Nullifier, + compiler: &mut COM, + ) { + compiler.assert_eq(lhs, rhs) + } +} + +impl Constant for BaseParameters +where + COM: Assert + Has, + C: BaseConfiguration + Constant, + C::Type: Configuration< + Bool = bool, + GroupGenerator = Const, + UtxoCommitmentScheme = Const, + IncomingBaseEncryptionScheme = Const, + LightIncomingBaseEncryptionScheme = Const, + ViewingKeyDerivationFunction = Const, + UtxoAccumulatorItemHash = Const, + NullifierCommitmentScheme = Const, + OutgoingBaseEncryptionScheme = Const, + >, + C::GroupGenerator: Constant, + C::UtxoCommitmentScheme: Constant, + C::IncomingBaseEncryptionScheme: Constant, + C::LightIncomingBaseEncryptionScheme: Constant, + C::ViewingKeyDerivationFunction: Constant, + C::UtxoAccumulatorItemHash: Constant, + C::NullifierCommitmentScheme: Constant, + C::OutgoingBaseEncryptionScheme: Constant, +{ + type Type = Parameters; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + Self { + group_generator: this.base.group_generator.as_constant(compiler), + utxo_commitment_scheme: this.base.utxo_commitment_scheme.as_constant(compiler), + incoming_base_encryption_scheme: this + .base + .incoming_base_encryption_scheme + .as_constant(compiler), + light_incoming_base_encryption_scheme: this + .base + .light_incoming_base_encryption_scheme + .as_constant(compiler), + viewing_key_derivation_function: this + .base + .viewing_key_derivation_function + .as_constant(compiler), + utxo_accumulator_item_hash: this.base.utxo_accumulator_item_hash.as_constant(compiler), + nullifier_commitment_scheme: this + .base + .nullifier_commitment_scheme + .as_constant(compiler), + outgoing_base_encryption_scheme: this + .base + .outgoing_base_encryption_scheme + .as_constant(compiler), + } + } +} + +impl + Sample<(DGG, DUCS, DIBES, DLIBES, DVKDF, DUAIH, DNCS, DOBES)> for BaseParameters +where + C: BaseConfiguration, + C::GroupGenerator: Sample, + C::UtxoCommitmentScheme: Sample, + C::IncomingBaseEncryptionScheme: Sample, + C::LightIncomingBaseEncryptionScheme: Sample, + C::ViewingKeyDerivationFunction: Sample, + C::UtxoAccumulatorItemHash: Sample, + C::NullifierCommitmentScheme: Sample, + C::OutgoingBaseEncryptionScheme: Sample, +{ + #[inline] + fn sample( + distribution: (DGG, DUCS, DIBES, DLIBES, DVKDF, DUAIH, DNCS, DOBES), + rng: &mut R, + ) -> Self + where + R: RngCore + ?Sized, + { + Self { + group_generator: rng.sample(distribution.0), + utxo_commitment_scheme: rng.sample(distribution.1), + incoming_base_encryption_scheme: rng.sample(distribution.2), + light_incoming_base_encryption_scheme: rng.sample(distribution.3), + viewing_key_derivation_function: rng.sample(distribution.4), + utxo_accumulator_item_hash: rng.sample(distribution.5), + nullifier_commitment_scheme: rng.sample(distribution.6), + outgoing_base_encryption_scheme: rng.sample(distribution.7), + } + } +} + +/// UTXO Model Parameters +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "BaseParameters: Deserialize<'de>, C::AddressPartitionFunction: Deserialize<'de>, C::SchnorrHashFunction: Deserialize<'de>", + serialize = "BaseParameters: Serialize, C::AddressPartitionFunction: Serialize, C::SchnorrHashFunction: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone( + bound = "BaseParameters: Clone, C::AddressPartitionFunction: Clone, C::SchnorrHashFunction: Clone" + ), + Copy( + bound = "BaseParameters: Copy, C::AddressPartitionFunction: Copy, C::SchnorrHashFunction: Copy" + ), + Debug( + bound = "BaseParameters: Debug, C::AddressPartitionFunction: Debug, C::SchnorrHashFunction: Debug" + ), + Default( + bound = "BaseParameters: Default, C::AddressPartitionFunction: Default, C::SchnorrHashFunction: Default" + ), + Eq( + bound = "BaseParameters: Eq, C::AddressPartitionFunction: Eq, C::SchnorrHashFunction: Eq" + ), + Hash( + bound = "BaseParameters: Hash, C::AddressPartitionFunction: Hash, C::SchnorrHashFunction: Hash" + ), + PartialEq( + bound = "BaseParameters: cmp::PartialEq, C::AddressPartitionFunction: cmp::PartialEq, C::SchnorrHashFunction: cmp::PartialEq" + ) +)] +pub struct Parameters +where + C: Configuration, +{ + /// Base Parameters + pub base: BaseParameters, + + /// Address Partition Function + pub address_partition_function: C::AddressPartitionFunction, + + /// Schnorr Hash Function + pub schnorr_hash_function: C::SchnorrHashFunction, +} + +impl Parameters +where + C: Configuration, +{ + /// Returns the signature scheme for `self`. + #[inline] + pub fn signature_scheme(&self) -> SignatureScheme { + SignatureScheme::::new( + self.schnorr_hash_function.clone(), + self.base.group_generator.generator().clone(), + ) + } + + /// Computes the [`Address`] corresponding to `spending_key`. + #[inline] + pub fn address_from_spending_key(&self, spending_key: &SpendingKey) -> Address + where + C: Configuration, + { + let generator = self.base.group_generator.generator(); + Address::new( + generator.scalar_mul( + &self + .base + .viewing_key_derivation_function + .viewing_key(&generator.scalar_mul(spending_key, &mut ()), &mut ()), + &mut (), + ), + ) + } +} + +impl auth::SpendingKeyType for Parameters +where + C: Configuration, +{ + type SpendingKey = C::Scalar; +} + +impl auth::AuthorizationContextType for Parameters +where + C: Configuration, +{ + type AuthorizationContext = AuthorizationContext; +} + +impl auth::AuthorizationKeyType for Parameters +where + C: Configuration, +{ + type AuthorizationKey = C::Group; +} + +impl auth::AuthorizationProofType for Parameters +where + C: Configuration, +{ + type AuthorizationProof = AuthorizationProof; +} + +impl auth::SigningKeyType for Parameters +where + C: Configuration, +{ + type SigningKey = C::Scalar; +} + +impl auth::SignatureType for Parameters +where + C: Configuration, +{ + type Signature = signature::Signature>; +} + +impl utxo::AssetType for Parameters +where + C: Configuration, +{ + type Asset = Asset; +} + +impl utxo::AssociatedDataType for Parameters +where + C: Configuration, +{ + type AssociatedData = Visibility; +} + +impl utxo::AddressType for Parameters +where + C: Configuration, +{ + type Address = Address; +} + +impl utxo::NoteType for Parameters +where + C: Configuration, +{ + type Note = FullIncomingNote; +} + +impl utxo::UtxoType for Parameters +where + C: Configuration, +{ + type Utxo = Utxo; +} + +impl utxo::NullifierType for Parameters +where + C: Configuration, +{ + type Nullifier = FullNullifier; +} + +impl utxo::IdentifierType for Parameters +where + C: Configuration, +{ + type Identifier = Identifier; +} + +impl auth::DeriveContext for Parameters +where + C: Configuration, +{ + #[inline] + fn derive_context(&self, spending_key: &Self::SpendingKey) -> Self::AuthorizationContext { + AuthorizationContext::new( + self.base + .group_generator + .generator() + .scalar_mul(spending_key, &mut ()), + ) + } +} + +impl auth::ProveAuthorization for Parameters +where + C: Configuration, + C::Scalar: Sample, +{ + #[inline] + fn prove( + &self, + spending_key: &Self::SpendingKey, + authorization_context: &Self::AuthorizationContext, + rng: &mut R, + ) -> Self::AuthorizationProof + where + R: RngCore + ?Sized, + { + let _ = spending_key; + let randomness = rng.gen(); + let randomized_proof_authorization_key = authorization_context + .proof_authorization_key + .scalar_mul(&randomness, &mut ()); + AuthorizationProof::new(randomness, randomized_proof_authorization_key) + } +} + +impl auth::VerifyAuthorization for Parameters +where + C: Configuration, + C::Group: cmp::PartialEq, +{ + #[inline] + fn verify( + &self, + spending_key: &Self::SpendingKey, + authorization_context: &Self::AuthorizationContext, + authorization_proof: &Self::AuthorizationProof, + ) -> bool { + (authorization_context == &self.derive_context(spending_key)) + && (authorization_proof.randomized_proof_authorization_key + == authorization_context + .proof_authorization_key + .scalar_mul(&authorization_proof.randomness, &mut ())) + } +} + +impl auth::DeriveSigningKey for Parameters +where + C: Configuration, + C::Scalar: Ring, +{ + #[inline] + fn derive_signing_key( + &self, + spending_key: &Self::SpendingKey, + authorization_context: &Self::AuthorizationContext, + authorization_proof: &Self::AuthorizationProof, + ) -> Self::SigningKey { + let _ = authorization_context; + spending_key.mul(&authorization_proof.randomness, &mut ()) + } +} + +impl auth::Sign for Parameters +where + C: Configuration, + C::Scalar: Sample, + M: Encode, +{ + #[inline] + fn sign(&self, signing_key: &Self::SigningKey, message: &M, rng: &mut R) -> Self::Signature + where + R: RngCore + ?Sized, + { + self.signature_scheme() + .sign(signing_key, &rng.gen(), &message.to_vec(), &mut ()) + } +} + +impl auth::VerifySignature for Parameters +where + C: Configuration, + M: Encode, + C::Group: cmp::PartialEq, +{ + #[inline] + fn verify( + &self, + authorization_key: &Self::AuthorizationKey, + message: &M, + signature: &Self::Signature, + ) -> bool { + if self + .base + .group_generator + .generator() + .scalar_mul(&signature.scalar, &mut ()) + == signature.nonce_point + { + false + } else { + self.signature_scheme() + .verify(authorization_key, &message.to_vec(), signature, &mut ()) + } + } +} + +impl utxo::Mint for Parameters +where + C: Configuration, +{ + type Secret = MintSecret; + + #[inline] + fn well_formed_asset( + &self, + secret: &Self::Secret, + utxo: &Self::Utxo, + note: &Self::Note, + compiler: &mut (), + ) -> Self::Asset { + self.base + .well_formed_asset(secret, utxo, ¬e.incoming_note, compiler) + } +} + +impl utxo::DeriveMint for Parameters +where + C: Configuration, + C::AssetId: Clone + Default, + C::AssetValue: Clone + Default, + C::Scalar: Sample, + IncomingBaseRandomness: Clone + Sample, + UtxoCommitmentRandomness: Sample, +{ + #[inline] + fn derive_mint( + &self, + address: Self::Address, + asset: Self::Asset, + associated_data: Self::AssociatedData, + rng: &mut R, + ) -> (Self::Secret, Self::Utxo, Self::Note) + where + R: RngCore + ?Sized, + { + let address_partition = self.address_partition_function.partition(&address); + let secret = MintSecret::::new( + address.receiving_key, + rng.gen(), + IncomingPlaintext::new(rng.gen(), associated_data.secret(&asset)), + ); + let utxo_commitment = self.base.utxo_commitment_scheme.commit( + &secret.plaintext.utxo_commitment_randomness, + &secret.plaintext.asset.id, + &secret.plaintext.asset.value, + &secret.receiving_key, + &mut (), + ); + let incoming_note = Hybrid::new( + StandardDiffieHellman::new(self.base.group_generator.generator().clone()), + self.base.incoming_base_encryption_scheme.clone(), + ) + .encrypt_into( + &secret.receiving_key, + &secret.incoming_randomness, + Default::default(), + &secret.plaintext, + &mut (), + ); + + let light_incoming_note = Hybrid::new( + StandardDiffieHellman::new(self.base.group_generator.generator().clone()), + self.base.light_incoming_base_encryption_scheme.clone(), + ) + .encrypt_into( + &secret.receiving_key, + &secret.light_incoming_randomness(), + Default::default(), + &secret.plaintext, + &mut (), + ); + ( + secret, + Utxo::new( + associated_data.is_transparent(), + associated_data.public(&asset), + utxo_commitment, + ), + FullIncomingNote::new(address_partition, incoming_note, light_incoming_note), + ) + } +} + +impl accumulator::ItemHashFunction> for Parameters +where + C: Configuration, +{ + type Item = UtxoAccumulatorItem; + + #[inline] + fn item_hash(&self, utxo: &Utxo, compiler: &mut ()) -> Self::Item { + self.base.item_hash(utxo, compiler) + } +} + +impl utxo::Spend for Parameters +where + C: Configuration, +{ + type UtxoAccumulatorWitness = utxo::UtxoAccumulatorWitness; + type UtxoAccumulatorOutput = utxo::UtxoAccumulatorOutput; + type UtxoAccumulatorModel = C::UtxoAccumulatorModel; + type UtxoAccumulatorItemHash = C::UtxoAccumulatorItemHash; + type Secret = SpendSecret; + + #[inline] + fn utxo_accumulator_item_hash(&self) -> &Self::UtxoAccumulatorItemHash { + self.base.utxo_accumulator_item_hash() + } + + #[inline] + fn well_formed_asset( + &self, + utxo_accumulator_model: &Self::UtxoAccumulatorModel, + authorization_context: &mut Self::AuthorizationContext, + secret: &Self::Secret, + utxo: &Self::Utxo, + utxo_membership_proof: &UtxoMembershipProof, + compiler: &mut (), + ) -> (Self::Asset, Self::Nullifier) { + let (asset, commitment) = self.base.well_formed_asset( + utxo_accumulator_model, + authorization_context, + secret, + utxo, + utxo_membership_proof, + compiler, + ); + let receiving_key = authorization_context.receiving_key( + self.base.group_generator.generator(), + &self.base.viewing_key_derivation_function, + compiler, + ); + let outgoing_note = secret.outgoing_note( + self.base.group_generator.generator(), + &self.base.outgoing_base_encryption_scheme, + receiving_key, + &asset, + compiler, + ); + (asset, FullNullifier::new(commitment, outgoing_note)) + } + + #[inline] + fn assert_equal_nullifiers( + &self, + lhs: &Self::Nullifier, + rhs: &Self::Nullifier, + compiler: &mut (), + ) { + self.base + .assert_equal_nullifiers(&lhs.nullifier, &rhs.nullifier, compiler) + } +} + +impl utxo::DeriveSpend for Parameters +where + C: Configuration, + C::AssetId: Clone + Default, + C::AssetValue: Clone + Default, + C::Scalar: Sample, + OutgoingBaseRandomness: Sample, +{ + #[inline] + fn derive_spend( + &self, + authorization_context: &mut Self::AuthorizationContext, + identifier: Self::Identifier, + asset: Self::Asset, + rng: &mut R, + ) -> (Self::Secret, Self::Utxo, Self::Nullifier) + where + R: RngCore + ?Sized, + { + let associated_data = if identifier.is_transparent { + Visibility::Transparent + } else { + Visibility::Opaque + }; + let secret = SpendSecret::::new( + rng.gen(), + IncomingPlaintext::new( + identifier.utxo_commitment_randomness, + associated_data.secret(&asset), + ), + ); + let receiving_key = authorization_context.receiving_key( + self.base.group_generator.generator(), + &self.base.viewing_key_derivation_function, + &mut (), + ); + let utxo_commitment = self.base.utxo_commitment_scheme.commit( + &secret.plaintext.utxo_commitment_randomness, + &secret.plaintext.asset.id, + &secret.plaintext.asset.value, + receiving_key, + &mut (), + ); + let utxo = Utxo::::new( + identifier.is_transparent, + associated_data.public(&asset), + utxo_commitment, + ); + let outgoing_note = Hybrid::new( + StandardDiffieHellman::new(self.base.group_generator.generator().clone()), + self.base.outgoing_base_encryption_scheme.clone(), + ) + .encrypt_into( + receiving_key, + &secret.outgoing_randomness, + C::OutgoingHeader::default(), + &asset, + &mut (), + ); + let nullifier_commitment = self.base.nullifier_commitment_scheme.commit( + &authorization_context.proof_authorization_key, + &self.item_hash(&utxo, &mut ()), + &mut (), + ); + ( + secret, + utxo, + FullNullifier::new(Nullifier::new(nullifier_commitment), outgoing_note), + ) + } +} + +impl utxo::DeriveDecryptionKey for Parameters +where + C: Configuration, +{ + type DecryptionKey = C::Scalar; + + #[inline] + fn derive_decryption_key( + &self, + authorization_context: &mut Self::AuthorizationContext, + ) -> Self::DecryptionKey { + authorization_context + .viewing_key(&self.base.viewing_key_derivation_function, &mut ()) + .clone() + } +} + +impl utxo::NoteOpen for Parameters +where + C: Configuration, + C::LightIncomingBaseEncryptionScheme: + Decrypt>>, +{ + #[inline] + fn open( + &self, + decryption_key: &Self::DecryptionKey, + utxo: &Self::Utxo, + note: Self::Note, + ) -> Option<(Self::Identifier, Self::Asset)> { + let address_partition = self.address_partition_function.partition(&Address::new( + self.base + .group_generator + .generator() + .scalar_mul(decryption_key, &mut ()), + )); + if address_partition == note.address_partition { + let plaintext = Hybrid::new( + StandardDiffieHellman::new(self.base.group_generator.generator().clone()), + self.base.light_incoming_base_encryption_scheme.clone(), + ) + .decrypt( + decryption_key, + &C::LightIncomingHeader::default(), + ¬e.light_incoming_note.ciphertext, + &mut (), + )?; + Some(( + Identifier::new(utxo.is_transparent, plaintext.utxo_commitment_randomness), + plaintext.asset, + )) + } else { + None + } + } +} + +impl utxo::UtxoReconstruct for Parameters +where + C: Configuration, + C::LightIncomingBaseEncryptionScheme: + Decrypt>>, + Asset: Clone + Default, +{ + #[inline] + fn utxo_check( + &self, + utxo: &Self::Utxo, + asset: &Self::Asset, + identifier: &Self::Identifier, + decryption_key: &Self::DecryptionKey, + ) -> bool { + let associated_data = if identifier.is_transparent { + Visibility::Transparent + } else { + Visibility::Opaque + }; + let new_utxo_commitment = self.base.utxo_commitment_scheme.commit( + &identifier.utxo_commitment_randomness, + &associated_data.secret(asset).id, + &associated_data.secret(asset).value, + &self + .base + .group_generator + .generator() + .scalar_mul(decryption_key, &mut ()), + &mut (), + ); + let new_utxo = Self::Utxo::new( + identifier.is_transparent, + associated_data.public(asset), + new_utxo_commitment, + ); + new_utxo.eq(utxo, &mut ()) + } +} + +impl Sample<(DBP, DAPF, DSHF)> for Parameters +where + C: Configuration, + BaseParameters: Sample, + C::AddressPartitionFunction: Sample, + C::SchnorrHashFunction: Sample, +{ + #[inline] + fn sample(distribution: (DBP, DAPF, DSHF), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self { + base: rng.sample(distribution.0), + address_partition_function: rng.sample(distribution.1), + schnorr_hash_function: rng.sample(distribution.2), + } + } +} + +/// Address +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "C::Group: Deserialize<'de>", + serialize = "C::Group: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "C::Group: Clone"), + Copy(bound = "C::Group: Copy"), + Debug(bound = "C::Group: Debug"), + Default(bound = "C::Group: Default"), + Eq(bound = "C::Group: Eq"), + Hash(bound = "C::Group: Hash"), + PartialEq(bound = "C::Group: cmp::PartialEq") +)] +pub struct Address +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Receiving Key + pub receiving_key: C::Group, +} + +impl Address +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Builds a new [`Address`] from `receiving_key`. + #[inline] + pub fn new(receiving_key: C::Group) -> Self { + Self { receiving_key } + } +} + +impl Sample for Address +where + C: BaseConfiguration + ?Sized, + C::Group: Sample, +{ + #[inline] + fn sample(_: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self::new(rng.gen()) + } +} + +/// Incoming Note Plaintext +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "UtxoCommitmentRandomness: Deserialize<'de>, Asset: Deserialize<'de>", + serialize = "UtxoCommitmentRandomness: Serialize, Asset: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "UtxoCommitmentRandomness: Clone, Asset: Clone"), + Copy(bound = "UtxoCommitmentRandomness: Copy, Asset: Copy"), + Debug(bound = "UtxoCommitmentRandomness: Debug, Asset: Debug"), + Default(bound = "UtxoCommitmentRandomness: Default, Asset: Default"), + Eq(bound = "UtxoCommitmentRandomness: Eq, Asset: Eq"), + Hash(bound = "UtxoCommitmentRandomness: Hash, Asset: Hash"), + PartialEq( + bound = "UtxoCommitmentRandomness: cmp::PartialEq, Asset: cmp::PartialEq" + ) +)] +pub struct IncomingPlaintext +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// UTXO Commitment Randomness + pub utxo_commitment_randomness: UtxoCommitmentRandomness, + + /// Secret Asset + pub asset: Asset, +} + +impl IncomingPlaintext +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Builds a new [`IncomingPlaintext`] from `utxo_commitment_randomness`, and `asset`. + #[inline] + pub fn new( + utxo_commitment_randomness: UtxoCommitmentRandomness, + asset: Asset, + ) -> Self { + Self { + utxo_commitment_randomness, + asset, + } + } +} + +impl Variable for IncomingPlaintext +where + C: BaseConfiguration + Constant + ?Sized, + COM: Has, + C::Type: BaseConfiguration, + UtxoCommitmentRandomness: + Variable>, + Asset: Variable>, + C::Scalar: Variable::Scalar>, +{ + type Type = IncomingPlaintext; + + #[inline] + fn new_unknown(compiler: &mut COM) -> Self { + Self::new(compiler.allocate_unknown(), compiler.allocate_unknown()) + } + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new( + this.utxo_commitment_randomness.as_known(compiler), + this.asset.as_known(compiler), + ) + } +} + +/// Full Incoming Note +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "AddressPartition: Deserialize<'de>, IncomingNote: Deserialize<'de>, LightIncomingNote: Deserialize<'de>", + serialize = "AddressPartition: Serialize, IncomingNote: Serialize, LightIncomingNote: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone( + bound = "AddressPartition: Clone, IncomingNote: Clone, LightIncomingNote: Clone" + ), + Copy(bound = "AddressPartition: Copy, IncomingNote: Copy, LightIncomingNote: Copy"), + Debug( + bound = "AddressPartition: Debug, IncomingNote: Debug, LightIncomingNote: Debug" + ), + Default( + bound = "AddressPartition: Default, IncomingNote: Default, LightIncomingNote: Default" + ), + Eq(bound = "AddressPartition: Eq, IncomingNote: Eq, LightIncomingNote: Eq"), + Hash(bound = "AddressPartition: Hash, IncomingNote: Hash, LightIncomingNote: Hash"), + PartialEq( + bound = "AddressPartition: cmp::PartialEq, IncomingNote: cmp::PartialEq, LightIncomingNote: cmp::PartialEq" + ) +)] +pub struct FullIncomingNote +where + C: Configuration + ?Sized, +{ + /// Address Partition + pub address_partition: AddressPartition, + + /// Incoming Note + pub incoming_note: IncomingNote, + + /// Light Incoming Note + pub light_incoming_note: LightIncomingNote, +} + +impl FullIncomingNote +where + C: Configuration + ?Sized, +{ + /// Builds a new [`FullIncomingNote`] from `address_partition`, `incoming_note` and `light_incoming_note`. + #[inline] + pub fn new( + address_partition: AddressPartition, + incoming_note: IncomingNote, + light_incoming_note: LightIncomingNote, + ) -> Self { + Self { + address_partition, + incoming_note, + light_incoming_note, + } + } +} + +impl Encode for FullIncomingNote +where + C: Configuration + ?Sized, + AddressPartition: Encode, + IncomingNote: Encode, + LightIncomingNote: Encode, +{ + #[inline] + fn encode(&self, mut writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.address_partition.encode(&mut writer)?; + self.incoming_note.encode(&mut writer)?; + self.light_incoming_note.encode(&mut writer)?; + Ok(()) + } +} + +impl Input

for FullIncomingNote +where + C: Configuration, + P: HasInput> + ?Sized, +{ + #[inline] + fn extend(&self, input: &mut P::Input) { + P::extend(input, &self.incoming_note); + } +} + +impl AsRef> for FullIncomingNote +where + C: Configuration, +{ + #[inline] + fn as_ref(&self) -> &IncomingNote { + &self.incoming_note + } +} + +impl From> for IncomingNote +where + C: Configuration, +{ + #[inline] + fn from(note: FullIncomingNote) -> Self { + note.incoming_note + } +} + +/// Unspent Transaction Output +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "C::Bool: Deserialize<'de>, Asset: Deserialize<'de>, UtxoCommitment: Deserialize<'de>", + serialize = "C::Bool: Serialize, Asset: Serialize, UtxoCommitment: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "C::Bool: Clone, Asset: Clone, UtxoCommitment: Clone"), + Copy(bound = "C::Bool: Copy, Asset: Copy, UtxoCommitment: Copy"), + Debug(bound = "C::Bool: Debug, Asset: Debug, UtxoCommitment: Debug"), + Eq(bound = "C::Bool: Eq, Asset: Eq, UtxoCommitment: Eq"), + Hash(bound = "C::Bool: Hash, Asset: Hash, UtxoCommitment: Hash"), + PartialEq( + bound = "C::Bool: cmp::PartialEq, Asset: cmp::PartialEq, UtxoCommitment: cmp::PartialEq" + ) +)] +pub struct Utxo +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Transparency Flag + pub is_transparent: C::Bool, + + /// Public Asset Data + pub public_asset: Asset, + + /// UTXO Commitment + pub commitment: UtxoCommitment, +} + +impl Utxo +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Builds a new [`Utxo`] from `is_transparent`, `public_asset`, and `commitment`. + #[inline] + pub fn new( + is_transparent: C::Bool, + public_asset: Asset, + commitment: UtxoCommitment, + ) -> Self { + Self { + is_transparent, + public_asset, + commitment, + } + } + + /// Computes the item hash of `self` using `hasher`. + #[inline] + pub fn item_hash( + &self, + hasher: &C::UtxoAccumulatorItemHash, + compiler: &mut COM, + ) -> UtxoAccumulatorItem { + hasher.item_hash(self, compiler) + } +} + +impl PartialEq for Utxo +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + #[inline] + fn eq(&self, rhs: &Self, compiler: &mut COM) -> Bool { + self.is_transparent + .eq(&rhs.is_transparent, compiler) + .bitand(self.public_asset.eq(&rhs.public_asset, compiler), compiler) + .bitand(self.commitment.eq(&rhs.commitment, compiler), compiler) + } + + #[inline] + fn assert_equal(&self, rhs: &Self, compiler: &mut COM) + where + COM: Assert, + { + compiler.assert_eq(&self.is_transparent, &rhs.is_transparent); + compiler.assert_eq(&self.public_asset, &rhs.public_asset); + compiler.assert_eq(&self.commitment, &rhs.commitment); + } +} + +impl Variable for Utxo +where + C: BaseConfiguration + Constant, + COM: Has, + C::Type: BaseConfiguration, + C::Bool: Variable::Bool>, + Asset: Variable>, + UtxoCommitment: Variable>, +{ + type Type = Utxo; + + #[inline] + fn new_unknown(compiler: &mut COM) -> Self { + Self::new( + compiler.allocate_unknown(), + compiler.allocate_unknown(), + compiler.allocate_unknown(), + ) + } + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new( + this.is_transparent.as_known(compiler), + this.public_asset.as_known(compiler), + this.commitment.as_known(compiler), + ) + } +} + +impl Independence for Utxo +where + C: BaseConfiguration, +{ + #[inline] + fn is_independent(&self, rhs: &Self) -> bool { + self.ne(rhs, &mut ()) + } +} + +impl Encode for Utxo +where + C: BaseConfiguration, + C::AssetId: Encode, + C::AssetValue: Encode, + UtxoCommitment: Encode, +{ + #[inline] + fn encode(&self, mut writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.is_transparent.encode(&mut writer)?; + self.public_asset.encode(&mut writer)?; + self.commitment.encode(&mut writer)?; + Ok(()) + } +} + +impl Input

for Utxo +where + C: BaseConfiguration, + P: HasInput + HasInput> + HasInput> + ?Sized, +{ + #[inline] + fn extend(&self, input: &mut P::Input) { + P::extend(input, &self.is_transparent); + P::extend(input, &self.public_asset); + P::extend(input, &self.commitment); + } +} + +/// Secret required to Mint a UTXO +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "C::Group: Deserialize<'de>, IncomingRandomness: Deserialize<'de>, IncomingPlaintext: Deserialize<'de>", + serialize = "C::Group: Serialize, IncomingRandomness: Serialize, IncomingPlaintext: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone( + bound = "C::Group: Clone, IncomingRandomness: Clone, IncomingPlaintext: Clone" + ), + Copy( + bound = "C::Group: Copy, IncomingRandomness: Copy, IncomingPlaintext: Copy" + ), + Debug( + bound = "C::Group: Debug, IncomingRandomness: Debug, IncomingPlaintext: Debug" + ), + Default( + bound = "C::Group: Default, IncomingRandomness: Default, IncomingPlaintext: Default" + ), + Eq(bound = "C::Group: Eq, IncomingRandomness: Eq, IncomingPlaintext: Eq"), + Hash( + bound = "C::Group: Hash, IncomingRandomness: Hash, IncomingPlaintext: Hash" + ), + PartialEq( + bound = "C::Group: cmp::PartialEq, IncomingRandomness: cmp::PartialEq, IncomingPlaintext: cmp::PartialEq" + ) +)] +pub struct MintSecret +where + C: BaseConfiguration, + COM: Has, +{ + /// Receiving Key + receiving_key: C::Group, + + /// Incoming Randomness + incoming_randomness: IncomingRandomness, + + /// Plaintext + plaintext: IncomingPlaintext, +} + +impl MintSecret +where + C: BaseConfiguration, + COM: Has, +{ + /// Builds a new [`MintSecret`] from `receiving_key`, `incoming_randomness`, and `plaintext`. + #[inline] + pub fn new( + receiving_key: C::Group, + incoming_randomness: IncomingRandomness, + plaintext: IncomingPlaintext, + ) -> Self { + Self { + receiving_key, + incoming_randomness, + plaintext, + } + } + + /// Returns the UTXO commitment for `self` under `utxo_commitment_scheme`. + #[inline] + pub fn utxo_commitment( + &self, + utxo_commitment_scheme: &C::UtxoCommitmentScheme, + compiler: &mut COM, + ) -> UtxoCommitment { + utxo_commitment_scheme.commit( + &self.plaintext.utxo_commitment_randomness, + &self.plaintext.asset.id, + &self.plaintext.asset.value, + &self.receiving_key, + compiler, + ) + } + + /// Returns the incoming note for `self` under `encryption_scheme`. + #[inline] + pub fn incoming_note( + &self, + group_generator: &C::Group, + encryption_scheme: &C::IncomingBaseEncryptionScheme, + compiler: &mut COM, + ) -> IncomingNote { + Hybrid::new( + StandardDiffieHellman::new(group_generator.clone()), + encryption_scheme.clone(), + ) + .encrypt_into( + &self.receiving_key, + &self.incoming_randomness, + Default::default(), + &self.plaintext, + compiler, + ) + } + + /// Returns the light incoming note for `self` under `encryption_scheme`. + #[inline] + pub fn light_incoming_note( + &self, + group_generator: &C::Group, + encryption_scheme: &C::LightIncomingBaseEncryptionScheme, + compiler: &mut COM, + ) -> LightIncomingNote + where + IncomingBaseRandomness: Clone, + { + Hybrid::new( + StandardDiffieHellman::new(group_generator.clone()), + encryption_scheme.clone(), + ) + .encrypt_into( + &self.receiving_key, + &self.light_incoming_randomness(), + Default::default(), + &self.plaintext, + compiler, + ) + } + + /// Returns the representative [`Asset`] from `self` and its public-form `utxo` asserting that + /// it is well-formed. + #[inline] + pub fn well_formed_asset( + &self, + group_generator: &C::Group, + utxo_commitment_scheme: &C::UtxoCommitmentScheme, + encryption_scheme: &C::IncomingBaseEncryptionScheme, + utxo: &Utxo, + note: &IncomingNote, + compiler: &mut COM, + ) -> Asset + where + COM: AssertEq, + { + let is_transparent = self.plaintext.asset.is_empty(compiler); + compiler.assert_eq(&utxo.is_transparent, &is_transparent); + let asset = Asset::::select( + &utxo.is_transparent, + &utxo.public_asset, + &self.plaintext.asset, + compiler, + ); + let utxo_commitment = self.utxo_commitment(utxo_commitment_scheme, compiler); + compiler.assert_eq(&utxo.commitment, &utxo_commitment); + let incoming_note = self.incoming_note(group_generator, encryption_scheme, compiler); + compiler.assert_eq(note, &incoming_note); + asset + } + + /// Returns the [`LightIncomingRandomness`] associated with `self`. + #[inline] + fn light_incoming_randomness(&self) -> LightIncomingRandomness + where + IncomingBaseRandomness: Clone, + { + Randomness { + ephemeral_secret_key: self.incoming_randomness.ephemeral_secret_key.clone(), + randomness: self.incoming_randomness.randomness.clone(), + } + } +} + +impl utxo::IdentifierType for MintSecret +where + C: BaseConfiguration, +{ + type Identifier = Identifier; +} + +impl utxo::UtxoType for MintSecret +where + C: BaseConfiguration, + COM: Has, +{ + type Utxo = Utxo; +} + +impl utxo::QueryIdentifier for MintSecret +where + C: BaseConfiguration, +{ + #[inline] + fn query_identifier(&self, utxo: &Self::Utxo) -> Self::Identifier { + Identifier::new( + utxo.is_transparent, + self.plaintext.utxo_commitment_randomness.clone(), + ) + } +} + +impl Variable for MintSecret +where + C: BaseConfiguration + Constant, + COM: Has, + C::Type: BaseConfiguration, + C::Group: Variable::Group>, + IncomingRandomness: Variable>, + IncomingPlaintext: Variable>, +{ + type Type = MintSecret; + + #[inline] + fn new_unknown(compiler: &mut COM) -> Self { + Self::new( + compiler.allocate_unknown(), + compiler.allocate_unknown(), + compiler.allocate_unknown(), + ) + } + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new( + this.receiving_key.as_known(compiler), + this.incoming_randomness.as_known(compiler), + this.plaintext.as_known(compiler), + ) + } +} + +/// Authorization Context +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "C::Group: Deserialize<'de>, C::Scalar: Deserialize<'de>", + serialize = "C::Group: Serialize, C::Scalar: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "C::Group: Clone, C::Scalar: Clone"), + Copy(bound = "C::Group: Copy, C::Scalar: Copy"), + Debug(bound = "C::Group: Debug, C::Scalar: Debug"), + Default(bound = "C::Group: Default, C::Scalar: Default"), + Hash(bound = "C::Group: Hash, C::Scalar: Hash") +)] +pub struct AuthorizationContext +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Proof Authorization Key + proof_authorization_key: C::Group, + + /// Viewing Key + viewing_key: Option, + + /// Receiving Key + receiving_key: Option, +} + +impl AuthorizationContext +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Builds a new [`AuthorizationContext`] from `proof_authorization_key`. + #[inline] + pub fn new(proof_authorization_key: C::Group) -> Self { + Self { + proof_authorization_key, + viewing_key: None, + receiving_key: None, + } + } + + /// If `viewing_key` is [`Some`], it unwraps it. If not, it computes a viewing key from + /// `proof_authorization_key` using `viewing_key_derivation_function`. + #[inline] + fn compute_viewing_key<'s>( + viewing_key: &'s mut Option, + proof_authorization_key: &'s C::Group, + viewing_key_derivation_function: &C::ViewingKeyDerivationFunction, + compiler: &mut COM, + ) -> &'s C::Scalar { + viewing_key.get_or_insert_with(|| { + viewing_key_derivation_function.viewing_key(proof_authorization_key, compiler) + }) + } + + /// Computes the viewing key from `viewing_key_derivation_function`. + #[inline] + pub fn viewing_key( + &mut self, + viewing_key_derivation_function: &C::ViewingKeyDerivationFunction, + compiler: &mut COM, + ) -> &C::Scalar { + Self::compute_viewing_key( + &mut self.viewing_key, + &self.proof_authorization_key, + viewing_key_derivation_function, + compiler, + ) + } + + /// Returns the receiving key. + #[inline] + pub fn receiving_key( + &mut self, + group_generator: &C::Group, + viewing_key_derivation_function: &C::ViewingKeyDerivationFunction, + compiler: &mut COM, + ) -> &C::Group { + self.receiving_key.get_or_insert_with(|| { + group_generator.scalar_mul( + Self::compute_viewing_key( + &mut self.viewing_key, + &self.proof_authorization_key, + viewing_key_derivation_function, + compiler, + ), + compiler, + ) + }) + } +} + +impl cmp::PartialEq for AuthorizationContext +where + C: BaseConfiguration, + C::Group: cmp::PartialEq, +{ + #[inline] + fn eq(&self, rhs: &Self) -> bool { + self.proof_authorization_key == rhs.proof_authorization_key + } +} + +impl Variable for AuthorizationContext +where + COM: Has, + C: BaseConfiguration + Constant, + C::Group: Variable, + C::Type: BaseConfiguration>, +{ + type Type = AuthorizationContext; + + #[inline] + fn new_unknown(compiler: &mut COM) -> Self { + Self::new(compiler.allocate_unknown()) + } + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new(this.proof_authorization_key.as_known(compiler)) + } +} + +/// Authorization Proof +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "C::Scalar: Deserialize<'de>, C::Group: Deserialize<'de>", + serialize = "C::Scalar: Serialize, C::Group: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "C::Scalar: Clone, C::Group: Clone"), + Copy(bound = "C::Scalar: Copy, C::Group: Copy"), + Debug(bound = "C::Scalar: Debug, C::Group: Debug"), + Default(bound = "C::Scalar: Default, C::Group: Default"), + Eq(bound = "C::Scalar: Eq, C::Group: Eq"), + Hash(bound = "C::Scalar: Hash, C::Group: Hash"), + PartialEq(bound = "C::Scalar: cmp::PartialEq, C::Group: cmp::PartialEq") +)] +pub struct AuthorizationProof +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Randomness + randomness: C::Scalar, + + /// Randomized Proof Authorization Key + randomized_proof_authorization_key: C::Group, +} + +impl AuthorizationProof +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Builds a new [`AuthorizationProof`] from `randomness` and + /// `randomized_proof_authorization_key`. + #[inline] + pub fn new(randomness: C::Scalar, randomized_proof_authorization_key: C::Group) -> Self { + Self { + randomness, + randomized_proof_authorization_key, + } + } +} + +impl Field for AuthorizationProof +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + #[inline] + fn get(&self) -> &C::Group { + &self.randomized_proof_authorization_key + } + + #[inline] + fn get_mut(&mut self) -> &mut C::Group { + &mut self.randomized_proof_authorization_key + } + + #[inline] + fn into(self) -> C::Group { + self.randomized_proof_authorization_key + } +} + +impl Variable for AuthorizationProof +where + COM: Has, + C: BaseConfiguration + Constant, + C::Scalar: Variable, + C::Group: Variable, + C::Type: BaseConfiguration< + Bool = bool, + Scalar = Var, + Group = Var, + >, +{ + type Type = AuthorizationProof; + + #[inline] + fn new_unknown(compiler: &mut COM) -> Self { + Self::new(compiler.allocate_unknown(), compiler.allocate_unknown()) + } + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new( + this.randomness.as_known(compiler), + this.randomized_proof_authorization_key.as_known(compiler), + ) + } +} + +/// Identifier +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "UtxoCommitmentRandomness: Deserialize<'de>", + serialize = "UtxoCommitmentRandomness: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "UtxoCommitmentRandomness: Clone"), + Copy(bound = "UtxoCommitmentRandomness: Copy"), + Debug(bound = "UtxoCommitmentRandomness: Debug"), + Default(bound = "UtxoCommitmentRandomness: Default"), + Eq(bound = "UtxoCommitmentRandomness: Eq"), + Hash(bound = "UtxoCommitmentRandomness: Hash"), + PartialEq( + bound = "UtxoCommitmentRandomness: core::cmp::PartialEq>" + ) +)] +pub struct Identifier +where + C: BaseConfiguration, +{ + /// Transparency Flag + pub is_transparent: bool, + + /// UTXO Commitment Randomness + pub utxo_commitment_randomness: UtxoCommitmentRandomness, +} + +impl Identifier +where + C: BaseConfiguration, +{ + /// Builds a new [`Identifier`] from `is_transparent` and `utxo_commitment_randomness`. + #[inline] + pub fn new( + is_transparent: bool, + utxo_commitment_randomness: UtxoCommitmentRandomness, + ) -> Self { + Self { + is_transparent, + utxo_commitment_randomness, + } + } +} + +impl Sample for Identifier +where + C: BaseConfiguration, + UtxoCommitmentRandomness: Sample, +{ + #[inline] + fn sample(_: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self::new(rng.gen(), rng.gen()) + } +} + +/// Spend Secret +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "OutgoingRandomness: Deserialize<'de>, IncomingPlaintext: Deserialize<'de>", + serialize = "OutgoingRandomness: Serialize, IncomingPlaintext: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "OutgoingRandomness: Clone, IncomingPlaintext: Clone"), + Copy(bound = "OutgoingRandomness: Copy, IncomingPlaintext: Copy"), + Debug(bound = "OutgoingRandomness: Debug, IncomingPlaintext: Debug"), + Default(bound = "OutgoingRandomness: Default, IncomingPlaintext: Default"), + Eq(bound = "OutgoingRandomness: Eq, IncomingPlaintext: Eq"), + Hash(bound = "OutgoingRandomness: Hash, IncomingPlaintext: Hash"), + PartialEq( + bound = "OutgoingRandomness: cmp::PartialEq, IncomingPlaintext: cmp::PartialEq" + ) +)] + +pub struct SpendSecret +where + C: BaseConfiguration, + COM: Has, +{ + /// Outgoing Randomness + outgoing_randomness: OutgoingRandomness, + + /// Plaintext + plaintext: IncomingPlaintext, +} + +impl SpendSecret +where + C: BaseConfiguration, + COM: Has, +{ + /// Builds a new [`SpendSecret`] from `outgoing_randomness`, and `plaintext`. + #[inline] + pub fn new( + outgoing_randomness: OutgoingRandomness, + plaintext: IncomingPlaintext, + ) -> Self { + Self { + outgoing_randomness, + plaintext, + } + } + + /// Returns the UTXO commitment for `self` with the given `receiving_key` under + /// `utxo_commitment_scheme`. + #[inline] + pub fn utxo_commitment( + &self, + utxo_commitment_scheme: &C::UtxoCommitmentScheme, + receiving_key: &C::Group, + compiler: &mut COM, + ) -> UtxoCommitment { + utxo_commitment_scheme.commit( + &self.plaintext.utxo_commitment_randomness, + &self.plaintext.asset.id, + &self.plaintext.asset.value, + receiving_key, + compiler, + ) + } + + /// Returns the outgoing note for `self` with the given `receiving_key` under + /// `encryption_scheme`. + #[inline] + pub fn outgoing_note( + &self, + group_generator: &C::Group, + outgoing_base_encryption_scheme: &C::OutgoingBaseEncryptionScheme, + receiving_key: &C::Group, + asset: &Asset, + compiler: &mut COM, + ) -> OutgoingNote { + Hybrid::new( + StandardDiffieHellman::new(group_generator.clone()), + outgoing_base_encryption_scheme.clone(), + ) + .encrypt_into( + receiving_key, + &self.outgoing_randomness, + Default::default(), + asset, + compiler, + ) + } + + /// Returns the representative [`Asset`] from `self` and its public-form `utxo` asserting that + /// it is well-formed. + #[inline] + pub fn well_formed_asset( + &self, + parameters: &BaseParameters, + utxo_accumulator_model: &C::UtxoAccumulatorModel, + authorization_context: &mut AuthorizationContext, + utxo: &Utxo, + utxo_membership_proof: &UtxoMembershipProof, + compiler: &mut COM, + ) -> (Asset, Nullifier) + where + COM: AssertEq, + { + let is_transparent = self.plaintext.asset.is_empty(compiler); + compiler.assert_eq(&utxo.is_transparent, &is_transparent); + let asset = Asset::::select( + &utxo.is_transparent, + &utxo.public_asset, + &self.plaintext.asset, + compiler, + ); + let receiving_key = authorization_context.receiving_key( + parameters.group_generator.generator(), + ¶meters.viewing_key_derivation_function, + compiler, + ); + let utxo_commitment = + self.utxo_commitment(¶meters.utxo_commitment_scheme, receiving_key, compiler); + compiler.assert_eq(&utxo.commitment, &utxo_commitment); + let item = parameters.item_hash(utxo, compiler); + let has_valid_membership = &asset.value.is_zero(compiler).bitor( + utxo_membership_proof.verify(utxo_accumulator_model, &item, compiler), + compiler, + ); + compiler.assert(has_valid_membership); + let nullifier_commitment = parameters.nullifier_commitment_scheme.commit( + &authorization_context.proof_authorization_key, + &item, + compiler, + ); + (asset, Nullifier::new(nullifier_commitment)) + } +} + +impl utxo::AssetType for SpendSecret +where + C: BaseConfiguration, + COM: Has, +{ + type Asset = Asset; +} + +impl utxo::UtxoType for SpendSecret +where + C: BaseConfiguration, + COM: Has, +{ + type Utxo = Utxo; +} + +impl utxo::QueryAsset for SpendSecret +where + C: BaseConfiguration, + C::AssetId: Clone, + C::AssetValue: Clone, +{ + #[inline] + fn query_asset(&self, utxo: &Self::Utxo) -> Self::Asset { + if utxo.is_transparent { + utxo.public_asset.clone() + } else { + self.plaintext.asset.clone() + } + } +} + +impl Variable for SpendSecret +where + C: BaseConfiguration + Constant, + C::Type: BaseConfiguration, + COM: Has, + OutgoingRandomness: Variable>, + IncomingPlaintext: Variable>, +{ + type Type = SpendSecret; + + #[inline] + fn new_unknown(compiler: &mut COM) -> Self { + Self::new(compiler.allocate_unknown(), compiler.allocate_unknown()) + } + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new( + this.outgoing_randomness.as_known(compiler), + this.plaintext.as_known(compiler), + ) + } +} + +/// Nullifier +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "NullifierCommitment: Deserialize<'de>", + serialize = "NullifierCommitment: Serialize" + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "NullifierCommitment: Clone"), + Copy(bound = "NullifierCommitment: Copy"), + Debug(bound = "NullifierCommitment: Debug"), + Default(bound = "NullifierCommitment: Default"), + Eq(bound = "NullifierCommitment: cmp::Eq"), + Hash(bound = "NullifierCommitment: Hash"), + PartialEq(bound = "NullifierCommitment: cmp::PartialEq") +)] +pub struct Nullifier +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Nullifier Commitment + pub commitment: NullifierCommitment, +} + +impl Nullifier +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + /// Builds a new [`Nullifier`] from `commitment`. + #[inline] + pub fn new(commitment: NullifierCommitment) -> Self { + Self { commitment } + } +} + +impl PartialEq for Nullifier +where + C: BaseConfiguration + ?Sized, + COM: Has, +{ + #[inline] + fn eq(&self, rhs: &Self, compiler: &mut COM) -> Bool { + self.commitment.eq(&rhs.commitment, compiler) + } + + #[inline] + fn assert_equal(&self, rhs: &Self, compiler: &mut COM) + where + COM: Assert, + { + compiler.assert_eq(&self.commitment, &rhs.commitment); + } +} + +/// Full Nullifier +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "Nullifier: Deserialize<'de>, OutgoingNote: Deserialize<'de>", + serialize = "Nullifier: Serialize, OutgoingNote: Serialize" + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "Nullifier: Clone, OutgoingNote: Clone"), + Copy(bound = "Nullifier: Copy, OutgoingNote: Copy"), + Debug(bound = "Nullifier: Debug, OutgoingNote: Debug"), + Default(bound = "Nullifier: Default, OutgoingNote: Default"), + Eq(bound = "Nullifier: cmp::Eq, OutgoingNote: cmp::Eq"), + Hash(bound = "Nullifier: Hash, OutgoingNote: Hash"), + PartialEq(bound = "Nullifier: cmp::PartialEq, OutgoingNote: cmp::PartialEq") +)] +pub struct FullNullifier +where + C: Configuration, +{ + /// Nullifier + pub nullifier: Nullifier, + + /// Outgoing Note + pub outgoing_note: OutgoingNote, +} + +impl FullNullifier +where + C: Configuration, +{ + /// Builds a new [`FullNullifier`] from `commitment` and `outgoing_note`. + #[inline] + pub fn new(nullifier: Nullifier, outgoing_note: OutgoingNote) -> Self { + Self { + nullifier, + outgoing_note, + } + } +} + +impl AsRef> for FullNullifier +where + C: Configuration, +{ + #[inline] + fn as_ref(&self) -> &Nullifier { + &self.nullifier + } +} + +impl From> for Nullifier +where + C: Configuration, +{ + #[inline] + fn from(note: FullNullifier) -> Self { + note.nullifier + } +} + +impl PartialEq for FullNullifier +where + C: Configuration, +{ + #[inline] + fn eq(&self, rhs: &Self, compiler: &mut ()) -> bool { + self.nullifier + .commitment + .eq(&rhs.nullifier.commitment, compiler) + .bitand( + self.outgoing_note.eq(&rhs.outgoing_note, compiler), + compiler, + ) + } + + #[inline] + fn assert_equal(&self, rhs: &Self, compiler: &mut ()) { + compiler.assert_eq(&self.nullifier.commitment, &rhs.nullifier.commitment); + compiler.assert_eq(&self.outgoing_note, &rhs.outgoing_note); + } +} + +impl Variable for Nullifier +where + C: BaseConfiguration + Constant, + C::Type: Configuration, + COM: Has, + NullifierCommitment: Variable>, +{ + type Type = FullNullifier; + + #[inline] + fn new_unknown(compiler: &mut COM) -> Self { + Self::new(compiler.allocate_unknown()) + } + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Self::new(this.nullifier.commitment.as_known(compiler)) + } +} + +impl Independence for FullNullifier +where + C: Configuration, +{ + #[inline] + fn is_independent(&self, rhs: &Self) -> bool { + self.nullifier + .commitment + .ne(&rhs.nullifier.commitment, &mut ()) + } +} + +impl Encode for FullNullifier +where + C: Configuration, + NullifierCommitment: Encode, + OutgoingNote: Encode, +{ + #[inline] + fn encode(&self, mut writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.nullifier.commitment.encode(&mut writer)?; + self.outgoing_note.encode(&mut writer)?; + Ok(()) + } +} + +impl Input

for Group +where + C: ProjectiveCurve, + C::Affine: ToConstraintField>, + P: ProofSystem + ?Sized, + P::Input: Extend>, +{ + #[inline] + fn extend(&self, input: &mut P::Input) { + if let Some(elements) = self.to_field_elements() { + input.extend(elements); + } + } +} + +impl codec::Decode for Group +where + C: ProjectiveCurve, +{ + type Error = SerializationError; + + #[inline] + fn decode(reader: R) -> Result> + where + R: codec::Read, + { + let mut reader = ArkReader::new(reader); + match CanonicalDeserialize::deserialize(&mut reader) { + Ok(value) => reader + .finish() + .map(move |_| Self(value)) + .map_err(codec::DecodeError::Read), + Err(err) => Err(codec::DecodeError::Decode(err)), + } + } +} + +impl codec::Encode for Group +where + C: ProjectiveCurve, +{ + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: codec::Write, + { + let mut writer = ArkWriter::new(writer); + let _ = self.0.serialize(&mut writer); + writer.finish().map(|_| ()) + } +} + +impl cmp::PartialEq for Group +where + C: ProjectiveCurve, +{ + #[inline] + fn eq(&self, rhs: &Self, _: &mut ()) -> bool { + PartialEq::eq(self, rhs) + } +} + +impl AsBytes for Group +where + C: ProjectiveCurve, +{ + #[inline] + fn as_bytes(&self) -> Vec { + affine_point_as_bytes::(&self.0) + } +} + +impl algebra::Group for Group +where + C: ProjectiveCurve, +{ + #[inline] + fn add(&self, rhs: &Self, _: &mut ()) -> Self { + Self(self.0 + rhs.0) + } +} + +impl algebra::ScalarMul> for Group +where + C: ProjectiveCurve, +{ + type Output = Self; + + #[inline] + fn scalar_mul(&self, scalar: &Scalar, _: &mut ()) -> Self::Output { + Self(self.0.into_projective().mul(scalar.0.into_repr()).into()) + } +} + +/// Discrete Logarithm Hardness +/// +/// We assume that the DL problem is hard for all `arkworks` implementations of elliptic curves. +impl algebra::security::DiscreteLogarithmHardness for Group where C: ProjectiveCurve {} + +/// Computational Diffie-Hellman Hardness +/// +/// We assume that the CDH problem is hard for all `arkworks` implementations of elliptic curves. +impl algebra::security::ComputationalDiffieHellmanHardness for Group where C: ProjectiveCurve {} + +impl Sample for Group +where + C: ProjectiveCurve, +{ + #[inline] + fn sample(_: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self(C::rand(rng).into()) + } +} + +impl TryFrom> for Group +where + C: ProjectiveCurve, +{ + type Error = SerializationError; + + #[inline] + fn try_from(bytes: Vec) -> Result { + CanonicalDeserialize::deserialize(&mut bytes.as_slice()).map(Self) + } +} + +impl ConditionalSelect for Group +where + C: ProjectiveCurve, +{ + #[inline] + fn select(bit: &Bool<()>, true_value: &Self, false_value: &Self, compiler: &mut ()) -> Self { + let _ = compiler; + if *bit { + *true_value + } else { + *false_value + } + } +} + +impl Zero for Group +where + C: ProjectiveCurve, +{ + type Verification = bool; + + #[inline] + fn zero(compiler: &mut ()) -> Self { + let _ = compiler; + Self(C::Affine::zero()) + } + + #[inline] + fn is_zero(&self, compiler: &mut ()) -> Self::Verification { + let _ = compiler; + C::Affine::is_zero(&self.0) + } +} + +/// Elliptic Curve Group Element Variable +#[derive(derivative::Derivative)] +#[derivative(Clone)] +pub struct GroupVar(pub CV, PhantomData) +where + C: ProjectiveCurve, + CV: CurveVar>; + +impl GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + /// Builds a new [`GroupVar`] from a given `point`. + #[inline] + fn new(point: CV) -> Self { + Self(point, PhantomData) + } +} + +impl algebra::Group> for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + #[inline] + fn add(&self, rhs: &Self, compiler: &mut Compiler) -> Self { + let _ = compiler; + let mut result = self.0.clone(); + result += &rhs.0; + Self::new(result) + } + + #[inline] + fn double_assign(&mut self, compiler: &mut Compiler) -> &mut Self { + let _ = compiler; + self.0 + .double_in_place() + .expect("Doubling is not allowed to fail."); + self + } +} + +impl algebra::ScalarMul, Compiler> for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + type Output = Self; + + #[inline] + fn scalar_mul(&self, scalar: &ScalarVar, compiler: &mut Compiler) -> Self { + let _ = compiler; + Self::new( + self.0 + .scalar_mul_le( + scalar + .as_ref() + .to_bits_le() + .expect("Bit decomposition is not allowed to fail.") + .iter(), + ) + .expect("Scalar multiplication is not allowed to fail."), + ) + } +} + +/// Discrete Logarithm Hardness +/// +/// We assume that the DL problem is hard for all `arkworks` implementations of elliptic curves. +impl algebra::security::DiscreteLogarithmHardness for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ +} + +/// Computational Diffie-Hellman Hardness +/// +/// We assume that the CDH problem is hard for all `arkworks` implementations of elliptic curves. +impl algebra::security::ComputationalDiffieHellmanHardness for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ +} + +impl cmp::PartialEq> for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + #[inline] + fn eq(&self, rhs: &Self, compiler: &mut Compiler) -> Boolean> { + let _ = compiler; + self.0 + .is_eq(&rhs.0) + .expect("Equality checking is not allowed to fail.") + } +} + +impl ConditionalSelect> for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + #[inline] + fn select( + bit: &Bool>, + true_value: &Self, + false_value: &Self, + compiler: &mut Compiler, + ) -> Self { + let _ = compiler; + Self::new(conditionally_select(bit, &true_value.0, &false_value.0)) + } +} + +impl Zero> for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + type Verification = Bool>; + + #[inline] + fn zero(compiler: &mut Compiler) -> Self { + let _ = compiler; + Self::new(CV::zero()) + } + + #[inline] + fn is_zero(&self, compiler: &mut Compiler) -> Self::Verification { + let _ = compiler; + self.0 + .is_zero() + .expect("Comparison with zero is not allowed to fail.") + } +} + +impl Constant> for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + type Type = Group; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new( + CV::new_constant( + ns!(compiler.as_ref(), "embedded curve point constant"), + this.0, + ) + .expect("Variable allocation is not allowed to fail."), + ) + } +} + +impl Variable> for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + type Type = Group; + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new( + CV::new_input( + ns!(compiler.as_ref(), "embedded curve point public input"), + full(this.0), + ) + .expect("Variable allocation is not allowed to fail."), + ) + } + + #[inline] + fn new_unknown(compiler: &mut Compiler) -> Self { + Self::new( + CV::new_input( + ns!(compiler.as_ref(), "embedded curve point public input"), + empty::, + ) + .expect("Variable allocation is not allowed to fail."), + ) + } +} + +impl Variable> for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + type Type = Group; + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new( + CV::new_witness( + ns!(compiler.as_ref(), "embedded curve point secret witness"), + full(this.0), + ) + .expect("Variable allocation is not allowed to fail."), + ) + } + + #[inline] + fn new_unknown(compiler: &mut Compiler) -> Self { + Self::new( + CV::new_witness( + ns!(compiler.as_ref(), "embedded curve point secret witness"), + empty::, + ) + .expect("Variable allocation is not allowed to fail."), + ) + } +} + +impl FixedBaseScalarMul, Compiler> for GroupVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + type Base = Group; + + #[inline] + fn fixed_base_scalar_mul( + precomputed_bases: I, + scalar: &ScalarVar, + compiler: &mut Compiler, + ) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + let _ = compiler; + let mut result = CV::zero(); + let scalar_bits = scalar + .as_ref() + .to_bits_le() + .expect("Bit decomposition is not allowed to fail."); + for (bit, base) in scalar_bits.into_iter().zip(precomputed_bases.into_iter()) { + result = bit + .select(&(result.clone() + base.borrow().0.into()), &result) + .expect("Conditional select is not allowed to fail. "); + } + Self::new(result) + } +} + +/// Testing Suite +#[cfg(test)] +mod test { + use super::*; + use crate::{ + algebra::{test::window_correctness, PrecomputedBaseTable, ScalarMul}, + arkworks::{ + algebra::scalar_bits, ed_on_bn254::EdwardsProjective as Bn254_Edwards, + r1cs_std::groups::curves::twisted_edwards::AffineVar, + }, + constraint::measure::Measure, + eclair::bool::AssertEq, + rand::OsRng, + }; + + /// Checks if the fixed base multiplcation is correct. + #[test] + fn fixed_base_mul_is_correct() { + let mut cs = Compiler::::for_proofs(); + let scalar = Scalar::::gen(&mut OsRng); + let base = Group::::gen(&mut OsRng); + const SCALAR_BITS: usize = scalar_bits::(); + let precomputed_table = PrecomputedBaseTable::<_, SCALAR_BITS>::from_base(base, &mut ()); + let base_var = base.as_known::>>(&mut cs); + let scalar_var = + scalar.as_known::>>(&mut cs); + let ctr1 = cs.constraint_count(); + let expected = base_var.scalar_mul(&scalar_var, &mut cs); + let ctr2 = cs.constraint_count(); + let actual = GroupVar::fixed_base_scalar_mul(precomputed_table, &scalar_var, &mut cs); + let ctr3 = cs.constraint_count(); + cs.assert_eq(&expected, &actual); + assert!(cs.is_satisfied()); + println!("variable base mul constraint: {:?}", ctr2 - ctr1); + println!("fixed base mul constraint: {:?}", ctr3 - ctr2); + } + + /// Checks if the windowed multiplication is correct in the native compiler. + #[test] + fn windowed_mul_is_correct() { + window_correctness( + 4, + &Scalar::::gen(&mut OsRng), + Group::::gen(&mut OsRng), + |scalar, _| scalar.0.into_repr().to_bits_be(), + &mut (), + ); + } +} diff --git a/manta-crypto/src/arkworks/constraint/mod.rs b/manta-crypto/src/arkworks/constraint/mod.rs index 0b643eecf..0c2bdcb64 100644 --- a/manta-crypto/src/arkworks/constraint/mod.rs +++ b/manta-crypto/src/arkworks/constraint/mod.rs @@ -18,10 +18,12 @@ use crate::{ arkworks::{ + algebra::modulus_is_smaller, constraint::fp::Fp, - ff::{FpParameters, PrimeField}, + ff::{BigInteger, FpParameters, PrimeField}, r1cs_std::{ - alloc::AllocVar, eq::EqGadget, fields::FieldVar, select::CondSelectGadget, ToBitsGadget, + alloc::AllocVar, eq::EqGadget, fields::FieldVar, select::CondSelectGadget, R1CSVar, + ToBitsGadget, }, relations::{ ns, @@ -40,10 +42,12 @@ use crate::{ }, bool::{Assert, ConditionalSelect, ConditionalSwap}, num::{AssertWithinBitRange, Zero}, - ops::{Add, BitAnd, BitOr}, + ops::{Add, BitAnd, BitOr, Rem}, Has, NonNative, }, }; +use core::marker::PhantomData; +use num_integer::Integer; pub use crate::arkworks::{ r1cs_std::{bits::boolean::Boolean, fields::fp::FpVar}, @@ -459,6 +463,93 @@ where } } +/// Prime Modulus +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub struct PrimeModulus(PhantomData) +where + F: PrimeField; + +impl Rem, R1CS> for FpVar +where + F: PrimeField, + R: PrimeField, +{ + type Output = FpVar; + + #[inline] + fn rem(self, rhs: PrimeModulus, compiler: &mut R1CS) -> Self::Output { + let _ = (rhs, compiler); + assert!( + modulus_is_smaller::(), + "The modulus of the embedded scalar field is larger than that of the constraint field." + ); + let (quotient, remainder) = match self.value() { + Ok(value) => { + let (quotient, remainder) = div_rem_mod_prime::(value); + ( + FpVar::new_witness(self.cs(), full(quotient)) + .expect("Allocating a witness is not allowed to fail."), + FpVar::new_witness( + self.cs(), + full(F::from_le_bytes_mod_order(&remainder.to_bytes_le())), + ) + .expect("Allocating a witness is not allowed to fail."), + ) + } + _ => ( + FpVar::new_witness(self.cs(), empty::) + .expect("Allocating a witness is not allowed to fail."), + FpVar::new_witness(self.cs(), empty::) + .expect("Allocating a witness is not allowed to fail."), + ), + }; + let modulus = FpVar::Constant(F::from_le_bytes_mod_order( + &::MODULUS.to_bytes_le(), + )); + self.enforce_equal(&(quotient * &modulus + &remainder)) + .expect("This equality holds because of the Euclidean algorithm."); + remainder + .enforce_cmp(&modulus, core::cmp::Ordering::Less, false) + .expect("This inequality holds because of the Euclidean algorithm."); + remainder + } +} + +/// Divides `value` by the modulus of the [`PrimeField`] `R` and returns the quotient and +/// the remainder. +#[inline] +pub fn div_rem_mod_prime(value: F) -> (F, R::BigInt) +where + F: PrimeField, + R: PrimeField, +{ + let modulus = ::MODULUS; + let (quotient, remainder) = value.into_repr().into().div_rem(&modulus.into()); + ( + F::from_le_bytes_mod_order( + &F::BigInt::try_from(quotient) + .ok() + .expect("Unable to compute modular reduction.") + .to_bytes_le(), + ), + R::BigInt::try_from(remainder) + .ok() + .expect("Unable to compute modular reduction."), + ) +} + +/// Returns the remainder of `value` divided by the modulus of the [`PrimeField`] `R`. +#[inline] +pub fn rem_mod_prime(value: F) -> R +where + F: PrimeField, + R: PrimeField, +{ + R::from_repr(div_rem_mod_prime::(value).1) + .expect("This element is guaranteed to be within the modulus.") +} + /// Testing Suite #[cfg(test)] mod tests { @@ -484,8 +575,7 @@ mod tests { let satisfied = cs.is_satisfied(); assert_eq!( should_pass, satisfied, - "on value {:?}, expect satisfied = {}, but got {}", - value, should_pass, satisfied + "on value {value:?}, expect satisfied = {should_pass}, but got {satisfied}", ); } diff --git a/manta-crypto/src/encryption/mod.rs b/manta-crypto/src/encryption/mod.rs index 890b652a1..aa0161d74 100644 --- a/manta-crypto/src/encryption/mod.rs +++ b/manta-crypto/src/encryption/mod.rs @@ -25,7 +25,7 @@ use crate::{ eclair::{ self, alloc::{ - mode::{Derived, Public}, + mode::{Derived, Public, Secret}, Allocate, Allocator, Constant, Var, Variable, }, bool::{Assert, AssertEq, Bool}, @@ -236,8 +236,11 @@ where #[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct UnsafeNoEncrypt(PhantomData<(E, COM)>); -impl Constant for UnsafeNoEncrypt { - type Type = E; +impl Constant for UnsafeNoEncrypt +where + E: Constant, +{ + type Type = E::Type; #[inline] fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { @@ -803,6 +806,27 @@ where } } +impl Variable for EncryptedMessage +where + E: CiphertextType + HeaderType + Constant, + E::Header: Variable, + E::Ciphertext: Variable, + E::Type: CiphertextType> + + HeaderType

>, +{ + type Type = EncryptedMessage; + + #[inline] + fn new_unknown(compiler: &mut COM) -> Self { + Variable::, _>::new_unknown(compiler) + } + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut COM) -> Self { + Variable::, _>::new_known(this, compiler) + } +} + impl Sample<(H, C)> for EncryptedMessage where E: CiphertextType + HeaderType, diff --git a/manta-crypto/src/merkle_tree/fork.rs b/manta-crypto/src/merkle_tree/fork.rs index 9adc3d7e3..c4d5fc021 100644 --- a/manta-crypto/src/merkle_tree/fork.rs +++ b/manta-crypto/src/merkle_tree/fork.rs @@ -70,7 +70,7 @@ where /// Converts `self` back into its inner [`Tree`]. /// - /// # Safety + /// # Crypto Safety /// /// This method automatically detaches all of the forks associated to this trunk. To attach them /// to another trunk, use [`Fork::attach`]. @@ -104,7 +104,7 @@ where /// Tries to merge `fork` onto `self`, returning `fork` back if it could not be merged. /// - /// # Safety + /// # Crypto Safety /// /// If the merge succeeds, this method automatically detaches all of the forks associated to /// this trunk. To attach them to another trunk, use [`Fork::attach`]. To attach them to this diff --git a/manta-crypto/src/merkle_tree/path.rs b/manta-crypto/src/merkle_tree/path.rs index f59efc018..4c492e3b7 100644 --- a/manta-crypto/src/merkle_tree/path.rs +++ b/manta-crypto/src/merkle_tree/path.rs @@ -82,7 +82,7 @@ where { /// Builds a new [`InnerPath`] from `leaf_index` and `path`. /// - /// # Safety + /// # Crypto Safety /// /// In order for paths to compute the correct root, they should always have a `path` with /// length given by [`path_length`]. @@ -280,7 +280,7 @@ where { /// Builds a new [`CurrentInnerPath`] from `leaf_index` and `path`. /// - /// # Safety + /// # Crypto Safety /// /// In order for paths to compute the correct root, they should always have a `path` with /// length given by [`path_length`]. For [`CurrentInnerPath`], we also have the invariant @@ -657,7 +657,7 @@ where { /// Builds a new [`Path`] from `sibling_digest`, `leaf_index`, and `path`. /// - /// # Safety + /// # Crypto Safety /// /// See [`InnerPath::new`] for the invariants on `path` assumed by this method. #[inline] @@ -807,7 +807,7 @@ where { /// Builds a new [`CurrentPath`] from `sibling_digest`, `leaf_index`, and `path`. /// - /// # Safety + /// # Crypto Safety /// /// See [`CurrentInnerPath::new`] for the invariants on `path` assumed by this method. #[inline] diff --git a/manta-parameters/Cargo.toml b/manta-parameters/Cargo.toml index dd3861e76..1655ef7ef 100644 --- a/manta-parameters/Cargo.toml +++ b/manta-parameters/Cargo.toml @@ -28,23 +28,26 @@ maintenance = { status = "actively-developed" } # Download Data from GitHub download = ["anyhow", "attohttpc", "std"] +# Git Utilities +git = ["anyhow", "git2", "std"] + # Enable Standard Library std = ["anyhow?/std"] [dependencies] -anyhow = { version = "1.0.65", optional = true, default-features = false } +anyhow = { version = "1.0.66", optional = true, default-features = false } attohttpc = { version = "0.22.0", optional = true } blake3 = { version = "1.3.1", default-features = false } +git2 = { version = "0.15.0", optional = true, default-features = false } [dev-dependencies] -git2 = { version = "0.15.0", default-features = false } hex = { version = "0.4.3", default-features = false, features = ["std"] } -manta-parameters = { path = ".", default-features = false, features = ["download"] } +manta-parameters = { path = ".", default-features = false, features = ["download", "git"] } tempfile = { version = "3.3.0", default-features = false } walkdir = { version = "2.3.2", default-features = false } [build-dependencies] -anyhow = { version = "1.0.65", default-features = false, features = ["std"] } +anyhow = { version = "1.0.66", default-features = false, features = ["std"] } blake3 = { version = "1.3.1", default-features = false, features = ["std"] } gitignore = { version = "1.0.7", default-features = false } hex = { version = "0.4.3", default-features = false, features = ["std"] } diff --git a/manta-parameters/data.checkfile b/manta-parameters/data.checkfile index 87bc37826..480538043 100644 --- a/manta-parameters/data.checkfile +++ b/manta-parameters/data.checkfile @@ -1,11 +1,18 @@ -df87563f5a6366a341f4dd1796ecd7eec85354e0e8e0bb9e2a8cdb33be3a31e9 data/pay/testnet/parameters/note-encryption-scheme.dat -05e6b3b6a0d6d6debe458b6f7ddaefa51a4d38608024a98fc4dedf5ee8b68e99 data/pay/testnet/parameters/utxo-accumulator-model.dat -348d7e0072ffdb56fd19d6115c89a3c9172639c5167e8a7e905dc8a575e4f17d data/pay/testnet/parameters/utxo-commitment-scheme.dat -9dcf5d51f6ffbcda46eaaab64e7350095465f8cc774f8276b46889c46479a2e4 data/pay/testnet/parameters/void-number-commitment-scheme.dat -d0dbeec5afcd85025678bf64db3a540622be09e3208ace153446639a7d5de98e data/pay/testnet/proving/mint.lfs -04b294703a2588e6a6a4bd98734fb18fa5f8c10b33be63bb9a73f68c05ccaf2c data/pay/testnet/proving/private-transfer.lfs -eb5c880bdf3c998a9a081f0259fd536e07f4bb71095bcef5664326bcb1ad6428 data/pay/testnet/proving/reclaim.lfs -5719387f9625828f46835a8375656578e028d8fc6da6822b765f47e296c0aaac data/pay/testnet/verifying/mint.dat -29a8229b59490223372c1f2b918f10d806be64ac4ffa5695dbdfe97b4b52e404 data/pay/testnet/verifying/private-transfer.dat -bbe115020d563d63d404437c38741f0d527ab0441b4aaf4de463d9e9452dee09 data/pay/testnet/verifying/reclaim.dat -25d2368d77dc834774504ca9b001fd4b5926c24c51e87f8e208db5fe40040075 data/ppot/round72powers19.lfs \ No newline at end of file +af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262 data/pay/testnet/parameters/address-partition-function.dat +65a28898c6f1a27a05682cee72bca89885fb7c27b63153971a5c657dfb246a83 data/pay/testnet/parameters/group-generator.dat +677ddceec2b7758b127381550f2d88a1c82624f4669b656d0167250823a35f58 data/pay/testnet/parameters/incoming-base-encryption-scheme.dat +af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262 data/pay/testnet/parameters/light-incoming-base-encryption-scheme.dat +d0e90f772d44b0c4b1fb9ed9cd785d8d5451c5ac6ad77a9617c923e7a45a686b data/pay/testnet/parameters/nullifier-commitment-scheme.dat +af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262 data/pay/testnet/parameters/outgoing-base-encryption-scheme.dat +af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262 data/pay/testnet/parameters/schnorr-hash-function.dat +d9a787351d03f048494160a90c9895b629f34a5ef12129e937d23f4adef73b97 data/pay/testnet/parameters/utxo-accumulator-item-hash.dat +4211fceef3834f077b71618d9424001df63570542175c83d79a8a7010737554e data/pay/testnet/parameters/utxo-accumulator-model.dat +60e7b366b0e15324263942d29bc1649190b4882987a03fa2a9e4fd21ab6e777a data/pay/testnet/parameters/utxo-commitment-scheme.dat +4211fceef3834f077b71618d9424001df63570542175c83d79a8a7010737554e data/pay/testnet/parameters/viewing-key-derivation-function.dat +7e25d965b4255d8192fdfd6f2c1719e281f308eb7721f6b8cb1b60a169acd9a6 data/pay/testnet/proving/private-transfer.lfs +6302dcbfcd2c40c034bbd35dcf6fe3344e83c572ed9ef673b2154fa6c06c4924 data/pay/testnet/proving/to-private.lfs +438b1971a521ea950cb0fa2cc10c94e54aad88c798736db9d1daf7abad7641f5 data/pay/testnet/proving/to-public.lfs +6ab2557f70f5583779f7cbcd27e73e66f8fcb5704ab21792a4126e89cc15b793 data/pay/testnet/verifying/private-transfer.dat +5e2e618e067c9414fed3ca5b570f8f2b33c9a0a42b925dc723f6fdfdd7436c5c data/pay/testnet/verifying/to-private.dat +d1467307aa8b51b26fb0247eede05cdb3a8d94a3db2a5939f5e181da6024a9a5 data/pay/testnet/verifying/to-public.dat +25d2368d77dc834774504ca9b001fd4b5926c24c51e87f8e208db5fe40040075 data/ppot/round72powers19.lfs diff --git a/manta-parameters/data/pay/testnet/parameters/address-partition-function.dat b/manta-parameters/data/pay/testnet/parameters/address-partition-function.dat new file mode 100644 index 000000000..e69de29bb diff --git a/manta-parameters/data/pay/testnet/parameters/group-generator.dat b/manta-parameters/data/pay/testnet/parameters/group-generator.dat new file mode 100644 index 000000000..363be5410 --- /dev/null +++ b/manta-parameters/data/pay/testnet/parameters/group-generator.dat @@ -0,0 +1,2 @@ +ÍŸìÂö¥ÿ'ÉøëíAÉ|@‘· +ÖŽX"‘þ÷»®²¤ \ No newline at end of file diff --git a/manta-parameters/data/pay/testnet/parameters/incoming-base-encryption-scheme.dat b/manta-parameters/data/pay/testnet/parameters/incoming-base-encryption-scheme.dat new file mode 100644 index 000000000..b2501e1fe Binary files /dev/null and b/manta-parameters/data/pay/testnet/parameters/incoming-base-encryption-scheme.dat differ diff --git a/manta-parameters/data/pay/testnet/parameters/light-incoming-base-encryption-scheme.dat b/manta-parameters/data/pay/testnet/parameters/light-incoming-base-encryption-scheme.dat new file mode 100644 index 000000000..e69de29bb diff --git a/manta-parameters/data/pay/testnet/parameters/note-encryption-scheme.dat b/manta-parameters/data/pay/testnet/parameters/note-encryption-scheme.dat deleted file mode 100644 index ada63bb05..000000000 --- a/manta-parameters/data/pay/testnet/parameters/note-encryption-scheme.dat +++ /dev/null @@ -1 +0,0 @@ -.Bàöé04ˆ§û¸1£tLo¬'5'–†ƒ°ÈQ for CurrentBranchError { + #[inline] + fn from(err: Error) -> Self { + Self::Git(err) + } + } + + impl fmt::Display for CurrentBranchError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::NotBranch => write!(f, "CurrentBranchError: Not a Branch"), + Self::MissingShorthand => write!(f, "Current Branch Error: Missing Shorthand"), + Self::Git(err) => write!(f, "Current Branch Error: Git Error: {err}"), + } + } + } + + impl error::Error for CurrentBranchError {} + + /// Returns the name of the current branch of this crate as a Git repository. + #[inline] + pub fn current_branch() -> Result { + let repo = Repository::discover(".")?; + let head = repo.head()?; + if head.is_branch() { + Ok(head + .shorthand() + .ok_or(CurrentBranchError::MissingShorthand)? + .to_owned()) + } else { + Err(CurrentBranchError::NotBranch) + } + } +} /// GitHub Data File Downloading #[cfg(feature = "download")] @@ -50,6 +113,7 @@ pub mod github { pub const DEFAULT_BRANCH: &str = "main"; /// Returns the Git-LFS URL for GitHub content at the given `branch` and `data_path`. + #[inline] pub fn lfs_url(branch: &str, data_path: &str) -> String { std::format!( "https://media.githubusercontent.com/media/{ORGANIZATION}/{REPO}/{branch}/{CRATE}/{data_path}" @@ -73,7 +137,7 @@ pub mod github { /// Downloads data from `data_path` relative to the given `branch` to a file at `path` without /// checking any checksums. /// - /// # Safety + /// # Crypto Safety /// /// Prefer the [`download`] method which checks the data against a given checksum. #[inline] @@ -116,11 +180,58 @@ pub fn verify(data: &[u8], checksum: &[u8; 32]) -> bool { #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] #[inline] -pub fn verify_file

(path: P, checksum: &[u8; 32]) -> std::io::Result +pub fn verify_file

(path: P, checksum: &[u8; 32]) -> io::Result where - P: AsRef, + P: AsRef, { - Ok(verify(&std::fs::read(path)?, checksum)) + Ok(verify(&fs::read(path)?, checksum)) +} + +/// Fixed Checksum +pub trait HasChecksum { + /// Data Checksum for the Type + const CHECKSUM: &'static [u8; 32]; + + /// Verifies that `data` is compatible with [`CHECKSUM`](Self::CHECKSUM). + #[inline] + fn verify_data(data: &[u8]) -> bool { + verify(data, Self::CHECKSUM) + } + + /// Verifies that the data in the file located at `path` is compatible with + /// [`CHECKSUM`](Self::CHECKSUM). + #[cfg(feature = "std")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] + #[inline] + fn verify_file

(path: P) -> io::Result + where + P: AsRef, + { + verify_file(path, Self::CHECKSUM) + } +} + +/// Local Data +pub trait Get: HasChecksum { + /// Binary Data Payload + const DATA: &'static [u8]; + + /// Verifies that [`DATA`](Self::DATA) is compatible with [`CHECKSUM`](HasChecksum::CHECKSUM). + #[inline] + fn verify() -> bool { + Self::verify_data(Self::DATA) + } + + /// Reads [`DATA`](Self::DATA), making sure that the [`CHECKSUM`](HasChecksum::CHECKSUM) is + /// compatible with [`verify`](Self::verify). + #[inline] + fn get() -> Option<&'static [u8]> { + if Self::verify() { + Some(Self::DATA) + } else { + None + } + } } /// Defines a data marker type loading its raw data and checksum from disk. @@ -130,33 +241,42 @@ macro_rules! define_dat { #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct $name; - impl $name { - #[doc = $doc] - #[doc = "Data Bytes"] - pub const DATA: &'static [u8] = include_bytes!(concat!(env!("OUT_DIR"), $path, ".dat")); + impl $crate::HasChecksum for $name { + const CHECKSUM: &'static [u8; 32] = + include_bytes!(concat!(env!("OUT_DIR"), "/data/", $path, ".checksum")); + } - #[doc = $doc] - #[doc = "Data Checksum"] - pub const CHECKSUM: &'static [u8; 32] = - include_bytes!(concat!(env!("OUT_DIR"), $path, ".checksum")); + impl $crate::Get for $name { + const DATA: &'static [u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/data/", $path, ".dat")); + } + }; +} - /// Verifies that [`Self::DATA`] is consistent against [`Self::CHECKSUM`]. - #[inline] - pub fn verify() -> bool { - crate::verify(Self::DATA, Self::CHECKSUM) - } +/// Nonlocal Download-able Data +#[cfg(feature = "download")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "download")))] +pub trait Download: HasChecksum { + /// Downloads the data for this type from GitHub. This method automatically verifies the + /// checksum while downloading. See [`github::download`] for more. + fn download

(path: P) -> Result<()> + where + P: AsRef; - /// Gets the underlying binary data after verifying against [`Self::CHECKSUM`]. - #[inline] - pub fn get() -> Option<&'static [u8]> { - if Self::verify() { - Some(Self::DATA) - } else { - None - } - } + /// Checks if the data for this type at the given `path` matches the [`CHECKSUM`] and if not, + /// then it downloads it from GitHub. This method automatically verifies the checksum while + /// downloading. See [`github::download`] for more. + /// + /// [`CHECKSUM`]: HasChecksum::CHECKSUM + fn download_if_invalid

(path: P) -> Result<()> + where + P: AsRef, + { + match verify_file(&path, Self::CHECKSUM) { + Ok(true) => Ok(()), + _ => Self::download(path), } - }; + } } /// Defines a data marker type for download-required data from GitHub LFS and checksum from disk. @@ -166,61 +286,30 @@ macro_rules! define_lfs { #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct $name; - impl $name { - #[doc = $doc] - #[doc = "Data Checksum"] - pub const CHECKSUM: &'static [u8; 32] = - include_bytes!(concat!(env!("OUT_DIR"), $path, ".checksum")); - - #[doc = "Downloads the data for the"] - #[doc = $doc] - #[doc = r"from GitHub. This method automatically verifies the checksum when downloading. - See [`github::download`](crate::github::download) for more."] - #[cfg(feature = "download")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "download")))] + impl $crate::HasChecksum for $name { + const CHECKSUM: &'static [u8; 32] = + include_bytes!(concat!(env!("OUT_DIR"), "/data/", $path, ".checksum")); + } + + #[cfg(feature = "download")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "download")))] + impl $crate::Download for $name { #[inline] - pub fn download

(path: P) -> anyhow::Result<()> + fn download

(path: P) -> $crate::Result<()> where - P: AsRef, + P: AsRef<$crate::Path>, { $crate::github::download( $crate::github::DEFAULT_BRANCH, - concat!($path, ".lfs"), + concat!("/data/", $path, ".lfs"), path, - Self::CHECKSUM, + ::CHECKSUM, ) } - - #[doc = "Checks if the data for the"] - #[doc = $doc] - #[doc = r"matches the checksum and if not downloads it from GitHub. This method - automatically verifies the checksum when downloading. - See [`github::download`](crate::github::download) for more."] - #[cfg(feature = "download")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "download")))] - #[inline] - pub fn download_if_invalid

(path: P) -> anyhow::Result<()> - where - P: AsRef, - { - match $crate::verify_file(&path, Self::CHECKSUM) { - Ok(true) => Ok(()), - _ => Self::download(path), - } - } } }; } -/// Perpetual Powers of Tau Accumulators -pub mod ppot { - define_lfs!( - Round72Powers19, - "Accumulator with 1 << 19 powers, Bn", - "/data/ppot/round72powers19", - ); -} - /// Concrete Parameters for Manta Pay pub mod pay { /// Testnet Data @@ -228,62 +317,97 @@ pub mod pay { /// Parameters pub mod parameters { define_dat!( - NoteEncryptionScheme, - "Note Encryption Scheme Parameters", - "/data/pay/testnet/parameters/note-encryption-scheme", + GroupGenerator, + "Group Generator", + "pay/testnet/parameters/group-generator", ); define_dat!( UtxoCommitmentScheme, "UTXO Commitment Scheme Parameters", - "/data/pay/testnet/parameters/utxo-commitment-scheme", + "pay/testnet/parameters/utxo-commitment-scheme", + ); + define_dat!( + IncomingBaseEncryptionScheme, + "Incoming Base Encryption Scheme Parameters", + "pay/testnet/parameters/incoming-base-encryption-scheme", + ); + define_dat!( + LightIncomingBaseEncryptionScheme, + "Light Incoming Base Encryption Scheme Parameters", + "pay/testnet/parameters/light-incoming-base-encryption-scheme", + ); + define_dat!( + ViewingKeyDerivationFunction, + "Viewing Key Derivation Function Parameters", + "pay/testnet/parameters/viewing-key-derivation-function", + ); + define_dat!( + UtxoAccumulatorItemHash, + "UTXO Accumulator Item Hash Parameters", + "pay/testnet/parameters/utxo-accumulator-item-hash", ); define_dat!( - VoidNumberCommitmentScheme, - "Void Number Commitment Scheme Parameters", - "/data/pay/testnet/parameters/void-number-commitment-scheme", + NullifierCommitmentScheme, + "Nullifier Commitment Scheme Parameters", + "pay/testnet/parameters/nullifier-commitment-scheme", + ); + define_dat!( + OutgoingBaseEncryptionScheme, + "Outgoing Base Encryption Scheme Parameters", + "pay/testnet/parameters/outgoing-base-encryption-scheme", + ); + define_dat!( + AddressPartitionFunction, + "Address Partition Function", + "pay/testnet/parameters/address-partition-function", + ); + define_dat!( + SchnorrHashFunction, + "Schnorr Hash Function Parameters", + "pay/testnet/parameters/schnorr-hash-function", ); define_dat!( UtxoAccumulatorModel, - "UTXO Accumulator Model", - "/data/pay/testnet/parameters/utxo-accumulator-model", + "UTXO Accumulator Model Parameters", + "pay/testnet/parameters/utxo-accumulator-model", ); } /// Zero-Knowledge Proof System Proving Data pub mod proving { define_lfs!( - Mint, - "Mint Proving Context", - "/data/pay/testnet/proving/mint", + ToPrivate, + "ToPrivate Proving Context", + "pay/testnet/proving/to-private", ); define_lfs!( PrivateTransfer, "Private Transfer Proving Context", - "/data/pay/testnet/proving/private-transfer", + "pay/testnet/proving/private-transfer", ); define_lfs!( - Reclaim, - "Reclaim Proving Context", - "/data/pay/testnet/proving/reclaim", + ToPublic, + "ToPublic Proving Context", + "pay/testnet/proving/to-public", ); } /// Zero-Knowledge Proof System Verifying Data pub mod verifying { define_dat!( - Mint, - "Mint Verifying Context", - "/data/pay/testnet/verifying/mint" + ToPrivate, + "ToPrivate Verifying Context", + "pay/testnet/verifying/to-private" ); define_dat!( PrivateTransfer, "Private Transfer Verifying Context", - "/data/pay/testnet/verifying/private-transfer" + "pay/testnet/verifying/private-transfer" ); define_dat!( - Reclaim, - "Reclaim Verifying Context", - "/data/pay/testnet/verifying/reclaim" + ToPublic, + "ToPublic Verifying Context", + "pay/testnet/verifying/to-public" ); } } @@ -293,17 +417,14 @@ pub mod pay { #[cfg(test)] mod test { use super::*; - use anyhow::{anyhow, bail, Result}; - use git2::Repository; + use anyhow::{anyhow, bail}; use hex::FromHex; use std::{ - borrow::ToOwned, collections::HashMap, - fs::{self, File, OpenOptions}, + fs::{File, OpenOptions}, io::{BufRead, BufReader, Read}, path::PathBuf, println, - string::String, }; /// Checks if two files `lhs` and `rhs` have equal content. @@ -353,38 +474,22 @@ mod test { /// Gets the checksum from the `checksums` map for `path` returning an error if it was not found. #[inline] - fn get_checksum

(checksums: &ChecksumMap, path: P) -> Result + fn get_checksum

(checksums: &ChecksumMap, path: P) -> Result<&Checksum> where P: AsRef, { let path = path.as_ref(); checksums .get(path) - .ok_or_else(|| anyhow!("Unable to get checksum for path: {:?}", path)) - .map(move |c| *c) - } - - /// Returns the name of the current branch of this crate as a Git repository. - #[inline] - fn get_current_branch() -> Result { - let repo = Repository::discover(".")?; - let head = repo.head()?; - if head.is_branch() { - Ok(head - .shorthand() - .ok_or_else(|| anyhow!("Unable to generate shorthand for branch name."))? - .to_owned()) - } else { - bail!("Current Git HEAD reference is not at a branch.") - } + .ok_or_else(|| anyhow!("Unable to get checksum for path: {path:?}")) } /// Downloads all data from GitHub and checks if they are the same as the data known locally to /// this Rust crate. - #[ignore] // NOTE: Adds `ignore` such that CI does NOT run this test while still allowing developers to test. + #[ignore] // NOTE: We use this so that CI doesn't run this test while still allowing developers to test. #[test] fn download_all_data() -> Result<()> { - let current_branch = get_current_branch()?; + let current_branch = super::git::current_branch()?; let directory = tempfile::tempdir()?; println!("[INFO] Temporary Directory: {directory:?}"); let checksums = parse_checkfile("data.checkfile")?; @@ -400,12 +505,12 @@ mod test { ¤t_branch, path.to_str().unwrap(), &target, - &get_checksum(&checksums, path)?, + get_checksum(&checksums, path)?, )?; - assert!(equal_files( - &mut File::open(path)?, - &mut File::open(target)? - )?); + assert!( + equal_files(&mut File::open(path)?, &mut File::open(&target)?)?, + "The files at {path:?} and {target:?} are not equal.", + ); } } Ok(()) diff --git a/manta-pay/Cargo.toml b/manta-pay/Cargo.toml index 635fe58a1..c1712a381 100644 --- a/manta-pay/Cargo.toml +++ b/manta-pay/Cargo.toml @@ -26,11 +26,7 @@ maintenance = { status = "actively-developed" } [[bin]] name = "generate_parameters" -required-features = ["groth16", "manta-util/std", "test"] - -[[bin]] -name = "measure" -required-features = ["groth16", "manta-crypto/getrandom", "test"] +required-features = ["groth16", "manta-util/std", "parameters", "serde"] [[bin]] name = "simulation" @@ -40,25 +36,37 @@ required-features = ["clap", "groth16", "simulation"] # Enable Arkworks Backend arkworks = [ "manta-crypto/ark-bls12-381", + "manta-crypto/ark-bn254", "manta-crypto/ark-ed-on-bls12-381", + "manta-crypto/ark-ed-on-bn254", "manta-crypto/arkworks", + "num-bigint", ] # Enable Download Parameters download = ["manta-parameters/download", "std"] +# Enable Groth16 ZKP System +groth16 = ["manta-crypto/ark-groth16", "arkworks"] + +# Enable HTTP Signer Client +http = ["manta-util/reqwest", "serde", "network"] + # Key Features key = ["bip32", "bip0039"] -# Enable Groth16 ZKP System -groth16 = [ - "arkworks", - "manta-crypto/ark-groth16", -] +# Enable Multiple Network Support +network = [] + +# Parameter Loading +parameters = ["groth16", "manta-crypto/test", "manta-parameters"] -# SCALE Codec and Type Info Support +# SCALE Codec and Type Info scale = ["scale-codec", "scale-info"] +# SCALE Codec and Type Info with the Standard Library Enabled +scale-std = ["scale", "scale-codec/std", "scale-info/std", "std"] + # Serde serde = ["manta-accounting/serde", "manta-crypto/serde"] @@ -78,10 +86,14 @@ simulation = [ ] # Standard Library -std = ["manta-accounting/std", "manta-util/std"] +std = [ + "manta-accounting/std", + "manta-crypto/std", + "manta-util/std", +] # Testing Frameworks -test = ["manta-accounting/test", "manta-crypto/test", "manta-parameters", "tempfile"] +test = ["manta-accounting/test", "manta-crypto/test", "manta-parameters/download", "tempfile"] # Wallet wallet = ["key", "manta-crypto/getrandom", "std"] @@ -97,9 +109,6 @@ websocket = [ "ws_stream_wasm", ] -# Enable Multiple Network Support -network = [] - [dependencies] aes-gcm = { version = "0.9.4", default-features = false, features = ["aes", "alloc"] } bip0039 = { version = "0.10.1", optional = true, default-features = false } @@ -114,15 +123,16 @@ manta-accounting = { path = "../manta-accounting", default-features = false } manta-crypto = { path = "../manta-crypto", default-features = false, features = ["rand_chacha"] } manta-parameters = { path = "../manta-parameters", optional = true, default-features = false } manta-util = { path = "../manta-util", default-features = false } +num-bigint = { version = "0.4.3", optional = true, default-features = false } parking_lot = { version = "0.12.1", optional = true, default-features = false } scale-codec = { package = "parity-scale-codec", version = "3.1.2", optional = true, default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.1.2", optional = true, default-features = false, features = ["derive"] } serde_json = { version = "1.0.85", optional = true, default-features = false, features = ["alloc"] } tempfile = { version = "3.3.0", optional = true, default-features = false } -tokio = { version = "1.21.1", optional = true, default-features = false } +tokio = { version = "1.21.2", optional = true, default-features = false } tokio-tungstenite = { version = "0.17.2", optional = true, default-features = false, features = ["native-tls"] } ws_stream_wasm = { version = "0.7.3", optional = true, default-features = false } [dev-dependencies] manta-crypto = { path = "../manta-crypto", default-features = false, features = ["getrandom"] } -manta-pay = { path = ".", default-features = false, features = ["download", "groth16", "test"] } +manta-pay = { path = ".", default-features = false, features = ["download", "parameters", "groth16", "scale", "scale-std", "std", "test", "wallet"] } diff --git a/manta-pay/src/bin/generate_parameters.rs b/manta-pay/src/bin/generate_parameters.rs index 8dbf7ef38..af0a136af 100644 --- a/manta-pay/src/bin/generate_parameters.rs +++ b/manta-pay/src/bin/generate_parameters.rs @@ -19,7 +19,10 @@ // TODO: Deduplicate the per-circuit proving context and verifying context serialization code. // TODO: Print some statistics about the parameters and circuits and into a stats file as well. -use manta_pay::{config::Parameters, parameters}; +use manta_pay::{ + config::{utxo::protocol::BaseParameters, Parameters}, + parameters, +}; use manta_util::codec::{Encode, IoWriter}; use std::{ env, @@ -38,32 +41,42 @@ pub fn main() -> io::Result<()> { .unwrap_or(env::current_dir()?); assert!( target_dir.is_dir() || !target_dir.exists(), - "Specify a directory to place the generated files: {:?}.", - target_dir, + "Specify a directory to place the generated files: {target_dir:?}.", ); fs::create_dir_all(&target_dir)?; let (proving_context, verifying_context, parameters, utxo_accumulator_model) = - parameters::generate().unwrap(); + parameters::generate().expect("Unable to generate parameters."); let Parameters { - note_encryption_scheme, - utxo_commitment, - void_number_commitment, + base: + BaseParameters { + group_generator, + utxo_commitment_scheme, + incoming_base_encryption_scheme, + light_incoming_base_encryption_scheme, + viewing_key_derivation_function, + utxo_accumulator_item_hash, + nullifier_commitment_scheme, + outgoing_base_encryption_scheme, + }, + address_partition_function, + schnorr_hash_function, } = ¶meters; let parameters_dir = target_dir.join("parameters"); fs::create_dir_all(¶meters_dir)?; - note_encryption_scheme + group_generator .encode(IoWriter( OpenOptions::new() .create(true) .write(true) - .open(parameters_dir.join("note-encryption-scheme.dat"))?, + .open(parameters_dir.join("group-generator.dat"))?, )) .unwrap(); - utxo_commitment + + utxo_commitment_scheme .encode(IoWriter( OpenOptions::new() .create(true) @@ -71,11 +84,66 @@ pub fn main() -> io::Result<()> { .open(parameters_dir.join("utxo-commitment-scheme.dat"))?, )) .unwrap(); - void_number_commitment + + incoming_base_encryption_scheme + .encode(IoWriter(OpenOptions::new().create(true).write(true).open( + parameters_dir.join("incoming-base-encryption-scheme.dat"), + )?)) + .unwrap(); + light_incoming_base_encryption_scheme .encode(IoWriter(OpenOptions::new().create(true).write(true).open( - parameters_dir.join("void-number-commitment-scheme.dat"), + parameters_dir.join("light-incoming-base-encryption-scheme.dat"), )?)) .unwrap(); + + viewing_key_derivation_function + .encode(IoWriter(OpenOptions::new().create(true).write(true).open( + parameters_dir.join("viewing-key-derivation-function.dat"), + )?)) + .unwrap(); + + utxo_accumulator_item_hash + .encode(IoWriter( + OpenOptions::new() + .create(true) + .write(true) + .open(parameters_dir.join("utxo-accumulator-item-hash.dat"))?, + )) + .unwrap(); + + nullifier_commitment_scheme + .encode(IoWriter( + OpenOptions::new() + .create(true) + .write(true) + .open(parameters_dir.join("nullifier-commitment-scheme.dat"))?, + )) + .unwrap(); + + outgoing_base_encryption_scheme + .encode(IoWriter(OpenOptions::new().create(true).write(true).open( + parameters_dir.join("outgoing-base-encryption-scheme.dat"), + )?)) + .unwrap(); + + address_partition_function + .encode(IoWriter( + OpenOptions::new() + .create(true) + .write(true) + .open(parameters_dir.join("address-partition-function.dat"))?, + )) + .unwrap(); + + schnorr_hash_function + .encode(IoWriter( + OpenOptions::new() + .create(true) + .write(true) + .open(parameters_dir.join("schnorr-hash-function.dat"))?, + )) + .unwrap(); + utxo_accumulator_model .encode(IoWriter( OpenOptions::new() @@ -92,21 +160,21 @@ pub fn main() -> io::Result<()> { fs::create_dir_all(&verifying_context_dir)?; proving_context - .mint + .to_private .encode(IoWriter( OpenOptions::new() .create(true) .write(true) - .open(proving_context_dir.join("mint.lfs"))?, + .open(proving_context_dir.join("to-private.lfs"))?, )) .unwrap(); verifying_context - .mint + .to_private .encode(IoWriter( OpenOptions::new() .create(true) .write(true) - .open(verifying_context_dir.join("mint.dat"))?, + .open(verifying_context_dir.join("to-private.dat"))?, )) .unwrap(); @@ -130,21 +198,21 @@ pub fn main() -> io::Result<()> { .unwrap(); proving_context - .reclaim + .to_public .encode(IoWriter( OpenOptions::new() .create(true) .write(true) - .open(proving_context_dir.join("reclaim.lfs"))?, + .open(proving_context_dir.join("to-public.lfs"))?, )) .unwrap(); verifying_context - .reclaim + .to_public .encode(IoWriter( OpenOptions::new() .create(true) .write(true) - .open(verifying_context_dir.join("reclaim.dat"))?, + .open(verifying_context_dir.join("to-public.dat"))?, )) .unwrap(); diff --git a/manta-pay/src/bin/measure.rs b/manta-pay/src/bin/measure.rs deleted file mode 100644 index cfd2bb5b4..000000000 --- a/manta-pay/src/bin/measure.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2019-2022 Manta Network. -// This file is part of manta-rs. -// -// manta-rs is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// manta-rs is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with manta-rs. If not, see . - -//! Manta Pay Circuit Measurements - -use manta_crypto::{ - constraint::measure::Instrument, - eclair::alloc::{mode::Secret, Allocate, Allocator}, - hash::ArrayHashFunction, - key::agreement::{Agree, Derive}, - rand::{ChaCha20Rng, Sample, SeedableRng}, -}; -use manta_pay::config::{ - Compiler, KeyAgreementScheme, KeyAgreementSchemeVar, Poseidon2, Poseidon2Var, Poseidon4, - Poseidon4Var, -}; - -/// Runs some basic measurements of the circuit component sizes. -#[inline] -pub fn main() { - let mut rng = ChaCha20Rng::from_entropy(); - let mut compiler = Compiler::for_contexts(); - - let mut instrument = Instrument::new(&mut compiler); - - let hasher = Poseidon2::gen(&mut rng).as_constant::(&mut instrument); - let poseidon_lhs = instrument.base.allocate_unknown::(); - let poseidon_rhs = instrument.base.allocate_unknown::(); - - let _ = instrument.measure("Poseidon ARITY-2", |compiler| { - hasher.hash([&poseidon_lhs, &poseidon_rhs], compiler) - }); - - let hasher = Poseidon4::gen(&mut rng).as_constant::(&mut instrument); - let poseidon_0 = instrument.base.allocate_unknown::(); - let poseidon_1 = instrument.base.allocate_unknown::(); - let poseidon_2 = instrument.base.allocate_unknown::(); - let poseidon_3 = instrument.base.allocate_unknown::(); - - let _ = instrument.measure("Poseidon ARITY-4", |compiler| { - hasher.hash( - [&poseidon_0, &poseidon_1, &poseidon_2, &poseidon_3], - compiler, - ) - }); - - let key_agreement = - KeyAgreementScheme::gen(&mut rng).as_constant::(&mut instrument); - let secret_key_0 = instrument.base.allocate_unknown::(); - let secret_key_1 = instrument.base.allocate_unknown::(); - - let public_key_0 = instrument.measure("DHKE `derive`", |compiler| { - key_agreement.derive(&secret_key_0, compiler) - }); - - let _ = instrument.measure("DHKE `agree`", |compiler| { - key_agreement.agree(&public_key_0, &secret_key_1, compiler) - }); - - println!("{:#?}", instrument.measurements); -} diff --git a/manta-pay/src/bin/simulation.rs b/manta-pay/src/bin/simulation.rs index 87429ac02..e2242f524 100644 --- a/manta-pay/src/bin/simulation.rs +++ b/manta-pay/src/bin/simulation.rs @@ -19,7 +19,7 @@ use clap::{error::ErrorKind, CommandFactory, Parser}; use manta_accounting::transfer::canonical::generate_context; use manta_crypto::rand::{OsRng, Rand}; -use manta_pay::{config::FullParameters, simulation::Simulation}; +use manta_pay::{config::FullParametersRef, simulation::Simulation}; /// Runs the Manta Pay simulation. pub fn main() { @@ -29,7 +29,7 @@ pub fn main() { let utxo_accumulator_model = rng.gen(); let (proving_context, verifying_context) = generate_context( &(), - FullParameters::new(¶meters, &utxo_accumulator_model), + FullParametersRef::new(¶meters, &utxo_accumulator_model), &mut rng, ) .expect("Failed to generate contexts."); diff --git a/manta-pay/src/config.rs b/manta-pay/src/config.rs deleted file mode 100644 index 366a125be..000000000 --- a/manta-pay/src/config.rs +++ /dev/null @@ -1,1103 +0,0 @@ -// Copyright 2019-2022 Manta Network. -// This file is part of manta-rs. -// -// manta-rs is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// manta-rs is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with manta-rs. If not, see . - -//! Manta-Pay Configuration - -use crate::crypto::{ - ecc, - encryption::aes::{self, FixedNonceAesGcm}, - key::Blake2sKdf, - poseidon::compat as poseidon, -}; -use alloc::vec::Vec; -use blake2::{ - digest::{Update, VariableOutput}, - Blake2sVar, -}; -use core::{ - iter::Sum, - ops::{AddAssign, Rem, Sub, SubAssign}, -}; -use manta_accounting::{ - asset, - transfer::{self, Asset}, -}; -use manta_crypto::{ - accumulator, - algebra::diffie_hellman::DiffieHellman, - arkworks::{ - bls12_381::{self, Bls12_381}, - constraint::{ - fp::{field_element_as_bytes, Fp}, - Boolean, FpVar, R1CS, - }, - ed_on_bls12_381::{self, constraints::EdwardsVar as Bls12_381_EdwardsVar}, - ff::ToConstraintField, - groth16, - serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}, - }, - constraint::Input, - eclair::{ - self, - alloc::{ - mode::{Public, Secret}, - Allocate, Allocator, Constant, Variable, - }, - ops::Add, - }, - encryption, - hash::ArrayHashFunction, - key::{self, kdf::KeyDerivationFunction}, - merkle_tree, - rand::{RngCore, Sample}, -}; -use manta_util::{ - codec::{Decode, DecodeError, Encode, Read, Write}, - num::CheckedSub, - Array, AsBytes, SizeLimit, -}; - -#[cfg(feature = "bs58")] -use alloc::string::String; - -#[cfg(feature = "test")] -use manta_crypto::rand::Rand; - -#[cfg(feature = "serde")] -use manta_util::serde::{Deserialize, Serialize}; - -pub(crate) use ed_on_bls12_381::EdwardsProjective as Bls12_381_Edwards; - -/// Asset Id Type -pub type AssetIdType = u32; - -/// Asset Id -#[cfg_attr( - feature = "serde", - derive(Deserialize, Serialize), - serde(crate = "manta_util::serde", deny_unknown_fields) -)] -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct AssetId(pub AssetIdType); - -impl SizeLimit for AssetId { - const SIZE: usize = (AssetIdType::BITS / 8) as usize; -} - -impl Encode for AssetId { - #[inline] - fn encode(&self, writer: W) -> Result<(), W::Error> - where - W: Write, - { - self.0.encode(writer) - } -} - -impl Decode for AssetId { - type Error = ::Error; - - #[inline] - fn decode(reader: R) -> Result> - where - R: Read, - { - Ok(Self(AssetIdType::decode(reader)?)) - } -} - -impl Sample for AssetId { - #[inline] - fn gen(rng: &mut R) -> Self - where - (): Default, - R: RngCore + ?Sized, - { - Self(AssetIdType::gen(rng)) - } - - #[inline] - fn sample(distribution: (), rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - Self(AssetIdType::sample(distribution, rng)) - } -} - -impl Input for AssetId { - #[inline] - fn extend(&self, input: &mut Vec) { - input.push(self.0.into()); - } -} - -/// Asset Value Type -pub type AssetValueType = u128; - -/// Asset Value -#[cfg_attr( - feature = "serde", - derive(Deserialize, Serialize), - serde(crate = "manta_util::serde", deny_unknown_fields) -)] -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct AssetValue(pub AssetValueType); - -impl SizeLimit for AssetValue { - const SIZE: usize = (AssetValueType::BITS / 8) as usize; -} - -impl Sub for AssetValue { - type Output = Self; - - #[inline] - fn sub(self, rhs: Self) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl SubAssign for AssetValue { - #[inline] - fn sub_assign(&mut self, rhs: Self) { - self.0.sub_assign(rhs.0); - } -} - -impl CheckedSub for AssetValue { - type Output = Self; - - #[inline] - fn checked_sub(self, rhs: Self) -> Option { - Some(Self(self.0.checked_sub(rhs.0)?)) - } -} - -impl CheckedSub for &AssetValue { - type Output = ::Output; - - #[inline] - fn checked_sub(self, rhs: Self) -> Option { - (*self).checked_sub(*rhs) - } -} - -impl Sub for &AssetValue { - type Output = ::Output; - - #[inline] - fn sub(self, rhs: Self) -> Self::Output { - (*self).sub(*rhs) - } -} - -impl Sum for AssetValue { - #[inline] - fn sum>(iter: I) -> Self { - Self(iter.map(|x| x.0).sum()) - } -} - -impl Rem for AssetValue { - type Output = Self; - - #[inline] - fn rem(self, rhs: Self) -> Self::Output { - Self(self.0 % rhs.0) - } -} - -impl AddAssign for AssetValue { - #[inline] - fn add_assign(&mut self, rhs: Self) { - self.0 += rhs.0; - } -} - -impl AddAssign for &AssetValue { - #[inline] - fn add_assign(&mut self, rhs: Self) { - let mut asset_value = **self; - asset_value.add_assign(*rhs); - } -} - -impl Encode for AssetValue { - #[inline] - fn encode(&self, writer: W) -> Result<(), W::Error> - where - W: Write, - { - self.0.encode(writer) - } -} - -impl Decode for AssetValue { - type Error = ::Error; - - #[inline] - fn decode(reader: R) -> Result> - where - R: Read, - { - Ok(Self(AssetValueType::decode(reader)?)) - } -} - -impl Sample for AssetValue { - #[inline] - fn gen(rng: &mut R) -> Self - where - (): Default, - R: RngCore + ?Sized, - { - Self(AssetValueType::gen(rng)) - } - - #[inline] - fn sample(distribution: (), rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - Self(AssetValueType::sample(distribution, rng)) - } -} - -impl Input for AssetValue { - #[inline] - fn extend(&self, input: &mut Vec) { - input.push(self.0.into()); - } -} - -/// Pairing Curve Type -pub type PairingCurve = Bls12_381; - -/// Embedded Scalar Field Type -pub type EmbeddedScalarField = ed_on_bls12_381::Fr; - -/// Embedded Scalar Type -pub type EmbeddedScalar = ecc::arkworks::Scalar; - -/// Embedded Scalar Variable Type -pub type EmbeddedScalarVar = ecc::arkworks::ScalarVar; - -/// Embedded Group Type -pub type Group = ecc::arkworks::Group; - -/// Embedded Group Variable Type -pub type GroupVar = ecc::arkworks::GroupVar; - -/// Constraint Field -pub type ConstraintField = bls12_381::Fr; - -/// Constraint Field Variable -pub type ConstraintFieldVar = FpVar; - -/// Constraint Compiler -pub type Compiler = R1CS; - -/// Proof System Proof -pub type Proof = groth16::Proof; - -/// Proof System -pub type ProofSystem = groth16::Groth16; - -/// Proof System Error -pub type ProofSystemError = groth16::Error; - -/// Poseidon Specification -pub struct PoseidonSpec; - -impl Constant for PoseidonSpec { - type Type = Self; - - #[inline] - fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { - let _ = (this, compiler); - Self - } -} - -/// Poseidon-2 Hash Parameters -pub type Poseidon2 = poseidon::Hasher, 2>; - -/// Poseidon-2 Hash Parameters Variable -pub type Poseidon2Var = poseidon::Hasher, 2, Compiler>; - -impl poseidon::arkworks::Specification for PoseidonSpec<2> { - type Field = ConstraintField; - const FULL_ROUNDS: usize = 8; - const PARTIAL_ROUNDS: usize = 57; - const SBOX_EXPONENT: u64 = 5; -} - -/// Poseidon-4 Hash Parameters -pub type Poseidon4 = poseidon::Hasher, 4>; - -/// Poseidon-4 Hash Parameters Variable -pub type Poseidon4Var = poseidon::Hasher, 4, Compiler>; - -impl poseidon::arkworks::Specification for PoseidonSpec<4> { - type Field = ConstraintField; - const FULL_ROUNDS: usize = 8; - const PARTIAL_ROUNDS: usize = 60; - const SBOX_EXPONENT: u64 = 5; -} - -/// Key Agreement Scheme Type -pub type KeyAgreementScheme = DiffieHellman; - -/// Secret Key Type -pub type SecretKey = ::SecretKey; - -/// Public Key Type -pub type PublicKey = ::PublicKey; - -/// Shared Secret Type -pub type SharedSecret = ::SharedSecret; - -/// Key Agreement Scheme Variable Type -pub type KeyAgreementSchemeVar = DiffieHellman; - -/// Secret Key Variable Type -pub type SecretKeyVar = ::SecretKey; - -/// Public Key Variable Type -pub type PublicKeyVar = ::PublicKey; - -/// Unspent Transaction Output Type -pub type Utxo = Fp; - -/// UTXO Commitment Scheme -#[derive(Clone, Debug)] -pub struct UtxoCommitmentScheme(pub Poseidon4); - -impl transfer::UtxoCommitmentScheme for UtxoCommitmentScheme { - type EphemeralSecretKey = EmbeddedScalar; - type PublicSpendKey = Group; - type Asset = asset::Asset; - type Utxo = Utxo; - - #[inline] - fn commit( - &self, - ephemeral_secret_key: &Self::EphemeralSecretKey, - public_spend_key: &Self::PublicSpendKey, - asset: &Self::Asset, - compiler: &mut (), - ) -> Self::Utxo { - self.0.hash( - [ - // FIXME: This is the lift from inner scalar to outer scalar and only exists in some - // cases! We need a better abstraction for this. - &ecc::arkworks::lift_embedded_scalar::(ephemeral_secret_key), - &Fp(public_spend_key.0.x), // NOTE: Group is in affine form, so we can extract `x`. - &Fp(asset.id.0.into()), - &Fp(asset.value.0.into()), - ], - compiler, - ) - } -} - -impl Decode for UtxoCommitmentScheme { - type Error = SerializationError; - - #[inline] - fn decode(reader: R) -> Result> - where - R: Read, - { - Ok(Self(Poseidon4::decode(reader)?)) - } -} - -impl Encode for UtxoCommitmentScheme { - #[inline] - fn encode(&self, writer: W) -> Result<(), W::Error> - where - W: Write, - { - self.0.encode(writer) - } -} - -#[cfg(any(feature = "test", test))] // NOTE: This is only safe in a test. -impl Sample for UtxoCommitmentScheme { - #[inline] - fn sample(distribution: (), rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - Self(rng.sample(distribution)) - } -} - -/// Unspent Transaction Output Variable Type -pub type UtxoVar = ConstraintFieldVar; - -/// UTXO Commitment Scheme Variable -pub struct UtxoCommitmentSchemeVar(pub Poseidon4Var); - -impl transfer::UtxoCommitmentScheme for UtxoCommitmentSchemeVar { - type EphemeralSecretKey = EmbeddedScalarVar; - type PublicSpendKey = GroupVar; - type Asset = asset::Asset; - type Utxo = UtxoVar; - - #[inline] - fn commit( - &self, - ephemeral_secret_key: &Self::EphemeralSecretKey, - public_spend_key: &Self::PublicSpendKey, - asset: &Self::Asset, - compiler: &mut Compiler, - ) -> Self::Utxo { - self.0.hash( - [ - &ephemeral_secret_key.0, - &public_spend_key.0.x, // NOTE: Group is in affine form, so we can extract `x`. - &asset.id.0, - &asset.value.0, - ], - compiler, - ) - } -} - -impl Constant for UtxoCommitmentSchemeVar { - type Type = UtxoCommitmentScheme; - - #[inline] - fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self(this.0.as_constant(compiler)) - } -} - -/// Void Number Type -pub type VoidNumber = Fp; - -/// Void Number Commitment Scheme -#[derive(Clone, Debug)] -pub struct VoidNumberCommitmentScheme(pub Poseidon2); - -impl transfer::VoidNumberCommitmentScheme for VoidNumberCommitmentScheme { - type SecretSpendKey = SecretKey; - type Utxo = Utxo; - type VoidNumber = VoidNumber; - - #[inline] - fn commit( - &self, - secret_spend_key: &Self::SecretSpendKey, - utxo: &Self::Utxo, - compiler: &mut (), - ) -> Self::VoidNumber { - self.0.hash( - [ - // FIXME: This is the lift from inner scalar to outer scalar and only exists in some - // cases! We need a better abstraction for this. - &ecc::arkworks::lift_embedded_scalar::(secret_spend_key), - utxo, - ], - compiler, - ) - } -} - -impl Decode for VoidNumberCommitmentScheme { - type Error = SerializationError; - - #[inline] - fn decode(reader: R) -> Result> - where - R: Read, - { - Ok(Self(Poseidon2::decode(reader)?)) - } -} - -impl Encode for VoidNumberCommitmentScheme { - #[inline] - fn encode(&self, writer: W) -> Result<(), W::Error> - where - W: Write, - { - self.0.encode(writer) - } -} - -#[cfg(any(feature = "test", test))] // NOTE: This is only safe in a test. -impl Sample for VoidNumberCommitmentScheme { - #[inline] - fn sample(distribution: (), rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - Self(rng.sample(distribution)) - } -} - -/// Void Number Variable Type -pub type VoidNumberVar = ConstraintFieldVar; - -/// Void Number Commitment Scheme Variable -pub struct VoidNumberCommitmentSchemeVar(pub Poseidon2Var); - -impl transfer::VoidNumberCommitmentScheme for VoidNumberCommitmentSchemeVar { - type SecretSpendKey = SecretKeyVar; - type Utxo = >::Utxo; - type VoidNumber = ConstraintFieldVar; - - #[inline] - fn commit( - &self, - secret_spend_key: &Self::SecretSpendKey, - utxo: &Self::Utxo, - compiler: &mut Compiler, - ) -> Self::VoidNumber { - self.0.hash([&secret_spend_key.0, utxo], compiler) - } -} - -impl Constant for VoidNumberCommitmentSchemeVar { - type Type = VoidNumberCommitmentScheme; - - #[inline] - fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self(this.0.as_constant(compiler)) - } -} - -/// Asset ID Variable -pub struct AssetIdVar(ConstraintFieldVar); - -impl eclair::cmp::PartialEq for AssetIdVar { - #[inline] - fn eq(&self, rhs: &Self, compiler: &mut Compiler) -> Boolean { - ConstraintFieldVar::eq(&self.0, &rhs.0, compiler) - } -} - -impl Variable for AssetIdVar { - type Type = AssetId; - - #[inline] - fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self(Fp(ConstraintField::from(this.0)).as_known::(compiler)) - } - - #[inline] - fn new_unknown(compiler: &mut Compiler) -> Self { - Self(compiler.allocate_unknown::()) - } -} - -impl Variable for AssetIdVar { - type Type = AssetId; - - #[inline] - fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self(Fp(ConstraintField::from(this.0)).as_known::(compiler)) - } - - #[inline] - fn new_unknown(compiler: &mut Compiler) -> Self { - Self(compiler.allocate_unknown::()) - } -} - -/// Asset Value Variable -pub struct AssetValueVar(ConstraintFieldVar); - -impl Add for AssetValueVar { - type Output = Self; - - #[inline] - fn add(self, rhs: Self, compiler: &mut Compiler) -> Self::Output { - Self(ConstraintFieldVar::add(self.0, rhs.0, compiler)) - } -} - -impl eclair::cmp::PartialEq for AssetValueVar { - #[inline] - fn eq(&self, rhs: &Self, compiler: &mut Compiler) -> Boolean { - ConstraintFieldVar::eq(&self.0, &rhs.0, compiler) - } -} - -impl Variable for AssetValueVar { - type Type = AssetValue; - - #[inline] - fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self(Fp(ConstraintField::from(this.0)).as_known::(compiler)) - } - - #[inline] - fn new_unknown(compiler: &mut Compiler) -> Self { - Self(compiler.allocate_unknown::()) - } -} - -impl Variable for AssetValueVar { - type Type = AssetValue; - - #[inline] - fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self(Fp(ConstraintField::from(this.0)).as_known::(compiler)) - } - - #[inline] - fn new_unknown(compiler: &mut Compiler) -> Self { - Self(compiler.allocate_unknown::()) - } -} - -/// Leaf Hash Configuration Type -pub type LeafHash = merkle_tree::IdentityLeafHash; - -/// Leaf Hash Variable Configuration Type -pub type LeafHashVar = merkle_tree::IdentityLeafHash; - -/// Inner Hash Configuration -pub struct InnerHash; - -impl merkle_tree::InnerHash for InnerHash { - type LeafDigest = Utxo; - type Parameters = Poseidon2; - type Output = Fp; - - #[inline] - fn join( - parameters: &Self::Parameters, - lhs: &Self::Output, - rhs: &Self::Output, - compiler: &mut (), - ) -> Self::Output { - parameters.hash([lhs, rhs], compiler) - } - - #[inline] - fn join_leaves( - parameters: &Self::Parameters, - lhs: &Self::LeafDigest, - rhs: &Self::LeafDigest, - compiler: &mut (), - ) -> Self::Output { - parameters.hash([lhs, rhs], compiler) - } -} - -/// Inner Hash Variable Configuration -pub struct InnerHashVar; - -impl merkle_tree::InnerHash for InnerHashVar { - type LeafDigest = UtxoVar; - type Parameters = Poseidon2Var; - type Output = ConstraintFieldVar; - - #[inline] - fn join( - parameters: &Self::Parameters, - lhs: &Self::Output, - rhs: &Self::Output, - compiler: &mut Compiler, - ) -> Self::Output { - parameters.hash([lhs, rhs], compiler) - } - - #[inline] - fn join_leaves( - parameters: &Self::Parameters, - lhs: &Self::LeafDigest, - rhs: &Self::LeafDigest, - compiler: &mut Compiler, - ) -> Self::Output { - parameters.hash([lhs, rhs], compiler) - } -} - -/// UTXO Accumulator Model -pub type UtxoAccumulatorModel = merkle_tree::Parameters; - -/// UTXO Accumulator Output -pub type UtxoAccumulatorOutput = merkle_tree::Root; - -/// Merkle Tree Configuration -pub struct MerkleTreeConfiguration; - -impl merkle_tree::HashConfiguration for MerkleTreeConfiguration { - type LeafHash = LeafHash; - type InnerHash = InnerHash; -} - -impl merkle_tree::Configuration for MerkleTreeConfiguration { - const HEIGHT: usize = 20; -} - -impl MerkleTreeConfiguration { - /// Width of the Merkle Forest - pub const FOREST_WIDTH: usize = 256; -} - -impl merkle_tree::forest::Configuration for MerkleTreeConfiguration { - type Index = u8; - - #[inline] - fn tree_index(leaf: &merkle_tree::Leaf) -> Self::Index { - let mut hasher = Blake2sVar::new(1).unwrap(); - let mut buffer = Vec::new(); - leaf.0 - .serialize_unchecked(&mut buffer) - .expect("Serializing is not allowed to fail."); - hasher.update(&buffer); - let mut result = [0]; - hasher - .finalize_variable(&mut result) - .expect("Hashing is not allowed to fail."); - result[0] - } -} - -/* NOTE: Configuration for testing single-tree forest. -impl MerkleTreeConfiguration { - /// Width of the Merkle Forest - pub const FOREST_WIDTH: usize = 1; -} -impl merkle_tree::forest::Configuration for MerkleTreeConfiguration { - type Index = merkle_tree::forest::SingleTreeIndex; - #[inline] - fn tree_index(leaf: &merkle_tree::Leaf) -> Self::Index { - let _ = leaf; - Default::default() - } -} -*/ - -#[cfg(any(feature = "test", test))] -impl merkle_tree::test::HashParameterSampling for MerkleTreeConfiguration { - type LeafHashParameterDistribution = (); - type InnerHashParameterDistribution = (); - - #[inline] - fn sample_leaf_hash_parameters( - distribution: Self::LeafHashParameterDistribution, - rng: &mut R, - ) -> merkle_tree::LeafHashParameters - where - R: RngCore + ?Sized, - { - let _ = (distribution, rng); - } - - #[inline] - fn sample_inner_hash_parameters( - distribution: Self::InnerHashParameterDistribution, - rng: &mut R, - ) -> merkle_tree::InnerHashParameters - where - R: RngCore + ?Sized, - { - rng.sample(distribution) - } -} - -/// Merkle Tree Variable Configuration -pub struct MerkleTreeConfigurationVar; - -impl merkle_tree::HashConfiguration for MerkleTreeConfigurationVar { - type LeafHash = LeafHashVar; - type InnerHash = InnerHashVar; -} - -impl merkle_tree::Configuration for MerkleTreeConfigurationVar { - const HEIGHT: usize = ::HEIGHT; -} - -impl Constant for MerkleTreeConfigurationVar { - type Type = MerkleTreeConfiguration; - - #[inline] - fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { - let _ = (this, compiler); - Self - } -} - -impl Input for Group { - #[inline] - fn extend(&self, input: &mut Vec) { - input.append(&mut self.0.to_field_elements().unwrap()); - } -} - -/// Note Plaintext Mapping -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct NotePlaintextMapping; - -impl encryption::PlaintextType for NotePlaintextMapping { - type Plaintext = Note; -} - -impl encryption::convert::plaintext::Forward for NotePlaintextMapping { - type TargetPlaintext = Array; - - #[inline] - fn as_target(source: &Self::Plaintext, _: &mut ()) -> Self::TargetPlaintext { - let mut bytes = Vec::new(); - bytes.append(&mut field_element_as_bytes(&source.ephemeral_secret_key.0)); - bytes - .write(&mut source.asset.to_vec().as_slice()) - .expect("This can never fail."); - Array::from_unchecked(bytes) - } -} - -impl encryption::DecryptedPlaintextType for NotePlaintextMapping { - type DecryptedPlaintext = Option; -} - -impl encryption::convert::plaintext::Reverse for NotePlaintextMapping { - type TargetDecryptedPlaintext = Option>; - - #[inline] - fn into_source(target: Self::TargetDecryptedPlaintext, _: &mut ()) -> Self::DecryptedPlaintext { - // TODO: Use a deserialization method to do this. - let target = target?; - let mut slice: &[u8] = target.as_ref(); - Some(Note { - ephemeral_secret_key: Fp(EmbeddedScalarField::deserialize(&mut slice).ok()?), - asset: Asset::::from_vec(slice.to_vec()) - .expect("Decoding Asset is not allowed to fail."), - }) - } -} - -/// Note Encryption KDF -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct NoteEncryptionKDF; - -impl encryption::EncryptionKeyType for NoteEncryptionKDF { - type EncryptionKey = Group; -} - -impl encryption::DecryptionKeyType for NoteEncryptionKDF { - type DecryptionKey = Group; -} - -impl encryption::convert::key::Encryption for NoteEncryptionKDF { - type TargetEncryptionKey = [u8; 32]; - - #[inline] - fn as_target(source: &Self::EncryptionKey, compiler: &mut ()) -> Self::TargetEncryptionKey { - Blake2sKdf.derive(&source.as_bytes(), compiler) - } -} -impl encryption::convert::key::Decryption for NoteEncryptionKDF { - type TargetDecryptionKey = [u8; 32]; - - #[inline] - fn as_target(source: &Self::DecryptionKey, compiler: &mut ()) -> Self::TargetDecryptionKey { - Blake2sKdf.derive(&source.as_bytes(), compiler) - } -} - -/// Note Symmetric Encryption Scheme -pub type NoteSymmetricEncryptionScheme = encryption::convert::key::Converter< - encryption::convert::plaintext::Converter< - FixedNonceAesGcm<{ Note::SIZE }, { aes::ciphertext_size(Note::SIZE) }>, - NotePlaintextMapping, - >, - NoteEncryptionKDF, ->; - -/// Note Encryption Scheme -pub type NoteEncryptionScheme = - encryption::hybrid::Hybrid; - -/// Base Configuration -pub struct Config; - -impl transfer::Configuration for Config { - type AssetId = AssetId; - type AssetValue = AssetValue; - type SecretKey = SecretKey; - type PublicKey = PublicKey; - type KeyAgreementScheme = KeyAgreementScheme; - type SecretKeyVar = SecretKeyVar; - type PublicKeyVar = PublicKeyVar; - type KeyAgreementSchemeVar = KeyAgreementSchemeVar; - type Utxo = ::Utxo; - type UtxoCommitmentScheme = UtxoCommitmentScheme; - type UtxoVar = - >::Utxo; - type UtxoCommitmentSchemeVar = UtxoCommitmentSchemeVar; - type VoidNumber = - ::VoidNumber; - type VoidNumberCommitmentScheme = VoidNumberCommitmentScheme; - type VoidNumberVar = - >::VoidNumber; - type VoidNumberCommitmentSchemeVar = VoidNumberCommitmentSchemeVar; - type UtxoAccumulatorModel = UtxoAccumulatorModel; - type UtxoAccumulatorWitnessVar = ::Witness; - type UtxoAccumulatorOutputVar = ::Output; - type UtxoAccumulatorModelVar = merkle_tree::Parameters; - type AssetIdVar = AssetIdVar; - type AssetValueVar = AssetValueVar; - type Compiler = Compiler; - type ProofSystem = ProofSystem; - type NoteEncryptionScheme = NoteSymmetricEncryptionScheme; -} - -/// Transfer Parameters -pub type Parameters = transfer::Parameters; - -/// Full Transfer Parameters -pub type FullParameters<'p> = transfer::FullParameters<'p, Config>; - -/// Note Type -pub type Note = transfer::Note; - -/// Encrypted Note Type -pub type EncryptedNote = transfer::EncryptedNote; - -/// Sender Type -pub type Sender = transfer::Sender; - -/// Sender Post Type -pub type SenderPost = transfer::SenderPost; - -/// Receiver Type -pub type Receiver = transfer::Receiver; - -/// Receiver Post Type -pub type ReceiverPost = transfer::ReceiverPost; - -/// Transfer Post Type -pub type TransferPost = transfer::TransferPost; - -/// Mint Transfer Type -pub type Mint = transfer::canonical::Mint; - -/// Private Transfer Type -pub type PrivateTransfer = transfer::canonical::PrivateTransfer; - -/// Reclaim Transfer Type -pub type Reclaim = transfer::canonical::Reclaim; - -/// Proving Context Type -pub type ProvingContext = transfer::ProvingContext; - -/// Verifying Context Type -pub type VerifyingContext = transfer::VerifyingContext; - -/// Multi-Proving Context Type -pub type MultiProvingContext = transfer::canonical::MultiProvingContext; - -/// Multi-Verifying Context Type -pub type MultiVerifyingContext = transfer::canonical::MultiVerifyingContext; - -/// Transaction Type -pub type Transaction = transfer::canonical::Transaction; - -/// Spending Key Type -pub type SpendingKey = transfer::SpendingKey; - -/// Receiving Key Type -pub type ReceivingKey = transfer::ReceivingKey; - -/// Converts a [`ReceivingKey`] into a base58-encoded string. -#[cfg(feature = "bs58")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "bs58")))] -#[inline] -pub fn receiving_key_to_base58(receiving_key: &ReceivingKey) -> String { - let mut bytes = Vec::new(); - receiving_key - .spend - .encode(&mut bytes) - .expect("Encoding is not allowed to fail."); - receiving_key - .view - .encode(&mut bytes) - .expect("Encoding is not allowed to fail."); - bs58::encode(bytes).into_string() -} - -/// Converts a base58-encoded string into a [`ReceivingKey`]. -#[cfg(feature = "bs58")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "bs58")))] -#[inline] -pub fn receiving_key_from_base58(string: &str) -> Option { - let bytes = bs58::decode(string.as_bytes()).into_vec().ok()?; - let (spend, view) = bytes.split_at(bytes.len() / 2); - Some(ReceivingKey { - spend: spend.to_owned().try_into().ok()?, - view: view.to_owned().try_into().ok()?, - }) -} - -/// Asset Value Sample -#[cfg(feature = "test")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] -pub mod asset_value_sample { - use super::*; - use manta_crypto::rand::{rand::Rng, SampleBorrow, SampleUniform, UniformInt, UniformSampler}; - - /// Asset Value Sampler - #[derive(Clone, Copy, Debug)] - pub struct AssetValueSampler(UniformInt); - - impl UniformSampler for AssetValueSampler { - type X = AssetValue; - - #[inline] - fn new(low: B1, high: B2) -> Self - where - B1: SampleBorrow, - B2: SampleBorrow, - { - AssetValueSampler(UniformInt::::new(low.borrow().0, high.borrow().0)) - } - - #[inline] - fn new_inclusive(low: B1, high: B2) -> Self - where - B1: SampleBorrow, - B2: SampleBorrow, - { - AssetValueSampler(UniformInt::::new_inclusive( - low.borrow().0, - high.borrow().0, - )) - } - - #[inline] - fn sample(&self, rng: &mut R) -> Self::X - where - R: Rng + ?Sized, - { - AssetValue(self.0.sample(rng)) - } - } - - impl SampleUniform for AssetValue { - type Sampler = AssetValueSampler; - } -} diff --git a/manta-pay/src/config/mod.rs b/manta-pay/src/config/mod.rs new file mode 100644 index 000000000..ab095b832 --- /dev/null +++ b/manta-pay/src/config/mod.rs @@ -0,0 +1,228 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Manta-Pay Configuration + +use manta_accounting::transfer; +use manta_crypto::arkworks::{ + algebra::{self, ScalarVar}, + bn254::{self, Bn254}, + constraint::{FpVar, R1CS}, + ed_on_bn254::{ + self, constraints::EdwardsVar as Bn254_EdwardsVar, EdwardsProjective as Bn254_Edwards, + }, + groth16, +}; + +#[cfg(feature = "bs58")] +use {alloc::string::String, manta_util::codec::Encode}; + +pub mod poseidon; +pub mod utxo; +pub mod utxo_utilities; + +/// Pairing Curve Type +pub type PairingCurve = Bn254; + +/// Embedded Scalar Field Type +pub type EmbeddedScalarField = ed_on_bn254::Fr; + +/// Embedded Scalar Type +pub type EmbeddedScalar = algebra::Scalar; + +/// Embedded Scalar Variable Type +pub type EmbeddedScalarVar = ScalarVar; + +/// Embedded Group Curve Type +pub type GroupCurve = Bn254_Edwards; + +/// Embedded Group Curve Type +pub type GroupCurveAffine = ed_on_bn254::EdwardsAffine; + +/// Embedded Group Curve Variable Type +pub type GroupCurveVar = Bn254_EdwardsVar; + +/// Embedded Group Type +pub type Group = algebra::Group; + +/// Embedded Group Variable Type +pub type GroupVar = algebra::GroupVar; + +/// Constraint Field +pub type ConstraintField = bn254::Fr; + +/// Constraint Field Variable +pub type ConstraintFieldVar = FpVar; + +/// Constraint Compiler +pub type Compiler = R1CS; + +/// Proof System Proof +pub type Proof = groth16::Proof; + +/// Proof System +pub type ProofSystem = groth16::Groth16; + +/// Proof System Error +pub type ProofSystemError = groth16::Error; + +/// Transfer Configuration +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub struct Config; + +impl transfer::Configuration for Config { + type Compiler = Compiler; + type AssetId = utxo::AssetId; + type AssetValue = utxo::AssetValue; + type AssociatedData = utxo::AssociatedData; + type Utxo = utxo::Utxo; + type Nullifier = utxo::Nullifier; + type Identifier = utxo::Identifier; + type Address = utxo::Address; + type Note = utxo::Note; + type MintSecret = utxo::MintSecret; + type SpendSecret = utxo::SpendSecret; + type UtxoAccumulatorWitness = utxo::UtxoAccumulatorWitness; + type UtxoAccumulatorOutput = utxo::UtxoAccumulatorOutput; + type UtxoAccumulatorItemHash = utxo::UtxoAccumulatorItemHash; + type Parameters = utxo::Parameters; + type AuthorizationContextVar = utxo::AuthorizationContextVar; + type AuthorizationProofVar = utxo::AuthorizationProofVar; + type AssetIdVar = utxo::AssetIdVar; + type AssetValueVar = utxo::AssetValueVar; + type UtxoVar = utxo::UtxoVar; + type NoteVar = utxo::NoteVar; + type NullifierVar = utxo::NullifierVar; + type UtxoAccumulatorWitnessVar = utxo::UtxoAccumulatorWitnessVar; + type UtxoAccumulatorOutputVar = utxo::UtxoAccumulatorOutputVar; + type UtxoAccumulatorModelVar = utxo::UtxoAccumulatorModelVar; + type MintSecretVar = utxo::MintSecretVar; + type SpendSecretVar = utxo::SpendSecretVar; + type ParametersVar = utxo::ParametersVar; + type ProofSystem = ProofSystem; +} + +/// Transfer Parameters +pub type Parameters = transfer::Parameters; + +/// UTXO Accumulator Output Type +pub type UtxoAccumulatorOutput = transfer::UtxoAccumulatorOutput; + +/// UTXO Accumulator Model Type +pub type UtxoAccumulatorModel = transfer::UtxoAccumulatorModel; + +/// Full Transfer Parameters +pub type FullParametersRef<'p> = transfer::FullParametersRef<'p, Config>; + +/// Authorization Context Type +pub type AuthorizationContext = transfer::AuthorizationContext; + +/// Authorization Type +pub type Authorization = transfer::Authorization; + +/// Asset Id Type +pub type AssetId = transfer::AssetId; + +/// Asset Value Type +pub type AssetValue = transfer::AssetValue; + +/// Asset Type +pub type Asset = transfer::Asset; + +/// Unspent Transaction Output Type +pub type Utxo = transfer::Utxo; + +/// Note Type +pub type Note = transfer::Note; + +/// Nullifier Type +pub type Nullifier = transfer::Nullifier; + +/// Sender Type +pub type Sender = transfer::Sender; + +/// Sender Post Type +pub type SenderPost = transfer::SenderPost; + +/// Receiver Type +pub type Receiver = transfer::Receiver; + +/// Receiver Post Type +pub type ReceiverPost = transfer::ReceiverPost; + +/// Transfer Post Body Type +pub type TransferPostBody = transfer::TransferPostBody; + +/// Transfer Post Type +pub type TransferPost = transfer::TransferPost; + +/// To-Private Transfer Type +pub type ToPrivate = transfer::canonical::ToPrivate; + +/// Private Transfer Type +pub type PrivateTransfer = transfer::canonical::PrivateTransfer; + +/// To-Public Transfer Type +pub type ToPublic = transfer::canonical::ToPublic; + +/// Proving Context Type +pub type ProvingContext = transfer::ProvingContext; + +/// Verifying Context Type +pub type VerifyingContext = transfer::VerifyingContext; + +/// Multi-Proving Context Type +pub type MultiProvingContext = transfer::canonical::MultiProvingContext; + +/// Multi-Verifying Context Type +pub type MultiVerifyingContext = transfer::canonical::MultiVerifyingContext; + +/// Transaction Type +pub type Transaction = transfer::canonical::Transaction; + +/// Spending Key Type +pub type SpendingKey = transfer::SpendingKey; + +/// Address Type +pub type Address = transfer::Address; + +/// Converts an [`Address`] into a base58-encoded string. +#[cfg(feature = "bs58")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "bs58")))] +#[inline] +pub fn address_to_base58(address: &Address) -> String { + let mut bytes = Vec::new(); + address + .receiving_key + .encode(&mut bytes) + .expect("Encoding is not allowed to fail."); + bs58::encode(bytes).into_string() +} + +/// Converts a base58-encoded string into an [`Address`]. +#[cfg(feature = "bs58")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "bs58")))] +#[inline] +pub fn address_from_base58(string: &str) -> Option

{ + Some(Address::new( + bs58::decode(string.as_bytes()) + .into_vec() + .ok()? + .try_into() + .ok()?, + )) +} diff --git a/manta-pay/src/config/poseidon.rs b/manta-pay/src/config/poseidon.rs new file mode 100644 index 000000000..1c68600ca --- /dev/null +++ b/manta-pay/src/config/poseidon.rs @@ -0,0 +1,122 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Poseidon Configuration + +use crate::{config::ConstraintField, crypto::poseidon}; +use manta_crypto::eclair::alloc::Constant; + +/// Poseidon Specification Configuration +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Spec; + +impl poseidon::Constants for Spec<2> { + const WIDTH: usize = 3; + const FULL_ROUNDS: usize = 8; + const PARTIAL_ROUNDS: usize = 55; +} + +impl poseidon::Constants for Spec<3> { + const WIDTH: usize = 4; + const FULL_ROUNDS: usize = 8; + const PARTIAL_ROUNDS: usize = 55; +} + +impl poseidon::Constants for Spec<4> { + const WIDTH: usize = 5; + const FULL_ROUNDS: usize = 8; + const PARTIAL_ROUNDS: usize = 56; +} + +impl poseidon::Constants for Spec<5> { + const WIDTH: usize = 6; + const FULL_ROUNDS: usize = 8; + const PARTIAL_ROUNDS: usize = 56; +} + +impl poseidon::arkworks::Specification for Spec +where + Self: poseidon::Constants, +{ + type Field = ConstraintField; + + const SBOX_EXPONENT: u64 = 5; +} + +impl Constant for Spec { + type Type = Self; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self + } +} + +/// Arity 2 Poseidon Specification +pub type Spec2 = Spec<2>; + +/// Arity 3 Poseidon Specification +pub type Spec3 = Spec<3>; + +/// Arity 4 Poseidon Specification +pub type Spec4 = Spec<4>; + +/// Arity 5 Poseidon Specification +pub type Spec5 = Spec<5>; + +/// Testing Framework +#[cfg(test)] +pub mod test { + use crate::{ + config::{poseidon::Spec, ConstraintField}, + crypto::poseidon::{ + encryption::{BlockArray, FixedDuplexer, PlaintextBlock}, + Constants, + }, + }; + use alloc::boxed::Box; + use manta_crypto::{ + arkworks::constraint::fp::Fp, + encryption::{Decrypt, Encrypt}, + rand::{OsRng, Sample}, + }; + + /// Tests Poseidon duplexer encryption works. + #[test] + fn poseidon_duplexer_test() { + const N: usize = 3; + let mut rng = OsRng; + let duplexer = FixedDuplexer::<1, Spec>::gen(&mut rng); + let field_elements = <[Fp; Spec::::WIDTH - 1]>::gen(&mut rng); + let plaintext_block = PlaintextBlock(Box::new(field_elements)); + let plaintext = BlockArray::<_, 1>([plaintext_block].into()); + let mut key = Vec::new(); + let key_element_1 = Fp::::gen(&mut rng); + let key_element_2 = Fp::::gen(&mut rng); + key.push(key_element_1); + key.push(key_element_2); + let header = vec![]; + let ciphertext = duplexer.encrypt(&key, &(), &header, &plaintext, &mut ()); + let (tag_matches, decrypted_plaintext) = + duplexer.decrypt(&key, &header, &ciphertext, &mut ()); + assert!(tag_matches, "Tag doesn't match"); + assert_eq!( + plaintext, decrypted_plaintext, + "Decrypted plaintext is not equal to original one." + ); + } +} diff --git a/manta-pay/src/config/utxo.rs b/manta-pay/src/config/utxo.rs new file mode 100644 index 000000000..22c5fe8cb --- /dev/null +++ b/manta-pay/src/config/utxo.rs @@ -0,0 +1,2197 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Manta-Pay UTXO Model Version 1 Configuration + +use crate::{ + config::{ + poseidon::{ + Spec2 as Poseidon2, Spec3 as Poseidon3, Spec4 as Poseidon4, Spec5 as Poseidon5, + }, + Compiler, ConstraintField, EmbeddedScalar, EmbeddedScalarField, EmbeddedScalarVar, Group, + GroupCurve, GroupVar, + }, + crypto::{ + encryption::aes, + poseidon::{self, encryption::BlockArray, hash::Hasher, ParameterFieldType}, + }, +}; +use alloc::{vec, vec::Vec}; +use blake2::{ + digest::{Update, VariableOutput}, + Blake2s256, Blake2sVar, Digest, +}; +use core::{convert::Infallible, fmt::Debug, marker::PhantomData}; +use manta_accounting::{ + asset::{self, Asset}, + wallet::ledger, +}; +use manta_crypto::{ + accumulator::ItemHashFunction, + algebra::HasGenerator, + arkworks::{ + algebra::{affine_point_as_bytes, ScalarVar}, + constraint::{fp::Fp, rem_mod_prime, Boolean, FpVar}, + ff::{try_into_u128, PrimeField}, + serialize::{CanonicalSerialize, SerializationError}, + }, + eclair::{ + alloc::{Allocate, Constant}, + num::U128, + }, + encryption::{self, EmptyHeader}, + hash, + hash::ArrayHashFunction, + merkle_tree, + rand::{Rand, RngCore, Sample}, + signature::schnorr, +}; +use manta_util::{ + codec::{Decode, DecodeError, Encode, Read, Write}, + Array, +}; + +#[cfg(feature = "serde")] +use manta_util::serde::{Deserialize, Serialize}; + +pub use manta_accounting::transfer::{ + self, + utxo::{self, protocol}, +}; + +/// Asset Id Type +pub type AssetId = Fp; + +/// Asset Id Variable Type +pub type AssetIdVar = FpVar; + +/// Asset Value Type +pub type AssetValue = u128; + +/// Asset Value Variable Type +pub type AssetValueVar = U128>; + +/// Authorization Context Type +pub type AuthorizationContext = utxo::auth::AuthorizationContext; + +/// Authorization Context Variable Type +pub type AuthorizationContextVar = utxo::auth::AuthorizationContext; + +/// Authorization Proof Type +pub type AuthorizationProof = utxo::auth::AuthorizationProof; + +/// Authorization Proof Variable Type +pub type AuthorizationProofVar = utxo::auth::AuthorizationProof; + +/// Authorization Signature Type +pub type AuthorizationSignature = utxo::auth::AuthorizationSignature; + +/// Proof Authorization Key Type +pub type ProofAuthorizationKey = Group; + +/// Proof Authorization Key Variable Type +pub type ProofAuthorizationKeyVar = GroupVar; + +/// Viewing Key Type +pub type ViewingKey = EmbeddedScalar; + +/// Viewing Key Variable Type +pub type ViewingKeyVar = EmbeddedScalarVar; + +/// Receiving Key Type +pub type ReceivingKey = Group; + +/// Receiving Key Variable Type +pub type ReceivingKeyVar = GroupVar; + +/// Utxo Accumulator Item Type +pub type UtxoAccumulatorItem = Fp; + +/// Utxo Accumulator Item Variable Type +pub type UtxoAccumulatorItemVar = FpVar; + +/// Utxo Accumulator Witness Type +pub type UtxoAccumulatorWitness = utxo::UtxoAccumulatorWitness; + +/// Utxo Accumulator Witness Variable Type +pub type UtxoAccumulatorWitnessVar = utxo::UtxoAccumulatorWitness; + +/// Utxo Accumulator Output Type +pub type UtxoAccumulatorOutput = utxo::UtxoAccumulatorOutput; + +/// Utxo Accumulator Output Variable Type +pub type UtxoAccumulatorOutputVar = utxo::UtxoAccumulatorOutput; + +/// Signature Scheme Type +pub type SignatureScheme = protocol::SignatureScheme; + +/// Parameters Type +pub type Parameters = protocol::Parameters; + +/// Parameters Variable Type +pub type ParametersVar = protocol::BaseParameters, Compiler>; + +/// Associated Data Type +pub type AssociatedData = utxo::AssociatedData; + +/// Utxo Type +pub type Utxo = utxo::Utxo; + +/// Utxo Variable Type +pub type UtxoVar = utxo::Utxo; + +/// Note Type +pub type Note = utxo::Note; + +/// Note Variable Type +pub type NoteVar = utxo::Note; + +/// Incoming Note Type +pub type IncomingNote = protocol::IncomingNote; + +/// Light Incoming Note Type +pub type LightIncomingNote = protocol::LightIncomingNote; + +/// Full Incoming Note Type +pub type FullIncomingNote = protocol::FullIncomingNote; + +/// Outgoing Note Type +pub type OutgoingNote = protocol::OutgoingNote; + +/// Nullifier Type +pub type Nullifier = utxo::Nullifier; + +/// Nullifier Variable Type +pub type NullifierVar = utxo::Nullifier; + +/// Identifier Type +pub type Identifier = utxo::Identifier; + +/// Address Type +pub type Address = utxo::Address; + +/// Mint Secret Type +pub type MintSecret = protocol::MintSecret; + +/// Mint Secret Variable Type +pub type MintSecretVar = protocol::MintSecret, Compiler>; + +/// Spend Secret Type +pub type SpendSecret = protocol::SpendSecret; + +/// Spend Secret Variable Type +pub type SpendSecretVar = protocol::SpendSecret, Compiler>; + +/// Embedded Group Generator +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub struct GroupGenerator(Group); + +impl HasGenerator for GroupGenerator { + type Generator = Group; + + #[inline] + fn generator(&self) -> &Group { + &self.0 + } +} + +impl Encode for GroupGenerator { + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.0.encode(writer) + } +} + +impl Decode for GroupGenerator { + type Error = SerializationError; + + #[inline] + fn decode(reader: R) -> Result> + where + R: Read, + { + Ok(Self(Decode::decode(reader)?)) + } +} + +impl Sample for GroupGenerator { + #[inline] + fn sample(_: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self(rng.gen()) + } +} + +/// Embedded Variable Group Generator +#[derive(Clone)] +pub struct GroupGeneratorVar(GroupVar); + +impl HasGenerator for GroupGeneratorVar { + type Generator = GroupVar; + + #[inline] + fn generator(&self) -> &GroupVar { + &self.0 + } +} + +impl Constant for GroupGeneratorVar { + type Type = GroupGenerator; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self(this.0.as_constant(compiler)) + } +} + +/// Utxo Commitment Scheme Domain Tag +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct UtxoCommitmentSchemeDomainTag; + +impl poseidon::hash::DomainTag for UtxoCommitmentSchemeDomainTag { + #[inline] + fn domain_tag() -> ::ParameterField { + Fp(0u8.into()) // FIXME: Use a real domain tag + } +} + +impl Constant for UtxoCommitmentSchemeDomainTag { + type Type = Self; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self + } +} + +/// Utxo Commitment Scheme Type +type UtxoCommitmentSchemeType = Hasher; + +/// Utxo Commitment Scheme +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "UtxoCommitmentSchemeType: Clone"), + Debug(bound = "UtxoCommitmentSchemeType: Debug") +)] +pub struct UtxoCommitmentScheme(UtxoCommitmentSchemeType) +where + Poseidon5: poseidon::Specification; + +impl Encode for UtxoCommitmentScheme { + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.0.encode(writer) + } +} + +impl Decode for UtxoCommitmentScheme { + type Error = as Decode>::Error; + + #[inline] + fn decode(reader: R) -> Result> + where + R: Read, + { + Ok(Self(Decode::decode(reader)?)) + } +} + +impl Sample for UtxoCommitmentScheme { + #[inline] + fn sample(distribution: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self(rng.sample(distribution)) + } +} + +impl Constant for UtxoCommitmentScheme { + type Type = UtxoCommitmentScheme; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self(this.0.as_constant(compiler)) + } +} + +impl protocol::UtxoCommitmentScheme for UtxoCommitmentScheme { + type AssetId = AssetId; + type AssetValue = AssetValue; + type ReceivingKey = ReceivingKey; + type Randomness = Fp; + type Commitment = Fp; + + #[inline] + fn commit( + &self, + randomness: &Self::Randomness, + asset_id: &Self::AssetId, + asset_value: &Self::AssetValue, + receiving_key: &Self::ReceivingKey, + compiler: &mut (), + ) -> Self::Commitment { + self.0.hash( + [ + randomness, + asset_id, + &Fp((*asset_value).into()), + &Fp(receiving_key.0.x), + &Fp(receiving_key.0.y), + ], + compiler, + ) + } +} + +impl protocol::UtxoCommitmentScheme for UtxoCommitmentScheme { + type AssetId = AssetIdVar; + type AssetValue = AssetValueVar; + type ReceivingKey = ReceivingKeyVar; + type Randomness = FpVar; + type Commitment = FpVar; + + #[inline] + fn commit( + &self, + randomness: &Self::Randomness, + asset_id: &Self::AssetId, + asset_value: &Self::AssetValue, + receiving_key: &Self::ReceivingKey, + compiler: &mut Compiler, + ) -> Self::Commitment { + self.0.hash( + [ + randomness, + asset_id, + asset_value.as_ref(), + &receiving_key.0.x, + &receiving_key.0.y, + ], + compiler, + ) + } +} + +/// Viewing Key Derivation Function Domain Tag +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ViewingKeyDerivationFunctionDomainTag; + +impl poseidon::hash::DomainTag for ViewingKeyDerivationFunctionDomainTag { + #[inline] + fn domain_tag() -> ::ParameterField { + Fp(0u8.into()) // FIXME: Use a real domain tag + } +} + +impl Constant for ViewingKeyDerivationFunctionDomainTag { + type Type = Self; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self + } +} + +/// Viewing Key Derivation Function Type +type ViewingKeyDerivationFunctionType = + Hasher; + +/// Viewing Key Derivation Function +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "ViewingKeyDerivationFunctionType: Clone"), + Copy(bound = "ViewingKeyDerivationFunctionType: Copy"), + Debug(bound = "ViewingKeyDerivationFunctionType: Debug"), + Default(bound = "ViewingKeyDerivationFunctionType: Default"), + Eq(bound = "ViewingKeyDerivationFunctionType: Eq"), + Hash(bound = "ViewingKeyDerivationFunctionType: core::hash::Hash"), + PartialEq(bound = "ViewingKeyDerivationFunctionType: PartialEq") +)] +pub struct ViewingKeyDerivationFunction(ViewingKeyDerivationFunctionType) +where + Poseidon2: poseidon::Specification; + +impl Encode for ViewingKeyDerivationFunction { + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.0.encode(writer) + } +} + +impl Decode for ViewingKeyDerivationFunction { + type Error = as Decode>::Error; + + #[inline] + fn decode(reader: R) -> Result> + where + R: Read, + { + Ok(Self(Decode::decode(reader)?)) + } +} + +impl Sample for ViewingKeyDerivationFunction { + #[inline] + fn sample(distribution: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self(rng.sample(distribution)) + } +} + +impl Constant for ViewingKeyDerivationFunction { + type Type = ViewingKeyDerivationFunction; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self(this.0.as_constant(compiler)) + } +} + +impl protocol::ViewingKeyDerivationFunction for ViewingKeyDerivationFunction { + type ProofAuthorizationKey = ProofAuthorizationKey; + type ViewingKey = ViewingKey; + + #[inline] + fn viewing_key( + &self, + proof_authorization_key: &Self::ProofAuthorizationKey, + compiler: &mut (), + ) -> Self::ViewingKey { + Fp(rem_mod_prime::( + self.0 + .hash( + [ + &Fp(proof_authorization_key.0.x), + &Fp(proof_authorization_key.0.y), + ], + compiler, + ) + .0, + )) + } +} + +impl protocol::ViewingKeyDerivationFunction for ViewingKeyDerivationFunction { + type ProofAuthorizationKey = ProofAuthorizationKeyVar; + type ViewingKey = ViewingKeyVar; + + #[inline] + fn viewing_key( + &self, + proof_authorization_key: &Self::ProofAuthorizationKey, + compiler: &mut Compiler, + ) -> Self::ViewingKey { + ScalarVar::new(self.0.hash( + [&proof_authorization_key.0.x, &proof_authorization_key.0.y], + compiler, + )) + } +} + +/// Incoming Encryption Scheme Converter +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct IncomingEncryptionSchemeConverter(PhantomData); + +impl encryption::HeaderType for IncomingEncryptionSchemeConverter { + type Header = encryption::EmptyHeader; +} + +impl encryption::HeaderType for IncomingEncryptionSchemeConverter { + type Header = encryption::EmptyHeader; +} + +impl encryption::convert::header::Header for IncomingEncryptionSchemeConverter { + type TargetHeader = encryption::Header; + + #[inline] + fn as_target(source: &Self::Header, _: &mut ()) -> Self::TargetHeader { + let _ = source; + vec![] + } +} + +impl encryption::convert::header::Header for IncomingEncryptionSchemeConverter { + type TargetHeader = encryption::Header>; + + #[inline] + fn as_target(source: &Self::Header, _: &mut Compiler) -> Self::TargetHeader { + let _ = source; + vec![] + } +} + +impl encryption::EncryptionKeyType for IncomingEncryptionSchemeConverter { + type EncryptionKey = Group; +} + +impl encryption::EncryptionKeyType for IncomingEncryptionSchemeConverter { + type EncryptionKey = GroupVar; +} + +impl encryption::convert::key::Encryption for IncomingEncryptionSchemeConverter { + type TargetEncryptionKey = encryption::EncryptionKey; + + #[inline] + fn as_target(source: &Self::EncryptionKey, _: &mut ()) -> Self::TargetEncryptionKey { + vec![Fp(source.0.x), Fp(source.0.y)] + } +} + +impl encryption::convert::key::Encryption + for IncomingEncryptionSchemeConverter +{ + type TargetEncryptionKey = + encryption::EncryptionKey>; + + #[inline] + fn as_target(source: &Self::EncryptionKey, _: &mut Compiler) -> Self::TargetEncryptionKey { + vec![source.0.x.clone(), source.0.y.clone()] + } +} + +impl encryption::DecryptionKeyType for IncomingEncryptionSchemeConverter { + type DecryptionKey = Group; +} + +impl encryption::DecryptionKeyType for IncomingEncryptionSchemeConverter { + type DecryptionKey = GroupVar; +} + +impl encryption::convert::key::Decryption for IncomingEncryptionSchemeConverter { + type TargetDecryptionKey = encryption::DecryptionKey; + + #[inline] + fn as_target(source: &Self::DecryptionKey, _: &mut ()) -> Self::TargetDecryptionKey { + vec![Fp(source.0.x), Fp(source.0.y)] + } +} + +impl encryption::convert::key::Decryption + for IncomingEncryptionSchemeConverter +{ + type TargetDecryptionKey = + encryption::DecryptionKey>; + + #[inline] + fn as_target(source: &Self::DecryptionKey, _: &mut Compiler) -> Self::TargetDecryptionKey { + vec![source.0.x.clone(), source.0.y.clone()] + } +} + +impl encryption::PlaintextType for IncomingEncryptionSchemeConverter { + type Plaintext = protocol::IncomingPlaintext; +} + +impl encryption::PlaintextType for IncomingEncryptionSchemeConverter { + type Plaintext = protocol::IncomingPlaintext, Compiler>; +} + +impl encryption::convert::plaintext::Forward for IncomingEncryptionSchemeConverter { + type TargetPlaintext = encryption::Plaintext; + + #[inline] + fn as_target(source: &Self::Plaintext, _: &mut ()) -> Self::TargetPlaintext { + BlockArray( + [poseidon::encryption::PlaintextBlock( + vec![ + source.utxo_commitment_randomness, + source.asset.id, + Fp(source.asset.value.into()), + ] + .into(), + )] + .into(), + ) + } +} + +impl encryption::convert::plaintext::Forward + for IncomingEncryptionSchemeConverter +{ + type TargetPlaintext = encryption::Plaintext>; + + #[inline] + fn as_target(source: &Self::Plaintext, _: &mut Compiler) -> Self::TargetPlaintext { + BlockArray( + [poseidon::encryption::PlaintextBlock( + vec![ + source.utxo_commitment_randomness.clone(), + source.asset.id.clone(), + source.asset.value.as_ref().clone(), + ] + .into(), + )] + .into(), + ) + } +} + +impl encryption::DecryptedPlaintextType for IncomingEncryptionSchemeConverter { + type DecryptedPlaintext = Option<::Plaintext>; +} + +impl encryption::convert::plaintext::Reverse for IncomingEncryptionSchemeConverter { + type TargetDecryptedPlaintext = + encryption::DecryptedPlaintext; + + #[inline] + fn into_source(target: Self::TargetDecryptedPlaintext, _: &mut ()) -> Self::DecryptedPlaintext { + if target.0 && target.1.len() == 1 { + let block = &target.1[0].0; + if block.len() == 3 { + Some(protocol::IncomingPlaintext::new( + Fp(block[0].0), + Asset::new(Fp(block[1].0), try_into_u128(block[2].0)?), + )) + } else { + None + } + } else { + None + } + } +} + +impl Constant for IncomingEncryptionSchemeConverter { + type Type = IncomingEncryptionSchemeConverter; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self::default() + } +} + +/// Incoming Poseidon Encryption Scheme +pub type IncomingPoseidonEncryptionScheme = + poseidon::encryption::FixedDuplexer<1, Poseidon3, COM>; + +/// Incoming Base Encryption Scheme +pub type IncomingBaseEncryptionScheme = encryption::convert::key::Converter< + encryption::convert::header::Converter< + encryption::convert::plaintext::Converter< + IncomingPoseidonEncryptionScheme, + IncomingEncryptionSchemeConverter, + >, + IncomingEncryptionSchemeConverter, + >, + IncomingEncryptionSchemeConverter, +>; + +/// AES Plaintext Size +pub const AES_PLAINTEXT_SIZE: usize = 80; + +/// AES Ciphertext Size +pub const AES_CIPHERTEXT_SIZE: usize = AES_PLAINTEXT_SIZE + 16; + +/// AES +pub type AES = aes::FixedNonceAesGcm; + +/// Incoming AES Encryption Scheme +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct IncomingAESEncryptionScheme(PhantomData); + +impl Constant for IncomingAESEncryptionScheme { + type Type = IncomingAESEncryptionScheme; + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Default::default() + } +} + +impl Sample for IncomingAESEncryptionScheme { + #[inline] + fn sample(distribution: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + let _ = (distribution, rng); + Default::default() + } +} + +impl Decode for IncomingAESEncryptionScheme { + type Error = Infallible; + + #[inline] + fn decode(reader: R) -> Result> + where + R: Read, + { + let _ = reader; + Ok(Default::default()) + } +} + +impl Encode for IncomingAESEncryptionScheme { + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: Write, + { + let _ = writer; + Ok(()) + } +} + +impl encryption::HeaderType for IncomingAESEncryptionScheme { + type Header = ::Header; +} + +impl encryption::EncryptionKeyType for IncomingAESEncryptionScheme { + type EncryptionKey = ::EncryptionKey; +} + +impl encryption::DecryptionKeyType for IncomingAESEncryptionScheme { + type DecryptionKey = ::DecryptionKey; +} + +impl encryption::PlaintextType for IncomingAESEncryptionScheme { + type Plaintext = ::Plaintext; +} + +impl encryption::DecryptedPlaintextType for IncomingAESEncryptionScheme { + type DecryptedPlaintext = ::DecryptedPlaintext; +} + +impl encryption::CiphertextType for IncomingAESEncryptionScheme { + type Ciphertext = ::Ciphertext; +} + +impl encryption::RandomnessType for IncomingAESEncryptionScheme { + type Randomness = ::Randomness; +} + +impl encryption::Encrypt for IncomingAESEncryptionScheme { + fn encrypt( + &self, + encryption_key: &Self::EncryptionKey, + randomness: &Self::Randomness, + header: &Self::Header, + plaintext: &Self::Plaintext, + compiler: &mut (), + ) -> Self::Ciphertext { + let aes = AES::default(); + aes.encrypt(encryption_key, randomness, header, plaintext, compiler) + } +} + +impl encryption::Decrypt for IncomingAESEncryptionScheme { + fn decrypt( + &self, + decryption_key: &Self::DecryptionKey, + header: &Self::Header, + ciphertext: &Self::Ciphertext, + compiler: &mut (), + ) -> Self::DecryptedPlaintext { + let aes = AES::default(); + aes.decrypt(decryption_key, header, ciphertext, compiler) + } +} + +/// Incoming AES Converter +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct IncomingAESConverter(PhantomData); + +impl encryption::HeaderType for IncomingAESConverter { + type Header = encryption::EmptyHeader; +} + +impl encryption::convert::header::Header for IncomingAESConverter { + type TargetHeader = encryption::Header>; + + #[inline] + fn as_target(source: &Self::Header, _: &mut COM) -> Self::TargetHeader { + let _ = source; + } +} + +impl encryption::EncryptionKeyType for IncomingAESConverter { + type EncryptionKey = Group; +} + +impl encryption::EncryptionKeyType for IncomingAESConverter { + type EncryptionKey = GroupVar; +} + +impl encryption::convert::key::Encryption for IncomingAESConverter { + type TargetEncryptionKey = encryption::EncryptionKey; + + #[inline] + fn as_target(source: &Self::EncryptionKey, _: &mut ()) -> Self::TargetEncryptionKey { + let key = source.to_vec(); + let mut hasher = Blake2s256::new(); + Digest::update(&mut hasher, key); + hasher.finalize().into() + } +} + +impl encryption::convert::key::Encryption for IncomingAESConverter { + type TargetEncryptionKey = encryption::EncryptionKey>; + + #[inline] + fn as_target(_: &Self::EncryptionKey, _: &mut Compiler) -> Self::TargetEncryptionKey { + // TODO: Remove the requirement to implement this trait. + unimplemented!( + "ECLAIR Compiler requires this implementation to exist but it is never called." + ) + } +} + +impl encryption::DecryptionKeyType for IncomingAESConverter { + type DecryptionKey = Group; +} + +impl encryption::DecryptionKeyType for IncomingAESConverter { + type DecryptionKey = GroupVar; +} + +impl encryption::convert::key::Decryption for IncomingAESConverter { + type TargetDecryptionKey = encryption::DecryptionKey; + + #[inline] + fn as_target(source: &Self::DecryptionKey, _: &mut ()) -> Self::TargetDecryptionKey { + let key = source.to_vec(); + let mut hasher = Blake2s256::new(); + Digest::update(&mut hasher, key); + hasher.finalize().into() + } +} + +impl encryption::convert::key::Decryption for IncomingAESConverter { + type TargetDecryptionKey = encryption::DecryptionKey>; + + #[inline] + fn as_target(_: &Self::DecryptionKey, _: &mut Compiler) -> Self::TargetDecryptionKey { + // TODO: Remove the requirement to implement this trait. + unimplemented!( + "ECLAIR Compiler requires this implementation to exist but it is never called." + ) + } +} + +impl encryption::PlaintextType for IncomingAESConverter { + type Plaintext = protocol::IncomingPlaintext; +} + +impl encryption::PlaintextType for IncomingAESConverter { + type Plaintext = protocol::IncomingPlaintext, Compiler>; +} + +impl encryption::convert::plaintext::Forward for IncomingAESConverter { + type TargetPlaintext = encryption::Plaintext; + + #[inline] + fn as_target(source: &Self::Plaintext, _: &mut ()) -> Self::TargetPlaintext { + let mut target_plaintext = Vec::::with_capacity(AES_PLAINTEXT_SIZE); + target_plaintext.extend(source.utxo_commitment_randomness.to_vec()); + target_plaintext.extend(source.asset.id.to_vec()); + target_plaintext.extend(source.asset.value.to_le_bytes().to_vec()); + assert_eq!( + target_plaintext.len(), + AES_PLAINTEXT_SIZE, + "Wrong plaintext length: {}. Expected {} bytes", + target_plaintext.len(), + AES_PLAINTEXT_SIZE + ); + Array::from_unchecked(target_plaintext) + } +} + +impl encryption::convert::plaintext::Forward for IncomingAESConverter { + type TargetPlaintext = encryption::Plaintext>; + + #[inline] + fn as_target(_: &Self::Plaintext, _: &mut Compiler) -> Self::TargetPlaintext { + // TODO: Remove the requirement to implement this trait. + unimplemented!( + "ECLAIR Compiler requires this implementation to exist but it is never called." + ) + } +} + +impl encryption::DecryptedPlaintextType for IncomingAESConverter { + type DecryptedPlaintext = Option<::Plaintext>; +} + +impl encryption::convert::plaintext::Reverse for IncomingAESConverter { + type TargetDecryptedPlaintext = encryption::DecryptedPlaintext; + + #[inline] + fn into_source(target: Self::TargetDecryptedPlaintext, _: &mut ()) -> Self::DecryptedPlaintext { + let bytes_vector = target?.0; + let utxo_randomness_bytes = bytes_vector[0..32].to_vec(); + let asset_id_bytes = bytes_vector[32..64].to_vec(); + let asset_value_bytes = + manta_util::Array::::from_vec(bytes_vector[64..80].to_vec()).0; + let utxo_randomness = Fp::::from_vec(utxo_randomness_bytes) + .expect("Error while converting the bytes into a field element."); + let asset_id = Fp::::from_vec(asset_id_bytes) + .expect("Error while converting the bytes into a field element."); + let asset_value = u128::from_le_bytes(asset_value_bytes); + let source_plaintext = protocol::IncomingPlaintext::::new( + utxo_randomness, + asset::Asset { + id: asset_id, + value: asset_value, + }, + ); + Some(source_plaintext) + } +} + +impl Constant for IncomingAESConverter { + type Type = IncomingAESConverter; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self::default() + } +} + +/// Incoming Base AES +pub type IncomingBaseAES = encryption::convert::key::Converter< + encryption::convert::header::Converter< + encryption::convert::plaintext::Converter< + IncomingAESEncryptionScheme, + IncomingAESConverter, + >, + IncomingAESConverter, + >, + IncomingAESConverter, +>; + +/// Utxo Accumulator Item Hash Domain Tag +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct UtxoAccumulatorItemHashDomainTag; + +impl poseidon::hash::DomainTag for UtxoAccumulatorItemHashDomainTag { + #[inline] + fn domain_tag() -> ::ParameterField { + Fp(0u8.into()) // FIXME: Use a real domain tag + } +} + +impl Constant for UtxoAccumulatorItemHashDomainTag { + type Type = Self; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self + } +} + +/// Utxo Accumulator Item Hash Type +type UtxoAccumulatorItemHashType = + Hasher; + +/// Utxo Accumulator Item Hash +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "UtxoAccumulatorItemHashType: Clone"), + Copy(bound = "UtxoAccumulatorItemHashType: Copy"), + Debug(bound = "UtxoAccumulatorItemHashType: Debug"), + Default(bound = "UtxoAccumulatorItemHashType: Default"), + Eq(bound = "UtxoAccumulatorItemHashType: Eq"), + Hash(bound = "UtxoAccumulatorItemHashType: core::hash::Hash"), + PartialEq(bound = "UtxoAccumulatorItemHashType: PartialEq") +)] +pub struct UtxoAccumulatorItemHash(UtxoAccumulatorItemHashType) +where + Poseidon4: poseidon::Specification; + +impl Encode for UtxoAccumulatorItemHash { + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.0.encode(writer) + } +} + +impl Decode for UtxoAccumulatorItemHash { + type Error = as Decode>::Error; + + #[inline] + fn decode(reader: R) -> Result> + where + R: Read, + { + Ok(Self(Decode::decode(reader)?)) + } +} + +impl Sample for UtxoAccumulatorItemHash { + #[inline] + fn sample(distribution: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self(rng.sample(distribution)) + } +} + +impl Constant for UtxoAccumulatorItemHash { + type Type = UtxoAccumulatorItemHash; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self(this.0.as_constant(compiler)) + } +} + +impl ItemHashFunction for UtxoAccumulatorItemHash { + type Item = UtxoAccumulatorItem; + + #[inline] + fn item_hash(&self, value: &Utxo, compiler: &mut ()) -> Self::Item { + self.0.hash( + [ + &Fp(value.is_transparent.into()), + &value.public_asset.id, + &value.public_asset.value.into(), + &value.commitment, + ], + compiler, + ) + } +} + +impl ItemHashFunction for UtxoAccumulatorItemHash { + type Item = UtxoAccumulatorItemVar; + + #[inline] + fn item_hash(&self, value: &UtxoVar, compiler: &mut Compiler) -> Self::Item { + self.0.hash( + [ + &value.is_transparent.clone().into(), + &value.public_asset.id, + value.public_asset.value.as_ref(), + &value.commitment, + ], + compiler, + ) + } +} + +/// Leaf Hash Configuration Type +pub type LeafHash = merkle_tree::IdentityLeafHash; + +/// Leaf Hash Variable Configuration Type +pub type LeafHashVar = merkle_tree::IdentityLeafHash; + +/// Inner Hash Domain Tag +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct InnerHashDomainTag; + +impl poseidon::hash::DomainTag for InnerHashDomainTag { + #[inline] + fn domain_tag() -> ::ParameterField { + Fp(0u8.into()) // FIXME: Use a real domain tag + } +} + +impl Constant for InnerHashDomainTag { + type Type = Self; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self + } +} + +/// Inner Hash Configuration +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct InnerHash(PhantomData); + +impl merkle_tree::InnerHash for InnerHash { + type LeafDigest = UtxoAccumulatorItem; + type Parameters = Hasher; + type Output = Fp; + + #[inline] + fn join( + parameters: &Self::Parameters, + lhs: &Self::Output, + rhs: &Self::Output, + compiler: &mut (), + ) -> Self::Output { + parameters.hash([lhs, rhs], compiler) + } + + #[inline] + fn join_leaves( + parameters: &Self::Parameters, + lhs: &Self::LeafDigest, + rhs: &Self::LeafDigest, + compiler: &mut (), + ) -> Self::Output { + parameters.hash([lhs, rhs], compiler) + } +} + +impl merkle_tree::InnerHash for InnerHash { + type LeafDigest = UtxoAccumulatorItemVar; + type Parameters = Hasher; + type Output = FpVar; + + #[inline] + fn join( + parameters: &Self::Parameters, + lhs: &Self::Output, + rhs: &Self::Output, + compiler: &mut Compiler, + ) -> Self::Output { + parameters.hash([lhs, rhs], compiler) + } + + #[inline] + fn join_leaves( + parameters: &Self::Parameters, + lhs: &Self::LeafDigest, + rhs: &Self::LeafDigest, + compiler: &mut Compiler, + ) -> Self::Output { + parameters.hash([lhs, rhs], compiler) + } +} + +/// Merkle Tree Configuration +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct MerkleTreeConfiguration; + +impl MerkleTreeConfiguration { + /// Width of the Merkle Forest + pub const FOREST_WIDTH: usize = 256; +} + +impl merkle_tree::HashConfiguration for MerkleTreeConfiguration { + type LeafHash = LeafHash; + type InnerHash = InnerHash; +} + +impl merkle_tree::HashConfiguration for MerkleTreeConfiguration { + type LeafHash = LeafHashVar; + type InnerHash = InnerHash; +} + +impl merkle_tree::Configuration for MerkleTreeConfiguration { + const HEIGHT: usize = 20; +} + +impl merkle_tree::Configuration for MerkleTreeConfiguration { + const HEIGHT: usize = 20; +} + +impl Constant for MerkleTreeConfiguration { + type Type = Self; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self + } +} + +/// UTXO Accumulator Model +pub type UtxoAccumulatorModel = merkle_tree::Parameters; + +/// UTXO Accumulator Model Variable +pub type UtxoAccumulatorModelVar = merkle_tree::Parameters; + +impl merkle_tree::forest::Configuration for MerkleTreeConfiguration { + type Index = u8; + + #[inline] + fn tree_index(leaf: &merkle_tree::Leaf) -> Self::Index { + let mut hasher = Blake2sVar::new(1).unwrap(); + hasher.update(b"manta-v1.0.0/merkle-tree-shard-function"); + let mut buffer = Vec::new(); + leaf.0 + .serialize_unchecked(&mut buffer) + .expect("Serializing is not allowed to fail."); + hasher.update(&buffer); + let mut result = [0]; + hasher + .finalize_variable(&mut result) + .expect("Hashing is not allowed to fail."); + result[0] + } +} + +#[cfg(any(feature = "parameters", test))] +impl merkle_tree::test::HashParameterSampling for MerkleTreeConfiguration { + type LeafHashParameterDistribution = (); + type InnerHashParameterDistribution = (); + + #[inline] + fn sample_leaf_hash_parameters( + distribution: Self::LeafHashParameterDistribution, + rng: &mut R, + ) -> merkle_tree::LeafHashParameters + where + R: RngCore + ?Sized, + { + let _ = (distribution, rng); + } + + #[inline] + fn sample_inner_hash_parameters( + distribution: Self::InnerHashParameterDistribution, + rng: &mut R, + ) -> merkle_tree::InnerHashParameters + where + R: RngCore + ?Sized, + { + rng.sample(distribution) + } +} + +/// Nullifier Commitment Scheme Domain Tag +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct NullifierCommitmentSchemeDomainTag; + +impl poseidon::hash::DomainTag for NullifierCommitmentSchemeDomainTag { + #[inline] + fn domain_tag() -> ::ParameterField { + Fp(0u8.into()) // FIXME: Use a real domain tag + } +} + +impl Constant for NullifierCommitmentSchemeDomainTag { + type Type = Self; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self + } +} + +/// Nullifier Commitment Scheme Type +type NullifierCommitmentSchemeType = + Hasher; + +/// Nullifier Commitment Scheme +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "NullifierCommitmentSchemeType: Clone"), + Copy(bound = "NullifierCommitmentSchemeType: Copy"), + Debug(bound = "NullifierCommitmentSchemeType: Debug"), + Default(bound = "NullifierCommitmentSchemeType: Default"), + Eq(bound = "NullifierCommitmentSchemeType: Eq"), + Hash(bound = "NullifierCommitmentSchemeType: core::hash::Hash"), + PartialEq(bound = "NullifierCommitmentSchemeType: PartialEq") +)] +pub struct NullifierCommitmentScheme(NullifierCommitmentSchemeType) +where + Poseidon3: poseidon::Specification; + +impl Encode for NullifierCommitmentScheme { + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.0.encode(writer) + } +} + +impl Decode for NullifierCommitmentScheme { + type Error = as Decode>::Error; + + #[inline] + fn decode(reader: R) -> Result> + where + R: Read, + { + Ok(Self(Decode::decode(reader)?)) + } +} + +impl Sample for NullifierCommitmentScheme { + #[inline] + fn sample(distribution: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self(rng.sample(distribution)) + } +} + +impl Constant for NullifierCommitmentScheme { + type Type = NullifierCommitmentScheme; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self(this.0.as_constant(compiler)) + } +} + +impl protocol::NullifierCommitmentScheme for NullifierCommitmentScheme { + type ProofAuthorizationKey = ProofAuthorizationKey; + type UtxoAccumulatorItem = Fp; + type Commitment = Fp; + + #[inline] + fn commit( + &self, + proof_authorization_key: &Self::ProofAuthorizationKey, + item: &Self::UtxoAccumulatorItem, + compiler: &mut (), + ) -> Self::Commitment { + self.0.hash( + [ + &Fp(proof_authorization_key.0.x), + &Fp(proof_authorization_key.0.y), + item, + ], + compiler, + ) + } +} + +impl protocol::NullifierCommitmentScheme for NullifierCommitmentScheme { + type ProofAuthorizationKey = ProofAuthorizationKeyVar; + type UtxoAccumulatorItem = FpVar; + type Commitment = FpVar; + + #[inline] + fn commit( + &self, + proof_authorization_key: &Self::ProofAuthorizationKey, + item: &Self::UtxoAccumulatorItem, + compiler: &mut Compiler, + ) -> Self::Commitment { + self.0.hash( + [ + &proof_authorization_key.0.x, + &proof_authorization_key.0.y, + item, + ], + compiler, + ) + } +} + +/// Outgoing AES Plaintext Size +pub const OUT_AES_PLAINTEXT_SIZE: usize = 48; + +/// Outgoing AES Ciphertext Size +pub const OUT_AES_CIPHERTEXT_SIZE: usize = OUT_AES_PLAINTEXT_SIZE + 16; + +/// Outgoing AES +pub type OutAes = aes::FixedNonceAesGcm; + +/// Outgoing AES Encryption Scheme +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct OutgoingAESEncryptionScheme(PhantomData); + +impl Constant for OutgoingAESEncryptionScheme { + type Type = OutgoingAESEncryptionScheme; + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Default::default() + } +} + +impl Sample for OutgoingAESEncryptionScheme { + #[inline] + fn sample(distribution: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + let _ = (distribution, rng); + Default::default() + } +} + +impl Decode for OutgoingAESEncryptionScheme { + type Error = Infallible; + + #[inline] + fn decode(reader: R) -> Result> + where + R: Read, + { + let _ = reader; + Ok(Default::default()) + } +} + +impl Encode for OutgoingAESEncryptionScheme { + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: Write, + { + let _ = writer; + Ok(()) + } +} + +impl encryption::HeaderType for OutgoingAESEncryptionScheme { + type Header = ::Header; +} + +impl encryption::EncryptionKeyType for OutgoingAESEncryptionScheme { + type EncryptionKey = ::EncryptionKey; +} + +impl encryption::DecryptionKeyType for OutgoingAESEncryptionScheme { + type DecryptionKey = ::DecryptionKey; +} + +impl encryption::PlaintextType for OutgoingAESEncryptionScheme { + type Plaintext = ::Plaintext; +} + +impl encryption::DecryptedPlaintextType for OutgoingAESEncryptionScheme { + type DecryptedPlaintext = ::DecryptedPlaintext; +} + +impl encryption::CiphertextType for OutgoingAESEncryptionScheme { + type Ciphertext = ::Ciphertext; +} + +impl encryption::RandomnessType for OutgoingAESEncryptionScheme { + type Randomness = ::Randomness; +} + +impl encryption::Encrypt for OutgoingAESEncryptionScheme { + fn encrypt( + &self, + encryption_key: &Self::EncryptionKey, + randomness: &Self::Randomness, + header: &Self::Header, + plaintext: &Self::Plaintext, + compiler: &mut (), + ) -> Self::Ciphertext { + let aes = OutAes::default(); + aes.encrypt(encryption_key, randomness, header, plaintext, compiler) + } +} + +impl encryption::Decrypt for OutgoingAESEncryptionScheme { + fn decrypt( + &self, + decryption_key: &Self::DecryptionKey, + header: &Self::Header, + ciphertext: &Self::Ciphertext, + compiler: &mut (), + ) -> Self::DecryptedPlaintext { + let aes = OutAes::default(); + aes.decrypt(decryption_key, header, ciphertext, compiler) + } +} + +/// Outgoing AES Converter +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct OutgoingAESConverter(PhantomData); + +impl encryption::HeaderType for OutgoingAESConverter { + type Header = encryption::EmptyHeader; +} + +impl encryption::convert::header::Header for OutgoingAESConverter { + type TargetHeader = encryption::Header>; + + #[inline] + fn as_target(source: &Self::Header, _: &mut COM) -> Self::TargetHeader { + let _ = source; + } +} + +impl encryption::EncryptionKeyType for OutgoingAESConverter { + type EncryptionKey = Group; +} + +impl encryption::EncryptionKeyType for OutgoingAESConverter { + type EncryptionKey = GroupVar; +} + +impl encryption::convert::key::Encryption for OutgoingAESConverter { + type TargetEncryptionKey = encryption::EncryptionKey; + + #[inline] + fn as_target(source: &Self::EncryptionKey, _: &mut ()) -> Self::TargetEncryptionKey { + let key = source.to_vec(); + let mut hasher = Blake2s256::new(); + Digest::update(&mut hasher, key); + hasher.finalize().into() + } +} + +impl encryption::convert::key::Encryption for OutgoingAESConverter { + type TargetEncryptionKey = encryption::EncryptionKey>; + + #[inline] + fn as_target(_: &Self::EncryptionKey, _: &mut Compiler) -> Self::TargetEncryptionKey { + // TODO: Remove the requirement to implement this trait. + unimplemented!( + "ECLAIR Compiler requires this implementation to exist but it is never called." + ) + } +} + +impl encryption::DecryptionKeyType for OutgoingAESConverter { + type DecryptionKey = Group; +} + +impl encryption::DecryptionKeyType for OutgoingAESConverter { + type DecryptionKey = GroupVar; +} + +impl encryption::convert::key::Decryption for OutgoingAESConverter { + type TargetDecryptionKey = encryption::DecryptionKey; + + #[inline] + fn as_target(source: &Self::DecryptionKey, _: &mut ()) -> Self::TargetDecryptionKey { + let key = source.to_vec(); + let mut hasher = Blake2s256::new(); + Digest::update(&mut hasher, key); + hasher.finalize().into() + } +} + +impl encryption::convert::key::Decryption for OutgoingAESConverter { + type TargetDecryptionKey = encryption::DecryptionKey>; + + #[inline] + fn as_target(_: &Self::DecryptionKey, _: &mut Compiler) -> Self::TargetDecryptionKey { + // TODO: Remove the requirement to implement this trait. + unimplemented!( + "ECLAIR Compiler requires this implementation to exist but it is never called." + ) + } +} + +impl encryption::PlaintextType for OutgoingAESConverter { + type Plaintext = Asset; +} + +impl encryption::PlaintextType for OutgoingAESConverter { + type Plaintext = Asset; +} + +impl encryption::convert::plaintext::Forward for OutgoingAESConverter { + type TargetPlaintext = encryption::Plaintext; + + #[inline] + fn as_target(source: &Self::Plaintext, _: &mut ()) -> Self::TargetPlaintext { + let mut target_plaintext = Vec::::with_capacity(OUT_AES_PLAINTEXT_SIZE); + target_plaintext.extend(source.id.to_vec()); + target_plaintext.extend(source.value.to_vec()); + assert_eq!( + target_plaintext.len(), + OUT_AES_PLAINTEXT_SIZE, + "Wrong plaintext length: {}. Expected {} bytes", + target_plaintext.len(), + OUT_AES_PLAINTEXT_SIZE + ); + Array::from_unchecked(target_plaintext) + } +} + +impl encryption::convert::plaintext::Forward for OutgoingAESConverter { + type TargetPlaintext = encryption::Plaintext>; + + #[inline] + fn as_target(_: &Self::Plaintext, _: &mut Compiler) -> Self::TargetPlaintext { + // TODO: Remove the requirement to implement this trait. + unimplemented!( + "ECLAIR Compiler requires this implementation to exist but it is never called." + ) + } +} + +impl encryption::DecryptedPlaintextType for OutgoingAESConverter { + type DecryptedPlaintext = Option<::Plaintext>; +} + +impl encryption::convert::plaintext::Reverse for OutgoingAESConverter { + type TargetDecryptedPlaintext = encryption::DecryptedPlaintext; + + #[inline] + fn into_source(target: Self::TargetDecryptedPlaintext, _: &mut ()) -> Self::DecryptedPlaintext { + let bytes_vector = target?.0; + let asset_id_bytes = bytes_vector[0..32].to_vec(); + let asset_value_bytes = manta_util::Array::::from_vec( + bytes_vector[32..OUT_AES_PLAINTEXT_SIZE].to_vec(), + ) + .0; + let asset_id = Fp::::from_vec(asset_id_bytes) + .expect("Error while converting the bytes into a field element."); + let asset_value = u128::from_le_bytes(asset_value_bytes); + let source_plaintext = asset::Asset { + id: asset_id, + value: asset_value, + }; + Some(source_plaintext) + } +} + +impl Constant for OutgoingAESConverter { + type Type = OutgoingAESConverter; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self::default() + } +} + +/// Outgoing Base AES +pub type OutgoingBaseAES = encryption::convert::key::Converter< + encryption::convert::header::Converter< + encryption::convert::plaintext::Converter< + OutgoingAESEncryptionScheme, + OutgoingAESConverter, + >, + OutgoingAESConverter, + >, + OutgoingAESConverter, +>; + +/// Address Partition Function +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct AddressPartitionFunction; + +impl protocol::AddressPartitionFunction for AddressPartitionFunction { + type Address = Address; + type Partition = u8; + + #[inline] + fn partition(&self, address: &Self::Address) -> Self::Partition { + let mut hasher = Blake2sVar::new(1).unwrap(); + hasher.update(b"manta-v1.0.0/address-partition-function"); + let mut buffer = Vec::new(); + address + .receiving_key + .0 + .serialize_unchecked(&mut buffer) + .expect("Serializing is not allowed to fail."); + hasher.update(&buffer); + let mut result = [0]; + hasher + .finalize_variable(&mut result) + .expect("Hashing is not allowed to fail."); + result[0] + } +} + +impl Encode for AddressPartitionFunction { + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: Write, + { + let _ = writer; + Ok(()) + } +} + +impl Decode for AddressPartitionFunction { + type Error = (); + + #[inline] + fn decode(reader: R) -> Result> + where + R: Read, + { + let _ = reader; + Ok(Self) + } +} + +impl Sample for AddressPartitionFunction { + #[inline] + fn sample(distribution: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + let _ = (distribution, rng); + Self + } +} + +/// Schnorr Hash Function +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct SchnorrHashFunction; + +impl hash::security::PreimageResistance for SchnorrHashFunction {} + +impl schnorr::HashFunction for SchnorrHashFunction { + type Scalar = EmbeddedScalar; + type Group = Group; + type Message = Vec; + + #[inline] + fn hash( + &self, + verifying_key: &Group, + nonce_point: &Group, + message: &Self::Message, + _: &mut (), + ) -> EmbeddedScalar { + let mut hasher = Blake2s256::new(); + Digest::update(&mut hasher, b"manta-pay/1.0.0/Schnorr-hash"); + Digest::update( + &mut hasher, + affine_point_as_bytes::(&verifying_key.0), + ); + Digest::update( + &mut hasher, + affine_point_as_bytes::(&nonce_point.0), + ); + Digest::update(&mut hasher, message); + let bytes: [u8; 32] = hasher.finalize().into(); + Fp(EmbeddedScalarField::from_le_bytes_mod_order(&bytes)) + } +} + +impl Encode for SchnorrHashFunction { + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: Write, + { + let _ = writer; + Ok(()) + } +} + +impl Decode for SchnorrHashFunction { + type Error = (); + + #[inline] + fn decode(reader: R) -> Result> + where + R: Read, + { + let _ = reader; + Ok(Self) + } +} + +impl Sample for SchnorrHashFunction { + #[inline] + fn sample(distribution: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + let _ = (distribution, rng); + Self + } +} + +/// MantaPay Configuration +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Config(PhantomData); + +impl Constant for Config { + type Type = Config; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self::default() + } +} + +impl protocol::BaseConfiguration for Config { + type Bool = bool; + type AssetId = AssetId; + type AssetValue = AssetValue; + type Scalar = EmbeddedScalar; + type Group = Group; + type GroupGenerator = GroupGenerator; + type UtxoCommitmentScheme = UtxoCommitmentScheme; + type ViewingKeyDerivationFunction = ViewingKeyDerivationFunction; + type IncomingHeader = EmptyHeader; + type IncomingCiphertext = + ::Ciphertext; + type IncomingBaseEncryptionScheme = IncomingBaseEncryptionScheme; + type LightIncomingHeader = EmptyHeader; + type LightIncomingBaseEncryptionScheme = IncomingBaseAES; + type LightIncomingCiphertext = + ::Ciphertext; + type UtxoAccumulatorItemHash = UtxoAccumulatorItemHash; + type UtxoAccumulatorModel = UtxoAccumulatorModel; + type NullifierCommitmentScheme = NullifierCommitmentScheme; + type OutgoingHeader = EmptyHeader; + type OutgoingCiphertext = + ::Ciphertext; + type OutgoingBaseEncryptionScheme = OutgoingBaseAES; +} + +impl protocol::BaseConfiguration for Config { + type Bool = Boolean; + type AssetId = AssetIdVar; + type AssetValue = AssetValueVar; + type Scalar = EmbeddedScalarVar; + type Group = GroupVar; + type GroupGenerator = GroupGeneratorVar; + type UtxoCommitmentScheme = UtxoCommitmentScheme; + type ViewingKeyDerivationFunction = ViewingKeyDerivationFunction; + type IncomingHeader = EmptyHeader; + type IncomingCiphertext = + ::Ciphertext; + type IncomingBaseEncryptionScheme = IncomingBaseEncryptionScheme; + type LightIncomingHeader = EmptyHeader; + type LightIncomingCiphertext = + ::Ciphertext; + type LightIncomingBaseEncryptionScheme = + encryption::UnsafeNoEncrypt, Compiler>; + type UtxoAccumulatorItemHash = UtxoAccumulatorItemHash; + type UtxoAccumulatorModel = UtxoAccumulatorModelVar; + type NullifierCommitmentScheme = NullifierCommitmentScheme; + type OutgoingHeader = EmptyHeader; + type OutgoingCiphertext = + ::Ciphertext; + type OutgoingBaseEncryptionScheme = + encryption::UnsafeNoEncrypt, Compiler>; +} + +impl protocol::Configuration for Config { + type AddressPartitionFunction = AddressPartitionFunction; + type SchnorrHashFunction = SchnorrHashFunction; +} + +/// Checkpoint +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Checkpoint { + /// Receiver Index + pub receiver_index: Array, + + /// Sender Index + pub sender_index: usize, +} + +impl Checkpoint { + /// Builds a new [`Checkpoint`] from `receiver_index` and `sender_index`. + #[inline] + pub fn new( + receiver_index: Array, + sender_index: usize, + ) -> Self { + Self { + receiver_index, + sender_index, + } + } +} + +impl Default for Checkpoint { + #[inline] + fn default() -> Self { + Self::new([0; MerkleTreeConfiguration::FOREST_WIDTH].into(), 0) + } +} + +impl From for Checkpoint { + #[inline] + fn from(checkpoint: RawCheckpoint) -> Self { + Self::new( + checkpoint.receiver_index.map(|i| i as usize).into(), + checkpoint.sender_index as usize, + ) + } +} + +impl ledger::Checkpoint for Checkpoint {} + +/// Raw Checkpoint for Encoding and Decoding +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct RawCheckpoint { + /// Receiver Index + pub receiver_index: [u64; MerkleTreeConfiguration::FOREST_WIDTH], + + /// Sender Index + pub sender_index: u64, +} + +impl RawCheckpoint { + /// Builds a new [`RawCheckpoint`] from `receiver_index` and `sender_index`. + #[inline] + pub fn new( + receiver_index: [u64; MerkleTreeConfiguration::FOREST_WIDTH], + sender_index: u64, + ) -> Self { + Self { + receiver_index, + sender_index, + } + } +} + +impl Default for RawCheckpoint { + #[inline] + fn default() -> Self { + Self::new([0; MerkleTreeConfiguration::FOREST_WIDTH], 0) + } +} + +impl From for RawCheckpoint { + #[inline] + fn from(checkpoint: Checkpoint) -> Self { + Self::new( + (*checkpoint.receiver_index).map(|i| i as u64), + checkpoint.sender_index as u64, + ) + } +} + +/// Test +#[cfg(test)] +pub mod test { + use crate::config::{ + utxo::{ + Config, IncomingBaseAES, IncomingBaseEncryptionScheme, OutgoingBaseAES, + AES_CIPHERTEXT_SIZE, OUT_AES_CIPHERTEXT_SIZE, + }, + ConstraintField, EmbeddedScalar, Group, + }; + use manta_accounting::{ + asset, + transfer::utxo::{ + protocol::{ + self, AddressPartitionFunction, UtxoCommitmentScheme, ViewingKeyDerivationFunction, + Visibility, + }, + UtxoReconstruct, + }, + }; + use manta_crypto::{ + algebra::{HasGenerator, ScalarMul}, + arkworks::constraint::fp::Fp, + encryption::{Decrypt, EmptyHeader, Encrypt}, + rand::{OsRng, Sample}, + }; + + /// Checks that encryption of light incoming notes is well-executed for [`Config`]. + #[test] + fn check_encryption_light_incoming_notes() { + let mut rng = OsRng; + let encryption_key = Group::gen(&mut rng); + let header = EmptyHeader::default(); + let base_aes = IncomingBaseAES::default(); + let utxo_commitment_randomness = Fp::::gen(&mut rng); + let asset_id = Fp::::gen(&mut rng); + let asset_value = u128::gen(&mut rng); + let plaintext = protocol::IncomingPlaintext::::new( + utxo_commitment_randomness, + asset::Asset { + id: asset_id, + value: asset_value, + }, + ); + let ciphertext = base_aes.encrypt(&encryption_key, &(), &header, &plaintext, &mut ()); + assert_eq!( + AES_CIPHERTEXT_SIZE, + ciphertext.len(), + "Ciphertext length doesn't match, should be {} but is {}", + AES_CIPHERTEXT_SIZE, + ciphertext.len() + ); + let decrypted_ciphertext = base_aes + .decrypt(&encryption_key, &header, &ciphertext, &mut ()) + .expect("Decryption returned None."); + let new_randomness = decrypted_ciphertext.utxo_commitment_randomness; + let (new_asset_id, new_asset_value) = ( + decrypted_ciphertext.asset.id, + decrypted_ciphertext.asset.value, + ); + assert_eq!( + new_randomness, utxo_commitment_randomness, + "Randomness is not the same" + ); + assert_eq!(new_asset_id, asset_id, "Asset ID is not the same."); + assert_eq!(new_asset_value, asset_value, "Asset value is not the same."); + } + + /// Checks that encryption of light incoming notes is well-executed for [`Config`] with [`Compiler`]. + #[test] + fn check_encryption_poseidon() { + let mut rng = OsRng; + let encryption_key = Group::gen(&mut rng); + let header = EmptyHeader::default(); + let base_poseidon = IncomingBaseEncryptionScheme::gen(&mut rng); + let utxo_commitment_randomness = Fp::::gen(&mut rng); + let asset_id = Fp::::gen(&mut rng); + let asset_value = u128::gen(&mut rng); + let plaintext = protocol::IncomingPlaintext::::new( + utxo_commitment_randomness, + asset::Asset { + id: asset_id, + value: asset_value, + }, + ); + let ciphertext = base_poseidon.encrypt(&encryption_key, &(), &header, &plaintext, &mut ()); + let decrypted_ciphertext = base_poseidon + .decrypt(&encryption_key, &header, &ciphertext, &mut ()) + .expect("Decryption returned None."); + let new_randomness = decrypted_ciphertext.utxo_commitment_randomness; + let (new_asset_id, new_asset_value) = ( + decrypted_ciphertext.asset.id, + decrypted_ciphertext.asset.value, + ); + assert_eq!( + new_randomness, utxo_commitment_randomness, + "Randomness is not the same" + ); + assert_eq!(new_asset_id, asset_id, "Asset ID is not the same."); + assert_eq!(new_asset_value, asset_value, "Asset value is not the same."); + } + + /// Checks UTXOs associated with notes are consistent. + /// Checks that address partition function is working correctly, while opening notes. + #[test] + fn check_note_consistency() { + let mut rng = OsRng; + let parameters = protocol::Parameters::::gen(&mut rng); + let group_generator = parameters.base.group_generator.generator(); + let spending_key = EmbeddedScalar::gen(&mut rng); + let receiving_key = parameters.address_from_spending_key(&spending_key); + let proof_authorization_key = group_generator.scalar_mul(&spending_key, &mut ()); + let decryption_key = parameters + .base + .viewing_key_derivation_function + .viewing_key(&proof_authorization_key, &mut ()); + let utxo_commitment_randomness = Fp::::gen(&mut rng); + let asset_id = Fp::::gen(&mut rng); + let asset_value = u128::gen(&mut rng); + let asset = asset::Asset { + id: asset_id, + value: asset_value, + }; + let is_transparent = bool::gen(&mut rng); + let associated_data = if is_transparent { + Visibility::Transparent + } else { + Visibility::Opaque + }; + let plaintext = + protocol::IncomingPlaintext::::new(utxo_commitment_randomness, asset); + let secret = protocol::MintSecret::::new( + receiving_key.receiving_key, + protocol::IncomingRandomness::::sample(((), ()), &mut rng), + plaintext, + ); + let base_poseidon = parameters.base.incoming_base_encryption_scheme.clone(); + let base_aes = parameters.base.light_incoming_base_encryption_scheme; + let address_partition = parameters + .address_partition_function + .partition(&receiving_key); + let incoming_note = secret.incoming_note(group_generator, &base_poseidon, &mut ()); + let light_incoming_note = secret.light_incoming_note(group_generator, &base_aes, &mut ()); + let full_incoming_note = protocol::FullIncomingNote::::new( + address_partition, + incoming_note, + light_incoming_note, + ); + let utxo_commitment = parameters.base.utxo_commitment_scheme.commit( + &utxo_commitment_randomness, + &associated_data.secret(&asset).id, + &associated_data.secret(&asset).value, + &receiving_key.receiving_key, + &mut (), + ); + let utxo = protocol::Utxo::::new( + is_transparent, + associated_data.public(&asset), + utxo_commitment, + ); + let (identifier, new_asset) = parameters + .open_with_check(&decryption_key, &utxo, full_incoming_note) + .expect("Inconsistent note"); + assert_eq!( + utxo_commitment_randomness, identifier.utxo_commitment_randomness, + "Randomness is not the same." + ); + assert_eq!(asset.value, new_asset.value, "Asset value is not the same."); + assert_eq!(asset.id, new_asset.id, "Asset id is not the same."); + } + + /// Checks encryption is properly executed, i.e. that the ciphertext size is consistent with all the parameters, and that + /// decryption is the inverse of encryption. + #[test] + fn check_outgoing_encryption() { + let mut rng = OsRng; + let encryption_key = Group::gen(&mut rng); + let header = EmptyHeader::default(); + let base_aes = OutgoingBaseAES::default(); + let asset_id = Fp::::gen(&mut rng); + let asset_value = u128::gen(&mut rng); + let plaintext = asset::Asset { + id: asset_id, + value: asset_value, + }; + let ciphertext = base_aes.encrypt(&encryption_key, &(), &header, &plaintext, &mut ()); + assert_eq!( + OUT_AES_CIPHERTEXT_SIZE, + ciphertext.len(), + "Ciphertext length doesn't match, should be {} but is {}", + OUT_AES_CIPHERTEXT_SIZE, + ciphertext.len() + ); + let decrypted_ciphertext = base_aes + .decrypt(&encryption_key, &header, &ciphertext, &mut ()) + .expect("Decryption returned None."); + let (new_asset_id, new_asset_value) = (decrypted_ciphertext.id, decrypted_ciphertext.value); + assert_eq!(new_asset_id, asset_id, "Asset ID is not the same."); + assert_eq!(new_asset_value, asset_value, "Asset value is not the same."); + } +} diff --git a/manta-pay/src/config/utxo_utilities.rs b/manta-pay/src/config/utxo_utilities.rs new file mode 100644 index 000000000..9067946fc --- /dev/null +++ b/manta-pay/src/config/utxo_utilities.rs @@ -0,0 +1,67 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! UTXO Utilities + +use alloc::vec::Vec; +use manta_crypto::{ + arkworks::{ + constraint::SynthesisError, + ff::Field, + r1cs_std::{R1CSVar, ToBytesGadget}, + }, + eclair::num::UnsignedInteger, +}; + +/// From a little endian vector `v` of a certain length, it returns a vector of length `n` after removing some zeroes. +/// +/// # Panics +/// +/// Panics if `vec` length is not at least equal to `n` or if any of it's elements +/// beyond index `n` are non-zero. +pub fn from_little_endian(vec: Vec, n: usize) -> Vec +where + T: manta_crypto::eclair::num::Zero + PartialEq + Clone, +{ + let vec_len = vec.len(); + assert!(vec_len >= n, "Vector length must be at least equal to N"); + assert!( + vec[n..vec_len].iter().all(|z| *z == T::zero(&mut ())), + "Extra elements of `vec` must be zero" + ); + vec[0..n].to_vec() +} + +/// Extracts a vector of bytes from `u`, where `u` implements +/// `ToBytesGadget` +pub fn bytes_from_gadget(u: U) -> Result, SynthesisError> +where + U: ToBytesGadget, + F: Field, +{ + u.to_bytes()?.into_iter().map(|x| x.value()).collect() +} + +/// Extracts a vector of bytes from an [`UnsignedInteger`]. +pub fn bytes_from_unsigned( + u: &UnsignedInteger, +) -> Result, SynthesisError> +where + T: ToBytesGadget, + F: Field, +{ + u.to_bytes()?.into_iter().map(|x| x.value()).collect() +} diff --git a/manta-pay/src/crypto/ecc/arkworks.rs b/manta-pay/src/crypto/ecc/arkworks.rs deleted file mode 100644 index e8868a5b4..000000000 --- a/manta-pay/src/crypto/ecc/arkworks.rs +++ /dev/null @@ -1,651 +0,0 @@ -// Copyright 2019-2022 Manta Network. -// This file is part of manta-rs. -// -// manta-rs is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// manta-rs is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with manta-rs. If not, see . - -//! Arkworks Elliptic Curve Primitives - -use alloc::vec::Vec; -use core::{borrow::Borrow, marker::PhantomData}; -use manta_crypto::{ - algebra, - algebra::FixedBaseScalarMul, - arkworks::{ - algebra::{affine_point_as_bytes, modulus_is_smaller}, - constraint::{conditionally_select, empty, fp::Fp, full, Boolean, FpVar, R1CS}, - ec::{AffineCurve, ProjectiveCurve}, - ff::{BigInteger, Field, PrimeField, Zero as _}, - r1cs_std::{groups::CurveVar, ToBitsGadget}, - relations::ns, - serialize::{ - ArkReader, ArkWriter, CanonicalDeserialize, CanonicalSerialize, SerializationError, - }, - }, - eclair::{ - self, - alloc::{ - mode::{Public, Secret}, - Allocate, Allocator, Constant, Variable, - }, - bool::{Bool, ConditionalSelect}, - cmp, - num::Zero, - }, - rand::{RngCore, Sample}, -}; -use manta_util::{codec, AsBytes}; - -#[cfg(feature = "serde")] -use { - manta_crypto::arkworks::algebra::serialize_group_element, - manta_util::serde::{Deserialize, Serialize}, -}; - -/// Constraint Field Type -type ConstraintField = <::BaseField as Field>::BasePrimeField; - -/// Compiler Type -type Compiler = R1CS>; - -/// Scalar Field Element -pub type Scalar = Fp<::ScalarField>; - -/// Lifts an embedded scalar to an outer scalar. -/// -/// # Safety -/// -/// This can only be used whenver the embedded scalar field is **smaller** than the outer scalar -/// field. -#[inline] -pub fn lift_embedded_scalar(scalar: &Scalar) -> Fp> -where - C: ProjectiveCurve, -{ - assert!( - modulus_is_smaller::>(), - "The modulus of the embedded scalar field is larger than that of the constraint field." - ); - Fp(ConstraintField::::from_le_bytes_mod_order( - &scalar.0.into_repr().to_bytes_le(), - )) -} - -/// Elliptic Curve Group Element -#[cfg_attr( - feature = "serde", - derive(Deserialize, Serialize), - serde( - bound(deserialize = "", serialize = ""), - crate = "manta_util::serde", - deny_unknown_fields, - try_from = "Vec" - ) -)] -#[derive(derivative::Derivative)] -#[derivative(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] -pub struct Group( - /// Affine Point Representation - #[cfg_attr( - feature = "serde", - serde(serialize_with = "serialize_group_element::") - )] - pub(crate) C::Affine, -) -where - C: ProjectiveCurve; - -impl codec::Decode for Group -where - C: ProjectiveCurve, -{ - type Error = SerializationError; - - #[inline] - fn decode(reader: R) -> Result> - where - R: codec::Read, - { - let mut reader = ArkReader::new(reader); - match CanonicalDeserialize::deserialize(&mut reader) { - Ok(value) => reader - .finish() - .map(move |_| Self(value)) - .map_err(codec::DecodeError::Read), - Err(err) => Err(codec::DecodeError::Decode(err)), - } - } -} - -impl codec::Encode for Group -where - C: ProjectiveCurve, -{ - #[inline] - fn encode(&self, writer: W) -> Result<(), W::Error> - where - W: codec::Write, - { - let mut writer = ArkWriter::new(writer); - let _ = self.0.serialize(&mut writer); - writer.finish().map(|_| ()) - } -} - -impl AsBytes for Group -where - C: ProjectiveCurve, -{ - #[inline] - fn as_bytes(&self) -> Vec { - affine_point_as_bytes::(&self.0) - } -} - -impl algebra::Group for Group -where - C: ProjectiveCurve, -{ - #[inline] - fn add(&self, rhs: &Self, _: &mut ()) -> Self { - Self(self.0 + rhs.0) - } -} - -impl algebra::ScalarMul> for Group -where - C: ProjectiveCurve, -{ - type Output = Self; - - #[inline] - fn scalar_mul(&self, scalar: &Scalar, _: &mut ()) -> Self::Output { - Self(self.0.mul(scalar.0.into_repr()).into()) - } -} - -/// Discrete Logarithm Hardness -/// -/// We assume that the DL problem is hard for all `arkworks` implementations of elliptic curves. -impl algebra::security::DiscreteLogarithmHardness for Group where C: ProjectiveCurve {} - -/// Computational Diffie-Hellman Hardness -/// -/// We assume that the CDH problem is hard for all `arkworks` implementations of elliptic curves. -impl algebra::security::ComputationalDiffieHellmanHardness for Group where C: ProjectiveCurve {} - -impl Sample for Group -where - C: ProjectiveCurve, -{ - #[inline] - fn sample(_: (), rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - Self(C::rand(rng).into()) - } -} - -impl TryFrom> for Group -where - C: ProjectiveCurve, -{ - type Error = SerializationError; - - #[inline] - fn try_from(bytes: Vec) -> Result { - CanonicalDeserialize::deserialize(&mut bytes.as_slice()).map(Self) - } -} - -impl ConditionalSelect for Group -where - C: ProjectiveCurve, -{ - #[inline] - fn select(bit: &Bool<()>, true_value: &Self, false_value: &Self, compiler: &mut ()) -> Self { - let _ = compiler; - if *bit { - *true_value - } else { - *false_value - } - } -} - -impl cmp::PartialEq for Group -where - C: ProjectiveCurve, -{ - #[inline] - fn eq(&self, rhs: &Self, compiler: &mut ()) -> Bool<()> { - let _ = compiler; - self == rhs - } -} - -impl Zero for Group -where - C: ProjectiveCurve, -{ - type Verification = bool; - - #[inline] - fn zero(compiler: &mut ()) -> Self { - let _ = compiler; - Self(C::Affine::zero()) - } - - #[inline] - fn is_zero(&self, compiler: &mut ()) -> Self::Verification { - let _ = compiler; - C::Affine::is_zero(&self.0) - } -} - -/// Elliptic Curve Scalar Element Variable -/// -/// # Safety -/// -/// This type can only be used whenever the embedded scalar field is **smaller** than the -/// outer scalar field. -pub struct ScalarVar(pub(crate) FpVar>, PhantomData) -where - C: ProjectiveCurve, - CV: CurveVar>; - -impl ScalarVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - /// Builds a new [`ScalarVar`] from a given `scalar`. - #[inline] - fn new(scalar: FpVar>) -> Self { - Self(scalar, PhantomData) - } -} - -impl algebra::Group> for ScalarVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - #[inline] - fn add(&self, rhs: &Self, compiler: &mut Compiler) -> Self { - let _ = compiler; - Self::new(&self.0 + &rhs.0) - } -} - -impl algebra::Ring> for ScalarVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - #[inline] - fn mul(&self, rhs: &Self, compiler: &mut Compiler) -> Self { - let _ = compiler; - Self::new(&self.0 * &rhs.0) - } -} - -impl Constant> for ScalarVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - type Type = Scalar; - - #[inline] - fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self::new(lift_embedded_scalar::(this).as_constant(compiler)) - } -} - -impl Variable> for ScalarVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - type Type = Scalar; - - #[inline] - fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self::new(lift_embedded_scalar::(this).as_known::(compiler)) - } - - #[inline] - fn new_unknown(compiler: &mut Compiler) -> Self { - Self::new(compiler.allocate_unknown::()) - } -} - -impl Variable> for ScalarVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - type Type = Scalar; - - #[inline] - fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self::new(lift_embedded_scalar::(this).as_known::(compiler)) - } - - #[inline] - fn new_unknown(compiler: &mut Compiler) -> Self { - Self::new(compiler.allocate_unknown::()) - } -} - -/// Elliptic Curve Group Element Variable -#[derive(derivative::Derivative)] -#[derivative(Clone)] -pub struct GroupVar(pub(crate) CV, PhantomData) -where - C: ProjectiveCurve, - CV: CurveVar>; - -impl GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - /// Builds a new [`GroupVar`] from a given `point`. - #[inline] - fn new(point: CV) -> Self { - Self(point, PhantomData) - } -} - -impl algebra::Group> for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - #[inline] - fn add(&self, rhs: &Self, compiler: &mut Compiler) -> Self { - let _ = compiler; - let mut result = self.0.clone(); - result += &rhs.0; - Self::new(result) - } - - #[inline] - fn double_assign(&mut self, compiler: &mut Compiler) -> &mut Self { - let _ = compiler; - self.0 - .double_in_place() - .expect("Doubling is not allowed to fail."); - self - } -} - -impl algebra::ScalarMul, Compiler> for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - type Output = Self; - - #[inline] - fn scalar_mul(&self, scalar: &ScalarVar, compiler: &mut Compiler) -> Self::Output { - let _ = compiler; - Self::new( - self.0 - .scalar_mul_le( - scalar - .0 - .to_bits_le() - .expect("Bit decomposition is not allowed to fail.") - .iter(), - ) - .expect("Scalar multiplication is not allowed to fail."), - ) - } -} - -/// Discrete Logarithm Hardness -/// -/// We assume that the DL problem is hard for all `arkworks` implementations of elliptic curves. -impl algebra::security::DiscreteLogarithmHardness for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ -} - -/// Computational Diffie-Hellman Hardness -/// -/// We assume that the CDH problem is hard for all `arkworks` implementations of elliptic curves. -impl algebra::security::ComputationalDiffieHellmanHardness for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ -} - -impl eclair::cmp::PartialEq> for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - #[inline] - fn eq(&self, rhs: &Self, compiler: &mut Compiler) -> Boolean> { - let _ = compiler; - self.0 - .is_eq(&rhs.0) - .expect("Equality checking is not allowed to fail.") - } -} - -impl ConditionalSelect> for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - #[inline] - fn select( - bit: &Bool>, - true_value: &Self, - false_value: &Self, - compiler: &mut Compiler, - ) -> Self { - let _ = compiler; - Self::new(conditionally_select(bit, &true_value.0, &false_value.0)) - } -} - -impl Zero> for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - type Verification = Bool>; - - #[inline] - fn zero(compiler: &mut Compiler) -> Self { - let _ = compiler; - Self::new(CV::zero()) - } - - #[inline] - fn is_zero(&self, compiler: &mut Compiler) -> Self::Verification { - let _ = compiler; - self.0 - .is_zero() - .expect("Comparison with zero is not allowed to fail.") - } -} - -impl Constant> for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - type Type = Group; - - #[inline] - fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self::new( - CV::new_constant( - ns!(compiler.as_ref(), "embedded curve point constant"), - this.0, - ) - .expect("Variable allocation is not allowed to fail."), - ) - } -} - -impl Variable> for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - type Type = Group; - - #[inline] - fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self::new( - CV::new_input( - ns!(compiler.as_ref(), "embedded curve point public input"), - full(this.0), - ) - .expect("Variable allocation is not allowed to fail."), - ) - } - - #[inline] - fn new_unknown(compiler: &mut Compiler) -> Self { - Self::new( - CV::new_input( - ns!(compiler.as_ref(), "embedded curve point public input"), - empty::, - ) - .expect("Variable allocation is not allowed to fail."), - ) - } -} - -impl Variable> for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - type Type = Group; - - #[inline] - fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self::new( - CV::new_witness( - ns!(compiler.as_ref(), "embedded curve point secret witness"), - full(this.0), - ) - .expect("Variable allocation is not allowed to fail."), - ) - } - - #[inline] - fn new_unknown(compiler: &mut Compiler) -> Self { - Self::new( - CV::new_witness( - ns!(compiler.as_ref(), "embedded curve point secret witness"), - empty::, - ) - .expect("Variable allocation is not allowed to fail."), - ) - } -} - -impl FixedBaseScalarMul, Compiler> for GroupVar -where - C: ProjectiveCurve, - CV: CurveVar>, -{ - type Base = Group; - - #[inline] - fn fixed_base_scalar_mul( - precomputed_bases: I, - scalar: &ScalarVar, - compiler: &mut Compiler, - ) -> Self - where - I: IntoIterator, - I::Item: Borrow, - { - let _ = compiler; - let mut result = CV::zero(); - let scalar_bits = scalar - .0 - .to_bits_le() - .expect("Bit decomposition is not allowed to fail."); - for (bit, base) in scalar_bits.into_iter().zip(precomputed_bases.into_iter()) { - result = bit - .select(&(result.clone() + base.borrow().0.into()), &result) - .expect("Conditional select is not allowed to fail. "); - } - Self::new(result) - } -} - -/// Testing Suite -#[cfg(test)] -mod test { - use super::*; - use crate::config::Bls12_381_Edwards; - use manta_crypto::{ - algebra::{test::window_correctness, PrecomputedBaseTable, ScalarMul}, - arkworks::{algebra::scalar_bits, r1cs_std::groups::curves::twisted_edwards::AffineVar}, - constraint::measure::Measure, - eclair::bool::AssertEq, - rand::OsRng, - }; - - /// Checks if the fixed base multiplcation is correct. - #[test] - fn fixed_base_mul_is_correct() { - let mut cs = Compiler::::for_proofs(); - let scalar = Scalar::::gen(&mut OsRng); - let base = Group::::sample((), &mut OsRng); - const SCALAR_BITS: usize = scalar_bits::(); - let precomputed_table = PrecomputedBaseTable::<_, SCALAR_BITS>::from_base(base, &mut ()); - let base_var = - base.as_known::>>(&mut cs); - let scalar_var = - scalar.as_known::>>(&mut cs); - let ctr1 = cs.constraint_count(); - let expected = base_var.scalar_mul(&scalar_var, &mut cs); - let ctr2 = cs.constraint_count(); - let actual = GroupVar::fixed_base_scalar_mul(precomputed_table, &scalar_var, &mut cs); - let ctr3 = cs.constraint_count(); - cs.assert_eq(&expected, &actual); - assert!(cs.is_satisfied()); - println!("variable base mul constraint: {:?}", ctr2 - ctr1); - println!("fixed base mul constraint: {:?}", ctr3 - ctr2); - } - - /// Checks if the windowed multiplication is correct in the native compiler. - #[test] - fn windowed_mul_is_correct() { - window_correctness( - 4, - &Scalar::::gen(&mut OsRng), - Group::::gen(&mut OsRng), - |scalar, _| scalar.0.into_repr().to_bits_be(), - &mut (), - ); - } -} diff --git a/manta-pay/src/crypto/ecc/mod.rs b/manta-pay/src/crypto/ecc/mod.rs deleted file mode 100644 index 353326543..000000000 --- a/manta-pay/src/crypto/ecc/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2019-2022 Manta Network. -// This file is part of manta-rs. -// -// manta-rs is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// manta-rs is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with manta-rs. If not, see . - -//! Elliptic Curve Primitives - -#[cfg(feature = "arkworks")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "arkworks")))] -pub mod arkworks; diff --git a/manta-pay/src/crypto/encryption/mod.rs b/manta-pay/src/crypto/encryption/mod.rs index e20e728f2..3760017b4 100644 --- a/manta-pay/src/crypto/encryption/mod.rs +++ b/manta-pay/src/crypto/encryption/mod.rs @@ -17,34 +17,3 @@ //! Encryption Implementations pub mod aes; - -/// Testing Suite -#[cfg(test)] -mod test { - use crate::config::NoteSymmetricEncryptionScheme; - use manta_crypto::{ - encryption, - rand::{OsRng, Rand}, - }; - - /// Tests if symmetric encryption of [`Note`] decrypts properly. - #[test] - fn note_symmetric_encryption() { - let mut rng = OsRng; - let key = rng.gen(); - encryption::test::correctness::( - &rng.gen(), - &key, - &key, - &(), - &(), - &rng.gen(), - |plaintext, decrypted_plaintext| { - assert_eq!( - plaintext, - &decrypted_plaintext.expect("Unable to decrypt ciphertext.") - ); - }, - ); - } -} diff --git a/manta-pay/src/crypto/mod.rs b/manta-pay/src/crypto/mod.rs index d5d4b6d37..7701f1327 100644 --- a/manta-pay/src/crypto/mod.rs +++ b/manta-pay/src/crypto/mod.rs @@ -16,7 +16,6 @@ //! Manta Pay Cryptographic Primitives Implementations -pub mod ecc; pub mod encryption; pub mod key; pub mod poseidon; diff --git a/manta-pay/src/crypto/poseidon/arkworks.rs b/manta-pay/src/crypto/poseidon/arkworks.rs index a6ee26292..345bccb5d 100644 --- a/manta-pay/src/crypto/poseidon/arkworks.rs +++ b/manta-pay/src/crypto/poseidon/arkworks.rs @@ -229,17 +229,17 @@ where } } -impl BlockElement for FpVar +impl BlockElement> for FpVar where F: PrimeField, { #[inline] - fn add(&self, rhs: &Self, _: &mut ()) -> Self { + fn add(&self, rhs: &Self, _: &mut R1CS) -> Self { self + rhs } #[inline] - fn sub(&self, rhs: &Self, _: &mut ()) -> Self { + fn sub(&self, rhs: &Self, _: &mut R1CS) -> Self { self - rhs } } diff --git a/manta-pay/src/crypto/poseidon/compat.rs b/manta-pay/src/crypto/poseidon/compat.rs deleted file mode 100644 index 31b4d21a4..000000000 --- a/manta-pay/src/crypto/poseidon/compat.rs +++ /dev/null @@ -1,459 +0,0 @@ -// Copyright 2019-2022 Manta Network. -// This file is part of manta-rs. -// -// manta-rs is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// manta-rs is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with manta-rs. If not, see . - -//! Compatibility for Poseidon Hash Implementation - -use alloc::vec::Vec; -use core::{fmt::Debug, hash::Hash, iter, mem}; -use manta_crypto::hash::ArrayHashFunction; -use manta_util::codec::{Decode, DecodeError, Encode, Read, Write}; - -#[cfg(feature = "serde")] -use manta_util::serde::{Deserialize, Serialize}; - -#[cfg(any(feature = "test", test))] -use { - core::iter::repeat, - manta_crypto::rand::{Rand, RngCore, Sample}, -}; - -/// Poseidon Permutation Specification -pub trait Specification { - /// Field Type - type Field; - - /// Number of Full Rounds - /// - /// This is counted twice for the first set of full rounds and then the second set after the - /// partial rounds. - const FULL_ROUNDS: usize; - - /// Number of Partial Rounds - const PARTIAL_ROUNDS: usize; - - /// Returns the additive identity of the field. - fn zero(compiler: &mut COM) -> Self::Field; - - /// Adds two field elements together. - fn add(lhs: &Self::Field, rhs: &Self::Field, compiler: &mut COM) -> Self::Field; - - /// Multiplies two field elements together. - fn mul(lhs: &Self::Field, rhs: &Self::Field, compiler: &mut COM) -> Self::Field; - - /// Adds the `rhs` field element to `self`, storing the value in `self`. - fn add_assign(lhs: &mut Self::Field, rhs: &Self::Field, compiler: &mut COM); - - /// Applies the S-BOX to `point`. - fn apply_sbox(point: &mut Self::Field, compiler: &mut COM); -} - -/// Poseidon State Vector -type State = Vec<>::Field>; - -/// Returns the total number of rounds in a Poseidon permutation. -#[inline] -pub fn rounds() -> usize -where - S: Specification, -{ - 2 * S::FULL_ROUNDS + S::PARTIAL_ROUNDS -} - -/// Poseidon Hash -#[cfg_attr( - feature = "serde", - derive(Deserialize, Serialize), - serde( - bound( - deserialize = "S::Field: Deserialize<'de>", - serialize = "S::Field: Serialize" - ), - crate = "manta_util::serde", - deny_unknown_fields - ) -)] -#[derive(derivative::Derivative)] -#[derivative( - Clone(bound = "S::Field: Clone"), - Debug(bound = "S::Field: Debug"), - Eq(bound = "S::Field: Eq"), - Hash(bound = "S::Field: Hash"), - PartialEq(bound = "S::Field: PartialEq") -)] -pub struct Hasher -where - S: Specification, -{ - /// Additive Round Keys - additive_round_keys: Vec, - - /// MDS Matrix - mds_matrix: Vec, -} - -impl Hasher -where - S: Specification, -{ - /// Width of the State Buffer - pub const WIDTH: usize = ARITY + 1; - - /// Total Number of Rounds - pub const ROUNDS: usize = 2 * S::FULL_ROUNDS + S::PARTIAL_ROUNDS; - - /// Number of Entries in the MDS Matrix - pub const MDS_MATRIX_SIZE: usize = Self::WIDTH * Self::WIDTH; - - /// Total Number of Additive Rounds Keys - pub const ADDITIVE_ROUND_KEYS_COUNT: usize = Self::ROUNDS * Self::WIDTH; - - /// Builds a new [`Hasher`] from `additive_round_keys` and `mds_matrix`. - /// - /// # Panics - /// - /// This method panics if the input vectors are not the correct size for the specified - /// [`Specification`]. - #[inline] - pub fn new(additive_round_keys: Vec, mds_matrix: Vec) -> Self { - assert_eq!( - additive_round_keys.len(), - Self::ADDITIVE_ROUND_KEYS_COUNT, - "Additive Rounds Keys are not the correct size." - ); - assert_eq!( - mds_matrix.len(), - Self::MDS_MATRIX_SIZE, - "MDS Matrix is not the correct size." - ); - Self::new_unchecked(additive_round_keys, mds_matrix) - } - - /// Builds a new [`Hasher`] from `additive_round_keys` and `mds_matrix` without - /// checking their sizes. - #[inline] - fn new_unchecked(additive_round_keys: Vec, mds_matrix: Vec) -> Self { - Self { - additive_round_keys, - mds_matrix, - } - } - - /// Returns the additive keys for the given `round`. - #[inline] - fn additive_keys(&self, round: usize) -> &[S::Field] { - let width = Self::WIDTH; - let start = round * width; - &self.additive_round_keys[start..start + width] - } - - /// Computes the MDS matrix multiplication against the `state`. - #[inline] - fn mds_matrix_multiply(&self, state: &mut State, compiler: &mut COM) { - let width = Self::WIDTH; - let mut next = Vec::with_capacity(width); - for i in 0..width { - #[allow(clippy::needless_collect)] - // NOTE: Clippy is wrong here, we need `&mut` access. - let linear_combination = state - .iter() - .enumerate() - .map(|(j, elem)| S::mul(elem, &self.mds_matrix[width * i + j], compiler)) - .collect::>(); - next.push( - linear_combination - .into_iter() - .reduce(|acc, next| S::add(&acc, &next, compiler)) - .unwrap(), - ); - } - mem::swap(&mut next, state); - } - - /// Computes the first round of the Poseidon permutation from `trapdoor` and `input`. - #[inline] - fn first_round(&self, input: [&S::Field; ARITY], compiler: &mut COM) -> State { - let mut state = Vec::with_capacity(Self::WIDTH); - for (i, point) in iter::once(&S::zero(compiler)).chain(input).enumerate() { - let mut elem = S::add(point, &self.additive_round_keys[i], compiler); - S::apply_sbox(&mut elem, compiler); - state.push(elem); - } - self.mds_matrix_multiply(&mut state, compiler); - state - } - - /// Computes a full round at the given `round` index on the internal permutation `state`. - #[inline] - fn full_round(&self, round: usize, state: &mut State, compiler: &mut COM) { - let keys = self.additive_keys(round); - for (i, elem) in state.iter_mut().enumerate() { - S::add_assign(elem, &keys[i], compiler); - S::apply_sbox(elem, compiler); - } - self.mds_matrix_multiply(state, compiler); - } - - /// Computes a partial round at the given `round` index on the internal permutation `state`. - #[inline] - fn partial_round(&self, round: usize, state: &mut State, compiler: &mut COM) { - let keys = self.additive_keys(round); - for (i, elem) in state.iter_mut().enumerate() { - S::add_assign(elem, &keys[i], compiler); - } - S::apply_sbox(&mut state[0], compiler); - self.mds_matrix_multiply(state, compiler); - } -} - -impl ArrayHashFunction for Hasher -where - S: Specification, -{ - type Input = S::Field; - type Output = S::Field; - - #[inline] - fn hash(&self, input: [&Self::Input; ARITY], compiler: &mut COM) -> Self::Output { - let mut state = self.first_round(input, compiler); - for round in 1..S::FULL_ROUNDS { - self.full_round(round, &mut state, compiler); - } - for round in S::FULL_ROUNDS..(S::FULL_ROUNDS + S::PARTIAL_ROUNDS) { - self.partial_round(round, &mut state, compiler); - } - for round in (S::FULL_ROUNDS + S::PARTIAL_ROUNDS)..(2 * S::FULL_ROUNDS + S::PARTIAL_ROUNDS) - { - self.full_round(round, &mut state, compiler); - } - state.truncate(1); - state.remove(0) - } -} - -#[cfg(any(feature = "test", test))] // NOTE: This is only safe to use in a test. -impl Sample for Hasher -where - D: Clone, - S: Specification, - S::Field: Sample, -{ - /// Samples random Poseidon parameters. - /// - /// # Warning - /// - /// This method samples the individual field elements of the parameters set, instead of - /// producing an actually correct/safe set of additive round keys and MDS matrix. - #[inline] - fn sample(distribution: D, rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - Self { - additive_round_keys: rng - .sample_iter(repeat(distribution.clone()).take(Self::ADDITIVE_ROUND_KEYS_COUNT)) - .collect(), - mds_matrix: rng - .sample_iter(repeat(distribution).take(Self::MDS_MATRIX_SIZE)) - .collect(), - } - } -} - -impl Decode for Hasher -where - S: Specification, - S::Field: Decode, -{ - type Error = ::Error; - - #[inline] - fn decode(mut reader: R) -> Result> - where - R: Read, - { - Ok(Self::new_unchecked( - (0..Self::ADDITIVE_ROUND_KEYS_COUNT) - .map(|_| S::Field::decode(&mut reader)) - .collect::, _>>()?, - (0..Self::MDS_MATRIX_SIZE) - .map(|_| S::Field::decode(&mut reader)) - .collect::, _>>()?, - )) - } -} - -impl Encode for Hasher -where - S: Specification, - S::Field: Encode, -{ - #[inline] - fn encode(&self, mut writer: W) -> Result<(), W::Error> - where - W: Write, - { - for key in &self.additive_round_keys { - key.encode(&mut writer)?; - } - for entry in &self.mds_matrix { - entry.encode(&mut writer)?; - } - Ok(()) - } -} - -/// Poseidon Hash Input Type -pub type Input = - as ArrayHashFunction>::Input; - -/// Poseidon Commitment Output Type -pub type Output = - as ArrayHashFunction>::Output; - -/// Arkworks Backend -#[cfg(feature = "arkworks")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "arkworks")))] -pub mod arkworks { - use manta_crypto::{ - arkworks::{ - constraint::{fp::Fp, FpVar, R1CS}, - ff::{Field, PrimeField}, - r1cs_std::fields::FieldVar, - }, - eclair::alloc::{Allocate, Constant}, - }; - - /// Compiler Type - type Compiler = R1CS<::Field>; - - /// Poseidon Permutation Specification - pub trait Specification { - /// Field Type - type Field: PrimeField; - - /// Number of Full Rounds - /// - /// This is counted twice for the first set of full rounds and then the second set after the - /// partial rounds. - const FULL_ROUNDS: usize; - - /// Number of Partial Rounds - const PARTIAL_ROUNDS: usize; - - /// S-BOX Exponenet - const SBOX_EXPONENT: u64; - } - - impl super::Specification for S - where - S: Specification, - { - type Field = Fp; - - const FULL_ROUNDS: usize = S::FULL_ROUNDS; - const PARTIAL_ROUNDS: usize = S::PARTIAL_ROUNDS; - - #[inline] - fn zero(_: &mut ()) -> Self::Field { - Default::default() - } - - #[inline] - fn add(lhs: &Self::Field, rhs: &Self::Field, _: &mut ()) -> Self::Field { - Fp(lhs.0 + rhs.0) - } - - #[inline] - fn mul(lhs: &Self::Field, rhs: &Self::Field, _: &mut ()) -> Self::Field { - Fp(lhs.0 * rhs.0) - } - - #[inline] - fn add_assign(lhs: &mut Self::Field, rhs: &Self::Field, _: &mut ()) { - lhs.0 += rhs.0; - } - - #[inline] - fn apply_sbox(point: &mut Self::Field, _: &mut ()) { - point.0 = point.0.pow([Self::SBOX_EXPONENT, 0, 0, 0]); - } - } - - impl super::Specification> for S - where - S: Specification, - { - type Field = FpVar; - - const FULL_ROUNDS: usize = S::FULL_ROUNDS; - const PARTIAL_ROUNDS: usize = S::PARTIAL_ROUNDS; - - #[inline] - fn zero(compiler: &mut Compiler) -> Self::Field { - let _ = compiler; - Self::Field::zero() - } - - #[inline] - fn add(lhs: &Self::Field, rhs: &Self::Field, compiler: &mut Compiler) -> Self::Field { - let _ = compiler; - lhs + rhs - } - - #[inline] - fn mul(lhs: &Self::Field, rhs: &Self::Field, compiler: &mut Compiler) -> Self::Field { - let _ = compiler; - lhs * rhs - } - - #[inline] - fn add_assign(lhs: &mut Self::Field, rhs: &Self::Field, compiler: &mut Compiler) { - let _ = compiler; - *lhs += rhs; - } - - #[inline] - fn apply_sbox(point: &mut Self::Field, compiler: &mut Compiler) { - let _ = compiler; - *point = point - .pow_by_constant([Self::SBOX_EXPONENT]) - .expect("Exponentiation is not allowed to fail."); - } - } - - impl Constant> for super::Hasher> - where - S: Specification, - { - type Type = super::Hasher; - - #[inline] - fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { - Self { - additive_round_keys: this - .additive_round_keys - .iter() - .map(|k| k.as_constant(compiler)) - .collect(), - mds_matrix: this - .mds_matrix - .iter() - .map(|k| k.as_constant(compiler)) - .collect(), - } - } - } -} diff --git a/manta-pay/src/crypto/poseidon/encryption.rs b/manta-pay/src/crypto/poseidon/encryption.rs index 887424a70..f2bbc029e 100644 --- a/manta-pay/src/crypto/poseidon/encryption.rs +++ b/manta-pay/src/crypto/poseidon/encryption.rs @@ -39,7 +39,7 @@ use manta_crypto::{ rand::{Rand, RngCore, Sample}, }; use manta_util::{ - codec::{self, Encode}, + codec::{self, Decode, DecodeError, Encode}, vec::padded_chunks_with, BoxArray, }; @@ -122,16 +122,18 @@ where } /// Plaintext Block -/* TODO: #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), - serde(bound( - deserialize = "S::Field: Deserialize<'de>", - serialize = "S::Field: Serialize" - ),) + serde( + bound( + deserialize = "S::Field: Deserialize<'de>", + serialize = "S::Field: Serialize" + ), + crate = "manta_util::serde", + deny_unknown_fields + ) )] -*/ #[derive(derivative::Derivative)] #[derivative( Clone(bound = "S::Field: Clone"), @@ -232,7 +234,6 @@ where } /// Ciphertext Block -/* TODO: #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), @@ -245,7 +246,6 @@ where deny_unknown_fields ) )] -*/ #[derive(derivative::Derivative)] #[derivative( Clone(bound = "S::Field: Clone"), @@ -267,10 +267,12 @@ where #[inline] fn write(&self, state: &mut State, compiler: &mut COM) -> Self::Output { + let mut plaintext = Vec::new(); for (i, elem) in state.iter_mut().skip(1).enumerate() { - *elem = self.0[i].sub(elem, compiler); + plaintext.push(self.0[i].sub(elem, compiler)); + *elem = self.0[i].clone(); } - PlaintextBlock(state.iter().skip(1).cloned().collect()) + PlaintextBlock(plaintext.into()) } } @@ -346,6 +348,15 @@ where } /// Block Array +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound(deserialize = "B: Deserialize<'de>", serialize = "B: Serialize"), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] #[derive(derivative::Derivative)] #[derivative( Clone(bound = "B: Clone"), @@ -454,7 +465,6 @@ pub type FixedPlaintext = BlockArray = BlockArray, N>; /// Authentication Tag -/* TODO: #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), @@ -467,7 +477,6 @@ pub type FixedCiphertext = BlockArray Decode for FixedEncryption +where + S: Specification, + State: Decode, +{ + type Error = as Decode>::Error; + + #[inline] + fn decode(reader: R) -> Result> + where + R: codec::Read, + { + Ok(Self { + initial_state: Decode::decode(reader)?, + }) + } +} + +impl Encode for FixedEncryption +where + S: Specification, + State: Encode, +{ + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: codec::Write, + { + self.initial_state.encode(writer) + } +} + impl Sample for FixedEncryption where S: Specification, diff --git a/manta-pay/src/crypto/poseidon/hash.rs b/manta-pay/src/crypto/poseidon/hash.rs index cff32115d..6f4cd4663 100644 --- a/manta-pay/src/crypto/poseidon/hash.rs +++ b/manta-pay/src/crypto/poseidon/hash.rs @@ -191,14 +191,14 @@ where } } -impl Sample for Hasher +impl Sample for Hasher where S: Specification, - S::ParameterField: Field + FieldGeneration + Sample, + S::ParameterField: Field + FieldGeneration, T: DomainTag, { #[inline] - fn sample(distribution: D, rng: &mut R) -> Self + fn sample(distribution: (), rng: &mut R) -> Self where R: RngCore + ?Sized, { diff --git a/manta-pay/src/crypto/poseidon/mod.rs b/manta-pay/src/crypto/poseidon/mod.rs index e9bc374c6..00cb388be 100644 --- a/manta-pay/src/crypto/poseidon/mod.rs +++ b/manta-pay/src/crypto/poseidon/mod.rs @@ -24,14 +24,13 @@ use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, mem, slice}; use manta_crypto::{ eclair::alloc::{Allocate, Const, Constant}, permutation::PseudorandomPermutation, - rand::{RngCore, Sample}, + rand::{Rand, RngCore, Sample}, }; use manta_util::codec::{Decode, DecodeError, Encode, Read, Write}; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize}; -pub mod compat; pub mod constants; pub mod encryption; pub mod hash; @@ -191,6 +190,69 @@ where } } +impl Constant for State +where + S: Specification + Constant, + S::Field: Constant, + S::Type: Specification>, +{ + type Type = State; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + Self(this.0.as_constant(compiler)) + } +} + +impl Decode for State +where + S: Specification, + S::Field: Decode, +{ + type Error = Option<::Error>; + + #[inline] + fn decode(reader: R) -> Result> + where + R: Read, + { + Ok(Self(Decode::decode(reader)?)) + } +} + +impl Encode for State +where + S: Specification, + S::Field: Encode, +{ + #[inline] + fn encode(&self, writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.0.encode(writer) + } +} + +impl Sample for State +where + S: Specification, + S::Field: Sample, + D: Clone, +{ + #[inline] + fn sample(distribution: D, rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self( + iter::repeat_with(|| rng.sample(distribution.clone())) + .take(S::WIDTH) + .collect(), + ) + } +} + /// Poseidon Permutation #[cfg_attr( feature = "serde", @@ -456,13 +518,13 @@ where } } -impl Sample for Permutation +impl Sample for Permutation where S: Specification, S::ParameterField: Field + FieldGeneration, { #[inline] - fn sample(distribution: D, rng: &mut R) -> Self + fn sample(distribution: (), rng: &mut R) -> Self where R: RngCore + ?Sized, { diff --git a/manta-pay/src/key.rs b/manta-pay/src/key.rs index 90bf993d2..2e8d95b44 100644 --- a/manta-pay/src/key.rs +++ b/manta-pay/src/key.rs @@ -187,7 +187,6 @@ where /// Returns the [`SecretKey`]. #[inline] pub fn xpr_secret_key(&self, index: &AccountIndex) -> SecretKey { - // TODO: This function should be made private in the following PRs. SecretKey::derive_from_path( self.seed, &path_string::(*index) diff --git a/manta-pay/src/lib.rs b/manta-pay/src/lib.rs index ce8e356ef..d22f940ac 100644 --- a/manta-pay/src/lib.rs +++ b/manta-pay/src/lib.rs @@ -33,8 +33,8 @@ pub mod config; #[cfg_attr(doc_cfg, doc(cfg(feature = "key")))] pub mod key; -#[cfg(all(feature = "groth16", feature = "test"))] -#[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "test"))))] +#[cfg(all(feature = "parameters"))] +#[cfg_attr(doc_cfg, doc(cfg(all(feature = "parameters"))))] pub mod parameters; #[cfg(feature = "groth16")] @@ -48,3 +48,17 @@ pub mod simulation; #[cfg(any(test, feature = "test"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] pub mod test; + +#[doc(inline)] +pub use manta_accounting; + +#[doc(inline)] +pub use manta_crypto; + +#[cfg(any(test, feature = "manta-parameters"))] +#[cfg_attr(doc_cfg, doc(cfg(feature = "manta-parameters")))] +#[doc(inline)] +pub use manta_parameters; + +#[doc(inline)] +pub use manta_util; diff --git a/manta-pay/src/parameters.rs b/manta-pay/src/parameters.rs index db35c37f4..01755679f 100644 --- a/manta-pay/src/parameters.rs +++ b/manta-pay/src/parameters.rs @@ -17,13 +17,18 @@ //! Generate Parameters and Proving/Verifying Contexts use crate::config::{ - FullParameters, Mint, MultiProvingContext, MultiVerifyingContext, NoteEncryptionScheme, - Parameters, PrivateTransfer, ProofSystemError, Reclaim, UtxoAccumulatorModel, - UtxoCommitmentScheme, VerifyingContext, VoidNumberCommitmentScheme, + utxo::protocol::BaseParameters, FullParametersRef, MultiProvingContext, MultiVerifyingContext, + Parameters, PrivateTransfer, ProofSystemError, ToPrivate, ToPublic, UtxoAccumulatorModel, + VerifyingContext, }; +use core::fmt::Debug; use manta_crypto::rand::{ChaCha20Rng, Rand, SeedableRng}; +use manta_parameters::Get; use manta_util::codec::Decode; +#[cfg(feature = "download")] +use manta_parameters::Download; + #[cfg(feature = "std")] use { crate::config::ProvingContext, @@ -62,23 +67,23 @@ pub fn generate_from_seed( let mut rng = ChaCha20Rng::from_seed(seed); let parameters = rng.gen(); let utxo_accumulator_model: UtxoAccumulatorModel = rng.gen(); - let full_parameters = FullParameters::new(¶meters, &utxo_accumulator_model); - let (mint_proving_context, mint_verifying_context) = - Mint::generate_context(&(), full_parameters, &mut rng)?; + let full_parameters = FullParametersRef::new(¶meters, &utxo_accumulator_model); + let (to_private_proving_context, to_private_verifying_context) = + ToPrivate::generate_context(&(), full_parameters, &mut rng)?; let (private_transfer_proving_context, private_transfer_verifying_context) = PrivateTransfer::generate_context(&(), full_parameters, &mut rng)?; - let (reclaim_proving_context, reclaim_verifying_context) = - Reclaim::generate_context(&(), full_parameters, &mut rng)?; + let (to_public_proving_context, to_public_verifying_context) = + ToPublic::generate_context(&(), full_parameters, &mut rng)?; Ok(( MultiProvingContext { - mint: mint_proving_context, + to_private: to_private_proving_context, private_transfer: private_transfer_proving_context, - reclaim: reclaim_proving_context, + to_public: to_public_proving_context, }, MultiVerifyingContext { - mint: mint_verifying_context, + to_private: to_private_verifying_context, private_transfer: private_transfer_verifying_context, - reclaim: reclaim_verifying_context, + to_public: to_public_verifying_context, }, parameters, utxo_accumulator_model, @@ -117,9 +122,9 @@ pub fn load_parameters( Ok(( load_proving_context(directory), MultiVerifyingContext { - mint: load_mint_verifying_context(), + to_private: load_to_private_verifying_context(), private_transfer: load_private_transfer_verifying_context(), - reclaim: load_reclaim_verifying_context(), + to_public: load_to_public_verifying_context(), }, load_transfer_parameters(), load_utxo_accumulator_model(), @@ -132,16 +137,16 @@ pub fn load_parameters( #[cfg_attr(doc_cfg, doc(cfg(feature = "download")))] #[inline] pub fn load_proving_context(directory: &Path) -> MultiProvingContext { - let mint_path = directory.join("mint.dat"); - manta_parameters::pay::testnet::proving::Mint::download(&mint_path) - .expect("Unable to download MINT proving context."); + let to_private_path = directory.join("to-private.dat"); + manta_parameters::pay::testnet::proving::ToPrivate::download(&to_private_path) + .expect("Unable to download ToPrivate proving context."); let private_transfer_path = directory.join("private-transfer.dat"); manta_parameters::pay::testnet::proving::PrivateTransfer::download(&private_transfer_path) - .expect("Unable to download PRIVATE_TRANSFER proving context."); - let reclaim_path = directory.join("reclaim.dat"); - manta_parameters::pay::testnet::proving::Reclaim::download(&reclaim_path) - .expect("Unable to download RECLAIM proving context."); - decode_proving_context(&mint_path, &private_transfer_path, &reclaim_path) + .expect("Unable to download PrivateTransfer proving context."); + let to_public_path = directory.join("to-public.dat"); + manta_parameters::pay::testnet::proving::ToPublic::download(&to_public_path) + .expect("Unable to download ToPublic proving context."); + decode_proving_context(&to_private_path, &private_transfer_path, &to_public_path) } /// Loads the [`MultiProvingContext`] from [`manta_parameters`], using `directory` as @@ -154,93 +159,108 @@ pub fn load_proving_context(directory: &Path) -> MultiProvingContext { #[cfg_attr(doc_cfg, doc(cfg(feature = "download")))] #[inline] pub fn try_load_proving_context(directory: &Path) -> MultiProvingContext { - let mint_path = directory.join("mint.dat"); - manta_parameters::pay::testnet::proving::Mint::download_if_invalid(&mint_path) - .expect("Unable to download MINT proving context."); + let to_private_path = directory.join("to-private.dat"); + manta_parameters::pay::testnet::proving::ToPrivate::download_if_invalid(&to_private_path) + .expect("Unable to download ToPrivate proving context."); let private_transfer_path = directory.join("private-transfer.dat"); manta_parameters::pay::testnet::proving::PrivateTransfer::download_if_invalid( &private_transfer_path, ) - .expect("Unable to download PRIVATE_TRANSFER proving context."); - let reclaim_path = directory.join("reclaim.dat"); - manta_parameters::pay::testnet::proving::Reclaim::download_if_invalid(&reclaim_path) - .expect("Unable to download RECLAIM proving context."); - decode_proving_context(&mint_path, &private_transfer_path, &reclaim_path) + .expect("Unable to download PrivateTransfer proving context."); + let to_public_path = directory.join("to-public.dat"); + manta_parameters::pay::testnet::proving::ToPublic::download_if_invalid(&to_public_path) + .expect("Unable to download ToPublic proving context."); + decode_proving_context(&to_private_path, &private_transfer_path, &to_public_path) } -/// Decodes [`MultiProvingContext`] by loading from `mint_path`, `private_transfer_path`, and `reclaim_path`. +/// Decodes [`MultiProvingContext`] by loading from `to_private_path`, `private_transfer_path`, and +/// `to_public_path`. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] #[inline] pub fn decode_proving_context( - mint_path: &Path, + to_private_path: &Path, private_transfer_path: &Path, - reclaim_path: &Path, + to_public_path: &Path, ) -> MultiProvingContext { MultiProvingContext { - mint: ProvingContext::decode(IoReader( - File::open(mint_path).expect("Unable to open MINT proving context file."), + to_private: ProvingContext::decode(IoReader( + File::open(to_private_path).expect("Unable to open ToPrivate proving context file."), )) - .expect("Unable to decode MINT proving context."), + .expect("Unable to decode ToPrivate proving context."), private_transfer: ProvingContext::decode(IoReader( File::open(private_transfer_path) - .expect("Unable to open PRIVATE_TRANSFER proving context file."), + .expect("Unable to open PrivateTransfer proving context file."), )) - .expect("Unable to decode PRIVATE_TRANSFER proving context."), - reclaim: ProvingContext::decode(IoReader( - File::open(reclaim_path).expect("Unable to open RECLAIM proving context file."), + .expect("Unable to decode PrivateTransfer proving context."), + to_public: ProvingContext::decode(IoReader( + File::open(to_public_path).expect("Unable to open ToPublic proving context file."), )) - .expect("Unable to decode RECLAIM proving context."), + .expect("Unable to decode ToPublic proving context."), } } -/// Loads the `Mint` verifying contexts from [`manta_parameters`]. +/// Loads the [`ToPrivate`] verifying contexts from [`manta_parameters`]. #[inline] -pub fn load_mint_verifying_context() -> VerifyingContext { +pub fn load_to_private_verifying_context() -> VerifyingContext { VerifyingContext::decode( - manta_parameters::pay::testnet::verifying::Mint::get().expect("Checksum did not match."), + manta_parameters::pay::testnet::verifying::ToPrivate::get() + .expect("Checksum did not match."), ) - .expect("Unable to decode MINT verifying context.") + .expect("Unable to decode To-Private verifying context.") } -/// Loads the `PrivateTransfer` verifying context from [`manta_parameters`]. +/// Loads the [`PrivateTransfer`] verifying context from [`manta_parameters`]. #[inline] pub fn load_private_transfer_verifying_context() -> VerifyingContext { VerifyingContext::decode( manta_parameters::pay::testnet::verifying::PrivateTransfer::get() .expect("Checksum did not match."), ) - .expect("Unable to decode PRIVATE_TRANSFER verifying context.") + .expect("Unable to decode PrivateTransfer verifying context.") } -/// Loads the `Reclaim` verifying context from [`manta_parameters`]. +/// Loads the [`ToPublic`] verifying context from [`manta_parameters`]. #[inline] -pub fn load_reclaim_verifying_context() -> VerifyingContext { +pub fn load_to_public_verifying_context() -> VerifyingContext { VerifyingContext::decode( - manta_parameters::pay::testnet::verifying::Reclaim::get().expect("Checksum did not match."), + manta_parameters::pay::testnet::verifying::ToPublic::get() + .expect("Checksum did not match."), ) - .expect("Unable to decode RECLAIM verifying context.") + .expect("Unable to decode ToPublic verifying context.") +} + +/// Load a [`Get`] object into an object of type `T`. +#[inline] +pub fn load_get_object() -> T +where + G: Get, + T: Decode, + T::Error: Debug, +{ + Decode::decode(G::get().expect("Mismatch of checksum.")).expect("Unable to decode object.") } /// Loads the transfer [`Parameters`] from [`manta_parameters`]. #[inline] pub fn load_transfer_parameters() -> Parameters { + use manta_parameters::pay::testnet::parameters::*; Parameters { - note_encryption_scheme: NoteEncryptionScheme::decode( - manta_parameters::pay::testnet::parameters::NoteEncryptionScheme::get() - .expect("Checksum did not match."), - ) - .expect("Unable to decode NOTE_ENCRYPTION_SCHEME parameters."), - utxo_commitment: UtxoCommitmentScheme::decode( - manta_parameters::pay::testnet::parameters::UtxoCommitmentScheme::get() - .expect("Checksum did not match."), - ) - .expect("Unable to decode UTXO_COMMITMENT_SCHEME parameters."), - void_number_commitment: VoidNumberCommitmentScheme::decode( - manta_parameters::pay::testnet::parameters::VoidNumberCommitmentScheme::get() - .expect("Checksum did not match."), - ) - .expect("Unable to decode VOID_NUMBER_COMMITMENT_SCHEME parameters."), + base: BaseParameters { + group_generator: load_get_object::(), + utxo_commitment_scheme: load_get_object::(), + incoming_base_encryption_scheme: load_get_object::(), + light_incoming_base_encryption_scheme: load_get_object::< + LightIncomingBaseEncryptionScheme, + _, + >(), + viewing_key_derivation_function: load_get_object::(), + utxo_accumulator_item_hash: load_get_object::(), + nullifier_commitment_scheme: load_get_object::(), + outgoing_base_encryption_scheme: load_get_object::(), + }, + address_partition_function: load_get_object::(), + schnorr_hash_function: load_get_object::(), } } diff --git a/manta-pay/src/signer/base.rs b/manta-pay/src/signer/base.rs index 479e010c0..20b34a10f 100644 --- a/manta-pay/src/signer/base.rs +++ b/manta-pay/src/signer/base.rs @@ -17,7 +17,10 @@ //! Manta Pay Signer Configuration use crate::{ - config::{Config, MerkleTreeConfiguration, Parameters, PublicKey, SecretKey}, + config::{ + utxo::{self, MerkleTreeConfiguration}, + Config, + }, key::{CoinType, KeySecret, Testnet}, signer::Checkpoint, }; @@ -25,61 +28,49 @@ use alloc::collections::BTreeMap; use core::{cmp, mem}; use manta_accounting::{ asset::HashAssetMap, - key::{self, AccountCollection, AccountIndex, DeriveAddresses}, - transfer, + key::{AccountCollection, AccountIndex, DeriveAddresses}, + transfer::{utxo::protocol, Identifier, SpendingKey}, wallet::{ self, signer::{self, SyncData}, }, }; use manta_crypto::{ + accumulator::ItemHashFunction, arkworks::{ constraint::fp::Fp, - ed_on_bls12_381::FrParameters, + ed_on_bn254::FrParameters, ff::{Fp256, PrimeField}, }, - key::agreement::Derive, merkle_tree::{self, forest::Configuration}, - rand::{ChaCha20Rng, CryptoRng, RngCore}, + rand::ChaCha20Rng, }; -impl DeriveAddresses for KeySecret +impl AccountCollection for KeySecret where C: CoinType, { - type Address = PublicKey; - type Parameters = Parameters; - #[inline] - fn address(&self, parameters: &Self::Parameters, index: AccountIndex) -> Self::Address { - let spending_key = self.spending_key(&index); - parameters - .key_agreement_scheme() - .derive(&spending_key, &mut ()) - } -} + type SpendingKey = SpendingKey; -impl key::AccountCollection for KeySecret -where - C: CoinType, -{ - type SpendingKey = SecretKey; #[inline] fn spending_key(&self, index: &AccountIndex) -> Self::SpendingKey { - let xpr_secret_key = self.xpr_secret_key(index); Fp(Fp256::::from_le_bytes_mod_order( - &xpr_secret_key.to_bytes(), + &self.xpr_secret_key(index).to_bytes(), )) } } -/// Samples a [`KeySecret`] from `rng`. -#[inline] -pub fn sample_key_secret(rng: &mut R) -> KeySecret +impl DeriveAddresses for KeySecret where C: CoinType, - R: CryptoRng + RngCore + ?Sized, { - KeySecret::sample(rng) + type Address = protocol::Address; + type Parameters = protocol::Parameters; + + #[inline] + fn address(&self, parameters: &Self::Parameters, index: AccountIndex) -> Self::Address { + parameters.address_from_spending_key(&AccountCollection::spending_key(&self, &index)) + } } /// Signer UTXO Accumulator @@ -93,22 +84,19 @@ pub type UtxoAccumulator = merkle_tree::forest::TreeArrayMerkleForest< >; impl wallet::signer::Configuration for Config { - type Checkpoint = Checkpoint; type Account = KeySecret; + type Checkpoint = Checkpoint; type UtxoAccumulator = UtxoAccumulator; - type AssetMap = HashAssetMap< - SecretKey, - ::AssetId, - ::AssetValue, - >; + type AssetMap = HashAssetMap, Self::AssetId, Self::AssetValue>; type Rng = ChaCha20Rng; } impl signer::Checkpoint for Checkpoint { type UtxoAccumulator = UtxoAccumulator; + type UtxoAccumulatorItemHash = utxo::UtxoAccumulatorItemHash; #[inline] - fn update_from_void_numbers(&mut self, count: usize) { + fn update_from_nullifiers(&mut self, count: usize) { self.sender_index += count; } @@ -128,17 +116,23 @@ impl signer::Checkpoint for Checkpoint { /// index or by global void number index until we reach some pruned data that is at least newer /// than `signer_checkpoint`. #[inline] - fn prune(data: &mut SyncData, origin: &Self, signer_checkpoint: &Self) -> bool { + fn prune( + parameters: &Self::UtxoAccumulatorItemHash, + data: &mut SyncData, + origin: &Self, + signer_checkpoint: &Self, + ) -> bool { const PRUNE_PANIC_MESSAGE: &str = "ERROR: Invalid pruning conditions"; if signer_checkpoint <= origin { return false; } let mut updated_origin = *origin; - for receiver in &data.receivers { - let key = MerkleTreeConfiguration::tree_index(&receiver.0); + for receiver in &data.utxo_note_data { + let key = + MerkleTreeConfiguration::tree_index(¶meters.item_hash(&receiver.0, &mut ())); updated_origin.receiver_index[key as usize] += 1; } - updated_origin.sender_index += data.senders.len(); + updated_origin.sender_index += data.nullifier_data.len(); if signer_checkpoint > &updated_origin { *data = Default::default(); return true; @@ -149,19 +143,19 @@ impl signer::Checkpoint for Checkpoint { .checked_sub(origin.sender_index) { Some(diff) => { - drop(data.senders.drain(0..diff)); + drop(data.nullifier_data.drain(0..diff)); if diff > 0 { has_pruned = true; } } _ => panic!( - "{}: Sender Pruning: {:?} {:?} {:?}", - PRUNE_PANIC_MESSAGE, data, origin, signer_checkpoint + "{PRUNE_PANIC_MESSAGE}: Sender Pruning: {data:?} {origin:?} {signer_checkpoint:?}", ), } let mut data_map = BTreeMap::<_, Vec<_>>::new(); - for receiver in mem::take(&mut data.receivers) { - let key = MerkleTreeConfiguration::tree_index(&receiver.0); + for receiver in mem::take(&mut data.utxo_note_data) { + let key = + MerkleTreeConfiguration::tree_index(¶meters.item_hash(&receiver.0, &mut ())); match data_map.get_mut(&key) { Some(entry) => entry.push(receiver), _ => { @@ -178,15 +172,14 @@ impl signer::Checkpoint for Checkpoint { match index.checked_sub(origin_index) { Some(diff) => { if let Some(entries) = data_map.remove(&(i as u8)) { - data.receivers.extend(entries.into_iter().skip(diff)); + data.utxo_note_data.extend(entries.into_iter().skip(diff)); if diff > 0 { has_pruned = true; } } } _ => panic!( - "{}: Receiver Pruning: {:?} {:?} {:?}", - PRUNE_PANIC_MESSAGE, data, origin, signer_checkpoint + "{PRUNE_PANIC_MESSAGE}: Receiver Pruning: {data:?} {origin:?} {signer_checkpoint:?}", ), } } diff --git a/manta-pay/src/signer/client/http.rs b/manta-pay/src/signer/client/http.rs new file mode 100644 index 000000000..57d919501 --- /dev/null +++ b/manta-pay/src/signer/client/http.rs @@ -0,0 +1,108 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Signer HTTP Client Implementation + +use crate::{ + config::{utxo::Address, Config}, + signer::{ + client::network::{Message, Network}, + Checkpoint, GetRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, + SyncResponse, + }, +}; +use alloc::boxed::Box; +use manta_accounting::wallet::{self, signer}; +use manta_util::{ + future::LocalBoxFutureResult, + http::reqwest::{self, IntoUrl, KnownUrlClient}, +}; + +#[doc(inline)] +pub use reqwest::Error; + +/// Wallet Associated to [`Client`] +pub type Wallet = wallet::Wallet; + +/// HTTP Signer Client +pub struct Client { + /// Base Client + base: KnownUrlClient, + + /// Network Selector + network: Option, +} + +impl Client { + /// Builds a new HTTP [`Client`] that connects to `server_url`. + #[inline] + pub fn new(server_url: U) -> Result + where + U: IntoUrl, + { + Ok(Self { + base: KnownUrlClient::new(server_url)?, + network: None, + }) + } + + /// Sets the network that will be used to wrap HTTP requests. + #[inline] + pub fn set_network(&mut self, network: Option) { + self.network = network + } + + /// Wraps the current outgoing `request` with a `network` if it is not `None`. + #[inline] + pub fn wrap_request(&self, request: T) -> Message { + Message { + network: self + .network + .expect("Unable to wrap request, missing network."), + message: request, + } + } +} + +impl signer::Connection for Client { + type Checkpoint = Checkpoint; + type Error = Error; + + #[inline] + fn sync( + &mut self, + request: SyncRequest, + ) -> LocalBoxFutureResult, Self::Error> { + Box::pin(async move { self.base.post("sync", &self.wrap_request(request)).await }) + } + + #[inline] + fn sign( + &mut self, + request: SignRequest, + ) -> LocalBoxFutureResult, Self::Error> { + Box::pin(async move { self.base.post("sign", &self.wrap_request(request)).await }) + } + + #[inline] + fn address(&mut self) -> LocalBoxFutureResult { + Box::pin(async move { + self.base + .post("address", &self.wrap_request(GetRequest::Get)) + .await + }) + } +} diff --git a/manta-pay/src/signer/client/mod.rs b/manta-pay/src/signer/client/mod.rs index 6ff155c49..4cbcda8f2 100644 --- a/manta-pay/src/signer/client/mod.rs +++ b/manta-pay/src/signer/client/mod.rs @@ -14,7 +14,11 @@ // You should have received a copy of the GNU General Public License // along with manta-rs. If not, see . -//! Signer Client +//! Signer Client Implementations + +#[cfg(feature = "http")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "http")))] +pub mod http; #[cfg(feature = "websocket")] #[cfg_attr(doc_cfg, doc(cfg(feature = "websocket")))] diff --git a/manta-pay/src/signer/client/websocket.rs b/manta-pay/src/signer/client/websocket.rs index 49f94434f..674766e94 100644 --- a/manta-pay/src/signer/client/websocket.rs +++ b/manta-pay/src/signer/client/websocket.rs @@ -19,7 +19,7 @@ // TODO: Make this code work on WASM and non-WASM by choosing the correct dependency library. use crate::{ - config::{Config, PublicKey}, + config::{utxo::Address, Config}, signer::{ Checkpoint, GetRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, SyncResponse, @@ -68,8 +68,12 @@ from_variant!(Error, SerializationError, serde_json::Error); from_variant!(Error, WebSocket, WebSocketError); /// Request -#[derive(derivative::Derivative, Deserialize, Serialize)] -#[serde(crate = "manta_util::serde")] +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] +#[derive(derivative::Derivative)] #[derivative(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub struct Request { /// Request Command @@ -86,6 +90,8 @@ pub struct Request { pub type Wallet = wallet::Wallet; /// WebSocket Client +#[derive(derivative::Derivative)] +#[derivative(Debug)] pub struct Client(WebSocketStream>); impl Client { @@ -141,7 +147,7 @@ impl signer::Connection for Client { } #[inline] - fn receiving_keys(&mut self) -> LocalBoxFutureResult { - Box::pin(async move { self.send("receivingKeys", GetRequest::Get).await }) + fn address(&mut self) -> LocalBoxFutureResult { + Box::pin(async move { self.send("address", GetRequest::Get).await }) } } diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index 91204d9aa..c0e8511d2 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -16,9 +16,10 @@ //! Manta Pay Signer Tools -use crate::config::{Config, MerkleTreeConfiguration}; -use manta_accounting::wallet::{ledger, signer}; -use manta_util::Array; +use manta_accounting::wallet::signer; + +#[cfg(feature = "groth16")] +use crate::config::{utxo::Checkpoint, Config}; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize}; @@ -53,160 +54,15 @@ pub type SignError = signer::SignError; /// Signing Result pub type SignResult = signer::SignResult; -/// Checkpoint -#[cfg_attr( - feature = "serde", - derive(Deserialize, Serialize), - serde(crate = "manta_util::serde", deny_unknown_fields) -)] -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Checkpoint { - /// Receiver Index - pub receiver_index: Array, - - /// Sender Index - pub sender_index: usize, -} - -impl Checkpoint { - /// Builds a new [`Checkpoint`] from `receiver_index` and `sender_index`. - #[inline] - pub fn new( - receiver_index: Array, - sender_index: usize, - ) -> Self { - Self { - receiver_index, - sender_index, - } - } -} - -impl Default for Checkpoint { - #[inline] - fn default() -> Self { - Self::new([0; MerkleTreeConfiguration::FOREST_WIDTH].into(), 0) - } -} - -impl From for Checkpoint { - #[inline] - fn from(checkpoint: RawCheckpoint) -> Self { - Self::new( - checkpoint.receiver_index.map(|i| i as usize).into(), - checkpoint.sender_index as usize, - ) - } -} - -impl ledger::Checkpoint for Checkpoint {} - -#[cfg(feature = "scale")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "scale")))] -impl scale_codec::Decode for Checkpoint { - #[inline] - fn decode(input: &mut I) -> Result - where - I: scale_codec::Input, - { - RawCheckpoint::decode(input).map(Into::into) - } -} - -#[cfg(feature = "scale")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "scale")))] -impl scale_codec::Encode for Checkpoint { - #[inline] - fn using_encoded(&self, f: Encoder) -> R - where - Encoder: FnOnce(&[u8]) -> R, - { - RawCheckpoint::from(*self).using_encoded(f) - } -} - -#[cfg(feature = "scale")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "scale")))] -impl scale_codec::EncodeLike for Checkpoint {} - -#[cfg(feature = "scale")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "scale")))] -impl scale_codec::MaxEncodedLen for Checkpoint { - #[inline] - fn max_encoded_len() -> usize { - RawCheckpoint::max_encoded_len() - } -} - -#[cfg(feature = "scale")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "scale")))] -impl scale_info::TypeInfo for Checkpoint { - type Identity = RawCheckpoint; - - #[inline] - fn type_info() -> scale_info::Type { - Self::Identity::type_info() - } -} - -/// Raw Checkpoint for Encoding and Decoding -#[cfg_attr( - feature = "scale", - derive( - scale_codec::Decode, - scale_codec::Encode, - scale_codec::MaxEncodedLen, - scale_info::TypeInfo - ) -)] -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct RawCheckpoint { - /// Receiver Index - pub receiver_index: [u64; MerkleTreeConfiguration::FOREST_WIDTH], - - /// Sender Index - pub sender_index: u64, -} - -impl RawCheckpoint { - /// Builds a new [`RawCheckpoint`] from `receiver_index` and `sender_index`. - #[inline] - pub fn new( - receiver_index: [u64; MerkleTreeConfiguration::FOREST_WIDTH], - sender_index: u64, - ) -> Self { - Self { - receiver_index, - sender_index, - } - } -} - -impl Default for RawCheckpoint { - #[inline] - fn default() -> Self { - Self::new([0; MerkleTreeConfiguration::FOREST_WIDTH], 0) - } -} - -impl From for RawCheckpoint { - #[inline] - fn from(checkpoint: Checkpoint) -> Self { - Self::new( - (*checkpoint.receiver_index).map(|i| i as u64), - checkpoint.sender_index as u64, - ) - } -} - -/// Get Request +/// Receiving Key Request #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), serde(crate = "manta_util::serde", deny_unknown_fields) )] -#[derive(Clone)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum GetRequest { /// GET + #[default] Get, } diff --git a/manta-pay/src/simulation/ledger/http/client.rs b/manta-pay/src/simulation/ledger/http/client.rs index 1cb25ce53..49ac07be8 100644 --- a/manta-pay/src/simulation/ledger/http/client.rs +++ b/manta-pay/src/simulation/ledger/http/client.rs @@ -17,7 +17,10 @@ //! Ledger Simulation Client use crate::{ - config::{Config, TransferPost}, + config::{ + utxo::{AssetId, AssetValue}, + Config, TransferPost, + }, simulation::ledger::{http::Request, AccountId, Checkpoint}, }; use manta_accounting::{ @@ -104,9 +107,9 @@ impl ledger::Write> for Client { } } -impl PublicBalanceOracle for Client { +impl PublicBalanceOracle for Client { #[inline] - fn public_balances(&self) -> LocalBoxFuture> { + fn public_balances(&self) -> LocalBoxFuture>> { Box::pin(async move { self.client .post("publicBalances", &self.account) diff --git a/manta-pay/src/simulation/ledger/http/server.rs b/manta-pay/src/simulation/ledger/http/server.rs index db35c37ed..d08539591 100644 --- a/manta-pay/src/simulation/ledger/http/server.rs +++ b/manta-pay/src/simulation/ledger/http/server.rs @@ -17,7 +17,10 @@ //! Ledger Simulation Server use crate::{ - config::{Config, TransferPost}, + config::{ + utxo::{AssetId, AssetValue}, + Config, TransferPost, + }, simulation::ledger::{http::Request, AccountId, Checkpoint, Ledger, SharedLedger}, }; use alloc::sync::Arc; @@ -62,7 +65,7 @@ impl State { /// Returns the public balances associated to `account` if they exist. #[inline] - async fn public_balances(self, account: AccountId) -> Option { + async fn public_balances(self, account: AccountId) -> Option> { self.0.read().await.public_balances(account) } } diff --git a/manta-pay/src/simulation/ledger/mod.rs b/manta-pay/src/simulation/ledger/mod.rs index 68ddf0283..0a2d68681 100644 --- a/manta-pay/src/simulation/ledger/mod.rs +++ b/manta-pay/src/simulation/ledger/mod.rs @@ -19,23 +19,22 @@ // TODO: How to model existential deposits and fee payments? // TODO: Add in some concurrency (and measure how much we need it). -use crate::{ - config::{ - Config, EncryptedNote, MerkleTreeConfiguration, MultiVerifyingContext, ProofSystem, - TransferPost, Utxo, UtxoAccumulatorModel, VoidNumber, +use crate::config::{ + utxo::{ + AssetId, AssetValue, Checkpoint, FullIncomingNote, MerkleTreeConfiguration, Parameters, }, - signer::Checkpoint, + Config, MultiVerifyingContext, Nullifier, ProofSystem, TransferPost, Utxo, + UtxoAccumulatorModel, }; use alloc::{sync::Arc, vec::Vec}; use core::convert::Infallible; use indexmap::IndexSet; use manta_accounting::{ - asset::AssetList, + asset::{Asset, AssetList}, transfer::{ - self, canonical::TransferShape, Asset, InvalidSinkAccount, InvalidSourceAccount, Proof, - ReceiverLedger, ReceiverPostingKey, SenderLedger, SenderPostingKey, SinkPostingKey, - SourcePostingKey, TransferLedger, TransferLedgerSuperPostingKey, TransferPostingKey, - UtxoAccumulatorOutput, + canonical::TransferShape, receiver::ReceiverLedger, sender::SenderLedger, + InvalidSinkAccount, InvalidSourceAccount, SinkPostingKey, SourcePostingKey, TransferLedger, + TransferLedgerSuperPostingKey, TransferPostingKeyRef, UtxoAccumulatorOutput, }, wallet::{ ledger::{self, ReadResponse}, @@ -44,6 +43,7 @@ use manta_accounting::{ }, }; use manta_crypto::{ + accumulator::ItemHashFunction, constraint::ProofSystem as _, merkle_tree::{ self, @@ -72,7 +72,12 @@ pub type UtxoMerkleForest = merkle_tree::forest::TreeArrayMerkleForest< >; /// Wrap Type -#[derive(Clone, Copy)] +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Wrap(T); impl AsRef for Wrap { @@ -83,7 +88,12 @@ impl AsRef for Wrap { } /// Wrap Pair Type -#[derive(Clone, Copy)] +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WrapPair(L, R); impl AsRef for WrapPair { @@ -105,29 +115,26 @@ pub struct AccountId(pub u64); /// Ledger #[derive(Debug)] pub struct Ledger { - /// Void Numbers - void_numbers: IndexSet, + /// Nullifier + nullifiers: IndexSet, /// UTXOs utxos: HashSet, /// Shards - shards: HashMap>, + shards: HashMap>, /// UTXO Forest utxo_forest: UtxoMerkleForest, /// Account Table - accounts: HashMap< - AccountId, - HashMap< - ::AssetId, - ::AssetValue, - >, - >, + accounts: HashMap>, /// Verifying Contexts verifying_context: MultiVerifyingContext, + + /// UTXO Configuration Parameters + parameters: Parameters, } impl Ledger { @@ -136,9 +143,10 @@ impl Ledger { pub fn new( utxo_accumulator_model: UtxoAccumulatorModel, verifying_context: MultiVerifyingContext, + parameters: Parameters, ) -> Self { Self { - void_numbers: Default::default(), + nullifiers: Default::default(), utxos: Default::default(), shards: (0..MerkleTreeConfiguration::FOREST_WIDTH) .map(move |i| (MerkleForestIndex::from_index(i), Default::default())) @@ -146,37 +154,26 @@ impl Ledger { utxo_forest: UtxoMerkleForest::new(utxo_accumulator_model), accounts: Default::default(), verifying_context, + parameters, } } /// Returns the public balances of `account` if it exists. #[inline] - pub fn public_balances( - &self, - account: AccountId, - ) -> Option< - AssetList< - ::AssetId, - ::AssetValue, - >, - > { + pub fn public_balances(&self, account: AccountId) -> Option> { Some( self.accounts .get(&account)? .iter() - .map(|(id, value)| Asset::::new(*id, *value)) + .map(|(id, value)| Asset::new(*id, *value)) .collect(), ) } /// Sets the public balance of `account` in assets with `id` to `value`. #[inline] - pub fn set_public_balance( - &mut self, - account: AccountId, - id: ::AssetId, - value: ::AssetValue, - ) { + pub fn set_public_balance(&mut self, account: AccountId, id: AssetId, value: AssetValue) { + assert_ne!(id, Default::default(), "Asset id can't be zero!"); self.accounts.entry(account).or_default().insert(id, value); } @@ -187,19 +184,22 @@ impl Ledger { for (i, mut index) in checkpoint.receiver_index.iter().copied().enumerate() { let shard = &self.shards[&MerkleForestIndex::from_index(i)]; while let Some(entry) = shard.get_index(index) { - receivers.push(*entry); + receivers.push(entry.clone()); index += 1; } } let senders = self - .void_numbers + .nullifiers .iter() .skip(checkpoint.sender_index) - .copied() + .cloned() .collect(); ReadResponse { should_continue: false, - data: SyncData { receivers, senders }, + data: SyncData { + utxo_note_data: receivers, + nullifier_data: senders, + }, } } @@ -208,13 +208,13 @@ impl Ledger { pub fn push(&mut self, account: AccountId, posts: Vec) -> bool { for post in posts { let (sources, sinks) = match TransferShape::from_post(&post) { - Some(TransferShape::Mint) => (vec![account], vec![]), + Some(TransferShape::ToPrivate) => (vec![account], vec![]), Some(TransferShape::PrivateTransfer) => (vec![], vec![]), - Some(TransferShape::Reclaim) => (vec![], vec![account]), + Some(TransferShape::ToPublic) => (vec![], vec![account]), _ => return false, }; - match post.validate(sources, sinks, &*self) { - Ok(posting_key) => posting_key.post(&(), &mut *self).unwrap(), + match post.validate(&self.parameters, &*self, sources, sinks) { + Ok(posting_key) => posting_key.post(&mut *self, &()).unwrap(), _ => return false, } } @@ -222,17 +222,17 @@ impl Ledger { } } -impl SenderLedger for Ledger { - type ValidVoidNumber = Wrap; +impl SenderLedger for Ledger { + type ValidNullifier = Wrap; type ValidUtxoAccumulatorOutput = Wrap>; type SuperPostingKey = (Wrap<()>, ()); #[inline] - fn is_unspent(&self, void_number: VoidNumber) -> Option { - if self.void_numbers.contains(&void_number) { + fn is_unspent(&self, nullifier: Nullifier) -> Option { + if self.nullifiers.contains(&nullifier) { None } else { - Some(Wrap(void_number)) + Some(Wrap(nullifier)) } } @@ -241,6 +241,9 @@ impl SenderLedger for Ledger { &self, output: UtxoAccumulatorOutput, ) -> Option { + if output == Default::default() { + return Some(Wrap(output)); + } for tree in self.utxo_forest.forest.as_ref() { if tree.root() == &output { return Some(Wrap(output)); @@ -252,16 +255,16 @@ impl SenderLedger for Ledger { #[inline] fn spend( &mut self, - utxo_accumulator_output: Self::ValidUtxoAccumulatorOutput, - void_number: Self::ValidVoidNumber, super_key: &Self::SuperPostingKey, + utxo_accumulator_output: Self::ValidUtxoAccumulatorOutput, + nullifier: Self::ValidNullifier, ) { let _ = (utxo_accumulator_output, super_key); - self.void_numbers.insert(void_number.0); + self.nullifiers.insert(nullifier.0); } } -impl ReceiverLedger for Ledger { +impl ReceiverLedger for Ledger { type ValidUtxo = Wrap; type SuperPostingKey = (Wrap<()>, ()); @@ -277,28 +280,26 @@ impl ReceiverLedger for Ledger { #[inline] fn register( &mut self, - utxo: Self::ValidUtxo, - note: EncryptedNote, super_key: &Self::SuperPostingKey, + utxo: Self::ValidUtxo, + note: FullIncomingNote, ) { let _ = super_key; - let shard = self - .shards - .get_mut(&MerkleTreeConfiguration::tree_index(&utxo.0)) - .expect("All shards exist when the ledger is constructed."); - shard.insert((utxo.0, note)); + let utxo_hash = self.parameters.item_hash(&utxo.0, &mut ()); + self.shards + .get_mut(&MerkleTreeConfiguration::tree_index(&utxo_hash)) + .expect("All shards exist when the ledger is constructed.") + .insert((utxo.0, note)); self.utxos.insert(utxo.0); - self.utxo_forest.push(&utxo.0); + self.utxo_forest.push(&utxo_hash); } } impl TransferLedger for Ledger { type AccountId = AccountId; type Event = (); - type ValidSourceAccount = - WrapPair::AssetValue>; - type ValidSinkAccount = - WrapPair::AssetValue>; + type ValidSourceAccount = WrapPair; + type ValidSinkAccount = WrapPair; type ValidProof = Wrap<()>; type SuperPostingKey = (); type UpdateError = Infallible; @@ -306,28 +307,23 @@ impl TransferLedger for Ledger { #[inline] fn check_source_accounts( &self, - asset_id: ::AssetId, + asset_id: &AssetId, sources: I, ) -> Result, InvalidSourceAccount> where - I: Iterator< - Item = ( - Self::AccountId, - ::AssetValue, - ), - >, + I: Iterator, { sources .map(|(account_id, withdraw)| { match self.accounts.get(&account_id) { - Some(map) => match map.get(&asset_id) { + Some(map) => match map.get(asset_id) { Some(balance) => { if balance >= &withdraw { Ok(WrapPair(account_id, withdraw)) } else { Err(InvalidSourceAccount { account_id, - asset_id, + asset_id: *asset_id, withdraw, }) } @@ -336,14 +332,14 @@ impl TransferLedger for Ledger { // FIXME: What about zero values in `sources`? Err(InvalidSourceAccount { account_id, - asset_id, + asset_id: *asset_id, withdraw, }) } }, _ => Err(InvalidSourceAccount { account_id, - asset_id, + asset_id: *asset_id, withdraw, }), } @@ -354,16 +350,11 @@ impl TransferLedger for Ledger { #[inline] fn check_sink_accounts( &self, - asset_id: ::AssetId, + asset_id: &AssetId, sinks: I, ) -> Result, InvalidSinkAccount> where - I: Iterator< - Item = ( - Self::AccountId, - ::AssetValue, - ), - >, + I: Iterator, { sinks .map(move |(account_id, deposit)| { @@ -372,7 +363,7 @@ impl TransferLedger for Ledger { } else { Err(InvalidSinkAccount { account_id, - asset_id, + asset_id: *asset_id, deposit, }) } @@ -383,24 +374,20 @@ impl TransferLedger for Ledger { #[inline] fn is_valid( &self, - asset_id: Option<::AssetId>, - sources: &[SourcePostingKey], - senders: &[SenderPostingKey], - receivers: &[ReceiverPostingKey], - sinks: &[SinkPostingKey], - proof: Proof, + posting_key: TransferPostingKeyRef, ) -> Option<(Self::ValidProof, Self::Event)> { let verifying_context = self.verifying_context.select(TransferShape::select( - asset_id.is_some(), - sources.len(), - senders.len(), - receivers.len(), - sinks.len(), + posting_key.authorization_key.is_some(), + posting_key.asset_id.is_some(), + posting_key.sources.len(), + posting_key.senders.len(), + posting_key.receivers.len(), + posting_key.sinks.len(), )?); ProofSystem::verify( verifying_context, - &TransferPostingKey::generate_proof_input(asset_id, sources, senders, receivers, sinks), - &proof, + &posting_key.generate_proof_input(), + &posting_key.proof, ) .ok()? .then_some((Wrap(()), ())) @@ -409,11 +396,11 @@ impl TransferLedger for Ledger { #[inline] fn update_public_balances( &mut self, - asset_id: ::AssetId, + super_key: &TransferLedgerSuperPostingKey, + asset_id: AssetId, sources: Vec>, sinks: Vec>, proof: Self::ValidProof, - super_key: &TransferLedgerSuperPostingKey, ) -> Result<(), Self::UpdateError> { let _ = (proof, super_key); for WrapPair(account_id, withdraw) in sources { @@ -440,6 +427,7 @@ impl TransferLedger for Ledger { pub type SharedLedger = Arc>; /// Ledger Connection +#[derive(Clone, Debug)] pub struct LedgerConnection { /// Ledger Account account: AccountId, @@ -486,16 +474,7 @@ impl ledger::Write> for LedgerConnection { impl PublicBalanceOracle for LedgerConnection { #[inline] - fn public_balances( - &self, - ) -> LocalBoxFuture< - Option< - AssetList< - ::AssetId, - ::AssetValue, - >, - >, - > { + fn public_balances(&self) -> LocalBoxFuture>> { Box::pin(async move { self.ledger.read().await.public_balances(self.account) }) } } diff --git a/manta-pay/src/simulation/mod.rs b/manta-pay/src/simulation/mod.rs index 8537a8905..bdc0eed67 100644 --- a/manta-pay/src/simulation/mod.rs +++ b/manta-pay/src/simulation/mod.rs @@ -18,16 +18,18 @@ use crate::{ config::{ - AssetId, AssetValue, AssetValueType, Config, MultiProvingContext, MultiVerifyingContext, - Parameters, UtxoAccumulatorModel, + utxo::{AssetId, AssetValue}, + Config, MultiProvingContext, MultiVerifyingContext, Parameters, UtxoAccumulatorModel, }, - signer::base::{sample_key_secret, Signer, UtxoAccumulator}, + key::KeySecret, + signer::base::{Signer, UtxoAccumulator}, simulation::ledger::{AccountId, Ledger, LedgerConnection}, }; use alloc::{format, sync::Arc}; use core::fmt::Debug; use manta_accounting::{ self, + asset::AssetList, key::AccountTable, wallet::{ self, @@ -55,9 +57,9 @@ where R: CryptoRng + RngCore + ?Sized, { Signer::new( - AccountTable::new(sample_key_secret(rng)), - proving_context.clone(), + AccountTable::new(KeySecret::sample(rng)), parameters.clone(), + proving_context.clone(), UtxoAccumulator::new(utxo_accumulator_model.clone()), rng.seed_rng().expect("Failed to sample PRNG for signer."), ) @@ -77,7 +79,7 @@ pub struct Simulation { pub asset_id_count: usize, /// Starting Balance - pub starting_balance: AssetValueType, + pub starting_balance: AssetValue, } impl Simulation { @@ -94,11 +96,15 @@ impl Simulation { /// Sets the correct public balances for `ledger` to set up the simulation. #[inline] pub fn setup(&self, ledger: &mut Ledger) { - let starting_balance = AssetValue(self.starting_balance); + let starting_balance = self.starting_balance; for i in 0..self.actor_count { let account = AccountId(i as u64); for id in 0..self.asset_id_count { - ledger.set_public_balance(account, AssetId(id as u32), starting_balance); + ledger.set_public_balance( + account, + AssetId::from((id + 1) as u128), + starting_balance, + ); } } } @@ -115,7 +121,11 @@ impl Simulation { ) where R: CryptoRng + RngCore + ?Sized, { - let mut ledger = Ledger::new(utxo_accumulator_model.clone(), verifying_context); + let mut ledger = Ledger::new( + utxo_accumulator_model.clone(), + verifying_context, + parameters.clone(), + ); self.setup(&mut ledger); let ledger = Arc::new(RwLock::new(ledger)); self.run_with( @@ -136,13 +146,14 @@ impl Simulation { where L: wallet::test::Ledger + PublicBalanceOracle, S: wallet::signer::Connection, + S::Error: Debug, GL: FnMut(usize) -> L, GS: FnMut(usize) -> S, Error: Debug, { assert!( self.config() - .run(ledger, signer, ChaCha20Rng::from_entropy, |event| { + .run::<_, _, _, AssetList, _, _, _, _, _, _>(ledger, signer, |_| ChaCha20Rng::from_entropy(), |event| { let event = format!("{event:?}\n"); async move { let _ = write_stdout(event.as_bytes()).await; diff --git a/manta-pay/src/test/balance.rs b/manta-pay/src/test/balance.rs new file mode 100644 index 000000000..faaa7377e --- /dev/null +++ b/manta-pay/src/test/balance.rs @@ -0,0 +1,99 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Manta Pay Wallet Balance Testing + +use crate::config::{AssetId, AssetValue}; +use manta_accounting::{ + asset, + wallet::balance::{ + self, + test::{assert_full_withdraw_should_remove_entry, assert_valid_withdraw}, + }, +}; +use manta_crypto::rand::OsRng; + +/// Asset List Type +type AssetList = asset::AssetList; + +/// BTreeMap Balance State Type +type BTreeMapBalanceState = balance::BTreeMapBalanceState; + +/// HashMap Balance State Type +#[cfg(feature = "std")] +type HashMapBalanceState = balance::HashMapBalanceState; + +/// Defines the tests across multiple different [`BalanceState`] types. +macro_rules! define_tests { + ($(( + $type:ty, + $doc:expr, + $valid_withdraw:ident, + $full_withdraw:ident + $(,)?)),* + $(,)?) => { + $( + #[doc = "Tests valid withdrawals for an"] + #[doc = $doc] + #[doc = "balance state."] + #[test] + fn $valid_withdraw() { + let mut state = <$type>::default(); + let mut rng = OsRng; + for _ in 0..0xFFFF { + assert_valid_withdraw(&mut state, &mut rng); + } + } + + #[doc = "Tests that there are no empty entries in"] + #[doc = $doc] + #[doc = "with no value stored in them."] + #[test] + fn $full_withdraw() { + assert_full_withdraw_should_remove_entry::<_, _, $type, _>(&mut OsRng); + } + )* + } +} + +define_tests!( + ( + AssetList, + "[`AssetList`]", + asset_list_valid_withdraw, + asset_list_full_withdraw, + ), + ( + BTreeMapBalanceState, + "[`BTreeMapBalanceState`]", + btree_map_valid_withdraw, + btree_map_full_withdraw, + ), +); + +/// Tests valid withdrawals for a [`HashMapBalanceState`] balance state. +#[cfg(feature = "std")] +#[test] +fn hash_map_valid_withdraw() { + assert_valid_withdraw(&mut HashMapBalanceState::new(), &mut OsRng); +} + +/// Tests that there are no empty entries in [`HashMapBalanceState`] with no value stored in them. +#[cfg(feature = "std")] +#[test] +fn hash_map_full_withdraw() { + assert_full_withdraw_should_remove_entry::<_, _, HashMapBalanceState, _>(&mut OsRng); +} diff --git a/manta-pay/src/test/compatibility.rs b/manta-pay/src/test/compatibility.rs index b75cfc187..12d9a1edb 100644 --- a/manta-pay/src/test/compatibility.rs +++ b/manta-pay/src/test/compatibility.rs @@ -15,48 +15,47 @@ // along with manta-rs. If not, see . //! Manta Pay UTXO Binary Compatibility +//! //! Checks if the current circuit implementation is compatible with precomputed parameters. use crate::{ parameters::load_parameters, - test::payment::{prove_mint, prove_private_transfer, prove_reclaim}, + signer::base::UtxoAccumulator, + test::payment::{ + private_transfer::prove as prove_private_transfer, to_private::prove as prove_to_private, + to_public::prove as prove_to_public, + }, }; -use manta_accounting::transfer::test::assert_valid_proof; -use manta_crypto::rand::{OsRng, Rand}; +use manta_crypto::rand::OsRng; /// Tests that the circuit is compatible with the current known parameters in `manta-parameters`. +#[ignore = "This would fail because it'd download the data from main before merging."] #[test] fn compatibility() { let directory = tempfile::tempdir().expect("Unable to generate temporary test directory."); let mut rng = OsRng; let (proving_context, verifying_context, parameters, utxo_accumulator_model) = load_parameters(directory.path()).expect("Failed to load parameters"); - assert_valid_proof( - &verifying_context.mint, - &prove_mint( - &proving_context.mint, - ¶meters, - &utxo_accumulator_model, - rng.gen(), - &mut rng, - ), - ); - assert_valid_proof( - &verifying_context.private_transfer, - &prove_private_transfer( - &proving_context, - ¶meters, - &utxo_accumulator_model, - &mut rng, - ), - ); - assert_valid_proof( - &verifying_context.reclaim, - &prove_reclaim( - &proving_context, - ¶meters, - &utxo_accumulator_model, - &mut rng, - ), - ); + let _ = &prove_to_private( + &proving_context.to_private, + ¶meters, + &utxo_accumulator_model, + &mut rng, + ) + .assert_valid_proof(&verifying_context.to_private); + let mut utxo_accumulator = UtxoAccumulator::new(utxo_accumulator_model); + let _ = &prove_private_transfer( + &proving_context.private_transfer, + ¶meters, + &mut utxo_accumulator, + &mut rng, + ) + .assert_valid_proof(&verifying_context.private_transfer); + let _ = &prove_to_public( + &proving_context.to_public, + ¶meters, + &mut utxo_accumulator, + &mut rng, + ) + .assert_valid_proof(&verifying_context.to_public); } diff --git a/manta-pay/src/test/mod.rs b/manta-pay/src/test/mod.rs index 054fecde5..8e95962fa 100644 --- a/manta-pay/src/test/mod.rs +++ b/manta-pay/src/test/mod.rs @@ -16,12 +16,8 @@ //! Manta Pay Testing -// TODO: This is the old simulation. We need to integrate its features into the new asynchronous -// simulation. -// -// #[cfg(feature = "simulation")] -// #[cfg_attr(doc_cfg, doc(cfg(feature = "simulation")))] -// pub mod simulation; +#[cfg(test)] +pub mod balance; #[cfg(test)] pub mod compatibility; diff --git a/manta-pay/src/test/payment.rs b/manta-pay/src/test/payment.rs index 58a5654f9..7dedc71dc 100644 --- a/manta-pay/src/test/payment.rs +++ b/manta-pay/src/test/payment.rs @@ -17,141 +17,378 @@ //! Prove and Verify Functions for Benchmark and Test Purposes use crate::config::{ - self, AssetId, AssetValue, Config, FullParameters, MerkleTreeConfiguration, Mint, - MultiProvingContext, Parameters, PrivateTransfer, ProvingContext, Reclaim, - UtxoAccumulatorModel, -}; -use manta_accounting::{ - asset, - transfer::{Asset, SpendingKey}, + utxo::{MerkleTreeConfiguration, UtxoAccumulatorItem, UtxoAccumulatorModel}, + Asset, AssetId, AssetValue, Authorization, Config, FullParametersRef, MultiProvingContext, + Parameters, PrivateTransfer, ProvingContext, Receiver, ToPrivate, ToPublic, TransferPost, }; +use manta_accounting::transfer::{self, internal_pair, test::value_distribution}; use manta_crypto::{ accumulator::Accumulator, merkle_tree::{forest::TreeArrayMerkleForest, full::Full}, rand::{CryptoRng, Rand, RngCore, Sample}, }; +/// Spending Key Type +pub type SpendingKey = transfer::SpendingKey; + /// UTXO Accumulator for Building Test Circuits pub type UtxoAccumulator = TreeArrayMerkleForest, 256>; -/// Generates a proof for a [`Mint`] transaction. -#[inline] -pub fn prove_mint( - proving_context: &ProvingContext, - parameters: &Parameters, - utxo_accumulator_model: &UtxoAccumulatorModel, - asset: Asset, - rng: &mut R, -) -> config::TransferPost -where - R: CryptoRng + RngCore + ?Sized, -{ - Mint::from_spending_key(parameters, &SpendingKey::gen(rng), asset, rng) +/// Utility Module for [`ToPrivate`] +pub mod to_private { + use super::*; + + /// Generates a proof for a [`ToPrivate`] transaction. + #[inline] + pub fn prove( + proving_context: &ProvingContext, + parameters: &Parameters, + utxo_accumulator_model: &UtxoAccumulatorModel, + rng: &mut R, + ) -> TransferPost + where + R: CryptoRng + RngCore + ?Sized, + { + let asset_0 = Asset::new(rng.gen(), rng.gen()); + let spending_key = rng.gen(); + let address = parameters.address_from_spending_key(&spending_key); + let mut authorization = Authorization::from_spending_key(parameters, &spending_key, rng); + let (to_private_0, _pre_sender_0) = ToPrivate::internal_pair( + parameters, + &mut authorization.context, + address, + asset_0, + Default::default(), + rng, + ); + to_private_0 + .into_post( + FullParametersRef::new(parameters, utxo_accumulator_model), + proving_context, + None, + rng, + ) + .expect("Unable to build TO_PRIVATE proof.") + .expect("Did not match transfer shape.") + } + + /// Generates a proof for a [`ToPrivate`] transaction with custom `asset` as input. + #[inline] + pub fn prove_full( + proving_context: &ProvingContext, + parameters: &Parameters, + utxo_accumulator: &mut A, + asset_id: AssetId, + value: AssetValue, + rng: &mut R, + ) -> TransferPost + where + A: Accumulator, + R: CryptoRng + RngCore + ?Sized, + { + let asset_0 = Asset::new(asset_id, value); + let spending_key = rng.gen(); + let address = parameters.address_from_spending_key(&spending_key); + let mut authorization = Authorization::from_spending_key(parameters, &spending_key, rng); + + let (to_private_0, pre_sender_0) = ToPrivate::internal_pair( + parameters, + &mut authorization.context, + address, + asset_0, + Default::default(), + rng, + ); + let _ = pre_sender_0 + .insert_and_upgrade(parameters, utxo_accumulator) + .expect(""); + to_private_0 + .into_post( + FullParametersRef::new(parameters, utxo_accumulator.model()), + proving_context, + None, + rng, + ) + .expect("Unable to build TO_PRIVATE proof.") + .expect("Did not match transfer shape.") + } +} + +/// Utility Module for [`PrivateTransfer`] +pub mod private_transfer { + use super::*; + + /// Generates a proof for a [`PrivateTransfer`] transaction including pre-requisite + /// [`ToPrivate`] transactions. + #[inline] + pub fn prove_full( + proving_context: &MultiProvingContext, + parameters: &Parameters, + utxo_accumulator: &mut A, + asset_id: AssetId, + values: [AssetValue; 2], + rng: &mut R, + ) -> ([TransferPost; 2], TransferPost) + where + A: Accumulator, + R: CryptoRng + RngCore + ?Sized, + { + let asset_0 = Asset::new(asset_id, values[0]); + let asset_1 = Asset::new(asset_id, values[1]); + let spending_key = rng.gen(); + let address = parameters.address_from_spending_key(&spending_key); + let mut authorization = Authorization::from_spending_key(parameters, &spending_key, rng); + + let (to_private_0, pre_sender_0) = ToPrivate::internal_pair( + parameters, + &mut authorization.context, + address, + asset_0, + Default::default(), + rng, + ); + let to_private_0 = to_private_0 + .into_post( + FullParametersRef::new(parameters, utxo_accumulator.model()), + &proving_context.to_private, + None, + rng, + ) + .expect("Unable to build TO_PRIVATE proof.") + .expect("Did not match transfer shape."); + let sender_0 = pre_sender_0 + .insert_and_upgrade(parameters, utxo_accumulator) + .expect(""); + let receiver_0 = Receiver::sample(parameters, address, asset_0, Default::default(), rng); + + let (to_private_1, pre_sender_1) = ToPrivate::internal_pair( + parameters, + &mut authorization.context, + address, + asset_1, + Default::default(), + rng, + ); + let to_private_1 = to_private_1 + .into_post( + FullParametersRef::new(parameters, utxo_accumulator.model()), + &proving_context.to_private, + None, + rng, + ) + .expect("Unable to build TO_PRIVATE proof.") + .expect("Did not match transfer shape."); + let sender_1 = pre_sender_1 + .insert_and_upgrade(parameters, utxo_accumulator) + .expect(""); + let receiver_1 = Receiver::sample(parameters, address, asset_1, Default::default(), rng); + + receiver_1.insert_utxo(parameters, utxo_accumulator); + receiver_0.insert_utxo(parameters, utxo_accumulator); + + let private_transfer = PrivateTransfer::build( + authorization, + [sender_0, sender_1], + [receiver_1, receiver_0], + ) .into_post( - FullParameters::new(parameters, utxo_accumulator_model), + FullParametersRef::new(parameters, utxo_accumulator.model()), + &proving_context.private_transfer, + Some(&spending_key), + rng, + ) + .expect("Unable to build PRIVATE_TRANSFER proof.") + .expect("Did not match transfer shape."); + + ([to_private_0, to_private_1], private_transfer) + } + + /// Generates a proof for a [`PrivateTransfer`] transaction. + #[inline] + pub fn prove( + proving_context: &ProvingContext, + parameters: &Parameters, + utxo_accumulator: &mut A, + rng: &mut R, + ) -> TransferPost + where + A: Accumulator, + R: CryptoRng + RngCore + ?Sized, + { + let asset_id = AssetId::gen(rng); + let values = value_distribution(2, rng.gen(), rng); + let spending_key = rng.gen(); + let address = parameters.address_from_spending_key(&spending_key); + let mut authorization = Authorization::from_spending_key(parameters, &spending_key, rng); + let (receiver_0, presender_0) = internal_pair::( + parameters, + &mut authorization.context, + address, + Asset::new(asset_id, values[0]), + Default::default(), + rng, + ); + let sender_0 = presender_0 + .insert_and_upgrade(parameters, utxo_accumulator) + .expect(""); + let (receiver_1, presender_1) = internal_pair::( + parameters, + &mut authorization.context, + address, + Asset::new(asset_id, values[1]), + Default::default(), + rng, + ); + let sender_1 = presender_1 + .insert_and_upgrade(parameters, utxo_accumulator) + .expect(""); + PrivateTransfer::build( + authorization, + [sender_0, sender_1], + [receiver_1, receiver_0], + ) + .into_post( + FullParametersRef::new(parameters, utxo_accumulator.model()), proving_context, + Some(&spending_key), rng, ) - .expect("Unable to build MINT proof.") + .expect("Unable to build PRIVATE_TRANSFER proof.") + .expect("") + } } -/// Samples a [`Mint`] spender. -/// -/// The spender is used in the [`prove_private_transfer`] and [`prove_reclaim`] functions for -/// benchmarking. Note that the [`Mint`] proof is not returned since it is not used when proving a -/// [`PrivateTransfer`] or [`Reclaim`]. -#[inline] -pub fn sample_mint_spender( - parameters: &Parameters, - utxo_accumulator: &mut UtxoAccumulator, - asset: Asset, - rng: &mut R, -) -> (config::SpendingKey, config::Sender) -where - R: CryptoRng + RngCore + ?Sized, -{ - let spending_key = SpendingKey::new(rng.gen(), rng.gen()); - let (_, pre_sender) = Mint::internal_pair(parameters, &spending_key, asset, rng); - let sender = pre_sender - .insert_and_upgrade(utxo_accumulator) - .expect("Just inserted so this should not fail."); - (spending_key, sender) -} +/// Utility Module for [`ToPublic`] +pub mod to_public { + use super::*; -/// Generates a proof for a [`PrivateTransfer`] transaction. -#[inline] -pub fn prove_private_transfer( - proving_context: &MultiProvingContext, - parameters: &Parameters, - utxo_accumulator_model: &UtxoAccumulatorModel, - rng: &mut R, -) -> config::TransferPost -where - R: CryptoRng + RngCore + ?Sized, -{ - let asset_id = AssetId(rng.gen()); - let asset_0 = asset::Asset { - id: asset_id, - value: AssetValue(10_000), - }; - let asset_1 = asset::Asset { - id: asset_id, - value: AssetValue(20_000), - }; - let mut utxo_accumulator = UtxoAccumulator::new(utxo_accumulator_model.clone()); - let (spending_key_0, sender_0) = - sample_mint_spender(parameters, &mut utxo_accumulator, asset_0, rng); - let (spending_key_1, sender_1) = - sample_mint_spender(parameters, &mut utxo_accumulator, asset_1, rng); - PrivateTransfer::build( - [sender_0, sender_1], - [ - spending_key_0.receiver(parameters, rng.gen(), asset_1), - spending_key_1.receiver(parameters, rng.gen(), asset_0), - ], - ) - .into_post( - FullParameters::new(parameters, utxo_accumulator.model()), - &proving_context.private_transfer, - rng, - ) - .expect("Unable to build PRIVATE_TRANSFER proof.") -} + /// Generates a proof for a [`ToPublic`] transaction including pre-requisite [`ToPrivate`] + /// transactions. + #[inline] + pub fn prove_full( + proving_context: &MultiProvingContext, + parameters: &Parameters, + utxo_accumulator: &mut A, + asset_id: AssetId, + values: [AssetValue; 2], + rng: &mut R, + ) -> ([TransferPost; 2], TransferPost) + where + A: Accumulator, + R: CryptoRng + RngCore + ?Sized, + { + let asset_0 = Asset::new(asset_id, values[0]); + let asset_1 = Asset::new(asset_id, values[1]); + let spending_key = rng.gen(); + let address = parameters.address_from_spending_key(&spending_key); + let mut authorization = Authorization::from_spending_key(parameters, &spending_key, rng); -/// Generates a proof for a [`Reclaim`] transaction. -#[inline] -pub fn prove_reclaim( - proving_context: &MultiProvingContext, - parameters: &Parameters, - utxo_accumulator_model: &UtxoAccumulatorModel, - rng: &mut R, -) -> config::TransferPost -where - R: CryptoRng + RngCore + ?Sized, -{ - let asset_id = AssetId(rng.gen()); - let asset_0 = asset::Asset { - id: asset_id, - value: AssetValue(10_000), - }; - let asset_1 = asset::Asset { - id: asset_id, - value: AssetValue(20_000), - }; - let mut utxo_accumulator = UtxoAccumulator::new(utxo_accumulator_model.clone()); - let (spending_key_0, sender_0) = - sample_mint_spender(parameters, &mut utxo_accumulator, asset_0, rng); - let (_, sender_1) = sample_mint_spender(parameters, &mut utxo_accumulator, asset_1, rng); - Reclaim::build( - [sender_0, sender_1], - [spending_key_0.receiver(parameters, rng.gen(), asset_1)], - asset_0, - ) - .into_post( - FullParameters::new(parameters, utxo_accumulator.model()), - &proving_context.reclaim, - rng, - ) - .expect("Unable to build RECLAIM proof.") + let (to_private_0, pre_sender_0) = ToPrivate::internal_pair( + parameters, + &mut authorization.context, + address, + asset_0, + Default::default(), + rng, + ); + let to_private_0 = to_private_0 + .into_post( + FullParametersRef::new(parameters, utxo_accumulator.model()), + &proving_context.to_private, + None, + rng, + ) + .expect("Unable to build TO_PRIVATE proof.") + .expect("Did not match transfer shape."); + let sender_0 = pre_sender_0 + .insert_and_upgrade(parameters, utxo_accumulator) + .expect(""); + + let (to_private_1, pre_sender_1) = ToPrivate::internal_pair( + parameters, + &mut authorization.context, + address, + asset_1, + Default::default(), + rng, + ); + let to_private_1 = to_private_1 + .into_post( + FullParametersRef::new(parameters, utxo_accumulator.model()), + &proving_context.to_private, + None, + rng, + ) + .expect("Unable to build TO_PRIVATE proof.") + .expect("Did not match transfer shape."); + let sender_1 = pre_sender_1 + .insert_and_upgrade(parameters, utxo_accumulator) + .expect(""); + let receiver_1 = Receiver::sample(parameters, address, asset_0, Default::default(), rng); + receiver_1.insert_utxo(parameters, utxo_accumulator); + + let to_public = ToPublic::build(authorization, [sender_0, sender_1], [receiver_1], asset_1) + .into_post( + FullParametersRef::new(parameters, utxo_accumulator.model()), + &proving_context.to_public, + Some(&spending_key), + rng, + ) + .expect("Unable to build TO_PUBLIC proof.") + .expect("Did not match transfer shape."); + + ([to_private_0, to_private_1], to_public) + } + + /// Generates a proof for a [`ToPublic`] transaction. + #[inline] + pub fn prove( + proving_context: &ProvingContext, + parameters: &Parameters, + utxo_accumulator: &mut A, + rng: &mut R, + ) -> TransferPost + where + A: Accumulator, + R: CryptoRng + RngCore + ?Sized, + { + let asset_id = AssetId::gen(rng); + let values = value_distribution(2, rng.gen(), rng); + let asset_0 = Asset::new(asset_id, values[0]); + let spending_key = rng.gen(); + let address = parameters.address_from_spending_key(&spending_key); + let mut authorization = Authorization::from_spending_key(parameters, &spending_key, rng); + let (_, presender_0) = internal_pair::( + parameters, + &mut authorization.context, + address, + asset_0, + Default::default(), + rng, + ); + let sender_0 = presender_0 + .insert_and_upgrade(parameters, utxo_accumulator) + .expect(""); + let (receiver_1, presender_1) = internal_pair::( + parameters, + &mut authorization.context, + address, + Asset::new(asset_id, values[1]), + Default::default(), + rng, + ); + let sender_1 = presender_1 + .insert_and_upgrade(parameters, utxo_accumulator) + .expect(""); + ToPublic::build(authorization, [sender_0, sender_1], [receiver_1], asset_0) + .into_post( + FullParametersRef::new(parameters, utxo_accumulator.model()), + proving_context, + Some(&spending_key), + rng, + ) + .expect("Unable to build TO_PUBLIC proof.") + .expect("") + } } diff --git a/manta-pay/src/test/simulation/mod.rs b/manta-pay/src/test/simulation/mod.rs deleted file mode 100644 index 39e98e486..000000000 --- a/manta-pay/src/test/simulation/mod.rs +++ /dev/null @@ -1,658 +0,0 @@ -// Copyright 2019-2022 Manta Network. -// This file is part of manta-rs. -// -// manta-rs is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// manta-rs is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with manta-rs. If not, see . - -//! Manta Pay Simulation - -// TODO: Implement asynchronous/dynamic simulation and have this static simulation as a degenerate -// form of this simulation when "asynchronousity" is turned down to zero. - -use core::{cmp::min, ops::Range}; -use indexmap::IndexMap; -use manta_accounting::asset::{Asset, AssetId, AssetValue}; -use manta_crypto::rand::{CryptoRng, RngCore, Sample}; -use rand::{distributions::Distribution, seq::SliceRandom, Rng}; -use statrs::{ - distribution::{Categorical, Discrete, Poisson}, - StatsError, -}; -use std::collections::HashMap; - -/// Choose `count`-many elements from `vec` randomly and drop the remaining ones. -#[inline] -fn choose_multiple(vec: &mut Vec, count: usize, rng: &mut R) -where - R: RngCore + ?Sized, -{ - let drop_count = vec.partial_shuffle(rng, count).1.len(); - vec.drain(0..drop_count); -} - -/// Action Types -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum Action { - /// No Action - None, - - /// Public Deposit Action - PublicDeposit, - - /// Public Withdraw Action - PublicWithdraw, - - /// Mint Action - Mint, - - /// Private Transfer Action - PrivateTransfer, - - /// Reclaim Action - Reclaim, -} - -/// Action Distribution Probability Mass Function -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct ActionDistributionPMF { - /// No Action Weight - pub none: T, - - /// Public Deposit Action Weight - pub public_deposit: T, - - /// Public Withdraw Action Weight - pub public_withdraw: T, - - /// Mint Action Weight - pub mint: T, - - /// Private Transfer Action Weight - pub private_transfer: T, - - /// Reclaim Action Weight - pub reclaim: T, -} - -impl Default for ActionDistributionPMF { - #[inline] - fn default() -> Self { - Self { - none: 1.0, - public_deposit: 1.0, - public_withdraw: 1.0, - mint: 1.0, - private_transfer: 1.0, - reclaim: 1.0, - } - } -} - -impl From for ActionDistributionPMF { - #[inline] - fn from(actions: ActionDistribution) -> Self { - Self { - none: actions.distribution.pmf(0), - public_deposit: actions.distribution.pmf(1), - public_withdraw: actions.distribution.pmf(2), - mint: actions.distribution.pmf(3), - private_transfer: actions.distribution.pmf(4), - reclaim: actions.distribution.pmf(5), - } - } -} - -/// Action Distribution -#[derive(Clone, Debug, PartialEq)] -pub struct ActionDistribution { - /// Distribution over Actions - distribution: Categorical, -} - -impl Default for ActionDistribution { - #[inline] - fn default() -> Self { - Self::try_from(ActionDistributionPMF::default()).unwrap() - } -} - -impl TryFrom for ActionDistribution { - type Error = StatsError; - - #[inline] - fn try_from(pmf: ActionDistributionPMF) -> Result { - Ok(Self { - distribution: Categorical::new(&[ - pmf.none, - pmf.public_deposit, - pmf.public_withdraw, - pmf.mint, - pmf.private_transfer, - pmf.reclaim, - ])?, - }) - } -} - -impl Distribution for ActionDistribution { - #[inline] - fn sample(&self, rng: &mut R) -> Action - where - R: RngCore + ?Sized, - { - match self.distribution.sample(rng) as usize { - 0 => Action::None, - 1 => Action::PublicDeposit, - 2 => Action::PublicWithdraw, - 3 => Action::Mint, - 4 => Action::PrivateTransfer, - 5 => Action::Reclaim, - _ => unreachable!(), - } - } -} - -impl Sample for Action { - #[inline] - fn sample(distribution: ActionDistribution, rng: &mut R) -> Self - where - R: CryptoRng + RngCore + ?Sized, - { - distribution.sample(rng) - } -} - -/// Balance State -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct BalanceState { - /// Asset Map - map: HashMap, -} - -impl BalanceState { - /// Returns the asset balance associated to the assets with the given `id`. - #[inline] - pub fn balance(&self, id: AssetId) -> AssetValue { - self.map.get(&id).copied().unwrap_or_default() - } - - /// Returns `true` if `self` contains at least `value` amount of the asset with the given `id`. - #[inline] - pub fn contains(&self, id: AssetId, value: AssetValue) -> bool { - self.balance(id) >= value - } - - /// Deposit `asset` into `self`. - #[inline] - pub fn deposit(&mut self, asset: Asset) { - *self.map.entry(asset.id).or_default() += asset.value; - } - - /// Withdraw `asset` from `self`, returning `false` if it would overdraw the balance. - #[inline] - pub fn withdraw(&mut self, asset: Asset) -> bool { - if asset.value == 0 { - true - } else { - self.map - .get_mut(&asset.id) - .map(move |balance| { - if let Some(result) = balance.checked_sub(asset.value) { - *balance = result; - true - } else { - false - } - }) - .unwrap_or(false) - } - } -} - -/// User Account -#[derive(Clone, Debug, Default, PartialEq)] -pub struct Account { - /// Public Balances - pub public: BalanceState, - - /// Secret Balances - pub secret: BalanceState, - - /// Action Distribution - pub actions: ActionDistribution, -} - -impl Account { - /// Samples a new account sampled using `config` settings and `rng`. - #[inline] - pub fn sample(config: &Config, rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - let mut public = BalanceState::default(); - // TODO: Use a better distribution to sample a starting balance. - for _ in 0usize..rng.gen_range(0..50) { - public.deposit(config.sample_asset(rng)); - } - Self { - public, - secret: Default::default(), - actions: ActionDistribution::try_from(ActionDistributionPMF { - none: rng.gen_range(config.action_sampling_ranges.none.clone()), - public_deposit: rng.gen_range(config.action_sampling_ranges.public_deposit.clone()), - public_withdraw: rng - .gen_range(config.action_sampling_ranges.public_withdraw.clone()), - mint: rng.gen_range(config.action_sampling_ranges.mint.clone()), - private_transfer: rng - .gen_range(config.action_sampling_ranges.private_transfer.clone()), - reclaim: rng.gen_range(config.action_sampling_ranges.reclaim.clone()), - }) - .unwrap(), - } - } -} - -/// Simulation Update -#[derive(Clone, Debug, PartialEq)] -pub enum Update { - /// Create Account - CreateAccount { - /// Account to Create - account: Account, - }, - - /// Deposit Public Balance - PublicDeposit { - /// Index of Target Account - account_index: usize, - - /// Asset to Deposit - asset: Asset, - }, - - /// Withdraw Public Balance - PublicWithdraw { - /// Index of Target Account - account_index: usize, - - /// Asset to Withdraw - asset: Asset, - }, - - /// Mint Asset - Mint { - /// Source Index - source_index: usize, - - /// Asset to Mint - asset: Asset, - }, - - /// Private Transfer Asset - PrivateTransfer { - /// Sender Index - sender_index: usize, - - /// Receiver Index - receiver_index: usize, - - /// Asset to Private Transfer - asset: Asset, - }, - - /// Reclaim Asset - Reclaim { - /// Reclaim Index - sender_index: usize, - - /// Asset to Reclaim - asset: Asset, - }, -} - -/// Simulation Configuration -#[derive(Clone, Debug, PartialEq)] -pub struct Config { - /// Number of starting accounts - pub starting_account_count: u64, - - /// Number of simulation steps before creating new accounts - pub new_account_sampling_cycle: u64, - - /// [`Poisson`] growth rate of the number of accounts - /// - /// This configuration setting is not used if `new_account_sampling_cycle == 0`. - pub account_count_growth_rate: f64, - - /// Maximum number of accounts - /// - /// If this value is less than `starting_account_count`, the maximum count is ignored. - pub maximum_account_count: u64, - - /// Which assets are allowed to be sampled and the maximum per sample - pub allowed_asset_sampling: IndexMap, - - /// Action Sampling Ranges - /// - /// This is a distribution over an [`ActionDistribution`] which is used to sample an - /// [`ActionDistribution`] for a particular account. - pub action_sampling_ranges: ActionDistributionPMF>, - - /// Maximum number of updates allowed per step - /// - /// If this value is `0`, it has no effect. - pub maximum_updates_per_step: u32, - - /// Maximum number of total updates - /// - /// If this value is `0`, it has no effect. - pub maximum_total_updates: u32, -} - -impl Config { - /// Returns `true` if `self` has an active account count maximum. - #[inline] - fn has_maximum_account_count(&self) -> bool { - self.maximum_account_count >= self.starting_account_count - } - - /// Returns `true` if `accounts` is equal to the account count maximum, if it is active. - #[inline] - fn maximum_account_count_has_been_reached(&self, accounts: u64) -> bool { - self.has_maximum_account_count() && self.maximum_account_count == accounts - } - - /// Returns `true` if new accounts should be created for the current `step_counter` and an - /// account list with `accounts`-many elements. - #[inline] - fn should_create_new_accounts(&self, step_counter: u64, accounts: u64) -> bool { - self.maximum_account_count != self.starting_account_count - && !self.maximum_account_count_has_been_reached(accounts) - && self.new_account_sampling_cycle != 0 - && step_counter % self.new_account_sampling_cycle == 0 - } - - /// Samples an allowed asset using `rng`. - #[inline] - fn sample_asset(&self, rng: &mut R) -> Asset - where - R: RngCore + ?Sized, - { - let id = self.sample_asset_id(rng); - Asset::new(id, self.sample_asset_value(id, rng)) - } - - /// Samples an allowed asset id using `rng`. - #[inline] - fn sample_asset_id(&self, rng: &mut R) -> AssetId - where - R: RngCore + ?Sized, - { - let mut ids = self.allowed_asset_sampling.keys(); - *ids.nth(rng.gen_range(0..ids.len())).unwrap() - } - - /// Samples an allowed asset value of the given `id` using `rng`. - #[inline] - fn sample_asset_value(&self, id: AssetId, rng: &mut R) -> AssetValue - where - R: RngCore + ?Sized, - { - AssetValue(rng.gen_range(0..=self.allowed_asset_sampling[&id].0)) - } - - /// Samples an allowed withdraw from `balances`. - #[inline] - fn sample_withdraw(&self, balances: &BalanceState, rng: &mut R) -> Asset - where - R: RngCore + ?Sized, - { - let mut ids = self.allowed_asset_sampling.keys().collect::>(); - ids.shuffle(rng); - for id in &ids { - let balance = balances.balance(**id); - if balance != 0 { - return Asset::new( - **id, - AssetValue( - rng.gen_range(0..=min(balance.0, self.allowed_asset_sampling[*id].0)), - ), - ); - } - } - Asset::zero(*ids[ids.len() - 1]) - } -} - -/// Simulator -#[derive(Clone, Debug, PartialEq)] -pub struct Simulator { - /// Configuration - config: Config, - - /// Step Counter - step_counter: u64, - - /// Accounts - accounts: Vec, -} - -impl Simulator { - /// Builds a new [`Simulator`] from the given `config`, sampling from `rng`. - #[inline] - pub fn new(config: Config, rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - Self { - accounts: (0..config.starting_account_count) - .map(|_| Account::sample(&config, rng)) - .collect(), - step_counter: Default::default(), - config, - } - } - - /// Computes one step of the simulation using `rng`. - #[inline] - pub fn step(&self, rng: &mut R) -> Vec - where - R: RngCore + ?Sized, - { - let mut updates = Vec::new(); - for (i, account) in self.accounts.iter().enumerate() { - match account.actions.sample(rng) { - Action::None => {} - Action::PublicDeposit => { - updates.push(Update::PublicDeposit { - account_index: i, - asset: self.config.sample_asset(rng), - }); - } - Action::PublicWithdraw => { - updates.push(Update::PublicWithdraw { - account_index: i, - asset: self.config.sample_withdraw(&account.public, rng), - }); - } - Action::Mint => { - updates.push(Update::Mint { - source_index: i, - asset: self.config.sample_withdraw(&account.public, rng), - }); - } - Action::PrivateTransfer => { - updates.push(Update::PrivateTransfer { - sender_index: i, - receiver_index: rng.gen_range(0..self.accounts.len()), - asset: self.config.sample_withdraw(&account.secret, rng), - }); - } - Action::Reclaim => { - updates.push(Update::Reclaim { - sender_index: i, - asset: self.config.sample_withdraw(&account.secret, rng), - }); - } - } - } - let accounts_len = self.accounts.len() as u64; - if self - .config - .should_create_new_accounts(self.step_counter, accounts_len) - { - let mut new_accounts = Poisson::new(self.config.account_count_growth_rate) - .unwrap() - .sample(rng) as u64; - if self.config.has_maximum_account_count() { - new_accounts = - new_accounts.clamp(0, self.config.maximum_account_count - accounts_len); - } - for _ in 0..new_accounts { - updates.push(Update::CreateAccount { - account: Account::sample(&self.config, rng), - }); - } - } - if self.config.maximum_updates_per_step > 0 { - choose_multiple( - &mut updates, - self.config.maximum_updates_per_step as usize, - rng, - ); - } - updates - } - - /// Applies `update` to the internal state of the simulator, returning the update back - /// if an error occured. - #[inline] - pub fn apply(&mut self, update: Update) -> Result<(), Update> { - match &update { - Update::CreateAccount { account } => { - self.accounts.push(account.clone()); - return Ok(()); - } - Update::PublicDeposit { - account_index, - asset, - } => { - if let Some(balances) = self.accounts.get_mut(*account_index) { - balances.public.deposit(*asset); - return Ok(()); - } - } - Update::PublicWithdraw { - account_index, - asset, - } => { - if let Some(balances) = self.accounts.get_mut(*account_index) { - if balances.public.withdraw(*asset) { - return Ok(()); - } - } - } - Update::Mint { - source_index, - asset, - } => { - if let Some(balances) = self.accounts.get_mut(*source_index) { - if balances.public.withdraw(*asset) { - balances.secret.deposit(*asset); - return Ok(()); - } - } - } - Update::PrivateTransfer { - sender_index, - receiver_index, - asset, - } => { - if let Some(sender) = self.accounts.get_mut(*sender_index) { - if sender.secret.withdraw(*asset) { - if let Some(receiver) = self.accounts.get_mut(*receiver_index) { - receiver.secret.deposit(*asset); - return Ok(()); - } - } - } - } - Update::Reclaim { - sender_index, - asset, - } => { - if let Some(balances) = self.accounts.get_mut(*sender_index) { - if balances.secret.withdraw(*asset) { - balances.public.deposit(*asset); - return Ok(()); - } - } - } - } - Err(update) - } - - /// Runs `self` for the given number of `steps`. - #[inline] - pub fn run(&mut self, steps: usize, rng: &mut R) -> Simulation - where - R: RngCore + ?Sized, - { - let initial_accounts = self.accounts.clone(); - let mut updates = Vec::new(); - for _ in 0..steps { - let mut next_updates = self.step(rng); - let update_limit = self.config.maximum_total_updates as usize; - if update_limit > 0 { - match update_limit - updates.len() { - 0 => break, - diff => next_updates.truncate(diff), - } - } - for update in &next_updates { - if let Err(update) = self.apply(update.clone()) { - panic!( - "ERROR: {}\n\n Panicked on the following state:\nSimulation: {:?}\nUpdate: {:?}", - "This is an internal simulation error. Please file a bug.", - self, - update - ); - } - } - updates.append(&mut next_updates); - self.step_counter += 1; - } - Simulation { - config: self.config.clone(), - initial_accounts, - final_accounts: self.accounts.clone(), - updates, - } - } -} - -/// Simulation Final State -#[derive(Clone, Debug, PartialEq)] -pub struct Simulation { - /// Configuration - pub config: Config, - - /// Initial Account State - pub initial_accounts: Vec, - - /// Final Account State - pub final_accounts: Vec, - - /// Updates - pub updates: Vec, -} diff --git a/manta-pay/src/test/transfer.rs b/manta-pay/src/test/transfer.rs index 9362a3cad..b6cb832a6 100644 --- a/manta-pay/src/test/transfer.rs +++ b/manta-pay/src/test/transfer.rs @@ -17,59 +17,57 @@ //! Manta Pay Transfer Testing use crate::{ - config::{FullParameters, Mint, PrivateTransfer, ProofSystem, Reclaim}, + config::{FullParametersRef, Parameters, PrivateTransfer, ProofSystem, ToPrivate, ToPublic}, test::payment::UtxoAccumulator, }; -use core::fmt::Debug; -use manta_accounting::transfer::{ - test::assert_valid_proof, Configuration, ProofSystemError, TransferPost, VerifyingContext, -}; +use manta_accounting::transfer::test::validity_check_with_fuzzing; use manta_crypto::{ accumulator::Accumulator, - constraint::{self, measure::Measure, test::verify_fuzz_public_input, ProofSystem as _}, - rand::{fuzz::Fuzz, OsRng, Rand, RngCore, Sample}, + constraint::{measure::Measure, ProofSystem as _}, + rand::{OsRng, Rand, Sample}, }; -/// Tests the generation of proving/verifying contexts for [`Mint`]. +/// Tests the generation of proving/verifying contexts for [`ToPrivate`]. #[test] -fn sample_mint_context() { +fn sample_to_private_context() { let mut rng = OsRng; - let cs = Mint::unknown_constraints(FullParameters::new(&rng.gen(), &rng.gen())); - println!("Mint: {:?}", cs.measure()); - ProofSystem::compile(&(), cs, &mut rng).expect("Unable to generate Mint context."); + let cs = ToPrivate::unknown_constraints(FullParametersRef::new(&rng.gen(), &rng.gen())); + println!("ToPrivate: {:?}", cs.measure()); + ProofSystem::compile(&(), cs, &mut rng).expect("Unable to generate ToPrivate context."); } /// Tests the generation of proving/verifying contexts for [`PrivateTransfer`]. #[test] fn sample_private_transfer_context() { let mut rng = OsRng; - let cs = PrivateTransfer::unknown_constraints(FullParameters::new(&rng.gen(), &rng.gen())); + let cs = PrivateTransfer::unknown_constraints(FullParametersRef::new(&rng.gen(), &rng.gen())); println!("PrivateTransfer: {:?}", cs.measure()); ProofSystem::compile(&(), cs, &mut rng).expect("Unable to generate PrivateTransfer context."); } -/// Tests the generation of proving/verifying contexts for [`Reclaim`]. +/// Tests the generation of proving/verifying contexts for [`ToPublic`]. #[test] -fn sample_reclaim_context() { +fn sample_to_public_context() { let mut rng = OsRng; - let cs = Reclaim::unknown_constraints(FullParameters::new(&rng.gen(), &rng.gen())); - println!("Reclaim: {:?}", cs.measure()); - ProofSystem::compile(&(), cs, &mut rng).expect("Unable to generate Reclaim context."); + let cs = ToPublic::unknown_constraints(FullParametersRef::new(&rng.gen(), &rng.gen())); + println!("ToPublic: {:?}", cs.measure()); + ProofSystem::compile(&(), cs, &mut rng).expect("Unable to generate ToPublic context."); } -/// Tests the generation of a [`Mint`]. +/// Tests the generation of a [`ToPrivate`]. #[test] -fn mint() { +fn to_private() { let mut rng = OsRng; assert!( - Mint::sample_and_check_proof( + ToPrivate::sample_and_check_proof( &(), &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), + None, &mut rng ) - .expect("Random Mint should have successfully produced a proof."), - "The Mint proof should have been valid." + .expect("Random ToPrivate should have successfully produced a proof."), + "The ToPrivate proof should have been valid." ); } @@ -82,6 +80,7 @@ fn private_transfer() { &(), &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), + Some(&rng.gen()), &mut rng ) .expect("Random PrivateTransfer should have successfully produced a proof."), @@ -89,116 +88,257 @@ fn private_transfer() { ); } -/// Tests the generation of a [`Reclaim`]. +/// Tests the generation of a [`ToPublic`]. #[test] -fn reclaim() { +fn to_public() { let mut rng = OsRng; assert!( - Reclaim::sample_and_check_proof( + ToPublic::sample_and_check_proof( &(), &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), + Some(&rng.gen()), &mut rng ) - .expect("Random Reclaim should have successfully produced a proof."), - "The Reclaim proof should have been valid." + .expect("Random ToPublic should have successfully produced a proof."), + "The ToPublic proof should have been valid." + ); +} + +/// Checks that an empty message will produce a valid signature. +#[test] +fn check_empty_message_signature() { + let mut rng = OsRng; + assert!( + manta_crypto::signature::test::correctness( + &Parameters::gen(&mut rng).signature_scheme(), + &rng.gen(), + &rng.gen(), + &vec![], + &mut (), + ), + "Unable to verify signature correctly." + ); +} + +/// Checks that a random [`PrivateTransfer`] produces a valid transaction signature. +#[test] +fn private_transfer_check_signature() { + let mut rng = OsRng; + let parameters = rng.gen(); + let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); + let (proving_context, verifying_context) = PrivateTransfer::generate_context( + &(), + FullParametersRef::new(¶meters, utxo_accumulator.model()), + &mut rng, + ) + .expect("Unable to create proving and verifying contexts."); + let spending_key = rng.gen(); + let post = PrivateTransfer::sample_post( + &proving_context, + ¶meters, + &mut utxo_accumulator, + Some(&spending_key), + &mut rng, + ) + .expect("Random Private Transfer should have produced a proof.") + .expect(""); + post.assert_valid_proof(&verifying_context); + assert!( + manta_accounting::transfer::utxo::auth::test::signature_correctness( + ¶meters, + &spending_key, + &post.body, + &mut rng, + ), + "Invalid signature" + ); +} + +/// Checks that a random [`ToPublic`] produces a valid transaction signature. +#[test] +fn to_public_check_signature() { + let mut rng = OsRng; + let parameters = rng.gen(); + let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); + let (proving_context, verifying_context) = ToPublic::generate_context( + &(), + FullParametersRef::new(¶meters, utxo_accumulator.model()), + &mut rng, + ) + .expect("Unable to create proving and verifying contexts."); + let spending_key = rng.gen(); + let post = ToPublic::sample_post( + &proving_context, + ¶meters, + &mut utxo_accumulator, + Some(&spending_key), + &mut rng, + ) + .expect("Random To-Public should have produced a proof.") + .expect(""); + post.assert_valid_proof(&verifying_context); + assert!( + manta_accounting::transfer::utxo::auth::test::signature_correctness( + ¶meters, + &spending_key, + &post.body, + &mut rng, + ), + "Invalid signature." + ); +} + +/// Checks that the zero signature is rejected for a random [`PrivateTransfer`]. +#[test] +fn private_transfer_check_zero_signature() { + let mut rng = OsRng; + let parameters = rng.gen(); + let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); + let (proving_context, verifying_context) = PrivateTransfer::generate_context( + &(), + FullParametersRef::new(¶meters, utxo_accumulator.model()), + &mut rng, + ) + .expect("Unable to create proving and verifying contexts."); + let spending_key = Default::default(); + let post = PrivateTransfer::sample_post( + &proving_context, + ¶meters, + &mut utxo_accumulator, + Some(&spending_key), + &mut rng, + ) + .expect("Random Private Transfer should have produced a proof.") + .expect(""); + post.assert_valid_proof(&verifying_context); + assert!( + !manta_accounting::transfer::utxo::auth::test::signature_correctness( + ¶meters, + &spending_key, + &post.body, + &mut rng, + ), + "Zero signature can't be valid!" ); } -/// Tests that `generate_proof_input` from [`Transfer`] and [`TransferPost`] gives the same [`ProofInput`]. +/// Checks that the zero signature is rejected for a random [`ToPublic`]. #[test] -fn generate_proof_input_is_compatibile() { +fn to_public_check_zero_signature() { + let mut rng = OsRng; + let parameters = rng.gen(); + let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); + let (proving_context, verifying_context) = ToPublic::generate_context( + &(), + FullParametersRef::new(¶meters, utxo_accumulator.model()), + &mut rng, + ) + .expect("Unable to create proving and verifying contexts."); + let spending_key = Default::default(); + let post = ToPublic::sample_post( + &proving_context, + ¶meters, + &mut utxo_accumulator, + Some(&spending_key), + &mut rng, + ) + .expect("Random To-Public should have produced a proof.") + .expect(""); + post.assert_valid_proof(&verifying_context); + assert!( + !manta_accounting::transfer::utxo::auth::test::signature_correctness( + ¶meters, + &spending_key, + &post.body, + &mut rng, + ), + "Zero signature can't be valid!" + ); +} + +/// Tests that `generate_proof_input` from [`Transfer`] and [`TransferPost`] gives the same +/// [`ProofInput`] for [`ToPrivate`]. +#[test] +fn to_private_generate_proof_input_is_compatibile() { let mut rng = OsRng; assert!( matches!( - Mint::sample_and_check_generate_proof_input_compatibility( + ToPrivate::sample_and_check_generate_proof_input_compatibility( &(), &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), + None, &mut rng ), Ok(true), ), - "For a random Mint, `generate_proof_input` from `Transfer` and `TransferPost` should have given the same `ProofInput`." + "For a random ToPrivate, `generate_proof_input` from `Transfer` and `TransferPost` should have given the same `ProofInput`." ); +} + +/// Tests that `generate_proof_input` from [`Transfer`] and [`TransferPost`] gives the same +/// [`ProofInput`] for [`PrivateTransfer`]. +#[test] +fn private_transfer_generate_proof_input_is_compatibile() { + let mut rng = OsRng; assert!( matches!( PrivateTransfer::sample_and_check_generate_proof_input_compatibility( &(), &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), + Some(&rng.gen()), &mut rng ), Ok(true), ), "For a random PrivateTransfer, `generate_proof_input` from `Transfer` and `TransferPost` should have given the same `ProofInput`." ); +} + +/// Tests that `generate_proof_input` from [`Transfer`] and [`TransferPost`] gives the same +/// [`ProofInput`] for [`ToPublic`]. +#[test] +fn to_public_generate_proof_input_is_compatibile() { + let mut rng = OsRng; assert!( matches!( - Reclaim::sample_and_check_generate_proof_input_compatibility( + ToPublic::sample_and_check_generate_proof_input_compatibility( &(), &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), + Some(&rng.gen()), &mut rng ), Ok(true), ), - "For a random Reclaim, `generate_proof_input` from `Transfer` and `TransferPost` should have given the same `ProofInput`." - ); -} - -/// Checks that a [`TransferPost`] is valid, and that its proof cannot be verified when tested against a fuzzed -/// or randomized `public_input`. -#[inline] -fn validity_check_with_fuzzing( - verifying_context: &VerifyingContext, - post: &TransferPost, - rng: &mut R, -) where - A: Clone + Sample + Fuzz, - C: Configuration, - C::ProofSystem: constraint::ProofSystem>, - ProofSystemError: Debug, - R: RngCore + ?Sized, - TransferPost: Debug, -{ - let public_input = post.generate_proof_input(); - let proof = &post.validity_proof; - assert_valid_proof(verifying_context, post); - verify_fuzz_public_input::( - verifying_context, - &public_input, - proof, - |input| input.fuzz(rng), - ); - verify_fuzz_public_input::( - verifying_context, - &public_input, - proof, - |input| (0..input.len()).map(|_| rng.gen()).collect(), + "For a random ToPublic, `generate_proof_input` from `Transfer` and `TransferPost` should have given the same `ProofInput`." ); } -/// Tests a [`Mint`] proof is valid verified against the right public input and invalid +/// Tests a [`ToPrivate`] proof is valid verified against the right public input and invalid /// when the public input has been fuzzed or randomly generated. #[test] -fn mint_proof_validity() { +fn to_private_proof_validity() { let mut rng = OsRng; let parameters = rng.gen(); let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); - let (proving_context, verifying_context) = Mint::generate_context( + let (proving_context, verifying_context) = ToPrivate::generate_context( &(), - FullParameters::new(¶meters, utxo_accumulator.model()), + FullParametersRef::new(¶meters, utxo_accumulator.model()), &mut rng, ) .expect("Unable to create proving and verifying contexts."); - let post = Mint::sample_post( + let post = ToPrivate::sample_post( &proving_context, ¶meters, &mut utxo_accumulator, + None, &mut rng, ) - .expect("Random Mint should have produced a proof."); + .expect("Random ToPrivate should have produced a proof.") + .expect("Random ToPrivate should have generated a TransferPost."); validity_check_with_fuzzing(&verifying_context, &post, &mut rng); } @@ -211,7 +351,7 @@ fn private_transfer_proof_validity() { let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); let (proving_context, verifying_context) = PrivateTransfer::generate_context( &(), - FullParameters::new(¶meters, utxo_accumulator.model()), + FullParametersRef::new(¶meters, utxo_accumulator.model()), &mut rng, ) .expect("Unable to create proving and verifying contexts."); @@ -219,31 +359,35 @@ fn private_transfer_proof_validity() { &proving_context, ¶meters, &mut utxo_accumulator, + Some(&rng.gen()), &mut rng, ) - .expect("Random Private Transfer should have produced a proof."); + .expect("Random Private Transfer should have produced a proof.") + .expect("Random Private Transfer should have generated a TransferPost."); validity_check_with_fuzzing(&verifying_context, &post, &mut rng); } -/// Tests a [`Reclaim`] proof is valid verified against the right public input and invalid +/// Tests a [`ToPublic`] proof is valid verified against the right public input and invalid /// when the public input has been fuzzed or randomly generated. #[test] -fn reclaim_proof_validity() { +fn to_public_proof_validity() { let mut rng = OsRng; let parameters = rng.gen(); let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); - let (proving_context, verifying_context) = Reclaim::generate_context( + let (proving_context, verifying_context) = ToPublic::generate_context( &(), - FullParameters::new(¶meters, utxo_accumulator.model()), + FullParametersRef::new(¶meters, utxo_accumulator.model()), &mut rng, ) .expect("Unable to create proving and verifying contexts."); - let post = Reclaim::sample_post( + let post = ToPublic::sample_post( &proving_context, ¶meters, &mut utxo_accumulator, + Some(&rng.gen()), &mut rng, ) - .expect("Random Reclaim should have produced a proof."); + .expect("Random ToPublic should have produced a proof.") + .expect("Random ToPublic should have generated a TransferPost."); validity_check_with_fuzzing(&verifying_context, &post, &mut rng); } diff --git a/manta-trusted-setup/src/groth16/ceremony/config/ppot.rs b/manta-trusted-setup/src/groth16/ceremony/config/ppot.rs index bca482b1d..33b5daa62 100644 --- a/manta-trusted-setup/src/groth16/ceremony/config/ppot.rs +++ b/manta-trusted-setup/src/groth16/ceremony/config/ppot.rs @@ -313,8 +313,7 @@ pub fn register(twitter_account: String, email: String) { &keypair.0, Default::default(), &format!( - "manta-trusted-setup-twitter:{}, manta-trusted-setup-email:{}", - twitter_account, email + "manta-trusted-setup-twitter:{twitter_account}, manta-trusted-setup-email:{email}", ), ) .expect("Signing message should succeed."); diff --git a/manta-trusted-setup/src/groth16/ceremony/coordinator.rs b/manta-trusted-setup/src/groth16/ceremony/coordinator.rs index f3b7b7e76..1eb1071f5 100644 --- a/manta-trusted-setup/src/groth16/ceremony/coordinator.rs +++ b/manta-trusted-setup/src/groth16/ceremony/coordinator.rs @@ -107,8 +107,7 @@ where ) -> Self { assert!( metadata.ceremony_size.matches(state.as_slice()), - "Mismatch of metadata `{:?}` and state.", - metadata, + "Mismatch of metadata `{metadata:?}` and state.", ); Self { registry, diff --git a/manta-trusted-setup/src/groth16/ceremony/mod.rs b/manta-trusted-setup/src/groth16/ceremony/mod.rs index 90b5b2f30..eba61ab1d 100644 --- a/manta-trusted-setup/src/groth16/ceremony/mod.rs +++ b/manta-trusted-setup/src/groth16/ceremony/mod.rs @@ -214,8 +214,7 @@ where f, "Unexpected error occurred. \ Please contact us at trusted-setup@manta.network and \ - paste the following error message in the email:\n{:?}", - err + paste the following error message in the email:\n{err:?}", ), } } diff --git a/manta-util/src/codec.rs b/manta-util/src/codec.rs index 378a6ceaa..b33b6682f 100644 --- a/manta-util/src/codec.rs +++ b/manta-util/src/codec.rs @@ -18,6 +18,7 @@ // TODO: Deprecate this in favor of pure `serde`. +use crate::Array; use core::{convert::Infallible, fmt::Debug, hash::Hash, marker::PhantomData}; #[cfg(feature = "alloc")] @@ -653,6 +654,19 @@ where } } +impl Encode for Array +where + T: Encode, +{ + #[inline] + fn encode(&self, mut writer: W) -> Result<(), W::Error> + where + W: Write, + { + self.0.encode(&mut writer) + } +} + #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl Encode for Vec diff --git a/manta-util/src/num.rs b/manta-util/src/num.rs index a8f436d5a..052dbaec9 100644 --- a/manta-util/src/num.rs +++ b/manta-util/src/num.rs @@ -122,3 +122,27 @@ macro_rules! impl_checked { } impl_checked!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128); + +impl CheckedAdd for &A +where + A: Clone + CheckedAdd, +{ + type Output = A::Output; + + #[inline] + fn checked_add(self, rhs: Self) -> Option { + self.clone().checked_add(rhs.clone()) + } +} + +impl CheckedSub for &A +where + A: Clone + CheckedSub, +{ + type Output = A::Output; + + #[inline] + fn checked_sub(self, rhs: Self) -> Option { + self.clone().checked_sub(rhs.clone()) + } +}

for FullNullifier +where + C: Configuration, + P: HasInput> + ?Sized, +{ + #[inline] + fn extend(&self, input: &mut P::Input) { + P::extend(input, &self.nullifier.commitment); + } +} diff --git a/manta-accounting/src/wallet/balance.rs b/manta-accounting/src/wallet/balance.rs index 999d41236..d84bdd2ad 100644 --- a/manta-accounting/src/wallet/balance.rs +++ b/manta-accounting/src/wallet/balance.rs @@ -20,88 +20,97 @@ //! protocol. Applications which define balances beyond fungible assets should extend these //! abstractions. -use crate::{ - asset::AssetList, - transfer::{Asset, Configuration}, -}; +use crate::asset::{Asset, AssetList}; use alloc::collections::btree_map::{BTreeMap, Entry as BTreeMapEntry}; -use manta_util::num::CheckedSub; +use core::ops::AddAssign; +use manta_util::{ + iter::{ConvertItemRef, ExactSizeIterable, RefItem}, + num::CheckedSub, +}; #[cfg(feature = "std")] use std::{ collections::hash_map::{Entry as HashMapEntry, HashMap, RandomState}, hash::BuildHasher, + hash::Hash, }; /// Balance State -pub trait BalanceState: Default -where - C: Configuration, +pub trait BalanceState: + Default + ExactSizeIterable + for<'t> ConvertItemRef<'t, (&'t I, &'t V), Item = RefItem<'t, Self>> { /// Returns the current balance associated with this `id`. - fn balance(&self, id: C::AssetId) -> C::AssetValue; + fn balance(&self, id: &I) -> V; /// Returns true if `self` contains at least `asset.value` of the asset of kind `asset.id`. #[inline] - fn contains(&self, asset: Asset) -> bool { - self.balance(asset.id) >= asset.value + fn contains(&self, asset: &Asset) -> bool + where + V: PartialOrd, + { + self.balance(&asset.id) >= asset.value } /// Deposits `asset` into the balance state, increasing the balance of the asset stored at /// `asset.id` by an amount equal to `asset.value`. - fn deposit(&mut self, asset: Asset); + fn deposit(&mut self, asset: Asset); /// Deposits every asset in `assets` into the balance state. #[inline] - fn deposit_all(&mut self, assets: I) + fn deposit_all(&mut self, assets: A) where - I: IntoIterator>, + A: IntoIterator>, { assets.into_iter().for_each(move |a| self.deposit(a)); } /// Withdraws `asset` from the balance state returning `false` if it would overdraw the balance. - fn withdraw(&mut self, asset: Asset) -> bool; + fn withdraw(&mut self, asset: Asset) -> bool; /// Withdraws every asset in `assets` from the balance state, returning `false` if it would /// overdraw the balance. - #[inline] - fn withdraw_all(&mut self, assets: I) -> bool + fn withdraw_all(&mut self, assets: A) -> bool where - I: IntoIterator>, - { - for asset in AssetList::from_iter(assets) { - if !self.withdraw(asset) { - return false; - } - } - true - } + A: IntoIterator>; /// Clears the entire balance state. fn clear(&mut self); } -impl BalanceState for AssetList +impl BalanceState for AssetList where - C: Configuration, - for<'v> &'v C::AssetValue: CheckedSub, + I: Ord, + V: AddAssign + Clone + Default + PartialEq, + for<'v> &'v V: CheckedSub, { #[inline] - fn balance(&self, id: C::AssetId) -> C::AssetValue { - self.value(&id) + fn balance(&self, id: &I) -> V { + self.value(id) } #[inline] - fn deposit(&mut self, asset: Asset) { + fn deposit(&mut self, asset: Asset) { self.deposit(asset); } #[inline] - fn withdraw(&mut self, asset: Asset) -> bool { + fn withdraw(&mut self, asset: Asset) -> bool { self.withdraw(&asset) } + #[inline] + fn withdraw_all(&mut self, assets: A) -> bool + where + A: IntoIterator>, + { + for asset in AssetList::from_iter(assets) { + if !self.withdraw(&asset) { + return false; + } + } + true + } + #[inline] fn clear(&mut self) { self.clear(); @@ -110,15 +119,15 @@ where /// Adds implementation of [`BalanceState`] for a map type with the given `$entry` type. macro_rules! impl_balance_state_map_body { - ($entry:tt, $config: ty) => { + ($I:ty, $V:ty, $entry:tt) => { #[inline] - fn balance(&self, id: <$config>::AssetId) -> <$config>::AssetValue { - self.get(&id).copied().unwrap_or_default() + fn balance(&self, id: &$I) -> $V { + self.get(id).cloned().unwrap_or_default() } #[inline] - fn deposit(&mut self, asset: Asset<$config>) { - if asset.is_zero() { + fn deposit(&mut self, asset: Asset<$I, $V>) { + if asset.value == Default::default() { return; } match self.entry(asset.id) { @@ -130,11 +139,11 @@ macro_rules! impl_balance_state_map_body { } #[inline] - fn withdraw(&mut self, asset: Asset<$config>) -> bool { - if !asset.is_zero() { + fn withdraw(&mut self, asset: Asset<$I, $V>) -> bool { + if asset.value != Default::default() { if let $entry::Occupied(mut entry) = self.entry(asset.id) { let balance = entry.get_mut(); - if let Some(next_balance) = balance.checked_sub(asset.value) { + if let Some(next_balance) = balance.checked_sub(&asset.value) { if next_balance == Default::default() { entry.remove(); } else { @@ -149,6 +158,19 @@ macro_rules! impl_balance_state_map_body { } } + #[inline] + fn withdraw_all(&mut self, assets: A) -> bool + where + A: IntoIterator>, + { + for asset in AssetList::from_iter(assets) { + if !self.withdraw(asset) { + return false; + } + } + true + } + #[inline] fn clear(&mut self) { self.clear(); @@ -157,85 +179,80 @@ macro_rules! impl_balance_state_map_body { } /// B-Tree Map [`BalanceState`] Implementation -pub type BTreeMapBalanceState = - BTreeMap<::AssetId, ::AssetValue>; +pub type BTreeMapBalanceState = BTreeMap; -impl BalanceState for BTreeMapBalanceState +impl BalanceState for BTreeMapBalanceState where - C: Configuration, - C::AssetValue: CheckedSub, + I: Ord, + V: AddAssign + Clone + Default + PartialEq, + for<'v> &'v V: CheckedSub, { - impl_balance_state_map_body! { BTreeMapEntry, C } + impl_balance_state_map_body! { I, V, BTreeMapEntry } } /// Hash Map [`BalanceState`] Implementation #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] -pub type HashMapBalanceState = - HashMap<::AssetId, ::AssetValue, S>; +pub type HashMapBalanceState = HashMap; #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] -impl BalanceState for HashMapBalanceState +impl BalanceState for HashMapBalanceState where - C: Configuration, + I: Eq + Hash + Ord, + V: AddAssign + Clone + Default + PartialEq, + for<'v> &'v V: CheckedSub, S: BuildHasher + Default, - C::AssetValue: CheckedSub, { - impl_balance_state_map_body! { HashMapEntry, C } + impl_balance_state_map_body! { I, V, HashMapEntry } } /// Testing Framework #[cfg(any(feature = "test", test))] #[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] pub mod test { - - use super::*; - use crate::asset; + use crate::{asset::Asset, wallet::BalanceState}; use core::{fmt::Debug, ops::Add}; use manta_crypto::rand::{CryptoRng, RngCore, Sample}; /// Asserts that a random deposit and withdraw is always valid. #[inline] - pub fn assert_valid_withdraw(state: &mut S, rng: &mut R) + pub fn assert_valid_withdraw(state: &mut S, rng: &mut R) where - C: Configuration, - S: BalanceState, + I: Clone + Sample, + V: Add + Clone + Debug + PartialEq + Sample, + S: BalanceState, R: CryptoRng + RngCore + ?Sized, - C::AssetId: Sample, - C::AssetValue: Add + Debug + Sample, { - let asset = Asset::::gen(rng); - let initial_balance = state.balance(asset.clone().id); + let asset = Asset::gen(rng); + let initial_balance = state.balance(&asset.id); state.deposit(asset.clone()); assert_eq!( - initial_balance + asset.value, - state.balance(asset.clone().id), + initial_balance.clone() + asset.clone().value, + state.balance(&asset.id), "Current balance and sum of initial balance and new deposit should have been equal." ); state.withdraw(asset.clone()); assert_eq!( initial_balance, - state.balance(asset.id), + state.balance(&asset.id), "Initial and final balances should have been equal." ); } - /// Asserts that a maximal withdraw that leaves the state with no value should delete its memory /// for this process. #[inline] - pub fn assert_full_withdraw_should_remove_entry(rng: &mut R) + pub fn assert_full_withdraw_should_remove_entry(rng: &mut R) where - C: Configuration, - C::AssetId: Sample, - C::AssetValue: Sample + Debug, - S: BalanceState, + I: Clone + Sample, + V: Clone + Debug + Default + PartialEq + Sample, + S: BalanceState, for<'s> &'s S: IntoIterator, for<'s> <&'s S as IntoIterator>::IntoIter: ExactSizeIterator, R: CryptoRng + RngCore + ?Sized, { let mut state = S::default(); - let asset = Asset::::gen(rng); + let asset = Asset::gen(rng); let initial_length = state.into_iter().len(); state.deposit(asset.clone()); assert_eq!( @@ -243,13 +260,13 @@ pub mod test { state.into_iter().len(), "Length should have increased by one after depositing a new asset." ); - let balance = state.balance(asset.clone().id); - state.withdraw(asset::Asset { + let balance = state.balance(&asset.id); + state.withdraw(Asset { id: asset.clone().id, value: balance, }); assert_eq!( - state.balance(asset.id), + state.balance(&asset.id), Default::default(), "Balance in the removed AssetId should be zero." ); @@ -259,72 +276,4 @@ pub mod test { "Removed AssetId should remove its entry in the database." ); } - - /* - - Note: the following tests cannot be defined until we specify a transfer configuration. - - /// Defines the tests across multiple different [`BalanceState`] types. - macro_rules! define_tests { - ($(( - $type:ty, - $config: ty, - $doc:expr, - $valid_withdraw:ident, - $full_withdraw:ident - $(,)?)),*$(,)?) => { - $( - #[doc = "Tests valid withdrawals for an"] - #[doc = $doc] - #[doc = "balance state."] - #[test] - fn $valid_withdraw() { - let mut state = <$type>::default(); - let mut rng = OsRng; - for _ in 0..0xFFFF { - assert_valid_withdraw::<$config, _, _>(&mut state, &mut rng); - } - } - - #[doc = "Tests that there are no empty entries in"] - #[doc = $doc] - #[doc = "with no value stored in them."] - #[test] - fn $full_withdraw() { - assert_full_withdraw_should_remove_entry::<$config, $type, _>(&mut OsRng); - } - )* - } - } - - define_tests!( - ( - AssetList, - "[`AssetList`]", - asset_list_valid_withdraw, - asset_list_full_withdraw, - ), - ( - BTreeMapBalanceState, - "[`BTreeMapBalanceState`]", - btree_map_valid_withdraw, - btree_map_full_withdraw, - ), - ); - - /// Tests valid withdrawals for a [`HashMapBalanceState`] balance state. - #[cfg(feature = "std")] - #[test] - fn hash_map_valid_withdraw() { - assert_valid_withdraw(&mut HashMapBalanceState::new(), &mut OsRng); - } - - /// - #[cfg(feature = "std")] - #[test] - fn hash_map_full_withdraw() { - assert_full_withdraw_should_remove_entry::(&mut OsRng); - } - - */ } diff --git a/manta-accounting/src/wallet/ledger.rs b/manta-accounting/src/wallet/ledger.rs index 233b117c7..5a9f99f7d 100644 --- a/manta-accounting/src/wallet/ledger.rs +++ b/manta-accounting/src/wallet/ledger.rs @@ -62,10 +62,13 @@ pub trait Data where T: Checkpoint, { + /// Parameters Type + type Parameters; + /// Prunes the data in `self`, which was retrieved at `origin`, so that it meets the current /// `checkpoint`, dropping data that is older than the given `checkpoint`. This method should /// return `true` if it dropped data from `self`. - fn prune(&mut self, origin: &T, checkpoint: &T) -> bool; + fn prune(&mut self, parameters: &Self::Parameters, origin: &T, checkpoint: &T) -> bool; } /// Ledger Connection Reading diff --git a/manta-accounting/src/wallet/mod.rs b/manta-accounting/src/wallet/mod.rs index e6fb136bc..7b5bfff07 100644 --- a/manta-accounting/src/wallet/mod.rs +++ b/manta-accounting/src/wallet/mod.rs @@ -31,7 +31,7 @@ use crate::{ asset::{AssetList, AssetMetadata}, transfer::{ canonical::{Transaction, TransactionKind}, - Asset, Configuration, PublicKey, TransferPost, + Address, Asset, Configuration, TransferPost, }, wallet::{ balance::{BTreeMapBalanceState, BalanceState}, @@ -43,7 +43,7 @@ use crate::{ }, }; use alloc::vec::Vec; -use core::{fmt::Debug, hash::Hash, marker::PhantomData}; +use core::{fmt::Debug, hash::Hash, marker::PhantomData, ops::AddAssign}; use manta_util::ops::ControlFlow; #[cfg(feature = "serde")] @@ -58,12 +58,38 @@ pub mod signer; pub mod test; /// Wallet -pub struct Wallet, B = BTreeMapBalanceState> -where +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "L: Deserialize<'de>, S::Checkpoint: Deserialize<'de>, S: Deserialize<'de>, B: Deserialize<'de>", + serialize = "L: Serialize, S::Checkpoint: Serialize, S: Serialize, B: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "L: Clone, S::Checkpoint: Clone, S: Clone, B: Clone"), + Copy(bound = "L: Copy, S::Checkpoint: Copy, S: Copy, B: Copy"), + Debug(bound = "L: Debug, S::Checkpoint: Debug, S: Debug, B: Debug"), + Default(bound = "L: Default, S::Checkpoint: Default, S: Default, B: Default"), + Eq(bound = "L: Eq, S::Checkpoint: Eq, S: Eq, B: Eq"), + Hash(bound = "L: Hash, S::Checkpoint: Hash, S: Hash, B: Hash"), + PartialEq(bound = "L: PartialEq, S::Checkpoint: PartialEq, S: PartialEq, B: PartialEq") +)] +pub struct Wallet< + C, + L, + S = signer::Signer, + B = BTreeMapBalanceState<::AssetId, ::AssetValue>, +> where C: Configuration, L: ledger::Connection, S: signer::Connection, - B: BalanceState, + B: BalanceState, { /// Ledger Connection ledger: L, @@ -72,7 +98,7 @@ where checkpoint: S::Checkpoint, /// Signer Connection - pub signer: S, + signer: S, /// Balance State assets: B, @@ -86,7 +112,7 @@ where C: Configuration, L: ledger::Connection, S: signer::Connection, - B: BalanceState, + B: BalanceState, { /// Builds a new [`Wallet`] without checking if `ledger`, `checkpoint`, `signer`, and `assets` /// are properly synchronized. @@ -117,6 +143,17 @@ where Self::new_unchecked(ledger, Default::default(), signer, Default::default()) } + /// Returns a mutable reference to the [`Connection`](signer::Connection). + /// + /// # Crypto Safety + /// + /// Calls to this function cannot modify the signer in any way that would leave the + /// [`BalanceState`] invalid. + #[inline] + pub fn signer_mut(&mut self) -> &mut S { + &mut self.signer + } + /// Starts a new wallet with `ledger` and `signer` connections. #[inline] pub async fn start(ledger: L, signer: S) -> Result> @@ -137,26 +174,28 @@ where /// Returns the current balance associated with this `id`. #[inline] - pub fn balance(&self, id: C::AssetId) -> C::AssetValue { + pub fn balance(&self, id: &C::AssetId) -> C::AssetValue { self.assets.balance(id) } /// Returns true if `self` contains at least `asset.value` of the asset of kind `asset.id`. #[inline] - pub fn contains(&self, asset: Asset) -> bool { + pub fn contains(&self, asset: &Asset) -> bool { self.assets.contains(asset) } /// Returns `true` if `self` contains at least every asset in `assets`. Assets are combined - /// first by [`AssetId`](Configuration::AssetValue) before checking for membership. + /// first by asset id before checking for membership. #[inline] - pub fn contains_all(&self, assets: I) -> bool + pub fn contains_all(&self, assets: A) -> bool where - I: IntoIterator>, + C::AssetId: Ord, + C::AssetValue: AddAssign + Default, + A: IntoIterator>, { AssetList::from_iter(assets) .into_iter() - .all(|asset| self.contains(asset)) + .all(|asset| self.contains(&asset)) } /// Returns a shared reference to the balance state associated to `self`. @@ -194,7 +233,7 @@ where { self.reset_state(); self.load_initial_state().await?; - while self.sync_with(true).await?.is_continue() {} + while self.sync_with().await?.is_continue() {} Ok(()) } @@ -240,12 +279,12 @@ where where L: ledger::Read, Checkpoint = S::Checkpoint>, { - self.sync_with(false).await + self.sync_with().await } /// Pulls data from the ledger, synchronizing the wallet and balance state. #[inline] - async fn sync_with(&mut self, with_recovery: bool) -> Result> + async fn sync_with(&mut self) -> Result> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -258,7 +297,6 @@ where .await .map_err(Error::LedgerConnectionError)?; self.signer_sync(SyncRequest { - with_recovery, origin_checkpoint: self.checkpoint.clone(), data, }) @@ -312,13 +350,18 @@ where /// kind of update that should be performed on the balance state if the transaction is /// successfully posted to the ledger. /// - /// # Safety + /// # Crypto Safety /// /// This method is already called by [`post`](Self::post), but can be used by custom /// implementations to perform checks elsewhere. #[inline] - pub fn check(&self, transaction: &Transaction) -> Result, Asset> { - transaction.check(move |a| self.contains(a)) + fn check<'s>( + &'s self, + transaction: &'s Transaction, + ) -> Result, Asset> { + transaction + .check(move |a| self.contains(a)) + .map_err(Clone::clone) } /// Signs the `transaction` using the signer connection, sending `metadata` for context. This @@ -379,10 +422,10 @@ where .map_err(Error::LedgerConnectionError) } - /// Returns public receiving keys. + /// Returns the address. #[inline] - pub async fn receiving_keys(&mut self) -> Result, S::Error> { - self.signer.receiving_keys().await + pub async fn address(&mut self) -> Result, S::Error> { + self.signer.address().await } } @@ -432,13 +475,13 @@ pub enum InconsistencyError { serde( bound( deserialize = r" - Asset: Deserialize<'de>, + Asset: Deserialize<'de>, SignError: Deserialize<'de>, L::Error: Deserialize<'de>, S::Error: Deserialize<'de> ", serialize = r" - Asset: Serialize, + Asset: Serialize, SignError: Serialize, L::Error: Serialize, S::Error: Serialize diff --git a/manta-accounting/src/wallet/signer.rs b/manta-accounting/src/wallet/signer.rs index 9f63524ed..9ea037caf 100644 --- a/manta-accounting/src/wallet/signer.rs +++ b/manta-accounting/src/wallet/signer.rs @@ -19,7 +19,6 @@ // TODO: Should have a mode on the signer where we return a generic error which reveals no detail // about what went wrong during signing. The kind of error returned from a signing could // reveal information about the internal state (privacy leak, not a secrecy leak). -// TODO: Setup multi-account wallets using `crate::key::AccountTable`. // TODO: Move `sync` to a streaming algorithm. // TODO: Add self-destruct feature for clearing all secret and private data. // TODO: Compress the `BalanceUpdate` data before sending (improves privacy and bandwidth). @@ -27,30 +26,33 @@ // internally. use crate::{ - asset::{self, AssetMap, AssetMetadata}, - key::{self, AccountCollection, DeriveAddress, DeriveAddresses}, + asset::{AssetMap, AssetMetadata}, + key::{self, Account, AccountCollection, DeriveAddress, DeriveAddresses}, transfer::{ self, batch::Join, canonical::{ - Mint, MultiProvingContext, PrivateTransfer, PrivateTransferShape, Reclaim, Selection, - Shape, Transaction, + MultiProvingContext, PrivateTransfer, PrivateTransferShape, Selection, ToPrivate, + ToPublic, Transaction, }, - Asset, EncryptedNote, FullParameters, Note, Parameters, PreSender, ProofSystemError, - ProvingContext, PublicKey, Receiver, ReceivingKey, SecretKey, Sender, SpendingKey, - Transfer, TransferPost, Utxo, VoidNumber, + requires_authorization, + utxo::{auth::DeriveContext, DeriveDecryptionKey, DeriveSpend, Spend, UtxoReconstruct}, + Address, Asset, AssociatedData, Authorization, AuthorizationContext, FullParametersRef, + IdentifiedAsset, Identifier, Note, Nullifier, Parameters, PreSender, ProofSystemError, + ProvingContext, Receiver, Sender, Shape, SpendingKey, Transfer, TransferPost, Utxo, + UtxoAccumulatorItem, UtxoAccumulatorModel, }, wallet::ledger::{self, Data}, }; use alloc::{boxed::Box, vec, vec::Vec}; use core::{convert::Infallible, fmt::Debug, hash::Hash}; use manta_crypto::{ - accumulator::{Accumulator, ExactSizeAccumulator, OptimizedAccumulator}, + accumulator::{Accumulator, ExactSizeAccumulator, ItemHashFunction, OptimizedAccumulator}, rand::{CryptoRng, FromEntropy, Rand, RngCore}, }; use manta_util::{ - array_map, future::LocalBoxFutureResult, into_array_unchecked, iter::IteratorExt, - persistence::Rollback, + array_map, cmp::Independence, future::LocalBoxFutureResult, into_array_unchecked, + iter::IteratorExt, persistence::Rollback, }; #[cfg(feature = "serde")] @@ -85,8 +87,8 @@ where request: SignRequest, ) -> LocalBoxFutureResult, SignError>, Self::Error>; - /// Returns public receiving keys. - fn receiving_keys(&mut self) -> LocalBoxFutureResult, Self::Error>; + /// Returns the [`Address`] corresponding to `self`. + fn address(&mut self) -> LocalBoxFutureResult, Self::Error>; } /// Signer Synchronization Data @@ -97,13 +99,13 @@ where bound( deserialize = r" Utxo: Deserialize<'de>, - EncryptedNote: Deserialize<'de>, - VoidNumber: Deserialize<'de> + Note: Deserialize<'de>, + Nullifier: Deserialize<'de> ", serialize = r" Utxo: Serialize, - EncryptedNote: Serialize, - VoidNumber: Serialize + Note: Serialize, + Nullifier: Serialize ", ), crate = "manta_util::serde", @@ -112,31 +114,38 @@ where )] #[derive(derivative::Derivative)] #[derivative( - Clone(bound = "Utxo: Clone, EncryptedNote: Clone, VoidNumber: Clone"), - Debug(bound = "Utxo: Debug, EncryptedNote: Debug, VoidNumber: Debug"), + Clone(bound = "Utxo: Clone, Note: Clone, Nullifier: Clone"), + Debug(bound = "Utxo: Debug, Note: Debug, Nullifier: Debug"), Default(bound = ""), - Eq(bound = "Utxo: Eq, EncryptedNote: Eq, VoidNumber: Eq"), - Hash(bound = "Utxo: Hash, EncryptedNote: Hash, VoidNumber: Hash"), - PartialEq(bound = "Utxo: PartialEq, EncryptedNote: PartialEq, VoidNumber: PartialEq") + Eq(bound = "Utxo: Eq, Note: Eq, Nullifier: Eq"), + Hash(bound = "Utxo: Hash, Note: Hash, Nullifier: Hash"), + PartialEq(bound = "Utxo: PartialEq, Note: PartialEq, Nullifier: PartialEq") )] pub struct SyncData where C: transfer::Configuration + ?Sized, { - /// Receiver Data - pub receivers: Vec<(Utxo, EncryptedNote)>, + /// UTXO-Note Data + pub utxo_note_data: Vec<(Utxo, Note)>, - /// Sender Data - pub senders: Vec>, + /// Nullifier Data + pub nullifier_data: Vec>, } impl Data for SyncData where C: Configuration + ?Sized, { + type Parameters = C::UtxoAccumulatorItemHash; + #[inline] - fn prune(&mut self, origin: &C::Checkpoint, checkpoint: &C::Checkpoint) -> bool { - C::Checkpoint::prune(self, origin, checkpoint) + fn prune( + &mut self, + parameters: &Self::Parameters, + origin: &C::Checkpoint, + checkpoint: &C::Checkpoint, + ) -> bool { + C::Checkpoint::prune(parameters, self, origin, checkpoint) } } @@ -167,9 +176,6 @@ where C: transfer::Configuration, T: ledger::Checkpoint, { - /// Recovery Flag - pub with_recovery: bool, - /// Origin Checkpoint /// /// This checkpoint was the one that was used to retrieve the [`data`](Self::data) from the @@ -191,11 +197,16 @@ where /// [`data`]: Self::data /// [`origin_checkpoint`]: Self::origin_checkpoint #[inline] - pub fn prune(&mut self, checkpoint: &T) -> bool + pub fn prune( + &mut self, + parameters: & as Data>::Parameters, + checkpoint: &T, + ) -> bool where SyncData: Data, { - self.data.prune(&self.origin_checkpoint, checkpoint) + self.data + .prune(parameters, &self.origin_checkpoint, checkpoint) } } @@ -208,8 +219,8 @@ where derive(Deserialize, Serialize), serde( bound( - deserialize = "BalanceUpdate: Deserialize<'de>, T: Deserialize<'de>", - serialize = "BalanceUpdate: Serialize, T: Serialize" + deserialize = "T: Deserialize<'de>, BalanceUpdate: Deserialize<'de>", + serialize = "T: Serialize, BalanceUpdate: Serialize", ), crate = "manta_util::serde", deny_unknown_fields @@ -217,11 +228,13 @@ where )] #[derive(derivative::Derivative)] #[derivative( - Clone(bound = "BalanceUpdate: Clone"), - Debug(bound = "BalanceUpdate: Debug, T: Debug"), - Eq(bound = "BalanceUpdate: Eq, T: Eq"), - Hash(bound = "BalanceUpdate: Hash, T: Hash"), - PartialEq(bound = "BalanceUpdate: PartialEq, T: PartialEq") + Clone(bound = "T: Clone, BalanceUpdate: Clone"), + Copy(bound = "T: Copy, BalanceUpdate: Copy"), + Debug(bound = "T: Debug, BalanceUpdate: Debug"), + Default(bound = "T: Default, BalanceUpdate: Default"), + Eq(bound = "T: Eq, BalanceUpdate: Eq"), + Hash(bound = "T: Hash, BalanceUpdate: Hash"), + PartialEq(bound = "T: PartialEq, BalanceUpdate: PartialEq") )] pub struct SyncResponse where @@ -242,7 +255,7 @@ where serde( bound( deserialize = "Asset: Deserialize<'de>", - serialize = "Asset: Serialize" + serialize = "Asset: Serialize", ), crate = "manta_util::serde", deny_unknown_fields @@ -435,10 +448,16 @@ where C: transfer::Configuration + ?Sized, { /// UTXO Accumulator Type - type UtxoAccumulator: Accumulator; + type UtxoAccumulator: Accumulator< + Item = UtxoAccumulatorItem, + Model = UtxoAccumulatorModel, + >; - /// Updates `self` by viewing `count`-many void numbers. - fn update_from_void_numbers(&mut self, count: usize); + /// UTXO Accumulator Hash Type + type UtxoAccumulatorItemHash: ItemHashFunction, Item = UtxoAccumulatorItem>; + + /// Updates `self` by viewing `count`-many nullifiers. + fn update_from_nullifiers(&mut self, count: usize); /// Updates `self` by viewing a new `accumulator`. fn update_from_utxo_accumulator(&mut self, accumulator: &Self::UtxoAccumulator); @@ -453,27 +472,36 @@ where /// Prunes the `data` required for a [`sync`](Connection::sync) call against `origin` and /// `signer_checkpoint`, returning `true` if the data was pruned. - fn prune(data: &mut SyncData, origin: &Self, signer_checkpoint: &Self) -> bool; + fn prune( + parameters: &Self::UtxoAccumulatorItemHash, + data: &mut SyncData, + origin: &Self, + signer_checkpoint: &Self, + ) -> bool; } /// Signer Configuration pub trait Configuration: transfer::Configuration { /// Checkpoint Type - type Checkpoint: Checkpoint; + type Checkpoint: Checkpoint< + Self, + UtxoAccumulator = Self::UtxoAccumulator, + UtxoAccumulatorItemHash = Self::UtxoAccumulatorItemHash, + >; /// Account Type - type Account: AccountCollection> + type Account: AccountCollection> + Clone - + DeriveAddresses, Address = PublicKey>; + + DeriveAddresses; /// [`Utxo`] Accumulator Type - type UtxoAccumulator: Accumulator + type UtxoAccumulator: Accumulator, Model = UtxoAccumulatorModel> + ExactSizeAccumulator + OptimizedAccumulator + Rollback; /// Asset Map Type - type AssetMap: AssetMap>; + type AssetMap: AssetMap>; /// Random Number Generator Type type Rng: CryptoRng + FromEntropy + RngCore; @@ -483,6 +511,18 @@ pub trait Configuration: transfer::Configuration { pub type AccountTable = key::AccountTable<::Account>; /// Signer Parameters +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "Parameters: Deserialize<'de>, MultiProvingContext: Deserialize<'de>", + serialize = "Parameters: Serialize, MultiProvingContext: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] #[derive(derivative::Derivative)] #[derivative( Clone(bound = "Parameters: Clone, MultiProvingContext: Clone"), @@ -539,13 +579,51 @@ where deny_unknown_fields ) )] +#[derive(derivative::Derivative)] +#[derivative( + Debug(bound = r" + AccountTable: Debug, + C::UtxoAccumulator: Debug, + C::AssetMap: Debug, + C::Checkpoint: Debug, + C::Rng: Debug + "), + Default(bound = r" + AccountTable: Default, + C::UtxoAccumulator: Default, + C::AssetMap: Default, + C::Checkpoint: Default, + C::Rng: Default + "), + Eq(bound = r" + AccountTable: Eq, + C::UtxoAccumulator: Eq, + C::AssetMap: Eq, + C::Checkpoint: Eq, + C::Rng: Eq + "), + Hash(bound = r" + AccountTable: Hash, + C::UtxoAccumulator: Hash, + C::AssetMap: Hash, + C::Checkpoint: Hash, + C::Rng: Hash + "), + PartialEq(bound = r" + AccountTable: PartialEq, + C::UtxoAccumulator: PartialEq, + C::AssetMap: PartialEq, + C::Checkpoint: PartialEq, + C::Rng: PartialEq + ") +)] pub struct SignerState where C: Configuration, { /// Account Table /// - /// # Note + /// # Implementation Note /// /// For now, we only use the default account, and the rest of the storage data is related to /// this account. Eventually, we want to have a global `utxo_accumulator` for all accounts and @@ -602,64 +680,125 @@ where ) } - /// Returns the [`AccountTable`] for `self.` + /// Returns the [`AccountTable`]. #[inline] pub fn accounts(&self) -> &AccountTable { &self.accounts } - /// Inserts the new `utxo`-`note` pair into the `utxo_accumulator` adding the spendable amount - /// to `assets` if there is no void number to match it. - #[allow(clippy::too_many_arguments)] + /// Returns the default account for `self`. + #[inline] + pub fn default_account(&self) -> Account { + self.accounts.get_default() + } + + /// Returns the default spending key for `self`. + #[inline] + fn default_spending_key(&self, parameters: &C::Parameters) -> SpendingKey { + let _ = parameters; + self.accounts.get_default().spending_key() + } + + /// Returns the default authorization context for `self`. + #[inline] + fn default_authorization_context(&self, parameters: &C::Parameters) -> AuthorizationContext { + parameters.derive_context(&self.default_spending_key(parameters)) + } + + /// Returns the authorization for the default spending key of `self`. + #[inline] + fn authorization_for_default_spending_key( + &mut self, + parameters: &C::Parameters, + ) -> Authorization { + Authorization::::from_spending_key( + parameters, + &self.default_spending_key(parameters), + &mut self.rng, + ) + } + + /// Returns the address for the default account of `self`. + #[inline] + fn default_address(&mut self, parameters: &C::Parameters) -> Address { + self.accounts.get_default().address(parameters) + } + + /// Hashes `utxo` using the [`UtxoAccumulatorItemHash`](transfer::Configuration::UtxoAccumulatorItemHash) + /// in the transfer [`Configuration`](transfer::Configuration). + #[inline] + fn item_hash(parameters: &C::Parameters, utxo: &Utxo) -> UtxoAccumulatorItem { + parameters + .utxo_accumulator_item_hash() + .item_hash(utxo, &mut ()) + } + + /// Inserts the hash of `utxo` in `utxo_accumulator`. + #[allow(clippy::too_many_arguments)] // FIXME: Use a better abstraction here. #[inline] - fn insert_next_item( + fn insert_next_item( + authorization_context: &mut AuthorizationContext, utxo_accumulator: &mut C::UtxoAccumulator, assets: &mut C::AssetMap, parameters: &Parameters, utxo: Utxo, - note: Note, - key: &SecretKey, - void_numbers: &mut Vec>, + identified_asset: IdentifiedAsset, + nullifiers: &mut Vec>, deposit: &mut Vec>, - ) { - if let Some(void_number) = - parameters.check_full_asset(key, ¬e.ephemeral_secret_key, ¬e.asset, &utxo) - { - if let Some(index) = void_numbers.iter().position(move |v| v == &void_number) { - void_numbers.remove(index); + rng: &mut R, + ) where + R: CryptoRng + RngCore + ?Sized, + { + let IdentifiedAsset:: { identifier, asset } = identified_asset; + let (_, computed_utxo, nullifier) = parameters.derive_spend( + authorization_context, + identifier.clone(), + asset.clone(), + rng, + ); + if computed_utxo.is_related(&utxo) { + if let Some(index) = nullifiers + .iter() + .position(move |n| n.is_related(&nullifier)) + { + nullifiers.remove(index); } else { - utxo_accumulator.insert(&utxo); - assets.insert(note.ephemeral_secret_key, note.asset.clone()); - if !note.asset.is_zero() { - deposit.push(note.asset); + utxo_accumulator.insert(&Self::item_hash(parameters, &utxo)); + if !asset.is_zero() { + deposit.push(asset.clone()); } + assets.insert(identifier, asset); return; } } - utxo_accumulator.insert_nonprovable(&utxo); + utxo_accumulator.insert_nonprovable(&Self::item_hash(parameters, &utxo)); } - /// Checks if `asset` matches with `void_number`, removing it from the `utxo_accumulator` and + /// Checks if `asset` matches with `nullifier`, removing it from the `utxo_accumulator` and /// inserting it into the `withdraw` set if this is the case. + #[allow(clippy::too_many_arguments)] // FIXME: Use a better abstraction here. #[inline] - fn is_asset_unspent( + fn is_asset_unspent( + authorization_context: &mut AuthorizationContext, utxo_accumulator: &mut C::UtxoAccumulator, parameters: &Parameters, - secret_spend_key: &SecretKey, - ephemeral_secret_key: &SecretKey, + identifier: Identifier, asset: Asset, - void_numbers: &mut Vec>, + nullifiers: &mut Vec>, withdraw: &mut Vec>, - ) -> bool { - let utxo = parameters.utxo( - ephemeral_secret_key, - ¶meters.derive(secret_spend_key), - &asset, - ); - let void_number = parameters.void_number(secret_spend_key, &utxo); - if let Some(index) = void_numbers.iter().position(move |v| v == &void_number) { - void_numbers.remove(index); - utxo_accumulator.remove_proof(&utxo); + rng: &mut R, + ) -> bool + where + R: CryptoRng + RngCore + ?Sized, + { + let (_, utxo, nullifier) = + parameters.derive_spend(authorization_context, identifier, asset.clone(), rng); + if let Some(index) = nullifiers + .iter() + .position(move |n| n.is_related(&nullifier)) + { + nullifiers.remove(index); + utxo_accumulator.remove_proof(&Self::item_hash(parameters, &utxo)); if !asset.is_zero() { withdraw.push(asset); } @@ -674,52 +813,54 @@ where fn sync_with( &mut self, parameters: &Parameters, - with_recovery: bool, inserts: I, - mut void_numbers: Vec>, + mut nullifiers: Vec>, is_partial: bool, ) -> SyncResponse where - I: Iterator, EncryptedNote)>, + I: Iterator, Note)>, { - let _ = with_recovery; - let void_number_count = void_numbers.len(); + let nullifier_count = nullifiers.len(); let mut deposit = Vec::new(); let mut withdraw = Vec::new(); - let default_key = self.accounts.get_default().spending_key(); - for (utxo, encrypted_note) in inserts { - if let Some(note) = - encrypted_note.decrypt(¶meters.note_encryption_scheme, &default_key, &mut ()) + let mut authorization_context = self.default_authorization_context(parameters); + let decryption_key = parameters.derive_decryption_key(&mut authorization_context); + for (utxo, note) in inserts { + if let Some((identifier, asset)) = + parameters.open_with_check(&decryption_key, &utxo, note) { Self::insert_next_item( + &mut authorization_context, &mut self.utxo_accumulator, &mut self.assets, parameters, utxo, - note, - &default_key, - &mut void_numbers, + transfer::utxo::IdentifiedAsset::new(identifier, asset), + &mut nullifiers, &mut deposit, + &mut self.rng, ); } else { - self.utxo_accumulator.insert_nonprovable(&utxo); + self.utxo_accumulator + .insert_nonprovable(&Self::item_hash(parameters, &utxo)); } } - self.assets.retain(|ephemeral_secret_key, assets| { + self.assets.retain(|identifier, assets| { assets.retain(|asset| { Self::is_asset_unspent( + &mut authorization_context, &mut self.utxo_accumulator, parameters, - &self.accounts.get_default().spending_key(), - ephemeral_secret_key, + identifier.clone(), asset.clone(), - &mut void_numbers, + &mut nullifiers, &mut withdraw, + &mut self.rng, ) }); !assets.is_empty() }); - self.checkpoint.update_from_void_numbers(void_number_count); + self.checkpoint.update_from_nullifiers(nullifier_count); self.checkpoint .update_from_utxo_accumulator(&self.utxo_accumulator); SyncResponse { @@ -736,51 +877,40 @@ where } } - /// Builds the pre-sender associated to `key` and `asset`. + /// Builds the [`PreSender`] associated to `identifier` and `asset`. #[inline] fn build_pre_sender( - &self, + &mut self, parameters: &Parameters, - key: SecretKey, + identifier: Identifier, asset: Asset, - ) -> Result, SignError> { - Ok(PreSender::new( + ) -> PreSender { + PreSender::::sample( parameters, - self.accounts.get_default().spending_key(), - key, + &mut self.default_authorization_context(parameters), + identifier, asset, - )) + &mut self.rng, + ) } - /// Builds the receiver for `asset`. + /// Builds the [`Receiver`] associated with `address` and `asset`. #[inline] - fn build_receiver( + fn receiver( &mut self, parameters: &Parameters, + address: Address, asset: Asset, - ) -> Result, SignError> { - let receiving_key = ReceivingKey:: { - spend: self.accounts.get_default().address(parameters), - view: self.accounts.get_default().address(parameters), - }; - Ok(receiving_key.into_receiver(parameters, self.rng.gen(), asset)) + associated_data: AssociatedData, + ) -> Receiver { + Receiver::::sample(parameters, address, asset, associated_data, &mut self.rng) } - /// Builds a new internal [`Mint`] for zero assets. + /// Builds the [`Receiver`] associated with the default address and `asset`. #[inline] - fn mint_zero( - &mut self, - parameters: &Parameters, - asset_id: C::AssetId, - ) -> Result<(Mint, PreSender), SignError> { - let asset = Asset::::zero(asset_id); - let key = self.accounts.get_default().spending_key(); - Ok(Mint::internal_pair( - parameters, - &SpendingKey::new(key.clone(), key), - asset, - &mut self.rng, - )) + fn default_receiver(&mut self, parameters: &Parameters, asset: Asset) -> Receiver { + let default_address = self.default_address(parameters); + self.receiver(parameters, default_address, asset, Default::default()) } /// Selects the pre-senders which collectively own at least `asset`, returning any change. @@ -788,87 +918,56 @@ where fn select( &mut self, parameters: &Parameters, - asset: Asset, + asset: &Asset, ) -> Result, SignError> { - let selection = self.assets.select(&asset); + let selection = self.assets.select(asset); if !asset.is_zero() && selection.is_empty() { - return Err(SignError::InsufficientBalance(asset)); + return Err(SignError::InsufficientBalance(asset.clone())); } Selection::new(selection, move |k, v| { - self.build_pre_sender( - parameters, - k, - asset::Asset { - id: asset.clone().id, - value: v, - }, - ) + Ok(self.build_pre_sender(parameters, k, Asset::::new(asset.id.clone(), v))) }) } /// Builds a [`TransferPost`] for the given `transfer`. #[inline] - fn build_post< + fn build_post_inner< const SOURCES: usize, const SENDERS: usize, const RECEIVERS: usize, const SINKS: usize, >( - parameters: FullParameters, + parameters: FullParametersRef, proving_context: &ProvingContext, + spending_key: Option<&SpendingKey>, transfer: Transfer, rng: &mut C::Rng, ) -> Result, SignError> { transfer - .into_post(parameters, proving_context, rng) + .into_post(parameters, proving_context, spending_key, rng) + .map(|p| p.expect("Internally, all transfer posts are constructed correctly.")) .map_err(SignError::ProofSystemError) } - /// Mints an asset with zero value for the given `asset_id`, returning the appropriate - /// Builds a [`TransferPost`] for `mint`. - #[inline] - fn mint_post( - &mut self, - parameters: &Parameters, - proving_context: &ProvingContext, - mint: Mint, - ) -> Result, SignError> { - Self::build_post( - FullParameters::new(parameters, self.utxo_accumulator.model()), - proving_context, - mint, - &mut self.rng, - ) - } - - /// Builds a [`TransferPost`] for `private_transfer`. - #[inline] - fn private_transfer_post( - &mut self, - parameters: &Parameters, - proving_context: &ProvingContext, - private_transfer: PrivateTransfer, - ) -> Result, SignError> { - Self::build_post( - FullParameters::new(parameters, self.utxo_accumulator.model()), - proving_context, - private_transfer, - &mut self.rng, - ) - } - - /// Builds a [`TransferPost`] for `reclaim`. + /// Builds a [`TransferPost`] for the given `transfer`. #[inline] - fn reclaim_post( + fn build_post< + const SOURCES: usize, + const SENDERS: usize, + const RECEIVERS: usize, + const SINKS: usize, + >( &mut self, parameters: &Parameters, proving_context: &ProvingContext, - reclaim: Reclaim, + transfer: Transfer, ) -> Result, SignError> { - Self::build_post( - FullParameters::new(parameters, self.utxo_accumulator.model()), + let spending_key = self.default_spending_key(parameters); + Self::build_post_inner( + FullParametersRef::::new(parameters, self.utxo_accumulator.model()), proving_context, - reclaim, + requires_authorization(SENDERS).then_some(&spending_key), + transfer, &mut self.rng, ) } @@ -879,17 +978,14 @@ where fn next_join( &mut self, parameters: &Parameters, - asset_id: C::AssetId, + asset_id: &C::AssetId, total: C::AssetValue, ) -> Result<([Receiver; PrivateTransferShape::RECEIVERS], Join), SignError> { - let secret_key = self.accounts.get_default().spending_key(); Ok(Join::new( parameters, - asset::Asset { - id: asset_id, - value: total, - }, - &SpendingKey::new(secret_key.clone(), secret_key), + &mut self.default_authorization_context(parameters), + self.default_address(parameters), + Asset::::new(asset_id.clone(), total), &mut self.rng, )) } @@ -899,43 +995,61 @@ where fn prepare_final_pre_senders( &mut self, parameters: &Parameters, - proving_context: &MultiProvingContext, - asset_id: C::AssetId, + asset_id: &C::AssetId, mut new_zeroes: Vec>, - pre_senders: &mut Vec>, - posts: &mut Vec>, - ) -> Result<(), SignError> { - let mut needed_zeroes = PrivateTransferShape::SENDERS - pre_senders.len(); + pre_senders: Vec>, + ) -> Result>, SignError> { + let mut senders = pre_senders + .into_iter() + .map(|s| s.try_upgrade(parameters, &self.utxo_accumulator)) + .collect::>>() + .expect("Unable to upgrade expected UTXOs."); + let mut needed_zeroes = PrivateTransferShape::SENDERS - senders.len(); if needed_zeroes == 0 { - return Ok(()); + return Ok(senders); } - let zeroes = self.assets.zeroes(needed_zeroes, &asset_id); + let zeroes = self.assets.zeroes(needed_zeroes, asset_id); needed_zeroes -= zeroes.len(); for zero in zeroes { - let pre_sender = - self.build_pre_sender(parameters, zero, Asset::::zero(asset_id.clone()))?; - pre_senders.push(pre_sender); + let pre_sender = self.build_pre_sender( + parameters, + zero, + Asset::::new(asset_id.clone(), Default::default()), + ); + senders.push( + pre_sender + .try_upgrade(parameters, &self.utxo_accumulator) + .expect("Unable to upgrade expected UTXOs."), + ); } if needed_zeroes == 0 { - return Ok(()); + return Ok(senders); } - let needed_mints = needed_zeroes.saturating_sub(new_zeroes.len()); + let needed_fake_zeroes = needed_zeroes.saturating_sub(new_zeroes.len()); for _ in 0..needed_zeroes { match new_zeroes.pop() { - Some(zero) => pre_senders.push(zero), + Some(zero) => senders.push( + zero.try_upgrade(parameters, &self.utxo_accumulator) + .expect("Unable to upgrade expected UTXOs."), + ), _ => break, } } - if needed_mints == 0 { - return Ok(()); + if needed_fake_zeroes == 0 { + return Ok(senders); } - for _ in 0..needed_mints { - let (mint, pre_sender) = self.mint_zero(parameters, asset_id.clone())?; - posts.push(self.mint_post(parameters, &proving_context.mint, mint)?); - pre_sender.insert_utxo(&mut self.utxo_accumulator); - pre_senders.push(pre_sender); + for _ in 0..needed_fake_zeroes { + let identifier = self.rng.gen(); + senders.push( + self.build_pre_sender( + parameters, + identifier, + Asset::::new(asset_id.clone(), Default::default()), + ) + .upgrade_unchecked(Default::default()), + ); } - Ok(()) + Ok(senders) } /// Computes the batched transactions for rebalancing before a final transfer. @@ -944,7 +1058,7 @@ where &mut self, parameters: &Parameters, proving_context: &MultiProvingContext, - asset_id: C::AssetId, + asset_id: &C::AssetId, mut pre_senders: Vec>, posts: &mut Vec>, ) -> Result<[Sender; PrivateTransferShape::SENDERS], SignError> { @@ -956,59 +1070,40 @@ where .chunk_by::<{ PrivateTransferShape::SENDERS }>(); for chunk in &mut iter { let senders = array_map(chunk, |s| { - s.try_upgrade(&self.utxo_accumulator) + s.try_upgrade(parameters, &self.utxo_accumulator) .expect("Unable to upgrade expected UTXO.") }); let (receivers, mut join) = self.next_join( parameters, - asset_id.clone(), - senders.iter().map(Sender::asset_value).sum(), + asset_id, + senders.iter().map(|s| s.asset().value).sum(), )?; - posts.push(self.private_transfer_post( + let authorization = self.authorization_for_default_spending_key(parameters); + posts.push(self.build_post( parameters, &proving_context.private_transfer, - PrivateTransfer::build(senders, receivers), + PrivateTransfer::build(authorization, senders, receivers), )?); - join.insert_utxos(&mut self.utxo_accumulator); + join.insert_utxos(parameters, &mut self.utxo_accumulator); joins.push(join.pre_sender); new_zeroes.append(&mut join.zeroes); } joins.append(&mut iter.remainder()); pre_senders = joins; } - self.prepare_final_pre_senders( + Ok(into_array_unchecked(self.prepare_final_pre_senders( parameters, - proving_context, asset_id, new_zeroes, - &mut pre_senders, - posts, - )?; - Ok(into_array_unchecked( - pre_senders - .into_iter() - .map(move |s| s.try_upgrade(&self.utxo_accumulator)) - .collect::>>() - .expect("Unable to upgrade expected UTXOs."), - )) - } - - /// Prepares a given [`ReceivingKey`] for receiving `asset`. - #[inline] - fn prepare_receiver( - &mut self, - parameters: &Parameters, - asset: Asset, - receiving_key: ReceivingKey, - ) -> Receiver { - receiving_key.into_receiver(parameters, self.rng.gen(), asset) + pre_senders, + )?)) } } impl Clone for SignerState where C: Configuration, - C::Account: Clone, + AccountTable: Clone, C::UtxoAccumulator: Clone, C::AssetMap: Clone, { @@ -1024,6 +1119,26 @@ where } /// Signer +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "SignerParameters: Deserialize<'de>, SignerState: Deserialize<'de>", + serialize = "SignerParameters: Serialize, SignerState: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "SignerParameters: Clone, SignerState: Clone"), + Debug(bound = "SignerParameters: Debug, SignerState: Debug"), + Eq(bound = "SignerParameters: Eq, SignerState: Eq"), + Hash(bound = "SignerParameters: Hash, SignerState: Hash"), + PartialEq(bound = "SignerParameters: PartialEq, SignerState: PartialEq") +)] pub struct Signer where C: Configuration, @@ -1049,8 +1164,8 @@ where #[inline] fn new_inner( accounts: AccountTable, - proving_context: MultiProvingContext, parameters: Parameters, + proving_context: MultiProvingContext, utxo_accumulator: C::UtxoAccumulator, assets: C::AssetMap, rng: C::Rng, @@ -1073,15 +1188,15 @@ where #[inline] pub fn new( accounts: AccountTable, - proving_context: MultiProvingContext, parameters: Parameters, + proving_context: MultiProvingContext, utxo_accumulator: C::UtxoAccumulator, rng: C::Rng, ) -> Self { Self::new_inner( accounts, - proving_context, parameters, + proving_context, utxo_accumulator, Default::default(), rng, @@ -1117,13 +1232,18 @@ where checkpoint: checkpoint.clone(), }) } else { - let has_pruned = request.prune(checkpoint); - let SyncData { receivers, senders } = request.data; + let has_pruned = request.prune( + self.parameters.parameters.utxo_accumulator_item_hash(), + checkpoint, + ); + let SyncData { + utxo_note_data, + nullifier_data, + } = request.data; let response = self.state.sync_with( &self.parameters.parameters, - request.with_recovery, - receivers.into_iter(), - senders, + utxo_note_data.into_iter(), + nullifier_data, !has_pruned, ); self.state.utxo_accumulator.commit(); @@ -1131,46 +1251,47 @@ where } } - /// Signs a withdraw transaction for `asset` sent to `receiver`. + /// Signs a withdraw transaction for `asset` sent to `address`. #[inline] fn sign_withdraw( &mut self, asset: Asset, - receiver: Option>, + address: Option>, ) -> Result, SignError> { - let selection = self - .state - .select(&self.parameters.parameters, asset.clone())?; - let change = self.state.build_receiver( - &self.parameters.parameters, - asset::Asset { - id: asset.clone().id, - value: selection.change, - }, - )?; + let selection = self.state.select(&self.parameters.parameters, &asset)?; let mut posts = Vec::new(); let senders = self.state.compute_batched_transactions( &self.parameters.parameters, &self.parameters.proving_context, - asset.clone().id, + &asset.id, selection.pre_senders, &mut posts, )?; - let final_post = match receiver { - Some(receiver) => { - let receiver = - self.state - .prepare_receiver(&self.parameters.parameters, asset, receiver); - self.state.private_transfer_post( + let change = self.state.default_receiver( + &self.parameters.parameters, + Asset::::new(asset.id.clone(), selection.change), + ); + let authorization = self + .state + .authorization_for_default_spending_key(&self.parameters.parameters); + let final_post = match address { + Some(address) => { + let receiver = self.state.receiver( + &self.parameters.parameters, + address, + asset, + Default::default(), + ); + self.state.build_post( &self.parameters.parameters, &self.parameters.proving_context.private_transfer, - PrivateTransfer::build(senders, [change, receiver]), + PrivateTransfer::build(authorization, senders, [change, receiver]), )? } - _ => self.state.reclaim_post( + _ => self.state.build_post( &self.parameters.parameters, - &self.parameters.proving_context.reclaim, - Reclaim::build(senders, [change], asset), + &self.parameters.proving_context.to_public, + ToPublic::build(authorization, senders, [change], asset), )?, }; posts.push(final_post); @@ -1184,20 +1305,20 @@ where transaction: Transaction, ) -> Result, SignError> { match transaction { - Transaction::Mint(asset) => { + Transaction::ToPrivate(asset) => { let receiver = self .state - .build_receiver(&self.parameters.parameters, asset.clone())?; - Ok(SignResponse::new(vec![self.state.mint_post( + .default_receiver(&self.parameters.parameters, asset.clone()); + Ok(SignResponse::new(vec![self.state.build_post( &self.parameters.parameters, - &self.parameters.proving_context.mint, - Mint::build(asset, receiver), + &self.parameters.proving_context.to_private, + ToPrivate::build(asset, receiver), )?])) } - Transaction::PrivateTransfer(asset, receiver) => { - self.sign_withdraw(asset, Some(receiver)) + Transaction::PrivateTransfer(asset, address) => { + self.sign_withdraw(asset, Some(address)) } - Transaction::Reclaim(asset) => self.sign_withdraw(asset, None), + Transaction::ToPublic(asset) => self.sign_withdraw(asset, None), } } @@ -1209,13 +1330,11 @@ where result } - /// Returns public receiving keys. + /// Returns the [`Address`] corresponding to `self`. #[inline] - pub fn receiving_keys(&mut self) -> PublicKey { - self.state - .accounts - .get_default() - .address(&self.parameters.parameters) + pub fn address(&mut self) -> Address { + let account = self.state.accounts.get_default(); + account.address(&self.parameters.parameters) } } @@ -1246,7 +1365,7 @@ where } #[inline] - fn receiving_keys(&mut self) -> LocalBoxFutureResult, Self::Error> { - Box::pin(async move { Ok(self.receiving_keys()) }) + fn address(&mut self) -> LocalBoxFutureResult, Self::Error> { + Box::pin(async move { Ok(self.address()) }) } } diff --git a/manta-accounting/src/wallet/test/mod.rs b/manta-accounting/src/wallet/test/mod.rs index 88643ef6b..ec2d95e16 100644 --- a/manta-accounting/src/wallet/test/mod.rs +++ b/manta-accounting/src/wallet/test/mod.rs @@ -20,8 +20,8 @@ // TODO: Generalize `PushResponse` so that we can test against more general wallet setups. use crate::{ - asset::{self, AssetList}, - transfer::{self, canonical::Transaction, Asset, PublicKey, ReceivingKey, TransferPost}, + asset::{AssetList, AssetMetadata}, + transfer::{canonical::Transaction, Address, Asset, Configuration, TransferPost}, wallet::{ ledger, signer::{self, SyncData}, @@ -29,16 +29,13 @@ use crate::{ }, }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; -use core::{fmt::Debug, future::Future, hash::Hash, marker::PhantomData}; +use core::{fmt::Debug, future::Future, hash::Hash, marker::PhantomData, ops::AddAssign}; use futures::StreamExt; use indexmap::IndexSet; use manta_crypto::rand::{CryptoRng, Distribution, Rand, RngCore, Sample, SampleUniform}; -use manta_util::{future::LocalBoxFuture, num::CheckedSub}; +use manta_util::{future::LocalBoxFuture, iter::Iterable, num::CheckedSub}; use parking_lot::Mutex; -use statrs::{ - distribution::{Categorical, Poisson}, - StatsError, -}; +use statrs::{distribution::Categorical, StatsError}; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize}; @@ -46,9 +43,30 @@ use manta_util::serde::{Deserialize, Serialize}; pub mod sim; /// Simulation Action Space +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "Transaction: Deserialize<'de>", + serialize = "Transaction: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "Transaction: Clone"), + Copy(bound = "Transaction: Copy"), + Debug(bound = "Transaction: Debug"), + Eq(bound = "Transaction: Eq"), + Hash(bound = "Transaction: Hash"), + PartialEq(bound = "Transaction: PartialEq") +)] pub enum Action where - C: transfer::Configuration, + C: Configuration, { /// No Action Skip, @@ -67,19 +85,13 @@ where transaction: Transaction, }, - /// Generate Receiving Keys - GenerateReceivingKeys { - /// Number of Keys to Generate - count: usize, - }, - /// Restart Wallet Restart, } impl Action where - C: transfer::Configuration, + C: Configuration, { /// Generates a [`Post`](Self::Post) on `transaction` self-pointed if `is_self` is `true` and /// maximal if `is_maximal` is `true`. @@ -99,31 +111,23 @@ where Self::post(true, is_maximal, transaction) } - /// Generates a [`Transaction::Mint`] for `asset`. + /// Generates a [`Transaction::ToPrivate`] for `asset`. #[inline] - pub fn mint(asset: Asset) -> Self { - Self::self_post(false, Transaction::Mint(asset)) + pub fn to_private(asset: Asset) -> Self { + Self::self_post(false, Transaction::ToPrivate(asset)) } - /// Generates a [`Transaction::PrivateTransfer`] for `asset` to `key` self-pointed if `is_self` - /// is `true`. + /// Generates a [`Transaction::PrivateTransfer`] for `asset` to `address` self-pointed if + /// `is_self` is `true`. #[inline] - pub fn private_transfer(is_self: bool, asset: Asset, key: PublicKey) -> Self { - let receiving_key = ReceivingKey:: { - spend: key.clone(), - view: key, - }; - Self::post( - is_self, - false, - Transaction::PrivateTransfer(asset, receiving_key), - ) + pub fn private_transfer(is_self: bool, asset: Asset, address: Address) -> Self { + Self::post(is_self, false, Transaction::PrivateTransfer(asset, address)) } - /// Generates a [`Transaction::Reclaim`] for `asset` which is maximal if `is_maximal` is `true`. + /// Generates a [`Transaction::ToPublic`] for `asset` which is maximal if `is_maximal` is `true`. #[inline] - pub fn reclaim(is_maximal: bool, asset: Asset) -> Self { - Self::self_post(is_maximal, Transaction::Reclaim(asset)) + pub fn to_public(is_maximal: bool, asset: Asset) -> Self { + Self::self_post(is_maximal, Transaction::ToPublic(asset)) } /// Computes the [`ActionType`] for a [`Post`](Self::Post) type with the `is_self`, @@ -136,15 +140,15 @@ where ) -> ActionType { use Transaction::*; match (is_self, is_maximal, transaction.is_zero(), transaction) { - (_, _, true, Mint { .. }) => ActionType::MintZero, - (_, _, false, Mint { .. }) => ActionType::Mint, + (_, _, true, ToPrivate { .. }) => ActionType::ToPrivateZero, + (_, _, false, ToPrivate { .. }) => ActionType::ToPrivate, (true, _, true, PrivateTransfer { .. }) => ActionType::SelfTransferZero, (true, _, false, PrivateTransfer { .. }) => ActionType::SelfTransfer, (false, _, true, PrivateTransfer { .. }) => ActionType::PrivateTransferZero, (false, _, false, PrivateTransfer { .. }) => ActionType::PrivateTransfer, - (_, true, _, Reclaim { .. }) => ActionType::FlushToPublic, - (_, false, true, Reclaim { .. }) => ActionType::ReclaimZero, - (_, false, false, Reclaim { .. }) => ActionType::Reclaim, + (_, true, _, ToPublic { .. }) => ActionType::FlushToPublic, + (_, false, true, ToPublic { .. }) => ActionType::ToPublicZero, + (_, false, false, ToPublic { .. }) => ActionType::ToPublic, } } @@ -158,13 +162,17 @@ where is_maximal, transaction, } => Self::as_post_type(*is_self, *is_maximal, transaction), - Self::GenerateReceivingKeys { .. } => ActionType::GenerateReceivingKeys, Self::Restart => ActionType::Restart, } } } /// Action Labelled Data +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct ActionLabelled { /// Action Type @@ -174,23 +182,28 @@ pub struct ActionLabelled { pub value: T, } -/// [ActionLabelled`] Error Type +/// [`ActionLabelled`] Error Type pub type ActionLabelledError = ActionLabelled>; /// Possible [`Action`] or an [`ActionLabelledError`] Variant pub type MaybeAction = Result, ActionLabelledError>; /// Action Types +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum ActionType { /// No Action Skip, - /// Mint Action - Mint, + /// To-Private Action + ToPrivate, - /// Mint Zero Action - MintZero, + /// To-Private Zero Action + ToPrivateZero, /// Private Transfer Action PrivateTransfer, @@ -198,11 +211,11 @@ pub enum ActionType { /// Private Transfer Zero Action PrivateTransferZero, - /// Reclaim Action - Reclaim, + /// To-Public Action + ToPublic, - /// Reclaim Zero Action - ReclaimZero, + /// To-Public Zero Action + ToPublicZero, /// Self Private Transfer Action SelfTransfer, @@ -213,9 +226,6 @@ pub enum ActionType { /// Flush-to-Public Transfer Action FlushToPublic, - /// Generate Receiving Keys Action - GenerateReceivingKeys, - /// Restart Wallet Action Restart, } @@ -242,11 +252,11 @@ pub struct ActionDistributionPMF { /// No Action Weight pub skip: T, - /// Mint Action Weight - pub mint: T, + /// To-Private Action Weight + pub to_private: T, - /// Mint Zero Action Weight - pub mint_zero: T, + /// To-Private Zero Action Weight + pub to_private_zero: T, /// Private Transfer Action Weight pub private_transfer: T, @@ -254,11 +264,11 @@ pub struct ActionDistributionPMF { /// Private Transfer Zero Action Weight pub private_transfer_zero: T, - /// Reclaim Action Weight - pub reclaim: T, + /// To-Public Action Weight + pub to_public: T, - /// Reclaim Action Zero Weight - pub reclaim_zero: T, + /// To-Public Action Zero Weight + pub to_public_zero: T, /// Self Private Transfer Action Weight pub self_transfer: T, @@ -269,9 +279,6 @@ pub struct ActionDistributionPMF { /// Flush-to-Public Transfer Action Weight pub flush_to_public: T, - /// Generate Receiving Keys Action Weight - pub generate_receiving_keys: T, - /// Restart Wallet Action Weight pub restart: T, } @@ -281,16 +288,15 @@ impl Default for ActionDistributionPMF { fn default() -> Self { Self { skip: 2, - mint: 5, - mint_zero: 1, + to_private: 5, + to_private_zero: 1, private_transfer: 9, private_transfer_zero: 1, - reclaim: 3, - reclaim_zero: 1, + to_public: 3, + to_public_zero: 1, self_transfer: 2, self_transfer_zero: 1, flush_to_public: 1, - generate_receiving_keys: 3, restart: 4, } } @@ -319,16 +325,15 @@ impl TryFrom for ActionDistribution { Ok(Self { distribution: Categorical::new(&[ pmf.skip as f64, - pmf.mint as f64, - pmf.mint_zero as f64, + pmf.to_private as f64, + pmf.to_private_zero as f64, pmf.private_transfer as f64, pmf.private_transfer_zero as f64, - pmf.reclaim as f64, - pmf.reclaim_zero as f64, + pmf.to_public as f64, + pmf.to_public_zero as f64, pmf.self_transfer as f64, pmf.self_transfer_zero as f64, pmf.flush_to_public as f64, - pmf.generate_receiving_keys as f64, pmf.restart as f64, ])?, }) @@ -343,17 +348,16 @@ impl Distribution for ActionDistribution { { match self.distribution.sample(rng) as usize { 0 => ActionType::Skip, - 1 => ActionType::Mint, - 2 => ActionType::MintZero, + 1 => ActionType::ToPrivate, + 2 => ActionType::ToPrivateZero, 3 => ActionType::PrivateTransfer, 4 => ActionType::PrivateTransferZero, - 5 => ActionType::Reclaim, - 6 => ActionType::ReclaimZero, + 5 => ActionType::ToPublic, + 6 => ActionType::ToPublicZero, 7 => ActionType::SelfTransfer, 8 => ActionType::SelfTransferZero, 9 => ActionType::FlushToPublic, - 10 => ActionType::GenerateReceivingKeys, - 11 => ActionType::Restart, + 10 => ActionType::Restart, _ => unreachable!(), } } @@ -372,7 +376,7 @@ impl Sample for ActionType { /// Public Balance Oracle pub trait PublicBalanceOracle where - C: transfer::Configuration, + C: Configuration, { /// Returns the public balances of `self`. fn public_balances(&self) -> LocalBoxFuture>>; @@ -385,27 +389,35 @@ where pub trait Ledger: ledger::Read> + ledger::Write>, Response = bool> where - C: transfer::Configuration, + C: Configuration, { } impl Ledger for L where - C: transfer::Configuration, + C: Configuration, L: ledger::Read> + ledger::Write>, Response = bool>, { } /// Actor -pub struct Actor +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "Wallet: Clone"), + Debug(bound = "Wallet: Debug"), + Default(bound = "Wallet: Default"), + Eq(bound = "Wallet: Eq"), + PartialEq(bound = "Wallet: PartialEq") +)] +pub struct Actor where - C: transfer::Configuration, + C: Configuration, L: Ledger, S: signer::Connection, - C::AssetValue: CheckedSub, + B: BalanceState, { /// Wallet - pub wallet: Wallet, + pub wallet: Wallet, /// Action Distribution pub distribution: ActionDistribution, @@ -414,16 +426,20 @@ where pub lifetime: usize, } -impl Actor +impl Actor where - C: transfer::Configuration, + C: Configuration, L: Ledger, S: signer::Connection, - C::AssetValue: CheckedSub + SampleUniform, + B: BalanceState, { /// Builds a new [`Actor`] with `wallet`, `distribution`, and `lifetime`. #[inline] - pub fn new(wallet: Wallet, distribution: ActionDistribution, lifetime: usize) -> Self { + pub fn new( + wallet: Wallet, + distribution: ActionDistribution, + lifetime: usize, + ) -> Self { Self { wallet, distribution, @@ -438,11 +454,11 @@ where Some(()) } - /// Returns the default receiving key for `self`. + /// Returns the default address for `self`. #[inline] - async fn default_receiving_key(&mut self) -> Result, Error> { + async fn default_address(&mut self) -> Result, Error> { self.wallet - .receiving_keys() + .address() .await .map_err(Error::SignerConnectionError) } @@ -459,16 +475,40 @@ where Ok(self.wallet.ledger().public_balances().await) } + /// Synchronizes the [`Wallet`] in `self`. + #[inline] + async fn sync(&mut self) -> Result<(), Error> { + self.wallet.sync().await + } + /// Synchronizes with the ledger, attaching the `action` marker for the possible error branch. #[inline] async fn sync_with(&mut self, action: ActionType) -> Result<(), ActionLabelledError> { - self.wallet.sync().await.map_err(|err| action.label(err)) + self.sync().await.map_err(|err| action.label(err)) + } + + /// Posts `transaction` to the ledger, returning a success [`Response`](ledger::Write::Response) if the + /// `transaction` was successfully posted. + #[inline] + async fn post( + &mut self, + transaction: Transaction, + metadata: Option, + ) -> Result> { + self.wallet.post(transaction, metadata).await + } + + /// Returns the [`Address`]. + #[inline] + pub async fn address(&mut self) -> Result, S::Error> { + self.wallet.address().await } /// Samples a deposit from `self` using `rng` returning `None` if no deposit is possible. #[inline] async fn sample_deposit(&mut self, rng: &mut R) -> Result>, Error> where + C::AssetValue: SampleUniform, L: PublicBalanceOracle, R: RngCore + ?Sized, { @@ -477,13 +517,10 @@ where _ => return Ok(None), }; match rng.select_item(assets) { - Some(asset) => { - let value = rng.gen_range(Default::default()..asset.value); - Ok(Some(asset::Asset { - id: asset.id, - value, - })) - } + Some(asset) => Ok(Some(Asset::::new( + asset.id, + rng.gen_range(Default::default()..asset.value), + ))), _ => Ok(None), } } @@ -497,17 +534,15 @@ where #[inline] async fn sample_withdraw(&mut self, rng: &mut R) -> Result>, Error> where + C::AssetValue: SampleUniform, R: RngCore + ?Sized, { - self.wallet.sync().await?; - match rng.select_item(self.wallet.assets()) { - Some((id, value)) => { - let value = rng.gen_range(Default::default()..*value); - Ok(Some(asset::Asset { - id: id.clone(), - value, - })) - } + self.sync().await?; + match rng.select_item(self.wallet.assets().convert_iter()) { + Some((id, value)) => Ok(Some(Asset::::new( + id.clone(), + rng.gen_range(Default::default()..value.clone()), + ))), _ => Ok(None), } } @@ -525,67 +560,68 @@ where { self.sync_with(action).await?; Ok(rng - .select_item(self.wallet.assets()) - .map(|(id, value)| Asset::::new(id.clone(), *value))) + .select_item(self.wallet.assets().convert_iter()) + .map(|(id, value)| Asset::::new(id.clone(), value.clone()))) } - /// Samples a [`Mint`] against `self` using `rng`, returning a [`Skip`] if [`Mint`] is + /// Samples a [`ToPrivate`] against `self` using `rng`, returning a [`Skip`] if [`ToPrivate`] is /// impossible. /// - /// [`Mint`]: ActionType::Mint + /// [`ToPrivate`]: ActionType::ToPrivate /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_mint(&mut self, rng: &mut R) -> MaybeAction + async fn sample_to_private(&mut self, rng: &mut R) -> MaybeAction where + C::AssetValue: SampleUniform, L: PublicBalanceOracle, R: RngCore + ?Sized, { match self.sample_deposit(rng).await { - Ok(Some(asset)) => Ok(Action::mint(asset)), + Ok(Some(asset)) => Ok(Action::to_private(asset)), Ok(_) => Ok(Action::Skip), - Err(err) => Err(ActionType::Mint.label(err)), + Err(err) => Err(ActionType::ToPrivate.label(err)), } } - /// Samples a [`MintZero`] against `self` using `rng` to select the [`AssetId`](transfer::Configuration::AssetId), returning - /// a [`Skip`] if [`MintZero`] is impossible. + /// Samples a [`ToPrivateZero`] against `self` using `rng` to select the `AssetId`, returning + /// a [`Skip`] if [`ToPrivateZero`] is impossible. /// - /// [`MintZero`]: ActionType::MintZero - /// [`AssetId`]: crate::asset::AssetId + /// [`ToPrivateZero`]: ActionType::ToPrivateZero /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_zero_mint(&mut self, rng: &mut R) -> MaybeAction + async fn sample_zero_to_private(&mut self, rng: &mut R) -> MaybeAction where L: PublicBalanceOracle, R: RngCore + ?Sized, { match self.public_balances().await { Ok(Some(assets)) => match rng.select_item(assets) { - Some(asset) => Ok(Action::mint(Asset::::zero(asset.id))), + Some(asset) => Ok(Action::to_private(Asset::::zero(asset.id))), _ => Ok(Action::Skip), }, Ok(_) => Ok(Action::Skip), - Err(err) => Err(ActionType::MintZero.label(err)), + Err(err) => Err(ActionType::ToPrivateZero.label(err)), } } - /// Samples a [`PrivateTransfer`] against `self` using `rng`, returning a [`Mint`] if - /// [`PrivateTransfer`] is impossible and then a [`Skip`] if the [`Mint`] is impossible. + /// Samples a [`PrivateTransfer`] against `self` using `rng`, returning a [`ToPrivate`] if + /// [`PrivateTransfer`] is impossible and then a [`Skip`] if the [`ToPrivate`] is impossible. /// /// [`PrivateTransfer`]: ActionType::PrivateTransfer - /// [`Mint`]: ActionType::Mint + /// [`ToPrivate`]: ActionType::ToPrivate /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_private_transfer( + async fn sample_private_transfer( &mut self, is_self: bool, rng: &mut R, - key: K, + address: A, ) -> MaybeAction where + C::AssetValue: SampleUniform, L: PublicBalanceOracle, R: RngCore + ?Sized, - K: FnOnce(&mut R) -> Result>, Error>, + A: FnOnce(&mut R) -> Result>, Error>, { let action = if is_self { ActionType::SelfTransfer @@ -593,34 +629,34 @@ where ActionType::PrivateTransfer }; match self.sample_withdraw(rng).await { - Ok(Some(asset)) => match key(rng) { - Ok(Some(key)) => Ok(Action::private_transfer(is_self, asset, key)), - Ok(_) => Ok(Action::GenerateReceivingKeys { count: 1 }), + Ok(Some(asset)) => match address(rng) { + Ok(Some(address)) => Ok(Action::private_transfer(is_self, asset, address)), + Ok(_) => Ok(Action::Skip), Err(err) => Err(action.label(err)), }, - Ok(_) => self.sample_mint(rng).await, + Ok(_) => self.sample_to_private(rng).await, Err(err) => Err(action.label(err)), } } - /// Samples a [`PrivateTransferZero`] against `self` using an `rng`, returning a [`Mint`] if - /// [`PrivateTransfer`] is impossible and then a [`Skip`] if the [`Mint`] is impossible. + /// Samples a [`PrivateTransferZero`] against `self` using an `rng`, returning a [`ToPrivate`] + /// if [`PrivateTransfer`] is impossible and then a [`Skip`] if the [`ToPrivate`] is impossible. /// /// [`PrivateTransferZero`]: ActionType::PrivateTransferZero /// [`PrivateTransfer`]: ActionType::PrivateTransfer - /// [`Mint`]: ActionType::Mint + /// [`ToPrivate`]: ActionType::ToPrivate /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_zero_private_transfer( + async fn sample_zero_private_transfer( &mut self, is_self: bool, rng: &mut R, - key: K, + address: A, ) -> MaybeAction where L: PublicBalanceOracle, R: RngCore + ?Sized, - K: FnOnce(&mut R) -> Result>, Error>, + A: FnOnce(&mut R) -> Result>, Error>, { let action = if is_self { ActionType::SelfTransfer @@ -628,59 +664,59 @@ where ActionType::PrivateTransfer }; match self.sample_asset(action, rng).await { - Ok(Some(asset)) => match key(rng) { - Ok(Some(key)) => Ok(Action::private_transfer( + Ok(Some(asset)) => match address(rng) { + Ok(Some(address)) => Ok(Action::private_transfer( is_self, Asset::::zero(asset.id), - key, + address, )), - Ok(_) => Ok(Action::GenerateReceivingKeys { count: 1 }), + Ok(_) => Ok(Action::Skip), Err(err) => Err(action.label(err)), }, - Ok(_) => Ok(self.sample_zero_mint(rng).await?), + Ok(_) => Ok(self.sample_zero_to_private(rng).await?), Err(err) => Err(err), } } - /// Samples a [`Reclaim`] against `self` using `rng`, returning a [`Skip`] if [`Reclaim`] is + /// Samples a [`ToPublic`] against `self` using `rng`, returning a [`Skip`] if [`ToPublic`] is /// impossible. /// - /// [`Reclaim`]: ActionType::Reclaim + /// [`ToPublic`]: ActionType::ToPublic /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_reclaim(&mut self, rng: &mut R) -> MaybeAction + async fn sample_to_public(&mut self, rng: &mut R) -> MaybeAction where + C::AssetValue: SampleUniform, L: PublicBalanceOracle, R: RngCore + ?Sized, { match self.sample_withdraw(rng).await { - Ok(Some(asset)) => Ok(Action::reclaim(false, asset)), - Ok(_) => self.sample_mint(rng).await, - Err(err) => Err(ActionType::Reclaim.label(err)), + Ok(Some(asset)) => Ok(Action::to_public(false, asset)), + Ok(_) => self.sample_to_private(rng).await, + Err(err) => Err(ActionType::ToPublic.label(err)), } } - /// Samples a [`ReclaimZero`] against `self` using `rng`, returning a [`Skip`] if - /// [`ReclaimZero`] is impossible. + /// Samples a [`ToPublicZero`] against `self` using `rng`, returning a [`Skip`] if + /// [`ToPublicZero`] is impossible. /// - /// [`ReclaimZero`]: ActionType::ReclaimZero + /// [`ToPublicZero`]: ActionType::ToPublicZero /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_zero_reclaim(&mut self, rng: &mut R) -> MaybeAction + async fn sample_zero_to_public(&mut self, rng: &mut R) -> MaybeAction where R: RngCore + ?Sized, { Ok(self - .sample_asset(ActionType::ReclaimZero, rng) + .sample_asset(ActionType::ToPublicZero, rng) .await? - .map(|asset| Action::reclaim(false, Asset::::zero(asset.id))) + .map(|asset| Action::to_public(false, Asset::::zero(asset.id))) .unwrap_or(Action::Skip)) } - /// Reclaims all of the private balance of a random [`AssetId`](transfer::Configuration::AssetId) to public balance or [`Skip`] if + /// Reclaims all of the private balance of a random `AssetId` to public balance or [`Skip`] if /// the private balance is empty. /// - /// [`AssetId`]: crate::asset::AssetId /// [`Skip`]: ActionType::Skip #[inline] async fn flush_to_public(&mut self, rng: &mut R) -> MaybeAction @@ -690,7 +726,7 @@ where Ok(self .sample_asset(ActionType::FlushToPublic, rng) .await? - .map(|asset| Action::reclaim(true, asset)) + .map(|asset| Action::to_public(true, asset)) .unwrap_or(Action::Skip)) } @@ -698,8 +734,13 @@ where /// that the balance state has the same or more funds than before the restart. #[inline] async fn restart(&mut self) -> Result> { - self.wallet.sync().await?; - let assets = AssetList::from_iter(self.wallet.assets().clone()); + self.sync().await?; + let assets = AssetList::from_iter( + self.wallet + .assets() + .convert_iter() + .map(|(i, v)| (i.clone(), v.clone())), + ); self.wallet .restart() .await @@ -711,65 +752,67 @@ where pub type Event = ActionLabelled>>>::Response, Error>>; -/// Receiving Key Database -pub type ReceivingKeyDatabase = IndexSet>; +/// Address Database +pub type AddressDatabase = IndexSet>; -/// Shared Receiving Key Database -pub type SharedReceivingKeyDatabase = Arc>>; +/// Shared Address Database +pub type SharedAddressDatabase = Arc>>; /// Simulation #[derive(derivative::Derivative)] -#[derivative(Default(bound = ""))] -pub struct Simulation +#[derivative(Clone, Debug(bound = "Address: Debug"), Default(bound = ""))] +pub struct Simulation where - C: transfer::Configuration, + C: Configuration, L: Ledger, S: signer::Connection, - PublicKey: Eq + Hash, + B: BalanceState, { - /// Receiving Key Database - receiving_keys: SharedReceivingKeyDatabase, + /// Address Database + addresses: SharedAddressDatabase, /// Type Parameter Marker - __: PhantomData<(L, S)>, + __: PhantomData<(L, S, B)>, } -impl Simulation +impl Simulation where - C: transfer::Configuration, + C: Configuration, L: Ledger, S: signer::Connection, - PublicKey: Eq + Hash, + B: BalanceState, + Address: Clone + Eq + Hash, { - /// Builds a new [`Simulation`] with a starting set of public `keys`. + /// Builds a new [`Simulation`] with a starting set of public `addresses`. #[inline] - pub fn new(keys: [PublicKey; N]) -> Self { + pub fn new(addresses: [Address; N]) -> Self { Self { - receiving_keys: Arc::new(Mutex::new(keys.into_iter().collect())), + addresses: Arc::new(Mutex::new(addresses.into_iter().collect())), __: PhantomData, } } - /// Samples a random receiving key from + /// Samples a random address from `rng`. #[inline] - pub fn sample_receiving_key(&self, rng: &mut R) -> Option> + pub fn sample_address(&self, rng: &mut R) -> Option> where R: RngCore + ?Sized, { - rng.select_item(self.receiving_keys.lock().iter()) + rng.select_item(self.addresses.lock().iter()) .map(Clone::clone) } } -impl sim::ActionSimulation for Simulation +impl sim::ActionSimulation for Simulation where - C: transfer::Configuration, + C: Configuration, + C::AssetValue: SampleUniform, L: Ledger + PublicBalanceOracle, S: signer::Connection, - PublicKey: Eq + Hash, - C::AssetValue: CheckedSub + SampleUniform, + B: BalanceState, + Address: Clone + Eq + Hash, { - type Actor = Actor; + type Actor = Actor; type Action = MaybeAction; type Event = Event; @@ -787,43 +830,37 @@ where let action = actor.distribution.sample(rng); Some(match action { ActionType::Skip => Ok(Action::Skip), - ActionType::Mint => actor.sample_mint(rng).await, - ActionType::MintZero => actor.sample_zero_mint(rng).await, + ActionType::ToPrivate => actor.sample_to_private(rng).await, + ActionType::ToPrivateZero => actor.sample_zero_to_private(rng).await, ActionType::PrivateTransfer => { actor - .sample_private_transfer(false, rng, |rng| { - Ok(self.sample_receiving_key(rng)) - }) + .sample_private_transfer(false, rng, |rng| Ok(self.sample_address(rng))) .await } ActionType::PrivateTransferZero => { actor - .sample_zero_private_transfer(false, rng, |rng| { - Ok(self.sample_receiving_key(rng)) - }) + .sample_zero_private_transfer( + false, + rng, + |rng| Ok(self.sample_address(rng)), + ) .await } - ActionType::Reclaim => actor.sample_reclaim(rng).await, - ActionType::ReclaimZero => actor.sample_zero_reclaim(rng).await, + ActionType::ToPublic => actor.sample_to_public(rng).await, + ActionType::ToPublicZero => actor.sample_zero_to_public(rng).await, ActionType::SelfTransfer => { - let key = actor.default_receiving_key().await; + let address = actor.default_address().await; actor - .sample_private_transfer(true, rng, |_| key.map(Some)) + .sample_private_transfer(true, rng, |_| address.map(Some)) .await } ActionType::SelfTransferZero => { - let key = actor.default_receiving_key().await; + let address = actor.default_address().await; actor - .sample_zero_private_transfer(true, rng, |_| key.map(Some)) + .sample_zero_private_transfer(true, rng, |_| address.map(Some)) .await } ActionType::FlushToPublic => actor.flush_to_public(rng).await, - ActionType::GenerateReceivingKeys => Ok(Action::GenerateReceivingKeys { - count: Poisson::new(1.0) - .expect("The Poisson parameter is greater than zero.") - .sample(rng) - .ceil() as usize, - }), ActionType::Restart => Ok(Action::Restart), }) }) @@ -852,7 +889,7 @@ where loop { let event = Event { action, - value: actor.wallet.post(transaction.clone(), None).await, + value: actor.post(transaction.clone(), None).await, }; if let Ok(false) = event.value { if retries == 0 { @@ -866,16 +903,6 @@ where } } } - Action::GenerateReceivingKeys { count: _ } => Event { - action: ActionType::GenerateReceivingKeys, - value: match actor.wallet.receiving_keys().await { - Ok(key) => { - self.receiving_keys.lock().insert(key); - Ok(true) - } - Err(err) => Err(Error::SignerConnectionError(err)), - }, - }, Action::Restart => Event { action: ActionType::Restart, value: actor.restart().await, @@ -892,36 +919,40 @@ where /// Measures the public and secret balances for each wallet, summing them all together. #[inline] -pub async fn measure_balances<'w, C, L, S, I>( +pub async fn measure_balances<'w, C, L, S, B, I>( wallets: I, ) -> Result, Error> where - C: 'w + transfer::Configuration, + C: 'w + Configuration, + C::AssetId: Ord, + C::AssetValue: AddAssign, + for<'v> &'v C::AssetValue: CheckedSub, L: 'w + Ledger + PublicBalanceOracle, S: 'w + signer::Connection, - I: IntoIterator>, - C::AssetValue: CheckedSub, - for<'v> &'v C::AssetValue: CheckedSub, + B: 'w + BalanceState, + I: IntoIterator>, { - let mut balances = AssetList::new(); - for wallet in wallets { + let mut balances = AssetList::::new(); + for wallet in wallets.into_iter() { wallet.sync().await?; - BalanceState::::deposit_all( - &mut balances, - wallet.ledger().public_balances().await.unwrap(), - ); - BalanceState::::deposit_all( - &mut balances, + let public_balance = wallet.ledger().public_balances().await.expect(""); + balances.deposit_all(public_balance); + balances.deposit_all({ wallet .assets() - .iter() - .map(|(id, value)| Asset::::new(id.clone(), *value)), - ); + .convert_iter() + .map(|(id, value)| Asset::::new(id.clone(), value.clone())) + }); } Ok(balances) } /// Simulation Configuration +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub struct Config { /// Actor Count @@ -938,7 +969,7 @@ impl Config { /// Runs the simulation on the configuration defined in `self`, sending events to the /// `event_subscriber`. #[inline] - pub async fn run( + pub async fn run( &self, mut ledger: GL, mut signer: GS, @@ -946,23 +977,24 @@ impl Config { mut event_subscriber: ES, ) -> Result> where - C: transfer::Configuration, + C: Configuration, + C::AssetValue: AddAssign + SampleUniform, + for<'v> &'v C::AssetValue: CheckedSub, L: Ledger + PublicBalanceOracle, S: signer::Connection, + S::Error: Debug, + B: BalanceState, R: CryptoRng + RngCore, GL: FnMut(usize) -> L, GS: FnMut(usize) -> S, - F: FnMut() -> R, - ES: Copy + FnMut(&sim::Event>>) -> ESFut, + F: FnMut(usize) -> R, + ES: Copy + FnMut(&sim::Event>>) -> ESFut, ESFut: Future, - Error: Debug, - PublicKey: Eq + Hash, - C::AssetValue: CheckedSub + SampleUniform, - for<'v> &'v C::AssetValue: CheckedSub, + Address: Clone + Eq + Hash, { let action_distribution = ActionDistribution::try_from(self.action_distribution) .expect("Unable to sample from action distribution."); - let actors = (0..self.actor_count) + let mut actors: Vec<_> = (0..self.actor_count) .map(|i| { Actor::new( Wallet::new(ledger(i), signer(i)), @@ -971,7 +1003,16 @@ impl Config { ) }) .collect(); - let mut simulator = sim::Simulator::new(sim::ActionSim(Simulation::default()), actors); + let simulation = Simulation::default(); + for actor in actors.iter_mut() { + let address = actor + .wallet + .address() + .await + .expect("Wallet should have address"); + simulation.addresses.lock().insert(address); + } + let mut simulator = sim::Simulator::new(sim::ActionSim(simulation), actors); let initial_balances = measure_balances(simulator.actors.iter_mut().map(|actor| &mut actor.wallet)).await?; simulator diff --git a/manta-accounting/src/wallet/test/sim.rs b/manta-accounting/src/wallet/test/sim.rs index c068092f5..2e7692d38 100644 --- a/manta-accounting/src/wallet/test/sim.rs +++ b/manta-accounting/src/wallet/test/sim.rs @@ -22,6 +22,9 @@ use futures::stream::{self, SelectAll, Stream}; use manta_crypto::rand::{CryptoRng, RngCore}; use manta_util::future::LocalBoxFuture; +#[cfg(feature = "serde")] +use manta_util::serde::{Deserialize, Serialize}; + /// Abstract Simulation pub trait Simulation { /// Actor Type @@ -64,6 +67,18 @@ where } /// Simulation Event +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "S::Event: Deserialize<'de>", + serialize = "S::Event: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] #[derive(derivative::Derivative)] #[derivative( Clone(bound = "S::Event: Clone"), @@ -100,6 +115,18 @@ where } /// Simulator +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "S: Deserialize<'de>, S::Actor: Deserialize<'de>", + serialize = "S: Serialize, S::Actor: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] #[derive(derivative::Derivative)] #[derivative( Clone(bound = "S: Clone, S::Actor: Clone"), @@ -135,12 +162,12 @@ where pub fn run<'s, R, F>(&'s mut self, mut rng: F) -> impl 's + Stream> where R: 's + CryptoRng + RngCore, - F: FnMut() -> R, + F: FnMut(usize) -> R, { let mut actors = SelectAll::new(); for (i, actor) in self.actors.iter_mut().enumerate() { actors.push(stream::unfold( - ActorStream::new(&self.simulation, i, actor, rng()), + ActorStream::new(&self.simulation, i, actor, rng(i)), move |mut s| Box::pin(async move { s.next().await.map(move |e| (e, s)) }), )); } @@ -159,6 +186,13 @@ where } /// Actor Stream +#[derive(derivative::Derivative)] +#[derivative( + Debug(bound = "S: Debug, S::Actor: Debug, R: Debug"), + Eq(bound = "S: Eq, S::Actor: Eq, R: Eq"), + Hash(bound = "S: Hash, S::Actor: Hash, R: Hash"), + PartialEq(bound = "S: PartialEq, S::Actor: PartialEq, R: PartialEq") +)] struct ActorStream<'s, S, R> where S: Simulation, @@ -250,6 +284,11 @@ pub trait ActionSimulation { /// /// This `struct` wraps an implementation of [`ActionSimulation`] and implements [`Simulation`] for /// use in some [`Simulator`]. +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct ActionSim(pub S) where diff --git a/manta-benchmark/Cargo.toml b/manta-benchmark/Cargo.toml index d6829c377..e691c6a1e 100644 --- a/manta-benchmark/Cargo.toml +++ b/manta-benchmark/Cargo.toml @@ -32,7 +32,7 @@ name = "ecc" harness = false [[bench]] -name = "mint" +name = "to_private" harness = false [[bench]] @@ -40,7 +40,7 @@ name = "private_transfer" harness = false [[bench]] -name = "reclaim" +name = "to_public" harness = false [dependencies] @@ -48,7 +48,7 @@ getrandom = { version = "0.2.8", default-features = false, features = ["js"] } instant = { version = "0.1.12", default-features = false, features = [ "wasm-bindgen" ] } manta-accounting = { path = "../manta-accounting", default-features = false, features = ["test"] } manta-crypto = { path = "../manta-crypto", default-features = false, features = ["ark-bls12-381", "getrandom", "test"] } -manta-pay = { path = "../manta-pay", default-features = false, features = ["groth16", "test"] } +manta-pay = { path = "../manta-pay", default-features = false, features = ["groth16", "parameters", "test"] } wasm-bindgen = { version = "0.2.83", default-features = false } wasm-bindgen-test = { version = "0.3.33", default-features = false } web-sys = { version = "0.3.59", default-features = false, features = ["console"] } diff --git a/manta-benchmark/benches/private_transfer.rs b/manta-benchmark/benches/private_transfer.rs index 15e875395..0809be538 100644 --- a/manta-benchmark/benches/private_transfer.rs +++ b/manta-benchmark/benches/private_transfer.rs @@ -17,9 +17,11 @@ //! Private Transfer Benchmarks use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use manta_accounting::transfer::test::assert_valid_proof; use manta_crypto::rand::OsRng; -use manta_pay::{parameters, test::payment::prove_private_transfer}; +use manta_pay::{ + parameters, + test::payment::{private_transfer::prove as prove_private_transfer, UtxoAccumulator}, +}; fn prove(c: &mut Criterion) { let mut group = c.benchmark_group("bench"); @@ -28,9 +30,9 @@ fn prove(c: &mut Criterion) { group.bench_function("private transfer prove", |b| { b.iter(|| { prove_private_transfer( - &proving_context, + &proving_context.private_transfer, ¶meters, - &utxo_accumulator_model, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), &mut rng, ); }) @@ -42,15 +44,15 @@ fn verify(c: &mut Criterion) { let mut rng = OsRng; let (proving_context, verifying_context, parameters, utxo_accumulator_model) = parameters::generate().unwrap(); - let private_transfer = black_box(prove_private_transfer( - &proving_context, + let transferpost = black_box(prove_private_transfer( + &proving_context.private_transfer, ¶meters, - &utxo_accumulator_model, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), &mut rng, )); group.bench_function("private transfer verify", |b| { b.iter(|| { - assert_valid_proof(&verifying_context.private_transfer, &private_transfer); + transferpost.assert_valid_proof(&verifying_context.private_transfer); }) }); } diff --git a/manta-benchmark/benches/mint.rs b/manta-benchmark/benches/to_private.rs similarity index 71% rename from manta-benchmark/benches/mint.rs rename to manta-benchmark/benches/to_private.rs index bb62b27e5..9641bae1e 100644 --- a/manta-benchmark/benches/mint.rs +++ b/manta-benchmark/benches/to_private.rs @@ -14,26 +14,23 @@ // You should have received a copy of the GNU General Public License // along with manta-rs. If not, see . -//! Mint Benchmarks +//! To Private Benchmarks use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use manta_accounting::transfer::test::assert_valid_proof; -use manta_crypto::rand::{OsRng, Rand}; -use manta_pay::{parameters, test::payment::prove_mint}; +use manta_crypto::rand::OsRng; +use manta_pay::{parameters, test::payment::to_private::prove as prove_to_private}; fn prove(c: &mut Criterion) { let mut group = c.benchmark_group("bench"); let (proving_context, _verifying_context, parameters, utxo_accumulator_model) = parameters::generate().unwrap(); let mut rng = OsRng; - group.bench_function("mint prove", |b| { - let asset = black_box(rng.gen()); + group.bench_function("to private prove", |b| { b.iter(|| { - prove_mint( - &proving_context.mint, + prove_to_private( + &proving_context.to_private, ¶meters, &utxo_accumulator_model, - asset, &mut rng, ); }) @@ -45,19 +42,18 @@ fn verify(c: &mut Criterion) { let (proving_context, verifying_context, parameters, utxo_accumulator_model) = parameters::generate().unwrap(); let mut rng = OsRng; - let mint = black_box(prove_mint( - &proving_context.mint, + let transferpost = black_box(prove_to_private( + &proving_context.to_private, ¶meters, &utxo_accumulator_model, - rng.gen(), &mut rng, )); - group.bench_function("mint verify", |b| { + group.bench_function("to private verify", |b| { b.iter(|| { - assert_valid_proof(&verifying_context.mint, &mint); + transferpost.assert_valid_proof(&verifying_context.to_private); }) }); } -criterion_group!(mint, prove, verify); -criterion_main!(mint); +criterion_group!(to_private, prove, verify); +criterion_main!(to_private); diff --git a/manta-benchmark/benches/reclaim.rs b/manta-benchmark/benches/to_public.rs similarity index 67% rename from manta-benchmark/benches/reclaim.rs rename to manta-benchmark/benches/to_public.rs index 9277238be..7a03ca671 100644 --- a/manta-benchmark/benches/reclaim.rs +++ b/manta-benchmark/benches/to_public.rs @@ -14,23 +14,25 @@ // You should have received a copy of the GNU General Public License // along with manta-rs. If not, see . -//! Reclaim Benchmarks +//! To Public Benchmarks use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use manta_accounting::transfer::test::assert_valid_proof; use manta_crypto::rand::OsRng; -use manta_pay::{parameters, test::payment::prove_reclaim}; +use manta_pay::{ + parameters, + test::payment::{to_public::prove as prove_to_public, UtxoAccumulator}, +}; fn prove(c: &mut Criterion) { let mut group = c.benchmark_group("bench"); let mut rng = OsRng; let (proving_context, _, parameters, utxo_accumulator_model) = parameters::generate().unwrap(); - group.bench_function("reclaim prove", |b| { + group.bench_function("to public prove", |b| { b.iter(|| { - prove_reclaim( - &proving_context, + prove_to_public( + &proving_context.to_public, ¶meters, - &utxo_accumulator_model, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), &mut rng, ); }) @@ -42,18 +44,18 @@ fn verify(c: &mut Criterion) { let mut rng = OsRng; let (proving_context, verifying_context, parameters, utxo_accumulator_model) = parameters::generate().unwrap(); - let reclaim = black_box(prove_reclaim( - &proving_context, + let transferpost = black_box(prove_to_public( + &proving_context.to_public, ¶meters, - &utxo_accumulator_model, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), &mut rng, )); - group.bench_function("reclaim verify", |b| { + group.bench_function("to public verify", |b| { b.iter(|| { - assert_valid_proof(&verifying_context.reclaim, &reclaim); + transferpost.assert_valid_proof(&verifying_context.to_public); }) }); } -criterion_group!(reclaim, prove, verify); -criterion_main!(reclaim); +criterion_group!(to_public, prove, verify); +criterion_main!(to_public); diff --git a/manta-benchmark/src/lib.rs b/manta-benchmark/src/lib.rs index d3d104599..d072913ea 100644 --- a/manta-benchmark/src/lib.rs +++ b/manta-benchmark/src/lib.rs @@ -20,106 +20,5 @@ #![forbid(rustdoc::broken_intra_doc_links)] #![forbid(missing_docs)] -use manta_accounting::transfer::test::assert_valid_proof; -use manta_crypto::rand::{OsRng, Rand}; -use manta_pay::{ - config::{ - MultiProvingContext, MultiVerifyingContext, Parameters, TransferPost, UtxoAccumulatorModel, - }, - parameters, - test::payment, -}; -use wasm_bindgen::prelude::wasm_bindgen; - pub mod ecc; - -/// Context Type -#[wasm_bindgen] -#[derive(Clone, Debug)] -pub struct Context { - proving_context: MultiProvingContext, - verifying_context: MultiVerifyingContext, - parameters: Parameters, - utxo_accumulator_model: UtxoAccumulatorModel, -} - -#[wasm_bindgen] -impl Context { - /// Constructs a new [`Context`] which can be used for proving and verifying [`Proof`]. - #[wasm_bindgen(constructor)] - pub fn new() -> Self { - let (proving_context, verifying_context, parameters, utxo_accumulator_model) = - parameters::generate().expect("Unable to generate default parameters."); - Self { - proving_context, - verifying_context, - parameters, - utxo_accumulator_model, - } - } -} - -impl Default for Context { - fn default() -> Self { - Self::new() - } -} - -/// Proof Type -#[wasm_bindgen] -pub struct Proof(TransferPost); - -/// Proves a mint [`Proof`] given the [`Context`]. -#[wasm_bindgen] -pub fn prove_mint(context: &Context) -> Proof { - let mut rng = OsRng; - Proof(payment::prove_mint( - &context.proving_context.mint, - &context.parameters, - &context.utxo_accumulator_model, - rng.gen(), - &mut rng, - )) -} - -/// Proves a private transfer [`Proof`] given the [`Context`]. -#[wasm_bindgen] -pub fn prove_private_transfer(context: &Context) -> Proof { - let mut rng = OsRng; - Proof(payment::prove_private_transfer( - &context.proving_context, - &context.parameters, - &context.utxo_accumulator_model, - &mut rng, - )) -} - -/// Proves a reclaim [`Proof`] given the [`Context`]. -#[wasm_bindgen] -pub fn prove_reclaim(context: &Context) -> Proof { - let mut rng = OsRng; - Proof(payment::prove_reclaim( - &context.proving_context, - &context.parameters, - &context.utxo_accumulator_model, - &mut rng, - )) -} - -/// Verifies a mint [`Proof`] given the [`Context`]. -#[wasm_bindgen] -pub fn verify_mint(context: &Context, proof: &Proof) { - assert_valid_proof(&context.verifying_context.mint, &proof.0); -} - -/// Verifies a private transfer [`Proof`] given the [`Context`]. -#[wasm_bindgen] -pub fn verify_private_transfer(context: &Context, proof: &Proof) { - assert_valid_proof(&context.verifying_context.private_transfer, &proof.0); -} - -/// Verifies a reclaim [`Proof`] given the [`Context`]. -#[wasm_bindgen] -pub fn verify_reclaim(context: &Context, proof: &Proof) { - assert_valid_proof(&context.verifying_context.reclaim, &proof.0); -} +pub mod transfer; diff --git a/manta-benchmark/src/transfer.rs b/manta-benchmark/src/transfer.rs new file mode 100644 index 000000000..3edf32e4a --- /dev/null +++ b/manta-benchmark/src/transfer.rs @@ -0,0 +1,144 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Transfer Benchmarking Suite + +use manta_crypto::rand::{OsRng, Rand}; +use manta_pay::{ + config::{self, MultiProvingContext, MultiVerifyingContext, Parameters, UtxoAccumulatorModel}, + parameters, + test::payment::{ + private_transfer::prove_full as prove_private_transfer_full, + to_private::prove_full as prove_to_private_full, + to_public::prove_full as prove_to_public_full, UtxoAccumulator, + }, +}; +use wasm_bindgen::prelude::wasm_bindgen; + +/// Context Type +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct Context { + /// Proving Context + proving_context: MultiProvingContext, + + /// Verifying Context + verifying_context: MultiVerifyingContext, + + /// Parameters + parameters: Parameters, + + /// Utxo Accumulator Model + utxo_accumulator_model: UtxoAccumulatorModel, +} + +#[wasm_bindgen] +impl Context { + /// Constructs a new [`Context`] which can be used for proving and verifying [`TransferPost`]. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + let (proving_context, verifying_context, parameters, utxo_accumulator_model) = + parameters::generate().expect("Unable to generate default parameters."); + Self { + proving_context, + verifying_context, + parameters, + utxo_accumulator_model, + } + } +} + +impl Default for Context { + fn default() -> Self { + Self::new() + } +} + +/// TransferPost +#[wasm_bindgen] +pub struct TransferPost(config::TransferPost); + +/// Generates a to_private [`TransferPost`] given the [`Context`]. +#[wasm_bindgen] +pub fn prove_to_private(context: &Context) -> TransferPost { + let mut rng = OsRng; + TransferPost(prove_to_private_full( + &context.proving_context.to_private, + &context.parameters, + &mut UtxoAccumulator::new(context.utxo_accumulator_model.clone()), + rng.gen(), + rng.gen(), + &mut rng, + )) +} + +/// Generates a private transfer [`TransferPost`] given the [`Context`]. +#[wasm_bindgen] +pub fn prove_private_transfer(context: &Context) -> TransferPost { + let mut rng = OsRng; + TransferPost( + prove_private_transfer_full( + &context.proving_context, + &context.parameters, + &mut UtxoAccumulator::new(context.utxo_accumulator_model.clone()), + rng.gen(), + [rng.gen::<_, u128>() / 2, rng.gen::<_, u128>() / 2], + &mut rng, + ) + .1, + ) +} + +/// Generates a to_public [`TransferPost`] given the [`Context`]. +#[wasm_bindgen] +pub fn prove_to_public(context: &Context) -> TransferPost { + let mut rng = OsRng; + TransferPost( + prove_to_public_full( + &context.proving_context, + &context.parameters, + &mut UtxoAccumulator::new(context.utxo_accumulator_model.clone()), + rng.gen(), + [rng.gen::<_, u128>() / 2, rng.gen::<_, u128>() / 2], + &mut rng, + ) + .1, + ) +} + +/// Verifies a to_private [`TransferPost`] given the [`Context`]. +#[wasm_bindgen] +pub fn verify_to_private(context: &Context, transferpost: &TransferPost) { + transferpost + .0 + .assert_valid_proof(&context.verifying_context.to_private); +} + +/// Verifies a private transfer [`TransferPost`] given the [`Context`]. +#[wasm_bindgen] +pub fn verify_private_transfer(context: &Context, transferpost: &TransferPost) { + transferpost + .0 + .assert_valid_proof(&context.verifying_context.private_transfer); +} + +/// Verifies a to_public [`TransferPost`] given the [`Context`]. +#[wasm_bindgen] +pub fn verify_to_public(context: &Context, transferpost: &TransferPost) { + transferpost + .0 + .assert_valid_proof(&context.verifying_context.to_public); +} diff --git a/manta-benchmark/tests/web.rs b/manta-benchmark/tests/web.rs index 0f6cd64c4..3f63ff46e 100644 --- a/manta-benchmark/tests/web.rs +++ b/manta-benchmark/tests/web.rs @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with manta-rs. If not, see . -use manta_benchmark::{prove_mint, prove_private_transfer, prove_reclaim, Context, Proof}; +use manta_benchmark::transfer::{ + prove_private_transfer, prove_to_private, prove_to_public, Context, TransferPost, +}; use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; use web_sys::console; @@ -24,7 +26,7 @@ static REPEAT: usize = 3; fn bench(mut f: F, operation: &str) where - F: FnMut(&Context) -> Proof, + F: FnMut(&Context) -> TransferPost, { let context = Context::new(); let start_time = instant::Instant::now(); @@ -43,8 +45,8 @@ where } #[wasm_bindgen_test] -fn bench_prove_mint() { - bench(prove_mint, "Prove Mint"); +fn bench_prove_to_private() { + bench(prove_to_private, "Prove ToPrivate"); } #[wasm_bindgen_test] @@ -53,6 +55,6 @@ fn bench_prove_private_transfer() { } #[wasm_bindgen_test] -fn bench_prove_reclaim() { - bench(prove_reclaim, "Prove Reclaim"); +fn bench_prove_to_public() { + bench(prove_to_public, "Prove ToPublic"); } diff --git a/manta-benchmark/www/index.js b/manta-benchmark/www/index.js index ed29b8ad5..1f73a204b 100644 --- a/manta-benchmark/www/index.js +++ b/manta-benchmark/www/index.js @@ -1,4 +1,4 @@ -import { Context, prove_mint, prove_private_transfer, prove_reclaim} from "wasm-prover"; +import { Context, prove_to_private, prove_private_transfer, prove_to_public} from "wasm-prover"; const pre = document.getElementById("wasm-prover"); @@ -11,18 +11,18 @@ const median = arr => { return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2; }; -function bench_prove_mint() { +function bench_prove_to_private() { const context = new Context(); const perf = Array.from( {length: REPEAT}, (_, i) => { const t0 = performance.now(); - prove_mint(context); + prove_to_private(context); const t1 = performance.now(); return t1 - t0; } ); - return `prove mint performance: ${median(perf)} ms \n`; + return `prove to_private performance: ${median(perf)} ms \n`; } function bench_prove_private_transfer() { @@ -39,25 +39,25 @@ function bench_prove_private_transfer() { return `prove private transfer performance: ${median(perf)} ms \n`; } -function bench_prove_reclaim() { +function bench_prove_to_public() { const context = new Context(); const perf = Array.from( {length: REPEAT}, (_, i) => { const t0 = performance.now(); - prove_reclaim(context); + prove_to_public(context); const t1 = performance.now(); return t1 - t0; } ); - return `prove reclaim performance: ${median(perf)} ms \n`; + return `prove to_public performance: ${median(perf)} ms \n`; } -// benchmarks proof time for mint -pre.textContent = bench_prove_mint(); +// benchmarks proof time for to_private +pre.textContent = bench_prove_to_private(); // benchmarks proof time for private transfer pre.textContent += bench_prove_private_transfer(); -// benchmarks proof time for reclaim -pre.textContent += bench_prove_reclaim(); +// benchmarks proof time for to_public +pre.textContent += bench_prove_to_public(); diff --git a/manta-crypto/Cargo.toml b/manta-crypto/Cargo.toml index 9a455aa7c..b4507888f 100644 --- a/manta-crypto/Cargo.toml +++ b/manta-crypto/Cargo.toml @@ -33,7 +33,8 @@ arkworks = [ "ark-relations", "ark-serialize", "ark-snark", - "ark-std" + "ark-std", + "num-integer", ] # Dalek Cryptography Backend @@ -87,9 +88,10 @@ ark-std = { version = "0.3.0", optional = true, default-features = false } derivative = { version = "2.2.0", default-features = false, features = ["use_core"] } ed25519-dalek = { version = "1.0.1", optional = true, default-features = false, features = ["u64_backend"] } manta-util = { path = "../manta-util", default-features = false, features = ["alloc"] } +num-integer = { version = "0.1.45", optional = true, default-features = false } rand = { version = "0.8.4", optional = true, default-features = false, features = ["alloc"] } rand_chacha = { version = "0.3.1", optional = true, default-features = false } rand_core = { version = "0.6.3", default-features = false } [dev-dependencies] -manta-crypto = { path = ".", default-features = false, features = ["ark-bn254", "getrandom"] } +manta-crypto = { path = ".", default-features = false, features = ["ark-bn254", "ark-ed-on-bn254", "getrandom", "std", "test"] } diff --git a/manta-crypto/src/arkworks/algebra.rs b/manta-crypto/src/arkworks/algebra.rs index 4b78bfd06..5e0154850 100644 --- a/manta-crypto/src/arkworks/algebra.rs +++ b/manta-crypto/src/arkworks/algebra.rs @@ -16,21 +16,46 @@ //! Arkworks Algebra Backend -use crate::arkworks::{ - ec::ProjectiveCurve, - ff::{BigInteger, Field, FpParameters, PrimeField}, - r1cs_std::{fields::fp::FpVar, groups::CurveVar}, - serialize::CanonicalSerialize, +use crate::{ + algebra::{self, FixedBaseScalarMul}, + arkworks::{ + constraint::{conditionally_select, empty, fp::Fp, full, Boolean, R1CS}, + ec::{AffineCurve, ProjectiveCurve}, + ff::{BigInteger, Field, FpParameters, PrimeField, ToConstraintField, Zero as _}, + r1cs_std::{eq::EqGadget, fields::fp::FpVar, groups::CurveVar, ToBitsGadget}, + relations::ns, + serialize::{ + ArkReader, ArkWriter, CanonicalDeserialize, CanonicalSerialize, SerializationError, + }, + }, + constraint::{Input, ProofSystem}, + eclair::{ + alloc::{ + mode::{Public, Secret}, + Allocate, Allocator, Constant, Variable, + }, + bool::{Bool, ConditionalSelect}, + cmp, + num::Zero, + }, + rand::{RngCore, Sample}, }; use alloc::vec::Vec; -use core::marker::PhantomData; +use core::{borrow::Borrow, marker::PhantomData}; +use manta_util::{codec, AsBytes}; #[cfg(feature = "serde")] -use manta_util::serde::Serializer; +use manta_util::serde::{Deserialize, Serialize, Serializer}; /// Constraint Field Type type ConstraintField = <::BaseField as Field>::BasePrimeField; +/// Compiler Type +type Compiler = R1CS>; + +/// Scalar Field Element +pub type Scalar = Fp<::ScalarField>; + /// Returns the modulus bits of scalar field of a given curve `C`. #[inline] pub const fn scalar_bits() -> usize @@ -91,6 +116,26 @@ where serializer.serialize_bytes(&affine_point_as_bytes::(point)) } +/// Lifts an embedded scalar to an outer scalar. +/// +/// # Crypto Safety +/// +/// This can only be used whenver the embedded scalar field is **smaller** than the outer scalar +/// field. +#[inline] +pub fn lift_embedded_scalar(scalar: &Scalar) -> Fp> +where + C: ProjectiveCurve, +{ + assert!( + modulus_is_smaller::>(), + "The modulus of the embedded scalar field is larger than that of the constraint field." + ); + Fp(ConstraintField::::from_le_bytes_mod_order( + &scalar.0.into_repr().to_bytes_le(), + )) +} + /// Elliptic Curve Scalar Element Variable /// /// # Safety @@ -99,7 +144,7 @@ where /// outer scalar field. #[derive(derivative::Derivative)] #[derivative(Clone(bound = ""))] -pub struct ScalarVar(FpVar>, PhantomData) +pub struct ScalarVar(pub(crate) FpVar>, PhantomData) where C: ProjectiveCurve, CV: CurveVar>; @@ -116,6 +161,69 @@ where } } +impl cmp::PartialEq> for ScalarVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + #[inline] + fn eq(&self, rhs: &Self, compiler: &mut Compiler) -> Boolean> { + let _ = compiler; + self.as_ref() + .is_eq(rhs.as_ref()) + .expect("Equality checking is not allowed to fail.") + } +} + +impl Constant> for ScalarVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + type Type = Scalar; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new(lift_embedded_scalar::(this).as_constant(compiler)) + } +} + +impl Variable> for ScalarVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + type Type = Scalar; + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new(lift_embedded_scalar::(this).as_known::(compiler)) + } + + #[inline] + fn new_unknown(compiler: &mut Compiler) -> Self { + Self::new(compiler.allocate_unknown::()) + } +} + +impl Variable> for ScalarVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + type Type = Scalar; + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new(lift_embedded_scalar::(this).as_known::(compiler)) + } + + #[inline] + fn new_unknown(compiler: &mut Compiler) -> Self { + Self::new(compiler.allocate_unknown::()) + } +} + impl AsRef>> for ScalarVar where C: ProjectiveCurve, @@ -126,3 +234,502 @@ where &self.0 } } + +/// Elliptic Curve Group Element +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound(deserialize = "", serialize = ""), + crate = "manta_util::serde", + deny_unknown_fields, + try_from = "Vec" + ) +)] +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub struct Group( + /// Affine Point Representation + #[cfg_attr( + feature = "serde", + serde(serialize_with = "serialize_group_element::") + )] + pub C::Affine, +) +where + C: ProjectiveCurve; + +impl ToConstraintField> for Group +where + C: ProjectiveCurve, + C::Affine: ToConstraintField>, +{ + #[inline] + fn to_field_elements(&self) -> Option>> { + self.0.to_field_elements() + } +} + +impl Input