diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml new file mode 100644 index 000000000..33cb11ab9 --- /dev/null +++ b/wallet/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "grin_wallet" +version = "0.1.0" +authors = ["Laurent Meunier "] + +[dependencies] + +secp256k1zkp = { path = "../secp256k1zkp" } +rust-crypto = "^0.2" +rand = "^0.3" +byteorder = "1" +rustc-serialize = "0.3.23" diff --git a/wallet/src/constants.rs b/wallet/src/constants.rs new file mode 100644 index 000000000..33364902c --- /dev/null +++ b/wallet/src/constants.rs @@ -0,0 +1,13 @@ +// Copyright 2016 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/wallet/src/extendedkey.rs b/wallet/src/extendedkey.rs new file mode 100644 index 000000000..7228ea2b2 --- /dev/null +++ b/wallet/src/extendedkey.rs @@ -0,0 +1,207 @@ +// Copyright 2016 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use Error; +use secp::Secp256k1; +use secp::key::SecretKey; +use crypto::mac::Mac; +use crypto::hmac::Hmac; +use crypto::sha2::Sha256; +use crypto::sha2::Sha512; +use crypto::ripemd160::Ripemd160; +use crypto::digest::Digest; +use Error::{InvalidSeedSize, InvalidSliceSize, InvalidExtendedKey}; +use byteorder::{ByteOrder, BigEndian}; + +/// An extended private key. +/// 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, +/// but when the key is derived, the amount is not known and must be +/// given. + +#[derive(Debug, Clone)] +pub struct ExtendedKey { + /// Depth of the extended key + pub depth: u8, + /// Child number of the key + pub n_child: u32, + /// Parent key's fingerprint + pub fingerprint: [u8; 4], + /// Code of the derivation chain + pub chaincode: [u8; 32], + /// Actual private key + pub key: SecretKey, +} + +impl ExtendedKey { + + /// Creates a new extended key from a serialized one + pub fn from_slice(secp: &Secp256k1, slice: &[u8]) -> Result { + //TODO change when ser. ext. size is fixed + if slice.len() != 73 { + return Err(InvalidSliceSize); + } + let depth: u8 = slice[0]; + let mut fingerprint: [u8; 4] = [0; 4]; + (&mut fingerprint).copy_from_slice(&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]); + let secret_key = match SecretKey::from_slice(secp, &slice[41..73]) { + Ok(key) => key, + Err(_) => return Err(InvalidExtendedKey), + }; + + Ok(ExtendedKey { + depth: depth, + fingerprint: fingerprint, + n_child: n_child, + chaincode: chaincode, + key: secret_key, + }) + } + + /// Creates a new extended master key from a seed + pub fn from_seed(secp: &Secp256k1, seed: &[u8]) -> Result { + let mut hmac = Hmac::new(Sha512::new(), b"Mimble seed"); + match seed.len() { + 16 | 32 | 64 => hmac.input(&seed), + _ => { + return Err(InvalidSeedSize) + }, + } + + let mut derived: [u8; 64] = [0; 64]; + hmac.raw_result(&mut derived); + + let mut chaincode: [u8; 32] = [0; 32]; + (&mut chaincode).copy_from_slice(&derived[32..]); + // TODO Error handling + let secret_key = SecretKey::from_slice(&secp, &derived[0..32]) + .expect("Error generating from seed"); + + Ok(ExtendedKey { + depth: 0, + fingerprint: [0; 4], + n_child: 0, + chaincode: chaincode, + key: secret_key, + }) + } + + /// Return the identifier of the key, which is the + /// Hash160 of the private key + pub fn identifier(&self) -> [u8; 20] { + let mut sha = Sha256::new(); + sha.input(&self.key[..]); + + let mut shres = [0; 32]; + sha.result(&mut shres); + + let mut ripe = Ripemd160::new(); + ripe.input(&shres[..]); + + let mut identifier = [0; 20]; + ripe.result(&mut identifier); + return identifier + } + + /// Derive an extended key from an extended key + pub fn derive(&self, secp: &Secp256k1, n: u32) -> Result { + let mut hmac = Hmac::new(Sha512::new(), &self.chaincode[..]); + let mut n_bytes: [u8; 4] = [0; 4]; + BigEndian::write_u32(&mut n_bytes, n); + + hmac.input(&self.key[..]); + hmac.input(&n_bytes[..]); + + let mut derived = [0; 64]; + hmac.raw_result(&mut derived); + + let mut secret_key = SecretKey::from_slice(&secp, &derived[0..32]) + .expect("Error deriving key"); + secret_key.add_assign(secp, &self.key) + .expect("Error deriving key"); + // TODO check if key != 0 ? + + 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, + n_child: n, + chaincode: chain_code, + key: secret_key + }) + } +} + +#[cfg(test)] +mod test { + extern crate rustc_serialize as serialize; + + use secp::Secp256k1; + use secp::key::SecretKey; + use super::ExtendedKey; + use self::serialize::hex::{FromHex}; + + #[test] + fn extkey_from_seed() { + //TODO More test vectors + let s = Secp256k1::new(); + let seed = "000102030405060708090a0b0c0d0e0f".from_hex().unwrap(); + let extk = ExtendedKey::from_seed(&s, &seed.as_slice()) + .unwrap(); + let sec = "04a7d66a82221501e67f2665332180bd1192c5e58a2cd26613827deb8ba14e75".from_hex().unwrap(); + let secret_key = SecretKey::from_slice(&s, sec.as_slice()) + .unwrap(); + let chaincode = "b7c6740dea1920ec629b3593678f6d8dc40fe6ec1ed824fcde37f476cd6c048c".from_hex().unwrap(); + let fingerprint = "00000000".from_hex().unwrap(); + let depth = 0; + let n_child = 0; + assert_eq!(extk.key, secret_key); + assert_eq!(extk.fingerprint, fingerprint.as_slice()); + assert_eq!(extk.chaincode, chaincode.as_slice()); + assert_eq!(extk.depth, depth); + assert_eq!(extk.n_child, n_child); + } + + #[test] + fn extkey_derivation() { + //TODO More test verctors + let s = Secp256k1::new(); + let seed = "000102030405060708090a0b0c0d0e0f".from_hex().unwrap(); + let extk = ExtendedKey::from_seed(&s, &seed.as_slice()) + .unwrap(); + let derived = extk.derive(&s, 0).unwrap(); + let sec = "908bf3264b8f5f5a5be57d3b0afa36eb5dbcc464ff4da2cf71183e8ec755184b".from_hex().unwrap(); + let secret_key = SecretKey::from_slice(&s, sec.as_slice()) + .unwrap(); + let chaincode = "e90c4559501fb956fa8ddcd6d08499691678cfd6d69e41efb9ee8e87f327e30a".from_hex().unwrap(); + let fingerprint = "8963be69".from_hex().unwrap(); + let depth = 1; + let n_child = 0; + assert_eq!(derived.key, secret_key); + assert_eq!(derived.fingerprint, 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/lib.rs b/wallet/src/lib.rs new file mode 100644 index 000000000..2464ea0eb --- /dev/null +++ b/wallet/src/lib.rs @@ -0,0 +1,41 @@ + +extern crate secp256k1zkp as secp; +extern crate crypto; +extern crate rand; +extern crate byteorder; + +use std::{error, fmt}; + +pub mod extendedkey; +pub mod constants; + +/// An ExtKey error +#[derive(Copy, PartialEq, Eq, Clone, Debug)] +pub enum Error { + /// The size of the seed is invalid + InvalidSeedSize, + InvalidSliceSize, + InvalidExtendedKey, +} + +// Passthrough Debug to Display, since errors should be user-visible +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + f.write_str(error::Error::description(self)) + } +} + +impl error::Error for Error { + fn cause(&self) -> Option<&error::Error> { + None + } + + fn description(&self) -> &str { + match *self { + Error::InvalidSeedSize => "wallet: seed isn't of size 128, 256 or 512", + //TODO change when ser. ext. size is fixed + Error::InvalidSliceSize => "wallet: serialized extended key must be of size 73", + Error::InvalidExtendedKey => "wallet: the given serialized extended key is invalid", + } + } +}