diff --git a/lib/bcoin/address.js b/lib/bcoin/address.js index f9272204..801c820b 100644 --- a/lib/bcoin/address.js +++ b/lib/bcoin/address.js @@ -15,6 +15,7 @@ var assert = utils.assert; var BufferWriter = require('./writer'); var BufferReader = require('./reader'); var Script = bcoin.script; +var scriptTypes = constants.scriptTypes; /** * Represents an address. @@ -37,7 +38,7 @@ function Address(options) { return new Address(options); this.hash = constants.ZERO_HASH160; - this.type = 'pubkeyhash'; + this.type = scriptTypes.PUBKEYHASH; this.version = -1; this.network = bcoin.network.get().type; @@ -82,6 +83,15 @@ Address.prototype.getHash = function getHash(enc) { return this.hash; }; +/** + * Get the address type as a string. + * @returns {AddressType} + */ + +Address.prototype.getType = function getType() { + return constants.scriptTypesByVal[this.type].toLowerCase(); +}; + /** * Compile the address object to its raw serialization. * @param {{NetworkType|Network)?} network @@ -97,9 +107,9 @@ Address.prototype.toRaw = function toRaw(network) { network = this.network; network = bcoin.network.get(network); - prefix = network.address.prefixes[this.type]; + prefix = Address.getPrefix(this.type, network); - assert(prefix != null, 'Not a valid address prefix.'); + assert(prefix !== -1, 'Not a valid address prefix.'); p.writeU8(prefix); if (this.version !== -1) { @@ -140,8 +150,10 @@ Address.prototype.toScript = function toScript() { Address.prototype.toString = function toString(enc) { if (enc === 'hex') return this.getHash('hex'); + if (enc === 'base58') enc = null; + return this.toBase58(enc); }; @@ -152,7 +164,7 @@ Address.prototype.toString = function toString(enc) { Address.prototype.inspect = function inspect() { return ''; @@ -175,12 +187,12 @@ Address.prototype.fromRaw = function fromRaw(data) { for (i = 0; i < networks.types.length; i++) { network = networks[networks.types[i]]; - type = network.address.prefixesByVal[prefix]; - if (type != null) + type = Address.getType(prefix, network); + if (type !== -1) break; } - assert(type != null, 'Unknown address prefix.'); + assert(i < networks.types.length, 'Unknown address prefix.'); if (data.length > 25) { version = p.readU8(); @@ -236,59 +248,52 @@ Address.fromBase58 = function fromBase58(address) { */ Address.prototype.fromScript = function fromScript(script) { - var program; - - if (script.isProgram()) { - program = script.toProgram(); - // TODO: MAST support - if (program.isUnknown()) - return; - this.hash = program.data; - this.type = program.type; - this.version = program.version; - return this; - } - - // Fast case - if (script.isPubkey(true)) { - this.hash = utils.hash160(script.raw.slice(1, script.raw[0] + 1)); - this.type = 'pubkeyhash'; - this.version = -1; - return this; - } - - if (script.isPubkeyhash(true)) { - this.hash = script.raw.slice(3, 23); - this.type = 'pubkeyhash'; - this.version = -1; - return this; - } - - if (script.isScripthash()) { - this.hash = script.raw.slice(2, 22); - this.type = 'scripthash'; - this.version = -1; - return this; - } - - // Slow case (allow non-minimal data and parse script) if (script.isPubkey()) { - this.hash = utils.hash160(script.code[0].data); - this.type = 'pubkeyhash'; + this.hash = utils.hash160(script.get(0)); + this.type = scriptTypes.PUBKEYHASH; this.version = -1; return this; } if (script.isPubkeyhash()) { - this.hash = script.code[2].data; - this.type = 'pubkeyhash'; + this.hash = script.get(2); + this.type = scriptTypes.PUBKEYHASH; this.version = -1; return this; } + if (script.isScripthash()) { + this.hash = script.get(1); + this.type = scriptTypes.SCRIPTHASH; + this.version = -1; + return this; + } + + if (script.isWitnessPubkeyhash()) { + this.hash = script.get(1); + this.type = scriptTypes.WITNESSPUBKEYHASH; + this.version = 0; + return this; + } + + if (script.isWitnessScripthash()) { + this.hash = script.get(1); + this.type = scriptTypes.WITNESSSCRIPTHASH; + this.version = 0; + return this; + } + + if (script.isWitnessMasthash()) { + this.hash = script.get(1); + this.type = scriptTypes.WITNESSSCRIPTHASH; + this.version = 1; + return this; + } + + // Put this last: it's the slowest to check. if (script.isMultisig()) { - this.hash = utils.hash160(script.raw); - this.type = 'scripthash'; + this.hash = utils.hash160(script.toRaw()); + this.type = scriptTypes.SCRIPTHASH; this.version = -1; return this; } @@ -301,16 +306,18 @@ Address.prototype.fromScript = function fromScript(script) { */ Address.prototype.fromWitness = function fromWitness(witness) { + // We're pretty much screwed here + // since we can't get the version. if (witness.isPubkeyhashInput()) { - this.hash = utils.hash160(witness.items[1]); - this.type = 'witnesspubkeyhash'; + this.hash = utils.hash160(witness.get(1)); + this.type = scriptTypes.WITNESSPUBKEYHASH; this.version = 0; return this; } if (witness.isScripthashInput()) { - this.hash = utils.sha256(witness.items[witness.items.length - 1]); - this.type = 'witnessscripthash'; + this.hash = utils.sha256(witness.get(witness.length - 1)); + this.type = scriptTypes.WITNESSSCRIPTHASH; this.version = 0; return this; } @@ -324,15 +331,15 @@ Address.prototype.fromWitness = function fromWitness(witness) { Address.prototype.fromInputScript = function fromInputScript(script) { if (script.isPubkeyhashInput()) { - this.hash = utils.hash160(script.code[1].data); - this.type = 'pubkeyhash'; + this.hash = utils.hash160(script.get(1)); + this.type = scriptTypes.PUBKEYHASH; this.version = -1; return this; } if (script.isScripthashInput()) { - this.hash = utils.hash160(script.code[script.code.length - 1].data); - this.type = 'scripthash'; + this.hash = utils.hash160(script.get(script.length - 1)); + this.type = scriptTypes.SCRIPTHASH; this.version = -1; return this; } @@ -389,8 +396,11 @@ Address.prototype.fromHash = function fromHash(hash, type, version, network) { if (typeof hash === 'string') hash = new Buffer(hash, 'hex'); - if (!type) - type = 'pubkeyhash'; + if (typeof type === 'string') + type = scriptTypes[type.toUpperCase()]; + + if (type == null) + type = scriptTypes.PUBKEYHASH; if (version == null) version = -1; @@ -398,21 +408,22 @@ Address.prototype.fromHash = function fromHash(hash, type, version, network) { network = bcoin.network.get(network); assert(Buffer.isBuffer(hash)); - assert(typeof type === 'string'); + assert(utils.isNumber(type)); assert(utils.isNumber(version)); - assert(network.address.prefixes[type] != null, 'Not a valid address prefix.'); + + assert(Address.getPrefix(type, network) !== -1, 'Not a valid address type.'); if (version === -1) { - assert(!network.address.witness[type], 'Wrong version (witness)'); + assert(!Address.isWitness(type), 'Wrong version (witness)'); assert(hash.length === 20, 'Hash is the wrong size.'); } else { - assert(network.address.witness[type], 'Wrong version (non-witness).'); + assert(Address.isWitness(type), 'Wrong version (non-witness).'); assert(version >= 0 && version <= 16, 'Bad program version.'); - if (version === 0 && type === 'witnesspubkeyhash') + if (version === 0 && type === scriptTypes.WITNESSPUBKEYHASH) assert(hash.length === 20, 'Hash is the wrong size.'); - else if (version === 0 && type === 'witnessscripthash') + else if (version === 0 && type === scriptTypes.WITNESSSCRIPTHASH) assert(hash.length === 32, 'Hash is the wrong size.'); - else if (version === 1 && type === 'witnessscripthash') + else if (version === 1 && type === scriptTypes.WITNESSSCRIPTHASH) assert(hash.length === 32, 'Hash is the wrong size.'); } @@ -439,30 +450,45 @@ Address.fromHash = function fromHash(hash, type, version, network) { }; /** - * Inject properties from hash. - * @param {Hash|Buffer} hash - * @param {AddressType?} type - * @param {Number?} version - Witness program version. + * Inject properties from data. + * @private + * @param {Buffer|Buffer[]} data + * @param {AddressType} type + * @param {Number} [version=-1] * @param {(Network|NetworkType)?} network - * @throws on bad hash size */ Address.prototype.fromData = function fromData(data, type, version, network) { - if (type === 'witnessscripthash') { - assert(version === 0); - data = utils.sha256(data); - } else if (type === 'witnesspubkeyhash') { - assert(version === 0); + if (typeof type === 'string') + type = scriptTypes[type.toUpperCase()]; + + if (type === scriptTypes.WITNESSSCRIPTHASH) { + if (version === 0) { + assert(Buffer.isBuffer(data)); + data = utils.sha256(data); + } else if (version === 1) { + assert(Array.isArray(data)); + data = utils.getMerkleRoot(data); + } else { + throw new Error('Cannot create from version=' + version); + } + } else if (type === scriptTypes.WITNESSPUBKEYHASH) { + if (version !== 0) + throw new Error('Cannot create from version=' + version); + assert(Buffer.isBuffer(data)); data = utils.hash160(data); } else { data = utils.hash160(data); } + return this.fromHash(data, type, version, network); }; /** * Create an Address from data/type/version. - * @param {Buffer} data - Data to be hashed. + * @param {Buffer|Buffer[]} data - Data to be hashed. + * Normally a buffer, but can also be an array of + * buffers for MAST. * @param {AddressType} type * @param {Number} [version=-1] * @param {(Network|NetworkType)?} network @@ -494,6 +520,9 @@ Address.validate = function validate(address, type) { return false; } + if (typeof type === 'string') + type = scriptTypes[type.toUpperCase()]; + if (type && address.type !== type) return false; @@ -530,6 +559,69 @@ Address.getHash = function getHash(data, enc) { : hash; }; +/** + * Get a network address prefix for a specified address type. + * @param {AddressType} type + * @param {Network} network + * @returns {Number} + */ + +Address.getPrefix = function getPrefix(type, network) { + var prefixes = network.addressPrefix; + switch (type) { + case scriptTypes.PUBKEYHASH: + return prefixes.pubkeyhash; + case scriptTypes.SCRIPTHASH: + return prefixes.scripthash; + case scriptTypes.WITNESSPUBKEYHASH: + return prefixes.witnesspubkeyhash; + case scriptTypes.WITNESSSCRIPTHASH: + return prefixes.witnessscripthash; + default: + return -1; + } +}; + +/** + * Get an address type for a specified network address prefix. + * @param {Number} prefix + * @param {Network} network + * @returns {AddressType} + */ + +Address.getType = function getType(prefix, network) { + var prefixes = network.addressPrefix; + switch (prefix) { + case prefixes.pubkeyhash: + return scriptTypes.PUBKEYHASH; + case prefixes.scripthash: + return scriptTypes.SCRIPTHASH; + case prefixes.witnesspubkeyhash: + return scriptTypes.WITNESSPUBKEYHASH; + case prefixes.witnessscripthash: + return scriptTypes.WITNESSSCRIPTHASH; + default: + return -1; + } +}; + +/** + * Test whether an address type is a witness program. + * @param {AddressType} type + * @returns {Boolean} + */ + +Address.isWitness = function isWitness(type) { + switch (type) { + case scriptTypes.WITNESSPUBKEYHASH: + return true; + case scriptTypes.WITNESSSCRIPTHASH: + return true; + default: + return false; + } +}; + /* * Expose */ diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 564320f7..b08298bf 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -1083,7 +1083,7 @@ HDPrivateKey.isExtended = function isExtended(data) { for (i = 0; i < networks.types.length; i++) { type = networks.types[i]; - prefix = networks[type].prefixes.xprivkey58; + prefix = networks[type].keyPrefix.xprivkey58; if (data.indexOf(prefix) === 0) return true; } @@ -1107,7 +1107,7 @@ HDPrivateKey.hasPrefix = function hasPrefix(data) { for (i = 0; i < networks.types.length; i++) { type = networks.types[i]; - prefix = networks[type].prefixes.xprivkey; + prefix = networks[type].keyPrefix.xprivkey; if (version === prefix) return type; } @@ -1326,7 +1326,7 @@ HDPrivateKey.prototype.fromRaw = function fromRaw(raw) { for (i = 0; i < networks.types.length; i++) { type = networks.types[i]; - prefix = networks[type].prefixes.xprivkey; + prefix = networks[type].keyPrefix.xprivkey; if (version === prefix) break; } @@ -1363,7 +1363,7 @@ HDPrivateKey.prototype.toRaw = function toRaw(network, writer) { network = bcoin.network.get(network); - p.writeU32BE(network.prefixes.xprivkey); + p.writeU32BE(network.keyPrefix.xprivkey); p.writeU8(this.depth); p.writeBytes(this.parentFingerPrint); p.writeU32BE(this.childIndex); @@ -1806,7 +1806,7 @@ HDPublicKey.isExtended = function isExtended(data) { for (i = 0; i < networks.types.length; i++) { type = networks.types[i]; - prefix = networks[type].prefixes.xpubkey58; + prefix = networks[type].keyPrefix.xpubkey58; if (data.indexOf(prefix) === 0) return true; } @@ -1830,7 +1830,7 @@ HDPublicKey.hasPrefix = function hasPrefix(data) { for (i = 0; i < networks.types.length; i++) { type = networks.types[i]; - prefix = networks[type].prefixes.xpubkey; + prefix = networks[type].keyPrefix.xpubkey; if (version === prefix) return type; } @@ -1870,7 +1870,7 @@ HDPublicKey.prototype.fromRaw = function fromRaw(raw) { for (i = 0; i < networks.types.length; i++) { type = networks.types[i]; - prefix = networks[type].prefixes.xpubkey; + prefix = networks[type].keyPrefix.xpubkey; if (version === prefix) break; } @@ -1906,7 +1906,7 @@ HDPublicKey.prototype.toRaw = function toRaw(network, writer) { network = bcoin.network.get(network); - p.writeU32BE(network.prefixes.xpubkey); + p.writeU32BE(network.keyPrefix.xpubkey); p.writeU8(this.depth); p.writeBytes(this.parentFingerPrint); p.writeU32BE(this.childIndex); diff --git a/lib/bcoin/input.js b/lib/bcoin/input.js index 92697729..0342d532 100644 --- a/lib/bcoin/input.js +++ b/lib/bcoin/input.js @@ -247,10 +247,10 @@ Input.fromOptions = function fromOptions(options) { }; /** - * Get the previous output script type. Will "guess" - * based on the input script and/or witness if coin - * is not available. - * @returns {String} type + * Get the previous output script type as a string. + * Will "guess" based on the input script and/or + * witness if coin is not available. + * @returns {ScriptType} type */ Input.prototype.getType = function getType() { @@ -267,7 +267,7 @@ Input.prototype.getType = function getType() { else type = this.script.getInputType(); - return type; + return constants.scriptTypesByVal[type].toLowerCase(); }; /** diff --git a/lib/bcoin/keypair.js b/lib/bcoin/keypair.js index 66d0366c..b59b159c 100644 --- a/lib/bcoin/keypair.js +++ b/lib/bcoin/keypair.js @@ -161,7 +161,7 @@ KeyPair.prototype.toRaw = function toRaw(network, writer) { network = bcoin.network.get(network); - p.writeU8(network.prefixes.privkey); + p.writeU8(network.keyPrefix.privkey); p.writeBytes(this.getPrivateKey()); if (this.compressed) @@ -199,7 +199,7 @@ KeyPair.prototype.fromRaw = function fromRaw(data) { for (i = 0; i < network.types.length; i++) { type = network.types[i]; - prefix = network[type].prefixes.privkey; + prefix = network[type].keyPrefix.privkey; if (version === prefix) break; } diff --git a/lib/bcoin/keyring.js b/lib/bcoin/keyring.js index ac82b706..20013f49 100644 --- a/lib/bcoin/keyring.js +++ b/lib/bcoin/keyring.js @@ -8,10 +8,12 @@ 'use strict'; var bcoin = require('./env'); +var constants = bcoin.protocol.constants; var utils = bcoin.utils; var assert = utils.assert; var BufferReader = require('./reader'); var BufferWriter = require('./writer'); +var scriptTypes = constants.scriptTypes; /** * Represents a key ring which amounts to an address. Used for {@link Wallet}. @@ -259,7 +261,7 @@ KeyRing.prototype.getProgramAddress = function getProgramAddress(enc) { if (!this._programAddress) { hash = this.getProgramHash(); - address = this.compile(hash, 'scripthash'); + address = this.compile(hash, scriptTypes.SCRIPTHASH); this._programAddress = address; } @@ -332,10 +334,10 @@ KeyRing.prototype.getScriptAddress = function getScriptAddress(enc) { if (!this._scriptAddress) { if (this.witness) { hash = this.getScriptHash256(); - address = this.compile(hash, 'witnessscripthash', 0); + address = this.compile(hash, scriptTypes.WITNESSSCRIPTHASH, 0); } else { hash = this.getScriptHash160(); - address = this.compile(hash, 'scripthash'); + address = this.compile(hash, scriptTypes.SCRIPTHASH); } this._scriptAddress = address; } @@ -373,9 +375,9 @@ KeyRing.prototype.getKeyAddress = function getKeyAddress(enc) { if (!this._keyAddress) { hash = this.getKeyHash(); if (this.witness) - address = this.compile(hash, 'witnesspubkeyhash', 0); + address = this.compile(hash, scriptTypes.WITNESSPUBKEYHASH, 0); else - address = this.compile(hash, 'pubkeyhash'); + address = this.compile(hash, scriptTypes.PUBKEYHASH); this._keyAddress = address; } diff --git a/lib/bcoin/network.js b/lib/bcoin/network.js index 0364abe1..08a7cdf6 100644 --- a/lib/bcoin/network.js +++ b/lib/bcoin/network.js @@ -46,8 +46,8 @@ function Network(options) { this.activationThreshold = options.activationThreshold; this.minerWindow = options.minerWindow; this.deployments = options.deployments; - this.prefixes = options.prefixes; - this.address = options.address; + this.keyPrefix = options.keyPrefix; + this.addressPrefix = options.addressPrefix; this.requireStandard = options.requireStandard; this.rpcPort = options.rpcPort; this.minRelay = options.minRelay; diff --git a/lib/bcoin/output.js b/lib/bcoin/output.js index ccd92725..5e2ca1ed 100644 --- a/lib/bcoin/output.js +++ b/lib/bcoin/output.js @@ -68,12 +68,12 @@ Output.fromOptions = function fromOptions(options) { }; /** - * Get the script type. - * @returns {String} type + * Get the script type as a string. + * @returns {ScriptType} type */ Output.prototype.getType = function getType() { - return this.script.getType(); + return constants.scriptTypesByVal[this.script.getType()].toLowerCase(); }; /** diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 13fbbadf..6a20b94e 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -674,11 +674,19 @@ exports.scriptTypes = { SCRIPTHASH: 3, MULTISIG: 4, NULLDATA: 5, - WITNESSSCRIPTHASH: 6, - WITNESSKEYHASH: 7, - WITNESSMAST: 8 + WITNESSMALFORMED: 0x80 | 0, + WITNESSSCRIPTHASH: 0x80 | 1, + WITNESSPUBKEYHASH: 0x80 | 2, + WITNESSMASTHASH: 0x80 | 3 }; +/** + * Output script types by value. + * @const {RevMap} + */ + +exports.scriptTypesByVal = utils.revMap(exports.scriptTypes); + /** * Script and locktime flags. See {@link VerifyFlags}. * @enum {Number} diff --git a/lib/bcoin/protocol/network.js b/lib/bcoin/protocol/network.js index 0b8d9be2..f32db12c 100644 --- a/lib/bcoin/protocol/network.js +++ b/lib/bcoin/protocol/network.js @@ -358,7 +358,7 @@ main.deployments = { * @default */ -main.prefixes = { +main.keyPrefix = { privkey: 128, xpubkey: 0x0488b21e, xprivkey: 0x0488ade4, @@ -367,42 +367,17 @@ main.prefixes = { }; /** - * {@link Base58Address} constants. + * {@link Address} prefixes. * @enum {Object} */ -main.address = { - /** - * {@link Base58Address} prefixes. - * @enum {Number} - */ - - prefixes: { - pubkeyhash: 0, - scripthash: 5, - witnesspubkeyhash: 6, - witnessscripthash: 10 - }, - - /** - * {@link Base58Address} witness types. - * @enum {Number} - */ - - witness: { - witnesspubkeyhash: true, - witnessscripthash: true - } +main.addressPrefix = { + pubkeyhash: 0, + scripthash: 5, + witnesspubkeyhash: 6, + witnessscripthash: 10 }; -/** - * {@link Base58Address} prefixes by value. - * @type {RevMap} - * @default - */ - -main.address.prefixesByVal = utils.revMap(main.address.prefixes); - /** * Default value for whether the mempool * accepts non-standard transactions. @@ -585,7 +560,7 @@ testnet.deployments = { } }; -testnet.prefixes = { +testnet.keyPrefix = { privkey: 239, xpubkey: 0x043587cf, xprivkey: 0x04358394, @@ -593,21 +568,13 @@ testnet.prefixes = { xpubkey58: 'tpub' }; -testnet.address = { - prefixes: { - pubkeyhash: 111, - scripthash: 196, - witnesspubkeyhash: 3, - witnessscripthash: 40 - }, - witness: { - witnesspubkeyhash: true, - witnessscripthash: true - } +testnet.addressPrefix = { + pubkeyhash: 111, + scripthash: 196, + witnesspubkeyhash: 3, + witnessscripthash: 40 }; -testnet.address.prefixesByVal = utils.revMap(testnet.address.prefixes); - testnet.requireStandard = false; testnet.rpcPort = 18332; @@ -736,7 +703,7 @@ regtest.deployments = { } }; -regtest.prefixes = { +regtest.keyPrefix = { privkey: 239, xpubkey: 0x043587cf, xprivkey: 0x04358394, @@ -744,21 +711,13 @@ regtest.prefixes = { xpubkey58: 'tpub' }; -regtest.address = { - prefixes: { - pubkeyhash: 111, - scripthash: 196, - witnesspubkeyhash: 3, - witnessscripthash: 40 - }, - witness: { - witnesspubkeyhash: true, - witnessscripthash: true - } +regtest.addressPrefix = { + pubkeyhash: 111, + scripthash: 196, + witnesspubkeyhash: 3, + witnessscripthash: 40 }; -regtest.address.prefixesByVal = utils.revMap(regtest.address.prefixes); - regtest.requireStandard = false; regtest.rpcPort = 18332; @@ -866,7 +825,7 @@ segnet3.minerWindow = 144; segnet3.deployments = {}; -segnet3.prefixes = { +segnet3.keyPrefix = { privkey: 158, xpubkey: 0x053587cf, xprivkey: 0x05358394, @@ -874,21 +833,13 @@ segnet3.prefixes = { xpubkey58: '2793' }; -segnet3.address = { - prefixes: { - pubkeyhash: 30, - scripthash: 50, - witnesspubkeyhash: 3, - witnessscripthash: 40 - }, - witness: { - witnesspubkeyhash: true, - witnessscripthash: true - } +segnet3.addressPrefix = { + pubkeyhash: 30, + scripthash: 50, + witnesspubkeyhash: 3, + witnessscripthash: 40 }; -segnet3.address.prefixesByVal = utils.revMap(segnet3.address.prefixes); - segnet3.requireStandard = false; segnet3.rpcPort = 28332; @@ -1011,7 +962,7 @@ segnet4.deployments = { } }; -segnet4.prefixes = { +segnet4.keyPrefix = { privkey: 158, xpubkey: 0x053587cf, xprivkey: 0x05358394, @@ -1019,21 +970,13 @@ segnet4.prefixes = { xpubkey58: '2793' }; -segnet4.address = { - prefixes: { - pubkeyhash: 30, - scripthash: 50, - witnesspubkeyhash: 3, - witnessscripthash: 40 - }, - witness: { - witnesspubkeyhash: true, - witnessscripthash: true - } +segnet4.addressPrefix = { + pubkeyhash: 30, + scripthash: 50, + witnesspubkeyhash: 3, + witnessscripthash: 40 }; -segnet4.address.prefixesByVal = utils.revMap(segnet4.address.prefixes); - segnet4.requireStandard = false; segnet4.rpcPort = 28902; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index e74aaa1f..b43c3eb6 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -19,6 +19,7 @@ var STACK_TRUE = new Buffer([1]); var STACK_FALSE = new Buffer([]); var STACK_NEGATE = new Buffer([0x81]); var ScriptError = bcoin.errors.ScriptError; +var scriptTypes = constants.scriptTypes; /** * Refers to the witness field of segregated witness transactions. @@ -166,13 +167,17 @@ Witness.prototype.toStack = function toStack() { /** * "Guess" the type of the witness. * This method is not 100% reliable. - * @returns {String} + * @returns {ScriptType} */ Witness.prototype.getInputType = function getInputType() { - return (this.isPubkeyhashInput() && 'witnesspubkeyhash') - || (this.isScripthashInput() && 'witnessscripthash') - || 'unknown'; + if (this.isPubkeyhashInput()) + return scriptTypes.WITNESSPUBKEYHASH; + + if (this.isScripthashInput()) + return scriptTypes.WITNESSSCRIPTHASH; + + return scriptTypes.NONSTANDARD; }; /** @@ -234,7 +239,7 @@ Witness.prototype.isScripthashInput = function isScripthashInput() { */ Witness.prototype.isUnknownInput = function isUnknownInput() { - return this.getInputType() === 'unknown'; + return this.getInputType() === scriptTypes.NONSTANDARD; }; /** @@ -2663,10 +2668,10 @@ Script.prototype.fromAddress = function fromAddress(address) { if (!address) throw new Error('Unknown address type.'); - if (address.type === 'pubkeyhash') + if (address.type === scriptTypes.PUBKEYHASH) return this.fromPubkeyhash(address.hash); - if (address.type === 'scripthash') + if (address.type === scriptTypes.SCRIPTHASH) return this.fromScripthash(address.hash); if (address.version !== -1) @@ -2740,27 +2745,35 @@ Script.prototype.getRedeem = function getRedeem() { /** * Get the standard script type. - * @returns {String} Script script (can be - * any of 'witnesspubkeyhash', 'witnessscripthash', - * 'pubkey', 'multisig', 'scripthash', 'nulldata', - * or 'unknown'). + * @returns {ScriptType} */ Script.prototype.getType = function getType() { - if (this.isProgram()) { - if (this.isWitnessPubkeyhash()) - return 'witnesspubkeyhash'; - if (this.isWitnessScripthash()) - return 'witnessscripthash'; - return 'unknown'; - } + if (this.isPubkey()) + return scriptTypes.PUBKEY; - return (this.isPubkey() && 'pubkey') - || (this.isPubkeyhash() && 'pubkeyhash') - || (this.isMultisig() && 'multisig') - || (this.isScripthash() && 'scripthash') - || (this.isNulldata() && 'nulldata') - || 'unknown'; + if (this.isPubkeyhash()) + return scriptTypes.PUBKEYHASH; + + if (this.isScripthash()) + return scriptTypes.SCRIPTHASH; + + if (this.isWitnessPubkeyhash()) + return scriptTypes.WITNESSPUBKEYHASH; + + if (this.isWitnessScripthash()) + return scriptTypes.WITNESSSCRIPTHASH; + + if (this.isWitnessMasthash()) + return scriptTypes.WITNESSMASTHASH; + + if (this.isMultisig()) + return scriptTypes.MULTISIG; + + if (this.isNulldata()) + return scriptTypes.NULLDATA; + + return scriptTypes.NONSTANDARD; }; /** @@ -2769,7 +2782,7 @@ Script.prototype.getType = function getType() { */ Script.prototype.isUnknown = function isUnknown() { - return this.getType() === 'unknown'; + return this.getType() === scriptTypes.NONSTANDARD; }; /** @@ -2781,7 +2794,7 @@ Script.prototype.isStandard = function isStandard() { var type = this.getType(); var m, n; - if (type === 'multisig') { + if (type === scriptTypes.MULTISIG) { m = Script.getSmall(this.raw[0]); n = Script.getSmall(this.raw[this.raw.length - 2]); @@ -2790,9 +2803,17 @@ Script.prototype.isStandard = function isStandard() { if (m < 1 || m > n) return false; + + return true; } - return type !== 'unknown'; + if (type === scriptTypes.NULLDATA) { + if (this.raw.length > constants.script.MAX_OP_RETURN_BYTES) + return false; + return true; + } + + return type !== scriptTypes.NONSTANDARD; }; /** @@ -2935,9 +2956,6 @@ Script.prototype.isScripthash = function isScripthash() { Script.prototype.isNulldata = function isNulldata(minimal) { var i, op; - if (this.raw.length > constants.script.MAX_OP_RETURN_BYTES) - return false; - if (this.raw.length === 0) return false; @@ -2948,6 +2966,9 @@ Script.prototype.isNulldata = function isNulldata(minimal) { return true; if (minimal) { + if (this.raw.length > constants.script.MAX_OP_RETURN_BYTES) + return false; + if (this.raw.length === 2) return Script.getSmall(this.raw[1]) !== -1; @@ -2962,10 +2983,13 @@ Script.prototype.isNulldata = function isNulldata(minimal) { for (i = 1; i < this.code.length; i++) { op = this.code[i]; + if (op.data) continue; + if (op.value === -1) return false; + if (op.value > opcodes.OP_16) return false; } @@ -3070,7 +3094,7 @@ Script.prototype.isWitnessScripthash = function isWitnessScripthash() { * @returns {Boolean} */ -Script.prototype.isMAST = function isMAST() { +Script.prototype.isWitnessMasthash = function isWitnessMasthash() { return this.raw.length === 34 && this.raw[0] === opcodes.OP_1 && this.raw[1] === 0x20; @@ -3088,15 +3112,23 @@ Script.prototype.isUnspendable = function isUnspendable() { /** * "Guess" the type of the input script. * This method is not 100% reliable. - * @returns {String} + * @returns {ScriptType} */ Script.prototype.getInputType = function getInputType() { - return (this.isPubkeyInput() && 'pubkey') - || (this.isPubkeyhashInput() && 'pubkeyhash') - || (this.isMultisigInput() && 'multisig') - || (this.isScripthashInput() && 'scripthash') - || 'unknown'; + if (this.isPubkeyInput()) + return scriptTypes.PUBKEY; + + if (this.isPubkeyhashInput()) + return scriptTypes.PUBKEYHASH; + + if (this.isScripthashInput()) + return scriptTypes.SCRIPTHASH; + + if (this.isMultisigInput()) + return scriptTypes.MULTISIG; + + return scriptTypes.NONSTANDARD; }; /** @@ -3106,7 +3138,7 @@ Script.prototype.getInputType = function getInputType() { */ Script.prototype.isUnknownInput = function isUnknownInput() { - return this.getInputType() === 'unknown'; + return this.getInputType() === scriptTypes.NONSTANDARD; }; /** @@ -4157,10 +4189,10 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { if (flags & constants.flags.VERIFY_P2SH) copy = stack.clone(); - // Execute the previous output script + // Execute the previous output script. output.execute(stack, flags, tx, i, 0); - // Verify the script did not fail as well as the stack values + // Verify the stack values. if (stack.length === 0 || !Script.bool(stack.pop())) throw new ScriptError('EVAL_FALSE'); @@ -4171,7 +4203,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { if (input.raw.length !== 0) throw new ScriptError('WITNESS_MALLEATED'); - // Verify the program in the output script + // Verify the program in the output script. Script.verifyProgram(witness, output, flags, tx, i); // Force a cleanstack @@ -4195,10 +4227,10 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { raw = stack.pop(); redeem = new Script(raw); - // Execute the redeem script + // Execute the redeem script. redeem.execute(stack, flags, tx, i, 0); - // Verify the script did not fail as well as the stack values + // Verify the the stack values. if (stack.length === 0 || !Script.bool(stack.pop())) throw new ScriptError('EVAL_FALSE'); @@ -4209,15 +4241,15 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { if (!utils.equal(input.raw, Opcode.fromPush(raw).toRaw())) throw new ScriptError('WITNESS_MALLEATED_P2SH'); - // Verify the program in the redeem script + // Verify the program in the redeem script. Script.verifyProgram(witness, redeem, flags, tx, i); - // Force a cleanstack + // Force a cleanstack. stack.length = 0; } } - // Ensure there is nothing left on the stack + // Ensure there is nothing left on the stack. if (flags & constants.flags.VERIFY_CLEANSTACK) { assert((flags & constants.flags.VERIFY_P2SH) !== 0); // assert((flags & constants.flags.VERIFY_WITNESS) !== 0); @@ -4273,7 +4305,7 @@ Script.verifyProgram = function verifyProgram(witness, output, flags, tx, i) { redeem = Script.fromPubkeyhash(program.data); } else { - // Failure on version=0 (bad program data length) + // Failure on version=0 (bad program data length). throw new ScriptError('WITNESS_PROGRAM_WRONG_LENGTH'); } } else if ((flags & constants.flags.VERIFY_MAST) && program.version === 1) { @@ -4341,14 +4373,16 @@ Script.verifyProgram = function verifyProgram(witness, output, flags, tx, i) { return true; } + // Witnesses still have push limits. for (j = 0; j < stack.length; j++) { if (stack.get(j).length > constants.script.MAX_PUSH) throw new ScriptError('PUSH_SIZE'); } + // Verify the redeem script. redeem.execute(stack, flags, tx, i, 1); - // Verify the script did not fail as well as the stack values + // Verify the stack values. if (stack.length !== 1 || !Script.bool(stack.pop())) throw new ScriptError('EVAL_FALSE'); @@ -4799,24 +4833,44 @@ function Program(version, data) { if (!(this instanceof Program)) return new Program(version, data); + assert(utils.isNumber(version)); + assert(Buffer.isBuffer(data)); + assert(version >= 0 && version <= 16); + assert(data.length >= 2 && data.length <= 40); + this.version = version; this.data = data; - this.type = null; - - // TODO: MAST support - if (version > 0) { - // No interpretation of script (anyone can spend) - this.type = 'unknown'; - } else if (version === 0 && data.length === 20) { - this.type = 'witnesspubkeyhash'; - } else if (version === 0 && data.length === 32) { - this.type = 'witnessscripthash'; - } else { - // Fail on bad version=0 - this.type = null; - } } +/** + * Get the witness program type. + * @returns {ScriptType} + */ + +Program.prototype.getType = function getType() { + if (this.version === 0) { + if (this.data.length === 20) + return scriptTypes.WITNESSPUBKEYHASH; + + if (this.data.length === 32) + return scriptTypes.WITNESSSCRIPTHASH; + + // Fail on bad version=0 + return scriptTypes.WITNESSMALFORMED; + } + + if (this.version === 1) { + if (this.data.length === 32) + return scriptTypes.WITNESSMASTHASH; + + // Fail on bad version=1 + return scriptTypes.WITNESSMALFORMED; + } + + // No interpretation of script (anyone can spend) + return scriptTypes.NONSTANDARD; +}; + /** * Test whether the program is either * an unknown version or malformed. @@ -4824,7 +4878,18 @@ function Program(version, data) { */ Program.prototype.isUnknown = function isUnknown() { - return !this.type || this.type === 'unknown'; + var type = this.getType(); + return type === scriptTypes.WITNESSMALFORMED + || type === scriptTypes.NONSTANDARD; +}; + +/** + * Test whether the program is malformed. + * @returns {Boolean} + */ + +Program.prototype.isMalformed = function isMalformed() { + return this.getType() === scriptTypes.WITNESSMALFORMED; }; /** @@ -4836,7 +4901,7 @@ Program.prototype.inspect = function inspect() { return ''; }; @@ -4848,7 +4913,8 @@ exports = Script; exports.opcodes = constants.opcodes; exports.opcodesByVal = constants.opcodesByVal; -exports.types = constants.scriptTypes; +exports.types = scriptTypes; +exports.typesByVal = constants.scriptTypesByVal; exports.flags = constants.flags; exports.Script = Script; diff --git a/lib/bcoin/types.js b/lib/bcoin/types.js index 3bdc0827..23493cb8 100644 --- a/lib/bcoin/types.js +++ b/lib/bcoin/types.js @@ -27,10 +27,22 @@ */ /** - * Can be `pubkeyhash`, `scripthash`, `witnesspubkeyhash`, - * or `witnessscripthash`, or an address prefix - * (see {@link network.address}). - * @typedef {String|Number} AddressType + * An output script type. + * @see {module:constants.scriptTypes} + * May sometimes be a string if specified. + * @typedef {Number|String} ScriptType + * @global + */ + +/** + * A subset of {@link ScriptType}, including + * pubkeyhash, scripthash, witnesspubkeyhash, + * and witnessscripthash. This value + * specifically refers to the address prefix. + * It is a network-agnostic way of representing + * prefixes. May sometimes be a string if + * specified. + * @typedef {Number|String} AddressType * @global */ diff --git a/test/wallet-test.js b/test/wallet-test.js index 6e5773ab..2bce257f 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -6,6 +6,7 @@ var constants = bcoin.protocol.constants; var network = bcoin.protocol.network; var utils = bcoin.utils; var assert = require('assert'); +var scriptTypes = constants.scriptTypes; var FAKE_SIG = new Buffer([0,0,0,0,0,0,0,0,0]); @@ -104,10 +105,12 @@ describe('Wallet', function() { walletdb.create({ witness: witness }, function(err, w) { assert.ifError(err); + var ad = bcoin.address.fromBase58(w.getAddress('base58')); + if (witness) - assert(bcoin.address.fromBase58(w.getAddress('base58')).type === 'witnesspubkeyhash'); + assert(ad.type === scriptTypes.WITNESSPUBKEYHASH); else - assert(bcoin.address.fromBase58(w.getAddress('base58')).type === 'pubkeyhash'); + assert(ad.type === scriptTypes.PUBKEYHASH); var src = bcoin.mtx({ outputs: [{ @@ -232,7 +235,7 @@ describe('Wallet', function() { w.scriptInputs(fake, function(err) { assert.ifError(err); // Fake signature - fake.inputs[0].script.code[0] = bcoin.opcode.fromData(FAKE_SIG); + fake.inputs[0].script.set(0, FAKE_SIG); fake.inputs[0].script.compile(); // balance: 11000 @@ -609,10 +612,12 @@ describe('Wallet', function() { // Our p2sh address var addr = w1.getAddress('base58'); + var ad = bcoin.address.fromBase58(addr); + if (witness) - assert(bcoin.address.fromBase58(addr).type === 'witnessscripthash'); + assert(ad.type === scriptTypes.WITNESSSCRIPTHASH); else - assert(bcoin.address.fromBase58(addr).type === 'scripthash'); + assert(ad.type === scriptTypes.SCRIPTHASH); assert.equal(w1.getAddress('base58'), addr); assert.equal(w2.getAddress('base58'), addr);