refactor: hd, script, and wallet.

This commit is contained in:
Christopher Jeffrey 2016-08-24 04:08:40 -07:00
parent 177272a2c5
commit bdf5058546
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
28 changed files with 5241 additions and 5096 deletions

View File

@ -14,7 +14,6 @@ var utils = require('../utils/utils');
var assert = utils.assert;
var BufferWriter = require('../utils/writer');
var BufferReader = require('../utils/reader');
var InvItem = bcoin.packets.InvItem;
/**
* Represents an entry in the chain. Unlike
@ -633,7 +632,7 @@ ChainEntry.prototype.toHeaders = function toHeaders() {
*/
ChainEntry.prototype.toInv = function toInv() {
return new InvItem(constants.inv.BLOCK, this.hash);
return new bcoin.invitem(constants.inv.BLOCK, this.hash);
};
/**

View File

@ -136,17 +136,22 @@ function Environment() {
this.ldb = require('./db/ldb');
this.rbt = require('./db/rbt');
// Script
this.script = require('./script/script');
this.opcode = require('./script/opcode');
this.stack = require('./script/stack');
this.witness = require('./script/witness');
this.program = require('./script/program');
this.sc = require('./script/sigcache');
// Primitives
this.bloom = require('./primitives/bloom');
this.script = require('./primitives/script');
this.opcode = this.script.Opcode;
this.stack = this.script.Stack;
this.witness = this.script.Witness;
this.address = require('./primitives/address');
this.outpoint = require('./primitives/outpoint');
this.input = require('./primitives/input');
this.outpoint = this.input.Outpoint;
this.output = require('./primitives/output');
this.coin = require('./primitives/coin');
this.invitem = require('./primitives/invitem');
this.tx = require('./primitives/tx');
this.mtx = require('./primitives/mtx');
this.abstractblock = require('./primitives/abstractblock');
@ -155,7 +160,9 @@ function Environment() {
this.merkleblock = require('./primitives/merkleblock');
this.headers = require('./primitives/headers');
this.keyring = require('./primitives/keyring');
this.hd = require('./primitives/hd');
// HD
this.hd = require('./hd/hd');
// Node
this.logger = require('./node/logger');
@ -167,7 +174,6 @@ function Environment() {
// Net
this.timedata = require('./net/timedata');
this.packets = require('./net/packets');
this.invitem = this.packets.InvItem;
this.bip150 = require('./net/bip150');
this.bip151 = require('./net/bip151');
this.bip152 = require('./net/bip152');
@ -191,11 +197,10 @@ function Environment() {
this.minerblock = require('./miner/minerblock');
// Wallet
this.txdb = require('./wallet/txdb');
this.wallet = require('./wallet/wallet');
this.account = this.wallet.Account;
this.account = require('./wallet/account');
this.walletdb = require('./wallet/walletdb');
this.path = this.walletdb.Path;
this.path = require('./wallet/path');
// HTTP
this.http = require('./http');
@ -203,9 +208,6 @@ function Environment() {
// Workers
this.workers = require('./workers/workers');
// Sigcache
this.sc = require('./sigcache');
// Global Instances
this.sigcache = new this.sc(0);
this.time = new this.timedata();

74
lib/bcoin/hd/README.md Normal file
View File

@ -0,0 +1,74 @@
HD mnemonics and keys (BIP32, BIP39) for bcoin.
Parts of this software were originally based on 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
- https://github.com/bitpay/bitcore-mnemonic/blob/master/lib/mnemonic.js
BIP32
```
Copyright (c) 2015-2016, Christopher Jeffrey (MIT License).
https://github.com/bcoin-org/bcoin
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.
```
BIP39
```
The MIT License (MIT)
Copyright (c) 2014 BitPay
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.
```

253
lib/bcoin/hd/hd.js Normal file
View File

@ -0,0 +1,253 @@
/*!
* hd.js - hd keys for bcoin
* Copyright (c) 2015-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var utils = require('../utils/utils');
var assert = utils.assert;
var constants = bcoin.constants;
var LRU = require('../utils/lru');
var Mnemonic = require('./mnemonic');
var HDPrivateKey = require('./private');
var HDPublicKey = require('./public');
/**
* @exports HD
*/
var HD = exports;
/**
* Instantiate an HD key (public or private) from an base58 string.
* @param {Base58String} xkey
* @returns {HDPrivateKey|HDPublicKey}
*/
HD.fromBase58 = function fromBase58(xkey) {
if (HDPrivateKey.isExtended(xkey))
return HDPrivateKey.fromBase58(xkey);
return HDPublicKey.fromBase58(xkey);
};
/**
* Generate an {@link HDPrivateKey}.
* @param {Object} options
* @param {Buffer?} options.privateKey
* @param {Buffer?} options.entropy
* @param {String?} network
* @returns {HDPrivateKey}
*/
HD.generate = function generate(options, network) {
return HDPrivateKey.generate(options, network);
};
/**
* Generate an {@link HDPrivateKey} from a seed.
* @param {Object|Mnemonic|Buffer} options - seed,
* mnemonic, mnemonic options.
* @param {String?} network
* @returns {HDPrivateKey}
*/
HD.fromSeed = function fromSeed(options, network) {
return HDPrivateKey.fromSeed(options, network);
};
/**
* Instantiate an hd private key from a mnemonic.
* @param {Mnemonic|Object} mnemonic
* @param {String?} network
* @returns {HDPrivateKey}
*/
HD.fromMnemonic = function fromMnemonic(options, network) {
return HDPrivateKey.fromMnemonic(options, network);
};
/**
* Instantiate an HD key from a jsonified key object.
* @param {Object} json - The jsonified transaction object.
* @returns {HDPrivateKey|HDPublicKey}
*/
HD.fromJSON = function fromJSON(json) {
if (json.xprivkey)
return HDPrivateKey.fromJSON(json);
return HDPublicKey.fromJSON(json);
};
/**
* Instantiate an HD key from serialized data.
* @param {Buffer} data
* @returns {HDPrivateKey|HDPublicKey}
*/
HD.fromRaw = function fromRaw(data) {
if (HDPrivateKey.hasPrefix(data))
return HDPrivateKey.fromRaw(data);
return HDPublicKey.fromRaw(data);
};
/**
* Instantiate HD key from extended serialized data.
* @param {Buffer} data
* @returns {HDPrivateKey|HDPublicKey}
*/
HD.fromExtended = function fromExtended(data) {
if (HDPrivateKey.hasPrefix(data))
return HDPrivateKey.fromExtended(data);
return HDPublicKey.fromRaw(data);
};
/**
* Generate an hdkey from any number of options.
* @param {Object|Mnemonic|Buffer} options - mnemonic, mnemonic
* options, seed, or base58 key.
* @param {(Network|NetworkType)?} network
* @returns {HDPrivateKey|HDPublicKey}
*/
HD.from = function from(options, network) {
var xkey;
assert(options, 'Options required.');
if (options.xkey)
xkey = options.xkey;
else if (options.xpubkey)
xkey = options.xpubkey;
else if (options.xprivkey)
xkey = options.xprivkey;
else
xkey = options;
if (HD.isExtended(xkey))
return HD.fromBase58(xkey);
if (HD.hasPrefix(options))
return HD.fromRaw(options);
return HD.fromMnemonic(options, network);
};
/**
* Test whether an object is in the form of a base58 hd key.
* @param {String} data
* @returns {Boolean}
*/
HD.isExtended = function isExtended(data) {
return HDPrivateKey.isExtended(data)
|| HDPublicKey.isExtended(data);
};
/**
* Test whether an object is in the form of a serialized hd key.
* @param {Buffer} data
* @returns {NetworkType}
*/
HD.hasPrefix = function hasPrefix(data) {
return HDPrivateKey.hasPrefix(data)
|| HDPublicKey.hasPrefix(data);
};
/**
* Parse a derivation path and return an array of indexes.
* @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
* @param {String} path
* @param {Number?} max - Max index.
* @returns {Number[]}
*/
HD.parsePath = function parsePath(path, max) {
var parts = path.split('/');
var root = parts.shift();
var result = [];
var i, hardened, index;
if (max == null)
max = constants.hd.MAX_INDEX;
if (root !== 'm'
&& root !== 'M'
&& root !== 'm\''
&& root !== 'M\'') {
throw new Error('Bad path root.');
}
for (i = 0; i < parts.length; i++) {
index = parts[i];
hardened = index[index.length - 1] === '\'';
if (hardened)
index = index.slice(0, -1);
if (!/^\d+$/.test(index))
throw new Error('Non-number path index.');
index = parseInt(index, 10);
if (hardened)
index += constants.hd.HARDENED;
if (!(index >= 0 && index < max))
throw new Error('Index out of range.');
result.push(index);
}
return result;
};
/**
* LRU cache to avoid deriving keys twice.
* @type {LRU}
*/
HD.cache = new LRU(500);
/**
* Test whether an object is an HD key.
* @param {Object} obj
* @returns {Boolean}
*/
HD.isHD = function isHD(obj) {
return HDPrivateKey.isHDPrivateKey(obj)
|| HDPublicKey.isHDPublicKey(obj);
};
/**
* Test whether an object is an HD private key.
* @param {Object} obj
* @returns {Boolean}
*/
HD.isPrivate = function isPrivate(obj) {
return HDPrivateKey.isHDPrivateKey(obj);
};
/**
* Test whether an object is an HD public key.
* @param {Object} obj
* @returns {Boolean}
*/
HD.isPublic = function isPublic(obj) {
return HDPublicKey.isHDPublicKey(obj);
};
/*
* Expose
*/
HD.Mnemonic = Mnemonic;
HD.PrivateKey = HDPrivateKey;
HD.PublicKey = HDPublicKey;

542
lib/bcoin/hd/mnemonic.js Normal file
View File

