@chainsafe/persistent-merkle-tree
Version:
Merkle tree implemented as a persistent datastructure
330 lines • 9.85 kB
JavaScript
import { hashObjectToUint8Array, hasher, uint8ArrayToHashObject } from "./hasher/index.js";
const TWO_POWER_32 = 2 ** 32;
/**
* An immutable binary merkle tree node
*/
export class Node {
/**
* May be null. This is to save an extra variable to check if a node has a root or not
*/
h0;
h1;
h2;
h3;
h4;
h5;
h6;
h7;
constructor(h0, h1, h2, h3, h4, h5, h6, h7) {
this.h0 = h0;
this.h1 = h1;
this.h2 = h2;
this.h3 = h3;
this.h4 = h4;
this.h5 = h5;
this.h6 = h6;
this.h7 = h7;
}
applyHash(root) {
this.h0 = root.h0;
this.h1 = root.h1;
this.h2 = root.h2;
this.h3 = root.h3;
this.h4 = root.h4;
this.h5 = root.h5;
this.h6 = root.h6;
this.h7 = root.h7;
}
}
/**
* An immutable binary merkle tree node that has a `left` and `right` child
*/
export class BranchNode extends Node {
_left;
_right;
constructor(_left, _right) {
// First null value is to save an extra variable to check if a node has a root or not
super(null, 0, 0, 0, 0, 0, 0, 0);
this._left = _left;
this._right = _right;
if (!_left) {
throw new Error("Left node is undefined");
}
if (!_right) {
throw new Error("Right node is undefined");
}
}
get rootHashObject() {
if (this.h0 === null) {
hasher.digest64HashObjects(this.left.rootHashObject, this.right.rootHashObject, this);
}
return this;
}
get root() {
return hashObjectToUint8Array(this.rootHashObject);
}
get left() {
return this._left;
}
get right() {
return this._right;
}
isLeaf() {
return false;
}
}
/**
* An immutable binary merkle tree node that has no children
*/
export class LeafNode extends Node {
get rootHashObject() {
return this;
}
get root() {
return hashObjectToUint8Array(this);
}
get left() {
throw Error("LeafNode has no left node");
}
get right() {
throw Error("LeafNode has no right node");
}
static fromRoot(root) {
return LeafNode.fromHashObject(uint8ArrayToHashObject(root));
}
/**
* New LeafNode from existing HashObject.
*/
static fromHashObject(ho) {
return new LeafNode(ho.h0, ho.h1, ho.h2, ho.h3, ho.h4, ho.h5, ho.h6, ho.h7);
}
/**
* New LeafNode with its internal value set to zero. Consider using `zeroNode(0)` if you don't need to mutate.
*/
static fromZero() {
return new LeafNode(0, 0, 0, 0, 0, 0, 0, 0);
}
/**
* LeafNode with HashObject `(uint32, 0, 0, 0, 0, 0, 0, 0)`.
*/
static fromUint32(uint32) {
return new LeafNode(uint32, 0, 0, 0, 0, 0, 0, 0);
}
/**
* Create a new LeafNode with the same internal values. The returned instance is safe to mutate
*/
clone() {
return LeafNode.fromHashObject(this);
}
isLeaf() {
return true;
}
writeToBytes(data, start, size) {
// TODO: Optimize
data.set(this.root.slice(0, size), start);
}
getUint(uintBytes, offsetBytes, clipInfinity) {
const hIndex = Math.floor(offsetBytes / 4);
// number has to be masked from an h value
if (uintBytes < 4) {
const bitIndex = (offsetBytes % 4) * 8;
const h = getNodeH(this, hIndex);
if (uintBytes === 1) {
return 0xff & (h >> bitIndex);
}
return 0xffff & (h >> bitIndex);
}
// number equals the h value
if (uintBytes === 4) {
return getNodeH(this, hIndex) >>> 0;
}
// number spans 2 h values
if (uintBytes === 8) {
const low = getNodeH(this, hIndex);
const high = getNodeH(this, hIndex + 1);
if (high === 0)
return low >>> 0;
if (high === -1 && low === -1 && clipInfinity) {
// Limit uint returns
return Infinity;
}
return (low >>> 0) + (high >>> 0) * TWO_POWER_32;
}
// Bigger uint can't be represented
throw Error("uintBytes > 8");
}
getUintBigint(uintBytes, offsetBytes) {
const hIndex = Math.floor(offsetBytes / 4);
// number has to be masked from an h value
if (uintBytes < 4) {
const bitIndex = (offsetBytes % 4) * 8;
const h = getNodeH(this, hIndex);
if (uintBytes === 1)
return BigInt(0xff & (h >> bitIndex));
return BigInt(0xffff & (h >> bitIndex));
}
// number equals the h value
if (uintBytes === 4) {
return BigInt(getNodeH(this, hIndex) >>> 0);
}
// number spans multiple h values
const hRange = Math.ceil(uintBytes / 4);
let v = BigInt(0);
for (let i = 0; i < hRange; i++) {
v += BigInt(getNodeH(this, hIndex + i) >>> 0) << BigInt(32 * i);
}
return v;
}
setUint(uintBytes, offsetBytes, value, clipInfinity) {
const hIndex = Math.floor(offsetBytes / 4);
// number has to be masked from an h value
if (uintBytes < 4) {
const bitIndex = (offsetBytes % 4) * 8;
let h = getNodeH(this, hIndex);
if (uintBytes === 1) {
h &= ~(0xff << bitIndex);
h |= (0xff & value) << bitIndex;
}
else {
h &= ~(0xffff << bitIndex);
h |= (0xffff & value) << bitIndex;
}
setNodeH(this, hIndex, h);
}
// number equals the h value
else if (uintBytes === 4) {
setNodeH(this, hIndex, value);
}
// number spans 2 h values
else if (uintBytes === 8) {
if (value === Infinity && clipInfinity) {
setNodeH(this, hIndex, -1);
setNodeH(this, hIndex + 1, -1);
}
else {
setNodeH(this, hIndex, value & 0xffffffff);
setNodeH(this, hIndex + 1, (value / TWO_POWER_32) & 0xffffffff);
}
}
// Bigger uint can't be represented
else {
throw Error("uintBytes > 8");
}
}
setUintBigint(uintBytes, offsetBytes, valueBN) {
const hIndex = Math.floor(offsetBytes / 4);
// number has to be masked from an h value
if (uintBytes < 4) {
const value = Number(valueBN);
const bitIndex = (offsetBytes % 4) * 8;
let h = getNodeH(this, hIndex);
if (uintBytes === 1) {
h &= ~(0xff << bitIndex);
h |= (0xff & value) << bitIndex;
}
else {
h &= ~(0xffff << bitIndex);
h |= (0xffff & value) << bitIndex;
}
setNodeH(this, hIndex, h);
}
// number equals the h value
else if (uintBytes === 4) {
setNodeH(this, hIndex, Number(valueBN));
}
// number spans multiple h values
else {
const hEnd = hIndex + Math.ceil(uintBytes / 4);
for (let i = hIndex; i < hEnd; i++) {
setNodeH(this, i, Number(valueBN & BigInt(0xffffffff)));
valueBN = valueBN >> BigInt(32);
}
}
}
bitwiseOrUint(uintBytes, offsetBytes, value) {
const hIndex = Math.floor(offsetBytes / 4);
// number has to be masked from an h value
if (uintBytes < 4) {
const bitIndex = (offsetBytes % 4) * 8;
bitwiseOrNodeH(this, hIndex, value << bitIndex);
}
// number equals the h value
else if (uintBytes === 4) {
bitwiseOrNodeH(this, hIndex, value);
}
// number spans multiple h values
else {
const hEnd = hIndex + Math.ceil(uintBytes / 4);
for (let i = hIndex; i < hEnd; i++) {
bitwiseOrNodeH(this, i, value & 0xffffffff);
value >>= 32;
}
}
}
}
export function identity(n) {
return n;
}
export function compose(inner, outer) {
return (n) => outer(inner(n));
}
export function getNodeH(node, hIndex) {
if (hIndex === 0)
return node.h0;
if (hIndex === 1)
return node.h1;
if (hIndex === 2)
return node.h2;
if (hIndex === 3)
return node.h3;
if (hIndex === 4)
return node.h4;
if (hIndex === 5)
return node.h5;
if (hIndex === 6)
return node.h6;
if (hIndex === 7)
return node.h7;
throw Error("hIndex > 7");
}
export function setNodeH(node, hIndex, value) {
if (hIndex === 0)
node.h0 = value;
else if (hIndex === 1)
node.h1 = value;
else if (hIndex === 2)
node.h2 = value;
else if (hIndex === 3)
node.h3 = value;
else if (hIndex === 4)
node.h4 = value;
else if (hIndex === 5)
node.h5 = value;
else if (hIndex === 6)
node.h6 = value;
else if (hIndex === 7)
node.h7 = value;
else
throw Error("hIndex > 7");
}
export function bitwiseOrNodeH(node, hIndex, value) {
if (hIndex === 0)
node.h0 |= value;
else if (hIndex === 1)
node.h1 |= value;
else if (hIndex === 2)
node.h2 |= value;
else if (hIndex === 3)
node.h3 |= value;
else if (hIndex === 4)
node.h4 |= value;
else if (hIndex === 5)
node.h5 |= value;
else if (hIndex === 6)
node.h6 |= value;
else if (hIndex === 7)
node.h7 |= value;
else
throw Error("hIndex > 7");
}
//# sourceMappingURL=node.js.map