这篇文章将去介绍如何使用区块链进行交易。

【本文禁止任何形式的全文粘贴式转载,本文来自 zacky31 的随笔】

目标:

上一篇文章中,我们已经创建了一个可信任的区块链。但是目前所创建的链中包含的有用信息还是比较少的。今天,我将会用交易信息来替换之前的 data 内容,我将会创建一个简单的加密货币,叫作 “noobcoin”。

前提:

  • 已经了解了区块链基本知识
  • 用到 GSONbounceycastle 

开始吧

在加密货币中,货币的所有权将会以交易的方式被传递,参与交易的人将会有一个地址,用来发送和接收交易金额。

上图,反映了一次交易过程。

首先,我们创建一个 Wallet 类,用来存放公钥和私钥。

import java.security.PrivateKey;
import java.security.PublicKey; public class Wallet {
public PrivateKey privateKey;
public PublicKey publicKey; }

那么,创建好的公钥和私钥是干嘛用的呢?

其实,公钥对于货币来说就是我们所需要的地址。在每次交易过程中可以共享给交易方的。我们的私钥是用来加密每次的交易,这样保证了拥有私钥的人才能够进行交易。私钥只能被自己说知道!同时,公钥伴随着交易时传递给交易方,可以用来去验证签名,判断数据是否被篡改。

接下来,就需要生成公钥和私钥键值对。我将会使用椭圆曲线加密来生成。那就来修改一下 Wallet 类吧。

import java.security.*;
import java.security.spec.ECGenParameterSpec; public class Wallet {
public PrivateKey privateKey;
public PublicKey publicKey; public Wallet() {
generateKeyPair();
} private void generateKeyPair() {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
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) {
throw new RuntimeException(e);
}
} public static void main(String[] args) {
Wallet wallet = new Wallet();
System.out.println(wallet.publicKey);
System.out.println(wallet.privateKey);
}
}

到目前为止,我们已经创建好了钱包,去考虑如何设计交易吧。

每一次交易过程中,都需要携带一下数据:

  • 付款方的公钥
  • 收款方的公钥
  • 交易金额
  • 输入,之前的交易参考,证明付款方有资金进行交易
  • 输出,显示这次交易中接收的相关地址
  • 加密签名,用来证明交易过程中数据未被篡改

那赶紧去创建一个 Transaction 类吧。

import java.security.PublicKey;
import java.util.ArrayList; public class Transcation {
public String transcationId;
public PublicKey sender;
public PublicKey reciepient;
public float value;
public byte[] signature; public ArrayList<TranscationInput> inputs = new ArrayList<TranscationInput>();
public ArrayList<TranscationOutput> outputs = new ArrayList<TranscationOutput>(); private static int sequence = 0; public Transcation(PublicKey from, PublicKey to, float value, ArrayList<TranscationInput> inputs) {
this.sender = from;
this.reciepient = to;
this.value = value;
this.inputs = inputs;
} private String calulateHash() {
sequence++;
return StringUtil.applySha256(
StringUtil.getStringFromKey(sender) +
StringUtil.getStringFromKey(reciepient) +
Float.toString(value) + sequence
);
} }

目前为止,程序会报错,一会,我将会创建 TransactionInput 类 和 TransactionOutput 类。同时,交易类中需要包含生成和验证签名的方法以及验证交易的方法。不过,这些都得稍等一下。

考虑一下签名的目的以及其工作方式?

签名在区块链中起到两个重要的作用:首先,签名保证了付款方的消费;其次,阻止了其他人篡改已发生的交易。这就是利用私钥加密数据,公钥去验证其可靠性。

那接下来就开始去做这些事情吧。在此之前,在 StringUtils 类中,添加两个方法。applyECDSASig()  用来生成签名,verifyECDSASig()  用来去验证签名。

 public static byte[] applyECDSASig(PrivateKey privateKey, String input) {
Signature dsa;
byte[] output = new byte[0];
try {
dsa = Signature.getInstance("ECDSA", "BS");
dsa.initSign(privateKey);
byte[] strByte = input.getBytes();
dsa.update(strByte);
byte[] realSig = dsa.sign();
output = realSig;
} catch (Exception e) {
throw new RuntimeException(e);
}
return output;
} public static boolean verifyECDSASig(PublicKey publicKey,String data,byte[] signature){
try{
Signature ecdsaVerify = Signature.getInstance("ECDSA","BC");
ecdsaVerify.initVerify(publicKey);
ecdsaVerify.update(data.getBytes());
return ecdsaVerify.verify(signature);
}catch (Exception e){
throw new RuntimeException(e);
}
} public static String getStringFromKey(Key key){
return Base64.getEncoder().encodeToString(key.getEncoded());
}

