【ETH钱包开发01】创建、导出钱包

简介

这篇文章主要是介绍ETH移动端(Android)钱包开发,核心功能包括创建钱包、导入钱包、钱包转账(收款)、交易查询等。

关于钱包的基本概念

钱包地址
以0x开头的42位的哈希值 (16进制) 字符串

keystore
明文私钥通过加密算法加密过后的 JSON 格式的字符串, 一般以文件形式存储

助记词
12 (或者 15、18、21) 单词构成, 用户可以通过助记词导入钱包, 但反过来讲, 如果他人得到了你的助记词, 不需要任何密码就可以轻而易举的转移你的资产, 所以要妥善保管自己的助记词

明文私钥
64位的16进制哈希值字符串, 用一句话阐述明文私钥的重要性 “谁掌握了私钥, 谁就掌握了该钱包的使用权!” 同样, 如果他人得到了你的明文私钥, 不需要任何密码就可以轻而易举的转移你的资产

【ETH钱包开发01】创建、导出钱包_第1张图片

和银行卡做个简单类比
地址=银行卡号
密码=银行卡密码
私钥=银行卡号+银行卡密码
助记词=银行卡号+银行卡密码
Keystore+密码=银行卡号+银行卡密码
Keystore ≠ 银行卡号

私钥通过椭圆曲线签名得到公钥 ,公钥经过哈希得到钱包地址 ,整个过程单向的(不可逆 )
私钥------->公钥------->钱包地址

关于BIP协议

这里先简单介绍一下BIP,后面再单独出一篇文章讲解。

BIP协议是比特币的一个改进协议,在钱包开发中主要用到BIP32、BIP39、BIP44

BIP32:定义了层级确定性钱包( Hierarchical Deterministic wallet ,简称 HD Wallet),是一个系统可以从单一个 seed 产生一树状结构储存多组 keypairs(私钥和公钥)。好处是可以方便的备份、转移到其他相容装置(因为都只需要 seed),以及分层的权限控制等。

BIP39:用于生成助记词,将 seed 用方便记忆和书写的单词表示,一般由 12 个单字组成,单词列表总共有2048个单词。
Wordlists

BIP44:基于 BIP32 的系统,赋予树状结构中的各层特殊的意义。让同一个 seed 可以支援多币种、多帐户等。各层定义如下:

m / purpose' / coin_type' / account' / change / address_index

  • purporse': 固定值44', 代表是BIP44
  • coin_type': 这个代表的是币种, 可以兼容很多种币, 比如BTC是0', ETH是60',btc一般是 m/44'/0'/0'/0,eth一般是 m/44'/60'/0'/0
  • account’:账号
  • change’: 0表示外部链(External Chain),用户接收比特币,1表示内部链(Internal Chain),用于接收找零
  • address_index:钱包索引

准备工具

eth钱包开发需要借助2个第三方库
web3j:可以理解为eth API的java版本

bitcoinj:生成支持bip32bip44的钱包。还有其他的一些库支持bip32和bip44,比如:Nova Crypto的系列包,包含bip32,bip39,bip44,我就是使用的Nova Crypto系列包。

注意:因为web3j不支持生成bip44的钱包,而市面上大多数钱包使用bip32,bip39,bip44标准结合生成,所以引用此包。

在创建完钱包之后,你可以使用下面这个工具去测试助记词, 和校验助记词生成的地址、公钥、私钥等。
https://iancoleman.io/bip39/

创建钱包

在了解BIP 后,我们开始以太坊钱包开发,创建的钱包的流程为:

1、随机生成一组助记词
2、生成 seed
3、生成 master key
4、生成 child key
5、我们取第一组child key即m/44'/60'/0'/0/0 得到私钥,keystore及地址

1、引用库:
web3j

 implementation 'org.web3j:core:3.3.1-android'

创建钱包相关
全家桶的那个bip32有点问题,用我这里给出的那个

// implementation 'org.bitcoinj:bitcoinj-core:0.14.7'
    implementation 'io.github.novacrypto:BIP39:0.1.9' //用于生成助记词
    implementation 'io.github.novacrypto:BIP44:0.0.3'
 //    implementation 'io.github.novacrypto:BIP32:0.0.9' 
    //使用这个bip32
    implementation 'com.lhalcyon:bip32:1.0.0'
    implementation 'com.lambdaworks:scrypt:1.4.0' //加密算法

2、生成随机助记词


    /**
     * generate a random group of mnemonics
     * 生成一组随机的助记词
     */
    public String generateMnemonics() {
        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();
    }

3. 根据助记词计算出Seed,得到master key ,根据BIP44派生地址,获取KeyPair

/**
    * generate key pair to create eth wallet_normal
    * 生成KeyPair , 用于创建钱包(助记词生成私钥)
    */
   public ECKeyPair generateKeyPair(String mnemonics) {
       // 1. we just need eth wallet_normal for now
       AddressIndex addressIndex = BIP44
               .m()
               .purpose44()
               .coinType(60)
               .account(0)
               .external()
               .address(0);
       // 2. calculate seed from mnemonics , then get master/root key ; Note that the bip39 passphrase we set "" for common
       byte[] seed = new SeedCalculator().calculateSeed(mnemonics, "");
       ExtendedPrivateKey rootKey = ExtendedPrivateKey.fromSeed(seed, Bitcoin.MAIN_NET);
       Log.i(TAG, "mnemonics:" + mnemonics);
       String extendedBase58 = rootKey.extendedBase58();
       Log.i(TAG, "extendedBase58:" + extendedBase58);

       // 3. get child private key deriving from master/root key
       ExtendedPrivateKey childPrivateKey = rootKey.derive(addressIndex, AddressIndex.DERIVATION);
       String childExtendedBase58 = childPrivateKey.extendedBase58();
       Log.i(TAG, "childExtendedBase58:" + childExtendedBase58);


       // 4. get key pair
       byte[] privateKeyBytes = childPrivateKey.getKey();
       ECKeyPair keyPair = ECKeyPair.create(privateKeyBytes);

       // we 've gotten what we need
       String privateKey = childPrivateKey.getPrivateKey();
       String publicKey = childPrivateKey.neuter().getPublicKey();
       String address = Keys.getAddress(keyPair);


       Log.i(TAG, "privateKey:" + privateKey);
       Log.i(TAG, "publicKey:" + publicKey);
       Log.i(TAG, "address:" + Constant.PREFIX_16 + address);

       return keyPair;
   }

