关键词:
这篇文章将去介绍如何使用区块链进行交易。
【本文禁止任何形式的全文粘贴式转载,本文来自 zacky31 的随笔】
目标:
在上一篇文章中,我们已经创建了一个可信任的区块链。但是目前所创建的链中包含的有用信息还是比较少的。今天,我将会用交易信息来替换之前的 data 内容,我将会创建一个简单的加密货币,叫作 “noobcoin”。
前提:
- 已经了解了区块链基本知识
- 用到 GSON 和 bounceycastle
开始吧
在加密货币中,货币的所有权将会以交易的方式被传递,参与交易的人将会有一个地址,用来发送和接收交易金额。
上图,反映了一次交易过程。
首先,我们创建一个 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开发智能合约nft-第五篇(铸造第一个nft)(代码片段)
...把手教你区块链java开发智能合约nft-第五篇(铸造第一个NFT)回顾部署NFT721铸造NFT根据tokenId查看NFT链上数据完整代码[NFT721相关智能合约源码下载](https://download.csdn.net/download/huangxuanheng/86260438)手把手教你区块链java开发智能... 查看详情
ctf的区块链入门资料(代码片段)
文章目录编写课件的心路历程第一阶段第二阶段题目列表靶场SecurityInnovationBlockchainCTF合约逆向其他常用平台区块链安全公司相关编写课件的心路历程第一阶段查找半个小时,发现CTF区块链的资料很少,稍微有点慌。在找... 查看详情
第一行代码:以太坊-创建自己的私有区块链(代码片段)
本文会利用以太坊客户端(geth)搭建一个私有区块链,并在这个私有区块链上挖矿,通过本文的案例,读者可以更深入理解区块链、以太坊、挖矿的理论。通过阅读本文,您可以:掌握搭建以太坊开发环境的方法掌握geth的基本... 查看详情
第一行代码:以太坊-创建自己的私有区块链(代码片段)
本文会利用以太坊客户端(geth)搭建一个私有区块链,并在这个私有区块链上挖矿,通过本文的案例,读者可以更深入理解区块链、以太坊、挖矿的理论。通过阅读本文,您可以:掌握搭建以太坊开发环境的方法掌握geth的基本... 查看详情
搭建你的第一个区块链网络(代码片段)
前一篇文章:搭建你的第一个区块链网络(一)共识与本地化POW共识共识机制也是区块链系统中不可缺少的一部分,在比特币网络中,使用的是POW共识,概念相对比较简单,所以我们在该项目中使用POW共识机制(后期如果可以的话修... 查看详情
搭建你的第一个区块链网络(代码片段)
前一篇文章:搭建你的第一个区块链网络(一)共识与本地化POW共识共识机制也是区块链系统中不可缺少的一部分,在比特币网络中,使用的是POW共识,概念相对比较简单,所以我们在该项目中使用POW共识机制(后期如果可以的话修... 查看详情
wdk学习笔记第四周_区块链总体简述(代码片段)
...三、以太坊简述四、超级账本简述总结参考摘要区块链是一个单向链式存储结构,区块中存储了前一个区块的hash值,交易数据等,区块间通过区块中存储的“前一个区块的hash值”连接,当链中某个区块数据被删... 查看详情
fiscobcos开发第一个区块链应用--小白实战(代码片段)
官方链接简单的步骤直接跳过了,主要是记录下整个过程中出现问题最多的地方4.创建区块链应用项目第一步.安装环境首先,我们需要安装JDK以及集成开发环境首先,在官网上下载JDK14(JDK1.8至JDK14都支持)并... 查看详情
创建自己的区块链网络六(代码片段)
目录前言系列文章直通车一、编写生成初始区块命令二、运行start.sh结语前言我们之前修改了几个配置文件,新建了一个start.sh文件并赋予了它权限,那么现在我们就是编写命令来启动网络了。系列文章直通车名称链接创... 查看详情
区块链的简单实现(代码片段)
...区块hash验证块的有效性记录数据从其他节点接收一个块编写测试代码测试结果完整代码什么是区块链一个维护着一个持续增长的有序数据记录列表的这么一个分布式数据库下面我将简单用nodejs实现一个简单的区块链,它具有... 查看详情
创建第一个substrate区块链(代码片段)
创建第一个Substrate区块链区块链软件使单个计算机(称为节点)能够相互通信以形成分散的点对点(P2P)网络。为保证链上数据的安全和链上的持续进展,节点通过某种形式的共识,就每个数据块中的数据状态以及... 查看详情
区块链-1了解(代码片段)
...概念区块链技术用区块去记录流水账。(比特币的第一个区块–创世区块)支付宝的相护宝是用了蚂蚁的区块链技术。区块链的优点去中心化,更安全分布式系统通过互联网来维护一个公共的账本,由两个以上节... 查看详情
12.fabric2.2区块链农产品溯源系统-智能合约开发-2(代码片段)
...发。1.查看智能合约设计查看第八节复习智能合约设计2.编写框架逻辑编写Invoke方法,根据发送给合约的方法将交易分发给不同的处理逻辑3.根据不同API构建合约索引(API设计参考第节、索引参考第九节)1)recordCrops逻辑参数:农... 查看详情
用java代码实现区块链技术(代码片段)
...f01;简言之,区块链首先让我们快速总结一下区块链。一个块包含一些标题信息和一组或一块任何类型数据的事务。链从第一个(起源)块开始。在添加/追加事务时,将根据一个块中可以存储的事务数量创建新的... 查看详情
python区块链教程(代码片段)
...,并建立我们自己的区块链。SatoshiNakamoto创建了世界上第一个虚拟货币,称为比特币。你可能想启用自己的货币,我们称为TPCion(TutorialsPointC 查看详情
区块链技术:truffle开发入门(代码片段)
...平台,而truffle是以太坊(Ethereum)最受欢迎的一个开发框架,这是我们第一篇区块链技术文章介绍truffle的原因,实战是最重要的事情,这篇文章不讲原理,只搭建环境,运行第一个区块链程序(... 查看详情
我居然用java构建了一个属于我自己的区块链(代码片段)
...#xff0c;区块链的设计是不可变的。在这里,我们将设计一个简单的区块链,它将帮助您理解散列如何使区块链不可变,只需几行Java代码。注:这只是一个演示区块链,真正的区块链涉及很多其他东西,如共... 查看详情
区块链开发编写调试第一个以太坊智能合约
... 智能合约IDE简介 目前以太坊上支持三种语言编写智能合约, Solidity:类似JavaScript,这是以太坊官方推荐语言,也是最流行的智能合约语言。具体用法参加Solidity文档,地址:https://solidity.read... 查看详情