Skip to content

Commit

Permalink
ECRECOVER ethereum addresses (FuelLabs#1374)
Browse files Browse the repository at this point in the history
* organize vm/evm stuff into sub libraries

* add ethereum sign to sig-gen-util. Test ethereum ecrecover.

* documents how eth addresses are padded

* remove redundant workspace member

* fmt

* relative path for std dependency

* remove cargo.toml from test project.

* dependency order

* dep order

* use ec_recover from std::ecr

* sway deps alphabetical

Co-authored-by: simonroberts0204 <[email protected]>
Co-authored-by: simonr0204 <[email protected]>
  • Loading branch information
3 people authored Apr 27, 2022
1 parent a379fd2 commit c41a542
Show file tree
Hide file tree
Showing 14 changed files with 185 additions and 2 deletions.
28 changes: 27 additions & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions sway-lib-std/src/lib.sw
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ dep block;
dep token;
dep ecr;
dep reentrancy;
dep vm/lib;

use core::*;
32 changes: 32 additions & 0 deletions sway-lib-std/src/vm/evm/ecr.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
library ecr;

use ::address::Address;
use ::b512::B512;
use ::context::registers::error;
use ::ecr::{EcRecoverError, ec_recover};
use ::hash::{HashMethod, hash_pair};
use ::result::*;

/// Recover the address derived from the private key used to sign a message.
/// Returns a `Result` to let the caller choose an error handling strategy.
/// Ethereum addresses are 20 bytes long, so these are left-padded to fit in a 32 byte Address type.
pub fn ec_recover_address(signature: B512, msg_hash: b256) -> Result<Address, EcRecoverError> {
let pub_key_result = ec_recover(signature, msg_hash);

if let Result::Err(e) = pub_key_result {
// propagate the error if it exists
Result::Err(e)
} else {
let pub_key = pub_key_result.unwrap();

// Note that Ethereum addresses are derived from the Keccak256 hash of the pubkey (not sha256)
let address = hash_pair((pub_key.bytes)[0], (pub_key.bytes)[1], HashMethod::Keccak256);

// Zero out first 12 bytes for ethereum address
asm(r1: address) {
mcli r1 i12;
};

Result::Ok(~Address::from(address))
}
}
3 changes: 3 additions & 0 deletions sway-lib-std/src/vm/evm/lib.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library evm;

dep ecr;
3 changes: 3 additions & 0 deletions sway-lib-std/src/vm/lib.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library vm;

dep evm/lib;
2 changes: 2 additions & 0 deletions test-sig-gen-util/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ publish = false
anyhow = "1.0.39"
fuel-crypto = "0.4"
fuel-tx = "0.7"
fuel-types = { version = "0.3", default-features = false }
fuel-vm = "0.6"
hex = "0.4"
secp256k1 = { version = "0.20", features = ["recovery"] }
sha3 = "0.10"
20 changes: 19 additions & 1 deletion test-sig-gen-util/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,21 @@ use fuel_vm::prelude::*;

use anyhow::Result;
use secp256k1::{PublicKey, Secp256k1, SecretKey};
use sha3::{Digest, Keccak256};
use std::str::FromStr;

// A keccak-256 method for generating ethereum signatures
fn keccak_hash<B>(data: B) -> Bytes32
where
B: AsRef<[u8]>,
{
// create a Keccak256 object
let mut hasher = Keccak256::new();
// write input message
hasher.update(data);
<[u8; Bytes32::LEN]>::from(hasher.finalize()).into()
}

fn main() -> Result<()> {
let secp = Secp256k1::new();
let secret =
Expand All @@ -19,6 +32,7 @@ fn main() -> Result<()> {
let public = Bytes64::try_from(&public[1..]).expect("Failed to parse public key!");
// 64 byte fuel address is the sha-256 hash of the public key.
let address = Hasher::hash(&public[..]);
let ethereum_pubkeyhash = keccak_hash(&public[..]);

let message = b"The gift of words is the gift of deception and illusion.";
let e = Hasher::hash(&message[..]);
Expand All @@ -27,7 +41,11 @@ fn main() -> Result<()> {

println!("Secret Key: {:?}", secret);
println!("Public Key: {:?}", public);
println!("Fuel Address: {:?}", address);
println!("Fuel Address (sha2-256): {:?}", address);
println!(
"Ethereum pubkey hash (keccak256): {:?}",
ethereum_pubkeyhash
);
println!("Message Hash: {:?}", e);
println!("Signature: {:?}", sig);

Expand Down
Empty file modified test/src/sdk-harness/build.sh
100755 → 100644
Empty file.
2 changes: 2 additions & 0 deletions test/src/sdk-harness/test_projects/evm_ecr/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
11 changes: 11 additions & 0 deletions test/src/sdk-harness/test_projects/evm_ecr/Forc.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[[package]]
name = 'core'
dependencies = []

[[package]]
name = 'evm_ecr'
dependencies = ['std']

[[package]]
name = 'std'
dependencies = ['core']
8 changes: 8 additions & 0 deletions test/src/sdk-harness/test_projects/evm_ecr/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "evm_ecr"

[dependencies]
std = { path = "../../../../../sway-lib-std" }
36 changes: 36 additions & 0 deletions test/src/sdk-harness/test_projects/evm_ecr/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use fuel_core::service::Config;
use fuel_tx::Transaction;
use fuels_contract::script::Script;
use fuels_signers::provider::Provider;
use std::fs::read;

async fn execute_script(bin_path: &str) -> u64 {
let bin = read(bin_path);
let client = Provider::launch(Config::local_node()).await.unwrap();

let tx = Transaction::Script {
gas_price: 0,
gas_limit: 1_000_000,
maturity: 0,
byte_price: 0,
receipts_root: Default::default(),
script: bin.unwrap(), // Here we pass the compiled script into the transaction
script_data: vec![],
inputs: vec![],
outputs: vec![],
witnesses: vec![vec![].into()],
metadata: None,
};

let script = Script::new(tx);
let receipts = script.call(&client).await.unwrap();

receipts[0].val().unwrap()
}

#[tokio::test]
async fn evm_ecr_implementation() {
let path_to_bin = "test_projects/evm_ecr/out/debug/evm_ecr.bin";
let return_val = execute_script(path_to_bin).await;
assert_eq!(1, return_val);
}
40 changes: 40 additions & 0 deletions test/src/sdk-harness/test_projects/evm_ecr/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
script;

use std::address::Address;
use std::assert::assert;
use std::b512::B512;
use std::ecr::EcRecoverError;
use std::result::*;
use std::vm::evm::ecr::ec_recover_address;

fn main() -> bool {
//======================================================
// test data from sig-gen-util: /sway/sig_gen_util/src/main.rs
/**
Secret Key: SecretKey(3b940b5586823dfd02ae3b461bb4336b5ecbaefd6627aa922efc048fec0c881c)
Public Key: 1d152307c6b72b0ed0418b0e70cd80e7f5295b8d86f5722d3f5213fbd2394f36b7ce9c3e45905178455900b44abb308f3ef480481a4b2ee3f70aca157fde396a
Fuel Address (sha2-256): 6ba48099f6b75cae5a403863ace6ee8dc03f75e7aebc58b819667477358ae677
Ethereum pubkey hash (keccak256): e4eab8f844a8d11b205fd137a1b7ea5ede26f651909505d99cf8b5c0d4c8e9c1
Message Hash: 8ddb13a2ab58f413bd3121e1ddc8b83a328f3b830d19a7c471f0be652d23bb0e
Signature: 82115ed208d8fe8dd522d88ca77812b34d270d6bb6326ff511297766a3af1166c07204f554a00e49a2ee69f0979dc4feef07f7dba8d779d388fb2a53bc9bcde4
*/

// Get the expected ethereum pubkeyhash
let pubkey: B512 = ~B512::from(0x1d152307c6b72b0ed0418b0e70cd80e7f5295b8d86f5722d3f5213fbd2394f36, 0xb7ce9c3e45905178455900b44abb308f3ef480481a4b2ee3f70aca157fde396a);
let ethereum_pubkeyhash: Address = ~Address::from(0xe4eab8f844a8d11b205fd137a1b7ea5ede26f651909505d99cf8b5c0d4c8e9c1);
// Manually zero the first 12 bytes.
let ethereum_address: Address = ~Address::from(0x000000000000000000000000a1b7ea5ede26f651909505d99cf8b5c0d4c8e9c1);

let msg_hash = 0x8ddb13a2ab58f413bd3121e1ddc8b83a328f3b830d19a7c471f0be652d23bb0e;

// create a signature:
let sig_hi = 0x82115ed208d8fe8dd522d88ca77812b34d270d6bb6326ff511297766a3af1166;
let sig_lo = 0xc07204f554a00e49a2ee69f0979dc4feef07f7dba8d779d388fb2a53bc9bcde4;
let signature: B512 = ~B512::from(sig_hi, sig_lo);

// recover the address:
let result: Result<Address, EcRecoverError> = ec_recover_address(signature, msg_hash);
let recovered_address = result.unwrap();

recovered_address == ethereum_address
}
1 change: 1 addition & 0 deletions test/src/sdk-harness/test_projects/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod auth;
mod call_frames;
mod context;
mod contract_id_type;
mod evm_ecr;
mod logging;
mod option;
mod reentrancy;
Expand Down

0 comments on commit c41a542

Please sign in to comment.