Skip to content

Commit eef3821

Browse files
committed
1 parent dba6462 commit eef3821

8 files changed

+84
-7
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ import the ones you need yourself.
181181
| `base64`, `base64pad`, `base64url`, `base64urlpad` | `multiformats/bases/base64` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
182182
| `base58btc`, `base58flick4` | `multiformats/bases/base58` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
183183

184-
Other (less useful) bases implemented in [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) include: `base2`, `base8`, `base10`, `base36` and `base256emoji`.
184+
Other (less useful) bases implemented in [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) include: `base2`, `base8`, `base10`, `base36`, `base45` and `base256emoji`.
185185

186186
### Multihash hashers
187187

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
"types": "./dist/src/bases/base36.d.ts",
7171
"import": "./dist/src/bases/base36.js"
7272
},
73+
"./bases/base45": {
74+
"types": "./dist/src/bases/base45.d.ts",
75+
"import": "./dist/src/bases/base45.js"
76+
},
7377
"./bases/base58": {
7478
"types": "./dist/src/bases/base58.d.ts",
7579
"import": "./dist/src/bases/base58.js"

src/bases/base45.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { from } from './base.js'
2+
3+
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'
4+
5+
function indexOf (char: string) {
6+
const index = alphabet.indexOf(char)
7+
if (index === -1) {
8+
throw new Error(`Non-base45 character: ${char}`)
9+
}
10+
return index
11+
}
12+
13+
export const base45 = from({
14+
name: 'base45',
15+
prefix: 'R',
16+
encode: (input: Uint8Array): string => {
17+
var ret = ''
18+
for (let i = 0; i < input.length; i += 2) {
19+
if (i + 1 === input.length) {
20+
const v = input[i]
21+
const a = v / 45 | 0
22+
const b = v % 45 | 0
23+
ret += alphabet[b] + alphabet[a]
24+
break
25+
}
26+
const v = input[i] << 8 | input[i + 1]
27+
const a = v / 45 ** 2 | 0
28+
const b = v / 45 % 45 | 0
29+
const c = v % 45
30+
ret += alphabet[c] + alphabet[b] + alphabet[a]
31+
}
32+
return ret
33+
},
34+
decode: (input: string): Uint8Array => {
35+
if ((input.length * 2) % 3 === 2) {
36+
throw new Error('Unexpected end of data')
37+
}
38+
const out = new Uint8Array(Math.floor(input.length * 2 / 3))
39+
for (let i = 0; i < input.length; i += 3) {
40+
if (i + 2 === input.length) {
41+
const v = indexOf(input[i]) + indexOf(input[i + 1]) * 45
42+
out[i / 3 * 2] = v
43+
break
44+
}
45+
const v = indexOf(input[i]) + indexOf(input[i + 1]) * 45 + indexOf(input[i + 2]) * 45 ** 2
46+
out[i / 3 * 2] = v >> 8
47+
out[i / 3 * 2 + 1] = v & 0xff
48+
}
49+
return out
50+
}
51+
})

src/basics.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as base2 from './bases/base2.js'
44
import * as base256emoji from './bases/base256emoji.js'
55
import * as base32 from './bases/base32.js'
66
import * as base36 from './bases/base36.js'
7+
import * as base45 from './bases/base45.js'
78
import * as base58 from './bases/base58.js'
89
import * as base64 from './bases/base64.js'
910
import * as base8 from './bases/base8.js'
@@ -14,7 +15,7 @@ import * as identity from './hashes/identity.js'
1415
import * as sha2 from './hashes/sha2.js'
1516
import { CID, hasher, digest, varint, bytes } from './index.js'
1617

17-
export const bases = { ...identityBase, ...base2, ...base8, ...base10, ...base16, ...base32, ...base36, ...base58, ...base64, ...base256emoji }
18+
export const bases = { ...identityBase, ...base2, ...base8, ...base10, ...base16, ...base32, ...base36, ...base45, ...base58, ...base64, ...base256emoji }
1819
export const hashes = { ...sha2, ...identity }
1920
export const codecs = { raw, json }
2021

src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
* | `base64`, `base64pad`, `base64url`, `base64urlpad` | `multiformats/bases/base64` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
177177
* | `base58btc`, `base58flick4` | `multiformats/bases/base58` | [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) |
178178
*
179-
* Other (less useful) bases implemented in [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) include: `base2`, `base8`, `base10`, `base36` and `base256emoji`.
179+
* Other (less useful) bases implemented in [multiformats/js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/bases) include: `base2`, `base8`, `base10`, `base36`, `base45` and `base256emoji`.
180180
*
181181
* ### Multihash hashers
182182
*

test/test-multibase-spec.spec.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const encoded = [
2525
['base32z', 'het1sg3mqqt3gn5djxj11y3msci3817depfzgqejb'],
2626
['base36', 'k343ixo7d49hqj1ium15pgy1wzww5fxrid21td7l'],
2727
['base36upper', 'K343IXO7D49HQJ1IUM15PGY1WZWW5FXRID21TD7L'],
28+
['base45', 'R4T8KPCG/DVKEXVDDLFD44O/EALEAWEZEDV1DX0'],
2829
['base58flickr', 'Ztwe7gVTeK8wswS1gf8hrgAua9fcw9reboD'],
2930
['base58btc', 'zUXE7GvtEk8XTXs1GF8HSGbVA9FCX9SEBPe'],
3031
['base64', 'mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ'],
@@ -54,6 +55,7 @@ const encoded = [
5455
['base32z', 'hxf1zgedpcfzg1ebb'],
5556
['base36', 'k2lcpzo5yikidynfl'],
5657
['base36upper', 'K2LCPZO5YIKIDYNFL'],
58+
['base45', 'RRFF.OEB$D5/DZ24'],
5759
['base58flickr', 'Z7Pznk19XTTzBtx'],
5860
['base58btc', 'z7paNL19xttacUY'],
5961
['base64', 'meWVzIG1hbmkgIQ'],
@@ -83,6 +85,7 @@ const encoded = [
8385
['base32z', 'hpb1sa5dxrb5s6hucco'],
8486
['base36', 'kfuvrsivvnfrbjwajo'],
8587
['base36upper', 'KFUVRSIVVNFRBJWAJO'],
88+
['base45', 'R+8D VD82EK4F.KEA2'],
8689
['base58flickr', 'ZrTu1dk6cWsRYjYu'],
8790
['base58btc', 'zStV1DL6CwTryKyV'],
8891
['base64', 'maGVsbG8gd29ybGQ'],
@@ -112,6 +115,7 @@ const encoded = [
112115
['base32z', 'hybhskh3ypiosh4jyrr'],
113116
['base36', 'k02lcpzo5yikidynfl'],
114117
['base36upper', 'K02LCPZO5YIKIDYNFL'],
118+
['base45', 'RV206$CL44CEC2DDX0'],
115119
['base58flickr', 'Z17Pznk19XTTzBtx'],
116120
['base58btc', 'z17paNL19xttacUY'],
117121
['base64', 'mAHllcyBtYW5pICE'],
@@ -141,6 +145,7 @@ const encoded = [
141145
['base32z', 'hyyy813murbssn5ujryoo'],
142146
['base36', 'k002lcpzo5yikidynfl'],
143147
['base36upper', 'K002LCPZO5YIKIDYNFL'],
148+
['base45', 'R000RFF.OEB$D5/DZ24'],
144149
['base58flickr', 'Z117Pznk19XTTzBtx'],
145150
['base58btc', 'z117paNL19xttacUY'],
146151
['base64', 'mAAB5ZXMgbWFuaSAh'],
@@ -149,7 +154,12 @@ const encoded = [
149154
['base64urlpad', 'UAAB5ZXMgbWFuaSAh'],
150155
['base256emoji', '🚀🚀🚀🏃✋🌈😅🌷🤤😻🌟😅👏']
151156
]
152-
}
157+
},
158+
{ input: 'AB', tests: [['base45', 'RBB8']] },
159+
{ input: 'Hello!!', tests: [['base45', 'R%69 VD92EX0']] },
160+
{ input: 'base-45', tests: [['base45', 'RUJCLQE7W581']] },
161+
{ input: 'ietf!', tests: [['base45', 'RQED8WEX0']] },
162+
{ input: 'ietf!', tests: [['base45', 'RQED8WEX0']] },
153163
]
154164

155165
describe('spec test', () => {
@@ -160,12 +170,12 @@ describe('spec test', () => {
160170
const base = bases[name as keyof typeof bases]
161171

162172
describe(name, () => {
163-
it('should encode buffer', () => {
173+
it(`should encode from buffer [${input}]`, () => {
164174
const out = base.encode(fromString(input))
165175
assert.deepStrictEqual(out, output)
166176
})
167177

168-
it('should decode string', () => {
178+
it(`should decode from string [${input}]`, () => {
169179
assert.deepStrictEqual(base.decode(output), fromString(input))
170180
})
171181
})
@@ -182,4 +192,9 @@ describe('spec test', () => {
182192
assert.throws(() => base.decode(base.prefix + '^!@$%!#$%@#y'), `Non-${base.name} character`)
183193
})
184194
}
195+
196+
it('base45 should fail with invalid input', () => {
197+
// not enough input chars, should be multiple of 3 or multiple of 3 + 2
198+
assert.throws(() => bases.base45.decode('R%69 VD92EX'), 'Unexpected end of data')
199+
})
185200
})

test/test-multibase.spec.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as b16 from '../src/bases/base16.js'
66
import * as b2 from '../src/bases/base2.js'
77
import * as b32 from '../src/bases/base32.js'
88
import * as b36 from '../src/bases/base36.js'
9+
import * as b45 from '../src/bases/base45.js'
910
import * as b58 from '../src/bases/base58.js'
1011
import * as b64 from '../src/bases/base64.js'
1112
import * as b8 from '../src/bases/base8.js'
@@ -64,7 +65,7 @@ describe('multibase', () => {
6465
const buff = bytes.fromString('test')
6566
const nonPrintableBuff = Uint8Array.from([239, 250, 254])
6667

67-
const baseTest = (bases: typeof b2 | typeof b8 | typeof b10 | typeof b16 | typeof b32 | typeof b36 | typeof b58 | typeof b64): void => {
68+
const baseTest = (bases: typeof b2 | typeof b8 | typeof b10 | typeof b16 | typeof b32 | typeof b36 | typeof b45 | typeof b58 | typeof b64): void => {
6869
for (const base of Object.values(bases)) {
6970
if (((base as { name: string })?.name) !== '') {
7071
it(`encode/decode ${base.name}`, () => {
@@ -110,6 +111,10 @@ describe('multibase', () => {
110111
baseTest(b36)
111112
})
112113

114+
describe('base45', () => {
115+
baseTest(b45)
116+
})
117+
113118
describe('base58', () => {
114119
baseTest(b58)
115120
})

typedoc.json

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"./src/bases/base256emoji.ts",
88
"./src/bases/base32.ts",
99
"./src/bases/base36.ts",
10+
"./src/bases/base45.ts",
1011
"./src/bases/base58.ts",
1112
"./src/bases/base64.ts",
1213
"./src/bases/base8.ts",

0 commit comments

Comments
 (0)