
Geth 源码系列:存储设计及实现
在区块链的世界中,状态存储是每一个节点的「记忆核心」 — — 它记录着亿万账户的余额、合约的代码、交易的痕迹,甚至决定着一笔交易能否被正确执行。作为以太坊生态的基石,Geth 客户端如何以精密的架构设计承载海量状态数据?其存储系统如何在性能、安全与可扩展性之间找到平衡? 这篇文章是 Geth 源码系列的第二篇,通过这个系列,我们将搭建一个研究 Geth 实现的框架,开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章,在这第二篇文章中,将系统讲解 Geth 的存储结构设计与相关源码,介绍其数据库层次划分并详细分析各个层次中相应模块的核心功能。 以太坊作为全球最大的区块链平台,其主流客户端 Geth(Go-Ethereum)承担了绝大部分节点运行与状态管理的职责。Geth 的状态存储系统,是理解以太坊运行机制、优化节点性能、以及推动未来客户端创新的基础。 本文作者: po Web3buidler.tech Core Contributor, EthStorage Engineer Geth 底层数据库总览 自 Geth v1.9.0 版本起,Geth 将其数据库分为...

以太坊单独质押实战教程:手把手教你完成部署
内容丨Ray 编辑 & 排版丨Yewlne、环环 以太坊的单独质押(Solo Staking)是保护网络安全和去中心化的黄金标准。通过运行自己的验证者节点,您可以直接参与网络共识,获得完整的质押奖励,并保持对资金的完全控制。本教程将为您提供详细的指导,帮助您在家中部署以太坊单独质押节点,确保安全性和稳定性。前提概要首先,我们需要有这样的一个基本概念:要处理来自执行层的传入验证器存款,您需要运行执行客户端以及共识客户端。这意味着我们至少需要部署两个客户端程序(在本教程中,为三个,共识客户端按照功能进行了拆分)。如果想了解更多执行客户端和共识客户端的差异及功能职责,可以点击阅读: https://ethereum.org/zh/developers/docs/networking-layer/一、选择执行客户端以太坊生态的客户端种类非常丰富,执行客户端和共识客户端都有多种不同语言的实现,如使用 Go 语言实现的 Geth, 使用 Rust 语言实现的 Reth 等。你可以根据自己喜好,选择不同的客户端,整体流程是类似的,只是不同客户端实现涉及的命令不同。在这里,我们不能对所有的客户端...

重磅 | LXDAO 中文名正式从“良心 DAO”改为“蓝翔 DAO”!
撰文 | Bruce 编辑&排版 | Connie 图片 | Ache、Connie、Cikey重磅消息! LXDAO 今日起中文名正式从“良心 DAO”改为“蓝翔 DAO”! 让我们抓紧看看到底怎么个事儿——Web3 技术哪家强?中国就找 LXDAO!LXDAO 创办于 2022 年 6 月,至今已有快 2 年的历史,是由几位喜欢开源的开发者联合发起的一所现代化、综合性的产学研一体的 DAO 组织,主要从事 Web3 公共物品相关的教育、研发和维护。 LXDAO 以创办历史不太悠久,但实训项目齐全、师资力量雄厚、培养模式先进而闻名全国。LXDAO 超过一半的同学为程序员,是现代化 Web3 技师、高级技工的摇篮。学院概况LXDAO 现有 0 个校区(正在筹备在清迈四海搭建第一个线下校区)。我们主要在 Internet 远程学习和工作交流。面向全球 13810 个城市招生,不限语言、地区和种族,能同时容纳上百万人。 LXDAO 拥有丰富的实训项目,涵盖了 Web3 的多种主题,包括但不限于教育、公共物品、全链游戏、开源、开发者关系维护、商务合作等。 我们也跟许多国际单位联合开展一...
LXDAO 是一个专注研发的 DAO 组织,致力于构建支持有价值的公共物品和开源项目的无限循环。 LXDAO is an R&D-focused DAO dedicated to building an Infinite Cycle that supports valuable

Geth 源码系列:存储设计及实现
在区块链的世界中,状态存储是每一个节点的「记忆核心」 — — 它记录着亿万账户的余额、合约的代码、交易的痕迹,甚至决定着一笔交易能否被正确执行。作为以太坊生态的基石,Geth 客户端如何以精密的架构设计承载海量状态数据?其存储系统如何在性能、安全与可扩展性之间找到平衡? 这篇文章是 Geth 源码系列的第二篇,通过这个系列,我们将搭建一个研究 Geth 实现的框架,开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章,在这第二篇文章中,将系统讲解 Geth 的存储结构设计与相关源码,介绍其数据库层次划分并详细分析各个层次中相应模块的核心功能。 以太坊作为全球最大的区块链平台,其主流客户端 Geth(Go-Ethereum)承担了绝大部分节点运行与状态管理的职责。Geth 的状态存储系统,是理解以太坊运行机制、优化节点性能、以及推动未来客户端创新的基础。 本文作者: po Web3buidler.tech Core Contributor, EthStorage Engineer Geth 底层数据库总览 自 Geth v1.9.0 版本起,Geth 将其数据库分为...

