bip32 & bip44 & slip10
通过上一节 bip39 中定义的助记词,我们可以产生一个种子。种子可以派生秘钥,秘钥可以派生地址。 这样,web3 的分层钱包系统也就建立起来了。
bip32
最开始提出的是 bip32 的标准,通过递归的方式,可以产生一个秘钥树。 但是, BIP32 并没有定义明确的路径结构,路径只要符合一个简单的字符串即可。 比如: m/0/1/2 各模块之间可以随意定义,所以,如果在一个钱包中支持多币种,BIP32 没有提供一个可靠的方案。 为了解决这个问题,提出了 bip44 的标准,给各个模块定义了标准。
bip44
所以,在 bip32 的基础上,提出了 bip44 的标准。同时,也给出了各个模块的定义标准:
一个实例 : m/44’/60’/0’/0/0
各个部分定义如下:
- m/44’ 表示使用 bip44 标准
- 60’ 表示使用以太坊
- 0’ 表示使用主网
- 0 表示使用第一个账户
- 0 表示使用第一个地址
程序实例
通过 bip39 的助记词,借助 bip44 标准,派生 evm 地址
安装基础类库:
cargo add bip32
cargo add sha3
以下是一个,通过 bip44 派生 evm 地址的实例。
use { bip32::{DerivationPath, PublicKey, XPrv}, bip39::Mnemonic, hex, sha3::Digest, std::str::FromStr, }; fn main() { // let mut rng = rand::thread_rng(); // let words = Mnemonic::generate_in_with(&mut rng, Language::English, 12).unwrap(); let words = Mnemonic::from_str( "giant fever unveil bench mass tourist green spoon song scissors goat thumb", ) .unwrap(); println!("current words: {}", words.to_string()); // use the words to generate a evm address let seeds = words.to_seed(""); // let root_xprv = XPrv::new(&seeds).unwrap(); let evm_derive_path = DerivationPath::from_str("m/44'/60'/0'/0/0").unwrap(); // Xprv is ExtendedPrivateKey<k256::ecdsa::SigningKey>, so you can get it by calling private_key() let evm_xprv = XPrv::derive_from_path(seeds, &evm_derive_path).unwrap(); let evm_public_key = evm_xprv.private_key().verifying_key(); println!("evm public key: {}", hex::encode(evm_public_key.to_bytes())); let mut kh = sha3::Keccak256::new(); kh.update(&evm_public_key.to_encoded_point(false).as_bytes()[1..]); let hash = kh.finalize().to_vec(); let evm_address = &hash[12..]; println!("evm address: 0x{}", hex::encode(evm_address)); }
部分代码说明:
- DerivationPath::from_str 可以定义推导路径,这部分,不同的币种有不同的推导路径。
- 通过 XPrv::derive_from_path 可以派生出私钥,这部分是一组扩展秘钥,可以转化为各种需要的秘钥格式。实例中 Xprv 是 ExtendedPrivateKeyk256::ecdsa::SigningKey 类型,所以,可以调用 private_key() 获取私钥。
- 通过 private_key() 可以获取私钥,通过 verifying_key() 可以获取公钥。
- 通过公钥,进行 keccak256 哈希运算,获取 evm 地址。
SLIP10
很多公链(比如 SUI、Aptos、Solana)采用的是 ed25519 算法,原始的 bip32 并没有定义 ed25519 的秘钥派生方式。 SLIP10 是 BIP32 的改进版,定义了 ed25519 的秘钥派生方式。生成代码逻辑如下:
#![allow(unused)] fn main() { use bip32::DerivationPath; use hmac::{Hmac, Mac}; use sha2::Sha512; pub fn derive_ed25519_private_key_by_path(seed: &[u8], path: DerivationPath) -> [u8; 32] { let indexes = path .into_iter() .map(|i: bip32::ChildNumber| i.into()) .collect::<Vec<_>>(); derive_ed25519_private_key(seed, &indexes) } #[allow(non_snake_case)] fn derive_ed25519_private_key(seed: &[u8], indexes: &[u32]) -> [u8; 32] { let mut I = hmac_sha512(b"ed25519 seed", &seed); let mut data = [0u8; 37]; for i in indexes { let hardened_index = 0x80000000 | *i; let Il = &I[0..32]; let Ir = &I[32..64]; data[1..33].copy_from_slice(Il); data[33..37].copy_from_slice(&hardened_index.to_be_bytes()); //I = HMAC-SHA512(Key = Ir, Data = 0x00 || Il || ser32(i')) I = hmac_sha512(&Ir, &data); } I[0..32].try_into().unwrap() } pub fn hmac_sha512(key: &[u8], data: &[u8]) -> [u8; 64] { type HmacSha512 = Hmac<Sha512>; let mut hmac = HmacSha512::new_from_slice(key).expect("HMAC can take key of any size"); hmac.update(data); let result = hmac.finalize(); result.into_bytes().try_into().unwrap() } }