接着,利用刚刚工具类中的方法,在 Transaction 类中添加 generateSignature()  和 verifiySignatur() 方法。

 public void generateSignature(PrivateKey privateKey) {
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient);
signature = StringUtil.applyECDSASig(privateKey, data);
} public boolean verfiySignature() {
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient);
return StringUtil.verifyECDSASig(sender, data, signature);
}

已经完成了一大半了,先去 Chain 类中去测试一下吧。

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
import java.util.ArrayList; public class Chain {
public static ArrayList<Block> blockChain = new ArrayList<>();
public static int difficulty = 5;
public static Wallet walletA;
public static Wallet walletB; public static void main(String[] args) {
Security.addProvider(new BouncyCastleProvider());
walletA = new Wallet();
walletB = new Wallet(); System.out.println("Private and Public keys:");
System.out.println(StringUtil.getStringFromKey(walletA.privateKey));
System.out.println(StringUtil.getStringFromKey(walletA.publicKey)); Transcation transcation = new Transcation(walletA.publicKey, walletB.publicKey, 5, null);
transcation.generateSignature(walletA.privateKey); System.out.println("Is signature verified");
System.out.println(transcation.verfiySignature());
}
}

我们可以看到,成功生成了私钥公钥,并能够去验证真实性。

考虑这样一个场景,当你拥有一个比特币,你必须前面收到过一个比特币,比特币的账本不会在你的账户中增加一个比特币也不会从消费者那里减去一个比特币,消费者只能指向他或她之前接受过一个比特币,所以一个交易输出被创建用来显示一个比特币发送给你的地址(交易的输入指向前一个交易的输出)。

从这一个点出发,我们会依照比特币中的说明,把所有未使用的交易输出称为 UTXO

那么去创建一个 TransactionInput 类。

public class TransactionInput {
public String transactionOutputId;
public TransactionOutput UTXO; public TransactionInput(String transactionOutputId) {
this.transactionOutputId = transactionOutputId;
}
}

以及 TransactionOutputs 类。 交易的输出会显示从这次交易中给每一方最终发送的金额,从而在新的交易中被引用未输入,作为证明你可以发送的金额数量。

import java.security.PublicKey;
public class TransactionOutput {
public String id;
public PublicKey reciepient;
public float value;
public String parentTransactionId; public TransactionOutput(PublicKey reciepient, float value, String parentTransactionId) {
this.reciepient = reciepient;
this.value = value;
this.parentTransactionId = parentTransactionId;
this.id = StringUtil.applySha256(StringUtil.getStringFromKey(reciepient)+Float.toString(value)+parentTransactionId);
} public boolean isMine(PublicKey publicKey) {
return (publicKey == reciepient);
}
}

在链条中的区块会收到很多交易信息,所以区块链会非常非常的长,这就会花费很长时间来处理一个新的交易因为我们必须寻找和检查它的输入,为了绕过这个我们保存了一个额外的集合称之为为使用的交易作为可用的输入,所以在主函数中增加一个集合称为 UTXO

那现在修改一下 Transaction 类。添加处理交易的方法。

public boolean processTransaction() {
if(verifySignature() == false) {
System.out.println("#Transaction Signature failed to verify");
return false;
} for(TransactionInput i : inputs) {
i.UTXO = Chain.UTXOs.get(i.transactionOutputId);
} if(getInputsValue() < Chain.minimumTransaction) {
System.out.println("Transaction Inputs too small: " + getInputsValue());
System.out.println("Please enter the amount greater than " + Chain.minimumTransaction);
return false;
} float leftOver = getInputsValue() - value;
transactionId = calulateHash();
outputs.add(new TransactionOutput( this.reciepient, value,transactionId));
outputs.add(new TransactionOutput( this.sender, leftOver,transactionId)); for(TransactionOutput o : outputs) {
Chain.UTXOs.put(o.id , o);
} for(TransactionInput i : inputs) {
if(i.UTXO == null) continue;
Chain.UTXOs.remove(i.UTXO.id);
} return true;
} public float getInputsValue() {
float total = 0;
for(TransactionInput i : inputs) {
if(i.UTXO == null) continue;
total += i.UTXO.value;
}
return total;
}

这个方法中我们执行了一些检查来确保交易是有效的,我们收集了输入来产生输出,最重要的是,到了喂结束的时候,我们抛弃了输入在我们的 UTXO 列表,这就意味着一个可以使用的交易输出必须之前一定是输入,所以输入的值必须被完全使用,所以付款人必须改变它们自身的金额状态。这和我们实际的情况非常类似,当你给出5元然后花费了1元,就会找给你4元。

最后,更新一下 Wallet ,添加获取余额等方法。你可以随意添加一些其他功能都您的钱包中去,比如保存您的交易的历史记录等等。

public float getBalance() {
float total = 0;
for (Map.Entry<String, TransactionOutput> item: Chain.UTXOs.entrySet()){
TransactionOutput UTXO = item.getValue();
if(UTXO.isMine(publicKey)) {
UTXOs.put(UTXO.id,UTXO);
total += UTXO.value ;
}
}
return total;
} public Transaction sendFunds(PublicKey _recipient,float value ) {
if(getBalance() < value) {
System.out.println("#Not Enough funds to send transaction. Transaction Discarded.");
return null;
}
ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>(); 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;
}

现在,已经有一个交易系统了,需要将其放到区块链中。我们把区块中的一些没有用的信息替换成交易的列表,但是在一个单一的区块中可能存放了1000个交易,这就会导致大量的 hash 计算,不用担心在这里我们使用了交易的merkle 树,稍后你会看到。让我们增加一个帮助方法来创建 merkleroot StringUtils 类中

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++) {
treeLayer.add(applySha256(previousTreeLayer.get(i - 1) + previousTreeLayer.get(i)));
}
count = treeLayer.size();
previousTreeLayer = treeLayer;
}
String merkleRoot = (treeLayer.size() == 1) ? treeLayer.get(0) : "";
return merkleRoot;
} public static String getDificultyString(int difficulty) {
return new String(new char[difficulty]).replace('\0', '0');
}

Block 类代码如下:

import java.util.ArrayList;
import java.util.Date; public class Block { public String hash;
public String previousHash;
public String merkleRoot;
public String data;
public ArrayList<Transaction> transactions = new ArrayList<>();
public long timeStamp;
public int nonce; public Block(String previousHash) {
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash = calculateHash();
} public Block(String data, String previousHash) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash = calculateHash();
} public String calculateHash() {
String calculatedhash = StringUtil.applySha256(
previousHash +
Long.toString(timeStamp) +
Integer.toString(nonce) +
merkleRoot
);
return calculatedhash;
} public void mineBlock(int difficulty) {
merkleRoot = StringUtil.getMerkleRoot(transactions);
String target = StringUtil.getDificultyString(difficulty);
while (!hash.substring(0, difficulty).equals(target)) {
nonce++;
hash = calculateHash();
}
System.out.println("Block Mined!!! : " + hash);
} public boolean addTransaction(Transaction transaction) {
if (transaction == null) return false;
if ((previousHash != "0")) {
if ((transaction.processTransaction() != true)) {
System.out.println("Transaction failed to process. Discarded.");
return false;
}
}
transactions.add(transaction);
System.out.println("Transaction Successfully added to Block");
return true;
} }

完整的测试一下。

之前我们已经测试过从钱包中发送货币,然后修改区块链进行有效性检查。但是首先让我们创建一些新的货币吧,有很多方法来创建新的货币,以比特币中的区块链举个例子:挖矿者挖矿成功就会得到一个奖励。但在这里我们只希望在创世纪区块中释放货币。就像比特币中一下,所以我们修改我们的主函数以达到下面的目的。

  • 创世纪区块发布100个货币给 walletA
  • 修改区块链进行有效性验证该账户是否进行了交易
  • 进行测试看是否一切都在运行中

