@feelyourprotocol/rlp 8141.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/LICENSE +373 -0
- package/README.md +89 -0
- package/bin/rlp.cjs +58 -0
- package/dist/cjs/errors.d.ts +39 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +44 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.d.ts +42 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +300 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/esm/errors.d.ts +39 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +39 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +42 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +280 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
- package/package.json +78 -0
- package/src/errors.ts +57 -0
- package/src/index.ts +327 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic EthereumJS error class with metadata attached
|
|
3
|
+
*
|
|
4
|
+
* Kudos to https://github.com/ChainSafe/lodestar monorepo
|
|
5
|
+
* for the inspiration :-)
|
|
6
|
+
* See: https://github.com/ChainSafe/lodestar/blob/unstable/packages/utils/src/errors.ts
|
|
7
|
+
*/
|
|
8
|
+
export type EthereumJSErrorMetaData = Record<string, string | number | null>
|
|
9
|
+
export type EthereumJSErrorObject = {
|
|
10
|
+
message: string
|
|
11
|
+
stack: string
|
|
12
|
+
className: string
|
|
13
|
+
type: EthereumJSErrorMetaData
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// In order to update all our errors to use `EthereumJSError`, temporarily include the
|
|
17
|
+
// unset error code. All errors throwing this code should be updated to use the relevant
|
|
18
|
+
// error code.
|
|
19
|
+
export const DEFAULT_ERROR_CODE = 'ETHEREUMJS_DEFAULT_ERROR_CODE'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generic EthereumJS error with attached metadata
|
|
23
|
+
*/
|
|
24
|
+
export class EthereumJSError<T extends { code: string }> extends Error {
|
|
25
|
+
type: T
|
|
26
|
+
constructor(type: T, message?: string, stack?: string) {
|
|
27
|
+
super(message ?? type.code)
|
|
28
|
+
this.type = type
|
|
29
|
+
if (stack !== undefined) this.stack = stack
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getMetadata(): EthereumJSErrorMetaData {
|
|
33
|
+
return this.type
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the metadata and the stacktrace for the error.
|
|
38
|
+
*/
|
|
39
|
+
toObject(): EthereumJSErrorObject {
|
|
40
|
+
return {
|
|
41
|
+
type: this.getMetadata(),
|
|
42
|
+
message: this.message ?? '',
|
|
43
|
+
stack: this.stack ?? '',
|
|
44
|
+
className: this.constructor.name,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @deprecated Use `EthereumJSError` with a set error code instead
|
|
51
|
+
* @param message Optional error message
|
|
52
|
+
* @param stack Optional stack trace
|
|
53
|
+
* @returns
|
|
54
|
+
*/
|
|
55
|
+
export function EthereumJSErrorWithoutCode(message?: string, stack?: string) {
|
|
56
|
+
return new EthereumJSError({ code: DEFAULT_ERROR_CODE }, message, stack)
|
|
57
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { EthereumJSErrorWithoutCode } from './errors.ts'
|
|
2
|
+
|
|
3
|
+
export * from './errors.ts'
|
|
4
|
+
|
|
5
|
+
export type Input = string | number | bigint | Uint8Array | Array<Input> | null | undefined
|
|
6
|
+
|
|
7
|
+
export type NestedUint8Array = Array<Uint8Array | NestedUint8Array>
|
|
8
|
+
|
|
9
|
+
export interface Decoded {
|
|
10
|
+
data: Uint8Array | NestedUint8Array
|
|
11
|
+
remainder: Uint8Array
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse integers. Check if there is no leading zeros
|
|
16
|
+
* @param v The value to parse
|
|
17
|
+
*/
|
|
18
|
+
function decodeLength(v: Uint8Array): number {
|
|
19
|
+
if (v[0] === 0) {
|
|
20
|
+
throw EthereumJSErrorWithoutCode('invalid RLP: extra zeros')
|
|
21
|
+
}
|
|
22
|
+
return parseHexByte(bytesToHex(v))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function encodeLength(len: number, offset: number): Uint8Array {
|
|
26
|
+
if (len < 56) {
|
|
27
|
+
return Uint8Array.from([len + offset])
|
|
28
|
+
}
|
|
29
|
+
const hexLength = numberToHex(len)
|
|
30
|
+
const lLength = hexLength.length / 2
|
|
31
|
+
const firstByte = numberToHex(offset + 55 + lLength)
|
|
32
|
+
return Uint8Array.from(hexToBytes(firstByte + hexLength))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Slices a Uint8Array, throws if the slice goes out-of-bounds of the Uint8Array.
|
|
37
|
+
* E.g. `safeSlice(hexToBytes('aa'), 1, 2)` will throw.
|
|
38
|
+
* @param input
|
|
39
|
+
* @param start
|
|
40
|
+
* @param end
|
|
41
|
+
*/
|
|
42
|
+
function safeSlice(input: Uint8Array, start: number, end: number) {
|
|
43
|
+
if (end > input.length) {
|
|
44
|
+
throw EthereumJSErrorWithoutCode(
|
|
45
|
+
'invalid RLP (safeSlice): end slice of Uint8Array out-of-bounds',
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
return input.slice(start, end)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Decode an input with RLP */
|
|
52
|
+
function _decode(input: Uint8Array): Decoded {
|
|
53
|
+
let length: number, lLength: number, data: Uint8Array, innerRemainder: Uint8Array, d: Decoded
|
|
54
|
+
const decoded = []
|
|
55
|
+
const firstByte = input[0]
|
|
56
|
+
|
|
57
|
+
if (firstByte <= 0x7f) {
|
|
58
|
+
// a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding.
|
|
59
|
+
return {
|
|
60
|
+
data: input.slice(0, 1),
|
|
61
|
+
remainder: input.subarray(1),
|
|
62
|
+
}
|
|
63
|
+
} else if (firstByte <= 0xb7) {
|
|
64
|
+
// string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string
|
|
65
|
+
// The range of the first byte is [0x80, 0xb7]
|
|
66
|
+
length = firstByte - 0x7f
|
|
67
|
+
|
|
68
|
+
// set 0x80 null to 0
|
|
69
|
+
if (firstByte === 0x80) {
|
|
70
|
+
data = Uint8Array.from([])
|
|
71
|
+
} else {
|
|
72
|
+
data = safeSlice(input, 1, length)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (length === 2 && data[0] < 0x80) {
|
|
76
|
+
throw EthereumJSErrorWithoutCode(
|
|
77
|
+
'invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed',
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
data,
|
|
83
|
+
remainder: input.subarray(length),
|
|
84
|
+
}
|
|
85
|
+
} else if (firstByte <= 0xbf) {
|
|
86
|
+
// string is greater than 55 bytes long. A single byte with the value (0xb7 plus the length of the length),
|
|
87
|
+
// followed by the length, followed by the string
|
|
88
|
+
lLength = firstByte - 0xb6
|
|
89
|
+
if (input.length - 1 < lLength) {
|
|
90
|
+
throw EthereumJSErrorWithoutCode('invalid RLP: not enough bytes for string length')
|
|
91
|
+
}
|
|
92
|
+
length = decodeLength(safeSlice(input, 1, lLength))
|
|
93
|
+
if (length <= 55) {
|
|
94
|
+
throw EthereumJSErrorWithoutCode('invalid RLP: expected string length to be greater than 55')
|
|
95
|
+
}
|
|
96
|
+
data = safeSlice(input, lLength, length + lLength)
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
data,
|
|
100
|
+
remainder: input.subarray(length + lLength),
|
|
101
|
+
}
|
|
102
|
+
} else if (firstByte <= 0xf7) {
|
|
103
|
+
// a list between 0-55 bytes long
|
|
104
|
+
length = firstByte - 0xbf
|
|
105
|
+
innerRemainder = safeSlice(input, 1, length)
|
|
106
|
+
while (innerRemainder.length) {
|
|
107
|
+
d = _decode(innerRemainder)
|
|
108
|
+
decoded.push(d.data)
|
|
109
|
+
innerRemainder = d.remainder
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
data: decoded,
|
|
114
|
+
remainder: input.subarray(length),
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
// a list over 55 bytes long
|
|
118
|
+
lLength = firstByte - 0xf6
|
|
119
|
+
length = decodeLength(safeSlice(input, 1, lLength))
|
|
120
|
+
if (length < 56) {
|
|
121
|
+
throw EthereumJSErrorWithoutCode('invalid RLP: encoded list too short')
|
|
122
|
+
}
|
|
123
|
+
const totalLength = lLength + length
|
|
124
|
+
if (totalLength > input.length) {
|
|
125
|
+
throw EthereumJSErrorWithoutCode('invalid RLP: total length is larger than the data')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
innerRemainder = safeSlice(input, lLength, totalLength)
|
|
129
|
+
|
|
130
|
+
while (innerRemainder.length) {
|
|
131
|
+
d = _decode(innerRemainder)
|
|
132
|
+
decoded.push(d.data)
|
|
133
|
+
innerRemainder = d.remainder
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
data: decoded,
|
|
138
|
+
remainder: input.subarray(totalLength),
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const cachedHexes = Array.from({ length: 256 }, (_v, i) => i.toString(16).padStart(2, '0'))
|
|
144
|
+
function bytesToHex(uint8a: Uint8Array): string {
|
|
145
|
+
// Pre-caching chars with `cachedHexes` speeds this up 6x
|
|
146
|
+
let hex = ''
|
|
147
|
+
for (let i = 0; i < uint8a.length; i++) {
|
|
148
|
+
hex += cachedHexes[uint8a[i]]
|
|
149
|
+
}
|
|
150
|
+
return hex
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseHexByte(hexByte: string): number {
|
|
154
|
+
const byte = Number.parseInt(hexByte, 16)
|
|
155
|
+
if (Number.isNaN(byte)) throw EthereumJSErrorWithoutCode('Invalid byte sequence')
|
|
156
|
+
return byte
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Borrowed from @noble/curves to avoid dependency
|
|
160
|
+
// Original code here - https://github.com/paulmillr/noble-curves/blob/d0a8d2134c5737d9d0aa81be13581cd416ebdeb4/src/abstract/utils.ts#L63-L91
|
|
161
|
+
const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 } as const
|
|
162
|
+
function asciiToBase16(char: number): number | undefined {
|
|
163
|
+
if (char >= asciis._0 && char <= asciis._9) return char - asciis._0
|
|
164
|
+
if (char >= asciis._A && char <= asciis._F) return char - (asciis._A - 10)
|
|
165
|
+
if (char >= asciis._a && char <= asciis._f) return char - (asciis._a - 10)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @example hexToBytes('0xcafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
|
171
|
+
*/
|
|
172
|
+
export function hexToBytes(hex: string): Uint8Array {
|
|
173
|
+
if (hex.slice(0, 2) === '0x') hex = hex.slice(0, 2)
|
|
174
|
+
if (typeof hex !== 'string')
|
|
175
|
+
throw EthereumJSErrorWithoutCode('hex string expected, got ' + typeof hex)
|
|
176
|
+
const hl = hex.length
|
|
177
|
+
const al = hl / 2
|
|
178
|
+
if (hl % 2)
|
|
179
|
+
throw EthereumJSErrorWithoutCode('padded hex string expected, got unpadded hex of length ' + hl)
|
|
180
|
+
const array = new Uint8Array(al)
|
|
181
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
182
|
+
const n1 = asciiToBase16(hex.charCodeAt(hi))
|
|
183
|
+
const n2 = asciiToBase16(hex.charCodeAt(hi + 1))
|
|
184
|
+
if (n1 === undefined || n2 === undefined) {
|
|
185
|
+
const char = hex[hi] + hex[hi + 1]
|
|
186
|
+
throw EthereumJSErrorWithoutCode(
|
|
187
|
+
'hex string expected, got non-hex character "' + char + '" at index ' + hi,
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
array[ai] = n1 * 16 + n2
|
|
191
|
+
}
|
|
192
|
+
return array
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Concatenates two Uint8Arrays into one. */
|
|
196
|
+
function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
|
197
|
+
if (arrays.length === 1) return arrays[0]
|
|
198
|
+
const length = arrays.reduce((a, arr) => a + arr.length, 0)
|
|
199
|
+
const result = new Uint8Array(length)
|
|
200
|
+
for (let i = 0, pad = 0; i < arrays.length; i++) {
|
|
201
|
+
const arr = arrays[i]
|
|
202
|
+
result.set(arr, pad)
|
|
203
|
+
pad += arr.length
|
|
204
|
+
}
|
|
205
|
+
return result
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Global symbols in both browsers and Node.js since v11
|
|
209
|
+
// See https://github.com/microsoft/TypeScript/issues/31535
|
|
210
|
+
declare const TextEncoder: any
|
|
211
|
+
|
|
212
|
+
function utf8ToBytes(utf: string): Uint8Array {
|
|
213
|
+
return new TextEncoder().encode(utf)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Transform an integer into its hexadecimal value */
|
|
217
|
+
function numberToHex(integer: number | bigint): string {
|
|
218
|
+
if (integer < 0) {
|
|
219
|
+
throw EthereumJSErrorWithoutCode('Invalid integer as argument, must be unsigned!')
|
|
220
|
+
}
|
|
221
|
+
const hex = integer.toString(16)
|
|
222
|
+
return hex.length % 2 ? `0${hex}` : hex
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Pad a string to be even */
|
|
226
|
+
function padToEven(a: string): string {
|
|
227
|
+
return a.length % 2 ? `0${a}` : a
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Check if a string is prefixed by 0x */
|
|
231
|
+
function isHexString(str: string): boolean {
|
|
232
|
+
return str.length >= 2 && str[0] === '0' && str[1] === 'x'
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Removes 0x from a given String */
|
|
236
|
+
function stripHexPrefix(str: string): string {
|
|
237
|
+
if (typeof str !== 'string') {
|
|
238
|
+
return str
|
|
239
|
+
}
|
|
240
|
+
return isHexString(str) ? str.slice(2) : str
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Transform anything into a Uint8Array */
|
|
244
|
+
function toBytes(v: Input): Uint8Array {
|
|
245
|
+
if (v instanceof Uint8Array) {
|
|
246
|
+
return v
|
|
247
|
+
}
|
|
248
|
+
if (typeof v === 'string') {
|
|
249
|
+
if (isHexString(v)) {
|
|
250
|
+
return hexToBytes(padToEven(stripHexPrefix(v)))
|
|
251
|
+
}
|
|
252
|
+
return utf8ToBytes(v)
|
|
253
|
+
}
|
|
254
|
+
if (typeof v === 'number' || typeof v === 'bigint') {
|
|
255
|
+
if (!v) {
|
|
256
|
+
return Uint8Array.from([])
|
|
257
|
+
}
|
|
258
|
+
return hexToBytes(numberToHex(v))
|
|
259
|
+
}
|
|
260
|
+
if (v === null || v === undefined) {
|
|
261
|
+
return Uint8Array.from([])
|
|
262
|
+
}
|
|
263
|
+
throw EthereumJSErrorWithoutCode('toBytes: received unsupported type ' + typeof v)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* RLP Encoding based on https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
|
|
268
|
+
* This function takes in data, converts it to Uint8Array if not,
|
|
269
|
+
* and adds a length for recursion.
|
|
270
|
+
* @param input Will be converted to Uint8Array
|
|
271
|
+
* @returns Uint8Array of encoded data
|
|
272
|
+
**/
|
|
273
|
+
export function encode(input: Input): Uint8Array {
|
|
274
|
+
if (Array.isArray(input)) {
|
|
275
|
+
const output: Uint8Array[] = []
|
|
276
|
+
let outputLength = 0
|
|
277
|
+
for (let i = 0; i < input.length; i++) {
|
|
278
|
+
const encoded = encode(input[i])
|
|
279
|
+
output.push(encoded)
|
|
280
|
+
outputLength += encoded.length
|
|
281
|
+
}
|
|
282
|
+
return concatBytes(encodeLength(outputLength, 192), ...output)
|
|
283
|
+
}
|
|
284
|
+
const inputBuf = toBytes(input)
|
|
285
|
+
if (inputBuf.length === 1 && inputBuf[0] < 128) {
|
|
286
|
+
return inputBuf
|
|
287
|
+
}
|
|
288
|
+
return concatBytes(encodeLength(inputBuf.length, 128), inputBuf)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* RLP Decoding based on https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
|
|
293
|
+
* @param input Will be converted to Uint8Array
|
|
294
|
+
* @param stream Is the input a stream (false by default)
|
|
295
|
+
* @returns decoded Array of Uint8Arrays containing the original message
|
|
296
|
+
**/
|
|
297
|
+
export function decode(input: Input, stream?: false): Uint8Array | NestedUint8Array
|
|
298
|
+
export function decode(input: Input, stream?: true): Decoded
|
|
299
|
+
export function decode(input: Input, stream = false): Uint8Array | NestedUint8Array | Decoded {
|
|
300
|
+
if (typeof input === 'undefined' || input === null || (input as any).length === 0) {
|
|
301
|
+
return Uint8Array.from([])
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const inputBytes = toBytes(input)
|
|
305
|
+
const decoded = _decode(inputBytes)
|
|
306
|
+
|
|
307
|
+
if (stream) {
|
|
308
|
+
return {
|
|
309
|
+
data: decoded.data,
|
|
310
|
+
remainder: decoded.remainder.slice(),
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (decoded.remainder.length !== 0) {
|
|
314
|
+
throw EthereumJSErrorWithoutCode('invalid RLP: remainder must be zero')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return decoded.data
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export const utils = {
|
|
321
|
+
bytesToHex,
|
|
322
|
+
concatBytes,
|
|
323
|
+
hexToBytes,
|
|
324
|
+
utf8ToBytes,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export const RLP = { encode, decode }
|