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
This commit is contained in:
AntiochP 2017-10-02 20:02:31 -04:00 committed by Ignotus Peverell
parent 6b4f2a63da
commit 677d0a3a95
38 changed files with 887 additions and 649 deletions

3
.gitignore vendored
View file

@ -1,7 +1,6 @@
*.swp *.swp
.DS_Store .DS_Store
.grin .grin*
.grin2
node* node*
target target
Cargo.lock Cargo.lock

View file

@ -25,6 +25,8 @@ env:
- TEST_DIR=pool - TEST_DIR=pool
- TEST_DIR=pow - TEST_DIR=pow
- TEST_DIR=wallet - TEST_DIR=wallet
- TEST_DIR=util
- TEST_DIR=keychain
- RUST_TEST_THREADS=1 TEST_DIR=grin - RUST_TEST_THREADS=1 TEST_DIR=grin
script: cd $TEST_DIR && cargo test --verbose script: cd $TEST_DIR && cargo test --verbose

View file

@ -5,11 +5,12 @@ authors = ["Ignotus Peverell <igno.peverell@protonmail.com>"]
exclude = ["**/*.grin", "**/*.grin2"] exclude = ["**/*.grin", "**/*.grin2"]
[workspace] [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] [dependencies]
grin_api = { path = "./api" } grin_api = { path = "./api" }
grin_wallet = { path = "./wallet" } grin_wallet = { path = "./wallet" }
grin_keychain = { path = "./keychain" }
grin_grin = { path = "./grin" } grin_grin = { path = "./grin" }
grin_config = { path = "./config" } grin_config = { path = "./config" }
grin_core = { path = "./core" } grin_core = { path = "./core" }

View file

@ -13,6 +13,7 @@ serde_derive = "~1.0.8"
time = "^0.1" time = "^0.1"
grin_core = { path = "../core" } grin_core = { path = "../core" }
grin_keychain = { path = "../keychain" }
grin_store = { path = "../store" } grin_store = { path = "../store" }
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }

View file

