【ETH錢包開發(fā)01】創(chuàng)建、導(dǎo)出錢包

簡(jiǎn)介

這篇文章主要是介紹ETH移動(dòng)端(Android)錢包開發(fā),核心功能包括創(chuàng)建錢包、導(dǎo)入錢包、錢包轉(zhuǎn)賬(收款)、交易查詢等。

關(guān)于錢包的基本概念

錢包地址
以0x開頭的42位的哈希值 (16進(jìn)制) 字符串

keystore
明文私鑰通過加密算法加密過后的 JSON 格式的字符串, 一般以文件形式存儲(chǔ)

助記詞
12 (或者 15、18、21) 單詞構(gòu)成, 用戶可以通過助記詞導(dǎo)入錢包, 但反過來講, 如果他人得到了你的助記詞, 不需要任何密碼就可以輕而易舉的轉(zhuǎn)移你的資產(chǎn), 所以要妥善保管自己的助記詞

明文私鑰
64位的16進(jìn)制哈希值字符串, 用一句話闡述明文私鑰的重要性 “誰掌握了私鑰, 誰就掌握了該錢包的使用權(quán)!” 同樣, 如果他人得到了你的明文私鑰, 不需要任何密碼就可以輕而易舉的轉(zhuǎn)移你的資產(chǎn)

和銀行卡做個(gè)簡(jiǎn)單類比
地址=銀行卡號(hào)
密碼=銀行卡密碼
私鑰=銀行卡號(hào)+銀行卡密碼
助記詞=銀行卡號(hào)+銀行卡密碼
Keystore+密碼=銀行卡號(hào)+銀行卡密碼
Keystore ≠ 銀行卡號(hào)

私鑰通過橢圓曲線簽名得到公鑰 ,公鑰經(jīng)過哈希得到錢包地址 ,整個(gè)過程單向的(不可逆 )
私鑰------->公鑰------->錢包地址

關(guān)于BIP協(xié)議

這里先簡(jiǎn)單介紹一下BIP,后面再單獨(dú)出一篇文章講解。

BIP協(xié)議是比特幣的一個(gè)改進(jìn)協(xié)議,在錢包開發(fā)中主要用到BIP32BIP39、BIP44

BIP32:定義了層級(jí)確定性錢包( Hierarchical Deterministic wallet ,簡(jiǎn)稱 HD Wallet),是一個(gè)系統(tǒng)可以從單一個(gè) seed 產(chǎn)生一樹狀結(jié)構(gòu)儲(chǔ)存多組 keypairs(私鑰和公鑰)。好處是可以方便的備份、轉(zhuǎn)移到其他相容裝置(因?yàn)槎贾恍枰?seed),以及分層的權(quán)限控制等。

BIP39:用于生成助記詞,將 seed 用方便記憶和書寫的單詞表示,一般由 12 個(gè)單字組成,單詞列表總共有2048個(gè)單詞。
Wordlists

BIP44:基于 BIP32 的系統(tǒng),賦予樹狀結(jié)構(gòu)中的各層特殊的意義。讓同一個(gè) seed 可以支援多幣種、多帳戶等。各層定義如下:

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

  • purporse': 固定值44', 代表是BIP44
  • coin_type': 這個(gè)代表的是幣種, 可以兼容很多種幣, 比如BTC是0', ETH是60',btc一般是 m/44'/0'/0'/0,eth一般是 m/44'/60'/0'/0
  • account’:賬號(hào)
  • change’: 0表示外部鏈(External Chain),用戶接收比特幣,1表示內(nèi)部鏈(Internal Chain),用于接收找零
  • address_index:錢包索引

準(zhǔn)備工具

eth錢包開發(fā)需要借助2個(gè)第三方庫
web3j:可以理解為eth API的java版本

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

注意:因?yàn)閣eb3j不支持生成bip44的錢包,而市面上大多數(shù)錢包使用bip32,bip39,bip44標(biāo)準(zhǔn)結(jié)合生成,所以引用此包。

在創(chuàng)建完錢包之后,你可以使用下面這個(gè)工具去測(cè)試助記詞, 和校驗(yàn)助記詞生成的地址、公鑰、私鑰等。
https://iancoleman.io/bip39/

創(chuàng)建錢包

在了解BIP 后,我們開始以太坊錢包開發(fā),創(chuàng)建的錢包的流程為:

1、隨機(jī)生成一組助記詞
2、生成 seed
3、生成 master key
4、生成 child key
5、我們?nèi)〉谝唤Mchild key即m/44'/60'/0'/0/0 得到私鑰,keystore及地址

