Skip to content

Commit

Permalink
add keccak256 operator and corresponding soft-fork
Browse files Browse the repository at this point in the history
  • Loading branch information
arvidn committed May 23, 2024
1 parent 2698c2c commit 251e35d
Show file tree
Hide file tree
Showing 14 changed files with 1,777 additions and 29 deletions.
41 changes: 22 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ hex-literal = "=0.4.1"
# for secp sigs
k256 = { version = "0.13.1", features = ["ecdsa"] }
p256 = { version = "0.13.2", features = ["ecdsa"] }
# for keccak256
sha3 = "0.10.8"

[dev-dependencies]
rstest = "0.17.0"
Expand Down
5 changes: 4 additions & 1 deletion fuzz/fuzz_targets/operators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use clvmr::bls_ops::{
};
use clvmr::core_ops::{op_cons, op_eq, op_first, op_if, op_listp, op_raise, op_rest};
use clvmr::cost::Cost;
use clvmr::keccak256_ops::op_keccak256;
use clvmr::more_ops::{
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_mod, op_modpow, op_multiply, op_not,
Expand All @@ -20,7 +21,7 @@ use clvmr::serde::node_from_bytes;

type Opf = fn(&mut Allocator, NodePtr, Cost) -> Response;

const FUNS: [Opf; 45] = [
const FUNS: [Opf; 46] = [
op_if as Opf,
op_cons as Opf,
op_first as Opf,
Expand Down Expand Up @@ -68,6 +69,8 @@ const FUNS: [Opf; 45] = [
// Secp operators
op_secp256k1_verify as Opf,
op_secp256r1_verify as Opf,
// keccak operator
op_keccak256 as Opf,
];

fuzz_target!(|data: &[u8]| {
Expand Down
1,565 changes: 1,565 additions & 0 deletions op-tests/test-keccak256-generated.txt

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions op-tests/test-keccak256.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
; the format for these test cases are:
; expression => expected result | expected-cost

keccak256 "foobar" => 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e | 542
keccak256 "f" "oobar" => 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e | 702
keccak256 "f" "o" "obar" => 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e | 862
keccak256 "f" "o" "o" "bar" => 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e | 1022
keccak256 "f" "o" "o" "b" "a" "r" => 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e | 1342

keccak256 "foo" => 0x41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d | 536
keccak256 "fo" "o" => 0x41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d | 696
keccak256 "f" "o" "o" => 0x41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d | 856
16 changes: 15 additions & 1 deletion src/chia_dialect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::core_ops::{op_cons, op_eq, op_first, op_if, op_listp, op_raise, op_re
use crate::cost::Cost;
use crate::dialect::{Dialect, OperatorSet};
use crate::err_utils::err;
use crate::keccak256_ops::op_keccak256;
use crate::more_ops::{
op_add, op_all, op_any, op_ash, op_coinid, op_concat, op_div, op_div_fixed, op_divmod, op_gr,
op_gr_bytes, op_logand, op_logior, op_lognot, op_logxor, op_lsh, op_mod, op_modpow,
Expand All @@ -33,6 +34,14 @@ pub const ENABLE_BLS_OPS_OUTSIDE_GUARD: u32 = 0x0020;
// division operator
pub const ENABLE_FIXED_DIV: u32 = 0x0080;

// enables the keccak256 op *outside* the softfork guard.
// This is a hard-fork and should only be enabled when it activates
pub const ENABLE_KECCAK_OPS_OUTSIDE_GUARD: u32 = 0x0100;

// enables the keccak softfork extension. This is a soft-fork and
// should be set for blocks past the activation height.
pub const ENABLE_KECCAK: u32 = 0x0200;

// The default mode when running grnerators in mempool-mode (i.e. the stricter
// mode)
pub const MEMPOOL_MODE: u32 = NO_UNKNOWN_OPS | LIMIT_HEAP;
Expand Down Expand Up @@ -72,8 +81,11 @@ impl Dialect for ChiaDialect {
) -> Response {
let flags = self.flags
| match extension {
OperatorSet::Default => 0,
OperatorSet::BLS => ENABLE_BLS_OPS_OUTSIDE_GUARD,
_ => 0,
OperatorSet::Keccak => {
ENABLE_KECCAK_OPS_OUTSIDE_GUARD | ENABLE_BLS_OPS_OUTSIDE_GUARD
}
};
let op_len = allocator.atom_len(o);
if op_len == 4 {
Expand Down Expand Up @@ -171,6 +183,7 @@ impl Dialect for ChiaDialect {
unreachable!();
}
},
62 if (flags & ENABLE_KECCAK_OPS_OUTSIDE_GUARD) != 0 => op_keccak256,
_ => {
return unknown_operator(allocator, o, argument_list, flags, max_cost);
}
Expand All @@ -193,6 +206,7 @@ impl Dialect for ChiaDialect {
fn softfork_extension(&self, ext: u32) -> OperatorSet {
match ext {
0 => OperatorSet::BLS,
1 if (self.flags & ENABLE_KECCAK) != 0 => OperatorSet::Keccak,
// new extensions go here
_ => OperatorSet::Default,
}
Expand Down
3 changes: 2 additions & 1 deletion src/dialect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use crate::reduction::Response;
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum OperatorSet {
Default,
BLS,
BLS, // BLS and coinid operators
Keccak, // keccak256 operator (as well as BLS)
}

pub trait Dialect {
Expand Down
32 changes: 32 additions & 0 deletions src/keccak256_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use crate::allocator::{Allocator, NodePtr};
use crate::cost::check_cost;
use crate::cost::Cost;
use crate::op_utils::atom;
use crate::op_utils::new_atom_and_cost;
use crate::reduction::Response;
use sha3::{Digest, Keccak256};

const KECCAK256_BASE_COST: Cost = 50;
const KECCAK256_COST_PER_ARG: Cost = 160;
const KECCAK256_COST_PER_BYTE: Cost = 2;

pub fn op_keccak256(a: &mut Allocator, mut input: NodePtr, max_cost: Cost) -> Response {
let mut cost = KECCAK256_BASE_COST;

let mut byte_count: usize = 0;
let mut hasher = Keccak256::new();
while let Some((arg, rest)) = a.next(input) {
input = rest;
cost += KECCAK256_COST_PER_ARG;
check_cost(
a,
cost + byte_count as Cost * KECCAK256_COST_PER_BYTE,
max_cost,
)?;
let blob = atom(a, arg, "keccak256")?;
byte_count += blob.as_ref().len();
hasher.update(blob);
}
cost += byte_count as Cost * KECCAK256_COST_PER_BYTE;
new_atom_and_cost(a, cost, &hasher.finalize())
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod cost;
pub mod dialect;
pub mod err_utils;
pub mod f_table;
pub mod keccak256_ops;
pub mod more_ops;
pub mod number;
pub mod op_utils;
Expand Down
72 changes: 68 additions & 4 deletions src/run_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,10 @@ struct RunProgramTest<'a> {
use crate::test_ops::parse_exp;

#[cfg(test)]
use crate::chia_dialect::{ENABLE_BLS_OPS_OUTSIDE_GUARD, ENABLE_FIXED_DIV, NO_UNKNOWN_OPS};
use crate::chia_dialect::{
ENABLE_BLS_OPS_OUTSIDE_GUARD, ENABLE_FIXED_DIV, ENABLE_KECCAK, ENABLE_KECCAK_OPS_OUTSIDE_GUARD,
NO_UNKNOWN_OPS,
};

#[cfg(test)]
const TEST_CASES: &[RunProgramTest] = &[
Expand Down Expand Up @@ -1182,7 +1185,7 @@ const TEST_CASES: &[RunProgramTest] = &[

// without the flag to enable the BLS extensions, it's an unknown extension
RunProgramTest {
prg: "(softfork (q . 161) (q . 1) (q . (q . 42)) (q . ()))",
prg: "(softfork (q . 161) (q . 2) (q . (q . 42)) (q . ()))",
args: "()",
flags: NO_UNKNOWN_OPS,
result: None,
Expand Down Expand Up @@ -1213,6 +1216,57 @@ const TEST_CASES: &[RunProgramTest] = &[
err: "",
},

// coinid is also available under softfork extension 1
RunProgramTest {
prg: "(softfork (q . 1432) (q . 1) (q a (i (= (coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q . 0) (q x)) (q . ())) (q . ()))",
args: "()",
flags: ENABLE_KECCAK,
result: Some("()"),
cost: 1513,
err: "",
},

// keccak256 is available when the softfork has activated
RunProgramTest {
prg: "(softfork (q . 1134) (q . 1) (q a (i (= (keccak256 (q . \"foobar\")) (q . 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e)) (q . 0) (q x)) (q . ())) (q . ()))",
args: "()",
flags: ENABLE_KECCAK,
result: Some("()"),
cost: 1215,
err: "",
},
// make sure keccak is actually executed, by comparing with the wrong output
RunProgramTest {
prg: "(softfork (q . 1134) (q . 1) (q a (i (= (keccak256 (q . \"foobar\")) (q . 0x58d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e)) (q . 0) (q x)) (q . ())) (q . ()))",
args: "()",
flags: ENABLE_KECCAK,
result: None,
cost: 1215,
err: "clvm raise",
},
// keccak is ignored when the softfork has not activated
RunProgramTest {
prg: "(softfork (q . 1134) (q . 1) (q a (i (= (keccak256 (q . \"foobar\")) (q . 0x58d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e)) (q . 0) (q x)) (q . ())) (q . ()))",
args: "()",
flags: 0,
result: Some("()"),
cost: 1215,
err: "",
},

// === HARD FORK ===
// new operators *outside* the softfork guard

// keccak256 is available outside the guard with the appropriate flag
RunProgramTest {
prg: "(a (i (= (keccak256 (q . \"foobar\")) (q . 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e)) (q . 0) (q x)) (q . ()))",
args: "()",
flags: ENABLE_KECCAK | ENABLE_KECCAK_OPS_OUTSIDE_GUARD,
result: Some("()"),
cost: 994,
err: "",
},

// coinid operator after hardfork, where coinid is available outside the
// softfork guard.
RunProgramTest {
Expand Down Expand Up @@ -1394,11 +1448,21 @@ use rstest::rstest;
#[case::g2_add("(i (= (g2_add (q . 0x93e12b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8) (q . 0x93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8)) (q . 0xaa4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c335771638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053)) (q . 0) (q x))",
(3981700, 0, ENABLE_BLS_OPS_OUTSIDE_GUARD),
"atom is not a G2 point")]
#[case::keccak(
"(i (= (keccak256 (q . \"foobar\")) (q . 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e)) (q . 0) (q x))",
(1134, 1, ENABLE_KECCAK_OPS_OUTSIDE_GUARD),
""
)]
#[case::keccak(
"(i (= (keccak256 (q . \"foobar\")) (q . 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873f)) (q . 0) (q x))",
(1134, 1, ENABLE_KECCAK_OPS_OUTSIDE_GUARD),
"clvm raise"
)]
fn test_softfork(
#[case] prg: &'static str,
#[case] fields: (u64, u8, u32), // cost, enabled, hard_fork_flag
#[case] err: &'static str,
#[values(0)] flags: u32,
#[values(0, ENABLE_KECCAK)] flags: u32,
#[values(false, true)] mempool: bool,
#[values(0, 1, 2)] test_ext: u8,
) {
Expand All @@ -1409,9 +1473,9 @@ fn test_softfork(
let flags = flags | if mempool { NO_UNKNOWN_OPS } else { 0 };

// softfork extensions that are enabled
#[allow(clippy::match_like_matches_macro)]
let ext_enabled = match test_ext {
0 => true,
1 => (flags & ENABLE_KECCAK) != 0,
_ => false,
};

Expand Down
5 changes: 5 additions & 0 deletions src/test_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::bls_ops::{
};
use crate::core_ops::{op_cons, op_eq, op_first, op_if, op_listp, op_raise, op_rest};
use crate::cost::Cost;
use crate::keccak256_ops::op_keccak256;
use crate::more_ops::{
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_mod, op_modpow, op_multiply, op_not,
Expand Down Expand Up @@ -103,6 +104,7 @@ fn parse_atom(a: &mut Allocator, v: &str) -> NodePtr {
"%" => a.new_atom(&[61]).unwrap(),
"secp256k1_verify" => a.new_atom(&[0x13, 0xd6, 0x1f, 0x00]).unwrap(),
"secp256r1_verify" => a.new_atom(&[0x1c, 0x3a, 0x8f, 0x00]).unwrap(),
"keccak256" => a.new_atom(&[62]).unwrap(),
_ => {
panic!("atom not supported \"{}\"", v);
}
Expand Down Expand Up @@ -229,6 +231,8 @@ use rstest::rstest;
#[case("test-secp256r1")]
#[case("test-modpow")]
#[case("test-sha256")]
#[case("test-keccak256")]
#[case("test-keccak256-generated")]
fn test_ops(#[case] filename: &str) {
use std::fs::read_to_string;

Expand Down Expand Up @@ -282,6 +286,7 @@ fn test_ops(#[case] filename: &str) {
("secp256k1_verify", op_secp256k1_verify as Opf),
("secp256r1_verify", op_secp256r1_verify as Opf),
("modpow", op_modpow as Opf),
("keccak256", op_keccak256 as Opf),
]);

println!("Test cases from: {filename}");
Expand Down
Loading

0 comments on commit 251e35d

Please sign in to comment.