Skip to content

Commit

Permalink
ripemd160 & bech32 (#150)
Browse files Browse the repository at this point in the history
* add ripemd160 to hash module

* add bech32 module

* fmt

* fix names

* fix test to use simple hex macro

---------

Co-authored-by: beer-1 <[email protected]>
  • Loading branch information
sh-cha and beer-1 authored Oct 23, 2024
1 parent ae238bf commit fdaff32
Show file tree
Hide file tree
Showing 14 changed files with 271 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ base64 = "0.21.7"
bigdecimal = ">=0.4.5"
bech32 = "0.11"
triomphe = "0.1.9"
ripemd = "0.1.1"
tiny-keccak = { version = "2.0.2", features = ["keccak", "sha3"] }

# Note: the BEGIN and END comments below are required for external tooling. Do not remove.
Expand Down
5 changes: 5 additions & 0 deletions crates/gas/src/initia_stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ crate::macros::define_gas_parameters!(
[base64_decode_base: InternalGas, "base64.decode.base", 1102],
[base64_decode_unit: InternalGasPerByte, "base64.decode.unit", 18],

[bech32_encode_base: InternalGas, "bech32.encode.base", 1102],
[bech32_encode_unit: InternalGasPerByte, "bech32.encode.unit", 18],
[bech32_decode_base: InternalGas, "bech32.decode.base", 1102],
[bech32_decode_unit: InternalGasPerByte, "bech32.decode.unit", 18],

[crypto_ed25519_base: InternalGas, "crypto.ed25519.base", 551],
[crypto_ed25519_per_sig_verify: InternalGasPerArg, "crypto.ed25519.per_sig_verify", 981492],
[crypto_ed25519_per_pubkey_deserialize: InternalGasPerArg, "crypto.ed25519.per_pubkey_deserialize", 139688],
Expand Down
2 changes: 2 additions & 0 deletions crates/gas/src/move_stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ crate::macros::define_gas_parameters!(
[hash_sha2_256_per_byte: InternalGasPerByte, "hash.sha2_256.per_byte", 183],
[hash_sha3_256_base: InternalGas, "hash.sha3_256.base", 14704],
[hash_sha3_256_per_byte: InternalGasPerByte, "hash.sha3_256.per_byte", 165],
[hash_ripemd160_base: InternalGas, "hash.ripemd160.base", 11028],
[hash_ripemd160_per_byte: InternalGasPerByte, "hash.ripemd160.per_byte", 183],

// Note(Gas): this initial value is guesswork.
[signer_borrow_address_base: InternalGas, "signer.borrow_address.base", 735],
Expand Down
1 change: 1 addition & 0 deletions crates/natives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ rand_core = { workspace = true }
libsecp256k1 = { workspace = true }
sha2 = { workspace = true }
sha3 = { workspace = true }
ripemd = { workspace = true }
ed25519-consensus = { workspace = true }
hex = { workspace = true }

Expand Down
125 changes: 125 additions & 0 deletions crates/natives/src/bech32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use bech32::{Bech32, Hrp};
use move_core_types::gas_algebra::NumBytes;
use move_vm_runtime::native_functions::NativeFunction;
use move_vm_types::{
loaded_data::runtime_types::Type,
values::{Struct, Value},
};
use smallvec::{smallvec, SmallVec};
use std::collections::VecDeque;

use crate::{
helpers::get_string,
interface::{
RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError, SafeNativeResult,
},
safely_pop_arg,
};

// See stdlib/error.move
const ECATEGORY_INVALID_ARGUMENT: u64 = 0x1;

// native errors always start from 100
const EUNABLE_TO_ENCODE: u64 = (ECATEGORY_INVALID_ARGUMENT << 16) + 100;
const EUNABLE_TO_DECODE: u64 = (ECATEGORY_INVALID_ARGUMENT << 16) + 101;
const EINVALID_PREFIX: u64 = (ECATEGORY_INVALID_ARGUMENT << 16) + 102;
const EINVALID_ADDRESS: u64 = (ECATEGORY_INVALID_ARGUMENT << 16) + 103;

/*
native public fun encode(prefix: String, data: vector<u8>): String;
native public fun decode(addr: String): (String, vector<u8>);
*/

/***************************************************************************************************
* native fun encode
*
* gas cost: base_cost + unit_cost * (prefix_len + data_len)
*
**************************************************************************************************/
fn native_encode(
context: &mut SafeNativeContext,
ty_args: Vec<Type>,
mut arguments: VecDeque<Value>,
) -> SafeNativeResult<SmallVec<[Value; 1]>> {
let gas_params = &context.native_gas_params.initia_stdlib;

debug_assert!(ty_args.is_empty());
debug_assert_eq!(arguments.len(), 2);

let data = safely_pop_arg!(arguments, Vec<u8>);
let raw_prefix = get_string(safely_pop_arg!(arguments, Struct))?;
let prefix = String::from_utf8(raw_prefix).map_err(|_| SafeNativeError::Abort {
abort_code: EINVALID_PREFIX,
})?;
context.charge(
gas_params.bech32_encode_base
+ gas_params.bech32_encode_unit * NumBytes::new((prefix.len() + data.len()) as u64),
)?;

let encoded_string = bech32::encode::<Bech32>(
Hrp::parse(prefix.as_str()).map_err(|_| SafeNativeError::Abort {
abort_code: EINVALID_PREFIX,
})?,
data.as_slice(),
)
.map_err(|_| SafeNativeError::Abort {
abort_code: EUNABLE_TO_ENCODE,
})?;

Ok(smallvec![Value::struct_(Struct::pack(vec![
Value::vector_u8(encoded_string.as_bytes().to_vec()),
]))])
}

/***************************************************************************************************
* native fun decode
*
* gas cost: base_cost + unit_cost * address_len
*
**************************************************************************************************/
fn native_decode(
context: &mut SafeNativeContext,
ty_args: Vec<Type>,
mut arguments: VecDeque<Value>,
) -> SafeNativeResult<SmallVec<[Value; 1]>> {
let gas_params = &context.native_gas_params.initia_stdlib;

debug_assert!(ty_args.is_empty());
debug_assert_eq!(arguments.len(), 1);

let raw_addr = get_string(safely_pop_arg!(arguments, Struct))?;
let addr = String::from_utf8(raw_addr).map_err(|_| SafeNativeError::Abort {
abort_code: EINVALID_ADDRESS,
})?;

context.charge(
gas_params.bech32_decode_base
+ gas_params.bech32_decode_unit * NumBytes::new(addr.len() as u64),
)?;

let (prefix, words) = bech32::decode(addr.as_str()).map_err(|_| SafeNativeError::Abort {
abort_code: EUNABLE_TO_DECODE,
})?;

Ok(smallvec![
Value::struct_(Struct::pack(vec![Value::vector_u8(
prefix.as_bytes().to_vec()
)])),
Value::vector_u8(words)
])
}

/***************************************************************************************************
* module
*
**************************************************************************************************/
pub fn make_all(
builder: &SafeNativeBuilder,
) -> impl Iterator<Item = (String, NativeFunction)> + '_ {
let natives = [
("encode", native_encode as RawSafeNative),
("decode", native_decode),
];

builder.make_named_natives(natives)
}
2 changes: 2 additions & 0 deletions crates/natives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod account;
pub mod address;
pub mod any;
pub mod base64;
pub mod bech32;
pub mod biguint;
pub mod block;
pub mod code;
Expand Down Expand Up @@ -62,6 +63,7 @@ pub fn initia_move_natives(
add_natives_from_module!("type_info", type_info::make_all(builder));
add_natives_from_module!("from_bcs", from_bcs::make_all(builder));
add_natives_from_module!("base64", base64::make_all(builder));
add_natives_from_module!("bech32", bech32::make_all(builder));
add_natives_from_module!("keccak", keccak::make_all(builder));
add_natives_from_module!("staking", staking::make_all(builder));
add_natives_from_module!("cosmos", cosmos::make_all(builder));
Expand Down
36 changes: 34 additions & 2 deletions crates/natives/src/move_stdlib/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
/***************************************************************************************************
* native fun sha2_256
*
* gas cost: base_cost + unit_cost * max(input_length_in_bytes, legacy_min_input_len)
* gas cost: base_cost + unit_cost * input_length_in_bytes
*
**************************************************************************************************/
#[inline]
Expand Down Expand Up @@ -48,7 +48,7 @@ fn native_sha2_256(
/***************************************************************************************************
* native fun sha3_256
*
* gas cost: base_cost + unit_cost * max(input_length_in_bytes, legacy_min_input_len)
* gas cost: base_cost + unit_cost * input_length_in_bytes
*
**************************************************************************************************/
#[inline]
Expand All @@ -73,6 +73,37 @@ fn native_sha3_256(
Ok(smallvec![Value::vector_u8(hash_vec)])
}

/***************************************************************************************************
* native fun ripemd160
*
* gas cost: base_cost + unit_cost * input_length_in_bytes
*
**************************************************************************************************/
#[inline]
fn native_ripemd160(
context: &mut SafeNativeContext,
_ty_args: Vec<Type>,
mut arguments: VecDeque<Value>,
) -> SafeNativeResult<SmallVec<[Value; 1]>> {
let gas_params = &context.native_gas_params.move_stdlib;

debug_assert!(_ty_args.is_empty());
debug_assert!(arguments.len() == 1);

let hash_arg = safely_pop_arg!(arguments, Vec<u8>);

context.charge(
gas_params.hash_ripemd160_base
+ gas_params.hash_ripemd160_per_byte * NumBytes::new(hash_arg.len() as u64),
)?;

let mut hasher = ripemd::Ripemd160::new();
hasher.update(&hash_arg);
let hash_vec = hasher.finalize().to_vec();

Ok(smallvec![Value::vector_u8(hash_vec)])
}

/***************************************************************************************************
* module
**************************************************************************************************/
Expand All @@ -82,6 +113,7 @@ pub fn make_all(
let natives = [
("sha2_256", native_sha2_256 as RawSafeNative),
("sha3_256", native_sha3_256),
("ripemd160", native_ripemd160),
];

builder.make_named_natives(natives)
Expand Down
Binary file added precompile/binaries/minlib/bech32.mv
Binary file not shown.
Binary file modified precompile/binaries/minlib/hash.mv
Binary file not shown.
Binary file added precompile/binaries/stdlib/bech32.mv
Binary file not shown.
Binary file modified precompile/binaries/stdlib/hash.mv
Binary file not shown.
37 changes: 37 additions & 0 deletions precompile/modules/initia_stdlib/sources/bech32.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module initia_std::bech32 {
use initia_std::string::String;

native public fun encode(prefix: String, data: vector<u8>): String;
native public fun decode(addr: String): (String, vector<u8>);

#[test_only]
use initia_std::string;

#[test]
fun test_bech32_encode() {
let prefix = string::utf8(b"init");
let data = x"12eafdba79c3dd7b90e3712ee475423153a722c7";
let got = encode(prefix, data);
let expected = string::utf8(b"init1zt40mwnec0whhy8rwyhwga2zx9f6wgk8p3x098");
assert!(got == expected, 0);

let prefix = string::utf8(b"celestia");
let data = x"12eafdba79c3dd7b90e3712ee475423153a722c7";
let got = encode(prefix, data);
let expected = string::utf8(b"celestia1zt40mwnec0whhy8rwyhwga2zx9f6wgk87dhv5g");
assert!(got == expected, 1);
}

#[test]
fun test_bech32_decode() {
let addr = string::utf8(b"init1zt40mwnec0whhy8rwyhwga2zx9f6wgk8p3x098");
let (prefix, data) = decode(addr);
assert!(prefix == string::utf8(b"init"), 0);
assert!(data == x"12eafdba79c3dd7b90e3712ee475423153a722c7", 1);

let addr = string::utf8(b"celestia1zt40mwnec0whhy8rwyhwga2zx9f6wgk87dhv5g");
let (prefix, data) = decode(addr);
assert!(prefix == string::utf8(b"celestia"), 2);
assert!(data == x"12eafdba79c3dd7b90e3712ee475423153a722c7", 3);
}
}
37 changes: 37 additions & 0 deletions precompile/modules/minitia_stdlib/sources/bech32.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module minitia_std::bech32 {
use minitia_std::string::String;

native public fun encode(prefix: String, data: vector<u8>): String;
native public fun decode(addr: String): (String, vector<u8>);

#[test_only]
use minitia_std::string;

#[test]
fun test_bech32_encode() {
let prefix = string::utf8(b"init");
let data = x"12eafdba79c3dd7b90e3712ee475423153a722c7";
let got = encode(prefix, data);
let expected = string::utf8(b"init1zt40mwnec0whhy8rwyhwga2zx9f6wgk8p3x098");
assert!(got == expected, 0);

let prefix = string::utf8(b"celestia");
let data = x"12eafdba79c3dd7b90e3712ee475423153a722c7";
let got = encode(prefix, data);
let expected = string::utf8(b"celestia1zt40mwnec0whhy8rwyhwga2zx9f6wgk87dhv5g");
assert!(got == expected, 1);
}

#[test]
fun test_bech32_decode() {
let addr = string::utf8(b"init1zt40mwnec0whhy8rwyhwga2zx9f6wgk8p3x098");
let (prefix, data) = decode(addr);
assert!(prefix == string::utf8(b"init"), 0);
assert!(data == x"12eafdba79c3dd7b90e3712ee475423153a722c7", 1);

let addr = string::utf8(b"celestia1zt40mwnec0whhy8rwyhwga2zx9f6wgk87dhv5g");
let (prefix, data) = decode(addr);
assert!(prefix == string::utf8(b"celestia"), 2);
assert!(data == x"12eafdba79c3dd7b90e3712ee475423153a722c7", 3);
}
}
27 changes: 27 additions & 0 deletions precompile/modules/move_stdlib/sources/hash.move
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,31 @@
module std::hash {
native public fun sha2_256(data: vector<u8>): vector<u8>;
native public fun sha3_256(data: vector<u8>): vector<u8>;
native public fun ripemd160(data: vector<u8>): vector<u8>;

#[test]
fun ripemd160_test() {
let inputs = vector[
b"testing",
b"",
];

// From https://www.browserling.com/tools/ripemd160-hash
let outputs = vector[
x"b89ba156b40bed29a5965684b7d244c49a3a769b",
x"9c1185a5c5e9fc54612808977ee8f548b2258d31",
];

let i = 0;
while (i < std::vector::length(&inputs)) {
let input = *std::vector::borrow(&inputs, i);
let hash_expected = *std::vector::borrow(&outputs, i);
let hash = ripemd160(input);

assert!(hash_expected == hash, 1);

i = i + 1;
};
}
}

0 comments on commit fdaff32

Please sign in to comment.