簡(jiǎn)介
這篇文章主要是介紹ETH移動(dòng)端(Android)錢包開發(fā),核心功能包括創(chuàng)建錢包、導(dǎo)入錢包、錢包轉(zhuǎn)賬(收款)、交易查詢等。
關(guān)于錢包的基本概念
錢包地址
以0x開頭的42位的哈希值 (16進(jìn)制) 字符串
keystore
明文私鑰通過加密算法加密過后的 JSON 格式的字符串, 一般以文件形式存儲(chǔ)
助記詞
12 (或者 15、18、21) 單詞構(gòu)成, 用戶可以通過助記詞導(dǎo)入錢包, 但反過來講, 如果他人得到了你的助記詞, 不需要任何密碼就可以輕而易舉的轉(zhuǎn)移你的資產(chǎn), 所以要妥善保管自己的助記詞
明文私鑰
64位的16進(jìn)制哈希值字符串, 用一句話闡述明文私鑰的重要性 “誰掌握了私鑰, 誰就掌握了該錢包的使用權(quán)!” 同樣, 如果他人得到了你的明文私鑰, 不需要任何密碼就可以輕而易舉的轉(zhuǎn)移你的資產(chǎn)

和銀行卡做個(gè)簡(jiǎn)單類比
地址=銀行卡號(hào)
密碼=銀行卡密碼
私鑰=銀行卡號(hào)+銀行卡密碼
助記詞=銀行卡號(hào)+銀行卡密碼
Keystore+密碼=銀行卡號(hào)+銀行卡密碼
Keystore ≠ 銀行卡號(hào)
私鑰通過橢圓曲線簽名得到公鑰 ,公鑰經(jīng)過哈希得到錢包地址 ,整個(gè)過程單向的(不可逆 )
私鑰------->公鑰------->錢包地址
關(guān)于BIP協(xié)議
這里先簡(jiǎn)單介紹一下BIP,后面再單獨(dú)出一篇文章講解。
BIP協(xié)議是比特幣的一個(gè)改進(jìn)協(xié)議,在錢包開發(fā)中主要用到BIP32、BIP39、BIP44
BIP32:定義了層級(jí)確定性錢包( Hierarchical Deterministic wallet ,簡(jiǎn)稱 HD Wallet),是一個(gè)系統(tǒng)可以從單一個(gè) seed 產(chǎn)生一樹狀結(jié)構(gòu)儲(chǔ)存多組 keypairs(私鑰和公鑰)。好處是可以方便的備份、轉(zhuǎn)移到其他相容裝置(因?yàn)槎贾恍枰?seed),以及分層的權(quán)限控制等。
BIP39:用于生成助記詞,將 seed 用方便記憶和書寫的單詞表示,一般由 12 個(gè)單字組成,單詞列表總共有2048個(gè)單詞。
Wordlists
BIP44:基于 BIP32 的系統(tǒng),賦予樹狀結(jié)構(gòu)中的各層特殊的意義。讓同一個(gè) seed 可以支援多幣種、多帳戶等。各層定義如下:
m / purpose' / coin_type' / account' / change / address_index
- purporse': 固定值44', 代表是BIP44
- coin_type': 這個(gè)代表的是幣種, 可以兼容很多種幣, 比如BTC是0', ETH是60',btc一般是
m/44'/0'/0'/0,eth一般是m/44'/60'/0'/0 - account’:賬號(hào)
- change’: 0表示外部鏈(External Chain),用戶接收比特幣,1表示內(nèi)部鏈(Internal Chain),用于接收找零
- address_index:錢包索引
準(zhǔn)備工具
eth錢包開發(fā)需要借助2個(gè)第三方庫
web3j:可以理解為eth API的java版本
bitcoinj:生成支持bip32和bip44的錢包。還有其他的一些庫支持bip32和bip44,比如:Nova Crypto的系列包,包含bip32,bip39,bip44,我就是使用的Nova Crypto系列包。
注意:因?yàn)閣eb3j不支持生成bip44的錢包,而市面上大多數(shù)錢包使用bip32,bip39,bip44標(biāo)準(zhǔn)結(jié)合生成,所以引用此包。
在創(chuàng)建完錢包之后,你可以使用下面這個(gè)工具去測(cè)試助記詞, 和校驗(yàn)助記詞生成的地址、公鑰、私鑰等。
https://iancoleman.io/bip39/
創(chuàng)建錢包
在了解BIP 后,我們開始以太坊錢包開發(fā),創(chuàng)建的錢包的流程為:
1、隨機(jī)生成一組助記詞
2、生成 seed
3、生成 master key
4、生成 child key
5、我們?nèi)〉谝唤Mchild key即m/44'/60'/0'/0/0 得到私鑰,keystore及地址
1、引用庫:
web3j
implementation 'org.web3j:core:3.3.1-android'
創(chuàng)建錢包相關(guān)
全家桶的那個(gè)bip32有點(diǎn)問題,用我這里給出的那個(gè)
// implementation 'org.bitcoinj:bitcoinj-core:0.14.7'
implementation 'io.github.novacrypto:BIP39:0.1.9' //用于生成助記詞
implementation 'io.github.novacrypto:BIP44:0.0.3'
// implementation 'io.github.novacrypto:BIP32:0.0.9'
//使用這個(gè)bip32
implementation 'com.lhalcyon:bip32:1.0.0'
implementation 'com.lambdaworks:scrypt:1.4.0' //加密算法
2、生成隨機(jī)助記詞
/**
* generate a random group of mnemonics
* 生成一組隨機(jī)的助記詞
*/
public 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);
return sb.toString();
}
3. 根據(jù)助記詞計(jì)算出Seed,得到master key ,根據(jù)BIP44派生地址,獲取KeyPair
/**
* generate key pair to create eth wallet_normal
* 生成KeyPair , 用于創(chuàng)建錢包(助記詞生成私鑰)
*/
public ECKeyPair generateKeyPair(String mnemonics) {
// 1. we just need eth wallet_normal for now
AddressIndex addressIndex = BIP44
.m()
.purpose44()
.coinType(60)
.account(0)
.external()
.address(0);
// 2. calculate seed from mnemonics , then get master/root key ; Note that the bip39 passphrase we set "" for common
byte[] seed = new SeedCalculator().calculateSeed(mnemonics, "");
ExtendedPrivateKey rootKey = ExtendedPrivateKey.fromSeed(seed, Bitcoin.MAIN_NET);
Log.i(TAG, "mnemonics:" + mnemonics);
String extendedBase58 = rootKey.extendedBase58();
Log.i(TAG, "extendedBase58:" + extendedBase58);
// 3. get child private key deriving from master/root key
ExtendedPrivateKey childPrivateKey = rootKey.derive(addressIndex, AddressIndex.DERIVATION);
String childExtendedBase58 = childPrivateKey.extendedBase58();
Log.i(TAG, "childExtendedBase58:" + childExtendedBase58);
// 4. get key pair
byte[] privateKeyBytes = childPrivateKey.getKey();
ECKeyPair keyPair = ECKeyPair.create(privateKeyBytes);
// we 've gotten what we need
String privateKey = childPrivateKey.getPrivateKey();
String publicKey = childPrivateKey.neuter().getPublicKey();
String address = Keys.getAddress(keyPair);
Log.i(TAG, "privateKey:" + privateKey);
Log.i(TAG, "publicKey:" + publicKey);
Log.i(TAG, "address:" + Constant.PREFIX_16 + address);
return keyPair;
}
這一步已經(jīng)得到錢包公鑰、私鑰、地址了。
如果需要測(cè)試助記詞, 和校驗(yàn)助記詞生成的地址, 那么可以訪問這個(gè)網(wǎng)站 : https://iancoleman.io/bip39/
4、通過keypair創(chuàng)建錢包
/**
* 創(chuàng)建錢包(助記詞方式)
*
* @param context app context 上下文
* @param password the wallet_normal password(not the bip39 password) 錢包密碼(而不是BIP39的密碼)
* @param mnemonics 助記詞
* @param walletName 錢包名稱
* @return wallet_normal 錢包
*/
public Flowable<HLWallet> generateWallet(Context context,
String password,
String mnemonics,
String walletName) {
Flowable<String> flowable = Flowable.just(mnemonics);
return flowable
.map(s -> {
ECKeyPair keyPair = generateKeyPair(s);
WalletFile walletFile = Wallet.createLight(password, keyPair);
HLWallet hlWallet = new HLWallet(walletFile, walletName);
WalletManager.shared().saveWallet(context, hlWallet); //保存錢包信息
return hlWallet;
});
}
這樣生成的就是符合bip32、bip39、bip44的錢包,也能和市面上包括imtoken在內(nèi)的大多數(shù)錢包通用了。
HLWallet
public class HLWallet {
public WalletFile walletFile; //錢包文件,包含私鑰、keystore、address等信息
public String walletName; //錢包名稱
@JsonIgnore
public boolean isCurrent = false;
public HLWallet() {
}
public HLWallet(WalletFile walletFile) {
this.walletFile = walletFile;
}
public HLWallet(WalletFile walletFile, String walletName) {
this.walletFile = walletFile;
this.walletName = walletName;
}
public String getAddress(){
return Constant.PREFIX_16 + this.walletFile.getAddress();
}
public String getWalletName() {
return walletName;
}
public void setWalletName(String walletName) {
this.walletName = walletName;
}
}
導(dǎo)出錢包
導(dǎo)出私鑰
通過解密獲得ECKeyPair
通過ECKeyPair獲得私鑰,并轉(zhuǎn)換成16進(jìn)制,就是最后的私鑰了。
/**
* 導(dǎo)出私鑰
*
* @param password 創(chuàng)建錢包時(shí)的密碼
* @param walletFile
* @return
*/
public String exportPrivateKey(String password, WalletFile walletFile) {
try {
// ECKeyPair ecKeyPair = Wallet.decrypt(password, walletFile); //可能出現(xiàn)OOM
ECKeyPair ecKeyPair = LWallet.decrypt(password, walletFile);
String privateKey = Numeric.toHexStringNoPrefix(ecKeyPair.getPrivateKey());
return privateKey;
} catch (CipherException e) {
e.printStackTrace();
return "error";
}
}
導(dǎo)出keystore
/**
* 導(dǎo)出Keystore
*
* @param password 創(chuàng)建錢包時(shí)的密碼
* @param walletFile
* @return
*/
public String exportKeystore(String password, WalletFile walletFile) {
if (decrypt(password, walletFile)) {
return new Gson().toJson(walletFile);
} else {
return "decrypt failed";
}
}
/**
* 解密
* 如果方法沒有拋出CipherException異常則表示解密成功,也就是說可以把Wallet相關(guān)信息展示給用戶看
*
* @param password 創(chuàng)建錢包時(shí)的密碼
* @param walletFile
* @return
*/
public boolean decrypt(String password, WalletFile walletFile) {
try {
// ECKeyPair ecKeyPair = Wallet.decrypt(password, walletFile); //可能出現(xiàn)OOM
ECKeyPair ecKeyPair = LWallet.decrypt(password, walletFile);
return true;
} catch (CipherException e) {
e.printStackTrace();
return false;
}
}
注意2點(diǎn):
1、在導(dǎo)出私鑰、keystore、助記詞之前都需要先驗(yàn)證密碼是否正確,也就是調(diào)用如下這個(gè)方法,如果沒有拋出異常,則把信息展示給用戶看。
Wallet.decrypt(password, walletFile);
2、使用web3j的這個(gè)decrypt,經(jīng)常會(huì)拋出OOM異常。關(guān)于解決方案,大家查看這里。http://www.itdecent.cn/p/41d4a38754a3
導(dǎo)出助記詞
助記詞是沒有辦法根據(jù)私鑰或者keystore推導(dǎo)出來的。一般的做法是在創(chuàng)建錢包的時(shí)候把助記詞加密后在本地存儲(chǔ),導(dǎo)出時(shí)解密。
注意:使用IMToken導(dǎo)入私鑰或者KeyStore創(chuàng)建的錢包,沒有導(dǎo)出助記詞的功能;如果是通過助記詞創(chuàng)建的,就會(huì)有導(dǎo)出助記詞的功能。而且助記詞一旦備份之后,備份這個(gè)功能就會(huì)消失,也就是說從本地存儲(chǔ)中刪除。
/**
* 導(dǎo)出助記詞
*
* @param password
* @param hlWallet
* @return
*/
public String exportMnemonics(String password, HLWallet hlWallet) {
WalletFile walletFile = hlWallet.walletFile;
if (decrypt(password, walletFile)) {
return hlWallet.getMnemonic();
} else {
return "decrypt failed";
}
}