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