用Java创建你第一个区块链-part2

      在上一章节中,我们主要讲述了区块链,区块,挖矿等等,设定了区块的数据格式,实现了如何通过加密的方式将区块加入到区块链中,以此来保证区块链的有效性,同时我们还通过设计一种数据难题,让区块加入到区块链中需要通过一定的计算能力(PoW)来证明是区块链中的一个新的区块。

      而在这一章节中,我们将会实现下面几个功能

      1、创建一个简单的钱包(wallet)

      2、运用我们的区块链来发送交易(transaction)

      这样就会产生我们自己的加密货币。

      在此之前,我们先来理解一下比特币中关于这部分的概念,交易就是比特币的核心,区块链的唯一意图就是要通过安全的可靠的方式进行存储交易,交易一旦被创建就没有人可以修改它们。

      只要你开发过web应用程序,你就知道为了实现交易你必须在数据库中创建:账户表和交易表,账户表中存放的是每一个账户的信息,包括账户的个人资料以及余额,而交易表中存放了每一笔交易从一个账户到另外一个账户。但在比特币中,交易的实现方式与web应用中的实现方式完全不同。在比特币中:

        1、没有账户

        2、没有余额

        3、没有地址

        4、没有货币信息

        5、没有付款人,也没有收款人

        因为区块链是完全公开的、开源的数据库,我们不希望存储关于用户的任何隐私的消息,所以在账户表中不存在任何金额的信息,交易表中也没有存储金额从一个账户到另一个账户的信息,更没有存储账户的余额信息。仅仅只有这一次交易的信息,那么究竟交易中存放的是什么呢?让我们继续看下去。

        从上一章节用Java创建你第一个区块链-part1,我们已经有了一个基本的区块链,但是在区块中我们存放的是一些无用的信息(data),今天我们将用交易替换这些信息,让我们的区块中也可以保存大量的交易信息,使得我们可以创建自己加密货币。注意,这篇课程是在上一篇课程的基础上实现的,并且依赖于bouncycastlegson

        第一步、准备一个钱包(wallet)

        在加密货币中,货币的所有权可以进行转移,在区块链中我们称之为交易,每个参与者都有一个自己私有的地址来发送或者是收取货币,这就类似于我们每个人都在银行中有一个独一无二的账户,这里的地址就类似于我们的账户。

        在我们的教程中,钱包就是用来存储地址信息的,同时钱包也可以让区块链产生新的交易。



        让我们创建一个Wallet,并拥有自己的公钥和私钥。

import java.security.*;

public class Wallet {
public PrivateKey privateKey;
public PublicKey publicKey;
}

      这时候大家可能会产生疑问,公钥和私钥究竟是起到什么作用呢?其实公钥的作用就是地址,你可以分享你的公钥给别人以此来获取付款,而你的私钥的作用是为了对交易进行签名,这样其他人就不可以花费金额除非它拥有你的私钥,所以对于每个人而言我们必须保护好我们的私钥,不能透露我们的私钥信息给其他人。同时在我们进行交易的时候我们也会同时发送我们的公钥由此来验证我们的签名是有效的而且没有数据被篡改。


      即私钥用来签名不想被篡改的数据,而公钥是用来验证签名的。

      我们通过密钥对(KeyPair)的方式来创建我们的公钥和私钥,密钥对采用的是椭圆曲线加密算法(ECDSA),我们在wallet类中的构造器中创建。

import java.security.*;

public class Wallet {
public PrivateKey privateKey;
public PublicKey publicKey;
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) {
throw new RuntimeException(e);
}
}
}

        你不需要完全理解椭圆曲线加密算法的核心逻辑究竟是什么,你只需要它是用来创建公钥和私钥,以及公钥和私钥分别所起到的作用是什么就可以了。

        现在我们已经又了钱包(wallet)类的大概框架,下面我们再来看一下交易(transaction)类。

        第二步、交易以及 数字签名

        在每一个交易中都会携带下面这些信息。

        1、资金付款人的公钥信息

        2、资金收款人的公钥信息

        3、转移的资金数量

        4、输入,参考之前的交易,证明付款人有资金可以发送

        5、输出,显示了在这次交易中接受的相关地址(这些输出被参考为新的交易中的输入)

        6、一个加密的签名,用来证明拥有者的地址可以发送交易而且数据没有被修改(阻止第三方机构更改发送的数量)

        让我们创建一个交易类。

import java.security.*;
import java.util.ArrayList;

public class Transaction {
public String transactionId; // 交易的hash编号
public PublicKey sender; // 付款人地址 公钥
public PublicKey reciepient; // 接受人地址 公钥
public float value;//转移金额
public byte[] signature; // 数字签名,防止其他人从我们的钱包中发送资金、
//输入列表
public ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>();

