14、创建/恢复ETH钱包身份

我们两清 提交于 2020-01-06 22:00:44

借助网上的一段描述:

若以银行账户为类比,这 5 个词分别对应内容如下:

地址=银行卡号
密码=银行卡密码
私钥=银行卡号+银行卡密码
助记词=银行卡号+银行卡密码
Keystore+密码=银行卡号+银行卡密码
Keystore ≠ 银行卡号

12
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 的这几个函数创建数字身份。

1234
WalletUtils.generateFullNewWalletFile();WalletUtils.generateLightNewWalletFile();WalletUtils.generateNewWalletFile();WalletUtils.generateWalletFile();

generateFullNewWalletFile 使用N_STANDARD加密强度,在Android上会发送OOM,Android的处理速度也跟不上。

generateLightNewWalletFile 相对来说比较轻量级,但是在我手机(红米4)上也花了21秒才创建完成,而加载为 Credentials 花了40秒。而在一台三星手机跑比较快,7秒左右。

12345678910111213141516171819202122232425262728293031323334
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上闪退,只有自己写一个了

1234567891011121314151617181920212223242526272829303132333435363738394041424344
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程序,我们跳进去看看就知道了,其实生成数字身份的代码是:

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

针对Android,我们需要将生成的数字身份 WalletFile 转为 JSON (Keystore)保存到 SharedPreferences ,所以整理一个工具类:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
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

1234
private Credentials(ECKeyPair ecKeyPair, String address) {        this.ecKeyPair = ecKeyPair;        this.address = address;}

address 可以通过 ECKeyPair 推导出来,而 ECKeyPair 的构造函数参数就是公钥和私钥

1234
public ECKeyPair(BigInteger privateKey, BigInteger publicKey) {	this.privateKey = privateKey;	this.publicKey = publicKey;}

公钥可以通过私钥推导出来,所以可以直接实例化 Credentials

1
Sign.publicKeyFromPrivate(privateKey)

助记词

助记词是明文私钥的另一种表现形式,其目的是为了帮助用户记忆复杂的私钥

Canache生成的一个助记词

123
助记词:jump dolphin leave reward allow farm gate hospital region diary seminar loan地址:0x7E728c371D66813434F340E6D473B212F506bA54私钥:6229413033912ab1f26e36f0aad7e1ea2b957de73cfedf788b9fff811192aa89

imToken 可以成功导入钱包,但是用下面的 BIP39 标准的代码却不行(passphrase是加盐,这里为空)。

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

结果是:

12
private=27538423023524426157929608133615570842335693203949154557762660148101331275721private=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
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!