mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
Introduce Identifier and Fingerprint to ExtendedKeys (#129)
This commit is contained in:
parent
dbc4e10cec
commit
a5b2c7d3f2
4 changed files with 94 additions and 34 deletions
|
@ -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);
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue