From 677d0a3a95dec606fc278b3b6ac9b05a50c834e5 Mon Sep 17 00:00:00 2001 From: AntiochP <30642645+antiochp@users.noreply.github.com> Date: Mon, 2 Oct 2017 20:02:31 -0400 Subject: [PATCH] keychain crate (no more secretkeys in core) (#146) * introduce grin_keychain, encapsulate derivation of secret_keys * core compiles against keychain, tests don't run yet * core tests are now passing against keychain * wip - getting wallet working with keychain * add util and keychain to travis test matrix * basic test around key derivation --- .gitignore | 3 +- .travis.yml | 2 + Cargo.toml | 3 +- chain/Cargo.toml | 1 + chain/tests/mine_simple_chain.rs | 20 +-- chain/tests/store_indices.rs | 9 +- chain/tests/test_coinbase_maturity.rs | 53 ++++--- core/Cargo.toml | 2 + core/src/core/block.rs | 144 ++++++++++--------- core/src/core/build.rs | 172 +++++++--------------- core/src/core/mod.rs | 158 ++++++++++++--------- core/src/core/transaction.rs | 2 +- core/src/lib.rs | 2 + grin/Cargo.toml | 1 + grin/src/lib.rs | 1 + grin/src/miner.rs | 8 +- grin/tests/framework.rs | 13 +- keychain/Cargo.toml | 13 ++ keychain/rustfmt.toml | 3 + keychain/src/blind.rs | 81 +++++++++++ {wallet => keychain}/src/extkey.rs | 57 +++----- keychain/src/keychain.rs | 196 ++++++++++++++++++++++++++ keychain/src/lib.rs | 32 +++++ p2p/Cargo.toml | 1 - pool/Cargo.toml | 4 +- pool/src/graph.rs | 13 +- pool/src/lib.rs | 3 +- pool/src/pool.rs | 167 ++++++++++------------ pow/Cargo.toml | 1 - src/bin/grin.rs | 20 ++- wallet/Cargo.toml | 7 +- wallet/rustfmt.toml | 3 + wallet/src/checker.rs | 31 ++-- wallet/src/info.rs | 24 ++-- wallet/src/lib.rs | 3 +- wallet/src/receiver.rs | 109 +++++++------- wallet/src/sender.rs | 103 ++++++-------- wallet/src/types.rs | 71 +++++----- 38 files changed, 887 insertions(+), 649 deletions(-) create mode 100644 keychain/Cargo.toml create mode 100644 keychain/rustfmt.toml create mode 100644 keychain/src/blind.rs rename {wallet => keychain}/src/extkey.rs (87%) create mode 100644 keychain/src/keychain.rs create mode 100644 keychain/src/lib.rs create mode 100644 wallet/rustfmt.toml diff --git a/.gitignore b/.gitignore index beee29e2b..4abe9a04b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *.swp .DS_Store -.grin -.grin2 +.grin* node* target Cargo.lock diff --git a/.travis.yml b/.travis.yml index 59064ca15..c0b908692 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,8 @@ env: - TEST_DIR=pool - TEST_DIR=pow - TEST_DIR=wallet + - TEST_DIR=util + - TEST_DIR=keychain - RUST_TEST_THREADS=1 TEST_DIR=grin script: cd $TEST_DIR && cargo test --verbose diff --git a/Cargo.toml b/Cargo.toml index 646214166..b076c52a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,12 @@ authors = ["Ignotus Peverell "] exclude = ["**/*.grin", "**/*.grin2"] [workspace] -members = ["api", "chain", "config", "core", "grin", "p2p", "store", "util", "pool", "wallet"] +members = ["api", "chain", "config", "core", "grin", "keychain", "p2p", "store", "util", "pool", "wallet"] [dependencies] grin_api = { path = "./api" } grin_wallet = { path = "./wallet" } +grin_keychain = { path = "./keychain" } grin_grin = { path = "./grin" } grin_config = { path = "./config" } grin_core = { path = "./core" } diff --git a/chain/Cargo.toml b/chain/Cargo.toml index 19eff9a09..15b7910fa 100644 --- a/chain/Cargo.toml +++ b/chain/Cargo.toml @@ -13,6 +13,7 @@ serde_derive = "~1.0.8" time = "^0.1" grin_core = { path = "../core" } +grin_keychain = { path = "../keychain" } grin_store = { path = "../store" } secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } diff --git a/chain/tests/mine_simple_chain.rs b/chain/tests/mine_simple_chain.rs index f9996d597..377795ea8 100644 --- a/chain/tests/mine_simple_chain.rs +++ b/chain/tests/mine_simple_chain.rs @@ -14,10 +14,10 @@ extern crate grin_core as core; extern crate grin_chain as chain; +extern crate grin_keychain as keychain; extern crate env_logger; extern crate time; extern crate rand; -extern crate secp256k1zkp as secp; extern crate grin_pow as pow; use std::fs; @@ -33,6 +33,8 @@ use core::consensus; use core::global; use core::global::MiningParameterMode; +use keychain::Keychain; + use pow::{types, cuckoo, MiningWorker}; fn clean_output_dir(dir_name: &str) { @@ -60,9 +62,9 @@ fn mine_empty_chain() { let mut rng = OsRng::new().unwrap(); let chain = setup(".grin"); - // mine and add a few blocks - let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); + let keychain = Keychain::from_random_seed().unwrap(); + // mine and add a few blocks let mut miner_config = types::MinerConfig { enable_mining: true, burn_reward: true, @@ -77,8 +79,8 @@ fn mine_empty_chain() { ); for n in 1..4 { let prev = chain.head_header().unwrap(); - let reward_key = secp::key::SecretKey::new(&secp, &mut rng); - let mut b = core::core::Block::new(&prev, vec![], reward_key).unwrap(); + let pk = keychain.derive_pubkey(n as u32).unwrap(); + let mut b = core::core::Block::new(&prev, vec![], &keychain, pk).unwrap(); b.header.timestamp = prev.timestamp + time::Duration::seconds(60); let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap(); @@ -246,10 +248,10 @@ fn prepare_block(prev: &BlockHeader, chain: &Chain, diff: u64) -> Block { } fn prepare_block_nosum(prev: &BlockHeader, diff: u64) -> Block { - let mut rng = OsRng::new().unwrap(); - let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); - let reward_key = secp::key::SecretKey::new(&secp, &mut rng); - let mut b = core::core::Block::new(prev, vec![], reward_key).unwrap(); + let keychain = Keychain::from_random_seed().unwrap(); + let pubkey = keychain.derive_pubkey(1).unwrap(); + + let mut b = core::core::Block::new(prev, vec![], &keychain, pubkey).unwrap(); b.header.timestamp = prev.timestamp + time::Duration::seconds(60); b.header.total_difficulty = Difficulty::from_num(diff); b diff --git a/chain/tests/store_indices.rs b/chain/tests/store_indices.rs index 7153de5a4..c8ddc60d0 100644 --- a/chain/tests/store_indices.rs +++ b/chain/tests/store_indices.rs @@ -15,15 +15,15 @@ extern crate env_logger; extern crate grin_chain as chain; extern crate grin_core as core; +extern crate grin_keychain as keychain; extern crate rand; -extern crate secp256k1zkp as secp; use std::fs; use chain::ChainStore; use core::core::hash::Hashed; use core::core::{Block, BlockHeader}; -use secp::key; +use keychain::Keychain; fn clean_output_dir(dir_name: &str) { let _ = fs::remove_dir_all(dir_name); @@ -34,9 +34,12 @@ fn test_various_store_indices() { let _ = env_logger::init(); clean_output_dir(".grin"); + let keychain = Keychain::from_random_seed().unwrap(); + let pubkey = keychain.derive_pubkey(1).unwrap(); + let chain_store = &chain::store::ChainKVStore::new(".grin".to_string()).unwrap() as &ChainStore; - let block = Block::new(&BlockHeader::default(), vec![], key::ONE_KEY).unwrap(); + let block = Block::new(&BlockHeader::default(), vec![], &keychain, pubkey).unwrap(); let commit = block.outputs[0].commitment(); let block_hash = block.hash(); diff --git a/chain/tests/test_coinbase_maturity.rs b/chain/tests/test_coinbase_maturity.rs index 01da6b2c6..bf0ea4576 100644 --- a/chain/tests/test_coinbase_maturity.rs +++ b/chain/tests/test_coinbase_maturity.rs @@ -14,15 +14,14 @@ extern crate grin_core as core; extern crate grin_chain as chain; +extern crate grin_keychain as keychain; +extern crate grin_pow as pow; extern crate env_logger; extern crate time; extern crate rand; -extern crate secp256k1zkp as secp; -extern crate grin_pow as pow; use std::fs; use std::sync::Arc; -use rand::os::OsRng; use chain::types::*; use core::core::build; @@ -31,6 +30,8 @@ use core::consensus; use core::global; use core::global::MiningParameterMode; +use keychain::Keychain; + use pow::{types, cuckoo, MiningWorker}; fn clean_output_dir(dir_name: &str) { @@ -43,7 +44,6 @@ fn test_coinbase_maturity() { clean_output_dir(".grin"); global::set_mining_mode(MiningParameterMode::AutomatedTesting); - let mut rng = OsRng::new().unwrap(); let mut genesis_block = None; if !chain::Chain::chain_exists(".grin".to_string()) { genesis_block = pow::mine_genesis_block(None); @@ -55,8 +55,6 @@ fn test_coinbase_maturity() { pow::verify_size, ).unwrap(); - let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); - let mut miner_config = types::MinerConfig { enable_mining: true, burn_reward: true, @@ -71,8 +69,14 @@ fn test_coinbase_maturity() { ); let prev = chain.head_header().unwrap(); - let reward_key = secp::key::SecretKey::new(&secp, &mut rng); - let mut block = core::core::Block::new(&prev, vec![], reward_key).unwrap(); + + let keychain = Keychain::from_random_seed().unwrap(); + let pk1 = keychain.derive_pubkey(1).unwrap(); + let pk2 = keychain.derive_pubkey(2).unwrap(); + let pk3 = keychain.derive_pubkey(3).unwrap(); + let pk4 = keychain.derive_pubkey(4).unwrap(); + + let mut block = core::core::Block::new(&prev, vec![], &keychain, pk1.clone()).unwrap(); block.header.timestamp = prev.timestamp + time::Duration::seconds(60); let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap(); @@ -96,15 +100,17 @@ fn test_coinbase_maturity() { let prev = chain.head_header().unwrap(); let amount = consensus::REWARD; - let (coinbase_txn, _) = build::transaction(vec![ - build::input(amount, reward_key), - build::output_rand(amount - 1), - build::with_fee(1), - ]).unwrap(); - - let reward_key = secp::key::SecretKey::new(&secp, &mut rng); - let mut block = core::core::Block::new(&prev, vec![&coinbase_txn], reward_key).unwrap(); + let (coinbase_txn, _) = build::transaction( + vec![ + build::input(amount, pk1.clone()), + build::output(amount - 1, pk2), + build::with_fee(1), + ], + &keychain, + ).unwrap(); + let mut block = core::core::Block::new(&prev, vec![&coinbase_txn], &keychain, pk3.clone()) + .unwrap(); block.header.timestamp = prev.timestamp + time::Duration::seconds(60); let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap(); @@ -124,13 +130,15 @@ fn test_coinbase_maturity() { _ => panic!("expected ImmatureCoinbase error here"), }; - // mine 10 blocks so we increase the height sufficiently - // coinbase will mature and be spendable in the block after these - for _ in 0..10 { + // mine enough blocks to increase the height sufficiently for + // coinbase to reach maturity and be spendable in the next block + for _ in 0..3 { let prev = chain.head_header().unwrap(); - let reward_key = secp::key::SecretKey::new(&secp, &mut rng); - let mut block = core::core::Block::new(&prev, vec![], reward_key).unwrap(); + let keychain = Keychain::from_random_seed().unwrap(); + let pk = keychain.derive_pubkey(1).unwrap(); + + let mut block = core::core::Block::new(&prev, vec![], &keychain, pk).unwrap(); block.header.timestamp = prev.timestamp + time::Duration::seconds(60); let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap(); @@ -149,8 +157,7 @@ fn test_coinbase_maturity() { let prev = chain.head_header().unwrap(); - let reward_key = secp::key::SecretKey::new(&secp, &mut rng); - let mut block = core::core::Block::new(&prev, vec![&coinbase_txn], reward_key).unwrap(); + let mut block = core::core::Block::new(&prev, vec![&coinbase_txn], &keychain, pk4).unwrap(); block.header.timestamp = prev.timestamp + time::Duration::seconds(60); diff --git a/core/Cargo.toml b/core/Cargo.toml index 700558d44..3493dab75 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,3 +15,5 @@ serde_derive = "~1.0.8" time = "^0.1" lazy_static = "~0.2.8" secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } +grin_keychain = { path = "../keychain" } +grin_util = { path = "../util" } diff --git a/core/src/core/block.rs b/core/src/core/block.rs index cc5351cc3..be21d602b 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -16,7 +16,6 @@ use time; use secp::{self, Secp256k1}; -use secp::key::SecretKey; use std::collections::HashSet; use core::Committed; @@ -27,6 +26,8 @@ use core::hash::{Hash, Hashed, ZERO_HASH}; use core::target::Difficulty; use ser::{self, Readable, Reader, Writeable, Writer}; use global; +use keychain; + bitflags! { /// Options for block validation @@ -243,13 +244,13 @@ impl Block { pub fn new( prev: &BlockHeader, txs: Vec<&Transaction>, - reward_key: SecretKey, - ) -> Result { + keychain: &keychain::Keychain, + pubkey: keychain::Identifier, + ) -> Result { - let secp = Secp256k1::with_caps(secp::ContextFlag::Commit); - let (reward_out, reward_proof) = try!(Block::reward_output(reward_key, &secp)); - - Block::with_reward(prev, txs, reward_out, reward_proof) + let (reward_out, reward_proof) = Block::reward_output(keychain, pubkey)?; + let block = Block::with_reward(prev, txs, reward_out, reward_proof)?; + Ok(block) } /// Builds a new block ready to mine from the header of the previous block, @@ -461,17 +462,17 @@ impl Block { /// 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(); - // let switch_commit = secp.switch_commit(skey).unwrap(); - let nonce = secp.nonce(); - let rproof = secp.range_proof(0, REWARD, skey, commit, nonce); + keychain: &keychain::Keychain, + pubkey: keychain::Identifier, + ) -> Result<(Output, TxKernel), keychain::Error> { + let secp = keychain.secp(); + + let msg = secp::Message::from_slice(&[0; secp::constants::MESSAGE_SIZE])?; + let sig = keychain.sign(&msg, &pubkey)?; + let commit = keychain.commit(REWARD, &pubkey)?; + // let switch_commit = keychain.switch_commit(pubkey)?; + + let rproof = keychain.range_proof(REWARD, &pubkey, commit)?; let output = Output { features: COINBASE_OUTPUT, @@ -497,29 +498,23 @@ impl Block { mod test { use super::*; use core::Transaction; - use core::build::{self, input, output, input_rand, output_rand, with_fee}; + use core::build::{self, input, output, with_fee}; use core::test::tx2i1o; + use keychain::{Identifier, Keychain}; - use secp::{self, Secp256k1}; - use secp::key::SecretKey; - use rand::os::OsRng; - - fn new_secp() -> Secp256k1 { - secp::Secp256k1::with_caps(secp::ContextFlag::Commit) - } + use secp; // utility to create a block without worrying about the key or previous // header - fn new_block(txs: Vec<&Transaction>, secp: &Secp256k1) -> Block { - let mut rng = OsRng::new().unwrap(); - let skey = SecretKey::new(secp, &mut rng); - Block::new(&BlockHeader::default(), txs, skey).unwrap() + fn new_block(txs: Vec<&Transaction>, keychain: &Keychain) -> Block { + let pubkey = keychain.derive_pubkey(1).unwrap(); + Block::new(&BlockHeader::default(), txs, keychain, pubkey).unwrap() } // utility producing a transaction that spends an output with the provided // value and blinding key - fn txspend1i1o(v: u64, b: SecretKey) -> Transaction { - build::transaction(vec![input(v, b), output_rand(3), with_fee(1)]) + fn txspend1i1o(v: u64, keychain: &Keychain, pk1: Identifier, pk2: Identifier) -> Transaction { + build::transaction(vec![input(v, pk1), output(3, pk2), with_fee(1)], &keychain) .map(|(tx, _)| tx) .unwrap() } @@ -527,21 +522,25 @@ mod test { #[test] // builds a block with a tx spending another and check if merging occurred fn compactable_block() { - let mut rng = OsRng::new().unwrap(); - let ref secp = new_secp(); + let keychain = Keychain::from_random_seed().unwrap(); + let pk1 = keychain.derive_pubkey(1).unwrap(); + let pk2 = keychain.derive_pubkey(2).unwrap(); + let pk3 = keychain.derive_pubkey(3).unwrap(); let mut btx1 = tx2i1o(); - let skey = SecretKey::new(secp, &mut rng); - let (mut btx2, _) = build::transaction(vec![input_rand(5), output(4, skey), with_fee(1)]) - .unwrap(); + let (mut btx2, _) = build::transaction( + vec![input(5, pk1), output(4, pk2.clone()), with_fee(1)], + &keychain, + ).unwrap(); - // spending tx2 - let mut btx3 = txspend1i1o(4, skey); - let b = new_block(vec![&mut btx1, &mut btx2, &mut btx3], secp); + // spending tx2 - reuse pk2 + + let mut btx3 = txspend1i1o(4, &keychain, pk2.clone(), pk3); + let b = new_block(vec![&mut btx1, &mut btx2, &mut btx3], &keychain); // block should have been automatically compacted (including reward // output) and should still be valid - b.validate(&secp).unwrap(); + b.validate(&keychain.secp()).unwrap(); assert_eq!(b.inputs.len(), 3); assert_eq!(b.outputs.len(), 3); } @@ -550,21 +549,26 @@ mod test { // builds 2 different blocks with a tx spending another and check if merging // occurs fn mergeable_blocks() { - let mut rng = OsRng::new().unwrap(); - let ref secp = new_secp(); + let keychain = Keychain::from_random_seed().unwrap(); + let pk1 = keychain.derive_pubkey(1).unwrap(); + let pk2 = keychain.derive_pubkey(2).unwrap(); + let pk3 = keychain.derive_pubkey(3).unwrap(); let mut btx1 = tx2i1o(); - let skey = SecretKey::new(secp, &mut rng); - let (mut btx2, _) = build::transaction(vec![input_rand(5), output(4, skey), with_fee(1)]) - .unwrap(); - // spending tx2 - let mut btx3 = txspend1i1o(4, skey); + let (mut btx2, _) = build::transaction( + vec![input(5, pk1), output(4, pk2.clone()), with_fee(1)], + &keychain, + ).unwrap(); - let b1 = new_block(vec![&mut btx1, &mut btx2], secp); - b1.validate(&secp).unwrap(); - let b2 = new_block(vec![&mut btx3], secp); - b2.validate(&secp).unwrap(); + // spending tx2 - reuse pk2 + let mut btx3 = txspend1i1o(4, &keychain, pk2.clone(), pk3); + + let b1 = new_block(vec![&mut btx1, &mut btx2], &keychain); + b1.validate(&keychain.secp()).unwrap(); + + let b2 = new_block(vec![&mut btx3], &keychain); + b2.validate(&keychain.secp()).unwrap(); // block should have been automatically compacted and should still be valid let b3 = b1.merge(b2); @@ -574,8 +578,8 @@ mod test { #[test] fn empty_block_with_coinbase_is_valid() { - let ref secp = new_secp(); - let b = new_block(vec![], secp); + let keychain = Keychain::from_random_seed().unwrap(); + let b = new_block(vec![], &keychain); assert_eq!(b.inputs.len(), 0); assert_eq!(b.outputs.len(), 1); @@ -597,7 +601,7 @@ mod test { // the block should be valid here (single coinbase output with corresponding // txn kernel) - assert_eq!(b.validate(&secp), Ok(())); + assert_eq!(b.validate(&keychain.secp()), Ok(())); } #[test] @@ -605,44 +609,50 @@ mod test { // invalidates the block and specifically it causes verify_coinbase to fail // additionally verifying the merkle_inputs_outputs also fails fn remove_coinbase_output_flag() { - let ref secp = new_secp(); - let mut b = new_block(vec![], secp); + let keychain = Keychain::from_random_seed().unwrap(); + let mut b = new_block(vec![], &keychain); assert!(b.outputs[0].features.contains(COINBASE_OUTPUT)); b.outputs[0].features.remove(COINBASE_OUTPUT); assert_eq!( - b.verify_coinbase(&secp), + b.verify_coinbase(&keychain.secp()), Err(secp::Error::IncorrectCommitSum) ); - assert_eq!(b.verify_kernels(&secp), Ok(())); + assert_eq!(b.verify_kernels(&keychain.secp()), Ok(())); - assert_eq!(b.validate(&secp), Err(secp::Error::IncorrectCommitSum)); + assert_eq!( + b.validate(&keychain.secp()), + Err(secp::Error::IncorrectCommitSum) + ); } #[test] // test that flipping the COINBASE_KERNEL flag on the kernel features // invalidates the block and specifically it causes verify_coinbase to fail fn remove_coinbase_kernel_flag() { - let ref secp = new_secp(); - let mut b = new_block(vec![], secp); + let keychain = Keychain::from_random_seed().unwrap(); + let mut b = new_block(vec![], &keychain); assert!(b.kernels[0].features.contains(COINBASE_KERNEL)); b.kernels[0].features.remove(COINBASE_KERNEL); assert_eq!( - b.verify_coinbase(&secp), + b.verify_coinbase(&keychain.secp()), Err(secp::Error::IncorrectCommitSum) ); - assert_eq!(b.verify_kernels(&secp), Ok(())); + assert_eq!(b.verify_kernels(&keychain.secp()), Ok(())); - assert_eq!(b.validate(&secp), Err(secp::Error::IncorrectCommitSum)); + assert_eq!( + b.validate(&keychain.secp()), + Err(secp::Error::IncorrectCommitSum) + ); } #[test] fn serialize_deserialize_block() { - let ref secp = new_secp(); - let b = new_block(vec![], secp); + let keychain = Keychain::from_random_seed().unwrap(); + let b = new_block(vec![], &keychain); let mut vec = Vec::new(); ser::serialize(&mut vec, &b).expect("serialization failed"); diff --git a/core/src/core/build.rs b/core/src/core/build.rs index 0415cc0f3..621690d16 100644 --- a/core/src/core/build.rs +++ b/core/src/core/build.rs @@ -26,59 +26,15 @@ //! with_fee(1)]) use byteorder::{ByteOrder, BigEndian}; -use secp::{self, Secp256k1}; -use secp::key::SecretKey; -use rand::os::OsRng; +use secp; use core::{Transaction, Input, Output, DEFAULT_OUTPUT}; +use keychain; +use keychain::{Keychain, BlindSum, BlindingFactor, Identifier}; /// Context information available to transaction combinators. -pub struct Context { - secp: Secp256k1, - rng: OsRng, -} - -/// Accumulator to compute the sum of blinding factors. Keeps track of each -/// factor as well as the "sign" with which they should be combined. -pub struct BlindSum { - positive: Vec, - negative: Vec, -} - -impl BlindSum { - /// Creates a new blinding factor sum. - fn new() -> BlindSum { - BlindSum { - positive: vec![], - negative: vec![], - } - } - - /// Adds the provided key to the sum of blinding factors. - fn add(self, key: SecretKey) -> BlindSum { - let mut new_pos = self.positive; - new_pos.push(key); - BlindSum { - positive: new_pos, - negative: self.negative, - } - } - - /// Subtractss the provided key to the sum of blinding factors. - fn sub(self, key: SecretKey) -> BlindSum { - let mut new_neg = self.negative; - new_neg.push(key); - BlindSum { - positive: self.positive, - negative: new_neg, - } - } - - /// Computes the sum of blinding factors from all the ones that have been - /// added and subtracted. - fn sum(self, secp: &Secp256k1) -> Result { - secp.blind_sum(self.positive, self.negative) - } +pub struct Context<'a> { + keychain: &'a Keychain, } /// Function type returned by the transaction combinators. Transforms a @@ -87,59 +43,25 @@ type Append = for<'a> Fn(&'a mut Context, (Transaction, BlindSum)) -> (Transacti /// Adds an input with the provided value and blinding key to the transaction /// being built. -pub fn input(value: u64, blinding: SecretKey) -> Box { +pub fn input(value: u64, pubkey: Identifier) -> Box { Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) { - let commit = build.secp.commit(value, blinding).unwrap(); - (tx.with_input(Input(commit)), sum.sub(blinding)) - }) -} - -/// Adds an input with the provided value and a randomly generated blinding -/// key to the transaction being built. This has no real use in practical -/// applications but is very convenient for tests. -pub fn input_rand(value: u64) -> Box { - Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) { - let blinding = SecretKey::new(&build.secp, &mut build.rng); - let commit = build.secp.commit(value, blinding).unwrap(); - (tx.with_input(Input(commit)), sum.sub(blinding)) + let commit = build.keychain.commit(value, &pubkey).unwrap(); + (tx.with_input(Input(commit)), sum.sub_pubkey(pubkey.clone())) }) } /// Adds an output with the provided value and blinding key to the transaction /// being built. -pub fn output(value: u64, blinding: SecretKey) -> Box { +pub fn output(value: u64, pubkey: Identifier) -> Box { Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) { - let commit = build.secp.commit(value, blinding).unwrap(); - let nonce = build.secp.nonce(); - let rproof = build.secp.range_proof(0, value, blinding, commit, nonce); - ( - tx.with_output(Output { - features: DEFAULT_OUTPUT, - commit: commit, - proof: rproof, - }), - sum.add(blinding), - ) - }) -} + let commit = build.keychain.commit(value, &pubkey).unwrap(); + let rproof = build.keychain.range_proof(value, &pubkey, commit).unwrap(); -/// Adds an output with the provided value and a randomly generated blinding -/// key to the transaction being built. This has no real use in practical -/// applications but is very convenient for tests. -pub fn output_rand(value: u64) -> Box { - Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) { - let blinding = SecretKey::new(&build.secp, &mut build.rng); - let commit = build.secp.commit(value, blinding).unwrap(); - let nonce = build.secp.nonce(); - let rproof = build.secp.range_proof(0, value, blinding, commit, nonce); - ( - tx.with_output(Output { - features: DEFAULT_OUTPUT, - commit: commit, - proof: rproof, - }), - sum.add(blinding), - ) + (tx.with_output(Output { + features: DEFAULT_OUTPUT, + commit: commit, + proof: rproof, + }), sum.add_pubkey(pubkey.clone())) }) } @@ -153,9 +75,9 @@ pub fn with_fee(fee: u64) -> Box { /// Sets a known excess value on the transaction being built. Usually used in /// combination with the initial_tx function when a new transaction is built /// by adding to a pre-existing one. -pub fn with_excess(excess: SecretKey) -> Box { +pub fn with_excess(excess: BlindingFactor) -> Box { Box::new(move |_build, (tx, sum)| -> (Transaction, BlindSum) { - (tx, sum.add(excess)) + (tx, sum.add_blinding_factor(excess.clone())) }) } @@ -176,21 +98,18 @@ pub fn initial_tx(tx: Transaction) -> Box { /// let (tx2, _) = build::transaction(vec![initial_tx(tx1), with_excess(sum), /// output_rand(2)]).unwrap(); /// -pub fn transaction(elems: Vec>) -> Result<(Transaction, SecretKey), secp::Error> { - let mut ctx = Context { - secp: Secp256k1::with_caps(secp::ContextFlag::Commit), - rng: OsRng::new().unwrap(), - }; +pub fn transaction( + elems: Vec>, + keychain: &keychain::Keychain, +) -> Result<(Transaction, BlindingFactor), keychain::Error> { + let mut ctx = Context { keychain }; let (mut tx, sum) = elems.iter().fold( - (Transaction::empty(), BlindSum::new()), - |acc, elem| elem(&mut ctx, acc), + (Transaction::empty(), BlindSum::new()), |acc, elem| elem(&mut ctx, acc) ); - - let blind_sum = sum.sum(&ctx.secp)?; + let blind_sum = ctx.keychain.blind_sum(&sum)?; let msg = secp::Message::from_slice(&u64_to_32bytes(tx.fee))?; - let sig = ctx.secp.sign(&msg, &blind_sum)?; - tx.excess_sig = sig.serialize_der(&ctx.secp); - + let sig = ctx.keychain.sign_with_blinding(&msg, &blind_sum)?; + tx.excess_sig = sig.serialize_der(&ctx.keychain.secp()); Ok((tx, blind_sum)) } @@ -200,30 +119,37 @@ fn u64_to_32bytes(n: u64) -> [u8; 32] { bytes } - // Just a simple test, most exhaustive tests in the core mod.rs. #[cfg(test)] mod test { use super::*; - use secp::{self, key, Secp256k1}; - #[test] fn blind_simple_tx() { - let secp = Secp256k1::with_caps(secp::ContextFlag::Commit); - let (tx, _) = transaction(vec![ - input_rand(10), - input_rand(11), - output_rand(20), - with_fee(1), - ]).unwrap(); - tx.verify_sig(&secp).unwrap(); + let keychain = Keychain::from_random_seed().unwrap(); + let pk1 = keychain.derive_pubkey(1).unwrap(); + let pk2 = keychain.derive_pubkey(2).unwrap(); + let pk3 = keychain.derive_pubkey(3).unwrap(); + + let (tx, _) = transaction( + vec![input(10, pk1), input(11, pk2), output(20, pk3), with_fee(1)], + &keychain, + ).unwrap(); + + tx.verify_sig(&keychain.secp()).unwrap(); } + #[test] fn blind_simpler_tx() { - let secp = Secp256k1::with_caps(secp::ContextFlag::Commit); - let (tx, _) = transaction(vec![input_rand(6), output(2, key::ONE_KEY), with_fee(4)]) - .unwrap(); - tx.verify_sig(&secp).unwrap(); + let keychain = Keychain::from_random_seed().unwrap(); + let pk1 = keychain.derive_pubkey(1).unwrap(); + let pk2 = keychain.derive_pubkey(2).unwrap(); + + let (tx, _) = transaction( + vec![input(6, pk1), output(2, pk2), with_fee(4)], + &keychain, + ).unwrap(); + + tx.verify_sig(&keychain.secp()).unwrap(); } } diff --git a/core/src/core/mod.rs b/core/src/core/mod.rs index 8be52840f..0216a884d 100644 --- a/core/src/core/mod.rs +++ b/core/src/core/mod.rs @@ -31,10 +31,10 @@ use secp::pedersen::*; pub use self::block::*; pub use self::transaction::*; -use self::hash::{Hash, Hashed, ZERO_HASH}; +use self::hash::Hashed; use ser::{Writeable, Writer, Reader, Readable, Error}; - use global; +// use keychain; /// Implemented by types that hold inputs and outputs including Pedersen /// commitments. Handles the collection of the commitments as well as their @@ -186,28 +186,22 @@ impl Writeable for Proof { mod test { use super::*; use core::hash::ZERO_HASH; - use secp; - use secp::Secp256k1; - use secp::key::SecretKey; + use core::build::{input, output, with_fee, initial_tx, with_excess}; use ser; - use rand::os::OsRng; - use core::build::{self, input, output, input_rand, output_rand, with_fee, initial_tx, - with_excess}; - - fn new_secp() -> Secp256k1 { - secp::Secp256k1::with_caps(secp::ContextFlag::Commit) - } + use keychain; + use keychain::{Keychain, BlindingFactor}; #[test] #[should_panic(expected = "InvalidSecretKey")] - fn zero_commit() { - // a transaction whose commitment sums to zero shouldn't validate - let ref secp = new_secp(); - let mut rng = OsRng::new().unwrap(); + fn test_zero_commit_fails() { + let keychain = Keychain::from_random_seed().unwrap(); + let pk1 = keychain.derive_pubkey(1).unwrap(); // blinding should fail as signing with a zero r*G shouldn't work - let skey = SecretKey::new(secp, &mut rng); - build::transaction(vec![input(10, skey), output(1, skey), with_fee(9)]).unwrap(); + build::transaction( + vec![input(10, pk1.clone()), output(9, pk1.clone()), with_fee(1)], + &keychain, + ).unwrap(); } #[test] @@ -250,12 +244,16 @@ mod test { #[test] fn hash_output() { - let (tx, _) = build::transaction(vec![ - input_rand(75), - output_rand(42), - output_rand(32), - with_fee(1), - ]).unwrap(); + let keychain = Keychain::from_random_seed().unwrap(); + let pk1 = keychain.derive_pubkey(1).unwrap(); + let pk2 = keychain.derive_pubkey(2).unwrap(); + let pk3 = keychain.derive_pubkey(3).unwrap(); + + let (tx, _) = + build::transaction( + vec![input(75, pk1), output(42, pk2), output(32, pk3), with_fee(1)], + &keychain, + ).unwrap(); let h = tx.outputs[0].hash(); assert!(h != ZERO_HASH); let h2 = tx.outputs[1].hash(); @@ -264,14 +262,14 @@ mod test { #[test] fn blind_tx() { - let ref secp = new_secp(); + let keychain = Keychain::from_random_seed().unwrap(); let btx = tx2i1o(); - btx.verify_sig(&secp).unwrap(); // unwrap will panic if invalid + btx.verify_sig(&keychain.secp()).unwrap(); // unwrap will panic if invalid // checks that the range proof on our blind output is sufficiently hiding let Output { proof, .. } = btx.outputs[0]; - let info = secp.range_proof_info(proof); + let info = &keychain.secp().range_proof_info(proof); assert!(info.min == 0); assert!(info.max == u64::max_value()); } @@ -290,20 +288,26 @@ mod test { /// 2 inputs, 2 outputs transaction. #[test] fn tx_build_exchange() { - let ref secp = new_secp(); + let keychain = Keychain::from_random_seed().unwrap(); + let pk1 = keychain.derive_pubkey(1).unwrap(); + let pk2 = keychain.derive_pubkey(2).unwrap(); + let pk3 = keychain.derive_pubkey(3).unwrap(); + let pk4 = keychain.derive_pubkey(4).unwrap(); let tx_alice: Transaction; - let blind_sum: SecretKey; + let blind_sum: BlindingFactor; { // Alice gets 2 of her pre-existing outputs to send 5 coins to Bob, they // become inputs in the new transaction - let (in1, in2) = (input_rand(4), input_rand(3)); + let (in1, in2) = (input(4, pk1), input(3, pk2)); // Alice builds her transaction, with change, which also produces the sum // of blinding factors before they're obscured. - let (tx, sum) = build::transaction(vec![in1, in2, output_rand(1), with_fee(1)]) - .unwrap(); + let (tx, sum) = build::transaction( + vec![in1, in2, output(1, pk3), with_fee(1)], + &keychain, + ).unwrap(); tx_alice = tx; blind_sum = sum; } @@ -311,69 +315,91 @@ mod test { // From now on, Bob only has the obscured transaction and the sum of // blinding factors. He adds his output, finalizes the transaction so it's // ready for broadcast. - let (tx_final, _) = build::transaction(vec![ - initial_tx(tx_alice), - with_excess(blind_sum), - output_rand(5), - ]).unwrap(); + let (tx_final, _) = + build::transaction( + vec![initial_tx(tx_alice), with_excess(blind_sum), output(5, pk4)], + &keychain, + ).unwrap(); - tx_final.validate(&secp).unwrap(); + tx_final.validate(&keychain.secp()).unwrap(); } #[test] fn reward_empty_block() { - let mut rng = OsRng::new().unwrap(); - let ref secp = new_secp(); - let skey = SecretKey::new(secp, &mut rng); + let keychain = new_keychain(); + let pubkey = keychain.derive_pubkey(1).unwrap(); - let b = Block::new(&BlockHeader::default(), vec![], skey).unwrap(); - b.compact().validate(&secp).unwrap(); + let b = Block::new(&BlockHeader::default(), vec![], &keychain, pubkey).unwrap(); + b.compact().validate(&keychain.secp()).unwrap(); + } + + fn new_keychain() -> keychain::Keychain { + keychain::Keychain::from_random_seed().unwrap() } #[test] fn reward_with_tx_block() { - let mut rng = OsRng::new().unwrap(); - let ref secp = new_secp(); - let skey = SecretKey::new(secp, &mut rng); + let keychain = new_keychain(); + let pubkey = keychain.derive_pubkey(1).unwrap(); let mut tx1 = tx2i1o(); - tx1.verify_sig(&secp).unwrap(); + tx1.verify_sig(keychain.secp()).unwrap(); - let b = Block::new(&BlockHeader::default(), vec![&mut tx1], skey).unwrap(); - b.compact().validate(&secp).unwrap(); + let b = Block::new(&BlockHeader::default(), vec![&mut tx1], &keychain, pubkey).unwrap(); + b.compact().validate(keychain.secp()).unwrap(); } #[test] fn simple_block() { - let mut rng = OsRng::new().unwrap(); - let ref secp = new_secp(); - let skey = SecretKey::new(secp, &mut rng); + let keychain = new_keychain(); + let pubkey = keychain.derive_pubkey(1).unwrap(); let mut tx1 = tx2i1o(); - tx1.verify_sig(&secp).unwrap(); + tx1.verify_sig(keychain.secp()).unwrap(); let mut tx2 = tx1i1o(); - tx2.verify_sig(&secp).unwrap(); + tx2.verify_sig(keychain.secp()).unwrap(); - let b = Block::new(&BlockHeader::default(), vec![&mut tx1, &mut tx2], skey).unwrap(); - b.validate(&secp).unwrap(); + let b = Block::new(&BlockHeader::default(), vec![&mut tx1, &mut tx2], &keychain, pubkey).unwrap(); + b.validate(keychain.secp()).unwrap(); + } + + #[test] + pub fn test_verify_1i1o_sig() { + let keychain = new_keychain(); + let tx = tx1i1o(); + tx.verify_sig(keychain.secp()).unwrap(); + } + + #[test] + pub fn test_verify_2i1o_sig() { + let keychain = new_keychain(); + let tx = tx2i1o(); + tx.verify_sig(keychain.secp()).unwrap(); } // utility producing a transaction with 2 inputs and a single outputs pub fn tx2i1o() -> Transaction { - build::transaction(vec![ - input_rand(10), - input_rand(11), - output_rand(20), - with_fee(1), - ]).map(|(tx, _)| tx) - .unwrap() + let keychain = new_keychain(); + let pk1 = keychain.derive_pubkey(1).unwrap(); + let pk2 = keychain.derive_pubkey(2).unwrap(); + let pk3 = keychain.derive_pubkey(3).unwrap(); + + build::transaction( + vec![input(10, pk1), input(11, pk2), output(20, pk3), with_fee(1)], + &keychain, + ).map(|(tx, _)| tx).unwrap() } // utility producing a transaction with a single input and output pub fn tx1i1o() -> Transaction { - build::transaction(vec![input_rand(5), output_rand(4), with_fee(1)]) - .map(|(tx, _)| tx) - .unwrap() + let keychain = new_keychain(); + let pk1 = keychain.derive_pubkey(1).unwrap(); + let pk2 = keychain.derive_pubkey(2).unwrap(); + + build::transaction( + vec![input(5, pk1), output(4, pk2), with_fee(1)], + &keychain, + ).map(|(tx, _)| tx).unwrap() } } diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index 58fc1889f..2d8a52317 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -230,7 +230,7 @@ impl Transaction { // we originally converted the commitment to a pubkey here (commitment to zero) // and then passed the pubkey to secp.verify() // the secp api no longer allows us to do this so we have wrapped the complexity - // of generating a publick key from a commitment behind verify_from_commit + // of generating a public key from a commitment behind verify_from_commit secp.verify_from_commit(&msg, &sig, &rsum)?; Ok(TxKernel { diff --git a/core/src/lib.rs b/core/src/lib.rs index 6604027bf..c35ca9094 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -28,6 +28,8 @@ extern crate byteorder; extern crate num_bigint as bigint; extern crate rand; extern crate secp256k1zkp as secp; +extern crate grin_keychain as keychain; +extern crate grin_util as util; extern crate serde; #[macro_use] extern crate serde_derive; diff --git a/grin/Cargo.toml b/grin/Cargo.toml index 2c347a476..b492eb948 100644 --- a/grin/Cargo.toml +++ b/grin/Cargo.toml @@ -12,6 +12,7 @@ grin_store = { path = "../store" } grin_p2p = { path = "../p2p" } grin_pool = { path = "../pool" } grin_util = { path = "../util" } +grin_keychain = { path = "../keychain" } grin_wallet = { path = "../wallet" } grin_pow = { path = "../pow" } secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } diff --git a/grin/src/lib.rs b/grin/src/lib.rs index ae4783216..8b958975c 100644 --- a/grin/src/lib.rs +++ b/grin/src/lib.rs @@ -44,6 +44,7 @@ extern crate grin_p2p as p2p; extern crate grin_pool as pool; extern crate grin_store as store; extern crate grin_util as util; +extern crate grin_keychain as keychain; extern crate grin_wallet as wallet; extern crate grin_pow as pow; extern crate secp256k1zkp as secp; diff --git a/grin/src/miner.rs b/grin/src/miner.rs index 11ad92224..35c2a3ed4 100644 --- a/grin/src/miner.rs +++ b/grin/src/miner.rs @@ -42,6 +42,7 @@ use chain; use secp; use pool; use util; +use keychain::Keychain; use wallet::{CbAmount, WalletReceiveRequest, CbData}; use pow::plugin::PluginMiner; @@ -548,10 +549,9 @@ impl Miner { fn get_coinbase(&self) -> (core::Output, core::TxKernel) { if self.config.burn_reward { - let mut rng = rand::OsRng::new().unwrap(); - let secp_inst = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); - let skey = secp::key::SecretKey::new(&secp_inst, &mut rng); - core::Block::reward_output(skey, &secp_inst).unwrap() + let keychain = Keychain::from_random_seed().unwrap(); + let pubkey = keychain.derive_pubkey(1).unwrap(); + core::Block::reward_output(&keychain, pubkey).unwrap() } else { let url = format!( "{}/v1/receive/coinbase", diff --git a/grin/tests/framework.rs b/grin/tests/framework.rs index 9d6705e3c..77c85d7fd 100644 --- a/grin/tests/framework.rs +++ b/grin/tests/framework.rs @@ -18,6 +18,7 @@ extern crate grin_p2p as p2p; extern crate grin_chain as chain; extern crate grin_api as api; extern crate grin_wallet as wallet; +extern crate grin_keychain as keychain; extern crate grin_pow as pow; extern crate secp256k1zkp as secp; @@ -38,7 +39,8 @@ use tokio_core::reactor; use tokio_timer::Timer; use secp::Secp256k1; - +// TODO - why does this need self here? Missing something somewhere. +use self::keychain::Keychain; use wallet::WalletConfig; @@ -271,10 +273,9 @@ impl LocalServerContainer { let seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes()); - let s = Secp256k1::new(); - let key = wallet::ExtendedKey::from_seed(&s, seed.as_bytes()).expect( - "Error deriving extended key from seed.", - ); + // TODO - just use from_random_seed here? + let keychain = + Keychain::from_seed(seed.as_bytes()).expect("Error initializing keychain from seed"); println!( "Starting the Grin wallet receiving daemon on {} ", @@ -292,7 +293,7 @@ impl LocalServerContainer { api_server.register_endpoint( "/receive".to_string(), wallet::WalletReceiver { - key: key, + keychain: keychain, config: wallet_config, }, ); diff --git a/keychain/Cargo.toml b/keychain/Cargo.toml new file mode 100644 index 000000000..4f5f40da5 --- /dev/null +++ b/keychain/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "grin_keychain" +version = "0.1.0" +authors = ["Antioch Peverell"] + +[dependencies] +byteorder = "1" +blake2-rfc = "~0.2.17" +rand = "~0.3" +serde = "~1.0.8" +serde_derive = "~1.0.8" +grin_util = { path = "../util" } +secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } diff --git a/keychain/rustfmt.toml b/keychain/rustfmt.toml new file mode 100644 index 000000000..e26e77f1d --- /dev/null +++ b/keychain/rustfmt.toml @@ -0,0 +1,3 @@ +hard_tabs = true +wrap_comments = true +write_mode = "Overwrite" diff --git a/keychain/src/blind.rs b/keychain/src/blind.rs new file mode 100644 index 000000000..0eeb778d3 --- /dev/null +++ b/keychain/src/blind.rs @@ -0,0 +1,81 @@ +// Copyright 2017 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. +/// Encapsulate a secret key for the blind_sum operation + + +use secp::{self, Secp256k1}; +use extkey::Identifier; +use keychain::Error; + +#[derive(Clone, Debug)] +pub struct BlindingFactor(secp::key::SecretKey); + +impl BlindingFactor { + pub fn new(secret_key: secp::key::SecretKey) -> BlindingFactor { + BlindingFactor(secret_key) + } + + pub fn secret_key(&self) -> secp::key::SecretKey { + self.0 + } + + pub fn from_slice(secp: &Secp256k1, data: &[u8]) -> Result { + Ok(BlindingFactor( + secp::key::SecretKey::from_slice(&secp, data)?, + )) + } +} + +/// Accumulator to compute the sum of blinding factors. Keeps track of each +/// factor as well as the "sign" with which they should be combined. +pub struct BlindSum { + pub positive_pubkeys: Vec, + pub negative_pubkeys: Vec, + pub positive_blinding_factors: Vec, + pub negative_blinding_factors: Vec, +} + +impl BlindSum { + /// Creates a new blinding factor sum. + pub fn new() -> BlindSum { + BlindSum { + positive_pubkeys: vec![], + negative_pubkeys: vec![], + positive_blinding_factors: vec![], + negative_blinding_factors: vec![], + } + } + + pub fn add_pubkey(mut self, pubkey: Identifier) -> BlindSum { + self.positive_pubkeys.push(pubkey); + self + } + + pub fn sub_pubkey(mut self, pubkey: Identifier) -> BlindSum { + self.negative_pubkeys.push(pubkey); + self + } + + /// Adds the provided key to the sum of blinding factors. + pub fn add_blinding_factor(mut self, blind: BlindingFactor) -> BlindSum { + self.positive_blinding_factors.push(blind); + self + } + + /// Subtractss the provided key to the sum of blinding factors. + pub fn sub_blinding_factor(mut self, blind: BlindingFactor) -> BlindSum { + self.negative_blinding_factors.push(blind); + self + } +} diff --git a/wallet/src/extkey.rs b/keychain/src/extkey.rs similarity index 87% rename from wallet/src/extkey.rs rename to keychain/src/extkey.rs index 032548fae..4205723e3 100644 --- a/wallet/src/extkey.rs +++ b/keychain/src/extkey.rs @@ -1,4 +1,4 @@ -// Copyright 2016 The Grin Developers +// Copyright 2017 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. @@ -12,18 +12,14 @@ // 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 std::cmp::min; -use util; - use byteorder::{ByteOrder, BigEndian}; use blake2::blake2b::blake2b; use secp::Secp256k1; use secp::key::SecretKey; +use util; /// An ExtKey error #[derive(Copy, PartialEq, Eq, Clone, Debug)] @@ -79,7 +75,6 @@ impl fmt::Display for Fingerprint { } } - #[derive(Serialize, Deserialize, Clone)] pub struct Identifier([u8; 20]); @@ -165,9 +160,6 @@ impl ExtendedKey { _ => return Err(Error::InvalidSeedSize), } - // let mut derived: [u8; 64] = [0; 64]; - // hmac.raw_result(&mut derived); - let derived = blake2b(64, b"Mimble seed", seed); let mut chaincode: [u8; 32] = [0; 32]; @@ -207,9 +199,8 @@ impl ExtendedKey { let mut secret_key = SecretKey::from_slice(&secp, &derived.as_bytes()[0..32]) .expect("Error deriving key"); - secret_key.add_assign(secp, &self.key).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]; @@ -242,26 +233,18 @@ mod test { let s = Secp256k1::new(); let seed = from_hex("000102030405060708090a0b0c0d0e0f"); let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap(); - let sec = from_hex( - "c3f5ae520f474b390a637de4669c84d0ed9bbc21742577fac930834d3c3083dd", - ); + let sec = + from_hex("c3f5ae520f474b390a637de4669c84d0ed9bbc21742577fac930834d3c3083dd"); let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap(); - let chaincode = from_hex( - "e7298e68452b0c6d54837670896e1aee76b118075150d90d4ee416ece106ae72", - ); + let chaincode = + from_hex("e7298e68452b0c6d54837670896e1aee76b118075150d90d4ee416ece106ae72"); let identifier = from_hex("942b6c0bd43bdcb24f3edfe7fadbc77054ecc4f2"); let fingerprint = from_hex("942b6c0b"); let depth = 0; let n_child = 0; assert_eq!(extk.key, secret_key); - assert_eq!( - extk.identifier(), - Identifier::from_bytes(identifier.as_slice()) - ); - assert_eq!( - extk.fingerprint, - Fingerprint::from_bytes(fingerprint.as_slice()) - ); + assert_eq!(extk.identifier(), Identifier::from_bytes(identifier.as_slice())); + assert_eq!(extk.fingerprint, Fingerprint::from_bytes(fingerprint.as_slice())); assert_eq!( extk.identifier().fingerprint(), Fingerprint::from_bytes(fingerprint.as_slice()) @@ -278,27 +261,19 @@ mod test { let seed = from_hex("000102030405060708090a0b0c0d0e0f"); let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap(); let derived = extk.derive(&s, 0).unwrap(); - let sec = from_hex( - "d75f70beb2bd3b56f9b064087934bdedee98e4b5aae6280c58b4eff38847888f", - ); + let sec = + from_hex("d75f70beb2bd3b56f9b064087934bdedee98e4b5aae6280c58b4eff38847888f"); let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap(); - let chaincode = from_hex( - "243cb881e1549e714db31d23af45540b13ad07941f64a786bbf3313b4de1df52", - ); + let chaincode = + from_hex("243cb881e1549e714db31d23af45540b13ad07941f64a786bbf3313b4de1df52"); let fingerprint = from_hex("942b6c0b"); let identifier = from_hex("8b011f14345f3f0071e85f6eec116de1e575ea10"); let identifier_fingerprint = from_hex("8b011f14"); let depth = 1; let n_child = 0; assert_eq!(derived.key, secret_key); - assert_eq!( - derived.identifier(), - Identifier::from_bytes(identifier.as_slice()) - ); - assert_eq!( - derived.fingerprint, - Fingerprint::from_bytes(fingerprint.as_slice()) - ); + assert_eq!(derived.identifier(), Identifier::from_bytes(identifier.as_slice())); + assert_eq!(derived.fingerprint, Fingerprint::from_bytes(fingerprint.as_slice())); assert_eq!( derived.identifier().fingerprint(), Fingerprint::from_bytes(identifier_fingerprint.as_slice()) diff --git a/keychain/src/keychain.rs b/keychain/src/keychain.rs new file mode 100644 index 000000000..722ab39ca --- /dev/null +++ b/keychain/src/keychain.rs @@ -0,0 +1,196 @@ +// Copyright 2017 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 rand::{thread_rng, Rng}; + +use secp; +use secp::{Message, Secp256k1, Signature}; +use secp::key::SecretKey; +use secp::pedersen::{Commitment, RangeProof}; +use blake2; + +use blind::{BlindingFactor, BlindSum}; +use extkey::{self, Fingerprint, Identifier}; + + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum Error { + ExtendedKey(extkey::Error), + Secp(secp::Error), + KeyDerivation(String), +} + +impl From for Error { + fn from(e: secp::Error) -> Error { + Error::Secp(e) + } +} + +impl From for Error { + fn from(e: extkey::Error) -> Error { + Error::ExtendedKey(e) + } +} + + +#[derive(Clone, Debug)] +pub struct Keychain { + secp: Secp256k1, + extkey: extkey::ExtendedKey, +} + +impl Keychain { + pub fn fingerprint(self) -> Fingerprint { + self.extkey.fingerprint.clone() + } + + pub fn from_seed(seed: &[u8]) -> Result { + let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); + let extkey = extkey::ExtendedKey::from_seed(&secp, seed)?; + let keychain = Keychain { + secp: secp, + extkey: extkey, + }; + Ok(keychain) + } + + /// For testing - probably not a good idea to use outside of tests. + pub fn from_random_seed() -> Result { + let seed: String = thread_rng().gen_ascii_chars().take(16).collect(); + let seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes()); + Keychain::from_seed(seed.as_bytes()) + } + + pub fn derive_pubkey(&self, derivation: u32) -> Result { + let extkey = self.extkey.derive(&self.secp, derivation)?; + Ok(extkey.identifier()) + } + + // TODO - this is a work in progress + // TODO - smarter lookups - can we cache key_id/fingerprint -> derivation + // number somehow? + fn derived_key(&self, pubkey: &Identifier) -> Result { + for i in 1..10000 { + let extkey = self.extkey.derive(&self.secp, i)?; + if extkey.identifier() == *pubkey { + return Ok(extkey.key); + } + } + Err(Error::KeyDerivation("cannot find one...".to_string())) + } + + pub fn commit(&self, amount: u64, pubkey: &Identifier) -> Result { + let skey = self.derived_key(pubkey)?; + let commit = self.secp.commit(amount, skey)?; + Ok(commit) + } + + pub fn switch_commit(&self, pubkey: &Identifier) -> Result { + let skey = self.derived_key(pubkey)?; + let commit = self.secp.switch_commit(skey)?; + Ok(commit) + } + + pub fn range_proof( + &self, + amount: u64, + pubkey: &Identifier, + commit: Commitment, + ) -> Result { + let skey = self.derived_key(pubkey)?; + let nonce = self.secp.nonce(); + let range_proof = self.secp.range_proof(0, amount, skey, commit, nonce); + Ok(range_proof) + } + + pub fn blind_sum(&self, blind_sum: &BlindSum) -> Result { + let mut pos_keys: Vec = blind_sum + .positive_pubkeys + .iter() + .filter_map(|k| self.derived_key(&k).ok()) + .collect(); + println!("pos_keys - {}", pos_keys.len()); + + let mut neg_keys: Vec = blind_sum + .negative_pubkeys + .iter() + .filter_map(|k| self.derived_key(&k).ok()) + .collect(); + println!("neg_keys - {}", neg_keys.len()); + + pos_keys.extend(&blind_sum + .positive_blinding_factors + .iter() + .map(|b| b.secret_key()) + .collect::>()); + + neg_keys.extend(&blind_sum + .negative_blinding_factors + .iter() + .map(|b| b.secret_key()) + .collect::>()); + + println!("pos_keys all - {}", pos_keys.len()); + println!("neg_keys all - {}", neg_keys.len()); + + let blinding = self.secp.blind_sum(pos_keys, neg_keys)?; + Ok(BlindingFactor::new(blinding)) + } + + pub fn sign(&self, msg: &Message, pubkey: &Identifier) -> Result { + let skey = self.derived_key(pubkey)?; + let sig = self.secp.sign(msg, &skey)?; + Ok(sig) + } + + pub fn sign_with_blinding( + &self, + msg: &Message, + blinding: &BlindingFactor, + ) -> Result { + let sig = self.secp.sign(msg, &blinding.secret_key())?; + Ok(sig) + } + + pub fn secp(&self) -> &Secp256k1 { + &self.secp + } +} + +#[cfg(test)] +mod test { + use keychain::Keychain; + use secp; + + #[test] + fn test_key_derivation() { + let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); + + let keychain = Keychain::from_random_seed().unwrap(); + + // use the keychain to derive a "pubkey" based on the underlying seed + let pubkey = keychain.derive_pubkey(1).unwrap(); + + let msg_bytes = [0; 32]; + let msg = secp::Message::from_slice(&msg_bytes[..]).unwrap(); + + // now create a zero commitment using the key on the keychain associated with + // the pubkey + let commit = keychain.commit(0, &pubkey).unwrap(); + + // now check we can use our key to verify a signature from this zero commitment + let sig = keychain.sign(&msg, &pubkey).unwrap(); + secp.verify_from_commit(&msg, &sig, &commit).unwrap(); + } +} diff --git a/keychain/src/lib.rs b/keychain/src/lib.rs new file mode 100644 index 000000000..aee79894f --- /dev/null +++ b/keychain/src/lib.rs @@ -0,0 +1,32 @@ +// Copyright 2017 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 key holder functionalities provided by Grin. + +extern crate blake2_rfc as blake2; +extern crate byteorder; +extern crate rand; +extern crate secp256k1zkp as secp; +extern crate grin_util as util; +extern crate serde; +#[macro_use] +extern crate serde_derive; + +mod blind; +mod extkey; + +pub use blind::{BlindSum, BlindingFactor}; +pub use extkey::{Identifier, Fingerprint, ExtendedKey}; +pub mod keychain; +pub use keychain::{Error, Keychain}; diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index a47a4a8e8..7c6acb60b 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -27,4 +27,3 @@ grin_util = { path = "../util" } [dev-dependencies] env_logger = "^0.3" -secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } diff --git a/pool/Cargo.toml b/pool/Cargo.toml index ad0a98d18..bebc7efe5 100644 --- a/pool/Cargo.toml +++ b/pool/Cargo.toml @@ -4,12 +4,12 @@ version = "0.1.0" authors = ["Grin Authors "] [dependencies] +blake2-rfc = "~0.2.17" grin_core = { path = "../core" } +grin_keychain = { path = "../keychain" } grin_store = { path = "../store" } grin_p2p = { path = "../p2p" } secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } time = "^0.1" rand = "0.3" log = "0.3" - -[dev-dependencies] diff --git a/pool/src/graph.rs b/pool/src/graph.rs index 4e2587f1a..d915515cf 100644 --- a/pool/src/graph.rs +++ b/pool/src/graph.rs @@ -277,7 +277,6 @@ mod tests { output_commit, ); - let mut test_graph = DirectedGraph::empty(); test_graph.add_entry(test_pool_entry, vec![incoming_edge_1]); @@ -287,11 +286,9 @@ mod tests { assert_eq!(test_graph.edges.len(), 1); } - -} - -/// For testing/debugging: a random tx hash -pub fn random_hash() -> core::hash::Hash { - let hash_bytes: [u8; 32] = rand::random(); - core::hash::Hash(hash_bytes) + /// For testing/debugging: a random tx hash + fn random_hash() -> core::hash::Hash { + let hash_bytes: [u8; 32] = rand::random(); + core::hash::Hash(hash_bytes) + } } diff --git a/pool/src/lib.rs b/pool/src/lib.rs index 406626b47..1addcfbb7 100644 --- a/pool/src/lib.rs +++ b/pool/src/lib.rs @@ -29,8 +29,9 @@ mod pool; extern crate time; extern crate rand; extern crate log; - +extern crate blake2_rfc as blake2; extern crate grin_core as core; +extern crate grin_keychain as keychain; extern crate secp256k1zkp as secp; pub use pool::TransactionPool; diff --git a/pool/src/pool.rs b/pool/src/pool.rs index c77604ea1..fa30d30d0 100644 --- a/pool/src/pool.rs +++ b/pool/src/pool.rs @@ -557,25 +557,26 @@ where mod tests { use super::*; use types::*; - use secp::{Secp256k1, ContextFlag, constants}; - use secp::key; use core::core::build; use blockchain::{DummyChain, DummyChainImpl, DummyUtxoSet}; + use keychain::Keychain; use std::sync::{Arc, RwLock}; + use blake2; macro_rules! expect_output_parent { - ($pool:expr, $expected:pat, $( $output:expr ),+ ) => { - $( - match $pool.search_for_best_output(&test_output($output).commitment()) { - $expected => {}, - x => panic!( - "Unexpected result from output search for {:?}, got {:?}", - $output, - x), - }; - )* - } - } + ($pool:expr, $expected:pat, $( $output:expr ),+ ) => { + $( + match $pool.search_for_best_output(&test_output($output).commitment()) { + $expected => {}, + x => panic!( + "Unexpected result from output search for {:?}, got {:?}", + $output, + x, + ), + }; + )* + } + } #[test] /// A basic test; add a pair of transactions to the pool. @@ -627,16 +628,10 @@ mod tests { { let read_pool = pool.read().unwrap(); assert_eq!(read_pool.total_size(), 2); - - expect_output_parent!(read_pool, - Parent::PoolTransaction{tx_ref: _}, 12); - expect_output_parent!(read_pool, - Parent::AlreadySpent{other_tx: _}, 11, 5); - expect_output_parent!(read_pool, - Parent::BlockTransaction{output: _}, 8); - expect_output_parent!(read_pool, - Parent::Unknown, 20); - + expect_output_parent!(read_pool, Parent::PoolTransaction{tx_ref: _}, 12); + expect_output_parent!(read_pool, Parent::AlreadySpent{other_tx: _}, 11, 5); + expect_output_parent!(read_pool, Parent::BlockTransaction{output: _}, 8); + expect_output_parent!(read_pool, Parent::Unknown, 20); } } @@ -721,13 +716,9 @@ mod tests { Err(x) => { match x { PoolError::AlreadyInPool => {} - _ => { - panic!("Unexpected error when adding already in pool tx: {:?}", - x) - } + _ => panic!("Unexpected error when adding already in pool tx: {:?}", x), }; } - }; assert_eq!(write_pool.total_size(), 1); @@ -868,10 +859,18 @@ mod tests { // Note: There are some ordering constraints that must be followed here // until orphans is 100% implemented. Once the orphans process has // stabilized, we can mix these up to exercise that path a bit. - let mut txs_to_add = vec![block_transaction, conflict_transaction, - valid_transaction, block_child, pool_child, conflict_child, - conflict_valid_child, valid_child_conflict, valid_child_valid, - mixed_child]; + let mut txs_to_add = vec![ + block_transaction, + conflict_transaction, + valid_transaction, + block_child, + pool_child, + conflict_child, + conflict_valid_child, + valid_child_conflict, + valid_child_valid, + mixed_child, + ]; let expected_pool_size = txs_to_add.len(); @@ -882,8 +881,7 @@ mod tests { assert_eq!(write_pool.total_size(), 0); for tx in txs_to_add.drain(..) { - assert!(write_pool.add_to_memory_pool(test_source(), - tx).is_ok()); + assert!(write_pool.add_to_memory_pool(test_source(), tx).is_ok()); } assert_eq!(write_pool.total_size(), expected_pool_size); @@ -898,13 +896,16 @@ mod tests { let block_tx_3 = test_transaction(vec![8], vec![4,3]); // - Output conflict w/ 8 let block_tx_4 = test_transaction(vec![40], vec![9]); - let block_transactions = vec![&block_tx_1, &block_tx_2, &block_tx_3, - &block_tx_4]; + let block_transactions = vec![&block_tx_1, &block_tx_2, &block_tx_3, &block_tx_4]; + + let keychain = Keychain::from_random_seed().unwrap(); + let pubkey = keychain.derive_pubkey(1).unwrap(); let block = block::Block::new( &block::BlockHeader::default(), block_transactions, - key::ONE_KEY, + &keychain, + pubkey, ).unwrap(); chain_ref.apply_block(&block); @@ -934,16 +935,13 @@ mod tests { expect_output_parent!(read_pool, Parent::BlockTransaction{output: _}, 9, 3); // We should have spent blockchain outputs at 4 and 7 - expect_output_parent!(read_pool, - Parent::AlreadySpent{other_tx: _}, 4, 7); + expect_output_parent!(read_pool, Parent::AlreadySpent{other_tx: _}, 4, 7); // We should have spent pool references at 15 - expect_output_parent!(read_pool, - Parent::AlreadySpent{other_tx: _}, 15); + expect_output_parent!(read_pool, Parent::AlreadySpent{other_tx: _}, 15); // We should have unspent pool references at 1, 13, 14 - expect_output_parent!(read_pool, - Parent::PoolTransaction{tx_ref: _}, 1, 13, 14); + expect_output_parent!(read_pool, Parent::PoolTransaction{tx_ref: _}, 1, 13, 14); // References internal to the block should be unknown expect_output_parent!(read_pool, Parent::Unknown, 8); @@ -982,16 +980,11 @@ mod tests { let mut write_pool = pool.write().unwrap(); assert_eq!(write_pool.total_size(), 0); - assert!(write_pool.add_to_memory_pool(test_source(), - root_tx_1).is_ok()); - assert!(write_pool.add_to_memory_pool(test_source(), - root_tx_2).is_ok()); - assert!(write_pool.add_to_memory_pool(test_source(), - root_tx_3).is_ok()); - assert!(write_pool.add_to_memory_pool(test_source(), - child_tx_1).is_ok()); - assert!(write_pool.add_to_memory_pool(test_source(), - child_tx_2).is_ok()); + assert!(write_pool.add_to_memory_pool(test_source(), root_tx_1).is_ok()); + assert!(write_pool.add_to_memory_pool(test_source(), root_tx_2).is_ok()); + assert!(write_pool.add_to_memory_pool(test_source(), root_tx_3).is_ok()); + assert!(write_pool.add_to_memory_pool(test_source(), child_tx_1).is_ok()); + assert!(write_pool.add_to_memory_pool(test_source(), child_tx_2).is_ok()); assert_eq!(write_pool.total_size(), 5); } @@ -1008,7 +1001,10 @@ mod tests { // prepare_mineable_transactions to return mut refs let block_txs: Vec = txs.drain(..).map(|x| *x).collect(); let tx_refs = block_txs.iter().collect(); - block = block::Block::new(&block::BlockHeader::default(), tx_refs, key::ONE_KEY) + + let keychain = Keychain::from_random_seed().unwrap(); + let pubkey = keychain.derive_pubkey(1).unwrap(); + block = block::Block::new(&block::BlockHeader::default(), tx_refs, &keychain, pubkey) .unwrap(); } @@ -1024,11 +1020,8 @@ mod tests { assert_eq!(evicted_transactions.unwrap().len(), 3); assert_eq!(write_pool.total_size(), 2); } - - } - fn test_setup(dummy_chain: &Arc) -> TransactionPool { TransactionPool { transactions: HashMap::new(), @@ -1050,6 +1043,8 @@ mod tests { input_values: Vec, output_values: Vec, ) -> transaction::Transaction { + let keychain = keychain_for_tests(); + let fees: i64 = input_values.iter().sum::() as i64 - output_values.iter().sum::() as i64; assert!(fees >= 0); @@ -1057,62 +1052,52 @@ mod tests { let mut tx_elements = Vec::new(); for input_value in input_values { - tx_elements.push(build::input(input_value, test_key(input_value))); + let pubkey = keychain.derive_pubkey(input_value as u32).unwrap(); + tx_elements.push(build::input(input_value, pubkey)); } for output_value in output_values { - tx_elements.push(build::output(output_value, test_key(output_value))); + let pubkey = keychain.derive_pubkey(output_value as u32).unwrap(); + tx_elements.push(build::output(output_value, pubkey)); } tx_elements.push(build::with_fee(fees as u64)); - let (tx, _) = build::transaction(tx_elements).unwrap(); + let (tx, _) = build::transaction(tx_elements, &keychain).unwrap(); tx } /// Deterministically generate an output defined by our test scheme fn test_output(value: u64) -> transaction::Output { - let ec = Secp256k1::with_caps(ContextFlag::Commit); - let output_key = test_key(value); - let output_commitment = ec.commit(value, output_key).unwrap(); + let keychain = keychain_for_tests(); + let pubkey = keychain.derive_pubkey(value as u32).unwrap(); + let commit = keychain.commit(value, &pubkey).unwrap(); + let proof = keychain.range_proof(value, &pubkey, commit).unwrap(); + transaction::Output { features: transaction::DEFAULT_OUTPUT, - commit: output_commitment, - proof: ec.range_proof(0, value, output_key, output_commitment, ec.nonce()), + commit: commit, + proof: proof, } } /// Deterministically generate a coinbase output defined by our test scheme fn test_coinbase_output(value: u64) -> transaction::Output { - let ec = Secp256k1::with_caps(ContextFlag::Commit); - let output_key = test_key(value); - let output_commitment = ec.commit(value, output_key).unwrap(); + let keychain = keychain_for_tests(); + let pubkey = keychain.derive_pubkey(value as u32).unwrap(); + let commit = keychain.commit(value, &pubkey).unwrap(); + let proof = keychain.range_proof(value, &pubkey, commit).unwrap(); + transaction::Output { features: transaction::COINBASE_OUTPUT, - commit: output_commitment, - proof: ec.range_proof(0, value, output_key, output_commitment, ec.nonce()), + commit: commit, + proof: proof, } } - /// Makes a SecretKey from a single u64 - fn test_key(value: u64) -> key::SecretKey { - let ec = Secp256k1::with_caps(ContextFlag::Commit); - // SecretKey takes a SECRET_KEY_SIZE slice of u8. - assert!(constants::SECRET_KEY_SIZE > 8); - - // (SECRET_KEY_SIZE - 8) zeros, followed by value as a big-endian byte - // sequence - let mut key_slice = vec![0;constants::SECRET_KEY_SIZE - 8]; - - key_slice.push((value >> 56) as u8); - key_slice.push((value >> 48) as u8); - key_slice.push((value >> 40) as u8); - key_slice.push((value >> 32) as u8); - key_slice.push((value >> 24) as u8); - key_slice.push((value >> 16) as u8); - key_slice.push((value >> 8) as u8); - key_slice.push(value as u8); - - key::SecretKey::from_slice(&ec, &key_slice).unwrap() + fn keychain_for_tests() -> Keychain { + let seed = "pool_tests"; + let seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes()); + Keychain::from_seed(seed.as_bytes()).unwrap() } /// A generic TxSource representing a test diff --git a/pow/Cargo.toml b/pow/Cargo.toml index d48628a23..dbfdd10b7 100644 --- a/pow/Cargo.toml +++ b/pow/Cargo.toml @@ -25,4 +25,3 @@ tag="grin_integration_12" [dev_dependencies] grin_chain = { path = "../chain"} -secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } diff --git a/src/bin/grin.rs b/src/bin/grin.rs index a986ef21f..9ceece06f 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -26,9 +26,9 @@ extern crate blake2_rfc as blake2; extern crate grin_api as api; extern crate grin_grin as grin; extern crate grin_wallet as wallet; +extern crate grin_keychain as keychain; extern crate grin_config as config; extern crate grin_core as core; -extern crate secp256k1zkp as secp; use std::thread; use std::io::Read; @@ -38,11 +38,10 @@ use std::time::Duration; use clap::{Arg, App, SubCommand, ArgMatches}; use daemonize::Daemonize; -use secp::Secp256k1; - use config::GlobalConfig; use wallet::WalletConfig; use core::global; +use keychain::Keychain; fn start_from_config_file(mut global_config: GlobalConfig) { info!( @@ -314,13 +313,10 @@ fn wallet_command(wallet_args: &ArgMatches) { // TODO do something closer to BIP39, eazy solution right now let seed = blake2::blake2b::blake2b(32, &[], hd_seed.as_bytes()); - - let s = Secp256k1::new(); - let key = wallet::ExtendedKey::from_seed(&s, seed.as_bytes()).expect( - "Error deriving extended key from seed.", + let keychain = Keychain::from_seed(seed.as_bytes()).expect( + "Failed to initialize keychain from the provided seed.", ); - let mut wallet_config = WalletConfig::default(); if let Some(port) = wallet_args.value_of("port") { let default_ip = "127.0.0.1"; @@ -344,7 +340,7 @@ fn wallet_command(wallet_args: &ArgMatches) { file.read_to_string(&mut contents).expect( "Unable to read transaction file.", ); - wallet::receive_json_tx(&wallet_config, &key, contents.as_str()).unwrap(); + wallet::receive_json_tx(&wallet_config, &keychain, contents.as_str()).unwrap(); } else { info!( "Starting the Grin wallet receiving daemon at {}...", @@ -354,7 +350,7 @@ fn wallet_command(wallet_args: &ArgMatches) { apis.register_endpoint( "/receive".to_string(), wallet::WalletReceiver { - key: key, + keychain: keychain, config: wallet_config.clone(), }, ); @@ -373,10 +369,10 @@ fn wallet_command(wallet_args: &ArgMatches) { if let Some(d) = send_args.value_of("dest") { dest = d; } - wallet::issue_send_tx(&wallet_config, &key, amount, dest.to_string()).unwrap(); + wallet::issue_send_tx(&wallet_config, &keychain, amount, dest.to_string()).unwrap(); } ("info", Some(_)) => { - wallet::show_info(&wallet_config, &key); + wallet::show_info(&wallet_config, &keychain); } _ => panic!("Unknown wallet command, use 'grin help wallet' for details"), } diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 4c96b0d06..85b431419 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "grin_wallet" version = "0.1.0" -authors = ["Ignotus Peverell ", "Laurent Meunier "] +authors = [ + "Ignotus Peverell ", + "Laurent Meunier ", + "Antioch Peverell", +] [dependencies] @@ -15,5 +19,6 @@ serde_json = "~1.0.2" grin_api = { path = "../api" } grin_core = { path = "../core" } +grin_keychain = { path = "../keychain" } grin_util = { path = "../util" } secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } diff --git a/wallet/rustfmt.toml b/wallet/rustfmt.toml new file mode 100644 index 000000000..e26e77f1d --- /dev/null +++ b/wallet/rustfmt.toml @@ -0,0 +1,3 @@ +hard_tabs = true +wrap_comments = true +write_mode = "Overwrite" diff --git a/wallet/src/checker.rs b/wallet/src/checker.rs index 46b9c79da..93d29b107 100644 --- a/wallet/src/checker.rs +++ b/wallet/src/checker.rs @@ -16,9 +16,8 @@ //! the wallet storage and update them. use api; -use extkey::ExtendedKey; -use secp::{self, pedersen}; use types::*; +use keychain::Keychain; use util; @@ -41,9 +40,11 @@ fn refresh_output(out: &mut OutputData, api_out: Option, tip: &api: /// Goes through the list of outputs that haven't been spent yet and check /// with a node whether their status has changed. -pub fn refresh_outputs(config: &WalletConfig, ext_key: &ExtendedKey) -> Result<(), Error> { - let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); - let tip = get_tip(config)?; +pub fn refresh_outputs( + config: &WalletConfig, + keychain: &Keychain, +) -> Result<(), Error>{ + let tip = get_tip_from_node(config)?; WalletData::with_wallet(&config.data_file_dir, |wallet_data| { // check each output that's not spent @@ -52,12 +53,8 @@ pub fn refresh_outputs(config: &WalletConfig, ext_key: &ExtendedKey) -> Result<( }) { - // figure out the commitment // TODO check the pool for unconfirmed - let key = ext_key.derive(&secp, out.n_child).unwrap(); - let commitment = secp.commit(out.value, key.key).unwrap(); - - match get_output_by_commitment(config, commitment) { + match get_output_from_node(config, keychain, out.value, out.n_child) { Ok(api_out) => refresh_output(&mut out, api_out, &tip), Err(_) => { // TODO find error with connection and return @@ -69,17 +66,21 @@ pub fn refresh_outputs(config: &WalletConfig, ext_key: &ExtendedKey) -> Result<( }) } -fn get_tip(config: &WalletConfig) -> Result { +fn get_tip_from_node(config: &WalletConfig) -> Result { let url = format!("{}/v1/chain/1", config.check_node_api_http_addr); api::client::get::(url.as_str()).map_err(|e| Error::Node(e)) } -// queries a reachable node for a given output, checking whether it's been -// confirmed -fn get_output_by_commitment( +// queries a reachable node for a given output, checking whether it's been confirmed +fn get_output_from_node( config: &WalletConfig, - commit: pedersen::Commitment, + keychain: &Keychain, + amount: u64, + derivation: u32, ) -> Result, Error> { + let pubkey = keychain.derive_pubkey(derivation)?; + let commit = keychain.commit(amount, &pubkey)?; + let url = format!( "{}/v1/chain/utxo/{}", config.check_node_api_http_addr, diff --git a/wallet/src/info.rs b/wallet/src/info.rs index 2cfc8c211..f17afb4bf 100644 --- a/wallet/src/info.rs +++ b/wallet/src/info.rs @@ -12,14 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use secp; use checker; -use extkey::ExtendedKey; +use keychain::Keychain; use types::{WalletConfig, WalletData}; -pub fn show_info(config: &WalletConfig, ext_key: &ExtendedKey) { - let _ = checker::refresh_outputs(&config, ext_key); - let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); +pub fn show_info( + config: &WalletConfig, + keychain: &Keychain, +) { + let fingerprint = keychain.clone().fingerprint(); + let _ = checker::refresh_outputs(&config, &keychain); // operate within a lock on wallet data let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| { @@ -27,15 +29,15 @@ pub fn show_info(config: &WalletConfig, ext_key: &ExtendedKey) { println!("Outputs - "); println!("fingerprint, n_child, height, lock_height, status, value"); println!("----------------------------------"); - for out in &mut wallet_data.outputs.iter().filter(|o| { - o.fingerprint == ext_key.fingerprint - }) + for out in &mut wallet_data.outputs + .iter() + .filter(|out| out.fingerprint == fingerprint) { - let key = ext_key.derive(&secp, out.n_child).unwrap(); + let pubkey = keychain.derive_pubkey(out.n_child).unwrap(); println!( - "{}, {}, {}, {}, {}, {}", - key.identifier().fingerprint(), + "{}, {}, {}, {}, {:?}, {}", + pubkey.fingerprint(), out.n_child, out.height, out.lock_height, diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index aeb25d912..fc1c1e65d 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -26,17 +26,16 @@ extern crate serde_json; extern crate grin_api as api; extern crate grin_core as core; +extern crate grin_keychain as keychain; extern crate grin_util as util; extern crate secp256k1zkp as secp; mod checker; -mod extkey; mod info; mod receiver; mod sender; mod types; -pub use extkey::ExtendedKey; pub use info::show_info; pub use receiver::{WalletReceiver, receive_json_tx}; pub use sender::issue_send_tx; diff --git a/wallet/src/receiver.rs b/wallet/src/receiver.rs index 17690ae09..d09f0c5b9 100644 --- a/wallet/src/receiver.rs +++ b/wallet/src/receiver.rs @@ -49,16 +49,13 @@ //! 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; -use secp::key::SecretKey; use core::core::{Block, Transaction, TxKernel, Output, build}; use core::ser; use api::{self, ApiEndpoint, Operation, ApiResult}; -use extkey::ExtendedKey; use types::*; use util; +use keychain::{BlindingFactor, Keychain}; /// Dummy wrapper for the hex-encoded serialized transaction. #[derive(Serialize, Deserialize)] @@ -71,11 +68,11 @@ struct TxWrapper { /// network. pub fn receive_json_tx( config: &WalletConfig, - ext_key: &ExtendedKey, + keychain: &Keychain, partial_tx_str: &str, ) -> Result<(), Error> { - let (amount, blinding, partial_tx) = partial_tx_from_json(partial_tx_str)?; - let final_tx = receive_transaction(&config, ext_key, amount, blinding, partial_tx)?; + let (amount, blinding, partial_tx) = partial_tx_from_json(keychain, partial_tx_str)?; + let final_tx = receive_transaction(config, keychain, amount, blinding, partial_tx)?; let tx_hex = util::to_hex(ser::ser_vec(&final_tx).unwrap()); let url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str()); @@ -88,7 +85,7 @@ pub fn receive_json_tx( /// wallet REST API as well as some of the command-line operations. #[derive(Clone)] pub struct WalletReceiver { - pub key: ExtendedKey, + pub keychain: Keychain, pub config: WalletConfig, } @@ -114,19 +111,22 @@ impl ApiEndpoint for WalletReceiver { if cb_amount.amount == 0 { return Err(api::Error::Argument(format!("Zero amount not allowed."))); } - let (out, kern) = receive_coinbase( - &self.config, - &self.key, - cb_amount.amount, - ).map_err(|e| { - api::Error::Internal(format!("Error building coinbase: {:?}", e)) - })?; - let out_bin = ser::ser_vec(&out).map_err(|e| { - api::Error::Internal(format!("Error serializing output: {:?}", e)) - })?; - let kern_bin = ser::ser_vec(&kern).map_err(|e| { - api::Error::Internal(format!("Error serializing kernel: {:?}", e)) - })?; + let (out, kern) = + receive_coinbase( + &self.config, + &self.keychain, + cb_amount.amount, + ).map_err(|e| { + api::Error::Internal(format!("Error building coinbase: {:?}", e)) + })?; + let out_bin = + ser::ser_vec(&out).map_err(|e| { + api::Error::Internal(format!("Error serializing output: {:?}", e)) + })?; + let kern_bin = + ser::ser_vec(&kern).map_err(|e| { + api::Error::Internal(format!("Error serializing kernel: {:?}", e)) + })?; Ok(CbData { output: util::to_hex(out_bin), kernel: util::to_hex(kern_bin), @@ -141,15 +141,11 @@ impl ApiEndpoint for WalletReceiver { match input { WalletReceiveRequest::PartialTransaction(partial_tx_str) => { debug!("Operation {} with transaction {}", op, &partial_tx_str); - receive_json_tx(&self.config, &self.key, &partial_tx_str) - .map_err(|e| { - api::Error::Internal( - format!("Error processing partial transaction: {:?}", e), - ) - }) - .unwrap(); + receive_json_tx(&self.config, &self.keychain, &partial_tx_str).map_err(|e| { + api::Error::Internal(format!("Error processing partial transaction: {:?}", e)) + }).unwrap(); - // TODO: Return emptiness for now, should be a proper enum return type + //TODO: Return emptiness for now, should be a proper enum return type Ok(CbData { output: String::from(""), kernel: String::from(""), @@ -168,52 +164,47 @@ impl ApiEndpoint for WalletReceiver { /// Build a coinbase output and the corresponding kernel fn receive_coinbase( config: &WalletConfig, - ext_key: &ExtendedKey, + keychain: &Keychain, amount: u64, ) -> Result<(Output, TxKernel), Error> { - let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); + let fingerprint = keychain.clone().fingerprint(); // operate within a lock on wallet data WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - - // derive a new private for the reward - 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 derivation = wallet_data.next_child(fingerprint.clone()); + let pubkey = keychain.derive_pubkey(derivation)?; // 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, + fingerprint: fingerprint.clone(), + n_child: derivation, value: amount, status: OutputStatus::Unconfirmed, height: 0, lock_height: 0, }); - debug!( - "Using child {} for a new coinbase output.", - coinbase_key.n_child - ); + debug!("Received coinbase and built output - {}, {}, {}", + fingerprint.clone(), pubkey.fingerprint(), derivation); - Block::reward_output(coinbase_key.key, &secp).map_err(&From::from) + let result = Block::reward_output(&keychain, pubkey)?; + Ok(result) })? } /// Builds a full transaction from the partial one sent to us for transfer fn receive_transaction( config: &WalletConfig, - ext_key: &ExtendedKey, + keychain: &Keychain, amount: u64, - blinding: SecretKey, + blinding: BlindingFactor, partial: Transaction, ) -> Result { - - let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); + let fingerprint = keychain.clone().fingerprint(); // operate within a lock on wallet data WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - - 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 derivation = wallet_data.next_child(fingerprint.clone()); + let pubkey = keychain.derive_pubkey(derivation)?; // TODO - replace with real fee calculation // TODO - note we are not enforcing this in consensus anywhere yet @@ -223,28 +214,24 @@ fn receive_transaction( let (tx_final, _) = build::transaction(vec![ build::initial_tx(partial), build::with_excess(blinding), - build::output(out_amount, out_key.key), + build::output(out_amount, pubkey.clone()), build::with_fee(fee_amount), - ])?; + ], keychain)?; - // make sure the resulting transaction is valid (could have been lied to - // on excess) - tx_final.validate(&secp)?; + // make sure the resulting transaction is valid (could have been lied to on excess) + tx_final.validate(&keychain.secp())?; // track the new output and return the finalized transaction to broadcast wallet_data.append_output(OutputData { - fingerprint: out_key.fingerprint, - n_child: out_key.n_child, + fingerprint: fingerprint.clone(), + n_child: derivation, value: out_amount, status: OutputStatus::Unconfirmed, height: 0, lock_height: 0, }); - - debug!( - "Using child {} for a new transaction output.", - out_key.n_child - ); + debug!("Received txn and built output - {}, {}, {}", + fingerprint.clone(), pubkey.fingerprint(), derivation); Ok(tx_final) })? diff --git a/wallet/src/sender.rs b/wallet/src/sender.rs index a10e99ef6..5f97df5aa 100644 --- a/wallet/src/sender.rs +++ b/wallet/src/sender.rs @@ -12,30 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::From; -use secp; -use secp::key::SecretKey; - +use api; use checker; use core::core::{Transaction, build}; -use extkey::ExtendedKey; +use keychain::{BlindingFactor, Keychain}; use types::*; -use api; - /// Issue a new transaction to the provided sender by spending some of our /// wallet /// UTXOs. The destination can be "stdout" (for command line) or a URL to the /// recipients wallet receiver (to be implemented). pub fn issue_send_tx( config: &WalletConfig, - ext_key: &ExtendedKey, + keychain: &Keychain, amount: u64, dest: String, ) -> Result<(), Error> { - let _ = checker::refresh_outputs(&config, ext_key); + let _ = checker::refresh_outputs(config, keychain); - let (tx, blind_sum) = build_send_tx(config, ext_key, amount)?; + let (tx, blind_sum) = build_send_tx(config, keychain, amount)?; let json_tx = partial_tx_to_json(amount, blind_sum, tx); if dest == "stdout" { @@ -44,10 +39,10 @@ pub fn issue_send_tx( let url = format!("{}/v1/receive/receive_json_tx", &dest); debug!("Posting partial transaction to {}", url); let request = WalletReceiveRequest::PartialTransaction(json_tx); - let _: CbData = api::client::post(url.as_str(), &request).expect(&format!( - "Wallet receiver at {} unreachable, could not send transaction. Is it running?", - url - )); + let _: CbData = api::client::post(url.as_str(), &request) + .expect(&format!("Wallet receiver at {} unreachable, could not send transaction. Is it running?", url)); + } else { + panic!("dest not in expected format: {}", dest); } Ok(()) } @@ -57,52 +52,51 @@ pub fn issue_send_tx( /// selecting outputs to spend and building the change. fn build_send_tx( config: &WalletConfig, - ext_key: &ExtendedKey, + keychain: &Keychain, amount: u64, -) -> Result<(Transaction, SecretKey), Error> { - // first, rebuild the private key from the seed - let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); +) -> Result<(Transaction, BlindingFactor), Error> { + let fingerprint = keychain.clone().fingerprint(); // operate within a lock on wallet data WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - // second, check from our local wallet data for outputs to spend - let (coins, change) = wallet_data.select(&ext_key.fingerprint, amount); + // select some suitable outputs to spend from our local wallet + let (coins, change) = wallet_data.select(fingerprint.clone(), 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 + // build inputs using the appropriate derived pubkeys 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)); + let pubkey = keychain.derive_pubkey(coin.n_child)?; + parts.push(build::input(coin.value, pubkey)); } - // 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)); + // derive an additional pubkey for change and build the change output + let change_derivation = wallet_data.next_child(fingerprint.clone()); + let change_key = keychain.derive_pubkey(change_derivation)?; + parts.push(build::output(change as u64, change_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, + fingerprint: fingerprint.clone(), + n_child: change_derivation, value: change as u64, status: OutputStatus::Unconfirmed, height: 0, lock_height: 0, }); - for coin in coins { - wallet_data.lock_output(&coin); + + for coin in &coins { + wallet_data.lock_output(coin); } - build::transaction(parts).map_err(&From::from) + let result = build::transaction(parts, &keychain)?; + Ok(result) })? } @@ -110,42 +104,25 @@ fn build_send_tx( mod test { use core::core::build::{input, output, transaction}; use types::{OutputData, OutputStatus}; - - use secp::Secp256k1; - use super::ExtendedKey; - use util; - - fn from_hex(hex_str: &str) -> Vec { - util::from_hex(hex_str.to_string()).unwrap() - } + use keychain::Keychain; #[test] // demonstrate that input.commitment == referenced output.commitment - // based on the wallet extended key and the coin being spent + // based on the public key and amount begin spent fn output_commitment_equals_input_commitment_on_spend() { - let secp = Secp256k1::new(); - let seed = from_hex("000102030405060708090a0b0c0d0e0f"); + let keychain = Keychain::from_random_seed().unwrap(); + let pk1 = keychain.derive_pubkey(1).unwrap(); - let ext_key = ExtendedKey::from_seed(&secp, &seed.as_slice()).unwrap(); + let (tx, _) = transaction( + vec![output(105, pk1.clone())], + &keychain, + ).unwrap(); - let out_key = ext_key.derive(&secp, 1).unwrap(); + let (tx2, _) = transaction( + vec![input(105, pk1.clone())], + &keychain, + ).unwrap(); - let coin = OutputData { - fingerprint: out_key.fingerprint, - n_child: out_key.n_child, - value: 5, - status: OutputStatus::Unconfirmed, - height: 0, - lock_height: 0, - }; - - let (tx, _) = transaction(vec![output(coin.value, out_key.key)]).unwrap(); - - let in_key = ext_key.derive(&secp, coin.n_child).unwrap(); - - let (tx2, _) = transaction(vec![input(coin.value, in_key.key)]).unwrap(); - - assert_eq!(in_key.key, out_key.key); assert_eq!(tx.outputs[0].commitment(), tx2.inputs[0].commitment()); } } diff --git a/wallet/src/types.rs b/wallet/src/types.rs index 2c3859f7e..61bf73a4f 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -19,16 +19,13 @@ use std::io::Write; use std::path::Path; use std::path::MAIN_SEPARATOR; - use serde_json; - use secp; -use secp::key::SecretKey; use api; use core::core::Transaction; use core::ser; -use extkey; +use keychain; use util; const DAT_FILE: &'static str = "wallet.dat"; @@ -38,8 +35,8 @@ const LOCK_FILE: &'static str = "wallet.lock"; #[derive(Debug)] pub enum Error { NotEnoughFunds(u64), - Crypto(secp::Error), - Key(extkey::Error), + Keychain(keychain::Error), + Secp(secp::Error), WalletData(String), /// An error in the format of the JSON structures exchanged by the wallet Format(String), @@ -47,34 +44,24 @@ pub enum Error { Node(api::Error), } -impl From for Error { - fn from(e: secp::Error) -> Error { - Error::Crypto(e) - } +impl From for Error { + fn from(e: keychain::Error) -> Error { Error::Keychain(e) } } -impl From for Error { - fn from(e: extkey::Error) -> Error { - Error::Key(e) - } +impl From for Error { + fn from(e: secp::Error) -> Error { Error::Secp(e) } } impl From for Error { - fn from(e: serde_json::Error) -> Error { - Error::Format(e.to_string()) - } + fn from(e: serde_json::Error) -> Error { Error::Format(e.to_string()) } } impl From for Error { - fn from(_: num::ParseIntError) -> Error { - Error::Format("Invalid hex".to_string()) - } + fn from(_: num::ParseIntError) -> Error { Error::Format("Invalid hex".to_string()) } } impl From for Error { - fn from(e: api::Error) -> Error { - Error::Node(e) - } + fn from(e: api::Error) -> Error { Error::Node(e) } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -132,7 +119,7 @@ impl fmt::Display for OutputStatus { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct OutputData { /// Private key fingerprint (in case the wallet tracks multiple) - pub fingerprint: extkey::Fingerprint, + pub fingerprint: keychain::Fingerprint, /// How many derivations down from the root key pub n_child: u32, /// Value of the output, necessary to rebuild the commitment @@ -283,13 +270,18 @@ impl WalletData { /// Select a subset of unspent outputs to spend in a transaction /// transferring /// the provided amount. - pub fn select(&self, fingerprint: &extkey::Fingerprint, amount: u64) -> (Vec, i64) { + pub fn select( + &self, + fingerprint: keychain::Fingerprint, + 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 + + // TODO very naive impl for now - definitely better coin selection // algos available 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()); input_total += out.value; if input_total >= amount { @@ -297,14 +289,15 @@ impl WalletData { } } } + // TODO - clean up our handling of i64 vs u64 so we are consistent (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: &extkey::Fingerprint) -> u32 { + pub fn next_child(&self, fingerprint: keychain::Fingerprint) -> u32 { let mut max_n = 0; 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; } } @@ -323,10 +316,14 @@ struct JSONPartialTx { /// 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 { +pub fn partial_tx_to_json( + receive_amount: u64, + blind_sum: keychain::BlindingFactor, + tx: Transaction, +) -> String { let partial_tx = JSONPartialTx { amount: receive_amount, - blind_sum: util::to_hex(blind_sum.as_ref().to_vec()), + blind_sum: util::to_hex(blind_sum.secret_key().as_ref().to_vec()), tx: util::to_hex(ser::ser_vec(&tx).unwrap()), }; serde_json::to_string_pretty(&partial_tx).unwrap() @@ -334,12 +331,18 @@ pub fn partial_tx_to_json(receive_amount: u64, blind_sum: SecretKey, tx: Transac /// Reads a partial transaction encoded as JSON into the amount, sum of blinding /// factors and the transaction itself. -pub fn partial_tx_from_json(json_str: &str) -> Result<(u64, SecretKey, Transaction), Error> { +pub fn partial_tx_from_json( + keychain: &keychain::Keychain, + json_str: &str, +) -> Result<(u64, keychain::BlindingFactor, Transaction), Error> { let partial_tx: JSONPartialTx = serde_json::from_str(json_str)?; - let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); let blind_bin = util::from_hex(partial_tx.blind_sum)?; - let blinding = SecretKey::from_slice(&secp, &blind_bin[..])?; + + // TODO - turn some data into a blinding factor here somehow + // let blinding = SecretKey::from_slice(&secp, &blind_bin[..])?; + let blinding = keychain::BlindingFactor::from_slice(keychain.secp(), &blind_bin[..])?; + let tx_bin = util::from_hex(partial_tx.tx)?; let tx = ser::deserialize(&mut &tx_bin[..]).map_err(|_| { Error::Format(