关于将助记词转换为 ETH 地址,你所需要知道的一切
LXDAO
2024-06-28 17:57
订阅此专栏
收藏此文章
本文旨在通过使用 Rust 语言带你一步步将助记词转化为 ETH 地址,以帮助想了解和从事区块链技术开发的伙伴加深对区块链系统和加密原理的理解。


撰文:0xhardman


01 介绍


让我们回忆一下,你如何开始你的以太坊之旅的?安装 Metamask 插件,创建助记词,将它抄写到一张纸上,然后点击确认按钮!恭喜你有了自己的以太坊账户,你可以复制你的钱包地址并将其发送给其他人,让他们用它来接收以太币。


但是你是否有这样的问题?


  1. 可以从词典中创建自己的助记词吗?
  2. 为什么助记词很重要?为什么我不能把我的助记词送给别人?
  3. 我可以用我的 ETH 私钥生成我的 BTC 地址吗?
  4. 这 12 个助记词是如何变成你的私钥和地址的?


恭喜你来对地方了!本文将带你一步步使用 Rust 语言将助记词转化为地址。了解助记词到地址之间的过程,不仅能加深对区块链系统和加密原理的理解,还能提高就业机会,满足相关开发需求,提高效率。因此,对于希望深入了解和从事区块链技术开发的人来说,这些知识至关重要,不可或缺。


02 为什么选择 Rust


Rust 是由 Mozilla 开发的一种通用编译编程语言,因其可靠性和高效性越来越受到开发人员的青睐。因此,我相信未来以太坊生态系统中会有越来越多的项目使用 Rust 进行重构和升级。以太坊生态系统中还出现了许多基于 Rust 的知名项目,例如:


  • Foundry:用于以太坊应用程序开发的快速、可移植和模块化工具包。
  • Reth:用于以太坊协议的模块化、对贡献者友好且速度极快的实现。


让我们尽快了解以太坊中的 Rust!


03 不是开发者?


步骤 1 - 生成助记词


助记词是一串便于记忆和书写的词 / 字表。你以为助记词是随机生成的吗?其实它并不是完全随机的。让我们深入看看。


助记词中的每个词都可以用 0 到 2047 之间的一个数字来表示,共 2048 个数字。您可以从 BIP39 词表中获取更多信息。另一个有趣的事情是,我们不仅可以使用英语助记词,还可以使用简体中文、繁体中文、日语、韩语和西班牙语助记词。例如:


英语:indoor dish desk flag debris potato excuse depart ticket judge file exit
韩语:수집 몸속 명의 분야 만족 인격 법원 멀리 터미널 시멘트 부작용 변명
中文简体:诗 失 圆 块 亲 幼 杂 却 厉 齐 顶 互
中文繁体:詩 失 圓 塊 親 幼 雜 卻 厲 齊 頂 互


回看问题 1:可以从词典中创建自己的助记词吗


不能。


可以用作助记词的单词 / 文字数量有限,且最后一个词是「校验码」,即:最后一个词是前面所有词的计算结果,和前面十一个词有对应关系。实际上,我们身份证号的最后一位也是「校验码」,助记词也与之类似。


接下来我们可以把助记词转换成二进制数,比如「indoor」是 920,二进制数就是 1110011000。每个二进制数都是 11 位。


助记词(Mnemonic)indoor dish desk flag debris potato excuse depart ticket judge file exit 

二进制下的助记词(MnemonicBinary)01110011000 00111111001 00111011111 01011000001 00111000011 10101000110 01001111000 00111010110 11100001101 01111000101 01010110001 01001111111 

二进制下的熵(Entropy)01110011000 00111111001 00111011111 01011000001 00111000011 10101000110 01001111000 00111010110 11100001101 01111000101 01010110001 0100111 

十六进制熵(EntropyInHex)7307e4efac13875193c1d6e1af1558a7 

二进制下的校验码(CheckSum)1111 

熵的二进制长度(lengthOfEntropy)128 

助记词的二进制长度(lengthOfMnemonicBinary)132 

助记词 = 熵 + 校验码 

校验码 = SHA256(entropy)[0:len(entropy)/32]=SHA256(entropy)[0:4]=


