From 2e597b975b6a5fc08565b3a570dcf965f014b7d7 Mon Sep 17 00:00:00 2001 From: Oli Evans Date: Wed, 3 Apr 2019 10:12:02 +0100 Subject: [PATCH] feat: preserve base when constructed from a string BREAKING CHANGE: previously base was not preserved and all CIDs would be normalised to base58btc when asking for their string representation. The default will change to base32 in https://github.com/multiformats/js-cid/pull/73/files The idea behind this change is that we shouldnt lose information when the user passes us a base encoded string, but keep it and use it as the default base so toString returns the same string they provided. I'd like this as a fix for ipld explorer, which currently forces all CIDs into base58btc, seee: https://github.com/ipfs-shipyard/ipfs-webui/issues/999 License: MIT Signed-off-by: Oli Evans --- src/index.js | 92 ++++++++++++++++++++++++++++------------------ test/index.spec.js | 39 +++++++++++++------- 2 files changed, 83 insertions(+), 48 deletions(-) diff --git a/src/index.js b/src/index.js index 41e6f64..5ffe32b 100644 --- a/src/index.js +++ b/src/index.js @@ -12,7 +12,6 @@ const withIs = require('class-is') * @param {string} codec * @param {number} version * @param {Buffer} multihash - * */ /** @@ -35,81 +34,106 @@ class CID { * * The algorithm for argument input is roughly: * ``` - * if (str) + * 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 (0 or 1) -> CID + * if (1st byte is 0 or 1) -> CID * else -> multihash * else if (Number) * -> construct CID by parts - * - * ..if only JS had traits.. * ``` * * @param {string|Buffer} version * @param {string} [codec] * @param {Buffer} [multihash] + * @param {string} [multibaseName] * * @example - * - * new CID(, , ) + * new CID(, , , ) * new CID() * new CID() * new CID() * new CID() * new CID() - * */ - constructor (version, codec, multihash) { + constructor (version, codec, multihash, multibaseName = 'base58btc') { if (module.exports.isCID(version)) { - let cid = version + // version is an exising CID instance + const cid = version this.version = cid.version this.codec = cid.codec this.multihash = Buffer.from(cid.multihash) + this.multibaseName = cid.multibaseName return } + if (typeof version === 'string') { - if (multibase.isEncoded(version)) { // CID String (encoded with multibase) + // 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) - version = parseInt(cid.slice(0, 1).toString('hex'), 16) - codec = multicodec.getCodec(cid.slice(1)) - multihash = multicodec.rmPrefix(cid.slice(1)) - } else { // bs58 string encoded multihash - codec = 'dag-pb' - multihash = mh.fromB58String(version) - version = 0 + 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' } - } else if (Buffer.isBuffer(version)) { + CID.validateCID(this) + return + } + + if (Buffer.isBuffer(version)) { const firstByte = version.slice(0, 1) const v = parseInt(firstByte.toString('hex'), 16) - if (v === 0 || v === 1) { // CID + if (v === 0 || v === 1) { + // version is a CID buffer const cid = version - version = v - codec = multicodec.getCodec(cid.slice(1)) - multihash = multicodec.rmPrefix(cid.slice(1)) - } else { // multihash - codec = 'dag-pb' - multihash = version - version = 0 + this.version = v + this.codec = multicodec.getCodec(cid.slice(1)) + this.multihash = multicodec.rmPrefix(cid.slice(1)) + this.multibaseName = (v === 0) ? 'base58btc' : multibaseName + } 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 } - /** - * @type {string} - */ - this.codec = codec + // 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 + CID.validateCID(this) } @@ -193,12 +217,10 @@ class CID { /** * Encode the CID into a string. * - * @param {string} [base='base58btc'] - Base encoding to use. + * @param {string} [base=this.multibaseName] - Base encoding to use. * @returns {string} */ - toBaseEncodedString (base) { - base = base || 'base58btc' - + toBaseEncodedString (base = this.multibaseName) { switch (this.version) { case 0: { if (base !== 'base58btc') { diff --git a/test/index.spec.js b/test/index.spec.js index 19bd4e6..58e47b5 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -167,6 +167,32 @@ describe('CID', () => { }) }) + describe('.toString', () => { + it('returns a CID string', () => { + const cid = new CID(hash) + expect(cid.toString()).to.equal('QmatYkNGZnELf8cAGdyJpUca2PyY4szai3RHyyWofNY1pY') + }) + + it('returns a string in the same base as the string passed to the constructor - base64 flavour', () => { + const base64Str = 'mAXASIOnrbGCADfkPyOI37VMkbzluh1eaukBqqnl2oFaFnuIt' + const cid = new CID(base64Str) + expect(cid.toString()).to.equal(base64Str) + }) + + it('returns a string in the same base as the string passed to the constructor - base16 flavour', () => { + const base16Str = 'f01701220e9eb6c60800df90fc8e237ed53246f396e87579aba406aaa7976a056859ee22d' + const cid = new CID(base16Str) + expect(cid.toString()).to.equal(base16Str) + }) + + it('returns a string in the base provided', () => { + const b58v1Str = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS' + const b32v1Str = 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u' + const cid = new CID(b58v1Str) + expect(cid.toString('base32')).to.equal(b32v1Str) + }) + }) + describe('utilities', () => { const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' const h2 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1o' @@ -207,19 +233,6 @@ describe('CID', () => { CID.isCID(new CID(h1).toV1()) ).to.equal(true) }) - - it('.toString() outputs default base encoded CID', () => { - const mhStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' - const cid = new CID(mhStr) - expect(`${cid}`).to.equal(mhStr) - }) - - it('.toString(base) outputs base encoded CID', () => { - const b58v1Str = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS' - const b32v1Str = 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u' - const cid = new CID(b58v1Str) - expect(cid.toString('base32')).to.equal(b32v1Str) - }) }) describe('throws on invalid inputs', () => {