如何用 Java 构建你的第一个区块链应用?

用 Java 做第一条教学链,目标不是复刻比特币,而是亲手实现三件事:把数据打成块、用 SHA-256 把块串起来、用简单的工作量证明让“改历史”变慢。整份程序可以只有三个类,在 JDK 17 和任意 IDE 里就能跑。跑通之后,你会对区块头、前一块哈希、nonce 和校验函数有具体感觉,再去看真实节点源码会轻松很多。

下面的例子是单机内存链,没有点对点广播,也没有钱包私钥。它适合当实验台:先看结构,再谈网络和共识。

准备环境和项目骨架

安装 JDK 17 或更高版本,用 IntelliJ IDEA、VS Code 或 Eclipse 新建普通 Java 项目即可,不必先上 Spring。建议包名例如 com.example.miniChain,三个类:Block、Chain、Main。哈希用标准库 MessageDigest,不额外引加密包。

新建类时先写哈希工具,后面每个块都要调用它。把“索引 + 时间戳 + 数据 + 前哈希 + nonce”拼成一个字符串再做 SHA-256,输出小写十六进制。拼接顺序一旦定下就不要改,否则新旧块对不上。

Java
 
public final class HashUtil {
    public static String sha256(String input) {
        try {
            var md = java.security.MessageDigest.getInstance("SHA-256");
            byte[] digest = md.digest(input.getBytes(java.nio.charset.StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) sb.append(String.format("%02x", b));
            return sb.toString();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }
}

先写区块:数据、前哈希和当前哈希

一个教学块至少包含:块高 index、时间戳、业务数据 data、前一块哈希 previousHash、随机数 nonce、本块哈希 hash。构造时先填字段,再调用 calculateHash() 得到当前哈希。创世块的 previousHash 用 64 个 0 占位。

Java
 
public class Block {
    private final int index;
    private final long timestamp;
    private final String data;
    private final String previousHash;
    private int nonce;
    private String hash;

    public Block(int index, String data, String previousHash) {
        this.index = index;
        this.timestamp = System.currentTimeMillis();
        this.data = data;
        this.previousHash = previousHash;
        this.nonce = 0;
        this.hash = calculateHash();
    }

    public String calculateHash() {
        return HashUtil.sha256(index + timestamp + data + previousHash + nonce);
    }

    public void mine(int difficulty) {
        String prefix = "0".repeat(difficulty);
        while (!hash.startsWith(prefix)) {
            nonce++;
            hash = calculateHash();
        }
    }

    public String getHash() { return hash; }
    public String getPreviousHash() { return previousHash; }
    public String getData() { return data; }
    public int getIndex() { return index; }
}

mine 就是最简工作量证明:不断加 nonce,直到哈希前缀出现指定个数的 0。难度 3 在普通笔记本上通常一眨眼;难度 5 开始能感觉到循环。教学时用 3 或 4 足够,不必追主网级别的难度。

再写链条:创世块、出块和完整性检查

链用 List<Block> 保存。构造函数里立刻放入创世块并挖一次,保证第一块也满足难度。添加新块时,把链尾哈希当作 previousHash,挖完再追加。校验函数从第二块走到末尾,同时检查两件事:本块哈希是否重算一致,以及 previousHash 是否等于前一块的 hash。

Java
 
public class Chain {
    private final java.util.List<Block> blocks = new java.util.ArrayList<>();
    private final int difficulty;

    public Chain(int difficulty) {
        this.difficulty = difficulty;
        Block genesis = new Block(0, "genesis", "0".repeat(64));
        genesis.mine(difficulty);
        blocks.add(genesis);
    }

    public void addBlock(String data) {
        Block prev = blocks.get(blocks.size() - 1);
        Block next = new Block(prev.getIndex() + 1, data, prev.getHash());
        next.mine(difficulty);
        blocks.add(next);
    }

    public boolean isValid() {
        for (int i = 1; i < blocks.size(); i++) {
            Block cur = blocks.get(i);
            Block prev = blocks.get(i - 1);
            if (!cur.getHash().equals(cur.calculateHash())) return false;
            if (!cur.getPreviousHash().equals(prev.getHash())) return false;
            if (!cur.getHash().startsWith("0".repeat(difficulty))) return false;
        }
        return true;
    }

    public void print() {
        for (Block b : blocks) {
            System.out.printf("#%d %s%n  prev=%s%n  hash=%s%n  data=%s%n",
                    b.getIndex(), "", b.getPreviousHash(), b.getHash(), b.getData());
        }
    }
}

校验失败只有两种常见原因:有人改了某块的 data 却没重挖后面所有块,或者你改了哈希拼接公式导致新旧块各算各的。打印时把 prev 和 hash 都打出来,肉眼就能看到后一块的 prev 正是前一块的 hash。

用主类把链跑起来

Java
 
public class Main {
    public static void main(String[] args) {
        Chain chain = new Chain(4);
        chain.addBlock("Alice pays Bob 5");
        chain.addBlock("Bob pays Carol 2");
        chain.print();
        System.out.println("valid = " + chain.isValid());
    }
}

运行后控制台会出现三块:创世块、两笔转账说明。每块哈希前面应有四个 0。把 isValid() 打成 true,说明链接和难度都成立。这就是最小可用的“区块链应用”:一个能追加、能自检的账本对象。

想观察防篡改,可临时给 Block 加一个包内可见的 setter,在 Main 里改第二块数据后再调用 isValid()。不重挖的话结果一定是 false,因为当前哈希与重算值不一致。若只改数据并重算当前哈希、不改后一块的 previousHash,后一块仍然断裂。这和教材里“改中间必须重做后面所有工作量”是同一件事,只是规模缩到三条记录。

把“应用”做像一点:交易对象和简单 API

教学链的 data 现在是字符串。下一步可改成交易对象:付款人、收款人、金额,再用 JSON 或自己的 toString() 参与哈希。注意:没有私钥签名之前,任何人都能构造“Alice pays Bob”。签名要引入密钥对,那是第二个实验,不要和第一条链绑在一起做。

如果想变成可访问的应用,用 JDK 自带的 com.sun.net.httpserver.HttpServer 或 Spring Boot 暴露两个接口即可:GET /chain 返回块列表,POST /mine 接收一段数据并出块。单机演示足够。真要多节点,需要gossip、选择主链、处理同时出块的分叉,那已经超出“第一个应用”的范围。

持久化也很直接:把每个 Block 写成一行 JSON 追加到文件,启动时读回并立刻 isValid()。文件被手工改过,程序应拒绝启动。教学价值在于:链的权威在校验函数,不在文件看起来完好。

写完后对照真实链,避免三个误区

第一,SHA-256 教学链没有默克尔树,交易一多,改一笔就要重哈希整块字符串。主网把交易树的根放进块头,验证单笔不必重放全部正文。第二,List<Block> 在一台 JVM 里,没有拜占庭对手,isValid() 为真只说明本地数据自洽。第三,难度前缀零是示意,真实难度是“哈希必须小于目标值”,用数值比较而不是只看字符串开头。

Java 生态里若要接现成网络,可研究 BitcoinJ、Web3j(连以太坊兼容链)或 Hyperledger Fabric 的 Java SDK。那些是客户端,默认你已经理解块和哈希。自己写过 100 行迷你链,再调 SDK 的 sendTransaction,才知道网络另一头的节点在校验什么。

第一条 Java 区块链应用的完成标准可以定得很具体:能出创世块、能追加两块、打印哈希链条、篡改后校验失败、提高难度时出块变慢。满足这五条,结构就已经在你的代码里,而不只在名词表里。之后加签名、加节点、加合约,都是在这块骨架上长功能。

本文链接地址:https://www.wwsww.cn/qkl/41479.html
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。