Skip to content

Commit

Permalink
crypto: implement move native fun for ecrecover (MystenLabs#3636)
Browse files Browse the repository at this point in the history
* crypto: implement move native fun for ecrecover

* increase mock move vm cost

* add API specification
  • Loading branch information
joyqvq authored Aug 8, 2022
1 parent 4c5877d commit 11efc67
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 12 deletions.
23 changes: 12 additions & 11 deletions Cargo.lock

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

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/sui-core/src/unit_tests/gas_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ async fn test_move_call_gas() -> SuiResult {
let gas_used_before_vm_exec = gas_status.summary(true).gas_used();
// The gas cost to execute the function in Move VM.
// Hard code it here since it's difficult to mock that in test.
// If a new native move module/function is modified, this value may need to be increased due to the increase of sui framework package
const MOVE_VM_EXEC_COST: u64 = 17006;
gas_status.charge_vm_exec_test_only(MOVE_VM_EXEC_COST)?;
let created_object = authority_state
Expand Down
1 change: 1 addition & 0 deletions crates/sui-framework/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ move-stdlib = { git = "https://github.com/move-language/move", rev = "7907152852
move-unit-test = { git = "https://github.com/move-language/move", rev = "79071528524f08b12e9abb84c1094d8e976aa17a" }
move-vm-runtime = { git = "https://github.com/move-language/move", rev = "79071528524f08b12e9abb84c1094d8e976aa17a" }
move-vm-types = { git = "https://github.com/move-language/move", rev = "79071528524f08b12e9abb84c1094d8e976aa17a" }
narwhal-crypto = { git = "https://github.com/MystenLabs/narwhal", rev = "259a37b487570763575e6b28f8b8057b16b3e916", package = "crypto" }
workspace-hack = { path = "../workspace-hack"}

[build-dependencies]
Expand Down
17 changes: 17 additions & 0 deletions crates/sui-framework/sources/crypto.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

/// Library for cryptography onchain.
module sui::crypto {
/// @param signature: A 65-bytes signature in form (r, s, v) that is signed using
/// Secp256k1. Reference implementation on signature generation using RFC6979:
/// https://github.com/MystenLabs/narwhal/blob/5d6f6df8ccee94446ff88786c0dbbc98be7cfc09/crypto/src/secp256k1.rs
///
/// @param hashed_msg: the hashed 32-bytes message. The message must be hashed instead
/// of plain text to be secure.
///
/// If the signature is valid, return the corresponding recovered Secpk256k1 public
/// key, otherwise throw error. This is similar to ecrecover in Ethereum, can only be
/// applied to Secp256k1 signatures.
public native fun ecrecover(signature: vector<u8>, hashed_msg: vector<u8>): vector<u8>;
}
42 changes: 42 additions & 0 deletions crates/sui-framework/src/natives/crypto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
use move_binary_format::errors::PartialVMResult;
use move_vm_runtime::native_functions::NativeContext;
use move_vm_types::{
gas_schedule::NativeCostIndex,
loaded_data::runtime_types::Type,
natives::function::{native_gas, NativeResult},
pop_arg,
values::Value,
};
use narwhal_crypto::traits::ToFromBytes;
use smallvec::smallvec;
use std::collections::VecDeque;

pub const FAIL_TO_RECOVER_PUBKEY: u64 = 0;
pub const INVALID_SIGNATURE: u64 = 1;

/// Native implemention of ecrecover in public Move API, see crypto.move for specifications.
pub fn ecrecover(
context: &mut NativeContext,
ty_args: Vec<Type>,
mut args: VecDeque<Value>,
) -> PartialVMResult<NativeResult> {
debug_assert!(ty_args.is_empty());
debug_assert!(args.len() == 2);

let hashed_msg = pop_arg!(args, Vec<u8>);
let signature = pop_arg!(args, Vec<u8>);
// TODO: implement native gas cost estimation https://github.com/MystenLabs/sui/issues/3593
let cost = native_gas(context.cost_table(), NativeCostIndex::EMIT_EVENT, 0);
match <narwhal_crypto::secp256k1::Secp256k1Signature as ToFromBytes>::from_bytes(&signature) {
Ok(signature) => match signature.recover(&hashed_msg) {
Ok(pubkey) => Ok(NativeResult::ok(
cost,
smallvec![Value::vector_u8(pubkey.as_bytes().to_vec())],
)),
Err(_) => Ok(NativeResult::err(cost, FAIL_TO_RECOVER_PUBKEY)),
},
Err(_) => Ok(NativeResult::err(cost, INVALID_SIGNATURE)),
}
}
2 changes: 2 additions & 0 deletions crates/sui-framework/src/natives/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

mod crypto;
mod event;
mod object;
mod test_scenario;
Expand All @@ -17,6 +18,7 @@ pub fn all_natives(
sui_framework_addr: AccountAddress,
) -> NativeFunctionTable {
const SUI_NATIVES: &[(&str, &str, NativeFunction)] = &[
("crypto", "ecrecover", crypto::ecrecover),
("event", "emit", event::emit),
("object", "bytes_to_address", object::bytes_to_address),
("object", "delete_impl", object::delete_impl),
Expand Down
34 changes: 34 additions & 0 deletions crates/sui-framework/tests/crypto_tests.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

#[test_only]
module sui::crypto_tests {
use sui::crypto;
#[test]
fun test_ecrecover_pubkey() {
// test case generated against https://docs.rs/secp256k1/latest/secp256k1/
let hashed_msg = vector[87, 202, 161, 118, 175, 26, 192, 67, 60, 93, 243, 14, 141, 171, 205, 46, 193, 175, 30, 146, 162, 110, 206, 213, 247, 25, 184, 132, 88, 119, 124, 214];
let sig = vector[132, 220, 128, 67, 151, 154, 45, 143, 50, 56, 176, 134, 137, 58, 223, 166, 191, 230, 178, 184, 123, 11, 19, 69, 59, 205, 72, 206, 153, 187, 184, 7, 16, 74, 73, 45, 38, 238, 81, 96, 138, 225, 235, 143, 95, 142, 185, 56, 99, 3, 97, 27, 66, 99, 79, 225, 139, 21, 67, 254, 78, 251, 176, 176, 0];
let pubkey_bytes = vector[2, 2, 87, 224, 47, 124, 255, 117, 223, 91, 188, 190, 151, 23, 241, 173, 148, 107, 20, 103, 63, 155, 108, 151, 251, 152, 205, 205, 239, 71, 224, 86, 9];

let pubkey = crypto::ecrecover(sig, hashed_msg);
assert!(pubkey == pubkey_bytes, 0);
}

#[test]
#[expected_failure(abort_code = 0)]
fun test_ecrecover_pubkey_fail_to_recover() {
let hashed_msg = vector[0];
let sig = vector[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
crypto::ecrecover(sig, hashed_msg);
}

#[test]
#[expected_failure(abort_code = 1)]
fun test_ecrecover_pubkey_invalid_sig() {
let hashed_msg = vector[0];
// incorrect length sig
let sig = vector[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
crypto::ecrecover(sig, hashed_msg);
}
}

0 comments on commit 11efc67

Please sign in to comment.