diff --git a/src/gen/condition_sanitizers.rs b/src/gen/condition_sanitizers.rs index f8790b19f..354f2896a 100644 --- a/src/gen/condition_sanitizers.rs +++ b/src/gen/condition_sanitizers.rs @@ -22,11 +22,18 @@ pub fn parse_amount(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result Err(ValidationErr(n, code)), + Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => Err(ValidationErr(n, code)), Err(r) => Err(r), Ok(r) => Ok(u64_from_bytes(r)), } } +pub fn parse_create_coin_amount(a: &Allocator, n: NodePtr) -> Result { + // amounts are not allowed to exceed 2^64. i.e. 8 bytes + let buf = sanitize_uint(a, n, 8, ErrorCode::InvalidCoinAmount)?; + Ok(u64_from_bytes(buf)) +} + // a negative height is always true. In this case the // condition can be ignored and this functon returns 0 pub fn parse_height(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result { @@ -35,6 +42,7 @@ pub fn parse_height(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result Ok(0), + Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => Err(ValidationErr(n, code)), Err(r) => Err(r), Ok(r) => Ok(u64_from_bytes(r) as u32), } @@ -47,6 +55,7 @@ pub fn parse_seconds(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result Ok(0), + Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => Err(ValidationErr(n, code)), Err(r) => Err(r), Ok(r) => Ok(u64_from_bytes(r)), } @@ -140,7 +149,6 @@ fn amount_tester(buf: &[u8]) -> Result { #[test] fn test_sanitize_amount() { // negative amounts are not allowed - // regardless of flags assert_eq!( amount_tester(&[0x80]).unwrap_err().1, ErrorCode::InvalidCoinAmount @@ -156,7 +164,7 @@ fn test_sanitize_amount() { // leading zeros are somtimes necessary to make values positive assert_eq!(amount_tester(&[0, 0xff]), Ok(0xff)); - // but are stripped when they are redundant + // but are disallowed when they are redundant assert_eq!( amount_tester(&[0, 0, 0, 0xff]).unwrap_err().1, ErrorCode::InvalidCoinAmount @@ -189,6 +197,35 @@ fn test_sanitize_amount() { ); } +#[cfg(test)] +fn create_amount_tester(buf: &[u8]) -> Result { + let mut a = Allocator::new(); + let n = a.new_atom(buf).unwrap(); + + parse_create_coin_amount(&mut a, n) +} + +#[test] +fn test_sanitize_create_coin_amount() { + // negative coin amounts are not allowed + assert_eq!( + create_amount_tester(&[0xff]).unwrap_err().1, + ErrorCode::NegativeAmount + ); + // amounts aren't allowed to be too big + let large_buf = [0x7f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + assert_eq!( + create_amount_tester(&large_buf).unwrap_err().1, + ErrorCode::AmountExceedsMaximum + ); + + // this is small enough though + assert_eq!( + create_amount_tester(&[0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), + Ok(0xffffffffffffffff) + ); +} + #[cfg(test)] fn height_tester(buf: &[u8]) -> Result { let mut a = Allocator::new(); diff --git a/src/gen/conditions.rs b/src/gen/conditions.rs index c62f353c1..2300280a1 100644 --- a/src/gen/conditions.rs +++ b/src/gen/conditions.rs @@ -1,6 +1,7 @@ use super::coin_id::compute_coin_id; use super::condition_sanitizers::{ - parse_amount, parse_height, parse_seconds, sanitize_announce_msg, sanitize_hash, + parse_amount, parse_create_coin_amount, parse_height, parse_seconds, sanitize_announce_msg, + sanitize_hash, }; use super::opcodes::{ parse_opcode, ConditionOpcode, AGG_SIG_COST, AGG_SIG_ME, AGG_SIG_UNSAFE, ALWAYS_TRUE, @@ -132,7 +133,7 @@ pub fn parse_args( CREATE_COIN => { let puzzle_hash = sanitize_hash(a, first(a, c)?, 32, ErrorCode::InvalidPuzzleHash)?; c = rest(a, c)?; - let amount = parse_amount(a, first(a, c)?, ErrorCode::InvalidCoinAmount)?; + let amount = parse_create_coin_amount(a, first(a, c)?)?; // CREATE_COIN takes an optional 3rd parameter, which is a list of // byte buffers (typically a 32 byte hash). We only pull out the // first element for now, but may support more in the future. @@ -235,29 +236,27 @@ pub fn parse_args( if (flags & STRICT_ARGS_COUNT) != 0 { check_nil(a, rest(a, c)?)?; } - Ok(Condition::AssertSecondsRelative(parse_seconds( - a, - first(a, c)?, - ErrorCode::AssertSecondsRelative, - )?)) + let seconds = parse_seconds(a, first(a, c)?, ErrorCode::AssertSecondsRelative)?; + Ok(Condition::AssertSecondsRelative(seconds)) } ASSERT_SECONDS_ABSOLUTE => { if (flags & STRICT_ARGS_COUNT) != 0 { check_nil(a, rest(a, c)?)?; } - Ok(Condition::AssertSecondsAbsolute(parse_seconds( - a, - first(a, c)?, - ErrorCode::AssertSecondsAbsolute, - )?)) + let seconds = parse_seconds(a, first(a, c)?, ErrorCode::AssertSecondsAbsolute)?; + Ok(Condition::AssertSecondsAbsolute(seconds)) } ASSERT_HEIGHT_RELATIVE => { if (flags & STRICT_ARGS_COUNT) != 0 { check_nil(a, rest(a, c)?)?; } - match sanitize_uint(a, first(a, c)?, 4, ErrorCode::AssertHeightRelative) { + let code = ErrorCode::AssertHeightRelative; + match sanitize_uint(a, first(a, c)?, 4, code) { // Height is always positive, so a negative requirement is always true, Err(ValidationErr(_, ErrorCode::NegativeAmount)) => Ok(Condition::Skip), + Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => { + Err(ValidationErr(n, code)) + } Err(r) => Err(r), Ok(r) => Ok(Condition::AssertHeightRelative(u64_from_bytes(r) as u32)), } @@ -266,11 +265,8 @@ pub fn parse_args( if (flags & STRICT_ARGS_COUNT) != 0 { check_nil(a, rest(a, c)?)?; } - Ok(Condition::AssertHeightAbsolute(parse_height( - a, - first(a, c)?, - ErrorCode::AssertHeightAbsolute, - )?)) + let height = parse_height(a, first(a, c)?, ErrorCode::AssertHeightAbsolute)?; + Ok(Condition::AssertHeightAbsolute(height)) } ALWAYS_TRUE => { // this condition is always true, we always ignore arguments @@ -397,10 +393,10 @@ fn parse_spend_conditions( spend = rest(a, spend)?; let puzzle_hash = sanitize_hash(a, first(a, spend)?, 32, ErrorCode::InvalidPuzzleHash)?; spend = rest(a, spend)?; - let amount_buf = sanitize_uint(a, first(a, spend)?, 8, ErrorCode::InvalidCoinAmount)?.to_vec(); - let my_amount = u64_from_bytes(&amount_buf); + let my_amount = parse_amount(a, first(a, spend)?, ErrorCode::InvalidCoinAmount)?; let cond = rest(a, spend)?; - let coin_id = Arc::new(compute_coin_id(a, parent_id, puzzle_hash, &amount_buf)); + let amount_buf = a.atom(first(a, spend)?); + let coin_id = Arc::new(compute_coin_id(a, parent_id, puzzle_hash, amount_buf)); if !state.spent_coins.insert(coin_id.clone()) { // if this coin ID has already been added to this set, it's a double @@ -2050,7 +2046,7 @@ fn test_create_coin_amount_exceeds_max() { cond_test("((({h1} ({h2} (123 (((51 ({h2} (0x010000000000000000 )))))") .unwrap_err() .1, - ErrorCode::InvalidCoinAmount + ErrorCode::AmountExceedsMaximum ); } @@ -2061,7 +2057,7 @@ fn test_create_coin_negative_amount() { cond_test("((({h1} ({h2} (123 (((51 ({h2} (-1 )))))") .unwrap_err() .1, - ErrorCode::InvalidCoinAmount + ErrorCode::NegativeAmount ); } @@ -2679,7 +2675,7 @@ fn test_single_spend_negative_amount() { // the coin we're trying to spend has a negative amount (i.e. it's invalid) assert_eq!( cond_test("((({h1} ({h2} (-123 ())))").unwrap_err().1, - ErrorCode::NegativeAmount + ErrorCode::InvalidCoinAmount ); } diff --git a/src/gen/sanitize_int.rs b/src/gen/sanitize_int.rs index 4e695ce3c..cf55fb474 100644 --- a/src/gen/sanitize_int.rs +++ b/src/gen/sanitize_int.rs @@ -32,7 +32,7 @@ pub fn sanitize_uint( // if there are too many bytes left in the value, it's too big if buf.len() > size_limit { - return Err(ValidationErr(n, code)); + return Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)); } Ok(buf) @@ -87,4 +87,10 @@ fn test_sanitize_uint() { sanitize_uint(&a, a1, 8, e).unwrap_err().1, ErrorCode::InvalidCoinAmount ); + + let exceed_maximum = a.new_substr(atom, 100, 110).unwrap(); + assert_eq!( + sanitize_uint(&a, exceed_maximum, 8, e).unwrap_err().1, + ErrorCode::AmountExceedsMaximum + ); } diff --git a/src/gen/validation_error.rs b/src/gen/validation_error.rs index bbd55c82a..ffc7be8bc 100644 --- a/src/gen/validation_error.rs +++ b/src/gen/validation_error.rs @@ -3,6 +3,7 @@ use clvmr::allocator::{Allocator, NodePtr, SExp}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ErrorCode { NegativeAmount, + AmountExceedsMaximum, InvalidConditionOpcode, InvalidParentId, InvalidPuzzleHash, @@ -44,6 +45,7 @@ impl From for u32 { fn from(err: ErrorCode) -> u32 { match err { ErrorCode::NegativeAmount => 124, + ErrorCode::AmountExceedsMaximum => 16, ErrorCode::InvalidPuzzleHash => 10, ErrorCode::InvalidPubkey => 10, ErrorCode::InvalidMessage => 10,