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
.DS_Store
.grin
.grin2
.grin*
node*
target
Cargo.lock

View file

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

View file

@ -5,11 +5,12 @@ authors = ["Ignotus Peverell <igno.peverell@protonmail.com>"]
exclude = ["**/*.grin", "**/*.grin2"]
[workspace]
members = ["api", "chain", "config", "core", "grin", "p2p", "store", "util", "pool", "wallet"]
members = ["api", "chain", "config", "core", "grin", "keychain", "p2p", "store", "util", "pool", "wallet"]
[dependencies]
grin_api = { path = "./api" }
grin_wallet = { path = "./wallet" }
grin_keychain = { path = "./keychain" }
grin_grin = { path = "./grin" }
grin_config = { path = "./config" }
grin_core = { path = "./core" }

View file

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

View file

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

View file

@ -15,15 +15,15 @@
extern crate env_logger;
extern crate grin_chain as chain;
extern crate grin_core as core;
extern crate grin_keychain as keychain;
extern crate rand;
extern crate secp256k1zkp as secp;
use std::fs;
use chain::ChainStore;
use core::core::hash::Hashed;
use core::core::{Block, BlockHeader};
use secp::key;
use keychain::Keychain;
fn clean_output_dir(dir_name: &str) {
let _ = fs::remove_dir_all(dir_name);
@ -34,9 +34,12 @@ fn test_various_store_indices() {
let _ = env_logger::init();
clean_output_dir(".grin");
let keychain = Keychain::from_random_seed().unwrap();
let pubkey = keychain.derive_pubkey(1).unwrap();
let chain_store = &chain::store::ChainKVStore::new(".grin".to_string()).unwrap() as &ChainStore;
let block = Block::new(&BlockHeader::default(), vec![], key::ONE_KEY).unwrap();
let block = Block::new(&BlockHeader::default(), vec![], &keychain, pubkey).unwrap();
let commit = block.outputs[0].commitment();
let block_hash = block.hash();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -230,7 +230,7 @@ impl Transaction {
// we originally converted the commitment to a pubkey here (commitment to zero)
// and then passed the pubkey to secp.verify()
// the secp api no longer allows us to do this so we have wrapped the complexity
// of generating a publick key from a commitment behind verify_from_commit
// of generating a public key from a commitment behind verify_from_commit
secp.verify_from_commit(&msg, &sig, &rsum)?;
Ok(TxKernel {

View file

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

View file

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

View file

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

View file

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

View file

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

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

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]
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>"]
[dependencies]
blake2-rfc = "~0.2.17"
grin_core = { path = "../core" }
grin_keychain = { path = "../keychain" }
grin_store = { path = "../store" }
grin_p2p = { path = "../p2p" }
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }
time = "^0.1"
rand = "0.3"
log = "0.3"
[dev-dependencies]

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,11 @@
[package]
name = "grin_wallet"
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]
@ -15,5 +19,6 @@ serde_json = "~1.0.2"
grin_api = { path = "../api" }
grin_core = { path = "../core" }
grin_keychain = { path = "../keychain" }
grin_util = { path = "../util" }
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }

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

View file

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

View file

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

View file

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

View file

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

View file

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