Note

How to Convert Mnemonic to ETH Start your Ether journey by installing MetaMask, creating mnemonics,

How to Convert Mnemonic to ETH Address in Rust?

Intro

How did you start your journey to Ether? Install the Metamask plugin, create a set of mnemonics, copy it on a piece of paper and click the confirm button! Congratulations you have your Ether account, you can copy your address and send him to someone else who can use it to receive Ether. But don’t lose your mnemonic word and your private key, otherwise, someone else will steal your assets.

So are you wondering how those 12 mnemonics became your private key, your address? Congratulations you have come to the right place, this article will take you step by step to turn your set of mnemonics into an address using the Rust language.

Step 1 — Generate Mnemonic

Mnemonic is a list of words, which is easy to remember and write.And do you think that mnemonic is generated randomly?Hesitate, it is not completely random. Let me explain.

Each word in the mnemonic can be represented by a number from 0 to 2047, total 2048 numbers. Such as “indoor” is 920, “dish” is 505, “abandon” is 0. You can get more information from the BIP39 wordlist. And then we can convert the mnemonic to a binary number, such as “indoor” is 920, the binary number is 1110011000. Each of them is 11 bits.

In other words we can randomly generate a string of 01 combinations as mnemonic’s material. And the length of the string is a multiple of 32.Also that means the length of the mnemonic is a multiple of 3, but maximum 24 words. The more words, the more secure.

To make sure the Raw Binary is valid, we need to calculate the checksum of the Raw Binary. The checksum is the first several bits of the SHA256 hash of the Raw Binary. Then we get a binary length of 132 bits, and we can convert it to a 12 words mnemonic.

fn step1_generate_mnemonic() {
    // generate mnemonic
    let entropy = &[
        0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84, 0x6A,
        0x79,
    ];
    let mnemonic = Mnemonic::from_entropy(entropy, Language::English).unwrap();

    // let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
    // let mnemonic: Mnemonic = Mnemonic::from_phrase(
    //     "indoor dish desk flag debris potato excuse depart ticket judge file exit", // It will be unvalid, if you change any word in it.
    //     Language::English,
    // )
    // .unwrap();
    let phrase = mnemonic.phrase();
    println!("Generated Mnemonic: {}", phrase);
}

Example

mnemonic = Raw Binary + checksum
checksum = SHA256(Raw Binary)[:len(01_string)/32]

