Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shatree condition #928

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions crates/chia-consensus/fuzz/fuzz_targets/parse-spends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ use chia_fuzz::{make_list, BitCursor};
use clvmr::{Allocator, NodePtr};

use chia_consensus::consensus_constants::TEST_CONSTANTS;
use chia_consensus::gen::flags::{NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT};
use chia_consensus::gen::flags::{
ENABLE_SHA256TREE_CONDITIONS, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT,
};

fuzz_target!(|data: &[u8]| {
let mut a = Allocator::new();
let input = make_list(&mut a, &mut BitCursor::new(data));
// 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] {
for flags in &[
0,
STRICT_ARGS_COUNT,
NO_UNKNOWN_CONDS,
ENABLE_SHA256TREE_CONDITIONS,
] {
let _ret = parse_spends::<MempoolVisitor>(
&a,
input,
Expand Down
110 changes: 102 additions & 8 deletions crates/chia-consensus/src/gen/conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ use super::opcodes::{
ASSERT_COIN_ANNOUNCEMENT, ASSERT_CONCURRENT_PUZZLE, ASSERT_CONCURRENT_SPEND, ASSERT_EPHEMERAL,
ASSERT_HEIGHT_ABSOLUTE, ASSERT_HEIGHT_RELATIVE, ASSERT_MY_AMOUNT, ASSERT_MY_BIRTH_HEIGHT,
ASSERT_MY_BIRTH_SECONDS, ASSERT_MY_COIN_ID, ASSERT_MY_PARENT_ID, ASSERT_MY_PUZZLEHASH,
ASSERT_PUZZLE_ANNOUNCEMENT, ASSERT_SECONDS_ABSOLUTE, ASSERT_SECONDS_RELATIVE, CREATE_COIN,
CREATE_COIN_ANNOUNCEMENT, CREATE_COIN_COST, CREATE_PUZZLE_ANNOUNCEMENT, RECEIVE_MESSAGE,
REMARK, RESERVE_FEE, SEND_MESSAGE, SOFTFORK,
ASSERT_PUZZLE_ANNOUNCEMENT, ASSERT_SECONDS_ABSOLUTE, ASSERT_SECONDS_RELATIVE,
ASSERT_SHA256_TREE, CREATE_COIN, CREATE_COIN_ANNOUNCEMENT, CREATE_COIN_COST,
CREATE_PUZZLE_ANNOUNCEMENT, RECEIVE_MESSAGE, REMARK, RESERVE_FEE, SEND_MESSAGE, SOFTFORK,
};
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::{DONT_VALIDATE_SIGNATURE, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT};
use crate::gen::flags::{
DONT_VALIDATE_SIGNATURE, ENABLE_SHA256TREE_CONDITIONS, 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;
Expand Down Expand Up @@ -235,6 +237,9 @@ pub enum Condition {
// this means the condition is unconditionally true and can be skipped
Skip,
SkipRelativeCondition,

// assert sha256tree (SExp, 32 bytes)
AssertSha256Tree(NodePtr, [u8; 32]),
}

fn check_agg_sig_unsafe_message(
Expand Down Expand Up @@ -581,6 +586,21 @@ pub fn parse_args(
// this condition is always true, we always ignore arguments
Ok(Condition::Skip)
}
ASSERT_SHA256_TREE => {
if flags & ENABLE_SHA256TREE_CONDITIONS == 0 {
return Err(ValidationErr(c, ErrorCode::InvalidConditionOpcode));
}
let sexp = first(a, c)?;
c = rest(a, c)?;
maybe_check_args_terminator(a, c, flags)?;
let id = sanitize_hash(a, first(a, c)?, 32, ErrorCode::InvalidHashValue)?;
let hash: [u8; 32] = a
.atom(id)
.as_ref()
.try_into()
.expect("we already sanitised this");
Ok(Condition::AssertSha256Tree(sexp, hash))
}
_ => Err(ValidationErr(c, ErrorCode::InvalidConditionOpcode)),
}
}
Expand Down Expand Up @@ -792,6 +812,9 @@ pub struct ParseState {
// TODO: We would probably save heap allocations by turning this into a
// blst_pairing object.
pub pkm_pairs: Vec<(PublicKey, Bytes)>,

// shatree asserts
sha256tree_asserts: Vec<(NodePtr, [u8; 32])>,
}

// returns (parent-id, puzzle-hash, amount, condition-list)
Expand Down Expand Up @@ -1257,6 +1280,15 @@ pub fn parse_conditions<V: SpendVisitor>(
Condition::SkipRelativeCondition => {
assert_not_ephemeral(&mut spend.flags, state, ret.spends.len());
}
Condition::AssertSha256Tree(sexp, hash) => {
if flags & ENABLE_SHA256TREE_CONDITIONS != 0 {
// the raison d'etre for this condition is to pay extra for guaranteed caching, to make it cheaper overall
// if clvm_utils::tree_hash(a, sexp).to_bytes() != a.atom(hash).as_ref() {
// return Err(ValidationErr(c, ErrorCode::AssertSha256TreeFailed));
// }
state.sha256tree_asserts.push((sexp, hash));
}
}
Condition::Skip => {}
}
}
Expand Down Expand Up @@ -1296,7 +1328,7 @@ fn is_ephemeral(
// condition op-code
pub fn parse_spends<V: SpendVisitor>(
a: &Allocator,
spends: NodePtr,
spends: NodePtr, // list of ((parent_id, puzzle_hash, amount, conditions)... )
max_cost: Cost,
flags: u32,
aggregate_signature: &Signature,
Expand Down Expand Up @@ -1459,6 +1491,13 @@ pub fn validate_conditions(
}
}

for (sexp, hash) in &state.sha256tree_asserts {
// TODO: add caching here
if clvm_utils::tree_hash(a, *sexp).to_bytes() != *hash {
return Err(ValidationErr(*sexp, ErrorCode::AssertSha256TreeFailed));
}
}

if !state.assert_puzzle.is_empty() {
let mut announcements = HashSet::<Bytes32>::new();

Expand Down Expand Up @@ -1968,6 +2007,7 @@ fn test_invalid_spend_list_terminator() {
#[case(AGG_SIG_PARENT_AMOUNT, "{pubkey} ({msg1}")]
#[case(ASSERT_CONCURRENT_SPEND, "{coin12}")]
#[case(ASSERT_CONCURRENT_PUZZLE, "{h2}")]
#[case(ASSERT_SHA256_TREE, "{h1} ({h2}")]
fn test_strict_args_count(
#[case] condition: ConditionOpcode,
#[case] arg: &str,
Expand All @@ -1979,17 +2019,19 @@ fn test_strict_args_count(
"((({{h1}} ({{h2}} (123 ((({} ({} ( 1337 )))))",
condition as u8, arg
),
flags | DONT_VALIDATE_SIGNATURE,
flags | DONT_VALIDATE_SIGNATURE | ENABLE_SHA256TREE_CONDITIONS,
);
if flags == 0 {
// two of the cases won't pass, even when garbage at the end is allowed.
// three of the cases won't pass, even when garbage at the end is allowed.
if condition == ASSERT_COIN_ANNOUNCEMENT {
assert_eq!(ret.unwrap_err().1, ErrorCode::AssertCoinAnnouncementFailed,);
} else if condition == ASSERT_PUZZLE_ANNOUNCEMENT {
assert_eq!(
ret.unwrap_err().1,
ErrorCode::AssertPuzzleAnnouncementFailed,
);
} else if condition == ASSERT_SHA256_TREE {
assert_eq!(ret.unwrap_err().1, ErrorCode::AssertSha256TreeFailed,);
} else {
assert!(ret.is_ok());
}
Expand Down Expand Up @@ -2352,12 +2394,13 @@ fn test_multiple_conditions(
#[case(AGG_SIG_PARENT_AMOUNT)]
#[case(ASSERT_CONCURRENT_SPEND)]
#[case(ASSERT_CONCURRENT_PUZZLE)]
#[case(ASSERT_SHA256_TREE)]
fn test_missing_arg(#[case] condition: ConditionOpcode) {
// extra args are disallowed in mempool mode
assert_eq!(
cond_test_flag(
&format!("((({{h1}} ({{h2}} (123 ((({} )))))", condition as u8),
0
ENABLE_SHA256TREE_CONDITIONS
)
.unwrap_err()
.1,
Expand Down Expand Up @@ -3951,6 +3994,57 @@ fn test_concurrent_puzzle_fail() {
}
}

#[cfg(test)]
#[rstest]
#[case(
"0x1000",
"0x21df504fc8e0a0c53f8da8728a6ce0b2c6911db03184ee59eda9a6a108b008e4",
true
)]
#[case(
"0x1000",
"0x21df504fc8e0a0c53f8da8728a6ce0b2c6911db03184ee59eda9a6a108b008e5",
false
)]
#[case(
"(0x1000 0x2000 ",
"0x52beefa879dfaab96e4e42d202da06853e0f64f07e5144fcb1e46cb2de3eb7dc",
true
)] // (0x1000 . 0x2000)
#[case(
"(0x1000 0x2000 ",
"0x52beefa879dfaab96e4e42d202da06853e0f64f07e5144fcb1e46cb2de3eb7dd",
false
)] // (0x1000 . 0x2000)
#[case(
"(0x1000 (0x2000 (0x3000 ) ",
"0x02fa79e6a347b47ddd95a785b045de89f87d9c0f0cafec012d127ae4ebc1dc9b",
true
)] // (0x1000 0x2000 0x300)
#[case(
"(0x1000 (0x2000 (0x3000 ) ",
"0x02fa79e6a347b47ddd95a785b045de89f87d9c0f0cafec012d127ae4ebc1dc9c",
false
)] // (0x1000 0x2000 0x300)
fn test_sha256tree(#[case] sexp: &str, #[case] hash: &str, #[case] expected: bool) {
let input = format!(
"(\
(({{h1}} ({{h2}} (123 (((91 ({sexp} ({hash} )))\
))"
);
assert_eq!(
cond_test_cb(
&input,
MEMPOOL_MODE | ENABLE_SHA256TREE_CONDITIONS,
None,
&Signature::default(),
None,
)
.is_ok(),
expected
);
}

