From 4be78ff1013886964a70b985645bebe5cc34888d Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 1 Dec 2024 16:32:35 +0200 Subject: [PATCH] Make the the software wallet support the old Store format. --- crates/wallet/src/keys.rs | 59 ++++++++++++++++++++++++- crates/wallet/src/lib.rs | 19 ++++---- crates/wallet/src/store.rs | 89 +++++++++++++++++++++++++++++++++++--- 3 files changed, 151 insertions(+), 16 deletions(-) diff --git a/crates/wallet/src/keys.rs b/crates/wallet/src/keys.rs index c3bfd4140ce..bc66d9608ee 100644 --- a/crates/wallet/src/keys.rs +++ b/crates/wallet/src/keys.rs @@ -1,6 +1,6 @@ //! Cryptographic keys for digital signatures support for the wallet. -use std::fmt::Display; +use std::fmt::{Display, Error, Formatter}; use std::marker::PhantomData; use std::str::FromStr; @@ -24,6 +24,55 @@ pub type DatedViewingKey = DatedKeypair; /// Type alias for a spending key with a birthday. pub type DatedSpendingKey = DatedKeypair; +/// Extended spending key with Borsh serialization compatible with +/// DatedSpendingKey. This is necessary to facilitate reading the old Store +/// format. +#[derive(Clone, Debug)] +pub struct StoreSpendingKey(ExtendedSpendingKey); + +impl FromStr for StoreSpendingKey { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + ExtendedSpendingKey::from_str(s).map(Self) + } +} + +impl Display for StoreSpendingKey { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + self.0.fmt(f) + } +} + +impl BorshDeserialize for StoreSpendingKey { + fn deserialize_reader( + reader: &mut R, + ) -> std::io::Result { + DatedSpendingKey::deserialize_reader(reader).map(|x| Self(x.key)) + } +} + +impl BorshSerialize for StoreSpendingKey { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + BorshSerialize::serialize(&DatedSpendingKey::new(self.0, None), writer) + } +} + +impl From for StoreSpendingKey { + fn from(key: ExtendedSpendingKey) -> Self { + Self(key) + } +} + +impl From for ExtendedSpendingKey { + fn from(key: StoreSpendingKey) -> Self { + key.0 + } +} + /// A keypair stored in a wallet #[derive(Debug)] pub enum StoredKeypair @@ -371,6 +420,14 @@ impl EncryptedKeypair { T::try_from_slice(&decrypted_data) .map_err(|_| DecryptionError::DeserializingError) } + + /// Change the type held by this encrypted key pair. This is only safe when + /// the new and old types have the same Borsh serialization. + pub fn map( + self, + ) -> EncryptedKeypair { + EncryptedKeypair(self.0, PhantomData) + } } /// Keypair encryption salt diff --git a/crates/wallet/src/lib.rs b/crates/wallet/src/lib.rs index e5ba30b630d..d6f3dfe4397 100644 --- a/crates/wallet/src/lib.rs +++ b/crates/wallet/src/lib.rs @@ -34,7 +34,7 @@ use zeroize::Zeroizing; pub use self::derivation_path::{DerivationPath, DerivationPathError}; pub use self::keys::{ DatedKeypair, DatedSpendingKey, DatedViewingKey, DecryptionError, - StoredKeypair, + StoreSpendingKey, StoredKeypair, }; pub use self::store::{ConfirmationResponse, ValidatorData, ValidatorKeys}; use crate::store::{derive_hd_secret_key, derive_hd_spending_key}; @@ -520,7 +520,7 @@ impl Wallet { /// Get all known viewing keys by their alias pub fn get_spending_keys( &self, - ) -> HashMap> { + ) -> HashMap> { self.store .get_spending_keys() .iter() @@ -905,7 +905,7 @@ impl Wallet { .ok_or_else(|| { FindKeyError::KeyNotFound(alias_pkh_or_pk.as_ref().to_string()) })?; - Self::decrypt_stored_key::<_>( + Self::decrypt_stored_key::<_, _>( &mut self.decrypted_key_cache, stored_key, alias_pkh_or_pk.into(), @@ -967,7 +967,7 @@ impl Wallet { .ok_or_else(|| { FindKeyError::KeyNotFound(alias.as_ref().to_string()) })?; - Self::decrypt_stored_key::<_>( + Self::decrypt_stored_key::<_, _>( &mut self.decrypted_spendkey_cache, stored_spendkey, alias.into(), @@ -1049,13 +1049,14 @@ impl Wallet { /// supplied, then interactively prompt for password and if successfully /// decrypted, store it in a cache. fn decrypt_stored_key< - T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone, + V: Clone, + T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone + Into, >( - decrypted_key_cache: &mut HashMap, + decrypted_key_cache: &mut HashMap, stored_key: &StoredKeypair, alias: Alias, password: Option>, - ) -> Result + ) -> Result where ::Err: Display, { @@ -1084,13 +1085,13 @@ impl Wallet { } .map_err(FindKeyError::KeyDecryptionError)?; - decrypted_key_cache.insert(alias.clone(), key); + decrypted_key_cache.insert(alias.clone(), key.into()); decrypted_key_cache .get(&alias) .cloned() .ok_or_else(|| FindKeyError::KeyNotFound(alias.to_string())) } - StoredKeypair::Raw(raw) => Ok(raw.clone()), + StoredKeypair::Raw(raw) => Ok(raw.clone().into()), } } diff --git a/crates/wallet/src/store.rs b/crates/wallet/src/store.rs index 7eb42bbbc48..75e087af227 100644 --- a/crates/wallet/src/store.rs +++ b/crates/wallet/src/store.rs @@ -22,7 +22,7 @@ use zeroize::Zeroizing; use super::alias::{self, Alias}; use super::derivation_path::DerivationPath; use super::pre_genesis; -use crate::{StoredKeypair, WalletIo}; +use crate::{StoreSpendingKey, StoredKeypair, WalletIo}; /// Actions that can be taken when there is an alias conflict pub enum ConfirmationResponse { @@ -67,7 +67,7 @@ pub struct Store { /// Known viewing keys view_keys: BTreeMap, /// Known spending keys - spend_keys: BTreeMap>, + spend_keys: BTreeMap>, /// Payment address book payment_addrs: BiBTreeMap, /// Cryptographic keypairs @@ -136,7 +136,7 @@ impl Store { pub fn find_spending_key( &self, alias: impl AsRef, - ) -> Option<&StoredKeypair> { + ) -> Option<&StoredKeypair> { self.spend_keys.get(&alias.into()) } @@ -283,7 +283,7 @@ impl Store { /// Get all known spending keys by their alias. pub fn get_spending_keys( &self, - ) -> &BTreeMap> { + ) -> &BTreeMap> { &self.spend_keys } @@ -422,7 +422,7 @@ impl Store { self.remove_alias(&alias); let (spendkey_to_store, _raw_spendkey) = - StoredKeypair::new(spendkey, password); + StoredKeypair::new(spendkey.into(), password); self.spend_keys.insert(alias.clone(), spendkey_to_store); // Simultaneously add the derived viewing key to ease balance viewing birthday.map(|x| self.birthdays.insert(alias.clone(), x)); @@ -735,7 +735,13 @@ impl Store { /// Decode a Store from the given bytes pub fn decode(data: Vec) -> Result { - toml::from_slice(&data) + // First try to decode Store from current version (with separate + // birthdays) + toml::from_slice(&data).or_else( + // Otherwise try to decode Store from older version (with + // integrated birthdays) + |_| toml::from_slice::(&data).map(Into::into), + ) } /// Encode a store into a string of bytes @@ -835,6 +841,77 @@ impl<'de> Deserialize<'de> for AddressVpType { } } +// A Storage area for keys and addresses. This is a deprecated format but it +// is required for compatability purposes. +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct StoreV0 { + /// Known viewing keys + view_keys: BTreeMap, + /// Known spending keys + spend_keys: BTreeMap>, + /// Payment address book + payment_addrs: BiBTreeMap, + /// Cryptographic keypairs + secret_keys: BTreeMap>, + /// Known public keys + public_keys: BTreeMap, + /// Known derivation paths + derivation_paths: BTreeMap, + /// Namada address book + addresses: BiBTreeMap, + /// Known mappings of public key hashes to their aliases in the `keys` + /// field. Used for look-up by a public key. + pkhs: BTreeMap, + /// Special keys if the wallet belongs to a validator + pub(crate) validator_data: Option, + /// Namada address vp type + address_vp_types: BTreeMap>, +} + +impl From for Store { + fn from(store: StoreV0) -> Self { + let mut to = Store { + payment_addrs: store.payment_addrs, + secret_keys: store.secret_keys, + public_keys: store.public_keys, + derivation_paths: store.derivation_paths, + addresses: store.addresses, + pkhs: store.pkhs, + validator_data: store.validator_data, + address_vp_types: store.address_vp_types, + ..Store::default() + }; + for (alias, key) in store.view_keys { + // Extract the birthday into the birthdays map + to.birthdays.insert(alias.clone(), key.birthday); + // Extrat the key into the viewing keys map + to.view_keys.insert(alias, key.key); + } + for (alias, key) in store.spend_keys { + match key { + StoredKeypair::Raw(key) => { + // Extract the birthday into the birthdays map + to.birthdays.insert(alias.clone(), key.birthday); + // Extract the key into the spending keys map + to.spend_keys + .insert(alias, StoredKeypair::Raw(key.key.into())); + } + StoredKeypair::Encrypted(key) => { + // This map is fine because DatedSpendingKey has the same + // Borsh serialization as StoreSpendingKey + to.spend_keys.insert( + alias, + StoredKeypair::Encrypted(key.map::()), + ); + // Here we assume the birthday for the current alias is + // already given in a viewing key with the same alias. + } + } + } + to + } +} + #[cfg(test)] mod test_wallet { use base58::FromBase58;