简介
本文主要讲解通过助记词、keystore、私钥 3种方式来导入钱包。导入钱包就是说根据输入的这3者中的一个去重新生成一个新的钱包。导入钱包的过程和创建的过程其实是差不多的。
根据助记词导入钱包
根据助记词导入钱包不需要原始密码,密码可以重新设置。根据用户输入的助记词,先验证助记词的合规性(格式、个数等),验证正确后,配合用户输入的密码重新生成一个新的钱包。
验证助记词的合规性(格式、个数等)
private boolean validateInput(String mnemonics, String password, String repassword) {
// validate empty
if (TextUtils.isEmpty(mnemonics) || TextUtils.isEmpty(password) || TextUtils.isEmpty(repassword)) {
ScheduleCompat.snackInMain(startImportBtn, "请填写助记词和密码");
return false;
}
// validate password
if (!TextUtils.equals(password, repassword)) {
ScheduleCompat.snackInMain(startImportBtn, "密码不一致");
return false;
}
// validate mnemonic
try {
MnemonicValidator.ofWordList(English.INSTANCE).validate(mnemonics);
} catch (InvalidChecksumException e) {
e.printStackTrace();
ScheduleCompat.snackInMain(startImportBtn, "助记词格式不正确");
return false;
} catch (InvalidWordCountException e) {
e.printStackTrace();
ScheduleCompat.snackInMain(startImportBtn, "请检查单词个数");
return false;
} catch (WordNotFoundException e) {
e.printStackTrace();
ScheduleCompat.snackInMain(startImportBtn, "无效的助记词");
return false;
} catch (UnexpectedWhiteSpaceException e) {
e.printStackTrace();
ScheduleCompat.snackInMain(startImportBtn, "请检查空格与分隔符");
return false;
}
return true;
}
助记词导入钱包
/**
* 助记词方式导入钱包,不需要以前的密码
*
* @param context
* @param password
* @param mnemonics 重新设置的密码
* @param walletName
* @return
*/
public Flowable<HLWallet> importMnemonic(Context context,
String password,
String mnemonics,
String walletName) {
Flowable<String> flowable = Flowable.just(mnemonics);
return flowable
.flatMap(s -> {
ECKeyPair keyPair = generateKeyPair(s);
WalletFile walletFile = Wallet.createLight(password, keyPair);
HLWallet hlWallet = new HLWallet(walletFile, walletName);
if (WalletManager.shared().isWalletExist(hlWallet.getAddress())) {
return Flowable.error(new HLError(ReplyCode.walletExisted, new Throwable("Wallet existed!")));
}
WalletManager.shared().saveWallet(context, hlWallet);
return Flowable.just(hlWallet);
});
}
根据私钥导入钱包
通过私钥导入钱包其实和创建钱包的过程基本一致。因为私钥在导出的时候转换成了16进制,所以在导入私钥的时候,要把16进制转换为byte数组。
/**
* 私钥方式导入钱包,不需要以前的密码
*
* @param context
* @param privateKey
* @param password 重新设置的密码
* @param walletName 钱包名称
* @return
*/
public Flowable<HLWallet> importPrivateKey(Context context, String privateKey, String password, String walletName) {
if (privateKey.startsWith(Constant.PREFIX_16)) {
privateKey = privateKey.substring(Constant.PREFIX_16.length());
}
Flowable<String> flowable = Flowable.just(privateKey);
return flowable.flatMap(s -> {
byte[] privateBytes = Hex.decode(s);
ECKeyPair ecKeyPair = ECKeyPair.create(privateBytes);
WalletFile walletFile = Wallet.createLight(password, ecKeyPair);
HLWallet hlWallet = new HLWallet(walletFile, walletName);
if (WalletManager.shared().isWalletExist(hlWallet.getAddress())) {
return Flowable.error(new HLError(ReplyCode.walletExisted, new Throwable("Wallet existed!")));
}
WalletManager.shared().saveWallet(context, hlWallet);
return Flowable.just(hlWallet);
});
}
根据Keystore导入钱包
keystore就是钱包文件,实际上就是钱包信息的json字符串。导入keystore是需要输入密码的,这个密码是你最后导出keystore时的密码。将keystore字符串变成walletFile实例再通过Wallet.decrypt(password, walletFile);
解密,成功则可以导入,否则不能导入。
ECKeyPair keyPair = Wallet.decrypt(password, walletFile);
这是Web3j的API,程序走到这里经常OOM!
具体原因的话,我就不多说了,细节大家可以看这里
//www.greatytc.com/p/41d4a38754a3
解决办法
根据源码修改decrypt
方法,这里我用一个已经修改好的第三方库
implementation 'com.lambdaworks:scrypt:1.4.0'
修改后的解密方法
public static ECKeyPair decrypt(String password, WalletFile walletFile)
throws CipherException {
validate(walletFile);
WalletFile.Crypto crypto = walletFile.getCrypto();
byte[] mac = Numeric.hexStringToByteArray(crypto.getMac());
byte[] iv = Numeric.hexStringToByteArray(crypto.getCipherparams().getIv());
byte[] cipherText = Numeric.hexStringToByteArray(crypto.getCiphertext());
byte[] derivedKey;
if (crypto.getKdfparams() instanceof WalletFile.ScryptKdfParams) {
WalletFile.ScryptKdfParams scryptKdfParams =
(WalletFile.ScryptKdfParams) crypto.getKdfparams();
int dklen = scryptKdfParams.getDklen();
int n = scryptKdfParams.getN();
int p = scryptKdfParams.getP();
int r = scryptKdfParams.getR();
byte[] salt = Numeric.hexStringToByteArray(scryptKdfParams.getSalt());
// derivedKey = generateDerivedScryptKey(password.getBytes(Charset.forName("UTF-8")), salt, n, r, p, dklen);
derivedKey = com.lambdaworks.crypto.SCrypt.scryptN(password.getBytes(Charset.forName("UTF-8")), salt, n, r, p, dklen);
} else if (crypto.getKdfparams() instanceof WalletFile.Aes128CtrKdfParams) {
WalletFile.Aes128CtrKdfParams aes128CtrKdfParams =
(WalletFile.Aes128CtrKdfParams) crypto.getKdfparams();
int c = aes128CtrKdfParams.getC();
String prf = aes128CtrKdfParams.getPrf();
byte[] salt = Numeric.hexStringToByteArray(aes128CtrKdfParams.getSalt());
derivedKey = generateAes128CtrDerivedKey(
password.getBytes(Charset.forName("UTF-8")), salt, c, prf);
} else {
throw new CipherException("Unable to deserialize params: " + crypto.getKdf());
}
byte[] derivedMac = generateMac(derivedKey, cipherText);
if (!Arrays.equals(derivedMac, mac)) {
throw new CipherException("Invalid password provided");
}
byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16);
byte[] privateKey = performCipherOperation(Cipher.DECRYPT_MODE, iv, encryptKey, cipherText);
return ECKeyPair.create(privateKey);
}
导入Kestore
/**
* 导入Keystore(推荐使用),Keystore+ 密码才能正确导入
*
* @param context
* @param keystore
* @param password 以前的密码
* @param walletName
* @return
*/
public Flowable<HLWallet> importKeystore(Context context, String keystore, String password, String walletName) {
return Flowable.just(keystore)
.flatMap(s -> {
ObjectMapper objectMapper = new ObjectMapper();
WalletFile walletFile = objectMapper.readValue(keystore, WalletFile.class);
//注意:这里用的是修改之后的解密方法
ECKeyPair keyPair = LWallet.decrypt(password, walletFile);
HLWallet hlWallet = new HLWallet(walletFile, walletName);
WalletFile generateWalletFile = Wallet.createLight(password, keyPair);
if (!generateWalletFile.getAddress().equalsIgnoreCase(walletFile.getAddress())) {
return Flowable.error(new HLError(ReplyCode.failure, new Throwable("address doesn't match private key")));
}
if (WalletManager.shared().isWalletExist(hlWallet.getAddress())) {
return Flowable.error(new HLError(ReplyCode.walletExisted, new Throwable("Wallet existed!")));
}
WalletManager.shared().saveWallet(context, hlWallet);
return Flowable.just(hlWallet);
});
}
注意:
1、导入助记词和私钥是不需要以前的密码的,而是重新输入新的密码;导入Keystore则需要以前的密码,如果密码不正确,会提示地址和私钥不匹配。
2、关于备份助记词
用过imtoken的同学可以看到imtoken是可以导出(备份)助记词的。这个一开始我也很困惑,后来了解到其实它实在创建钱包的时候,在app本地保存了助记词,导出只是讲数据读取出来而已。还有一点,imtoken一旦备份了助记词之后,之后就没有备份那个功能了,也就是说助记词在本地存储中删除了;而且导入钱包的时候也是没有备份助记词这个功能的。