diff --git a/src/chia_dialect.rs b/src/chia_dialect.rs index daa16667..171e8fb3 100644 --- a/src/chia_dialect.rs +++ b/src/chia_dialect.rs @@ -4,9 +4,9 @@ use crate::cost::Cost; use crate::dialect::{Dialect, Extension}; use crate::err_utils::err; use crate::more_ops::{ - op_add, op_all, op_any, op_ash, op_concat, op_div, op_divmod, op_gr, op_gr_bytes, op_logand, - op_logior, op_lognot, op_logxor, op_lsh, op_multiply, op_not, op_point_add, op_pubkey_for_exp, - op_sha256, op_strlen, op_substr, op_subtract, op_unknown, + op_add, op_all, op_any, op_ash, op_coinid, op_concat, op_div, op_divmod, op_gr, op_gr_bytes, + op_logand, op_logior, op_lognot, op_logxor, op_lsh, op_multiply, op_not, op_point_add, + op_pubkey_for_exp, op_sha256, op_strlen, op_substr, op_subtract, op_unknown, }; use crate::reduction::Response; @@ -21,6 +21,10 @@ pub const LIMIT_HEAP: u32 = 0x0004; // When set, enforce a stack size limit for CLVM programs pub const LIMIT_STACK: u32 = 0x0008; +// When set, we allow softfork with extension 0 (which includes coinid and the +// BLS operators) +pub const ENABLE_BLS_OPS: u32 = 0x0010; + // The default mode when running grnerators in mempool-mode (i.e. the stricter // mode) pub const MEMPOOL_MODE: u32 = NO_UNKNOWN_OPS | LIMIT_HEAP | LIMIT_STACK; @@ -56,7 +60,7 @@ impl Dialect for ChiaDialect { o: NodePtr, argument_list: NodePtr, max_cost: Cost, - _extensions: Extension, + extensions: Extension, ) -> Response { let b = &allocator.atom(o); if b.len() != 1 { @@ -99,10 +103,18 @@ impl Dialect for ChiaDialect { 34 => op_all, // 35 --- // 36 = softfork - _ => { - // new extension opcodes go here - return unknown_operator(allocator, o, argument_list, self.flags, max_cost); - } + _ => match extensions { + Extension::BLS => match b[0] { + 48 => op_coinid, + // TODO: add BLS operators here + _ => { + return unknown_operator(allocator, o, argument_list, self.flags, max_cost); + } + }, + _ => { + return unknown_operator(allocator, o, argument_list, self.flags, max_cost); + } + }, }; f(allocator, argument_list, max_cost) } @@ -121,6 +133,13 @@ impl Dialect for ChiaDialect { fn softfork_extension(&self, ext: u32) -> Extension { match ext { + 0 => { + if (self.flags & ENABLE_BLS_OPS) == 0 { + Extension::None + } else { + Extension::BLS + } + } // new extensions go here _ => Extension::None, } diff --git a/src/dialect.rs b/src/dialect.rs index 13564d98..c8954f21 100644 --- a/src/dialect.rs +++ b/src/dialect.rs @@ -5,7 +5,8 @@ use crate::reduction::Response; #[repr(u32)] #[derive(Clone, Copy, Eq, PartialEq)] pub enum Extension { - None = 0, + None, + BLS, } pub trait Dialect { diff --git a/src/more_ops.rs b/src/more_ops.rs index f13eee08..14cffde7 100644 --- a/src/more_ops.rs +++ b/src/more_ops.rs @@ -85,6 +85,10 @@ const PUBKEY_BASE_COST: Cost = 1325730; // increased from 12 to closer model Raspberry PI const PUBKEY_COST_PER_BYTE: Cost = 38; +// the new coinid operator +const COINID_COST: Cost = + SHA256_BASE_COST + SHA256_COST_PER_ARG * 3 + SHA256_COST_PER_BYTE * (32 + 32 + 8); + fn limbs_for_int(v: &Number) -> usize { ((v.bits() + 7) / 8) as usize } @@ -860,3 +864,45 @@ pub fn op_point_add(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Respon let total: G1Affine = total.into(); new_atom_and_cost(a, cost, &total.to_compressed()) } + +pub fn op_coinid(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response { + let args = Node::new(a, input); + check_arg_count(&args, 3, "coinid")?; + + let parent_coin = atom(args.first()?, "coinid")?; + if parent_coin.len() != 32 { + return args.err("coinid: invalid parent coin id (must be 32 bytes)"); + } + let args = args.rest()?; + let puzzle_hash = atom(args.first()?, "coinid")?; + if puzzle_hash.len() != 32 { + return args.err("coinid: invalid puzzle hash (must be 32 bytes)"); + } + let args = args.rest()?; + let amount = atom(args.first()?, "coinid")?; + if !amount.is_empty() { + if (amount[0] & 0x80) != 0 { + return args.err("coinid: invalid amount (may not be negative"); + } + if amount == [0_u8] || (amount.len() > 1 && amount[0] == 0 && (amount[1] & 0x80) == 0) { + return args.err("coinid: invalid amount (may not have redundant leading zero)"); + } + // the only valid coin value that's 9 bytes is when a leading zero is + // required to not have the value interpreted as negative + if amount.len() > 9 || (amount.len() == 9 && amount[0] != 0) { + return args.err("coinid: invalid amount (may not exceed max coin amount)"); + } + } + + let mut hasher = Sha256::new(); + hasher.update(parent_coin); + hasher.update(puzzle_hash); + hasher.update(amount); + let ret: [u8; 32] = hasher + .finalize() + .as_slice() + .try_into() + .expect("sha256 hash is not 32 bytes"); + let ret = a.new_atom(&ret)?; + Ok(Reduction(COINID_COST, ret)) +} diff --git a/src/run_program.rs b/src/run_program.rs index 24365a8f..caa5a6f7 100644 --- a/src/run_program.rs +++ b/src/run_program.rs @@ -566,6 +566,8 @@ struct RunProgramTest { #[cfg(test)] use crate::test_ops::parse_exp; +#[cfg(test)] +use crate::chia_dialect::ENABLE_BLS_OPS; #[cfg(test)] use crate::chia_dialect::NO_UNKNOWN_OPS; @@ -922,7 +924,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 979))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: Some("()"), cost: 1000, err: "", @@ -938,7 +940,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 959) (q . 9))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: Some("()"), cost: 1000, err: "", @@ -954,7 +956,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 939) (q . 9) (q x))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: Some("()"), cost: 1000, err: "", @@ -972,7 +974,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 919) (q . 9) (q x) (q . ()))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: Some("()"), cost: 1000, err: "", @@ -981,7 +983,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 0x00000397) (q . 9) (q x) (q . ()))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: Some("()"), cost: 1000, err: "", @@ -999,7 +1001,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 919) (q . 0x00ffffffff) (q x) (q . ()))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: Some("()"), cost: 1000, err: "", @@ -1017,7 +1019,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 919) (q . -1) (q x) (q . ()))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: Some("()"), cost: 1000, err: "", @@ -1035,7 +1037,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 919) (q . 0x0100000000) (q x) (q . ()))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: Some("()"), cost: 1000, err: "", @@ -1053,7 +1055,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 919) (q 1 2 3) (q x) (q . ()))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: Some("()"), cost: 1000, err: "", @@ -1071,7 +1073,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 1000))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: None, cost: 1000, err: "cost exceeded", @@ -1080,7 +1082,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork)", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: None, cost: 0, err: "first of non-cons", @@ -1088,7 +1090,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . 0))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: None, cost: 1000, err: "cost must be > 0", @@ -1097,7 +1099,7 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q . -1))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: None, cost: 1000, err: "softfork requires positive int arg", @@ -1105,11 +1107,73 @@ const TEST_CASES: &[RunProgramTest] = &[ RunProgramTest { prg: "(softfork (q 1 2 3))", args: "()", - flags: 0, + flags: ENABLE_BLS_OPS, result: None, cost: 1000, err: "softfork requires int arg", }, + + // test mismatching cost + RunProgramTest { + prg: "(softfork (q . 160) (q . 0) (q . (q . 42)) (q . ()))", + args: "()", + flags: ENABLE_BLS_OPS, + result: Some("()"), + cost: 241, + err: "", + }, + // the program under the softfork is restricted by the specified cost + RunProgramTest { + prg: "(softfork (q . 159) (q . 0) (q . (q . 42)) (q . ()))", + args: "()", + flags: ENABLE_BLS_OPS, + result: None, + cost: 241, + err: "cost exceeded", + }, + // the cost specified on the softfork must match exactly the cost of + // executing the program + RunProgramTest { + prg: "(softfork (q . 161) (q . 0) (q . (q . 42)) (q . ()))", + args: "()", + flags: ENABLE_BLS_OPS, + result: None, + cost: 10000, + err: "softfork specified cost mismatch", + }, + + // without the flag to enable the BLS extensions, it's an unknown extension + RunProgramTest { + prg: "(softfork (q . 161) (q . 0) (q . (q . 42)) (q . ()))", + args: "()", + flags: NO_UNKNOWN_OPS, + result: None, + cost: 10000, + err: "unknown softfork extension", + }, + + // coinid extension + // make sure we can execute the coinid operator under softfork 0 + // this program raises an exception if the computed coin ID matches the + // expected + RunProgramTest { + prg: "(softfork (q . 1265) (q . 0) (q a (i (= (coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q x) (q . 0)) (q . ())) (q . ()))", + args: "()", + flags: ENABLE_BLS_OPS, + result: None, + cost: 1346, + err: "clvm raise", + }, + // also test the opposite. This program is the same as above but it raises + // if the coin ID is a mismatch + RunProgramTest { + prg: "(softfork (q . 1265) (q . 0) (q a (i (= (coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q . 0) (q x)) (q . ())) (q . ()))", + args: "()", + flags: ENABLE_BLS_OPS, + result: Some("()"), + cost: 1346, + err: "", + }, ]; #[cfg(test)] diff --git a/src/test_ops.rs b/src/test_ops.rs index 1250b74c..3fac8d3a 100644 --- a/src/test_ops.rs +++ b/src/test_ops.rs @@ -2,9 +2,9 @@ use crate::allocator::{Allocator, NodePtr, SExp}; use crate::core_ops::{op_cons, op_eq, op_first, op_if, op_listp, op_raise, op_rest}; use crate::cost::Cost; use crate::more_ops::{ - op_add, op_all, op_any, op_ash, op_concat, op_div, op_divmod, op_gr, op_gr_bytes, op_logand, - op_logior, op_lognot, op_logxor, op_lsh, op_multiply, op_not, op_point_add, op_pubkey_for_exp, - op_sha256, op_strlen, op_substr, op_subtract, + op_add, op_all, op_any, op_ash, op_coinid, op_concat, op_div, op_divmod, op_gr, op_gr_bytes, + op_logand, op_logior, op_lognot, op_logxor, op_lsh, op_multiply, op_not, op_point_add, + op_pubkey_for_exp, op_sha256, op_strlen, op_substr, op_subtract, }; use crate::number::{ptr_from_number, Number}; use crate::reduction::{EvalErr, Reduction, Response}; @@ -775,6 +775,9 @@ sha256 0x616263 => 0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20 sha256 0x61 0x62 0x63 => 0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad | 815 sha256 => 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 | 407 sha256 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" => 0x248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1 | 653 +sha256 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 => 0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b | 803 +sha256 0x1234500000000000000000000000000000000000000000000000000000000000 0x6789abcdef000000000000000000000000000000000000000000000000000000 123456789 => 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc | 945 +sha256 0x1234500000000000000000000000000000000000000000000000000000000000 0x6789abcdef000000000000000000000000000000000000000000000000000000 0x00ffffffffffffffff => 0x609d2d5e3081fbc1106950950f3ea3dbb4eaec96a57a544ba83b8a762b457168 | 955 c => FAIL c 1 => FAIL @@ -853,6 +856,27 @@ pubkey_for_exp 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00f0000 pubkey_for_exp 0x8c1258acd66282b7ccc627f7f65e27faac425bfd0001a40100000000fffffffe => 0xb7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb | 1327426 pubkey_for_exp 0x8c1258acd66282b7ccc627f7f65e27faac425bfd0001a40100000000ffffffff => 0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 | 1327426 pubkey_for_exp 0x8c1258acd66282b7ccc627f7f65e27faac425bfd0001a4010000000000000000 => 0x847f5fcce0b9aa0f2bb3de6847337c9ed1bc2184a125c232721e1c81b0f0fee78506790a78c98abff2dd4b01a0756352 | 1327426 + +; BLS extensions + +; coinid +coinid 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 0 => 0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b | 633 +coinid 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 123456789 => 0x2f6c01d9205e70f55b940367dcdc1b518f077ee7308788ec4467447708a76e79 | 633 +coinid 0x1234500000000000000000000000000000000000000000000000000000000000 0x6789abcdef000000000000000000000000000000000000000000000000000000 123456789 => 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc | 633 +coinid 0x1234500000000000000000000000000000000000000000000000000000000000 0x6789abcdef000000000000000000000000000000000000000000000000000000 0x00ffffffffffffffff => 0x609d2d5e3081fbc1106950950f3ea3dbb4eaec96a57a544ba83b8a762b457168 | 633 + +; parent coin ID has the wrong size +coinid 0x123450000000000000000000000000000000000000000000000000000000000000 0x6789abcdef000000000000000000000000000000000000000000000000000000 123456789 => FAIL +coinid 0x12345000000000000000000000000000000000000000000000000000000000 0x6789abcdef000000000000000000000000000000000000000000000000000000 123456789 => FAIL + +; puzzle hash has the wrong size +coinid 0x1234500000000000000000000000000000000000000000000000000000000000 0x6789abcdef00000000000000000000000000000000000000000000000000000000 123456789 => FAIL +coinid 0x1234500000000000000000000000000000000000000000000000000000000000 0x6789abcdef0000000000000000000000000000000000000000000000000000 123456789 => FAIL + +; amount is invalid +coinid 0x1234500000000000000000000000000000000000000000000000000000000000 0x6789abcdef000000000000000000000000000000000000000000000000000000 0x010000000000000000 => FAIL +coinid 0x1234500000000000000000000000000000000000000000000000000000000000 0x6789abcdef000000000000000000000000000000000000000000000000000000 -1 => FAIL +coinid 0x1234500000000000000000000000000000000000000000000000000000000000 0x6789abcdef000000000000000000000000000000000000000000000000000000 0x00001234 => FAIL "#; fn parse_atom(a: &mut Allocator, v: &str) -> NodePtr { @@ -919,6 +943,8 @@ fn parse_atom(a: &mut Allocator, v: &str) -> NodePtr { "all" => a.new_atom(&[34]).unwrap(), "softfork" => a.new_atom(&[36]).unwrap(), + + "coinid" => a.new_atom(&[48]).unwrap(), _ => { panic!("atom not supported \"{}\"", v); } @@ -1066,6 +1092,8 @@ fn test_ops() { ("not", op_not as Opf), ("any", op_any as Opf), ("all", op_all as Opf), + // part of the BLS extension + ("coinid", op_coinid as Opf), ]); for t in TEST_CASES.split("\n") {