From e060b1953ef13ccb9e2f46db453f92fcd90713e1 Mon Sep 17 00:00:00 2001 From: AntiochP <30642645+antiochp@users.noreply.github.com> Date: Thu, 5 Oct 2017 17:40:46 -0400 Subject: [PATCH] rust-secp256k1-zkp breaking changes to support (#155) Rewinding range proofs to recover transaction value from outputs --- core/src/core/block.rs | 3 +- core/src/core/build.rs | 3 +- core/src/core/transaction.rs | 51 ++++++++++++++++++++++++++++++++ keychain/src/keychain.rs | 57 +++++++++++++++++++++++++++++++++--- pool/src/graph.rs | 18 +++++++----- pool/src/pool.rs | 7 +++-- 6 files changed, 124 insertions(+), 15 deletions(-) diff --git a/core/src/core/block.rs b/core/src/core/block.rs index 51f581d5a..5b1fb67f1 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -484,9 +484,10 @@ impl Block { let msg = secp::Message::from_slice(&[0; secp::constants::MESSAGE_SIZE])?; let sig = keychain.sign(&msg, &pubkey)?; let commit = keychain.commit(REWARD, &pubkey)?; + let msg = secp::pedersen::ProofMessage::empty(); // let switch_commit = keychain.switch_commit(pubkey)?; - let rproof = keychain.range_proof(REWARD, &pubkey, commit)?; + let rproof = keychain.range_proof(REWARD, &pubkey, commit, msg)?; let output = Output { features: COINBASE_OUTPUT, diff --git a/core/src/core/build.rs b/core/src/core/build.rs index 621690d16..31afc93a3 100644 --- a/core/src/core/build.rs +++ b/core/src/core/build.rs @@ -55,7 +55,8 @@ pub fn input(value: u64, pubkey: Identifier) -> Box { pub fn output(value: u64, pubkey: Identifier) -> Box { Box::new(move |build, (tx, sum)| -> (Transaction, BlindSum) { let commit = build.keychain.commit(value, &pubkey).unwrap(); - let rproof = build.keychain.range_proof(value, &pubkey, commit).unwrap(); + let msg = secp::pedersen::ProofMessage::empty(); + let rproof = build.keychain.range_proof(value, &pubkey, commit, msg).unwrap(); (tx.with_output(Output { features: DEFAULT_OUTPUT, diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index 2d8a52317..fce651a85 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -21,6 +21,7 @@ use std::ops; use core::Committed; use core::pmmr::Summable; +use keychain::{Identifier, Keychain}; use ser::{self, Reader, Writer, Readable, Writeable}; bitflags! { @@ -357,6 +358,21 @@ impl Output { pub fn verify_proof(&self, secp: &Secp256k1) -> Result<(), secp::Error> { secp.verify_range_proof(self.commit, self.proof).map(|_| ()) } + + /// Given the original blinding factor we can recover the + /// value from the range proof and the commitment + pub fn recover_value(&self, keychain: &Keychain, pubkey: &Identifier) -> Option { + match keychain.rewind_range_proof(pubkey, self.commit, self.proof) { + Ok(proof_info) => { + if proof_info.success { + Some(proof_info.value) + } else { + None + } + }, + Err(_) => None + } + } } /// Wrapper to Output commitments to provide the Summable trait. @@ -423,3 +439,38 @@ fn u64_to_32bytes(n: u64) -> [u8; 32] { BigEndian::write_u64(&mut bytes[24..32], n); bytes } + +#[cfg(test)] +mod test { + use super::*; + use keychain::Keychain; + use secp; + + #[test] + fn test_output_value_recovery() { + let keychain = Keychain::from_random_seed().unwrap(); + let pubkey = keychain.derive_pubkey(1).unwrap(); + + let commit = keychain.commit(1003, &pubkey).unwrap(); + let msg = secp::pedersen::ProofMessage::empty(); + let proof = keychain.range_proof(1003, &pubkey, commit, msg).unwrap(); + + let output = Output { + features: DEFAULT_OUTPUT, + commit: commit, + proof: proof, + }; + + // check we can successfully recover the value with the original blinding factor + let recovered_value = output.recover_value(&keychain, &pubkey).unwrap(); + assert_eq!(recovered_value, 1003); + + // check we cannot recover the value without the original blinding factor + let pubkey2 = keychain.derive_pubkey(2).unwrap(); + let not_recoverable = output.recover_value(&keychain, &pubkey2); + match not_recoverable { + Some(_) => panic!("expected value to be None here"), + None => {} + } + } +} diff --git a/keychain/src/keychain.rs b/keychain/src/keychain.rs index b11137870..daa855b0c 100644 --- a/keychain/src/keychain.rs +++ b/keychain/src/keychain.rs @@ -17,7 +17,7 @@ use rand::{thread_rng, Rng}; use secp; use secp::{Message, Secp256k1, Signature}; use secp::key::SecretKey; -use secp::pedersen::{Commitment, RangeProof}; +use secp::pedersen::{Commitment, ProofMessage, ProofInfo, RangeProof}; use blake2; use blind::{BlindingFactor, BlindSum}; @@ -107,13 +107,23 @@ impl Keychain { amount: u64, pubkey: &Identifier, commit: Commitment, + msg: ProofMessage, ) -> Result { let skey = self.derived_key(pubkey)?; - let nonce = self.secp.nonce(); - let range_proof = self.secp.range_proof(0, amount, skey, commit, nonce); + let range_proof = self.secp.range_proof(0, amount, skey, commit, msg); Ok(range_proof) } + pub fn rewind_range_proof( + &self, + pubkey: &Identifier, + commit: Commitment, + proof: RangeProof, + ) -> Result { + let nonce = self.derived_key(pubkey)?; + Ok(self.secp.rewind_range_proof(commit, proof, nonce)) + } + pub fn blind_sum(&self, blind_sum: &BlindSum) -> Result { let mut pos_keys: Vec = blind_sum .positive_pubkeys @@ -167,11 +177,11 @@ impl Keychain { mod test { use keychain::Keychain; use secp; + use secp::pedersen::ProofMessage; #[test] fn test_key_derivation() { let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); - let keychain = Keychain::from_random_seed().unwrap(); // use the keychain to derive a "pubkey" based on the underlying seed @@ -188,4 +198,43 @@ mod test { let sig = keychain.sign(&msg, &pubkey).unwrap(); secp.verify_from_commit(&msg, &sig, &commit).unwrap(); } + + #[test] + fn test_rewind_range_proof() { + let keychain = Keychain::from_random_seed().unwrap(); + let pubkey = keychain.derive_pubkey(1).unwrap(); + let commit = keychain.commit(5, &pubkey).unwrap(); + let msg = ProofMessage::empty(); + + let proof = keychain.range_proof(5, &pubkey, commit, msg).unwrap(); + let proof_info = keychain.rewind_range_proof(&pubkey, commit, proof).unwrap(); + + assert_eq!(proof_info.success, true); + assert_eq!(proof_info.value, 5); + + // now check the recovered message is "empty" (but not truncated) i.e. all zeroes + assert_eq!( + proof_info.message, + secp::pedersen::ProofMessage::from_bytes(&[0; secp::constants::PROOF_MSG_SIZE]) + ); + + let pubkey2 = keychain.derive_pubkey(2).unwrap(); + + // cannot rewind with a different nonce + let proof_info = keychain.rewind_range_proof(&pubkey2, commit, proof).unwrap(); + assert_eq!(proof_info.success, false); + assert_eq!(proof_info.value, 0); + + // cannot rewind with a commitment to the same value using a different key + let commit2 = keychain.commit(5, &pubkey2).unwrap(); + let proof_info = keychain.rewind_range_proof(&pubkey, commit2, proof).unwrap(); + assert_eq!(proof_info.success, false); + assert_eq!(proof_info.value, 0); + + // cannot rewind with a commitment to a different value + let commit3 = keychain.commit(4, &pubkey).unwrap(); + let proof_info = keychain.rewind_range_proof(&pubkey, commit3, proof).unwrap(); + assert_eq!(proof_info.success, false); + assert_eq!(proof_info.value, 0); + } } diff --git a/pool/src/graph.rs b/pool/src/graph.rs index d915515cf..ee0223f86 100644 --- a/pool/src/graph.rs +++ b/pool/src/graph.rs @@ -248,23 +248,27 @@ pub fn transaction_identifier(tx: &core::transaction::Transaction) -> core::hash #[cfg(test)] mod tests { use super::*; - use secp::{Secp256k1, ContextFlag}; - use secp::key; + use secp; + use keychain::Keychain; #[test] fn test_add_entry() { - let ec = Secp256k1::with_caps(ContextFlag::Commit); + let 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(); - let output_commit = ec.commit_value(70).unwrap(); + let output_commit = keychain.commit(70, &pk1).unwrap(); let inputs = vec![ - core::transaction::Input(ec.commit_value(50).unwrap()), - core::transaction::Input(ec.commit_value(25).unwrap()), + core::transaction::Input(keychain.commit(50, &pk2).unwrap()), + core::transaction::Input(keychain.commit(25, &pk3).unwrap()), ]; + let msg = secp::pedersen::ProofMessage::empty(); let outputs = vec![ core::transaction::Output { features: core::transaction::DEFAULT_OUTPUT, commit: output_commit, - proof: ec.range_proof(0, 100, key::ZERO_KEY, output_commit, ec.nonce()), + proof: keychain.range_proof(100, &pk1, output_commit, msg).unwrap(), }, ]; let test_transaction = core::transaction::Transaction::new(inputs, outputs, 5); diff --git a/pool/src/pool.rs b/pool/src/pool.rs index aa6f91bc4..c3d06bc30 100644 --- a/pool/src/pool.rs +++ b/pool/src/pool.rs @@ -561,6 +561,7 @@ mod tests { use types::*; use core::core::build; use blockchain::{DummyChain, DummyChainImpl, DummyUtxoSet}; + use secp; use keychain::Keychain; use std::sync::{Arc, RwLock}; use blake2; @@ -1074,7 +1075,8 @@ mod tests { let keychain = keychain_for_tests(); let pubkey = keychain.derive_pubkey(value as u32).unwrap(); let commit = keychain.commit(value, &pubkey).unwrap(); - let proof = keychain.range_proof(value, &pubkey, commit).unwrap(); + let msg = secp::pedersen::ProofMessage::empty(); + let proof = keychain.range_proof(value, &pubkey, commit, msg).unwrap(); transaction::Output { features: transaction::DEFAULT_OUTPUT, @@ -1088,7 +1090,8 @@ mod tests { let keychain = keychain_for_tests(); let pubkey = keychain.derive_pubkey(value as u32).unwrap(); let commit = keychain.commit(value, &pubkey).unwrap(); - let proof = keychain.range_proof(value, &pubkey, commit).unwrap(); + let msg = secp::pedersen::ProofMessage::empty(); + let proof = keychain.range_proof(value, &pubkey, commit, msg).unwrap(); transaction::Output { features: transaction::COINBASE_OUTPUT,