創建以太坊HD錢包
HD錢包 分層確定性錢包
先簡單介紹一下錢包的原理和組成:
每一個錢包賬戶包含一份密鑰對,即私鑰與公鑰。私鑰(k)是一個數字,通常是隨機選出的。有了私鑰,我們就可以使用橢圓曲線乘法這個單向加密函數生成一個公鑰(K)。有了公鑰(K),我們就可以使用一個單向加密哈希函數生成該賬戶地址(A)。
當你發生交易時,每筆交易都需要一個有效的簽名纔會被存儲在區塊鏈。只有有效的私鑰才能產生有效的數字簽名,因此擁有錢包賬戶的私鑰就擁有了該賬戶的支配權。
錢包的常見的形態
- Private Key
- keystore && Password
- Mnemonic Seed
市面上主流的都是第三種 生成助記詞的形式 創建或者導入生成錢包。
先簡單介紹一下錢包生成流程:
- 隨機生成128到258位的隨機數,我們這裏叫做熵;
- 熵經過一定處理方法,生成助記詞;
- 助記詞經過密鑰延伸函數PBKDF2,生成種子;
- 種子經過HMAC-SHA512算法,生成母密鑰
- 通過CKD(child key derivation)函數,母密鑰生成衆多子密鑰。
- 利用私鑰加密生成keystore
ps:私鑰通過橢圓曲線生成公鑰, 公鑰通過哈希函數生成地址,這兩個過程都是單向的
BIP協議
Bitcoin Improvement Proposals 比特幣改進建議
生成錢包,BIP協議我們一定要了解
- BIP32 通過一個隨機種子,提出的爲了避免管理一堆私鑰的麻煩提出的分層推導方案。
- BIP39 通過定義助記詞讓種子的備份更友好。(單詞總列表爲2048個單詞組成)
- BIP32 的分層增強了路徑定義規範,讓同一個 seed 可以支援多幣種、多帳戶
格式: m / purpose’ / coin_type’ / account’ / change / address_index
目前市面上的Ethereum 錢包均採用以上 Bitcoin HD Wallet 的架構,用的都是BIP44:,具體是這樣的m/44’/60’/0’/0/0
好了,上面這麼東西瞭解以後,那可以着手做一個錢包的項目了。
準備工作
創建錢包需要兩個庫
web3j
主要使用它來生成助記詞和seed,後續需要用此庫來進行交易(轉賬,部署合約,加載合約等)
implementation 'org.web3j:core:4.2.0-android'
通過前面生成的seed生成支持bip32和bip44的私鑰。
implementation "org.bitcoinj:bitcoinj-core:0.14.7"
依賴web3j項目安裝時會出現失敗錯誤:
Installation did not succeed.
The application could not be installed: INSTALL_FAILED_NO_MATCHING_ABIS
Installation failed due to: ‘null’
在gradle中android下添加packagingOptions,這樣就剔除了mips,mips64,x86_64架構,也就用不到對應的架構的工具鏈了
packagingOptions {
exclude 'lib/x86_64/darwin/libscrypt.dylib'
exclude 'lib/x86_64/freebsd/libscrypt.so'
exclude 'lib/x86_64/linux/libscrypt.so'
}
重點說一下 現在半數以上的文章都是以web3j種創建的方式,沒有采用BIP44,這種方式你創建出來的錢包地址或者導入助記詞產生的錢包地址,你會發現和Imtoken cobo metaMask這些錢包地址不一樣。
你會看到很多這樣的實現方式,但是就是和主流錢包統一不起來
public static String create(String pwd) {
StringBuilder sb = new StringBuilder();
byte[] entropy = new byte[Words.TWELVE.byteLength()];
new SecureRandom().nextBytes(entropy);
new MnemonicGenerator(English.INSTANCE).createMnemonic(entropy, sb::append);
String mnemonics = sb.toString();
System.out.println("+++助記詞:==" + mnemonics);
//生成種子
byte[] seed = MnemonicUtils.generateSeed(mnemonics, pwd);
//生成ECKeyPair
ECKeyPair ecKeyPair = ECKeyPair.create(seed);
String privateKey = Numeric.toHexStringWithPrefix(ecKeyPair.getPrivateKey());
System.out.println("+++私鑰==" + privateKey);
String publicKey = Numeric.toHexStringWithPrefix(ecKeyPair.getPublicKey());
System.out.println("+++公鑰==" + publicKey);
//根據公鑰和ecKeyPair獲取錢包地址
String address = Keys.getAddress(publicKey);
System.out.println("+++地址==" + address);
return address;
}
正確姿勢
- 生成或者導入助記詞
- 生成 seed
- 生成 master key
- 生成 child key
- 我們取第一組child key即m/44’/60’/0’/0/0 得到私鑰,keystore及地址
/**
* 產生助記詞
*/
public static String generateMnemonics() {
StringBuilder sb = new StringBuilder();
byte[] entropy = new byte[Words.TWELVE.byteLength()];
new SecureRandom().nextBytes(entropy);
new MnemonicGenerator(English.INSTANCE).createMnemonic(entropy, sb::append);
String mnemonics = sb.toString();
return mnemonics;
}
創建錢包
/**
*
* @param mnemonics 助記詞
* @param password 密碼(生成私鑰用)
* @return BabelWallet自己創建bean對象,方法返回
*/
public static BabelWallet generateBIP44Wallet(String mnemonics, String password) {
//2.生成種子
byte[] seed = MnemonicUtils.generateSeed(mnemonics, null);
//3. 生成根私鑰 root private key 樹頂點的master key ;bip32
DeterministicKey rootPrivateKey = HDKeyDerivation.createMasterPrivateKey(seed);
// 4. 由根私鑰生成 第一個HD 錢包
DeterministicHierarchy dh = new DeterministicHierarchy(rootPrivateKey);
// 5. 定義父路徑 H則是加強 imtoken中的eth錢包進過測試發現使用的是此方式生成 bip44
List<ChildNumber> parentPath = HDUtils.parsePath("M/44H/60H/0H/0");
DeterministicKey child = dh.deriveChild(parentPath, true, true, new ChildNumber(0));
byte[] privateKeyByte = child.getPrivKeyBytes();
//7.通過私鑰生成公私鑰對
ECKeyPair ecKeyPair = ECKeyPair.create(privateKeyByte);
//8.通過密碼和鑰匙對生成WalletFile也就是keystore的bean類
WalletFile walletFile = null;
try {
walletFile = Wallet.createLight(password, ecKeyPair);
} catch (CipherException e) {
e.printStackTrace();
}
String privateKey = Numeric.toHexStringWithPrefix(ecKeyPair.getPrivateKey());
System.out.println("+++私鑰==" + privateKey);
String publicKey = Numeric.toHexStringWithPrefix(ecKeyPair.getPublicKey());
System.out.println("+++公鑰==" + publicKey);
//根據公鑰和ecKeyPair獲取錢包地址
String address = Keys.getAddress(publicKey);
System.out.println("+++地址BIP44==" + address);
return new BabelWallet(mnemonics, address, walletFile);
}
到這我們的錢包創建就好了,我們可以把助記詞,導入到imtoken或者matemask 測試一下生成是是否相同。