UNPKG

check-ethereum-scanner

Version:

Safety checks for new Ethereum tokens

309 lines (276 loc) 7.43 kB
'use strict' const { Buffer } = require('buffer') const mh = require('multihashes') const multibase = require('multibase') const multicodec = require('multicodec') const codecs = require('multicodec/src/base-table.json') const CIDUtil = require('./cid-util') const withIs = require('class-is') /** * @typedef {Object} SerializedCID * @param {string} codec * @param {number} version * @param {Buffer} multihash */ /** * Test if the given input is a CID. * @function isCID * @memberof CID * @static * @param {any} other * @returns {bool} */ /** * Class representing a CID `<mbase><version><mcodec><mhash>` * , as defined in [ipld/cid](https://github.com/multiformats/cid). * @class CID */ class CID { /** * Create a new CID. * * The algorithm for argument input is roughly: * ``` * if (cid) * -> create a copy * else if (str) * if (1st char is on multibase table) -> CID String * else -> bs58 encoded multihash * else if (Buffer) * if (1st byte is 0 or 1) -> CID * else -> multihash * else if (Number) * -> construct CID by parts * ``` * * @param {string|Buffer|CID} version * @param {string} [codec] * @param {Buffer} [multihash] * @param {string} [multibaseName] * * @example * new CID(<version>, <codec>, <multihash>, <multibaseName>) * new CID(<cidStr>) * new CID(<cid.buffer>) * new CID(<multihash>) * new CID(<bs58 encoded multihash>) * new CID(<cid>) */ constructor (version, codec, multihash, multibaseName) { if (_CID.isCID(version)) { // version is an exising CID instance const cid = version this.version = cid.version this.codec = cid.codec this.multihash = Buffer.from(cid.multihash) // Default guard for when a CID < 0.7 is passed with no multibaseName this.multibaseName = cid.multibaseName || (cid.version === 0 ? 'base58btc' : 'base32') return } if (typeof version === 'string') { // e.g. 'base32' or false const baseName = multibase.isEncoded(version) if (baseName) { // version is a CID String encoded with multibase, so v1 const cid = multibase.decode(version) this.version = parseInt(cid.slice(0, 1).toString('hex'), 16) this.codec = multicodec.getCodec(cid.slice(1)) this.multihash = multicodec.rmPrefix(cid.slice(1)) this.multibaseName = baseName } else { // version is a base58btc string multihash, so v0 this.version = 0 this.codec = 'dag-pb' this.multihash = mh.fromB58String(version) this.multibaseName = 'base58btc' } CID.validateCID(this) Object.defineProperty(this, 'string', { value: version }) return } if (Buffer.isBuffer(version)) { const firstByte = version.slice(0, 1) const v = parseInt(firstByte.toString('hex'), 16) if (v === 1) { // version is a CID buffer const cid = version this.version = v this.codec = multicodec.getCodec(cid.slice(1)) this.multihash = multicodec.rmPrefix(cid.slice(1)) this.multibaseName = 'base32' } else { // version is a raw multihash buffer, so v0 this.version = 0 this.codec = 'dag-pb' this.multihash = version this.multibaseName = 'base58btc' } CID.validateCID(this) return } // otherwise, assemble the CID from the parameters /** * @type {number} */ this.version = version /** * @type {string} */ this.codec = codec /** * @type {Buffer} */ this.multihash = multihash /** * @type {string} */ this.multibaseName = multibaseName || (version === 0 ? 'base58btc' : 'base32') CID.validateCID(this) } /** * The CID as a `Buffer` * * @return {Buffer} * @readonly * * @memberOf CID */ get buffer () { let buffer = this._buffer if (!buffer) { if (this.version === 0) { buffer = this.multihash } else if (this.version === 1) { buffer = Buffer.concat([ Buffer.from('01', 'hex'), multicodec.getCodeVarint(this.codec), this.multihash ]) } else { throw new Error('unsupported version') } // Cache this buffer so it doesn't have to be recreated Object.defineProperty(this, '_buffer', { value: buffer }) } return buffer } /** * Get the prefix of the CID. * * @returns {Buffer} * @readonly */ get prefix () { return Buffer.concat([ Buffer.from(`0${this.version}`, 'hex'), multicodec.getCodeVarint(this.codec), mh.prefix(this.multihash) ]) } /** * Convert to a CID of version `0`. * * @returns {CID} */ toV0 () { if (this.codec !== 'dag-pb') { throw new Error('Cannot convert a non dag-pb CID to CIDv0') } const { name, length } = mh.decode(this.multihash) if (name !== 'sha2-256') { throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0') } if (length !== 32) { throw new Error('Cannot convert non 32 byte multihash CID to CIDv0') } return new _CID(0, this.codec, this.multihash) } /** * Convert to a CID of version `1`. * * @returns {CID} */ toV1 () { return new _CID(1, this.codec, this.multihash) } /** * Encode the CID into a string. * * @param {string} [base=this.multibaseName] - Base encoding to use. * @returns {string} */ toBaseEncodedString (base = this.multibaseName) { if (this.string && base === this.multibaseName) { return this.string } let str = null if (this.version === 0) { if (base !== 'base58btc') { throw new Error('not supported with CIDv0, to support different bases, please migrate the instance do CIDv1, you can do that through cid.toV1()') } str = mh.toB58String(this.multihash) } else if (this.version === 1) { str = multibase.encode(base, this.buffer).toString() } else { throw new Error('unsupported version') } if (base === this.multibaseName) { // cache the string value Object.defineProperty(this, 'string', { value: str }) } return str } /** * CID(QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n) * * @returns {String} */ [Symbol.for('nodejs.util.inspect.custom')] () { return 'CID(' + this.toString() + ')' } toString (base) { return this.toBaseEncodedString(base) } /** * Serialize to a plain object. * * @returns {SerializedCID} */ toJSON () { return { codec: this.codec, version: this.version, hash: this.multihash } } /** * Compare equality with another CID. * * @param {CID} other * @returns {bool} */ equals (other) { return this.codec === other.codec && this.version === other.version && this.multihash.equals(other.multihash) } /** * Test if the given input is a valid CID object. * Throws if it is not. * * @param {any} other * @returns {void} */ static validateCID (other) { const errorMsg = CIDUtil.checkCIDComponents(other) if (errorMsg) { throw new Error(errorMsg) } } } const _CID = withIs(CID, { className: 'CID', symbolName: '@ipld/js-cid/CID' }) _CID.codecs = codecs module.exports = _CID