Small tweaks and addition to a Transaction to make 2-parts building easier. Unit test covering the standard exchange between 2 parties when building a transaction.

This commit is contained in:
Ignotus Peverell 2017-03-11 13:48:33 -08:00
parent f64d20749f
commit cb84f588fb
No known key found for this signature in database
GPG key ID: 99CD25F39F8F8211
3 changed files with 114 additions and 22 deletions

View file

@ -448,14 +448,14 @@ mod test {
let ref secp = new_secp(); let ref secp = new_secp();
let tx1 = tx2i1o(secp, &mut rng); let tx1 = tx2i1o(secp, &mut rng);
let mut btx1 = tx1.blind(&secp).unwrap(); let mut btx1 = tx1.blind(&secp, None).unwrap();
let tx2 = tx1i1o(secp, &mut rng); let tx2 = tx1i1o(secp, &mut rng);
let mut btx2 = tx2.blind(&secp).unwrap(); let mut btx2 = tx2.blind(&secp, None).unwrap();
// spending tx2 // spending tx2
let spending = txspend1i1o(secp, &mut rng, tx2.outputs[0], btx2.outputs[0].hash()); let spending = txspend1i1o(secp, &mut rng, tx2.outputs[0], btx2.outputs[0].hash());
let mut btx3 = spending.blind(&secp).unwrap(); let mut btx3 = spending.blind(&secp, None).unwrap();
let b = new_block(vec![&mut btx1, &mut btx2, &mut btx3], secp); let b = new_block(vec![&mut btx1, &mut btx2, &mut btx3], secp);
// block should have been automatically compacted (including reward output) and // block should have been automatically compacted (including reward output) and
@ -473,14 +473,14 @@ mod test {
let ref secp = new_secp(); let ref secp = new_secp();
let tx1 = tx2i1o(secp, &mut rng); let tx1 = tx2i1o(secp, &mut rng);
let mut btx1 = tx1.blind(&secp).unwrap(); let mut btx1 = tx1.blind(&secp, None).unwrap();
let tx2 = tx1i1o(secp, &mut rng); let tx2 = tx1i1o(secp, &mut rng);
let mut btx2 = tx2.blind(&secp).unwrap(); let mut btx2 = tx2.blind(&secp, None).unwrap();
// spending tx2 // spending tx2
let spending = txspend1i1o(secp, &mut rng, tx2.outputs[0], btx2.outputs[0].hash()); let spending = txspend1i1o(secp, &mut rng, tx2.outputs[0], btx2.outputs[0].hash());
let mut btx3 = spending.blind(&secp).unwrap(); let mut btx3 = spending.blind(&secp, None).unwrap();
let b1 = new_block(vec![&mut btx1, &mut btx2], secp); let b1 = new_block(vec![&mut btx1, &mut btx2], secp);
b1.verify(&secp).unwrap(); b1.verify(&secp).unwrap();

View file

@ -235,7 +235,7 @@ mod test {
}], }],
9); 9);
// blinding should fail as signing with a zero r*G shouldn't work // blinding should fail as signing with a zero r*G shouldn't work
tx.blind(&secp).unwrap(); tx.blind(&secp, None).unwrap();
} }
#[test] #[test]
@ -255,7 +255,7 @@ mod test {
let skey = SecretKey::new(secp, &mut rng); let skey = SecretKey::new(secp, &mut rng);
let tx1 = tx2i1o(secp, &mut rng); let tx1 = tx2i1o(secp, &mut rng);
let mut btx1 = tx1.blind(&secp).unwrap(); let mut btx1 = tx1.blind(&secp, None).unwrap();
btx1.verify_sig(&secp).unwrap(); btx1.verify_sig(&secp).unwrap();
let b = Block::new(&BlockHeader::default(), vec![&mut btx1], skey).unwrap(); let b = Block::new(&BlockHeader::default(), vec![&mut btx1], skey).unwrap();
@ -269,11 +269,11 @@ mod test {
let skey = SecretKey::new(secp, &mut rng); let skey = SecretKey::new(secp, &mut rng);
let tx1 = tx2i1o(secp, &mut rng); let tx1 = tx2i1o(secp, &mut rng);
let mut btx1 = tx1.blind(&secp).unwrap(); let mut btx1 = tx1.blind(&secp, None).unwrap();
btx1.verify_sig(&secp).unwrap(); btx1.verify_sig(&secp).unwrap();
let tx2 = tx1i1o(secp, &mut rng); let tx2 = tx1i1o(secp, &mut rng);
let mut btx2 = tx2.blind(&secp).unwrap(); let mut btx2 = tx2.blind(&secp, None).unwrap();
btx2.verify_sig(&secp).unwrap(); btx2.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 btx1, &mut btx2], skey).unwrap();

View file

