区块链与比特币概念
-
区块链(BlockChain)起源于比特币,2008年11月1日,一位自称中本聪(Satoshi Nakamoto)的人发表了《比特币:一种点对点的电子现金系统》一文,阐述了基于P2P网络技术、加密技术、时间戳技术、区块链技术等的电子现金系统的构架理念,这标志着比特币的诞生。 -
在比特币系统中,区块(Block)是一个一个的存储单元,记录了一定时间内各个区块节点全部的交流信息。各个区块之间通过随机散列(也称哈希算法)实现链接,后一个区块包含前一个区块的哈希值,随着信息交流的扩大,一个区块与一个区块相继接续,形成的结果就叫区块链(BlockChain)。 -
在知乎上看到一个漫画讲得很形象: -
看着这些概念头脑中也难以形成一个具体的印象,不如实现一个区块链的demo来看一看。 -
参考教程 -
代码地址
1. 实现Block结构
- 区块链(BlockChain)顾名思义,是将一个个区块(Block)链接起来形成。所以我们实现区块链的第一步是实现Block结构。
- 区块链的链不是传统的通过指针等技术实现,而是通过哈希值来链接。所以一个Block中需要包含自身的哈希值,前一个Block的哈希值,还有自身的数据(这个数据与交易信息有关)。而当前块的哈希值是通过前一个块的哈希值、当前块的创建的时间以及当前块的数据三者根据加密算法计算得出的。所以Block中还要包含时间戳变量表示块创建时间。
- Block类实现如下:
public class Block {
public String hash;
public String prevHash;
private String data;
private long timestamp;
public ZJChain.Block(String data, String prevHash) {
this.data = data;
this.prevHash = prevHash;
this.timestamp = new Date().getTime();
try {
this.hash = calculateHash();
} catch (Exception e) {
e.printStackTrace();
}
}
public String calculateHash() throws Exception {
String calculatedHash = StringUtil.applySha256(prevHash+data+timestamp);
return calculatedHash;
}
}
public static String applySha256(String input) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes("UTF-8"));
StringBuffer hexString = new StringBuffer();
for(int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
if(hex.length() == 1) {
hexString.append("0");
}
hexString.append(hex);
}
return hexString.toString();
}
- SHA256:
- SHA256的中文全称叫做“安全哈希算法”。所谓的“哈希”是Hash的音译,而Hash就是进行Hash函数的意思。通常来说,Hash函数的运算有一个共同特点。就是不论原始数据有多少位,只要通过Hash运算后,得到结果的长度都是固定的。
- Hash函数的类型有很多种,包括SHA224、SHA256、SHA384、SHA512、SHA512/224、SHA512/256等。但是比特币仅选用了SHA256。这个256代表的意思是,数据经过函数运算后得到的结果必须是一个256位的2进制数字。
- 每次Hash计算后得到的结果有三个要求:第一、输入Hash函数之前的数据和通过Hash函数处理过后得到的编号必须一一对应。第二、每一个编号的长度都是固定的。第三、我们无法通过编号倒推出数据的内容。
- 因为哈希值的涉及到前一个块的哈希值,时间,数据等,所以哈希链表是无法在中间插入修改的。
2. 实现区块链(BlockChain)结构
- 前面构造了区块(Block),现在把他们连接起来存储就形成了区块链。我们采用ArrayList结构来组织这些Block。
public class ZJChain {
public static ArrayList<Block> blockChain = new ArrayList<Block>();
public boolean isChainValid() {
Block curBlock;
Block prevBlock;
for(int i = 1; i < blockChain.size(); i++) {
curBlock = blockChain.get(i);
prevBlock = blockChain.get(i - 1);
try {
if(!curBlock.hash.equals(curBlock.calculateHash())) {
System.out.println("block的hash值计算错误");
return false;
}
} catch (Exception e) {
e.printStackTrace();
}
if(!prevBlock.hash.equals(curBlock.prevHash)) {
System.out.println("当前block与前面block的hash值不对应");
return false;
}
}
return true;
}
public void addBlock(Block block) {
blockChain.add(block);
}
public String toJson() {
String blockChainString = JSON.toJSONString(blockChain);
return blockChainString;
}
}
- 因为ArrayList是内存中的数据结构,需要长期保存的话需要转换为JSON字符串写入文件中保存,通过alibaba的fastjson包实现。
- 构建区块链的测试:
@Test
public void test1() {
ZJChain zjChain = new ZJChain();
for(int i = 0; i < 10; i++) {
Block block;
if(zjChain.blockChain.size() == 0) {
block = new Block("Block: " + i, "0");
} else {
block = new Block("Block: " + i, zjChain.blockChain.get(
zjChain.blockChain.size() - 1).hash);
}
zjChain.addBlock(block);
}
for(ZJChain.Block block : zjChain.blockChain) {
System.out.println("hash: " + block.hash + " prevHash: " + block.prevHash);
}
}
3. 准备挖矿!!!
- 提起比特币、区块链,便离不开挖矿这个话题。那什么是挖矿?比特币挖矿就是找到一个随机数(Nonce)参与哈希运算Hash,使得最后得到的哈希值符合难度要求(在很多种组合中试出满足要求的组合, 有一点运气成分),用公式表示就是Hash <= target。具体的说就是使生成的哈希值的开头至少有指定数目个0。实现如下:
public void mineBlock(int difficulty) {
String target = new String(new char[difficulty]).replace('\0', '0');
while(!hash.substring(0, difficulty).equals(target)) {
nonce++;
try {
hash = calculateHash();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("nonce:" + nonce);
}
- 简单来看挖矿难度的高低就是生成区块头的哈希值有多少0,前面固定的0越多,满足条件的值就越少,计算得到符合标准的hash值的概率就低,宏观上说需要计算的次数就越多。difficulty每增加1,运算量都是呈几何速率增加,十分恐怖。
- 当难度为4:
- 当难度为5:
- 当难度为6:(好家伙,直接跑了8分多钟。。。)
- 对比可以看出,难度增加1,运算量(nonce可以表示运算的次数)直接增加一个量级,怪不得作为工作量衡量的标准(proof-of-work)。
- 这里还可以看出,挖矿之所以很耗算力,并不是因为这个哈希计算本身有多么复杂,而是它是需要重复这个哈希计算很多次(我这随便提一点难度,都到千万级了。。。)直到满足要求。这也是为什么GPU挖矿效率比CPU高的原因:其实不是GPU运算速度比CPU快,而是GPU运算的数据是单一的,是经过CPU运算往后分离出来的单一数据。CPU运算的所需求的东西许多,而且不是单一的某种数据。CPU可以运行更复杂的指令。如果是做一个简单的数学计算,一个最大16核的CPU最多只能同时跑16个线程,而一个普通的GPU就可以同时跑3000多个线程,所以做简单数学,GPU就比CPU要快几个数量级,而生成区块要做的哈希计算偏偏就是一个很简单的数学题。
- 另外,在检查区块链的有效性(isChainValid)的时候,还需要增加检查hash值是否满足难度要求这一点:
if(!curBlock.hash.substring(0, difficulty).equals(target)) {
System.out.println("当前块未满足难度标准!");
return false;
}
4. 创建钱包
- 比特币是一种点对点的电子现金系统,没有实物形态,可以存储在比特币钱包里。日常生活中,钱包是用来放钱的,但比特币钱包里却没有比特币,而只是确立比特币所有权的工具:比特币被记录在比特币网络的区块中(即Block,实际记录的也不是具体的比特币而是一笔笔交易记录)中,比特币的所有权是通过数字密钥、比特币地址和数字签名(接下来要实现的)来确立的。
- 数字密钥并不存储在网络中,而是由用户生成并存储在一个文件或简单的数据库中,称为钱包。比特币钱包里存储着你的比特币信息,包括比特币地址(类似于你的银行卡账号)和数字秘钥。
- 数字秘钥是用公钥加密创建一个密钥对,用于控制比特币的获取。密钥对包括一个私钥,和由其衍生出的唯一的公钥。公钥用于接收比特币,而私钥用于生成比特币支付时的交易签名(类似于你银行卡的密码)。支付比特币时,比特币的当前所有者需要在交易中提交其公钥和签名(每次交易的签名都不同,但均从同一个私钥生成)。比特币网络中的所有人都可以通过所提交的公钥和签名进行验证,并确认该交易是否有效,即确认支付者在该时刻对所交易的比特币拥有所有权。比特币私钥就用来保护你的钱包,如果私钥丢失,你将永远失去这笔比特币。
- Wallet实现:
public class Wallet {
public PublicKey publicKey;
public PrivateKey privateKey;
public Wallet() {
generateKeyPair();
}
public void generateKeyPair() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");
keyGen.initialize(ecSpec, random);
KeyPair keyPair = keyGen.generateKeyPair();
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- ECDSA:椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm,缩写ECDSA)是一种被广泛应用于数字签名的加密算法。
5. 实现交易(Transaction)
- 既然是一种货币,那么最重要的功能就是用来交易,作为最早出现的加密货币,比特币采用了 UTXO 模型作为其底层存储的数据结构,其全称为 Unspent Transaction output,也就是未被使用的交易输出。
- UTXO 模型的加密货币中,某一个账户中的余额并不是由一个数字表示的,而是由当前区块链网络中所有属于当前账户的 UTXO 组成的。每一个UTXO就跟现实世界中的一张纸钞类似,一个UTXO只能用一次,如果数额超出需要支付的价钱则发给自己新的UTXO(自己给自己找零)。
- 比特币实质上没有存储货币,它有的不过是在一个个交易中记录的数字变化,而这个数字的源头来自矿工。我们挖矿产生的Block实质上是一个账本,其中记录一笔笔交易的记录。每挖出一个Block,就会从无到有生成可以用于交易的value(交易中的数字)给矿工,这便是比特币产生的地方。
- 交易中每个人用钱包来保管自己的UTXO,以及公私钥。公钥就类似于银行卡号,别人通过指定你的公钥来转账给你。私钥类似于你的密码,但是又有不同:每次你发起转账,都需要通过你的私钥结合交易的内容来生成一个签名,通过验证签名与公钥来确认身份。签名与交易是一一对应的,即使暴露了也无妨,但是私钥是绝对不能泄漏的。
- UTXO 其实就是交易的一部分,基于 UTXO 模型的交易由输入和输出两个部分组成:UTXO 模型中的每一笔交易都是由多个交易输入组成的,这些输入其实就是 UTXO + 签名:属于某个人的UTXO加上这个人的签名(由秘钥生成)则可以表示这个人授权使用这个UTXO。每一个交易都可能会有多个输出,每一个输出都可以指向不同的地址,其中也有当前输出包含的值 value,这个value也就是比特币的计量数。
- 交易实现:
public class Transaction {
public String transactionId;
public static int sequence = 0;
public PublicKey sender;
public PublicKey recipient;
public float value;
public byte[] signature;
public ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>();
public ArrayList<TransactionOutput> outputs = new ArrayList<TransactionOutput>();
public Transaction(PublicKey from, PublicKey to, float value, ArrayList<TransactionInput> inputs) {
this.sender = from;
this.recipient = to;
this.value = value;
this.inputs = inputs;
}
private String calculateHash() throws Exception {
sequence++;
return StringUtil.applySha256(
StringUtil.getStringFromKey(sender) +
StringUtil.getStringFromKey(recipient) +
value + sequence);
}
public void generateSignature(PrivateKey privateKey) {
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(recipient) + value;
signature = StringUtil.applyECDSASig(privateKey, data);
}
public boolean verifySignature() throws Exception {
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(recipient) + value;
return StringUtil.verifyECDSASig(sender, data, signature);
}
public boolean processTransaction() {
try {
if(!verifySignature()) {
System.out.println("交易签名验证失败");
return false;
}
} catch (Exception e) {
e.printStackTrace();
}
for(TransactionInput input : inputs) {
input.UTXO = ZJChain.UTXOs.get(input.transactionOutputId);
}
if(getInputsValue() < ZJChain.minimumTransaction) {
System.out.println("交易输入数额:" + getInputsValue() + " 小于最小交易额");
return false;
}
float leftover = getInputsValue() - value;
if(leftover < 0) {
System.out.println("金额不足,交易终止!");
return false;
}
try {
transactionId = calculateHash();
} catch (Exception e) {
e.printStackTrace();
}
outputs.add(new TransactionOutput(this.recipient, value, transactionId));
if(leftover > 0) {
outputs.add(new TransactionOutput(this.sender, leftover, transactionId));
}
for(TransactionOutput output : outputs) {
ZJChain.UTXOs.put(output.id, output);
}
for(TransactionInput input : inputs) {
if(input.UTXO != null) {
ZJChain.UTXOs.remove(input.UTXO.id);
}
}
return true;
}
public float getInputsValue() {
float sum = 0;
for(TransactionInput i : inputs) {
if(i.UTXO != null) {
sum += i.UTXO.value;
}
}
return sum;
}
public float getOutputsValue() {
float sum = 0;
for(TransactionOutput output : outputs) {
sum += output.value;
}
return sum;
}
}
public class TransactionInput {
public String transactionOutputId;
public TransactionOutput UTXO;
public TransactionInput(String transactionOutputId) {
this.transactionOutputId = transactionOutputId;
}
}
public class TransactionOutput {
public String id;
public PublicKey recipient;
public float value;
public String parentTransactionId;
public TransactionOutput(PublicKey recipient, float value, String parentTransactionId) {
this.recipient = recipient;
this.value = value;
this.parentTransactionId = parentTransactionId;
try {
this.id = StringUtil.applySha256(StringUtil.getStringFromKey(recipient) + this.value + this.parentTransactionId);
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean isMine(PublicKey publicKey) {
return (publicKey == recipient);
}
}
6. 完善钱包(Wallet)
- 一个钱包中保存着每个账户的公私钥,并且具备统计该账户拥有的UTXO并计算余额的能力,此外钱包还有创建交易发起支出的能力。
public class Wallet {
public PublicKey publicKey;
public PrivateKey privateKey;
public HashMap<String, TransactionOutput> UTXOs = new HashMap<>();
public Wallet() {
generateKeyPair();
}
public void generateKeyPair() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");
keyGen.initialize(ecSpec, random);
KeyPair keyPair = keyGen.generateKeyPair();
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
} catch (Exception e) {
e.printStackTrace();
}
}
public float getBalance() {
float sum = 0;
for(Map.Entry<String, TransactionOutput> item : ZJChain.UTXOs.entrySet()) {
TransactionOutput UTXO = item.getValue();
if(UTXO.isMine(publicKey)) {
UTXOs.put(UTXO.id, UTXO);
sum += UTXO.value;
}
}
return sum;
}
public Transaction sendFunds(PublicKey _recipient, float value) {
if(getBalance() < value) {
System.out.println("余额不足,交易终止!");
return null;
}
ArrayList<TransactionInput> inputs = new ArrayList<>();
float total = 0;
for(Map.Entry<String, TransactionOutput> item : UTXOs.entrySet()) {
TransactionOutput UTXO = item.getValue();
total += UTXO.value;
inputs.add(new TransactionInput(UTXO.id));
if(total >= value) {
break;
}
}
Transaction newTransaction = new Transaction(publicKey, _recipient, value, inputs);
newTransaction.generateSignature(privateKey);
for(TransactionInput input : inputs) {
UTXOs.remove(input.transactionOutputId);
}
return newTransaction;
}
}
7. 完善其它类
- 在StringUtil工具类中,增加生成和验证数字签名的功能。同时还有根据添加到区块中的交易记录生成merkleRoot的功能,merkelRoot用于标识区块及其中的唯一性。
public class StringUtil {
public static String applySha256(String input) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes("UTF-8"));
StringBuffer hexString = new StringBuffer();
for(int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
if(hex.length() == 1) {
hexString.append("0");
}
hexString.append(hex);
}
return hexString.toString();
}
public static String getStringFromKey(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
public static byte[] applyECDSASig(PrivateKey privateKey, String data) {
Signature dsa;
byte[] output = new byte[0];
try{
dsa = Signature.getInstance("ECDSA", "BC");
dsa.initSign(privateKey);
byte[] strByte = data.getBytes();
dsa.update(strByte);
byte[] realSig = dsa.sign();
output = realSig;
} catch (Exception e) {
e.printStackTrace();
}
return output;
}
public static boolean verifyECDSASig(PublicKey publicKey, String data, byte[] signature) throws Exception {
Signature ecdsaVerify = Signature.getInstance("ECDSA", "BC");
ecdsaVerify.initVerify(publicKey);
ecdsaVerify.update(data.getBytes());
return ecdsaVerify.verify(signature);
}
public static String getMerkleRoot(ArrayList<Transaction> transactions) {
int count = transactions.size();
ArrayList<String> previousTreeLayer = new ArrayList<>();
for(Transaction transaction : transactions) {
previousTreeLayer.add(transaction.transactionId);
}
ArrayList<String> treeLayer = previousTreeLayer;
while(count > 1) {
treeLayer = new ArrayList<>();
for(int i = 1; i < previousTreeLayer.size(); i++) {
try {
treeLayer.add(StringUtil.applySha256(previousTreeLayer.get(i - 1) + previousTreeLayer.get(i)));
} catch (Exception e) {
e.printStackTrace();
}
count = treeLayer.size();
previousTreeLayer = treeLayer;
}
}
String merkleRoot = (treeLayer.size() == 1) ? treeLayer.get(0) : "";
return merkleRoot;
}
}
- Block类的设置中,用merkleRoot来做数据生成哈希值,同时新增将交易记录添加到区块中的功能。
public class Block {
public String hash;
public String prevHash;
public long timestamp;
public int nonce;
public ArrayList<Transaction> transactions = new ArrayList<>();
public String merkleRoot;
public Block(String prevHash) {
this.prevHash = prevHash;
this.timestamp = new Date().getTime();
try {
this.hash = calculateHash();
} catch (Exception e) {
e.printStackTrace();
}
}
public String calculateHash() throws Exception {
String calculatedHash = StringUtil.applySha256(prevHash+merkleRoot+timestamp+nonce);
return calculatedHash;
}
public void mineBlock(int difficulty) {
merkleRoot = StringUtil.getMerkleRoot(transactions);
String target = new String(new char[difficulty]).replace('\0', '0');
while(!hash.substring(0, difficulty).equals(target)) {
nonce++;
try {
hash = calculateHash();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("nonce:" + nonce);
}
public boolean addTransaction(Transaction transaction) {
if(transaction == null) return false;
if(!prevHash.equals("0")) {
if(!transaction.processTransaction()) {
System.out.println("交易处理失败!");
return false;
}
}
transactions.add(transaction);
System.out.println("交易成功添加到Block中!");
return true;
}
}
- ZJchain类:加强验证区块链有效功能,在将一个块添加到区块前进行挖矿计算。
public class ZJChain {
public static ArrayList<Block> blockChain = new ArrayList<Block>();
public static int difficulty = 5;
public static HashMap<String, TransactionOutput> UTXOs = new HashMap<>();
public static float minimumTransaction = 0.1f;
public static Wallet walletA;
public static Wallet walletB;
public static Transaction genesisTransaction;
public boolean isChainValid() {
Block curBlock;
Block prevBlock;
String target = new String(new char[difficulty]).replace('\0', '0');
HashMap<String, TransactionOutput> tempUTXOs = new HashMap<>();
tempUTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));
for(int i = 1; i < blockChain.size(); i++) {
curBlock = blockChain.get(i);
prevBlock = blockChain.get(i - 1);
try {
if(!curBlock.hash.equals(curBlock.calculateHash())) {
System.out.println("block的hash值计算错误");
return false;
}
} catch (Exception e) {
e.printStackTrace();
}
if(!prevBlock.hash.equals(curBlock.prevHash)) {
System.out.println("当前block与前面block的hash值不对应");
return false;
}
if(!curBlock.hash.substring(0, difficulty).equals(target)) {
System.out.println("当前块未满足挖矿难度标准!");
return false;
}
TransactionOutput tempOutput;
for(int t = 0; i < curBlock.transactions.size(); t++) {
Transaction currentTransaction = curBlock.transactions.get(t);
try {
if(!currentTransaction.verifySignature()) {
System.out.println("第" + t + "个交易的签名无效!");
return false;
}
} catch (Exception e) {
e.printStackTrace();
}
if(!(currentTransaction.getInputsValue() == currentTransaction.getOutputsValue())) {
System.out.println("第" + t + "个交易的交易输出与交易输入额不相等!");
return false;
}
for(TransactionInput input : currentTransaction.inputs) {
tempOutput = tempUTXOs.get(input.transactionOutputId);
if(tempOutput == null) {
System.out.println("第" + t + "个交易的交易输入不存在!");
return false;
}
if(input.UTXO.value != tempOutput.value) {
System.out.println("第" + t + "个交易的交易输入的值无效!");
return false;
}
tempUTXOs.remove(input.transactionOutputId);
}
for(TransactionOutput output : currentTransaction.outputs) {
tempUTXOs.put(output.id, output);
}
if(currentTransaction.outputs.get(0).recipient != currentTransaction.recipient) {
System.out.println("第" + t + "个交易的交易输出目的方错误!");
return false;
}
if(currentTransaction.outputs.get(1).recipient != currentTransaction.sender) {
System.out.println("第" + t + "个交易的找零的交易输出没有发给发送者!");
return false;
}
}
}
System.out.println("区块链有效!");
return true;
}
public void addBlock(Block newBlock) {
newBlock.mineBlock(difficulty);
blockChain.add(newBlock);
}
public String toJson() {
String blockChainString = JSON.toJSONString(blockChain);
return blockChainString;
}
public void setDifficulty(int difficulty) {
this.difficulty = difficulty;
}
}
8. 测试
public void test2() {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
ZJChain zjChain = new ZJChain();
Wallet walletA = new Wallet();
Wallet walletB = new Wallet();
Wallet coinBase = new Wallet();
System.out.println("第一次交易:coinbase向walletA转账100");
ZJChain.genesisTransaction = new Transaction(coinBase.publicKey, walletA.publicKey, 100f, null);
ZJChain.genesisTransaction.generateSignature(coinBase.privateKey);
ZJChain.genesisTransaction.transactionId = "0";
ZJChain.genesisTransaction.outputs.add(new TransactionOutput(
ZJChain.genesisTransaction.recipient,
ZJChain.genesisTransaction.value,
ZJChain.genesisTransaction.transactionId));
ZJChain.UTXOs.put(ZJChain.genesisTransaction.outputs.get(0).id,
ZJChain.genesisTransaction.outputs.get(0));
System.out.println("挖矿生成第一个区块。。。");
Block genesis = new Block("0");
genesis.addTransaction(ZJChain.genesisTransaction);
zjChain.addBlock(genesis);
System.out.println("第二笔交易: walletA向walletB转账20");
System.out.println("WalletA的余额:" + walletA.getBalance());
System.out.println("WalletB的余额:" + walletB.getBalance());
System.out.println("挖矿生成第二个区块。。。");
Block block1 = new Block(genesis.hash);
block1.addTransaction(walletA.sendFunds(walletB.publicKey, 20f));
zjChain.addBlock(block1);
System.out.println("第二笔交易结束");
System.out.println("WalletA的余额:" + walletA.getBalance());
System.out.println("WalletB的余额:" + walletB.getBalance());
zjChain.isChainValid();
}
- 结果:
9. 写在最后
- 不得不说,比特币的出现是一次思维的革命。写这个小项目的时候虽然我没有去涉及高深的密码学算法,但是其中蕴含的天才思维逻辑也依旧让我震撼不已(同时也迷糊不已,即使已经粗略写了一下,很多地方还是不能理解得很清楚。。。)。
- 再想到比特币的创始人至今不知道其真实姓名,此时看这神秘的“中本聪”仿佛有了看金庸笔下的隐世高手的感觉。这或许就是程序员的浪漫吧!
|