14、創建/恢復ETH錢包身份


借助網上的一段描述:

若以銀行賬戶為類比,這 5 個詞分別對應內容如下:

地址=銀行卡號
密碼=銀行卡密碼
私鑰=銀行卡號+銀行卡密碼
助記詞=銀行卡號+銀行卡密碼
Keystore+密碼=銀行卡號+銀行卡密碼
Keystore ≠ 銀行卡號

1
2
implementation 'org.web3j:core:3.3.1-android'
implementation 'io.github.novacrypto:BIP39:0.1.9'

org.web3j:core 這個庫是Java的,org.web3j:core:x-android 是兼容Android平台,所有接口和工具類都是為Java應用設計的,所以在Android上使用的時候要注意變通一下。

創建數字身份

創建錢包身份可以通過 WalletUtils 類來實現,它可以創建兩種錢包:標准和 BIP39。

可以通過 generateWalletFile 函數創建,直接保存為json文件,以下其他三個函數都是它的封裝。

在Android上不建議使用 WalletUtils 的這幾個函數創建數字身份。

1
2
3
4
WalletUtils.generateFullNewWalletFile();
WalletUtils.generateLightNewWalletFile();
WalletUtils.generateNewWalletFile();
WalletUtils.generateWalletFile();

generateFullNewWalletFile 使用N_STANDARD加密強度,在Android上會發送OOM,Android的處理速度也跟不上。

