diff --git a/CHANGELOG.md b/CHANGELOG.md index c7894709d..796964a3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added ### Changed +- [\#282](https://github.com/Manta-Network/manta-rs/pull/282) Upgrade key system. ### Deprecated diff --git a/manta-accounting/src/key.rs b/manta-accounting/src/key.rs index 858dbf3d8..03f878fd5 100644 --- a/manta-accounting/src/key.rs +++ b/manta-accounting/src/key.rs @@ -14,34 +14,22 @@ // You should have received a copy of the GNU General Public License // along with manta-rs. If not, see . -//! Hierarchical Key Derivation Schemes -//! -//! This module defines a Hierarchical Key Derivation Scheme similar to the one defined in the -//! [`BIP-0044`] specification. -//! -//! [`BIP-0044`]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +//! Key Accounting use alloc::vec::Vec; use core::{ - cmp, fmt::{self, Debug}, hash::Hash, - iter, marker::PhantomData, }; -use manta_crypto::{ - key::kdf::KeyDerivationFunction, - rand::{RngCore, Sample}, -}; -use manta_util::collections::btree_map::{self, BTreeMap}; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize}; -/// Hierarchical Key Derivation Parameter Type +/// Base Index Type pub type IndexType = u32; -/// Hierarchical Key Derivation Parameter +/// Index #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), @@ -49,7 +37,7 @@ pub type IndexType = u32; )] #[derive(derivative::Derivative)] #[derivative(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct HierarchicalKeyDerivationParameter { +pub struct Index { /// Index index: IndexType, @@ -57,8 +45,8 @@ pub struct HierarchicalKeyDerivationParameter { __: PhantomData, } -impl HierarchicalKeyDerivationParameter { - /// Builds a new [`HierarchicalKeyDerivationParameter`] from `index`. +impl Index { + /// Builds a new [`Index`] from `index`. #[inline] pub const fn new(index: IndexType) -> Self { Self { @@ -69,7 +57,7 @@ impl HierarchicalKeyDerivationParameter { /// Increments the index of `self` by one unit. #[inline] - fn increment(&mut self) { + pub fn increment(&mut self) { self.index += 1; } @@ -80,7 +68,7 @@ impl HierarchicalKeyDerivationParameter { } } -/// Implements the [`HierarchicalKeyDerivationParameter`] subtype for `$type` and `$index`. +/// Implements the [`Index`] subtype for `$type` and `$index`. macro_rules! impl_index_type { ($doc:expr, $fmt:expr, $type:ident, $index:ident) => { #[doc = $doc] @@ -89,7 +77,7 @@ macro_rules! impl_index_type { pub struct $type; #[doc = $doc] - pub type $index = HierarchicalKeyDerivationParameter<$type>; + pub type $index = Index<$type>; impl Debug for $index { #[inline] @@ -97,6 +85,20 @@ macro_rules! impl_index_type { f.debug_tuple($fmt).field(&self.index).finish() } } + + impl From for $index { + #[inline] + fn from(index: IndexType) -> Self { + Self::new(index) + } + } + + impl From<$index> for IndexType { + #[inline] + fn from(index: $index) -> Self { + index.index() + } + } }; } @@ -107,679 +109,202 @@ impl_index_type!( AccountIndex ); -impl_index_type!("Key Index", "KeyIndex", KeyIndexType, KeyIndex); - -/// Key Kind -#[cfg_attr( - feature = "serde", - derive(Deserialize, Serialize), - serde(crate = "manta_util::serde", deny_unknown_fields) -)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Kind { - /// Spend Key - Spend, - - /// View Key - View, -} - -/// Hierarchical Key Derivation Scheme -pub trait HierarchicalKeyDerivationScheme { - /// [`KeyIndex`] Gap Limit - const GAP_LIMIT: IndexType; - - /// Secret Key Type - type SecretKey; - - /// Derives a secret key for `account` with `kind` and `index`. - fn derive(&self, account: AccountIndex, kind: Kind, index: KeyIndex) -> Self::SecretKey; - - /// Derives a spend secret key for `account` using the spend `index`. - #[inline] - fn derive_spend(&self, account: AccountIndex, index: KeyIndex) -> Self::SecretKey { - self.derive(account, Kind::Spend, index) - } - - /// Derives a view secret key for `account` using the view `index`. - #[inline] - fn derive_view(&self, account: AccountIndex, index: KeyIndex) -> Self::SecretKey { - self.derive(account, Kind::View, index) - } +/// Account Collection +pub trait AccountCollection { + /// Spending Key Type + type SpendingKey; - /// Derives a spend-view pair of secret keys for `account` and `index`. - #[inline] - fn derive_pair(&self, account: AccountIndex, index: KeyIndex) -> SecretKeyPair { - SecretKeyPair::new( - self.derive_spend(account, index), - self.derive_view(account, index), - ) - } - - /// Borrows `self` rather than consuming it, returning an implementation of - /// [`HierarchicalKeyDerivationScheme`]. - #[inline] - fn by_ref(&self) -> &Self { - self - } - - /// Maps `self` along a key derivation function. - #[inline] - fn map(self, key_derivation_function: F) -> Map - where - Self: Sized, - F: KeyDerivationFunction, - { - Map::new(self, key_derivation_function) - } + /// Returns the [`SpendingKey`] corresponding to `index`. + fn spending_key(&self, index: &AccountIndex) -> Self::SpendingKey; } -impl HierarchicalKeyDerivationScheme for &H +impl AccountCollection for &A where - H: HierarchicalKeyDerivationScheme + ?Sized, + A: AccountCollection, { - const GAP_LIMIT: IndexType = H::GAP_LIMIT; - - type SecretKey = H::SecretKey; + type SpendingKey = A::SpendingKey; #[inline] - fn derive(&self, account: AccountIndex, kind: Kind, index: KeyIndex) -> Self::SecretKey { - (*self).derive(account, kind, index) - } - - #[inline] - fn derive_spend(&self, account: AccountIndex, index: KeyIndex) -> Self::SecretKey { - (*self).derive_spend(account, index) - } - - #[inline] - fn derive_view(&self, account: AccountIndex, index: KeyIndex) -> Self::SecretKey { - (*self).derive_view(account, index) + fn spending_key(&self, index: &AccountIndex) -> Self::SpendingKey { + (**self).spending_key(index) } } -/// Mapping Hierarchical Key Derivation Scheme -#[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 Map -where - H: HierarchicalKeyDerivationScheme, - F: KeyDerivationFunction, -{ - /// Base Derivation Scheme - base: H, +/// Spending Key Type +pub type SpendingKey = ::SpendingKey; - /// Key Derivation Function - key_derivation_function: F, -} - -impl Map -where - H: HierarchicalKeyDerivationScheme, - F: KeyDerivationFunction, -{ - /// Builds a new [`Map`] from `base` and `key_derivation_function`. - #[inline] - pub fn new(base: H, key_derivation_function: F) -> Self { - Self { - base, - key_derivation_function, - } - } +/// Account Map +pub trait AccountMap { + /// Account Type + type Account; - /// Returns the base [`HierarchicalKeyDerivationScheme`] for `self`. - #[inline] - pub fn base(&self) -> &H { - &self.base - } -} + /// Builds a new [`AccountMap`] with a starting account. + fn new() -> Self; -impl HierarchicalKeyDerivationScheme for Map -where - H: HierarchicalKeyDerivationScheme, - F: KeyDerivationFunction, -{ - const GAP_LIMIT: IndexType = H::GAP_LIMIT; + /// Returns the last account index stored in the map. + fn last_account(&self) -> AccountIndex; - type SecretKey = F::Output; + /// Adds a new account to the map, returning the new account index. + fn create_account(&mut self) -> AccountIndex; - #[inline] - fn derive(&self, account: AccountIndex, kind: Kind, index: KeyIndex) -> Self::SecretKey { - self.key_derivation_function - .derive(&self.base.derive(account, kind, index), &mut ()) - } + /// Returns a shared reference to the [`Account`](Self::Account) associated to `account`. + fn get(&self, account: AccountIndex) -> Option<&Self::Account>; + /// Returns a shared referece to the default [`Account`](Self::Account) for this map. #[inline] - fn derive_spend(&self, account: AccountIndex, index: KeyIndex) -> Self::SecretKey { - self.key_derivation_function - .derive(&self.base.derive_spend(account, index), &mut ()) + fn get_default(&self) -> &Self::Account { + self.get(Default::default()) + .expect("The default account must always exist.") } - #[inline] - fn derive_view(&self, account: AccountIndex, index: KeyIndex) -> Self::SecretKey { - self.key_derivation_function - .derive(&self.base.derive_view(account, index), &mut ()) - } -} + /// Returns a mutable reference to the [`Account`](Self::Account) associated to `account`. + fn get_mut(&mut self, account: AccountIndex) -> Option<&mut Self::Account>; -impl Sample<(D, F)> for Map -where - H: HierarchicalKeyDerivationScheme + Sample, - F: KeyDerivationFunction, -{ + /// Returns a mutable reference to the default [`Account`](Self::Account) for this map. #[inline] - fn sample(distribution: (D, F), rng: &mut R) -> Self - where - R: RngCore + ?Sized, - { - Self::new(H::sample(distribution.0, rng), distribution.1) + fn get_mut_default(&mut self) -> &mut Self::Account { + self.get_mut(Default::default()) + .expect("The default account must always exist.") } } -/// Hierarchical Key Derivation Secret Key Pair -pub struct SecretKeyPair -where - H: HierarchicalKeyDerivationScheme + ?Sized, -{ - /// Spend Part of the Key Pair - pub spend: H::SecretKey, - - /// View Part of the Key Pair - pub view: H::SecretKey, -} - -impl SecretKeyPair -where - H: HierarchicalKeyDerivationScheme + ?Sized, -{ - /// Builds a new [`SecretKeyPair`] from `spend` and `view`. - #[inline] - pub fn new(spend: H::SecretKey, view: H::SecretKey) -> Self { - Self { spend, view } - } -} - -/// Account Keys -#[derive(derivative::Derivative)] -#[derivative( - Clone(bound = ""), - Copy(bound = ""), - Debug(bound = "H: Debug"), - Eq(bound = "H: Eq"), - Hash(bound = "H: Hash"), - PartialEq(bound = "H: PartialEq") -)] -pub struct AccountKeys<'h, H> -where - H: HierarchicalKeyDerivationScheme + ?Sized, -{ - /// Hierarchical Key Derivation Scheme - keys: &'h H, - - /// Account Index - index: AccountIndex, - - /// Account Information - account: Account, -} +/// [`Vec`] Account Map Type +pub type VecAccountMap = Vec; -impl<'h, H> AccountKeys<'h, H> -where - H: HierarchicalKeyDerivationScheme + ?Sized, -{ - /// Builds a new [`AccountKeys`] from `keys`, `index`, and `account`. - #[inline] - fn new(keys: &'h H, index: AccountIndex, account: Account) -> Self { - Self { - keys, - index, - account, - } - } +impl AccountMap for VecAccountMap { + type Account = AccountIndex; - /// Performs the bounds check on `index` and then runs `f`. #[inline] - fn with_bounds_check(&self, index: KeyIndex, f: F) -> Option - where - F: FnOnce(&Self, KeyIndex) -> T, - { - (index <= self.account.maximum_index).then(|| f(self, index)) + fn new() -> Self { + let mut this = Self::new(); + this.create_account(); + this } - /// Derives the spend key for this account at `index` without performing bounds checks. #[inline] - fn derive_spend(&self, index: KeyIndex) -> H::SecretKey { - self.keys.derive_spend(self.index, index) + fn last_account(&self) -> AccountIndex { + AccountIndex::new( + (self.len() - 1) + .try_into() + .expect("AccountIndex is not allowed to exceed IndexType::MAX."), + ) } - /// Returns the default spend key for this account. #[inline] - pub fn default_spend_key(&self) -> H::SecretKey { - self.derive_spend(Default::default()) + fn create_account(&mut self) -> AccountIndex { + let index = AccountIndex::new( + self.len() + .try_into() + .expect("AccountIndex is not allowed to exceed IndexType::MAX."), + ); + self.push(Default::default()); + index } - /// Returns the spend key for this account at `index`, if it does not exceed the maximum index. #[inline] - pub fn spend_key(&self, index: KeyIndex) -> Option { - self.with_bounds_check(index, Self::derive_spend) + fn get(&self, account: AccountIndex) -> Option<&Self::Account> { + self.as_slice().get(account.index() as usize) } - /// Derives the view key for this account at `index` without performing bounds checks. #[inline] - fn derive_view(&self, index: KeyIndex) -> H::SecretKey { - self.keys.derive_view(self.index, index) + fn get_mut(&mut self, account: AccountIndex) -> Option<&mut Self::Account> { + self.as_mut_slice().get_mut(account.index() as usize) } +} - /// Returns the default view key for this account. - #[inline] - pub fn default_view_key(&self) -> H::SecretKey { - self.derive_view(Default::default()) - } +/// Derive Address Trait +pub trait DeriveAddress { + /// Address Generation Parameters + type Parameters; - /// Returns the view key for this account at `index`, if it does not exceed the maximum index. - #[inline] - pub fn view_key(&self, index: KeyIndex) -> Option { - self.with_bounds_check(index, Self::derive_view) - } + /// Address Type + type Address; - /// Derives the secret key pair for this account at `index` without performing bounds checks. - #[inline] - fn derive_pair(&self, index: KeyIndex) -> SecretKeyPair { - self.keys.derive_pair(self.index, index) - } + /// Returns the [`Address`](Self::Address) corresponding to `self`. + fn address(&self, parameters: &Self::Parameters) -> Self::Address; +} - /// Returns the default secret key pair for this account. - #[inline] - pub fn default_keypair(&self) -> SecretKeyPair { - self.derive_pair(Default::default()) - } +/// Derive Addresses Trait +pub trait DeriveAddresses { + /// Address Generation Parameters + type Parameters; - /// Returns the key pair for this account at the `spend` and `view` indices, if those indices - /// do not exceed the maximum indices. - #[inline] - pub fn keypair(&self, index: KeyIndex) -> Option> { - self.with_bounds_check(index, Self::derive_pair) - } + /// Address Type + type Address; - /// Returns an iterator over all the key pairs associated to `self`. - #[inline] - pub fn keypairs(&self) -> impl '_ + Iterator> { - let mut index = KeyIndex::default(); - iter::from_fn(move || { - let next = self.keypair(index); - index.increment(); - next - }) - } + /// Returns the [`Address`](Self::Address) corresponding to `index`. + fn address(&self, parameters: &Self::Parameters, index: AccountIndex) -> Self::Address; } -/// Account Keys with Mutable Access to the Account Table +/// Account +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound(deserialize = "H: Deserialize<'de>", serialize = "H: Serialize",), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] #[derive(derivative::Derivative)] #[derivative( + Clone(bound = "H: Clone"), + Copy(bound = "H: Copy"), Debug(bound = "H: Debug"), Eq(bound = "H: Eq"), Hash(bound = "H: Hash"), PartialEq(bound = "H: PartialEq") )] -pub struct AccountKeysMut<'h, H> +pub struct Account where - H: HierarchicalKeyDerivationScheme + ?Sized, + H: AccountCollection, { - /// Hierarchical Key Derivation Scheme - keys: &'h H, + /// Account Collection + key: H, - /// Account Index + /// Index index: AccountIndex, - - /// Account Information - account: &'h mut Account, -} - -impl<'h, H> AccountKeysMut<'h, H> -where - H: HierarchicalKeyDerivationScheme + ?Sized, -{ - /// Builds a new [`AccountKeysMut`] from `keys`, `index`, and `account`. - #[inline] - fn new(keys: &'h H, index: AccountIndex, account: &'h mut Account) -> Self { - Self { - keys, - index, - account, - } - } - - /// Performs the bounds check on `index` and then runs `f`. - #[inline] - fn with_bounds_check(&self, index: KeyIndex, f: F) -> Option - where - F: FnOnce(&Self, KeyIndex) -> T, - { - (index <= self.account.maximum_index).then(|| f(self, index)) - } - - /// Derives the spend key for this account at `index` without performing bounds checks. - #[inline] - fn derive_spend(&self, index: KeyIndex) -> H::SecretKey { - self.keys.derive_spend(self.index, index) - } - - /// Returns the default spend key for this account. - #[inline] - pub fn default_spend_key(&self) -> H::SecretKey { - self.derive_spend(Default::default()) - } - - /// Returns the spend key for this account at `index`, if it does not exceed the maximum index. - #[inline] - pub fn spend_key(&self, index: KeyIndex) -> Option { - self.with_bounds_check(index, Self::derive_spend) - } - - /// Derives the view key for this account at `index` without performing bounds checks. - #[inline] - fn derive_view(&self, index: KeyIndex) -> H::SecretKey { - self.keys.derive_view(self.index, index) - } - - /// Returns the default view key for this account. - #[inline] - pub fn default_view_key(&self) -> H::SecretKey { - self.derive_view(Default::default()) - } - - /// Returns the view key for this account at `index`, if it does not exceed the maximum index. - #[inline] - pub fn view_key(&self, index: KeyIndex) -> Option { - self.with_bounds_check(index, Self::derive_view) - } - - /// Derives the secret key pair for this account at `index` without performing bounds checks. - #[inline] - fn derive_pair(&self, index: KeyIndex) -> SecretKeyPair { - self.keys.derive_pair(self.index, index) - } - - /// Returns the default secret key pair for this account. - #[inline] - pub fn default_keypair(&self) -> SecretKeyPair { - self.derive_pair(Default::default()) - } - - /// Returns the key pair for this account at the `spend` and `view` indices, if those indices - /// do not exceed the maximum indices. - #[inline] - pub fn keypair(&self, index: KeyIndex) -> Option> { - self.with_bounds_check(index, Self::derive_pair) - } - - /// Returns an iterator over all the key pairs associated to `self`. - #[inline] - pub fn keypairs(&self) -> impl '_ + Iterator> { - let mut index = KeyIndex::default(); - iter::from_fn(move || { - let next = self.keypair(index); - index.increment(); - next - }) - } - - /// Returns a new [`ViewKeyTable`] for `self`. - #[inline] - pub fn view_key_table(self) -> ViewKeyTable<'h, H> { - ViewKeyTable::new(self) - } -} - -/// View Key Table -pub struct ViewKeyTable<'h, H> -where - H: HierarchicalKeyDerivationScheme + ?Sized, -{ - /// Account Keys - keys: AccountKeysMut<'h, H>, - - /// Pre-computed View Keys - view_keys: BTreeMap, } -impl<'h, H> ViewKeyTable<'h, H> +impl Account where - H: HierarchicalKeyDerivationScheme + ?Sized, + H: AccountCollection, { - /// View Key Buffer Maximum Size Limit - pub const VIEW_KEY_BUFFER_LIMIT: usize = 16 * (H::GAP_LIMIT as usize); - - /// Builds a new [`ViewKeyTable`] over the account `keys`. + /// Builds a new [`Account`] from `key` and `index`. #[inline] - pub fn new(keys: AccountKeysMut<'h, H>) -> Self { - Self { - keys, - view_keys: Default::default(), - } + pub fn new(key: H, index: AccountIndex) -> Self { + Self { key, index } } - /// Returns the account keys associated to `self`. + /// Returns the [`SpendingKey`] corresponding to `self`. #[inline] - pub fn into_keys(self) -> AccountKeysMut<'h, H> { - self.keys + pub fn spending_key(&self) -> SpendingKey { + self.key.spending_key(&self.index) } - /// Returns the view key for this account at `index`, if it does not exceed the maximum index. - /// - /// # Limits - /// - /// This function uses a view key buffer that stores the computed keys to reduce the number of - /// times a re-compute of the view keys is needed while searching. The buffer only grows past - /// the current key bounds with a call to [`find_index_with_gap`](Self::find_index_with_gap) - /// which extends the buffer by at most [`GAP_LIMIT`]-many keys per round. To prevent allocating - /// too much memory, the internal buffer is capped at [`VIEW_KEY_BUFFER_LIMIT`]-many elements. - /// - /// [`GAP_LIMIT`]: HierarchicalKeyDerivationScheme::GAP_LIMIT - /// [`VIEW_KEY_BUFFER_LIMIT`]: Self::VIEW_KEY_BUFFER_LIMIT + /// Returns the [`AccountCollection`] corresponding to `self`. #[inline] - pub fn view_key(&mut self, index: KeyIndex) -> Option<&H::SecretKey> { - btree_map::get_or_mutate(&mut self.view_keys, &index, |map| { - let next_key = self.keys.view_key(index)?; - if map.len() == Self::VIEW_KEY_BUFFER_LIMIT { - btree_map::pop_last(map); - } - Some(btree_map::insert_then_get(map, index, next_key)) - }) - } - - /// Applies `f` to the view keys generated by `self` returning the first non-`None` result with - /// it's key index and key attached, or returns `None` if every application of `f` returned - /// `None`. - #[inline] - pub fn find_index(&mut self, mut f: F) -> Option> - where - F: FnMut(&H::SecretKey) -> Option, - { - let mut index = KeyIndex::default(); - loop { - if let Some(item) = f(self.view_key(index)?) { - self.keys.account.last_used_index = - cmp::max(self.keys.account.last_used_index, index); - return Some(ViewKeySelection { - index, - keypair: self.keys.derive_pair(index), - item, - }); - } - index.increment(); - } - } - - /// Applies `f` to the view keys generated by `self` returning the first non-`None` result with - /// it's key index and key attached, or returns `None` if every application pf `f` returned - /// `None`. - /// - /// # Gap Limit - /// - /// This method extends the current maximum index by [`GAP_LIMIT`]-many indices while searching - /// and then sets the new maximum to the previous maximum or the located index, whichever is - /// larger. - /// - /// [`GAP_LIMIT`]: HierarchicalKeyDerivationScheme::GAP_LIMIT - #[inline] - pub fn find_index_with_gap(&mut self, f: F) -> Option> - where - F: FnMut(&H::SecretKey) -> Option, - { - let previous_maximum = self.keys.account.maximum_index; - self.keys.account.maximum_index.index += H::GAP_LIMIT; - match self.find_index(f) { - Some(result) => { - self.keys.account.maximum_index = cmp::max(previous_maximum, result.index); - Some(result) - } - _ => { - self.keys.account.maximum_index = previous_maximum; - None - } - } - } - - /// Runs one of the index search algorithms depending on the value of `use_gap_limit`, where - /// [`find_index_with_gap`](Self::find_index_with_gap) is used in the case that `use_gap_limit` - /// is `true`, and [`find_index`](Self::find_index) is used otherwise. - #[inline] - pub fn find_index_with_maybe_gap( - &mut self, - use_gap_limit: bool, - f: F, - ) -> Option> - where - F: FnMut(&H::SecretKey) -> Option, - { - if use_gap_limit { - self.find_index_with_gap(f) - } else { - self.find_index(f) - } + pub fn keys(&self) -> &H { + &self.key } } -/// View Key Selection -pub struct ViewKeySelection +impl DeriveAddress for Account where - H: HierarchicalKeyDerivationScheme + ?Sized, + H: AccountCollection + DeriveAddresses, { - /// Selection Index - pub index: KeyIndex, + type Address = H::Address; + type Parameters = H::Parameters; - /// Selection Key Pair - pub keypair: SecretKeyPair, - - /// Selection Item - pub item: T, -} - -impl ViewKeySelection -where - H: HierarchicalKeyDerivationScheme + ?Sized, -{ - /// Computes `f` on `self.item` returning a new [`ViewKeySelection`] with the same `index` and - /// `keypair`. #[inline] - pub fn map(self, f: F) -> ViewKeySelection - where - F: FnOnce(T) -> U, - { - ViewKeySelection { - index: self.index, - keypair: self.keypair, - item: f(self.item), - } - } -} - -/// Account -#[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 Account { - /// Last Used Index - /// - /// This index is used to enforce limits when generating new keys beyond the `maximum_index`. - pub last_used_index: KeyIndex, - - /// Maximum Index - pub maximum_index: KeyIndex, -} - -/// Account Map Trait -pub trait AccountMap { - /// Builds a new [`AccountMap`] with a starting account with the default maximum index. - fn new() -> Self; - - /// Returns the last account index stored in the map. - fn last_account(&self) -> AccountIndex; - - /// Adds a new account to the map, returning the new account index. - fn create_account(&mut self) -> AccountIndex; - - /// Returns the [`Account`] associated to `account`. - fn get(&self, account: AccountIndex) -> Option; - - /// Returns the [`Account`] associated to `account`. - fn get_mut(&mut self, account: AccountIndex) -> Option<&mut Account>; -} - -/// [`Vec`] Account Map Type -pub type VecAccountMap = Vec; - -impl AccountMap for VecAccountMap { - #[inline] - fn new() -> Self { - let mut this = Self::new(); - this.create_account(); - this - } - - #[inline] - fn last_account(&self) -> AccountIndex { - AccountIndex::new( - (self.len() - 1) - .try_into() - .expect("AccountIndex is not allowed to exceed IndexType::MAX."), - ) - } - - #[inline] - fn create_account(&mut self) -> AccountIndex { - let index = AccountIndex::new( - self.len() - .try_into() - .expect("AccountIndex is not allowed to exceed IndexType::MAX."), - ); - self.push(Default::default()); - index - } - - #[inline] - fn get(&self, account: AccountIndex) -> Option { - self.as_slice().get(account.index() as usize).copied() - } - - #[inline] - fn get_mut(&mut self, account: AccountIndex) -> Option<&mut Account> { - self.as_mut_slice().get_mut(account.index() as usize) + fn address(&self, parameters: &Self::Parameters) -> Self::Address { + self.key.address(parameters, self.index) } } /// Account Table /// -/// The account table stores an underlying [`HierarchicalKeyDerivationScheme`] for key generation +/// The account table stores an underlying [`AccountCollection`] for key generation /// and a set of accounts defined by an [`AccountMap`] which can be queried to get the set of /// existing keys and for generating new keys. #[cfg_attr( @@ -798,10 +323,10 @@ impl AccountMap for VecAccountMap { )] pub struct AccountTable where - H: HierarchicalKeyDerivationScheme, - M: AccountMap, + H: AccountCollection, + M: AccountMap, { - /// Hierarchical Key Derivation Scheme + /// Account Collection keys: H, /// Account Map @@ -810,8 +335,8 @@ where impl AccountTable where - H: HierarchicalKeyDerivationScheme, - M: AccountMap, + H: AccountCollection, + M: AccountMap, { /// Builds a new [`AccountTable`] using `keys` and the default account map. #[inline] @@ -825,115 +350,42 @@ where Self { keys, accounts } } - /// Returns the underlying [`HierarchicalKeyDerivationScheme`] of `self`. + /// Returns the [`AccountCollection`] corresponding to `self`. #[inline] pub fn keys(&self) -> &H { &self.keys } - /// Returns the secret key pair associated to `account` if it exists, using `index` if it does - /// not exceed the maximum key index. - #[inline] - pub fn keypair( - &self, - account: AccountIndex, - index: KeyIndex, - ) -> Option>> { - self.get(account).map(move |k| k.keypair(index)) - } - - /// Returns the account keys for `account` if it exists. - #[inline] - pub fn get(&self, account: AccountIndex) -> Option> { - Some(AccountKeys::new( - &self.keys, - account, - self.accounts.get(account)?, - )) - } - /// Returns the account keys for `account` if it exists. #[inline] - pub fn get_mut(&mut self, account: AccountIndex) -> Option> { - Some(AccountKeysMut::new( - &self.keys, - account, - self.accounts.get_mut(account)?, - )) + pub fn get(&self, account: AccountIndex) -> Option> + where + H: Clone, + { + let index = self.accounts.get(account).copied()?; + Some(Account::new(self.keys.clone(), index)) } /// Returns the account keys for the default account. #[inline] - pub fn get_default(&self) -> AccountKeys { + pub fn get_default(&self) -> Account + where + H: Clone, + { self.get(Default::default()).unwrap() } - /// Returns the account keys for the default account. - #[inline] - pub fn get_mut_default(&mut self) -> AccountKeysMut { - self.get_mut(Default::default()).unwrap() - } - - /// Returns the maximum key index for `account`, if it exists. - #[inline] - pub fn maximum_index(&self, account: AccountIndex) -> Option { - self.accounts - .get(account) - .map(|account| account.maximum_index) - } - /// Adds a new account to the map, returning the new account parameter. #[inline] pub fn create_account(&mut self) -> AccountIndex { self.accounts.create_account() } - - /// Increments the maximum key index for `account`, if it exists, returning the current - /// maximum index. This method also returns `None` in the case that the - /// [`GAP_LIMIT`](HierarchicalKeyDerivationScheme::GAP_LIMIT) would be exceeded. - #[inline] - pub fn increment_maximum_index(&mut self, account: AccountIndex) -> Option { - self.accounts.get_mut(account).and_then(|account| { - match H::GAP_LIMIT - .checked_sub(account.maximum_index.index - account.last_used_index.index) - { - Some(diff) if diff > 0 => { - account.maximum_index.increment(); - Some(account.maximum_index) - } - _ => None, - } - }) - } - - /// Increments the spend index and returns the [`KeyIndex`] for the new index. - #[inline] - pub fn next_index(&mut self, account: AccountIndex) -> Option { - let max_index = self.increment_maximum_index(account)?; - Some(max_index) - } - - /// Increments the spend index and returns the [`SecretKeyPair`] for the new index. - #[inline] - pub fn next(&mut self, account: AccountIndex) -> Option> { - let index = self.next_index(account)?; - Some(self.keys.derive_pair(account, index)) - } - - /// Returns an iterator over keys, generated by calling [`next`](Self::next) repeatedly. - #[inline] - pub fn generate_keys( - &mut self, - account: AccountIndex, - ) -> impl '_ + Iterator> { - iter::from_fn(move || self.next(account)) - } } impl Default for AccountTable where - H: Default + HierarchicalKeyDerivationScheme, - M: AccountMap, + H: Default + AccountCollection, + M: AccountMap, { #[inline] fn default() -> Self { diff --git a/manta-accounting/src/wallet/mod.rs b/manta-accounting/src/wallet/mod.rs index 8bc7cba8e..8aeef190d 100644 --- a/manta-accounting/src/wallet/mod.rs +++ b/manta-accounting/src/wallet/mod.rs @@ -31,14 +31,14 @@ use crate::{ asset::{Asset, AssetId, AssetList, AssetMetadata, AssetValue}, transfer::{ canonical::{Transaction, TransactionKind}, - Configuration, ReceivingKey, TransferPost, + Configuration, PublicKey, TransferPost, }, wallet::{ balance::{BTreeMapBalanceState, BalanceState}, ledger::ReadResponse, signer::{ - BalanceUpdate, ReceivingKeyRequest, SignError, SignRequest, SignResponse, SyncData, - SyncError, SyncRequest, SyncResponse, + BalanceUpdate, SignError, SignRequest, SignResponse, SyncData, SyncError, SyncRequest, + SyncResponse, }, }, }; @@ -379,13 +379,10 @@ where .map_err(Error::LedgerConnectionError) } - /// Returns public receiving keys according to the `request`. + /// Returns public receiving keys. #[inline] - pub async fn receiving_keys( - &mut self, - request: ReceivingKeyRequest, - ) -> Result>, S::Error> { - self.signer.receiving_keys(request).await + pub async fn receiving_keys(&mut self) -> Result, S::Error> { + self.signer.receiving_keys().await } } diff --git a/manta-accounting/src/wallet/signer.rs b/manta-accounting/src/wallet/signer.rs index c96446aa3..7d46cb633 100644 --- a/manta-accounting/src/wallet/signer.rs +++ b/manta-accounting/src/wallet/signer.rs @@ -28,10 +28,7 @@ use crate::{ asset::{Asset, AssetId, AssetMap, AssetMetadata, AssetValue}, - key::{ - self, HierarchicalKeyDerivationScheme, KeyIndex, SecretKeyPair, ViewKeySelection, - ViewKeyTable, - }, + key::{self, AccountCollection, DeriveAddress, DeriveAddresses}, transfer::{ self, batch::Join, @@ -40,8 +37,8 @@ use crate::{ Shape, Transaction, }, EncryptedNote, FullParameters, Note, Parameters, PreSender, ProofSystemError, - ProvingContext, Receiver, ReceivingKey, SecretKey, Sender, SpendingKey, Transfer, - TransferPost, Utxo, VoidNumber, + ProvingContext, PublicKey, Receiver, ReceivingKey, SecretKey, Sender, SpendingKey, + Transfer, TransferPost, Utxo, VoidNumber, }, wallet::ledger::{self, Data}, }; @@ -52,10 +49,7 @@ use manta_crypto::{ rand::{CryptoRng, FromEntropy, Rand, RngCore}, }; use manta_util::{ - array_map, - future::LocalBoxFutureResult, - into_array_unchecked, - iter::{Finder, IteratorExt}, + array_map, future::LocalBoxFutureResult, into_array_unchecked, iter::IteratorExt, persistence::Rollback, }; @@ -91,11 +85,8 @@ where request: SignRequest, ) -> LocalBoxFutureResult, SignError>, Self::Error>; - /// Returns public receiving keys according to the `request`. - fn receiving_keys( - &mut self, - request: ReceivingKeyRequest, - ) -> LocalBoxFutureResult>, Self::Error>; + /// Returns public receiving keys. + fn receiving_keys(&mut self) -> LocalBoxFutureResult, Self::Error>; } /// Signer Synchronization Data @@ -177,12 +168,6 @@ where T: ledger::Checkpoint, { /// Recovery Flag - /// - /// If `with_recovery` is set to `true`, the [`GAP_LIMIT`] is used during sync to perform a full - /// recovery. See [`Configuration::HierarchicalKeyDerivationScheme`] for the scheme where the - /// [`GAP_LIMIT`] is configured. - /// - /// [`GAP_LIMIT`]: HierarchicalKeyDerivationScheme::GAP_LIMIT pub with_recovery: bool, /// Origin Checkpoint @@ -412,40 +397,6 @@ where /// Signing Result pub type SignResult = Result, SignError>; -/// Receiving Key Request -#[cfg_attr( - feature = "serde", - derive(Deserialize, Serialize), - serde(crate = "manta_util::serde", deny_unknown_fields) -)] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum ReceivingKeyRequest { - /// Get Specific Key - /// - /// Requests the key at the specific `index`. If the signer's response is an empty key vector, - /// then the index was out of bounds. - Get { - /// Target Key Index - index: KeyIndex, - }, - - /// Get All Keys - /// - /// Requests all the public keys associated to the signer. The signer should always respond to - /// this request with at least one key, the default public key. - GetAll, - - /// New Keys - /// - /// Requests `count`-many new keys from the hierarchical key derivation scheme. The signer - /// should always respond with at most `count`-many keys. If there are fewer, this is because, - /// adding such keys would exceed the [`GAP_LIMIT`](HierarchicalKeyDerivationScheme::GAP_LIMIT). - New { - /// Number of New Keys to Generate - count: usize, - }, -} - /// Signer Checkpoint pub trait Checkpoint: ledger::Checkpoint where @@ -478,10 +429,10 @@ pub trait Configuration: transfer::Configuration { /// Checkpoint Type type Checkpoint: Checkpoint; - /// Hierarchical Key Derivation Scheme - type HierarchicalKeyDerivationScheme: HierarchicalKeyDerivationScheme< - SecretKey = SecretKey, - >; + /// Account Type + type Account: AccountCollection> + + Clone + + DeriveAddresses, Address = PublicKey>; /// [`Utxo`] Accumulator Type type UtxoAccumulator: Accumulator @@ -490,17 +441,14 @@ pub trait Configuration: transfer::Configuration { + Rollback; /// Asset Map Type - type AssetMap: AssetMap>; + type AssetMap: AssetMap>; /// Random Number Generator Type type Rng: CryptoRng + FromEntropy + RngCore; } /// Account Table Type -pub type AccountTable = key::AccountTable<::HierarchicalKeyDerivationScheme>; - -/// Asset Map Key Type -pub type AssetMapKey = (KeyIndex, SecretKey); +pub type AccountTable = key::AccountTable<::Account>; /// Signer Parameters #[derive(derivative::Derivative)] @@ -534,16 +482,6 @@ where proving_context, } } - - /// Converts `keypair` into a [`ReceivingKey`] by using the key-agreement scheme to derive the - /// public keys associated to `keypair`. - #[inline] - fn receiving_key( - &self, - keypair: SecretKeyPair, - ) -> ReceivingKey { - SpendingKey::new(keypair.spend, keypair.view).derive(self.parameters.key_agreement_scheme()) - } } /// Signer State @@ -623,10 +561,7 @@ where /// Builds a new [`SignerState`] from `keys` and `utxo_accumulator`. #[inline] - pub fn new( - keys: C::HierarchicalKeyDerivationScheme, - utxo_accumulator: C::UtxoAccumulator, - ) -> Self { + pub fn new(keys: C::Account, utxo_accumulator: C::UtxoAccumulator) -> Self { Self::build( AccountTable::::new(keys), utxo_accumulator, @@ -641,50 +576,30 @@ where &self.accounts } - /// Finds the next viewing key that can decrypt the `encrypted_note` from the `view_key_table`. - #[inline] - fn find_next_key<'h>( - view_key_table: &mut ViewKeyTable<'h, C::HierarchicalKeyDerivationScheme>, - parameters: &Parameters, - with_recovery: bool, - encrypted_note: EncryptedNote, - ) -> Option>> { - let mut finder = Finder::new(encrypted_note); - view_key_table.find_index_with_maybe_gap(with_recovery, move |k| { - finder.next(|note| note.decrypt(¶meters.note_encryption_scheme, k, &mut ())) - }) - } - /// 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)] #[inline] fn insert_next_item( utxo_accumulator: &mut C::UtxoAccumulator, assets: &mut C::AssetMap, parameters: &Parameters, utxo: Utxo, - selection: ViewKeySelection>, + note: Note, + key: &SecretKey, void_numbers: &mut Vec>, deposit: &mut Vec, ) { - let ViewKeySelection { - index, - keypair, - item: Note { - ephemeral_secret_key, - asset, - }, - } = selection; if let Some(void_number) = - parameters.check_full_asset(&keypair.spend, &ephemeral_secret_key, &asset, &utxo) + 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); } else { utxo_accumulator.insert(&utxo); - assets.insert((index, ephemeral_secret_key), asset); - if !asset.is_zero() { - deposit.push(asset); + assets.insert(note.ephemeral_secret_key, note.asset); + if !note.asset.is_zero() { + deposit.push(note.asset); } return; } @@ -735,23 +650,22 @@ where where I: Iterator, EncryptedNote)>, { + let _ = with_recovery; let void_number_count = void_numbers.len(); let mut deposit = Vec::new(); let mut withdraw = Vec::new(); - let mut view_key_table = self.accounts.get_mut_default().view_key_table(); + let default_key = self.accounts.get_default().spending_key(); for (utxo, encrypted_note) in inserts { - if let Some(selection) = Self::find_next_key( - &mut view_key_table, - parameters, - with_recovery, - encrypted_note, - ) { + if let Some(note) = + encrypted_note.decrypt(¶meters.note_encryption_scheme, &default_key, &mut ()) + { Self::insert_next_item( &mut self.utxo_accumulator, &mut self.assets, parameters, utxo, - selection, + note, + &default_key, &mut void_numbers, &mut deposit, ); @@ -759,21 +673,18 @@ where self.utxo_accumulator.insert_nonprovable(&utxo); } } - self.assets.retain(|(index, ephemeral_secret_key), assets| { - assets.retain( - |asset| match self.accounts.get_default().spend_key(*index) { - Some(secret_spend_key) => Self::is_asset_unspent( - &mut self.utxo_accumulator, - parameters, - &secret_spend_key, - ephemeral_secret_key, - *asset, - &mut void_numbers, - &mut withdraw, - ), - _ => true, - }, - ); + self.assets.retain(|ephemeral_secret_key, assets| { + assets.retain(|asset| { + Self::is_asset_unspent( + &mut self.utxo_accumulator, + parameters, + &self.accounts.get_default().spending_key(), + ephemeral_secret_key, + *asset, + &mut void_numbers, + &mut withdraw, + ) + }); !assets.is_empty() }); self.checkpoint.update_from_void_numbers(void_number_count); @@ -798,17 +709,13 @@ where fn build_pre_sender( &self, parameters: &Parameters, - key: AssetMapKey, + key: SecretKey, asset: Asset, ) -> Result, SignError> { - let (spend_index, ephemeral_secret_key) = key; Ok(PreSender::new( parameters, - self.accounts - .get_default() - .spend_key(spend_index) - .expect("Index is guaranteed to be within bounds."), - ephemeral_secret_key, + self.accounts.get_default().spending_key(), + key, asset, )) } @@ -820,12 +727,11 @@ where parameters: &Parameters, asset: Asset, ) -> Result, SignError> { - let keypair = self.accounts.get_default().default_keypair(); - Ok(SpendingKey::new(keypair.spend, keypair.view).receiver( - parameters, - self.rng.gen(), - asset, - )) + 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)) } /// Builds a new internal [`Mint`] for zero assets. @@ -836,10 +742,10 @@ where asset_id: AssetId, ) -> Result<(Mint, PreSender), SignError> { let asset = Asset::zero(asset_id); - let keypair = self.accounts.get_default().default_keypair(); + let key = self.accounts.get_default().spending_key(); Ok(Mint::internal_pair( parameters, - &SpendingKey::new(keypair.spend, keypair.view), + &SpendingKey::new(key.clone(), key), asset, &mut self.rng, )) @@ -937,11 +843,11 @@ where asset_id: AssetId, total: AssetValue, ) -> Result<([Receiver; PrivateTransferShape::RECEIVERS], Join), SignError> { - let keypair = self.accounts.get_default().default_keypair(); + let secret_key = self.accounts.get_default().spending_key(); Ok(Join::new( parameters, asset_id.with(total), - &SpendingKey::new(keypair.spend, keypair.view), + &SpendingKey::new(secret_key.clone(), secret_key), &mut self.rng, )) } @@ -1059,7 +965,7 @@ where impl Clone for SignerState where C: Configuration, - C::HierarchicalKeyDerivationScheme: Clone, + C::Account: Clone, C::UtxoAccumulator: Clone, C::AssetMap: Clone, { @@ -1254,33 +1160,13 @@ where result } - /// Returns public receiving keys according to the `request`. + /// Returns public receiving keys. #[inline] - pub fn receiving_keys(&mut self, request: ReceivingKeyRequest) -> Vec> { - match request { - ReceivingKeyRequest::Get { index } => self - .state - .accounts - .get_default() - .keypair(index) - .into_iter() - .map(|k| self.parameters.receiving_key(k)) - .collect(), - ReceivingKeyRequest::GetAll => self - .state - .accounts - .get_default() - .keypairs() - .map(|k| self.parameters.receiving_key(k)) - .collect(), - ReceivingKeyRequest::New { count } => self - .state - .accounts - .generate_keys(Default::default()) - .take(count) - .map(|k| self.parameters.receiving_key(k)) - .collect(), - } + pub fn receiving_keys(&mut self) -> PublicKey { + self.state + .accounts + .get_default() + .address(&self.parameters.parameters) } } @@ -1311,10 +1197,7 @@ where } #[inline] - fn receiving_keys( - &mut self, - request: ReceivingKeyRequest, - ) -> LocalBoxFutureResult>, Self::Error> { - Box::pin(async move { Ok(self.receiving_keys(request)) }) + fn receiving_keys(&mut self) -> LocalBoxFutureResult, Self::Error> { + Box::pin(async move { Ok(self.receiving_keys()) }) } } diff --git a/manta-accounting/src/wallet/test/mod.rs b/manta-accounting/src/wallet/test/mod.rs index 9a0091459..e4ce2c217 100644 --- a/manta-accounting/src/wallet/test/mod.rs +++ b/manta-accounting/src/wallet/test/mod.rs @@ -24,7 +24,7 @@ use crate::{ transfer::{self, canonical::Transaction, PublicKey, ReceivingKey, TransferPost}, wallet::{ ledger, - signer::{self, ReceivingKeyRequest, SyncData}, + signer::{self, SyncData}, BalanceState, Error, Wallet, }, }; @@ -33,7 +33,7 @@ use core::{fmt::Debug, future::Future, hash::Hash, marker::PhantomData}; use futures::StreamExt; use indexmap::IndexSet; use manta_crypto::rand::{CryptoRng, Distribution, Rand, RngCore, Sample}; -use manta_util::{future::LocalBoxFuture, vec::VecExt}; +use manta_util::future::LocalBoxFuture; use parking_lot::Mutex; use statrs::{ distribution::{Categorical, Poisson}, @@ -108,8 +108,16 @@ where /// Generates a [`Transaction::PrivateTransfer`] for `asset` to `key` self-pointed if `is_self` /// is `true`. #[inline] - pub fn private_transfer(is_self: bool, asset: Asset, key: ReceivingKey) -> Self { - Self::post(is_self, false, Transaction::PrivateTransfer(asset, key)) + 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), + ) } /// Generates a [`Transaction::Reclaim`] for `asset` which is maximal if `is_maximal` is `true`. @@ -427,14 +435,11 @@ where /// Returns the default receiving key for `self`. #[inline] - async fn default_receiving_key(&mut self) -> Result, Error> { + async fn default_receiving_key(&mut self) -> Result, Error> { self.wallet - .receiving_keys(ReceivingKeyRequest::Get { - index: Default::default(), - }) + .receiving_keys() .await .map_err(Error::SignerConnectionError) - .map(Vec::take_first) } /// Returns the latest public balances from the ledger. @@ -561,7 +566,7 @@ where where L: PublicBalanceOracle, R: RngCore + ?Sized, - K: FnOnce(&mut R) -> Result>, Error>, + K: FnOnce(&mut R) -> Result>, Error>, { let action = if is_self { ActionType::SelfTransfer @@ -596,7 +601,7 @@ where where L: PublicBalanceOracle, R: RngCore + ?Sized, - K: FnOnce(&mut R) -> Result>, Error>, + K: FnOnce(&mut R) -> Result>, Error>, { let action = if is_self { ActionType::SelfTransfer @@ -684,7 +689,7 @@ pub type Event = ActionLabelled>>>::Response, Error>>; /// Receiving Key Database -pub type ReceivingKeyDatabase = IndexSet>; +pub type ReceivingKeyDatabase = IndexSet>; /// Shared Receiving Key Database pub type SharedReceivingKeyDatabase = Arc>>; @@ -715,7 +720,7 @@ where { /// Builds a new [`Simulation`] with a starting set of public `keys`. #[inline] - pub fn new(keys: [ReceivingKey; N]) -> Self { + pub fn new(keys: [PublicKey; N]) -> Self { Self { receiving_keys: Arc::new(Mutex::new(keys.into_iter().collect())), __: PhantomData, @@ -724,7 +729,7 @@ where /// Samples a random receiving key from #[inline] - pub fn sample_receiving_key(&self, rng: &mut R) -> Option> + pub fn sample_receiving_key(&self, rng: &mut R) -> Option> where R: RngCore + ?Sized, { @@ -837,17 +842,11 @@ where } } } - Action::GenerateReceivingKeys { count } => Event { + Action::GenerateReceivingKeys { count: _ } => Event { action: ActionType::GenerateReceivingKeys, - value: match actor - .wallet - .receiving_keys(ReceivingKeyRequest::New { count }) - .await - { - Ok(keys) => { - for key in keys { - self.receiving_keys.lock().insert(key); - } + value: match actor.wallet.receiving_keys().await { + Ok(key) => { + self.receiving_keys.lock().insert(key); Ok(true) } Err(err) => Err(Error::SignerConnectionError(err)), diff --git a/manta-pay/Cargo.toml b/manta-pay/Cargo.toml index b7774bf05..5409c7fe7 100644 --- a/manta-pay/Cargo.toml +++ b/manta-pay/Cargo.toml @@ -48,6 +48,9 @@ arkworks = [ # Enable Download Parameters download = ["manta-parameters/download", "std"] +# Key Features +key = ["bip32", "bip0039"] + # Enable Groth16 ZKP System groth16 = ["ark-groth16", "ark-snark", "arkworks"] @@ -82,7 +85,7 @@ std = ["manta-accounting/std", "manta-util/std"] test = ["manta-accounting/test", "manta-crypto/test", "manta-parameters", "tempfile"] # Wallet -wallet = ["bip32", "bip0039", "manta-crypto/getrandom", "std"] +wallet = ["key", "manta-crypto/getrandom", "std"] # Enable WebSocket Signer Client websocket = [ diff --git a/manta-pay/src/key.rs b/manta-pay/src/key.rs index 2f587e7ec..90bf993d2 100644 --- a/manta-pay/src/key.rs +++ b/manta-pay/src/key.rs @@ -24,19 +24,17 @@ //! //! [`BIP-0044`]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki -use alloc::{format, string::String}; +use alloc::{format, string::String, vec::Vec}; use core::marker::PhantomData; -use manta_accounting::key::{ - self, AccountIndex, HierarchicalKeyDerivationScheme, IndexType, KeyIndex, Kind, -}; +use manta_accounting::key::{self, AccountIndex}; use manta_crypto::rand::{CryptoRng, RngCore}; use manta_util::{create_seal, seal, Array}; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize, Serializer}; -pub use bip0039; -pub use bip32::{self, Error, XPrv as SecretKey}; +pub use bip0039::{self, Error}; +pub use bip32::{self, XPrv as SecretKey}; create_seal! {} @@ -67,7 +65,7 @@ macro_rules! impl_coin_type { $id:expr, $coin_type_id:ident, $key_secret:ident, - $account_table:ident + $account_map:ident ) => { #[doc = $doc] #[doc = "Network"] @@ -84,8 +82,8 @@ macro_rules! impl_coin_type { pub type $key_secret = KeySecret<$coin>; #[doc = stringify!($coin)] - #[doc = "[`AccountTable`] Type"] - pub type $account_table = AccountTable<$coin>; + #[doc = "[`VecAccountMap`] Type"] + pub type $account_map = VecAccountMap<$coin>; seal!($coin); @@ -102,7 +100,7 @@ impl_coin_type!( 1, TESTNET_COIN_TYPE_ID, TestnetKeySecret, - TestnetAccountTable + TestnetAccountMap ); impl_coin_type!( @@ -112,7 +110,7 @@ impl_coin_type!( 611, MANTA_COIN_TYPE_ID, MantaKeySecret, - MantaAccountTable + MantaAccountMap ); impl_coin_type!( @@ -122,14 +120,11 @@ impl_coin_type!( 612, CALAMARI_COIN_TYPE_ID, CalamariKeySecret, - CalamariAccountTable + CalamariAccountMap ); -/// Account Table Type -pub type AccountTable = key::AccountTable>; - -/// Seed Bytes -pub type SeedBytes = Array; +/// Seed Byte Array Type +type SeedBytes = Array; /// Key Secret #[cfg_attr( @@ -188,48 +183,45 @@ where { Self::new(Mnemonic::sample(rng), "") } + + /// 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) + .parse() + .expect("Path string is valid by construction."), + ) + .expect("Unable to generate secret key for valid seed and path string.") + } } +/// Account type +pub type Account = key::Account>; + +/// Vec Account type +pub type VecAccountMap = Vec>; + /// Computes the [`BIP-0044`] path string for the given coin settings. /// /// [`BIP-0044`]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki #[inline] #[must_use] -pub fn path_string(account: AccountIndex, kind: Kind, index: KeyIndex) -> String +pub fn path_string(account: AccountIndex) -> String where C: CoinType, { const BIP_44_PURPOSE_ID: u8 = 44; format!( - "m/{}'/{}'/{}'/{}'/{}'", + "m/{}'/{}'/{}'", BIP_44_PURPOSE_ID, C::COIN_TYPE_ID, - account.index(), - kind as u8, - index.index(), + account.index() ) } -impl HierarchicalKeyDerivationScheme for KeySecret -where - C: CoinType, -{ - const GAP_LIMIT: IndexType = 20; - - type SecretKey = SecretKey; - - #[inline] - fn derive(&self, account: AccountIndex, kind: Kind, index: KeyIndex) -> Self::SecretKey { - SecretKey::derive_from_path( - self.seed, - &path_string::(account, kind, index) - .parse() - .expect("Path string is valid by construction."), - ) - .expect("Unable to generate secret key for valid seed and path string.") - } -} - /// Mnemonic #[cfg_attr( feature = "serde", @@ -243,14 +235,11 @@ pub struct Mnemonic( bip0039::Mnemonic, ); -/// Seed Type -pub type Seed = [u8; 64]; - impl Mnemonic { - /// Create a new BIP39 mnemonic phrase from the given phrase. + /// Create a new BIP0039 mnemonic phrase from the given string. #[inline] pub fn new(phrase: &str) -> Result { - Ok(Self(bip0039::Mnemonic::from_phrase(phrase).unwrap())) + bip0039::Mnemonic::from_phrase(phrase).map(Self) } /// Samples a random 12 word [`Mnemonic`] using the entropy returned from `rng`. @@ -261,12 +250,15 @@ impl Mnemonic { { let mut entropy: [u8; 16] = [0; 16]; rng.fill_bytes(&mut entropy); - Self(bip0039::Mnemonic::from_entropy(entropy.to_vec()).unwrap()) + Self( + bip0039::Mnemonic::from_entropy(entropy.to_vec()) + .expect("Creating a Mnemonic from 16 bytes of entropy is not allowed to fail."), + ) } - /// Convert this mnemonic phrase into the BIP39 seed value. + /// Convert this mnemonic phrase into the BIP32 seed value. #[inline] - pub fn to_seed(&self, password: &str) -> Seed { + pub fn to_seed(&self, password: &str) -> [u8; bip32::Seed::SIZE] { self.0.to_seed(password) } diff --git a/manta-pay/src/lib.rs b/manta-pay/src/lib.rs index 273691e3e..41776d4e5 100644 --- a/manta-pay/src/lib.rs +++ b/manta-pay/src/lib.rs @@ -30,8 +30,8 @@ pub mod util; #[cfg_attr(doc_cfg, doc(cfg(feature = "groth16")))] pub mod config; -#[cfg(feature = "bip32")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "bip32")))] +#[cfg(feature = "key")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "key")))] pub mod key; #[cfg(all(feature = "groth16", feature = "test"))] diff --git a/manta-pay/src/signer/base.rs b/manta-pay/src/signer/base.rs index 520b3d7b2..99985d5bc 100644 --- a/manta-pay/src/signer/base.rs +++ b/manta-pay/src/signer/base.rs @@ -17,74 +17,68 @@ //! Manta Pay Signer Configuration use crate::{ - config::{Bls12_381_Edwards, Config, MerkleTreeConfiguration, SecretKey}, + config::{Config, MerkleTreeConfiguration, Parameters, PublicKey, SecretKey}, crypto::constraint::arkworks::Fp, key::{CoinType, KeySecret, Testnet}, signer::Checkpoint, }; use alloc::collections::BTreeMap; -use core::{cmp, marker::PhantomData, mem}; +use core::{cmp, mem}; use manta_accounting::{ asset::HashAssetMap, - key, + key::{self, AccountCollection, AccountIndex, DeriveAddresses}, wallet::{ self, - signer::{self, AssetMapKey, SyncData}, + signer::{self, SyncData}, }, }; use manta_crypto::{ - arkworks::{ec::ProjectiveCurve, ff::PrimeField}, - key::kdf::KeyDerivationFunction, + arkworks::{ + ed_on_bls12_381::FrParameters, + ff::{Fp256, PrimeField}, + }, + key::agreement::Derive, merkle_tree::{self, forest::Configuration}, rand::{ChaCha20Rng, CryptoRng, RngCore}, }; -#[cfg(feature = "serde")] -use manta_util::serde::{Deserialize, Serialize}; - -/// Hierarchical Key Derivation Function -#[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, Ord, PartialEq, PartialOrd)] -pub struct HierarchicalKeyDerivationFunction(PhantomData) +impl DeriveAddresses for KeySecret where - C: CoinType; + 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 ()) + } +} -impl KeyDerivationFunction for HierarchicalKeyDerivationFunction +impl key::AccountCollection for KeySecret where C: CoinType, { - type Key = as key::HierarchicalKeyDerivationScheme>::SecretKey; - type Output = SecretKey; - + type SpendingKey = SecretKey; #[inline] - fn derive(&self, key: &Self::Key, _: &mut ()) -> Self::Output { - // FIXME: Check that this conversion is logical/safe. - let bytes: [u8; 32] = key - .private_key() - .to_bytes() - .try_into() - .expect("The secret key has 32 bytes."); - Fp(::ScalarField::from_le_bytes_mod_order(&bytes)) + 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(), + )) } } -/// Hierarchical Key Derivation Scheme -pub type HierarchicalKeyDerivationScheme = - key::Map, HierarchicalKeyDerivationFunction>; - -/// Samples a [`HierarchicalKeyDerivationFunction`] from `rng`. +/// Samples a [`KeySecret`] from `rng`. #[inline] -pub fn sample_key_secret(rng: &mut R) -> HierarchicalKeyDerivationScheme +pub fn sample_key_secret(rng: &mut R) -> KeySecret where C: CoinType, R: CryptoRng + RngCore + ?Sized, { - HierarchicalKeyDerivationScheme::new(KeySecret::sample(rng), Default::default()) + KeySecret::sample(rng) } /// Signer UTXO Accumulator @@ -99,9 +93,9 @@ pub type UtxoAccumulator = merkle_tree::forest::TreeArrayMerkleForest< impl wallet::signer::Configuration for Config { type Checkpoint = Checkpoint; - type HierarchicalKeyDerivationScheme = HierarchicalKeyDerivationScheme; + type Account = KeySecret; type UtxoAccumulator = UtxoAccumulator; - type AssetMap = HashAssetMap>; + type AssetMap = HashAssetMap; type Rng = ChaCha20Rng; } diff --git a/manta-pay/src/signer/client/websocket.rs b/manta-pay/src/signer/client/websocket.rs index 0f2b21cc5..49f94434f 100644 --- a/manta-pay/src/signer/client/websocket.rs +++ b/manta-pay/src/signer/client/websocket.rs @@ -19,13 +19,13 @@ // TODO: Make this code work on WASM and non-WASM by choosing the correct dependency library. use crate::{ - config::{Config, ReceivingKey}, + config::{Config, PublicKey}, signer::{ - Checkpoint, ReceivingKeyRequest, SignError, SignRequest, SignResponse, SyncError, - SyncRequest, SyncResponse, + Checkpoint, GetRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, + SyncResponse, }, }; -use alloc::{boxed::Box, vec::Vec}; +use alloc::boxed::Box; use core::marker::Unpin; use futures::{SinkExt, StreamExt}; use manta_accounting::wallet::{self, signer}; @@ -141,10 +141,7 @@ impl signer::Connection for Client { } #[inline] - fn receiving_keys( - &mut self, - request: ReceivingKeyRequest, - ) -> LocalBoxFutureResult, Self::Error> { - Box::pin(async move { self.send("receivingKeys", request).await }) + fn receiving_keys(&mut self) -> LocalBoxFutureResult { + Box::pin(async move { self.send("receivingKeys", GetRequest::Get).await }) } } diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index 9feb02c3e..2e16d0b0d 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -53,9 +53,6 @@ pub type SignError = signer::SignError; /// Signing Result pub type SignResult = signer::SignResult; -/// Receiving Key Request -pub type ReceivingKeyRequest = signer::ReceivingKeyRequest; - /// Checkpoint #[cfg_attr( feature = "serde", @@ -201,3 +198,15 @@ impl From for RawCheckpoint { ) } } + +/// Get Request +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(crate = "manta_util::serde", deny_unknown_fields) +)] +#[derive(Clone)] +pub enum GetRequest { + /// GET + Get, +}