diff --git a/lib/net/bip152.js b/lib/net/bip152.js index e015ab32..22d35e62 100644 --- a/lib/net/bip152.js +++ b/lib/net/bip152.js @@ -16,6 +16,7 @@ var constants = require('../protocol/constants'); var siphash = require('../crypto/siphash'); var AbstractBlock = require('../primitives/abstractblock'); var TX = require('../primitives/tx'); +var Headers = require('../primitives/headers'); var Block = require('../primitives/block'); /** @@ -403,6 +404,13 @@ CompactBlock.prototype.destroy = function destroy() { } }; +CompactBlock.prototype.toHeaders = function toHeaders() { + var headers = new Headers(this); + headers._hash = this._hash; + headers._valid = true; + return headers; +}; + /** * Represents a BlockTransactionsRequest (bip152): `getblocktxn` packet. * @see https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki diff --git a/lib/primitives/abstractblock.js b/lib/primitives/abstractblock.js index e0ecfc06..3dd1e67d 100644 --- a/lib/primitives/abstractblock.js +++ b/lib/primitives/abstractblock.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = AbstractBlock; - var constants = require('../protocol/constants'); var utils = require('../utils/utils'); var crypto = require('../crypto/crypto'); @@ -17,7 +15,6 @@ var VerifyResult = utils.VerifyResult; var BufferWriter = require('../utils/writer'); var time = require('../net/timedata'); var InvItem = require('./invitem'); -var Headers = require('./headers'); /** * The class which all block-like objects inherit from. @@ -262,18 +259,6 @@ AbstractBlock.prototype.toInv = function toInv() { return new InvItem(constants.inv.BLOCK, this.hash('hex')); }; -/** - * Convert the block to a headers object. - * @returns {Headers} - */ - -AbstractBlock.prototype.toHeaders = function toHeaders() { - var headers = new Headers(this); - headers._hash = this._hash; - headers._valid = true; - return headers; -}; - /* * Expose */ diff --git a/lib/primitives/address.js b/lib/primitives/address.js index 6cb1ea2f..c2516881 100644 --- a/lib/primitives/address.js +++ b/lib/primitives/address.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = Address; - var Network = require('../protocol/network'); var networks = require('../protocol/networks'); var constants = require('../protocol/constants'); @@ -17,7 +15,7 @@ var crypto = require('../crypto/crypto'); var assert = require('assert'); var BufferWriter = require('../utils/writer'); var BufferReader = require('../utils/reader'); -var Script = require('../script/script'); +var scriptTypes = constants.scriptTypes; /** * Represents an address. @@ -40,7 +38,7 @@ function Address(options) { return new Address(options); this.hash = constants.ZERO_HASH160; - this.type = Script.types.PUBKEYHASH; + this.type = scriptTypes.PUBKEYHASH; this.version = -1; this.network = Network.primary; @@ -141,15 +139,6 @@ Address.prototype.toBase58 = function toBase58(network) { return utils.toBase58(this.toRaw(network)); }; -/** - * Convert the address to an output script. - * @returns {Script} - */ - -Address.prototype.toScript = function toScript() { - return Script.fromAddress(this); -}; - /** * Convert the Address to a string. * @returns {Base58String} @@ -259,42 +248,42 @@ Address.fromBase58 = function fromBase58(address) { Address.prototype.fromScript = function fromScript(script) { if (script.isPubkey()) { this.hash = crypto.hash160(script.get(0)); - this.type = Script.types.PUBKEYHASH; + this.type = scriptTypes.PUBKEYHASH; this.version = -1; return this; } if (script.isPubkeyhash()) { this.hash = script.get(2); - this.type = Script.types.PUBKEYHASH; + this.type = scriptTypes.PUBKEYHASH; this.version = -1; return this; } if (script.isScripthash()) { this.hash = script.get(1); - this.type = Script.types.SCRIPTHASH; + this.type = scriptTypes.SCRIPTHASH; this.version = -1; return this; } if (script.isWitnessPubkeyhash()) { this.hash = script.get(1); - this.type = Script.types.WITNESSPUBKEYHASH; + this.type = scriptTypes.WITNESSPUBKEYHASH; this.version = 0; return this; } if (script.isWitnessScripthash()) { this.hash = script.get(1); - this.type = Script.types.WITNESSSCRIPTHASH; + this.type = scriptTypes.WITNESSSCRIPTHASH; this.version = 0; return this; } if (script.isWitnessMasthash()) { this.hash = script.get(1); - this.type = Script.types.WITNESSSCRIPTHASH; + this.type = scriptTypes.WITNESSSCRIPTHASH; this.version = 1; return this; } @@ -302,7 +291,7 @@ Address.prototype.fromScript = function fromScript(script) { // Put this last: it's the slowest to check. if (script.isMultisig()) { this.hash = script.hash160(); - this.type = Script.types.SCRIPTHASH; + this.type = scriptTypes.SCRIPTHASH; this.version = -1; return this; } @@ -319,14 +308,14 @@ Address.prototype.fromWitness = function fromWitness(witness) { // since we can't get the version. if (witness.isPubkeyhashInput()) { this.hash = crypto.hash160(witness.get(1)); - this.type = Script.types.WITNESSPUBKEYHASH; + this.type = scriptTypes.WITNESSPUBKEYHASH; this.version = 0; return this; } if (witness.isScripthashInput()) { this.hash = crypto.sha256(witness.get(witness.length - 1)); - this.type = Script.types.WITNESSSCRIPTHASH; + this.type = scriptTypes.WITNESSSCRIPTHASH; this.version = 0; return this; } @@ -341,14 +330,14 @@ Address.prototype.fromWitness = function fromWitness(witness) { Address.prototype.fromInputScript = function fromInputScript(script) { if (script.isPubkeyhashInput()) { this.hash = crypto.hash160(script.get(1)); - this.type = Script.types.PUBKEYHASH; + this.type = scriptTypes.PUBKEYHASH; this.version = -1; return this; } if (script.isScripthashInput()) { this.hash = crypto.hash160(script.get(script.length - 1)); - this.type = Script.types.SCRIPTHASH; + this.type = scriptTypes.SCRIPTHASH; this.version = -1; return this; } @@ -406,10 +395,10 @@ Address.prototype.fromHash = function fromHash(hash, type, version, network) { hash = new Buffer(hash, 'hex'); if (typeof type === 'string') - type = Script.types[type.toUpperCase()]; + type = scriptTypes[type.toUpperCase()]; if (type == null) - type = Script.types.PUBKEYHASH; + type = scriptTypes.PUBKEYHASH; if (version == null) version = -1; @@ -428,11 +417,11 @@ Address.prototype.fromHash = function fromHash(hash, type, version, network) { } else { assert(Address.isWitness(type), 'Wrong version (non-witness).'); assert(version >= 0 && version <= 16, 'Bad program version.'); - if (version === 0 && type === Script.types.WITNESSPUBKEYHASH) + if (version === 0 && type === scriptTypes.WITNESSPUBKEYHASH) assert(hash.length === 20, 'Hash is the wrong size.'); - else if (version === 0 && type === Script.types.WITNESSSCRIPTHASH) + else if (version === 0 && type === scriptTypes.WITNESSSCRIPTHASH) assert(hash.length === 32, 'Hash is the wrong size.'); - else if (version === 1 && type === Script.types.WITNESSSCRIPTHASH) + else if (version === 1 && type === scriptTypes.WITNESSSCRIPTHASH) assert(hash.length === 32, 'Hash is the wrong size.'); } @@ -469,9 +458,9 @@ Address.fromHash = function fromHash(hash, type, version, network) { Address.prototype.fromData = function fromData(data, type, version, network) { if (typeof type === 'string') - type = Script.types[type.toUpperCase()]; + type = scriptTypes[type.toUpperCase()]; - if (type === Script.types.WITNESSSCRIPTHASH) { + if (type === scriptTypes.WITNESSSCRIPTHASH) { if (version === 0) { assert(Buffer.isBuffer(data)); data = crypto.sha256(data); @@ -481,7 +470,7 @@ Address.prototype.fromData = function fromData(data, type, version, network) { } else { throw new Error('Cannot create from version=' + version); } - } else if (type === Script.types.WITNESSPUBKEYHASH) { + } else if (type === scriptTypes.WITNESSPUBKEYHASH) { if (version !== 0) throw new Error('Cannot create from version=' + version); assert(Buffer.isBuffer(data)); @@ -530,7 +519,7 @@ Address.validate = function validate(address, type) { } if (typeof type === 'string') - type = Script.types[type.toUpperCase()]; + type = scriptTypes[type.toUpperCase()]; if (type && address.type !== type) return false; @@ -580,13 +569,13 @@ Address.getHash = function getHash(data, enc) { Address.getPrefix = function getPrefix(type, network) { var prefixes = network.addressPrefix; switch (type) { - case Script.types.PUBKEYHASH: + case scriptTypes.PUBKEYHASH: return prefixes.pubkeyhash; - case Script.types.SCRIPTHASH: + case scriptTypes.SCRIPTHASH: return prefixes.scripthash; - case Script.types.WITNESSPUBKEYHASH: + case scriptTypes.WITNESSPUBKEYHASH: return prefixes.witnesspubkeyhash; - case Script.types.WITNESSSCRIPTHASH: + case scriptTypes.WITNESSSCRIPTHASH: return prefixes.witnessscripthash; default: return -1; @@ -604,13 +593,13 @@ Address.getType = function getType(prefix, network) { var prefixes = network.addressPrefix; switch (prefix) { case prefixes.pubkeyhash: - return Script.types.PUBKEYHASH; + return scriptTypes.PUBKEYHASH; case prefixes.scripthash: - return Script.types.SCRIPTHASH; + return scriptTypes.SCRIPTHASH; case prefixes.witnesspubkeyhash: - return Script.types.WITNESSPUBKEYHASH; + return scriptTypes.WITNESSPUBKEYHASH; case prefixes.witnessscripthash: - return Script.types.WITNESSSCRIPTHASH; + return scriptTypes.WITNESSSCRIPTHASH; default: return -1; } @@ -624,9 +613,9 @@ Address.getType = function getType(prefix, network) { Address.isWitness = function isWitness(type) { switch (type) { - case Script.types.WITNESSPUBKEYHASH: + case scriptTypes.WITNESSPUBKEYHASH: return true; - case Script.types.WITNESSSCRIPTHASH: + case scriptTypes.WITNESSSCRIPTHASH: return true; default: return false; diff --git a/lib/primitives/block.js b/lib/primitives/block.js index a2ecf145..2be9a15b 100644 --- a/lib/primitives/block.js +++ b/lib/primitives/block.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = Block; - var utils = require('../utils/utils'); var crypto = require('../crypto/crypto'); var assert = require('assert'); @@ -19,6 +17,7 @@ var BufferWriter = require('../utils/writer'); var BufferReader = require('../utils/reader'); var TX = require('./tx'); var MerkleBlock = require('./merkleblock'); +var Headers = require('./headers'); var Network = require('../protocol/network'); /** @@ -790,6 +789,18 @@ Block.prototype.frameWitness = function frameWitness(writer) { return this.frame(true, writer); }; +/** + * Convert the block to a headers object. + * @returns {Headers} + */ + +Block.prototype.toHeaders = function toHeaders() { + var headers = new Headers(this); + headers._hash = this._hash; + headers._valid = true; + return headers; +}; + /** * Test whether an object is a Block. * @param {Object} obj diff --git a/lib/primitives/coin.js b/lib/primitives/coin.js index 928b32ae..9378fdae 100644 --- a/lib/primitives/coin.js +++ b/lib/primitives/coin.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = Coin; - var utils = require('../utils/utils'); var constants = require('../protocol/constants'); var Network = require('../protocol/network'); diff --git a/lib/primitives/headers.js b/lib/primitives/headers.js index e15bbdd2..23ef030a 100644 --- a/lib/primitives/headers.js +++ b/lib/primitives/headers.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = Headers; - var utils = require('../utils/utils'); var AbstractBlock = require('./abstractblock'); var BufferWriter = require('../utils/writer'); diff --git a/lib/primitives/input.js b/lib/primitives/input.js index 7a4cb6c2..e7cdaef3 100644 --- a/lib/primitives/input.js +++ b/lib/primitives/input.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = Input; - var utils = require('../utils/utils'); var assert = require('assert'); var constants = require('../protocol/constants'); diff --git a/lib/primitives/invitem.js b/lib/primitives/invitem.js index 204e86dc..5fcdbc5e 100644 --- a/lib/primitives/invitem.js +++ b/lib/primitives/invitem.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = InvItem; - var constants = require('../protocol/constants'); var BufferWriter = require('../utils/writer'); var BufferReader = require('../utils/reader'); diff --git a/lib/primitives/keyring.js b/lib/primitives/keyring.js index 53d7f40e..8174e12b 100644 --- a/lib/primitives/keyring.js +++ b/lib/primitives/keyring.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = KeyRing; - var constants = require('../protocol/constants'); var utils = require('../utils/utils'); var crypto = require('../crypto/crypto'); diff --git a/lib/primitives/memblock.js b/lib/primitives/memblock.js index 2a95b708..9c352437 100644 --- a/lib/primitives/memblock.js +++ b/lib/primitives/memblock.js @@ -7,12 +7,11 @@ 'use strict'; -module.exports = MemBlock; - var utils = require('../utils/utils'); var AbstractBlock = require('./abstractblock'); var Block = require('./block'); var Script = require('../script/script'); +var Headers = require('./headers'); var BufferReader = require('../utils/reader'); /** @@ -194,6 +193,18 @@ MemBlock.prototype.toBlock = function toBlock() { return block; }; +/** + * Convert the block to a headers object. + * @returns {Headers} + */ + +MemBlock.prototype.toHeaders = function toHeaders() { + var headers = new Headers(this); + headers._hash = this._hash; + headers._valid = true; + return headers; +}; + /** * Test whether an object is a MemBlock. * @param {Object} obj diff --git a/lib/primitives/merkleblock.js b/lib/primitives/merkleblock.js index eefda81a..3a29e0db 100644 --- a/lib/primitives/merkleblock.js +++ b/lib/primitives/merkleblock.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = MerkleBlock; - var utils = require('../utils/utils'); var crypto = require('../crypto/crypto'); var assert = require('assert'); @@ -18,6 +16,7 @@ var AbstractBlock = require('./abstractblock'); var VerifyResult = utils.VerifyResult; var BufferWriter = require('../utils/writer'); var BufferReader = require('../utils/reader'); +var Headers = require('./headers'); var TX = require('./tx'); /** @@ -639,6 +638,18 @@ MerkleBlock.isMerkleBlock = function isMerkleBlock(obj) { && typeof obj.verifyPartial === 'function'; }; +/** + * Convert the block to a headers object. + * @returns {Headers} + */ + +MerkleBlock.prototype.toHeaders = function toHeaders() { + var headers = new Headers(this); + headers._hash = this._hash; + headers._valid = true; + return headers; +}; + /* * Helpers */ diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js index b85966b9..cef6c223 100644 --- a/lib/primitives/mtx.js +++ b/lib/primitives/mtx.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = MTX; - var utils = require('../utils/utils'); var assert = require('assert'); var constants = require('../protocol/constants'); diff --git a/lib/primitives/netaddress.js b/lib/primitives/netaddress.js index 11a01d02..5ed15f65 100644 --- a/lib/primitives/netaddress.js +++ b/lib/primitives/netaddress.js @@ -6,8 +6,6 @@ 'use strict'; -module.exports = NetworkAddress; - var constants = require('../protocol/constants'); var Network = require('../protocol/network'); var time = require('../net/timedata'); diff --git a/lib/primitives/outpoint.js b/lib/primitives/outpoint.js index ce17d441..92b192f4 100644 --- a/lib/primitives/outpoint.js +++ b/lib/primitives/outpoint.js @@ -6,8 +6,6 @@ 'use strict'; -module.exports = Outpoint; - var utils = require('../utils/utils'); var assert = require('assert'); var constants = require('../protocol/constants'); diff --git a/lib/primitives/output.js b/lib/primitives/output.js index 8172297e..7856043c 100644 --- a/lib/primitives/output.js +++ b/lib/primitives/output.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = Output; - var utils = require('../utils/utils'); var constants = require('../protocol/constants'); var Network = require('../protocol/network'); @@ -16,7 +14,6 @@ var Script = require('../script/script'); var BufferWriter = require('../utils/writer'); var BufferReader = require('../utils/reader'); var assert = require('assert'); -var TX = require('./tx'); /** * Represents a transaction output. @@ -156,7 +153,7 @@ Output.prototype.toJSON = function toJSON(network) { Output.prototype.getDustThreshold = function getDustThreshold(rate) { var scale = constants.WITNESS_SCALE_FACTOR; - var size; + var size, fee; if (rate == null) rate = constants.tx.MIN_RELAY; @@ -173,7 +170,12 @@ Output.prototype.getDustThreshold = function getDustThreshold(rate) { size += 32 + 4 + 1 + 107 + 4; } - return 3 * TX.getMinFee(size, rate); + fee = Math.floor(rate * size / 1000); + + if (fee === 0 && rate > 0) + fee = rate; + + return 3 * fee; }; /** diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index f6c6f23c..6a00d710 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = TX; - var utils = require('../utils/utils'); var crypto = require('../crypto/crypto'); var assert = require('assert'); @@ -1466,10 +1464,7 @@ TX.prototype.hasStandardInputs = function hasStandardInputs() { if (stack.length === 0) return false; - redeem = stack.getRedeem(); - - if (!redeem) - return false; + redeem = Script.fromRaw(stack.top(-1)); if (redeem.getSigops(true) > maxSigops) return false; diff --git a/lib/script/encoding.js b/lib/script/encoding.js new file mode 100644 index 00000000..52826c8b --- /dev/null +++ b/lib/script/encoding.js @@ -0,0 +1,468 @@ +/*! + * encoding.js - script-related encoding for bcoin + * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var BN = require('bn.js'); +var constants = require('../protocol/constants'); +var utils = require('../utils/utils'); +var assert = require('assert'); +var opcodes = constants.opcodes; +var STACK_FALSE = new Buffer(0); +var ScriptError = require('../utils/errors').ScriptError; + +/** + * Test whether the data element is a ripemd160 hash. + * @param {Buffer?} hash + * @returns {Boolean} + */ + +exports.isHash = function isHash(hash) { + return Buffer.isBuffer(hash) && hash.length === 20; +}; + +/** + * Test whether the data element is a public key. Note that + * this does not verify the format of the key, only the length. + * @param {Buffer?} key + * @returns {Boolean} + */ + +exports.isKey = function isKey(key) { + return Buffer.isBuffer(key) && key.length >= 33 && key.length <= 65; +}; + +/** + * Test whether the data element is a signature. Note that + * this does not verify the format of the signature, only the length. + * @param {Buffer?} sig + * @returns {Boolean} + */ + +exports.isSignature = function isSignature(sig) { + return Buffer.isBuffer(sig) && sig.length >= 9 && sig.length <= 73; +}; + +/** + * Test whether the data element is a null dummy (a zero-length array). + * @param {Buffer?} data + * @returns {Boolean} + */ + +exports.isDummy = function isDummy(data) { + return Buffer.isBuffer(data) && data.length === 0; +}; + +/** + * Test whether the data element is a compressed key. + * @param {Buffer} key + * @returns {Boolean} + */ + +exports.isCompressedEncoding = function isCompressedEncoding(key) { + assert(Buffer.isBuffer(key)); + + if (key.length !== 33) + return false; + + if (key[0] !== 0x02 && key[0] !== 0x03) + return false; + + return true; +}; + +/** + * Test whether the data element is a valid key. + * @param {Buffer} key + * @returns {Boolean} + */ + +exports.isKeyEncoding = function isKeyEncoding(key) { + assert(Buffer.isBuffer(key)); + + if (key.length < 33) + return false; + + if (key[0] === 0x04) { + if (key.length !== 65) + return false; + } else if (key[0] === 0x02 || key[0] === 0x03) { + if (key.length !== 33) + return false; + } else { + return false; + } + + return true; +}; + +/** + * Test a signature to see if it abides by BIP66. + * @see https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki + * @param {Buffer} sig + * @returns {Boolean} + */ + +exports.isSignatureEncoding = function isSignatureEncoding(sig) { + var lenR, lenS; + + assert(Buffer.isBuffer(sig)); + + // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] + // * total-length: 1-byte length descriptor of everything that follows, + // excluding the sighash byte. + // * R-length: 1-byte length descriptor of the R value that follows. + // * R: arbitrary-length big-endian encoded R value. It must use the shortest + // possible encoding for a positive integers (which means no null bytes at + // the start, except a single one when the next byte has its highest bit set). + // * S-length: 1-byte length descriptor of the S value that follows. + // * S: arbitrary-length big-endian encoded S value. The same rules apply. + // * sighash: 1-byte value indicating what data is hashed (not part of the DER + // signature) + + // Minimum and maximum size constraints. + if (sig.length < 9) + return false; + + if (sig.length > 73) + return false; + + // A signature is of type 0x30 (compound). + if (sig[0] !== 0x30) + return false; + + // Make sure the length covers the entire signature. + if (sig[1] !== sig.length - 3) + return false; + + // Extract the length of the R element. + lenR = sig[3]; + + // Make sure the length of the S element is still inside the signature. + if (5 + lenR >= sig.length) + return false; + + // Extract the length of the S element. + lenS = sig[5 + lenR]; + + // Verify that the length of the signature matches the sum of the length + // of the elements. + if (lenR + lenS + 7 !== sig.length) + return false; + + // Check whether the R element is an integer. + if (sig[2] !== 0x02) + return false; + + // Zero-length integers are not allowed for R. + if (lenR === 0) + return false; + + // Negative numbers are not allowed for R. + if (sig[4] & 0x80) + return false; + + // Null bytes at the start of R are not allowed, unless R would + // otherwise be interpreted as a negative number. + if (lenR > 1 && (sig[4] === 0x00) && !(sig[5] & 0x80)) + return false; + + // Check whether the S element is an integer. + if (sig[lenR + 4] !== 0x02) + return false; + + // Zero-length integers are not allowed for S. + if (lenS === 0) + return false; + + // Negative numbers are not allowed for S. + if (sig[lenR + 6] & 0x80) + return false; + + // Null bytes at the start of S are not allowed, unless S would otherwise be + // interpreted as a negative number. + if (lenS > 1 && (sig[lenR + 6] === 0x00) && !(sig[lenR + 7] & 0x80)) + return false; + + return true; +}; + +/** + * Format script code into a human readable-string. + * @param {Array} code + * @returns {String} Human-readable string. + */ + +exports.formatStack = function formatStack(items) { + var out = []; + var i; + + for (i = 0; i < items.length; i++) + out.push(items[i].toString('hex')); + + return out.join(' '); +}; + +/** + * Format script code into a human readable-string. + * @param {Array} code + * @returns {String} Human-readable string. + */ + +exports.formatCode = function formatCode(code) { + var out = []; + var i, op, data, value, size; + + for (i = 0; i < code.length; i++) { + op = code[i]; + data = op.data; + value = op.value; + + if (data) { + size = data.length.toString(16); + + while (size.length % 2 !== 0) + size = '0' + size; + + if (!constants.opcodesByVal[value]) { + value = value.toString(16); + if (value.length < 2) + value = '0' + value; + value = '0x' + value + ' 0x' + data.toString('hex'); + out.push(value); + continue; + } + + value = constants.opcodesByVal[value]; + value = value + ' 0x' + size + ' 0x' + data.toString('hex'); + out.push(value); + continue; + } + + assert(typeof value === 'number'); + + if (constants.opcodesByVal[value]) { + value = constants.opcodesByVal[value]; + out.push(value); + continue; + } + + if (value === -1) { + out.push('OP_INVALIDOPCODE'); + break; + } + + value = value.toString(16); + + if (value.length < 2) + value = '0' + value; + + value = '0x' + value; + out.push(value); + } + + return out.join(' '); +}; + +/** + * Format script code into bitcoind asm format. + * @param {Array} code + * @param {Boolean?} decode - Attempt to decode hash types. + * @returns {String} Human-readable string. + */ + +exports.formatItem = function formatItem(data, decode) { + var symbol, type; + + if (data.length <= 4) { + data = exports.num(data, constants.flags.VERIFY_NONE); + return data.toString(10); + } + + if (decode) { + symbol = ''; + if (exports.isSignatureEncoding(data)) { + type = data[data.length - 1]; + symbol = constants.hashTypeByVal[type & 0x1f] || ''; + if (symbol) { + if (type & constants.hashType.ANYONECANPAY) + symbol += '|ANYONECANPAY'; + symbol = '[' + symbol + ']'; + } + data = data.slice(0, -1); + } + return data.toString('hex') + symbol; + } + + return data.toString('hex'); +}; + +/** + * Format script code into bitcoind asm format. + * @param {Array} code + * @param {Boolean?} decode - Attempt to decode hash types. + * @returns {String} Human-readable string. + */ + +exports.formatASM = function formatASM(code, decode) { + var out = []; + var i, op, data, value; + + if (code.length > 0 && code[0].value === opcodes.OP_RETURN) + decode = false; + + for (i = 0; i < code.length; i++) { + op = code[i]; + data = op.data; + value = op.value; + + if (value === -1) { + out.push('[error]'); + break; + } + + if (data) { + data = exports.formatItem(data, decode); + out.push(data); + continue; + } + + value = constants.opcodesByVal[value] || 'OP_UNKNOWN'; + + out.push(value); + } + + return out.join(' '); +}; + +/** + * Format script code into bitcoind asm format. + * @param {Array} code + * @param {Boolean?} decode - Attempt to decode hash types. + * @returns {String} Human-readable string. + */ + +exports.formatStackASM = function formatStackASM(items, decode) { + var out = []; + var i, item, data; + + for (i = 0; i < items.length; i++) { + item = items[i]; + data = exports.formatItem(item, decode); + out.push(data); + } + + return out.join(' '); +}; + +/** + * Create a CScriptNum. + * @param {Buffer} value + * @param {Number?} flags - Script standard flags. + * @param {Number?} size - Max size in bytes. + * @returns {BN} + * @throws {ScriptError} + */ + +exports.num = function num(value, flags, size) { + var result; + + assert(Buffer.isBuffer(value)); + + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; + + if (size == null) + size = 4; + + if (value.length > size) + throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); + + if ((flags & constants.flags.VERIFY_MINIMALDATA) && value.length > 0) { + // If the low bits on the last byte are unset, + // fail if the value's second to last byte does + // not have the high bit set. A number can't + // justify having the last byte's low bits unset + // unless they ran out of space for the sign bit + // in the second to last bit. We also fail on [0] + // to avoid negative zero (also avoids positive + // zero). + if (!(value[value.length - 1] & 0x7f)) { + if (value.length === 1 || !(value[value.length - 2] & 0x80)) { + throw new ScriptError( + 'UNKNOWN_ERROR', + 'Non-minimally encoded Script number.'); + } + } + } + + if (value.length === 0) + return new BN(0); + + result = new BN(value, 'le'); + + // If the input vector's most significant byte is + // 0x80, remove it from the result's msb and return + // a negative. + // Equivalent to: + // -(result & ~(0x80 << (8 * (value.length - 1)))) + if (value[value.length - 1] & 0x80) + result.setn((value.length * 8) - 1, 0).ineg(); + + return result; +}; + +/** + * Create a script array. Will convert Numbers and big + * numbers to a little-endian buffer while taking into + * account negative zero, minimaldata, etc. + * @example + * assert.deepEqual(Script.array(0), new Buffer(0)); + * assert.deepEqual(Script.array(0xffee), new Buffer('eeff00', 'hex')); + * assert.deepEqual(Script.array(new BN(0xffee)), new Buffer('eeff00', 'hex')); + * assert.deepEqual(Script.array(new BN(0x1e).ineg()), new Buffer('9e', 'hex')); + * @param {Number|BN} value + * @returns {Buffer} + */ + +exports.array = function(value) { + var neg, result; + + if (utils.isNumber(value)) + value = new BN(value); + + assert(BN.isBN(value)); + + if (value.cmpn(0) === 0) + return STACK_FALSE; + + // If the most significant byte is >= 0x80 + // and the value is positive, push a new + // zero-byte to make the significant + // byte < 0x80 again. + + // If the most significant byte is >= 0x80 + // and the value is negative, push a new + // 0x80 byte that will be popped off when + // converting to an integral. + + // If the most significant byte is < 0x80 + // and the value is negative, add 0x80 to + // it, since it will be subtracted and + // interpreted as a negative when + // converting to an integral. + + neg = value.cmpn(0) < 0; + result = value.toArray('le'); + + if (result[result.length - 1] & 0x80) + result.push(neg ? 0x80 : 0); + else if (neg) + result[result.length - 1] |= 0x80; + + return new Buffer(result); +}; diff --git a/lib/script/opcode.js b/lib/script/opcode.js index b3ce76e7..20c8f940 100644 --- a/lib/script/opcode.js +++ b/lib/script/opcode.js @@ -7,12 +7,11 @@ 'use strict'; -module.exports = Opcode; - var BN = require('bn.js'); var constants = require('../protocol/constants'); var utils = require('../utils/utils'); -var Script = require('./script'); +var encoding = require('./encoding'); +var BufferWriter = require('../utils/writer'); var assert = require('assert'); var opcodes = constants.opcodes; @@ -40,8 +39,36 @@ function Opcode(value, data) { * @returns {Buffer} */ -Opcode.prototype.toRaw = function toRaw(writer) { - return Script.encode([this], writer); +Opcode.prototype.toRaw = function toRaw() { + var p = new BufferWriter(); + + if (this.value === -1) + throw new Error('Cannot reserialize a parse error.'); + + if (this.data) { + if (this.value <= 0x4b) { + p.writeU8(this.data.length); + p.writeBytes(this.data); + } else if (this.value === opcodes.OP_PUSHDATA1) { + p.writeU8(opcodes.OP_PUSHDATA1); + p.writeU8(this.data.length); + p.writeBytes(this.data); + } else if (this.value === opcodes.OP_PUSHDATA2) { + p.writeU8(opcodes.OP_PUSHDATA2); + p.writeU16(this.data.length); + p.writeBytes(this.data); + } else if (this.value === opcodes.OP_PUSHDATA4) { + p.writeU8(opcodes.OP_PUSHDATA4); + p.writeU32(this.data.length); + p.writeBytes(this.data); + } else { + throw new Error('Unknown pushdata opcode.'); + } + } else { + p.writeU8(this.value); + } + + return p.render(); }; /** @@ -107,7 +134,7 @@ Opcode.fromPush = function fromPush(data) { */ Opcode.fromNumber = function fromNumber(num) { - return Opcode.fromData(Script.array(num)); + return Opcode.fromData(encoding.array(num)); }; /** diff --git a/lib/script/program.js b/lib/script/program.js index b69ba8d1..0ac3c3eb 100644 --- a/lib/script/program.js +++ b/lib/script/program.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = Program; - var constants = require('../protocol/constants'); var utils = require('../utils/utils'); var assert = require('assert'); diff --git a/lib/script/script.js b/lib/script/script.js index a5729872..adb82111 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = Script; - var BN = require('bn.js'); var constants = require('../protocol/constants'); var utils = require('../utils/utils'); @@ -26,6 +24,7 @@ var Program = require('./program'); var Opcode = require('./opcode'); var Stack = require('./stack'); var SigCache = require('./sigcache'); +var encoding = require('./encoding'); var ec = require('../crypto/ec'); var Address = require('../primitives/address'); @@ -1332,51 +1331,7 @@ Script.bool = function bool(value) { */ Script.num = function num(value, flags, size) { - var result; - - assert(Buffer.isBuffer(value)); - - if (flags == null) - flags = constants.flags.STANDARD_VERIFY_FLAGS; - - if (size == null) - size = 4; - - if (value.length > size) - throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); - - if ((flags & constants.flags.VERIFY_MINIMALDATA) && value.length > 0) { - // If the low bits on the last byte are unset, - // fail if the value's second to last byte does - // not have the high bit set. A number can't - // justify having the last byte's low bits unset - // unless they ran out of space for the sign bit - // in the second to last bit. We also fail on [0] - // to avoid negative zero (also avoids positive - // zero). - if (!(value[value.length - 1] & 0x7f)) { - if (value.length === 1 || !(value[value.length - 2] & 0x80)) { - throw new ScriptError( - 'UNKNOWN_ERROR', - 'Non-minimally encoded Script number.'); - } - } - } - - if (value.length === 0) - return new BN(0); - - result = new BN(value, 'le'); - - // If the input vector's most significant byte is - // 0x80, remove it from the result's msb and return - // a negative. - // Equivalent to: - // -(result & ~(0x80 << (8 * (value.length - 1)))) - if (value[value.length - 1] & 0x80) - result.setn((value.length * 8) - 1, 0).ineg(); - - return result; + return encoding.num(value, flags, size); }; /** @@ -1393,41 +1348,7 @@ Script.num = function num(value, flags, size) { */ Script.array = function(value) { - var neg, result; - - if (utils.isNumber(value)) - value = new BN(value); - - assert(BN.isBN(value)); - - if (value.cmpn(0) === 0) - return STACK_FALSE; - - // If the most significant byte is >= 0x80 - // and the value is positive, push a new - // zero-byte to make the significant - // byte < 0x80 again. - - // If the most significant byte is >= 0x80 - // and the value is negative, push a new - // 0x80 byte that will be popped off when - // converting to an integral. - - // If the most significant byte is < 0x80 - // and the value is negative, add 0x80 to - // it, since it will be subtracted and - // interpreted as a negative when - // converting to an integral. - - neg = value.cmpn(0) < 0; - result = value.toArray('le'); - - if (result[result.length - 1] & 0x80) - result.push(neg ? 0x80 : 0); - else if (neg) - result[result.length - 1] |= 0x80; - - return new Buffer(result); + return encoding.array(value); }; /** @@ -2690,7 +2611,7 @@ Script.prototype.set = function set(i, data) { */ Script.isHash = function isHash(hash) { - return Buffer.isBuffer(hash) && hash.length === 20; + return encoding.isHash(hash); }; /** @@ -2701,7 +2622,7 @@ Script.isHash = function isHash(hash) { */ Script.isKey = function isKey(key) { - return Buffer.isBuffer(key) && key.length >= 33 && key.length <= 65; + return encoding.isKey(key); }; /** @@ -2712,7 +2633,7 @@ Script.isKey = function isKey(key) { */ Script.isSignature = function isSignature(sig) { - return Buffer.isBuffer(sig) && sig.length >= 9 && sig.length <= 73; + return encoding.isSignature(sig); }; /** @@ -2722,7 +2643,7 @@ Script.isSignature = function isSignature(sig) { */ Script.isDummy = function isDummy(data) { - return Buffer.isBuffer(data) && data.length === 0; + return encoding.isDummy(data); }; /** @@ -2761,15 +2682,7 @@ Script.validateKey = function validateKey(key, flags, version) { */ Script.isCompressedEncoding = function isCompressedEncoding(key) { - assert(Buffer.isBuffer(key)); - - if (key.length !== 33) - return false; - - if (key[0] !== 0x02 && key[0] !== 0x03) - return false; - - return true; + return encoding.isCompressedEncoding(key); }; /** @@ -2779,22 +2692,7 @@ Script.isCompressedEncoding = function isCompressedEncoding(key) { */ Script.isKeyEncoding = function isKeyEncoding(key) { - assert(Buffer.isBuffer(key)); - - if (key.length < 33) - return false; - - if (key[0] === 0x04) { - if (key.length !== 65) - return false; - } else if (key[0] === 0x02 || key[0] === 0x03) { - if (key.length !== 33) - return false; - } else { - return false; - } - - return true; + return encoding.isKeyEncoding(key); }; /** @@ -2847,87 +2745,7 @@ Script.validateSignature = function validateSignature(sig, flags) { */ Script.isSignatureEncoding = function isSignatureEncoding(sig) { - var lenR, lenS; - - assert(Buffer.isBuffer(sig)); - - // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] - // * total-length: 1-byte length descriptor of everything that follows, - // excluding the sighash byte. - // * R-length: 1-byte length descriptor of the R value that follows. - // * R: arbitrary-length big-endian encoded R value. It must use the shortest - // possible encoding for a positive integers (which means no null bytes at - // the start, except a single one when the next byte has its highest bit set). - // * S-length: 1-byte length descriptor of the S value that follows. - // * S: arbitrary-length big-endian encoded S value. The same rules apply. - // * sighash: 1-byte value indicating what data is hashed (not part of the DER - // signature) - - // Minimum and maximum size constraints. - if (sig.length < 9) - return false; - - if (sig.length > 73) - return false; - - // A signature is of type 0x30 (compound). - if (sig[0] !== 0x30) - return false; - - // Make sure the length covers the entire signature. - if (sig[1] !== sig.length - 3) - return false; - - // Extract the length of the R element. - lenR = sig[3]; - - // Make sure the length of the S element is still inside the signature. - if (5 + lenR >= sig.length) - return false; - - // Extract the length of the S element. - lenS = sig[5 + lenR]; - - // Verify that the length of the signature matches the sum of the length - // of the elements. - if (lenR + lenS + 7 !== sig.length) - return false; - - // Check whether the R element is an integer. - if (sig[2] !== 0x02) - return false; - - // Zero-length integers are not allowed for R. - if (lenR === 0) - return false; - - // Negative numbers are not allowed for R. - if (sig[4] & 0x80) - return false; - - // Null bytes at the start of R are not allowed, unless R would - // otherwise be interpreted as a negative number. - if (lenR > 1 && (sig[4] === 0x00) && !(sig[5] & 0x80)) - return false; - - // Check whether the S element is an integer. - if (sig[lenR + 4] !== 0x02) - return false; - - // Zero-length integers are not allowed for S. - if (lenS === 0) - return false; - - // Negative numbers are not allowed for S. - if (sig[lenR + 6] & 0x80) - return false; - - // Null bytes at the start of S are not allowed, unless S would otherwise be - // interpreted as a negative number. - if (lenS > 1 && (sig[lenR + 6] === 0x00) && !(sig[lenR + 7] & 0x80)) - return false; - - return true; + return encoding.isSignatureEncoding(sig); }; /** @@ -2972,58 +2790,7 @@ Script.isLowDER = function isLowDER(sig) { */ Script.format = function format(code) { - var out = []; - var i, op, data, value, size; - - for (i = 0; i < code.length; i++) { - op = code[i]; - data = op.data; - value = op.value; - - if (data) { - size = data.length.toString(16); - - while (size.length % 2 !== 0) - size = '0' + size; - - if (!constants.opcodesByVal[value]) { - value = value.toString(16); - if (value.length < 2) - value = '0' + value; - value = '0x' + value + ' 0x' + data.toString('hex'); - out.push(value); - continue; - } - - value = constants.opcodesByVal[value]; - value = value + ' 0x' + size + ' 0x' + data.toString('hex'); - out.push(value); - continue; - } - - assert(typeof value === 'number'); - - if (constants.opcodesByVal[value]) { - value = constants.opcodesByVal[value]; - out.push(value); - continue; - } - - if (value === -1) { - out.push('OP_INVALIDOPCODE'); - break; - } - - value = value.toString(16); - - if (value.length < 2) - value = '0' + value; - - value = '0x' + value; - out.push(value); - } - - return out.join(' '); + return encoding.formatCode(code); }; /** @@ -3034,52 +2801,7 @@ Script.format = function format(code) { */ Script.formatASM = function formatASM(code, decode) { - var out = []; - var i, op, type, symbol, data, value; - - for (i = 0; i < code.length; i++) { - op = code[i]; - data = op.data; - value = op.value; - - if (value === -1) { - out.push('[error]'); - break; - } - - if (data) { - if (data.length <= 4) { - data = Script.num(data, constants.flags.VERIFY_NONE); - out.push(data.toString(10)); - continue; - } - - if (decode && code[0] !== opcodes.OP_RETURN) { - symbol = ''; - if (Script.isSignatureEncoding(data)) { - type = data[data.length - 1]; - symbol = constants.hashTypeByVal[type & 0x1f] || ''; - if (symbol) { - if (type & constants.hashType.ANYONECANPAY) - symbol += '|ANYONECANPAY'; - symbol = '[' + symbol + ']'; - } - data = data.slice(0, -1); - } - out.push(data.toString('hex') + symbol); - continue; - } - - out.push(data.toString('hex')); - continue; - } - - value = constants.opcodesByVal[value] || 'OP_UNKNOWN'; - - out.push(value); - } - - return out.join(' '); + return encoding.formatASM(code, decode); }; /** diff --git a/lib/script/stack.js b/lib/script/stack.js index a7b56b7d..f54b7f79 100644 --- a/lib/script/stack.js +++ b/lib/script/stack.js @@ -7,10 +7,7 @@ 'use strict'; -module.exports = Stack; - -var Script = require('./script'); -var Witness = require('./witness'); +var encoding = require('./encoding'); /** * Represents the stack of a Script during execution. @@ -51,7 +48,7 @@ Stack.prototype.inspect = function inspect() { */ Stack.prototype.toString = function toString() { - return Witness.format(this.items); + return encoding.formatStack(this.items); }; /** @@ -61,19 +58,7 @@ Stack.prototype.toString = function toString() { */ Stack.prototype.toASM = function toASM(decode) { - return Script.formatASM(this.items, decode); -}; - -/** - * Pop the redeem script off the stack and deserialize it. - * @returns {Script|null} The redeem script. - */ - -Stack.prototype.getRedeem = function getRedeem() { - var redeem = this.items[this.items.length - 1]; - if (!redeem) - return; - return new Script(redeem); + return encoding.formatStackASM(this.items, decode); }; /** diff --git a/lib/script/witness.js b/lib/script/witness.js index 2ca39de0..d29fe09c 100644 --- a/lib/script/witness.js +++ b/lib/script/witness.js @@ -7,8 +7,6 @@ 'use strict'; -module.exports = Witness; - var BN = require('bn.js'); var constants = require('../protocol/constants'); var utils = require('../utils/utils'); @@ -18,6 +16,7 @@ var STACK_FALSE = new Buffer(0); var STACK_NEGATE = new Buffer([0x81]); var scriptTypes = constants.scriptTypes; var Script = require('./script'); +var encoding = require('./encoding'); var Opcode = require('./opcode'); var BufferWriter = require('../utils/writer'); var BufferReader = require('../utils/reader'); @@ -131,7 +130,7 @@ Witness.prototype.inspect = function inspect() { */ Witness.prototype.toString = function toString() { - return Witness.format(this.items); + return encoding.formatStack(this.items); }; /** @@ -141,7 +140,7 @@ Witness.prototype.toString = function toString() { */ Witness.prototype.toASM = function toASM(decode) { - return Script.formatASM(Script.parseArray(this.items), decode); + return encoding.formatStackASM(this.items, decode); }; /** @@ -211,8 +210,8 @@ Witness.prototype.isPubkeyInput = function isPubkeyInput() { Witness.prototype.isPubkeyhashInput = function isPubkeyhashInput() { return this.items.length === 2 - && Script.isSignatureEncoding(this.items[0]) - && Script.isKeyEncoding(this.items[1]); + && encoding.isSignatureEncoding(this.items[0]) + && encoding.isKeyEncoding(this.items[1]); }; /** @@ -446,7 +445,7 @@ Witness.prototype.getNumber = function getNumber(i) { var item = this.items[i]; if (!item || item.length > 5) return; - return Script.num(item, constants.flags.VERIFY_NONE, 5); + return encoding.num(item, constants.flags.VERIFY_NONE, 5); }; /** @@ -505,7 +504,7 @@ Witness.encodeItem = function encodeItem(data) { } if (BN.isBN(data)) - return Script.array(data); + return encoding.array(data); if (typeof data === 'string') return new Buffer(data, 'utf8'); @@ -583,22 +582,6 @@ Witness.fromString = function fromString(items) { return new Witness().fromString(items); }; -/** - * Format script code into a human readable-string. - * @param {Array} code - * @returns {String} Human-readable string. - */ - -Witness.format = function format(items) { - var out = []; - var i; - - for (i = 0; i < items.length; i++) - out.push(items[i].toString('hex')); - - return out.join(' '); -}; - /** * Test an object to see if it is a Witness. * @param {Object} obj