用Java建立你第一個區塊鏈-part2__PHP

來源:互聯網
上載者:User

      在上一章節中,我們主要講述了區塊鏈,區塊,挖礦等等,設定了區塊的資料格式,實現了如何通過加密的方式將區塊加入到區塊鏈中,以此來保證區塊鏈的有效性,同時我們還通過設計一種資料難題,讓區塊加入到區塊鏈中需要通過一定的計算能力(PoW)來證明是區塊鏈中的一個新的區塊。

      而在這一章節中,我們將會實現下面幾個功能

      1、建立一個簡單的錢包(wallet)

      2、運用我們的區塊鏈來發送交易(transaction)

      這樣就會產生我們自己的加密貨幣。

      在此之前,我們先來理解一下比特幣中關於這部分的概念,交易就是比特幣的核心,區塊鏈的唯一意圖就是要通過安全的可靠的方式進行儲存體交易,交易一旦被建立就沒有人可以修改它們。

      只要你開發過web應用程式,你就知道為了實現交易你必須在資料庫中建立:賬戶表和交易表,賬戶表中存放的是每一個賬戶的資訊,包括賬戶的設定檔以及餘額,而交易表中存放了每一筆交易從一個賬戶到另外一個賬戶。但在比特幣中,交易的實現方式與web應用中的實現方式完全不同。在比特幣中:

        1、沒有賬戶

        2、沒有餘額

        3、沒有地址

        4、沒有貨幣資訊

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

        因為區塊鏈是完全公開的、開源的資料庫,我們不希望儲存關於使用者的任何隱私的訊息,所以在賬戶表中不存在任何金額的資訊,交易表中也沒有儲存金額從一個賬戶到另一個賬戶的資訊,更沒有儲存賬戶的餘額資訊。僅僅只有這一次交易的資訊,那麼究竟交易中存放的是什麼呢。讓我們繼續看下去。

        從上一章節用Java建立你第一個區塊鏈-part1,我們已經有了一個基本的區塊鏈,但是在區塊中我們存放的是一些無用的資訊(data),今天我們將用交易替換這些資訊,讓我們的區塊中也可以儲存大量的交易資訊,使得我們可以建立自己加密貨幣。注意,這篇課程是在上一篇課程的基礎上實現的,並且依賴於bouncycastle和gson。

        第一步、準備一個錢包(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; } //建立交易輸出

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.