Dominic

给十年后的自己

  • 主页
  • 琐记
所有文章 关于我

Dominic

给十年后的自己

  • 主页
  • 琐记

用Java创造你的第一个区块链-02


阅读数: 86次    2019-03-30

本文系博主在Medium上找到,翻译并作出了一定修改和解读的文章。原文链接附于末尾。

本系列教程的目标是帮助你构建一个如何开发区块链的蓝图。

在第二章我们将会:

  • 创建一个简单的钱包
  • 用我们的区块链发送签名标识的交易
  • 感到更cool 从上一篇教程中,我们得到了一个基础的可验证的区块链,但是目前我们的区块链只包含无用的信息,今天我们将把他们替换成交易(我们的区块链将可以保存多个交易),允许我们创建一个非常简单的加密货币(crypto-currency),我们将称我们的货币为:NoobCoin。

注意,你需要引入bounceycastle和GSON包.

1. 准备一个钱包

在加密货币中,货币的所有权以交易业务的形式传递,参与者们有一个可供货币发送和接收的地址。最基本的钱包只是存储地址(静态钱包),更多的钱包,则同时是可以在区块链上进行交易的软件。

所以让我们创建一个存储公钥和私钥的Wallet类:

1
2
3
4
5
6
7
package noobchain;
import java.security.*;

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

公钥和私钥都是干什么的?

对于我们的‘noobCoin’而言公钥相当于地址,为了和其他人交易,这个公钥是允许分享的。私钥用来签名我们的交易,所以除了私钥的主人,没有人可以花我们的noobCoin。用户必须保持私钥是秘密的,在交易的过程中我们也发送公钥,用来验证我们的签名是合法的,没有被篡改攻击。

签名是通过私钥,发送地址,接受地址和交易值决定的,任何人可以通过公钥+签名+交易发送、接收地址和值来校验签名是否正确。

我们生成一对私钥和公钥,利用 Elliptic-curve cryptography来生成键对。现在让我们对钱包类加上一个generateKeyPair()方法并在构造方法中调用他:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package noobchain;
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");
// Initialize the key generator and generate a KeyPair
keyGen.initialize(ecSpec, random); //256 bytes provides an acceptable security level
KeyPair keyPair = keyGen.generateKeyPair();
// Set the public and private keys from the keyPair
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
}catch(Exception e) {
throw new RuntimeException(e);
}
}

}

你要理解这个方法是用了java.security.KeyPairGenerator去生成椭圆曲线键对(Elliptic Curve KeyPair),这个方法产生和设置我们的公钥和私钥。

现在我们已经构造出了钱包类的基本轮廓,接下来看交易。

2. 交易和签名

每一笔交易包含下列信息:

  • 货币的发送者公钥(地址)
  • 货币接受者的公钥(地址)
  • 交易的值/总数
  • 输入,能够证明发送者有足够的货币去发送的之前的关联交易
  • 输出,显示这笔交易接收方关联地址的总数(这些输出被作为新交易的输入)
  • 一个加密算法,证明该笔交易发送方地址是持有者本人,数据未被篡改。(比如,防止第三方修改交易总额)

现在我们创建这个新的交易(Transaction)类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.security.*;
import java.util.ArrayList;

public class Transaction {

public String transactionId; // this is also the hash of the transaction.
public PublicKey sender; // senders address/public key.
public PublicKey reciepient; // Recipients address/public key.
public float value;
public byte[] signature; // this is to prevent anybody else from spending funds in our wallet.

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

private static int sequence = 0; // a rough count of how many transactions have been generated.

// Constructor:
public Transaction(PublicKey from, PublicKey to, float value, ArrayList<TransactionInput> inputs) {
this.sender = from;
this.reciepient = to;
this.value = value;
this.inputs = inputs;
}

// This Calculates the transaction hash (which will be used as its Id)
private String calulateHash() {
sequence++; //increase the sequence to avoid 2 identical transactions having the same hash
return StringUtil.applySha256(
StringUtil.getStringFromKey(sender) +
StringUtil.getStringFromKey(reciepient) +
Float.toString(value) + sequence
);
}
}

