hd and p2sh
This commit is contained in:
parent
3bfd10d0d0
commit
ea9af49f4b
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,11 @@
|
||||
var bcoin = exports;
|
||||
var elliptic = require('elliptic');
|
||||
|
||||
bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1);
|
||||
if (elliptic.ec) {
|
||||
bcoin.ecdsa = elliptic.ec('secp256k1');
|
||||
} else {
|
||||
bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1);
|
||||
}
|
||||
bcoin.utils = require('./bcoin/utils');
|
||||
bcoin.bloom = require('./bcoin/bloom');
|
||||
bcoin.protocol = require('./bcoin/protocol');
|
||||
@ -13,3 +17,4 @@ 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');
|
||||
|
||||
@ -427,6 +427,76 @@ Chain.prototype.getStartHeight = function getLast(cb) {
|
||||
}
|
||||
};
|
||||
|
||||
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.locatorHashes = function(index) {
|
||||
var chain = this.index.hashes;
|
||||
var hashes = [];
|
||||
var top = chain.length - 1;
|
||||
var step = 1, start = 0;
|
||||
|
||||
if (typeof index === 'string') {
|
||||
for (i = top; i >= 0; i--) {
|
||||
if (chain[i] === index) {
|
||||
top = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (typeof index === 'number') {
|
||||
top = index;
|
||||
}
|
||||
|
||||
for (var i = top; i > 0; i -= step, ++start) {
|
||||
if (start >= 10) step *= 2;
|
||||
hashes.push(chain[i]);
|
||||
}
|
||||
|
||||
hashes.push(chain[0]);
|
||||
|
||||
return hashes;
|
||||
};
|
||||
|
||||
Chain.prototype.__defineGetter__('orphan_bmap', function() {
|
||||
var self = this;
|
||||
return Object.keys(this.orphan.map).reduce(function(out, prev) {
|
||||
var orphan = self.orphan.map[prev];
|
||||
out[orphan.hash('hex')] = orphan;
|
||||
return out;
|
||||
}, {});
|
||||
});
|
||||
|
||||
Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) {
|
||||
var self = this;
|
||||
|
||||
@ -434,14 +504,7 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) {
|
||||
hash = hash.hash('hex');
|
||||
}
|
||||
|
||||
/*
|
||||
var orphans = Object.keys(this.orphan.map).reduce(function(out, prev) {
|
||||
var orphan = self.orphan.map[prev];
|
||||
out[orphan.hash('hex')] = orphan;
|
||||
return out;
|
||||
}, {});
|
||||
*/
|
||||
var orphans = this.orphan.bmap;
|
||||
var orphans = this.orphan_bmap;
|
||||
|
||||
/*
|
||||
var orphanRoot = hash;
|
||||
|
||||
739
lib/bcoin/hd.js
Normal file
739
lib/bcoin/hd.js
Normal file
@ -0,0 +1,739 @@
|
||||
/**
|
||||
* 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 EventEmitter = require('events').EventEmitter;
|
||||
var crypto = require('crypto');
|
||||
|
||||
var english = require('../../etc/english.json');
|
||||
|
||||
var ec;
|
||||
if (!elliptic.curves) {
|
||||
ec = elliptic.nist.secp256k1;
|
||||
ec.curve.g = ec.g;
|
||||
ec.curve.n = ec.n;
|
||||
} else {
|
||||
ec = elliptic.curves.secp256k1;
|
||||
}
|
||||
var ecPoint = ec.curve.point.bind(ec.curve);
|
||||
var ecPointFromX = ec.curve.pointFromX.bind(ec.curve);
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// XXX Do not use crypto
|
||||
return Array.prototype.slice.call(crypto.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 VERSION = {
|
||||
xpubkey: 0x0488b21e,
|
||||
xprivkey: 0x0488ade4
|
||||
};
|
||||
|
||||
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, VERSION.xprivkey);
|
||||
|
||||
this.data = data;
|
||||
|
||||
this._build(data);
|
||||
}
|
||||
|
||||
HDPriv.prototype._normalize = function(data, version) {
|
||||
var b;
|
||||
data.version = version || VERSION.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.toArray(data.privateKey, 'hex');
|
||||
}
|
||||
}
|
||||
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.toArray(data.publicKey, 'hex');
|
||||
}
|
||||
}
|
||||
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 (typeof seed === 'string' && /^[0-9a-f]+$/i.test(seed)) {
|
||||
seed = utils.toArray(seed, 'hex');
|
||||
}
|
||||
if (seed.length < MIN_ENTROPY || seed.length > MAX_ENTROPY) {
|
||||
throw new Error;
|
||||
}
|
||||
var hash = sha512hmac(seed, 'Bitcoin seed');
|
||||
return {
|
||||
// version: VERSION.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;
|
||||
if (hard) {
|
||||
data = [0].concat(this.privateKey).concat(index_);
|
||||
} else {
|
||||
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; // cast to number
|
||||
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, VERSION.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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (index < 0) {
|
||||
throw new Error;
|
||||
}
|
||||
|
||||
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: VERSION.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);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
function sha512hmac(data, salt) {
|
||||
// XXX Do not use crypto
|
||||
var hmac = crypto.createHmac('sha512', new Buffer(salt));
|
||||
var hash = hmac.update(new Buffer(data)).digest();
|
||||
return Array.prototype.slice.call(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Test
|
||||
*/
|
||||
|
||||
var phrase = 'volume doll flush federal inflict tomato result property total curtain shield aisle';
|
||||
var checkSeed = utils.toHex(pbkdf2(phrase, 'mnemonic' + 'foo', 2048, 64));
|
||||
var seed = '5559092716434b83f158bffb51337a944529ae30d7e62d46d3be0c66fa4b36e8d60ccfd2c976b831885dc9df9ac3716ee4bf90003f25621070a49cbea58f528b';
|
||||
assert.equal(checkSeed, seed);
|
||||
|
||||
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 = hd.priv({ seed: seed });
|
||||
var child1 = master.derive(0);
|
||||
var child2 = master.hdpub.derive(1);
|
||||
var child3 = master.derive(1);
|
||||
var child4 = master.derive(2);
|
||||
var child5 = child4.derive(0);
|
||||
var child6 = child4.derive(1);
|
||||
master._unbuild(master.xprivkey);
|
||||
master.hdpub._unbuild(master.hdpub.xpubkey);
|
||||
|
||||
// console.log(new HDSeed({
|
||||
// // 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'
|
||||
// }));
|
||||
|
||||
try {
|
||||
assert.equal(master.xprivkey, master_priv);
|
||||
assert.equal(master.xpubkey, master_pub);
|
||||
|
||||
assert.equal(child1.xprivkey, child1_priv);
|
||||
assert.equal(child1.xpubkey, child1_pub);
|
||||
|
||||
assert.equal(child2.xpubkey, child2_pub);
|
||||
|
||||
assert.equal(child3.xprivkey, child3_priv);
|
||||
assert.equal(child3.xpubkey, child3_pub);
|
||||
|
||||
assert.equal(child4.xprivkey, child4_priv);
|
||||
assert.equal(child4.xpubkey, child4_pub);
|
||||
|
||||
assert.equal(child5.xprivkey, child5_priv);
|
||||
assert.equal(child5.xpubkey, child5_pub);
|
||||
|
||||
assert.equal(child6.xprivkey, child6_priv);
|
||||
assert.equal(child6.xpubkey, child6_pub);
|
||||
} catch (e) {
|
||||
console.log('master xprivkey: %s', master.xprivkey);
|
||||
console.log('master xpubkey: %s', master.xpubkey);
|
||||
console.log('-');
|
||||
console.log('child1 xprivkey: %s', child1.xprivkey);
|
||||
console.log('child1 xpubkey: %s', child1.xpubkey);
|
||||
console.log('-');
|
||||
console.log('child2 xpubkey: %s', child2.xpubkey);
|
||||
console.log('-');
|
||||
console.log('child3 xprivkey: %s', child3.xprivkey);
|
||||
console.log('child3 xpubkey: %s', child3.xpubkey);
|
||||
console.log('-');
|
||||
console.log('child3 xprivkey: %s', child4.xprivkey);
|
||||
console.log('child3 xpubkey: %s', child4.xpubkey);
|
||||
console.log('-');
|
||||
console.log('child5 xprivkey: %s', child5.xprivkey);
|
||||
console.log('child5 xpubkey: %s', child5.xpubkey);
|
||||
console.log('-');
|
||||
console.log('child6 xprivkey: %s', child6.xprivkey);
|
||||
console.log('child6 xpubkey: %s', child6.xpubkey);
|
||||
throw e;
|
||||
}
|
||||
@ -94,8 +94,7 @@ Peer.prototype._init = function init() {
|
||||
var ip = self.socket && self.socket.remoteAddress || '0.0.0.0';
|
||||
self.pool.emit('debug', 'version (%s): loading locator hashes for getblocks', ip);
|
||||
self.chain.on('load', function() {
|
||||
// self.loadBlocks(self.pool.locatorHashes(), 0);
|
||||
self.loadHeaders(self.pool.locatorHashes(), 0);
|
||||
self.loadHeaders(self.chain.locatorHashes(), 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -438,61 +437,6 @@ Peer.prototype._handleInv = function handleInv(items) {
|
||||
});
|
||||
this.emit('blocks', blocks);
|
||||
|
||||
if (0)
|
||||
if (this.pool.options.fullNode) {
|
||||
var self = this;
|
||||
|
||||
if (txs.length) {
|
||||
this.emit('txs', txs.map(function(tx) {
|
||||
return tx.hash;
|
||||
}));
|
||||
this.getData(txs);
|
||||
}
|
||||
|
||||
if (blocks.length === 1) {
|
||||
this._rChainHead = utils.toHex(blocks[0]);
|
||||
}
|
||||
|
||||
var orph = blocks.filter(function(block) {
|
||||
return self._requestingOrphan === utils.toHex(block);
|
||||
});
|
||||
|
||||
if (orph.length) {
|
||||
utils.debug('FOUND MISSING BLOCK');
|
||||
utils.debug('FOUND MISSING BLOCK');
|
||||
utils.debug('FOUND MISSING BLOCK');
|
||||
}
|
||||
|
||||
/*
|
||||
var orphans = Object.keys(self.chain.orphan.map).reduce(function(out, prev) {
|
||||
var orphan = self.chain.orphan.map[prev];
|
||||
out[orphan.hash('hex')] = orphan;
|
||||
return out;
|
||||
}, {});
|
||||
*/
|
||||
|
||||
var orphans = self.chain.orphan.bmap;
|
||||
|
||||
for (var i = 0; i < blocks.length; i++) {
|
||||
var hash = utils.toHex(blocks[i]);
|
||||
if (orphans[hash]) {
|
||||
this.loadBlocks(this.pool.locatorHashes(), this.chain.getOrphanRoot(hash));
|
||||
continue;
|
||||
}
|
||||
if (!this.chain.index.bloom.test(hash, 'hex')) {
|
||||
this.getData([{ type: 'block', hash: hash }]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
this.getData(blocks.map(function(block) {
|
||||
return { type: 'block', hash: utils.toHex(block) };
|
||||
}));
|
||||
*/
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (txs.length === 0)
|
||||
return;
|
||||
|
||||
@ -504,36 +448,30 @@ Peer.prototype._handleInv = function handleInv(items) {
|
||||
|
||||
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);
|
||||
var abbr = bcoin.block.prototype.abbr.call(header);
|
||||
header._hash = utils.toHex(utils.dsha256(abbr));
|
||||
header.hash = utils.toHex(utils.dsha256(header._raw));
|
||||
return header;
|
||||
});
|
||||
|
||||
if (this.pool.options.fullNode) {
|
||||
var self = this;
|
||||
|
||||
if (headers.length === 1) {
|
||||
this._rChainHead = headers[0]._hash;
|
||||
}
|
||||
|
||||
var orphans = self.chain.orphan.bmap;
|
||||
|
||||
for (var i = 0; i < headers.length; i++) {
|
||||
var hash = headers[i]._hash;
|
||||
if (orphans[hash]) {
|
||||
this.loadHeaders(this.pool.locatorHashes(), this.chain.getOrphanRoot(hash));
|
||||
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')) {
|
||||
if (!this.chain.index.bloom.test(hash, 'hex') || i === headers.length - 1) {
|
||||
this.getData([{ type: 'block', hash: hash }]);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('headers', headers);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -327,25 +327,21 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
|
||||
peer.on('block', function(block) {
|
||||
backoff = 0;
|
||||
|
||||
var hash = block.hash('hex');
|
||||
if (hash === peer._requestingOrphan) {
|
||||
delete peer._requestingOrphan;
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
// Otherwise, accept block
|
||||
self._response(block);
|
||||
self.chain.add(block);
|
||||
|
||||
if (self.chain.orphan.map[block.prevBlock] || !self.chain.index.bloom.test(block.prevBlock)) {
|
||||
// if (!self.chain.index.bloom.test(block.prevBlock)) {
|
||||
if (peer._requestingOrphan) return;
|
||||
var orphanRoot = self.chain.getOrphanRoot(block);
|
||||
peer._requestingOrphan = orphanRoot;
|
||||
peer.loadHeaders(self.locatorHashes(), orphanRoot);
|
||||
} else if (self.chain.index.hashes.length === height) {
|
||||
return;
|
||||
}
|
||||
// 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);
|
||||
@ -391,68 +387,6 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
|
||||
});
|
||||
};
|
||||
|
||||
Pool.prototype.locatorHashes = function(index) {
|
||||
var chain = this.chain.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;
|
||||
};
|
||||
|
||||
Pool.prototype.locatorHashes = function(index) {
|
||||
var self = this;
|
||||
var hashes = this.chain.index.hashes;
|
||||
var indicies = [];
|
||||
var top = hashes.length - 1;
|
||||
var step = 1, start = 0;
|
||||
|
||||
if (typeof index === 'string') {
|
||||
for (i = top; i >= 0; i--) {
|
||||
if (chain[i] === index) {
|
||||
top = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (typeof index === 'number') {
|
||||
top = index;
|
||||
}
|
||||
|
||||
for (var i = top; i > 0; i -= step, ++start) {
|
||||
if (start >= 10) step *= 2;
|
||||
indicies.push(hashes[i]);
|
||||
}
|
||||
|
||||
indicies.push(hashes[0]);
|
||||
|
||||
return indicies;
|
||||
};
|
||||
|
||||
Pool.prototype._removePeer = function _removePeer(peer) {
|
||||
var i = this.peers.pending.indexOf(peer);
|
||||
if (i !== -1)
|
||||
|
||||
@ -19,6 +19,12 @@ exports.genesis = {
|
||||
nonce: 2083236893
|
||||
};
|
||||
|
||||
// address versions
|
||||
exports.addr = {
|
||||
normal: 0,
|
||||
p2sh: 5
|
||||
};
|
||||
|
||||
// version - services field
|
||||
exports.services = {
|
||||
network: 1
|
||||
@ -135,6 +141,9 @@ exports.opcodes = {
|
||||
for (var i = 1; i <= 16; i++)
|
||||
exports.opcodes[i] = 0x50 + i;
|
||||
|
||||
exports.opcodes['false'] = exports.opcodes['0'];
|
||||
exports.opcodes['true'] = exports.opcodes['1'];
|
||||
|
||||
exports.opcodesByVal = new Array(256);
|
||||
Object.keys(exports.opcodes).forEach(function(name) {
|
||||
exports.opcodesByVal[exports.opcodes[name]] = name;
|
||||
|
||||
@ -237,6 +237,7 @@ Parser.prototype.parseHeaders = function parseHeaders(p) {
|
||||
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);
|
||||
@ -252,6 +253,7 @@ Parser.prototype.parseHeaders = function parseHeaders(p) {
|
||||
var r = readIntv(p, off);
|
||||
header.totalTX = r.r;
|
||||
off = r.off;
|
||||
header._raw = p.slice(start, start + 80);
|
||||
headers.push(header);
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +62,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);
|
||||
|
||||
@ -128,17 +128,45 @@ TX.prototype.out = function out(output, value) {
|
||||
|
||||
var script = output.script ? output.script.slice() : [];
|
||||
|
||||
// Multisig script if given addresses
|
||||
if (Array.isArray(output.keys || output.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 = output.keys || output.address;
|
||||
if (keys === output.address) {
|
||||
keys = keys.map(function(address) {
|
||||
return bcoin.wallet.addr2hash(address);
|
||||
});
|
||||
}
|
||||
keys = keys.map(function(key) {
|
||||
if (typeof key === 'string') {
|
||||
return utils.toKeyArray(key);
|
||||
}
|
||||
return key;
|
||||
});
|
||||
script = [
|
||||
[ output.minSignatures || keys.length ]
|
||||
].concat(
|
||||
keys,
|
||||
[ [ keys.length ], 'checkmultisig' ]
|
||||
);
|
||||
// Default script if given address
|
||||
// outputs: [ [ 2 ], 'key1', 'key2', [ 2 ], 'checkmultisig' ]
|
||||
// in reality:
|
||||
// outputs: [ [ 2 ], [0,1,...], [2,3,...], [ 2 ], 'checkmultisig' ]
|
||||
} else if (bcoin.wallet.validAddress(output.address, 'p2sh')) {
|
||||
// p2sh transaction
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki
|
||||
// hash160 [20-byte-redeemscript-hash] equal
|
||||
script = [
|
||||
'hash160',
|
||||
bcoin.wallet.addr2hash(output.address, 'p2sh'),
|
||||
'eq'
|
||||
];
|
||||
} else if (output.address) {
|
||||
// p2pkh transaction
|
||||
// dup hash160 [pubkey-hash] equalverify checksig
|
||||
script = [
|
||||
'dup',
|
||||
'hash160',
|
||||
@ -207,6 +235,23 @@ TX.prototype.verify = function verify(index, force) {
|
||||
}, this);
|
||||
};
|
||||
|
||||
TX.prototype.signature = function signature(tx, key, index, sighash) {
|
||||
if (typeof index !== 'number') {
|
||||
index = tx.inputs.indexOf(index);
|
||||
if (index === -1) return;
|
||||
}
|
||||
var input = tx.inputs[index];
|
||||
var subscript = input.out.tx.getSubscript(input.out.index);
|
||||
var hash = tx.subscriptHash(input, subscript, sighash);
|
||||
var signature = bcoin.ecdsa.sign(hash, key).toDER();
|
||||
signature = signature.concat(bcoin.protocol.constants.hashType[sighash]);
|
||||
return signature;
|
||||
};
|
||||
|
||||
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();
|
||||
@ -300,7 +345,3 @@ TX.fromJSON = function fromJSON(json) {
|
||||
|
||||
return tx;
|
||||
};
|
||||
|
||||
TX.prototype.clone = function clone() {
|
||||
return new TX(new bcoin.protocol.parser().parseTX(this.render()));
|
||||
};
|
||||
|
||||
@ -467,6 +467,20 @@ 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.debug = function(msg) {
|
||||
console.log('\x1b[31m' + msg + '\x1b[m');
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ var inherits = require('inherits');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
var constants = bcoin.protocol.constants;
|
||||
|
||||
function Wallet(options, passphrase) {
|
||||
if (!(this instanceof Wallet))
|
||||
@ -28,8 +29,18 @@ function Wallet(options, passphrase) {
|
||||
this.key = null;
|
||||
this.loaded = false;
|
||||
this.lastTs = 0;
|
||||
this.publicKeys = options.publicKeys;
|
||||
|
||||
if (options.passphrase) {
|
||||
if (options.priv instanceof bcoin.hd.priv) {
|
||||
this.hd = options.priv;
|
||||
this.key = this.hd.pair;
|
||||
} else if (options.pub instanceof bcoin.hd.pub) {
|
||||
this.hd = options.pub;
|
||||
this.key = this.hd.pair;
|
||||
} else if (options.hd) {
|
||||
this.hd = bcoin.hd.priv(options);
|
||||
this.key = this.hd.pair;
|
||||
} else if (options.passphrase) {
|
||||
this.key = bcoin.ecdsa.genKeyPair({
|
||||
pers: options.scope,
|
||||
entropy: hash.sha256().update(options.passphrase).digest()
|
||||
@ -47,6 +58,26 @@ function Wallet(options, passphrase) {
|
||||
this.fee = 10000;
|
||||
this.dust = 5460;
|
||||
|
||||
if (options.m != null) {
|
||||
this.m = options.m;
|
||||
this.n = options.n;
|
||||
this.publicKeys = options.publicKeys || [];
|
||||
|
||||
if (this.n < this.publicKeys.length) {
|
||||
this.publicKeys.push(this.getPublicKey('base58'));
|
||||
}
|
||||
|
||||
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.publicKeys.length !== this.n) {
|
||||
throw new Error(this.n + ' public keys required');
|
||||
}
|
||||
}
|
||||
|
||||
this._init();
|
||||
}
|
||||
inherits(Wallet, EventEmitter);
|
||||
@ -114,6 +145,8 @@ Wallet.prototype.getPublicKey = function getPublicKey(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;
|
||||
};
|
||||
@ -126,23 +159,25 @@ Wallet.prototype.getAddress = function getAddress() {
|
||||
return Wallet.hash2addr(this.getHash());
|
||||
};
|
||||
|
||||
Wallet.hash2addr = function hash2addr(hash) {
|
||||
Wallet.hash2addr = function hash2addr(hash, version) {
|
||||
hash = utils.toArray(hash, 'hex');
|
||||
|
||||
// Add version
|
||||
hash = [ 0 ].concat(hash);
|
||||
version = constants.addr[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 = constants.addr[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,6 +187,13 @@ Wallet.addr2hash = function addr2hash(addr) {
|
||||
return addr.slice(1, -4);
|
||||
};
|
||||
|
||||
Wallet.validAddress = function validAddr(addr, version) {
|
||||
if (!addr)
|
||||
return false;
|
||||
|
||||
return !!Wallet.addr2hash(addr, version).length;
|
||||
};
|
||||
|
||||
Wallet.prototype.validateAddress = function validateAddress(addr) {
|
||||
var p = Wallet.addr2hash(addr);
|
||||
return p.length !== 0;
|
||||
@ -161,6 +203,7 @@ Wallet.validateAddress = Wallet.prototype.validateAddress;
|
||||
Wallet.prototype.ownOutput = function ownOutput(tx, index) {
|
||||
var hash = this.getHash();
|
||||
var key = this.getPublicKey();
|
||||
|
||||
var outputs = tx.outputs.filter(function(output, i) {
|
||||
if (index !== undefined && index !== i)
|
||||
return false;
|
||||
@ -176,6 +219,11 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
|
||||
if (bcoin.script.isMultisig(s, key))
|
||||
return true;
|
||||
|
||||
if (bcoin.script.isScripthash(s)
|
||||
&& utils.isEqual(s[1], this.getP2SHHash())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, this);
|
||||
if (outputs.length === 0)
|
||||
@ -206,6 +254,11 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
|
||||
if (bcoin.script.isMultisig(s, key))
|
||||
return true;
|
||||
|
||||
if (bcoin.script.isScripthash(s)
|
||||
&& utils.isEqual(s[1], this.getP2SHHash())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, this);
|
||||
if (inputs.length === 0)
|
||||
@ -217,7 +270,8 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
|
||||
Wallet.prototype.sign = function sign(tx, type, inputs, off) {
|
||||
if (!type)
|
||||
type = 'all';
|
||||
assert.equal(type, 'all');
|
||||
|
||||
// assert.equal(type, 'all');
|
||||
|
||||
if (!off)
|
||||
off = 0;
|
||||
@ -236,13 +290,31 @@ Wallet.prototype.sign = function sign(tx, type, inputs, off) {
|
||||
var signature = bcoin.ecdsa.sign(hash, this.key).toDER();
|
||||
signature = signature.concat(bcoin.protocol.constants.hashType[type]);
|
||||
|
||||
if (bcoin.script.isPubkeyhash(s)) {
|
||||
if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) {
|
||||
input.script = [ signature, pub ];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Multisig
|
||||
input.script = [ [], signature ];
|
||||
// empty array == OP_FALSE == OP_0
|
||||
// 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)) {
|
||||
// XXX Check own?
|
||||
// || (bcoin.script.isScripthash(s) && utils.isEqual(s[1], this.getP2SHHash())) {
|
||||
if (!input.script || !input.script.length) {
|
||||
input.script = [ [], signature ];
|
||||
} else if (!~input.script.indexOf(signature)) {
|
||||
input.script.push(signature);
|
||||
}
|
||||
}
|
||||
|
||||
if (bcoin.script.isScripthash(s)) {
|
||||
if (input.script.length - 1 === this.n) {
|
||||
input.script.push(this.getP2SHRedemption());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}, this);
|
||||
@ -391,6 +463,50 @@ Wallet.prototype.getChange = function fill(tx) {
|
||||
return this.fill(tx, { _getChange: true });
|
||||
};
|
||||
|
||||
/**
|
||||
* P2SH (and Multisig)
|
||||
*/
|
||||
|
||||
Wallet.prototype.getP2SHHash = function() {
|
||||
return this.getP2SH().hash;
|
||||
};
|
||||
|
||||
Wallet.prototype.getP2SHAddress = function() {
|
||||
return this.getP2SH().address;
|
||||
};
|
||||
|
||||
Wallet.prototype.getP2SHRedemption = function() {
|
||||
return this.getP2SH().redemption;
|
||||
};
|
||||
|
||||
Wallet.prototype.getP2SH = function(redeem) {
|
||||
this.publicKeys = this.publicKeys.map(function(key) {
|
||||
return utils.toKeyArray(key);
|
||||
});
|
||||
var redemption = redeem || this._createMultisigRedemption();
|
||||
var hash = utils.ripasha(redemption);
|
||||
return {
|
||||
hash: hash,
|
||||
address: bcoin.wallet.hash2addr(hash, 'p2sh'),
|
||||
redemption: redemption
|
||||
};
|
||||
};
|
||||
|
||||
Wallet.prototype._createMultisigRedemption = function() {
|
||||
var publicKeys = this.publicKeys;
|
||||
var mcode = constants.opcodes['1'] + (this.m - 1);
|
||||
var ncode = script.push(constants.opcodes['1'] + (this.n - 1));
|
||||
var redemption = [];
|
||||
redemption.push(mcode);
|
||||
this.publicKeys.forEach(function(pubkey) {
|
||||
redemption.push(pubkey.length);
|
||||
redemption = redemption.concat(pubkey);
|
||||
}, this);
|
||||
redemption.push(ncode);
|
||||
redemption.push(constants.opcodes.checkmultisig);
|
||||
return redemption;
|
||||
};
|
||||
|
||||
Wallet.prototype.toJSON = function toJSON() {
|
||||
return {
|
||||
v: 1,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user