Skip to content

Instantly share code, notes, and snippets.

@markodayan
Last active August 22, 2022 03:03
Show Gist options
  • Save markodayan/32dc85c28593c5174390954f752724b3 to your computer and use it in GitHub Desktop.
Save markodayan/32dc85c28593c5174390954f752724b3 to your computer and use it in GitHub Desktop.
RLP encoding algorithm implemented in TypeScript
type Input = string | number | bigint | Uint8Array | Array<Input> | null | undefined | any;
function encode(input: Input): Uint8Array {
/* (1) Non-value input */
if (input === '' || input === false || input === null) {
const value = parseInt('0x80', 16);
return Uint8Array.from([value]);
}
/* (2) Empty list input */
if (input === []) {
const value = parseInt('0xc0', 16);
return Uint8Array.from([value]);
}
/* For decimal value inputs */
if (typeof input === 'number') {
if (input < 0) {
throw new Error('Integer must be unsigned (provide decimal value greater than or equal to 0');
}
if (input === 0) {
const value = parseInt('0x80', 16);
return Uint8Array.from([value]);
}
/* (3) A single byte with value within range [0x00, 0x7f] as input */
if (input <= 127) {
return Uint8Array.from([input]);
}
if (input > 127) {
const hexStr = input.toString(16);
const byteArr = hexStringToByteArr(hexStr);
const first = parseInt('0x80', 16) + byteArr.length;
return Uint8Array.from([first, ...byteArr]);
}
}
/* true boolean input */
if (input === true) {
const value = parseInt('0x01', 16);
return Uint8Array.from([value]);
}
/* For hexadecimal escape sequence inputs */
if (isEscapedFormat(input)) {
// @ts-ignore
const payload: any[] = (input as string)
.split('')
.reduce((acc, v) => acc + encodeURI(v).slice(1), '')
.match(/.{1,2}/g);
if (payload.length === 1) {
const value = parseInt(payload[0], 16);
return Uint8Array.from([value]);
}
if (payload.length <= 55) {
const first = parseInt('0x80', 16) + payload.length;
return Uint8Array.from([first, ...payload]);
}
}
/* For hexadecimal strings prefixed by '0x' */
if (typeof input === 'string' && input.startsWith('0x')) {
const payload = stripHexPrefix(input);
// If odd number of digits in hexadecimal string -> append '0' prefix
const padded = payload.length % 2 === 0 ? payload : '0' + payload;
// Create array of hex values where each element is prefixed by '0x' (we do this so byte array can convert these hex values to decimal byte values in the return statement)
const hexArr: any[] = padded.match(/.{1,2}/g)!.map((x) => '0x' + x);
// This is for hexadecimal strings with length greater than 55 bytes
if (hexArr.length > 55) {
const lengthInHex = hexArr.length.toString(16);
const bytesToStoreLengthInHex = hexStringToByteArr(lengthInHex);
const first = parseInt('0xb7', 16) + bytesToStoreLengthInHex.length;
return Uint8Array.from([first, ...bytesToStoreLengthInHex, ...hexArr]);
}
const first = parseInt('0x80', 16) + hexArr.length;
return Uint8Array.from([first, ...hexArr]);
}
/* (4) Input is string with length of 1 byte */
if (typeof input === 'string' && input.length === 1) {
const value = input.charCodeAt(0);
return Uint8Array.from([value]);
}
/* (5) Input is string between 2-55 bytes in length */
if (typeof input === 'string' && input.length <= 55) {
const first = parseInt('0x80', 16) + input.length;
const encoded = input.split('').map((c) => c.charCodeAt(0));
return Uint8Array.from([first, ...encoded]);
}
/* (6) Input is string greater than 55 bytes in length */
if (typeof input === 'string' && input.length > 55) {
const lengthInHex = stripHexPrefix(input).length.toString(16);
const bytesToStoreLengthInHex = hexStringToByteArr(lengthInHex);
const first = parseInt('0xb7', 16) + bytesToStoreLengthInHex.length;
const encoded = input.split('').map((c) => c.charCodeAt(0));
return Uint8Array.from([first, ...bytesToStoreLengthInHex, ...encoded]);
}
if (Array.isArray(input)) {
const encoded = [];
let encodedLength = 0;
for (const item of input) {
const enc = encode(item);
encoded.push(...enc);
encodedLength += enc.length;
}
/* (7) Input is list with the sum of its RLP-encoded contents being between 1–55 bytes */
if (encodedLength <= 55) {
const first = parseInt('0xc0', 16) + encodedLength;
return Uint8Array.from([first, ...encoded]);
}
/* (8) Input is list with the sum of its RLP-encoded contents being greater than 55 bytes */
if (encodedLength > 55) {
const lengthInHex = encodedLength.toString(16);
const bytesToStoreLengthInHex = hexStringToByteArr(lengthInHex);
const first = parseInt('0xf7', 16) + bytesToStoreLengthInHex.length;
return Uint8Array.from([first, ...bytesToStoreLengthInHex, ...encoded]);
}
}
throw new Error(
'Unhandled input payload (if bigint payload, then encode method requires an update) - no encoding scheme available (perhaps stringify or encode as a list)'
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment