Java实现一个简易的区块链(三)

继前两篇文章主要完成对区块链总体框架结构的设计、引入hash值验证前后节点数据是否被篡改等等,这里引入数字签名,进一步对交易进行合法性验证。在数字签名的部分没有使用secp256k1的数字签名算法,而是DSA签名算法,整个代码完成后,发现冗余的地方挺多,还有许多需要改进的地方,尽管后面做了一些改进,但是仍有一些缺憾。
详见GitHub代码

  • 引入DSA数字签名算法
/**
 * @Auther:刘兰斌
 * @Date: 2021/06/07/17:43
 * @Explain:
 */


import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import org.apache.commons.codec.binary.Hex;

class DSA {

    private static String src = "imooc security dsa";

    public static void main(String[] args) {
        String data = "zhuangzhang12yuan";
        dsatest(data);
    }

    public static String dsatest(String data) {
        String res = "";
        try {
            //1.初始化密钥
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
            keyPairGenerator.initialize(512);

            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            DSAPublicKey dsaPublicKey = (DSAPublicKey) keyPair.getPublic();
            DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) keyPair.getPrivate();
            //2.执行签名
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(dsaPrivateKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance("DSA");
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            Signature signature = Signature.getInstance("SHA1withDSA");
            byte[] digest = MessageDigest.getInstance("SHA-256").digest(data.getBytes(StandardCharsets.UTF_8));
            signature.initSign(privateKey);
            signature.update(digest);
            byte[] result = signature.sign();
            res = Hex.encodeHexString(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    //3.验证签名
    //          isVerfy(data,result,dsaPublicKey);  String data,
    public static void isVerfy(String hash ,byte[] result, DSAPublicKey dsaPublicKey) throws NoSuchAlgorithmException {

        try {
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(dsaPublicKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance("DSA");
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            Signature signature = Signature.getInstance("SHA1withDSA");
            signature.initVerify(dsaPublicKey);
        //    byte[] digest2 = MessageDigest.getInstance("SHA-256").digest(data.getBytes(StandardCharsets.UTF_8));
            signature.update(hash.getBytes(StandardCharsets.UTF_8));
            boolean bool = signature.verify(result);//result是签名
            System.out.println("dsa verify : " + bool);
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException | InvalidKeySpecException e) {
            e.printStackTrace();
        }
    }
    public static boolean isVerfy2(String hash ,byte[] result, DSAPublicKey dsaPublicKey){
        boolean bool = false;
        try {
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(dsaPublicKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance("DSA");
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            Signature signature = Signature.getInstance("SHA1withDSA");
            signature.initVerify(dsaPublicKey);
            //    byte[] digest2 = MessageDigest.getInstance("SHA-256").digest(data.getBytes(StandardCharsets.UTF_8));
            signature.update(hash.getBytes(StandardCharsets.UTF_8));
            bool = signature.verify(result);//result是签名

        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException | InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return bool;
    }

}

  • 引入sha256算法


import java.nio.charset.StandardCharsets;
import org.apache.commons.codec.binary.Hex;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * @Auther:刘兰斌
 * @Date: 2021/05/31/9:07
 * @Explain:
 */
public class GetSHA256Str {
    public static void main(String[] args) {
        String t = "luotuo1";//刘兰斌
        String s = getsha256str(t);
        System.out.println(s);
    }
    public static String getsha256str(String string){
        String encoderes="";
        try {//初始化
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            byte[] hash = messageDigest.digest(string.getBytes(StandardCharsets.UTF_8));
            encoderes = Hex.encodeHexString(hash);


        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return encoderes;
    }
}

  • block与chain的总体类


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import org.apache.commons.codec.binary.Hex;


import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;


/**
 * @Auther:刘兰斌
 * @Date: 2021/05/31/20:33
 * @Explain:
 */
public class Blocks {
    //区块中存放交易信息类,更具体的是区块的成员变量transaction中存放着Transaction的类
    private ArrayList<Transaction> transactions;
    private String prehash;
    private String curhash;
    private Blocks next;
    private int nonce;
    private long timestamp;

    public Blocks(ArrayList<Transaction> transaction, String prehash) {
        this.transactions = transaction;
        this.prehash = prehash;
        this.curhash = this.computerHash();
        this.next = next;
        this.nonce = 1;
        this.timestamp = System.currentTimeMillis();
    }


    //新建一个方法将Transaction[]转变为String
    public String turnoff(ArrayList<Transaction> transactionarr) {
        return JSON.toJSONString(transactionarr);
    }


    public String getPrehash() {
        return prehash;
    }

    public void setPrehash(String prehash) {
        this.prehash = prehash;
    }

    public String getCurhash() {
        return curhash;
    }

    public void setCurhash(String curhash) {
        this.curhash = curhash;
    }

    public Blocks getNext() {
        return next;
    }

    public void setNext(Blocks next) {
        this.next = next;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public ArrayList<Transaction> getTransactions() {
        return transactions;
    }

    public void setTransactions(ArrayList<Transaction> transactions) {
        this.transactions = transactions;
    }

    //导入阿里巴巴的Fastjson。实现json字符串与Java对象的相互转化
    public String computerHash() {
        return GetSHA256Str.getsha256str(JSON.toJSONString(this.transactions) + this.prehash + this.nonce + this.timestamp);
    }

    //获取难度值
    public String getDificult(int dificulnum) {
        String res = "";
        for (int i = 0; i < dificulnum; i++) {
            res += "0";
        }
        return res;
    }

    //开始挖矿
    public String mine(int dificulnum,DSAPublicKey dsaPublicKey,byte[] sign) throws Exception {
        //验证从交易池中取到的数据的合法性
        for (Transaction trans: this.transactions) {
            if (!trans.Verify(dsaPublicKey,sign)){//transaction,dsaPublicKey,sign)
                throw  new Exception("交易池中数据发生改变,交易发生篡改!");
            }
        }
        System.out.println("交易打包入块时一切正常!");
        while (true) {
            //           System.out.println(this.curhash);
            this.curhash = this.computerHash();
            if (!this.curhash.substring(0, dificulnum).equals(this.getDificult(dificulnum))) {
                this.nonce++;
                this.curhash = this.computerHash();
            } else {
                break;
            }
        }
        return this.curhash;

    }



    @Override
    public String toString() {
        return "Blocks{" +
                "transactions=" + transactions +
                ", prehash='" + prehash + '\'' +
                ", curhash='" + curhash + '\'' +
                ", next=" + next +
                ", nonce=" + nonce +
                ", timestamp=" + timestamp +
                '}';
    }

    public static void main(String[] args) throws Exception {

        LinkedLists llb = new LinkedLists();

//        Transaction t1 = new Transaction("addr1", "addr2", 10);
//        Transaction t2 = new Transaction("addr2", "addr1", 5);
//        llb.addTransactionToPool(t1);
//        llb.addTransactionToPool(t2);
//        System.out.println(llb);

//        llb.minetransaction("addr3");
//        System.out.println(llb);
//        //获取交易信息的测试
//        String transaction = llb.fromListToBlockToTransaction(llb.getChain());
//        System.out.println(transaction);
//        System.out.println("--------------------------");
//        Transaction t5 = new Transaction("addr5", "addr6", 333);
//        Transaction t6 = new Transaction("addr6", "addr5", 122);
//        llb.addTransactionToPool(t5);
//        llb.addTransactionToPool(t6);
//        llb.minetransaction("addr66");
//        System.out.println(llb);
        //new对象时引用
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator.initialize(512);

        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        DSAPublicKey dsaPublicKey = (DSAPublicKey) keyPair.getPublic();
        DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) keyPair.getPrivate();

        KeyPairGenerator keyPairGenerator2 = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator2.initialize(512);
        KeyPair keyPair2 = keyPairGenerator2.generateKeyPair();
        DSAPublicKey dsaPublicKey2 = (DSAPublicKey) keyPair2.getPublic();
        DSAPrivateKey dsaPrivateKey2 = (DSAPrivateKey) keyPair2.getPrivate();

        Transaction transaction1 = new Transaction(Hex.encodeHexString(dsaPublicKey.getEncoded()),Hex.encodeHexString(dsaPublicKey2.getEncoded()),10);
        System.out.println(transaction1);//dsaPublicKey
        byte[] sign = transaction1.sign(dsaPrivateKey);
        String signature = Hex.encodeHexString(sign);
        System.out.println("得到的签名是"+signature);
        transaction1.isVerified(dsaPublicKey,sign);
 //       transaction1.setAmount(12);
        llb.addTransactionToPool(transaction1,dsaPublicKey,sign);
        llb.minetransaction("addr3",dsaPublicKey,sign);
        System.out.println(llb);
//        //获取交易信息的测试
        String transaction = llb.fromListToBlockToTransaction(llb.getChain());
        System.out.println(transaction);
    }

}

class LinkedLists {
    //初始化第一个结点
    private LinkedList<Blocks> chain;
    private ArrayList<Transaction> transactionpool;//交易池
    private int mineReward;
    //引入工作量证明机制
    private int dificultnum;

    public LinkedList<Blocks> getChain() {
        return chain;
    }

    public void setChain(LinkedList<Blocks> chain) {
        this.chain = chain;
    }

    public LinkedLists() {
        this.chain = new LinkedList<Blocks>();
        //初始化第一个块上链
        chain.add(transactionToBlock());
        this.transactionpool = new ArrayList<>();
        this.mineReward = 50;
        this.dificultnum = 5;
    }

    //通过链表找到指定的块,并查看区块中的交易,以字符串接受
    public String fromListToBlockToTransaction(LinkedList<Blocks> list) {
        Blocks next = list.get(0).getNext();
        ArrayList<Transaction> transactions = next.getTransactions();//
        return JSON.toJSONString(transactions);
    }

    //创建第一笔创世交易
    public ArrayList<Transaction> firsttransaction() {
        //js的数组可以存放任意js类型,故其对象数组中可以放String,可以放对象
        ArrayList<Transaction> firsttransaction = new ArrayList<>();
        firsttransaction.add(new Transaction(null, "祖先", 50));
//        firsttransaction.add(new Transaction(null, "祖先", 50));
        return firsttransaction;
    }

    //将第一笔交易放到块中
    public Blocks transactionToBlock() {
        return new Blocks(firsttransaction(), "");
    }

    public ArrayList<Transaction> getTransactionpool() {
        return transactionpool;
    }

    public void setTransactionpool(ArrayList<Transaction> transactionpool) {
        this.transactionpool = transactionpool;
    }

    @Override
    public String toString() {
        return "LinkedLists{" +
                "chain=" + chain +
                ", transactionpool=" + transactionpool +
                ", mineReward=" + mineReward +
                ", dificultnum=" + dificultnum +
                '}';
    }

    //添加一个将交易对象的Java字符串转变为交易对象数组
//    public Transaction[] stringToArr(String transactionfrompool){
//        JSONArray array = JSON.parseArray(transactionfrompool);
        (Transaction[])array;
//        return array;
//    }
    //矿
    public void minetransaction(String mineadress,DSAPublicKey dsaPublicKey,byte[] sign) throws Exception {
        //发放矿工奖励
        Transaction transactionreward = new Transaction("", mineadress, this.mineReward);
        this.transactionpool.add(transactionreward);
        //注意当调用 this.transactionpool.clear();方法时,下面所有的transaction均为0,问题的关键是怎么解决这个引用地址问题
        ArrayList<Transaction> copypooladress = new ArrayList<>();
        for (int i = 0; i <= transactionpool.size() - 1; i++) {
            copypooladress.add(transactionpool.get(i));
        }
        Blocks blocks = new Blocks(copypooladress, this.getLastBlock().getCurhash());//
        //blocks.mine(this.dificultnum);
        String curhash = blocks.mine(this.dificultnum,dsaPublicKey,sign);
        if (curhash != null) {
            System.out.println("挖矿结束:" + curhash);
            addNode(blocks, copypooladress, curhash);
            //添加区块到链
            //更改地址引用。为了删除交易池中数据。
            //清空交易池
            this.transactionpool.clear();
        }
    }

    //添加交易到交易池
    public void addTransactionToPool(Transaction transaction,DSAPublicKey dsaPublicKey,byte[] sign) throws Exception {
        try {
            if (!transaction.Verify(dsaPublicKey,sign)){
                throw new Exception("不合法的交易,公钥错误或者数据被篡改,交易不能添加到交易池中!");
            }
            System.out.println("合法的交易!");
            if (transaction.toString() == null) {
                System.out.println("传入的数据为空");
            } else {
                this.transactionpool.add(transaction);
            }
        } catch (Exception n) {
            n.printStackTrace();
        }

    }

    //获取链中最后一个区块
    public Blocks getLastBlock() {
        Blocks tempblock = this.chain.get(0);
        while (tempblock.getNext() != null) {
            tempblock = tempblock.getNext();
        }
        return tempblock;
    }

    //添加未知节点到区块链上中
    public void addNode(Blocks blocks, ArrayList<Transaction> coppy, String curhash) {//,ArrayList<Transaction> copypooladress
        Blocks temp = getLastBlock();
        blocks.setPrehash(temp.getCurhash());//prehash赋值
//        blocks.mine(dificultnum);//curhash赋值
        blocks.setCurhash(curhash);
        temp.setNext(blocks);
        blocks.setTransactions(coppy);
        blocks.setNext(null);
    }

    //    验证数据合法性
    //验证数据是否合法
    //验证hash是否正确
    public boolean isValidate(DSAPublicKey dsaPublicKey,byte[] sign) throws Exception {
        //如果为初始化链,肯定合法

        if (chain.size() == 1) {//chain.size() == 1
            //    if(chain.get(0).getOwnhash() != chain.get(0).computerhash()){
            if (!chain.get(0).getCurhash().equals(chain.get(0).computerHash())) {
                //!chain.get(0).getOwnhash().equals(chain.get(0).computerhash())
                return false;
            }
            return true;
        }
        //验证完第一个数据后,开始验证后面的数据
        while (chain.get(0).getNext() != null) {
            //看区块是否有非法交易



            ArrayList<Transaction> transactions = chain.get(0).getNext().getTransactions();
            for (Transaction trans: transactions) {
                if (!trans.Verify(dsaPublicKey,sign)){//transaction,dsaPublicKey,sign)
                    throw  new Exception("交易池中数据发生改变,交易发生篡改!");
                }
            }
            Blocks nextblocks = chain.get(0).getNext();
            if (!nextblocks.getCurhash().equals(nextblocks.computerHash())) {
                System.out.println("数据发生篡改!");
                return false;
            }

            //验证此区块的的前哈希值是否等于前一个区块的哈希值
            Blocks preblocks = chain.get(0);
            if (!nextblocks.getPrehash().equals(preblocks.getCurhash())) {
                System.out.println("失败,前后数据块断裂!");
                return false;
            }
            nextblocks = nextblocks.getNext();
            preblocks = preblocks.getNext();
        }
        return true;
    }

}

class Transaction {
    private String sender;
    private String receiver;
    private int amount;

    // 时间戳应该是在区块产生时产生,不是产生交易的时间点
    //添加数字签名到区块链
    public String computerHash() {
        return GetSHA256Str.getsha256str(this.sender + this.receiver + this.amount);
    }

    //签名
    public byte[] sign(DSAPrivateKey privatekeyPair) {
        // String signature = DSA.dsatest(this.sender + this.receiver + this.amount);
      //  String res = "";
        byte[] result=null;
        try {

            //2.执行签名 dsaPrivateKey
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privatekeyPair.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance("DSA");
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            Signature signature = Signature.getInstance("SHA1withDSA");
            //    byte[] digest = MessageDigest.getInstance("SHA-256").digest(data.getBytes(StandardCharsets.UTF_8));
            signature.initSign(privateKey);
            signature.update(computerHash().getBytes(StandardCharsets.UTF_8));
            result = signature.sign();
           // res = Hex.encodeHexString(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        return result;
    }

    //验证
    public void isVerified(DSAPublicKey dsapublickey, byte[] signature) throws NoSuchAlgorithmException {
        if(this.sender ==""){
            return;
        }
        DSA.isVerfy(computerHash(),signature,dsapublickey);
    }
    //获取上面验证的一个bool值、实际上是对验证方法的一种封装
    public boolean Verify(DSAPublicKey dsapublickey, byte[] signature){
        if(this.sender ==""){
            return true;
        }
        boolean verfy2 = DSA.isVerfy2(computerHash(),signature,dsapublickey);
        return verfy2;
    }


    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getReceiver() {
        return receiver;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }

    public int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }


    public Transaction(String sender, String receiver, int amount) {
        this.sender = sender;
        this.receiver = receiver;
        this.amount = amount;
    }

    @Override
    public String toString() {
        return "Transaction{" +
                "sender='" + sender + '\'' +
                ", receiver='" + receiver + '\'' +
                ", amount=" + amount +
                '}';
    }
}

总结

整个搭建与测试都放到了一起,所有代码行数加起来接近600行,而使用弱类型语言如JavaScript,大概只需要200行左右的语言,因为其不用声明变量类型,对象数组都可以直接添加到方法中,不过Java整体的思路更加清晰,实现起来有挺多的代码冗余,可以进一步改进。这是一个简易的单机版的区块链实现,希望能给大家带来一定思路的帮助。

参考:

luotuocoin

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值