UNPKG

@ethereumjs/rlp

Version:
300 lines 10.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RLP = exports.utils = void 0; exports.hexToBytes = hexToBytes; exports.encode = encode; exports.decode = decode; const errors_ts_1 = require("./errors.js"); __exportStar(require("./errors.js"), exports); /** * Parse integers. Check if there is no leading zeros * @param v The value to parse */ function decodeLength(v) { if (v[0] === 0) { throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('invalid RLP: extra zeros'); } return parseHexByte(bytesToHex(v)); } function encodeLength(len, offset) { if (len < 56) { return Uint8Array.from([len + offset]); } const hexLength = numberToHex(len); const lLength = hexLength.length / 2; const firstByte = numberToHex(offset + 55 + lLength); return Uint8Array.from(hexToBytes(firstByte + hexLength)); } /** * Slices a Uint8Array, throws if the slice goes out-of-bounds of the Uint8Array. * E.g. `safeSlice(hexToBytes('aa'), 1, 2)` will throw. * @param input * @param start * @param end */ function safeSlice(input, start, end) { if (end > input.length) { throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('invalid RLP (safeSlice): end slice of Uint8Array out-of-bounds'); } return input.slice(start, end); } /** Decode an input with RLP */ function _decode(input) { let length, lLength, data, innerRemainder, d; 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), remainder: input.subarray(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 = Uint8Array.from([]); } else { data = safeSlice(input, 1, length); } if (length === 2 && data[0] < 0x80) { throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed'); } return { data, remainder: input.subarray(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 (0, errors_ts_1.EthereumJSErrorWithoutCode)('invalid RLP: not enough bytes for string length'); } length = decodeLength(safeSlice(input, 1, lLength)); if (length <= 55) { throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('invalid RLP: expected string length to be greater than 55'); } data = safeSlice(input, lLength, length + lLength); return { data, remainder: input.subarray(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, remainder: input.subarray(length), }; } else { // a list over 55 bytes long lLength = firstByte - 0xf6; length = decodeLength(safeSlice(input, 1, lLength)); if (length < 56) { throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('invalid RLP: encoded list too short'); } const totalLength = lLength + length; if (totalLength > input.length) { throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('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, remainder: input.subarray(totalLength), }; } } const cachedHexes = Array.from({ length: 256 }, (_v, i) => i.toString(16).padStart(2, '0')); function bytesToHex(uint8a) { // 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) { const byte = Number.parseInt(hexByte, 16); if (Number.isNaN(byte)) throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('Invalid byte sequence'); return byte; } // Borrowed from @noble/curves to avoid dependency // Original code here - https://github.com/paulmillr/noble-curves/blob/d0a8d2134c5737d9d0aa81be13581cd416ebdeb4/src/abstract/utils.ts#L63-L91 const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 }; function asciiToBase16(char) { if (char >= asciis._0 && char <= asciis._9) return char - asciis._0; if (char >= asciis._A && char <= asciis._F) return char - (asciis._A - 10); if (char >= asciis._a && char <= asciis._f) return char - (asciis._a - 10); return; } /** * @example hexToBytes('0xcafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23]) */ function hexToBytes(hex) { if (hex.slice(0, 2) === '0x') hex = hex.slice(0, 2); if (typeof hex !== 'string') throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('hex string expected, got ' + typeof hex); const hl = hex.length; const al = hl / 2; if (hl % 2) throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('padded hex string expected, got unpadded hex of length ' + hl); const array = new Uint8Array(al); for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { const n1 = asciiToBase16(hex.charCodeAt(hi)); const n2 = asciiToBase16(hex.charCodeAt(hi + 1)); if (n1 === undefined || n2 === undefined) { const char = hex[hi] + hex[hi + 1]; throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('hex string expected, got non-hex character "' + char + '" at index ' + hi); } array[ai] = n1 * 16 + n2; } return array; } /** Concatenates two Uint8Arrays into one. */ function concatBytes(...arrays) { if (arrays.length === 1) return arrays[0]; const length = arrays.reduce((a, arr) => a + arr.length, 0); const result = new Uint8Array(length); for (let i = 0, pad = 0; i < arrays.length; i++) { const arr = arrays[i]; result.set(arr, pad); pad += arr.length; } return result; } function utf8ToBytes(utf) { return new TextEncoder().encode(utf); } /** Transform an integer into its hexadecimal value */ function numberToHex(integer) { if (integer < 0) { throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('Invalid integer as argument, must be unsigned!'); } const hex = integer.toString(16); return hex.length % 2 ? `0${hex}` : hex; } /** Pad a string to be even */ function padToEven(a) { return a.length % 2 ? `0${a}` : a; } /** Check if a string is prefixed by 0x */ function isHexString(str) { return str.length >= 2 && str[0] === '0' && str[1] === 'x'; } /** Removes 0x from a given String */ function stripHexPrefix(str) { if (typeof str !== 'string') { return str; } return isHexString(str) ? str.slice(2) : str; } /** Transform anything into a Uint8Array */ function toBytes(v) { if (v instanceof Uint8Array) { return v; } if (typeof v === 'string') { if (isHexString(v)) { return hexToBytes(padToEven(stripHexPrefix(v))); } return utf8ToBytes(v); } if (typeof v === 'number' || typeof v === 'bigint') { if (!v) { return Uint8Array.from([]); } return hexToBytes(numberToHex(v)); } if (v === null || v === undefined) { return Uint8Array.from([]); } throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('toBytes: received unsupported type ' + typeof v); } /** * RLP Encoding based on https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ * This function takes in data, converts it to Uint8Array if not, * and adds a length for recursion. * @param input Will be converted to Uint8Array * @returns Uint8Array of encoded data **/ function encode(input) { if (Array.isArray(input)) { const output = []; let outputLength = 0; for (let i = 0; i < input.length; i++) { const encoded = encode(input[i]); output.push(encoded); outputLength += encoded.length; } return concatBytes(encodeLength(outputLength, 192), ...output); } const inputBuf = toBytes(input); if (inputBuf.length === 1 && inputBuf[0] < 128) { return inputBuf; } return concatBytes(encodeLength(inputBuf.length, 128), inputBuf); } function decode(input, stream = false) { if (typeof input === 'undefined' || input === null || input.length === 0) { return Uint8Array.from([]); } const inputBytes = toBytes(input); const decoded = _decode(inputBytes); if (stream) { return { data: decoded.data, remainder: decoded.remainder.slice(), }; } if (decoded.remainder.length !== 0) { throw (0, errors_ts_1.EthereumJSErrorWithoutCode)('invalid RLP: remainder must be zero'); } return decoded.data; } exports.utils = { bytesToHex, concatBytes, hexToBytes, utf8ToBytes, }; exports.RLP = { encode, decode }; //# sourceMappingURL=index.js.map