@ -0,0 +1,542 @@
/*!
* mnemonic.js - hd mnemonics for bcoin
* Copyright (c) 2015-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var utils = require('../utils/utils');
var ec = require('../crypto/ec');
var assert = utils.assert;
var constants = bcoin.constants;
var BufferWriter = require('../utils/writer');
var BufferReader = require('../utils/reader');
var HD = require('./hd');
var unorm;
/**
* HD Mnemonic
* @exports Mnemonic
* @constructor
* @param {Object} options
* @param {Number?} options.bit - Bits of entropy (Must
* be a multiple of 8) (default=128).
* @param {Buffer?} options.entropy - Entropy bytes. Will
* be generated with `options.bits` bits of entropy
* if not present.
* @param {String?} options.phrase - Mnemonic phrase (will
* be generated if not present).
* @param {String?} options.passphrase - Optional salt for
* key stretching (empty string if not present).
* @param {String?} options.language - Language.
*/
function Mnemonic(options) {
if (!(this instanceof Mnemonic))
return new Mnemonic(options);
this.bits = constants.hd.MIN_ENTROPY;
this.language = 'english';
this.entropy = null;
this.phrase = null;
this.passphrase = '';
if (options)
this.fromOptions(options);
}
/**
* List of languages.
* @const {String[]}
* @default
*/
Mnemonic.languages = [
'simplified chinese',
'traditional chinese',
'english',
'french',
'italian',
'japanese'
];
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
Mnemonic.prototype.fromOptions = function fromOptions(options) {
if (typeof options === 'string')
options = { phrase: options };
if (options.bits != null) {
assert(utils.isNumber(options.bits));
assert(options.bits >= constants.hd.MIN_ENTROPY);
assert(options.bits <= constants.hd.MAX_ENTROPY);
assert(options.bits % 32 === 0);
this.bits = options.bits;
}
if (options.language) {
assert(typeof options.language === 'string');
assert(Mnemonic.languages.indexOf(options.language) !== -1);
this.language = options.language;
}
if (options.passphrase) {
assert(typeof options.passphrase === 'string');
this.passphrase = options.passphrase;
}
if (options.phrase) {
this.fromPhrase(options.phrase);
return this;
}
if (options.entropy) {
this.fromEntropy(options.entropy);
return this;
}
return this;
};
/**
* Instantiate mnemonic from options.
* @param {Object} options
* @returns {Mnemonic}
*/
Mnemonic.fromOptions = function fromOptions(options) {
return new Mnemonic().fromOptions(options);
};
/**
* Destroy the mnemonic (zeroes entropy).
*/
Mnemonic.prototype.destroy = function destroy() {
this.bits = constants.hd.MIN_ENTROPY;
this.language = 'english';
if (this.entropy) {
this.entropy.fill(0);
this.entropy = null;
}
this.phrase = null;
this.passphrase = '';
};
/**
* Generate the seed.
* @param {String?} passphrase
* @returns {Buffer} pbkdf2 seed.
*/
Mnemonic.prototype.toSeed = function toSeed(passphrase) {
if (!passphrase)
passphrase = this.passphrase;
this.passphrase = passphrase;
return utils.pbkdf2Sync(
nfkd(this.getPhrase()),
nfkd('mnemonic' + passphrase),
2048, 64, 'sha512');
};
/**
* Generate seed and create an hd private key.
* @param {String?} passphrase
* @param {(Network|NetworkType)?} network
* @returns {HDPrivateKey}
*/
Mnemonic.prototype.toKey = function toKey(passphrase, network) {
var seed = this.toSeed(passphrase);
var key = HD.PrivateKey.fromSeed(seed, network);
key.mnemonic = this;
return key;
};
/**
* Get or generate entropy.
* @returns {Buffer}
*/
Mnemonic.prototype.getEntropy = function getEntropy() {
if (!this.entropy)
this.entropy = ec.random(this.bits / 8);
assert(this.bits / 8 === this.entropy.length);
return this.entropy;
};
/**
* Generate a mnemonic phrase from chosen language.
* @returns {String}
*/
Mnemonic.prototype.getPhrase = function getPhrase() {
var i, j, phrase, wordlist, bits, entropy, index, pos, oct, bit;
if (this.phrase)
return this.phrase;
phrase = [];
wordlist = Mnemonic.getWordlist(this.language);
entropy = this.getEntropy();
bits = this.bits;
// Append the hash to the entropy to
// make things easy when grabbing
// the checksum bits.
entropy = Buffer.concat([entropy, utils.sha256(entropy)]);
// Include the first `ENT / 32` bits
// of the hash (the checksum).
bits += bits / 32;
// Build the mnemonic by reading
// 11 bit indexes from the entropy.
for (i = 0; i < bits / 11; i++) {
index = 0;
for (j = 0; j < 11; j++) {
pos = i * 11 + j;
bit = pos % 8;
oct = (pos - bit) / 8;
index <<= 1;
index |= (entropy[oct] >>> (7 - bit)) & 1;
}
phrase.push(wordlist[index]);
}
// Japanese likes double-width spaces.
if (this.language === 'japanese')
phrase = phrase.join('\u3000');
else
phrase = phrase.join(' ');
this.phrase = phrase;
return phrase;
};
/**
* Inject properties from phrase.
* @private
* @param {String} phrase
*/
Mnemonic.prototype.fromPhrase = function fromPhrase(phrase) {
var i, j, bits, pos, oct, bit, b, ent, entropy, lang;
var chk, word, wordlist, index, cbits, cbytes, words;
assert(typeof phrase === 'string');
words = phrase.split(/[ \u3000]+/);
bits = words.length * 11;
cbits = bits % 32;
cbytes = Math.ceil(cbits / 8);
bits -= cbits;
assert(bits >= constants.hd.MIN_ENTROPY);
assert(bits <= constants.hd.MAX_ENTROPY);
assert(bits % 32 === 0);
assert(cbits !== 0, 'Invalid checksum.');
ent = new Buffer(Math.ceil((bits + cbits) / 8));
ent.fill(0);
lang = Mnemonic.getLanguage(words[0]);
wordlist = Mnemonic.getWordlist(lang);
for (i = 0; i < words.length; i++) {
word = words[i];
index = wordlist.indexOf(word);
if (index === -1)
throw new Error('Could not find word.');
for (j = 0; j < 11; j++) {
pos = i * 11 + j;
bit = pos % 8;
oct = (pos - bit) / 8;
b = (index >>> (10 - j)) & 1;
ent[oct] |= b << (7 - bit);
}
}
entropy = ent.slice(0, ent.length - cbytes);
ent = ent.slice(ent.length - cbytes);
chk = utils.sha256(entropy);
for (i = 0; i < cbits; i++) {
bit = i % 8;
oct = (i - bit) / 8;
b = (ent[oct] >>> (7 - bit)) & 1;
j = (chk[oct] >>> (7 - bit)) & 1;
if (b !== j)
throw new Error('Invalid checksum.');
}
assert(bits / 8 === entropy.length);
this.bits = bits;
this.language = lang;
this.entropy = entropy;
this.phrase = phrase;
return this;
};
/**
* Instantiate mnemonic from a phrase (validates checksum).
* @param {String} phrase
* @returns {Mnemonic}
* @throws on bad checksum
*/
Mnemonic.fromPhrase = function fromPhrase(phrase) {
return new Mnemonic().fromPhrase(phrase);
};
/**
* Inject properties from entropy.
* @private
* @param {Buffer} entropy
* @param {String?} lang
*/
Mnemonic.prototype.fromEntropy = function fromEntropy(entropy, lang) {
assert(Buffer.isBuffer(entropy));
assert(entropy.length * 8 >= constants.hd.MIN_ENTROPY);
assert(entropy.length * 8 <= constants.hd.MAX_ENTROPY);
assert((entropy.length * 8) % 32 === 0);
assert(!lang || Mnemonic.languages.indexOf(lang) !== -1);
this.entropy = entropy;
this.bits = entropy.length * 8;
if (lang)
this.language = lang;
return this;
};
/**
* Instantiate mnemonic from entropy.
* @param {Buffer} entropy
* @param {String?} lang
* @returns {Mnemonic}
*/
Mnemonic.fromEntropy = function fromEntropy(entropy, lang) {
return new Mnemonic().fromEntropy(entropy, lang);
};
/**
* Determine a single word's language.
* @param {String} word
* @returns {String} Language.
* @throws on not found.
*/
Mnemonic.getLanguage = function getLanguage(word) {
var i, lang, wordlist;
for (i = 0; i < Mnemonic.languages.length; i++) {
lang = Mnemonic.languages[i];
wordlist = Mnemonic.getWordlist(lang);
if (wordlist.indexOf(word) !== -1)
return lang;
}
throw new Error('Could not determine language.');
};
/**
* Retrieve the wordlist for a language.
* @param {String} language
* @returns {String[]}
*/
Mnemonic.getWordlist = function getWordlist(language) {
switch (language) {
case 'simplified chinese':
return require('../../../etc/chinese-simplified.js');
case 'traditional chinese':
return require('../../../etc/chinese-traditional.js');
case 'english':
return require('../../../etc/english.js');
case 'french':
return require('../../../etc/french.js');
case 'italian':
return require('../../../etc/italian.js');
case 'japanese':
return require('../../../etc/japanese.js');
default:
throw new Error('Unknown language: ' + language);
}
};
/**
* Convert mnemonic to a json-friendly object.
* @returns {Object}
*/
Mnemonic.prototype.toJSON = function toJSON() {
return {
bits: this.bits,
language: this.language,
entropy: this.getEntropy().toString('hex'),
phrase: this.getPhrase(),
passphrase: this.passphrase
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
Mnemonic.prototype.fromJSON = function fromJSON(json) {
assert(utils.isNumber(json.bits));
assert(typeof json.language === 'string');
assert(typeof json.entropy === 'string');
assert(typeof json.phrase === 'string');
assert(typeof json.passphrase === 'string');
assert(json.bits >= constants.hd.MIN_ENTROPY);
assert(json.bits <= constants.hd.MAX_ENTROPY);
assert(json.bits % 32 === 0);
assert(json.bits / 8 === json.entropy.length / 2);
this.bits = json.bits;
this.language = json.language;
this.entropy = new Buffer(json.entropy, 'hex');
this.phrase = json.phrase;
this.passphrase = json.passphrase;
return this;
};
/**
* Instantiate mnemonic from json object.
* @param {Object} json
* @returns {Mnemonic}
*/
Mnemonic.fromJSON = function fromJSON(json) {
return new Mnemonic().fromJSON(json);
};
/**
* Serialize mnemonic.
* @returns {Buffer}
*/
Mnemonic.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
var lang = Mnemonic.languages.indexOf(this.language);
assert(lang !== -1);
p.writeU16(this.bits);
p.writeU8(lang);
p.writeBytes(this.getEntropy());
p.writeVarString(this.getPhrase(), 'utf8');
p.writeVarString(this.passphrase, 'utf8');
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
Mnemonic.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
this.bits = p.readU16();
this.language = Mnemonic.languages[p.readU8()];
this.entropy = p.readBytes(this.bits / 8);
this.phrase = p.readVarString('utf8');
this.passphrase = p.readVarString('utf8');
assert(this.language);
assert(this.bits >= constants.hd.MIN_ENTROPY);
assert(this.bits <= constants.hd.MAX_ENTROPY);
assert(this.bits % 32 === 0);
return this;
};
/**
* Instantiate mnemonic from serialized data.
* @param {Buffer} data
* @returns {Mnemonic}
*/
Mnemonic.fromRaw = function fromRaw(data) {
return new Mnemonic().fromRaw(data);
};
/**
* Convert the mnemonic to a string.
* @returns {String}
*/
Mnemonic.prototype.toString = function toString() {
return this.getPhrase();
};
/**
* Inspect the mnemonic.
* @returns {String}
*/
Mnemonic.prototype.inspect = function inspect() {
return '<Mnemonic: ' + this.getPhrase() + '>';
};
/**
* Test whether an object is a Mnemonic.
* @param {Object} obj
* @returns {Boolean}
*/
Mnemonic.isMnemonic = function isMnemonic(obj) {
return obj
&& typeof obj.bits === 'number'
&& typeof obj.toSeed === 'function';
};
/*
* Helpers
*/
function nfkd(str) {
if (str.normalize)
return str.normalize('NFKD');
if (!unorm)
unorm = require('../../../vendor/unorm');
return unorm.nfkd(str);
}
/*
* Expose
*/
module.exports = Mnemonic;

794
lib/bcoin/hd/private.js Normal file
View File

@ -0,0 +1,794 @@
/*!
* private.js - hd private keys for bcoin
* Copyright (c) 2015-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var utils = require('../utils/utils');
var ec = require('../crypto/ec');
var assert = utils.assert;
var constants = bcoin.constants;
var networks = bcoin.networks;
var BufferWriter = require('../utils/writer');
var BufferReader = require('../utils/reader');
var HD = require('./hd');
/*
* Constants
*/
var FINGER_PRINT = new Buffer('00000000', 'hex');
/**
* HDPrivateKey
* @exports HDPrivateKey
* @constructor
* @param {Object|Base58String} options
* @param {Base58String?} options.xkey - Serialized base58 key.
* @param {Mnemonic?} options.mnemonic
* @param {Number?} options.depth
* @param {Buffer?} options.parentFingerPrint
* @param {Number?} options.childIndex
* @param {Buffer?} options.chainCode
* @param {Buffer?} options.privateKey
* @property {Network} network
* @property {Base58String} xprivkey
* @property {Base58String} xpubkey
* @property {Mnemonic?} mnemonic
* @property {Number} depth
* @property {Buffer} parentFingerPrint
* @property {Number} childIndex
* @property {Buffer} chainCode
* @property {Buffer} privateKey
* @property {HDPublicKey} hdPublicKey
*/
function HDPrivateKey(options) {
if (!(this instanceof HDPrivateKey))
return new HDPrivateKey(options);
this.network = bcoin.network.get();
this.depth = 0;
this.parentFingerPrint = FINGER_PRINT;
this.childIndex = 0;
this.chainCode = constants.ZERO_HASH;
this.privateKey = constants.ZERO_HASH;
this.publicKey = constants.ZERO_KEY;
this.fingerPrint = null;
this.mnemonic = null;
this._xprivkey = null;
this.hdPrivateKey = this;
this._hdPublicKey = null;
if (options)
this.fromOptions(options);
}
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
HDPrivateKey.prototype.fromOptions = function fromOptions(options) {
assert(options, 'No options for HD private key.');
assert(utils.isNumber(options.depth));
assert(Buffer.isBuffer(options.parentFingerPrint));
assert(utils.isNumber(options.childIndex));
assert(Buffer.isBuffer(options.chainCode));
assert(Buffer.isBuffer(options.privateKey));
assert(options.depth <= 0xff, 'Depth is too high.');
if (options.network)
this.network = bcoin.network.get(options.network);
this.depth = options.depth;
this.parentFingerPrint = options.parentFingerPrint;
this.childIndex = options.childIndex;
this.chainCode = options.chainCode;
this.privateKey = options.privateKey;
this.publicKey = ec.publicKeyCreate(options.privateKey, true);
if (options.mnemonic) {
assert(options.mnemonic instanceof HD.Mnemonic);
this.mnemonic = options.mnemonic;
}
if (options.xprivkey) {
assert(typeof options.xprivkey === 'string');
this._xprivkey = options.xprivkey;
}
return this;
};
/**
* Instantiate HD private key from options object.
* @param {Object} options
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromOptions = function fromOptions(options) {
return new HDPrivateKey().fromOptions(options);
};
HDPrivateKey.prototype.__defineGetter__('hdPublicKey', function() {
var key = this._hdPublicKey;
if (!key) {
key = new HD.PublicKey();
key.network = this.network;
key.depth = this.depth;
key.parentFingerPrint = this.parentFingerPrint;
key.childIndex = this.childIndex;
key.chainCode = this.chainCode;
key.publicKey = this.publicKey;
this._hdPublicKey = key;
}
return key;
});
HDPrivateKey.prototype.__defineGetter__('xprivkey', function() {
if (!this._xprivkey)
this._xprivkey = this.toBase58();
return this._xprivkey;
});
HDPrivateKey.prototype.__defineGetter__('xpubkey', function() {
return this.hdPublicKey.xpubkey;
});
/**
* Destroy the key (zeroes chain code, privkey, and pubkey).
* @param {Boolean} pub - Destroy hd public key as well.
*/
HDPrivateKey.prototype.destroy = function destroy(pub) {
this.depth = 0;
this.parentFingerPrint.fill(0);
this.childIndex = 0;
this.chainCode.fill(0);
this.privateKey.fill(0);
this.publicKey.fill(0);
if (this.fingerPrint) {
this.fingerPrint.fill(0);
this.fingerPrint = null;
}
if (this._hdPublicKey) {
if (pub)
this._hdPublicKey.destroy();
this._hdPublicKey = null;
}
this._xprivkey = null;
if (this.mnemonic) {
this.mnemonic.destroy();
this.mnemonic = null;
}
};
/**
* Derive a child key.
* @param {Number|String} - Child index or path.
* @param {Boolean?} hardened - Whether the derivation should be hardened.
* @returns {HDPrivateKey}
*/
HDPrivateKey.prototype.derive = function derive(index, hardened) {
var p, id, data, hash, left, right, key, child;
if (typeof index === 'string')
return this.derivePath(index);
hardened = index >= constants.hd.HARDENED ? true : hardened;
if (index < constants.hd.HARDENED && hardened)
index += constants.hd.HARDENED;
if (!(index >= 0 && index < constants.hd.MAX_INDEX))
throw new Error('Index out of range.');
if (this.depth >= 0xff)
throw new Error('Depth too high.');
id = this.getID(index);
child = HD.cache.get(id);
if (child)
return child;
p = new BufferWriter();
if (hardened) {
p.writeU8(0);
p.writeBytes(this.privateKey);
p.writeU32BE(index);
} else {
p.writeBytes(this.publicKey);
p.writeU32BE(index);
}
data = p.render();
hash = utils.hmac('sha512', data, this.chainCode);
left = hash.slice(0, 32);
right = hash.slice(32, 64);
try {
key = ec.privateKeyTweakAdd(this.privateKey, left);
} catch (e) {
return this.derive(index + 1);
}
if (!this.fingerPrint)
this.fingerPrint = utils.hash160(this.publicKey).slice(0, 4);
child = new HDPrivateKey();
child.network = this.network;
child.depth = this.depth + 1;
child.parentFingerPrint = this.fingerPrint;
child.childIndex = index;
child.chainCode = right;
child.privateKey = key;
child.publicKey = ec.publicKeyCreate(key, true);
HD.cache.set(id, child);
return child;
};
/**
* Unique HD key ID.
* @private
* @param {Number} index
* @returns {String}
*/
HDPrivateKey.prototype.getID = function getID(index) {
return this.network.keyPrefix.xprivkey58
+ this.publicKey.toString('hex')
+ index;
};
/**
* Derive a BIP44 account key.
* @param {Number} accountIndex
* @returns {HDPrivateKey}
* @throws Error if key is not a master key.
*/
HDPrivateKey.prototype.deriveAccount44 = function deriveAccount44(accountIndex) {
assert(utils.isNumber(accountIndex), 'Account index must be a number.');
assert(this.isMaster(), 'Cannot derive account index.');
return this
.derive(44, true)
.derive(this.network.type === 'main' ? 0 : 1, true)
.derive(accountIndex, true);
};
/**
* Derive a BIP45 purpose key.
* @returns {HDPrivateKey}
*/
HDPrivateKey.prototype.derivePurpose45 = function derivePurpose45() {
assert(this.isMaster(), 'Cannot derive purpose 45.');
return this.derive(45, true);
};
/**
* Test whether the key is a master key.
* @returns {Boolean}
*/
HDPrivateKey.prototype.isMaster = function isMaster() {
return this.depth === 0
&& this.childIndex === 0
&& this.parentFingerPrint.readUInt32LE(0, true) === 0;
};
/**
* Test whether the key is (most likely) a BIP44 account key.
* @param {Number?} accountIndex
* @returns {Boolean}
*/
HDPrivateKey.prototype.isAccount44 = function isAccount44(accountIndex) {
if (accountIndex != null) {
if (this.childIndex !== constants.hd.HARDENED + accountIndex)
return false;
}
return this.depth === 3 && this.childIndex >= constants.hd.HARDENED;
};
/**
* Test whether the key is a BIP45 purpose key.
* @returns {Boolean}
*/
HDPrivateKey.prototype.isPurpose45 = function isPurpose45() {
return this.depth === 1 && this.childIndex === constants.hd.HARDENED + 45;
};
/**
* Test whether an object is in the form of a base58 xprivkey.
* @param {String} data
* @returns {Boolean}
*/
HDPrivateKey.isExtended = function isExtended(data) {
var i, type, prefix;
if (typeof data !== 'string')
return false;
for (i = 0; i < networks.types.length; i++) {
type = networks.types[i];
prefix = networks[type].keyPrefix.xprivkey58;
if (data.indexOf(prefix) === 0)
return true;
}
return false;
};
/**
* Test whether a buffer has a valid network prefix.
* @param {Buffer} data
* @returns {NetworkType}
*/
HDPrivateKey.hasPrefix = function hasPrefix(data) {
var i, version, prefix, type;
if (!Buffer.isBuffer(data))
return false;
version = data.readUInt32BE(0, true);
for (i = 0; i < networks.types.length; i++) {
type = networks.types[i];
prefix = networks[type].keyPrefix.xprivkey;
if (version === prefix)
return type;
}
return false;
};
/**
* Test whether a string is a valid path.
* @param {String} path
* @param {Boolean?} hardened
* @returns {Boolean}
*/
HDPrivateKey.isValidPath = function isValidPath(path) {
if (typeof path !== 'string')
return false;
try {
HD.parsePath(path, constants.hd.MAX_INDEX);
return true;
} catch (e) {
return false;
}
};
/**
* Derive a key from a derivation path.
* @param {String} path
* @returns {HDPrivateKey}
* @throws Error if `path` is not a valid path.
*/
HDPrivateKey.prototype.derivePath = function derivePath(path) {
var indexes = HD.parsePath(path, constants.hd.MAX_INDEX);
var key = this;
var i;
for (i = 0; i < indexes.length; i++)
key = key.derive(indexes[i]);
return key;
};
/**
* Compare a key against an object.
* @param {Object} obj
* @returns {Boolean}
*/
HDPrivateKey.prototype.equal = function equal(obj) {
if (!HDPrivateKey.isHDPrivateKey(obj))
return false;
return this.network === obj.network
&& this.depth === obj.depth
&& utils.equal(this.parentFingerPrint, obj.parentFingerPrint)
&& this.childIndex === obj.childIndex
&& utils.equal(this.chainCode, obj.chainCode)
&& utils.equal(this.privateKey, obj.privateKey);
};
/**
* Compare a key against an object.
* @param {Object} obj
* @returns {Boolean}
*/
HDPrivateKey.prototype.compare = function compare(key) {
var cmp;
if (!HDPrivateKey.isHDPrivateKey(key))
return 1;
cmp = this.depth - key.depth;
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.parentFingerPrint, key.parentFingerPrint);
if (cmp !== 0)
return cmp;
cmp = this.childIndex - key.childIndex;
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.chainCode, key.chainCode);
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.privateKey, key.privateKey);
if (cmp !== 0)
return cmp;
return 0;
};
/**
* Inject properties from seed.
* @private
* @param {Buffer} seed
* @param {(Network|NetworkType)?} network
*/
HDPrivateKey.prototype.fromSeed = function fromSeed(seed, network) {
var hash, left, right;
assert(Buffer.isBuffer(seed));
if (!(seed.length * 8 >= constants.hd.MIN_ENTROPY
&& seed.length * 8 <= constants.hd.MAX_ENTROPY)) {
throw new Error('Entropy not in range.');
}
hash = utils.hmac('sha512', seed, 'Bitcoin seed');
left = hash.slice(0, 32);
right = hash.slice(32, 64);
// Only a 1 in 2^127 chance of happening.
if (!ec.privateKeyVerify(left))
throw new Error('Master private key is invalid.');
this.network = bcoin.network.get(network);
this.depth = 0;
this.parentFingerPrint = new Buffer([0, 0, 0, 0]);
this.childIndex = 0;
this.chainCode = right;
this.privateKey = left;
this.publicKey = ec.publicKeyCreate(left, true);
return this;
};
/**
* Instantiate an hd private key from a 512 bit seed.
* @param {Buffer} seed
* @param {(Network|NetworkType)?} network
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromSeed = function fromSeed(seed, network) {
return new HDPrivateKey().fromSeed(seed, network);
};
/**
* Inject properties from a mnemonic.
* @private
* @param {Mnemonic|Object} mnemonic
* @param {(Network|NetworkType)?} network
*/
HDPrivateKey.prototype.fromMnemonic = function fromMnemonic(mnemonic, network) {
if (!(mnemonic instanceof HD.Mnemonic))
mnemonic = new HD.Mnemonic(mnemonic);
this.fromSeed(mnemonic.toSeed(), network);
this.mnemonic = mnemonic;
return this;
};
/**
* Instantiate an hd private key from a mnemonic.
* @param {Mnemonic|Object} mnemonic
* @param {(Network|NetworkType)?} network
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromMnemonic = function fromMnemonic(mnemonic, network) {
return new HDPrivateKey().fromMnemonic(mnemonic, network);
};
/**
* Inject properties from privateKey and entropy.
* @private
* @param {Buffer} key
* @param {Buffer} entropy
* @param {(Network|NetworkType)?} network
*/
HDPrivateKey.prototype.fromKey = function fromKey(key, entropy, network) {
assert(Buffer.isBuffer(key) && key.length === 32);
assert(Buffer.isBuffer(entropy) && entropy.length === 32);
this.network = bcoin.network.get(network);
this.depth = 0;
this.parentFingerPrint = new Buffer([0, 0, 0, 0]);
this.childIndex = 0;
this.chainCode = entropy;
this.privateKey = key;
this.publicKey = ec.publicKeyCreate(key, true);
return this;
};
/**
* Create an hd private key from a key and entropy bytes.
* @param {Buffer} key
* @param {Buffer} entropy
* @param {(Network|NetworkType)?} network
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromKey = function fromKey(key, entropy, network) {
return new HDPrivateKey().fromKey(key, entropy, network);
};
/**
* Generate an hd private key.
* @param {(Network|NetworkType)?} network
* @returns {HDPrivateKey}
*/
HDPrivateKey.generate = function generate(network) {
var key = ec.generatePrivateKey();
var entropy = ec.random(32);
return HDPrivateKey.fromKey(key, entropy, network);
};
/**
* Inject properties from base58 key.
* @private
* @param {Base58String} xkey
*/
HDPrivateKey.prototype.fromBase58 = function fromBase58(xkey) {
this.fromRaw(utils.fromBase58(xkey));
this._xprivkey = xkey;
return this;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} raw
*/
HDPrivateKey.prototype.fromRaw = function fromRaw(raw) {
var p = new BufferReader(raw);
var i, version, type, prefix;
version = p.readU32BE();
this.depth = p.readU8();
this.parentFingerPrint = p.readBytes(4);
this.childIndex = p.readU32BE();
this.chainCode = p.readBytes(32);
p.readU8();
this.privateKey = p.readBytes(32);
p.verifyChecksum();
for (i = 0; i < networks.types.length; i++) {
type = networks.types[i];
prefix = networks[type].keyPrefix.xprivkey;
if (version === prefix)
break;
}
assert(i < networks.types.length, 'Network not found.');
this.publicKey = ec.publicKeyCreate(this.privateKey, true);
this.network = bcoin.network.get(type);
return this;
};
/**
* Serialize key to a base58 string.
* @param {(Network|NetworkType)?} network
* @returns {Base58String}
*/
HDPrivateKey.prototype.toBase58 = function toBase58(network) {
return utils.toBase58(this.toRaw(network));
};
/**
* Serialize the key.
* @param {(Network|NetworkType)?} network
* @returns {Buffer}
*/
HDPrivateKey.prototype.toRaw = function toRaw(network, writer) {
var p = new BufferWriter(writer);
if (!network)
network = this.network;
network = bcoin.network.get(network);
p.writeU32BE(network.keyPrefix.xprivkey);
p.writeU8(this.depth);
p.writeBytes(this.parentFingerPrint);
p.writeU32BE(this.childIndex);
p.writeBytes(this.chainCode);
p.writeU8(0);
p.writeBytes(this.privateKey);
p.writeChecksum();
if (!writer)
p = p.render();
return p;
};
/**
* Serialize the key in "extended"
* format (includes the mnemonic).
* @param {(Network|NetworkType)?} network
* @returns {Buffer}
*/
HDPrivateKey.prototype.toExtended = function toExtended(network, writer) {
var p = new BufferWriter(writer);
this.toRaw(network, p);
if (this.mnemonic) {
p.writeU8(1);
this.mnemonic.toRaw(p);
} else {
p.writeU8(0);
}
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from extended serialized data.
* @private
* @param {Buffer} data
*/
HDPrivateKey.prototype.fromExtended = function fromExtended(data) {
var p = new BufferReader(data);
this.fromRaw(p);
if (p.readU8() === 1)
this.mnemonic = HD.Mnemonic.fromRaw(p);
return this;
};
/**
* Instantiate key from "extended" serialized data.
* @param {Buffer} data
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromExtended = function fromExtended(data) {
return new HDPrivateKey().fromExtended(data);
};
/**
* Instantiate an HD private key from a base58 string.
* @param {Base58String} xkey
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromBase58 = function fromBase58(xkey) {
return new HDPrivateKey().fromBase58(xkey);
};
/**
* Instantiate key from serialized data.
* @param {Buffer} raw
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromRaw = function fromRaw(raw) {
return new HDPrivateKey().fromRaw(raw);
};
/**
* Convert key to a more json-friendly object.
* @returns {Object}
*/
HDPrivateKey.prototype.toJSON = function toJSON() {
return {
xprivkey: this.xprivkey,
mnemonic: this.mnemonic ? this.mnemonic.toJSON() : null
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
HDPrivateKey.prototype.fromJSON = function fromJSON(json) {
assert(json.xprivkey, 'Could not handle key JSON.');
this.fromBase58(json.xprivkey);
if (json.mnemonic)
this.mnemonic = HD.Mnemonic.fromJSON(json.mnemonic);
return this;
};
/**
* Instantiate an HDPrivateKey from a jsonified key object.
* @param {Object} json - The jsonified key object.
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromJSON = function fromJSON(json) {
return new HDPrivateKey().fromJSON(json);
};
/**
* Test whether an object is an HDPrivateKey.
* @param {Object} obj
* @returns {Boolean}
*/
HDPrivateKey.isHDPrivateKey = function isHDPrivateKey(obj) {
return obj
&& typeof obj.derive === 'function'
&& typeof obj.toExtended === 'function'
&& obj.chainCode !== undefined;
};
/*
* Expose
*/
module.exports = HDPrivateKey;

557
lib/bcoin/hd/public.js Normal file
View File

@ -0,0 +1,557 @@
/*!
* public.js - hd public keys for bcoin
* Copyright (c) 2015-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var utils = require('../utils/utils');
var ec = require('../crypto/ec');
var assert = utils.assert;
var constants = bcoin.constants;
var networks = bcoin.networks;
var BufferWriter = require('../utils/writer');
var BufferReader = require('../utils/reader');
var HD = require('./hd');
/*
* Constants
*/
var FINGER_PRINT = new Buffer('00000000', 'hex');
/**
* HDPublicKey
* @exports HDPublicKey
* @constructor
* @param {Object|Base58String} options
* @param {Base58String?} options.xkey - Serialized base58 key.
* @param {Number?} options.depth
* @param {Buffer?} options.parentFingerPrint
* @param {Number?} options.childIndex
* @param {Buffer?} options.chainCode
* @param {Buffer?} options.publicKey
* @property {Network} network
* @property {Base58String} xpubkey
* @property {Number} depth
* @property {Buffer} parentFingerPrint
* @property {Number} childIndex
* @property {Buffer} chainCode
* @property {Buffer} publicKey
*/
function HDPublicKey(options) {
if (!(this instanceof HDPublicKey))
return new HDPublicKey(options);
this.network = bcoin.network.get();
this.depth = 0;
this.parentFingerPrint = FINGER_PRINT;
this.childIndex = 0;
this.chainCode = constants.ZERO_HASH;
this.publicKey = constants.ZERO_KEY;
this.fingerPrint = null;
this._xpubkey = null;
this.hdPublicKey = this;
this.hdPrivateKey = null;
if (options)
this.fromOptions(options);
}
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
HDPublicKey.prototype.fromOptions = function fromOptions(options) {
assert(options, 'No options for HDPublicKey');
assert(utils.isNumber(options.depth));
assert(Buffer.isBuffer(options.parentFingerPrint));
assert(utils.isNumber(options.childIndex));
assert(Buffer.isBuffer(options.chainCode));
assert(Buffer.isBuffer(options.publicKey));
if (options.network)
this.network = bcoin.network.get(options.network);
this.depth = options.depth;
this.parentFingerPrint = options.parentFingerPrint;
this.childIndex = options.childIndex;
this.chainCode = options.chainCode;
this.publicKey = options.publicKey;
if (options.xpubkey) {
assert(typeof options.xpubkey === 'string');
this._xpubkey = options.xpubkey;
}
return this;
};
/**
* Instantiate HD public key from options object.
* @param {Object} options
* @returns {HDPublicKey}
*/
HDPublicKey.fromOptions = function fromOptions(options) {
return new HDPublicKey().fromOptions(options);
};
HDPublicKey.prototype.__defineGetter__('xpubkey', function() {
if (!this._xpubkey)
this._xpubkey = this.toBase58();
return this._xpubkey;
});
/**
* Destroy the key (zeroes chain code and pubkey).
*/
HDPublicKey.prototype.destroy = function destroy() {
this.depth = 0;
this.parentFingerPrint.fill(0);
this.childIndex = 0;
this.chainCode.fill(0);
this.publicKey.fill(0);
if (this.fingerPrint) {
this.fingerPrint.fill(0);
this.fingerPrint = null;
}
this._xpubkey = null;
};
/**
* Derive a child key.
* @param {Number|String} - Child index or path.
* @param {Boolean?} hardened - Whether the derivation
* should be hardened (throws if true).
* @returns {HDPrivateKey}
* @throws on `hardened`
*/
HDPublicKey.prototype.derive = function derive(index, hardened) {
var p, id, data, hash, left, right, key, child;
if (typeof index === 'string')
return this.derivePath(index);
if (index >= constants.hd.HARDENED || hardened)
throw new Error('Index out of range.');
if (index < 0)
throw new Error('Index out of range.');
if (this.depth >= 0xff)
throw new Error('Depth too high.');
id = this.getID(index);
child = HD.cache.get(id);
if (child)
return child;
p = new BufferWriter();
p.writeBytes(this.publicKey);
p.writeU32BE(index);
data = p.render();
hash = utils.hmac('sha512', data, this.chainCode);
left = hash.slice(0, 32);
right = hash.slice(32, 64);
try {
key = ec.publicKeyTweakAdd(this.publicKey, left, true);
} catch (e) {
return this.derive(index + 1);
}
if (!this.fingerPrint)
this.fingerPrint = utils.hash160(this.publicKey).slice(0, 4);
child = new HDPublicKey();
child.network = this.network;
child.depth = this.depth + 1;
child.parentFingerPrint = this.fingerPrint;
child.childIndex = index;
child.chainCode = right;
child.publicKey = key;
HD.cache.set(id, child);
return child;
};
/**
* Unique HD key ID.
* @private
* @param {Number} index
* @returns {String}
*/
HDPublicKey.prototype.getID = function getID(index) {
return this.network.keyPrefix.xpubkey58
+ this.publicKey.toString('hex')
+ index;
};
/**
* Derive a BIP44 account key (does not derive, only ensures account key).
* @method
* @param {Number} accountIndex
* @returns {HDPublicKey}
* @throws Error if key is not already an account key.
*/
HDPublicKey.prototype.deriveAccount44 = function deriveAccount44(accountIndex) {
assert(this.isAccount44(accountIndex), 'Cannot derive account index.');
return this;
};
/**
* Derive a BIP45 purpose key (does not derive, only ensures account key).
* @method
* @returns {HDPublicKey}
* @throws Error if key is not already a purpose key.
*/
HDPublicKey.prototype.derivePurpose45 = function derivePurpose45() {
assert(this.isPurpose45(), 'Cannot derive purpose 45.');
return this;
};
/**
* Test whether the key is a master key.
* @method
* @returns {Boolean}
*/
HDPublicKey.prototype.isMaster = function() {
return HD.PrivateKey.prototype.isMaster.call(this);
};
/**
* Test whether the key is (most likely) a BIP44 account key.
* @method
* @param {Number?} accountIndex
* @returns {Boolean}
*/
HDPublicKey.prototype.isAccount44 = function(accountIndex) {
return HD.PrivateKey.prototype.isAccount44.call(this, accountIndex);
};
/**
* Test whether the key is a BIP45 purpose key.
* @method
* @returns {Boolean}
*/
HDPublicKey.prototype.isPurpose45 = function() {
return HD.PrivateKey.prototype.isPurpose45.call(this);
};
/**
* Test whether a string is a valid path.
* @param {String} path
* @param {Boolean?} hardened
* @returns {Boolean}
*/
HDPublicKey.isValidPath = function isValidPath(path) {
if (typeof path !== 'string')
return false;
try {
HD.parsePath(path, constants.hd.HARDENED);
return true;
} catch (e) {
return false;
}
};
/**
* Derive a key from a derivation path.
* @param {String} path
* @returns {HDPublicKey}
* @throws Error if `path` is not a valid path.
* @throws Error if hardened.
*/
HDPublicKey.prototype.derivePath = function derivePath(path) {
var indexes = HD.parsePath(path, constants.hd.HARDENED);
var key = this;
var i;
for (i = 0; i < indexes.length; i++)
key = key.derive(indexes[i]);
return key;
};
/**
* Compare a key against an object.
* @param {Object} obj
* @returns {Boolean}
*/
HDPublicKey.prototype.equal = function equal(obj) {
if (!HDPublicKey.isHDPublicKey(obj))
return false;
return this.network === obj.network
&& this.depth === obj.depth
&& utils.equal(this.parentFingerPrint, obj.parentFingerPrint)
&& this.childIndex === obj.childIndex
&& utils.equal(this.chainCode, obj.chainCode)
&& utils.equal(this.publicKey, obj.publicKey);
};
/**
* Compare a key against an object.
* @param {Object} obj
* @returns {Boolean}
*/
HDPublicKey.prototype.compare = function compare(key) {
var cmp;
if (!HDPublicKey.isHDPublicKey(key))
return 1;
cmp = this.depth - key.depth;
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.parentFingerPrint, key.parentFingerPrint);
if (cmp !== 0)
return cmp;
cmp = this.childIndex - key.childIndex;
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.chainCode, key.chainCode);
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.publicKey, key.publicKey);
if (cmp !== 0)
return cmp;
return 0;
};
/**
* Convert key to a more json-friendly object.
* @returns {Object}
*/
HDPublicKey.prototype.toJSON = function toJSON() {
return {
xpubkey: this.xpubkey
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
HDPublicKey.prototype.fromJSON = function fromJSON(json) {
assert(json.xpubkey, 'Could not handle HD key JSON.');
this.fromBase58(json.xpubkey);
return this;
};
/**
* Instantiate an HDPrivateKey from a jsonified key object.
* @param {Object} json - The jsonified transaction object.
* @returns {HDPrivateKey}
*/
HDPublicKey.fromJSON = function fromJSON(json) {
return new HDPublicKey().fromJSON(json);
};
/**
* Test whether an object is in the form of a base58 xpubkey.
* @param {String} data
* @returns {Boolean}
*/
HDPublicKey.isExtended = function isExtended(data) {
var i, type, prefix;
if (typeof data !== 'string')
return false;
for (i = 0; i < networks.types.length; i++) {
type = networks.types[i];
prefix = networks[type].keyPrefix.xpubkey58;
if (data.indexOf(prefix) === 0)
return true;
}
return false;
};
/**
* Test whether a buffer has a valid network prefix.
* @param {Buffer} data
* @returns {NetworkType}
*/
HDPublicKey.hasPrefix = function hasPrefix(data) {
var i, version, prefix, type;
if (!Buffer.isBuffer(data))
return false;
version = data.readUInt32BE(0, true);
for (i = 0; i < networks.types.length; i++) {
type = networks.types[i];
prefix = networks[type].keyPrefix.xpubkey;
if (version === prefix)
return type;
}
return false;
};
/**
* Inject properties from a base58 key.
* @private
* @param {Base58String} xkey
*/
HDPublicKey.prototype.fromBase58 = function fromBase58(xkey) {
this.fromRaw(utils.fromBase58(xkey));
this._xpubkey = xkey;
return this;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} raw
*/
HDPublicKey.prototype.fromRaw = function fromRaw(raw) {
var p = new BufferReader(raw);
var i, version, type, prefix;
version = p.readU32BE();
this.depth = p.readU8();
this.parentFingerPrint = p.readBytes(4);
this.childIndex = p.readU32BE();
this.chainCode = p.readBytes(32);
this.publicKey = p.readBytes(33);
p.verifyChecksum();
for (i = 0; i < networks.types.length; i++) {
type = networks.types[i];
prefix = networks[type].keyPrefix.xpubkey;
if (version === prefix)
break;
}
assert(i < networks.types.length, 'Network not found.');
this.network = bcoin.network.get(type);
return this;
};
/**
* Serialize key data to base58 extended key.
* @param {Network|String} network
* @returns {Base58String}
*/
HDPublicKey.prototype.toBase58 = function toBase58(network) {
return utils.toBase58(this.toRaw(network));
};
/**
* Serialize the key.
* @param {Network|NetworkType} network
* @returns {Buffer}
*/
HDPublicKey.prototype.toRaw = function toRaw(network, writer) {
var p = new BufferWriter(writer);
if (!network)
network = this.network;
network = bcoin.network.get(network);
p.writeU32BE(network.keyPrefix.xpubkey);
p.writeU8(this.depth);
p.writeBytes(this.parentFingerPrint);
p.writeU32BE(this.childIndex);
p.writeBytes(this.chainCode);
p.writeBytes(this.publicKey);
p.writeChecksum();
if (!writer)
p = p.render();
return p;
};
/**
* Instantiate an HD public key from a base58 string.
* @param {Base58String} xkey
* @returns {HDPublicKey}
*/
HDPublicKey.fromBase58 = function fromBase58(xkey) {
return new HDPublicKey().fromBase58(xkey);
};
/**
* Instantiate key from serialized data.
* @param {Buffer} raw
* @returns {HDPublicKey}
*/
HDPublicKey.fromRaw = function fromRaw(data) {
return new HDPublicKey().fromRaw(data);
};
/**
* Test whether an object is a HDPublicKey.
* @param {Object} obj
* @returns {Boolean}
*/
HDPublicKey.isHDPublicKey = function isHDPublicKey(obj) {
return obj
&& typeof obj.derive === 'function'
&& typeof obj.toExtended !== 'function'
&& obj.chainCode !== undefined;
};
/*
* Expose
*/
module.exports = HDPublicKey;

View File

@ -236,106 +236,6 @@ VersionPacket.fromRaw = function fromRaw(data, enc) {
return new VersionPacket().fromRaw(data, enc);
};
/**
* Inv Item
* @param {Number} type
* @param {Hash} hash
* @property {InvType} type
* @property {Hash} hash
*/
function InvItem(type, hash) {
if (!(this instanceof InvItem))
return new InvItem(type, hash);
this.type = type;
this.hash = hash;
}
/**
* Serialize inv item.
* @returns {Buffer}
*/
InvItem.prototype.toRaw = function toRaw(writer) {
var p = bcoin.writer(writer);
p.writeU32(this.type);
p.writeHash(this.hash);
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from serialized data.
* @param {Buffer} data
*/
InvItem.prototype.fromRaw = function fromRaw(data) {
var p = bcoin.reader(data);
this.type = p.readU32();
this.hash = p.readHash('hex');
return this;
};
/**
* Instantiate inv item from serialized data.
* @param {Buffer} data
* @param {String?} enc
* @returns {InvItem}
*/
InvItem.fromRaw = function fromRaw(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return new InvItem().fromRaw(data);
};
/**
* Test whether the inv item is a block.
* @returns {Boolean}
*/
InvItem.prototype.isBlock = function isBlock() {
switch (this.type) {
case constants.inv.BLOCK:
case constants.inv.WITNESS_BLOCK:
case constants.inv.FILTERED_BLOCK:
case constants.inv.WITNESS_FILTERED_BLOCK:
case constants.inv.CMPCT_BLOCK:
return true;
default:
return false;
}
};
/**
* Test whether the inv item is a tx.
* @returns {Boolean}
*/
InvItem.prototype.isTX = function isTX() {
switch (this.type) {
case constants.inv.TX:
case constants.inv.WITNESS_TX:
return true;
default:
return false;
}
};
/**
* Test whether the inv item has the witness bit set.
* @returns {Boolean}
*/
InvItem.prototype.hasWitness = function hasWitness() {
return (this.type & constants.WITNESS_MASK) !== 0;
};
/**
* Represents a `getblocks` packet.
* @exports GetBlocksPacket
@ -1102,7 +1002,6 @@ NetworkAddress.prototype.toRaw = function toRaw(full, writer) {
*/
exports.VersionPacket = VersionPacket;
exports.InvItem = InvItem;
exports.GetBlocksPacket = GetBlocksPacket;
exports.AlertPacket = AlertPacket;
exports.RejectPacket = RejectPacket;

View File

@ -568,7 +568,7 @@ Parser.parseInv = function parseInv(p) {
assert(count <= 50000, 'Item count too high.');
for (i = 0; i < count; i++)
items.push(bcoin.packets.InvItem.fromRaw(p));
items.push(bcoin.invitem.fromRaw(p));
return items;
};

View File

@ -15,7 +15,7 @@ var Parser = require('./parser');
var Framer = require('./framer');
var assert = utils.assert;
var constants = bcoin.constants;
var InvItem = bcoin.packets.InvItem;
var InvItem = bcoin.invitem;
var VersionPacket = bcoin.packets.VersionPacket;
var GetBlocksPacket = bcoin.packets.GetBlocksPacket;
var RejectPacket = bcoin.packets.RejectPacket;

View File

@ -16,7 +16,7 @@ var assert = utils.assert;
var constants = bcoin.constants;
var VerifyError = bcoin.errors.VerifyError;
var NetworkAddress = bcoin.packets.NetworkAddress;
var InvItem = bcoin.packets.InvItem;
var InvItem = bcoin.invitem;
/**
* A pool of peers for handling all network activity.

File diff suppressed because it is too large Load Diff

View File

@ -11,171 +11,7 @@ var bcoin = require('../env');
var utils = require('../utils/utils');
var assert = utils.assert;
var constants = bcoin.constants;
/**
* Represents a COutPoint.
* @exports Outpoint
* @constructor
* @param {Hash?} hash
* @param {Number?} index
* @property {Hash} hash
* @property {Number} index
*/
function Outpoint(hash, index) {
if (!(this instanceof Outpoint))
return new Outpoint(hash, index);
this.hash = hash || constants.NULL_HASH;
this.index = index != null ? index : 0xffffffff;
}
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
Outpoint.prototype.fromOptions = function fromOptions(options) {
assert(typeof options.hash === 'string');
assert(utils.isNumber(options.index));
this.hash = options.hash;
this.index = options.index;
return this;
};
/**
* Instantate outpoint from options object.
* @param {Object} options
* @returns {Outpoint}
*/
Outpoint.fromOptions = function fromOptions(options) {
return new Outpoint().fromOptions(options);
};
/**
* Test whether the outpoint is null (hash of zeroes
* with max-u32 index). Used to detect coinbases.
* @returns {Boolean}
*/
Outpoint.prototype.isNull = function isNull() {
return this.index === 0xffffffff && this.hash === constants.NULL_HASH;
};
/**
* Serialize outpoint.
* @returns {Buffer}
*/
Outpoint.prototype.toRaw = function toRaw(writer) {
var p = bcoin.writer(writer);
p.writeHash(this.hash);
p.writeU32(this.index);
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
Outpoint.prototype.fromRaw = function fromRaw(data) {
var p = bcoin.reader(data);
this.hash = p.readHash('hex');
this.index = p.readU32();
return this;
};
/**
* Instantiate outpoint from serialized data.
* @param {Buffer} data
* @returns {Outpoint}
*/
Outpoint.fromRaw = function fromRaw(data) {
return new Outpoint().fromRaw(data);
};
/**
* Inject properties from json object.
* @private
* @params {Object} json
*/
Outpoint.prototype.fromJSON = function fromJSON(json) {
assert(typeof json.hash === 'string');
assert(utils.isNumber(json.index));
this.hash = utils.revHex(json.hash);
this.index = json.index;
return this;
};
/**
* Convert the outpoint to an object suitable
* for JSON serialization. Note that the hash
* will be reversed to abide by bitcoind's legacy
* of little-endian uint256s.
* @returns {Object}
*/
Outpoint.prototype.toJSON = function toJSON() {
return {
hash: utils.revHex(this.hash),
index: this.index
};
};
/**
* Instantiate outpoint from json object.
* @param {Object} json
* @returns {Outpoint}
*/
Outpoint.fromJSON = function fromJSON(json) {
return new Outpoint().fromJSON(json);
};
/**
* Inject properties from tx.
* @private
* @param {TX} tx
* @param {Number} index
*/
Outpoint.prototype.fromTX = function fromTX(tx, index) {
assert(utils.isNumber(index));
this.hash = tx.hash('hex');
this.index = index;
return this;
};
/**
* Instantiate outpoint from tx.
* @param {TX} tx
* @param {Number} index
* @returns {Outpoint}
*/
Outpoint.fromTX = function fromTX(tx, index) {
return new Outpoint().fromTX(tx, index);
};
/**
* Convert the outpoint to a user-friendly string.
* @returns {String}
*/
Outpoint.prototype.inspect = function inspect() {
return '<Outpoint: ' + utils.revHex(this.hash) + '/' + this.index + '>';
};
var Outpoint = require('./outpoint');
/**
* Represents a transaction input.
@ -617,6 +453,4 @@ Input.isInput = function isInput(obj) {
* Expose
*/
exports = Input;
exports.Outpoint = Outpoint;
module.exports = exports;
module.exports = Input;

View File

@ -0,0 +1,117 @@
/*!
* invitem.js - inv item object for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var constants = require('../protocol/constants');
/**
* Inv Item
* @param {Number} type
* @param {Hash} hash
* @property {InvType} type
* @property {Hash} hash
*/
function InvItem(type, hash) {
if (!(this instanceof InvItem))
return new InvItem(type, hash);
this.type = type;
this.hash = hash;
}
/**
* Serialize inv item.
* @returns {Buffer}
*/
InvItem.prototype.toRaw = function toRaw(writer) {
var p = bcoin.writer(writer);
p.writeU32(this.type);
p.writeHash(this.hash);
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from serialized data.
* @param {Buffer} data
*/
InvItem.prototype.fromRaw = function fromRaw(data) {
var p = bcoin.reader(data);
this.type = p.readU32();
this.hash = p.readHash('hex');
return this;
};
/**
* Instantiate inv item from serialized data.
* @param {Buffer} data
* @param {String?} enc
* @returns {InvItem}
*/
InvItem.fromRaw = function fromRaw(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return new InvItem().fromRaw(data);
};
/**
* Test whether the inv item is a block.
* @returns {Boolean}
*/
InvItem.prototype.isBlock = function isBlock() {
switch (this.type) {
case constants.inv.BLOCK:
case constants.inv.WITNESS_BLOCK:
case constants.inv.FILTERED_BLOCK:
case constants.inv.WITNESS_FILTERED_BLOCK:
case constants.inv.CMPCT_BLOCK:
return true;
default:
return false;
}
};
/**
* Test whether the inv item is a tx.
* @returns {Boolean}
*/
InvItem.prototype.isTX = function isTX() {
switch (this.type) {
case constants.inv.TX:
case constants.inv.WITNESS_TX:
return true;
default:
return false;
}
};
/**
* Test whether the inv item has the witness bit set.
* @returns {Boolean}
*/
InvItem.prototype.hasWitness = function hasWitness() {
return (this.type & constants.WITNESS_MASK) !== 0;
};
/*
* Expose
*/
module.exports = InvItem;

View File

@ -882,11 +882,11 @@ function toKey(opt) {
if (!opt)
return opt;
if (opt.getPrivateKey)
return opt.getPrivateKey();
if (opt.privateKey)
return opt.privateKey
if (opt.getPublicKey)
return opt.getPublicKey();
if (opt.publicKey)
return opt.publicKey;
return opt;
}

View File

@ -0,0 +1,183 @@
/*!
* outpoint.js - outpoint object for bcoin
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var utils = require('../utils/utils');
var assert = utils.assert;
var constants = bcoin.constants;
/**
* Represents a COutPoint.
* @exports Outpoint
* @constructor
* @param {Hash?} hash
* @param {Number?} index
* @property {Hash} hash
* @property {Number} index
*/
function Outpoint(hash, index) {
if (!(this instanceof Outpoint))
return new Outpoint(hash, index);
this.hash = hash || constants.NULL_HASH;
this.index = index != null ? index : 0xffffffff;
}
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
Outpoint.prototype.fromOptions = function fromOptions(options) {
assert(typeof options.hash === 'string');
assert(utils.isNumber(options.index));
this.hash = options.hash;
this.index = options.index;
return this;
};
/**
* Instantate outpoint from options object.
* @param {Object} options
* @returns {Outpoint}
*/
Outpoint.fromOptions = function fromOptions(options) {
return new Outpoint().fromOptions(options);
};
/**
* Test whether the outpoint is null (hash of zeroes
* with max-u32 index). Used to detect coinbases.
* @returns {Boolean}
*/
Outpoint.prototype.isNull = function isNull() {
return this.index === 0xffffffff && this.hash === constants.NULL_HASH;
};
/**
* Serialize outpoint.
* @returns {Buffer}
*/
Outpoint.prototype.toRaw = function toRaw(writer) {
var p = bcoin.writer(writer);
p.writeHash(this.hash);
p.writeU32(this.index);
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
Outpoint.prototype.fromRaw = function fromRaw(data) {
var p = bcoin.reader(data);
this.hash = p.readHash('hex');
this.index = p.readU32();
return this;
};
/**
* Instantiate outpoint from serialized data.
* @param {Buffer} data
* @returns {Outpoint}
*/
Outpoint.fromRaw = function fromRaw(data) {
return new Outpoint().fromRaw(data);
};
/**
* Inject properties from json object.
* @private
* @params {Object} json
*/
Outpoint.prototype.fromJSON = function fromJSON(json) {
assert(typeof json.hash === 'string');
assert(utils.isNumber(json.index));
this.hash = utils.revHex(json.hash);
this.index = json.index;
return this;
};
/**
* Convert the outpoint to an object suitable
* for JSON serialization. Note that the hash
* will be reversed to abide by bitcoind's legacy
* of little-endian uint256s.
* @returns {Object}
*/
Outpoint.prototype.toJSON = function toJSON() {
return {
hash: utils.revHex(this.hash),
index: this.index
};
};
/**
* Instantiate outpoint from json object.
* @param {Object} json
* @returns {Outpoint}
*/
Outpoint.fromJSON = function fromJSON(json) {
return new Outpoint().fromJSON(json);
};
/**
* Inject properties from tx.
* @private
* @param {TX} tx
* @param {Number} index
*/
Outpoint.prototype.fromTX = function fromTX(tx, index) {
assert(utils.isNumber(index));
this.hash = tx.hash('hex');
this.index = index;
return this;
};
/**
* Instantiate outpoint from tx.
* @param {TX} tx
* @param {Number} index
* @returns {Outpoint}
*/
Outpoint.fromTX = function fromTX(tx, index) {
return new Outpoint().fromTX(tx, index);
};
/**
* Convert the outpoint to a user-friendly string.
* @returns {String}
*/
Outpoint.prototype.inspect = function inspect() {
return '<Outpoint: ' + utils.revHex(this.hash) + '/' + this.index + '>';
};
/*
* Expose
*/
module.exports = Outpoint;

176
lib/bcoin/script/opcode.js Normal file
View File

@ -0,0 +1,176 @@
/*!
* script.js - script interpreter for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var bn = require('bn.js');
var constants = bcoin.constants;
var utils = require('../utils/utils');
var assert = utils.assert;
var opcodes = constants.opcodes;
/**
* A simple struct which contains
* an opcode and pushdata buffer.
* @exports Opcode
* @constructor
* @param {Number} value - Opcode.
* @param {Buffer?} data - Pushdata buffer.
* @property {Number} value
* @property {Buffer|null} data
*/
function Opcode(value, data) {
if (!(this instanceof Opcode))
return new Opcode(value, data);
this.value = value;
this.data = data || null;
}
/**
* Encode the opcode.
* @returns {Buffer}
*/
Opcode.prototype.toRaw = function toRaw(writer) {
return bcoin.script.encode([this], writer);
};
/**
* Instantiate an opcode from a number opcode.
* @param {Number} op
* @returns {Opcode}
*/
Opcode.fromOp = function fromOp(op) {
return new Opcode(op);
};
/**
* Instantiate a pushdata opcode from
* a buffer (will encode minimaldata).
* @param {Buffer} data
* @returns {Opcode}
*/
Opcode.fromData = function fromData(data) {
if (data.length === 0)
return new Opcode(opcodes.OP_0);
if (data.length === 1) {
if (data[0] >= 1 && data[0] <= 16)
return new Opcode(data[0] + 0x50);
if (data[0] === 0x81)
return new Opcode(opcodes.OP_1NEGATE);
}
return Opcode.fromPush(data);
};
/**
* Instantiate a pushdata opcode from a
* buffer (this differs from fromData in
* that it will _always_ be a pushdata op).
* @param {Buffer} data
* @returns {Opcode}
*/
Opcode.fromPush = function fromPush(data) {
if (data.length <= 0x4b)
return new Opcode(data.length, data);
if (data.length <= 0xff)
return new Opcode(opcodes.OP_PUSHDATA1, data);
if (data.length <= 0xffff)
return new Opcode(opcodes.OP_PUSHDATA2, data);
if (data.length <= 0xffffffff)
return new Opcode(opcodes.OP_PUSHDATA4, data);
throw new Error('Pushdata size too large.');
};
/**
* Instantiate an opcode from a Number.
* @param {Number|BN} num
* @returns {Opcode}
*/
Opcode.fromNumber = function fromNumber(num) {
return Opcode.fromData(bcoin.script.array(num));
};
/**
* Instantiate an opcode from a small number.
* @param {Number} num
* @returns {Opcode}
*/
Opcode.fromSmall = function fromSmall(num) {
assert(utils.isNumber(num) && num >= 0 && num <= 16);
return new Opcode(num === 0 ? 0 : num + 0x50);
};
/**
* Instantiate a pushdata opcode from a string.
* @param {String} data
* @returns {Opcode}
*/
Opcode.fromString = function fromString(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return Opcode.fromData(data);
};
/**
* Instantiate a pushdata opcode from anything.
* @param {String|Buffer|Number|BN|Opcode} data
* @returns {Opcode}
*/
Opcode.from = function from(data) {
if (data instanceof Opcode)
return data;
if (typeof data === 'number')
return Opcode.fromOp(data);
if (Buffer.isBuffer(data))
return Opcode.fromData(data);
if (typeof data === 'string')
return Opcode.fromString(data, 'utf8');
if (bn.isBN(data))
return Opcode.fromNumber(data);
assert(false, 'Bad data for opcode.');
};
/**
* Test whether an object an Opcode.
* @param {Object} obj
* @returns {Boolean}
*/
Opcode.isOpcode = function isOpcode(obj) {
return obj
&& typeof obj.value === 'number'
&& (Buffer.isBuffer(obj.data) || obj.data === null);
};
/*
* Expose
*/
module.exports = Opcode;

109
lib/bcoin/script/program.js Normal file
View File

@ -0,0 +1,109 @@
/*!
* script.js - script interpreter for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var constants = bcoin.constants;
var utils = require('../utils/utils');
var assert = utils.assert;
var opcodes = constants.opcodes;
var scriptTypes = constants.scriptTypes;
/**
* Witness Program
* @constructor
* @param {Number} version
* @param {Buffer} data
* @property {Number} version - Ranges from 0 to 16.
* @property {String|null} type - Null if malformed. `unknown` if unknown
* version (treated as anyone-can-spend). Otherwise one of `witnesspubkeyhash`
* or `witnessscripthash`.
* @property {Buffer} data - The hash (for now).
*/
function Program(version, data) {
if (!(this instanceof Program))
return new Program(version, data);
assert(utils.isNumber(version));
assert(Buffer.isBuffer(data));
assert(version >= 0 && version <= 16);
assert(data.length >= 2 && data.length <= 40);
this.version = version;
this.data = data;
}
/**
* Get the witness program type.
* @returns {ScriptType}
*/
Program.prototype.getType = function getType() {
if (this.version === 0) {
if (this.data.length === 20)
return scriptTypes.WITNESSPUBKEYHASH;
if (this.data.length === 32)
return scriptTypes.WITNESSSCRIPTHASH;
// Fail on bad version=0
return scriptTypes.WITNESSMALFORMED;
}
if (this.version === 1) {
if (this.data.length === 32)
return scriptTypes.WITNESSMASTHASH;
// Fail on bad version=1
return scriptTypes.WITNESSMALFORMED;
}
// No interpretation of script (anyone can spend)
return scriptTypes.NONSTANDARD;
};
/**
* Test whether the program is either
* an unknown version or malformed.
* @returns {Boolean}
*/
Program.prototype.isUnknown = function isUnknown() {
var type = this.getType();
return type === scriptTypes.WITNESSMALFORMED
|| type === scriptTypes.NONSTANDARD;
};
/**
* Test whether the program is malformed.
* @returns {Boolean}
*/
Program.prototype.isMalformed = function isMalformed() {
return this.getType() === scriptTypes.WITNESSMALFORMED;
};
/**
* Inspect the program.
* @returns {String}
*/
Program.prototype.inspect = function inspect() {
return '<Program:'
+ ' version=' + this.version
+ ' data=' + this.data.toString('hex')
+ ' type=' + constants.scriptTypesByVal[this.getType()].toLowerCase()
+ '>';
};
/*
* Expose
*/
module.exports = Program;

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
'use strict';
var bcoin = require('./env');
var bcoin = require('../env');
var utils = bcoin.utils;
var assert = utils.assert;

537
lib/bcoin/script/stack.js Normal file
View File

@ -0,0 +1,537 @@
/*!
* script.js - script interpreter for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var constants = bcoin.constants;
var opcodes = constants.opcodes;
var ScriptError = bcoin.errors.ScriptError;
/**
* Represents the stack of a Script during execution.
* @exports Stack
* @constructor
* @param {Buffer[]?} items - Stack items.
* @property {Buffer[]} items - Stack items.
* @property {Number} length - Size of stack.
*/
function Stack(items) {
if (!(this instanceof Stack))
return new Stack(items);
this.items = items || [];
}
Stack.prototype.__defineGetter__('length', function() {
return this.items.length;
});
Stack.prototype.__defineSetter__('length', function(length) {
return this.items.length = length;
});
/**
* Inspect the stack.
* @returns {String} Human-readable stack.
*/
Stack.prototype.inspect = function inspect() {
return '<Stack: ' + this.toString() + '>';
};
/**
* Convert the stack to a string.
* @returns {String} Human-readable stack.
*/
Stack.prototype.toString = function toString() {
return bcoin.witness.format(this.items);
};
/**
* Format the stack as bitcoind asm.
* @param {Boolean?} decode - Attempt to decode hash types.
* @returns {String} Human-readable script.
*/
Stack.prototype.toASM = function toASM(decode) {
return bcoin.script.formatASM(this.items, decode);
};
/**
* Pop the redeem script off the stack and deserialize it.
* @returns {Script|null} The redeem script.
*/
Stack.prototype.getRedeem = function getRedeem() {
var redeem = this.items[this.items.length - 1];
if (!redeem)
return;
return new bcoin.script(redeem);
};
/**
* Clone the stack.
* @returns {Stack} Cloned stack.
*/
Stack.prototype.clone = function clone() {
return new Stack(this.items.slice());
};
/**
* Get total size of the stack, including the alt stack.
* @param {Array} alt - Alt stack.
* @returns {Number}
*/
Stack.prototype.getSize = function getSize(alt) {
return this.items.length + alt.length;
};
/**
* Push item onto stack.
* @see Array#push
* @param {Buffer} item
* @returns {Number} Stack size.
*/
Stack.prototype.push = function push(item) {
return this.items.push(item);
};
/**
* Unshift item from stack.
* @see Array#unshift
* @param {Buffer} item
* @returns {Number}
*/
Stack.prototype.unshift = function unshift(item) {
return this.items.unshift(item);
};
/**
* Slice out part of the stack items.
* @param {Number} start
* @param {Number} end
* @see Array#slice
* @returns {Buffer[]}
*/
Stack.prototype.slice = function slice(start, end) {
return this.items.slice(start, end);
};
/**
* Splice stack items.
* @see Array#splice
* @param {Number} index
* @param {Number} remove
* @param {Buffer?} insert
* @returns {Buffer[]}
*/
Stack.prototype.splice = function splice(i, remove, insert) {
if (insert === undefined)
return this.items.splice(i, remove);
return this.items.splice(i, remove, insert);
};
/**
* Pop a stack item.
* @see Array#pop
* @returns {Buffer|null}
*/
Stack.prototype.pop = function pop() {
return this.items.pop();
};
/**
* Shift a stack item.
* @see Array#shift
* @returns {Buffer|null}
*/
Stack.prototype.shift = function shift() {
return this.items.shift();
};
/**
* Get a stack item by index.
* @param {Number} index
* @returns {Buffer|null}
*/
Stack.prototype.get = function get(i) {
return this.items[i];
};
/**
* Get a stack item relative to
* the top of the stack.
* @example
* stack.top(-1);
* @param {Number} index
* @returns {Buffer|null}
*/
Stack.prototype.top = function top(i) {
return this.items[this.items.length + i];
};
/**
* Clear the stack.
*/
Stack.prototype.clear = function clear() {
this.items.length = 0;
};
/**
* Set stack item at index.
* @param {Number} index
* @param {Buffer} value
* @returns {Buffer}
*/
Stack.prototype.set = function set(i, value) {
return this.items[i] = value;
};
/**
* Swap stack values.
* @private
* @param {Number} i1 - Index 1.
* @param {Number} i2 - Index 2.
*/
Stack.prototype._swap = function _swap(i1, i2) {
var v1, v2;
i1 = this.items.length + i1;
i2 = this.items.length + i2;
v1 = this.items[i1];
v2 = this.items[i2];
this.items[i1] = v2;
this.items[i2] = v1;
};
/**
* Perform the OP_TOALTSTACK operation.
* @param {Array} alt - Alt stack.
* @throws {ScriptError}
*/
Stack.prototype.toalt = function toalt(alt) {
if (this.length === 0)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_TOALTSTACK);
alt.push(this.pop());
};
/**
* Perform the OP_FROMALTSTACK operation.
* @param {Array} alt - Alt stack.
* @throws {ScriptError}
*/
Stack.prototype.fromalt = function fromalt(alt) {
if (alt.length === 0)
throw new ScriptError('INVALID_ALTSTACK_OPERATION', opcodes.OP_FROMALTSTACK);
this.push(alt.pop());
};
/**
* Perform the OP_IFDUP operation.
* @throws {ScriptError}
*/
Stack.prototype.ifdup = function ifdup() {
if (this.length === 0)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_IFDUP);
if (bcoin.script.bool(this.top(-1)))
this.push(this.top(-1));
};
/**
* Perform the OP_DEPTH operation.
* @throws {ScriptError}
*/
Stack.prototype.depth = function depth() {
this.push(bcoin.script.array(this.length));
};
/**
* Perform the OP_DROP operation.
* @throws {ScriptError}
*/
Stack.prototype.drop = function drop() {
if (this.length === 0)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_DROP);
this.pop();
};
/**
* Perform the OP_DUP operation.
* @throws {ScriptError}
*/
Stack.prototype.dup = function dup() {
if (this.length === 0)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_DUP);
this.push(this.top(-1));
};
/**
* Perform the OP_NIP operation.
* @throws {ScriptError}
*/
Stack.prototype.nip = function nip() {
if (this.length < 2)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_NIP);
this.splice(this.length - 2, 1);
};
/**
* Perform the OP_OVER operation.
* @throws {ScriptError}
*/
Stack.prototype.over = function over() {
if (this.length < 2)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_OVER);
this.push(this.top(-2));
};
/**
* Perform the OP_PICK operation.
* @param {VerifyFlags} flags
* @throws {ScriptError}
*/
Stack.prototype.pick = function pick(flags) {
return this._pickroll(opcodes.OP_PICK, flags);
};
/**
* Perform the OP_ROLL operation.
* @param {VerifyFlags} flags
* @throws {ScriptError}
*/
Stack.prototype.roll = function roll(flags) {
return this._pickroll(opcodes.OP_ROLL, flags);
};
/**
* Perform a pick or roll.
* @private
* @param {Number} op
* @param {VerifyFlags} flags
* @throws {ScriptError}
*/
Stack.prototype._pickroll = function pickroll(op, flags) {
var val, n;
if (this.length < 2)
throw new ScriptError('INVALID_STACK_OPERATION', op);
val = this.pop();
n = bcoin.script.num(val, flags).toNumber();
if (n < 0 || n >= this.length)
throw new ScriptError('INVALID_STACK_OPERATION', op);
val = this.top(-n - 1);
if (op === opcodes.OP_ROLL)
this.splice(this.length - n - 1, 1);
this.push(val);
};
/**
* Perform the OP_ROT operation.
* @throws {ScriptError}
*/
Stack.prototype.rot = function rot() {
if (this.length < 3)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_ROT);
this._swap(-3, -2);
this._swap(-2, -1);
};
/**
* Perform the OP_SWAP operation.
* @throws {ScriptError}
*/
Stack.prototype.swap = function swap() {
if (this.length < 2)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_SWAP);
this._swap(-2, -1);
};
/**
* Perform the OP_TUCK operation.
* @throws {ScriptError}
*/
Stack.prototype.tuck = function tuck() {
if (this.length < 2)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_TUCK);
this.splice(this.length - 2, 0, this.top(-1));
};
/**
* Perform the OP_2DROP operation.
* @throws {ScriptError}
*/
Stack.prototype.drop2 = function drop2() {
if (this.length < 2)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2DROP);
this.pop();
this.pop();
};
/**
* Perform the OP_2DUP operation.
* @throws {ScriptError}
*/
Stack.prototype.dup2 = function dup2() {
var v1, v2;
if (this.length < 2)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2DUP);
v1 = this.top(-2);
v2 = this.top(-1);
this.push(v1);
this.push(v2);
};
/**
* Perform the OP_3DUP operation.
* @throws {ScriptError}
*/
Stack.prototype.dup3 = function dup3() {
var v1, v2, v3;
if (this.length < 3)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_3DUP);
v1 = this.top(-3);
v2 = this.top(-2);
v3 = this.top(-1);
this.push(v1);
this.push(v2);
this.push(v3);
};
/**
* Perform the OP_2OVER operation.
* @throws {ScriptError}
*/
Stack.prototype.over2 = function over2() {
var v1, v2;
if (this.length < 4)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2OVER);
v1 = this.top(-4);
v2 = this.top(-3);
this.push(v1);
this.push(v2);
};
/**
* Perform the OP_2ROT operation.
* @throws {ScriptError}
*/
Stack.prototype.rot2 = function rot2() {
var v1, v2;
if (this.length < 6)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2ROT);
v1 = this.top(-6);
v2 = this.top(-5);
this.splice(this.length - 6, 2);
this.push(v1);
this.push(v2);
};
/**
* Perform the OP_2SWAP operation.
* @throws {ScriptError}
*/
Stack.prototype.swap2 = function swap2() {
if (this.length < 4)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2SWAP);
this._swap(-4, -2);
this._swap(-3, -1);
};
/**
* Perform the OP_SIZE operation.
* @throws {ScriptError}
*/
Stack.prototype.size = function size() {
if (this.length < 1)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_SIZE);
this.push(bcoin.script.array(this.top(-1).length));
};
/**
* Test an object to see if it is a Stack.
* @param {Object} obj
* @returns {Boolean}
*/
Stack.isStack = function isStack(obj) {
return obj && Array.isArray(obj.items) && typeof obj.swap2 === 'function';
};
/*
* Expose
*/
module.exports = Stack;

