diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index f8ab2ffe..b0e6a73d 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -152,8 +152,6 @@ function HDPrivateKey(options) { data = options; } - this.master = options.master || this; - data = this._normalize(data, network.prefixes.xprivkey); this.data = data; @@ -347,7 +345,6 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) { return new HDPrivateKey({ version: null, - master: this.master, depth: new bn(this.depth).toNumber() + 1, parentFingerPrint: this.fingerPrint, childIndex: index, diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index 0d1f8036..b8f9e9dd 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -156,7 +156,7 @@ Miner.prototype.addTX = function addTX(tx) { if (tx.height !== -1) return; - if (!tx.verify()) + if (!tx.verify(null, true)) return; if (tx.isCoinbase()) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index cc80a1a9..206ef01d 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -248,7 +248,7 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) { var v, v1, v2, v3, v4; var n, n1, n2, n3; var res; - var pub, sig, type, subscript, hash; + var key, sig, type, subscript, hash; var keys, i, j, key, m; var succ; var lock, threshold; @@ -679,11 +679,11 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) { if (!tx || stack.length < 2) return false; - pub = stack.pop(); + key = stack.pop(); sig = stack.pop(); type = sig[sig.length - 1]; - if (!constants.hashTypeByVal[type & 0x1f]) + if (!script.isKey(key)) return false; if (flags.strictder !== false) { @@ -694,10 +694,13 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) { return false; } + if (!constants.hashTypeByVal[type & 0x1f]) + return false; + subscript = script.subscript(s, lastSep); hash = tx.subscriptHash(index, subscript, type); - res = script.verify(hash, sig.slice(0, -1), pub); + res = script.verify(hash, sig.slice(0, -1), key); if (o === 'checksigverify') { if (!res) return false; @@ -723,7 +726,7 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) { keys = []; for (i = 0; i < n; i++) { key = stack.pop(); - if (!(33 <= key.length && key.length <= 65)) + if (!script.isKey(key)) return false; keys.push(key); @@ -745,9 +748,6 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) { sig = stack.pop(); type = sig[sig.length - 1]; - if (!constants.hashTypeByVal[type & 0x1f]) - return false; - if (flags.strictder !== false) { if (!script.isValidSig(sig)) return false; @@ -756,6 +756,9 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) { return false; } + if (!constants.hashTypeByVal[type & 0x1f]) + return false; + hash = tx.subscriptHash(index, subscript, type); res = false; @@ -1123,11 +1126,12 @@ script.isPubkeyInput = function isPubkeyInput(s, key, tx, i) { if (s.length !== 1 || !Array.isArray(s[0])) return false; - // res = script.isValidSig(s[0]); - res = 9 <= s[0].length && s[0].length <= 73; - if (!res) + if (!script.isSig(s[0])) return false; + // Execute the script against our key's + // checksig script to see if this is our input. + // This will only work if the script verifies. if (key) return script.exec(s, [key, 'checksig'], tx, i); @@ -1135,20 +1139,15 @@ script.isPubkeyInput = function isPubkeyInput(s, key, tx, i) { }; script.isPubkeyhashInput = function isPubkeyhashInput(s, key) { - var res; - s = script.subscript(s); if (s.length !== 2 || !Array.isArray(s[0]) || !Array.isArray(s[1])) return false; - // res = script.isValidSig(s[0]) - // && 33 <= s[1].length && s[1].length <= 65; + if (!script.isSig(s[0])) + return false; - res = 9 <= s[0].length && s[0].length <= 73 - && 33 <= s[1].length && s[1].length <= 65; - - if (!res) + if (!script.isKey(s[1])) return false; if (key) @@ -1160,7 +1159,8 @@ script.isPubkeyhashInput = function isPubkeyhashInput(s, key) { script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) { var i, res, o; - // We need to rule out scripthash because it may look like multisig + // We need to rule out scripthash + // because it may look like multisig if (script.isScripthashInput(s)) return false; @@ -1173,12 +1173,13 @@ script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) { return false; for (i = 1; i < s.length; i++) { - // res = script.isValidSig(s[i]); - res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73; - if (!res) + if (!script.isSig(s[i])) return false; } + // Execute the script against our pubkeys' + // redeem script to see if this is our input. + // This will only work if the script verifies. if (pubs && pubs.length >= 2) { o = script.redeem(pubs, 2, pubs.length); return script.exec(s, o, tx, i); @@ -1199,9 +1200,7 @@ script.isScripthashInput = function isScripthashInput(s, redeem) { return false; for (i = 1; i < s.length - 1; i++) { - // res = script.isValidSig(s[i]); - res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73; - if (!res) + if (!script.isSig(s[i])) return false; } @@ -1320,6 +1319,13 @@ script.isCoinbase = function isCoinbase(s, block, strict) { return coinbase; }; +script.isKey = function isKey(key) { + if (!utils.isBuffer(key)) + return false; + + return key.length >= 33 && key.length <= 65; +}; + script.isSig = function isSig(sig, allowZero) { if (!utils.isBuffer(sig)) return false; @@ -1327,7 +1333,7 @@ script.isSig = function isSig(sig, allowZero) { if (allowZero && sig.length === 0) return true; - return 9 <= sig.length && sig.length <= 73; + return sig.length >= 9 && sig.length <= 73; }; // https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki diff --git a/lib/bcoin/tx-pool.js b/lib/bcoin/tx-pool.js index 80ede089..58a0602b 100644 --- a/lib/bcoin/tx-pool.js +++ b/lib/bcoin/tx-pool.js @@ -96,6 +96,9 @@ TXPool.prototype.add = function add(tx, noWrite) { key = input.out.hash + '/' + input.out.index; unspent = this._unspent[key]; + if (!input.out.tx && this._all[input.out.hash]) + input.out.tx = this._all[input.out.hash]; + if (unspent) { // Add TX to inputs and spend money index = tx._inputIndex(unspent.tx.hash('hex'), unspent.index); @@ -121,7 +124,7 @@ TXPool.prototype.add = function add(tx, noWrite) { // we could in theory use ownInput here (and down below) // instead. // if (this._wallet.ownInput(input.out.tx, input.out.index)) - if (tx.inputs[i].out.tx) { + if (input.out.tx) { if (!this._wallet.ownOutput(input.out.tx, input.out.index)) continue; } diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index f700a445..c1621481 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -157,10 +157,18 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) { }; // Build the scriptSigs for inputs, excluding the signatures -TX.prototype.scriptInput = function scriptInput(input, pub) { +TX.prototype.scriptInput = function scriptInput(index, pub) { + var input, s, n, i, redeem; + + if (typeof index !== 'number') + index = this.inputs.indexOf(index); + + // Get the input + input = this.inputs[index]; + assert(input); + // Get the previous output's subscript - var s = input.out.tx.getSubscript(input.out.index); - var n, i, redeem; + s = input.out.tx.getSubscript(input.out.index); // Already has a script template (at least) if (input.script.length) @@ -218,16 +226,23 @@ TX.prototype.scriptInput = function scriptInput(input, pub) { }; // Sign the now-built scriptSigs -TX.prototype.signInput = function signInput(input, key, type) { - var s, hash, signature; +TX.prototype.signInput = function signInput(index, key, type) { + var input, s, hash, signature; var len, redeem, m, keys, pub, pubn, ki, totalSigs, i; + if (typeof index !== 'number') + index = this.inputs.indexOf(index); + if (!type) type = 'all'; if (typeof type === 'string') type = constants.hashType[type]; + // Get the input + input = this.inputs[index]; + assert(input); + // Get the previous output's subscript s = input.out.tx.getSubscript(input.out.index); @@ -239,7 +254,7 @@ TX.prototype.signInput = function signInput(input, key, type) { } // Get the hash of the current tx, minus the other inputs, plus the sighash. - hash = this.subscriptHash(this.inputs.indexOf(input), redeem, type); + hash = this.subscriptHash(index, redeem, type); // Sign the transaction with our one input signature = bcoin.ecdsa.sign(hash, key.priv).toDER(); @@ -324,23 +339,32 @@ TX.prototype.signInput = function signInput(input, key, type) { }; // Build the scriptSig and sign it -TX.prototype.scriptSig = function scriptSig(input, key, pub, type) { +TX.prototype.scriptSig = function scriptSig(index, key, pub, type) { + var input; + if (!utils.isBuffer(pub)) { type = pub; pub = key.getPublic(true, 'array'); } + if (typeof index !== 'number') + index = this.inputs.indexOf(index); + + // Get the input + input = this.inputs[index]; + assert(input); + // Build script for input - this.scriptInput(input, pub); + this.scriptInput(index, pub); // Sign input - this.signInput(input, key, type); + this.signInput(index, key, type); return input.script; }; TX.prototype.output = function output(obj, value) { - var options; + var options, output; if (obj instanceof bcoin.wallet) obj = obj.getAddress(); @@ -354,7 +378,7 @@ TX.prototype.output = function output(obj, value) { options = obj; } - var output = bcoin.output({ + output = bcoin.output({ tx: this, value: options.value, script: options.script @@ -362,7 +386,7 @@ TX.prototype.output = function output(obj, value) { this.outputs.push(output); - this.scriptOutput(output, options); + this.scriptOutput(this.outputs.length - 1, options); return this; }; @@ -370,11 +394,19 @@ TX.prototype.output = function output(obj, value) { // compat TX.prototype.out = TX.prototype.output; -TX.prototype.scriptOutput = function scriptOutput(output, options) { - var script = output.script; - var keys, m, n, hash, locktime, flags; +TX.prototype.scriptOutput = function scriptOutput(index, options) { + var output, script, keys, m, n, hash, locktime, flags; - options = options || output; + if (typeof index !== 'number') + index = this.outputs.indexOf(index); + + output = this.outputs[index]; + assert(output); + + if (!options) + options = output; + + script = output.script; if (options instanceof bcoin.output) { options = Object.keys(options).reduce(function(out, key) { @@ -499,6 +531,9 @@ TX.prototype.subscriptHash = function subscriptHash(index, s, type) { var copy = this.clone(); var verifyStr, hash; + if (typeof index !== 'number') + index = this.inputs.indexOf(index); + if (typeof type === 'string') type = constants.hashType[type]; @@ -508,7 +543,7 @@ TX.prototype.subscriptHash = function subscriptHash(index, s, type) { return constants.oneHash.slice(); copy.inputs.forEach(function(input, i) { - input.script = index === i ? s : []; + input.script = i === index ? s : []; }); if ((type & 0x1f) === constants.hashType.all) { @@ -564,12 +599,18 @@ TX.prototype.verify = function verify(index, force, flags) { return this.inputs.every(function(input, i) { var stack, prev, push, res, redeem; - if (index !== undefined && index !== i) + if (index != null && index !== i) return true; if (!input.out.tx) return false; + assert.equal(input.out.tx.hash('hex'), input.out.hash); + + // Transaction cannot reference itself + if (input.out.tx.hash('hex') === this.hash('hex')) + return false; + assert(input.out.tx.outputs.length > input.out.index); stack = []; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 832cb093..08de94bf 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -125,11 +125,6 @@ Wallet.prototype._init = function init() { }); }; -// If we want to use p2sh addresses for the prefix: -// Wallet.prototype.__defineGetter__('prefix', function() { -// return 'bt/' + this.getFullAddress() + '/'; -// }); - Wallet.prototype.multisig = function multisig(options) { var pub = this.getOwnPublicKey(); @@ -364,7 +359,7 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) { var outputs = tx.outputs.filter(function(output, i) { var s = output.script; - if (index !== undefined && index !== i) + if (index != null && index !== i) return false; if (bcoin.script.isPubkey(s, key)) @@ -398,7 +393,10 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { var inputs = tx.inputs.filter(function(input, i) { var s; - if (index !== undefined && index !== i) + if (!input.out.tx && this.tx._all[input.out.hash]) + input.out.tx = this.tx._all[input.out.hash]; + + if (index != null && index !== i) return false; // if (bcoin.script.isPubkeyInput(input.script, key, tx, i)) @@ -416,7 +414,7 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { if (!input.out.tx) return false; - s = input.out.tx.outputs[input.out.index].script; + s = input.out.tx.getSubscript(input.out.index); if (bcoin.script.isPubkey(s, key)) return true; @@ -456,12 +454,15 @@ Wallet.prototype.scriptInputs = function scriptInputs(tx, inputs) { inputs = inputs || tx.inputs; - inputs = inputs.filter(function(input) { + inputs = inputs.filter(function(input, i) { + if (!input.out.tx && this.tx._all[input.out.hash]) + input.out.tx = this.tx._all[input.out.hash]; + // Filter inputs that this wallet own if (!input.out.tx || !this.ownOutput(input.out.tx)) return false; - tx.scriptInput(input, pub); + tx.scriptInput(i, pub); return true; }, this); @@ -477,11 +478,14 @@ Wallet.prototype.signInputs = function signInputs(tx, type, inputs) { inputs = inputs || tx.inputs; - inputs = inputs.filter(function(input) { + inputs = inputs.filter(function(input, i) { + if (!input.out.tx && this.tx._all[input.out.hash]) + input.out.tx = this.tx._all[input.out.hash]; + if (!input.out.tx || !this.ownOutput(input.out.tx)) return false; - tx.signInput(input, key, type); + tx.signInput(i, key, type); return true; }, this); @@ -499,12 +503,15 @@ Wallet.prototype.sign = function sign(tx, type, inputs) { inputs = inputs || tx.inputs; // Add signature script to each input - inputs = inputs.filter(function(input) { + inputs = inputs.filter(function(input, i) { + if (!input.out.tx && this.tx._all[input.out.hash]) + input.out.tx = this.tx._all[input.out.hash]; + // Filter inputs that this wallet own if (!input.out.tx || !this.ownOutput(input.out.tx)) return false; - tx.scriptSig(input, key, pub, type); + tx.scriptSig(i, key, pub, type); return true; }, this); @@ -591,7 +598,7 @@ Wallet.prototype.toAddress = function toAddress() { Wallet.prototype.toJSON = function toJSON(encrypt) { return { - v: 1, + v: 2, type: 'wallet', network: network.type, encrypted: encrypt ? true : false, @@ -603,21 +610,9 @@ Wallet.prototype.toJSON = function toJSON(encrypt) { ? encrypt(this.getPrivateKey('base58')) : this.getPrivateKey('base58'), tx: this.tx.toJSON(), - ntx: this.tx.all().length, - hd: this.hd ? { - seed: this.hd.seed ? { - mnemonic: encrypt - ? encrypt(this.hd.seed.mnemonic) - : this.hd.seed.mnemonic, - passphrase: encrypt - ? encrypt(this.hd.seed.passphrase) - : this.hd.seed.passphrase - } : undefined, - depth: new bn(this.hd.data.depth).toNumber(), - parentFingerPrint: utils.toHex(this.hd.data.parentFingerPrint), - childIndex: new bn(this.hd.data.childIndex).toNumber(), - chainCode: utils.toHex(this.hd.data.chainCode) - } : undefined, + xprivkey: this.hd + ? (encrypt ? encrypt(this.hd.xprivkey) : this.hd.xprivkey) + : null, multisig: this.n > 1 ? { type: this.type, keys: this.keys.map(utils.toBase58), @@ -628,9 +623,13 @@ Wallet.prototype.toJSON = function toJSON(encrypt) { }; Wallet.fromJSON = function fromJSON(json, decrypt) { - var priv, pub, compressed, key, w; + var compressed, key, w; + var priv = json.priv; + var pub = json.pub; + var xprivkey = json.xprivkey; + var multisig = json.multisig; - assert.equal(json.v, 1); + assert.equal(json.v, 2); assert.equal(json.type, 'wallet'); if (json.network) @@ -639,11 +638,11 @@ Wallet.fromJSON = function fromJSON(json, decrypt) { if (json.encrypted && !decrypt) throw new Error('Cannot decrypt wallet'); - if (json.encrypted) - json.priv = decrypt(json.priv); + if (priv) { + if (json.encrypted) + priv = decrypt(priv); - if (json.priv) { - key = bcoin.utils.fromBase58(json.priv); + key = bcoin.utils.fromBase58(priv); assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4)))); assert.equal(key[0], network.prefixes.privkey); @@ -657,20 +656,23 @@ Wallet.fromJSON = function fromJSON(json, decrypt) { compressed = false; } } else { - pub = bcoin.utils.fromBase58(json.pub); + pub = bcoin.utils.fromBase58(pub); compressed = pub[0] !== 0x04; } - if (json.multisig) - json.multisig.keys = json.multisig.keys.map(utils.fromBase58); + if (multisig) { + multisig = { + type: multisig.type, + keys: multisig.keys.map(utils.fromBase58), + m: multisig.m, + n: multisig.n + }; + } - if (json.hd) { - json.hd.privateKey = priv; - if (json.encrypted && json.hd.seed) { - json.hd.seed.mnemonic = decrypt(json.hd.seed.mnemonic); - json.hd.seed.passphrase = decrypt(json.hd.seed.passphrase); - } - priv = bcoin.hd.priv(json.hd); + if (xprivkey) { + if (json.encrypted) + xprivkey = decrypt(xprivkey); + priv = bcoin.hd.priv(xprivkey); } w = new Wallet({ @@ -678,7 +680,7 @@ Wallet.fromJSON = function fromJSON(json, decrypt) { priv: priv, pub: pub, compressed: compressed, - multisig: json.multisig + multisig: multisig }); w.tx.fromJSON(json.tx);