generateLightNewWalletFile 相對來說比較輕量級,但是在我手機(紅米4)上也花了21秒才創建完成,而加載為 Credentials 花了40秒。而在一台三星手機跑比較快,7秒左右。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void (View view) {

try {
String password = "123456";
String path = Environment.getExternalStorageDirectory().getPath() + "/MyWallet";
File fileDir = new File(path);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
//生成錢包身份保存到目錄下
String fileName = null;

Log.e(TAG, "startClickCreateDefault: "+fileDir.getPath() );
fileName = WalletUtils.generateLightNewWalletFile("123456",fileDir);

Log.e(TAG, "wallet fileName: " + fileName );

//加載錢包身份
Credentials credentials = WalletUtils.loadCredentials(password,path + "/" +fileName);
Log.e(TAG, "getAddress: "+credentials.getAddress());
Log.e(TAG, "getPrivateKey: "+credentials.getEcKeyPair().getPrivateKey());
Log.e(TAG, "getPublicKey: "+credentials.getEcKeyPair().getPublicKey());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (CipherException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

創建BIP39身份

可以導出助記詞,創建花了43秒,用助記詞導入很快,只花了幾秒。

1
WalletUtils.generateBip39Wallet();

WalletUtils提供的這個方法在Android上閃退,只有自己寫一個了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public void startClickCreateBip39(View view){

String password = "123456";
String path = Environment.getExternalStorageDirectory().getPath() + "/MyWallet";
File fileDir = new File(path);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
Log.e(TAG, "wallet start");
//閃退
//Bip39Wallet wallet = WalletUtils.generateBip39Wallet(password,fileDir);
//String mnemonic = wallet.getMnemonic();
//Log.e(TAG, "助記詞wallet.getMnemonic(): " + mnemonic);
//String fileName = wallet.getFilename();
//Log.e(TAG, "fileName: " + fileName);
try {

StringBuilder sb = new StringBuilder();
byte[] entropy = new byte[Words.TWELVE.byteLength()];
new SecureRandom().nextBytes(entropy);
new MnemonicGenerator(English.INSTANCE).createMnemonic(entropy, sb::append);
String mnemonic = sb.toString();
//String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy);
byte[] seed = MnemonicUtils.generateSeed(mnemonic, password);
ECKeyPair privateKey = ECKeyPair.create(sha256(seed));

String fileName = WalletUtils.generateWalletFile(password, privateKey, fileDir, false);
Log.e(TAG, "fileName: " + fileName);
Log.e(TAG, "助記詞wallet.getMnemonic(): " + mnemonic);
//加載錢包身份
Credentials credentials = WalletUtils.loadBip39Credentials(password,mnemonic);
Log.e(TAG, "getAddress: "+credentials.getAddress());
Log.e(TAG, "getPrivateKey: "+credentials.getEcKeyPair().getPrivateKey());
Log.e(TAG, "getPublicKey: "+credentials.getEcKeyPair().getPublicKey());

} catch (CipherException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e){
e.printStackTrace();
}

}

Android創建錢包

上面這些感覺比較適合Java程序,我們跳進去看看就知道了,其實生成數字身份的代碼是:

1
2
3
4
public static WalletFile createLight(String password, ECKeyPair ecKeyPair)
throws CipherException {
return create(password, ecKeyPair, N_LIGHT, P_LIGHT);
}

針對Android,我們需要將生成的數字身份 WalletFile 轉為 JSON (Keystore)保存到 SharedPreferences ,所以整理一個工具類:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
大專欄  14、創建/恢復ETH錢包身份s="line">80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import android.support.annotation.Nullable;
import android.util.Log;

import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Keys;
import org.web3j.crypto.MnemonicUtils;
import org.web3j.crypto.Wallet;
import org.web3j.crypto.WalletFile;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

import io.github.novacrypto.bip39.MnemonicGenerator;
import io.github.novacrypto.bip39.Words;
import io.github.novacrypto.bip39.wordlists.English;

import static org.web3j.crypto.Hash.sha256;

public class MyWalletTool {

private final String TAG = getClass().getName();

/**
* 創建一個輕量級錢包,沒有助記詞
* @param password
* @return
*/
public WalletFile createLightWallet(String password){

WalletFile walletFile = null;
try {
walletFile = Wallet.createLight(password, Keys.createEcKeyPair());
} catch (CipherException | NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return walletFile;
}

/**
* 創建一個助記詞
* @return
*/
public String createMnemonic(){

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

/**
* 創建一個帶有助記詞的輕量級錢包
* @param password
* @param mnemonic
* @return
*/

public WalletFile createBip39Wallet(String password,String mnemonic){

WalletFile walletFile = null;
try {
byte[] seed = MnemonicUtils.generateSeed(mnemonic, password);
Log.d(TAG, "createLight start...");
walletFile = Wallet.createLight(password, ECKeyPair.create(sha256(seed)));
Log.d(TAG, "createLight end.");
} catch (Exception e){
e.printStackTrace();
}
return walletFile;
}



/**
* 生成憑證
* @param password
* @param walletFile
* @return
* @throws CipherException
*/
public Credentials createCredentials(String password,String mnemonic){

byte[] seed = MnemonicUtils.generateSeed(mnemonic, password);
return Credentials.create(ECKeyPair.create(sha256(seed)));
}

public Credentials createCredentials(String password,WalletFile walletFile) throws CipherException {

return Credentials.create(Wallet.decrypt(password, walletFile));
}

public Credentials createCredentials(String privateKey) {

return Credentials.create(privateKey);
}

交易憑證Credentials

web3j 中每一個交易都需要一個參數:CredentialsCredentials 實例化有三種方法,其中私鑰權限最高,所以絕不能泄露自己的私鑰和助記詞,常用的是密碼 + Keystore

MyWalletTool調用的函數來看,交易憑證的實例化只需要以下之一:

  1. 私鑰
  2. 助記詞
  3. 密碼 + Keystore

私鑰

一個錢包只有一個私鑰且不能修改

為什么 私鑰 單獨可以實現實例化 Credentials

Credentials 的構造函數參數是 ECKeyPairaddress

1
2
3
4
private Credentials(ECKeyPair ecKeyPair, String address) {
this.ecKeyPair = ecKeyPair;
this.address = address;
}

address 可以通過 ECKeyPair 推導出來,而 ECKeyPair 的構造函數參數就是公鑰和私鑰

1
2
3
4
public ECKeyPair(BigInteger privateKey, BigInteger publicKey) {
this.privateKey = privateKey;
this.publicKey = publicKey;
}

公鑰可以通過私鑰推導出來,所以可以直接實例化 Credentials

1
Sign.publicKeyFromPrivate(privateKey)

助記詞

助記詞是明文私鑰的另一種表現形式,其目的是為了幫助用戶記憶復雜的私鑰

Canache生成的一個助記詞

1
2
3
助記詞:jump dolphin leave reward allow farm gate hospital region diary seminar loan
地址:0x7E728c371D66813434F340E6D473B212F506bA54
私鑰:6229413033912ab1f26e36f0aad7e1ea2b957de73cfedf788b9fff811192aa89

imToken 可以成功導入錢包,但是用下面的 BIP39 標准的代碼卻不行(passphrase是加鹽,這里為空)。

1
2
3
4
byte[] seed = MnemonicUtils.generateSeed(mnemonic, passphrase);//passphrase=null
ECKeyPair ecKeyPair = ECKeyPair.create(sha256(seed));
System.out.println("private=" + ecKeyPair.getPrivateKey().toString());
System.out.println("private=" + ecKeyPair.getPrivateKey().toString(16));

結果是:

1
2
private=27538423023524426157929608133615570842335693203949154557762660148101331275721
private=3ce231f097447fe5d623b3a1f9a37e8c554ee014959903c4e2ebadf69ac7cfc9

網上查資料說 imToken 用的是 BIP44 標准。后面再看看怎么搞,imToken核心碼開源地址

BIP44助記詞創建和導入

Keystore

將私鑰以加密的方式保存為一份 JSON 文件,這份 JSON 文件就是 Keystore,所以它就是加密后的私鑰,它必須配合錢包密碼才能使用該賬號。

1
ECKeyPair ecKeyPair = Wallet.decrypt(password, walletFile);

錢包開源項目

  1. A ethereum wallet like imToken for Android
  2. A beautiful, secure and native Ethereum Wallet for Android
  3. Lightweight JS Wallet for Node and the browser
  4. A plugin that turns Vault into an Ethereum wallet. Golang


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM