From a8683ed83540961f4e99774bd67f1b12ad0c2b9c Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Fri, 24 Nov 2023 17:44:19 +0300 Subject: [PATCH] MIR: add HASH_KEY instruction --- contrib/mir/src/ast.rs | 1 + contrib/mir/src/ast/michelson_key.rs | 52 ++++++++++++++++++++++++---- contrib/mir/src/gas.rs | 1 + contrib/mir/src/interpreter.rs | 29 ++++++++++++++++ contrib/mir/src/typechecker.rs | 39 +++++++++++++++++++++ 5 files changed, 116 insertions(+), 6 deletions(-) diff --git a/contrib/mir/src/ast.rs b/contrib/mir/src/ast.rs index 3b7b4ce86c77..1a397c823768 100644 --- a/contrib/mir/src/ast.rs +++ b/contrib/mir/src/ast.rs @@ -314,6 +314,7 @@ pub enum Instruction<'a> { Right, Lambda(Lambda<'a>), Exec, + HashKey, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/contrib/mir/src/ast/michelson_key.rs b/contrib/mir/src/ast/michelson_key.rs index 8aaf4599f167..808146ae7b1c 100644 --- a/contrib/mir/src/ast/michelson_key.rs +++ b/contrib/mir/src/ast/michelson_key.rs @@ -5,11 +5,15 @@ /* */ /******************************************************************************/ -use tezos_crypto_rs::hash::{ - Hash, HashTrait, PublicKeyBls, PublicKeyEd25519, PublicKeyP256, PublicKeySecp256k1, +use tezos_crypto_rs::{ + hash::{Hash, HashTrait, PublicKeyBls, PublicKeyEd25519, PublicKeyP256, PublicKeySecp256k1}, + PublicKeyWithHash, }; -use super::byte_repr_trait::{ByteReprError, ByteReprTrait}; +use super::{ + byte_repr_trait::{ByteReprError, ByteReprTrait}, + KeyHash, +}; macro_rules! key_type_and_impls { ($($con:ident($ty:ident)),* $(,)*) => { @@ -83,6 +87,18 @@ impl Key { /// Smallest key size pub const MIN_BASE58_SIZE: usize = 54; pub const MIN_BYTE_SIZE: usize = 32; + + pub fn hash(&self) -> KeyHash { + use Key::*; + // unwrap because errors should be literally impossible, any bytestring + // can be hashed, and hash size is constant. + match self { + Ed25519(hash) => KeyHash::Tz1(hash.pk_hash().unwrap()), + Secp256k1(hash) => KeyHash::Tz2(hash.pk_hash().unwrap()), + P256(hash) => KeyHash::Tz3(hash.pk_hash().unwrap()), + Bls(hash) => KeyHash::Tz4(hash.pk_hash().unwrap()), + } + } } impl ByteReprTrait for Key { @@ -150,7 +166,7 @@ mod tests { #[test] fn test_base58_to_bin() { - for (b58, hex) in FIXTURES { + for (b58, hex, _) in FIXTURES { assert_eq!( hex::encode(Key::from_base58_check(b58).unwrap().to_bytes_vec()), hex, @@ -158,6 +174,16 @@ mod tests { } } + #[test] + fn test_hash() { + for (b58, _, hash) in FIXTURES { + assert_eq!( + Key::from_base58_check(b58).unwrap().hash(), + KeyHash::from_base58_check(hash).unwrap(), + ); + } + } + #[test] fn test_bin_to_base58() { // unknown tag @@ -169,7 +195,7 @@ mod tests { Err(ByteReprError::UnknownPrefix("0xff".to_owned())), ); - for (b58, hex) in FIXTURES { + for (b58, hex, _) in FIXTURES { assert_eq!( Key::from_bytes(&hex::decode(hex).unwrap()) .unwrap() @@ -179,41 +205,55 @@ mod tests { } } + // Triples of key_base58, key_binary, key_hash_base58. + // // binary representation produced by running // // `octez-client --mode mockup normalize data ... of type key --unparsing-mode Optimized` - const FIXTURES: [(&str, &str); 8] = [ + // + // address hashes are produced by running + // + // octez-client --mode mockup run michelson code '{ HASH_KEY }' on stack '{Stack_elt key "..."}' + const FIXTURES: [(&str, &str, &str); 8] = [ ( "edpkupxHveP7SFVnBq4X9Dkad5smzLcSxpRx9tpR7US8DPN5bLPFwu", "009c0f7c35a4352c2eb5e3ad30bf3ea9ecabb8b65b40ccfeea3d58bea08a36c286", + "tz1eq8SApJE7gK2wufuutpaqZjy6wFJaBWkp", ), ( "edpkupH22qrz1sNQt5HSvWfRJFfyJ9dhNbZLptE6GR4JbMoBcACZZH", "009a85e0f3f47852869ae667adc3b03a20fa9f324d046174dff6834e7d1fab0e8d", + "tz1NaZzLvdDBLfV2LWC6F4SJfNV2jHdZJXkJ", ), ( "edpkuwTWKgQNnhR5v17H2DYHbfcxYepARyrPGbf1tbMoGQAj8Ljr3V", "00aad3f16293766169f7db278c5e0e9db4fb82ffe1cbcc35258059617dc0fec082", + "tz1Yz3VPaCNB5FjhdEVnSoN8Xv3ZM8g2LYhw", ), ( "sppk7cdA7Afj8MvuBFrP6KsTLfbM5DtH9GwYaRZwCf5tBVCz6UKGQFR", "0103b524d0184276467c848ac13557fb0ff8bec5907960f72683f22af430503edfc1", + "tz2EfqCbLmpfv7mNiLcMmhxAwdgHtPTcwR4W", ), ( "sppk7Ze7NMs6EHF2uB8qq8GrEgJvE9PWYkUijN3LcesafzQuGyniHBD", "01022c380cd1ff286a0a1a7c3aad6e891d237fa82e2a7cdeec08ccb55e90fdef995f", + "tz2Darj3LyQzekU98ZK8diHvuyn1YYjcHpc6", ), ( "p2pk67K1dwkDFPB63RZU5H3SoMCvmJdKZDZszc7U4FiGKN2YypKdDCB", "020368afbb09255d849813712108a4144237dc1fdd5bb74e68335f4c68c12c1e5723", + "tz3S7wbUwQV581kroR81fYbgDBskFicZ6czW", ), ( "p2pk68C6tJr7pNLvgBH63K3hBVoztCPCA36zcWhXFUGywQJTjYBfpxk", "0203dcb1916c475902f2b1083212e1b4e6f8ce1531710218c7d34340439f47040e7c", + "tz3QEbmdCdsMcnUo2rNjXdbKwg5tyack3goN", ), ( "BLpk1yoPpFtFF3jGUSn2GrGzgHVcj1cm5o6HTMwiqSjiTNFSJskXFady9nrdhoZzrG6ybXiTSK5G", "03b6cf94b6a59d102044d1ff16ebe3eccc5cd554965bb66ac80fb2728c18715817e185fb5ac9437908c9e609a742610177", + "tz4J46gb6DxDFYxkex8k9sKiYZwjuiaoNSqN", ), ]; } diff --git a/contrib/mir/src/gas.rs b/contrib/mir/src/gas.rs index a62bd70d2709..df5f9f65b8f3 100644 --- a/contrib/mir/src/gas.rs +++ b/contrib/mir/src/gas.rs @@ -245,6 +245,7 @@ pub mod interpret_cost { pub const SET_DELEGATE: u32 = 60; pub const LAMBDA: u32 = 10; pub const EXEC: u32 = 10; + pub const HASH_KEY: u32 = 605; pub const INTERPRET_RET: u32 = 15; // corresponds to KNil in the Tezos protocol pub const LOOP_ENTER: u32 = 10; // corresponds to KLoop_in in the Tezos protocol diff --git a/contrib/mir/src/interpreter.rs b/contrib/mir/src/interpreter.rs index 0b6da4ab6e26..1ba8ac1211b2 100644 --- a/contrib/mir/src/interpreter.rs +++ b/contrib/mir/src/interpreter.rs @@ -722,6 +722,11 @@ fn interpret_one<'a>( }; stack.push(res_stk.pop().unwrap_or_else(|| unreachable_state())); } + I::HashKey => { + ctx.gas.consume(interpret_cost::HASH_KEY)?; + let key = pop!(V::Key); + stack.push(TypedValue::KeyHash(key.hash())) + } I::Seq(nested) => interpret(nested, ctx, stack)?, } Ok(()) @@ -2527,4 +2532,28 @@ mod interpreter_tests { ); assert_eq!(stack, stk![TypedValue::nat(5)]); } + + #[test] + fn hash_key() { + // specific key-to-hash correspondence is checked in michelson_key + // tests, here we only want to check interpreter works, so testing only + // one particular key + let mut stack = stk![V::Key( + "edpktxDQJUF9AqUegbhhD9zJWBCPRJ3PtewuwiuAxrnaQbRmdi2tW1" + .try_into() + .unwrap() + )]; + let ctx = &mut Ctx::default(); + assert_eq!(interpret_one(&HashKey, ctx, &mut stack), Ok(())); + assert_eq!( + stack, + stk![V::KeyHash( + "tz1Nw5nr152qddEjKT2dKBH8XcBMDAg72iLw".try_into().unwrap() + )] + ); + assert_eq!( + ctx.gas.milligas(), + Ctx::default().gas.milligas() - interpret_cost::HASH_KEY + ); + } } diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index d472e60e8637..2a6964ea58ab 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -1202,6 +1202,16 @@ pub(crate) fn typecheck_instruction<'a>( (App(EXEC, [], _), [] | [_]) => no_overload!(EXEC, len 2), (App(EXEC, expect_args!(0), _), _) => unexpected_micheline!(), + (App(HASH_KEY, [], _), [.., T::Key]) => { + stack[0] = T::KeyHash; + I::HashKey + } + (App(HASH_KEY, [], _), [.., t]) => { + no_overload!(HASH_KEY, TypesNotEqual(T::Key, t.clone())) + } + (App(HASH_KEY, [], _), []) => no_overload!(HASH_KEY, len 1), + (App(HASH_KEY, expect_args!(0), _), _) => unexpected_micheline!(), + (App(other, ..), _) => todo!("Unhandled instruction {other}"), (Seq(nested), _) => I::Seq(typecheck(nested, ctx, self_entrypoints, opt_stack)?), @@ -4555,4 +4565,33 @@ mod typecheck_tests { }) ); } + + #[test] + fn hash_key() { + let stk = &mut tc_stk![Type::Key]; + let ctx = &mut Ctx::default(); + assert_eq!( + typecheck_instruction(&parse("HASH_KEY").unwrap(), ctx, stk), + Ok(HashKey) + ) + } + + #[test] + fn hash_key_bad_arg() { + let stk = &mut tc_stk![Type::Bytes]; + let ctx = &mut Ctx::default(); + assert_eq!( + typecheck_instruction(&parse("HASH_KEY").unwrap(), ctx, stk), + Err(TcError::NoMatchingOverload { + instr: Prim::HASH_KEY, + stack: stk![Type::Bytes], + reason: Some(TypesNotEqual(Type::Key, Type::Bytes).into()), + }) + ) + } + + #[test] + fn hash_key_too_short() { + too_short_test(&app!(HASH_KEY), Prim::HASH_KEY, 1); + } } -- GitLab