Add test for duplicate rangeproof inflation bug (#3630)

* invalid range proof test

* create output with 1 million grin

* Move `output_negative` into `tests/`

So it cannot be called from outside of tests!

* Verify InvalidBlockProof & InvalidRangeProof types

Co-authored-by: Snape 479 <Severus_Snape93@protonmail.com>
This commit is contained in:
trevyn 2021-04-06 14:06:52 +04:00 committed by GitHub
parent f6ec77a592
commit df91bff0f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -15,12 +15,18 @@
use self::chain::types::{NoopAdapter, Tip};
use self::chain::Chain;
use self::core::core::hash::Hashed;
use self::core::core::{Block, BlockHeader, KernelFeatures, Transaction};
use self::core::core::{
block, transaction, Block, BlockHeader, KernelFeatures, Output, OutputFeatures, Transaction,
};
use self::core::global::ChainTypes;
use self::core::libtx::{self, build, ProofBuilder};
use self::core::libtx::build::{self, Append};
use self::core::libtx::proof::{self, ProofBuild};
use self::core::libtx::{self, Error, ProofBuilder};
use self::core::pow::Difficulty;
use self::core::{consensus, global, pow};
use self::keychain::{ExtKeychain, ExtKeychainPath, Keychain};
use self::keychain::{
BlindSum, ExtKeychain, ExtKeychainPath, Identifier, Keychain, SwitchCommitmentType,
};
use self::util::RwLock;
use chrono::Duration;
use grin_chain as chain;
@ -814,6 +820,148 @@ fn output_header_mappings() {
clean_output_dir(".grin_header_for_output");
}
/// Build a negative output. This function must not be used outside of tests.
/// The commitment will be an inversion of the value passed in and the value is
/// subtracted from the sum.
fn build_output_negative<K, B>(value: u64, key_id: Identifier) -> Box<Append<K, B>>
where
K: Keychain,
B: ProofBuild,
{
Box::new(
move |build, acc| -> Result<(Transaction, BlindSum), Error> {
let (tx, sum) = acc?;
// TODO: proper support for different switch commitment schemes
let switch = SwitchCommitmentType::Regular;
let commit = build.keychain.commit(value, &key_id, switch)?;
// invert commitment
let commit = build.keychain.secp().commit_sum(vec![], vec![commit])?;
eprintln!("Building output: {}, {:?}", value, commit);
// build a proof with a rangeproof of 0 as a placeholder
// the test will replace this later
let proof = proof::create(
build.keychain,
build.builder,
0,
&key_id,
switch,
commit,
None,
)?;
// we return the output and the value is subtracted instead of added
Ok((
tx.with_output(Output::new(OutputFeatures::Plain, commit, proof)),
sum.sub_key_id(key_id.to_value_path(value)),
))
},
)
}
/// Test the duplicate rangeproof bug
#[test]
fn test_overflow_cached_rangeproof() {
clean_output_dir(".grin_overflow");
global::set_local_chain_type(ChainTypes::AutomatedTesting);
util::init_test_logger();
{
let chain = init_chain(".grin_overflow", pow::mine_genesis_block().unwrap());
let prev = chain.head_header().unwrap();
let kc = ExtKeychain::from_random_seed(false).unwrap();
let pb = ProofBuilder::new(&kc);
let mut head = prev;
// mine the first block and keep track of the block_hash
// so we can spend the coinbase later
let b = prepare_block(&kc, &head, &chain, 2);
assert!(b.outputs()[0].is_coinbase());
head = b.header.clone();
chain
.process_block(b.clone(), chain::Options::SKIP_POW)
.unwrap();
// now mine three further blocks
for n in 3..6 {
let b = prepare_block(&kc, &head, &chain, n);
head = b.header.clone();
chain.process_block(b, chain::Options::SKIP_POW).unwrap();
}
// create a few keys for use in txns
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
let key_id30 = ExtKeychainPath::new(1, 30, 0, 0, 0).to_identifier();
let key_id31 = ExtKeychainPath::new(1, 31, 0, 0, 0).to_identifier();
let key_id32 = ExtKeychainPath::new(1, 32, 0, 0, 0).to_identifier();
// build a regular transaction so we have a rangeproof to copy
let tx1 = build::transaction(
KernelFeatures::Plain { fee: 20000.into() },
&[
build::coinbase_input(consensus::REWARD, key_id2.clone()),
build::output(consensus::REWARD - 20000, key_id30.clone()),
],
&kc,
&pb,
)
.unwrap();
// mine block with tx1
let next = prepare_block_tx(&kc, &head, &chain, 7, &[tx1.clone()]);
let prev_main = next.header.clone();
chain
.process_block(next.clone(), chain::Options::SKIP_POW)
.unwrap();
chain.validate(false).unwrap();
// create a second tx that contains a negative output
// and a positive output for 1m grin
let mut tx2 = build::transaction(
KernelFeatures::Plain { fee: 0.into() },
&[
build::input(consensus::REWARD - 20000, key_id30.clone()),
build::output(
consensus::REWARD - 20000 + 1_000_000_000_000_000,
key_id31.clone(),
),
build_output_negative(1_000_000_000_000_000, key_id32.clone()),
],
&kc,
&pb,
)
.unwrap();
// make sure tx1 only has one output as expected
assert_eq!(tx1.body.outputs.len(), 1);
let last_rp = tx1.body.outputs[0].proof;
// overwrite all our rangeproofs with the rangeproof from last block
for i in 0..tx2.body.outputs.len() {
tx2.body.outputs[i].proof = last_rp;
}
let next = prepare_block_tx(&kc, &prev_main, &chain, 8, &[tx2.clone()]);
// process_block fails with verifier_cache disabled or with correct verifier_cache
// implementations
let res = chain.process_block(next, chain::Options::SKIP_POW);
assert_eq!(
res.unwrap_err().kind(),
chain::ErrorKind::InvalidBlockProof(block::Error::Transaction(
transaction::Error::Secp(util::secp::Error::InvalidRangeProof)
))
);
}
clean_output_dir(".grin_overflow");
}
// Use diff as both diff *and* key_idx for convenience (deterministic private key for test blocks)
fn prepare_block<K>(kc: &K, prev: &BlockHeader, chain: &Chain, diff: u64) -> Block
where