Skip to content

Commit

Permalink
Make the the software wallet support the old Store format.
Browse files Browse the repository at this point in the history
  • Loading branch information
murisi committed Dec 1, 2024
1 parent 24fcb3b commit 4be78ff
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 16 deletions.
59 changes: 58 additions & 1 deletion crates/wallet/src/keys.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -24,6 +24,55 @@ pub type DatedViewingKey = DatedKeypair<ExtendedViewingKey>;
/// Type alias for a spending key with a birthday.
pub type DatedSpendingKey = DatedKeypair<ExtendedSpendingKey>;

/// 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 = <ExtendedSpendingKey as FromStr>::Err;

fn from_str(s: &str) -> Result<Self, Self::Err> {
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<R: std::io::Read>(
reader: &mut R,
) -> std::io::Result<Self> {
DatedSpendingKey::deserialize_reader(reader).map(|x| Self(x.key))
}
}

impl BorshSerialize for StoreSpendingKey {
fn serialize<W: std::io::Write>(
&self,
writer: &mut W,
) -> std::io::Result<()> {
BorshSerialize::serialize(&DatedSpendingKey::new(self.0, None), writer)
}
}

impl From<ExtendedSpendingKey> for StoreSpendingKey {
fn from(key: ExtendedSpendingKey) -> Self {
Self(key)
}
}

impl From<StoreSpendingKey> for ExtendedSpendingKey {
fn from(key: StoreSpendingKey) -> Self {
key.0
}
}

/// A keypair stored in a wallet
#[derive(Debug)]
pub enum StoredKeypair<T: BorshSerialize + BorshDeserialize + Display + FromStr>
Expand Down Expand Up @@ -371,6 +420,14 @@ impl<T: BorshSerialize + BorshDeserialize> EncryptedKeypair<T> {
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<U: BorshSerialize + BorshDeserialize>(
self,
) -> EncryptedKeypair<U> {
EncryptedKeypair(self.0, PhantomData)
}
}

/// Keypair encryption salt
Expand Down
19 changes: 10 additions & 9 deletions crates/wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -520,7 +520,7 @@ impl<U> Wallet<U> {
/// Get all known viewing keys by their alias
pub fn get_spending_keys(
&self,
) -> HashMap<String, &StoredKeypair<ExtendedSpendingKey>> {
) -> HashMap<String, &StoredKeypair<StoreSpendingKey>> {
self.store
.get_spending_keys()
.iter()
Expand Down Expand Up @@ -905,7 +905,7 @@ impl<U: WalletIo> Wallet<U> {
.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(),
Expand Down Expand Up @@ -967,7 +967,7 @@ impl<U: WalletIo> Wallet<U> {
.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(),
Expand Down Expand Up @@ -1049,13 +1049,14 @@ impl<U: WalletIo> Wallet<U> {
/// 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<V>,
>(
decrypted_key_cache: &mut HashMap<Alias, T>,
decrypted_key_cache: &mut HashMap<Alias, V>,
stored_key: &StoredKeypair<T>,
alias: Alias,
password: Option<Zeroizing<String>>,
) -> Result<T, FindKeyError>
) -> Result<V, FindKeyError>
where
<T as std::str::FromStr>::Err: Display,
{
Expand Down Expand Up @@ -1084,13 +1085,13 @@ impl<U: WalletIo> Wallet<U> {
}
.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()),
}
}

Expand Down
89 changes: 83 additions & 6 deletions crates/wallet/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -67,7 +67,7 @@ pub struct Store {
/// Known viewing keys
view_keys: BTreeMap<Alias, ExtendedViewingKey>,
/// Known spending keys
spend_keys: BTreeMap<Alias, StoredKeypair<ExtendedSpendingKey>>,
spend_keys: BTreeMap<Alias, StoredKeypair<StoreSpendingKey>>,
/// Payment address book
payment_addrs: BiBTreeMap<Alias, PaymentAddress>,
/// Cryptographic keypairs
Expand Down Expand Up @@ -136,7 +136,7 @@ impl Store {
pub fn find_spending_key(
&self,
alias: impl AsRef<str>,
) -> Option<&StoredKeypair<ExtendedSpendingKey>> {
) -> Option<&StoredKeypair<StoreSpendingKey>> {
self.spend_keys.get(&alias.into())
}

Expand Down Expand Up @@ -283,7 +283,7 @@ impl Store {
/// Get all known spending keys by their alias.
pub fn get_spending_keys(
&self,
) -> &BTreeMap<Alias, StoredKeypair<ExtendedSpendingKey>> {
) -> &BTreeMap<Alias, StoredKeypair<StoreSpendingKey>> {
&self.spend_keys
}

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -735,7 +735,13 @@ impl Store {

/// Decode a Store from the given bytes
pub fn decode(data: Vec<u8>) -> Result<Self, toml::de::Error> {
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::<StoreV0>(&data).map(Into::into),
)
}

/// Encode a store into a string of bytes
Expand Down Expand Up @@ -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<Alias, crate::DatedViewingKey>,
/// Known spending keys
spend_keys: BTreeMap<Alias, StoredKeypair<crate::DatedSpendingKey>>,
/// Payment address book
payment_addrs: BiBTreeMap<Alias, PaymentAddress>,
/// Cryptographic keypairs
secret_keys: BTreeMap<Alias, StoredKeypair<common::SecretKey>>,
/// Known public keys
public_keys: BTreeMap<Alias, common::PublicKey>,
/// Known derivation paths
derivation_paths: BTreeMap<Alias, DerivationPath>,
/// Namada address book
addresses: BiBTreeMap<Alias, Address>,
/// Known mappings of public key hashes to their aliases in the `keys`
/// field. Used for look-up by a public key.
pkhs: BTreeMap<PublicKeyHash, Alias>,
/// Special keys if the wallet belongs to a validator
pub(crate) validator_data: Option<ValidatorData>,
/// Namada address vp type
address_vp_types: BTreeMap<AddressVpType, HashSet<Address>>,
}

impl From<StoreV0> 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::<StoreSpendingKey>()),
);
// 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;
Expand Down

0 comments on commit 4be78ff

Please sign in to comment.