@interop/bnid 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/LICENSE +27 -0
- package/README.md +329 -0
- package/dist/baseX.d.ts +2 -0
- package/dist/baseX.d.ts.map +1 -0
- package/dist/baseX.js +3 -0
- package/dist/baseX.js.map +1 -0
- package/dist/index.d.ts +192 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +450 -0
- package/dist/index.js.map +1 -0
- package/dist/util-browser.d.ts +4 -0
- package/dist/util-browser.d.ts.map +1 -0
- package/dist/util-browser.js +20 -0
- package/dist/util-browser.js.map +1 -0
- package/dist/util-reactnative.d.ts +4 -0
- package/dist/util-reactnative.d.ts.map +1 -0
- package/dist/util-reactnative.js +24 -0
- package/dist/util-reactnative.js.map +1 -0
- package/dist/util.d.ts +4 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +14 -0
- package/dist/util.js.map +1 -0
- package/package.json +103 -0
- package/src/baseX.ts +3 -0
- package/src/index.ts +560 -0
- package/src/util-browser.ts +23 -0
- package/src/util-reactnative.ts +27 -0
- package/src/util.ts +17 -0
package/src/baseX.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2020 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import { base58btc } from './baseX.js'
|
|
5
|
+
import { getRandomBytes, bytesToHex, bytesFromHex } from './util.js'
|
|
6
|
+
|
|
7
|
+
// multihash identity function code
|
|
8
|
+
const MULTIHASH_IDENTITY_FUNCTION_CODE = 0x00
|
|
9
|
+
|
|
10
|
+
function _calcOptionsBitLength({
|
|
11
|
+
defaultLength,
|
|
12
|
+
// TODO: allow any bit length
|
|
13
|
+
minLength = 8,
|
|
14
|
+
// TODO: support maxLength
|
|
15
|
+
// maxLength = Infinity,
|
|
16
|
+
bitLength
|
|
17
|
+
}: {
|
|
18
|
+
defaultLength: number
|
|
19
|
+
minLength?: number
|
|
20
|
+
bitLength?: number
|
|
21
|
+
}): number {
|
|
22
|
+
if (bitLength === undefined) {
|
|
23
|
+
return defaultLength
|
|
24
|
+
}
|
|
25
|
+
// TODO: allow any bit length
|
|
26
|
+
if (bitLength % 8 !== 0) {
|
|
27
|
+
throw new Error('Bit length must be a multiple of 8.')
|
|
28
|
+
}
|
|
29
|
+
if (bitLength < minLength) {
|
|
30
|
+
throw new Error(`Minimum bit length is ${minLength}.`)
|
|
31
|
+
}
|
|
32
|
+
// TODO: support maxLength
|
|
33
|
+
// if(bitLength > maxLength) {
|
|
34
|
+
// throw new Error(`Maximum bit length is ${maxLength}.`);
|
|
35
|
+
// }
|
|
36
|
+
return bitLength
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _calcDataBitLength({
|
|
40
|
+
bitLength,
|
|
41
|
+
maxLength
|
|
42
|
+
}: {
|
|
43
|
+
bitLength: number
|
|
44
|
+
maxLength?: number
|
|
45
|
+
}): number {
|
|
46
|
+
if (maxLength === 0) {
|
|
47
|
+
return bitLength
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (maxLength && bitLength > maxLength) {
|
|
51
|
+
throw new Error(`Input length greater than ${maxLength} bits.`)
|
|
52
|
+
}
|
|
53
|
+
// @ts-expect-error - maxLength may be undefined when no fixed length is set
|
|
54
|
+
return maxLength
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function _bytesWithBitLength({
|
|
58
|
+
bytes,
|
|
59
|
+
bitLength
|
|
60
|
+
}: {
|
|
61
|
+
bytes: Uint8Array
|
|
62
|
+
bitLength: number
|
|
63
|
+
}): Uint8Array {
|
|
64
|
+
const length = bytes.length * 8
|
|
65
|
+
if (length === bitLength) {
|
|
66
|
+
return bytes
|
|
67
|
+
}
|
|
68
|
+
if (length < bitLength) {
|
|
69
|
+
// pad start
|
|
70
|
+
const data = new Uint8Array(bitLength / 8)
|
|
71
|
+
data.set(bytes, data.length - bytes.length)
|
|
72
|
+
return data
|
|
73
|
+
}
|
|
74
|
+
// trim start, ensure trimmed data is zero
|
|
75
|
+
const start = (length - bitLength) / 8
|
|
76
|
+
if (bytes.subarray(0, start).some(d => d !== 0)) {
|
|
77
|
+
throw new Error(`Data length greater than ${bitLength} bits.`)
|
|
78
|
+
}
|
|
79
|
+
return bytes.subarray(start)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface IEncoder {
|
|
83
|
+
bytes: Uint8Array
|
|
84
|
+
idEncoder: IdEncoder
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const _log2_16 = 4
|
|
88
|
+
|
|
89
|
+
function _base16Encoder({ bytes, idEncoder }: IEncoder): string {
|
|
90
|
+
let encoded = bytesToHex(bytes)
|
|
91
|
+
if (idEncoder.encoding === 'base16upper') {
|
|
92
|
+
encoded = encoded.toUpperCase()
|
|
93
|
+
}
|
|
94
|
+
if (idEncoder.fixedLength && idEncoder.fixedLength !== undefined) {
|
|
95
|
+
const fixedBitLength = _calcDataBitLength({
|
|
96
|
+
bitLength: bytes.length * 8,
|
|
97
|
+
maxLength: idEncoder.fixedBitLength
|
|
98
|
+
})
|
|
99
|
+
const wantLength = Math.ceil(fixedBitLength / _log2_16)
|
|
100
|
+
// pad start with 0s
|
|
101
|
+
return encoded.padStart(wantLength, '0')
|
|
102
|
+
}
|
|
103
|
+
return encoded
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const _log2_58 = Math.log2(58)
|
|
107
|
+
|
|
108
|
+
function _base58Encoder({ bytes, idEncoder }: IEncoder): string {
|
|
109
|
+
const encoded = base58btc.encode(bytes)
|
|
110
|
+
if (idEncoder.fixedLength) {
|
|
111
|
+
const fixedBitLength = _calcDataBitLength({
|
|
112
|
+
bitLength: bytes.length * 8,
|
|
113
|
+
maxLength: idEncoder.fixedBitLength
|
|
114
|
+
})
|
|
115
|
+
const wantLength = Math.ceil(fixedBitLength / _log2_58)
|
|
116
|
+
// pad start with 0s (encoded as '1's)
|
|
117
|
+
return encoded.padStart(wantLength, '1')
|
|
118
|
+
}
|
|
119
|
+
return encoded
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class IdGenerator {
|
|
123
|
+
public bitLength: number
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Creates a new IdGenerator instance.
|
|
127
|
+
*
|
|
128
|
+
* An IdGenerator generates an array of id bytes.
|
|
129
|
+
*
|
|
130
|
+
* @param {object} [options] - The options to use.
|
|
131
|
+
* @param {number} [options.bitLength=128] - Number of bits to generate.
|
|
132
|
+
*
|
|
133
|
+
* @returns {IdGenerator} - New IdGenerator.
|
|
134
|
+
*/
|
|
135
|
+
constructor({ bitLength }: { bitLength?: number } = {}) {
|
|
136
|
+
this.bitLength = _calcOptionsBitLength({
|
|
137
|
+
// default to 128 bits / 16 bytes
|
|
138
|
+
defaultLength: 128,
|
|
139
|
+
// TODO: allow any bit length
|
|
140
|
+
minLength: 8,
|
|
141
|
+
bitLength
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Generate random id bytes.
|
|
147
|
+
*
|
|
148
|
+
* @returns {Uint8Array} - Array of random id bytes.
|
|
149
|
+
*/
|
|
150
|
+
async generate(): Promise<Uint8Array> {
|
|
151
|
+
const buf = new Uint8Array(this.bitLength / 8)
|
|
152
|
+
await getRandomBytes(buf)
|
|
153
|
+
return buf
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface IIdEncoder {
|
|
158
|
+
encoding?: string
|
|
159
|
+
fixedLength?: boolean
|
|
160
|
+
fixedBitLength?: number
|
|
161
|
+
bitLength?: number
|
|
162
|
+
multibase?: boolean
|
|
163
|
+
multihash?: boolean
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export class IdEncoder {
|
|
167
|
+
public encoder: ({ bytes, idEncoder }: IEncoder) => string
|
|
168
|
+
public encoding: string
|
|
169
|
+
public multibasePrefix: string
|
|
170
|
+
public fixedLength: boolean
|
|
171
|
+
public fixedBitLength?: number
|
|
172
|
+
public multibase: boolean = true
|
|
173
|
+
public multihash: boolean = false
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Creates a new IdEncoder instance.
|
|
177
|
+
*
|
|
178
|
+
* An IdEncoder encodes an array of id bytes into a specific encoding.
|
|
179
|
+
*
|
|
180
|
+
* @param {object} [options] - The options to use.
|
|
181
|
+
* @param {string} [options.encoding='base58'] - Encoding format.
|
|
182
|
+
* @param {boolean} [options.fixedLength=false] - `true` to ensure fixed
|
|
183
|
+
* output length.
|
|
184
|
+
* @param {number} [options.fixedBitLength] - Fixed output bit length or 0 to
|
|
185
|
+
* base on input byte size.
|
|
186
|
+
* @param {boolean} [options.multibase=true] - Use multibase encoding.
|
|
187
|
+
* @param {boolean} [options.multihash=false] - Use multihash encoding.
|
|
188
|
+
*
|
|
189
|
+
* @returns {IdEncoder} - New IdEncoder.
|
|
190
|
+
*/
|
|
191
|
+
constructor({
|
|
192
|
+
encoding = 'base58',
|
|
193
|
+
fixedLength = false,
|
|
194
|
+
fixedBitLength,
|
|
195
|
+
multibase = true,
|
|
196
|
+
multihash = false
|
|
197
|
+
}: IIdEncoder = {}) {
|
|
198
|
+
switch (encoding) {
|
|
199
|
+
case 'hex':
|
|
200
|
+
case 'base16':
|
|
201
|
+
this.encoder = _base16Encoder
|
|
202
|
+
this.multibasePrefix = 'f'
|
|
203
|
+
break
|
|
204
|
+
case 'base16upper':
|
|
205
|
+
this.encoder = _base16Encoder
|
|
206
|
+
this.multibasePrefix = 'F'
|
|
207
|
+
break
|
|
208
|
+
case 'base58':
|
|
209
|
+
case 'base58btc':
|
|
210
|
+
this.encoder = _base58Encoder
|
|
211
|
+
this.multibasePrefix = 'z'
|
|
212
|
+
break
|
|
213
|
+
default:
|
|
214
|
+
throw new Error(`Unknown encoding type: "${encoding}".`)
|
|
215
|
+
}
|
|
216
|
+
this.fixedLength = fixedLength || fixedBitLength !== undefined
|
|
217
|
+
if (this.fixedLength) {
|
|
218
|
+
this.fixedBitLength = _calcOptionsBitLength({
|
|
219
|
+
// default of 0 calculates from input size
|
|
220
|
+
defaultLength: 0,
|
|
221
|
+
bitLength: fixedBitLength
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
this.encoding = encoding
|
|
225
|
+
this.multibase = multibase
|
|
226
|
+
this.multihash = multihash
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Encode id bytes into a string.
|
|
231
|
+
*
|
|
232
|
+
* @param {Uint8Array} bytes - Bytes to encode.
|
|
233
|
+
*
|
|
234
|
+
* @returns {string} - Encoded string.
|
|
235
|
+
*/
|
|
236
|
+
encode(bytes: Uint8Array): string {
|
|
237
|
+
if (this.multihash) {
|
|
238
|
+
const byteSize = bytes.length
|
|
239
|
+
|
|
240
|
+
if (byteSize > 127) {
|
|
241
|
+
throw new RangeError('Identifier size too large.')
|
|
242
|
+
}
|
|
243
|
+
// <varint hash fn code> <varint digest size in bytes> <hash fn output>
|
|
244
|
+
// <identity function> <byte size> <raw bytes>
|
|
245
|
+
const multihash = new Uint8Array(2 + byteSize)
|
|
246
|
+
// <varint hash fn code>: identity function
|
|
247
|
+
multihash.set([MULTIHASH_IDENTITY_FUNCTION_CODE])
|
|
248
|
+
// <varint digest size in bytes>
|
|
249
|
+
multihash.set([byteSize], 1)
|
|
250
|
+
// <hash fn output>: identifier bytes
|
|
251
|
+
multihash.set(bytes, 2)
|
|
252
|
+
bytes = multihash
|
|
253
|
+
}
|
|
254
|
+
const encoded = this.encoder({ bytes, idEncoder: this })
|
|
255
|
+
if (this.multibase) {
|
|
256
|
+
return this.multibasePrefix + encoded
|
|
257
|
+
}
|
|
258
|
+
return encoded
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface IIdDecoder {
|
|
263
|
+
encoding?: string
|
|
264
|
+
fixedBitLength?: number
|
|
265
|
+
multibase?: boolean
|
|
266
|
+
multihash?: boolean
|
|
267
|
+
expectedSize?: number
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export class IdDecoder {
|
|
271
|
+
public encoding: string
|
|
272
|
+
public fixedBitLength?: number = 0
|
|
273
|
+
public multibase: boolean
|
|
274
|
+
public multihash: boolean
|
|
275
|
+
public expectedSize: number
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Creates a new IdDecoder instance.
|
|
279
|
+
*
|
|
280
|
+
* An IdDecoder decodes an id string into a byte array. It is recommended to
|
|
281
|
+
* use the fixedBitLength option to avoid padding ids resulting in a larger
|
|
282
|
+
* than expected byte length.
|
|
283
|
+
*
|
|
284
|
+
* @param {object} [options] - The options to use.
|
|
285
|
+
* @param {string} [options.encoding='base58'] - Encoding format. Ignored if
|
|
286
|
+
* multibase is true.
|
|
287
|
+
* @param {number} [options.fixedBitLength] - Fixed output bit length. Values
|
|
288
|
+
* with leading non-zero data will error.
|
|
289
|
+
* @param {boolean} [options.multibase=true] - Use multibase encoding to
|
|
290
|
+
* detect the id format.
|
|
291
|
+
* @param {boolean} [options.multihash=false] - Use multihash encoding to
|
|
292
|
+
* detect the id format.
|
|
293
|
+
* @param {number} [options.expectedSize=32] - Optional expected identifier
|
|
294
|
+
* size in bytes (only for multihash encoding). Use `0` to disable size
|
|
295
|
+
* check.
|
|
296
|
+
* @returns {IdDecoder} - New IdDecoder.
|
|
297
|
+
*/
|
|
298
|
+
constructor({
|
|
299
|
+
encoding = 'base58',
|
|
300
|
+
fixedBitLength = 0,
|
|
301
|
+
multibase = true,
|
|
302
|
+
multihash = false,
|
|
303
|
+
expectedSize = 32
|
|
304
|
+
}: IIdDecoder = {}) {
|
|
305
|
+
this.encoding = encoding
|
|
306
|
+
this.fixedBitLength = fixedBitLength
|
|
307
|
+
this.multibase = multibase
|
|
308
|
+
this.multihash = multihash
|
|
309
|
+
this.expectedSize = expectedSize
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Decode id string into bytes.
|
|
314
|
+
*
|
|
315
|
+
* @param {string} id - Id to decode.
|
|
316
|
+
*
|
|
317
|
+
* @returns {Uint8Array} - Array of decoded id bytes.
|
|
318
|
+
*/
|
|
319
|
+
decode(id: string): Uint8Array {
|
|
320
|
+
let encoding
|
|
321
|
+
let data
|
|
322
|
+
if (this.multibase) {
|
|
323
|
+
if (id.length < 1) {
|
|
324
|
+
throw new Error('Multibase encoding not found.')
|
|
325
|
+
}
|
|
326
|
+
const prefix = id[0]
|
|
327
|
+
data = id.substring(1)
|
|
328
|
+
switch (id[0]) {
|
|
329
|
+
case 'f':
|
|
330
|
+
encoding = 'base16'
|
|
331
|
+
break
|
|
332
|
+
case 'F':
|
|
333
|
+
encoding = 'base16upper'
|
|
334
|
+
break
|
|
335
|
+
case 'z':
|
|
336
|
+
encoding = 'base58'
|
|
337
|
+
break
|
|
338
|
+
default:
|
|
339
|
+
throw new Error(`Unknown multibase prefix "${prefix}".`)
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
encoding = this.encoding
|
|
343
|
+
data = id
|
|
344
|
+
}
|
|
345
|
+
let decoded
|
|
346
|
+
switch (encoding) {
|
|
347
|
+
case 'hex':
|
|
348
|
+
case 'base16':
|
|
349
|
+
case 'base16upper':
|
|
350
|
+
if (data.length % 2 !== 0) {
|
|
351
|
+
throw new Error('Invalid base16 data length.')
|
|
352
|
+
}
|
|
353
|
+
decoded = bytesFromHex(data)
|
|
354
|
+
break
|
|
355
|
+
case 'base58':
|
|
356
|
+
decoded = base58btc.decode(data)
|
|
357
|
+
break
|
|
358
|
+
default:
|
|
359
|
+
throw new Error(`Unknown encoding "${encoding}".`)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (!decoded) {
|
|
363
|
+
throw new Error(`Invalid encoded data "${data}".`)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (this.fixedBitLength) {
|
|
367
|
+
return _bytesWithBitLength({
|
|
368
|
+
bytes: decoded,
|
|
369
|
+
bitLength: this.fixedBitLength ?? 0
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
if (this.multihash) {
|
|
373
|
+
// <varint hash fn code>: identity function
|
|
374
|
+
const [hashFnCode] = decoded
|
|
375
|
+
|
|
376
|
+
if (hashFnCode !== MULTIHASH_IDENTITY_FUNCTION_CODE) {
|
|
377
|
+
throw new Error('Invalid multihash function code.')
|
|
378
|
+
}
|
|
379
|
+
// <varint digest size in bytes>
|
|
380
|
+
const digestSize = decoded[1]
|
|
381
|
+
|
|
382
|
+
if (digestSize > 127) {
|
|
383
|
+
throw new RangeError('Decoded identifier size too large.')
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const bytes = decoded.subarray(2)
|
|
387
|
+
|
|
388
|
+
if (bytes.byteLength !== digestSize) {
|
|
389
|
+
throw new RangeError('Unexpected identifier size.')
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (this.expectedSize && bytes.byteLength !== this.expectedSize) {
|
|
393
|
+
throw new RangeError(
|
|
394
|
+
'Invalid decoded identifier size. Identifier must be ' +
|
|
395
|
+
`"${this.expectedSize}" bytes.`
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
decoded = bytes
|
|
400
|
+
}
|
|
401
|
+
return decoded
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Generates an encoded id string from random bits.
|
|
407
|
+
*
|
|
408
|
+
* @param {object} [options] - The options to use. See `IdEncoder` and
|
|
409
|
+
* `IdGenerator` for available options.
|
|
410
|
+
*
|
|
411
|
+
* @returns {string} - Encoded string id.
|
|
412
|
+
*/
|
|
413
|
+
export async function generateId(options: IIdEncoder): Promise<string> {
|
|
414
|
+
return new IdEncoder(options).encode(
|
|
415
|
+
await new IdGenerator(options).generate()
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Decodes an encoded id string to an array of bytes.
|
|
421
|
+
*
|
|
422
|
+
* @param {object} options - The options to use. See `IdDecoder` for available
|
|
423
|
+
* options.
|
|
424
|
+
* @param {string} options.id - Id to decode.
|
|
425
|
+
*
|
|
426
|
+
* @returns {Uint8Array} - Decoded array of id bytes.
|
|
427
|
+
*/
|
|
428
|
+
export function decodeId(options: IIdDecoder & { id: string }): Uint8Array {
|
|
429
|
+
return new IdDecoder(options).decode(options.id)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Minimum number of bytes needed to encode an id of a given bit length.
|
|
434
|
+
*
|
|
435
|
+
* @param {object} options - The options to use.
|
|
436
|
+
* @param {string} [options.encoding='base58'] - Encoding format.
|
|
437
|
+
* @param {number} [options.bitLength=128] - Number of id bits.
|
|
438
|
+
* @param {boolean} [options.multibase=true] - Account for multibase encoding.
|
|
439
|
+
*
|
|
440
|
+
* @returns {number} - The minimum number of encoded bytes.
|
|
441
|
+
*/
|
|
442
|
+
export function minEncodedIdBytes({
|
|
443
|
+
encoding = 'base58',
|
|
444
|
+
bitLength = 128,
|
|
445
|
+
multibase = true
|
|
446
|
+
}: IIdEncoder = {}): number {
|
|
447
|
+
let plainBytes
|
|
448
|
+
switch (encoding) {
|
|
449
|
+
case 'hex':
|
|
450
|
+
case 'base16':
|
|
451
|
+
case 'base16upper':
|
|
452
|
+
plainBytes = bitLength / 4
|
|
453
|
+
break
|
|
454
|
+
case 'base58':
|
|
455
|
+
case 'base58btc':
|
|
456
|
+
plainBytes = bitLength / 8
|
|
457
|
+
break
|
|
458
|
+
default:
|
|
459
|
+
throw new Error(`Unknown encoding type: "${encoding}".`)
|
|
460
|
+
}
|
|
461
|
+
return plainBytes + (multibase ? 1 : 0)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Maximum number of bytes needed to encode an id of a given bit length.
|
|
466
|
+
*
|
|
467
|
+
* @param {object} options - The options to use.
|
|
468
|
+
* @param {string} [options.encoding='base58'] - Encoding format.
|
|
469
|
+
* @param {number} [options.bitLength=128] - Number of id bits.
|
|
470
|
+
* @param {boolean} [options.multibase=true] - Account for multibase encoding.
|
|
471
|
+
*
|
|
472
|
+
* @returns {number} - The maximum number of encoded bytes.
|
|
473
|
+
*/
|
|
474
|
+
export function maxEncodedIdBytes({
|
|
475
|
+
encoding = 'base58',
|
|
476
|
+
bitLength = 128,
|
|
477
|
+
multibase = true
|
|
478
|
+
}: IIdEncoder = {}): number {
|
|
479
|
+
let plainBytes
|
|
480
|
+
switch (encoding) {
|
|
481
|
+
case 'hex':
|
|
482
|
+
case 'base16':
|
|
483
|
+
case 'base16upper':
|
|
484
|
+
plainBytes = bitLength / 4
|
|
485
|
+
break
|
|
486
|
+
case 'base58':
|
|
487
|
+
case 'base58btc':
|
|
488
|
+
plainBytes = Math.ceil(bitLength / Math.log2(58))
|
|
489
|
+
break
|
|
490
|
+
default:
|
|
491
|
+
throw new Error(`Unknown encoding type: "${encoding}".`)
|
|
492
|
+
}
|
|
493
|
+
return plainBytes + (multibase ? 1 : 0)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Generates a secret key seed encoded as a string that can be stored and later
|
|
498
|
+
* used to generate a key pair. The public key from the key pair can be used as
|
|
499
|
+
* an identifier. The key seed (both raw and encoded form) MUST be kept secret.
|
|
500
|
+
*
|
|
501
|
+
* @param {object} [options] - The options to use.
|
|
502
|
+
* @param {string} [options.encoding='base58'] - Encoding format.
|
|
503
|
+
* @param {number} [options.bitLength=32 * 8] - Number of bits to generate.
|
|
504
|
+
* @param {boolean} [options.multibase=true] - Use multibase encoding.
|
|
505
|
+
* @param {boolean} [options.multihash=true] - Use multihash encoding.
|
|
506
|
+
|
|
507
|
+
* @returns {string} - Secret key seed encoded as a string.
|
|
508
|
+
*/
|
|
509
|
+
export async function generateSecretKeySeed({
|
|
510
|
+
bitLength = 32 * 8,
|
|
511
|
+
encoding = 'base58',
|
|
512
|
+
multibase = true,
|
|
513
|
+
multihash = true
|
|
514
|
+
}: IIdEncoder = {}): Promise<string> {
|
|
515
|
+
// reuse `generateId` for convenience, but a key seed is *SECRET* and
|
|
516
|
+
// not an identifier itself, rather it is used to generate an identifier via
|
|
517
|
+
// a public key
|
|
518
|
+
// Note: Setting fixedLength to false even though that's the (current)
|
|
519
|
+
// default as not using a fixed length of false for a seed is a security
|
|
520
|
+
// problem
|
|
521
|
+
return await generateId({
|
|
522
|
+
bitLength,
|
|
523
|
+
encoding,
|
|
524
|
+
fixedLength: false,
|
|
525
|
+
multibase,
|
|
526
|
+
multihash
|
|
527
|
+
})
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Decodes an encoded secret key seed into an array of secret key seed bytes.
|
|
532
|
+
* The key seed bytes MUST be kept secret.
|
|
533
|
+
*
|
|
534
|
+
* @param {object} options - The options to use.
|
|
535
|
+
* @param {boolean} [options.multibase=true] - Use multibase encoding to detect
|
|
536
|
+
* the id format.
|
|
537
|
+
* @param {boolean} [options.multihash=true] - Use multihash encoding to detect
|
|
538
|
+
* the id format.
|
|
539
|
+
* @param {number} [options.expectedSize] - Optional expected identifier size
|
|
540
|
+
* in bytes (only for multihash encoding). Use `0` to disable size check.
|
|
541
|
+
* @param {string} options.secretKeySeed - The secret key seed to be decoded.
|
|
542
|
+
*
|
|
543
|
+
* @returns {Uint8Array} - An array of secret key seed bytes (default size:
|
|
544
|
+
* 32 bytes).
|
|
545
|
+
*/
|
|
546
|
+
export function decodeSecretKeySeed({
|
|
547
|
+
multibase = true,
|
|
548
|
+
multihash = true,
|
|
549
|
+
expectedSize = 32,
|
|
550
|
+
secretKeySeed
|
|
551
|
+
}: {
|
|
552
|
+
multibase?: boolean
|
|
553
|
+
multihash?: boolean
|
|
554
|
+
expectedSize?: number
|
|
555
|
+
secretKeySeed: string
|
|
556
|
+
}): Uint8Array {
|
|
557
|
+
// reuse `decodeId` for convenience, but key seed bytes are *SECRET* and
|
|
558
|
+
// are NOT identifiers, they are used to generate identifiers from public keys
|
|
559
|
+
return decodeId({ multihash, multibase, expectedSize, id: secretKeySeed })
|
|
560
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// browser support
|
|
2
|
+
const crypto = globalThis.crypto
|
|
3
|
+
|
|
4
|
+
export async function getRandomBytes(buf: Uint8Array): Promise<Uint8Array> {
|
|
5
|
+
return crypto.getRandomValues(buf)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
9
|
+
return Array.from(bytes)
|
|
10
|
+
.map(d => d.toString(16).padStart(2, '0'))
|
|
11
|
+
.join('')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// adapted from:
|
|
15
|
+
|
|
16
|
+
// https://stackoverflow.com/questions/43131242/how-to-convert-a-hexadecimal-string-of-data-to-an-arraybuffer-in-javascript
|
|
17
|
+
export function bytesFromHex(hex: string): Uint8Array {
|
|
18
|
+
if (hex.length === 0) {
|
|
19
|
+
return new Uint8Array()
|
|
20
|
+
}
|
|
21
|
+
// @ts-expect-error - match() is non-null for validated hex input
|
|
22
|
+
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16)))
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// React Native support
|
|
2
|
+
//
|
|
3
|
+
// Requires the `react-native-get-random-values` polyfill, imported once at the
|
|
4
|
+
// top of the app entry, so that `globalThis.crypto.getRandomValues()` is
|
|
5
|
+
// available. See the README "React Native" section.
|
|
6
|
+
const crypto = globalThis.crypto
|
|
7
|
+
|
|
8
|
+
export async function getRandomBytes(buf: Uint8Array): Promise<Uint8Array> {
|
|
9
|
+
return crypto.getRandomValues(buf)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
13
|
+
return Array.from(bytes)
|
|
14
|
+
.map(d => d.toString(16).padStart(2, '0'))
|
|
15
|
+
.join('')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// adapted from:
|
|
19
|
+
|
|
20
|
+
// https://stackoverflow.com/questions/43131242/how-to-convert-a-hexadecimal-string-of-data-to-an-arraybuffer-in-javascript
|
|
21
|
+
export function bytesFromHex(hex: string): Uint8Array {
|
|
22
|
+
if (hex.length === 0) {
|
|
23
|
+
return new Uint8Array()
|
|
24
|
+
}
|
|
25
|
+
// @ts-expect-error - match() is non-null for validated hex input
|
|
26
|
+
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16)))
|
|
27
|
+
}
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Node.js support
|
|
2
|
+
import * as crypto from 'crypto'
|
|
3
|
+
import { promisify } from 'util'
|
|
4
|
+
|
|
5
|
+
const randomFill = promisify(crypto.randomFill)
|
|
6
|
+
|
|
7
|
+
export async function getRandomBytes(buf: Uint8Array): Promise<unknown> {
|
|
8
|
+
return await randomFill(buf)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
12
|
+
return Buffer.from(bytes).toString('hex')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function bytesFromHex(hex: string): Uint8Array {
|
|
16
|
+
return new Uint8Array(Buffer.from(hex, 'hex'))
|
|
17
|
+
}
|