@ethereumjs/mpt
Version:
Implementation of the modified merkle patricia tree as specified in Ethereum's yellow paper.
200 lines • 7.68 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeAndFormatKeyPaths = exports.pathToHexKey = exports.byteTypeToNibbleType = exports.nibbleTypeToByteType = exports.compactBytesToNibbles = exports.bytesToNibbles = exports.nibblesToCompactBytes = exports.hexToKeybytes = exports.nibblesToBytes = void 0;
const util_1 = require("@ethereumjs/util");
const nibbles_ts_1 = require("./nibbles.js");
// Reference: https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/
//
// MerklePatriciaTrie keys are dealt with in three distinct encodings:
//
// KEYBYTES encoding contains the actual key and nothing else. This encoding is the
// input to most API functions.
//
// HEX encoding contains one byte for each nibble of the key and an optional trailing
// 'terminator' byte of value 0x10 which indicates whether or not the node at the key
// contains a value. Hex key encoding is used for nodes loaded in memory because it's
// convenient to access.
//
// COMPACT encoding is defined by the Ethereum Yellow Paper (it's called "hex prefix
// encoding" there) and contains the bytes of the key and a flag. The high nibble of the
// first byte contains the flag; the lowest bit encoding the oddness of the length and
// the second-lowest encoding whether the node at the key is a value node. The low nibble
// of the first byte is zero in the case of an even number of nibbles and the first nibble
// in the case of an odd number. All remaining nibbles (now an even number) fit properly
// into the remaining bytes. Compact encoding is used for nodes stored on disk.
/**
*
* @param nibbles byte sequence
* @returns boolean indicating if input hex nibble sequence has terminator indicating leaf-node
* terminator is represented with 16 because a nibble ranges from 0 - 15(f)
*/
const hasTerminator = (nibbles) => {
return nibbles.length > 0 && nibbles[nibbles.length - 1] === 16;
};
const nibblesToBytes = (nibbles) => {
const bytes = new Uint8Array(nibbles.length / 2);
for (let bi = 0, ni = 0; ni < nibbles.length; bi += 1, ni += 2) {
bytes[bi] = (nibbles[ni] << 4) | nibbles[ni + 1];
}
return bytes;
};
exports.nibblesToBytes = nibblesToBytes;
const hexToKeybytes = (hex) => {
if (hasTerminator(hex)) {
hex = hex.subarray(0, hex.length - 1);
}
if (hex.length % 2 === 1) {
throw Error("Can't convert hex key of odd length");
}
return (0, exports.nibblesToBytes)(hex);
};
exports.hexToKeybytes = hexToKeybytes;
// hex to compact
const nibblesToCompactBytes = (nibbles) => {
let terminator = 0;
if (hasTerminator(nibbles)) {
terminator = 1;
// Remove the terminator from the sequence
nibbles = nibbles.subarray(0, nibbles.length - 1);
}
const buf = new Uint8Array(nibbles.length / 2 + 1);
// Shift the terminator info into the first nibble of buf[0]
buf[0] = terminator << 5;
// If odd length, then add that flag into the first nibble and put the odd nibble to
// second part of buf[0] which otherwise will be left padded with a 0
if ((nibbles.length & 1) === 1) {
buf[0] |= 1 << 4;
buf[0] |= nibbles[0];
nibbles = nibbles.subarray(1);
}
// create bytes out of the rest even nibbles
return (0, util_1.concatBytes)(buf.subarray(0, 1), (0, exports.nibblesToBytes)(nibbles));
};
exports.nibblesToCompactBytes = nibblesToCompactBytes;
const bytesToNibbles = (str) => {
const l = str.length * 2 + 1;
const nibbles = new Uint8Array(l);
for (let i = 0; i < str.length; i++) {
const b = str[i];
nibbles[i * 2] = b / 16;
nibbles[i * 2 + 1] = b % 16;
}
// This will get removed from calling function if the first nibble
// indicates that terminator is not present
nibbles[l - 1] = 16;
return nibbles;
};
exports.bytesToNibbles = bytesToNibbles;
const compactBytesToNibbles = (compact) => {
if (compact.length === 0) {
return compact;
}
let base = (0, exports.bytesToNibbles)(compact);
// delete terminator flag if terminator flag was not in first nibble
if (base[0] < 2) {
base = base.subarray(0, base.length - 1);
}
// chop the terminator nibble and the even padding (if there is one)
// i.e. chop 2 left nibbles when even else 1 when odd
const chop = 2 - (base[0] & 1);
return base.subarray(chop);
};
exports.compactBytesToNibbles = compactBytesToNibbles;
/**
* Converts each nibble into a single byte
*
* @param arr Nibble typed nibble array
* @returns Uint8Array typed byte array
*/
const nibbleTypeToByteType = (arr) => {
const l = arr.length;
const buf = new Uint8Array(l);
for (let i = 0; i < buf.length; i++) {
buf[i] = arr[i];
}
return buf;
};
exports.nibbleTypeToByteType = nibbleTypeToByteType;
/**
* Turns each byte into a single nibble, only extracting the lower nibble of each byte
*
* @param key Uint8Array typed byte array
* @returns Nibble typed nibble array
*/
const byteTypeToNibbleType = (key) => {
const nibbles = [];
for (let i = 0; i < key.length; i++) {
const q = i;
nibbles[q] = key[i] % 16;
}
return nibbles;
};
exports.byteTypeToNibbleType = byteTypeToNibbleType;
/**
* Takes a string path and extends it by the given extension nibbles
*
* @param path String node path
* @param extension nibbles to extend by
* @param retType string indicating whether to return the key in "keybyte" or "hex" encoding
* @returns hex-encoded key
*/
const pathToHexKey = (path, extension, retType) => {
const b = (0, util_1.hexToBytes)(`0x${path}`);
const n = (0, exports.byteTypeToNibbleType)(b);
if (retType === 'hex') {
return (0, exports.nibbleTypeToByteType)(n.concat(extension));
}
else if (retType === 'keybyte') {
return (0, nibbles_ts_1.nibblesTypeToPackedBytes)(n.concat(extension));
}
throw Error('retType must be either "keybyte" or "hex"');
};
exports.pathToHexKey = pathToHexKey;
const mergeAndFormatKeyPaths = (pathStrings) => {
const ret = [];
let paths = [];
let i = 0;
while (i < pathStrings.length) {
const outerPathString = pathStrings[i].split('/');
const outerAccountPath = outerPathString[0];
const outerStoragePath = outerPathString[1];
paths.push(outerAccountPath);
if (outerStoragePath !== undefined) {
paths.push(outerStoragePath);
}
let j = ++i;
while (j < pathStrings.length) {
const innerPathString = pathStrings[j].split('/');
const innerAccountPath = innerPathString[0];
const innerStoragePath = innerPathString[1];
if (innerAccountPath === outerAccountPath) {
paths.push(innerStoragePath);
}
else {
ret.push(paths);
paths = [];
i = j;
break;
}
j++;
}
if (paths.length > 0) {
ret.push(paths);
paths = [];
}
}
if (paths.length > 0)
ret.push(paths);
return ret.map((pathStrings) => pathStrings.map((s) => {
if (s.length < 64) {
// partial path is compact encoded
return (0, exports.nibblesToCompactBytes)((0, util_1.unprefixedHexToBytes)(s));
}
else {
// full path is keybyte encoded
return (0, exports.hexToKeybytes)((0, util_1.unprefixedHexToBytes)(s));
}
}));
};
exports.mergeAndFormatKeyPaths = mergeAndFormatKeyPaths;
//# sourceMappingURL=encoding.js.map