├── package.json ├── src ├── Wallet.js ├── Config.js ├── Blockchain.js ├── init_blockchain.js ├── Miner.js ├── Transaction.js └── Block.js ├── LICENSE └── README.md /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-blockchain", 3 | "version": "0.0.1", 4 | "description": "Proof of concept javascript blockchain with wallet and miner", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "mkdir dist; ./node_modules/.bin/browserify src/init_blockchain.js -r ./node_modules/ezcrypto/lib/hmac.js:./lib/hmac -r ./node_modules/ezcrypto/lib/sha1.js:./lib/sha1 -o dist/init_blockchain.js -t [ babelify --presets es2015 ] -t [ envify ]", 8 | "init_blockchain": "node --turbo ./dist/init_blockchain.js" 9 | }, 10 | "repository": "git@github.com:allbinmani/js-blockchain.git", 11 | "author": "origo ", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "babel-preset-env": "^1.6.0", 15 | "babel-preset-es2015": "^6.24.1", 16 | "babelify": "^7.3.0", 17 | "browserify": "^14.4.0", 18 | "envify": "^4.1.0", 19 | "ezcrypto": "^0.0.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Wallet.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Extremely simple transaction-summarizing 'wallet' 4 | export default class Wallet { 5 | 6 | constructor(id, blockchain) { 7 | this._id = id; 8 | this._chain = blockchain; 9 | // Genesis block has no blockchain, yet, hence no transactions either 10 | this._transactions = this._chain !== void 0 ? this._findTransactions() : []; 11 | } 12 | 13 | get Id() { 14 | return this._id; 15 | } 16 | 17 | get Transactions() { 18 | return this._transactions; 19 | } 20 | 21 | get Balance() { 22 | return this._transactions.reduce((a,v) => v.Sender === this.Id ? -v.Amount : v.Amount, 0); 23 | } 24 | 25 | // FIXME: Traverses the entire blockchain to find transactions involving this wallet.. 26 | _findTransactions() { 27 | return this._chain.Chain.map(block => { 28 | return block.Transactions.filter(t => t.Receiver === this.Id || t.Sender === this._id); 29 | }).reduce((a,v) => a.concat(v), []); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 origo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Config.js: -------------------------------------------------------------------------------- 1 | 2 | const hfn = (process.env.HASH_FUNC !== void 0 ? process.env.HASH_FUNC : 'SHA256'); 3 | const hash_func = require('ezcrypto/lib/' + hfn.toLowerCase())[hfn]; 4 | const nonce_size = parseInt(process.env.NONCE_SIZE !== void 0 ? process.env.NONCE_SIZE : 32, 10); 5 | 6 | const Config = { 7 | VERSION: parseFloat(process.env.VERSION !== void 0 ? process.env.VERSION : 1.0), 8 | GENESIS_HASH: process.env.GENESIS_HASH !== void 0 ? process.env.GENESIS_HASH : '0000000000000000000000000000000000000000000000000000000000000000', 9 | GENESIS_AMOUNT: parseInt(process.env.GENESIS_AMOUNT !== void 0 ? process.env.GENESIS_AMOUNT : 10000000, 10), 10 | GENESIS_WALLET: process.env.GENESIS_WALLET !== void 0 ? process.env.GENESIS_WALLET : 'GENESIS', 11 | COINBASE1: process.env.COINBASE1 !== void 0 ? process.env.COINBASE1 : '12345678A0B0C0D0E0F0D0E0A0D0C0DE0010C013374000000000000000000000', 12 | DIFFICULTY: parseInt(process.env.DIFFICULTY !== void 0 ? process.env.DIFFICULTY : 5, 10), 13 | NONCE_SIZE: nonce_size, 14 | NONCE_STEP: ~~(Math.random()*((1<<(nonce_size-1)))), 15 | HASH_FUNC_NAME: hfn, 16 | HASH_FUNC: hash_func 17 | }; 18 | 19 | export default Config; 20 | -------------------------------------------------------------------------------- /src/Blockchain.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Block from './Block'; 4 | import Config from './Config'; 5 | 6 | export default class Blockchain { 7 | 8 | constructor(genesisBlock, genesisHash, difficulty=5) { 9 | this._height = 0; 10 | this._difficulty = difficulty; 11 | this._chain = [ genesisBlock ]; 12 | this._nextBlock = new Block(++this._height, Config.VERSION, new Date(), [], genesisHash); 13 | } 14 | 15 | get Difficulty() { 16 | return this._difficulty; 17 | } 18 | 19 | get Height() { 20 | return this._height; 21 | } 22 | 23 | get LastBlock() { 24 | return this._chain[this.Height-1]; 25 | } 26 | 27 | get NextBlock() { 28 | return this._nextBlock; 29 | } 30 | 31 | get Chain() { 32 | return this._chain; 33 | } 34 | 35 | nextBlock() { 36 | this._chain.push(this._nextBlock); 37 | this._nextBlock = new Block(++this._height, Config.VERSION, new Date(), [], this.LastBlock.Hash); 38 | } 39 | 40 | verify() { 41 | let current = this._chain[0]; 42 | for(let i = 1; i < this._chain.length; i++) { 43 | let next = this._chain[i]; 44 | if(next.PreviousHash !== current.Hash) { 45 | console.error('verify error, %s !== %s ', next.PreviousHash, current.Hash) 46 | return false; 47 | } 48 | current = next; 49 | } 50 | 51 | return true; 52 | } 53 | 54 | toJSON() { 55 | return {config: Config, chain: this._chain.map(b => b.toJSON())}; 56 | } 57 | 58 | toString() { 59 | return JSON.stringify(this.toJSON()) 60 | } 61 | 62 | pretty() { 63 | return JSON.stringify(this.toJSON(), false, 2); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/init_blockchain.js: -------------------------------------------------------------------------------- 1 | 2 | import Blockchain from './Blockchain'; 3 | import Transaction from './Transaction'; 4 | import Block from './Block'; 5 | import Wallet from './Wallet'; 6 | import Miner from './Miner'; 7 | import Config from './Config'; 8 | 9 | function _createGenesisBlock(genesis_wallet) { 10 | let gb = new Block(0, Config.VERSION, new Date()); 11 | // (id, sender, receiver, amount, timestamp) 12 | gb.addTransaction(new Transaction(0, genesis_wallet.Id, genesis_wallet.Id, Config.GENESIS_AMOUNT, new Date())); 13 | return gb; 14 | } 15 | 16 | // The Genesis wallet is the origin of all funds 17 | const gw = new Wallet(Config.GENESIS_WALLET); 18 | const genesisBlock = _createGenesisBlock(gw); 19 | const bc = new Blockchain(genesisBlock, Config.GENESIS_HASH, Config.DIFFICULTY); 20 | const miner = new Miner(bc); 21 | 22 | // Once the blockchain exists, we can create wallets 23 | const w1 = new Wallet('s1', bc); 24 | const w2 = new Wallet('s2', bc); 25 | 26 | console.log('Created Blockchain with Genesis Block: %s', bc.toString()); 27 | 28 | // Add a few transactions to the next block .. 29 | const newBlock = bc.NextBlock; 30 | newBlock.addTransaction(new Transaction(0, gw, w1, Config.GENESIS_AMOUNT/2)); 31 | newBlock.addTransaction(new Transaction(0, gw, w2, Config.GENESIS_AMOUNT/4)); 32 | newBlock.addTransaction(new Transaction(0, w1, gw, Config.GENESIS_AMOUNT/2)); 33 | newBlock.addTransaction(new Transaction(0, w2, gw, Config.GENESIS_AMOUNT/4)); 34 | 35 | // .. then mine for it 36 | 37 | let minedBlock = false; 38 | let mineCnt = 0; 39 | const MINE_STEP = 2048; 40 | function mine(num) { 41 | minedBlock = miner.mine(MINE_STEP); 42 | if(!minedBlock) { 43 | mineCnt++; 44 | if((mineCnt & 63) === 0) { 45 | console.log('..still mining for block %d @ %d khash/s: %d/%d hashes: %d', bc.NextBlock.Id, ~~(miner.Hashrate), miner.BestDifficulty, bc.Difficulty, mineCnt*MINE_STEP); 46 | } 47 | setTimeout(mine, 1); 48 | } else { 49 | console.log('CANDIDATE FOR BLOCK %d FOUND!', minedBlock.Id, minedBlock.toString()); 50 | // Append current NextBlock to chain and generates new Nextblock 51 | bc.nextBlock(); 52 | console.log('Blockchain', bc.pretty()); 53 | } 54 | } 55 | mine(); 56 | -------------------------------------------------------------------------------- /src/Miner.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Config from './Config'; 4 | 5 | export default class Miner { 6 | 7 | constructor(blockchain) { 8 | this._nonce = ~~(Math.random()*(1<<(Config.NONCE_SIZE-1))); 9 | this._bestDifficulty = 0; 10 | this._chain = blockchain; 11 | this._hashrate = 0; 12 | } 13 | 14 | _attempt(block) { 15 | this._nonce = (this._nonce + Config.NONCE_STEP) & ((1<<(Config.NONCE_SIZE-1))-1); 16 | //console.log('nonce', this.Nonce.toString(16)); 17 | block.Nonce = this._nonce; 18 | this._hash = block.Hash.toString(); 19 | // FIXME: Calculates leading HEX zeros for now, we can't mine much (64 lvls) 20 | let lz = -(this._hash.replace(/^0*/,'').length - this._hash.length); 21 | return lz; 22 | } 23 | 24 | /** 25 | * Mine given blockchain for it's NextBlock hash. Call after all transactions has 26 | * been added, and a candidate hash is required to add it to the chain. 27 | * @return {Block|null} If a candidate cash is found, the new Block is returned. 28 | */ 29 | mine(max_iters = 1000) { 30 | let block = this._chain.NextBlock; 31 | let difficulty = this._chain.Difficulty; 32 | let i = 0; 33 | let result = null; 34 | let start = Date.now(); 35 | if(start - block.Timestamp.getTime() > 10000) { 36 | block.Timestamp = new Date(); 37 | console.log('New timestamp: %s', block.Timestamp.toISOString()); 38 | } 39 | while(i++ < max_iters && result === null) { 40 | let lz = this._attempt(block); 41 | if(lz >= difficulty) { 42 | this._bestDifficulty = lz; 43 | result = block; 44 | console.log('FOUND NONCE! difficulty:%d(>=%d) nonce:%d hash:%s', lz, difficulty, this.Nonce, this.Hash); 45 | } else if(lz >= this._bestDifficulty) { 46 | this._bestDifficulty = lz; 47 | } 48 | } 49 | 50 | let end = Date.now(); 51 | let hashes_per_s = (i/(end-start)); 52 | this._hashrate = (this._hashrate*3 + hashes_per_s) / 4; 53 | return result; 54 | } 55 | 56 | get Hashrate() { 57 | return this._hashrate; 58 | } 59 | 60 | get Block() { 61 | return this._block; 62 | } 63 | 64 | get Nonce() { 65 | return Number(this._nonce); 66 | } 67 | 68 | get BestDifficulty() { 69 | return this._bestDifficulty; 70 | } 71 | 72 | get Hash() { 73 | return this._hash; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Transaction.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Wallet from './Wallet'; 4 | import Config from './Config'; 5 | 6 | export default class Transaction { 7 | 8 | constructor(id, sender, receiver, amount, timestamp, hash=void 0) { 9 | this._id = id; 10 | this._sender = sender; 11 | this._receiver = receiver; 12 | this._amount = amount; 13 | this._timestamp = timestamp === void 0 ? new Date() : timestamp; 14 | this._hash = hash !== void 0 ? hash : this.Hash; 15 | } 16 | 17 | get Id() { 18 | return this._id; 19 | } 20 | 21 | set Id(id) { 22 | this._hash = false; 23 | this._id = id; 24 | } 25 | 26 | // @return {Wallet|null} 27 | get Sender() { 28 | return this._sender; 29 | } 30 | 31 | // @return {Wallet|null} 32 | get Receiver() { 33 | return this._receiver; 34 | } 35 | 36 | // @return {Number} 37 | get Amount() { 38 | return Number(this._amount||0); 39 | } 40 | 41 | // @return {Date} 42 | get Timestamp() { 43 | return this._timestamp; 44 | } 45 | 46 | // @return {Hash} The Hash object 47 | get Hash() { 48 | if(!this._hash) { 49 | this._hash = Config.HASH_FUNC(this.HashFields.join('')); 50 | } 51 | return this._hash; 52 | } 53 | 54 | // @return {Array} The transaction field values 55 | get HashFields() { 56 | return [this.Id, this.Sender, this.Receiver, this.Amount, this.Timestamp.toISOString()]; 57 | } 58 | 59 | static fromJSON(json, blockchain) { 60 | let nt = new Transaction(parseInt(json.id), 61 | new Wallet(json.sender, blockchain), 62 | new Wallet(json.receiver, blockchain), 63 | parseFloat(json.amount), 64 | new Date(timestamp)); 65 | let hash = nt.Hash.toString(); 66 | if(hash !== json.hash) { 67 | throw new Error('Hash mismatch in deserialized transaction!'); 68 | } 69 | return nt; 70 | } 71 | 72 | toJSON() { 73 | return { 74 | id: this.Id, 75 | sender: this.Sender.Id, 76 | receiver: this.Receiver.Id, 77 | amount: this.Amount, 78 | timestamp: this.Timestamp.toISOString(), 79 | hash: this.Hash.toString() 80 | }; 81 | } 82 | 83 | toString() { 84 | return JSON.stringify(this.toJSON()); 85 | } 86 | 87 | pretty() { 88 | return JSON.stringify(this.toJSON(), false, 2); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-blockchain 2 | 3 | ## Description 4 | 5 | Proof of concept javascript blockchain with basic wallet and miner. 6 | 7 | ## Disclaimer 8 | 9 | This is simply a Proof of Concept implementation to familiarize myself with blockchains, it most likely is wrong in a lot of places, lacks any (thought of) any security features, and will most likely only serve well as an enjoyable reading excercise for bored peers. 10 | 11 | 12 | ## Usage 13 | 14 | Clone repository, install dependencies: 15 | 16 | ```sh 17 | bash$ git clone git@github.com:allbinmani/js-blockchain.git 18 | bash$ cd js-blockchain; npm i 19 | ``` 20 | 21 | Generate code for your own unique blockchain, with it's unique `COINBASE1`, `GENESIS_WALLET`, `GENESIS_AMOUNT` and `HASH_FUNC`: 22 | 23 | ```sh 24 | bash$ DIFFICULTY=5 COINBASE1=012345678A0B0C0D0E0F0D0E0A0D0C0DE0010C01337400000000000000000000 \ 25 | GENESIS_WALLET=I_AM_GENESIS GENESIS_AMOUNT=10000000 \ 26 | VERSION=1.0 HASH_FUNC=SHA256 \ 27 | npm run build 28 | ``` 29 | 30 | `HASH_FUNC` can be selected from any hashing function provided by [`ezcrypto`](https://www.npmjs.com/package/ezcrypto), namely `SHA1`, `SHA256` and `MD5`. 31 | The `Block` hash is always double-hashed, transaction hashes are single hashed, but not using [Merkle Trees]() yet. 32 | 33 | Create blockchain and initial blocks / transactions: 34 | 35 | ```sh 36 | sh$ npm run init_blockchain 37 | ``` 38 | 39 | The first block (the genesis block) is generated, containing a single transaction from and to the `GENESIS_WALLET` with `amount` set to `GENESIS_AMOUNT`. 40 | 41 | Another block is also mined, with a few dummy transactions back and forth to two temporary wallets. Once that block has been mined, its added to the blockchain, which is then printed as a JSON struct to stdout. 42 | 43 | 44 | ## Roadmap 45 | 46 | - Implement basic blockchain. DONE 47 | - Implement fundaental miner. DONE 48 | - Implement fundamental wallet. DONE 49 | 50 | - TODO: Merkle trees 51 | - TODO: (De)serialization to/from disk 52 | 53 | - FUTURE: Networking, confirmations, consensus, etc etc etc ? 54 | 55 | 56 | ## Contributing 57 | 58 | Read the code, understand it's style and inner workings, then fork and submit pull requests, I might merge them, I might not. 59 | 60 | 61 | ## Donate 62 | 63 | I hear it's a good idea to have your wallets exposed these days. If you appreciate the work, consider dropping off a few cents. 64 | 65 | **BTC**: `34xzza81xCAtGEceZpPgMAJqw9MW2pHTFR` 66 | **ZEC**: `t1fmXwQR4whaGCXAsfjAZn9vJb1oGUe3eF3` 67 | **XMR**: `47KecbLbAfQUdBLvusz7dbWzrcgLsMLhs1kBQxsamGfth5GXuUttfnzjXqrT3anyZ22j7DEE74GkbVcQFyH2nNiC3eeiE4T` 68 | **BCN**: `27WeDqwNuqJApsvks5JtBFDuShsz6iqrmVV48yXjuMai2x4BBAK28Nidi7ok6B5SQT6UXUtQgusruCoXbqUZm8VJAgbnitW` 69 | 70 | Any tiny coin is appreciated, and will encourage me to produce and share more free code. 71 | -------------------------------------------------------------------------------- /src/Block.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Config from './Config'; 4 | import Transaction from './Transaction'; 5 | 6 | export default class Block { 7 | 8 | constructor(id, version, timestamp, transactions=[], previousHash=false, nonce=0) { 9 | this._id = id; 10 | this._version = version; 11 | this._timestamp = timestamp; 12 | this._transactions = transactions; 13 | this._previousHash = previousHash; 14 | this._nonce = nonce; 15 | this._hash = false; 16 | } 17 | 18 | addTransaction(transaction) { 19 | transaction.Id = this._transactions.length; 20 | this._transactions.push(transaction); 21 | this._hash = false; 22 | this._thash = false; 23 | } 24 | 25 | get Id() { 26 | return this._id; 27 | } 28 | 29 | get Version() { 30 | return this._version; 31 | } 32 | 33 | get Timestamp() { 34 | return this._timestamp; 35 | } 36 | 37 | set Timestamp(new_timestamp) { 38 | this._timestamp = new_timestamp; 39 | this._hash = false; // new timestamp, new hash 40 | } 41 | 42 | get Transactions() { 43 | return this._transactions; 44 | } 45 | 46 | // @return {String} 47 | get PreviousHash() { 48 | return this._previousHash; 49 | } 50 | 51 | // @return {String} 52 | set PreviousHash(previousHash) { 53 | this._previousHash = Util.zeropad(previousHash); 54 | this._hash = false; // new previous hash, new hash 55 | } 56 | 57 | // @param {Number} New nonce / invalidates any currently calculated hash 58 | set Nonce(nonce) { 59 | this._hash = false; // new nonce, new hash 60 | this._nonce = nonce; 61 | } 62 | 63 | // @return {Number} Nonce 64 | get Nonce() { 65 | return Number(this._nonce); 66 | } 67 | 68 | get Hash() { 69 | if(this._hash === false) { 70 | this._hash = Config.HASH_FUNC(Config.HASH_FUNC(this.HashFields.join('')).toString()); 71 | } 72 | return this._hash; 73 | } 74 | 75 | get TransactionsHash() { 76 | if(!this._thash) { 77 | // TODO: Merkle Root the transactions 78 | this._thash = Config.HASH_FUNC(this.Transactions.map(t => t.Hash.toString()).join('')).toString(); 79 | } 80 | return this._thash; 81 | } 82 | 83 | get HashFields() { 84 | return [this.Version, 85 | this.Id, 86 | this.Timestamp.toISOString(), 87 | this.TransactionsHash, 88 | this.PreviousHash, 89 | Config.COINBASE1, 90 | this.Nonce.toString(16)]; 91 | } 92 | 93 | static fromJSON(json) { 94 | 95 | } 96 | 97 | toJSON() { 98 | return {id: this.Id, 99 | version: this.Version, 100 | timestamp: this.Timestamp, 101 | thash: this.TransactionsHash, 102 | phash: this.PreviousHash, 103 | nonce: this.Nonce.toString(16), 104 | transactions: this.Transactions.map(t => t.toJSON()), 105 | hash: this.PreviousHash ? this.Hash.toString() : undefined 106 | }; 107 | } 108 | 109 | toString() { 110 | return JSON.stringify(this.toJSON()); 111 | } 112 | 113 | pretty() { 114 | return JSON.stringify(this.toJSON(), false, 2); 115 | } 116 | } 117 | --------------------------------------------------------------------------------