#[test]
fn test_assert_concurrent_puzzle_self() {
// ASSERT_CONCURRENT_PUZZLE
Expand Down
2 changes: 2 additions & 0 deletions crates/chia-consensus/src/gen/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ pub const STRICT_ARGS_COUNT: u32 = 0x8_0000;
pub const DONT_VALIDATE_SIGNATURE: u32 = 0x1_0000;

pub const MEMPOOL_MODE: u32 = CLVM_MEMPOOL_MODE | NO_UNKNOWN_CONDS | STRICT_ARGS_COUNT;

pub const ENABLE_SHA256TREE_CONDITIONS: u32 = 0x4_0000;
5 changes: 4 additions & 1 deletion crates/chia-consensus/src/gen/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ pub const ASSERT_BEFORE_SECONDS_ABSOLUTE: ConditionOpcode = 85;
pub const ASSERT_BEFORE_HEIGHT_RELATIVE: ConditionOpcode = 86;
pub const ASSERT_BEFORE_HEIGHT_ABSOLUTE: ConditionOpcode = 87;

pub const ASSERT_SHA256_TREE: ConditionOpcode = 91;

// no-op condition
pub const REMARK: ConditionOpcode = 1;

Expand Down Expand Up @@ -155,7 +157,8 @@ pub fn parse_opcode(a: &Allocator, op: NodePtr, _flags: u32) -> Option<Condition
| AGG_SIG_PARENT_AMOUNT
| AGG_SIG_PARENT_PUZZLE
| SEND_MESSAGE
| RECEIVE_MESSAGE => Some(b0),
| RECEIVE_MESSAGE
| ASSERT_SHA256_TREE => Some(b0),
_ => None,
}
} else {
Expand Down
4 changes: 4 additions & 0 deletions crates/chia-consensus/src/gen/validation_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ pub enum ErrorCode {
InvalidMessageMode,
InvalidCoinId,
MessageNotSentOrReceived,
AssertSha256TreeFailed,
InvalidHashValue,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
Expand Down Expand Up @@ -359,6 +361,8 @@ impl From<ErrorCode> for u32 {
ErrorCode::InvalidMessageMode => 145,
ErrorCode::InvalidCoinId => 146,
ErrorCode::MessageNotSentOrReceived => 147,
ErrorCode::AssertSha256TreeFailed => 148,
ErrorCode::InvalidHashValue => 149,
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions crates/chia-tools/src/bin/run-spend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ impl DebugPrint for Condition {
}
Self::Skip => "[Skip] REMARK ...".to_string(),
Self::SkipRelativeCondition => "[SkipRelativeCondition]".to_string(),
Self::AssertSha256Tree(sexp, hash) => {
format!(
"ASSERT_SHA256_TREE {} {}",
sexp.debug_print(a),
hash.debug_print(a)
)
}
}
}
}
Expand Down
Loading