       //输出列表

public ArrayList<TransactionOutput> outputs = new ArrayList<TransactionOutput>();

//多少个交易已经被创建
 private static int sequence = 0;
//构造器
public Transaction(PublicKey from, PublicKey to, float value, ArrayList<TransactionInput> inputs) {
this.sender = from;
this.reciepient = to;
this.value = value;
this.inputs = inputs;
}
// 计算交易的hash值(用于交易编号)
private String calulateHash() {
sequence++; //增加sequence,用来防治两个不同的交易有相同的hash值
return StringUtil.applySha256(
StringUtil.getStringFromKey(sender) +
StringUtil.getStringFromKey(reciepient) +
Float.toString(value) + sequence
);
}
}
        让我们创建一个空的TransactionInput和TransactionOutput类,不用担心我在后面会填补并说明其用途。

        我们的交易类中已经有了相关的方法来创建和验证签名以及验证交易,那么签名的意图是什么,究竟它们是怎么工作的?签名在我们的区块链中执行了两个非常重要的任务:第一,签名用来保证只有货币的拥有者才可以用来发送自己的货币,第二,签名用来阻止其他人试图篡改提交的交易。即私钥被用来签名数据,而公钥用来验证其完整性。

        举个例子:小明想要发送2个加密货币给小红,他们用各自的钱包创建了交易,并提交到全网的区块链中作为一个新的区块,一个挖矿者试图篡改接受者把2个加密货币给小蓝,但是幸运的事,小明在交易数据中已经用私钥进行了签名,这就允许了全网中的任何节点进行验证数据是否已经被篡改通过使用小明的公钥(因为没有其他人的公钥可以用来验证交易)。

        我们可以从区块类中看到我们的签名是一串字符数据,所以让我们也创建一个方法来产生签名在StringUtil类中。

//应用ECDSA签名并产生字符数组
public static byte[] applyECDSASig(PrivateKey privateKey, String input) {
Signature dsa;
byte[] output = new byte[0];
try {
dsa = Signature.getInstance("ECDSA", "BC");
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;
}
//应用ECDSA验证数字签名
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());
}

       不用担心大多不能理解这些方法的内容,所有你需要知道的就是applyECDSASig方法的输入参数为付款人的私钥和需要加密的数据信息,签名后返回字符数组。而verifyECDSASig方法的输入参数为签名、公钥和需要加密的数据,调用该方法后返回true或false来说明签名是否是有效的。getStringFromKey返回任何key的编码字符串。

        让我们利用签名的方法在交易类中,增加generateSiganature() 和 varifiySignature()方法

//签名所有我们不想被篡改的数据
public void generateSignature(PrivateKey privateKey) {
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ;
signature = StringUtil.applyECDSASig(privateKey,data);
}
//验证我们已签名的数据没有被窜给过
public boolean verifiySignature() {
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ;
return StringUtil.verifyECDSASig(sender, data, signature);
}

        在实际的业务逻辑过程中,我们会签名更多的消息(比如已用的输入输出还有时间戳等等)。

        挖矿者会验证签名,只有签名验证成功后一个新的交易才能被添加到区块中去。

        第三步、测试交易和签名

        现在我们简单的进行一些测试,在主方法中,我们增加了一些新的变量也替换了我们主方法中的一些内容。

import java.security.Security;
import java.util.ArrayList;
import java.util.Base64;
import com.google.gson.GsonBuilder;

public class NoobChain {
public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static int difficulty = 5;
public static Wallet walletA;
public static Wallet walletB;
public static void main(String[] args) {
//调用Bouncey castle作为安全性的提供类
Security.addProvider(new org.bouncycastle.jce.provider.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));
//创建一个交易从WalletA地址到walletB地址
Transaction transaction = new Transaction(walletA.publicKey, walletB.publicKey, 5, null);

//用wallectA的私钥进行签名

transaction.generateSignature(walletA.privateKey);
//通过wallectA的公钥验证签名是否工作
System.out.println("Is signature verified");
System.out.println(transaction.verifiySignature());
}

        这样我们创建了两个钱包,以及两个钱包的公钥和私钥,并创建了一个交易,你的输出会类似于下图所示。


        接下来我们将创建并验证输入和输出,并把交易保存到区块链中去。

        第四步、输入输出1:如何证明加密货币是属于你的?

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

        你账户的余额是所有指向你的未使用交易的输出。

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

        让我们创建一个交易输入类(TransactionInput)

public class TransactionInput {
public String transactionOutputId; //指向交易输出类 -> transactionId
public TransactionOutput UTXO; //包含所有未使用的交易输出
public TransactionInput(String transactionOutputId) {
this.transactionOutputId = transactionOutputId;
}
}

        然后再增加一个交易输出类(TransactionOutput)

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);
}
}

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

        第五步、输入输出2:交易的处理

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

