@chainsafe/persistent-merkle-tree
Version:
Merkle tree implemented as a persistent datastructure
132 lines • 5.19 kB
JavaScript
import { byteArrayIntoHashObject } from "@chainsafe/as-sha256";
import { hashInto } from "@chainsafe/hashtree";
import { doDigestNLevel, doMerkleizeBlockArray, doMerkleizeBlocksBytes } from "./util.js";
/**
* Best SIMD implementation is in 512 bits = 64 bytes
* If not, hashtree will make a loop inside
* Given sha256 operates on a block of 4 bytes, we can hash 16 inputs at once
* Each input is 64 bytes
*/
const PARALLEL_FACTOR = 16;
const MAX_INPUT_SIZE = PARALLEL_FACTOR * 64;
const uint8Input = new Uint8Array(MAX_INPUT_SIZE);
const uint32Input = new Uint32Array(uint8Input.buffer);
const uint8Output = new Uint8Array(PARALLEL_FACTOR * 32);
// having this will cause more memory to extract uint32
// const uint32Output = new Uint32Array(uint8Output.buffer);
// convenient reusable Uint8Array for hash64
const hash64Input = uint8Input.subarray(0, 64);
const hash64Output = uint8Output.subarray(0, 32);
// size input array to 2 HashObject per computation * 32 bytes per object
const destNodes = new Array(PARALLEL_FACTOR);
export const hasher = {
name: "hashtree",
hashInto,
digest64(obj1, obj2) {
if (obj1.length !== 32 || obj2.length !== 32) {
throw new Error("Invalid input length");
}
hash64Input.set(obj1, 0);
hash64Input.set(obj2, 32);
hashInto(hash64Input, hash64Output);
return hash64Output.slice();
},
digest64Into: (obj1, obj2, output) => {
if (obj1.length !== 32 || obj2.length !== 32) {
throw new Error("Invalid input length");
}
if (output.length !== 32) {
throw new Error("Invalid output length");
}
hash64Input.set(obj1, 0);
hash64Input.set(obj2, 32);
hashInto(hash64Input, output);
},
digest64HashObjects(left, right, parent) {
hashObjectsToUint32Array(left, right, uint32Input);
hashInto(hash64Input, hash64Output);
byteArrayIntoHashObject(hash64Output, 0, parent);
},
merkleizeBlocksBytes(blocksBytes, padFor, output, offset) {
doMerkleizeBlocksBytes(blocksBytes, padFor, output, offset, hashInto);
},
merkleizeBlockArray(blocks, blockLimit, padFor, output, offset) {
doMerkleizeBlockArray(blocks, blockLimit, padFor, output, offset, hashInto, uint8Input);
},
digestNLevel(data, nLevel) {
return doDigestNLevel(data, nLevel, hashInto);
},
executeHashComputations(hashComputations) {
for (let level = hashComputations.length - 1; level >= 0; level--) {
const hcArr = hashComputations[level];
if (!hcArr) {
// should not happen
throw Error(`no hash computations for level ${level}`);
}
if (hcArr.length === 0) {
// nothing to hash
continue;
}
// hash every 16 inputs at once to avoid memory allocation
let i = 0;
for (const { src0, src1, dest } of hcArr) {
if (!src0 || !src1 || !dest) {
throw new Error(`Invalid HashComputation at index ${i}`);
}
const indexInBatch = i % PARALLEL_FACTOR;
const offset = indexInBatch * 16;
hashObjectToUint32Array(src0, uint32Input, offset);
hashObjectToUint32Array(src1, uint32Input, offset + 8);
destNodes[indexInBatch] = dest;
if (indexInBatch === PARALLEL_FACTOR - 1) {
hashInto(uint8Input, uint8Output);
for (const [j, destNode] of destNodes.entries()) {
byteArrayIntoHashObject(uint8Output, j * 32, destNode);
}
}
i++;
}
const remaining = hcArr.length % PARALLEL_FACTOR;
// we prepared data in input, now hash the remaining
if (remaining > 0) {
const remainingInput = uint8Input.subarray(0, remaining * 64);
const remainingOutput = uint8Output.subarray(0, remaining * 32);
hashInto(remainingInput, remainingOutput);
// destNodes was prepared above
for (let j = 0; j < remaining; j++) {
byteArrayIntoHashObject(remainingOutput, j * 32, destNodes[j]);
}
}
}
},
};
function hashObjectToUint32Array(obj, arr, offset) {
arr[offset] = obj.h0;
arr[offset + 1] = obj.h1;
arr[offset + 2] = obj.h2;
arr[offset + 3] = obj.h3;
arr[offset + 4] = obj.h4;
arr[offset + 5] = obj.h5;
arr[offset + 6] = obj.h6;
arr[offset + 7] = obj.h7;
}
// note that uint32ArrayToHashObject will cause more memory
function hashObjectsToUint32Array(obj1, obj2, arr) {
arr[0] = obj1.h0;
arr[1] = obj1.h1;
arr[2] = obj1.h2;
arr[3] = obj1.h3;
arr[4] = obj1.h4;
arr[5] = obj1.h5;
arr[6] = obj1.h6;
arr[7] = obj1.h7;
arr[8] = obj2.h0;
arr[9] = obj2.h1;
arr[10] = obj2.h2;
arr[11] = obj2.h3;
arr[12] = obj2.h4;
arr[13] = obj2.h5;
arr[14] = obj2.h6;
arr[15] = obj2.h7;
}
//# sourceMappingURL=hashtree.js.map