Comparing version 2.2.7 to 3.0.0
@@ -1,25 +0,38 @@ | ||
/// <reference types="node" /> | ||
import { Decoded, Input, List } from './types'; | ||
export { Decoded, Input, List }; | ||
export declare type Input = string | number | bigint | Uint8Array | Array<Input> | null | undefined; | ||
export declare type NestedUint8Array = Array<Uint8Array | NestedUint8Array>; | ||
export interface Decoded { | ||
data: Uint8Array | NestedUint8Array; | ||
remainder: Uint8Array; | ||
} | ||
/** | ||
* RLP Encoding based on: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP | ||
* This function takes in a data, convert it to buffer if not, and a length for recursion | ||
* @param input - will be converted to buffer | ||
* @returns returns buffer of encoded data | ||
* RLP Encoding based on https://eth.wiki/en/fundamentals/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 | ||
**/ | ||
export declare function encode(input: Input): Buffer; | ||
export declare function encode(input: Input): Uint8Array; | ||
/** | ||
* RLP Decoding based on: {@link https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP|RLP} | ||
* @param input - will be converted to buffer | ||
* @param stream - Is the input a stream (false by default) | ||
* @returns - returns decode Array of Buffers containg the original message | ||
* RLP Decoding based on https://eth.wiki/en/fundamentals/rlp | ||
* @param input Will be converted to Uint8Array | ||
* @param stream Is the input a stream (false by default) | ||
* @returns decoded Array of Uint8Arrays containing the original message | ||
**/ | ||
export declare function decode(input: Buffer, stream?: boolean): Buffer; | ||
export declare function decode(input: Buffer[], stream?: boolean): Buffer[]; | ||
export declare function decode(input: Input, stream?: boolean): Buffer[] | Buffer | Decoded; | ||
/** | ||
* Get the length of the RLP input | ||
* @param input | ||
* @returns The length of the input or an empty Buffer if no input | ||
*/ | ||
export declare function getLength(input: Input): Buffer | number; | ||
export declare function decode(input: Input, stream?: false): Uint8Array | NestedUint8Array; | ||
export declare function decode(input: Input, stream?: true): Decoded; | ||
declare function bytesToHex(uint8a: Uint8Array): string; | ||
declare function hexToBytes(hex: string): Uint8Array; | ||
/** Concatenates two Uint8Arrays into one. */ | ||
declare function concatBytes(...arrays: Uint8Array[]): Uint8Array; | ||
declare function utf8ToBytes(utf: string): Uint8Array; | ||
export declare const utils: { | ||
bytesToHex: typeof bytesToHex; | ||
concatBytes: typeof concatBytes; | ||
hexToBytes: typeof hexToBytes; | ||
utf8ToBytes: typeof utf8ToBytes; | ||
}; | ||
declare const RLP: { | ||
encode: typeof encode; | ||
decode: typeof decode; | ||
}; | ||
export default RLP; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getLength = exports.decode = exports.encode = void 0; | ||
const bn_js_1 = __importDefault(require("bn.js")); | ||
exports.utils = exports.decode = exports.encode = void 0; | ||
/** | ||
* RLP Encoding based on: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP | ||
* This function takes in a data, convert it to buffer if not, and a length for recursion | ||
* @param input - will be converted to buffer | ||
* @returns returns buffer of encoded data | ||
* RLP Encoding based on https://eth.wiki/en/fundamentals/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 | ||
**/ | ||
@@ -20,41 +17,50 @@ function encode(input) { | ||
} | ||
const buf = Buffer.concat(output); | ||
return Buffer.concat([encodeLength(buf.length, 192), buf]); | ||
const buf = concatBytes(...output); | ||
return concatBytes(encodeLength(buf.length, 192), buf); | ||
} | ||
else { | ||
const inputBuf = toBuffer(input); | ||
return inputBuf.length === 1 && inputBuf[0] < 128 | ||
? inputBuf | ||
: Buffer.concat([encodeLength(inputBuf.length, 128), inputBuf]); | ||
const inputBuf = toBytes(input); | ||
if (inputBuf.length === 1 && inputBuf[0] < 128) { | ||
return inputBuf; | ||
} | ||
return concatBytes(encodeLength(inputBuf.length, 128), inputBuf); | ||
} | ||
exports.encode = encode; | ||
/** | ||
* 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 new Error('invalid RLP (safeSlice): end slice of Uint8Array out-of-bounds'); | ||
} | ||
return input.slice(start, end); | ||
} | ||
/** | ||
* Parse integers. Check if there is no leading zeros | ||
* @param v The value to parse | ||
* @param base The base to parse the integer into | ||
*/ | ||
function safeParseInt(v, base) { | ||
if (v[0] === '0' && v[1] === '0') { | ||
function decodeLength(v) { | ||
if (v[0] === 0) { | ||
throw new Error('invalid RLP: extra zeros'); | ||
} | ||
return parseInt(v, base); | ||
return parseHexByte(bytesToHex(v)); | ||
} | ||
function encodeLength(len, offset) { | ||
if (len < 56) { | ||
return Buffer.from([len + offset]); | ||
return Uint8Array.from([len + offset]); | ||
} | ||
else { | ||
const hexLength = intToHex(len); | ||
const lLength = hexLength.length / 2; | ||
const firstByte = intToHex(offset + 55 + lLength); | ||
return Buffer.from(firstByte + hexLength, 'hex'); | ||
} | ||
const hexLength = numberToHex(len); | ||
const lLength = hexLength.length / 2; | ||
const firstByte = numberToHex(offset + 55 + lLength); | ||
return Uint8Array.from(hexToBytes(firstByte + hexLength)); | ||
} | ||
function decode(input, stream = false) { | ||
if (!input || input.length === 0) { | ||
return Buffer.from([]); | ||
return Uint8Array.from([]); | ||
} | ||
const inputBuffer = toBuffer(input); | ||
const decoded = _decode(inputBuffer); | ||
const inputBytes = toBytes(input); | ||
const decoded = _decode(inputBytes); | ||
if (stream) { | ||
@@ -64,3 +70,3 @@ return decoded; | ||
if (decoded.remainder.length !== 0) { | ||
throw new Error('invalid remainder'); | ||
throw new Error('invalid RLP: remainder must be zero'); | ||
} | ||
@@ -70,34 +76,2 @@ return decoded.data; | ||
exports.decode = decode; | ||
/** | ||
* Get the length of the RLP input | ||
* @param input | ||
* @returns The length of the input or an empty Buffer if no input | ||
*/ | ||
function getLength(input) { | ||
if (!input || input.length === 0) { | ||
return Buffer.from([]); | ||
} | ||
const inputBuffer = toBuffer(input); | ||
const firstByte = inputBuffer[0]; | ||
if (firstByte <= 0x7f) { | ||
return inputBuffer.length; | ||
} | ||
else if (firstByte <= 0xb7) { | ||
return firstByte - 0x7f; | ||
} | ||
else if (firstByte <= 0xbf) { | ||
return firstByte - 0xb6; | ||
} | ||
else if (firstByte <= 0xf7) { | ||
// a list between 0-55 bytes long | ||
return firstByte - 0xbf; | ||
} | ||
else { | ||
// a list over 55 bytes long | ||
const llength = firstByte - 0xf6; | ||
const length = safeParseInt(inputBuffer.slice(1, llength).toString('hex'), 16); | ||
return llength + length; | ||
} | ||
} | ||
exports.getLength = getLength; | ||
/** Decode an input with RLP */ | ||
@@ -121,9 +95,9 @@ function _decode(input) { | ||
if (firstByte === 0x80) { | ||
data = Buffer.from([]); | ||
data = Uint8Array.from([]); | ||
} | ||
else { | ||
data = input.slice(1, length); | ||
data = safeSlice(input, 1, length); | ||
} | ||
if (length === 2 && data[0] < 0x80) { | ||
throw new Error('invalid rlp encoding: byte must be less 0x80'); | ||
throw new Error('invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed'); | ||
} | ||
@@ -142,10 +116,7 @@ return { | ||
} | ||
length = safeParseInt(input.slice(1, llength).toString('hex'), 16); | ||
length = decodeLength(safeSlice(input, 1, llength)); | ||
if (length <= 55) { | ||
throw new Error('invalid RLP: expected string length to be greater than 55'); | ||
} | ||
data = input.slice(llength, length + llength); | ||
if (data.length < length) { | ||
throw new Error('invalid RLP: not enough bytes for string'); | ||
} | ||
data = safeSlice(input, llength, length + llength); | ||
return { | ||
@@ -157,5 +128,5 @@ data: data, | ||
else if (firstByte <= 0xf7) { | ||
// a list between 0-55 bytes long | ||
// a list between 0-55 bytes long | ||
length = firstByte - 0xbf; | ||
innerRemainder = input.slice(1, length); | ||
innerRemainder = safeSlice(input, 1, length); | ||
while (innerRemainder.length) { | ||
@@ -172,13 +143,13 @@ d = _decode(innerRemainder); | ||
else { | ||
// a list over 55 bytes long | ||
// a list over 55 bytes long | ||
llength = firstByte - 0xf6; | ||
length = safeParseInt(input.slice(1, llength).toString('hex'), 16); | ||
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'); | ||
throw new Error('invalid RLP: total length is larger than the data'); | ||
} | ||
innerRemainder = input.slice(llength, totalLength); | ||
if (innerRemainder.length === 0) { | ||
throw new Error('invalid rlp, List has a invalid length'); | ||
} | ||
innerRemainder = safeSlice(input, llength, totalLength); | ||
while (innerRemainder.length) { | ||
@@ -195,15 +166,49 @@ d = _decode(innerRemainder); | ||
} | ||
/** Check if a string is prefixed by 0x */ | ||
function isHexPrefixed(str) { | ||
return str.slice(0, 2) === '0x'; | ||
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; | ||
} | ||
/** Removes 0x from a given String */ | ||
function stripHexPrefix(str) { | ||
if (typeof str !== 'string') { | ||
return str; | ||
function parseHexByte(hexByte) { | ||
const byte = Number.parseInt(hexByte, 16); | ||
if (Number.isNaN(byte)) | ||
throw new Error('Invalid byte sequence'); | ||
return byte; | ||
} | ||
// Caching slows it down 2-3x | ||
function hexToBytes(hex) { | ||
if (typeof hex !== 'string') { | ||
throw new TypeError('hexToBytes: expected string, got ' + typeof hex); | ||
} | ||
return isHexPrefixed(str) ? str.slice(2) : str; | ||
if (hex.length % 2) | ||
throw new Error('hexToBytes: received invalid unpadded hex'); | ||
const array = new Uint8Array(hex.length / 2); | ||
for (let i = 0; i < array.length; i++) { | ||
const j = i * 2; | ||
array[i] = parseHexByte(hex.slice(j, j + 2)); | ||
} | ||
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 intToHex(integer) { | ||
function numberToHex(integer) { | ||
if (integer < 0) { | ||
@@ -219,42 +224,43 @@ throw new Error('Invalid integer as argument, must be unsigned!'); | ||
} | ||
/** Transform an integer into a Buffer */ | ||
function intToBuffer(integer) { | ||
const hex = intToHex(integer); | ||
return Buffer.from(hex, 'hex'); | ||
/** Check if a string is prefixed by 0x */ | ||
function isHexPrefixed(str) { | ||
return str.length >= 2 && str[0] === '0' && str[1] === 'x'; | ||
} | ||
/** Transform anything into a Buffer */ | ||
function toBuffer(v) { | ||
if (!Buffer.isBuffer(v)) { | ||
if (typeof v === 'string') { | ||
if (isHexPrefixed(v)) { | ||
return Buffer.from(padToEven(stripHexPrefix(v)), 'hex'); | ||
} | ||
else { | ||
return Buffer.from(v); | ||
} | ||
/** Removes 0x from a given String */ | ||
function stripHexPrefix(str) { | ||
if (typeof str !== 'string') { | ||
return str; | ||
} | ||
return isHexPrefixed(str) ? str.slice(2) : str; | ||
} | ||
/** Transform anything into a Uint8Array */ | ||
function toBytes(v) { | ||
if (v instanceof Uint8Array) { | ||
return v; | ||
} | ||
if (typeof v === 'string') { | ||
if (isHexPrefixed(v)) { | ||
return hexToBytes(padToEven(stripHexPrefix(v))); | ||
} | ||
else if (typeof v === 'number' || typeof v === 'bigint') { | ||
if (!v) { | ||
return Buffer.from([]); | ||
} | ||
else { | ||
return intToBuffer(v); | ||
} | ||
return utf8ToBytes(v); | ||
} | ||
if (typeof v === 'number' || typeof v === 'bigint') { | ||
if (!v) { | ||
return Uint8Array.from([]); | ||
} | ||
else if (v === null || v === undefined) { | ||
return Buffer.from([]); | ||
} | ||
else if (v instanceof Uint8Array) { | ||
return Buffer.from(v); | ||
} | ||
else if (bn_js_1.default.isBN(v)) { | ||
// converts a BN to a Buffer | ||
return Buffer.from(v.toArray()); | ||
} | ||
else { | ||
throw new Error('invalid type'); | ||
} | ||
return hexToBytes(numberToHex(v)); | ||
} | ||
return v; | ||
if (v === null || v === undefined) { | ||
return Uint8Array.from([]); | ||
} | ||
throw new Error('toBytes: received unsupported type ' + typeof v); | ||
} | ||
exports.utils = { | ||
bytesToHex, | ||
concatBytes, | ||
hexToBytes, | ||
utf8ToBytes, | ||
}; | ||
const RLP = { encode, decode }; | ||
exports.default = RLP; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "rlp", | ||
"version": "2.2.7", | ||
"version": "3.0.0", | ||
"description": "Recursive Length Prefix Encoding Module", | ||
"license": "MPL-2.0", | ||
"keywords": [ | ||
"rlp", | ||
"ethereum" | ||
], | ||
"files": [ | ||
"dist", | ||
"dist.browser", | ||
"bin", | ||
"src" | ||
], | ||
"main": "./dist/index.js", | ||
"types": "./dist/index.d.ts", | ||
"browser": "dist.browser/index.js", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"bin": { | ||
"rlp": "./bin/rlp" | ||
"rlp": "bin/rlp" | ||
}, | ||
"scripts": { | ||
"build": "tsc -p ./tsconfig.prod.json && tsc -p tsconfig.browser.json", | ||
"prepublishOnly": "npm run test && npm run build", | ||
"coverage": "npm run build && nyc --reporter=lcov npm run test:unit", | ||
"tsc": "tsc -p ./tsconfig.prod.json --noEmit", | ||
"lint": "ethereumjs-config-lint", | ||
"lint:fix": "ethereumjs-config-lint-fix", | ||
"test": "npm run lint && npm run build && npm run test:unit && npm run test:browser", | ||
"test:unit": "mocha --reporter spec --require ts-node/register test/*.spec.ts", | ||
"build": "../../config/cli/ts-build.sh node", | ||
"prepublishOnly": "../../config/cli/prepublish.sh", | ||
"clean": "../../config/cli/clean-package.sh", | ||
"coverage": "../../config/cli/coverage.sh", | ||
"tsc": "../../config/cli/ts-compile.sh", | ||
"lint": "../../config/cli/lint.sh", | ||
"lint:fix": "../../config/cli/lint-fix.sh", | ||
"tape": "tape -r ts-node/register", | ||
"test": "npm run test:node && npm run test:browser", | ||
"test:node": "npm run tape -- test/*.spec.ts", | ||
"test:browser": "karma start karma.conf.js" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-push": "npm run lint" | ||
} | ||
"devDependencies": { | ||
"@types/node": "^16.11.7", | ||
"@types/tape": "^4.13.2", | ||
"karma": "^6.3.4", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-firefox-launcher": "^2.1.1", | ||
"karma-tap": "^4.2.0", | ||
"karma-typescript": "^5.5.3", | ||
"nyc": "^15.1.0", | ||
"prettier": "^2.4.1", | ||
"tape": "^5.3.1", | ||
"ts-node": "^10.2.1", | ||
"typescript": "^4.4.2" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/ethereumjs/rlp.git" | ||
}, | ||
"keywords": [ | ||
"rlp", | ||
"ethereum" | ||
], | ||
"author": { | ||
@@ -47,30 +53,13 @@ "name": "martin becze", | ||
"Alex Beregszaszi <alex@rtfs.hu>", | ||
"Holger Drewes <Holger.Drewes@gmail.com>" | ||
"Holger Drewes <Holger.Drewes@gmail.com>", | ||
"Paul Miller <pkg@paulmillr.com>" | ||
], | ||
"license": "MPL-2.0", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/ethereumjs/ethereumjs-monorepo.git" | ||
}, | ||
"homepage": "https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/rlp#readme", | ||
"bugs": { | ||
"url": "https://github.com/ethereumjs/rlp/issues" | ||
}, | ||
"dependencies": { | ||
"bn.js": "^5.2.0" | ||
}, | ||
"devDependencies": { | ||
"@ethereumjs/eslint-config-defaults": "^2.0.0", | ||
"@ethereumjs/config-coverage": "^2.0.0", | ||
"@ethereumjs/config-typescript": "^2.0.0", | ||
"@types/bn.js": "^5.1.0", | ||
"@types/mocha": "^9.0.0", | ||
"@types/node": "^12.13.0", | ||
"husky": "^4.2.5", | ||
"karma": "^6.3.4", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-firefox-launcher": "^2.1.1", | ||
"karma-mocha": "^2.0.1", | ||
"karma-typescript": "^5.5.2", | ||
"mocha": "7.1.2", | ||
"nyc": "^15.1.0", | ||
"prettier": "^2.4.1", | ||
"ts-node": "^10.2.1", | ||
"typescript": "^4.4.3" | ||
"url": "https://github.com/ethereumjs/ethereumjs-monorepo/issues?q=is%3Aissue+label%3A%22package%3A+rlp%22" | ||
} | ||
} |
@@ -1,9 +0,10 @@ | ||
# SYNOPSIS | ||
# rlp | ||
[![NPM Package](https://img.shields.io/npm/v/rlp.svg)](https://www.npmjs.org/package/rlp) | ||
[![Actions Status](https://github.com/ethereumjs/rlp/workflows/Build/badge.svg)](https://github.com/ethereumjs/rlp/actions) | ||
[![Coverage Status](https://img.shields.io/coveralls/ethereumjs/rlp.svg)](https://coveralls.io/r/ethereumjs/rlp) | ||
[![Discord](https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue)](https://discord.gg/TNwARpR) | ||
[![NPM Package][rlp-npm-badge]][rlp-npm-link] | ||
[![GitHub Issues][rlp-issues-badge]][rlp-issues-link] | ||
[![Actions Status][rlp-actions-badge]][rlp-actions-link] | ||
[![Code Coverage][rlp-coverage-badge]][rlp-coverage-link] | ||
[![Discord][discord-badge]][discord-link] | ||
[Recursive Length](https://github.com/ethereum/wiki/wiki/RLP) Prefix Encoding for Node.js. | ||
[Recursive Length Prefix](https://eth.wiki/en/fundamentals/rlp) encoding for Node.js and the browser. | ||
@@ -14,3 +15,3 @@ ## INSTALL | ||
install with `-g` if you want to use the cli. | ||
install with `-g` if you want to use the CLI. | ||
@@ -20,8 +21,8 @@ ## USAGE | ||
```typescript | ||
import * as assert from 'assert' | ||
import * as rlp from 'rlp' | ||
import assert from 'assert' | ||
import RLP from 'rlp' | ||
const nestedList = [[], [[]], [[], [[]]]] | ||
const encoded = rlp.encode(nestedList) | ||
const decoded = rlp.decode(encoded) | ||
const encoded = RLP.encode(nestedList) | ||
const decoded = RLP.decode(encoded) | ||
assert.deepEqual(nestedList, decoded) | ||
@@ -32,11 +33,35 @@ ``` | ||
`rlp.encode(plain)` - RLP encodes an `Array`, `Buffer` or `String` and returns a `Buffer`. | ||
`RLP.encode(plain)` - RLP encodes an `Array`, `Uint8Array` or `String` and returns a `Uint8Array`. | ||
`rlp.decode(encoded, [skipRemainderCheck=false])` - Decodes an RLP encoded `Buffer`, `Array` or `String` and returns a `Buffer` or an `Array` of `Buffers`. If `skipRemainderCheck` is enabled, `rlp` will just decode the first rlp sequence in the buffer. By default, it would throw an error if there are more bytes in Buffer than used by rlp sequence. | ||
`RLP.decode(encoded, [stream=false])` - Decodes an RLP encoded `Uint8Array`, `Array` or `String` and returns a `Uint8Array` or `NestedUint8Array`. If `stream` is enabled, it will just decode the first rlp sequence in the Uint8Array. By default, it would throw an error if there are more bytes in Uint8Array than used by the rlp sequence. | ||
### Buffer compatibility | ||
If you would like to continue using Buffers like in rlp v2, you can use: | ||
```typescript | ||
import assert from 'assert' | ||
import { arrToBufArr, bufArrToArr } from 'ethereumjs-util' | ||
import RLP from 'rlp' | ||
const bufferList = [Buffer.from('123', 'hex'), Buffer.from('456', 'hex')] | ||
const encoded = RLP.encode(bufArrToArr(bufferList)) | ||
const encodedAsBuffer = Buffer.from(encoded) | ||
const decoded = RLP.decode(Uint8Array.from(encodedAsBuffer)) // or RLP.decode(encoded) | ||
const decodedAsBuffers = arrToBufArr(decoded) | ||
assert.deepEqual(bufferList, decodedAsBuffers) | ||
``` | ||
## CLI | ||
`rlp decode <hex string>` | ||
`rlp encode <json String>` | ||
`rlp encode <JSON string>`\ | ||
`rlp decode <0x-prefixed hex string>` | ||
### Examples | ||
- `rlp encode '5'` -> `0x05` | ||
- `rlp encode '[5]'` -> `0xc105` | ||
- `rlp encode '["cat", "dog"]'` -> `0xc88363617483646f67` | ||
- `rlp decode 0xc88363617483646f67` -> `["cat","dog"]` | ||
## TESTS | ||
@@ -48,14 +73,11 @@ | ||
To auto fix linting problems use: `npm run lint:fix` | ||
To auto-fix linting problems run: `npm run lint:fix` | ||
## CODE COVERAGE | ||
Install dev dependencies | ||
`npm install` | ||
Install dev dependencies: `npm install` | ||
Run | ||
`npm run coverage` | ||
Run coverage: `npm run coverage` | ||
The results are at | ||
`coverage/lcov-report/index.html` | ||
The results will be at: `coverage/lcov-report/index.html` | ||
@@ -67,1 +89,12 @@ # EthereumJS | ||
If you want to join for work or do improvements on the libraries have a look at our [contribution guidelines](https://ethereumjs.readthedocs.io/en/latest/contributing.html). | ||
[discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue | ||
[discord-link]: https://discord.gg/TNwARpR | ||
[rlp-npm-badge]: https://img.shields.io/npm/v/rlp.svg | ||
[rlp-npm-link]: https://www.npmjs.com/package/rlp | ||
[rlp-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-monorepo/package:%20rlp?label=issues | ||
[rlp-issues-link]: https://github.com/ethereumjs/ethereumjs-monorepo/issues?q=is%3Aopen+is%3Aissue+label%3A"package%3A+rlp" | ||
[rlp-actions-badge]: https://github.com/ethereumjs/ethereumjs-monorepo/workflows/rlp/badge.svg | ||
[rlp-actions-link]: https://github.com/ethereumjs/ethereumjs-monorepo/actions?query=workflow%3A%22rlp%22 | ||
[rlp-coverage-badge]: https://codecov.io/gh/ethereumjs/ethereumjs-monorepo/branch/master/graph/badge.svg?flag=rlp | ||
[rlp-coverage-link]: https://codecov.io/gh/ethereumjs/ethereumjs-monorepo/tree/master/packages/rlp |
291
src/index.ts
@@ -1,70 +0,83 @@ | ||
import BN from 'bn.js' | ||
export type Input = string | number | bigint | Uint8Array | Array<Input> | null | undefined | ||
import { Decoded, Input, List } from './types' | ||
export type NestedUint8Array = Array<Uint8Array | NestedUint8Array> | ||
// Types exported outside of this package | ||
export { Decoded, Input, List } | ||
export interface Decoded { | ||
data: Uint8Array | NestedUint8Array | ||
remainder: Uint8Array | ||
} | ||
/** | ||
* RLP Encoding based on: https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP | ||
* This function takes in a data, convert it to buffer if not, and a length for recursion | ||
* @param input - will be converted to buffer | ||
* @returns returns buffer of encoded data | ||
* RLP Encoding based on https://eth.wiki/en/fundamentals/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 | ||
**/ | ||
export function encode(input: Input): Buffer { | ||
export function encode(input: Input): Uint8Array { | ||
if (Array.isArray(input)) { | ||
const output: Buffer[] = [] | ||
const output: Uint8Array[] = [] | ||
for (let i = 0; i < input.length; i++) { | ||
output.push(encode(input[i])) | ||
} | ||
const buf = Buffer.concat(output) | ||
return Buffer.concat([encodeLength(buf.length, 192), buf]) | ||
} else { | ||
const inputBuf = toBuffer(input) | ||
return inputBuf.length === 1 && inputBuf[0] < 128 | ||
? inputBuf | ||
: Buffer.concat([encodeLength(inputBuf.length, 128), inputBuf]) | ||
const buf = concatBytes(...output) | ||
return concatBytes(encodeLength(buf.length, 192), buf) | ||
} | ||
const inputBuf = toBytes(input) | ||
if (inputBuf.length === 1 && inputBuf[0] < 128) { | ||
return inputBuf | ||
} | ||
return concatBytes(encodeLength(inputBuf.length, 128), inputBuf) | ||
} | ||
/** | ||
* 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: Uint8Array, start: number, end: number) { | ||
if (end > input.length) { | ||
throw new Error('invalid RLP (safeSlice): end slice of Uint8Array out-of-bounds') | ||
} | ||
return input.slice(start, end) | ||
} | ||
/** | ||
* Parse integers. Check if there is no leading zeros | ||
* @param v The value to parse | ||
* @param base The base to parse the integer into | ||
*/ | ||
function safeParseInt(v: string, base: number): number { | ||
if (v[0] === '0' && v[1] === '0') { | ||
function decodeLength(v: Uint8Array): number { | ||
if (v[0] === 0) { | ||
throw new Error('invalid RLP: extra zeros') | ||
} | ||
return parseInt(v, base) | ||
return parseHexByte(bytesToHex(v)) | ||
} | ||
function encodeLength(len: number, offset: number): Buffer { | ||
function encodeLength(len: number, offset: number): Uint8Array { | ||
if (len < 56) { | ||
return Buffer.from([len + offset]) | ||
} else { | ||
const hexLength = intToHex(len) | ||
const lLength = hexLength.length / 2 | ||
const firstByte = intToHex(offset + 55 + lLength) | ||
return Buffer.from(firstByte + hexLength, 'hex') | ||
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)) | ||
} | ||
/** | ||
* RLP Decoding based on: {@link https://github.com/ethereum/wiki/wiki/%5BEnglish%5D-RLP|RLP} | ||
* @param input - will be converted to buffer | ||
* @param stream - Is the input a stream (false by default) | ||
* @returns - returns decode Array of Buffers containg the original message | ||
* RLP Decoding based on https://eth.wiki/en/fundamentals/rlp | ||
* @param input Will be converted to Uint8Array | ||
* @param stream Is the input a stream (false by default) | ||
* @returns decoded Array of Uint8Arrays containing the original message | ||
**/ | ||
export function decode(input: Buffer, stream?: boolean): Buffer | ||
export function decode(input: Buffer[], stream?: boolean): Buffer[] | ||
export function decode(input: Input, stream?: boolean): Buffer[] | Buffer | Decoded | ||
export function decode(input: Input, stream: boolean = false): Buffer[] | Buffer | Decoded { | ||
export function decode(input: Input, stream?: false): Uint8Array | NestedUint8Array | ||
export function decode(input: Input, stream?: true): Decoded | ||
export function decode(input: Input, stream = false): Uint8Array | NestedUint8Array | Decoded { | ||
if (!input || (input as any).length === 0) { | ||
return Buffer.from([]) | ||
return Uint8Array.from([]) | ||
} | ||
const inputBuffer = toBuffer(input) | ||
const decoded = _decode(inputBuffer) | ||
const inputBytes = toBytes(input) | ||
const decoded = _decode(inputBytes) | ||
@@ -75,3 +88,3 @@ if (stream) { | ||
if (decoded.remainder.length !== 0) { | ||
throw new Error('invalid remainder') | ||
throw new Error('invalid RLP: remainder must be zero') | ||
} | ||
@@ -82,35 +95,5 @@ | ||
/** | ||
* Get the length of the RLP input | ||
* @param input | ||
* @returns The length of the input or an empty Buffer if no input | ||
*/ | ||
export function getLength(input: Input): Buffer | number { | ||
if (!input || (input as any).length === 0) { | ||
return Buffer.from([]) | ||
} | ||
const inputBuffer = toBuffer(input) | ||
const firstByte = inputBuffer[0] | ||
if (firstByte <= 0x7f) { | ||
return inputBuffer.length | ||
} else if (firstByte <= 0xb7) { | ||
return firstByte - 0x7f | ||
} else if (firstByte <= 0xbf) { | ||
return firstByte - 0xb6 | ||
} else if (firstByte <= 0xf7) { | ||
// a list between 0-55 bytes long | ||
return firstByte - 0xbf | ||
} else { | ||
// a list over 55 bytes long | ||
const llength = firstByte - 0xf6 | ||
const length = safeParseInt(inputBuffer.slice(1, llength).toString('hex'), 16) | ||
return llength + length | ||
} | ||
} | ||
/** Decode an input with RLP */ | ||
function _decode(input: Buffer): Decoded { | ||
let length, llength, data, innerRemainder, d | ||
function _decode(input: Uint8Array): Decoded { | ||
let length: number, llength: number, data: Uint8Array, innerRemainder: Uint8Array, d: Decoded | ||
const decoded = [] | ||
@@ -132,9 +115,9 @@ const firstByte = input[0] | ||
if (firstByte === 0x80) { | ||
data = Buffer.from([]) | ||
data = Uint8Array.from([]) | ||
} else { | ||
data = input.slice(1, length) | ||
data = safeSlice(input, 1, length) | ||
} | ||
if (length === 2 && data[0] < 0x80) { | ||
throw new Error('invalid rlp encoding: byte must be less 0x80') | ||
throw new Error('invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed') | ||
} | ||
@@ -153,10 +136,7 @@ | ||
} | ||
length = safeParseInt(input.slice(1, llength).toString('hex'), 16) | ||
length = decodeLength(safeSlice(input, 1, llength)) | ||
if (length <= 55) { | ||
throw new Error('invalid RLP: expected string length to be greater than 55') | ||
} | ||
data = input.slice(llength, length + llength) | ||
if (data.length < length) { | ||
throw new Error('invalid RLP: not enough bytes for string') | ||
} | ||
data = safeSlice(input, llength, length + llength) | ||
@@ -168,8 +148,8 @@ return { | ||
} else if (firstByte <= 0xf7) { | ||
// a list between 0-55 bytes long | ||
// a list between 0-55 bytes long | ||
length = firstByte - 0xbf | ||
innerRemainder = input.slice(1, length) | ||
innerRemainder = safeSlice(input, 1, length) | ||
while (innerRemainder.length) { | ||
d = _decode(innerRemainder) | ||
decoded.push(d.data as Buffer) | ||
decoded.push(d.data) | ||
innerRemainder = d.remainder | ||
@@ -183,20 +163,21 @@ } | ||
} else { | ||
// a list over 55 bytes long | ||
// a list over 55 bytes long | ||
llength = firstByte - 0xf6 | ||
length = safeParseInt(input.slice(1, llength).toString('hex'), 16) | ||
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') | ||
throw new Error('invalid RLP: total length is larger than the data') | ||
} | ||
innerRemainder = input.slice(llength, totalLength) | ||
if (innerRemainder.length === 0) { | ||
throw new Error('invalid rlp, List has a invalid length') | ||
} | ||
innerRemainder = safeSlice(input, llength, totalLength) | ||
while (innerRemainder.length) { | ||
d = _decode(innerRemainder) | ||
decoded.push(d.data as Buffer) | ||
decoded.push(d.data) | ||
innerRemainder = d.remainder | ||
} | ||
return { | ||
@@ -209,17 +190,56 @@ data: decoded, | ||
/** Check if a string is prefixed by 0x */ | ||
function isHexPrefixed(str: string): boolean { | ||
return str.slice(0, 2) === '0x' | ||
const cachedHexes = Array.from({ length: 256 }, (_v, i) => i.toString(16).padStart(2, '0')) | ||
function bytesToHex(uint8a: Uint8Array): 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 | ||
} | ||
/** Removes 0x from a given String */ | ||
function stripHexPrefix(str: string): string { | ||
if (typeof str !== 'string') { | ||
return str | ||
function parseHexByte(hexByte: string): number { | ||
const byte = Number.parseInt(hexByte, 16) | ||
if (Number.isNaN(byte)) throw new Error('Invalid byte sequence') | ||
return byte | ||
} | ||
// Caching slows it down 2-3x | ||
function hexToBytes(hex: string): Uint8Array { | ||
if (typeof hex !== 'string') { | ||
throw new TypeError('hexToBytes: expected string, got ' + typeof hex) | ||
} | ||
return isHexPrefixed(str) ? str.slice(2) : str | ||
if (hex.length % 2) throw new Error('hexToBytes: received invalid unpadded hex') | ||
const array = new Uint8Array(hex.length / 2) | ||
for (let i = 0; i < array.length; i++) { | ||
const j = i * 2 | ||
array[i] = parseHexByte(hex.slice(j, j + 2)) | ||
} | ||
return array | ||
} | ||
/** Concatenates two Uint8Arrays into one. */ | ||
function concatBytes(...arrays: Uint8Array[]): Uint8Array { | ||
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 | ||
} | ||
// Global symbols in both browsers and Node.js since v11 | ||
// See https://github.com/microsoft/TypeScript/issues/31535 | ||
declare const TextEncoder: any | ||
declare const TextDecoder: any | ||
function utf8ToBytes(utf: string): Uint8Array { | ||
return new TextEncoder().encode(utf) | ||
} | ||
/** Transform an integer into its hexadecimal value */ | ||
function intToHex(integer: number | bigint): string { | ||
function numberToHex(integer: number | bigint): string { | ||
if (integer < 0) { | ||
@@ -237,35 +257,46 @@ throw new Error('Invalid integer as argument, must be unsigned!') | ||
/** Transform an integer into a Buffer */ | ||
function intToBuffer(integer: number | bigint): Buffer { | ||
const hex = intToHex(integer) | ||
return Buffer.from(hex, 'hex') | ||
/** Check if a string is prefixed by 0x */ | ||
function isHexPrefixed(str: string): boolean { | ||
return str.length >= 2 && str[0] === '0' && str[1] === 'x' | ||
} | ||
/** Transform anything into a Buffer */ | ||
function toBuffer(v: Input): Buffer { | ||
if (!Buffer.isBuffer(v)) { | ||
if (typeof v === 'string') { | ||
if (isHexPrefixed(v)) { | ||
return Buffer.from(padToEven(stripHexPrefix(v)), 'hex') | ||
} else { | ||
return Buffer.from(v) | ||
} | ||
} else if (typeof v === 'number' || typeof v === 'bigint') { | ||
if (!v) { | ||
return Buffer.from([]) | ||
} else { | ||
return intToBuffer(v) | ||
} | ||
} else if (v === null || v === undefined) { | ||
return Buffer.from([]) | ||
} else if (v instanceof Uint8Array) { | ||
return Buffer.from(v as any) | ||
} else if (BN.isBN(v)) { | ||
// converts a BN to a Buffer | ||
return Buffer.from(v.toArray()) | ||
} else { | ||
throw new Error('invalid type') | ||
/** Removes 0x from a given String */ | ||
function stripHexPrefix(str: string): string { | ||
if (typeof str !== 'string') { | ||
return str | ||
} | ||
return isHexPrefixed(str) ? str.slice(2) : str | ||
} | ||
/** Transform anything into a Uint8Array */ | ||
function toBytes(v: Input): Uint8Array { | ||
if (v instanceof Uint8Array) { | ||
return v | ||
} | ||
if (typeof v === 'string') { | ||
if (isHexPrefixed(v)) { | ||
return hexToBytes(padToEven(stripHexPrefix(v))) | ||
} | ||
return utf8ToBytes(v) | ||
} | ||
return 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 new Error('toBytes: received unsupported type ' + typeof v) | ||
} | ||
export const utils = { | ||
bytesToHex, | ||
concatBytes, | ||
hexToBytes, | ||
utf8ToBytes, | ||
} | ||
const RLP = { encode, decode } | ||
export default RLP |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
0
12
96
1
49715
8
555
- Removedbn.js@^5.2.0
- Removedbn.js@5.2.1(transitive)