Skip to content
This repository was archived by the owner on Aug 24, 2021. It is now read-only.

Commit 0a0bcf4

Browse files
theobatdaviddias
authored andcommitted
feat: support multiple bases
1 parent 3cdc43b commit 0a0bcf4

File tree

7 files changed

+241
-125
lines changed

7 files changed

+241
-125
lines changed

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,77 @@ Loading this module through a script tag will make the ```Multibase``` obj avail
8383

8484
You will need to use Node.js `Buffer` API compatible, if you are running inside the browser, you can access it by `multibase.Buffer` or you can load Feross's [Buffer](https://github.com/feross/buffer) module.
8585

86+
## Architecture and Encoding/Decoding
87+
88+
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.
89+
```js
90+
class Base {
91+
constructor (name, code, implementation, alphabet) {
92+
//...
93+
}
94+
// ...
95+
}
96+
```
97+
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.
98+
99+
The ```alphabet``` is the **ordered** set of defined symbols for a given base.
100+
101+
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).
102+
103+
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.
104+
105+
## Adding additional bases
106+
107+
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
108+
(**you'll need to create an issue about that beforehand since a code and a canonical name have to be defined**):
109+
110+
```js
111+
const baseX = require('base-x')
112+
//const newPackage = require('your-package-name')
113+
114+
const constants = [
115+
['base1', '1', '', '1'],
116+
['base2', '0', baseX, '01'],
117+
['base8', '7', baseX, '01234567'],
118+
// ... [ 'your-base-name', 'code-to-be-defined', newPackage, 'alphabet']
119+
]
120+
```
121+
The required package defines the implementation of the encoding/decoding process. **It must comply by these rules** :
122+
- `encode` and `decode` functions with to-be-encoded buffer as the only expected argument
123+
- the require call use the `alphabet` given as an argument for the encoding/decoding process
124+
125+
*If no package is specified (such as for base1 in the above example, it means the base is not implemented yet)*
126+
127+
Adding a new base requires the tests to be updated. Test files to be updated are :
128+
- constants.spec.js
129+
```js
130+
describe('constants', () => {
131+
it('constants indexed by name', () => {
132+
const names = constants.names
133+
expect(Object.keys(names).length).to.equal(constants-count) // currently 12
134+
})
135+
136+
it('constants indexed by code', () => {
137+
const codes = constants.codes
138+
expect(Object.keys(codes).length).to.equal(constants-count)
139+
})
140+
})
141+
```
142+
143+
- multibase.spec.js
144+
- if the base is implemented
145+
```js
146+
const supportedBases = [
147+
['base2', 'yes mani !', '01111001011001010111001100100000011011010110000101101110011010010010000000100001'],
148+
['base8', 'yes mani !', '7171312714403326055632220041'],
149+
['base10', 'yes mani !', '9573277761329450583662625'],
150+
// ... ['your-base-name', 'what you want', 'expected output']
151+
```
152+
- if the base is not implemented yet
153+
```js
154+
const supportedBases = [
155+
// ... ['your-base-name']
156+
```
86157

87158
## License
88159

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@
4444
},
4545
"homepage": "https://github.com/multiformats/js-multibase#readme",
4646
"dependencies": {
47-
"bs58": "^3.0.0"
47+
"base-x": "1.0.4"
4848
},
4949
"contributors": [
50-
"David Dias <daviddias.p@gmail.com>"
50+
"David Dias <daviddias.p@gmail.com>",
51+
"theobat <theophile.batoz@gmail.com>"
5152
]
52-
}
53+
}

src/Base.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict'
2+
3+
class Base {
4+
constructor (name, code, implementation, alphabet) {
5+
this.name = name
6+
this.code = code
7+
this.alphabet = alphabet
8+
if (implementation && alphabet) {
9+
this.engine = implementation(alphabet)
10+
}
11+
}
12+
13+
encode (stringOrBuffer) {
14+
return this.engine.encode(stringOrBuffer)
15+
}
16+
17+
decode (stringOrBuffer) {
18+
return this.engine.decode(stringOrBuffer)
19+
}
20+
21+
isImplemented () {
22+
return this.engine
23+
}
24+
25+
}
26+
27+
module.exports = Base

src/constants.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
11
'use strict'
22

