This commit is contained in:
Fedor Indutny 2014-04-28 17:12:26 +04:00
commit 8a3c9d2dcd
12 changed files with 410 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
npm-debug.log
node_modules/

28
README.md Normal file
View File

@ -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.

6
lib/bcoin.js Normal file
View File

@ -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');

View File

@ -0,0 +1,8 @@
exports.minVersion = 70001;
exports.version = 70002;
exports.magic = 0xd9b4bef9;
// version - services field
exports.services = {
network: 1
};

View File

@ -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);
};

View File

@ -0,0 +1,5 @@
var protocol = exports;
protocol.constants = require('./constants');
protocol.framer = require('./framer');
protocol.parser = require('./parser');

View File

@ -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)
};
};

94
lib/bcoin/utils.js Normal file
View File

@ -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);
};

37
lib/bcoin/wallet.js Normal file
View File

@ -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;

31
package.json Normal file
View File

@ -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 <fedor@indutny.com>",
"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"
}
}

11
test/utils-test.js Normal file
View File

@ -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);
});
});

19
test/wallet-test.js Normal file
View File

@ -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'));
});
});