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)
+ })
+ })
+ }
+})