// Copyright 2021 The Aigc Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Core tests pub mod common; use self::core::core::block::BlockHeader; use self::core::core::block::Error::KernelLockHeight; use self::core::core::hash::{Hashed, ZERO_HASH}; use self::core::core::{ aggregate, deaggregate, FeeFields, KernelFeatures, Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel, Weighting, }; use self::core::libtx::build::{self, initial_tx, input, output, with_excess}; use self::core::libtx::{aggsig, ProofBuilder}; use self::core::{global, ser}; use crate::common::{new_block, tx1i1o, tx1i2o, tx2i1o}; use aigc_core as core; use keychain::{BlindingFactor, ExtKeychain, Keychain}; use util::static_secp_instance; // Setup test with AutomatedTesting chain_type; fn test_setup() { global::set_local_chain_type(global::ChainTypes::AutomatedTesting); } #[test] fn simple_tx_ser() { let tx = tx2i1o(); // Default protocol version (3). let mut vec = Vec::new(); ser::serialize_default(&mut vec, &tx).expect("serialization failed"); assert_eq!(vec.len(), 945); // Explicit protocol version 3. let mut vec = Vec::new(); ser::serialize(&mut vec, ser::ProtocolVersion(3), &tx).expect("serialization failed"); assert_eq!(vec.len(), 945); // We need to convert the tx to v2 compatibility with "features and commitment" inputs // to serialize to any previous protocol version. // Normally we would do this conversion against the utxo and txpool but we fake it here for testing. let inputs: Vec<_> = tx.inputs().into(); let inputs: Vec<_> = inputs .iter() .map(|input| OutputIdentifier { features: OutputFeatures::Plain, commit: input.commitment(), }) .collect(); let tx = Transaction { body: tx.body.replace_inputs(inputs.as_slice().into()), ..tx }; // Explicit protocol version 1. let mut vec = Vec::new(); ser::serialize(&mut vec, ser::ProtocolVersion(1), &tx).expect("serialization failed"); assert_eq!(vec.len(), 955); // Explicit protocol version 2. let mut vec = Vec::new(); ser::serialize(&mut vec, ser::ProtocolVersion(2), &tx).expect("serialization failed"); assert_eq!(vec.len(), 947); // Check we can still serialize to protocol version 3 without explicitly converting the tx. let mut vec = Vec::new(); ser::serialize(&mut vec, ser::ProtocolVersion(3), &tx).expect("serialization failed"); assert_eq!(vec.len(), 945); // And default protocol version for completeness. let mut vec = Vec::new(); ser::serialize_default(&mut vec, &tx).expect("serialization failed"); assert_eq!(vec.len(), 945); } #[test] fn simple_tx_ser_deser() { test_setup(); let tx = tx2i1o(); let mut vec = Vec::new(); ser::serialize_default(&mut vec, &tx).expect("serialization failed"); let dtx: Transaction = ser::deserialize_default(&mut &vec[..]).unwrap(); assert_eq!(dtx.fee(), 2); assert_eq!(dtx.inputs().len(), 2); assert_eq!(dtx.outputs().len(), 1); assert_eq!(tx.hash(), dtx.hash()); } #[test] fn tx_double_ser_deser() { test_setup(); // checks serializing doesn't mess up the tx and produces consistent results let btx = tx2i1o(); let mut vec = Vec::new(); assert!(ser::serialize_default(&mut vec, &btx).is_ok()); let dtx: Transaction = ser::deserialize_default(&mut &vec[..]).unwrap(); let mut vec2 = Vec::new(); assert!(ser::serialize_default(&mut vec2, &btx).is_ok()); let dtx2: Transaction = ser::deserialize_default(&mut &vec2[..]).unwrap(); assert_eq!(btx.hash(), dtx.hash()); assert_eq!(dtx.hash(), dtx2.hash()); } #[test] fn test_zero_commit_fails() { 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); // blinding should fail as signing with a zero r*G shouldn't work let res = build::transaction( KernelFeatures::Plain { fee: FeeFields::zero(), }, &[input(10, key_id1.clone()), output(10, key_id1)], &keychain, &builder, ); assert!(res.is_err()); } #[test] fn build_tx_kernel() { 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); // first build a valid tx with corresponding blinding factor let tx = build::transaction( KernelFeatures::Plain { fee: 2.into() }, &[input(10, key_id1), output(5, key_id2), output(3, key_id3)], &keychain, &builder, ) .unwrap(); // check the tx is valid tx.validate(Weighting::AsTransaction).unwrap(); // check the kernel is also itself valid assert_eq!(tx.kernels().len(), 1); let kern = &tx.kernels()[0]; kern.verify().unwrap(); assert_eq!(kern.features, KernelFeatures::Plain { fee: 2.into() }); 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.into() }); // 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( &[input(10, key_id1), output(8, key_id2.clone())], kernel.clone(), excess.clone(), &keychain, &builder, ) .unwrap(); let tx2 = build::transaction_with_kernel( &[input(8, key_id2), output(6, key_id3)], kernel.clone(), excess.clone(), &keychain, &builder, ) .unwrap(); assert_eq!(tx1.validate(Weighting::AsTransaction), Ok(()),); assert_eq!(tx2.validate(Weighting::AsTransaction), 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] fn transaction_cut_through() { test_setup(); let tx1 = tx1i2o(); let tx2 = tx2i1o(); assert!(tx1.validate(Weighting::AsTransaction).is_ok()); assert!(tx2.validate(Weighting::AsTransaction).is_ok()); // now build a "cut_through" tx from tx1 and tx2 let tx3 = aggregate(&[tx1, tx2]).unwrap(); assert!(tx3.validate(Weighting::AsTransaction).is_ok()); } // Attempt to deaggregate a multi-kernel transaction in a different way #[test] fn multi_kernel_transaction_deaggregation() { test_setup(); let tx1 = tx1i1o(); let tx2 = tx1i1o(); let tx3 = tx1i1o(); let tx4 = tx1i1o(); assert!(tx1.validate(Weighting::AsTransaction).is_ok()); assert!(tx2.validate(Weighting::AsTransaction).is_ok()); assert!(tx3.validate(Weighting::AsTransaction).is_ok()); assert!(tx4.validate(Weighting::AsTransaction).is_ok()); let tx1234 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()]).unwrap(); let tx12 = aggregate(&[tx1, tx2]).unwrap(); let tx34 = aggregate(&[tx3, tx4]).unwrap(); assert!(tx1234.validate(Weighting::AsTransaction).is_ok()); assert!(tx12.validate(Weighting::AsTransaction).is_ok()); assert!(tx34.validate(Weighting::AsTransaction).is_ok()); let deaggregated_tx34 = deaggregate(tx1234.clone(), &[tx12.clone()]).unwrap(); assert!(deaggregated_tx34 .validate(Weighting::AsTransaction) .is_ok()); assert_eq!(tx34, deaggregated_tx34); let deaggregated_tx12 = deaggregate(tx1234, &[tx34]).unwrap(); assert!(deaggregated_tx12 .validate(Weighting::AsTransaction) .is_ok()); assert_eq!(tx12, deaggregated_tx12); } #[test] fn multi_kernel_transaction_deaggregation_2() { test_setup(); let tx1 = tx1i1o(); let tx2 = tx1i1o(); let tx3 = tx1i1o(); assert!(tx1.validate(Weighting::AsTransaction).is_ok()); assert!(tx2.validate(Weighting::AsTransaction).is_ok()); assert!(tx3.validate(Weighting::AsTransaction).is_ok()); let tx123 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone()]).unwrap(); let tx12 = aggregate(&[tx1, tx2]).unwrap(); assert!(tx123.validate(Weighting::AsTransaction).is_ok()); assert!(tx12.validate(Weighting::AsTransaction).is_ok()); let deaggregated_tx3 = deaggregate(tx123, &[tx12]).unwrap(); assert!(deaggregated_tx3 .validate(Weighting::AsTransaction) .is_ok()); assert_eq!(tx3, deaggregated_tx3); } #[test] fn multi_kernel_transaction_deaggregation_3() { test_setup(); let tx1 = tx1i1o(); let tx2 = tx1i1o(); let tx3 = tx1i1o(); assert!(tx1.validate(Weighting::AsTransaction).is_ok()); assert!(tx2.validate(Weighting::AsTransaction).is_ok()); assert!(tx3.validate(Weighting::AsTransaction).is_ok()); let tx123 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone()]).unwrap(); let tx13 = aggregate(&[tx1, tx3]).unwrap(); let tx2 = aggregate(&[tx2]).unwrap(); assert!(tx123.validate(Weighting::AsTransaction).is_ok()); assert!(tx2.validate(Weighting::AsTransaction).is_ok()); let deaggregated_tx13 = deaggregate(tx123, &[tx2]).unwrap(); assert!(deaggregated_tx13 .validate(Weighting::AsTransaction) .is_ok()); assert_eq!(tx13, deaggregated_tx13); } #[test] fn multi_kernel_transaction_deaggregation_4() { test_setup(); let tx1 = tx1i1o(); let tx2 = tx1i1o(); let tx3 = tx1i1o(); let tx4 = tx1i1o(); let tx5 = tx1i1o(); assert!(tx1.validate(Weighting::AsTransaction).is_ok()); assert!(tx2.validate(Weighting::AsTransaction).is_ok()); assert!(tx3.validate(Weighting::AsTransaction).is_ok()); assert!(tx4.validate(Weighting::AsTransaction).is_ok()); assert!(tx5.validate(Weighting::AsTransaction).is_ok()); let tx12345 = aggregate(&[ tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx5.clone(), ]) .unwrap(); assert!(tx12345.validate(Weighting::AsTransaction).is_ok()); let deaggregated_tx5 = deaggregate(tx12345, &[tx1, tx2, tx3, tx4]).unwrap(); assert!(deaggregated_tx5 .validate(Weighting::AsTransaction) .is_ok()); assert_eq!(tx5, deaggregated_tx5); } #[test] fn multi_kernel_transaction_deaggregation_5() { test_setup(); let tx1 = tx1i1o(); let tx2 = tx1i1o(); let tx3 = tx1i1o(); let tx4 = tx1i1o(); let tx5 = tx1i1o(); assert!(tx1.validate(Weighting::AsTransaction).is_ok()); assert!(tx2.validate(Weighting::AsTransaction).is_ok()); assert!(tx3.validate(Weighting::AsTransaction).is_ok()); assert!(tx4.validate(Weighting::AsTransaction).is_ok()); assert!(tx5.validate(Weighting::AsTransaction).is_ok()); let tx12345 = aggregate(&[ tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx5.clone(), ]) .unwrap(); let tx12 = aggregate(&[tx1, tx2]).unwrap(); let tx34 = aggregate(&[tx3, tx4]).unwrap(); assert!(tx12345.validate(Weighting::AsTransaction).is_ok()); let deaggregated_tx5 = deaggregate(tx12345, &[tx12, tx34]).unwrap(); assert!(deaggregated_tx5 .validate(Weighting::AsTransaction) .is_ok()); assert_eq!(tx5, deaggregated_tx5); } // Attempt to deaggregate a multi-kernel transaction #[test] fn basic_transaction_deaggregation() { test_setup(); let tx1 = tx1i2o(); let tx2 = tx2i1o(); assert!(tx1.validate(Weighting::AsTransaction).is_ok()); assert!(tx2.validate(Weighting::AsTransaction).is_ok()); // now build a "cut_through" tx from tx1 and tx2 let tx3 = aggregate(&[tx1.clone(), tx2.clone()]).unwrap(); assert!(tx3.validate(Weighting::AsTransaction).is_ok()); let deaggregated_tx1 = deaggregate(tx3.clone(), &[tx2.clone()]).unwrap(); assert!(deaggregated_tx1 .validate(Weighting::AsTransaction) .is_ok()); assert_eq!(tx1, deaggregated_tx1); let deaggregated_tx2 = deaggregate(tx3, &[tx1]).unwrap(); assert!(deaggregated_tx2 .validate(Weighting::AsTransaction) .is_ok()); assert_eq!(tx2, deaggregated_tx2); } #[test] fn hash_output() { 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); let tx = build::transaction( KernelFeatures::Plain { fee: 1.into() }, &[input(75, key_id1), output(42, key_id2), output(32, key_id3)], &keychain, &builder, ) .unwrap(); let h = tx.outputs()[0].identifier.hash(); assert!(h != ZERO_HASH); let h2 = tx.outputs()[1].identifier.hash(); assert!(h != h2); } #[ignore] #[test] fn blind_tx() { let btx = tx2i1o(); assert!(btx.validate(Weighting::AsTransaction).is_ok()); // Ignored for bullet proofs, because calling range_proof_info // with a bullet proof causes painful errors // checks that the range proof on our blind output is sufficiently hiding let Output { proof, .. } = btx.outputs()[0]; let secp = static_secp_instance(); let secp = secp.lock(); let info = secp.range_proof_info(proof); assert!(info.min == 0); assert!(info.max == u64::max_value()); } #[test] fn tx_hash_diff() { let btx1 = tx2i1o(); let btx2 = tx1i1o(); 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() { 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); let key_id4 = ExtKeychain::derive_key_id(1, 4, 0, 0, 0); let (tx_alice, blind_sum) = { // Alice gets 2 of her pre-existing outputs to send 5 coins to Bob, they // become inputs in the new transaction let (in1, in2) = (input(4, key_id1), input(3, key_id2)); // Alice builds her transaction, with change, which also produces the sum // of blinding factors before they're obscured. let tx = Transaction::empty().with_kernel(TxKernel::with_features(KernelFeatures::Plain { fee: 2.into(), })); let (tx, sum) = build::partial_transaction(tx, &[in1, in2, output(1, key_id3)], &keychain, &builder) .unwrap(); (tx, sum) }; // 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_final = build::transaction( KernelFeatures::Plain { fee: 2.into() }, &[ initial_tx(tx_alice), with_excess(blind_sum), output(4, key_id4), ], &keychain, &builder, ) .unwrap(); tx_final.validate(Weighting::AsTransaction).unwrap(); } #[test] fn reward_empty_block() { test_setup(); let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); let builder = ProofBuilder::new(&keychain); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let previous_header = BlockHeader::default(); let b = new_block(&[], &keychain, &builder, &previous_header, &key_id); b.validate(&BlindingFactor::zero()).unwrap(); } #[test] fn reward_with_tx_block() { test_setup(); let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); let builder = ProofBuilder::new(&keychain); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let tx1 = tx2i1o(); let previous_header = BlockHeader::default(); tx1.validate(Weighting::AsTransaction) .unwrap(); let block = new_block(&[tx1], &keychain, &builder, &previous_header, &key_id); block.validate(&BlindingFactor::zero()).unwrap(); } #[test] fn simple_block() { test_setup(); let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); let builder = ProofBuilder::new(&keychain); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let tx1 = tx2i1o(); let tx2 = tx1i1o(); let previous_header = BlockHeader::default(); let b = new_block(&[tx1, tx2], &keychain, &builder, &previous_header, &key_id); b.validate(&BlindingFactor::zero()).unwrap(); } #[test] fn test_block_with_timelocked_tx() { test_setup(); let keychain = 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); // first check we can add a timelocked tx where lock height matches current // block height and that the resulting block is valid let tx1 = build::transaction( KernelFeatures::HeightLocked { fee: 2.into(), lock_height: 1, }, &[input(5, key_id1.clone()), output(3, key_id2.clone())], &keychain, &builder, ) .unwrap(); let previous_header = BlockHeader::default(); let b = new_block( &[tx1], &keychain, &builder, &previous_header, &key_id3.clone(), ); b.validate(&BlindingFactor::zero()).unwrap(); // now try adding a timelocked tx where lock height is greater than current // block height let tx1 = build::transaction( KernelFeatures::HeightLocked { fee: 2.into(), lock_height: 2, }, &[input(5, key_id1), output(3, key_id2)], &keychain, &builder, ) .unwrap(); let previous_header = BlockHeader::default(); let b = new_block(&[tx1], &keychain, &builder, &previous_header, &key_id3); match b.validate(&BlindingFactor::zero()) { Err(KernelLockHeight(height)) => { assert_eq!(height, 2); } _ => panic!("expecting KernelLockHeight error here"), } } #[test] pub fn test_verify_1i1o_sig() { test_setup(); let tx = tx1i1o(); tx.validate(Weighting::AsTransaction).unwrap(); } #[test] pub fn test_verify_2i1o_sig() { test_setup(); let tx = tx2i1o(); tx.validate(Weighting::AsTransaction).unwrap(); }