mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
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:
parent
6b4f2a63da
commit
677d0a3a95
38 changed files with 887 additions and 649 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,7 +1,6 @@
|
|||
*.swp
|
||||
.DS_Store
|
||||
.grin
|
||||
.grin2
|
||||
.grin*
|
||||
node*
|
||||
target
|
||||
Cargo.lock
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
13
keychain/Cargo.toml
Normal 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
3
keychain/rustfmt.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
hard_tabs = true
|
||||
wrap_comments = true
|
||||
write_mode = "Overwrite"
|
81
keychain/src/blind.rs
Normal file
81
keychain/src/blind.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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
196
keychain/src/keychain.rs
Normal 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
32
keychain/src/lib.rs
Normal 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};
|
|
@ -27,4 +27,3 @@ grin_util = { path = "../util" }
|
|||
|
||||
[dev-dependencies]
|
||||
env_logger = "^0.3"
|
||||
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
167
pool/src/pool.rs
167
pool/src/pool.rs
|
@ -557,25 +557,26 @@ where
|
|||
mod tests {
|
||||
use super::*;
|
||||
use types::*;
|
||||
use secp::{Secp256k1, ContextFlag, constants};
|
||||
use secp::key;
|
||||
use core::core::build;
|
||||
use blockchain::{DummyChain, DummyChainImpl, DummyUtxoSet};
|
||||
use keychain::Keychain;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use blake2;
|
||||
|
||||
macro_rules! expect_output_parent {
|
||||
($pool:expr, $expected:pat, $( $output:expr ),+ ) => {
|
||||
$(
|
||||
match $pool.search_for_best_output(&test_output($output).commitment()) {
|
||||
$expected => {},
|
||||
x => panic!(
|
||||
"Unexpected result from output search for {:?}, got {:?}",
|
||||
$output,
|
||||
x),
|
||||
};
|
||||
)*
|
||||
}
|
||||
}
|
||||
($pool:expr, $expected:pat, $( $output:expr ),+ ) => {
|
||||
$(
|
||||
match $pool.search_for_best_output(&test_output($output).commitment()) {
|
||||
$expected => {},
|
||||
x => panic!(
|
||||
"Unexpected result from output search for {:?}, got {:?}",
|
||||
$output,
|
||||
x,
|
||||
),
|
||||
};
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// A basic test; add a pair of transactions to the pool.
|
||||
|
@ -627,16 +628,10 @@ mod tests {
|
|||
{
|
||||
let read_pool = pool.read().unwrap();
|
||||
assert_eq!(read_pool.total_size(), 2);
|
||||
|
||||
expect_output_parent!(read_pool,
|
||||
Parent::PoolTransaction{tx_ref: _}, 12);
|
||||
expect_output_parent!(read_pool,
|
||||
Parent::AlreadySpent{other_tx: _}, 11, 5);
|
||||
expect_output_parent!(read_pool,
|
||||
Parent::BlockTransaction{output: _}, 8);
|
||||
expect_output_parent!(read_pool,
|
||||
Parent::Unknown, 20);
|
||||
|
||||
expect_output_parent!(read_pool, Parent::PoolTransaction{tx_ref: _}, 12);
|
||||
expect_output_parent!(read_pool, Parent::AlreadySpent{other_tx: _}, 11, 5);
|
||||
expect_output_parent!(read_pool, Parent::BlockTransaction{output: _}, 8);
|
||||
expect_output_parent!(read_pool, Parent::Unknown, 20);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -721,13 +716,9 @@ mod tests {
|
|||
Err(x) => {
|
||||
match x {
|
||||
PoolError::AlreadyInPool => {}
|
||||
_ => {
|
||||
panic!("Unexpected error when adding already in pool tx: {:?}",
|
||||
x)
|
||||
}
|
||||
_ => panic!("Unexpected error when adding already in pool tx: {:?}", x),
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
assert_eq!(write_pool.total_size(), 1);
|
||||
|
@ -868,10 +859,18 @@ mod tests {
|
|||
// Note: There are some ordering constraints that must be followed here
|
||||
// until orphans is 100% implemented. Once the orphans process has
|
||||
// stabilized, we can mix these up to exercise that path a bit.
|
||||
let mut txs_to_add = vec![block_transaction, conflict_transaction,
|
||||
valid_transaction, block_child, pool_child, conflict_child,
|
||||
conflict_valid_child, valid_child_conflict, valid_child_valid,
|
||||
mixed_child];
|
||||
let mut txs_to_add = vec![
|
||||
block_transaction,
|
||||
conflict_transaction,
|
||||
valid_transaction,
|
||||
block_child,
|
||||
pool_child,
|
||||
conflict_child,
|
||||
conflict_valid_child,
|
||||
valid_child_conflict,
|
||||
valid_child_valid,
|
||||
mixed_child,
|
||||
];
|
||||
|
||||
let expected_pool_size = txs_to_add.len();
|
||||
|
||||
|
@ -882,8 +881,7 @@ mod tests {
|
|||
assert_eq!(write_pool.total_size(), 0);
|
||||
|
||||
for tx in txs_to_add.drain(..) {
|
||||
assert!(write_pool.add_to_memory_pool(test_source(),
|
||||
tx).is_ok());
|
||||
assert!(write_pool.add_to_memory_pool(test_source(), tx).is_ok());
|
||||
}
|
||||
|
||||
assert_eq!(write_pool.total_size(), expected_pool_size);
|
||||
|
@ -898,13 +896,16 @@ mod tests {
|
|||
let block_tx_3 = test_transaction(vec![8], vec![4,3]);
|
||||
// - Output conflict w/ 8
|
||||
let block_tx_4 = test_transaction(vec![40], vec![9]);
|
||||
let block_transactions = vec![&block_tx_1, &block_tx_2, &block_tx_3,
|
||||
&block_tx_4];
|
||||
let block_transactions = vec![&block_tx_1, &block_tx_2, &block_tx_3, &block_tx_4];
|
||||
|
||||
let keychain = Keychain::from_random_seed().unwrap();
|
||||
let pubkey = keychain.derive_pubkey(1).unwrap();
|
||||
|
||||
let block = block::Block::new(
|
||||
&block::BlockHeader::default(),
|
||||
block_transactions,
|
||||
key::ONE_KEY,
|
||||
&keychain,
|
||||
pubkey,
|
||||
).unwrap();
|
||||
|
||||
chain_ref.apply_block(&block);
|
||||
|
@ -934,16 +935,13 @@ mod tests {
|
|||
expect_output_parent!(read_pool, Parent::BlockTransaction{output: _}, 9, 3);
|
||||
|
||||
// We should have spent blockchain outputs at 4 and 7
|
||||
expect_output_parent!(read_pool,
|
||||
Parent::AlreadySpent{other_tx: _}, 4, 7);
|
||||
expect_output_parent!(read_pool, Parent::AlreadySpent{other_tx: _}, 4, 7);
|
||||
|
||||
// We should have spent pool references at 15
|
||||
expect_output_parent!(read_pool,
|
||||
Parent::AlreadySpent{other_tx: _}, 15);
|
||||
expect_output_parent!(read_pool, Parent::AlreadySpent{other_tx: _}, 15);
|
||||
|
||||
// We should have unspent pool references at 1, 13, 14
|
||||
expect_output_parent!(read_pool,
|
||||
Parent::PoolTransaction{tx_ref: _}, 1, 13, 14);
|
||||
expect_output_parent!(read_pool, Parent::PoolTransaction{tx_ref: _}, 1, 13, 14);
|
||||
|
||||
// References internal to the block should be unknown
|
||||
expect_output_parent!(read_pool, Parent::Unknown, 8);
|
||||
|
@ -982,16 +980,11 @@ mod tests {
|
|||
let mut write_pool = pool.write().unwrap();
|
||||
assert_eq!(write_pool.total_size(), 0);
|
||||
|
||||
assert!(write_pool.add_to_memory_pool(test_source(),
|
||||
root_tx_1).is_ok());
|
||||
assert!(write_pool.add_to_memory_pool(test_source(),
|
||||
root_tx_2).is_ok());
|
||||
assert!(write_pool.add_to_memory_pool(test_source(),
|
||||
root_tx_3).is_ok());
|
||||
assert!(write_pool.add_to_memory_pool(test_source(),
|
||||
child_tx_1).is_ok());
|
||||
assert!(write_pool.add_to_memory_pool(test_source(),
|
||||
child_tx_2).is_ok());
|
||||
assert!(write_pool.add_to_memory_pool(test_source(), root_tx_1).is_ok());
|
||||
assert!(write_pool.add_to_memory_pool(test_source(), root_tx_2).is_ok());
|
||||
assert!(write_pool.add_to_memory_pool(test_source(), root_tx_3).is_ok());
|
||||
assert!(write_pool.add_to_memory_pool(test_source(), child_tx_1).is_ok());
|
||||
assert!(write_pool.add_to_memory_pool(test_source(), child_tx_2).is_ok());
|
||||
|
||||
assert_eq!(write_pool.total_size(), 5);
|
||||
}
|
||||
|
@ -1008,7 +1001,10 @@ mod tests {
|
|||
// prepare_mineable_transactions to return mut refs
|
||||
let block_txs: Vec<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
|
||||
|
|
|
@ -25,4 +25,3 @@ tag="grin_integration_12"
|
|||
|
||||
[dev_dependencies]
|
||||
grin_chain = { path = "../chain"}
|
||||
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
|
|
|
@ -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
3
wallet/rustfmt.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
hard_tabs = true
|
||||
wrap_comments = true
|
||||
write_mode = "Overwrite"
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
})?
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue