diff --git a/lib/bcoin/address.js b/lib/bcoin/address.js index a40da666..e00e7fc5 100644 --- a/lib/bcoin/address.js +++ b/lib/bcoin/address.js @@ -87,8 +87,8 @@ function Address(options) { this.addKey(key); }, this); - if (options.redeem) - this.setRedeem(options.redeem); + if (options.redeem || options.script) + this.setRedeem(options.redeem || options.script); this.prefix = 'bt/address/' + this.getKeyAddress() + '/'; } @@ -104,7 +104,7 @@ Address.prototype.setRedeem = function setRedeem(redeem) { this.type = 'scripthash'; this.subtype = null; this.redeem = redeem; - this.emit('scriptaddress', old, this.getScriptAddress()); + this.emit('update script', old, this.getScriptAddress()); }; Address.prototype.addKey = function addKey(key) { @@ -124,10 +124,15 @@ Address.prototype.addKey = function addKey(key) { this.keys = utils.sortKeys(this.keys); + delete this._scriptAddress; + delete this._scriptHash; + delete this._script; + this.getScriptAddress(); + cur = this.getScriptAddress(); if (old !== cur) - this.emit('scriptaddress', old, cur); + this.emit('update script', old, cur); }; Address.prototype.removeKey = function removeKey(key) { @@ -149,10 +154,15 @@ Address.prototype.removeKey = function removeKey(key) { this.keys = utils.sortKeys(this.keys); + delete this._scriptAddress; + delete this._scriptHash; + delete this._script; + this.getScriptAddress(); + cur = this.getScriptAddress(); if (old !== cur) - this.emit('scriptaddress', old, this.getScriptAddress()); + this.emit('update script', old, cur); }; Address.prototype.getPrivateKey = function getPrivateKey(enc) { @@ -171,51 +181,82 @@ Address.prototype.getScript = function getScript() { if (this.type !== 'scripthash') return; + if (this._script) + return this._script; + if (this.redeem) - return this.redeem.slice(); + return this._script = this.redeem.slice(); if (this.subtype === 'pubkey') - return bcoin.script.encode([this.getPublicKey(), 'checksig']); + this._script = bcoin.script.createPubkey(this.getPublicKey()); + else if (this.subtype === 'pubkeyhash' || this.keys.length < this.n) + this._script = bcoin.script.createPubkeyhash(this.getKeyHash()); + else if (this.subtype === 'multisig') + this._script = bcoin.script.createMultisig(this.keys, this.m, this.n); + else + assert(false); - if (this.subtype === 'pubkeyhash' || this.keys.length < this.n) { - return bcoin.script.encode([ - 'dup', - 'hash160', - this.getKeyHash(), - 'equalverify', - 'checksig' - ]); - } + this._script = bcoin.script.encode(this._script); - return bcoin.script.encode( - bcoin.script.createMultisig(this.keys, this.m, this.n) - ); + return this._script; }; Address.prototype.getScriptHash = function getScriptHash() { if (this.type !== 'scripthash') return; - return utils.ripesha(this.getScript()); + if (this._scriptHash) + return this._scriptHash; + + this._scriptHash = utils.ripesha(this.getScript()); + + return this._scriptHash; }; Address.prototype.getScriptAddress = function getScriptAddress() { if (this.type !== 'scripthash') return; - return Address.hash2addr(this.getScriptHash(), this.type); + if (this._scriptAddress) + return this._scriptAddress; + + this._scriptAddress = Address.hash2addr(this.getScriptHash(), this.type); + + return this._scriptAddress; }; Address.prototype.getPublicKey = function getPublicKey(enc) { + if (!this.key.priv) + return; + + if (!enc) { + if (this._pub) + return this._pub; + + this._pub = this.key.getPublic(); + + return this._pub; + } + return this.key.getPublic(enc); }; Address.prototype.getKeyHash = function getKeyHash() { - return Address.key2hash(this.getPublicKey()); + if (this._hash) + return this._hash; + + this._hash = Address.key2hash(this.getPublicKey()); + + return this._hash; }; Address.prototype.getKeyAddress = function getKeyAddress() { - return Address.hash2addr(this.getKeyHash(), 'pubkeyhash'); + if (this._address) + return this._address; + + this._address = Address.hash2addr(this.getKeyHash(), 'pubkeyhash'); + + return this._address; }; Address.prototype.getHash = function getHash() { @@ -248,6 +289,10 @@ Address.hash2addr = function hash2addr(hash, prefix) { return utils.toBase58(addr); }; +Address.key2addr = function key2addr(key, prefix) { + return Address.hash2addr(Address.key2hash(key), prefix); +}; + Address.__defineGetter__('prefixes', function() { if (Address._prefixes) return Address._prefixes; @@ -286,7 +331,7 @@ Address.addr2hash = function addr2hash(addr, prefix) { return addr.slice(1, -4); }; -Address.validate = function validateAddress(addr, prefix) { +Address.validate = function validate(addr, prefix) { if (!addr || typeof addr !== 'string') return false; @@ -298,12 +343,13 @@ Address.validate = function validateAddress(addr, prefix) { Address.validateAddress = Address.validate; Address.prototype.ownOutput = function ownOutput(tx, index) { - var scripthash = this.getScriptHash(); + var scriptHash = this.getScriptHash(); var hash = this.getKeyHash(); var key = this.getPublicKey(); var keys = this.keys; + var outputs; - var outputs = tx.outputs.filter(function(output, i) { + outputs = tx.outputs.filter(function(output, i) { var s = output.script; if (index != null && index !== i) @@ -318,8 +364,8 @@ Address.prototype.ownOutput = function ownOutput(tx, index) { if (bcoin.script.isMultisig(s, keys)) return true; - if (scripthash) { - if (bcoin.script.isScripthash(s, scripthash)) + if (scriptHash) { + if (bcoin.script.isScripthash(s, scriptHash)) return true; } @@ -333,13 +379,14 @@ Address.prototype.ownOutput = function ownOutput(tx, index) { }; Address.prototype.ownInput = function ownInput(tx, index) { - var scripthash = this.getScriptHash(); + var scriptHash = this.getScriptHash(); var hash = this.getKeyHash(); var key = this.getPublicKey(); var redeem = this.getScript(); var keys = this.keys; + var inputs; - var inputs = tx.inputs.filter(function(input, i) { + inputs = tx.inputs.filter(function(input, i) { var s; if (!input.prevout.tx && this.tx._all[input.prevout.hash]) @@ -376,8 +423,8 @@ Address.prototype.ownInput = function ownInput(tx, index) { if (bcoin.script.isMultisig(s, keys)) return true; - if (scripthash) { - if (bcoin.script.isScripthash(s, scripthash)) + if (scriptHash) { + if (bcoin.script.isScripthash(s, scriptHash)) return true; } @@ -390,6 +437,108 @@ Address.prototype.ownInput = function ownInput(tx, index) { return inputs; }; +Address.prototype.scriptInputs = function scriptInputs(tx) { + var self = this; + var pub = this.getPublicKey(); + var redeem = this.getScript(); + + return tx.inputs.reduce(function(total, input, i) { + if (!input.prevout.tx) + return total; + + if (!self.ownOutput(input.prevout.tx, input.prevout.index)) + return total; + + if (tx.scriptInput(i, pub, redeem)) + total++; + + return total; + }, 0); +}; + +Address.prototype.signInputs = function signInputs(tx, type) { + var self = this; + var key = this.key; + var total = 0; + + if (!key.priv) + return 0; + + return tx.inputs.reduce(function(total, input, i) { + if (!input.prevout.tx) + return total; + + if (!self.ownOutput(input.prevout.tx, input.prevout.index)) + return total; + + if (tx.signInput(i, key, type)) + total++; + + return total; + }, 0); +}; + +Address.prototype.sign = function sign(tx, type) { + var self = this; + var pub = this.getPublicKey(); + var redeem = this.getScript(); + var key = this.key; + + if (!key.priv) + return 0; + + // Add signature script to each input + return tx.inputs.reduce(function(total, input, i) { + // Filter inputs that this wallet own + if (!input.prevout.tx) + return total; + + if (!self.ownOutput(input.prevout.tx, input.prevout.index)) + return total; + + if (tx.scriptSig(i, key, pub, redeem, type)) + total++; + + return total; + }, 0); +}; + +Address.prototype.__defineGetter__('script', function() { + return this.getScript(); +}); + +Address.prototype.__defineGetter__('scriptHash', function() { + return this.getScriptHash(); +}); + +Address.prototype.__defineGetter__('scriptAddress', function() { + return this.getScriptAddress(); +}); + +Address.prototype.__defineGetter__('privateKey', function() { + return this.getPrivateKey(); +}); + +Address.prototype.__defineGetter__('publicKey', function() { + return this.getPublicKey(); +}); + +Address.prototype.__defineGetter__('keyHash', function() { + return this.getKeyHash(); +}); + +Address.prototype.__defineGetter__('keyAddress', function() { + return this.getKeyAddress(); +}); + +Address.prototype.__defineGetter__('hash', function() { + return this.getHash(); +}); + +Address.prototype.__defineGetter__('address', function() { + return this.getAddress(); +}); + Address.prototype.toJSON = function toJSON(encrypt) { return { v: 1, @@ -398,7 +547,7 @@ Address.prototype.toJSON = function toJSON(encrypt) { label: this.label, change: this.change, address: this.getKeyAddress(), - scriptaddress: this.getScriptAddress(), + scriptAddress: this.getScriptAddress(), key: this.key.toJSON(encrypt), type: this.type, subtype: this.subtype, diff --git a/lib/bcoin/input.js b/lib/bcoin/input.js index 3549d699..5ecf0c9d 100644 --- a/lib/bcoin/input.js +++ b/lib/bcoin/input.js @@ -21,6 +21,8 @@ function Input(options) { prevout = options.prevout || options.out; + this.tx = options.tx; + this.prevout = { tx: prevout.tx || null, hash: prevout.hash, @@ -66,8 +68,10 @@ Input.prototype.__defineGetter__('data', function() { data = Input.getData(this); - if (this.script.length && this.prevout.tx) - utils.hidden(this, '_data', data); + if (!this.tx || this.tx.ps === 0) { + if (this.script.length && this.prevout.tx) + utils.hidden(this, '_data', data); + } return data; }); @@ -89,11 +93,11 @@ Input.prototype.__defineGetter__('key', function() { }); Input.prototype.__defineGetter__('hash', function() { - return this.data.scripthash || this.hashes[0]; + return this.data.scriptHash || this.hashes[0]; }); Input.prototype.__defineGetter__('address', function() { - return this.data.scriptaddress || this.addresses[0] || this.getID(); + return this.data.scriptAddress || this.addresses[0] || this.getID(); }); Input.prototype.__defineGetter__('signatures', function() { @@ -116,12 +120,12 @@ Input.prototype.__defineGetter__('redeem', function() { return this.data.redeem; }); -Input.prototype.__defineGetter__('scripthash', function() { - return this.data.scripthash; +Input.prototype.__defineGetter__('scriptHash', function() { + return this.data.scriptHash; }); -Input.prototype.__defineGetter__('scriptaddress', function() { - return this.data.scriptaddress; +Input.prototype.__defineGetter__('scriptAddress', function() { + return this.data.scriptAddress; }); Input.prototype.__defineGetter__('m', function() { @@ -132,10 +136,10 @@ Input.prototype.__defineGetter__('n', function() { return this.data.n || this.m; }); -Input.prototype.__defineGetter__('lockTime', function() { +Input.prototype.__defineGetter__('locktime', function() { if (!this.output) return 0; - return this.output.lockTime; + return this.output.locktime; }); Input.prototype.__defineGetter__('flags', function() { @@ -187,7 +191,7 @@ Input.prototype.__defineGetter__('sigs', function() { }); Input.prototype.__defineGetter__('scriptaddr', function() { - return this.scriptaddress; + return this.scriptAddress; }); // Schema and defaults for data object: @@ -200,14 +204,14 @@ Input.prototype.__defineGetter__('scriptaddr', function() { // hashes: Array, // addresses: Array, // redeem: Array, -// scripthash: Array, -// scriptaddress: String, +// scriptHash: Array, +// scriptAddress: String, // m: Number, // n: Number, // height: Number, // flags: Array, // text: String, -// lockTime: Number, +// locktime: Number, // value: bn, // script: Array, // seq: Number, @@ -281,6 +285,49 @@ Input.prototype.getLocktime = function getLocktime() { return bcoin.script.getLocktime(redeem); }; +Input.prototype.createScript = function createScript(pub, redeem) { + return this.tx.scriptInput(this, pub, redeem); +}; + +Input.prototype.signatureHash = function signatureHash(s, type) { + return this.tx.signatureHash(this, s, type); +}; + +Input.prototype.createSignature = function createSignature(key, type) { + return this.tx.createSignature(this, key, type); +}; + +Input.prototype.sign = function sign(key, type) { + return this.tx.signInput(this, key, type); +}; + +Input.prototype.scriptSig = function scriptSig(key, pub, redeem, type) { + return this.tx.scriptSig(this, key, pub, redeem, type); +}; + +Input.prototype.isSigned = function isSigned(required) { + return this.tx.isSigned(this, required); +}; + +Input.prototype.verify = function verify(force, flags) { + return this.tx.verify(this, force, flags); +}; + +Input.prototype.isCoinbase = function isCoinbase() { + return this.tx.isCoinbase(); +}; + +Input.prototype.test = function test(addressTable, collect) { + return this.tx.testInputs(addressTable, this, collect); +}; + +Input.prototype.getSigops = function getSigops(scriptHash, accurate) { + var n = bcoin.script.getSigops(this.script, accurate); + if (scriptHash && !this.tx.isCoinbase()) + n += bcoin.script.getScripthashSigops(this.script); + return n; +}; + Input.prototype.inspect = function inspect() { var output = this.output ? this.output.inspect() @@ -297,10 +344,10 @@ Input.prototype.inspect = function inspect() { keys: this.keys.map(utils.toHex), hashes: this.hashes.map(utils.toHex), addresses: this.addresses, - scriptaddress: this.scriptaddress, + scriptAddress: this.scriptAddress, signatures: this.signatures.map(utils.toHex), text: this.text, - lockTime: this.lockTime, + locktime: this.locktime, value: utils.btc(output.value), script: bcoin.script.format(this.script)[0], redeem: this.redeem ? bcoin.script.format(this.redeem)[0] : null, diff --git a/lib/bcoin/output.js b/lib/bcoin/output.js index bd18eb30..935094f9 100644 --- a/lib/bcoin/output.js +++ b/lib/bcoin/output.js @@ -25,6 +25,7 @@ function Output(options) { if (typeof value === 'number' && (value | 0) === value) value = new bn(value); + this.tx = options.tx; this.value = utils.satoshi(value || new bn(0)); this.script = options.script ? options.script.slice() : []; @@ -47,8 +48,10 @@ Output.prototype.__defineGetter__('data', function() { data = Output.getData(this); - if (this.script.length && this.value.cmpn(0) !== 0) - utils.hidden(this, '_data', data); + if (!this.tx || this.tx.ps === 0) { + if (this.script.length && this.value.cmpn(0) > 0) + utils.hidden(this, '_data', data); + } return data; }); @@ -66,11 +69,11 @@ Output.prototype.__defineGetter__('key', function() { }); Output.prototype.__defineGetter__('hash', function() { - return this.data.scripthash || this.hashes[0]; + return this.data.scriptHash || this.hashes[0]; }); Output.prototype.__defineGetter__('address', function() { - return this.data.scriptaddress || this.addresses[0] || this.getID(); + return this.data.scriptAddress || this.addresses[0] || this.getID(); }); Output.prototype.__defineGetter__('signatures', function() { @@ -89,12 +92,12 @@ Output.prototype.__defineGetter__('addresses', function() { return this.data.addresses || []; }); -Output.prototype.__defineGetter__('scripthash', function() { - return this.data.scripthash; +Output.prototype.__defineGetter__('scriptHash', function() { + return this.data.scriptHash; }); -Output.prototype.__defineGetter__('scriptaddress', function() { - return this.data.scriptaddress; +Output.prototype.__defineGetter__('scriptAddress', function() { + return this.data.scriptAddress; }); Output.prototype.__defineGetter__('m', function() { @@ -105,8 +108,8 @@ Output.prototype.__defineGetter__('n', function() { return this.data.n || this.m; }); -Output.prototype.__defineGetter__('lockTime', function() { - return bcoin.script.getLockTime(this.script); +Output.prototype.__defineGetter__('locktime', function() { + return bcoin.script.getLocktime(this.script); }); Output.prototype.__defineGetter__('flags', function() { @@ -117,6 +120,7 @@ Output.prototype.__defineGetter__('text', function() { return this.data.text; }); +// Legacy Output.prototype.__defineGetter__('addr', function() { return this.address; }); @@ -142,7 +146,7 @@ Output.prototype.__defineGetter__('sigs', function() { }); Output.prototype.__defineGetter__('scriptaddr', function() { - return this.scriptaddress; + return this.scriptAddress; }); // Schema and defaults for data object: @@ -155,14 +159,14 @@ Output.prototype.__defineGetter__('scriptaddr', function() { // hashes: Array, // addresses: Array, // redeem: Array, -// scripthash: Array, -// scriptaddress: String, +// scriptHash: Array, +// scriptAddress: String, // m: Number, // n: Number, // height: Number, // flags: Array, // text: String, -// lockTime: Number, +// locktime: Number, // value: bn, // script: Array, // seq: Number, @@ -192,6 +196,18 @@ Output.prototype.getID = function getID() { return '[' + this.type + ':' + hash.slice(0, 7) + ']'; }; +Output.prototype.createScript = function createScript(options) { + return this.tx.scriptOutput(this, options); +}; + +Output.prototype.test = function test(addressTable, collect) { + return this.tx.testOutputs(addressTable, this, collect); +}; + +Output.prototype.getSigops = function getSigops(accurate) { + return bcoin.script.getSigops(this.script, accurate); +}; + Output.prototype.inspect = function inspect() { return { type: this.type, @@ -199,11 +215,11 @@ Output.prototype.inspect = function inspect() { keys: this.keys.map(utils.toHex), hashes: this.hashes.map(utils.toHex), addresses: this.addresses, - scriptaddress: this.scriptaddress, + scriptAddress: this.scriptAddress, m: this.m, n: this.n, text: this.text, - lockTime: this.lockTime, + locktime: this.locktime, value: utils.btc(this.value), script: bcoin.script.format(this.script)[0] }; diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 9e7c0e3f..f2441786 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -301,7 +301,7 @@ Framer.tx = function tx(tx) { off += utils.writeIntv(p, s.length, off); off += utils.copy(s, p, off, true); } - off += utils.writeU32(p, tx.lockTime, off); + off += utils.writeU32(p, tx.locktime, off); return p; }; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 644ac74b..ed4171ff 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -461,7 +461,7 @@ Parser.prototype.parseTX = function parseTX(p) { version: utils.read32(p, 0), inputs: txIn, outputs: txOut, - lockTime: utils.readU32(p, off), + locktime: utils.readU32(p, off), _off: off + 4, _size: p.length }; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 8c77dd62..e7249652 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -386,7 +386,7 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) { var key, sig, type, subscript, hash; var keys, i, j, m; var succ; - var lockTime, threshold; + var locktime, threshold; var evalScript; stack.alt = stack.alt || []; @@ -934,28 +934,28 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) { if (!tx || stack.length === 0) return false; - lockTime = stack[stack.length - 1]; + locktime = stack[stack.length - 1]; - if (!Array.isArray(lockTime)) + if (!Array.isArray(locktime)) return false; - if (lockTime.length > 6) + if (locktime.length > 6) return false; - lockTime = script.num(lockTime, true); + locktime = script.num(locktime, true); - if (lockTime < 0) + if (locktime < 0) return false; threshold = constants.locktimeThreshold; if (!( - (tx.lockTime < threshold && lockTime < threshold) - || (tx.lockTime >= threshold && lockTime >= threshold) + (tx.locktime < threshold && locktime < threshold) + || (tx.locktime >= threshold && locktime >= threshold) )) { return false; } - if (lockTime > tx.lockTime) + if (locktime > tx.locktime) return false; if (!tx.inputs[index] || tx.inputs[index].seq === 0xffffffff) @@ -1158,6 +1158,20 @@ script.checkPush = function checkPush(op, value, flags) { return true; }; +script.createPubkey = function createPubkey(key) { + return [key, 'checksig']; +}; + +script.createPubkeyhash = function createPubkeyhash(hash) { + return [ + 'dup', + 'hash160', + hash, + 'equalverify', + 'checksig' + ]; +}; + script.createMultisig = function createMultisig(keys, m, n) { if (keys.length !== n) throw new Error(n + ' keys are required to generate multisig script'); @@ -1171,15 +1185,21 @@ script.createMultisig = function createMultisig(keys, m, n) { ); }; -script.createScripthash = function createScripthash(s) { - assert(Array.isArray(s)); +script.createScripthash = function createScripthash(hash) { return [ 'hash160', - utils.ripesha(script.encode(s)), + hash, 'equal' ]; }; +script.createNulldata = function createNulldata(flags) { + return [ + 'return', + flags + ]; +}; + script.getRedeem = function getRedeem(s) { if (!Array.isArray(s[s.length - 1])) return; @@ -1247,7 +1267,7 @@ script.isEncoded = function isEncoded(s) { return true; }; -script._lockTime = function _lockTime(s) { +script._locktime = function _locktime(s) { var i; if (s.length < 2) @@ -1259,22 +1279,22 @@ script._lockTime = function _lockTime(s) { } }; -script.isLockTime = function isLockTime(s) { - return !!script._lockTime(s); +script.isLocktime = function isLocktime(s) { + return !!script._locktime(s); }; -script.getLockTime = function getLockTime(s) { - var lockTime = script._lockTime(s); +script.getLocktime = function getLocktime(s) { + var locktime = script._locktime(s); - if (!lockTime) + if (!locktime) return; - lockTime = script.num(lockTime, true); + locktime = script.num(locktime, true); - if (lockTime < constants.locktimeThreshold) - return { type: 'height', value: lockTime }; + if (locktime < constants.locktimeThreshold) + return { type: 'height', value: locktime }; - return { type: 'time', value: lockTime }; + return { type: 'time', value: locktime }; }; script.getInputData = function getData(s, prev) { @@ -1320,7 +1340,7 @@ script.getInputData = function getData(s, prev) { }; script._getInputData = function _getInputData(s, type) { - var sig, key, hash, raw, redeem, lockTime, hash, address, input, output; + var sig, key, hash, raw, redeem, locktime, hash, address, input, output; assert(typeof type === 'string'); @@ -1363,7 +1383,7 @@ script._getInputData = function _getInputData(s, type) { if (type === 'scripthash') { raw = s[s.length - 1]; redeem = script.decode(raw); - lockTime = script.getLockTime(redeem); + locktime = script.getLocktime(redeem); hash = bcoin.wallet.key2hash(raw); address = bcoin.wallet.hash2addr(hash, 'scripthash'); output = script.getOutputData(script.getSubscript(redeem)); @@ -1374,9 +1394,9 @@ script._getInputData = function _getInputData(s, type) { side: 'input', subtype: output.type, redeem: redeem, - scripthash: hash, - scriptaddress: address, - lockTime: lockTime + scriptHash: hash, + scriptAddress: address, + locktime: locktime }); } @@ -1433,8 +1453,8 @@ script.getOutputData = function getOutputData(s) { return { type: 'scripthash', side: 'output', - scripthash: hash, - scriptaddress: bcoin.wallet.hash2addr(hash, 'scripthash') + scriptHash: hash, + scriptAddress: bcoin.wallet.hash2addr(hash, 'scripthash') }; } diff --git a/lib/bcoin/tx-pool.js b/lib/bcoin/tx-pool.js index f2fd7e88..e511bac8 100644 --- a/lib/bcoin/tx-pool.js +++ b/lib/bcoin/tx-pool.js @@ -233,6 +233,19 @@ TXPool.prototype._removeTX = function _removeTX(tx, noWrite) { }); }; +TXPool.prototype.prune = function prune(pruneOrphans) { + var unspent = Object.keys(this._unspent).reduce(function(key) { + out[key.split('/')[0]] = true; + return out; + }, {}); + Object.keys(this._all).forEach(function(key) { + if (!unspent[key]) + delete this._all[key]; + }); + if (pruneOrphans) + this._orphans = {}; +}; + TXPool.prototype.getAll = function getAll() { return Object.keys(this._all).map(function(key) { return this._all[key]; @@ -250,43 +263,6 @@ TXPool.prototype.getUnspent = function getUnspent() { }, this); }; -TXPool.prototype.hasUnspent = function hasUnspent(hash, unspent) { - var has; - - if (utils.isBuffer(hash) && hash.length && typeof hash[0] !== 'number') { - unspent = this.getUnspent(); - has = hash.map(function(hash) { - var h = this.hasUnspent(hash, unspent); - if (!h) - return false; - return h[0]; - }, this).filter(Boolean); - if (has.length !== hash.length) - return null; - return has; - } - - if (utils.isBuffer(hash)) - hash = utils.toHex(hash); - else if (hash.prevout) - hash = hash.prevout.hash; - else if (hash.tx) - hash = hash.tx.hash('hex'); - else if (hash instanceof bcoin.tx) - hash = hash.hash('hex'); - - unspent = unspent || this.getUnspent(); - - has = unspent.filter(function(item) { - return item.tx.hash('hex') === hash; - }); - - if (!has.length) - return null; - - return has; -}; - TXPool.prototype.getPending = function getPending() { return Object.keys(this._all).map(function(key) { return this._all[key]; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 364097f2..d74a0284 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -26,14 +26,14 @@ function TX(data, block) { this.version = data.version || 1; this.inputs = []; this.outputs = []; - this.lockTime = data.lockTime || 0; + this.locktime = data.locktime || 0; this.ts = data.ts || 0; this.block = data.block || null; this._hash = null; // Legacy if (data.lock != null) - this.lockTime = data.lock; + this.locktime = data.lock; this._raw = data._raw || null; this._size = data._size || 0; @@ -73,19 +73,23 @@ function TX(data, block) { // ps = Pending Since this.ps = this.ts === 0 ? utils.now() : 0; - - // Discourage fee snipping a la bitcoind - // if (data.lockTime == null && data.lock == null) - // this.avoidFeeSnipping(); } // Legacy -TX.prototype.__defineSetter__('lock', function(lockTime) { - return this.lockTime = lockTime; +TX.prototype.__defineSetter__('lock', function(locktime) { + return this.locktime = locktime; }); TX.prototype.__defineGetter__('lock', function() { - return this.lockTime; + return this.locktime; +}); + +TX.prototype.__defineSetter__('lockTime', function(locktime) { + return this.locktime = locktime; +}); + +TX.prototype.__defineGetter__('lockTime', function() { + return this.locktime; }); TX.prototype.clone = function clone() { @@ -530,10 +534,9 @@ TX.prototype.signInput = function signInput(index, key, type) { TX.prototype.scriptSig = function scriptSig(index, key, pub, redeem, type) { var input; - if (typeof index !== 'number') + if (index && typeof index === 'object') index = this.inputs.indexOf(index); - // Get the input input = this.inputs[index]; assert(input); @@ -551,6 +554,12 @@ TX.prototype.scriptSig = function scriptSig(index, key, pub, redeem, type) { TX.prototype.isSigned = function isSigned(index, required) { var i, input, s, len, m, j, total; + if (index && typeof index === 'object') + index = this.inputs.indexOf(index); + + if (index != null) + assert(this.inputs[index]); + for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; @@ -716,25 +725,19 @@ TX.prototype.scriptOutput = function scriptOutput(index, options) { return; script = bcoin.script.createMultisig(keys, m, n); - } else if (bcoin.wallet.validateAddress(options.address, 'scripthash')) { + } else if (bcoin.address.validate(options.address, 'scripthash')) { // P2SH Transaction // https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki // hash160 [20-byte-redeemscript-hash] equal - script = [ - 'hash160', - bcoin.wallet.addr2hash(options.address, 'scripthash'), - 'equal' - ]; + script = bcoin.script.createScripthash( + bcoin.wallet.addr2hash(options.address, 'scripthash') + ); } else if (options.address) { // P2PKH Transaction // dup hash160 [pubkey-hash] equalverify checksig - script = [ - 'dup', - 'hash160', - bcoin.wallet.addr2hash(options.address, 'pubkeyhash'), - 'equalverify', - 'checksig' - ]; + script = bcoin.script.createPubkeyhash( + bcoin.wallet.addr2hash(options.address, 'pubkeyhash') + ); } else if (options.key) { // P2PK Transaction // [pubkey] checksig @@ -750,36 +753,37 @@ TX.prototype.scriptOutput = function scriptOutput(index, options) { flags = utils.ascii2array(flags); assert(utils.isBuffer(flags)); assert(flags.length <= constants.script.maxOpReturn); - script = [ - 'return', - flags - ]; + script = bcoin.script.createNulldata(flags); } // P2SH Transaction // hash160 [hash] eq if (options.scripthash) { - if (options.lockTime != null) { + if (options.locktime != null) { script = [ - bcoin.script.array(options.lockTime), + bcoin.script.array(options.locktime), 'checklocktimeverify', 'drop', 'codeseparator' ].concat(script); } hash = utils.ripesha(bcoin.script.encode(script)); - script = [ - 'hash160', - hash, - 'equal' - ]; + script = bcoin.script.createScripthash(hash); } output.script = script; }; TX.prototype.getSubscript = function getSubscript(index) { - var script = this.outputs[index].script; + var script; + + if (typeof index !== 'number') + index = this.outputs.indexOf(index); + + assert(this.outputs[index]); + + script = this.outputs[index].script; + return bcoin.script.getSubscript(script); }; @@ -877,8 +881,10 @@ TX.prototype.tbsHash = function tbsHash(enc, force) { return this.hash(enc); if (!this._tbsHash || force) { - for (i = 0; i < copy.inputs.length; i++) - copy.inputs[i].script = []; + for (i = 0; i < copy.inputs.length; i++) { + if (!copy.isCoinbase()) + copy.inputs[i].script = []; + } this._tbsHash = utils.dsha256(copy.render(true)); } @@ -896,10 +902,16 @@ TX.prototype.verify = function verify(index, force, flags) { if (this.inputs.length === 0) return false; + if (index && typeof index === 'object') + index = this.inputs.indexOf(index); + + if (index != null) + assert(this.inputs[index]); + return this.inputs.every(function(input, i) { var output; - if (index != null && index !== i) + if (index != null && i !== index) return true; if (!input.prevout.tx) @@ -1248,24 +1260,24 @@ TX.prototype.funds = TX.prototype.getFunds; TX.prototype.getTargetTime = function getTargetTime() { var bestValue = 0; - var i, lockTime, bestType; + var i, locktime, bestType; for (i = 0; i < this.inputs.length; i++) { - lockTime = this.inputs[i].getLocktime(); + locktime = this.inputs[i].getLocktime(); - if (!lockTime) + if (!locktime) continue; // Incompatible types - if (bestType && bestType !== lockTime.type) + if (bestType && bestType !== locktime.type) return; - bestType = lockTime.type; + bestType = locktime.type; - if (lockTime.value < bestValue) + if (locktime.value < bestValue) continue; - bestValue = lockTime.value; + bestValue = locktime.value; } return { @@ -1288,6 +1300,12 @@ TX.prototype.testInputs = function testInputs(addressTable, index, collect) { }, {}); } + if (index && typeof index === 'object') + index = this.inputs.indexOf(index); + + if (index != null) + assert(this.inputs[index]); + for (i = 0; i < this.inputs.length; i++) { if (index != null && i !== index) continue; @@ -1311,8 +1329,8 @@ TX.prototype.testInputs = function testInputs(addressTable, index, collect) { } } - if (data.scriptaddress) { - if (addressTable[data.scriptaddress] != null) { + if (data.scriptAddress) { + if (addressTable[data.scriptAddress] != null) { if (!collect) return true; inputs.push(input); @@ -1343,6 +1361,12 @@ TX.prototype.testOutputs = function testOutputs(addressTable, index, collect) { }, {}); } + if (index && typeof index === 'object') + index = this.outputs.indexOf(index); + + if (index != null) + assert(this.outputs[index]); + for (i = 0; i < this.outputs.length; i++) { if (index != null && i !== index) continue; @@ -1361,8 +1385,8 @@ TX.prototype.testOutputs = function testOutputs(addressTable, index, collect) { } } - if (data.scriptaddress) { - if (addressTable[data.scriptaddress] != null) { + if (data.scriptAddress) { + if (addressTable[data.scriptAddress] != null) { if (!collect) return true; outputs.push(output); @@ -1383,16 +1407,16 @@ TX.prototype.avoidFeeSnipping = function avoidFeeSnipping() { if (!this.chain) return; - this.lockTime = this.chain.height(); + this.locktime = this.chain.height(); if ((Math.random() * 10 | 0) === 0) - this.lockTime = Math.max(0, this.lockTime - (Math.random() * 100 | 0)); + this.locktime = Math.max(0, this.locktime - (Math.random() * 100 | 0)); }; -TX.prototype.setLockTime = function setLockTime(lockTime) { +TX.prototype.setLocktime = function setLocktime(locktime) { var i, input; - this.lockTime = lockTime; + this.locktime = locktime; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; @@ -1489,10 +1513,10 @@ TX.prototype.isFinal = function isFinal(height, ts) { if (!this.chain) return true; - if (this.lockTime === 0) + if (this.locktime === 0) return true; - if (this.lockTime < (this.lockTime < threshold ? height : ts)) + if (this.locktime < (this.locktime < threshold ? height : ts)) return true; for (i = 0; i < this.inputs.length; i++) { @@ -1503,11 +1527,11 @@ TX.prototype.isFinal = function isFinal(height, ts) { return true; }; -TX.prototype.getSigops = function getSigops(scripthash, accurate) { +TX.prototype.getSigops = function getSigops(scriptHash, accurate) { var n = 0; this.inputs.forEach(function(input) { n += bcoin.script.getSigops(input.script, accurate); - if (scripthash && !this.isCoinbase()) + if (scriptHash && !this.isCoinbase()) n += bcoin.script.getScripthashSigops(input.script); }, this); this.outputs.forEach(function(output) { diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 2d322410..054996ba 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -133,7 +133,7 @@ Wallet.prototype._init = function init() { }); }; -Wallet.prototype.__defineGetter__('address', function() { +Wallet.prototype.__defineGetter__('primary', function() { return this.addresses[0]; }); @@ -233,7 +233,7 @@ Wallet.prototype.addAddress = function addAddress(address) { index = this.addresses.push(address) - 1; - address.on('scriptaddress', address._onUpdate = function(old, cur) { + address.on('update script', address._onUpdate = function(old, cur) { self._addressTable[cur] = self._addressTable[old]; delete self._addressTable[old]; self.emit('add address', address); @@ -267,7 +267,7 @@ Wallet.prototype.removeAddress = function removeAddress(address) { this.addresses.splice(i, 1); - address.removeListener('scriptaddress', address._onUpdate); + address.removeListener('update script', address._onUpdate); this._addressTable = this._getAddressTable(); @@ -283,47 +283,51 @@ Wallet.prototype.removeAddress = function removeAddress(address) { }; Wallet.prototype.addKey = function addKey(key, i) { - return this.address.addKey(key); + return this.primary.addKey(key); }; Wallet.prototype.removeKey = function removeKey(key) { - return this.address.removeKey(key); + return this.primary.removeKey(key); }; Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { - return this.address.getPrivateKey(enc); + return this.primary.getPrivateKey(enc); }; Wallet.prototype.getScript = function getScript() { - return this.address.getScript(); + return this.primary.getScript(); }; -Wallet.prototype.getScriptHash = function getScriptHash() { - return this.address.getScriptHash(); +Wallet.prototype.getScriptHash = +Wallet.prototype.getScripthash = function getScripthash() { + return this.primary.getScriptHash(); }; -Wallet.prototype.getScriptAddress = function getScriptAddress() { - return this.address.getScriptAddress(); +Wallet.prototype.getScriptAddress = +Wallet.prototype.getScriptaddress = function getScriptaddress() { + return this.primary.getScriptAddress(); }; Wallet.prototype.getPublicKey = function getPublicKey(enc) { - return this.address.getPublicKey(enc); + return this.primary.getPublicKey(enc); }; -Wallet.prototype.getKeyHash = function getKeyHash() { - return this.address.getKeyHash(); +Wallet.prototype.getKeyHash = +Wallet.prototype.getKeyhash = function getKeyhash() { + return this.primary.getKeyHash(); }; -Wallet.prototype.getKeyAddress = function getKeyAddress() { - return this.address.getKeyAddress(); +Wallet.prototype.getKeyAddress = +Wallet.prototype.getKeyaddress = function getKeyaddress() { + return this.primary.getKeyAddress(); }; Wallet.prototype.getHash = function getHash() { - return this.address.getHash(); + return this.primary.getHash(); }; Wallet.prototype.getAddress = function getAddress() { - return this.address.getAddress(); + return this.primary.getAddress(); }; Wallet.prototype.ownInput = function ownInput(tx, index) { @@ -347,12 +351,12 @@ Wallet.prototype.fill = function fill(tx, address, fee) { items = unspent.filter(function(item) { var output = item.tx.outputs[item.index]; if (bcoin.script.isScripthash(output.script)) { - if (this.address.type === 'scripthash') + if (this.primary.type === 'scripthash') return true; return false; } if (bcoin.script.isMultisig(output.script)) { - if (this.address.n > 1) + if (this.primary.n > 1) return true; return false; } @@ -405,7 +409,7 @@ Wallet.prototype.createTX = function createTX(outputs, fee) { return; if (target.value > 0) - tx.setLockTime(target.value); + tx.setLocktime(target.value); else tx.avoidFeeSnipping(); @@ -415,83 +419,23 @@ Wallet.prototype.createTX = function createTX(outputs, fee) { }; Wallet.prototype.scriptInputs = function scriptInputs(tx) { - var self = this; - + this.fillPrevout(tx); return this.addresses.reduce(function(total, address) { - var pub = address.getPublicKey(); - var redeem = address.getScript(); - - tx.inputs.forEach(function(input, i) { - if (!input.prevout.tx && self.tx._all[input.prevout.hash]) - input.prevout.tx = self.tx._all[input.prevout.hash]; - - if (!input.prevout.tx) - return; - - if (!address.ownOutput(input.prevout.tx, input.prevout.index)) - return; - - if (tx.scriptInput(i, pub, redeem)) - total++; - }); - - return total; + return total + address.signInputs(tx); }, 0); }; Wallet.prototype.signInputs = function signInputs(tx, type) { - var self = this; - + this.fillPrevout(tx); return this.addresses.reduce(function(total, address) { - if (!address.key.priv) - return total; - - tx.inputs.forEach(function(input, i) { - if (!input.prevout.tx && self.tx._all[input.prevout.hash]) - input.prevout.tx = self.tx._all[input.prevout.hash]; - - if (!input.prevout.tx) - return; - - if (!address.ownOutput(input.prevout.tx, input.prevout.index)) - return; - - if (tx.signInput(i, address.key, type)) - total++; - }); - - return total; + return total + address.signInputs(tx, type); }, 0); }; Wallet.prototype.sign = function sign(tx, type) { - var self = this; - + this.fillPrevout(tx); return this.addresses.reduce(function(total, address) { - var pub = address.getPublicKey(); - var redeem = address.getScript(); - var key = address.key; - - if (!key.priv) - return total; - - // Add signature script to each input - tx.inputs.forEach(function(input, i) { - if (!input.prevout.tx && self.tx._all[input.prevout.hash]) - input.prevout.tx = self.tx._all[input.prevout.hash]; - - // Filter inputs that this wallet own - if (!input.prevout.tx) - return; - - if (!address.ownOutput(input.prevout.tx, input.prevout.index)) - return; - - if (tx.scriptSig(i, key, pub, redeem, type)) - total++; - }); - - return total; + return total + address.sign(tx, type); }, 0); }; @@ -552,6 +496,42 @@ Wallet.prototype.toAddress = function toAddress() { }; }; +Wallet.prototype.__defineGetter__('script', function() { + return this.getScript(); +}); + +Wallet.prototype.__defineGetter__('scriptHash', function() { + return this.getScriptHash(); +}); + +Wallet.prototype.__defineGetter__('scriptAddress', function() { + return this.getScriptAddress(); +}); + +Wallet.prototype.__defineGetter__('privateKey', function() { + return this.getPrivateKey(); +}); + +Wallet.prototype.__defineGetter__('publicKey', function() { + return this.getPublicKey(); +}); + +Wallet.prototype.__defineGetter__('keyHash', function() { + return this.getKeyHash(); +}); + +Wallet.prototype.__defineGetter__('keyAddress', function() { + return this.getKeyAddress(); +}); + +Wallet.prototype.__defineGetter__('hash', function() { + return this.getHash(); +}); + +Wallet.prototype.__defineGetter__('address', function() { + return this.getAddress(); +}); + Wallet.prototype.toJSON = function toJSON(encrypt) { return { v: 3, diff --git a/test/wallet-test.js b/test/wallet-test.js index de63e017..80c86378 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -286,6 +286,7 @@ describe('Wallet', function() { n: 3 } }); + w3 = bcoin.wallet.fromJSON(w3.toJSON()); var receive = bcoin.wallet();