mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
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:
parent
f6ec77a592
commit
df91bff0f1
1 changed files with 151 additions and 3 deletions
|
@ -15,12 +15,18 @@
|
||||||
use self::chain::types::{NoopAdapter, Tip};
|
use self::chain::types::{NoopAdapter, Tip};
|
||||||
use self::chain::Chain;
|
use self::chain::Chain;
|
||||||
use self::core::core::hash::Hashed;
|
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::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::pow::Difficulty;
|
||||||
use self::core::{consensus, global, pow};
|
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 self::util::RwLock;
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use grin_chain as chain;
|
use grin_chain as chain;
|
||||||
|
@ -814,6 +820,148 @@ fn output_header_mappings() {
|
||||||
clean_output_dir(".grin_header_for_output");
|
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)
|
// 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
|
fn prepare_block<K>(kc: &K, prev: &BlockHeader, chain: &Chain, diff: u64) -> Block
|
||||||
where
|
where
|
||||||
|
|
Loading…
Reference in a new issue