@ -14,10 +14,10 @@
extern crate grin_core as core; extern crate grin_core as core;
extern crate grin_chain as chain; extern crate grin_chain as chain;
extern crate grin_keychain as keychain;
extern crate env_logger; extern crate env_logger;
extern crate time; extern crate time;
extern crate rand; extern crate rand;
extern crate secp256k1zkp as secp;
extern crate grin_pow as pow; extern crate grin_pow as pow;
use std::fs; use std::fs;
@ -33,6 +33,8 @@ use core::consensus;
use core::global; use core::global;
use core::global::MiningParameterMode; use core::global::MiningParameterMode;
use keychain::Keychain;
use pow::{types, cuckoo, MiningWorker}; use pow::{types, cuckoo, MiningWorker};
fn clean_output_dir(dir_name: &str) { fn clean_output_dir(dir_name: &str) {
@ -60,9 +62,9 @@ fn mine_empty_chain() {
let mut rng = OsRng::new().unwrap(); let mut rng = OsRng::new().unwrap();
let chain = setup(".grin"); let chain = setup(".grin");
// mine and add a few blocks let keychain = Keychain::from_random_seed().unwrap();
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
// mine and add a few blocks
let mut miner_config = types::MinerConfig { let mut miner_config = types::MinerConfig {
enable_mining: true, enable_mining: true,
burn_reward: true, burn_reward: true,
@ -77,8 +79,8 @@ fn mine_empty_chain() {
); );
for n in 1..4 { for n in 1..4 {
let prev = chain.head_header().unwrap(); let prev = chain.head_header().unwrap();
let reward_key = secp::key::SecretKey::new(&secp, &mut rng); let pk = keychain.derive_pubkey(n as u32).unwrap();
let mut b = core::core::Block::new(&prev, vec![], reward_key).unwrap(); let mut b = core::core::Block::new(&prev, vec![], &keychain, pk).unwrap();
b.header.timestamp = prev.timestamp + time::Duration::seconds(60); b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap(); 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 { fn prepare_block_nosum(prev: &BlockHeader, diff: u64) -> Block {
let mut rng = OsRng::new().unwrap(); let keychain = Keychain::from_random_seed().unwrap();
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); let pubkey = keychain.derive_pubkey(1).unwrap();
let reward_key = secp::key::SecretKey::new(&secp, &mut rng);
let mut b = core::core::Block::new(prev, vec![], reward_key).unwrap(); let mut b = core::core::Block::new(prev, vec![], &keychain, pubkey).unwrap();
b.header.timestamp = prev.timestamp + time::Duration::seconds(60); b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
b.header.total_difficulty = Difficulty::from_num(diff); b.header.total_difficulty = Difficulty::from_num(diff);
b b

View file

@ -15,15 +15,15 @@
extern crate env_logger; extern crate env_logger;
extern crate grin_chain as chain; extern crate grin_chain as chain;
extern crate grin_core as core; extern crate grin_core as core;
extern crate grin_keychain as keychain;
extern crate rand; extern crate rand;
extern crate secp256k1zkp as secp;
use std::fs; use std::fs;
use chain::ChainStore; use chain::ChainStore;
use core::core::hash::Hashed; use core::core::hash::Hashed;
use core::core::{Block, BlockHeader}; use core::core::{Block, BlockHeader};
use secp::key; use keychain::Keychain;
fn clean_output_dir(dir_name: &str) { fn clean_output_dir(dir_name: &str) {
let _ = fs::remove_dir_all(dir_name); let _ = fs::remove_dir_all(dir_name);
@ -34,9 +34,12 @@ fn test_various_store_indices() {
let _ = env_logger::init(); let _ = env_logger::init();
clean_output_dir(".grin"); 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 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 commit = block.outputs[0].commitment();
let block_hash = block.hash(); let block_hash = block.hash();

View file

@ -14,15 +14,14 @@
extern crate grin_core as core; extern crate grin_core as core;
extern crate grin_chain as chain; extern crate grin_chain as chain;
extern crate grin_keychain as keychain;
extern crate grin_pow as pow;
extern crate env_logger; extern crate env_logger;
extern crate time; extern crate time;
extern crate rand; extern crate rand;
extern crate secp256k1zkp as secp;
extern crate grin_pow as pow;
use std::fs; use std::fs;
use std::sync::Arc; use std::sync::Arc;
use rand::os::OsRng;
use chain::types::*; use chain::types::*;
use core::core::build; use core::core::build;
@ -31,6 +30,8 @@ use core::consensus;
use core::global; use core::global;
use core::global::MiningParameterMode; use core::global::MiningParameterMode;
use keychain::Keychain;
use pow::{types, cuckoo, MiningWorker}; use pow::{types, cuckoo, MiningWorker};
fn clean_output_dir(dir_name: &str) { fn clean_output_dir(dir_name: &str) {
@ -43,7 +44,6 @@ fn test_coinbase_maturity() {
clean_output_dir(".grin"); clean_output_dir(".grin");
global::set_mining_mode(MiningParameterMode::AutomatedTesting); global::set_mining_mode(MiningParameterMode::AutomatedTesting);
let mut rng = OsRng::new().unwrap();
let mut genesis_block = None; let mut genesis_block = None;
if !chain::Chain::chain_exists(".grin".to_string()) { if !chain::Chain::chain_exists(".grin".to_string()) {
genesis_block = pow::mine_genesis_block(None); genesis_block = pow::mine_genesis_block(None);
@ -55,8 +55,6 @@ fn test_coinbase_maturity() {
pow::verify_size, pow::verify_size,
).unwrap(); ).unwrap();
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
let mut miner_config = types::MinerConfig { let mut miner_config = types::MinerConfig {
enable_mining: true, enable_mining: true,
burn_reward: true, burn_reward: true,
@ -71,8 +69,14 @@ fn test_coinbase_maturity() {
); );
let prev = chain.head_header().unwrap(); 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); block.header.timestamp = prev.timestamp + time::Duration::seconds(60);
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap(); let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
@ -96,15 +100,17 @@ fn test_coinbase_maturity() {
let prev = chain.head_header().unwrap(); let prev = chain.head_header().unwrap();
let amount = consensus::REWARD; let amount = consensus::REWARD;
let (coinbase_txn, _) = build::transaction(vec![ let (coinbase_txn, _) = build::transaction(
build::input(amount, reward_key), vec![
build::output_rand(amount - 1), build::input(amount, pk1.clone()),
build::with_fee(1), build::output(amount - 1, pk2),
]).unwrap(); build::with_fee(1),
],
let reward_key = secp::key::SecretKey::new(&secp, &mut rng); &keychain,
let mut block = core::core::Block::new(&prev, vec![&coinbase_txn], reward_key).unwrap(); ).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); block.header.timestamp = prev.timestamp + time::Duration::seconds(60);
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap(); let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
@ -124,13 +130,15 @@ fn test_coinbase_maturity() {
_ => panic!("expected ImmatureCoinbase error here"), _ => panic!("expected ImmatureCoinbase error here"),
}; };
// mine 10 blocks so we increase the height sufficiently // mine enough blocks to increase the height sufficiently for
// coinbase will mature and be spendable in the block after these // coinbase to reach maturity and be spendable in the next block
for _ in 0..10 { for _ in 0..3 {
let prev = chain.head_header().unwrap(); let prev = chain.head_header().unwrap();
let reward_key = secp::key::SecretKey::new(&secp, &mut rng); let keychain = Keychain::from_random_seed().unwrap();
let mut block = core::core::Block::new(&prev, vec![], reward_key).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); block.header.timestamp = prev.timestamp + time::Duration::seconds(60);
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap(); let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
@ -149,8 +157,7 @@ fn test_coinbase_maturity() {
let prev = chain.head_header().unwrap(); 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], &keychain, pk4).unwrap();
let mut block = core::core::Block::new(&prev, vec![&coinbase_txn], reward_key).unwrap();
block.header.timestamp = prev.timestamp + time::Duration::seconds(60); block.header.timestamp = prev.timestamp + time::Duration::seconds(60);

View file

@ -15,3 +15,5 @@ serde_derive = "~1.0.8"
time = "^0.1" time = "^0.1"
lazy_static = "~0.2.8" lazy_static = "~0.2.8"
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }
grin_keychain = { path = "../keychain" }
grin_util = { path = "../util" }

View file

@ -16,7 +16,6 @@
use time; use time;
use secp::{self, Secp256k1}; use secp::{self, Secp256k1};
use secp::key::SecretKey;
use std::collections::HashSet; use std::collections::HashSet;
use core::Committed; use core::Committed;
@ -27,6 +26,8 @@ use core::hash::{Hash, Hashed, ZERO_HASH};
use core::target::Difficulty; use core::target::Difficulty;
use ser::{self, Readable, Reader, Writeable, Writer}; use ser::{self, Readable, Reader, Writeable, Writer};
use global; use global;
use keychain;
bitflags! { bitflags! {
/// Options for block validation /// Options for block validation
@ -243,13 +244,13 @@ impl Block {
pub fn new( pub fn new(
prev: &BlockHeader, prev: &BlockHeader,
txs: Vec<&Transaction>, txs: Vec<&Transaction>,
reward_key: SecretKey, keychain: &keychain::Keychain,
) -> Result<Block, secp::Error> { pubkey: keychain::Identifier,
) -> Result<Block, keychain::Error> {
let secp = Secp256k1::with_caps(secp::ContextFlag::Commit); let (reward_out, reward_proof) = Block::reward_output(keychain, pubkey)?;
let (reward_out, reward_proof) = try!(Block::reward_output(reward_key, &secp)); let block = Block::with_reward(prev, txs, reward_out, reward_proof)?;
Ok(block)
Block::with_reward(prev, txs, reward_out, reward_proof)
} }
/// Builds a new block ready to mine from the header of the previous 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 /// Builds the blinded output and related signature proof for the block
/// reward. /// reward.
pub fn reward_output( pub fn reward_output(
skey: secp::key::SecretKey, keychain: &keychain::Keychain,
secp: &Secp256k1, pubkey: keychain::Identifier,
) -> Result<(Output, TxKernel), secp::Error> { ) -> Result<(Output, TxKernel), keychain::Error> {
let msg = try!(secp::Message::from_slice( let secp = keychain.secp();
&[0; secp::constants::MESSAGE_SIZE],
)); let msg = secp::Message::from_slice(&[0; secp::constants::MESSAGE_SIZE])?;
let sig = try!(secp.sign(&msg, &skey)); let sig = keychain.sign(&msg, &pubkey)?;
let commit = secp.commit(REWARD, skey).unwrap(); let commit = keychain.commit(REWARD, &pubkey)?;
// let switch_commit = secp.switch_commit(skey).unwrap(); // let switch_commit = keychain.switch_commit(pubkey)?;
let nonce = secp.nonce();
let rproof = secp.range_proof(0, REWARD, skey, commit, nonce); let rproof = keychain.range_proof(REWARD, &pubkey, commit)?;
let output = Output { let output = Output {
features: COINBASE_OUTPUT, features: COINBASE_OUTPUT,
@ -497,29 +498,23 @@ impl Block {
mod test { mod test {
use super::*; use super::*;
use core::Transaction; 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 core::test::tx2i1o;
use keychain::{Identifier, Keychain};
use secp::{self, Secp256k1}; use secp;
use secp::key::SecretKey;
use rand::os::OsRng;
fn new_secp() -> Secp256k1 {
secp::Secp256k1::with_caps(secp::ContextFlag::Commit)
}
// utility to create a block without worrying about the key or previous // utility to create a block without worrying about the key or previous
// header // header
fn new_block(txs: Vec<&Transaction>, secp: &Secp256k1) -> Block { fn new_block(txs: Vec<&Transaction>, keychain: &Keychain) -> Block {
let mut rng = OsRng::new().unwrap(); let pubkey = keychain.derive_pubkey(1).unwrap();
let skey = SecretKey::new(secp, &mut rng); Block::new(&BlockHeader::default(), txs, keychain, pubkey).unwrap()
Block::new(&BlockHeader::default(), txs, skey).unwrap()
} }
// utility producing a transaction that spends an output with the provided // utility producing a transaction that spends an output with the provided
// value and blinding key // value and blinding key
fn txspend1i1o(v: u64, b: SecretKey) -> Transaction { fn txspend1i1o(v: u64, keychain: &Keychain, pk1: Identifier, pk2: Identifier) -> Transaction {
build::transaction(vec![input(v, b), output_rand(3), with_fee(1)]) build::transaction(vec![input(v, pk1), output(3, pk2), with_fee(1)], &keychain)
.map(|(tx, _)| tx) .map(|(tx, _)| tx)
.unwrap() .unwrap()
} }
@ -527,21 +522,25 @@ mod test {
#[test] #[test]
// builds a block with a tx spending another and check if merging occurred // builds a block with a tx spending another and check if merging occurred
fn compactable_block() { fn compactable_block() {
let mut rng = OsRng::new().unwrap(); let keychain = Keychain::from_random_seed().unwrap();
let ref secp = new_secp(); 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 mut btx1 = tx2i1o();
let skey = SecretKey::new(secp, &mut rng); let (mut btx2, _) = build::transaction(
let (mut btx2, _) = build::transaction(vec![input_rand(5), output(4, skey), with_fee(1)]) vec![input(5, pk1), output(4, pk2.clone()), with_fee(1)],
.unwrap(); &keychain,
).unwrap();
// spending tx2 // spending tx2 - reuse pk2
let mut btx3 = txspend1i1o(4, skey);
let b = new_block(vec![&mut btx1, &mut btx2, &mut btx3], secp); 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 // block should have been automatically compacted (including reward
// output) and should still be valid // output) and should still be valid
b.validate(&secp).unwrap(); b.validate(&keychain.secp()).unwrap();
assert_eq!(b.inputs.len(), 3); assert_eq!(b.inputs.len(), 3);
assert_eq!(b.outputs.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 // builds 2 different blocks with a tx spending another and check if merging
// occurs // occurs
fn mergeable_blocks() { fn mergeable_blocks() {
let mut rng = OsRng::new().unwrap(); let keychain = Keychain::from_random_seed().unwrap();
let ref secp = new_secp(); 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 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 btx2, _) = build::transaction(
let mut btx3 = txspend1i1o(4, skey); vec![input(5, pk1), output(4, pk2.clone()), with_fee(1)],
&keychain,
).unwrap();
let b1 = new_block(vec![&mut btx1, &mut btx2], secp); // spending tx2 - reuse pk2
b1.validate(&secp).unwrap(); let mut btx3 = txspend1i1o(4, &keychain, pk2.clone(), pk3);
let b2 = new_block(vec![&mut btx3], secp);
b2.validate(&secp).unwrap(); 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 // block should have been automatically compacted and should still be valid
let b3 = b1.merge(b2); let b3 = b1.merge(b2);
@ -574,8 +578,8 @@ mod test {
#[test] #[test]
fn empty_block_with_coinbase_is_valid() { fn empty_block_with_coinbase_is_valid() {
let ref secp = new_secp(); let keychain = Keychain::from_random_seed().unwrap();
let b = new_block(vec![], secp); let b = new_block(vec![], &keychain);
assert_eq!(b.inputs.len(), 0); assert_eq!(b.inputs.len(), 0);
assert_eq!(b.outputs.len(), 1); assert_eq!(b.outputs.len(), 1);
@ -597,7 +601,7 @@ mod test {
// the block should be valid here (single coinbase output with corresponding // the block should be valid here (single coinbase output with corresponding
// txn kernel) // txn kernel)
assert_eq!(b.validate(&secp), Ok(())); assert_eq!(b.validate(&keychain.secp()), Ok(()));
} }
#[test] #[test]
@ -605,44 +609,50 @@ mod test {
// invalidates the block and specifically it causes verify_coinbase to fail // invalidates the block and specifically it causes verify_coinbase to fail
// additionally verifying the merkle_inputs_outputs also fails // additionally verifying the merkle_inputs_outputs also fails
fn remove_coinbase_output_flag() { fn remove_coinbase_output_flag() {
let ref secp = new_secp(); let keychain = Keychain::from_random_seed().unwrap();
let mut b = new_block(vec![], secp); let mut b = new_block(vec![], &keychain);
assert!(b.outputs[0].features.contains(COINBASE_OUTPUT)); assert!(b.outputs[0].features.contains(COINBASE_OUTPUT));
b.outputs[0].features.remove(COINBASE_OUTPUT); b.outputs[0].features.remove(COINBASE_OUTPUT);
assert_eq!( assert_eq!(
b.verify_coinbase(&secp), b.verify_coinbase(&keychain.secp()),
Err(secp::Error::IncorrectCommitSum) 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]
// test that flipping the COINBASE_KERNEL flag on the kernel features // test that flipping the COINBASE_KERNEL flag on the kernel features
// invalidates the block and specifically it causes verify_coinbase to fail // invalidates the block and specifically it causes verify_coinbase to fail
fn remove_coinbase_kernel_flag() { fn remove_coinbase_kernel_flag() {
let ref secp = new_secp(); let keychain = Keychain::from_random_seed().unwrap();
let mut b = new_block(vec![], secp); let mut b = new_block(vec![], &keychain);
assert!(b.kernels[0].features.contains(COINBASE_KERNEL)); assert!(b.kernels[0].features.contains(COINBASE_KERNEL));
b.kernels[0].features.remove(COINBASE_KERNEL); b.kernels[0].features.remove(COINBASE_KERNEL);
assert_eq!( assert_eq!(
b.verify_coinbase(&secp), b.verify_coinbase(&keychain.secp()),
Err(secp::Error::IncorrectCommitSum) 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]
fn serialize_deserialize_block() { fn serialize_deserialize_block() {
let ref secp = new_secp(); let keychain = Keychain::from_random_seed().unwrap();
let b = new_block(vec![], secp); let b = new_block(vec![], &keychain);
let mut vec = Vec::new(); let mut vec = Vec::new();
ser::serialize(&mut vec, &b).expect("serialization failed"); ser::serialize(&mut vec, &b).expect("serialization failed");

View file

@ -26,59 +26,15 @@
//! with_fee(1)]) //! with_fee(1)])
use byteorder::{ByteOrder, BigEndian}; use byteorder::{ByteOrder, BigEndian};
use secp::{self, Secp256k1}; use secp;
use secp::key::SecretKey;
use rand::os::OsRng;
use core::{Transaction, Input, Output, DEFAULT_OUTPUT}; use core::{Transaction, Input, Output, DEFAULT_OUTPUT};
use keychain;
use keychain::{Keychain, BlindSum, BlindingFactor, Identifier};
/// Context information available to transaction combinators. /// Context information available to transaction combinators.
pub struct Context { pub struct Context<'a> {
secp: Secp256k1, keychain: &'a Keychain,
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<SecretKey>,
negative: Vec<SecretKey>,
}
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<SecretKey, secp::Error> {
secp.blind_sum(self.positive, self.negative)
}
} }
/// Function type returned by the transaction combinators. Transforms a /// 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 /// Adds an input with the provided value and blinding key to the transaction
/// being built. /// being built.
pub fn input(value: u64, blinding: SecretKey) -> Box<Append> { pub fn input(value: u64, pubkey: Identifier) -> Box<Append> {
Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) { Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) {
let commit = build.secp.commit(value, blinding).unwrap(); let commit = build.keychain.commit(value, &pubkey).unwrap();
(tx.with_input(Input(commit)), sum.sub(blinding)) (tx.with_input(Input(commit)), sum.sub_pubkey(pubkey.clone()))
})
}
/// 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<Append> {
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))
}) })
} }
/// Adds an output with the provided value and blinding key to the transaction /// Adds an output with the provided value and blinding key to the transaction
/// being built. /// being built.
pub fn output(value: u64, blinding: SecretKey) -> Box<Append> { pub fn output(value: u64, pubkey: Identifier) -> Box<Append> {
Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) { Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) {
let commit = build.secp.commit(value, blinding).unwrap(); let commit = build.keychain.commit(value, &pubkey).unwrap();
let nonce = build.secp.nonce(); let rproof = build.keychain.range_proof(value, &pubkey, commit).unwrap();
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),
)
})
}
/// Adds an output with the provided value and a randomly generated blinding (tx.with_output(Output {
/// key to the transaction being built. This has no real use in practical features: DEFAULT_OUTPUT,
/// applications but is very convenient for tests. commit: commit,
pub fn output_rand(value: u64) -> Box<Append> { proof: rproof,
Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) { }), sum.add_pubkey(pubkey.clone()))
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),
)
}) })
} }
@ -153,9 +75,9 @@ pub fn with_fee(fee: u64) -> Box<Append> {
/// Sets a known excess value on the transaction being built. Usually used in /// 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 /// combination with the initial_tx function when a new transaction is built
/// by adding to a pre-existing one. /// by adding to a pre-existing one.
pub fn with_excess(excess: SecretKey) -> Box<Append> { pub fn with_excess(excess: BlindingFactor) -> Box<Append> {
Box::new(move |_build, (tx, sum)| -> (Transaction, BlindSum) { 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<Append> {
/// let (tx2, _) = build::transaction(vec![initial_tx(tx1), with_excess(sum), /// let (tx2, _) = build::transaction(vec![initial_tx(tx1), with_excess(sum),
/// output_rand(2)]).unwrap(); /// output_rand(2)]).unwrap();
/// ///
pub fn transaction(elems: Vec<Box<Append>>) -> Result<(Transaction, SecretKey), secp::Error> { pub fn transaction(
let mut ctx = Context { elems: Vec<Box<Append>>,
secp: Secp256k1::with_caps(secp::ContextFlag::Commit), keychain: &keychain::Keychain,
rng: OsRng::new().unwrap(), ) -> Result<(Transaction, BlindingFactor), keychain::Error> {
}; let mut ctx = Context { keychain };
let (mut tx, sum) = elems.iter().fold( let (mut tx, sum) = elems.iter().fold(
(Transaction::empty(), BlindSum::new()), (Transaction::empty(), BlindSum::new()), |acc, elem| elem(&mut ctx, acc)
|acc, elem| elem(&mut ctx, acc),
); );
let blind_sum = ctx.keychain.blind_sum(&sum)?;
let blind_sum = sum.sum(&ctx.secp)?;
let msg = secp::Message::from_slice(&u64_to_32bytes(tx.fee))?; let msg = secp::Message::from_slice(&u64_to_32bytes(tx.fee))?;
let sig = ctx.secp.sign(&msg, &blind_sum)?; let sig = ctx.keychain.sign_with_blinding(&msg, &blind_sum)?;
tx.excess_sig = sig.serialize_der(&ctx.secp); tx.excess_sig = sig.serialize_der(&ctx.keychain.secp());
Ok((tx, blind_sum)) Ok((tx, blind_sum))
} }
@ -200,30 +119,37 @@ fn u64_to_32bytes(n: u64) -> [u8; 32] {
bytes bytes
} }
// Just a simple test, most exhaustive tests in the core mod.rs. // Just a simple test, most exhaustive tests in the core mod.rs.
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use secp::{self, key, Secp256k1};
#[test] #[test]
fn blind_simple_tx() { fn blind_simple_tx() {
let secp = Secp256k1::with_caps(secp::ContextFlag::Commit); let keychain = Keychain::from_random_seed().unwrap();
let (tx, _) = transaction(vec![ let pk1 = keychain.derive_pubkey(1).unwrap();
input_rand(10), let pk2 = keychain.derive_pubkey(2).unwrap();
input_rand(11), let pk3 = keychain.derive_pubkey(3).unwrap();
output_rand(20),
with_fee(1), let (tx, _) = transaction(
]).unwrap(); vec![input(10, pk1), input(11, pk2), output(20, pk3), with_fee(1)],
tx.verify_sig(&secp).unwrap(); &keychain,
).unwrap();
tx.verify_sig(&keychain.secp()).unwrap();
} }
#[test] #[test]
fn blind_simpler_tx() { fn blind_simpler_tx() {
let secp = Secp256k1::with_caps(secp::ContextFlag::Commit); let keychain = Keychain::from_random_seed().unwrap();
let (tx, _) = transaction(vec![input_rand(6), output(2, key::ONE_KEY), with_fee(4)]) let pk1 = keychain.derive_pubkey(1).unwrap();
.unwrap(); let pk2 = keychain.derive_pubkey(2).unwrap();
tx.verify_sig(&secp).unwrap();
let (tx, _) = transaction(
vec![input(6, pk1), output(2, pk2), with_fee(4)],
&keychain,
).unwrap();
tx.verify_sig(&keychain.secp()).unwrap();
} }
} }

View file

@ -31,10 +31,10 @@ use secp::pedersen::*;
pub use self::block::*; pub use self::block::*;
pub use self::transaction::*; pub use self::transaction::*;
use self::hash::{Hash, Hashed, ZERO_HASH}; use self::hash::Hashed;
use ser::{Writeable, Writer, Reader, Readable, Error}; use ser::{Writeable, Writer, Reader, Readable, Error};
use global; use global;
// use keychain;
/// Implemented by types that hold inputs and outputs including Pedersen /// Implemented by types that hold inputs and outputs including Pedersen
/// commitments. Handles the collection of the commitments as well as their /// commitments. Handles the collection of the commitments as well as their
@ -186,28 +186,22 @@ impl Writeable for Proof {
mod test { mod test {
use super::*; use super::*;
use core::hash::ZERO_HASH; use core::hash::ZERO_HASH;
use secp; use core::build::{input, output, with_fee, initial_tx, with_excess};
use secp::Secp256k1;
use secp::key::SecretKey;
use ser; use ser;
use rand::os::OsRng; use keychain;
use core::build::{self, input, output, input_rand, output_rand, with_fee, initial_tx, use keychain::{Keychain, BlindingFactor};
with_excess};
fn new_secp() -> Secp256k1 {
secp::Secp256k1::with_caps(secp::ContextFlag::Commit)
}
#[test] #[test]
#[should_panic(expected = "InvalidSecretKey")] #[should_panic(expected = "InvalidSecretKey")]
fn zero_commit() { fn test_zero_commit_fails() {
// a transaction whose commitment sums to zero shouldn't validate let keychain = Keychain::from_random_seed().unwrap();
let ref secp = new_secp(); let pk1 = keychain.derive_pubkey(1).unwrap();
let mut rng = OsRng::new().unwrap();
// blinding should fail as signing with a zero r*G shouldn't work // blinding should fail as signing with a zero r*G shouldn't work
let skey = SecretKey::new(secp, &mut rng); build::transaction(
build::transaction(vec![input(10, skey), output(1, skey), with_fee(9)]).unwrap(); vec![input(10, pk1.clone()), output(9, pk1.clone()), with_fee(1)],
&keychain,
).unwrap();
} }
#[test] #[test]
@ -250,12 +244,16 @@ mod test {
#[test] #[test]
fn hash_output() { fn hash_output() {
let (tx, _) = build::transaction(vec![ let keychain = Keychain::from_random_seed().unwrap();
input_rand(75), let pk1 = keychain.derive_pubkey(1).unwrap();
output_rand(42), let pk2 = keychain.derive_pubkey(2).unwrap();
output_rand(32), let pk3 = keychain.derive_pubkey(3).unwrap();
with_fee(1),
]).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(); let h = tx.outputs[0].hash();
assert!(h != ZERO_HASH); assert!(h != ZERO_HASH);
let h2 = tx.outputs[1].hash(); let h2 = tx.outputs[1].hash();
@ -264,14 +262,14 @@ mod test {
#[test] #[test]
fn blind_tx() { fn blind_tx() {
let ref secp = new_secp(); let keychain = Keychain::from_random_seed().unwrap();
let btx = tx2i1o(); 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 // checks that the range proof on our blind output is sufficiently hiding
let Output { proof, .. } = btx.outputs[0]; 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.min == 0);
assert!(info.max == u64::max_value()); assert!(info.max == u64::max_value());
} }
@ -290,20 +288,26 @@ mod test {
/// 2 inputs, 2 outputs transaction. /// 2 inputs, 2 outputs transaction.
#[test] #[test]
fn tx_build_exchange() { 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 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 // Alice gets 2 of her pre-existing outputs to send 5 coins to Bob, they
// become inputs in the new transaction // 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 // Alice builds her transaction, with change, which also produces the sum
// of blinding factors before they're obscured. // of blinding factors before they're obscured.
let (tx, sum) = build::transaction(vec![in1, in2, output_rand(1), with_fee(1)]) let (tx, sum) = build::transaction(
.unwrap(); vec![in1, in2, output(1, pk3), with_fee(1)],
&keychain,
).unwrap();
tx_alice = tx; tx_alice = tx;
blind_sum = sum; blind_sum = sum;
} }
@ -311,69 +315,91 @@ mod test {
// From now on, Bob only has the obscured transaction and the sum of // 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 // blinding factors. He adds his output, finalizes the transaction so it's
// ready for broadcast. // ready for broadcast.
let (tx_final, _) = build::transaction(vec![ let (tx_final, _) =
initial_tx(tx_alice), build::transaction(
with_excess(blind_sum), vec![initial_tx(tx_alice), with_excess(blind_sum), output(5, pk4)],
output_rand(5), &keychain,
]).unwrap(); ).unwrap();
tx_final.validate(&secp).unwrap(); tx_final.validate(&keychain.secp()).unwrap();
} }
#[test] #[test]
fn reward_empty_block() { fn reward_empty_block() {
let mut rng = OsRng::new().unwrap(); let keychain = new_keychain();
let ref secp = new_secp(); let pubkey = keychain.derive_pubkey(1).unwrap();
let skey = SecretKey::new(secp, &mut rng);
let b = Block::new(&BlockHeader::default(), vec![], skey).unwrap(); let b = Block::new(&BlockHeader::default(), vec![], &keychain, pubkey).unwrap();
b.compact().validate(&secp).unwrap(); b.compact().validate(&keychain.secp()).unwrap();
}
fn new_keychain() -> keychain::Keychain {
keychain::Keychain::from_random_seed().unwrap()
} }
#[test] #[test]
fn reward_with_tx_block() { fn reward_with_tx_block() {
let mut rng = OsRng::new().unwrap(); let keychain = new_keychain();
let ref secp = new_secp(); let pubkey = keychain.derive_pubkey(1).unwrap();
let skey = SecretKey::new(secp, &mut rng);
let mut tx1 = tx2i1o(); 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(); let b = Block::new(&BlockHeader::default(), vec![&mut tx1], &keychain, pubkey).unwrap();
b.compact().validate(&secp).unwrap(); b.compact().validate(keychain.secp()).unwrap();
} }
#[test] #[test]
fn simple_block() { fn simple_block() {
let mut rng = OsRng::new().unwrap(); let keychain = new_keychain();
let ref secp = new_secp(); let pubkey = keychain.derive_pubkey(1).unwrap();
let skey = SecretKey::new(secp, &mut rng);
let mut tx1 = tx2i1o(); let mut tx1 = tx2i1o();
tx1.verify_sig(&secp).unwrap(); tx1.verify_sig(keychain.secp()).unwrap();
let mut tx2 = tx1i1o(); 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(); let b = Block::new(&BlockHeader::default(), vec![&mut tx1, &mut tx2], &keychain, pubkey).unwrap();
b.validate(&secp).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 // utility producing a transaction with 2 inputs and a single outputs
pub fn tx2i1o() -> Transaction { pub fn tx2i1o() -> Transaction {
build::transaction(vec![ let keychain = new_keychain();
input_rand(10), let pk1 = keychain.derive_pubkey(1).unwrap();
input_rand(11), let pk2 = keychain.derive_pubkey(2).unwrap();
output_rand(20), let pk3 = keychain.derive_pubkey(3).unwrap();
with_fee(1),
]).map(|(tx, _)| tx) build::transaction(
.unwrap() 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 // utility producing a transaction with a single input and output
pub fn tx1i1o() -> Transaction { pub fn tx1i1o() -> Transaction {
build::transaction(vec![input_rand(5), output_rand(4), with_fee(1)]) let keychain = new_keychain();
.map(|(tx, _)| tx) let pk1 = keychain.derive_pubkey(1).unwrap();
.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()
} }
} }

View file

@ -230,7 +230,7 @@ impl Transaction {
// we originally converted the commitment to a pubkey here (commitment to zero) // we originally converted the commitment to a pubkey here (commitment to zero)
// and then passed the pubkey to secp.verify() // and then passed the pubkey to secp.verify()
// the secp api no longer allows us to do this so we have wrapped the complexity // 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)?; secp.verify_from_commit(&msg, &sig, &rsum)?;
Ok(TxKernel { Ok(TxKernel {

View file

@ -28,6 +28,8 @@ extern crate byteorder;
extern crate num_bigint as bigint; extern crate num_bigint as bigint;
extern crate rand; extern crate rand;
extern crate secp256k1zkp as secp; extern crate secp256k1zkp as secp;
extern crate grin_keychain as keychain;
extern crate grin_util as util;
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;

View file

@ -12,6 +12,7 @@ grin_store = { path = "../store" }
grin_p2p = { path = "../p2p" } grin_p2p = { path = "../p2p" }
grin_pool = { path = "../pool" } grin_pool = { path = "../pool" }
grin_util = { path = "../util" } grin_util = { path = "../util" }
grin_keychain = { path = "../keychain" }
grin_wallet = { path = "../wallet" } grin_wallet = { path = "../wallet" }
grin_pow = { path = "../pow" } grin_pow = { path = "../pow" }
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }

View file

@ -44,6 +44,7 @@ extern crate grin_p2p as p2p;
extern crate grin_pool as pool; extern crate grin_pool as pool;
extern crate grin_store as store; extern crate grin_store as store;
extern crate grin_util as util; extern crate grin_util as util;
extern crate grin_keychain as keychain;
extern crate grin_wallet as wallet; extern crate grin_wallet as wallet;
extern crate grin_pow as pow; extern crate grin_pow as pow;
extern crate secp256k1zkp as secp; extern crate secp256k1zkp as secp;

View file

@ -42,6 +42,7 @@ use chain;
use secp; use secp;
use pool; use pool;
use util; use util;
use keychain::Keychain;
use wallet::{CbAmount, WalletReceiveRequest, CbData}; use wallet::{CbAmount, WalletReceiveRequest, CbData};
use pow::plugin::PluginMiner; use pow::plugin::PluginMiner;
@ -548,10 +549,9 @@ impl Miner {
fn get_coinbase(&self) -> (core::Output, core::TxKernel) { fn get_coinbase(&self) -> (core::Output, core::TxKernel) {
if self.config.burn_reward { if self.config.burn_reward {
let mut rng = rand::OsRng::new().unwrap(); let keychain = Keychain::from_random_seed().unwrap();
let secp_inst = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); let pubkey = keychain.derive_pubkey(1).unwrap();
let skey = secp::key::SecretKey::new(&secp_inst, &mut rng); core::Block::reward_output(&keychain, pubkey).unwrap()
core::Block::reward_output(skey, &secp_inst).unwrap()
} else { } else {
let url = format!( let url = format!(
"{}/v1/receive/coinbase", "{}/v1/receive/coinbase",

View file

@ -18,6 +18,7 @@ extern crate grin_p2p as p2p;
extern crate grin_chain as chain; extern crate grin_chain as chain;
extern crate grin_api as api; extern crate grin_api as api;
extern crate grin_wallet as wallet; extern crate grin_wallet as wallet;
extern crate grin_keychain as keychain;
extern crate grin_pow as pow; extern crate grin_pow as pow;
extern crate secp256k1zkp as secp; extern crate secp256k1zkp as secp;
@ -38,7 +39,8 @@ use tokio_core::reactor;
use tokio_timer::Timer; use tokio_timer::Timer;
use secp::Secp256k1; use secp::Secp256k1;
// TODO - why does this need self here? Missing something somewhere.
use self::keychain::Keychain;
use wallet::WalletConfig; use wallet::WalletConfig;
@ -271,10 +273,9 @@ impl LocalServerContainer {
let seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes()); let seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes());
let s = Secp256k1::new(); // TODO - just use from_random_seed here?
let key = wallet::ExtendedKey::from_seed(&s, seed.as_bytes()).expect( let keychain =
"Error deriving extended key from seed.", Keychain::from_seed(seed.as_bytes()).expect("Error initializing keychain from seed");
);
println!( println!(
"Starting the Grin wallet receiving daemon on {} ", "Starting the Grin wallet receiving daemon on {} ",
@ -292,7 +293,7 @@ impl LocalServerContainer {
api_server.register_endpoint( api_server.register_endpoint(
"/receive".to_string(), "/receive".to_string(),
wallet::WalletReceiver { wallet::WalletReceiver {
key: key, keychain: keychain,
config: wallet_config, config: wallet_config,
}, },
); );

13
keychain/Cargo.toml Normal file
View file

@ -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" }

3
keychain/rustfmt.toml Normal file
View file

@ -0,0 +1,3 @@
hard_tabs = true
wrap_comments = true
write_mode = "Overwrite"

81
keychain/src/blind.rs Normal file
View file

@ -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<BlindingFactor, Error> {
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<Identifier>,
pub negative_pubkeys: Vec<Identifier>,
pub positive_blinding_factors: Vec<BlindingFactor>,
pub negative_blinding_factors: Vec<BlindingFactor>,
}
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
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2016 The Grin Developers // Copyright 2017 The Grin Developers
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with 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 // See the License for the specific language governing permissions and
// limitations under the License. // 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::{error, fmt};
use std::cmp::min; use std::cmp::min;
use util;
use byteorder::{ByteOrder, BigEndian}; use byteorder::{ByteOrder, BigEndian};
use blake2::blake2b::blake2b; use blake2::blake2b::blake2b;
use secp::Secp256k1; use secp::Secp256k1;
use secp::key::SecretKey; use secp::key::SecretKey;
use util;
/// An ExtKey error /// An ExtKey error
#[derive(Copy, PartialEq, Eq, Clone, Debug)] #[derive(Copy, PartialEq, Eq, Clone, Debug)]
@ -79,7 +75,6 @@ impl fmt::Display for Fingerprint {
} }
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct Identifier([u8; 20]); pub struct Identifier([u8; 20]);
@ -165,9 +160,6 @@ impl ExtendedKey {
_ => return Err(Error::InvalidSeedSize), _ => 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 derived = blake2b(64, b"Mimble seed", seed);
let mut chaincode: [u8; 32] = [0; 32]; 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]) let mut secret_key = SecretKey::from_slice(&secp, &derived.as_bytes()[0..32])
.expect("Error deriving key"); .expect("Error deriving key");
secret_key.add_assign(secp, &self.key).expect( secret_key.add_assign(secp, &self.key)
"Error deriving key", .expect("Error deriving key");
);
// TODO check if key != 0 ? // TODO check if key != 0 ?
let mut chain_code: [u8; 32] = [0; 32]; let mut chain_code: [u8; 32] = [0; 32];
@ -242,26 +233,18 @@ mod test {
let s = Secp256k1::new(); let s = Secp256k1::new();
let seed = from_hex("000102030405060708090a0b0c0d0e0f"); let seed = from_hex("000102030405060708090a0b0c0d0e0f");
let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap(); let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap();
let sec = from_hex( let sec =
"c3f5ae520f474b390a637de4669c84d0ed9bbc21742577fac930834d3c3083dd", from_hex("c3f5ae520f474b390a637de4669c84d0ed9bbc21742577fac930834d3c3083dd");
);
let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap(); let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap();
let chaincode = from_hex( let chaincode =
"e7298e68452b0c6d54837670896e1aee76b118075150d90d4ee416ece106ae72", from_hex("e7298e68452b0c6d54837670896e1aee76b118075150d90d4ee416ece106ae72");
);
let identifier = from_hex("942b6c0bd43bdcb24f3edfe7fadbc77054ecc4f2"); let identifier = from_hex("942b6c0bd43bdcb24f3edfe7fadbc77054ecc4f2");
let fingerprint = from_hex("942b6c0b"); let fingerprint = from_hex("942b6c0b");
let depth = 0; let depth = 0;
let n_child = 0; let n_child = 0;
assert_eq!(extk.key, secret_key); assert_eq!(extk.key, secret_key);
assert_eq!( assert_eq!(extk.identifier(), Identifier::from_bytes(identifier.as_slice()));
extk.identifier(), assert_eq!(extk.fingerprint, Fingerprint::from_bytes(fingerprint.as_slice()));
Identifier::from_bytes(identifier.as_slice())
);
assert_eq!(
extk.fingerprint,
Fingerprint::from_bytes(fingerprint.as_slice())
);
assert_eq!( assert_eq!(
extk.identifier().fingerprint(), extk.identifier().fingerprint(),
Fingerprint::from_bytes(fingerprint.as_slice()) Fingerprint::from_bytes(fingerprint.as_slice())
@ -278,27 +261,19 @@ mod test {
let seed = from_hex("000102030405060708090a0b0c0d0e0f"); let seed = from_hex("000102030405060708090a0b0c0d0e0f");
let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap(); let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap();
let derived = extk.derive(&s, 0).unwrap(); let derived = extk.derive(&s, 0).unwrap();
let sec = from_hex( let sec =
"d75f70beb2bd3b56f9b064087934bdedee98e4b5aae6280c58b4eff38847888f", from_hex("d75f70beb2bd3b56f9b064087934bdedee98e4b5aae6280c58b4eff38847888f");
);
let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap(); let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap();
let chaincode = from_hex( let chaincode =
"243cb881e1549e714db31d23af45540b13ad07941f64a786bbf3313b4de1df52", from_hex("243cb881e1549e714db31d23af45540b13ad07941f64a786bbf3313b4de1df52");
);
let fingerprint = from_hex("942b6c0b"); let fingerprint = from_hex("942b6c0b");
let identifier = from_hex("8b011f14345f3f0071e85f6eec116de1e575ea10"); let identifier = from_hex("8b011f14345f3f0071e85f6eec116de1e575ea10");
let identifier_fingerprint = from_hex("8b011f14"); let identifier_fingerprint = from_hex("8b011f14");
let depth = 1; let depth = 1;
let n_child = 0; let n_child = 0;
assert_eq!(derived.key, secret_key); assert_eq!(derived.key, secret_key);
assert_eq!( assert_eq!(derived.identifier(), Identifier::from_bytes(identifier.as_slice()));
derived.identifier(), assert_eq!(derived.fingerprint, Fingerprint::from_bytes(fingerprint.as_slice()));
Identifier::from_bytes(identifier.as_slice())
);
assert_eq!(
derived.fingerprint,
Fingerprint::from_bytes(fingerprint.as_slice())
);
assert_eq!( assert_eq!(
derived.identifier().fingerprint(), derived.identifier().fingerprint(),
Fingerprint::from_bytes(identifier_fingerprint.as_slice()) Fingerprint::from_bytes(identifier_fingerprint.as_slice())

196
keychain/src/keychain.rs Normal file
View file

@ -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<secp::Error> for Error {
fn from(e: secp::Error) -> Error {
Error::Secp(e)
}
}
impl From<extkey::Error> 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<Keychain, Error> {
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<Keychain, Error> {
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<Identifier, Error> {
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<SecretKey, Error> {
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<Commitment, Error> {
let skey = self.derived_key(pubkey)?;
let commit = self.secp.commit(amount, skey)?;
Ok(commit)
}
pub fn switch_commit(&self, pubkey: &Identifier) -> Result<Commitment, Error> {
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<RangeProof, Error> {
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<BlindingFactor, Error> {
let mut pos_keys: Vec<SecretKey> = 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<SecretKey> = 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::<Vec<SecretKey>>());
neg_keys.extend(&blind_sum
.negative_blinding_factors
.iter()
.map(|b| b.secret_key())
.collect::<Vec<SecretKey>>());
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<Signature, Error> {
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<Signature, Error> {
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();
}
}

32
keychain/src/lib.rs Normal file
View file

@ -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};

View file

@ -27,4 +27,3 @@ grin_util = { path = "../util" }
[dev-dependencies] [dev-dependencies]
env_logger = "^0.3" env_logger = "^0.3"
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }

View file

@ -4,12 +4,12 @@ version = "0.1.0"
authors = ["Grin Authors <mimblewimble@lists.launchpad.net>"] authors = ["Grin Authors <mimblewimble@lists.launchpad.net>"]
[dependencies] [dependencies]
blake2-rfc = "~0.2.17"
grin_core = { path = "../core" } grin_core = { path = "../core" }
grin_keychain = { path = "../keychain" }
grin_store = { path = "../store" } grin_store = { path = "../store" }
grin_p2p = { path = "../p2p" } grin_p2p = { path = "../p2p" }
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }
time = "^0.1" time = "^0.1"
rand = "0.3" rand = "0.3"
log = "0.3" log = "0.3"
[dev-dependencies]

View file

@ -277,7 +277,6 @@ mod tests {
output_commit, output_commit,
); );
let mut test_graph = DirectedGraph::empty(); let mut test_graph = DirectedGraph::empty();
test_graph.add_entry(test_pool_entry, vec![incoming_edge_1]); test_graph.add_entry(test_pool_entry, vec![incoming_edge_1]);
@ -287,11 +286,9 @@ mod tests {
assert_eq!(test_graph.edges.len(), 1); assert_eq!(test_graph.edges.len(), 1);
} }
/// For testing/debugging: a random tx hash
} fn random_hash() -> core::hash::Hash {
let hash_bytes: [u8; 32] = rand::random();
/// For testing/debugging: a random tx hash core::hash::Hash(hash_bytes)
pub fn random_hash() -> core::hash::Hash { }
let hash_bytes: [u8; 32] = rand::random();
core::hash::Hash(hash_bytes)
} }

View file

@ -29,8 +29,9 @@ mod pool;
extern crate time; extern crate time;
extern crate rand; extern crate rand;
extern crate log; extern crate log;
extern crate blake2_rfc as blake2;
extern crate grin_core as core; extern crate grin_core as core;
extern crate grin_keychain as keychain;
extern crate secp256k1zkp as secp; extern crate secp256k1zkp as secp;
pub use pool::TransactionPool; pub use pool::TransactionPool;

View file

@ -557,25 +557,26 @@ where
mod tests { mod tests {
use super::*; use super::*;
use types::*; use types::*;
use secp::{Secp256k1, ContextFlag, constants};
use secp::key;
use core::core::build; use core::core::build;
use blockchain::{DummyChain, DummyChainImpl, DummyUtxoSet}; use blockchain::{DummyChain, DummyChainImpl, DummyUtxoSet};
use keychain::Keychain;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use blake2;
macro_rules! expect_output_parent { macro_rules! expect_output_parent {
($pool:expr, $expected:pat, $( $output:expr ),+ ) => { ($pool:expr, $expected:pat, $( $output:expr ),+ ) => {
$( $(
match $pool.search_for_best_output(&test_output($output).commitment()) { match $pool.search_for_best_output(&test_output($output).commitment()) {
$expected => {}, $expected => {},
x => panic!( x => panic!(
"Unexpected result from output search for {:?}, got {:?}", "Unexpected result from output search for {:?}, got {:?}",
$output, $output,
x), x,
}; ),
)* };
} )*
} }
}
#[test] #[test]
/// A basic test; add a pair of transactions to the pool. /// A basic test; add a pair of transactions to the pool.
@ -627,16 +628,10 @@ mod tests {
{ {
let read_pool = pool.read().unwrap(); let read_pool = pool.read().unwrap();
assert_eq!(read_pool.total_size(), 2); assert_eq!(read_pool.total_size(), 2);
expect_output_parent!(read_pool, Parent::PoolTransaction{tx_ref: _}, 12);
expect_output_parent!(read_pool, expect_output_parent!(read_pool, Parent::AlreadySpent{other_tx: _}, 11, 5);
Parent::PoolTransaction{tx_ref: _}, 12); expect_output_parent!(read_pool, Parent::BlockTransaction{output: _}, 8);
expect_output_parent!(read_pool, expect_output_parent!(read_pool, Parent::Unknown, 20);
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) => { Err(x) => {
match x { match x {
PoolError::AlreadyInPool => {} 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); assert_eq!(write_pool.total_size(), 1);
@ -868,10 +859,18 @@ mod tests {
// Note: There are some ordering constraints that must be followed here // Note: There are some ordering constraints that must be followed here
// until orphans is 100% implemented. Once the orphans process has // until orphans is 100% implemented. Once the orphans process has
// stabilized, we can mix these up to exercise that path a bit. // stabilized, we can mix these up to exercise that path a bit.
let mut txs_to_add = vec![block_transaction, conflict_transaction, let mut txs_to_add = vec![
valid_transaction, block_child, pool_child, conflict_child, block_transaction,
conflict_valid_child, valid_child_conflict, valid_child_valid, conflict_transaction,
mixed_child]; 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(); let expected_pool_size = txs_to_add.len();
@ -882,8 +881,7 @@ mod tests {
assert_eq!(write_pool.total_size(), 0); assert_eq!(write_pool.total_size(), 0);
for tx in txs_to_add.drain(..) { for tx in txs_to_add.drain(..) {
assert!(write_pool.add_to_memory_pool(test_source(), assert!(write_pool.add_to_memory_pool(test_source(), tx).is_ok());
tx).is_ok());
} }
assert_eq!(write_pool.total_size(), expected_pool_size); 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]); let block_tx_3 = test_transaction(vec![8], vec![4,3]);
// - Output conflict w/ 8 // - Output conflict w/ 8
let block_tx_4 = test_transaction(vec![40], vec![9]); let block_tx_4 = test_transaction(vec![40], vec![9]);
let block_transactions = vec![&block_tx_1, &block_tx_2, &block_tx_3, let block_transactions = vec![&block_tx_1, &block_tx_2, &block_tx_3, &block_tx_4];
&block_tx_4];
let keychain = Keychain::from_random_seed().unwrap();
let pubkey = keychain.derive_pubkey(1).unwrap();
let block = block::Block::new( let block = block::Block::new(
&block::BlockHeader::default(), &block::BlockHeader::default(),
block_transactions, block_transactions,
key::ONE_KEY, &keychain,
pubkey,
).unwrap(); ).unwrap();
chain_ref.apply_block(&block); chain_ref.apply_block(&block);
@ -934,16 +935,13 @@ mod tests {
expect_output_parent!(read_pool, Parent::BlockTransaction{output: _}, 9, 3); expect_output_parent!(read_pool, Parent::BlockTransaction{output: _}, 9, 3);
// We should have spent blockchain outputs at 4 and 7 // We should have spent blockchain outputs at 4 and 7
expect_output_parent!(read_pool, expect_output_parent!(read_pool, Parent::AlreadySpent{other_tx: _}, 4, 7);
Parent::AlreadySpent{other_tx: _}, 4, 7);
// We should have spent pool references at 15 // We should have spent pool references at 15
expect_output_parent!(read_pool, expect_output_parent!(read_pool, Parent::AlreadySpent{other_tx: _}, 15);
Parent::AlreadySpent{other_tx: _}, 15);
// We should have unspent pool references at 1, 13, 14 // We should have unspent pool references at 1, 13, 14
expect_output_parent!(read_pool, expect_output_parent!(read_pool, Parent::PoolTransaction{tx_ref: _}, 1, 13, 14);
Parent::PoolTransaction{tx_ref: _}, 1, 13, 14);
// References internal to the block should be unknown // References internal to the block should be unknown
expect_output_parent!(read_pool, Parent::Unknown, 8); expect_output_parent!(read_pool, Parent::Unknown, 8);
@ -982,16 +980,11 @@ mod tests {
let mut write_pool = pool.write().unwrap(); let mut write_pool = pool.write().unwrap();
assert_eq!(write_pool.total_size(), 0); assert_eq!(write_pool.total_size(), 0);
assert!(write_pool.add_to_memory_pool(test_source(), assert!(write_pool.add_to_memory_pool(test_source(), root_tx_1).is_ok());
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(), assert!(write_pool.add_to_memory_pool(test_source(), root_tx_3).is_ok());
root_tx_2).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(), assert!(write_pool.add_to_memory_pool(test_source(), child_tx_2).is_ok());
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); assert_eq!(write_pool.total_size(), 5);
} }
@ -1008,7 +1001,10 @@ mod tests {
// prepare_mineable_transactions to return mut refs // prepare_mineable_transactions to return mut refs
let block_txs: Vec<transaction::Transaction> = txs.drain(..).map(|x| *x).collect(); let block_txs: Vec<transaction::Transaction> = txs.drain(..).map(|x| *x).collect();
let tx_refs = block_txs.iter().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(); .unwrap();
} }
@ -1024,11 +1020,8 @@ mod tests {
assert_eq!(evicted_transactions.unwrap().len(), 3); assert_eq!(evicted_transactions.unwrap().len(), 3);
assert_eq!(write_pool.total_size(), 2); assert_eq!(write_pool.total_size(), 2);
} }
} }
fn test_setup(dummy_chain: &Arc<DummyChainImpl>) -> TransactionPool<DummyChainImpl> { fn test_setup(dummy_chain: &Arc<DummyChainImpl>) -> TransactionPool<DummyChainImpl> {
TransactionPool { TransactionPool {
transactions: HashMap::new(), transactions: HashMap::new(),
@ -1050,6 +1043,8 @@ mod tests {
input_values: Vec<u64>, input_values: Vec<u64>,
output_values: Vec<u64>, output_values: Vec<u64>,
) -> transaction::Transaction { ) -> transaction::Transaction {
let keychain = keychain_for_tests();
let fees: i64 = input_values.iter().sum::<u64>() as i64 - let fees: i64 = input_values.iter().sum::<u64>() as i64 -
output_values.iter().sum::<u64>() as i64; output_values.iter().sum::<u64>() as i64;
assert!(fees >= 0); assert!(fees >= 0);
@ -1057,62 +1052,52 @@ mod tests {
let mut tx_elements = Vec::new(); let mut tx_elements = Vec::new();
for input_value in input_values { 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 { 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)); 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 tx
} }
/// Deterministically generate an output defined by our test scheme /// Deterministically generate an output defined by our test scheme
fn test_output(value: u64) -> transaction::Output { fn test_output(value: u64) -> transaction::Output {
let ec = Secp256k1::with_caps(ContextFlag::Commit); let keychain = keychain_for_tests();
let output_key = test_key(value); let pubkey = keychain.derive_pubkey(value as u32).unwrap();
let output_commitment = ec.commit(value, output_key).unwrap(); let commit = keychain.commit(value, &pubkey).unwrap();
let proof = keychain.range_proof(value, &pubkey, commit).unwrap();
transaction::Output { transaction::Output {
features: transaction::DEFAULT_OUTPUT, features: transaction::DEFAULT_OUTPUT,
commit: output_commitment, commit: commit,
proof: ec.range_proof(0, value, output_key, output_commitment, ec.nonce()), proof: proof,
} }
} }
/// Deterministically generate a coinbase output defined by our test scheme /// Deterministically generate a coinbase output defined by our test scheme
fn test_coinbase_output(value: u64) -> transaction::Output { fn test_coinbase_output(value: u64) -> transaction::Output {
let ec = Secp256k1::with_caps(ContextFlag::Commit); let keychain = keychain_for_tests();
let output_key = test_key(value); let pubkey = keychain.derive_pubkey(value as u32).unwrap();
let output_commitment = ec.commit(value, output_key).unwrap(); let commit = keychain.commit(value, &pubkey).unwrap();
let proof = keychain.range_proof(value, &pubkey, commit).unwrap();
transaction::Output { transaction::Output {
features: transaction::COINBASE_OUTPUT, features: transaction::COINBASE_OUTPUT,
commit: output_commitment, commit: commit,
proof: ec.range_proof(0, value, output_key, output_commitment, ec.nonce()), proof: proof,
} }
} }
/// Makes a SecretKey from a single u64 fn keychain_for_tests() -> Keychain {
fn test_key(value: u64) -> key::SecretKey { let seed = "pool_tests";
let ec = Secp256k1::with_caps(ContextFlag::Commit); let seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes());
// SecretKey takes a SECRET_KEY_SIZE slice of u8. Keychain::from_seed(seed.as_bytes()).unwrap()
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()
} }
/// A generic TxSource representing a test /// A generic TxSource representing a test

View file

@ -25,4 +25,3 @@ tag="grin_integration_12"
[dev_dependencies] [dev_dependencies]
grin_chain = { path = "../chain"} grin_chain = { path = "../chain"}
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }

View file

@ -26,9 +26,9 @@ extern crate blake2_rfc as blake2;
extern crate grin_api as api; extern crate grin_api as api;
extern crate grin_grin as grin; extern crate grin_grin as grin;
extern crate grin_wallet as wallet; extern crate grin_wallet as wallet;
extern crate grin_keychain as keychain;
extern crate grin_config as config; extern crate grin_config as config;
extern crate grin_core as core; extern crate grin_core as core;
extern crate secp256k1zkp as secp;
use std::thread; use std::thread;
use std::io::Read; use std::io::Read;
@ -38,11 +38,10 @@ use std::time::Duration;
use clap::{Arg, App, SubCommand, ArgMatches}; use clap::{Arg, App, SubCommand, ArgMatches};
use daemonize::Daemonize; use daemonize::Daemonize;
use secp::Secp256k1;
use config::GlobalConfig; use config::GlobalConfig;
use wallet::WalletConfig; use wallet::WalletConfig;
use core::global; use core::global;
use keychain::Keychain;
fn start_from_config_file(mut global_config: GlobalConfig) { fn start_from_config_file(mut global_config: GlobalConfig) {
info!( info!(
@ -314,13 +313,10 @@ fn wallet_command(wallet_args: &ArgMatches) {
// TODO do something closer to BIP39, eazy solution right now // TODO do something closer to BIP39, eazy solution right now
let seed = blake2::blake2b::blake2b(32, &[], hd_seed.as_bytes()); let seed = blake2::blake2b::blake2b(32, &[], hd_seed.as_bytes());
let keychain = Keychain::from_seed(seed.as_bytes()).expect(
let s = Secp256k1::new(); "Failed to initialize keychain from the provided seed.",
let key = wallet::ExtendedKey::from_seed(&s, seed.as_bytes()).expect(
"Error deriving extended key from seed.",
); );
let mut wallet_config = WalletConfig::default(); let mut wallet_config = WalletConfig::default();
if let Some(port) = wallet_args.value_of("port") { if let Some(port) = wallet_args.value_of("port") {
let default_ip = "127.0.0.1"; let default_ip = "127.0.0.1";
@ -344,7 +340,7 @@ fn wallet_command(wallet_args: &ArgMatches) {
file.read_to_string(&mut contents).expect( file.read_to_string(&mut contents).expect(
"Unable to read transaction file.", "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 { } else {
info!( info!(
"Starting the Grin wallet receiving daemon at {}...", "Starting the Grin wallet receiving daemon at {}...",
@ -354,7 +350,7 @@ fn wallet_command(wallet_args: &ArgMatches) {
apis.register_endpoint( apis.register_endpoint(
"/receive".to_string(), "/receive".to_string(),
wallet::WalletReceiver { wallet::WalletReceiver {
key: key, keychain: keychain,
config: wallet_config.clone(), config: wallet_config.clone(),
}, },
); );
@ -373,10 +369,10 @@ fn wallet_command(wallet_args: &ArgMatches) {
if let Some(d) = send_args.value_of("dest") { if let Some(d) = send_args.value_of("dest") {
dest = d; 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(_)) => { ("info", Some(_)) => {
wallet::show_info(&wallet_config, &key); wallet::show_info(&wallet_config, &keychain);
} }
_ => panic!("Unknown wallet command, use 'grin help wallet' for details"), _ => panic!("Unknown wallet command, use 'grin help wallet' for details"),
} }

View file

@ -1,7 +1,11 @@
[package] [package]
name = "grin_wallet" name = "grin_wallet"
version = "0.1.0" version = "0.1.0"
authors = ["Ignotus Peverell <igno_peverell@protonmail.com>", "Laurent Meunier <laurent.meunier95@gmail.com>"] authors = [
"Ignotus Peverell <igno_peverell@protonmail.com>",
"Laurent Meunier <laurent.meunier95@gmail.com>",
"Antioch Peverell",
]
[dependencies] [dependencies]
@ -15,5 +19,6 @@ serde_json = "~1.0.2"
grin_api = { path = "../api" } grin_api = { path = "../api" }
grin_core = { path = "../core" } grin_core = { path = "../core" }
grin_keychain = { path = "../keychain" }
grin_util = { path = "../util" } grin_util = { path = "../util" }
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" } secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }

3
wallet/rustfmt.toml Normal file
View file

@ -0,0 +1,3 @@
hard_tabs = true
wrap_comments = true
write_mode = "Overwrite"

View file

@ -16,9 +16,8 @@
//! the wallet storage and update them. //! the wallet storage and update them.
use api; use api;
use extkey::ExtendedKey;
use secp::{self, pedersen};
use types::*; use types::*;
use keychain::Keychain;
use util; use util;
@ -41,9 +40,11 @@ fn refresh_output(out: &mut OutputData, api_out: Option<api::Output>, tip: &api:
/// Goes through the list of outputs that haven't been spent yet and check /// Goes through the list of outputs that haven't been spent yet and check
/// with a node whether their status has changed. /// with a node whether their status has changed.
pub fn refresh_outputs(config: &WalletConfig, ext_key: &ExtendedKey) -> Result<(), Error> { pub fn refresh_outputs(
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); config: &WalletConfig,
let tip = get_tip(config)?; keychain: &Keychain,
) -> Result<(), Error>{
let tip = get_tip_from_node(config)?;
WalletData::with_wallet(&config.data_file_dir, |wallet_data| { WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
// check each output that's not spent // 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 // TODO check the pool for unconfirmed
let key = ext_key.derive(&secp, out.n_child).unwrap(); match get_output_from_node(config, keychain, out.value, out.n_child) {
let commitment = secp.commit(out.value, key.key).unwrap();
match get_output_by_commitment(config, commitment) {
Ok(api_out) => refresh_output(&mut out, api_out, &tip), Ok(api_out) => refresh_output(&mut out, api_out, &tip),
Err(_) => { Err(_) => {
// TODO find error with connection and return // 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<api::Tip, Error> { fn get_tip_from_node(config: &WalletConfig) -> Result<api::Tip, Error> {
let url = format!("{}/v1/chain/1", config.check_node_api_http_addr); let url = format!("{}/v1/chain/1", config.check_node_api_http_addr);
api::client::get::<api::Tip>(url.as_str()).map_err(|e| Error::Node(e)) api::client::get::<api::Tip>(url.as_str()).map_err(|e| Error::Node(e))
} }
// queries a reachable node for a given output, checking whether it's been // queries a reachable node for a given output, checking whether it's been confirmed
// confirmed fn get_output_from_node(
fn get_output_by_commitment(
config: &WalletConfig, config: &WalletConfig,
commit: pedersen::Commitment, keychain: &Keychain,
amount: u64,
derivation: u32,
) -> Result<Option<api::Output>, Error> { ) -> Result<Option<api::Output>, Error> {
let pubkey = keychain.derive_pubkey(derivation)?;
let commit = keychain.commit(amount, &pubkey)?;
let url = format!( let url = format!(
"{}/v1/chain/utxo/{}", "{}/v1/chain/utxo/{}",
config.check_node_api_http_addr, config.check_node_api_http_addr,

View file

@ -12,14 +12,16 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use secp;
use checker; use checker;
use extkey::ExtendedKey; use keychain::Keychain;
use types::{WalletConfig, WalletData}; use types::{WalletConfig, WalletData};
pub fn show_info(config: &WalletConfig, ext_key: &ExtendedKey) { pub fn show_info(
let _ = checker::refresh_outputs(&config, ext_key); config: &WalletConfig,
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); keychain: &Keychain,
) {
let fingerprint = keychain.clone().fingerprint();
let _ = checker::refresh_outputs(&config, &keychain);
// operate within a lock on wallet data // operate within a lock on wallet data
let _ = WalletData::with_wallet(&config.data_file_dir, |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!("Outputs - ");
println!("fingerprint, n_child, height, lock_height, status, value"); println!("fingerprint, n_child, height, lock_height, status, value");
println!("----------------------------------"); println!("----------------------------------");
for out in &mut wallet_data.outputs.iter().filter(|o| { for out in &mut wallet_data.outputs
o.fingerprint == ext_key.fingerprint .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!( println!(
"{}, {}, {}, {}, {}, {}", "{}, {}, {}, {}, {:?}, {}",
key.identifier().fingerprint(), pubkey.fingerprint(),
out.n_child, out.n_child,
out.height, out.height,
out.lock_height, out.lock_height,

View file

@ -26,17 +26,16 @@ extern crate serde_json;
extern crate grin_api as api; extern crate grin_api as api;
extern crate grin_core as core; extern crate grin_core as core;
extern crate grin_keychain as keychain;
extern crate grin_util as util; extern crate grin_util as util;
extern crate secp256k1zkp as secp; extern crate secp256k1zkp as secp;
mod checker; mod checker;
mod extkey;
mod info; mod info;
mod receiver; mod receiver;
mod sender; mod sender;
mod types; mod types;
pub use extkey::ExtendedKey;
pub use info::show_info; pub use info::show_info;
pub use receiver::{WalletReceiver, receive_json_tx}; pub use receiver::{WalletReceiver, receive_json_tx};
pub use sender::issue_send_tx; pub use sender::issue_send_tx;

View file

@ -49,16 +49,13 @@
//! double-exchange will be required as soon as we support Schnorr signatures. //! double-exchange will be required as soon as we support Schnorr signatures.
//! So we may as well have it in place already. //! 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::core::{Block, Transaction, TxKernel, Output, build};
use core::ser; use core::ser;
use api::{self, ApiEndpoint, Operation, ApiResult}; use api::{self, ApiEndpoint, Operation, ApiResult};
use extkey::ExtendedKey;
use types::*; use types::*;
use util; use util;
use keychain::{BlindingFactor, Keychain};
/// Dummy wrapper for the hex-encoded serialized transaction. /// Dummy wrapper for the hex-encoded serialized transaction.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -71,11 +68,11 @@ struct TxWrapper {
/// network. /// network.
pub fn receive_json_tx( pub fn receive_json_tx(
config: &WalletConfig, config: &WalletConfig,
ext_key: &ExtendedKey, keychain: &Keychain,
partial_tx_str: &str, partial_tx_str: &str,
) -> Result<(), Error> { ) -> Result<(), Error> {
let (amount, blinding, partial_tx) = partial_tx_from_json(partial_tx_str)?; let (amount, blinding, partial_tx) = partial_tx_from_json(keychain, partial_tx_str)?;
let final_tx = receive_transaction(&config, ext_key, amount, blinding, partial_tx)?; let final_tx = receive_transaction(config, keychain, amount, blinding, partial_tx)?;
let tx_hex = util::to_hex(ser::ser_vec(&final_tx).unwrap()); 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()); 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. /// wallet REST API as well as some of the command-line operations.
#[derive(Clone)] #[derive(Clone)]
pub struct WalletReceiver { pub struct WalletReceiver {
pub key: ExtendedKey, pub keychain: Keychain,
pub config: WalletConfig, pub config: WalletConfig,
} }
@ -114,19 +111,22 @@ impl ApiEndpoint for WalletReceiver {
if cb_amount.amount == 0 { if cb_amount.amount == 0 {
return Err(api::Error::Argument(format!("Zero amount not allowed."))); return Err(api::Error::Argument(format!("Zero amount not allowed.")));
} }
let (out, kern) = receive_coinbase( let (out, kern) =
&self.config, receive_coinbase(
&self.key, &self.config,
cb_amount.amount, &self.keychain,
).map_err(|e| { cb_amount.amount,
api::Error::Internal(format!("Error building coinbase: {:?}", e)) ).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 out_bin =
})?; ser::ser_vec(&out).map_err(|e| {
let kern_bin = ser::ser_vec(&kern).map_err(|e| { api::Error::Internal(format!("Error serializing output: {:?}", e))
api::Error::Internal(format!("Error serializing kernel: {:?}", e)) })?;
})?; let kern_bin =
ser::ser_vec(&kern).map_err(|e| {
api::Error::Internal(format!("Error serializing kernel: {:?}", e))
})?;
Ok(CbData { Ok(CbData {
output: util::to_hex(out_bin), output: util::to_hex(out_bin),
kernel: util::to_hex(kern_bin), kernel: util::to_hex(kern_bin),
@ -141,15 +141,11 @@ impl ApiEndpoint for WalletReceiver {
match input { match input {
WalletReceiveRequest::PartialTransaction(partial_tx_str) => { WalletReceiveRequest::PartialTransaction(partial_tx_str) => {
debug!("Operation {} with transaction {}", op, &partial_tx_str); debug!("Operation {} with transaction {}", op, &partial_tx_str);
receive_json_tx(&self.config, &self.key, &partial_tx_str) receive_json_tx(&self.config, &self.keychain, &partial_tx_str).map_err(|e| {
.map_err(|e| { api::Error::Internal(format!("Error processing partial transaction: {:?}", e))
api::Error::Internal( }).unwrap();
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 { Ok(CbData {
output: String::from(""), output: String::from(""),
kernel: String::from(""), kernel: String::from(""),
@ -168,52 +164,47 @@ impl ApiEndpoint for WalletReceiver {
/// Build a coinbase output and the corresponding kernel /// Build a coinbase output and the corresponding kernel
fn receive_coinbase( fn receive_coinbase(
config: &WalletConfig, config: &WalletConfig,
ext_key: &ExtendedKey, keychain: &Keychain,
amount: u64, amount: u64,
) -> Result<(Output, TxKernel), Error> { ) -> 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 // operate within a lock on wallet data
WalletData::with_wallet(&config.data_file_dir, |wallet_data| { WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
let derivation = wallet_data.next_child(fingerprint.clone());
// derive a new private for the reward let pubkey = keychain.derive_pubkey(derivation)?;
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 // track the new output and return the stuff needed for reward
wallet_data.append_output(OutputData { wallet_data.append_output(OutputData {
fingerprint: coinbase_key.fingerprint, fingerprint: fingerprint.clone(),
n_child: coinbase_key.n_child, n_child: derivation,
value: amount, value: amount,
status: OutputStatus::Unconfirmed, status: OutputStatus::Unconfirmed,
height: 0, height: 0,
lock_height: 0, lock_height: 0,
}); });
debug!( debug!("Received coinbase and built output - {}, {}, {}",
"Using child {} for a new coinbase output.", fingerprint.clone(), pubkey.fingerprint(), derivation);
coinbase_key.n_child
);
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 /// Builds a full transaction from the partial one sent to us for transfer
fn receive_transaction( fn receive_transaction(
config: &WalletConfig, config: &WalletConfig,
ext_key: &ExtendedKey, keychain: &Keychain,
amount: u64, amount: u64,
blinding: SecretKey, blinding: BlindingFactor,
partial: Transaction, partial: Transaction,
) -> Result<Transaction, Error> { ) -> Result<Transaction, Error> {
let fingerprint = keychain.clone().fingerprint();
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
// operate within a lock on wallet data // operate within a lock on wallet data
WalletData::with_wallet(&config.data_file_dir, |wallet_data| { WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
let derivation = wallet_data.next_child(fingerprint.clone());
let next_child = wallet_data.next_child(&ext_key.fingerprint); let pubkey = keychain.derive_pubkey(derivation)?;
let out_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?;
// TODO - replace with real fee calculation // TODO - replace with real fee calculation
// TODO - note we are not enforcing this in consensus anywhere yet // TODO - note we are not enforcing this in consensus anywhere yet
@ -223,28 +214,24 @@ fn receive_transaction(
let (tx_final, _) = build::transaction(vec![ let (tx_final, _) = build::transaction(vec![
build::initial_tx(partial), build::initial_tx(partial),
build::with_excess(blinding), build::with_excess(blinding),
build::output(out_amount, out_key.key), build::output(out_amount, pubkey.clone()),
build::with_fee(fee_amount), build::with_fee(fee_amount),
])?; ], keychain)?;
// make sure the resulting transaction is valid (could have been lied to // make sure the resulting transaction is valid (could have been lied to on excess)
// on excess) tx_final.validate(&keychain.secp())?;
tx_final.validate(&secp)?;
// track the new output and return the finalized transaction to broadcast // track the new output and return the finalized transaction to broadcast
wallet_data.append_output(OutputData { wallet_data.append_output(OutputData {
fingerprint: out_key.fingerprint, fingerprint: fingerprint.clone(),
n_child: out_key.n_child, n_child: derivation,
value: out_amount, value: out_amount,
status: OutputStatus::Unconfirmed, status: OutputStatus::Unconfirmed,
height: 0, height: 0,
lock_height: 0, lock_height: 0,
}); });
debug!("Received txn and built output - {}, {}, {}",
debug!( fingerprint.clone(), pubkey.fingerprint(), derivation);
"Using child {} for a new transaction output.",
out_key.n_child
);
Ok(tx_final) Ok(tx_final)
})? })?

View file

@ -12,30 +12,25 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::convert::From; use api;
use secp;
use secp::key::SecretKey;
use checker; use checker;
use core::core::{Transaction, build}; use core::core::{Transaction, build};
use extkey::ExtendedKey; use keychain::{BlindingFactor, Keychain};
use types::*; use types::*;
use api;
/// Issue a new transaction to the provided sender by spending some of our /// Issue a new transaction to the provided sender by spending some of our
/// wallet /// wallet
/// UTXOs. The destination can be "stdout" (for command line) or a URL to the /// UTXOs. The destination can be "stdout" (for command line) or a URL to the
/// recipients wallet receiver (to be implemented). /// recipients wallet receiver (to be implemented).
pub fn issue_send_tx( pub fn issue_send_tx(
config: &WalletConfig, config: &WalletConfig,
ext_key: &ExtendedKey, keychain: &Keychain,
amount: u64, amount: u64,
dest: String, dest: String,
) -> Result<(), Error> { ) -> 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); let json_tx = partial_tx_to_json(amount, blind_sum, tx);
if dest == "stdout" { if dest == "stdout" {
@ -44,10 +39,10 @@ pub fn issue_send_tx(
let url = format!("{}/v1/receive/receive_json_tx", &dest); let url = format!("{}/v1/receive/receive_json_tx", &dest);
debug!("Posting partial transaction to {}", url); debug!("Posting partial transaction to {}", url);
let request = WalletReceiveRequest::PartialTransaction(json_tx); let request = WalletReceiveRequest::PartialTransaction(json_tx);
let _: CbData = api::client::post(url.as_str(), &request).expect(&format!( let _: CbData = api::client::post(url.as_str(), &request)
"Wallet receiver at {} unreachable, could not send transaction. Is it running?", .expect(&format!("Wallet receiver at {} unreachable, could not send transaction. Is it running?", url));
url } else {
)); panic!("dest not in expected format: {}", dest);
} }
Ok(()) Ok(())
} }
@ -57,52 +52,51 @@ pub fn issue_send_tx(
/// selecting outputs to spend and building the change. /// selecting outputs to spend and building the change.
fn build_send_tx( fn build_send_tx(
config: &WalletConfig, config: &WalletConfig,
ext_key: &ExtendedKey, keychain: &Keychain,
amount: u64, amount: u64,
) -> Result<(Transaction, SecretKey), Error> { ) -> Result<(Transaction, BlindingFactor), Error> {
// first, rebuild the private key from the seed let fingerprint = keychain.clone().fingerprint();
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
// operate within a lock on wallet data // operate within a lock on wallet data
WalletData::with_wallet(&config.data_file_dir, |wallet_data| { WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
// second, check from our local wallet data for outputs to spend // select some suitable outputs to spend from our local wallet
let (coins, change) = wallet_data.select(&ext_key.fingerprint, amount); let (coins, change) = wallet_data.select(fingerprint.clone(), amount);
if change < 0 { if change < 0 {
return Err(Error::NotEnoughFunds((-change) as u64)); return Err(Error::NotEnoughFunds((-change) as u64));
} }
// TODO add fees, which is likely going to make this iterative // 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![]; let mut parts = vec![];
for coin in &coins { for coin in &coins {
let in_key = ext_key.derive(&secp, coin.n_child).map_err( let pubkey = keychain.derive_pubkey(coin.n_child)?;
|e| Error::Key(e), parts.push(build::input(coin.value, pubkey));
)?;
parts.push(build::input(coin.value, in_key.key));
} }
// fourth, derive a new private for change and build the change output // derive an additional pubkey for change and build the change output
let next_child = wallet_data.next_child(&ext_key.fingerprint); let change_derivation = wallet_data.next_child(fingerprint.clone());
let change_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?; let change_key = keychain.derive_pubkey(change_derivation)?;
parts.push(build::output(change as u64, change_key.key)); parts.push(build::output(change as u64, change_key));
// we got that far, time to start tracking the new output, finalize tx // we got that far, time to start tracking the new output, finalize tx
// and lock the outputs used // and lock the outputs used
wallet_data.append_output(OutputData { wallet_data.append_output(OutputData {
fingerprint: change_key.fingerprint, fingerprint: fingerprint.clone(),
n_child: change_key.n_child, n_child: change_derivation,
value: change as u64, value: change as u64,
status: OutputStatus::Unconfirmed, status: OutputStatus::Unconfirmed,
height: 0, height: 0,
lock_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 { mod test {
use core::core::build::{input, output, transaction}; use core::core::build::{input, output, transaction};
use types::{OutputData, OutputStatus}; use types::{OutputData, OutputStatus};
use keychain::Keychain;
use secp::Secp256k1;
use super::ExtendedKey;
use util;
fn from_hex(hex_str: &str) -> Vec<u8> {
util::from_hex(hex_str.to_string()).unwrap()
}
#[test] #[test]
// demonstrate that input.commitment == referenced output.commitment // 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() { fn output_commitment_equals_input_commitment_on_spend() {
let secp = Secp256k1::new(); let keychain = Keychain::from_random_seed().unwrap();
let seed = from_hex("000102030405060708090a0b0c0d0e0f"); 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()); assert_eq!(tx.outputs[0].commitment(), tx2.inputs[0].commitment());
} }
} }

View file

@ -19,16 +19,13 @@ use std::io::Write;
use std::path::Path; use std::path::Path;
use std::path::MAIN_SEPARATOR; use std::path::MAIN_SEPARATOR;
use serde_json; use serde_json;
use secp; use secp;
use secp::key::SecretKey;
use api; use api;
use core::core::Transaction; use core::core::Transaction;
use core::ser; use core::ser;
use extkey; use keychain;
use util; use util;
const DAT_FILE: &'static str = "wallet.dat"; const DAT_FILE: &'static str = "wallet.dat";
@ -38,8 +35,8 @@ const LOCK_FILE: &'static str = "wallet.lock";
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
NotEnoughFunds(u64), NotEnoughFunds(u64),
Crypto(secp::Error), Keychain(keychain::Error),
Key(extkey::Error), Secp(secp::Error),
WalletData(String), WalletData(String),
/// An error in the format of the JSON structures exchanged by the wallet /// An error in the format of the JSON structures exchanged by the wallet
Format(String), Format(String),
@ -47,34 +44,24 @@ pub enum Error {
Node(api::Error), Node(api::Error),
} }
impl From<secp::Error> for Error { impl From<keychain::Error> for Error {
fn from(e: secp::Error) -> Error { fn from(e: keychain::Error) -> Error { Error::Keychain(e) }
Error::Crypto(e)
}
} }
impl From<extkey::Error> for Error { impl From<secp::Error> for Error {
fn from(e: extkey::Error) -> Error { fn from(e: secp::Error) -> Error { Error::Secp(e) }
Error::Key(e)
}
} }
impl From<serde_json::Error> for Error { impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Error { fn from(e: serde_json::Error) -> Error { Error::Format(e.to_string()) }
Error::Format(e.to_string())
}
} }
impl From<num::ParseIntError> for Error { impl From<num::ParseIntError> for Error {
fn from(_: num::ParseIntError) -> Error { fn from(_: num::ParseIntError) -> Error { Error::Format("Invalid hex".to_string()) }
Error::Format("Invalid hex".to_string())
}
} }
impl From<api::Error> for Error { impl From<api::Error> for Error {
fn from(e: api::Error) -> Error { fn from(e: api::Error) -> Error { Error::Node(e) }
Error::Node(e)
}
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -132,7 +119,7 @@ impl fmt::Display for OutputStatus {
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct OutputData { pub struct OutputData {
/// Private key fingerprint (in case the wallet tracks multiple) /// Private key fingerprint (in case the wallet tracks multiple)
pub fingerprint: extkey::Fingerprint, pub fingerprint: keychain::Fingerprint,
/// How many derivations down from the root key /// How many derivations down from the root key
pub n_child: u32, pub n_child: u32,
/// Value of the output, necessary to rebuild the commitment /// Value of the output, necessary to rebuild the commitment
@ -283,13 +270,18 @@ impl WalletData {
/// Select a subset of unspent outputs to spend in a transaction /// Select a subset of unspent outputs to spend in a transaction
/// transferring /// transferring
/// the provided amount. /// the provided amount.
pub fn select(&self, fingerprint: &extkey::Fingerprint, amount: u64) -> (Vec<OutputData>, i64) { pub fn select(
&self,
fingerprint: keychain::Fingerprint,
amount: u64
) -> (Vec<OutputData>, i64) {
let mut to_spend = vec![]; let mut to_spend = vec![];
let mut input_total = 0; let mut input_total = 0;
// TODO very naive impl for now, there's definitely better coin selection
// TODO very naive impl for now - definitely better coin selection
// algos available // algos available
for out in &self.outputs { for out in &self.outputs {
if out.status == OutputStatus::Unspent && out.fingerprint == *fingerprint { if out.status == OutputStatus::Unspent && out.fingerprint == fingerprint {
to_spend.push(out.clone()); to_spend.push(out.clone());
input_total += out.value; input_total += out.value;
if input_total >= amount { if input_total >= amount {
@ -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)) (to_spend, (input_total as i64) - (amount as i64))
} }
/// Next child index when we want to create a new output. /// Next child index when we want to create a new output.
pub fn next_child(&self, fingerprint: &extkey::Fingerprint) -> u32 { pub fn next_child(&self, fingerprint: keychain::Fingerprint) -> u32 {
let mut max_n = 0; let mut max_n = 0;
for out in &self.outputs { for out in &self.outputs {
if max_n < out.n_child && out.fingerprint == *fingerprint { if max_n < out.n_child && out.fingerprint == fingerprint {
max_n = out.n_child; max_n = out.n_child;
} }
} }
@ -323,10 +316,14 @@ struct JSONPartialTx {
/// Encodes the information for a partial transaction (not yet completed by the /// Encodes the information for a partial transaction (not yet completed by the
/// receiver) into JSON. /// 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 { let partial_tx = JSONPartialTx {
amount: receive_amount, 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()), tx: util::to_hex(ser::ser_vec(&tx).unwrap()),
}; };
serde_json::to_string_pretty(&partial_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 /// Reads a partial transaction encoded as JSON into the amount, sum of blinding
/// factors and the transaction itself. /// 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 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 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_bin = util::from_hex(partial_tx.tx)?;
let tx = ser::deserialize(&mut &tx_bin[..]).map_err(|_| { let tx = ser::deserialize(&mut &tx_bin[..]).map_err(|_| {
Error::Format( Error::Format(