Wallet+Keychain refactoring (#1035)

* beginning to refactor keychain into wallet lib

* rustfmt

* more refactor of aggsig lib, simplify aggsig context manager, hold instance statically for now

* clean some warnings

* clean some warnings

* fix wallet send test a bit

* fix core tests, move wallet dependent tests into integration tests

* repair chain tests

* refactor/fix pool tests

* fix wallet tests, moved from keychain

* add wallet tests
This commit is contained in:
Yeastplume
2018-05-09 10:15:58 +01:00
committed by GitHub
parent 982fdea636
commit 4121ea1240
44 changed files with 3599 additions and 3183 deletions
+12 -442
View File
@@ -18,8 +18,8 @@ use time;
use rand::{thread_rng, Rng};
use std::collections::HashSet;
use core::{Commitment, Committed, Input, KernelFeatures, Output, OutputFeatures, Proof,
ProofMessageElements, ShortId, Transaction, TxKernel};
use core::{Commitment, Committed, Input, KernelFeatures, Output, OutputFeatures, Proof, ShortId,
Transaction, TxKernel};
use consensus;
use consensus::{exceeds_weight, reward, VerifySortOrder, REWARD};
use core::hash::{Hash, HashWriter, Hashed, ZERO_HASH};
@@ -30,7 +30,6 @@ use ser::{self, read_and_verify_sorted, Readable, Reader, Writeable, WriteableSo
use global;
use keychain;
use keychain::BlindingFactor;
use util::kernel_sig_msg;
use util::LOGGER;
use util::{secp, static_secp_instance};
@@ -402,14 +401,10 @@ impl Block {
pub fn new(
prev: &BlockHeader,
txs: Vec<&Transaction>,
keychain: &keychain::Keychain,
key_id: &keychain::Identifier,
difficulty: Difficulty,
reward_output: (Output, TxKernel),
) -> Result<Block, Error> {
let fees = txs.iter().map(|tx| tx.fee()).sum();
let (reward_out, reward_proof) =
Block::reward_output(keychain, key_id, fees, prev.height + 1)?;
let block = Block::with_reward(prev, txs, reward_out, reward_proof, difficulty)?;
let block = Block::with_reward(prev, txs, reward_output.0, reward_output.1, difficulty)?;
Ok(block)
}
@@ -705,7 +700,8 @@ impl Block {
Ok(())
}
fn verify_sums(
/// Verify sums
pub fn verify_sums(
&self,
prev_output_sum: &Commitment,
prev_kernel_sum: &Commitment,
@@ -744,12 +740,12 @@ impl Block {
Ok((io_sum, kernel_sum))
}
// Validate the coinbase outputs generated by miners. Entails 2 main checks:
//
// * That the sum of all coinbase-marked outputs equal the supply.
// * That the sum of blinding factors for all coinbase-marked outputs match
// the coinbase-marked kernels.
fn verify_coinbase(&self) -> Result<(), Error> {
/// Validate the coinbase outputs generated by miners. Entails 2 main checks:
///
/// * That the sum of all coinbase-marked outputs equal the supply.
/// * That the sum of blinding factors for all coinbase-marked outputs match
/// the coinbase-marked kernels.
pub fn verify_coinbase(&self) -> Result<(), Error> {
let cb_outs = self.outputs
.iter()
.filter(|out| out.features.contains(OutputFeatures::COINBASE_OUTPUT))
@@ -781,430 +777,4 @@ impl Block {
}
Ok(())
}
/// Builds the blinded output and related signature proof for the block
/// reward.
pub fn reward_output(
keychain: &keychain::Keychain,
key_id: &keychain::Identifier,
fees: u64,
height: u64,
) -> Result<(Output, TxKernel), keychain::Error> {
let value = reward(fees);
let commit = keychain.commit(value, key_id)?;
let msg = ProofMessageElements::new(value, key_id);
trace!(LOGGER, "Block reward - Pedersen Commit is: {:?}", commit,);
let rproof = keychain.range_proof(value, key_id, commit, None, msg.to_proof_message())?;
let output = Output {
features: OutputFeatures::COINBASE_OUTPUT,
commit: commit,
proof: rproof,
};
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
let over_commit = secp.commit_value(reward(fees))?;
let out_commit = output.commitment();
let excess = secp.commit_sum(vec![out_commit], vec![over_commit])?;
// NOTE: Remember we sign the fee *and* the lock_height.
// For a coinbase output the fee is 0 and the lock_height is
// the lock_height of the coinbase output itself,
// not the lock_height of the tx (there is no tx for a coinbase output).
// This output will not be spendable earlier than lock_height (and we sign this
// here).
let msg = secp::Message::from_slice(&kernel_sig_msg(0, height))?;
let sig = keychain.aggsig_sign_from_key_id(&msg, &key_id)?;
let proof = TxKernel {
features: KernelFeatures::COINBASE_KERNEL,
excess: excess,
excess_sig: sig,
fee: 0,
// lock_height here is the height of the block (tx should be valid immediately)
// *not* the lock_height of the coinbase output (only spendable 1,000 blocks later)
lock_height: height,
};
Ok((output, proof))
}
}
#[cfg(test)]
mod test {
use std::time::Instant;
use super::*;
use core::Transaction;
use core::build::{self, input, output, with_fee};
use core::test::{tx1i2o, tx2i1o};
use keychain::{Identifier, Keychain};
use consensus::{BLOCK_OUTPUT_WEIGHT, MAX_BLOCK_WEIGHT};
use util::{secp, secp_static};
// utility to create a block without worrying about the key or previous
// header
fn new_block(
txs: Vec<&Transaction>,
keychain: &Keychain,
previous_header: &BlockHeader,
) -> Block {
let key_id = keychain.derive_key_id(1).unwrap();
Block::new(&previous_header, txs, keychain, &key_id, Difficulty::one()).unwrap()
}
// utility producing a transaction that spends an output with the provided
// value and blinding key
fn txspend1i1o(
v: u64,
keychain: &Keychain,
key_id1: Identifier,
key_id2: Identifier,
) -> Transaction {
build::transaction(
vec![input(v, key_id1), output(3, key_id2), with_fee(2)],
&keychain,
).unwrap()
}
// Too slow for now #[test]
// TODO: make this fast enough or add similar but faster test?
#[allow(dead_code)]
fn too_large_block() {
let keychain = Keychain::from_random_seed().unwrap();
let max_out = MAX_BLOCK_WEIGHT / BLOCK_OUTPUT_WEIGHT;
let zero_commit = secp_static::commit_to_zero_value();
let mut pks = vec![];
for n in 0..(max_out + 1) {
pks.push(keychain.derive_key_id(n as u32).unwrap());
}
let mut parts = vec![];
for _ in 0..max_out {
parts.push(output(5, pks.pop().unwrap()));
}
let now = Instant::now();
parts.append(&mut vec![input(500000, pks.pop().unwrap()), with_fee(2)]);
let mut tx = build::transaction(parts, &keychain).unwrap();
println!("Build tx: {}", now.elapsed().as_secs());
let prev = BlockHeader::default();
let b = new_block(vec![&mut tx], &keychain, &prev);
assert!(b.validate(&zero_commit, &zero_commit).is_err());
}
#[test]
// block with no inputs/outputs/kernels
// no fees, no reward, no coinbase
fn very_empty_block() {
let b = Block {
header: BlockHeader::default(),
inputs: vec![],
outputs: vec![],
kernels: vec![],
};
assert_eq!(
b.verify_coinbase(),
Err(Error::Secp(secp::Error::IncorrectCommitSum))
);
}
#[test]
// builds a block with a tx spending another and check that cut_through occurred
fn block_with_cut_through() {
let keychain = Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
let key_id3 = keychain.derive_key_id(3).unwrap();
let zero_commit = secp_static::commit_to_zero_value();
let mut btx1 = tx2i1o();
let mut btx2 = build::transaction(
vec![input(7, key_id1), output(5, key_id2.clone()), with_fee(2)],
&keychain,
).unwrap();
// spending tx2 - reuse key_id2
let mut btx3 = txspend1i1o(5, &keychain, key_id2.clone(), key_id3);
let prev = BlockHeader::default();
let b = new_block(vec![&mut btx1, &mut btx2, &mut btx3], &keychain, &prev);
// block should have been automatically compacted (including reward
// output) and should still be valid
b.validate(&zero_commit, &zero_commit).unwrap();
assert_eq!(b.inputs.len(), 3);
assert_eq!(b.outputs.len(), 3);
}
#[test]
fn empty_block_with_coinbase_is_valid() {
let keychain = Keychain::from_random_seed().unwrap();
let zero_commit = secp_static::commit_to_zero_value();
let prev = BlockHeader::default();
let b = new_block(vec![], &keychain, &prev);
assert_eq!(b.inputs.len(), 0);
assert_eq!(b.outputs.len(), 1);
assert_eq!(b.kernels.len(), 1);
let coinbase_outputs = b.outputs
.iter()
.filter(|out| out.features.contains(OutputFeatures::COINBASE_OUTPUT))
.map(|o| o.clone())
.collect::<Vec<_>>();
assert_eq!(coinbase_outputs.len(), 1);
let coinbase_kernels = b.kernels
.iter()
.filter(|out| out.features.contains(KernelFeatures::COINBASE_KERNEL))
.map(|o| o.clone())
.collect::<Vec<_>>();
assert_eq!(coinbase_kernels.len(), 1);
// the block should be valid here (single coinbase output with corresponding
// txn kernel)
assert!(b.validate(&zero_commit, &zero_commit).is_ok());
}
#[test]
// test that flipping the COINBASE_OUTPUT flag on the output features
// 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 keychain = Keychain::from_random_seed().unwrap();
let zero_commit = secp_static::commit_to_zero_value();
let prev = BlockHeader::default();
let mut b = new_block(vec![], &keychain, &prev);
assert!(
b.outputs[0]
.features
.contains(OutputFeatures::COINBASE_OUTPUT)
);
b.outputs[0]
.features
.remove(OutputFeatures::COINBASE_OUTPUT);
assert_eq!(b.verify_coinbase(), Err(Error::CoinbaseSumMismatch));
assert!(b.verify_sums(&zero_commit, &zero_commit).is_ok());
assert_eq!(
b.validate(&zero_commit, &zero_commit),
Err(Error::CoinbaseSumMismatch)
);
}
#[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 keychain = Keychain::from_random_seed().unwrap();
let zero_commit = secp_static::commit_to_zero_value();
let prev = BlockHeader::default();
let mut b = new_block(vec![], &keychain, &prev);
assert!(
b.kernels[0]
.features
.contains(KernelFeatures::COINBASE_KERNEL)
);
b.kernels[0]
.features
.remove(KernelFeatures::COINBASE_KERNEL);
assert_eq!(
b.verify_coinbase(),
Err(Error::Secp(secp::Error::IncorrectCommitSum))
);
assert_eq!(
b.validate(&zero_commit, &zero_commit),
Err(Error::Secp(secp::Error::IncorrectCommitSum))
);
}
#[test]
fn serialize_deserialize_block() {
let keychain = Keychain::from_random_seed().unwrap();
let prev = BlockHeader::default();
let b = new_block(vec![], &keychain, &prev);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b).expect("serialization failed");
let b2: Block = ser::deserialize(&mut &vec[..]).unwrap();
assert_eq!(b.header, b2.header);
assert_eq!(b.inputs, b2.inputs);
assert_eq!(b.outputs, b2.outputs);
assert_eq!(b.kernels, b2.kernels);
}
#[test]
fn empty_block_serialized_size() {
let keychain = Keychain::from_random_seed().unwrap();
let prev = BlockHeader::default();
let b = new_block(vec![], &keychain, &prev);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b).expect("serialization failed");
let target_len = 1_216;
assert_eq!(vec.len(), target_len,);
}
#[test]
fn block_single_tx_serialized_size() {
let keychain = Keychain::from_random_seed().unwrap();
let tx1 = tx1i2o();
let prev = BlockHeader::default();
let b = new_block(vec![&tx1], &keychain, &prev);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b).expect("serialization failed");
let target_len = 2_796;
assert_eq!(vec.len(), target_len);
}
#[test]
fn empty_compact_block_serialized_size() {
let keychain = Keychain::from_random_seed().unwrap();
let prev = BlockHeader::default();
let b = new_block(vec![], &keychain, &prev);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b.as_compact_block()).expect("serialization failed");
let target_len = 1_224;
assert_eq!(vec.len(), target_len,);
}
#[test]
fn compact_block_single_tx_serialized_size() {
let keychain = Keychain::from_random_seed().unwrap();
let tx1 = tx1i2o();
let prev = BlockHeader::default();
let b = new_block(vec![&tx1], &keychain, &prev);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b.as_compact_block()).expect("serialization failed");
let target_len = 1_230;
assert_eq!(vec.len(), target_len,);
}
#[test]
fn block_10_tx_serialized_size() {
let keychain = Keychain::from_random_seed().unwrap();
global::set_mining_mode(global::ChainTypes::Mainnet);
let mut txs = vec![];
for _ in 0..10 {
let tx = tx1i2o();
txs.push(tx);
}
let prev = BlockHeader::default();
let b = new_block(txs.iter().collect(), &keychain, &prev);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b).expect("serialization failed");
let target_len = 17_016;
assert_eq!(vec.len(), target_len,);
}
#[test]
fn compact_block_10_tx_serialized_size() {
let keychain = Keychain::from_random_seed().unwrap();
let mut txs = vec![];
for _ in 0..10 {
let tx = tx1i2o();
txs.push(tx);
}
let prev = BlockHeader::default();
let b = new_block(txs.iter().collect(), &keychain, &prev);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b.as_compact_block()).expect("serialization failed");
let target_len = 1_284;
assert_eq!(vec.len(), target_len,);
}
#[test]
fn compact_block_hash_with_nonce() {
let keychain = Keychain::from_random_seed().unwrap();
let tx = tx1i2o();
let prev = BlockHeader::default();
let b = new_block(vec![&tx], &keychain, &prev);
let cb1 = b.as_compact_block();
let cb2 = b.as_compact_block();
// random nonce will not affect the hash of the compact block itself
// hash is based on header POW only
assert!(cb1.nonce != cb2.nonce);
assert_eq!(b.hash(), cb1.hash());
assert_eq!(cb1.hash(), cb2.hash());
assert!(cb1.kern_ids[0] != cb2.kern_ids[0]);
// check we can identify the specified kernel from the short_id
// correctly in both of the compact_blocks
assert_eq!(
cb1.kern_ids[0],
tx.kernels[0].short_id(&cb1.hash(), cb1.nonce)
);
assert_eq!(
cb2.kern_ids[0],
tx.kernels[0].short_id(&cb2.hash(), cb2.nonce)
);
}
#[test]
fn convert_block_to_compact_block() {
let keychain = Keychain::from_random_seed().unwrap();
let tx1 = tx1i2o();
let prev = BlockHeader::default();
let b = new_block(vec![&tx1], &keychain, &prev);
let cb = b.as_compact_block();
assert_eq!(cb.out_full.len(), 1);
assert_eq!(cb.kern_full.len(), 1);
assert_eq!(cb.kern_ids.len(), 1);
assert_eq!(
cb.kern_ids[0],
b.kernels
.iter()
.find(|x| !x.features.contains(KernelFeatures::COINBASE_KERNEL))
.unwrap()
.short_id(&cb.hash(), cb.nonce)
);
}
#[test]
fn hydrate_empty_compact_block() {
let keychain = Keychain::from_random_seed().unwrap();
let prev = BlockHeader::default();
let b = new_block(vec![], &keychain, &prev);
let cb = b.as_compact_block();
let hb = Block::hydrate_from(cb, vec![]);
assert_eq!(hb.header, b.header);
assert_eq!(hb.outputs, b.outputs);
assert_eq!(hb.kernels, b.kernels);
}
#[test]
fn serialize_deserialize_compact_block() {
let b = CompactBlock {
header: BlockHeader::default(),
nonce: 0,
out_full: vec![],
kern_full: vec![],
kern_ids: vec![ShortId::zero()],
};
let mut vec = Vec::new();
ser::serialize(&mut vec, &b).expect("serialization failed");
let b2: CompactBlock = ser::deserialize(&mut &vec[..]).unwrap();
assert_eq!(b.header, b2.header);
assert_eq!(b.kern_ids, b2.kern_ids);
}
}
-316
View File
@@ -1,316 +0,0 @@
// Copyright 2018 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.
//! Utility functions to build Grin transactions. Handles the blinding of
//! inputs and outputs, maintaining the sum of blinding factors, producing
//! the excess signature, etc.
//!
//! Each building function is a combinator that produces a function taking
//! a transaction a sum of blinding factors, to return another transaction
//! and sum. Combinators can then be chained and executed using the
//! _transaction_ function.
//!
//! Example:
//! build::transaction(vec![input_rand(75), output_rand(42), output_rand(32),
//! with_fee(1)])
use util::{kernel_sig_msg, secp};
use core::{Input, Output, OutputFeatures, ProofMessageElements, Transaction, TxKernel};
use core::hash::Hash;
use core::pmmr::MerkleProof;
use keychain;
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
use util::LOGGER;
/// Context information available to transaction combinators.
pub struct Context<'a> {
keychain: &'a Keychain,
}
/// Function type returned by the transaction combinators. Transforms a
/// (Transaction, BlindSum) pair into another, provided some context.
pub type Append = for<'a> Fn(&'a mut Context, (Transaction, TxKernel, BlindSum))
-> (Transaction, TxKernel, BlindSum);
/// Adds an input with the provided value and blinding key to the transaction
/// being built.
fn build_input(
value: u64,
features: OutputFeatures,
block_hash: Option<Hash>,
merkle_proof: Option<MerkleProof>,
key_id: Identifier,
) -> Box<Append> {
Box::new(
move |build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
let commit = build.keychain.commit(value, &key_id).unwrap();
let input = Input::new(features, commit, block_hash.clone(), merkle_proof.clone());
(tx.with_input(input), kern, sum.sub_key_id(key_id.clone()))
},
)
}
/// Adds an input with the provided value and blinding key to the transaction
/// being built.
pub fn input(value: u64, key_id: Identifier) -> Box<Append> {
debug!(
LOGGER,
"Building input (spending regular output): {}, {}", value, key_id
);
build_input(value, OutputFeatures::DEFAULT_OUTPUT, None, None, key_id)
}
/// Adds a coinbase input spending a coinbase output.
/// We will use the block hash to verify coinbase maturity.
pub fn coinbase_input(
value: u64,
block_hash: Hash,
merkle_proof: MerkleProof,
key_id: Identifier,
) -> Box<Append> {
debug!(
LOGGER,
"Building input (spending coinbase): {}, {}", value, key_id
);
build_input(
value,
OutputFeatures::COINBASE_OUTPUT,
Some(block_hash),
Some(merkle_proof),
key_id,
)
}
/// Adds an output with the provided value and key identifier from the
/// keychain.
pub fn output(value: u64, key_id: Identifier) -> Box<Append> {
Box::new(
move |build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
debug!(LOGGER, "Building an output: {}, {}", value, key_id,);
let commit = build.keychain.commit(value, &key_id).unwrap();
trace!(LOGGER, "Builder - Pedersen Commit is: {:?}", commit,);
let msg = ProofMessageElements::new(value, &key_id);
let rproof = build
.keychain
.range_proof(value, &key_id, commit, None, msg.to_proof_message())
.unwrap();
(
tx.with_output(Output {
features: OutputFeatures::DEFAULT_OUTPUT,
commit: commit,
proof: rproof,
}),
kern,
sum.add_key_id(key_id.clone()),
)
},
)
}
/// Sets the fee on the transaction being built.
pub fn with_fee(fee: u64) -> Box<Append> {
Box::new(
move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
(tx, kern.with_fee(fee), sum)
},
)
}
/// Sets the lock_height on the transaction being built.
pub fn with_lock_height(lock_height: u64) -> Box<Append> {
Box::new(
move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
(tx, kern.with_lock_height(lock_height), sum)
},
)
}
/// Adds 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: BlindingFactor) -> Box<Append> {
Box::new(
move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
(tx, kern, sum.add_blinding_factor(excess.clone()))
},
)
}
/// Sets a known tx "offset". Used in final step of tx construction.
pub fn with_offset(offset: BlindingFactor) -> Box<Append> {
Box::new(
move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
(tx.with_offset(offset), kern, sum)
},
)
}
/// Sets an initial transaction to add to when building a new transaction.
/// We currently only support building a tx with a single kernel with build::transaction()
pub fn initial_tx(mut tx: Transaction) -> Box<Append> {
assert_eq!(tx.kernels.len(), 1);
let kern = tx.kernels.remove(0);
Box::new(
move |_build, (_, _, sum)| -> (Transaction, TxKernel, BlindSum) {
(tx.clone(), kern.clone(), sum)
},
)
}
/// Builds a new transaction by combining all the combinators provided in a
/// Vector. Transactions can either be built "from scratch" with a list of
/// inputs or outputs or from a pre-existing transaction that gets added to.
///
/// Example:
/// let (tx1, sum) = build::transaction(vec![input_rand(4), output_rand(1),
/// with_fee(1)], keychain).unwrap();
/// let (tx2, _) = build::transaction(vec![initial_tx(tx1), with_excess(sum),
/// output_rand(2)], keychain).unwrap();
///
pub fn partial_transaction(
elems: Vec<Box<Append>>,
keychain: &keychain::Keychain,
) -> Result<(Transaction, BlindingFactor), keychain::Error> {
let mut ctx = Context { keychain };
let (mut tx, kern, sum) = elems.iter().fold(
(Transaction::empty(), TxKernel::empty(), BlindSum::new()),
|acc, elem| elem(&mut ctx, acc),
);
let blind_sum = ctx.keychain.blind_sum(&sum)?;
// we only support building a tx with a single kernel via build::transaction()
assert!(tx.kernels.is_empty());
tx.kernels.push(kern);
Ok((tx, blind_sum))
}
/// Builds a complete transaction.
pub fn transaction(
elems: Vec<Box<Append>>,
keychain: &keychain::Keychain,
) -> Result<Transaction, keychain::Error> {
let (mut tx, blind_sum) = partial_transaction(elems, keychain)?;
assert_eq!(tx.kernels.len(), 1);
let mut kern = tx.kernels.remove(0);
let msg = secp::Message::from_slice(&kernel_sig_msg(kern.fee, kern.lock_height))?;
let skey = blind_sum.secret_key(&keychain.secp())?;
kern.excess = keychain.secp().commit(0, skey)?;
kern.excess_sig = Keychain::aggsig_sign_with_blinding(&keychain.secp(), &msg, &blind_sum)?;
tx.kernels.push(kern);
Ok(tx)
}
/// Builds a complete transaction, splitting the key and
/// setting the excess, excess_sig and tx offset as necessary.
pub fn transaction_with_offset(
elems: Vec<Box<Append>>,
keychain: &keychain::Keychain,
) -> Result<Transaction, keychain::Error> {
let mut ctx = Context { keychain };
let (mut tx, mut kern, sum) = elems.iter().fold(
(Transaction::empty(), TxKernel::empty(), BlindSum::new()),
|acc, elem| elem(&mut ctx, acc),
);
let blind_sum = ctx.keychain.blind_sum(&sum)?;
let split = blind_sum.split(&keychain.secp())?;
let k1 = split.blind_1;
let k2 = split.blind_2;
let msg = secp::Message::from_slice(&kernel_sig_msg(kern.fee, kern.lock_height))?;
// generate kernel excess and excess_sig using the split key k1
let skey = k1.secret_key(&keychain.secp())?;
kern.excess = ctx.keychain.secp().commit(0, skey)?;
kern.excess_sig = Keychain::aggsig_sign_with_blinding(&keychain.secp(), &msg, &k1)?;
// store the kernel offset (k2) on the tx itself
// commitments will sum correctly when including the offset
tx.offset = k2.clone();
assert!(tx.kernels.is_empty());
tx.kernels.push(kern);
Ok(tx)
}
// Just a simple test, most exhaustive tests in the core mod.rs.
#[cfg(test)]
mod test {
use super::*;
#[test]
fn blind_simple_tx() {
let keychain = Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
let key_id3 = keychain.derive_key_id(3).unwrap();
let tx = transaction(
vec![
input(10, key_id1),
input(12, key_id2),
output(20, key_id3),
with_fee(2),
],
&keychain,
).unwrap();
tx.validate().unwrap();
}
#[test]
fn blind_simple_tx_with_offset() {
let keychain = Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
let key_id3 = keychain.derive_key_id(3).unwrap();
let tx = transaction_with_offset(
vec![
input(10, key_id1),
input(12, key_id2),
output(20, key_id3),
with_fee(2),
],
&keychain,
).unwrap();
tx.validate().unwrap();
}
#[test]
fn blind_simpler_tx() {
let keychain = Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
let tx = transaction(
vec![input(6, key_id1), output(2, key_id2), with_fee(4)],
&keychain,
).unwrap();
tx.validate().unwrap();
}
}
-556
View File
@@ -15,13 +15,11 @@
//! Core types
pub mod block;
pub mod build;
pub mod hash;
pub mod id;
pub mod pmmr;
pub mod target;
pub mod transaction;
// pub mod txoset;
#[allow(dead_code)]
use rand::{thread_rng, Rng};
@@ -274,14 +272,6 @@ pub fn amount_to_hr_string(amount: u64) -> String {
#[cfg(test)]
mod test {
use super::*;
use core::target::Difficulty;
use core::hash::ZERO_HASH;
use core::build::{initial_tx, input, output, with_excess, with_fee, with_lock_height};
use core::block::Error::KernelLockHeight;
use ser;
use keychain;
use keychain::Keychain;
use util::secp_static;
#[test]
pub fn test_amount_to_hr() {
@@ -304,550 +294,4 @@ mod test {
assert!("5000000000.000000000" == amount_to_hr_string(5_000_000_000_000_000_000));
}
#[test]
#[should_panic(expected = "InvalidSecretKey")]
fn test_zero_commit_fails() {
let keychain = Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
// blinding should fail as signing with a zero r*G shouldn't work
build::transaction(
vec![
input(10, key_id1.clone()),
output(9, key_id1.clone()),
with_fee(1),
],
&keychain,
).unwrap();
}
#[test]
fn simple_tx_ser() {
let tx = tx2i1o();
let mut vec = Vec::new();
ser::serialize(&mut vec, &tx).expect("serialization failed");
let target_len = 954;
assert_eq!(vec.len(), target_len,);
}
#[test]
fn simple_tx_ser_deser() {
let tx = tx2i1o();
let mut vec = Vec::new();
ser::serialize(&mut vec, &tx).expect("serialization failed");
let dtx: Transaction = ser::deserialize(&mut &vec[..]).unwrap();
assert_eq!(dtx.fee(), 2);
assert_eq!(dtx.inputs.len(), 2);
assert_eq!(dtx.outputs.len(), 1);
assert_eq!(tx.hash(), dtx.hash());
}
#[test]
fn tx_double_ser_deser() {
// checks serializing doesn't mess up the tx and produces consistent results
let btx = tx2i1o();
let mut vec = Vec::new();
assert!(ser::serialize(&mut vec, &btx).is_ok());
let dtx: Transaction = ser::deserialize(&mut &vec[..]).unwrap();
let mut vec2 = Vec::new();
assert!(ser::serialize(&mut vec2, &btx).is_ok());
let dtx2: Transaction = ser::deserialize(&mut &vec2[..]).unwrap();
assert_eq!(btx.hash(), dtx.hash());
assert_eq!(dtx.hash(), dtx2.hash());
}
#[test]
fn build_tx_kernel() {
let keychain = Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
let key_id3 = keychain.derive_key_id(3).unwrap();
// first build a valid tx with corresponding blinding factor
let tx = build::transaction(
vec![
input(10, key_id1),
output(5, key_id2),
output(3, key_id3),
with_fee(2),
],
&keychain,
).unwrap();
// check the tx is valid
tx.validate().unwrap();
// check the kernel is also itself valid
assert_eq!(tx.kernels.len(), 1);
let kern = &tx.kernels[0];
kern.verify().unwrap();
assert_eq!(kern.features, KernelFeatures::DEFAULT_KERNEL);
assert_eq!(kern.fee, tx.fee());
}
// Combine two transactions into one big transaction (with multiple kernels)
// and check it still validates.
#[test]
fn transaction_cut_through() {
let tx1 = tx1i2o();
let tx2 = tx2i1o();
assert!(tx1.validate().is_ok());
assert!(tx2.validate().is_ok());
// now build a "cut_through" tx from tx1 and tx2
let tx3 = aggregate_with_cut_through(vec![tx1, tx2]).unwrap();
assert!(tx3.validate().is_ok());
}
// Attempt to deaggregate a multi-kernel transaction in a different way
#[test]
fn multi_kernel_transaction_deaggregation() {
let tx1 = tx1i1o();
let tx2 = tx1i1o();
let tx3 = tx1i1o();
let tx4 = tx1i1o();
assert!(tx1.validate().is_ok());
assert!(tx2.validate().is_ok());
assert!(tx3.validate().is_ok());
assert!(tx4.validate().is_ok());
let tx1234 = aggregate(vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()]).unwrap();
let tx12 = aggregate(vec![tx1.clone(), tx2.clone()]).unwrap();
let tx34 = aggregate(vec![tx3.clone(), tx4.clone()]).unwrap();
assert!(tx1234.validate().is_ok());
assert!(tx12.validate().is_ok());
assert!(tx34.validate().is_ok());
let deaggregated_tx34 = deaggregate(tx1234.clone(), vec![tx12.clone()]).unwrap();
assert!(deaggregated_tx34.validate().is_ok());
assert_eq!(tx34, deaggregated_tx34);
let deaggregated_tx12 = deaggregate(tx1234.clone(), vec![tx34.clone()]).unwrap();
assert!(deaggregated_tx12.validate().is_ok());
assert_eq!(tx12, deaggregated_tx12);
}
#[test]
fn multi_kernel_transaction_deaggregation_2() {
let tx1 = tx1i1o();
let tx2 = tx1i1o();
let tx3 = tx1i1o();
assert!(tx1.validate().is_ok());
assert!(tx2.validate().is_ok());
assert!(tx3.validate().is_ok());
let tx123 = aggregate(vec![tx1.clone(), tx2.clone(), tx3.clone()]).unwrap();
let tx12 = aggregate(vec![tx1.clone(), tx2.clone()]).unwrap();
assert!(tx123.validate().is_ok());
assert!(tx12.validate().is_ok());
let deaggregated_tx3 = deaggregate(tx123.clone(), vec![tx12.clone()]).unwrap();
assert!(deaggregated_tx3.validate().is_ok());
assert_eq!(tx3, deaggregated_tx3);
}
#[test]
fn multi_kernel_transaction_deaggregation_3() {
let tx1 = tx1i1o();
let tx2 = tx1i1o();
let tx3 = tx1i1o();
assert!(tx1.validate().is_ok());
assert!(tx2.validate().is_ok());
assert!(tx3.validate().is_ok());
let tx123 = aggregate(vec![tx1.clone(), tx2.clone(), tx3.clone()]).unwrap();
let tx13 = aggregate(vec![tx1.clone(), tx3.clone()]).unwrap();
let tx2 = aggregate(vec![tx2.clone()]).unwrap();
assert!(tx123.validate().is_ok());
assert!(tx2.validate().is_ok());
let deaggregated_tx13 = deaggregate(tx123.clone(), vec![tx2.clone()]).unwrap();
assert!(deaggregated_tx13.validate().is_ok());
assert_eq!(tx13, deaggregated_tx13);
}
#[test]
fn multi_kernel_transaction_deaggregation_4() {
let tx1 = tx1i1o();
let tx2 = tx1i1o();
let tx3 = tx1i1o();
let tx4 = tx1i1o();
let tx5 = tx1i1o();
assert!(tx1.validate().is_ok());
assert!(tx2.validate().is_ok());
assert!(tx3.validate().is_ok());
assert!(tx4.validate().is_ok());
assert!(tx5.validate().is_ok());
let tx12345 = aggregate(vec![
tx1.clone(),
tx2.clone(),
tx3.clone(),
tx4.clone(),
tx5.clone(),
]).unwrap();
assert!(tx12345.validate().is_ok());
let deaggregated_tx5 = deaggregate(
tx12345.clone(),
vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()],
).unwrap();
assert!(deaggregated_tx5.validate().is_ok());
assert_eq!(tx5, deaggregated_tx5);
}
#[test]
fn multi_kernel_transaction_deaggregation_5() {
let tx1 = tx1i1o();
let tx2 = tx1i1o();
let tx3 = tx1i1o();
let tx4 = tx1i1o();
let tx5 = tx1i1o();
assert!(tx1.validate().is_ok());
assert!(tx2.validate().is_ok());
assert!(tx3.validate().is_ok());
assert!(tx4.validate().is_ok());
assert!(tx5.validate().is_ok());
let tx12345 = aggregate(vec![
tx1.clone(),
tx2.clone(),
tx3.clone(),
tx4.clone(),
tx5.clone(),
]).unwrap();
let tx12 = aggregate(vec![tx1.clone(), tx2.clone()]).unwrap();
let tx34 = aggregate(vec![tx3.clone(), tx4.clone()]).unwrap();
assert!(tx12345.validate().is_ok());
let deaggregated_tx5 =
deaggregate(tx12345.clone(), vec![tx12.clone(), tx34.clone()]).unwrap();
assert!(deaggregated_tx5.validate().is_ok());
assert_eq!(tx5, deaggregated_tx5);
}
// Attempt to deaggregate a multi-kernel transaction
#[test]
fn basic_transaction_deaggregation() {
let tx1 = tx1i2o();
let tx2 = tx2i1o();
assert!(tx1.validate().is_ok());
assert!(tx2.validate().is_ok());
// now build a "cut_through" tx from tx1 and tx2
let tx3 = aggregate(vec![tx1.clone(), tx2.clone()]).unwrap();
assert!(tx3.validate().is_ok());
let deaggregated_tx1 = deaggregate(tx3.clone(), vec![tx2.clone()]).unwrap();
assert!(deaggregated_tx1.validate().is_ok());
assert_eq!(tx1, deaggregated_tx1);
let deaggregated_tx2 = deaggregate(tx3.clone(), vec![tx1.clone()]).unwrap();
assert!(deaggregated_tx2.validate().is_ok());
assert_eq!(tx2, deaggregated_tx2);
}
#[test]
fn hash_output() {
let keychain = Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
let key_id3 = keychain.derive_key_id(3).unwrap();
let tx = build::transaction(
vec![
input(75, key_id1),
output(42, key_id2),
output(32, key_id3),
with_fee(1),
],
&keychain,
).unwrap();
let h = tx.outputs[0].hash();
assert!(h != ZERO_HASH);
let h2 = tx.outputs[1].hash();
assert!(h != h2);
}
#[ignore]
#[test]
fn blind_tx() {
let btx = tx2i1o();
assert!(btx.validate().is_ok());
// Ignored for bullet proofs, because calling range_proof_info
// with a bullet proof causes painful errors
// checks that the range proof on our blind output is sufficiently hiding
let Output { proof, .. } = btx.outputs[0];
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
let info = secp.range_proof_info(proof);
assert!(info.min == 0);
assert!(info.max == u64::max_value());
}
#[test]
fn tx_hash_diff() {
let btx1 = tx2i1o();
let btx2 = tx1i1o();
if btx1.hash() == btx2.hash() {
panic!("diff txs have same hash")
}
}
/// Simulate the standard exchange between 2 parties when creating a basic
/// 2 inputs, 2 outputs transaction.
#[test]
fn tx_build_exchange() {
let keychain = Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
let key_id3 = keychain.derive_key_id(3).unwrap();
let key_id4 = keychain.derive_key_id(4).unwrap();
let (tx_alice, blind_sum) = {
// 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(4, key_id1), input(3, key_id2));
// Alice builds her transaction, with change, which also produces the sum
// of blinding factors before they're obscured.
let (tx, sum) = build::partial_transaction(
vec![in1, in2, output(1, key_id3), with_fee(2)],
&keychain,
).unwrap();
(tx, sum)
};
// 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(4, key_id4),
],
&keychain,
).unwrap();
tx_final.validate().unwrap();
}
#[test]
fn reward_empty_block() {
let keychain = keychain::Keychain::from_random_seed().unwrap();
let key_id = keychain.derive_key_id(1).unwrap();
let zero_commit = secp_static::commit_to_zero_value();
let previous_header = BlockHeader::default();
let b = Block::new(
&previous_header,
vec![],
&keychain,
&key_id,
Difficulty::one(),
).unwrap();
b.cut_through()
.validate(&zero_commit, &zero_commit)
.unwrap();
}
#[test]
fn reward_with_tx_block() {
let keychain = keychain::Keychain::from_random_seed().unwrap();
let key_id = keychain.derive_key_id(1).unwrap();
let zero_commit = secp_static::commit_to_zero_value();
let mut tx1 = tx2i1o();
tx1.validate().unwrap();
let previous_header = BlockHeader::default();
let block = Block::new(
&previous_header,
vec![&mut tx1],
&keychain,
&key_id,
Difficulty::one(),
).unwrap();
block
.cut_through()
.validate(&zero_commit, &zero_commit)
.unwrap();
}
#[test]
fn simple_block() {
let keychain = keychain::Keychain::from_random_seed().unwrap();
let key_id = keychain.derive_key_id(1).unwrap();
let zero_commit = secp_static::commit_to_zero_value();
let mut tx1 = tx2i1o();
let mut tx2 = tx1i1o();
let previous_header = BlockHeader::default();
let b = Block::new(
&previous_header,
vec![&mut tx1, &mut tx2],
&keychain,
&key_id,
Difficulty::one(),
).unwrap();
b.validate(&zero_commit, &zero_commit).unwrap();
}
#[test]
fn test_block_with_timelocked_tx() {
let keychain = keychain::Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
let key_id3 = keychain.derive_key_id(3).unwrap();
let zero_commit = secp_static::commit_to_zero_value();
// first check we can add a timelocked tx where lock height matches current
// block height and that the resulting block is valid
let tx1 = build::transaction(
vec![
input(5, key_id1.clone()),
output(3, key_id2.clone()),
with_fee(2),
with_lock_height(1),
],
&keychain,
).unwrap();
let previous_header = BlockHeader::default();
let b = Block::new(
&previous_header,
vec![&tx1],
&keychain,
&key_id3.clone(),
Difficulty::one(),
).unwrap();
b.validate(&zero_commit, &zero_commit).unwrap();
// now try adding a timelocked tx where lock height is greater than current
// block height
let tx1 = build::transaction(
vec![
input(5, key_id1.clone()),
output(3, key_id2.clone()),
with_fee(2),
with_lock_height(2),
],
&keychain,
).unwrap();
let previous_header = BlockHeader::default();
let b = Block::new(
&previous_header,
vec![&tx1],
&keychain,
&key_id3.clone(),
Difficulty::one(),
).unwrap();
match b.validate(&zero_commit, &zero_commit) {
Err(KernelLockHeight(height)) => {
assert_eq!(height, 2);
}
_ => panic!("expecting KernelLockHeight error here"),
}
}
#[test]
pub fn test_verify_1i1o_sig() {
let tx = tx1i1o();
tx.validate().unwrap();
}
#[test]
pub fn test_verify_2i1o_sig() {
let tx = tx2i1o();
tx.validate().unwrap();
}
// utility producing a transaction with 2 inputs and a single outputs
pub fn tx2i1o() -> Transaction {
let keychain = keychain::Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
let key_id3 = keychain.derive_key_id(3).unwrap();
build::transaction_with_offset(
vec![
input(10, key_id1),
input(11, key_id2),
output(19, key_id3),
with_fee(2),
],
&keychain,
).unwrap()
}
// utility producing a transaction with a single input and output
pub fn tx1i1o() -> Transaction {
let keychain = keychain::Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
build::transaction_with_offset(
vec![input(5, key_id1), output(3, key_id2), with_fee(2)],
&keychain,
).unwrap()
}
// utility producing a transaction with a single input
// and two outputs (one change output)
// Note: this tx has an "offset" kernel
pub fn tx1i2o() -> Transaction {
let keychain = keychain::Keychain::from_random_seed().unwrap();
let key_id1 = keychain.derive_key_id(1).unwrap();
let key_id2 = keychain.derive_key_id(2).unwrap();
let key_id3 = keychain.derive_key_id(3).unwrap();
build::transaction_with_offset(
vec![
input(6, key_id1),
output(3, key_id2),
output(1, key_id3),
with_fee(2),
],
&keychain,
).unwrap()
}
}
+11 -26
View File
@@ -30,7 +30,7 @@ use core::BlockHeader;
use core::hash::{Hash, Hashed, ZERO_HASH};
use core::pmmr::MerkleProof;
use keychain;
use keychain::{BlindingFactor, Keychain};
use keychain::BlindingFactor;
use ser::{self, read_and_verify_sorted, ser_vec, PMMRable, Readable, Reader, Writeable,
WriteableSorted, Writer};
use util;
@@ -186,7 +186,15 @@ impl TxKernel {
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
let sig = &self.excess_sig;
let valid = Keychain::aggsig_verify_single_from_commit(&secp, &sig, &msg, &self.excess);
// Verify aggsig directly in libsecp
let pubkeys = &self.excess.to_two_pubkeys(&secp);
let mut valid = false;
for i in 0..pubkeys.len() {
valid = secp::aggsig::verify_single(&secp, &sig, &msg, None, &pubkeys[i], false);
if valid {
break;
}
}
if !valid {
return Err(secp::Error::IncorrectSignature);
}
@@ -947,7 +955,7 @@ impl Output {
pub fn verify_proof(&self) -> Result<(), secp::Error> {
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
match Keychain::verify_range_proof(&secp, self.commit, self.proof, None) {
match secp.verify_bullet_proof(self.commit, self.proof, None) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
@@ -1196,29 +1204,6 @@ mod test {
assert_eq!(kernel2.fee, 10);
}
#[test]
fn test_output_ser_deser() {
let keychain = Keychain::from_random_seed().unwrap();
let key_id = keychain.derive_key_id(1).unwrap();
let commit = keychain.commit(5, &key_id).unwrap();
let msg = secp::pedersen::ProofMessage::empty();
let proof = keychain.range_proof(5, &key_id, commit, None, msg).unwrap();
let out = Output {
features: OutputFeatures::DEFAULT_OUTPUT,
commit: commit,
proof: proof,
};
let mut vec = vec![];
ser::serialize(&mut vec, &out).expect("serialized failed");
let dout: Output = ser::deserialize(&mut &vec[..]).unwrap();
assert_eq!(dout.features, OutputFeatures::DEFAULT_OUTPUT);
assert_eq!(dout.commit, out.commit);
assert_eq!(dout.proof, out.proof);
}
#[test]
fn commit_consistency() {
let keychain = Keychain::from_seed(&[0; 32]).unwrap();
-1
View File
@@ -120,7 +120,6 @@ mod test {
use global;
use core::target::Difficulty;
use genesis;
use global::ChainTypes;
/// We'll be generating genesis blocks differently
#[ignore]