以太坊单独质押实战教程:手把手教你完成部署
内容丨Ray 编辑 & 排版丨Yewlne、环环 以太坊的单独质押(Solo Staking)是保护网络安全和去中心化的黄金标准。通过运行自己的验证者节点,您可以直接参与网络共识,获得完整的质押奖励,并保持对资金的完全控制。本教程将为您提供详细的指导,帮助您在家中部署以太坊单独质押节点,确保安全性和稳定性。前提概要首先,我们需要有这样的一个基本概念:要处理来自执行层的传入验证器存款,您需要运行执行客户端以及共识客户端。这意味着我们至少需要部署两个客户端程序(在本教程中,为三个,共识客户端按照功能进行了拆分)。如果想了解更多执行客户端和共识客户端的差异及功能职责,可以点击阅读: https://ethereum.org/zh/developers/docs/networking-layer/一、选择执行客户端以太坊生态的客户端种类非常丰富,执行客户端和共识客户端都有多种不同语言的实现,如使用 Go 语言实现的 Geth, 使用 Rust 语言实现的 Reth 等。你可以根据自己喜好,选择不同的客户端,整体流程是类似的,只是不同客户端实现涉及的命令不同。在这里,我们不能对所有的客户端...

重磅 | LXDAO 中文名正式从“良心 DAO”改为“蓝翔 DAO”!
撰文 | Bruce 编辑&排版 | Connie 图片 | Ache、Connie、Cikey重磅消息! LXDAO 今日起中文名正式从“良心 DAO”改为“蓝翔 DAO”! 让我们抓紧看看到底怎么个事儿——Web3 技术哪家强?中国就找 LXDAO!LXDAO 创办于 2022 年 6 月,至今已有快 2 年的历史,是由几位喜欢开源的开发者联合发起的一所现代化、综合性的产学研一体的 DAO 组织,主要从事 Web3 公共物品相关的教育、研发和维护。 LXDAO 以创办历史不太悠久,但实训项目齐全、师资力量雄厚、培养模式先进而闻名全国。LXDAO 超过一半的同学为程序员,是现代化 Web3 技师、高级技工的摇篮。学院概况LXDAO 现有 0 个校区(正在筹备在清迈四海搭建第一个线下校区)。我们主要在 Internet 远程学习和工作交流。面向全球 13810 个城市招生,不限语言、地区和种族,能同时容纳上百万人。 LXDAO 拥有丰富的实训项目,涵盖了 Web3 的多种主题,包括但不限于教育、公共物品、全链游戏、开源、开发者关系维护、商务合作等。 我们也跟许多国际单位联合开展一...
LXDAO 是一个专注研发的 DAO 组织,致力于构建支持有价值的公共物品和开源项目的无限循环。 LXDAO is an R&D-focused DAO dedicated to building an Infinite Cycle that supports valuable

Subscribe to LXDAO

Subscribe to LXDAO
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers



撰文丨0xhardman
编辑 & 排版丨Soleil
设计丨Daisy
本文由 LXDAO Buidler:0xhardman 撰写,旨在通过使用 Rust 语言带你一步步将助记词转化为 ETH 地址,以帮助想了解和从事区块链技术开发的伙伴加深对区块链系统和加密原理的理解。
让我们回忆一下,你如何开始你的以太坊之旅的?安装 Metamask 插件,创建助记词,将它抄写到一张纸上,然后点击确认按钮!恭喜你有了自己的以太坊账户,你可以复制你的钱包地址并将其发送给其他人,让他们用它来接收以太币。
但是你是否有这样的问题?1、可以从词典中创建自己的助记词吗?2、为什么助记词很重要?为什么我不能把我的助记词送给别人?3、我可以用我的 ETH 私钥生成我的 BTC 地址吗?4、这 12 个助记词是如何变成你的私钥和地址的?
恭喜你来对地方了!本文将带你一步步使用 Rust 语言将助记词转化为地址。了解助记词到地址之间的过程,不仅能加深对区块链系统和加密原理的理解,还能提高就业机会,满足相关开发需求,提高效率。因此,对于希望深入了解和从事区块链技术开发的人来说,这些知识至关重要,不可或缺。
Rust 是由 Mozilla 开发的一种通用编译编程语言,因其可靠性和高效性越来越受到开发人员的青睐。因此,我相信未来以太坊生态系统中会有越来越多的项目使用 Rust 进行重构和升级。以太坊生态系统中还出现了许多基于 Rust 的知名项目,例如:Foundry:用于以太坊应用程序开发的快速、可移植和模块化工具包。Reth:用于以太坊协议的模块化、对贡献者友好且速度极快的实现。
让我们尽快了解以太坊中的 Rust!
助记词是一串便于记忆和书写的词/字表。你以为助记词是随机生成的吗?其实它并不是完全随机的。让我们深入看看。
助记词中的每个词都可以用 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);
}
要将助记词转换为种子,我们需要使用 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