换句话说,我们可以随机生成一串 01 组合作为助记词的材料,也就是熵。熵的长度是 32 的倍数。在 12 个助记词的情况下,熵的长度为 128。


这也意味着助记词的长度是 3 的倍数,但最多不超过 24 个词。词数越多越安全。


为了确保助记词有效,我们需要计算助记词的校验和。校验和是熵的 SHA256 哈希值的前几位。接下来我们就得到二进制长度为 132 位,可以将其转换为 12 个助记词(11 位一个词)。


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);

}


步骤 2 - 助记词到种子


要将助记词转换为种子,我们需要使用 PBKDF2 函数,将助记词作为密码,将字符串「助记词」和口令作为盐值。


PBKDF2 的主要功能是将密码转换成加密密钥。与传统的单次散列函数不同,PBKDF2 通过将密码与盐值结合并多次重复应用散列函数来生成密钥。


通常情况下,迭代次数设置为 2048 次,并使用 HMAC-SHA512 作为散列函数,提高暴力破解的难度。SHA512 可以理解为生成 512 位哈希值的函数,也因此种子长度为 512 位(64 字节)。


fn step2_mnemonic_to_seed(mnemonic: &Mnemonic) -> String {

let seed = Seed::new(mnemonic, "");

seed.as_bytes();

hex::encode(seed.as_bytes())

}

// return 3bd0bda567d4ea90f01e92d1921aacc5046128fd0e9bee96d070e1d606cb79225ee3e488bf6c898a857b5f980070d4d4ce9adf07d73458a271846ef3a8415320


第 3 步 - 将种子转入主密钥


BIP32


现在,我们需要深入了解 BIP32 - Hierarchical Deterministic Wallets(分层确定性钱包:https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)。HDW 的主要目的是更好地管理钱包。只有一个种子可以恢复由它生成的所有钱包。有了 HDW,我们每次进行交易时都可以使用一个新地址,从而更好地确保匿名性。虽然 BIP32 最初是为比特币设计的,但其原理可以应用于其他加密货币,允许同一种子为多种货币生成多个钱包地址。所有的层次结构都来自主密钥,现在就让我们仔细看看。


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))

}

// return 5e01502044f205b98ba493971561284565e41f34f03494bb521654b0c35cb3a9 bccd1f17319e02baa4b2688f5656267d2eeaf8b49a49607e4b37efe815629c82


通过主密钥,我们可以用不同的派生路径派生出子密钥。


回看问题 2:为什么助记词很重要?为什么我不能把我的助记词送给别人?


助记词是找回你所有钱包的关键。如果你丢失了私钥,您将无法访问您的资产。如果您泄露了助记词,由该助记词衍生的账户中的所有资产都可能被恶意者窃取。


步骤 4 - 主密钥到私钥


有了主密钥和链代码,我们就可以为第一个账户导出私钥了。


让我们先检查一下派生路径:


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


  1. m 是主密钥
  2. purpose 为 44',表示 BIP44;coin type 为 0',表示比特币;60',表示以太坊
  3. account 是账户的索引,从 0 开始。您可以将 0 定义为日常使用的主账户,将 1 定义为捐赠或其他用途的账户
  4. change 字段用于区分内部链和外部链
  5. address 是链上地址的索引。你可以用它来生成多个地址


要获取私钥,我们需要从主密钥中派生私钥和每一级的路径参数。


派生函数的作用如下:


// 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_with_path(

master_private_key: SecretKey,

master_chain_code: [u8; 32],

path_numbers: &[u32; 5],

) -> SecretKey {

let mut depth = 0;


let mut child_number: Option<u32> = None;

let mut private_key = master_private_key;

let mut chain_code = master_chain_code;


for &i in path_numbers {

depth += 1;

println!("depth: {}", depth);


child_number = Some(i);

println!("child_number: {:?}", child_number);


(private_key, chain_code) = derive(child_number.unwrap(), private_key, chain_code);

}

private_key

}



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)

}


// Calculate the fingerprint for a private key.

pub fn fingerprint_from_private_key(k: SecretKey) -> [u8; 4] {

let pk = curve_point_from_int(k);


// Serialize the public key in compressed format

let pk_compressed = serialize_curve_point(pk);


// Perform SHA256 hashing

let sha256_result = digest::digest(&digest::SHA256, &pk_compressed);


// Perform RIPEMD160 hashing


let ripemd_result = ripemd160::Hash::hash(sha256_result.as_ref());


// Return the first 4 bytes as the fingerprint

ripemd_result[0..4].try_into().unwrap()

}


