refactor: hd, script, and wallet.
This commit is contained in:
parent
177272a2c5
commit
bdf5058546
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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
74
lib/bcoin/hd/README.md
Normal 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
253
lib/bcoin/hd/hd.js
Normal 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
542
lib/bcoin/hd/mnemonic.js
Normal 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
794
lib/bcoin/hd/private.js
Normal 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
557
lib/bcoin/hd/public.js
Normal 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;
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
@ -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;
|
||||
|
||||
117
lib/bcoin/primitives/invitem.js
Normal file
117
lib/bcoin/primitives/invitem.js
Normal 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;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
183
lib/bcoin/primitives/outpoint.js
Normal file
183
lib/bcoin/primitives/outpoint.js
Normal 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
176
lib/bcoin/script/opcode.js
Normal 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
109
lib/bcoin/script/program.js
Normal 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
@ -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
537
lib/bcoin/script/stack.js
Normal 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
613
lib/bcoin/script/witness.js
Normal 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
851
lib/bcoin/wallet/account.js
Normal 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
285
lib/bcoin/wallet/path.js
Normal 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;
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user