@dcl/content-hash-tree
Version:
Lib to generate and validate a merkle tree of content hashes
101 lines • 3.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const ethers_1 = require("ethers");
const { solidityKeccak256 } = ethers_1.utils;
function bufferToHex(buf) {
return '0x' + buf.toString('hex');
}
class MerkleTree {
constructor(elements) {
this.elements = [...elements];
// Sort elements
this.elements.sort(Buffer.compare);
// Deduplicate elements
this.elements = MerkleTree.bufDedup(this.elements);
this.bufferElementPositionIndex = this.elements.reduce((memo, el, index) => {
memo[bufferToHex(el)] = index;
return memo;
}, {});
// Create layers
this.layers = this.getLayers(this.elements);
}
getLayers(elements) {
if (elements.length === 0) {
throw new Error('empty tree');
}
const layers = [];
layers.push(elements);
// Get next layer until we reach the root
while (layers[layers.length - 1].length > 1) {
layers.push(this.getNextLayer(layers[layers.length - 1]));
}
return layers;
}
getNextLayer(elements) {
return elements.reduce((layer, el, idx, arr) => {
if (idx % 2 === 0) {
// Hash the current element with its pair element
layer.push(MerkleTree.combinedHash(el, arr[idx + 1]));
}
return layer;
}, []);
}
static combinedHash(first, second) {
if (!first) {
return second;
}
if (!second) {
return first;
}
return Buffer.from(solidityKeccak256(['bytes'], [bufferToHex(MerkleTree.sortAndConcat(first, second))]).substr(2), 'hex');
}
getRoot() {
return this.layers[this.layers.length - 1][0];
}
getHexRoot() {
return bufferToHex(this.getRoot());
}
getProof(el) {
let idx = this.bufferElementPositionIndex[bufferToHex(el)];
if (typeof idx !== 'number') {
throw new Error('Element does not exist in Merkle tree');
}
return this.layers.reduce((proof, layer) => {
const pairElement = MerkleTree.getPairElement(idx, layer);
if (pairElement) {
proof.push(pairElement);
}
idx = Math.floor(idx / 2);
return proof;
}, []);
}
getHexProof(el) {
const proof = this.getProof(el);
return MerkleTree.bufArrToHexArr(proof);
}
static getPairElement(idx, layer) {
const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1;
if (pairIdx < layer.length) {
return layer[pairIdx];
}
else {
return null;
}
}
static bufDedup(elements) {
return elements.filter((el, idx) => {
return idx === 0 || !elements[idx - 1].equals(el);
});
}
static bufArrToHexArr(arr) {
if (arr.some((el) => !Buffer.isBuffer(el))) {
throw new Error('Array is not an array of buffers');
}
return arr.map((el) => '0x' + el.toString('hex'));
}
static sortAndConcat(...args) {
return Buffer.concat([...args].sort(Buffer.compare));
}
}
exports.default = MerkleTree;
//# sourceMappingURL=merkle-tree.js.map