本系列教程的目的是帮助您了解如何开发区块链技术。
在这第二个教程中,我们将:
- 创建一个简单的钱包;
- 使用我们的区块链发送已签名的交易;
- 感觉更酷。
以上这些将使我们拥有自己的加密货币!
从上一个教程开始,我们有一个基本可验证的区块链。但是目前我们的区块链只存储了些相当无用的信息。今天我们将用交易数据代替这些无用信息(我们每个区块可以容纳多个交易信息)。我们将创建一个非常简单的加密货币,称之为“NoobCoin”。
本教程假设您已经进行了教程1中的操作。
依赖关系:您需要导入 bounceycastle (这是一个关于如何操作的迷你教程) and GSON.
1,准备一个钱包
在加密货币中,货币所有权作为交易在区块链上转移,参与者有一个可以发送和接收资金的地址。钱包可以存储这些地址,大多数钱包也是一个能够在区块链上进行新的交易的软件。
因此让我们创建一个Wallet类来保存我们的公钥和私钥:
1 package noobchain; 2 import java.security.*; 3 4 public class Wallet { 5 public PrivateKey privateKey; 6 public PublicKey publicKey; 7 }
什么是公钥什么是私钥??
对于我们的“noobcoin”,公钥将作为我们的地址。可以与他人共享此公钥以接收付款。我们的私钥用于签署我们的交易,因此除了私钥的所有者,没人可以使用我们的noobcoins。用户必须保密他们的私钥!我们还将我们的公钥与交易一起发送,它可用于验证我们的签名是否有效以及数据是否被篡改。
私钥用于签署我们不希望被篡改的数据,公钥用于验证签名。
我们在KeyPair中生成私钥和公钥。我们将使用Elliptic-curve cryptography来生成KeyPairs。在Wallet类中添加generateKeyPair()方法并在构造函数中调用它:
1 package noobchain; 2 import java.security.*; 3 4 public class Wallet { 5 6 public PrivateKey privateKey; 7 public PublicKey publicKey; 8 9 public Wallet(){ 10 generateKeyPair(); 11 } 12 13 public void generateKeyPair() { 14 try { 15 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA","BC"); 16 SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); 17 ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1"); 18 // Initialize the key generator and generate a KeyPair 19 keyGen.initialize(ecSpec, random); //256 bytes provides an acceptable security level 20 KeyPair keyPair = keyGen.generateKeyPair(); 21 // Set the public and private keys from the keyPair 22 privateKey = keyPair.getPrivate(); 23 publicKey = keyPair.getPublic(); 24 }catch(Exception e) { 25 throw new RuntimeException(e); 26 } 27 } 28 29 }
现在我们已经有了钱包类,接下来看看交易。
2,交易和签名
每笔交易都会带有一定数量的数据:
- 资金发送方的公钥(地址);
- 资金接收方的公钥(地址);
- 要转移的资金的价值/金额;
- 输入是对以前交易的引用,证明发送方有资金要发送;
- 输出显示交易中收到的相关地址金额;(这些输出在新的交易中作为输入引用)
- 验证地址所有者的加密签名是发送此事务并且数据未更改的加密签名。(例如,阻止第三方更改发送的金额)。
让我们创建这个新的Transaction类:
1 import java.security.*; 2 import java.util.ArrayList; 3 4 public class Transaction { 5 6 public String transactionId; // this is also the hash of the transaction. 7 public PublicKey sender; // senders address/public key. 8 public PublicKey reciepient; // Recipients address/public key. 9 public float value; 10 public byte[] signature; // this is to prevent anybody else from spending funds in our wallet. 11 12 public ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>(); 13 public ArrayList<TransactionOutput> outputs = new ArrayList<TransactionOutput>(); 14 15 private static int sequence = 0; // a rough count of how many transactions have been generated. 16 17 // Constructor: 18 public Transaction(PublicKey from, PublicKey to, float value, ArrayList<TransactionInput> inputs) { 19 this.sender = from; 20 this.reciepient = to; 21 this.value = value; 22 this.inputs = inputs; 23 } 24 25 // This Calculates the transaction hash (which will be used as its Id) 26 private String calulateHash() { 27 sequence++; //increase the sequence to avoid 2 identical transactions having the same hash 28 return StringUtil.applySha256( 29 StringUtil.getStringFromKey(sender) + 30 StringUtil.getStringFromKey(reciepient) + 31 Float.toString(value) + sequence 32 ); 33 } 34 }
我们还应该创建空的TransactionInput和TransactionOutput类,我们后面填写。
我们的交易类还将包含生成/验证签名和验证交易的相关方法。
签名的目的是什么?它们如何工作?
签名在我们区块链上执行两项非常重要的任务:首先,它们只允许所有者使用它们的货币;其次,它们阻止其他人在开采新区块前(在进入点)篡改它们提交的交易。
私钥用于签署数据,公钥用于验证其完整性。
例如: Bob希望向Sally发送2个 NoobCoins,因此他们的钱包软件生成此交易并将其提交给矿工以包含在下一个块中。矿工试图将2个硬币的收件人改为约翰。然而,幸运的是,Bob用他的私钥签署了交易数据,允许任何人使用Bob的公钥验证交易数据是否已被更改(因为没有其他人公钥可以验证交易)。
我们可以看到(从前面的代码块中)我们的签名将是一堆字节,所以让我们创建一个生成它们的方法。我们需要的第一件事是StringUtil 类中的一些辅助函数 :
1 //Applies ECDSA Signature and returns the result ( as bytes ). 2 public static byte[] applyECDSASig(PrivateKey privateKey, String input) { 3 Signature dsa; 4 byte[] output = new byte[0]; 5 try { 6 dsa = Signature.getInstance("ECDSA", "BC"); 7 dsa.initSign(privateKey); 8 byte[] strByte = input.getBytes(); 9 dsa.update(strByte); 10 byte[] realSig = dsa.sign(); 11 output = realSig; 12 } catch (Exception e) { 13 throw new RuntimeException(e); 14 } 15 return output; 16 } 17 18 //Verifies a String signature 19 public static boolean verifyECDSASig(PublicKey publicKey, String data, byte[] signature) { 20 try { 21 Signature ecdsaVerify = Signature.getInstance("ECDSA", "BC"); 22 ecdsaVerify.initVerify(publicKey); 23 ecdsaVerify.update(data.getBytes()); 24 return ecdsaVerify.verify(signature); 25 }catch(Exception e) { 26 throw new RuntimeException(e); 27 } 28 } 29 30 public static String getStringFromKey(Key key) { 31 return Base64.getEncoder().encodeToString(key.getEncoded()); 32 }
不要太在意理解这些方法的内容。您真正需要知道的是:applyECDSASig接收发送者私钥和字符串输入,对其进行签名并返回一个字节数组。verifyECDSASig接收签名,公钥和字符串数据,如果签名有效,则返回true或false。getStringFromKey从任何键返回编码的字符串。
现在让我们在Transaction 类中使用这些签名方法,generateSignature()和verifiySignature() 方法:
1 //Signs all the data we dont wish to be tampered with. 2 public void generateSignature(PrivateKey privateKey) { 3 String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ; 4 signature = StringUtil.applyECDSASig(privateKey,data); 5 } 6 //Verifies the data we signed hasnt been tampered with 7 public boolean verifiySignature() { 8 String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ; 9 return StringUtil.verifyECDSASig(sender, data, signature); 10 }
实际上,您可能希望签署更多信息,例如使用的输出/输入和/或时间戳(现在我们只签署最低限度)。
当一个新的交易被添加到一个区块时,矿工将验证签名。
当我们检查区块链的有效性时,我们也可以检查签名。
3,测试钱包和签名
现在我们已经完成了一半让我们测试一些事情正在发挥作用。在NoobChain类中,让我们添加一些新变量并替换main 方法的内容 :
1 import java.security.Security; 2 import java.util.ArrayList; 3 import java.util.Base64; 4 import com.google.gson.GsonBuilder; 5 6 public class NoobChain { 7 8 public static ArrayList<Block> blockchain = new ArrayList<Block>(); 9 public static int difficulty = 5; 10 public static Wallet walletA; 11 public static Wallet walletB; 12 13 public static void main(String[] args) { 14 //Setup Bouncey castle as a Security Provider 15 Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); 16 //Create the new wallets 17 walletA = new Wallet(); 18 walletB = new Wallet(); 19 //Test public and private keys 20 System.out.println("Private and public keys:"); 21 System.out.println(StringUtil.getStringFromKey(walletA.privateKey)); 22 System.out.println(StringUtil.getStringFromKey(walletA.publicKey)); 23 //Create a test transaction from WalletA to walletB 24 Transaction transaction = new Transaction(walletA.publicKey, walletB.publicKey, 5, null); 25 transaction.generateSignature(walletA.privateKey); 26 //Verify the signature works and verify it from the public key 27 System.out.println("Is signature verified"); 28 System.out.println(transaction.verifiySignature()); 29 30 }
我们创建了两个钱包,walletA和walletB然后打印walletA的私钥和公钥。生成一个事务并使用walletA的私钥对其进行签名。
输出如下;
现在我们只需要创建/验证输出和输入,然后将事务存储在区块链中。
4,输入和输出1:如何拥有加密货币
为了拥有1比特币,你必须获得1比特币。总账单并没有真正为您添加一个比特币,减去发件人的一个比特币,发件人引用他/她之前收到的一个比特币,然后创建了一个交易输出,显示1比特币已发送到您的地址。(交易输入是对先前交易输出的引用。)。
您的钱包余额是发给您的所有未花费的交易输出的总和。
那么让我们创建一个TransactionInput 类:
1 public class TransactionInput { 2 public String transactionOutputId; //Reference to TransactionOutputs -> transactionId 3 public TransactionOutput UTXO; //Contains the Unspent transaction output 4 5 public TransactionInput(String transactionOutputId) { 6 this.transactionOutputId = transactionOutputId; 7 } 8 }
此类将用于引用尚未使用的TransactionOutputs。transactionOutputId将用于查找相关的TransactionOutput,允许矿工检查您的所有权。
从这一点开始,我们将遵循比特币约定并调用未使用的事务输出:UTXO。
创建TransactionOutput 类:
1 import java.security.PublicKey; 2 3 public class TransactionOutput { 4 public String id; 5 public PublicKey reciepient; //also known as the new owner of these coins. 6 public float value; //the amount of coins they own 7 public String parentTransactionId; //the id of the transaction this output was created in 8 9 //Constructor 10 public TransactionOutput(PublicKey reciepient, float value, String parentTransactionId) { 11 this.reciepient = reciepient; 12 this.value = value; 13 this.parentTransactionId = parentTransactionId; 14 this.id = StringUtil.applySha256(StringUtil.getStringFromKey(reciepient)+Float.toString(value)+parentTransactionId); 15 } 16 17 //Check if coin belongs to you 18 public boolean isMine(PublicKey publicKey) { 19 return (publicKey == reciepient); 20 } 21 22 }
交易输出将显示从交易发送给每一方的最终金额。当在新交易中作为输入引用时,这些作为您要发送硬币的证明。
5,输入和输出2:处理交易
链中的块可能会收到许多事务,而区块链可能会非常非常长,因为我们必须查找并检查其输入,所以可能需要很长时间来处理新事务。为了解决这个问题,我们将额外收集所有可用作输入的未花费的交易。在我们的N oobChain 类中添加所有UTXO的集合:
1 public class NoobChain { 2 3 public static ArrayList<Block> blockchain = new ArrayList<Block>(); 4 public static HashMap<String,TransactionOutputs> UTXOs = new HashMap<String,TransactionOutputs>(); //list of all unspent transactions. 5 public static int difficulty = 5; 6 public static Wallet walletA; 7 public static Wallet walletB; 8 9 public static void main(String[] args) {
好的,是时候深入了解细节......
让我们把一切融合在一起来处理交易,用Transaction类中的processTransaction()方法:
1 //Returns true if new transaction could be created. 2 public boolean processTransaction() { 3 4 if(verifiySignature() == false) { 5 System.out.println("#Transaction Signature failed to verify"); 6 return false; 7 } 8 9 //gather transaction inputs (Make sure they are unspent): 10 for(TransactionInput i : inputs) { 11 i.UTXO = NoobChain.UTXOs.get(i.transactionOutputId); 12 } 13 14 //check if transaction is valid: 15 if(getInputsValue() < NoobChain.minimumTransaction) { 16 System.out.println("#Transaction Inputs to small: " + getInputsValue()); 17 return false; 18 } 19 20 //generate transaction outputs: 21 float leftOver = getInputsValue() - value; //get value of inputs then the left over change: 22 transactionId = calulateHash(); 23 outputs.add(new TransactionOutput( this.reciepient, value,transactionId)); //send value to recipient 24 outputs.add(new TransactionOutput( this.sender, leftOver,transactionId)); //send the left over 'change' back to sender 25 26 //add outputs to Unspent list 27 for(TransactionOutput o : outputs) { 28 NoobChain.UTXOs.put(o.id , o); 29 } 30 31 //remove transaction inputs from UTXO lists as spent: 32 for(TransactionInput i : inputs) { 33 if(i.UTXO == null) continue; //if Transaction can't be found skip it 34 NoobChain.UTXOs.remove(i.UTXO.id); 35 } 36 37 return true; 38 } 39 40 //returns sum of inputs(UTXOs) values 41 public float getInputsValue() { 42 float total = 0; 43 for(TransactionInput i : inputs) { 44 if(i.UTXO == null) continue; //if Transaction can't be found skip it 45 total += i.UTXO.value; 46 } 47 return total; 48 } 49 50 //returns sum of outputs: 51 public float getOutputsValue() { 52 float total = 0; 53 for(TransactionOutput o : outputs) { 54 total += o.value; 55 } 56 return total; 57 } 58
使用此方法,我们执行一些检查以确保事务有效,然后收集输入并生成输出。
重要时刻,我们需要把UTXO列表删除,这意味着交易的输出只能作为输入,,,因此,所有的输入值必须被使用,所以,发送端发送“改变”回他们自己。
红色箭头是输出。请注意,绿色输入是对先前输出的引用。
最后让我们将钱包更新为:
- 收集我们的余额(遍历UTXOs列表并检查事务输出是否是我的);
- 为我们生成交易。
1 import java.security.*; 2 import java.security.spec.ECGenParameterSpec; 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 import java.util.Map; 6 7 public class Wallet { 8 9 public PrivateKey privateKey; 10 public PublicKey publicKey; 11 12 public HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>(); //only UTXOs owned by this wallet. 13 14 public Wallet() {... 15 16 public void generateKeyPair() {... 17 18 //returns balance and stores the UTXO's owned by this wallet in this.UTXOs 19 public float getBalance() { 20 float total = 0; 21 for (Map.Entry<String, TransactionOutput> item: NoobChain.UTXOs.entrySet()){ 22 TransactionOutput UTXO = item.getValue(); 23 if(UTXO.isMine(publicKey)) { //if output belongs to me ( if coins belong to me ) 24 UTXOs.put(UTXO.id,UTXO); //add it to our list of unspent transactions. 25 total += UTXO.value ; 26 } 27 } 28 return total; 29 } 30 //Generates and returns a new transaction from this wallet. 31 public Transaction sendFunds(PublicKey _recipient,float value ) { 32 if(getBalance() < value) { //gather balance and check funds. 33 System.out.println("#Not Enough funds to send transaction. Transaction Discarded."); 34 return null; 35 } 36 //create array list of inputs 37 ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>(); 38 39 float total = 0; 40 for (Map.Entry<String, TransactionOutput> item: UTXOs.entrySet()){ 41 TransactionOutput UTXO = item.getValue(); 42 total += UTXO.value; 43 inputs.add(new TransactionInput(UTXO.id)); 44 if(total > value) break; 45 } 46 47 Transaction newTransaction = new Transaction(publicKey, _recipient , value, inputs); 48 newTransaction.generateSignature(privateKey); 49 50 for(TransactionInput input: inputs){ 51 UTXOs.remove(input.transactionOutputId); 52 } 53 return newTransaction; 54 } 55 56 }
您可以随意在钱包中添加其他功能,例如记录您的交易历史记录。
6,把交易数据添加到区块
现在我们有一个工作交易系统,我们需要将它实现到我们的区块链中。我们应该使用事务的ArrayList替换块中的无用数据。但是,在一个块中可能有1000个事务,在我们的哈希计算中包含的事务太多......但是不要担心我们可以使用事务的merkle根(您可以在这里快速阅读有关merkle树的信息*)*。
让我们添加一个帮助方法来生成StringUtils中的merkleroot:
1 //Tacks in array of transactions and returns a merkle root. 2 public static String getMerkleRoot(ArrayList<Transaction> transactions) { 3 int count = transactions.size(); 4 ArrayList<String> previousTreeLayer = new ArrayList<String>(); 5 for(Transaction transaction : transactions) { 6 previousTreeLayer.add(transaction.transactionId); 7 } 8 ArrayList<String> treeLayer = previousTreeLayer; 9 while(count > 1) { 10 treeLayer = new ArrayList<String>(); 11 for(int i=1; i < previousTreeLayer.size(); i++) { 12 treeLayer.add(applySha256(previousTreeLayer.get(i-1) + previousTreeLayer.get(i))); 13 } 14 count = treeLayer.size(); 15 previousTreeLayer = treeLayer; 16 } 17 String merkleRoot = (treeLayer.size() == 1) ? treeLayer.get(0) : ""; 18 return merkleRoot; 19 }
现在更改我们的Block类:
1 import java.util.ArrayList; 2 import java.util.Date; 3 4 public class Block { 5 6 public String hash; 7 public String previousHash; 8 public String merkleRoot; 9 public ArrayList<Transaction> transactions = new ArrayList<Transaction>(); //our data will be a simple message. 10 public long timeStamp; //as number of milliseconds since 1/1/1970. 11 public int nonce; 12 13 //Block Constructor. 14 public Block(String previousHash ) { 15 this.previousHash = previousHash; 16 this.timeStamp = new Date().getTime(); 17 18 this.hash = calculateHash(); //Making sure we do this after we set the other values. 19 } 20 21 //Calculate new hash based on blocks contents 22 public String calculateHash() { 23 String calculatedhash = StringUtil.applySha256( 24 previousHash + 25 Long.toString(timeStamp) + 26 Integer.toString(nonce) + 27 merkleRoot 28 ); 29 return calculatedhash; 30 } 31 32 //Increases nonce value until hash target is reached. 33 public void mineBlock(int difficulty) { 34 merkleRoot = StringUtil.getMerkleRoot(transactions); 35 String target = StringUtil.getDificultyString(difficulty); //Create a string with difficulty * "0" 36 while(!hash.substring( 0, difficulty).equals(target)) { 37 nonce ++; 38 hash = calculateHash(); 39 } 40 System.out.println("Block Mined!!! : " + hash); 41 } 42 43 //Add transactions to this block 44 public boolean addTransaction(Transaction transaction) { 45 //process transaction and check if valid, unless block is genesis block then ignore. 46 if(transaction == null) return false; 47 if((previousHash != "0")) { 48 if((transaction.processTransaction() != true)) { 49 System.out.println("Transaction failed to process. Discarded."); 50 return false; 51 } 52 } 53 transactions.add(transaction); 54 System.out.println("Transaction Successfully added to Block"); 55 return true; 56 } 57 58 }
注意我们还更新了我们的Block构造函数,因为我们不再需要传入字符串数据并在计算哈希方法中包含了merkle root。
我们的addTransaction 方法将添加事务,并且只有在成功添加事务后才返回true。
现在,实现区块链上交易功能所需的每个组件我们都实现了。
7,大结局
我们应该测试从钱包发送硬币,并更新我们的区块链有效性检查。但首先我们需要一种方法来引入新的硬币。例如,在比特币区块链上创建新硬币的方法有很多种:矿工可以将自己的交易作为对每个开采区块的奖励。但就目前而言,我们将在第一个区块(创世块)中释放我们希望拥有的所有硬币。就像比特币一样,我们会对创世块进行硬编码。
我们需要更新我们的NoobChain类:
- 一个创世块,释放100个Noobcoins到walletA;
- 更新的链式有效性检查,考虑了事务;
- 一些测试事务,看看是否一切正常。
1 public class NoobChain { 2 3 public static ArrayList<Block> blockchain = new ArrayList<Block>(); 4 public static HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>(); 5 6 public static int difficulty = 3; 7 public static float minimumTransaction = 0.1f; 8 public static Wallet walletA; 9 public static Wallet walletB; 10 public static Transaction genesisTransaction; 11 12 public static void main(String[] args) { 13 //add our blocks to the blockchain ArrayList: 14 Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //Setup Bouncey castle as a Security Provider 15 16 //Create wallets: 17 walletA = new Wallet(); 18 walletB = new Wallet(); 19 Wallet coinbase = new Wallet(); 20 21 //create genesis transaction, which sends 100 NoobCoin to walletA: 22 genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null); 23 genesisTransaction.generateSignature(coinbase.privateKey); //manually sign the genesis transaction 24 genesisTransaction.transactionId = "0"; //manually set the transaction id 25 genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId)); //manually add the Transactions Output 26 UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0)); //its important to store our first transaction in the UTXOs list. 27 28 System.out.println("Creating and Mining Genesis block... "); 29 Block genesis = new Block("0"); 30 genesis.addTransaction(genesisTransaction); 31 addBlock(genesis); 32 33 //testing 34 Block block1 = new Block(genesis.hash); 35 System.out.println(" WalletA's balance is: " + walletA.getBalance()); 36 System.out.println(" WalletA is Attempting to send funds (40) to WalletB..."); 37 block1.addTransaction(walletA.sendFunds(walletB.publicKey, 40f)); 38 addBlock(block1); 39 System.out.println(" WalletA's balance is: " + walletA.getBalance()); 40 System.out.println("WalletB's balance is: " + walletB.getBalance()); 41 42 Block block2 = new Block(block1.hash); 43 System.out.println(" WalletA Attempting to send more funds (1000) than it has..."); 44 block2.addTransaction(walletA.sendFunds(walletB.publicKey, 1000f)); 45 addBlock(block2); 46 System.out.println(" WalletA's balance is: " + walletA.getBalance()); 47 System.out.println("WalletB's balance is: " + walletB.getBalance()); 48 49 Block block3 = new Block(block2.hash); 50 System.out.println(" WalletB is Attempting to send funds (20) to WalletA..."); 51 block3.addTransaction(walletB.sendFunds( walletA.publicKey, 20)); 52 System.out.println(" WalletA's balance is: " + walletA.getBalance()); 53 System.out.println("WalletB's balance is: " + walletB.getBalance()); 54 55 isChainValid(); 56 57 } 58 59 public static Boolean isChainValid() { 60 Block currentBlock; 61 Block previousBlock; 62 String hashTarget = new String(new char[difficulty]).replace(' ', '0'); 63 HashMap<String,TransactionOutput> tempUTXOs = new HashMap<String,TransactionOutput>(); //a temporary working list of unspent transactions at a given block state. 64 tempUTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0)); 65 66 //loop through blockchain to check hashes: 67 for(int i=1; i < blockchain.size(); i++) { 68 69 currentBlock = blockchain.get(i); 70 previousBlock = blockchain.get(i-1); 71 //compare registered hash and calculated hash: 72 if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){ 73 System.out.println("#Current Hashes not equal"); 74 return false; 75 } 76 //compare previous hash and registered previous hash 77 if(!previousBlock.hash.equals(currentBlock.previousHash) ) { 78 System.out.println("#Previous Hashes not equal"); 79 return false; 80 } 81 //check if hash is solved 82 if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) { 83 System.out.println("#This block hasn't been mined"); 84 return false; 85 } 86 87 //loop thru blockchains transactions: 88 TransactionOutput tempOutput; 89 for(int t=0; t <currentBlock.transactions.size(); t++) { 90 Transaction currentTransaction = currentBlock.transactions.get(t); 91 92 if(!currentTransaction.verifiySignature()) { 93 System.out.println("#Signature on Transaction(" + t + ") is Invalid"); 94 return false; 95 } 96 if(currentTransaction.getInputsValue() != currentTransaction.getOutputsValue()) { 97 System.out.println("#Inputs are note equal to outputs on Transaction(" + t + ")"); 98 return false; 99 } 100 101 for(TransactionInput input: currentTransaction.inputs) { 102 tempOutput = tempUTXOs.get(input.transactionOutputId); 103 104 if(tempOutput == null) { 105 System.out.println("#Referenced input on Transaction(" + t + ") is Missing"); 106 return false; 107 } 108 109 if(input.UTXO.value != tempOutput.value) { 110 System.out.println("#Referenced input Transaction(" + t + ") value is Invalid"); 111 return false; 112 } 113 114 tempUTXOs.remove(input.transactionOutputId); 115 } 116 117 for(TransactionOutput output: currentTransaction.outputs) { 118 tempUTXOs.put(output.id, output); 119 } 120 121 if( currentTransaction.outputs.get(0).reciepient != currentTransaction.reciepient) { 122 System.out.println("#Transaction(" + t + ") output reciepient is not who it should be"); 123 return false; 124 } 125 if( currentTransaction.outputs.get(1).reciepient != currentTransaction.sender) { 126 System.out.println("#Transaction(" + t + ") output 'change' is not sender."); 127 return false; 128 } 129 130 } 131 132 } 133 System.out.println("Blockchain is valid"); 134 return true; 135 } 136 137 public static void addBlock(Block newBlock) { 138 newBlock.mineBlock(difficulty); 139 blockchain.add(newBlock); 140 } 141 }
输出:
钱包现在能够安全地在区块链上发送资金,只要他们有资金发送即可。这意味着您拥有自己的本地加密货币*。
您已经完成了区块链上的交易!
您已成功创建自己的加密货币。您的区块链现在:
- 允许用户使用 new Wallet()创建钱包;
- 使用Elliptic-Curve加密技术为钱包提供公钥和私钥;
- 通过使用数字签名算法来证明所有权,确保资金转移;
- 允许用户使用 “Block.addTransaction(walletA.sendFunds(walletB.publicKey,20))”在您的区块链上进行交易。
您可以在Github上下载这些项目文件。
https://medium.com/programmers-blockchain/creating-your-first-blockchain-with-java-part-2-transactions-2cdac335e0ce