这一步已经得到钱包公钥、私钥、地址了。
如果需要测试助记词, 和校验助记词生成的地址, 那么可以访问这个网站 : https://iancoleman.io/bip39/

4、通过keypair创建钱包

 /**
     * 创建钱包(助记词方式)
     *
     * @param context    app context 上下文
     * @param password   the wallet_normal password(not the bip39 password) 钱包密码(而不是BIP39的密码)
     * @param mnemonics  助记词
     * @param walletName 钱包名称
     * @return wallet_normal 钱包
     */
    public Flowable generateWallet(Context context,
                                             String password,
                                             String mnemonics,
                                             String walletName) {
        Flowable flowable = Flowable.just(mnemonics);

        return flowable
                .map(s -> {
                    ECKeyPair keyPair = generateKeyPair(s);
                    WalletFile walletFile = Wallet.createLight(password, keyPair);
                    HLWallet hlWallet = new HLWallet(walletFile, walletName);
                    WalletManager.shared().saveWallet(context, hlWallet); //保存钱包信息
                    return hlWallet;
                });
    }

这样生成的就是符合bip32、bip39、bip44的钱包,也能和市面上包括imtoken在内的大多数钱包通用了。

HLWallet

public class HLWallet {

    public WalletFile walletFile; //钱包文件,包含私钥、keystore、address等信息

    public String walletName; //钱包名称


    @JsonIgnore
    public boolean isCurrent = false;

    public HLWallet() {
    }

    public HLWallet(WalletFile walletFile) {
        this.walletFile = walletFile;
    }

    public HLWallet(WalletFile walletFile, String walletName) {
        this.walletFile = walletFile;
        this.walletName = walletName;
    }

    public String getAddress(){
        return Constant.PREFIX_16 + this.walletFile.getAddress();
    }


    public String getWalletName() {
        return walletName;
    }

    public void setWalletName(String walletName) {
        this.walletName = walletName;
    }
}

导出钱包

导出私钥
通过解密获得ECKeyPair
通过ECKeyPair获得私钥,并转换成16进制,就是最后的私钥了。

 /**
     * 导出私钥
     *
     * @param password   创建钱包时的密码
     * @param walletFile
     * @return
     */
    public String exportPrivateKey(String password, WalletFile walletFile) {
        try {
//            ECKeyPair ecKeyPair = Wallet.decrypt(password, walletFile); //可能出现OOM
            ECKeyPair ecKeyPair = LWallet.decrypt(password, walletFile);
            String privateKey = Numeric.toHexStringNoPrefix(ecKeyPair.getPrivateKey());
            return privateKey;
        } catch (CipherException e) {
            e.printStackTrace();
            return "error";
        }
    }

导出keystore

         /**
     * 导出Keystore
     *
     * @param password   创建钱包时的密码
     * @param walletFile
     * @return
     */
    public String exportKeystore(String password, WalletFile walletFile) {
        if (decrypt(password, walletFile)) {
            return new Gson().toJson(walletFile);
        } else {
            return "decrypt failed";
        }
    }

/**
     * 解密
     * 如果方法没有抛出CipherException异常则表示解密成功,也就是说可以把Wallet相关信息展示给用户看
     *
     * @param password   创建钱包时的密码
     * @param walletFile
     * @return
     */
    public boolean decrypt(String password, WalletFile walletFile) {
        try {
//            ECKeyPair ecKeyPair = Wallet.decrypt(password, walletFile); //可能出现OOM
            ECKeyPair ecKeyPair = LWallet.decrypt(password, walletFile);
            return true;
        } catch (CipherException e) {
            e.printStackTrace();
            return false;
        }
    }

注意2点:
1、在导出私钥、keystore、助记词之前都需要先验证密码是否正确,也就是调用如下这个方法,如果没有抛出异常,则把信息展示给用户看。

Wallet.decrypt(password, walletFile);

2、使用web3j的这个decrypt,经常会抛出OOM异常。关于解决方案,大家查看这里。https://www.jianshu.com/p/41d4a38754a3

导出助记词
助记词是没有办法根据私钥或者keystore推导出来的。一般的做法是在创建钱包的时候把助记词加密后在本地存储,导出时解密。

注意:使用IMToken导入私钥或者KeyStore创建的钱包,没有导出助记词的功能;如果是通过助记词创建的,就会有导出助记词的功能。而且助记词一旦备份之后,备份这个功能就会消失,也就是说从本地存储中删除。

    /**
     * 导出助记词
     *
     * @param password
     * @param hlWallet
     * @return
     */
    public String exportMnemonics(String password, HLWallet hlWallet) {
        WalletFile walletFile = hlWallet.walletFile;
        if (decrypt(password, walletFile)) {
            return hlWallet.getMnemonic();
        } else {
            return "decrypt failed";
        }
    }

你可能感兴趣的:(【ETH钱包开发01】创建、导出钱包)