public class NoobChain {
public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static HashMap<String,TransactionOutputs> UTXOs = new HashMap<String,TransactionOutputs>(); //未使用的输出集合
public static int difficulty = 5;
public static Wallet walletA;
public static Wallet walletB;

public static void main(String[] args) {


        是时候言归正传了,让我们在交易类中增加一个processTransaction方法,这个方法是把一切放在一起用来处理交易。

//返回boolean值用来说明新的交集是否被创建
public boolean processTransaction() {
//验证签名
if(verifiySignature() == false) {
System.out.println("#Transaction Signature failed to verify");
return false;
}
//收集交易的输入(必须注意的是输入是未被使用的)
for(TransactionInput i : inputs) {
i.UTXO = NoobChain.UTXOs.get(i.transactionOutputId);
}

//检查交易是否是有效的
if(getInputsValue() < NoobChain.minimumTransaction) {
System.out.println("#Transaction Inputs to small: " + getInputsValue());
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) {
NoobChain.UTXOs.put(o.id , o);
}
//把已经使用的交易的输入从UTXO中移除
for(TransactionInput i : inputs) {
if(i.UTXO == null) continue; //if Transaction can't be found skip it
NoobChain.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;
}

//返回输出的总和
public float getOutputsValue() {
float total = 0;
for(TransactionOutput o : outputs) {
total += o.value;
}
return total;
}

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

        最后让我们修改我们的wallet类

        搜集余额(通过循环遍历UTXO列表来检查交易的输出是否是我的)并为我们自己创建交易。

import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class Wallet {
public PrivateKey privateKey;
public PublicKey publicKey;
public HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>(); //钱包自身的UTXO
public Wallet() {...
public void generateKeyPair() {...
//返回余额并保存钱包自身的UTXO
public float getBalance() {
float total = 0;
for (Map.Entry<String, TransactionOutput> item: NoobChain.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类中

//可追踪的交易列表并返回一个merkleroot
public static String getMerkleRoot(ArrayList<Transaction> transactions) {
int count = transactions.size();
ArrayList<String> previousTreeLayer = new ArrayList<String>();
for(Transaction transaction : transactions) {
previousTreeLayer.add(transaction.transactionId);
}
ArrayList<String> treeLayer = previousTreeLayer;
while(count > 1) {
treeLayer = new ArrayList<String>();
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;
}

        修改Block类,来应用merkle树

import java.util.ArrayList;
import java.util.Date;

public class Block {
public String hash;
public String previousHash;
public String merkleRoot;
public ArrayList<Transaction> transactions = new ArrayList<Transaction>(); //数据改为交易的列表
public long timeStamp; //时间戳
public int nonce;
//构造器
public Block(String previousHash ) {
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash = calculateHash();
}
//根据区块的内容计算hash值
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;
}
}

        需要注意的是我们修改Block的构造器,因为我们引入了merklet树来计算hash值。addTransaction方法用来增加交易,只有满足条件下才可以成功的在区块中增加交易。

        值得高兴的是我们的区块链已经实现了!

        第七步、完整的测试

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

        1、创世纪区块发布100个货币给walletA

        2、修改区块链进行有效性验证该账户是否进行了交易

        3、进行测试看是否一切都在运行中

        

public class NoobChain {
public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>();
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 org.bouncycastle.jce.provider.BouncyCastleProvider());
walletA = new Wallet();
walletB = new Wallet();
Wallet coinbase = new Wallet();
//创建创世纪交易,将100个货币发送给walletA
genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null);
genesisTransaction.generateSignature(coinbase.privateKey); //对创世纪交易进行签名
genesisTransaction.transactionId = "0"; //默认设置创世纪交易的输入为0
genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId)); //手工添加交易输出
UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0)); //保存我们第一个交易到UTXO列表中
System.out.println("Creating and Mining Genesis block... ");
Block genesis = new Block("0");
genesis.addTransaction(genesisTransaction);
addBlock(genesis);
//测试
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 Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
String hashTarget = new String(new char[difficulty]).replace('\0', '0');
HashMap<String,TransactionOutput> tempUTXOs = new HashMap<String,TransactionOutput>(); //未完成的临时交易列表
tempUTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));
//循环遍历区块链进行hash检查
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.verifiySignature()) {
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;
}
public static void addBlock(Block newBlock) {
newBlock.mineBlock(difficulty);
blockchain.add(newBlock);
}
}

        我们的输出将会如下所示。



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

        第八步、在区块链中完成交易

        现在我们已经成功的创建了自己的加密货币

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

        1、允许所有用户创建钱包

        2、利用ECDSA为钱包创建公钥和私钥

        3、通过数字签名算法来证明所有权这样可以安全的转移资金

        4、最后允许所有用户可以在区块链中增加交易

        你可以从以下地址下载工程NoobChain-Tutorial-Part-2-master

        下一章节中我们会继续讲述p2p网络,共识算法,以及区块的存储(耐心等待吧)




猜你喜欢

转载自blog.csdn.net/u010093971/article/details/79366801