我们也应该创建空的TransactionInput和TransactionOutput类,不用担心,我们将会在之后补全它。

我们的交易类将同时包含用于生成、验证签名,验证交易的关联方法。

但是,等一下。。

签名的目的是什么?它如何工作?

签名承担了区块链中两项重要的任务:
第一,它们仅允许拥有者花费他们的货币,第二,防止其他人在一个新区块被挖掘(在入口点)之前篡改他们已提交的交易。

私钥用于签名数据,公钥用于验证它的完整性。

比如:Bob想要给Sally两个NoobCoins,所以他们的钱包软件生成了这次交易,把他们提交给了下一个区块的矿工,一个矿工想要修改这两个货币给John,然而幸运的是,Bob通过他的私钥给交易信息加了签名,并允许任何人通过公钥校验该数据是否被篡改(任何其他人的公钥都不能验证该笔交易)。

从上一个代码模块中我们可以知道我们的签名是一串字节,所以我们创建一个方法去生成它们,首先我们需要用到StringUtil类中的一些辅助方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//Applies ECDSA Signature and returns the result ( as bytes ).
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;
}

//Verifies a String signature
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());
}

不用担心看不懂他们,你真正需要了解的是:applyESDSASig接受发送方的私钥和输出,签名它们并返回一串字节。verifyECDSASig接受签名、公钥和数据并返回签名有效或者无效。getStringFromKey返回任意key经过重新编码的字符串。

现在我们在Transaction类中应用这些工具方法,通过增加一个generateSignature()和verifySignature()方法:

1
2
3
4
5
6
7
8
9
10
11

//Signs all the data we dont wish to be tampered with.
public void generateSignature(PrivateKey privateKey) {
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ;
signature = StringUtil.applyECDSASig(privateKey,data);
}
//Verifies the data we signed hasnt been tampered with
public boolean verifiySignature() {
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ;
return StringUtil.verifyECDSASig(sender, data, signature);
}

实际应用中,你可能需要为更多的信息做签名,比如用到的输入输出信息和时间戳

在新的交易被加到区块链上时,签名将会被矿工们校验。

3. 测试钱包和签名

现在我们基本已经做了一半了,测试一下。在NoobChain类里增加一些新的变量,更换main方法里的一些代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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) {
//Setup Bouncey castle as a Security Provider
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
//Create the new wallets
walletA = new Wallet();
walletB = new Wallet();
//Test public and private keys
System.out.println("Private and public keys:");
System.out.println(StringUtil.getStringFromKey(walletA.privateKey));
System.out.println(StringUtil.getStringFromKey(walletA.publicKey));
//Create a test transaction from WalletA to walletB
Transaction transaction = new Transaction(walletA.publicKey, walletB.publicKey, 5, null);
transaction.generateSignature(walletA.privateKey);
//Verify the signature works and verify it from the public key
System.out.println("Is signature verified");
System.out.println(transaction.verifiySignature());

}

我们创建了两个钱包,walletA和walletB,生成一笔交易并且用A的私钥加密。

现在我们只需要创建\验证输出和输入,并把交易存储在区块链中即可。

4. 输出&输出 1:加密货币是如何被拥有的

如果你想要拥有1个比特币,你需要接受1个比特币。总账不会真的给你的账户上加上一个比特币并在发送方那里减去一个。发送方参照他之前收到一个比特币,之后一个显示1个比特币被发送到你的账户的交易就被创建了。(交易输入参照之前的交易输出)

你的钱包余额是所有未花费的输出地址指向你的交易之和。

我们将遵循比特币约定,将所有未花费的输出交易称为UTXO’s。

我们创建一个交易输入类:

1
2
3
4
5
6
7
8
public class TransactionInput {
public String transactionOutputId; //Reference to TransactionOutputs -> transactionId
public TransactionOutput UTXO; //Contains the Unspent transaction output

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

以及一个交易输出类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.security.PublicKey;

public class TransactionOutput {
public String id;
public PublicKey reciepient; //also known as the new owner of these coins.
public float value; //the amount of coins they own
public String parentTransactionId; //the id of the transaction this output was created in

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

//Check if coin belongs to you
public boolean isMine(PublicKey publicKey) {
return (publicKey == reciepient);
}

}

交易输出将显示该交易发送给各方的总额,这些将要被作为一笔新交易的输入参照的交易,是你有可供支付的货币的证明。

5. 输出&输出 2:处理交易

链表中的区块将接收到很多交易,这使他可能变得非常长,这可能花费成吨的时间用来找出和检查它的输入。为了解决这个问题我们将维护一个可以用作输入的额外的未花费的交易集合。在我们的NoobChain类中增加所有UTXOs的集合:

1
2
3
4
5
6
7
8
9
10

public class NoobChain {

public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static HashMap<String,TransactionOutputs> UTXOs = new HashMap<String,TransactionOutputs>(); //list of all unspent transactions.
public static int difficulty = 5;
public static Wallet walletA;
public static Wallet walletB;

public static void main(String[] args) {

我们把所有从来处理交易的类放在一个叫processTransaction的返回布尔值的方法中,把它放在交易(Transaction)类里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//Returns true if new transaction could be created.	
public boolean processTransaction() {

if(verifiySignature() == false) {
System.out.println("#Transaction Signature failed to verify");
return false;
}

//gather transaction inputs (Make sure they are unspent):
for(TransactionInput i : inputs) {
i.UTXO = NoobChain.UTXOs.get(i.transactionOutputId);
}

//check if transaction is valid:
if(getInputsValue() < NoobChain.minimumTransaction) {
System.out.println("#Transaction Inputs to small: " + getInputsValue());
return false;
}

//generate transaction outputs:
float leftOver = getInputsValue() - value; //get value of inputs then the left over change:
transactionId = calulateHash();
outputs.add(new TransactionOutput( this.reciepient, value,transactionId)); //send value to recipient
outputs.add(new TransactionOutput( this.sender, leftOver,transactionId)); //send the left over 'change' back to sender

//add outputs to Unspent list
for(TransactionOutput o : outputs) {
NoobChain.UTXOs.put(o.id , o);
}

//remove transaction inputs from UTXO lists as spent:
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;
}

//returns sum of inputs(UTXOs) values
public float getInputsValue() {
float total = 0;
for(TransactionInput i : inputs) {
if(i.UTXO == null) continue; //if Transaction can't be found skip it
total += i.UTXO.value;
}
return total;
}

//returns sum of outputs:
public float getOutputsValue() {
float total = 0;
for(TransactionOutput o : outputs) {
total += o.value;
}
return total;
}

同时也增加了一个getInputsValue方法

通过这个方法我们做了一些检查以确保交易是合法的,然后收集输入并生成输出。

重要的是,在结尾我们舍弃了从UXTO’s的列表产生的输入,这意味着一个交易输出只能被用作一次输入。因此输入的完整值必须被使用,所以发送方可以返还给他们‘零钱’.

最后我们更新我们的钱包:

  • 收集我们的账目(通过遍历UTXOs列表,并且检查交易输出是否为自己产生的)
  • 然后生成我们的交易
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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>(); //only UTXOs owned by this wallet.

public Wallet() {...

public void generateKeyPair() {...

//returns balance and stores the UTXO's owned by this wallet in this.UTXOs
public float getBalance() {
float total = 0;
for (Map.Entry<String, TransactionOutput> item: NoobChain.UTXOs.entrySet()){
TransactionOutput UTXO = item.getValue();
if(UTXO.isMine(publicKey)) { //if output belongs to me ( if coins belong to me )
UTXOs.put(UTXO.id,UTXO); //add it to our list of unspent transactions.
total += UTXO.value ;
}
}
return total;
}
//Generates and returns a new transaction from this wallet.
public Transaction sendFunds(PublicKey _recipient,float value ) {
if(getBalance() < value) { //gather balance and check funds.
System.out.println("#Not Enough funds to send transaction. Transaction Discarded.");
return null;
}
//create array list of inputs
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;
}

}

6. 增加交易到我们的区块链中:

现在我们有了个可工作的交易系统,我们需要把它引入到我们的区块链中。我们需要用交易列表来替代我们区块中无用的数据。然而一个区块种可能有1000笔交易,这意味着哈希值需要很多运算。不过不用担心,我们可以用交易的merkle根。

我们增加一个生成mekle根的帮助方法到StringUtils类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Tacks in array of transactions and returns a merkle root.
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;
}

现在这个方法可以使用,不过很快就会被替换成真正的merkle根

现在引入到block类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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>(); //our data will be a simple message.
public long timeStamp; //as number of milliseconds since 1/1/1970.
public int nonce;

//Block Constructor.
public Block(String previousHash ) {
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();

this.hash = calculateHash(); //Making sure we do this after we set the other values.
}

//Calculate new hash based on blocks contents
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(
previousHash +
Long.toString(timeStamp) +
Integer.toString(nonce) +
merkleRoot
);
return calculatedhash;
}

//Increases nonce value until hash target is reached.
public void mineBlock(int difficulty) {
merkleRoot = StringUtil.getMerkleRoot(transactions);
String target = StringUtil.getDificultyString(difficulty); //Create a string with difficulty * "0"
while(!hash.substring( 0, difficulty).equals(target)) {
nonce ++;
hash = calculateHash();
}
System.out.println("Block Mined!!! : " + hash);
}

//Add transactions to this block
public boolean addTransaction(Transaction transaction) {
//process transaction and check if valid, unless block is genesis block then ignore.
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;
}

}

注意我们同时更新了区块构造方法,因为我们不再需要字符串传递,并且在计算哈希方法中加入了merkle根。

我们的返回布尔值的addTransaction方法将增加交易,并且只当成功添加交易时返回true.

7. 压轴好戏

我们需要测试发送给或者从钱包发送货币,然后更新我们的区块链校验。不过首先我们需要一种在组合中引入新货币的方式。有很多种方式可以创建新货币,用比特币举例:矿工们可以在每一个成功挖出的区块中创建一笔交易给自己作为奖赏。不过现在我们只需要释放我们所需要的全部货币在第一个区块。和比特币一样,我们要硬编码创世块。

我们更新noobChain需要的一切:

