diff --git a/core/src/libtx/build.rs b/core/src/libtx/build.rs index 02beb197b..076f52b35 100644 --- a/core/src/libtx/build.rs +++ b/core/src/libtx/build.rs @@ -199,47 +199,58 @@ where } /// Builds a complete transaction. +/// NOTE: We only use this in tests (for convenience). +/// In the real world we use signature aggregation across multiple participants. pub fn transaction( features: KernelFeatures, elems: Vec>>, keychain: &K, builder: &B, ) -> Result +where + K: Keychain, + B: ProofBuild, +{ + let mut kernel = TxKernel::with_features(features); + + // Construct the message to be signed. + let msg = kernel.msg_to_sign()?; + + // Generate kernel public excess and associated signature. + let excess = BlindingFactor::rand(&keychain.secp()); + let skey = excess.secret_key(&keychain.secp())?; + kernel.excess = keychain.secp().commit(0, skey)?; + let pubkey = &kernel.excess.to_pubkey(&keychain.secp())?; + kernel.excess_sig = aggsig::sign_with_blinding(&keychain.secp(), &msg, &excess, Some(&pubkey))?; + kernel.verify()?; + transaction_with_kernel(elems, kernel, excess, keychain, builder) +} + +/// Build a complete transaction with the provided kernel and corresponding private excess. +/// NOTE: Only used in tests (for convenience). +/// Cannot recommend passing private excess around like this in the real world. +pub fn transaction_with_kernel( + elems: Vec>>, + kernel: TxKernel, + excess: BlindingFactor, + keychain: &K, + builder: &B, +) -> Result where K: Keychain, B: ProofBuild, { let mut ctx = Context { keychain, builder }; - let (mut tx, sum) = elems + let (tx, sum) = elems .iter() .fold(Ok((Transaction::empty(), BlindSum::new())), |acc, elem| { elem(&mut ctx, acc) })?; let blind_sum = ctx.keychain.blind_sum(&sum)?; - // Split the key so we can generate an offset for the tx. - let split = blind_sum.split(&keychain.secp())?; - let k1 = split.blind_1; - let k2 = split.blind_2; - - let mut kern = TxKernel::with_features(features); - - // Construct the message to be signed. - let msg = kern.msg_to_sign()?; - - // Generate kernel excess and excess_sig using the split key k1. - let skey = k1.secret_key(&keychain.secp())?; - kern.excess = ctx.keychain.secp().commit(0, skey)?; - let pubkey = &kern.excess.to_pubkey(&keychain.secp())?; - kern.excess_sig = aggsig::sign_with_blinding(&keychain.secp(), &msg, &k1, Some(&pubkey))?; - - // Store the kernel offset (k2) on the tx. - // Commitments will sum correctly when accounting for the offset. - tx.offset = k2; - - // Set the kernel on the tx. - let tx = tx.replace_kernel(kern); - + // Update tx with new kernel and offset. + let mut tx = tx.replace_kernel(kernel); + tx.offset = blind_sum.split(&excess, &keychain.secp())?; Ok(tx) } diff --git a/core/tests/core.rs b/core/tests/core.rs index b9bc58f8b..4c9437d6f 100644 --- a/core/tests/core.rs +++ b/core/tests/core.rs @@ -24,7 +24,7 @@ use self::core::core::{ aggregate, deaggregate, KernelFeatures, Output, Transaction, TxKernel, Weighting, }; use self::core::libtx::build::{self, initial_tx, input, output, with_excess}; -use self::core::libtx::ProofBuilder; +use self::core::libtx::{aggsig, ProofBuilder}; use self::core::{global, ser}; use crate::common::{new_block, tx1i1o, tx1i2o, tx2i1o}; use grin_core as core; @@ -148,6 +148,81 @@ fn build_tx_kernel() { assert_eq!(2, tx.fee()); } +// Proof of concept demonstrating we can build two transactions that share +// the *same* kernel public excess. This is a key part of building a transaction as two +// "halves" for NRD kernels. +// Note: In a real world scenario multiple participants would build the kernel signature +// using signature aggregation. No party would see the full private kernel excess and +// the halves would need to be constructed with carefully crafted individual offsets to +// adjust the excess as required. +// For the sake of convenience we are simply constructing the kernel directly and we have access +// to the full private excess. +#[test] +fn build_two_half_kernels() { + test_setup(); + let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); + let key_id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); + let key_id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0); + let key_id3 = ExtKeychain::derive_key_id(1, 3, 0, 0, 0); + + // build kernel with associated private excess + let mut kernel = TxKernel::with_features(KernelFeatures::Plain { fee: 2 }); + + // Construct the message to be signed. + let msg = kernel.msg_to_sign().unwrap(); + + // Generate a kernel with public excess and associated signature. + let excess = BlindingFactor::rand(&keychain.secp()); + let skey = excess.secret_key(&keychain.secp()).unwrap(); + kernel.excess = keychain.secp().commit(0, skey).unwrap(); + let pubkey = &kernel.excess.to_pubkey(&keychain.secp()).unwrap(); + kernel.excess_sig = + aggsig::sign_with_blinding(&keychain.secp(), &msg, &excess, Some(&pubkey)).unwrap(); + kernel.verify().unwrap(); + + let tx1 = build::transaction_with_kernel( + vec![input(10, key_id1), output(8, key_id2.clone())], + kernel.clone(), + excess.clone(), + &keychain, + &builder, + ) + .unwrap(); + + let tx2 = build::transaction_with_kernel( + vec![input(8, key_id2), output(6, key_id3)], + kernel.clone(), + excess.clone(), + &keychain, + &builder, + ) + .unwrap(); + + assert_eq!( + tx1.validate(Weighting::AsTransaction, verifier_cache()), + Ok(()), + ); + + assert_eq!( + tx2.validate(Weighting::AsTransaction, verifier_cache()), + Ok(()), + ); + + // The transactions share an identical kernel. + assert_eq!(tx1.kernels()[0], tx2.kernels()[0]); + + // The public kernel excess is shared between both "halves". + assert_eq!(tx1.kernels()[0].excess(), tx2.kernels()[0].excess()); + + // Each transaction is built from different inputs and outputs. + // The offset differs to compensate for the shared excess commitments. + assert!(tx1.offset != tx2.offset); + + // For completeness, these are different transactions. + assert!(tx1.hash() != tx2.hash()); +} + // Combine two transactions into one big transaction (with multiple kernels) // and check it still validates. #[test] diff --git a/keychain/src/types.rs b/keychain/src/types.rs index 947e7ddd9..5de483ed5 100644 --- a/keychain/src/types.rs +++ b/keychain/src/types.rs @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Keychain trait and its main supporting types. The Identifier is a +//! semi-opaque structure (just bytes) to track keys within the Keychain. +//! BlindingFactor is a useful wrapper around a private key to help with +//! commitment generation. + use rand::thread_rng; use std::cmp::min; use std::convert::TryFrom; use std::io::Cursor; -use std::ops::Add; -/// Keychain trait and its main supporting types. The Identifier is a -/// semi-opaque structure (just bytes) to track keys within the Keychain. -/// BlindingFactor is a useful wrapper around a private key to help with -/// commitment generation. use std::{error, fmt}; use crate::blake2::blake2b::blake2b; @@ -28,10 +28,9 @@ use crate::extkey_bip32::{self, ChildNumber}; use serde::{de, ser}; //TODO: Convert errors to use ErrorKind use crate::util::secp::constants::SECRET_KEY_SIZE; -use crate::util::secp::key::{PublicKey, SecretKey}; +use crate::util::secp::key::{PublicKey, SecretKey, ZERO_KEY}; use crate::util::secp::pedersen::Commitment; use crate::util::secp::{self, Message, Secp256k1, Signature}; -use crate::util::static_secp_instance; use crate::util::ToHex; use zeroize::Zeroize; @@ -242,34 +241,8 @@ impl AsRef<[u8]> for BlindingFactor { } } -impl Add for BlindingFactor { - type Output = Result; - - // Convenient (and robust) way to add two blinding_factors together. - // Handles "zero" blinding_factors correctly. - // - // let bf = (bf1 + bf2)?; - // - fn add(self, other: BlindingFactor) -> Self::Output { - let secp = static_secp_instance(); - let secp = secp.lock(); - let keys = vec![self, other] - .into_iter() - .filter(|x| *x != BlindingFactor::zero()) - .filter_map(|x| x.secret_key(&secp).ok()) - .collect::>(); - - if keys.is_empty() { - Ok(BlindingFactor::zero()) - } else { - let sum = secp.blind_sum(keys, vec![])?; - Ok(BlindingFactor::from_secret_key(sum)) - } - } -} - impl BlindingFactor { - pub fn from_secret_key(skey: secp::key::SecretKey) -> BlindingFactor { + pub fn from_secret_key(skey: SecretKey) -> BlindingFactor { BlindingFactor::from_slice(&skey.as_ref()) } @@ -281,7 +254,15 @@ impl BlindingFactor { } pub fn zero() -> BlindingFactor { - BlindingFactor::from_secret_key(secp::key::ZERO_KEY) + BlindingFactor::from_secret_key(ZERO_KEY) + } + + pub fn is_zero(&self) -> bool { + self.0 == ZERO_KEY.as_ref() + } + + pub fn rand(secp: &Secp256k1) -> BlindingFactor { + BlindingFactor::from_secret_key(SecretKey::new(secp, &mut thread_rng())) } pub fn from_hex(hex: &str) -> Result { @@ -289,14 +270,29 @@ impl BlindingFactor { Ok(BlindingFactor::from_slice(&bytes)) } - pub fn secret_key(&self, secp: &Secp256k1) -> Result { - if *self == BlindingFactor::zero() { - // TODO - need this currently for tx tests - // the "zero" secret key is not actually a valid secret_key - // and secp lib checks this - Ok(secp::key::ZERO_KEY) + // Handle "zero" blinding_factor correctly, by returning the "zero" key. + // We need this for some of the tests. + pub fn secret_key(&self, secp: &Secp256k1) -> Result { + if self.is_zero() { + Ok(ZERO_KEY) } else { - secp::key::SecretKey::from_slice(secp, &self.0).map_err(Error::Secp) + SecretKey::from_slice(secp, &self.0).map_err(Error::Secp) + } + } + + // Convenient (and robust) way to add two blinding_factors together. + // Handles "zero" blinding_factors correctly. + pub fn add(&self, other: &BlindingFactor, secp: &Secp256k1) -> Result { + let keys = vec![self, other] + .into_iter() + .filter(|x| !x.is_zero()) + .filter_map(|x| x.secret_key(&secp).ok()) + .collect::>(); + if keys.is_empty() { + Ok(BlindingFactor::zero()) + } else { + let sum = secp.blind_sum(keys, vec![])?; + Ok(BlindingFactor::from_secret_key(sum)) } } @@ -306,26 +302,19 @@ impl BlindingFactor { /// This prevents an actor from being able to sum a set of inputs, outputs /// and kernels from a block to identify and reconstruct a particular tx /// from a block. You would need both k1, k2 to do this. - pub fn split(&self, secp: &Secp256k1) -> Result { - let skey_1 = secp::key::SecretKey::new(secp, &mut thread_rng()); - - // use blind_sum to subtract skey_1 from our key (to give k = k1 + k2) + pub fn split( + &self, + blind_1: &BlindingFactor, + secp: &Secp256k1, + ) -> Result { + // use blind_sum to subtract skey_1 from our key such that skey = skey_1 + skey_2 let skey = self.secret_key(secp)?; - let skey_2 = secp.blind_sum(vec![skey], vec![skey_1.clone()])?; - - let blind_1 = BlindingFactor::from_secret_key(skey_1); - let blind_2 = BlindingFactor::from_secret_key(skey_2); - - Ok(SplitBlindingFactor { blind_1, blind_2 }) + let skey_1 = blind_1.secret_key(secp)?; + let skey_2 = secp.blind_sum(vec![skey], vec![skey_1])?; + Ok(BlindingFactor::from_secret_key(skey_2)) } } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SplitBlindingFactor { - pub blind_1: BlindingFactor, - pub blind_2: BlindingFactor, -} - /// Accumulator to compute the sum of blinding factors. Keeps track of each /// factor as well as the "sign" with which they should be combined. #[derive(Clone, Debug, PartialEq)] @@ -557,16 +546,17 @@ mod test { assert!(all_zeros) } + // split a key, sum the split keys and confirm the sum matches the original key #[test] fn split_blinding_factor() { let secp = Secp256k1::new(); let skey_in = SecretKey::new(&secp, &mut thread_rng()); let blind = BlindingFactor::from_secret_key(skey_in.clone()); - let split = blind.split(&secp).unwrap(); + let blind_1 = BlindingFactor::rand(&secp); + let blind_2 = blind.split(&blind_1, &secp).unwrap(); - // split a key, sum the split keys and confirm the sum matches the original key - let mut skey_sum = split.blind_1.secret_key(&secp).unwrap(); - let skey_2 = split.blind_2.secret_key(&secp).unwrap(); + let mut skey_sum = blind_1.secret_key(&secp).unwrap(); + let skey_2 = blind_2.secret_key(&secp).unwrap(); skey_sum.add_assign(&secp, &skey_2).unwrap(); assert_eq!(skey_in, skey_sum); } diff --git a/pool/src/pool.rs b/pool/src/pool.rs index 8d7eeb531..eaa894d8d 100644 --- a/pool/src/pool.rs +++ b/pool/src/pool.rs @@ -29,6 +29,7 @@ use grin_util as util; use std::cmp::Reverse; use std::collections::{HashMap, HashSet}; use std::sync::Arc; +use util::static_secp_instance; pub struct Pool where @@ -272,7 +273,12 @@ where header: &BlockHeader, ) -> Result { let overage = tx.overage(); - let offset = (header.total_kernel_offset() + tx.offset.clone())?; + + let offset = { + let secp = static_secp_instance(); + let secp = secp.lock(); + header.total_kernel_offset().add(&tx.offset, &secp) + }?; let block_sums = self.blockchain.get_block_sums(&header.hash())?;