From cb84f588fb611bf961a95d6a875dbd5336ac6f32 Mon Sep 17 00:00:00 2001 From: Ignotus Peverell Date: Sat, 11 Mar 2017 13:48:33 -0800 Subject: [PATCH] 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. --- core/src/core/block.rs | 12 ++-- core/src/core/mod.rs | 8 +-- core/src/core/transaction.rs | 116 +++++++++++++++++++++++++++++++---- 3 files changed, 114 insertions(+), 22 deletions(-) diff --git a/core/src/core/block.rs b/core/src/core/block.rs index 647c6dde8..e31c8f632 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -448,14 +448,14 @@ mod test { let ref secp = new_secp(); 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 mut btx2 = tx2.blind(&secp).unwrap(); + 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).unwrap(); + 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 @@ -473,14 +473,14 @@ mod test { let ref secp = new_secp(); 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 mut btx2 = tx2.blind(&secp).unwrap(); + 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).unwrap(); + let mut btx3 = spending.blind(&secp, None).unwrap(); let b1 = new_block(vec![&mut btx1, &mut btx2], secp); b1.verify(&secp).unwrap(); diff --git a/core/src/core/mod.rs b/core/src/core/mod.rs index 2fb994b2f..598b6acd0 100644 --- a/core/src/core/mod.rs +++ b/core/src/core/mod.rs @@ -235,7 +235,7 @@ mod test { }], 9); // blinding should fail as signing with a zero r*G shouldn't work - tx.blind(&secp).unwrap(); + tx.blind(&secp, None).unwrap(); } #[test] @@ -255,7 +255,7 @@ mod test { let skey = SecretKey::new(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(); 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 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(); 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(); let b = Block::new(&BlockHeader::default(), vec![&mut btx1, &mut btx2], skey).unwrap(); diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index 5b24c6f0f..0e92c15f4 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -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) -> 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 /// hashes. None of the rest is required. fn hash(&mut self) -> Hash { @@ -178,9 +192,15 @@ impl Transaction { /// 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. - pub fn blind(&self, secp: &Secp256k1) -> Result { + /// 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) + -> Result { // 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 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 - /// of the transaction to get the k remainder. - pub fn blind_sum(&self, secp: &Secp256k1) -> Result { - let inputs_blinding_fact = filter_map_vec!(self.inputs, |inp| inp.blinding_factor()); + /// 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) + -> Result { + 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) } @@ -231,6 +260,16 @@ impl Transaction { 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 { + 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 @@ -410,7 +449,7 @@ mod test { let ref secp = new_secp(); let tx = tx2i1o(secp, &mut rng); - let btx = tx.blind(&secp).unwrap(); + let btx = tx.blind(&secp, None).unwrap(); let mut vec = Vec::new(); serialize(&mut vec, &btx).expect("serialized failed"); assert!(vec.len() > 5320); @@ -423,7 +462,7 @@ mod test { let ref secp = new_secp(); let tx = tx2i1o(secp, &mut rng); - let btx = tx.blind(&secp).unwrap(); + 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[..] @@ -442,7 +481,7 @@ mod test { let ref secp = new_secp(); let tx = tx2i1o(secp, &mut rng); - let btx = tx.blind(&secp).unwrap(); + let btx = tx.blind(&secp, None).unwrap(); let mut vec = Vec::new(); assert!(serialize(&mut vec, &btx).is_ok()); @@ -520,7 +559,7 @@ mod test { let mut rng = OsRng::new().unwrap(); 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 // 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 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 btx2 = tx2.blind(&secp).unwrap(); + 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(); + } }