搭建你的第一個區塊鏈網絡(三):錢包與客戶端


前一篇文章: 搭建你的第一個區塊鏈網絡(二)

錢包與CLI

錢包

對於區塊鏈系統來說,密碼學是必不可少的,因此加密與解密也是核心操作,而密鑰通常使用錢包進行保存,這一節我們完成錢包的設計。這一節比較簡單。
在比特幣網絡中,使用的是非對稱加密算法,密鑰是通過橢圓曲線算法實現的,而本文中,暫且使用RSA算法進行實現,后期再對橢圓曲線算法進行添加。
首先是RSA算法的工具類,參考這里.整理成以下方法:

#RSAKey.java
@Getter
@Setter
public final class RSAKey {
     //非對稱密鑰算法
     public static final String KEY_ALGORITHM = "RSA";
     /**
      * 密鑰長度必須是64的倍數,在512到65536位之間
      */
     private static final int KEY_SIZE = 512;
     //公鑰
     private static final String PUBLIC_KEY = "RSAPublicKey";
 
     //私鑰
     private static final String PRIVATE_KEY = "RSAPrivateKey";
    private byte[] privateKey;
    private byte[] publicKey;
    private String address;
    private RSAKey() {
    }
    /**
     * 生成密鑰
     */
    public static RSAKey GenerateKeyPair() throws Exception {
        RSAKey key = new RSAKey();
        Map<String, Object> keyPair = key.initKey();
        Key pk = (Key) keyPair.get(PRIVATE_KEY);
        key.setPrivateKey(pk.getEncoded());
        pk = (Key)keyPair.get(PUBLIC_KEY);
        key.setPublicKey(pk.getEncoded());
        return key;
    }
    private Map<String, Object> initKey() throws Exception {
        //實例化密鑰生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        //初始化密鑰生成器
        keyPairGenerator.initialize(KEY_SIZE);
        //生成密鑰對
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //公鑰
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //私鑰
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        //將密鑰存儲在map中
        Map<String, Object> keyMap = new HashMap<String, Object>();
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }
    /**
     * 私鑰加密
     *
     * @param data 待加密數據
     * @param key       密鑰
     * @return byte[] 加密數據
     */
    public static byte[] encryptByPrivateKey(byte[] data,byte[] pk) throws Exception {
        //取得私鑰
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(pk);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //生成私鑰
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //數據加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }
   /**
    * 公鑰解密
    *
    * @param data 待解密數據
    * @param key  密鑰
    * @return byte[] 解密數據
    */
    public static byte[] decryptByPublicKey(byte[] data,byte[] pk) throws Exception {
        //實例化密鑰工廠
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //初始化公鑰
        //密鑰材料轉換
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pk);
        //產生公鑰
        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
        //數據解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, pubKey);
        return cipher.doFinal(data);
    }
}

密鑰

接下來創建錢包實例,對於用戶的錢包,需要一下屬性:

  • 私鑰: 用於加密數據,簽名數據。
  • 公鑰: 用於解密數據,驗證簽名。
  • 地址: 錢包的地址。

目前,暫時將錢包設置為單例模式,同時一個錢包中只含有一對密鑰對:

#Wallet.java

@Getter
@Setter
public class Wallet {
    //單例模式的Wallet
    private static Wallet wallet;
    //私鑰
    private byte[] privateKey;
    //公鑰
    private byte[] publicKey;
    //地址
    private String address;
    private Wallet() throws Exception {
        RSAKey key  = RSAKey.GenerateKeyPair();
        this.privateKey = key.getPrivateKey();
        this.publicKey = key.getPublicKey();
        this.address = generateAddress();
    }
    public static Wallet getInstance() throws Exception {
        if (wallet == null) {
            synchronized (Wallet.class) {
                if (wallet == null) {
                    wallet = new Wallet();
                }
            }
        }
        return wallet;
    }
 }

接下來是加密與解密操作,只需要包裝一下工具類即可:

#Wallet.java
    /**
     * 加密數據
     */
    public String encrypt(byte[] data) throws Exception {
        byte[] encry = RSAKey.encryptByPrivateKey(data,this.privateKey);
        return Hex.encodeHexString(encry);
    }
    /**
     *  解密數據
     */
    public byte[] decrypt(String encry) throws DecoderException, Exception {
        return RSAKey.decryptByPublicKey(Hex.decodeHex(encry),this.publicKey);
    }

還有重要的簽名與驗證簽名的操作:
簽名的本質實際上就是哈希操作+加密操作。

  1. 對數據原文進行哈希: 哈希(數據原文)
  2. 使用私鑰對哈希值進行加密: 加密(哈希(數據原文))
  3. 將數據原文與簽名數據進行組合
    數字簽名的組成部分為:數據原文+加密(哈希(數據原文))
#Wallet.java
    /**
     * 簽名數據
     */
    public String sign(byte[] data) throws Exception {
        //原文首先進行哈希
        String hash = Util.getSHA256(
            Hex.encodeHexString(data));
        //哈希值進行加密
        String sign = encrypt(
            Hex.decodeHex(hash));
        //原文+encry(hash(原文))
        return Hex.encodeHexString(data)+"%%%"+sign;
    }

