【ETH钱包开发02】导入钱包

作者: 唠嗑008 | 来源:发表于2018-11-12 14:42 被阅读1次

    简介

    本文主要讲解通过助记词、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!

    具体原因的话,我就不多说了,细节大家可以看这里
    https://www.jianshu.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一旦备份了助记词之后,之后就没有备份那个功能了,也就是说助记词在本地存储中删除了;而且导入钱包的时候也是没有备份助记词这个功能的。

    相关文章

      网友评论

        本文标题:【ETH钱包开发02】导入钱包

        本文链接:https://www.haomeiwen.com/subject/dgwoxqtx.html