现在,我们需要深入了解 BIP32 - Hierarchical Deterministic Wallets(分层确定性钱包)。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:为什么助记词很重要?为什么我不能把我的助记词送给别人?助记词是找回你所有钱包的关键。如果你丢失了私钥,您将无法访问您的资产。如果您泄露了助记词,由该助记词衍生的账户中的所有资产都可能被恶意者窃取。
有了主密钥和链代码,我们就可以为第一个账户导出私钥了。
让我们先检查一下派生路径:
m / purpose' / coin_type' / account' / change / address_index
m 是主密钥
purpose 为 44',表示 BIP44;coin type 为 0',表示比特币;60',表示以太坊
account 是账户的索引,从 0 开始。您可以将 0 定义为日常使用的主账户,将 1 定义为捐赠或其他用途的账户
change 字段用于区分内部链和外部链
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())
困难的部分已经结束了,现在我们可以从私钥中获取公钥啦!
公钥是椭圆曲线上的一个点,通过将私钥与生成点相乘来生成。
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))
}
地址是公钥 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 个字节构成以太坊地址。
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

撰文丨0xhardman
编辑 & 排版丨Soleil
设计丨Daisy
本文由 LXDAO Buidler:0xhardman 撰写,旨在通过使用 Rust 语言带你一步步将助记词转化为 ETH 地址,以帮助想了解和从事区块链技术开发的伙伴加深对区块链系统和加密原理的理解。
让我们回忆一下,你如何开始你的以太坊之旅的?安装 Metamask 插件,创建助记词,将它抄写到一张纸上,然后点击确认按钮!恭喜你有了自己的以太坊账户,你可以复制你的钱包地址并将其发送给其他人,让他们用它来接收以太币。
但是你是否有这样的问题?1、可以从词典中创建自己的助记词吗?2、为什么助记词很重要?为什么我不能把我的助记词送给别人?3、我可以用我的 ETH 私钥生成我的 BTC 地址吗?4、这 12 个助记词是如何变成你的私钥和地址的?
恭喜你来对地方了!本文将带你一步步使用 Rust 语言将助记词转化为地址。了解助记词到地址之间的过程,不仅能加深对区块链系统和加密原理的理解,还能提高就业机会,满足相关开发需求,提高效率。因此,对于希望深入了解和从事区块链技术开发的人来说,这些知识至关重要,不可或缺。
Rust 是由 Mozilla 开发的一种通用编译编程语言,因其可靠性和高效性越来越受到开发人员的青睐。因此,我相信未来以太坊生态系统中会有越来越多的项目使用 Rust 进行重构和升级。以太坊生态系统中还出现了许多基于 Rust 的知名项目,例如:Foundry:用于以太坊应用程序开发的快速、可移植和模块化工具包。Reth:用于以太坊协议的模块化、对贡献者友好且速度极快的实现。
让我们尽快了解以太坊中的 Rust!
助记词是一串便于记忆和书写的词/字表。你以为助记词是随机生成的吗?其实它并不是完全随机的。让我们深入看看。
助记词中的每个词都可以用 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);
}
要将助记词转换为种子,我们需要使用 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

现在,我们需要深入了解 BIP32 - Hierarchical Deterministic Wallets(分层确定性钱包)。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:为什么助记词很重要?为什么我不能把我的助记词送给别人?助记词是找回你所有钱包的关键。如果你丢失了私钥,您将无法访问您的资产。如果您泄露了助记词,由该助记词衍生的账户中的所有资产都可能被恶意者窃取。
有了主密钥和链代码,我们就可以为第一个账户导出私钥了。
让我们先检查一下派生路径:
m / purpose' / coin_type' / account' / change / address_index
m 是主密钥
purpose 为 44',表示 BIP44;coin type 为 0',表示比特币;60',表示以太坊
account 是账户的索引,从 0 开始。您可以将 0 定义为日常使用的主账户,将 1 定义为捐赠或其他用途的账户
change 字段用于区分内部链和外部链
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())
困难的部分已经结束了,现在我们可以从私钥中获取公钥啦!
公钥是椭圆曲线上的一个点,通过将私钥与生成点相乘来生成。
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))
}
地址是公钥 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 个字节构成以太坊地址。
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
No activity yet