在這里,我們簡單使用%%%將兩部分數據進行組合。
至於數字簽名的驗證則可以分析了:

  1. 首先將數字簽名拆分為數據原文 加密(哈希(數據原文))
  2. 將數據原文進行哈希操作: 哈希(數據原文)
  3. 使用公鑰解密簽名信息: 解密->加密(哈希(數據原文))->哈希(數據原文)
  4. 對兩部分哈希值進行對比,相同則說明數字簽名是有效的。
#Wallet.java
    /**
     * 驗證簽名
     */
    public boolean verify(String data) throws DecoderException, Exception {
        String[] str = data.split("%%%");
        // 原文     encry(hash(原文))
        if(str.length!=2){
            return false;
        }
        String hash = Util.getSHA256(str[0]);
        String hash2 = Hex.encodeHexString(this.decrypt(str[1]));
        if(hash.equals(hash2)){
            return true;
        }
        return false;
    }

地址

對於錢包地址,比特幣是有自己的一套生成地址的算法,步驟相對比較繁雜,在本文中,簡單使用哈希操作模擬地址的生成。

#Wallet.java
    /**
     * 根據密鑰生成地址
     */
    private String generateAddress() throws NoSuchAlgorithmException {
        String pk = Hex.encodeHexString(this.publicKey);
        this.address = "R" + Util.getSHA256(pk) + Util.getSHA256(Util.getSHA256(pk));
        return this.address;
    }
    /**
     * 獲取地址
     * @return
     * @throws NoSuchAlgorithmException
     */
    public String getAddress() throws NoSuchAlgorithmException {
        return this.generateAddress();
    }

所有的一切都已經完成,測試一下:

#KeyTest.java
public class KeyTest {
    public static void main(String[] args) throws DecoderException, Exception {
        Wallet wallet  = Wallet.getInstance();
        System.out.println("private Key:  "+Hex.encodeHexString(wallet.getPrivateKey()));
        System.out.println();
        System.out.println("public Key:  "+Hex.encodeHexString(wallet.getPublicKey()));
        System.out.println(
            wallet.verify(
                wallet.sign("test".getBytes())));
        System.out.println("address: "+wallet.getAddress());
    }
}

看起來一切都沒有問題:

private Key:  30820156020100300d06092a864886f70d0101010500048201403082013c020100024100b204075a20a86a8773681a2bee6574a68d1028516577c80f22d1f693dbc1c70cca59d95a74b8c7a55c3e02801ebdb025272f1df18ca862701b640a6bc444b7e50203010001024066a67a12d7a8261dcb47a967d1c5813995384ef778da546b9df993057a0048a5b9e2f3986bef45bbcffc13baff6a93b31b054ecd6f23ad9c23a088597bc169b5022100e210191df6e5661b7fbe239866110bc54ace03e22d9e242d199b0f95d42c3e7f022100c9970dbe3640ad34633cb1a3defa5fc4be1dd9881eb65ff19d53d0ae2c569f9b022100c46a544872b2926b262ca064d399cfee55b6762d589164c142d435506b0f1e25022100a65a09543aaeda7f4d98eb3a4029ba57bf4f20904c4fd112aff25755336f741b022100dbe2e256464346e26c134395aada2bd669f72700b146b494920e9c75df12403f

public Key:  305c300d06092a864886f70d0101010500034b003048024100b204075a20a86a8773681a2bee6574a68d1028516577c80f22d1f693dbc1c70cca59d95a74b8c7a55c3e02801ebdb025272f1df18ca862701b640a6bc444b7e50203010001
true
address: R92439f4d205def0794e23f626cf61013d04ccf1fdf9106ff78ca3ec30f7bc7cad4cdc346ee44501831c67085a54463e4ffd774654a2bd9328a382652de663f1a

Cli

到此為止我們開發了區塊鏈系統的部分功能,距離完成還有很長一段距離。不過,先完成一個比較小的功能好了,即與用戶進行交互的操作。之前考慮過使用curl,不過看到了apachecli工具,所以決定使用這個了。其實就是一種命令行解析工具,根據輸入的命令解析后執行對應的功能。參考這里
所以需要在pom.xml添加一下字段導包:

        <dependency>
            <groupId>commons-cli</groupId>
            <artifactId>commons-cli</artifactId>
            <version>1.2</version>
        </dependency>

整個步驟分為三個階段: 定義,解析,詢問

定義

首先需要定義一些參數信息,作為應用程序的接口。

        //創建Options對象
        Options options = new Options();
        //添加-h參數。 h為參數簡單形式,help為參數復雜形式,false定義該參數不需要額外的輸入,Print help為參數的介紹
        Option opt = new Option("h", "help", false, "Print help");
        //定義該參數是否為必須的
        opt.setRequired(false);
        options.addOption(opt);

解析