@ -165,6 +165,20 @@ impl Transaction {
} }
} }
/// 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,
}
}
/// The hash of a transaction is the Merkle tree of its inputs and outputs /// The hash of a transaction is the Merkle tree of its inputs and outputs
/// hashes. None of the rest is required. /// hashes. None of the rest is required.
fn hash(&mut self) -> Hash { fn hash(&mut self) -> Hash {
@ -178,9 +192,15 @@ impl Transaction {
/// algorithm: calculates the commitments for each inputs and outputs /// algorithm: calculates the commitments for each inputs and outputs
/// using the values and blinding factors, takes the blinding factors /// using the values and blinding factors, takes the blinding factors
/// remainder and uses it for an empty signature. /// remainder and uses it for an empty signature.
pub fn blind(&self, secp: &Secp256k1) -> Result<Transaction, secp::Error> { /// 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 // we compute the sum of blinding factors to get the k remainder
let remainder = try!(self.blind_sum(secp)); let remainder = try!(self.blind_sum(secp, excess));
// next, blind the inputs and outputs if they haven't been yet // 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_inputs = map_vec!(self.inputs, |inp| inp.blind(secp));
@ -200,11 +220,20 @@ impl Transaction {
}) })
} }
/// Compute the sum of blinding factors on all overt inputs and outputs /// Compute the sum of blinding factors on all overt inputs and outputs of
/// of the transaction to get the k remainder. /// the transaction to get the k remainder.
pub fn blind_sum(&self, secp: &Secp256k1) -> Result<SecretKey, secp::Error> { /// An excess value can optionally be provided to account for cases when the
let inputs_blinding_fact = filter_map_vec!(self.inputs, |inp| inp.blinding_factor()); /// 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()); 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) secp.blind_sum(inputs_blinding_fact, outputs_blinding_fact)
} }
@ -231,6 +260,16 @@ impl Transaction {
fee: self.fee, fee: self.fee,
}) })
} }
/// 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> {
for out in &self.outputs {
out.verify_proof(secp)?;
}
self.verify_sig(secp)
}
} }
/// A transaction input, mostly a reference to an output being spent by the /// A transaction input, mostly a reference to an output being spent by the
@ -410,7 +449,7 @@ mod test {
let ref secp = new_secp(); let ref secp = new_secp();
let tx = tx2i1o(secp, &mut rng); let tx = tx2i1o(secp, &mut rng);
let btx = tx.blind(&secp).unwrap(); let btx = tx.blind(&secp, None).unwrap();
let mut vec = Vec::new(); let mut vec = Vec::new();
serialize(&mut vec, &btx).expect("serialized failed"); serialize(&mut vec, &btx).expect("serialized failed");
assert!(vec.len() > 5320); assert!(vec.len() > 5320);
@ -423,7 +462,7 @@ mod test {
let ref secp = new_secp(); let ref secp = new_secp();
let tx = tx2i1o(secp, &mut rng); let tx = tx2i1o(secp, &mut rng);
let btx = tx.blind(&secp).unwrap(); let btx = tx.blind(&secp, None).unwrap();
let mut vec = Vec::new(); let mut vec = Vec::new();
serialize(&mut vec, &btx).expect("serialization failed"); serialize(&mut vec, &btx).expect("serialization failed");
// let mut dtx = Transaction::read(&mut BinReader { source: &mut &vec[..] // let mut dtx = Transaction::read(&mut BinReader { source: &mut &vec[..]
@ -442,7 +481,7 @@ mod test {
let ref secp = new_secp(); let ref secp = new_secp();
let tx = tx2i1o(secp, &mut rng); let tx = tx2i1o(secp, &mut rng);
let btx = tx.blind(&secp).unwrap(); let btx = tx.blind(&secp, None).unwrap();
let mut vec = Vec::new(); let mut vec = Vec::new();
assert!(serialize(&mut vec, &btx).is_ok()); assert!(serialize(&mut vec, &btx).is_ok());
@ -520,7 +559,7 @@ mod test {
let mut rng = OsRng::new().unwrap(); let mut rng = OsRng::new().unwrap();
let tx = tx2i1o(secp, &mut rng); let tx = tx2i1o(secp, &mut rng);
let btx = tx.blind(&secp).unwrap(); let btx = tx.blind(&secp, None).unwrap();
btx.verify_sig(&secp).unwrap(); // unwrap will panic if invalid btx.verify_sig(&secp).unwrap(); // unwrap will panic if invalid
// checks that the range proof on our blind output is sufficiently hiding // checks that the range proof on our blind output is sufficiently hiding
@ -537,13 +576,66 @@ mod test {
let mut rng = OsRng::new().unwrap(); let mut rng = OsRng::new().unwrap();
let tx1 = tx2i1o(secp, &mut rng); let tx1 = tx2i1o(secp, &mut rng);
let btx1 = tx1.blind(&secp).unwrap(); let btx1 = tx1.blind(&secp, None).unwrap();
let tx2 = tx1i1o(secp, &mut rng); let tx2 = tx1i1o(secp, &mut rng);
let btx2 = tx2.blind(&secp).unwrap(); let btx2 = tx2.blind(&secp, None).unwrap();
if btx1.hash() == btx2.hash() { if btx1.hash() == btx2.hash() {
panic!("diff txs have same 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();
}
} }