标签搜索

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

heyuan
2023-03-03 / 0 评论 / 8 阅读 / 正在检测是否收录...

简介

本文主要讲解通过助记词、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一旦备份了助记词之后,之后就没有备份那个功能了,也就是说助记词在本地存储中删除了;而且导入钱包的时候也是没有备份助记词这个功能的。

0

评论

博主关闭了所有页面的评论