Skip to content

Commit

Permalink
Tweaks to the developer experience with EVM (#713)
Browse files Browse the repository at this point in the history
* Add tests to cli ethereum utils

* Rename ethereum cli to evm

* Update the cli snapshot

* Increase the test timeout to 30 seconds at block-check

* Expose the evm cli command key-info code at crypto-utils-evm crate

* Switch to evm_account_id_from_dev_seed at chainspec

* Update the features shapshot

* Clena up unused imports

* Disable the libsecp256k1 features

* Drop the unused dependencies from humanode-peer

* Put the error handling in order

* Eliminate an unwrap

* Fix docs
  • Loading branch information
MOZGIII authored Aug 8, 2023
1 parent 33498b9 commit 431247a
Show file tree
Hide file tree
Showing 15 changed files with 287 additions and 69 deletions.
18 changes: 15 additions & 3 deletions Cargo.lock

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

17 changes: 17 additions & 0 deletions crates/crypto-utils-evm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "crypto-utils-evm"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
anyhow = "1"
bip32 = "0.5"
libsecp256k1 = { version = "0.7", default-features = false }
sha3 = "0.10"
sp-core = { git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
thiserror = "1"
tiny-bip39 = "1.0"

[dev-dependencies]
hex-literal = "0.4"
204 changes: 204 additions & 0 deletions crates/crypto-utils-evm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//! Various crypto helper functions for EVM.
use sp_core::{H160, H256};

/// A structure representing the key information.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyData {
/// The account address.
pub account: H160,
/// The private key of this account.
pub private_key: H256,
/// The mnemonic phrase.
pub mnemonic: String,
/// The drivation path used for this account.
pub derivation_path: bip32::DerivationPath,
}

/// An error that can occur at [`KeyData::from_mnemonic_bip39`] call.
#[derive(Debug, thiserror::Error)]
pub enum FromMnemonicBip39Error {
/// Derivation has failed.
#[error("derivation: {0}")]
Derivation(bip32::Error),
/// Secret key parsing failed.
#[error("secret key: {0}")]
SecretKey(libsecp256k1::Error),
}

/// An error that can occur at [`KeyData::from_mnemonic_bip44`] call.
#[derive(Debug, thiserror::Error)]
pub enum FromMnemonicBip44Error {
/// Derivation path was invalid.
#[error("derivation path: {0}")]
DerivationPath(bip32::Error),
/// Inner [`KeyData::from_mnemonic_bip39`] call failed.
#[error(transparent)]
FromMnemonicBip39(FromMnemonicBip39Error),
}

/// An error that can occur at [`KeyData::from_phrase_bip44`] call.
#[derive(Debug, thiserror::Error)]
pub enum FromPhraseBip44 {
/// Mnemonic parsing failed.
#[error("mnemonic: {0}")]
Mnemonic(anyhow::Error),
/// Inner [`KeyData::from_mnemonic_bip44`] call failed.
#[error(transparent)]
FromMnemonicBip44(FromMnemonicBip44Error),
}

impl KeyData {
/// Create a new [`KeyData`] from the given BIP39 mnemonic and an account index.
pub fn from_mnemonic_bip39(
mnemonic: &bip39::Mnemonic,
password: &str,
derivation_path: &bip32::DerivationPath,
) -> Result<Self, FromMnemonicBip39Error> {
// Retrieve the seed from the mnemonic.
let seed = bip39::Seed::new(mnemonic, password);

// Derives the private key from.
let ext = bip32::XPrv::derive_from_path(seed, derivation_path)
.map_err(FromMnemonicBip39Error::Derivation)?;

let private_key = libsecp256k1::SecretKey::parse_slice(&ext.to_bytes())
.map_err(FromMnemonicBip39Error::SecretKey)?;

// Retrieves the public key.
let public_key = libsecp256k1::PublicKey::from_secret_key(&private_key);

// Convert into Ethereum-style address.
let mut raw_public_key = [0u8; 64];
raw_public_key.copy_from_slice(&public_key.serialize()[1..65]);

use sha3::Digest;
let digest = sha3::Keccak256::digest(raw_public_key);

let account = H160::from(H256::from_slice(digest.as_ref()));

Ok(Self {
account,
mnemonic: mnemonic.phrase().to_owned(),
private_key: H256::from(private_key.serialize()),
derivation_path: derivation_path.clone(),
})
}

/// Construct the key info from the BIP39 mnemonic using BIP44 convenions.
pub fn from_mnemonic_bip44(
mnemonic: &bip39::Mnemonic,
password: &str,
account_index: Option<u32>,
) -> Result<Self, FromMnemonicBip44Error> {
let derivation_path = format!("m/44'/60'/0'/0/{}", account_index.unwrap_or(0));
let derivation_path = derivation_path
.parse()
.map_err(FromMnemonicBip44Error::DerivationPath)?;
Self::from_mnemonic_bip39(mnemonic, password, &derivation_path)
.map_err(FromMnemonicBip44Error::FromMnemonicBip39)
}

/// Construct the key info from the BIP39 mnemonic phrase (in English) using BIP44 convenions.
/// If you need other language - use [`Self::from_mnemonic_bip44`].
pub fn from_phrase_bip44(
phrase: &str,
password: &str,
account_index: Option<u32>,
) -> Result<Self, FromPhraseBip44> {
let mnemonic = bip39::Mnemonic::from_phrase(phrase, bip39::Language::English)
.map_err(FromPhraseBip44::Mnemonic)?;
Self::from_mnemonic_bip44(&mnemonic, password, account_index)
.map_err(FromPhraseBip44::FromMnemonicBip44)
}

/// Construct the key info from the account on the Substrate standard dev seed.
pub fn from_dev_seed(account_index: u32) -> Self {
Self::from_phrase_bip44(sp_core::crypto::DEV_PHRASE, "", Some(account_index)).unwrap()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn mnemonic_bip44() {
let cases = [
(
"test test test test test test test test test test test junk",
"",
KeyData {
account: H160(hex_literal::hex!(
"f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
)),
private_key: H256(hex_literal::hex!(
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
)),
mnemonic: "test test test test test test test test test test test junk".into(),
derivation_path: "m/44'/60'/0'/0/0".parse().unwrap(),
},
),
(
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",
"Substrate",
KeyData {
account: H160(hex_literal::hex!(
"cf1269fb02698ab9ee45426297e20c84142d9195"
)),
private_key: H256(hex_literal::hex!(
"b29728f71053098351f20350e7087dcb091b151689f8a878734b519901d19853"
)),
mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong".into(),
derivation_path: "m/44'/60'/0'/0/0".parse().unwrap(),
},
),
];

for (phrase, pw, expected_key_info) in cases {
let key_info = KeyData::from_phrase_bip44(phrase, pw, None).unwrap();
assert_eq!(key_info, expected_key_info);
}
}

#[test]
fn dev_seed() {
let cases = [
(
0,
KeyData {
account: H160(hex_literal::hex!(
"f24ff3a9cf04c71dbc94d0b566f7a27b94566cac"
)),
private_key: H256(hex_literal::hex!(
"5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133"
)),
mnemonic:
"bottom drive obey lake curtain smoke basket hold race lonely fit walk"
.into(),
derivation_path: "m/44'/60'/0'/0/0".parse().unwrap(),
},
),
(
1,
KeyData {
account: H160(hex_literal::hex!(
"3cd0a705a2dc65e5b1e1205896baa2be8a07c6e0"
)),
private_key: H256(hex_literal::hex!(
"8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b"
)),
mnemonic:
"bottom drive obey lake curtain smoke basket hold race lonely fit walk"
.into(),
derivation_path: "m/44'/60'/0'/0/1".parse().unwrap(),
},
),
];

for (account_index, expected_key_info) in cases {
let key_info = KeyData::from_dev_seed(account_index);
assert_eq!(key_info, expected_key_info);
}
}
}
6 changes: 2 additions & 4 deletions crates/humanode-peer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ default-run = "humanode-peer"
bioauth-flow-rpc = { version = "0.1", path = "../bioauth-flow-rpc" }
bioauth-keys = { version = "0.1", path = "../bioauth-keys" }
crypto-utils = { version = "0.1", path = "../crypto-utils" }
crypto-utils-evm = { version = "0.1", path = "../crypto-utils-evm" }
humanode-rpc = { version = "0.1", path = "../humanode-rpc" }
humanode-runtime = { version = "0.1", path = "../humanode-runtime" }
keystore-bioauth-account-id = { version = "0.1", path = "../keystore-bioauth-account-id" }
Expand All @@ -22,7 +23,6 @@ robonode-client = { version = "0.1", path = "../robonode-client" }

anyhow = "1"
async-trait = "0.1"
bip32 = "0.5.0"
clap = { version = "4.1", features = ["derive"] }
codec = { package = "parity-scale-codec", version = "3.2.2" }
fc-cli = { git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" }
Expand All @@ -42,7 +42,6 @@ frame-system-rpc-runtime-api = { git = "https://github.com/humanode-network/subs
futures = "0.3"
hex = "0.4.3"
hex-literal = "0.4"
libsecp256k1 = "0.7"
pallet-balances = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
pallet-dynamic-fee = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" }
pallet-im-online = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
Expand All @@ -67,7 +66,6 @@ sc-transaction-pool = { git = "https://github.com/humanode-network/substrate", b
sc-utils = { git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha3 = "0.10"
sp-api = { git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
sp-application-crypto = { git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
sp-consensus = { git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
Expand All @@ -82,7 +80,7 @@ sp-panic-handler = { git = "https://github.com/humanode-network/substrate", bran
sp-runtime = { git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
sp-timestamp = { git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
thiserror = "1"
tiny-bip39 = "1.0"
tiny-bip39 = "1"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
try-runtime-cli = { git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38", optional = true }
Expand Down
23 changes: 16 additions & 7 deletions crates/humanode-peer/src/chain_spec.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Provides the [`ChainSpec`] portion of the config.
use crypto_utils::{authority_keys_from_seed, evm_account_from_seed, get_account_id_from_seed};
use crypto_utils::{authority_keys_from_seed, get_account_id_from_seed};
use frame_support::BoundedVec;
use hex_literal::hex;
use humanode_runtime::{
Expand All @@ -15,7 +15,6 @@ use sc_chain_spec_derive::{ChainSpecExtension, ChainSpecGroup};
use sc_service::ChainType;
use serde::{Deserialize, Serialize};
use sp_consensus_babe::AuthorityId as BabeId;
use sp_core::H160;
use sp_finality_grandpa::AuthorityId as GrandpaId;
use sp_runtime::{app_crypto::sr25519, traits::Verify};

Expand Down Expand Up @@ -49,8 +48,9 @@ pub fn authority_keys(seed: &str) -> (AccountId, BabeId, GrandpaId, ImOnlineId)
}

/// Generate an EVM account from seed.
pub fn evm_account_id(seed: &str) -> EvmAccountId {
H160::from_slice(&evm_account_from_seed(seed))
pub fn evm_account_id_from_dev_seed(account_index: u32) -> EvmAccountId {
let key_data = crypto_utils_evm::KeyData::from_dev_seed(account_index);
key_data.account
}

/// The default Humanode ss58 prefix.
Expand Down Expand Up @@ -126,7 +126,10 @@ pub fn local_testnet_config() -> Result<ChainSpec, String> {
account_id("Eve//stash"),
account_id("Ferdie//stash"),
],
vec![evm_account_id("EvmAlice"), evm_account_id("EvmBob")],
vec![
evm_account_id_from_dev_seed(0),
evm_account_id_from_dev_seed(1),
],
robonode_public_key,
vec![account_id("Alice")],
)
Expand Down Expand Up @@ -172,7 +175,10 @@ pub fn development_config() -> Result<ChainSpec, String> {
account_id("Alice//stash"),
account_id("Bob//stash"),
],
vec![evm_account_id("EvmAlice"), evm_account_id("EvmBob")],
vec![
evm_account_id_from_dev_seed(0),
evm_account_id_from_dev_seed(1),
],
robonode_public_key,
vec![account_id("Alice")],
)
Expand Down Expand Up @@ -223,7 +229,10 @@ pub fn benchmark_config() -> Result<ChainSpec, String> {
account_id("Alice//stash"),
account_id("Bob//stash"),
],
vec![evm_account_id("EvmAlice"), evm_account_id("EvmBob")],
vec![
evm_account_id_from_dev_seed(0),
evm_account_id_from_dev_seed(1),
],
robonode_public_key,
vec![account_id("Alice")],
)
Expand Down
2 changes: 1 addition & 1 deletion crates/humanode-peer/src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ pub async fn run() -> sc_cli::Result<()> {
.async_run(|config| async move { cmd.run(config.bioauth_flow).await })
.await
}
Some(Subcommand::Ethereum(cmd)) => cmd.run().await,
Some(Subcommand::Evm(cmd)) => cmd.run().await,
Some(Subcommand::Benchmark(cmd)) => {
let cmd = &**cmd;
let runner = root.create_humanode_runner(cmd)?;
Expand Down
Loading

0 comments on commit 431247a

Please sign in to comment.