  • 创世块,释放100个NoobCoins给钱包A
  • 更新过后的用作账户交易的合法性验证方法
  • 用来测试一切是否工作正常的测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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) {
//add our blocks to the blockchain ArrayList:
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //Setup Bouncey castle as a Security Provider

//Create wallets:
walletA = new Wallet();
walletB = new Wallet();
Wallet coinbase = new Wallet();

//create genesis transaction, which sends 100 NoobCoin to walletA:
genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null);
genesisTransaction.generateSignature(coinbase.privateKey); //manually sign the genesis transaction
genesisTransaction.transactionId = "0"; //manually set the transaction id
genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId)); //manually add the Transactions Output
UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0)); //its important to store our first transaction in the UTXOs list.

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 Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
String hashTarget = new String(new char[difficulty]).replace('\0', '0');
HashMap<String,TransactionOutput> tempUTXOs = new HashMap<String,TransactionOutput>(); //a temporary working list of unspent transactions at a given block state.
tempUTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));

//loop through blockchain to check hashes:
for(int i=1; i < blockchain.size(); i++) {

currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i-1);
//compare registered hash and calculated hash:
if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){
System.out.println("#Current Hashes not equal");
return false;
}
//compare previous hash and registered previous hash
if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
System.out.println("#Previous Hashes not equal");
return false;
}
//check if hash is solved
if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
System.out.println("#This block hasn't been mined");
return false;
}

//loop thru blockchains transactions:
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);
}
}

钱包现在可以当且仅当它们有现金用作发送时,安全地发送交易给你的区块链了。这意味着你拥有了自己的加密货币。

现在我们完成了区块链的交易部分!

你成功的创建了你的加密货币,你的区块链现在:

  • 允许用户通过new Wallet()创建新钱包
  • 通过Elliptic-Curve加密方法给钱包提供公钥和私钥。
  • 为货币交易的安全起见,通过数字签名算法保证归属性
  • 最后允许所有用户通过‘Block.addTransaction(walletA.sendFunds(walletB.publicKey,20))’在你的区块链上创建交易。

