add lock_height to Transaction and TxKernel (#167)

* add lock_height to Transaction and TxKernel, sign msg including both fee and lock_height in kernel
* make the order of the fields in tx and kernel more consistent
* rename to kernel_sig_msg
* add test to cover adding timelocked tx to pool, where tx is invalid based on current height of the blockchain
* add tests for adding timelocked txs to blocks (valid and otherwise)
This commit is contained in:
AntiochP
2017-10-11 14:12:01 -04:00
committed by Ignotus Peverell
parent dc0dbc62be
commit bf7c1fb44f
20 changed files with 432 additions and 167 deletions
+25 -8
View File
@@ -39,8 +39,12 @@ pub enum Error {
OddKernelFee,
/// Too many inputs, outputs or kernels in the block
WeightExceeded,
/// Underlying Secp256k1 error (signature validation or invalid public
/// key typically)
/// Kernel not valid due to lock_height exceeding block header height
KernelLockHeight {
/// The lock_height causing this validation error
lock_height: u64,
},
/// Underlying Secp256k1 error (signature validation or invalid public key typically)
Secp(secp::Error),
}
@@ -252,13 +256,15 @@ impl Block {
/// Builds a new block from the header of the previous block, a vector of
/// transactions and the private key that will receive the reward. Checks
/// that all transactions are valid and calculates the Merkle tree.
///
/// Only used in tests (to be confirmed, may be wrong here).
///
pub fn new(
prev: &BlockHeader,
txs: Vec<&Transaction>,
keychain: &keychain::Keychain,
pubkey: &keychain::Identifier,
) -> Result<Block, keychain::Error> {
let fees = txs.iter().map(|tx| tx.fee).sum();
let (reward_out, reward_proof) = Block::reward_output(keychain, pubkey, fees)?;
let block = Block::with_reward(prev, txs, reward_out, reward_proof)?;
@@ -415,6 +421,9 @@ impl Block {
/// Validates all the elements in a block that can be checked without
/// additional data. Includes commitment sums and kernels, Merkle
/// trees, reward, etc.
///
/// TODO - performs various verification steps - discuss renaming this to "verify"
///
pub fn validate(&self, secp: &Secp256k1) -> Result<(), Error> {
if exceeds_weight(self.inputs.len(), self.outputs.len(), self.kernels.len()) {
return Err(Error::WeightExceeded);
@@ -424,14 +433,20 @@ impl Block {
Ok(())
}
/// Validate the sum of input/output commitments match the sum in kernels
/// Verifies the sum of input/output commitments match the sum in kernels
/// and that all kernel signatures are valid.
/// TODO - when would we skip_sig? Is this needed or used anywhere?
fn verify_kernels(&self, secp: &Secp256k1, skip_sig: bool) -> Result<(), Error> {
for k in &self.kernels {
if k.fee & 1 != 0 {
return Err(Error::OddKernelFee);
}
if k.lock_height > self.header.height {
return Err(Error::KernelLockHeight { lock_height: k.lock_height });
}
}
// sum all inputs and outs commitments
let io_sum = self.sum_commitments(secp)?;
@@ -483,8 +498,7 @@ impl Block {
Ok(())
}
/// Builds the blinded output and related signature proof for the block
/// reward.
/// Builds the blinded output and related signature proof for the block reward.
pub fn reward_output(
keychain: &keychain::Keychain,
pubkey: &keychain::Identifier,
@@ -515,6 +529,7 @@ impl Block {
excess: excess,
excess_sig: sig.serialize_der(&secp),
fee: 0,
lock_height: 0,
};
Ok((output, proof))
}
@@ -553,7 +568,7 @@ mod test {
let max_out = MAX_BLOCK_WEIGHT / BLOCK_OUTPUT_WEIGHT;
let mut pks = vec![];
for n in 0..(max_out+1) {
for n in 0..(max_out + 1) {
pks.push(keychain.derive_pubkey(n as u32).unwrap());
}
@@ -564,7 +579,9 @@ mod test {
let now = Instant::now();
parts.append(&mut vec![input(500000, pks.pop().unwrap()), with_fee(2)]);
let mut tx = build::transaction(parts, &keychain).map(|(tx, _)| tx).unwrap();
let mut tx = build::transaction(parts, &keychain)
.map(|(tx, _)| tx)
.unwrap();
println!("Build tx: {}", now.elapsed().as_secs());
let b = new_block(vec![&mut tx], &keychain);
+27 -21
View File
@@ -25,10 +25,10 @@
//! build::transaction(vec![input_rand(75), output_rand(42), output_rand(32),
//! with_fee(1)])
use byteorder::{ByteOrder, BigEndian};
use secp;
use core::{Transaction, Input, Output, DEFAULT_OUTPUT};
use core::transaction::kernel_sig_msg;
use keychain;
use keychain::{Keychain, BlindSum, BlindingFactor, Identifier};
@@ -56,13 +56,19 @@ pub fn output(value: u64, pubkey: Identifier) -> Box<Append> {
Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) {
let commit = build.keychain.commit(value, &pubkey).unwrap();
let msg = secp::pedersen::ProofMessage::empty();
let rproof = build.keychain.range_proof(value, &pubkey, commit, msg).unwrap();
let rproof = build
.keychain
.range_proof(value, &pubkey, commit, msg)
.unwrap();
(tx.with_output(Output {
features: DEFAULT_OUTPUT,
commit: commit,
proof: rproof,
}), sum.add_pubkey(pubkey.clone()))
(
tx.with_output(Output {
features: DEFAULT_OUTPUT,
commit: commit,
proof: rproof,
}),
sum.add_pubkey(pubkey.clone()),
)
})
}
@@ -73,6 +79,13 @@ pub fn with_fee(fee: u64) -> Box<Append> {
})
}
/// Sets the lock_height on the transaction being built.
pub fn with_lock_height(lock_height: u64) -> Box<Append> {
Box::new(move |_build, (tx, sum)| -> (Transaction, BlindSum) {
(tx.with_lock_height(lock_height), sum)
})
}
/// 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.
@@ -95,9 +108,9 @@ pub fn initial_tx(tx: Transaction) -> Box<Append> {
///
/// Example:
/// let (tx1, sum) = build::transaction(vec![input_rand(4), output_rand(1),
/// with_fee(1)]).unwrap();
/// with_fee(1)], keychain).unwrap();
/// let (tx2, _) = build::transaction(vec![initial_tx(tx1), with_excess(sum),
/// output_rand(2)]).unwrap();
/// output_rand(2)], keychain).unwrap();
///
pub fn transaction(
elems: Vec<Box<Append>>,
@@ -105,21 +118,16 @@ pub fn transaction(
) -> 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 = ctx.keychain.blind_sum(&sum)?;
let msg = secp::Message::from_slice(&u64_to_32bytes(tx.fee))?;
let msg = secp::Message::from_slice(&kernel_sig_msg(tx.fee, tx.lock_height))?;
let sig = ctx.keychain.sign_with_blinding(&msg, &blind_sum)?;
tx.excess_sig = sig.serialize_der(&ctx.keychain.secp());
Ok((tx, blind_sum))
}
fn u64_to_32bytes(n: u64) -> [u8; 32] {
let mut bytes = [0; 32];
BigEndian::write_u64(&mut bytes[24..32], n);
bytes
}
// Just a simple test, most exhaustive tests in the core mod.rs.
#[cfg(test)]
mod test {
@@ -146,10 +154,8 @@ mod test {
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();
let (tx, _) = transaction(vec![input(6, pk1), output(2, pk2), with_fee(4)], &keychain)
.unwrap();
tx.verify_sig(&keychain.secp()).unwrap();
}
+80 -37
View File
@@ -186,7 +186,8 @@ impl Writeable for Proof {
mod test {
use super::*;
use core::hash::ZERO_HASH;
use core::build::{input, output, with_fee, initial_tx, with_excess};
use core::build::{input, output, with_fee, initial_tx, with_excess, with_lock_height};
use core::block::Error::KernelLockHeight;
use ser;
use keychain;
use keychain::{Keychain, BlindingFactor};
@@ -209,8 +210,8 @@ mod test {
let tx = tx2i1o();
let mut vec = Vec::new();
ser::serialize(&mut vec, &tx).expect("serialized failed");
assert!(vec.len() > 5320);
assert!(vec.len() < 5340);
assert!(vec.len() > 5340);
assert!(vec.len() < 5360);
}
#[test]
@@ -249,11 +250,15 @@ mod test {
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 (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();
@@ -304,10 +309,8 @@ mod test {
// 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(1, pk3), with_fee(2)],
&keychain,
).unwrap();
let (tx, sum) =
build::transaction(vec![in1, in2, output(1, pk3), with_fee(2)], &keychain).unwrap();
tx_alice = tx;
blind_sum = sum;
}
@@ -315,31 +318,26 @@ 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(4, pk4)],
&keychain,
).unwrap();
let (tx_final, _) = build::transaction(
vec![initial_tx(tx_alice), with_excess(blind_sum), output(4, pk4)],
&keychain,
).unwrap();
tx_final.validate(&keychain.secp()).unwrap();
}
#[test]
fn reward_empty_block() {
let keychain = new_keychain();
let keychain = keychain::Keychain::from_random_seed().unwrap();
let pubkey = keychain.derive_pubkey(1).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 keychain = new_keychain();
let keychain = keychain::Keychain::from_random_seed().unwrap();
let pubkey = keychain.derive_pubkey(1).unwrap();
let mut tx1 = tx2i1o();
@@ -351,36 +349,81 @@ mod test {
#[test]
fn simple_block() {
let keychain = new_keychain();
let keychain = keychain::Keychain::from_random_seed().unwrap();
let pubkey = keychain.derive_pubkey(1).unwrap();
let mut tx1 = tx2i1o();
tx1.verify_sig(keychain.secp()).unwrap();
let mut tx2 = tx1i1o();
tx2.verify_sig(keychain.secp()).unwrap();
let b = Block::new(&BlockHeader::default(), vec![&mut tx1, &mut tx2], &keychain, &pubkey).unwrap();
let b = Block::new(
&BlockHeader::default(),
vec![&mut tx1, &mut tx2],
&keychain,
&pubkey,
).unwrap();
b.validate(keychain.secp()).unwrap();
}
#[test]
fn test_block_with_timelocked_tx() {
let keychain = 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();
// 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, pk1.clone()), output(3, pk2.clone()), with_fee(2), with_lock_height(1)],
&keychain,
).map(|(tx, _)| tx).unwrap();
let b = Block::new(
&BlockHeader::default(),
vec![&tx1],
&keychain,
&pk3.clone(),
).unwrap();
b.validate(keychain.secp()).unwrap();
// now try adding a timelocked tx where lock height is greater than current block height
let tx1 = build::transaction(
vec![input(5, pk1.clone()), output(3, pk2.clone()), with_fee(2), with_lock_height(2)],
&keychain,
).map(|(tx, _)| tx).unwrap();
let b = Block::new(
&BlockHeader::default(),
vec![&tx1],
&keychain,
&pk3.clone(),
).unwrap();
match b.validate(keychain.secp()) {
Err(KernelLockHeight{ lock_height: height}) => {
assert_eq!(height, 2);
},
_ => panic!("expecting KernelLockHeight error here"),
}
}
#[test]
pub fn test_verify_1i1o_sig() {
let keychain = new_keychain();
let keychain = keychain::Keychain::from_random_seed().unwrap();
let tx = tx1i1o();
tx.verify_sig(keychain.secp()).unwrap();
}
#[test]
pub fn test_verify_2i1o_sig() {
let keychain = new_keychain();
let keychain = keychain::Keychain::from_random_seed().unwrap();
let tx = tx2i1o();
tx.verify_sig(keychain.secp()).unwrap();
}
// utility producing a transaction with 2 inputs and a single outputs
pub fn tx2i1o() -> Transaction {
let keychain = new_keychain();
let keychain = 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();
@@ -388,18 +431,18 @@ mod test {
build::transaction(
vec![input(10, pk1), input(11, pk2), output(19, pk3), with_fee(2)],
&keychain,
).map(|(tx, _)| tx).unwrap()
).map(|(tx, _)| tx)
.unwrap()
}
// utility producing a transaction with a single input and output
pub fn tx1i1o() -> Transaction {
let keychain = new_keychain();
let keychain = keychain::Keychain::from_random_seed().unwrap();
let pk1 = keychain.derive_pubkey(1).unwrap();
let pk2 = keychain.derive_pubkey(2).unwrap();
build::transaction(
vec![input(5, pk1), output(3, pk2), with_fee(2)],
&keychain,
).map(|(tx, _)| tx).unwrap()
build::transaction(vec![input(5, pk1), output(3, pk2), with_fee(2)], &keychain)
.map(|(tx, _)| tx)
.unwrap()
}
}
+5 -7
View File
@@ -1,4 +1,4 @@
// Copyright 2016 The Grin Developers
// Copyright 2017 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,11 +15,7 @@
//! Persistent and prunable Merkle Mountain Range implementation. For a high
//! level description of MMRs, see:
//!
//! https://github.
//! com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.
//!
//!
//! md
//! https://github.com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.md
//!
//! This implementation is built in two major parts:
//!
@@ -648,7 +644,9 @@ fn peaks(num: u64) -> Vec<u64> {
/// any node, from its postorder traversal position. Which is the order in which
/// nodes are added in a MMR.
///
/// [1] https://github.com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.md
/// [1] https://github.
/// com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.
/// md
pub fn bintree_postorder_height(num: u64) -> u64 {
let mut h = num;
while !all_ones(h) {
+159 -52
View File
@@ -25,13 +25,13 @@ use keychain::{Identifier, Keychain};
use ser::{self, Reader, Writer, Readable, Writeable};
bitflags! {
/// Options for a kernel's structure or use
pub flags KernelFeatures: u8 {
/// No flags
const DEFAULT_KERNEL = 0b00000000,
/// Kernel matching a coinbase output
const COINBASE_KERNEL = 0b00000001,
}
/// Options for a kernel's structure or use
pub flags KernelFeatures: u8 {
/// No flags
const DEFAULT_KERNEL = 0b00000000,
/// Kernel matching a coinbase output
const COINBASE_KERNEL = 0b00000001,
}
}
/// Errors thrown by Block validation
@@ -50,14 +50,28 @@ impl From<secp::Error> for Error {
}
}
/// Construct msg bytes from tx fee and lock_height
pub fn kernel_sig_msg(fee: u64, lock_height: u64) -> [u8; 32] {
let mut bytes = [0; 32];
BigEndian::write_u64(&mut bytes[16..24], fee);
BigEndian::write_u64(&mut bytes[24..], lock_height);
bytes
}
/// A proof that a transaction sums to zero. Includes both the transaction's
/// Pedersen commitment and the signature, that guarantees that the commitments
/// amount to zero. The signature signs the fee, which is retained for
/// amount to zero.
/// The signature signs the fee and the lock_height, which are retained for
/// signature validation.
#[derive(Debug, Clone, PartialEq)]
pub struct TxKernel {
/// Options for a kernel's structure or use
pub features: KernelFeatures,
/// Fee originally included in the transaction this proof is for.
pub fee: u64,
/// This kernel is not valid earlier than lock_height blocks
/// The max lock_height of all *inputs* to this transaction
pub lock_height: u64,
/// Remainder of the sum of all transaction commitments. If the transaction
/// is well formed, amounts components should sum to zero and the excess
/// is hence a valid public key.
@@ -65,8 +79,6 @@ pub struct TxKernel {
/// The signature proving the excess is a valid public key, which signs
/// the transaction fee.
pub excess_sig: Vec<u8>,
/// Fee originally included in the transaction this proof is for.
pub fee: u64,
}
impl Writeable for TxKernel {
@@ -74,9 +86,10 @@ impl Writeable for TxKernel {
ser_multiwrite!(
writer,
[write_u8, self.features.bits()],
[write_u64, self.fee],
[write_u64, self.lock_height],
[write_fixed_bytes, &self.excess],
[write_bytes, &self.excess_sig],
[write_u64, self.fee]
[write_bytes, &self.excess_sig]
);
Ok(())
}
@@ -84,13 +97,16 @@ impl Writeable for TxKernel {
impl Readable for TxKernel {
fn read(reader: &mut Reader) -> Result<TxKernel, ser::Error> {
let features = KernelFeatures::from_bits(reader.read_u8()?).ok_or(
ser::Error::CorruptedData,
)?;
Ok(TxKernel {
features: KernelFeatures::from_bits(reader.read_u8()?).ok_or(
ser::Error::CorruptedData,
)?,
features: features,
fee: reader.read_u64()?,
lock_height: reader.read_u64()?,
excess: Commitment::read(reader)?,
excess_sig: reader.read_vec()?,
fee: reader.read_u64()?,
})
}
}
@@ -100,7 +116,9 @@ impl TxKernel {
/// as a public key and checking the signature verifies with the fee as
/// message.
pub fn verify(&self, secp: &Secp256k1) -> Result<(), secp::Error> {
let msg = try!(Message::from_slice(&u64_to_32bytes(self.fee)));
let msg = try!(Message::from_slice(
&kernel_sig_msg(self.fee, self.lock_height),
));
let sig = try!(Signature::from_der(secp, &self.excess_sig));
secp.verify_from_commit(&msg, &sig, &self.excess)
}
@@ -115,6 +133,9 @@ pub struct Transaction {
pub outputs: Vec<Output>,
/// Fee paid by the transaction.
pub fee: u64,
/// Transaction is not valid before this block height.
/// It is invalid for this to be less than the lock_height of any UTXO being spent.
pub lock_height: u64,
/// The signature proving the excess is a valid public key, which signs
/// the transaction fee.
pub excess_sig: Vec<u8>,
@@ -127,6 +148,7 @@ impl Writeable for Transaction {
ser_multiwrite!(
writer,
[write_u64, self.fee],
[write_u64, self.lock_height],
[write_bytes, &self.excess_sig],
[write_u64, self.inputs.len() as u64],
[write_u64, self.outputs.len() as u64]
@@ -145,14 +167,15 @@ impl Writeable for Transaction {
/// transaction from a binary stream.
impl Readable for Transaction {
fn read(reader: &mut Reader) -> Result<Transaction, ser::Error> {
let (fee, excess_sig, input_len, output_len) =
ser_multiread!(reader, read_u64, read_vec, read_u64, read_u64);
let (fee, lock_height, excess_sig, input_len, output_len) =
ser_multiread!(reader, read_u64, read_u64, read_vec, read_u64, read_u64);
let inputs = try!((0..input_len).map(|_| Input::read(reader)).collect());
let outputs = try!((0..output_len).map(|_| Output::read(reader)).collect());
Ok(Transaction {
fee: fee,
lock_height: lock_height,
excess_sig: excess_sig,
inputs: inputs,
outputs: outputs,
@@ -185,17 +208,24 @@ impl Transaction {
pub fn empty() -> Transaction {
Transaction {
fee: 0,
lock_height: 0,
excess_sig: vec![],
inputs: vec![],
outputs: vec![],
}
}
/// Creates a new transaction initialized with the provided inputs,
/// outputs and fee.
pub fn new(inputs: Vec<Input>, outputs: Vec<Output>, fee: u64) -> Transaction {
/// Creates a new transaction initialized with
/// the provided inputs, outputs, fee and lock_height.
pub fn new(
inputs: Vec<Input>,
outputs: Vec<Output>,
fee: u64,
lock_height: u64,
) -> Transaction {
Transaction {
fee: fee,
lock_height: lock_height,
excess_sig: vec![],
inputs: inputs,
outputs: outputs,
@@ -229,6 +259,14 @@ impl Transaction {
Transaction { fee: fee, ..self }
}
/// Builds a new transaction with the provided lock_height.
pub fn with_lock_height(self, lock_height: u64) -> Transaction {
Transaction {
lock_height: lock_height,
..self
}
}
/// The verification for a MimbleWimble transaction involves getting the
/// excess of summing all commitments and using it as a public key
/// to verify the embedded signature. The rational is that if the values
@@ -238,7 +276,7 @@ impl Transaction {
pub fn verify_sig(&self, secp: &Secp256k1) -> Result<TxKernel, secp::Error> {
let rsum = self.sum_commitments(secp)?;
let msg = Message::from_slice(&u64_to_32bytes(self.fee))?;
let msg = Message::from_slice(&kernel_sig_msg(self.fee, self.lock_height))?;
let sig = Signature::from_der(secp, &self.excess_sig)?;
// pretend the sum is a public key (which it is, being of the form r.G) and
@@ -250,12 +288,20 @@ impl Transaction {
// of generating a public key from a commitment behind verify_from_commit
secp.verify_from_commit(&msg, &sig, &rsum)?;
Ok(TxKernel {
let kernel = TxKernel {
features: DEFAULT_KERNEL,
excess: rsum,
excess_sig: self.excess_sig.clone(),
fee: self.fee,
})
lock_height: self.lock_height,
};
debug!(
"tx verify_sig: fee - {}, lock_height - {}",
kernel.fee,
kernel.lock_height
);
Ok(kernel)
}
/// Validates all relevant parts of a fully built transaction. Checks the
@@ -303,14 +349,14 @@ impl Input {
}
bitflags! {
/// Options for block validation
#[derive(Serialize, Deserialize)]
pub flags OutputFeatures: u8 {
/// No flags
const DEFAULT_OUTPUT = 0b00000000,
/// Output is a coinbase output, has fixed amount and must not be spent until maturity
const COINBASE_OUTPUT = 0b00000001,
}
/// Options for block validation
#[derive(Serialize, Deserialize)]
pub flags OutputFeatures: u8 {
/// No flags
const DEFAULT_OUTPUT = 0b00000000,
/// Output is a coinbase output, must not be spent until maturity
const COINBASE_OUTPUT = 0b00000001,
}
}
/// Output for a transaction, defining the new ownership of coins that are being
@@ -318,9 +364,8 @@ bitflags! {
/// range proof guarantees the commitment includes a positive value without
/// overflow and the ownership of the private key.
///
/// The hash of an output only covers its features and commitment. The range
/// proof is expected to have its own hash and is stored and committed to
/// separately.
/// The hash of an output only covers its features, lock_height and commitment.
/// The range proof is expected to have its own hash and is stored and committed to separately.
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct Output {
/// Options for an output's structure or use
@@ -335,11 +380,9 @@ pub struct Output {
/// an Output as binary.
impl Writeable for Output {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
ser_multiwrite!(
writer,
[write_u8, self.features.bits()],
[write_fixed_bytes, &self.commit]
);
writer.write_u8(self.features.bits())?;
writer.write_fixed_bytes(&self.commit)?;
// The hash of an output doesn't include the range proof
if writer.serialization_mode() == ser::SerializationMode::Full {
writer.write_bytes(&self.proof)?
@@ -352,10 +395,12 @@ impl Writeable for Output {
/// an Output from a binary stream.
impl Readable for Output {
fn read(reader: &mut Reader) -> Result<Output, ser::Error> {
let features = OutputFeatures::from_bits(reader.read_u8()?).ok_or(
ser::Error::CorruptedData,
)?;
Ok(Output {
features: OutputFeatures::from_bits(reader.read_u8()?).ok_or(
ser::Error::CorruptedData,
)?,
features: features,
commit: Commitment::read(reader)?,
proof: RangeProof::read(reader)?,
})
@@ -388,8 +433,8 @@ impl Output {
} else {
None
}
},
Err(_) => None
}
Err(_) => None,
}
}
}
@@ -453,18 +498,80 @@ impl ops::Add for SumCommit {
}
}
fn u64_to_32bytes(n: u64) -> [u8; 32] {
let mut bytes = [0; 32];
BigEndian::write_u64(&mut bytes[24..32], n);
bytes
}
#[cfg(test)]
mod test {
use super::*;
use keychain::Keychain;
use secp;
#[test]
fn test_kernel_ser_deser() {
let keychain = Keychain::from_random_seed().unwrap();
let pubkey = keychain.derive_pubkey(1).unwrap();
let commit = keychain.commit(5, &pubkey).unwrap();
// just some bytes for testing ser/deser
let sig = vec![1, 0, 0, 0, 0, 0, 0, 1];
let kernel = TxKernel {
features: DEFAULT_KERNEL,
lock_height: 0,
excess: commit,
excess_sig: sig.clone(),
fee: 10,
};
let mut vec = vec![];
ser::serialize(&mut vec, &kernel).expect("serialized failed");
let kernel2: TxKernel = ser::deserialize(&mut &vec[..]).unwrap();
assert_eq!(kernel2.features, DEFAULT_KERNEL);
assert_eq!(kernel2.lock_height, 0);
assert_eq!(kernel2.excess, commit);
assert_eq!(kernel2.excess_sig, sig.clone());
assert_eq!(kernel2.fee, 10);
// now check a kernel with lock_height serializes/deserializes correctly
let kernel = TxKernel {
features: DEFAULT_KERNEL,
lock_height: 100,
excess: commit,
excess_sig: sig.clone(),
fee: 10,
};
let mut vec = vec![];
ser::serialize(&mut vec, &kernel).expect("serialized failed");
let kernel2: TxKernel = ser::deserialize(&mut &vec[..]).unwrap();
assert_eq!(kernel2.features, DEFAULT_KERNEL);
assert_eq!(kernel2.lock_height, 100);
assert_eq!(kernel2.excess, commit);
assert_eq!(kernel2.excess_sig, sig.clone());
assert_eq!(kernel2.fee, 10);
}
#[test]
fn test_output_ser_deser() {
let keychain = Keychain::from_random_seed().unwrap();
let pubkey = keychain.derive_pubkey(1).unwrap();
let commit = keychain.commit(5, &pubkey).unwrap();
let msg = secp::pedersen::ProofMessage::empty();
let proof = keychain.range_proof(5, &pubkey, commit, msg).unwrap();
let out = Output {
features: 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, DEFAULT_OUTPUT);
assert_eq!(dout.commit, out.commit);
assert_eq!(dout.proof, out.proof);
}
#[test]
fn test_output_value_recovery() {
let keychain = Keychain::from_random_seed().unwrap();
+2
View File
@@ -25,6 +25,8 @@
extern crate bitflags;
extern crate blake2_rfc as blake2;
extern crate byteorder;
#[macro_use]
extern crate log;
extern crate num_bigint as bigint;
extern crate rand;
extern crate secp256k1zkp as secp;