613
lib/bcoin/script/witness.js Normal file
View File

@ -0,0 +1,613 @@
/*!
* script.js - script interpreter for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var bn = require('bn.js');
var constants = bcoin.constants;
var utils = require('../utils/utils');
var assert = utils.assert;
var opcodes = constants.opcodes;
var STACK_FALSE = new Buffer([]);
var STACK_NEGATE = new Buffer([0x81]);
var scriptTypes = constants.scriptTypes;
var Script = require('./script');
var Opcode = require('./opcode');
/**
* Refers to the witness field of segregated witness transactions.
* @exports Witness
* @constructor
* @param {Buffer[]|NakedWitness} items - Array of
* stack items.
* @property {Buffer[]} items
* @property {Script?} redeem
* @property {Number} length
*/
function Witness(options) {
if (!(this instanceof Witness))
return new Witness(options);
this.items = [];
if (options)
this.fromOptions(options);
}
Witness.prototype.__defineGetter__('length', function() {
return this.items.length;
});
Witness.prototype.__defineSetter__('length', function(length) {
return this.items.length = length;
});
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
Witness.prototype.fromOptions = function fromOptions(options) {
var items;
assert(options, 'Witness data is required.');
items = options.items;
if (!items)
items = options;
if (items)
this.fromArray(items);
return this;
};
/**
* Instantiate witness from options.
* @param {Object} options
* @returns {Witness}
*/
Witness.fromOptions = function fromOptions(options) {
return new Witness().fromOptions(options);
};
/**
* Convert witness to an array of buffers.
* @returns {Buffer[]}
*/
Witness.prototype.toArray = function toArray() {
return this.items.slice();
};
/**
* Inject properties from an array of buffers.
* @private
* @param {Buffer[]} items
*/
Witness.prototype.fromArray = function fromArray(items) {
assert(Array.isArray(items));
this.items = items;
return this;
};
/**
* Insantiate witness from an array of buffers.
* @param {Buffer[]} items
* @returns {Witness}
*/
Witness.fromArray = function fromArray(items) {
return new Witness().fromArray(items);
};
/**
* Inspect a Witness object.
* @returns {String} Human-readable script.
*/
Witness.prototype.inspect = function inspect() {
return '<Witness: ' + this.toString() + '>';
};
/**
* Convert a Witness object to a String.
* @returns {String} Human-readable script.
*/
Witness.prototype.toString = function toString() {
return Witness.format(this.items);
};
/**
* Format the witness object as bitcoind asm.
* @param {Boolean?} decode - Attempt to decode hash types.
* @returns {String} Human-readable script.
*/
Witness.prototype.toASM = function toASM(decode) {
return Script.formatASM(Script.parseArray(this.items), decode);
};
/**
* Clone the witness object. Note that the raw
* encoded witness data will be lost. This is
* because the function assumes you are going
* to be altering the stack items by hand.
* @returns {Witness} A clone of the current witness object.
*/
Witness.prototype.clone = function clone() {
return new Witness(this.items.slice());
};
/**
* Convert the Witness to a Stack object.
* This is usually done before executing
* a witness program.
* @returns {Stack}
*/
Witness.prototype.toStack = function toStack() {
return new bcoin.stack(this.items.slice());
};
/**
* "Guess" the type of the witness.
* This method is not 100% reliable.
* @returns {ScriptType}
*/
Witness.prototype.getInputType = function getInputType() {
if (this.isPubkeyhashInput())
return scriptTypes.WITNESSPUBKEYHASH;
if (this.isScripthashInput())
return scriptTypes.WITNESSSCRIPTHASH;
return scriptTypes.NONSTANDARD;
};
/**
* "Guess" the address of the witness.
* This method is not 100% reliable.
* @returns {Address|null}
*/
Witness.prototype.getInputAddress = function getInputAddress() {
return bcoin.address.fromWitness(this);
};
/**
* "Test" whether the witness is a pubkey input.
* Always returns false.
* @returns {Boolean}
*/
Witness.prototype.isPubkeyInput = function isPubkeyInput() {
return false;
};
/**
* "Guess" whether the witness is a pubkeyhash input.
* This method is not 100% reliable.
* @returns {Boolean}
*/
Witness.prototype.isPubkeyhashInput = function isPubkeyhashInput() {
return this.items.length === 2
&& Script.isSignatureEncoding(this.items[0])
&& Script.isKeyEncoding(this.items[1]);
};
/**
* "Test" whether the witness is a multisig input.
* Always returns false.
* @returns {Boolean}
*/
Witness.prototype.isMultisigInput = function isMultisigInput() {
return false;
};
/**
* "Guess" whether the witness is a scripthash input.
* This method is not 100% reliable.
* @returns {Boolean}
*/
Witness.prototype.isScripthashInput = function isScripthashInput() {
return this.items.length > 0 && !this.isPubkeyhashInput();
};
/**
* "Guess" whether the witness is an unknown/non-standard type.
* This method is not 100% reliable.
* @returns {Boolean}
*/
Witness.prototype.isUnknownInput = function isUnknownInput() {
return this.getInputType() === scriptTypes.NONSTANDARD;
};
/**
* Test the witness against a bloom filter.
* @param {Bloom} filter
* @returns {Boolean}
*/
Witness.prototype.test = function test(filter) {
var i, chunk;
for (i = 0; i < this.items.length; i++) {
chunk = this.items[i];
if (chunk.length === 0)
continue;
if (filter.test(chunk))
return true;
}
return false;
};
/**
* Grab and deserialize the redeem script from the witness.
* @returns {Script} Redeem script.
*/
Witness.prototype.getRedeem = function getRedeem() {
var redeem = this.items[this.items.length - 1];
if (!redeem)
return;
return new Script(redeem);
};
/**
* Does nothing currently.
*/
Witness.prototype.compile = function compile() {
// NOP
};
/**
* Find a data element in a witness.
* @param {Buffer} data - Data element to match against.
* @returns {Number} Index (`-1` if not present).
*/
Witness.prototype.indexOf = function indexOf(data) {
return utils.indexOf(this.items, data);
};
/**
* Encode the witness to a Buffer.
* @param {String} enc - Encoding, either `'hex'` or `null`.
* @returns {Buffer|String} Serialized script.
*/
Witness.prototype.toRaw = function toRaw(writer) {
var p = bcoin.writer(writer);
var i;
p.writeVarint(this.items.length);
for (i = 0; i < this.items.length; i++)
p.writeVarBytes(this.items[i]);
if (!writer)
p = p.render();
return p;
};
/**
* Convert witness to a hex string.
* @returns {String}
*/
Witness.prototype.toJSON = function toJSON() {
return this.toRaw().toString('hex');
};
/**
* Inject properties from json object.
* @private
* @param {String} json
*/
Witness.prototype.fromJSON = function fromJSON(json) {
assert(typeof json === 'string');
return this.fromRaw(new Buffer(json, 'hex'));
};
/**
* Insantiate witness from a hex string.
* @param {String} json
* @returns {Witness}
*/
Witness.fromJSON = function fromJSON(json) {
return new Witness().fromJSON(json);
};
/**
* Unshift an item onto the witness vector.
* @param {Number|String|Buffer|BN} data
* @returns {Number}
*/
Witness.prototype.unshift = function unshift(data) {
return this.items.unshift(Witness.encodeItem(data));
};
/**
* Push an item onto the witness vector.
* @param {Number|String|Buffer|BN} data
* @returns {Number}
*/
Witness.prototype.push = function push(data) {
return this.items.push(Witness.encodeItem(data));
};
/**
* Shift an item off the witness vector.
* @returns {Buffer}
*/
Witness.prototype.shift = function shift() {
return this.items.shift();
};
/**
* Shift an item off the witness vector.
* @returns {Buffer}
*/
Witness.prototype.pop = function push(data) {
return this.items.pop();
};
/**
* Remove an item from the witness vector.
* @param {Number} index
* @returns {Buffer}
*/
Witness.prototype.remove = function remove(i) {
return this.items.splice(i, 1)[0];
};
/**
* Insert an item into the witness vector.
* @param {Number} index
* @param {Number|String|Buffer|BN} data
*/
Witness.prototype.insert = function insert(i, data) {
assert(i <= this.items.length, 'Index out of bounds.');
this.items.splice(i, 0, Witness.encodeItem(data))[0];
};
/**
* Get an item from the witness vector.
* @param {Number} index
* @returns {Buffer}
*/
Witness.prototype.get = function get(i) {
return this.items[i];
};
/**
* Get a small int (0-16) from the witness vector.
* @param {Number} index
* @returns {Number} `-1` on non-existent.
*/
Witness.prototype.getSmall = function getSmall(i) {
var item = this.items[i];
if (!item || item.length > 1)
return -1;
if (item.length === 0)
return 0;
if (!(item[0] >= 1 && item[1] <= 16))
return -1;
return item[0];
};
/**
* Get a number from the witness vector.
* @param {Number} index
* @returns {BN}
*/
Witness.prototype.getNumber = function getNumber(i) {
var item = this.items[i];
if (!item || item.length > 5)
return;
return Script.num(item, constants.flags.VERIFY_NONE, 5);
};
/**
* Get a string from the witness vector.
* @param {Number} index
* @returns {String}
*/
Witness.prototype.getString = function getString(i) {
var item = this.items[i];
if (!item)
return;
return item.toString('utf8');
};
/**
* Clear the witness items.
*/
Witness.prototype.clear = function clear() {
this.items.length = 0;
};
/**
* Set an item in the witness vector.
* @param {Number} index
* @param {Number|String|Buffer|BN} data
*/
Witness.prototype.set = function set(i, data) {
assert(i <= this.items.length, 'Index out of bounds.');
this.items[i] = Witness.encodeItem(data);
};
/**
* Encode a witness item.
* @param {Number|String|Buffer|BN} data
* @returns {Buffer}
*/
Witness.encodeItem = function encodeItem(data) {
if (data instanceof Opcode)
data = data.data || data.value;
if (typeof data === 'number') {
if (data === opcodes.OP_1NEGATE)
return STACK_NEGATE;
if (data === opcodes.OP_0)
return STACK_FALSE;
if (data >= opcodes.OP_1 && data <= opcodes.OP_16)
return new Buffer([data - 0x50]);
throw new Error('Non-push opcode in witness.');
}
if (bn.isBN(data))
return Script.array(data);
if (typeof data === 'string')
return new Buffer(data, 'utf8');
return data;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
Witness.prototype.fromRaw = function fromRaw(data) {
var p = bcoin.reader(data);
var chunkCount = p.readVarint();
var i;
for (i = 0; i < chunkCount; i++)
this.items.push(p.readVarBytes());
return this;
};
/**
* Create a Witness from a serialized buffer.
* @param {Buffer|String} data - Serialized witness.
* @param {String?} enc - Either `"hex"` or `null`.
* @returns {Witness}
*/
Witness.fromRaw = function fromRaw(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return new Witness().fromRaw(data);
};
/**
* Inject items from string.
* @private
* @param {String|String[]} items
*/
Witness.prototype.fromString = function fromString(items) {
var i;
if (!Array.isArray(items)) {
assert(typeof items === 'string');
items = items.trim();
if (items.length === 0)
return this;
items = items.split(/\s+/);
}
for (i = 0; i < items.length; i++)
this.items.push(new Buffer(items[i], 'hex'));
return this;
};
/**
* Parse a test script/array
* string into a witness object. _Must_
* contain only stack items (no non-push
* opcodes).
* @param {String|String[]} items - Script string.
* @returns {Witness}
* @throws Parse error.
*/
Witness.fromString = function fromString(items) {
return new Witness().fromString(items);
};
/**
* Format script code into a human readable-string.
* @param {Array} code
* @returns {String} Human-readable string.
*/
Witness.format = function format(items) {
var out = [];
var i;
for (i = 0; i < items.length; i++)
out.push(items[i].toString('hex'));
return out.join(' ');
};
/**
* Test an object to see if it is a Witness.
* @param {Object} obj
* @returns {Boolean}
*/
Witness.isWitness = function isWitness(obj) {
return obj
&& Array.isArray(obj.items)
&& typeof obj.toStack === 'function';
};
/*
* Expose
*/
module.exports = Witness;

851
lib/bcoin/wallet/account.js Normal file
View File

@ -0,0 +1,851 @@
/*!
* account.js - account object for bcoin
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var utils = require('../utils/utils');
var assert = utils.assert;
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
/**
* Represents a BIP44 Account belonging to a {@link Wallet}.
* Note that this object does not enforce locks. Any method
* that does a write is internal API only and will lead
* to race conditions if used elsewhere.
* @exports Account
* @constructor
* @param {Object} options
* @param {WalletDB} options.db
* @param {HDPublicKey} options.accountKey
* @param {Boolean?} options.witness - Whether to use witness programs.
* @param {Number} options.accountIndex - The BIP44 account index.
* @param {Number?} options.receiveDepth - The index of the _next_ receiving
* address.
* @param {Number?} options.changeDepth - The index of the _next_ change
* address.
* @param {String?} options.type - Type of wallet (pubkeyhash, multisig)
* (default=pubkeyhash).
* @param {Number?} options.m - `m` value for multisig.
* @param {Number?} options.n - `n` value for multisig.
* @param {String?} options.wid - Wallet ID
* @param {String?} options.name - Account name
*/
function Account(db, options) {
if (!(this instanceof Account))
return new Account(db, options);
assert(db, 'Database is required.');
this.db = db;
this.network = db.network;
this.lookahead = Account.MAX_LOOKAHEAD;
this.receiveAddress = null;
this.changeAddress = null;
this.wid = 0;
this.id = null;
this.name = null;
this.witness = this.db.options.witness;
this.accountKey = null;
this.accountIndex = 0;
this.receiveDepth = 0;
this.changeDepth = 0;
this.type = Account.types.PUBKEYHASH;
this.m = 1;
this.n = 1;
this.keys = [];
this.initialized = false;
if (options)
this.fromOptions(options);
}
/**
* Account types.
* @enum {Number}
* @default
*/
Account.types = {
PUBKEYHASH: 0,
MULTISIG: 1
};
/**
* Account types by value.
* @const {RevMap}
*/
Account.typesByVal = {
0: 'pubkeyhash',
1: 'multisig'
};
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
Account.prototype.fromOptions = function fromOptions(options) {
var i;
assert(options, 'Options are required.');
assert(utils.isNumber(options.wid));
assert(utils.isName(options.id), 'Bad Wallet ID.');
assert(bcoin.hd.isHD(options.accountKey), 'Account key is required.');
assert(utils.isNumber(options.accountIndex), 'Account index is required.');
this.wid = options.wid;
this.id = options.id;
if (options.name != null) {
assert(utils.isName(options.name), 'Bad account name.');
this.name = options.name;
}
if (options.witness != null) {
assert(typeof options.witness === 'boolean');
this.witness = options.witness;
}
this.accountKey = options.accountKey;
if (options.accountIndex != null) {
assert(utils.isNumber(options.accountIndex));
this.accountIndex = options.accountIndex;
}
if (options.receiveDepth != null) {
assert(utils.isNumber(options.receiveDepth));
this.receiveDepth = options.receiveDepth;
}
if (options.changeDepth != null) {
assert(utils.isNumber(options.changeDepth));
this.changeDepth = options.changeDepth;
}
if (options.type != null) {
if (typeof options.type === 'string') {
this.type = Account.types[options.type.toUpperCase()];
assert(this.type != null);
} else {
assert(typeof options.type === 'number');
this.type = options.type;
assert(Account.typesByVal[this.type]);
}
}
if (options.m != null) {
assert(utils.isNumber(options.m));
this.m = options.m;
}
if (options.n != null) {
assert(utils.isNumber(options.n));
this.n = options.n;
}
if (options.initialized != null) {
assert(typeof options.initialized === 'boolean');
this.initialized = options.initialized;
}
if (this.n > 1)
this.type = Account.types.MULTISIG;
if (this.m < 1 || this.m > this.n)
throw new Error('m ranges between 1 and n');
if (!this.name)
this.name = this.accountIndex + '';
if (options.keys) {
assert(Array.isArray(options.keys));
for (i = 0; i < options.keys.length; i++)
this.pushKey(options.keys[i]);
}
return this;
};
/**
* Instantiate account from options.
* @param {WalletDB} db
* @param {Object} options
* @returns {Account}
*/
Account.fromOptions = function fromOptions(db, options) {
return new Account(db).fromOptions(options);
};
/*
* Default address lookahead.
* @const {Number}
*/
Account.MAX_LOOKAHEAD = 5;
/**
* Attempt to intialize the account (generating
* the first addresses along with the lookahead
* addresses). Called automatically from the
* walletdb.
* @param {Function} callback
*/
Account.prototype.init = function init(callback) {
// Waiting for more keys.
if (this.keys.length !== this.n - 1) {
assert(!this.initialized);
this.save();
return callback();
}
assert(this.receiveDepth === 0);
assert(this.changeDepth === 0);
this.initialized = true;
this.setDepth(1, 1, callback);
};
/**
* Open the account (done after retrieval).
* @param {Function} callback
*/
Account.prototype.open = function open(callback) {
if (!this.initialized)
return callback();
this.receiveAddress = this.deriveReceive(this.receiveDepth - 1);
this.changeAddress = this.deriveChange(this.changeDepth - 1);
callback();
};
/**
* Add a public account key to the account (multisig).
* Does not update the database.
* @param {HDPublicKey} key - Account (bip44)
* key (can be in base58 form).
* @throws Error on non-hdkey/non-accountkey.
*/
Account.prototype.pushKey = function pushKey(key) {
var index;
if (bcoin.hd.isExtended(key))
key = bcoin.hd.fromBase58(key);
if (!bcoin.hd.isPublic(key))
throw new Error('Must add HD keys to wallet.');
if (!key.isAccount44())
throw new Error('Must add HD account keys to BIP44 wallet.');
if (key.equal(this.accountKey))
throw new Error('Cannot add own key.');
index = utils.binaryInsert(this.keys, key, cmp, true);
if (index === -1)
return false;
if (this.keys.length > this.n - 1) {
utils.binaryRemove(this.keys, key, cmp);
throw new Error('Cannot add more keys.');
}
return true;
};
/**
* Remove a public account key to the account (multisig).
* Does not update the database.
* @param {HDPublicKey} key - Account (bip44)
* key (can be in base58 form).
* @throws Error on non-hdkey/non-accountkey.
*/
Account.prototype.spliceKey = function spliceKey(key) {
if (bcoin.hd.isExtended(key))
key = bcoin.hd.fromBase58(key);
if (!bcoin.hd.isHDPublicKey(key))
throw new Error('Must add HD keys to wallet.');
if (!key.isAccount44())
throw new Error('Must add HD account keys to BIP44 wallet.');
if (key.equal(this.accountKey))
throw new Error('Cannot remove own key.');
if (this.keys.length === this.n - 1)
throw new Error('Cannot remove key.');
return utils.binaryRemove(this.keys, key, cmp);
};
/**
* Add a public account key to the account (multisig).
* Saves the key in the wallet database.
* @param {HDPublicKey} key
* @param {Function} callback
*/
Account.prototype.addKey = function addKey(key, callback) {
var self = this;
var result = false;
try {
result = this.pushKey(key);
} catch (e) {
return callback(e);
}
this._checkKeys(function(err, exists) {
if (err)
return callback(err);
if (exists) {
self.spliceKey(key);
return callback(new Error('Cannot add a key from another account.'));
}
// Try to initialize again.
self.init(function(err) {
if (err)
return callback(err);
callback(null, result);
});
});
};
/**
* Ensure accounts are not sharing keys.
* @private
* @param {Function} callback
*/
Account.prototype._checkKeys = function _checkKeys(callback) {
var self = this;
var ring, hash;
if (this.initialized || this.type !== Account.types.MULTISIG)
return callback(null, false);
if (this.keys.length !== this.n - 1)
return callback(null, false);
ring = this.deriveReceive(0);
hash = ring.getScriptHash('hex');
this.db.getAddressPaths(hash, function(err, paths) {
if (err)
return callback(err);
if (!paths)
return callback(null, false);
callback(null, paths[self.wid] != null);
});
};
/**
* Remove a public account key from the account (multisig).
* Remove the key from the wallet database.
* @param {HDPublicKey} key
* @param {Function} callback
*/
Account.prototype.removeKey = function removeKey(key, callback) {
var result = false;
try {
result = this.spliceKey(key);
} catch (e) {
return callback(e);
}
this.save();
callback(null, result);
};
/**
* Create a new receiving address (increments receiveDepth).
* @returns {KeyRing}
*/
Account.prototype.createReceive = function createReceive(callback) {
return this.createAddress(false, callback);
};
/**
* Create a new change address (increments receiveDepth).
* @returns {KeyRing}
*/
Account.prototype.createChange = function createChange(callback) {
return this.createAddress(true, callback);
};
/**
* Create a new address (increments depth).
* @param {Boolean} change
* @param {Function} callback - Returns [Error, {@link KeyRing}].
*/
Account.prototype.createAddress = function createAddress(change, callback) {
var self = this;
var ring, lookahead;
if (typeof change === 'function') {
callback = change;
change = false;
}
if (change) {
ring = this.deriveChange(this.changeDepth);
lookahead = this.deriveChange(this.changeDepth + this.lookahead);
this.changeDepth++;
this.changeAddress = ring;
} else {
ring = this.deriveReceive(this.receiveDepth);
lookahead = this.deriveReceive(this.receiveDepth + this.lookahead);
this.receiveDepth++;
this.receiveAddress = ring;
}
this.saveAddress([ring, lookahead], function(err) {
if (err)
return callback(err);
self.save();
callback(null, ring);
});
};
/**
* Derive a receiving address at `index`. Do not increment depth.
* @param {Number} index
* @returns {KeyRing}
*/
Account.prototype.deriveReceive = function deriveReceive(index, master) {
return this.deriveAddress(false, index, master);
};
/**
* Derive a change address at `index`. Do not increment depth.
* @param {Number} index
* @returns {KeyRing}
*/
Account.prototype.deriveChange = function deriveChange(index, master) {
return this.deriveAddress(true, index, master);
};
/**
* Derive an address from `path` object.
* @param {Path} path
* @param {MasterKey} master
* @returns {KeyRing}
*/
Account.prototype.derivePath = function derivePath(path, master) {
var ring, script, raw;
// Imported key.
if (path.index === -1) {
assert(path.imported);
assert(this.type === Account.types.PUBKEYHASH);
raw = path.imported;
if (path.encrypted)
raw = master.decipher(raw, path.hash);
if (!raw)
return;
ring = bcoin.keyring.fromRaw(raw, this.network);
ring.path = path;
return ring;
}
// Custom redeem script.
if (path.script)
script = new bcoin.script(path.script);
ring = this.deriveAddress(path.change, path.index, master, script);
return ring;
};
/**
* Derive an address at `index`. Do not increment depth.
* @param {Boolean} change - Whether the address on the change branch.
* @param {Number} index
* @returns {KeyRing}
*/
Account.prototype.deriveAddress = function deriveAddress(change, index, master, script) {
var keys = [];
var i, key, shared, ring;
change = +change;
if (master && master.key) {
key = master.key.deriveAccount44(this.accountIndex);
key = key.derive(change).derive(index);
} else {
key = this.accountKey.derive(change).derive(index);
}
ring = bcoin.keyring.fromPublic(key.publicKey, this.network);
ring.witness = this.witness;
if (script) {
// Custom redeem script.
assert(this.type === Account.types.PUBKEYHASH);
ring.script = script;
} else {
switch (this.type) {
case Account.types.PUBKEYHASH:
break;
case Account.types.MULTISIG:
keys.push(key.publicKey);
for (i = 0; i < this.keys.length; i++) {
shared = this.keys[i];
shared = shared.derive(change).derive(index);
keys.push(shared.publicKey);
}
ring.script = bcoin.script.fromMultisig(this.m, this.n, keys);
break;
}
}
if (key.privateKey)
ring.privateKey = key.privateKey;
ring.path = bcoin.path.fromAccount(this, ring, change, index);
return ring;
};
/**
* Save the account to the database. Necessary
* when address depth and keys change.
* @param {Function} callback
*/
Account.prototype.save = function save() {
return this.db.saveAccount(this);
};
/**
* Save addresses to path map.
* @param {KeyRing[]} rings
* @param {Function} callback
*/
Account.prototype.saveAddress = function saveAddress(rings, callback) {
return this.db.saveAddress(this.wid, rings, callback);
};
/**
* Set change and receiving depth (depth is the index of the _next_ address).
* Allocate all addresses up to depth. Note that this also allocates
* new lookahead addresses.
* @param {Number} depth
* @param {Function} callback - Returns [Error, {@link KeyRing}, {@link KeyRing}].
*/
Account.prototype.setDepth = function setDepth(receiveDepth, changeDepth, callback) {
var self = this;
var rings = [];
var i, receive, change;
if (receiveDepth > this.receiveDepth) {
for (i = this.receiveDepth; i < receiveDepth; i++) {
receive = this.deriveReceive(i);
rings.push(receive);
}
for (i = receiveDepth; i < receiveDepth + this.lookahead; i++)
rings.push(this.deriveReceive(i));
this.receiveAddress = receive;
this.receiveDepth = receiveDepth;
}
if (changeDepth > this.changeDepth) {
for (i = this.changeDepth; i < changeDepth; i++) {
change = this.deriveChange(i);
rings.push(change);
}
for (i = changeDepth; i < changeDepth + this.lookahead; i++)
rings.push(this.deriveChange(i));
this.changeAddress = change;
this.changeDepth = changeDepth;
}
if (rings.length === 0)
return callback();
this.saveAddress(rings, function(err) {
if (err)
return callback(err);
self.save();
callback(null, receive, change);
});
};
/**
* Convert the account to a more inspection-friendly object.
* @returns {Object}
*/
Account.prototype.inspect = function inspect() {
return {
wid: this.wid,
name: this.name,
network: this.network,
initialized: this.initialized,
type: Account.typesByVal[this.type].toLowerCase(),
m: this.m,
n: this.n,
address: this.initialized
? this.receiveAddress.getAddress()
: null,
programAddress: this.initialized
? this.receiveAddress.getProgramAddress()
: null,
witness: this.witness,
accountIndex: this.accountIndex,
receiveDepth: this.receiveDepth,
changeDepth: this.changeDepth,
accountKey: this.accountKey.xpubkey,
keys: this.keys.map(function(key) {
return key.xpubkey;
})
};
};
/**
* Convert the account to an object suitable for
* serialization.
* @returns {Object}
*/
Account.prototype.toJSON = function toJSON() {
return {
network: this.network.type,
wid: this.wid,
name: this.name,
initialized: this.initialized,
type: Account.typesByVal[this.type].toLowerCase(),
m: this.m,
n: this.n,
witness: this.witness,
accountIndex: this.accountIndex,
receiveDepth: this.receiveDepth,
changeDepth: this.changeDepth,
receiveAddress: this.receiveAddress
? this.receiveAddress.getAddress('base58')
: null,
programAddress: this.receiveAddress
? this.receiveAddress.getProgramAddress('base58')
: null,
changeAddress: this.changeAddress
? this.changeAddress.getAddress('base58')
: null,
accountKey: this.accountKey.xpubkey,
keys: this.keys.map(function(key) {
return key.xpubkey;
})
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
Account.prototype.fromJSON = function fromJSON(json) {
var i, key;
assert.equal(json.network, this.network.type);
assert(utils.isNumber(json.wid));
assert(utils.isName(json.id), 'Bad wallet ID.');
assert(utils.isName(json.name), 'Bad account name.');
assert(typeof json.initialized === 'boolean');
assert(typeof json.type === 'string');
assert(utils.isNumber(json.m));
assert(utils.isNumber(json.n));
assert(typeof json.witness === 'boolean');
assert(utils.isNumber(json.accountIndex));
assert(utils.isNumber(json.receiveDepth));
assert(utils.isNumber(json.changeDepth));
assert(Array.isArray(json.keys));
this.wid = json.wid;
this.name = json.name;
this.initialized = json.initialized;
this.type = Account.types[json.type.toUpperCase()];
this.m = json.m;
this.n = json.n;
this.witness = json.witness;
this.accountIndex = json.accountIndex;
this.receiveDepth = json.receiveDepth;
this.changeDepth = json.changeDepth;
this.accountKey = bcoin.hd.fromBase58(json.accountKey);
assert(this.type != null);
for (i = 0; i < json.keys.length; i++) {
key = bcoin.hd.fromBase58(json.keys[i]);
this.pushKey(key);
}
return this;
};
/**
* Serialize the account.
* @returns {Buffer}
*/
Account.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
var i, key;
p.writeU32(this.network.magic);
p.writeVarString(this.name, 'utf8');
p.writeU8(this.initialized ? 1 : 0);
p.writeU8(this.type);
p.writeU8(this.m);
p.writeU8(this.n);
p.writeU8(this.witness ? 1 : 0);
p.writeU32(this.accountIndex);
p.writeU32(this.receiveDepth);
p.writeU32(this.changeDepth);
p.writeBytes(this.accountKey.toRaw());
p.writeU8(this.keys.length);
for (i = 0; i < this.keys.length; i++) {
key = this.keys[i];
p.writeBytes(key.toRaw());
}
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
* @returns {Object}
*/
Account.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
var i, count, key;
this.network = bcoin.network.fromMagic(p.readU32());
this.name = p.readVarString('utf8');
this.initialized = p.readU8() === 1;
this.type = p.readU8();
this.m = p.readU8();
this.n = p.readU8();
this.witness = p.readU8() === 1;
this.accountIndex = p.readU32();
this.receiveDepth = p.readU32();
this.changeDepth = p.readU32();
this.accountKey = bcoin.hd.fromRaw(p.readBytes(82));
assert(Account.typesByVal[this.type]);
count = p.readU8();
for (i = 0; i < count; i++) {
key = bcoin.hd.fromRaw(p.readBytes(82));
this.pushKey(key);
}
return this;
};
/**
* Instantiate a account from serialized data.
* @param {WalletDB} data
* @param {Buffer} data
* @returns {Account}
*/
Account.fromRaw = function fromRaw(db, data) {
return new Account(db).fromRaw(data);
};
/**
* Instantiate a Account from a
* jsonified account object.
* @param {WalletDB} db
* @param {Object} json - The jsonified account object.
* @returns {Account}
*/
Account.fromJSON = function fromJSON(db, json) {
return new Account(db).fromJSON(json);
};
/**
* Test an object to see if it is a Account.
* @param {Object} obj
* @returns {Boolean}
*/
Account.isAccount = function isAccount(obj) {
return obj
&& typeof obj.receiveDepth === 'number'
&& obj.deriveAddress === 'function';
};
/*
* Helpers
*/
function cmp(key1, key2) {
return key1.compare(key2);
}
/*
* Expose
*/
module.exports = Account;

285
lib/bcoin/wallet/path.js Normal file
View File

@ -0,0 +1,285 @@
/*!
* path.js - path object for wallets
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('../env');
var utils = require('../utils/utils');
var assert = utils.assert;
var constants = bcoin.constants;
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
/**
* Path
* @constructor
* @private
* @property {WalletID} wid
* @property {String} name - Account name.
* @property {Number} account - Account index.
* @property {Number} change - Change index.
* @property {Number} index - Address index.
* @property {Address|null} address
*/
function Path() {
if (!(this instanceof Path))
return new Path();
this.wid = null;
this.name = null;
this.account = 0;
this.change = -1;
this.index = -1;
this.encrypted = false;
this.imported = null;
this.script = null;
// Currently unused.
this.type = bcoin.script.types.PUBKEYHASH;
this.version = -1;
// Passed in by caller.
this.id = null;
this.hash = null;
}
/**
* Clone the path object.
* @returns {Path}
*/
Path.prototype.clone = function clone() {
var path = new Path();
path.wid = this.wid;
path.name = this.name;
path.account = this.account;
path.change = this.change;
path.index = this.index;
path.encrypted = this.encrypted;
path.imported = this.imported;
path.script = this.script;
path.type = this.type;
path.version = this.version;
path.id = this.id;
path.hash = this.hash;
return path;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
Path.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
this.wid = p.readU32();
this.name = p.readVarString('utf8');
this.account = p.readU32();
switch (p.readU8()) {
case 0:
this.change = p.readU32();
this.index = p.readU32();
if (p.readU8() === 1)
this.script = p.readVarBytes();
break;
case 1:
this.encrypted = p.readU8() === 1;
this.imported = p.readVarBytes();
this.change = -1;
this.index = -1;
break;
default:
assert(false);
break;
}
this.version = p.read8();
this.type = p.readU8();
return this;
};
/**
* Instantiate path from serialized data.
* @param {Buffer} data
* @returns {Path}
*/
Path.fromRaw = function fromRaw(data) {
return new Path().fromRaw(data);
};
/**
* Serialize path.
* @returns {Buffer}
*/
Path.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
p.writeU32(this.wid);
p.writeVarString(this.name, 'utf8');
p.writeU32(this.account);
if (this.index !== -1) {
assert(!this.imported);
p.writeU8(0);
p.writeU32(this.change);
p.writeU32(this.index);
if (this.script) {
p.writeU8(1);
p.writeVarBytes(this.script);
} else {
p.writeU8(0);
}
} else {
assert(this.imported);
p.writeU8(1);
p.writeU8(this.encrypted ? 1 : 0);
p.writeVarBytes(this.imported);
}
p.write8(this.version);
p.writeU8(this.type);
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from account.
* @private
* @param {WalletID} wid
* @param {KeyRing} ring
*/
Path.prototype.fromAccount = function fromAccount(account, ring, change, index) {
this.wid = account.wid;
this.name = account.name;
this.account = account.accountIndex;
if (change != null)
this.change = change;
if (index != null)
this.index = index;
this.version = ring.witness ? 0 : -1;
this.type = ring.getType();
this.id = account.id;
this.hash = ring.getHash('hex');
return this;
};
/**
* Instantiate path from keyring.
* @param {WalletID} wid
* @param {KeyRing} ring
* @returns {Path}
*/
Path.fromAccount = function fromAccount(account, ring, change, index) {
return new Path().fromAccount(account, ring, change, index);
};
/**
* Convert path object to string derivation path.
* @returns {String}
*/
Path.prototype.toPath = function toPath() {
return 'm/' + this.account
+ '\'/' + this.change
+ '/' + this.index;
};
/**
* Convert path object to an address (currently unused).
* @returns {Address}
*/
Path.prototype.toAddress = function toAddress(network) {
return bcoin.address.fromHash(this.hash, this.type, this.version, network);
};
/**
* Convert path to a json-friendly object.
* @returns {Object}
*/
Path.prototype.toJSON = function toJSON() {
return {
name: this.name,
change: this.change === 1,
path: this.toPath()
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
Path.prototype.fromJSON = function fromJSON(json) {
var indexes = bcoin.hd.parsePath(json.path, constants.hd.MAX_INDEX);
assert(indexes.length === 3);
assert(indexes[0] >= constants.hd.HARDENED);
indexes[0] -= constants.hd.HARDENED;
this.wid = json.wid;
this.id = json.id;
this.name = json.name;
this.account = indexes[0];
this.change = indexes[1];
this.index = indexes[2];
return this;
};
/**
* Instantiate path from json object.
* @param {Object} json
* @returns {Path}
*/
Path.fromJSON = function fromJSON(json) {
return new Path().fromJSON(json);
};
/**
* Inspect the path.
* @returns {String}
*/
Path.prototype.inspect = function() {
return '<Path: ' + this.id
+ '(' + this.wid + ')'
+ '/' + this.name
+ ': ' + this.toPath()
+ '>';
};
/**
* Expose
*/
module.exports = Path;

View File

@ -2327,120 +2327,6 @@ TXDB.prototype.abandon = function abandon(hash, callback) {
});
};
/**
* Details
*/
function Details(info) {
if (!(this instanceof Details))
return new Details(info);
this.db = info.db;
this.network = info.db.network;
this.wid = info.wid;
this.id = info.id;
this.hash = info.tx.hash('hex');
this.height = info.tx.height;
this.block = info.tx.block;
this.index = info.tx.index;
this.confirmations = info.tx.getConfirmations(this.db.height);
this.fee = info.tx.getFee();
this.ts = info.tx.ts;
this.ps = info.tx.ps;
this.tx = info.tx;
this.inputs = [];
this.outputs = [];
this.init(info.table);
}
Details.prototype.init = function init(table) {
this._insert(this.tx.inputs, this.inputs, table);
this._insert(this.tx.outputs, this.outputs, table);
};
Details.prototype._insert = function _insert(vector, target, table) {
var i, j, io, address, hash, paths, path, member;
for (i = 0; i < vector.length; i++) {
io = vector[i];
member = new DetailsMember();
if (io instanceof bcoin.input)
member.value = io.coin ? io.coin.value : 0;
else
member.value = io.value;
address = io.getAddress();
if (address) {
member.address = address;
hash = address.getHash('hex');
paths = table[hash];
for (j = 0; j < paths.length; j++) {
path = paths[j];
if (path.wid === this.wid) {
path.id = this.id;
member.path = path;
break;
}
}
}
target.push(member);
}
};
Details.prototype.toJSON = function toJSON() {
var self = this;
return {
wid: this.wid,
id: this.id,
hash: utils.revHex(this.hash),
height: this.height,
block: this.block ? utils.revHex(this.block) : null,
ts: this.ts,
ps: this.ps,
index: this.index,
fee: utils.btc(this.fee),
confirmations: this.confirmations,
inputs: this.inputs.map(function(input) {
return input.toJSON(self.network);
}),
outputs: this.outputs.map(function(output) {
return output.toJSON(self.network);
}),
tx: this.tx.toRaw().toString('hex')
};
};
/**
* DetailsMember
*/
function DetailsMember() {
if (!(this instanceof DetailsMember))
return new DetailsMember();
this.value = 0;
this.address = null;
this.path = null;
}
DetailsMember.prototype.toJSON = function toJSON(network) {
return {
value: utils.btc(this.value),
address: this.address
? this.address.toBase58(network)
: null,
path: this.path
? this.path.toJSON()
: null
};
};
/*
* Balance
*/
@ -2522,5 +2408,4 @@ function sortCoins(coins) {
* Expose
*/
TXDB.Details = Details;
module.exports = TXDB;

View File

@ -15,6 +15,7 @@ var assert = utils.assert;
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
var TXDB = require('./txdb');
var Path = require('./path');
/**
* BIP44 Wallet
@ -834,7 +835,7 @@ Wallet.prototype.importKey = function importKey(account, ring, passphrase, callb
if (!account)
return callback(new Error('Account not found.'));
if (account.type !== Account.types.PUBKEYHASH)
if (account.type !== bcoin.account.types.PUBKEYHASH)
return callback(new Error('Cannot import into non-pkh account.'));
self.unlock(passphrase, null, function(err) {
@ -842,7 +843,7 @@ Wallet.prototype.importKey = function importKey(account, ring, passphrase, callb
return callback(err);
raw = ring.toRaw();
path = bcoin.path.fromAccount(account, ring);
path = Path.fromAccount(account, ring);
if (self.master.encrypted) {
raw = self.master.encipher(raw, path.hash);
@ -2112,830 +2113,6 @@ Wallet.isWallet = function isWallet(obj) {
&& obj.template === 'function';
};
/**
* Represents a BIP44 Account belonging to a {@link Wallet}.
* Note that this object does not enforce locks. Any method
* that does a write is internal API only and will lead
* to race conditions if used elsewhere.
* @exports Account
* @constructor
* @param {Object} options
* @param {WalletDB} options.db
* @param {HDPublicKey} options.accountKey
* @param {Boolean?} options.witness - Whether to use witness programs.
* @param {Number} options.accountIndex - The BIP44 account index.
* @param {Number?} options.receiveDepth - The index of the _next_ receiving
* address.
* @param {Number?} options.changeDepth - The index of the _next_ change
* address.
* @param {String?} options.type - Type of wallet (pubkeyhash, multisig)
* (default=pubkeyhash).
* @param {Number?} options.m - `m` value for multisig.
* @param {Number?} options.n - `n` value for multisig.
* @param {String?} options.wid - Wallet ID
* @param {String?} options.name - Account name
*/
function Account(db, options) {
if (!(this instanceof Account))
return new Account(db, options);
assert(db, 'Database is required.');
this.db = db;
this.network = db.network;
this.lookahead = Account.MAX_LOOKAHEAD;
this.receiveAddress = null;
this.changeAddress = null;
this.wid = 0;
this.id = null;
this.name = null;
this.witness = this.db.options.witness;
this.accountKey = null;
this.accountIndex = 0;
this.receiveDepth = 0;
this.changeDepth = 0;
this.type = Account.types.PUBKEYHASH;
this.m = 1;
this.n = 1;
this.keys = [];
this.initialized = false;
if (options)
this.fromOptions(options);
}
/**
* Account types.
* @enum {Number}
* @default
*/
Account.types = {
PUBKEYHASH: 0,
MULTISIG: 1
};
/**
* Account types by value.
* @const {RevMap}
*/
Account.typesByVal = {
0: 'pubkeyhash',
1: 'multisig'
};
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
Account.prototype.fromOptions = function fromOptions(options) {
var i;
assert(options, 'Options are required.');
assert(utils.isNumber(options.wid));
assert(utils.isName(options.id), 'Bad Wallet ID.');
assert(bcoin.hd.isHD(options.accountKey), 'Account key is required.');
assert(utils.isNumber(options.accountIndex), 'Account index is required.');
this.wid = options.wid;
this.id = options.id;
if (options.name != null) {
assert(utils.isName(options.name), 'Bad account name.');
this.name = options.name;
}
if (options.witness != null) {
assert(typeof options.witness === 'boolean');
this.witness = options.witness;
}
this.accountKey = options.accountKey;
if (options.accountIndex != null) {
assert(utils.isNumber(options.accountIndex));
this.accountIndex = options.accountIndex;
}
if (options.receiveDepth != null) {
assert(utils.isNumber(options.receiveDepth));
this.receiveDepth = options.receiveDepth;
}
if (options.changeDepth != null) {
assert(utils.isNumber(options.changeDepth));
this.changeDepth = options.changeDepth;
}
if (options.type != null) {
if (typeof options.type === 'string') {
this.type = Account.types[options.type.toUpperCase()];
assert(this.type != null);
} else {
assert(typeof options.type === 'number');
this.type = options.type;
assert(Account.typesByVal[this.type]);
}
}
if (options.m != null) {
assert(utils.isNumber(options.m));
this.m = options.m;
}
if (options.n != null) {
assert(utils.isNumber(options.n));
this.n = options.n;
}
if (options.initialized != null) {
assert(typeof options.initialized === 'boolean');
this.initialized = options.initialized;
}
if (this.n > 1)
this.type = Account.types.MULTISIG;
if (this.m < 1 || this.m > this.n)
throw new Error('m ranges between 1 and n');
if (!this.name)
this.name = this.accountIndex + '';
if (options.keys) {
assert(Array.isArray(options.keys));
for (i = 0; i < options.keys.length; i++)
this.pushKey(options.keys[i]);
}
return this;
};
/**
* Instantiate account from options.
* @param {WalletDB} db
* @param {Object} options
* @returns {Account}
*/
Account.fromOptions = function fromOptions(db, options) {
return new Account(db).fromOptions(options);
};
/*
* Default address lookahead.
* @const {Number}
*/
Account.MAX_LOOKAHEAD = 5;
/**
* Attempt to intialize the account (generating
* the first addresses along with the lookahead
* addresses). Called automatically from the
* walletdb.
* @param {Function} callback
*/
Account.prototype.init = function init(callback) {
// Waiting for more keys.
if (this.keys.length !== this.n - 1) {
assert(!this.initialized);
this.save();
return callback();
}
assert(this.receiveDepth === 0);
assert(this.changeDepth === 0);
this.initialized = true;
this.setDepth(1, 1, callback);
};
/**
* Open the account (done after retrieval).
* @param {Function} callback
*/
Account.prototype.open = function open(callback) {
if (!this.initialized)
return callback();
this.receiveAddress = this.deriveReceive(this.receiveDepth - 1);
this.changeAddress = this.deriveChange(this.changeDepth - 1);
callback();
};
/**
* Add a public account key to the account (multisig).
* Does not update the database.
* @param {HDPublicKey} key - Account (bip44)
* key (can be in base58 form).
* @throws Error on non-hdkey/non-accountkey.
*/
Account.prototype.pushKey = function pushKey(key) {
var index;
if (bcoin.hd.isExtended(key))
key = bcoin.hd.fromBase58(key);
if (!bcoin.hd.isPublic(key))
throw new Error('Must add HD keys to wallet.');
if (!key.isAccount44())
throw new Error('Must add HD account keys to BIP44 wallet.');
if (key.equal(this.accountKey))
throw new Error('Cannot add own key.');
index = utils.binaryInsert(this.keys, key, cmp, true);
if (index === -1)
return false;
if (this.keys.length > this.n - 1) {
utils.binaryRemove(this.keys, key, cmp);
throw new Error('Cannot add more keys.');
}
return true;
};
/**
* Remove a public account key to the account (multisig).
* Does not update the database.
* @param {HDPublicKey} key - Account (bip44)
* key (can be in base58 form).
* @throws Error on non-hdkey/non-accountkey.
*/
Account.prototype.spliceKey = function spliceKey(key) {
if (bcoin.hd.isExtended(key))
key = bcoin.hd.fromBase58(key);
if (!bcoin.hd.isHDPublicKey(key))
throw new Error('Must add HD keys to wallet.');
if (!key.isAccount44())
throw new Error('Must add HD account keys to BIP44 wallet.');
if (key.equal(this.accountKey))
throw new Error('Cannot remove own key.');
if (this.keys.length === this.n - 1)
throw new Error('Cannot remove key.');
return utils.binaryRemove(this.keys, key, cmp);
};
/**
* Add a public account key to the account (multisig).
* Saves the key in the wallet database.
* @param {HDPublicKey} key
* @param {Function} callback
*/
Account.prototype.addKey = function addKey(key, callback) {
var self = this;
var result = false;
try {
result = this.pushKey(key);
} catch (e) {
return callback(e);
}
this._checkKeys(function(err, exists) {
if (err)
return callback(err);
if (exists) {
self.spliceKey(key);
return callback(new Error('Cannot add a key from another account.'));
}
// Try to initialize again.
self.init(function(err) {
if (err)
return callback(err);
callback(null, result);
});
});
};
/**
* Ensure accounts are not sharing keys.
* @private
* @param {Function} callback
*/
Account.prototype._checkKeys = function _checkKeys(callback) {
var self = this;
var ring, hash;
if (this.initialized || this.type !== Account.types.MULTISIG)
return callback(null, false);
if (this.keys.length !== this.n - 1)
return callback(null, false);
ring = this.deriveReceive(0);
hash = ring.getScriptHash('hex');
this.db.getAddressPaths(hash, function(err, paths) {
if (err)
return callback(err);
if (!paths)
return callback(null, false);
callback(null, paths[self.wid] != null);
});
};
/**
* Remove a public account key from the account (multisig).
* Remove the key from the wallet database.
* @param {HDPublicKey} key
* @param {Function} callback
*/
Account.prototype.removeKey = function removeKey(key, callback) {
var result = false;
try {
result = this.spliceKey(key);
} catch (e) {
return callback(e);
}
this.save();
callback(null, result);
};
/**
* Create a new receiving address (increments receiveDepth).
* @returns {KeyRing}
*/
Account.prototype.createReceive = function createReceive(callback) {
return this.createAddress(false, callback);
};
/**
* Create a new change address (increments receiveDepth).
* @returns {KeyRing}
*/
Account.prototype.createChange = function createChange(callback) {
return this.createAddress(true, callback);
};
/**
* Create a new address (increments depth).
* @param {Boolean} change
* @param {Function} callback - Returns [Error, {@link KeyRing}].
*/
Account.prototype.createAddress = function createAddress(change, callback) {
var self = this;
var ring, lookahead;
if (typeof change === 'function') {
callback = change;
change = false;
}
if (change) {
ring = this.deriveChange(this.changeDepth);
lookahead = this.deriveChange(this.changeDepth + this.lookahead);
this.changeDepth++;
this.changeAddress = ring;
} else {
ring = this.deriveReceive(this.receiveDepth);
lookahead = this.deriveReceive(this.receiveDepth + this.lookahead);
this.receiveDepth++;
this.receiveAddress = ring;
}
this.saveAddress([ring, lookahead], function(err) {
if (err)
return callback(err);
self.save();
callback(null, ring);
});
};
/**
* Derive a receiving address at `index`. Do not increment depth.
* @param {Number} index
* @returns {KeyRing}
*/
Account.prototype.deriveReceive = function deriveReceive(index, master) {
return this.deriveAddress(false, index, master);
};
/**
* Derive a change address at `index`. Do not increment depth.
* @param {Number} index
* @returns {KeyRing}
*/
Account.prototype.deriveChange = function deriveChange(index, master) {
return this.deriveAddress(true, index, master);
};
/**
* Derive an address from `path` object.
* @param {Path} path
* @param {MasterKey} master
* @returns {KeyRing}
*/
Account.prototype.derivePath = function derivePath(path, master) {
var ring, script, raw;
// Imported key.
if (path.index === -1) {
assert(path.imported);
assert(this.type === Account.types.PUBKEYHASH);
raw = path.imported;
if (path.encrypted)
raw = master.decipher(raw, path.hash);
if (!raw)
return;
ring = bcoin.keyring.fromRaw(raw, this.network);
ring.path = path;
return ring;
}
// Custom redeem script.
if (path.script)
script = new bcoin.script(path.script);
ring = this.deriveAddress(path.change, path.index, master, script);
return ring;
};
/**
* Derive an address at `index`. Do not increment depth.
* @param {Boolean} change - Whether the address on the change branch.
* @param {Number} index
* @returns {KeyRing}
*/
Account.prototype.deriveAddress = function deriveAddress(change, index, master, script) {
var keys = [];
var i, key, shared, ring;
change = +change;
if (master && master.key) {
key = master.key.deriveAccount44(this.accountIndex);
key = key.derive(change).derive(index);
} else {
key = this.accountKey.derive(change).derive(index);
}
ring = bcoin.keyring.fromPublic(key.publicKey, this.network);
ring.witness = this.witness;
if (script) {
// Custom redeem script.
assert(this.type === Account.types.PUBKEYHASH);
ring.script = script;
} else {
switch (this.type) {
case Account.types.PUBKEYHASH:
break;
case Account.types.MULTISIG:
keys.push(key.publicKey);
for (i = 0; i < this.keys.length; i++) {
shared = this.keys[i];
shared = shared.derive(change).derive(index);
keys.push(shared.publicKey);
}
ring.script = bcoin.script.fromMultisig(this.m, this.n, keys);
break;
}
}
if (key.privateKey)
ring.privateKey = key.privateKey;
ring.path = bcoin.path.fromAccount(this, ring, change, index);
return ring;
};
/**
* Save the account to the database. Necessary
* when address depth and keys change.
* @param {Function} callback
*/
Account.prototype.save = function save() {
return this.db.saveAccount(this);
};
/**
* Save addresses to path map.
* @param {KeyRing[]} rings
* @param {Function} callback
*/
Account.prototype.saveAddress = function saveAddress(rings, callback) {
return this.db.saveAddress(this.wid, rings, callback);
};
/**
* Set change and receiving depth (depth is the index of the _next_ address).
* Allocate all addresses up to depth. Note that this also allocates
* new lookahead addresses.
* @param {Number} depth
* @param {Function} callback - Returns [Error, {@link KeyRing}, {@link KeyRing}].
*/
Account.prototype.setDepth = function setDepth(receiveDepth, changeDepth, callback) {
var self = this;
var rings = [];
var i, receive, change;
if (receiveDepth > this.receiveDepth) {
for (i = this.receiveDepth; i < receiveDepth; i++) {
receive = this.deriveReceive(i);
rings.push(receive);
}
for (i = receiveDepth; i < receiveDepth + this.lookahead; i++)
rings.push(this.deriveReceive(i));
this.receiveAddress = receive;
this.receiveDepth = receiveDepth;
}
if (changeDepth > this.changeDepth) {
for (i = this.changeDepth; i < changeDepth; i++) {
change = this.deriveChange(i);
rings.push(change);
}
for (i = changeDepth; i < changeDepth + this.lookahead; i++)
rings.push(this.deriveChange(i));
this.changeAddress = change;
this.changeDepth = changeDepth;
}
if (rings.length === 0)
return callback();
this.saveAddress(rings, function(err) {
if (err)
return callback(err);
self.save();
callback(null, receive, change);
});
};
/**
* Convert the account to a more inspection-friendly object.
* @returns {Object}
*/
Account.prototype.inspect = function inspect() {
return {
wid: this.wid,
name: this.name,
network: this.network,
initialized: this.initialized,
type: Account.typesByVal[this.type].toLowerCase(),
m: this.m,
n: this.n,
address: this.initialized
? this.receiveAddress.getAddress()
: null,
programAddress: this.initialized
? this.receiveAddress.getProgramAddress()
: null,
witness: this.witness,
accountIndex: this.accountIndex,
receiveDepth: this.receiveDepth,
changeDepth: this.changeDepth,
accountKey: this.accountKey.xpubkey,
keys: this.keys.map(function(key) {
return key.xpubkey;
})
};
};
/**
* Convert the account to an object suitable for
* serialization.
* @returns {Object}
*/
Account.prototype.toJSON = function toJSON() {
return {
network: this.network.type,
wid: this.wid,
name: this.name,
initialized: this.initialized,
type: Account.typesByVal[this.type].toLowerCase(),
m: this.m,
n: this.n,
witness: this.witness,
accountIndex: this.accountIndex,
receiveDepth: this.receiveDepth,
changeDepth: this.changeDepth,
receiveAddress: this.receiveAddress
? this.receiveAddress.getAddress('base58')
: null,
programAddress: this.receiveAddress
? this.receiveAddress.getProgramAddress('base58')
: null,
changeAddress: this.changeAddress
? this.changeAddress.getAddress('base58')
: null,
accountKey: this.accountKey.xpubkey,
keys: this.keys.map(function(key) {
return key.xpubkey;
})
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
Account.prototype.fromJSON = function fromJSON(json) {
var i, key;
assert.equal(json.network, this.network.type);
assert(utils.isNumber(json.wid));
assert(utils.isName(json.id), 'Bad wallet ID.');
assert(utils.isName(json.name), 'Bad account name.');
assert(typeof json.initialized === 'boolean');
assert(typeof json.type === 'string');
assert(utils.isNumber(json.m));
assert(utils.isNumber(json.n));
assert(typeof json.witness === 'boolean');
assert(utils.isNumber(json.accountIndex));
assert(utils.isNumber(json.receiveDepth));
assert(utils.isNumber(json.changeDepth));
assert(Array.isArray(json.keys));
this.wid = json.wid;
this.name = json.name;
this.initialized = json.initialized;
this.type = Account.types[json.type.toUpperCase()];
this.m = json.m;
this.n = json.n;
this.witness = json.witness;
this.accountIndex = json.accountIndex;
this.receiveDepth = json.receiveDepth;
this.changeDepth = json.changeDepth;
this.accountKey = bcoin.hd.fromBase58(json.accountKey);
assert(this.type != null);
for (i = 0; i < json.keys.length; i++) {
key = bcoin.hd.fromBase58(json.keys[i]);
this.pushKey(key);
}
return this;
};
/**
* Serialize the account.
* @returns {Buffer}
*/
Account.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
var i, key;
p.writeU32(this.network.magic);
p.writeVarString(this.name, 'utf8');
p.writeU8(this.initialized ? 1 : 0);
p.writeU8(this.type);
p.writeU8(this.m);
p.writeU8(this.n);
p.writeU8(this.witness ? 1 : 0);
p.writeU32(this.accountIndex);
p.writeU32(this.receiveDepth);
p.writeU32(this.changeDepth);
p.writeBytes(this.accountKey.toRaw());
p.writeU8(this.keys.length);
for (i = 0; i < this.keys.length; i++) {
key = this.keys[i];
p.writeBytes(key.toRaw());
}
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
* @returns {Object}
*/
Account.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
var i, count, key;
this.network = bcoin.network.fromMagic(p.readU32());
this.name = p.readVarString('utf8');
this.initialized = p.readU8() === 1;
this.type = p.readU8();
this.m = p.readU8();
this.n = p.readU8();
this.witness = p.readU8() === 1;
this.accountIndex = p.readU32();
this.receiveDepth = p.readU32();
this.changeDepth = p.readU32();
this.accountKey = bcoin.hd.fromRaw(p.readBytes(82));
assert(Account.typesByVal[this.type]);
count = p.readU8();
for (i = 0; i < count; i++) {
key = bcoin.hd.fromRaw(p.readBytes(82));
this.pushKey(key);
}
return this;
};
/**
* Instantiate a account from serialized data.
* @param {WalletDB} data
* @param {Buffer} data
* @returns {Account}
*/
Account.fromRaw = function fromRaw(db, data) {
return new Account(db).fromRaw(data);
};
/**
* Instantiate a Account from a
* jsonified account object.
* @param {WalletDB} db
* @param {Object} json - The jsonified account object.
* @returns {Account}
*/
Account.fromJSON = function fromJSON(db, json) {
return new Account(db).fromJSON(json);
};
/**
* Test an object to see if it is a Account.
* @param {Object} obj
* @returns {Boolean}
*/
Account.isAccount = function isAccount(obj) {
return obj
&& typeof obj.receiveDepth === 'number'
&& obj.deriveAddress === 'function';
};
/**
* Master BIP32 key which can exist
* in a timed out encrypted state.
@ -3400,20 +2577,8 @@ MasterKey.isMasterKey = function isMasterKey(obj) {
&& typeof obj.decrypt === 'function';
};
/*
* Helpers
*/
function cmp(key1, key2) {
return key1.compare(key2);
}
/*
* Expose
*/
exports = Wallet;
exports.Account = Account;
exports.MasterKey = MasterKey;
module.exports = exports;
module.exports = Wallet;

View File

@ -15,6 +15,7 @@ var constants = bcoin.constants;
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
var TXDB = require('./txdb');
var Path = require('./path');
var MAX_POINT = String.fromCharCode(0xdbff, 0xdfff); // U+10FFFF
/*
@ -1612,271 +1613,6 @@ WalletDB.prototype.getAddressPath = function getAddressPath(wid, hash, callback)
});
};
/**
* Path
* @constructor
* @private
* @property {WalletID} wid
* @property {String} name - Account name.
* @property {Number} account - Account index.
* @property {Number} change - Change index.
* @property {Number} index - Address index.
* @property {Address|null} address
*/
function Path() {
if (!(this instanceof Path))
return new Path();
this.wid = null;
this.name = null;
this.account = 0;
this.change = -1;
this.index = -1;
this.encrypted = false;
this.imported = null;
this.script = null;
// Currently unused.
this.type = bcoin.script.types.PUBKEYHASH;
this.version = -1;
// Passed in by caller.
this.id = null;
this.hash = null;
}
/**
* Clone the path object.
* @returns {Path}
*/
Path.prototype.clone = function clone() {
var path = new Path();
path.wid = this.wid;
path.name = this.name;
path.account = this.account;
path.change = this.change;
path.index = this.index;
path.encrypted = this.encrypted;
path.imported = this.imported;
path.script = this.script;
path.type = this.type;
path.version = this.version;
path.id = this.id;
path.hash = this.hash;
return path;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
Path.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
this.wid = p.readU32();
this.name = p.readVarString('utf8');
this.account = p.readU32();
switch (p.readU8()) {
case 0:
this.change = p.readU32();
this.index = p.readU32();
if (p.readU8() === 1)
this.script = p.readVarBytes();
break;
case 1:
this.encrypted = p.readU8() === 1;
this.imported = p.readVarBytes();
this.change = -1;
this.index = -1;
break;
default:
assert(false);
break;
}
this.version = p.read8();
this.type = p.readU8();
return this;
};
/**
* Instantiate path from serialized data.
* @param {Buffer} data
* @returns {Path}
*/
Path.fromRaw = function fromRaw(data) {
return new Path().fromRaw(data);
};
/**
* Serialize path.
* @returns {Buffer}
*/
Path.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
p.writeU32(this.wid);
p.writeVarString(this.name, 'utf8');
p.writeU32(this.account);
if (this.index !== -1) {
assert(!this.imported);
p.writeU8(0);
p.writeU32(this.change);
p.writeU32(this.index);
if (this.script) {
p.writeU8(1);
p.writeVarBytes(this.script);
} else {
p.writeU8(0);
}
} else {
assert(this.imported);
p.writeU8(1);
p.writeU8(this.encrypted ? 1 : 0);
p.writeVarBytes(this.imported);
}
p.write8(this.version);
p.writeU8(this.type);
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from account.
* @private
* @param {WalletID} wid
* @param {KeyRing} ring
*/
Path.prototype.fromAccount = function fromAccount(account, ring, change, index) {
this.wid = account.wid;
this.name = account.name;
this.account = account.accountIndex;
if (change != null)
this.change = change;
if (index != null)
this.index = index;
this.version = ring.witness ? 0 : -1;
this.type = ring.getType();
this.id = account.id;
this.hash = ring.getHash('hex');
return this;
};
/**
* Instantiate path from keyring.
* @param {WalletID} wid
* @param {KeyRing} ring
* @returns {Path}
*/
Path.fromAccount = function fromAccount(account, ring, change, index) {
return new Path().fromAccount(account, ring, change, index);
};
/**
* Convert path object to string derivation path.
* @returns {String}
*/
Path.prototype.toPath = function toPath() {
return 'm/' + this.account
+ '\'/' + this.change
+ '/' + this.index;
};
/**
* Convert path object to an address (currently unused).
* @returns {Address}
*/
Path.prototype.toAddress = function toAddress(network) {
return bcoin.address.fromHash(this.hash, this.type, this.version, network);
};
/**
* Convert path to a json-friendly object.
* @returns {Object}
*/
Path.prototype.toJSON = function toJSON() {
return {
name: this.name,
change: this.change === 1,
path: this.toPath()
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
Path.prototype.fromJSON = function fromJSON(json) {
var indexes = bcoin.hd.parsePath(json.path, constants.hd.MAX_INDEX);
assert(indexes.length === 3);
assert(indexes[0] >= constants.hd.HARDENED);
indexes[0] -= constants.hd.HARDENED;
this.wid = json.wid;
this.id = json.id;
this.name = json.name;
this.account = indexes[0];
this.change = indexes[1];
this.index = indexes[2];
return this;
};
/**
* Instantiate path from json object.
* @param {Object} json
* @returns {Path}
*/
Path.fromJSON = function fromJSON(json) {
return new Path().fromJSON(json);
};
/**
* Inspect the path.
* @returns {String}
*/
Path.prototype.inspect = function() {
return '<Path: ' + this.id
+ '(' + this.wid + ')'
+ '/' + this.name
+ ': ' + this.toPath()
+ '>';
};
/**
* Path Info
*/
@ -2023,7 +1759,7 @@ PathInfo.prototype.toDetails = function toDetails() {
var details = this._details;
if (!details) {
details = new TXDB.Details(this);
details = new Details(this);
this._details = details;
}
@ -2041,6 +1777,120 @@ PathInfo.prototype.toJSON = function toJSON() {
return json;
};
/**
* Details
*/
function Details(info) {
if (!(this instanceof Details))
return new Details(info);
this.db = info.db;
this.network = info.db.network;
this.wid = info.wid;
this.id = info.id;
this.hash = info.tx.hash('hex');
this.height = info.tx.height;
this.block = info.tx.block;
this.index = info.tx.index;
this.confirmations = info.tx.getConfirmations(this.db.height);
this.fee = info.tx.getFee();
this.ts = info.tx.ts;
this.ps = info.tx.ps;
this.tx = info.tx;
this.inputs = [];
this.outputs = [];
this.init(info.table);
}
Details.prototype.init = function init(table) {
this._insert(this.tx.inputs, this.inputs, table);
this._insert(this.tx.outputs, this.outputs, table);
};
Details.prototype._insert = function _insert(vector, target, table) {
var i, j, io, address, hash, paths, path, member;
for (i = 0; i < vector.length; i++) {
io = vector[i];
member = new DetailsMember();
if (io instanceof bcoin.input)
member.value = io.coin ? io.coin.value : 0;
else
member.value = io.value;
address = io.getAddress();
if (address) {
member.address = address;
hash = address.getHash('hex');
paths = table[hash];
for (j = 0; j < paths.length; j++) {
path = paths[j];
if (path.wid === this.wid) {
path.id = this.id;
member.path = path;
break;
}
}
}
target.push(member);
}
};
Details.prototype.toJSON = function toJSON() {
var self = this;
return {
wid: this.wid,
id: this.id,
hash: utils.revHex(this.hash),
height: this.height,
block: this.block ? utils.revHex(this.block) : null,
ts: this.ts,
ps: this.ps,
index: this.index,
fee: utils.btc(this.fee),
confirmations: this.confirmations,
inputs: this.inputs.map(function(input) {
return input.toJSON(self.network);
}),
outputs: this.outputs.map(function(output) {
return output.toJSON(self.network);
}),
tx: this.tx.toRaw().toString('hex')
};
};
/**
* DetailsMember
*/
function DetailsMember() {
if (!(this instanceof DetailsMember))
return new DetailsMember();
this.value = 0;
this.address = null;
this.path = null;
}
DetailsMember.prototype.toJSON = function toJSON(network) {
return {
value: utils.btc(this.value),
address: this.address
? this.address.toBase58(network)
: null,
path: this.path
? this.path.toJSON()
: null
};
};
/*
* Helpers
*/

View File

@ -828,7 +828,7 @@ Framer.item = function _item(item, writer) {
} else if (item instanceof bcoin.keyring) {
p.writeU8(47);
item.toRaw(p);
} else if (item instanceof bcoin.hd) {
} else if (bcoin.hd.isHD(item)) {
p.writeU8(48);
p.writeBytes(item.toRaw());
} else if (item instanceof bcoin.script) {