你可以在GitHub上下载这些项目文件。

TO BE CONTINUED…

原文发表于: https://medium.com/programmers-blockchain/creating-your-first-blockchain-with-java-part-2-transactions-2cdac335e0ce

本文作者: Dominic
本文链接: https://dominicpoi.com/2019/03/30/JAVA-chainBlock-02/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!
知识共享许可协议
赏

帮作者买一盒泡面

支付宝
微信
  • ARTS
  • MEDIUM
  • 技术
  • 文摘
  • 后端
  • JAVA
LeetCode链表集合放送(二)
LeetCode链表集合放送(一)
  1. 1. 1. 准备一个钱包
  2. 2. 2. 交易和签名
    1. 2.1. 签名的目的是什么?它如何工作?
  3. 3. 3. 测试钱包和签名
  4. 4. 4. 输出&输出 1:加密货币是如何被拥有的
  5. 5. 5. 输出&输出 2:处理交易
  6. 6. 6. 增加交易到我们的区块链中:
  7. 7. 7. 压轴好戏
0 comments
Anonymous
Markdown is supported

Be the first person to leave a comment!

© 2021 Dominic
总访问量:8894| 访客数:6632
  • 所有文章
  • 关于我

tag:

  • ARTS
  • 源码分析
  • 并发
  • MEDIUM
  • 技术
  • 文摘
  • NIO
  • 备忘录
  • 闲谈
  • LeetCode
  • MySQL
  • Spring
  • Framework
  • spring
  • 归档

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • LeetCode402、316、321(保留相对顺序取极值)

    2021-09-05

    #ARTS#LeetCode

  • 回溯算法(N皇后、数独)

    2019-09-08

    #LeetCode

  • 利用walkFileTree遍历文件

    2019-09-01

    #NIO#备忘录

  • MySQL为什么默认隔离级别为可重复读?

    2019-06-16

    #ARTS#MySQL

  • Spring IoC容器介绍及实例化流程分析

    2019-06-13

    #源码分析#Spring#Framework

  • JVM基础

    2019-06-12

    #ARTS

  • LeetCode 215 数组中的第K个最大元素

    2019-05-27

    #ARTS#LeetCode

  • LeetCode101-106

    2019-05-13

    #ARTS#LeetCode

  • AQS源码分析(共享模式、 CyclicBarrier)

    2019-05-09

    #ARTS#源码分析#并发

  • LeetCode 63 不同路径II

    2019-04-20

    #ARTS#LeetCode

  • LeetCode 62 不同路径

    2019-04-20

    #ARTS#LeetCode

  • AQS源码分析(公平锁、非公平锁、Condition)

    2019-04-16

    #ARTS#源码分析#并发

  • AQS源码分析(ReentrantLock - lock() & unlock())

    2019-04-09

    #ARTS#源码分析#并发

  • LeetCode链表集合放送(二)

    2019-04-07

    #ARTS#LeetCode

  • 用Java创造你的第一个区块链-02

    2019-03-30

    #ARTS#MEDIUM#技术#文摘

  • LeetCode链表集合放送(一)

    2019-03-26

    #ARTS#LeetCode

  • LeetCode 86 Partition List

    2019-03-20

    #ARTS#LeetCode

  • 用JAVA创造你的第一个区块链-01

    2019-03-19

    #ARTS#MEDIUM#技术#文摘

  • SpringBoot + Mybatis 显示sql的配置方式

    2019-03-18

    #备忘录#spring#归档

  • 关于webuploader + springmvc 出现的406问题

    2019-03-18

    #备忘录#spring#归档

  • 关于SpringBoot :java.lang.ClassNotFoundException:org.dom4j.io.STAXEventReader错误解决方法

    2019-03-18

    #备忘录#spring#归档

  • Hello, World

    2019-03-18

    #闲谈

一个步履蹒跚的前行者。
Java后端开发初学者。
希望未来的自己不会后悔当下。