Repository: phishman3579/Bitcoin Branch: master Commit: 4ed6be50a2a6 Files: 33 Total size: 159.6 KB Directory structure: gitextract_1ggmfz6a/ ├── .classpath ├── .gitignore ├── .project ├── LICENSE ├── README.md └── src/ └── com/ └── jwetherell/ └── bitcoin/ ├── Blockchain.java ├── Peer.java ├── ProofOfWork.java ├── Wallet.java ├── common/ │ ├── Constants.java │ ├── HashUtils.java │ └── KeyUtils.java ├── data_model/ │ ├── Block.java │ ├── Data.java │ └── Transaction.java ├── interfaces/ │ ├── MessageListener.java │ ├── Receiver.java │ └── Sender.java ├── networking/ │ ├── Multicast.java │ ├── TCP.java │ └── UDP.java └── test/ ├── AllTest.java ├── BlockChainTest.java ├── BlockTest.java ├── DataTest.java ├── EncodeDecodeTest.java ├── MulticastTest.java ├── PeerTest.java ├── ProofOfWorkTest.java ├── TCPTest.java ├── TransactionTest.java ├── UDPTest.java └── WalletTest.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .classpath ================================================ ================================================ FILE: .gitignore ================================================ /bin/ ================================================ FILE: .project ================================================ Bitcoin org.eclipse.jdt.core.javabuilder org.eclipse.jdt.core.javanature ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Bitcoin An example Bitcoin implementation which can be used to learn about Bitcoin/Blockchain. This implementations is for educational use only. # Overview. ## Wallet The Wallet is how peers interact with the Bitcoin peer-to-peer network. The Wallet generates a public key and a private key which it uses to sign each Transaction. The pulic key is the send-to address used by the Bitcoin network. Each Wallet has the ability to send coins from your account to another account and it also has the ability to confirm Transactions (except it's own) which it receives from the Bitcoin peer-to-peer network. ``` Wallet { sendCoin(entity, value); // Creates a new Transaction handleTransaction(Transaction); // Receives a unconfirmed Transaction handleConfirmation(Transaction); // Receives a confirmed Transaction and adds to blockchain } ``` ## Transaction Transactions are just a collection of input transactions, output transactions, a value, and a signature. ``` Transaction { byte[] header; Transaction[] inputs; Transaction[] outputs; long value; byte[] signature; } ``` See the [Transaction Class](https://github.com/phishman3579/Bitcoin/blob/master/src/com/jwetherell/bitcoin/data_model/Transaction.java) for reference. #### The Wallet also has a number of Transaction rules: * Once a Transaction has been used as an input, it cannot be used again. * All inputs on a Transaction have to be completely consumed on a transaction. Note: To send a Bitcoin transaction, you have to already own a Bitcoin. Getting an initial Bitcoin is usually done by trading something for a number of Bitcoins. One caveat of, having to own a Bitcoin to make a transaction, is the first transaction. The first transaction is called the genesis transaction, it is the only transaction which does not need input transactions. ### An example Transaction If Justin wants to send 6 coins to George: Ledger: | Justin's unused Transactions | George's unused Transaction | | ----------------------------- | ----------------------------- | | Transaction #1 : 5 Coins | | | Transaction #2 : 3 Coins | | | Transaction #3 : 7 Coins | | ``` Aggregate Transaction #4 { byte[] header "6 coins for George and 2 coins to Justin" Transaction[] input { Transaction #1, Transaction #2 } Transaction[] output { Transaction #5, Transaction #6 } int value 0 byte[] signature "Justin's signature based on the Header" } ``` Note: The 'value' on the Aggregate Transaction (#4) is a reward for anyone who confirms the Transaction. The higher the reward, the better chance the Transaction will be processed quicker. ``` Transaction #5 { byte[] header "2 coins to Justin" Transaction[] input { Transaction #1, Transaction #2 } Transaction[] output { } int value 2 byte[] signature "Justin's signature based on the Header" } Transaction #6 { byte[] header "6 coins for George" Transaction[] input { Transaction #1, Transaction #2 } Transaction[] output { } int value 6 byte[] signature "Justin's signature based on the Header" } ``` The Aggregate Transaction (#4) will remove Transaction #1 and #2 from Justin's unused Transactions. Since the total of all inputs is 8 coins, which is 2 more than what Justin wants to send to George, the output will contain a Transaction which sends 2 coins back to Justin. The Wallet will use it's private key to sign the Header of the Aggregate Transactions (#4) and it will also sign each of the output Transactions (#5 & #6). It will then send Transaction #4 to the Bitcoin network for confirmation. Each peer on the Bitcoin network will receive the Transaction and try to confirm it. To confirm a Transaction, a Peer will: * Check the Signature of Transaction against the public key of the sender. If it passes: * Send the confirmed Transaction to the Bitcoin network. ## Block The confirmed Transaction (#4) is added to a pool of confirmed Transactions. Peers (also called Miners) will gather confirmed Transactions from the pool and put them into a Block. A Block contains a number of confirmed Transactions, the Miner's signature, and a couple of other fields used for "Proof of work" processing. ``` Block { Transaction[] transactions int nonce int zeros byte[] previousHash byte[] nextHash byte[] signature } ``` See the [Block Class](https://github.com/phishman3579/Bitcoin/blob/master/src/com/jwetherell/bitcoin/data_model/Block.java) for reference. Miners will create a single 'block hash' from all the confirmed Transactions in the Block. They will then go through the process of "Proof of work". The goal of the "Proof of work" is to create a hash which begins with a random number of zeros (see the 'zeros' field). "Proof of work" is designed to be processor intensive which adds randomness to the time it takes to process a Block. A Miner will take the 'block hash' and append a random integer (called a 'nonce') to it. It will then create a new hash from 'block hash + nonce' and see if it satisfies the "Proof of work", this process will repeat until it finds a 'nonce' which satisfies the "Proof of work" See the [Proof of work](https://github.com/phishman3579/Bitcoin/blob/master/src/com/jwetherell/bitcoin/ProofOfWork.java) for reference. Once a Miner finds a 'nonce' which satisfies the "Proof of work", it will: * Create another hash (see 'nextHash') using the Blockchain's current hash (see 'previousHash') and the 'block hash' * Send the Block to the Bitcoin network. ``` Block #1 { Transaction[] transactions { Transaction #4 } int nonce 453; int zeros 3; byte[] previousHash "Blockchain hash #1"; byte[] nextHash "Blockchain hash #2"; byte[] signature "Miner's signature"; } ``` Peers on the Bitcoin network will receive the Block and start confirming it. To confirm the Block, A Peer will: * Make sure the 'nonce' satisfies the "Proof of work" * Check the Block's signature * Check the signature of each Trasaction in the Block. If everything passes: * Add the block to it's Blockchain. * Send the confirmed Block to the Bitcoin network ## Blockchain The Blockchain is a simple structure which contains a list of confirmed Blocks, a list of Transactions in chronological order, a list of unused Transactions, and the current hash. Note: all transactions in the same block are said to have happened at the same time. ``` Blockchain { List blockchain List transactions List unused byte[] currentHash } ``` See the [Blockchain](https://github.com/phishman3579/Bitcoin/blob/master/src/com/jwetherell/bitcoin/BlockChain.java) for reference. When the Peer adds the Block to the Blockchain, the Blockchain will: * Check to see if the 'previousHash' from the Block matches it's 'currentHash', * Check to see if the input Transactions from all the Transactions in the Block are 'unused' If everything passes: * The Block is added to the 'blockChain' * The Transaction is added to the 'transactions' list * All 'input' transactions are removed from the 'unused' list * All the 'output' transactions are added to the 'unused' list * The 'currentHash' is updated to 'nextHash' from the current Block. ``` Blockchain { List blockchain { Block #0 } List transactions { Transaction #0 } List unused { Transaction #1, Transaction #2, Transaction #3 } byte[] currentHash "Blockchain hash #1" } ``` Updated Blockchain. ``` Blockchain { List blockchain { Block #0, Block #1 }; List transactions { Transaction #0, Transaction #4 } List unused { Transaction #3, Transaction #5, Transaction #6 } byte[] currentHash "Blockchain hash #2" } ``` Ledger: | Justin's unused Transactions | George's unused Transaction | | ----------------------------- | ----------------------------- | | Transaction #3 : 7 Coins | Transaction #6 : 6 Coins | | Transaction #5 : 2 Coins | | | | | Based off of [1](http://www.michaelnielsen.org/ddi/how-the-bitcoin-protocol-actually-works/) and [2](http://www.imponderablethings.com/2013/07/how-bitcoin-works-under-hood.html) Also see the [original paper](https://bitcoin.org/bitcoin.pdf) ================================================ FILE: src/com/jwetherell/bitcoin/Blockchain.java ================================================ package com.jwetherell.bitcoin; import java.nio.ByteBuffer; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import com.jwetherell.bitcoin.common.Constants; import com.jwetherell.bitcoin.common.HashUtils; import com.jwetherell.bitcoin.data_model.Block; import com.jwetherell.bitcoin.data_model.Transaction; public class Blockchain { public static final String NO_ONE = "no one"; public static final Signature NO_ONE_SIGNATURE; public static final byte[] NO_ONE_PUB_KEY; static { try { final KeyPairGenerator gen = KeyPairGenerator.getInstance("DSA", "SUN"); final SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN"); gen.initialize(512, random); final KeyPair pair = gen.generateKeyPair(); final PrivateKey privateKey = pair.getPrivate(); NO_ONE_SIGNATURE = Signature.getInstance("SHA1withDSA", "SUN"); NO_ONE_SIGNATURE.initSign(privateKey); final PublicKey publicKey = pair.getPublic(); NO_ONE_PUB_KEY = publicKey.getEncoded(); } catch (Exception e) { throw new RuntimeException(e); } } public static final String GENESIS_NAME = "genesis"; protected static final boolean DEBUG = Boolean.getBoolean("debug"); private static final byte[] INITIAL_HASH = new byte[0]; private static final Transaction GENESIS_TRANS; private static final Block GENESIS_BLOCK; static { // To start the blockchain, we have an initial transaction which has no inputs and one output which // is given to the genesis entity. This is the only transaction which has no inputs. final Transaction[] empty = new Transaction[0]; final Transaction[] output = new Transaction[1]; final String outputMsg = "Genesis gets 50 coins."; output[0] = Transaction.newSignedTransaction(NO_ONE_SIGNATURE, NO_ONE, GENESIS_NAME, outputMsg, 50, empty, empty); final String msg = "Genesis transfer."; GENESIS_TRANS = Transaction.newSignedTransaction(NO_ONE_SIGNATURE, NO_ONE, GENESIS_NAME, msg, 0, empty, output); final ByteBuffer buffer = ByteBuffer.allocate(GENESIS_TRANS.getBufferLength()); GENESIS_TRANS.toBuffer(buffer); buffer.flip(); final byte[] bytes = buffer.array(); final byte[] nextHash = Blockchain.getNextHash(Blockchain.INITIAL_HASH, bytes); final Transaction[] trans = new Transaction[]{ GENESIS_TRANS }; GENESIS_BLOCK = new Block(NO_ONE, Blockchain.INITIAL_HASH, nextHash, trans, 0); GENESIS_BLOCK.confirmed = true; } private final List blockchain = new CopyOnWriteArrayList(); private final List transactions = new CopyOnWriteArrayList(); private final List unused = new CopyOnWriteArrayList(); private final String owner; private volatile byte[] latestHash = INITIAL_HASH; public Blockchain(String owner) { this.owner = owner; // transfer initial coins to genesis entity this.addBlock(GENESIS_NAME, GENESIS_BLOCK); } public int getLength() { return blockchain.size(); } public Block getBlock(int blockNumber) { if (blockNumber>=blockchain.size()) return null; return blockchain.get(blockNumber); } public List getUnused() { return unused; } public Block getNextBlock(String name, Transaction[] transactions) { int length = 0; for (Transaction transaction : transactions) length += transaction.getBufferLength(); final byte[] bytes = new byte[length]; final ByteBuffer bb = ByteBuffer.wrap(bytes); for (Transaction transaction : transactions) { final ByteBuffer buffer = ByteBuffer.allocate(transaction.getBufferLength()); transaction.toBuffer(buffer); buffer.flip(); bb.put(buffer.array()); } final byte[] nextHash = getNextHash(latestHash, bytes); return (new Block(name, latestHash, nextHash, transactions, this.blockchain.size())); } public Constants.Status checkHash(Block block) { if (block.blockLength > this.blockchain.size()) { // This block is in the future if (DEBUG) System.out.println(owner+" found a future block. lengths="+this.blockchain.size()+"\n"+"block={\n"+block.toString()+"\n}"); return Constants.Status.FUTURE_BLOCK; } int length = 0; for (Transaction transaction : block.transactions) length += transaction.getBufferLength(); final byte[] bytes = new byte[length]; final ByteBuffer bb = ByteBuffer.wrap(bytes); for (Transaction transaction : block.transactions) { final ByteBuffer buffer = ByteBuffer.allocate(transaction.getBufferLength()); transaction.toBuffer(buffer); buffer.flip(); bb.put(buffer.array()); } // Calculate what I think the next has should be final byte[] nextHash = getNextHash(latestHash, bytes); // Store the previous and next hash from the block final byte[] incomingPrev = block.prev; final byte[] incomingHash = block.hash; if (!(Arrays.equals(incomingHash, nextHash))) { // This block has a different 'next' hash then I expect, someone is out of sync if (DEBUG) { StringBuilder builder = new StringBuilder(); builder.append(owner).append(" Invalid hash in block\n"); builder.append("confirmed="+block.confirmed).append("\n"); builder.append("length=").append(this.blockchain.size()).append("\n"); builder.append("latest=["+HashUtils.bytesToHex(latestHash)+"]\n"); builder.append("next=["+HashUtils.bytesToHex(nextHash)+"]\n"); builder.append("incomingLength=").append(block.blockLength).append("\n"); builder.append("incomingPrev=["+HashUtils.bytesToHex(incomingPrev)+"]\n"); builder.append("incomingNext=["+HashUtils.bytesToHex(incomingHash)+"]\n"); System.err.println(builder.toString()); } return Constants.Status.BAD_HASH; } return Constants.Status.SUCCESS; } public Constants.Status addBlock(String dataFrom, Block block) { // Already processed this block? Happens if a miner is slow and isn't first to confirm the block if (blockchain.contains(block)) return Constants.Status.DUPLICATE; // Check to see if the block's hash is what I expect final Constants.Status status = checkHash(block); if (status != Constants.Status.SUCCESS) return status; // Get the aggregate transaction for processing for (Transaction transaction : block.transactions) { // Remove the inputs from the unused pool for (Transaction t : transaction.inputs) { boolean exists = unused.remove(t); if (exists == false) { if (DEBUG) System.err.println(owner+" Bad inputs in block from '"+dataFrom+"'. block={\n"+block.toString()+"\n}"); return Constants.Status.BAD_INPUTS; } } } // Add outputs to unused pool for (Transaction transaction : block.transactions) { for (Transaction t : transaction.outputs) unused.add(t); } // Update the hash and add the new transaction to the list final byte[] prevHash = latestHash; final byte[] nextHash = block.hash; blockchain.add(block); for (Transaction transaction : block.transactions) transactions.add(transaction); latestHash = nextHash; if (DEBUG) { final String prev = HashUtils.bytesToHex(prevHash); final String next = HashUtils.bytesToHex(nextHash); final StringBuilder builder = new StringBuilder(); builder.append(owner).append(" updated hash").append(" msg_from='"+dataFrom+"'").append(" block_from='"+block.from+"'\n"); builder.append("blockchain length=").append(blockchain.size()).append("\n"); builder.append("transactions=[\n"); for (Transaction t : block.transactions) { builder.append(t.toString()).append("\n"); } builder.append("]\n"); builder.append("prev=[").append(prev).append("]\n"); builder.append("next=[").append(next).append("]\n"); System.err.println(builder.toString()); } return Constants.Status.SUCCESS; } public long getBalance(String name) { long result = 0; for (Transaction t : transactions) { // Remove the inputs for (Transaction c : t.inputs) { if (name.equals(c.to)) result -= c.value; } // Add the outputs for (Transaction c : t.outputs) { if (name.equals(c.to)) result += c.value; } } return result; } public static final byte[] getNextHash(byte[] hash, byte[] bytes) { final StringBuilder builder = new StringBuilder(); builder.append(HashUtils.bytesToHex(hash)).append(HashUtils.bytesToHex(bytes)); final String string = builder.toString(); final byte[] output = HashUtils.calculateSha256(string); return output; } /** * {@inheritDoc} */ @Override public boolean equals(Object o) { if (!(o instanceof Blockchain)) return false; final Blockchain other = (Blockchain) o; for (Block b : blockchain) { if (!(other.blockchain.contains(b))) return false; } for (Transaction t : transactions) { if (!(other.transactions.contains(t))) return false; } return true; } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("hash=[").append(HashUtils.bytesToHex(latestHash)).append("]\n"); builder.append("inputs={").append("\n"); for (Transaction c : transactions) { for (Transaction i : c.inputs) builder.append('\t').append(i.value).append(" from '").append(i.from).append("' to '").append(i.to).append("'\n"); builder.append("}\n"); } builder.append("outputs={").append("\n"); for (Transaction c : transactions) { for (Transaction i : c.outputs) builder.append('\t').append(i.value).append(" from '").append(i.from).append("' to '").append(i.to).append("'\n"); builder.append("}\n"); } return builder.toString(); } } ================================================ FILE: src/com/jwetherell/bitcoin/Peer.java ================================================ package com.jwetherell.bitcoin; import java.nio.ByteBuffer; import java.util.Map; import java.util.Queue; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import com.jwetherell.bitcoin.common.Constants; import com.jwetherell.bitcoin.common.HashUtils; import com.jwetherell.bitcoin.data_model.Transaction; import com.jwetherell.bitcoin.data_model.Data; import com.jwetherell.bitcoin.data_model.Block; import com.jwetherell.bitcoin.interfaces.MessageListener; import com.jwetherell.bitcoin.interfaces.Receiver; import com.jwetherell.bitcoin.networking.Multicast; import com.jwetherell.bitcoin.networking.TCP; /** * Class which handles lower level sending and receiving of messages. * * Thread-Safe (Hopefully) */ public abstract class Peer { protected static final boolean DEBUG = Boolean.getBoolean("debug"); private static final int HEADER_LENGTH = 16; private static final String WHOIS = "Who is "; private static final String IAM = "I am "; private static final String TRANSACTION = "Transaction "; private static final String TRANSACTION_ACK = "Transaction ACK "; private static final String BLOCK = "Block "; private static final String CONFIRMATION = "Block Confirm "; private static final String RESEND = "Resend "; private static final String REHASH = "Rehash "; private static final int KEY_LENGTH = 4; private static final int NAME_LENGTH = 4; private static final String EVERY_ONE = "EVERYONE"; private static final byte[] NO_SIG = new byte[0]; private final TCP.Peer.RunnableSend runnableSendTcp = new TCP.Peer.RunnableSend(); private final Multicast.Peer.RunnableSend runnableSendMulti = new Multicast.Peer.RunnableSend(); private final MessageListener listener = new MessageListener() { /** * {@inheritDoc} */ @Override public void onMessage(Receiver recv) { Data data = recv.getQueue().poll(); while (data != null) { final String from = data.from; final byte[] bytes = data.message.array(); final String string = new String(bytes); final String hdr = string.substring(0, HEADER_LENGTH); if (DEBUG) System.out.println(myName+" Listener received '"+hdr+"' msg"); if (hdr.equals(WHOIS)) { handleWhois(bytes,data); } else if (hdr.equals(IAM)) { handleIam(bytes,data); processTransactionsToSend(from); processTransactionsToRecv(from); } else if (hdr.equals(TRANSACTION)) { handleTransaction(from,bytes,data); } else if (hdr.equals(TRANSACTION_ACK)) { handleTransactionAck(from,bytes,data); } else if (hdr.equals(BLOCK)) { handleBlock(from,bytes,data); } else if (hdr.equals(CONFIRMATION)) { handleConfirmation(from,bytes,data); processFutureBlocksToRecv(from); } else if (hdr.equals(RESEND)) { handleResend(from,bytes,data); } else if (hdr.equals(REHASH)) { handleRehash(from,bytes,data); } else { System.err.println(myName+" Cannot handle msg. hdr="+hdr); } // Get next message data = recv.getQueue().poll(); Thread.yield(); } Thread.yield(); } }; // Keep track of everyone's name -> ip+port private final Map peers = new ConcurrentHashMap(); // Pending msgs (This happens if we don't know the ip+port OR the public key of a host private final Map> transactionsToSend = new ConcurrentHashMap>(); private final Map> transactionsToRecv = new ConcurrentHashMap>(); private final Map> futureTransactionsToRecv = new ConcurrentHashMap>(); private final Map timerMap = new ConcurrentHashMap(); private final Timer timer = new Timer(); private final Thread tcpSendThread; private final Thread tcpRecvThread; private final Thread multiSendThread; private final Thread multiRecvThread; // Thread safe queues for sending messages private final Queue sendTcpQueue; private final Queue sendMultiQueue; protected final TCP.Peer.RunnableRecv runnableRecvTcp = new TCP.Peer.RunnableRecv(listener); protected final Multicast.Peer.RunnableRecv runnableRecvMulti = new Multicast.Peer.RunnableRecv(listener); protected final String myName; protected Peer(String name) { this.myName = name; // Receivers tcpRecvThread = new Thread(runnableRecvTcp, myName+" recvTcp"); tcpRecvThread.start(); multiRecvThread = new Thread(runnableRecvMulti, myName+" recvMulti"); multiRecvThread.start(); // Senders tcpSendThread = new Thread(runnableSendTcp, myName+" sendTcp"); sendTcpQueue = runnableSendTcp.getQueue(); tcpSendThread.start(); multiSendThread = new Thread(runnableSendMulti, myName+" sendMulti"); sendMultiQueue = runnableSendMulti.getQueue(); multiSendThread.start(); while (isReady()==false) { Thread.yield(); } } public String getName() { return myName; } public void shutdown() throws InterruptedException { TCP.Peer.RunnableSend.run = false; TCP.Peer.RunnableRecv.run = false; Multicast.Peer.RunnableSend.run = false; Multicast.Peer.RunnableRecv.run = false; // Senders multiSendThread.interrupt(); multiSendThread.join(); tcpSendThread.interrupt(); tcpSendThread.join(); // Receivers tcpRecvThread.interrupt(); tcpRecvThread.join(); multiRecvThread.interrupt(); multiRecvThread.join(); } public boolean isReady() { return (runnableRecvTcp.isReady() && runnableSendTcp.isReady() && runnableRecvMulti.isReady() && runnableSendMulti.isReady()); } public abstract Blockchain getBlockChain(); /** Get encoded public key **/ protected abstract byte[] getPublicKey(); private void sendWhois(String who) { final byte[] msg = getWhoisMsg(who); final Data data = new Data(myName, runnableRecvTcp.getHost(), runnableRecvTcp.getPort(), EVERY_ONE, runnableRecvMulti.getHost(), runnableRecvMulti.getPort(), NO_SIG, msg); if (DEBUG) { final String string = new String(msg); final String hdr = string.substring(0, HEADER_LENGTH); System.out.println(myName+" Sending '"+hdr+"' msg"); } sendMultiQueue.add(data); } private void handleWhois(byte[] bytes, Data data) { final String name = parseWhoisMsg(bytes); // If your name and not from myself then shout it out! if (name.equals(this.myName) && !(data.from.equals(this.myName))) sendIam(); } private void sendIam() { final byte[] msg = getIamMsg(getPublicKey()); final Data data = new Data(myName, runnableRecvTcp.getHost(), runnableRecvTcp.getPort(), EVERY_ONE, runnableRecvMulti.getHost(), runnableRecvMulti.getPort(), NO_SIG, msg); if (DEBUG) { final String string = new String(msg); final String hdr = string.substring(0, HEADER_LENGTH); System.out.println(myName+" Sending '"+hdr+"' msg"); } sendMultiQueue.add(data); } private void handleIam(byte[] bytes, Data data) { final String name = data.from; final byte[] key = parseIamMsg(bytes); // Update peers peers.put(name, new Host(data.sourceAddr.getHostAddress(), data.sourcePort)); // New public key newPublicKey(name, key); } /** What do you want to do with a new public key **/ protected abstract void newPublicKey(String name, byte[] publicKey); /** Sign message with private key **/ protected abstract byte[] signMsg(byte[] bytes); /** Verify the bytes given the public key and signature **/ protected abstract boolean verifyMsg(byte[] publicKey, byte[] signature, byte[] bytes); /** Send transaction to the peer named 'to' **/ protected void sendTransaction(String to, Transaction transaction) { final Host d = peers.get(to); if (d == null){ // Could not find peer, broadcast a whois addTransactionToSend(Queued.State.NEW, to, transaction); sendWhois(to); return; } final byte[] msg = getTransactionMsg(transaction); final byte[] sig = signMsg(msg); final Data data = new Data(myName, runnableRecvTcp.getHost(), runnableRecvTcp.getPort(), to, d.ip, d.port, sig, msg); if (DEBUG) { final String string = new String(msg); final String hdr = string.substring(0, HEADER_LENGTH); System.out.println(myName+" Sending '"+hdr+"' msg"); } sendTcpQueue.add(data); } private Constants.Status handleTransaction(String dataFrom, byte[] bytes, Data data) { final Transaction trans = parseTransactionMsg(bytes); return handleTransaction(dataFrom, trans, data); } private Constants.Status handleTransaction(String dataFrom, Transaction transaction, Data data) { // I shouldn't ACK my own transaction if (transaction.from.equals(myName)) return Constants.Status.OWN_TRANSACTION; // Let the app logic do what it needs to final Constants.Status status = handleTransaction(dataFrom, transaction, data.signature.array(), data.message.array()); if (status == Constants.Status.NO_PUBLIC_KEY) { addTransactionToRecv(Queued.State.NEW, dataFrom, transaction, data); sendWhois(dataFrom); return status; } else if (status != Constants.Status.SUCCESS) { return status; } // Send an ACK msg ackTransaction(dataFrom, transaction); return status; } /** What do you want to do now that you have received a transaction, return the KeyStatus **/ protected abstract Constants.Status handleTransaction(String dataFrom, Transaction transaction, byte[] signature, byte[] bytes); private void ackTransaction(String to, Transaction transaction) { final Host d = peers.get(to); if (d == null){ // Could not find peer, broadcast a whois addTransactionToSend(Queued.State.ACK, to, transaction); sendWhois(to); return; } final byte[] msg = getTransactionAckMsg(transaction); final byte[] sig = signMsg(msg); final Data data = new Data(myName, runnableRecvTcp.getHost(), runnableRecvTcp.getPort(), to, d.ip, d.port, sig, msg); if (DEBUG) { final String string = new String(msg); final String hdr = string.substring(0, HEADER_LENGTH); System.out.println(myName+" Sending '"+hdr+"' msg"); } sendTcpQueue.add(data); } private void handleTransactionAck(String dataFrom, byte[] bytes, Data data) { final Transaction trans = parseTransactionAckMsg(bytes); handleTransactionAck(dataFrom, trans, data); } private void handleTransactionAck(String dataFrom, Transaction transaction, Data data) { // Let the app logic do what it needs to Constants.Status knownPublicKey = handleTransactionAck(dataFrom, transaction, data.signature.array(), data.message.array()); if (knownPublicKey == Constants.Status.NO_PUBLIC_KEY) { addTransactionToRecv(Queued.State.ACK, dataFrom, transaction, data); sendWhois(dataFrom); return; } else if (knownPublicKey != Constants.Status.SUCCESS) { return; } final Block block = aggegateTransaction(transaction); if (block != null) sendBlock(block, data); } /** What do you want to do now that you received an ACK for a sent transaction, return the KeyStatus **/ protected abstract Constants.Status handleTransactionAck(String dataFrom, Transaction transaction, byte[] signature, byte[] bytes); /** Collect the transactions, either return null or a new Block **/ protected abstract Block aggegateTransaction(Transaction transaction); protected void sendBlock(Block block, Data data) { final byte[] msg = getBlockMsg(block); final byte[] sig = signMsg(msg); final Data dataToSend = new Data(myName, runnableRecvTcp.getHost(), runnableRecvTcp.getPort(), data.from, data.sourceAddr.getHostAddress(), data.sourcePort, sig, msg); if (DEBUG) { final String string = new String(msg); final String hdr = string.substring(0, HEADER_LENGTH); System.out.println(myName+" Sending '"+hdr+"' msg. confirmed="+block.confirmed); } sendTcpQueue.add(dataToSend); } private void handleBlock(String dataFrom, byte[] bytes, Data data) { final Block block = parseBlockMsg(bytes); handleBlock(dataFrom, block, data); } private void handleBlock(String dataFrom, Block block, Data data) { // I shouldn't handle my own block if (block.from.equals(myName)) return; final int length = this.getBlockChain().getLength(); final Constants.Status status = checkTransaction(data.from, block, data.signature.array(), data.message.array()); if (status == Constants.Status.NO_PUBLIC_KEY) { addBlockToRecv(Queued.State.CONFIRM, dataFrom, block, data); sendWhois(dataFrom); return; } else if (status == Constants.Status.BAD_HASH) { sendRehash(block, data); return; } else if (status == Constants.Status.FUTURE_BLOCK) { // If we have a transaction which is in the future, we are likely missing a previous block // Save the block for later processing and ask the network for the missing block. addFutureBlockToRecv(Queued.State.FUTURE, dataFrom, block, data); sendResend(length); return; } else if (status != Constants.Status.SUCCESS) { System.out.println(myName+" handleBlock() error="+status); // Drop all other errors return; } // Hash looks good to me, ask everyone else sendConfirmation(block); } /** multicast **/ protected void sendConfirmation(Block block) { final byte[] msg = getConfirmationMsg(block); final byte[] sig = signMsg(msg); final Data data = new Data(myName, runnableRecvTcp.getHost(), runnableRecvTcp.getPort(), EVERY_ONE, runnableRecvMulti.getHost(), runnableRecvMulti.getPort(), sig, msg); if (DEBUG) { final String string = new String(msg); final String hdr = string.substring(0, HEADER_LENGTH); System.out.println(myName+" Sending multicast '"+hdr+"' msg. confirmed="+block.confirmed); } sendMultiQueue.add(data); } /** tcp **/ protected void sendConfirmation(Block block, Data data) { final byte[] msg = getConfirmationMsg(block); final byte[] sig = signMsg(msg); final Data dataToSend = new Data(myName, runnableRecvTcp.getHost(), runnableRecvTcp.getPort(), data.from, data.sourceAddr.getHostAddress(), data.sourcePort, sig, msg); if (DEBUG) { final String string = new String(msg); final String hdr = string.substring(0, HEADER_LENGTH); System.out.println(myName+" Sending unicast '"+hdr+"' msg. confirmed="+block.confirmed); } sendTcpQueue.add(dataToSend); } private void handleConfirmation(String dataFrom, byte[] bytes, Data data) { final Block block = parseConfirmationMsg(bytes); handleConfirmation(dataFrom, block, data); } private void handleConfirmation(String dataFrom, Block block, Data data) { if (block.confirmed) { // Yey! we got a confirmation from the community final int length = this.getBlockChain().getLength(); final Constants.Status status = handleConfirmation(dataFrom, block, data.signature.array(), data.message.array()); if (status == Constants.Status.SUCCESS) { // Someone solved the nonce, cancel async task - if we have one final String hex = HashUtils.bytesToHex(block.hash); final TimerTask miner = timerMap.get(hex); if (miner != null) miner.cancel(); return; } else if (status == Constants.Status.NO_PUBLIC_KEY) { addBlockToRecv(Queued.State.CONFIRM, dataFrom, block, data); sendWhois(dataFrom); return; } else if (status == Constants.Status.BAD_HASH) { sendRehash(block, data); return; } else if (status == Constants.Status.FUTURE_BLOCK) { // If we have a transaction which is in the future, we are likely missing a previous block // Save the block for later processing and ask the network for the missing block. addFutureBlockToRecv(Queued.State.FUTURE, dataFrom, block, data); sendResend(length); return; } else if (status != Constants.Status.SUCCESS) { // Drop all other errors return; } return; } // Don't confirm my own block if (block.from.equals(myName)) return; final int length = this.getBlockChain().getLength(); final Constants.Status status = checkTransaction(dataFrom, block, data.signature.array(), data.message.array()); if (status == Constants.Status.NO_PUBLIC_KEY) { addBlockToRecv(Queued.State.CONFIRM, dataFrom, block, data); sendWhois(dataFrom); return; } else if (status == Constants.Status.FUTURE_BLOCK) { // Do not add to 'FutureBlockToRecv' since this block isn't confirmed sendResend(length); return; } else if (status == Constants.Status.BAD_HASH) { sendRehash(block, data); return; } else if (status != Constants.Status.SUCCESS) { // Drop all other errors return; } // async task final String hex = HashUtils.bytesToHex(block.hash); final TimerTask miningTask = new MiningTask(this,hex,block); timer.schedule(miningTask, 0); } /** What do you want to do now that you received a block, return the HashStatus **/ protected abstract Constants.Status checkTransaction(String dataFrom, Block block, byte[] signature, byte[] bytes); /** What do you want to do now that you received a valid block, return the HashStatus **/ protected abstract Constants.Status handleConfirmation(String dataFrom, Block block, byte[] signature, byte[] bytes); /** Mine the nonce sent in the transaction **/ protected abstract int mineHash(MiningTask timer, byte[] sha256, long numberOfZerosInPrefix); private void sendResend(int blockNumber) { final byte[] msg = getResendBlockMsg(blockNumber); final byte[] sig = signMsg(msg); final Data data = new Data(myName, runnableRecvTcp.getHost(), runnableRecvTcp.getPort(), EVERY_ONE, runnableRecvMulti.getHost(), runnableRecvMulti.getPort(), sig, msg); if (DEBUG) { final String string = new String(msg); final String hdr = string.substring(0, HEADER_LENGTH); System.out.println(myName+" Sending '"+hdr+"' msg"); } sendMultiQueue.add(data); } private void handleResend(String dataFrom, byte[] bytes, Data data) { // I shouldn't resend from my own resend msg (flood warning!) if (dataFrom.equals(myName)) return; final int blockNumber = parseResendBlockMsg(bytes); final int length = this.getBlockChain().getLength(); // I don't have the block, cannot help. if (length<=blockNumber) return; final Block toSend = this.getBlockChain().getBlock(blockNumber); sendConfirmation(toSend, data); } protected void sendRehash(Block block, Data data) { final byte[] msg = getRehashMsg(block); final byte[] sig = signMsg(msg); final Data dataToSend = new Data(myName, runnableRecvTcp.getHost(), runnableRecvTcp.getPort(), data.from, data.sourceAddr.getHostAddress(), data.sourcePort, sig, msg); if (DEBUG) { final String string = new String(msg); final String hdr = string.substring(0, HEADER_LENGTH); System.out.println(myName+" Sending '"+hdr+"' msg"); } sendTcpQueue.add(dataToSend); } private void handleRehash(String dataFrom, byte[] bytes, Data data) { final Block block = parseRehashMsg(bytes); // hash was out of sync with the blockchain, reprocess it. for (Transaction t : block.transactions) handleTransactionAck(dataFrom, t, data); } private void addTransactionToSend(Queued.State state, String to, Transaction transaction) { final Queued q = new Queued(state, transaction, null); Queue l = transactionsToSend.get(to); if (l == null) { l = new ConcurrentLinkedQueue(); transactionsToSend.put(to, l); } l.add(q); } private void processTransactionsToSend(String to) { Queue l = transactionsToSend.get(to); if (l==null || l.size()==0) return; while (l.size()>0) { final Queued q = l.poll(); if (q == null) return; final Host d = peers.get(to); // Do not use the data object in the queue object final byte[] msg = getTransactionMsg(q.transaction); final byte[] sig = signMsg(msg); final Data data = new Data(myName, runnableRecvTcp.getHost(), runnableRecvTcp.getPort(), to, d.ip, d.port, sig, msg); if (DEBUG) { final String string = new String(msg); final String hdr = string.substring(0, HEADER_LENGTH); System.out.println(myName+" Sending '"+hdr+"' msg"); } sendTcpQueue.add(data); } } private void addTransactionToRecv(Queued.State state, String dataFrom, Transaction transaction, Data data) { final Queued q = new Queued(state, transaction, data); Queue lc = transactionsToRecv.get(dataFrom); if (lc == null) { lc = new ConcurrentLinkedQueue(); transactionsToRecv.put(dataFrom, lc); } lc.add(q); } private void addBlockToRecv(Queued.State state, String dataFrom, Block block, Data data) { final Queued q = new Queued(state, block, data); Queue lc = transactionsToRecv.get(dataFrom); if (lc == null) { lc = new ConcurrentLinkedQueue(); transactionsToRecv.put(dataFrom, lc); } lc.add(q); } private void processTransactionsToRecv(String from) { Queue l = transactionsToRecv.get(from); if (l==null || l.size()==0) return; while (l.size()>0) { final Queued q = l.poll(); if (q == null) return; if (q.state == Queued.State.CONFIRM) handleConfirmation(from, q.block, q.data); else if (q.state == Queued.State.ACK) handleTransactionAck(from, q.transaction, q.data); else handleTransaction(from, q.transaction, q.data); } } private void addFutureBlockToRecv(Queued.State state, String dataFrom, Block block, Data data) { final Queued q = new Queued(state, block, data); Queue lc = futureTransactionsToRecv.get(dataFrom); if (lc == null) { lc = new ConcurrentLinkedQueue(); futureTransactionsToRecv.put(dataFrom, lc); } lc.add(q); } private void processFutureBlocksToRecv(String from) { Queue l = futureTransactionsToRecv.get(from); if (l==null || l.size()==0) return; while (l.size()>0) { final Queued q = l.poll(); if (q == null) return; if (q.state == Queued.State.FUTURE) handleConfirmation(from, q.block, q.data); } } private static final class Host { private final String ip; private final int port; private Host(String ip, int port) { this.ip = ip; this.port = port; } } private static final class Queued { private enum State {NEW, ACK, CONFIRM, FUTURE}; private final State state; private final Transaction transaction; private final Block block; private final Data data; private Queued(State state, Transaction transaction, Data data) { if (state == State.CONFIRM) throw new RuntimeException("Cannot have a CONFIRM without a Block"); this.state = state; this.transaction = transaction; this.block = null; this.data = data; } private Queued(State state, Block block, Data data) { if (state != State.CONFIRM && state != State.FUTURE) throw new RuntimeException("Cannot have a non-CONFIRM/FUTURE with a Block"); this.state = state; this.transaction = null; this.block = block; this.data = data; } } public static class MiningTask extends TimerTask { public volatile boolean run = true; private final Peer peer; private final String hex; private final Block block; /** unit testing constructor **/ public MiningTask() { this.peer = null; this.hex = null; this.block = null; } public MiningTask(Peer peer, String hex, Block block) { this.peer = peer; this.hex = hex; this.block = block; TimerTask oldTask = peer.timerMap.put(hex, this); if (oldTask != null) oldTask.cancel(); } @Override public void run() { peer.timerMap.remove(hex); // Let's mine this sucker. final int nonce = peer.mineHash(this, block.hash, block.numberOfZeros); if (nonce < 0) return; // Hash looks good to me and I have computed a nonce, let everyone know block.confirmed = true; block.nonce = nonce; // Make sure the block is still valid (hasn't been solved by someone else while we were processing) Constants.Status status = peer.getBlockChain().checkHash(block); if (status != Constants.Status.SUCCESS) { System.err.println(peer.myName +" NOT sending block, hash is no longer valid."); return; } peer.sendConfirmation(block); } @Override public boolean cancel() { peer.timerMap.remove(hex); run = false; return super.cancel(); } } public static final byte[] getWhoisMsg(String name) { final byte[] bName = name.getBytes(); final int nameLength = bName.length; final byte[] msg = new byte[HEADER_LENGTH + NAME_LENGTH + nameLength]; final ByteBuffer buffer = ByteBuffer.wrap(msg); buffer.put(WHOIS.getBytes()); buffer.putInt(nameLength); buffer.put(bName); return msg; } public static final String parseWhoisMsg(byte[] bytes) { final ByteBuffer buffer = ByteBuffer.wrap(bytes); final byte [] bMsgType = new byte[HEADER_LENGTH]; buffer.get(bMsgType); final int nLength = buffer.getInt(); final byte [] bName = new byte[nLength]; buffer.get(bName); return new String(bName); } public static final byte[] getIamMsg(byte[] publicKey) { final int publicKeyLength = publicKey.length; final byte[] msg = new byte[HEADER_LENGTH + KEY_LENGTH + publicKeyLength]; final ByteBuffer buffer = ByteBuffer.wrap(msg); buffer.put(IAM.getBytes()); buffer.putInt(publicKeyLength); buffer.put(publicKey); return msg; } public static final byte[] parseIamMsg(byte[] bytes) { final ByteBuffer buffer = ByteBuffer.wrap(bytes); final byte [] bMsgType = new byte[HEADER_LENGTH]; buffer.get(bMsgType); final int kLength = buffer.getInt(); final byte [] bKey = new byte[kLength]; buffer.get(bKey); return bKey; } public static final byte[] getTransactionMsg(Transaction transaction) { final byte[] msg = new byte[HEADER_LENGTH + transaction.getBufferLength()]; final ByteBuffer transactionBuffer = ByteBuffer.allocate(transaction.getBufferLength()); transaction.toBuffer(transactionBuffer); transactionBuffer.flip(); final ByteBuffer buffer = ByteBuffer.wrap(msg); buffer.put(TRANSACTION.getBytes()); buffer.put(transactionBuffer); return msg; } public static final Transaction parseTransactionMsg(byte[] bytes) { final ByteBuffer buffer = ByteBuffer.wrap(bytes); final byte [] bMsgType = new byte[HEADER_LENGTH]; buffer.get(bMsgType); final Transaction transaction = new Transaction(); transaction.fromBuffer(buffer); return transaction; } public static final byte[] getTransactionAckMsg(Transaction transaction) { final byte[] msg = new byte[HEADER_LENGTH + transaction.getBufferLength()]; final ByteBuffer transactionBuffer = ByteBuffer.allocate(transaction.getBufferLength()); transaction.toBuffer(transactionBuffer); transactionBuffer.flip(); final ByteBuffer buffer = ByteBuffer.wrap(msg); buffer.put(TRANSACTION_ACK.getBytes()); buffer.put(transactionBuffer); return msg; } public static final Transaction parseTransactionAckMsg(byte[] bytes) { final ByteBuffer buffer = ByteBuffer.wrap(bytes); final byte [] bMsgType = new byte[HEADER_LENGTH]; buffer.get(bMsgType); final Transaction block = new Transaction(); block.fromBuffer(buffer); return block; } public static final byte[] getBlockMsg(Block block) { final byte[] msg = new byte[HEADER_LENGTH + block.getBufferLength()]; final ByteBuffer blockBuffer = ByteBuffer.allocate(block.getBufferLength()); block.toBuffer(blockBuffer); blockBuffer.flip(); final ByteBuffer buffer = ByteBuffer.wrap(msg); buffer.put(BLOCK.getBytes()); buffer.put(blockBuffer); return msg; } public static final Block parseBlockMsg(byte[] bytes) { final ByteBuffer buffer = ByteBuffer.wrap(bytes); final byte [] bMsgType = new byte[HEADER_LENGTH]; buffer.get(bMsgType); final Block block = new Block(); block.fromBuffer(buffer); return block; } public static final byte[] getConfirmationMsg(Block block) { final byte[] msg = new byte[HEADER_LENGTH + block.getBufferLength()]; final ByteBuffer blockBuffer = ByteBuffer.allocate(block.getBufferLength()); block.toBuffer(blockBuffer); blockBuffer.flip(); final ByteBuffer buffer = ByteBuffer.wrap(msg); buffer.put(CONFIRMATION.getBytes()); buffer.put(blockBuffer); return msg; } public static final Block parseConfirmationMsg(byte[] bytes) { final ByteBuffer buffer = ByteBuffer.wrap(bytes); final byte [] bMsgType = new byte[HEADER_LENGTH]; buffer.get(bMsgType); final Block block = new Block(); block.fromBuffer(buffer); return block; } public static final byte[] getResendBlockMsg(int blockNumber) { final byte[] msg = new byte[HEADER_LENGTH + 4]; final ByteBuffer buffer = ByteBuffer.wrap(msg); buffer.put(RESEND.getBytes()); buffer.putInt(blockNumber); return msg; } public static final int parseResendBlockMsg(byte[] bytes) { final ByteBuffer buffer = ByteBuffer.wrap(bytes); final byte [] bMsgType = new byte[HEADER_LENGTH]; buffer.get(bMsgType); final int blockNumber = buffer.getInt(); return blockNumber; } public static final byte[] getRehashMsg(Block block) { final byte[] msg = new byte[HEADER_LENGTH + block.getBufferLength()]; final ByteBuffer blockBuffer = ByteBuffer.allocate(block.getBufferLength()); block.toBuffer(blockBuffer); blockBuffer.flip(); final ByteBuffer buffer = ByteBuffer.wrap(msg); buffer.put(REHASH.getBytes()); buffer.put(blockBuffer); return msg; } public static final Block parseRehashMsg(byte[] bytes) { final ByteBuffer buffer = ByteBuffer.wrap(bytes); final byte [] bMsgType = new byte[HEADER_LENGTH]; buffer.get(bMsgType); final Block block = new Block(); block.fromBuffer(buffer); return block; } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("name=").append(myName); return builder.toString(); } } ================================================ FILE: src/com/jwetherell/bitcoin/ProofOfWork.java ================================================ package com.jwetherell.bitcoin; import java.nio.ByteBuffer; import com.jwetherell.bitcoin.Peer.MiningTask; import com.jwetherell.bitcoin.common.HashUtils; public class ProofOfWork { private static final byte getBit(int ID, int position) { return (byte) ((ID >> position) & (byte)1); } /** * Given the sha256 hash, what number can we append to the hash which'll create a hash * which has a leading 'numberOfZerosInPrefix' number of zeros. * * e.g. * if numberOfZerosInPrefix == 3 * input = 7982970534e089b839957b7e174725ce1878731ed6d700766e59cb16f1c25e27 * solution = 0000fedb24e31adb71559d43a9a3ddc8be7606fa6befe3260b3eee2cf2aeb642 * output = 21080 * **/ public static final int solve(MiningTask task, byte[] sha256, long numberOfZerosInPrefix) { final int length = sha256.length; final ByteBuffer buffer = ByteBuffer.allocate(length+4); buffer.put(sha256, 0, length); int x = 0; while (task.run && x < Integer.MAX_VALUE) { // append x buffer.putInt(length, x); // calculate new hash final byte[] result = HashUtils.calculateSha256(buffer.array()); // wrap in buffer for easier processing final ByteBuffer bb = ByteBuffer.wrap(result); boolean wrong = false; boolean done = false; int numOfZeros = 0; for (int i=0; i TRANSACTION_COMPARATOR = new Comparator() { @Override public int compare(Transaction o1, Transaction o2) { // Higher value is processed quicker if (o1.value > o2.value) return -1; if (o2.value > o1.value) return 1; return 0; } }; // Keep track of everyone's name -> public key private final Map publicKeys = new ConcurrentHashMap(); // My BLockChain private final Blockchain blockchain; // Ranks transactions by their value to me private final PriorityQueue transactionQueue = new PriorityQueue(10, TRANSACTION_COMPARATOR); public Wallet(String name) { super(name); // add the initial pub key this.publicKeys.put(Blockchain.NO_ONE, ByteBuffer.wrap(Blockchain.NO_ONE_PUB_KEY)); this.publicKeys.put(myName, ByteBuffer.wrap(bPublicKey)); // initialize the blockchain this.blockchain = new Blockchain(name); } /** * {@inheritDoc} */ @Override protected byte[] getPublicKey() { return bPublicKey; } /** * {@inheritDoc} */ @Override public Blockchain getBlockChain() { return blockchain; } public long getBalance() { return blockchain.getBalance(myName); } /** * {@inheritDoc} */ @Override protected void newPublicKey(String name, byte[] publicKey) { publicKeys.put(name, ByteBuffer.wrap(publicKey)); } /** * {@inheritDoc} * * synchronized to protect enc from changing while processing */ @Override protected synchronized byte[] signMsg(byte[] bytes) { return KeyUtils.signMsg(enc, bytes); } /** * {@inheritDoc} * * synchronized to protect keyfactory/dec from changing while processing */ @Override protected synchronized boolean verifyMsg(byte[] publicKey, byte[] signature, byte[] bytes) { return KeyUtils.verifyMsg(keyFactory, dec, publicKey, signature, bytes); } // synchronized to protect the blockchain from changing while processing public synchronized void sendCoin(String name, int value) { // Iterate through the our unused transactions to see if we have enough coins final List inputList = new ArrayList(); int coins = 0; for (Transaction t : this.blockchain.getUnused()) { if (!(t.to.equals(myName))) continue; coins += t.value; inputList.add(t); if (coins >= value) break; } if (coins < value) { System.err.println(myName+" Sorry, you do not have enough coins."); return; } // Convert the list into an array final Transaction[] inputs = new Transaction[inputList.size()]; for (int i=0; i outputList = new ArrayList(); if (coins > value) { // I get some change back, so add myself as an output final int myCoins = coins - value; final String msg = "I get some change back."; final Transaction t = Transaction.newSignedTransaction(enc, myName, myName, msg, myCoins, inputs, EMPTY); outputList.add(t); } // Create the transaction for the recipient final String msg = "Here are some coins for you"; final Transaction t = Transaction.newSignedTransaction(enc, myName, name, msg, value, inputs, EMPTY); outputList.add(t); // Convert the list into an array final Transaction[] outputs = new Transaction[outputList.size()]; for (int i=0; i= NUMBER_OF_TRANSACTIONS_IN_BLOCK) { Transaction[] transactions = new Transaction[NUMBER_OF_TRANSACTIONS_IN_BLOCK]; for (int i=0; i= 0) status = "SOLVED"; System.err.println(myName+" mineHash() "+status+". nonce="+nonce+"\n"+"sha256=["+HashUtils.bytesToHex(sha256)+"]\n"); } return nonce; } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append(super.toString()).append(" blockChain={").append(blockchain.toString()).append("}"); return builder.toString(); } } ================================================ FILE: src/com/jwetherell/bitcoin/common/Constants.java ================================================ package com.jwetherell.bitcoin.common; public abstract class Constants { public static enum Status { OWN_TRANSACTION, NO_PUBLIC_KEY, INCORRECT_NONCE, FUTURE_BLOCK, BAD_HASH, BAD_SIGNATURE, BAD_INPUTS, DUPLICATE, SUCCESS, UNKNOWN }; } ================================================ FILE: src/com/jwetherell/bitcoin/common/HashUtils.java ================================================ package com.jwetherell.bitcoin.common; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; public abstract class HashUtils { public static final byte[] calculateSha256(String text) { byte[] hash2; try { hash2 = calculateSha256(text.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } return hash2; } public static final byte[] calculateSha256(byte[] utf8Bytes) { byte[] hash2; try { final MessageDigest digest = MessageDigest.getInstance("SHA-256"); final byte[] hash1 = digest.digest(utf8Bytes); hash2 = digest.digest(hash1); } catch (Exception e) { throw new RuntimeException(e); } return hash2; } public static final String bytesToHex(byte[] bytes) { final StringBuilder result = new StringBuilder(); for (byte byt : bytes) result.append(Integer.toString((byt & 0xff) + 0x100, 16).substring(1)); return result.toString(); } } ================================================ FILE: src/com/jwetherell/bitcoin/common/KeyUtils.java ================================================ package com.jwetherell.bitcoin.common; import java.security.KeyFactory; import java.security.PublicKey; import java.security.Signature; import java.security.spec.X509EncodedKeySpec; public abstract class KeyUtils { public static final byte[] signMsg(Signature enc, byte[] bytes) { byte[] signed = null; try { enc.update(bytes); signed = enc.sign(); } catch (Exception e) { System.err.println("Could not encode msg. "+e); } return signed; } public static final boolean verifyMsg(KeyFactory keyFactory, Signature dec, byte[] publicKey, byte[] signature, byte[] bytes) { boolean verified = false; try { PublicKey key = keyFactory.generatePublic(new X509EncodedKeySpec(publicKey)); dec.initVerify(key); dec.update(bytes); verified = dec.verify(signature); } catch (Exception e) { System.err.println("Could not decode msg. "+e); } return verified; } } ================================================ FILE: src/com/jwetherell/bitcoin/data_model/Block.java ================================================ package com.jwetherell.bitcoin.data_model; import java.nio.ByteBuffer; import java.util.Arrays; import com.jwetherell.bitcoin.common.HashUtils; public class Block { private static final int FROM_LENGTH = 4; private static final int BOOLEAN_LENGTH = 2; private static final int NUM_OF_ZEROS_LENGTH = 4; private static final int NONCE_LENGTH = 4; private static final int BLOCK_LENGTH = 4; private static final int LENGTH_LENGTH = 4; public String from; public boolean confirmed = false; public int numberOfZeros; public int nonce; public int blockLength; public Transaction[] transactions; public byte[] prev; public byte[] hash; public Block() { } public Block(String from, byte[] prevHash, byte[] hash, Transaction[] transactions, int blockLength) { this.from = from; this.prev = prevHash; this.hash = hash; this.transactions = transactions; this.blockLength = blockLength; } public int getBufferLength() { int transactionsLength = 0; for (Transaction t : transactions) transactionsLength += LENGTH_LENGTH + t.getBufferLength(); return FROM_LENGTH + from.length() + BOOLEAN_LENGTH + NUM_OF_ZEROS_LENGTH + NONCE_LENGTH + BLOCK_LENGTH + LENGTH_LENGTH + prev.length + LENGTH_LENGTH + hash.length + LENGTH_LENGTH + transactionsLength; } public void toBuffer(ByteBuffer buffer) { final byte[] fBytes = from.getBytes(); buffer.putInt(fBytes.length); buffer.put(fBytes); buffer.putChar(getBoolean(confirmed)); buffer.putInt(numberOfZeros); buffer.putInt(nonce); buffer.putInt(blockLength); buffer.putInt(prev.length); buffer.put(prev); buffer.putInt(hash.length); buffer.put(hash); buffer.putInt(transactions.length); for (Transaction t : transactions) { buffer.putInt(t.getBufferLength()); t.toBuffer(buffer); } } public void fromBuffer(ByteBuffer buffer) { final int fLength = buffer.getInt(); final byte[] fBytes = new byte[fLength]; buffer.get(fBytes, 0, fLength); from = new String(fBytes); confirmed = parseBoolean(buffer.getChar()); numberOfZeros = buffer.getInt(); nonce = buffer.getInt(); blockLength = buffer.getInt(); { // previous hash final int length = buffer.getInt(); prev = new byte[length]; buffer.get(prev); } { // next hash final int length = buffer.getInt(); hash = new byte[length]; buffer.get(hash); } int tLength = buffer.getInt(); transactions = new Transaction[tLength]; for (int i=0; i < tLength; i++) { int length = buffer.getInt(); final byte[] bytes = new byte[length]; buffer.get(bytes); final ByteBuffer bb = ByteBuffer.wrap(bytes); final Transaction t = new Transaction(); t.fromBuffer(bb); transactions[i] = t; } } private static final char getBoolean(boolean bool) { return (bool?'T':'F'); } private static final boolean parseBoolean(char bool) { return (bool=='T'?true:false); } /** * {@inheritDoc} */ @Override public int hashCode() { int hashCode = 0; hashCode += from.length(); if (confirmed) hashCode += 1; hashCode += nonce; hashCode += blockLength; hashCode += numberOfZeros; hashCode += transactions.length; for (Transaction t : transactions) hashCode += t.hashCode(); for (byte b : prev) hashCode += b; for (byte b : hash) hashCode += b; return 31 * hashCode; } /** * {@inheritDoc} */ @Override public boolean equals(Object o) { if (!(o instanceof Block)) return false; Block c = (Block) o; if (!(c.from.equals(from))) return false; if (confirmed != c.confirmed) return false; if (nonce != c.nonce) return false; if (blockLength != c.blockLength) return false; if (numberOfZeros != c.numberOfZeros) return false; if (c.transactions.length != this.transactions.length) return false; { // compare transactions for (int i=0; i getQueue(); public boolean isReady(); public String getHost(); public int getPort(); } ================================================ FILE: src/com/jwetherell/bitcoin/interfaces/Sender.java ================================================ package com.jwetherell.bitcoin.interfaces; import java.util.Queue; import com.jwetherell.bitcoin.data_model.Data; public interface Sender { public Queue getQueue(); public boolean isReady(); } ================================================ FILE: src/com/jwetherell/bitcoin/networking/Multicast.java ================================================ package com.jwetherell.bitcoin.networking; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import com.jwetherell.bitcoin.data_model.Data; import com.jwetherell.bitcoin.interfaces.MessageListener; import com.jwetherell.bitcoin.interfaces.Receiver; import com.jwetherell.bitcoin.interfaces.Sender; public class Multicast { private static final boolean DEBUG = Boolean.getBoolean("debug_all"); public static final int PORT = 5000; public static final String GROUP = "225.4.5.6"; public static MulticastSocket createReceiver() throws IOException { // Create the socket and bind it to port 'port'. final MulticastSocket s = new MulticastSocket(PORT); // join the multicast group s.joinGroup(InetAddress.getByName(GROUP)); // Now the socket is set up and we are ready to receive packets return s; } public static void destoryReceiver(MulticastSocket s) throws UnknownHostException, IOException { if (s == null) return; // Leave the multicast group and close the socket s.leaveGroup(InetAddress.getByName(GROUP)); s.close(); } /** * Blocking call */ public static boolean recvData(MulticastSocket s, byte[] buffer) throws IOException { s.setSoTimeout(100); // Create a DatagramPacket and do a receive final DatagramPacket pack = new DatagramPacket(buffer, buffer.length); try { s.receive(pack); } catch (SocketTimeoutException e) { return false; } // We have finished receiving data return true; } public static MulticastSocket createSender() throws IOException { // Create the socket but we don't bind it as we are only going to send data final MulticastSocket s = new MulticastSocket(); // Note that we don't have to join the multicast group if we are only // sending data and not receiving return s; } public static void destroySender(MulticastSocket s) throws IOException { if (s == null) return; // When we have finished sending data close the socket s.close(); } public static void sendData(MulticastSocket s, int ourTTL, byte[] buffer) throws IOException { // Create a DatagramPacket final DatagramPacket pack = new DatagramPacket(buffer, buffer.length, InetAddress.getByName(GROUP), PORT); // Get the current TTL, set our TTL, do a send, reset the TTL final int ttl = s.getTimeToLive(); s.setTimeToLive(ourTTL); s.send(pack); s.setTimeToLive(ttl); } public static final class Peer { private static final int BUFFER_SIZE = 10*1024; public static final class RunnableRecv implements Runnable, Receiver { public static volatile boolean run = true; private final ConcurrentLinkedQueue toRecv = new ConcurrentLinkedQueue(); private final MessageListener listener; private volatile boolean isReady = false; public RunnableRecv(MessageListener listener) { run = true; this.listener = listener; } /** * {@inheritDoc} */ @Override public Queue getQueue() { return toRecv; } /** * {@inheritDoc} */ @Override public boolean isReady() { return isReady; } /** * {@inheritDoc} */ @Override public String getHost() { return GROUP; } /** * {@inheritDoc} */ @Override public int getPort() { return PORT; } /** * {@inheritDoc} */ @Override public void run() { MulticastSocket s = null; try { if (DEBUG) System.out.println("Creating receiver"); s = Multicast.createReceiver(); final byte[] array = new byte[BUFFER_SIZE]; final ByteBuffer bb = ByteBuffer.wrap(array); isReady = true; while (run) { bb.clear(); final boolean p = Multicast.recvData(s, array); if (!p) { Thread.yield(); continue; } final Data data = new Data(); data.fromBuffer(bb); if (DEBUG) System.out.println("Server received '"+new String(data.message.array())+"' from "+data.sourceAddr.getHostAddress()+":"+data.sourcePort); toRecv.add(data); listener.onMessage(this); Thread.yield(); } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { Multicast.destoryReceiver(s); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } }; public static final class RunnableSend implements Runnable, Sender { private static final int ttl = 1; public static volatile boolean run = true; public final ConcurrentLinkedQueue toSend = new ConcurrentLinkedQueue(); private volatile boolean isReady = false; public RunnableSend() { run = true; } /** * {@inheritDoc} */ @Override public Queue getQueue() { return toSend; } /** * {@inheritDoc} */ @Override public boolean isReady() { return isReady; } /** * {@inheritDoc} */ @Override public void run() { MulticastSocket s = null; try { if (DEBUG) System.out.println("Creating sender"); s = Multicast.createSender(); final byte[] buffer = new byte[BUFFER_SIZE]; final ByteBuffer bb = ByteBuffer.wrap(buffer); isReady = true; while (run) { if (DEBUG && toSend.size()>1) System.out.println("Client toSend size="+toSend.size()); final Data d = toSend.poll(); if (d != null) { bb.clear(); d.toBuffer(bb); bb.flip(); if (DEBUG) System.out.println("Client ("+d.sourceAddr.getHostAddress()+":"+d.sourcePort+") sending '"+new String(d.message.array())+"'"); Multicast.sendData(s, ttl, buffer); } Thread.yield(); } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { Multicast.destroySender(s); } catch (IOException e) { e.printStackTrace(); } } } }; } } ================================================ FILE: src/com/jwetherell/bitcoin/networking/TCP.java ================================================ package com.jwetherell.bitcoin.networking; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import com.jwetherell.bitcoin.data_model.Data; import com.jwetherell.bitcoin.interfaces.MessageListener; import com.jwetherell.bitcoin.interfaces.Receiver; import com.jwetherell.bitcoin.interfaces.Sender; public class TCP { private static final boolean DEBUG = Boolean.getBoolean("debug_all"); public static final String LOCAL = "127.0.0.1"; public static int port = 2221; public static ServerSocket createServer(int port) throws IOException { final ServerSocket serverSocket = new ServerSocket(port); return serverSocket; } public static void destoryServer(ServerSocket s) throws IOException { if (s != null) s.close(); } public static Socket createClient(String host, int port) throws IOException { final Socket outgoingSocket = new Socket(host, port); return outgoingSocket; } public static void destoryClient(Socket s) throws IOException { if (s != null) s.close(); } public static void sendData(Socket socket, byte[] buffer) throws IOException { final OutputStream out = socket.getOutputStream(); final DataOutputStream dos = new DataOutputStream(out); dos.write(buffer); dos.flush(); dos.close(); out.flush(); out.close(); } /** * Blocking call */ public static boolean recvData(ServerSocket serverSocket, byte[] buffer) throws IOException { serverSocket.setSoTimeout(10); Socket incomingSocket = null; try { incomingSocket = serverSocket.accept(); } catch (SocketTimeoutException e) { return false; } final InputStream input = incomingSocket.getInputStream(); input.read(buffer); input.close(); return true; } public static final class Peer { private static final int BUFFER_SIZE = 10*1024; private Peer() { } public static final class RunnableRecv implements Runnable, Receiver { public static volatile boolean run = true; private final ConcurrentLinkedQueue toRecv = new ConcurrentLinkedQueue(); private final int port; private final MessageListener listener; private volatile boolean isReady = false; public RunnableRecv(MessageListener listener) { run = true; this.port = TCP.port++; this.listener = listener; } /** * {@inheritDoc} */ @Override public Queue getQueue() { return toRecv; } /** * {@inheritDoc} */ @Override public boolean isReady() { return isReady; } /** * {@inheritDoc} */ @Override public String getHost() { return LOCAL; } /** * {@inheritDoc} */ @Override public int getPort() { return port; } /** * {@inheritDoc} */ @Override public void run() { ServerSocket s = null; try { if (DEBUG) System.out.println("Creating server. port="+port); s = TCP.createServer(port); final byte[] array = new byte[BUFFER_SIZE]; final ByteBuffer bb = ByteBuffer.wrap(array); isReady = true; while (run) { bb.clear(); final boolean p = TCP.recvData(s,array); if (!p) { Thread.yield(); continue; } final Data data = new Data(); data.fromBuffer(bb); if (DEBUG) System.out.println("Server ("+getHost()+":"+getPort()+") received '"+new String(data.message.array())+"' from "+data.sourceAddr.getHostAddress()+":"+data.sourcePort); toRecv.add(data); listener.onMessage(this); Thread.yield(); } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { TCP.destoryServer(s); } catch (IOException e) { e.printStackTrace(); } } } }; public static final class RunnableSend implements Runnable, Sender { public static volatile boolean run = true; private final ConcurrentLinkedQueue toSend = new ConcurrentLinkedQueue(); private volatile boolean isReady = false; public RunnableSend() { run = true; } /** * {@inheritDoc} */ @Override public Queue getQueue() { return toSend; } /** * {@inheritDoc} */ @Override public boolean isReady() { return isReady; } /** * {@inheritDoc} */ @Override public void run() { Socket s = null; try { final byte[] buffer = new byte[BUFFER_SIZE]; final ByteBuffer bb = ByteBuffer.wrap(buffer); isReady = true; while (run) { if (DEBUG && toSend.size()>1) System.out.println("Client toSend size="+toSend.size()); final Data d = toSend.poll(); if (d != null) { bb.clear(); d.toBuffer(bb); bb.flip(); if (DEBUG) System.out.println("Client ("+d.sourceAddr.getHostAddress()+":"+d.sourcePort+") sending '"+new String(d.message.array())+"'"); s = TCP.createClient(d.destAddr.getHostAddress(), d.destPort); TCP.sendData(s, buffer); } Thread.yield(); } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { TCP.destoryClient(s); } catch (IOException e) { e.printStackTrace(); } } } }; } } ================================================ FILE: src/com/jwetherell/bitcoin/networking/UDP.java ================================================ package com.jwetherell.bitcoin.networking; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import com.jwetherell.bitcoin.data_model.Data; import com.jwetherell.bitcoin.interfaces.MessageListener; import com.jwetherell.bitcoin.interfaces.Receiver; import com.jwetherell.bitcoin.interfaces.Sender; public class UDP { private static final boolean DEBUG = Boolean.getBoolean("debug_all"); public static final String LOCAL = "127.0.0.1"; public static int port = 1111; public static DatagramSocket createServer(int port) throws SocketException { final DatagramSocket serverSocket = new DatagramSocket(port); return serverSocket; } public static void destoryServer(DatagramSocket s) { if (s != null) s.close(); } public static DatagramSocket createClient() throws SocketException { final DatagramSocket clientSocket = new DatagramSocket(); return clientSocket; } public static void destoryClient(DatagramSocket s) { if (s != null) s.close(); } public static void sendData(DatagramSocket socket, InetAddress IPAddress, int port, byte[] buffer) throws IOException { final DatagramPacket sendPacket = new DatagramPacket(buffer, buffer.length, IPAddress, port); socket.send(sendPacket); } /** * Blocking call */ public static boolean recvData(DatagramSocket socket, byte[] buffer) throws IOException { socket.setSoTimeout(100); final DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length); try { socket.receive(receivePacket); } catch (SocketTimeoutException e) { return false; } return true; } public static final class Peer { private static final int BUFFER_SIZE = 10*1024; private Peer() { } public static final class RunnableRecv implements Runnable, Receiver { public static volatile boolean run = true; private final ConcurrentLinkedQueue toRecv = new ConcurrentLinkedQueue(); private final int port; private final MessageListener listener; private volatile boolean isReady = false; public RunnableRecv(MessageListener listener) { run = true; this.port = UDP.port++; this.listener = listener; } /** * {@inheritDoc} */ @Override public Queue getQueue() { return toRecv; } /** * {@inheritDoc} */ @Override public boolean isReady() { return isReady; } /** * {@inheritDoc} */ @Override public String getHost() { return LOCAL; } /** * {@inheritDoc} */ @Override public int getPort() { return port; } /** * {@inheritDoc} */ @Override public void run() { DatagramSocket s = null; try { if (DEBUG) System.out.println("Creating server. port="+port); s = UDP.createServer(port); final byte[] array = new byte[BUFFER_SIZE]; final ByteBuffer bb = ByteBuffer.wrap(array); isReady = true; while (run) { bb.clear(); final boolean p = UDP.recvData(s,bb.array()); if (!p) { Thread.yield(); continue; } final Data data = new Data(); data.fromBuffer(bb); if (DEBUG) System.out.println("Server ("+getHost()+":"+getPort()+") received '"+new String(data.message.array())+"' from "+data.sourceAddr.getHostAddress()+":"+data.sourcePort); toRecv.add(data); listener.onMessage(this); Thread.yield(); } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { UDP.destoryServer(s); } } }; public static final class RunnableSend implements Runnable, Sender { public static volatile boolean run = true; private final ConcurrentLinkedQueue toSend = new ConcurrentLinkedQueue(); private volatile boolean isReady = false; public RunnableSend() { run = true; } /** * {@inheritDoc} */ @Override public Queue getQueue() { return toSend; } /** * {@inheritDoc} */ @Override public boolean isReady() { return isReady; } /** * {@inheritDoc} */ @Override public void run() { DatagramSocket s = null; try { if (DEBUG) System.out.println("Creating client"); s = UDP.createClient(); final byte[] buffer = new byte[BUFFER_SIZE]; final ByteBuffer bb = ByteBuffer.wrap(buffer); isReady = true; while (run) { if (DEBUG && toSend.size()>1) System.out.println("Client toSend size="+toSend.size()); final Data d = toSend.poll(); if (d != null) { bb.clear(); d.toBuffer(bb); bb.flip(); if (DEBUG) System.out.println("Client ("+d.sourceAddr.getHostAddress()+":"+d.sourcePort+") sending '"+new String(d.message.array())+"'"); UDP.sendData(s, d.destAddr, d.destPort, buffer); } Thread.yield(); } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (s != null) UDP.destoryClient(s); } } }; } } ================================================ FILE: src/com/jwetherell/bitcoin/test/AllTest.java ================================================ package com.jwetherell.bitcoin.test; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; //@formatter:off @RunWith(Suite.class) @SuiteClasses( { com.jwetherell.bitcoin.test.ProofOfWorkTest.class, com.jwetherell.bitcoin.test.BlockChainTest.class, com.jwetherell.bitcoin.test.EncodeDecodeTest.class, com.jwetherell.bitcoin.test.TransactionTest.class, com.jwetherell.bitcoin.test.BlockTest.class, com.jwetherell.bitcoin.test.DataTest.class, com.jwetherell.bitcoin.test.UDPTest.class, com.jwetherell.bitcoin.test.TCPTest.class, com.jwetherell.bitcoin.test.MulticastTest.class, com.jwetherell.bitcoin.test.PeerTest.class, com.jwetherell.bitcoin.test.WalletTest.class, } ) //@formatter:on public class AllTest { // Ignore } ================================================ FILE: src/com/jwetherell/bitcoin/test/BlockChainTest.java ================================================ package com.jwetherell.bitcoin.test; import java.nio.ByteBuffer; import java.util.Arrays; import org.junit.Assert; import org.junit.Test; import com.jwetherell.bitcoin.Blockchain; import com.jwetherell.bitcoin.data_model.Transaction; public class BlockChainTest { private static final Transaction[] EMPTY = new Transaction[0]; private static final byte[] SIGNATURE = "sig".getBytes(); @Test public void test() { final byte[] hash1; { final byte[] hash = "This is a hash".getBytes(); final Transaction block = new Transaction("me","you","msg",7,SIGNATURE,EMPTY,EMPTY); final ByteBuffer buffer = ByteBuffer.allocate(block.getBufferLength()); block.toBuffer(buffer); buffer.flip(); final byte[] bytes = buffer.array(); hash1 = Blockchain.getNextHash(hash, bytes); } final byte[] hash2; { byte[] hash = "This is a hash".getBytes(); final Transaction block = new Transaction("me","you","msg",7,SIGNATURE,EMPTY,EMPTY); final ByteBuffer buffer = ByteBuffer.allocate(block.getBufferLength()); block.toBuffer(buffer); buffer.flip(); final byte[] bytes = buffer.array(); hash2 = Blockchain.getNextHash(hash, bytes); } Assert.assertTrue(Arrays.equals(hash1, hash2)); } } ================================================ FILE: src/com/jwetherell/bitcoin/test/BlockTest.java ================================================ package com.jwetherell.bitcoin.test; import java.nio.ByteBuffer; import org.junit.Assert; import org.junit.Test; import com.jwetherell.bitcoin.data_model.Transaction; import com.jwetherell.bitcoin.data_model.Block; public class BlockTest { private static final Transaction[] EMPTY = new Transaction[0]; private static final byte[] SIGNATURE = "sig".getBytes(); @Test public void testSerialization() { final String f = "me"; final String t = "you"; final String m = "Here is a coin for you!"; final Transaction trans = new Transaction(f, t, m, 10, SIGNATURE, EMPTY, EMPTY); final Transaction[] transactions = new Transaction[]{ trans }; byte[] prev = "I am a hash!".getBytes(); byte[] hash = "I am also a hash!".getBytes(); final Block block = new Block(f,prev,hash,transactions,1); final ByteBuffer buffer = ByteBuffer.allocate(block.getBufferLength()); block.toBuffer(buffer); buffer.flip(); final Block block2 = new Block(); block2.fromBuffer(buffer); Assert.assertTrue(block.equals(block2)); } } ================================================ FILE: src/com/jwetherell/bitcoin/test/DataTest.java ================================================ package com.jwetherell.bitcoin.test; import java.nio.ByteBuffer; import org.junit.Assert; import org.junit.Test; import com.jwetherell.bitcoin.data_model.Data; public class DataTest { @Test public void testSerialization() { final String from = "me"; final String sHost = "127.0.0.1"; final int sPort = 1024; final String to = "you"; final String dHost = "localhost"; final int dPort = 1025; final byte[] sig = "sig".getBytes(); final byte[] msg = "This is a message".getBytes(); final Data d1 = new Data(from,sHost,sPort,to,dHost,dPort,sig,msg); final ByteBuffer buffer = ByteBuffer.allocate(d1.getBufferLength()); d1.toBuffer(buffer); buffer.flip(); final Data d2 = new Data(); d2.fromBuffer(buffer); Assert.assertTrue(d1.equals(d2)); } } ================================================ FILE: src/com/jwetherell/bitcoin/test/EncodeDecodeTest.java ================================================ package com.jwetherell.bitcoin.test; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.security.spec.X509EncodedKeySpec; import org.junit.Assert; import org.junit.Test; public class EncodeDecodeTest { @Test public void test1() { byte[] data = "hello.".getBytes(); /* Test generating and verifying a DSA signature */ try { /* generate a key pair */ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA"); keyGen.initialize(1024, new SecureRandom()); final KeyPair pair = keyGen.generateKeyPair(); /* create a Signature object to use for signing and verifying */ final Signature dsa = Signature.getInstance("SHA/DSA"); /* initialize the Signature object for signing */ final PrivateKey priv = pair.getPrivate(); dsa.initSign(priv); /* Update and sign the data */ dsa.update(data); /* Now that all the data to be signed has been read in, sign it */ final byte[] sig = dsa.sign(); /* Verify the signature */ /* Initialize the Signature object for verification */ final PublicKey pub = pair.getPublic(); dsa.initVerify(pub); /* Update and verify the data */ dsa.update(data); final boolean verified = dsa.verify(sig); Assert.assertTrue(verified); } catch (Exception e) { System.err.println("Caught exception " + e.toString()); } } @Test public void test2() { byte[] data = "hello.".getBytes(); /* Test generating and verifying a DSA signature */ try { /* generate a key pair */ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA"); keyGen.initialize(1024, new SecureRandom()); final KeyPair pair = keyGen.generateKeyPair(); /* create a Signature object to use * for signing and verifying */ final Signature dsa = Signature.getInstance("SHA/DSA"); /* initialize the Signature object for signing */ final PrivateKey priv = pair.getPrivate(); dsa.initSign(priv); /* Update and sign the data */ dsa.update(data); /* Now that all the data to be signed has been read in, sign it */ final byte[] sig = dsa.sign(); /* Verify the signature */ /* Initialize the Signature object for verification */ final PublicKey pub = pair.getPublic(); /* Encode the public key into a byte array */ final byte[] encoded = pub.getEncoded(); /* Get the public key from the encoded byte array */ final PublicKey fromEncoded = KeyFactory.getInstance("DSA", "SUN").generatePublic(new X509EncodedKeySpec(encoded)); dsa.initVerify(fromEncoded); /* Update and verify the data */ dsa.update(data); final boolean verified = dsa.verify(sig); Assert.assertTrue(verified); } catch (Exception e) { System.err.println("Caught exception " + e.toString()); } } } ================================================ FILE: src/com/jwetherell/bitcoin/test/MulticastTest.java ================================================ package com.jwetherell.bitcoin.test; import org.junit.Assert; import org.junit.Test; import com.jwetherell.bitcoin.data_model.Data; import com.jwetherell.bitcoin.interfaces.MessageListener; import com.jwetherell.bitcoin.interfaces.Receiver; import com.jwetherell.bitcoin.networking.Multicast; public class MulticastTest { private static final boolean DEBUG = Boolean.getBoolean("debug"); @Test(timeout=5000) public void test() throws InterruptedException { final String from = "me"; final String to = "you"; final byte[] sig = "sig".getBytes(); final byte[] toSend = "Hello world.".getBytes(); final MessageListener listener = new MessageListener() { /** * {@inheritDoc} */ @Override public void onMessage(Receiver recv) { Data d = recv.getQueue().poll(); while (d != null) { final byte[] data = d.message.array(); if (DEBUG) System.out.println("Listener received '"+new String(data)+"'"); Assert.assertTrue(isEquals(toSend,data,toSend.length)); d = recv.getQueue().poll(); } Multicast.Peer.RunnableRecv.run = false; Multicast.Peer.RunnableSend.run = false; } }; // Start both the sender and receiver final Multicast.Peer.RunnableRecv recv = new Multicast.Peer.RunnableRecv(listener); final Thread r = new Thread(recv,"recv"); r.start(); final Multicast.Peer.RunnableSend send = new Multicast.Peer.RunnableSend(); final Thread s = new Thread(send,"send"); s.start(); // Wait for everyone to initialize while (recv.isReady()==false || send.isReady()==false) { Thread.yield(); } final Data data = new Data(from, recv.getHost(), recv.getPort(), to, recv.getHost(), recv.getPort(), sig, toSend); send.getQueue().add(data); // Wait for threads to finish r.join(); s.join(); } private static final boolean isEquals(byte[] a, byte[] b, int length) { for (int i=0; i