Mnemonic: indoor dish desk flag debris potato excuse depart ticket judge file exit
Raw Binary: 01110011000 00111111001 00111011111 01011000001 00111000011 10101000110 01001111000 00111010110 11100001101 01111000101 01010110001 01001111111
Checksum: 1111 (Tail 4(128/32) bits of SHA256(Raw Binary[0:128])

Step 2 — Mnemonic to Seed

To convert a mnemonic to a seed, we need to use the PBKDF2 function with the mnemonic as the password and the string “mnemonic” + passphrase as the salt.

The main function of PBKDF2 is to convert a password into an encryption key. Unlike traditional one-shot hash functions, PBKDF2 generates the key by combining the password with a salt (Salt) and repeatedly applying the hash function several times.

Normally, the number of iterations is set to 2048 and HMAC-SHA512 is used as the hash function to make it difficult to brute force the seed.So the length of the seed is 512 bits (64 bytes).

fn step2_mnemonic_to_seed(mnemonic: &Mnemonic) -> String {
    let seed = Seed::new(mnemonic, "");
    seed.as_bytes();
    hex::encode(seed.as_bytes())
}

Step 3 — seed to master

BIP32

Now we need to dive into the BIP32 — Hierarchical Deterministic Wallets. The main purpose of HDW is to manage the wallet better. Only one seed can recover all the wallet generated by it. And with HDW, each time we make a transaction we can use a new address to better ensure anonymity. While BIP32 was originally designed for Bitcoin, its principles can be applied to other cryptocurrencies, allowing the same seed to generate wallet addresses for multiple currencies. All the hierarchy comes from master, so let’s check it now.

fn step3_seed_to_master_key(seed_hex: &String) -> (String, String) {
    let seed_bytes = hex::decode(seed_hex).unwrap();

    let key = hmac::Key::new(hmac::HMAC_SHA512, b"Bitcoin seed");
    let tag = hmac::sign(&key, &seed_bytes);

    let (il, ir) = tag.as_ref().split_at(32);
    (hex::encode(il), hex::encode(ir))
}

With master key we can derive child keys with different derive paths.

Step 4 — master key to private key

With master key and chain code, we can derive the private key for the first account now.

Let’s check the derivation path first:

m / purpose' / coin_type' / account' / change / address_index

m is the master key,

The purpose is 44' for BIP44, and the coin type is 0' for Bitcoin, 60' for Ethereum.

The account is the index of the account, starting from 0. You can define 0 as the main account for daily use, and 1 as the account for donate or anything else.

The Change field is used to differentiate between internal and external chains.

The Address Index is the index of the address in the chain. You can use it to generate multiple addresses.

To get the private key, we need to derive the private key from the master key with the path of each level.

the derivation function do things below:

// CKDpriv((key_parent, chain_code_parent), i) -> (child_key_i, child_chain_code_i)
// `i` is the level number.
// CKDpriv: child key derivation (private)
pub fn derive(
    child_number: u32,
    private_key: SecretKey,
    chain_code: [u8; 32],
) -> (SecretKey, [u8; 32]) {
    println!("child_number: {:?}", child_number);

    let child_fingerprint = fingerprint_from_private_key(private_key.clone());
    println!("child_fingerprint: {:?}", hex::encode(child_fingerprint));

    let derived = derive_ext_private_key(private_key.clone(), &chain_code, child_number);
    let private_key = derived.0;
    let chain_code = derived.1;

    println!("private_key: {:?}", hex::encode(private_key.as_ref()));
    println!("chain_code: {:?}\n", hex::encode(chain_code));

    (private_key, chain_code)
}

Itereate the derivation function with the path, we can get the private key for the first account with the path `m/44'/ 60'/ 0'/ 0/ 0`

One more thing need to be mentioned is Apostrophe in the path indicates that BIP32 hardened derivation is used. such as 44' is a hardened derivation, while 44 is not. And 44' actually means 2³¹+44, which is a hardened derivation.

“Hardening” in BIP32 increases the security of derived keys by making it impossible to derive other child keys using just a public key and a child key, effectively preventing potential attackers from accessing your key hierarchy.

fn step3_master_kay_to_private_key(
    master_secret_key_hex: String,
    master_chain_code_hex: String,
    derived_path: [u32; 5],
) -> String {
    let master_secret_key_vec = hex::decode(master_secret_key_hex).unwrap();
    let master_secret_key: &[u8] = master_secret_key_vec.as_ref();
    let master_code_vec: Vec<u8> = hex::decode(master_chain_code_hex).unwrap();
    let master_code: &[u8] = master_code_vec.as_ref();

    let private_key = derive_with_path(
        SecretKey::from_slice(master_secret_key.clone()).unwrap(),
        master_code.try_into().unwrap(),
        &derived_path,
    );
    hex::encode(private_key.as_ref())
}

Step 5 — private key to public key

Hard part is done, we can get the public key from the private key.

The public key is a point on the elliptic curve, and it is generated by multiplying the private key with the generator point.

fn step5_private_key_to_public_key(private_key_hex: String) -> String {
    let private_key_vec = hex::decode(private_key_hex).unwrap();
    let private_key = SecretKey::from_slice(private_key_vec.as_ref()).unwrap();
    let public_key = curve_point_from_int(private_key);
    hex::encode(serialize_curve_point(public_key))
}

Step 6 — public key to address

The address is the last 20 bytes of the Keccak-256 hash of the public key.

fn step6_public_key_to_address(pub_key_hex: String) -> String {
    let public_key_vec = hex::decode(pub_key_hex).unwrap();
    let public_key = PublicKey::from_slice(public_key_vec.as_ref()).unwrap();
    let serialized_pub_key = public_key.serialize_uncompressed();
    let public_key_bytes = &serialized_pub_key[1..];
    let mut hasher = Keccak::v256();
    hasher.update(public_key_bytes);
    let mut output = [0u8; 32];
    hasher.finalize(&mut output);

    let address = &output[12..];
    hex::encode(address)
}

Reference

https://wolovim.medium.com/ethereum-201-mnemonics-bb01a9108c38

Tool

https://iancoleman.io/bip39/

0
0
...
...
...
Avatar