用java写一个简单的区块链(下)
用java寫一個簡單的區塊鏈(下)
2018年03月29日 21:44:35?java派大星?閱讀數:725?標簽:?區塊鏈java?更多
個人分類:?區塊鏈
版權聲明:本文為博主原創文章,轉載請標明出處和鏈接! https://blog.csdn.net/junmoxi/article/details/79749744
本章目標
- 創建一個簡單的錢包。
- 使用我們的區塊鏈發送帶簽名的交易。
這樣我們就有自己的加密貨幣
值得注意的是,這里創建的區塊鏈并不是功能完全的完全適合應用與生產的區塊鏈,相反只是為了幫助你更好的理解區塊鏈的概念。
別擔心,這實際上是很簡單的,但比上一個教程要長!
在上一章節,我們已經有了一個基本的區塊鏈,但在區塊鏈中存放的是一些無用的信息。今天我們將用交易取代這些信息(我們的區塊將能夠保存多個交易),允許我們創建一個非常簡單的加密貨幣,我們的貨幣名字pibigstar。
- 本教程是在上一邊基礎上實現的
- 導入?bounceycastle和GSON
準備一個錢包
在加密貨幣中,在區塊鏈作為交易時,貨幣所有權可以進行轉移,每個參與者都有一個自己私有的地址來發送或者是收取貨幣。,錢包可以存儲這些地址。因此錢包就是可以在區塊鏈上進行新交易的軟件。
Don’t worry about the information on the transaction, that will be explained soon
讓我們創建一個錢包類來保存公鑰和私鑰:
package noobchain; import java.security.*;public class Wallet {public PrivateKey privateKey;public PublicKey publicKey; }公鑰和私鑰究竟是起到什么作用呢,其實公鑰的作用就是地址,你可以分享你的公鑰給別人以此來獲取付款,而你的私鑰的作用是為了對交易進行簽名,這樣其他人就不可以花費你的金額除非它擁有你的私鑰,所以對于每個人而言我們必須保護好我們的私鑰,不能透露我們的私鑰信息給其他人。同時在我們進行交易的時候我們也會同時發送我們的公鑰由此來驗證我們的簽名是有效的而且沒有數據被篡改。
我們在密鑰對KeyPair生成私有和公鑰。我們將使用橢圓曲線加密來生成我們的密鑰對KeyPair。讓我們將generateKeyPair()方法添加到我們的錢包類中,并在構造函數中調用它:
私鑰用于簽署我們不想被篡改的數據。公鑰用于驗證簽名。
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 KeyPairkeyGen.initialize(ecSpec, random); //256 bytes provides an acceptable security levelKeyPair keyPair = keyGen.generateKeyPair();// Set the public and private keys from the keyPairprivateKey = keyPair.getPrivate();publicKey = keyPair.getPublic();}catch(Exception e) {throw new RuntimeException(e);}}}你不需要完全理解橢圓曲線加密算法的核心邏輯究竟是什么,你只需要它是用來創建公鑰和私鑰,以及公鑰和私鑰分別所起到的作用是什么就可以了。
現在我們已經又了錢包類的大概框架,下面我們再來看一下交易類。
交易和數字簽名
每筆交易將攜帶一定以下信息:
讓我們創建這個新的交易類:
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 hashreturn StringUtil.applySha256(StringUtil.getStringFromKey(sender) +StringUtil.getStringFromKey(reciepient) +Float.toString(value) + sequence);} }我們還應該創建空的transactioninput和transactionoutput類,不要擔心我們可以在后面填寫它們。
我們的交易類還將包含生成/驗證簽名和驗證交易的相關方法,那么簽名的意圖是什么?簽名在我們的區塊鏈中執行了兩個非常重要的任務:第一,簽名用來保證只有貨幣的擁有者才可以用來發送自己的貨幣,第二,簽名用來阻止其他人試圖篡改提交的交易。
即私鑰被用來簽名數據,而公鑰用來驗證其完整性。
舉個例子:Bob 想要發送2個加密貨幣給Sally,他們用各自的錢包創建了交易,并提交到全網的區塊鏈中作為一個新的區塊,一個挖礦者試圖篡改接受者把2個加密貨幣給John,但是幸運的事,Bob在交易數據中已經用私鑰進行了簽名,這就允許了全網中的任何節點使用小明的公匙進行驗證數據是否已經被篡改(因為沒有其他人的公鑰可以用來驗證小明發出的這筆交易)。
從前面的代碼中我們可以看到我們的簽名將是一堆字符串,所以讓我們創建一個方法來生成它們。首先我們在StringUtil類中創建產生簽名的方法。
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());}不用擔心你不能理解這些方法的內容,你所需要知道的就是applyECDSASig方法的輸入參數為付款人的私鑰和需要加密的數據信息,簽名后返回字節數組。而verifyECDSASig方法的輸入參數為公鑰、加密的數據和簽名,調用該方法后返回true或false來說明簽名是否是有效的。getStringFromKey返回任何key的編碼字符串
讓我們使用簽名的方法在交易類中,增加generatesiganature()?和?varifiysignature()方法:
//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); }在現實中,您可能希望簽名更多信息,例如使用的輸出/輸入和/或時間戳(現在我們只是簽名最低限度的信息)
簽名將由礦工驗證,只有簽名驗證成功后交易才能被添加到區塊中去。
當我們檢查區塊鏈的有效性時,我們也可以檢查簽名
測試錢包和簽名
現在我們簡單的進行一些測試,在主方法中,我們增加了一些新的變量也替換了我們之前在主方法中的一些內容。
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 ProviderSecurity.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //Create the new walletswalletA = new Wallet();walletB = new Wallet();//Test public and private keysSystem.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 keySystem.out.println("Is signature verified");System.out.println(transaction.verifiySignature());}確定要添加bounceycastle依賴
我們創建了兩個錢包walleta和walletb,然后打印了walleta的私鑰和公鑰。生成一個交易并使用walleta的私鑰對其進行簽名。
打印
Connected to the target VM, address: '127.0.0.1:55757', transport: 'socket' Private and public keys: MHsCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQEEYTBfAgEBBBiJzZiesBnBWwwB+uog+fJX84mx4lPUTUagCgYIKoZIzj0DAQGhNAMyAAQfPzad0zqBUGQAany2rRZE1+2ml5IOCZST8LywjBQT8ux+3UPVbr2u0+LaExxz1WI= MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEHz82ndM6gVBkAGp8tq0WRNftppeSDgmUk/C8sIwUE/Lsft1D1W69rtPi2hMcc9Vi Is signature verified true接下來我們將創建并驗證輸入和輸出,并把交易保存到區塊鏈中去。
輸入和輸出 1:如何驗證貨幣是你的
如果你擁有1比特幣,你必須前面就得接收1比特幣。比特幣的賬本不會在你的賬戶中增加一個比特幣也不會從發送者那里減去一個比特幣,發送者只能指向他/她之前收到過一個比特幣,所以一個交易輸出被創建用來顯示一個比特幣發送給你的地址(交易的輸入指向前一個交易的輸出)。
你的錢包余額是所有未使用的交易輸出的總和
從這一個點出發,我們會依照比特幣中的說明,把所有未使用的交易輸出稱為UTXO.
創建TransactionInput?類:
public class TransactionInput {public String transactionOutputId; //Reference to TransactionOutputs -> transactionIdpublic TransactionOutput UTXO; //Contains the Unspent transaction outputpublic TransactionInput(String transactionOutputId) {this.transactionOutputId = transactionOutputId;} }這個類將用于引用尚未使用的transactionoutput。transactionOutputId將用于查找相關的TransactionOutput,允許礦工檢查您的所有權。
創建TransactionOutputs?類:
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 ownpublic String parentTransactionId; //the id of the transaction this output was created in//Constructorpublic 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 youpublic 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>(); //list of all unspent transactions. public static int difficulty = 5;public static Wallet walletA;public static Wallet walletB;public static void main(String[] args) {hashmap允許我們使用鍵來查找值,但是您需要導入java.util.hashmap
重點來了,我們在交易類中增加一個processTransaction方法,這個方法是把一切放在一起用來處理交易。
//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 recipientoutputs.add(new TransactionOutput( this.sender, leftOver,transactionId)); //send the left over 'change' back to sender //add outputs to Unspent listfor(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) valuespublic 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方法
通過這種方法,我們執行一些檢查以確保交易有效,然后收集輸入并生成輸出。最重要的是,最后,我們拋棄了輸入在我們的UTXO列表,這就意味著一個可以使用的交易輸出必須之前一定是輸入,所以輸入的值必須被完全使用,所以付款人必須改變它們自身的金額狀態。
紅色箭頭是輸出。請注意,綠色輸入是對之前輸出的參考
最后讓我們修改我們的wallet類
- 搜集余額(通過循環遍歷UTXO列表來檢查交易的輸出是否是我的)并
- 創建交易
你可以隨時為錢包添加一些其他功能,例如記錄您的交易歷史記錄。
添加交易到區塊中
現在我們已經有了一個有效的交易系統,我們需要把交易加入到我們的區塊鏈中。我們把交易列表替換我們塊中無用的數據,但是在一個單一的區塊中可能存放了1000個交易,這就會導致大量的hash計算,不用擔心在這里我們使用了交易的merkle root,稍后你會看到。讓我們增加一個幫助方法來創建merkle root在StringUtils類中。
在StringUtils類中創建getMerkleRoot方法
//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;}我會盡快用一個實際的merkleroot取代這個方法,但是現在使用這個可以正常運行
修來Block?類
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 contentspublic 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 blockpublic 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;}}需要注意的是我們還更新了Block構造函數,因為我們不再需要傳遞字符串數據,并將merkle root包含在計算哈希方法中。addTransaction方法用來增加交易,只有滿足條件下才可以成功的在區塊中增加交易。
我們已經實現了一個可交易的區塊鏈。
最后的測試
我們應該測試從錢包發送貨幣,更新區塊鏈并進行有效性檢查。但首先我們需要一種將新硬幣引入混合的方法。有很多方法來創建新的硬幣。在比特幣區塊鏈上,有很多方法可以創造新的比特幣:礦工可以將交易包括在內,作為對每個礦工挖礦的獎勵。但在這里我們只希望在創世紀區塊中釋放貨幣。就像比特幣中一下,所以我們修改我們的主函數以達到下面的目的。
打印:
Connected to the target VM, address: '127.0.0.1:57085', transport: 'socket' Creating and Mining Genesis block... Transaction Successfully added to Block Block Mined!!! : 000b5a7ca6bf07639122cb31e884996895a482c281dd89c203824f1e93a661bfWalletA's balance is: 100.0WalletA is Attempting to send funds (40) to WalletB... Transaction Successfully added to Block Block Mined!!! : 000c7f814357abfea86ad1b38ec1dc3afed2afc9107f2bacc933d8136bf34df0WalletA's balance is: 60.0 WalletB's balance is: 40.0WalletA Attempting to send more funds (1000) than it has... #Not Enough funds to send transaction. Transaction Discarded. Block Mined!!! : 000b3822fb7985db438712816727d4bc382926a1c4654062aad4071d9b9fad98WalletA's balance is: 60.0 WalletB's balance is: 40.0WalletB is Attempting to send funds (20) to WalletA... Transaction Successfully added to BlockWalletA's balance is: 80.0 WalletB's balance is: 20.0 Blockchain is valid現在錢包能夠安全地在您的區塊鏈上發送金額,只要他們有金額發送。這意味著你有你自己的本地加密貨幣
可以進行交易的區塊鏈
你已經成功地創建了自己的加密貨幣。你的區塊鏈:
- 允許用戶創建錢包
- 使用橢圓曲線加密方式為錢包提供公鑰和私鑰
- 通過使用數字簽名算法證明所有權,確保資金轉移
- 允許用戶在區塊鏈上進行交易
項目地址:https://github.com/pibigstar/blockchain
轉載自:https://longfeizheng.github.io/2018/03/11/用Java創建你的第一個區塊鏈-part2/
總結
以上是生活随笔為你收集整理的用java写一个简单的区块链(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用Java编写第一个区块链(二)
- 下一篇: 基于Java语言构建区块链(六)—— 交