1、引用庫:
web3j

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

創(chuàng)建錢包相關(guān)
全家桶的那個(gè)bip32有點(diǎn)問題,用我這里給出的那個(gè)

// 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' 
    //使用這個(gè)bip32
    implementation 'com.lhalcyon:bip32:1.0.0'
    implementation 'com.lambdaworks:scrypt:1.4.0' //加密算法

2、生成隨機(jī)助記詞


    /**
     * generate a random group of mnemonics
     * 生成一組隨機(jī)的助記詞
     */
    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. 根據(jù)助記詞計(jì)算出Seed,得到master key ,根據(jù)BIP44派生地址,獲取KeyPair

/**
    * generate key pair to create eth wallet_normal
    * 生成KeyPair , 用于創(chuàng)建錢包(助記詞生成私鑰)
    */
   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;
   }

這一步已經(jīng)得到錢包公鑰、私鑰、地址了。
如果需要測(cè)試助記詞, 和校驗(yàn)助記詞生成的地址, 那么可以訪問這個(gè)網(wǎng)站 : https://iancoleman.io/bip39/

4、通過keypair創(chuàng)建錢包

 /**
     * 創(chuàng)建錢包(助記詞方式)
     *
     * @param context    app context 上下文
     * @param password   the wallet_normal password(not the bip39 password) 錢包密碼(而不是BIP39的密碼)
     * @param mnemonics  助記詞
     * @param walletName 錢包名稱
     * @return wallet_normal 錢包
     */
    public Flowable<HLWallet> generateWallet(Context context,
                                             String password,
                                             String mnemonics,
                                             String walletName) {
        Flowable<String> 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在內(nèi)的大多數(shù)錢包通用了。

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;
    }
}

導(dǎo)出錢包

導(dǎo)出私鑰
通過解密獲得ECKeyPair
通過ECKeyPair獲得私鑰,并轉(zhuǎn)換成16進(jìn)制,就是最后的私鑰了。

 /**
     * 導(dǎo)出私鑰
     *
     * @param password   創(chuàng)建錢包時(shí)的密碼
     * @param walletFile
     * @return
     */
    public String exportPrivateKey(String password, WalletFile walletFile) {
        try {
//            ECKeyPair ecKeyPair = Wallet.decrypt(password, walletFile); //可能出現(xiàn)OOM
            ECKeyPair ecKeyPair = LWallet.decrypt(password, walletFile);
            String privateKey = Numeric.toHexStringNoPrefix(ecKeyPair.getPrivateKey());
            return privateKey;
        } catch (CipherException e) {
            e.printStackTrace();
            return "error";
        }
    }

導(dǎo)出keystore

         /**
     * 導(dǎo)出Keystore
     *
     * @param password   創(chuàng)建錢包時(shí)的密碼
     * @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相關(guān)信息展示給用戶看
     *
     * @param password   創(chuàng)建錢包時(shí)的密碼
     * @param walletFile
     * @return
     */
    public boolean decrypt(String password, WalletFile walletFile) {
        try {
//            ECKeyPair ecKeyPair = Wallet.decrypt(password, walletFile); //可能出現(xiàn)OOM
            ECKeyPair ecKeyPair = LWallet.decrypt(password, walletFile);
            return true;
        } catch (CipherException e) {
            e.printStackTrace();
            return false;
        }
    }

注意2點(diǎn):
1、在導(dǎo)出私鑰、keystore、助記詞之前都需要先驗(yàn)證密碼是否正確,也就是調(diào)用如下這個(gè)方法,如果沒有拋出異常,則把信息展示給用戶看。

Wallet.decrypt(password, walletFile);

2、使用web3j的這個(gè)decrypt,經(jīng)常會(huì)拋出OOM異常。關(guān)于解決方案,大家查看這里。http://www.itdecent.cn/p/41d4a38754a3

導(dǎo)出助記詞
助記詞是沒有辦法根據(jù)私鑰或者keystore推導(dǎo)出來的。一般的做法是在創(chuàng)建錢包的時(shí)候把助記詞加密后在本地存儲(chǔ),導(dǎo)出時(shí)解密。

注意:使用IMToken導(dǎo)入私鑰或者KeyStore創(chuàng)建的錢包,沒有導(dǎo)出助記詞的功能;如果是通過助記詞創(chuàng)建的,就會(huì)有導(dǎo)出助記詞的功能。而且助記詞一旦備份之后,備份這個(gè)功能就會(huì)消失,也就是說從本地存儲(chǔ)中刪除。

    /**
     * 導(dǎo)出助記詞
     *
     * @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";
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容