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:
+12
-442
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user