import type { RangeOf, Remainders } from "./types";
import { BUFFER_EMPTY, uintToBuffer } from "@ganache/utils";
export declare type Input = Buffer | Buffer[] | List;
export interface List extends Array {}
export declare type EncodingInput = Buffer[] | EncodingList;
export interface EncodingList extends Array {}
export type EncodedPart = {
length: number;
output: Buffer[];
};
export type NestedBuffer = Array;
export interface Decoded {
data: T;
remainder: Buffer;
}
/**
* Begin RLP encoding of `items`, from `start` until `length`. Call `RLP.digest` to
* finish encoding.
*
**/
export function encodeRange<
T extends EncodingInput | Readonly,
Start extends RangeOf
>(
items: T,
start: Start,
length: Exclude, 0>
): EncodedPart {
let count = 0;
const end = start + length;
const output: Buffer[] = [];
for (var i = start; i < end; i++) {
const item = items[i];
const encoded = encode(item);
count += encoded.length;
output.push(encoded);
}
return { length: count, output };
}
/**
* Finishes encoding started by `encodeRange`.
*
* @param ranges -
* @returns returns a Buffer of encoded data
*/
export function digest(ranges: Readonly[], length: number) {
const encodedLength = encodeLength(length, 192);
const lengthEncodedLength = encodedLength.length;
const buf = Buffer.allocUnsafe(lengthEncodedLength + length);
encodedLength.copy(buf, 0, 0, lengthEncodedLength);
let offset = lengthEncodedLength;
for (let i = 0, l = ranges.length; i < l; i++) {
const range = ranges[i];
for (let j = 0, m = range.length; j < m; j++) {
const entry = range[j];
const size = entry.length;
entry.copy(buf, offset, 0, size);
offset += size;
}
}
return buf;
}
/**
* RLP Encoding based on: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP
* @param input -
* @returns returns a Buffer of encoded data
**/
export function encode(input: Input | Readonly): Buffer {
if (Array.isArray(input)) {
let length = 0;
const output: Buffer[] = [];
for (let i = 0, l = input.length; i < l; i++) {
const enc = encode(input[i]);
length += enc.length;
output.push(enc);
}
const buf = Buffer.concat(output, length);
const encodedLength = encodeLength(length, 192);
return Buffer.concat([encodedLength, buf], encodedLength.length + length);
} else {
if (input == null) {
const buf = Buffer.allocUnsafe(1);
buf[0] = 128;
return buf;
} else {
const length = input.length;
if (length === 1 && input[0] < 128) {
return input as Buffer;
} else {
const encLength = encodeLength(length, 128);
return Buffer.concat(
[encLength, input as Buffer],
encLength.length + length
);
}
}
}
}
export function encodeLength(len: number, offset: number): Buffer {
if (len < 56) {
const buf = Buffer.allocUnsafe(1);
buf[0] = len + offset;
return buf;
} else {
const hexLength = uintToBuffer(len);
const lLength = hexLength.length;
const firstByte = uintToBuffer(offset + 55 + lLength);
return Buffer.concat([firstByte, hexLength], firstByte.length + lLength);
}
}
/**
* Slices a Buffer, throws if the slice goes out-of-bounds of the Buffer.
* E.g. `safeSlice(hexToBytes('aa'), 1, 2)` will throw.
* @param input
* @param start
* @param end
*/
function safeSlice(input: Buffer, start: number, end: number) {
if (end > input.length) {
throw new Error(
"invalid RLP (safeSlice): end slice of Buffer out-of-bounds"
);
}
return input.slice(start, end);
}
/**
* RLP Decoding based on https://eth.wiki/en/fundamentals/rlp
* @param input Will be converted to Buffer
* @returns decoded Array of Buffers containing the original message
**/
export function decode(
input: Buffer
): T {
if (!input || input.length === 0) {
return BUFFER_EMPTY as T;
}
const decoded = _decode(input);
if (decoded.remainder.length !== 0) {
throw new Error("invalid RLP: remainder must be zero");
}
return decoded.data;
}
/**
* Parse integers. Check if there is no leading zeros
* @param v The value to parse
*/
function decodeLength(v: Buffer): number {
if (v[0] === 0) {
throw new Error("invalid RLP: extra zeros");
}
return parseHexByte(bytesToHex(v));
}
/** Decode an input with RLP */
function _decode(input: Buffer): Decoded {
let length: number,
llength: number,
data: T,
innerRemainder: Buffer,
d: Decoded;
const decoded = [];
const firstByte = input[0];
if (firstByte <= 0x7f) {
// a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding.
return {
data: input.slice(0, 1) as unknown as T,
remainder: input.slice(1)
};
} else if (firstByte <= 0xb7) {
// string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string
// The range of the first byte is [0x80, 0xb7]
length = firstByte - 0x7f;
// set 0x80 null to 0
if (firstByte === 0x80) {
data = Buffer.from([]) as unknown as T;
} else {
data = safeSlice(input, 1, length) as unknown as T;
}
if (length === 2 && data[0] < 0x80) {
throw new Error(
"invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed"
);
}
return {
data,
remainder: input.slice(length)
};
} else if (firstByte <= 0xbf) {
// string is greater than 55 bytes long. A single byte with the value (0xb7 plus the length of the length),
// followed by the length, followed by the string
llength = firstByte - 0xb6;
if (input.length - 1 < llength) {
throw new Error("invalid RLP: not enough bytes for string length");
}
length = decodeLength(safeSlice(input, 1, llength));
if (length <= 55) {
throw new Error(
"invalid RLP: expected string length to be greater than 55"
);
}
data = safeSlice(input, llength, length + llength) as unknown as T;
return {
data,
remainder: input.slice(length + llength)
};
} else if (firstByte <= 0xf7) {
// a list between 0-55 bytes long
length = firstByte - 0xbf;
innerRemainder = safeSlice(input, 1, length);
while (innerRemainder.length) {
d = _decode(innerRemainder);
decoded.push(d.data);
innerRemainder = d.remainder;
}
return {
data: decoded as unknown as T,
remainder: input.slice(length)
};
} else {
// a list over 55 bytes long
llength = firstByte - 0xf6;
length = decodeLength(safeSlice(input, 1, llength));
if (length < 56) {
throw new Error("invalid RLP: encoded list too short");
}
const totalLength = llength + length;
if (totalLength > input.length) {
throw new Error("invalid RLP: total length is larger than the data");
}
innerRemainder = safeSlice(input, llength, totalLength);
while (innerRemainder.length) {
d = _decode(innerRemainder);
decoded.push(d.data);
innerRemainder = d.remainder;
}
return {
data: decoded as unknown as T,
remainder: input.slice(totalLength)
};
}
}
const cachedHexes = Array.from({ length: 256 }, (_v, i) =>
i.toString(16).padStart(2, "0")
);
function bytesToHex(uint8a: Buffer): string {
// Pre-caching chars with `cachedHexes` speeds this up 6x
let hex = "";
for (let i = 0; i < uint8a.length; i++) {
hex += cachedHexes[uint8a[i]];
}
return hex;
}
function parseHexByte(hexByte: string): number {
const byte = Number.parseInt(hexByte, 16);
if (Number.isNaN(byte)) throw new Error("Invalid byte sequence");
return byte;
}