// Derived ExtPrivate key

pub fn derive_ext_private_key(

private_key: SecretKey,

chain_code: &[u8],

child_number: u32,

) -> (SecretKey, [u8; 32]) {

let key = hmac::Key::new(hmac::HMAC_SHA512, chain_code);


let mut data = if child_number >= (1 << 31) {

[&[0u8], &private_key[..]].concat()

} else {

let p = curve_point_from_int(private_key);

serialize_curve_point(p)

// private_key.as_ref().to_vec()

};

data.extend_from_slice(&child_number.to_be_bytes());


let hmac_result = hmac::sign(&key, &data);


let (l, r) = hmac_result.as_ref().split_at(32);


let l = (*l).to_owned();

let r = (*r).to_owned();


let mut l_32 = [0u8; 32];

l_32.clone_from_slice(&l);


let private_byte = private_key.as_ref();


let l_secret = SecretKey::from_slice(&l).unwrap();

let child_private_key = l_secret

.add_tweak(&Scalar::from_be_bytes(*private_byte).unwrap())

.unwrap();

let child_chain_code = r;


(child_private_key, child_chain_code.try_into().unwrap())

}


derive_with_path 函数以派生路径(path_numbers)、主密钥(master_master_key)、主链码(master_chain_code)迭代调用派生函数(derive),计算出对应账户的私钥。比如,我们可以通过路径 "m/44'/ 60'/ 0'/ 0/ 0 "获取第一个账户的私钥,通过路径 "m/44'/ 60'/ 0'/ 0/ 1 "获取第二个账户的私钥。


回看问题 3:我可以用我的 ETH 私钥生成我的 BTC 地址吗?


不能。比特币的派生路径是 m/44'/0'/0'/0,ETH 的派生路径是 m/44'/60'/0'/0/0。我们不能用子私钥计算父私钥,这也意味着,如果我们有了主密钥,我们就可以计算所有链上的所有钱包。


还有一点需要说明的是,路径中的撇号表示使用了 BIP32 强化派生,如 44' 是强化派生,而 44 不是。而 44' 的实际意思是 2³¹+44。


BIP32 中的「强化」可提高派生密钥的安全性,使其无法仅使用一个公开密钥和一个子密钥来派生其他子密钥,从而有效防止潜在攻击者访问你的密钥层次结构。


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())

}


步骤 5:私钥到公钥


困难的部分已经结束了,现在我们可以从私钥中获取公钥啦!


公钥是椭圆曲线上的一个点,通过将私钥与生成点相乘来生成。


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))

}


步骤 6 - 公钥地址


地址是公钥 Keccak-256 哈希值的最后 20 个字节。


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)

}


回望问题 4:这 12 个助记词是如何变成你的私钥和地址的?


总之,助记词由熵生成,然后通过 PBKDF2 转换成种子。种子用于通过 HMAC-SHA512 生成主密钥和链码。通过特定路径,从主密钥中提取私钥。最后,私钥生成公钥,公钥的 Keccak-256 哈希值的最后 20 个字节构成以太坊地址。


04 推荐工具


BIP39 - Mnemonic Code:

https://iancoleman.io/bip39/


如果你想直接获取到将助记词转换为 ETH 地址的步骤详情,也可见👇


0xhardman/rust-mnemonic-to-address:

https://github.com/0xhardman/rust-mnemonic-to-address


参考资料
[1]Ethereum 201: MnemonicsA guided tour of BIP 39 mnemonic words and seed generation with Python exampleswolovim.medium.com
[2]Ethereum 201: HD WalletsFrom mnemonic words to public address — a walkthrough of BIP 32 and BIP 44 with Python examples.wolovim.medium.com
[3]How to Convert Mnemonic (12 Word) to Private Key & Address Wallet Bitcoin and EthereumConvert Mnemonic 12 Word to Private Key Hex (SHA256) and Address Bitcoin and Ethereum Wallet.mdrza.medium.com

【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。

LXDAO
数据请求中
查看更多

推荐专栏

数据请求中
在 App 打开