Chain 类。

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
import java.util.ArrayList;
import java.util.HashMap; public class Chain {
public static ArrayList<Block> blockchain = new ArrayList<>();
public static HashMap<String, TransactionOutput> UTXOs = new HashMap<>();
public static int difficulty = 3;
public static float minimumTransaction = 0.1f;
public static Wallet walletA;
public static Wallet walletB;
public static Transaction genesisTransaction; public static void main(String[] args) {
Security.addProvider(new BouncyCastleProvider());
walletA = new Wallet();
walletB = new Wallet(); Wallet coinbase = new Wallet(); genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null);
genesisTransaction.generateSignature(coinbase.privateKey);
genesisTransaction.transactionId = "0";
genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId));
UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0)); System.out.println("Creating and Mining Genesis block... ");
Block genesis = new Block("0");
genesis.addTransaction(genesisTransaction);
addBlock(genesis); //testing
Block block1 = new Block(genesis.hash);
System.out.println("\nWalletA's balance is: " + walletA.getBalance());
System.out.println("\nWalletA is Attempting to send funds (40) to WalletB...");
block1.addTransaction(walletA.sendFunds(walletB.publicKey, 40f));
addBlock(block1);
System.out.println("\nWalletA's balance is: " + walletA.getBalance());
System.out.println("WalletB's balance is: " + walletB.getBalance()); Block block2 = new Block(block1.hash);
System.out.println("\nWalletA Attempting to send more funds (1000) than it has...");
block2.addTransaction(walletA.sendFunds(walletB.publicKey, 1000f));
addBlock(block2);
System.out.println("\nWalletA's balance is: " + walletA.getBalance());
System.out.println("WalletB's balance is: " + walletB.getBalance()); Block block3 = new Block(block2.hash);
System.out.println("\nWalletB is Attempting to send funds (20) to WalletA...");
block3.addTransaction(walletB.sendFunds(walletA.publicKey, 20));
System.out.println("\nWalletA's balance is: " + walletA.getBalance());
System.out.println("WalletB's balance is: " + walletB.getBalance()); isChainValid();
} public static void addBlock(Block newBlock) {
newBlock.mineBlock(difficulty);
blockchain.add(newBlock);
} public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
String hashTarget = 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++) { currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i - 1);
if (!currentBlock.hash.equals(currentBlock.calculateHash())) {
System.out.println("#Current Hashes not equal");
return false;
}
if (!previousBlock.hash.equals(currentBlock.previousHash)) {
System.out.println("#Previous Hashes not equal");
return false;
}
if (!currentBlock.hash.substring(0, difficulty).equals(hashTarget)) {
System.out.println("#This block hasn't been mined");
return false;
} TransactionOutput tempOutput;
for (int t = 0; t < currentBlock.transactions.size(); t++) {
Transaction currentTransaction = currentBlock.transactions.get(t); if (!currentTransaction.verifySignature()) {
System.out.println("#Signature on Transaction(" + t + ") is Invalid");
return false;
}
if (currentTransaction.getInputsValue() != currentTransaction.getOutputsValue()) {
System.out.println("#Inputs are note equal to outputs on Transaction(" + t + ")");
return false;
} for (TransactionInput input : currentTransaction.inputs) {
tempOutput = tempUTXOs.get(input.transactionOutputId); if (tempOutput == null) {
System.out.println("#Referenced input on Transaction(" + t + ") is Missing");
return false;
} if (input.UTXO.value != tempOutput.value) {
System.out.println("#Referenced input Transaction(" + t + ") value is Invalid");
return false;
} tempUTXOs.remove(input.transactionOutputId);
} for (TransactionOutput output : currentTransaction.outputs) {
tempUTXOs.put(output.id, output);
} if (currentTransaction.outputs.get(0).reciepient != currentTransaction.reciepient) {
System.out.println("#Transaction(" + t + ") output reciepient is not who it should be");
return false;
}
if (currentTransaction.outputs.get(1).reciepient != currentTransaction.sender) {
System.out.println("#Transaction(" + t + ") output 'change' is not sender.");
return false;
} } }
System.out.println("Blockchain is valid");
return true;
}
}

运行结果。

现在钱包可以在区块链中安全的发送金额,当钱包拥有金额时才可以发送给别人。这也就意味着你拥有了你自己的加密货币。

总结一下,我们在区块链中实现了:

  • 允许所有用户创建钱包
  • 利用 ECDSA 为钱包创建公钥和私钥
  • 通过数字签名算法来证明所有权这样可以安全的转移资金
  • 最后允许所有用户可以在区块链中增加交易

