From a022481a3e2baa5a7feeccea92a4b578f1b0682b Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Fri, 24 Nov 2023 16:10:48 +0300 Subject: [PATCH] MIR: add BLAKE2B, KECCAK, SHA256, SHA3, SHA512 They're basically the same, so easier to add them in bulk. --- contrib/mir/src/ast.rs | 5 ++ contrib/mir/src/gas.rs | 30 ++++++++++++ contrib/mir/src/interpreter.rs | 74 +++++++++++++++++++++++++++++ contrib/mir/src/lexer.rs | 2 +- contrib/mir/src/typechecker.rs | 86 ++++++++++++++++++++++++++++++++++ 5 files changed, 196 insertions(+), 1 deletion(-) diff --git a/contrib/mir/src/ast.rs b/contrib/mir/src/ast.rs index 39d7b1e59414..4aea6d220b12 100644 --- a/contrib/mir/src/ast.rs +++ b/contrib/mir/src/ast.rs @@ -434,6 +434,11 @@ pub enum Instruction<'a> { SplitTicket, JoinTickets, LoopLeft(Vec), + Blake2b, + Keccak, + Sha256, + Sha3, + Sha512, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/contrib/mir/src/gas.rs b/contrib/mir/src/gas.rs index dbfe6c4b4cdd..0549e6ddfca2 100644 --- a/contrib/mir/src/gas.rs +++ b/contrib/mir/src/gas.rs @@ -615,6 +615,36 @@ pub mod interpret_cost { // In practice, they both have the same cost. ((Checked::from(length) >> 1) + 25).as_gas_cost() } + + pub fn blake2b(msg: &[u8]) -> Result { + /* fun size -> (430. + (1.125 * size)) */ + let size = Checked::from(msg.len()); + (Checked::from(430) + ((size >> 3) + size)).as_gas_cost() + } + + pub fn keccak(msg: &[u8]) -> Result { + /* fun size -> (1350. + (8.25 * size)) */ + let size = Checked::from(msg.len()); + (Checked::from(1350) + ((size >> 2) + (size * 8))).as_gas_cost() + } + + pub fn sha256(msg: &[u8]) -> Result { + /* fun size -> (600. + (4.75 * size)) */ + let size = Checked::from(msg.len()); + (Checked::from(600) + ((size >> 2) + ((size >> 1) + (size * 4)))).as_gas_cost() + } + + pub fn sha3(msg: &[u8]) -> Result { + /* fun size -> (1350. + (8.25 * size)) */ + let size = Checked::from(msg.len()); + (Checked::from(1350) + ((size >> 2) + (size * 8))).as_gas_cost() + } + + pub fn sha512(msg: &[u8]) -> Result { + /* fun size -> (680. + (3. * size)) */ + let size = Checked::from(msg.len()); + (Checked::from(680) + (size * 3)).as_gas_cost() + } } #[cfg(test)] diff --git a/contrib/mir/src/interpreter.rs b/contrib/mir/src/interpreter.rs index 284f8b4feb23..d8189567f9aa 100644 --- a/contrib/mir/src/interpreter.rs +++ b/contrib/mir/src/interpreter.rs @@ -8,6 +8,7 @@ #![warn(clippy::redundant_clone)] use checked::Checked; +use cryptoxide::hashing::{blake2b_256, keccak256, sha256, sha3_256, sha512}; use num_bigint::{BigInt, BigUint}; use num_traits::{Signed, Zero}; use std::rc::Rc; @@ -877,6 +878,31 @@ fn interpret_one<'a>( stack.push(V::new_option(None)); } } + I::Blake2b => { + let msg = irrefutable_match!(&mut stack[0]; V::Bytes); + ctx.gas.consume(interpret_cost::blake2b(msg)?)?; + *msg = blake2b_256(msg).to_vec(); + } + I::Keccak => { + let msg = irrefutable_match!(&mut stack[0]; V::Bytes); + ctx.gas.consume(interpret_cost::keccak(msg)?)?; + *msg = keccak256(msg).to_vec(); + } + I::Sha256 => { + let msg = irrefutable_match!(&mut stack[0]; V::Bytes); + ctx.gas.consume(interpret_cost::sha256(msg)?)?; + *msg = sha256(msg).to_vec(); + } + I::Sha3 => { + let msg = irrefutable_match!(&mut stack[0]; V::Bytes); + ctx.gas.consume(interpret_cost::sha3(msg)?)?; + *msg = sha3_256(msg).to_vec(); + } + I::Sha512 => { + let msg = irrefutable_match!(&mut stack[0]; V::Bytes); + ctx.gas.consume(interpret_cost::sha512(msg)?)?; + *msg = sha512(msg).to_vec(); + } I::Seq(nested) => interpret(nested, ctx, stack)?, } Ok(()) @@ -3149,4 +3175,52 @@ mod interpreter_tests { assert_eq!(interpret_one(&JoinTickets, &mut ctx, &mut stack), Ok(())); assert_eq!(stack, stk![V::new_option(None),]); } + + mod byte_hashes { + use super::*; + + #[track_caller] + fn test(i: Instruction, input: &str, expected_output: &str) { + let input = hex::decode(input).unwrap(); + let mut stack = stk![V::Bytes(input)]; + assert_eq!(interpret_one(&i, &mut Ctx::default(), &mut stack), Ok(())); + assert_eq!(stack.len(), 1); + let out = irrefutable_match!(&stack[0]; V::Bytes); + assert_eq!(out, &hex::decode(expected_output).unwrap()); + } + macro_rules! test { + ($i:ident; $($input:expr => $output:expr);* $(;)*) => { + #[test] + #[allow(non_snake_case)] + fn $i() { + $(test(Instruction::$i, $input, $output);)* + } + }; + } + test!( + Blake2b; + "00" => "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314"; + "deadbeef" => "f3e925002fed7cc0ded46842569eb5c90c910c091d8d04a1bdf96e0db719fd91"; + ); + test!( + Keccak; + "00" => "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"; + "deadbeef" => "d4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1"; + ); + test!( + Sha256; + "00" => "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"; + "deadbeef" => "5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953"; + ); + test!( + Sha3; + "00" => "5d53469f20fef4f8eab52b88044ede69c77a6a68a60728609fc4a65ff531e7d0"; + "deadbeef" => "352b82608dad6c7ac3dd665bc2666e5d97803cb13f23a1109e2105e93f42c448"; + ); + test!( + Sha512; + "00" => "b8244d028981d693af7b456af8efa4cad63d282e19ff14942c246e50d9351d22704a802a71c3580b6370de4ceb293c324a8423342557d4e5c38438f0e36910ee"; + "deadbeef" => "1284b2d521535196f22175d5f558104220a6ad7680e78b49fa6f20e57ea7b185d71ec1edb137e70eba528dedb141f5d2f8bb53149d262932b27cf41fed96aa7f"; + ); + } } diff --git a/contrib/mir/src/lexer.rs b/contrib/mir/src/lexer.rs index ecd4c1a10a91..fb3038698e18 100644 --- a/contrib/mir/src/lexer.rs +++ b/contrib/mir/src/lexer.rs @@ -158,7 +158,7 @@ impl std::fmt::Display for Annotation<'_> { #[derive(Debug, Clone, PartialEq, Eq, Logos)] #[logos(error = LexerError, skip r"[ \t\r\n\v\f]+|#[^\n]*\n")] pub enum Tok<'a> { - #[regex(r"[A-Za-z_]+", lex_noun)] + #[regex(r"[A-Za-z_][A-Za-z_0-9]*", lex_noun)] Noun(Noun), #[regex("([+-]?)[0-9]+", lex_number)] diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index 1f315ec7e6c7..55fab5ef8c3e 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -1340,6 +1340,22 @@ pub(crate) fn typecheck_instruction<'a>( (App(JOIN_TICKETS, [], _), []) => no_overload!(JOIN_TICKETS, len 1), (App(JOIN_TICKETS, expect_args!(0), _), _) => unexpected_micheline!(), + // stack type doesn't change in these instructions, so we don't touch it + (App(BLAKE2B, [], _), [.., T::Bytes]) => I::Blake2b, + (App(KECCAK, [], _), [.., T::Bytes]) => I::Keccak, + (App(SHA256, [], _), [.., T::Bytes]) => I::Sha256, + (App(SHA3, [], _), [.., T::Bytes]) => I::Sha3, + (App(SHA512, [], _), [.., T::Bytes]) => I::Sha512, + (App(prim @ (BLAKE2B | KECCAK | SHA256 | SHA3 | SHA512), [], _), [.., t]) => { + no_overload!(*prim, TypesNotEqual(T::Bytes, t.clone())) + } + (App(prim @ (BLAKE2B | KECCAK | SHA256 | SHA3 | SHA512), [], _), []) => { + no_overload!(*prim, len 1) + } + (App(BLAKE2B | KECCAK | SHA256 | SHA3 | SHA512, expect_args!(0), _), _) => { + unexpected_micheline!() + } + (App(other, ..), _) => todo!("Unhandled instruction {other}"), (Seq(nested), _) => I::Seq(typecheck(nested, ctx, self_entrypoints, opt_stack)?), @@ -5043,4 +5059,74 @@ mod typecheck_tests { fn hash_key_too_short() { too_short_test(&app!(HASH_KEY), Prim::HASH_KEY, 1); } + + mod hash_instructions { + use super::*; + + // hash instructions are all basically the same as far as typechecking + // is concerned, so instead of duplicating a bunch of tests 5 times, a + // macro. -- @lierdakl + macro_rules! test { + ($instr_prim:ident, $expected_instruction:expr) => { + #[allow(non_snake_case)] + mod $instr_prim { + use super::*; + + #[test] + fn ok() { + let mut stack = tc_stk![Type::Bytes]; + assert_eq!( + typecheck_instruction( + &parse(stringify!($instr_prim)).unwrap(), + &mut Ctx::default(), + &mut stack, + ), + Ok($expected_instruction), + ); + assert_eq!(stack, tc_stk![Type::Bytes]); + } + + #[test] + fn too_short() { + too_short_test(&app!($instr_prim), Prim::$instr_prim, 1); + } + + #[test] + fn bad_input() { + let mut stack = tc_stk![Type::Unit]; + assert_eq!( + typecheck_instruction( + &app!($instr_prim), + &mut Ctx::default(), + &mut stack, + ), + Err(TcError::NoMatchingOverload { + instr: Prim::$instr_prim, + stack: stk![Type::Unit], + reason: Some(TypesNotEqual(Type::Bytes, Type::Unit).into()), + }), + ); + } + + #[test] + fn bad_micheline() { + assert!(matches!( + typecheck_instruction( + // instruction with unit argument + &app!($instr_prim[app!(unit)]), + &mut Ctx::default(), + &mut tc_stk![], + ), + Err(TcError::UnexpectedMicheline(_)), + )); + } + } + }; + } + test!(BLAKE2B, Blake2b); + test!(KECCAK, Keccak); + test!(SHA256, Sha256); + test!(SHA3, Sha3); + test!(SHA512, Sha512); + } } -- GitLab