Merge branch 'satoshi-p2sh-hd'
This commit is contained in:
commit
f939d73b38
2050
etc/english.json
Normal file
2050
etc/english.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
var bcoin = exports;
|
||||
var elliptic = require('elliptic');
|
||||
|
||||
bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1);
|
||||
bcoin.ecdsa = elliptic.ec('secp256k1');
|
||||
bcoin.utils = require('./bcoin/utils');
|
||||
bcoin.bloom = require('./bcoin/bloom');
|
||||
bcoin.protocol = require('./bcoin/protocol');
|
||||
@ -13,3 +13,6 @@ bcoin.chain = require('./bcoin/chain');
|
||||
bcoin.wallet = require('./bcoin/wallet');
|
||||
bcoin.peer = require('./bcoin/peer');
|
||||
bcoin.pool = require('./bcoin/pool');
|
||||
bcoin.hd = require('./bcoin/hd');
|
||||
|
||||
bcoin.protocol.network.set(process.env.BCOIN_NETWORK || 'main');
|
||||
|
||||
@ -3,7 +3,7 @@ var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
var bcoin = require('../bcoin');
|
||||
var constants = bcoin.protocol.constants;
|
||||
var preload = bcoin.protocol.preload;
|
||||
var network = bcoin.protocol.network;
|
||||
var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
|
||||
@ -26,6 +26,7 @@ function Chain(options) {
|
||||
};
|
||||
this.orphan = {
|
||||
map: {},
|
||||
bmap: {},
|
||||
count: 0
|
||||
};
|
||||
this.index = {
|
||||
@ -37,6 +38,20 @@ function Chain(options) {
|
||||
};
|
||||
this.request = new utils.RequestCache();
|
||||
|
||||
var preload = network.preload;
|
||||
|
||||
// Start from the genesis block
|
||||
// if we're a full node.
|
||||
if (this.options.fullNode) {
|
||||
preload = {
|
||||
v: preload.v,
|
||||
type: preload.type,
|
||||
hashes: preload.hashes.slice(0, 1),
|
||||
ts: preload.ts.slice(0, 1),
|
||||
heights: preload.heights.slice(0, 1)
|
||||
};
|
||||
}
|
||||
|
||||
this.fromJSON(preload);
|
||||
|
||||
// Last TS after preload, needed for fill percent
|
||||
@ -53,25 +68,35 @@ function compareTs(a, b) {
|
||||
}
|
||||
|
||||
Chain.prototype._init = function _init() {
|
||||
var self = this;
|
||||
|
||||
if (!this.storage)
|
||||
return;
|
||||
|
||||
utils.nextTick(function() {
|
||||
self.emit('debug', 'Chain is loading.');
|
||||
});
|
||||
|
||||
this.loading = true;
|
||||
var self = this;
|
||||
|
||||
var s = this.storage.createReadStream({
|
||||
start: this.prefix,
|
||||
end: this.prefix + 'z'
|
||||
});
|
||||
|
||||
s.on('data', function(data) {
|
||||
var hash = data.key.slice(self.prefix.length);
|
||||
self._addIndex(hash, data.value.ts, data.value.height);
|
||||
});
|
||||
|
||||
s.on('error', function(err) {
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
s.on('end', function() {
|
||||
self.loading = false;
|
||||
self.emit('load');
|
||||
self.emit('debug', 'Chain successfully loaded.');
|
||||
});
|
||||
};
|
||||
|
||||
@ -222,6 +247,7 @@ Chain.prototype.add = function add(block) {
|
||||
if (!this._probeIndex(hash, block.ts) && !prevProbe) {
|
||||
this.orphan.count++;
|
||||
this.orphan.map[prev] = block;
|
||||
this.orphan.bmap[block.hash('hex')] = block;
|
||||
|
||||
var range = this._getRange(hash, block.ts, true);
|
||||
var hashes = this.index.hashes.slice(range.start, range.end + 1);
|
||||
@ -247,6 +273,7 @@ Chain.prototype.add = function add(block) {
|
||||
// We have orphan child for this block - add it to chain
|
||||
block = this.orphan.map[hash];
|
||||
delete this.orphan.map[hash];
|
||||
delete this.orphan.bmap[block.hash('hex')];
|
||||
this.orphan.count--;
|
||||
} while (true);
|
||||
|
||||
@ -388,6 +415,65 @@ Chain.prototype.getLast = function getLast(cb) {
|
||||
return cb(this.index.hashes[this.index.hashes.length - 1]);
|
||||
};
|
||||
|
||||
Chain.prototype.getStartHeight = function getStartHeight() {
|
||||
if (!this.options.fullNode) {
|
||||
if (this.options.startHeight != null) {
|
||||
return this.options.startHeight;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return this.index.heights[this.index.heights.length - 1];
|
||||
};
|
||||
|
||||
Chain.prototype.locatorHashes = function(index) {
|
||||
var chain = this.index.hashes;
|
||||
var hashes = [];
|
||||
var top = chain.length - 1;
|
||||
var step = 1;
|
||||
var i;
|
||||
|
||||
if (typeof index === 'string') {
|
||||
for (i = top; i >= 0; i--) {
|
||||
if (chain[i] === index) {
|
||||
top = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (typeof index === 'number') {
|
||||
top = index;
|
||||
}
|
||||
|
||||
i = top;
|
||||
for (;;) {
|
||||
hashes.push(chain[i]);
|
||||
i = i - step;
|
||||
if (i <= 0) {
|
||||
hashes.push(chain[0]);
|
||||
break;
|
||||
}
|
||||
if (hashes.length >= 10)
|
||||
step *= 2;
|
||||
}
|
||||
|
||||
return hashes;
|
||||
};
|
||||
|
||||
Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) {
|
||||
var self = this;
|
||||
|
||||
if (typeof hash !== 'string') {
|
||||
hash = hash.hash('hex');
|
||||
}
|
||||
|
||||
var orphans = this.orphan.bmap;
|
||||
|
||||
var orphanRoot = hash;
|
||||
while (orphans[orphanRoot.prevBlock])
|
||||
orphanRoot = orphans[orphanRoot.prevBlock];
|
||||
|
||||
return orphanRoot;
|
||||
};
|
||||
|
||||
Chain.prototype.toJSON = function toJSON() {
|
||||
var keep = 1000;
|
||||
|
||||
@ -432,6 +518,7 @@ Chain.prototype.toJSON = function toJSON() {
|
||||
return {
|
||||
v: 1,
|
||||
type: 'chain',
|
||||
network: network.type,
|
||||
hashes: first.hashes.concat(last.hashes),
|
||||
ts: first.ts.concat(last.ts),
|
||||
heights: first.heights.concat(last.heights)
|
||||
@ -441,16 +528,18 @@ Chain.prototype.toJSON = function toJSON() {
|
||||
Chain.prototype.fromJSON = function fromJSON(json) {
|
||||
assert.equal(json.v, 1);
|
||||
assert.equal(json.type, 'chain');
|
||||
if (json.network)
|
||||
assert.equal(json.network, network.type);
|
||||
this.index.hashes = json.hashes.slice();
|
||||
this.index.ts = json.ts.slice();
|
||||
this.index.heights = json.heights.slice();
|
||||
if (this.index.bloom)
|
||||
this.index.bloom.reset();
|
||||
else
|
||||
this.index.bloom = new bcoin.bloom(28 * 1024 * 1024, 16, 0xdeadbee0);
|
||||
this.index.bloom = new bcoin.bloom(28 * 1024 * 1024, 16, 0xdeadbeef);
|
||||
|
||||
if (this.index.hashes.length === 0)
|
||||
this.add(new bcoin.block(constants.genesis, 'block'));
|
||||
this.add(new bcoin.block(network.genesis, 'block'));
|
||||
|
||||
for (var i = 0; i < this.index.hashes.length; i++) {
|
||||
this.index.bloom.add(this.index.hashes[i], 'hex');
|
||||
|
||||
686
lib/bcoin/hd.js
Normal file
686
lib/bcoin/hd.js
Normal file
@ -0,0 +1,686 @@
|
||||
/**
|
||||
* hd.js - hierarchical determistic seeds and keys (BIP32, BIP39)
|
||||
* https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
||||
* https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
|
||||
*/
|
||||
|
||||
/**
|
||||
* Code adapted from bitcore-lib:
|
||||
* https://github.com/bitpay/bitcore-lib/blob/master/lib/hdprivatekey.js
|
||||
* https://github.com/bitpay/bitcore-lib/blob/master/lib/hdpublickey.js
|
||||
* https://github.com/ryanxcharles/fullnode/blob/master/lib/bip32.js
|
||||
*
|
||||
* Copyright (c) 2013-2015 BitPay, Inc.
|
||||
*
|
||||
* Parts of this software are based on Bitcoin Core
|
||||
* Copyright (c) 2009-2015 The Bitcoin Core developers
|
||||
*
|
||||
* Parts of this software are based on fullnode
|
||||
* Copyright (c) 2014 Ryan X. Charles
|
||||
* Copyright (c) 2014 reddit, Inc.
|
||||
*
|
||||
* Parts of this software are based on BitcoinJS
|
||||
* Copyright (c) 2011 Stefan Thomas <justmoon@members.fsf.org>
|
||||
*
|
||||
* Parts of this software are based on BitcoinJ
|
||||
* Copyright (c) 2011 Google Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
var hd = exports;
|
||||
|
||||
/**
|
||||
* Modules
|
||||
*/
|
||||
|
||||
var bcoin = require('../bcoin');
|
||||
var hash = require('hash.js');
|
||||
var bn = require('bn.js');
|
||||
var elliptic = require('elliptic');
|
||||
var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
var network = bcoin.protocol.network;
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
var english = require('../../etc/english.json');
|
||||
|
||||
var ec = elliptic.curves.secp256k1;
|
||||
|
||||
/**
|
||||
* HD Seeds
|
||||
*/
|
||||
|
||||
function HDSeed(options) {
|
||||
if (!(this instanceof HDSeed))
|
||||
return new HDSeed(options);
|
||||
|
||||
this.bits = options.bits || 128;
|
||||
this.entropy = options.entropy || HDSeed._entropy(this.bits / 8);
|
||||
this.mnemonic = options.mnemonic || HDSeed._mnemonic(this.entropy);
|
||||
if (options.passphrase !== undefined) {
|
||||
this.seed = this.createSeed(options.passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
HDSeed.create = function(options) {
|
||||
var obj = new HDSeed(options);
|
||||
return obj.seed || obj;
|
||||
};
|
||||
|
||||
HDSeed.prototype.createSeed = function(passphrase) {
|
||||
this.passphrase = passphrase || '';
|
||||
return pbkdf2(this.mnemonic, 'mnemonic' + passphrase, 2048, 64);
|
||||
};
|
||||
|
||||
HDSeed._entropy = function(size) {
|
||||
return randomBytes(size);
|
||||
};
|
||||
|
||||
HDSeed._mnemonic = function(entropy) {
|
||||
var bin = '';
|
||||
for (var i = 0; i < entropy.length; i++) {
|
||||
bin = bin + ('00000000' + entropy[i].toString(2)).slice(-8);
|
||||
}
|
||||
|
||||
var mnemonic = [];
|
||||
for (i = 0; i < bin.length / 11; i++) {
|
||||
var wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2);
|
||||
mnemonic.push(english[wi]);
|
||||
}
|
||||
|
||||
return mnemonic.join(' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* HD Keys
|
||||
*/
|
||||
|
||||
var HARDENED = 0x80000000;
|
||||
var MAX_INDEX = 2 * HARDENED;
|
||||
var MIN_ENTROPY = 128 / 8;
|
||||
var MAX_ENTROPY = 512 / 8;
|
||||
var PARENT_FINGER_PRINT_SIZE = 4;
|
||||
var PATH_ROOTS = ['m', 'M', 'm\'', 'M\''];
|
||||
|
||||
/**
|
||||
* HD Private Key
|
||||
*/
|
||||
|
||||
function HDPriv(options) {
|
||||
var data;
|
||||
|
||||
if (!(this instanceof HDPriv))
|
||||
return new HDPriv(options);
|
||||
|
||||
if (!options)
|
||||
options = { seed: new HDSeed({ passphrase: '' }) };
|
||||
|
||||
if (typeof options === 'string' && options.indexOf('xprv') === 0)
|
||||
options = { xkey: options };
|
||||
|
||||
if (options.passphrase)
|
||||
options.seed = new HDSeed({ passphrase: options.passphrase });
|
||||
|
||||
if (options.seed) {
|
||||
if (typeof options.seed === 'object' && !(options.seed instanceof HDSeed)) {
|
||||
options.seed = new HDSeed(options.seed);
|
||||
}
|
||||
this.seed = options.seed;
|
||||
data = this._seed(options.seed);
|
||||
} else if (options.xkey) {
|
||||
data = this._unbuild(options.xkey);
|
||||
} else {
|
||||
data = options;
|
||||
}
|
||||
|
||||
data = this._normalize(data, network.prefixes.xprivkey);
|
||||
|
||||
this.data = data;
|
||||
|
||||
this._build(data);
|
||||
}
|
||||
|
||||
HDPriv.prototype._normalize = function(data, version) {
|
||||
var b;
|
||||
|
||||
data.version = version || network.prefixes.xprivkey;
|
||||
data.version = +data.version;
|
||||
|
||||
data.depth = +data.depth;
|
||||
|
||||
if (typeof data.parentFingerPrint === 'number') {
|
||||
b = [];
|
||||
utils.writeU32BE(b, data.parentFingerPrint, 0);
|
||||
data.parentFingerPrint = b;
|
||||
}
|
||||
|
||||
data.childIndex = +data.childIndex;
|
||||
|
||||
if (typeof data.chainCode === 'string')
|
||||
data.chainCode = utils.toArray(data.chainCode, 'hex');
|
||||
|
||||
data.privateKey = data.privateKey || data.priv;
|
||||
if (data.privateKey) {
|
||||
if (data.privateKey.getPrivate)
|
||||
data.privateKey = data.privateKey.getPrivate().toArray();
|
||||
else if (typeof data.privateKey === 'string')
|
||||
data.privateKey = utils.toKeyArray(data.privateKey);
|
||||
}
|
||||
|
||||
data.publicKey = data.publicKey || data.pub;
|
||||
if (data.publicKey) {
|
||||
if (data.publicKey.getPublic)
|
||||
data.publicKey = data.privateKey.getPublic(true, 'array');
|
||||
else if (typeof data.publicKey === 'string')
|
||||
data.publicKey = utils.toKeyArray(data.publicKey);
|
||||
}
|
||||
|
||||
if (typeof data.checksum === 'number') {
|
||||
b = [];
|
||||
utils.writeU32BE(b, data.checksum, 0);
|
||||
data.checksum = b;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
HDPriv.prototype._seed = function(seed) {
|
||||
if (seed instanceof HDSeed)
|
||||
seed = seed.seed;
|
||||
|
||||
if (utils.isHex(seed))
|
||||
seed = utils.toArray(seed, 'hex');
|
||||
|
||||
if (seed.length < MIN_ENTROPY || seed.length > MAX_ENTROPY)
|
||||
throw new Error('entropy not in range');
|
||||
|
||||
var hash = sha512hmac(seed, 'Bitcoin seed');
|
||||
|
||||
return {
|
||||
// version: network.prefixes.xprivkey,
|
||||
depth: 0,
|
||||
parentFingerPrint: 0,
|
||||
childIndex: 0,
|
||||
chainCode: hash.slice(32, 64),
|
||||
privateKey: hash.slice(0, 32),
|
||||
checksum: null
|
||||
};
|
||||
};
|
||||
|
||||
HDPriv.prototype._unbuild = function(xkey) {
|
||||
var raw = utils.fromBase58(xkey);
|
||||
var data = {};
|
||||
var off = 0;
|
||||
|
||||
data.version = utils.readU32BE(raw, off);
|
||||
off += 4;
|
||||
data.depth = raw[off];
|
||||
off += 1;
|
||||
data.parentFingerPrint = utils.readU32BE(raw, off);
|
||||
off += 4;
|
||||
data.childIndex = utils.readU32BE(raw, off);
|
||||
off += 4;
|
||||
data.chainCode = raw.slice(off, off + 32);
|
||||
off += data.chainCode.length;
|
||||
off += 1; // nul byte
|
||||
data.privateKey = raw.slice(off, off + 32);
|
||||
off += data.privateKey.length;
|
||||
data.checksum = utils.readU32BE(raw, off);
|
||||
off += 4;
|
||||
|
||||
var hash = utils.dsha256(raw.slice(0, -4));
|
||||
if (data.checksum !== utils.readU32BE(hash, 0))
|
||||
throw new Error('checksum mismatch');
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
HDPriv.prototype._build = function(data) {
|
||||
var sequence = [];
|
||||
var off = 0;
|
||||
|
||||
utils.writeU32BE(sequence, data.version, off);
|
||||
off += 4;
|
||||
sequence[off] = data.depth;
|
||||
off += 1;
|
||||
utils.copy(data.parentFingerPrint, sequence, off, true);
|
||||
off += data.parentFingerPrint.length;
|
||||
utils.writeU32BE(sequence, data.childIndex, off);
|
||||
off += 4;
|
||||
utils.copy(data.chainCode, sequence, off, true);
|
||||
off += data.chainCode.length;
|
||||
sequence[off] = 0;
|
||||
off += 1;
|
||||
utils.copy(data.privateKey, sequence, off, true);
|
||||
off += data.privateKey.length;
|
||||
var checksum = utils.dsha256(sequence).slice(0, 4);
|
||||
utils.copy(checksum, sequence, off, true);
|
||||
off += 4;
|
||||
|
||||
var xprivkey = utils.toBase58(sequence);
|
||||
|
||||
var pair = bcoin.ecdsa.keyPair({ priv: data.privateKey });
|
||||
var privateKey = pair.getPrivate().toArray();
|
||||
var publicKey = pair.getPublic(true, 'array');
|
||||
|
||||
var size = PARENT_FINGER_PRINT_SIZE;
|
||||
var fingerPrint = utils.ripesha(publicKey).slice(0, size);
|
||||
|
||||
this.version = data.version;
|
||||
this.depth = data.depth;
|
||||
this.parentFingerPrint = data.parentFingerPrint;
|
||||
this.childIndex = data.childIndex;
|
||||
this.chainCode = data.chainCode;
|
||||
this.privateKey = privateKey;
|
||||
// this.checksum = checksum;
|
||||
|
||||
this.xprivkey = xprivkey;
|
||||
this.fingerPrint = fingerPrint;
|
||||
this.publicKey = publicKey;
|
||||
|
||||
this.hdpub = new HDPub(this);
|
||||
this.xpubkey = this.hdpub.xpubkey;
|
||||
this.pair = bcoin.ecdsa.keyPair({ priv: this.privateKey });
|
||||
};
|
||||
|
||||
HDPriv.prototype.derive = function(index, hard) {
|
||||
if (typeof index === 'string')
|
||||
return this.deriveString(index);
|
||||
|
||||
hard = index >= HARDENED ? true : hard;
|
||||
if (index < HARDENED && hard === true)
|
||||
index += HARDENED;
|
||||
|
||||
var index_ = [];
|
||||
utils.writeU32BE(index_, index, 0);
|
||||
|
||||
var data = hard
|
||||
? [0].concat(this.privateKey).concat(index_)
|
||||
: data = [].concat(this.publicKey).concat(index_);
|
||||
|
||||
var hash = sha512hmac(data, this.chainCode);
|
||||
var leftPart = new bn(hash.slice(0, 32));
|
||||
var chainCode = hash.slice(32, 64);
|
||||
|
||||
var privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray();
|
||||
|
||||
return new HDPriv({
|
||||
// version: this.version,
|
||||
depth: this.depth + 1,
|
||||
parentFingerPrint: this.fingerPrint,
|
||||
childIndex: index,
|
||||
chainCode: chainCode,
|
||||
privateKey: privateKey
|
||||
});
|
||||
};
|
||||
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
||||
HDPriv._getIndexes = function(path) {
|
||||
var steps = path.split('/');
|
||||
var root = steps.shift();
|
||||
var indexes = [];
|
||||
|
||||
if (~PATH_ROOTS.indexOf(path))
|
||||
return indexes;
|
||||
|
||||
if (!~PATH_ROOTS.indexOf(root))
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < steps.length; i++) {
|
||||
var step = steps[i];
|
||||
var hard = step[step.length - 1] === '\'';
|
||||
|
||||
if (hard)
|
||||
step = step.slice(0, -1);
|
||||
|
||||
if (!step || step[0] === '-')
|
||||
return null;
|
||||
|
||||
var index = +step;
|
||||
if (hard)
|
||||
index += HARDENED;
|
||||
|
||||
indexes.push(index);
|
||||
}
|
||||
|
||||
return indexes;
|
||||
};
|
||||
|
||||
HDPriv.isValidPath = function(path, hard) {
|
||||
if (typeof path === 'string') {
|
||||
var indexes = HDPriv._getIndexes(path);
|
||||
return indexes !== null && indexes.every(HDPriv.isValidPath);
|
||||
}
|
||||
|
||||
if (typeof path === 'number') {
|
||||
if (path < HARDENED && hard === true) {
|
||||
path += HARDENED;
|
||||
}
|
||||
return path >= 0 && path < MAX_INDEX;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
HDPriv.prototype.deriveString = function(path) {
|
||||
if (!HDPriv.isValidPath(path))
|
||||
throw new Error('invalid path');
|
||||
|
||||
var indexes = HDPriv._getIndexes(path);
|
||||
|
||||
return indexes.reduce(function(prev, index) {
|
||||
return prev.derive(index);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* HD Public Key
|
||||
*/
|
||||
|
||||
function HDPub(options) {
|
||||
if (!(this instanceof HDPub))
|
||||
return new HDPriv(options);
|
||||
|
||||
var data;
|
||||
|
||||
if (typeof options === 'string' && options.indexOf('xpub') === 0)
|
||||
options = { xkey: options };
|
||||
|
||||
if (options.xkey)
|
||||
data = this._unbuild(options.xkey);
|
||||
else
|
||||
data = options;
|
||||
|
||||
data = this._normalize(data, network.prefixes.xpubkey);
|
||||
|
||||
this.data = data;
|
||||
|
||||
this._build(data);
|
||||
}
|
||||
|
||||
HDPub.prototype._normalize = HDPriv.prototype._normalize;
|
||||
|
||||
HDPub.prototype._unbuild = function(xkey) {
|
||||
var raw = utils.fromBase58(xkey);
|
||||
var data = {};
|
||||
var off = 0;
|
||||
|
||||
data.version = utils.readU32BE(raw, off);
|
||||
off += 4;
|
||||
data.depth = raw[off];
|
||||
off += 1;
|
||||
data.parentFingerPrint = utils.readU32BE(raw, off);
|
||||
off += 4;
|
||||
data.childIndex = utils.readU32BE(raw, off);
|
||||
off += 4;
|
||||
data.chainCode = raw.slice(off, off + 32);
|
||||
off += data.chainCode.length;
|
||||
data.publicKey = raw.slice(off, off + 33);
|
||||
off += data.publicKey.length;
|
||||
data.checksum = utils.readU32BE(raw, off);
|
||||
off += 4;
|
||||
|
||||
var hash = utils.dsha256(raw.slice(0, -4));
|
||||
if (data.checksum !== utils.readU32BE(hash, 0))
|
||||
throw new Error('checksum mismatch');
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
HDPub.prototype._build = function(data) {
|
||||
var sequence = [];
|
||||
var off = 0;
|
||||
|
||||
utils.writeU32BE(sequence, data.version, off);
|
||||
off += 4;
|
||||
sequence[off] = data.depth;
|
||||
off += 1;
|
||||
utils.copy(data.parentFingerPrint, sequence, off, true);
|
||||
off += data.parentFingerPrint.length;
|
||||
utils.writeU32BE(sequence, data.childIndex, off);
|
||||
off += 4;
|
||||
utils.copy(data.chainCode, sequence, off, true);
|
||||
off += data.chainCode.length;
|
||||
utils.copy(data.publicKey, sequence, off, true);
|
||||
off += data.publicKey.length;
|
||||
var checksum = utils.dsha256(sequence).slice(0, 4);
|
||||
utils.copy(checksum, sequence, off, true);
|
||||
off += 4;
|
||||
|
||||
if (!data.checksum || !data.checksum.length)
|
||||
data.checksum = checksum;
|
||||
else if (utils.toHex(checksum) !== utils.toHex(data.checksum))
|
||||
throw new Error('checksum mismatch');
|
||||
|
||||
var xpubkey = utils.toBase58(sequence);
|
||||
|
||||
var publicKey = data.publicKey;
|
||||
var size = PARENT_FINGER_PRINT_SIZE;
|
||||
var fingerPrint = utils.ripesha(publicKey).slice(0, size);
|
||||
|
||||
this.version = data.version;
|
||||
this.depth = data.depth;
|
||||
this.parentFingerPrint = data.parentFingerPrint;
|
||||
this.childIndex = data.childIndex;
|
||||
this.chainCode = data.chainCode;
|
||||
this.publicKey = publicKey;
|
||||
// this.checksum = checksum;
|
||||
|
||||
this.xpubkey = xpubkey;
|
||||
this.fingerPrint = fingerPrint;
|
||||
|
||||
this.xprivkey = data.xprivkey;
|
||||
this.pair = bcoin.ecdsa.keyPair({ pub: this.publicKey });
|
||||
};
|
||||
|
||||
HDPub.prototype.derive = function(index, hard) {
|
||||
if (typeof index === 'string')
|
||||
return this.deriveString(index);
|
||||
|
||||
if (index >= HARDENED || hard)
|
||||
throw new Error('invalid index');
|
||||
|
||||
if (index < 0)
|
||||
throw new Error('invalid path');
|
||||
|
||||
var index_ = [];
|
||||
utils.writeU32BE(index_, index, 0);
|
||||
|
||||
var data = [].concat(this.publicKey).concat(index_);
|
||||
var hash = sha512hmac(data, this.chainCode);
|
||||
var leftPart = new bn(hash.slice(0, 32));
|
||||
var chainCode = hash.slice(32, 64);
|
||||
|
||||
var pair = bcoin.ecdsa.keyPair({ pub: this.publicKey });
|
||||
var pubkeyPoint = ec.curve.g.mul(leftPart).add(pair.pub);
|
||||
var publicKey = bcoin.ecdsa.keyFromPublic(pubkeyPoint).getPublic(true, 'array');
|
||||
|
||||
return new HDPub({
|
||||
// version: network.prefixes.xpubkey,
|
||||
depth: this.depth + 1,
|
||||
parentFingerPrint: this.fingerPrint,
|
||||
childIndex: index,
|
||||
chainCode: chainCode,
|
||||
publicKey: publicKey
|
||||
});
|
||||
};
|
||||
|
||||
HDPub.isValidPath = function(arg) {
|
||||
if (typeof arg === 'string') {
|
||||
var indexes = HDPriv._getIndexes(arg);
|
||||
return indexes !== null && indexes.every(HDPub.isValidPath);
|
||||
}
|
||||
|
||||
if (typeof arg === 'number')
|
||||
return arg >= 0 && arg < HARDENED;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
HDPub.prototype.deriveString = function(path) {
|
||||
if (~path.indexOf('\''))
|
||||
throw new Error('cannot derive hardened');
|
||||
else if (!HDPub.isValidPath(path))
|
||||
throw new Error('invalid path');
|
||||
|
||||
var indexes = HDPriv._getIndexes(path);
|
||||
|
||||
return indexes.reduce(function(prev, index) {
|
||||
return prev.derive(index);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Make HD keys behave like elliptic KeyPairs
|
||||
*/
|
||||
|
||||
[HDPriv, HDPub].forEach(function(HD) {
|
||||
HD.prototype.validate = function validate() {
|
||||
return this.pair.validate.apply(this.pair, arguments);
|
||||
};
|
||||
|
||||
HD.prototype.getPublic = function getPublic() {
|
||||
return this.pair.getPublic.apply(this.pair, arguments);
|
||||
};
|
||||
|
||||
HD.prototype.getPrivate = function getPrivate() {
|
||||
return this.pair.getPublic.apply(this.pair, arguments);
|
||||
};
|
||||
|
||||
HD.prototype.sign = function sign(msg) {
|
||||
return this.pair.sign.apply(this.pair, arguments);
|
||||
};
|
||||
|
||||
HD.prototype.verify = function verify(msg, signature) {
|
||||
return this.pair.verify.apply(this.pair, arguments);
|
||||
};
|
||||
|
||||
HD.prototype.inspect = function inspect() {
|
||||
return this.pair.inspect.apply(this.pair, arguments);
|
||||
};
|
||||
|
||||
HD.prototype.__defineGetter__('pub', function() {
|
||||
return this.pair.pub;
|
||||
});
|
||||
|
||||
HD.prototype.__defineGetter__('priv', function() {
|
||||
return this.pair.priv;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
var isBrowser = (typeof process !== 'undefined' && process.browser)
|
||||
|| typeof window !== 'undefined';
|
||||
|
||||
function sha512hmac(data, salt) {
|
||||
if (isBrowser) {
|
||||
var hmac = hash.hmac(hash.sha512, utils.toArray(salt));
|
||||
return hmac.update(utils.toArray(data)).digest();
|
||||
}
|
||||
var crypto = require('crypto');
|
||||
var hmac = crypto.createHmac('sha512', new Buffer(salt));
|
||||
var h = hmac.update(new Buffer(data)).digest();
|
||||
return Array.prototype.slice.call(h);
|
||||
}
|
||||
|
||||
function randomBytes(size) {
|
||||
if (isBrowser) {
|
||||
var a = Uint8Array(size);
|
||||
(window.crypto || window.msCrypto).getRandomValues(a);
|
||||
var buf = new Array(size);
|
||||
utils.copy(a, buf, 0);
|
||||
return buf;
|
||||
}
|
||||
var crypto = require('crypto');
|
||||
return Array.prototype.slice.call(crypto.randomBytes(size));
|
||||
}
|
||||
|
||||
/**
|
||||
* PDKBF2
|
||||
* Credit to: https://github.com/stayradiated/pbkdf2-sha512
|
||||
* Copyright (c) 2014, JP Richardson Copyright (c) 2010-2011 Intalio Pte, All Rights Reserved
|
||||
*/
|
||||
|
||||
function pbkdf2(key, salt, iterations, dkLen) {
|
||||
'use strict';
|
||||
|
||||
var hLen = 64;
|
||||
if (dkLen > (Math.pow(2, 32) - 1) * hLen)
|
||||
throw Error('Requested key length too long');
|
||||
|
||||
if (typeof key !== 'string' && typeof key.length !== 'number')
|
||||
throw new TypeError('key must a string or array');
|
||||
|
||||
if (typeof salt !== 'string' && typeof salt.length !== 'number')
|
||||
throw new TypeError('salt must a string or array');
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = utils.toArray(key, null);
|
||||
|
||||
if (typeof salt === 'string')
|
||||
salt = utils.toArray(salt, null);
|
||||
|
||||
var DK = new Array(dkLen);
|
||||
var U = new Array(hLen);
|
||||
var T = new Array(hLen);
|
||||
var block1 = new Array(salt.length + 4);
|
||||
|
||||
var l = Math.ceil(dkLen / hLen);
|
||||
var r = dkLen - (l - 1) * hLen;
|
||||
|
||||
utils.copy(salt.slice(0, salt.length), block1, 0);
|
||||
|
||||
for (var i = 1; i <= l; i++) {
|
||||
block1[salt.length + 0] = (i >> 24 & 0xff);
|
||||
block1[salt.length + 1] = (i >> 16 & 0xff);
|
||||
block1[salt.length + 2] = (i >> 8 & 0xff);
|
||||
block1[salt.length + 3] = (i >> 0 & 0xff);
|
||||
|
||||
U = sha512hmac(block1, key);
|
||||
|
||||
utils.copy(U.slice(0, hLen), T, 0);
|
||||
|
||||
for (var j = 1; j < iterations; j++) {
|
||||
U = sha512hmac(U, key);
|
||||
|
||||
for (var k = 0; k < hLen; k++)
|
||||
T[k] ^= U[k];
|
||||
}
|
||||
|
||||
var destPos = (i - 1) * hLen;
|
||||
var len = (i === l ? r : hLen);
|
||||
utils.copy(T.slice(0, len), DK, 0);
|
||||
}
|
||||
|
||||
return DK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose
|
||||
*/
|
||||
|
||||
hd.seed = HDSeed;
|
||||
hd.priv = HDPriv;
|
||||
hd.pub = HDPub;
|
||||
hd.pbkdf2 = pbkdf2;
|
||||
@ -89,6 +89,17 @@ Peer.prototype._init = function init() {
|
||||
self._error(err);
|
||||
});
|
||||
|
||||
if (this.pool.options.fullNode) {
|
||||
this.once('version', function() {
|
||||
var ip = self.socket && self.socket.remoteAddress || '0.0.0.0';
|
||||
self.pool.emit('debug',
|
||||
'Sent version (%s): height=%s',
|
||||
ip, this.pool.chain.getStartHeight());
|
||||
self.pool.emit('debug', 'version (%s): sending locator hashes', ip);
|
||||
self.loadHeaders(self.chain.locatorHashes(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
this._ping.timer = setInterval(function() {
|
||||
self._write(self.framer.ping([
|
||||
0xde, 0xad, 0xbe, 0xef,
|
||||
@ -98,9 +109,7 @@ Peer.prototype._init = function init() {
|
||||
|
||||
// Send hello
|
||||
this._write(this.framer.version({
|
||||
height: this.options.startHeight != null
|
||||
? this.options.startHeight
|
||||
: 0,
|
||||
height: this.pool.chain.getStartHeight(),
|
||||
relay: this.options.relay
|
||||
}));
|
||||
|
||||
@ -167,18 +176,8 @@ Peer.prototype.broadcast = function broadcast(items) {
|
||||
};
|
||||
|
||||
Peer.prototype.updateWatch = function updateWatch() {
|
||||
if (!this.pool.options.relay) {
|
||||
if (this.ack) {
|
||||
var self = this;
|
||||
if (this.pool.block.lastHash)
|
||||
this.loadBlocks([ self.pool.block.lastHash ], 0);
|
||||
else
|
||||
this.pool.chain.getLast(function(hash) {
|
||||
self.loadBlocks([ hash ], 0);
|
||||
});
|
||||
}
|
||||
if (this.pool.options.fullNode)
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.ack)
|
||||
this._write(this.framer.filterLoad(this.bloom, 'none'));
|
||||
@ -292,6 +291,8 @@ Peer.prototype._onPacket = function onPacket(packet) {
|
||||
return this._handleVersion(payload);
|
||||
else if (cmd === 'inv')
|
||||
return this._handleInv(payload);
|
||||
else if (cmd === 'headers')
|
||||
return this._handleHeaders(payload);
|
||||
else if (cmd === 'getdata')
|
||||
return this._handleGetData(payload);
|
||||
else if (cmd === 'addr')
|
||||
@ -437,15 +438,6 @@ Peer.prototype._handleInv = function handleInv(items) {
|
||||
});
|
||||
this.emit('blocks', blocks);
|
||||
|
||||
if (!this.pool.options.relay) {
|
||||
if (txs.length)
|
||||
this.emit('txs', txs.map(function(tx) {
|
||||
return tx.hash;
|
||||
}));
|
||||
this.getData(items);
|
||||
return;
|
||||
}
|
||||
|
||||
if (txs.length === 0)
|
||||
return;
|
||||
|
||||
@ -455,6 +447,38 @@ Peer.prototype._handleInv = function handleInv(items) {
|
||||
this.getData(txs);
|
||||
};
|
||||
|
||||
Peer.prototype._handleHeaders = function handleHeaders(headers) {
|
||||
var self = this;
|
||||
|
||||
headers = headers.map(function(header) {
|
||||
header.prevBlock = utils.toHex(header.prevBlock);
|
||||
header.merkleRoot = utils.toHex(header.merkleRoot);
|
||||
header.hash = utils.toHex(utils.dsha256(header._raw));
|
||||
return header;
|
||||
});
|
||||
|
||||
if (this.pool.options.fullNode) {
|
||||
for (var i = 0; i < headers.length; i++) {
|
||||
var header = headers[i];
|
||||
var hash = header.hash;
|
||||
// if (this.chain.orphan.bmap[hash]) {
|
||||
if (this.chain.orphan.map[header.prevBlock]) {
|
||||
this.loadHeaders(this.chain.locatorHashes(), this.chain.getOrphanRoot(hash));
|
||||
continue;
|
||||
}
|
||||
if (!this.chain.index.bloom.test(hash, 'hex') || i === headers.length - 1)
|
||||
this.getData([{ type: 'block', hash: hash }]);
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('headers', headers);
|
||||
};
|
||||
|
||||
|
||||
Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) {
|
||||
this._write(this.framer.getHeaders(hashes, stop));
|
||||
};
|
||||
|
||||
Peer.prototype.loadBlocks = function loadBlocks(hashes, stop) {
|
||||
this._write(this.framer.getBlocks(hashes, stop));
|
||||
};
|
||||
|
||||
@ -7,13 +7,18 @@ var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
|
||||
function Pool(options) {
|
||||
var self = this;
|
||||
|
||||
if (!(this instanceof Pool))
|
||||
return new Pool(options);
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.options = options || {};
|
||||
this.options.relay = this.options.relay !== false;
|
||||
this.options.fullNode = !!this.options.fullNode;
|
||||
this.options.relay == null
|
||||
? (this.options.fullNode ? false : true)
|
||||
: this.options.relay;
|
||||
this.storage = this.options.storage;
|
||||
this.destroyed = false;
|
||||
this.size = options.size || 32;
|
||||
@ -40,7 +45,9 @@ function Pool(options) {
|
||||
storage: this.storage,
|
||||
// Since regular blocks contain transactions and full merkle
|
||||
// trees, it's risky to cache 2000 blocks. Let's do 100.
|
||||
cacheLimit: !this.options.relay ? 100 : null
|
||||
cacheLimit: this.options.fullNode ? 100 : null,
|
||||
fullNode: this.options.fullNode,
|
||||
startHeight: this.options.startHeight
|
||||
});
|
||||
this.watchMap = {};
|
||||
this.bloom = new bcoin.bloom(8 * 1024,
|
||||
@ -85,19 +92,32 @@ function Pool(options) {
|
||||
this.createConnection = options.createConnection;
|
||||
assert(this.createConnection);
|
||||
|
||||
this._init();
|
||||
this.chain.on('debug', function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
self.emit.apply(self, ['debug'].concat(args));
|
||||
});
|
||||
|
||||
if (!this.chain.loading) {
|
||||
this._init();
|
||||
} else {
|
||||
this.chain.once('load', function() {
|
||||
self._init();
|
||||
});
|
||||
}
|
||||
}
|
||||
inherits(Pool, EventEmitter);
|
||||
module.exports = Pool;
|
||||
|
||||
Pool.prototype._init = function _init() {
|
||||
var self = this;
|
||||
|
||||
this._addLoader();
|
||||
for (var i = 0; i < this.size; i++)
|
||||
this._addPeer(0);
|
||||
this._load();
|
||||
|
||||
var self = this;
|
||||
this.chain.on('missing', function(hash, preload, parent) {
|
||||
if (self.options.fullNode) return;
|
||||
self._request('block', hash, { force: true });
|
||||
self._scheduleRequests();
|
||||
self._loadRange(preload);
|
||||
@ -142,6 +162,8 @@ Pool.prototype._addLoader = function _addLoader() {
|
||||
clearTimeout(timer);
|
||||
});
|
||||
|
||||
if (this.options.fullNode) return;
|
||||
|
||||
function destroy() {
|
||||
// Chain is full and up-to-date
|
||||
if (self.chain.isFull()) {
|
||||
@ -198,6 +220,8 @@ Pool.prototype.isFull = function isFull() {
|
||||
};
|
||||
|
||||
Pool.prototype._loadRange = function _loadRange(hashes, force) {
|
||||
if (this.options.fullNode) return;
|
||||
|
||||
if (!hashes)
|
||||
return;
|
||||
|
||||
@ -220,6 +244,8 @@ Pool.prototype._loadRange = function _loadRange(hashes, force) {
|
||||
};
|
||||
|
||||
Pool.prototype._load = function _load() {
|
||||
if (this.options.fullNode) return;
|
||||
|
||||
if (this.request.queue.length >= this.load.hwm) {
|
||||
this.load.hiReached = true;
|
||||
return false;
|
||||
@ -295,19 +321,38 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
|
||||
self._scheduleRequests();
|
||||
});
|
||||
|
||||
peer.on('merkleblock', function(block) {
|
||||
// Reset backoff, peer seems to be responsive
|
||||
backoff = 0;
|
||||
if (!this.options.fullNode) {
|
||||
peer.on('merkleblock', function(block) {
|
||||
// Reset backoff, peer seems to be responsive
|
||||
backoff = 0;
|
||||
|
||||
self._response(block);
|
||||
self.chain.add(block);
|
||||
self.emit('chain-progress', self.chain.fillPercent(), peer);
|
||||
self.emit('block', block, peer);
|
||||
});
|
||||
|
||||
if (!this.options.relay) {
|
||||
self._response(block);
|
||||
self.chain.add(block);
|
||||
self.emit('chain-progress', self.chain.fillPercent(), peer);
|
||||
self.emit('block', block, peer);
|
||||
});
|
||||
} else {
|
||||
peer.on('block', function(block) {
|
||||
peer.emit('merkleblock', block);
|
||||
backoff = 0;
|
||||
|
||||
var height = self.chain.index.hashes.length;
|
||||
var hash = block.hash('hex');
|
||||
|
||||
if (!self.chain.index.bloom.test(block.prevBlock, 'hex')) {
|
||||
peer.loadHeaders(self.chain.locatorHashes(), self.chain.getOrphanRoot(block));
|
||||
}
|
||||
|
||||
self._response(block);
|
||||
self.chain.add(block);
|
||||
|
||||
// if (!self.chain.index.bloom.test(block.prevBlock, 'hex')) {
|
||||
// peer.loadHeaders(self.chain.locatorHashes(), self.chain.getOrphanRoot(block));
|
||||
// } else if (self.chain.index.hashes.length === height) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
self.emit('chain-progress', self.chain.fillPercent(), peer);
|
||||
self.emit('block', block, peer);
|
||||
});
|
||||
}
|
||||
|
||||
@ -365,8 +410,11 @@ Pool.prototype._removePeer = function _removePeer(peer) {
|
||||
|
||||
Pool.prototype.watch = function watch(id) {
|
||||
if (id instanceof bcoin.wallet) {
|
||||
this.watch(id.getAddress());
|
||||
this.watch(id.getPublicKey());
|
||||
// this.watch(id.getAddress());
|
||||
this.watch(id.getFullHash());
|
||||
this.watch(id.getFullPublicKey());
|
||||
this.watch(id.getOwnHash());
|
||||
this.watch(id.getOwnPublicKey());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -415,8 +463,10 @@ Pool.prototype.unwatch = function unwatch(id) {
|
||||
Pool.prototype.addWallet = function addWallet(w, defaultTs) {
|
||||
if (this.wallets.indexOf(w) !== -1)
|
||||
return false;
|
||||
this.watch(w.getHash());
|
||||
this.watch(w.getPublicKey());
|
||||
this.watch(w.getFullHash());
|
||||
this.watch(w.getFullPublicKey());
|
||||
this.watch(w.getOwnHash());
|
||||
this.watch(w.getOwnPublicKey());
|
||||
|
||||
var self = this;
|
||||
var e = new EventEmitter();
|
||||
@ -447,8 +497,10 @@ Pool.prototype.removeWallet = function removeWallet(w) {
|
||||
if (i == -1)
|
||||
return;
|
||||
this.wallets.splice(i, 1);
|
||||
this.unwatch(w.getHash());
|
||||
this.unwatch(w.getPublicKey());
|
||||
this.unwatch(w.getFullHash());
|
||||
this.unwatch(w.getFullPublicKey());
|
||||
this.unwatch(w.getOwnHash());
|
||||
this.unwatch(w.getOwnPublicKey());
|
||||
}
|
||||
|
||||
Pool.prototype.search = function search(id, range, e) {
|
||||
|
||||
@ -3,21 +3,6 @@ var utils = bcoin.utils;
|
||||
|
||||
exports.minVersion = 70001;
|
||||
exports.version = 70002;
|
||||
exports.magic = 0xd9b4bef9;
|
||||
exports.genesis = {
|
||||
version: 1,
|
||||
prevBlock: [ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0 ],
|
||||
merkleRoot: utils.toArray(
|
||||
'4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
|
||||
'hex'
|
||||
).reverse(),
|
||||
ts: 1231006505,
|
||||
bits: 0x1d00ffff,
|
||||
nonce: 2083236893
|
||||
};
|
||||
|
||||
// version - services field
|
||||
exports.services = {
|
||||
@ -49,9 +34,9 @@ exports.opcodes = {
|
||||
pushdata1: 0x4c,
|
||||
pushdata2: 0x4d,
|
||||
pushdata4: 0x4e,
|
||||
negate1: 0x4f,
|
||||
// negate1: 0x4f,
|
||||
|
||||
nop: 0x61,
|
||||
nop1: 0x61,
|
||||
if_: 0x63,
|
||||
notif: 0x64,
|
||||
else_: 0x67,
|
||||
@ -129,12 +114,17 @@ exports.opcodes = {
|
||||
checksig: 0xac,
|
||||
checksigverify: 0xad,
|
||||
checkmultisig: 0xae,
|
||||
checkmultisigverify: 0xaf
|
||||
checkmultisigverify: 0xaf,
|
||||
checklocktimeverify: 0xb1
|
||||
};
|
||||
|
||||
exports.opcodes['-1'] = 0x50 + -1;
|
||||
for (var i = 1; i <= 16; i++)
|
||||
exports.opcodes[i] = 0x50 + i;
|
||||
|
||||
for (var i = 0; i <= 7; i++)
|
||||
exports.opcodes['nop' + (i + 3)] = 0xb2 + i;
|
||||
|
||||
exports.opcodesByVal = new Array(256);
|
||||
Object.keys(exports.opcodes).forEach(function(name) {
|
||||
exports.opcodesByVal[exports.opcodes[name]] = name;
|
||||
@ -148,8 +138,15 @@ exports.hashType = {
|
||||
anyonecaypay: 0x80
|
||||
};
|
||||
|
||||
exports.rhashType = Object.keys(exports.hashType).reduce(function(out, type) {
|
||||
out[exports.hashType[type]] = type;
|
||||
return out;
|
||||
}, {});
|
||||
|
||||
exports.block = {
|
||||
maxSize: 1000000,
|
||||
maxSigops: 1000000 / 50,
|
||||
maxOrphanTx: 1000000 / 100
|
||||
};
|
||||
|
||||
exports.locktimeThreshold = 500000000; // Tue Nov 5 00:53:20 1985 UTC
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
var bcoin = require('../../bcoin');
|
||||
var network = require('./network');
|
||||
var constants = require('./constants');
|
||||
var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
@ -27,7 +28,7 @@ Framer.prototype.header = function header(cmd, payload) {
|
||||
var h = new Array(24);
|
||||
|
||||
// Magic value
|
||||
writeU32(h, constants.magic, 0);
|
||||
writeU32(h, network.magic, 0);
|
||||
|
||||
// Command
|
||||
var len = writeAscii(h, cmd, 4);
|
||||
@ -201,7 +202,15 @@ Framer.prototype.filterClear = function filterClear() {
|
||||
return this.packet('filterclear', []);
|
||||
};
|
||||
|
||||
Framer.prototype.getHeaders = function getHeaders(hashes, stop) {
|
||||
return this._getBlocks('getheaders', hashes, stop);
|
||||
};
|
||||
|
||||
Framer.prototype.getBlocks = function getBlocks(hashes, stop) {
|
||||
return this._getBlocks('getblocks', hashes, stop);
|
||||
};
|
||||
|
||||
Framer.prototype._getBlocks = function _getBlocks(cmd, hashes, stop) {
|
||||
var p = [];
|
||||
writeU32(p, constants.version, 0);
|
||||
var off = 4 + varint(p, hashes.length, 4);
|
||||
@ -227,7 +236,7 @@ Framer.prototype.getBlocks = function getBlocks(hashes, stop) {
|
||||
p[off + len] = 0;
|
||||
assert.equal(off + len, p.length);
|
||||
|
||||
return this.packet('getblocks', p);
|
||||
return this.packet(cmd, p);
|
||||
};
|
||||
|
||||
Framer.tx = function tx(tx) {
|
||||
|
||||
@ -3,4 +3,4 @@ var protocol = exports;
|
||||
protocol.constants = require('./constants');
|
||||
protocol.framer = require('./framer');
|
||||
protocol.parser = require('./parser');
|
||||
protocol.preload = require('./preload');
|
||||
protocol.network = require('./network');
|
||||
|
||||
178
lib/bcoin/protocol/network.js
Normal file
178
lib/bcoin/protocol/network.js
Normal file
@ -0,0 +1,178 @@
|
||||
var bcoin = require('../../bcoin');
|
||||
var utils = bcoin.utils;
|
||||
|
||||
/**
|
||||
* Network
|
||||
*/
|
||||
|
||||
var network = exports;
|
||||
|
||||
network.set = function(type) {
|
||||
var net = network[type];
|
||||
utils.merge(network, net);
|
||||
};
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
|
||||
var main = network.main = {};
|
||||
|
||||
main.prefixes = {
|
||||
pubkey: 0,
|
||||
script: 5,
|
||||
privkey: 128,
|
||||
xpubkey: 0x0488b21e,
|
||||
xprivkey: 0x0488ade4
|
||||
};
|
||||
|
||||
utils.merge(main.prefixes, {
|
||||
normal: main.prefixes.pubkey,
|
||||
p2pkh: main.prefixes.pubkey,
|
||||
multisig: main.prefixes.pubkey,
|
||||
p2sh: main.prefixes.script
|
||||
});
|
||||
|
||||
main.type = 'main';
|
||||
|
||||
main.seeds = [
|
||||
'seed.bitcoin.sipa.be', // Pieter Wuille
|
||||
'dnsseed.bluematt.me', // Matt Corallo
|
||||
'dnsseed.bitcoin.dashjr.org', // Luke Dashjr
|
||||
'seed.bitcoinstats.com', // Christian Decker
|
||||
'bitseed.xf2.org', // Jeff Garzik
|
||||
'seed.bitcoin.jonasschnelli.ch' // Jonas Schnelli
|
||||
];
|
||||
|
||||
main.port = 8333;
|
||||
|
||||
main.alertKey = utils.toArray(''
|
||||
+ '04fc9702847840aaf195de8442ebecedf5b095c'
|
||||
+ 'dbb9bc716bda9110971b28a49e0ead8564ff0db'
|
||||
+ '22209e0374782c093bb899692d524e9d6a6956e'
|
||||
+ '7c5ecbcd68284',
|
||||
'hex');
|
||||
|
||||
main.checkpoints = [
|
||||
{ height: 11111, hash: '0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d' },
|
||||
{ height: 33333, hash: '000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6' },
|
||||
{ height: 74000, hash: '0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20' },
|
||||
{ height: 105000, hash: '00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97' },
|
||||
{ height: 134444, hash: '00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe' },
|
||||
{ height: 168000, hash: '000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763' },
|
||||
{ height: 193000, hash: '000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317' },
|
||||
{ height: 210000, hash: '000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e' },
|
||||
{ height: 216116, hash: '00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e' },
|
||||
{ height: 225430, hash: '00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932' },
|
||||
{ height: 250000, hash: '000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214' },
|
||||
{ height: 279000, hash: '0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40' },
|
||||
{ height: 295000, hash: '00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983' }
|
||||
];
|
||||
|
||||
main.checkpoints.tsLastCheckpoint = 1397080064;
|
||||
main.checkpoints.txsLastCheckpoint = 36544669;
|
||||
main.checkpoints.txsPerDay = 60000.0;
|
||||
|
||||
// http://blockexplorer.com/b/0
|
||||
// http://blockexplorer.com/rawblock/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
|
||||
main.genesis = {
|
||||
version: 1,
|
||||
_hash: utils.toArray(
|
||||
'000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
||||
'hex'
|
||||
).reverse(),
|
||||
prevBlock: [ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0 ],
|
||||
merkleRoot: utils.toArray(
|
||||
'4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
|
||||
'hex'
|
||||
).reverse(),
|
||||
ts: 1231006505,
|
||||
bits: 0x1d00ffff,
|
||||
nonce: 2083236893
|
||||
};
|
||||
|
||||
main.magic = 0xd9b4bef9;
|
||||
|
||||
main.preload = require('./preload');
|
||||
|
||||
/**
|
||||
* Testnet (v3)
|
||||
* https://en.bitcoin.it/wiki/Testnet
|
||||
*/
|
||||
|
||||
var testnet = network.testnet = {};
|
||||
|
||||
testnet.type = 'testnet';
|
||||
|
||||
testnet.prefixes = {
|
||||
pubkey: 111,
|
||||
script: 196,
|
||||
privkey: 239,
|
||||
xpubkey: 0x043587cf,
|
||||
xprivkey: 0x04358394
|
||||
};
|
||||
|
||||
utils.merge(testnet.prefixes, {
|
||||
normal: testnet.prefixes.pubkey,
|
||||
p2pkh: testnet.prefixes.pubkey,
|
||||
multisig: testnet.prefixes.pubkey,
|
||||
p2sh: testnet.prefixes.script
|
||||
});
|
||||
|
||||
testnet.seeds = [
|
||||
'testnet-seed.alexykot.me',
|
||||
'testnet-seed.bitcoin.petertodd.org',
|
||||
'testnet-seed.bluematt.me',
|
||||
'testnet-seed.bitcoin.schildbach.de'
|
||||
];
|
||||
|
||||
testnet.port = 18333;
|
||||
|
||||
testnet.alertKey = utils.toArray(''
|
||||
+ '04302390343f91cc401d56d68b123028bf52e5f'
|
||||
+ 'ca1939df127f63c6467cdf9c8e2c14b61104cf8'
|
||||
+ '17d0b780da337893ecc4aaff1309e536162dabb'
|
||||
+ 'db45200ca2b0a',
|
||||
'hex');
|
||||
|
||||
testnet.checkpoints = [
|
||||
{ height: 546, hash: '000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70' }
|
||||
];
|
||||
|
||||
testnet.checkpoints.tsLastCheckpoint = 1338180505;
|
||||
testnet.checkpoints.txsLastCheckpoint = 16341;
|
||||
testnet.checkpoints.txsPerDay = 300;
|
||||
|
||||
// http://blockexplorer.com/testnet/b/0
|
||||
// http://blockexplorer.com/testnet/rawblock/000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943
|
||||
testnet.genesis = {
|
||||
version: 1,
|
||||
_hash: utils.toArray(
|
||||
'000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943',
|
||||
'hex'
|
||||
).reverse(),
|
||||
prevBlock: [ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0 ],
|
||||
merkleRoot: utils.toArray(
|
||||
'4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
|
||||
'hex'
|
||||
).reverse(),
|
||||
ts: 1296688602,
|
||||
bits: 0x1d00ffff,
|
||||
nonce: 414098458
|
||||
};
|
||||
|
||||
testnet.magic = 0x0709110b;
|
||||
|
||||
testnet.preload = {
|
||||
'v': 1,
|
||||
'type': 'chain',
|
||||
'hashes': [utils.toHex(testnet.genesis._hash)],
|
||||
'ts': [testnet.genesis.ts],
|
||||
'heights': [0]
|
||||
};
|
||||
@ -6,6 +6,7 @@ var bcoin = require('../../bcoin');
|
||||
var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
var constants = require('./constants');
|
||||
var network = require('./network');
|
||||
|
||||
var readU32 = utils.readU32;
|
||||
var readU64 = utils.readU64;
|
||||
@ -71,7 +72,7 @@ Parser.prototype.parse = function parse(chunk) {
|
||||
|
||||
Parser.prototype.parseHeader = function parseHeader(h) {
|
||||
var magic = readU32(h, 0);
|
||||
if (magic !== constants.magic) {
|
||||
if (magic !== network.magic) {
|
||||
return this._error('Invalid magic value: ' + magic.toString(16));
|
||||
}
|
||||
|
||||
@ -97,6 +98,8 @@ Parser.prototype.parsePayload = function parsePayload(cmd, p) {
|
||||
return this.parseInvList(p);
|
||||
else if (cmd === 'merkleblock')
|
||||
return this.parseMerkleBlock(p);
|
||||
else if (cmd === 'headers')
|
||||
return this.parseHeaders(p);
|
||||
else if (cmd === 'block')
|
||||
return this.parseBlock(p);
|
||||
else if (cmd === 'tx')
|
||||
@ -135,10 +138,6 @@ Parser.prototype.parseVersion = function parseVersion(p) {
|
||||
// Relay
|
||||
var relay = p.length > off ? p[off] === 1 : true;
|
||||
|
||||
// NOTE: Could just do this to make relay
|
||||
// `false` when it's not included:
|
||||
// var relay = p[off] === 1;
|
||||
|
||||
return {
|
||||
v: v,
|
||||
services: services,
|
||||
@ -226,6 +225,43 @@ Parser.prototype.parseMerkleBlock = function parseMerkleBlock(p) {
|
||||
};
|
||||
};
|
||||
|
||||
Parser.prototype.parseHeaders = function parseHeaders(p) {
|
||||
if (p.length < 81)
|
||||
return this._error('Invalid headers size');
|
||||
|
||||
var result = readIntv(p, 0);
|
||||
var off = result.off;
|
||||
var count = result.r;
|
||||
|
||||
var headers = [];
|
||||
|
||||
if (p.length >= off + 81) {
|
||||
for (var i = 0; i < count && off + 81 < p.length; i++) {
|
||||
var header = {};
|
||||
var start = off;
|
||||
header.version = readU32(p, off);
|
||||
off += 4;
|
||||
header.prevBlock = p.slice(off, off + 32);
|
||||
off += 32;
|
||||
header.merkleRoot = p.slice(off, off + 32);
|
||||
off += 32;
|
||||
header.ts = readU32(p, off);
|
||||
off += 4;
|
||||
header.bits = readU32(p, off);
|
||||
off += 4;
|
||||
header.nonce = readU32(p, off);
|
||||
off += 4;
|
||||
var r = readIntv(p, off);
|
||||
header.totalTX = r.r;
|
||||
off = r.off;
|
||||
header._raw = p.slice(start, start + 80);
|
||||
headers.push(header);
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
Parser.prototype.parseBlock = function parseBlock(p) {
|
||||
if (p.length < 81)
|
||||
return this._error('Invalid block size');
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
var bcoin = require('../bcoin');
|
||||
var bn = require('bn.js');
|
||||
var constants = bcoin.protocol.constants;
|
||||
var utils = bcoin.utils;
|
||||
var assert = bcoin.utils.assert;
|
||||
var script = exports;
|
||||
|
||||
script.decode = function decode(s) {
|
||||
@ -23,8 +25,8 @@ script.decode = function decode(s) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Raw number
|
||||
if (b >= 0x51 && b <= 0x60) {
|
||||
// Raw number (-1 and 1-16)
|
||||
if (b === 0x4f || (b >= 0x51 && b <= 0x60)) {
|
||||
opcodes.push(b - 0x50);
|
||||
continue;
|
||||
}
|
||||
@ -62,6 +64,7 @@ script.encode = function encode(s) {
|
||||
// Push value to stack
|
||||
if (Array.isArray(instr)) {
|
||||
if (instr.length === 0) {
|
||||
// OP_FALSE
|
||||
res.push(0);
|
||||
} else if (1 <= instr.length && instr.length <= 0x4b) {
|
||||
res = res.concat(instr.length, instr);
|
||||
@ -109,113 +112,635 @@ script.subscript = function subscript(s) {
|
||||
return res;
|
||||
};
|
||||
|
||||
script.execute = function execute(s, stack, tx) {
|
||||
script.verify = function verify(hash, sig, pub) {
|
||||
var k = bcoin.ecdsa.keyFromPublic(pub);
|
||||
|
||||
// Points at Infinity make verify() throw.
|
||||
// This specifically throws on wallet-test.js
|
||||
// where [1] is concatted to the pubkey.
|
||||
if (k.getPublic().isInfinity())
|
||||
return false;
|
||||
|
||||
// Use a try catch in case there are
|
||||
// any uncaught errors for bad inputs in verify().
|
||||
try {
|
||||
return bcoin.ecdsa.verify(hash, sig, pub);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
script._next = function(to, s, pc) {
|
||||
var depth = 0;
|
||||
while (s[pc]) {
|
||||
var o = s[pc];
|
||||
if (o === 'if_' || o === 'notif')
|
||||
depth++;
|
||||
else if (o === 'else_')
|
||||
depth--;
|
||||
else if (o === 'endif')
|
||||
depth--;
|
||||
if (depth < 0)
|
||||
break;
|
||||
if (depth === 0 && o === to)
|
||||
return pc;
|
||||
if (o === 'else_')
|
||||
depth++;
|
||||
pc++;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
script.execute = function execute(s, stack, tx, index) {
|
||||
s = s.slice();
|
||||
|
||||
if (s.length > 10000)
|
||||
return false;
|
||||
|
||||
var lastSep = -1;
|
||||
|
||||
stack.alt = stack.alt || [];
|
||||
|
||||
for (var pc = 0; pc < s.length; pc++) {
|
||||
var o = s[pc];
|
||||
|
||||
if (Array.isArray(o)) {
|
||||
stack.push(o);
|
||||
} else if (o === 'dup') {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
stack.push(stack[stack.length - 1]);
|
||||
} else if (o === 'hash160') {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
if (o === -1 || (o >= 1 && o <= 16)) {
|
||||
stack.push([o]);
|
||||
continue;
|
||||
}
|
||||
|
||||
stack.push(utils.ripesha(stack.pop()));
|
||||
} else if (o === 'eqverify' || o === 'eq') {
|
||||
if (stack.length < 2)
|
||||
return false;
|
||||
|
||||
var res = utils.isEqual(stack.pop(), stack.pop());
|
||||
if (o === 'eqverify') {
|
||||
if (!res)
|
||||
return false;
|
||||
} else {
|
||||
stack.push(res ? [ 1 ] : []);
|
||||
switch (o) {
|
||||
case 'nop1':
|
||||
case 'nop3':
|
||||
case 'nop4':
|
||||
case 'nop5':
|
||||
case 'nop6':
|
||||
case 'nop7':
|
||||
case 'nop8':
|
||||
case 'nop9':
|
||||
case 'nop10': {
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (o === 'checksigverify' || o === 'checksig') {
|
||||
if (!tx || stack.length < 2)
|
||||
return false;
|
||||
|
||||
var pub = stack.pop();
|
||||
var sig = stack.pop();
|
||||
var type = sig[sig.length - 1];
|
||||
if (type !== 1)
|
||||
return false;
|
||||
|
||||
var res = bcoin.ecdsa.verify(tx, sig.slice(0, -1), pub);
|
||||
if (o === 'checksigverify') {
|
||||
if (!res)
|
||||
case 'if_':
|
||||
case 'notif': {
|
||||
var val = false;
|
||||
if (stack.length < 1)
|
||||
return false;
|
||||
} else {
|
||||
stack.push(res ? [ 1 ] : []);
|
||||
var v = stack.pop();
|
||||
val = new bn(v).cmpn(0) !== 0;
|
||||
if (o === 'notif')
|
||||
val = !val;
|
||||
var if_ = pc;
|
||||
var else_ = script._next('else_', s, pc);
|
||||
var endif = script._next('endif', s, pc);
|
||||
// Splice out the statement blocks we don't need
|
||||
if (val) {
|
||||
if (endif === -1)
|
||||
return false;
|
||||
if (else_ === -1) {
|
||||
s.splice(endif, 1);
|
||||
s.splice(if_, 1);
|
||||
} else {
|
||||
s.splice(else_, (endif - else_) + 1);
|
||||
s.splice(if_, 1);
|
||||
}
|
||||
} else {
|
||||
if (endif === -1)
|
||||
return false;
|
||||
if (else_ === -1) {
|
||||
s.splice(if_, (endif - if_) + 1);
|
||||
} else {
|
||||
s.splice(endif, 1);
|
||||
s.splice(if_, (else_ - if_) + 1);
|
||||
}
|
||||
}
|
||||
// Subtract one since we removed the if/notif opcode
|
||||
pc--;
|
||||
break;
|
||||
}
|
||||
} else if (o === 'checkmultisigverify' || o === 'checkmultisig') {
|
||||
if (!tx || stack.length < 3)
|
||||
case 'else_': {
|
||||
return false;
|
||||
|
||||
var n = stack.pop();
|
||||
if (n.length !== 1 || !(1 <= n[0] && n[0] <= 3))
|
||||
}
|
||||
case 'endif': {
|
||||
return false;
|
||||
n = n[0];
|
||||
|
||||
if (stack.length < n + 1)
|
||||
}
|
||||
case 'verify': {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
if (new bn(stack[stack.length - 1]).cmpn(0) === 0)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case 'ret': {
|
||||
return false;
|
||||
}
|
||||
case 'toaltstack': {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
stack.alt.push(stack.pop());
|
||||
break;
|
||||
}
|
||||
case 'fromaltstack': {
|
||||
if (stack.alt.length === 0)
|
||||
return false;
|
||||
stack.push(stack.alt.pop());
|
||||
break;
|
||||
}
|
||||
case 'ifdup': {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
if (new bn(stack[stack.length - 1]).cmpn(0) !== 0)
|
||||
stack.push(new bn(stack[stack.length - 1]).toArray());
|
||||
break;
|
||||
}
|
||||
case 'depth': {
|
||||
stack.push(new bn(stack.length).toArray());
|
||||
break;
|
||||
}
|
||||
case 'drop': {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
stack.pop();
|
||||
break;
|
||||
}
|
||||
case 'dup': {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
stack.push(stack[stack.length - 1]);
|
||||
break;
|
||||
}
|
||||
case 'nip': {
|
||||
if (stack.length < 2)
|
||||
return false;
|
||||
stack.splice(stack.length - 2, 1);
|
||||
break;
|
||||
}
|
||||
case 'over': {
|
||||
if (stack.length < 2)
|
||||
return false;
|
||||
stack.push(stack[stack.length - 2]);
|
||||
break;
|
||||
}
|
||||
case 'pick':
|
||||
case 'roll': {
|
||||
if (stack.length < 2)
|
||||
return false;
|
||||
var n = new bn(stack.pop()).toNumber();
|
||||
if (n < 0 || n >= stack.length)
|
||||
return false;
|
||||
var v = stack[-n - 1];
|
||||
if (o === 'roll')
|
||||
stack.splice(stack.length - n - 1, 1);
|
||||
stack.push(v);
|
||||
break;
|
||||
}
|
||||
case 'rot': {
|
||||
if (stack.length < 3)
|
||||
return false;
|
||||
var v3 = stack[stack.length - 3];
|
||||
var v2 = stack[stack.length - 2];
|
||||
var v1 = stack[stack.length - 1];
|
||||
stack[stack.length - 3] = v2;
|
||||
stack[stack.length - 2] = v3;
|
||||
v2 = stack[stack.length - 2];
|
||||
stack[stack.length - 2] = v1;
|
||||
stack[stack.length - 1] = v2;
|
||||
break;
|
||||
}
|
||||
case 'swap': {
|
||||
if (stack.length < 2)
|
||||
return false;
|
||||
var v2 = stack[stack.length - 2];
|
||||
var v1 = stack[stack.length - 1];
|
||||
stack[stack.length - 2] = v1;
|
||||
stack[stack.length - 1] = v2;
|
||||
break;
|
||||
}
|
||||
case 'tuck': {
|
||||
if (stack.length < 2)
|
||||
return false;
|
||||
stack.splice(stack.length - 2, 0, stack[stack.length - 1]);
|
||||
break;
|
||||
}
|
||||
case 'drop2': {
|
||||
if (stack.length < 2)
|
||||
return false;
|
||||
stack.pop();
|
||||
stack.pop();
|
||||
break;
|
||||
}
|
||||
case 'dup2': {
|
||||
if (stack.length < 2)
|
||||
return false;
|
||||
var v1 = stack[stack.length - 1];
|
||||
var v2 = stack[stack.length - 2];
|
||||
stack.push(v1);
|
||||
stack.push(v2);
|
||||
break;
|
||||
}
|
||||
case 'dup3': {
|
||||
if (stack.length < 3)
|
||||
return false;
|
||||
var v1 = stack[stack.length - 1];
|
||||
var v2 = stack[stack.length - 2];
|
||||
var v3 = stack[stack.length - 3];
|
||||
stack.push(v1);
|
||||
stack.push(v2);
|
||||
stack.push(v3);
|
||||
break;
|
||||
}
|
||||
case 'over2': {
|
||||
if (stack.length < 4)
|
||||
return false;
|
||||
var v1 = stack[stack.length - 4];
|
||||
var v2 = stack[stack.length - 3];
|
||||
stack.push(v1);
|
||||
stack.push(v2);
|
||||
break;
|
||||
}
|
||||
case 'rot2': {
|
||||
if (stack.length < 6)
|
||||
return false;
|
||||
var v1 = stack[stack.length - 6];
|
||||
var v2 = stack[stack.length - 5];
|
||||
stack.splice(stack.length - 6, 2);
|
||||
stack.push(v1);
|
||||
stack.push(v2);
|
||||
break;
|
||||
}
|
||||
case 'swap2': {
|
||||
if (stack.length < 4)
|
||||
return false;
|
||||
var v4 = stack[stack.length - 4];
|
||||
var v3 = stack[stack.length - 3];
|
||||
var v2 = stack[stack.length - 2];
|
||||
var v1 = stack[stack.length - 1];
|
||||
stack[stack.length - 4] = v2;
|
||||
stack[stack.length - 2] = v4;
|
||||
stack[stack.length - 3] = v1;
|
||||
stack[stack.length - 1] = v3;
|
||||
break;
|
||||
}
|
||||
case 'size': {
|
||||
if (stack.length < 1)
|
||||
return false;
|
||||
stack.push(new bn(stack[stack.length - 1].length || 0).toArray());
|
||||
break;
|
||||
}
|
||||
case 'add1':
|
||||
case 'sub1':
|
||||
case 'negate':
|
||||
case 'abs':
|
||||
case 'not':
|
||||
case 'noteq0': {
|
||||
if (stack.length < 1)
|
||||
return false;
|
||||
var n = new bn(stack.pop());
|
||||
switch (o) {
|
||||
case 'add1':
|
||||
n.iadd(1);
|
||||
break;
|
||||
case 'sub1':
|
||||
n.isub(1);
|
||||
break;
|
||||
case 'negate':
|
||||
n = n.neg();
|
||||
break;
|
||||
case 'abs':
|
||||
if (n.cmpn(0) < 0)
|
||||
n = n.neg();
|
||||
break;
|
||||
case 'not':
|
||||
n = n.cmpn(0) === 0;
|
||||
break;
|
||||
case 'noteq0':
|
||||
n = n.cmpn(0) !== 0;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
stack.push(n.toArray());
|
||||
break;
|
||||
}
|
||||
case 'add':
|
||||
case 'sub':
|
||||
case 'booland':
|
||||
case 'boolor':
|
||||
case 'numeq':
|
||||
case 'numeqverify':
|
||||
case 'numneq':
|
||||
case 'lt':
|
||||
case 'gt':
|
||||
case 'lte':
|
||||
case 'gte':
|
||||
case 'min':
|
||||
case 'max': {
|
||||
switch (o) {
|
||||
case 'add':
|
||||
case 'sub':
|
||||
case 'booland':
|
||||
case 'boolor':
|
||||
case 'numeq':
|
||||
case 'numeqverify':
|
||||
case 'numneq':
|
||||
case 'lt':
|
||||
case 'gt':
|
||||
case 'lte':
|
||||
case 'gte':
|
||||
case 'min':
|
||||
case 'max':
|
||||
if (stack.length < 2)
|
||||
return false;
|
||||
var n2 = new bn(stack.pop());
|
||||
var n1 = new bn(stack.pop());
|
||||
var n = new bn(0);
|
||||
switch (o) {
|
||||
case 'add':
|
||||
n = n1.add(b2);
|
||||
break;
|
||||
case 'sub':
|
||||
n = n1.sub(n2);
|
||||
break;
|
||||
case 'booland':
|
||||
n = n1.cmpn(0) !== 0 && n2.cmpn(0) !== 0;
|
||||
break;
|
||||
case 'boolor':
|
||||
n = n1.cmpn(0) !== 0 || n2.cmpn(0) !== 0;
|
||||
break;
|
||||
case 'numeq':
|
||||
n = n1.cmp(n2) === 0;
|
||||
break;
|
||||
case 'numeqverify':
|
||||
n = n1.cmp(n2) === 0;
|
||||
break;
|
||||
case 'numneq':
|
||||
n = n1.cmp(n2) !== 0;
|
||||
break;
|
||||
case 'lt':
|
||||
n = n1.cmp(n2) < 0;
|
||||
break;
|
||||
case 'gt':
|
||||
n = n1.cmp(n2) > 0;
|
||||
break;
|
||||
case 'lte':
|
||||
n = n1.cmp(n2) <= 0;
|
||||
break;
|
||||
case 'gte':
|
||||
n = n1.cmp(n2) >= 0;
|
||||
break;
|
||||
case 'min':
|
||||
n = n1.cmp(n2) < 0 ? n1 : n2;
|
||||
break;
|
||||
case 'max':
|
||||
n = n1.cmp(n2) > 0 ? n1 : n2;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
var res = n.cmpn(0) !== 0;
|
||||
if (o === 'numeqverify') {
|
||||
if (!res)
|
||||
return false;
|
||||
} else {
|
||||
stack.push(n.toArray());
|
||||
// stack.push(res ? [ 1 ] : []);
|
||||
}
|
||||
break;
|
||||
case 'within':
|
||||
if (stack.length < 3)
|
||||
return false;
|
||||
var n3 = new bn(stack.pop());
|
||||
var n2 = new bn(stack.pop());
|
||||
var n1 = new bn(stack.pop());
|
||||
var val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0;
|
||||
stack.push(val.cmpn(0) !== 0 ? [ 1 ] : []);
|
||||
break;
|
||||
}
|
||||
|
||||
var keys = [];
|
||||
for (var i = 0; i < n; i++) {
|
||||
var key = stack.pop();
|
||||
if (!(33 <= key.length && key.length <= 65))
|
||||
break;
|
||||
}
|
||||
case 'codesep': {
|
||||
lastSep = pc;
|
||||
break;
|
||||
}
|
||||
case 'ripemd160': {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
stack.push(utils.ripemd160(stack.pop()));
|
||||
break;
|
||||
}
|
||||
case 'sha1': {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
stack.push(utils.sha1(stack.pop()));
|
||||
break;
|
||||
}
|
||||
case 'sha256': {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
stack.push(utils.sha256(stack.pop()));
|
||||
break;
|
||||
}
|
||||
case 'hash256': {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
stack.push(utils.dsha256(stack.pop()));
|
||||
break;
|
||||
}
|
||||
case 'hash160': {
|
||||
if (stack.length === 0)
|
||||
return false;
|
||||
stack.push(utils.ripesha(stack.pop()));
|
||||
break;
|
||||
}
|
||||
case 'eqverify':
|
||||
case 'eq': {
|
||||
if (stack.length < 2)
|
||||
return false;
|
||||
var res = utils.isEqual(stack.pop(), stack.pop());
|
||||
if (o === 'eqverify') {
|
||||
if (!res)
|
||||
return false;
|
||||
} else {
|
||||
stack.push(res ? [ 1 ] : []);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'checksigverify':
|
||||
case 'checksig': {
|
||||
if (!tx || stack.length < 2)
|
||||
return false;
|
||||
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
var m = stack.pop();
|
||||
if (m.length !== 1 || !(1 <= m[0] && m[0] <= n))
|
||||
return false;
|
||||
m = m[0];
|
||||
|
||||
if (stack.length < m + 1)
|
||||
return false;
|
||||
|
||||
// Get signatures
|
||||
var succ = 0;
|
||||
for (var i = 0, j = 0; i < m && j < n; i++) {
|
||||
var pub = stack.pop();
|
||||
var sig = stack.pop();
|
||||
var type = sig[sig.length - 1];
|
||||
if (type !== 1)
|
||||
if (!constants.rhashType[type & 0x7f])
|
||||
return false;
|
||||
|
||||
var res = false;
|
||||
for (; !res && j < n; j++)
|
||||
res = bcoin.ecdsa.verify(tx, sig.slice(0, -1), keys[j]);
|
||||
if (res)
|
||||
succ++;
|
||||
}
|
||||
|
||||
// Extra value
|
||||
stack.pop();
|
||||
|
||||
var res = succ >= m;
|
||||
if (o === 'checkmultisigverify') {
|
||||
if (!res)
|
||||
if (!script.isValidSig(sig))
|
||||
return false;
|
||||
} else {
|
||||
stack.push(res ? [ 1 ] : []);
|
||||
|
||||
var subscript = s.slice(lastSep + 1);
|
||||
var hash = tx.subscriptHash(index, subscript, type);
|
||||
|
||||
var res = script.verify(hash, sig.slice(0, -1), pub);
|
||||
if (o === 'checksigverify') {
|
||||
if (!res)
|
||||
return false;
|
||||
} else {
|
||||
stack.push(res ? [ 1 ] : []);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'checkmultisigverify':
|
||||
case 'checkmultisig': {
|
||||
if (!tx || stack.length < 3)
|
||||
return false;
|
||||
|
||||
var n = stack.pop();
|
||||
if (n.length !== 1 || !(1 <= n[0] && n[0] <= 3))
|
||||
return false;
|
||||
n = n[0] || 0;
|
||||
|
||||
if (stack.length < n + 1)
|
||||
return false;
|
||||
|
||||
var keys = [];
|
||||
for (var i = 0; i < n; i++) {
|
||||
var key = stack.pop();
|
||||
if (!(33 <= key.length && key.length <= 65))
|
||||
return false;
|
||||
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
var m = stack.pop();
|
||||
if (m.length !== 1 || !(1 <= m[0] && m[0] <= n))
|
||||
return false;
|
||||
m = m[0] || 0;
|
||||
|
||||
if (stack.length < m + 1)
|
||||
return false;
|
||||
|
||||
// Get signatures
|
||||
var succ = 0;
|
||||
for (var i = 0; i < m; i++) {
|
||||
var sig = stack.pop();
|
||||
var type = sig[sig.length - 1];
|
||||
if (!constants.rhashType[type & 0x7f])
|
||||
return false;
|
||||
|
||||
var subscript = s.slice(lastSep + 1);
|
||||
var hash = tx.subscriptHash(index, subscript, type);
|
||||
|
||||
if (!script.isValidSig(sig))
|
||||
return false;
|
||||
|
||||
// Strict order:
|
||||
var res = script.verify(hash, sig.slice(0, -1), keys.pop());
|
||||
if (res)
|
||||
succ++;
|
||||
}
|
||||
|
||||
// Extra value
|
||||
stack.pop();
|
||||
|
||||
var res = succ >= m;
|
||||
if (o === 'checkmultisigverify') {
|
||||
if (!res)
|
||||
return false;
|
||||
} else {
|
||||
stack.push(res ? [ 1 ] : []);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'checklocktimeverify': {
|
||||
// OP_CHECKLOCKTIMEVERIFY = OP_NOP2
|
||||
// input: [[], sig1, sig2, 1]
|
||||
// prev_out: [[lock], 'checklocktimeverify', 'drop',
|
||||
// 'dup', 'hash160', pubkey, 'equalverify', 'checksig']
|
||||
if (!tx || stack.length === 0)
|
||||
return false;
|
||||
|
||||
var lock = new bn(stack[stack.length - 1]).toNumber();
|
||||
|
||||
if (lock < 0)
|
||||
return false;
|
||||
|
||||
var threshold = constants.locktimeThreshold;
|
||||
if (!(
|
||||
(tx.lock < threshold && lock < threshold) ||
|
||||
(tx.lock >= threshold && lock >= threshold)
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lock > tx.lock)
|
||||
return false;
|
||||
|
||||
if (!tx.inputs[index] || tx.inputs[index].seq === 0xffffffff)
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
case 'eval_': {
|
||||
// OP_EVAL = OP_NOP1
|
||||
// var evalScript = script.decode(stack.pop());
|
||||
// if (!Array.isArray(evalScript))
|
||||
// return false;
|
||||
// var res = script.execute(evalScript, stack, tx, index);
|
||||
// if (!res)
|
||||
// return false;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Unknown operation
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Unknown operation
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.length + stack.alt.length > 1000)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
script.multisig = function(keys, m, n) {
|
||||
if (keys.length < m)
|
||||
throw new Error('wrong amount of pubkeys for multisig script');
|
||||
|
||||
assert(m >= 1 && m <= n);
|
||||
assert(n >= 1 && n <= 7);
|
||||
|
||||
// Format:
|
||||
// op_[m] [pubkey1-len] [pubkey1] ... op_[n] op_checkmultisig
|
||||
|
||||
// Using pushdata ops for m and n:
|
||||
// return [ [ m ] ].concat(
|
||||
// keys,
|
||||
// [ [ n ], 'checkmultisig' ]
|
||||
// );
|
||||
|
||||
// Keys need to be in a predictable order.
|
||||
keys = keys.sort(function(a, b) {
|
||||
return new bn(a).cmp(new bn(b)) > 0;
|
||||
});
|
||||
|
||||
// Using OP_1-16 for m and n:
|
||||
return [ m ].concat(
|
||||
keys,
|
||||
[ n, 'checkmultisig' ]
|
||||
);
|
||||
};
|
||||
|
||||
script.isPubkeyhash = function isPubkeyhash(s, hash) {
|
||||
if (s.length !== 5)
|
||||
return false;
|
||||
@ -253,17 +778,21 @@ script.isMultisig = function isMultisig(s, key) {
|
||||
return false;
|
||||
|
||||
var m = s[0];
|
||||
if (typeof m === 'number' && m >= 1 && m <= 16)
|
||||
m = [m];
|
||||
if (!Array.isArray(m) || m.length !== 1)
|
||||
return false;
|
||||
m = m[0];
|
||||
m = m[0] || 0;
|
||||
|
||||
if (s[s.length - 1] !== 'checkmultisig')
|
||||
return false;
|
||||
|
||||
var n = s[s.length - 2];
|
||||
if (typeof n === 'number' && n >= 1 && n <= 16)
|
||||
n = [n];
|
||||
if (!Array.isArray(n) || n.length !== 1)
|
||||
return false;
|
||||
n = n[0];
|
||||
n = n[0] || 0;
|
||||
|
||||
if (n + 3 !== s.length)
|
||||
return false;
|
||||
@ -291,14 +820,22 @@ script.isPubkeyhashInput = function isPubkeyhashInput(s) {
|
||||
33 <= s[1].length && s[1].length <= 65;
|
||||
};
|
||||
|
||||
script.isScripthash = function isScripthash(s) {
|
||||
script.isScripthash = function isScripthash(s, hash) {
|
||||
if (s.length !== 3)
|
||||
return false;
|
||||
|
||||
return s[0] === 'hash160' &&
|
||||
Array.isArray(s[1]) &&
|
||||
s[1].length === 20 &&
|
||||
s[2] === 'eq';
|
||||
var ret = s[0] === 'hash160' &&
|
||||
Array.isArray(s[1]) &&
|
||||
s[1].length === 20 &&
|
||||
s[2] === 'eq';
|
||||
|
||||
if (!ret)
|
||||
return false;
|
||||
|
||||
if (hash)
|
||||
return utils.isEqual(s[1], hash);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
script.isNullData = function isNullData(s) {
|
||||
@ -309,3 +846,98 @@ script.isNullData = function isNullData(s) {
|
||||
Array.isArray(s[1]) &&
|
||||
s[1].length <= 40;
|
||||
};
|
||||
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
|
||||
/**
|
||||
* A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
|
||||
* Where R and S are not negative (their first byte has its highest bit not set), and not
|
||||
* excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
|
||||
* in which case a single 0 byte is necessary and even required).
|
||||
*
|
||||
* See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
|
||||
*
|
||||
* This function is consensus-critical since BIP66.
|
||||
*/
|
||||
script.isValidSig = function(sig) {
|
||||
// Empty signature. Not strictly DER encoded, but allowed to provide a
|
||||
// compact way to provide an invalid signature for use with CHECK(MULTI)SIG
|
||||
if (sig.length === 0)
|
||||
return true;
|
||||
|
||||
// Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash]
|
||||
// * total-length: 1-byte length descriptor of everything that follows,
|
||||
// excluding the sighash byte.
|
||||
// * R-length: 1-byte length descriptor of the R value that follows.
|
||||
// * R: arbitrary-length big-endian encoded R value. It must use the shortest
|
||||
// possible encoding for a positive integers (which means no null bytes at
|
||||
// the start, except a single one when the next byte has its highest bit set).
|
||||
// * S-length: 1-byte length descriptor of the S value that follows.
|
||||
// * S: arbitrary-length big-endian encoded S value. The same rules apply.
|
||||
// * sighash: 1-byte value indicating what data is hashed (not part of the DER
|
||||
// signature)
|
||||
|
||||
// Minimum and maximum size constraints.
|
||||
if (sig.length < 9)
|
||||
return false;
|
||||
if (sig.length > 73)
|
||||
return false;
|
||||
|
||||
// A signature is of type 0x30 (compound).
|
||||
if (sig[0] !== 0x30)
|
||||
return false;
|
||||
|
||||
// Make sure the length covers the entire signature.
|
||||
if (sig[1] !== sig.length - 3)
|
||||
return false;
|
||||
|
||||
// Extract the length of the R element.
|
||||
var lenR = sig[3];
|
||||
|
||||
// Make sure the length of the S element is still inside the signature.
|
||||
if (5 + lenR >= sig.length)
|
||||
return false;
|
||||
|
||||
// Extract the length of the S element.
|
||||
var lenS = sig[5 + lenR];
|
||||
|
||||
// Verify that the length of the signature matches the sum of the length
|
||||
// of the elements.
|
||||
if (lenR + lenS + 7 !== sig.length)
|
||||
return false;
|
||||
|
||||
// Check whether the R element is an integer.
|
||||
if (sig[2] !== 0x02)
|
||||
return false;
|
||||
|
||||
// Zero-length integers are not allowed for R.
|
||||
if (lenR === 0)
|
||||
return false;
|
||||
|
||||
// Negative numbers are not allowed for R.
|
||||
if (sig[4] & 0x80)
|
||||
return false;
|
||||
|
||||
// Null bytes at the start of R are not allowed, unless R would
|
||||
// otherwise be interpreted as a negative number.
|
||||
if (lenR > 1 && (sig[4] === 0x00) && !(sig[5] & 0x80))
|
||||
return false;
|
||||
|
||||
// Check whether the S element is an integer.
|
||||
if (sig[lenR + 4] !== 0x02)
|
||||
return false;
|
||||
|
||||
// Zero-length integers are not allowed for S.
|
||||
if (lenS === 0)
|
||||
return false;
|
||||
|
||||
// Negative numbers are not allowed for S.
|
||||
if (sig[lenR + 6] & 0x80)
|
||||
return false;
|
||||
|
||||
// Null bytes at the start of S are not allowed, unless S would otherwise be
|
||||
// interpreted as a negative number.
|
||||
if (lenS > 1 && (sig[lenR + 6] === 0x00) && !(sig[lenR + 7] & 0x80))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
504
lib/bcoin/tx.js
504
lib/bcoin/tx.js
@ -3,6 +3,7 @@ var bn = require('bn.js');
|
||||
var bcoin = require('../bcoin');
|
||||
var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
var constants = bcoin.protocol.constants;
|
||||
|
||||
function TX(data, block) {
|
||||
if (!(this instanceof TX))
|
||||
@ -40,6 +41,10 @@ function TX(data, block) {
|
||||
|
||||
// ps = Pending Since
|
||||
this.ps = this.ts === 0 ? +new Date() / 1000 : 0;
|
||||
|
||||
this.change = data.change || null;
|
||||
this.fee = data.fee || 10000;
|
||||
this.dust = 5460;
|
||||
}
|
||||
module.exports = TX;
|
||||
|
||||
@ -56,6 +61,11 @@ TX.prototype.render = function render() {
|
||||
return bcoin.protocol.framer.tx(this);
|
||||
};
|
||||
|
||||
TX.prototype.input = function input(i, index) {
|
||||
this._input(i, index);
|
||||
return this;
|
||||
};
|
||||
|
||||
TX.prototype._input = function _input(i, index) {
|
||||
if (i instanceof TX)
|
||||
i = { tx: i, index: index };
|
||||
@ -87,10 +97,10 @@ TX.prototype._input = function _input(i, index) {
|
||||
var index = this._inputIndex(hash, index);
|
||||
if (index !== -1) {
|
||||
var ex = this.inputs[index];
|
||||
|
||||
ex.out.tx = input.out.tx || ex.out.tx;
|
||||
ex.seq = input.seq || ex.seq;
|
||||
ex.script = input.script.length ? input.script : ex.script;
|
||||
input.out.tx = input.out.tx || ex.out.tx;
|
||||
input.seq = input.seq || ex.seq;
|
||||
input.script = input.script.length ? input.script : ex.script;
|
||||
this.inputs[index] = input;
|
||||
} else {
|
||||
this.inputs.push(input);
|
||||
index = this.inputs.length - 1;
|
||||
@ -111,14 +121,186 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) {
|
||||
return -1;
|
||||
};
|
||||
|
||||
TX.prototype.input = function input(i, index) {
|
||||
this._input(i, index);
|
||||
return this;
|
||||
TX.prototype.signature = function(input, key, type) {
|
||||
if (!type)
|
||||
type = 'all';
|
||||
|
||||
if (typeof type === 'string')
|
||||
type = bcoin.protocol.constants.hashType[type];
|
||||
|
||||
// Get the previous output's subscript
|
||||
var s = input.out.tx.getSubscript(input.out.index);
|
||||
|
||||
// Get the hash of the current tx, minus the other inputs, plus the sighash.
|
||||
var hash = this.subscriptHash(tx.inputs.indexOf(input), s, type);
|
||||
|
||||
// Sign the transaction with our one input
|
||||
var signature = bcoin.ecdsa.sign(hash, key.priv).toDER();
|
||||
|
||||
// Add the sighash as a single byte to the signature
|
||||
signature = signature.concat(type);
|
||||
|
||||
return signature;
|
||||
};
|
||||
|
||||
TX.prototype.out = function out(output, value) {
|
||||
// Build the scriptSigs for inputs, excluding the signatures
|
||||
TX.prototype.scriptInput = function(input, pub) {
|
||||
// Get the previous output's subscript
|
||||
var s = input.out.tx.getSubscript(input.out.index);
|
||||
|
||||
// Already has a script template (at least)
|
||||
if (input.script.length)
|
||||
return;
|
||||
|
||||
// P2PKH and simple tx
|
||||
if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) {
|
||||
input.script = [ [], pub ];
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE for multisig: Technically we should create m signature slots,
|
||||
// but we create n signature slots so we can order the signatures properly.
|
||||
|
||||
// Multisig
|
||||
// raw format: OP_FALSE [sig-1] [sig-2] ...
|
||||
if (bcoin.script.isMultisig(s)) {
|
||||
input.script = [ [] ];
|
||||
var n = s[s.length - 2];
|
||||
// If using pushdata instead of OP_1-16:
|
||||
if (Array.isArray(n))
|
||||
n = n[0] || 0;
|
||||
for (var i = 0; i < n; i++)
|
||||
input.script[i + 1] = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// P2SH multisig
|
||||
// p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script]
|
||||
if (bcoin.script.isScripthash(s)) {
|
||||
input.script = [ [] ];
|
||||
var redeem = bcoin.script.decode(pub);
|
||||
var n = redeem[redeem.length - 2];
|
||||
// If using pushdata instead of OP_1-16:
|
||||
if (Array.isArray(n))
|
||||
n = n[0] || 0;
|
||||
for (var i = 0; i < n; i++)
|
||||
input.script[i + 1] = [];
|
||||
// P2SH requires the redeem script after signatures
|
||||
input.script.push(pub);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('scriptInput(): could not identify prev_out type');
|
||||
};
|
||||
|
||||
// Sign the now-built scriptSigs
|
||||
TX.prototype.signInput = function(input, key, type) {
|
||||
if (!type)
|
||||
type = 'all';
|
||||
|
||||
if (typeof type === 'string')
|
||||
type = bcoin.protocol.constants.hashType[type];
|
||||
|
||||
// Get the previous output's subscript
|
||||
var s = input.out.tx.getSubscript(input.out.index);
|
||||
|
||||
// Get the hash of the current tx, minus the other inputs, plus the sighash.
|
||||
var hash = this.subscriptHash(this.inputs.indexOf(input), s, type);
|
||||
|
||||
// Sign the transaction with our one input
|
||||
var signature = bcoin.ecdsa.sign(hash, key.priv).toDER();
|
||||
|
||||
// Add the sighash as a single byte to the signature
|
||||
signature = signature.concat(type);
|
||||
|
||||
// P2PKH and simple tx
|
||||
if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) {
|
||||
input.script[0] = signature;
|
||||
return;
|
||||
}
|
||||
|
||||
// Multisig
|
||||
// raw format: OP_FALSE [sig-1] [sig-2] ...
|
||||
// p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script]
|
||||
if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) {
|
||||
var len = input.script.length;
|
||||
var redeem;
|
||||
|
||||
if (bcoin.script.isScripthash(s)) {
|
||||
len--;
|
||||
redeem = bcoin.script.decode(input.script[input.script.length - 1]);
|
||||
} else {
|
||||
redeem = s;
|
||||
}
|
||||
|
||||
var m = redeem[0];
|
||||
// If using pushdata instead of OP_1-16:
|
||||
if (Array.isArray(m))
|
||||
m = m[0] || 0;
|
||||
|
||||
var keys = redeem.slice(1, -2);
|
||||
var pub = key.getPublic(true, 'array');
|
||||
var pubn = key.getPublic(false, 'array');
|
||||
|
||||
// Find the key index so we can place
|
||||
// the signature in the same index.
|
||||
for (var ki = 0; ki < keys.length; ki++) {
|
||||
if (utils.isEqual(pub, keys[ki]) || utils.isEqual(pubn, keys[ki]))
|
||||
break;
|
||||
}
|
||||
|
||||
if (ki === keys.length)
|
||||
throw new Error('Public key is not in the prev_out script');
|
||||
|
||||
if (ki + 1 > len - 1)
|
||||
throw new Error('No signature slot available');
|
||||
|
||||
// Add our signature to the correct slot
|
||||
// and count the total number of signatures.
|
||||
var totalSigs = 0;
|
||||
for (var i = 1; i < len; i++) {
|
||||
if (Array.isArray(input.script[i]) && input.script[i].length) {
|
||||
totalSigs++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i - 1 === ki) {
|
||||
if (totalSigs >= m)
|
||||
continue;
|
||||
input.script[i] = signature;
|
||||
totalSigs++;
|
||||
}
|
||||
}
|
||||
|
||||
// All signatures added. Finalize by removing empty slots.
|
||||
if (totalSigs >= m) {
|
||||
for (var i = len - 1; i >= 1; i--) {
|
||||
if (Array.isArray(input.script[i]) && !input.script[i].length)
|
||||
input.script.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('signInput(): could not identify prev_out type');
|
||||
};
|
||||
|
||||
// Build the scriptSig and sign it
|
||||
TX.prototype.scriptSig = function(input, key, pub, type) {
|
||||
// Build script for input
|
||||
this.scriptInput(input, pub);
|
||||
|
||||
// Sign input
|
||||
this.signInput(input, key, type);
|
||||
|
||||
return input.script;
|
||||
};
|
||||
|
||||
TX.prototype.output = function output(output, value) {
|
||||
if (output instanceof bcoin.wallet)
|
||||
output = output.getAddress();
|
||||
|
||||
if (typeof output === 'string') {
|
||||
output = {
|
||||
address: output,
|
||||
@ -126,34 +308,86 @@ TX.prototype.out = function out(output, value) {
|
||||
};
|
||||
}
|
||||
|
||||
var script = output.script ? output.script.slice() : [];
|
||||
this.outputs.push({
|
||||
value: new bn(output.value),
|
||||
script: this.scriptOutput(output)
|
||||
});
|
||||
|
||||
// Multisig script if given addresses
|
||||
if (Array.isArray(output.keys || output.address)) {
|
||||
var keys = output.keys || output.address;
|
||||
return this;
|
||||
};
|
||||
|
||||
// compat
|
||||
TX.prototype.out = TX.prototype.output;
|
||||
|
||||
TX.prototype.scriptOutput = function(options) {
|
||||
var script = options.script ? options.script.slice() : [];
|
||||
|
||||
if (Array.isArray(options.keys || options.address)) {
|
||||
// Raw multisig transaction
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0010.mediawiki
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0019.mediawiki
|
||||
// [required-sigs] [pubkey-hash1] [pubkey-hash2] ... [number-of-keys] checkmultisig
|
||||
var keys = options.keys || options.address;
|
||||
|
||||
if (keys === options.address) {
|
||||
keys = keys.map(function(address) {
|
||||
return bcoin.wallet.addr2hash(address, 'normal');
|
||||
});
|
||||
}
|
||||
|
||||
keys = keys.map(function(key) {
|
||||
if (typeof key === 'string')
|
||||
return utils.toKeyArray(key);
|
||||
return key;
|
||||
});
|
||||
|
||||
// compat:
|
||||
options.m = options.minSignatures || options.m;
|
||||
var m = options.m || keys.length;
|
||||
var n = options.n || keys.length;
|
||||
|
||||
assert(m >= 1 && m <= n);
|
||||
if (options.hash)
|
||||
assert(n >= 1 && n <= 7);
|
||||
else
|
||||
assert(n >= 1 && n <= 3);
|
||||
|
||||
script = bcoin.script.multisig(keys, m, n);
|
||||
} else if (bcoin.wallet.validateAddress(options.address, 'p2sh')) {
|
||||
// p2sh transaction
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki
|
||||
// hash160 [20-byte-redeemscript-hash] equal
|
||||
script = [
|
||||
[ output.minSignatures || keys.length ]
|
||||
].concat(
|
||||
keys,
|
||||
[ [ keys.length ], 'checkmultisig' ]
|
||||
);
|
||||
// Default script if given address
|
||||
} else if (output.address) {
|
||||
'hash160',
|
||||
bcoin.wallet.addr2hash(options.address, 'p2sh'),
|
||||
'eq'
|
||||
];
|
||||
} else if (options.address) {
|
||||
// p2pkh transaction
|
||||
// dup hash160 [pubkey-hash] equalverify checksig
|
||||
script = [
|
||||
'dup',
|
||||
'hash160',
|
||||
bcoin.wallet.addr2hash(output.address),
|
||||
bcoin.wallet.addr2hash(options.address, 'normal'),
|
||||
'eqverify',
|
||||
'checksig'
|
||||
];
|
||||
}
|
||||
|
||||
this.outputs.push({
|
||||
value: new bn(output.value),
|
||||
script: script
|
||||
});
|
||||
// make it p2sh
|
||||
if (options.hash) {
|
||||
var redeem = script;
|
||||
var hash = utils.ripesha(bcoin.script.encode(redeem));
|
||||
script = [
|
||||
'hash160',
|
||||
hash,
|
||||
'eq'
|
||||
];
|
||||
script.redeem = redeem;
|
||||
}
|
||||
|
||||
return this;
|
||||
return script;
|
||||
};
|
||||
|
||||
TX.prototype.getSubscript = function getSubscript(index) {
|
||||
@ -167,13 +401,14 @@ TX.prototype.getSubscript = function getSubscript(index) {
|
||||
TX.prototype.subscriptHash = function subscriptHash(index, s, type) {
|
||||
var copy = this.clone();
|
||||
|
||||
if (typeof type === 'string')
|
||||
type = bcoin.protocol.constants.hashType[type];
|
||||
|
||||
copy.inputs.forEach(function(input, i) {
|
||||
input.script = index === i ? s : [];
|
||||
});
|
||||
var verifyStr = copy.render();
|
||||
verifyStr = verifyStr.concat(
|
||||
bcoin.protocol.constants.hashType[type], 0, 0, 0
|
||||
);
|
||||
utils.writeU32(verifyStr, type, verifyStr.length);
|
||||
var hash = utils.dsha256(verifyStr);
|
||||
|
||||
return hash;
|
||||
@ -184,6 +419,9 @@ TX.prototype.verify = function verify(index, force) {
|
||||
if (!force && this.ts !== 0)
|
||||
return true;
|
||||
|
||||
if (this.inputs.length === 0)
|
||||
return false;
|
||||
|
||||
return this.inputs.every(function(input, i) {
|
||||
if (index !== undefined && index !== i)
|
||||
return true;
|
||||
@ -193,20 +431,46 @@ TX.prototype.verify = function verify(index, force) {
|
||||
|
||||
assert(input.out.tx.outputs.length > input.out.index);
|
||||
|
||||
var subscript = input.out.tx.getSubscript(input.out.index);
|
||||
var hash = this.subscriptHash(i, subscript, 'all');
|
||||
|
||||
var stack = [];
|
||||
bcoin.script.execute(input.script, stack);
|
||||
var prev = input.out.tx.outputs[input.out.index].script;
|
||||
var res = bcoin.script.execute(prev, stack, hash);
|
||||
|
||||
if (bcoin.script.isScripthash(prev)) {
|
||||
// p2sh transactions cannot have anything
|
||||
// other than pushdata ops in the scriptSig
|
||||
var push = !input.script.slice(1).every(Array.isArray);
|
||||
if (push)
|
||||
return false;
|
||||
}
|
||||
|
||||
bcoin.script.execute(input.script, stack, this, i);
|
||||
var res = bcoin.script.execute(prev, stack, this, i);
|
||||
if (!res)
|
||||
return false;
|
||||
|
||||
return stack.length > 0 && utils.isEqual(stack.pop(), [ 1 ]);
|
||||
// Might be necessary for arithmetic:
|
||||
// if (stack.length === 0 || new bn(stack.pop()).cmp(0) !== 0)
|
||||
|
||||
if (stack.length === 0 || !utils.isEqual(stack.pop(), [ 1 ]))
|
||||
return false;
|
||||
|
||||
if (bcoin.script.isScripthash(prev)) {
|
||||
var redeem = input.script[input.script.length - 1];
|
||||
if (!Array.isArray(redeem))
|
||||
return false;
|
||||
redeem = bcoin.script.decode(redeem);
|
||||
res = bcoin.script.execute(redeem, stack, this, i);
|
||||
if (!res)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, this);
|
||||
};
|
||||
|
||||
TX.prototype.isCoinbase = function isCoinbase() {
|
||||
return this.inputs.length === 1 && +this.inputs[0].out.hash === 0;
|
||||
};
|
||||
|
||||
TX.prototype.maxSize = function maxSize() {
|
||||
// Create copy with 0-script inputs
|
||||
var copy = this.clone();
|
||||
@ -217,9 +481,14 @@ TX.prototype.maxSize = function maxSize() {
|
||||
var size = copy.render().length;
|
||||
|
||||
// Add size for signatures and public keys
|
||||
copy.inputs.forEach(function(input) {
|
||||
var s = input.out.tx.outputs[input.out.index].script;
|
||||
if (bcoin.script.isPubkeyhash(s)) {
|
||||
copy.inputs.forEach(function(input, i) {
|
||||
// Get the previous output's script
|
||||
// var s = input.out.tx.outputs[input.out.index].script;
|
||||
|
||||
// Get the previous output's subscript
|
||||
var s = input.out.tx.getSubscript(input.out.index);
|
||||
|
||||
if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) {
|
||||
// Signature + len
|
||||
size += 74;
|
||||
// Pub key + len
|
||||
@ -227,23 +496,170 @@ TX.prototype.maxSize = function maxSize() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Multisig
|
||||
// Empty byte
|
||||
size += 1;
|
||||
// Signature + len
|
||||
size += 74;
|
||||
});
|
||||
if (bcoin.script.isMultisig(s)) {
|
||||
// Multisig
|
||||
// Empty byte
|
||||
size += 1;
|
||||
// Signature + len
|
||||
var m = s[0];
|
||||
// If using pushdata instead of OP_1-16:
|
||||
if (Array.isArray(m))
|
||||
m = m[0] || 0;
|
||||
assert(m >= 1 && m <= 3);
|
||||
size += 74 * m;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bcoin.script.isScripthash(s)) {
|
||||
var script = this.inputs[i].script;
|
||||
var redeem, m, n;
|
||||
if (script.length) {
|
||||
redeem = bcoin.script.decode(script[script.length - 1]);
|
||||
m = redeem[0];
|
||||
n = redeem[redeem.length - 2];
|
||||
// If using pushdata instead of OP_1-16:
|
||||
if (Array.isArray(m))
|
||||
m = m[0] || 0;
|
||||
if (Array.isArray(n))
|
||||
n = n[0] || 0;
|
||||
} else {
|
||||
// May end up in a higher fee if we
|
||||
// do not have the redeem script available.
|
||||
m = 7;
|
||||
n = 7;
|
||||
}
|
||||
assert(m >= 1 && m <= n);
|
||||
assert(n >= 1 && n <= 7);
|
||||
// Multisig
|
||||
// Empty byte
|
||||
size += 1;
|
||||
// Signature + len
|
||||
size += 74 * m;
|
||||
// Redeem script
|
||||
// m byte
|
||||
size += 1;
|
||||
// 1 byte length + 65 byte pubkey
|
||||
size += 66 * n;
|
||||
// n byte
|
||||
size += 1;
|
||||
// checkmultisig byte
|
||||
size += 1;
|
||||
return;
|
||||
}
|
||||
}, this);
|
||||
|
||||
return size;
|
||||
};
|
||||
|
||||
// Building a TX:
|
||||
// 1. Add outputs:
|
||||
// - this.output({ address: ..., value: ... });
|
||||
// - this.output({ address: ..., value: ... });
|
||||
// 2. Add inputs with utxos and change output:
|
||||
// - this.fillUnspent(unspentItems, [changeAddr]);
|
||||
// 3. Fill input scripts (for each input):
|
||||
// - this.scriptInput(input, pub)
|
||||
// - this.signInput(input, key, [sigHashType])
|
||||
TX.prototype.utxos = function utxos(unspent) {
|
||||
// NOTE: tx should be prefilled with all outputs
|
||||
var cost = this.funds('out');
|
||||
|
||||
// Use initial fee for starters
|
||||
var fee = 1;
|
||||
|
||||
// total = cost + fee
|
||||
var total = cost.add(new bn(this.fee));
|
||||
|
||||
var inputs = this.inputs.slice();
|
||||
var utxos = [];
|
||||
|
||||
var lastAdded = 0;
|
||||
function addInput(unspent, i) {
|
||||
// Add new inputs until TX will have enough funds to cover both
|
||||
// minimum post cost and fee
|
||||
var index = this._input(unspent);
|
||||
utxos.push(this.inputs[index]);
|
||||
lastAdded++;
|
||||
return this.funds('in').cmp(total) < 0;
|
||||
}
|
||||
|
||||
// Transfer `total` funds maximum
|
||||
// var unspent = wallet.unspent();
|
||||
unspent.every(addInput, this);
|
||||
|
||||
// Add dummy output (for `left`) to calculate maximum TX size
|
||||
this.output({ address: null, value: new bn(0) });
|
||||
|
||||
// Change fee value if it is more than 1024 bytes
|
||||
// (10000 satoshi for every 1024 bytes)
|
||||
do {
|
||||
// Calculate maximum possible size after signing
|
||||
var byteSize = this.maxSize();
|
||||
|
||||
var addFee = Math.ceil(byteSize / 1024) - fee;
|
||||
total.iadd(new bn(addFee * this.fee));
|
||||
fee += addFee;
|
||||
|
||||
// Failed to get enough funds, add more inputs
|
||||
if (this.funds('in').cmp(total) < 0)
|
||||
unspent.slice(lastAdded).every(addInput, this);
|
||||
} while (this.funds('in').cmp(total) < 0 && lastAdded < unspent.length);
|
||||
|
||||
// Still failing to get enough funds
|
||||
if (this.funds('in').cmp(total) < 0) {
|
||||
this.inputs = inputs;
|
||||
this.outputs.pop();
|
||||
this.cost = total;
|
||||
return null;
|
||||
}
|
||||
|
||||
// How much money is left after sending outputs
|
||||
var left = this.funds('in').sub(total);
|
||||
|
||||
// Clear the tx of everything we added.
|
||||
this.inputs = inputs;
|
||||
this.outputs.pop();
|
||||
this.cost = total;
|
||||
|
||||
// Return necessary utxos and change.
|
||||
return {
|
||||
utxos: utxos,
|
||||
change: left,
|
||||
cost: total
|
||||
};
|
||||
};
|
||||
|
||||
TX.prototype.fillUnspent = function fillUnspent(unspent, change) {
|
||||
var result = this.utxos(unspent);
|
||||
|
||||
if (!result)
|
||||
return result;
|
||||
|
||||
result.utxos.forEach(function(utxo) {
|
||||
this.input(utxo, null);
|
||||
}, this);
|
||||
|
||||
// Not enough money, transfer everything to owner
|
||||
if (result.change.cmpn(this.dust) < 0) {
|
||||
// NOTE: that this output is either `postCost` or one of the `dust` values
|
||||
this.outputs[this.outputs.length - 1].value.iadd(result.change);
|
||||
} else {
|
||||
this.output({
|
||||
address: change || this.change,
|
||||
value: result.change
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
TX.prototype.inputAddrs = function inputAddrs() {
|
||||
return this.inputs.filter(function(input) {
|
||||
return bcoin.script.isPubkeyhashInput(input.script);
|
||||
}).map(function(input) {
|
||||
var pub = input.script[1];
|
||||
var hash = utils.ripesha(pub);
|
||||
return bcoin.wallet.hash2addr(hash);
|
||||
return bcoin.wallet.hash2addr(hash, 'normal');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ var utils = exports;
|
||||
|
||||
var bn = require('bn.js');
|
||||
var hash = require('hash.js');
|
||||
var util = require('util');
|
||||
|
||||
function toArray(msg, enc) {
|
||||
if (Array.isArray(msg))
|
||||
@ -118,6 +119,14 @@ utils.fromBase58 = function fromBase58(str) {
|
||||
return z.concat(res.toArray());
|
||||
};
|
||||
|
||||
utils.ripemd160 = function ripemd160(data, enc) {
|
||||
return hash.ripemd160().update(data, enc).digest();
|
||||
};
|
||||
|
||||
utils.sha1 = function sha1(data, enc) {
|
||||
return hash.sha1().update(data, enc).digest();
|
||||
};
|
||||
|
||||
utils.ripesha = function ripesha(data, enc) {
|
||||
return hash.ripemd160().update(utils.sha256(data, enc)).digest();
|
||||
};
|
||||
@ -417,6 +426,8 @@ RequestCache.prototype.fullfill = function fullfill(id, err, data) {
|
||||
|
||||
utils.asyncify = function asyncify(fn) {
|
||||
return function _asynicifedFn(err, data1, data2) {
|
||||
if (!fn)
|
||||
return;
|
||||
utils.nextTick(function() {
|
||||
fn(err, data1, data2);
|
||||
});
|
||||
@ -466,3 +477,56 @@ utils.isIP = function(ip) {
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
utils.isHex = function(msg) {
|
||||
return typeof msg === 'string' && /^[0-9a-f]+$/i.test(msg);
|
||||
};
|
||||
|
||||
utils.toKeyArray = function(msg) {
|
||||
if (typeof msg !== 'string')
|
||||
return msg;
|
||||
|
||||
if (utils.isHex(msg))
|
||||
return utils.toArray(msg, 'hex');
|
||||
|
||||
return utils.fromBase58(msg);
|
||||
};
|
||||
|
||||
utils.inspect = function(obj) {
|
||||
return typeof obj !== 'string'
|
||||
? util.inspect(obj, null, 20, true)
|
||||
: obj;
|
||||
};
|
||||
|
||||
utils.print = function(msg) {
|
||||
return typeof msg === 'object'
|
||||
? process.stdout.write(utils.inspect(msg) + '\n')
|
||||
: console.log.apply(console, arguments);
|
||||
};
|
||||
|
||||
utils.debug = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args[0] = '\x1b[31m' + args[0] + '\x1b[m';
|
||||
return utils.print.apply(null, args);
|
||||
};
|
||||
|
||||
utils.merge = function(target) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
args.forEach(function(obj) {
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
target[key] = obj[key];
|
||||
});
|
||||
});
|
||||
return target;
|
||||
};
|
||||
|
||||
utils.fromBTC = function(btc) {
|
||||
var satoshi = new bn(+btc || 0);
|
||||
satoshi.imuln(100000000);
|
||||
return satoshi;
|
||||
};
|
||||
|
||||
utils.ntoBTC = function(satoshi) {
|
||||
satoshi = new bn(Math.floor(+satoshi || 0).toString(16), 16);
|
||||
return bcoin.utils.toBTC(satoshi);
|
||||
};
|
||||
|
||||
@ -5,6 +5,8 @@ var inherits = require('inherits');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
var constants = bcoin.protocol.constants;
|
||||
var network = bcoin.protocol.network;
|
||||
|
||||
function Wallet(options, passphrase) {
|
||||
if (!(this instanceof Wallet))
|
||||
@ -22,6 +24,7 @@ function Wallet(options, passphrase) {
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
this.options = options;
|
||||
this.compressed = typeof options.compressed !== 'undefined' ?
|
||||
options.compressed : true;
|
||||
this.storage = options.storage;
|
||||
@ -29,18 +32,45 @@ function Wallet(options, passphrase) {
|
||||
this.loaded = false;
|
||||
this.lastTs = 0;
|
||||
|
||||
if (options.passphrase) {
|
||||
if (options.priv instanceof bcoin.hd.priv) {
|
||||
this.hd = options.priv;
|
||||
this.key = this.hd;
|
||||
} else if (options.pub instanceof bcoin.hd.pub) {
|
||||
this.hd = options.pub;
|
||||
this.key = this.hd;
|
||||
} else if (options.hd) {
|
||||
this.hd = bcoin.hd.priv(options);
|
||||
this.key = this.hd;
|
||||
} else if (options.key) {
|
||||
if ((options.key instanceof bcoin.hd.priv)
|
||||
|| (options.key instanceof bcoin.hd.pub)) {
|
||||
this.hd = options.key;
|
||||
this.key = options.key;
|
||||
} else {
|
||||
this.key = options.key;
|
||||
}
|
||||
} else if (options.passphrase) {
|
||||
this.key = bcoin.ecdsa.genKeyPair({
|
||||
pers: options.scope,
|
||||
entropy: hash.sha256().update(options.passphrase).digest()
|
||||
});
|
||||
} else if (options.priv || options.pub) {
|
||||
this.key = bcoin.ecdsa.keyPair(options.priv || options.pub, 'hex');
|
||||
this.key = bcoin.ecdsa.keyPair({
|
||||
priv: options.priv,
|
||||
pub: options.pub
|
||||
});
|
||||
} else {
|
||||
this.key = bcoin.ecdsa.genKeyPair();
|
||||
}
|
||||
|
||||
this.prefix = 'bt/' + this.getAddress() + '/';
|
||||
this.addressType = 'normal';
|
||||
this.sharedKeys = [];
|
||||
this.m = 1;
|
||||
this.n = 1;
|
||||
|
||||
this.multisig(options.multisig || {});
|
||||
|
||||
this.prefix = 'bt/' + this.getOwnAddress() + '/';
|
||||
this.tx = new bcoin.txPool(this);
|
||||
|
||||
// Just a constants, actually
|
||||
@ -84,6 +114,48 @@ Wallet.prototype._init = function init() {
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.multisig = function multisig(options) {
|
||||
var pub = this.getOwnPublicKey();
|
||||
|
||||
options.type = options.type || options.addressType;
|
||||
options.keys = options.keys || options.sharedKeys;
|
||||
|
||||
this.addressType = options.type || 'normal';
|
||||
|
||||
// Multisig
|
||||
this.sharedKeys = (options.keys || []).map(utils.toKeyArray);
|
||||
this.m = options.m || 1;
|
||||
this.n = options.n || 1;
|
||||
|
||||
this.sharedKeys = this.sharedKeys.filter(function(key) {
|
||||
return !utils.isEqual(key, pub);
|
||||
});
|
||||
|
||||
// Use p2sh multisig by default
|
||||
if (!options.addressType && this.sharedKeys.length)
|
||||
this.addressType = 'p2sh';
|
||||
|
||||
if (this.m < 1 || this.m > this.n)
|
||||
throw new Error('m ranges between 1 and n');
|
||||
|
||||
if (this.n < 1 || this.n > 7)
|
||||
throw new Error('n ranges between 1 and 7');
|
||||
|
||||
if (this.sharedKeys.length < this.m - 1)
|
||||
throw new Error(this.m + ' public keys required');
|
||||
};
|
||||
|
||||
Wallet.prototype.derive = function derive() {
|
||||
var options = this.options;
|
||||
|
||||
if (!this.hd)
|
||||
throw new Error('wallet is not HD');
|
||||
|
||||
options.priv = this.hd.derive.apply(this.hd, arguments);
|
||||
|
||||
return bcoin.wallet(options);
|
||||
};
|
||||
|
||||
Wallet.prototype.getPrivateKey = function getPrivateKey(enc) {
|
||||
var priv = this.key.getPrivate();
|
||||
if (priv)
|
||||
@ -95,7 +167,7 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) {
|
||||
|
||||
if (enc === 'base58') {
|
||||
// We'll be using ncompressed public key as an address
|
||||
var arr = [ 128 ];
|
||||
var arr = [ network.prefixes.privkey ];
|
||||
|
||||
// 0-pad key
|
||||
while (arr.length + priv.length < 33)
|
||||
@ -110,39 +182,99 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) {
|
||||
}
|
||||
};
|
||||
|
||||
Wallet.prototype.getPublicKey = function getPublicKey(enc) {
|
||||
var pub = this.key.getPublic(this.compressed, 'array');
|
||||
Wallet.prototype.getFullPublicKey = function getFullPublicKey(enc) {
|
||||
var pub = this.getOwnPublicKey();
|
||||
|
||||
if (this.addressType === 'p2sh') {
|
||||
var keys = this.getPublicKeys();
|
||||
pub = bcoin.script.encode(bcoin.script.multisig(keys, this.m, this.n));
|
||||
}
|
||||
|
||||
if (enc === 'base58')
|
||||
return utils.toBase58(pub);
|
||||
else if (enc === 'hex')
|
||||
return utils.toHex(pub);
|
||||
else
|
||||
return pub;
|
||||
};
|
||||
|
||||
Wallet.prototype.getOwnPublicKey = function getOwnPublicKey(enc) {
|
||||
var pub = this.key.getPublic(this.compressed, 'array');
|
||||
|
||||
if (enc === 'base58')
|
||||
return utils.toBase58(pub);
|
||||
else if (enc === 'hex')
|
||||
return utils.toHex(pub);
|
||||
else
|
||||
return pub;
|
||||
};
|
||||
|
||||
Wallet.prototype.getPublicKey = function getPublicKey(enc) {
|
||||
return this.getFullPublicKey(enc);
|
||||
};
|
||||
|
||||
Wallet.prototype.getPublicKeys = function() {
|
||||
var pub = this.getOwnPublicKey();
|
||||
|
||||
this.sharedKeys = this.sharedKeys.filter(function(key) {
|
||||
return !utils.isEqual(key, pub);
|
||||
});
|
||||
|
||||
var keys = this.sharedKeys.slice().map(utils.toKeyArray);
|
||||
|
||||
keys.push(pub);
|
||||
|
||||
// Keys need to be in a predictable order.
|
||||
keys = keys.sort(function(a, b) {
|
||||
return new bn(a).cmp(new bn(b)) > 0;
|
||||
});
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
Wallet.prototype.getFullHash = function getFullHash() {
|
||||
return utils.ripesha(this.getFullPublicKey());
|
||||
};
|
||||
|
||||
Wallet.prototype.getFullAddress = function getFullAddress() {
|
||||
return Wallet.hash2addr(this.getFullHash(), this.addressType);
|
||||
};
|
||||
|
||||
Wallet.prototype.getOwnHash = function getOwnHash() {
|
||||
return utils.ripesha(this.getOwnPublicKey());
|
||||
};
|
||||
|
||||
Wallet.prototype.getOwnAddress = function getOwnAddress() {
|
||||
return Wallet.hash2addr(this.getOwnHash(), this.addressType);
|
||||
};
|
||||
|
||||
Wallet.prototype.getHash = function getHash() {
|
||||
return utils.ripesha(this.getPublicKey());
|
||||
return utils.ripesha(this.getFullPublicKey());
|
||||
};
|
||||
|
||||
Wallet.prototype.getAddress = function getAddress() {
|
||||
return Wallet.hash2addr(this.getHash());
|
||||
return Wallet.hash2addr(this.getFullHash(), this.addressType);
|
||||
};
|
||||
|
||||
Wallet.hash2addr = function hash2addr(hash) {
|
||||
Wallet.hash2addr = function hash2addr(hash, version) {
|
||||
hash = utils.toArray(hash, 'hex');
|
||||
|
||||
// Add version
|
||||
hash = [ 0 ].concat(hash);
|
||||
version = network.prefixes[version || 'normal'];
|
||||
hash = [ version ].concat(hash);
|
||||
|
||||
var addr = hash.concat(utils.checksum(hash));
|
||||
return utils.toBase58(addr);
|
||||
};
|
||||
|
||||
Wallet.addr2hash = function addr2hash(addr) {
|
||||
Wallet.addr2hash = function addr2hash(addr, version) {
|
||||
if (!Array.isArray(addr))
|
||||
addr = utils.fromBase58(addr);
|
||||
|
||||
version = network.prefixes[version || 'normal'];
|
||||
|
||||
if (addr.length !== 25)
|
||||
return [];
|
||||
if (addr[0] !== 0)
|
||||
if (addr[0] !== version)
|
||||
return [];
|
||||
|
||||
var chk = utils.checksum(addr.slice(0, -4));
|
||||
@ -152,15 +284,19 @@ Wallet.addr2hash = function addr2hash(addr) {
|
||||
return addr.slice(1, -4);
|
||||
};
|
||||
|
||||
Wallet.prototype.validateAddress = function validateAddress(addr) {
|
||||
var p = Wallet.addr2hash(addr);
|
||||
Wallet.prototype.validateAddress = function validateAddress(addr, version) {
|
||||
if (!addr)
|
||||
return false;
|
||||
var p = Wallet.addr2hash(addr, version);
|
||||
return p.length !== 0;
|
||||
};
|
||||
Wallet.validateAddress = Wallet.prototype.validateAddress;
|
||||
|
||||
Wallet.prototype.ownOutput = function ownOutput(tx, index) {
|
||||
var hash = this.getHash();
|
||||
var key = this.getPublicKey();
|
||||
var scriptHash = this.getFullHash();
|
||||
var hash = this.getOwnHash();
|
||||
var key = this.getOwnPublicKey();
|
||||
|
||||
var outputs = tx.outputs.filter(function(output, i) {
|
||||
if (index !== undefined && index !== i)
|
||||
return false;
|
||||
@ -176,6 +312,9 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
|
||||
if (bcoin.script.isMultisig(s, key))
|
||||
return true;
|
||||
|
||||
if (bcoin.script.isScripthash(s, scriptHash))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}, this);
|
||||
if (outputs.length === 0)
|
||||
@ -185,8 +324,9 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
|
||||
};
|
||||
|
||||
Wallet.prototype.ownInput = function ownInput(tx, index) {
|
||||
var hash = this.getHash();
|
||||
var key = this.getPublicKey();
|
||||
var scriptHash = this.getFullHash();
|
||||
var hash = this.getOwnHash();
|
||||
var key = this.getOwnPublicKey();
|
||||
|
||||
var inputs = tx.inputs.filter(function(input, i) {
|
||||
if (index !== undefined && index !== i)
|
||||
@ -206,6 +346,9 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
|
||||
if (bcoin.script.isMultisig(s, key))
|
||||
return true;
|
||||
|
||||
if (bcoin.script.isScripthash(s, scriptHash))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}, this);
|
||||
if (inputs.length === 0)
|
||||
@ -214,35 +357,22 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
|
||||
return inputs;
|
||||
};
|
||||
|
||||
Wallet.prototype.sign = function sign(tx, type, inputs, off) {
|
||||
Wallet.prototype.sign = function sign(tx, type, inputs) {
|
||||
if (!type)
|
||||
type = 'all';
|
||||
assert.equal(type, 'all');
|
||||
|
||||
if (!off)
|
||||
off = 0;
|
||||
var pub = this.getFullPublicKey();
|
||||
var key = this.key;
|
||||
|
||||
var pub = this.getPublicKey();
|
||||
inputs = inputs || tx.inputs;
|
||||
|
||||
// Add signature script to each input
|
||||
inputs = inputs.filter(function(input, i) {
|
||||
inputs = inputs.filter(function(input) {
|
||||
// Filter inputs that this wallet own
|
||||
if (!input.out.tx || !this.ownOutput(input.out.tx))
|
||||
return false;
|
||||
|
||||
var s = input.out.tx.getSubscript(input.out.index);
|
||||
var hash = tx.subscriptHash(off + i, s, type);
|
||||
var signature = bcoin.ecdsa.sign(hash, this.key).toDER();
|
||||
signature = signature.concat(bcoin.protocol.constants.hashType[type]);
|
||||
|
||||
if (bcoin.script.isPubkeyhash(s)) {
|
||||
input.script = [ signature, pub ];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Multisig
|
||||
input.script = [ [], signature ];
|
||||
tx.scriptSig(input, key, pub, type);
|
||||
|
||||
return true;
|
||||
}, this);
|
||||
@ -272,89 +402,40 @@ Wallet.prototype.balance = function balance() {
|
||||
|
||||
Wallet.prototype.fill = function fill(tx, cb) {
|
||||
cb = utils.asyncify(cb);
|
||||
|
||||
// NOTE: tx should be prefilled with all outputs
|
||||
var cost = tx.funds('out');
|
||||
|
||||
// Use initial fee for starters
|
||||
var fee = 1;
|
||||
|
||||
// total = cost + fee
|
||||
var total = cost.add(new bn(this.fee));
|
||||
|
||||
var lastAdded = 0;
|
||||
function addInput(unspent, i) {
|
||||
// Add new inputs until TX will have enough funds to cover both
|
||||
// minimum post cost and fee
|
||||
tx.input(unspent);
|
||||
lastAdded++;
|
||||
return tx.funds('in').cmp(total) < 0;
|
||||
}
|
||||
|
||||
// Transfer `total` funds maximum
|
||||
var unspent = this.unspent();
|
||||
unspent.every(addInput, this);
|
||||
|
||||
// Add dummy output (for `left`) to calculate maximum TX size
|
||||
tx.out(this, new bn(0));
|
||||
|
||||
// Change fee value if it is more than 1024 bytes
|
||||
// (10000 satoshi for every 1024 bytes)
|
||||
do {
|
||||
// Calculate maximum possible size after signing
|
||||
var byteSize = tx.maxSize();
|
||||
|
||||
var addFee = Math.ceil(byteSize / 1024) - fee;
|
||||
total.iadd(new bn(addFee * this.fee));
|
||||
fee += addFee;
|
||||
|
||||
// Failed to get enough funds, add more inputs
|
||||
if (tx.funds('in').cmp(total) < 0)
|
||||
unspent.slice(lastAdded).every(addInput, this);
|
||||
} while (tx.funds('in').cmp(total) < 0 && lastAdded < unspent.length);
|
||||
|
||||
// Still failing to get enough funds, notify caller
|
||||
if (tx.funds('in').cmp(total) < 0) {
|
||||
var result = tx.fillUnspent(this.unspent(), this.getAddress());
|
||||
if (!result) {
|
||||
var err = new Error('Not enough funds');
|
||||
err.minBalance = total;
|
||||
return cb(err);
|
||||
err.minBalance = tx.cost;
|
||||
cb(err);
|
||||
return null;
|
||||
}
|
||||
|
||||
// How much money is left after sending outputs
|
||||
var left = tx.funds('in').sub(total);
|
||||
|
||||
// Not enough money, transfer everything to owner
|
||||
if (left.cmpn(this.dust) < 0) {
|
||||
// NOTE: that this output is either `postCost` or one of the `dust` values
|
||||
tx.outputs[tx.outputs.length - 2].value.iadd(left);
|
||||
left = new bn(0);
|
||||
}
|
||||
|
||||
// Change or remove last output if there is some money left
|
||||
if (left.cmpn(0) === 0)
|
||||
tx.outputs.pop();
|
||||
else
|
||||
tx.outputs[tx.outputs.length - 1].value = left;
|
||||
|
||||
// Sign transaction
|
||||
this.sign(tx);
|
||||
|
||||
cb(null, tx);
|
||||
return tx;
|
||||
};
|
||||
|
||||
Wallet.prototype.toJSON = function toJSON() {
|
||||
return {
|
||||
v: 1,
|
||||
type: 'wallet',
|
||||
pub: this.getPublicKey('base58'),
|
||||
network: network.type,
|
||||
pub: this.getOwnPublicKey('base58'),
|
||||
priv: this.getPrivateKey('base58'),
|
||||
tx: this.tx.toJSON()
|
||||
tx: this.tx.toJSON(),
|
||||
multisig: {
|
||||
type: this.addressType,
|
||||
keys: this.sharedKeys.map(utils.toBase58),
|
||||
m: this.m,
|
||||
n: this.n
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Wallet.fromJSON = function fromJSON(json) {
|
||||
assert.equal(json.v, 1);
|
||||
assert.equal(json.type, 'wallet');
|
||||
if (json.network)
|
||||
assert.equal(json.network, network.type);
|
||||
|
||||
var priv;
|
||||
var pub;
|
||||
@ -363,7 +444,7 @@ Wallet.fromJSON = function fromJSON(json) {
|
||||
if (json.priv) {
|
||||
var key = bcoin.utils.fromBase58(json.priv);
|
||||
assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4))));
|
||||
assert.equal(key[0], 128);
|
||||
assert.equal(key[0], network.prefixes.privkey);
|
||||
|
||||
key = key.slice(0, -4);
|
||||
if (key.length === 34) {
|
||||
@ -379,10 +460,14 @@ Wallet.fromJSON = function fromJSON(json) {
|
||||
compressed = pub[0] !== 0x04;
|
||||
}
|
||||
|
||||
if (json.multisig && json.multisig.keys)
|
||||
json.multisig.keys = json.multisig.keys.map(utils.toKeyArray);
|
||||
|
||||
var w = new Wallet({
|
||||
priv: priv,
|
||||
pub: pub,
|
||||
compressed: compressed
|
||||
compressed: compressed,
|
||||
multisig: json.multisig
|
||||
});
|
||||
|
||||
w.tx.fromJSON(json.tx);
|
||||
|
||||
@ -22,9 +22,9 @@
|
||||
"homepage": "https://github.com/indutny/bcoin",
|
||||
"dependencies": {
|
||||
"async": "^0.8.0",
|
||||
"bn.js": "^0.10.0",
|
||||
"elliptic": "^0.14.1",
|
||||
"hash.js": "^0.2.0",
|
||||
"bn.js": "^4.5.0",
|
||||
"elliptic": "^6.0.2",
|
||||
"hash.js": "^1.0.3",
|
||||
"inherits": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
95
test/hd-test.js
Normal file
95
test/hd-test.js
Normal file
@ -0,0 +1,95 @@
|
||||
// var assert = require('assert');
|
||||
var bn = require('bn.js');
|
||||
var bcoin = require('../');
|
||||
var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
|
||||
describe('HD', function() {
|
||||
var phrase = 'volume doll flush federal inflict tomato result property total curtain shield aisle';
|
||||
|
||||
var seed = '5559092716434b83f158bffb51337a944529ae30d7e62d46d3be0c66fa4b36e8d60ccfd2c976b831885dc9df9ac3716ee4bf90003f25621070a49cbea58f528b';
|
||||
|
||||
var master_priv = 'xprv9s21ZrQH143K35zTejeVRhkXgegDFUVpoh8Mxs2BQmXEB4w9SZ1CuoJPuQ2KGQrS1ZF3Pk7V7KWHn7FqR2JbAE9Bh8PURnrFnrmArj4kxos';
|
||||
var master_pub = 'xpub661MyMwAqRbcFa4vkmBVnqhGEgWhewDgAv3xmFRny74D3sGHz6KTTbcskg2vZEMbEwxc4oaR435oczhSu4GdNwhwiVewcewU8A1Rr6HehAU';
|
||||
|
||||
var child1_priv = 'xprv9v414VeuxMoGt3t7jzkPni79suCfkgFwjxG38X2wgfg2mrYtV4Bhj3prhDDCcBiJrz9n4xLYoDtBFRuQmreVLKzmiZAqvbGpx5q4yHfzfah';
|
||||
var child1_pub = 'xpub693MU1BonjMa6Xxar2HQ9r3tRw3AA8yo7BBdvuSZF1D1eet32bVxGr9LYViWMtaLfQaa2StXeUmDG5VELFkU9pc3yfTzCk61WQJdR6ezj7a';
|
||||
|
||||
var child2_pub = 'xpub693MU1BonjMa8MMoz9opJhrFejcXcGmhMP9gzySLsip4Dz1UrSLT4i2pAimHDyM2onW2H2L2HkbwrZqoizQLMoErXu8mPYxDf8tJUBAfBuT';
|
||||
|
||||
var child3_priv = 'xprv9v414VeuxMoGusHLt8GowZuX6hn3Cp3qzAE6Cb2jKPH5MBgLJu2CWuiLKTdWV8WoNFYvpCcBfbpWfeyEQ8zytZW5qy39roTcugBGUkeAvCc';
|
||||
var child3_pub = 'xpub693MU1BonjMa8MMoz9opJhrFejcXcGmhMP9gzySLsip4Dz1UrSLT4i2pAimHDyM2onW2H2L2HkbwrZqoizQLMoErXu8mPYxDf8tJUBAfBuT';
|
||||
|
||||
var child4_priv = 'xprv9v414VeuxMoGyViVYuzEN5vLDzff3nkrH5Bf4KzD1iTeY855Q4cCc6xPPNoc6MJcsqqRQiGqR977cEEGK2mhVp7ALKHqY1icEw3Q9UmfQ1v';
|
||||
var child4_pub = 'xpub693MU1BonjMaBynxewXEjDs4n2W9TFUheJ7FriPpa3zdQvQDwbvT9uGsEebvioAcYbtRUU7ge4yVgj8WDLrwtwuXKTWiieFoYX2B1JYUEfK';
|
||||
|
||||
var child5_priv = 'xprv9xaK29Nm86ytEwsV9YSsL3jWYR6KtZYY3cKdjAbxHrwKyxH9YWoxvqKwtgQmExGpxAEDrwB4WK9YG1iukth3XiSgsxXLK1W3NB31gLee8fi';
|
||||
var child5_pub = 'xpub6BZfReuexUYBTRwxFZyshBgF6SvpJ2GPQqFEXZ1ZrCUJrkcJ648DUdeRjx9QiNQxQvPcHYV3rGkvuExFQbVRS4kU5ynx4fAsWWhHgyPh1pP';
|
||||
|
||||
var child6_priv = 'xprv9xaK29Nm86ytGx9uDhNKUBjvbJ1sAEM11aYxGQS66Rmg6oHwy7HbB6kWwMHvukzdbPpGhfNXhZgePWFHm1DCh5PACPFywJJKr1AnUJTLjUc';
|
||||
var child6_pub = 'xpub6BZfReuexUYBVSENKiuKqKgf9KrMZh4rNoUZ4nqhemJeybd6Webqiu4zndBwa9UB4Jvr5jB5Bcgng6reXAKCuDiVm7zhzJ13BUDBiM8HidZ';
|
||||
|
||||
var master, child1, child2, child3, child4, child5, child6;
|
||||
|
||||
it('should create a pbkdf2 seed', function() {
|
||||
var checkSeed = utils.toHex(bcoin.hd.pbkdf2(phrase, 'mnemonic' + 'foo', 2048, 64));
|
||||
assert.equal(checkSeed, seed);
|
||||
});
|
||||
|
||||
it('should create master private key', function() {
|
||||
master = bcoin.hd.priv({ seed: seed });
|
||||
assert.equal(master.xprivkey, master_priv);
|
||||
assert.equal(master.xpubkey, master_pub);
|
||||
});
|
||||
|
||||
it('should derive(0) child from master', function() {
|
||||
child1 = master.derive(0);
|
||||
assert.equal(child1.xprivkey, child1_priv);
|
||||
assert.equal(child1.xpubkey, child1_pub);
|
||||
});
|
||||
|
||||
it('should derive(1) child from master public key', function() {
|
||||
child2 = master.hdpub.derive(1);
|
||||
assert.equal(child2.xpubkey, child2_pub);
|
||||
});
|
||||
|
||||
it('should derive(1) child from master', function() {
|
||||
child3 = master.derive(1);
|
||||
assert.equal(child3.xprivkey, child3_priv);
|
||||
assert.equal(child3.xpubkey, child3_pub);
|
||||
});
|
||||
|
||||
it('should derive(2) child from master', function() {
|
||||
child4 = master.derive(2);
|
||||
assert.equal(child4.xprivkey, child4_priv);
|
||||
assert.equal(child4.xpubkey, child4_pub);
|
||||
});
|
||||
|
||||
it('should derive(0) child from child(2)', function() {
|
||||
child5 = child4.derive(0);
|
||||
assert.equal(child5.xprivkey, child5_priv);
|
||||
assert.equal(child5.xpubkey, child5_pub);
|
||||
});
|
||||
|
||||
it('should derive(1) child from child(2)', function() {
|
||||
child6 = child4.derive(1);
|
||||
assert.equal(child6.xprivkey, child6_priv);
|
||||
assert.equal(child6.xpubkey, child6_pub);
|
||||
});
|
||||
|
||||
it('should deserialize master private key', function() {
|
||||
master._unbuild(master.xprivkey);
|
||||
});
|
||||
|
||||
it('should deserialize master public key', function() {
|
||||
master.hdpub._unbuild(master.hdpub.xpubkey);
|
||||
});
|
||||
|
||||
it('should create an hd seed', function() {
|
||||
var seed = new bcoin.hd.seed({
|
||||
// I have the same combination on my luggage:
|
||||
entropy: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
passphrase: 'foo'
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -44,4 +44,46 @@ describe('Script', function() {
|
||||
var decoded = bcoin.script.decode(encoded);
|
||||
assert(bcoin.script.isNullData(decoded))
|
||||
})
|
||||
|
||||
it('should handle if statements correctly', function () {
|
||||
var inputScript = [1, 2];
|
||||
var prevOutScript = [2, 'eq', 'if_', 3, 'else_', 4, 'endif', 5];
|
||||
var stack = [];
|
||||
bcoin.script.execute(inputScript, stack);
|
||||
var res = bcoin.script.execute(prevOutScript, stack);
|
||||
assert(res);
|
||||
assert.deepEqual(stack.slice(), [[1], [3], [5]]);
|
||||
|
||||
var inputScript = [1, 2];
|
||||
var prevOutScript = [9, 'eq', 'if_', 3, 'else_', 4, 'endif', 5];
|
||||
var stack = [];
|
||||
bcoin.script.execute(inputScript, stack);
|
||||
var res = bcoin.script.execute(prevOutScript, stack);
|
||||
assert(res);
|
||||
assert.deepEqual(stack.slice(), [[1], [4], [5]]);
|
||||
|
||||
var inputScript = [1, 2];
|
||||
var prevOutScript = [2, 'eq', 'if_', 3, 'endif', 5];
|
||||
var stack = [];
|
||||
bcoin.script.execute(inputScript, stack);
|
||||
var res = bcoin.script.execute(prevOutScript, stack);
|
||||
assert(res);
|
||||
assert.deepEqual(stack.slice(), [[1], [3], [5]]);
|
||||
|
||||
var inputScript = [1, 2];
|
||||
var prevOutScript = [9, 'eq', 'if_', 3, 'endif', 5];
|
||||
var stack = [];
|
||||
bcoin.script.execute(inputScript, stack);
|
||||
var res = bcoin.script.execute(prevOutScript, stack);
|
||||
assert(res);
|
||||
assert.deepEqual(stack.slice(), [[1], [5]]);
|
||||
|
||||
var inputScript = [1, 2];
|
||||
var prevOutScript = [9, 'eq', 'notif', 3, 'endif', 5];
|
||||
var stack = [];
|
||||
bcoin.script.execute(inputScript, stack);
|
||||
var res = bcoin.script.execute(prevOutScript, stack);
|
||||
assert(res);
|
||||
assert.deepEqual(stack.slice(), [[1], [3], [5]]);
|
||||
})
|
||||
});
|
||||
|
||||
@ -2,6 +2,33 @@ var assert = require('assert');
|
||||
var bn = require('bn.js');
|
||||
var bcoin = require('../');
|
||||
|
||||
function printScript(input) {
|
||||
var scripts = [];
|
||||
var script = input.script;
|
||||
scripts.push(script);
|
||||
var prev = input.out.tx.outputs[input.out.index].script;
|
||||
scripts.push(prev);
|
||||
if (bcoin.script.isScripthash(prev)) {
|
||||
var redeem = bcoin.script.decode(input.script[input.script.length - 1]);
|
||||
scripts.push(redeem);
|
||||
}
|
||||
scripts = scripts.map(function(script) {
|
||||
return script.map(function(chunk) {
|
||||
if (Array.isArray(chunk)) {
|
||||
if (chunk.length === 0)
|
||||
return [0];
|
||||
return [bcoin.utils.toHex(chunk)];
|
||||
}
|
||||
if (typeof chunk === 'number')
|
||||
return [chunk];
|
||||
return chunk;
|
||||
});
|
||||
});
|
||||
scripts.forEach(function(script) {
|
||||
console.log(script);
|
||||
});
|
||||
}
|
||||
|
||||
describe('Wallet', function() {
|
||||
it('should generate new key and address', function() {
|
||||
var w = bcoin.wallet();
|
||||
@ -46,13 +73,15 @@ describe('Wallet', function() {
|
||||
|
||||
it('should multisign/verify TX', function() {
|
||||
var w = bcoin.wallet();
|
||||
var k2 = bcoin.wallet().getPublicKey();
|
||||
|
||||
// Input transcation
|
||||
var src = bcoin.tx({
|
||||
outputs: [{
|
||||
value: 5460 * 2,
|
||||
minSignatures: 1,
|
||||
address: [ w.getPublicKey(), w.getPublicKey().concat(1) ]
|
||||
keys: [ w.getPublicKey(), w.getPublicKey().concat(1) ]
|
||||
// keys: [ w.getPublicKey(), k2 ]
|
||||
}, {
|
||||
value: 5460 * 2,
|
||||
address: w.getAddress() + 'x'
|
||||
@ -223,4 +252,88 @@ describe('Wallet', function() {
|
||||
|
||||
cb();
|
||||
});
|
||||
|
||||
it('should verify 2-of-3 p2sh tx', function(cb) {
|
||||
var hd = bcoin.hd.priv();
|
||||
var hd1 = hd.derive(0);
|
||||
var hd2 = hd.derive(1);
|
||||
var hd3 = hd.derive(2);
|
||||
|
||||
// Generate 3 key pairs
|
||||
var key1 = bcoin.ecdsa.genKeyPair();
|
||||
var key2 = bcoin.ecdsa.genKeyPair();
|
||||
var key3 = bcoin.ecdsa.genKeyPair();
|
||||
|
||||
// var key1 = hd1;
|
||||
// var key2 = hd2;
|
||||
// var key3 = hd3;
|
||||
|
||||
// Grab the 3 pubkeys
|
||||
var pub1 = key1.getPublic(true, 'array');
|
||||
var pub2 = key2.getPublic(true, 'array');
|
||||
var pub3 = key3.getPublic(true, 'array');
|
||||
|
||||
// Create 3 2-of-3 wallets with our pubkeys as "shared keys"
|
||||
var w1 = bcoin.wallet({
|
||||
key: key1,
|
||||
multisig: {
|
||||
type: 'p2sh',
|
||||
keys: [pub2, pub3],
|
||||
m: 2,
|
||||
n: 3
|
||||
}
|
||||
});
|
||||
var w2 = bcoin.wallet({
|
||||
key: key2,
|
||||
multisig: {
|
||||
type: 'p2sh',
|
||||
keys: [pub1, pub3],
|
||||
m: 2,
|
||||
n: 3
|
||||
}
|
||||
});
|
||||
var w3 = bcoin.wallet({
|
||||
key: key3,
|
||||
multisig: {
|
||||
type: 'p2sh',
|
||||
keys: [pub1, pub2],
|
||||
m: 2,
|
||||
n: 3
|
||||
}
|
||||
});
|
||||
var receive = bcoin.wallet();
|
||||
|
||||
// Our p2sh address
|
||||
var addr = w1.getAddress();
|
||||
assert.equal(w1.getAddress(), addr);
|
||||
assert.equal(w2.getAddress(), addr);
|
||||
assert.equal(w3.getAddress(), addr);
|
||||
|
||||
// Add a shared unspent transaction to our wallets
|
||||
var utx = bcoin.tx();
|
||||
utx.output({ address: addr, value: 5460 * 10 });
|
||||
|
||||
w1.addTX(utx);
|
||||
w2.addTX(utx);
|
||||
w3.addTX(utx);
|
||||
|
||||
// Create a tx requiring 2 signatures
|
||||
var send = bcoin.tx();
|
||||
send.output({ address: receive.getAddress(), value: 5460 });
|
||||
assert(!send.verify());
|
||||
var result = w1.fill(send);
|
||||
assert(result);
|
||||
|
||||
// printScript(send.inputs[0]);
|
||||
|
||||
assert(!send.verify());
|
||||
w2.sign(send);
|
||||
|
||||
assert(send.verify());
|
||||
|
||||
send.inputs[0].script[2] = [];
|
||||
assert(!send.verify());
|
||||
|
||||
cb();
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user