publicclassTransaction { 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. publicfloat value; publicbyte[] signature; // this is to prevent anybody else from spending funds in our wallet. public ArrayList<TransactionInput> inputs = newArrayList<TransactionInput>(); public ArrayList<TransactionOutput> outputs = newArrayList<TransactionOutput>(); privatestaticintsequence=0; // a rough count of how many transactions have been generated. // Constructor: publicTransaction(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 ); } }
//Signs all the data we dont wish to be tampered with. publicvoidgenerateSignature(PrivateKey privateKey) { Stringdata= StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ; signature = StringUtil.applyECDSASig(privateKey,data); } //Verifies the data we signed hasnt been tampered with publicbooleanverifiySignature() { Stringdata= StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ; return StringUtil.verifyECDSASig(sender, data, signature); }
 当该交易被矿工添加到区块链上的一个新块的时候,这个签名将会被验证。
3.测试钱包和签名
 现在,我们基本上已经完成了一半的工作。在 NoobChain 这个类中,添加一些新的变量,并替换到 main 方法中的一些方法。
publicstaticvoidmain(String[] args) { //Setup Bouncey castle as a Security Provider Security.addProvider(neworg.bouncycastle.jce.provider.BouncyCastleProvider()); //Create the new wallets walletA = newWallet(); walletB = newWallet(); //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 Transactiontransaction=newTransaction(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()); }
publicclassTransactionInput { public String transactionOutputId; //Reference to TransactionOutputs -> transactionId public TransactionOutput UTXO; //Contains the Unspent transaction output
publicclassTransactionOutput { public String id; public PublicKey reciepient; //also known as the new owner of these coins. publicfloat value; //the amount of coins they own public String parentTransactionId; //the id of the transaction this output was created in //Constructor publicTransactionOutput(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 publicbooleanisMine(PublicKey publicKey) { return (publicKey == reciepient); } }
//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()); returnfalse; }
//generate transaction outputs: floatleftOver= getInputsValue() - value; //get value of inputs then the left over change: transactionId = calulateHash(); outputs.add(newTransactionOutput( this.reciepient, value,transactionId)); //send value to recipient outputs.add(newTransactionOutput( 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); }
returntrue; }
//returns sum of inputs(UTXOs) values publicfloatgetInputsValue() { floattotal=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: publicfloatgetOutputsValue() { floattotal=0; for(TransactionOutput o : outputs) { total += o.value; } return total; }
publicclassWallet { public PrivateKey privateKey; public PublicKey publicKey; public HashMap<String,TransactionOutput> UTXOs = newHashMap<String,TransactionOutput>(); //only UTXOs owned by this wallet. publicWallet() {... publicvoidgenerateKeyPair() {... //returns balance and stores the UTXO's owned by this wallet in this.UTXOs publicfloatgetBalance() { floattotal=0; for (Map.Entry<String, TransactionOutput> item: NoobChain.UTXOs.entrySet()){ TransactionOutputUTXO= 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."); returnnull; } //create array list of inputs ArrayList<TransactionInput> inputs = newArrayList<TransactionInput>(); floattotal=0; for (Map.Entry<String, TransactionOutput> item: UTXOs.entrySet()){ TransactionOutputUTXO= item.getValue(); total += UTXO.value; inputs.add(newTransactionInput(UTXO.id)); if(total > value) break; } TransactionnewTransaction=newTransaction(publicKey, _recipient , value, inputs); newTransaction.generateSignature(privateKey); for(TransactionInput input: inputs){ UTXOs.remove(input.transactionOutputId); } return newTransaction; } }
6. 将交易添加到区块中
 现在,我们有了一个可以工作的交易系统了,接下来,需要将其实现到区块链上。现在,我们可以将以前那些无用的数据替换为 ArrayList of transactions(交易列表),同时,使用根哈希的方式,计算区块的哈希值。  下面,在 StringUtil 中增加一个获取根哈希(译注:可以参考 维基百科)的方法:
publicclassBlock { public String hash; public String previousHash; public String merkleRoot; public ArrayList<Transaction> transactions = newArrayList<Transaction>(); //our data will be a simple message. publiclong timeStamp; //as number of milliseconds since 1/1/1970. publicint nonce; //Block Constructor. publicBlock(String previousHash ) { this.previousHash = previousHash; this.timeStamp = newDate().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() { Stringcalculatedhash= StringUtil.applySha256( previousHash + Long.toString(timeStamp) + Integer.toString(nonce) + merkleRoot ); return calculatedhash; } //Increases nonce value until hash target is reached. publicvoidmineBlock(int difficulty) { merkleRoot = StringUtil.getMerkleRoot(transactions); Stringtarget= 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 publicbooleanaddTransaction(Transaction transaction) { //process transaction and check if valid, unless block is genesis block then ignore. if(transaction == null) returnfalse; if((previousHash != "0")) { if((transaction.processTransaction() != true)) { System.out.println("Transaction failed to process. Discarded."); returnfalse; } } transactions.add(transaction); System.out.println("Transaction Successfully added to Block"); returntrue; } }