From ac553493f17883d0784963a9627cc0d29d4eb1f3 Mon Sep 17 00:00:00 2001 From: Ignotus Peverell Date: Wed, 24 May 2017 16:08:39 -0700 Subject: [PATCH] WIP: Wallet functionality to receive and send coins Beginning of a first pass at simple wallet functionalities so Grin can be used to author transactions. We introduce a receiving server, to be at least able to build coinbase outputs that can be used by the mining daemon. Present: * Coinbase receiving API. * Command to start the receiving server. * Beginning of a transaction sending command. * Improvements to the REST API abstractions to support the above. Still to do: * Change to the miner daemon to use the receiving server. * A command line sender. * API to receive any transaction (not just coinbase). * A command line receiver. Beyond that, HD derivation and seed generation are very simple so far and almost certainly insecure. Just for testing for now. --- Cargo.toml | 6 +- api/src/endpoints.rs | 2 + api/src/lib.rs | 1 + api/src/rest.rs | 61 +++++++--- core/src/core/block.rs | 8 +- src/bin/grin.rs | 55 ++++++++- util/src/hex.rs | 46 +++++++ util/src/lib.rs | 3 + wallet/Cargo.toml | 17 ++- wallet/src/constants.rs | 13 -- wallet/src/extendedkey.rs | 207 -------------------------------- wallet/src/extkey.rs | 245 ++++++++++++++++++++++++++++++++++++++ wallet/src/lib.rs | 69 +++++------ wallet/src/receiver.rs | 135 +++++++++++++++++++++ wallet/src/sender.rs | 78 ++++++++++++ wallet/src/types.rs | 182 ++++++++++++++++++++++++++++ 16 files changed, 846 insertions(+), 282 deletions(-) create mode 100644 util/src/hex.rs delete mode 100644 wallet/src/constants.rs delete mode 100644 wallet/src/extendedkey.rs create mode 100644 wallet/src/extkey.rs create mode 100644 wallet/src/receiver.rs create mode 100644 wallet/src/sender.rs create mode 100644 wallet/src/types.rs diff --git a/Cargo.toml b/Cargo.toml index 4e0cbd4b..7ad65759 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,13 @@ version = "0.1.0" authors = ["Ignotus Peverell "] [workspace] -members = ["api", "chain", "core", "grin", "p2p", "store", "util", "pool"] +members = ["api", "chain", "core", "grin", "p2p", "store", "util", "pool", "wallet"] [dependencies] +grin_api = { path = "./api" } grin_grin = { path = "./grin" } +grin_wallet = { path = "./wallet" } +secp256k1zkp = { path = "./secp256k1zkp" } clap = "^2.23.3" daemonize = "^0.2.3" @@ -15,3 +18,4 @@ env_logger="^0.3.5" log = "^0.3" serde = "~0.9.10" serde_json = "~0.9.9" +tiny-keccak = "1.1" diff --git a/api/src/endpoints.rs b/api/src/endpoints.rs index a6493cdd..dcc3c315 100644 --- a/api/src/endpoints.rs +++ b/api/src/endpoints.rs @@ -38,6 +38,8 @@ pub struct ChainApi { impl ApiEndpoint for ChainApi { type ID = String; type T = Tip; + type OP_IN = (); + type OP_OUT = (); fn operations(&self) -> Vec { vec![Operation::Get] diff --git a/api/src/lib.rs b/api/src/lib.rs index 0549d94c..4668b0f2 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -25,3 +25,4 @@ mod endpoints; mod rest; pub use endpoints::start_rest_apis; +pub use rest::*; diff --git a/api/src/rest.rs b/api/src/rest.rs index 7fdcb39d..e04d3a83 100644 --- a/api/src/rest.rs +++ b/api/src/rest.rs @@ -110,6 +110,8 @@ pub type ApiResult = ::std::result::Result; pub trait ApiEndpoint: Clone + Send + Sync + 'static { type ID: ToString + FromStr; type T: Serialize + Deserialize; + type OP_IN: Serialize + Deserialize; + type OP_OUT: Serialize + Deserialize; fn operations(&self) -> Vec; @@ -134,10 +136,7 @@ pub trait ApiEndpoint: Clone + Send + Sync + 'static { } #[allow(unused_variables)] - fn operation(&self, op: String, input: IN) -> ApiResult - where IN: Serialize + Deserialize, - OUT: Serialize + Deserialize - { + fn operation(&self, op: String, input: Self::OP_IN) -> ApiResult { unimplemented!() } } @@ -181,6 +180,24 @@ impl Handler for ApiWrapper } } +struct OpWrapper { + operation: String, + endpoint: E, +} + +impl Handler for OpWrapper + where E: ApiEndpoint +{ + fn handle(&self, req: &mut Request) -> IronResult { + let t: E::OP_IN = serde_json::from_reader(req.body.by_ref()) + .map_err(|e| IronError::new(e, status::BadRequest))?; + let res = self.endpoint.operation(self.operation.clone(), t)?; + let res_json = serde_json::to_string(&res) + .map_err(|e| IronError::new(e, status::InternalServerError))?; + Ok(Response::with((status::Ok, res_json))) + } +} + fn extract_param(req: &mut Request, param: &'static str) -> IronResult where ID: ToString + FromStr, ::Err: Debug + Send + Error + 'static @@ -224,17 +241,31 @@ impl ApiServer { let route_postfix = &subpath[1..]; let root = self.root.clone() + &subpath; for op in endpoint.operations() { - let full_path = match op.clone() { - Operation::Get => root.clone() + "/:id", - Operation::Update => root.clone() + "/:id", - Operation::Delete => root.clone() + "/:id", - Operation::Create => root.clone(), - Operation::Custom(op_s) => format!("{}/:{}", root.clone(), op_s), - }; - self.router.route(op.to_method(), - full_path, - ApiWrapper(endpoint.clone()), - format!("{:?}_{}", op, route_postfix)); + let route_name = format!("{:?}_{}", op, route_postfix); + + // special case of custom operations + if let Operation::Custom(op_s) = op.clone() { + let wrapper = OpWrapper { + operation: op_s.clone(), + endpoint: endpoint.clone(), + }; + let full_path = format!("{}", root.clone()); + self.router.route(op.to_method(), full_path.clone(), wrapper, route_name); + info!("POST {}", full_path); + } else { + + // regular REST operations + let full_path = match op.clone() { + Operation::Get => root.clone() + "/:id", + Operation::Update => root.clone() + "/:id", + Operation::Delete => root.clone() + "/:id", + Operation::Create => root.clone(), + _ => panic!("unreachable"), + }; + let wrapper = ApiWrapper(endpoint.clone()); + self.router.route(op.to_method(), full_path.clone(), wrapper, route_name); + info!("{} {}", op.to_method(), full_path); + } } // support for the HTTP Options method by differentiating what's on the diff --git a/core/src/core/block.rs b/core/src/core/block.rs index dc52c354..01bd5f2b 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -434,10 +434,10 @@ impl Block { .verify_kernels(secp) } - // Builds the blinded output and related signature proof for the block reward. - fn reward_output(skey: secp::key::SecretKey, - secp: &Secp256k1) - -> Result<(Output, TxKernel), secp::Error> { + /// Builds the blinded output and related signature proof for the block reward. + pub fn reward_output(skey: secp::key::SecretKey, + secp: &Secp256k1) + -> Result<(Output, TxKernel), secp::Error> { let msg = try!(secp::Message::from_slice(&[0; secp::constants::MESSAGE_SIZE])); let sig = try!(secp.sign(&msg, &skey)); let commit = secp.commit(REWARD, skey).unwrap(); diff --git a/src/bin/grin.rs b/src/bin/grin.rs index 1b543341..5d308d19 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -21,8 +21,12 @@ extern crate log; extern crate env_logger; extern crate serde; extern crate serde_json; +extern crate tiny_keccak; +extern crate grin_api as api; extern crate grin_grin as grin; +extern crate grin_wallet as wallet; +extern crate secp256k1zkp as secp; const GRIN_HOME: &'static str = ".grin"; @@ -31,10 +35,13 @@ use std::thread; use std::io::Read; use std::fs::File; use std::time::Duration; +use tiny_keccak::Keccak; use clap::{Arg, App, SubCommand, ArgMatches}; use daemonize::Daemonize; +use secp::Secp256k1; + fn main() { env_logger::init().unwrap(); @@ -79,6 +86,17 @@ fn main() { .about("Communicates with the Grin server") .subcommand(SubCommand::with_name("status") .about("current status of the Grin chain"))) + + // specification of the wallet commands and options + .subcommand(SubCommand::with_name("wallet") + .about("Wallet software for Grin") + .arg(Arg::with_name("pass") + .short("p") + .long("pass") + .help("Wallet passphrase used to generate the private key seed") + .takes_value(true)) + .subcommand(SubCommand::with_name("receive") + .about("Run the wallet in receiving mode"))) .get_matches(); match args.subcommand() { @@ -97,7 +115,17 @@ fn main() { } } - _ => panic!("Unknown command, use 'grin help' for a list of all commands"), + // client commands and options + ("wallet", Some(wallet_args)) => { + match wallet_args.subcommand() { + ("receive", _) => { + wallet_command(wallet_args); + }, + _ => panic!("Unknown client command, use 'grin help client' for details"), + } + } + + _ => println!("Unknown command, use 'grin help' for a list of all commands"), } } @@ -149,6 +177,31 @@ fn server_command(server_args: &ArgMatches) { } } +fn wallet_command(wallet_args: &ArgMatches) { + let hd_seed = wallet_args.value_of("pass").expect("Wallet passphrase required."); + + // TODO do something closer to BIP39, eazy solution right now + let mut sha3 = Keccak::new_sha3_256(); + sha3.update(hd_seed.as_bytes()); + let mut seed = [0; 32]; + sha3.finalize(&mut seed); + + let s = Secp256k1::new(); + let key = wallet::ExtendedKey::from_seed(&s, &seed[..]).expect("Error deriving extended key from seed."); + + match wallet_args.subcommand() { + ("receive", _) => { + info!("Starting the Grin wallet receiving daemon..."); + let mut apis = api::ApiServer::new("/v1".to_string()); + apis.register_endpoint("/receive_coinbase".to_string(), wallet::WalletReceiver { key: key }); + apis.start("127.0.0.1:13416").unwrap_or_else(|e| { + error!("Failed to start Grin wallet receiver: {}.", e); + }); + } + _ => panic!("Unknown wallet command, use 'grin help wallet' for details"), + } +} + fn read_config() -> grin::ServerConfig { let mut config_path = env::home_dir().ok_or("Failed to detect home directory!").unwrap(); config_path.push(GRIN_HOME); diff --git a/util/src/hex.rs b/util/src/hex.rs new file mode 100644 index 00000000..1cf92842 --- /dev/null +++ b/util/src/hex.rs @@ -0,0 +1,46 @@ +// 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. + +/// Implements hex-encoding from bytes to string and decoding of strings +/// to bytes. Given that rustc-serialize is deprecated and serde doesn't +/// provide easy hex encoding, hex is a bit in limbo right now in Rust- +/// land. It's simple enough that we can just have our own. + +use std::fmt::Write; +use std::num; + +/// Encode the provided bytes into a hex string +pub fn to_hex(bytes: Vec) -> String { + let mut s = String::new(); + for byte in bytes { + write!(&mut s, "{:X}", byte).expect("Unable to write"); + } + s +} + +/// Decode a hex string into bytes. +pub fn from_hex(hex_str: String) -> Result, num::ParseIntError> { + let hex_trim = if &hex_str[..2] == "0x" { + hex_str[2..].to_owned() + } else { + hex_str.clone() + }; + split_n(&hex_str.trim()[..], 2).iter() + .map(|b| u8::from_str_radix(b, 16)) + .collect::, _>>() +} + +fn split_n(s: &str, n: usize) -> Vec<&str> { + (0 .. (s.len() - n + 1)/2 + 1).map(|i| &s[2*i .. 2*i + n]).collect() +} diff --git a/util/src/lib.rs b/util/src/lib.rs index f8bb8d91..5256ee20 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -19,6 +19,9 @@ use std::cell::{RefCell, Ref}; #[allow(unused_imports)] use std::ops::Deref; +mod hex; +pub use hex::*; + // Encapsulation of a RefCell> for one-time initialization after // construction. This implementation will purposefully fail hard if not used // properly, for example if it's not initialized before being first used diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 33cb11ab..963c82dd 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -1,12 +1,19 @@ [package] name = "grin_wallet" version = "0.1.0" -authors = ["Laurent Meunier "] +authors = ["Ignotus Peverell ", "Laurent Meunier "] [dependencies] -secp256k1zkp = { path = "../secp256k1zkp" } -rust-crypto = "^0.2" -rand = "^0.3" byteorder = "1" -rustc-serialize = "0.3.23" +log = "^0.3" +rand = "^0.3" +rust-crypto = "^0.2" +serde = "~0.9.10" +serde_derive = "~0.9.10" +serde_json = "~0.9.8" + +grin_api = { path = "../api" } +grin_core = { path = "../core" } +grin_util = { path = "../util" } +secp256k1zkp = { path = "../secp256k1zkp" } diff --git a/wallet/src/constants.rs b/wallet/src/constants.rs deleted file mode 100644 index 33364902..00000000 --- a/wallet/src/constants.rs +++ /dev/null @@ -1,13 +0,0 @@ -// 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 deleted file mode 100644 index 7228ea2b..00000000 --- a/wallet/src/extendedkey.rs +++ /dev/null @@ -1,207 +0,0 @@ -// 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/extkey.rs b/wallet/src/extkey.rs new file mode 100644 index 00000000..51286865 --- /dev/null +++ b/wallet/src/extkey.rs @@ -0,0 +1,245 @@ +// 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. + +/// Key derivation scheme used by Grin to build chains of private keys +/// in its wallet logic. Largely inspired by bitcoin's BIP32. + +use std::{error, fmt}; + +use byteorder::{ByteOrder, BigEndian}; +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 secp::Secp256k1; +use secp::key::SecretKey; + +/// 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", + } + } +} + +/// 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(Error::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(Error::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(Error::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"); + + let mut ext_key = ExtendedKey { + depth: 0, + fingerprint: [0; 4], + 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; + + Ok(ext_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 index 2464ea0e..057653fa 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1,41 +1,38 @@ +// 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. + +//! Library module for the main wallet functionalities provided by Grin. -extern crate secp256k1zkp as secp; -extern crate crypto; -extern crate rand; extern crate byteorder; +extern crate crypto; +#[macro_use] +extern crate log; +extern crate rand; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate serde_json; -use std::{error, fmt}; +extern crate grin_api as api; +extern crate grin_core as core; +extern crate grin_util as util; +extern crate secp256k1zkp as secp; -pub mod extendedkey; -pub mod constants; +mod extkey; +mod receiver; +mod sender; +mod types; -/// 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", - } - } -} +pub use extkey::ExtendedKey; +pub use receiver::WalletReceiver; diff --git a/wallet/src/receiver.rs b/wallet/src/receiver.rs new file mode 100644 index 00000000..08881c67 --- /dev/null +++ b/wallet/src/receiver.rs @@ -0,0 +1,135 @@ +// 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. + +//! Provides the JSON/HTTP API for wallets to receive payments. Because +//! receiving money in MimbleWimble requires an interactive exchange, a +//! wallet server that's running at all time is required in many cases. +//! +//! The API looks like this: +//! +//! POST /v1/wallet/receive +//! > { +//! > "amount": 10, +//! > "blind_sum": "a12b7f...", +//! > "tx": "f083de...", +//! > } +//! +//! < { +//! < "tx": "f083de...", +//! < "status": "ok" +//! < } +//! +//! POST /v1/wallet/finalize +//! > { +//! > "tx": "f083de...", +//! > } +//! +//! POST /v1/wallet/receive_coinbase +//! > { +//! > "amount": 1, +//! > } +//! +//! < { +//! < "output": "8a90bc...", +//! < "kernel": "f083de...", +//! < } +//! +//! Note that while at this point the finalize call is completely unecessary, a +//! double-exchange will be required as soon as we support Schnorr signatures. +//! So we may as well have it in place already. + +use std::convert::From; +use secp::{self, Secp256k1}; +use secp::key::SecretKey; + +use core::core::{Block, Transaction, TxKernel, Output, build}; +use core::ser; +use api::*; +use extkey::{self, ExtendedKey}; +use types::*; +use util; + +/// Amount in request to build a coinbase output. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CbAmount { + amount: u64, +} + +/// Response to build a coinbase output. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CbData { + output: String, + kernel: String, +} + +/// Component used to receive coins, implements all the receiving end of the +/// wallet REST API as well as some of the command-line operations. +#[derive(Clone)] +pub struct WalletReceiver { + pub key: ExtendedKey, +} + +impl ApiEndpoint for WalletReceiver { + type ID = String; + type T = String; + type OP_IN = CbAmount; + type OP_OUT = CbData; + + fn operations(&self) -> Vec { + vec![Operation::Custom("receive_coinbase".to_string())] + } + + fn operation(&self, op: String, input: CbAmount) -> ApiResult { + debug!("Operation {} with amount {}", op, input.amount); + if input.amount == 0 { + return Err(ApiError::Argument(format!("Zero amount not allowed."))) + } + match op.as_str() { + "receive_coinbase" => { + let (out, kern) = receive_coinbase(&self.key, input.amount). + map_err(|e| ApiError::Internal(format!("Error building coinbase: {:?}", e)))?; + let out_bin = ser::ser_vec(&out). + map_err(|e| ApiError::Internal(format!("Error serializing output: {:?}", e)))?; + let kern_bin = ser::ser_vec(&kern). + map_err(|e| ApiError::Internal(format!("Error serializing kernel: {:?}", e)))?; + Ok(CbData { + output: util::to_hex(out_bin), + kernel: util::to_hex(kern_bin), + }) + }, + _ => Err(ApiError::Argument(format!("Unknown operation: {}", op))), + } + } +} + +/// Build a coinbase output and the corresponding kernel +fn receive_coinbase(ext_key: &ExtendedKey, amount: u64) -> Result<(Output, TxKernel), Error> { + let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); + + // derive a new private for the reward + let mut wallet_data = WalletData::read_or_create()?; + 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 + wallet_data.append_output(OutputData { + fingerprint: coinbase_key.fingerprint, + n_child: coinbase_key.n_child, + value: amount, + status: OutputStatus::Unconfirmed, + }); + wallet_data.write()?; + + Block::reward_output(ext_key.key, &secp).map_err(&From::from) +} diff --git a/wallet/src/sender.rs b/wallet/src/sender.rs new file mode 100644 index 00000000..19601ca7 --- /dev/null +++ b/wallet/src/sender.rs @@ -0,0 +1,78 @@ +// 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 std::convert::From; +use secp::{self, Secp256k1}; +use secp::key::SecretKey; + +use core::core::{Transaction, build}; +use extkey::ExtendedKey; +use types::*; + +fn issue_send_tx(hd_seed: &[u8], amount: u64, dest: String) -> Result<(), Error> { + + let (tx, blind_sum) = build_send_tx(hd_seed, amount)?; + let json_tx = partial_tx_to_json(amount, blind_sum, tx); + if dest == "stdout" { + println!("{}", dest); + } else if &dest[..4] == "http" { + // TODO + unimplemented!(); + } + Ok(()) +} + +/// Builds a transaction to send to someone from the HD seed associated with the +/// wallet and the amount to send. Handles reading through the wallet data file, +/// selecting outputs to spend and building the change. +fn build_send_tx(hd_seed: &[u8], amount: u64) -> Result<(Transaction, SecretKey), Error> { + // first, rebuild the private key from the seed + let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); + let ext_key = ExtendedKey::from_seed(&secp, hd_seed).map_err(|e| Error::Key(e))?; + + // second, check from our local wallet data for outputs to spend + let mut wallet_data = WalletData::read()?; + let (mut coins, change) = wallet_data.select(ext_key.fingerprint, amount); + if change < 0 { + return Err(Error::NotEnoughFunds((-change) as u64)); + } + + // TODO add fees, which is likely going to make this iterative + + // third, build inputs using the appropriate key + let mut parts = vec![]; + for coin in &coins { + let in_key = ext_key.derive(&secp, coin.n_child).map_err(|e| Error::Key(e))?; + parts.push(build::input(coin.value, in_key.key)); + } + + // fourth, derive a new private for change and build the change output + 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)); + + // we got that far, time to start tracking the new output, finalize tx + // and lock the outputs used + wallet_data.append_output(OutputData { + fingerprint: change_key.fingerprint, + n_child: change_key.n_child, + value: change as u64, + status: OutputStatus::Unconfirmed, + }); + for mut coin in coins { + coin.lock(); + } + wallet_data.write()?; + build::transaction(parts).map_err(&From::from) +} diff --git a/wallet/src/types.rs b/wallet/src/types.rs new file mode 100644 index 00000000..8d5f0157 --- /dev/null +++ b/wallet/src/types.rs @@ -0,0 +1,182 @@ +// 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 std::fs::File; +use std::io::Write; +use std::path::Path; +use std::convert::From; + +use serde::{Serialize, Deserialize}; +use serde_json; + +use secp::{self, Secp256k1}; +use secp::key::SecretKey; + +use core::core::Transaction; +use core::ser; +use extkey; +use util; + +const DAT_FILE: &'static str = "wallet.dat"; + +/// Wallet errors, mostly wrappers around underlying crypto or I/O errors. +#[derive(Debug)] +pub enum Error { + NotEnoughFunds(u64), + Crypto(secp::Error), + Key(extkey::Error), + WalletData(String), +} + +impl From for Error { + fn from(e: secp::Error) -> Error { + Error::Crypto(e) + } +} + +impl From for Error { + fn from(e: extkey::Error) -> Error { + Error::Key(e) + } +} + +/// Status of an output that's being tracked by the wallet. Can either be +/// unconfirmed, spent, unspent, or locked (when it's been used to generate +/// a transaction but we don't have confirmation that the transaction was +/// broadcasted or mined). +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub enum OutputStatus { + Unconfirmed, + Unspent, + Locked, + Spent, +} + +/// Information about an output that's being tracked by the wallet. Must be +/// enough to reconstruct the commitment associated with the ouput when the +/// root private key is known. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct OutputData { + /// Private key fingerprint (in case the wallet tracks multiple) + pub fingerprint: [u8; 4], + /// How many derivations down from the root key + pub n_child: u32, + /// Value of the output, necessary to rebuild the commitment + pub value: u64, + /// Current status of the output + pub status: OutputStatus, +} + +impl OutputData { + /// Lock a given output to avoid conflicting use + pub fn lock(&mut self) { + self.status = OutputStatus::Locked; + } +} + +/// Wallet information tracking all our outputs. Based on HD derivation and +/// avoids storing any key data, only storing output amounts and child index. +/// This data structure is directly based on the JSON representation stored +/// on disk, so selection algorithms are fairly primitive and non optimized. +/// +/// TODO optimization so everything isn't O(n) or even O(n^2) +/// TODO account for fees +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct WalletData { + outputs: Vec, +} + +impl WalletData { + + /// Read the wallet data or created a brand new one if it doesn't exist yet + pub fn read_or_create() -> Result { + if Path::new(DAT_FILE).exists() { + WalletData::read() + } else { + // just create a new instance, it will get written afterward + Ok(WalletData { outputs: vec![] }) + } + } + + /// Read the wallet data from disk. + pub fn read() -> Result { + let mut data_file = File::open(DAT_FILE) + .map_err(|e| Error::WalletData(format!("Could not open {}: {}", DAT_FILE, e)))?; + serde_json::from_reader(data_file) + .map_err(|e| Error::WalletData(format!("Error reading {}: {}", DAT_FILE, e))) + } + + /// Write the wallet data to disk. + pub fn write(&self) -> Result<(), Error> { + let mut data_file = File::create(DAT_FILE) + .map_err(|e| Error::WalletData(format!("Could not create {}: {}", DAT_FILE, e)))?; + let res_json = serde_json::to_vec_pretty(self) + .map_err(|_| Error::WalletData(format!("Error serializing wallet data.")))?; + data_file.write_all(res_json.as_slice()) + .map_err(|e| Error::WalletData(format!("Error writing {}: {}", DAT_FILE, e))) + } + + /// Append a new output information to the wallet data. + pub fn append_output(&mut self, out: OutputData) { + self.outputs.push(out); + } + + /// 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) { + 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 { + to_spend.push(out.clone()); + input_total += out.value; + } + } + (to_spend, (input_total as i64) - (amount as i64)) + } + + /// Next child index when we want to create a new output. + pub fn next_child(&self, fingerprint: [u8; 4]) -> u32 { + let mut max_n = 0; + for out in &self.outputs { + println!("{:?} {:?}", out.fingerprint, fingerprint); + if max_n < out.n_child && out.fingerprint == fingerprint { + max_n = out.n_child; + } + } + max_n + 1 + } +} + +/// Helper in serializing the information a receiver requires to build a +/// transaction. +#[derive(Serialize, Deserialize, Debug, Clone)] +struct JSONPartialTx { + amount: u64, + blind_sum: String, + tx: String, +} + +/// Encodes the information for a partial transaction (not yet completed by the +/// receiver) into JSON. +pub fn partial_tx_to_json(receive_amount: u64, blind_sum: SecretKey, tx: Transaction) -> String { + let partial_tx = JSONPartialTx { + amount: receive_amount, + blind_sum: util::to_hex(blind_sum.as_ref().to_vec()), + tx: util::to_hex(ser::ser_vec(&tx).unwrap()), + }; + serde_json::to_string_pretty(&partial_tx).unwrap() +}