From 280d5d38e59661466358a26db044dc0b8b317ffa Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 15 Nov 2017 21:23:47 -0800 Subject: [PATCH] net/miner/hd: classify. --- lib/hd/mnemonic.js | 1042 +++---- lib/hd/private.js | 1294 ++++----- lib/hd/public.js | 963 +++---- lib/mempool/mempoolentry.js | 677 ++--- lib/mining/template.js | 1389 ++++----- lib/net/packets.js | 5402 ++++++++++++++++++----------------- 6 files changed, 5434 insertions(+), 5333 deletions(-) diff --git a/lib/hd/mnemonic.js b/lib/hd/mnemonic.js index bb708cfb..fbb97fb0 100644 --- a/lib/hd/mnemonic.js +++ b/lib/hd/mnemonic.js @@ -28,32 +28,521 @@ const wordlistCache = Object.create(null); /** * HD Mnemonic * @alias module:hd.Mnemonic - * @constructor - * @param {Object} options - * @param {Number?} options.bit - Bits of entropy (Must - * be a multiple of 8) (default=128). - * @param {Buffer?} options.entropy - Entropy bytes. Will - * be generated with `options.bits` bits of entropy - * if not present. - * @param {String?} options.phrase - Mnemonic phrase (will - * be generated if not present). - * @param {String?} options.passphrase - Optional salt for - * key stretching (empty string if not present). - * @param {String?} options.language - Language. */ -function Mnemonic(options) { - if (!(this instanceof Mnemonic)) - return new Mnemonic(options); +class Mnemonic { + /** + * Create a mnemonic. + * @constructor + * @param {Object} options + * @param {Number?} options.bit - Bits of entropy (Must + * be a multiple of 8) (default=128). + * @param {Buffer?} options.entropy - Entropy bytes. Will + * be generated with `options.bits` bits of entropy + * if not present. + * @param {String?} options.phrase - Mnemonic phrase (will + * be generated if not present). + * @param {String?} options.passphrase - Optional salt for + * key stretching (empty string if not present). + * @param {String?} options.language - Language. + */ - this.bits = common.MIN_ENTROPY; - this.language = 'english'; - this.entropy = null; - this.phrase = null; - this.passphrase = ''; + constructor(options) { + this.bits = common.MIN_ENTROPY; + this.language = 'english'; + this.entropy = null; + this.phrase = null; + this.passphrase = ''; - if (options) - this.fromOptions(options); + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + if (typeof options === 'string') + options = { phrase: options }; + + if (options.bits != null) { + assert((options.bits & 0xffff) === options.bits); + assert(options.bits >= common.MIN_ENTROPY); + assert(options.bits <= common.MAX_ENTROPY); + assert(options.bits % 32 === 0); + this.bits = options.bits; + } + + if (options.language) { + assert(typeof options.language === 'string'); + assert(Mnemonic.languages.indexOf(options.language) !== -1); + this.language = options.language; + } + + if (options.passphrase) { + assert(typeof options.passphrase === 'string'); + this.passphrase = options.passphrase; + } + + if (options.phrase) { + this.fromPhrase(options.phrase); + return this; + } + + if (options.entropy) { + this.fromEntropy(options.entropy); + return this; + } + + return this; + } + + /** + * Instantiate mnemonic from options. + * @param {Object} options + * @returns {Mnemonic} + */ + + static fromOptions(options) { + return new this().fromOptions(options); + } + + /** + * Destroy the mnemonic (zeroes entropy). + */ + + destroy() { + this.bits = common.MIN_ENTROPY; + this.language = 'english'; + if (this.entropy) { + cleanse(this.entropy); + this.entropy = null; + } + this.phrase = null; + this.passphrase = ''; + } + + /** + * Generate the seed. + * @param {String?} passphrase + * @returns {Buffer} pbkdf2 seed. + */ + + toSeed(passphrase) { + if (!passphrase) + passphrase = this.passphrase; + + this.passphrase = passphrase; + + const phrase = nfkd(this.getPhrase()); + const passwd = nfkd('mnemonic' + passphrase); + + return pbkdf2.derive(sha512, + Buffer.from(phrase, 'utf8'), + Buffer.from(passwd, 'utf8'), + 2048, 64); + } + + /** + * Get or generate entropy. + * @returns {Buffer} + */ + + getEntropy() { + if (!this.entropy) + this.entropy = random.randomBytes(this.bits / 8); + + assert(this.bits / 8 === this.entropy.length); + + return this.entropy; + } + + /** + * Generate a mnemonic phrase from chosen language. + * @returns {String} + */ + + getPhrase() { + if (this.phrase) + return this.phrase; + + // Include the first `ENT / 32` bits + // of the hash (the checksum). + const wbits = this.bits + (this.bits / 32); + + // Get entropy and checksum. + const entropy = this.getEntropy(); + const chk = sha256.digest(entropy); + + // Append the hash to the entropy to + // make things easy when grabbing + // the checksum bits. + const size = Math.ceil(wbits / 8); + const data = Buffer.allocUnsafe(size); + entropy.copy(data, 0); + chk.copy(data, entropy.length); + + // Build the mnemonic by reading + // 11 bit indexes from the entropy. + const list = Mnemonic.getWordlist(this.language); + + let phrase = []; + for (let i = 0; i < wbits / 11; i++) { + let index = 0; + for (let j = 0; j < 11; j++) { + const pos = i * 11 + j; + const bit = pos % 8; + const oct = (pos - bit) / 8; + index <<= 1; + index |= (data[oct] >>> (7 - bit)) & 1; + } + phrase.push(list.words[index]); + } + + // Japanese likes double-width spaces. + if (this.language === 'japanese') + phrase = phrase.join('\u3000'); + else + phrase = phrase.join(' '); + + this.phrase = phrase; + + return phrase; + } + + /** + * Inject properties from phrase. + * @private + * @param {String} phrase + */ + + fromPhrase(phrase) { + assert(typeof phrase === 'string'); + assert(phrase.length <= 1000); + + const words = phrase.trim().split(/[\s\u3000]+/); + const wbits = words.length * 11; + const cbits = wbits % 32; + + assert(cbits !== 0, 'Invalid checksum.'); + + const bits = wbits - cbits; + + assert(bits >= common.MIN_ENTROPY); + assert(bits <= common.MAX_ENTROPY); + assert(bits % 32 === 0); + + const size = Math.ceil(wbits / 8); + const data = Buffer.allocUnsafe(size); + data.fill(0); + + const lang = Mnemonic.getLanguage(words[0]); + const list = Mnemonic.getWordlist(lang); + + // Rebuild entropy bytes. + for (let i = 0; i < words.length; i++) { + const word = words[i]; + const index = list.map[word]; + + if (index == null) + throw new Error('Could not find word.'); + + for (let j = 0; j < 11; j++) { + const pos = i * 11 + j; + const bit = pos % 8; + const oct = (pos - bit) / 8; + const val = (index >>> (10 - j)) & 1; + data[oct] |= val << (7 - bit); + } + } + + const cbytes = Math.ceil(cbits / 8); + const entropy = data.slice(0, data.length - cbytes); + const chk1 = data.slice(data.length - cbytes); + const chk2 = sha256.digest(entropy); + + // Verify checksum. + for (let i = 0; i < cbits; i++) { + const bit = i % 8; + const oct = (i - bit) / 8; + const b1 = (chk1[oct] >>> (7 - bit)) & 1; + const b2 = (chk2[oct] >>> (7 - bit)) & 1; + if (b1 !== b2) + throw new Error('Invalid checksum.'); + } + + assert(bits / 8 === entropy.length); + + this.bits = bits; + this.language = lang; + this.entropy = entropy; + this.phrase = phrase; + + return this; + } + + /** + * Instantiate mnemonic from a phrase (validates checksum). + * @param {String} phrase + * @returns {Mnemonic} + * @throws on bad checksum + */ + + static fromPhrase(phrase) { + return new this().fromPhrase(phrase); + } + + /** + * Inject properties from entropy. + * @private + * @param {Buffer} entropy + * @param {String?} lang + */ + + fromEntropy(entropy, lang) { + assert(Buffer.isBuffer(entropy)); + assert(entropy.length * 8 >= common.MIN_ENTROPY); + assert(entropy.length * 8 <= common.MAX_ENTROPY); + assert((entropy.length * 8) % 32 === 0); + assert(!lang || Mnemonic.languages.indexOf(lang) !== -1); + + this.entropy = entropy; + this.bits = entropy.length * 8; + + if (lang) + this.language = lang; + + return this; + } + + /** + * Instantiate mnemonic from entropy. + * @param {Buffer} entropy + * @param {String?} lang + * @returns {Mnemonic} + */ + + static fromEntropy(entropy, lang) { + return new this().fromEntropy(entropy, lang); + } + + /** + * Determine a single word's language. + * @param {String} word + * @returns {String} Language. + * @throws on not found. + */ + + static getLanguage(word) { + for (const lang of Mnemonic.languages) { + const list = Mnemonic.getWordlist(lang); + if (list.map[word] != null) + return lang; + } + + throw new Error('Could not determine language.'); + } + + /** + * Retrieve the wordlist for a language. + * @param {String} lang + * @returns {Object} + */ + + static getWordlist(lang) { + const cache = wordlistCache[lang]; + + if (cache) + return cache; + + const words = wordlist.get(lang); + const list = new WordList(words); + + wordlistCache[lang] = list; + + return list; + } + + /** + * Convert mnemonic to a json-friendly object. + * @returns {Object} + */ + + toJSON() { + return { + bits: this.bits, + language: this.language, + entropy: this.getEntropy().toString('hex'), + phrase: this.getPhrase(), + passphrase: this.passphrase + }; + } + + /** + * Inject properties from json object. + * @private + * @param {Object} json + */ + + fromJSON(json) { + assert(json); + assert((json.bits & 0xffff) === json.bits); + assert(typeof json.language === 'string'); + assert(typeof json.entropy === 'string'); + assert(typeof json.phrase === 'string'); + assert(typeof json.passphrase === 'string'); + assert(json.bits >= common.MIN_ENTROPY); + assert(json.bits <= common.MAX_ENTROPY); + assert(json.bits % 32 === 0); + assert(json.bits / 8 === json.entropy.length / 2); + + this.bits = json.bits; + this.language = json.language; + this.entropy = Buffer.from(json.entropy, 'hex'); + this.phrase = json.phrase; + this.passphrase = json.passphrase; + + return this; + } + + /** + * Instantiate mnemonic from json object. + * @param {Object} json + * @returns {Mnemonic} + */ + + static fromJSON(json) { + return new this().fromJSON(json); + } + + /** + * Calculate serialization size. + * @returns {Number} + */ + + getSize() { + let size = 0; + size += 3; + size += this.getEntropy().length; + size += encoding.sizeVarString(this.getPhrase(), 'utf8'); + size += encoding.sizeVarString(this.passphrase, 'utf8'); + return size; + } + + /** + * Write the mnemonic to a buffer writer. + * @params {BufferWriter} bw + */ + + toWriter(bw) { + const lang = Mnemonic.languages.indexOf(this.language); + + assert(lang !== -1); + + bw.writeU16(this.bits); + bw.writeU8(lang); + bw.writeBytes(this.getEntropy()); + bw.writeVarString(this.getPhrase(), 'utf8'); + bw.writeVarString(this.passphrase, 'utf8'); + + return bw; + } + + /** + * Serialize mnemonic. + * @returns {Buffer} + */ + + toRaw(writer) { + const size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + const bits = br.readU16(); + + assert(bits >= common.MIN_ENTROPY); + assert(bits <= common.MAX_ENTROPY); + assert(bits % 32 === 0); + + const language = Mnemonic.languages[br.readU8()]; + assert(language); + + this.bits = bits; + this.language = language; + this.entropy = br.readBytes(bits / 8); + this.phrase = br.readVarString('utf8'); + this.passphrase = br.readVarString('utf8'); + + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate mnemonic from buffer reader. + * @param {BufferReader} br + * @returns {Mnemonic} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate mnemonic from serialized data. + * @param {Buffer} data + * @returns {Mnemonic} + */ + + static fromRaw(data) { + return new this().fromRaw(data); + } + + /** + * Convert the mnemonic to a string. + * @returns {String} + */ + + toString() { + return this.getPhrase(); + } + + /** + * Inspect the mnemonic. + * @returns {String} + */ + + inspect() { + return ``; + } + + /** + * Test whether an object is a Mnemonic. + * @param {Object} obj + * @returns {Boolean} + */ + + static isMnemonic(obj) { + return obj instanceof Mnemonic; + } } /** @@ -72,506 +561,27 @@ Mnemonic.languages = [ 'spanish' ]; -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -Mnemonic.prototype.fromOptions = function fromOptions(options) { - if (typeof options === 'string') - options = { phrase: options }; - - if (options.bits != null) { - assert((options.bits & 0xffff) === options.bits); - assert(options.bits >= common.MIN_ENTROPY); - assert(options.bits <= common.MAX_ENTROPY); - assert(options.bits % 32 === 0); - this.bits = options.bits; - } - - if (options.language) { - assert(typeof options.language === 'string'); - assert(Mnemonic.languages.indexOf(options.language) !== -1); - this.language = options.language; - } - - if (options.passphrase) { - assert(typeof options.passphrase === 'string'); - this.passphrase = options.passphrase; - } - - if (options.phrase) { - this.fromPhrase(options.phrase); - return this; - } - - if (options.entropy) { - this.fromEntropy(options.entropy); - return this; - } - - return this; -}; - -/** - * Instantiate mnemonic from options. - * @param {Object} options - * @returns {Mnemonic} - */ - -Mnemonic.fromOptions = function fromOptions(options) { - return new Mnemonic().fromOptions(options); -}; - -/** - * Destroy the mnemonic (zeroes entropy). - */ - -Mnemonic.prototype.destroy = function destroy() { - this.bits = common.MIN_ENTROPY; - this.language = 'english'; - if (this.entropy) { - cleanse(this.entropy); - this.entropy = null; - } - this.phrase = null; - this.passphrase = ''; -}; - -/** - * Generate the seed. - * @param {String?} passphrase - * @returns {Buffer} pbkdf2 seed. - */ - -Mnemonic.prototype.toSeed = function toSeed(passphrase) { - if (!passphrase) - passphrase = this.passphrase; - - this.passphrase = passphrase; - - const phrase = nfkd(this.getPhrase()); - const passwd = nfkd('mnemonic' + passphrase); - - return pbkdf2.derive(sha512, - Buffer.from(phrase, 'utf8'), - Buffer.from(passwd, 'utf8'), - 2048, 64); -}; - -/** - * Get or generate entropy. - * @returns {Buffer} - */ - -Mnemonic.prototype.getEntropy = function getEntropy() { - if (!this.entropy) - this.entropy = random.randomBytes(this.bits / 8); - - assert(this.bits / 8 === this.entropy.length); - - return this.entropy; -}; - -/** - * Generate a mnemonic phrase from chosen language. - * @returns {String} - */ - -Mnemonic.prototype.getPhrase = function getPhrase() { - if (this.phrase) - return this.phrase; - - // Include the first `ENT / 32` bits - // of the hash (the checksum). - const wbits = this.bits + (this.bits / 32); - - // Get entropy and checksum. - const entropy = this.getEntropy(); - const chk = sha256.digest(entropy); - - // Append the hash to the entropy to - // make things easy when grabbing - // the checksum bits. - const size = Math.ceil(wbits / 8); - const data = Buffer.allocUnsafe(size); - entropy.copy(data, 0); - chk.copy(data, entropy.length); - - // Build the mnemonic by reading - // 11 bit indexes from the entropy. - const list = Mnemonic.getWordlist(this.language); - - let phrase = []; - for (let i = 0; i < wbits / 11; i++) { - let index = 0; - for (let j = 0; j < 11; j++) { - const pos = i * 11 + j; - const bit = pos % 8; - const oct = (pos - bit) / 8; - index <<= 1; - index |= (data[oct] >>> (7 - bit)) & 1; - } - phrase.push(list.words[index]); - } - - // Japanese likes double-width spaces. - if (this.language === 'japanese') - phrase = phrase.join('\u3000'); - else - phrase = phrase.join(' '); - - this.phrase = phrase; - - return phrase; -}; - -/** - * Inject properties from phrase. - * @private - * @param {String} phrase - */ - -Mnemonic.prototype.fromPhrase = function fromPhrase(phrase) { - assert(typeof phrase === 'string'); - assert(phrase.length <= 1000); - - const words = phrase.trim().split(/[\s\u3000]+/); - const wbits = words.length * 11; - const cbits = wbits % 32; - - assert(cbits !== 0, 'Invalid checksum.'); - - const bits = wbits - cbits; - - assert(bits >= common.MIN_ENTROPY); - assert(bits <= common.MAX_ENTROPY); - assert(bits % 32 === 0); - - const size = Math.ceil(wbits / 8); - const data = Buffer.allocUnsafe(size); - data.fill(0); - - const lang = Mnemonic.getLanguage(words[0]); - const list = Mnemonic.getWordlist(lang); - - // Rebuild entropy bytes. - for (let i = 0; i < words.length; i++) { - const word = words[i]; - const index = list.map[word]; - - if (index == null) - throw new Error('Could not find word.'); - - for (let j = 0; j < 11; j++) { - const pos = i * 11 + j; - const bit = pos % 8; - const oct = (pos - bit) / 8; - const val = (index >>> (10 - j)) & 1; - data[oct] |= val << (7 - bit); - } - } - - const cbytes = Math.ceil(cbits / 8); - const entropy = data.slice(0, data.length - cbytes); - const chk1 = data.slice(data.length - cbytes); - const chk2 = sha256.digest(entropy); - - // Verify checksum. - for (let i = 0; i < cbits; i++) { - const bit = i % 8; - const oct = (i - bit) / 8; - const b1 = (chk1[oct] >>> (7 - bit)) & 1; - const b2 = (chk2[oct] >>> (7 - bit)) & 1; - if (b1 !== b2) - throw new Error('Invalid checksum.'); - } - - assert(bits / 8 === entropy.length); - - this.bits = bits; - this.language = lang; - this.entropy = entropy; - this.phrase = phrase; - - return this; -}; - -/** - * Instantiate mnemonic from a phrase (validates checksum). - * @param {String} phrase - * @returns {Mnemonic} - * @throws on bad checksum - */ - -Mnemonic.fromPhrase = function fromPhrase(phrase) { - return new Mnemonic().fromPhrase(phrase); -}; - -/** - * Inject properties from entropy. - * @private - * @param {Buffer} entropy - * @param {String?} lang - */ - -Mnemonic.prototype.fromEntropy = function fromEntropy(entropy, lang) { - assert(Buffer.isBuffer(entropy)); - assert(entropy.length * 8 >= common.MIN_ENTROPY); - assert(entropy.length * 8 <= common.MAX_ENTROPY); - assert((entropy.length * 8) % 32 === 0); - assert(!lang || Mnemonic.languages.indexOf(lang) !== -1); - - this.entropy = entropy; - this.bits = entropy.length * 8; - - if (lang) - this.language = lang; - - return this; -}; - -/** - * Instantiate mnemonic from entropy. - * @param {Buffer} entropy - * @param {String?} lang - * @returns {Mnemonic} - */ - -Mnemonic.fromEntropy = function fromEntropy(entropy, lang) { - return new Mnemonic().fromEntropy(entropy, lang); -}; - -/** - * Determine a single word's language. - * @param {String} word - * @returns {String} Language. - * @throws on not found. - */ - -Mnemonic.getLanguage = function getLanguage(word) { - for (const lang of Mnemonic.languages) { - const list = Mnemonic.getWordlist(lang); - if (list.map[word] != null) - return lang; - } - - throw new Error('Could not determine language.'); -}; - -/** - * Retrieve the wordlist for a language. - * @param {String} lang - * @returns {Object} - */ - -Mnemonic.getWordlist = function getWordlist(lang) { - const cache = wordlistCache[lang]; - - if (cache) - return cache; - - const words = wordlist.get(lang); - const list = new WordList(words); - - wordlistCache[lang] = list; - - return list; -}; - -/** - * Convert mnemonic to a json-friendly object. - * @returns {Object} - */ - -Mnemonic.prototype.toJSON = function toJSON() { - return { - bits: this.bits, - language: this.language, - entropy: this.getEntropy().toString('hex'), - phrase: this.getPhrase(), - passphrase: this.passphrase - }; -}; - -/** - * Inject properties from json object. - * @private - * @param {Object} json - */ - -Mnemonic.prototype.fromJSON = function fromJSON(json) { - assert(json); - assert((json.bits & 0xffff) === json.bits); - assert(typeof json.language === 'string'); - assert(typeof json.entropy === 'string'); - assert(typeof json.phrase === 'string'); - assert(typeof json.passphrase === 'string'); - assert(json.bits >= common.MIN_ENTROPY); - assert(json.bits <= common.MAX_ENTROPY); - assert(json.bits % 32 === 0); - assert(json.bits / 8 === json.entropy.length / 2); - - this.bits = json.bits; - this.language = json.language; - this.entropy = Buffer.from(json.entropy, 'hex'); - this.phrase = json.phrase; - this.passphrase = json.passphrase; - - return this; -}; - -/** - * Instantiate mnemonic from json object. - * @param {Object} json - * @returns {Mnemonic} - */ - -Mnemonic.fromJSON = function fromJSON(json) { - return new Mnemonic().fromJSON(json); -}; - -/** - * Calculate serialization size. - * @returns {Number} - */ - -Mnemonic.prototype.getSize = function getSize() { - let size = 0; - size += 3; - size += this.getEntropy().length; - size += encoding.sizeVarString(this.getPhrase(), 'utf8'); - size += encoding.sizeVarString(this.passphrase, 'utf8'); - return size; -}; - -/** - * Write the mnemonic to a buffer writer. - * @params {BufferWriter} bw - */ - -Mnemonic.prototype.toWriter = function toWriter(bw) { - const lang = Mnemonic.languages.indexOf(this.language); - - assert(lang !== -1); - - bw.writeU16(this.bits); - bw.writeU8(lang); - bw.writeBytes(this.getEntropy()); - bw.writeVarString(this.getPhrase(), 'utf8'); - bw.writeVarString(this.passphrase, 'utf8'); - - return bw; -}; - -/** - * Serialize mnemonic. - * @returns {Buffer} - */ - -Mnemonic.prototype.toRaw = function toRaw(writer) { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -Mnemonic.prototype.fromReader = function fromReader(br) { - const bits = br.readU16(); - - assert(bits >= common.MIN_ENTROPY); - assert(bits <= common.MAX_ENTROPY); - assert(bits % 32 === 0); - - const language = Mnemonic.languages[br.readU8()]; - assert(language); - - this.bits = bits; - this.language = language; - this.entropy = br.readBytes(bits / 8); - this.phrase = br.readVarString('utf8'); - this.passphrase = br.readVarString('utf8'); - - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -Mnemonic.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate mnemonic from buffer reader. - * @param {BufferReader} br - * @returns {Mnemonic} - */ - -Mnemonic.fromReader = function fromReader(br) { - return new Mnemonic().fromReader(br); -}; - -/** - * Instantiate mnemonic from serialized data. - * @param {Buffer} data - * @returns {Mnemonic} - */ - -Mnemonic.fromRaw = function fromRaw(data) { - return new Mnemonic().fromRaw(data); -}; - -/** - * Convert the mnemonic to a string. - * @returns {String} - */ - -Mnemonic.prototype.toString = function toString() { - return this.getPhrase(); -}; - -/** - * Inspect the mnemonic. - * @returns {String} - */ - -Mnemonic.prototype.inspect = function inspect() { - return ``; -}; - -/** - * Test whether an object is a Mnemonic. - * @param {Object} obj - * @returns {Boolean} - */ - -Mnemonic.isMnemonic = function isMnemonic(obj) { - return obj instanceof Mnemonic; -}; - /** * Word List - * @constructor * @ignore - * @param {Array} words */ -function WordList(words) { - this.words = words; - this.map = Object.create(null); +class WordList { + /** + * Create word list. + * @constructor + * @ignore + * @param {Array} words + */ - for (let i = 0; i < words.length; i++) { - const word = words[i]; - this.map[word] = i; + constructor(words) { + this.words = words; + this.map = Object.create(null); + + for (let i = 0; i < words.length; i++) { + const word = words[i]; + this.map[word] = i; + } } } diff --git a/lib/hd/private.js b/lib/hd/private.js index 3c6961da..979d37c2 100644 --- a/lib/hd/private.js +++ b/lib/hd/private.js @@ -31,14 +31,6 @@ const SEED_SALT = Buffer.from('Bitcoin seed', 'ascii'); /** * HDPrivateKey * @alias module:hd.PrivateKey - * @constructor - * @param {Object|Base58String} options - * @param {Base58String?} options.xkey - Serialized base58 key. - * @param {Number?} options.depth - * @param {Number?} options.parentFingerPrint - * @param {Number?} options.childIndex - * @param {Buffer?} options.chainCode - * @param {Buffer?} options.privateKey * @property {Number} depth * @property {Number} parentFingerPrint * @property {Number} childIndex @@ -46,677 +38,687 @@ const SEED_SALT = Buffer.from('Bitcoin seed', 'ascii'); * @property {Buffer} privateKey */ -function HDPrivateKey(options) { - if (!(this instanceof HDPrivateKey)) - return new HDPrivateKey(options); +class HDPrivateKey { + /** + * Create an hd private key. + * @constructor + * @param {Object|String} options + * @param {Number?} options.depth + * @param {Number?} options.parentFingerPrint + * @param {Number?} options.childIndex + * @param {Buffer?} options.chainCode + * @param {Buffer?} options.privateKey + */ - this.depth = 0; - this.parentFingerPrint = 0; - this.childIndex = 0; - this.chainCode = encoding.ZERO_HASH; - this.privateKey = encoding.ZERO_HASH; + constructor(options) { + this.depth = 0; + this.parentFingerPrint = 0; + this.childIndex = 0; + this.chainCode = encoding.ZERO_HASH; + this.privateKey = encoding.ZERO_HASH; - this.publicKey = encoding.ZERO_KEY; - this.fingerPrint = -1; + this.publicKey = encoding.ZERO_KEY; + this.fingerPrint = -1; - this._hdPublicKey = null; - - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -HDPrivateKey.prototype.fromOptions = function fromOptions(options) { - assert(options, 'No options for HD private key.'); - assert((options.depth & 0xff) === options.depth); - assert((options.parentFingerPrint >>> 0) === options.parentFingerPrint); - assert((options.childIndex >>> 0) === options.childIndex); - assert(Buffer.isBuffer(options.chainCode)); - assert(Buffer.isBuffer(options.privateKey)); - - this.depth = options.depth; - this.parentFingerPrint = options.parentFingerPrint; - this.childIndex = options.childIndex; - this.chainCode = options.chainCode; - this.privateKey = options.privateKey; - this.publicKey = secp256k1.publicKeyCreate(options.privateKey, true); - - return this; -}; - -/** - * Instantiate HD private key from options object. - * @param {Object} options - * @returns {HDPrivateKey} - */ - -HDPrivateKey.fromOptions = function fromOptions(options) { - return new HDPrivateKey().fromOptions(options); -}; - -/** - * Get HD public key. - * @returns {HDPublicKey} - */ - -HDPrivateKey.prototype.toPublic = function toPublic() { - let key = this._hdPublicKey; - - if (!key) { - key = new HDPublicKey(); - key.depth = this.depth; - key.parentFingerPrint = this.parentFingerPrint; - key.childIndex = this.childIndex; - key.chainCode = this.chainCode; - key.publicKey = this.publicKey; - this._hdPublicKey = key; - } - - return key; -}; - -/** - * Get cached base58 xprivkey. - * @returns {Base58String} - */ - -HDPrivateKey.prototype.xprivkey = function xprivkey(network) { - return this.toBase58(network); -}; - -/** - * Get cached base58 xpubkey. - * @returns {Base58String} - */ - -HDPrivateKey.prototype.xpubkey = function xpubkey(network) { - return this.toPublic().xpubkey(network); -}; - -/** - * Destroy the key (zeroes chain code, privkey, and pubkey). - * @param {Boolean} pub - Destroy hd public key as well. - */ - -HDPrivateKey.prototype.destroy = function destroy(pub) { - this.depth = 0; - this.childIndex = 0; - this.parentFingerPrint = 0; - - cleanse(this.chainCode); - cleanse(this.privateKey); - cleanse(this.publicKey); - - this.fingerPrint = -1; - - if (this._hdPublicKey) { - if (pub) - this._hdPublicKey.destroy(); this._hdPublicKey = null; - } -}; -/** - * Derive a child key. - * @param {Number} index - Derivation index. - * @param {Boolean?} hardened - Whether the derivation should be hardened. - * @returns {HDPrivateKey} - */ - -HDPrivateKey.prototype.derive = function derive(index, hardened) { - assert(typeof index === 'number'); - - if ((index >>> 0) !== index) - throw new Error('Index out of range.'); - - if (this.depth >= 0xff) - throw new Error('Depth too high.'); - - if (hardened) { - index |= common.HARDENED; - index >>>= 0; + if (options) + this.fromOptions(options); } - const id = this.getID(index); - const cache = common.cache.get(id); + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ - if (cache) - return cache; + fromOptions(options) { + assert(options, 'No options for HD private key.'); + assert((options.depth & 0xff) === options.depth); + assert((options.parentFingerPrint >>> 0) === options.parentFingerPrint); + assert((options.childIndex >>> 0) === options.childIndex); + assert(Buffer.isBuffer(options.chainCode)); + assert(Buffer.isBuffer(options.privateKey)); - const bw = StaticWriter.pool(37); + this.depth = options.depth; + this.parentFingerPrint = options.parentFingerPrint; + this.childIndex = options.childIndex; + this.chainCode = options.chainCode; + this.privateKey = options.privateKey; + this.publicKey = secp256k1.publicKeyCreate(options.privateKey, true); - if (index & common.HARDENED) { + return this; + } + + /** + * Instantiate HD private key from options object. + * @param {Object} options + * @returns {HDPrivateKey} + */ + + static fromOptions(options) { + return new this().fromOptions(options); + } + + /** + * Get HD public key. + * @returns {HDPublicKey} + */ + + toPublic() { + let key = this._hdPublicKey; + + if (!key) { + key = new HDPublicKey(); + key.depth = this.depth; + key.parentFingerPrint = this.parentFingerPrint; + key.childIndex = this.childIndex; + key.chainCode = this.chainCode; + key.publicKey = this.publicKey; + this._hdPublicKey = key; + } + + return key; + } + + /** + * Get cached base58 xprivkey. + * @returns {Base58String} + */ + + xprivkey(network) { + return this.toBase58(network); + } + + /** + * Get cached base58 xpubkey. + * @returns {Base58String} + */ + + xpubkey(network) { + return this.toPublic().xpubkey(network); + } + + /** + * Destroy the key (zeroes chain code, privkey, and pubkey). + * @param {Boolean} pub - Destroy hd public key as well. + */ + + destroy(pub) { + this.depth = 0; + this.childIndex = 0; + this.parentFingerPrint = 0; + + cleanse(this.chainCode); + cleanse(this.privateKey); + cleanse(this.publicKey); + + this.fingerPrint = -1; + + if (this._hdPublicKey) { + if (pub) + this._hdPublicKey.destroy(); + this._hdPublicKey = null; + } + } + + /** + * Derive a child key. + * @param {Number} index - Derivation index. + * @param {Boolean?} hardened - Whether the derivation should be hardened. + * @returns {HDPrivateKey} + */ + + derive(index, hardened) { + assert(typeof index === 'number'); + + if ((index >>> 0) !== index) + throw new Error('Index out of range.'); + + if (this.depth >= 0xff) + throw new Error('Depth too high.'); + + if (hardened) { + index |= common.HARDENED; + index >>>= 0; + } + + const id = this.getID(index); + const cache = common.cache.get(id); + + if (cache) + return cache; + + const bw = StaticWriter.pool(37); + + if (index & common.HARDENED) { + bw.writeU8(0); + bw.writeBytes(this.privateKey); + bw.writeU32BE(index); + } else { + bw.writeBytes(this.publicKey); + bw.writeU32BE(index); + } + + const data = bw.render(); + + const hash = sha512.mac(data, this.chainCode); + const left = hash.slice(0, 32); + const right = hash.slice(32, 64); + + let key; + try { + key = secp256k1.privateKeyTweakAdd(this.privateKey, left); + } catch (e) { + return this.derive(index + 1); + } + + if (this.fingerPrint === -1) { + const fp = hash160.digest(this.publicKey); + this.fingerPrint = fp.readUInt32BE(0, true); + } + + const child = new this.constructor(); + child.depth = this.depth + 1; + child.parentFingerPrint = this.fingerPrint; + child.childIndex = index; + child.chainCode = right; + child.privateKey = key; + child.publicKey = secp256k1.publicKeyCreate(key, true); + + common.cache.set(id, child); + + return child; + } + + /** + * Unique HD key ID. + * @private + * @param {Number} index + * @returns {String} + */ + + getID(index) { + return 'v' + this.publicKey.toString('hex') + index; + } + + /** + * Derive a BIP44 account key. + * @param {Number} purpose + * @param {Number} type + * @param {Number} account + * @returns {HDPrivateKey} + * @throws Error if key is not a master key. + */ + + deriveAccount(purpose, type, account) { + assert((purpose >>> 0) === purpose, 'Purpose must be a number.'); + assert((type >>> 0) === type, 'Account index must be a number.'); + assert((account >>> 0) === account, 'Account index must be a number.'); + assert(this.isMaster(), 'Cannot derive account index.'); + return this + .derive(purpose, true) + .derive(type, true) + .derive(account, true); + } + + /** + * Test whether the key is a master key. + * @returns {Boolean} + */ + + isMaster() { + return common.isMaster(this); + } + + /** + * Test whether the key is (most likely) a BIP44 account key. + * @param {Number?} account + * @returns {Boolean} + */ + + isAccount(account) { + return common.isAccount(this, account); + } + + /** + * Test whether an object is in the form of a base58 xprivkey. + * @param {String} data + * @param {Network?} network + * @returns {Boolean} + */ + + static isBase58(data, network) { + if (typeof data !== 'string') + return false; + + if (data.length < 4) + return false; + + const prefix = data.substring(0, 4); + + try { + Network.fromPrivate58(prefix, network); + return true; + } catch (e) { + return false; + } + } + + /** + * Test whether a buffer has a valid network prefix. + * @param {Buffer} data + * @param {Network?} network + * @returns {Boolean} + */ + + static isRaw(data, network) { + if (!Buffer.isBuffer(data)) + return false; + + if (data.length < 4) + return false; + + const version = data.readUInt32BE(0, true); + + try { + Network.fromPrivate(version, network); + return true; + } catch (e) { + return false; + } + } + + /** + * Test whether a string is a valid path. + * @param {String} path + * @returns {Boolean} + */ + + static isValidPath(path) { + try { + common.parsePath(path, true); + return true; + } catch (e) { + return false; + } + } + + /** + * Derive a key from a derivation path. + * @param {String} path + * @returns {HDPrivateKey} + * @throws Error if `path` is not a valid path. + */ + + derivePath(path) { + const indexes = common.parsePath(path, true); + + let key = this; + + for (const index of indexes) + key = key.derive(index); + + return key; + } + + /** + * Compare a key against an object. + * @param {Object} obj + * @returns {Boolean} + */ + + equals(obj) { + assert(HDPrivateKey.isHDPrivateKey(obj)); + + return this.depth === obj.depth + && this.parentFingerPrint === obj.parentFingerPrint + && this.childIndex === obj.childIndex + && this.chainCode.equals(obj.chainCode) + && this.privateKey.equals(obj.privateKey); + } + + /** + * Compare a key against an object. + * @param {Object} obj + * @returns {Boolean} + */ + + compare(key) { + assert(HDPrivateKey.isHDPrivateKey(key)); + + let cmp = this.depth - key.depth; + + if (cmp !== 0) + return cmp; + + cmp = this.parentFingerPrint - key.parentFingerPrint; + + if (cmp !== 0) + return cmp; + + cmp = this.childIndex - key.childIndex; + + if (cmp !== 0) + return cmp; + + cmp = this.chainCode.compare(key.chainCode); + + if (cmp !== 0) + return cmp; + + cmp = this.privateKey.compare(key.privateKey); + + if (cmp !== 0) + return cmp; + + return 0; + } + + /** + * Inject properties from seed. + * @private + * @param {Buffer} seed + */ + + fromSeed(seed) { + assert(Buffer.isBuffer(seed)); + + if (seed.length * 8 < common.MIN_ENTROPY + || seed.length * 8 > common.MAX_ENTROPY) { + throw new Error('Entropy not in range.'); + } + + const hash = sha512.mac(seed, SEED_SALT); + const left = hash.slice(0, 32); + const right = hash.slice(32, 64); + + // Only a 1 in 2^127 chance of happening. + if (!secp256k1.privateKeyVerify(left)) + throw new Error('Master private key is invalid.'); + + this.depth = 0; + this.parentFingerPrint = 0; + this.childIndex = 0; + this.chainCode = right; + this.privateKey = left; + this.publicKey = secp256k1.publicKeyCreate(left, true); + + return this; + } + + /** + * Instantiate an hd private key from a 512 bit seed. + * @param {Buffer} seed + * @returns {HDPrivateKey} + */ + + static fromSeed(seed) { + return new this().fromSeed(seed); + } + + /** + * Inject properties from a mnemonic. + * @private + * @param {Mnemonic} mnemonic + */ + + fromMnemonic(mnemonic) { + assert(mnemonic instanceof Mnemonic); + return this.fromSeed(mnemonic.toSeed()); + } + + /** + * Instantiate an hd private key from a mnemonic. + * @param {Mnemonic} mnemonic + * @returns {HDPrivateKey} + */ + + static fromMnemonic(mnemonic) { + return new this().fromMnemonic(mnemonic); + } + + /** + * Inject properties from a mnemonic. + * @private + * @param {String} mnemonic + */ + + fromPhrase(phrase) { + const mnemonic = Mnemonic.fromPhrase(phrase); + this.fromMnemonic(mnemonic); + return this; + } + + /** + * Instantiate an hd private key from a phrase. + * @param {String} phrase + * @returns {HDPrivateKey} + */ + + static fromPhrase(phrase) { + return new this().fromPhrase(phrase); + } + + /** + * Inject properties from privateKey and entropy. + * @private + * @param {Buffer} key + * @param {Buffer} entropy + */ + + fromKey(key, entropy) { + assert(Buffer.isBuffer(key) && key.length === 32); + assert(Buffer.isBuffer(entropy) && entropy.length === 32); + this.depth = 0; + this.parentFingerPrint = 0; + this.childIndex = 0; + this.chainCode = entropy; + this.privateKey = key; + this.publicKey = secp256k1.publicKeyCreate(key, true); + return this; + } + + /** + * Create an hd private key from a key and entropy bytes. + * @param {Buffer} key + * @param {Buffer} entropy + * @returns {HDPrivateKey} + */ + + static fromKey(key, entropy) { + return new this().fromKey(key, entropy); + } + + /** + * Generate an hd private key. + * @returns {HDPrivateKey} + */ + + static generate() { + const key = secp256k1.generatePrivateKey(); + const entropy = random.randomBytes(32); + return HDPrivateKey.fromKey(key, entropy); + } + + /** + * Inject properties from base58 key. + * @private + * @param {Base58String} xkey + * @param {Network?} network + */ + + fromBase58(xkey, network) { + assert(typeof xkey === 'string'); + return this.fromRaw(base58.decode(xkey), network); + } + + /** + * Inject properties from serialized data. + * @private + * @param {BufferReader} br + * @param {(Network|NetworkType)?} network + */ + + fromReader(br, network) { + const version = br.readU32BE(); + + Network.fromPrivate(version, network); + + this.depth = br.readU8(); + this.parentFingerPrint = br.readU32BE(); + this.childIndex = br.readU32BE(); + this.chainCode = br.readBytes(32); + assert(br.readU8() === 0); + this.privateKey = br.readBytes(32); + this.publicKey = secp256k1.publicKeyCreate(this.privateKey, true); + + br.verifyChecksum(hash256.digest); + + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @param {(Network|NetworkType)?} network + */ + + fromRaw(data, network) { + return this.fromReader(new BufferReader(data), network); + } + + /** + * Serialize key to a base58 string. + * @param {(Network|NetworkType)?} network + * @returns {Base58String} + */ + + toBase58(network) { + return base58.encode(this.toRaw(network)); + } + + /** + * Calculate serialization size. + * @returns {Number} + */ + + getSize() { + return 82; + } + + /** + * Write the key to a buffer writer. + * @param {BufferWriter} bw + * @param {(Network|NetworkType)?} network + */ + + toWriter(bw, network) { + network = Network.get(network); + + bw.writeU32BE(network.keyPrefix.xprivkey); + bw.writeU8(this.depth); + bw.writeU32BE(this.parentFingerPrint); + bw.writeU32BE(this.childIndex); + bw.writeBytes(this.chainCode); bw.writeU8(0); bw.writeBytes(this.privateKey); - bw.writeU32BE(index); - } else { - bw.writeBytes(this.publicKey); - bw.writeU32BE(index); + bw.writeChecksum(hash256.digest); + + return bw; } - const data = bw.render(); + /** + * Serialize the key. + * @param {(Network|NetworkType)?} network + * @returns {Buffer} + */ - const hash = sha512.mac(data, this.chainCode); - const left = hash.slice(0, 32); - const right = hash.slice(32, 64); - - let key; - try { - key = secp256k1.privateKeyTweakAdd(this.privateKey, left); - } catch (e) { - return this.derive(index + 1); + toRaw(network) { + return this.toWriter(new StaticWriter(82), network).render(); } - if (this.fingerPrint === -1) { - const fp = hash160.digest(this.publicKey); - this.fingerPrint = fp.readUInt32BE(0, true); + /** + * Instantiate an HD private key from a base58 string. + * @param {Base58String} xkey + * @param {Network?} network + * @returns {HDPrivateKey} + */ + + static fromBase58(xkey, network) { + return new this().fromBase58(xkey, network); } - const child = new HDPrivateKey(); - child.depth = this.depth + 1; - child.parentFingerPrint = this.fingerPrint; - child.childIndex = index; - child.chainCode = right; - child.privateKey = key; - child.publicKey = secp256k1.publicKeyCreate(key, true); + /** + * Instantiate key from buffer reader. + * @param {BufferReader} br + * @param {(Network|NetworkType)?} network + * @returns {HDPrivateKey} + */ - common.cache.set(id, child); - - return child; -}; - -/** - * Unique HD key ID. - * @private - * @param {Number} index - * @returns {String} - */ - -HDPrivateKey.prototype.getID = function getID(index) { - return 'v' + this.publicKey.toString('hex') + index; -}; - -/** - * Derive a BIP44 account key. - * @param {Number} purpose - * @param {Number} type - * @param {Number} account - * @returns {HDPrivateKey} - * @throws Error if key is not a master key. - */ - -HDPrivateKey.prototype.deriveAccount = function deriveAccount(purpose, type, account) { - assert((purpose >>> 0) === purpose, 'Purpose must be a number.'); - assert((type >>> 0) === type, 'Account index must be a number.'); - assert((account >>> 0) === account, 'Account index must be a number.'); - assert(this.isMaster(), 'Cannot derive account index.'); - return this - .derive(purpose, true) - .derive(type, true) - .derive(account, true); -}; - -/** - * Test whether the key is a master key. - * @returns {Boolean} - */ - -HDPrivateKey.prototype.isMaster = function isMaster() { - return common.isMaster(this); -}; - -/** - * Test whether the key is (most likely) a BIP44 account key. - * @param {Number?} account - * @returns {Boolean} - */ - -HDPrivateKey.prototype.isAccount = function isAccount(account) { - return common.isAccount(this, account); -}; - -/** - * Test whether an object is in the form of a base58 xprivkey. - * @param {String} data - * @param {Network?} network - * @returns {Boolean} - */ - -HDPrivateKey.isBase58 = function isBase58(data, network) { - if (typeof data !== 'string') - return false; - - if (data.length < 4) - return false; - - const prefix = data.substring(0, 4); - - try { - Network.fromPrivate58(prefix, network); - return true; - } catch (e) { - return false; - } -}; - -/** - * Test whether a buffer has a valid network prefix. - * @param {Buffer} data - * @param {Network?} network - * @returns {Boolean} - */ - -HDPrivateKey.isRaw = function isRaw(data, network) { - if (!Buffer.isBuffer(data)) - return false; - - if (data.length < 4) - return false; - - const version = data.readUInt32BE(0, true); - - try { - Network.fromPrivate(version, network); - return true; - } catch (e) { - return false; - } -}; - -/** - * Test whether a string is a valid path. - * @param {String} path - * @returns {Boolean} - */ - -HDPrivateKey.isValidPath = function isValidPath(path) { - try { - common.parsePath(path, true); - return true; - } catch (e) { - return false; - } -}; - -/** - * Derive a key from a derivation path. - * @param {String} path - * @returns {HDPrivateKey} - * @throws Error if `path` is not a valid path. - */ - -HDPrivateKey.prototype.derivePath = function derivePath(path) { - const indexes = common.parsePath(path, true); - - let key = this; - - for (const index of indexes) - key = key.derive(index); - - return key; -}; - -/** - * Compare a key against an object. - * @param {Object} obj - * @returns {Boolean} - */ - -HDPrivateKey.prototype.equals = function equals(obj) { - assert(HDPrivateKey.isHDPrivateKey(obj)); - - return this.depth === obj.depth - && this.parentFingerPrint === obj.parentFingerPrint - && this.childIndex === obj.childIndex - && this.chainCode.equals(obj.chainCode) - && this.privateKey.equals(obj.privateKey); -}; - -/** - * Compare a key against an object. - * @param {Object} obj - * @returns {Boolean} - */ - -HDPrivateKey.prototype.compare = function compare(key) { - assert(HDPrivateKey.isHDPrivateKey(key)); - - let cmp = this.depth - key.depth; - - if (cmp !== 0) - return cmp; - - cmp = this.parentFingerPrint - key.parentFingerPrint; - - if (cmp !== 0) - return cmp; - - cmp = this.childIndex - key.childIndex; - - if (cmp !== 0) - return cmp; - - cmp = this.chainCode.compare(key.chainCode); - - if (cmp !== 0) - return cmp; - - cmp = this.privateKey.compare(key.privateKey); - - if (cmp !== 0) - return cmp; - - return 0; -}; - -/** - * Inject properties from seed. - * @private - * @param {Buffer} seed - */ - -HDPrivateKey.prototype.fromSeed = function fromSeed(seed) { - assert(Buffer.isBuffer(seed)); - - if (seed.length * 8 < common.MIN_ENTROPY - || seed.length * 8 > common.MAX_ENTROPY) { - throw new Error('Entropy not in range.'); + static fromReader(br, network) { + return new this().fromReader(br, network); } - const hash = sha512.mac(seed, SEED_SALT); - const left = hash.slice(0, 32); - const right = hash.slice(32, 64); + /** + * Instantiate key from serialized data. + * @param {Buffer} data + * @param {(Network|NetworkType)?} network + * @returns {HDPrivateKey} + */ - // Only a 1 in 2^127 chance of happening. - if (!secp256k1.privateKeyVerify(left)) - throw new Error('Master private key is invalid.'); + static fromRaw(data, network) { + return new this().fromRaw(data, network); + } - this.depth = 0; - this.parentFingerPrint = 0; - this.childIndex = 0; - this.chainCode = right; - this.privateKey = left; - this.publicKey = secp256k1.publicKeyCreate(left, true); + /** + * Convert key to a more json-friendly object. + * @returns {Object} + */ - return this; -}; + toJSON(network) { + return { + xprivkey: this.xprivkey(network) + }; + } -/** - * Instantiate an hd private key from a 512 bit seed. - * @param {Buffer} seed - * @returns {HDPrivateKey} - */ + /** + * Inject properties from json object. + * @private + * @param {Object} json + * @param {Network?} network + */ -HDPrivateKey.fromSeed = function fromSeed(seed) { - return new HDPrivateKey().fromSeed(seed); -}; + fromJSON(json, network) { + assert(json.xprivkey, 'Could not handle key JSON.'); -/** - * Inject properties from a mnemonic. - * @private - * @param {Mnemonic} mnemonic - */ + this.fromBase58(json.xprivkey, network); -HDPrivateKey.prototype.fromMnemonic = function fromMnemonic(mnemonic) { - assert(mnemonic instanceof Mnemonic); - return this.fromSeed(mnemonic.toSeed()); -}; + return this; + } -/** - * Instantiate an hd private key from a mnemonic. - * @param {Mnemonic} mnemonic - * @returns {HDPrivateKey} - */ + /** + * Instantiate an HDPrivateKey from a jsonified key object. + * @param {Object} json - The jsonified key object. + * @param {Network?} network + * @returns {HDPrivateKey} + */ -HDPrivateKey.fromMnemonic = function fromMnemonic(mnemonic) { - return new HDPrivateKey().fromMnemonic(mnemonic); -}; + static fromJSON(json, network) { + return new this().fromJSON(json, network); + } -/** - * Inject properties from a mnemonic. - * @private - * @param {String} mnemonic - */ + /** + * Test whether an object is an HDPrivateKey. + * @param {Object} obj + * @returns {Boolean} + */ -HDPrivateKey.prototype.fromPhrase = function fromPhrase(phrase) { - const mnemonic = Mnemonic.fromPhrase(phrase); - this.fromMnemonic(mnemonic); - return this; -}; - -/** - * Instantiate an hd private key from a phrase. - * @param {String} phrase - * @returns {HDPrivateKey} - */ - -HDPrivateKey.fromPhrase = function fromPhrase(phrase) { - return new HDPrivateKey().fromPhrase(phrase); -}; - -/** - * Inject properties from privateKey and entropy. - * @private - * @param {Buffer} key - * @param {Buffer} entropy - */ - -HDPrivateKey.prototype.fromKey = function fromKey(key, entropy) { - assert(Buffer.isBuffer(key) && key.length === 32); - assert(Buffer.isBuffer(entropy) && entropy.length === 32); - this.depth = 0; - this.parentFingerPrint = 0; - this.childIndex = 0; - this.chainCode = entropy; - this.privateKey = key; - this.publicKey = secp256k1.publicKeyCreate(key, true); - return this; -}; - -/** - * Create an hd private key from a key and entropy bytes. - * @param {Buffer} key - * @param {Buffer} entropy - * @returns {HDPrivateKey} - */ - -HDPrivateKey.fromKey = function fromKey(key, entropy) { - return new HDPrivateKey().fromKey(key, entropy); -}; - -/** - * Generate an hd private key. - * @returns {HDPrivateKey} - */ - -HDPrivateKey.generate = function generate() { - const key = secp256k1.generatePrivateKey(); - const entropy = random.randomBytes(32); - return HDPrivateKey.fromKey(key, entropy); -}; - -/** - * Inject properties from base58 key. - * @private - * @param {Base58String} xkey - * @param {Network?} network - */ - -HDPrivateKey.prototype.fromBase58 = function fromBase58(xkey, network) { - assert(typeof xkey === 'string'); - return this.fromRaw(base58.decode(xkey), network); -}; - -/** - * Inject properties from serialized data. - * @private - * @param {BufferReader} br - * @param {(Network|NetworkType)?} network - */ - -HDPrivateKey.prototype.fromReader = function fromReader(br, network) { - const version = br.readU32BE(); - - Network.fromPrivate(version, network); - - this.depth = br.readU8(); - this.parentFingerPrint = br.readU32BE(); - this.childIndex = br.readU32BE(); - this.chainCode = br.readBytes(32); - assert(br.readU8() === 0); - this.privateKey = br.readBytes(32); - this.publicKey = secp256k1.publicKeyCreate(this.privateKey, true); - - br.verifyChecksum(hash256.digest); - - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @param {(Network|NetworkType)?} network - */ - -HDPrivateKey.prototype.fromRaw = function fromRaw(data, network) { - return this.fromReader(new BufferReader(data), network); -}; - -/** - * Serialize key to a base58 string. - * @param {(Network|NetworkType)?} network - * @returns {Base58String} - */ - -HDPrivateKey.prototype.toBase58 = function toBase58(network) { - return base58.encode(this.toRaw(network)); -}; - -/** - * Calculate serialization size. - * @returns {Number} - */ - -HDPrivateKey.prototype.getSize = function getSize() { - return 82; -}; - -/** - * Write the key to a buffer writer. - * @param {BufferWriter} bw - * @param {(Network|NetworkType)?} network - */ - -HDPrivateKey.prototype.toWriter = function toWriter(bw, network) { - network = Network.get(network); - - bw.writeU32BE(network.keyPrefix.xprivkey); - bw.writeU8(this.depth); - bw.writeU32BE(this.parentFingerPrint); - bw.writeU32BE(this.childIndex); - bw.writeBytes(this.chainCode); - bw.writeU8(0); - bw.writeBytes(this.privateKey); - bw.writeChecksum(hash256.digest); - - return bw; -}; - -/** - * Serialize the key. - * @param {(Network|NetworkType)?} network - * @returns {Buffer} - */ - -HDPrivateKey.prototype.toRaw = function toRaw(network) { - return this.toWriter(new StaticWriter(82), network).render(); -}; - -/** - * Instantiate an HD private key from a base58 string. - * @param {Base58String} xkey - * @param {Network?} network - * @returns {HDPrivateKey} - */ - -HDPrivateKey.fromBase58 = function fromBase58(xkey, network) { - return new HDPrivateKey().fromBase58(xkey, network); -}; - -/** - * Instantiate key from buffer reader. - * @param {BufferReader} br - * @param {(Network|NetworkType)?} network - * @returns {HDPrivateKey} - */ - -HDPrivateKey.fromReader = function fromReader(br, network) { - return new HDPrivateKey().fromReader(br, network); -}; - -/** - * Instantiate key from serialized data. - * @param {Buffer} data - * @param {(Network|NetworkType)?} network - * @returns {HDPrivateKey} - */ - -HDPrivateKey.fromRaw = function fromRaw(data, network) { - return new HDPrivateKey().fromRaw(data, network); -}; - -/** - * Convert key to a more json-friendly object. - * @returns {Object} - */ - -HDPrivateKey.prototype.toJSON = function toJSON(network) { - return { - xprivkey: this.xprivkey(network) - }; -}; - -/** - * Inject properties from json object. - * @private - * @param {Object} json - * @param {Network?} network - */ - -HDPrivateKey.prototype.fromJSON = function fromJSON(json, network) { - assert(json.xprivkey, 'Could not handle key JSON.'); - - this.fromBase58(json.xprivkey, network); - - return this; -}; - -/** - * Instantiate an HDPrivateKey from a jsonified key object. - * @param {Object} json - The jsonified key object. - * @param {Network?} network - * @returns {HDPrivateKey} - */ - -HDPrivateKey.fromJSON = function fromJSON(json, network) { - return new HDPrivateKey().fromJSON(json, network); -}; - -/** - * Test whether an object is an HDPrivateKey. - * @param {Object} obj - * @returns {Boolean} - */ - -HDPrivateKey.isHDPrivateKey = function isHDPrivateKey(obj) { - return obj instanceof HDPrivateKey; -}; + static isHDPrivateKey(obj) { + return obj instanceof HDPrivateKey; + } +} /* * Expose diff --git a/lib/hd/public.js b/lib/hd/public.js index 491f5db6..f783db41 100644 --- a/lib/hd/public.js +++ b/lib/hd/public.js @@ -22,14 +22,6 @@ const common = require('./common'); /** * HDPublicKey * @alias module:hd.PublicKey - * @constructor - * @param {Object|Base58String} options - * @param {Base58String?} options.xkey - Serialized base58 key. - * @param {Number?} options.depth - * @param {Number?} options.parentFingerPrint - * @param {Number?} options.childIndex - * @param {Buffer?} options.chainCode - * @param {Buffer?} options.publicKey * @property {Number} depth * @property {Number} parentFingerPrint * @property {Number} childIndex @@ -37,517 +29,528 @@ const common = require('./common'); * @property {Buffer} publicKey */ -function HDPublicKey(options) { - if (!(this instanceof HDPublicKey)) - return new HDPublicKey(options); +class HDPublicKey { + /** + * Create an HD public key. + * @constructor + * @param {Object|Base58String} options + * @param {Base58String?} options.xkey - Serialized base58 key. + * @param {Number?} options.depth + * @param {Number?} options.parentFingerPrint + * @param {Number?} options.childIndex + * @param {Buffer?} options.chainCode + * @param {Buffer?} options.publicKey + */ - this.depth = 0; - this.parentFingerPrint = 0; - this.childIndex = 0; - this.chainCode = encoding.ZERO_HASH; - this.publicKey = encoding.ZERO_KEY; + constructor(options) { + this.depth = 0; + this.parentFingerPrint = 0; + this.childIndex = 0; + this.chainCode = encoding.ZERO_HASH; + this.publicKey = encoding.ZERO_KEY; - this.fingerPrint = -1; + this.fingerPrint = -1; - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -HDPublicKey.prototype.fromOptions = function fromOptions(options) { - assert(options, 'No options for HDPublicKey'); - assert((options.depth & 0xff) === options.depth); - assert((options.parentFingerPrint >>> 0) === options.parentFingerPrint); - assert((options.childIndex >>> 0) === options.childIndex); - assert(Buffer.isBuffer(options.chainCode)); - assert(Buffer.isBuffer(options.publicKey)); - - this.depth = options.depth; - this.parentFingerPrint = options.parentFingerPrint; - this.childIndex = options.childIndex; - this.chainCode = options.chainCode; - this.publicKey = options.publicKey; - - return this; -}; - -/** - * Instantiate HD public key from options object. - * @param {Object} options - * @returns {HDPublicKey} - */ - -HDPublicKey.fromOptions = function fromOptions(options) { - return new HDPublicKey().fromOptions(options); -}; - -/** - * Get HD public key (self). - * @returns {HDPublicKey} - */ - -HDPublicKey.prototype.toPublic = function toPublic() { - return this; -}; - -/** - * Get cached base58 xprivkey (always null here). - * @returns {null} - */ - -HDPublicKey.prototype.xprivkey = function xprivkey(network) { - return null; -}; - -/** - * Get cached base58 xpubkey. - * @returns {Base58String} - */ - -HDPublicKey.prototype.xpubkey = function xpubkey(network) { - return this.toBase58(network); -}; - -/** - * Destroy the key (zeroes chain code and pubkey). - */ - -HDPublicKey.prototype.destroy = function destroy() { - this.depth = 0; - this.childIndex = 0; - this.parentFingerPrint = 0; - - cleanse(this.chainCode); - cleanse(this.publicKey); - - this.fingerPrint = -1; -}; - -/** - * Derive a child key. - * @param {Number} index - Derivation index. - * @param {Boolean?} hardened - Whether the derivation - * should be hardened (throws if true). - * @returns {HDPrivateKey} - * @throws on `hardened` - */ - -HDPublicKey.prototype.derive = function derive(index, hardened) { - assert(typeof index === 'number'); - - if ((index >>> 0) !== index) - throw new Error('Index out of range.'); - - if ((index & common.HARDENED) || hardened) - throw new Error('Cannot derive hardened.'); - - if (this.depth >= 0xff) - throw new Error('Depth too high.'); - - const id = this.getID(index); - const cache = common.cache.get(id); - - if (cache) - return cache; - - const bw = StaticWriter.pool(37); - - bw.writeBytes(this.publicKey); - bw.writeU32BE(index); - - const data = bw.render(); - - const hash = sha512.mac(data, this.chainCode); - const left = hash.slice(0, 32); - const right = hash.slice(32, 64); - - let key; - try { - key = secp256k1.publicKeyTweakAdd(this.publicKey, left, true); - } catch (e) { - return this.derive(index + 1); + if (options) + this.fromOptions(options); } - if (this.fingerPrint === -1) { - const fp = hash160.digest(this.publicKey); - this.fingerPrint = fp.readUInt32BE(0, true); + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + assert(options, 'No options for HDPublicKey'); + assert((options.depth & 0xff) === options.depth); + assert((options.parentFingerPrint >>> 0) === options.parentFingerPrint); + assert((options.childIndex >>> 0) === options.childIndex); + assert(Buffer.isBuffer(options.chainCode)); + assert(Buffer.isBuffer(options.publicKey)); + + this.depth = options.depth; + this.parentFingerPrint = options.parentFingerPrint; + this.childIndex = options.childIndex; + this.chainCode = options.chainCode; + this.publicKey = options.publicKey; + + return this; } - const child = new HDPublicKey(); - child.depth = this.depth + 1; - child.parentFingerPrint = this.fingerPrint; - child.childIndex = index; - child.chainCode = right; - child.publicKey = key; + /** + * Instantiate HD public key from options object. + * @param {Object} options + * @returns {HDPublicKey} + */ - common.cache.set(id, child); - - return child; -}; - -/** - * Unique HD key ID. - * @private - * @param {Number} index - * @returns {String} - */ - -HDPublicKey.prototype.getID = function getID(index) { - return 'b' + this.publicKey.toString('hex') + index; -}; - -/** - * Derive a BIP44 account key (does not derive, only ensures account key). - * @method - * @param {Number} purpose - * @param {Number} type - * @param {Number} account - * @returns {HDPublicKey} - * @throws Error if key is not already an account key. - */ - -HDPublicKey.prototype.deriveAccount = function deriveAccount(purpose, type, account) { - assert((purpose >>> 0) === purpose); - assert((type >>> 0) === type); - assert((account >>> 0) === account); - assert(this.isAccount(account), 'Cannot derive account index.'); - return this; -}; - -/** - * Test whether the key is a master key. - * @method - * @returns {Boolean} - */ - -HDPublicKey.prototype.isMaster = function isMaster() { - return common.isMaster(this); -}; - -/** - * Test whether the key is (most likely) a BIP44 account key. - * @method - * @param {Number?} account - * @returns {Boolean} - */ - -HDPublicKey.prototype.isAccount = function isAccount(account) { - return common.isAccount(this, account); -}; - -/** - * Test whether a string is a valid path. - * @param {String} path - * @param {Boolean?} hardened - * @returns {Boolean} - */ - -HDPublicKey.isValidPath = function isValidPath(path) { - try { - common.parsePath(path, false); - return true; - } catch (e) { - return false; + static fromOptions(options) { + return new this().fromOptions(options); } -}; -/** - * Derive a key from a derivation path. - * @param {String} path - * @returns {HDPublicKey} - * @throws Error if `path` is not a valid path. - * @throws Error if hardened. - */ + /** + * Get HD public key (self). + * @returns {HDPublicKey} + */ -HDPublicKey.prototype.derivePath = function derivePath(path) { - const indexes = common.parsePath(path, false); - - let key = this; - - for (const index of indexes) - key = key.derive(index); - - return key; -}; - -/** - * Compare a key against an object. - * @param {Object} obj - * @returns {Boolean} - */ - -HDPublicKey.prototype.equals = function equals(obj) { - assert(HDPublicKey.isHDPublicKey(obj)); - - return this.depth === obj.depth - && this.parentFingerPrint === obj.parentFingerPrint - && this.childIndex === obj.childIndex - && this.chainCode.equals(obj.chainCode) - && this.publicKey.equals(obj.publicKey); -}; - -/** - * Compare a key against an object. - * @param {Object} obj - * @returns {Boolean} - */ - -HDPublicKey.prototype.compare = function compare(key) { - assert(HDPublicKey.isHDPublicKey(key)); - - let cmp = this.depth - key.depth; - - if (cmp !== 0) - return cmp; - - cmp = this.parentFingerPrint - key.parentFingerPrint; - - if (cmp !== 0) - return cmp; - - cmp = this.childIndex - key.childIndex; - - if (cmp !== 0) - return cmp; - - cmp = this.chainCode.compare(key.chainCode); - - if (cmp !== 0) - return cmp; - - cmp = this.publicKey.compare(key.publicKey); - - if (cmp !== 0) - return cmp; - - return 0; -}; - -/** - * Convert key to a more json-friendly object. - * @returns {Object} - */ - -HDPublicKey.prototype.toJSON = function toJSON(network) { - return { - xpubkey: this.xpubkey(network) - }; -}; - -/** - * Inject properties from json object. - * @private - * @param {Object} json - * @param {Network?} network - */ - -HDPublicKey.prototype.fromJSON = function fromJSON(json, network) { - assert(json.xpubkey, 'Could not handle HD key JSON.'); - this.fromBase58(json.xpubkey, network); - return this; -}; - -/** - * Instantiate an HDPublicKey from a jsonified key object. - * @param {Object} json - The jsonified transaction object. - * @param {Network?} network - * @returns {HDPrivateKey} - */ - -HDPublicKey.fromJSON = function fromJSON(json, network) { - return new HDPublicKey().fromJSON(json, network); -}; - -/** - * Test whether an object is in the form of a base58 xpubkey. - * @param {String} data - * @param {(Network|NetworkType)?} network - * @returns {Boolean} - */ - -HDPublicKey.isBase58 = function isBase58(data, network) { - if (typeof data !== 'string') - return false; - - if (data.length < 4) - return false; - - const prefix = data.substring(0, 4); - - try { - Network.fromPublic58(prefix, network); - return true; - } catch (e) { - return false; + toPublic() { + return this; } -}; -/** - * Test whether a buffer has a valid network prefix. - * @param {Buffer} data - * @param {(Network|NetworkType)?} network - * @returns {NetworkType} - */ + /** + * Get cached base58 xprivkey (always null here). + * @returns {null} + */ -HDPublicKey.isRaw = function isRaw(data, network) { - if (!Buffer.isBuffer(data)) - return false; + xprivkey(network) { + return null; + } - if (data.length < 4) - return false; + /** + * Get cached base58 xpubkey. + * @returns {Base58String} + */ - const version = data.readUInt32BE(0, true); + xpubkey(network) { + return this.toBase58(network); + } + + /** + * Destroy the key (zeroes chain code and pubkey). + */ + + destroy() { + this.depth = 0; + this.childIndex = 0; + this.parentFingerPrint = 0; + + cleanse(this.chainCode); + cleanse(this.publicKey); + + this.fingerPrint = -1; + } + + /** + * Derive a child key. + * @param {Number} index - Derivation index. + * @param {Boolean?} hardened - Whether the derivation + * should be hardened (throws if true). + * @returns {HDPrivateKey} + * @throws on `hardened` + */ + + derive(index, hardened) { + assert(typeof index === 'number'); + + if ((index >>> 0) !== index) + throw new Error('Index out of range.'); + + if ((index & common.HARDENED) || hardened) + throw new Error('Cannot derive hardened.'); + + if (this.depth >= 0xff) + throw new Error('Depth too high.'); + + const id = this.getID(index); + const cache = common.cache.get(id); + + if (cache) + return cache; + + const bw = StaticWriter.pool(37); + + bw.writeBytes(this.publicKey); + bw.writeU32BE(index); + + const data = bw.render(); + + const hash = sha512.mac(data, this.chainCode); + const left = hash.slice(0, 32); + const right = hash.slice(32, 64); + + let key; + try { + key = secp256k1.publicKeyTweakAdd(this.publicKey, left, true); + } catch (e) { + return this.derive(index + 1); + } + + if (this.fingerPrint === -1) { + const fp = hash160.digest(this.publicKey); + this.fingerPrint = fp.readUInt32BE(0, true); + } + + const child = new this.constructor(); + child.depth = this.depth + 1; + child.parentFingerPrint = this.fingerPrint; + child.childIndex = index; + child.chainCode = right; + child.publicKey = key; + + common.cache.set(id, child); + + return child; + } + + /** + * Unique HD key ID. + * @private + * @param {Number} index + * @returns {String} + */ + + getID(index) { + return 'b' + this.publicKey.toString('hex') + index; + } + + /** + * Derive a BIP44 account key (does not derive, only ensures account key). + * @method + * @param {Number} purpose + * @param {Number} type + * @param {Number} account + * @returns {HDPublicKey} + * @throws Error if key is not already an account key. + */ + + deriveAccount(purpose, type, account) { + assert((purpose >>> 0) === purpose); + assert((type >>> 0) === type); + assert((account >>> 0) === account); + assert(this.isAccount(account), 'Cannot derive account index.'); + return this; + } + + /** + * Test whether the key is a master key. + * @method + * @returns {Boolean} + */ + + isMaster() { + return common.isMaster(this); + } + + /** + * Test whether the key is (most likely) a BIP44 account key. + * @method + * @param {Number?} account + * @returns {Boolean} + */ + + isAccount(account) { + return common.isAccount(this, account); + } + + /** + * Test whether a string is a valid path. + * @param {String} path + * @param {Boolean?} hardened + * @returns {Boolean} + */ + + static isValidPath(path) { + try { + common.parsePath(path, false); + return true; + } catch (e) { + return false; + } + } + + /** + * Derive a key from a derivation path. + * @param {String} path + * @returns {HDPublicKey} + * @throws Error if `path` is not a valid path. + * @throws Error if hardened. + */ + + derivePath(path) { + const indexes = common.parsePath(path, false); + + let key = this; + + for (const index of indexes) + key = key.derive(index); + + return key; + } + + /** + * Compare a key against an object. + * @param {Object} obj + * @returns {Boolean} + */ + + equals(obj) { + assert(HDPublicKey.isHDPublicKey(obj)); + + return this.depth === obj.depth + && this.parentFingerPrint === obj.parentFingerPrint + && this.childIndex === obj.childIndex + && this.chainCode.equals(obj.chainCode) + && this.publicKey.equals(obj.publicKey); + } + + /** + * Compare a key against an object. + * @param {Object} obj + * @returns {Boolean} + */ + + compare(key) { + assert(HDPublicKey.isHDPublicKey(key)); + + let cmp = this.depth - key.depth; + + if (cmp !== 0) + return cmp; + + cmp = this.parentFingerPrint - key.parentFingerPrint; + + if (cmp !== 0) + return cmp; + + cmp = this.childIndex - key.childIndex; + + if (cmp !== 0) + return cmp; + + cmp = this.chainCode.compare(key.chainCode); + + if (cmp !== 0) + return cmp; + + cmp = this.publicKey.compare(key.publicKey); + + if (cmp !== 0) + return cmp; + + return 0; + } + + /** + * Convert key to a more json-friendly object. + * @returns {Object} + */ + + toJSON(network) { + return { + xpubkey: this.xpubkey(network) + }; + } + + /** + * Inject properties from json object. + * @private + * @param {Object} json + * @param {Network?} network + */ + + fromJSON(json, network) { + assert(json.xpubkey, 'Could not handle HD key JSON.'); + this.fromBase58(json.xpubkey, network); + return this; + } + + /** + * Instantiate an HDPublicKey from a jsonified key object. + * @param {Object} json - The jsonified transaction object. + * @param {Network?} network + * @returns {HDPrivateKey} + */ + + static fromJSON(json, network) { + return new this().fromJSON(json, network); + } + + /** + * Test whether an object is in the form of a base58 xpubkey. + * @param {String} data + * @param {(Network|NetworkType)?} network + * @returns {Boolean} + */ + + static isBase58(data, network) { + if (typeof data !== 'string') + return false; + + if (data.length < 4) + return false; + + const prefix = data.substring(0, 4); + + try { + Network.fromPublic58(prefix, network); + return true; + } catch (e) { + return false; + } + } + + /** + * Test whether a buffer has a valid network prefix. + * @param {Buffer} data + * @param {(Network|NetworkType)?} network + * @returns {NetworkType} + */ + + static isRaw(data, network) { + if (!Buffer.isBuffer(data)) + return false; + + if (data.length < 4) + return false; + + const version = data.readUInt32BE(0, true); + + try { + Network.fromPublic(version, network); + return true; + } catch (e) { + return false; + } + } + + /** + * Inject properties from a base58 key. + * @private + * @param {Base58String} xkey + * @param {Network?} network + */ + + fromBase58(xkey, network) { + assert(typeof xkey === 'string'); + return this.fromRaw(base58.decode(xkey), network); + } + + /** + * Inject properties from serialized data. + * @private + * @param {BufferReader} br + * @param {(Network|NetworkType)?} network + */ + + fromReader(br, network) { + const version = br.readU32BE(); - try { Network.fromPublic(version, network); - return true; - } catch (e) { - return false; + + this.depth = br.readU8(); + this.parentFingerPrint = br.readU32BE(); + this.childIndex = br.readU32BE(); + this.chainCode = br.readBytes(32); + this.publicKey = br.readBytes(33); + + br.verifyChecksum(hash256.digest); + + return this; } -}; -/** - * Inject properties from a base58 key. - * @private - * @param {Base58String} xkey - * @param {Network?} network - */ + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @param {(Network|NetworkType)?} network + */ -HDPublicKey.prototype.fromBase58 = function fromBase58(xkey, network) { - assert(typeof xkey === 'string'); - return this.fromRaw(base58.decode(xkey), network); -}; + fromRaw(data, network) { + return this.fromReader(new BufferReader(data), network); + } -/** - * Inject properties from serialized data. - * @private - * @param {BufferReader} br - * @param {(Network|NetworkType)?} network - */ + /** + * Serialize key data to base58 extended key. + * @param {(Network|NetworkType)?} network + * @returns {Base58String} + */ -HDPublicKey.prototype.fromReader = function fromReader(br, network) { - const version = br.readU32BE(); + toBase58(network) { + return base58.encode(this.toRaw(network)); + } - Network.fromPublic(version, network); + /** + * Write the key to a buffer writer. + * @param {BufferWriter} bw + * @param {(Network|NetworkType)?} network + */ - this.depth = br.readU8(); - this.parentFingerPrint = br.readU32BE(); - this.childIndex = br.readU32BE(); - this.chainCode = br.readBytes(32); - this.publicKey = br.readBytes(33); + toWriter(bw, network) { + network = Network.get(network); - br.verifyChecksum(hash256.digest); + bw.writeU32BE(network.keyPrefix.xpubkey); + bw.writeU8(this.depth); + bw.writeU32BE(this.parentFingerPrint); + bw.writeU32BE(this.childIndex); + bw.writeBytes(this.chainCode); + bw.writeBytes(this.publicKey); + bw.writeChecksum(hash256.digest); - return this; -}; + return bw; + } -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @param {(Network|NetworkType)?} network - */ + /** + * Calculate serialization size. + * @returns {Number} + */ -HDPublicKey.prototype.fromRaw = function fromRaw(data, network) { - return this.fromReader(new BufferReader(data), network); -}; + getSize() { + return 82; + } -/** - * Serialize key data to base58 extended key. - * @param {(Network|NetworkType)?} network - * @returns {Base58String} - */ + /** + * Serialize the key. + * @param {(Network|NetworkType)?} network + * @returns {Buffer} + */ -HDPublicKey.prototype.toBase58 = function toBase58(network) { - return base58.encode(this.toRaw(network)); -}; + toRaw(network) { + return this.toWriter(new StaticWriter(82), network).render(); + } -/** - * Write the key to a buffer writer. - * @param {BufferWriter} bw - * @param {(Network|NetworkType)?} network - */ + /** + * Instantiate an HD public key from a base58 string. + * @param {Base58String} xkey + * @param {Network?} network + * @returns {HDPublicKey} + */ -HDPublicKey.prototype.toWriter = function toWriter(bw, network) { - network = Network.get(network); + static fromBase58(xkey, network) { + return new this().fromBase58(xkey, network); + } - bw.writeU32BE(network.keyPrefix.xpubkey); - bw.writeU8(this.depth); - bw.writeU32BE(this.parentFingerPrint); - bw.writeU32BE(this.childIndex); - bw.writeBytes(this.chainCode); - bw.writeBytes(this.publicKey); - bw.writeChecksum(hash256.digest); + /** + * Instantiate key from serialized data. + * @param {BufferReader} br + * @param {(Network|NetworkType)?} network + * @returns {HDPublicKey} + */ - return bw; -}; + static fromReader(br, network) { + return new this().fromReader(br, network); + } -/** - * Calculate serialization size. - * @returns {Number} - */ + /** + * Instantiate key from serialized data. + * @param {Buffer} data + * @param {(Network|NetworkType)?} network + * @returns {HDPublicKey} + */ -HDPublicKey.prototype.getSize = function getSize() { - return 82; -}; + static fromRaw(data, network) { + return new this().fromRaw(data, network); + } -/** - * Serialize the key. - * @param {(Network|NetworkType)?} network - * @returns {Buffer} - */ + /** + * Test whether an object is a HDPublicKey. + * @param {Object} obj + * @returns {Boolean} + */ -HDPublicKey.prototype.toRaw = function toRaw(network) { - return this.toWriter(new StaticWriter(82), network).render(); -}; - -/** - * Instantiate an HD public key from a base58 string. - * @param {Base58String} xkey - * @param {Network?} network - * @returns {HDPublicKey} - */ - -HDPublicKey.fromBase58 = function fromBase58(xkey, network) { - return new HDPublicKey().fromBase58(xkey, network); -}; - -/** - * Instantiate key from serialized data. - * @param {BufferReader} br - * @param {(Network|NetworkType)?} network - * @returns {HDPublicKey} - */ - -HDPublicKey.fromReader = function fromReader(br, network) { - return new HDPublicKey().fromReader(br, network); -}; - -/** - * Instantiate key from serialized data. - * @param {Buffer} data - * @param {(Network|NetworkType)?} network - * @returns {HDPublicKey} - */ - -HDPublicKey.fromRaw = function fromRaw(data, network) { - return new HDPublicKey().fromRaw(data, network); -}; - -/** - * Test whether an object is a HDPublicKey. - * @param {Object} obj - * @returns {Boolean} - */ - -HDPublicKey.isHDPublicKey = function isHDPublicKey(obj) { - return obj instanceof HDPublicKey; -}; + static isHDPublicKey(obj) { + return obj instanceof HDPublicKey; + } +} /* * Expose diff --git a/lib/mempool/mempoolentry.js b/lib/mempool/mempoolentry.js index 242dfdae..b3293fd4 100644 --- a/lib/mempool/mempoolentry.js +++ b/lib/mempool/mempoolentry.js @@ -14,15 +14,9 @@ const StaticWriter = require('bufio/lib/staticwriter'); const TX = require('../primitives/tx'); /** + * Mempool Entry * Represents a mempool entry. * @alias module:mempool.MempoolEntry - * @constructor - * @param {Object} options - * @param {TX} options.tx - Transaction in mempool. - * @param {Number} options.height - Entry height. - * @param {Number} options.priority - Entry priority. - * @param {Number} options.time - Entry time. - * @param {Amount} options.value - Value of on-chain coins. * @property {TX} tx * @property {Number} height * @property {Number} priority @@ -30,338 +24,351 @@ const TX = require('../primitives/tx'); * @property {Amount} value */ -function MempoolEntry(options) { - if (!(this instanceof MempoolEntry)) - return new MempoolEntry(options); +class MempoolEntry { + /** + * Create a mempool entry. + * @constructor + * @param {Object} options + * @param {TX} options.tx - Transaction in mempool. + * @param {Number} options.height - Entry height. + * @param {Number} options.priority - Entry priority. + * @param {Number} options.time - Entry time. + * @param {Amount} options.value - Value of on-chain coins. + */ - this.tx = null; - this.height = -1; - this.size = 0; - this.sigops = 0; - this.priority = 0; - this.fee = 0; - this.deltaFee = 0; - this.time = 0; - this.value = 0; - this.coinbase = false; - this.dependencies = false; - this.descFee = 0; - this.descSize = 0; + constructor(options) { + this.tx = null; + this.height = -1; + this.size = 0; + this.sigops = 0; + this.priority = 0; + this.fee = 0; + this.deltaFee = 0; + this.time = 0; + this.value = 0; + this.coinbase = false; + this.dependencies = false; + this.descFee = 0; + this.descSize = 0; - if (options) - this.fromOptions(options); + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + this.tx = options.tx; + this.height = options.height; + this.size = options.size; + this.sigops = options.sigops; + this.priority = options.priority; + this.fee = options.fee; + this.deltaFee = options.deltaFee; + this.time = options.time; + this.value = options.value; + this.coinbase = options.coinbase; + this.dependencies = options.dependencies; + this.descFee = options.descFee; + this.descSize = options.descSize; + return this; + } + + /** + * Instantiate mempool entry from options. + * @param {Object} options + * @returns {MempoolEntry} + */ + + static fromOptions(options) { + return new this().fromOptions(options); + } + + /** + * Inject properties from transaction. + * @private + * @param {TX} tx + * @param {Number} height + */ + + fromTX(tx, view, height) { + const flags = Script.flags.STANDARD_VERIFY_FLAGS; + const value = tx.getChainValue(view); + const sigops = tx.getSigopsCost(view, flags); + const size = tx.getSigopsSize(sigops); + const priority = tx.getPriority(view, height, size); + const fee = tx.getFee(view); + + let dependencies = false; + let coinbase = false; + + for (const {prevout} of tx.inputs) { + if (view.isCoinbase(prevout)) + coinbase = true; + + if (view.getHeight(prevout) === -1) + dependencies = true; + } + + this.tx = tx; + this.height = height; + this.size = size; + this.sigops = sigops; + this.priority = priority; + this.fee = fee; + this.deltaFee = fee; + this.time = util.now(); + this.value = value; + this.coinbase = coinbase; + this.dependencies = dependencies; + this.descFee = fee; + this.descSize = size; + + return this; + } + + /** + * Create a mempool entry from a TX. + * @param {TX} tx + * @param {Number} height - Entry height. + * @returns {MempoolEntry} + */ + + static fromTX(tx, view, height) { + return new this().fromTX(tx, view, height); + } + + /** + * Calculate transaction hash. + * @param {String?} enc + * @returns {Hash} + */ + + hash(enc) { + return this.tx.hash(enc); + } + + /** + * Calculate reverse transaction hash. + * @returns {Hash} + */ + + txid() { + return this.tx.txid(); + } + + /** + * Calculate priority, taking into account + * the entry height delta, modified size, + * and chain value. + * @param {Number} height + * @returns {Number} Priority. + */ + + getPriority(height) { + const delta = height - this.height; + const priority = (delta * this.value) / this.size; + + let result = this.priority + Math.floor(priority); + + if (result < 0) + result = 0; + + return result; + } + + /** + * Get fee. + * @returns {Amount} + */ + + getFee() { + return this.fee; + } + + /** + * Get delta fee. + * @returns {Amount} + */ + + getDeltaFee() { + return this.deltaFee; + } + + /** + * Calculate fee rate. + * @returns {Rate} + */ + + getRate() { + return policy.getRate(this.size, this.fee); + } + + /** + * Calculate delta fee rate. + * @returns {Rate} + */ + + getDeltaRate() { + return policy.getRate(this.size, this.deltaFee); + } + + /** + * Calculate fee cumulative descendant rate. + * @returns {Rate} + */ + + getDescRate() { + return policy.getRate(this.descSize, this.descFee); + } + + /** + * Calculate the memory usage of a transaction. + * Note that this only calculates the JS heap + * size. Sizes of buffers are ignored (the v8 + * heap is what we care most about). All numbers + * are based on the output of v8 heap snapshots + * of TX objects. + * @returns {Number} Usage in bytes. + */ + + memUsage() { + const tx = this.tx; + let total = 0; + + total += 176; // mempool entry + total += 48; // coinbase + total += 48; // dependencies + + total += 208; // tx + total += 80; // _hash + total += 88; // _hhash + total += 80; // _raw + total += 80; // _whash + total += 48; // mutable + + total += 32; // input array + + for (const input of tx.inputs) { + total += 120; // input + total += 104; // prevout + total += 88; // prevout hash + + total += 40; // script + total += 80; // script raw buffer + total += 32; // script code array + total += input.script.code.length * 40; // opcodes + + for (const op of input.script.code) { + if (op.data) + total += 80; // op buffers + } + + total += 96; // witness + total += 32; // witness items + total += input.witness.items.length * 80; // witness buffers + } + + total += 32; // output array + + for (const output of tx.outputs) { + total += 104; // output + total += 40; // script + total += 80; // script raw buffer + total += 32; // script code array + total += output.script.code.length * 40; // opcodes + + for (const op of output.script.code) { + if (op.data) + total += 80; // op buffers + } + } + + return total; + } + + /** + * Test whether the entry is free with + * the current priority (calculated by + * current height). + * @param {Number} height + * @returns {Boolean} + */ + + isFree(height) { + const priority = this.getPriority(height); + return priority > policy.FREE_THRESHOLD; + } + + /** + * Get entry serialization size. + * @returns {Number} + */ + + getSize() { + return this.tx.getSize() + 42; + } + + /** + * Serialize entry to a buffer. + * @returns {Buffer} + */ + + toRaw() { + const bw = new StaticWriter(this.getSize()); + bw.writeBytes(this.tx.toRaw()); + bw.writeU32(this.height); + bw.writeU32(this.size); + bw.writeU32(this.sigops); + bw.writeDouble(this.priority); + bw.writeU64(this.fee); + bw.writeU32(this.time); + bw.writeU64(this.value); + bw.writeU8(this.coinbase ? 1 : 0); + bw.writeU8(this.dependencies ? 1 : 0); + return bw.render(); + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + * @returns {MempoolEntry} + */ + + fromRaw(data) { + const br = new BufferReader(data); + this.tx = TX.fromReader(br); + this.height = br.readU32(); + this.size = br.readU32(); + this.sigops = br.readU32(); + this.priority = br.readDouble(); + this.fee = br.readU64(); + this.deltaFee = this.fee; + this.time = br.readU32(); + this.value = br.readU64(); + this.coinbase = br.readU8() === 1; + this.dependencies = br.readU8() === 1; + this.descFee = this.fee; + this.descSize = this.size; + return this; + } + + /** + * Instantiate entry from serialized data. + * @param {Buffer} data + * @returns {MempoolEntry} + */ + + static fromRaw(data) { + return new this().fromRaw(data); + } } -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -MempoolEntry.prototype.fromOptions = function fromOptions(options) { - this.tx = options.tx; - this.height = options.height; - this.size = options.size; - this.sigops = options.sigops; - this.priority = options.priority; - this.fee = options.fee; - this.deltaFee = options.deltaFee; - this.time = options.time; - this.value = options.value; - this.coinbase = options.coinbase; - this.dependencies = options.dependencies; - this.descFee = options.descFee; - this.descSize = options.descSize; - return this; -}; - -/** - * Instantiate mempool entry from options. - * @param {Object} options - * @returns {MempoolEntry} - */ - -MempoolEntry.fromOptions = function fromOptions(options) { - return new MempoolEntry().fromOptions(options); -}; - -/** - * Inject properties from transaction. - * @private - * @param {TX} tx - * @param {Number} height - */ - -MempoolEntry.prototype.fromTX = function fromTX(tx, view, height) { - const flags = Script.flags.STANDARD_VERIFY_FLAGS; - const value = tx.getChainValue(view); - const sigops = tx.getSigopsCost(view, flags); - const size = tx.getSigopsSize(sigops); - const priority = tx.getPriority(view, height, size); - const fee = tx.getFee(view); - - let dependencies = false; - let coinbase = false; - - for (const {prevout} of tx.inputs) { - if (view.isCoinbase(prevout)) - coinbase = true; - - if (view.getHeight(prevout) === -1) - dependencies = true; - } - - this.tx = tx; - this.height = height; - this.size = size; - this.sigops = sigops; - this.priority = priority; - this.fee = fee; - this.deltaFee = fee; - this.time = util.now(); - this.value = value; - this.coinbase = coinbase; - this.dependencies = dependencies; - this.descFee = fee; - this.descSize = size; - - return this; -}; - -/** - * Create a mempool entry from a TX. - * @param {TX} tx - * @param {Number} height - Entry height. - * @returns {MempoolEntry} - */ - -MempoolEntry.fromTX = function fromTX(tx, view, height) { - return new MempoolEntry().fromTX(tx, view, height); -}; - -/** - * Calculate transaction hash. - * @param {String?} enc - * @returns {Hash} - */ - -MempoolEntry.prototype.hash = function hash(enc) { - return this.tx.hash(enc); -}; - -/** - * Calculate reverse transaction hash. - * @returns {Hash} - */ - -MempoolEntry.prototype.txid = function txid() { - return this.tx.txid(); -}; - -/** - * Calculate priority, taking into account - * the entry height delta, modified size, - * and chain value. - * @param {Number} height - * @returns {Number} Priority. - */ - -MempoolEntry.prototype.getPriority = function getPriority(height) { - const delta = height - this.height; - const priority = (delta * this.value) / this.size; - let result = this.priority + Math.floor(priority); - if (result < 0) - result = 0; - return result; -}; - -/** - * Get fee. - * @returns {Amount} - */ - -MempoolEntry.prototype.getFee = function getFee() { - return this.fee; -}; - -/** - * Get delta fee. - * @returns {Amount} - */ - -MempoolEntry.prototype.getDeltaFee = function getDeltaFee() { - return this.deltaFee; -}; - -/** - * Calculate fee rate. - * @returns {Rate} - */ - -MempoolEntry.prototype.getRate = function getRate() { - return policy.getRate(this.size, this.fee); -}; - -/** - * Calculate delta fee rate. - * @returns {Rate} - */ - -MempoolEntry.prototype.getDeltaRate = function getDeltaRate() { - return policy.getRate(this.size, this.deltaFee); -}; - -/** - * Calculate fee cumulative descendant rate. - * @returns {Rate} - */ - -MempoolEntry.prototype.getDescRate = function getDescRate() { - return policy.getRate(this.descSize, this.descFee); -}; - -/** - * Calculate the memory usage of a transaction. - * Note that this only calculates the JS heap - * size. Sizes of buffers are ignored (the v8 - * heap is what we care most about). All numbers - * are based on the output of v8 heap snapshots - * of TX objects. - * @returns {Number} Usage in bytes. - */ - -MempoolEntry.prototype.memUsage = function memUsage() { - const tx = this.tx; - let total = 0; - - total += 176; // mempool entry - total += 48; // coinbase - total += 48; // dependencies - - total += 208; // tx - total += 80; // _hash - total += 88; // _hhash - total += 80; // _raw - total += 80; // _whash - total += 48; // mutable - - total += 32; // input array - - for (const input of tx.inputs) { - total += 120; // input - total += 104; // prevout - total += 88; // prevout hash - - total += 40; // script - total += 80; // script raw buffer - total += 32; // script code array - total += input.script.code.length * 40; // opcodes - - for (const op of input.script.code) { - if (op.data) - total += 80; // op buffers - } - - total += 96; // witness - total += 32; // witness items - total += input.witness.items.length * 80; // witness buffers - } - - total += 32; // output array - - for (const output of tx.outputs) { - total += 104; // output - total += 40; // script - total += 80; // script raw buffer - total += 32; // script code array - total += output.script.code.length * 40; // opcodes - - for (const op of output.script.code) { - if (op.data) - total += 80; // op buffers - } - } - - return total; -}; - -/** - * Test whether the entry is free with - * the current priority (calculated by - * current height). - * @param {Number} height - * @returns {Boolean} - */ - -MempoolEntry.prototype.isFree = function isFree(height) { - const priority = this.getPriority(height); - return priority > policy.FREE_THRESHOLD; -}; - -/** - * Get entry serialization size. - * @returns {Number} - */ - -MempoolEntry.prototype.getSize = function getSize() { - return this.tx.getSize() + 42; -}; - -/** - * Serialize entry to a buffer. - * @returns {Buffer} - */ - -MempoolEntry.prototype.toRaw = function toRaw() { - const bw = new StaticWriter(this.getSize()); - bw.writeBytes(this.tx.toRaw()); - bw.writeU32(this.height); - bw.writeU32(this.size); - bw.writeU32(this.sigops); - bw.writeDouble(this.priority); - bw.writeU64(this.fee); - bw.writeU32(this.time); - bw.writeU64(this.value); - bw.writeU8(this.coinbase ? 1 : 0); - bw.writeU8(this.dependencies ? 1 : 0); - return bw.render(); -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {MempoolEntry} - */ - -MempoolEntry.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - this.tx = TX.fromReader(br); - this.height = br.readU32(); - this.size = br.readU32(); - this.sigops = br.readU32(); - this.priority = br.readDouble(); - this.fee = br.readU64(); - this.deltaFee = this.fee; - this.time = br.readU32(); - this.value = br.readU64(); - this.coinbase = br.readU8() === 1; - this.dependencies = br.readU8() === 1; - this.descFee = this.fee; - this.descSize = this.size; - return this; -}; - -/** - * Instantiate entry from serialized data. - * @param {Buffer} data - * @returns {MempoolEntry} - */ - -MempoolEntry.fromRaw = function fromRaw(data) { - return new MempoolEntry().fromRaw(data); -}; - /* * Expose */ diff --git a/lib/mining/template.js b/lib/mining/template.js index 7a7a4bd2..8e362ea1 100644 --- a/lib/mining/template.js +++ b/lib/mining/template.js @@ -27,562 +27,563 @@ const DUMMY = Buffer.alloc(0); /** * Block Template * @alias module:mining.BlockTemplate - * @constructor - * @param {Object} options */ -function BlockTemplate(options) { - if (!(this instanceof BlockTemplate)) - return new BlockTemplate(options); +class BlockTemplate { + /** + * Create a block template. + * @constructor + * @param {Object} options + */ - this.prevBlock = encoding.NULL_HASH; - this.version = 1; - this.height = 0; - this.time = 0; - this.bits = 0; - this.target = encoding.ZERO_HASH; - this.locktime = 0; - this.mtp = 0; - this.flags = 0; - this.coinbaseFlags = DUMMY; - this.witness = false; - this.address = new Address(); - this.sigops = 400; - this.weight = 4000; - this.interval = 210000; - this.fees = 0; - this.tree = new MerkleTree(); - this.commitment = encoding.ZERO_HASH; - this.left = DUMMY; - this.right = DUMMY; - this.items = []; + constructor(options) { + this.prevBlock = encoding.NULL_HASH; + this.version = 1; + this.height = 0; + this.time = 0; + this.bits = 0; + this.target = encoding.ZERO_HASH; + this.locktime = 0; + this.mtp = 0; + this.flags = 0; + this.coinbaseFlags = DUMMY; + this.witness = false; + this.address = new Address(); + this.sigops = 400; + this.weight = 4000; + this.interval = 210000; + this.fees = 0; + this.tree = new MerkleTree(); + this.commitment = encoding.ZERO_HASH; + this.left = DUMMY; + this.right = DUMMY; + this.items = []; - if (options) - this.fromOptions(options); + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from options. + * @private + * @param {Object} options + * @returns {BlockTemplate} + */ + + fromOptions(options) { + assert(options); + + if (options.prevBlock != null) { + assert(typeof options.prevBlock === 'string'); + this.prevBlock = options.prevBlock; + } + + if (options.version != null) { + assert(typeof options.version === 'number'); + this.version = options.version; + } + + if (options.height != null) { + assert(typeof options.height === 'number'); + this.height = options.height; + } + + if (options.time != null) { + assert(typeof options.time === 'number'); + this.time = options.time; + } + + if (options.bits != null) + this.setBits(options.bits); + + if (options.target != null) + this.setTarget(options.target); + + if (options.locktime != null) { + assert(typeof options.locktime === 'number'); + this.locktime = options.locktime; + } + + if (options.mtp != null) { + assert(typeof options.mtp === 'number'); + this.mtp = options.mtp; + } + + if (options.flags != null) { + assert(typeof options.flags === 'number'); + this.flags = options.flags; + } + + if (options.coinbaseFlags != null) { + assert(Buffer.isBuffer(options.coinbaseFlags)); + this.coinbaseFlags = options.coinbaseFlags; + } + + if (options.witness != null) { + assert(typeof options.witness === 'boolean'); + this.witness = options.witness; + } + + if (options.address != null) + this.address.fromOptions(options.address); + + if (options.sigops != null) { + assert(typeof options.sigops === 'number'); + this.sigops = options.sigops; + } + + if (options.weight != null) { + assert(typeof options.weight === 'number'); + this.weight = options.weight; + } + + if (options.interval != null) { + assert(typeof options.interval === 'number'); + this.interval = options.interval; + } + + if (options.fees != null) { + assert(typeof options.fees === 'number'); + this.fees = options.fees; + } + + if (options.items != null) { + assert(Array.isArray(options.items)); + this.items = options.items; + } + + return this; + } + + /** + * Instantiate block template from options. + * @param {Object} options + * @returns {BlockTemplate} + */ + + static fromOptions(options) { + return new this().fromOptions(options); + } + + /** + * Create witness commitment hash. + * @returns {Buffer} + */ + + getWitnessHash() { + const nonce = encoding.ZERO_HASH; + const leaves = []; + + leaves.push(encoding.ZERO_HASH); + + for (const {tx} of this.items) + leaves.push(tx.witnessHash()); + + const [root, malleated] = merkle.createRoot(hash256, leaves); + + assert(!malleated); + + return hash256.root(root, nonce); + } + + /** + * Create witness commitment script. + * @returns {Script} + */ + + getWitnessScript() { + return Script.fromCommitment(this.commitment); + } + + /** + * Set the target (bits). + * @param {Number} bits + */ + + setBits(bits) { + assert(typeof bits === 'number'); + this.bits = bits; + this.target = common.getTarget(bits); + } + + /** + * Set the target (uint256le). + * @param {Buffer} target + */ + + setTarget(target) { + assert(Buffer.isBuffer(target)); + this.bits = common.getBits(target); + this.target = target; + } + + /** + * Calculate the block reward. + * @returns {Amount} + */ + + getReward() { + const reward = consensus.getReward(this.height, this.interval); + return reward + this.fees; + } + + /** + * Initialize the default coinbase. + * @param {Buffer} hash - Witness commitment hash. + * @returns {TX} + */ + + createCoinbase(hash) { + const scale = consensus.WITNESS_SCALE_FACTOR; + const cb = new TX(); + + // Coinbase input. + const input = new Input(); + + // Height (required in v2+ blocks) + input.script.pushInt(this.height); + + // Coinbase flags. + input.script.pushData(encoding.ZERO_HASH160); + + // Smaller nonce for good measure. + const nonce = Buffer.allocUnsafe(4); + nonce.writeUInt32LE(Math.random() * 0x100000000, 0, true); + input.script.pushData(nonce); + + // Extra nonce: incremented when + // the nonce overflows. + input.script.pushData(encoding.ZERO_U64); + + input.script.compile(); + + // Set up the witness nonce. + if (this.witness) { + input.witness.push(encoding.ZERO_HASH); + input.witness.compile(); + } + + cb.inputs.push(input); + + // Reward output. + const output = new Output(); + output.script.fromPubkeyhash(encoding.ZERO_HASH160); + output.value = this.getReward(); + + cb.outputs.push(output); + + // If we're using segwit, we + // need to set up the commitment. + if (this.witness) { + // Commitment output. + const commit = new Output(); + commit.script.fromCommitment(hash); + cb.outputs.push(commit); + } + + // Padding for the CB height (constant size). + const op = input.script.get(0); + assert(op); + const padding = 5 - op.getSize(); + assert(padding >= 0); + + // Reserved size. + // Without segwit: + // CB weight = 500 + // CB stripped size = 125 + // CB size = 125 + // Sigops cost = 4 + // With segwit: + // CB weight = 724 + // CB stripped size = 172 + // CB size = 208 + // Sigops cost = 4 + if (!this.witness) { + assert.strictEqual(cb.getWeight() + padding * scale, 500); + assert.strictEqual(cb.getBaseSize() + padding, 125); + assert.strictEqual(cb.getSize() + padding, 125); + } else { + assert.strictEqual(cb.getWeight() + padding * scale, 724); + assert.strictEqual(cb.getBaseSize() + padding, 172); + assert.strictEqual(cb.getSize() + padding, 208); + } + + // Setup coinbase flags (variable size). + input.script.setData(1, this.coinbaseFlags); + input.script.compile(); + + // Setup output script (variable size). + output.script.fromAddress(this.address); + + cb.refresh(); + + assert(input.script.getSize() <= 100, + 'Coinbase input script is too large!'); + + return cb; + } + + /** + * Refresh the coinbase and merkle tree. + */ + + refresh() { + const hash = this.getWitnessHash(); + const cb = this.createCoinbase(hash); + const raw = cb.toNormal(); + let size = 0; + + size += 4; // version + size += 1; // varint inputs length + size += cb.inputs[0].getSize(); // input size + size -= 4 + 4 + 4; // -(nonce1 + nonce2 + sequence) + + // Cut off right after the nonce + // push and before the sequence. + const left = raw.slice(0, size); + + // Include the sequence. + size += 4 + 4; // nonce1 + nonce2 + const right = raw.slice(size); + + this.commitment = hash; + this.left = left; + this.right = right; + this.tree = MerkleTree.fromItems(this.items); + } + + /** + * Get raw coinbase with desired nonces. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @returns {Buffer} + */ + + getRawCoinbase(nonce1, nonce2) { + let size = 0; + + size += this.left.length; + size += 4 + 4; + size += this.right.length; + + const bw = new StaticWriter(size); + bw.writeBytes(this.left); + bw.writeU32BE(nonce1); + bw.writeU32BE(nonce2); + bw.writeBytes(this.right); + + return bw.render(); + } + + /** + * Calculate the merkle root with given nonces. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @returns {Buffer} + */ + + getRoot(nonce1, nonce2) { + const raw = this.getRawCoinbase(nonce1, nonce2); + const hash = hash256.digest(raw); + return this.tree.withFirst(hash); + } + + /** + * Create raw block header with given parameters. + * @param {Buffer} root + * @param {Number} time + * @param {Number} nonce + * @returns {Buffer} + */ + + getHeader(root, time, nonce) { + const bw = new StaticWriter(80); + + bw.writeU32(this.version); + bw.writeHash(this.prevBlock); + bw.writeHash(root); + bw.writeU32(time); + bw.writeU32(this.bits); + bw.writeU32(nonce); + + return bw.render(); + } + + /** + * Calculate proof with given parameters. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @param {Number} time + * @param {Number} nonce + * @returns {BlockProof} + */ + + getProof(nonce1, nonce2, time, nonce) { + const root = this.getRoot(nonce1, nonce2); + const data = this.getHeader(root, time, nonce); + const hash = hash256.digest(data); + return new BlockProof(hash, root, nonce1, nonce2, time, nonce); + } + + /** + * Create coinbase from given parameters. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @returns {TX} + */ + + getCoinbase(nonce1, nonce2) { + const raw = this.getRawCoinbase(nonce1, nonce2); + const tx = TX.fromRaw(raw); + + if (this.witness) { + const input = tx.inputs[0]; + input.witness.push(encoding.ZERO_HASH); + input.witness.compile(); + tx.refresh(); + } + + return tx; + } + + /** + * Create block from calculated proof. + * @param {BlockProof} proof + * @returns {Block} + */ + + commit(proof) { + const root = proof.root; + const n1 = proof.nonce1; + const n2 = proof.nonce2; + const time = proof.time; + const nonce = proof.nonce; + const block = new Block(); + + block.version = this.version; + block.prevBlock = this.prevBlock; + block.merkleRoot = root.toString('hex'); + block.time = time; + block.bits = this.bits; + block.nonce = nonce; + + const tx = this.getCoinbase(n1, n2); + + block.txs.push(tx); + + for (const item of this.items) + block.txs.push(item.tx); + + return block; + } + + /** + * Quick and dirty way to + * get a coinbase tx object. + * @returns {TX} + */ + + toCoinbase() { + return this.getCoinbase(0, 0); + } + + /** + * Quick and dirty way to get a block + * object (most likely to be an invalid one). + * @returns {Block} + */ + + toBlock() { + const proof = this.getProof(0, 0, this.time, 0); + return this.commit(proof); + } + + /** + * Calculate the target difficulty. + * @returns {Number} + */ + + getDifficulty() { + return common.getDifficulty(this.target); + } + + /** + * Set the reward output + * address and refresh. + * @param {Address} address + */ + + setAddress(address) { + this.address = new Address(address); + this.refresh(); + } + + /** + * Add a transaction to the template. + * @param {TX} tx + * @param {CoinView} view + */ + + addTX(tx, view) { + assert(!tx.mutable, 'Cannot add mutable TX to block.'); + + const item = BlockEntry.fromTX(tx, view, this); + const weight = item.tx.getWeight(); + const sigops = item.sigops; + + if (!tx.isFinal(this.height, this.locktime)) + return false; + + if (this.weight + weight > consensus.MAX_BLOCK_WEIGHT) + return false; + + if (this.sigops + sigops > consensus.MAX_BLOCK_SIGOPS_COST) + return false; + + if (!this.witness && tx.hasWitness()) + return false; + + this.weight += weight; + this.sigops += sigops; + this.fees += item.fee; + + // Add the tx to our block + this.items.push(item); + + return true; + } + + /** + * Add a transaction to the template + * (less verification than addTX). + * @param {TX} tx + * @param {CoinView?} view + */ + + pushTX(tx, view) { + assert(!tx.mutable, 'Cannot add mutable TX to block.'); + + if (!view) + view = new CoinView(); + + const item = BlockEntry.fromTX(tx, view, this); + const weight = item.tx.getWeight(); + const sigops = item.sigops; + + this.weight += weight; + this.sigops += sigops; + this.fees += item.fee; + + // Add the tx to our block + this.items.push(item); + + return true; + } } /** - * Inject properties from options. - * @private - * @param {Object} options - * @returns {BlockTemplate} - */ - -BlockTemplate.prototype.fromOptions = function fromOptions(options) { - assert(options); - - if (options.prevBlock != null) { - assert(typeof options.prevBlock === 'string'); - this.prevBlock = options.prevBlock; - } - - if (options.version != null) { - assert(typeof options.version === 'number'); - this.version = options.version; - } - - if (options.height != null) { - assert(typeof options.height === 'number'); - this.height = options.height; - } - - if (options.time != null) { - assert(typeof options.time === 'number'); - this.time = options.time; - } - - if (options.bits != null) - this.setBits(options.bits); - - if (options.target != null) - this.setTarget(options.target); - - if (options.locktime != null) { - assert(typeof options.locktime === 'number'); - this.locktime = options.locktime; - } - - if (options.mtp != null) { - assert(typeof options.mtp === 'number'); - this.mtp = options.mtp; - } - - if (options.flags != null) { - assert(typeof options.flags === 'number'); - this.flags = options.flags; - } - - if (options.coinbaseFlags != null) { - assert(Buffer.isBuffer(options.coinbaseFlags)); - this.coinbaseFlags = options.coinbaseFlags; - } - - if (options.witness != null) { - assert(typeof options.witness === 'boolean'); - this.witness = options.witness; - } - - if (options.address != null) - this.address.fromOptions(options.address); - - if (options.sigops != null) { - assert(typeof options.sigops === 'number'); - this.sigops = options.sigops; - } - - if (options.weight != null) { - assert(typeof options.weight === 'number'); - this.weight = options.weight; - } - - if (options.interval != null) { - assert(typeof options.interval === 'number'); - this.interval = options.interval; - } - - if (options.fees != null) { - assert(typeof options.fees === 'number'); - this.fees = options.fees; - } - - if (options.items != null) { - assert(Array.isArray(options.items)); - this.items = options.items; - } - - return this; -}; - -/** - * Instantiate block template from options. - * @param {Object} options - * @returns {BlockTemplate} - */ - -BlockTemplate.fromOptions = function fromOptions(options) { - return new BlockTemplate().fromOptions(options); -}; - -/** - * Create witness commitment hash. - * @returns {Buffer} - */ - -BlockTemplate.prototype.getWitnessHash = function getWitnessHash() { - const nonce = encoding.ZERO_HASH; - const leaves = []; - - leaves.push(encoding.ZERO_HASH); - - for (const item of this.items) - leaves.push(item.tx.witnessHash()); - - const [root, malleated] = merkle.createRoot(hash256, leaves); - - assert(!malleated); - - return hash256.root(root, nonce); -}; - -/** - * Create witness commitment script. - * @returns {Script} - */ - -BlockTemplate.prototype.getWitnessScript = function getWitnessScript() { - return Script.fromCommitment(this.commitment); -}; - -/** - * Set the target (bits). - * @param {Number} bits - */ - -BlockTemplate.prototype.setBits = function setBits(bits) { - assert(typeof bits === 'number'); - this.bits = bits; - this.target = common.getTarget(bits); -}; - -/** - * Set the target (uint256le). - * @param {Buffer} target - */ - -BlockTemplate.prototype.setTarget = function setTarget(target) { - assert(Buffer.isBuffer(target)); - this.bits = common.getBits(target); - this.target = target; -}; - -/** - * Calculate the block reward. - * @returns {Amount} - */ - -BlockTemplate.prototype.getReward = function getReward() { - const reward = consensus.getReward(this.height, this.interval); - return reward + this.fees; -}; - -/** - * Initialize the default coinbase. - * @param {Buffer} hash - Witness commitment hash. - * @returns {TX} - */ - -BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) { - const scale = consensus.WITNESS_SCALE_FACTOR; - const cb = new TX(); - - // Coinbase input. - const input = new Input(); - - // Height (required in v2+ blocks) - input.script.pushInt(this.height); - - // Coinbase flags. - input.script.pushData(encoding.ZERO_HASH160); - - // Smaller nonce for good measure. - const nonce = Buffer.allocUnsafe(4); - nonce.writeUInt32LE(Math.random() * 0x100000000, 0, true); - input.script.pushData(nonce); - - // Extra nonce: incremented when - // the nonce overflows. - input.script.pushData(encoding.ZERO_U64); - - input.script.compile(); - - // Set up the witness nonce. - if (this.witness) { - input.witness.push(encoding.ZERO_HASH); - input.witness.compile(); - } - - cb.inputs.push(input); - - // Reward output. - const output = new Output(); - output.script.fromPubkeyhash(encoding.ZERO_HASH160); - output.value = this.getReward(); - - cb.outputs.push(output); - - // If we're using segwit, we - // need to set up the commitment. - if (this.witness) { - // Commitment output. - const commit = new Output(); - commit.script.fromCommitment(hash); - cb.outputs.push(commit); - } - - // Padding for the CB height (constant size). - const op = input.script.get(0); - assert(op); - const padding = 5 - op.getSize(); - assert(padding >= 0); - - // Reserved size. - // Without segwit: - // CB weight = 500 - // CB stripped size = 125 - // CB size = 125 - // Sigops cost = 4 - // With segwit: - // CB weight = 724 - // CB stripped size = 172 - // CB size = 208 - // Sigops cost = 4 - if (!this.witness) { - assert.strictEqual(cb.getWeight() + padding * scale, 500); - assert.strictEqual(cb.getBaseSize() + padding, 125); - assert.strictEqual(cb.getSize() + padding, 125); - } else { - assert.strictEqual(cb.getWeight() + padding * scale, 724); - assert.strictEqual(cb.getBaseSize() + padding, 172); - assert.strictEqual(cb.getSize() + padding, 208); - } - - // Setup coinbase flags (variable size). - input.script.setData(1, this.coinbaseFlags); - input.script.compile(); - - // Setup output script (variable size). - output.script.fromAddress(this.address); - - cb.refresh(); - - assert(input.script.getSize() <= 100, - 'Coinbase input script is too large!'); - - return cb; -}; - -/** - * Refresh the coinbase and merkle tree. - */ - -BlockTemplate.prototype.refresh = function refresh() { - const hash = this.getWitnessHash(); - const cb = this.createCoinbase(hash); - const raw = cb.toNormal(); - let size = 0; - - size += 4; // version - size += 1; // varint inputs length - size += cb.inputs[0].getSize(); // input size - size -= 4 + 4 + 4; // -(nonce1 + nonce2 + sequence) - - // Cut off right after the nonce - // push and before the sequence. - const left = raw.slice(0, size); - - // Include the sequence. - size += 4 + 4; // nonce1 + nonce2 - const right = raw.slice(size); - - this.commitment = hash; - this.left = left; - this.right = right; - this.tree = MerkleTree.fromItems(this.items); -}; - -/** - * Get raw coinbase with desired nonces. - * @param {Number} nonce1 - * @param {Number} nonce2 - * @returns {Buffer} - */ - -BlockTemplate.prototype.getRawCoinbase = function getRawCoinbase(nonce1, nonce2) { - let size = 0; - - size += this.left.length; - size += 4 + 4; - size += this.right.length; - - const bw = new StaticWriter(size); - bw.writeBytes(this.left); - bw.writeU32BE(nonce1); - bw.writeU32BE(nonce2); - bw.writeBytes(this.right); - - return bw.render(); -}; - -/** - * Calculate the merkle root with given nonces. - * @param {Number} nonce1 - * @param {Number} nonce2 - * @returns {Buffer} - */ - -BlockTemplate.prototype.getRoot = function getRoot(nonce1, nonce2) { - const raw = this.getRawCoinbase(nonce1, nonce2); - const hash = hash256.digest(raw); - return this.tree.withFirst(hash); -}; - -/** - * Create raw block header with given parameters. - * @param {Buffer} root - * @param {Number} time - * @param {Number} nonce - * @returns {Buffer} - */ - -BlockTemplate.prototype.getHeader = function getHeader(root, time, nonce) { - const bw = new StaticWriter(80); - - bw.writeU32(this.version); - bw.writeHash(this.prevBlock); - bw.writeHash(root); - bw.writeU32(time); - bw.writeU32(this.bits); - bw.writeU32(nonce); - - return bw.render(); -}; - -/** - * Calculate proof with given parameters. - * @param {Number} nonce1 - * @param {Number} nonce2 - * @param {Number} time - * @param {Number} nonce - * @returns {BlockProof} - */ - -BlockTemplate.prototype.getProof = function getProof(nonce1, nonce2, time, nonce) { - const root = this.getRoot(nonce1, nonce2); - const data = this.getHeader(root, time, nonce); - const hash = hash256.digest(data); - return new BlockProof(hash, root, nonce1, nonce2, time, nonce); -}; - -/** - * Create coinbase from given parameters. - * @param {Number} nonce1 - * @param {Number} nonce2 - * @returns {TX} - */ - -BlockTemplate.prototype.getCoinbase = function getCoinbase(nonce1, nonce2) { - const raw = this.getRawCoinbase(nonce1, nonce2); - const tx = TX.fromRaw(raw); - - if (this.witness) { - const input = tx.inputs[0]; - input.witness.push(encoding.ZERO_HASH); - input.witness.compile(); - tx.refresh(); - } - - return tx; -}; - -/** - * Create block from calculated proof. - * @param {BlockProof} proof - * @returns {Block} - */ - -BlockTemplate.prototype.commit = function commit(proof) { - const root = proof.root; - const n1 = proof.nonce1; - const n2 = proof.nonce2; - const time = proof.time; - const nonce = proof.nonce; - const block = new Block(); - - block.version = this.version; - block.prevBlock = this.prevBlock; - block.merkleRoot = root.toString('hex'); - block.time = time; - block.bits = this.bits; - block.nonce = nonce; - - const tx = this.getCoinbase(n1, n2); - - block.txs.push(tx); - - for (const item of this.items) - block.txs.push(item.tx); - - return block; -}; - -/** - * Quick and dirty way to - * get a coinbase tx object. - * @returns {TX} - */ - -BlockTemplate.prototype.toCoinbase = function toCoinbase() { - return this.getCoinbase(0, 0); -}; - -/** - * Quick and dirty way to get a block - * object (most likely to be an invalid one). - * @returns {Block} - */ - -BlockTemplate.prototype.toBlock = function toBlock() { - const proof = this.getProof(0, 0, this.time, 0); - return this.commit(proof); -}; - -/** - * Calculate the target difficulty. - * @returns {Number} - */ - -BlockTemplate.prototype.getDifficulty = function getDifficulty() { - return common.getDifficulty(this.target); -}; - -/** - * Set the reward output - * address and refresh. - * @param {Address} address - */ - -BlockTemplate.prototype.setAddress = function setAddress(address) { - this.address = new Address(address); - this.refresh(); -}; - -/** - * Add a transaction to the template. - * @param {TX} tx - * @param {CoinView} view - */ - -BlockTemplate.prototype.addTX = function addTX(tx, view) { - assert(!tx.mutable, 'Cannot add mutable TX to block.'); - - const item = BlockEntry.fromTX(tx, view, this); - const weight = item.tx.getWeight(); - const sigops = item.sigops; - - if (!tx.isFinal(this.height, this.locktime)) - return false; - - if (this.weight + weight > consensus.MAX_BLOCK_WEIGHT) - return false; - - if (this.sigops + sigops > consensus.MAX_BLOCK_SIGOPS_COST) - return false; - - if (!this.witness && tx.hasWitness()) - return false; - - this.weight += weight; - this.sigops += sigops; - this.fees += item.fee; - - // Add the tx to our block - this.items.push(item); - - return true; -}; - -/** - * Add a transaction to the template - * (less verification than addTX). - * @param {TX} tx - * @param {CoinView?} view - */ - -BlockTemplate.prototype.pushTX = function pushTX(tx, view) { - assert(!tx.mutable, 'Cannot add mutable TX to block.'); - - if (!view) - view = new CoinView(); - - const item = BlockEntry.fromTX(tx, view, this); - const weight = item.tx.getWeight(); - const sigops = item.sigops; - - this.weight += weight; - this.sigops += sigops; - this.fees += item.fee; - - // Add the tx to our block - this.items.push(item); - - return true; -}; - -/** - * BlockEntry + * Block Entry * @alias module:mining.BlockEntry - * @constructor - * @param {TX} tx * @property {TX} tx * @property {Hash} hash * @property {Amount} fee @@ -593,170 +594,190 @@ BlockTemplate.prototype.pushTX = function pushTX(tx, view) { * @property {Number} depCount */ -function BlockEntry(tx) { - this.tx = tx; - this.hash = tx.hash('hex'); - this.fee = 0; - this.rate = 0; - this.priority = 0; - this.free = false; - this.sigops = 0; - this.descRate = 0; - this.depCount = 0; +class BlockEntry { + /** + * Create a block entry. + * @constructor + * @param {TX} tx + */ + + constructor(tx) { + this.tx = tx; + this.hash = tx.hash('hex'); + this.fee = 0; + this.rate = 0; + this.priority = 0; + this.free = false; + this.sigops = 0; + this.descRate = 0; + this.depCount = 0; + } + + /** + * Instantiate block entry from transaction. + * @param {TX} tx + * @param {CoinView} view + * @param {BlockTemplate} attempt + * @returns {BlockEntry} + */ + + static fromTX(tx, view, attempt) { + const item = new this(tx); + item.fee = tx.getFee(view); + item.rate = tx.getRate(view); + item.priority = tx.getPriority(view, attempt.height); + item.free = false; + item.sigops = tx.getSigopsCost(view, attempt.flags); + item.descRate = item.rate; + return item; + } + + /** + * Instantiate block entry from mempool entry. + * @param {MempoolEntry} entry + * @param {BlockTemplate} attempt + * @returns {BlockEntry} + */ + + static fromEntry(entry, attempt) { + const item = new this(entry.tx); + item.fee = entry.getFee(); + item.rate = entry.getDeltaRate(); + item.priority = entry.getPriority(attempt.height); + item.free = entry.getDeltaFee() < policy.getMinFee(entry.size); + item.sigops = entry.sigops; + item.descRate = entry.getDescRate(); + return item; + } } /** - * Instantiate block entry from transaction. - * @param {TX} tx - * @param {CoinView} view - * @param {BlockTemplate} attempt - * @returns {BlockEntry} + * Block Proof */ -BlockEntry.fromTX = function fromTX(tx, view, attempt) { - const item = new BlockEntry(tx); - item.fee = tx.getFee(view); - item.rate = tx.getRate(view); - item.priority = tx.getPriority(view, attempt.height); - item.free = false; - item.sigops = tx.getSigopsCost(view, attempt.flags); - item.descRate = item.rate; - return item; -}; +class BlockProof { + /** + * Create a block proof. + * @constructor + * @param {Hash} hash + * @param {Hash} root + * @param {Number} nonce1 + * @param {Number} nonce2 + * @param {Number} time + * @param {Number} nonce + */ -/** - * Instantiate block entry from mempool entry. - * @param {MempoolEntry} entry - * @param {BlockTemplate} attempt - * @returns {BlockEntry} - */ + constructor(hash, root, nonce1, nonce2, time, nonce) { + this.hash = hash; + this.root = root; + this.nonce1 = nonce1; + this.nonce2 = nonce2; + this.time = time; + this.nonce = nonce; + } -BlockEntry.fromEntry = function fromEntry(entry, attempt) { - const item = new BlockEntry(entry.tx); - item.fee = entry.getFee(); - item.rate = entry.getDeltaRate(); - item.priority = entry.getPriority(attempt.height); - item.free = entry.getDeltaFee() < policy.getMinFee(entry.size); - item.sigops = entry.sigops; - item.descRate = entry.getDescRate(); - return item; -}; + rhash() { + return encoding.revHex(this.hash.toString('hex')); + } -/* - * BlockProof - * @constructor - * @param {Hash} hash - * @param {Hash} root - * @param {Number} nonce1 - * @param {Number} nonce2 - * @param {Number} time - * @param {Number} nonce - */ + verify(target) { + return common.rcmp(this.hash, target) <= 0; + } -function BlockProof(hash, root, nonce1, nonce2, time, nonce) { - this.hash = hash; - this.root = root; - this.nonce1 = nonce1; - this.nonce2 = nonce2; - this.time = time; - this.nonce = nonce; + getDifficulty() { + return common.getDifficulty(this.hash); + } } -BlockProof.prototype.rhash = function rhash() { - return encoding.revHex(this.hash.toString('hex')); -}; - -BlockProof.prototype.verify = function verify(target) { - return common.rcmp(this.hash, target) <= 0; -}; - -BlockProof.prototype.getDifficulty = function getDifficulty() { - return common.getDifficulty(this.hash); -}; - -/* - * MerkleTree - * @constructor +/** + * Merkle Tree * @property {Hash[]} steps */ -function MerkleTree() { - this.steps = []; -} +class MerkleTree { + /** + * Create a merkle tree. + * @constructor + */ -MerkleTree.prototype.withFirst = function withFirst(hash) { - for (const step of this.steps) - hash = hash256.root(hash, step); - return hash; -}; - -MerkleTree.prototype.toJSON = function toJSON() { - const steps = []; - - for (const step of this.steps) - steps.push(step.toString('hex')); - - return steps; -}; - -MerkleTree.prototype.fromItems = function fromItems(items) { - const leaves = []; - - leaves.push(encoding.ZERO_HASH); - - for (const item of items) - leaves.push(item.tx.hash()); - - return this.fromLeaves(leaves); -}; - -MerkleTree.fromItems = function fromItems(items) { - return new MerkleTree().fromItems(items); -}; - -MerkleTree.prototype.fromBlock = function fromBlock(txs) { - const leaves = []; - - leaves.push(encoding.ZERO_HASH); - - for (let i = 1; i < txs.length; i++) { - const tx = txs[i]; - leaves.push(tx.hash()); + constructor() { + this.steps = []; } - return this.fromLeaves(leaves); -}; + withFirst(hash) { + for (const step of this.steps) + hash = hash256.root(hash, step); + return hash; + } -MerkleTree.fromBlock = function fromBlock(txs) { - return new MerkleTree().fromBlock(txs); -}; + toJSON() { + const steps = []; -MerkleTree.prototype.fromLeaves = function fromLeaves(leaves) { - let len = leaves.length; + for (const step of this.steps) + steps.push(step.toString('hex')); - while (len > 1) { - const hashes = [encoding.ZERO_HASH]; + return steps; + } - this.steps.push(leaves[1]); + fromItems(items) { + const leaves = []; - if (len % 2) - leaves.push(leaves[len - 1]); + leaves.push(encoding.ZERO_HASH); - for (let i = 2; i < len; i += 2) { - const hash = hash256.root(leaves[i], leaves[i + 1]); - hashes.push(hash); + for (const item of items) + leaves.push(item.tx.hash()); + + return this.fromLeaves(leaves); + } + + static fromItems(items) { + return new this().fromItems(items); + } + + fromBlock(txs) { + const leaves = []; + + leaves.push(encoding.ZERO_HASH); + + for (let i = 1; i < txs.length; i++) { + const tx = txs[i]; + leaves.push(tx.hash()); } - leaves = hashes; - len = leaves.length; + return this.fromLeaves(leaves); } - return this; -}; + static fromBlock(txs) { + return new this().fromBlock(txs); + } -MerkleTree.fromLeaves = function fromLeaves(leaves) { - return new MerkleTree().fromLeaves(leaves); -}; + fromLeaves(leaves) { + let len = leaves.length; + + while (len > 1) { + const hashes = [encoding.ZERO_HASH]; + + this.steps.push(leaves[1]); + + if (len % 2) + leaves.push(leaves[len - 1]); + + for (let i = 2; i < len; i += 2) { + const hash = hash256.root(leaves[i], leaves[i + 1]); + hashes.push(hash); + } + + leaves = hashes; + len = leaves.length; + } + + return this; + } + + static fromLeaves(leaves) { + return new this().fromLeaves(leaves); + } +} /* * Expose diff --git a/lib/net/packets.js b/lib/net/packets.js index 4577f1fb..8776854b 100644 --- a/lib/net/packets.js +++ b/lib/net/packets.js @@ -11,9 +11,9 @@ * @module net/packets */ +const assert = require('assert'); const common = require('./common'); const util = require('../utils/util'); -const assert = require('assert'); const BloomFilter = require('bfilter/lib/bloom'); const bip152 = require('./bip152'); const NetAddress = require('../primitives/netaddress'); @@ -117,73 +117,68 @@ exports.typesByVal = [ /** * Base Packet - * @constructor */ -function Packet() {} +class Packet { + /** + * Create a base packet. + * @constructor + */ -Packet.prototype.type = -1; -Packet.prototype.cmd = ''; + constructor() { + this.type = -1; + this.cmd = ''; + } -/** - * Get serialization size. - * @returns {Number} - */ + /** + * Get serialization size. + * @returns {Number} + */ -Packet.prototype.getSize = function getSize() { - return 0; -}; + getSize() { + return 0; + } -/** - * Serialize packet to writer. - * @param {BufferWriter} bw - */ + /** + * Serialize packet to writer. + * @param {BufferWriter} bw + */ -Packet.prototype.toWriter = function toWriter(bw) { - return bw; -}; + toWriter(bw) { + return bw; + } -/** - * Serialize packet. - * @returns {Buffer} - */ + /** + * Serialize packet. + * @returns {Buffer} + */ -Packet.prototype.toRaw = function toRaw() { - return DUMMY; -}; + toRaw() { + return DUMMY; + } -/** - * Inject properties from buffer reader. - * @param {BufferReader} br - */ + /** + * Inject properties from buffer reader. + * @param {BufferReader} br + */ -Packet.prototype.fromReader = function fromReader(br) { - return this; -}; + fromReader(br) { + return this; + } -/** - * Inject properties from serialized data. - * @param {Buffer} data - */ + /** + * Inject properties from serialized data. + * @param {Buffer} data + */ -Packet.prototype.fromRaw = function fromRaw(data) { - return this; -}; + fromRaw(data) { + return this; + } +} /** * Version Packet - * @constructor - * @param {Object?} options - * @param {Number} options.version - Protocol version. - * @param {Number} options.services - Service bits. - * @param {Number} options.time - Timestamp of discovery. - * @param {NetAddress} options.local - Our address. - * @param {NetAddress} options.remote - Their address. - * @param {Buffer} options.nonce - * @param {String} options.agent - User agent string. - * @param {Number} options.height - Chain height. - * @param {Boolean} options.noRelay - Whether transactions - * should be relayed immediately. + * @extends Packet * @property {Number} version - Protocol version. * @property {Number} services - Service bits. * @property {Number} time - Timestamp of discovery. @@ -196,1266 +191,1298 @@ Packet.prototype.fromRaw = function fromRaw(data) { * should be relayed immediately. */ -function VersionPacket(options) { - if (!(this instanceof VersionPacket)) - return new VersionPacket(options); +class VersionPacket extends Packet { + /** + * Create a version packet. + * @constructor + * @param {Object?} options + * @param {Number} options.version - Protocol version. + * @param {Number} options.services - Service bits. + * @param {Number} options.time - Timestamp of discovery. + * @param {NetAddress} options.local - Our address. + * @param {NetAddress} options.remote - Their address. + * @param {Buffer} options.nonce + * @param {String} options.agent - User agent string. + * @param {Number} options.height - Chain height. + * @param {Boolean} options.noRelay - Whether transactions + * should be relayed immediately. + */ - Packet.call(this); + constructor(options) { + super(); - this.version = common.PROTOCOL_VERSION; - this.services = common.LOCAL_SERVICES; - this.time = util.now(); - this.remote = new NetAddress(); - this.local = new NetAddress(); - this.nonce = encoding.ZERO_U64; - this.agent = common.USER_AGENT; - this.height = 0; - this.noRelay = false; + this.cmd = 'version'; + this.type = exports.types.VERSION; - if (options) - this.fromOptions(options); -} + this.version = common.PROTOCOL_VERSION; + this.services = common.LOCAL_SERVICES; + this.time = util.now(); + this.remote = new NetAddress(); + this.local = new NetAddress(); + this.nonce = encoding.ZERO_U64; + this.agent = common.USER_AGENT; + this.height = 0; + this.noRelay = false; -Object.setPrototypeOf(VersionPacket.prototype, Packet.prototype); - -VersionPacket.prototype.cmd = 'version'; -VersionPacket.prototype.type = exports.types.VERSION; - -/** - * Inject properties from options. - * @private - * @param {Object} options - */ - -VersionPacket.prototype.fromOptions = function fromOptions(options) { - if (options.version != null) - this.version = options.version; - - if (options.services != null) - this.services = options.services; - - if (options.time != null) - this.time = options.time; - - if (options.remote) - this.remote.fromOptions(options.remote); - - if (options.local) - this.local.fromOptions(options.local); - - if (options.nonce) - this.nonce = options.nonce; - - if (options.agent) - this.agent = options.agent; - - if (options.height != null) - this.height = options.height; - - if (options.noRelay != null) - this.noRelay = options.noRelay; - - return this; -}; - -/** - * Instantiate version packet from options. - * @param {Object} options - * @returns {VersionPacket} - */ - -VersionPacket.fromOptions = function fromOptions(options) { - return new VersionPacket().fromOptions(options); -}; - -/** - * Get serialization size. - * @returns {Number} - */ - -VersionPacket.prototype.getSize = function getSize() { - let size = 0; - size += 20; - size += this.remote.getSize(false); - size += this.local.getSize(false); - size += 8; - size += encoding.sizeVarString(this.agent, 'ascii'); - size += 5; - return size; -}; - -/** - * Write version packet to buffer writer. - * @param {BufferWriter} bw - */ - -VersionPacket.prototype.toWriter = function toWriter(bw) { - bw.writeI32(this.version); - bw.writeU32(this.services); - bw.writeU32(0); - bw.writeI64(this.time); - this.remote.toWriter(bw, false); - this.local.toWriter(bw, false); - bw.writeBytes(this.nonce); - bw.writeVarString(this.agent, 'ascii'); - bw.writeI32(this.height); - bw.writeU8(this.noRelay ? 0 : 1); - return bw; -}; - -/** - * Serialize version packet. - * @returns {Buffer} - */ - -VersionPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -VersionPacket.prototype.fromReader = function fromReader(br) { - this.version = br.readI32(); - this.services = br.readU32(); - - // Note: hi service bits - // are currently unused. - br.readU32(); - - this.time = br.readI64(); - this.remote.fromReader(br, false); - - if (br.left() > 0) { - this.local.fromReader(br, false); - this.nonce = br.readBytes(8); + if (options) + this.fromOptions(options); } - if (br.left() > 0) - this.agent = br.readVarString('ascii', 256); + /** + * Inject properties from options. + * @private + * @param {Object} options + */ - if (br.left() > 0) - this.height = br.readI32(); + fromOptions(options) { + if (options.version != null) + this.version = options.version; - if (br.left() > 0) - this.noRelay = br.readU8() === 0; + if (options.services != null) + this.services = options.services; - if (this.version === 10300) - this.version = 300; + if (options.time != null) + this.time = options.time; - assert(this.version >= 0, 'Version is negative.'); - assert(this.time >= 0, 'Timestamp is negative.'); + if (options.remote) + this.remote.fromOptions(options.remote); - // No idea why so many peers do this. - if (this.height < 0) - this.height = 0; + if (options.local) + this.local.fromOptions(options.local); - return this; -}; + if (options.nonce) + this.nonce = options.nonce; -/** - * Instantiate version packet from buffer reader. - * @param {BufferReader} br - * @returns {VersionPacket} - */ + if (options.agent) + this.agent = options.agent; -VersionPacket.fromReader = function fromReader(br) { - return new VersionPacket().fromReader(br); -}; + if (options.height != null) + this.height = options.height; -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ + if (options.noRelay != null) + this.noRelay = options.noRelay; -VersionPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; + return this; + } -/** - * Instantiate version packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {VersionPacket} - */ + /** + * Instantiate version packet from options. + * @param {Object} options + * @returns {VersionPacket} + */ -VersionPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new VersionPacket().fromRaw(data, enc); -}; + static fromOptions(options) { + return new this().fromOptions(options); + } -/** - * Represents a `verack` packet. - * @constructor - */ + /** + * Get serialization size. + * @returns {Number} + */ -function VerackPacket() { - if (!(this instanceof VerackPacket)) - return new VerackPacket(); + getSize() { + let size = 0; + size += 20; + size += this.remote.getSize(false); + size += this.local.getSize(false); + size += 8; + size += encoding.sizeVarString(this.agent, 'ascii'); + size += 5; + return size; + } - Packet.call(this); + /** + * Write version packet to buffer writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeI32(this.version); + bw.writeU32(this.services); + bw.writeU32(0); + bw.writeI64(this.time); + this.remote.toWriter(bw, false); + this.local.toWriter(bw, false); + bw.writeBytes(this.nonce); + bw.writeVarString(this.agent, 'ascii'); + bw.writeI32(this.height); + bw.writeU8(this.noRelay ? 0 : 1); + return bw; + } + + /** + * Serialize version packet. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.version = br.readI32(); + this.services = br.readU32(); + + // Note: hi service bits + // are currently unused. + br.readU32(); + + this.time = br.readI64(); + this.remote.fromReader(br, false); + + if (br.left() > 0) { + this.local.fromReader(br, false); + this.nonce = br.readBytes(8); + } + + if (br.left() > 0) + this.agent = br.readVarString('ascii', 256); + + if (br.left() > 0) + this.height = br.readI32(); + + if (br.left() > 0) + this.noRelay = br.readU8() === 0; + + if (this.version === 10300) + this.version = 300; + + assert(this.version >= 0, 'Version is negative.'); + assert(this.time >= 0, 'Timestamp is negative.'); + + // No idea why so many peers do this. + if (this.height < 0) + this.height = 0; + + return this; + } + + /** + * Instantiate version packet from buffer reader. + * @param {BufferReader} br + * @returns {VersionPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate version packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {VersionPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data, enc); + } } -Object.setPrototypeOf(VerackPacket.prototype, Packet.prototype); - -VerackPacket.prototype.cmd = 'verack'; -VerackPacket.prototype.type = exports.types.VERACK; - /** - * Instantiate verack packet from serialized data. - * @param {BufferReader} br - * @returns {VerackPacket} + * Verack Packet + * @extends Packet */ -VerackPacket.fromReader = function fromReader(br) { - return new VerackPacket().fromReader(br); -}; +class VerackPacket extends Packet { + /** + * Create a `verack` packet. + * @constructor + */ + + constructor() { + super(); + this.cmd = 'verack'; + this.type = exports.types.VERACK; + } + + /** + * Instantiate verack packet from serialized data. + * @param {BufferReader} br + * @returns {VerackPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate verack packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {VerackPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } +} /** - * Instantiate verack packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {VerackPacket} - */ - -VerackPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new VerackPacket().fromRaw(data); -}; - -/** - * Represents a `ping` packet. - * @constructor - * @param {BN?} nonce + * Ping Packet + * @extends Packet * @property {BN|null} nonce */ -function PingPacket(nonce) { - if (!(this instanceof PingPacket)) - return new PingPacket(nonce); +class PingPacket extends Packet { + /** + * Create a `ping` packet. + * @constructor + * @param {BN?} nonce + */ - Packet.call(this); + constructor(nonce) { + super(); - this.nonce = nonce || null; + this.cmd = 'ping'; + this.type = exports.types.PING; + + this.nonce = nonce || null; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return this.nonce ? 8 : 0; + } + + /** + * Serialize ping packet. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); + } + + /** + * Serialize ping packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + if (this.nonce) + bw.writeBytes(this.nonce); + return bw; + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + if (br.left() >= 8) + this.nonce = br.readBytes(8); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate ping packet from serialized data. + * @param {BufferReader} br + * @returns {PingPacket} + */ + + static fromReader(br) { + return new this().fromRaw(br); + } + + /** + * Instantiate ping packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {PingPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(PingPacket.prototype, Packet.prototype); - -PingPacket.prototype.cmd = 'ping'; -PingPacket.prototype.type = exports.types.PING; - /** - * Get serialization size. - * @returns {Number} - */ - -PingPacket.prototype.getSize = function getSize() { - return this.nonce ? 8 : 0; -}; - -/** - * Serialize ping packet. - * @returns {Buffer} - */ - -PingPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Serialize ping packet to writer. - * @param {BufferWriter} bw - */ - -PingPacket.prototype.toWriter = function toWriter(bw) { - if (this.nonce) - bw.writeBytes(this.nonce); - return bw; -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -PingPacket.prototype.fromReader = function fromReader(br) { - if (br.left() >= 8) - this.nonce = br.readBytes(8); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -PingPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate ping packet from serialized data. - * @param {BufferReader} br - * @returns {PingPacket} - */ - -PingPacket.fromReader = function fromReader(br) { - return new PingPacket().fromRaw(br); -}; - -/** - * Instantiate ping packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {PingPacket} - */ - -PingPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new PingPacket().fromRaw(data); -}; - -/** - * Represents a `pong` packet. - * @constructor - * @param {BN?} nonce + * Pong Packet + * @extends Packet * @property {BN} nonce */ -function PongPacket(nonce) { - if (!(this instanceof PongPacket)) - return new PongPacket(nonce); +class PongPacket extends Packet { + /** + * Create a `pong` packet. + * @constructor + * @param {BN?} nonce + */ - Packet.call(this); + constructor(nonce) { + super(); - this.nonce = nonce || encoding.ZERO_U64; + this.cmd = 'pong'; + this.type = exports.types.PONG; + + this.nonce = nonce || encoding.ZERO_U64; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return 8; + } + + /** + * Serialize pong packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeBytes(this.nonce); + return bw; + } + + /** + * Serialize pong packet. + * @returns {Buffer} + */ + + toRaw() { + return this.toWriter(new StaticWriter(8)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.nonce = br.readBytes(8); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate pong packet from buffer reader. + * @param {BufferReader} br + * @returns {VerackPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate pong packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {VerackPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(PongPacket.prototype, Packet.prototype); - -PongPacket.prototype.cmd = 'pong'; -PongPacket.prototype.type = exports.types.PONG; - /** - * Get serialization size. - * @returns {Number} + * GetAddr Packet + * @extends Packet */ -PongPacket.prototype.getSize = function getSize() { - return 8; -}; +class GetAddrPacket extends Packet { + /** + * Create a `getaddr` packet. + * @constructor + */ -/** - * Serialize pong packet to writer. - * @param {BufferWriter} bw - */ + constructor() { + super(); + this.cmd = 'getaddr'; + this.type = exports.types.GETADDR; + } -PongPacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.nonce); - return bw; -}; + /** + * Instantiate getaddr packet from buffer reader. + * @param {BufferReader} br + * @returns {GetAddrPacket} + */ -/** - * Serialize pong packet. - * @returns {Buffer} - */ + static fromReader(br) { + return new this().fromReader(br); + } -PongPacket.prototype.toRaw = function toRaw() { - return this.toWriter(new StaticWriter(8)).render(); -}; + /** + * Instantiate getaddr packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {GetAddrPacket} + */ -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -PongPacket.prototype.fromReader = function fromReader(br) { - this.nonce = br.readBytes(8); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -PongPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate pong packet from buffer reader. - * @param {BufferReader} br - * @returns {VerackPacket} - */ - -PongPacket.fromReader = function fromReader(br) { - return new PongPacket().fromReader(br); -}; - -/** - * Instantiate pong packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {VerackPacket} - */ - -PongPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new PongPacket().fromRaw(data); -}; - -/** - * Represents a `getaddr` packet. - * @constructor - */ - -function GetAddrPacket() { - if (!(this instanceof GetAddrPacket)) - return new GetAddrPacket(); - - Packet.call(this); + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(GetAddrPacket.prototype, Packet.prototype); - -GetAddrPacket.prototype.cmd = 'getaddr'; -GetAddrPacket.prototype.type = exports.types.GETADDR; - /** - * Instantiate getaddr packet from buffer reader. - * @param {BufferReader} br - * @returns {GetAddrPacket} - */ - -GetAddrPacket.fromReader = function fromReader(br) { - return new GetAddrPacket().fromReader(br); -}; - -/** - * Instantiate getaddr packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {GetAddrPacket} - */ - -GetAddrPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new GetAddrPacket().fromRaw(data); -}; - -/** - * Represents a `addr` packet. - * @constructor - * @param {(NetAddress[])?} items + * Addr Packet + * @extends Packet * @property {NetAddress[]} items */ -function AddrPacket(items) { - if (!(this instanceof AddrPacket)) - return new AddrPacket(items); +class AddrPacket extends Packet { + /** + * Create a `addr` packet. + * @constructor + * @param {(NetAddress[])?} items + */ - Packet.call(this); + constructor(items) { + super(); - this.items = items || []; + this.cmd = 'addr'; + this.type = exports.types.ADDR; + + this.items = items || []; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + let size = 0; + size += encoding.sizeVarint(this.items.length); + size += 30 * this.items.length; + return size; + } + + /** + * Serialize addr packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeVarint(this.items.length); + + for (const item of this.items) + item.toWriter(bw, true); + + return bw; + } + + /** + * Serialize addr packet. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + const br = new BufferReader(data); + const count = br.readVarint(); + + for (let i = 0; i < count; i++) + this.items.push(NetAddress.fromReader(br, true)); + + return this; + } + + /** + * Instantiate addr packet from Buffer reader. + * @param {BufferReader} br + * @returns {AddrPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate addr packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {AddrPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(AddrPacket.prototype, Packet.prototype); - -AddrPacket.prototype.cmd = 'addr'; -AddrPacket.prototype.type = exports.types.ADDR; - /** - * Get serialization size. - * @returns {Number} - */ - -AddrPacket.prototype.getSize = function getSize() { - let size = 0; - size += encoding.sizeVarint(this.items.length); - size += 30 * this.items.length; - return size; -}; - -/** - * Serialize addr packet to writer. - * @param {BufferWriter} bw - */ - -AddrPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarint(this.items.length); - - for (const item of this.items) - item.toWriter(bw, true); - - return bw; -}; - -/** - * Serialize addr packet. - * @returns {Buffer} - */ - -AddrPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -AddrPacket.prototype.fromRaw = function fromRaw(data) { - const br = new BufferReader(data); - const count = br.readVarint(); - - for (let i = 0; i < count; i++) - this.items.push(NetAddress.fromReader(br, true)); - - return this; -}; - -/** - * Instantiate addr packet from Buffer reader. - * @param {BufferReader} br - * @returns {AddrPacket} - */ - -AddrPacket.fromReader = function fromReader(br) { - return new AddrPacket().fromReader(br); -}; - -/** - * Instantiate addr packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {AddrPacket} - */ - -AddrPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new AddrPacket().fromRaw(data); -}; - -/** - * Represents a `inv` packet. - * @constructor - * @param {(InvItem[])?} items + * Inv Packet + * @extends Packet * @property {InvItem[]} items */ -function InvPacket(items) { - if (!(this instanceof InvPacket)) - return new InvPacket(items); +class InvPacket extends Packet { + /** + * Create a `inv` packet. + * @constructor + * @param {(InvItem[])?} items + */ - Packet.call(this); + constructor(items) { + super(); - this.items = items || []; + this.cmd = 'inv'; + this.type = exports.types.INV; + + this.items = items || []; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + let size = 0; + size += encoding.sizeVarint(this.items.length); + size += 36 * this.items.length; + return size; + } + + /** + * Serialize inv packet to writer. + * @param {Buffer} bw + */ + + toWriter(bw) { + assert(this.items.length <= 50000); + + bw.writeVarint(this.items.length); + + for (const item of this.items) + item.toWriter(bw); + + return bw; + } + + /** + * Serialize inv packet. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + const count = br.readVarint(); + + assert(count <= 50000, 'Inv item count too high.'); + + for (let i = 0; i < count; i++) + this.items.push(InvItem.fromReader(br)); + + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate inv packet from buffer reader. + * @param {BufferReader} br + * @returns {InvPacket} + */ + + static fromReader(br) { + return new this().fromRaw(br); + } + + /** + * Instantiate inv packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {InvPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(InvPacket.prototype, Packet.prototype); - -InvPacket.prototype.cmd = 'inv'; -InvPacket.prototype.type = exports.types.INV; - /** - * Get serialization size. - * @returns {Number} - */ - -InvPacket.prototype.getSize = function getSize() { - let size = 0; - size += encoding.sizeVarint(this.items.length); - size += 36 * this.items.length; - return size; -}; - -/** - * Serialize inv packet to writer. - * @param {Buffer} bw - */ - -InvPacket.prototype.toWriter = function toWriter(bw) { - assert(this.items.length <= 50000); - - bw.writeVarint(this.items.length); - - for (const item of this.items) - item.toWriter(bw); - - return bw; -}; - -/** - * Serialize inv packet. - * @returns {Buffer} - */ - -InvPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -InvPacket.prototype.fromReader = function fromReader(br) { - const count = br.readVarint(); - - assert(count <= 50000, 'Inv item count too high.'); - - for (let i = 0; i < count; i++) - this.items.push(InvItem.fromReader(br)); - - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -InvPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate inv packet from buffer reader. - * @param {BufferReader} br - * @returns {InvPacket} - */ - -InvPacket.fromReader = function fromReader(br) { - return new InvPacket().fromRaw(br); -}; - -/** - * Instantiate inv packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {InvPacket} - */ - -InvPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new InvPacket().fromRaw(data); -}; - -/** - * Represents a `getdata` packet. + * GetData Packet * @extends InvPacket - * @constructor - * @param {(InvItem[])?} items */ -function GetDataPacket(items) { - if (!(this instanceof GetDataPacket)) - return new GetDataPacket(items); +class GetDataPacket extends InvPacket { + /** + * Create a `getdata` packet. + * @constructor + * @param {(InvItem[])?} items + */ - InvPacket.call(this, items); + constructor(items) { + super(items); + this.cmd = 'getdata'; + this.type = exports.types.GETDATA; + } + + /** + * Instantiate getdata packet from buffer reader. + * @param {BufferReader} br + * @returns {GetDataPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate getdata packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {GetDataPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(GetDataPacket.prototype, InvPacket.prototype); - -GetDataPacket.prototype.cmd = 'getdata'; -GetDataPacket.prototype.type = exports.types.GETDATA; - /** - * Instantiate getdata packet from buffer reader. - * @param {BufferReader} br - * @returns {GetDataPacket} - */ - -GetDataPacket.fromReader = function fromReader(br) { - return new GetDataPacket().fromReader(br); -}; - -/** - * Instantiate getdata packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {GetDataPacket} - */ - -GetDataPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new GetDataPacket().fromRaw(data); -}; - -/** - * Represents a `notfound` packet. + * NotFound Packet * @extends InvPacket - * @constructor - * @param {(InvItem[])?} items */ -function NotFoundPacket(items) { - if (!(this instanceof NotFoundPacket)) - return new NotFoundPacket(items); +class NotFoundPacket extends InvPacket { + /** + * Create a `notfound` packet. + * @constructor + * @param {(InvItem[])?} items + */ - InvPacket.call(this, items); + constructor(items) { + super(items); + this.cmd = 'notfound'; + this.type = exports.types.NOTFOUND; + } + + /** + * Instantiate notfound packet from buffer reader. + * @param {BufferReader} br + * @returns {NotFoundPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate notfound packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {NotFoundPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(NotFoundPacket.prototype, InvPacket.prototype); - -NotFoundPacket.prototype.cmd = 'notfound'; -NotFoundPacket.prototype.type = exports.types.NOTFOUND; - /** - * Instantiate notfound packet from buffer reader. - * @param {BufferReader} br - * @returns {NotFoundPacket} - */ - -NotFoundPacket.fromReader = function fromReader(br) { - return new NotFoundPacket().fromReader(br); -}; - -/** - * Instantiate notfound packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {NotFoundPacket} - */ - -NotFoundPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new NotFoundPacket().fromRaw(data); -}; - -/** - * Represents a `getblocks` packet. - * @constructor - * @param {Hash[]} locator - * @param {Hash?} stop + * GetBlocks Packet + * @extends Packet * @property {Hash[]} locator * @property {Hash|null} stop */ -function GetBlocksPacket(locator, stop) { - if (!(this instanceof GetBlocksPacket)) - return new GetBlocksPacket(locator, stop); +class GetBlocksPacket extends Packet { + /** + * Create a `getblocks` packet. + * @constructor + * @param {Hash[]} locator + * @param {Hash?} stop + */ - Packet.call(this); + constructor(locator, stop) { + super(); - this.version = common.PROTOCOL_VERSION; - this.locator = locator || []; - this.stop = stop || null; + this.cmd = 'getblocks'; + this.type = exports.types.GETBLOCKS; + + this.version = common.PROTOCOL_VERSION; + this.locator = locator || []; + this.stop = stop || null; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + let size = 0; + size += 4; + size += encoding.sizeVarint(this.locator.length); + size += 32 * this.locator.length; + size += 32; + return size; + } + + /** + * Serialize getblocks packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + assert(this.locator.length <= 50000, 'Too many block hashes.'); + + bw.writeU32(this.version); + bw.writeVarint(this.locator.length); + + for (const hash of this.locator) + bw.writeHash(hash); + + bw.writeHash(this.stop || encoding.ZERO_HASH); + + return bw; + } + + /** + * Serialize getblocks packet. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.version = br.readU32(); + + const count = br.readVarint(); + + assert(count <= 50000, 'Too many block hashes.'); + + for (let i = 0; i < count; i++) + this.locator.push(br.readHash('hex')); + + this.stop = br.readHash('hex'); + + if (this.stop === encoding.NULL_HASH) + this.stop = null; + + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate getblocks packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {GetBlocksPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(GetBlocksPacket.prototype, Packet.prototype); - -GetBlocksPacket.prototype.cmd = 'getblocks'; -GetBlocksPacket.prototype.type = exports.types.GETBLOCKS; - /** - * Get serialization size. - * @returns {Number} - */ - -GetBlocksPacket.prototype.getSize = function getSize() { - let size = 0; - size += 4; - size += encoding.sizeVarint(this.locator.length); - size += 32 * this.locator.length; - size += 32; - return size; -}; - -/** - * Serialize getblocks packet to writer. - * @param {BufferWriter} bw - */ - -GetBlocksPacket.prototype.toWriter = function toWriter(bw) { - assert(this.locator.length <= 50000, 'Too many block hashes.'); - - bw.writeU32(this.version); - bw.writeVarint(this.locator.length); - - for (const hash of this.locator) - bw.writeHash(hash); - - bw.writeHash(this.stop || encoding.ZERO_HASH); - - return bw; -}; - -/** - * Serialize getblocks packet. - * @returns {Buffer} - */ - -GetBlocksPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -GetBlocksPacket.prototype.fromReader = function fromReader(br) { - this.version = br.readU32(); - - const count = br.readVarint(); - - assert(count <= 50000, 'Too many block hashes.'); - - for (let i = 0; i < count; i++) - this.locator.push(br.readHash('hex')); - - this.stop = br.readHash('hex'); - - if (this.stop === encoding.NULL_HASH) - this.stop = null; - - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -GetBlocksPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate getblocks packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {GetBlocksPacket} - */ - -GetBlocksPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new GetBlocksPacket().fromRaw(data); -}; - -/** - * Represents a `getheaders` packet. + * GetHeader Packets * @extends GetBlocksPacket - * @constructor - * @param {Hash[]} locator - * @param {Hash?} stop */ -function GetHeadersPacket(locator, stop) { - if (!(this instanceof GetHeadersPacket)) - return new GetHeadersPacket(locator, stop); +class GetHeadersPacket extends GetBlocksPacket { + /** + * Create a `getheaders` packet. + * @constructor + * @param {Hash[]} locator + * @param {Hash?} stop + */ - GetBlocksPacket.call(this, locator, stop); + constructor(locator, stop) { + super(locator, stop); + this.cmd = 'getheaders'; + this.type = exports.types.GETHEADERS; + } + + /** + * Instantiate getheaders packet from buffer reader. + * @param {BufferReader} br + * @returns {GetHeadersPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate getheaders packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {GetHeadersPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(GetHeadersPacket.prototype, GetBlocksPacket.prototype); - -GetHeadersPacket.prototype.cmd = 'getheaders'; -GetHeadersPacket.prototype.type = exports.types.GETHEADERS; - /** - * Instantiate getheaders packet from buffer reader. - * @param {BufferReader} br - * @returns {GetHeadersPacket} - */ - -GetHeadersPacket.fromReader = function fromReader(br) { - return new GetHeadersPacket().fromReader(br); -}; - -/** - * Instantiate getheaders packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {GetHeadersPacket} - */ - -GetHeadersPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new GetHeadersPacket().fromRaw(data); -}; - -/** - * Represents a `headers` packet. - * @constructor - * @param {(Headers[])?} items + * Headers Packet + * @extends Packet * @property {Headers[]} items */ -function HeadersPacket(items) { - if (!(this instanceof HeadersPacket)) - return new HeadersPacket(items); +class HeadersPacket extends Packet { + /** + * Create a `headers` packet. + * @constructor + * @param {(Headers[])?} items + */ - Packet.call(this); + constructor(items) { + super(); - this.items = items || []; + this.cmd = 'headers'; + this.type = exports.types.HEADERS; + + this.items = items || []; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + let size = 0; + + size += encoding.sizeVarint(this.items.length); + + for (const item of this.items) + size += item.getSize(); + + return size; + } + + /** + * Serialize headers packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + assert(this.items.length <= 2000, 'Too many headers.'); + + bw.writeVarint(this.items.length); + + for (const item of this.items) + item.toWriter(bw); + + return bw; + } + + /** + * Serialize headers packet. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + const count = br.readVarint(); + + assert(count <= 2000, 'Too many headers.'); + + for (let i = 0; i < count; i++) + this.items.push(Headers.fromReader(br)); + + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate headers packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {VerackPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(HeadersPacket.prototype, Packet.prototype); - -HeadersPacket.prototype.cmd = 'headers'; -HeadersPacket.prototype.type = exports.types.HEADERS; - /** - * Get serialization size. - * @returns {Number} + * SendHeaders Packet + * @extends Packet */ -HeadersPacket.prototype.getSize = function getSize() { - let size = 0; +class SendHeadersPacket extends Packet { + /** + * Create a `sendheaders` packet. + * @constructor + */ - size += encoding.sizeVarint(this.items.length); + constructor() { + super(); + this.cmd = 'sendheaders'; + this.type = exports.types.SENDHEADERS; + } - for (const item of this.items) - size += item.getSize(); + /** + * Instantiate sendheaders packet from buffer reader. + * @param {BufferReader} br + * @returns {SendHeadersPacket} + */ - return size; -}; + static fromReader(br) { + return new this().fromReader(br); + } -/** - * Serialize headers packet to writer. - * @param {BufferWriter} bw - */ + /** + * Instantiate sendheaders packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {SendHeadersPacket} + */ -HeadersPacket.prototype.toWriter = function toWriter(bw) { - assert(this.items.length <= 2000, 'Too many headers.'); - - bw.writeVarint(this.items.length); - - for (const item of this.items) - item.toWriter(bw); - - return bw; -}; - -/** - * Serialize headers packet. - * @returns {Buffer} - */ - -HeadersPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -HeadersPacket.prototype.fromReader = function fromReader(br) { - const count = br.readVarint(); - - assert(count <= 2000, 'Too many headers.'); - - for (let i = 0; i < count; i++) - this.items.push(Headers.fromReader(br)); - - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -HeadersPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate headers packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {VerackPacket} - */ - -HeadersPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new HeadersPacket().fromRaw(data); -}; - -/** - * Represents a `sendheaders` packet. - * @constructor - */ - -function SendHeadersPacket() { - if (!(this instanceof SendHeadersPacket)) - return new SendHeadersPacket(); - - Packet.call(this); + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(SendHeadersPacket.prototype, Packet.prototype); - -SendHeadersPacket.prototype.cmd = 'sendheaders'; -SendHeadersPacket.prototype.type = exports.types.SENDHEADERS; - /** - * Instantiate sendheaders packet from buffer reader. - * @param {BufferReader} br - * @returns {SendHeadersPacket} - */ - -SendHeadersPacket.fromReader = function fromReader(br) { - return new SendHeadersPacket().fromReader(br); -}; - -/** - * Instantiate sendheaders packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {SendHeadersPacket} - */ - -SendHeadersPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new SendHeadersPacket().fromRaw(data); -}; - -/** - * Represents a `block` packet. - * @constructor - * @param {Block|null} block - * @param {Boolean?} witness + * Block Packet + * @extends Packet * @property {Block} block * @property {Boolean} witness */ -function BlockPacket(block, witness) { - if (!(this instanceof BlockPacket)) - return new BlockPacket(block, witness); +class BlockPacket extends Packet { + /** + * Create a `block` packet. + * @constructor + * @param {Block|null} block + * @param {Boolean?} witness + */ - Packet.call(this); + constructor(block, witness) { + super(); - this.block = block || new MemBlock(); - this.witness = witness || false; + this.cmd = 'block'; + this.type = exports.types.BLOCK; + + this.block = block || new MemBlock(); + this.witness = witness || false; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + if (this.witness) + return this.block.getSize(); + return this.block.getBaseSize(); + } + + /** + * Serialize block packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + if (this.witness) + return this.block.toWriter(bw); + return this.block.toNormalWriter(bw); + } + + /** + * Serialize block packet. + * @returns {Buffer} + */ + + toRaw() { + if (this.witness) + return this.block.toRaw(); + return this.block.toNormal(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.block.fromReader(br); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + this.block.fromRaw(data); + return this; + } + + /** + * Instantiate block packet from buffer reader. + * @param {BufferReader} br + * @returns {BlockPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate block packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {BlockPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(BlockPacket.prototype, Packet.prototype); - -BlockPacket.prototype.cmd = 'block'; -BlockPacket.prototype.type = exports.types.BLOCK; - /** - * Get serialization size. - * @returns {Number} - */ - -BlockPacket.prototype.getSize = function getSize() { - if (this.witness) - return this.block.getSize(); - return this.block.getBaseSize(); -}; - -/** - * Serialize block packet to writer. - * @param {BufferWriter} bw - */ - -BlockPacket.prototype.toWriter = function toWriter(bw) { - if (this.witness) - return this.block.toWriter(bw); - return this.block.toNormalWriter(bw); -}; - -/** - * Serialize block packet. - * @returns {Buffer} - */ - -BlockPacket.prototype.toRaw = function toRaw() { - if (this.witness) - return this.block.toRaw(); - return this.block.toNormal(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -BlockPacket.prototype.fromReader = function fromReader(br) { - this.block.fromReader(br); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -BlockPacket.prototype.fromRaw = function fromRaw(data) { - this.block.fromRaw(data); - return this; -}; - -/** - * Instantiate block packet from buffer reader. - * @param {BufferReader} br - * @returns {BlockPacket} - */ - -BlockPacket.fromReader = function fromReader(br) { - return new BlockPacket().fromReader(br); -}; - -/** - * Instantiate block packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {BlockPacket} - */ - -BlockPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new BlockPacket().fromRaw(data); -}; - -/** - * Represents a `tx` packet. - * @constructor - * @param {TX|null} tx - * @param {Boolean?} witness + * TX Packet + * @extends Packet * @property {TX} block * @property {Boolean} witness */ -function TXPacket(tx, witness) { - if (!(this instanceof TXPacket)) - return new TXPacket(tx, witness); +class TXPacket extends Packet { + /** + * Create a `tx` packet. + * @constructor + * @param {TX|null} tx + * @param {Boolean?} witness + */ - Packet.call(this); + constructor(tx, witness) { + super(); - this.tx = tx || new TX(); - this.witness = witness || false; + this.cmd = 'tx'; + this.type = exports.types.TX; + + this.tx = tx || new TX(); + this.witness = witness || false; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + if (this.witness) + return this.tx.getSize(); + return this.tx.getBaseSize(); + } + + /** + * Serialize tx packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + if (this.witness) + return this.tx.toWriter(bw); + return this.tx.toNormalWriter(bw); + } + + /** + * Serialize tx packet. + * @returns {Buffer} + */ + + toRaw() { + if (this.witness) + return this.tx.toRaw(); + return this.tx.toNormal(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.tx.fromRaw(br); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + this.tx.fromRaw(data); + return this; + } + + /** + * Instantiate tx packet from buffer reader. + * @param {BufferReader} br + * @returns {TXPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate tx packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {TXPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(TXPacket.prototype, Packet.prototype); - -TXPacket.prototype.cmd = 'tx'; -TXPacket.prototype.type = exports.types.TX; - -/** - * Get serialization size. - * @returns {Number} - */ - -TXPacket.prototype.getSize = function getSize() { - if (this.witness) - return this.tx.getSize(); - return this.tx.getBaseSize(); -}; - -/** - * Serialize tx packet to writer. - * @param {BufferWriter} bw - */ - -TXPacket.prototype.toWriter = function toWriter(bw) { - if (this.witness) - return this.tx.toWriter(bw); - return this.tx.toNormalWriter(bw); -}; - -/** - * Serialize tx packet. - * @returns {Buffer} - */ - -TXPacket.prototype.toRaw = function toRaw() { - if (this.witness) - return this.tx.toRaw(); - return this.tx.toNormal(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -TXPacket.prototype.fromReader = function fromReader(br) { - this.tx.fromRaw(br); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -TXPacket.prototype.fromRaw = function fromRaw(data) { - this.tx.fromRaw(data); - return this; -}; - -/** - * Instantiate tx packet from buffer reader. - * @param {BufferReader} br - * @returns {TXPacket} - */ - -TXPacket.fromReader = function fromReader(br) { - return new TXPacket().fromReader(br); -}; - -/** - * Instantiate tx packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {TXPacket} - */ - -TXPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new TXPacket().fromRaw(data); -}; - /** * Reject Packet - * @constructor + * @extends Packet * @property {(Number|String)?} code - Code * (see {@link RejectPacket.codes}). * @property {String?} msg - Message. @@ -1463,23 +1490,268 @@ TXPacket.fromRaw = function fromRaw(data, enc) { * @property {(Hash|Buffer)?} data - Transaction or block hash. */ -function RejectPacket(options) { - if (!(this instanceof RejectPacket)) - return new RejectPacket(options); +class RejectPacket extends Packet { + /** + * Create reject packet. + * @constructor + */ - Packet.call(this); + constructor(options) { + super(); - this.message = ''; - this.code = RejectPacket.codes.INVALID; - this.reason = ''; - this.hash = null; + this.cmd = 'reject'; + this.type = exports.types.REJECT; - if (options) - this.fromOptions(options); + this.message = ''; + this.code = RejectPacket.codes.INVALID; + this.reason = ''; + this.hash = null; + + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + let code = options.code; + + if (options.message) + this.message = options.message; + + if (code != null) { + if (typeof code === 'string') + code = RejectPacket.codes[code.toUpperCase()]; + + if (code >= RejectPacket.codes.INTERNAL) + code = RejectPacket.codes.INVALID; + + this.code = code; + } + + if (options.reason) + this.reason = options.reason; + + if (options.hash) + this.hash = options.hash; + + return this; + } + + /** + * Instantiate reject packet from options. + * @param {Object} options + * @returns {RejectPacket} + */ + + static fromOptions(options) { + return new this().fromOptions(options); + } + + /** + * Get uint256le hash if present. + * @returns {Hash} + */ + + rhash() { + return this.hash ? encoding.revHex(this.hash) : null; + } + + /** + * Get symbolic code. + * @returns {String} + */ + + getCode() { + const code = RejectPacket.codesByVal[this.code]; + + if (!code) + return this.code.toString(10); + + return code.toLowerCase(); + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + let size = 0; + + size += encoding.sizeVarString(this.message, 'ascii'); + size += 1; + size += encoding.sizeVarString(this.reason, 'ascii'); + + if (this.hash) + size += 32; + + return size; + } + + /** + * Serialize reject packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + assert(this.message.length <= 12); + assert(this.reason.length <= 111); + + bw.writeVarString(this.message, 'ascii'); + bw.writeU8(this.code); + bw.writeVarString(this.reason, 'ascii'); + + if (this.hash) + bw.writeHash(this.hash); + + return bw; + } + + /** + * Serialize reject packet. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.message = br.readVarString('ascii', 12); + this.code = br.readU8(); + this.reason = br.readVarString('ascii', 111); + + switch (this.message) { + case 'block': + case 'tx': + this.hash = br.readHash('hex'); + break; + default: + this.hash = null; + break; + } + + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate reject packet from buffer reader. + * @param {BufferReader} br + * @returns {RejectPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate reject packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {RejectPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data, enc); + } + + /** + * Inject properties from reason message and object. + * @private + * @param {Number} code + * @param {String} reason + * @param {String?} msg + * @param {Hash?} hash + */ + + fromReason(code, reason, msg, hash) { + if (typeof code === 'string') + code = RejectPacket.codes[code.toUpperCase()]; + + if (!code) + code = RejectPacket.codes.INVALID; + + if (code >= RejectPacket.codes.INTERNAL) + code = RejectPacket.codes.INVALID; + + this.message = ''; + this.code = code; + this.reason = reason; + + if (msg) { + assert(hash); + this.message = msg; + this.hash = hash; + } + + return this; + } + + /** + * Instantiate reject packet from reason message. + * @param {Number} code + * @param {String} reason + * @param {String?} msg + * @param {Hash?} hash + * @returns {RejectPacket} + */ + + static fromReason(code, reason, msg, hash) { + return new this().fromReason(code, reason, msg, hash); + } + + /** + * Instantiate reject packet from verify error. + * @param {VerifyError} err + * @param {(TX|Block)?} obj + * @returns {RejectPacket} + */ + + static fromError(err, obj) { + return this.fromReason(err.code, err.reason, obj); + } + + /** + * Inspect reject packet. + * @returns {String} + */ + + inspect() { + const code = RejectPacket.codesByVal[this.code] || this.code; + const hash = this.hash ? encoding.revHex(this.hash) : null; + return ''; + } } -Object.setPrototypeOf(RejectPacket.prototype, Packet.prototype); - /** * Reject codes. Note that `internal` and higher * are not meant for use on the p2p network. @@ -1524,1632 +1796,1418 @@ RejectPacket.codesByVal = { 0x103: 'CONFLICT' }; -RejectPacket.prototype.cmd = 'reject'; -RejectPacket.prototype.type = exports.types.REJECT; - /** - * Inject properties from options object. - * @private - * @param {Object} options + * Mempool Packet + * @extends Packet */ -RejectPacket.prototype.fromOptions = function fromOptions(options) { - let code = options.code; +class MempoolPacket extends Packet { + /** + * Create a `mempool` packet. + * @constructor + */ - if (options.message) - this.message = options.message; - - if (code != null) { - if (typeof code === 'string') - code = RejectPacket.codes[code.toUpperCase()]; - - if (code >= RejectPacket.codes.INTERNAL) - code = RejectPacket.codes.INVALID; - - this.code = code; + constructor() { + super(); + this.cmd = 'mempool'; + this.type = exports.types.MEMPOOL; } - if (options.reason) - this.reason = options.reason; + /** + * Instantiate mempool packet from buffer reader. + * @param {BufferReader} br + * @returns {VerackPacket} + */ - if (options.hash) - this.hash = options.hash; - - return this; -}; - -/** - * Instantiate reject packet from options. - * @param {Object} options - * @returns {RejectPacket} - */ - -RejectPacket.fromOptions = function fromOptions(options) { - return new RejectPacket().fromOptions(options); -}; - -/** - * Get uint256le hash if present. - * @returns {Hash} - */ - -RejectPacket.prototype.rhash = function rhash() { - return this.hash ? encoding.revHex(this.hash) : null; -}; - -/** - * Get symbolic code. - * @returns {String} - */ - -RejectPacket.prototype.getCode = function getCode() { - const code = RejectPacket.codesByVal[this.code]; - - if (!code) - return this.code.toString(10); - - return code.toLowerCase(); -}; - -/** - * Get serialization size. - * @returns {Number} - */ - -RejectPacket.prototype.getSize = function getSize() { - let size = 0; - - size += encoding.sizeVarString(this.message, 'ascii'); - size += 1; - size += encoding.sizeVarString(this.reason, 'ascii'); - - if (this.hash) - size += 32; - - return size; -}; - -/** - * Serialize reject packet to writer. - * @param {BufferWriter} bw - */ - -RejectPacket.prototype.toWriter = function toWriter(bw) { - assert(this.message.length <= 12); - assert(this.reason.length <= 111); - - bw.writeVarString(this.message, 'ascii'); - bw.writeU8(this.code); - bw.writeVarString(this.reason, 'ascii'); - - if (this.hash) - bw.writeHash(this.hash); - - return bw; -}; - -/** - * Serialize reject packet. - * @returns {Buffer} - */ - -RejectPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -RejectPacket.prototype.fromReader = function fromReader(br) { - this.message = br.readVarString('ascii', 12); - this.code = br.readU8(); - this.reason = br.readVarString('ascii', 111); - - switch (this.message) { - case 'block': - case 'tx': - this.hash = br.readHash('hex'); - break; - default: - this.hash = null; - break; + static fromReader(br) { + return new this().fromReader(br); } - return this; -}; + /** + * Instantiate mempool packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {VerackPacket} + */ -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -RejectPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate reject packet from buffer reader. - * @param {BufferReader} br - * @returns {RejectPacket} - */ - -RejectPacket.fromReader = function fromReader(br) { - return new RejectPacket().fromReader(br); -}; - -/** - * Instantiate reject packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {RejectPacket} - */ - -RejectPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new RejectPacket().fromRaw(data, enc); -}; - -/** - * Inject properties from reason message and object. - * @private - * @param {Number} code - * @param {String} reason - * @param {String?} msg - * @param {Hash?} hash - */ - -RejectPacket.prototype.fromReason = function fromReason(code, reason, msg, hash) { - if (typeof code === 'string') - code = RejectPacket.codes[code.toUpperCase()]; - - if (!code) - code = RejectPacket.codes.INVALID; - - if (code >= RejectPacket.codes.INTERNAL) - code = RejectPacket.codes.INVALID; - - this.message = ''; - this.code = code; - this.reason = reason; - - if (msg) { - assert(hash); - this.message = msg; - this.hash = hash; + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); } - - return this; -}; - -/** - * Instantiate reject packet from reason message. - * @param {Number} code - * @param {String} reason - * @param {String?} msg - * @param {Hash?} hash - * @returns {RejectPacket} - */ - -RejectPacket.fromReason = function fromReason(code, reason, msg, hash) { - return new RejectPacket().fromReason(code, reason, msg, hash); -}; - -/** - * Instantiate reject packet from verify error. - * @param {VerifyError} err - * @param {(TX|Block)?} obj - * @returns {RejectPacket} - */ - -RejectPacket.fromError = function fromError(err, obj) { - return RejectPacket.fromReason(err.code, err.reason, obj); -}; - -/** - * Inspect reject packet. - * @returns {String} - */ - -RejectPacket.prototype.inspect = function inspect() { - const code = RejectPacket.codesByVal[this.code] || this.code; - const hash = this.hash ? encoding.revHex(this.hash) : null; - return ''; -}; - -/** - * Represents a `mempool` packet. - * @constructor - */ - -function MempoolPacket() { - if (!(this instanceof MempoolPacket)) - return new MempoolPacket(); - - Packet.call(this); } -Object.setPrototypeOf(MempoolPacket.prototype, Packet.prototype); - -MempoolPacket.prototype.cmd = 'mempool'; -MempoolPacket.prototype.type = exports.types.MEMPOOL; - /** - * Instantiate mempool packet from buffer reader. - * @param {BufferReader} br - * @returns {VerackPacket} + * FilterLoad Packet + * @extends Packet */ -MempoolPacket.fromReader = function fromReader(br) { - return new MempoolPacket().fromReader(br); -}; +class FilterLoadPacket extends Packet { + /** + * Create a `filterload` packet. + * @constructor + * @param {BloomFilter|null} filter + */ -/** - * Instantiate mempool packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {VerackPacket} - */ + constructor(filter) { + super(); -MempoolPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new MempoolPacket().fromRaw(data); -}; + this.cmd = 'filterload'; + this.type = exports.types.FILTERLOAD; -/** - * Represents a `filterload` packet. - * @constructor - * @param {BloomFilter|null} filter - */ + this.filter = filter || new BloomFilter(); + } -function FilterLoadPacket(filter) { - if (!(this instanceof FilterLoadPacket)) - return new FilterLoadPacket(filter); + /** + * Get serialization size. + * @returns {Number} + */ - Packet.call(this); + getSize() { + return this.filter.getSize(); + } - this.filter = filter || new BloomFilter(); + /** + * Serialize filterload packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + return this.filter.toWriter(bw); + } + + /** + * Serialize filterload packet. + * @returns {Buffer} + */ + + toRaw() { + return this.filter.toRaw(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.filter.fromReader(br); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + this.filter.fromRaw(data); + return this; + } + + /** + * Instantiate filterload packet from buffer reader. + * @param {BufferReader} br + * @returns {FilterLoadPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate filterload packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {FilterLoadPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } + + /** + * Ensure the filter is within the size limits. + * @returns {Boolean} + */ + + isWithinConstraints() { + return this.filter.isWithinConstraints(); + } } -Object.setPrototypeOf(FilterLoadPacket.prototype, Packet.prototype); - -FilterLoadPacket.prototype.cmd = 'filterload'; -FilterLoadPacket.prototype.type = exports.types.FILTERLOAD; - /** - * Get serialization size. - * @returns {Number} - */ - -FilterLoadPacket.prototype.getSize = function getSize() { - return this.filter.getSize(); -}; - -/** - * Serialize filterload packet to writer. - * @param {BufferWriter} bw - */ - -FilterLoadPacket.prototype.toWriter = function toWriter(bw) { - return this.filter.toWriter(bw); -}; - -/** - * Serialize filterload packet. - * @returns {Buffer} - */ - -FilterLoadPacket.prototype.toRaw = function toRaw() { - return this.filter.toRaw(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -FilterLoadPacket.prototype.fromReader = function fromReader(br) { - this.filter.fromReader(br); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -FilterLoadPacket.prototype.fromRaw = function fromRaw(data) { - this.filter.fromRaw(data); - return this; -}; - -/** - * Instantiate filterload packet from buffer reader. - * @param {BufferReader} br - * @returns {FilterLoadPacket} - */ - -FilterLoadPacket.fromReader = function fromReader(br) { - return new FilterLoadPacket().fromReader(br); -}; - -/** - * Instantiate filterload packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {FilterLoadPacket} - */ - -FilterLoadPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new FilterLoadPacket().fromRaw(data); -}; - -/** - * Ensure the filter is within the size limits. - * @returns {Boolean} - */ - -FilterLoadPacket.prototype.isWithinConstraints = function isWithinConstraints() { - return this.filter.isWithinConstraints(); -}; - -/** - * Represents a `filteradd` packet. - * @constructor - * @param {Buffer?} data + * FilterAdd Packet + * @extends Packet * @property {Buffer} data */ -function FilterAddPacket(data) { - if (!(this instanceof FilterAddPacket)) - return new FilterAddPacket(data); +class FilterAddPacket extends Packet { + /** + * Create a `filteradd` packet. + * @constructor + * @param {Buffer?} data + */ - Packet.call(this); + constructor(data) { + super(); - this.data = data || DUMMY; + this.cmd = 'filteradd'; + this.type = exports.types.FILTERADD; + + this.data = data || DUMMY; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return encoding.sizeVarBytes(this.data); + } + + /** + * Serialize filteradd packet to writer. + * @returns {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeVarBytes(this.data); + return bw; + } + + /** + * Serialize filteradd packet. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.data = br.readVarBytes(); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate filteradd packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {FilterAddPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(FilterAddPacket.prototype, Packet.prototype); - -FilterAddPacket.prototype.cmd = 'filteradd'; -FilterAddPacket.prototype.type = exports.types.FILTERADD; - /** - * Get serialization size. - * @returns {Number} + * FilterClear Packet + * @extends Packet */ -FilterAddPacket.prototype.getSize = function getSize() { - return encoding.sizeVarBytes(this.data); -}; +class FilterClearPacket extends Packet { + /** + * Create a `filterclear` packet. + * @constructor + */ -/** - * Serialize filteradd packet to writer. - * @returns {BufferWriter} bw - */ + constructor() { + super(); + this.cmd = 'filterclear'; + this.type = exports.types.FILTERCLEAR; + } -FilterAddPacket.prototype.toWriter = function toWriter(bw) { - bw.writeVarBytes(this.data); - return bw; -}; + /** + * Instantiate filterclear packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {FilterClearPacket} + */ -/** - * Serialize filteradd packet. - * @returns {Buffer} - */ - -FilterAddPacket.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -FilterAddPacket.prototype.fromReader = function fromReader(br) { - this.data = br.readVarBytes(); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -FilterAddPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate filteradd packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {FilterAddPacket} - */ - -FilterAddPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new FilterAddPacket().fromRaw(data); -}; - -/** - * Represents a `filterclear` packet. - * @constructor - */ - -function FilterClearPacket() { - if (!(this instanceof FilterClearPacket)) - return new FilterClearPacket(); - - Packet.call(this); + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(FilterClearPacket.prototype, Packet.prototype); - -FilterClearPacket.prototype.cmd = 'filterclear'; -FilterClearPacket.prototype.type = exports.types.FILTERCLEAR; - /** - * Instantiate filterclear packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {FilterClearPacket} - */ - -FilterClearPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new FilterClearPacket().fromRaw(data); -}; - -/** - * Represents a `merkleblock` packet. - * @constructor - * @param {MerkleBlock?} block + * MerkleBlock Packet + * @extends Packet * @property {MerkleBlock} block */ -function MerkleBlockPacket(block) { - if (!(this instanceof MerkleBlockPacket)) - return new MerkleBlockPacket(block); +class MerkleBlockPacket extends Packet { + /** + * Create a `merkleblock` packet. + * @constructor + * @param {MerkleBlock?} block + */ - Packet.call(this); + constructor(block) { + super(); - this.block = block || new MerkleBlock(); + this.cmd = 'merkleblock'; + this.type = exports.types.MERKLEBLOCK; + + this.block = block || new MerkleBlock(); + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return this.block.getSize(); + } + + /** + * Serialize merkleblock packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + return this.block.toWriter(bw); + } + + /** + * Serialize merkleblock packet. + * @returns {Buffer} + */ + + toRaw() { + return this.block.toRaw(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.block.fromReader(br); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + this.block.fromRaw(data); + return this; + } + + /** + * Instantiate merkleblock packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {MerkleBlockPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(MerkleBlockPacket.prototype, Packet.prototype); - -MerkleBlockPacket.prototype.cmd = 'merkleblock'; -MerkleBlockPacket.prototype.type = exports.types.MERKLEBLOCK; - /** - * Get serialization size. - * @returns {Number} - */ - -MerkleBlockPacket.prototype.getSize = function getSize() { - return this.block.getSize(); -}; - -/** - * Serialize merkleblock packet to writer. - * @param {BufferWriter} bw - */ - -MerkleBlockPacket.prototype.toWriter = function toWriter(bw) { - return this.block.toWriter(bw); -}; - -/** - * Serialize merkleblock packet. - * @returns {Buffer} - */ - -MerkleBlockPacket.prototype.toRaw = function toRaw() { - return this.block.toRaw(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -MerkleBlockPacket.prototype.fromReader = function fromReader(br) { - this.block.fromReader(br); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -MerkleBlockPacket.prototype.fromRaw = function fromRaw(data) { - this.block.fromRaw(data); - return this; -}; - -/** - * Instantiate merkleblock packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {MerkleBlockPacket} - */ - -MerkleBlockPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new MerkleBlockPacket().fromRaw(data); -}; - -/** - * Represents a `feefilter` packet. - * @constructor - * @param {Rate?} rate + * FeeFilter Packet + * @extends Packet * @property {Rate} rate */ -function FeeFilterPacket(rate) { - if (!(this instanceof FeeFilterPacket)) - return new FeeFilterPacket(rate); +class FeeFilterPacket extends Packet { + /** + * Create a `feefilter` packet. + * @constructor + * @param {Rate?} rate + */ - Packet.call(this); + constructor(rate) { + super(); - this.rate = rate || 0; + this.cmd = 'feefilter'; + this.type = exports.types.FEEFILTER; + + this.rate = rate || 0; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return 8; + } + + /** + * Serialize feefilter packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeI64(this.rate); + return bw; + } + + /** + * Serialize feefilter packet. + * @returns {Buffer} + */ + + toRaw() { + return this.toWriter(new StaticWriter(8)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.rate = br.readI64(); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate feefilter packet from buffer reader. + * @param {BufferReader} br + * @returns {FeeFilterPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate feefilter packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {FeeFilterPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(FeeFilterPacket.prototype, Packet.prototype); - -FeeFilterPacket.prototype.cmd = 'feefilter'; -FeeFilterPacket.prototype.type = exports.types.FEEFILTER; - /** - * Get serialization size. - * @returns {Number} - */ - -FeeFilterPacket.prototype.getSize = function getSize() { - return 8; -}; - -/** - * Serialize feefilter packet to writer. - * @param {BufferWriter} bw - */ - -FeeFilterPacket.prototype.toWriter = function toWriter(bw) { - bw.writeI64(this.rate); - return bw; -}; - -/** - * Serialize feefilter packet. - * @returns {Buffer} - */ - -FeeFilterPacket.prototype.toRaw = function toRaw() { - return this.toWriter(new StaticWriter(8)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -FeeFilterPacket.prototype.fromReader = function fromReader(br) { - this.rate = br.readI64(); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -FeeFilterPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate feefilter packet from buffer reader. - * @param {BufferReader} br - * @returns {FeeFilterPacket} - */ - -FeeFilterPacket.fromReader = function fromReader(br) { - return new FeeFilterPacket().fromReader(br); -}; - -/** - * Instantiate feefilter packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {FeeFilterPacket} - */ - -FeeFilterPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new FeeFilterPacket().fromRaw(data); -}; - -/** - * Represents a `sendcmpct` packet. - * @constructor - * @param {Number|null} mode - * @param {Number|null} version + * SendCmpct Packet + * @extends Packet * @property {Number} mode * @property {Number} version */ -function SendCmpctPacket(mode, version) { - if (!(this instanceof SendCmpctPacket)) - return new SendCmpctPacket(mode, version); +class SendCmpctPacket extends Packet { + /** + * Create a `sendcmpct` packet. + * @constructor + * @param {Number|null} mode + * @param {Number|null} version + */ - Packet.call(this); + constructor(mode, version) { + super(); - this.mode = mode || 0; - this.version = version || 1; + this.cmd = 'sendcmpct'; + this.type = exports.types.SENDCMPCT; + + this.mode = mode || 0; + this.version = version || 1; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return 9; + } + + /** + * Serialize sendcmpct packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeU8(this.mode); + bw.writeU64(this.version); + return bw; + } + + /** + * Serialize sendcmpct packet. + * @returns {Buffer} + */ + + toRaw() { + return this.toWriter(new StaticWriter(9)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.mode = br.readU8(); + this.version = br.readU64(); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate sendcmpct packet from buffer reader. + * @param {BufferReader} br + * @returns {SendCmpctPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate sendcmpct packet from buffer reader. + * @param {BufferReader} br + * @returns {SendCmpctPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate sendcmpct packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {SendCmpctPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(SendCmpctPacket.prototype, Packet.prototype); - -SendCmpctPacket.prototype.cmd = 'sendcmpct'; -SendCmpctPacket.prototype.type = exports.types.SENDCMPCT; - /** - * Get serialization size. - * @returns {Number} - */ - -SendCmpctPacket.prototype.getSize = function getSize() { - return 9; -}; - -/** - * Serialize sendcmpct packet to writer. - * @param {BufferWriter} bw - */ - -SendCmpctPacket.prototype.toWriter = function toWriter(bw) { - bw.writeU8(this.mode); - bw.writeU64(this.version); - return bw; -}; - -/** - * Serialize sendcmpct packet. - * @returns {Buffer} - */ - -SendCmpctPacket.prototype.toRaw = function toRaw() { - return this.toWriter(new StaticWriter(9)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -SendCmpctPacket.prototype.fromReader = function fromReader(br) { - this.mode = br.readU8(); - this.version = br.readU64(); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -SendCmpctPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate sendcmpct packet from buffer reader. - * @param {BufferReader} br - * @returns {SendCmpctPacket} - */ - -SendCmpctPacket.fromReader = function fromReader(br) { - return new SendCmpctPacket().fromReader(br); -}; - -/** - * Instantiate sendcmpct packet from buffer reader. - * @param {BufferReader} br - * @returns {SendCmpctPacket} - */ - -SendCmpctPacket.fromReader = function fromReader(br) { - return new SendCmpctPacket().fromReader(br); -}; - -/** - * Instantiate sendcmpct packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {SendCmpctPacket} - */ - -SendCmpctPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new SendCmpctPacket().fromRaw(data); -}; - -/** - * Represents a `cmpctblock` packet. - * @constructor - * @param {Block|null} block - * @param {Boolean|null} witness + * CmpctBlock Packet + * @extends Packet * @property {Block} block * @property {Boolean} witness */ -function CmpctBlockPacket(block, witness) { - if (!(this instanceof CmpctBlockPacket)) - return new CmpctBlockPacket(block, witness); +class CmpctBlockPacket extends Packet { + /** + * Create a `cmpctblock` packet. + * @constructor + * @param {Block|null} block + * @param {Boolean|null} witness + */ - Packet.call(this); + constructor(block, witness) { + super(); - this.block = block || new bip152.CompactBlock(); - this.witness = witness || false; + this.cmd = 'cmpctblock'; + this.type = exports.types.CMPCTBLOCK; + + this.block = block || new bip152.CompactBlock(); + this.witness = witness || false; + } + + /** + * Serialize cmpctblock packet. + * @returns {Buffer} + */ + + getSize() { + if (this.witness) + return this.block.getSize(true); + return this.block.getSize(false); + } + + /** + * Serialize cmpctblock packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + if (this.witness) + return this.block.toWriter(bw); + return this.block.toNormalWriter(bw); + } + + /** + * Serialize cmpctblock packet. + * @returns {Buffer} + */ + + toRaw() { + if (this.witness) + return this.block.toRaw(); + return this.block.toNormal(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.block.fromReader(br); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + this.block.fromRaw(data); + return this; + } + + /** + * Instantiate cmpctblock packet from buffer reader. + * @param {BufferReader} br + * @returns {CmpctBlockPacket} + */ + + static fromReader(br) { + return new this().fromRaw(br); + } + + /** + * Instantiate cmpctblock packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {CmpctBlockPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(CmpctBlockPacket.prototype, Packet.prototype); - -CmpctBlockPacket.prototype.cmd = 'cmpctblock'; -CmpctBlockPacket.prototype.type = exports.types.CMPCTBLOCK; - /** - * Serialize cmpctblock packet. - * @returns {Buffer} - */ - -CmpctBlockPacket.prototype.getSize = function getSize() { - if (this.witness) - return this.block.getSize(true); - return this.block.getSize(false); -}; - -/** - * Serialize cmpctblock packet to writer. - * @param {BufferWriter} bw - */ - -CmpctBlockPacket.prototype.toWriter = function toWriter(bw) { - if (this.witness) - return this.block.toWriter(bw); - return this.block.toNormalWriter(bw); -}; - -/** - * Serialize cmpctblock packet. - * @returns {Buffer} - */ - -CmpctBlockPacket.prototype.toRaw = function toRaw() { - if (this.witness) - return this.block.toRaw(); - return this.block.toNormal(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -CmpctBlockPacket.prototype.fromReader = function fromReader(br) { - this.block.fromReader(br); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -CmpctBlockPacket.prototype.fromRaw = function fromRaw(data) { - this.block.fromRaw(data); - return this; -}; - -/** - * Instantiate cmpctblock packet from buffer reader. - * @param {BufferReader} br - * @returns {CmpctBlockPacket} - */ - -CmpctBlockPacket.fromReader = function fromReader(br) { - return new CmpctBlockPacket().fromRaw(br); -}; - -/** - * Instantiate cmpctblock packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {CmpctBlockPacket} - */ - -CmpctBlockPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new CmpctBlockPacket().fromRaw(data); -}; - -/** - * Represents a `getblocktxn` packet. - * @constructor - * @param {TXRequest?} request + * GetBlockTxn Packet + * @extends Packet * @property {TXRequest} request */ -function GetBlockTxnPacket(request) { - if (!(this instanceof GetBlockTxnPacket)) - return new GetBlockTxnPacket(request); +class GetBlockTxnPacket extends Packet { + /** + * Create a `getblocktxn` packet. + * @constructor + * @param {TXRequest?} request + */ - Packet.call(this); + constructor(request) { + super(); - this.request = request || new bip152.TXRequest(); + this.cmd = 'getblocktxn'; + this.type = exports.types.GETBLOCKTXN; + + this.request = request || new bip152.TXRequest(); + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return this.request.getSize(); + } + + /** + * Serialize getblocktxn packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + return this.request.toWriter(bw); + } + + /** + * Serialize getblocktxn packet. + * @returns {Buffer} + */ + + toRaw() { + return this.request.toRaw(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.request.fromReader(br); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + this.request.fromRaw(data); + return this; + } + + /** + * Instantiate getblocktxn packet from buffer reader. + * @param {BufferReader} br + * @returns {GetBlockTxnPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate getblocktxn packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {GetBlockTxnPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(GetBlockTxnPacket.prototype, Packet.prototype); - -GetBlockTxnPacket.prototype.cmd = 'getblocktxn'; -GetBlockTxnPacket.prototype.type = exports.types.GETBLOCKTXN; - /** - * Get serialization size. - * @returns {Number} - */ - -GetBlockTxnPacket.prototype.getSize = function getSize() { - return this.request.getSize(); -}; - -/** - * Serialize getblocktxn packet to writer. - * @param {BufferWriter} bw - */ - -GetBlockTxnPacket.prototype.toWriter = function toWriter(bw) { - return this.request.toWriter(bw); -}; - -/** - * Serialize getblocktxn packet. - * @returns {Buffer} - */ - -GetBlockTxnPacket.prototype.toRaw = function toRaw() { - return this.request.toRaw(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -GetBlockTxnPacket.prototype.fromReader = function fromReader(br) { - this.request.fromReader(br); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -GetBlockTxnPacket.prototype.fromRaw = function fromRaw(data) { - this.request.fromRaw(data); - return this; -}; - -/** - * Instantiate getblocktxn packet from buffer reader. - * @param {BufferReader} br - * @returns {GetBlockTxnPacket} - */ - -GetBlockTxnPacket.fromReader = function fromReader(br) { - return new GetBlockTxnPacket().fromReader(br); -}; - -/** - * Instantiate getblocktxn packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {GetBlockTxnPacket} - */ - -GetBlockTxnPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new GetBlockTxnPacket().fromRaw(data); -}; - -/** - * Represents a `blocktxn` packet. - * @constructor - * @param {TXResponse?} response - * @param {Boolean?} witness + * BlockTxn Packet + * @extends Packet * @property {TXResponse} response * @property {Boolean} witness */ -function BlockTxnPacket(response, witness) { - if (!(this instanceof BlockTxnPacket)) - return new BlockTxnPacket(response, witness); +class BlockTxnPacket extends Packet { + /** + * Create a `blocktxn` packet. + * @constructor + * @param {TXResponse?} response + * @param {Boolean?} witness + */ - Packet.call(this); + constructor(response, witness) { + super(); - this.response = response || new bip152.TXResponse(); - this.witness = witness || false; + this.cmd = 'blocktxn'; + this.type = exports.types.BLOCKTXN; + + this.response = response || new bip152.TXResponse(); + this.witness = witness || false; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + if (this.witness) + return this.response.getSize(true); + return this.response.getSize(false); + } + + /** + * Serialize blocktxn packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + if (this.witness) + return this.response.toWriter(bw); + return this.response.toNormalWriter(bw); + } + + /** + * Serialize blocktxn packet. + * @returns {Buffer} + */ + + toRaw() { + if (this.witness) + return this.response.toRaw(); + return this.response.toNormal(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.response.fromReader(br); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + this.response.fromRaw(data); + return this; + } + + /** + * Instantiate blocktxn packet from buffer reader. + * @param {BufferReader} br + * @returns {BlockTxnPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate blocktxn packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {BlockTxnPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(BlockTxnPacket.prototype, Packet.prototype); - -BlockTxnPacket.prototype.cmd = 'blocktxn'; -BlockTxnPacket.prototype.type = exports.types.BLOCKTXN; - /** - * Get serialization size. - * @returns {Number} - */ - -BlockTxnPacket.prototype.getSize = function getSize() { - if (this.witness) - return this.response.getSize(true); - return this.response.getSize(false); -}; - -/** - * Serialize blocktxn packet to writer. - * @param {BufferWriter} bw - */ - -BlockTxnPacket.prototype.toWriter = function toWriter(bw) { - if (this.witness) - return this.response.toWriter(bw); - return this.response.toNormalWriter(bw); -}; - -/** - * Serialize blocktxn packet. - * @returns {Buffer} - */ - -BlockTxnPacket.prototype.toRaw = function toRaw() { - if (this.witness) - return this.response.toRaw(); - return this.response.toNormal(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -BlockTxnPacket.prototype.fromReader = function fromReader(br) { - this.response.fromReader(br); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -BlockTxnPacket.prototype.fromRaw = function fromRaw(data) { - this.response.fromRaw(data); - return this; -}; - -/** - * Instantiate blocktxn packet from buffer reader. - * @param {BufferReader} br - * @returns {BlockTxnPacket} - */ - -BlockTxnPacket.fromReader = function fromReader(br) { - return new BlockTxnPacket().fromReader(br); -}; - -/** - * Instantiate blocktxn packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {BlockTxnPacket} - */ - -BlockTxnPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new BlockTxnPacket().fromRaw(data); -}; - -/** - * Represents a `encinit` packet. - * @constructor - * @param {Buffer|null} publicKey - * @param {Number|null} cipher + * Encinit Packet + * @extends Packet * @property {Buffer} publicKey * @property {Number} cipher */ -function EncinitPacket(publicKey, cipher) { - if (!(this instanceof EncinitPacket)) - return new EncinitPacket(publicKey, cipher); +class EncinitPacket extends Packet { + /** + * Create an `encinit` packet. + * @constructor + * @param {Buffer|null} publicKey + * @param {Number|null} cipher + */ - Packet.call(this); + constructor(publicKey, cipher) { + super(); - this.publicKey = publicKey || encoding.ZERO_KEY; - this.cipher = cipher || 0; + this.cmd = 'encinit'; + this.type = exports.types.ENCINIT; + + this.publicKey = publicKey || encoding.ZERO_KEY; + this.cipher = cipher || 0; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return 34; + } + + /** + * Serialize encinit packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeBytes(this.publicKey); + bw.writeU8(this.cipher); + return bw; + } + + /** + * Serialize encinit packet. + * @returns {Buffer} + */ + + toRaw() { + return this.toWriter(new StaticWriter(34)).render(); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.publicKey = br.readBytes(33); + this.cipher = br.readU8(); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate getblocks packet from buffer reader. + * @param {BufferReader} br + * @returns {EncinitPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate getblocks packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {EncinitPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(EncinitPacket.prototype, Packet.prototype); - -EncinitPacket.prototype.cmd = 'encinit'; -EncinitPacket.prototype.type = exports.types.ENCINIT; - /** - * Get serialization size. - * @returns {Number} - */ - -EncinitPacket.prototype.getSize = function getSize() { - return 34; -}; - -/** - * Serialize encinit packet to writer. - * @param {BufferWriter} bw - */ - -EncinitPacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.publicKey); - bw.writeU8(this.cipher); - return bw; -}; - -/** - * Serialize encinit packet. - * @returns {Buffer} - */ - -EncinitPacket.prototype.toRaw = function toRaw() { - return this.toWriter(new StaticWriter(34)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -EncinitPacket.prototype.fromReader = function fromReader(br) { - this.publicKey = br.readBytes(33); - this.cipher = br.readU8(); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -EncinitPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate getblocks packet from buffer reader. - * @param {BufferReader} br - * @returns {EncinitPacket} - */ - -EncinitPacket.fromReader = function fromReader(br) { - return new EncinitPacket().fromReader(br); -}; - -/** - * Instantiate getblocks packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {EncinitPacket} - */ - -EncinitPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new EncinitPacket().fromRaw(data); -}; - -/** - * Represents a `encack` packet. - * @constructor - * @param {Buffer?} publicKey + * Encack Packet + * @extends Packet * @property {Buffer} publicKey */ -function EncackPacket(publicKey) { - if (!(this instanceof EncackPacket)) - return new EncackPacket(publicKey); +class EncackPacket extends Packet { + /** + * Create a `encack` packet. + * @constructor + * @param {Buffer?} publicKey + */ - Packet.call(this); + constructor(publicKey) { + super(); - this.publicKey = publicKey || encoding.ZERO_KEY; + this.cmd = 'encack'; + this.type = exports.types.ENCACK; + + this.publicKey = publicKey || encoding.ZERO_KEY; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return 33; + } + + /** + * Serialize encack packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeBytes(this.publicKey); + return bw; + } + + /** + * Serialize encack packet. + * @returns {Buffer} + */ + + toRaw() { + return this.publicKey; + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.publicKey = br.readBytes(33); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate encack packet from buffer reader. + * @param {BufferReader} br + * @returns {EncackPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate encack packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {EncackPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(EncackPacket.prototype, Packet.prototype); - -EncackPacket.prototype.cmd = 'encack'; -EncackPacket.prototype.type = exports.types.ENCACK; - /** - * Get serialization size. - * @returns {Number} - */ - -EncackPacket.prototype.getSize = function getSize() { - return 33; -}; - -/** - * Serialize encack packet to writer. - * @param {BufferWriter} bw - */ - -EncackPacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.publicKey); - return bw; -}; - -/** - * Serialize encack packet. - * @returns {Buffer} - */ - -EncackPacket.prototype.toRaw = function toRaw() { - return this.publicKey; -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -EncackPacket.prototype.fromReader = function fromReader(br) { - this.publicKey = br.readBytes(33); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -EncackPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate encack packet from buffer reader. - * @param {BufferReader} br - * @returns {EncackPacket} - */ - -EncackPacket.fromReader = function fromReader(br) { - return new EncackPacket().fromReader(br); -}; - -/** - * Instantiate encack packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {EncackPacket} - */ - -EncackPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new EncackPacket().fromRaw(data); -}; - -/** - * Represents a `authchallenge` packet. - * @constructor - * @param {Buffer?} hash + * AuthChallenge Packet + * @extends Packet * @property {Buffer} hash */ -function AuthChallengePacket(hash) { - if (!(this instanceof AuthChallengePacket)) - return new AuthChallengePacket(hash); +class AuthChallengePacket extends Packet { + /** + * Create an `authchallenge` packet. + * @constructor + * @param {Buffer?} hash + */ - Packet.call(this); + constructor(hash) { + super(); - this.hash = hash || encoding.ZERO_HASH; + this.cmd = 'authchallenge'; + this.type = exports.types.AUTHCHALLENGE; + + this.hash = hash || encoding.ZERO_HASH; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return 32; + } + + /** + * Serialize authchallenge packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeBytes(this.hash); + return bw; + } + + /** + * Serialize authchallenge packet. + * @returns {Buffer} + */ + + toRaw() { + return this.hash; + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.hash = br.readHash(); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate authchallenge packet from buffer reader. + * @param {BufferReader} br + * @returns {AuthChallengePacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate authchallenge packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {AuthChallengePacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(AuthChallengePacket.prototype, Packet.prototype); - -AuthChallengePacket.prototype.cmd = 'authchallenge'; -AuthChallengePacket.prototype.type = exports.types.AUTHCHALLENGE; - /** - * Get serialization size. - * @returns {Number} - */ - -EncackPacket.prototype.getSize = function getSize() { - return 32; -}; - -/** - * Serialize authchallenge packet to writer. - * @param {BufferWriter} bw - */ - -AuthChallengePacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.hash); - return bw; -}; - -/** - * Serialize authchallenge packet. - * @returns {Buffer} - */ - -AuthChallengePacket.prototype.toRaw = function toRaw() { - return this.hash; -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -AuthChallengePacket.prototype.fromReader = function fromReader(br) { - this.hash = br.readHash(); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -AuthChallengePacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate authchallenge packet from buffer reader. - * @param {BufferReader} br - * @returns {AuthChallengePacket} - */ - -AuthChallengePacket.fromReader = function fromReader(br) { - return new AuthChallengePacket().fromReader(br); -}; - -/** - * Instantiate authchallenge packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {AuthChallengePacket} - */ - -AuthChallengePacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new AuthChallengePacket().fromRaw(data); -}; - -/** - * Represents a `authreply` packet. - * @constructor - * @param {Buffer?} signature + * AuthReply Packet + * @extends Packet * @property {Buffer} signature */ -function AuthReplyPacket(signature) { - if (!(this instanceof AuthReplyPacket)) - return new AuthReplyPacket(signature); +class AuthReplyPacket extends Packet { + /** + * Create a `authreply` packet. + * @constructor + * @param {Buffer?} signature + */ - Packet.call(this); + constructor(signature) { + super(); - this.signature = signature || encoding.ZERO_SIG64; + this.cmd = 'authreply'; + this.type = exports.types.AUTHREPLY; + + this.signature = signature || encoding.ZERO_SIG64; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return 64; + } + + /** + * Serialize authreply packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeBytes(this.signature); + return bw; + } + + /** + * Serialize authreply packet. + * @returns {Buffer} + */ + + toRaw() { + return this.signature; + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.signature = br.readBytes(64); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate authreply packet from buffer reader. + * @param {BufferReader} br + * @returns {AuthReplyPacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate authreply packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {AuthReplyPacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(AuthReplyPacket.prototype, Packet.prototype); - -AuthReplyPacket.prototype.cmd = 'authreply'; -AuthReplyPacket.prototype.type = exports.types.AUTHREPLY; - /** - * Get serialization size. - * @returns {Number} - */ - -AuthReplyPacket.prototype.getSize = function getSize() { - return 64; -}; - -/** - * Serialize authreply packet to writer. - * @param {BufferWriter} bw - */ - -AuthReplyPacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.signature); - return bw; -}; - -/** - * Serialize authreply packet. - * @returns {Buffer} - */ - -AuthReplyPacket.prototype.toRaw = function toRaw() { - return this.signature; -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -AuthReplyPacket.prototype.fromReader = function fromReader(br) { - this.signature = br.readBytes(64); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -AuthReplyPacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate authreply packet from buffer reader. - * @param {BufferReader} br - * @returns {AuthReplyPacket} - */ - -AuthReplyPacket.fromReader = function fromReader(br) { - return new AuthReplyPacket().fromReader(br); -}; - -/** - * Instantiate authreply packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {AuthReplyPacket} - */ - -AuthReplyPacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new AuthReplyPacket().fromRaw(data); -}; - -/** - * Represents a `authpropose` packet. - * @constructor - * @param {Hash?} hash + * AuthPropose Packet + * @extends Packet * @property {Hash} hash */ -function AuthProposePacket(hash) { - if (!(this instanceof AuthProposePacket)) - return new AuthProposePacket(hash); +class AuthProposePacket extends Packet { + /** + * Create a `authpropose` packet. + * @constructor + * @param {Hash?} hash + */ - Packet.call(this); + constructor(hash) { + super(); - this.hash = hash || encoding.ZERO_HASH; + this.cmd = 'authpropose'; + this.type = exports.types.AUTHPROPOSE; + + this.hash = hash || encoding.ZERO_HASH; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return 32; + } + + /** + * Serialize authpropose packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeBytes(this.hash); + return bw; + } + + /** + * Serialize authpropose packet. + * @returns {Buffer} + */ + + toRaw() { + return this.hash; + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + this.hash = br.readHash(); + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(new BufferReader(data)); + } + + /** + * Instantiate authpropose packet from buffer reader. + * @param {BufferReader} br + * @returns {AuthProposePacket} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Instantiate authpropose packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {AuthProposePacket} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } } -Object.setPrototypeOf(AuthProposePacket.prototype, Packet.prototype); - -AuthProposePacket.prototype.cmd = 'authpropose'; -AuthProposePacket.prototype.type = exports.types.AUTHPROPOSE; - /** - * Get serialization size. - * @returns {Number} - */ - -AuthProposePacket.prototype.getSize = function getSize() { - return 32; -}; - -/** - * Serialize authpropose packet to writer. - * @param {BufferWriter} bw - */ - -AuthProposePacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.hash); - return bw; -}; - -/** - * Serialize authpropose packet. - * @returns {Buffer} - */ - -AuthProposePacket.prototype.toRaw = function toRaw() { - return this.hash; -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -AuthProposePacket.prototype.fromReader = function fromReader(br) { - this.hash = br.readHash(); - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -AuthProposePacket.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate authpropose packet from buffer reader. - * @param {BufferReader} br - * @returns {AuthProposePacket} - */ - -AuthProposePacket.fromReader = function fromReader(br) { - return new AuthProposePacket().fromReader(br); -}; - -/** - * Instantiate authpropose packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {AuthProposePacket} - */ - -AuthProposePacket.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new AuthProposePacket().fromRaw(data); -}; - -/** - * Represents an unknown packet. - * @constructor - * @param {String|null} cmd - * @param {Buffer|null} data + * Unknown Packet + * @extends Packet * @property {String} cmd * @property {Buffer} data */ -function UnknownPacket(cmd, data) { - if (!(this instanceof UnknownPacket)) - return new UnknownPacket(cmd, data); +class UnknownPacket extends Packet { + /** + * Create an unknown packet. + * @constructor + * @param {String|null} cmd + * @param {Buffer|null} data + */ - Packet.call(this); + constructor(cmd, data) { + super(); - this.cmd = cmd; - this.data = data; + this.cmd = cmd; + this.type = exports.types.UNKNOWN; + this.data = data; + } + + /** + * Get serialization size. + * @returns {Number} + */ + + getSize() { + return this.data.length; + } + + /** + * Serialize unknown packet to writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeBytes(this.data); + return bw; + } + + /** + * Serialize unknown packet. + * @returns {Buffer} + */ + + toRaw() { + return this.data; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(cmd, data) { + assert(Buffer.isBuffer(data)); + this.cmd = cmd; + this.data = data; + return this; + } + + /** + * Instantiate unknown packet from serialized data. + * @param {Buffer} data + * @param {String?} enc + * @returns {UnknownPacket} + */ + + static fromRaw(cmd, data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(cmd, data); + } } -Object.setPrototypeOf(UnknownPacket.prototype, Packet.prototype); - -UnknownPacket.prototype.type = exports.types.UNKNOWN; - -/** - * Get serialization size. - * @returns {Number} - */ - -UnknownPacket.prototype.getSize = function getSize() { - return this.data.length; -}; - -/** - * Serialize unknown packet to writer. - * @param {BufferWriter} bw - */ - -UnknownPacket.prototype.toWriter = function toWriter(bw) { - bw.writeBytes(this.data); - return bw; -}; - -/** - * Serialize unknown packet. - * @returns {Buffer} - */ - -UnknownPacket.prototype.toRaw = function toRaw() { - return this.data; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -UnknownPacket.prototype.fromRaw = function fromRaw(cmd, data) { - assert(Buffer.isBuffer(data)); - this.cmd = cmd; - this.data = data; - return this; -}; - -/** - * Instantiate unknown packet from serialized data. - * @param {Buffer} data - * @param {String?} enc - * @returns {UnknownPacket} - */ - -UnknownPacket.fromRaw = function fromRaw(cmd, data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new UnknownPacket().fromRaw(cmd, data); -}; - /** * Parse a payload. * @param {String} cmd