diff --git a/wallet/src/extkey.rs b/wallet/src/extkey.rs index 714b72c52..2a1ce2043 100644 --- a/wallet/src/extkey.rs +++ b/wallet/src/extkey.rs @@ -16,6 +16,7 @@ /// in its wallet logic. Largely inspired by bitcoin's BIP32. use std::{error, fmt}; +use std::cmp::min; use byteorder::{ByteOrder, BigEndian}; use crypto::mac::Mac; @@ -58,6 +59,74 @@ impl error::Error for Error { } } +#[derive(Serialize, Deserialize, Clone)] +pub struct Fingerprint([u8; 4]); + +impl Fingerprint { + fn from_bytes(bytes: &[u8]) -> Fingerprint { + let mut fingerprint = [0; 4]; + for i in 0..min(4, bytes.len()) { + fingerprint[i] = bytes[i]; + } + Fingerprint(fingerprint) + } + + fn zero() -> Fingerprint { + Fingerprint([0; 4]) + } +} + +impl PartialEq for Fingerprint { + fn eq(&self, other: &Self) -> bool { + self.0.as_ref() == other.0.as_ref() + } +} + +impl ::std::fmt::Display for Fingerprint { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + for i in self.0.iter().cloned() { + try!(write!(f, "{:02x}", i)); + } + write!(f, "") + } +} + +impl ::std::fmt::Debug for Fingerprint { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + try!(write!(f, "{}(", stringify!(Fingerprint))); + for i in self.0.iter().cloned() { + try!(write!(f, "{:02x}", i)); + } + write!(f, ")") + } +} + +pub struct Identifier([u8; 20]); + +impl Identifier { + fn from_bytes(bytes: &[u8]) -> Identifier { + let mut identifier = [0; 20]; + for i in 0..min(20, bytes.len()) { + identifier[i] = bytes[i]; + } + Identifier(identifier) + } + + pub fn fingerprint(&self) -> Fingerprint { + Fingerprint::from_bytes(&self.0) + } +} + +impl ::std::fmt::Debug for Identifier { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + try!(write!(f, "{}(", stringify!(Identifier))); + for i in self.0.iter().cloned() { + try!(write!(f, "{:02x}", i)); + } + write!(f, ")") + } +} + /// An ExtendedKey is a secret key which can be used to derive new /// secret keys to blind the commitment of a transaction output. /// To be usable, a secret key should have an amount assigned to it, @@ -70,7 +139,7 @@ pub struct ExtendedKey { /// Child number of the key pub n_child: u32, /// Parent key's fingerprint - pub fingerprint: [u8; 4], + pub fingerprint: Fingerprint, /// Code of the derivation chain pub chaincode: [u8; 32], /// Actual private key @@ -85,8 +154,7 @@ impl ExtendedKey { return Err(Error::InvalidSliceSize); } let depth: u8 = slice[0]; - let mut fingerprint: [u8; 4] = [0; 4]; - (&mut fingerprint).copy_from_slice(&slice[1..5]); + let fingerprint = Fingerprint::from_bytes(&slice[1..5]); let n_child = BigEndian::read_u32(&slice[5..9]); let mut chaincode: [u8; 32] = [0; 32]; (&mut chaincode).copy_from_slice(&slice[9..41]); @@ -123,23 +191,20 @@ impl ExtendedKey { let mut ext_key = ExtendedKey { depth: 0, - fingerprint: [0; 4], + fingerprint: Fingerprint::zero(), n_child: 0, chaincode: chaincode, key: secret_key, }; - let mut fingerprint: [u8; 4] = [0; 4]; - let identifier = ext_key.identifier(); - (&mut fingerprint).clone_from_slice(&identifier[0..4]); - ext_key.fingerprint = fingerprint; + ext_key.fingerprint = ext_key.identifier().fingerprint(); Ok(ext_key) } /// Return the identifier of the key, which is the /// Hash160 of the private key - pub fn identifier(&self) -> [u8; 20] { + pub fn identifier(&self) -> Identifier { let mut sha = Sha256::new(); sha.input(&self.key[..]); @@ -151,7 +216,7 @@ impl ExtendedKey { let mut identifier = [0; 20]; ripe.result(&mut identifier); - return identifier; + Identifier::from_bytes(&identifier) } /// Derive an extended key from an extended key @@ -175,13 +240,9 @@ impl ExtendedKey { let mut chain_code: [u8; 32] = [0; 32]; (&mut chain_code).clone_from_slice(&derived[32..]); - let mut fingerprint: [u8; 4] = [0; 4]; - let parent_identifier = self.identifier(); - (&mut fingerprint).clone_from_slice(&parent_identifier[0..4]); - Ok(ExtendedKey { depth: self.depth + 1, - fingerprint: fingerprint, + fingerprint: self.identifier().fingerprint(), n_child: n, chaincode: chain_code, key: secret_key, @@ -195,7 +256,7 @@ mod test { use secp::Secp256k1; use secp::key::SecretKey; - use super::ExtendedKey; + use super::{ExtendedKey, Fingerprint}; use self::serialize::hex::FromHex; #[test] @@ -213,7 +274,7 @@ mod test { let depth = 0; let n_child = 0; assert_eq!(extk.key, secret_key); - assert_eq!(extk.fingerprint, fingerprint.as_slice()); + assert_eq!(extk.fingerprint, Fingerprint::from_bytes(fingerprint.as_slice())); assert_eq!(extk.chaincode, chaincode.as_slice()); assert_eq!(extk.depth, depth); assert_eq!(extk.n_child, n_child); @@ -235,7 +296,7 @@ mod test { let depth = 1; let n_child = 0; assert_eq!(derived.key, secret_key); - assert_eq!(derived.fingerprint, fingerprint.as_slice()); + assert_eq!(derived.fingerprint, Fingerprint::from_bytes(fingerprint.as_slice())); assert_eq!(derived.chaincode, chaincode.as_slice()); assert_eq!(derived.depth, depth); assert_eq!(derived.n_child, n_child); diff --git a/wallet/src/receiver.rs b/wallet/src/receiver.rs index 9bd2db06b..c57348a67 100644 --- a/wallet/src/receiver.rs +++ b/wallet/src/receiver.rs @@ -160,7 +160,7 @@ fn receive_coinbase(config: &WalletConfig, ext_key: &ExtendedKey, amount: u64) - WalletData::with_wallet(&config.data_file_dir, |wallet_data| { // derive a new private for the reward - let next_child = wallet_data.next_child(ext_key.fingerprint); + let next_child = wallet_data.next_child(&ext_key.fingerprint); let coinbase_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?; // track the new output and return the stuff needed for reward @@ -192,7 +192,7 @@ fn receive_transaction(config: &WalletConfig, // operate within a lock on wallet data WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - let next_child = wallet_data.next_child(ext_key.fingerprint); + let next_child = wallet_data.next_child(&ext_key.fingerprint); let out_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?; let (tx_final, _) = build::transaction(vec![build::initial_tx(partial), diff --git a/wallet/src/sender.rs b/wallet/src/sender.rs index b52d0e654..473378178 100644 --- a/wallet/src/sender.rs +++ b/wallet/src/sender.rs @@ -56,7 +56,7 @@ fn build_send_tx(config: &WalletConfig, ext_key: &ExtendedKey, amount: u64) -> R WalletData::with_wallet(&config.data_file_dir, |wallet_data| { // second, check from our local wallet data for outputs to spend - let (coins, change) = wallet_data.select(ext_key.fingerprint, amount); + let (coins, change) = wallet_data.select(&ext_key.fingerprint, amount); if change < 0 { return Err(Error::NotEnoughFunds((-change) as u64)); } @@ -71,7 +71,7 @@ fn build_send_tx(config: &WalletConfig, ext_key: &ExtendedKey, amount: u64) -> R } // fourth, derive a new private for change and build the change output - let next_child = wallet_data.next_child(ext_key.fingerprint); + let next_child = wallet_data.next_child(&ext_key.fingerprint); let change_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?; parts.push(build::output(change as u64, change_key.key)); diff --git a/wallet/src/types.rs b/wallet/src/types.rs index 68e16ccb3..ba2430bfd 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -119,7 +119,7 @@ pub enum OutputStatus { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct OutputData { /// Private key fingerprint (in case the wallet tracks multiple) - pub fingerprint: [u8; 4], + pub fingerprint: extkey::Fingerprint, /// How many derivations down from the root key pub n_child: u32, /// Value of the output, necessary to rebuild the commitment @@ -154,27 +154,26 @@ pub struct WalletData { impl WalletData { /// Allows the reading and writing of the wallet data within a file lock. /// Just provide a closure taking a mutable WalletData. The lock should - /// be held for as short a period as possible to avoid contention. + /// be held for as short a period as possible to avoid contention. /// Note that due to the impossibility to do an actual file lock easily /// across operating systems, this just creates a lock file with a "should - /// not exist" option. + /// not exist" option. pub fn with_wallet(data_file_dir:&str, f: F) -> Result where F: FnOnce(&mut WalletData) -> T { //create directory if it doesn't exist fs::create_dir_all(data_file_dir).unwrap_or_else(|why| { - info!("! {:?}", why.kind()); + info!("! {:?}", why.kind()); }); let data_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, DAT_FILE); let lock_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, LOCK_FILE); // create the lock files, if it already exists, will produce an error - OpenOptions::new().write(true).create_new(true).open(lock_file_path).map_err(|_| { + OpenOptions::new().write(true).create_new(true).open(lock_file_path).map_err(|_| { Error::WalletData(format!("Could not create wallet lock file. Either \ - some other process is using the wallet or there's a write access \ - issue.")) - })?; + some other process is using the wallet or there's a write access issue.")) + })?; // do what needs to be done let mut wdat = WalletData::read_or_create(data_file_path)?; @@ -226,13 +225,13 @@ impl WalletData { /// Select a subset of unspent outputs to spend in a transaction /// transferring /// the provided amount. - pub fn select(&self, fingerprint: [u8; 4], amount: u64) -> (Vec, i64) { + pub fn select(&self, fingerprint: &extkey::Fingerprint, amount: u64) -> (Vec, i64) { let mut to_spend = vec![]; let mut input_total = 0; // TODO very naive impl for now, there's definitely better coin selection // algos available for out in &self.outputs { - if out.status == OutputStatus::Unspent && out.fingerprint == fingerprint { + if out.status == OutputStatus::Unspent && out.fingerprint == *fingerprint { to_spend.push(out.clone()); input_total += out.value; if input_total >= amount { @@ -244,10 +243,10 @@ impl WalletData { } /// Next child index when we want to create a new output. - pub fn next_child(&self, fingerprint: [u8; 4]) -> u32 { + pub fn next_child(&self, fingerprint: &extkey::Fingerprint) -> u32 { let mut max_n = 0; for out in &self.outputs { - if max_n < out.n_child && out.fingerprint == fingerprint { + if max_n < out.n_child && out.fingerprint == *fingerprint { max_n = out.n_child; } }