mirror of
https://github.com/mimblewimble/grin.git
synced 2025-04-26 20:31:16 +03:00
Cleanup of Transaction and Block data structures
Some renaming and cleanup of the Input, Output, Transaction and Block structs. The main change is the removal of all the overt structures which are now replaced by a specialized module to build transactions easily. More specifically: * Rename the TxProof to TxKernel in Block to reflect the current naming consensus in MimbleWimble. * Change Input and Output to be plain structs instead of enums making their manipulation a lot easier. The building of transactions is now handled by the build module. * Input now directly includes the commitment of the Output it's spending instead of going through an intermediate hash. * The new build module encapsulates all the transaction building logic, making it very straightforward by chaining combinators. * Moves some tests to the core mod.rs as they required being able to build a transaction to test it.
This commit is contained in:
parent
95592d6624
commit
6bd3fc0d48
7 changed files with 567 additions and 592 deletions
|
@ -21,7 +21,7 @@ use secp::key::SecretKey;
|
|||
use std::collections::HashSet;
|
||||
|
||||
use core::Committed;
|
||||
use core::{Input, Output, Proof, TxProof, Transaction};
|
||||
use core::{Input, Output, Proof, TxKernel, Transaction};
|
||||
use core::transaction::merkle_inputs_outputs;
|
||||
use consensus::{REWARD, DEFAULT_SIZESHIFT};
|
||||
use core::hash::{Hash, Hashed, ZERO_HASH};
|
||||
|
@ -128,7 +128,7 @@ pub struct Block {
|
|||
pub header: BlockHeader,
|
||||
pub inputs: Vec<Input>,
|
||||
pub outputs: Vec<Output>,
|
||||
pub proofs: Vec<TxProof>,
|
||||
pub kernels: Vec<TxKernel>,
|
||||
}
|
||||
|
||||
/// Implementation of Writeable for a block, defines how to write the block to a
|
||||
|
@ -142,7 +142,7 @@ impl Writeable for Block {
|
|||
ser_multiwrite!(writer,
|
||||
[write_u64, self.inputs.len() as u64],
|
||||
[write_u64, self.outputs.len() as u64],
|
||||
[write_u64, self.proofs.len() as u64]);
|
||||
[write_u64, self.kernels.len() as u64]);
|
||||
|
||||
for inp in &self.inputs {
|
||||
try!(inp.write(writer));
|
||||
|
@ -150,7 +150,7 @@ impl Writeable for Block {
|
|||
for out in &self.outputs {
|
||||
try!(out.write(writer));
|
||||
}
|
||||
for proof in &self.proofs {
|
||||
for proof in &self.kernels {
|
||||
try!(proof.write(writer));
|
||||
}
|
||||
}
|
||||
|
@ -169,13 +169,13 @@ impl Readable<Block> for Block {
|
|||
|
||||
let inputs = try!((0..input_len).map(|_| Input::read(reader)).collect());
|
||||
let outputs = try!((0..output_len).map(|_| Output::read(reader)).collect());
|
||||
let proofs = try!((0..proof_len).map(|_| TxProof::read(reader)).collect());
|
||||
let kernels = try!((0..proof_len).map(|_| TxKernel::read(reader)).collect());
|
||||
|
||||
Ok(Block {
|
||||
header: header,
|
||||
inputs: inputs,
|
||||
outputs: outputs,
|
||||
proofs: proofs,
|
||||
kernels: kernels,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ impl Default for Block {
|
|||
header: Default::default(),
|
||||
inputs: vec![],
|
||||
outputs: vec![],
|
||||
proofs: vec![],
|
||||
kernels: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -222,9 +222,9 @@ impl Block {
|
|||
// note: the following reads easily but may not be the most efficient due to
|
||||
// repeated iterations, revisit if a problem
|
||||
|
||||
// validate each transaction and gather their proofs
|
||||
let mut proofs = try_map_vec!(txs, |tx| tx.verify_sig(&secp));
|
||||
proofs.push(reward_proof);
|
||||
// validate each transaction and gather their kernels
|
||||
let mut kernels = try_map_vec!(txs, |tx| tx.verify_sig(&secp));
|
||||
kernels.push(reward_proof);
|
||||
|
||||
// build vectors with all inputs and all outputs, ordering them by hash
|
||||
// needs to be a fold so we don't end up with a vector of vectors and we
|
||||
|
@ -259,7 +259,7 @@ impl Block {
|
|||
},
|
||||
inputs: inputs,
|
||||
outputs: outputs,
|
||||
proofs: proofs,
|
||||
kernels: kernels,
|
||||
}
|
||||
.compact())
|
||||
}
|
||||
|
@ -269,7 +269,7 @@ impl Block {
|
|||
}
|
||||
|
||||
pub fn total_fees(&self) -> u64 {
|
||||
self.proofs.iter().map(|p| p.fee).sum()
|
||||
self.kernels.iter().map(|p| p.fee).sum()
|
||||
}
|
||||
|
||||
/// Matches any output with a potential spending input, eliminating them
|
||||
|
@ -279,22 +279,22 @@ impl Block {
|
|||
// the chosen ones
|
||||
let mut new_inputs = vec![];
|
||||
|
||||
// build a set of all output hashes
|
||||
// build a set of all output commitments
|
||||
let mut out_set = HashSet::new();
|
||||
for out in &self.outputs {
|
||||
out_set.insert(out.hash());
|
||||
out_set.insert(out.commitment());
|
||||
}
|
||||
// removes from the set any hash referenced by an input, keeps the inputs that
|
||||
// don't have a match
|
||||
for inp in &self.inputs {
|
||||
if !out_set.remove(&inp.output_hash()) {
|
||||
if !out_set.remove(&inp.commitment()) {
|
||||
new_inputs.push(*inp);
|
||||
}
|
||||
}
|
||||
// we got ourselves a keep list in that set
|
||||
let new_outputs = self.outputs
|
||||
.iter()
|
||||
.filter(|out| out_set.contains(&(out.hash())))
|
||||
.filter(|out| out_set.contains(&(out.commitment())))
|
||||
.map(|&out| out)
|
||||
.collect::<Vec<Output>>();
|
||||
|
||||
|
@ -310,11 +310,11 @@ impl Block {
|
|||
},
|
||||
inputs: new_inputs,
|
||||
outputs: new_outputs,
|
||||
proofs: self.proofs.clone(),
|
||||
kernels: self.kernels.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
// Merges the 2 blocks, essentially appending the inputs, outputs and proofs.
|
||||
// Merges the 2 blocks, essentially appending the inputs, outputs and kernels.
|
||||
// Also performs a compaction on the result.
|
||||
pub fn merge(&self, other: Block) -> Block {
|
||||
let mut all_inputs = self.inputs.clone();
|
||||
|
@ -323,8 +323,8 @@ impl Block {
|
|||
let mut all_outputs = self.outputs.clone();
|
||||
all_outputs.append(&mut other.outputs.clone());
|
||||
|
||||
let mut all_proofs = self.proofs.clone();
|
||||
all_proofs.append(&mut other.proofs.clone());
|
||||
let mut all_kernels = self.kernels.clone();
|
||||
all_kernels.append(&mut other.kernels.clone());
|
||||
|
||||
all_inputs.sort_by_key(|inp| inp.hash());
|
||||
all_outputs.sort_by_key(|out| out.hash());
|
||||
|
@ -339,19 +339,19 @@ impl Block {
|
|||
},
|
||||
inputs: all_inputs,
|
||||
outputs: all_outputs,
|
||||
proofs: all_proofs,
|
||||
kernels: all_kernels,
|
||||
}
|
||||
.compact()
|
||||
}
|
||||
|
||||
/// Checks the block is valid by verifying the overall commitments sums and
|
||||
/// proofs.
|
||||
/// kernels.
|
||||
pub fn verify(&self, secp: &Secp256k1) -> Result<(), secp::Error> {
|
||||
// sum all inputs and outs commitments
|
||||
let io_sum = try!(self.sum_commitments(secp));
|
||||
|
||||
// sum all proofs commitments
|
||||
let proof_commits = map_vec!(self.proofs, |proof| proof.remainder);
|
||||
// sum all kernels commitments
|
||||
let proof_commits = map_vec!(self.kernels, |proof| proof.excess);
|
||||
let proof_sum = try!(secp.commit_sum(proof_commits, vec![]));
|
||||
|
||||
// both should be the same
|
||||
|
@ -361,7 +361,7 @@ impl Block {
|
|||
}
|
||||
|
||||
// verify all signatures with the commitment as pk
|
||||
for proof in &self.proofs {
|
||||
for proof in &self.kernels {
|
||||
try!(proof.verify(secp));
|
||||
}
|
||||
|
||||
|
@ -378,118 +378,125 @@ impl Block {
|
|||
// Builds the blinded output and related signature proof for the block reward.
|
||||
fn reward_output(skey: secp::key::SecretKey,
|
||||
secp: &Secp256k1)
|
||||
-> Result<(Output, TxProof), secp::Error> {
|
||||
-> Result<(Output, TxKernel), secp::Error> {
|
||||
let msg = try!(secp::Message::from_slice(&[0; secp::constants::MESSAGE_SIZE]));
|
||||
let sig = try!(secp.sign(&msg, &skey));
|
||||
let output = Output::OvertOutput {
|
||||
value: REWARD,
|
||||
blindkey: skey,
|
||||
}
|
||||
.blind(&secp);
|
||||
let commit = secp.commit(REWARD, skey).unwrap();
|
||||
let rproof = secp.range_proof(0, REWARD, skey, commit);
|
||||
|
||||
let output = Output {
|
||||
commit: commit,
|
||||
proof: rproof,
|
||||
};
|
||||
|
||||
let over_commit = try!(secp.commit_value(REWARD as u64));
|
||||
let out_commit = output.commitment().unwrap();
|
||||
let remainder = try!(secp.commit_sum(vec![over_commit], vec![out_commit]));
|
||||
let out_commit = output.commitment();
|
||||
let excess = try!(secp.commit_sum(vec![over_commit], vec![out_commit]));
|
||||
|
||||
let proof = TxProof {
|
||||
remainder: remainder,
|
||||
sig: sig.serialize_der(&secp),
|
||||
let proof = TxKernel {
|
||||
excess: excess,
|
||||
excess_sig: sig.serialize_der(&secp),
|
||||
fee: 0,
|
||||
};
|
||||
Ok((output, proof))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use core::{Input, Output, Transaction};
|
||||
use core::hash::{Hash, Hashed};
|
||||
use core::test::{tx1i1o, tx2i1o};
|
||||
|
||||
use secp::{self, Secp256k1};
|
||||
use secp::key::SecretKey;
|
||||
use rand::Rng;
|
||||
use rand::os::OsRng;
|
||||
|
||||
fn new_secp() -> Secp256k1 {
|
||||
secp::Secp256k1::with_caps(secp::ContextFlag::Commit)
|
||||
}
|
||||
|
||||
// utility to create a block without worrying about the key or previous header
|
||||
fn new_block(txs: Vec<&mut Transaction>, secp: &Secp256k1) -> Block {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let skey = SecretKey::new(secp, &mut rng);
|
||||
Block::new(&BlockHeader::default(), txs, skey).unwrap()
|
||||
}
|
||||
|
||||
// utility producing a transaction that spends the above
|
||||
fn txspend1i1o<R: Rng>(secp: &Secp256k1, rng: &mut R, oout: Output, outh: Hash) -> Transaction {
|
||||
if let Output::OvertOutput { blindkey, value } = oout {
|
||||
Transaction::new(vec![Input::OvertInput {
|
||||
output: outh,
|
||||
value: value,
|
||||
blindkey: blindkey,
|
||||
}],
|
||||
vec![Output::OvertOutput {
|
||||
value: 3,
|
||||
blindkey: SecretKey::new(secp, rng),
|
||||
}],
|
||||
1)
|
||||
} else {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
// builds a block with a tx spending another and check if merging occurred
|
||||
fn compactable_block() {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let ref secp = new_secp();
|
||||
|
||||
let tx1 = tx2i1o(secp, &mut rng);
|
||||
let mut btx1 = tx1.blind(&secp, None).unwrap();
|
||||
|
||||
let tx2 = tx1i1o(secp, &mut rng);
|
||||
let mut btx2 = tx2.blind(&secp, None).unwrap();
|
||||
|
||||
// spending tx2
|
||||
let spending = txspend1i1o(secp, &mut rng, tx2.outputs[0], btx2.outputs[0].hash());
|
||||
let mut btx3 = spending.blind(&secp, None).unwrap();
|
||||
let b = new_block(vec![&mut btx1, &mut btx2, &mut btx3], secp);
|
||||
|
||||
// block should have been automatically compacted (including reward output) and
|
||||
// should still be valid
|
||||
b.verify(&secp).unwrap();
|
||||
assert_eq!(b.inputs.len(), 3);
|
||||
assert_eq!(b.outputs.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// builds 2 different blocks with a tx spending another and check if merging
|
||||
// occurs
|
||||
fn mergeable_blocks() {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let ref secp = new_secp();
|
||||
|
||||
let tx1 = tx2i1o(secp, &mut rng);
|
||||
let mut btx1 = tx1.blind(&secp, None).unwrap();
|
||||
|
||||
let tx2 = tx1i1o(secp, &mut rng);
|
||||
let mut btx2 = tx2.blind(&secp, None).unwrap();
|
||||
|
||||
// spending tx2
|
||||
let spending = txspend1i1o(secp, &mut rng, tx2.outputs[0], btx2.outputs[0].hash());
|
||||
let mut btx3 = spending.blind(&secp, None).unwrap();
|
||||
|
||||
let b1 = new_block(vec![&mut btx1, &mut btx2], secp);
|
||||
b1.verify(&secp).unwrap();
|
||||
let b2 = new_block(vec![&mut btx3], secp);
|
||||
b2.verify(&secp).unwrap();
|
||||
|
||||
// block should have been automatically compacted and should still be valid
|
||||
let b3 = b1.merge(b2);
|
||||
assert_eq!(b3.inputs.len(), 3);
|
||||
assert_eq!(b3.outputs.len(), 4);
|
||||
}
|
||||
}
|
||||
// #[cfg(test)]
|
||||
// mod test {
|
||||
// use super::*;
|
||||
// use core::{Input, Output, Transaction};
|
||||
// use core::hash::{Hash, Hashed};
|
||||
// use core::test::{tx1i1o, tx2i1o};
|
||||
//
|
||||
// use secp::{self, Secp256k1};
|
||||
// use secp::key::SecretKey;
|
||||
// use rand::Rng;
|
||||
// use rand::os::OsRng;
|
||||
//
|
||||
// fn new_secp() -> Secp256k1 {
|
||||
// secp::Secp256k1::with_caps(secp::ContextFlag::Commit)
|
||||
// }
|
||||
//
|
||||
// // utility to create a block without worrying about the key or previous
|
||||
// header
|
||||
// fn new_block(txs: Vec<&mut Transaction>, secp: &Secp256k1) -> Block {
|
||||
// let mut rng = OsRng::new().unwrap();
|
||||
// let skey = SecretKey::new(secp, &mut rng);
|
||||
// Block::new(&BlockHeader::default(), txs, skey).unwrap()
|
||||
// }
|
||||
//
|
||||
// // utility producing a transaction that spends the above
|
||||
// fn txspend1i1o<R: Rng>(secp: &Secp256k1, rng: &mut R, oout: Output, outh:
|
||||
// Hash) -> Transaction {
|
||||
// if let Output::OvertOutput { blindkey, value } = oout {
|
||||
// Transaction::new(vec![Input::OvertInput {
|
||||
// output: outh,
|
||||
// value: value,
|
||||
// blindkey: blindkey,
|
||||
// }],
|
||||
// vec![Output::OvertOutput {
|
||||
// value: 3,
|
||||
// blindkey: SecretKey::new(secp, rng),
|
||||
// }],
|
||||
// 1)
|
||||
// } else {
|
||||
// panic!();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// // builds a block with a tx spending another and check if merging occurred
|
||||
// fn compactable_block() {
|
||||
// let mut rng = OsRng::new().unwrap();
|
||||
// let ref secp = new_secp();
|
||||
//
|
||||
// let tx1 = tx2i1o(secp, &mut rng);
|
||||
// let mut btx1 = tx1.blind(&secp, None).unwrap();
|
||||
//
|
||||
// let tx2 = tx1i1o(secp, &mut rng);
|
||||
// let mut btx2 = tx2.blind(&secp, None).unwrap();
|
||||
//
|
||||
// // spending tx2
|
||||
// let spending = txspend1i1o(secp, &mut rng, tx2.outputs[0],
|
||||
// btx2.outputs[0].hash());
|
||||
// let mut btx3 = spending.blind(&secp, None).unwrap();
|
||||
// let b = new_block(vec![&mut btx1, &mut btx2, &mut btx3], secp);
|
||||
//
|
||||
// // block should have been automatically compacted (including reward
|
||||
// output) and
|
||||
// // should still be valid
|
||||
// b.verify(&secp).unwrap();
|
||||
// assert_eq!(b.inputs.len(), 3);
|
||||
// assert_eq!(b.outputs.len(), 3);
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// // builds 2 different blocks with a tx spending another and check if merging
|
||||
// // occurs
|
||||
// fn mergeable_blocks() {
|
||||
// let mut rng = OsRng::new().unwrap();
|
||||
// let ref secp = new_secp();
|
||||
//
|
||||
// let tx1 = tx2i1o(secp, &mut rng);
|
||||
// let mut btx1 = tx1.blind(&secp, None).unwrap();
|
||||
//
|
||||
// let tx2 = tx1i1o(secp, &mut rng);
|
||||
// let mut btx2 = tx2.blind(&secp, None).unwrap();
|
||||
//
|
||||
// // spending tx2
|
||||
// let spending = txspend1i1o(secp, &mut rng, tx2.outputs[0],
|
||||
// btx2.outputs[0].hash());
|
||||
// let mut btx3 = spending.blind(&secp, None).unwrap();
|
||||
//
|
||||
// let b1 = new_block(vec![&mut btx1, &mut btx2], secp);
|
||||
// b1.verify(&secp).unwrap();
|
||||
// let b2 = new_block(vec![&mut btx3], secp);
|
||||
// b2.verify(&secp).unwrap();
|
||||
//
|
||||
// // block should have been automatically compacted and should still be valid
|
||||
// let b3 = b1.merge(b2);
|
||||
// assert_eq!(b3.inputs.len(), 3);
|
||||
// assert_eq!(b3.outputs.len(), 4);
|
||||
// }
|
||||
// }
|
||||
|
|
204
core/src/core/build.rs
Normal file
204
core/src/core/build.rs
Normal file
|
@ -0,0 +1,204 @@
|
|||
// Copyright 2016 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 byteorder::{ByteOrder, BigEndian};
|
||||
use secp::{self, Secp256k1};
|
||||
use secp::key::SecretKey;
|
||||
use secp::pedersen::*;
|
||||
use rand::os::OsRng;
|
||||
|
||||
use core::{Transaction, Input, Output};
|
||||
|
||||
/// Context information available to transaction combinators.
|
||||
pub struct Context {
|
||||
secp: Secp256k1,
|
||||
rng: OsRng,
|
||||
}
|
||||
|
||||
/// Accumulator to compute the sum of blinding factors. Keeps track of each
|
||||
/// factor as well as the "sign" with which they should be combined.
|
||||
pub struct BlindSum {
|
||||
positive: Vec<SecretKey>,
|
||||
negative: Vec<SecretKey>,
|
||||
}
|
||||
|
||||
impl BlindSum {
|
||||
/// Creates a new blinding factor sum.
|
||||
fn new() -> BlindSum {
|
||||
BlindSum {
|
||||
positive: vec![],
|
||||
negative: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the provided key to the sum of blinding factors.
|
||||
fn add(self, key: SecretKey) -> BlindSum {
|
||||
let mut new_pos = self.positive;
|
||||
new_pos.push(key);
|
||||
BlindSum {
|
||||
positive: new_pos,
|
||||
negative: self.negative,
|
||||
}
|
||||
}
|
||||
|
||||
/// Subtractss the provided key to the sum of blinding factors.
|
||||
fn sub(self, key: SecretKey) -> BlindSum {
|
||||
let mut new_neg = self.negative;
|
||||
new_neg.push(key);
|
||||
BlindSum {
|
||||
positive: self.positive,
|
||||
negative: new_neg,
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the sum of blinding factors from all the ones that have been
|
||||
/// added and subtracted.
|
||||
fn sum(self, secp: &Secp256k1) -> Result<SecretKey, secp::Error> {
|
||||
secp.blind_sum(self.positive, self.negative)
|
||||
}
|
||||
}
|
||||
|
||||
/// Function type returned by the transaction combinators. Transforms a
|
||||
/// (Transaction, BlindSum) pair into another, provided some context.
|
||||
type Append = for<'a> Fn(&'a mut Context, (Transaction, BlindSum)) -> (Transaction, BlindSum);
|
||||
|
||||
/// Adds an input with the provided value and blinding key to the transaction
|
||||
/// being built.
|
||||
pub fn input(value: u64, blinding: SecretKey) -> Box<Append> {
|
||||
Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) {
|
||||
let commit = build.secp.commit(value, blinding).unwrap();
|
||||
(tx.with_input(Input(commit)), sum.add(blinding))
|
||||
})
|
||||
}
|
||||
|
||||
/// Adds an input with the provided value and a randomly generated blinding
|
||||
/// key to the transaction being built. This has no real use in practical
|
||||
/// applications but is very convenient for tests.
|
||||
pub fn input_rand(value: u64) -> Box<Append> {
|
||||
Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) {
|
||||
let blinding = SecretKey::new(&build.secp, &mut build.rng);
|
||||
let commit = build.secp.commit(value, blinding).unwrap();
|
||||
(tx.with_input(Input(commit)), sum.add(blinding))
|
||||
})
|
||||
}
|
||||
|
||||
/// Adds an output with the provided value and blinding key to the transaction
|
||||
/// being built.
|
||||
pub fn output(value: u64, blinding: SecretKey) -> Box<Append> {
|
||||
Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) {
|
||||
let commit = build.secp.commit(value, blinding).unwrap();
|
||||
let rproof = build.secp.range_proof(0, value, blinding, commit);
|
||||
(tx.with_output(Output {
|
||||
commit: commit,
|
||||
proof: rproof,
|
||||
}),
|
||||
sum.sub(blinding))
|
||||
})
|
||||
}
|
||||
|
||||
/// Adds an output with the provided value and a randomly generated blinding
|
||||
/// key to the transaction being built. This has no real use in practical
|
||||
/// applications but is very convenient for tests.
|
||||
pub fn output_rand(value: u64) -> Box<Append> {
|
||||
Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) {
|
||||
let blinding = SecretKey::new(&build.secp, &mut build.rng);
|
||||
let commit = build.secp.commit(value, blinding).unwrap();
|
||||
let rproof = build.secp.range_proof(0, value, blinding, commit);
|
||||
(tx.with_output(Output {
|
||||
commit: commit,
|
||||
proof: rproof,
|
||||
}),
|
||||
sum.sub(blinding))
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the fee on the transaction being built.
|
||||
pub fn with_fee(fee: u64) -> Box<Append> {
|
||||
Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) { (tx.with_fee(fee), 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.
|
||||
pub fn with_excess(excess: SecretKey) -> Box<Append> {
|
||||
Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) { (tx, sum.add(excess)) })
|
||||
}
|
||||
|
||||
/// Sets an initial transaction to add to when building a new transaction.
|
||||
pub fn initial_tx(tx: Transaction) -> Box<Append> {
|
||||
Box::new(move |build, (_, sum)| -> (Transaction, BlindSum) { (tx.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)]).unwrap();
|
||||
/// let (tx2, _) = build::transaction(vec![initial_tx(tx1), with_excess(sum),
|
||||
/// output_rand(2)]).unwrap();
|
||||
///
|
||||
pub fn transaction(elems: Vec<Box<Append>>) -> Result<(Transaction, SecretKey), secp::Error> {
|
||||
let mut ctx = Context {
|
||||
secp: Secp256k1::with_caps(secp::ContextFlag::Commit),
|
||||
rng: OsRng::new().unwrap(),
|
||||
};
|
||||
let (mut tx, sum) = elems.iter().fold((Transaction::empty(), BlindSum::new()),
|
||||
|acc, elem| elem(&mut ctx, acc));
|
||||
|
||||
let blind_sum = sum.sum(&ctx.secp)?;
|
||||
let msg = try!(secp::Message::from_slice(&u64_to_32bytes(tx.fee)));
|
||||
let sig = try!(ctx.secp.sign(&msg, &blind_sum));
|
||||
tx.excess_sig = sig.serialize_der(&ctx.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 {
|
||||
use super::*;
|
||||
|
||||
use secp::{self, Secp256k1};
|
||||
|
||||
#[test]
|
||||
fn blind_simple_tx() {
|
||||
let secp = Secp256k1::with_caps(secp::ContextFlag::Commit);
|
||||
let (tx, _) =
|
||||
transaction(vec![input_rand(10), input_rand(11), output_rand(20), with_fee(1)])
|
||||
.unwrap();
|
||||
tx.verify_sig(&secp).unwrap();
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
//! Core types
|
||||
|
||||
pub mod block;
|
||||
pub mod build;
|
||||
pub mod hash;
|
||||
pub mod target;
|
||||
pub mod transaction;
|
||||
|
@ -28,7 +29,7 @@ use secp::pedersen::*;
|
|||
|
||||
use consensus::PROOFSIZE;
|
||||
pub use self::block::{Block, BlockHeader};
|
||||
pub use self::transaction::{Transaction, Input, Output, TxProof};
|
||||
pub use self::transaction::{Transaction, Input, Output, TxKernel};
|
||||
use self::hash::{Hash, Hashed, HashWriter, ZERO_HASH};
|
||||
use ser::{Writeable, Writer, Reader, Readable, Error};
|
||||
|
||||
|
@ -45,8 +46,8 @@ pub trait Committed {
|
|||
}
|
||||
|
||||
// then gather the commitments
|
||||
let mut input_commits = filter_map_vec!(self.inputs_committed(), |inp| inp.commitment());
|
||||
let mut output_commits = filter_map_vec!(self.outputs_committed(), |out| out.commitment());
|
||||
let mut input_commits = map_vec!(self.inputs_committed(), |inp| inp.commitment());
|
||||
let mut output_commits = map_vec!(self.outputs_committed(), |out| out.commitment());
|
||||
|
||||
// add the overage as input commitment if positive, as an output commitment if
|
||||
// negative
|
||||
|
@ -208,8 +209,11 @@ mod test {
|
|||
use secp;
|
||||
use secp::Secp256k1;
|
||||
use secp::key::SecretKey;
|
||||
use ser;
|
||||
use rand::Rng;
|
||||
use rand::os::OsRng;
|
||||
use core::build::{self, input, output, input_rand, output_rand, with_fee, initial_tx,
|
||||
with_excess};
|
||||
|
||||
fn new_secp() -> Secp256k1 {
|
||||
secp::Secp256k1::with_caps(secp::ContextFlag::Commit)
|
||||
|
@ -222,20 +226,115 @@ mod test {
|
|||
let ref secp = new_secp();
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
|
||||
let skey = SecretKey::new(secp, &mut rng);
|
||||
let outh = ZERO_HASH;
|
||||
let tx = Transaction::new(vec![Input::OvertInput {
|
||||
output: outh,
|
||||
value: 10,
|
||||
blindkey: skey,
|
||||
}],
|
||||
vec![Output::OvertOutput {
|
||||
value: 1,
|
||||
blindkey: skey,
|
||||
}],
|
||||
9);
|
||||
// blinding should fail as signing with a zero r*G shouldn't work
|
||||
tx.blind(&secp, None).unwrap();
|
||||
let skey = SecretKey::new(secp, &mut rng);
|
||||
build::transaction(vec![input(10, skey), output(1, skey), with_fee(9)]).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_tx_ser() {
|
||||
let tx = tx2i1o();
|
||||
let mut vec = Vec::new();
|
||||
ser::serialize(&mut vec, &tx).expect("serialized failed");
|
||||
assert!(vec.len() > 5320);
|
||||
assert!(vec.len() < 5340);
|
||||
}
|
||||
|
||||
#[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, 1);
|
||||
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 hash_output() {
|
||||
let (tx, _) =
|
||||
build::transaction(vec![input_rand(75), output_rand(42), output_rand(32), with_fee(1)])
|
||||
.unwrap();
|
||||
let h = tx.outputs[0].hash();
|
||||
assert!(h != ZERO_HASH);
|
||||
let h2 = tx.outputs[1].hash();
|
||||
assert!(h != h2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blind_tx() {
|
||||
let ref secp = new_secp();
|
||||
|
||||
let btx = tx2i1o();
|
||||
btx.verify_sig(&secp).unwrap(); // unwrap will panic if invalid
|
||||
|
||||
// checks that the range proof on our blind output is sufficiently hiding
|
||||
let Output { proof, .. } = btx.outputs[0];
|
||||
let info = secp.range_proof_info(proof);
|
||||
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 ref secp = new_secp();
|
||||
let outh = ZERO_HASH;
|
||||
|
||||
let tx_alice: Transaction;
|
||||
let blind_sum: SecretKey;
|
||||
|
||||
{
|
||||
// Alice gets 2 of her pre-existing outputs to send 5 coins to Bob, they
|
||||
// become inputs in the new transaction
|
||||
let (in1, in2) = (input_rand(4), input_rand(3));
|
||||
|
||||
// Alice builds her transaction, with change, which also produces the sum
|
||||
// of blinding factors before they're obscured.
|
||||
let (tx, sum) = build::transaction(vec![in1, in2, output_rand(1), with_fee(1)])
|
||||
.unwrap();
|
||||
tx_alice = tx;
|
||||
blind_sum = 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_rand(5)])
|
||||
.unwrap();
|
||||
|
||||
tx_final.validate(&secp).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -254,11 +353,10 @@ mod test {
|
|||
let ref secp = new_secp();
|
||||
let skey = SecretKey::new(secp, &mut rng);
|
||||
|
||||
let tx1 = tx2i1o(secp, &mut rng);
|
||||
let mut btx1 = tx1.blind(&secp, None).unwrap();
|
||||
btx1.verify_sig(&secp).unwrap();
|
||||
let mut tx1 = tx2i1o();
|
||||
tx1.verify_sig(&secp).unwrap();
|
||||
|
||||
let b = Block::new(&BlockHeader::default(), vec![&mut btx1], skey).unwrap();
|
||||
let b = Block::new(&BlockHeader::default(), vec![&mut tx1], skey).unwrap();
|
||||
b.compact().verify(&secp).unwrap();
|
||||
}
|
||||
|
||||
|
@ -268,50 +366,27 @@ mod test {
|
|||
let ref secp = new_secp();
|
||||
let skey = SecretKey::new(secp, &mut rng);
|
||||
|
||||
let tx1 = tx2i1o(secp, &mut rng);
|
||||
let mut btx1 = tx1.blind(&secp, None).unwrap();
|
||||
btx1.verify_sig(&secp).unwrap();
|
||||
let mut tx1 = tx2i1o();
|
||||
tx1.verify_sig(&secp).unwrap();
|
||||
|
||||
let tx2 = tx1i1o(secp, &mut rng);
|
||||
let mut btx2 = tx2.blind(&secp, None).unwrap();
|
||||
btx2.verify_sig(&secp).unwrap();
|
||||
let mut tx2 = tx1i1o();
|
||||
tx2.verify_sig(&secp).unwrap();
|
||||
|
||||
let b = Block::new(&BlockHeader::default(), vec![&mut btx1, &mut btx2], skey).unwrap();
|
||||
let b = Block::new(&BlockHeader::default(), vec![&mut tx1, &mut tx2], skey).unwrap();
|
||||
b.verify(&secp).unwrap();
|
||||
}
|
||||
|
||||
// utility producing a transaction with 2 inputs and a single outputs
|
||||
pub fn tx2i1o<R: Rng>(secp: &Secp256k1, rng: &mut R) -> Transaction {
|
||||
let outh = ZERO_HASH;
|
||||
Transaction::new(vec![Input::OvertInput {
|
||||
output: outh,
|
||||
value: 10,
|
||||
blindkey: SecretKey::new(secp, rng),
|
||||
},
|
||||
Input::OvertInput {
|
||||
output: outh,
|
||||
value: 11,
|
||||
blindkey: SecretKey::new(secp, rng),
|
||||
}],
|
||||
vec![Output::OvertOutput {
|
||||
value: 20,
|
||||
blindkey: SecretKey::new(secp, rng),
|
||||
}],
|
||||
1)
|
||||
pub fn tx2i1o() -> Transaction {
|
||||
build::transaction(vec![input_rand(10), input_rand(11), output_rand(20), with_fee(1)])
|
||||
.map(|(tx, _)| tx)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// utility producing a transaction with a single input and output
|
||||
pub fn tx1i1o<R: Rng>(secp: &Secp256k1, rng: &mut R) -> Transaction {
|
||||
let outh = ZERO_HASH;
|
||||
Transaction::new(vec![Input::OvertInput {
|
||||
output: outh,
|
||||
value: 5,
|
||||
blindkey: SecretKey::new(secp, rng),
|
||||
}],
|
||||
vec![Output::OvertOutput {
|
||||
value: 4,
|
||||
blindkey: SecretKey::new(secp, rng),
|
||||
}],
|
||||
1)
|
||||
pub fn tx1i1o() -> Transaction {
|
||||
build::transaction(vec![input_rand(5), output_rand(4), with_fee(1)])
|
||||
.map(|(tx, _)| tx)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,58 +29,62 @@ use ser::{self, Reader, Writer, Readable, Writeable};
|
|||
/// amount to zero. The signature signs the fee, which is retained for
|
||||
/// signature validation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TxProof {
|
||||
pub struct TxKernel {
|
||||
/// Remainder of the sum of all transaction commitments. If the transaction
|
||||
/// is well formed, amounts components should sum to zero and the remainder
|
||||
/// is well formed, amounts components should sum to zero and the excess
|
||||
/// is hence a valid public key.
|
||||
pub remainder: Commitment,
|
||||
/// The signature proving the remainder is a valid public key, which signs
|
||||
pub excess: Commitment,
|
||||
/// The signature proving the excess is a valid public key, which signs
|
||||
/// the transaction fee.
|
||||
pub sig: Vec<u8>,
|
||||
pub excess_sig: Vec<u8>,
|
||||
/// Fee originally included in the transaction this proof is for.
|
||||
pub fee: u64,
|
||||
}
|
||||
|
||||
impl Writeable for TxProof {
|
||||
impl Writeable for TxKernel {
|
||||
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
|
||||
try!(writer.write_fixed_bytes(&self.remainder));
|
||||
try!(writer.write_bytes(&self.sig));
|
||||
try!(writer.write_fixed_bytes(&self.excess));
|
||||
try!(writer.write_bytes(&self.excess_sig));
|
||||
writer.write_u64(self.fee)
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable<TxProof> for TxProof {
|
||||
fn read(reader: &mut Reader) -> Result<TxProof, ser::Error> {
|
||||
let remainder = try!(Commitment::read(reader));
|
||||
impl Readable<TxKernel> for TxKernel {
|
||||
fn read(reader: &mut Reader) -> Result<TxKernel, ser::Error> {
|
||||
let excess = try!(Commitment::read(reader));
|
||||
let (sig, fee) = ser_multiread!(reader, read_vec, read_u64);
|
||||
Ok(TxProof {
|
||||
remainder: remainder,
|
||||
sig: sig,
|
||||
Ok(TxKernel {
|
||||
excess: excess,
|
||||
excess_sig: sig,
|
||||
fee: fee,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TxProof {
|
||||
impl TxKernel {
|
||||
/// Verify the transaction proof validity. Entails handling the commitment
|
||||
/// 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 pubk = try!(self.remainder.to_pubkey(secp));
|
||||
let sig = try!(Signature::from_der(secp, &self.sig));
|
||||
let pubk = try!(self.excess.to_pubkey(secp));
|
||||
let sig = try!(Signature::from_der(secp, &self.excess_sig));
|
||||
secp.verify(&msg, &sig, &pubk)
|
||||
}
|
||||
}
|
||||
|
||||
/// A transaction
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Transaction {
|
||||
hash_mem: Option<Hash>,
|
||||
pub fee: u64,
|
||||
pub zerosig: Vec<u8>,
|
||||
/// Set of inputs spent by the transaction.
|
||||
pub inputs: Vec<Input>,
|
||||
/// Set of outputs the transaction produces.
|
||||
pub outputs: Vec<Output>,
|
||||
/// Fee paid by the transaction.
|
||||
pub fee: u64,
|
||||
/// The signature proving the excess is a valid public key, which signs
|
||||
/// the transaction fee.
|
||||
pub excess_sig: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Implementation of Writeable for a fully blinded transaction, defines how to
|
||||
|
@ -89,7 +93,7 @@ impl Writeable for Transaction {
|
|||
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
|
||||
ser_multiwrite!(writer,
|
||||
[write_u64, self.fee],
|
||||
[write_bytes, &self.zerosig],
|
||||
[write_bytes, &self.excess_sig],
|
||||
[write_u64, self.inputs.len() as u64],
|
||||
[write_u64, self.outputs.len() as u64]);
|
||||
for inp in &self.inputs {
|
||||
|
@ -106,7 +110,7 @@ impl Writeable for Transaction {
|
|||
/// transaction from a binary stream.
|
||||
impl Readable<Transaction> for Transaction {
|
||||
fn read(reader: &mut Reader) -> Result<Transaction, ser::Error> {
|
||||
let (fee, zerosig, input_len, output_len) =
|
||||
let (fee, excess_sig, input_len, output_len) =
|
||||
ser_multiread!(reader, read_u64, read_vec, read_u64, read_u64);
|
||||
|
||||
let inputs = try!((0..input_len).map(|_| Input::read(reader)).collect());
|
||||
|
@ -114,7 +118,7 @@ impl Readable<Transaction> for Transaction {
|
|||
|
||||
Ok(Transaction {
|
||||
fee: fee,
|
||||
zerosig: zerosig,
|
||||
excess_sig: excess_sig,
|
||||
inputs: inputs,
|
||||
outputs: outputs,
|
||||
..Default::default()
|
||||
|
@ -145,9 +149,8 @@ impl Transaction {
|
|||
/// Creates a new empty transaction (no inputs or outputs, zero fee).
|
||||
pub fn empty() -> Transaction {
|
||||
Transaction {
|
||||
hash_mem: None,
|
||||
fee: 0,
|
||||
zerosig: vec![],
|
||||
excess_sig: vec![],
|
||||
inputs: vec![],
|
||||
outputs: vec![],
|
||||
}
|
||||
|
@ -157,106 +160,60 @@ impl Transaction {
|
|||
/// outputs and fee.
|
||||
pub fn new(inputs: Vec<Input>, outputs: Vec<Output>, fee: u64) -> Transaction {
|
||||
Transaction {
|
||||
hash_mem: None,
|
||||
fee: fee,
|
||||
zerosig: vec![],
|
||||
excess_sig: vec![],
|
||||
inputs: inputs,
|
||||
outputs: outputs,
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new transaction with the provided outputs added. Existing
|
||||
/// outputs, if any, are kept intact.
|
||||
pub fn with_outputs(&self, outputs: &mut Vec<Output>) -> Transaction {
|
||||
let mut new_outs = self.outputs.clone();
|
||||
new_outs.append(outputs);
|
||||
Transaction {
|
||||
hash_mem: None,
|
||||
fee: self.fee,
|
||||
zerosig: vec![],
|
||||
inputs: self.inputs.clone(),
|
||||
outputs: new_outs,
|
||||
}
|
||||
/// Builds a new transaction with the provided inputs added. Existing
|
||||
/// inputs, if any, are kept intact.
|
||||
pub fn with_input(self, input: Input) -> Transaction {
|
||||
let mut new_ins = self.inputs;
|
||||
new_ins.push(input);
|
||||
Transaction { inputs: new_ins, ..self }
|
||||
}
|
||||
|
||||
/// Builds a new transaction with the provided output added. Existing
|
||||
/// outputs, if any, are kept intact.
|
||||
pub fn with_output(self, output: Output) -> Transaction {
|
||||
let mut new_outs = self.outputs;
|
||||
new_outs.push(output);
|
||||
Transaction { outputs: new_outs, ..self }
|
||||
}
|
||||
|
||||
/// Builds a new transaction with the provided fee.
|
||||
pub fn with_fee(self, fee: u64) -> Transaction {
|
||||
Transaction { fee: fee, ..self }
|
||||
}
|
||||
|
||||
|
||||
/// The hash of a transaction is the Merkle tree of its inputs and outputs
|
||||
/// hashes. None of the rest is required.
|
||||
fn hash(&mut self) -> Hash {
|
||||
if let None = self.hash_mem {
|
||||
self.hash_mem = Some(merkle_inputs_outputs(&self.inputs, &self.outputs));
|
||||
}
|
||||
self.hash_mem.unwrap()
|
||||
}
|
||||
|
||||
/// Takes a transaction and fully blinds it. Following the MW
|
||||
/// algorithm: calculates the commitments for each inputs and outputs
|
||||
/// using the values and blinding factors, takes the blinding factors
|
||||
/// remainder and uses it for an empty signature.
|
||||
/// An excess value can optionally be provided to account for cases when the
|
||||
/// transaction has already been partially blinded (when a recipient
|
||||
/// receives a partially built transaction).
|
||||
pub fn blind(&self,
|
||||
secp: &Secp256k1,
|
||||
excess: Option<SecretKey>)
|
||||
-> Result<Transaction, secp::Error> {
|
||||
// we compute the sum of blinding factors to get the k remainder
|
||||
let remainder = try!(self.blind_sum(secp, excess));
|
||||
|
||||
// next, blind the inputs and outputs if they haven't been yet
|
||||
let blind_inputs = map_vec!(self.inputs, |inp| inp.blind(secp));
|
||||
let blind_outputs = map_vec!(self.outputs, |out| out.blind(secp));
|
||||
|
||||
// and sign with the remainder so the signature can be checked to match with
|
||||
// the k.G commitment leftover, that should also be the pubkey
|
||||
let msg = try!(Message::from_slice(&u64_to_32bytes(self.fee)));
|
||||
let sig = try!(secp.sign(&msg, &remainder));
|
||||
|
||||
Ok(Transaction {
|
||||
hash_mem: None,
|
||||
fee: self.fee,
|
||||
zerosig: sig.serialize_der(secp),
|
||||
inputs: blind_inputs,
|
||||
outputs: blind_outputs,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute the sum of blinding factors on all overt inputs and outputs of
|
||||
/// the transaction to get the k remainder.
|
||||
/// An excess value can optionally be provided to account for cases when the
|
||||
/// transaction has already been partially blinded (when a recipient
|
||||
/// receives a partially built transaction).
|
||||
pub fn blind_sum(&self,
|
||||
secp: &Secp256k1,
|
||||
excess: Option<SecretKey>)
|
||||
-> Result<SecretKey, secp::Error> {
|
||||
let mut inputs_blinding_fact = filter_map_vec!(self.inputs, |inp| inp.blinding_factor());
|
||||
let outputs_blinding_fact = filter_map_vec!(self.outputs, |out| out.blinding_factor());
|
||||
if let Some(exc) = excess {
|
||||
inputs_blinding_fact.push(exc);
|
||||
}
|
||||
|
||||
secp.blind_sum(inputs_blinding_fact, outputs_blinding_fact)
|
||||
merkle_inputs_outputs(&self.inputs, &self.outputs)
|
||||
}
|
||||
|
||||
/// The verification for a MimbleWimble transaction involves getting the
|
||||
/// remainder of summing all commitments and using it as a public key
|
||||
/// excess of summing all commitments and using it as a public key
|
||||
/// to verify the embedded signature. The rational is that if the values
|
||||
/// sum to zero as they should in r.G + v.H then only k.G the remainder
|
||||
/// sum to zero as they should in r.G + v.H then only k.G the excess
|
||||
/// of the sum of r.G should be left. And r.G is the definition of a
|
||||
/// public key generated using r as a private key.
|
||||
pub fn verify_sig(&self, secp: &Secp256k1) -> Result<TxProof, secp::Error> {
|
||||
pub fn verify_sig(&self, secp: &Secp256k1) -> Result<TxKernel, secp::Error> {
|
||||
let rsum = try!(self.sum_commitments(secp));
|
||||
|
||||
// pretend the sum is a public key (which it is, being of the form r.G) and
|
||||
// verify the transaction sig with it
|
||||
let pubk = try!(rsum.to_pubkey(secp));
|
||||
let msg = try!(Message::from_slice(&u64_to_32bytes(self.fee)));
|
||||
let sig = try!(Signature::from_der(secp, &self.zerosig));
|
||||
let sig = try!(Signature::from_der(secp, &self.excess_sig));
|
||||
try!(secp.verify(&msg, &sig, &pubk));
|
||||
|
||||
Ok(TxProof {
|
||||
remainder: rsum,
|
||||
sig: self.zerosig.clone(),
|
||||
Ok(TxKernel {
|
||||
excess: rsum,
|
||||
excess_sig: self.excess_sig.clone(),
|
||||
fee: self.fee,
|
||||
})
|
||||
}
|
||||
|
@ -264,7 +221,7 @@ impl Transaction {
|
|||
/// Validates all relevant parts of a fully built transaction. Checks the
|
||||
/// excess value against the signature as well as range proofs for each
|
||||
/// output.
|
||||
pub fn validate(&self, secp: &Secp256k1) -> Result<TxProof, secp::Error> {
|
||||
pub fn validate(&self, secp: &Secp256k1) -> Result<TxKernel, secp::Error> {
|
||||
for out in &self.outputs {
|
||||
out.verify_proof(secp)?;
|
||||
}
|
||||
|
@ -275,21 +232,13 @@ impl Transaction {
|
|||
/// A transaction input, mostly a reference to an output being spent by the
|
||||
/// transaction.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Input {
|
||||
BareInput { output: Hash },
|
||||
BlindInput { output: Hash, commit: Commitment },
|
||||
OvertInput {
|
||||
output: Hash,
|
||||
value: u64,
|
||||
blindkey: SecretKey,
|
||||
},
|
||||
}
|
||||
pub struct Input(pub Commitment);
|
||||
|
||||
/// Implementation of Writeable for a transaction Input, defines how to write
|
||||
/// an Input as binary.
|
||||
impl Writeable for Input {
|
||||
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
|
||||
writer.write_fixed_bytes(&self.output_hash())
|
||||
writer.write_fixed_bytes(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,51 +246,27 @@ impl Writeable for Input {
|
|||
/// an Input from a binary stream.
|
||||
impl Readable<Input> for Input {
|
||||
fn read(reader: &mut Reader) -> Result<Input, ser::Error> {
|
||||
Hash::read(reader).map(|h| Input::BareInput { output: h })
|
||||
Ok(Input(Commitment::read(reader)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// The input for a transaction, which spends a pre-existing output. The input
|
||||
/// commitment is a reproduction of the commitment of the output it's spending.
|
||||
impl Input {
|
||||
pub fn commitment(&self) -> Option<Commitment> {
|
||||
match self {
|
||||
&Input::BlindInput { commit, .. } => Some(commit),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn blind(&self, secp: &Secp256k1) -> Input {
|
||||
match self {
|
||||
&Input::OvertInput { output, value, blindkey } => {
|
||||
let commit = secp.commit(value, blindkey).unwrap();
|
||||
Input::BlindInput {
|
||||
output: output,
|
||||
commit: commit,
|
||||
}
|
||||
}
|
||||
_ => *self,
|
||||
}
|
||||
}
|
||||
pub fn blinding_factor(&self) -> Option<SecretKey> {
|
||||
match self {
|
||||
&Input::OvertInput { blindkey, .. } => Some(blindkey),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn output_hash(&self) -> Hash {
|
||||
match self {
|
||||
&Input::BlindInput { output, .. } => output,
|
||||
&Input::OvertInput { output, .. } => output,
|
||||
&Input::BareInput { output, .. } => output,
|
||||
}
|
||||
pub fn commitment(&self) -> Commitment {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Output for a transaction, defining the new ownership of coins that are being
|
||||
/// transferred. The commitment is a blinded value for the output while the
|
||||
/// range
|
||||
/// proof guarantees the commitment includes a positive value without overflow
|
||||
/// and the ownership of the private key.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Output {
|
||||
BlindOutput {
|
||||
commit: Commitment,
|
||||
proof: RangeProof,
|
||||
},
|
||||
OvertOutput { value: u64, blindkey: SecretKey },
|
||||
pub struct Output {
|
||||
pub commit: Commitment,
|
||||
pub proof: RangeProof,
|
||||
}
|
||||
|
||||
/// Implementation of Writeable for a transaction Output, defines how to write
|
||||
|
@ -349,9 +274,9 @@ pub enum Output {
|
|||
impl Writeable for Output {
|
||||
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
|
||||
// The hash of an output is only the hash of its commitment.
|
||||
try!(writer.write_fixed_bytes(&self.commitment().unwrap()));
|
||||
try!(writer.write_fixed_bytes(&self.commit));
|
||||
if writer.serialization_mode() == ser::SerializationMode::Full {
|
||||
try!(writer.write_bytes(&self.proof().unwrap().bytes()))
|
||||
try!(writer.write_bytes(&self.proof.bytes()))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -363,7 +288,7 @@ impl Readable<Output> for Output {
|
|||
fn read(reader: &mut Reader) -> Result<Output, ser::Error> {
|
||||
let commit = try!(Commitment::read(reader));
|
||||
let proof = try!(RangeProof::read(reader));
|
||||
Ok(Output::BlindOutput {
|
||||
Ok(Output {
|
||||
commit: commit,
|
||||
proof: proof,
|
||||
})
|
||||
|
@ -371,45 +296,19 @@ impl Readable<Output> for Output {
|
|||
}
|
||||
|
||||
impl Output {
|
||||
pub fn commitment(&self) -> Option<Commitment> {
|
||||
match self {
|
||||
&Output::BlindOutput { commit, .. } => Some(commit),
|
||||
_ => None,
|
||||
}
|
||||
/// Commitment for the output
|
||||
pub fn commitment(&self) -> Commitment {
|
||||
self.commit
|
||||
}
|
||||
pub fn proof(&self) -> Option<RangeProof> {
|
||||
match self {
|
||||
&Output::BlindOutput { proof, .. } => Some(proof),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn blinding_factor(&self) -> Option<SecretKey> {
|
||||
match self {
|
||||
&Output::OvertOutput { blindkey, .. } => Some(blindkey),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn blind(&self, secp: &Secp256k1) -> Output {
|
||||
match self {
|
||||
&Output::OvertOutput { value, blindkey } => {
|
||||
let commit = secp.commit(value, blindkey).unwrap();
|
||||
let rproof = secp.range_proof(0, value, blindkey, commit);
|
||||
Output::BlindOutput {
|
||||
commit: commit,
|
||||
proof: rproof,
|
||||
}
|
||||
}
|
||||
_ => *self,
|
||||
}
|
||||
|
||||
/// Range proof for the output
|
||||
pub fn proof(&self) -> RangeProof {
|
||||
self.proof
|
||||
}
|
||||
|
||||
/// Validates the range proof using the commitment
|
||||
pub fn verify_proof(&self, secp: &Secp256k1) -> Result<(), secp::Error> {
|
||||
match self {
|
||||
&Output::BlindOutput { commit, proof } => {
|
||||
secp.verify_range_proof(commit, proof).map(|_| ())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
secp.verify_range_proof(self.commit, self.proof).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -426,216 +325,3 @@ fn u64_to_32bytes(n: u64) -> [u8; 32] {
|
|||
BigEndian::write_u64(&mut bytes[24..32], n);
|
||||
bytes
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use core::hash::Hashed;
|
||||
use core::hash::ZERO_HASH;
|
||||
use core::test::{tx1i1o, tx2i1o};
|
||||
use ser::{deserialize, serialize};
|
||||
|
||||
use secp::{self, Secp256k1};
|
||||
use secp::key::SecretKey;
|
||||
use rand::os::OsRng;
|
||||
|
||||
fn new_secp() -> Secp256k1 {
|
||||
secp::Secp256k1::with_caps(secp::ContextFlag::Commit)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_tx_ser() {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let ref secp = new_secp();
|
||||
|
||||
let tx = tx2i1o(secp, &mut rng);
|
||||
let btx = tx.blind(&secp, None).unwrap();
|
||||
let mut vec = Vec::new();
|
||||
serialize(&mut vec, &btx).expect("serialized failed");
|
||||
assert!(vec.len() > 5320);
|
||||
assert!(vec.len() < 5340);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_tx_ser_deser() {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let ref secp = new_secp();
|
||||
|
||||
let tx = tx2i1o(secp, &mut rng);
|
||||
let btx = tx.blind(&secp, None).unwrap();
|
||||
let mut vec = Vec::new();
|
||||
serialize(&mut vec, &btx).expect("serialization failed");
|
||||
// let mut dtx = Transaction::read(&mut BinReader { source: &mut &vec[..]
|
||||
// }).unwrap();
|
||||
let dtx: Transaction = deserialize(&mut &vec[..]).unwrap();
|
||||
assert_eq!(dtx.fee, 1);
|
||||
assert_eq!(dtx.inputs.len(), 2);
|
||||
assert_eq!(dtx.outputs.len(), 1);
|
||||
assert_eq!(btx.hash(), dtx.hash());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_double_ser_deser() {
|
||||
// checks serializing doesn't mess up the tx and produces consistent results
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let ref secp = new_secp();
|
||||
|
||||
let tx = tx2i1o(secp, &mut rng);
|
||||
let btx = tx.blind(&secp, None).unwrap();
|
||||
|
||||
let mut vec = Vec::new();
|
||||
assert!(serialize(&mut vec, &btx).is_ok());
|
||||
let dtx: Transaction = deserialize(&mut &vec[..]).unwrap();
|
||||
|
||||
let mut vec2 = Vec::new();
|
||||
assert!(serialize(&mut vec2, &btx).is_ok());
|
||||
let dtx2: Transaction = deserialize(&mut &vec2[..]).unwrap();
|
||||
|
||||
assert_eq!(btx.hash(), dtx.hash());
|
||||
assert_eq!(dtx.hash(), dtx2.hash());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blind_overt_output() {
|
||||
let ref secp = new_secp();
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
|
||||
let oo = Output::OvertOutput {
|
||||
value: 42,
|
||||
blindkey: SecretKey::new(secp, &mut rng),
|
||||
};
|
||||
if let Output::BlindOutput { commit, proof } = oo.blind(secp) {
|
||||
// checks the blind output is sane and verifies
|
||||
assert!(commit.len() > 0);
|
||||
assert!(proof.bytes().len() > 5000);
|
||||
secp.verify_range_proof(commit, proof).unwrap();
|
||||
|
||||
// checks that changing the value changes the proof and commitment
|
||||
let oo2 = Output::OvertOutput {
|
||||
value: 32,
|
||||
blindkey: SecretKey::new(secp, &mut rng),
|
||||
};
|
||||
if let Output::BlindOutput { commit: c2, proof: p2 } = oo2.blind(secp) {
|
||||
assert!(c2 != commit);
|
||||
assert!(p2.bytes() != proof.bytes());
|
||||
secp.verify_range_proof(c2, p2).unwrap();
|
||||
|
||||
// checks that swapping the proofs fails the validation
|
||||
if let Ok(_) = secp.verify_range_proof(commit, p2) {
|
||||
panic!("verification successful on wrong proof");
|
||||
}
|
||||
} else {
|
||||
panic!("not a blind output");
|
||||
}
|
||||
} else {
|
||||
panic!("not a blind output");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_output() {
|
||||
let ref secp = new_secp();
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
|
||||
let oo = Output::OvertOutput {
|
||||
value: 42,
|
||||
blindkey: SecretKey::new(secp, &mut rng),
|
||||
}
|
||||
.blind(secp);
|
||||
let oo2 = Output::OvertOutput {
|
||||
value: 32,
|
||||
blindkey: SecretKey::new(secp, &mut rng),
|
||||
}
|
||||
.blind(secp);
|
||||
let h = oo.hash();
|
||||
assert!(h != ZERO_HASH);
|
||||
let h2 = oo2.hash();
|
||||
assert!(h != h2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blind_tx() {
|
||||
let ref secp = new_secp();
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
|
||||
let tx = tx2i1o(secp, &mut rng);
|
||||
let btx = tx.blind(&secp, None).unwrap();
|
||||
btx.verify_sig(&secp).unwrap(); // unwrap will panic if invalid
|
||||
|
||||
// checks that the range proof on our blind output is sufficiently hiding
|
||||
if let Output::BlindOutput { proof, .. } = btx.outputs[0] {
|
||||
let info = secp.range_proof_info(proof);
|
||||
assert!(info.min == 0);
|
||||
assert!(info.max == u64::max_value());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_hash_diff() {
|
||||
let ref secp = new_secp();
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
|
||||
let tx1 = tx2i1o(secp, &mut rng);
|
||||
let btx1 = tx1.blind(&secp, None).unwrap();
|
||||
|
||||
let tx2 = tx1i1o(secp, &mut rng);
|
||||
let btx2 = tx2.blind(&secp, None).unwrap();
|
||||
|
||||
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 ref secp = new_secp();
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let outh = ZERO_HASH;
|
||||
|
||||
let tx_alice: Transaction;
|
||||
let blind_sum: SecretKey;
|
||||
|
||||
{
|
||||
// Alice gets 2 of her outputs to send 5 coins to Bob, they become
|
||||
// inputs in the new transaction
|
||||
let inputs = vec![Input::OvertInput {
|
||||
// should match hash, value and blinding factor of output 1
|
||||
output: outh,
|
||||
value: 4,
|
||||
blindkey: SecretKey::new(secp, &mut rng),
|
||||
},
|
||||
Input::OvertInput {
|
||||
// should match hash, value and blinding factor of output 2
|
||||
output: outh,
|
||||
value: 3,
|
||||
blindkey: SecretKey::new(secp, &mut rng),
|
||||
}];
|
||||
|
||||
// Alice also builds her change (we assume fees of 1 coin, so 1 coin change
|
||||
// left)
|
||||
let kc = SecretKey::new(secp, &mut rng);
|
||||
let change = Output::OvertOutput {
|
||||
value: 1,
|
||||
blindkey: kc,
|
||||
};
|
||||
|
||||
// All of this gets into a resulting transaction, which we also use to get
|
||||
// the sum of blinding factors, before we obscure the transaction itself.
|
||||
let tx_open = Transaction::new(inputs, vec![change], 1);
|
||||
blind_sum = tx_open.blind_sum(&secp, None).unwrap();
|
||||
tx_alice = tx_open.blind(&secp, None).unwrap();
|
||||
}
|
||||
|
||||
// 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_full = tx_alice.with_outputs(&mut vec![Output::OvertOutput {
|
||||
value: 5,
|
||||
blindkey: SecretKey::new(secp, &mut rng),
|
||||
}]);
|
||||
let tx_final = tx_full.blind(&secp, Some(blind_sum)).unwrap();
|
||||
tx_final.validate(&secp).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,6 @@ pub fn genesis() -> core::Block {
|
|||
},
|
||||
inputs: vec![],
|
||||
outputs: vec![],
|
||||
proofs: vec![],
|
||||
kernels: vec![],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,12 +72,6 @@ impl PublicKey {
|
|||
}
|
||||
}
|
||||
|
||||
impl hash::Hash for PublicKey {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
state.write(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Library-internal representation of a Secp256k1 signature
|
||||
#[repr(C)]
|
||||
pub struct Signature([c_uchar; 64]);
|
||||
|
|
|
@ -116,6 +116,15 @@ macro_rules! impl_array_newtype {
|
|||
}
|
||||
}
|
||||
|
||||
impl ::std::hash::Hash for $thing {
|
||||
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write(&self.0)
|
||||
// for n in 0..self.len() {
|
||||
// state.write_u8(self.0[n]);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
impl ::serialize::Decodable for $thing {
|
||||
fn decode<D: ::serialize::Decoder>(d: &mut D) -> Result<$thing, D::Error> {
|
||||
use serialize::Decodable;
|
||||
|
|
Loading…
Add table
Reference in a new issue