From f3dcac801ae2f33cb8a6d211f13432d2b7aff12c Mon Sep 17 00:00:00 2001 From: arvidn Date: Thu, 3 Oct 2024 16:48:26 +0200 Subject: [PATCH] validate block signature as part of run_block_generator(). There is an opt-out for tests and maybe we'll need it in production too --- Cargo.lock | 1 + crates/chia-bls/src/bls_cache.rs | 7 + .../chia-consensus/benches/run-generator.rs | 9 +- crates/chia-consensus/fuzz/Cargo.toml | 1 + .../fuzz/fuzz_targets/parse-spends.rs | 12 +- .../fuzz/fuzz_targets/run-generator.rs | 5 + crates/chia-consensus/src/fast_forward.rs | 3 +- crates/chia-consensus/src/gen/conditions.rs | 381 ++++++++++++++++-- crates/chia-consensus/src/gen/flags.rs | 6 + .../src/gen/get_puzzle_and_solution.rs | 7 +- .../src/gen/owned_conditions.rs | 4 + .../src/gen/run_block_generator.rs | 26 +- .../chia-consensus/src/gen/test_generators.rs | 11 +- .../src/spendbundle_conditions.rs | 38 +- .../src/spendbundle_validation.rs | 57 +-- crates/chia-tools/src/bin/analyze-chain.rs | 2 + .../src/bin/test-block-generators.rs | 16 +- tests/run_gen.py | 12 +- tests/test_get_puzzle_and_solution.py | 10 +- tests/test_run_block_generator.py | 55 ++- tests/test_streamable.py | 12 +- wheel/generate_type_stubs.py | 6 +- wheel/python/chia_rs/chia_rs.pyi | 12 +- wheel/src/api.rs | 3 +- wheel/src/run_generator.rs | 33 +- 25 files changed, 600 insertions(+), 129 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1dea13b0..e963b49bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,6 +373,7 @@ dependencies = [ name = "chia-fuzz" version = "0.13.0" dependencies = [ + "chia-bls 0.14.1", "chia-consensus", "chia-protocol", "chia-sha2", diff --git a/crates/chia-bls/src/bls_cache.rs b/crates/chia-bls/src/bls_cache.rs index a77f8297f..fccadc293 100644 --- a/crates/chia-bls/src/bls_cache.rs +++ b/crates/chia-bls/src/bls_cache.rs @@ -72,6 +72,13 @@ impl BlsCache { aggregate_verify_gt(sig, iter) } + + pub fn update(&mut self, aug_msg: &[u8], gt: GTElement) { + let mut hasher = Sha256::new(); + hasher.update(aug_msg.as_ref()); + let hash: [u8; 32] = hasher.finalize(); + self.cache.put(hash, gt); + } } #[cfg(feature = "py-bindings")] diff --git a/crates/chia-consensus/benches/run-generator.rs b/crates/chia-consensus/benches/run-generator.rs index 9ee010abf..30a7c74ab 100644 --- a/crates/chia-consensus/benches/run-generator.rs +++ b/crates/chia-consensus/benches/run-generator.rs @@ -1,5 +1,6 @@ +use chia_bls::Signature; use chia_consensus::consensus_constants::TEST_CONSTANTS; -use chia_consensus::gen::flags::ALLOW_BACKREFS; +use chia_consensus::gen::flags::{ALLOW_BACKREFS, DONT_VALIDATE_SIGNATURE}; use chia_consensus::gen::run_block_generator::{run_block_generator, run_block_generator2}; use clvmr::serde::{node_from_bytes, node_to_bytes_backrefs}; use clvmr::Allocator; @@ -55,7 +56,9 @@ fn run(c: &mut Criterion) { gen, &block_refs, 11_000_000_000, - ALLOW_BACKREFS, + ALLOW_BACKREFS | DONT_VALIDATE_SIGNATURE, + &Signature::default(), + None, &TEST_CONSTANTS, ); let _ = black_box(conds); @@ -74,6 +77,8 @@ fn run(c: &mut Criterion) { &block_refs, 11_000_000_000, ALLOW_BACKREFS, + &Signature::default(), + None, &TEST_CONSTANTS, ); let _ = black_box(conds); diff --git a/crates/chia-consensus/fuzz/Cargo.toml b/crates/chia-consensus/fuzz/Cargo.toml index 2bc426fcd..ac2dc6092 100644 --- a/crates/chia-consensus/fuzz/Cargo.toml +++ b/crates/chia-consensus/fuzz/Cargo.toml @@ -20,6 +20,7 @@ chia-protocol = { workspace = true } chia-sha2 = { workspace = true } chia-traits = { workspace = true } chia-consensus = { workspace = true } +chia-bls = { workspace = true } hex-literal = { workspace = true } [[bin]] diff --git a/crates/chia-consensus/fuzz/fuzz_targets/parse-spends.rs b/crates/chia-consensus/fuzz/fuzz_targets/parse-spends.rs index 84257c734..7e9dbdcf4 100644 --- a/crates/chia-consensus/fuzz/fuzz_targets/parse-spends.rs +++ b/crates/chia-consensus/fuzz/fuzz_targets/parse-spends.rs @@ -1,6 +1,7 @@ #![no_main] use libfuzzer_sys::fuzz_target; +use chia_bls::Signature; use chia_consensus::gen::conditions::{parse_spends, MempoolVisitor}; use chia_fuzz::{make_list, BitCursor}; use clvmr::{Allocator, NodePtr}; @@ -14,7 +15,14 @@ fuzz_target!(|data: &[u8]| { // spends is a list of spends let input = a.new_pair(input, NodePtr::NIL).unwrap(); for flags in &[0, STRICT_ARGS_COUNT, NO_UNKNOWN_CONDS] { - let _ret = - parse_spends::(&a, input, 33_000_000_000, *flags, &TEST_CONSTANTS); + let _ret = parse_spends::( + &a, + input, + 33_000_000_000, + *flags, + &Signature::default(), + None, + &TEST_CONSTANTS, + ); } }); diff --git a/crates/chia-consensus/fuzz/fuzz_targets/run-generator.rs b/crates/chia-consensus/fuzz/fuzz_targets/run-generator.rs index b34c03811..ee7118e0a 100644 --- a/crates/chia-consensus/fuzz/fuzz_targets/run-generator.rs +++ b/crates/chia-consensus/fuzz/fuzz_targets/run-generator.rs @@ -1,4 +1,5 @@ #![no_main] +use chia_bls::Signature; use chia_consensus::allocator::make_allocator; use chia_consensus::consensus_constants::TEST_CONSTANTS; use chia_consensus::gen::flags::ALLOW_BACKREFS; @@ -15,6 +16,8 @@ fuzz_target!(|data: &[u8]| { [], 110_000_000, ALLOW_BACKREFS, + &Signature::default(), + None, &TEST_CONSTANTS, ); drop(a1); @@ -26,6 +29,8 @@ fuzz_target!(|data: &[u8]| { [], 110_000_000, ALLOW_BACKREFS, + &Signature::default(), + None, &TEST_CONSTANTS, ); drop(a2); diff --git a/crates/chia-consensus/src/fast_forward.rs b/crates/chia-consensus/src/fast_forward.rs index a803a5a21..f3195a4c6 100644 --- a/crates/chia-consensus/src/fast_forward.rs +++ b/crates/chia-consensus/src/fast_forward.rs @@ -155,7 +155,6 @@ pub fn fast_forward_singleton( #[cfg(test)] mod tests { use super::*; - use crate::consensus_constants::ConsensusConstants; use crate::consensus_constants::TEST_CONSTANTS; use crate::gen::conditions::MempoolVisitor; use crate::gen::conditions::{ @@ -173,7 +172,7 @@ mod tests { use clvmr::chia_dialect::ChiaDialect; use clvmr::reduction::Reduction; use clvmr::run_program::run_program; - use clvmr::serde::{node_from_bytes, node_from_bytes_backrefs, node_to_bytes}; + use clvmr::serde::{node_from_bytes, node_to_bytes}; use hex_literal::hex; use rstest::rstest; use std::fs; diff --git a/crates/chia-consensus/src/gen/conditions.rs b/crates/chia-consensus/src/gen/conditions.rs index e2a491ee3..a15b23169 100644 --- a/crates/chia-consensus/src/gen/conditions.rs +++ b/crates/chia-consensus/src/gen/conditions.rs @@ -17,11 +17,12 @@ use super::opcodes::{ use super::sanitize_int::{sanitize_uint, SanitizedUint}; use super::validation_error::{first, next, rest, ErrorCode, ValidationErr}; use crate::consensus_constants::ConsensusConstants; -use crate::gen::flags::{NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT}; +use crate::gen::flags::{DONT_VALIDATE_SIGNATURE, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT}; +use crate::gen::make_aggsig_final_message::u64_to_bytes; use crate::gen::messages::{Message, SpendId}; use crate::gen::spend_visitor::SpendVisitor; use crate::gen::validation_error::check_nil; -use chia_bls::PublicKey; +use chia_bls::{aggregate_verify, BlsCache, PublicKey, Signature}; use chia_protocol::Bytes32; use chia_sha2::Sha256; use clvmr::allocator::{Allocator, NodePtr, SExp}; @@ -719,6 +720,9 @@ pub struct SpendBundleConditions { // the sum of all amounts of CREATE_COIN conditions pub addition_amount: u128, + + // true if the block/spend bundle aggregate signature was validated + pub validated_signature: bool, } #[derive(Default)] @@ -775,6 +779,13 @@ pub struct ParseState { // ASSERT_MY_BIRTH_HEIGHT // each item is the index into the SpendBundleConditions::spends vector assert_not_ephemeral: HashSet, + + // All public keys and messages emitted by the generator. We'll validate + // these against the aggregate signature at the end, unless the + // DONT_VALIDATE_SIGNATURE flag is set + // TODO: We would probably save heap allocations by turning this into a + // blst_pairing object. + pub pkm_pairs: Vec<(PublicKey, Vec)>, } // returns (parent-id, puzzle-hash, amount, condition-list) @@ -1121,30 +1132,80 @@ pub fn parse_conditions( } Condition::AggSigMe(pk, msg) => { spend.agg_sig_me.push((to_key(a, pk)?, msg)); + if (flags & DONT_VALIDATE_SIGNATURE) == 0 { + let mut msg = a.atom(msg).as_ref().to_vec(); + msg.extend((*spend.coin_id).as_slice()); + msg.extend(constants.agg_sig_me_additional_data.as_slice()); + state.pkm_pairs.push((to_key(a, pk)?, msg)); + } } Condition::AggSigParent(pk, msg) => { spend.agg_sig_parent.push((to_key(a, pk)?, msg)); + if (flags & DONT_VALIDATE_SIGNATURE) == 0 { + let mut msg = a.atom(msg).as_ref().to_vec(); + msg.extend(a.atom(spend.parent_id).as_ref()); + msg.extend(constants.agg_sig_parent_additional_data.as_slice()); + state.pkm_pairs.push((to_key(a, pk)?, msg)); + } } Condition::AggSigPuzzle(pk, msg) => { spend.agg_sig_puzzle.push((to_key(a, pk)?, msg)); + if (flags & DONT_VALIDATE_SIGNATURE) == 0 { + let mut msg = a.atom(msg).as_ref().to_vec(); + msg.extend(a.atom(spend.puzzle_hash).as_ref()); + msg.extend(constants.agg_sig_puzzle_additional_data.as_slice()); + state.pkm_pairs.push((to_key(a, pk)?, msg)); + } } Condition::AggSigAmount(pk, msg) => { spend.agg_sig_amount.push((to_key(a, pk)?, msg)); + if (flags & DONT_VALIDATE_SIGNATURE) == 0 { + let mut msg = a.atom(msg).as_ref().to_vec(); + msg.extend(u64_to_bytes(spend.coin_amount).as_slice()); + msg.extend(constants.agg_sig_amount_additional_data.as_slice()); + state.pkm_pairs.push((to_key(a, pk)?, msg)); + } } Condition::AggSigPuzzleAmount(pk, msg) => { spend.agg_sig_puzzle_amount.push((to_key(a, pk)?, msg)); + if (flags & DONT_VALIDATE_SIGNATURE) == 0 { + let mut msg = a.atom(msg).as_ref().to_vec(); + msg.extend(a.atom(spend.puzzle_hash).as_ref()); + msg.extend(u64_to_bytes(spend.coin_amount).as_slice()); + msg.extend(constants.agg_sig_puzzle_amount_additional_data.as_slice()); + state.pkm_pairs.push((to_key(a, pk)?, msg)); + } } Condition::AggSigParentAmount(pk, msg) => { spend.agg_sig_parent_amount.push((to_key(a, pk)?, msg)); + if (flags & DONT_VALIDATE_SIGNATURE) == 0 { + let mut msg = a.atom(msg).as_ref().to_vec(); + msg.extend(a.atom(spend.parent_id).as_ref()); + msg.extend(u64_to_bytes(spend.coin_amount).as_slice()); + msg.extend(constants.agg_sig_parent_amount_additional_data.as_slice()); + state.pkm_pairs.push((to_key(a, pk)?, msg)); + } } Condition::AggSigParentPuzzle(pk, msg) => { spend.agg_sig_parent_puzzle.push((to_key(a, pk)?, msg)); + if (flags & DONT_VALIDATE_SIGNATURE) == 0 { + let mut msg = a.atom(msg).as_ref().to_vec(); + msg.extend(a.atom(spend.parent_id).as_ref()); + msg.extend(a.atom(spend.puzzle_hash).as_ref()); + msg.extend(constants.agg_sig_parent_puzzle_additional_data.as_slice()); + state.pkm_pairs.push((to_key(a, pk)?, msg)); + } } Condition::AggSigUnsafe(pk, msg) => { - // AGG_SIG_UNSAFE messages are not allowed to end with the - // suffix added to other AGG_SIG_* conditions - check_agg_sig_unsafe_message(a, msg, constants)?; ret.agg_sig_unsafe.push((to_key(a, pk)?, msg)); + if (flags & DONT_VALIDATE_SIGNATURE) == 0 { + // AGG_SIG_UNSAFE messages are not allowed to end with the + // suffix added to other AGG_SIG_* conditions + check_agg_sig_unsafe_message(a, msg, constants)?; + state + .pkm_pairs + .push((to_key(a, pk)?, a.atom(msg).as_ref().to_vec())); + } } Condition::Softfork(cost) => { if *max_cost < cost { @@ -1229,6 +1290,8 @@ pub fn parse_spends( spends: NodePtr, max_cost: Cost, flags: u32, + aggregate_signature: &Signature, + bls_cache: Option<&mut BlsCache>, constants: &ConsensusConstants, ) -> Result { let mut ret = SpendBundleConditions::default(); @@ -1261,8 +1324,10 @@ pub fn parse_spends( } validate_conditions(a, &ret, &state, spends, flags)?; - ret.cost = max_cost - cost_left; + validate_signature(&state, aggregate_signature, flags, bls_cache)?; + ret.validated_signature = (flags & DONT_VALIDATE_SIGNATURE) == 0; + ret.cost = max_cost - cost_left; Ok(ret) } @@ -1434,6 +1499,38 @@ pub fn validate_conditions( Ok(()) } +pub fn validate_signature( + state: &ParseState, + signature: &Signature, + flags: u32, + bls_cache: Option<&mut BlsCache>, +) -> Result<(), ValidationErr> { + if (flags & DONT_VALIDATE_SIGNATURE) != 0 { + return Ok(()); + } + + if let Some(bls_cache) = bls_cache { + if !bls_cache.aggregate_verify( + state.pkm_pairs.iter().map(|(pk, msg)| (pk, msg.as_slice())), + signature, + ) { + return Err(ValidationErr( + NodePtr::NIL, + ErrorCode::BadAggregateSignature, + )); + } + } else if !aggregate_verify( + signature, + state.pkm_pairs.iter().map(|(pk, msg)| (pk, msg.as_slice())), + ) { + return Err(ValidationErr( + NodePtr::NIL, + ErrorCode::BadAggregateSignature, + )); + } + Ok(()) +} + #[cfg(test)] use crate::consensus_constants::TEST_CONSTANTS; #[cfg(test)] @@ -1465,7 +1562,10 @@ const LONG_VEC: &[u8; 33] = &[ ]; #[cfg(test)] -const PUBKEY: &[u8; 48] = &hex!("97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb"); +const PUBKEY: &[u8; 48] = &hex!("aefe1789d6476f60439e1168f588ea16652dc321279f05a805fbc63933e88ae9c175d6c6ab182e54af562e1a0dce41bb"); +#[cfg(test)] +const SECRET_KEY: &[u8; 32] = + &hex!("6fc9d9a2b05fd1f0e51bc91041a03be8657081f272ec281aff731624f0d1c220"); #[cfg(test)] const MSG1: &[u8; 13] = &[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]; #[cfg(test)] @@ -1508,9 +1608,6 @@ const LONGMSG: &[u8; 1025] = &[ 4, ]; -#[cfg(test)] -use crate::gen::make_aggsig_final_message::u64_to_bytes; - #[cfg(test)] fn hash_buf(b1: &[u8], b2: &[u8]) -> Vec { let mut ctx = Sha256::new(); @@ -1668,6 +1765,8 @@ fn cond_test_cb( input: &str, flags: u32, callback: Callback, + signature: &Signature, + bls_cache: Option<&mut BlsCache>, ) -> Result<(Allocator, SpendBundleConditions), ValidationErr> { let mut a = Allocator::new(); @@ -1678,7 +1777,15 @@ fn cond_test_cb( print!("{c:02x}"); } println!(); - match parse_spends::(&a, n, 11_000_000_000, flags, &TEST_CONSTANTS) { + match parse_spends::( + &a, + n, + 11_000_000_000, + flags, + signature, + bls_cache, + &TEST_CONSTANTS, + ) { Ok(list) => { for n in &list.spends { println!("{n:?}"); @@ -1695,7 +1802,7 @@ use crate::gen::flags::MEMPOOL_MODE; #[cfg(test)] fn cond_test(input: &str) -> Result<(Allocator, SpendBundleConditions), ValidationErr> { // by default, run all tests in strict mempool mode - cond_test_cb(input, MEMPOOL_MODE, None) + cond_test_cb(input, MEMPOOL_MODE, None, &Signature::default(), None) } #[cfg(test)] @@ -1703,7 +1810,17 @@ fn cond_test_flag( input: &str, flags: u32, ) -> Result<(Allocator, SpendBundleConditions), ValidationErr> { - cond_test_cb(input, flags, None) + cond_test_cb(input, flags, None, &Signature::default(), None) +} + +#[cfg(test)] +fn cond_test_sig( + input: &str, + signature: &Signature, + bls_cache: Option<&mut BlsCache>, + flags: u32, +) -> Result<(Allocator, SpendBundleConditions), ValidationErr> { + cond_test_cb(input, flags, None, signature, bls_cache) } #[test] @@ -1853,7 +1970,7 @@ fn test_strict_args_count( "((({{h1}} ({{h2}} (123 ((({} ({} ( 1337 )))))", condition as u8, arg ), - flags, + flags | DONT_VALIDATE_SIGNATURE, ); if flags == 0 { // two of the cases won't pass, even when garbage at the end is allowed. @@ -1898,7 +2015,7 @@ fn test_message_strict_args_count( &format!( "((({{h1}} ({{h2}} (123 (((66 ({mode} ({msg} {arg} {extra1} ) ((67 ({mode} ({msg} {extra2} ) ))))" ), - flags, + flags | DONT_VALIDATE_SIGNATURE, ); if flags == 0 { ret.unwrap(); @@ -1942,14 +2059,26 @@ fn test_extra_arg( #[case] extra_cond: &str, #[case] test: impl Fn(&SpendBundleConditions, &SpendConditions), ) { + let signature = match condition { + AGG_SIG_PARENT + | AGG_SIG_PUZZLE + | AGG_SIG_AMOUNT + | AGG_SIG_PUZZLE_AMOUNT + | AGG_SIG_PARENT_PUZZLE + | AGG_SIG_PARENT_AMOUNT => sign_tx(H1, H2, 123, condition, MSG1), + _ => Signature::default(), + }; + // extra args are ignored in consensus mode // and a failure in mempool mode assert_eq!( - cond_test_flag( + cond_test_sig( &format!( "((({{h1}} ({{h2}} (123 ((({} ({} ( 1337 ) {} ))))", condition as u8, arg, extra_cond ), + &signature, + None, MEMPOOL_MODE, ) .unwrap_err() @@ -1957,11 +2086,13 @@ fn test_extra_arg( ErrorCode::InvalidCondition ); - let (a, conds) = cond_test_flag( + let (a, conds) = cond_test_sig( &format!( "((({{h1}} ({{h2}} (123 ((({} ({} ( 1337 ) {} ))))", condition as u8, arg, extra_cond ), + &signature, + None, 0, ) .unwrap(); @@ -2934,7 +3065,9 @@ fn test_create_coin_exceed_cost() { rest = a.new_pair(coin, rest).unwrap(); } rest - })) + })), + &Signature::default(), + None, ) .unwrap_err() .1, @@ -2993,8 +3126,11 @@ fn test_single_agg_sig_me( #[case] condition: ConditionOpcode, #[values(MEMPOOL_MODE, 0)] mempool: u32, ) { - let (a, conds) = cond_test_flag( + let signature = sign_tx(H1, H2, 123, condition, MSG1); + let (a, conds) = cond_test_sig( &format!("((({{h1}} ({{h2}} (123 ((({condition} ({{pubkey}} ({{msg1}} )))))"), + &signature, + None, mempool, ) .unwrap(); @@ -3033,7 +3169,7 @@ fn test_duplicate_agg_sig( // aggregated, and so must all copies of the public keys let (a, conds) = cond_test_flag(&format!("((({{h1}} ({{h2}} (123 ((({} ({{pubkey}} ({{msg1}} ) (({} ({{pubkey}} ({{msg1}} ) ))))", condition as u8, condition as u8), - mempool) + mempool | DONT_VALIDATE_SIGNATURE) .unwrap(); assert_eq!(conds.cost, AGG_SIG_COST * 2); @@ -3073,7 +3209,7 @@ fn test_agg_sig_invalid_pubkey( "((({{h1}} ({{h2}} (123 ((({} ({{h2}} ({{msg1}} )))))", condition as u8 ), - mempool + mempool | DONT_VALIDATE_SIGNATURE ) .unwrap_err() .1, @@ -3166,7 +3302,9 @@ fn test_agg_sig_exceed_cost(#[case] condition: ConditionOpcode) { rest = a.new_pair(aggsig, rest).unwrap(); } rest - })) + })), + &Signature::default(), + None, ) .unwrap_err() .1, @@ -3177,7 +3315,15 @@ fn test_agg_sig_exceed_cost(#[case] condition: ConditionOpcode) { #[test] fn test_single_agg_sig_unsafe() { // AGG_SIG_UNSAFE - let (a, conds) = cond_test("((({h1} ({h2} (123 (((49 ({pubkey} ({msg1} )))))").unwrap(); + let signature = sign_tx(H1, H2, 123, 49, MSG1); + + let (a, conds) = cond_test_sig( + "((({h1} ({h2} (123 (((49 ({pubkey} ({msg1} )))))", + &signature, + None, + 0, + ) + .unwrap(); assert_eq!(conds.cost, AGG_SIG_COST); assert_eq!(conds.spends.len(), 1); @@ -3211,7 +3357,7 @@ fn test_agg_sig_extra_arg(#[case] condition: ConditionOpcode) { "((({{h1}} ({{h2}} (123 ((({} ({{pubkey}} ({{msg1}} ( 1337 ) ))))", condition as u8 ), - 0, + DONT_VALIDATE_SIGNATURE, ) .unwrap(); @@ -3246,8 +3392,14 @@ fn test_agg_sig_extra_arg(#[case] condition: ConditionOpcode) { fn test_agg_sig_unsafe_invalid_terminator() { // AGG_SIG_UNSAFE // in non-mempool mode, even an invalid terminator is allowed - let (a, conds) = - cond_test_flag("((({h1} ({h2} (123 (((49 ({pubkey} ({msg1} 456 ))))", 0).unwrap(); + let signature = sign_tx(H1, H2, 123, 49, MSG1); + let (a, conds) = cond_test_sig( + "((({h1} ({h2} (123 (((49 ({pubkey} ({msg1} 456 ))))", + &signature, + None, + 0, + ) + .unwrap(); assert_eq!(conds.cost, AGG_SIG_COST); assert_eq!(conds.spends.len(), 1); @@ -3269,8 +3421,14 @@ fn test_agg_sig_me_invalid_terminator() { // AGG_SIG_ME // this has an invalid list terminator of the argument list. This is OK // according to the original consensus rules - let (a, conds) = - cond_test_flag("((({h1} ({h2} (123 (((50 ({pubkey} ({msg1} 456 ))))", 0).unwrap(); + let signature = sign_tx(H1, H2, 123, 50, MSG1); + let (a, conds) = cond_test_sig( + "((({h1} ({h2} (123 (((50 ({pubkey} ({msg1} 456 ))))", + &signature, + None, + 0, + ) + .unwrap(); assert_eq!(conds.cost, AGG_SIG_COST); assert_eq!(conds.spends.len(), 1); @@ -3291,9 +3449,15 @@ fn test_agg_sig_me_invalid_terminator() { fn test_duplicate_agg_sig_unsafe() { // AGG_SIG_UNSAFE // these conditions may not be deduplicated - let (a, conds) = - cond_test("((({h1} ({h2} (123 (((49 ({pubkey} ({msg1} ) ((49 ({pubkey} ({msg1} ) ))))") - .unwrap(); + let mut signature = sign_tx(H1, H2, 123, 49, MSG1); + signature.aggregate(&sign_tx(H1, H2, 123, 49, MSG1)); + let (a, conds) = cond_test_sig( + "((({h1} ({h2} (123 (((49 ({pubkey} ({msg1} ) ((49 ({pubkey} ({msg1} ) ))))", + &signature, + None, + 0, + ) + .unwrap(); assert_eq!(conds.cost, AGG_SIG_COST * 2); assert_eq!(conds.spends.len(), 1); @@ -3332,6 +3496,51 @@ fn test_agg_sig_unsafe_long_msg() { ); } +#[cfg(test)] +fn final_message( + parent: &[u8; 32], + puzzle: &[u8; 32], + amount: u64, + opcode: u16, + msg: &[u8], +) -> Vec { + use crate::allocator::make_allocator; + use crate::gen::make_aggsig_final_message::make_aggsig_final_message; + use crate::gen::owned_conditions::OwnedSpendConditions; + use chia_protocol::Coin; + use clvmr::LIMIT_HEAP; + + let coin = Coin::new(Bytes32::from(parent), Bytes32::from(puzzle), amount); + + let mut a: Allocator = make_allocator(LIMIT_HEAP); + let spend = SpendConditions::new( + a.new_atom(parent.as_slice()).expect("should pass"), + amount, + a.new_atom(puzzle.as_slice()).expect("test should pass"), + Arc::new(Bytes32::try_from(coin.coin_id()).expect("test should pass")), + ); + + let spend = OwnedSpendConditions::from(&a, spend); + + let mut final_msg = msg.to_vec(); + make_aggsig_final_message(opcode, &mut final_msg, &spend, &TEST_CONSTANTS); + final_msg +} + +#[cfg(test)] +fn sign_tx( + parent: &[u8; 32], + puzzle: &[u8; 32], + amount: u64, + opcode: u16, + msg: &[u8], +) -> Signature { + use chia_bls::{sign, SecretKey}; + + let final_msg = final_message(parent, puzzle, amount, opcode, msg); + sign(&SecretKey::from_bytes(SECRET_KEY).unwrap(), final_msg) +} + #[cfg(test)] #[rstest] // these are the suffixes used for AGG_SIG_* conditions (other than @@ -3355,8 +3564,18 @@ fn test_agg_sig_unsafe_invalid_msg( #[case] msg: &str, #[values(43, 44, 45, 46, 47, 48, 49, 50)] opcode: u16, ) { - let ret = cond_test_flag( + let signature = sign_tx( + H1, + H2, + 123, + opcode, + &hex::decode(&msg[2..]).expect("msg not hex"), + ); + + let ret = cond_test_sig( format!("((({{h1}} ({{h2}} (123 ((({opcode} ({{pubkey}} ({msg} )))))").as_str(), + &signature, + None, 0, ); if opcode == AGG_SIG_UNSAFE { @@ -3394,7 +3613,9 @@ fn test_agg_sig_unsafe_exceed_cost() { rest = a.new_pair(aggsig, rest).unwrap(); } rest - })) + })), + &Signature::default(), + None, ) .unwrap_err() .1, @@ -4321,6 +4542,8 @@ fn test_limit_announcements( } rest })), + &Signature::default(), + None, ); if expect_err.is_some() { @@ -4346,7 +4569,7 @@ fn test_eligible_for_ff_assert_parent() { ))\ ))"; - let (_a, cond) = cond_test(test).expect("cond_test"); + let (_a, cond) = cond_test_flag(test, DONT_VALIDATE_SIGNATURE).expect("cond_test"); assert!(cond.spends.len() == 1); assert!((cond.spends[0].flags & ELIGIBLE_FOR_FF) != 0); } @@ -4441,6 +4664,8 @@ fn test_eligible_for_ff_invalid_agg_sig_me( #[case] condition: ConditionOpcode, #[case] eligible: bool, ) { + let signature = sign_tx(H1, H2, 1, condition, MSG1); + // 51=CREATE_COIN let test: &str = &format!( "(\ @@ -4451,7 +4676,7 @@ fn test_eligible_for_ff_invalid_agg_sig_me( ))" ); - let (_a, cond) = cond_test_flag(test, 0).expect("cond_test"); + let (_a, cond) = cond_test_sig(test, &signature, None, 0).expect("cond_test"); assert!(cond.spends.len() == 1); let flags = cond.spends[0].flags; if eligible { @@ -4461,6 +4686,90 @@ fn test_eligible_for_ff_invalid_agg_sig_me( } } +// test aggregate signature validation. Both positive and negative cases + +#[cfg(test)] +fn add_signature(sig: &mut Signature, puzzle: &mut String, opcode: ConditionOpcode) { + if opcode == 0 { + return; + } + sig.aggregate(&sign_tx(H1, H2, 123, opcode, MSG1)); + puzzle.push_str(format!("(({opcode} ({{pubkey}} ({{msg1}} )").as_str()); +} + +#[cfg(test)] +fn populate_cache(opcode: ConditionOpcode, bls_cache: &mut BlsCache) { + use chia_bls::hash_to_g2; + let msg = final_message(H1, H2, 123, opcode, MSG1); + // Otherwise, we need to calculate the pairing and add it to the cache. + let mut aug_msg = PUBKEY.to_vec(); + aug_msg.extend_from_slice(msg.as_ref()); + let aug_hash = hash_to_g2(&aug_msg); + + let gt = aug_hash.pair(&PublicKey::from_bytes(PUBKEY).unwrap()); + bls_cache.update(&aug_msg, gt); +} + +#[cfg(test)] +#[rstest] +fn test_agg_sig( + #[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)] a: u32, + #[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)] b: u32, + #[values(true, false)] expect_pass: bool, + #[values(true, false)] with_cache: bool, +) { + use chia_bls::{sign, SecretKey}; + let mut signature = Signature::default(); + let mut bls_cache = BlsCache::default(); + let cache: Option<&mut BlsCache> = if with_cache { + populate_cache(43, &mut bls_cache); + populate_cache(44, &mut bls_cache); + populate_cache(45, &mut bls_cache); + populate_cache(46, &mut bls_cache); + populate_cache(47, &mut bls_cache); + populate_cache(48, &mut bls_cache); + populate_cache(49, &mut bls_cache); + populate_cache(50, &mut bls_cache); + Some(&mut bls_cache) + } else { + None + }; + + let combination = (a << 4) | b; + let mut puzzle: String = "((({h1} ({h2} (123 (".into(); + let opcodes: &[ConditionOpcode] = &[ + AGG_SIG_PARENT, + AGG_SIG_PUZZLE, + AGG_SIG_AMOUNT, + AGG_SIG_PUZZLE_AMOUNT, + AGG_SIG_PARENT_AMOUNT, + AGG_SIG_PARENT_PUZZLE, + AGG_SIG_UNSAFE, + AGG_SIG_ME, + ]; + for (i, opcode) in opcodes.iter().enumerate() { + if (combination & (1 << i)) == 0 { + continue; + } + add_signature(&mut signature, &mut puzzle, *opcode); + } + puzzle.push_str("))))"); + if !expect_pass { + signature.aggregate(&sign( + &SecretKey::from_bytes(SECRET_KEY).unwrap(), + b"foobar", + )); + } + match cond_test_sig(puzzle.as_str(), &signature, cache, 0) { + Ok(..) => { + assert!(expect_pass); + } + Err(..) => { + assert!(!expect_pass); + } + } +} + // the message condition takes a mode-parameter. This is a 6-bit integer that // determines which aspects of the sending spend and receiving spends must // match. The second argument is the message. The message and mode must always @@ -4697,6 +5006,8 @@ fn test_limit_messages(#[case] count: i32, #[case] expect_err: Option } rest })), + &Signature::default(), + None, ); if expect_err.is_some() { diff --git a/crates/chia-consensus/src/gen/flags.rs b/crates/chia-consensus/src/gen/flags.rs index 830e4d50d..e5991e355 100644 --- a/crates/chia-consensus/src/gen/flags.rs +++ b/crates/chia-consensus/src/gen/flags.rs @@ -16,4 +16,10 @@ pub const STRICT_ARGS_COUNT: u32 = 0x8_0000; // contain back-references pub const ALLOW_BACKREFS: u32 = 0x0200_0000; +// By default, run_block_generator validates the signatures of any AGG_SIG +// condition. By passing in this flag, the signatures are not validated (saving +// time). This is useful when we've already validated a block but we need to +// re-run it to compute additions and removals. +pub const DONT_VALIDATE_SIGNATURE: u32 = 0x1_0000; + pub const MEMPOOL_MODE: u32 = CLVM_MEMPOOL_MODE | NO_UNKNOWN_CONDS | STRICT_ARGS_COUNT; diff --git a/crates/chia-consensus/src/gen/get_puzzle_and_solution.rs b/crates/chia-consensus/src/gen/get_puzzle_and_solution.rs index 53c1e839a..b5b70f45f 100644 --- a/crates/chia-consensus/src/gen/get_puzzle_and_solution.rs +++ b/crates/chia-consensus/src/gen/get_puzzle_and_solution.rs @@ -61,9 +61,10 @@ pub fn get_puzzle_and_solution_for_coin( mod test { use super::*; use crate::consensus_constants::TEST_CONSTANTS; - use crate::gen::flags::{ALLOW_BACKREFS, MEMPOOL_MODE}; + use crate::gen::flags::{ALLOW_BACKREFS, DONT_VALIDATE_SIGNATURE, MEMPOOL_MODE}; use crate::gen::make_aggsig_final_message::u64_to_bytes; use crate::gen::run_block_generator::{run_block_generator2, setup_generator_args}; + use chia_bls::Signature; use chia_protocol::Bytes32; use chia_sha2::Sha256; use clvm_traits::FromClvm; @@ -239,7 +240,9 @@ mod test { &generator, blocks, MAX_COST, - ALLOW_BACKREFS | MEMPOOL_MODE, + ALLOW_BACKREFS | MEMPOOL_MODE | DONT_VALIDATE_SIGNATURE, + &Signature::default(), + None, &TEST_CONSTANTS, ) .expect("run_block_generator2"); diff --git a/crates/chia-consensus/src/gen/owned_conditions.rs b/crates/chia-consensus/src/gen/owned_conditions.rs index 491d71d54..3ce68a3d0 100644 --- a/crates/chia-consensus/src/gen/owned_conditions.rs +++ b/crates/chia-consensus/src/gen/owned_conditions.rs @@ -67,6 +67,9 @@ pub struct OwnedSpendBundleConditions { pub removal_amount: u128, // the sum of all amounts of CREATE_COIN conditions pub addition_amount: u128, + // set if the aggregate signature of the block/spend bundle were + // successfully validated + pub validated_signature: bool, } impl OwnedSpendConditions { @@ -140,6 +143,7 @@ impl OwnedSpendBundleConditions { cost: sb.cost, removal_amount: sb.removal_amount, addition_amount: sb.addition_amount, + validated_signature: sb.validated_signature, } } } diff --git a/crates/chia-consensus/src/gen/run_block_generator.rs b/crates/chia-consensus/src/gen/run_block_generator.rs index af767294f..55c4b47b4 100644 --- a/crates/chia-consensus/src/gen/run_block_generator.rs +++ b/crates/chia-consensus/src/gen/run_block_generator.rs @@ -1,11 +1,12 @@ use crate::consensus_constants::ConsensusConstants; use crate::gen::conditions::{ - parse_spends, process_single_spend, validate_conditions, EmptyVisitor, ParseState, - SpendBundleConditions, + parse_spends, process_single_spend, validate_conditions, validate_signature, EmptyVisitor, + ParseState, SpendBundleConditions, }; -use crate::gen::flags::ALLOW_BACKREFS; +use crate::gen::flags::{ALLOW_BACKREFS, DONT_VALIDATE_SIGNATURE}; use crate::gen::validation_error::{first, ErrorCode, ValidationErr}; use crate::generator_rom::{CLVM_DESERIALIZER, GENERATOR_ROM}; +use chia_bls::{BlsCache, Signature}; use clvm_utils::{tree_hash_cached, TreeHash}; use clvmr::allocator::{Allocator, NodePtr}; use clvmr::chia_dialect::ChiaDialect; @@ -67,12 +68,15 @@ where // the only reason we need to pass in the allocator is because the returned // SpendBundleConditions contains NodePtr fields. If that's changed, we could // create the allocator inside this functions as well. +#[allow(clippy::too_many_arguments)] pub fn run_block_generator, I: IntoIterator>( a: &mut Allocator, program: &[u8], block_refs: I, max_cost: u64, flags: u32, + signature: &Signature, + bls_cache: Option<&mut BlsCache>, constants: &ConsensusConstants, ) -> Result where @@ -112,8 +116,15 @@ where // we pass in what's left of max_cost here, to fail early in case the // cost of a condition brings us over the cost limit - let mut result = - parse_spends::(a, generator_output, cost_left, flags, constants)?; + let mut result = parse_spends::( + a, + generator_output, + cost_left, + flags, + signature, + bls_cache, + constants, + )?; result.cost += max_cost - cost_left; Ok(result) } @@ -147,12 +158,15 @@ pub fn extract_n( // you only pay cost for the generator, the puzzles and the conditions). // it also does not apply the stack depth or object allocation limits the same, // as each puzzle run in its own environment. +#[allow(clippy::too_many_arguments)] pub fn run_block_generator2, I: IntoIterator>( a: &mut Allocator, program: &[u8], block_refs: I, max_cost: u64, flags: u32, + signature: &Signature, + bls_cache: Option<&mut BlsCache>, constants: &ConsensusConstants, ) -> Result where @@ -217,6 +231,8 @@ where } validate_conditions(a, &ret, &state, a.nil(), flags)?; + validate_signature(&state, signature, flags, bls_cache)?; + ret.validated_signature = (flags & DONT_VALIDATE_SIGNATURE) == 0; ret.cost = max_cost - cost_left; Ok(ret) diff --git a/crates/chia-consensus/src/gen/test_generators.rs b/crates/chia-consensus/src/gen/test_generators.rs index ad3ee3388..1366e3ca5 100644 --- a/crates/chia-consensus/src/gen/test_generators.rs +++ b/crates/chia-consensus/src/gen/test_generators.rs @@ -2,7 +2,8 @@ use super::conditions::{NewCoin, SpendBundleConditions, SpendConditions}; use super::run_block_generator::{run_block_generator, run_block_generator2}; use crate::allocator::make_allocator; use crate::consensus_constants::TEST_CONSTANTS; -use crate::gen::flags::{ALLOW_BACKREFS, MEMPOOL_MODE}; +use crate::gen::flags::{ALLOW_BACKREFS, DONT_VALIDATE_SIGNATURE, MEMPOOL_MODE}; +use chia_bls::Signature; use chia_protocol::{Bytes, Bytes48}; use clvmr::allocator::NodePtr; use clvmr::Allocator; @@ -236,7 +237,9 @@ fn run_generator(#[case] name: &str) { &generator, &block_refs, 11_000_000_000, - *flags, + *flags | DONT_VALIDATE_SIGNATURE, + &Signature::default(), + None, &TEST_CONSTANTS, ); @@ -251,7 +254,9 @@ fn run_generator(#[case] name: &str) { &generator, &block_refs, 11_000_000_000, - *flags, + *flags | DONT_VALIDATE_SIGNATURE, + &Signature::default(), + None, &TEST_CONSTANTS, ); let output_hard_fork = match conds { diff --git a/crates/chia-consensus/src/spendbundle_conditions.rs b/crates/chia-consensus/src/spendbundle_conditions.rs index c033ce5ba..22f99e53f 100644 --- a/crates/chia-consensus/src/spendbundle_conditions.rs +++ b/crates/chia-consensus/src/spendbundle_conditions.rs @@ -2,11 +2,12 @@ use crate::consensus_constants::ConsensusConstants; use crate::gen::conditions::{ process_single_spend, validate_conditions, MempoolVisitor, ParseState, SpendBundleConditions, }; -use crate::gen::flags::MEMPOOL_MODE; +use crate::gen::flags::{DONT_VALIDATE_SIGNATURE, MEMPOOL_MODE}; use crate::gen::run_block_generator::subtract_cost; use crate::gen::solution_generator::calculate_generator_length; use crate::gen::validation_error::ValidationErr; use crate::spendbundle_validation::get_flags_for_height_and_constants; +use chia_bls::PublicKey; use chia_protocol::SpendBundle; use clvm_utils::tree_hash; use clvmr::allocator::Allocator; @@ -24,7 +25,29 @@ pub fn get_conditions_from_spendbundle( height: u32, constants: &ConsensusConstants, ) -> Result { - let flags = get_flags_for_height_and_constants(height, constants) | MEMPOOL_MODE; + Ok(run_spendbundle( + a, + spend_bundle, + max_cost, + height, + DONT_VALIDATE_SIGNATURE, + constants, + )? + .0) +} + +// returns the conditions for the spendbundle, along with the (public key, +// message) pairs emitted by the spends (for validating the aggregate signature) +#[allow(clippy::type_complexity)] +pub fn run_spendbundle( + a: &mut Allocator, + spend_bundle: &SpendBundle, + max_cost: u64, + height: u32, + flags: u32, + constants: &ConsensusConstants, +) -> Result<(SpendBundleConditions, Vec<(PublicKey, Vec)>), ValidationErr> { + let flags = get_flags_for_height_and_constants(height, constants) | flags | MEMPOOL_MODE; // below is an adapted version of the code from run_block_generators::run_block_generator2() // it assumes no block references are passed in @@ -67,9 +90,10 @@ pub fn get_conditions_from_spendbundle( } validate_conditions(a, &ret, &state, a.nil(), flags)?; + assert!(max_cost >= cost_left); ret.cost = max_cost - cost_left; - Ok(ret) + Ok((ret, state.pkm_pairs)) } #[cfg(test)] @@ -133,7 +157,9 @@ mod tests { program.as_slice(), blocks, 11_000_000_000, - MEMPOOL_MODE, + MEMPOOL_MODE | DONT_VALIDATE_SIGNATURE, + &Signature::default(), + None, &TEST_CONSTANTS, ) .expect("run_block_generator2 failed"); @@ -312,7 +338,9 @@ mod tests { &generator_buffer, &block_refs, 11_000_000_000, - DEFAULT_FLAGS, + DEFAULT_FLAGS | DONT_VALIDATE_SIGNATURE, + &Signature::default(), + None, &TEST_CONSTANTS, ); match block_conds { diff --git a/crates/chia-consensus/src/spendbundle_validation.rs b/crates/chia-consensus/src/spendbundle_validation.rs index aef026663..0a790d47c 100644 --- a/crates/chia-consensus/src/spendbundle_validation.rs +++ b/crates/chia-consensus/src/spendbundle_validation.rs @@ -1,14 +1,9 @@ use crate::allocator::make_allocator; use crate::consensus_constants::ConsensusConstants; use crate::gen::flags::ALLOW_BACKREFS; -use crate::gen::make_aggsig_final_message::make_aggsig_final_message; -use crate::gen::opcodes::{ - AGG_SIG_AMOUNT, AGG_SIG_ME, AGG_SIG_PARENT, AGG_SIG_PARENT_AMOUNT, AGG_SIG_PARENT_PUZZLE, - AGG_SIG_PUZZLE, AGG_SIG_PUZZLE_AMOUNT, -}; use crate::gen::owned_conditions::OwnedSpendBundleConditions; use crate::gen::validation_error::ErrorCode; -use crate::spendbundle_conditions::get_conditions_from_spendbundle; +use crate::spendbundle_conditions::run_spendbundle; use chia_bls::GTElement; use chia_bls::{aggregate_verify_gt, hash_to_g2}; use chia_protocol::SpendBundle; @@ -30,8 +25,8 @@ pub fn validate_clvm_and_signature( ) -> Result<(OwnedSpendBundleConditions, Vec, Duration), ErrorCode> { let start_time = Instant::now(); let mut a = make_allocator(LIMIT_HEAP); - let sbc = get_conditions_from_spendbundle(&mut a, spend_bundle, max_cost, height, constants) - .map_err(|e| e.1)?; + let (sbc, pkm_pairs) = + run_spendbundle(&mut a, spend_bundle, max_cost, height, 0, constants).map_err(|e| e.1)?; let conditions = OwnedSpendBundleConditions::from(&a, sbc); // Collect all pairs in a single vector to avoid multiple iterations @@ -39,45 +34,17 @@ pub fn validate_clvm_and_signature( let mut aug_msg = Vec::::new(); - for spend in &conditions.spends { - let condition_items_pairs = [ - (AGG_SIG_PARENT, &spend.agg_sig_parent), - (AGG_SIG_PUZZLE, &spend.agg_sig_puzzle), - (AGG_SIG_AMOUNT, &spend.agg_sig_amount), - (AGG_SIG_PUZZLE_AMOUNT, &spend.agg_sig_puzzle_amount), - (AGG_SIG_PARENT_AMOUNT, &spend.agg_sig_parent_amount), - (AGG_SIG_PARENT_PUZZLE, &spend.agg_sig_parent_puzzle), - (AGG_SIG_ME, &spend.agg_sig_me), - ]; - - for (condition, items) in condition_items_pairs { - for (pk, msg) in items { - aug_msg.clear(); - aug_msg.extend_from_slice(&pk.to_bytes()); - aug_msg.extend_from_slice(msg.as_slice()); - make_aggsig_final_message(condition, &mut aug_msg, spend, constants); - let aug_hash = hash_to_g2(&aug_msg); - let pairing = aug_hash.pair(pk); - let mut hasher = Sha256::new(); - hasher.update(&aug_msg); - let aug_msg_hash = hasher.finalize(); - pairs.push((aug_msg_hash, pairing)); - } - } - } - - // Adding unsafe items - for (pk, msg) in &conditions.agg_sig_unsafe { - let mut aug_msg = pk.to_bytes().to_vec(); - aug_msg.extend_from_slice(msg.as_ref()); + for (pk, msg) in pkm_pairs { + aug_msg.clear(); + aug_msg.extend_from_slice(&pk.to_bytes()); + aug_msg.extend(&msg); let aug_hash = hash_to_g2(&aug_msg); - let pairing = aug_hash.pair(pk); - let mut hasher = Sha256::new(); - hasher.update(&aug_msg); - let aug_msg_hash = hasher.finalize(); - pairs.push((aug_msg_hash, pairing)); - } + let pairing = aug_hash.pair(&pk); + let mut key = Sha256::new(); + key.update(&aug_msg); + pairs.push((key.finalize(), pairing)); + } // Verify aggregated signature let result = aggregate_verify_gt( &spend_bundle.aggregated_signature, diff --git a/crates/chia-tools/src/bin/analyze-chain.rs b/crates/chia-tools/src/bin/analyze-chain.rs index 0ab55631d..9d9c58638 100644 --- a/crates/chia-tools/src/bin/analyze-chain.rs +++ b/crates/chia-tools/src/bin/analyze-chain.rs @@ -81,6 +81,8 @@ fn main() { &block_refs, ti.cost, flags, + &ti.aggregated_signature, + None, &TEST_CONSTANTS, ) .expect("failed to run block generator"); diff --git a/crates/chia-tools/src/bin/test-block-generators.rs b/crates/chia-tools/src/bin/test-block-generators.rs index 0f701d12b..869faaca5 100644 --- a/crates/chia-tools/src/bin/test-block-generators.rs +++ b/crates/chia-tools/src/bin/test-block-generators.rs @@ -188,9 +188,17 @@ fn main() { } else { 0 }; - let mut conditions = - block_runner(&mut a, generator, &block_refs, ti.cost, flags, constants) - .expect("failed to run block generator"); + let mut conditions = block_runner( + &mut a, + generator, + &block_refs, + ti.cost, + flags, + &ti.aggregated_signature, + None, + constants, + ) + .expect("failed to run block generator"); if args.original_generator && height < args.hard_fork_height { // when running pre-hardfork blocks with the post-hard fork @@ -214,6 +222,8 @@ fn main() { &block_refs, ti.cost, flags, + &ti.aggregated_signature, + None, constants, ) .expect("run_block_generator()"); diff --git a/tests/run_gen.py b/tests/run_gen.py index bd1572b0b..5a28ce643 100755 --- a/tests/run_gen.py +++ b/tests/run_gen.py @@ -5,6 +5,8 @@ SpendBundleConditions, run_block_generator2, ConsensusConstants, + DONT_VALIDATE_SIGNATURE, + G2Element, ) from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint8, uint16, uint32, uint64, uint128 @@ -107,7 +109,15 @@ def run_gen( start_time = perf_counter() try: - ret = block_runner(generator, block_refs, max_cost, flags, DEFAULT_CONSTANTS) + ret = block_runner( + generator, + block_refs, + max_cost, + flags | DONT_VALIDATE_SIGNATURE, + G2Element(), + None, + DEFAULT_CONSTANTS, + ) run_time = perf_counter() - start_time return ret + (run_time,) except Exception as e: diff --git a/tests/test_get_puzzle_and_solution.py b/tests/test_get_puzzle_and_solution.py index 2e3ffbc82..0cfcea264 100644 --- a/tests/test_get_puzzle_and_solution.py +++ b/tests/test_get_puzzle_and_solution.py @@ -6,6 +6,8 @@ run_chia_program, Program, Coin, + G2Element, + DONT_VALIDATE_SIGNATURE, ) from run_gen import DEFAULT_CONSTANTS from chia_rs.sized_bytes import bytes32 @@ -43,7 +45,13 @@ def test_get_puzzle_and_solution_for_coin(input_file: str) -> None: # first, run the block generator just to list all the spends err, conds = run_block_generator2( - block, [], MAX_COST, ALLOW_BACKREFS, DEFAULT_CONSTANTS + block, + [], + MAX_COST, + ALLOW_BACKREFS | DONT_VALIDATE_SIGNATURE, + G2Element(), + None, + DEFAULT_CONSTANTS, ) assert err is None assert conds is not None diff --git a/tests/test_run_block_generator.py b/tests/test_run_block_generator.py index d6f842d51..ec13346a8 100644 --- a/tests/test_run_block_generator.py +++ b/tests/test_run_block_generator.py @@ -1,4 +1,9 @@ -from chia_rs import run_block_generator, run_block_generator2 +from chia_rs import ( + run_block_generator, + run_block_generator2, + G2Element, + DONT_VALIDATE_SIGNATURE, +) from run_gen import print_spend_bundle_conditions, DEFAULT_CONSTANTS @@ -14,13 +19,25 @@ def test_run_block_generator_cost() -> None: open("generator-tests/block-834768.txt", "r").read().split("\n")[0] ) err, conds = run_block_generator( - generator, [], original_consensus_cost, 0, DEFAULT_CONSTANTS + generator, + [], + original_consensus_cost, + DONT_VALIDATE_SIGNATURE, + G2Element(), + None, + DEFAULT_CONSTANTS, ) assert err is None assert conds is not None err2, conds2 = run_block_generator2( - generator, [], hard_fork_consensus_cost, 0, DEFAULT_CONSTANTS + generator, + [], + hard_fork_consensus_cost, + DONT_VALIDATE_SIGNATURE, + G2Element(), + None, + DEFAULT_CONSTANTS, ) assert err2 is None assert conds2 is not None @@ -35,14 +52,26 @@ def test_run_block_generator_cost() -> None: # we exceed the cost limit by 1 err, conds = run_block_generator( - generator, [], original_consensus_cost - 1, 0, DEFAULT_CONSTANTS + generator, + [], + original_consensus_cost - 1, + DONT_VALIDATE_SIGNATURE, + G2Element(), + None, + DEFAULT_CONSTANTS, ) # BLOCK_COST_EXCEEDS_MAX = 23 assert err == 23 assert conds is None err, conds = run_block_generator2( - generator, [], hard_fork_consensus_cost - 1, 0, DEFAULT_CONSTANTS + generator, + [], + hard_fork_consensus_cost - 1, + DONT_VALIDATE_SIGNATURE, + G2Element(), + None, + DEFAULT_CONSTANTS, ) # BLOCK_COST_EXCEEDS_MAX = 23 assert err == 23 @@ -50,7 +79,13 @@ def test_run_block_generator_cost() -> None: # the byte cost alone exceeds the limit by 1 err, conds = run_block_generator( - generator, [], len(generator) * 12000 - 1, 0, DEFAULT_CONSTANTS + generator, + [], + len(generator) * 12000 - 1, + DONT_VALIDATE_SIGNATURE, + G2Element(), + None, + DEFAULT_CONSTANTS, ) # BLOCK_COST_EXCEEDS_MAX = 23 assert err == 23 @@ -58,7 +93,13 @@ def test_run_block_generator_cost() -> None: # the byte cost alone exceeds the limit by 1 err, conds = run_block_generator2( - generator, [], len(generator) * 12000 - 1, 0, DEFAULT_CONSTANTS + generator, + [], + len(generator) * 12000 - 1, + DONT_VALIDATE_SIGNATURE, + G2Element(), + None, + DEFAULT_CONSTANTS, ) # BLOCK_COST_EXCEEDS_MAX = 23 assert err == 23 diff --git a/tests/test_streamable.py b/tests/test_streamable.py index d26755e8b..cc9b30c0b 100644 --- a/tests/test_streamable.py +++ b/tests/test_streamable.py @@ -85,10 +85,10 @@ def test_hash_spend() -> None: def test_hash_spend_bundle_conditions() -> None: a1 = SpendBundleConditions( - [], 1000, 1337, 42, None, None, [(pk, b"msg")], 12345678, 123, 456 + [], 1000, 1337, 42, None, None, [(pk, b"msg")], 12345678, 123, 456, False ) a2 = SpendBundleConditions( - [], 1001, 1337, 42, None, None, [(pk, b"msg")], 12345678, 123, 456 + [], 1001, 1337, 42, None, None, [(pk, b"msg")], 12345678, 123, 456, False ) b = hash(a1) c = hash(a2) @@ -391,7 +391,7 @@ def test_missing_field() -> None: def test_json_spend_bundle_conditions() -> None: a = SpendBundleConditions( - [], 1000, 1337, 42, None, None, [(pk, b"msg")], 12345678, 123, 456 + [], 1000, 1337, 42, None, None, [(pk, b"msg")], 12345678, 123, 456, False ) assert a.to_json_dict() == { @@ -405,13 +405,14 @@ def test_json_spend_bundle_conditions() -> None: "cost": 12345678, "removal_amount": 123, "addition_amount": 456, + "validated_signature": False, } def test_from_json_spend_bundle_conditions() -> None: a = SpendBundleConditions( - [], 1000, 1337, 42, None, None, [(pk, b"msg")], 12345678, 123, 456 + [], 1000, 1337, 42, None, None, [(pk, b"msg")], 12345678, 123, 456, False ) b = SpendBundleConditions.from_json_dict( { @@ -425,6 +426,7 @@ def test_from_json_spend_bundle_conditions() -> None: "cost": 12345678, "removal_amount": 123, "addition_amount": 456, + "validated_signature": False, } ) assert a == b @@ -466,7 +468,7 @@ def test_copy_spend() -> None: def test_copy_spend_bundle_conditions() -> None: a = SpendBundleConditions( - [], 1000, 1337, 42, None, None, [(pk, b"msg")], 12345678, 123, 456 + [], 1000, 1337, 42, None, None, [(pk, b"msg")], 12345678, 123, 456, False ) b = copy.copy(a) diff --git a/wheel/generate_type_stubs.py b/wheel/generate_type_stubs.py index a0b81eab0..dbc988a97 100644 --- a/wheel/generate_type_stubs.py +++ b/wheel/generate_type_stubs.py @@ -281,11 +281,11 @@ def supports_fast_forward(spend: CoinSpend) -> bool : ... def fast_forward_singleton(spend: CoinSpend, new_coin: Coin, new_parent: Coin) -> bytes: ... def run_block_generator( - program: ReadableBuffer, block_refs: List[ReadableBuffer], max_cost: int, flags: int, constants: ConsensusConstants + program: ReadableBuffer, block_refs: List[ReadableBuffer], max_cost: int, flags: int, signature: G2Element, bls_cache: Optional[BLSCache], constants: ConsensusConstants ) -> Tuple[Optional[int], Optional[SpendBundleConditions]]: ... def run_block_generator2( - program: ReadableBuffer, block_refs: List[ReadableBuffer], max_cost: int, flags: int, constants: ConsensusConstants + program: ReadableBuffer, block_refs: List[ReadableBuffer], max_cost: int, flags: int, signature: G2Element, bls_cache: Optional[BLSCache], constants: ConsensusConstants ) -> Tuple[Optional[int], Optional[SpendBundleConditions]]: ... def confirm_included_already_hashed( @@ -325,6 +325,7 @@ def get_flags_for_height_and_constants( LIMIT_HEAP: int = ... MEMPOOL_MODE: int = ... ALLOW_BACKREFS: int = ... +DONT_VALIDATE_SIGNATURE: int = ... ELIGIBLE_FOR_DEDUP: int = ... ELIGIBLE_FOR_FF: int = ... @@ -486,6 +487,7 @@ def __init__( "cost: int", "removal_amount: int", "addition_amount: int", + "validated_signature: bool", ], ) diff --git a/wheel/python/chia_rs/chia_rs.pyi b/wheel/python/chia_rs/chia_rs.pyi index 05147d0de..b30b26af8 100644 --- a/wheel/python/chia_rs/chia_rs.pyi +++ b/wheel/python/chia_rs/chia_rs.pyi @@ -23,11 +23,11 @@ def supports_fast_forward(spend: CoinSpend) -> bool : ... def fast_forward_singleton(spend: CoinSpend, new_coin: Coin, new_parent: Coin) -> bytes: ... def run_block_generator( - program: ReadableBuffer, block_refs: List[ReadableBuffer], max_cost: int, flags: int, constants: ConsensusConstants + program: ReadableBuffer, block_refs: List[ReadableBuffer], max_cost: int, flags: int, signature: G2Element, bls_cache: Optional[BLSCache], constants: ConsensusConstants ) -> Tuple[Optional[int], Optional[SpendBundleConditions]]: ... def run_block_generator2( - program: ReadableBuffer, block_refs: List[ReadableBuffer], max_cost: int, flags: int, constants: ConsensusConstants + program: ReadableBuffer, block_refs: List[ReadableBuffer], max_cost: int, flags: int, signature: G2Element, bls_cache: Optional[BLSCache], constants: ConsensusConstants ) -> Tuple[Optional[int], Optional[SpendBundleConditions]]: ... def confirm_included_already_hashed( @@ -67,6 +67,7 @@ STRICT_ARGS_COUNT: int = ... LIMIT_HEAP: int = ... MEMPOOL_MODE: int = ... ALLOW_BACKREFS: int = ... +DONT_VALIDATE_SIGNATURE: int = ... ELIGIBLE_FOR_DEDUP: int = ... ELIGIBLE_FOR_FF: int = ... @@ -341,6 +342,7 @@ class SpendBundleConditions: cost: int removal_amount: int addition_amount: int + validated_signature: bool def __init__( self, spends: Sequence[SpendConditions], @@ -352,7 +354,8 @@ class SpendBundleConditions: agg_sig_unsafe: Sequence[Tuple[G1Element, bytes]], cost: int, removal_amount: int, - addition_amount: int + addition_amount: int, + validated_signature: bool ) -> None: ... def __hash__(self) -> int: ... def __repr__(self) -> str: ... @@ -380,7 +383,8 @@ class SpendBundleConditions: agg_sig_unsafe: Union[ List[Tuple[G1Element, bytes]], _Unspec] = _Unspec(), cost: Union[ int, _Unspec] = _Unspec(), removal_amount: Union[ int, _Unspec] = _Unspec(), - addition_amount: Union[ int, _Unspec] = _Unspec()) -> SpendBundleConditions: ... + addition_amount: Union[ int, _Unspec] = _Unspec(), + validated_signature: Union[ bool, _Unspec] = _Unspec()) -> SpendBundleConditions: ... @final class BlockRecord: diff --git a/wheel/src/api.rs b/wheel/src/api.rs index f100bd97c..58b70be98 100644 --- a/wheel/src/api.rs +++ b/wheel/src/api.rs @@ -2,7 +2,7 @@ use crate::run_generator::{py_to_slice, run_block_generator, run_block_generator use chia_consensus::allocator::make_allocator; use chia_consensus::consensus_constants::ConsensusConstants; use chia_consensus::gen::flags::{ - ALLOW_BACKREFS, MEMPOOL_MODE, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT, + ALLOW_BACKREFS, DONT_VALIDATE_SIGNATURE, MEMPOOL_MODE, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT, }; use chia_consensus::gen::owned_conditions::{OwnedSpendBundleConditions, OwnedSpendConditions}; use chia_consensus::gen::run_block_generator::setup_generator_args; @@ -484,6 +484,7 @@ pub fn chia_rs(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add("STRICT_ARGS_COUNT", STRICT_ARGS_COUNT)?; m.add("MEMPOOL_MODE", MEMPOOL_MODE)?; m.add("ALLOW_BACKREFS", ALLOW_BACKREFS)?; + m.add("DONT_VALIDATE_SIGNATURE", DONT_VALIDATE_SIGNATURE)?; // Chia classes m.add_class::()?; diff --git a/wheel/src/run_generator.rs b/wheel/src/run_generator.rs index 8c628db75..eb92d0a14 100644 --- a/wheel/src/run_generator.rs +++ b/wheel/src/run_generator.rs @@ -1,3 +1,4 @@ +use chia_bls::{BlsCache, Signature}; use chia_consensus::allocator::make_allocator; use chia_consensus::consensus_constants::ConsensusConstants; use chia_consensus::gen::owned_conditions::OwnedSpendBundleConditions; @@ -17,12 +18,16 @@ pub fn py_to_slice<'a>(buf: PyBuffer) -> &'a [u8] { } #[pyfunction] +#[pyo3(signature = (program, block_refs, max_cost, flags, signature, bls_cache, constants))] +#[allow(clippy::too_many_arguments)] pub fn run_block_generator<'a>( py: Python<'a>, program: PyBuffer, block_refs: &Bound<'_, PyList>, max_cost: Cost, flags: u32, + signature: &Signature, + bls_cache: Option<&mut BlsCache>, constants: &ConsensusConstants, ) -> (Option, Option) { let mut allocator = make_allocator(flags); @@ -39,8 +44,16 @@ pub fn run_block_generator<'a>( let program = py_to_slice::<'a>(program); py.allow_threads(|| { - match native_run_block_generator(&mut allocator, program, refs, max_cost, flags, constants) - { + match native_run_block_generator( + &mut allocator, + program, + refs, + max_cost, + flags, + signature, + bls_cache, + constants, + ) { Ok(spend_bundle_conds) => ( None, Some(OwnedSpendBundleConditions::from( @@ -57,12 +70,16 @@ pub fn run_block_generator<'a>( } #[pyfunction] +#[pyo3(signature = (program, block_refs, max_cost, flags, signature, bls_cache, constants))] +#[allow(clippy::too_many_arguments)] pub fn run_block_generator2<'a>( py: Python<'a>, program: PyBuffer, block_refs: &Bound<'_, PyList>, max_cost: Cost, flags: u32, + signature: &Signature, + bls_cache: Option<&mut BlsCache>, constants: &ConsensusConstants, ) -> (Option, Option) { let mut allocator = make_allocator(flags); @@ -80,8 +97,16 @@ pub fn run_block_generator2<'a>( let program = py_to_slice::<'a>(program); py.allow_threads(|| { - match native_run_block_generator2(&mut allocator, program, refs, max_cost, flags, constants) - { + match native_run_block_generator2( + &mut allocator, + program, + refs, + max_cost, + flags, + signature, + bls_cache, + constants, + ) { Ok(spend_bundle_conds) => ( None, Some(OwnedSpendBundleConditions::from(