diff --git a/src/wallet/bip32.rs b/src/wallet/bip32.rs index d59053f..4d9c0ed 100644 --- a/src/wallet/bip32.rs +++ b/src/wallet/bip32.rs @@ -123,6 +123,16 @@ impl ExtendedPrivKey { }) } + /// Creates a privkey from a path + pub fn from_path(master: &ExtendedPrivKey, path: &[ChildNumber]) + -> Result { + let mut sk = *master; + for &num in path.iter() { + sk = try!(sk.ckd_priv(num)); + } + Ok(sk) + } + /// Private->Private child key derivation pub fn ckd_priv(&self, i: ChildNumber) -> Result { let mut result = [0, ..64]; diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index 0de633d..70f95d8 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -22,16 +22,31 @@ use std::io::extensions::u64_from_be_bytes; use collections::hash::sip::hash_with_keys; use serialize::{Decoder, Decodable, Encoder, Encodable}; +use secp256k1::key::PublicKey; + use blockdata::transaction::{PayToPubkeyHash, TxOut}; use network::constants::Network; use wallet::bip32::{mod, ChildNumber, ExtendedPrivKey, Normal, Hardened}; +use wallet::address::Address; /// A Wallet error pub enum Error { /// Tried to lookup an account by name, but none was found AccountNotFound, /// Tried to add an account when one already exists with that name - DuplicateAccount + DuplicateAccount, + /// An error occured in a BIP32 derivation + Bip32Error(bip32::Error) +} + +/// Each account has two chains, as specified in BIP32 +pub enum AccountChain { + /// Internal addresses are used within the wallet for change, etc, + /// and in principle every generated one will be used. + Internal, + /// External addresses are shared, and might not be used after generatation, + /// complicating recreating the whole wallet from seed. + External } /// An account @@ -39,7 +54,11 @@ pub enum Error { pub struct Account { name: String, internal_path: Vec, - external_path: Vec + internal_used: Vec, + internal_next: u32, + external_path: Vec, + external_used: Vec, + external_next: u32 } impl Default for Account { @@ -47,7 +66,11 @@ impl Default for Account { Account { name: String::new(), internal_path: vec![Hardened(0), Normal(1)], - external_path: vec![Hardened(0), Normal(0)] + internal_used: vec![], + internal_next: 0, + external_path: vec![Hardened(0), Normal(0)], + external_used: vec![], + external_next: 0 } } } @@ -74,6 +97,9 @@ impl, E> Encodable for Wallet { } } +impl Account { +} + impl, E> Decodable for Wallet { fn decode(d: &mut D) -> Result { d.read_struct("wallet", 2, |d| { @@ -108,8 +134,8 @@ impl Wallet { } /// Adds an account to a wallet - pub fn add_account(&mut self, name: String) - -> Result<(), Error> { + pub fn account_insert(&mut self, name: String) + -> Result<(), Error> { if self.accounts.find(&name).is_some() { return Err(DuplicateAccount); } @@ -118,11 +144,69 @@ impl Wallet { self.accounts.insert(name.clone(), Account { name: name, internal_path: vec![Hardened(idx), Normal(1)], - external_path: vec![Hardened(idx), Normal(0)] + internal_used: vec![], + internal_next: 0, + external_path: vec![Hardened(idx), Normal(0)], + external_used: vec![], + external_next: 0 }); Ok(()) } + /// Locates an account in a wallet + pub fn account_find<'a>(&'a self, name: &str) + -> Option<&'a Account> { + self.accounts.find_equiv(&name) + } + + /// Create a new address + pub fn new_address(&mut self, + account: &str, + chain: AccountChain) + -> Result { + let (k1, k2) = self.siphash_key(); + // TODO: unnecessary allocation, waiting on *_equiv in stdlib + let account = self.accounts.find_mut(&account.to_string()); + let account = match account { Some(a) => a, None => return Err(AccountNotFound) }; + + let (mut i, master) = match chain { + Internal => (account.internal_next, + try!(ExtendedPrivKey::from_path( + &self.master, + account.internal_path.as_slice()).map_err(Bip32Error))), + External => (account.external_next, + try!(ExtendedPrivKey::from_path( + &self.master, + account.external_path.as_slice()).map_err(Bip32Error))), + }; + + // Scan for next admissible address + let mut sk = try!(master.ckd_priv(Normal(i)).map_err(Bip32Error)); + let mut address = Address::from_key( + master.network, + &PublicKey::from_secret_key(&sk.secret_key, true)); + while !admissible_address(k1, k2, &address) { + i += 1; + sk = try!(master.ckd_priv(Normal(i)).map_err(Bip32Error)); + address = Address::from_key( + master.network, + &PublicKey::from_secret_key(&sk.secret_key, true)); + } + + match chain { + Internal => { + account.internal_used.push(Normal(i)); + account.internal_next = i + 1; + } + External => { + account.external_used.push(Normal(i)); + account.external_next = i + 1; + } + } + + Ok(address) + } + /// Returns the network of the wallet #[inline] pub fn network(&self) -> Network { @@ -142,11 +226,17 @@ impl Wallet { pub fn might_be_mine(&self, out: &TxOut) -> bool { let (k1, k2) = self.siphash_key(); match out.classify(self.network()) { - PayToPubkeyHash(addr) => hash_with_keys(k1, k2, &addr.as_slice()) & 0xFF == 0, + PayToPubkeyHash(addr) => admissible_address(k1, k2, &addr), _ => false } } } - +/// A filter used for creating a small address index. Note that this +/// function, `might_be_mine` and `siphash_key` are used by wizards-wallet +/// to create a cheap UTXO index +#[inline] +pub fn admissible_address(k1: u64, k2: u64, addr: &Address) -> bool { + hash_with_keys(k1, k2, &addr.as_slice()) & 0xFF == 0 +}