From c836786b99b146b285a5593f2a30df3bf7da3095 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 19 Aug 2016 00:39:56 -0700 Subject: [PATCH] mtx/wallet: refactor keyring. --- lib/bcoin/ec.js | 7 +- lib/bcoin/http/rpc.js | 73 ++--- lib/bcoin/keyring.js | 717 +++++++++++++++++++++++------------------- lib/bcoin/mtx.js | 159 ++++------ lib/bcoin/wallet.js | 134 ++++---- lib/bcoin/walletdb.js | 2 +- lib/bcoin/workers.js | 9 +- test/wallet-test.js | 2 +- 8 files changed, 572 insertions(+), 531 deletions(-) diff --git a/lib/bcoin/ec.js b/lib/bcoin/ec.js index d359c7c4..35c310ef 100644 --- a/lib/bcoin/ec.js +++ b/lib/bcoin/ec.js @@ -278,9 +278,6 @@ ec.rand = function rand(min, max) { ec.verify = function verify(msg, sig, key, historical, high) { var result; - if (key.getPublicKey) - key = key.getPublicKey(); - assert(Buffer.isBuffer(msg)); assert(Buffer.isBuffer(sig)); assert(Buffer.isBuffer(key)); @@ -375,8 +372,8 @@ ec.privateKeyVerify = function privateKeyVerify(key) { ec.sign = function sign(msg, key) { var sig; - if (key.getPrivateKey) - key = key.getPrivateKey(); + assert(Buffer.isBuffer(msg)); + assert(Buffer.isBuffer(key)); if (secp256k1) { // Sign message diff --git a/lib/bcoin/http/rpc.js b/lib/bcoin/http/rpc.js index 6798b5d6..86b3cbc9 100644 --- a/lib/bcoin/http/rpc.js +++ b/lib/bcoin/http/rpc.js @@ -2028,7 +2028,7 @@ RPC.prototype.signrawtransaction = function signrawtransaction(args, callback) { RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, args, callback) { var keys = []; var keyMap = {}; - var k, i, secret, key, addr; + var k, i, secret, key; var coins, prevout, prev; var hash, index, script, value; var redeem, op, j; @@ -2038,12 +2038,12 @@ RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, arg k = args[2]; for (i = 0; i < k.length; i++) { secret = k[i]; + if (!utils.isBase58(secret)) return callback(new RPCError('Invalid parameter')); - key = bcoin.keypair.fromSecret(secret); - addr = new bcoin.keyring({ publicKey: key.getPublicKey() }); - key = { addr: addr, key: key.getPrivateKey() }; - keyMap[addr.getPublicKey('hex')] = key; + + key = bcoin.keyring.fromSecret(secret); + keyMap[key.getPublicKey('hex')] = key; keys.push(key); } } @@ -2051,14 +2051,18 @@ RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, arg coins = []; if (args.length > 1 && Array.isArray(args[1])) { prevout = args[1]; + for (i = 0; i < prevout.length; i++) { prev = prevout[i]; + if (!prev) return callback(new RPCError('Invalid parameter')); + hash = toHash(prev.txid); index = prev.vout; script = prev.scriptPubKey; value = toSatoshi(prev.amount); + if (!hash || !utils.isNumber(index) || index < 0 @@ -2081,22 +2085,22 @@ RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, arg if (script.isScripthash() || script.isWitnessScripthash()) { redeem = bcoin.script.fromRaw(prev.redeemScript, 'hex'); - if (!redeem.isMultisig()) - continue; - for (j = 1; j < redeem.length - 2; j++) { + for (j = 0; j < redeem.length; j++) { op = redeem.get(j); + + if (!Buffer.isBuffer(op)) + continue; + key = keyMap[op.toString('hex')]; if (key) { - key.addr.type = bcoin.keyring.types.MULTISIG; - key.addr.m = redeem.getSmall(0); - key.addr.n = redeem.getSmall(redeem.length - 1); - key.addr.keys = redeem.slice(1, -2); - key.addr.witness = script.isWitnessScripthash(); + key.script = redeem; + key.witness = script.isWitnessScripthash(); break; } } } } + tx.fillCoins(coins); } @@ -2117,17 +2121,14 @@ RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, arg for (i = 0; i < keys.length; i++) { key = keys[i]; - key.addr.sign(merged, key.key, null, type); + merged.sign(key, type); } this.wallet.sign(merged, { type: type }, function(err) { if (err) return callback(err); - for (i = 1; i < txs.length; i++) { - tx = txs[i]; - mergeSigs(merged, tx); - } + // TODO: Merge with other txs here. callback(null, { hex: merged.toRaw().toString('hex'), @@ -2136,28 +2137,6 @@ RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, arg }); }; -function mergeSigs(a, b) { - var map = {}; - var i, input, prev, key, ia, ib; - - for (i = 0; i < b.inputs.length; i++) { - input = b.inputs[i]; - prev = input.prevout; - key = prev.hash + prev.index; - map[key] = input; - } - - for (i = 0; i < b.inputs.length; i++) { - ia = a.inputs[i]; - if (!ia || ia.length !== 0) - break; - key = prev.hash + prev.index; - ib = map[key]; - if (ib) - ia.script = ib.script; - } -} - RPC.prototype.fundrawtransaction = function fundrawtransaction(args, callback) { var tx, options, changeAddress, feeRate; @@ -2223,6 +2202,7 @@ RPC.prototype._createRedeem = function _createRedeem(args, callback) { } hash = bcoin.address.getHash(key, 'hex'); + if (!hash) return next(new RPCError('Invalid key.')); @@ -2233,7 +2213,7 @@ RPC.prototype._createRedeem = function _createRedeem(args, callback) { if (!ring) return next(new RPCError('Invalid key.')); - keys[i] = ring.getPublicKey(); + keys[i] = ring.publicKey; next(); }); @@ -2597,9 +2577,7 @@ RPC.prototype.dumpprivkey = function dumpprivkey(args, callback) { if (!key) return callback(new RPCError('Wallet is locked.')); - key = ring.derive(key); - - callback(null, key.toSecret()); + callback(null, ring.toSecret()); }); }; @@ -2642,14 +2620,13 @@ RPC.prototype.dumpwallet = function dumpwallet(args, callback) { if (!key) return callback(new RPCError('Wallet is locked.')); - key = ring.derive(key); address = ring.getAddress('base58'); fmt = '%s %s label= addr=%s'; if (ring.change) fmt = '%s %s change=1 addr=%s'; - str = utils.fmt(fmt, key.toSecret(), time, address); + str = utils.fmt(fmt, ring.toSecret(), time, address); out.push(str); @@ -3826,12 +3803,10 @@ RPC.prototype.signmessage = function signmessage(args, callback) { if (!key) return callback(new RPCError('Wallet is locked.')); - key = ring.derive(key); - msg = new Buffer(RPC.magic + msg, 'utf8'); msg = utils.hash256(msg); - sig = bcoin.ec.sign(msg, key); + sig = ring.sign(msg); callback(null, sig.toString('base64')); }); diff --git a/lib/bcoin/keyring.js b/lib/bcoin/keyring.js index 0d34ad32..ec6fb17f 100644 --- a/lib/bcoin/keyring.js +++ b/lib/bcoin/keyring.js @@ -16,168 +16,82 @@ var BufferWriter = require('./writer'); var scriptTypes = constants.scriptTypes; /** - * Represents a key ring which amounts to an address. Used for {@link Wallet}. + * Represents a key ring which amounts to an address. * @exports KeyRing * @constructor * @param {Object} options - * @param {HDPrivateKey|HDPublicKey|KeyPair|Buffer} options.key - * @param {String?} options.name - * @param {Number?} options.account - * @param {Number?} options.change - * @param {Number?} options.index - * @param {String?} options.type - `"pubkeyhash"` or `"multisig"`. + * @param {HDPrivateKey|HDPublicKey|Buffer} options.key * @param {Buffer[]} options.keys - Shared multisig keys. * @param {Number?} options.m - Multisig `m` value. * @param {Number?} options.n - Multisig `n` value. * @param {Boolean?} options.witness - Whether witness programs are enabled. */ -function KeyRing(options) { +function KeyRing(options, network) { if (!(this instanceof KeyRing)) - return new KeyRing(options); + return new KeyRing(options, network); this.network = bcoin.network.get(); - this.type = KeyRing.types.PUBKEYHASH; - this.m = 1; - this.n = 1; this.witness = false; + this.publicKey = null; + this.privateKey = null; + this.script = null; + this.wid = 0; this.id = null; this.name = null; this.account = 0; this.change = 0; this.index = 0; - this.key = null; - this.keys = []; this._keyHash = null; this._keyAddress = null; this._program = null; this._programHash = null; this._programAddress = null; - this._script = null; this._scriptHash160 = null; this._scriptHash256 = null; this._scriptAddress = null; - this._addressMap = null; if (options) - this.fromOptions(options); + this.fromOptions(options, network); } -/** - * KeyRing types. - * @enum {Number} - * @default - */ - -KeyRing.types = { - PUBKEYHASH: 0, - MULTISIG: 1 -}; - -/** - * KeyRing types by value. - * @const {RevMap} - */ - -KeyRing.typesByVal = { - 0: 'pubkeyhash', - 1: 'multisig' -}; - /** * Inject properties from options object. * @private * @param {Object} options */ -KeyRing.prototype.fromOptions = function fromOptions(options) { - var i; +KeyRing.prototype.fromOptions = function fromOptions(options, network) { + var key = toKey(options); - assert(options.key); + if (Buffer.isBuffer(key)) + return this.fromKey(key, network); + + key = toKey(options.key); + + if (options.privateKey) + key = toKey(options.privateKey); + + if (options.publicKey) + key = toKey(options.publicKey); if (options.network) this.network = bcoin.network.get(options.network); - if (options.type != null) { - if (typeof options.type === 'string') { - this.type = KeyRing.types[options.type.toUpperCase()]; - assert(this.type != null); - } else { - assert(typeof options.type === 'number'); - this.type = options.type; - assert(KeyRing.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.witness != null) { assert(typeof options.witness === 'boolean'); this.witness = options.witness; } - if (options.wid) { - assert(utils.isNumber(options.wid)); - this.wid = options.wid; - } + if (options.keys) + return this.fromKeys(key, options.m, options.n, options.keys, this.network); - if (options.id) { - assert(utils.isName(options.id)); - this.id = options.id; - } + if (options.script) + return this.fromScript(key, options.script, this.network); - if (options.name) { - assert(utils.isName(options.name)); - this.name = options.name; - } - - if (options.account != null) { - assert(utils.isNumber(options.account)); - this.account = options.account; - } - - if (options.change != null) { - assert(utils.isNumber(options.change)); - this.change = options.change; - } - - if (options.index != null) { - assert(utils.isNumber(options.index)); - this.index = options.index; - } - - this.key = options.key; - - if (this.key.getPublicKey) - this.key = this.key.getPublicKey(); - - assert(Buffer.isBuffer(this.key)); - - if (this.n > 1) - this.type = KeyRing.types.MULTISIG; - - if (this.m < 1 || this.m > this.n) - throw new Error('m ranges between 1 and n'); - - this.addKey(this.key); - - if (options.keys) { - assert(Array.isArray(options.keys)); - for (i = 0; i < options.keys.length; i++) - this.addKey(options.keys[i]); - } - - return this; + this.fromKey(key, this.network); }; /** @@ -190,6 +104,234 @@ KeyRing.fromOptions = function fromOptions(options) { return new KeyRing().fromOptions(options); }; +/** + * Inject data from private key. + * @private + * @param {Buffer} privateKey + * @param {Boolean?} compressed + * @param {(NetworkType|Network}) network + */ + +KeyRing.prototype.fromPrivate = function fromPrivate(privateKey, network) { + assert(Buffer.isBuffer(privateKey), 'Private key must be a buffer.'); + assert(bcoin.ec.privateKeyVerify(privateKey), 'Not a valid private key.'); + this.network = bcoin.network.get(network); + this.privateKey = privateKey; + this.publicKey = bcoin.ec.publicKeyCreate(this.privateKey, true); + return this; +}; + +/** + * Instantiate keyring from a private key. + * @param {Buffer} privateKey + * @param {Boolean?} compressed + * @param {(NetworkType|Network}) network + * @returns {KeyRing} + */ + +KeyRing.fromPrivate = function fromPrivate(privateKey, network) { + return new KeyRing().fromPrivate(privateKey, network); +}; + +/** + * Inject data from public key. + * @private + * @param {Buffer} privateKey + * @param {(NetworkType|Network}) network + */ + +KeyRing.prototype.fromPublic = function fromPublic(publicKey, network) { + assert(Buffer.isBuffer(publicKey), 'Public key must be a buffer.'); + assert(bcoin.ec.publicKeyVerify(publicKey), 'Not a valid public key.'); + this.network = bcoin.network.get(network); + this.publicKey = publicKey; + return this; +}; + +/** + * Generate a keyring. + * @param {(Network|NetworkType)?} network + * @returns {KeyRing} + */ + +KeyRing.generate = function(witness, network) { + var key = new KeyRing(); + key.network = bcoin.network.get(network); + key.privateKey = bcoin.ec.generatePrivateKey(); + key.publicKey = bcoin.ec.publicKeyCreate(key.privateKey, true); + key.witness = !!witness; + return key; +}; + +/** + * Instantiate keyring from a public key. + * @param {Buffer} publicKey + * @param {(NetworkType|Network}) network + * @returns {KeyRing} + */ + +KeyRing.fromPublic = function fromPublic(publicKey, network) { + return new KeyRing().fromPublic(publicKey, network); +}; + +/** + * Inject data from public key. + * @private + * @param {Buffer} privateKey + * @param {(NetworkType|Network}) network + */ + +KeyRing.prototype.fromKey = function fromKey(key, network) { + assert(Buffer.isBuffer(key), 'Key must be a buffer.'); + assert(key.length === 32 || key.length === 33, 'Not a key.'); + + if (key.length === 33) + return this.fromPublic(key, network); + + return this.fromPrivate(key, network); +}; + +/** + * Instantiate keyring from a public key. + * @param {Buffer} publicKey + * @param {(NetworkType|Network}) network + * @returns {KeyRing} + */ + +KeyRing.fromKey = function fromKey(key, network) { + return new KeyRing().fromKey(key, network); +}; + +/** + * Inject data from public key. + * @private + * @param {Buffer} key + * @param {Number} m + * @param {Number} n + * @param {Buffer[]} keys + * @param {(NetworkType|Network}) network + */ + +KeyRing.prototype.fromKeys = function fromKeys(key, m, n, keys, network) { + var script = bcoin.script.fromMultisig(m, n, keys); + this.fromScript(key, script, network); + return this; +}; + +/** + * Instantiate keyring from keys. + * @param {Buffer} key + * @param {Number} m + * @param {Number} n + * @param {Buffer[]} keys + * @param {(NetworkType|Network}) network + * @returns {KeyRing} + */ + +KeyRing.fromKeys = function fromKeys(key, m, n, keys, network) { + return new KeyRing().fromKeys(key, m, n, keys, network); +}; + +/** + * Inject data from script. + * @private + * @param {Buffer} key + * @param {Script} script + * @param {(NetworkType|Network}) network + */ + +KeyRing.prototype.fromScript = function fromScript(key, script, network) { + assert(script instanceof bcoin.script, 'Non-script passed into KeyRing.'); + this.fromKey(key, network); + this.script = script; + return this; +}; + +/** + * Instantiate keyring from script. + * @param {Buffer} key + * @param {Script} script + * @param {(NetworkType|Network}) network + * @returns {KeyRing} + */ + +KeyRing.fromScript = function fromScript(key, script, network) { + return new KeyRing().fromScript(key, script, network); +}; + +/** + * Convert key to a CBitcoinSecret. + * @param {(Network|NetworkType)?} network + * @returns {Base58String} + */ + +KeyRing.prototype.toSecret = function toSecret(network) { + var p = new BufferWriter(); + + assert(this.privateKey, 'Cannot serialize without private key.'); + + if (!network) + network = this.network; + + network = bcoin.network.get(network); + + p.writeU8(network.keyPrefix.privkey); + p.writeBytes(this.privateKey); + + p.writeU8(1); + + p.writeChecksum(); + + return utils.toBase58(p.render()); +}; + +/** + * Inject properties from serialized CBitcoinSecret. + * @private + * @param {Base58String} secret + */ + +KeyRing.prototype.fromSecret = function fromSecret(data) { + var p = new BufferReader(utils.fromBase58(data), true); + var i, prefix, version, type, key, compressed; + + version = p.readU8(); + + for (i = 0; i < network.types.length; i++) { + type = network.types[i]; + prefix = network[type].keyPrefix.privkey; + if (version === prefix) + break; + } + + assert(i < network.types.length, 'Network not found.'); + + key = p.readBytes(32); + + if (p.left() > 4) { + assert(p.readU8() === 1, 'Bad compression flag.'); + compressed = true; + } else { + compressed = false; + } + + p.verifyChecksum(); + + assert(compressed === false, 'Cannot handle uncompressed.'); + + return this.fromPrivate(key, type); +}; + +/** + * Instantiate a keyring from a serialized CBitcoinSecret. + * @param {Base58String} secret + * @returns {KeyRing} + */ + +KeyRing.fromSecret = function fromSecret(data) { + return new KeyRing().fromSecret(data); +}; + /** * Inject properties from account object. * @private @@ -201,25 +343,20 @@ KeyRing.fromOptions = function fromOptions(options) { */ KeyRing.prototype.fromAccount = function fromAccount(account, key, keys, change, index) { - var i; - this.network = account.network; - this.key = key.publicKey; + this.publicKey = key.publicKey; + + if (account.n > 1) + this.script = bcoin.script.fromMultisig(account.m, account.n, keys); + + this.witness = account.witness; + this.wid = account.wid; this.id = account.id; this.name = account.name; this.account = account.accountIndex; this.change = change; this.index = index; - this.type = account.type; - this.witness = account.witness; - this.m = account.m; - this.n = account.n; - - this.addKey(this.key); - - for (i = 0; i < keys.length; i++) - this.addKey(keys[i]); return this; }; @@ -238,36 +375,6 @@ KeyRing.fromAccount = function fromAccount(account, key, keys, change, index) { return new KeyRing().fromAccount(account, key, keys, change, index); }; -/** - * Add a key to shared keys. - * @param {Buffer} key - */ - -KeyRing.prototype.addKey = function addKey(key) { - assert(Buffer.isBuffer(key)); - - utils.binaryInsert(this.keys, key, utils.cmp, true); - - if (this.keys.length > this.n) { - utils.binaryRemove(this.keys, key, utils.cmp); - throw new Error('Cannot add more keys.'); - } -}; - -/** - * Remove a key from shared keys. - * @param {Buffer} key - */ - -KeyRing.prototype.removeKey = function removeKey(key) { - assert(Buffer.isBuffer(key)); - - if (this.keys.length === this.n) - throw new Error('Cannot remove key.'); - - utils.binaryRemove(this.keys, key, utils.cmp); -}; - /** * Get public key. * @param {String?} enc - `"hex"` or `null`. @@ -276,12 +383,12 @@ KeyRing.prototype.removeKey = function removeKey(key) { KeyRing.prototype.getPublicKey = function getPublicKey(enc) { if (enc === 'base58') - return utils.toBase58(this.key); + return utils.toBase58(this.publicKey); if (enc === 'hex') - return this.key.toString('hex'); + return this.publicKey.toString('hex'); - return this.key; + return this.publicKey; }; /** @@ -290,23 +397,7 @@ KeyRing.prototype.getPublicKey = function getPublicKey(enc) { */ KeyRing.prototype.getScript = function getScript() { - var redeem; - - if (this.type !== KeyRing.types.MULTISIG) - return; - - if (!this._script) { - assert(this.keys.length === this.n, 'Not all keys have been added.'); - - redeem = bcoin.script.fromMultisig(this.m, this.n, this.keys); - - if (redeem.getSize() > 520) - throw new Error('Redeem script too large (520 byte limit).'); - - this._script = redeem; - } - - return this._script; + return this.script; }; /** @@ -321,14 +412,12 @@ KeyRing.prototype.getProgram = function getProgram() { return; if (!this._program) { - if (this.type === KeyRing.types.PUBKEYHASH) { - hash = utils.hash160(this.getPublicKey()); - program = bcoin.script.fromProgram(0, hash); - } else if (this.type === KeyRing.types.MULTISIG) { - hash = utils.sha256(this.getScript().toRaw()); + if (!this.script) { + hash = utils.hash160(this.publicKey); program = bcoin.script.fromProgram(0, hash); } else { - assert(false, 'Unknown address type.'); + hash = this.script.sha256(); + program = bcoin.script.fromProgram(0, hash); } this._program = program; } @@ -348,7 +437,7 @@ KeyRing.prototype.getProgramHash = function getProgramHash(enc) { return; if (!this._programHash) - this._programHash = utils.hash160(this.getProgram().toRaw()); + this._programHash = this.getProgram().hash160(); return enc === 'hex' ? this._programHash.toString('hex') @@ -398,11 +487,11 @@ KeyRing.prototype.getScriptHash = function getScriptHash(enc) { */ KeyRing.prototype.getScriptHash160 = function getScriptHash256(enc) { - if (this.type !== KeyRing.types.MULTISIG) + if (!this.script) return; if (!this._scriptHash160) - this._scriptHash160 = utils.hash160(this.getScript().toRaw()); + this._scriptHash160 = this.script.hash160(); return enc === 'hex' ? this._scriptHash160.toString('hex') @@ -416,11 +505,11 @@ KeyRing.prototype.getScriptHash160 = function getScriptHash256(enc) { */ KeyRing.prototype.getScriptHash256 = function getScriptHash256(enc) { - if (this.type !== KeyRing.types.MULTISIG) + if (!this.script) return; if (!this._scriptHash256) - this._scriptHash256 = utils.sha256(this.getScript().toRaw()); + this._scriptHash256 = this.script.sha256(); return enc === 'hex' ? this._scriptHash256.toString('hex') @@ -436,7 +525,7 @@ KeyRing.prototype.getScriptHash256 = function getScriptHash256(enc) { KeyRing.prototype.getScriptAddress = function getScriptAddress(enc) { var hash, address; - if (this.type !== KeyRing.types.MULTISIG) + if (!this.script) return; if (!this._scriptAddress) { @@ -464,7 +553,7 @@ KeyRing.prototype.getScriptAddress = function getScriptAddress(enc) { KeyRing.prototype.getKeyHash = function getKeyHash(enc) { if (!this._keyHash) - this._keyHash = utils.hash160(this.getPublicKey()); + this._keyHash = utils.hash160(this.publicKey); return enc === 'hex' ? this._keyHash.toString('hex') @@ -516,7 +605,7 @@ KeyRing.prototype.compile = function compile(hash, type, version) { */ KeyRing.prototype.getHash = function getHash(enc) { - if (this.type === KeyRing.types.MULTISIG) + if (this.script) return this.getScriptHash(enc); return this.getKeyHash(enc); }; @@ -528,25 +617,33 @@ KeyRing.prototype.getHash = function getHash(enc) { */ KeyRing.prototype.getAddress = function getAddress(enc) { - if (this.type === KeyRing.types.MULTISIG) + if (this.script) return this.getScriptAddress(enc); return this.getKeyAddress(enc); }; /** - * Create the address map for testing txs. - * @returns {AddressMap} + * Test an address hash against hash and program hash. + * @param {Buffer} hash + * @returns {Boolean} */ -KeyRing.prototype.getAddressMap = function getAddressMap() { - if (!this._addressMap) { - this._addressMap = {}; - this._addressMap[this.getHash('hex')] = true; - if (this.witness) - this._addressMap[this.getProgramHash('hex')] = true; +KeyRing.prototype.ownHash = function ownHash(hash) { + if (!hash) + return false; + + if (utils.equal(hash, this.keyHash)) + return true; + + if (utils.equal(hash, this.scriptHash)) + return true; + + if (this.witness) { + if (utils.equal(hash, this.programHash)) + return true; } - return this._addressMap; + return false; }; /** @@ -557,8 +654,7 @@ KeyRing.prototype.getAddressMap = function getAddressMap() { */ KeyRing.prototype.ownInput = function ownInput(tx, index) { - var addressMap = this.getAddressMap(); - var input, hash; + var input; if (tx instanceof bcoin.input) { input = tx; @@ -567,12 +663,7 @@ KeyRing.prototype.ownInput = function ownInput(tx, index) { assert(input, 'Input does not exist.'); } - hash = input.getHash('hex'); - - if (!hash) - return false; - - return addressMap[hash] === true; + return this.ownHash(input.getHash()); }; /** @@ -583,8 +674,7 @@ KeyRing.prototype.ownInput = function ownInput(tx, index) { */ KeyRing.prototype.ownOutput = function ownOutput(tx, index) { - var addressMap = this.getAddressMap(); - var output, hash; + var output; if (tx instanceof bcoin.output) { output = tx; @@ -593,61 +683,53 @@ KeyRing.prototype.ownOutput = function ownOutput(tx, index) { assert(output, 'Output does not exist.'); } - hash = output.getHash('hex'); - - if (!hash) - return false; - - return addressMap[hash] === true; + return this.ownHash(output.getHash()); }; +/** + * Test a hash against script hashes to + * find the correct redeem script, if any. + * @param {Buffer} hash + * @returns {Script|null} + */ + KeyRing.prototype.getRedeem = function(hash) { - if (this.program && utils.equal(hash, this.programHash)) - return this.program; + if (this.program) { + if (utils.equal(hash, this.programHash)) + return this.program; + } - if (this.script && utils.equal(hash, this.scriptHash160)) - return this.script; + if (this.script) { + if (utils.equal(hash, this.scriptHash160)) + return this.script; - if (this.script && utils.equal(hash, this.scriptHash256)) - return this.script; + if (utils.equal(hash, this.scriptHash256)) + return this.script; + } + + return null; }; /** - * Build input scripts templates for a transaction (does not - * sign, only creates signature slots). Only builds scripts - * for inputs that are redeemable by this address. - * @param {MTX} tx - * @param {Number?} index - Index of input. If not present, - * it will attempt to sign all redeemable inputs. - * @returns {Number} Total number of scripts built. + * Sign a message. + * @param {Buffer} msg + * @returns {Buffer} Signature in DER format. */ -KeyRing.prototype.scriptInputs = function scriptInputs(tx) { - return tx.template(this.publicKey, this.script); +KeyRing.prototype.sign = function sign(msg) { + assert(this.privateKey, 'Cannot sign without private key.'); + return bcoin.ec.sign(msg, this.privateKey); }; /** - * Build input scripts and sign inputs for a transaction. Only attempts - * to build/sign inputs that are redeemable by this address. - * @param {MTX} tx - * @param {HDPrivateKey|KeyPair|Buffer} key - Private key. - * @returns {Number} Total number of inputs scripts built and signed. + * Verify a message. + * @param {Buffer} msg + * @param {Buffer} sig - Signature in DER format. + * @returns {Boolean} */ -KeyRing.prototype.sign = function sign(tx, key) { - return tx.sign(key, this.script); -}; - -/** - * Derive to address index. - * @param {HDPrivateKey} key - * @returns {HDPrivateKey} - */ - -KeyRing.prototype.derive = function derive(key) { - if (key.isMaster()) - key = key.deriveAccount44(this.account); - return key.derive(this.change).derive(this.index); +KeyRing.prototype.verify = function verify(msg, sig) { + return bcoin.ec.verify(msg, sig, this.publicKey); }; /** @@ -655,28 +737,20 @@ KeyRing.prototype.derive = function derive(key) { * @returns {ScriptType} */ -KeyRing.prototype.getScriptType = function getScriptType() { - switch (this.type) { - case KeyRing.types.PUBKEYHASH: - return this.witness - ? scriptTypes.WITNESSPUBKEYHASH - : scriptTypes.PUBKEYHASH; - case KeyRing.types.MULTISIG: - return this.witness - ? scriptTypes.WITNESSSCRIPTHASH - : scriptTypes.SCRIPTHASH; - default: - assert(false, 'Bad keyring type.'); - break; - } +KeyRing.prototype.getType = function getType() { + if (this.program) + return this.program.getType(); + if (this.script) + return this.script.getType(); + return scriptTypes.PUBKEYHASH; }; -KeyRing.prototype.__defineGetter__('publicKey', function() { - return this.getPublicKey(); -}); +/* + * Getters + */ -KeyRing.prototype.__defineGetter__('script', function() { - return this.getScript(); +KeyRing.prototype.__defineGetter__('type', function() { + return this.getType(); }); KeyRing.prototype.__defineGetter__('scriptHash', function() { @@ -723,6 +797,15 @@ KeyRing.prototype.__defineGetter__('address', function() { return this.getAddress(); }); +/** + * Inspect keyring. + * @returns {Object} + */ + +KeyRing.prototype.inspect = function inspect() { + return this.toJSON(); +}; + /** * Convert an KeyRing to a more json-friendly object. * @returns {Object} @@ -731,20 +814,16 @@ KeyRing.prototype.__defineGetter__('address', function() { KeyRing.prototype.toJSON = function toJSON() { return { network: this.network.type, - type: KeyRing.typesByVal[this.type].toLowerCase(), - m: this.m, - n: this.n, witness: this.witness, + key: this.publicKey.toString('hex'), + script: this.script ? this.script.toRaw().toString('hex') : null, + type: constants.scriptTypesByVal[this.type].toLowerCase(), wid: this.wid, id: this.id, name: this.name, account: this.account, change: this.change, index: this.index, - key: this.key.toString('hex'), - keys: this.keys.map(function(key) { - return key.toString('hex'); - }), address: this.getAddress('base58'), programAddress: this.getProgramAddress('base58') }; @@ -757,39 +836,31 @@ KeyRing.prototype.toJSON = function toJSON() { */ KeyRing.prototype.fromJSON = function fromJSON(json) { - var i; - assert(json); assert(typeof json.network === 'string'); - assert(typeof json.type === 'string'); - assert(utils.isNumber(json.m)); - assert(utils.isNumber(json.n)); assert(typeof json.witness === 'boolean'); + assert(typeof json.publicKey === 'string'); + assert(!json.script || typeof json.script === 'string'); + assert(!json.wid || utils.isNumber(json.wid)); assert(!json.id || utils.isName(json.id)); assert(!json.name || utils.isName(json.name)); assert(utils.isNumber(json.account)); assert(utils.isNumber(json.change)); assert(utils.isNumber(json.index)); - assert(typeof json.key === 'string'); - assert(Array.isArray(json.keys)); this.nework = bcoin.network.get(json.network); - this.type = KeyRing.types[json.type.toUpperCase()]; - this.m = json.m; - this.n = json.n; this.witness = json.witness; + this.publicKey = new Buffer(json.publicKey, 'hex'); + + if (json.script) + this.script = new Buffer(json.script, 'hex'); + this.wid = json.wid; this.name = json.name; this.account = json.account; this.change = json.change; this.index = json.index; - this.key = new Buffer(json.key, 'hex'); - - assert(this.type != null); - - for (i = 0; i < json.keys.length; i++) - this.keys.push(new Buffer(json.keys[i], 'hex')); return this; }; @@ -814,21 +885,18 @@ KeyRing.prototype.toRaw = function toRaw(writer) { var i; p.writeU32(this.network.magic); - p.writeU8(this.type); - p.writeU8(this.m); - p.writeU8(this.n); p.writeU8(this.witness ? 1 : 0); - p.writeU32(this.wid); - p.writeVarString(this.id, 'utf8'); - p.writeVarString(this.name, 'utf8'); - p.writeU32(this.account); - p.writeU32(this.change); - p.writeU32(this.index); - p.writeVarBytes(this.key); - p.writeU8(this.keys.length); + p.writeVarBytes(this.publicKey); - for (i = 0; i < this.keys.length; i++) - p.writeVarBytes(this.keys[i]); + if (this.privateKey) + p.writeVarBytes(this.privateKey); + else + p.writeVarint(0); + + if (this.script) + p.writeVarBytes(this.script.toRaw()); + else + p.writeVarint(0); if (!writer) p = p.render(); @@ -847,24 +915,18 @@ KeyRing.prototype.fromRaw = function fromRaw(data) { var i, count; this.network = bcoin.network.fromMagic(p.readU32()); - this.type = p.readU8(); - this.m = p.readU8(); - this.n = p.readU8(); this.witness = p.readU8() === 1; - this.wid = p.readU32(); - this.id = p.readVarString('utf8'); - this.name = p.readVarString('utf8'); - this.account = p.readU32(); - this.change = p.readU32(); - this.index = p.readU32(); - this.key = p.readVarBytes(); + this.publicKey = p.readVarBytes(); - assert(KeyRing.typesByVal[this.type]); + this.privateKey = p.readVarBytes(); - count = p.readU8(); + if (this.privateKey.length === 0) + this.privateKey = null; - for (i = 0; i < count; i++) - this.keys.push(p.readVarBytes()); + this.script = p.readVarBytes(); + + if (this.script.length === 0) + this.script = null; return this; }; @@ -887,10 +949,27 @@ KeyRing.fromRaw = function fromRaw(data) { KeyRing.isKeyRing = function isKeyRing(obj) { return obj - && Array.isArray(obj.keys) - && typeof obj.getAddressMap === 'function'; + && Buffer.isBuffer(obj.publicKey) + && typeof obj.toSecret === 'function'; }; +/* + * Helpers + */ + +function toKey(opt) { + if (!opt) + return opt; + + if (opt.getPrivateKey) + return opt.getPrivateKey(); + + if (opt.getPublicKey) + return opt.getPublicKey(); + + return opt; +} + /* * Expose */ diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 04e7d128..1ee5ca08 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -211,12 +211,11 @@ MTX.prototype.addOutput = function addOutput(options, value) { * Build input script (or witness) templates (with * OP_0 in place of signatures). * @param {Number} index - Input index. - * @param {Buffer} key - Public key. - * @param {Script} script - Redeem script. + * @param {KeyRing} ring * @returns {Boolean} Whether the script was able to be built. */ -MTX.prototype.scriptInput = function scriptInput(index, key, script) { +MTX.prototype.scriptInput = function scriptInput(index, ring) { var input = this.inputs[index]; var prev, redeem; @@ -233,9 +232,6 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) { return true; } - if (key.getPublicKey) - key = key.getPublicKey(); - // Get the previous output's script prev = input.coin.script; @@ -243,7 +239,7 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) { // with segwit: figuring out where the redeem script and witness // redeem scripts go. if (prev.isScripthash()) { - redeem = this._getRedeem(prev.get(1), key, script); + redeem = ring.getRedeem(prev.get(1)); if (!redeem) return false; @@ -252,12 +248,12 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) { if (redeem.isProgram()) { // P2WSH nested within pay-to-scripthash. if (redeem.isWitnessScripthash()) { - prev = this._getRedeem(redeem.get(1), key, script); + prev = ring.getRedeem(redeem.get(1)); if (!prev) return false; - if (!this.scriptVector(prev, input.witness, key)) + if (!this.scriptVector(prev, input.witness, ring)) return false; input.witness.push(prev.toRaw()); @@ -269,9 +265,9 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) { // P2WPKH nested within pay-to-scripthash. if (redeem.isWitnessPubkeyhash()) { - prev = Script.fromPubkeyhash(utils.hash160(key)); + prev = Script.fromPubkeyhash(ring.keyHash); - if (!this.scriptVector(prev, input.witness, key)) + if (!this.scriptVector(prev, input.witness, ring)) return false; input.script.push(redeem.toRaw()); @@ -285,7 +281,7 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) { } // Regular P2SH. - if (!this.scriptVector(redeem, input.script, key)) + if (!this.scriptVector(redeem, input.script, ring)) return false; input.script.push(redeem.toRaw()); @@ -298,12 +294,12 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) { if (prev.isProgram()) { // Bare P2WSH. if (prev.isWitnessScripthash()) { - redeem = this._getRedeem(prev.get(1), key, script); + redeem = ring.getRedeem(prev.get(1)); if (!redeem) return false; - if (!this.scriptVector(redeem, input.witness, key)) + if (!this.scriptVector(redeem, input.witness, ring)) return false; input.witness.push(redeem.toRaw()); @@ -316,7 +312,7 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) { if (prev.isWitnessPubkeyhash()) { prev = Script.fromPubkeyhash(prev.get(1)); - if (!this.scriptVector(prev, input.witness, key)) + if (!this.scriptVector(prev, input.witness, ring)) return false; input.script.compile(); @@ -329,7 +325,7 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) { } // Wow, a normal output! Praise be to Jengus and Gord. - return this.scriptVector(prev, input.script, key); + return this.scriptVector(prev, input.script, ring); }; /** @@ -337,16 +333,16 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) { * based on a previous script. * @param {Script} prev * @param {Witness|Script} vector - * @param {Buffer} key + * @param {Buffer} ring * @return {Boolean} */ -MTX.prototype.scriptVector = function scriptVector(prev, vector, key) { +MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) { var i, n; // P2PK if (prev.isPubkey()) { - if (!utils.equal(prev.get(1), key)) + if (!utils.equal(prev.get(1), ring.publicKey)) return false; vector.set(0, opcodes.OP_0); @@ -356,18 +352,18 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, key) { // P2PKH if (prev.isPubkeyhash()) { - if (!utils.equal(prev.get(2), utils.hash160(key))) + if (!utils.equal(prev.get(2), ring.keyHash)) return false; vector.set(0, opcodes.OP_0); - vector.set(1, key); + vector.set(1, ring.publicKey); return true; } // Multisig if (prev.isMultisig()) { - if (prev.indexOf(key) === -1) + if (prev.indexOf(ring.publicKey) === -1) return false; // Technically we should create m signature slots, @@ -388,52 +384,6 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, key) { return false; }; -/** - * Calculate a redeem script based on hash. - * Test against passed in redeem script. - * @private - * @param {Buffer} hash - 32 or 20 byte hash. - * @param {Buffer} key - Public key. - * @param {Script} script - Known redeem script. - * @returns {Script|null} - */ - -MTX.prototype._getRedeem = function getRedeem(hash, key, script) { - var program; - - if (!key) - return; - - switch (hash.length) { - case 20: - program = bcoin.script.fromProgram(0, utils.hash160(key)); - - if (utils.equal(program.hash160(), hash)) - return program; - - if (!script) - return; - - program = script.forWitness(); - - if (utils.equal(program.hash160(), hash)) - return program; - - if (utils.equal(script.hash160(), hash)) - return script; - - break; - case 32: - if (!script) - return; - - if (utils.equal(script.sha256(), hash)) - return script; - - break; - } -}; - /** * Sign an input. * @param {Number} index - Index of input being signed. @@ -454,9 +404,6 @@ MTX.prototype.signInput = function signInput(index, key, type) { if (!input.coin) return false; - if (key.getPrivateKey) - key = key.getPrivateKey(); - // Get the previous output's script prev = input.coin.script; vector = input.script; @@ -832,26 +779,23 @@ MTX.prototype.isSigned = function isSigned() { /** * Built input scripts (or witnesses) and sign the inputs. - * @param {Number} index - Index of input being signed. * @param {KeyRing} ring - Address used to sign. The address * must be able to redeem the coin. - * @param {HDPrivateKey|KeyPair|Buffer} key - Private key. - * @param {SighashType} type * @returns {Boolean} Whether the input was able to be signed. - * @throws on unavailable coins. */ -MTX.prototype.template = function template(key, script) { +MTX.prototype.template = function template(ring) { var total = 0; var i; - if (key.getPublicKey) - key = key.getPublicKey(); - for (i = 0; i < this.inputs.length; i++) { - // Build script for input - if (!this.scriptInput(i, key, script)) + if (!ring.ownInput(this, i)) continue; + + // Build script for input + if (!this.scriptInput(i, ring)) + continue; + total++; } @@ -860,27 +804,32 @@ MTX.prototype.template = function template(key, script) { /** * Built input scripts (or witnesses) and sign the inputs. - * @param {Number} index - Index of input being signed. * @param {KeyRing} ring - Address used to sign. The address * must be able to redeem the coin. - * @param {HDPrivateKey|KeyPair|Buffer} key - Private key. * @param {SighashType} type * @returns {Boolean} Whether the input was able to be signed. - * @throws on unavailable coins. */ -MTX.prototype.sign = function sign(key, script, type) { +MTX.prototype.sign = function sign(ring, type) { var total = 0; - var i, pub; + var i, key; - if (key.getPrivateKey) - key = key.getPrivateKey(); + if (Array.isArray(ring)) { + for (i = 0; i < ring.length; i++) + total += this.sign(ring[i], type); + return total; + } - pub = bcoin.ec.publicKeyCreate(key, true); + key = ring.privateKey; + + assert(key, 'No private key available.'); for (i = 0; i < this.inputs.length; i++) { + if (!ring.ownInput(this, i)) + continue; + // Build script for input - if (!this.scriptInput(i, pub, script)) + if (!this.scriptInput(i, ring)) continue; // Sign input @@ -893,6 +842,36 @@ MTX.prototype.sign = function sign(key, script, type) { return total; }; +/** + * Sign the transaction inputs on the worker pool + * (if workers are enabled). + * @param {KeyRing} ring + * @param {SighashType?} type + * @param {Function} callback + * @returns {Boolean} Whether the inputs are valid. + */ + +MTX.prototype.signAsync = function signAsync(ring, type, callback) { + var result; + + if (typeof type === 'function') { + callback = type; + type = null; + } + + if (!bcoin.useWorkers) { + callback = utils.asyncify(callback); + try { + result = this.sign(ring, type); + } catch (e) { + return callback(e); + } + return callback(null, result); + } + + bcoin.workerPool.sign(this, ring, type, callback); +}; + /** * Test whether the transaction at least * has all script templates built. diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 8c96d9ac..99da3435 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -15,8 +15,6 @@ var assert = utils.assert; var BufferReader = require('./reader'); var BufferWriter = require('./writer'); var TXDB = require('./txdb'); -var keyTypes = bcoin.keyring.types; -var keyTypesByVal = bcoin.keyring.typesByVal; /** * BIP44 Wallet @@ -943,12 +941,12 @@ Wallet.prototype.createTX = function createTX(options, callback, force) { if (!tx.checkInputs(self.db.height)) return callback(new Error('CheckInputs failed.')); - self.scriptInputs(tx, function(err, total) { + self.template(tx, function(err, total) { if (err) return callback(err); if (total === 0) - return callback(new Error('scriptInputs failed.')); + return callback(new Error('template failed.')); callback(null, tx); }); @@ -1029,11 +1027,16 @@ Wallet.prototype.resend = function resend(callback) { * @param {Function} callback - Returns [Error, {@link KeyRing}[]]. */ -Wallet.prototype.deriveInputs = function deriveInputs(tx, callback) { +Wallet.prototype.deriveInputs = function deriveInputs(tx, master, callback) { var self = this; var rings = []; var ring; + if (typeof master === 'function') { + callback = master; + master = null; + } + this.getInputPaths(tx, function(err, paths) { if (err) return callback(err); @@ -1046,7 +1049,7 @@ Wallet.prototype.deriveInputs = function deriveInputs(tx, callback) { if (!account) return next(); - ring = account.deriveAddress(path.change, path.index); + ring = account.deriveAddress(path.change, path.index, master); rings.push(ring); next(); @@ -1088,7 +1091,7 @@ Wallet.prototype.getKeyring = function getKeyring(address, callback) { if (!account) return callback(); - ring = account.deriveAddress(path.change, path.index); + ring = account.deriveAddress(path.change, path.index, self.master.key); callback(null, ring); }); @@ -1365,16 +1368,18 @@ Wallet.prototype.getRedeem = function getRedeem(hash, callback) { * (total number of scripts built). */ -Wallet.prototype.scriptInputs = function scriptInputs(tx, callback) { +Wallet.prototype.template = function template(tx, callback) { var total = 0; - var i; + var i, ring; this.deriveInputs(tx, function(err, rings) { if (err) return callback(err); - for (i = 0; i < rings.length; i++) - total += rings[i].scriptInputs(tx); + for (i = 0; i < rings.length; i++) { + ring = rings[i]; + total += tx.template(ring); + } callback(null, total); }); @@ -1404,15 +1409,15 @@ Wallet.prototype.sign = function sign(tx, options, callback) { passphrase = options.passphrase; timeout = options.timeout; - this.deriveInputs(tx, function(err, rings) { + this.unlock(passphrase, timeout, function(err, master) { if (err) return callback(err); - self.unlock(passphrase, timeout, function(err, master) { + self.deriveInputs(tx, master, function(err, rings) { if (err) return callback(err); - self.signAsync(rings, master, tx, callback); + self.signAsync(rings, tx, callback); }); }); }; @@ -1420,51 +1425,25 @@ Wallet.prototype.sign = function sign(tx, options, callback) { /** * Sign a transaction asynchronously. * @param {KeyRing[]} rings - * @param {HDPrivateKey} master * @param {MTX} tx - * @param {Number?} index - * @param {SighashType?} type * @param {Function} callback - Returns [Error, Number] (total number * of inputs scripts built and signed). */ -Wallet.prototype.signAsync = function signAsync(rings, master, tx, callback) { +Wallet.prototype.signAsync = function signAsync(rings, tx, callback) { var result; if (!this.workerPool) { callback = utils.asyncify(callback); try { - result = Wallet.sign(rings, master, tx); + result = tx.sign(rings); } catch (e) { return callback(e); } return callback(null, result); } - this.workerPool.sign(rings, master, tx, callback); -}; - -/** - * Sign a transaction. - * @param {KeyRing[]} rings - * @param {HDPrivateKey} master - * @param {MTX} tx - * @param {Number?} index - * @param {SighashType?} type - */ - -Wallet.sign = function sign(rings, master, tx) { - var total = 0; - var i, ring, key; - - for (i = 0; i < rings.length; i++) { - ring = rings[i]; - key = ring.derive(master); - assert(utils.equal(key.getPublicKey(), ring.key)); - total += ring.sign(tx, key); - } - - return total; + this.workerPool.sign(tx, rings, null, callback); }; /** @@ -2072,7 +2051,7 @@ Wallet.fromJSON = function fromJSON(db, json) { Wallet.isWallet = function isWallet(obj) { return obj && typeof obj.accountDepth === 'number' - && obj.scriptInputs === 'function'; + && obj.template === 'function'; }; /** @@ -2120,7 +2099,7 @@ function Account(db, options) { this.accountIndex = 0; this.receiveDepth = 0; this.changeDepth = 0; - this.type = keyTypes.PUBKEYHASH; + this.type = Account.types.PUBKEYHASH; this.m = 1; this.n = 1; this.keys = []; @@ -2130,6 +2109,27 @@ function Account(db, 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 @@ -2177,12 +2177,12 @@ Account.prototype.fromOptions = function fromOptions(options) { if (options.type != null) { if (typeof options.type === 'string') { - this.type = keyTypes[options.type.toUpperCase()]; + this.type = Account.types[options.type.toUpperCase()]; assert(this.type != null); } else { assert(typeof options.type === 'number'); this.type = options.type; - assert(keyTypesByVal[this.type]); + assert(Account.typesByVal[this.type]); } } @@ -2202,7 +2202,7 @@ Account.prototype.fromOptions = function fromOptions(options) { } if (this.n > 1) - this.type = keyTypes.MULTISIG; + this.type = Account.types.MULTISIG; if (this.m < 1 || this.m > this.n) throw new Error('m ranges between 1 and n'); @@ -2384,7 +2384,7 @@ Account.prototype._checkKeys = function _checkKeys(callback) { var self = this; var ring, hash; - if (this.initialized || this.type !== keyTypes.MULTISIG) + if (this.initialized || this.type !== Account.types.MULTISIG) return callback(null, false); if (this.keys.length !== this.n - 1) @@ -2486,8 +2486,8 @@ Account.prototype.createAddress = function createAddress(change, callback) { * @returns {KeyRing} */ -Account.prototype.deriveReceive = function deriveReceive(index) { - return this.deriveAddress(false, index); +Account.prototype.deriveReceive = function deriveReceive(index, master) { + return this.deriveAddress(false, index, master); }; /** @@ -2496,8 +2496,8 @@ Account.prototype.deriveReceive = function deriveReceive(index) { * @returns {KeyRing} */ -Account.prototype.deriveChange = function deriveChange(index) { - return this.deriveAddress(true, index); +Account.prototype.deriveChange = function deriveChange(index, master) { + return this.deriveAddress(true, index, master); }; /** @@ -2507,13 +2507,20 @@ Account.prototype.deriveChange = function deriveChange(index) { * @returns {KeyRing} */ -Account.prototype.deriveAddress = function deriveAddress(change, index) { +Account.prototype.deriveAddress = function deriveAddress(change, index, master) { var keys = []; - var i, key, shared; + var i, key, shared, ring; change = +change; - key = this.accountKey.derive(change).derive(index); + if (master) { + key = master.deriveAccount44(this.accountIndex); + key = key.derive(change).derive(index); + } else { + key = this.accountKey.derive(change).derive(index); + } + + keys.push(key.publicKey); for (i = 0; i < this.keys.length; i++) { shared = this.keys[i]; @@ -2521,7 +2528,12 @@ Account.prototype.deriveAddress = function deriveAddress(change, index) { keys.push(shared.publicKey); } - return bcoin.keyring.fromAccount(this, key, keys, change, index); + ring = bcoin.keyring.fromAccount(this, key, keys, change, index); + + if (master) + ring.privateKey = key.privateKey; + + return ring; }; /** @@ -2607,7 +2619,7 @@ Account.prototype.inspect = function inspect() { name: this.name, network: this.network, initialized: this.initialized, - type: keyTypesByVal[this.type].toLowerCase(), + type: Account.typesByVal[this.type].toLowerCase(), m: this.m, n: this.n, address: this.initialized @@ -2639,7 +2651,7 @@ Account.prototype.toJSON = function toJSON() { wid: this.wid, name: this.name, initialized: this.initialized, - type: keyTypesByVal[this.type].toLowerCase(), + type: Account.typesByVal[this.type].toLowerCase(), m: this.m, n: this.n, witness: this.witness, @@ -2688,7 +2700,7 @@ Account.prototype.fromJSON = function fromJSON(json) { this.wid = json.wid; this.name = json.name; this.initialized = json.initialized; - this.type = keyTypes[json.type.toUpperCase()]; + this.type = Account.types[json.type.toUpperCase()]; this.m = json.m; this.n = json.n; this.witness = json.witness; @@ -2763,7 +2775,7 @@ Account.prototype.fromRaw = function fromRaw(data) { this.changeDepth = p.readU32(); this.accountKey = bcoin.hd.fromRaw(p.readBytes(82)); - assert(keyTypesByVal[this.type]); + assert(Account.typesByVal[this.type]); count = p.readU8(); diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index a3fa61ea..715ab31d 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -1673,7 +1673,7 @@ Path.prototype.fromKeyRing = function fromKeyRing(ring) { this.index = ring.index; this.version = ring.witness ? 0 : -1; - this.type = ring.getScriptType(); + this.type = ring.getType(); this.id = ring.id; this.hash = ring.getHash('hex'); diff --git a/lib/bcoin/workers.js b/lib/bcoin/workers.js index 24bef1a2..fb9686fa 100644 --- a/lib/bcoin/workers.js +++ b/lib/bcoin/workers.js @@ -248,11 +248,10 @@ Workers.prototype.verify = function verify(tx, flags, callback) { * @param {Function} callback */ -Workers.prototype.sign = function sign(rings, master, tx, callback) { - var args = [rings, master, tx]; +Workers.prototype.sign = function sign(tx, ring, type, callback) { var i, input, sig, sigs, total; - this.execute('sign', args, -1, function(err, result) { + this.execute('sign', [tx, ring, type], -1, function(err, result) { if (err) return callback(err); @@ -775,8 +774,8 @@ jobs.verify = function verify(tx, flags) { * @param {MTX} tx */ -jobs.sign = function sign(rings, master, tx) { - var total = bcoin.wallet.sign(rings, master, tx); +jobs.sign = function sign(tx, ring, type) { + var total = tx.sign(ring, type); var sigs = []; var i, input; diff --git a/test/wallet-test.js b/test/wallet-test.js index 7c15d04f..52565167 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -239,7 +239,7 @@ describe('Wallet', function() { var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed) .addOutput(w, 500); // Script inputs but do not sign - w.scriptInputs(fake, function(err) { + w.template(fake, function(err) { assert.ifError(err); // Fake signature fake.inputs[0].script.set(0, FAKE_SIG);