From 33e76eb35ad570ae1ad34b7cea1152f09aae625d Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 24 Nov 2022 12:27:48 +0100 Subject: [PATCH 01/44] utxo protocol Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/transfer/mod.rs | 1 + manta-accounting/src/transfer/utxo/auth.rs | 441 +++ manta-accounting/src/transfer/utxo/mod.rs | 520 ++++ .../src/transfer/utxo/protocol.rs | 2735 +++++++++++++++++ 4 files changed, 3697 insertions(+) create mode 100644 manta-accounting/src/transfer/utxo/auth.rs create mode 100644 manta-accounting/src/transfer/utxo/mod.rs create mode 100644 manta-accounting/src/transfer/utxo/protocol.rs diff --git a/manta-accounting/src/transfer/mod.rs b/manta-accounting/src/transfer/mod.rs index c251b78d2..cb604b1c3 100644 --- a/manta-accounting/src/transfer/mod.rs +++ b/manta-accounting/src/transfer/mod.rs @@ -64,6 +64,7 @@ mod sender; pub mod batch; pub mod canonical; +pub mod utxo; #[cfg(feature = "test")] #[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] diff --git a/manta-accounting/src/transfer/utxo/auth.rs b/manta-accounting/src/transfer/utxo/auth.rs new file mode 100644 index 000000000..fcff0f5f2 --- /dev/null +++ b/manta-accounting/src/transfer/utxo/auth.rs @@ -0,0 +1,441 @@ +// 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 +#[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 } + } + + /// + #[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) + } + + /// + #[inline] + pub fn verify(&self, parameters: &T, spending_key: &T::SpendingKey) -> bool + where + T: VerifyAuthorization, + { + parameters.verify(spending_key, &self.context, &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, + ) 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."); + assert!( + signature.verify(parameters, message), + "Unable to verify message." + ) + } +} \ No newline at end of file diff --git a/manta-accounting/src/transfer/utxo/mod.rs b/manta-accounting/src/transfer/utxo/mod.rs new file mode 100644 index 000000000..c4234866a --- /dev/null +++ b/manta-accounting/src/transfer/utxo/mod.rs @@ -0,0 +1,520 @@ +// 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}, + algebra::{HasGenerator, ScalarMul}, + eclair::alloc::{Allocate, Constant}, + rand::RngCore, +}; +use manta_util::cmp::IndependenceContext; + +pub mod auth; +pub mod protocol; + +use self::protocol::ViewingKeyDerivationFunction; + +/// 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 +#[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 +#[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 { + /// Check if `utxo` is consistent with `asset` and `identifier`, which come from + /// decrypting a Note. + fn utxo_check( + &self, + utxo: &Self::Utxo, + asset: &Self::Asset, + identifier: &Self::Identifier, + decryption_key: &Self::DecryptionKey, + ) -> bool; + + /// Check if `utxo` is consistent with a `note` and tries to open `note`. + /// Mainly used when `note` is of type LightIncomingNote which is computed off-circuit. + 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; + + /// + 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. +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 + } +} + +/// Computes the address corresponding to `spending_key`. +#[inline] +pub fn address_from_spending_key( + spending_key: &C::Scalar, + parameters: &protocol::Parameters, +) -> protocol::Address +where + C: protocol::Configuration, +{ + let generator = parameters.base.group_generator.generator(); + protocol::Address::new( + generator.scalar_mul( + ¶meters + .base + .viewing_key_derivation_function + .viewing_key(&generator.scalar_mul(spending_key, &mut ()), &mut ()), + &mut (), + ), + ) +} diff --git a/manta-accounting/src/transfer/utxo/protocol.rs b/manta-accounting/src/transfer/utxo/protocol.rs new file mode 100644 index 000000000..b20649d7f --- /dev/null +++ b/manta-accounting/src/transfer/utxo/protocol.rs @@ -0,0 +1,2735 @@ +// 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}, + }, +}; +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::{Decode, DecodeError, 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, + >; + + /// Incoming Light Ciphertext Type + type LightIncomingCiphertext: PartialEq; + + /// Incoming Light 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 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, +>; + +/// Incoming Base Randomness +pub type IncomingBaseRandomness = + <>::IncomingBaseEncryptionScheme as encryption::RandomnessType>::Randomness; + +/// 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 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 +#[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, + "), + 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, + ") +)] +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, +// COM: Has, +// { +// type Identifier = Identifier; +// } + +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(), + ) + } +} + +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: core::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, +{ + #[inline] + fn verify( + &self, + authorization_key: &Self::AuthorizationKey, + message: &M, + signature: &Self::Signature, + ) -> bool { + self.signature_scheme() + .verify(authorization_key, &message.to_vec(), signature, &mut ()) + } +} + +// impl utxo::DeriveAddress> for Parameters +// where +// C: Configuration, +// { +// #[inline] +// fn derive_address(&self, key: &SpendingKey) -> Self::Address { +// let generator = self.base.group_generator.generator(); +// Address::new( +// generator.scalar_mul( +// &self +// .base +// .viewing_key_derivation_function +// .viewing_key(&generator.scalar_mul(key, &mut ()), &mut ()), +// &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, + encryption::Randomness: Sample, + UtxoCommitmentRandomness: Sample, + C::Group: Debug, + C::Scalar: Debug, + C::IncomingCiphertext: Debug, + C::LightIncomingCiphertext: Debug, + ::Randomness: Clone, +{ + #[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, + C::IncomingHeader::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(), + C::LightIncomingHeader::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 Secret = SpendSecret; + type UtxoAccumulatorItemHash = C::UtxoAccumulatorItemHash; + + #[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, + encryption::Randomness: 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>>, + C::Group: Debug, + C::LightIncomingCiphertext: Debug, +{ + /// opens `note` if the note partition is consistent with the receiving key par + #[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>>, + C::Group: Debug, + C::LightIncomingCiphertext: Debug, + 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"), + 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 +#[derive(derivative::Derivative)] +#[derivative(Clone(bound = "UtxoCommitmentRandomness: Clone, Asset: Clone"))] +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" + ), + Debug( + bound = "AddressPartition: Debug, IncomingNote: Debug, LightIncomingNote: Debug" + ), + 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, + 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 +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, + C::IncomingHeader::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 + ::Randomness: Clone, + { + Hybrid::new( + StandardDiffieHellman::new(group_generator.clone()), + encryption_scheme.clone(), + ) + .encrypt_into( + &self.receiving_key, + &self.light_incoming_randomness(), + C::LightIncomingHeader::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 + } + + /// + #[inline] + fn light_incoming_randomness(&self) -> LightIncomingRandomness + where + ::Randomness: 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, +// COM: Has, +// { +// type Identifier = Identifier; +// } + +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 +#[derive(derivative::Derivative)] +#[derivative(Debug(bound = "C::Group: Debug, C::Scalar: Debug"))] +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, + } + } + + /// + #[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 core::cmp::PartialEq for AuthorizationContext +where + C: BaseConfiguration, + C::Group: core::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 +#[derive(derivative::Derivative)] +#[derivative(Debug(bound = "C::Scalar: Debug, C::Group: Debug"))] +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 +// #[derive(derivative::Derivative)] +// #[derivative( +// Clone(bound = "C::Bool: Clone, UtxoCommitmentRandomness: Clone"), +// Hash(bound = "C::Bool: Hash, UtxoCommitmentRandomness: Hash") +// )] +// pub struct Identifier +// where +// C: BaseConfiguration, +// COM: Has, +// { +// /// Transparency Flag +// pub is_transparent: C::Bool, + +// /// UTXO Commitment Randomness +// pub utxo_commitment_randomness: UtxoCommitmentRandomness, +// } + +// impl Identifier +// where +// C: BaseConfiguration, +// COM: Has, +// { +// /// Builds a new [`Identifier`] from `is_transparent` and `utxo_commitment_randomness`. +// #[inline] +// pub fn new( +// is_transparent: C::Bool, +// utxo_commitment_randomness: UtxoCommitmentRandomness, +// ) -> Self { +// Self { +// is_transparent, +// utxo_commitment_randomness, +// } +// } +// } + +/// Identifier +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "UtxoCommitmentRandomness: Clone"), + 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, + { + // FIXME: Should we sample the transparency flag. + Self::new(false, rng.gen()) + } +} + +/// Spend Secret +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, + C::OutgoingHeader::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"), + Debug(bound = "NullifierCommitment: Debug"), + 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"), + Debug(bound = "Nullifier: Debug, OutgoingNote: Debug"), + 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 ()) -> C::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(()) + } +} + +/// Full Nullifier Decode Error +#[derive(derivative::Derivative)] +#[derivative( + Clone( + bound = " as Decode>::Error: Clone, as Decode>::Error: Clone" + ), + Copy( + bound = " as Decode>::Error: Copy, as Decode>::Error: Copy" + ), + Debug( + bound = " as Decode>::Error: Debug, as Decode>::Error: Debug" + ), + Eq( + bound = " as Decode>::Error: Eq, as Decode>::Error: Eq" + ), + Hash( + bound = " as Decode>::Error: Hash, as Decode>::Error: Hash" + ), + PartialEq( + bound = " as Decode>::Error: cmp::PartialEq, as Decode>::Error: cmp::PartialEq" + ) +)] +pub enum FullNullifierDecodeError +where + C: Configuration, + NullifierCommitment: Decode, + OutgoingNote: Decode, +{ + /// Nullifier Commitment Error + CommitmentDecodeError( as Decode>::Error), + + /// Note Error + NoteDecodeError( as Decode>::Error), +} + +impl Decode for FullNullifier +where + C: Configuration, + NullifierCommitment: Decode, + OutgoingNote: Decode, +{ + type Error = FullNullifierDecodeError; + + fn decode(mut reader: R) -> Result> + where + R: manta_util::codec::Read, + { + let nullifier_commitment = + NullifierCommitment::::decode(&mut reader).map_err(|e| match e { + DecodeError::Decode(r) => { + DecodeError::Decode(FullNullifierDecodeError::CommitmentDecodeError(r)) + } + DecodeError::Read(e) => DecodeError::Read(e), + })?; + let outgoing_note = OutgoingNote::::decode(&mut reader).map_err(|e| match e { + DecodeError::Decode(r) => { + DecodeError::Decode(FullNullifierDecodeError::NoteDecodeError(r)) + } + DecodeError::Read(e) => DecodeError::Read(e), + })?; + Ok(Self { + nullifier: Nullifier::new(nullifier_commitment), + outgoing_note, + }) + } +} + +impl Input

for FullNullifier +where + C: Configuration, + P: HasInput> + ?Sized, +{ + #[inline] + fn extend(&self, input: &mut P::Input) { + P::extend(input, &self.nullifier.commitment); + } +} From 7b5f578e7c679c511330ff45f9f6d2e0359381b7 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 24 Nov 2022 16:06:51 +0100 Subject: [PATCH 02/44] utxo finished Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/transfer/utxo/auth.rs | 20 +- manta-accounting/src/transfer/utxo/mod.rs | 82 ++-- .../src/transfer/utxo/protocol.rs | 440 +++++++++++------- 3 files changed, 331 insertions(+), 211 deletions(-) diff --git a/manta-accounting/src/transfer/utxo/auth.rs b/manta-accounting/src/transfer/utxo/auth.rs index fcff0f5f2..05050575a 100644 --- a/manta-accounting/src/transfer/utxo/auth.rs +++ b/manta-accounting/src/transfer/utxo/auth.rs @@ -174,6 +174,18 @@ pub trait VerifySignature: AuthorizationKeyType + SignatureType { } /// 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"), @@ -205,7 +217,7 @@ where 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 @@ -217,7 +229,7 @@ where 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 @@ -226,7 +238,7 @@ where 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 @@ -438,4 +450,4 @@ pub mod test { "Unable to verify message." ) } -} \ No newline at end of file +} diff --git a/manta-accounting/src/transfer/utxo/mod.rs b/manta-accounting/src/transfer/utxo/mod.rs index c4234866a..20b0011f8 100644 --- a/manta-accounting/src/transfer/utxo/mod.rs +++ b/manta-accounting/src/transfer/utxo/mod.rs @@ -25,17 +25,17 @@ use crate::transfer::utxo::auth::AuthorizationContextType; use core::{fmt::Debug, hash::Hash, marker::PhantomData, ops::Deref}; use manta_crypto::{ accumulator::{self, ItemHashFunction, MembershipProof}, - algebra::{HasGenerator, ScalarMul}, 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; -use self::protocol::ViewingKeyDerivationFunction; - /// Current UTXO Protocol Version pub const VERSION: u8 = protocol::VERSION; @@ -101,6 +101,18 @@ pub trait IdentifierType { 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"), @@ -152,6 +164,18 @@ pub trait AssociatedDataType { 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"), @@ -237,8 +261,7 @@ pub trait NoteOpen: AssetType + DeriveDecryptionKey + IdentifierType + NoteType /// Utxo Reconstruction pub trait UtxoReconstruct: NoteOpen { - /// Check if `utxo` is consistent with `asset` and `identifier`, which come from - /// decrypting a Note. + /// Checks if `utxo` is consistent with `asset` and `identifier`. fn utxo_check( &self, utxo: &Self::Utxo, @@ -247,8 +270,9 @@ pub trait UtxoReconstruct: NoteOpen { decryption_key: &Self::DecryptionKey, ) -> bool; - /// Check if `utxo` is consistent with a `note` and tries to open `note`. - /// Mainly used when `note` is of type LightIncomingNote which is computed off-circuit. + /// 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, @@ -333,7 +357,7 @@ pub trait Spend: AuthorizationContextType + AssetType + UtxoType + Nul /// 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 @@ -394,6 +418,27 @@ pub type UtxoMembershipProof = MembershipProof where P: Mint + Spend, @@ -497,24 +542,3 @@ where self.base } } - -/// Computes the address corresponding to `spending_key`. -#[inline] -pub fn address_from_spending_key( - spending_key: &C::Scalar, - parameters: &protocol::Parameters, -) -> protocol::Address -where - C: protocol::Configuration, -{ - let generator = parameters.base.group_generator.generator(); - protocol::Address::new( - generator.scalar_mul( - ¶meters - .base - .viewing_key_derivation_function - .viewing_key(&generator.scalar_mul(spending_key, &mut ()), &mut ()), - &mut (), - ), - ) -} diff --git a/manta-accounting/src/transfer/utxo/protocol.rs b/manta-accounting/src/transfer/utxo/protocol.rs index b20649d7f..e79014c98 100644 --- a/manta-accounting/src/transfer/utxo/protocol.rs +++ b/manta-accounting/src/transfer/utxo/protocol.rs @@ -20,7 +20,7 @@ use crate::{ asset, transfer::utxo::{ self, - auth::{self, DeriveContext}, + auth::{self, DeriveContext, SpendingKey}, }, }; use alloc::vec::Vec; @@ -53,7 +53,7 @@ use manta_crypto::{ }; use manta_util::{ cmp::Independence, - codec::{Decode, DecodeError, Encode, Write}, + codec::{Encode, Write}, convert::Field, }; @@ -245,10 +245,10 @@ where ViewingKey = Self::Scalar, >; - /// Incoming Light Ciphertext Type + /// Light Incoming Ciphertext Type type LightIncomingCiphertext: PartialEq; - /// Incoming Light Header + /// Light Incoming Header type LightIncomingHeader: Default + PartialEq; /// Base Encryption Scheme for [`LightIncomingNote`] @@ -346,6 +346,10 @@ pub type UtxoCommitment = pub type UtxoCommitmentRandomness = <>::UtxoCommitmentScheme as UtxoCommitmentScheme>::Randomness; +/// Incoming Base Randomness +pub type IncomingBaseRandomness = + encryption::Randomness<>::IncomingBaseEncryptionScheme>; + /// Incoming Encryption Scheme pub type IncomingEncryptionScheme = Hybrid< StandardDiffieHellman< @@ -370,10 +374,6 @@ pub type LightIncomingEncryptionScheme = Hybrid< >::LightIncomingBaseEncryptionScheme, >; -/// Incoming Base Randomness -pub type IncomingBaseRandomness = - <>::IncomingBaseEncryptionScheme as encryption::RandomnessType>::Randomness; - /// Light Incoming Randomness pub type LightIncomingRandomness = encryption::Randomness>; @@ -400,6 +400,10 @@ pub type UtxoMembershipProof = pub type NullifierCommitment = <>::NullifierCommitmentScheme as NullifierCommitmentScheme>::Commitment; +/// Outgoing Base Randomness +pub type OutgoingBaseRandomness = + encryption::Randomness<>::OutgoingBaseEncryptionScheme>; + /// Outgoing Encryption Scheme pub type OutgoingEncryptionScheme = Hybrid< StandardDiffieHellman< @@ -423,6 +427,32 @@ pub type AddressPartition = 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" @@ -435,6 +465,16 @@ pub type SignatureScheme = schnorr::Schnorr<::SchnorrHash 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, @@ -454,6 +494,36 @@ pub type SignatureScheme = schnorr::Schnorr<::SchnorrHash 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 @@ -550,14 +620,6 @@ where type Nullifier = Nullifier; } -// impl utxo::IdentifierType for BaseParameters -// where -// C: BaseConfiguration, -// COM: Has, -// { -// type Identifier = Identifier; -// } - impl utxo::IdentifierType for BaseParameters where C: BaseConfiguration, @@ -826,6 +888,24 @@ where 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 @@ -961,7 +1041,7 @@ where impl auth::VerifyAuthorization for Parameters where C: Configuration, - C::Group: core::cmp::PartialEq, + C::Group: cmp::PartialEq, { #[inline] fn verify( @@ -1028,25 +1108,6 @@ where } } -// impl utxo::DeriveAddress> for Parameters -// where -// C: Configuration, -// { -// #[inline] -// fn derive_address(&self, key: &SpendingKey) -> Self::Address { -// let generator = self.base.group_generator.generator(); -// Address::new( -// generator.scalar_mul( -// &self -// .base -// .viewing_key_derivation_function -// .viewing_key(&generator.scalar_mul(key, &mut ()), &mut ()), -// &mut (), -// ), -// ) -// } -// } - impl utxo::Mint for Parameters where C: Configuration, @@ -1071,14 +1132,9 @@ where C: Configuration, C::AssetId: Clone + Default, C::AssetValue: Clone + Default, - C::Scalar: Sample, - encryption::Randomness: Sample, + IncomingBaseRandomness: Clone, + IncomingRandomness: Sample, UtxoCommitmentRandomness: Sample, - C::Group: Debug, - C::Scalar: Debug, - C::IncomingCiphertext: Debug, - C::LightIncomingCiphertext: Debug, - ::Randomness: Clone, { #[inline] fn derive_mint( @@ -1111,7 +1167,7 @@ where .encrypt_into( &secret.receiving_key, &secret.incoming_randomness, - C::IncomingHeader::default(), + Default::default(), &secret.plaintext, &mut (), ); @@ -1123,7 +1179,7 @@ where .encrypt_into( &secret.receiving_key, &secret.light_incoming_randomness(), - C::LightIncomingHeader::default(), + Default::default(), &secret.plaintext, &mut (), ); @@ -1158,8 +1214,8 @@ where type UtxoAccumulatorWitness = utxo::UtxoAccumulatorWitness; type UtxoAccumulatorOutput = utxo::UtxoAccumulatorOutput; type UtxoAccumulatorModel = C::UtxoAccumulatorModel; - type Secret = SpendSecret; type UtxoAccumulatorItemHash = C::UtxoAccumulatorItemHash; + type Secret = SpendSecret; #[inline] fn utxo_accumulator_item_hash(&self) -> &Self::UtxoAccumulatorItemHash { @@ -1217,7 +1273,7 @@ where C::AssetId: Clone + Default, C::AssetValue: Clone + Default, C::Scalar: Sample, - encryption::Randomness: Sample, + OutgoingBaseRandomness: Sample, { #[inline] fn derive_spend( @@ -1305,10 +1361,7 @@ where C: Configuration, C::LightIncomingBaseEncryptionScheme: Decrypt>>, - C::Group: Debug, - C::LightIncomingCiphertext: Debug, { - /// opens `note` if the note partition is consistent with the receiving key par #[inline] fn open( &self, @@ -1348,7 +1401,6 @@ where C: Configuration, C::LightIncomingBaseEncryptionScheme: Decrypt>>, - C::Group: Debug, C::LightIncomingCiphertext: Debug, Asset: Clone + Default, { @@ -1423,6 +1475,7 @@ where 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") @@ -1463,8 +1516,30 @@ where } /// 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"))] +#[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, @@ -1539,9 +1614,13 @@ where 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( @@ -1702,7 +1781,7 @@ where impl PartialEq for Utxo where - C: BaseConfiguration, + C: BaseConfiguration + ?Sized, COM: Has, { #[inline] @@ -1797,6 +1876,40 @@ where } /// 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, @@ -1862,7 +1975,7 @@ where .encrypt_into( &self.receiving_key, &self.incoming_randomness, - C::IncomingHeader::default(), + Default::default(), &self.plaintext, compiler, ) @@ -1877,7 +1990,7 @@ where compiler: &mut COM, ) -> LightIncomingNote where - ::Randomness: Clone, + IncomingBaseRandomness: Clone, { Hybrid::new( StandardDiffieHellman::new(group_generator.clone()), @@ -1886,7 +1999,7 @@ where .encrypt_into( &self.receiving_key, &self.light_incoming_randomness(), - C::LightIncomingHeader::default(), + Default::default(), &self.plaintext, compiler, ) @@ -1922,11 +2035,11 @@ where asset } - /// + /// Returns the [`LightIncomingRandomness`] associated with `self`. #[inline] fn light_incoming_randomness(&self) -> LightIncomingRandomness where - ::Randomness: Clone, + IncomingBaseRandomness: Clone, { Randomness { ephemeral_secret_key: self.incoming_randomness.ephemeral_secret_key.clone(), @@ -1935,14 +2048,6 @@ where } } -// impl utxo::IdentifierType for MintSecret -// where -// C: BaseConfiguration, -// COM: Has, -// { -// type Identifier = Identifier; -// } - impl utxo::IdentifierType for MintSecret where C: BaseConfiguration, @@ -2002,8 +2107,26 @@ where } /// 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(Debug(bound = "C::Group: Debug, C::Scalar: Debug"))] +#[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, @@ -2034,7 +2157,8 @@ where } } - /// + /// 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, @@ -2084,10 +2208,10 @@ where } } -impl core::cmp::PartialEq for AuthorizationContext +impl cmp::PartialEq for AuthorizationContext where C: BaseConfiguration, - C::Group: core::cmp::PartialEq, + C::Group: cmp::PartialEq, { #[inline] fn eq(&self, rhs: &Self) -> bool { @@ -2116,8 +2240,28 @@ where } /// 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(Debug(bound = "C::Scalar: Debug, C::Group: Debug"))] +#[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, @@ -2195,46 +2339,25 @@ where } } -// /// Identifier -// #[derive(derivative::Derivative)] -// #[derivative( -// Clone(bound = "C::Bool: Clone, UtxoCommitmentRandomness: Clone"), -// Hash(bound = "C::Bool: Hash, UtxoCommitmentRandomness: Hash") -// )] -// pub struct Identifier -// where -// C: BaseConfiguration, -// COM: Has, -// { -// /// Transparency Flag -// pub is_transparent: C::Bool, - -// /// UTXO Commitment Randomness -// pub utxo_commitment_randomness: UtxoCommitmentRandomness, -// } - -// impl Identifier -// where -// C: BaseConfiguration, -// COM: Has, -// { -// /// Builds a new [`Identifier`] from `is_transparent` and `utxo_commitment_randomness`. -// #[inline] -// pub fn new( -// is_transparent: C::Bool, -// utxo_commitment_randomness: UtxoCommitmentRandomness, -// ) -> Self { -// Self { -// is_transparent, -// utxo_commitment_randomness, -// } -// } -// } - /// 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( @@ -2279,12 +2402,36 @@ where where R: RngCore + ?Sized, { - // FIXME: Should we sample the transparency flag. - Self::new(false, rng.gen()) + 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, @@ -2350,7 +2497,7 @@ where .encrypt_into( receiving_key, &self.outgoing_randomness, - C::OutgoingHeader::default(), + Default::default(), asset, compiler, ) @@ -2474,7 +2621,9 @@ where #[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") @@ -2535,7 +2684,9 @@ where #[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") @@ -2590,7 +2741,7 @@ where C: Configuration, { #[inline] - fn eq(&self, rhs: &Self, compiler: &mut ()) -> C::Bool { + fn eq(&self, rhs: &Self, compiler: &mut ()) -> bool { self.nullifier .commitment .eq(&rhs.nullifier.commitment, compiler) @@ -2656,73 +2807,6 @@ where } } -/// Full Nullifier Decode Error -#[derive(derivative::Derivative)] -#[derivative( - Clone( - bound = " as Decode>::Error: Clone, as Decode>::Error: Clone" - ), - Copy( - bound = " as Decode>::Error: Copy, as Decode>::Error: Copy" - ), - Debug( - bound = " as Decode>::Error: Debug, as Decode>::Error: Debug" - ), - Eq( - bound = " as Decode>::Error: Eq, as Decode>::Error: Eq" - ), - Hash( - bound = " as Decode>::Error: Hash, as Decode>::Error: Hash" - ), - PartialEq( - bound = " as Decode>::Error: cmp::PartialEq, as Decode>::Error: cmp::PartialEq" - ) -)] -pub enum FullNullifierDecodeError -where - C: Configuration, - NullifierCommitment: Decode, - OutgoingNote: Decode, -{ - /// Nullifier Commitment Error - CommitmentDecodeError( as Decode>::Error), - - /// Note Error - NoteDecodeError( as Decode>::Error), -} - -impl Decode for FullNullifier -where - C: Configuration, - NullifierCommitment: Decode, - OutgoingNote: Decode, -{ - type Error = FullNullifierDecodeError; - - fn decode(mut reader: R) -> Result> - where - R: manta_util::codec::Read, - { - let nullifier_commitment = - NullifierCommitment::::decode(&mut reader).map_err(|e| match e { - DecodeError::Decode(r) => { - DecodeError::Decode(FullNullifierDecodeError::CommitmentDecodeError(r)) - } - DecodeError::Read(e) => DecodeError::Read(e), - })?; - let outgoing_note = OutgoingNote::::decode(&mut reader).map_err(|e| match e { - DecodeError::Decode(r) => { - DecodeError::Decode(FullNullifierDecodeError::NoteDecodeError(r)) - } - DecodeError::Read(e) => DecodeError::Read(e), - })?; - Ok(Self { - nullifier: Nullifier::new(nullifier_commitment), - outgoing_note, - }) - } -} - impl Input

for FullNullifier where C: Configuration, From 7d2874d5bf19ea64533deb70f2d7f7b448213826 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 24 Nov 2022 17:09:56 +0100 Subject: [PATCH 03/44] sender and receiver Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/lib.rs | 2 +- manta-accounting/src/transfer/batch.rs | 41 +- manta-accounting/src/transfer/canonical.rs | 321 ++- manta-accounting/src/transfer/mod.rs | 1982 +++++++++-------- manta-accounting/src/transfer/receiver.rs | 427 ++-- manta-accounting/src/transfer/sender.rs | 686 +++--- manta-accounting/src/transfer/test.rs | 409 ++-- .../src/transfer/utxo/protocol.rs | 1 - 8 files changed, 2154 insertions(+), 1715 deletions(-) diff --git a/manta-accounting/src/lib.rs b/manta-accounting/src/lib.rs index 9a8fe02ee..957658c26 100644 --- a/manta-accounting/src/lib.rs +++ b/manta-accounting/src/lib.rs @@ -35,7 +35,7 @@ extern crate derive_more; pub mod asset; pub mod key; pub mod transfer; -pub mod wallet; +//pub mod wallet; #[cfg(feature = "fs")] #[cfg_attr(doc_cfg, doc(cfg(feature = "fs")))] diff --git a/manta-accounting/src/transfer/batch.rs b/manta-accounting/src/transfer/batch.rs index 949192c2a..ea11d03bc 100644 --- a/manta-accounting/src/transfer/batch.rs +++ b/manta-accounting/src/transfer/batch.rs @@ -18,11 +18,14 @@ // 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 manta_crypto::{ accumulator::Accumulator, - rand::{CryptoRng, Rand, RngCore}, + rand::{CryptoRng, RngCore}, }; use manta_util::into_array_unchecked; @@ -42,12 +45,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 +59,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 +86,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..fc246ed43 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::{boxed::Box, 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()) { - Ok(TransactionKind::Withdraw(asset.clone())) + Self::ToPrivate(asset) => Ok(TransactionKind::Deposit(Box::new(asset.clone()))), + Self::PrivateTransfer(asset, _) | Self::ToPublic(asset) => { + if balance(asset) { + Ok(TransactionKind::Withdraw(Box::new(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), } } } @@ -367,8 +444,12 @@ where derive(Deserialize, Serialize), serde( bound( - deserialize = "Asset: Deserialize<'de>", - serialize = "Asset: Serialize" + deserialize = r" + Asset: Deserialize<'de>, + ", + serialize = r" + Asset: Serialize, + ", ), crate = "manta_util::serde", deny_unknown_fields @@ -377,11 +458,10 @@ where #[derive(derivative::Derivative)] #[derivative( 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 @@ -390,12 +470,12 @@ where /// Deposit Transaction /// /// A transaction of this kind will result in a deposit of `asset`. - Deposit(Asset), + Deposit(Box>), /// Withdraw Transaction /// /// A transaction of this kind will result in a withdraw of `asset`. - Withdraw(Asset), + Withdraw(Box>), } /// Transfer Asset Selection @@ -459,14 +539,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,9 +557,9 @@ 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, } } } @@ -499,14 +579,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 +597,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 +608,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 cb604b1c3..798892d7a 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,50 +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] @@ -80,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; - - /// UTXO Commitment Scheme Variable Type - type UtxoCommitmentSchemeVar: Constant - + UtxoCommitmentScheme< - Self::Compiler, - EphemeralSecretKey = SecretKeyVar, - PublicSpendKey = PublicKeyVar, - Asset = AssetVar, - Utxo = UtxoVar, - >; + /// Authorization Proof Variable Type + type AuthorizationProofVar: Variable>; - /// 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< @@ -230,618 +206,240 @@ 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, -} +/// UTXO Accumulator Witness Type +pub type UtxoAccumulatorWitness = utxo::UtxoAccumulatorWitness>; -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 Output Type +pub type UtxoAccumulatorOutput = utxo::UtxoAccumulatorOutput>; -impl<'p, C> AsRef> for FullParameters<'p, C> -where - C: Configuration, -{ - #[inline] - fn as_ref(&self) -> &Parameters { - self.base - } -} +/// Address Type +pub type Address = utxo::Address>; -impl<'p, C> Deref for FullParameters<'p, C> -where - C: Configuration, -{ - type Target = Parameters; +/// Asset Id Type +pub type AssetId = ::AssetId; - #[inline] - fn deref(&self) -> &Self::Target { - self.base - } -} +/// Asset Value Type +pub type AssetValue = ::AssetValue; -/// Transfer Full Parameters Variables -pub struct FullParametersVar<'p, C> -where - C: Configuration, -{ - /// Key Agreement Scheme - key_agreement: C::KeyAgreementSchemeVar, +/// Asset Type +pub type Asset = asset::Asset<::AssetId, ::AssetValue>; - /// UTXO Commitment Scheme - utxo_commitment: C::UtxoCommitmentSchemeVar, +/// Asset Variable Type +pub type AssetVar = + asset::Asset<::AssetIdVar, ::AssetValueVar>; - /// Void Number Commitment Scheme - void_number_commitment: C::VoidNumberCommitmentSchemeVar, +/// Associated Data Type +pub type AssociatedData = utxo::AssociatedData>; - /// UTXO Accumulator Model - utxo_accumulator_model: C::UtxoAccumulatorModelVar, +/// Spending Key Type +pub type SpendingKey = auth::SpendingKey>; - /// Type Parameter Marker - __: PhantomData<&'p ()>, -} +/// Authorization Context Type +pub type AuthorizationContext = auth::AuthorizationContext>; -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 Variable Type +pub type AuthorizationContextVar = 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 Proof Type +pub type AuthorizationProof = auth::AuthorizationProof>; - /// 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 Variable Type +pub type AuthorizationProofVar = auth::AuthorizationProof>; -impl<'p, C> Constant for FullParametersVar<'p, C> -where - C: Configuration, - Parameters: 'p, -{ - type Type = FullParameters<'p, C>; +/// Authorization Type +pub type Authorization = auth::Authorization>; - #[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 Variable Type +pub type AuthorizationVar = 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 Key Type +pub type AuthorizationKey = auth::AuthorizationKey>; - /// View Part of the Spending Key - view: SecretKey, -} +/// Authorization Signature Type +pub type AuthorizationSignature = auth::AuthorizationSignature>; -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 } - } +/// Unspent Transaction Output Type +pub type Utxo = utxo::Utxo>; - /// 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 ()), - } - } +/// Base Note Type +pub type BaseNote = Var<::NoteVar, Public, ::Compiler>; - /// 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) - } +/// Note Type +pub type Note = utxo::Note>; - /// 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) - } +/// Nullifier Type +pub type Nullifier = utxo::Nullifier>; - /// 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) - } +/// Identifier Type +pub type Identifier = utxo::Identifier>; - /// 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) - } +/// Identified Asset Type +pub type IdentifiedAsset = utxo::IdentifiedAsset>; - /// 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)) - } -} +/// Pre-Sender Type +pub type PreSender = sender::PreSender>; -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), - ) - } -} +/// Sender Type +pub type Sender = sender::Sender>; -/// 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 Variable Type +pub type SenderVar = sender::Sender, Compiler>; - /// View Part of the Receiving Key - pub view: PublicKey, -} +/// Sender Post Type +pub type SenderPost = sender::SenderPost>; -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, - ) - } -} +/// Receiver Type +pub type Receiver = receiver::Receiver>; -/// 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 Variable Type +pub type ReceiverVar = receiver::Receiver, Compiler>; - /// Asset - pub asset: Asset, -} +/// Receiver Post Type +pub type ReceiverPost = receiver::ReceiverPost>; -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, - } - } -} - -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 @@ -854,6 +452,9 @@ pub struct Transfer< > where C: Configuration, { + /// Authorization + authorization: Option>, + /// Asset Id asset_id: Option, @@ -875,53 +476,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, @@ -931,7 +513,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, @@ -939,9 +521,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, @@ -959,6 +552,7 @@ where /// output sides. #[inline] fn new_unchecked( + authorization: Option>, asset_id: Option, sources: [C::AssetValue; SOURCES], senders: [Sender; SENDERS], @@ -966,6 +560,7 @@ where sinks: [C::AssetValue; SINKS], ) -> Self { Self { + authorization, asset_id, sources, senders, @@ -974,9 +569,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); @@ -985,7 +610,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); @@ -997,7 +622,7 @@ where #[inline] pub fn generate_context( public_parameters: &ProofSystemPublicParameters, - parameters: FullParameters, + parameters: FullParametersRef, rng: &mut R, ) -> Result<(ProvingContext, VerifyingContext), ProofSystemError> where @@ -1010,33 +635,116 @@ 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. #[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)); } } @@ -1050,6 +758,9 @@ struct TransferVar< > where C: Configuration, { + /// Authorization + authorization: Option>, + /// Asset Id asset_id: Option, @@ -1079,28 +790,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); @@ -1110,6 +812,63 @@ 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 @@ -1130,9 +889,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() @@ -1159,30 +947,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 @@ -1192,11 +956,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; @@ -1237,16 +1012,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 @@ -1255,21 +1025,16 @@ 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. @@ -1281,11 +1046,11 @@ where /// [`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>; } @@ -1295,9 +1060,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 @@ -1307,10 +1096,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, @@ -1331,10 +1128,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, @@ -1348,45 +1153,38 @@ where /// Transfer Post Error /// -/// This `enum` is the error state of the [`TransferPost::validate`] method. See its documentation -/// for more. -#[cfg_attr( - feature = "serde", - derive(Deserialize, Serialize), - serde( - bound( - deserialize = "InvalidSourceAccount: Deserialize<'de>, InvalidSinkAccount: Deserialize<'de>, UpdateError: Deserialize<'de>", - serialize = "InvalidSourceAccount: Serialize, InvalidSinkAccount: Serialize, UpdateError: Serialize" - ), - crate = "manta_util::serde", - deny_unknown_fields - ) +/// This `enum` is the error state of the [`TransferPost::validate`] method. See its documentation +/// for more. +/* TODO: +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) )] +*/ #[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), @@ -1402,8 +1200,8 @@ where /// Duplicate Spend Error DuplicateSpend, - /// Duplicate Register Error - DuplicateRegister, + /// Duplicate Mint Error + DuplicateMint, /// Invalid Transfer Proof Error /// @@ -1416,10 +1214,21 @@ where UpdateError(UpdateError), } +impl From + for TransferPostError +where + C: Configuration + ?Sized, +{ + #[inline] + fn from(err: InvalidAuthorizationSignature) -> Self { + Self::InvalidAuthorizationSignature(err) + } +} + impl From> for TransferPostError where - C: Configuration, + C: Configuration + ?Sized, { #[inline] fn from(err: InvalidSourceAccount) -> Self { @@ -1430,7 +1239,7 @@ where impl From> for TransferPostError where - C: Configuration, + C: Configuration + ?Sized, { #[inline] fn from(err: InvalidSinkAccount) -> Self { @@ -1438,44 +1247,44 @@ where } } -impl From +impl From for TransferPostError where - C: Configuration, + C: Configuration + ?Sized, { #[inline] - fn from(err: SenderPostError) -> Self { + fn from(err: sender::SenderPostError) -> Self { Self::Sender(err) } } -impl From +impl From for TransferPostError where - C: Configuration, + C: Configuration + ?Sized, { #[inline] - fn from(err: ReceiverPostError) -> Self { + fn from(err: receiver::ReceiverPostError) -> Self { Self::Receiver(err) } } -/// Transfer Post +/// Transfer Post Body #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), serde( bound( deserialize = r" - C::AssetId: Deserialize<'de>, - C::AssetValue: Deserialize<'de>, + 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, + C::AssetId: Serialize, + C::AssetValue: Serialize, SenderPost: Serialize, ReceiverPost: Serialize, Proof: Serialize, @@ -1487,25 +1296,45 @@ 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 = 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 TransferPost +pub struct TransferPostBody where - C: Configuration, + C: Configuration + ?Sized, { /// Asset Id pub asset_id: Option, @@ -1522,33 +1351,185 @@ where /// Sinks pub sinks: Vec, - /// Validity Proof - pub validity_proof: Proof, + /// Proof + pub proof: Proof, } -impl TransferPost +impl TransferPostBody where - C: Configuration, + C: Configuration + ?Sized, { - /// Generates the public input for the [`Transfer`] validation proof. + /// Builds a new [`TransferPostBody`]. #[inline] - pub fn generate_proof_input(&self) -> ProofInput { - let mut input = Default::default(); + 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, + } + } + + /// 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)) + } +} + +impl Encode for TransferPostBody +where + C: Configuration + ?Sized, + C::AssetId: Encode, + C::AssetValue: Encode, + SenderPost: Encode, + ReceiverPost: Encode, + Proof: Encode, +{ + #[inline] + 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 Input for TransferPostBody +where + C: Configuration + ?Sized, +{ + #[inline] + fn extend(&self, input: &mut ProofInput) { if let Some(asset_id) = &self.asset_id { - C::ProofSystem::extend(&mut input, asset_id); + C::ProofSystem::extend(input, asset_id); } self.sources .iter() - .for_each(|source| C::ProofSystem::extend(&mut input, source)); + .for_each(|source| C::ProofSystem::extend(input, source)); self.sender_posts .iter() - .for_each(|post| post.extend_input(&mut input)); + .for_each(|post| C::ProofSystem::extend(input, post)); self.receiver_posts .iter() - .for_each(|post| post.extend_input(&mut input)); + .for_each(|post| C::ProofSystem::extend(input, post)); self.sinks .iter() - .for_each(|sink| C::ProofSystem::extend(&mut input, sink)); + .for_each(|sink| C::ProofSystem::extend(input, sink)); + } +} + +/// Transfer Post +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = r" + AuthorizationSignature: Deserialize<'de>, + TransferPostBody: Deserialize<'de>, + ", + serialize = r" + AuthorizationSignature: Serialize, + TransferPostBody: Serialize, + ", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + 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 + ?Sized, +{ + /// Authorization Signature + pub authorization_signature: Option>, + + /// Transfer Post Body + pub body: TransferPostBody, +} + +impl TransferPost +where + 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(); + self.extend(&mut input); input } @@ -1561,16 +1542,56 @@ 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, @@ -1596,7 +1617,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 { @@ -1604,7 +1625,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 { @@ -1614,73 +1635,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, }) } @@ -1690,24 +1704,55 @@ 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 pub struct TransferPostingKey where - C: Configuration, + C: Configuration + ?Sized, L: TransferLedger, { /// Asset Id @@ -1725,8 +1770,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, @@ -1734,37 +1779,9 @@ 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 @@ -1775,21 +1792,104 @@ 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 +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..767620e23 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,19 +199,16 @@ 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 /// @@ -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 /// - /// 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..02f13620d 100644 --- a/manta-accounting/src/transfer/sender.rs +++ b/manta-accounting/src/transfer/sender.rs @@ -16,128 +16,179 @@ //! 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 = "S::Secret: Deserialize<'de>, S::Utxo: Deserialize<'de>, S::Nullifier: Deserialize<'de>", + serialize = "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 +199,216 @@ 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 = "UtxoMembershipProof: Deserialize<'de>", + serialize = "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 = "S::Secret: Deserialize<'de>, + S::Utxo: Deserialize<'de>, + UtxoMembershipProof: Deserialize<'de>, + S::Nullifier: Deserialize<'de>", + serialize = "S::Secret: Serialize, + S::Utxo: Serialize, + UtxoMembershipProof: Serialize, + S::Nullifier: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone( + bound = "S::Secret: Clone, S::Utxo: Clone, UtxoMembershipProof: Clone, S::Nullifier: Clone" + ), + Copy( + bound = "S::Secret: Copy, S::Utxo: Copy, UtxoMembershipProof: Copy, S::Nullifier: Copy" + ), + Debug( + bound = "S::Secret: Debug,S::Utxo: Debug, UtxoMembershipProof: Debug, S::Nullifier: Debug" + ), + Default( + bound = "S::Secret: Default,S::Utxo: Default, UtxoMembershipProof: Default, S::Nullifier: Default" + ), + Eq(bound = "S::Secret: Eq,S::Utxo: Eq, UtxoMembershipProof: Eq, S::Nullifier: Eq"), + Hash( + bound = "S::Secret: Hash,S::Utxo: Hash, UtxoMembershipProof: Hash, S::Nullifier: Hash" + ), + PartialEq( + bound = "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 +417,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 +467,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 /// - /// 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 /// - /// 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 +508,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 +530,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 +556,8 @@ pub enum SenderPostError { derive(Deserialize, Serialize), serde( bound( - deserialize = "UtxoAccumulatorOutput: Deserialize<'de>, VoidNumber: Deserialize<'de>", - serialize = "UtxoAccumulatorOutput: Serialize, VoidNumber: Serialize", + deserialize = "UtxoAccumulatorOutput: Deserialize<'de>, S::Nullifier: Deserialize<'de>", + serialize = "UtxoAccumulatorOutput: Serialize, S::Nullifier: Serialize", ), crate = "manta_util::serde", deny_unknown_fields @@ -460,93 +565,152 @@ 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 = "L::ValidUtxoAccumulatorOutput: Deserialize<'de>, L::ValidNullifier: Deserialize<'de>", + serialize = "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..56e7374c7 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,34 @@ use core::{ }; use manta_crypto::{ accumulator::Accumulator, - constraint::ProofSystem, rand::{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 +71,77 @@ 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>, { + /// + #[inline] + pub fn new( + parameters: &'p Parameters, + utxo_accumulator: &'p mut A, + authorization: Option>, + ) -> Self { + Self { + parameters, + utxo_accumulator, + authorization, + } + } + + /// #[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 +158,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 +183,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 +222,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 +247,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 +265,7 @@ where &verifying_context, parameters, utxo_accumulator, + spending_key, rng, ) } @@ -255,116 +278,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("") + .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!(""), + }; + ( + 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 +395,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 +447,75 @@ 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`] -/// 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`. -#[inline] -pub fn assert_valid_proof(verifying_context: &VerifyingContext, post: &TransferPost) -where - C: Configuration, - ::Error: Debug, - TransferPost: Debug, -{ - assert!( - C::ProofSystem::verify( - verifying_context, - &post.generate_proof_input(), - &post.validity_proof, - ) - .expect("Unable to verify proof."), - "Invalid proof: {:?}.", - post, - ); -} diff --git a/manta-accounting/src/transfer/utxo/protocol.rs b/manta-accounting/src/transfer/utxo/protocol.rs index e79014c98..62a9ca6c8 100644 --- a/manta-accounting/src/transfer/utxo/protocol.rs +++ b/manta-accounting/src/transfer/utxo/protocol.rs @@ -1401,7 +1401,6 @@ where C: Configuration, C::LightIncomingBaseEncryptionScheme: Decrypt>>, - C::LightIncomingCiphertext: Debug, Asset: Clone + Default, { #[inline] From 5896feb61e6e9a07838ddbd859727e0ffb9a16d9 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 24 Nov 2022 18:49:58 +0100 Subject: [PATCH 04/44] transfer mod finished Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/transfer/batch.rs | 25 +++ manta-accounting/src/transfer/canonical.rs | 53 +++++- manta-accounting/src/transfer/mod.rs | 209 ++++++++++++++++++++- manta-accounting/src/transfer/receiver.rs | 4 +- manta-accounting/src/transfer/sender.rs | 106 +++++++---- manta-accounting/src/transfer/test.rs | 9 +- manta-crypto/src/merkle_tree/fork.rs | 4 +- manta-crypto/src/merkle_tree/path.rs | 8 +- 8 files changed, 358 insertions(+), 60 deletions(-) diff --git a/manta-accounting/src/transfer/batch.rs b/manta-accounting/src/transfer/batch.rs index ea11d03bc..b3a4f2169 100644 --- a/manta-accounting/src/transfer/batch.rs +++ b/manta-accounting/src/transfer/batch.rs @@ -23,13 +23,38 @@ use crate::transfer::{ Parameters, PreSender, Receiver, UtxoAccumulatorItem, UtxoAccumulatorModel, }; use alloc::vec::Vec; +use core::{fmt::Debug, hash::Hash}; use manta_crypto::{ accumulator::Accumulator, 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, diff --git a/manta-accounting/src/transfer/canonical.rs b/manta-accounting/src/transfer/canonical.rs index fc246ed43..d122de4e7 100644 --- a/manta-accounting/src/transfer/canonical.rs +++ b/manta-accounting/src/transfer/canonical.rs @@ -444,12 +444,8 @@ where derive(Deserialize, Serialize), serde( bound( - deserialize = r" - Asset: Deserialize<'de>, - ", - serialize = r" - Asset: Serialize, - ", + deserialize = "Asset: Deserialize<'de>", + serialize = "Asset: Serialize", ), crate = "manta_util::serde", deny_unknown_fields @@ -479,6 +475,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, @@ -525,6 +542,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"), @@ -565,6 +594,18 @@ where } /// 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"), diff --git a/manta-accounting/src/transfer/mod.rs b/manta-accounting/src/transfer/mod.rs index 798892d7a..e4994efd6 100644 --- a/manta-accounting/src/transfer/mod.rs +++ b/manta-accounting/src/transfer/mod.rs @@ -443,6 +443,45 @@ where } /// 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, @@ -749,6 +788,39 @@ where } /// 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, @@ -875,7 +947,6 @@ where 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() @@ -1039,7 +1110,7 @@ where /// 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 @@ -1155,13 +1226,26 @@ where /// /// This `enum` is the error state of the [`TransferPost::validate`] method. See its documentation /// for more. -/* TODO: #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), - serde(crate = "manta_util::serde", deny_unknown_fields) + serde( + bound( + 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 + ) )] -*/ #[derive(derivative::Derivative)] #[derivative( Clone(bound = "AccountId: Clone, UpdateError: Clone, C::AssetId: Clone, C::AssetValue: Clone"), @@ -1750,6 +1834,84 @@ where } /// 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 + ?Sized, @@ -1784,7 +1946,7 @@ where { /// 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 @@ -1816,6 +1978,41 @@ where } /// 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, diff --git a/manta-accounting/src/transfer/receiver.rs b/manta-accounting/src/transfer/receiver.rs index 767620e23..44f85c65d 100644 --- a/manta-accounting/src/transfer/receiver.rs +++ b/manta-accounting/src/transfer/receiver.rs @@ -210,7 +210,7 @@ where /// 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. @@ -235,7 +235,7 @@ where /// 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 [`Note`] are not /// already stored on the ledger. See [`is_not_registered`](Self::is_not_registered) for more. diff --git a/manta-accounting/src/transfer/sender.rs b/manta-accounting/src/transfer/sender.rs index 02f13620d..44a04ff44 100644 --- a/manta-accounting/src/transfer/sender.rs +++ b/manta-accounting/src/transfer/sender.rs @@ -40,8 +40,14 @@ use manta_util::serde::{Deserialize, Serialize}; derive(Deserialize, Serialize), serde( bound( - deserialize = "S::Secret: Deserialize<'de>, S::Utxo: Deserialize<'de>, S::Nullifier: Deserialize<'de>", - serialize = "S::Secret: Serialize, S::Utxo: Serialize, S::Nullifier: Serialize", + 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 @@ -204,8 +210,8 @@ where derive(Deserialize, Serialize), serde( bound( - deserialize = "UtxoMembershipProof: Deserialize<'de>", - serialize = "UtxoMembershipProof: Serialize", + deserialize = r"UtxoMembershipProof: Deserialize<'de>", + serialize = r"UtxoMembershipProof: Serialize", ), crate = "manta_util::serde", deny_unknown_fields @@ -246,14 +252,16 @@ where derive(Deserialize, Serialize), serde( bound( - deserialize = "S::Secret: Deserialize<'de>, - S::Utxo: Deserialize<'de>, - UtxoMembershipProof: Deserialize<'de>, - S::Nullifier: Deserialize<'de>", - serialize = "S::Secret: Serialize, - S::Utxo: Serialize, - UtxoMembershipProof: Serialize, - S::Nullifier: Serialize", + 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 @@ -261,25 +269,41 @@ where )] #[derive(derivative::Derivative)] #[derivative( - Clone( - bound = "S::Secret: Clone, S::Utxo: Clone, UtxoMembershipProof: Clone, S::Nullifier: Clone" - ), - Copy( - bound = "S::Secret: Copy, S::Utxo: Copy, UtxoMembershipProof: Copy, S::Nullifier: Copy" - ), - Debug( - bound = "S::Secret: Debug,S::Utxo: Debug, UtxoMembershipProof: Debug, S::Nullifier: Debug" - ), - Default( - bound = "S::Secret: Default,S::Utxo: Default, UtxoMembershipProof: Default, S::Nullifier: Default" - ), - Eq(bound = "S::Secret: Eq,S::Utxo: Eq, UtxoMembershipProof: Eq, S::Nullifier: Eq"), - Hash( - bound = "S::Secret: Hash,S::Utxo: Hash, UtxoMembershipProof: Hash, S::Nullifier: Hash" - ), - PartialEq( - bound = "S::Secret: PartialEq,S::Utxo: PartialEq, UtxoMembershipProof: PartialEq, S::Nullifier: PartialEq" - ) + 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 @@ -472,7 +496,7 @@ where /// Posts the `nullifier` to the ledger, spending the asset. /// - /// # Safety + /// # Crypto Safety /// /// 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. @@ -497,7 +521,7 @@ where /// 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 [`Nullifier`]s are not already /// stored on the ledger. See [`is_unspent`](Self::is_unspent) for more. @@ -556,8 +580,12 @@ pub enum SenderPostError { derive(Deserialize, Serialize), serde( bound( - deserialize = "UtxoAccumulatorOutput: Deserialize<'de>, S::Nullifier: Deserialize<'de>", - serialize = "UtxoAccumulatorOutput: Serialize, S::Nullifier: Serialize", + deserialize = r" + UtxoAccumulatorOutput: Deserialize<'de>, + S::Nullifier: Deserialize<'de>", + serialize = r" + UtxoAccumulatorOutput: Serialize, + S::Nullifier: Serialize", ), crate = "manta_util::serde", deny_unknown_fields @@ -648,8 +676,12 @@ where derive(Deserialize, Serialize), serde( bound( - deserialize = "L::ValidUtxoAccumulatorOutput: Deserialize<'de>, L::ValidNullifier: Deserialize<'de>", - serialize = "L::ValidUtxoAccumulatorOutput: Serialize, L::ValidNullifier: Serialize", + 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 diff --git a/manta-accounting/src/transfer/test.rs b/manta-accounting/src/transfer/test.rs index 56e7374c7..70e5bc2c5 100644 --- a/manta-accounting/src/transfer/test.rs +++ b/manta-accounting/src/transfer/test.rs @@ -101,7 +101,8 @@ where C: Configuration, A: Accumulator, Model = UtxoAccumulatorModel>, { - /// + /// Builds a new [`TransferDistribution`] from `parameters`, `utxo_accumulator` + /// and `authorization`. #[inline] pub fn new( parameters: &'p Parameters, @@ -115,7 +116,8 @@ where } } - /// + /// Builds a new [`TransferDistribution`] from `parameters`, `utxo_accumulator` + /// and `spending_key`. #[inline] pub fn from_spending_key( parameters: &'p Parameters, @@ -489,7 +491,8 @@ where } } -/// +/// Samples a [`ToPrivate`] transfers and returns the corresponding [`TransferPost`] +/// and [`PreSender`]. #[inline] pub fn sample_to_private( parameters: FullParametersRef, 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] From d762ad63f3ac472ca82435b2da1bb190b185138a Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 25 Nov 2022 15:49:15 +0100 Subject: [PATCH 05/44] wallet done Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/lib.rs | 2 +- manta-accounting/src/wallet/balance.rs | 189 ++++--- manta-accounting/src/wallet/ledger.rs | 5 +- manta-accounting/src/wallet/mod.rs | 82 ++- manta-accounting/src/wallet/signer.rs | 707 ++++++++++++++---------- manta-accounting/src/wallet/test/mod.rs | 529 ++++++++++-------- manta-accounting/src/wallet/test/sim.rs | 43 +- 7 files changed, 896 insertions(+), 661 deletions(-) diff --git a/manta-accounting/src/lib.rs b/manta-accounting/src/lib.rs index 957658c26..9a8fe02ee 100644 --- a/manta-accounting/src/lib.rs +++ b/manta-accounting/src/lib.rs @@ -35,7 +35,7 @@ extern crate derive_more; pub mod asset; pub mod key; pub mod transfer; -//pub mod wallet; +pub mod wallet; #[cfg(feature = "fs")] #[cfg_attr(doc_cfg, doc(cfg(feature = "fs")))] diff --git a/manta-accounting/src/wallet/balance.rs b/manta-accounting/src/wallet/balance.rs index 999d41236..f4684114a 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,16 +276,12 @@ 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. - +} +/* TODO: move these to manta-pay /// Defines the tests across multiple different [`BalanceState`] types. macro_rules! define_tests { ($(( $type:ty, - $config: ty, $doc:expr, $valid_withdraw:ident, $full_withdraw:ident @@ -282,21 +295,19 @@ pub mod test { let mut state = <$type>::default(); let mut rng = OsRng; for _ in 0..0xFFFF { - assert_valid_withdraw::<$config, _, _>(&mut state, &mut rng); + 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::<$config, $type, _>(&mut OsRng); + assert_full_withdraw_should_remove_entry::<_, _, $type, _>(&mut OsRng); } )* } } - define_tests!( ( AssetList, @@ -311,20 +322,16 @@ pub mod test { 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..919373710 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, + pub signer: S, // This has been made public because of compatibility issues with sdk. /// 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. @@ -137,26 +163,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 +222,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 +268,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 +286,6 @@ where .await .map_err(Error::LedgerConnectionError)?; self.signer_sync(SyncRequest { - with_recovery, origin_checkpoint: self.checkpoint.clone(), data, }) @@ -312,13 +339,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)) + pub 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 +411,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 +464,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..124f4e57e 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 addresses according to the `request`. + 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"), @@ -523,13 +563,13 @@ where serde( bound( deserialize = r" - AccountTable: Deserialize<'de>, + AccountTable: Deserialize<'de>, C::UtxoAccumulator: Deserialize<'de>, C::AssetMap: Deserialize<'de>, C::Checkpoint: Deserialize<'de> ", serialize = r" - AccountTable: Serialize, + AccountTable: Serialize, C::UtxoAccumulator: Serialize, C::AssetMap: Serialize, C::Checkpoint: Serialize @@ -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,119 @@ where ) } - /// Returns the [`AccountTable`] for `self.` + /// 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] - pub fn accounts(&self) -> &AccountTable { - &self.accounts + fn default_address(&mut self, parameters: &C::Parameters) -> Address { + self.accounts.get_default().address(parameters) } - /// 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)] + /// Hashes `utxo` using the [`UtxoAccumulatorItemHash`](transfer::Configuration::UtxoAccumulatorItemHash) + /// in the transfer [`Configuration`](transfer::Configuration). #[inline] - fn insert_next_item( + 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( + 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 +807,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 +871,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 +912,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 +972,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 +989,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 +1052,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 +1064,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 +1113,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 +1158,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 +1182,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 +1226,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 +1245,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 +1299,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 +1324,11 @@ where result } - /// Returns public receiving keys. + /// Returns address according to the `request`. #[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 +1359,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..52794c74b 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`](L::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 From 19c49ea566afa6fce1117bfe34b42ced07d85ae5 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 25 Nov 2022 17:28:21 +0100 Subject: [PATCH 06/44] mantapay config Signed-off-by: Francisco Hernandez Iglesias --- manta-crypto/Cargo.toml | 4 +- manta-crypto/src/arkworks/constraint/mod.rs | 94 +- manta-parameters/src/lib.rs | 2 +- manta-pay/Cargo.toml | 54 +- manta-pay/src/config.rs | 1103 -------- manta-pay/src/config/mod.rs | 229 ++ manta-pay/src/config/poseidon.rs | 125 + manta-pay/src/config/utxo.rs | 2483 +++++++++++++++++++ manta-pay/src/config/utxo_utilities.rs | 69 + manta-pay/src/lib.rs | 18 +- 10 files changed, 3045 insertions(+), 1136 deletions(-) delete mode 100644 manta-pay/src/config.rs create mode 100644 manta-pay/src/config/mod.rs create mode 100644 manta-pay/src/config/poseidon.rs create mode 100644 manta-pay/src/config/utxo.rs create mode 100644 manta-pay/src/config/utxo_utilities.rs diff --git a/manta-crypto/Cargo.toml b/manta-crypto/Cargo.toml index 9a455aa7c..270e310bc 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,6 +88,7 @@ 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 } diff --git a/manta-crypto/src/arkworks/constraint/mod.rs b/manta-crypto/src/arkworks/constraint/mod.rs index 0b643eecf..c568a9a1a 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,90 @@ 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(""), + FpVar::new_witness( + self.cs(), + full(F::from_le_bytes_mod_order(&remainder.to_bytes_le())), + ) + .expect(""), + ) + } + _ => ( + FpVar::new_witness(self.cs(), empty::).expect(""), + FpVar::new_witness(self.cs(), empty::).expect(""), + ), + }; + let modulus = FpVar::Constant(F::from_le_bytes_mod_order( + &::MODULUS.to_bytes_le(), + )); + self.enforce_equal(&(quotient * &modulus + &remainder)) + .expect(""); + remainder + .enforce_cmp(&modulus, core::cmp::Ordering::Less, false) + .expect(""); + 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 { diff --git a/manta-parameters/src/lib.rs b/manta-parameters/src/lib.rs index 95327b2da..612a6b7b1 100644 --- a/manta-parameters/src/lib.rs +++ b/manta-parameters/src/lib.rs @@ -73,7 +73,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] diff --git a/manta-pay/Cargo.toml b/manta-pay/Cargo.toml index 635fe58a1..9838e90f2 100644 --- a/manta-pay/Cargo.toml +++ b/manta-pay/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "manta-pay" -version = "0.5.7" +version = "0.5.6" edition = "2021" authors = ["Manta Network "] readme = "README.md" @@ -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" @@ -39,26 +35,40 @@ required-features = ["clap", "groth16", "simulation"] [features] # Enable Arkworks Backend arkworks = [ + "ark-std", "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", + "num-integer", ] # Enable Download Parameters download = ["manta-parameters/download", "std"] +# Enable Groth16 ZKP System +groth16 = ["ark-groth16", "ark-snark", "arkworks"] + +# Enable HTTP Signer Client +http = ["manta-util/reqwest", "serde"] + # 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 +88,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,11 +111,11 @@ websocket = [ "ws_stream_wasm", ] -# Enable Multiple Network Support -network = [] - [dependencies] aes-gcm = { version = "0.9.4", default-features = false, features = ["aes", "alloc"] } +ark-groth16 = { version = "0.3.0", optional = true, default-features = false } +ark-snark = { version = "0.3.0", optional = true, default-features = false } +ark-std = { version = "0.3.0", optional = true, default-features = false } bip0039 = { version = "0.10.1", optional = true, default-features = false } bip32 = { version = "0.3.0", optional = true, default-features = false, features = ["bip39", "secp256k1"] } blake2 = { version = "0.10.4", default-features = false } @@ -114,15 +128,17 @@ 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 } +num-integer = { version = "0.1.45", 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"] } \ No newline at end of file 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..8f62376e2 --- /dev/null +++ b/manta-pay/src/config/mod.rs @@ -0,0 +1,229 @@ +// 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; +use manta_accounting::transfer; +use manta_crypto::arkworks::{ + algebra::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 = ecc::arkworks::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 = ecc::arkworks::Group; + +/// Embedded Group Variable Type +pub type GroupVar = ecc::arkworks::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..5b91aec65 --- /dev/null +++ b/manta-pay/src/config/poseidon.rs @@ -0,0 +1,125 @@ +// 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; // FIXME + const PARTIAL_ROUNDS: usize = 55; // FIXME +} + +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; // FIXME: + const PARTIAL_ROUNDS: usize = 56; // FIXME +} + +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::{ + constraint::arkworks::Fp, + poseidon::{ + encryption::{BlockArray, FixedDuplexer, PlaintextBlock}, + Constants, + }, + }, + }; + use alloc::boxed::Box; + use manta_crypto::{ + 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 randomness = (); + let header = vec![]; + let ciphertext = duplexer.encrypt(&key, &randomness, &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..35dc6d053 --- /dev/null +++ b/manta-pay/src/config/utxo.rs @@ -0,0 +1,2483 @@ +// 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, + }, + utxo_utilities, 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}, + ec::ProjectiveCurve, + ff::{try_into_u128, PrimeField}, + r1cs_std::R1CSVar, + 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 +#[derive(Clone, Debug)] +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 +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"), + Debug(bound = "ViewingKeyDerivationFunctionType: Debug") +)] +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(Default)] +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, Default, Debug)] +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, Default)] +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(source: &Self::EncryptionKey, _: &mut Compiler) -> Self::TargetEncryptionKey { + let mut key = [0u8; 32]; + source + .0 + .value() + .expect("Getting the group element from GroupVar is not allowed to fail.") + .into_affine() + .serialize(&mut key[..]) + .expect("Serialization error"); + let mut hasher = Blake2s256::new(); + Digest::update(&mut hasher, key); + hasher.finalize().into() + } +} + +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(source: &Self::DecryptionKey, _: &mut Compiler) -> Self::TargetDecryptionKey { + let mut key = [0u8; 32]; + source + .0 + .value() + .expect("Getting the group element from GroupVar is not allowed to fail.") + .into_affine() + .serialize(&mut key[..]) + .expect("Serialization error"); + let mut hasher = Blake2s256::new(); + Digest::update(&mut hasher, key); + hasher.finalize().into() + } +} + +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(source: &Self::Plaintext, _: &mut Compiler) -> Self::TargetPlaintext { + let mut target_plaintext = Vec::::with_capacity(AES_PLAINTEXT_SIZE); + target_plaintext.extend( + utxo_utilities::bytes_from_gadget(&source.utxo_commitment_randomness) + .expect("Converting FpVar into bytes is not allowed to fail."), + ); + target_plaintext.extend( + utxo_utilities::bytes_from_gadget(&source.asset.id) + .expect("Converting FpVar into bytes is not allowed to fail."), + ); + target_plaintext.extend(utxo_utilities::from_little_endian( + utxo_utilities::bytes_from_unsigned(&source.asset.value) + .expect("Converting U128 into bytes is not allowed to fail."), + 16, + )); + 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::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"), + Debug(bound = "UtxoAccumulatorItemHashType: Debug") +)] +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, Debug)] +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"), + Debug(bound = "NullifierCommitmentSchemeType: Debug"), + Eq(bound = "NullifierCommitmentSchemeType: Eq"), + 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, Default, Debug)] +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, Default)] +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(source: &Self::EncryptionKey, _: &mut Compiler) -> Self::TargetEncryptionKey { + let mut key = [0u8; 32]; + source + .0 + .value() + .expect("Getting the group element from GroupVar is not allowed to fail.") + .into_affine() + .serialize(&mut key[..]) + .expect("Serialization error"); + let mut hasher = Blake2s256::new(); + Digest::update(&mut hasher, key); + hasher.finalize().into() + } +} + +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(source: &Self::DecryptionKey, _: &mut Compiler) -> Self::TargetDecryptionKey { + let mut key = [0u8; 32]; + source + .0 + .value() + .expect("Getting the group element from GroupVar is not allowed to fail.") + .into_affine() + .serialize(&mut key[..]) + .expect("Serialization error"); + let mut hasher = Blake2s256::new(); + Digest::update(&mut hasher, key); + hasher.finalize().into() + } +} + +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(source: &Self::Plaintext, _: &mut Compiler) -> Self::TargetPlaintext { + let mut target_plaintext = Vec::::with_capacity(OUT_AES_PLAINTEXT_SIZE); + target_plaintext.extend( + utxo_utilities::bytes_from_gadget(&source.id) + .expect("Converting FpVar into bytes is not allowed to fail."), + ); + target_plaintext.extend(utxo_utilities::from_little_endian( + utxo_utilities::bytes_from_unsigned(&source.value) + .expect("Converting U128 into bytes is not allowed to fail."), + 16, + )); + 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::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"domain tag"); // FIXME: Use specific domain tag + 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 {} + +#[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, + ) + } +} + +/// Test +#[cfg(test)] +pub mod test { + use crate::{ + config::{ + utxo::v3::{ + Config, IncomingAESConverter, IncomingBaseAES, IncomingBaseEncryptionScheme, + OutgoingBaseAES, AES_CIPHERTEXT_SIZE, AES_PLAINTEXT_SIZE, OUT_AES_CIPHERTEXT_SIZE, + OUT_AES_PLAINTEXT_SIZE, + }, + Compiler, ConstraintField, EmbeddedScalar, Group, GroupVar, + }, + crypto::constraint::arkworks::Fp, + }; + use manta_accounting::{ + asset, + transfer::utxo::{ + address_from_spending_key, v3 as protocol, v3::Visibility, UtxoReconstruct, + }, + }; + use manta_crypto::{ + algebra::{HasGenerator, ScalarMul}, + arkworks::constraint::FpVar, + eclair::{ + alloc::{mode::Secret, Allocate}, + num::U128, + }, + encryption::{ + convert::{ + key::Encryption, + plaintext::{Forward, Reverse}, + }, + Decrypt, EmptyHeader, Encrypt, + }, + rand::{OsRng, Sample}, + }; + use protocol::{ + AddressPartitionFunction as AddressPartitionFunctionTrait, + UtxoCommitmentScheme as UtxoCommitmentSchemeTrait, + ViewingKeyDerivationFunction as ViewingKeyDerivationFunctionTrait, + }; + + use super::OutgoingAESConverter; + + /// Checks that the length of the fixed-nonce AES plaintext is 80. Checks that converting back returns + /// what we started with. + #[test] + fn check_plaintext_conversion() { + let mut rng = OsRng; + let utxo_commitment_randomness = Fp::::gen(&mut rng); + let asset_id = Fp::::gen(&mut rng); + let asset_value = u128::gen(&mut rng); + let source_plaintext = protocol::IncomingPlaintext::::new( + utxo_commitment_randomness, + asset::Asset { + id: asset_id, + value: asset_value, + }, + ); + let final_array = ::as_target(&source_plaintext, &mut ()); + assert_eq!( + AES_PLAINTEXT_SIZE, + final_array.len(), + "Length doesn't match, should be {} but is {}", + AES_PLAINTEXT_SIZE, + final_array.len() + ); + let new_source = IncomingAESConverter::into_source(Some(final_array), &mut ()) + .expect("Converting back returns None."); + let new_randomness = new_source.utxo_commitment_randomness; + let (new_asset_id, new_asset_value) = (new_source.asset.id, new_source.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."); + } + + /// Same but w.r.t. compiler + #[test] + fn check_plaintext_conversion_r1cs() { + let mut cs = Compiler::for_proofs(); + let mut rng = OsRng; + let base_utxo = Fp::::gen(&mut rng); + let utxo_commitment_randomness = + base_utxo.as_known::>(&mut cs); + let base_asset_id = Fp::::gen(&mut rng); + let asset_id = base_asset_id.as_known::>(&mut cs); + let base_asset_value = u128::gen(&mut rng); + let asset_value = + base_asset_value.as_known::>>(&mut cs); + let source_plaintext = protocol::IncomingPlaintext::, Compiler>::new( + utxo_commitment_randomness, + asset::Asset { + id: asset_id, + value: asset_value, + }, + ); + let final_array = as Forward>::as_target( + &source_plaintext, + &mut cs, + ); + assert_eq!( + AES_PLAINTEXT_SIZE, + final_array.len(), + "Length doesn't match, should be {} but is {}", + AES_PLAINTEXT_SIZE, + final_array.len() + ); + } + + /// Checks the encryption key conversion is properly executed. + #[test] + fn check_encryption_key_conversion() { + let mut rng = OsRng; + let group_element = Group::gen(&mut rng); + ::as_target(&group_element, &mut ()); + } + + /// Checks the encryption key conversion is properly executed for Compiler. + #[test] + fn check_encryption_key_conversion_r1cs() { + let mut cs = Compiler::for_proofs(); + let mut rng = OsRng; + let base_group = Group::gen(&mut rng); + let group = base_group.as_known::(&mut cs); + as Encryption>::as_target(&group, &mut cs); + } + + /// 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_encryption() { + let mut rng = OsRng; + let encryption_key = Group::gen(&mut rng); + let header = EmptyHeader::default(); + let base_aes = IncomingBaseAES::default(); + let randomness = (); + 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, &randomness, &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 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_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 randomness = (); + 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, &randomness, &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 = address_from_spending_key(&spending_key, ¶meters); + 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); + println!("{is_transparent:?}"); + 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.clone(), + ); + let base_poseidon = parameters.base.incoming_base_encryption_scheme.clone(); + let base_aes = parameters + .base + .light_incoming_base_encryption_scheme + .clone(); + 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 that the length of the fixed-nonce AES plaintext is 80. Checks that converting back returns + /// what we started with. + #[test] + fn check_outgoing_plaintext_conversion() { + let mut rng = OsRng; + let asset_id = Fp::::gen(&mut rng); + let asset_value = u128::gen(&mut rng); + let source_plaintext = asset::Asset { + id: asset_id, + value: asset_value, + }; + let final_array = ::as_target(&source_plaintext, &mut ()); + assert_eq!( + OUT_AES_PLAINTEXT_SIZE, + final_array.len(), + "Length doesn't match, should be {} but is {}", + OUT_AES_PLAINTEXT_SIZE, + final_array.len() + ); + let new_source = OutgoingAESConverter::into_source(Some(final_array), &mut ()) + .expect("Converting back returns None."); + let (new_asset_id, new_asset_value) = (new_source.id, new_source.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."); + } + + /// Same but w.r.t. compiler + #[test] + fn check_outgoing_plaintext_conversion_r1cs() { + let mut cs = Compiler::for_proofs(); + let mut rng = OsRng; + let base_asset_id = Fp::::gen(&mut rng); + let asset_id = base_asset_id.as_known::>(&mut cs); + let base_asset_value = u128::gen(&mut rng); + let asset_value = + base_asset_value.as_known::>>(&mut cs); + let source_plaintext = asset::Asset { + id: asset_id, + value: asset_value, + }; + let final_array = as Forward>::as_target( + &source_plaintext, + &mut cs, + ); + assert_eq!( + OUT_AES_PLAINTEXT_SIZE, + final_array.len(), + "Length doesn't match, should be {} but is {}", + OUT_AES_PLAINTEXT_SIZE, + final_array.len() + ); + } + + /// Checks the encryption key conversion is properly executed. + #[test] + fn check_outgoing_encryption_key_conversion() { + let mut rng = OsRng; + let group_element = Group::gen(&mut rng); + ::as_target(&group_element, &mut ()); + } + + /// Checks the encryption key conversion is properly executed for Compiler. + #[test] + fn check_outgoing_encryption_key_conversion_r1cs() { + let mut cs = Compiler::for_proofs(); + let mut rng = OsRng; + let base_group = Group::gen(&mut rng); + let group = base_group.as_known::(&mut cs); + as Encryption>::as_target(&group, &mut cs); + } + + /// 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 randomness = (); + 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, &randomness, &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..418ece985 --- /dev/null +++ b/manta-pay/src/config/utxo_utilities.rs @@ -0,0 +1,69 @@ +// 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 if things go wrong. +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/lib.rs b/manta-pay/src/lib.rs index ce8e356ef..61f9b560a 100644 --- a/manta-pay/src/lib.rs +++ b/manta-pay/src/lib.rs @@ -37,14 +37,14 @@ pub mod key; #[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "test"))))] pub mod parameters; -#[cfg(feature = "groth16")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] -pub mod signer; +// #[cfg(feature = "groth16")] +// #[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] +// pub mod signer; -#[cfg(all(feature = "groth16", feature = "simulation"))] -#[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "simulation"))))] -pub mod simulation; +// #[cfg(all(feature = "groth16", feature = "simulation"))] +// #[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "simulation"))))] +// pub mod simulation; -#[cfg(any(test, feature = "test"))] -#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] -pub mod test; +// #[cfg(any(test, feature = "test"))] +// #[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] +// pub mod test; From d5a9e628ac8b8f4298bf802e30e08a716ff69da4 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 25 Nov 2022 20:52:12 +0100 Subject: [PATCH 07/44] mantapay config works Signed-off-by: Francisco Hernandez Iglesias --- .../src/transfer/utxo/protocol.rs | 9 +- manta-crypto/src/arkworks/algebra.rs | 636 ++++++++++++++++- manta-crypto/src/encryption/mod.rs | 30 +- manta-pay/Cargo.toml | 4 +- manta-pay/src/config/mod.rs | 9 +- manta-pay/src/config/poseidon.rs | 9 +- manta-pay/src/config/utxo.rs | 48 -- manta-pay/src/crypto/ecc/arkworks.rs | 651 ------------------ manta-pay/src/crypto/ecc/mod.rs | 21 - manta-pay/src/crypto/mod.rs | 1 - manta-pay/src/crypto/poseidon/arkworks.rs | 6 +- manta-pay/src/crypto/poseidon/encryption.rs | 50 +- manta-pay/src/crypto/poseidon/hash.rs | 8 +- manta-pay/src/crypto/poseidon/mod.rs | 71 +- manta-pay/src/lib.rs | 6 +- manta-util/src/codec.rs | 14 + 16 files changed, 807 insertions(+), 766 deletions(-) delete mode 100644 manta-pay/src/crypto/ecc/arkworks.rs delete mode 100644 manta-pay/src/crypto/ecc/mod.rs diff --git a/manta-accounting/src/transfer/utxo/protocol.rs b/manta-accounting/src/transfer/utxo/protocol.rs index 62a9ca6c8..409ad5906 100644 --- a/manta-accounting/src/transfer/utxo/protocol.rs +++ b/manta-accounting/src/transfer/utxo/protocol.rs @@ -1132,8 +1132,8 @@ where C: Configuration, C::AssetId: Clone + Default, C::AssetValue: Clone + Default, - IncomingBaseRandomness: Clone, - IncomingRandomness: Sample, + C::Scalar: Sample, + IncomingBaseRandomness: Clone + Sample, UtxoCommitmentRandomness: Sample, { #[inline] @@ -1150,7 +1150,10 @@ where let address_partition = self.address_partition_function.partition(&address); let secret = MintSecret::::new( address.receiving_key, - rng.gen(), + Randomness { + ephemeral_secret_key: rng.gen(), + randomness: rng.gen(), + }, // FIXME: manta-pay doesn't compile when I simply use rng.gen() here. IncomingPlaintext::new(rng.gen(), associated_data.secret(&asset)), ); let utxo_commitment = self.base.utxo_commitment_scheme.commit( diff --git a/manta-crypto/src/arkworks/algebra.rs b/manta-crypto/src/arkworks/algebra.rs index 4b78bfd06..0c92031c6 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,81 @@ where } } +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.as_ref() + rhs.as_ref()) + } +} + +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 +246,503 @@ 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

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_bls12_381::EdwardsProjective as Bls12_381_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/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-pay/Cargo.toml b/manta-pay/Cargo.toml index 9838e90f2..685c4ad1e 100644 --- a/manta-pay/Cargo.toml +++ b/manta-pay/Cargo.toml @@ -49,7 +49,7 @@ arkworks = [ download = ["manta-parameters/download", "std"] # Enable Groth16 ZKP System -groth16 = ["ark-groth16", "ark-snark", "arkworks"] +groth16 = ["manta-crypto/ark-groth16", "ark-snark", "arkworks"] # Enable HTTP Signer Client http = ["manta-util/reqwest", "serde"] @@ -141,4 +141,4 @@ 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", "parameters", "groth16", "scale", "scale-std", "std", "test", "wallet"] } \ No newline at end of file +manta-pay = { path = ".", default-features = false, features = ["download", "parameters", "groth16", "scale", "scale-std", "std", "test", "wallet"] } diff --git a/manta-pay/src/config/mod.rs b/manta-pay/src/config/mod.rs index 8f62376e2..ab095b832 100644 --- a/manta-pay/src/config/mod.rs +++ b/manta-pay/src/config/mod.rs @@ -16,10 +16,9 @@ //! Manta-Pay Configuration -use crate::crypto::ecc; use manta_accounting::transfer; use manta_crypto::arkworks::{ - algebra::ScalarVar, + algebra::{self, ScalarVar}, bn254::{self, Bn254}, constraint::{FpVar, R1CS}, ed_on_bn254::{ @@ -42,7 +41,7 @@ pub type PairingCurve = Bn254; pub type EmbeddedScalarField = ed_on_bn254::Fr; /// Embedded Scalar Type -pub type EmbeddedScalar = ecc::arkworks::Scalar; +pub type EmbeddedScalar = algebra::Scalar; /// Embedded Scalar Variable Type pub type EmbeddedScalarVar = ScalarVar; @@ -57,10 +56,10 @@ pub type GroupCurveAffine = ed_on_bn254::EdwardsAffine; pub type GroupCurveVar = Bn254_EdwardsVar; /// Embedded Group Type -pub type Group = ecc::arkworks::Group; +pub type Group = algebra::Group; /// Embedded Group Variable Type -pub type GroupVar = ecc::arkworks::GroupVar; +pub type GroupVar = algebra::GroupVar; /// Constraint Field pub type ConstraintField = bn254::Fr; diff --git a/manta-pay/src/config/poseidon.rs b/manta-pay/src/config/poseidon.rs index 5b91aec65..ae2864f8f 100644 --- a/manta-pay/src/config/poseidon.rs +++ b/manta-pay/src/config/poseidon.rs @@ -83,12 +83,9 @@ pub type Spec5 = Spec<5>; pub mod test { use crate::{ config::{poseidon::Spec, ConstraintField}, - crypto::{ - constraint::arkworks::Fp, - poseidon::{ - encryption::{BlockArray, FixedDuplexer, PlaintextBlock}, - Constants, - }, + crypto::poseidon::{ + encryption::{BlockArray, FixedDuplexer, PlaintextBlock}, + Constants, }, }; use alloc::boxed::Box; diff --git a/manta-pay/src/config/utxo.rs b/manta-pay/src/config/utxo.rs index 35dc6d053..57b9c4ddb 100644 --- a/manta-pay/src/config/utxo.rs +++ b/manta-pay/src/config/utxo.rs @@ -1996,54 +1996,6 @@ impl From for Checkpoint { 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", 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/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/encryption.rs b/manta-pay/src/crypto/poseidon/encryption.rs index 887424a70..1c0ec94ee 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, }; @@ -267,10 +267,13 @@ 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 +349,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 +466,6 @@ pub type FixedPlaintext = BlockArray = BlockArray, N>; /// Authentication Tag -/* TODO: #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), @@ -467,7 +478,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..ef45df871 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: Specification, + 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..078a6ece0 100644 --- a/manta-pay/src/crypto/poseidon/mod.rs +++ b/manta-pay/src/crypto/poseidon/mod.rs @@ -24,7 +24,7 @@ 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}; @@ -191,6 +191,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 +519,13 @@ where } } -impl Sample for Permutation +impl Sample for Permutation where - S: Specification, + 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/lib.rs b/manta-pay/src/lib.rs index 61f9b560a..e1d564240 100644 --- a/manta-pay/src/lib.rs +++ b/manta-pay/src/lib.rs @@ -33,9 +33,9 @@ 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"))))] -pub mod parameters; +// #[cfg(all(feature = "groth16", feature = "test"))] +// #[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "test"))))] +// pub mod parameters; // #[cfg(feature = "groth16")] // #[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] 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 From 63a3de4b708481eb595114f0616db1350221af09 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 25 Nov 2022 21:35:36 +0100 Subject: [PATCH 08/44] tests Signed-off-by: Francisco Hernandez Iglesias --- manta-pay/src/config/poseidon.rs | 4 +- manta-pay/src/config/utxo.rs | 268 ++++---------------- manta-pay/src/crypto/encryption/mod.rs | 31 --- manta-pay/src/crypto/poseidon/encryption.rs | 1 - 4 files changed, 46 insertions(+), 258 deletions(-) diff --git a/manta-pay/src/config/poseidon.rs b/manta-pay/src/config/poseidon.rs index ae2864f8f..29e588154 100644 --- a/manta-pay/src/config/poseidon.rs +++ b/manta-pay/src/config/poseidon.rs @@ -90,6 +90,7 @@ pub mod test { }; use alloc::boxed::Box; use manta_crypto::{ + arkworks::constraint::fp::Fp, encryption::{Decrypt, Encrypt}, rand::{OsRng, Sample}, }; @@ -108,9 +109,8 @@ pub mod test { let key_element_2 = Fp::::gen(&mut rng); key.push(key_element_1); key.push(key_element_2); - let randomness = (); let header = vec![]; - let ciphertext = duplexer.encrypt(&key, &randomness, &header, &plaintext, &mut ()); + 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"); diff --git a/manta-pay/src/config/utxo.rs b/manta-pay/src/config/utxo.rs index 57b9c4ddb..12e5121c3 100644 --- a/manta-pay/src/config/utxo.rs +++ b/manta-pay/src/config/utxo.rs @@ -198,7 +198,12 @@ pub type SpendSecret = protocol::SpendSecret; pub type SpendSecretVar = protocol::SpendSecret, Compiler>; /// Embedded Group Generator -#[derive(Clone, Debug)] +#[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 { @@ -243,6 +248,7 @@ impl Sample for GroupGenerator { } /// Embedded Variable Group Generator +#[derive(Clone)] pub struct GroupGeneratorVar(GroupVar); impl HasGenerator for GroupGeneratorVar { @@ -715,7 +721,7 @@ pub type AES = aes::FixedNonceAesGcm; /// Incoming AES Encryption Scheme #[derive(derivative::Derivative)] -#[derivative(Clone, Default, Debug)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct IncomingAESEncryptionScheme(PhantomData); impl Constant for IncomingAESEncryptionScheme { @@ -819,7 +825,7 @@ impl encryption::Decrypt for IncomingAESEncryptionScheme { /// Incoming AES Converter #[derive(derivative::Derivative)] -#[derivative(Clone, Default)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct IncomingAESConverter(PhantomData); impl encryption::HeaderType for IncomingAESConverter { @@ -1052,7 +1058,12 @@ type UtxoAccumulatorItemHashType = #[derive(derivative::Derivative)] #[derivative( Clone(bound = "UtxoAccumulatorItemHashType: Clone"), - Debug(bound = "UtxoAccumulatorItemHashType: Debug") + 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 @@ -1162,7 +1173,7 @@ impl Constant for InnerHashDomainTag { /// Inner Hash Configuration #[derive(derivative::Derivative)] -#[derivative(Clone, Debug)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct InnerHash(PhantomData); impl merkle_tree::InnerHash for InnerHash { @@ -1337,8 +1348,11 @@ type NullifierCommitmentSchemeType = #[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) @@ -1443,7 +1457,7 @@ pub type OutAes = aes::FixedNonceAesGcm(PhantomData); impl Constant for OutgoingAESEncryptionScheme { @@ -1547,7 +1561,7 @@ impl encryption::Decrypt for OutgoingAESEncryptionScheme { /// Outgoing AES Converter #[derive(derivative::Derivative)] -#[derivative(Clone, Default)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct OutgoingAESConverter(PhantomData); impl encryption::HeaderType for OutgoingAESConverter { @@ -1997,15 +2011,6 @@ impl From for Checkpoint { impl ledger::Checkpoint for Checkpoint {} /// 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 @@ -2049,142 +2054,37 @@ impl From for RawCheckpoint { /// Test #[cfg(test)] pub mod test { - use crate::{ - config::{ - utxo::v3::{ - Config, IncomingAESConverter, IncomingBaseAES, IncomingBaseEncryptionScheme, - OutgoingBaseAES, AES_CIPHERTEXT_SIZE, AES_PLAINTEXT_SIZE, OUT_AES_CIPHERTEXT_SIZE, - OUT_AES_PLAINTEXT_SIZE, - }, - Compiler, ConstraintField, EmbeddedScalar, Group, GroupVar, + use crate::config::{ + utxo::{ + Config, IncomingBaseAES, IncomingBaseEncryptionScheme, OutgoingBaseAES, + AES_CIPHERTEXT_SIZE, OUT_AES_CIPHERTEXT_SIZE, }, - crypto::constraint::arkworks::Fp, + ConstraintField, EmbeddedScalar, Group, }; use manta_accounting::{ asset, transfer::utxo::{ - address_from_spending_key, v3 as protocol, v3::Visibility, UtxoReconstruct, + protocol::{ + self, AddressPartitionFunction, UtxoCommitmentScheme, ViewingKeyDerivationFunction, + Visibility, + }, + UtxoReconstruct, }, }; use manta_crypto::{ algebra::{HasGenerator, ScalarMul}, - arkworks::constraint::FpVar, - eclair::{ - alloc::{mode::Secret, Allocate}, - num::U128, - }, - encryption::{ - convert::{ - key::Encryption, - plaintext::{Forward, Reverse}, - }, - Decrypt, EmptyHeader, Encrypt, - }, + arkworks::constraint::fp::Fp, + encryption::{Decrypt, EmptyHeader, Encrypt}, rand::{OsRng, Sample}, }; - use protocol::{ - AddressPartitionFunction as AddressPartitionFunctionTrait, - UtxoCommitmentScheme as UtxoCommitmentSchemeTrait, - ViewingKeyDerivationFunction as ViewingKeyDerivationFunctionTrait, - }; - - use super::OutgoingAESConverter; - - /// Checks that the length of the fixed-nonce AES plaintext is 80. Checks that converting back returns - /// what we started with. - #[test] - fn check_plaintext_conversion() { - let mut rng = OsRng; - let utxo_commitment_randomness = Fp::::gen(&mut rng); - let asset_id = Fp::::gen(&mut rng); - let asset_value = u128::gen(&mut rng); - let source_plaintext = protocol::IncomingPlaintext::::new( - utxo_commitment_randomness, - asset::Asset { - id: asset_id, - value: asset_value, - }, - ); - let final_array = ::as_target(&source_plaintext, &mut ()); - assert_eq!( - AES_PLAINTEXT_SIZE, - final_array.len(), - "Length doesn't match, should be {} but is {}", - AES_PLAINTEXT_SIZE, - final_array.len() - ); - let new_source = IncomingAESConverter::into_source(Some(final_array), &mut ()) - .expect("Converting back returns None."); - let new_randomness = new_source.utxo_commitment_randomness; - let (new_asset_id, new_asset_value) = (new_source.asset.id, new_source.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."); - } - - /// Same but w.r.t. compiler - #[test] - fn check_plaintext_conversion_r1cs() { - let mut cs = Compiler::for_proofs(); - let mut rng = OsRng; - let base_utxo = Fp::::gen(&mut rng); - let utxo_commitment_randomness = - base_utxo.as_known::>(&mut cs); - let base_asset_id = Fp::::gen(&mut rng); - let asset_id = base_asset_id.as_known::>(&mut cs); - let base_asset_value = u128::gen(&mut rng); - let asset_value = - base_asset_value.as_known::>>(&mut cs); - let source_plaintext = protocol::IncomingPlaintext::, Compiler>::new( - utxo_commitment_randomness, - asset::Asset { - id: asset_id, - value: asset_value, - }, - ); - let final_array = as Forward>::as_target( - &source_plaintext, - &mut cs, - ); - assert_eq!( - AES_PLAINTEXT_SIZE, - final_array.len(), - "Length doesn't match, should be {} but is {}", - AES_PLAINTEXT_SIZE, - final_array.len() - ); - } - - /// Checks the encryption key conversion is properly executed. - #[test] - fn check_encryption_key_conversion() { - let mut rng = OsRng; - let group_element = Group::gen(&mut rng); - ::as_target(&group_element, &mut ()); - } - /// Checks the encryption key conversion is properly executed for Compiler. + /// Checks that encryption of light incoming notes is well-executed for [`Config`]. #[test] - fn check_encryption_key_conversion_r1cs() { - let mut cs = Compiler::for_proofs(); - let mut rng = OsRng; - let base_group = Group::gen(&mut rng); - let group = base_group.as_known::(&mut cs); - as Encryption>::as_target(&group, &mut cs); - } - - /// 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_encryption() { + 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 randomness = (); let utxo_commitment_randomness = Fp::::gen(&mut rng); let asset_id = Fp::::gen(&mut rng); let asset_value = u128::gen(&mut rng); @@ -2195,8 +2095,7 @@ pub mod test { value: asset_value, }, ); - let ciphertext = - base_aes.encrypt(&encryption_key, &randomness, &header, &plaintext, &mut ()); + let ciphertext = base_aes.encrypt(&encryption_key, &(), &header, &plaintext, &mut ()); assert_eq!( AES_CIPHERTEXT_SIZE, ciphertext.len(), @@ -2220,15 +2119,13 @@ pub mod test { assert_eq!(new_asset_value, asset_value, "Asset value 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. + /// 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 randomness = (); let utxo_commitment_randomness = Fp::::gen(&mut rng); let asset_id = Fp::::gen(&mut rng); let asset_value = u128::gen(&mut rng); @@ -2239,8 +2136,7 @@ pub mod test { value: asset_value, }, ); - let ciphertext = - base_poseidon.encrypt(&encryption_key, &randomness, &header, &plaintext, &mut ()); + 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."); @@ -2265,7 +2161,7 @@ pub mod test { 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 = address_from_spending_key(&spending_key, ¶meters); + 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 @@ -2290,18 +2186,15 @@ pub mod test { let secret = protocol::MintSecret::::new( receiving_key.receiving_key, protocol::IncomingRandomness::::sample(((), ()), &mut rng), - plaintext.clone(), + plaintext, ); let base_poseidon = parameters.base.incoming_base_encryption_scheme.clone(); - let base_aes = parameters - .base - .light_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 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, @@ -2330,77 +2223,6 @@ pub mod test { assert_eq!(asset.id, new_asset.id, "Asset id is not the same."); } - /// Checks that the length of the fixed-nonce AES plaintext is 80. Checks that converting back returns - /// what we started with. - #[test] - fn check_outgoing_plaintext_conversion() { - let mut rng = OsRng; - let asset_id = Fp::::gen(&mut rng); - let asset_value = u128::gen(&mut rng); - let source_plaintext = asset::Asset { - id: asset_id, - value: asset_value, - }; - let final_array = ::as_target(&source_plaintext, &mut ()); - assert_eq!( - OUT_AES_PLAINTEXT_SIZE, - final_array.len(), - "Length doesn't match, should be {} but is {}", - OUT_AES_PLAINTEXT_SIZE, - final_array.len() - ); - let new_source = OutgoingAESConverter::into_source(Some(final_array), &mut ()) - .expect("Converting back returns None."); - let (new_asset_id, new_asset_value) = (new_source.id, new_source.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."); - } - - /// Same but w.r.t. compiler - #[test] - fn check_outgoing_plaintext_conversion_r1cs() { - let mut cs = Compiler::for_proofs(); - let mut rng = OsRng; - let base_asset_id = Fp::::gen(&mut rng); - let asset_id = base_asset_id.as_known::>(&mut cs); - let base_asset_value = u128::gen(&mut rng); - let asset_value = - base_asset_value.as_known::>>(&mut cs); - let source_plaintext = asset::Asset { - id: asset_id, - value: asset_value, - }; - let final_array = as Forward>::as_target( - &source_plaintext, - &mut cs, - ); - assert_eq!( - OUT_AES_PLAINTEXT_SIZE, - final_array.len(), - "Length doesn't match, should be {} but is {}", - OUT_AES_PLAINTEXT_SIZE, - final_array.len() - ); - } - - /// Checks the encryption key conversion is properly executed. - #[test] - fn check_outgoing_encryption_key_conversion() { - let mut rng = OsRng; - let group_element = Group::gen(&mut rng); - ::as_target(&group_element, &mut ()); - } - - /// Checks the encryption key conversion is properly executed for Compiler. - #[test] - fn check_outgoing_encryption_key_conversion_r1cs() { - let mut cs = Compiler::for_proofs(); - let mut rng = OsRng; - let base_group = Group::gen(&mut rng); - let group = base_group.as_known::(&mut cs); - as Encryption>::as_target(&group, &mut cs); - } - /// 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] @@ -2409,15 +2231,13 @@ pub mod test { let encryption_key = Group::gen(&mut rng); let header = EmptyHeader::default(); let base_aes = OutgoingBaseAES::default(); - let randomness = (); 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, &randomness, &header, &plaintext, &mut ()); + let ciphertext = base_aes.encrypt(&encryption_key, &(), &header, &plaintext, &mut ()); assert_eq!( OUT_AES_CIPHERTEXT_SIZE, ciphertext.len(), 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/poseidon/encryption.rs b/manta-pay/src/crypto/poseidon/encryption.rs index 1c0ec94ee..e09d3f154 100644 --- a/manta-pay/src/crypto/poseidon/encryption.rs +++ b/manta-pay/src/crypto/poseidon/encryption.rs @@ -269,7 +269,6 @@ where 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(); } From b2720e294e53fe58e7e288cb62c19978a655f642 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Tue, 29 Nov 2022 15:09:53 +0100 Subject: [PATCH 09/44] signer base fixed Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/asset.rs | 6 +- manta-pay/src/config/utxo.rs | 18 ++-- manta-pay/src/key.rs | 1 - manta-pay/src/lib.rs | 6 +- manta-pay/src/signer/base.rs | 83 +++++++++--------- manta-pay/src/signer/mod.rs | 160 ++-------------------------------- manta-util/src/num.rs | 24 +++++ 7 files changed, 87 insertions(+), 211 deletions(-) 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-pay/src/config/utxo.rs b/manta-pay/src/config/utxo.rs index 12e5121c3..edd55c701 100644 --- a/manta-pay/src/config/utxo.rs +++ b/manta-pay/src/config/utxo.rs @@ -431,7 +431,12 @@ type ViewingKeyDerivationFunctionType = #[derive(derivative::Derivative)] #[derivative( Clone(bound = "ViewingKeyDerivationFunctionType: Clone"), - Debug(bound = "ViewingKeyDerivationFunctionType: Debug") + 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 @@ -521,7 +526,7 @@ impl protocol::ViewingKeyDerivationFunction for ViewingKeyDerivationFu /// Incoming Encryption Scheme Converter #[derive(derivative::Derivative)] -#[derivative(Default)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct IncomingEncryptionSchemeConverter(PhantomData); impl encryption::HeaderType for IncomingEncryptionSchemeConverter { @@ -943,7 +948,7 @@ impl encryption::convert::plaintext::Forward for IncomingAESConverter { target_plaintext.len(), AES_PLAINTEXT_SIZE ); - Array::from_unchecked::>(target_plaintext) + Array::from_unchecked(target_plaintext) } } @@ -973,7 +978,7 @@ impl encryption::convert::plaintext::Forward for IncomingAESConverter< target_plaintext.len(), AES_PLAINTEXT_SIZE ); - Array::from_unchecked::>(target_plaintext) + Array::from_unchecked(target_plaintext) } } @@ -1678,7 +1683,7 @@ impl encryption::convert::plaintext::Forward for OutgoingAESConverter { target_plaintext.len(), OUT_AES_PLAINTEXT_SIZE ); - Array::from_unchecked::>(target_plaintext) + Array::from_unchecked(target_plaintext) } } @@ -1704,7 +1709,7 @@ impl encryption::convert::plaintext::Forward for OutgoingAESConverter< target_plaintext.len(), OUT_AES_PLAINTEXT_SIZE ); - Array::from_unchecked::>(target_plaintext) + Array::from_unchecked(target_plaintext) } } @@ -2175,7 +2180,6 @@ pub mod test { value: asset_value, }; let is_transparent = bool::gen(&mut rng); - println!("{is_transparent:?}"); let associated_data = if is_transparent { Visibility::Transparent } else { 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 e1d564240..a85b095b3 100644 --- a/manta-pay/src/lib.rs +++ b/manta-pay/src/lib.rs @@ -37,9 +37,9 @@ pub mod key; // #[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "test"))))] // pub mod parameters; -// #[cfg(feature = "groth16")] -// #[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] -// pub mod signer; +#[cfg(feature = "groth16")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] +pub mod signer; // #[cfg(all(feature = "groth16", feature = "simulation"))] // #[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "simulation"))))] diff --git a/manta-pay/src/signer/base.rs b/manta-pay/src/signer/base.rs index 479e010c0..d53806bf0 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,7 +143,7 @@ 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; } @@ -160,8 +154,9 @@ impl signer::Checkpoint for 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,7 +173,7 @@ 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; } diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index 91204d9aa..c8e277ba6 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -16,14 +16,15 @@ //! 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}; -pub mod client; +//pub mod client; #[cfg(feature = "wallet")] #[cfg_attr(doc_cfg, doc(cfg(feature = "wallet")))] @@ -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-util/src/num.rs b/manta-util/src/num.rs index a8f436d5a..2ed907415 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: CheckedAdd + Copy, +{ + type Output = A::Output; + + #[inline] + fn checked_add(self, rhs: Self) -> Option { + (*self).checked_add(*rhs) + } +} + +impl CheckedSub for &A +where + A: CheckedSub + Copy, +{ + type Output = A::Output; + + #[inline] + fn checked_sub(self, rhs: Self) -> Option { + (*self).checked_sub(*rhs) + } +} From 2e1a03bdab7e08f8dd3ef262ae239f72087b85e7 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Tue, 29 Nov 2022 15:19:38 +0100 Subject: [PATCH 10/44] signer client Signed-off-by: Francisco Hernandez Iglesias --- manta-pay/src/crypto/poseidon/encryption.rs | 16 +-- manta-pay/src/signer/client/http.rs | 104 ++++++++++++++++++++ manta-pay/src/signer/client/mod.rs | 6 +- manta-pay/src/signer/client/websocket.rs | 6 +- manta-pay/src/signer/mod.rs | 2 +- 5 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 manta-pay/src/signer/client/http.rs diff --git a/manta-pay/src/crypto/poseidon/encryption.rs b/manta-pay/src/crypto/poseidon/encryption.rs index e09d3f154..f2bbc029e 100644 --- a/manta-pay/src/crypto/poseidon/encryption.rs +++ b/manta-pay/src/crypto/poseidon/encryption.rs @@ -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"), diff --git a/manta-pay/src/signer/client/http.rs b/manta-pay/src/signer/client/http.rs new file mode 100644 index 000000000..4294d1dd7 --- /dev/null +++ b/manta-pay/src/signer/client/http.rs @@ -0,0 +1,104 @@ +// 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", &request).await }) + } + + #[inline] + fn sign( + &mut self, + request: SignRequest, + ) -> LocalBoxFutureResult, Self::Error> { + Box::pin(async move { self.base.post("sign", &request).await }) + } + + #[inline] + fn address(&mut self) -> LocalBoxFutureResult { + Box::pin(async move { self.base.post("address", &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..123ed9261 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, @@ -141,7 +141,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 c8e277ba6..c0e8511d2 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -24,7 +24,7 @@ use crate::config::{utxo::Checkpoint, Config}; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize}; -//pub mod client; +pub mod client; #[cfg(feature = "wallet")] #[cfg_attr(doc_cfg, doc(cfg(feature = "wallet")))] From 4f9a6bb3d298fd3e89972fdb25487629f873f1ed Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Tue, 29 Nov 2022 16:22:24 +0100 Subject: [PATCH 11/44] parameters + testing Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/wallet/mod.rs | 2 +- manta-parameters/Cargo.toml | 15 +- manta-parameters/data.checkfile | 29 +- .../parameters/address-partition-function.dat | 0 .../testnet/parameters/group-generator.dat | 2 + .../incoming-base-encryption-scheme.dat | Bin 0 -> 8712 bytes .../light-incoming-base-encryption-scheme.dat | 0 .../parameters/note-encryption-scheme.dat | 1 - .../nullifier-commitment-scheme.dat | Bin 0 -> 8608 bytes .../outgoing-base-encryption-scheme.dat | 0 .../parameters/schnorr-hash-function.dat | 0 .../parameters/utxo-accumulator-item-hash.dat | Bin 0 -> 11072 bytes .../parameters/utxo-accumulator-model.dat | Bin 7296 -> 6368 bytes .../parameters/utxo-commitment-scheme.dat | Bin 12960 -> 13472 bytes .../viewing-key-derivation-function.dat | Bin 0 -> 6368 bytes .../void-number-commitment-scheme.dat | Bin 7296 -> 0 bytes .../data/pay/testnet/proving/mint.lfs | 3 - .../pay/testnet/proving/private-transfer.lfs | 4 +- .../data/pay/testnet/proving/reclaim.lfs | 3 - .../data/pay/testnet/proving/to-private.lfs | 3 + .../data/pay/testnet/proving/to-public.lfs | 3 + .../data/pay/testnet/verifying/mint.dat | Bin 40298 -> 0 bytes .../testnet/verifying/private-transfer.dat | Bin 40442 -> 36442 bytes .../data/pay/testnet/verifying/reclaim.dat | Bin 40490 -> 0 bytes .../data/pay/testnet/verifying/to-private.dat | Bin 0 -> 35994 bytes .../data/pay/testnet/verifying/to-public.dat | Bin 0 -> 36186 bytes manta-parameters/src/lib.rs | 418 +++++++---- manta-pay/src/lib.rs | 12 +- manta-pay/src/parameters.rs | 152 ++-- manta-pay/src/signer/client/websocket.rs | 10 +- manta-pay/src/test/compatibility.rs | 65 +- manta-pay/src/test/mod.rs | 9 +- manta-pay/src/test/payment.rs | 457 +++++++++--- manta-pay/src/test/simulation/mod.rs | 658 ------------------ manta-pay/src/test/transfer.rs | 190 +++-- 35 files changed, 961 insertions(+), 1075 deletions(-) create mode 100644 manta-parameters/data/pay/testnet/parameters/address-partition-function.dat create mode 100644 manta-parameters/data/pay/testnet/parameters/group-generator.dat create mode 100644 manta-parameters/data/pay/testnet/parameters/incoming-base-encryption-scheme.dat create mode 100644 manta-parameters/data/pay/testnet/parameters/light-incoming-base-encryption-scheme.dat delete mode 100644 manta-parameters/data/pay/testnet/parameters/note-encryption-scheme.dat create mode 100644 manta-parameters/data/pay/testnet/parameters/nullifier-commitment-scheme.dat create mode 100644 manta-parameters/data/pay/testnet/parameters/outgoing-base-encryption-scheme.dat create mode 100644 manta-parameters/data/pay/testnet/parameters/schnorr-hash-function.dat create mode 100644 manta-parameters/data/pay/testnet/parameters/utxo-accumulator-item-hash.dat create mode 100644 manta-parameters/data/pay/testnet/parameters/viewing-key-derivation-function.dat delete mode 100644 manta-parameters/data/pay/testnet/parameters/void-number-commitment-scheme.dat delete mode 100644 manta-parameters/data/pay/testnet/proving/mint.lfs delete mode 100644 manta-parameters/data/pay/testnet/proving/reclaim.lfs create mode 100644 manta-parameters/data/pay/testnet/proving/to-private.lfs create mode 100644 manta-parameters/data/pay/testnet/proving/to-public.lfs delete mode 100644 manta-parameters/data/pay/testnet/verifying/mint.dat delete mode 100644 manta-parameters/data/pay/testnet/verifying/reclaim.dat create mode 100644 manta-parameters/data/pay/testnet/verifying/to-private.dat create mode 100644 manta-parameters/data/pay/testnet/verifying/to-public.dat delete mode 100644 manta-pay/src/test/simulation/mod.rs diff --git a/manta-accounting/src/wallet/mod.rs b/manta-accounting/src/wallet/mod.rs index 919373710..7855064f0 100644 --- a/manta-accounting/src/wallet/mod.rs +++ b/manta-accounting/src/wallet/mod.rs @@ -344,7 +344,7 @@ where /// This method is already called by [`post`](Self::post), but can be used by custom /// implementations to perform checks elsewhere. #[inline] - pub fn check<'s>( + fn check<'s>( &'s self, transaction: &'s Transaction, ) -> Result, Asset> { diff --git a/manta-parameters/Cargo.toml b/manta-parameters/Cargo.toml index dd3861e76..6f4138447 100644 --- a/manta-parameters/Cargo.toml +++ b/manta-parameters/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "manta-parameters" -version = "0.5.7" +version = "0.5.6" edition = "2021" authors = ["Manta Network "] readme = "README.md" @@ -28,24 +28,27 @@ 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.57", 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.57", 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"] } -walkdir = { version = "2.3.2", default-features = false } +walkdir = { version = "2.3.2", default-features = false } \ No newline at end of file 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 0000000000000000000000000000000000000000..b2501e1fece8d1616469b8765ddf4da1a542d56f GIT binary patch literal 8712 zcmcJT^K%`5gT(XVyy(TY@#40zZJTWxr?DERu^JnVZQHhAY}-!mJ9B@)-OSDH@B5jZ z{b^@I6l3*S zLfR;Vx9SR8Hn`{UV7q`Q7my@fTb|@$vM1Q+4i_gc=LV$8kf5f9N0p9leh0%EfDg^i zWce@%rUw4R^ZCs8;*!bZQ6YcqP*p>G^5>Do5Q6>3@8uxPw6Dc0wVvl!+j1kDASjtRCmR$F+;!|OjKEQ7v2qKv*4#Q^3~!MvT4 z2$kY14qoE;tkEpR5?Nd4vo5r2M&SiaHa5(h7FmHJEgWm%1qmcUPyOfdyrC^I-g5uD z9OMZoy9$2+E!$TK_w?(k3=+nU-Oo7VWQY5zlOVtn$f7Jf?}Ca&nYnKN`YU=QkO>6u z&W7^K_ldgJ#|Yx5NdBW@sGv_O=BQNJcf_`-!W$$$j2hmdmc4nw#viMgc$!4qOKXUS z5&KZ4w-XBVj>nmZyZgelhKy7W>pDy611bPBl5p09cum1%v(AWSKQo1%1i@7HXT|75 z@sK|z-IWgyj0UO1yjt%W`0G_-w9@|L7>!XduN0b_*HZy$kcplAYTn9$srW7PM~Mi( zJQ~?fjSw#66_ACbM@0~)O*!iG=a?zsNbt~1nJSfo4M@c4rN8*7qk>3{bNcB7CC4!e z6VDShUFkN(hX$LgH-Oc3bZB|I%~LrU>C0qwb=;Jy{2fh6%g~ua%aa%Axs4I?Z)Y;= z*?;QWQvf!?obHU$tz+!?iqiBR0z@)8c^*8P=GYOYETW^ch^t!sVj+i$p`o-c@jHz( zRLBhdgaS5g<$`_yl&)e%^SM@Hl1wSG%vX=~mh;2*`@)GA>@;I;M`aq;!Ww+2uFPhu zwjSG8njU__3(v*pZab#wv=v-@zM{HfLw%_QLh^}0BpJp20nMoi$bE5I`=Fz=QCE_!<_LW>=Fl-Re-t7d z={v={$bb3DYT*Q|7lbJT!E%;F?U{4@)pN{24=kXU5y`cPAc`oJXnMNIA*9J)^3ThS zuHg)*we{8urw@o(Y#v0Wd|W(=79^Vqs;sXJFWQh1W}*(F`h4PmjB*aqE&4p*A~SVI zhc^$=46GW_U>|tcggL&sCBjATkEN!ttg|IoEft4bE@{g3+vO#ss*~hnnr>uG;aYX zd<4EJY^mZwVb)jXM9;6a>Nifsbq(m*2kVfZ*pE8U{RF;NY&h&M6S13<#%y_hCgYASy6S^6hF-H@}+L@tM!xhR8c`PU({= z5y(ze5aRDWm1AMWl-B)~fK|-B zZBp4HI4%|{!13r+K!A&MP#{taSO45Z!{-+%Mt{CqL_O&9sAdS~#%_8022Hy#pQ_S zY06r;O`ZH~&9*HaBTLpYWh>Lka{p9%Raf$u+mUxwa~e_7?-Mcj49;6*Vup+ff0Y-zrR>vg4hz z27jjtt_~lm+GPyWIsiM?rU$v{@)WEvxWn+wB>u@^!O(I4|txg!q z6)vZ7Cqnv(T&D^0+V~ zl^g&NZOu8}84bNO?&ivfqSjxL6Mw(n=%-uDZe%S-`~^DIn*z^M2_>ZVg5kynpH28P zo5DHsL@Rmae2FJg9YT%h$HtjESz78|JIDB6UV}8>n99ZTs?oDvCC%b!|4P*(aN4JZ zM}m>uXwPN!^kT%nN|esx8-6q%6DO&*prwq*TXc5w{T4!yQ!$YnoFcyDar;VJLVqh< z#6OU2Rneom3eBQrlA+8Zv9kZst4YtFNeQ;W0bSv$#X+A6*uxm%89Lx`d;*L zwqCsJHZifG_-eR4`nnhn|F}&B4ZSpqOg`>X8wusR=N)lt`r{zJlNq!z^D2W&LtP4< z!SnxelbG%MZEM%2#qrsl26)b>v6(GrjOVBF?QDbc&XnER#;iXrK7e?KoQoOdR{aL8 zeAX5hy7@HN#vl<{Tm_e;m#S8*hG@9nXmb7KO-Bn>)}=FydXEVzmWrBw|6Jfo2+=BL zTCsE9`5VyM3F;90>UjD{1%_bRDH@4zHRqc6pIW9XB0x=5g?*lMrBl@ATVL~)DpEYH zZoL`f_AaRG>0@8hs$98ZN%GWQo5(~KX|H?^pXJ=4R}mYPRC^aqd!e#yF@g#?O)E%mTtlI9G+Ps@h{WO3M?ZE{u%pR?rFj<-B? z)EJF^>|Y~6AL0io{j8_7=xx}sL)e$`R4f4n3{26r*p12|RM||3sX~Rku;JU=2JCqK=N$a4mmh8suQ?=iT@d8SjovE;w&zIWy62jBgapIQp6(ikEl`Wy*AWe_amfEMkHk-l z-y9$~AaWg(zt>EVgPju|zGZhaPJ!!xUD3U%W8w730atLzglw&D$ip4Q+eCD4GMm>f zr4BbKRr1MGfQ>&r1&g4`elC{e%Xt5DEYI{vNS{Balu4e!o71Cd3MyF2VAup-#^P*Y zTN}*5)LkD&m>%KCy=aq;3*${if^ssS=G5VomB0rgmpZ}<^8$P4v`mJr${EV0u2w-r z3W%|qVj_%$Rvl42n+#A*O*uD#45Q=%Jr+A^)hv%T8ydXfxXK7ao0e73j`R{Al2y48 zO%+g#4knOH;X`^D_MkzN_OG-n=dYp;f77S2#ec`GvNJ_-X(R3W{9+HiQv3EPh2R0} zh%4cVtZ}fHs=00R=PL0t$|!P%6BKUc+E0%NB#93G7xVtN$4ydHCPy~!T~p7PaHf2WSw^lOON|u z%fYD!m5G=t{u1I4rWyvIj55r&oTYD&x9EB=sAr990vCLGJsjSaqvs&D>lo3U>=tyY z;~6_XXS^c^gJ#-xh0(UJHbVbq3Bxx)S}72whDoPLJBnc++nK%2&0i#&F?h!?<<5HV z(PTSDR&;@{pOksJ)LZoNPdc{-lG?}_SMH3E+!x_C6X7oQvkYdNv8JmdLK>BM~Pp1Yk@~0xrZZ<3v@q10QXzvXTO+Vbzd=n?+ZIRc8n? zXzKAiV9Fk2x=b#k{FU`d#3 zM{}AU=fgo(K>*h8>+vW~)Co}n$*=-PWVsUYQQkgBTR6gNo=$1U_~NMz zEG&FhYDB9K`_W8G_)&@+Ah}H9Mn87L%%+9l@|~A%^l9HsAQ0f#r9~rVIyR(tqCHB? z8%z0?5uI6GAM~|&vxV%cF`J$k3;T$=U%Q<=>A9FNJm9pV4jCChH4`7R)*iK4GCjgN zG>*I2h%=n5p;^HED&JRE>fmbvIdsB2D;?~s`{4R}C%m)ac!Xi8$)t@IG+!vd)r=wZ zm1QUJQtDSPi)o8wvN_ywsjA)D+!KHywR8VykpHZ;d|AIrFq910=Wa<$a9 zBE23tQR3UVRhe}lh^SxmjwlZkRA?gLhOeS#LRZ56y9u38DWE|?0d7$G?To&l*aUI& zl}D9#DPdI&4M*GVECe2b6xBanK_(g6fp+JRLDefYKaw9Il+srD2mz9cE;a~%O_4~Oa=K?XGQCj|zYhqt?j2V=RYsj)A zqBH*P!N)9G^L64}szwi6K)Ib2B2-ma6{}rr5SUWPu1ok)zs;43U4uy`WKFMv&Uz!y z2&bI*eky~N_!dXBm7EK&-(QzH8Ya9{ynXtES#r%qkW?T(_?c#3VV|4w6z?tYWnqar zu$Wk;yt@|L_7iz8vZ4j$RoJaPX*#k;b?3BolJuECDOf$CQZ30r}oR<@76rH=ThSDXdXZ+PxKdcRyR4Bp^hfPR=w_pw?}(%$Ea1M}J# zRA3VV0~<}_Hi2O~(JDAeK#1uk#Z^^inC|O_(B;Vb0=xvv7$P0=sC`m1z8T<>Ox5($ zL26vHw6>p0Uu1CGG^2R0>_{>V@?5D* zUoNEz4zgA7OD z?=O4LhJ5X>o9E-S-nLk@(Os}aP&0BT&FXG+KupcUmF+d!OTnvxi|y`|Z{vCKg`37e zO*Yx+H6-)l)Hy4N!Cak;Ocu`fGN`b0&id8H5o` zk=`a5JI8N|YN9}ZMRghqc7fDm!KJvh7iLspf%ze6!3qpbdT@TNw8=-Ga!e{6#MObATVd@9>uPeZzo8y-# zij(}m==I=+X}*H`fIKvA3i1HVc;t3<_K%CyWmMq*4J~*LxF^wEVCf}PxOv-fIrX3S{Se=nKOgiI3 z!}1HZYYsw$7>_|ada|}xLF($7J5&O&9l!r2s}e-Rf!gsU2FM}IN_$2@O}GQmQV?su zD0WI|axl(=>M1VRb#L%+n*c#W0clTB^e^O$x8UPdfG&A_?K>QpOCt0{3B3ZwibXfr zvCHQ_c^np}u^q&An=GJ~3V-IgwI}cyi0nQQ6@td;Y%ffG^-X~wS zB83>)UCL<+l|&5@eg8%^!BWdN`nKyc7=CLbsjMpb_!(;ooV)sp-vH-HX$#+8s2$v@ zl`_CDvYsBkH3Gbt*Q9GY!36N+qyItE2Mh6jJAN(#gwZQFLAJ0^2Db=UhRF!(elN zbM$TX*m8KdOpLu7^6Jz*d{v`ld(hDkqua3xLh8K~US)eB#o!L#h3Q{7X++8r4(ou}x>) z!K<04h^A+)NECUK1EuW;5dq|tR*6BQRB3n1#y~+^*nf*AAF_XKVC{hJ+2eg}hLlo* zfY1R27aUw#g%;wF9moMF_-@eI9kZ=Xdk6-jPZdruFQIemkvih3yj$)~kMI8t?K?0P zL{2CxWW}B@*LPXNZ)r5L1uvKqVt8dmx!o{W&VZ;uoO?`Nd_iHNjm}pf!G%EbjXF;H z>34k6a5%jm46aJMKg28ovk;@gCbGfsZ*Sd%pm;dzYwzFjz!DRhK^DkHBKTjHdJEF< zFXDV?`L#aI*;+maH83HmN+NnGMeHNdbrCOER! zpyqZ;2+#~y-RDeMim%rmi`h!DjwKBPy}4ozN5ZgAokVhLm*A5&)0F&1sTIjLb$`Yx zHcm_!rDf0ePff$0!-I;_yG}iTwIqN2?xs8Tg3#8Yms3<}gLztw(JvStkL#Xc-}{!6^?eC;FqCN4R+=2- zfIuz11=fp%>vPAT={M(^~q*~^O3Y_^KjWLSnKPdDooUDN-a{Z1vK;r5)pO?{6 zt*jd;HW5x8cSnY)!tg=0{ULPy#qu5*h0Y@?=r*^D_r!LJRcJ=mfV57GY_#$;cZ#cAA2DsPwRp;p7)WnY%a$7SB^;1NILI1d-Z*KN<;X?4}VU39MHA54Am{w>CUo zn+epN-J0BnC0vg4IVr9X{&Tnb2aoSlvvrao*7=F~xu&T$w&A=h+(*79A9o`n(yUuT zXer*cT9OYWrl=VlY*MMI42A9#L#an2jR@*P6DgEI6QIb5dr8O3G39;!E6!Qx&l~R@Jag+9Ye9;6< zu3^|#{D$;|1br^;7ud1iF$uC_kUkMlMo}UZx-nce3XDE75|M*r6kO3n+IQsR$mMnG5YzkJ-OCWz_!q%_$o)yKypdz&!;2Ry*arb|OQ&GV>`fT%z&MbaL(Xh?gxX13Cb;B+Fvv`%&uNE?D(KJB`hp@OTHp|~ zC*~IXh}mN^9y683&rzvtRA8Odk-c<~N^4?#x`qh;hos&1j+--^5?Y}uKS+c472?EG zA=Vmi451w25Rj%9W&Q41vH6f_wWl>pM=IKrmiR)8h(m{GRHwr*!R*IOCb4K~uiW74 zr0M#q0Vy?zsohAABvV zpNTL)$A=HkJiJ7{H;2>KTbX4AGtUwt6i1DOa%mAWTNGC6U!M5LoMo;D@6^l*^v0nf z^n#>T87SfdprGzp+>dW;u;ui=UeInxKvQf>bMecPaqrVF^2$-0o0$#Hzuq3ggJnmL z^(#vvG$%N{8d_vZm$7scRQ0g_3%J0`={J-Y^U-13@Sd_nGVmnb?orS_kr%sih>f z9P0I5`BsLEiZQiJmOCz2@{BBznV6S$!Y$VNz8#Rf`6+?Ic7TRg8Pe#s4YQK0xe zwGJ@=->n0_xa)<9ctM|YwwsH8M#O*RsbB*0B4jiRIz~5R0bc#VclT-|Lv$LU zKz-_lv!P-5^JOOq`9Ky@xZ*KN$~W{Zj;iwZiMjG!!C{f29PMh~`Nw0Q^TXu+k5kbV zC~)d@GJbXQXMqHx;`Cs&4Ws00#?lE(aH zj=ms@-9yb+b7DURdeg5Y16!!=+0(ukEStdeR1!_`;Safnm)mL=`bo|GBV3AY2fbkI z4C6Mdr)9xe3_cN^kvamf7GIEWv$hyCJf^-W&!&!guq$RO0f^1A^SJmDov~3NHJpoN zCHB6gwPWs)LYbC7%Evp)g2y~jj^brElL$U#9srrjFguc#{;z`>K;*=p~?Kc|`sNHLD6s-Tw7gj3@vz5W#~T z^_SlhO|7pn#9xWxN5xQKpLFa|nTp?tT~mb*P+}M@qC-7r^MYLPGWY zJSN|PhX+Q3RbyXm_6!5`D=}N?{&9}RDq2(u&&}(r0yW9SPkuFT<-%6{mi?nlOaP8S zu~#R84}FEsM%Je$jMt$W_5E|qoOmR3Xs$w?#>oyK=JGaJeAHD%qQO1=bcT}W9EDBb zjh?P_pW;V{%hMmg?m9ZOy4~ijoQ(2gw!S)UN>llcuB>h3!l~`W2k_d)jQzJWnf)9v z_3bGT2XRhsM)}q$ZhS>~dJhpI6_YX#8clcV2v-r+)m_9>D|xY$N5j-qUYGox&J`wX zj&VW>m%ehrFaXR@HK+YtD>Y4_5?$u6$9~KGVfTIE#2ap!DX*h49eZI7AxuwpvsFi* z{VQ#c0MUil;&Zn>^K|+O9)j_0v-3&99O;FxCtbsph%k^dhJ%A~e-N>Z*3JS7Ie0;B z4l_nx4v}Ez+U!kbultOt2B8QZTF$GqMT2iFSJ~%H+gW@p@$u2f(9H;{O{&i0E`MU$WsCj~N71 z1oF!kB~1##Bp%OB_&dpv(Z%8dhR8LokiInA%^$ce2d%KiF2ZUNdZ^{377ciuz#!Md zwD)n{oWX^AL(@!I>qqx4ZX(yiu519XbXDa{|?KEWr<~z?TuJwa8$~Xw?`7`pF^W$zKZ3%Z+Xk zjA*s>HVdZ@NZIV3#Af{5yh@g&n~7>{uZ%A`kP#N*4&wR(lE6$br`Q%lK46iBrlZ4$ zmv{zFo%r$}&zaMR)cLdPM1dJ$H3|~aJw5SAN@OqcmgOdt9qy9MbNuS@l-R3Fr$@Hp z3pCteRP2LA=O+{N58F7p62l4+3w%RsC4rP5sk7yS8d%F;#)`P0;G-ny^OGN_XvIFa z8|!jePbIH1UBvwqH5tLU7qb;CJEhEekUmE!64%A>1NvVivGV_fDwou(KM=ZoRfO+g zLi~wZOa`Fo5Ks|fvH6$kkof`@goCQ1LXj8zRq;C0u4;B|nrvE`TqtC6DQh}y?;8qx zYrW8Te)pZciZbDgVR-vHE4!i)j}*{#JP7@P_Nrc7#myYj1wkoW%Zq3k3#rn+1)lH| z`lYg`Nd$-6Tv-r5zt(EpIG5BlVB{RELw@2s>caFB`dPE%a=c8$ZBCl7-$@;uR^^>bC6S$ch%DXt7Tx9MMr&_h-EiJ8A(*k#UlLSDU8!)zqHfV*WN%!I5jqfLu8* z>ReqLuLh^-{ZcB22ORG|8c1>)I8D?&NPCmEZG1#1H&sDIu=fPU#*Qtk`zr~jly}>t zxtQ4W~xrt67AX*!E%)&YhG2XVDQJaQ$$wvAED>yZdydD4-m4~+opCxMw#9DQCgMze? zDspWKt>WzjkQ*-r8ZEU|vGk=(&P%kEx9LrCZ2k-;nE2Thp`1R-VHo0|8szl1FHoQF zN-FAuA-+l#e?`WTh4jcJ*NV-!A{uVzg*xcjbbhQ+=_b92bGKrZQ&Zi5(yxg}=1za? zRzql2V-E?R_YrE7nv|n%0pHpd+MUl0NbRdb#d1Wh%=DuJV*Dqi>L*u01|qYrM6r5# zVb9~(n;TL4vAtHyad_H0xKcj~fVtEyoD(4chBlh1pY1f=l+` z?=+#+;UhKs%wal5K*!qjAP;@MqBSN@IKH{$KY46;hLkWqob<0wHV_M4`d0DP2_yNU z<#e7TNI$Wg1KE+O0%+DT^pk1@oecFKJetRY1M{yctzEZUeySvQdJWmWGzAg_e?zf9 zz4ShQ6gBykPJX=v1(WVBJpxe6QO^ho%6v)Z*!*14Qg%cd)X#7Kgh);;5O69?wsiUv z#3urhBO{y-0dZ~?e}55~Bi_>ixYr$XMkb2C9KP!IR|9@ia>AW^{v!z5ey_xtr;vr? z%8PN$bsZS`9n0C*1OL~YevF@A+U7S*G=zPQhkQTM4@ZY*uJ*>rz24j$6^x~T2Rl;9 z2@RyHImbVvWst$!Tp3Z){wsPC;NKhbbZga(qU}VmK(BUF=yfWojND#0+}Pl|iEw6H zG-r`yt)NmM`9!8mq#5(rICCdQN7HNXl<>=YkoFrhSR%g~Bl}g#Jf7~abUh-MLwZCM z2-%(PTuxsR`vRVsz>Ue@>XE*OPURMr73QIwZlSvVqTCp~=88E{;J>3HmL5*I9zPa`_mHwNp>i>Sx|2^ZqUKA zc6cz&ry;h6NhlJkc%;45wc<6z!}Z3K>o0G*+Hi8No#8Zl%usREGz|Oaf>**w*0Iw{ zo%1f=05;B0hd5Wq(?_bXgv-t`$V98T*ChYcv)quN)zwrv=E+t%#caRzHD9SAC(!BD zn=@_i0?VI1_O-0RDiuporw%&ArgF%8;5h=;bH`pK95gbWU38s=%JRiXYLs;CXuTH_ zcfHI+o@~{T*oQ3=FsUK5(v}Um6C9NZ$Ae#4u<2x?)w-+nAM#+=SXAG)G}16TxNe5k z$bqR{>;p+5hq)M|+KD~*n7l&*V`w{14#L!Q7ifa?0!Uysr~TO`SGCAFYhLYm%R@(v z@#x3?H8RX0L9p`AdMeA_h8=sPy&?#np*+qOcNAqCIc|FUD5)|Pri$XV_qAkfwRl$C zrpu$Q{q5YKKz#0>QIC*`8;Ss=r+wKr>=Z%nOI-RxnNZyH4$5rBE4f`x;+yQi2M`5n z;Zdqg@7iE4O)j}c%KZ?BdWc#r$ZRxUWC-W7lLX`*mBQ_%tW~x3!K;|qS-0c^izYun zIutMe&ht@h$tog}a+RuMkUp+&v_MB5g`>tVZ{ST#f7Q5*?aJXpPFK|~mPufsLfZWs z3ng#);TGwdQ&P_rQGvqvy<%W{jx4@=u8CJzD8l^du3^{`t;Ay;$?zJN;vdUM!nDNA z0iq)k_c6tL%>)I=CGp`~PB+sOsQ%X#{hJ0hZjU@*1&>_V&iaNT!bze{RPQFMdF@jA zaFa^4fFc#p_|r?M7>4}kVrhY_&p#({mSy6yu>{T&=i1e-aR}c{)ZhW zmp)V$Qkuj|sAITVI5btXQI6FtLxX~4*Lz_-TYM9!@YDO@@D_}bi`1@bOn%B*p z>lj(l1H67x? zo~g$*VHWVuD}T@tYO2?+qO(0-;vk4bCbe5_Lc=-;AKMgOvHs9Dn%;Ow4M_b!jFTpJ z9_tvclU+yhK<%q5@vhWkq236@oScNd5WkEUQ{4`Fw6o4m4x)imM-FKgTM<#4Ai>2#wGGLd9MhCg6sENeQ1j5|@*6MOh_-P^)$g(BC<-)e%SV6UAl zXnR}^2ib(6v43BWM{}V~h!IMK7doNHmr9KC^*PzW6J7Im$~YyI%vE=Z%yp)bYSW_e zvYX3|W_J1cL_txXP;t{~Fo{EI9Pr6{{lehuD?Pse*K_^5$yljGv^~&m$tDNoThISh zn;V|zlK%UMWtLj3L4MO>C9X}e0qxnZW8yF|d}GsF)Sj*L$9otNk~a{q7;vtiv_H|j z0u;ad5kh>`VB)iQzTGugeiuwVweY#z__|0_*|6zh>aBz+!2UN*pVGraPULsljdN5{ z(X(jj%fOI+9{HsONb%@Pb=zBP@t)25@Ofdqc=;ZN7#nO z@fI6#hf_4Q3Rzwi`s&IY{Y)W;&RAzx=IBNwj8&nB}k;Z#xpwc^^?8;28mf2Ng z)T1Oxeml1=w+R9g_lw;T=VJkjOa56ZlqF%*`VB5l6% zs_`u)uBxNs>e!!!A|R5X1!O47hQ_2&l6fOp#l;&E@xCpL*@yZ#8pb?6qrw^f{IiWG zP;W34q!=g7Q;Y~U=7xToS%vViQPU|NY+$T@KX>9WhgrhjMibU3Nlp zA=o|mm_={CPMS;8>|qZC+gl?+RfSiv*~bNgsD$miL>>*=+^9J;nPo%Q^egCXHu8;e z!6f%nnQSDtxMHmo-1z+gdNeVx5oHqX(;qBSYpz0Mf(aqdwEK$tJXELnZ$U2$OEf_x zB(mV{S{%Dil)b2m7SvY}_x9xJs2;VQ)7D9{XF}x=jmS#%WJl3Q=U>O#$b{mpxihjR z7MC2xl92^y=gbwzEg>pf@HaRO$3)oNrq>z6Msh5~JSnmdL*mc=j0l^Sm+nLViK_!)|(v)q0WjK35!A)W)I# znh+V;X&bi*joOJy?WMSdIHsJ|ZS;FeaoM(7pzQ{_>TQ?R+7h>?p7 z2kTcf9w_&5|GOF8G~LG0%XG6<-Ia%3Wrg(==Y2?T`>Fgz7Ozb!n(xYTW->S%%bp%?^3ZWKyF5F+(Ub z+N9#<1kBJ(6$!DaPs5;J`azw;mbxt8^Oec}BvYR$f44ZVbV$A3|751Eu9a8Dm3ng; zW!Cvc+SIp?GO$`|BJVMCpP}oDn5anK!#nCuMQ#CMSyoNcjwu72?#LS!y~&{w z_=5eKlL#@^bI_iFyzNzprn=@1jSyrn5OB$+4AFF?ae9dbaEh?eol(*d?Lf2@#oI4R zoKu?|P4c07N(y&98a&-6fG|)1x>Hnx3we_*galQ9Yd(Mb4ky-t1}giQ!}awTqAebxm?>vJMj+SzsFcs=LC6Bj9J=8ao}F``$D@S&csXsa z6a%nQ;70frRAx^<0I{o3>1mVs6X?X!+K+4E=>uo)>E|}UHR3gXQ$BoI^sUV2n6^k+%m~TvZ*&tJjY6Yey8)xow>Hwss?v|2ab|$ItFHtN@Lp7Q2<=5WA+6d0 zUYoMwSvWRN&O^M%OYg8BP)Z-xOyguVjHe)~Muqj$@HuqI^djrb2ZCO> z+W~Vm)ohJi&#$d2yDa7I@@@23om%(r7K+4JsEa*K+t3)PW0<%FLhg18!PIby_w2&M zxYk|b?d7;)zFJVH;)A%;FxzF+f_rqjs*ld0b-pK^eB74B*Y&P&of7db6;!y4w)Zzj z-&T*UhKD=OXb$+SB6v|r?;B7sbo9IB2Zm|41wY5#!Q@-BE}yqCabWAtyz;|nvqdbl z8J=9R^auDJn#e*s9UhyT9`UQ(6wWWu?Dh>pI(8Nlmv$Y>?I#ppddkzOpYo1vJL?W! z&AmjmylO?GDViLq>^_JIA+L1GjGAT2yIZz~iaH_zTeJm`{cA%TM+~nXpKEjE)KWym z4k-ALkg_WDP{*7gPUyn#hMnE9+d6cIAP~k>(FDs929G|O6Ta%Z)!y{@{@<{^12Z9% z#PT9GoO!T;>l#5zqp=-m!HfvgJ3HF_hS6#UNDbuLWA5S)4i{^5xdI9;1W|0%aWPE4 z6Ocu~>;GVMQ{Md{ZW)w~6dgX1144Lv>m~vwz}sB={7wLrn$ixkLN<~>|FSh&kcWSf z6u>B~^>NMC@;j=7h{#lvFv=+79*M7u1=#m((`Js7jB&oKbVXPBapEsxjEUhW{hOz> zMAL%Pe}C#K9?x?Nf`v{mFtKJA5G3izS%eu|$*9cqa0UPNVs{4oRZln_EM&+7E^b6p zPq7+4X@rFL{n)&&KroQ)AzYBKubF(Xjxu zwo^j^=6D*uXDZVCy$;wc)>3t>>6jSJ6?3?fMtvHjQd_%(pLAJf6gSGP$bM=2GuCnO z;v%Rmdwzdv8vYy}RFvIy>SGU?FmFRjxb^i^$mlCWqK6-RcI|16uh}9-lyZZ;RiFSy z^jsa3g^X#X1?zV=-EkL0c9y+dV#*sV)9OtAAqe=~_l)~Kw_I%ROK^i>#ItrX6hKEr z8ksGSeiVG42PSR51^1l5cM@i`a(^_y%=Z|~QT%{G;a?Ht4ZKn7cQk>LSGNUxOjhdU z-2m~42%7jiax7KG59;j?;p;C}_b90Jp3%X#d0l)bc2jJ^GjfJxb>ifsm8W@Ae8myp z+Yxip6)nPKhJB4DzYxjZ#Y3=ppQ0LYen=vU*8cm^NJMKtjVMKEy&7<+<0-SX;pNs$ zsNv$?3&yEPzWe4mEBlN_neU)NVxK8wrVa-3mfS z`L5lPa%jeP&UPM}qr#yvtx#UeJqVT^5@AaYZtCKiEXo(hxUm|9H;A&>vA zQrLDXEkR%sz%NG63GNllFg{etteCExVGM6E!j8wFVgE70SE2@%?? z)9lrsRL-{|ux?>e4`KHpaAjr;gZD?t3M5uG*f6CeW>ZisFMQO>RcgmgI`0X^6R>$k z;adqCG82*vd30ak#(u{p%85hzM7@~Ah*9as@YE?W`^ZT}58i=pElg1l_)nd~11`*m za!p4e=FIf>V&#W!S%P2TDL!e8^RSSEaeC=J1-9jC@JPCm3$+^J=eO41R7-ENwTr{aYS~ngClJ55p=kMBc35L!{Yl%1xdT7bnUD(fp%8m8@rbkq~xWN0j=j*ccHxVro_{|3xr1?njZ z7uSY=5R$ZX3boAMgyRj2186wq&4)*5oMmJ~N-v0l&1L(Xcets8|17O9C^4Z24Y7D( zZ6S=9KQ`mDP;34imCiu})JY#X$ONmlCM9HOiV}QC*>CT-yRa*x7pVyVHA!9}&b$@k ztqCR&sv%B683r-7?_L#~4@uU0+OzazVm;|eFLX$_^!Ubgx{MPn{w(B@i&hRQ4SvpA zZm*hQ#E>jEt(iBx>r>wa`X68O zzZBuaFI+NV+gIs;+#&4XJW%vd;q>_Y^k;-Ow~tAZ5QS{ulMB-17j}fUGk;fYcLRwd zU+>X<+_6YoHq(d$f7>!|OLt1C?nP1PuKl*K!6~ zhy(Qe1PCm{OB8!^xNW_a+19Z0tf9j3G{~rzma((N;bj5fq(_!)3w;FVW-fpaE-jHa zB(2I&i2xc3>W`rG z_p#Vo)ViC4W&Usd{9pOp nTwMC~PbawJlh9OYRM(a++QoWl?{Ls3?Ri;$rZ*{}{)_$xx9%4a literal 0 HcmV?d00001 diff --git a/manta-parameters/data/pay/testnet/parameters/outgoing-base-encryption-scheme.dat b/manta-parameters/data/pay/testnet/parameters/outgoing-base-encryption-scheme.dat new file mode 100644 index 000000000..e69de29bb diff --git a/manta-parameters/data/pay/testnet/parameters/schnorr-hash-function.dat b/manta-parameters/data/pay/testnet/parameters/schnorr-hash-function.dat new file mode 100644 index 000000000..e69de29bb diff --git a/manta-parameters/data/pay/testnet/parameters/utxo-accumulator-item-hash.dat b/manta-parameters/data/pay/testnet/parameters/utxo-accumulator-item-hash.dat new file mode 100644 index 0000000000000000000000000000000000000000..b056bff3fc9104b0c3a8fd5b68eaf1e0f79b7ced GIT binary patch literal 11072 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 literal 0 HcmV?d00001 diff --git a/manta-parameters/data/pay/testnet/parameters/utxo-accumulator-model.dat b/manta-parameters/data/pay/testnet/parameters/utxo-accumulator-model.dat index 1f297cbeea393d6f0693e6c2f8023e98e07e3769..2bc256aa6cce0b04a4734d6838928ba020116ef4 100644 GIT binary patch literal 6368 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 a7zjbfRqaS-5RhR!INGaB{9nEr1Edj^+5m|F diff --git a/manta-parameters/data/pay/testnet/parameters/utxo-commitment-scheme.dat b/manta-parameters/data/pay/testnet/parameters/utxo-commitment-scheme.dat index a4b2d1b235e23d8f9ca558edf5198fede5273ea8..b4179455be4d1d8ca5bc8252116b703132e52c48 100644 GIT binary patch literal 13472 zcmd6t^K&Im5a3^o7u)uWZD(U!8*5|RPByk}+u3Ae+t}E)bKm<5?y9b??)ulMuKrAG zre>O!_86kT13*fbUxG%yE+k>SW_~Nt4eqcwZ}H|ZRfVm4J|oSv;bodOw`gpKwANcv z^|7PVTo)P z%=)w&&@**IK>epJtSbC6_l0f*B9s&>iw!N7(1qj`^ofLad*byVJ8Trb@^Y$>65lTY zQXTW^_adN$SSzfWJrw7LuwPCqJm3FGW?boO?#H zLsO3y+N4Cy-a>Uef+`*LOaozWQYv?yB8#TXd_h8S;krU~F$y(Ir9~i>QA6ErQ65;_ zOFH_Cty_pArkK~urZt~d3QnV|_8e;B`oeujF9L4YODLoGXp}nf)eV)RxZBS?mFU|W zk-c2e?v6>wxs1Z3E#drb3t~j6CXt+={iWC8P55_vYm$5ikREX)#sGS@$wV64r-H9=5P5AZD z!>_;hV)4@M}ilB`fa~|0KbJ3 z%jozuPzMr&+p{Avt2zMN5Ygg}?Pmj1kJw>GofLXTtSfuDdlfAg#PTEx1*5wmTmEs% z7~+U~_C2o-k_-U@X}fLfsR*-LI3waTfBRsqr1|l>iOPU=Q-~%7pQ)uYEV7TF`#$kn z3#IBidMi!2B+ggItS_=j38>NCFxXfCiybGs42ghRsNEObqLPa(^fXt+63-SYKHMZl zu$xoT2RA&hOAAF|JWt%Kz3bD5z0zog9aTCJiepKF4lP8QHQmigpA@8$)~bycel26( zeBB7po77^#)=%OtylI@@^~g*`afU9X6e-JWltTfb;X=jnPTpb}b)S z3w2XCKBvT#8NomlST2hxs@b| zlkejn&}D{WDuj6|`0x0jps=d%!Cw}&V#1CyLJ(fo(v@j>OR_x#c{y&~)kydLU^ga# zV&tNT?=f-G{4G+^(UHlBZmTXUB6KMYr8}T~zC-VCI&m2tro3{^(ibf1Snrz(bA#Ml z^IDg2s&Rs#^}OLDxxWbR5qhPhQ4s^2Y1}z0KN72Fr;OL|@<3o9I;3>^c9+DP)oQke-Um{uuivo1I~&)u0TYzo)py ztA}-`4UW3nx>_Fj{9X3DU4@ypb=+}mM1c8#Z|rlyq1p)BS-)KNRr5TGLx$p0bI|JtV2W=|;MRo|kRl-t!@E=4D$`=J4 z!PD8x_pB%jpRbCBSh(QoDz>=UTNLW6>g&K+;v&zXUx^abOnray^UN+@w^{~PWhfa~ z+DkX^)>5nzJq>#Ai3gR^R+JN-_(d1f6@pDwCV+_N=$GhvT?i1Qn5?ytnXB?E&AN^3 zxEx~O?voqIJLLqOeWy3ZHLgP_`Y{u6vZ~c^=?K3V;ad<|pjH2lmJ9>_oF2uv)ro5V zNp|{CA@pSrDN^OET0Np<3L!ut>NJHxVuZgco&Z2Bvnju`c%C>{+2rh=( zGIq1nD-3ZrQOo3y&Noz4YC`4Zrv8xcF1eZ!D~smYW6l?gA*1F1EqVn@r2 zK9oZBS2M%PGKSbX1I?Nqf$Fk0-96~qm$$lj(fu?5rcnc3#~V+~P?2dyhw-h;0zD)a zI^TF9yK6ZUMz=@ijnADD2_Ncvh9#O!WzqXyuvA;T#$>lom()Kw_Cof18gt+ebtZ$O z@g9)ggt1|rFY5HZ&T^}hW`6N6AD3NZlnC6FJ~E~WGqKqa`Es?SGnqZ_V=oHPpPaWt zO2U+wVG$BXfH?(EP|)A?Ba)8KM2>T~;Fm9jJzi4ei!9&sdu}2@MN}eiCk|}FW>7{u z{=nCD*9!+9(Ji8K9UQnuL#EaT<`9XO%7T|Q(FRx-b}jY6zn^np0q{IaMKm;ci!FBe zFbqU!(uIXfk)->_Dnm12i3mGVh#Dfi?|}*fUs>n;b6I$c`2=TUp-`T}%X)QmJC&*= z`8NE25slOyLiW~NOs=t)W646Og&S#N1%&B&bQFCMVe`_R@Uj@D5`6wyj7Ol3Fg&qc zr8PQh{lF9sfenraP4I7Gpxn3d$@S<)K9J%k9l}$aZlc`2zTk&aBe#G^*f;#9HQ!8X zt}6>)V~bH7fsc*LiAE*~tJck=f6mI+t`9|7FSkiF*3^|cn>X=*BGPTP8C+CAM<@Go zh4v=@;W+CxA>KYdPxFM^xekDMYbb6b`UV+v;zNxdu)N?e~(jhPzja2HNGF&7Fc%62{rW>?acKJu}7j0Gt3?O~)Qj&=(V4cV^ zdB=u$@^m<@ij@5e!B?ifq7-@)?b!kQ8``Wrv6yX%AV?>JFCUWr8-N zi73i&h8E2qt`$pi&4rxsMM<~PWJtt@UMP7xQiraAJ?HqnS=>cO9?fV#JKx+u2waxR z2V)}ouDTepp-a+U)g>2wrh2)b?alt!og-|XVkVN5&H%jIz|GP(VeyD>5BMh4TitvL zlm$F><5#)F{WwnYu7NLjB;OnfR+*}i^eY<;)Ok8>{Jrmp9e1*^cm!2M{u7%OaZ^!P zGr8^x{7{b~p1dDwcK$JGioUdkiyi9eL#4JAgbwi@DJl^fUM+;~YCNR=%o6{*3EQYA{!Zj& zSpOA@XOX2Z?9AzV|FjUrkOghdfV&wr$;!aL?v6(^Ntw%c6=JqQ#t=%BdSV|en$c<9 zk}w=CWQ(umx&e$L{Bunn#iwIkhTi)NOR9tu0`A>c(=CuHOAa>3@(mhvEoWtLXhvA? zzNPt37wIl}VP!s*w%X$qr*&j`vmT~Z#2e06WUzBH*O1T=t&UFD(xrG_?(wrI6fO}Q zZIv;piZ0v680mO@k&~EUJ8StwAC>Du9cPmkvTsF}Ck)E8Peg=jegDo;^uJq4>c(-U z*n7?lEXJjwI@ex`z8o;Y3!(XD0r{rbL%$zm-|<=on{HeLnbN}5_w9Wt_#YmbTwc9K zs%Hz==4vXm@j3Ow+}X2S<1>uGJMk}R+)04I8#Np?Ao8!1H8lpZO4EHyFaLqb!m=*> z-+VdtwK}FBLmcQGRo6U$oU!J2(pFf1tcOkZRuXHx;DN= z{%I0j*Mn~PKl>Zn)O?Dl#gvh@Dl@q=z_aoAr{6f|{f#@$;CcP@v3Cg~1B-7)X1VFq z?Aj87ApqYeZ{-TZ!Q*4R?T)fRSx-Bz1?2%qBPh5>d>A1GwP(xVV)~e`pD6h$BUB>w<@h`CDxQs1=FSvgk+{DM^MwZ#*S%DSAnDfRB! z93v2n@nFUw9i@>bzt)6OFXJl}&6>DJzia(Osk*Ex+HWaAo#0GFA1x#>?feC>j{8f^ zl}e;#h?HdNYLcnmmf(3`25biB9W z+RPxN#Du@`uePIeeC`VkA6Mw=f8b_Y5)t2_?uKRNGm}kvbg2cAFQG2*K&)ERb0zvi zOLZhjJ&%etC*P|-P|nLuD1{eIU&(_2<*2g8kttZC7_4!(&`7p^TrpG#HD7*6)_L)d zj!`NRkp z(X)PB^G!11!Q_EtrajH#<3yTfupQjL5IrK3rKv(LuwY+0-Ejv*eystNF(DW>h}L~? ze5WvxY(IXuPOcRt1W%(!OgULHGhn{*p4%Ddd}tS~57{#OV@woL4^V;#qf?N9F%bdd zNp)O_hknV%=GOL+X)7dMWQ6)kqfx@^=&T^M@6?aIY`Q{$IAo5~C056?5DY^aj0zk> z8@#)DGaCCvXNR7kY;60Lcft*Y|B$D5crVMuo(-p-xsPX|EcmM0rxp*F zQ!+BwsSJzhtH3j(i{pisk9(I5iQfBKRbux0aV1<}EFQd3BA$v|br_O1U5kwr_Wx4;m&26CdB-#1I#e)K8U%5iz)cz`w@&P@2t-eUacR1sK?;$lgxlIbMshg2 z>kRY0)$$DQfO3<>3v~}F2~rH&gykSgMQsj#01?SVt{Y#eui8^Oe%gcov6$2$LB{(; zQVK+Y3^+rNaR~O+zSd)Ur-6SwF;{x%QyQh&He@U%XXl_ppG|d+F85aplGy$yLW1m+ z(D4t5&i64vcF{vG!;3}`^GF6K&NRpfCy0nR8Y8Ptz;Q@G+bBHkx{E9FE&{KU4+S<5@oPx_~#CCBZecEw_yBaJ>P4{ z0WQB}GOWu`epi~DOdF}K#hE-HVq2x6!@57dB9@R)WK@#+wyOfA$;#p11O9M%qOcXV z5b&Pk&01fjN-r|`%Xdij^-=!jG-Q+pWqB zUf;(R2d~OVTRef7`!4e`IMePhLsid-KkzZOAH9R>LS_H@5}m`A?EHczyR!P}WDSul zEGF7+brtskc1fZscV_oV;}*XM`=|re&pVPQ*V6N5J4Ec%&Yzb`A;kk9F37y(4@Jl8 zrk^o4Spa4w1GmRcUdJqvq^n zpDZ^ENK1i@}ux1 zoDmun`=MQpPb>*z3)OcpSbxaikNykGr6#iMtJ``x#G=)PK$|uJ&`aZ?(`L47t>TP6 z=W~Rz)+P6+ps<4$c^7VZ;ZmWYRon0&YjMW2`)d`U4qpL$D`Mxl`vwD!FCb7V!3M6` zm3O%)EmEQIn&6ST&8;*F&J>Ph@`<1SZGgX#Zc)5Ut1o*X%AzuRvgi!c!@&vwIfgc# zn`lwzeF$4i7W?b3A#F-OY`mZ}zN{9y+C*fU)PAyxfbK6VX%Fma%h_B^t3bgMA1j`p z?CwDzsy?K4<$QV&QSnbBG18a20y^!VmGy?qI#x| z1ajO$qWUb$l{W`VaIqI^7XA9 zl92t1?*kt4Eonb99dA^$AS>ErIe5%0FLqX-*1^v({v<>vf zf_Y3pM4F1my4P>4X<{ka6Mj1{pfnc@D&wzDB@bCA*(nbMwx13i&tf{+vX~)Rq$I*~ zCmEFoj5psyh=st3E?+RCBEMJ%@z+S0Wq^WHp$SHdmA`oy49YW6WE z9F7euBg02C+e9&2eCx}Hj2J(<_|1*?h=lgRlJ$RUMU#d;Z{6``saT={RxG5K|B!0n zf8P4M2bxf_Pty)`k4y$zef^3ZM<7iZ=ft_JUKcu9W0-4*tWsKgq_z zu67Rm#o@PujrQFjvJ$spe0Jn{uX)r8_GRwj{Sj=SC%c^S70`Cnc$I{P%F$fQkAPS* zARrv4>d?LN(@N?-9{a`7%lDqPv?e}*SQ_Nu!fYEv8`PCTCM?ir{!;OxKh4QVN-9VU zy+C;j?iv0srx6M1<52UE)&2wnmYEu!_AsErZd&~C=}hqw{wI?X1MuH8mk#`7N91GA zCUx=%$ssIwMTa=St3cKxBV)VeffgCbBwEzH&n-FMV~CJh0R!z_XrMRm|UHle5txUHT3xBtP3ux{M4KgrVVJA zNRc~nV{?ODux^)rYJCUdwl3$0L*w3KD!jLvoTfKwM2G$b?$u;XCft8a>aY2un%ho} zHd1BzB)Q&k#=kykuLN%dfqk=oL4!@NZ%R@cq5f4mq@Pi%(=+uC&><%IqwV1BmN>hs zU^hoQha9;0hH%T^igz#jfaF#vo&Gxx!kEGodb0K|Xv-1}I&?KNSV?=4-WMr3sbDL( zXCS|$-_HRc{5dWY5!(!{Z}3^X7jhqR+|4355`X6e3q-zVf0sq3c7BUD8u^Joh}{nrWd5B*{;^e`+hmDKpV2_mKNrW7k3uCb8~y$wgsJ#*Ud;xcX)s(vX-`^5%9#zjQH+8ZPc0b|3J zjDH@=IsX>lplY*cUy@#y>RB}nVYs*;DZLCC1Z4YXcB$K_-GyD>#Py@G8_~@~(7ptx zX{~{D9~Te<``<}B39#TzwvzSpv5!G1-9C}c5xZ_LXQ=fO%!)}3NcyIS$LjnEc-z=Y zr*&B3fgYf`5u0u29!W#98nAx?MKa8a(By!z?-Qy!m192W)BS0Fc1U8-ShZz{0H;%d zoP~$<#@?(5lW)UUS}T&Nzp4RkGJn*YwHBn1-2ZeXd~K;$4`w@rftbR$zt}`_xvdeM z&ecXSZIK`EF5H2qv*!KqMY$;J(VJq%OZzQFEeB1(zjppNI+dO*2mkUfNpDBYVg0%~ z%(VYOn}n$WS;M{;IGG8WyC|cA2aFY|j?W|8Bc-@xrAluFrza@nS13 zT49{n!M&tca%LQ|zmaCZCy?rnC|W=P8upGwuZjO1azZT`+@wk2)`p1seT|>7vtzBU z6lVCGW7pVJ-87wjVK-s;ihCmX3*CPta7#n~sl52zG44f4d1OUcTO_-rmhN|s*e^p* zzYEqV*V?BsKPd^TVq{{a%qI(1|;9Lz5r zY>SWHrp1+TELgh*f~jCEu{Uf973FZYf%?|&GHF@nyfUK)h(YLv=%HB^{uEL;3qpSf zVU^Hs8t-GC3zJ_*FZ%hD5Dv1Uli;aghX|0MSPmCvz*JViQdi=Cz6b>6TD^d}-h{b4q#rh}(5om@b z(CNF@$6Pz3neFpI9PlexrDQ%HE?WL!+NaiLb7GTg)Sz$#`$NO6o?7v?ugfL#l{@*7K zIB>JXcBBvlQV9y7o-wy}GDq^ZS;4AGA3A}DAB+DbfaM6dh|vQL`Z`>mVV72nN5;^r&*O8q}-v>6OX zLdwNPQz;K7b)|XxhUhqAP{=}Ak%KrXgItz(dm zmNKh;cj=t%UW}q2l-NXpl#Z1g7=dp>5A6Drl0Uh->RCoC6=LeC(BaHV#GviU@Ox`~ z>rMsa=pjPNHk-{FFV83zCOlMQ?(ST?ZgUs%J_j3T!Ag`&2)4xT z$b8tK$RZo&*j^aIU<`fnHwg1%BD6KID!7*-MXx``i9KseD$)?qHTC+3^2|!pN0quW z+L9~ZysHI~!LLL0cH5}$R3VVZIW4&>6Nj6EEK|H;&Lf?XDAM(AB=ay9aPTg~?=aUx zZ_&J`VY;#5*D^&_``$BK8g@xb;jyYe)_G@EXR9;f zAufqWh&53D#BmITmR<4T#<(c0;)N8_<^JjX63OqMvt5Usu|ldi5ol0-+(3XA=N4mD zZ-TkFe=NY88TW3zT!Sx7P2gGl{()kZ7?S`jo1 zQ_4x&jSk&siDuY`JzQWlX~5a`a6M8?b*x!)!`m5)*cX&#+iIaq^@h!kug> zKm?kt3hE5jDt_6Ywd_UXs)>eL+ZGRMd{z*5cXKL9RHF_sAh3Ud9?~?V&6Q}gT;ac% zh@84BBg~6Ap`0%OXt$t4X##4wVJe--!L9U4&ZZZ0Op=h;9RPwXri99lXqbCJDa8ap zOh-8k*&TjtDioF~NI&s{&&$N@t7e=nTT-~9nS84octR*P;PcUM?mGjb)ogPTwH8HL z&I_NDO?gHKLe==T0c`seHNpwiR##?)Qd3vO3E+y=Av1o$I1z6!!vD@?e5}13ZlwYj zU$Xt9Tu+*og`PX6$L5mMllj3`4yL$>$#Pt1_Lz!ZtSD?i>Qep@j;B=+r2ZOAtI=zz zh0`QwK|1rw7F=YL(ZxD;r&N>}&ga(cD(`iko8J!=N3r^?W5cTb5m$BRf1;kG7K>*P z+p`+mA4Qq7>%UrY;$TC%0#gl8 zNl`}YMe3;|TZXKrGdgFHS^n(`AJPjp_f?BdIaGnkvHjdM0oL2-p6hG>do@naf64b) ztj%kVjW^`C0rQ>$>CJE!G4j}WV8Ro_2)VD^9PfBTjuwr2!1)i5MBZ|+Av~We4gHHQ zg_mF7QAO<~mxUy&1VXUfzL^B13$_|3DRcSQJo>Eyt+Rrlw51vs-BGh07$1nkbbRS1-#>R^E!MxHX8Az3V(_pMz{CPHU#4 z!hQn>+co=xjzFk&MgOw{oc!W(BIC{J8eSO%8#H#x4iXIm{I1gNWdRpRn5B-hWvv#_ z2!4Sho-AYi-5AsgWHhBtp=uqqs@d+UZrkpeC}Mr2d1L}P^Qg1Je88cKP$bM?@rU{m z3%Oj5>yMKS2RcivPyc#Zr0MDcN=hiDf~qU65I&@>oJ+sH8_aSyx-w_YmEhdfG%}^T zh&=9t&|HdDvErkWhL_HrL)p-_x^>hP9g@2->P)Mw&8Pjr$SA0VW0*T(NG&8_4wD#7 zISi28XIsI=7QxM<67?rFvBS~#8j6jKgbxXbhYwfr1fEpRCb8N)ZfM|^HW;3_$i!E? z8T-!@&g=ZAKEJl4m=B>6ysdiE6wToo?yGhkkh?prpO7)OCS7_XKX46hY7?OJGDF9x zC2!|3m^a>8aGzW6V+sy`d-dDK<9gzsnFI(9$jC%NBG+8p0VGk%7C!#u?V4rj zxbtaC`c>TafMZ$01P?YPYB3V%)3ljbt&7O>_O*<^SxX%^Ch(6YM-u6{j6LA})DCSN zWT^2vV3&m2aW*TK5wo?X25f>Ul@7Dh;J0Bo_T8xXp(6EPlSe#nN&v`6K*TSl1Oi!N zMdB2*9~9cf9mS)+81=oT!mphy{ahB6w8)=fOfjXPLf$ycs8&l3=mYwjiK4sp8wF;j z#P(hbX!Q@nhkaU&J~`&P1zEvLcUl!n7@XFH{#m}I{9 zpuh3}bhB(@xIJeRD72v3K(R5@Mmn*=?zP=9@DtSZzhei*)W5s(DR=b(b`yH<^r2v? zu!+^ASpo^{o9=sQ3*(I%2#W8d7?|i4jc(so6!s|ZB~A{oD=EHzg(N}D33gsYw-lWaD0o^4-noLYT=s7;@##lCTygPdm4QPa64G+*w3RRNfMdU z9*LC|I;p#o+NqzZk4-`Fsh4d$U<{sa1&J^-|5&~I%gUqJW8e-_>C>B0-v6AJF3b?as)tU^BKzLpp}*fBxlgZTg7_KIjTX>qKtrEf zn@A^UI6x(QNtF584|4Gvugp@qrAuaez{a~-hg8(k5uwmO451h`!fRg6pGq*Cj zE4@v@*fHzG`XQy*Q`z7tkSF2N@l?OMhz_|&W&yRI8{1KrM_DL;5-q#Z-+QFxK@(3X z;s*xcftQ@pFMWo<|5m<`rQfgK;2yno2BHq(j2^fS|K5Owhv1K+Uj5^c`G73va4e@-ay2(-jAfuqe3fW-F_8MhxpL2jh5uNjq=*%QM7IFS8m(okwzFAH1zd!T8r3P#@w;_fY1^CP;37;a=cs;OUw4@LcEYm4S&RhSW!l z_EON1ffk!meqX~m?UTrhW{W+&Ue|-x3g3@Ck{*kD1F!y+d6c)eagRh^ebj?WM8}8= zJLd+ya>?W%cEF=lffPk##r{xHzr_;Vjzh$P=wM!qv6qh;5xBtn@Da14{j<=gWd7dw z$7p=z#?vx9&ZbK`M=Gs)O1tC`Se|cou42pZMX5@y_1!LZEtEVBeqyEa7g&>O4#57! z2bZsA^L;*gLWGR(I4LNGgg(p+@fdt&Z&(Z5xB!kq%<6FNJnP@`<8qjhLWwC6kthHk z;z2F>>-|csRkqj!>i0MQvER!xb}r$^IgDai9ZYf(1vd{74c1OBSG=L7ux0jPyj2W5l(AyJr(2Iu|l-m8*STOWm7 z-i( zU!x(arV+X$##16O_}?AWw7nnOiKL7OU)sBmdeIIQ42pjAfR_9t;s3TszM23pYDpqj8NM$xwO5T1XIJ0a!0vn97up1X9QME{uXU z*`!9F^O4Lm+^71fRa;*#JZ*b@*f1QBHnQ9~EbARJ_eAC#Dwmm-)M)f8*Mua_an;Kq z$bK#&cR<|oC=56G1%4mU9OzA@bTW+OloYe#1^d($l*!#D6Te*0*|gsppRfbZMEy84 zd4a~jmLX35z?D{o<<*D<1Cwv1lZGkZabFx*%+z?N$E(cLQQ&{t_L#IuXIOr5<|~pp z+XnZZL=8`IV6$Zpuaw<*{X22UsC6U`UWy~9z5J1xKyC6m4|NDpJ5%)uiszAkkl{&% z>lup`S{4c}FyX*sbM>7GMge<#{o2O+s#}c@IR5!G=r3M^WymTEIT_Fu9Wqqx6I3=F z8%Qwl1e`yQ$^7{GRha5ci)FAXRZ!oo=}kcb*x;skmJF~>|63DHVLU?or#GLDDen8_ zN!#~1{6^T{MjinMQxO~Rg=I0u!{>IO zhE{Au7*Pj@=*sQXnNBUQS}>~tQT$SZRx6vrNK&>~0};230U!U9;7Zrs-WT%lTtE&~ z8CS4oyRl1;e;V83_Ip`A(D7Q2PDekpo4s!&{xqO|6)vqX5~kp&JUk_#buSrFfRr) znY;~gf_MvK!eyNaX>Nc3T!P6bC#H+YgzhiUD7#zn*>+EB2zfTZ&wQHVJk*pb^K*)S zSfF;O@J587ia>u`g#l~~p9Vi)h3T89xZ3n|q!86ZG4q27{- zO+l8{;DQ0=$IbWvxn3=r2(Pxk<^rXEJhIo$;sp8pM)}bZ2%rT3PsAIpYf#V$&|k5BE(n&8qC*L;wlLtW_tV z1a=3s5d}Bu2A!R+5wX0NYX^1gIv;*rZFFR@UqtUU`g24=Orm74^Fp>G8*}-t9ec7{ z5%}+3HPfCsVi-xTp#!bTmX_S)rdeFwQBP~a2H|WA5yfz+70b#~0q0+!CY{vh?WRQ( zmDPvdrak+2G}@La%5jauin_2-D%pR(WR^2M$F;hl=H`MF9lPGOrkxy2qG{daOjs+w zkFp)ZN%s(?yytBp8&%Wt@siwOz=%DCvqf9n+gvVQ;URw|M|B$hEAo6I%Y!}=G&m2Z zAODv)%^Vl^K>mxj@oil#P$J48U731-SO+O6g1w0s2G?krBnY(`ti+f05iW}t%s~SCId~4K(zw^tHFO%l?!^x=%EwS0I-jfv|Y6OhA zsq?|8Dm~*CoHL`Q&4*!eCtltvD0(G{%p;3pmaDDr)z}el%j$FlEtk1R?vvjRL?3AM z3#H~$ETPp(mRigD;=ETRIIeXA2Mhp^dM`+5CubY#Ob`Ldy%34?|2+^LB&+{IX-QS3 zvD;|p;obAi0cIH#ELiu|N{hz+mZmSu-zi7}eA!~+UWd`Mj zZ~h;F6gI%n;z3Qi|JDP7Evpl9B>NRTr&rH#S;4A%>#{K^0cIKx3lJc7Ha6Ynmov=C zX)v-3vRg|R%`%;gPe`z{?!uhl*?*)=ohN&V4pv~-H1sp)lym~Aw{zGagO!M2Wm>D2 z<#g2l-}(OAfA+`(kKeko5Z+}nQ)eZgQ2~F7v>+7?&TG=0F8%*&|NU?931^o9&7Gh) z-h5F@{}%tg%q5DemUOj&{(-Mo^*{Olug3TPl&{Tp)81>*lS|Nd|DO-)W-{HQ1`)HEGPe<9-C4<5-dr#%)J;J(*&W*K&d{$JI{ r|7*Vg>^J;3PQethMtC<+QLnxtYK5ySGC@EcM${Ay_mctezmWd{7pM-_ literal 12960 zcmV;RGGEPO6sxa`ek!y~Qi<9^@g)eWw20!?3ye^;^XG0#FDeNZ+foDtNS0d@Lqvm( zG-J+50q};5Uh6sq>&|(wI@ocOPAxvjO!J#T!qz_P5PD~jzq^^7`zuu0Yn;23)P#4G2Ts)&4LgK)`C&>hQ`K(S4Ae<$*aos7GBFs!OH;HBr@{689VcfbQE!N7X zYN~=d`v zQfslh5jDM#&Y2)^UBfBXv>M=FePK6l=`35IK$aM;Qd0+`X7L(H-REC`4H)sbX_yGW ztr}<=V_~&6Kx7fD{e*%bNpA5qp-GXbC*8+KZDwyy^4DHPxjg2>aiiyz{_21j8vK8B zq)aZ{Id0|kg9vkux<&-%dq-?BXn}uz$cgGvVMxcoPQZ=67nNOT9XUR%ZW{!t=jpXx zCN+(>KW`mV@bvC8!4onxKl0qPwEJTE5~v)=no$`WKf}T-_RVI(?Vj~kX5>lKD6VHL zNv^wbw{-&`sN5kt(-mEBtKuOXhgcSXyQ{WMsuk$W=Yb)cI1Cn!JQvDL?L>3z=~&J! z0CSM|=nkVc(d74PQkMV8Ev+sl&9TvjiCIAB0g=^{(uV!`GPSA(ndRY|H;$bO^nfr~ zu3a%xQ79H*2~y>1kQG`3z#pQp=WGSmj^*n{djt-aWLZ@_fXeNQJd0by0ClCka5 zr1Yj_ZaKB6ddo)`Y}Cs74P$5-Yr0Tl+hATpgM_V27A9H5dGGUGuk-Isa1tMQRF za(&?Uec?qpqZ$z!6vJVrM@?-19oUe#vzW7jL51Ru85x_%imV#5izE@p>Uu`#1TQa0 z#xvve$~Tk@g{c~r6aPa z5jsa5)bbrUCruh5REjx!P+HyTMI-{_mIxq+WA(d%8_8Z`R*%@Oi_d8(|vEx6DIK$yC)m(qoO5VbQ>UdltyHK(!+xAb=Y)XXHfOR`pVJRj* z)bZ@sigeOP3G{IgdPOu$)rtMKEP-@4ygygLwu0RpRsaVtN>Gef)R*gxV;{7sWP;Ke zt__T=#7l5P(tK5JzZiu@B2}D?mx_P>0=AP&xcaowKQfBx!9gjyrWHIx8*SLSecUj% zd`UG(bO#995rWY zBi%(-MVCWsFz~@v48%ikTndUxK9MF+t-@!HUBme@Z6sSIV?eE*w*OCCjAMVD9_HeZ zwoqAW&do5yyGS!~Kc){V5;yIJ%J|R;0MkK?s1kvy0ee~(Nc|mQvjmAua6Yqk2dvz0 zr0LH>^#y2*T(j4D7E4E<3_k09=p3MVQ0&7rF^M4K^1kJ9r-=kXtawMBu_q&L7G9i|dAnTX>`5{c2}mMj7H7HD zB!;YAT|H?bgpo4Cd=e}hZM}FtR1fiod5W|+P`6hCq()`354RpRuLX~Zm#R5Va}P?j znq4dQDy8p1JE-vsOn?*)B;sY^GY1tr%XU7fp)-u0$!`Xt|Dc4^AdpOF3gIvMi=I^2L>$bw57WMmH#(ydiX7wfNreVi$p*Zch#jj14y#p`YIt@d|=WPu_c zkT6&zt=bkRH56E@_R(TKvIz0r$|n#JZsWw(QgcV~_puaRiqmh(D?};h2i(8j#mY*! z`2ZAq?TW1uHq4y9%sUNz?@QneJ%+hR$0XgiO-l$wa3(`ACC0Ff`+Db%2qKQ-;CSB< zn5E*CYi?|=c;Pm-K6DE2q-{1B$_7s&?Dql6Fo)zDzb#p!o{gS$JWgv>=E85h-euTi z?u>vD3b%rPlIrPEb#TYgE7*IZwP^z1KSnb!4dqjkb?acDvOz93i7PM{o;YQ!hk6^S zXBKENX0K^3o)68%O)Z@fEzS6fFNL%SZ|Z_*K5!lryyYY(&&hF~4Q|IA@>+iHpO zm(!hrx2b^{_|jwrQj*nG2=z`~8izOHzWsTk18bk*XhY{g0iiEayUGFbp#w0ygklTF zUU(SvG)OCswrY#eG9Xn&+2o5heL*Rcx%(LCEPe?1%Ui;`&k zhi+V&r9|w*in<9Aj2OxY2)4_p78krcIZ&g4>X6qbrY1ahhqm=ja4H%25dyTr`h3*f z^rZR}B#rsvTp_zSPmn+1C^LfheVIK7{Zf#o?h*;fzt808B8(|;J)HtSP~XRQ9|asN|L3HBM%GGP(4(lWjIc}Kupq)`Q!V^Qm+n5lrRV=U8Y`@;I(a^ZYiuG zSskxC84|VQ3iQ&$rPD^I`ibL0|r;p3L(wYGnzAG4ik?|L>BALCx0&Lq*DD+Ju_TI zd5WSBKn*pw-w8p^bn@oC7<4Slz3#T~0FMK8w)b3h?neT!D4zhzs~b>`T9sPbFa1S5 zlmv~UR>BmD2C=4eGBNHbFWm!r^x+hw`unYPf<`T`Pi$9aSwe#uVcF8Xu6zCwY87^{ z;~yPx@w5Fm*}(4RN1eLm`=Uif?8*bpXrJkqxv+Y+HkMAdVDvMy=+y{XS#(X$CorY#2m*$-PrbC~8> z8mBxR*2rjfUvIc<{@e@eWaqh!ye*7-y9H|u5XCJM#+rDUMm_%aDVSA`8YeI%S?x9Gg!cHJzY6r0(Y-M z4F2v%!S-CV2aw-Z9PBey%=Kc!f*TjrLMGZQEIj>PD|qYZkLY&H)PrsbX{A?dNNfhm zsh^TLi=&^2^nngM>I-h(+&6hkj`C7ID%)39qzY8~d3>TtnSRHAgG^$O#lcb_3c+Xl z;p%uiVWB-l{y;xj<83mfVluo=OeuLO2wyQy*KBL3{{~&-^pNnT$I&0Fp=6N&Icc7L z6hz1@^@iLful{l}%3zBxCu-bOcimpu(ntm*yRdtV=b1=kw5r@B(7*vwAri-ni2JK@ zIZJ0Dllv^q7>5hYDTte?5D>SACIhn557~z}73i9v1NR)zz*YU6qw*?-w>NSAYRi>J zl{OxlJMHl!Sw9SkVr45!(>5|*WW?20tR*W^_7ezX=8S&k%WAdaLc(R91wuXt2m6;h5nu8s{(l8< zLFf+lcm7!+c7}E2qER_{KTdqnjlFAmg&#@j%Xr6zN$^pYR6C;?f|s2dM1-aS`sgsmYKN!Fgs+WrO^4vOo&}|bswXZXNC3_ce%(-&k$@A zqDlb&y+=l*>Tn&ckE5LBhqp}E2>+3ruUI3{58ZIsCsDN;j;(c7%VKg~!KMm4>iG$$ zcvNJ;9#z6mU4}(XAl`cF3{OjIX=|`m4>}TtH$C6qQ@SRp6}e|y+gw%Lmb_(bRbpKi z3X(oVqf29MFo}_C3G!;n)&su4BvS^iPM<+}K6$;+PLKp}AcdP1Qc5hSt;UaR1z*+s z=uj|YYCzE%llKVkD5aZ^0DK!UH3Hos8uSeXqb6RAT9O>v7YbAF6q!>1Wqu7~V3-$* zx|Atk{~W{>D%2_XJMT6X8ROXfTE53im}^rsSGgg7P#Hcq7L2Q-fXX-Jt*t^I7&oDD zMdUK*p2oKSs-^eJVxMOnn}UZdG0ga04j(d2GP*ozTs(1ma2YIDTGv{8KMc*#!LtDk zJlgrVu`5iVOOm)#+~P{Xn>zSk1uaq4x`@r12)y!|XpZcG1cF(@oZD0Rnc<^&MlYO( z=q_coHFMh1B|x;daVbKJcd#mqbwJ%$e6Iyb-}1N7Yj;b&R&lBCw`*4+mOsM#;#e-N zYsut$NO)ndvRQ@K4=TaQj(?t$ef9I5dNH+dYS9%tGG^p}PaYF!1BIL9(>`lZv&uMU zu7QA=w9;LRqvTwXV*C%zW+=9R9<=?bElLV{Qm1*&lX4*{?nH#m3qn$&h=r>gSgoFYcFlhIXbU&VD3;*kK+1z z_Jd!D4qeL~IRaY=xl|52Cl;u}qnE^k%4YBs3?EGNxFAm)Nq;m~ddCR5i619nHu6CL zEE01crt_#&#uf^7Zv=35k8VVL(DoS?nRgU3>GN{}Ptlgzh*Uw$=z%Q1QY#8C15o30 z?v>g3I050$xVv5r<1bwGx(>F#(X+}FN4f=xkYZ-CT3GY&`HW!w@;)u7h=cril5?T( z2-4TZI`?nqfrq_TZ)DH8jmDx|(><2W+lJgfx`8lI(9 z=j;TRs8CFWc>*ZP!t_?tiGl+SQ^)udWUSS^!_@b;$sLHU)O_feMnJ{)l14Hu79Wd{ zHbtecbF~L*QEnH5ba1`N4*A#r_A++kMWhb;`1G88I~t6uDjWNfU-S>CNd?jA>G?606=Ql;ukhf-3hI+5$-@$IyoxG3*>Gm&7G zJwyu>@mnVLmnmQu;qeSbZJ$KXFjF=>o7-)A<62FH2GG+AcG?ez?1Kzk=^gXag3@x@ zel>()If?kFw&e?OxzO38Oy_em8z$V#DRI7C zur*{eP9ty&$q(-w3+Fr@lSe{?fP7=J%9pVv!6PXhtO{k45m9n%OT{HW89!}>`Ef8R z;*Mld{Y{z!{@X;s|AVWH&r$eZ{Qhloah|Ur&uuvChQ>{|6B_wRqs2oYWWE^}D z>>T+DW$>F;5NI`Ngi7}+<_>1siPbid*lV(#R z8Z63RTG~s|N!j}YA!L;8h}x9QA$cGi`w`dwagQAmOC~#70=O8L=u`SJH|K;KyYD8m zx5(_RW<^`Qw`*{SunJB~TXzs z$LZ5UM14+=N9=G{-|1pSR1v@0Z_i3Du0zc0@vFL5@D7~6qqQ(RD(lZp;p8p|zdyDv zWnwu89#eIiNF&~P;^jH{6r3bjfV|R5KeoLDTt-0#Hnmns_dkAFc6r6#O6WsFAy)N7 zm=ib%b(B%EpUfU53NS38AXcr66gK^=z3i9%TO7UxRB%-mA{!9JO5-svlSnPTx{}i} zZo!0f?L4SKpAwjeqPfz)kG?&{s+@cw7Pcj-KA4FjzvfY=hgfz;!a4u3+x^HKffpJ1 z*M?$ed95HIBOGiOX9X%PWt+|fhe|ua2h!b5RIjP3g*#Hv=gTjKBj#TY|4KxsxTwmG zf!U+|-@7m-a0b*x5e8s`o|FRlXRd&~AfLGIAF#IFIt4Jyfe9COSQ-WMpsK#26^{-$ zFX%hhvX-@cKD`g(unLJPB$`UbLGt&s>v_7stJgl&^m9Qp`|i6OOXr0r)FVZA(}4N! zs&fFVZ+#J)Sc)a&9g$L(C*tnB*0^pyI!r;YQA9UaUWOMn3Q;49e{vC-@C;a&L>lp4 zr}%TJ7r0m0chZ^WRB}uGgdkTeo6=4^ee=$CHZ;1fg(dKOZ@~4rGsdCGJR+`SJBWZ#CE!oq3V4cUHrjKC~;)@9QJG_1lAsG2RL%pne$JW7s}bF($#cBY~? z>Q(W3d}-SSpOu4A<#$wz{SdT=Izx9z4Mf6AE|IF0q+g@P5)?DGgNTnwo2pX|guHX` zMCbDO=z%Ll1LX>=qAvE+qghoht*%F6RBN4Fx#o4ey$k@0{ZYGw6f9CJ;PC^vurgkB(>IehyGp_iH|8S0vb!em;G2T zrm=m=(;fGhj=QhK5-)i2i%z+4ZPRrfH0dD%2Gcaijka4*<=o&sJ)= zb@OWXT>Mx_lE*=BZpy^RN;UtHI<{JLU~%MMIO8i^ahPqW)BtxGptTi;d`YKagSKxl z)QlO7W#n|V0W4n(-X&jY&>@mOua!VkL}w3Llhk*_oeCNGJDC?x3^#K!XQNc~{K=>s z&Ff5823Nbq_HP=uRL+$D;V)a|hq+g0l}7IkguVZ?jY(MAEm4d49Zp^n53kt( z{9HRk4N)jK&CWlekch5OwJd|>nC;H1oOLvsSS|9Hv%p797!;3`d-z8JcxtUJ>Hh7s z^;UzD)j-+U3+9o_J2WGrPs(|&(X|cXifdw`aT~gHjVMoq?PV%BF{NYCG^j1`_##mv z_L@bC^%*P10No547X?54U)Vg8`( zI&kkb0?j_AZ7G}oH4JBi`(#aPCyHM~;l(Xw8F{$2fFr>gV7t{%+zFL72a;W?!T0rL?7CKFr zrvAJvgZQ@})cBJm8b8-Y+PqKgL-GQ|c;B47!HscY?>>+=TTm6fo4*{sU!(tX7JD$U z(60Mc;2~MpJL@jr#Cif`M8Xzf>^!Oc>iA-`2yC8iiglQ!CEE!slO9NRj9y9H_klY)hpD%cBsI*=47Aow$3=L-isKqJ#eLkOoNa<<>@# z>4u7h!JtDH&fRNDD33)CRQUxt?V(lR%AyHeD3#nelB@!7f77b1aD>G@^L2e=4PtM6 zE|S8cz(P!4-2+sdKf5idXcA{v<9{PO7m*#-7vHU+@X}2SQI{`&{zpRaf}s$BE{LSF zd2C8~H?t>3r)<%mHN_#@0T*nkHEtY_YX7I&?g}$yv5{y7F{G8jh&(r#Aoe}kZwEH< zb_3(Wd@z3;IrE6EW3?`q;YXe=IQjjuo+ut4@yB8?+iUIl^Kz+q)q8tIgc!-C(Q?fJ zg%>K>FAzHx#4{A%mSz>`3Jq%0xW3jEDtyqPzlFb3X?Mt05@$qJUOZtbhd4tere)U&_vBrkEu zhzVrFcB#s|??D9T6rQ@!@tu!f9>!*^>7iTai)u^ZcEUksT_U+9B+I=x9XRP1xUj)N z?8{cu(sQB0Owk#@Mj;ecV2eIc@j1`x;R}g4i&ka~-#+vnp7n?3_n$2ZPRVKi@Ig;a zd<%dZurvvHT2d9G0EQSax~wmf?T6(gnF(M0`q`lm8tqPf z(^gr%LA-31%ktfr$t-Z0t7}5J&zofr&(NUI4Aar$1HD#qjZ|6q$F41^Esxgo)*Rd@ z*WeUSV!S~CdD>MoB`eDi46HrhF&dC1$G$BPobLB&Gu(qq3i*1oq*=pSj3I3A9PJeZ zcT*1=&fr^2%t0h6@|FXVt$&*e+G2hq9JXJ-V{=9OyCI@KcA+{*q)s~we+l|gb+_%4 zBC74I-zWzx4zgP<^bP8pGAfb{s@@ylzoBKQhy13{#zY+qW=MK;s=G^}jIz$Ky_4j& z8uEstilDilt76>iw`dTrhOp|o2(m3Dw70l*OtkwZ_0@1AFCR8XhrIs#uQ)1e5fFC(7l^Q9&kraomwQ z9u}4-$MJ7<reBz+_9)4?AeBe&!=9p2Gpd+6~GuisC5ZVth$1ZR&5S)Ka(GRS*=qn( zRKbxiwA&<<-lMO66{VE$A5?4%(^FXU49{vtmpQ=}orG=#)Q!wsG0zjR zl$|5DY|qP-Ue5lzc3v+`67Bdl(=)KyHF*z+Xs{w6H@3KzZ)!S`uxd9l>A~K;WfBZt zIvRV%$3|haN_thjM1R6B++YRG4z4flZjuBxe$4D#YrVA9UK#4Wfk;Ctoo0zQc zz{<2tH4RBBDFdndsw(mI(7B$4|0${VdlT2OBkE@Q5W>@4kTEvCX+wGkI|ha$3Go05cT1$z;63Akn{4_K7B8+%Yr zb|+%~luvb31}43)osi+$;cT|K7-E}NIa{jUGfyRjl$!_0Pci$Xn6Pi*K1V%F-=GsI zq>E9cvvkGEqvB18s7VDJhH@<$!}^=V6}Y8pKD9oH;lzWtiz`7&hoU!Om371KTKzA# zBN~F{SLwq|Oes{@_6tdfhO*S;aL94h2i!q6dBDQsXnTT4QAu0<6b~>Xm3BHe>aiYA z{y0O1(I{@jB_%JFXXVrCeaDbi|z?>?B96HPnJd59QEU4l9B;0<{u5-{94CeJE>c}$_mCXcVwZFF>dCiM^ek5Je zkTtNaLKa6G1In(IV0i8lp`TZIGnu|Q^PnMgx9L(;Mv_su5y&5pi83`Ev$GufK|dwY z1V6r>;=|K(Lio?5ml+UMyR(OcSKCOI_3E_NO_f$J?g?UUT-I;bece{VSBrn`>B5J{%BsFZpa#CLC7}~yo~V#59fN`4D;$Sw$YAx)GdK< z{s2+ow&=8?g0~!HIF44xQi7iq2~=cN6lpJM7-3i+$M74@kzUE+)yW-XMuzfdoxpT&t z+Ux5MD5$_ehia-oMjZJF-F`M1I@ptD2D(2lBY-TevuP5rH4qMYk35p!sWTFQsA$g! z(yfgE|N9ec={o)xH)ABz#UME$8^t2L%Tf+H5trkw9KhbDZplCShtT-9 zWKn1XDTZ2-0f6IPxBFtGVTd0iDGAL~#odr8h*6a-QEU4wOg+jOuFcmV(quxv%wgL1 z)ZAu|$2nYbkob-V;1`KmUkg)t=Xck)JHT<|^5cbgW-U8Q=EwfIVirw~U)0zovZD&0 z0)9_6^@A}b7g~(5+--Ck`~~9O1Cqgk2_x{H{L>L>vCowZEh%m&z4(q#`_|swDJdw6#;KT(Uf&C$%pj zgXoM^)5jo6^(7zt$+*ZyN$N7B+kZYckv zrW*n2DXqnp9~w(6>HkwZ&=rv|HxBbw;Q%p4u&4ADCGRLrmH_(}CK#+XfD2^WH+^0l za+hkDntKv$w(cy$KyL7lvY4$ar{_1v$9h31DsTkXOl+i*yq2`h^!K`r?e;hPxZba4 zc)4lU$&iGA#3_Km7Xq!U_Uv((Et%p%mNR8K&gdW$ zct%*MD>@k=+`1-I8pJA=zj04-xzRwDP#QAYMEpqPn`%Pfjx|?o^$eyw;lTYC>i0j8 zDXlPda_-;rw7PqkB)g9R<2ewW1FoytLT#pG#?cX~yf)Y)qhUGiV8pa$(Ul=N{Jx`q z^8I|hk~Ny{LZA=I9d0hW^pvg-pc-oR99E@+`rLB zclVyWXHqg+hgxY$(>H!~*TeyL1R!lMlg|IJoBX4lfAcC>p-TP=s_=#y64VTEVU)Sq z)fED8btwNq!Pd90zeXy+dL({qfHg`yYhF2wG0?1C27n)E;lU|mM$%xNIb@}+?iK3T z8wcgPPLM2cX(ZI3-h)gmk@Q92CGsUd&6G0}5LeH?rs`KU>cpv=u7--VneJK_bVp>_ z8n=cAe;+KU7p-u|9`=9vx6BV1-tEmpx=s%E$i?rV=F9J$g=FMr$QXg{wZ~*~X=o7! zkL%j!c8N&R`f8q1HQ`+WTiEMCT4lV?4TKC?{>%U)kYwP(7(+aLQta<4Fc|qav~R{x z_3j3ywfR_c673%k=TkkYy$)4bYQq4K#qbVe@RAZ~GKQH`1Cmq`q@sopsR!{RB}G_~ zOp>f;6hI?B0$MjJ(U-d;DLkB~{ntq#)0=O| z)Vw#=cacjOc1#W5;3WSCOft)v6QA17?yE+S{%Mc3lcgSMjYEoOl{XLmOr;|EF!5Vp z_@7Q_3zT(9kb#AwPIrcQr_Z;5RX1&(1J*7+TEO3SM#_zw0J2LEAQg zIE|$>5@mCSx@1H2neeZ9+5#+x3(v9Gl230v1%k#chE~*u3+d5lK%rlnz3{q*i)S#e zW?ql^TnR|Bhe|fyr29=Bb{K7PK3%P{=%d!pFptP`O>8d{)}bdB(r=(Es`)xV=XG#t z3j3!1t*H1*qz8n#AFWToz=#w(PfhFSFcXS^VnE56P-$1xtLG%{1*i*AWG_RTKpt%i zb~vA;hp^x1EPV2~Dak@0k^4S$iN@Lmw(`QF$DlBUodz7{f8CG@A!z15Ui;Pd?iiuA z%fY7DjNyu@I7Bc944W4n8z+Hf%=9gVT8Ly!=+B*e5WDZz|^Bg1W>w`5R?NB3X%X0!z{6(lu!ucHua{5cK|oT}xHV%yO$AE>Ig0yXueptk>`~F)bRWJ# zexG%bkVF_5{`px_g|1~CNQ&`%o@wG5+6pPz3};X4&cRT2oz!U`AEI3OihM`koD zYg;TPQ=>(>a!A&QShIwq+5%lp2<9B9su)yi{7@0U%F1gFvj+p%K)_xDtC~BU*bMEp zD>pYve`*B|mW#;B1ZXP?4fQoPr(u-6Z!8teNdAOE6O5uiy7NHn51N=X_fnDOmn;Wr z4KN*p@`-w%2tB!8#^N{ju{2vaL|)sKU8_A#uFt{6HVt9GMBNm`LzU+qy)>0JH-8$g z?-6KrN-X|0e%9cPK1CUmNc}dV5x_D`ulZE6V)9 zu+jU1Y%d3eH9RsR>v}|*mz@R`>G{x*>N4ZLGls~koMw=(vE!$!``j3rj>0Gqln7&1 zzgCS;8H#dANk_5(^R4-f12fFj+Nj#3w4zjJelgg>wyHU_<7Cs;sCl62Z0tieTZ{Zo z^b<_?ba69$ywB5k*zM!fMio-cJU>BzDKDE$35p6i{6LR;6{sLk5I@Io<2Z~z$a}{C z2A@Xjw2h3c>0G3?lMg)9h)q|8iyFOAkWO#A`;Pf5;xYJE9u(jZs&NfN2ydE1kFn75YpD)Ak|ZsyowSwRB;kd*nH*SvrU?ed#rs@Qg{uzJbp|D)_i6_e+u0Y~h`LQb+ji}c+zq%V$keF4i_Z;z0-MSON*8aM zt%UOsh@ktroe<>Hr<1_%i5Y`sjSprwW*vlBNEM=Lb_(`!MElwy!9JVg!i&ukjXgKgyJZE|VUyTXU! zg5Abb>i7dksnz!WwIpWj0!k>_wT56D9baUhE;hOUM(C5_xZJ)X9-Tp~`*~<#*ldYk zM@!FU91I@_)_<#(Kb}+u%*T)Xv#9mrfo(r+s-e!>o!j~J{jJdLcFamG;h#dDgYJl( zk@++)yX0pdjKQXf4b3+f&vQT$@!tCVO%%$nDhRMguu$KxvnT`@^V=pA0$!_wVQrX) zOBO#aolCTAAOlIPYbEKh@bc5^d0$wd(2B#zW_8tMPyA8Pg&r=FmJ^r|Is=4q4v{ulBLub$88h zJOIV1n(oHHIV1x_gKM87Kof`M;&M`gIlu`TD@~$@c30ul@CrK}PPt(ZYdrkld6@Xg z4l0KC#W9*$WkG7cgY3X>HXkEq?toQmRaC~@s9pi*kuIOe;LHa`}yQPyxi?6$dT{T2jj} z$G2QaCD3YK&fR)R)_)gWg)?{1CbQ;#;LO#RwF?7#*(Zw2{ys;S0Ou|_l0AJB9;C|? Wv8v;8okZxWukCeDH?jWL?&LKD4%oT? diff --git a/manta-parameters/data/pay/testnet/parameters/viewing-key-derivation-function.dat b/manta-parameters/data/pay/testnet/parameters/viewing-key-derivation-function.dat new file mode 100644 index 0000000000000000000000000000000000000000..2bc256aa6cce0b04a4734d6838928ba020116ef4 GIT binary patch literal 6368 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%0A@;3sEzLc9Q@eqc-1Z$pl4gT%jO4J;!2mM1 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 diff --git a/manta-parameters/data/pay/testnet/verifying/private-transfer.dat b/manta-parameters/data/pay/testnet/verifying/private-transfer.dat index d084afff816e66bfb17aeaf486b59455a347256a..9ec6b8e2f5edc94552808c2e23dad7f0b9e44f7c 100644 GIT binary patch literal 36442 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�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 G8W#XoEpsaX diff --git a/manta-parameters/data/pay/testnet/verifying/reclaim.dat b/manta-parameters/data/pay/testnet/verifying/reclaim.dat deleted file mode 100644 index b048c0410d1667b172e972a47d748fbc9ce01e2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40490 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%0A{x!ccpMr! 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℘wm~VG{8KjMCNsGp0vO1-N|tu@H0gA%z3hsR&Ib zctJcQa?iqps{C~3T4p)!&1NkHD0?}1SN4xSCx?UYqcE-%W)CF1;VF^Y1s3@?0?m5$ z`Gbd+rP$nM+?GGtH$YA&D)2dTQj?YDso-)3?{XA6LdG;GBfn+7Rg@)^l(`fL4Kz4- zF4TF?Oy=dlyvnzXXx5$fqZ-VGax|BbO%tOlJI2J#Cyd^v>3G}01eZM{8r%j{*^cmN zyuyb6WNxPn>!+j6;NS=^dDEXD)f*Fcp9o;zEJFYnQwo{`P!6gmsKBRadG4NMRWGC) z>WBQOziK@XJs1$0&%VCHz!?`0%~`WbKlMh+eK;gj!^flf7ZKF;s2l%*OE@hCv$P>B z04c9TLMD2$oW_-1BQFkJ=n@tkakliKlW)0&y`)GCJ>bsq@(iALVVnbF0G_3`U!^_X zNUiaOd-dNHaG%{CcjfA{7JIFPAW@RJO=zvoGYD(e1UrXYVx3D0-CfifO(oc0P)7LW zGAv2L{`{tJ97=4hKP7QY_B9ij1j!v1Xdiz+CC#t7XHf&w^uLi7DqIPli1`cP>_tHo zq~Dqr2TiPRsNajB@!0Fc3*nMN@yU(o13Gf`m|ieD`m-4fh~9g2L5j=Ba~h`5s*Z=t z(VbWRwf^xPxyN$lL<>$BR}l~QkZzbo1r?oawL5l|-sbkXOA*gN6rJjF=_)xH#UE-R z=j7R`k3@Fjtf@k*6}yjRRn2xy)?(QuG2}iOXSI^WT+1X5dg4bq)iRz=G03{W$!KbI zv5Bh_=$FA1rf#2eQMI_hJM{QC*IWfVF5I3T@tj=89NBa<*H1?)m*slEJGf6XjY&&_ z5Hyuw&^;L?zaWQKg@-XlxcGJpTws8${<03u1#IFXP~omQEsVbiF9bL^SnZSbLbSiQsyO#b#I{a+fLus)w@ zA^DAbq3Fw5!!{M3K}p(@J5f;hEmR~cURUQ7XCqmW(^`3chJhD^R6VUA{0t6@ zck5SS_bfjk=+mNGe#{!=sL>7Wu&3oS*e`G>_}SD~zC z&u7uBZ9oVq0VUY#Du4Hk$P*w)NwPk5SSS(8{<|J0Oj-E{hU%jfrgR@A8u#So6e$I2 zZfKYQpsmQBVg!3sSLjN*2Y{yujBPxT-4YI7%B+Kr0T!qk2TQWQ|3wgaEn`^rzCIoq zIzdlr7=+qnt%!`@60=j((eio-+(ub!vD}qhny88dj-@?HAe(cIzDvL$K zh`Ti&uVaW6h>w}ze7}F49~_Eml1p77EFqO9HS3$i-rg5}RIkLt~>5WuH0cWtbmbE+J1j;~z=48yS{DgDT!nH*<{ z5`iV`H5A$Ut&@A_ql9J>zK* zL1zAGQblynU(SWTyEY_Bv2c}C){M(W7>HXK)YqID&g*KWp5}2(B#_UBRtLWF(UxdE z#Y<%EA~3TGIoF~M09yb6000000DHxcD=`LQmkEj@hri-SH?IPQK?5t)izK(5HRt8f z3b0K8?!VNQo3N)kCm1L03_$a1)$3Frnzv(@QjU)Z1d{gXkyw<)Sa`?RTTnD!AGFU> zhD2t-5d_2HLk#5349ns0KiPJrmA&ZzdJyh-$pY-Ne3tYNit9HtqEije2=cPj8@?qO zV}P{XGi$Er&r)eP2{$U$*3L)QkRE@iFC?M3iU1ZdhD5WwqxI8Y(&pCA_9v@ler*M* zcD;cP6qu*14)V|iTs?22+B%{waiw?o+g2GF66k5dGGcQG5O zHmXaz=FQH-9*lVe-obH2L{p4S@!I(+z!VCe+0Fif*e)p+l3}U7%{zXQE8AxZnitm1>8GLL#GfEI_ep!ygwL zI^Ux0%4TDHc{LEKQ-C+n?LAzGg?>PuW)$wP#vI*6hES^FJeifKlIG3BNph z7>K*QV0##O7HtK?Nlf_MUAWd4XLs-*hpWl--H)!BuEv=ym_a|>{6Eq$63 zdjgJ`t=&WiS))6uUV_`#&xCJ!U8TieH<6s_1uWryQ;A%d)pvo9?`>0icE`f+9!}## zT$o~HJbunwDW&{o?Ss}R_;>&J>lPcx&3}Sz_%6l!W7>$Vmk-=bDKU!0IG74IaZKLG zcF*;uDjN#5?EvnQ3WBg|O31@+2!?Y6_PsH@-RBl-Mk^%dz2l5*G4KWSOa{F$6WJw! z5wZ>fVs!+Ca=nn17ObSl(|pdFOW!fer7G2!7dQKG2xgEJ5;M(fV#9E^_*o;y^X20%0yTNM{ zBcoXa0jwytpv`8$C@}QqggPlAPbzZ`d-eJoF#|~s(&fAZ7cp<5rFsxxoDpmD*g)cF zRU@f1om&VEy*9u&V^~1q8PczxHjYKG6JVbmIG;oXO_dx789>&ntDqMOITY^18bnu= zswxCbz(XB9E~A$(`t0v5O}ms$mNoOy#Pi__ac2{F zjOstmlWu!R1;G}Fx5P-Y663h(R=8o0T+cE$)3TXl+y3kGe_`uBB=rZ)C2_%qa+5+j z{HLy7#!RX?=#IC>Y=ijDM#Kv~3E(Fv4kv2T0h|i%2ulP@vO`O8wUCazyVg&WH3^f= zAfIW%Y7y2o4dZJ>DRv(1`e(~me!v`YDn_jqeu4Fg2q$CU*Ja7tr)i>eQ=VKA`!U$M z3k_6l(j&1-`RT{@0C`2u!d(9M&V3|x$-g4ht9UNsCU1;c7Fk6KiN1D{B)U$oOW_VS z1*S^dk-v`uhUTBV-LSYtB+>r!mR05H03P^_XX~+D5!g9^Ll1dZ+J87Geqf!F5eA|G zfQ6rEVO5!^=4sWHmk!q(P|iv&D5N$#V*UVip_VQ?A?b+P ze;!-*+$Mj5^n5%}yV^}>$_p`Fe8MW;IyKfD4{6s-AtNa1#04hDm7+y#hlFdv#=Icb z?234C1G1tW7@(2!(Cz1$+;G!H@Z!gYd7cb-qN{|C05J3hUU%D3p4==MrgvOs5|o0>)ih4bOVqo{E&K!Zhu>8X57qdgJe&(*IeRU) z&$SW9XFUAl-TY17BCih;ea&OKaL0Ec(g}h$4bFV*EQFJ+qqC%Mhc;Yq4|XFiZR9o3 z33oBX)7Rk7b|1M+z; za72fw1>Cyf*|%(F*<5ISNIU54qB|!v3g?^<`?ud0nlWE%BHFeXSOzA7*OukJ?o+&1 z7Krvp5ICbdPDoo3IavOEE0OQYlRG=mQLmOE&Xk!&EO+SOkK0PT=P&LGGO^fA5$l2o zaHfER&b>c48TN%Q=9U8&fl<{l%-(5tDKdfV2k+1yu?VnDCnUB*07NsNleh76@QYx= z+uHk}$d3&s7|zCR%#2wrz4^l;x>3N9L-C;!9mp~tHNlCw(tU670_<5lZ9Z-8|Jw&z z;jsN<;v=E2O^?IV9{nBNaFaI%EZYB17rWyKo-{|(zDueEsOFe`a?A~-(mEKCy3a4V zB5x1uI*Ls5Me(c`QQAb!vnL!|RZH+(bXF)X>%Q$C91hZrbs=-6=3sQ)jmxuA*WAPb z%?;4w*nqlq=o8=DD#gDH;~o05<%WDJ1?q{xmKcvxzDDN@7v3o6SKC_rC>rd{I=DJX zy?^+1G|AvTLJ~2x7rI^KBPrnKlT$4x7{X7!zXNBV6D7jYg^@{rCb*Ccbfu%n5~C>( zPioyE1nkrl)f5vnu61Rl7rsP`5}dr*&4v`;d0fb}5oQto9Rf7BhMQbMH<<5uxd0?5u9_%3b`-{bE#$oRK6H~F1X^{i%F3p zWbQ1q7iZ2FPS74Jd)z?kpc}Mt_gXE~|buL#TFQ>0Ieu z;oowgPvUgBmme2UO`9ZJCOs%G2A!8!o#l(g9H=fAF^k{$I8ncHa-#kf7ikcx9d^af z6)VnKPe@muH+y_Pyv`w_vqfd7`X84_%Sm)_AcJs)3$_nJIQH}32s%f|6L-!Tf#ZsJ zIJB4R&ntNmVO9c4As_JA0nLJfvSa({ph6<%sA2OHkEcZ7TS5C z-91=p(fO_P%b^&vF{cSFH8$R%v{u0QBJ`&36D;`h)ziW_*dVGg@_@w^v?DIef1J(f-aWCegehmN3gFDp`c)U|JUcA~|pk4Nw!gn!D0{L-cL~ z6#+@{$7e8?p1zAPg*OCh8%oEIZVZw-6KapPbHv)(8i7;wjmFI$IrE`U6DO|^4v!ER zj>MF8JHo<-jvNvNA1P-UbzPPw;s(pSimGBdMWHt{RRsRfTWQ77a%3fj8IL6(-x#MF z^LR29c(cB!`{l*0zkTkg*$LsN@Z7=c3oL9OCFvCK0|UFL$R@`FP%eu+E?b_(S>S6u z*Pg7~Aa%VfLg(cUC25z>x3Kiep zDuYhn3e^TQ$KEr}`)CzkoX;X*fh%Bf)x zYD5{ImZ%dX1hR4(ADXQwSI|9cFBI>#CU6ilA~xoqTBpAwF0~S9jLq(_3Vs%EaSg-i zBcx};@HFhRqNG|J?GO|;cBnj5rAbPH)Aw>)UCwmpq8JCIr{8_ua7f zKAArXaZarkt=K`u6#a;|tsl3UGI>qm1X4X76xAW<8vsJuaIVr=Qc0XMEV@N|N}>1U z7^fuZr(V321>Hoxrd@M+C&?@sAOD?MBKg#oW^meBZ7sbNlJc)tjtVdSF~@Xbeyw1M zPX}GB77jv57>f|tj8~09(R;Ymkcsr2TsaZ}_4T}}GAJK27COu)0-NgoXB5 z!B;|9&UK-a5$nw|T${H>nt6vyw_&HpyZaO()!DGq(GiPj(~DyTEK>fvd58(t-CMrN z%Orf>Drtfj$@)8Q**+$943{uo4Z69@M-9d zAl3~+_SF#d_z2~d8}Q=frTGAC@Hf{O28eY5JFt8|1xUMNGIWnyj_~(>K`bnpy#B*p zJ}iiIBNzFM*<-MLD_S+EnY6OvkC+wRabBXH0(KFsIZG>X_U92al5ri90vWLIU0SH| zgeI%({WVeIkHj00Q``{b{$RastY&zoCE$6)TquhNnDtOJsXbXOzA%q!;r+R(>y^Pz z(j^*$81xmLC-^#-Y80ev!hmxD6A)S03gEu>+N_<17`KIyCJd`e_>X*aK3V4OLu&8c zLzJy`_QZ6_N8V!qOe|Wb1h)zF9^1D@C9BC9X<=Pmbt3HxY$i8k7-P2n?eJaBgd9ODMv(RBk4lL5X*7{b7pu+Hnjrljqm)5?T zf%uiGZ=f-}vWR8?Azm0c-xm4b$z$A%!fr3B3;4r;uYUHB*bc z0g6hwaCsH0hY7^q=_hB%^MYl7+8-AgL2Kc7#!d!S6yj#?jJ2} z8?2*VN>eOEduos?&5ucz-n){C7Wx%1UJxOn8QQ`MLegUIHB*ky<5S|NYn6W83_ql4 z3_~Ie@Xv$JqOuG88V9H$>Nb+m@i^T-eQJOZ*fw$>2FWWB$~OL4($bv|&~G7GYk|7V zEjTsFUcmk?@Nk!(Acx$JVe9djOMaiQZIM*Hn;EjQD@LFmY5%Dg^Z*QC7b9#vCWc|F zu5d7w8ZR_n8J`vpne}CSI*!5)fIt;36C53Z>0#wQ47ybEFZjhRy%;jwLmvg1x_@P1uG?%nanTisY>Tu zcHy(a{@_}eT`Q>dWLl}sDXrjrYf`jLH3g7YxH{L#$81I_=Y^g5*N?KC6^l7!0G-Eq z0D@q~IqztJ)P2R^fJW*`I3ZO3x(8K&SLF1SFKz(J6@a;Ds7?(WmcbB}(i!seLj6Gj zy2nW)p2z&21v%>D%u~wmMGA_$tnzZV$8q3zRHnT}0EJfK2ffMhPQQc2G>zN( zfl*>u%u~M_B)aK%F$C0S6xqfg1c4*}HB^_>%ExobT5nVA{q;)N^(*mJbsI&~x{QZ% zB_!4yBwK#4s^h;9=6zo`00PeCPIq8d=I)XE(YX~SFOe=f`tek(Zf|wFH7gk4;x{1*?nANW8_(%g%a1kb znJ@XXyfB$O0A92jkqY$Z+BNJKN!LZY-fXvCvF!+QF zhAmv%QS5k&MVfAGdbwESB=d$OL3wO5F|00z!+ao0x)a=2-gB-qO@KwWopi|9BHam( z8Ew8u4R&KXJAppA^w};K6Nox)i|W7nqrij76yNE0p(u(a&+q(5B~cq+lT~xk*HE{b z0xSK*Ox8v79FTQ1XRbb8W0HEX(CWMCBR^K1Jo}ZTF;x^r)eNlJE~@wYG{|0wsS*`G zYqRQ9nuM1l^ioAwPfh-wp-7b}77Pp&SH(Ia)O}XU$hk5n%C_=H~(Iq1lsEN!|f`FV7A0IlLV+_jG})Y8rsp91#47xdL}ExbYN_ z1K8Lz82)#0m3%%ra~KYP#p+QG1v~>dVIO6_jhJow4H~$79dkL6hBhYQ@mWD1wKlwa zVoeJFpbEu|@ih55{og3>FNwrLAa5mg4?}#&))JP1`{`DW1`YCCTbWG_|47H15m+r& zLOO}@Vqo#l9*^7#@(8|P*d@pAqxJKUg&@bY3aRphLOVK-c~ry*{I}nxo0S1IKm}9S z0Pm&XwQP|O1rw+7J?SnPEgLI#z4;k;aSo#2_cL8l&*MtfUe=QtFoy`#&#>}+ib2Z7 zCT~D92t*^UfVJ9Dh}n>0oHt!461929iHo|OBdBb5%sxow@(C}eW)|T zl(;ag8X-#R4v}8eDYJ3b=)j>-7FVDbpuX8gsF4g`DVYcnNY6nKND%}}3R#p3CGPC0 z_();b`fi%E=C@ZJe$x&px9db&`r+M{D+4o%Gqh~ReyPV&Ed>G4DBJXm$vGpS7JA(t}snSdqMln8lG8}9KA~dHE+b`m)BpaThL~GwbK93wk9d{L)~s~4k@t~ zVuw(F3VAt(*u}&){SP3upUKmey_8%c(e40N80+|U77h0Z6Mopcdm>AC4OTw(*;ckwSdA7 z>M7<7K0I2@dP>+4p)_<~-sbTCC4PnwTFrQl(eSteydt){Y_FwGc%iveVit!WL5pDl z9kf~0Mq7rLjZO9XWd)$zZJvTrA`*Dxa0{)nU?N|_9u&Z&t%Sl8FwdyXZbGU+h417Y zRuOjU%=v<~x&X3rEwr&pyGcK8=7-bY0W}~8>?dt3tdP%?Tnh4?ndT^mp5vFG zXJA#V6YK$#hQlhWu_AapLe4r_Y+^BlkkAn{2oF0a71xzIJ}X&WnyvKEeX-=;|C6kJO6HP!gwjWW`77L=!T zuz}R&@gTw6J$Adl=EUhzpSy>`B;lWFM669`CoaCU7Qxs+WS^mfRviCnwDN#<+BBCt zOi=%>9df-A5Hc!z^v6lprMJYN6%@u=v*h(YERe4+^%0XPC6WB=AeqxAd5g5KqOya- zABJQuL>*ElTtSfpkJ)UODW0^vD%Bfhm}8W;s#*bY+G5(62+r^&B0>;G6Za_S$VPMT z72~zBzhZE3P24C>geML_dp88CkOE9e@F>tUqghH%AxGQ}O*`dBxrr%cJ!Ev0J&96G zt;D-EvOdm7a4X;V15#BKZ&aaJ7+#2$^?iDvps|{Q{UCGr?MD!_(-V+faLLm z^jSMuBS+qee~aN@oIgcZuF~SZr9ubaCLlg!Ito8F5)9($pj zpBoU?G9%JFn@drPYBBu*iUY7n?qqgH#&jkx28t6X6yiP2eH5S*VU3^BdPx`IfpGnwCk3>gDvgCdHUd|B@W-;+-|ILSiPjPw9{3Ha(0IY}&u8A` z128f4$cO22(*|5YsEvderucwm)iPIAZWBOtK|OMn3n8r>JSnBy$0UAruGUhIsTd2R zmhhk|n`@v=Pj)XV31ZMsusqWw$%GK$D&LCP)7smYDgp}UD7I@GZggSU1=7d0AZPL? zX`KjA8(*YSdY;x%z>hX4;PvlC%Iak>5PER!^4_EF`s_h^k>=~$;Yr^pq@u2XvJaq( z>*L4*CfqTWj_$BkbthfOq#zdH^>Q=J!PjW!$CeGd@rH+gC~ex@F~pQ%0%a!eK;0v$ z1G-0hOD8o7!o`L%{&3LU2;d(!3Ip@|P({Z)KL zY+0!gym6!8Bo2HyQFy&LsrC;eft%XtV2iB;;Ei=Nq;z84q6Sn42VAacpP9S_HsQ{A zs3Ec(9O5o5VG0e&tDG;_#yX4o00zqBjVr}d?VguG9=mBW0ML!F#y%{@{ z1Ry=A&RQ*v`eo}bD&pU`0~FG8!UGcnS*1iH^dC$ft&^qN=fBMCAElfR4?!I>>ll-6 z(PLA=V*KC;mz0t@Af%8p*kAyq!vi}@4XAE#Tj)d19EKOWAZq|-kv$}cxqQD_bzn+1 z&YQh_3~ZtJ6{)^kpo~iIH1o-SsibrxHRSEls2-p#A&d3^1Bk?E_}SrYA9FXggI)3` zf}jXT!;2qXv)-;;?~jc31B4?M&T`3B77?s5?_Is=@q7;kRZ^g8vLMJeb_neKFNbKe z&NtRv!4_0`W-F?3L>XYpU#h1PJuu(C0Qz-QBv%hFa-19bGMf7Mb}C=y7%k1w9!bZR}YLub<(E&Ea2 z?2fkkS6xLadE)$U9{r|~F5Ie2D|?BB;enc<be=&SY=tP^^03;`k1HCSk1066U$)PDV22VO6r^H)jn<|7y z9d1QzFKMd5m%|`8`IK>qQFq60@OBh^_*vdLbZiRxp{3W$BnCR6eZorcJO3QL8Xh9V zsV%7OD)WoIVk>H$4R)q`6oOXs|A+s6K3JO-&)7Xv38o;m1^r#u_;+2=+N81~0CXSQ zCUV8q;0dUyl1bcGtkDAOa-s?SIa4$u9l*xt398} z3|K<^N*zs1!qpRcDe7b?wrFIY7{3NKG{YsNUS>X5xg;Or88)Wjn0XF3Yr;I%GCIwDs3^W-{^lM39 zB3D#^uc>$Z6Pg_R2yW25xV<2r_mgwXZoA(rC=HPH1vAYoDK$^Es$Af@9Jg=!>ZbUJ z%Q%}UdFXs?h-|)u*aXqN+?G6#T zDHr4j7$7cJ@cNt{Uvapw5Ez}$4%Ksn*PE2ApezNUrMWIPwi&Twz^+ER2!%}r?;@n$;JLo2D@TuaJIjlradi?K9Tt2yNKcUh>-jm zN!0I1^K4Ts7{EH*WM()?h8JtIl0MNrMmRAX2mGIRQS_RL!qoo16E3(Cx&8=CLLKLv9KJMn zEHOCZ_8Md;b9KLx%*hLLKDkl2EKU#wkU&kq^YMt(5hQwoDq~fNgpAR2$Tl}Fqe3xi z0?W`>mnRwTaw(H);YJw}O{eM(mT)$K&hrum0ba+q8tu z!urIn5NP?_j^TP}Q6r@rV)0UE2>%WoX-WH@`9>fA^+;^CA~T|&T5~parHV|uaysPs z@p>(m|D2AS60joad=M?I6)uNMc&vM;iFu#lbI@mBrVNSIR7hk&Ez&p1xMx)-N$*B0aNz_A%u!&W=4GZ47gwY9zsYa7~2x zBBDPtpaII6FXWqHQ05i|^E13rRj-c16S#j)K5syX>f_y~&#jFz@HNp9-r5(S8V`B<)3P8kk0e@{%eI)#|dT_{8XS z`gQi)r5__5tsq#|H#{o49@S<6wsF9tuUg`{$lw_bzI{cY2augGpCz&qANPAf3>$-B zs&+q`Dm1`j`-eP@*F-u2QpOMY%IQK7dhN#q6)lG34BabXvZNpuZqDo{b1E$IBo^Ay z%8a4; zIvhENckxbqTl`r3|RB1_m&;DJx z12LGP@GkBOBBSbMnc;&Q9C{V7Svb2Xzg<9 zii@>8hx1*?seqO(QfR88Ov-@75>|SH3f+()J(mCL*s)=W^|`_NkFLe}nZ+N4t=Cqf zYvuRRAq0=gGpWcy%hkZbyGBu&#Bv<1`I z;2t{SG};*0C>`HrJvh$A+@yo*N(lvCWOlu zqfm$1_4jpIPtMa7daf)BFi2OR6FNZ+l0g{@EuM>aFo|xO;$Q{CEXz*NF}14;D-i=O z_c6YBpw(d$6p01$d9!%G+S3;luM@7R36%D#s1|aw`@X7vjvh#R1|xDFkN-lXWMCYe?FEjQ?bkHAE@3@*qpzHRBgmIU?a&Si-`pZ!m2@h#sX z2UDdgo4)8h zof^k|of&8U!LNUj1zC(X-vMU?h5=}v8uIfZYDqFje%QVJj}ARJD>un6B|w?p!nGQMA(0kezbOFsaXA%@y|_tgnF_)%ykal44R%$ zTI+^DvdzzzOG(37Sx^~JXcnJa&jHld0#BQ*5?X`1VpJ1c@$CyasYwKcESXOjqey(h z(t`}VAdmio7Bz~De{$?`3-)NJ6D`Tbk@ev@lhLE68VYJ_Q^A}x9ng99r3L7)PsyXM za?CEbA1_f6lFh2LAfOkYC^+AR9k}9olTy@!akgeBpZarnjxkswAJ>N`XoW(;Mne^y zAZb?98-+vN06*L#oc6X+)4ZHF1aG^NpJx^YB~wE;5!2Ci8VABlUodZ(fHIHYG;a&% z29Tdq{u#~iM`HCO8B)FFI{;4rSR)wbaZL61Vmg~`7i`@DcS)5{tepo=0^kArTjUK@ z`1muXN<<3xrR1yQcshP$DW6Od#irQQBl>7Lk|48HRw2P3X~(k9jyl^iF!ng-IK{nF znA^3oB|HZ3^x%}!C*WJ7teQ(dsR*f8F|2kbei)jL$ycM)01ZAJ(y8DegEmw3i3m1J z8UMmgm(7$l%kCVl(eQ&R7Mgvh`5#UH>s(dkTTTkcg%w!~4;;XiG4UnkO#b3dCWN8A z0k)tuub%<$D~k6IJd{dn?!GEU**unIyP$>(CqEg%A=gA(yY~prw_x8Y=wFb`8iZ%5 zW5W=u>(0ZgB25S6>7N^fXzV7H8RB~F=(BU>ojG-rCrWCcI`mm3CkEwUgY#2-tm}}01`)D9 zDeU9MU}`qG7>@j^v52e^(mtyhkE*@Jp#&wrfqMdu0HmY2meffV2l3m6;J^a}2#!O3 zEcqqa?-T4QMF!R;0OSW(NQ;ng;$?(_$yv^=?j}dnKuJwSUxMNld5G1U1s?RmH|&$= zX53d4?>aNfExhXILC+*yoBY52c1dag0f_L}y|7eo7YGSkL;|G=`)Q$z4ZQK%f)A1< z*`<_uCe*7{=vm6ns2>*nUEPDmLe`Hs6h{9RSSYf86`iLm2~epe-jZH9CrfevP9FSI zDEU$OaVVhs8cP57#-G-12C2>3tC2ipCbV!hn&$N0HneBKE$U~Jw9}CD`@?%085*fZt{(MQqQl>ed7p?n=}IM%Zh1 zv?Fp7z<&6o4HPB7j;PNq;c+Mrj-vyC?coKn5PNz3MCuy>kPDd0GaL9DcEY z29&AhBdBv4U6R-%j*u&gPQGN_db4r2NMoReL<3o-Gz)iL_nqQ-3{$aUX5zjB5~#JJ9JQqi6q#I4LZ z3={Rk*hI|n+Jc!w&OoNcMGSHkJ$#}0RWF6e_S=wXDh_e3a!0NR=XTAwMB-$8yY0ac z)F$731^Q{vb>UMQ5~kxkwUz~G>SXV;c2{k{mK>88r#L%1MztaXbQ4CL6hmmT`v@g= z&ydZKXaMzf6~zYRe~|cU3%3DpiH8JJBc`<>HEr&w1#rHl-HP6Ivxc2{ka7OXR09QB zf|{CjE%w5P`%4ejdf(>PeaRNFK#x{jNgdD~VV^|o4P=l^F8*v&?#Lh?1f9Tz;oy66IU-Z*WzGocP=U!Dk-8- zIYnwSD_2{`^@KLHA{FgY@{R<2FLiMEP^h2S9j_MaHSeQ_j(E;t`7O`YEWsdX13*my zz;z~gx!d0r1$cwff@;NmE z3z9AKkqIAWS?4Xn6orUCd(?X6Lf@e$an!$wB28vWCD;D>Pps+GLd!?=$t((ETSD?d z?+G2Kf&7~5;-;x+B|n~2NWvKVBO8K+16fQdGaPQSlfmDAp$3~SbW{`}Fzf?l{co9p zsjM!89q|1jvei9UgYt8`_KXKTL>0VuEXl}Oo@mXk+I8HoiU_ANuv|U82km?djJ&uc zOkXv>AzSbAV}GBhnebU`Qd-9u_dQb9bjw0cG8eH*mRE8fA^w9L}w} z<5@=8=GA+x5%kE}5jP9bWSUS;u9vKY-<`S(YHt*oPyC{6ig9fq0$8H7Jjc=2_Eib` zz?DRn1~(x2gCY`f(VtO326yX^6=)`RwRver%^V+*gIa(&!HijS@+jH{jcR00xixlb z8KRPxfRRx(@Ag;b^FTbxIkaioEJubI&gPLbfRIP62mMk0p9Oa^FN_aFT#=$$R>)42 z#kgwWd*542x<~8^E`tdmB_?3HgGxO)nwokw8IOfldN}-atZ7W@f^R+t5U0TgL;Ig{ z5h9@1HNu<;X}N3`b-r9-%fN{91OBa%7P39WkEzsGn(TsoHz^>qe#ke*SAy@6K_7Qz z(X!=d5@PVpT1RYx18JAA>hLEv`+K|})Pk|&H9J$J8B~GR5H7B^(jc*Buf7eKr6+Wx z@U#@RAhzQ08L98xVsOlBD+mlg=6h^b`-8V12^Wa|Gmi+fI<2O$*#PviE&);wDWqi% zhG{rwXC|0Dj}2;m3K->~WXXSGStJpHlJfH%411ArNSt@PKK`LGT)N7EWZ~!T7ckS? zk#NM5mdfW#B`e9~;vh=|R_R})jr2_GOHSa}pK`u9wn3#2N|JoEA!tu?>ZPelknr(k zguH(skx^j{XQS8rZOPzKgeYRv0n zEpn?)Id!n!^R)tph;)?zV1kwC&ui_Q<`h?*dNdds6e7qEfCc#0y+7>yC%mQvZHID` zYsnuH(eI^;t({LZ6|>;(#O)(9bEU(l+S)nPw1G@kqZE`Qq@T4n8^zic0M{Z-Tc94= z81453UZ{PrH9-aq@!m@lS)Tn3Q8Bq@4%`5r&xd?+g^@Z(a^2(KrWBgCydvkiJGB(@ zVVL`DEQX>lzoIx`cxshr z3QFKF#i58*9qxmbL@)MHEbeej82;mIE(;qge=!_twMs~ND(8&GuDFvwPD(%`6u7$G z(;udLlRtRw<1(k5v~!v&Z9gA#M*=EZv<}(V0MXTtE-5BNDX^fX%z@U*1VpU%#hQdC z*(Z~zIOh2v09d$F<9ZZG?dWmA$_q|BmF*DG`e#3jf)dg zh9AZXj9lg02%Q+Fw#6X@EQt?f1M|pAdu7uz(JWkjw4s*(^hN>nkLwo-{`B^uYflTY zDBtCkK{k;N(&oQ|^PmchZPkIA#PYiEHYwVY4HFVx5n(_U(d<%Fs1D!^Vn>=eq@4}V zfoaF&901lDa!Oyn66db=^9p&E>^7MIq}mTpmno{EB&{*8A!m?~#|qBI3s(Aftr$rH zmHnI^ik^XJxW;8I*?Q^WKyZH+Lc!Fk2T$BwulTW85M6auS{9YnE=WnYrq7Im))n~~ zAIFb16miOMaw$jQuZO0>-6>X{!O^oc^#8+l=KvwR3r#6d2Bsb=kP2Q(CaPO#(res> zFL}Ll^Jol;GkEK8N9fR$4;Q_mRfZThA}!ove-~tlm=Ev)V?7L09Kd+n;$+#qCKeH2 z312m5sD2PxJXoi+_XxYkf-polTojE;Zn{QI2rFEkGC;}}Ej}NpTTb1NdQ&m*f7Ca2 zu2Qi}MyVZ72xG8tE<01#L&8@i9UrJQY~fOObU$vpwjz@s+TLy)8f)Ey#Ekn;I2TSY z&7CP+4uGQ$Hne}C0e6I#`Q@R+2kv)eA>+xJrz?m{VUfqW>w3WxPgwW9LhRPBbn1)2w;%-&dFZ>75xBG9Yf?l1^n zmf(3)_z9RXN)uckE;d?ZjULLKU(E|{YhJZ)v^Gvq)9E1dv;IZ#rXm822KT66u0R25 zLhu;HKMpmTNl&5@v)5pXGT!g0@dze`0{a$HMNc2^T<0H3_dd3sBm%>KtmZURhz9e2 z>+7IL0YVJD^A*WC#KB!NcsR))7noMD{7MObCT@YESZU9wegjdC^AT+@V=tfbe=icE z6Mlw!n11WBwq>EMb-r}&ew$t>)F>2j4xsxxvKXm`e0#L$sQq3)^0Tv5)733T8XA>3>z!3&{0MELuuGd}Q&-Wn?wIqk2S%VB!hcZb$rF>I#XNrm zVjT%DJ1(mb1e|%Xk3tMj)iO;h9qiwhBB&8O$+JR@E*Q+3G}D@uHG(hlVW9nR|KhcM zcnK)rq`DSBUH{)Biw%=^EpEl>8S?7a2@1@(hks=6S|Ibn>m-ux=)xH(9S2z`8c}CG zaT_Osfklu_doa!9ntt=&F_)+X;4`nopaObZNim$6+2TiqhTwYyS~>5Bw;fss6$qc1 zl4v1w{U^x`&rX1ZRz^eJEBzSToIgR?q^-RwwRIZxrMB!dVFT#>GI#1^d05|RSy9rH z!xmMi{B`|?J7cK63xF?Qkpp?)Y7&IWlxIM?Mb2i0Gjqm-Ty6`LM}N=ciL+i#fFMQT zkE|b3(qy=(D7Z#^i0P5rRyX>2L+POW_<&33tD*!~hN3f+Gmyzd38oWpVak zxVmt(pNwMWF$Ujz50h~jBoFd3?4|;7=0HM#7}jbcZ}nNhU*}3uN+w8aIr>4=HZ1GI zLt44{X@{^%*{Kg^_*=4t_;3-lR}!KEWb!vbst606)8S%hRXMM<^hMq3l}9**tZTJ8 zwG<5cmeH4=Hafa)_)@QO)RH;lFu;U0%#nDt<3aqJLLaDs59~l4KcDqZ>}hoQ_o@T1 zfAU{OZ8JZ1EDyzHYZr70(hOFHJ9Is~NKq?fbm9xqGFw0?kw?~ExRQ`^^#?_&`&g!E z>B9{p8qX6Rg*~7uni>?nr6PgMfzu_-*(Bj3G6jg@5AWQ}b5r1^ES;^`y@M6sP*uFf z9Mhk((;zAQNo%#R`aZZNk=Bb!85Gkm=y68PNAL;f618T_2`(){-r?4h@|W2tnym#S zc4Ix#zM91Ysv3~;>t9TNA}K07`DF{j)n%@Mw}$$D3~pl@kMBCWq|G=LX8yC690drB zR7W&-O^(924u#r7RuPOi8tyPWYlRVkWU7#aPHS ztG3ymyPY>zS!sY%V*zi{U7{JP*Nyq#U(=RPjX+ zybFrqz)T!j;1yiMuRc6U<@mi}mINy~>qu4fFrn3?Q+J=$oc_XRd@n>0aXoC52Am&C z=NrGxA-0*-%?OvY0J67B>L}Aocaufi={&>IX=H6A?;A$kyLZkCzQyK86QWGWg}B98 z7Ss%8f_iEKNv&;2k0))W9aVtQ!?pxO5q%RBp}}ePg5cGj%SuS zO>aZM&C5hjCq&UE4W#*wCaprd=_vkH4hd3-cGNngEk$V+3(b|*n2#BgnBQRwO4t9J z;!u3gR1Q>M!baw`Ml@=rLkYDxhCa+V{T~R@^nUZCZNBWTfCd*c1RnXrC{EqcNQaCsi6a1m9jUMavM4|k~rvj?zd0~XfAvo8c=YQmYmiE_oIy&&>oRqU>MD~POrO>W(E_P&!jb21L2*J|lmVH;7FHk0#8#TYy9u}ZDW?BIU-;u( zI_HX<-4I2h#CyAOwB0&*$G*BRK?=pMeeQx&0&oLt@TAciBVZRT%X$b1ky@z8uNk4( zRxYD(owN9=#7$|Qcb1F+0=N|5`6sz+MMOlj5L)eS{TjO1fSVd; z`8^a8&YaSz6!jo$lO)z%V}kN-Q?tKL(JM7-{^m7ig5y=rtQ;QO_K-2zFbA$B9M?42 z{rexI*`>hO`SWt+3*nzf-=6ViAYA)k3MeLBC}sS)uC`yE)&`I-^gY~74ZwNIC_3smskXeuIwmwC%~bA0uiw8oI7t|2_Yhj8m}-?#l^mG-~YsX z>IuO0zgS7RoN)R)(E3-yt0z+gAxC~C$c9{DiLZAlo1)MI9~+O;Tb1$>T)ztF+AQLo zxw&iTq;<&&r$UFb1S6Zo>uUDATC8`5PTlfk1qG4UZN^YtPX2@hSBB0*cIODPw8Gx^ z2RPf(@cy^ei5h~W&A}751Iw`tM|D-}XZvycDTV2{l`%BuRiIs^>Jyvg3*J5AV1daE zL_FLe;wekLcDC(u`dTE0zc}*1%n~mVI73X5(&~g#+YhYRchMAZRI$CC)g@g+dH~K= z*Ch(sA`y5&msnt!QJjEuPxJ0os~l)xDD~db&&#?pYZ5bzU1|p*ca4yr4mi+&U7Q1) zG2u}QYpVKNFmJseF&g1G{3!I^(`8nS@?-|rVa`1z_8s%k2SN@xT=m31PbEbyRlkBj zggs##+`%H^NdLLTa$4SW;>?KtLMF)^j|YKwW;(bf>>wflD%vY7j?3IE{IOLylxSr9 z@d-ik$Ryueditm_VhE%$DKQ*tl|&=R=O$YZnF2Fel}c0chvazOB#cG)+p>QDOV!9 zB~B3RTU~^!0#%i7+#ABy{NxUKu9xW)P?OZx5F zu^si!xg z=pG~I|4-~5=uC*(W)Rm_sJ#;(e?aI2Ei+^NEXDx?lP4O}?r6ERK18j2mIMFK-$g@->}D=4UPGB#{{&}ZGbb(4TUfrOC# zXY*vPaT{C2t09kr41>K8BF_*`RUcPDmBfsQ#dBy-vBW3Fs1o*o9<@T8xCQUQUuX{# zkt$(C!h%^HITd5L+JF-{Lm!3`?tw3gUJB zcai3JMj+ysXzcX02bFEP1Ir`G3)sY$mk?+6=_g1;GOynT%PQ}xFM-9aKCJ-s>Oi)o zV`S4jqvXPLOAbnz=}RoWFn61$sj&zi*qC$Um6OKBrF44B&Tz*S-Xf!Kwt6l%|9B)_ zqThbTj^=$iF8v=@9+Tz`Nz8;@u$nw};dAwQZn(5@iG&N+uh)2rN7xOuu3xfIn`9xW2o+zt1Pj6U_p@yXR~B}%aeYVvvAFJc{EK! z#C_&n=Nt{^!-uC;c6lA1Iq{a-DlibP&m+%%Fo~*2cBAqjIzK!Hn_C6SCoG~|8`Mm@ zYy)XA1#C8;@SsEl-8zt^tU0nm{jj7H0000000020Y!1a_q7B%;f`_S3UG|NzP<0QD z*EAx^GQkjQz$^);V}p>Smb&4`KfqyHQRfSED|Ocgb|0#=4R2T|&3>2kqRGF8t}$$& z`gM9f_K%uGfDg9aiBWXI6@L>D8#j}_26O#oV&FE|NCB^*!>Prd+1(wK-vH_ZpI?S&`pg28@QA0 z)^n2{i$?rN7D-NH<>Ls)W*w(kxbnWS%mSfzyq&E)diT6cD^KrmTxbdb-n3jdyc*bO z)&{}#4U<^i&JieekIw*{&`2uZZ9?7vlzh5p@#2Rn2cw~Wq3M)J`NSHw5=?cTaHoIh zC<4xwlqDyP>Cs_;s}@4fV9)9%1P&;wuE0D>Ai}f2+jRYGtWKY;PST|qi5fF)StyQ} zbLo((pT)|zSUe4-><{o8KJb8_@V+*O*JurA%`h40h zoz`X;Xz4xKKJb4p)vK;60S^D%LX1mw-_cmv(S?1+H~LT-Fm+#SDsoc+eBK(%s!5ci zMfrb*cwG`&eswsX*G3OOi(0kTh+Hg}O(YYlaU35&8mHjN$WT`Zts(Kx>N{vgdd$H? zT7l?k0#58(7LN^A#c3 zGgpYFT>k{t2{z{IfmE=+OcWEDmK|S>Gdh8#@uoJpn#@dPLFQ$A;cS(WEzyHI0HQ86 zAG7&9!9HwuW7H?St%@fo6ZTS!zUWWllaio}h zugE%Rg3MG1Q2Em0+Hd)athbHwh0Xx{U-plA@Q(LX{pv=V78P2-hGUT!BZ=BEEg;-A zr1=5NiTs)n%|-a`NOxt==ojwlBtAgyz+1;F1pI;Y=yp`&XKkX-nu%#0NtX-SMn?w5$&PVq+;L z8-oQfe+s4_jK=Ykdz$zO;tyX150A?@aB|F67Px@X5L5pV;QsJ1Jp+lcS*_9-6k8?8( zs{uZ=i4K4RiWk!@y+*FDp3H@Elepr2Y5@>r5zwIgeoYt;F5Io(pn{v#=Zly^Mxg*N z){m!pORoqsrj~4(g`zHvq_*nVF$H`17^^Zm8k9+Nh9DU=OH3pNyoyQ$yBSK*fx$UH zN#$X_&soUN!JpblG_jQNM9_pOvZRXUF(!YEb;Ld-OfK1*u_$F zi%9kRi016;=T9!V|J|lB?B8NDG8jI$R?^S^+AT!YmUKr^|n8ss)rC z{qjwdrGkgUcSAc=Ai31Q&ySN7x@HF!Rg$U8#@8u|)taxHUObw21%F!KD)??~$B_8n zVV4PIVeZJoWjJcGL_&;#GsLXrU#F(h_ImrfpRToRht2{KNc?4%IJ?ssA%~*-DKN>T zk&v64DJ;9?G}1xgt?ChV0NYa|(Zv}H!zn#$ue~J-#y;Ck32qXEQZb;sq!%S#SE9_- zKl?lHFQuUo5;w-XAzFT2}zLpt%$)AgsdIqfLa4oy={iUozHY$94kNi zUc|}%a1a4TM-nFA5z=er|2_zgIm3!Aa7uWo>Mb}nDi}HM+c|uL`Sjww+NKcGmzyZ1 zC7q4Vo;n0%VW3Qo?eLA}G(|1xt?c~V3Qx(Llyd^AS#)wpt;_49z!Uirl$N*2T;+Bb zI)lo<%yE^WxOfIM@H9mFka)Nba%^FR6@HJ!e~RBAay{0YE%w5;wf+pWjUrna$Z_zO3>Le_spX4pLPwkq$<|wW6X;ylv)bS#}BfWi!y1cvU+@xt~ zu}%P4_$#XyIsVXMU2RZq&m|L`nmf*Qv$afmHM&HlS}YDrf$GWHq<5*qfch3t+Bg=V zd%2OGdqTZ)K!1uo4|@;kPMsac&XM}V5kTt{g{)I_T*I)#-p_(bq~{0oYGf=GvD!@Z zf+gpXT;5o0GOWAtpjLc}DnFu{yrNw;OZg*XiTs+lSO@xd_D~n3ZlcNkpTY5(XvD3} zX2FGzu9g}HTPZ{!TstKF#6ba)LwLueHo|VPU;UYRmr!SFYthj*AJu*(YLZ=d9QW#qZKcc6OGzh2@2et*`<%vRXzsRx~0L~?({(!Ue zv5DVUb$=v&R0epz!@L58^qF%3{wJ%lA_Dw*v;WTW*?T^xo$QAUDz3R+GTj6;Jf*%J z1C3o`P-0H(w|Gj2^9h{ue^Fd*8{b>_NM|GnuGg1SXuqH$G;)#-8ksP?IZLXwl;91s zp^@`<7@7gp#9J?z?bD6n(IDhYemq`eoeess8U|gBI_d^>d@T{_>})3Cy`b#cWQUx+ zue+WL%z?2vG&>RH1ZC$1ediHmxTMsZ zo3kidX_my>alRj@2;oj=;L!f&5~WLcd_K7t4Aaxg)vW{0jzdDzpy3c^!1q^qP#ZpW z3{<#`ox2Swd3MP_;p&Mfz_A;-)VUSt1FK_?#hkC8zBl58Z1Lm$DjfCf3+%bI2}@DD za>^$i3DhIvdGw$!SsEey?;XgM3S^@f%FXxCTXK|4?#n^)~!=dq$D(+84!I) zCN9Wx34A0n)+Q&*u?G?T%FsZFp4FH+I4$f~=XVD3cW8OOC%F;o?Mssn7Z4dvVB|uE zcbrPLQVyc<=$WCOvn--gc0LinYo1eSGpIGy-7L{B7eAE$GJ)KIQbqfq{u z`fBm<6$`Bz<4ps>YjXzIgMZH7<65PeLq&w&Z=)kmru<+o&5>=`{y{fg2Ey|dOz$Ze z0KVMr?)rVUQ^NxQ{ROBhrIw`1%5-R8W5c>bnsX=mIa`SN!mRv)j?XXsE!f2o) zJd55Ukt%!CCo4IS4z3dEc&qgSKjQ_T$~vCW1VAj(i#ihA?T051HTPiSBh>$Dul-d0x zfh)cHZaxWIbvu*a=TdQ!o`4*#)lS7olT3dG7iJTJ!YC8FWiA2+!%SVXm)+Iz=F>Hv zhIg))F@N^2@+su6lIj1Y*#j=(p%@8bal`QZ-;JXz6Aa!#p46_mTk$A)J0|eso{jhXGU1R&BWt z*~Z)vz!PD#cMK#Qh-a^ccE<;MhPlYNu-#6bVZ!Bwl1XvRxPANT+qnuJ`631)`P@JV z4eI8z{d6jJ*c1KK$8la(h=0+oujwrBoOgX2qV9|;XJbUgdfPOIoD#haaNZb3ADrX1 z&{`Ig|4Eet{lN&{hf^fx)Lq3(J`a~}!(pqIh2!%)5vrC=zG!;uXVl{hA`k956Mr*KP<14N89M@N{q+Bow^)KFxg4O|&HA6zg& z4oCWn=x%0rhk07f)d;XGigq0eixro$hp=ni#G&!woDKIK9wNHkeK)A>y z92^-wA{86G08yPDKlB}@wrr3Ycm}cfnHFOEE&8)6U14M)<0vJn3zjcGEVW(>OOZ-2 zrYWdc_fkmKkV-gHFt-mJ*R(33AC|zkgoC9E;N96b5;|5H!;jm~s+ri^nyBUN1~C#1 zDiFw4_~@=1jVVWhQ8HEBMMAHX+!z^@XI9EDmz6HSPKM4pqn*P}r4KyaA{QStMV_Qd zXRz*s`CPl%$V~@76_-pj16X#i&8_t(P6=*|$~&E2Gqt~xxa7J7TfYn>BZF)Qx>3Dk zdSV(N?Nt3D^3tb{u0)~W@-gJt6G2uFPjqjFi&O?^mk&m~o>4?$oq_ZoNfE~$v zihBybTXRCb7fiR-;bLs6ogj=hI{QKjqxT4o*oH_zGy#~$^>CWCH_`Wx>xRSsKM zmrU%9Sd$UtM)zG8`Xv(K45x!gtiBx04a-}YU=NF=TZpx_-nSgFI3Ka{1Aak{M6}!i z{_f3VKRC8{3ln7_`R5bNT4yXxA%2qJ2v{60C=6m65SLJ@wt7mT;Kk)+i+Tr`Amtpa zI^Xj1ED^HFb-_(g|IHO3^6Czz^~MQK(f<%x13d(zp(FCtONO*Wa0a))ma#U$hfR^} zO{pzK=RNtHr8^kvixg*I>{1#(WN!?;rJV-bdyxc^jfjjRf^sKDUXdHd;p1JOK}M;~ z6$X>Soi|eq2ng1lmJ~Bzr267r`&}EP{!tUJh2x);2!8#Nj!z!%DlK?=3;Ajoc7KUL}3?|xB7>XLzgLH8sL!DcqqFQoH_bza)R$K=}y z7f!Z1OI1LWPGT$O)Nd4qOGYZ_xBYV&?$787-tm^M-!d!#`f`-Y#yNR0f*m0^y3vC# z^%V#l`BwB7)8YyFhgg))M)Y9?1sIMKgGd00#)r#=$+9IF#9tlb=W-pd$jW23UI&h? z6Ex%?vzHU*MZ&zN+04ISv8^vYa~93S@GXjbBEZI*nW|nW;7uEbv6coK$b_)c*$THX zfOT0J(U~L`^moS)U+8^Aqs0ljPZfrq2|fNh@XgV5O8A}fefEY~@efKTMf`C0@TnDc zXV)D197uft-2^2SwM}i?f}gCO^Bo`cu`n`Dx3L0RCuz~zFkW_<{zAZpZuX-z`%7~I zgvl6%A436+oS+ykq-Nja4v65^I8h93ZM!I^gm+Yq;(=`Boolmd3Qy09wAkK$RIm*W*U+?2kY{Dix29mNsln!8&7 z=ON3C$YAohj$A#nsv4{8+tuA-4|GAq*c>YQ#l~F;jZ@7N9A{doZcC|{EPghtg3uy?}4~-g!&FRSz$a~^JS_sBikkk zA$*XJcLc7Wz$Kq?n*H2IzY`S{T|POrC-fJXCukTG7*iz4B^KGkn#{-Mh=VbiA3wll z4OyA?6J(g1c`q1b6BKjGM}6dZAYptP%0sjxqVyAs?_U((J9%)X4p-6XmuI47Q7M@?V04cRnPGQvEmTrmH~{%q{#+|&d!GKT0h3Uf&+j!Qvd4@ z(_Dt+DoeSLd!B!ZR8N=yq*W?y%Wiv7 z+G9D7WYsSnHbfEhAS1tKVO0S)z?dJJAo-TNMtztmkyLi4@M$=Tw8(fi!Gz3@Bc+*sQ^D_Es9 z;`Alh>n$4q{=dPBp0dye7$lA>B_!@jP?Q6zxirg&p z?GGu3kiSzFD@rsW^vW8uek7(0BWhuJPgV;;7K$tSE26NhBpa|*-tNT~+?@o+qA=JD zZ^oyy!r#hCdu@KI*Nu*x_P}XWpRSq4|H~I1Xf%iyf656Ctb*NL_?R#n!Q)j{ZsE(V zf_#2u9}F#@L3}SRkBUaNJ@)}N8VlNsoq{kWuafB(Kp?W-fAuWcG}EAbTBA80+sZEq;juWHne-4@KVS9Th5tcfAcnM3Jy%i{rjF`@3QTL+BR3~qupkf~ z!L*>a`x`N?>5}@T1Cq)U`m+LqDV)u~h9H_Qm`*1~;k$g8)$84|UH{XsOg~S!;z>km zhXJ)aO6dC8jJ6)=e#k820Yit#W0ifd1m=GB#8#CDr-_ngEgWfgHNU=4 z5^5Deyz?+{wKf0;tgl>VIKNJJmILHXQK<~0pPr9~yvY|i$rY)0G3_dxzGvnhQ+1jU z6Wy4vs2S_w_x0QwPy`*VrxhFzN&B!|BYI#_Qgr%`1Dap5Jwtk1bT5%DpH>rk_FEge zu&HH25%!_i_U#>t=Aep(PT>CIBL%D2rfV6cr42*LSfWmrQvvT*2a>acwl8&o+H;Lf z8{p>*Q@SFTVR*eTmz{mk9Ca9I%wICPF_@oWuvwvWjsqG^%g`YR0MN?NJ@IPN9_>!7 z;bw!|)FD++o9EL-JE}$KS==X~BUqCUtS{1obr(vgw9he8IGO>gy+K5#ewZopIlX z--KRW?BK;H4G%**)R~1wth^~#$Mhnu0E8S}N(q!-Z2^&xA@eNJ4iarAB}%d+l8JEN z;FBkdY&x)E38o#idU;sS?~T~0FD3LhfUlQbk*jGPEH?#cn`w@Ovvk#y-k#S?R5W2o zImZf|NS7Srkg>n%EOV{squUJz*(2UGgF_%LJW&b@j)7FggV?D5!;l z8Mq3%8)?aT{Sw#a69C*Fa9~Z`Ocf7&hfFZDh7vQ|Oy2R4en^y%4y1Hzx8i-=!~D99 zMCU_;eCh_TYISw#rhguS;ioVj4N=TQ*bIK+Jmwo(3A3yhd@vXpJdL#G6DSGpFngx8 zSm0x6O05fcFJ>`ebVh|12mlMSlR22qSRn6g!gSF#I|l$#QY+^dOTyZ+O! zpJ2x6Ba*wvBe4once~Y+ zpG{i|3V=Kj%xCv?iY6Sv;m^HCkZ!zc)RzSvT_CNp2rX5&dUwedtj8YH-r(3Y zYkkfW(FOD~D}xttj|@M6Ur+9U08ha&B zg-Vf!E!>nD_$#wFSJqU<_(=fe+AQTJk$5WBUa&Am+3Pz$5qe~$Nv>?|VT9GG9;gVy z@1c*1J5{BP>d9Bf(%@%WuMn+drOTGkvgU{&%{~bNor97g9?ZVbkquo^920)aSbk$c zw3(YUS%jLSJh~%|wi~A?!o@-+n4@=@T|)$f(%89t+j=0<0TnZ0Z+8J!BG~(>Sl<}- z`3uEKUPjJf(HvK2f}5Aon#(^rqID^@t(1IPn4|BSbw_RdtvYEEFN0m(`gaw@q2mCo z3XmVpQTPXMp1}3XGT|m@HefMUBtZQLhnJH89ZwW}{W1>33?REv2;J{;KnPZo?rg|!OXbwt*BJ-wZ@1POQKmTLd7A*-{#1zOC z250RmDia0P7L7?b_Nf@LHL2CfO1yTVTh9+Bu_TrFLq37kRPpTLM`3&Ejrvwj6F;7i zae`-|cM&5|e&682ltE%TG`*c=uvD(0L;h!=ZW9;RBh*^w6o@swYERA6}ZT7hzi^ ziI=0x@M1trJRxMqlyS9$nNQ@8xA`mW|7kWy-KC=Flp2e;ymBshJWM-@M8S_=Q>9^; z)ISt!6b6g)V$`L06EG0rVY@%9VA;eo({Gk4u{_->fuA52+jj}ezz;}RxGe;r!N_L3 zte}k}sJ;*)3AmLVH`OBcoBGmu^OQj3$(m3*3$W;E@se3EgFM*9Gnebn{R|gB_Ginc zEPli3m)prMe$J@l+m^*1R8df^SIzH{Hf;`|vlsB7aE&CexiLgvpGS7CKwRW%WrP^8;;d*C)zWt>5K z^wc}Qq5wQD_~e&l;4!5+mT?$>HdJ!UCN-)^pzQA`_gDyZ=A>f`R~fBSsU-=@tHvTT2Tf#XdGoSY>6j&<#)4bc3k)q2x2NUFFH_{{4t5*-nqoSBNw%szy z@yA)a9hBveuCq14(+9Xz)Xcyme+DeL!DAYI!rLgAwTZi2rGx1qwv#o#PT5d!ivbT;*C*sjok;4Ngie47Y|P z5VS|MEO#`vWl6bp_Zb-8flY>Lvzy{tHG!Xnr}i^(6#i!7?K); zNc!<2Iz^}o{c5-;O(($H~JVVYo*{izfLLGv26w8Q+l?A3+kI8)%<^R4c zy3i}1>r>&C`CM03?OSObD?96K*c?k~27|A&KNuQH&=3zs6hQ^$A{mYW(zFH!m;AQG zCIm8wMk)N`qE-gsZ}=KaSO=Cclc3=1HjgL5F4B%H7!0DXwl1lZv*NpR4s!|8=Q`0o ztL~OkW@U)*6E0-@o&P^cpuR;BKYqX9PudYu1)#fW&fI)5tjdv9#0K)PCa*uHU3+a~ zAv1=u;j0HcmdBllAKAO_#+nQgEsM0MGPzEYRvY*+`exM*Yc>c-1wn?<>HPLWcFz4U znZwvnP-c>~5U4hx>~dd96oV)!=9k6}H?>mTe)AVti9j8-lq7Fdq7vE$?TbAUyqO2{ zoFqxV5oU(CzQ*P}GztQ0VY5rtTDiSlK+jGklBfiZyRF2o__H32TfX z*^#Tak-lzL#wiV4?GgT3Km_9(bYs z?MYftPV-y39jWMlL9)<5MGhI0=4C5|V|bRh6r2Kc7J&JHDYfgU8uT%8=;4rG?xI`l zS79y4DAotY1nx*h#)zN+8Y}Zy@Dzz&nccD(G6>yv#4r!+&tmsdc#LhhkX{#}2`i*t z@VyCw*DJe@ObUlK*32#T595PsCmPgB?R!1tLn(%p$jujha^_U4g<}+di}oC$N_;1- zh53SWeNI}x(CADyFIraB&>U_P*I%i4edI09Na*4M73&5&w4{@C5Iwx7JqM^R027OT z`2xxnSRN)UYyvle1E`~XwT_!p(qoYO+aG;c6&K>OYi+?c8~YJ|h!4q{@`tQudPQym zgx00EP1C}dz_1P7U*d?n3GEnalqu%!ke-P+NPweMW>yez z;oRmo){hb4W2u>MJ5xM3^>yGR5;w1cHPbHb4OklU%9&@HYR70LQ%x;VkKh~{WR)sUhrFpmM#7-WW2t}js6?T@bGv5LkR&YT)<*$_u_9#irufl}u z51kb%z%f-htSsj@VA>+yV3zI^bbzoOlLY=I{#EeBw;vb78eG0=NYQLET5kAY}MIG@*L~yl4&T$D9L|{f2RX%xqLkjA}hO z3suq1{&F{StZhwE!k(arZ5|PznUjc*8mdn{jJ#8`y6{Y8RAFbDfc@X?IMJDU}0VxwFb!19uug!+@GjjnE%)=^a>O=x;FO ze#vv+Eni6wUY+t*nH*JJ!|BHmX3+t#X_Mzjr)(wIAQ{_J{9_wdlScaZ`o-=fh)fqc zaWDYlc%3qJiRy$H;q9&?X#tc+!JdX(ENe;XMIIUl)sHNpkSkXMW)Zu|y$N$Lg=1iW zkOk+wCHo7gaZN3MDR2~yAb}MTlE9Y2wEQqurcxL(r)nIk&s%B*Ruj~)y$JMP67zA-`z`Gcm(1O@xwcd=Aq=W zd-h+>iGT(MlJqyEhjco`M&U}60b6vD5ltYSgJHL!t7)<3EgS@CvW@BPau1cvwD0QR z#BDLK9G`N3o1o>bnX(lTaD{mNbV*ivm(`DF)2v; zY*AkRp}vi{7~Bhl(rj-Mj|npqajiImC>;Fl;PV1WxCRcgxQg z2nOtozl66N8H)$@&HpZ6GY1(_exCuAs6jNAlN+>h+k+0IPL;ot5>Btb64nH}8u?6& zC0?an8#P@p*XJgzo(#_X6fVzG&75y-Gf^PS3%8?EZq8|7i@b{TlMgpr{0P%sApYre z_I4arpn5Ci_v)SgeZ!f$YnJvn(tlY{u558a%Sw(Tw`R5glz1n&!xO-E)G@I1<|lxk z9>pt1oZc;Lw+d4^VuYVDBJNQf_IwnU}(?b}ueGI%{{Wx8ch% z-XzO64aC_TUC05%+}$JHnj^WYT*U|D(3c|Ad}xKowzO(}0xo~nt%)Hr z2ibrc*8?wmbK+(o8&85>ICO1tlG!Zg-o|u>UR=Q7*CM9%kw~g*JnoWj*Rp(ilQTLb z*!drL@{=dqzscsnQcDhpkX-dfJUJUtLK?P`1Iu0r1L9cj@- zs$T0;{|kU>|MhxhFKqY3#K0J4P1HNtK5YBfwe-yW0~KQ|r!#t;tN?jZb0$>W?1oXJ!MIUSzelfG~QR1VQz3k2sppf1{5v ziW4V_vEs8qbyWjh^SY8pWAm8tA>MhrrpAD0Z&r~uShc}u#m#r%9;6P!DjR9l7ZbiS zwKJi?4bJoUC&Gw*x}w9X4{C9C?N2W;4jdAku3g?2$x+jgFLE{(VFT-Jl(0jLtrkwY zvLqXLKRc#>h}hrSZ_U6}sbBy1<6PhqoJLk3y2e)Km$4Y8@+yOkrG91?k6EP957_Tp zinO8NABZo(1$0dh&(RKL?;C=8YdLTg9LJX$rEZca(#7+)!uu5cY0k8B#>5#f<^zKM zEe8h_nZ^iA8rn(U?NjGo2OrR|p1!q&X8Rc(GDNAl56f%4um{*VdD>ZVoKOzd;?o?@ zx-fN0^fWI|YSXce6qXdMD4zw%a})$VwVDc>hz*RIJJZGu+A$`exK5%qYBl-gh8x>+n)e#xnQTWc#CD;kbKKE|kq_z{P{c1u z$wZF>8Mi@l5Kk}OguOlefn+rJ*Gj1P!qtYNy)*^r;Ut~X-sl?A zsx3R0iyoQoGyK3R@ipF(0Vu?r+7l8_xYwkfr~4%Ir6}-OH%DK@3-S9-W|x%qcdN5Q zC2_C_@5(`(BTT2v#!5g%ny%qw-kPQ(o z5or^gp%D^e=jer)urnW=y)E{v)Pv?fdqulspsEC%+yn)Zxr?%!tT*eF`*RA@{VR>E zomJF0v0OKhg*2?6t@zcylhjKnSs2CX&2Au1p`#8V(|#vh6z}FBusR>79s{_AANU#g zHwYTboF*(FL>j=)W|;(r22uQheZth~ZIY#oc^pJmoE4OFpo@@h8 zCenjZmdK~SN*>HsrPrdhztfp~^krW1pU`4CHJ1ZaNiGs#@TIyN-fT<|9sPnP&Qx#a zv(qSJrXad{eRd_rS^EVXnEg+by>21RQ=`-Bvj(??jAg0?!4R~e+-C+a7=|o+)_1A%vTb$R8@HiU?!{h=LG?>=EZzp zm9UM@G;yCR0>gD2TJ|OO)a??uaq!3F>~27ULQVR`etnGuo9j4 z!mNfS6?lc!%s&9*&NrKGPVoDJmVjvlW3K^g*TA8_M+om^@9Xeoe|A(&OS$}Um`)!{ zhN{cIIP@#LQF^d(Ae6UMV7jBzyXDnQ%i{2Cv(k%;l&mK6+>#W#*U~}2U)F=7p4MxM z=L*LsTm`DSFv3p-5T2V5gxv&t(&w}BD*z^gSPX^KM4oV1Hy0JG`WN>9%z2%El#~-o zanSL>d={AB=#f+XsEr}{1NF%~n6=LSqk!MZ251U8v($^5lsH)^DsoDnkLhP9|IRtBQ-Z4dbB?Np{@;`Rf51}OhTZSnFR zyj~Ok1)Y;SiyUCT;g0sAL+OpLH1i~%82Iisg#Yl}mrOzI32&66-rm6-_Iqy8g#W#_ zhawv}{I5yo!Y@jMT8`}ZdMjh@)#Dp+`$9q4mw zZqwFQM?Vyw4r1Y1rj*R$zj|~QcV;D~vW8l3l;YLf&+k&hU!^Y2ynI>2?R#ll)FfH8 zjP-(H2w-}K9SuM&h7_RqO0pIdkdZ}oH9EugM@WGWE_HwPp70mg#5Ymgk!P79{*xgS z;=vhV?@bzZk|Ec~IUAk~ay{lHV*f8g2+bMYs*Ek;eQ=_W@h25iZ`JbljS$rFV&~{U zk(LYdOCBJO!TbcOJ1ONjFVg@~VGCGu5@7Q-{;El?l{jB*vm80fkO2t3qR$&%3aAH_ z$W%qQJXT6YA}=D$_O=vl!U~OpFYgdB^~iWsH0LnT!f zxHjSkv)vSTODx2w3LR68a)KxVAr{jyG9@actUSstiCr>r+hWBjqZ#VVsg<(j?f;m}Ld1K{oK7;wul+&p8Jysfqag6Kl_Ex)ANgOP*R& zI-~{}#?5lEmOqK#+6QK|?djvF>01$-Xw@dP$@~JfdMFsycx@nC^3+kzC3ZEc*a8ki zAylir68S7&M*}#hQCAPWG3$Qm^fZeRqujc+yO{u0IBh9BQf2D_d(ERQO34IGdICO| zp;ew`$rlQoF6mb`b#ZK1(Z57C7ZjM4LW-|DX@1v&~F|S8jGFnaMl)$dX3%f|CB3v!&xT@ zzBSH_L}fCu_o+y0pI%6U4WuIBb_5-Xo~vQikGe%zhpAvZW9>Mp4`Xj#gso!ROd$rq ze&KC=|07oC7I9^`wq)$*HbG1&1cJ{9dW^a;#T^@u{Z}%qtXf|Y{LO}j^R9~4?7Ie) z!lVX>NJGWm!k-`aVRnx$_wKGXBKG4ekmE|aKN<0wl!9*0Zr?z;8@(PtbKVGx$md-& z;%LRI{u$D^tefYKiwPlVEP1^vBIYLMRCexik!%j&zTx1nldui&{_^T@n$9%F9@5Yt zBttGEpm^es$*Pc#M;39ou8y$GctrHmVPu5T+>gZm7-Ad&6n4-vqJukm6s#zFKpiRTH}Sqc?UxBhHSE^&Lyr2J z{|L3D6E!V$5*-5Z(H)q!UGrZ=e@!MkMOcjHl4XHgsmWd%%grT8@8kusH11L$z#6|! zbQ~Zbl8K-SiACbuXpe!#8?+ki0FQjunvybn+h2I-i1q$ojZwMuuiGYV zDSPLzreQNaesC+%VV?(0oYuVtN90h==Sf%^eSRV=irW7x2Qva$#!w`9z67*kCIq?1 z4lnyv`h^~{gQW?)s0Uy6=EN!3y%Svhp)9N;Ca@W8KIp#ig zSIJb`SLor7+PC$T5+(qB%T#gPu+s_e8p8zfGA}pH2@BwZURS4 zo7iJwXh)V}m-^YkX()#x7I{IqZm6L$B1c!A}TWEhLvz(%N!D@EgWRKx{Vv>}^utbABx&woG80!}tw8Ka$5<%Fu3H)0u7 z#*7ffEOZn+MaS<+<;q9Xd;mwPv2;77LV+nzZMKS0wm&L*5vb(ibk6|_PdxcdUq<|g z-4uCn`R>_A4-P;ZvFk0zr23lQn`eO)_sB25$Qx^Cjf5aY9);rWQ2%qgq`xft@%6&< zQ#ZD?Fht@+QNz6We-JENuBNm~V&1l{nD+r|!~`yFC|Z$FzWTqTriyT`hKYDWN~JBh zwV21|omwC`-W$D$q#1_mO7S(;t)Tj+;&$UuKzdMaIiZ~a48Ri7pXfIF>*#rP`i5ZI93tdR@!(oXq zm+nrJ$K~2Qst6h$7OIG?I^(n%;t~ih5$wa1pf5Zc8@lTR6BJ5Q8k!R!xoLr&VUHKn zO`PZ2!0tuwSCR~k9Xz2=z4jxnV+{hrJqOVazL)4??$sJ|{U51!$)FnMODQ#m;2TI= zj7k}wS5*=qimKR+%08ERwt`H}+7nFMInBdJ${2~rMa34=ES>0OLLkx3eslNgX^ri} z1ZuF`IwMqUn!R2ZeNhHv_P^@MM_S=bduA@s2%f!Kk^niSnc*@%bq1ur{Yeh$f+b0(oW=geD#GY0Ri}SDXHU)| zl5}S;*AXUt&55br%-1+T-;5IOczme>&RKUCM&U6-^s~O>fO#a2lcTb@q;#e+Za%5O zt};}+ZNkh+u#3tR!SD&*EL9M-RIJ*b_E-&hppUhRL+eL0W>4W`;HRzy19c(0aHR^A zPxm`1D9E2z`~pEx*I%e#n%}|u%80a#tTPX5JIyQRTPNhi1rL25+pnfOhw{can9p&u z*lt1ljj%2vjn)s@gv(Y)N_@K&A_U4J0;^o1Fx5B9C1u^n*aE_L@%|K#C_6|82CGuL zR7Xrad-fA#2khN08EUi7OhxEYovjjc%gZBUJxoJ0Ee)T<{ca*J4=J9mKcD;}5k8?f zCYlO0Spv|^^IpFW&O1c9gP17)68TW*fR(2$xMSUIT8R{i&z5nw<$=C;MDjzz7d^y( z0DvM&owThcsHS5<3}Xk%&d=tuO??e;%q{mjX9R`4UliwAU7zb>8N2(Es2mm)plzC! zt;B(#!>jIhAe=}lU*+c}c>L@F5`mK{5dqVU0?ab- zy~#WdI+rOLP0^Y`^P{UCTBM+)ojpY22(#5T5Pw|8Nc}H$+Q2Sx_fCo(Q>Y$TGZZ*X zOtR!+->YES4~7D4WZH(R5*`GO!aUWP(NQ7DIA@M0AcabU&i(ZW*2Z-5crG#3@}nLj z`AR`>d;Pr`X6ILoss@J^ch6+p3-6~%ZNb?<|M0VSB%$aw=d@CjyE zAn#%A!cq9N&%+fp-9HLAoHz34<7+mfkA_7=m7r1d1u`(LBJTtG z{-<`_cv|otk8`V@W%#1-!Qo4XXT>9KB1*ng#vuu~#r@SOJ4=XZ;gNfv*03Op6QDX4?o+qRz}GMmb*sc zZ08hVm^T#p%3U`ibpju8Kgoh(XH(ShY(*=?{KfXUr&hLQ%4Zg-24qaGjM)%Pl_{Qv zl92VS6=32&E;gb4zpFodJr1$9Vh!k(s@Nm5!t2nL(l{-7KK)4WP@^AIX?@r4=tnjo zy)i`aBW?*<{6WB3Kte)+4I;QW(wVpaIGfe}9Mn?WV&uvpP^~34)IhgR+q90-8h9Qf z)`=N=m!B6urG0VszVYY-PlFKUawn*v%*Troc2=xLh#X>|e)OhP1u2POaDM>5G}#yo zva{PQi`SQB)0fQ7V3@t^X+}9IGd9R-O^qo%OM@Y36T`ZISRchACmv;(DY)zdf~0Oa z&u}SV%8s}8J)9qzWBHkA!i$}n1ngG?>@X%}!zwnz9^SDwsz#EP3>pEz%q&(QjDd8o zNijzWtUjyaEBXsa(dXZ}rTExmO27ua8Gz%IOw20kFRDsW@s<{m9>VYWwvuEcY#u(r zV7>+lQ``NapCcfG?)0<#Iobh<(p;x{?W?ibt;o{KBwZ!di`0}D%NR2)0XV=?l7>Wv z2@%NH+BiWjnEPJ5^WO*f8JEW6f*6OEbUFb11g#CqaAWDd5Nv^yy0oqxL6aWwigT;6 z6WMlE)hI9q+jTrd8?(l6z7!&;9wUFIhtncuO6J!f;3Qk=0Wvb5NZ*P8RzRu0rSl-1 zJq7$Yo2HV^KdY7kHKqe7Wu<9ZU|9^Eqr14aGXBwOw!B({U*_GyMKvNEr3#M=P&itk zX8%SmpmZP$;;qn@a&kQ<;j`Ju5;aFJk+kUpK&MqJ8xPOjvm(fMJm7VToCO|7c^GBx zy2QIIFtovswDCoFaWK}nU1Jk2Kvsb46996SRoY#8ExS-pt1 zZwKfd!W3=1c1+*LMGO1d$N2Lszecd~l#S{qhoN6+hfP1XJY;I!w^UOIm=Q~N2x$+775LqF0{$O;-^%8x!~-3j#d%DtWeJX8_PFTHv}5 znI(s2AcH;72n2lXpM91(pg!7;`xD_Fi1oOsNaDgH#;W@EBbR`tbmKe%QKmWZc9Uu9 z#9J7{dXlUC-T(anKIeQrDNv+7>7hkgxLszh$a3?-=mG-))OP?;;+b_UomtC~v(pLQ zx9|I!MdAQ*KyPoZ;!`(j1$dYdn4PT_w=j7&?Y`_(HrfP&%YVoPyCh0bC*pzz1pMcb zNDZMZMi7+!DiGW-13I#{91#P*!!(H}!UpwQx8Vq%fK7%fkd2QCn4;$DTP@8ZIzC~1 zXRrsg0wSL$6gvm~yNZbvtSCowR22j$OW=0ZHE<-w)BUJYP0E@HqeFk~9WWyTqKvT2 ziH_0H;7;5yDFROOkM~#SY}L~V#~x5|4n(mETgjm~e2ql;-oBYGvk^vIPDx=znTOU; zou^YgU$lS{kwXKok8z}UZlBXlrmCIIq8YnEc4Bz0FGkE>4y&#A^mFEfrIZGBqpuc~!V7-=d0M$a3h4 zz7+D1s4+43e1wmd4z)3oL`Re0?GGsiA`vG!H01?25Xl$uMP*-BU_5920)oh!Yxo#Q zC1{`=a-qw)9FY-^YxSN3we!ho*|9$zJUTuvQD*2zdZ=X*@c*gBU0FAUgVrkU@Jl{i zX3#(dE3hAtA_MJ-K+pRME0DQCxX&wP+0^y(f6>C!z5wBac^sN(yG#~LWC;odT+-H zLtV?AllJ|(P^MldfSj<}rv-p5!X0BE1>AZN&zWA9Z69Q{XX)Z18|qd=r7hZTTirv* z4(1}%d`QtCnT~Hm^5`XDjn5c>AdfAS3@kUn{UgBn!w=hdIS#}Gn{!x}vnCzSN13mc z4AL0PGhcJ_(rMvr8^@rT4C)*qX>Rm17&r7B5T0`5`r6S1G6?xpvi9o9Rf>=TIEyX^ zYb8je!%wAQV3iy>u#!msqI$pHEIIogJ9uvNHLxix3&WFHGxcyJuAU3kU(rV8bBNLNYNC#f z#`8@~)k%2R7?ABB2>M~5BvzPqj`sATex}s1!UuL2Dna7m5so%n6vDjfGu1z3#2L#L z+UK~h>w4NmBjzgkg!RS7J40%I3V)Not6F~Y$DAy$e=}n{23DHm={^pUZq$`sjgL7vd%}g4}20SAD7h_AYO0;&DD&#>{;aj$r8nV zY*6)olmGPIXwqC#%OdY}gAR%|V^9;(&SMym)YoU=B*QEO1w=}NoMM&a@6`jGg#@vU&j z9vXWG$1(_Oy?)oYKqM@E7ya`qOS5S-;U+v8!gi-Q0n1bEVD+?(!8Zf6FSPP5_Hd6Y zV#V>0#Q_gnJQGBuL4wVw18pN#1WoE~@S(dI&+2 z=McOGPPTmQpyc-;SUU>dm24$SUKZ7@CZbWqx`dB-PVv zlvh?V!}~mF51I{X3s-GG`Ln$F2Tvg0Lah;+7xNB&vZ-S@(tY0U1<(O&7<2Iae!2bQ z7nFc@KW;2(LM@Ae&G|0>!$qxK($0H6@)MnxN=cEXsdmENX%^)PP`hu2(9-shf(r(Ht>0l*A9 z&)xh)6QwWjqQzy7J^QC7S8t5Dlb309qU2N_Afs{MC=zTYDG8iyuq7r|cIyX-70?*K zYnw|flGR|~48TS=yz8hYW*Qg^AE-&Wk!0J%kOOI`QI-h*dIO<|EfD+(%`VP6H3`m+ zZ@s@`wqHPH8+NWGn@RGjj<*2x5RQ40GRgn&gM{3<3lrx*!G$8ZG88}cFfrfX57&#) z3Ln9t8WZ3;VXgXgV5V+la7hQ1@riSK`V~BLN$bmLBkHQGvpozDxZaW!1kLC+x-$Zt zj*T=;*162|i@6^gD1vD|#jt){%9o8S)HtEN={3AFv9_#9(zvx*#KeTH0hv-IpDUJ> z>nS5Nt2hZZ2NBh+VC`iD*Y9N`&ngXe8)`O4+A)sA;CDT%O{&mdQ||LqGY_|VEisDu zT1v55F1^~lSXS}?plJWcKb1-m8a0ddwG)^H3_?bWz_X90DUWr;d)*&%VFN2jOqAlo z1%4$+VL=#l)fKx4{izN~6HMv&@KFh$bOd})DKpnm=y4#`_0j2+xZg4h%)ZUV2qYPr zfQ$n3qQ1a7T?4*M+`E=_L!k5P-c;V_Swt~i4&&qk%C+p^{3E|G$q&7M>I)7fZ7T?< zQ>JH`@7bW{6J_FrVmXM%Z3S5v)^)&tPcFQKY?hOi{eI;O?pg{h5nC7r-pE5Tf=V0r zh&@C58$M4snG1BDujWNEGe?Wo4XSOGn=-JPL8`^*#5lHeUz04LEH?d`|L;LUG7ptp z4g*a!4pfQp(44glFo#gja-c4CKH;CGKOk--KKWSG$=CB=fCJMxnj^Pqy$X$%QVuz*Uj&)y7{hi0@ zfGAIE0cwJpr;d?#GD&$Z@4>6Uxgv>j98Vnwsy9KYsh#o02y6+XzmJE4jOXSbV{&Me z+1StdPVd`aV}Ey81@j4L0)Ej$tL4!1{N98|yl^I{mU7{%(4UU>ZVQ);n)vWV9S+p2 z>GlU>8zhgii)UM%gW0m1DxbOUm#W+u-yz&SE6jrYtyBAH;xdJs!{WG~p|kNNwT_Iy zd|U0noU&<1C1H@?)Q%i*S}gpRQh8V=c+Fvf>5C4L6rZs8DGM6g4>ShbmP}3Wl(eUYqnnJV4aM7dmF1*?WKUXS|V5wC3n+y z7(F{U6{QOANSP!UGTMN5hae6lzSjXm-MM))`EF%tAN%U*WNU)L#W`qTX^RYJ!?U4cFZPS9$x1 zcdK2$WvE#MA5GCxlazX@8`y`U+Ko^r6^lX)&QChli#udyk@bn9G4v)pr1#h#P`-a| zvQ(5e1GHL9$BeD>@cnX$rgbfYR3te%x^a4QW3vCgXfanf};NSgN)Zm`!v% zA2e5UT|F+Ch2)e@*-dSKAyTX}J>Q(ilgGMw#G5^fKd|76B+pjD9AhuevzpYhG&qN;#a3nG?(F z*r)K@Uv$Qa0(NvDh;*z;Y+vVk2=6WfNx3Cej5kBfmE9jvP zDQ!AeUvEu)48M7Mx3~6u?i%6t{BCA!4n*Q7wyXxrDmX{_i@VZ%5aYFd&z4cJROr}c z@R>@OC-Xygqt*4EEd}&Sb!sNM38v0}8Na&{g9NsYc#XInUs{O@-_{d(9N8*tH3)Qg zNJsRI5tJ`b+!c4>!8KZG_XH<6%1In@2NPZL>(1J0$Q=V+IqcBkGXtYC2Rq!LP;8A! zDCedqCLIx!DF1YM2grj3U*T&VKS+r1TAN~oV7=;I(Q_Uc6@nRd+BB8c6;sQlzO6yM z?KLvioZ08ZA_L`hcvCAn1E{LO-x{>1x8<+MmxGfgdr*M3f;Zcn8O{lJnL}+?FII_a z6Nr%Ja~qz*qqoSR&0>GZk>ccs3--9MR)|sZ3)aJxOhJNS0}e>LOQJFG%&@tbpn34G zYQ>G>tbXY20yl}6HpW&b@s9*4uKUAh?OgVd|%+r#jM0gcq-~mAp3b&|e`#0e(5v zl49&e5-!AwF;9p)i555jZNUe-fgD_(mK0tU8f7{{j}lYfEgV(q$uIWNyidSCO2x9R z<$a~@()Yb+m4!M1iNPRX6eQ48-9 zrzQTf_=5dZ_zBW4I9@WpoTX9b0>MQ-M)o~+Fk|nMbTgwG>ngKDtRtQwA8jW}L(sXZ z3(QD^&pG|cC9eU6ufYSpov={Iy*Oa=nqJ~k`?6w70?B@b@Lz_(%jKkM5;S!mTA-=} zafAwbT^Gdr8r+13CaE88F|>E$_?d~=-s>rkwTU5{abF|lU2Ch;pvJ5Q8?-Y^Xc7HU ztz)EbB?86m!v=o&y=;!ZfV??nv4}4g0`rC~PAx!W<*$^>cN*+xU`;FQ*nO{UQ}Z;_ zZDUAO3E-z>C0i8GR=5xQ6q)x@tixy5D#@Z_WQq(95QQIn>){80yLVI)(dIu*2D{D< zWn{0U!j8vbYe~} z@<`j2LB5~N8wAN*PY-%4)om3XM1lZf+-+9DWU}5f^BzB^3a|e7x0SW$u;6Plecua@ zch9J?sW#o3iHv!g7xF795i!!;HVXJiDXLqPg^91y)wQqhxTEsK+=!7pK7d8D8OKl< zB3xIRSu(p@N2D^oY{JL4&#fA}Gb5gQr_0KtE+Tvq2A6Oh2Zk;L7JXFxyQ_zQd>ESp z4$ANnT%DPTA+p?^{hZVaDQ3SO*Zh~nm8i_HbgERbMEzp5ZG4zU0n?7}jiU2~f@n9} zo+J4WdDpE-A4_)XT+wNwrr=;s36RXl+rVMkNGjm{2#>-vJBFKkLPW08X9=l40Indr z9P^}4{Az>pO&|y(pArV-D+*vY41N3FT9@)hv^H#P3OybT3iM$BnH7JZhtXhdSwR)O z(26VLyK)|)h>u%^Cfy5r@6!tS`olRPX;(Fb>rleYD1YnQ5EWmVOI0d?7b4>z(h(v@ z_ZE;+BS32xAfDUWo#=jxDGgPkzK$g+C{TJXmswG4bl)tAQs6Bx^9CYHn6HP-m7enlE}|X`;<+X# zAiNnd?ycst5<6Frw(+kBGLyU#+q_g+zOnX@Jf9im3c|M#M+Im$8&%}vhK7ULtgg1w zm&3RmsK|hN@_A<;{}aFh00kuu0N?K5qF^aZkAAGiU<&c`H(!>zc9%rw2hnGQcv}5O z5|BJ3&|^ z8D<3!-f0HA4Z)*oz>q0B#Rx7OGwr^0*$$s}toJ&Gx@@T}BO233E1xVJ!_g!JV%#J+ zEg#hT5q{x7Bxe)zD^(bym=L3S3Q=kYno7!-&nGaXFu?gKAtx*=SCE6o)2}^7MwByIFbMDBW)Hgvur({ra5&F4x%N2ja>O^U;_ zUlynhEl6U3+6ftDNe`FW(agl|*Pxv0`m)efTTd4v;T&L2A|9b}hmadCaH>*|xhtWX55KBD(m?z3yjtUyJ|~&{xhVQ32{qb~{YBO9roto62d%}$O>ge) zrLwBk^pw2(q&D#XEUfR>ICb!#&SttoD0DHnODm&;h@rb}K@SZdc3z%DZyc0$HCt}E zT?W^b00#AtcIMJ87i#O3a3SetAg-!}vC(e?*uE_%J%d~LDe&m62O4~pljou_O~2X{ zC(v-iMZ(HvKGc=9-1V{&6fKNWeRue?#|*Mh#`v@C9@SZ+W9XOy#c5L8kHkyi7R?g> zO*wJfY5lpk>riE%DQSKtHkw2(N-h}^u#k@{XO!`S0B{*&c-I{Y#8}tGO}ZpA4sMq2 zp?u5009xz*pSsZgA(a(38@WVjBl>YoOH)q(6Do2>b^;p^ugv!}p4^uAjgH9*o zy+22P!fb5BkCG2})_Q5bM!h1x;4rmP!Brow^%{j~y`oRH zQSp@T3kjp_Ge~JZ*a97nfg-I(lEMJ-m12hN>yoBLlgcmPBJ>tC;1d4*HEld}v`FZ5 zQYvaNx(dDz;#N|rIt!6M5jvW6K&L64-=74i)~Xqa!u2wk0a_x>KdRscaT?3o7BAc} z^7KHwy9?Ltx~Y0|w&AcH+)7Pq{*2b`)XFj1wFy8Jf*iW{JY0!#!OXuJWTK`-f-gEn95?>I|p$s6lDI*rpL>a;^MQ&J# zh6y&(r{QRH!wdt*^yZM{P=M)ECLmTVpMV7``{Dhd?y!DUg;y}fU2N(sI&FWmDF7QD36bd6M~_@AvZxf}s+C>`T9-~m z6A6CTTUqxg1Vgo?612)QB@0<6;Jt>BUpTzEm~D9M{Snearczi@787cj&>-tzs1VRU zqOPjBdpMA8H>01P+b)Lp;!|j=4C#-M=kF|P2&5boJ}~Btr~*`DtO0(rn@ww$n!o_0 z7XuIEr0Mm^mh(Y+%;U5Ma|fLq3;aNID>g9erO@jP9q1~NdbY?{^!vW60^ZfbCu`ny z8VcF;Zr6J{x3y1eP*K`ou zd>*&#-<=V=9qbr|gdP*~B26cx{WV>Abl#8KNXRj6=CL3iA~vC3ItR|+pj7`rB9?&6 zU{?hLI=Ys)Ex*(*d-bSz*$Q~?u|q(RzG;~F34I^+pS&7eGpD{cP-VZj(2IlU*$W#P zrYG*xPLKv|7|Dm%f=7-zquQj)-M{rm8Q10`z<)9!PfQ68KTZcGCfg9)?9JuF1Y1D6 zn4GN{M>){B$`6A)I$HIoN%KEy2@Ma`LF&@X@H8`|&+Jam9hd0mhRD#>1h{b2y@Bpd zD92oe-Hdky=`B+yRz$*m=OG219=N?2m`u{kQ!!ZW2L3-te&=m#?iou(_q1$AzimAcsK*-JDT+;4(Ujxnj0UaF+!#aL|WjInGKOeB_5y? zjXDT$(o<2AqiJyXf+rExbU69X(v@y32@!I=FYRzXl`f#{b5OncSa~0N$oPB5 z!C(1{`E3|+C>4vio3$go7Je_f;j2{Z$j;Q2CIYee1i$FbI?&g~302qb$~S}1)Dl7x zM!rX6$fS$S07{^Kev|%>KFMse4#8;lY!z!sB{`t_Dc&)=!MWoP|1L<91H(|1esi#uMe3%7~{ikRHBGl@=of34L&*##!N3)Bl z7q`}|`w7p8=qOkd>G|)<4t!Npb|vYZT9Cpi6crfx-YUsuAIvAg^d2w+o^{#kEJRK+ zQk8p>H=&;sJ-R-b7LwdGXVhqkPQoceipA$qEA>x1%#Aur^Kn2-`8*?i>YQBMma7nz zN;J9or+d(X8|E=QtE1zAO6q1+U;bH_plVO>?l4LS(GY=w`B%JxBm$p0qzp>UA3l|I zF+t-fo+>L2Oi6PA8ntQZfO7~EAJL{<&JuM-<@AXp+FDgg)%Vhck6soQPZoc8S!qXc zD-sPv9{}NfoA1%;e;!$ZXS?c)z9%~0(E!>}Xx?;g4?}r7S+N_BVMgPz9e?KLvh8F= zkl0u*f071~C3#2dDhXaYt7mN0g~c&!(pbvxlyNc-i<(v3jFIwl`>~GN5wAkmNiHYG zxA&|#t5}fg;@TR47Wc~$j}|brIwIqNAY6?5GY~G+v^NBeb*l_MMzc9@x3AT+evy^- z8MVRA3d_hMHlJeDj}9{rtozLj?yiuXQQS5B4!z?U!B&@=8!8XbMdF_@Z%L)%L+NJ= zeYl4`s&TOyp{oFTojgAuDEoD#Eh_5!ou69_`yVmU-Wj0F)gb#VjbxN5Ze@>}8%!8= zQRgXIU#8aw$<=fOFlT>l@f()2f1D)2oTJau*S$I?6thv4LRCiX403S)Hp00oGWB~<|fL{JWCh`T+YG1~jCdh)Ep8Z?C*y54)5L+77x*mwUP0_`4CEuWGd&!JQia0GVgiRM42-}QD=6>?%g^Bp0qt@Ljh~x4rug&)W_1ouisGvv0;+3Tk^ohu9MT=+9p`O) zQM!Nyb8m8t@OuRSCv+f7jo0n%1##^w4YJT`A$Y)ck6#FH7a0>KY<(2fr9|0c>xn35 z&St-&jd+ZR1L@lnVYwoJR6<-dsMv_cl=H#{8fY#TPL~a40&6eq7}pn3s})t%1;B)c z(9OGR6B;88M}z@v_I=b((8vwk6-3O-F`2=DMkhk1b=(lxr_N)E)E_S}0riZ?%Q63( zCqsj2fWZQZNlZRu0PdpRQ3>pj*OqnJ_-U09SE=lH_bR8w6-GzrG`Iv%y=9 z3V%{_Q}4HPFfM4mCmovI7(dj}s%3r1G*-D42^&Qmo+2D_v^#)(%H*Gt=0qaGRI zMS;lkb=qz%G&D72Zx`V06y>^;OpaZ~ssa)`pKpLec%(Sl2;Xv)e8H-P7yTycBba$w z9nXUFy4_zsE|{%D%@$_MpXUiI1I-6k?XuFE z22AqTqSn4w650WOR#k5004>It9>YK?9`m*m`1+W?#R<0Ivyt?E` zD9!9@@WdYX!@lse`)vLuIzriM^ZwLEirE)pf`(T`#{_Ad6=q1$ywBz=tfY+{jk@JPHp^Z|u9;42k; z-A#`@k}%KzWmu{f~~CdfP$&LKZy{G;_x@{owpmgB+UoR zofRM?EV9aHx2zo-#&peur^mFG6Pb7tuf+xN8*&nC8um^nw|$CA7!Y&y9NHSU>whQ= zm~mfb1(u*#86+J)zaUp#Z<2$&Gd4zgDDXNu;Dy$m98KiW>1i1Q7>TCgUcFzEz(Dno z3WH5pTMT9Xl&?l8Mx?x7`wksZ71`fMEYMrL<>e!_yJ?QAE^HlU01lZW(@A5?!TQ!8 zBWeU6yP+~!p)`4|B8uKtBhqH9ExvDb1l}mgSZCd*0J1AWn*xIu3#j=_itj9k3cJ*u zhbn8vtohRo7tdNiFU^aVdeSX?7x89&L5agCX%4<>-|4X`{u+F62QaYFDB(9!-MnKu z9*LsCXWjfVM(a6g_GTfzV-y`hgt0903`M_xXury(j>4O)AYMdvq`e$mojRKC5EbVO zs(ZDb5u#k@)UivwmP`k3$&yaw2+OO>M!&KQOWiKgb`Yr|4l-MigK0kM<1bQg;|TU6 z&i0`BkVZxz4?UWPY-bg!2&_K+TY{;9xwmLiO5GXpv1gstrZNq z1J%^kut!6<9qkef>9RzGxl=p-6T^;sZGZ$jN+OIfdvhX;e|R-4!RHQRbQ4ns0G3#?&jp{4 z!Tp?#?k{b3dvdJo4^1;+2@*LkJD7lb0dQ_uoUJI>WL$E4sXS#N(4dBp)6mw1t^@M4 zIukvz8IgpcvCfZ;5o4#R9xh?X&g3$;N(DwI(pl}=`6Kgs|W zO2EE|gP?P|({mSmF$5ViE1Cnz$2U1xLkI z9V5MQKG1TSCj9eX41yBdltz`lmcj4F6YjKFB}_RTD$W+;H5&CB2{P5KeUllVv+Eu{ z?BQoIPj3Li5x&V!){jB9rFmkFz*@QRD_U_*-gqd+0A?W}>778DA(Jw#*n;(>a-dJi zU@y`2a)s3H-;>;#&t(r|U7JH;0ef8l)iLEs+M@$-SDmnJW@r2G3in0pOx!zD3mzUJ zA30#y4iV9_@=CQs0qG-D=Y|%~$8Y7`!d@^E-VWKnAVxCehO7T@j=rP*6nt#3`vPb`1 z!(;b`Blb4Zc{1Vm!`PIekmu+z+lmP47JPd(zQ(UIpgCx|98>J3s`Cip9pxQMOrL_y zqRXZ95lL=b6PvOYM;3Bp9X|PI>emEOkjWO>W4-55Pm&wvBKyKBvAp5Z)^H(qVe*2t zVWvKXG1~%$)e?MSfrpL#1x?&@tjFq$?~v>q2aA&}XW3QsZkp>$jDO)PsO9wMjF+ylxaMwyXCx1dCMxZ6&`)T(H5K`L=wsmM&LP1mu zH;v7^e0lD&A#S)-7hQzHmcF!m64h1Cr7jl7hz{Z#?8?Xx1aJli@foCN8)OC1TZCn* z8n@62&{09?_5-vrqIEEjG9(S6C)I4Jp%BH%1mBP|42Y}HS2#toj(P_3lV3&xF;=BE z6uf7LdaicSE_zUsD{on>yBH)xXwv=3M;ElZlDoyZ^GrY=wP55o1UaP&hK?3LF1e{Y9)KJ4zDhWk zE4I^3o;POE^#w0W4*njBvBs)`CqJ0k$-&`gtQswREls-4R(XcytTe-P_2m6uZn)(` z23@<`eepg@djF__UP_e#3srFU;|_dvyjv|BI=pCE7ZeQYso$v+|2Qz}pTibx)K9*H z@R9Ex1vV~%w)Ih}3*JpoLkp65t)#NeuP*&tCFK@=iHUObT@?jywnU0-N39?H;_f`$iI>4;X5iPhA!SOyd- zo&@vb9kP|YqHQ%o1KroZB1yx4H(VpV5@AD4MC}dDQ>^6L=}SVj)mM`CA<8iv@>9rw z`Zog`m5j`&9CsHO#LV{2ZjY`}bICT51~507Z~&&RHy2>{tJ&}R)v`68qN5p=4`6#g z!F@`S3ZIJ;9>oniP)EQ}!X45~j8Z{2Y=u$GdgS#^@|q;d4W;VQYPZMKn( zzhhUr5l{1P_KnUa`BkrxF2F|7xQ`up?37mZkL8(Gr}DPe!-UceycXH_JD3ocEefG( zf3JhP`?OOg*h>&?j+LRinc8{2TwZ9>96E|99%&Rkf;s_j6Myi_ShB&9#0GCmu1(;x zxngwUP>42A0hLx2R+6M*ccxYtWi-n0StFXt7;3Pi_0x`b!O7;oABG~|wrFddD@S6N zLx1-V7`GP7^87m(*s;mAyPc|TCX0<8VW5AsRRW%IR<<~-wJD?vXM|2KTE6zf6GL^3 z6cr_ZqViX9Jk@2Ax}YD6>xwe;y1AvGapc4?9jkTkBB72-_-g8erFM5lK9~0Rb&b+{ z^?l3%uCnn5gF`*s3-z}3?V~p+N$mc!>xsicyS}Al8}LC85ZI^-yBcy{3zhAnpGbE% zJ9k0b^#tKquth;5Tq~R4&SrLD`}hUMFNPRCm>OdJEU1}N^xW8s(XtnDtI1X zNikLXV>bk$>;&X^1T8u>z3VXP3-PRmn_hDh32PFQk8-7^C(i&OOIK1aLKXcxIFfA= zOlDiv!CRy&8ilr~dfjOBmHs;>*OmG_fka&hfs6BgjpQVmgXVpP6+CBpQIY4$yS^{; zZAOj&ec_iLB@*8Qf=z84II|~eA4eF!FhOoKvwu6W<2!@wbdq0DpPAP_Uo+aNk_p!Db9iQaPii3U5GK7yG)kXhk7i5_|NDw4g(i z6jFY=eMqjWQF+X&c&r1uGQ+@v2%d7_4yP{A98`#wLS}mX z$lzjQ3c#3Z)3qY@&jhmco#fxV1R)0;%79k=;t;lS6Gi(@cGhg9#7@N?*g%&`A&Mz;dbYJT^o|`6ph{VFnaAA*$iD008(=$o~Cud zIE8~`q9xG!^;~cff9m92?nn5)dOq%p5$9okFu>Y8undH_&Oj?HsK*R(`21=nk04;a zR+H{+3AQ>%J>OtV3$KZHKWTBv4}P}}^XuGKRNj4N>_D$cB046(ATVcJq-+gQP3TTE zQ`8rV{AKXkxJ9ea;0AsC9j?5YnB?~HyKk|?B&}J=SaV`=mW~dbN2i~l;}6q(9gm|a zJ9MR;du6$!B}6)(n#u`Z=AQSwMzKC!|aEW}vv^o^kSKg2SJqA_ zMNfj#*(dHyn^KK2-z76N*Y4}pR(FWeASKst$}r77kJ`lyUaO3>Oy?)RbfU_wmj;mE zY>k0sj06y8%s}c3pX?OsYF+la(Fwkx@~bQyKnq^0mg7O?mk>p9=oe?Tx{5_C%~4`y zbyJU7ubQS{$&o7AVxo&LBLUJpbj3Y%jDWW?G^HB%7-fi70*4=%7YV%1 zl{TJ_dsD+kS+%?gb5I(cm&w_Qa3nz7&W1~Ottho83^Ol)Y_UWc5`QKdo!tbP>kk*d z_epcS-PP*lcOtD&d@nuCPcrBz`bQ8L^U1Rr<)428twXt~BS?5ADi$S><#skcq$pb( zZhw2#=M?jaVo}mWf7wR_1$X9DBM~Bfz8}T}l>6_BSCwYsdaIWBD%qn*_+7Th5J-+x zHYubI?y2S6?u+41RGIraYGAzBMnH(0r!2Ud+RJ^rWi7p{#?IKo{GoBqTY_MCkT2fY zuxI>Wlm&4Lwb5;a#~WcF#3YhJ{rQ7LtLqmCLb{uYLBs8A2?GK--*ICLOAeL5UcP!& zY5OO8b9gt2=Z4aY2|?(DIN3lDtdl4Q}4XFB+JacM_t)K@V+6Z;h|C#qWv!9%l@X1XPp$_jIx@au!8q!$xT9^(_i3bqd z28>Nf*N`u*(UvmwXCfLK#vGD>k;^avCib!^h9C?Gth| zVdrz^zx39HW<&z>n%s&9qAyrj!zlaa9X#z>_G%5@By$G5Ol2uXV*BELcN%<<73KL# zQ7$3EO$_C9Aw97D(gT%h7Qv^aaA2uQj5{NT4>%(#f-E&7uSmv1_1QT9tXH{A>$uSA zQ?G9cH3j+`V7&wY^br)KDJEye1gf828Q6Yk9fycW_DD{+*IB9;&Y}Ow;|JKc*VEX8 z1SXS>o#~hHXW4El;}LOMP?M1gTuoQC92EX|uAnXFN*vpoXD%XUax%^u218|ht-uE* z-M+pfy9(oq4f&%8ES190n!gS3ze13?9t8CTD|= zULP|GvP7=}0X(kj{Q}dUcuDactfNP3YlsYf_gQ| z3b2h!{w%h_%nt0D#5tg}#<_FSp!~mA;G+%67RA_F-mPv$;22|YoE=pXR zVjm2dyhHLzJzkPJsor56Y@-5)BaQC?R}VZY68u0J$H9gk9G)J`xA;q@Ig~wtv~2QTCo4um7{(yRMvw55+oIe-?W}8aa#*iq6ZX0 zczML{`c_VWK?6Oobnh)r^&E*~dZaZeE~CMPo8btv5#?|@Vo!X+HW97}Ygmc5h6#KRPl}$N{;zg{}MB*N*CpC?q@SBubkBrP&?Eoj79NcOyuaL{2qe~Gj%1)LO}?m1i2qRr*AMMS z$T@^=C^VZ_SqG1sE=JTs`5r5~J;+Oc?B;g+cEG(Yu-|Z+4t{w{R4Z94 zyIGUsUvedN8y)-Xcnk0yr7!N}(Ezr+6mH*JOc=HMuzbJ7pcIFmX^fe9H&Hol0T2fI z$?#%n_V2Q~SOa*3_0qTxnH?w>1)ih;Jl|Zr)sstS08*2O?hFgH5i8&|kFRkk1UWFB zR42oIYurGK=mAv`gPEMl(CSAE{w&Iz0$=Z;WysLZdx7sD!_jz1bXyiPI)0XFPoh>4 z^C(PP05wPK#FZys0YdbxOSy7S63EI%epJo2E@aWT9u(Lm6XB7SUiukxZjSqA`G!3vRQF=x_DaXcv?4#)xswbo0Uin$*m=|f{>EYwX*A#{dET8tC1v2Obycxf_#MgV+V zjqw7?cTr?xjf3ZtMh<#QX(Y9b6o*x2j)LuKfEcX}i^H`=3vbYwS`h_4qpXD0T`E=! zQOt*($F%IU%?URafLq+dc-*qLEWh3bdP7V%G@oI+d3B5z9n}<6VG`Z@E^={D-?UFB zO@+x0A%K_1k?vt%Z`X*vGE66#3>)LE3duO0toi3=)m?kyjoE08ot*2+LXEq_!qbMqV$W8I1MaxVvZOQV*5**UfO)k!y8EJw| zgH0+pI#biRkO<2gUo^LGAz1cHGEXYo{0S9@k#W;#k!hwmNlrmIxY3~fbV~s1L!IQY zbsS3eI2zg$@DP$Ej?~%|UO{E%S-v-q`a^cUNC7J!P+n2eY9CUXgfzE_p^Ia`U_!P4 zJy0kzwjQ2FYt%3BsDTY>g$m)3F(#gu)7gn#FndM2)-mhX1Gs5L8V?61^7PzRDlLrt zvfDRv(WsOyhOmM#{2aq7%9LZGgkr)Nj5L5x6Kk%t= zeRUKH6-I%Gg5ST#skXz4;iEgofC3W~3l-JJVr6<1Nhpk=OYrBEYj+ME%;BXuAGOge z(*Vh6jrs4OP08k>+CgMCYt=r??U5Q7W_fH4LJ-o0g)RtM2^;KlSoLyes1tcR!pHuH zgs;~`yRT)`QVi zSd%@5!X@Lp>oaClxG}`f1de6V`=<F6uGOGDhbHidV z3I3W0%d))-w`p?aBBfP>W*v0chG$Wgw5RF1_sLVbtA`E)J2C-wD$0I@O@^H(ss?Qs z3D|{e8a3=trS;)!& literal 0 HcmV?d00001 diff --git a/manta-parameters/src/lib.rs b/manta-parameters/src/lib.rs index 612a6b7b1..941137608 100644 --- a/manta-parameters/src/lib.rs +++ b/manta-parameters/src/lib.rs @@ -24,8 +24,71 @@ #[cfg(feature = "std")] extern crate std; +#[cfg(feature = "std")] +use std::{fs, io, path::Path}; + #[cfg(feature = "download")] -use {anyhow::Result, std::path::Path}; +use anyhow::Result; + +/// Git Utilities +#[cfg(feature = "git")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "git")))] +pub mod git { + use super::*; + use core::fmt; + use std::{borrow::ToOwned, error, string::String}; + + #[doc(inline)] + pub use git2::*; + + /// Errors for the [`current_branch`] Function + #[derive(Debug, PartialEq)] + pub enum CurrentBranchError { + /// Current Git HEAD reference is not at a branch + NotBranch, + + /// Unable to generate shorthand for the branch name + MissingShorthand, + + /// Git Error + Git(Error), + } + + impl From 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")] @@ -47,9 +110,10 @@ pub mod github { pub const CRATE: &str = "manta-parameters"; /// Default GitHub Branch - pub const DEFAULT_BRANCH: &str = "main"; + pub const DEFAULT_BRANCH: &str = "feat/new-circuits"; /// 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. /// - /// # Crypto Safety + /// # Safety /// /// Prefer the [`download`] method which checks the data against a given checksum. #[inline] @@ -104,6 +168,24 @@ pub mod github { ); Ok(()) } + + /// Downloads data from `data_path` relative to the given `branch` to a file at `path` without verifying + /// that the data matches the `checksum`. + #[inline] + pub fn unsafe_download

( + branch: &str, + data_path: &str, + path: P, + checksum: &[u8; 32], + ) -> Result<()> + where + P: AsRef, + { + let _ = checksum; + let path = path.as_ref(); + download_unchecked(branch, data_path, path)?; + Ok(()) + } } /// Verifies the `data` against the `checksum`. @@ -116,11 +198,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 +259,51 @@ 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 { + /// Downlaods 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), } - }; + } + + /// Unsafe download + #[inline] + fn unsafe_download

(path: P) -> Result<()> + where + P: AsRef, + { + Self::download(path) + } } /// Defines a data marker type for download-required data from GitHub LFS and checksum from disk. @@ -166,61 +313,42 @@ 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<()> + fn unsafe_download

(path: P) -> $crate::Result<()> where - P: AsRef, + P: AsRef<$crate::Path>, { - match $crate::verify_file(&path, Self::CHECKSUM) { - Ok(true) => Ok(()), - _ => Self::download(path), - } + $crate::github::unsafe_download( + $crate::github::DEFAULT_BRANCH, + concat!("/data/", $path, ".lfs"), + path, + ::CHECKSUM, + ) } } }; } -/// 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 +356,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!( - VoidNumberCommitmentScheme, - "Void Number Commitment Scheme Parameters", - "/data/pay/testnet/parameters/void-number-commitment-scheme", + UtxoAccumulatorItemHash, + "UTXO Accumulator Item Hash Parameters", + "pay/testnet/parameters/utxo-accumulator-item-hash", + ); + define_dat!( + 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 +456,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,7 +513,7 @@ 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

( - branch: &str, - data_path: &str, - path: P, - checksum: &[u8; 32], - ) -> Result<()> - where - P: AsRef, - { - let _ = checksum; - let path = path.as_ref(); - download_unchecked(branch, data_path, path)?; - Ok(()) - } } /// Verifies the `data` against the `checksum`. @@ -295,15 +277,6 @@ pub trait Download: HasChecksum { _ => Self::download(path), } } - - /// Unsafe download - #[inline] - fn unsafe_download

(path: P) -> Result<()> - where - P: AsRef, - { - Self::download(path) - } } /// Defines a data marker type for download-required data from GitHub LFS and checksum from disk. @@ -333,18 +306,6 @@ macro_rules! define_lfs { ::CHECKSUM, ) } - #[inline] - fn unsafe_download

(path: P) -> $crate::Result<()> - where - P: AsRef<$crate::Path>, - { - $crate::github::unsafe_download( - $crate::github::DEFAULT_BRANCH, - concat!("/data/", $path, ".lfs"), - path, - ::CHECKSUM, - ) - } } }; } @@ -556,38 +517,4 @@ mod test { } Ok(()) } - - /// Downloads all data from GitHub and checks if they are the same as the data known locally to - /// this Rust crate. - #[ignore] // NOTE: We use this so that CI doesn't run this test while still allowing developers to test. - #[test] - fn unsafe_download_all_data() -> Result<()> { - let current_branch = super::git::current_branch()?; - let directory = tempfile::tempdir()?; - println!("[INFO] Temporary Directory: {directory:?}"); - let checksums = parse_checkfile("data.checkfile")?; - let directory_path = directory.path(); - for file in walkdir::WalkDir::new("data") { - let file = file?; - let path = file.path(); - if !path.is_dir() { - println!("[INFO] Checking path: {path:?}"); - let target = directory_path.join(path); - fs::create_dir_all(target.parent().unwrap())?; - github::unsafe_download( - ¤t_branch, - path.to_str().unwrap(), - &target, - get_checksum(&checksums, path)?, - )?; - assert!( - equal_files(&mut File::open(path)?, &mut File::open(&target)?)?, - "The files at {:?} and {:?} are not equal.", - path, - target - ); - } - } - Ok(()) - } } diff --git a/manta-pay/Cargo.toml b/manta-pay/Cargo.toml index 74bce96d5..c1712a381 100644 --- a/manta-pay/Cargo.toml +++ b/manta-pay/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "manta-pay" -version = "0.5.6" +version = "0.5.7" edition = "2021" authors = ["Manta Network "] readme = "README.md" @@ -35,21 +35,19 @@ required-features = ["clap", "groth16", "simulation"] [features] # Enable Arkworks Backend arkworks = [ - "ark-std", "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", - "num-integer", ] # Enable Download Parameters download = ["manta-parameters/download", "std"] # Enable Groth16 ZKP System -groth16 = ["manta-crypto/ark-groth16", "ark-snark", "arkworks"] +groth16 = ["manta-crypto/ark-groth16", "arkworks"] # Enable HTTP Signer Client http = ["manta-util/reqwest", "serde", "network"] @@ -113,9 +111,6 @@ websocket = [ [dependencies] aes-gcm = { version = "0.9.4", default-features = false, features = ["aes", "alloc"] } -ark-groth16 = { version = "0.3.0", optional = true, default-features = false } -ark-snark = { version = "0.3.0", optional = true, default-features = false } -ark-std = { version = "0.3.0", optional = true, default-features = false } bip0039 = { version = "0.10.1", optional = true, default-features = false } bip32 = { version = "0.3.0", optional = true, default-features = false, features = ["bip39", "secp256k1"] } blake2 = { version = "0.10.4", default-features = false } @@ -129,7 +124,6 @@ manta-crypto = { path = "../manta-crypto", default-features = false, features = 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 } -num-integer = { version = "0.1.45", 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"] } diff --git a/manta-pay/src/bin/simulation.rs b/manta-pay/src/bin/simulation.rs index b45861dbb..e77bf4dae 100644 --- a/manta-pay/src/bin/simulation.rs +++ b/manta-pay/src/bin/simulation.rs @@ -21,8 +21,6 @@ use manta_accounting::transfer::canonical::generate_context; use manta_crypto::rand::{OsRng, Rand}; use manta_pay::{config::FullParametersRef, simulation::Simulation}; -// cargo run --release --package manta-pay --all-features --bin simulation - /// Runs the Manta Pay simulation. pub fn main() { let simulation = Simulation::parse(); diff --git a/manta-pay/src/config/utxo_utilities.rs b/manta-pay/src/config/utxo_utilities.rs index dbc3c81f3..9067946fc 100644 --- a/manta-pay/src/config/utxo_utilities.rs +++ b/manta-pay/src/config/utxo_utilities.rs @@ -52,10 +52,7 @@ where U: ToBytesGadget, F: Field, { - u.to_bytes()? - .into_iter() - .map(|x| x.value()) - .collect::, _>>() + u.to_bytes()?.into_iter().map(|x| x.value()).collect() } /// Extracts a vector of bytes from an [`UnsignedInteger`]. @@ -66,8 +63,5 @@ where T: ToBytesGadget, F: Field, { - u.to_bytes()? - .into_iter() - .map(|x| x.value()) - .collect::, _>>() + u.to_bytes()?.into_iter().map(|x| x.value()).collect() } diff --git a/manta-pay/src/lib.rs b/manta-pay/src/lib.rs index 9caa51605..d22f940ac 100644 --- a/manta-pay/src/lib.rs +++ b/manta-pay/src/lib.rs @@ -29,10 +29,6 @@ pub mod crypto; #[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] pub mod config; -#[cfg(feature = "groth16")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] -pub mod signer; - #[cfg(feature = "key")] #[cfg_attr(doc_cfg, doc(cfg(feature = "key")))] pub mod key; @@ -41,6 +37,10 @@ pub mod key; #[cfg_attr(doc_cfg, doc(cfg(all(feature = "parameters"))))] pub mod parameters; +#[cfg(feature = "groth16")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] +pub mod signer; + #[cfg(all(feature = "groth16", feature = "simulation"))] #[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "simulation"))))] pub mod simulation; diff --git a/manta-pay/src/simulation/ledger/mod.rs b/manta-pay/src/simulation/ledger/mod.rs index 77db5bb50..0a2d68681 100644 --- a/manta-pay/src/simulation/ledger/mod.rs +++ b/manta-pay/src/simulation/ledger/mod.rs @@ -284,15 +284,14 @@ impl ReceiverLedger for Ledger { utxo: Self::ValidUtxo, note: FullIncomingNote, ) { - let temp = self.parameters.item_hash(&utxo.0, &mut ()); // todo let _ = super_key; - let shard = self - .shards - .get_mut(&MerkleTreeConfiguration::tree_index(&temp)) - .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(&temp); + self.utxo_forest.push(&utxo_hash); } } @@ -308,7 +307,7 @@ impl TransferLedger for Ledger { #[inline] fn check_source_accounts( &self, - asset_id: &::AssetId, + asset_id: &AssetId, sources: I, ) -> Result, InvalidSourceAccount> where @@ -351,7 +350,7 @@ impl TransferLedger for Ledger { #[inline] fn check_sink_accounts( &self, - asset_id: &::AssetId, + asset_id: &AssetId, sinks: I, ) -> Result, InvalidSinkAccount> where @@ -398,7 +397,7 @@ impl TransferLedger for Ledger { fn update_public_balances( &mut self, super_key: &TransferLedgerSuperPostingKey, - asset_id: ::AssetId, + asset_id: AssetId, sources: Vec>, sinks: Vec>, proof: Self::ValidProof, diff --git a/manta-pay/src/simulation/mod.rs b/manta-pay/src/simulation/mod.rs index 87ba81c92..bdc0eed67 100644 --- a/manta-pay/src/simulation/mod.rs +++ b/manta-pay/src/simulation/mod.rs @@ -151,7 +151,6 @@ impl Simulation { GS: FnMut(usize) -> S, Error: Debug, { - // FIXME: rng assert!( self.config() .run::<_, _, _, AssetList, _, _, _, _, _, _>(ledger, signer, |_| ChaCha20Rng::from_entropy(), |event| { diff --git a/manta-pay/src/test/balance.rs b/manta-pay/src/test/balance.rs index c1debc1e0..faaa7377e 100644 --- a/manta-pay/src/test/balance.rs +++ b/manta-pay/src/test/balance.rs @@ -57,7 +57,7 @@ macro_rules! define_tests { 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."] From 5b811bcfd9126e786a94238c95b91c4d94b94165 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 1 Dec 2022 17:32:52 +0100 Subject: [PATCH 26/44] more comments addressed Signed-off-by: Francisco Hernandez Iglesias --- manta-benchmark/benches/private_transfer.rs | 31 ++--- manta-benchmark/benches/to_private.rs | 25 ++-- manta-benchmark/benches/to_public.rs | 31 ++--- manta-parameters/src/lib.rs | 2 +- manta-pay/src/config/utxo.rs | 126 +++++--------------- manta-pay/src/test/compatibility.rs | 27 ++--- manta-pay/src/test/payment.rs | 82 +++++++------ 7 files changed, 119 insertions(+), 205 deletions(-) diff --git a/manta-benchmark/benches/private_transfer.rs b/manta-benchmark/benches/private_transfer.rs index 21974b9f0..0809be538 100644 --- a/manta-benchmark/benches/private_transfer.rs +++ b/manta-benchmark/benches/private_transfer.rs @@ -17,10 +17,10 @@ //! Private Transfer Benchmarks use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use manta_crypto::rand::{OsRng, Rand}; +use manta_crypto::rand::OsRng; use manta_pay::{ parameters, - test::payment::{private_transfer::prove_full, UtxoAccumulator}, + test::payment::{private_transfer::prove as prove_private_transfer, UtxoAccumulator}, }; fn prove(c: &mut Criterion) { @@ -28,15 +28,11 @@ fn prove(c: &mut Criterion) { let mut rng = OsRng; let (proving_context, _, parameters, utxo_accumulator_model) = parameters::generate().unwrap(); group.bench_function("private transfer prove", |b| { - let asset_id = black_box(rng.gen()); - let asset_value = black_box(rng.gen()); b.iter(|| { - prove_full( - &proving_context, + prove_private_transfer( + &proving_context.private_transfer, ¶meters, &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), - asset_id, - asset_value, &mut rng, ); }) @@ -48,20 +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_full( - &proving_context, - ¶meters, - &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), - rng.gen(), - rng.gen(), - &mut rng, - ) - .1, - ); + let transferpost = black_box(prove_private_transfer( + &proving_context.private_transfer, + ¶meters, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + &mut rng, + )); group.bench_function("private transfer verify", |b| { b.iter(|| { - private_transfer.assert_valid_proof(&verifying_context.private_transfer); + transferpost.assert_valid_proof(&verifying_context.private_transfer); }) }); } diff --git a/manta-benchmark/benches/to_private.rs b/manta-benchmark/benches/to_private.rs index f2842268e..9641bae1e 100644 --- a/manta-benchmark/benches/to_private.rs +++ b/manta-benchmark/benches/to_private.rs @@ -17,27 +17,20 @@ //! To Private Benchmarks use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use manta_crypto::rand::{OsRng, Rand}; -use manta_pay::{ - parameters, - test::payment::{to_private::prove_full, UtxoAccumulator}, -}; +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("to_private prove", |b| { - let asset_id = black_box(rng.gen()); - let asset_value = black_box(rng.gen()); + group.bench_function("to private prove", |b| { b.iter(|| { - prove_full( + prove_to_private( &proving_context.to_private, ¶meters, - &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), - asset_id, - asset_value, + &utxo_accumulator_model, &mut rng, ); }) @@ -49,15 +42,13 @@ fn verify(c: &mut Criterion) { let (proving_context, verifying_context, parameters, utxo_accumulator_model) = parameters::generate().unwrap(); let mut rng = OsRng; - let transferpost = black_box(prove_full( + let transferpost = black_box(prove_to_private( &proving_context.to_private, ¶meters, - &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), - rng.gen(), - rng.gen(), + &utxo_accumulator_model, &mut rng, )); - group.bench_function("mint verify", |b| { + group.bench_function("to private verify", |b| { b.iter(|| { transferpost.assert_valid_proof(&verifying_context.to_private); }) diff --git a/manta-benchmark/benches/to_public.rs b/manta-benchmark/benches/to_public.rs index f116fb6a2..7a03ca671 100644 --- a/manta-benchmark/benches/to_public.rs +++ b/manta-benchmark/benches/to_public.rs @@ -17,10 +17,10 @@ //! To Public Benchmarks use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use manta_crypto::rand::{OsRng, Rand}; +use manta_crypto::rand::OsRng; use manta_pay::{ parameters, - test::payment::{to_public::prove_full, UtxoAccumulator}, + test::payment::{to_public::prove as prove_to_public, UtxoAccumulator}, }; fn prove(c: &mut Criterion) { @@ -28,15 +28,11 @@ fn prove(c: &mut Criterion) { let mut rng = OsRng; let (proving_context, _, parameters, utxo_accumulator_model) = parameters::generate().unwrap(); group.bench_function("to public prove", |b| { - let asset_id = black_box(rng.gen()); - let asset_value = black_box(rng.gen()); b.iter(|| { - prove_full( - &proving_context, + prove_to_public( + &proving_context.to_public, ¶meters, &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), - asset_id, - asset_value, &mut rng, ); }) @@ -48,20 +44,15 @@ fn verify(c: &mut Criterion) { let mut rng = OsRng; let (proving_context, verifying_context, parameters, utxo_accumulator_model) = parameters::generate().unwrap(); - let to_public = black_box( - prove_full( - &proving_context, - ¶meters, - &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), - rng.gen(), - rng.gen(), - &mut rng, - ) - .1, - ); + let transferpost = black_box(prove_to_public( + &proving_context.to_public, + ¶meters, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + &mut rng, + )); group.bench_function("to public verify", |b| { b.iter(|| { - to_public.assert_valid_proof(&verifying_context.to_public); + transferpost.assert_valid_proof(&verifying_context.to_public); }) }); } diff --git a/manta-parameters/src/lib.rs b/manta-parameters/src/lib.rs index 44c2c8fe9..8ba02ae77 100644 --- a/manta-parameters/src/lib.rs +++ b/manta-parameters/src/lib.rs @@ -110,7 +110,7 @@ pub mod github { pub const CRATE: &str = "manta-parameters"; /// Default GitHub Branch - pub const DEFAULT_BRANCH: &str = "main"; + pub const DEFAULT_BRANCH: &str = "feat/mantapay-v1"; /// Returns the Git-LFS URL for GitHub content at the given `branch` and `data_path`. #[inline] diff --git a/manta-pay/src/config/utxo.rs b/manta-pay/src/config/utxo.rs index 63671a462..f6e1eba9c 100644 --- a/manta-pay/src/config/utxo.rs +++ b/manta-pay/src/config/utxo.rs @@ -21,8 +21,8 @@ use crate::{ poseidon::{ Spec2 as Poseidon2, Spec3 as Poseidon3, Spec4 as Poseidon4, Spec5 as Poseidon5, }, - utxo_utilities, Compiler, ConstraintField, EmbeddedScalar, EmbeddedScalarField, - EmbeddedScalarVar, Group, GroupCurve, GroupVar, + Compiler, ConstraintField, EmbeddedScalar, EmbeddedScalarField, EmbeddedScalarVar, Group, + GroupCurve, GroupVar, }, crypto::{ encryption::aes, @@ -45,9 +45,7 @@ use manta_crypto::{ arkworks::{ algebra::{affine_point_as_bytes, ScalarVar}, constraint::{fp::Fp, rem_mod_prime, Boolean, FpVar}, - ec::ProjectiveCurve, ff::{try_into_u128, PrimeField}, - r1cs_std::R1CSVar, serialize::{CanonicalSerialize, SerializationError}, }, eclair::{ @@ -870,18 +868,11 @@ impl encryption::convert::key::Encryption for IncomingAESConverter>; #[inline] - fn as_target(source: &Self::EncryptionKey, _: &mut Compiler) -> Self::TargetEncryptionKey { - let mut key = [0u8; 32]; - source - .0 - .value() - .expect("Getting the group element from GroupVar is not allowed to fail.") - .into_affine() - .serialize(&mut key[..]) - .expect("Serialization error"); - let mut hasher = Blake2s256::new(); - Digest::update(&mut hasher, key); - hasher.finalize().into() + 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." + ) } } @@ -909,18 +900,11 @@ impl encryption::convert::key::Decryption for IncomingAESConverter>; #[inline] - fn as_target(source: &Self::DecryptionKey, _: &mut Compiler) -> Self::TargetDecryptionKey { - let mut key = [0u8; 32]; - source - .0 - .value() - .expect("Getting the group element from GroupVar is not allowed to fail.") - .into_affine() - .serialize(&mut key[..]) - .expect("Serialization error"); - let mut hasher = Blake2s256::new(); - Digest::update(&mut hasher, key); - hasher.finalize().into() + 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." + ) } } @@ -956,29 +940,11 @@ impl encryption::convert::plaintext::Forward for IncomingAESConverter< type TargetPlaintext = encryption::Plaintext>; #[inline] - fn as_target(source: &Self::Plaintext, _: &mut Compiler) -> Self::TargetPlaintext { - let mut target_plaintext = Vec::::with_capacity(AES_PLAINTEXT_SIZE); - target_plaintext.extend( - utxo_utilities::bytes_from_gadget(&source.utxo_commitment_randomness) - .expect("Converting FpVar into bytes is not allowed to fail."), - ); - target_plaintext.extend( - utxo_utilities::bytes_from_gadget(&source.asset.id) - .expect("Converting FpVar into bytes is not allowed to fail."), - ); - target_plaintext.extend(utxo_utilities::from_little_endian( - utxo_utilities::bytes_from_unsigned(&source.asset.value) - .expect("Converting U128 into bytes is not allowed to fail."), - 16, - )); - assert_eq!( - target_plaintext.len(), - AES_PLAINTEXT_SIZE, - "Wrong plaintext length: {}. Expected {} bytes", - target_plaintext.len(), - AES_PLAINTEXT_SIZE - ); - Array::from_unchecked(target_plaintext) + 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." + ) } } @@ -1606,18 +1572,11 @@ impl encryption::convert::key::Encryption for OutgoingAESConverter>; #[inline] - fn as_target(source: &Self::EncryptionKey, _: &mut Compiler) -> Self::TargetEncryptionKey { - let mut key = [0u8; 32]; - source - .0 - .value() - .expect("Getting the group element from GroupVar is not allowed to fail.") - .into_affine() - .serialize(&mut key[..]) - .expect("Serialization error"); - let mut hasher = Blake2s256::new(); - Digest::update(&mut hasher, key); - hasher.finalize().into() + 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." + ) } } @@ -1645,18 +1604,11 @@ impl encryption::convert::key::Decryption for OutgoingAESConverter>; #[inline] - fn as_target(source: &Self::DecryptionKey, _: &mut Compiler) -> Self::TargetDecryptionKey { - let mut key = [0u8; 32]; - source - .0 - .value() - .expect("Getting the group element from GroupVar is not allowed to fail.") - .into_affine() - .serialize(&mut key[..]) - .expect("Serialization error"); - let mut hasher = Blake2s256::new(); - Digest::update(&mut hasher, key); - hasher.finalize().into() + 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." + ) } } @@ -1691,25 +1643,11 @@ impl encryption::convert::plaintext::Forward for OutgoingAESConverter< type TargetPlaintext = encryption::Plaintext>; #[inline] - fn as_target(source: &Self::Plaintext, _: &mut Compiler) -> Self::TargetPlaintext { - let mut target_plaintext = Vec::::with_capacity(OUT_AES_PLAINTEXT_SIZE); - target_plaintext.extend( - utxo_utilities::bytes_from_gadget(&source.id) - .expect("Converting FpVar into bytes is not allowed to fail."), - ); - target_plaintext.extend(utxo_utilities::from_little_endian( - utxo_utilities::bytes_from_unsigned(&source.value) - .expect("Converting U128 into bytes is not allowed to fail."), - 16, - )); - 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) + 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." + ) } } diff --git a/manta-pay/src/test/compatibility.rs b/manta-pay/src/test/compatibility.rs index 9bc8060b3..11f93f41c 100644 --- a/manta-pay/src/test/compatibility.rs +++ b/manta-pay/src/test/compatibility.rs @@ -22,11 +22,11 @@ use crate::{ parameters::load_parameters, signer::base::UtxoAccumulator, test::payment::{ - private_transfer::prove_full as prove_private_transfer, - to_private::prove_full as prove_to_private, to_public::prove_full as prove_to_public, + private_transfer::prove as prove_private_transfer, to_private::prove as prove_to_private, + to_public::prove as prove_to_public, }, }; -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`. #[test] @@ -38,30 +38,25 @@ fn compatibility() { let _ = &prove_to_private( &proving_context.to_private, ¶meters, - &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), - rng.gen(), - rng.gen(), + &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, + &proving_context.private_transfer, ¶meters, - &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), - rng.gen(), - [rng.gen::<_, u128>() / 2, rng.gen::<_, u128>() / 2], + &mut utxo_accumulator, &mut rng, ) - .1 .assert_valid_proof(&verifying_context.private_transfer); let _ = &prove_to_public( - &proving_context, + &proving_context.to_public, ¶meters, - &mut UtxoAccumulator::new(utxo_accumulator_model), - rng.gen(), - [rng.gen::<_, u128>() / 2, rng.gen::<_, u128>() / 2], + &mut utxo_accumulator, &mut rng, ) - .1 .assert_valid_proof(&verifying_context.to_public); } + +// cargo test --release --package manta-pay --lib --all-features -- test::compatibility::compatibility --exact --nocapture diff --git a/manta-pay/src/test/payment.rs b/manta-pay/src/test/payment.rs index a899ac9f6..2689e7565 100644 --- a/manta-pay/src/test/payment.rs +++ b/manta-pay/src/test/payment.rs @@ -17,11 +17,9 @@ //! Prove and Verify Functions for Benchmark and Test Purposes use crate::config::{ - self, utxo::{MerkleTreeConfiguration, UtxoAccumulatorItem, UtxoAccumulatorModel}, - Asset, AssetId, AssetValue, Authorization, AuthorizationContext, Config, FullParametersRef, - MultiProvingContext, Parameters, PrivateTransfer, ProvingContext, Receiver, Sender, ToPrivate, - ToPublic, TransferPost, + Asset, AssetId, AssetValue, Authorization, Config, FullParametersRef, MultiProvingContext, + Parameters, PrivateTransfer, ProvingContext, Receiver, ToPrivate, ToPublic, TransferPost, }; use manta_accounting::transfer::{self, test::value_distribution}; use manta_crypto::{ @@ -37,28 +35,6 @@ pub type SpendingKey = transfer::SpendingKey; pub type UtxoAccumulator = TreeArrayMerkleForest, 256>; -/// Builds a new internal pair for use in [`private_transfer::prove`] and [`to_public::prove`]. -#[inline] -fn internal_pair_unchecked( - parameters: &Parameters, - authorization_context: &mut AuthorizationContext, - asset: Asset, - rng: &mut R, -) -> (Receiver, Sender) -where - R: CryptoRng + RngCore + ?Sized, -{ - let (receiver, pre_sender) = transfer::internal_pair::( - parameters, - authorization_context, - rng.gen(), - asset, - Default::default(), - rng, - ); - (receiver, pre_sender.assign_default_proof_unchecked()) -} - /// Utility Module for [`ToPrivate`] pub mod to_private { use super::*; @@ -141,6 +117,8 @@ pub mod to_private { /// Utility Module for [`PrivateTransfer`] pub mod private_transfer { + use manta_accounting::transfer::internal_pair; + use super::*; /// Generates a proof for a [`PrivateTransfer`] transaction including pre-requisite @@ -230,38 +208,50 @@ pub mod private_transfer { /// Generates a proof for a [`PrivateTransfer`] transaction. #[inline] - pub fn prove( + pub fn prove( proving_context: &ProvingContext, parameters: &Parameters, - utxo_accumulator_model: &UtxoAccumulatorModel, + 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, sender_0) = internal_pair_unchecked( + let (receiver_0, presender_0) = internal_pair::( parameters, &mut authorization.context, + address, Asset::new(asset_id, values[0]), + Default::default(), rng, ); - let (receiver_1, sender_1) = internal_pair_unchecked( + 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), + FullParametersRef::new(parameters, utxo_accumulator.model()), proving_context, Some(&spending_key), rng, @@ -273,6 +263,8 @@ pub mod private_transfer { /// Utility Module for [`ToPublic`] pub mod to_public { + use manta_accounting::transfer::internal_pair; + use super::*; /// Generates a proof for a [`ToPublic`] transaction including pre-requisite [`ToPrivate`] @@ -355,31 +347,47 @@ pub mod to_public { /// Generates a proof for a [`ToPublic`] transaction. #[inline] - pub fn prove( + pub fn prove( proving_context: &ProvingContext, parameters: &Parameters, - utxo_accumulator_model: &UtxoAccumulatorModel, + 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 (_, sender_0) = - internal_pair_unchecked(parameters, &mut authorization.context, asset_0, rng); - let (receiver_1, sender_1) = internal_pair_unchecked( + 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), + FullParametersRef::new(parameters, utxo_accumulator.model()), proving_context, Some(&spending_key), rng, From 655b194341861dcf227320bc50f2951e3df4372d Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 1 Dec 2022 18:46:59 +0100 Subject: [PATCH 27/44] sample issue solved Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/transfer/test.rs | 35 +++++++++- manta-pay/src/bin/simulation.rs | 2 +- manta-pay/src/config/poseidon.rs | 4 +- manta-pay/src/config/utxo.rs | 10 ++- manta-pay/src/crypto/poseidon/hash.rs | 6 +- manta-pay/src/crypto/poseidon/mod.rs | 4 +- manta-pay/src/parameters.rs | 2 +- manta-pay/src/test/payment.rs | 6 +- manta-pay/src/test/transfer.rs | 94 +++++++++------------------ 9 files changed, 78 insertions(+), 85 deletions(-) diff --git a/manta-accounting/src/transfer/test.rs b/manta-accounting/src/transfer/test.rs index 82d8d74c1..e6c1be5c1 100644 --- a/manta-accounting/src/transfer/test.rs +++ b/manta-accounting/src/transfer/test.rs @@ -30,7 +30,8 @@ use core::{ }; use manta_crypto::{ accumulator::Accumulator, - 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; @@ -522,3 +523,35 @@ where pre_sender, )) } + +/// 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 validity_check_with_fuzzing( + verifying_context: &VerifyingContext, + post: &TransferPost, + rng: &mut R, +) where + A: Clone + Sample + Fuzz, + C: Configuration, + C::ProofSystem: ProofSystem>, + ProofSystemError: Debug, + R: RngCore + ?Sized, + TransferPost: Debug, +{ + 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-pay/src/bin/simulation.rs b/manta-pay/src/bin/simulation.rs index e77bf4dae..e2242f524 100644 --- a/manta-pay/src/bin/simulation.rs +++ b/manta-pay/src/bin/simulation.rs @@ -25,7 +25,7 @@ use manta_pay::{config::FullParametersRef, simulation::Simulation}; pub fn main() { let simulation = Simulation::parse(); let mut rng = OsRng; - let parameters = rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(); + let parameters = rng.gen(); let utxo_accumulator_model = rng.gen(); let (proving_context, verifying_context) = generate_context( &(), diff --git a/manta-pay/src/config/poseidon.rs b/manta-pay/src/config/poseidon.rs index 69b20d3be..29e588154 100644 --- a/manta-pay/src/config/poseidon.rs +++ b/manta-pay/src/config/poseidon.rs @@ -92,7 +92,7 @@ pub mod test { use manta_crypto::{ arkworks::constraint::fp::Fp, encryption::{Decrypt, Encrypt}, - rand::{OsRng, Rand, Sample}, + rand::{OsRng, Sample}, }; /// Tests Poseidon duplexer encryption works. @@ -100,7 +100,7 @@ pub mod test { fn poseidon_duplexer_test() { const N: usize = 3; let mut rng = OsRng; - let duplexer = rng.gen::<((), ()), FixedDuplexer<1, Spec>>(); + 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()); diff --git a/manta-pay/src/config/utxo.rs b/manta-pay/src/config/utxo.rs index f6e1eba9c..22c5fe8cb 100644 --- a/manta-pay/src/config/utxo.rs +++ b/manta-pay/src/config/utxo.rs @@ -1781,7 +1781,7 @@ impl schnorr::HashFunction for SchnorrHashFunction { _: &mut (), ) -> EmbeddedScalar { let mut hasher = Blake2s256::new(); - Digest::update(&mut hasher, b"domain tag"); // FIXME: Use specific domain tag + Digest::update(&mut hasher, b"manta-pay/1.0.0/Schnorr-hash"); Digest::update( &mut hasher, affine_point_as_bytes::(&verifying_key.0), @@ -2018,7 +2018,7 @@ pub mod test { algebra::{HasGenerator, ScalarMul}, arkworks::constraint::fp::Fp, encryption::{Decrypt, EmptyHeader, Encrypt}, - rand::{OsRng, Rand, Sample}, + rand::{OsRng, Sample}, }; /// Checks that encryption of light incoming notes is well-executed for [`Config`]. @@ -2068,7 +2068,7 @@ pub mod test { let mut rng = OsRng; let encryption_key = Group::gen(&mut rng); let header = EmptyHeader::default(); - let base_poseidon = rng.gen::<((), ()), IncomingBaseEncryptionScheme>(); + 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); @@ -2101,9 +2101,7 @@ pub mod test { #[test] fn check_note_consistency() { let mut rng = OsRng; - let parameters = rng - .gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), protocol::Parameters>( - ); + 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); 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 5472d931c..a79a89d85 100644 --- a/manta-pay/src/crypto/poseidon/mod.rs +++ b/manta-pay/src/crypto/poseidon/mod.rs @@ -519,13 +519,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/parameters.rs b/manta-pay/src/parameters.rs index 7aa97606a..01755679f 100644 --- a/manta-pay/src/parameters.rs +++ b/manta-pay/src/parameters.rs @@ -65,7 +65,7 @@ pub fn generate_from_seed( ProofSystemError, > { let mut rng = ChaCha20Rng::from_seed(seed); - let parameters = rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(); + let parameters = rng.gen(); let utxo_accumulator_model: UtxoAccumulatorModel = rng.gen(); let full_parameters = FullParametersRef::new(¶meters, &utxo_accumulator_model); let (to_private_proving_context, to_private_verifying_context) = diff --git a/manta-pay/src/test/payment.rs b/manta-pay/src/test/payment.rs index 2689e7565..7dedc71dc 100644 --- a/manta-pay/src/test/payment.rs +++ b/manta-pay/src/test/payment.rs @@ -21,7 +21,7 @@ use crate::config::{ Asset, AssetId, AssetValue, Authorization, Config, FullParametersRef, MultiProvingContext, Parameters, PrivateTransfer, ProvingContext, Receiver, ToPrivate, ToPublic, TransferPost, }; -use manta_accounting::transfer::{self, test::value_distribution}; +use manta_accounting::transfer::{self, internal_pair, test::value_distribution}; use manta_crypto::{ accumulator::Accumulator, merkle_tree::{forest::TreeArrayMerkleForest, full::Full}, @@ -117,8 +117,6 @@ pub mod to_private { /// Utility Module for [`PrivateTransfer`] pub mod private_transfer { - use manta_accounting::transfer::internal_pair; - use super::*; /// Generates a proof for a [`PrivateTransfer`] transaction including pre-requisite @@ -263,8 +261,6 @@ pub mod private_transfer { /// Utility Module for [`ToPublic`] pub mod to_public { - use manta_accounting::transfer::internal_pair; - use super::*; /// Generates a proof for a [`ToPublic`] transaction including pre-requisite [`ToPrivate`] diff --git a/manta-pay/src/test/transfer.rs b/manta-pay/src/test/transfer.rs index 95bceeefb..b6cb832a6 100644 --- a/manta-pay/src/test/transfer.rs +++ b/manta-pay/src/test/transfer.rs @@ -20,20 +20,18 @@ use crate::{ config::{FullParametersRef, Parameters, PrivateTransfer, ProofSystem, ToPrivate, ToPublic}, test::payment::UtxoAccumulator, }; +use manta_accounting::transfer::test::validity_check_with_fuzzing; use manta_crypto::{ accumulator::Accumulator, constraint::{measure::Measure, ProofSystem as _}, - rand::{OsRng, Rand}, + rand::{OsRng, Rand, Sample}, }; /// Tests the generation of proving/verifying contexts for [`ToPrivate`]. #[test] fn sample_to_private_context() { let mut rng = OsRng; - let cs = ToPrivate::unknown_constraints(FullParametersRef::new( - &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), - &rng.gen(), - )); + 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."); } @@ -42,10 +40,7 @@ fn sample_to_private_context() { #[test] fn sample_private_transfer_context() { let mut rng = OsRng; - let cs = PrivateTransfer::unknown_constraints(FullParametersRef::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."); } @@ -54,10 +49,7 @@ fn sample_private_transfer_context() { #[test] fn sample_to_public_context() { let mut rng = OsRng; - let cs = ToPublic::unknown_constraints(FullParametersRef::new( - &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), - &rng.gen(), - )); + 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."); } @@ -69,7 +61,7 @@ fn to_private() { assert!( ToPrivate::sample_and_check_proof( &(), - &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), + &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), None, &mut rng @@ -86,7 +78,7 @@ fn private_transfer() { assert!( PrivateTransfer::sample_and_check_proof( &(), - &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), + &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), Some(&rng.gen()), &mut rng @@ -103,7 +95,7 @@ fn to_public() { assert!( ToPublic::sample_and_check_proof( &(), - &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), + &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), Some(&rng.gen()), &mut rng @@ -119,8 +111,7 @@ fn check_empty_message_signature() { let mut rng = OsRng; assert!( manta_crypto::signature::test::correctness( - &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), Parameters>() - .signature_scheme(), + &Parameters::gen(&mut rng).signature_scheme(), &rng.gen(), &rng.gen(), &vec![], @@ -134,7 +125,7 @@ fn check_empty_message_signature() { #[test] fn private_transfer_check_signature() { let mut rng = OsRng; - let parameters = rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(); + let parameters = rng.gen(); let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); let (proving_context, verifying_context) = PrivateTransfer::generate_context( &(), @@ -168,7 +159,7 @@ fn private_transfer_check_signature() { #[test] fn to_public_check_signature() { let mut rng = OsRng; - let parameters = rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(); + let parameters = rng.gen(); let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); let (proving_context, verifying_context) = ToPublic::generate_context( &(), @@ -202,7 +193,7 @@ fn to_public_check_signature() { #[test] fn private_transfer_check_zero_signature() { let mut rng = OsRng; - let parameters = rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(); + let parameters = rng.gen(); let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); let (proving_context, verifying_context) = PrivateTransfer::generate_context( &(), @@ -236,7 +227,7 @@ fn private_transfer_check_zero_signature() { #[test] fn to_public_check_zero_signature() { let mut rng = OsRng; - let parameters = rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(); + let parameters = rng.gen(); let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); let (proving_context, verifying_context) = ToPublic::generate_context( &(), @@ -275,7 +266,7 @@ fn to_private_generate_proof_input_is_compatibile() { matches!( ToPrivate::sample_and_check_generate_proof_input_compatibility( &(), - &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), + &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), None, &mut rng @@ -295,7 +286,7 @@ fn private_transfer_generate_proof_input_is_compatibile() { matches!( PrivateTransfer::sample_and_check_generate_proof_input_compatibility( &(), - &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), + &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), Some(&rng.gen()), &mut rng @@ -315,7 +306,7 @@ fn to_public_generate_proof_input_is_compatibile() { matches!( ToPublic::sample_and_check_generate_proof_input_compatibility( &(), - &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), + &rng.gen(), &mut UtxoAccumulator::new(rng.gen()), Some(&rng.gen()), &mut rng @@ -326,38 +317,6 @@ fn to_public_generate_proof_input_is_compatibile() { ); } -/* TODO: fix the fuzzing test bug, then uncomment. -/// 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(), - ); -} /// 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] @@ -367,7 +326,7 @@ fn to_private_proof_validity() { let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); 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."); @@ -375,11 +334,14 @@ fn to_private_proof_validity() { &proving_context, ¶meters, &mut utxo_accumulator, + None, &mut rng, ) - .expect("Random ToPrivate 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); } + /// Tests a [`PrivateTransfer`] proof is valid verified against the right public input and invalid /// when the public input has been fuzzed or randomly generated. #[test] @@ -389,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."); @@ -397,11 +359,14 @@ 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 [`ToPublic`] proof is valid verified against the right public input and invalid /// when the public input has been fuzzed or randomly generated. #[test] @@ -411,7 +376,7 @@ fn to_public_proof_validity() { let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); 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."); @@ -419,9 +384,10 @@ fn to_public_proof_validity() { &proving_context, ¶meters, &mut utxo_accumulator, + Some(&rng.gen()), &mut rng, ) - .expect("Random ToPublic 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); } -*/ From e481aecfbcf9b0d353118c93616ddc4df157af09 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 1 Dec 2022 19:32:08 +0100 Subject: [PATCH 28/44] public fields made private again Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/key.rs | 2 +- manta-accounting/src/wallet/mod.rs | 13 ++++++++++++- manta-accounting/src/wallet/signer.rs | 8 +++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/manta-accounting/src/key.rs b/manta-accounting/src/key.rs index f509d5dd6..03f878fd5 100644 --- a/manta-accounting/src/key.rs +++ b/manta-accounting/src/key.rs @@ -327,7 +327,7 @@ where M: AccountMap, { /// Account Collection - pub keys: H, // This has been made public because of compatibility issues with sdk. + keys: H, /// Account Map accounts: M, diff --git a/manta-accounting/src/wallet/mod.rs b/manta-accounting/src/wallet/mod.rs index 7855064f0..7b5bfff07 100644 --- a/manta-accounting/src/wallet/mod.rs +++ b/manta-accounting/src/wallet/mod.rs @@ -98,7 +98,7 @@ pub struct Wallet< checkpoint: S::Checkpoint, /// Signer Connection - pub signer: S, // This has been made public because of compatibility issues with sdk. + signer: S, /// Balance State assets: B, @@ -143,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> diff --git a/manta-accounting/src/wallet/signer.rs b/manta-accounting/src/wallet/signer.rs index f80b50d9d..9ea037caf 100644 --- a/manta-accounting/src/wallet/signer.rs +++ b/manta-accounting/src/wallet/signer.rs @@ -628,7 +628,7 @@ where /// 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 /// a local `assets` map for each account. - pub accounts: AccountTable, // This has been made public because of compatibility issues with sdk. + accounts: AccountTable, /// UTXO Accumulator utxo_accumulator: C::UtxoAccumulator, @@ -680,6 +680,12 @@ where ) } + /// Returns the [`AccountTable`]. + #[inline] + pub fn accounts(&self) -> &AccountTable { + &self.accounts + } + /// Returns the default account for `self`. #[inline] pub fn default_account(&self) -> Account { From c7b1e94a79cdbfd2774b17dd2f94a9c6b86bd19c Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 1 Dec 2022 19:35:47 +0100 Subject: [PATCH 29/44] compat deleted Signed-off-by: Francisco Hernandez Iglesias --- manta-pay/src/crypto/poseidon/compat.rs | 459 ------------------------ manta-pay/src/crypto/poseidon/mod.rs | 1 - 2 files changed, 460 deletions(-) delete mode 100644 manta-pay/src/crypto/poseidon/compat.rs 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/mod.rs b/manta-pay/src/crypto/poseidon/mod.rs index a79a89d85..00cb388be 100644 --- a/manta-pay/src/crypto/poseidon/mod.rs +++ b/manta-pay/src/crypto/poseidon/mod.rs @@ -31,7 +31,6 @@ 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; From 6142d47e3c0f8355101af34538227b649d45153e Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 1 Dec 2022 19:49:37 +0100 Subject: [PATCH 30/44] FIXME Signed-off-by: Francisco Hernandez Iglesias --- manta-pay/src/config/poseidon.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/manta-pay/src/config/poseidon.rs b/manta-pay/src/config/poseidon.rs index 29e588154..1c68600ca 100644 --- a/manta-pay/src/config/poseidon.rs +++ b/manta-pay/src/config/poseidon.rs @@ -31,8 +31,8 @@ impl poseidon::Constants for Spec<2> { impl poseidon::Constants for Spec<3> { const WIDTH: usize = 4; - const FULL_ROUNDS: usize = 8; // FIXME - const PARTIAL_ROUNDS: usize = 55; // FIXME + const FULL_ROUNDS: usize = 8; + const PARTIAL_ROUNDS: usize = 55; } impl poseidon::Constants for Spec<4> { @@ -43,8 +43,8 @@ impl poseidon::Constants for Spec<4> { impl poseidon::Constants for Spec<5> { const WIDTH: usize = 6; - const FULL_ROUNDS: usize = 8; // FIXME: - const PARTIAL_ROUNDS: usize = 56; // FIXME + const FULL_ROUNDS: usize = 8; + const PARTIAL_ROUNDS: usize = 56; } impl poseidon::arkworks::Specification for Spec From cc64ef9850283f58d70130ed8632428aa7649064 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 1 Dec 2022 19:58:41 +0100 Subject: [PATCH 31/44] parameters branch changed to main + feature issue solved Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/Cargo.toml | 1 + manta-parameters/src/lib.rs | 2 +- manta-pay/src/test/compatibility.rs | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manta-accounting/Cargo.toml b/manta-accounting/Cargo.toml index 8eff93656..4bb02a009 100644 --- a/manta-accounting/Cargo.toml +++ b/manta-accounting/Cargo.toml @@ -47,6 +47,7 @@ std = ["manta-crypto/std", "manta-util/std"] test = [ "futures", "indexmap", + "manta-crypto/arkworks", "manta-crypto/rand", "parking_lot", "statrs" diff --git a/manta-parameters/src/lib.rs b/manta-parameters/src/lib.rs index 8ba02ae77..44c2c8fe9 100644 --- a/manta-parameters/src/lib.rs +++ b/manta-parameters/src/lib.rs @@ -110,7 +110,7 @@ pub mod github { pub const CRATE: &str = "manta-parameters"; /// Default GitHub Branch - pub const DEFAULT_BRANCH: &str = "feat/mantapay-v1"; + pub const DEFAULT_BRANCH: &str = "main"; /// Returns the Git-LFS URL for GitHub content at the given `branch` and `data_path`. #[inline] diff --git a/manta-pay/src/test/compatibility.rs b/manta-pay/src/test/compatibility.rs index 11f93f41c..12d9a1edb 100644 --- a/manta-pay/src/test/compatibility.rs +++ b/manta-pay/src/test/compatibility.rs @@ -29,6 +29,7 @@ use crate::{ 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."); @@ -58,5 +59,3 @@ fn compatibility() { ) .assert_valid_proof(&verifying_context.to_public); } - -// cargo test --release --package manta-pay --lib --all-features -- test::compatibility::compatibility --exact --nocapture From 563e95d240b24ad2c3f0f0d8b5bb10e4dcd1a1d8 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 1 Dec 2022 20:42:58 +0100 Subject: [PATCH 32/44] feature issue solved Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/manta-accounting/Cargo.toml b/manta-accounting/Cargo.toml index 4bb02a009..42683fd36 100644 --- a/manta-accounting/Cargo.toml +++ b/manta-accounting/Cargo.toml @@ -49,6 +49,7 @@ test = [ "indexmap", "manta-crypto/arkworks", "manta-crypto/rand", + "manta-crypto/test", "parking_lot", "statrs" ] From 2d52252ca1c61dfc0693706cdbb1355fae32c8cd Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 2 Dec 2022 13:27:46 +0100 Subject: [PATCH 33/44] generalised AssetMetadata to trait --- manta-accounting/src/asset.rs | 71 ++++-------- manta-accounting/src/transfer/canonical.rs | 5 +- manta-accounting/src/wallet/mod.rs | 45 ++++---- manta-accounting/src/wallet/signer.rs | 29 ++--- manta-accounting/src/wallet/test/mod.rs | 128 ++++++++++++--------- manta-pay/src/signer/client/http.rs | 6 +- manta-pay/src/signer/client/websocket.rs | 6 +- manta-pay/src/signer/mod.rs | 44 ++++++- manta-pay/src/simulation/mod.rs | 11 +- 9 files changed, 196 insertions(+), 149 deletions(-) diff --git a/manta-accounting/src/asset.rs b/manta-accounting/src/asset.rs index 3e4b54274..e719e2472 100644 --- a/manta-accounting/src/asset.rs +++ b/manta-accounting/src/asset.rs @@ -25,7 +25,6 @@ use alloc::{ collections::btree_map::{BTreeMap, Entry as BTreeMapEntry}, - format, string::String, vec, vec::Vec, @@ -1058,90 +1057,70 @@ where impl<'s, I, V, M> FusedIterator for SelectionKeys<'s, I, V, M> where M: AssetMap + ?Sized {} /// Asset Metadata -#[cfg_attr( - feature = "serde", - derive(Deserialize, Serialize), - serde(crate = "manta_util::serde") -)] -#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] -pub struct AssetMetadata { - /// Number of Decimals - pub decimals: u32, - - /// Asset Symbol - pub symbol: String, -} - -impl AssetMetadata { +pub trait AssetMetadata { /// Returns a string formatting of only the `value` interpreted using `self` as the metadata. - #[inline] - pub fn display_value(&self, value: V) -> String + fn display_value(&self, value: V) -> String where - for<'v> &'v V: Div, - { - // TODO: What if we want more than three `FRACTIONAL_DIGITS`? How do we make this method - // more general? - const FRACTIONAL_DIGITS: u32 = 3; - let value_base_units = &value / (10u128.pow(self.decimals)); - let fractional_digits = &value / (10u128.pow(self.decimals - FRACTIONAL_DIGITS)) - % (10u128.pow(FRACTIONAL_DIGITS)); - format!("{value_base_units}.{fractional_digits:0>3}") - } + for<'v> &'v V: Div; /// Returns a string formatting of `value` interpreted using `self` as the metadata including /// the symbol. - #[inline] - pub fn display(&self, value: V) -> String + fn display(&self, value: V) -> String where - for<'v> &'v V: Div, - { - format!("{} {}", self.display_value(value), self.symbol) - } + for<'v> &'v V: Div; } /// Metadata Display -pub trait MetadataDisplay { +pub trait MetadataDisplay +where + A: AssetMetadata, +{ /// Returns a string representation of `self` given the asset `metadata`. - fn display(&self, metadata: &AssetMetadata) -> String; + fn display(&self, metadata: &A) -> String; } /// Asset Manager -pub trait AssetManager { +pub trait AssetManager +where + A: AssetMetadata, +{ /// Returns the metadata associated to `id`. - fn metadata(&self, id: &I) -> Option<&AssetMetadata>; + fn metadata(&self, id: &I) -> Option<&A>; } /// Implements [`AssetManager`] for map types. macro_rules! impl_asset_manager_for_maps_body { - ($I:ident) => { + ($I:ident, $A: ident) => { #[inline] - fn metadata(&self, id: &$I) -> Option<&AssetMetadata> { + fn metadata(&self, id: &$I) -> Option<&A> { self.get(id) } }; } /// B-Tree Map [`AssetManager`] Implementation -pub type BTreeAssetManager = BTreeMap; +pub type BTreeAssetManager = BTreeMap; -impl AssetManager for BTreeAssetManager +impl AssetManager for BTreeAssetManager where I: Ord, + A: AssetMetadata, { - impl_asset_manager_for_maps_body! { I } + impl_asset_manager_for_maps_body! { I, A } } /// Hash Map [`AssetManager`] Implementation #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] -pub type HashAssetManager = HashMap; +pub type HashAssetManager = HashMap; #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] -impl AssetManager for HashAssetManager +impl AssetManager for HashAssetManager where I: Eq + Hash, + A: AssetMetadata, S: BuildHasher + Default, { - impl_asset_manager_for_maps_body! { I } + impl_asset_manager_for_maps_body! { I, A } } diff --git a/manta-accounting/src/transfer/canonical.rs b/manta-accounting/src/transfer/canonical.rs index cff2a0a4b..a8dbff85a 100644 --- a/manta-accounting/src/transfer/canonical.rs +++ b/manta-accounting/src/transfer/canonical.rs @@ -423,10 +423,11 @@ where /// 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) + pub fn display(&self, metadata: &A, f: F) -> (String, Option) where + A: AssetMetadata, F: FnOnce(&Address) -> String, - C::AssetValue: MetadataDisplay, + C::AssetValue: MetadataDisplay, { match self { Self::ToPrivate(asset) => (asset.value.display(metadata), None), diff --git a/manta-accounting/src/wallet/mod.rs b/manta-accounting/src/wallet/mod.rs index 7b5bfff07..694081d54 100644 --- a/manta-accounting/src/wallet/mod.rs +++ b/manta-accounting/src/wallet/mod.rs @@ -81,14 +81,16 @@ pub mod test; PartialEq(bound = "L: PartialEq, S::Checkpoint: PartialEq, S: PartialEq, B: PartialEq") )] pub struct Wallet< + A, C, L, S = signer::Signer, B = BTreeMapBalanceState<::AssetId, ::AssetValue>, > where + A: AssetMetadata, C: Configuration, L: ledger::Connection, - S: signer::Connection, + S: signer::Connection, B: BalanceState, { /// Ledger Connection @@ -107,11 +109,12 @@ pub struct Wallet< __: PhantomData, } -impl Wallet +impl Wallet where + A: AssetMetadata, C: Configuration, L: ledger::Connection, - S: signer::Connection, + S: signer::Connection, B: BalanceState, { /// Builds a new [`Wallet`] without checking if `ledger`, `checkpoint`, `signer`, and `assets` @@ -156,7 +159,7 @@ where /// Starts a new wallet with `ledger` and `signer` connections. #[inline] - pub async fn start(ledger: L, signer: S) -> Result> + pub async fn start(ledger: L, signer: S) -> Result> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -187,11 +190,11 @@ where /// Returns `true` if `self` contains at least every asset in `assets`. Assets are combined /// first by asset id before checking for membership. #[inline] - pub fn contains_all(&self, assets: A) -> bool + pub fn contains_all(&self, assets: I) -> bool where C::AssetId: Ord, C::AssetValue: AddAssign + Default, - A: IntoIterator>, + I: IntoIterator>, { AssetList::from_iter(assets) .into_iter() @@ -227,7 +230,7 @@ where /// [`InconsistencyError`] type for more information on the kinds of errors that can occur and /// how to resolve them. #[inline] - pub async fn restart(&mut self) -> Result<(), Error> + pub async fn restart(&mut self) -> Result<(), Error> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -241,7 +244,7 @@ where /// [`restart`](Self::restart) to avoid querying the ledger at genesis when a known later /// checkpoint exists. #[inline] - async fn load_initial_state(&mut self) -> Result<(), Error> { + async fn load_initial_state(&mut self) -> Result<(), Error> { self.signer_sync(Default::default()).await } @@ -256,7 +259,7 @@ where /// [`InconsistencyError`] type for more information on the kinds of errors that can occur and /// how to resolve them. #[inline] - pub async fn sync(&mut self) -> Result<(), Error> + pub async fn sync(&mut self) -> Result<(), Error> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -275,7 +278,7 @@ where /// [`InconsistencyError`] type for more information on the kinds of errors that can occur and /// how to resolve them. #[inline] - pub async fn sync_partial(&mut self) -> Result> + pub async fn sync_partial(&mut self) -> Result> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -284,7 +287,7 @@ where /// Pulls data from the ledger, synchronizing the wallet and balance state. #[inline] - async fn sync_with(&mut self) -> Result> + async fn sync_with(&mut self) -> Result> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -309,7 +312,7 @@ where async fn signer_sync( &mut self, request: SyncRequest, - ) -> Result<(), Error> { + ) -> Result<(), Error> { match self .signer .sync(request) @@ -371,8 +374,8 @@ where pub async fn sign( &mut self, transaction: Transaction, - metadata: Option, - ) -> Result, Error> { + metadata: Option, + ) -> Result, Error> { self.check(&transaction) .map_err(Error::InsufficientBalance)?; self.signer @@ -408,8 +411,8 @@ where pub async fn post( &mut self, transaction: Transaction, - metadata: Option, - ) -> Result> + metadata: Option, + ) -> Result> where L: ledger::Read, Checkpoint = S::Checkpoint> + ledger::Write>>, @@ -502,11 +505,12 @@ pub enum InconsistencyError { bound = "Asset: PartialEq, SignError: PartialEq, L::Error: PartialEq, S::Error: PartialEq" ) )] -pub enum Error +pub enum Error where + A: AssetMetadata, C: Configuration, L: ledger::Connection, - S: signer::Connection, + S: signer::Connection, { /// Insufficient Balance InsufficientBalance(Asset), @@ -526,11 +530,12 @@ where LedgerConnectionError(L::Error), } -impl From for Error +impl From for Error where + A: AssetMetadata, C: Configuration, L: ledger::Connection, - S: signer::Connection, + S: signer::Connection, { #[inline] fn from(err: InconsistencyError) -> Self { diff --git a/manta-accounting/src/wallet/signer.rs b/manta-accounting/src/wallet/signer.rs index 9ea037caf..e418881c3 100644 --- a/manta-accounting/src/wallet/signer.rs +++ b/manta-accounting/src/wallet/signer.rs @@ -59,8 +59,9 @@ use manta_util::{ use manta_util::serde::{Deserialize, Serialize}; /// Signer Connection -pub trait Connection +pub trait Connection where + A: AssetMetadata, C: transfer::Configuration, { /// Checkpoint Type @@ -84,7 +85,7 @@ where /// Signs a transaction and returns the ledger transfer posts if successful. fn sign( &mut self, - request: SignRequest, + request: SignRequest, ) -> LocalBoxFutureResult, SignError>, Self::Error>; /// Returns the [`Address`] corresponding to `self`. @@ -334,8 +335,8 @@ pub type SyncResult = Result, SyncError>; derive(Deserialize, Serialize), serde( bound( - deserialize = "Transaction: Deserialize<'de>", - serialize = "Transaction: Serialize" + deserialize = "Transaction: Deserialize<'de>, A: Deserialize<'de>", + serialize = "Transaction: Serialize, A: Serialize" ), crate = "manta_util::serde", deny_unknown_fields @@ -343,21 +344,22 @@ pub type SyncResult = Result, SyncError>; )] #[derive(derivative::Derivative)] #[derivative( - Clone(bound = "Transaction: Clone"), - Debug(bound = "Transaction: Debug"), - Eq(bound = "Transaction: Eq"), - Hash(bound = "Transaction: Hash"), - PartialEq(bound = "Transaction: PartialEq") + Clone(bound = "Transaction: Clone, A: Clone"), + Debug(bound = "Transaction: Debug, A: Debug"), + Eq(bound = "Transaction: Eq, A: Eq"), + Hash(bound = "Transaction: Hash, A: Hash"), + PartialEq(bound = "Transaction: PartialEq, A: PartialEq") )] -pub struct SignRequest +pub struct SignRequest where + A: AssetMetadata, C: transfer::Configuration, { /// Transaction Data pub transaction: Transaction, /// Asset Metadata - pub metadata: Option, + pub metadata: Option, } /// Signer Signing Response @@ -1338,8 +1340,9 @@ where } } -impl Connection for Signer +impl Connection for Signer where + A: AssetMetadata, C: Configuration, { type Checkpoint = C::Checkpoint; @@ -1359,7 +1362,7 @@ where #[inline] fn sign( &mut self, - request: SignRequest, + request: SignRequest, ) -> LocalBoxFutureResult, SignError>, Self::Error> { Box::pin(async move { Ok(self.sign(request.transaction)) }) } diff --git a/manta-accounting/src/wallet/test/mod.rs b/manta-accounting/src/wallet/test/mod.rs index ec2d95e16..9da32252a 100644 --- a/manta-accounting/src/wallet/test/mod.rs +++ b/manta-accounting/src/wallet/test/mod.rs @@ -183,10 +183,10 @@ pub struct ActionLabelled { } /// [`ActionLabelled`] Error Type -pub type ActionLabelledError = ActionLabelled>; +pub type ActionLabelledError = ActionLabelled>; /// Possible [`Action`] or an [`ActionLabelledError`] Variant -pub type MaybeAction = Result, ActionLabelledError>; +pub type MaybeAction = Result, ActionLabelledError>; /// Action Types #[cfg_attr( @@ -403,21 +403,22 @@ where /// 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") + Clone(bound = "Wallet: Clone"), + Debug(bound = "Wallet: Debug"), + Default(bound = "Wallet: Default"), + Eq(bound = "Wallet: Eq"), + PartialEq(bound = "Wallet: PartialEq") )] -pub struct Actor +pub struct Actor where + A: AssetMetadata, C: Configuration, L: Ledger, - S: signer::Connection, + S: signer::Connection, B: BalanceState, { /// Wallet - pub wallet: Wallet, + pub wallet: Wallet, /// Action Distribution pub distribution: ActionDistribution, @@ -426,17 +427,18 @@ where pub lifetime: usize, } -impl Actor +impl Actor where + A: AssetMetadata, C: Configuration, L: Ledger, - S: signer::Connection, + S: signer::Connection, B: BalanceState, { /// Builds a new [`Actor`] with `wallet`, `distribution`, and `lifetime`. #[inline] pub fn new( - wallet: Wallet, + wallet: Wallet, distribution: ActionDistribution, lifetime: usize, ) -> Self { @@ -456,7 +458,7 @@ where /// Returns the default address for `self`. #[inline] - async fn default_address(&mut self) -> Result, Error> { + async fn default_address(&mut self) -> Result, Error> { self.wallet .address() .await @@ -467,7 +469,7 @@ where #[inline] async fn public_balances( &mut self, - ) -> Result>, Error> + ) -> Result>, Error> where L: PublicBalanceOracle, { @@ -477,13 +479,16 @@ where /// Synchronizes the [`Wallet`] in `self`. #[inline] - async fn sync(&mut self) -> Result<(), Error> { + 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> { + async fn sync_with( + &mut self, + action: ActionType, + ) -> Result<(), ActionLabelledError> { self.sync().await.map_err(|err| action.label(err)) } @@ -493,8 +498,8 @@ where async fn post( &mut self, transaction: Transaction, - metadata: Option, - ) -> Result> { + metadata: Option, + ) -> Result> { self.wallet.post(transaction, metadata).await } @@ -506,7 +511,10 @@ where /// 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> + async fn sample_deposit( + &mut self, + rng: &mut R, + ) -> Result>, Error> where C::AssetValue: SampleUniform, L: PublicBalanceOracle, @@ -532,7 +540,10 @@ where /// This method samples from a uniform distribution over the asset IDs and asset values present /// in the balance state of `self`. #[inline] - async fn sample_withdraw(&mut self, rng: &mut R) -> Result>, Error> + async fn sample_withdraw( + &mut self, + rng: &mut R, + ) -> Result>, Error> where C::AssetValue: SampleUniform, R: RngCore + ?Sized, @@ -554,7 +565,7 @@ where &mut self, action: ActionType, rng: &mut R, - ) -> Result>, ActionLabelledError> + ) -> Result>, ActionLabelledError> where R: RngCore + ?Sized, { @@ -570,7 +581,7 @@ where /// [`ToPrivate`]: ActionType::ToPrivate /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_to_private(&mut self, rng: &mut R) -> MaybeAction + async fn sample_to_private(&mut self, rng: &mut R) -> MaybeAction where C::AssetValue: SampleUniform, L: PublicBalanceOracle, @@ -589,7 +600,7 @@ where /// [`ToPrivateZero`]: ActionType::ToPrivateZero /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_zero_to_private(&mut self, rng: &mut R) -> MaybeAction + async fn sample_zero_to_private(&mut self, rng: &mut R) -> MaybeAction where L: PublicBalanceOracle, R: RngCore + ?Sized, @@ -611,17 +622,17 @@ where /// [`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, - address: A, - ) -> MaybeAction + address: F, + ) -> MaybeAction where C::AssetValue: SampleUniform, L: PublicBalanceOracle, R: RngCore + ?Sized, - A: FnOnce(&mut R) -> Result>, Error>, + F: FnOnce(&mut R) -> Result>, Error>, { let action = if is_self { ActionType::SelfTransfer @@ -647,16 +658,16 @@ where /// [`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, - address: A, - ) -> MaybeAction + address: F, + ) -> MaybeAction where L: PublicBalanceOracle, R: RngCore + ?Sized, - A: FnOnce(&mut R) -> Result>, Error>, + F: FnOnce(&mut R) -> Result>, Error>, { let action = if is_self { ActionType::SelfTransfer @@ -684,7 +695,7 @@ where /// [`ToPublic`]: ActionType::ToPublic /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_to_public(&mut self, rng: &mut R) -> MaybeAction + async fn sample_to_public(&mut self, rng: &mut R) -> MaybeAction where C::AssetValue: SampleUniform, L: PublicBalanceOracle, @@ -703,7 +714,7 @@ where /// [`ToPublicZero`]: ActionType::ToPublicZero /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_zero_to_public(&mut self, rng: &mut R) -> MaybeAction + async fn sample_zero_to_public(&mut self, rng: &mut R) -> MaybeAction where R: RngCore + ?Sized, { @@ -719,7 +730,7 @@ where /// /// [`Skip`]: ActionType::Skip #[inline] - async fn flush_to_public(&mut self, rng: &mut R) -> MaybeAction + async fn flush_to_public(&mut self, rng: &mut R) -> MaybeAction where R: RngCore + ?Sized, { @@ -733,7 +744,7 @@ where /// Computes the current balance state of the wallet, performs a wallet restart, and then checks /// that the balance state has the same or more funds than before the restart. #[inline] - async fn restart(&mut self) -> Result> { + async fn restart(&mut self) -> Result> { self.sync().await?; let assets = AssetList::from_iter( self.wallet @@ -749,8 +760,8 @@ where } /// Simulation Event -pub type Event = - ActionLabelled>>>::Response, Error>>; +pub type Event = + ActionLabelled>>>::Response, Error>>; /// Address Database pub type AddressDatabase = IndexSet>; @@ -761,25 +772,27 @@ pub type SharedAddressDatabase = Arc>>; /// Simulation #[derive(derivative::Derivative)] #[derivative(Clone, Debug(bound = "Address: Debug"), Default(bound = ""))] -pub struct Simulation +pub struct Simulation where + A: AssetMetadata, C: Configuration, L: Ledger, - S: signer::Connection, + S: signer::Connection, B: BalanceState, { /// Address Database addresses: SharedAddressDatabase, /// Type Parameter Marker - __: PhantomData<(L, S, B)>, + __: PhantomData<(A, L, S, B)>, } -impl Simulation +impl Simulation where + A: AssetMetadata, C: Configuration, L: Ledger, - S: signer::Connection, + S: signer::Connection, B: BalanceState, Address: Clone + Eq + Hash, { @@ -803,18 +816,19 @@ where } } -impl sim::ActionSimulation for Simulation +impl sim::ActionSimulation for Simulation where + A: AssetMetadata, C: Configuration, C::AssetValue: SampleUniform, L: Ledger + PublicBalanceOracle, - S: signer::Connection, + S: signer::Connection, B: BalanceState, Address: Clone + Eq + Hash, { - type Actor = Actor; - type Action = MaybeAction; - type Event = Event; + type Actor = Actor; + type Action = MaybeAction; + type Event = Event; #[inline] fn sample<'s, R>( @@ -919,18 +933,19 @@ where /// Measures the public and secret balances for each wallet, summing them all together. #[inline] -pub async fn measure_balances<'w, C, L, S, B, I>( +pub async fn measure_balances<'w, A, C, L, S, B, I>( wallets: I, -) -> Result, Error> +) -> Result, Error> where + A: 'w + AssetMetadata, C: 'w + Configuration, C::AssetId: Ord, C::AssetValue: AddAssign, for<'v> &'v C::AssetValue: CheckedSub, L: 'w + Ledger + PublicBalanceOracle, - S: 'w + signer::Connection, + S: 'w + signer::Connection, B: 'w + BalanceState, - I: IntoIterator>, + I: IntoIterator>, { let mut balances = AssetList::::new(); for wallet in wallets.into_iter() { @@ -969,26 +984,27 @@ 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, rng: F, mut event_subscriber: ES, - ) -> Result> + ) -> Result> where + A: AssetMetadata, C: Configuration, C::AssetValue: AddAssign + SampleUniform, for<'v> &'v C::AssetValue: CheckedSub, L: Ledger + PublicBalanceOracle, - S: signer::Connection, + S: signer::Connection, S::Error: Debug, B: BalanceState, R: CryptoRng + RngCore, GL: FnMut(usize) -> L, GS: FnMut(usize) -> S, F: FnMut(usize) -> R, - ES: Copy + FnMut(&sim::Event>>) -> ESFut, + ES: Copy + FnMut(&sim::Event>>) -> ESFut, ESFut: Future, Address: Clone + Eq + Hash, { diff --git a/manta-pay/src/signer/client/http.rs b/manta-pay/src/signer/client/http.rs index 57d919501..6d5ace9d6 100644 --- a/manta-pay/src/signer/client/http.rs +++ b/manta-pay/src/signer/client/http.rs @@ -20,8 +20,8 @@ use crate::{ config::{utxo::Address, Config}, signer::{ client::network::{Message, Network}, - Checkpoint, GetRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, - SyncResponse, + AssetMetadata, Checkpoint, GetRequest, SignError, SignRequest, SignResponse, SyncError, + SyncRequest, SyncResponse, }, }; use alloc::boxed::Box; @@ -77,7 +77,7 @@ impl Client { } } -impl signer::Connection for Client { +impl signer::Connection for Client { type Checkpoint = Checkpoint; type Error = Error; diff --git a/manta-pay/src/signer/client/websocket.rs b/manta-pay/src/signer/client/websocket.rs index 674766e94..859252c9d 100644 --- a/manta-pay/src/signer/client/websocket.rs +++ b/manta-pay/src/signer/client/websocket.rs @@ -21,8 +21,8 @@ use crate::{ config::{utxo::Address, Config}, signer::{ - Checkpoint, GetRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, - SyncResponse, + AssetMetadata, Checkpoint, GetRequest, SignError, SignRequest, SignResponse, SyncError, + SyncRequest, SyncResponse, }, }; use alloc::boxed::Box; @@ -126,7 +126,7 @@ impl Client { } } -impl signer::Connection for Client { +impl signer::Connection for Client { type Checkpoint = Checkpoint; type Error = Error; diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index c0e8511d2..062bf4cfa 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -16,7 +16,8 @@ //! Manta Pay Signer Tools -use manta_accounting::wallet::signer; +use core::ops::Div; +use manta_accounting::{asset, wallet::signer}; #[cfg(feature = "groth16")] use crate::config::{utxo::Checkpoint, Config}; @@ -43,7 +44,7 @@ pub type SyncError = signer::SyncError; pub type SyncResult = signer::SyncResult; /// Signing Request -pub type SignRequest = signer::SignRequest; +pub type SignRequest = signer::SignRequest; /// Signing Response pub type SignResponse = signer::SignResponse; @@ -66,3 +67,42 @@ pub enum GetRequest { #[default] Get, } + +/// Asset Metadata +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde") +)] +#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] +pub struct AssetMetadata { + /// Number of Decimals + pub decimals: u32, + + /// Asset Symbol + pub symbol: String, +} + +impl asset::AssetMetadata for AssetMetadata { + #[inline] + fn display_value(&self, value: V) -> String + where + for<'v> &'v V: Div, + { + // TODO: What if we want more than three `FRACTIONAL_DIGITS`? How do we make this method + // more general? + const FRACTIONAL_DIGITS: u32 = 3; + let value_base_units = &value / (10u128.pow(self.decimals)); + let fractional_digits = &value / (10u128.pow(self.decimals - FRACTIONAL_DIGITS)) + % (10u128.pow(FRACTIONAL_DIGITS)); + format!("{value_base_units}.{fractional_digits:0>3}") + } + + #[inline] + fn display(&self, value: V) -> String + where + for<'v> &'v V: Div, + { + format!("{} {}", self.display_value(value), self.symbol) + } +} diff --git a/manta-pay/src/simulation/mod.rs b/manta-pay/src/simulation/mod.rs index bdc0eed67..fc1ac2455 100644 --- a/manta-pay/src/simulation/mod.rs +++ b/manta-pay/src/simulation/mod.rs @@ -22,7 +22,10 @@ use crate::{ Config, MultiProvingContext, MultiVerifyingContext, Parameters, UtxoAccumulatorModel, }, key::KeySecret, - signer::base::{Signer, UtxoAccumulator}, + signer::{ + base::{Signer, UtxoAccumulator}, + AssetMetadata, + }, simulation::ledger::{AccountId, Ledger, LedgerConnection}, }; use alloc::{format, sync::Arc}; @@ -145,15 +148,15 @@ impl Simulation { pub async fn run_with(&self, ledger: GL, signer: GS) where L: wallet::test::Ledger + PublicBalanceOracle, - S: wallet::signer::Connection, + S: wallet::signer::Connection, S::Error: Debug, GL: FnMut(usize) -> L, GS: FnMut(usize) -> S, - Error: Debug, + Error: Debug, { assert!( self.config() - .run::<_, _, _, AssetList, _, _, _, _, _, _>(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; From c086a874ca30a2827df12bac81be5a17a94d9a41 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 2 Dec 2022 13:40:54 +0100 Subject: [PATCH 34/44] small upgrade AssetMetadata trait Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/asset.rs | 8 +------- manta-pay/src/signer/mod.rs | 5 ++++- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/manta-accounting/src/asset.rs b/manta-accounting/src/asset.rs index e719e2472..ad18e2651 100644 --- a/manta-accounting/src/asset.rs +++ b/manta-accounting/src/asset.rs @@ -1058,13 +1058,7 @@ impl<'s, I, V, M> FusedIterator for SelectionKeys<'s, I, V, M> where M: AssetMap /// Asset Metadata pub trait AssetMetadata { - /// Returns a string formatting of only the `value` interpreted using `self` as the metadata. - fn display_value(&self, value: V) -> String - where - for<'v> &'v V: Div; - - /// Returns a string formatting of `value` interpreted using `self` as the metadata including - /// the symbol. + /// Returns a string formatting of `value` interpreted using `self` as the metadata. fn display(&self, value: V) -> String where for<'v> &'v V: Div; diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index 062bf4cfa..ee23e9909 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -83,7 +83,8 @@ pub struct AssetMetadata { pub symbol: String, } -impl asset::AssetMetadata for AssetMetadata { +impl AssetMetadata { + /// Returns a string formatting of only the `value` interpreted using `self` as the metadata. #[inline] fn display_value(&self, value: V) -> String where @@ -97,7 +98,9 @@ impl asset::AssetMetadata for AssetMetadata { % (10u128.pow(FRACTIONAL_DIGITS)); format!("{value_base_units}.{fractional_digits:0>3}") } +} +impl asset::AssetMetadata for AssetMetadata { #[inline] fn display(&self, value: V) -> String where From 44de3ec74a82d041726b01265c610c5b9096ad87 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 2 Dec 2022 13:55:05 +0100 Subject: [PATCH 35/44] NFT support Signed-off-by: Francisco Hernandez Iglesias --- manta-pay/src/signer/mod.rs | 47 ++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index ee23e9909..daa20a6e7 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -68,6 +68,27 @@ pub enum GetRequest { Get, } +/// Asset Type +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde") +)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum TokenType { + /// Fungible Token + FT(u32), + + /// Non-fungible Token + NFT, +} + +impl Default for TokenType { + fn default() -> Self { + TokenType::FT(Default::default()) + } +} + /// Asset Metadata #[cfg_attr( feature = "serde", @@ -76,8 +97,8 @@ pub enum GetRequest { )] #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] pub struct AssetMetadata { - /// Number of Decimals - pub decimals: u32, + /// TokenType + pub token_type: TokenType, /// Asset Symbol pub symbol: String, @@ -86,17 +107,22 @@ pub struct AssetMetadata { impl AssetMetadata { /// Returns a string formatting of only the `value` interpreted using `self` as the metadata. #[inline] - fn display_value(&self, value: V) -> String + fn display_value(&self, value: V) -> Option where for<'v> &'v V: Div, { // TODO: What if we want more than three `FRACTIONAL_DIGITS`? How do we make this method // more general? - const FRACTIONAL_DIGITS: u32 = 3; - let value_base_units = &value / (10u128.pow(self.decimals)); - let fractional_digits = &value / (10u128.pow(self.decimals - FRACTIONAL_DIGITS)) - % (10u128.pow(FRACTIONAL_DIGITS)); - format!("{value_base_units}.{fractional_digits:0>3}") + match self.token_type { + TokenType::FT(decimals) => { + const FRACTIONAL_DIGITS: u32 = 3; + let value_base_units = &value / (10u128.pow(decimals)); + let fractional_digits = &value / (10u128.pow(decimals - FRACTIONAL_DIGITS)) + % (10u128.pow(FRACTIONAL_DIGITS)); + Some(format!("{value_base_units}.{fractional_digits:0>3}")) + } + TokenType::NFT => None, + } } } @@ -106,6 +132,9 @@ impl asset::AssetMetadata for AssetMetadata { where for<'v> &'v V: Div, { - format!("{} {}", self.display_value(value), self.symbol) + match self.display_value(value) { + Some(str) => format!("{} {}", str, self.symbol), + _ => format!("{} {}", "NFT", self.symbol), + } } } From ce92a2864122860c03d9e9c8ce4dc3573251f39d Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 2 Dec 2022 14:07:55 +0100 Subject: [PATCH 36/44] changelog Signed-off-by: Francisco Hernandez Iglesias --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6d3f5ec4..24a13b844 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 +- [\#289](https://github.com/Manta-Network/manta-rs/pull/289) AssetMetadata upgrade and NFT support. - [\#286](https://github.com/Manta-Network/manta-rs/pull/286) MantaPay v1.0.0 ### Changed From 865f0a3c5292b558101a67880f8ed1af6aa4b1d6 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 2 Dec 2022 14:47:19 +0100 Subject: [PATCH 37/44] clippy issues Signed-off-by: Francisco Hernandez Iglesias --- manta-pay/src/signer/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index daa20a6e7..f0da6e6ba 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -16,6 +16,7 @@ //! Manta Pay Signer Tools +use alloc::{format, string::String}; use core::ops::Div; use manta_accounting::{asset, wallet::signer}; From 17abb0a016d2bd3b57343dcc01ebea98e0dd78de Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 2 Dec 2022 16:47:51 +0100 Subject: [PATCH 38/44] now AssetMetadata is part of signer connection and signer configuration Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/asset.rs | 66 +---------- manta-accounting/src/transfer/canonical.rs | 21 +--- manta-accounting/src/wallet/mod.rs | 43 ++++---- manta-accounting/src/wallet/signer.rs | 20 ++-- manta-accounting/src/wallet/test/mod.rs | 122 +++++++++------------ manta-pay/src/signer/base.rs | 3 +- manta-pay/src/signer/client/http.rs | 3 +- manta-pay/src/signer/client/websocket.rs | 3 +- manta-pay/src/signer/mod.rs | 11 +- manta-pay/src/simulation/mod.rs | 11 +- 10 files changed, 102 insertions(+), 201 deletions(-) diff --git a/manta-accounting/src/asset.rs b/manta-accounting/src/asset.rs index ad18e2651..f39f39081 100644 --- a/manta-accounting/src/asset.rs +++ b/manta-accounting/src/asset.rs @@ -25,7 +25,6 @@ use alloc::{ collections::btree_map::{BTreeMap, Entry as BTreeMapEntry}, - string::String, vec, vec::Vec, }; @@ -34,7 +33,7 @@ use core::{ fmt::Debug, hash::Hash, iter::{self, FusedIterator}, - ops::{Add, AddAssign, Deref, Div, Sub, SubAssign}, + ops::{Add, AddAssign, Deref, Sub, SubAssign}, slice, }; use derive_more::{Display, From}; @@ -1055,66 +1054,3 @@ where } impl<'s, I, V, M> FusedIterator for SelectionKeys<'s, I, V, M> where M: AssetMap + ?Sized {} - -/// Asset Metadata -pub trait AssetMetadata { - /// Returns a string formatting of `value` interpreted using `self` as the metadata. - fn display(&self, value: V) -> String - where - for<'v> &'v V: Div; -} - -/// Metadata Display -pub trait MetadataDisplay -where - A: AssetMetadata, -{ - /// Returns a string representation of `self` given the asset `metadata`. - fn display(&self, metadata: &A) -> String; -} - -/// Asset Manager -pub trait AssetManager -where - A: AssetMetadata, -{ - /// Returns the metadata associated to `id`. - fn metadata(&self, id: &I) -> Option<&A>; -} - -/// Implements [`AssetManager`] for map types. -macro_rules! impl_asset_manager_for_maps_body { - ($I:ident, $A: ident) => { - #[inline] - fn metadata(&self, id: &$I) -> Option<&A> { - self.get(id) - } - }; -} - -/// B-Tree Map [`AssetManager`] Implementation -pub type BTreeAssetManager = BTreeMap; - -impl AssetManager for BTreeAssetManager -where - I: Ord, - A: AssetMetadata, -{ - impl_asset_manager_for_maps_body! { I, A } -} - -/// Hash Map [`AssetManager`] Implementation -#[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] -pub type HashAssetManager = HashMap; - -#[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] -impl AssetManager for HashAssetManager -where - I: Eq + Hash, - A: AssetMetadata, - S: BuildHasher + Default, -{ - impl_asset_manager_for_maps_body! { I, A } -} diff --git a/manta-accounting/src/transfer/canonical.rs b/manta-accounting/src/transfer/canonical.rs index a8dbff85a..c60c13deb 100644 --- a/manta-accounting/src/transfer/canonical.rs +++ b/manta-accounting/src/transfer/canonical.rs @@ -19,7 +19,7 @@ // TODO: Add typing for `ProvingContext` and `VerifyingContext` against the canonical shapes. use crate::{ - asset::{self, AssetMap, AssetMetadata, MetadataDisplay}, + asset::{self, AssetMap}, transfer::{ has_public_participants, internal_pair, requires_authorization, Address, Asset, AssociatedData, Authorization, AuthorizationContext, Configuration, FullParametersRef, @@ -28,7 +28,7 @@ use crate::{ VerifyingContext, }, }; -use alloc::{string::String, vec::Vec}; +use alloc::vec::Vec; use core::{fmt::Debug, hash::Hash}; use manta_crypto::rand::{CryptoRng, RngCore}; use manta_util::{create_seal, seal}; @@ -420,23 +420,6 @@ where { 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: &A, f: F) -> (String, Option) - where - A: AssetMetadata, - F: FnOnce(&Address) -> String, - C::AssetValue: MetadataDisplay, - { - match self { - 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), - } - } } /// Transaction Kind diff --git a/manta-accounting/src/wallet/mod.rs b/manta-accounting/src/wallet/mod.rs index 694081d54..53ada9cd4 100644 --- a/manta-accounting/src/wallet/mod.rs +++ b/manta-accounting/src/wallet/mod.rs @@ -28,7 +28,7 @@ //! [`Ledger`]: ledger::Connection use crate::{ - asset::{AssetList, AssetMetadata}, + asset::AssetList, transfer::{ canonical::{Transaction, TransactionKind}, Address, Asset, Configuration, TransferPost, @@ -81,16 +81,14 @@ pub mod test; PartialEq(bound = "L: PartialEq, S::Checkpoint: PartialEq, S: PartialEq, B: PartialEq") )] pub struct Wallet< - A, C, L, S = signer::Signer, B = BTreeMapBalanceState<::AssetId, ::AssetValue>, > where - A: AssetMetadata, C: Configuration, L: ledger::Connection, - S: signer::Connection, + S: signer::Connection, B: BalanceState, { /// Ledger Connection @@ -109,12 +107,11 @@ pub struct Wallet< __: PhantomData, } -impl Wallet +impl Wallet where - A: AssetMetadata, C: Configuration, L: ledger::Connection, - S: signer::Connection, + S: signer::Connection, B: BalanceState, { /// Builds a new [`Wallet`] without checking if `ledger`, `checkpoint`, `signer`, and `assets` @@ -159,7 +156,7 @@ where /// Starts a new wallet with `ledger` and `signer` connections. #[inline] - pub async fn start(ledger: L, signer: S) -> Result> + pub async fn start(ledger: L, signer: S) -> Result> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -230,7 +227,7 @@ where /// [`InconsistencyError`] type for more information on the kinds of errors that can occur and /// how to resolve them. #[inline] - pub async fn restart(&mut self) -> Result<(), Error> + pub async fn restart(&mut self) -> Result<(), Error> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -244,7 +241,7 @@ where /// [`restart`](Self::restart) to avoid querying the ledger at genesis when a known later /// checkpoint exists. #[inline] - async fn load_initial_state(&mut self) -> Result<(), Error> { + async fn load_initial_state(&mut self) -> Result<(), Error> { self.signer_sync(Default::default()).await } @@ -259,7 +256,7 @@ where /// [`InconsistencyError`] type for more information on the kinds of errors that can occur and /// how to resolve them. #[inline] - pub async fn sync(&mut self) -> Result<(), Error> + pub async fn sync(&mut self) -> Result<(), Error> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -278,7 +275,7 @@ where /// [`InconsistencyError`] type for more information on the kinds of errors that can occur and /// how to resolve them. #[inline] - pub async fn sync_partial(&mut self) -> Result> + pub async fn sync_partial(&mut self) -> Result> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -287,7 +284,7 @@ where /// Pulls data from the ledger, synchronizing the wallet and balance state. #[inline] - async fn sync_with(&mut self) -> Result> + async fn sync_with(&mut self) -> Result> where L: ledger::Read, Checkpoint = S::Checkpoint>, { @@ -312,7 +309,7 @@ where async fn signer_sync( &mut self, request: SyncRequest, - ) -> Result<(), Error> { + ) -> Result<(), Error> { match self .signer .sync(request) @@ -374,8 +371,8 @@ where pub async fn sign( &mut self, transaction: Transaction, - metadata: Option, - ) -> Result, Error> { + metadata: Option, + ) -> Result, Error> { self.check(&transaction) .map_err(Error::InsufficientBalance)?; self.signer @@ -411,8 +408,8 @@ where pub async fn post( &mut self, transaction: Transaction, - metadata: Option, - ) -> Result> + metadata: Option, + ) -> Result> where L: ledger::Read, Checkpoint = S::Checkpoint> + ledger::Write>>, @@ -505,12 +502,11 @@ pub enum InconsistencyError { bound = "Asset: PartialEq, SignError: PartialEq, L::Error: PartialEq, S::Error: PartialEq" ) )] -pub enum Error +pub enum Error where - A: AssetMetadata, C: Configuration, L: ledger::Connection, - S: signer::Connection, + S: signer::Connection, { /// Insufficient Balance InsufficientBalance(Asset), @@ -530,12 +526,11 @@ where LedgerConnectionError(L::Error), } -impl From for Error +impl From for Error where - A: AssetMetadata, C: Configuration, L: ledger::Connection, - S: signer::Connection, + S: signer::Connection, { #[inline] fn from(err: InconsistencyError) -> Self { diff --git a/manta-accounting/src/wallet/signer.rs b/manta-accounting/src/wallet/signer.rs index e418881c3..61944cadf 100644 --- a/manta-accounting/src/wallet/signer.rs +++ b/manta-accounting/src/wallet/signer.rs @@ -26,7 +26,7 @@ // internally. use crate::{ - asset::{AssetMap, AssetMetadata}, + asset::AssetMap, key::{self, Account, AccountCollection, DeriveAddress, DeriveAddresses}, transfer::{ self, @@ -59,11 +59,13 @@ use manta_util::{ use manta_util::serde::{Deserialize, Serialize}; /// Signer Connection -pub trait Connection +pub trait Connection where - A: AssetMetadata, C: transfer::Configuration, { + /// Asset Metadata Type + type AssetMetadata; + /// Checkpoint Type /// /// This checkpoint is used by the signer to stay synchronized with wallet and the ledger. @@ -85,7 +87,7 @@ where /// Signs a transaction and returns the ledger transfer posts if successful. fn sign( &mut self, - request: SignRequest, + request: SignRequest, ) -> LocalBoxFutureResult, SignError>, Self::Error>; /// Returns the [`Address`] corresponding to `self`. @@ -352,7 +354,6 @@ pub type SyncResult = Result, SyncError>; )] pub struct SignRequest where - A: AssetMetadata, C: transfer::Configuration, { /// Transaction Data @@ -505,6 +506,9 @@ pub trait Configuration: transfer::Configuration { /// Asset Map Type type AssetMap: AssetMap>; + /// Asset Metadata Type + type AssetMetadata; + /// Random Number Generator Type type Rng: CryptoRng + FromEntropy + RngCore; } @@ -1340,11 +1344,11 @@ where } } -impl Connection for Signer +impl Connection for Signer where - A: AssetMetadata, C: Configuration, { + type AssetMetadata = C::AssetMetadata; type Checkpoint = C::Checkpoint; type Error = Infallible; @@ -1362,7 +1366,7 @@ where #[inline] fn sign( &mut self, - request: SignRequest, + request: SignRequest, ) -> LocalBoxFutureResult, SignError>, Self::Error> { Box::pin(async move { Ok(self.sign(request.transaction)) }) } diff --git a/manta-accounting/src/wallet/test/mod.rs b/manta-accounting/src/wallet/test/mod.rs index 9da32252a..85ac0d707 100644 --- a/manta-accounting/src/wallet/test/mod.rs +++ b/manta-accounting/src/wallet/test/mod.rs @@ -20,7 +20,7 @@ // TODO: Generalize `PushResponse` so that we can test against more general wallet setups. use crate::{ - asset::{AssetList, AssetMetadata}, + asset::AssetList, transfer::{canonical::Transaction, Address, Asset, Configuration, TransferPost}, wallet::{ ledger, @@ -183,10 +183,10 @@ pub struct ActionLabelled { } /// [`ActionLabelled`] Error Type -pub type ActionLabelledError = ActionLabelled>; +pub type ActionLabelledError = ActionLabelled>; /// Possible [`Action`] or an [`ActionLabelledError`] Variant -pub type MaybeAction = Result, ActionLabelledError>; +pub type MaybeAction = Result, ActionLabelledError>; /// Action Types #[cfg_attr( @@ -403,22 +403,21 @@ where /// 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") + Clone(bound = "Wallet: Clone"), + Debug(bound = "Wallet: Debug"), + Default(bound = "Wallet: Default"), + Eq(bound = "Wallet: Eq"), + PartialEq(bound = "Wallet: PartialEq") )] -pub struct Actor +pub struct Actor where - A: AssetMetadata, C: Configuration, L: Ledger, - S: signer::Connection, + S: signer::Connection, B: BalanceState, { /// Wallet - pub wallet: Wallet, + pub wallet: Wallet, /// Action Distribution pub distribution: ActionDistribution, @@ -427,18 +426,17 @@ where pub lifetime: usize, } -impl Actor +impl Actor where - A: AssetMetadata, C: Configuration, L: Ledger, - S: signer::Connection, + S: signer::Connection, B: BalanceState, { /// Builds a new [`Actor`] with `wallet`, `distribution`, and `lifetime`. #[inline] pub fn new( - wallet: Wallet, + wallet: Wallet, distribution: ActionDistribution, lifetime: usize, ) -> Self { @@ -458,7 +456,7 @@ where /// Returns the default address for `self`. #[inline] - async fn default_address(&mut self) -> Result, Error> { + async fn default_address(&mut self) -> Result, Error> { self.wallet .address() .await @@ -469,7 +467,7 @@ where #[inline] async fn public_balances( &mut self, - ) -> Result>, Error> + ) -> Result>, Error> where L: PublicBalanceOracle, { @@ -479,16 +477,13 @@ where /// Synchronizes the [`Wallet`] in `self`. #[inline] - async fn sync(&mut self) -> Result<(), Error> { + 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> { + async fn sync_with(&mut self, action: ActionType) -> Result<(), ActionLabelledError> { self.sync().await.map_err(|err| action.label(err)) } @@ -498,8 +493,8 @@ where async fn post( &mut self, transaction: Transaction, - metadata: Option, - ) -> Result> { + metadata: Option, + ) -> Result> { self.wallet.post(transaction, metadata).await } @@ -511,10 +506,7 @@ where /// 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> + async fn sample_deposit(&mut self, rng: &mut R) -> Result>, Error> where C::AssetValue: SampleUniform, L: PublicBalanceOracle, @@ -540,10 +532,7 @@ where /// This method samples from a uniform distribution over the asset IDs and asset values present /// in the balance state of `self`. #[inline] - async fn sample_withdraw( - &mut self, - rng: &mut R, - ) -> Result>, Error> + async fn sample_withdraw(&mut self, rng: &mut R) -> Result>, Error> where C::AssetValue: SampleUniform, R: RngCore + ?Sized, @@ -565,7 +554,7 @@ where &mut self, action: ActionType, rng: &mut R, - ) -> Result>, ActionLabelledError> + ) -> Result>, ActionLabelledError> where R: RngCore + ?Sized, { @@ -581,7 +570,7 @@ where /// [`ToPrivate`]: ActionType::ToPrivate /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_to_private(&mut self, rng: &mut R) -> MaybeAction + async fn sample_to_private(&mut self, rng: &mut R) -> MaybeAction where C::AssetValue: SampleUniform, L: PublicBalanceOracle, @@ -600,7 +589,7 @@ where /// [`ToPrivateZero`]: ActionType::ToPrivateZero /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_zero_to_private(&mut self, rng: &mut R) -> MaybeAction + async fn sample_zero_to_private(&mut self, rng: &mut R) -> MaybeAction where L: PublicBalanceOracle, R: RngCore + ?Sized, @@ -627,12 +616,12 @@ where is_self: bool, rng: &mut R, address: F, - ) -> MaybeAction + ) -> MaybeAction where C::AssetValue: SampleUniform, L: PublicBalanceOracle, R: RngCore + ?Sized, - F: FnOnce(&mut R) -> Result>, Error>, + F: FnOnce(&mut R) -> Result>, Error>, { let action = if is_self { ActionType::SelfTransfer @@ -663,11 +652,11 @@ where is_self: bool, rng: &mut R, address: F, - ) -> MaybeAction + ) -> MaybeAction where L: PublicBalanceOracle, R: RngCore + ?Sized, - F: FnOnce(&mut R) -> Result>, Error>, + F: FnOnce(&mut R) -> Result>, Error>, { let action = if is_self { ActionType::SelfTransfer @@ -695,7 +684,7 @@ where /// [`ToPublic`]: ActionType::ToPublic /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_to_public(&mut self, rng: &mut R) -> MaybeAction + async fn sample_to_public(&mut self, rng: &mut R) -> MaybeAction where C::AssetValue: SampleUniform, L: PublicBalanceOracle, @@ -714,7 +703,7 @@ where /// [`ToPublicZero`]: ActionType::ToPublicZero /// [`Skip`]: ActionType::Skip #[inline] - async fn sample_zero_to_public(&mut self, rng: &mut R) -> MaybeAction + async fn sample_zero_to_public(&mut self, rng: &mut R) -> MaybeAction where R: RngCore + ?Sized, { @@ -730,7 +719,7 @@ where /// /// [`Skip`]: ActionType::Skip #[inline] - async fn flush_to_public(&mut self, rng: &mut R) -> MaybeAction + async fn flush_to_public(&mut self, rng: &mut R) -> MaybeAction where R: RngCore + ?Sized, { @@ -744,7 +733,7 @@ where /// Computes the current balance state of the wallet, performs a wallet restart, and then checks /// that the balance state has the same or more funds than before the restart. #[inline] - async fn restart(&mut self) -> Result> { + async fn restart(&mut self) -> Result> { self.sync().await?; let assets = AssetList::from_iter( self.wallet @@ -760,8 +749,8 @@ where } /// Simulation Event -pub type Event = - ActionLabelled>>>::Response, Error>>; +pub type Event = + ActionLabelled>>>::Response, Error>>; /// Address Database pub type AddressDatabase = IndexSet>; @@ -772,27 +761,25 @@ pub type SharedAddressDatabase = Arc>>; /// Simulation #[derive(derivative::Derivative)] #[derivative(Clone, Debug(bound = "Address: Debug"), Default(bound = ""))] -pub struct Simulation +pub struct Simulation where - A: AssetMetadata, C: Configuration, L: Ledger, - S: signer::Connection, + S: signer::Connection, B: BalanceState, { /// Address Database addresses: SharedAddressDatabase, /// Type Parameter Marker - __: PhantomData<(A, L, S, B)>, + __: PhantomData<(L, S, B)>, } -impl Simulation +impl Simulation where - A: AssetMetadata, C: Configuration, L: Ledger, - S: signer::Connection, + S: signer::Connection, B: BalanceState, Address: Clone + Eq + Hash, { @@ -816,19 +803,18 @@ where } } -impl sim::ActionSimulation for Simulation +impl sim::ActionSimulation for Simulation where - A: AssetMetadata, C: Configuration, C::AssetValue: SampleUniform, L: Ledger + PublicBalanceOracle, - S: signer::Connection, + S: signer::Connection, B: BalanceState, Address: Clone + Eq + Hash, { - type Actor = Actor; - type Action = MaybeAction; - type Event = Event; + type Actor = Actor; + type Action = MaybeAction; + type Event = Event; #[inline] fn sample<'s, R>( @@ -933,19 +919,18 @@ where /// Measures the public and secret balances for each wallet, summing them all together. #[inline] -pub async fn measure_balances<'w, A, C, L, S, B, I>( +pub async fn measure_balances<'w, C, L, S, B, I>( wallets: I, -) -> Result, Error> +) -> Result, Error> where - A: 'w + AssetMetadata, C: 'w + Configuration, C::AssetId: Ord, C::AssetValue: AddAssign, for<'v> &'v C::AssetValue: CheckedSub, L: 'w + Ledger + PublicBalanceOracle, - S: 'w + signer::Connection, + S: 'w + signer::Connection, B: 'w + BalanceState, - I: IntoIterator>, + I: IntoIterator>, { let mut balances = AssetList::::new(); for wallet in wallets.into_iter() { @@ -984,27 +969,26 @@ 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, rng: F, mut event_subscriber: ES, - ) -> Result> + ) -> Result> where - A: AssetMetadata, C: Configuration, C::AssetValue: AddAssign + SampleUniform, for<'v> &'v C::AssetValue: CheckedSub, L: Ledger + PublicBalanceOracle, - S: signer::Connection, + S: signer::Connection, S::Error: Debug, B: BalanceState, R: CryptoRng + RngCore, GL: FnMut(usize) -> L, GS: FnMut(usize) -> S, F: FnMut(usize) -> R, - ES: Copy + FnMut(&sim::Event>>) -> ESFut, + ES: Copy + FnMut(&sim::Event>>) -> ESFut, ESFut: Future, Address: Clone + Eq + Hash, { diff --git a/manta-pay/src/signer/base.rs b/manta-pay/src/signer/base.rs index d53806bf0..a644e86de 100644 --- a/manta-pay/src/signer/base.rs +++ b/manta-pay/src/signer/base.rs @@ -22,7 +22,7 @@ use crate::{ Config, }, key::{CoinType, KeySecret, Testnet}, - signer::Checkpoint, + signer::{AssetMetadata, Checkpoint}, }; use alloc::collections::BTreeMap; use core::{cmp, mem}; @@ -88,6 +88,7 @@ impl wallet::signer::Configuration for Config { type Checkpoint = Checkpoint; type UtxoAccumulator = UtxoAccumulator; type AssetMap = HashAssetMap, Self::AssetId, Self::AssetValue>; + type AssetMetadata = AssetMetadata; type Rng = ChaCha20Rng; } diff --git a/manta-pay/src/signer/client/http.rs b/manta-pay/src/signer/client/http.rs index 6d5ace9d6..583ad191c 100644 --- a/manta-pay/src/signer/client/http.rs +++ b/manta-pay/src/signer/client/http.rs @@ -77,7 +77,8 @@ impl Client { } } -impl signer::Connection for Client { +impl signer::Connection for Client { + type AssetMetadata = AssetMetadata; type Checkpoint = Checkpoint; type Error = Error; diff --git a/manta-pay/src/signer/client/websocket.rs b/manta-pay/src/signer/client/websocket.rs index 859252c9d..b429e5e90 100644 --- a/manta-pay/src/signer/client/websocket.rs +++ b/manta-pay/src/signer/client/websocket.rs @@ -126,7 +126,8 @@ impl Client { } } -impl signer::Connection for Client { +impl signer::Connection for Client { + type AssetMetadata = AssetMetadata; type Checkpoint = Checkpoint; type Error = Error; diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index f0da6e6ba..d92f0e439 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -18,7 +18,7 @@ use alloc::{format, string::String}; use core::ops::Div; -use manta_accounting::{asset, wallet::signer}; +use manta_accounting::wallet::signer; #[cfg(feature = "groth16")] use crate::config::{utxo::Checkpoint, Config}; @@ -108,7 +108,7 @@ pub struct AssetMetadata { impl AssetMetadata { /// Returns a string formatting of only the `value` interpreted using `self` as the metadata. #[inline] - fn display_value(&self, value: V) -> Option + pub fn display_value(&self, value: V) -> Option where for<'v> &'v V: Div, { @@ -125,11 +125,10 @@ impl AssetMetadata { TokenType::NFT => None, } } -} - -impl asset::AssetMetadata for AssetMetadata { + /// Returns a string formatting of only the `value` interpreted using `self` as the metadata + /// using `symbol`. #[inline] - fn display(&self, value: V) -> String + pub fn display(&self, value: V) -> String where for<'v> &'v V: Div, { diff --git a/manta-pay/src/simulation/mod.rs b/manta-pay/src/simulation/mod.rs index fc1ac2455..bdc0eed67 100644 --- a/manta-pay/src/simulation/mod.rs +++ b/manta-pay/src/simulation/mod.rs @@ -22,10 +22,7 @@ use crate::{ Config, MultiProvingContext, MultiVerifyingContext, Parameters, UtxoAccumulatorModel, }, key::KeySecret, - signer::{ - base::{Signer, UtxoAccumulator}, - AssetMetadata, - }, + signer::base::{Signer, UtxoAccumulator}, simulation::ledger::{AccountId, Ledger, LedgerConnection}, }; use alloc::{format, sync::Arc}; @@ -148,15 +145,15 @@ impl Simulation { pub async fn run_with(&self, ledger: GL, signer: GS) where L: wallet::test::Ledger + PublicBalanceOracle, - S: wallet::signer::Connection, + S: wallet::signer::Connection, S::Error: Debug, GL: FnMut(usize) -> L, GS: FnMut(usize) -> S, - Error: Debug, + Error: Debug, { assert!( self.config() - .run::<_, _, _, _, AssetList, _, _, _, _, _, _>(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; From 88a8be051477cd49ef27d2df33ce8c69d346fb16 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 2 Dec 2022 17:01:26 +0100 Subject: [PATCH 39/44] docs Signed-off-by: Francisco Hernandez Iglesias --- manta-pay/src/signer/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index d92f0e439..1c27cee8d 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -125,8 +125,8 @@ impl AssetMetadata { TokenType::NFT => None, } } - /// Returns a string formatting of only the `value` interpreted using `self` as the metadata - /// using `symbol`. + /// Returns a string formatting of `value` interpreted using `self` as the metadata including + /// the symbol. #[inline] pub fn display(&self, value: V) -> String where From e2df72fb72f76c3330d3af59a9e9235c6c64bb9d Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Fri, 2 Dec 2022 17:17:44 +0100 Subject: [PATCH 40/44] docs Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/asset.rs | 4 +--- manta-pay/src/signer/mod.rs | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manta-accounting/src/asset.rs b/manta-accounting/src/asset.rs index f39f39081..0e013ad29 100644 --- a/manta-accounting/src/asset.rs +++ b/manta-accounting/src/asset.rs @@ -17,9 +17,7 @@ //! Assets //! //! This module defines the data structures and canonical encodings of a standard notion of "asset". -//! Assets are defined by an `AssetId` field and an `AssetValue` field. For describing an [`Asset`] -//! with a particular `AssetId` we use [`AssetMetadata`] to assign a symbol and decimals for -//! human-readable display purposes. +//! Assets are defined by an `AssetId` field and an `AssetValue` field. #![allow(clippy::uninlined_format_args)] // NOTE: Clippy false positive https://github.com/rust-lang/rust-clippy/issues/9715 on Display implementation on Asset below diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index 1c27cee8d..798d649c8 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -90,7 +90,9 @@ impl Default for TokenType { } } -/// Asset Metadata +/// Asset Metadata. To describe an [`Asset`](manta_accounting::asset::Asset) with a +/// particular `AssetId` we use [`AssetMetadata`] to assign a symbol and distinguish between +/// FTs and NFTs. For FTs we assign decimals for human-readable display purposes. #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), From a0e323655a60252e40bcb405618d4381da3159f6 Mon Sep 17 00:00:00 2001 From: "Brandon H. Gomes" Date: Tue, 10 Jan 2023 11:32:19 -0500 Subject: [PATCH 41/44] chore: fix Rust upgrade warnings Signed-off-by: Brandon H. Gomes --- manta-accounting/src/transfer/mod.rs | 8 +------- manta-crypto/src/merkle_tree/forest.rs | 2 -- manta-trusted-setup/src/groth16/ppot/serialization.rs | 6 ++---- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/manta-accounting/src/transfer/mod.rs b/manta-accounting/src/transfer/mod.rs index 632a3d770..d7764c1d5 100644 --- a/manta-accounting/src/transfer/mod.rs +++ b/manta-accounting/src/transfer/mod.rs @@ -970,19 +970,13 @@ where 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(), + senders: (0..SENDERS).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(), } diff --git a/manta-crypto/src/merkle_tree/forest.rs b/manta-crypto/src/merkle_tree/forest.rs index 975dce3b0..378a6fce6 100644 --- a/manta-crypto/src/merkle_tree/forest.rs +++ b/manta-crypto/src/merkle_tree/forest.rs @@ -544,7 +544,6 @@ where fn default() -> Self { Self::new(BoxArray::from_unchecked( (0..N) - .into_iter() .map(move |_| Default::default()) .collect::>() .into_boxed_slice(), @@ -564,7 +563,6 @@ where fn new(parameters: &Parameters) -> Self { Self::new(BoxArray::from_unchecked( (0..N) - .into_iter() .map(move |_| T::new(parameters)) .collect::>() .into_boxed_slice(), diff --git a/manta-trusted-setup/src/groth16/ppot/serialization.rs b/manta-trusted-setup/src/groth16/ppot/serialization.rs index f2d827257..94a998256 100644 --- a/manta-trusted-setup/src/groth16/ppot/serialization.rs +++ b/manta-trusted-setup/src/groth16/ppot/serialization.rs @@ -253,10 +253,10 @@ impl Serializer for PpotSerializer { // Final result will be reversed, so this is like modifying first byte res[31] |= 1 << 6; } else { - let mut temp_writer = &mut res[..]; + let temp_writer = &mut res[..]; // Write x coordinate - point.x.write(&mut temp_writer)?; + point.x.write(temp_writer)?; // Check whether y-coordinate is lexicographically greatest // Final result will be reversed, so this is like modifying first byte @@ -785,11 +785,9 @@ mod tests { const N: usize = 100; // number of samples let mut rng = ChaCha20Rng::from_seed([0; 32]); let g1: Vec = (0..N) - .into_iter() .map(|_| ::Projective::gen(&mut rng).into_affine()) .collect(); let g2: Vec = (0..N) - .into_iter() .map(|_| ::Projective::gen(&mut rng).into_affine()) .collect(); From 58baf176d6e19e7ded4d26d37e15f7697d699baa Mon Sep 17 00:00:00 2001 From: "Brandon H. Gomes" Date: Tue, 10 Jan 2023 14:22:07 -0500 Subject: [PATCH 42/44] chore: fix std import Signed-off-by: Brandon H. Gomes --- manta-accounting/src/asset.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/manta-accounting/src/asset.rs b/manta-accounting/src/asset.rs index 09bac1abe..0e013ad29 100644 --- a/manta-accounting/src/asset.rs +++ b/manta-accounting/src/asset.rs @@ -36,7 +36,6 @@ use core::{ }; use derive_more::{Display, From}; use manta_crypto::{ - arkworks::std, constraint::{HasInput, Input}, eclair::{ self, @@ -61,7 +60,7 @@ use manta_util::{ use manta_util::serde::{Deserialize, Serialize}; #[cfg(feature = "std")] -use self::std::{ +use std::{ collections::hash_map::{Entry as HashMapEntry, HashMap, RandomState}, hash::BuildHasher, }; From bbceb0a06c6171948b9f8afdc912b708982df7a7 Mon Sep 17 00:00:00 2001 From: SupremoUGH Date: Tue, 24 Jan 2023 13:16:03 +0100 Subject: [PATCH 43/44] display value methods updated as in PR 296 --- manta-pay/src/signer/mod.rs | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index 6b86f720f..1ab998820 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -17,7 +17,7 @@ //! Manta Pay Signer Tools use alloc::{format, string::String}; -use core::ops::Div; +use core::ops::{Div, Sub}; use manta_accounting::wallet::signer; #[cfg(feature = "groth16")] @@ -122,19 +122,30 @@ pub struct AssetMetadata { impl AssetMetadata { /// Returns a string formatting of only the `value` interpreted using `self` as the metadata. #[inline] - pub fn display_value(&self, value: V) -> Option + pub fn display_value(&self, value: V, digits: u32) -> Option where for<'v> &'v V: Div, + V: Sub, { - // TODO: What if we want more than three `FRACTIONAL_DIGITS`? How do we make this method - // more general? match self.token_type { TokenType::FT(decimals) => { - const FRACTIONAL_DIGITS: u32 = 3; let value_base_units = &value / (10u128.pow(decimals)); - let fractional_digits = &value / (10u128.pow(decimals - FRACTIONAL_DIGITS)) - % (10u128.pow(FRACTIONAL_DIGITS)); - Some(format!("{value_base_units}.{fractional_digits:0>3}")) + let fractional_digits = + &value / (10u128.pow(decimals - digits)) % (10u128.pow(digits)); + let decimals: u128 = value - (value_base_units * 10u128.pow(digits)); + let decimals_length: u32 = decimals + .to_string() + .len() + .try_into() + .expect("Conversion to u32 failed."); + let leading_zeros = "0".repeat( + (digits - decimals_length) + .try_into() + .expect("Conversion from u32 to usize is not allowed to fail."), + ); + Some(format!( + "{value_base_units}.{leading_zeros}{fractional_digits}" + )) } TokenType::NFT => None, } @@ -142,11 +153,12 @@ impl AssetMetadata { /// Returns a string formatting of `value` interpreted using `self` as the metadata including /// the symbol. #[inline] - pub fn display(&self, value: V) -> String + pub fn display(&self, value: V, digits: u32) -> String where for<'v> &'v V: Div, + V: Sub, { - match self.display_value(value) { + match self.display_value(value, digits) { Some(str) => format!("{} {}", str, self.symbol), _ => format!("{} {}", "NFT", self.symbol), } From 207c8c72baa0a1d3f78ef84373edc7fcbbc3e835 Mon Sep 17 00:00:00 2001 From: SupremoUGH Date: Tue, 24 Jan 2023 13:32:55 +0100 Subject: [PATCH 44/44] clippy --- manta-pay/src/signer/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index 1ab998820..3ea4c18d7 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -16,7 +16,10 @@ //! Manta Pay Signer Tools -use alloc::{format, string::String}; +use alloc::{ + format, + string::{String, ToString}, +}; use core::ops::{Div, Sub}; use manta_accounting::wallet::signer;

(checksums: &ChecksumMap, path: P) -> Result<&Checksum> where P: AsRef, { @@ -361,30 +521,48 @@ mod test { 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.") + /// Downloads all data from GitHub and checks if they are the same as the data known locally to + /// this Rust crate. + #[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 = super::git::current_branch()?; + let directory = tempfile::tempdir()?; + println!("[INFO] Temporary Directory: {directory:?}"); + let checksums = parse_checkfile("data.checkfile")?; + let directory_path = directory.path(); + for file in walkdir::WalkDir::new("data") { + let file = file?; + let path = file.path(); + if !path.is_dir() { + println!("[INFO] Checking path: {path:?}"); + let target = directory_path.join(path); + fs::create_dir_all(target.parent().unwrap())?; + github::download( + ¤t_branch, + path.to_str().unwrap(), + &target, + get_checksum(&checksums, path)?, + )?; + assert!( + equal_files(&mut File::open(path)?, &mut File::open(&target)?)?, + "The files at {:?} and {:?} are not equal.", + path, + target + ); + } } + Ok(()) } /// 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()?; + fn unsafe_download_all_data() -> Result<()> { + let current_branch = super::git::current_branch()?; let directory = tempfile::tempdir()?; println!("[INFO] Temporary Directory: {directory:?}"); let checksums = parse_checkfile("data.checkfile")?; @@ -396,16 +574,18 @@ mod test { println!("[INFO] Checking path: {path:?}"); let target = directory_path.join(path); fs::create_dir_all(target.parent().unwrap())?; - github::download( + github::unsafe_download( ¤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 {:?} and {:?} are not equal.", + path, + target + ); } } Ok(()) diff --git a/manta-pay/src/lib.rs b/manta-pay/src/lib.rs index a85b095b3..9dffbaeb7 100644 --- a/manta-pay/src/lib.rs +++ b/manta-pay/src/lib.rs @@ -33,9 +33,9 @@ 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"))))] -// pub mod parameters; +#[cfg(all(feature = "groth16", feature = "test"))] +#[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "test"))))] +pub mod parameters; #[cfg(feature = "groth16")] #[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] @@ -45,6 +45,6 @@ pub mod signer; // #[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "simulation"))))] // pub mod simulation; -// #[cfg(any(test, feature = "test"))] -// #[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] -// pub mod test; +#[cfg(any(test, feature = "test"))] +#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] +pub mod test; 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/client/websocket.rs b/manta-pay/src/signer/client/websocket.rs index 123ed9261..674766e94 100644 --- a/manta-pay/src/signer/client/websocket.rs +++ b/manta-pay/src/signer/client/websocket.rs @@ -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 { diff --git a/manta-pay/src/test/compatibility.rs b/manta-pay/src/test/compatibility.rs index b75cfc187..60d3e3373 100644 --- a/manta-pay/src/test/compatibility.rs +++ b/manta-pay/src/test/compatibility.rs @@ -15,13 +15,17 @@ // 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_full as prove_private_transfer, + to_private::prove_full as prove_to_private, to_public::prove_full as prove_to_public, + }, }; -use manta_accounting::transfer::test::assert_valid_proof; use manta_crypto::rand::{OsRng, Rand}; /// Tests that the circuit is compatible with the current known parameters in `manta-parameters`. @@ -31,32 +35,33 @@ fn compatibility() { 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, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + rng.gen(), + rng.gen(), + &mut rng, + ) + .assert_valid_proof(&verifying_context.to_private); + let _ = &prove_private_transfer( + &proving_context, + ¶meters, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + rng.gen(), + [rng.gen::<_, u128>() / 2, rng.gen::<_, u128>() / 2], + &mut rng, + ) + .1 + .assert_valid_proof(&verifying_context.private_transfer); + let _ = &prove_to_public( + &proving_context, + ¶meters, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + rng.gen(), + [rng.gen::<_, u128>() / 2, rng.gen::<_, u128>() / 2], + &mut rng, + ) + .1 + .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..73edd8613 100644 --- a/manta-pay/src/test/mod.rs +++ b/manta-pay/src/test/mod.rs @@ -16,13 +16,6 @@ //! 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 compatibility; @@ -31,4 +24,4 @@ pub mod transfer; #[cfg(feature = "groth16")] #[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] -pub mod payment; +pub mod payment; \ No newline at end of file diff --git a/manta-pay/src/test/payment.rs b/manta-pay/src/test/payment.rs index 58a5654f9..a899ac9f6 100644 --- a/manta-pay/src/test/payment.rs +++ b/manta-pay/src/test/payment.rs @@ -17,141 +17,374 @@ //! 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}, + self, + utxo::{MerkleTreeConfiguration, UtxoAccumulatorItem, UtxoAccumulatorModel}, + Asset, AssetId, AssetValue, Authorization, AuthorizationContext, Config, FullParametersRef, + MultiProvingContext, Parameters, PrivateTransfer, ProvingContext, Receiver, Sender, ToPrivate, + ToPublic, TransferPost, }; +use manta_accounting::transfer::{self, 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. +/// Builds a new internal pair for use in [`private_transfer::prove`] and [`to_public::prove`]. #[inline] -pub fn prove_mint( - proving_context: &ProvingContext, +fn internal_pair_unchecked( parameters: &Parameters, - utxo_accumulator_model: &UtxoAccumulatorModel, - asset: Asset, + authorization_context: &mut AuthorizationContext, + asset: Asset, rng: &mut R, -) -> config::TransferPost +) -> (Receiver, Sender) where R: CryptoRng + RngCore + ?Sized, { - Mint::from_spending_key(parameters, &SpendingKey::gen(rng), asset, rng) + let (receiver, pre_sender) = transfer::internal_pair::( + parameters, + authorization_context, + rng.gen(), + asset, + Default::default(), + rng, + ); + (receiver, pre_sender.assign_default_proof_unchecked()) +} + +/// 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( + 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_model: &UtxoAccumulatorModel, + rng: &mut R, + ) -> TransferPost + where + R: CryptoRng + RngCore + ?Sized, + { + let asset_id = AssetId::gen(rng); + let values = value_distribution(2, rng.gen(), rng); + let spending_key = rng.gen(); + let mut authorization = Authorization::from_spending_key(parameters, &spending_key, rng); + let (receiver_0, sender_0) = internal_pair_unchecked( + parameters, + &mut authorization.context, + Asset::new(asset_id, values[0]), + rng, + ); + let (receiver_1, sender_1) = internal_pair_unchecked( + parameters, + &mut authorization.context, + Asset::new(asset_id, values[1]), + rng, + ); + 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, + 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_model: &UtxoAccumulatorModel, + rng: &mut R, + ) -> TransferPost + where + 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 mut authorization = Authorization::from_spending_key(parameters, &spending_key, rng); + let (_, sender_0) = + internal_pair_unchecked(parameters, &mut authorization.context, asset_0, rng); + let (receiver_1, sender_1) = internal_pair_unchecked( + parameters, + &mut authorization.context, + Asset::new(asset_id, values[1]), + rng, + ); + 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..f21c0b255 100644 --- a/manta-pay/src/test/transfer.rs +++ b/manta-pay/src/test/transfer.rs @@ -17,59 +17,58 @@ //! 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_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 +81,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,64 +89,162 @@ 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." ); } -/// Tests that `generate_proof_input` from [`Transfer`] and [`TransferPost`] gives the same [`ProofInput`]. +/// Checks that an empty message will produce a valid signature. #[test] -fn generate_proof_input_is_compatibile() { +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); + manta_accounting::transfer::utxo::auth::test::signature_correctness( + ¶meters, + &spending_key, + &post.body, + &mut rng, + ); +} + +/// 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); + manta_accounting::transfer::utxo::auth::test::signature_correctness( + ¶meters, + &spending_key, + &post.body, + &mut rng, + ); +} + +/// 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`." + "For a random ToPublic, `generate_proof_input` from `Transfer` and `TransferPost` should have given the same `ProofInput`." ); } +/* TODO: fix the fuzzing test bug, then uncomment. /// Checks that a [`TransferPost`] is valid, and that its proof cannot be verified when tested against a fuzzed /// or randomized `public_input`. #[inline] @@ -178,30 +276,28 @@ fn validity_check_with_fuzzing( |input| (0..input.len()).map(|_| rng.gen()).collect(), ); } - -/// 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()), &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, &mut rng, ) - .expect("Random Mint should have produced a proof."); + .expect("Random ToPrivate should have produced a proof."); validity_check_with_fuzzing(&verifying_context, &post, &mut rng); } - /// Tests a [`PrivateTransfer`] proof is valid verified against the right public input and invalid /// when the public input has been fuzzed or randomly generated. #[test] @@ -224,26 +320,26 @@ fn private_transfer_proof_validity() { .expect("Random Private Transfer should have produced a proof."); 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()), &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, &mut rng, ) - .expect("Random Reclaim should have produced a proof."); + .expect("Random ToPublic should have produced a proof."); validity_check_with_fuzzing(&verifying_context, &post, &mut rng); } +*/ From eed927ee79ea170a0500bdf9afd537aee03b3337 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Tue, 29 Nov 2022 16:49:26 +0100 Subject: [PATCH 12/44] parameters, test, simulation and binaries Signed-off-by: Francisco Hernandez Iglesias --- manta-parameters/src/lib.rs | 2 +- manta-pay/src/bin/generate_parameters.rs | 105 ++++++++-- manta-pay/src/bin/measure.rs | 74 ------- manta-pay/src/bin/simulation.rs | 7 +- manta-pay/src/lib.rs | 6 +- .../src/simulation/ledger/http/client.rs | 9 +- .../src/simulation/ledger/http/server.rs | 7 +- manta-pay/src/simulation/ledger/mod.rs | 196 ++++++++---------- manta-pay/src/simulation/mod.rs | 32 ++- manta-pay/src/test/compatibility.rs | 2 +- manta-pay/src/test/mod.rs | 2 +- manta-pay/src/test/transfer.rs | 4 +- 12 files changed, 220 insertions(+), 226 deletions(-) delete mode 100644 manta-pay/src/bin/measure.rs diff --git a/manta-parameters/src/lib.rs b/manta-parameters/src/lib.rs index 941137608..07ea3e9b7 100644 --- a/manta-parameters/src/lib.rs +++ b/manta-parameters/src/lib.rs @@ -137,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] diff --git a/manta-pay/src/bin/generate_parameters.rs b/manta-pay/src/bin/generate_parameters.rs index 8dbf7ef38..a6f0cc63a 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, @@ -44,26 +47,37 @@ pub fn main() -> io::Result<()> { 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 +85,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 +161,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 +199,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..ad47fa2e0 100644 --- a/manta-pay/src/bin/simulation.rs +++ b/manta-pay/src/bin/simulation.rs @@ -19,7 +19,10 @@ 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}; + +// cargo run --release --package manta-pay --all-features --bin simulation +// cargo run --release --package manta-pay --all-features --bin simulation 5 100000000 3 1000 > simulation_output_1 /// Runs the Manta Pay simulation. pub fn main() { @@ -29,7 +32,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/lib.rs b/manta-pay/src/lib.rs index 9dffbaeb7..ce8e356ef 100644 --- a/manta-pay/src/lib.rs +++ b/manta-pay/src/lib.rs @@ -41,9 +41,9 @@ pub mod parameters; #[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] pub mod signer; -// #[cfg(all(feature = "groth16", feature = "simulation"))] -// #[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "simulation"))))] -// pub mod simulation; +#[cfg(all(feature = "groth16", feature = "simulation"))] +#[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "simulation"))))] +pub mod simulation; #[cfg(any(test, feature = "test"))] #[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] 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..77db5bb50 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,27 @@ impl ReceiverLedger for Ledger { #[inline] fn register( &mut self, - utxo: Self::ValidUtxo, - note: EncryptedNote, super_key: &Self::SuperPostingKey, + utxo: Self::ValidUtxo, + note: FullIncomingNote, ) { + let temp = self.parameters.item_hash(&utxo.0, &mut ()); // todo let _ = super_key; let shard = self .shards - .get_mut(&MerkleTreeConfiguration::tree_index(&utxo.0)) + .get_mut(&MerkleTreeConfiguration::tree_index(&temp)) .expect("All shards exist when the ledger is constructed."); shard.insert((utxo.0, note)); self.utxos.insert(utxo.0); - self.utxo_forest.push(&utxo.0); + self.utxo_forest.push(&temp); } } 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 +308,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 +333,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 +351,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 +364,7 @@ impl TransferLedger for Ledger { } else { Err(InvalidSinkAccount { account_id, - asset_id, + asset_id: *asset_id, deposit, }) } @@ -383,24 +375,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 +397,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 +428,7 @@ impl TransferLedger for Ledger { pub type SharedLedger = Arc>; /// Ledger Connection +#[derive(Clone, Debug)] pub struct LedgerConnection { /// Ledger Account account: AccountId, @@ -486,16 +475,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..87ba81c92 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,15 @@ 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, { + // FIXME: rng 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/compatibility.rs b/manta-pay/src/test/compatibility.rs index 60d3e3373..9bc8060b3 100644 --- a/manta-pay/src/test/compatibility.rs +++ b/manta-pay/src/test/compatibility.rs @@ -57,7 +57,7 @@ fn compatibility() { let _ = &prove_to_public( &proving_context, ¶meters, - &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + &mut UtxoAccumulator::new(utxo_accumulator_model), rng.gen(), [rng.gen::<_, u128>() / 2, rng.gen::<_, u128>() / 2], &mut rng, diff --git a/manta-pay/src/test/mod.rs b/manta-pay/src/test/mod.rs index 73edd8613..d4e365043 100644 --- a/manta-pay/src/test/mod.rs +++ b/manta-pay/src/test/mod.rs @@ -24,4 +24,4 @@ pub mod transfer; #[cfg(feature = "groth16")] #[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] -pub mod payment; \ No newline at end of file +pub mod payment; diff --git a/manta-pay/src/test/transfer.rs b/manta-pay/src/test/transfer.rs index f21c0b255..ff6168b82 100644 --- a/manta-pay/src/test/transfer.rs +++ b/manta-pay/src/test/transfer.rs @@ -17,9 +17,7 @@ //! Manta Pay Transfer Testing use crate::{ - config::{ - FullParametersRef, Parameters, PrivateTransfer, ProofSystem, ToPrivate, ToPublic, - }, + config::{FullParametersRef, Parameters, PrivateTransfer, ProofSystem, ToPrivate, ToPublic}, test::payment::UtxoAccumulator, }; use manta_crypto::{ From f5d957e6ebaf76f915c25985e646ee03ff5ad006 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Tue, 29 Nov 2022 17:36:40 +0100 Subject: [PATCH 13/44] benchmark Signed-off-by: Francisco Hernandez Iglesias --- manta-benchmark/Cargo.toml | 4 +- manta-benchmark/benches/private_transfer.rs | 35 +++-- .../benches/{mint.rs => to_private.rs} | 35 +++-- .../benches/{reclaim.rs => to_public.rs} | 45 +++--- manta-benchmark/src/lib.rs | 103 +------------ manta-benchmark/src/transfer.rs | 144 ++++++++++++++++++ manta-benchmark/tests/web.rs | 14 +- manta-benchmark/www/index.js | 22 +-- 8 files changed, 237 insertions(+), 165 deletions(-) rename manta-benchmark/benches/{mint.rs => to_private.rs} (66%) rename manta-benchmark/benches/{reclaim.rs => to_public.rs} (60%) create mode 100644 manta-benchmark/src/transfer.rs diff --git a/manta-benchmark/Cargo.toml b/manta-benchmark/Cargo.toml index d6829c377..b6ee061df 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] diff --git a/manta-benchmark/benches/private_transfer.rs b/manta-benchmark/benches/private_transfer.rs index 15e875395..21974b9f0 100644 --- a/manta-benchmark/benches/private_transfer.rs +++ b/manta-benchmark/benches/private_transfer.rs @@ -17,20 +17,26 @@ //! 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_crypto::rand::{OsRng, Rand}; +use manta_pay::{ + parameters, + test::payment::{private_transfer::prove_full, 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("private transfer prove", |b| { + let asset_id = black_box(rng.gen()); + let asset_value = black_box(rng.gen()); b.iter(|| { - prove_private_transfer( + prove_full( &proving_context, ¶meters, - &utxo_accumulator_model, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + asset_id, + asset_value, &mut rng, ); }) @@ -42,15 +48,20 @@ 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, - ¶meters, - &utxo_accumulator_model, - &mut rng, - )); + let private_transfer = black_box( + prove_full( + &proving_context, + ¶meters, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + rng.gen(), + rng.gen(), + &mut rng, + ) + .1, + ); group.bench_function("private transfer verify", |b| { b.iter(|| { - assert_valid_proof(&verifying_context.private_transfer, &private_transfer); + private_transfer.assert_valid_proof(&verifying_context.private_transfer); }) }); } diff --git a/manta-benchmark/benches/mint.rs b/manta-benchmark/benches/to_private.rs similarity index 66% rename from manta-benchmark/benches/mint.rs rename to manta-benchmark/benches/to_private.rs index bb62b27e5..f2842268e 100644 --- a/manta-benchmark/benches/mint.rs +++ b/manta-benchmark/benches/to_private.rs @@ -14,26 +14,30 @@ // 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_pay::{ + parameters, + test::payment::{to_private::prove_full, UtxoAccumulator}, +}; 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| { + let asset_id = black_box(rng.gen()); + let asset_value = black_box(rng.gen()); b.iter(|| { - prove_mint( - &proving_context.mint, + prove_full( + &proving_context.to_private, ¶meters, - &utxo_accumulator_model, - asset, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + asset_id, + asset_value, &mut rng, ); }) @@ -45,19 +49,20 @@ 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_full( + &proving_context.to_private, ¶meters, - &utxo_accumulator_model, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + rng.gen(), rng.gen(), &mut rng, )); group.bench_function("mint 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 60% rename from manta-benchmark/benches/reclaim.rs rename to manta-benchmark/benches/to_public.rs index 9277238be..f116fb6a2 100644 --- a/manta-benchmark/benches/reclaim.rs +++ b/manta-benchmark/benches/to_public.rs @@ -14,23 +14,29 @@ // 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_crypto::rand::{OsRng, Rand}; +use manta_pay::{ + parameters, + test::payment::{to_public::prove_full, 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| { + let asset_id = black_box(rng.gen()); + let asset_value = black_box(rng.gen()); b.iter(|| { - prove_reclaim( + prove_full( &proving_context, ¶meters, - &utxo_accumulator_model, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + asset_id, + asset_value, &mut rng, ); }) @@ -42,18 +48,23 @@ 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, - ¶meters, - &utxo_accumulator_model, - &mut rng, - )); - group.bench_function("reclaim verify", |b| { + let to_public = black_box( + prove_full( + &proving_context, + ¶meters, + &mut UtxoAccumulator::new(utxo_accumulator_model.clone()), + rng.gen(), + rng.gen(), + &mut rng, + ) + .1, + ); + group.bench_function("to public verify", |b| { b.iter(|| { - assert_valid_proof(&verifying_context.reclaim, &reclaim); + to_public.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..8a782bd2b --- /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 [`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() + } +} + +/// 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(); From 9905cdecad04d97cfcede7faf0b09e4b69d64abc Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Tue, 29 Nov 2022 17:49:03 +0100 Subject: [PATCH 14/44] sdk Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/key.rs | 2 +- manta-accounting/src/wallet/signer.rs | 2 +- manta-parameters/Cargo.toml | 2 +- manta-pay/src/signer/client/http.rs | 10 +++++++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/manta-accounting/src/key.rs b/manta-accounting/src/key.rs index 03f878fd5..f509d5dd6 100644 --- a/manta-accounting/src/key.rs +++ b/manta-accounting/src/key.rs @@ -327,7 +327,7 @@ where M: AccountMap, { /// Account Collection - keys: H, + pub keys: H, // This has been made public because of compatibility issues with sdk. /// Account Map accounts: M, diff --git a/manta-accounting/src/wallet/signer.rs b/manta-accounting/src/wallet/signer.rs index 124f4e57e..651a355c3 100644 --- a/manta-accounting/src/wallet/signer.rs +++ b/manta-accounting/src/wallet/signer.rs @@ -628,7 +628,7 @@ where /// 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 /// a local `assets` map for each account. - accounts: AccountTable, + pub accounts: AccountTable, // This has been made public because of compatibility issues with sdk. /// UTXO Accumulator utxo_accumulator: C::UtxoAccumulator, diff --git a/manta-parameters/Cargo.toml b/manta-parameters/Cargo.toml index 6f4138447..d24b52033 100644 --- a/manta-parameters/Cargo.toml +++ b/manta-parameters/Cargo.toml @@ -51,4 +51,4 @@ anyhow = { version = "1.0.57", 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"] } -walkdir = { version = "2.3.2", default-features = false } \ No newline at end of file +walkdir = { version = "2.3.2", default-features = false } diff --git a/manta-pay/src/signer/client/http.rs b/manta-pay/src/signer/client/http.rs index 4294d1dd7..57d919501 100644 --- a/manta-pay/src/signer/client/http.rs +++ b/manta-pay/src/signer/client/http.rs @@ -86,7 +86,7 @@ impl signer::Connection for Client { &mut self, request: SyncRequest, ) -> LocalBoxFutureResult, Self::Error> { - Box::pin(async move { self.base.post("sync", &request).await }) + Box::pin(async move { self.base.post("sync", &self.wrap_request(request)).await }) } #[inline] @@ -94,11 +94,15 @@ impl signer::Connection for Client { &mut self, request: SignRequest, ) -> LocalBoxFutureResult, Self::Error> { - Box::pin(async move { self.base.post("sign", &request).await }) + 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", &GetRequest::Get).await }) + Box::pin(async move { + self.base + .post("address", &self.wrap_request(GetRequest::Get)) + .await + }) } } From 4a714cfd857b13e7ee94845b5f971fe398b4c8ea Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Tue, 29 Nov 2022 18:40:28 +0100 Subject: [PATCH 15/44] re-exports in manta-pay Signed-off-by: Francisco Hernandez Iglesias --- manta-pay/src/config/utxo.rs | 2 +- manta-pay/src/lib.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/manta-pay/src/config/utxo.rs b/manta-pay/src/config/utxo.rs index edd55c701..161e670d4 100644 --- a/manta-pay/src/config/utxo.rs +++ b/manta-pay/src/config/utxo.rs @@ -1296,7 +1296,7 @@ impl merkle_tree::forest::Configuration for MerkleTreeConfiguration { } } -#[cfg(any(feature = "parameters", test))] +#[cfg(feature = "test")] impl merkle_tree::test::HashParameterSampling for MerkleTreeConfiguration { type LeafHashParameterDistribution = (); type InnerHashParameterDistribution = (); diff --git a/manta-pay/src/lib.rs b/manta-pay/src/lib.rs index ce8e356ef..0f41fe0f6 100644 --- a/manta-pay/src/lib.rs +++ b/manta-pay/src/lib.rs @@ -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; From 7eda1b110a5d940f761dcc92934b74765abd06a0 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Tue, 29 Nov 2022 18:48:00 +0100 Subject: [PATCH 16/44] feature issue solved Signed-off-by: Francisco Hernandez Iglesias --- manta-benchmark/Cargo.toml | 2 +- manta-pay/src/config/utxo.rs | 2 +- manta-pay/src/lib.rs | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/manta-benchmark/Cargo.toml b/manta-benchmark/Cargo.toml index b6ee061df..e691c6a1e 100644 --- a/manta-benchmark/Cargo.toml +++ b/manta-benchmark/Cargo.toml @@ -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-pay/src/config/utxo.rs b/manta-pay/src/config/utxo.rs index 161e670d4..edd55c701 100644 --- a/manta-pay/src/config/utxo.rs +++ b/manta-pay/src/config/utxo.rs @@ -1296,7 +1296,7 @@ impl merkle_tree::forest::Configuration for MerkleTreeConfiguration { } } -#[cfg(feature = "test")] +#[cfg(any(feature = "parameters", test))] impl merkle_tree::test::HashParameterSampling for MerkleTreeConfiguration { type LeafHashParameterDistribution = (); type InnerHashParameterDistribution = (); diff --git a/manta-pay/src/lib.rs b/manta-pay/src/lib.rs index 0f41fe0f6..9caa51605 100644 --- a/manta-pay/src/lib.rs +++ b/manta-pay/src/lib.rs @@ -29,18 +29,18 @@ pub mod crypto; #[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] pub mod config; +#[cfg(feature = "groth16")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] +pub mod signer; + #[cfg(feature = "key")] #[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")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] -pub mod signer; - #[cfg(all(feature = "groth16", feature = "simulation"))] #[cfg_attr(doc_cfg, doc(cfg(all(feature = "groth16", feature = "simulation"))))] pub mod simulation; From 9853cdf16b7e80332c106f67481c209dc160393a Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Tue, 29 Nov 2022 21:28:37 +0100 Subject: [PATCH 17/44] docs Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/wallet/test/mod.rs | 2 +- manta-benchmark/src/transfer.rs | 2 +- manta-crypto/src/arkworks/constraint/mod.rs | 2 +- manta-pay/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manta-accounting/src/wallet/test/mod.rs b/manta-accounting/src/wallet/test/mod.rs index 52794c74b..ec2d95e16 100644 --- a/manta-accounting/src/wallet/test/mod.rs +++ b/manta-accounting/src/wallet/test/mod.rs @@ -487,7 +487,7 @@ where self.sync().await.map_err(|err| action.label(err)) } - /// Posts `transaction` to the ledger, returning a success [`Response`](L::Response) if the + /// Posts `transaction` to the ledger, returning a success [`Response`](ledger::Write::Response) if the /// `transaction` was successfully posted. #[inline] async fn post( diff --git a/manta-benchmark/src/transfer.rs b/manta-benchmark/src/transfer.rs index 8a782bd2b..3edf32e4a 100644 --- a/manta-benchmark/src/transfer.rs +++ b/manta-benchmark/src/transfer.rs @@ -47,7 +47,7 @@ pub struct Context { #[wasm_bindgen] impl Context { - /// Constructs a new [`Context`] which can be used for proving and verifying [`Proof`]. + /// 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) = diff --git a/manta-crypto/src/arkworks/constraint/mod.rs b/manta-crypto/src/arkworks/constraint/mod.rs index c568a9a1a..217e75ddd 100644 --- a/manta-crypto/src/arkworks/constraint/mod.rs +++ b/manta-crypto/src/arkworks/constraint/mod.rs @@ -536,7 +536,7 @@ where ) } -/// Returns the remainder of `value` divided by the modulus of the [`Primefield`] `R`. +/// Returns the remainder of `value` divided by the modulus of the [`PrimeField`] `R`. #[inline] pub fn rem_mod_prime(value: F) -> R where diff --git a/manta-pay/Cargo.toml b/manta-pay/Cargo.toml index 685c4ad1e..74bce96d5 100644 --- a/manta-pay/Cargo.toml +++ b/manta-pay/Cargo.toml @@ -52,7 +52,7 @@ download = ["manta-parameters/download", "std"] groth16 = ["manta-crypto/ark-groth16", "ark-snark", "arkworks"] # Enable HTTP Signer Client -http = ["manta-util/reqwest", "serde"] +http = ["manta-util/reqwest", "serde", "network"] # Key Features key = ["bip32", "bip0039"] From 9fbbdf5f64125605d9fb5b2902fc9847d55c309f Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Tue, 29 Nov 2022 21:30:51 +0100 Subject: [PATCH 18/44] changelog Signed-off-by: Francisco Hernandez Iglesias --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9021f8a5..796e0c1d0 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 v.1.0.0. ### Changed - [\#283](https://github.com/Manta-Network/manta-rs/pull/283) Upgrade asset system. From c4e0eecb6ae74a6de5af9d9c09583a955f26c21c Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Wed, 30 Nov 2022 12:36:57 +0100 Subject: [PATCH 19/44] sample impls revised Signed-off-by: Francisco Hernandez Iglesias --- CHANGELOG.md | 2 +- manta-pay/src/bin/simulation.rs | 2 +- manta-pay/src/crypto/poseidon/hash.rs | 8 ++++---- manta-pay/src/crypto/poseidon/mod.rs | 6 +++--- manta-pay/src/parameters.rs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 796e0c1d0..f6d3f5ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +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 v.1.0.0. +- [\#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-pay/src/bin/simulation.rs b/manta-pay/src/bin/simulation.rs index ad47fa2e0..ab9606e5f 100644 --- a/manta-pay/src/bin/simulation.rs +++ b/manta-pay/src/bin/simulation.rs @@ -28,7 +28,7 @@ use manta_pay::{config::FullParametersRef, simulation::Simulation}; pub fn main() { let simulation = Simulation::parse(); let mut rng = OsRng; - let parameters = rng.gen(); + let parameters = rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(); let utxo_accumulator_model = rng.gen(); let (proving_context, verifying_context) = generate_context( &(), diff --git a/manta-pay/src/crypto/poseidon/hash.rs b/manta-pay/src/crypto/poseidon/hash.rs index ef45df871..cff32115d 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, + S: Specification, + S::ParameterField: Field + FieldGeneration + Sample, T: DomainTag, { #[inline] - fn sample(distribution: (), rng: &mut R) -> Self + fn sample(distribution: D, 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 078a6ece0..5472d931c 100644 --- a/manta-pay/src/crypto/poseidon/mod.rs +++ b/manta-pay/src/crypto/poseidon/mod.rs @@ -519,13 +519,13 @@ where } } -impl Sample for Permutation +impl Sample for Permutation where - S: Specification, + S: Specification, S::ParameterField: Field + FieldGeneration, { #[inline] - fn sample(distribution: (), rng: &mut R) -> Self + fn sample(distribution: D, rng: &mut R) -> Self where R: RngCore + ?Sized, { diff --git a/manta-pay/src/parameters.rs b/manta-pay/src/parameters.rs index 01755679f..7aa97606a 100644 --- a/manta-pay/src/parameters.rs +++ b/manta-pay/src/parameters.rs @@ -65,7 +65,7 @@ pub fn generate_from_seed( ProofSystemError, > { let mut rng = ChaCha20Rng::from_seed(seed); - let parameters = rng.gen(); + let parameters = rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(); let utxo_accumulator_model: UtxoAccumulatorModel = rng.gen(); let full_parameters = FullParametersRef::new(¶meters, &utxo_accumulator_model); let (to_private_proving_context, to_private_verifying_context) = From a451d2e5756699b1f29fd0bcf7a3e008a655b80d Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Wed, 30 Nov 2022 12:59:59 +0100 Subject: [PATCH 20/44] sample issue solved in tests Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/wallet/balance.rs | 58 ------------------------ manta-pay/src/config/poseidon.rs | 4 +- manta-pay/src/config/utxo.rs | 8 ++-- manta-pay/src/signer/mod.rs | 63 ++++++++++++++++++++++++++ manta-pay/src/test/transfer.rs | 36 +++++++++------ 5 files changed, 93 insertions(+), 76 deletions(-) diff --git a/manta-accounting/src/wallet/balance.rs b/manta-accounting/src/wallet/balance.rs index f4684114a..d84bdd2ad 100644 --- a/manta-accounting/src/wallet/balance.rs +++ b/manta-accounting/src/wallet/balance.rs @@ -277,61 +277,3 @@ pub mod test { ); } } -/* TODO: move these to manta-pay - /// 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); - } - /// - #[cfg(feature = "std")] - #[test] - fn hash_map_full_withdraw() { - assert_full_withdraw_should_remove_entry::(&mut OsRng); - } -*/ diff --git a/manta-pay/src/config/poseidon.rs b/manta-pay/src/config/poseidon.rs index 29e588154..69b20d3be 100644 --- a/manta-pay/src/config/poseidon.rs +++ b/manta-pay/src/config/poseidon.rs @@ -92,7 +92,7 @@ pub mod test { use manta_crypto::{ arkworks::constraint::fp::Fp, encryption::{Decrypt, Encrypt}, - rand::{OsRng, Sample}, + rand::{OsRng, Rand, Sample}, }; /// Tests Poseidon duplexer encryption works. @@ -100,7 +100,7 @@ pub mod test { fn poseidon_duplexer_test() { const N: usize = 3; let mut rng = OsRng; - let duplexer = FixedDuplexer::<1, Spec>::gen(&mut rng); + let duplexer = rng.gen::<((), ()), FixedDuplexer<1, Spec>>(); 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()); diff --git a/manta-pay/src/config/utxo.rs b/manta-pay/src/config/utxo.rs index edd55c701..63671a462 100644 --- a/manta-pay/src/config/utxo.rs +++ b/manta-pay/src/config/utxo.rs @@ -2080,7 +2080,7 @@ pub mod test { algebra::{HasGenerator, ScalarMul}, arkworks::constraint::fp::Fp, encryption::{Decrypt, EmptyHeader, Encrypt}, - rand::{OsRng, Sample}, + rand::{OsRng, Rand, Sample}, }; /// Checks that encryption of light incoming notes is well-executed for [`Config`]. @@ -2130,7 +2130,7 @@ pub mod test { let mut rng = OsRng; let encryption_key = Group::gen(&mut rng); let header = EmptyHeader::default(); - let base_poseidon = IncomingBaseEncryptionScheme::gen(&mut rng); + let base_poseidon = rng.gen::<((), ()), IncomingBaseEncryptionScheme>(); let utxo_commitment_randomness = Fp::::gen(&mut rng); let asset_id = Fp::::gen(&mut rng); let asset_value = u128::gen(&mut rng); @@ -2163,7 +2163,9 @@ pub mod test { #[test] fn check_note_consistency() { let mut rng = OsRng; - let parameters = protocol::Parameters::::gen(&mut rng); + let parameters = rng + .gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), protocol::Parameters>( + ); 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); diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index c0e8511d2..0409ec5e6 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -66,3 +66,66 @@ pub enum GetRequest { #[default] Get, } + +/// Testing Framework +#[cfg(test)] +pub mod test { + /* TODO: move these to manta-pay + /// 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); + } + /// + #[cfg(feature = "std")] + #[test] + fn hash_map_full_withdraw() { + assert_full_withdraw_should_remove_entry::(&mut OsRng); + } + */ +} diff --git a/manta-pay/src/test/transfer.rs b/manta-pay/src/test/transfer.rs index ff6168b82..a9c0ee72b 100644 --- a/manta-pay/src/test/transfer.rs +++ b/manta-pay/src/test/transfer.rs @@ -23,14 +23,17 @@ use crate::{ use manta_crypto::{ accumulator::Accumulator, constraint::{measure::Measure, ProofSystem as _}, - rand::{OsRng, Rand, Sample}, + rand::{OsRng, Rand}, }; /// Tests the generation of proving/verifying contexts for [`ToPrivate`]. #[test] fn sample_to_private_context() { let mut rng = OsRng; - let cs = ToPrivate::unknown_constraints(FullParametersRef::new(&rng.gen(), &rng.gen())); + 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."); } @@ -39,7 +42,10 @@ fn sample_to_private_context() { #[test] fn sample_private_transfer_context() { let mut rng = OsRng; - let cs = PrivateTransfer::unknown_constraints(FullParametersRef::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."); } @@ -48,7 +54,10 @@ fn sample_private_transfer_context() { #[test] fn sample_to_public_context() { let mut rng = OsRng; - let cs = ToPublic::unknown_constraints(FullParametersRef::new(&rng.gen(), &rng.gen())); + 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."); } @@ -60,7 +69,7 @@ fn to_private() { assert!( ToPrivate::sample_and_check_proof( &(), - &rng.gen(), + &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), &mut UtxoAccumulator::new(rng.gen()), None, &mut rng @@ -77,7 +86,7 @@ fn private_transfer() { assert!( PrivateTransfer::sample_and_check_proof( &(), - &rng.gen(), + &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), &mut UtxoAccumulator::new(rng.gen()), Some(&rng.gen()), &mut rng @@ -94,7 +103,7 @@ fn to_public() { assert!( ToPublic::sample_and_check_proof( &(), - &rng.gen(), + &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), &mut UtxoAccumulator::new(rng.gen()), Some(&rng.gen()), &mut rng @@ -110,7 +119,8 @@ fn check_empty_message_signature() { let mut rng = OsRng; assert!( manta_crypto::signature::test::correctness( - &Parameters::gen(&mut rng).signature_scheme(), + &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), Parameters>() + .signature_scheme(), &rng.gen(), &rng.gen(), &vec![], @@ -124,7 +134,7 @@ fn check_empty_message_signature() { #[test] fn private_transfer_check_signature() { let mut rng = OsRng; - let parameters = rng.gen(); + let parameters = rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(); let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); let (proving_context, verifying_context) = PrivateTransfer::generate_context( &(), @@ -155,7 +165,7 @@ fn private_transfer_check_signature() { #[test] fn to_public_check_signature() { let mut rng = OsRng; - let parameters = rng.gen(); + let parameters = rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(); let mut utxo_accumulator = UtxoAccumulator::new(rng.gen()); let (proving_context, verifying_context) = ToPublic::generate_context( &(), @@ -191,7 +201,7 @@ fn to_private_generate_proof_input_is_compatibile() { matches!( ToPrivate::sample_and_check_generate_proof_input_compatibility( &(), - &rng.gen(), + &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), &mut UtxoAccumulator::new(rng.gen()), None, &mut rng @@ -211,7 +221,7 @@ fn private_transfer_generate_proof_input_is_compatibile() { matches!( PrivateTransfer::sample_and_check_generate_proof_input_compatibility( &(), - &rng.gen(), + &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), &mut UtxoAccumulator::new(rng.gen()), Some(&rng.gen()), &mut rng @@ -231,7 +241,7 @@ fn to_public_generate_proof_input_is_compatibile() { matches!( ToPublic::sample_and_check_generate_proof_input_compatibility( &(), - &rng.gen(), + &rng.gen::<(((), (), ((), ()), (), (), (), (), ()), (), ()), _>(), &mut UtxoAccumulator::new(rng.gen()), Some(&rng.gen()), &mut rng From 83ba1dd4ff6d574d139cdd27c6a29bf27ddc5198 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Wed, 30 Nov 2022 13:21:36 +0100 Subject: [PATCH 21/44] balance tests Signed-off-by: Francisco Hernandez Iglesias --- manta-pay/src/signer/mod.rs | 63 ----------------------- manta-pay/src/test/balance.rs | 94 +++++++++++++++++++++++++++++++++++ manta-pay/src/test/mod.rs | 3 ++ 3 files changed, 97 insertions(+), 63 deletions(-) create mode 100644 manta-pay/src/test/balance.rs diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index 0409ec5e6..c0e8511d2 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -66,66 +66,3 @@ pub enum GetRequest { #[default] Get, } - -/// Testing Framework -#[cfg(test)] -pub mod test { - /* TODO: move these to manta-pay - /// 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); - } - /// - #[cfg(feature = "std")] - #[test] - fn hash_map_full_withdraw() { - assert_full_withdraw_should_remove_entry::(&mut OsRng); - } - */ -} diff --git a/manta-pay/src/test/balance.rs b/manta-pay/src/test/balance.rs new file mode 100644 index 000000000..4702b0cae --- /dev/null +++ b/manta-pay/src/test/balance.rs @@ -0,0 +1,94 @@ +// 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/mod.rs b/manta-pay/src/test/mod.rs index d4e365043..8e95962fa 100644 --- a/manta-pay/src/test/mod.rs +++ b/manta-pay/src/test/mod.rs @@ -16,6 +16,9 @@ //! Manta Pay Testing +#[cfg(test)] +pub mod balance; + #[cfg(test)] pub mod compatibility; From 5ce37e4d1995d5236074f99368c5ae09a26c28a5 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Wed, 30 Nov 2022 13:27:04 +0100 Subject: [PATCH 22/44] DEFAULT_BRANCH changed Signed-off-by: Francisco Hernandez Iglesias --- manta-parameters/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manta-parameters/src/lib.rs b/manta-parameters/src/lib.rs index 07ea3e9b7..5a8ac2891 100644 --- a/manta-parameters/src/lib.rs +++ b/manta-parameters/src/lib.rs @@ -110,7 +110,7 @@ pub mod github { pub const CRATE: &str = "manta-parameters"; /// Default GitHub Branch - pub const DEFAULT_BRANCH: &str = "feat/new-circuits"; + pub const DEFAULT_BRANCH: &str = "feat/mantapay-v1"; /// Returns the Git-LFS URL for GitHub content at the given `branch` and `data_path`. #[inline] From ce575e7da913bc123f2088c3550950751265ff21 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Wed, 30 Nov 2022 18:08:42 +0100 Subject: [PATCH 23/44] zero signature issue tested and fixed Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/transfer/utxo/auth.rs | 8 +- .../src/transfer/utxo/protocol.rs | 15 +++- manta-pay/src/bin/simulation.rs | 1 - manta-pay/src/test/transfer.rs | 90 +++++++++++++++++-- 4 files changed, 98 insertions(+), 16 deletions(-) diff --git a/manta-accounting/src/transfer/utxo/auth.rs b/manta-accounting/src/transfer/utxo/auth.rs index 05050575a..9c7f79641 100644 --- a/manta-accounting/src/transfer/utxo/auth.rs +++ b/manta-accounting/src/transfer/utxo/auth.rs @@ -433,7 +433,8 @@ pub mod test { spending_key: &T::SpendingKey, message: &M, rng: &mut R, - ) where + ) -> bool + where T: DeriveContext + DeriveSigningKey + ProveAuthorization @@ -445,9 +446,6 @@ pub mod test { let authorization = Authorization::from_spending_key(parameters, spending_key, rng); let signature = sign(parameters, spending_key, authorization, message, rng) .expect("Unable to sign message."); - assert!( - signature.verify(parameters, message), - "Unable to verify message." - ) + signature.verify(parameters, message) } } diff --git a/manta-accounting/src/transfer/utxo/protocol.rs b/manta-accounting/src/transfer/utxo/protocol.rs index 409ad5906..f07853b1d 100644 --- a/manta-accounting/src/transfer/utxo/protocol.rs +++ b/manta-accounting/src/transfer/utxo/protocol.rs @@ -1095,6 +1095,7 @@ impl auth::VerifySignature for Parameters where C: Configuration, M: Encode, + C::Group: cmp::PartialEq, { #[inline] fn verify( @@ -1103,8 +1104,18 @@ where message: &M, signature: &Self::Signature, ) -> bool { - self.signature_scheme() - .verify(authorization_key, &message.to_vec(), signature, &mut ()) + 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 ()) + } } } diff --git a/manta-pay/src/bin/simulation.rs b/manta-pay/src/bin/simulation.rs index ab9606e5f..b45861dbb 100644 --- a/manta-pay/src/bin/simulation.rs +++ b/manta-pay/src/bin/simulation.rs @@ -22,7 +22,6 @@ use manta_crypto::rand::{OsRng, Rand}; use manta_pay::{config::FullParametersRef, simulation::Simulation}; // cargo run --release --package manta-pay --all-features --bin simulation -// cargo run --release --package manta-pay --all-features --bin simulation 5 100000000 3 1000 > simulation_output_1 /// Runs the Manta Pay simulation. pub fn main() { diff --git a/manta-pay/src/test/transfer.rs b/manta-pay/src/test/transfer.rs index a9c0ee72b..95bceeefb 100644 --- a/manta-pay/src/test/transfer.rs +++ b/manta-pay/src/test/transfer.rs @@ -153,11 +153,14 @@ fn private_transfer_check_signature() { .expect("Random Private Transfer should have produced a proof.") .expect(""); post.assert_valid_proof(&verifying_context); - manta_accounting::transfer::utxo::auth::test::signature_correctness( - ¶meters, - &spending_key, - &post.body, - &mut rng, + assert!( + manta_accounting::transfer::utxo::auth::test::signature_correctness( + ¶meters, + &spending_key, + &post.body, + &mut rng, + ), + "Invalid signature" ); } @@ -184,11 +187,82 @@ fn to_public_check_signature() { .expect("Random To-Public should have produced a proof.") .expect(""); post.assert_valid_proof(&verifying_context); - manta_accounting::transfer::utxo::auth::test::signature_correctness( + 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!" + ); +} + +/// Checks that the zero signature is rejected for a random [`ToPublic`]. +#[test] +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, - &spending_key, - &post.body, + &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!" ); } From 7d314185785e141bacbe0a5afb96d72031d38f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Hern=C3=A1ndez=20Iglesias?= <38819712+SupremoUGH@users.noreply.github.com> Date: Thu, 1 Dec 2022 12:49:04 +0100 Subject: [PATCH 24/44] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Brandon H. Gomes Signed-off-by: Francisco Hernández Iglesias <38819712+SupremoUGH@users.noreply.github.com> --- manta-accounting/src/transfer/mod.rs | 116 ++++++++++++------------- manta-pay/src/config/utxo_utilities.rs | 6 +- manta-pay/src/test/balance.rs | 59 +++++++------ manta-util/src/num.rs | 8 +- 4 files changed, 99 insertions(+), 90 deletions(-) diff --git a/manta-accounting/src/transfer/mod.rs b/manta-accounting/src/transfer/mod.rs index e4994efd6..a65cbbf2b 100644 --- a/manta-accounting/src/transfer/mod.rs +++ b/manta-accounting/src/transfer/mod.rs @@ -446,41 +446,41 @@ where #[derive(derivative::Derivative)] #[derivative( Clone(bound = r" - Authorization: Clone, - C::AssetId: Clone, - C::AssetValue: Clone, - Sender: Clone, - Receiver: Clone"), + 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"), + 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"), + 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"), + 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"), + 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") + Authorization: PartialEq, + C::AssetId: PartialEq, + C::AssetValue: PartialEq, + Sender: PartialEq, + Receiver: PartialEq") )] pub struct Transfer< C, @@ -1981,37 +1981,37 @@ where #[derive(derivative::Derivative)] #[derivative( Debug(bound = r" - AuthorizationKey: Debug, - C::AssetId: Debug, - SourcePostingKey: Debug, - SenderPostingKey: Debug, - ReceiverPostingKey: Debug, - SinkPostingKey: Debug, - Proof: Debug"), + 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"), + 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"), + 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") + AuthorizationKey: PartialEq, + C::AssetId: PartialEq, + SourcePostingKey: PartialEq, + SenderPostingKey: PartialEq, + ReceiverPostingKey: PartialEq, + SinkPostingKey: PartialEq, + Proof: PartialEq") )] pub struct TransferPostingKeyRef<'k, C, L> where diff --git a/manta-pay/src/config/utxo_utilities.rs b/manta-pay/src/config/utxo_utilities.rs index 418ece985..dbc3c81f3 100644 --- a/manta-pay/src/config/utxo_utilities.rs +++ b/manta-pay/src/config/utxo_utilities.rs @@ -27,7 +27,11 @@ use manta_crypto::{ }; /// From a little endian vector `v` of a certain length, it returns a vector of length `n` after removing some zeroes. -/// Panics if things go wrong. +/// +/// # 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, diff --git a/manta-pay/src/test/balance.rs b/manta-pay/src/test/balance.rs index 4702b0cae..c1debc1e0 100644 --- a/manta-pay/src/test/balance.rs +++ b/manta-pay/src/test/balance.rs @@ -38,34 +38,37 @@ 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); - } - )* + ($(( + $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, @@ -80,12 +83,14 @@ define_tests!( 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] diff --git a/manta-util/src/num.rs b/manta-util/src/num.rs index 2ed907415..052dbaec9 100644 --- a/manta-util/src/num.rs +++ b/manta-util/src/num.rs @@ -125,24 +125,24 @@ impl_checked!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128); impl CheckedAdd for &A where - A: CheckedAdd + Copy, + A: Clone + CheckedAdd, { type Output = A::Output; #[inline] fn checked_add(self, rhs: Self) -> Option { - (*self).checked_add(*rhs) + self.clone().checked_add(rhs.clone()) } } impl CheckedSub for &A where - A: CheckedSub + Copy, + A: Clone + CheckedSub, { type Output = A::Output; #[inline] fn checked_sub(self, rhs: Self) -> Option { - (*self).checked_sub(*rhs) + self.clone().checked_sub(rhs.clone()) } } From 1982dd67447e872eb2d0561f60edbe7b413eb405 Mon Sep 17 00:00:00 2001 From: Francisco Hernandez Iglesias Date: Thu, 1 Dec 2022 13:33:19 +0100 Subject: [PATCH 25/44] some comments addressed Signed-off-by: Francisco Hernandez Iglesias --- manta-accounting/src/transfer/canonical.rs | 11 +-- manta-accounting/src/transfer/test.rs | 4 +- manta-accounting/src/wallet/signer.rs | 18 ++--- manta-crypto/src/arkworks/algebra.rs | 19 +++--- manta-crypto/src/arkworks/constraint/mod.rs | 15 +++-- manta-parameters/Cargo.toml | 6 +- manta-parameters/src/lib.rs | 75 +-------------------- manta-pay/Cargo.toml | 10 +-- manta-pay/src/bin/simulation.rs | 2 - manta-pay/src/config/utxo_utilities.rs | 10 +-- manta-pay/src/lib.rs | 8 +-- manta-pay/src/simulation/ledger/mod.rs | 19 +++--- manta-pay/src/simulation/mod.rs | 1 - manta-pay/src/test/balance.rs | 2 +- 14 files changed, 57 insertions(+), 143 deletions(-) diff --git a/manta-accounting/src/transfer/canonical.rs b/manta-accounting/src/transfer/canonical.rs index d122de4e7..cff2a0a4b 100644 --- a/manta-accounting/src/transfer/canonical.rs +++ b/manta-accounting/src/transfer/canonical.rs @@ -28,7 +28,7 @@ use crate::{ VerifyingContext, }, }; -use alloc::{boxed::Box, string::String, vec::Vec}; +use alloc::{string::String, vec::Vec}; use core::{fmt::Debug, hash::Hash}; use manta_crypto::rand::{CryptoRng, RngCore}; use manta_util::{create_seal, seal}; @@ -381,10 +381,10 @@ where F: FnOnce(&Asset) -> bool, { match self { - Self::ToPrivate(asset) => Ok(TransactionKind::Deposit(Box::new(asset.clone()))), + Self::ToPrivate(asset) => Ok(TransactionKind::Deposit(asset.clone())), Self::PrivateTransfer(asset, _) | Self::ToPublic(asset) => { if balance(asset) { - Ok(TransactionKind::Withdraw(Box::new(asset.clone()))) + Ok(TransactionKind::Withdraw(asset.clone())) } else { Err(asset) } @@ -454,6 +454,7 @@ where #[derive(derivative::Derivative)] #[derivative( Clone(bound = "Asset: Clone"), + Copy(bound = "Asset: Copy"), Debug(bound = "Asset: Debug"), Hash(bound = "Asset: Hash"), Eq(bound = "Asset: Eq"), @@ -466,12 +467,12 @@ where /// Deposit Transaction /// /// A transaction of this kind will result in a deposit of `asset`. - Deposit(Box>), + Deposit(Asset), /// Withdraw Transaction /// /// A transaction of this kind will result in a withdraw of `asset`. - Withdraw(Box>), + Withdraw(Asset), } /// Transfer Asset Selection diff --git a/manta-accounting/src/transfer/test.rs b/manta-accounting/src/transfer/test.rs index 70e5bc2c5..82d8d74c1 100644 --- a/manta-accounting/src/transfer/test.rs +++ b/manta-accounting/src/transfer/test.rs @@ -295,7 +295,7 @@ where spending_key, rng, )? - .expect("") + .expect("Sample post cannot return None.") .has_valid_proof(verifying_context) } @@ -367,7 +367,7 @@ where }) .collect(), (None, false) => Vec::new(), - _ => unreachable!(""), + _ => unreachable!("Badly shaped transaction."), }; ( senders, diff --git a/manta-accounting/src/wallet/signer.rs b/manta-accounting/src/wallet/signer.rs index 651a355c3..f80b50d9d 100644 --- a/manta-accounting/src/wallet/signer.rs +++ b/manta-accounting/src/wallet/signer.rs @@ -87,7 +87,7 @@ where request: SignRequest, ) -> LocalBoxFutureResult, SignError>, Self::Error>; - /// Returns addresses according to the `request`. + /// Returns the [`Address`] corresponding to `self`. fn address(&mut self) -> LocalBoxFutureResult, Self::Error>; } @@ -563,13 +563,13 @@ where serde( bound( deserialize = r" - AccountTable: Deserialize<'de>, + AccountTable: Deserialize<'de>, C::UtxoAccumulator: Deserialize<'de>, C::AssetMap: Deserialize<'de>, C::Checkpoint: Deserialize<'de> ", serialize = r" - AccountTable: Serialize, + AccountTable: Serialize, C::UtxoAccumulator: Serialize, C::AssetMap: Serialize, C::Checkpoint: Serialize @@ -582,35 +582,35 @@ where #[derive(derivative::Derivative)] #[derivative( Debug(bound = r" - AccountTable: Debug, + AccountTable: Debug, C::UtxoAccumulator: Debug, C::AssetMap: Debug, C::Checkpoint: Debug, C::Rng: Debug "), Default(bound = r" - AccountTable: Default, + AccountTable: Default, C::UtxoAccumulator: Default, C::AssetMap: Default, C::Checkpoint: Default, C::Rng: Default "), Eq(bound = r" - AccountTable: Eq, + AccountTable: Eq, C::UtxoAccumulator: Eq, C::AssetMap: Eq, C::Checkpoint: Eq, C::Rng: Eq "), Hash(bound = r" - AccountTable: Hash, + AccountTable: Hash, C::UtxoAccumulator: Hash, C::AssetMap: Hash, C::Checkpoint: Hash, C::Rng: Hash "), PartialEq(bound = r" - AccountTable: PartialEq, + AccountTable: PartialEq, C::UtxoAccumulator: PartialEq, C::AssetMap: PartialEq, C::Checkpoint: PartialEq, @@ -1324,7 +1324,7 @@ where result } - /// Returns address according to the `request`. + /// Returns the [`Address`] corresponding to `self`. #[inline] pub fn address(&mut self) -> Address { let account = self.state.accounts.get_default(); diff --git a/manta-crypto/src/arkworks/algebra.rs b/manta-crypto/src/arkworks/algebra.rs index 0c92031c6..00a7c8554 100644 --- a/manta-crypto/src/arkworks/algebra.rs +++ b/manta-crypto/src/arkworks/algebra.rs @@ -703,7 +703,7 @@ mod test { use crate::{ algebra::{test::window_correctness, PrecomputedBaseTable, ScalarMul}, arkworks::{ - algebra::scalar_bits, ed_on_bls12_381::EdwardsProjective as Bls12_381_Edwards, + algebra::scalar_bits, ed_on_bn254::EdwardsProjective as Bn254_Edwards, r1cs_std::groups::curves::twisted_edwards::AffineVar, }, constraint::measure::Measure, @@ -714,15 +714,14 @@ mod test { /// 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 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 base_var = base.as_known::>>(&mut cs); let scalar_var = - scalar.as_known::>>(&mut cs); + 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(); @@ -739,8 +738,8 @@ mod test { fn windowed_mul_is_correct() { window_correctness( 4, - &Scalar::::gen(&mut OsRng), - Group::::gen(&mut OsRng), + &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 217e75ddd..4423aeb10 100644 --- a/manta-crypto/src/arkworks/constraint/mod.rs +++ b/manta-crypto/src/arkworks/constraint/mod.rs @@ -488,27 +488,30 @@ where Ok(value) => { let (quotient, remainder) = div_rem_mod_prime::(value); ( - FpVar::new_witness(self.cs(), full(quotient)).expect(""), + 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(""), + .expect("Allocating a witness is not allowed to fail."), ) } _ => ( - FpVar::new_witness(self.cs(), empty::).expect(""), - FpVar::new_witness(self.cs(), empty::).expect(""), + 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(""); + .expect("This equality holds because of the Euclidean algorithm."); remainder .enforce_cmp(&modulus, core::cmp::Ordering::Less, false) - .expect(""); + .expect("This inequality holds because of the Euclidean algorithm."); remainder } } diff --git a/manta-parameters/Cargo.toml b/manta-parameters/Cargo.toml index d24b52033..1655ef7ef 100644 --- a/manta-parameters/Cargo.toml +++ b/manta-parameters/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "manta-parameters" -version = "0.5.6" +version = "0.5.7" edition = "2021" authors = ["Manta Network "] readme = "README.md" @@ -35,7 +35,7 @@ git = ["anyhow", "git2", "std"] std = ["anyhow?/std"] [dependencies] -anyhow = { version = "1.0.57", 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 } @@ -47,7 +47,7 @@ tempfile = { version = "3.3.0", default-features = false } walkdir = { version = "2.3.2", default-features = false } [build-dependencies] -anyhow = { version = "1.0.57", 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/src/lib.rs b/manta-parameters/src/lib.rs index 5a8ac2891..44c2c8fe9 100644 --- a/manta-parameters/src/lib.rs +++ b/manta-parameters/src/lib.rs @@ -110,7 +110,7 @@ pub mod github { pub const CRATE: &str = "manta-parameters"; /// Default GitHub Branch - pub const DEFAULT_BRANCH: &str = "feat/mantapay-v1"; + pub const DEFAULT_BRANCH: &str = "main"; /// Returns the Git-LFS URL for GitHub content at the given `branch` and `data_path`. #[inline] @@ -168,24 +168,6 @@ pub mod github { ); Ok(()) } - - /// Downloads data from `data_path` relative to the given `branch` to a file at `path` without verifying - /// that the data matches the `checksum`. - #[inline] - pub fn unsafe_download