Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: error handling #7

Merged
merged 1 commit into from
Sep 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/account/keychain/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use bip32::XPub;
use crate::{
hex::{add0x, assert_is_valid_hex_address, encode},
utils::crypto::sha3::keccak256,
AccountError,
};

#[derive(Clone, Debug)]
Expand All @@ -13,11 +14,11 @@ pub struct Account {

impl Account {
/// Create a new `Account` from an extended public key
pub fn from_extended_public_key(extended_public_key: &XPub) -> Result<Self, String> {
pub fn from_extended_public_key(extended_public_key: &XPub) -> Result<Self, AccountError> {
let extended_address = encode(&keccak256(&extended_public_key.to_bytes()));
let address = extended_address[extended_address.len() - 40..].to_string();

assert_is_valid_hex_address(&address)?;
assert_is_valid_hex_address(&address).or(Err(AccountError::InvalidPublicKey))?;

Ok(Account {
address: add0x(&address).to_owned(),
Expand Down
18 changes: 18 additions & 0 deletions src/account/keychain/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::VaultError;

#[derive(Debug)]
pub enum KeychainError {
VaultError(VaultError),
KeyNotFoundForAddress(String),
}

impl From<VaultError> for KeychainError {
fn from(error: VaultError) -> Self {
Self::VaultError(error)
}
}

#[derive(Debug)]
pub enum AccountError {
InvalidPublicKey,
}
19 changes: 10 additions & 9 deletions src/account/keychain/keychain.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Account, Controller, Observable, Signer, Vault};
use crate::{Account, Controller, KeychainError, Observable, Signer, Vault};

#[derive(Clone, Debug)]
pub struct KeychainState {
Expand Down Expand Up @@ -48,7 +48,7 @@ impl Keychain {
/// assert!(keychain.is_ok());
/// ```
///
pub fn from_mnemonic(mnemonic: String) -> Result<Self, String> {
pub fn from_mnemonic(mnemonic: String) -> Result<Self, KeychainError> {
Ok(Keychain {
vault: Vault::from_phrase(mnemonic)?,
store: Observable::new(KeychainState { accounts: vec![] }),
Expand All @@ -69,7 +69,7 @@ impl Keychain {
/// assert!(key.is_ok());
/// ```
///
pub fn add_account(&mut self) -> Result<Account, String> {
pub fn add_account(&mut self) -> Result<Account, KeychainError> {
let account = self.vault.add_key()?;
self.store.update(|state| {
state.accounts.push(account.clone());
Expand All @@ -95,7 +95,7 @@ impl Keychain {
///
/// assert!(signature.is_ok());
/// ```
pub fn use_signer<T, R>(&self, address: String, hook: T) -> Result<R, String>
pub fn use_signer<T, R>(&self, address: String, hook: T) -> Result<R, KeychainError>
where
T: FnMut(&Signer) -> R,
{
Expand All @@ -107,8 +107,8 @@ impl Keychain {
.enumerate()
.find(|(_, key)| key.address == address)
{
Some((key_index, _)) => self.vault.use_signer(key_index, hook),
None => Err("Key not found".to_string()),
Some((key_index, _)) => Ok(self.vault.use_signer(key_index, hook)?),
None => Err(KeychainError::KeyNotFoundForAddress(address)),
}
}

Expand All @@ -128,11 +128,12 @@ impl Keychain {
///
/// assert!(!key.is_ok());
/// ```
pub fn lock(&mut self, password: &str) -> Result<(), String> {
pub fn lock(&mut self, password: &str) -> Result<(), KeychainError> {
self.store.update(|state| {
state.accounts = vec![];
});
self.vault.lock(password.as_bytes())

Ok(self.vault.lock(password.as_bytes())?)
}

/// Unlock the keychain
Expand All @@ -150,7 +151,7 @@ impl Keychain {
///
/// assert_eq!(account.address, recovered_accounts[0].address);
/// ```
pub fn unlock(&mut self, password: &str) -> Result<&Vec<Account>, String> {
pub fn unlock(&mut self, password: &str) -> Result<&Vec<Account>, KeychainError> {
let recovered_accounts = self.vault.unlock(password.as_bytes())?;
self.store.update(|state| {
state.accounts = recovered_accounts.clone();
Expand Down
2 changes: 2 additions & 0 deletions src/account/keychain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod account;
pub mod errors;
pub mod keychain;

pub use account::*;
pub use errors::*;
pub use keychain::*;
6 changes: 3 additions & 3 deletions src/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ pub mod keychain;
pub mod signer;
pub mod vault;

pub use keychain::*;
pub use signer::*;
pub use vault::*;
pub use keychain::{Account, AccountError, Keychain, KeychainError};
pub use signer::{get_secret_key_from_bytes, Signable, Signer, SignerError};
pub use vault::{Vault, VaultError};
4 changes: 4 additions & 0 deletions src/account/signer/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[derive(Debug)]
pub enum SignerError {
InvalidPrivateKey,
}
3 changes: 3 additions & 0 deletions src/account/signer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ pub use signer::*;

pub mod signable;
pub use signable::*;

pub mod errors;
pub use errors::SignerError;
7 changes: 4 additions & 3 deletions src/account/signer/signer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use secp256k1::{ecdsa::Signature, Secp256k1, SecretKey};

use crate::Signable;
use crate::{Signable, SignerError};

/// A `Signer` is a safe wrapper around a Hierarchical Deterministic (HD) wallet
/// secret key. It can sign messages.
Expand Down Expand Up @@ -38,8 +38,9 @@ impl Signer {
///
/// assert!(signer.is_ok());
/// ```
pub fn new(private_key: [u8; 32]) -> Result<Self, String> {
let secret_key = get_secret_key_from_bytes(private_key)?;
pub fn new(private_key: [u8; 32]) -> Result<Self, SignerError> {
let secret_key =
get_secret_key_from_bytes(private_key).or(Err(SignerError::InvalidPrivateKey))?;

Ok(Self { secret_key })
}
Expand Down
26 changes: 26 additions & 0 deletions src/account/vault/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use crate::{AccountError, SignerError};

#[derive(Debug)]
pub enum VaultError {
ForbiddenWhileLocked,
AccountCreation,
InvalidPassword,
InvalidMnemonic,
SignerCreation,
KeyDerivation,
AlreadyUnlocked,
SafeCreation,
SafeDecrypt,
}

impl From<AccountError> for VaultError {
fn from(_: AccountError) -> Self {
Self::AccountCreation
}
}

impl From<SignerError> for VaultError {
fn from(_: SignerError) -> Self {
Self::SignerCreation
}
}
5 changes: 4 additions & 1 deletion src/account/vault/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
pub mod errors;
pub mod vault;
pub use vault::*;

pub use errors::VaultError;
pub use vault::Vault;
78 changes: 37 additions & 41 deletions src/account/vault/vault.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bip32::XPrv;

use crate::{signer::Signer, Account, EncryptionKey, HDWallet, Safe};
use crate::{signer::Signer, Account, EncryptionKey, HDWallet, Safe, VaultError};

/// A `Vault` is a safe wrapper around a Hierarchical Deterministic (HD) wallet
/// backed by a mnemonic phrase. It can generate new keys and sign transactions.
Expand Down Expand Up @@ -77,9 +77,11 @@ impl Vault {
///
/// let vault = Vault::from_phrase("oak ethics setup flat gesture security must leader people boring donkey one".to_string());
/// ```
pub fn from_phrase(phrase: String) -> Result<Self, String> {
pub fn from_phrase(phrase: String) -> Result<Self, VaultError> {
Ok(Vault {
hdwallet: Some(HDWallet::from_mnemonic_str(phrase.as_str())?),
hdwallet: Some(
HDWallet::from_mnemonic_str(phrase.as_str()).or(Err(VaultError::InvalidMnemonic))?,
),
private_keys: vec![],
safe: None,
})
Expand All @@ -96,36 +98,18 @@ impl Vault {
/// let mut vault = Vault::new();
/// let key = vault.add_key();
/// ```
pub fn add_key(&mut self) -> Result<Account, String> {
pub fn add_key(&mut self) -> Result<Account, VaultError> {
let index = self.private_keys.len();
let hdwallet = self.get_hdwallet()?;
let (private_key, public_key) = hdwallet.keypair_at_path(0, 0, index)?;
let (private_key, public_key) = hdwallet
.keypair_at_path(0, 0, index)
.or(Err(VaultError::KeyDerivation))?;

self.private_keys.push(private_key);

Ok(Account::from_extended_public_key(&public_key)?)
}

/// Pop a key from the vault
/// Returns the key
///
/// # Example
///
/// ```
/// use walleth::Vault;
///
/// let mut vault = Vault::new();
/// let key = vault.pop_key();
/// ```
pub fn pop_key(&mut self) -> Result<Account, String> {
match self.private_keys.pop() {
Some(private_key) => Ok(Account::from_extended_public_key(
&private_key.public_key(),
)?),
None => Err("No keys to pop".to_string()),
}
}

/// Use a `Signer` from the vault, capable of signing transactions
/// Returns the result of the hook
///
Expand All @@ -144,7 +128,7 @@ impl Vault {
///
/// assert!(signature.is_ok());
/// ```
pub fn use_signer<T, R>(&self, key_index: usize, mut hook: T) -> Result<R, String>
pub fn use_signer<T, R>(&self, key_index: usize, mut hook: T) -> Result<R, VaultError>
where
T: FnMut(&Signer) -> R,
{
Expand All @@ -169,19 +153,22 @@ impl Vault {
///
/// vault.lock(b"my secret password");
/// ```
pub fn lock(&mut self, password: &[u8]) -> Result<(), String> {
pub fn lock(&mut self, password: &[u8]) -> Result<(), VaultError> {
match &self.hdwallet {
Some(hdwallet) => {
// Create an encryption key from the password
let encryption_key = EncryptionKey::new(password, 1000);
// A safe is created with the number of keys in the vault
// and the encryption salt as metadata, and
// the HD wallet as encrypted data bytes
self.safe = Some(Safe::from_plain_bytes(
(encryption_key.salt, self.private_keys.len()),
&encryption_key.pubk,
hdwallet.to_bytes(),
)?);
self.safe = Some(
Safe::from_plain_bytes(
(encryption_key.salt, self.private_keys.len()),
&encryption_key.pubk,
hdwallet.to_bytes(),
)
.or(Err(VaultError::SafeCreation))?,
);
// The HD wallet is removed from memory
self.hdwallet = None;
// The private keys are removed from memory
Expand Down Expand Up @@ -213,20 +200,24 @@ impl Vault {
/// assert_eq!(recovered_accounts.len(), 1);
/// assert_eq!(account.address, recovered_accounts[0].address);
/// ```
pub fn unlock(&mut self, password: &[u8]) -> Result<Vec<Account>, String> {
pub fn unlock(&mut self, password: &[u8]) -> Result<Vec<Account>, VaultError> {
match &self.safe {
Some(safe) => {
// The encryption key is recreated from the password and the salt
let encryption_key = EncryptionKey::with_salt(password, safe.metadata.0, 1000);
// The seed is decrypted from the safe
let recovered_seed = safe.decrypt(&encryption_key.pubk)?;
let recovered_seed = safe
.decrypt(&encryption_key.pubk)
.or(Err(VaultError::SafeDecrypt))?;
// The HD wallet is recreated from the seed
let hdwallet = HDWallet::from_bytes(&recovered_seed)?;
let hdwallet =
HDWallet::from_bytes(&recovered_seed).or(Err(VaultError::InvalidMnemonic))?;
// The number of keys in the vault is retrieved from the safe
// metadata and private keys are recreated from the HD wallet
self.private_keys = (0..safe.metadata.1)
.map(|index| hdwallet.private_key_at_path(0, 0, index))
.collect::<Result<Vec<XPrv>, String>>()?;
.collect::<Result<Vec<XPrv>, String>>()
.or(Err(VaultError::KeyDerivation))?;
// The safe is removed from memory
self.safe = None;
// The HD wallet is stored in memory
Expand All @@ -236,19 +227,24 @@ impl Vault {
self
.private_keys
.iter()
.map(|key| Ok(Account::from_extended_public_key(&key.public_key())?))
.collect::<Result<Vec<Account>, String>>()?,
.map(|key| {
Ok(
Account::from_extended_public_key(&key.public_key())
.or(Err(VaultError::AccountCreation))?,
)
})
.collect::<Result<Vec<Account>, VaultError>>()?,
)
}
None => Err("Vault is not locked".to_string()),
None => Err(VaultError::AlreadyUnlocked),
}
}

/// Get the HD wallet of the vault
fn get_hdwallet(&mut self) -> Result<&mut HDWallet, String> {
fn get_hdwallet(&mut self) -> Result<&mut HDWallet, VaultError> {
match &mut self.hdwallet {
Some(hdwallet) => Ok(hdwallet),
None => Err("Vault is locked".to_string()),
None => Err(VaultError::ForbiddenWhileLocked),
}
}
}
Loading