initial
This commit is contained in:
commit
8a3c9d2dcd
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
npm-debug.log
|
||||
node_modules/
|
||||
28
README.md
Normal file
28
README.md
Normal 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
6
lib/bcoin.js
Normal 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');
|
||||
8
lib/bcoin/protocol/constants.js
Normal file
8
lib/bcoin/protocol/constants.js
Normal file
@ -0,0 +1,8 @@
|
||||
exports.minVersion = 70001;
|
||||
exports.version = 70002;
|
||||
exports.magic = 0xd9b4bef9;
|
||||
|
||||
// version - services field
|
||||
exports.services = {
|
||||
network: 1
|
||||
};
|
||||
83
lib/bcoin/protocol/framer.js
Normal file
83
lib/bcoin/protocol/framer.js
Normal 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);
|
||||
};
|
||||
5
lib/bcoin/protocol/index.js
Normal file
5
lib/bcoin/protocol/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
var protocol = exports;
|
||||
|
||||
protocol.constants = require('./constants');
|
||||
protocol.framer = require('./framer');
|
||||
protocol.parser = require('./parser');
|
||||
86
lib/bcoin/protocol/parser.js
Normal file
86
lib/bcoin/protocol/parser.js
Normal 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
94
lib/bcoin/utils.js
Normal 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
37
lib/bcoin/wallet.js
Normal 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
31
package.json
Normal 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
11
test/utils-test.js
Normal 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
19
test/wallet-test.js
Normal 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'));
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user