Introduce Identifier and Fingerprint to ExtendedKeys (#129)

This commit is contained in:
AntiochP 2017-09-22 12:45:06 -04:00 committed by Ignotus Peverell
parent dbc4e10cec
commit a5b2c7d3f2
4 changed files with 94 additions and 34 deletions

View file

@ -16,6 +16,7 @@
/// in its wallet logic. Largely inspired by bitcoin's BIP32. /// in its wallet logic. Largely inspired by bitcoin's BIP32.
use std::{error, fmt}; use std::{error, fmt};
use std::cmp::min;
use byteorder::{ByteOrder, BigEndian}; use byteorder::{ByteOrder, BigEndian};
use crypto::mac::Mac; 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 /// An ExtendedKey is a secret key which can be used to derive new
/// secret keys to blind the commitment of a transaction output. /// secret keys to blind the commitment of a transaction output.
/// To be usable, a secret key should have an amount assigned to it, /// 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 /// Child number of the key
pub n_child: u32, pub n_child: u32,
/// Parent key's fingerprint /// Parent key's fingerprint
pub fingerprint: [u8; 4], pub fingerprint: Fingerprint,
/// Code of the derivation chain /// Code of the derivation chain
pub chaincode: [u8; 32], pub chaincode: [u8; 32],
/// Actual private key /// Actual private key
@ -85,8 +154,7 @@ impl ExtendedKey {
return Err(Error::InvalidSliceSize); return Err(Error::InvalidSliceSize);
} }
let depth: u8 = slice[0]; let depth: u8 = slice[0];
let mut fingerprint: [u8; 4] = [0; 4]; let fingerprint = Fingerprint::from_bytes(&slice[1..5]);
(&mut fingerprint).copy_from_slice(&slice[1..5]);
let n_child = BigEndian::read_u32(&slice[5..9]); let n_child = BigEndian::read_u32(&slice[5..9]);
let mut chaincode: [u8; 32] = [0; 32]; let mut chaincode: [u8; 32] = [0; 32];
(&mut chaincode).copy_from_slice(&slice[9..41]); (&mut chaincode).copy_from_slice(&slice[9..41]);
@ -123,23 +191,20 @@ impl ExtendedKey {
let mut ext_key = ExtendedKey { let mut ext_key = ExtendedKey {
depth: 0, depth: 0,
fingerprint: [0; 4], fingerprint: Fingerprint::zero(),
n_child: 0, n_child: 0,
chaincode: chaincode, chaincode: chaincode,
key: secret_key, key: secret_key,
}; };
let mut fingerprint: [u8; 4] = [0; 4]; ext_key.fingerprint = ext_key.identifier().fingerprint();
let identifier = ext_key.identifier();
(&mut fingerprint).clone_from_slice(&identifier[0..4]);
ext_key.fingerprint = fingerprint;
Ok(ext_key) Ok(ext_key)
} }
/// Return the identifier of the key, which is the /// Return the identifier of the key, which is the
/// Hash160 of the private key /// Hash160 of the private key
pub fn identifier(&self) -> [u8; 20] { pub fn identifier(&self) -> Identifier {
let mut sha = Sha256::new(); let mut sha = Sha256::new();
sha.input(&self.key[..]); sha.input(&self.key[..]);
@ -151,7 +216,7 @@ impl ExtendedKey {
let mut identifier = [0; 20]; let mut identifier = [0; 20];
ripe.result(&mut identifier); ripe.result(&mut identifier);
return identifier; Identifier::from_bytes(&identifier)
} }
/// Derive an extended key from an extended key /// Derive an extended key from an extended key
@ -175,13 +240,9 @@ impl ExtendedKey {
let mut chain_code: [u8; 32] = [0; 32]; let mut chain_code: [u8; 32] = [0; 32];
(&mut chain_code).clone_from_slice(&derived[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 { Ok(ExtendedKey {
depth: self.depth + 1, depth: self.depth + 1,
fingerprint: fingerprint, fingerprint: self.identifier().fingerprint(),
n_child: n, n_child: n,
chaincode: chain_code, chaincode: chain_code,
key: secret_key, key: secret_key,
@ -195,7 +256,7 @@ mod test {
use secp::Secp256k1; use secp::Secp256k1;
use secp::key::SecretKey; use secp::key::SecretKey;
use super::ExtendedKey; use super::{ExtendedKey, Fingerprint};
use self::serialize::hex::FromHex; use self::serialize::hex::FromHex;
#[test] #[test]
@ -213,7 +274,7 @@ mod test {
let depth = 0; let depth = 0;
let n_child = 0; let n_child = 0;
assert_eq!(extk.key, secret_key); 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.chaincode, chaincode.as_slice());
assert_eq!(extk.depth, depth); assert_eq!(extk.depth, depth);
assert_eq!(extk.n_child, n_child); assert_eq!(extk.n_child, n_child);
@ -235,7 +296,7 @@ mod test {
let depth = 1; let depth = 1;
let n_child = 0; let n_child = 0;
assert_eq!(derived.key, secret_key); 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.chaincode, chaincode.as_slice());
assert_eq!(derived.depth, depth); assert_eq!(derived.depth, depth);
assert_eq!(derived.n_child, n_child); assert_eq!(derived.n_child, n_child);

View file

@ -160,7 +160,7 @@ fn receive_coinbase(config: &WalletConfig, ext_key: &ExtendedKey, amount: u64) -
WalletData::with_wallet(&config.data_file_dir, |wallet_data| { WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
// derive a new private for the reward // 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))?; 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 // 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 // operate within a lock on wallet data
WalletData::with_wallet(&config.data_file_dir, |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 out_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?;
let (tx_final, _) = build::transaction(vec![build::initial_tx(partial), let (tx_final, _) = build::transaction(vec![build::initial_tx(partial),

View file

@ -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| { WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
// second, check from our local wallet data for outputs to spend // 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 { if change < 0 {
return Err(Error::NotEnoughFunds((-change) as u64)); 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 // 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))?; 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)); parts.push(build::output(change as u64, change_key.key));

View file

@ -119,7 +119,7 @@ pub enum OutputStatus {
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct OutputData { pub struct OutputData {
/// Private key fingerprint (in case the wallet tracks multiple) /// 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 /// How many derivations down from the root key
pub n_child: u32, pub n_child: u32,
/// Value of the output, necessary to rebuild the commitment /// Value of the output, necessary to rebuild the commitment
@ -154,27 +154,26 @@ pub struct WalletData {
impl WalletData { impl WalletData {
/// Allows the reading and writing of the wallet data within a file lock. /// Allows the reading and writing of the wallet data within a file lock.
/// Just provide a closure taking a mutable WalletData. The lock should /// 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 /// 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 /// across operating systems, this just creates a lock file with a "should
/// not exist" option. /// not exist" option.
pub fn with_wallet<T, F>(data_file_dir:&str, f: F) -> Result<T, Error> pub fn with_wallet<T, F>(data_file_dir:&str, f: F) -> Result<T, Error>
where F: FnOnce(&mut WalletData) -> T where F: FnOnce(&mut WalletData) -> T
{ {
//create directory if it doesn't exist //create directory if it doesn't exist
fs::create_dir_all(data_file_dir).unwrap_or_else(|why| { 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 data_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, DAT_FILE);
let lock_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, LOCK_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 // 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 \ Error::WalletData(format!("Could not create wallet lock file. Either \
some other process is using the wallet or there's a write access \ some other process is using the wallet or there's a write access issue."))
issue.")) })?;
})?;
// do what needs to be done // do what needs to be done
let mut wdat = WalletData::read_or_create(data_file_path)?; 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 /// Select a subset of unspent outputs to spend in a transaction
/// transferring /// transferring
/// the provided amount. /// the provided amount.
pub fn select(&self, fingerprint: [u8; 4], amount: u64) -> (Vec<OutputData>, i64) { pub fn select(&self, fingerprint: &extkey::Fingerprint, amount: u64) -> (Vec<OutputData>, i64) {
let mut to_spend = vec![]; let mut to_spend = vec![];
let mut input_total = 0; let mut input_total = 0;
// TODO very naive impl for now, there's definitely better coin selection // TODO very naive impl for now, there's definitely better coin selection
// algos available // algos available
for out in &self.outputs { 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()); to_spend.push(out.clone());
input_total += out.value; input_total += out.value;
if input_total >= amount { if input_total >= amount {
@ -244,10 +243,10 @@ impl WalletData {
} }
/// Next child index when we want to create a new output. /// 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; let mut max_n = 0;
for out in &self.outputs { 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; max_n = out.n_child;
} }
} }