3+
const Base = require('./Base.js')
4+
const baseX = require('base-x')
5+
// name, code, implementation, alphabet
36
const constants = [
4-
['base1', '1'],
5-
['base2', '0'],
6-
['base8', '7'],
7-
['base10', '9'],
8-
['base16', 'f'],
9-
['base58flickr', 'Z'],
10-
['base58btc', 'z'],
11-
['base64', 'y'],
12-
['base64url', 'Y']
7+
['base1', '1', '', '1'],
8+
['base2', '0', baseX, '01'],
9+
['base8', '7', baseX, '01234567'],
10+
['base10', '9', baseX, '0123456789'],
11+
['base16', 'f', baseX, '0123456789abcdef'],
12+
['base32hex', 'v', baseX, '0123456789abcdefghijklmnopqrstuv'],
13+
['base32', 'b', baseX, 'abcdefghijklmnopqrstuvwxyz234567'],
14+
['base32z', 'h', baseX, 'ybndrfg8ejkmcpqxot1uwisza345h769'],
15+
['base58flickr', 'Z', baseX, '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'],
16+
['base58btc', 'z', baseX, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'],
17+
['base64', 'm', baseX, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'],
18+
['base64url', 'u', baseX, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_']
1319
]
1420

1521
const names = constants.reduce((prev, tupple) => {
16-
prev[tupple[0]] = tupple[1]
22+
prev[tupple[0]] = new Base(tupple[0], tupple[1], tupple[2], tupple[3])
1723
return prev
1824
}, {})
1925

2026
const codes = constants.reduce((prev, tupple) => {
21-
prev[tupple[1]] = tupple[0]
27+
prev[tupple[1]] = names[tupple[0]]
2228
return prev
2329
}, {})
2430

src/multibase.js

Lines changed: 28 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict'
22

33
const constants = require('./constants')
4-
const bs58 = require('bs58')
54

65
exports = module.exports = multibase
76
exports.encode = encode
@@ -15,32 +14,27 @@ function multibase (nameOrCode, buf) {
1514
if (!buf) {
1615
throw new Error('requires an encoded buffer')
1716
}
18-
const code = getCode(nameOrCode)
19-
const codeBuf = new Buffer(code)
17+
const base = getBase(nameOrCode)
18+
const codeBuf = new Buffer(base.code)
2019

21-
const name = getName(nameOrCode)
20+
const name = base.name
2221
validEncode(name, buf)
2322
return Buffer.concat([codeBuf, buf])
2423
}
2524

2625
function encode (nameOrCode, buf) {
27-
const name = getName(nameOrCode)
26+
const base = getBase(nameOrCode)
27+
const name = base.name
2828

29-
let encode
30-
31-
switch (name) {
32-
case 'base58btc': {
33-
encode = (buf) => { return new Buffer(bs58.encode(buf)) }
34-
} break
35-
default: throw errNotSupported
36-
}
37-
38-
return multibase(name, encode(buf))
29+
return multibase(name, new Buffer(base.encode(buf)))
3930
}
4031

4132
// receives a buffer or string encoded with multibase header
4233
// decodes it and returns an object with the decoded buffer
4334
// and the encoded type { base: <name>, data: <buffer> }
35+
36+
// from @theobat : This is not what the multibase.spec.js test is waiting for,
37+
// hence the return decodeObject.data
4438
function decode (bufOrString) {
4539
if (Buffer.isBuffer(bufOrString)) {
4640
bufOrString = bufOrString.toString()
@@ -53,16 +47,13 @@ function decode (bufOrString) {
5347
bufOrString = new Buffer(bufOrString)
5448
}
5549

56-
let decode
50+
const base = getBase(code)
5751

58-
switch (code) {
59-
case 'z': {
60-
decode = (buf) => { return new Buffer(bs58.decode(buf.toString())) }
61-
} break
62-
default: throw errNotSupported
52+
const decodeObject = {
53+
base: base.name,
54+
data: new Buffer(base.decode(bufOrString.toString()))
6355
}
64-
65-
return decode(bufOrString)
56+
return decodeObject.data
6657
}
6758

6859
function isEncoded (bufOrString) {
@@ -72,56 +63,32 @@ function isEncoded (bufOrString) {
7263

7364
const code = bufOrString.substring(0, 1)
7465
try {
75-
const name = getName(code)
76-
return name
66+
const base = getBase(code)
67+
return base.name
7768
} catch (err) {
7869
return false
7970
}
8071
}
8172

82-
function validNameOrCode (nameOrCode) {
83-
const err = new Error('Unsupported encoding')
84-
85-
if (!constants.names[nameOrCode] &&
86-
!constants.codes[nameOrCode]) {
87-
throw err
88-
}
89-
}
90-
9173
function validEncode (name, buf) {
92-
let decode
93-
94-
switch (name) {
95-
case 'base58btc': {
96-
decode = bs58.decode
97-
buf = buf.toString() // bs58 only operates in strings bs58 strings
98-
} break
99-
default: throw errNotSupported
100-
}
101-
102-
decode(buf)
74+
const base = getBase(name)
75+
base.decode(buf.toString())
10376
}
10477

105-
function getCode (nameOrCode) {
106-
validNameOrCode(nameOrCode)
107-
108-
let code = nameOrCode
78+
function getBase (nameOrCode) {
79+
let base
10980

11081
if (constants.names[nameOrCode]) {
111-
code = constants.names[nameOrCode]
82+
base = constants.names[nameOrCode]
83+
} else if (constants.codes[nameOrCode]) {
84+
base = constants.codes[nameOrCode]
85+
} else {
86+
throw errNotSupported
11287
}
11388

114-
return code
115-
}
116-
117-
function getName (nameOrCode) {
118-
validNameOrCode(nameOrCode)
119-
120-
let name = nameOrCode
121-
122-
if (constants.codes[nameOrCode]) {
123-
name = constants.codes[nameOrCode]
89+
if (!base.isImplemented()) {
90+
throw new Error('Base ' + nameOrCode + ' is not implemented yet')
12491
}
12592

126-
return name
93+
return base
12794
}

test/constants.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ const constants = require('../src/constants.js')
77
describe('constants', () => {
88
it('constants indexed by name', () => {
99
const names = constants.names
10-
expect(Object.keys(names).length).to.equal(9)
10+
expect(Object.keys(names).length).to.equal(12)
1111
})
1212

1313
it('constants indexed by code', () => {
1414
const codes = constants.codes
15-
expect(Object.keys(codes).length).to.equal(9)
15+
expect(Object.keys(codes).length).to.equal(12)
1616
})
1717
})

0 commit comments

Comments
 (0)