From 8a3c9d2dcdadf17c904590343c13e9421211ad1c Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Mon, 28 Apr 2014 17:12:26 +0400 Subject: [PATCH] initial --- .gitignore | 2 + README.md | 28 ++++++++++ lib/bcoin.js | 6 +++ lib/bcoin/protocol/constants.js | 8 +++ lib/bcoin/protocol/framer.js | 83 +++++++++++++++++++++++++++++ lib/bcoin/protocol/index.js | 5 ++ lib/bcoin/protocol/parser.js | 86 ++++++++++++++++++++++++++++++ lib/bcoin/utils.js | 94 +++++++++++++++++++++++++++++++++ lib/bcoin/wallet.js | 37 +++++++++++++ package.json | 31 +++++++++++ test/utils-test.js | 11 ++++ test/wallet-test.js | 19 +++++++ 12 files changed, 410 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 lib/bcoin.js create mode 100644 lib/bcoin/protocol/constants.js create mode 100644 lib/bcoin/protocol/framer.js create mode 100644 lib/bcoin/protocol/index.js create mode 100644 lib/bcoin/protocol/parser.js create mode 100644 lib/bcoin/utils.js create mode 100644 lib/bcoin/wallet.js create mode 100644 package.json create mode 100644 test/utils-test.js create mode 100644 test/wallet-test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..71b051e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +npm-debug.log +node_modules/ diff --git a/README.md b/README.md new file mode 100644 index 00000000..cf7bfbdc --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# BCoin + +Just a bike-shed. + +#### LICENSE + +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2014. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/bcoin.js b/lib/bcoin.js new file mode 100644 index 00000000..a2ff4f6e --- /dev/null +++ b/lib/bcoin.js @@ -0,0 +1,6 @@ +var bcoin = exports; +var elliptic = require('elliptic'); + +bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1); +bcoin.utils = require('./bcoin/utils'); +bcoin.wallet = require('./bcoin/wallet'); diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js new file mode 100644 index 00000000..7356a9ef --- /dev/null +++ b/lib/bcoin/protocol/constants.js @@ -0,0 +1,8 @@ +exports.minVersion = 70001; +exports.version = 70002; +exports.magic = 0xd9b4bef9; + +// version - services field +exports.services = { + network: 1 +}; diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js new file mode 100644 index 00000000..66e53a7e --- /dev/null +++ b/lib/bcoin/protocol/framer.js @@ -0,0 +1,83 @@ +var assert = require('assert'); + +var bcoin = require('../../bcoin'); +var constants = require('./constants'); +var utils = bcoin.utils; + +function Framer(peer) { + if (!(this instanceof Framer)) + return new Framer(peer); + + this.peer = peer; + this.network = peer.network; +} +module.exports = Framer; + +Framer.prototype.header = function header(cmd, payload) { + assert(cmd.length < 12); + assert(payload.length <= 0xffffffff); + + var h = new Buffer(24); + + // Magic value + h.writeUInt32LE(constants.magic, 0, true); + + // Command + var len = h.write(cmd, 4); + for (var i = 4 + len; i < 4 + 12; i++) + h[i] = 0; + + // Payload length + h.writeUInt32LE(payload.length, 16, true); + + // Checksum + h.writeUInt32LE(utils.checksum(payload), 20, true); + + return h; +}; + +Framer.prototype.packet = function packet(cmd, payload) { + var h = this.header('version', payload); + return Buffer.concat([ h, payload ], h.length + payload.length); +}; + +Framer.prototype._addr = function addr(buf, off, addr) { +}; + +Framer.prototype.version = function version() { + var local = this.network.externalAddr; + var remote = this.peer.addr; + + var p = new Buffer(86); + + // Version + p.writeUInt32LE(constants.version, 0, true); + + // Services + p.writeUInt32LE(constants.services.network, 4, true); + p.writeUInt32LE(0, 8, true); + + // Timestamp + var ts = ((+new Date) / 1000) | 0; + p.writeUInt32LE(ts, 12, true); + p.writeUInt32LE(0, 16, true); + + // Remote and local addresses + this._addr(p, 20, remote); + this._addr(p, 46, local); + + // Nonce, very dramatic + p.writeUInt32LE(0xdeadbeef, 72, true); + p.writeUInt32LE(0xabbadead, 76, true); + + // No user-agent + p[80] = 0; + + // Start height + p.writeUInt32LE(0x0, 81, true); + + // Relay + p[85] = 0; + + return this.packet('version', p); +}; diff --git a/lib/bcoin/protocol/index.js b/lib/bcoin/protocol/index.js new file mode 100644 index 00000000..b1ba0845 --- /dev/null +++ b/lib/bcoin/protocol/index.js @@ -0,0 +1,5 @@ +var protocol = exports; + +protocol.constants = require('./constants'); +protocol.framer = require('./framer'); +protocol.parser = require('./parser'); diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js new file mode 100644 index 00000000..f0423893 --- /dev/null +++ b/lib/bcoin/protocol/parser.js @@ -0,0 +1,86 @@ +var assert = require('assert'); +var util = require('util'); +var Transform = require('stream').Transform; + +var bspv = require('../../bspv'); +var utils = bspv.utils; +var constants = require('./constants'); + +function Parser(peer) { + if (!(this instanceof Parser)) + return new Parser(peer); + + Transform.call(this); + this._readableState.objectMode = true; + + this.peer = peer; + + this.pending = []; + this.pendingTotal = 0; + this.waiting = 24; + this.packet = null; +} +util.inherits(Parser, Transform); +module.exports = Parser; + +Parser.prototype._transform = function(data, enc, cb) { + if (data) { + this.pendingTotal += data.length; + this.pending.push(data); + } + while (this.pendingTotal >= this.waiting) { + // Concat chunks + var chunk = new Buffer(this.waiting); + console.log(this.pending); + for (var i = 0, off = 0, len = 0; off < chunk.length; i++) { + len = this.pending[i].copy(chunk, off); + off += len; + } + assert.equal(off, chunk.length); + + // Slice buffers + this.pending = this.pending.slice(); + this.pendingTotal -= chunk.length; + if (this.pending.length && len !== this.pending[0].length) + this.pending[0] = this.pending[0].slice(len); + + this.parse(chunk); + } + cb(); +}; + +Parser.prototype.parse = function parse(chunk) { + if (this.packet === null) { + this.packet = this.parseHeader(chunk); + } else { + this.packet.payload = chunk; + if (utils.checksum(this.packet.payload) !== this.packet.checksum) + return this.emit('error', new Error('Invalid checksum')); + this.push(this.packet); + + this.waiting = 24; + this.packet = null; + } +}; + +Parser.prototype.parseHeader = function parseHeader(h) { + var magic = h.readUInt32LE(0); + if (magic !== constants.magic) { + return this.emit('error', + new Error('Invalid magic value: ' + magic.toString(16))); + } + + // Count length of the cmd + for (var i = 0; h[i + 4] !== 0 && i < 12; i++); + if (i === 12) + return this.emit('error', new Error('Not NULL-terminated cmd')); + + var cmd = h.slice(4, 4 + i).toString(); + this.waiting = h.readUInt32LE(16); + + return { + cmd: cmd, + length: this.waiting, + checksum: h.readUInt32LE(20) + }; +}; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js new file mode 100644 index 00000000..7e064ed1 --- /dev/null +++ b/lib/bcoin/utils.js @@ -0,0 +1,94 @@ +var utils = exports; + +var assert = require('assert'); +var bn = require('bn.js'); +var hash = require('hash.js'); + + +var base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZ' + + 'abcdefghijkmnopqrstuvwxyz'; + +utils.toBase58 = function toBase58(arr) { + var n = new bn(arr, 16); + + // 58 ^ 4 + var mod = new bn(0xacad10); + + var res = ''; + do { + var r = n.mod(mod); + n = n.div(mod); + + var end = n.cmp(0) === 0; + + assert.equal(r.length, 1); + r = r.words[0]; + + for (var i = 0; i < 4; i++) { + var c = r % 58; + r = (r - c) / 58; + + if (c === 0 && r === 0 && end) + break; + res = base58[c] + res; + } + assert.equal(r, 0); + } while (!end); + + // Add leading "zeroes" + for (var i = 0; i < arr.length; i++) { + if (arr[i] !== 0) + break; + res = '1' + res; + } + + return res; +}; + +utils.fromBase58 = function fromBase58(str) { + // Count leading "zeroes" + for (var i = 0; i < str.length; i++) + if (str[i] !== '1') + break; + zeroes = i; + + // Read 4-char words and add them to bignum + var q = 1; + var w = 0; + var res = new bn(0); + for (var i = zeroes; i < str.length; i++) { + var c = base58.indexOf(str[i]); + assert(c >= 0 && c < 58); + + q *= 58; + w *= 58; + w += c; + if (i === str.length - 1 || q === 0xacad10) { + res = res.mul(q).add(w); + q = 1; + w = 0; + } + } + + // Add leading "zeroes" + var z = []; + for (var i = 0; i < zeroes; i++) + z.push(0); + return z.concat(res.toArray()); +}; + +utils.sha256 = function sha256(data, enc) { + return hash.sha256().update(data, enc).digest(); +}; + +utils.readU32 = function readU32(arr) { + return arr[0] | (arr[1] << 8) | (arr[2] << 16) | (arr[3] << 24); +}; + +utils.ripesha = function ripesha(data, enc) { + return hash.ripemd160().update(utils.sha256(data, enc)).digest(); +}; + +utils.checksum = function checksum(data, enc) { + return utils.sha256(utils.sha256(data, enc)).slice(0, 4); +}; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js new file mode 100644 index 00000000..03b1d7cd --- /dev/null +++ b/lib/bcoin/wallet.js @@ -0,0 +1,37 @@ +var bcoin = require('../bcoin'); +var utils = bcoin.utils; + +function Wallet() { + if (!(this instanceof Wallet)) + return new Wallet(); + + this.key = bcoin.ecdsa.genKeyPair(); +} +module.exports = Wallet; + +Wallet.prototype.getAddress = function getAddress() { + var pub = this.key.getPublic('array'); + var keyHash = utils.ripesha(pub); + + // Add version + keyHash = [ 0 ].concat(keyHash); + + var addr = keyHash.concat(utils.checksum(keyHash)); + return utils.toBase58(addr); +} + +Wallet.prototype.validateAddress = function validateAddress(addr) { + if (!Array.isArray(addr)) + addr = utils.fromBase58(addr); + + if (addr.length !== 25) + return false; + if (addr[0] !== 0) + return false; + var chk = utils.checksum(addr.slice(0, -4)); + if (utils.readU32(chk) !== utils.readU32(addr.slice(21))) + return false; + + return true; +}; +Wallet.validateAddress = Wallet.prototype.validateAddress; diff --git a/package.json b/package.json new file mode 100644 index 00000000..17670e36 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "bcoin", + "version": "0.0.0", + "description": "Bitcoin bike-shed", + "main": "lib/bcoin.js", + "scripts": { + "test": "mocha --reporter spec test/*-test.js" + }, + "repository": { + "type": "git", + "url": "git@github.com:indutny/bcoin" + }, + "keywords": [ + "bitcoin", + "bcoin" + ], + "author": "Fedor Indutny ", + "license": "MIT", + "bugs": { + "url": "https://github.com/indutny/bcoin/issues" + }, + "homepage": "https://github.com/indutny/bcoin", + "dependencies": { + "bn.js": "^0.1.7", + "elliptic": "^0.6.0", + "hash.js": "^0.2.0" + }, + "devDependencies": { + "mocha": "^1.18.2" + } +} diff --git a/test/utils-test.js b/test/utils-test.js new file mode 100644 index 00000000..7f6a869a --- /dev/null +++ b/test/utils-test.js @@ -0,0 +1,11 @@ +var assert = require('assert'); +var bcoin = require('../'); + +describe('Utils', function() { + it('should encode/decode base58', function() { + var arr = [ 0, 0, 0, 0xde, 0xad, 0xbe, 0xef ]; + var b = bcoin.utils.toBase58(arr); + assert.equal(b, '1116h8cQN'); + assert.deepEqual(bcoin.utils.fromBase58(b), arr); + }); +}); diff --git a/test/wallet-test.js b/test/wallet-test.js new file mode 100644 index 00000000..cf558c2d --- /dev/null +++ b/test/wallet-test.js @@ -0,0 +1,19 @@ +var assert = require('assert'); +var bcoin = require('../'); + +describe('Wallet', function() { + it('should generate new key and address', function() { + var w = bcoin.wallet(); + var addr = w.getAddress(); + assert(addr); + assert(bcoin.wallet.validateAddress(addr)); + }); + + it('should validate existing address', function() { + assert(bcoin.wallet.validateAddress('1KQ1wMNwXHUYj1nV2xzsRcKUH8gVFpTFUc')); + }); + + it('should fail to validate invalid address', function() { + assert(!bcoin.wallet.validateAddress('1KQ1wMNwXHUYj1nv2xzsRcKUH8gVFpTFUc')); + }); +});