【原】用Java编写第一个区块链(二)的更多相关文章

  1. 【原】用Java编写第一个区块链(一)

    写这篇随笔主要是尝试帮助自己了解如何学习区块链技术开发. [本文禁止任何形式的全文粘贴式转载,本文来自 zacky31 的随笔] 目标: 创建一个最基本的"区块链" 实现一个简单的 ...

  2. 用Java编写第一个区块链

    原文地址:https://www.cnblogs.com/zacky31/p/9057193.html 目标: 创建一个最基本的“区块链” 实现一个简单的挖矿系统 前提: 对面向对象编程有一定的基础 ...

  3. 我是如何从Java转型为Go区块链工程师

    我是如何从Java转型为Go区块链工程师 本文来自于一个比原链核心开发的陈述 前言 IT部落在加入比原链之前一直是做Java开发的,当初看到Go还有点犹豫,还怕过不了面试,结果是否掌握一门语言的考量确 ...

  4. 用Java实现简单的区块链

    用 Java 实现简单的区块链 1. 概述 本文中,我们将学习区块链技术的基本概念.也将根据概念使用 Java 来实现一个基本的应用程序. 进一步,我们将讨论一些先进的概念以及该技术的实际应用. 2. ...

  5. 使用 java 创建你的第一个区块链(第二部分)

    本系列教程的目的是帮助您了解如何开发区块链技术. 在这第二个教程中,我们将: 创建一个简单的钱包: 使用我们的区块链发送已签名的交易: 感觉更酷. 以上这些将使我们拥有自己的加密货币! 从上一个教程开 ...

  6. 使用 java 创建你的第一个区块链(第一部分)

    本系列教程的目的是帮助您了解如何开发区块链技术. 在本教程中,我们将: 创建你的第一个(非常)基本的“区块链”. 实施简单的工作证明(采矿)系统. 惊叹于可能性. (我假设您对面向对象编程有基本的了解 ...

  7. [C#]使用 C# 编写自己的区块链挖矿算法

    [C#] 使用 C# 编写自己区块链的挖矿算法 文章原文来自:Code your own blockchain mining algorithm in Go! ,原始文章通过 Go 语言来实现的,这里 ...

  8. 基于java实现的简单区块链

    技术:maven3.0.5 + jdk1.8   概述 区块链是分布式数据存储.点对点传输.共识机制.加密算法等计算机技术的新型应用模式.所谓共识机制是区块链系统中实现不同节点之间建立信任.获取权益的 ...

  9. android和java以太坊开发区块链应用使用web3j类库

    如何使用web3j为Java应用或Android App增加以太坊区块链支持,教程内容即涉及以太坊中的核心概念,例如账户管理包括账户的创建.钱包创建.交易转账,交易与状态.智能合约开发与交互.过滤器和 ...

随机推荐

  1. iOS socket 笔记

    ios 客服端: 下载 AsyncSocket 开发框架,拖到项目中 //建立 #import "ViewController.h" #import <sys/socket. ...

  2. thinkphp中page方法

    page方法也是模型的连贯操作方法之一,是完全为分页查询而诞生的一个人性化操作方法. 用法 我们在前面已经了解了关于limit方法用于分页查询的情况,而page方法则是更人性化的进行分页查询的方法,例 ...

  3. 文件墙 CFilewall

    文件墙 CFilewall 记于 2013-09-26 == @[代码] [C#] []WPF] #### 使用了一些公司的组件和放大,但是不多,可以单独抽取出来 --- 程序结构 - Control ...

  4. 王学长的AAA树

    让我们响应王学长的号召勇敢的分开写splay和lct吧! 分开写大法好!!!!!!!!!!!杜教的ch[4]弱爆了!!!! #include <stdio.h> #include < ...

  5. C++ Builder多线程编程技术经验谈(转)

    源:C++ Builder多线程编程技术经验谈 线程之可行性   在很多情况下,可能需要为程序创建线程.这里给出其中一些可能性: (1)如果创建的是一个多文档接口(Multiple Document ...

  6. pandas.DataFrame的pivot()和unstack()实现行转列

    示例: 有如下表需要进行行转列: 代码如下: # -*- coding:utf-8 -*- import pandas as pd import MySQLdb from warnings impor ...

  7. webdriver保存验证码截图

    element = wait.until ( EC.visibility_of_element_located((By.CSS_SELECTOR,'.quc-main .quc-field-captc ...

  8. spring 在容器中一个bean依赖另一个bean 需要通过ref方式注入进去 通过构造器 或property

    spring  在容器中一个bean依赖另一个bean 需要通过ref方式注入进去 通过构造器 或property

  9. 值得收藏:一份非常完整的 MySQL 规范

    一.数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割. 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来). 数据库对象的命名要能 ...

  10. 在myeclipse上发布自己的webservice

       什么是WebServices? 它是一种构建应用程序的普遍模型,可以在任何支持网络通信的操作系统中实施运行;它是一种新的web应用程序分支,是自包含.自描述.模块化的应用,可以发布.定位.通过w ...