由CLi對用戶輸入的命令行進行解析。

        HelpFormatter hf = new HelpFormatter();
        hf.setWidth(110);
        CommandLine commandLine = null;
        CommandLineParser parser = new PosixParser();
        try {
            commandLine = parser.parse(options, args);
            //如果含有參數h,則打印幫助信息
            if (commandLine.hasOption('h')) {
                // 打印使用幫助
                hf.printHelp("Jchain", options, true);
            }
            ...
        }catch(Exception e){
        }

CommandLineParser 類中定義的 parse 方法將用 CLI 定義階段中產生的 Options 實例和一組字符串作為輸入,並返回解析后生成的 CommandLine
CLI 解析階段的目標結果就是創建 CommandLine 實例。

詢問

該階段是根據輸入的參數決定進入哪一個邏輯分支中。

            Option[] opts = commandLine.getOptions();
            if (opts != null) {
                for (Option opt1 : opts) {
                    //name為參數名稱
                    String name = opt1.getLongOpt();
                    //如果有額外的參數則傳入value中
                    String value = commandLine.getOptionValue(name);
                    //...根據name指定具體的邏輯分支
                }
            }

分析完了,然后制定需要的參數好了:
這里指定了三個參數:s,a,w,分別為獲取區塊鏈實例,添加區塊以及獲取錢包實例的功能。

        opt = new Option("s", "start", false, "start blockchain");
        opt.setRequired(false);
        options.addOption(opt);
        opt = new Option("a", "add", true, "add block");
        opt.setRequired(false);
        options.addOption(opt);
        opt = new Option("w", "wallet", false, "init wallet");
        opt.setRequired(false);
        options.addOption(opt);

然后是具體的邏輯分支:

        if (name.equals("s") || name.equals("start")) {
            System.out.println(Blockchain.getInstance().block.toString());
        }
        if(name.equals("a")||name.equals("add")&&value!=""){
            System.out.println(Blockchain.getInstance().addBlock(value).toString());
        } 
        if(name.equals("w")||name.equals("wallet")){
            Wallet wallet = Wallet.getInstance();
            System.out.println("private Key:  "+Hex.encodeHexString(wallet.getPrivateKey()));
            System.out.println();
            System.out.println("public Key:  "+Hex.encodeHexString(wallet.getPublicKey()));
        } 

簡單測試一下是否正常工作:

#CliTest.java
public class CliTest {
    public static void main(String[] args){
        String[] str = {"-s","-w","-a","block"};
        // String[] str = {"-h"};
        Cli.Start(str);
    }
}

看起來沒有問題,獲得了高度為4的區塊鏈實例。也成功創建了錢包打印出公私鑰。
最后生成了高度為5的區塊 區塊信息為我們輸入的"block"。

Current Last Block num is:4
{"blkNum":4,"curBlockHash":"0000287895ae8f4e4fc781137adee2b2fd0da4d7be3abb68c04507979157eb70","data":"block","nonce":121263,"prevBlockHash":"00003ae4ca11f2dd6262d9218ffe6a98416b4e9e2ad789b39aef74b383cc96a6","timeStamp":"2020-05-17 14:28:16"}
private Key:  30820154020100300d06092a864886f70d01010105000482013e3082013a02010002410082a46b7f68d835b5e8047b8794cfd4d5daf4b6f3d8258a8a78f670052f35bab562f52aa7a6eeeb69bc14e03f0d8019db5b754d68e8d0918aa6f2f4b636ba0f070203010001024009de8ffc6d18405e80abae055d19a253919a012444c4f94562c4034c70f79726372e85f8853b9093e984b2fee8d828cf6078b2b66239e5871e299985a9b85ea1022100bed5f1748ffeabd423e7518c2c9840d9299f5190f3e482b0ec50ae203cd25b17022100af409e3b74a06c4937f8ca3bc12776ff21217750a4b5e1d02de71d1a7ceda19102202085d3959ae8bb1df75477d85ccd41d800b8ef2cb5f40eb5da4051bc9ac0fad702206342871c87b6e0fe2b6c872687051239f88adae85b12051f0310b6842d23ee710221008ebb60975fc2ee07e94242da08e0cb81478b7c57091e20e2177aa325883e4714

public Key:  305c300d06092a864886f70d0101010500034b00304802410082a46b7f68d835b5e8047b8794cfd4d5daf4b6f3d8258a8a78f670052f35bab562f52aa7a6eeeb69bc14e03f0d8019db5b754d68e8d0918aa6f2f4b636ba0f070203010001
{"blkNum":5,"curBlockHash":"000047e8fc13a5fe0404aeca104f8624581738361f12f2a8c07c4f172dde62cc","data":"block","nonce":67701,"prevBlockHash":"0000287895ae8f4e4fc781137adee2b2fd0da4d7be3abb68c04507979157eb70","timeStamp":"2020-05-17 16:27:29"}

后一篇文章: 搭建你的第一個區塊鏈網絡(四)

Github倉庫地址在這里,隨時保持更新中.....

Github地址:Jchain


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM