add test to demonstrate pair of "half" kernels sharing same public excess (#3314)

* cleanup how we handle key splitting for transaction offset
add test to demonstrate a pair of transaction halves sharing same kernel excess

* cleanup

* cleanup
This commit is contained in:
Antioch Peverell
2020-05-28 15:26:18 +01:00
committed by GitHub
parent b570ac9925
commit a8b8dc3a7f
4 changed files with 170 additions and 88 deletions
+35 -24
View File
@@ -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<K, B>(
features: KernelFeatures,
elems: Vec<Box<Append<K, B>>>,
keychain: &K,
builder: &B,
) -> Result<Transaction, Error>
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<K, B>(
elems: Vec<Box<Append<K, B>>>,
kernel: TxKernel,
excess: BlindingFactor,
keychain: &K,
builder: &B,
) -> Result<Transaction, Error>
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)
}
+76 -1
View File
@@ -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]