UNPKG

hsd

Version:
567 lines (442 loc) 11.1 kB
/*! * address.js - address object for hsd * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). * https://github.com/handshake-org/hsd */ 'use strict'; const assert = require('bsert'); const bio = require('bufio'); const bech32 = require('bcrypto/lib/encoding/bech32'); const blake2b = require('bcrypto/lib/blake2b'); const sha3 = require('bcrypto/lib/sha3'); const Network = require('../protocol/network'); const consensus = require('../protocol/consensus'); /** @typedef {import('../types').Hash} Hash */ /** @typedef {import('../types').NetworkType} NetworkType */ /** @typedef {import('../types').BufioWriter} BufioWriter */ /** @typedef {import('../script/script')} Script */ /** @typedef {import('../script/witness')} Witness */ /* * Constants */ const ZERO_HASH160 = Buffer.alloc(20, 0x00); /** * @typedef {Object} AddressOptions * @property {Hash} hash * @property {Number} version */ /** * Address * Represents an address. * @alias module:primitives.Address * @property {Number} version * @property {Buffer} hash */ class Address extends bio.Struct { /** * Create an address. * @constructor * @param {AddressOptions|String} [options] * @param {(NetworkType|Network)?} [network] */ constructor(options, network) { super(); this.version = 0; this.hash = ZERO_HASH160; if (options) this.fromOptions(options, network); } /** * Inject properties from options object. * @param {AddressOptions|String} options * @param {(NetworkType|Network)?} [network] */ fromOptions(options, network) { if (typeof options === 'string') return this.fromString(options, network); assert(options); const {hash, version} = options; return this.fromHash(hash, version); } /** * Count the sigops in a script, taking into account witness programs. * @param {Witness} witness * @returns {Number} sigop count */ getSigops(witness) { if (this.version === 0) { if (this.hash.length === 20) return 1; if (this.hash.length === 32 && witness.items.length > 0) { const redeem = witness.getRedeem(); return redeem.getSigops(); } } return 0; } /** * Get the address hash. * @returns {Hash} */ getHash() { return this.hash; } /** * Test whether the address is null. * @returns {Boolean} */ isNull() { if (this.hash.length === 20) return this.hash.equals(ZERO_HASH160); if (this.hash.length === 32) return this.hash.equals(consensus.ZERO_HASH); for (let i = 0; i < this.hash.length; i++) { if (this.hash[i] !== 0) return false; } return true; } /** * Test whether the address is unspendable. * @returns {Boolean} */ isUnspendable() { return this.isNulldata(); } /** * Test equality against another address. * @param {Address} addr * @returns {Boolean} */ equals(addr) { assert(addr instanceof Address); return this.version === addr.version && this.hash.equals(addr.hash); } /** * Compare against another address. * @param {Address} addr * @returns {Number} */ compare(addr) { assert(addr instanceof Address); const cmp = this.version - addr.version; if (cmp !== 0) return cmp; return this.hash.compare(addr.hash); } /** * Inject properties from another address. * @param {Address} addr * @returns {this} */ inject(addr) { this.version = addr.version; this.hash = addr.hash; return this; } /** * Clone address. * @returns {this} */ clone() { // @ts-ignore return new this.constructor().inject(this); } /** * Compile the address object to a bech32 address. * @param {(NetworkType|Network)?} [network] * @returns {String} * @throws Error on bad hash/prefix. */ toString(network) { const version = this.version; const hash = this.hash; assert(version <= 31); assert(hash.length >= 2 && hash.length <= 40); network = Network.get(network); const hrp = network.addressPrefix; return bech32.encode(hrp, version, hash); } /** * Instantiate address from pubkey. * @param {Buffer} key * @returns {Address} */ fromPubkey(key) { assert(Buffer.isBuffer(key) && key.length === 33); return this.fromHash(blake2b.digest(key, 20), 0); } /** * Instantiate address from script. * @param {Script} script * @returns {Address} */ fromScript(script) { assert(script && typeof script.encode === 'function'); return this.fromHash(sha3.digest(script.encode()), 0); } /** * Inject properties from bech32 address. * @param {String} data * @param {(NetworkType|Network)?} [network] * @throws Parse error */ fromString(data, network) { assert(typeof data === 'string'); const [hrp, version, hash] = bech32.decode(data); Network.fromAddress(hrp, network); return this.fromHash(hash, version); } /** * Inject properties from witness. * @param {Witness} witness * @returns {Address|null} */ fromWitness(witness) { const [, pk] = witness.getPubkeyhashInput(); if (pk) { this.hash = blake2b.digest(pk, 20); this.version = 0; return this; } const redeem = witness.getScripthashInput(); if (redeem) { this.hash = sha3.digest(redeem); this.version = 0; return this; } return null; } /** * Inject properties from a hash. * @param {Hash} hash * @param {Number} [version=0] * @throws on bad hash size */ fromHash(hash, version) { if (version == null) version = 0; assert(Buffer.isBuffer(hash)); assert((version & 0xff) === version); assert(version >= 0 && version <= 31, 'Bad program version.'); assert(hash.length >= 2 && hash.length <= 40, 'Hash is the wrong size.'); if (version === 0) { assert(hash.length === 20 || hash.length === 32, 'Witness program hash is the wrong size.'); } this.hash = hash; this.version = version; return this; } /** * Inject properties from witness pubkeyhash. * @param {Buffer} hash * @returns {Address} */ fromPubkeyhash(hash) { assert(hash && hash.length === 20, 'P2WPKH must be 20 bytes.'); return this.fromHash(hash, 0); } /** * Inject properties from witness scripthash. * @param {Buffer} hash * @returns {Address} */ fromScripthash(hash) { assert(hash && hash.length === 32, 'P2WSH must be 32 bytes.'); return this.fromHash(hash, 0); } /** * Inject properties from witness program. * @param {Number} version * @param {Buffer} hash * @returns {Address} */ fromProgram(version, hash) { assert(version >= 0, 'Bad version for witness program.'); return this.fromHash(hash, version); } /** * Instantiate address from nulldata. * @param {Buffer} data * @returns {Address} */ fromNulldata(data) { return this.fromHash(data, 31); } /** * Test whether the address is witness pubkeyhash. * @returns {Boolean} */ isPubkeyhash() { return this.version === 0 && this.hash.length === 20; } /** * Test whether the address is witness scripthash. * @returns {Boolean} */ isScripthash() { return this.version === 0 && this.hash.length === 32; } /** * Test whether the address is unspendable. * @returns {Boolean} */ isNulldata() { return this.version === 31; } /** * Test whether the address is an unknown witness program. * @returns {Boolean} */ isUnknown() { switch (this.version) { case 0: return this.hash.length !== 20 && this.hash.length !== 32; case 31: return false; } return true; } /** * Test address validity. * @returns {Boolean} */ isValid() { assert(this.version >= 0); if (this.version > 31) return false; if (this.hash.length < 2 || this.hash.length > 40) return false; return true; } /** * Calculate address size. * @returns {Number} */ getSize() { return 1 + 1 + this.hash.length; } /** * Write address to buffer writer. * @param {BufioWriter} bw * @returns {BufioWriter} */ write(bw) { bw.writeU8(this.version); bw.writeU8(this.hash.length); bw.writeBytes(this.hash); return bw; } /** * Read address from buffer reader. * @param {bio.BufferReader} br * @returns {this} */ read(br) { const version = br.readU8(); assert(version <= 31); const size = br.readU8(); assert(size >= 2 && size <= 40); const hash = br.readBytes(size); return this.fromHash(hash, version); } /** * Inspect the Address. * @returns {String} */ format() { return '<Address:' + ` version=${this.version}` + ` str=${this.toString()}` + '>'; } /** * Instantiate address from pubkey. * @param {Buffer} key * @returns {Address} */ static fromPubkey(key) { return new this().fromPubkey(key); } /** * Instantiate address from script. * @param {Script} script * @returns {Address} */ static fromScript(script) { return new this().fromScript(script); } /** * Create an Address from a witness. * Attempt to extract address * properties from a witness. * @param {Witness} witness * @returns {Address|null} */ static fromWitness(witness) { return new this().fromWitness(witness); } /** * Create a naked address from hash/version. * @param {Hash} hash * @param {Number} [version=0] * @returns {Address} * @throws on bad hash size */ static fromHash(hash, version) { return new this().fromHash(hash, version); } /** * Instantiate address from witness pubkeyhash. * @param {Buffer} hash * @returns {Address} */ static fromPubkeyhash(hash) { return new this().fromPubkeyhash(hash); } /** * Instantiate address from witness scripthash. * @param {Buffer} hash * @returns {Address} */ static fromScripthash(hash) { return new this().fromScripthash(hash); } /** * Instantiate address from witness program. * @param {Number} version * @param {Buffer} hash * @returns {Address} */ static fromProgram(version, hash) { return new this().fromProgram(version, hash); } /** * Instantiate address from nulldata. * @param {Buffer} data * @returns {Address} */ static fromNulldata(data) { return new this().fromNulldata(data); } /** * Get the hash of a base58 address or address-related object. * @param {Address|Hash} data * @returns {Hash} */ static getHash(data) { if (!data) throw new Error('Object is not an address.'); if (Buffer.isBuffer(data)) return data; if (data instanceof Address) return data.hash; throw new Error('Object is not an address.'); } } /* * Expose */ module.exports = Address;