diff --git a/.travis.yml b/.travis.yml index 3df7565..c7f74c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,6 @@ jobs: include: - stage: check script: - - npx aegir commitlint --travis - npx aegir dep-check - npm run lint diff --git a/README.md b/README.md index 26aeb80..0e61779 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,8 @@ js-multibase ## Table of Contents - [Install](#install) - - [In Node.js through npm](#in-nodejs-through-npm) - - [Browser: Browserify, Webpack, other bundlers](#browser-browserify-webpack-other-bundlers) + - [NPM](#npm) - [In the Browser through ` - - ``` ## Usage @@ -86,111 +73,54 @@ console.log(decodedBuf.toString()) ## API https://multiformats.github.io/js-multibase/ -### `multibase` - Prefixes an encoded buffer with its multibase code +#### `multibase` - Prefixes an encoded buffer with its multibase code ``` const multibased = multibase(, encodedBuf) ``` -### `multibase.encode` - Encodes a buffer into one of the supported encodings, prefixing it with the multibase code +#### `multibase.encode` - Encodes a buffer into one of the supported encodings, prefixing it with the multibase code ```JavaScript const encodedBuf = multibase.encode(, ) ``` -### `multibase.decode` - Decodes a buffer or string +#### `multibase.decode` - Decodes a buffer or string ```JavaScript const decodedBuf = multibase.decode(bufOrString) ``` -### `multibase.isEncoded` - Checks if buffer or string is encoded +#### `multibase.isEncoded` - Checks if buffer or string is encoded ```JavaScript const value = multibase.isEncoded(bufOrString) // value is the name of the encoding if it is encoded, false otherwise ``` -### `multibase.names` - -A frozen `Array` of supported base encoding names. - -### `multibase.codes` - -A frozen `Array` of supported base encoding codes. +#### `multibase.encoding` - Get the encoding by name or code -### Supported Encodings, see [`src/constants.js`](/src/constants.js) - -## Architecture and Encoding/Decoding - -Multibase package defines all the supported bases and the location of their implementation in the constants.js file. A base is a class with a name, a code, an implementation and an alphabet. -```js -class Base { - constructor (name, code, implementation, alphabet) { - //... - } - // ... -} +```JavaScript +const value = multibase.encoding(nameOrCode) +// value is an instance of the corresponding `Base` ``` -The ```implementation``` is an object where the encoding/decoding functions are implemented. It must take one argument, (the alphabet) following the [base-x module](https://github.com/cryptocoinjs/base-x) architecture. - -The ```alphabet``` is the **ordered** set of defined symbols for a given base. -The idea behind this is that several bases may have implementations from different locations/modules so it's useful to have an object (and a summary) of all of them in one location (hence the constants.js). +#### `multibase.encodingFromData` - Get the encoding from data either a `string` or `Buffer` -All the supported bases are currently using the npm [base-x](https://github.com/cryptocoinjs/base-x) module as their implementation. It is using bitwise maipulation to go from one base to another, so this module does not support padding at the moment. +```JavaScript +const value = multibase.encodingFromData(data) +// value is an instance of the corresponding `Base` +``` -## Adding additional bases +#### `multibase.names` -If the base you are looking for is not supported yet in js-multibase and you know a good encoding/decoding algorithm, you can add support for this base easily by editing the constants.js file -(**you'll need to create an issue about that beforehand since a code and a canonical name have to be defined**): +A frozen `Object` of supported base encoding names mapped to the corresponding `Base` instance. -```js -const baseX = require('base-x') -//const newPackage = require('your-package-name') +#### `multibase.codes` -const constants = [ - ['base2', '0', baseX, '01'], - ['base8', '7', baseX, '01234567'], - // ... [ 'your-base-name', 'code-to-be-defined', newPackage, 'alphabet'] -] -``` -The required package defines the implementation of the encoding/decoding process. **It must comply by these rules** : -- `encode` and `decode` functions with to-be-encoded buffer as the only expected argument -- the require call use the `alphabet` given as an argument for the encoding/decoding process - -*If no package is specified , it means the base is not implemented yet* - -Adding a new base requires the tests to be updated. Test files to be updated are : -- constants.spec.js -```js -describe('constants', () => { - it('constants indexed by name', () => { - const names = constants.names - expect(Object.keys(names).length).to.equal(constants-count) // currently 12 - }) - - it('constants indexed by code', () => { - const codes = constants.codes - expect(Object.keys(codes).length).to.equal(constants-count) - }) -}) -``` +A frozen `Object` of supported base encoding codes mapped to the corresponding `Base` instance. -- multibase.spec.js - - if the base is implemented - ```js - const supportedBases = [ - ['base2', 'yes mani !', '01111001011001010111001100100000011011010110000101101110011010010010000000100001'], - ['base8', 'yes mani !', '7171312714403326055632220041'], - ['base10', 'yes mani !', '9573277761329450583662625'], - // ... ['your-base-name', 'what you want', 'expected output'] - ``` - - if the base is not implemented yet - ```js - const supportedBases = [ - // ... ['your-base-name'] - ``` +### Supported Encodings, see [`src/constants.js`](/src/constants.js) ## Contribute diff --git a/benchmark.js b/benchmark.js new file mode 100644 index 0000000..9ef09e5 --- /dev/null +++ b/benchmark.js @@ -0,0 +1,23 @@ +/* eslint-disable no-console */ +'use strict' +const Benchmark = require('benchmark') +const multibase = require('./src') + +const names = Object.keys(multibase.names) +const suite = new Benchmark.Suite() +const input = 'Decentralize everything!!' + +for (const enc of names) { + suite.add(enc, () => { + multibase.encode(enc, Buffer.from(input)) + }) +} + +suite + .on('cycle', function (event) { + console.log(String(event.target)) + }) + .on('complete', function () { + console.log('Fastest is ' + this.filter('fastest').map('name')) + }) + .run({ async: true }) diff --git a/package.json b/package.json index 4a92602..821768c 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ }, "devDependencies": { "aegir": "^22.0.0", + "benchmark": "^2.1.4", "chai": "^4.1.2", "dirty-chai": "^2.0.1", "pre-commit": "^1.2.2" diff --git a/src/base.js b/src/base.js index 1a66059..3b2235a 100644 --- a/src/base.js +++ b/src/base.js @@ -4,22 +4,22 @@ class Base { constructor (name, code, implementation, alphabet) { this.name = name this.code = code + this.codeBuf = Buffer.from(this.code) this.alphabet = alphabet - if (implementation && alphabet) { - this.engine = implementation(alphabet) - } - } - - encode (stringOrBuffer) { - return this.engine.encode(stringOrBuffer) + this.engine = implementation(alphabet) } - decode (stringOrBuffer) { - return this.engine.decode(stringOrBuffer) + encode (buf) { + return this.engine.encode(buf) } - isImplemented () { - return this.engine + decode (string) { + for (const char of string) { + if (this.alphabet && this.alphabet.indexOf(char) < 0) { + throw new Error(`invalid character '${char}' in '${string}'`) + } + } + return this.engine.decode(string) } } diff --git a/src/base16.js b/src/base16.js deleted file mode 100644 index f911f80..0000000 --- a/src/base16.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' -const { Buffer } = require('buffer') - -module.exports = function base16 (alphabet) { - return { - encode (input) { - if (typeof input === 'string') { - return Buffer.from(input).toString('hex') - } - return input.toString('hex') - }, - decode (input) { - for (const char of input) { - if (alphabet.indexOf(char) < 0) { - throw new Error('invalid base16 character') - } - } - return Buffer.from(input, 'hex') - } - } -} diff --git a/src/base32.js b/src/base32.js deleted file mode 100644 index cc38046..0000000 --- a/src/base32.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict' - -function decode (input, alphabet) { - input = input.replace(new RegExp('=', 'g'), '') - const length = input.length - - let bits = 0 - let value = 0 - - let index = 0 - const output = new Uint8Array((length * 5 / 8) | 0) - - for (let i = 0; i < length; i++) { - value = (value << 5) | alphabet.indexOf(input[i]) - bits += 5 - - if (bits >= 8) { - output[index++] = (value >>> (bits - 8)) & 255 - bits -= 8 - } - } - - return output.buffer -} - -function encode (buffer, alphabet) { - const length = buffer.byteLength - const view = new Uint8Array(buffer) - const padding = alphabet.indexOf('=') === alphabet.length - 1 - - if (padding) { - alphabet = alphabet.substring(0, alphabet.length - 1) - } - - let bits = 0 - let value = 0 - let output = '' - - for (let i = 0; i < length; i++) { - value = (value << 8) | view[i] - bits += 8 - - while (bits >= 5) { - output += alphabet[(value >>> (bits - 5)) & 31] - bits -= 5 - } - } - - if (bits > 0) { - output += alphabet[(value << (5 - bits)) & 31] - } - - if (padding) { - while ((output.length % 8) !== 0) { - output += '=' - } - } - - return output -} - -module.exports = function base32 (alphabet) { - return { - encode (input) { - if (typeof input === 'string') { - return encode(Uint8Array.from(input), alphabet) - } - - return encode(input, alphabet) - }, - decode (input) { - for (const char of input) { - if (alphabet.indexOf(char) < 0) { - throw new Error('invalid base32 character') - } - } - - return decode(input, alphabet) - } - } -} diff --git a/src/base64.js b/src/base64.js deleted file mode 100644 index c4608bb..0000000 --- a/src/base64.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict' -const { Buffer } = require('buffer') - -module.exports = function base64 (alphabet) { - // The alphabet is only used to know: - // 1. If padding is enabled (must contain '=') - // 2. If the output must be url-safe (must contain '-' and '_') - // 3. If the input of the output function is valid - // The alphabets from RFC 4648 are always used. - const padding = alphabet.indexOf('=') > -1 - const url = alphabet.indexOf('-') > -1 && alphabet.indexOf('_') > -1 - - return { - encode (input) { - let output = '' - - if (typeof input === 'string') { - output = Buffer.from(input).toString('base64') - } else { - output = input.toString('base64') - } - - if (url) { - output = output.replace(/\+/g, '-').replace(/\//g, '_') - } - - const pad = output.indexOf('=') - if (pad > 0 && !padding) { - output = output.substring(0, pad) - } - - return output - }, - decode (input) { - for (const char of input) { - if (alphabet.indexOf(char) < 0) { - throw new Error('invalid base64 character') - } - } - - return Buffer.from(input, 'base64') - } - } -} diff --git a/src/constants.js b/src/constants.js index 1b6bd37..deb3b4c 100644 --- a/src/constants.js +++ b/src/constants.js @@ -2,28 +2,40 @@ const Base = require('./base.js') const baseX = require('base-x') -const base16 = require('./base16') -const base32 = require('./base32') -const base64 = require('./base64') +const rfc4648 = require('./rfc4648') + +const identity = () => { + return { + encode: (data) => Buffer.from(data).toString(), + decode: (string) => Buffer.from(string) + } +} // name, code, implementation, alphabet const constants = [ - ['base1', '1', '', '1'], - ['base2', '0', baseX, '01'], - ['base8', '7', baseX, '01234567'], + ['identity', '\x00', identity, ''], + ['base2', '0', rfc4648(1), '01'], + ['base8', '7', rfc4648(3), '01234567'], ['base10', '9', baseX, '0123456789'], - ['base16', 'f', base16, '0123456789abcdef'], - ['base32', 'b', base32, 'abcdefghijklmnopqrstuvwxyz234567'], - ['base32pad', 'c', base32, 'abcdefghijklmnopqrstuvwxyz234567='], - ['base32hex', 'v', base32, '0123456789abcdefghijklmnopqrstuv'], - ['base32hexpad', 't', base32, '0123456789abcdefghijklmnopqrstuv='], - ['base32z', 'h', base32, 'ybndrfg8ejkmcpqxot1uwisza345h769'], - ['base58flickr', 'Z', baseX, '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'], + ['base16', 'f', rfc4648(4), '0123456789abcdef'], + ['base16upper', 'F', rfc4648(4), '0123456789ABCDEF'], + ['base32hex', 'v', rfc4648(5), '0123456789abcdefghijklmnopqrstuv'], + ['base32hexupper', 'V', rfc4648(5), '0123456789ABCDEFGHIJKLMNOPQRSTUV'], + ['base32hexpad', 't', rfc4648(5), '0123456789abcdefghijklmnopqrstuv='], + ['base32hexpadupper', 'T', rfc4648(5), '0123456789ABCDEFGHIJKLMNOPQRSTUV='], + ['base32', 'b', rfc4648(5), 'abcdefghijklmnopqrstuvwxyz234567'], + ['base32upper', 'B', rfc4648(5), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'], + ['base32pad', 'c', rfc4648(5), 'abcdefghijklmnopqrstuvwxyz234567='], + ['base32padupper', 'C', rfc4648(5), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567='], + ['base32z', 'h', rfc4648(5), 'ybndrfg8ejkmcpqxot1uwisza345h769'], + ['base36', 'k', baseX, '0123456789abcdefghijklmnopqrstuvwxyz'], + ['base36upper', 'K', baseX, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'], ['base58btc', 'z', baseX, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'], - ['base64', 'm', base64, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'], - ['base64pad', 'M', base64, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='], - ['base64url', 'u', base64, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'], - ['base64urlpad', 'U', base64, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_='] + ['base58flickr', 'Z', baseX, '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'], + ['base64', 'm', rfc4648(6), 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'], + ['base64pad', 'M', rfc4648(6), 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='], + ['base64url', 'u', rfc4648(6), 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'], + ['base64urlpad', 'U', rfc4648(6), 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_='] ] const names = constants.reduce((prev, tupple) => { @@ -37,6 +49,6 @@ const codes = constants.reduce((prev, tupple) => { }, {}) module.exports = { - names: names, - codes: codes + names, + codes } diff --git a/src/index.js b/src/index.js index 32760ea..a9915c9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ /** * Implementation of the [multibase](https://github.com/multiformats/multibase) specification. + * * @module Multibase */ 'use strict' @@ -7,31 +8,23 @@ const { Buffer } = require('buffer') const constants = require('./constants') -exports = module.exports = multibase -exports.encode = encode -exports.decode = decode -exports.isEncoded = isEncoded -exports.names = Object.freeze(Object.keys(constants.names)) -exports.codes = Object.freeze(Object.keys(constants.codes)) +/** @typedef {import("./base")} Base */ /** * Create a new buffer with the multibase varint+code. * * @param {string|number} nameOrCode - The multibase name or code number. * @param {Buffer} buf - The data to be prefixed with multibase. - * @memberof Multibase * @returns {Buffer} + * @throws {Error} Will throw if the encoding is not supported */ function multibase (nameOrCode, buf) { if (!buf) { throw new Error('requires an encoded buffer') } - const base = getBase(nameOrCode) - const codeBuf = Buffer.from(base.code) - - const name = base.name - validEncode(name, buf) - return Buffer.concat([codeBuf, buf]) + const enc = encoding(nameOrCode) + validEncode(enc.name, buf) + return Buffer.concat([enc.codeBuf, buf]) } /** @@ -40,91 +33,112 @@ function multibase (nameOrCode, buf) { * @param {string|number} nameOrCode - The multibase name or code number. * @param {Buffer} buf - The data to be encoded. * @returns {Buffer} - * @memberof Multibase + * @throws {Error} Will throw if the encoding is not supported + * */ function encode (nameOrCode, buf) { - const base = getBase(nameOrCode) - const name = base.name + const enc = encoding(nameOrCode) - return multibase(name, Buffer.from(base.encode(buf))) + return Buffer.concat([enc.codeBuf, Buffer.from(enc.encode(buf))]) } /** * Takes a buffer or string encoded with multibase header, decodes it and * returns the decoded buffer * - * @param {Buffer|string} bufOrString + * @param {Buffer|string} data * @returns {Buffer} - * @memberof Multibase + * @throws {Error} Will throw if the encoding is not supported * */ -function decode (bufOrString) { - if (Buffer.isBuffer(bufOrString)) { - bufOrString = bufOrString.toString() +function decode (data) { + if (Buffer.isBuffer(data)) { + data = data.toString() } + const prefix = data[0] - const code = bufOrString.substring(0, 1) - bufOrString = bufOrString.substring(1, bufOrString.length) - - if (typeof bufOrString === 'string') { - bufOrString = Buffer.from(bufOrString) + // Make all encodings case-insensitive except the ones that include upper and lower chars in the alphabet + if (['f', 'F', 'v', 'V', 't', 'T', 'b', 'B', 'c', 'C', 'h', 'k', 'K'].includes(prefix)) { + data = data.toLowerCase() } - - const base = getBase(code) - return Buffer.from(base.decode(bufOrString.toString())) + const enc = encoding(data[0]) + return Buffer.from(enc.decode(data.substring(1))) } /** * Is the given data multibase encoded? * - * @param {Buffer|string} bufOrString + * @param {Buffer|string} data * @returns {boolean} - * @memberof Multibase */ -function isEncoded (bufOrString) { - if (Buffer.isBuffer(bufOrString)) { - bufOrString = bufOrString.toString() +function isEncoded (data) { + if (Buffer.isBuffer(data)) { + data = data.toString() } // Ensure bufOrString is a string - if (Object.prototype.toString.call(bufOrString) !== '[object String]') { + if (Object.prototype.toString.call(data) !== '[object String]') { return false } - const code = bufOrString.substring(0, 1) try { - const base = getBase(code) - return base.name + const enc = encoding(data[0]) + return enc.name } catch (err) { return false } } /** + * Validate encoded data + * * @param {string} name * @param {Buffer} buf - * @private * @returns {undefined} + * @throws {Error} Will throw if the encoding is not supported */ function validEncode (name, buf) { - const base = getBase(name) - base.decode(buf.toString()) + const enc = encoding(name) + enc.decode(buf.toString()) } -function getBase (nameOrCode) { - let base - +/** + * Get the encoding by name or code + * + * @param {string} nameOrCode + * @returns {Base} + * @throws {Error} Will throw if the encoding is not supported + */ +function encoding (nameOrCode) { if (constants.names[nameOrCode]) { - base = constants.names[nameOrCode] + return constants.names[nameOrCode] } else if (constants.codes[nameOrCode]) { - base = constants.codes[nameOrCode] + return constants.codes[nameOrCode] } else { - throw new Error('Unsupported encoding') + throw new Error(`Unsupported encoding: ${nameOrCode}`) } +} - if (!base.isImplemented()) { - throw new Error('Base ' + nameOrCode + ' is not implemented yet') +/** + * Get encoding from data + * + * @param {string|Buffer} data + * @returns {Base} + * @throws {Error} Will throw if the encoding is not supported + */ +function encodingFromData (data) { + if (Buffer.isBuffer(data)) { + data = data.toString() } - return base + return encoding(data[0]) } + +exports = module.exports = multibase +exports.encode = encode +exports.decode = decode +exports.isEncoded = isEncoded +exports.encoding = encoding +exports.encodingFromData = encodingFromData +exports.names = Object.freeze(constants.names) +exports.codes = Object.freeze(constants.codes) diff --git a/src/rfc4648.js b/src/rfc4648.js new file mode 100644 index 0000000..b020a5a --- /dev/null +++ b/src/rfc4648.js @@ -0,0 +1,92 @@ +'use strict' + +const decode = (string, alphabet, bitsPerChar) => { + // Build the character lookup table: + const codes = {} + for (let i = 0; i < alphabet.length; ++i) { + codes[alphabet[i]] = i + } + + // Count the padding bytes: + let end = string.length + while (string[end - 1] === '=') { + --end + } + + // Allocate the output: + const out = new Uint8Array((end * bitsPerChar / 8) | 0) + + // Parse the data: + let bits = 0 // Number of bits currently in the buffer + let buffer = 0 // Bits waiting to be written out, MSB first + let written = 0 // Next byte to write + for (let i = 0; i < end; ++i) { + // Read one character from the string: + const value = codes[string[i]] + if (value === undefined) { + throw new SyntaxError('Invalid character ' + string[i]) + } + + // Append the bits to the buffer: + buffer = (buffer << bitsPerChar) | value + bits += bitsPerChar + + // Write out some bits if the buffer has a byte's worth: + if (bits >= 8) { + bits -= 8 + out[written++] = 0xff & (buffer >> bits) + } + } + + // Verify that we have received just enough bits: + if (bits >= bitsPerChar || 0xff & (buffer << (8 - bits))) { + throw new SyntaxError('Unexpected end of data') + } + + return out +} + +const encode = (data, alphabet, bitsPerChar) => { + const pad = alphabet[alphabet.length - 1] === '=' + const mask = (1 << bitsPerChar) - 1 + let out = '' + + let bits = 0 // Number of bits currently in the buffer + let buffer = 0 // Bits waiting to be written out, MSB first + for (let i = 0; i < data.length; ++i) { + // Slurp data into the buffer: + buffer = (buffer << 8) | data[i] + bits += 8 + + // Write out as much as we can: + while (bits > bitsPerChar) { + bits -= bitsPerChar + out += alphabet[mask & (buffer >> bits)] + } + } + + // Partial character: + if (bits) { + out += alphabet[mask & (buffer << (bitsPerChar - bits))] + } + + // Add padding characters until we hit a byte boundary: + if (pad) { + while ((out.length * bitsPerChar) & 7) { + out += '=' + } + } + + return out +} + +module.exports = (bitsPerChar) => (alphabet) => { + return { + encode (input) { + return encode(input, alphabet, bitsPerChar) + }, + decode (input) { + return decode(input, alphabet, bitsPerChar) + } + } +} diff --git a/test/constants.spec.js b/test/constants.spec.js index 302e95c..787d410 100644 --- a/test/constants.spec.js +++ b/test/constants.spec.js @@ -10,11 +10,11 @@ const constants = require('../src/constants.js') describe('constants', () => { it('constants indexed by name', () => { const names = constants.names - expect(Object.keys(names).length).to.equal(16) + expect(Object.keys(names).length).to.equal(23) }) it('constants indexed by code', () => { const codes = constants.codes - expect(Object.keys(codes).length).to.equal(16) + expect(Object.keys(codes).length).to.equal(23) }) }) diff --git a/test/multibase.spec.js b/test/multibase.spec.js index bbd3ef0..c40228b 100644 --- a/test/multibase.spec.js +++ b/test/multibase.spec.js @@ -11,10 +11,6 @@ const constants = require('../src/constants.js') const unsupportedBases = [] const supportedBases = [ - ['base2', 'yes mani !', '01111001011001010111001100100000011011010110000101101110011010010010000000100001'], - ['base8', 'yes mani !', '7171312714403326055632220041'], - ['base10', 'yes mani !', '9573277761329450583662625'], - ['base16', 'yes mani !', 'f796573206d616e692021'], ['base16', Buffer.from([0x01]), 'f01'], ['base16', Buffer.from([15]), 'f0f'], @@ -219,7 +215,7 @@ for (const elements of unsupportedBases) { describe('multibase.names', () => { it('includes all base names', () => { Object.keys(constants.names).forEach(name => { - expect(multibase.names).to.include(name) + expect(Object.keys(multibase.names)).to.include(name) }) }) @@ -231,7 +227,7 @@ describe('multibase.names', () => { describe('multibase.codes', () => { it('includes all base codes', () => { Object.keys(constants.codes).forEach(code => { - expect(multibase.codes).to.include(code) + expect(Object.keys(multibase.codes)).to.include(code) }) }) diff --git a/test/spec-test1.spec.js b/test/spec-test1.spec.js new file mode 100644 index 0000000..4b40c81 --- /dev/null +++ b/test/spec-test1.spec.js @@ -0,0 +1,72 @@ +/* eslint-env mocha */ +'use strict' + +const { expect } = require('aegir/utils/chai') +const multibase = require('../src') +const constants = require('../src/constants.js') +const input = 'Decentralize everything!!' +const encoded = [ + ['identity', '\x00Decentralize everything!!'], + ['base2', '001000100011001010110001101100101011011100111010001110010011000010110110001101001011110100110010100100000011001010111011001100101011100100111100101110100011010000110100101101110011001110010000100100001'], + ['base8', '72106254331267164344605543227514510062566312711713506415133463441102'], + ['base10', '9429328951066508984658627669258025763026247056774804621697313'], + ['base16', 'f446563656e7472616c697a652065766572797468696e672121'], + ['base16upper', 'F446563656E7472616C697A652065766572797468696E672121'], + ['base32', 'birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb'], + ['base32upper', 'BIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJB'], + ['base32hex', 'v8him6pbeehp62r39f9ii0pbmclp7it38d5n6e891'], + ['base32hexupper', 'V8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E891'], + ['base32pad', 'cirswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb'], + ['base32padupper', 'CIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJB'], + ['base32hexpad', 't8him6pbeehp62r39f9ii0pbmclp7it38d5n6e891'], + ['base32hexpadupper', 'T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E891'], + ['base32z', 'het1sg3mqqt3gn5djxj11y3msci3817depfzgqejb'], + ['base36', 'k343ixo7d49hqj1ium15pgy1wzww5fxrid21td7l'], + ['base36upper', 'K343IXO7D49HQJ1IUM15PGY1WZWW5FXRID21TD7L'], + ['base58flickr', 'Ztwe7gVTeK8wswS1gf8hrgAua9fcw9reboD'], + ['base58btc', 'zUXE7GvtEk8XTXs1GF8HSGbVA9FCX9SEBPe'], + ['base64', 'mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ'], + ['base64pad', 'MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ=='], + ['base64url', 'uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ'], + ['base64urlpad', 'URGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ=='] +] + +describe('spec test1', () => { + for (const [name, output] of encoded) { + const base = constants.names[name] + + describe(name, () => { + it('should encode buffer by base name', () => { + const out = multibase.encode(name, Buffer.from(input)) + expect(out.toString()).to.equal(output) + }) + + it('should encode buffer by base code', () => { + const out = multibase.encode(base.code, Buffer.from(input)) + expect(out.toString()).to.equal(output) + }) + + it('should decode string', () => { + const out = multibase.decode(output) + expect(out.toString()).to.equal(input) + }) + + it('should prefix encoded buffer', () => { + const base = constants.names[name] + const data = base.encode(Buffer.from(input)) + + expect(multibase(name, Buffer.from(data)).toString()).to.equal(output) + }) + + it('should fail decode with invalid char', function () { + if (name === 'identity') { + return this.skip() + } + const nonEncodedBuf = Buffer.from(base.code + '^!@$%!#$%@#y') + expect(() => { + multibase.decode(nonEncodedBuf) + }).to.throw(Error, 'invalid character \'^\' in \'^!@$%!#$%@#y\'') + }) + }) + } +}) diff --git a/test/spec-test2.spec.js b/test/spec-test2.spec.js new file mode 100644 index 0000000..4493997 --- /dev/null +++ b/test/spec-test2.spec.js @@ -0,0 +1,55 @@ +/* eslint-env mocha */ +'use strict' + +const { expect } = require('aegir/utils/chai') +const multibase = require('../src') +const constants = require('../src/constants.js') +const input = 'yes mani !' +const encoded = [ + ['identity', '\x00yes mani !'], + ['base2', '001111001011001010111001100100000011011010110000101101110011010010010000000100001'], + ['base8', '7362625631006654133464440102'], + ['base10', '9573277761329450583662625'], + ['base16', 'f796573206d616e692021'], + ['base16upper', 'F796573206D616E692021'], + ['base32', 'bpfsxgidnmfxgsibb'], + ['base32upper', 'BPFSXGIDNMFXGSIBB'], + ['base32hex', 'vf5in683dc5n6i811'], + ['base32hexupper', 'VF5IN683DC5N6I811'], + ['base32pad', 'cpfsxgidnmfxgsibb'], + ['base32padupper', 'CPFSXGIDNMFXGSIBB'], + ['base32hexpad', 'tf5in683dc5n6i811'], + ['base32hexpadupper', 'TF5IN683DC5N6I811'], + ['base32z', 'hxf1zgedpcfzg1ebb'], + ['base36', 'k2lcpzo5yikidynfl'], + ['base36upper', 'K2LCPZO5YIKIDYNFL'], + ['base58flickr', 'Z7Pznk19XTTzBtx'], + ['base58btc', 'z7paNL19xttacUY'], + ['base64', 'meWVzIG1hbmkgIQ'], + ['base64pad', 'MeWVzIG1hbmkgIQ=='], + ['base64url', 'ueWVzIG1hbmkgIQ'], + ['base64urlpad', 'UeWVzIG1hbmkgIQ=='] +] + +describe('spec test2', () => { + for (const [name, output] of encoded) { + const base = constants.names[name] + + describe(name, () => { + it('should encode buffer by base name', () => { + const out = multibase.encode(name, Buffer.from(input)) + expect(out.toString()).to.equal(output) + }) + + it('should encode buffer by base code', () => { + const out = multibase.encode(base.code, Buffer.from(input)) + expect(out.toString()).to.equal(output) + }) + + it('should decode string', () => { + const out = multibase.decode(output) + expect(out.toString()).to.equal(input) + }) + }) + } +}) diff --git a/test/spec-test3.spec.js b/test/spec-test3.spec.js new file mode 100644 index 0000000..3cfba31 --- /dev/null +++ b/test/spec-test3.spec.js @@ -0,0 +1,55 @@ +/* eslint-env mocha */ +'use strict' + +const { expect } = require('aegir/utils/chai') +const multibase = require('../src') +const constants = require('../src/constants.js') +const input = 'hello world' +const encoded = [ + ['identity', '\x00hello world'], + ['base2', '00110100001100101011011000110110001101111001000000111011101101111011100100110110001100100'], + ['base8', '7320625543306744035667562330620'], + ['base10', '9126207244316550804821666916'], + ['base16', 'f68656c6c6f20776f726c64'], + ['base16upper', 'F68656C6C6F20776F726C64'], + ['base32', 'bnbswy3dpeb3w64tmmq'], + ['base32upper', 'BNBSWY3DPEB3W64TMMQ'], + ['base32hex', 'vd1imor3f41rmusjccg'], + ['base32hexupper', 'VD1IMOR3F41RMUSJCCG'], + ['base32pad', 'cnbswy3dpeb3w64tmmq======'], + ['base32padupper', 'CNBSWY3DPEB3W64TMMQ======'], + ['base32hexpad', 'td1imor3f41rmusjccg======'], + ['base32hexpadupper', 'TD1IMOR3F41RMUSJCCG======'], + ['base32z', 'hpb1sa5dxrb5s6hucco'], + ['base36', 'kfuvrsivvnfrbjwajo'], + ['base36upper', 'KFUVRSIVVNFRBJWAJO'], + ['base58flickr', 'ZrTu1dk6cWsRYjYu'], + ['base58btc', 'zStV1DL6CwTryKyV'], + ['base64', 'maGVsbG8gd29ybGQ'], + ['base64pad', 'MaGVsbG8gd29ybGQ='], + ['base64url', 'uaGVsbG8gd29ybGQ'], + ['base64urlpad', 'UaGVsbG8gd29ybGQ='] +] + +describe('spec test3', () => { + for (const [name, output] of encoded) { + const base = constants.names[name] + + describe(name, () => { + it('should encode buffer by base name', () => { + const out = multibase.encode(name, Buffer.from(input)) + expect(out.toString()).to.equal(output) + }) + + it('should encode buffer by base code', () => { + const out = multibase.encode(base.code, Buffer.from(input)) + expect(out.toString()).to.equal(output) + }) + + it('should decode string', () => { + const out = multibase.decode(output) + expect(out.toString()).to.equal(input) + }) + }) + } +}) diff --git a/test/spec-test4.spec.js b/test/spec-test4.spec.js new file mode 100644 index 0000000..8c48cc1 --- /dev/null +++ b/test/spec-test4.spec.js @@ -0,0 +1,55 @@ +/* eslint-env mocha */ +'use strict' + +const { expect } = require('aegir/utils/chai') +const multibase = require('../src') +const constants = require('../src/constants.js') +const input = '\x00yes mani !' +const encoded = [ + ['identity', '\x00\x00yes mani !'], + ['base2', '00000000001111001011001010111001100100000011011010110000101101110011010010010000000100001'], + ['base8', '7000745453462015530267151100204'], + ['base10', '90573277761329450583662625'], + ['base16', 'f00796573206d616e692021'], + ['base16upper', 'F00796573206D616E692021'], + ['base32', 'bab4wk4zanvqw42jaee'], + ['base32upper', 'BAB4WK4ZANVQW42JAEE'], + ['base32hex', 'v01smasp0dlgmsq9044'], + ['base32hexupper', 'V01SMASP0DLGMSQ9044'], + ['base32pad', 'cab4wk4zanvqw42jaee======'], + ['base32padupper', 'CAB4WK4ZANVQW42JAEE======'], + ['base32hexpad', 't01smasp0dlgmsq9044======'], + ['base32hexpadupper', 'T01SMASP0DLGMSQ9044======'], + ['base32z', 'hybhskh3ypiosh4jyrr'], + ['base36', 'k02lcpzo5yikidynfl'], + ['base36upper', 'K02LCPZO5YIKIDYNFL'], + ['base58flickr', 'Z17Pznk19XTTzBtx'], + ['base58btc', 'z17paNL19xttacUY'], + ['base64', 'mAHllcyBtYW5pICE'], + ['base64pad', 'MAHllcyBtYW5pICE='], + ['base64url', 'uAHllcyBtYW5pICE'], + ['base64urlpad', 'UAHllcyBtYW5pICE='] +] + +describe('spec test4', () => { + for (const [name, output] of encoded) { + const base = constants.names[name] + + describe(name, () => { + it('should encode buffer by base name', () => { + const out = multibase.encode(name, Buffer.from(input)) + expect(out.toString()).to.equal(output) + }) + + it('should encode buffer by base code', () => { + const out = multibase.encode(base.code, Buffer.from(input)) + expect(out.toString()).to.equal(output) + }) + + it('should decode string', () => { + const out = multibase.decode(output) + expect(out.toString()).to.equal(input) + }) + }) + } +}) diff --git a/test/spec-test5.spec.js b/test/spec-test5.spec.js new file mode 100644 index 0000000..a243d49 --- /dev/null +++ b/test/spec-test5.spec.js @@ -0,0 +1,55 @@ +/* eslint-env mocha */ +'use strict' + +const { expect } = require('aegir/utils/chai') +const multibase = require('../src') +const constants = require('../src/constants.js') +const input = '\x00\x00yes mani !' +const encoded = [ + ['identity', '\x00\x00\x00yes mani !'], + ['base2', '0000000000000000001111001011001010111001100100000011011010110000101101110011010010010000000100001'], + ['base8', '700000171312714403326055632220041'], + ['base10', '900573277761329450583662625'], + ['base16', 'f0000796573206d616e692021'], + ['base16upper', 'F0000796573206D616E692021'], + ['base32', 'baaahszltebwwc3tjeaqq'], + ['base32upper', 'BAAAHSZLTEBWWC3TJEAQQ'], + ['base32hex', 'v0007ipbj41mm2rj940gg'], + ['base32hexupper', 'V0007IPBJ41MM2RJ940GG'], + ['base32pad', 'caaahszltebwwc3tjeaqq===='], + ['base32padupper', 'CAAAHSZLTEBWWC3TJEAQQ===='], + ['base32hexpad', 't0007ipbj41mm2rj940gg===='], + ['base32hexpadupper', 'T0007IPBJ41MM2RJ940GG===='], + ['base32z', 'hyyy813murbssn5ujryoo'], + ['base36', 'k002lcpzo5yikidynfl'], + ['base36upper', 'K002LCPZO5YIKIDYNFL'], + ['base58flickr', 'Z117Pznk19XTTzBtx'], + ['base58btc', 'z117paNL19xttacUY'], + ['base64', 'mAAB5ZXMgbWFuaSAh'], + ['base64pad', 'MAAB5ZXMgbWFuaSAh'], + ['base64url', 'uAAB5ZXMgbWFuaSAh'], + ['base64urlpad', 'UAAB5ZXMgbWFuaSAh'] +] + +describe('spec test5', () => { + for (const [name, output] of encoded) { + const base = constants.names[name] + + describe(name, () => { + it('should encode buffer by base name', () => { + const out = multibase.encode(name, Buffer.from(input)) + expect(out.toString()).to.equal(output) + }) + + it('should encode buffer by base code', () => { + const out = multibase.encode(base.code, Buffer.from(input)) + expect(out.toString()).to.equal(output) + }) + + it('should decode string', () => { + const out = multibase.decode(output) + expect(out.toString()).to.equal(input) + }) + }) + } +}) diff --git a/test/spec-test6.spec.js b/test/spec-test6.spec.js new file mode 100644 index 0000000..da7ff01 --- /dev/null +++ b/test/spec-test6.spec.js @@ -0,0 +1,31 @@ +/* eslint-env mocha */ +'use strict' + +const { expect } = require('aegir/utils/chai') +const multibase = require('../src') +const input = 'hello world' +const encoded = [ + ['base16', 'f68656c6c6f20776F726C64'], + ['base16upper', 'F68656c6c6f20776F726C64'], + ['base32', 'bnbswy3dpeB3W64TMMQ'], + ['base32upper', 'Bnbswy3dpeB3W64TMMQ'], + ['base32hex', 'vd1imor3f41RMUSJCCG'], + ['base32hexupper', 'Vd1imor3f41RMUSJCCG'], + ['base32pad', 'cnbswy3dpeB3W64TMMQ======'], + ['base32padupper', 'Cnbswy3dpeB3W64TMMQ======'], + ['base32hexpad', 'td1imor3f41RMUSJCCG======'], + ['base32hexpadupper', 'Td1imor3f41RMUSJCCG======'], + ['base36', 'kfUvrsIvVnfRbjWaJo'], + ['base36upper', 'KfUVrSIVVnFRbJWAJo'] +] + +describe('spec test6', () => { + for (const [name, output] of encoded) { + describe(name, () => { + it('should decode string', () => { + const out = multibase.decode(output) + expect(out.toString()).to.equal(input) + }) + }) + } +})