@jackallabs/dogwood-tree
Version:
Jackal Labs JS Merkletree implementation
570 lines (569 loc) • 11.9 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const js_sha3 = require("js-sha3");
function calculateBranches(leaves) {
return Math.pow(2, Math.ceil(Math.log2(leaves)));
}
function stringToUint8(str) {
const uintView = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
uintView[i] = str.charCodeAt(i);
}
return uintView;
}
const { sha3_512 } = js_sha3;
const branchHashOptions = {
sha3_512: sha3_512.arrayBuffer
};
function bufferToHex(buf) {
return new Uint8Array(buf).reduce((acc, curr) => {
return acc + hexPrecompute[curr];
}, "");
}
const hexPrecompute = [
"00",
"01",
"02",
"03",
"04",
"05",
"06",
"07",
"08",
"09",
"0a",
"0b",
"0c",
"0d",
"0e",
"0f",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"1a",
"1b",
"1c",
"1d",
"1e",
"1f",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"2a",
"2b",
"2c",
"2d",
"2e",
"2f",
"30",
"31",
"32",
"33",
"34",
"35",
"36",
"37",
"38",
"39",
"3a",
"3b",
"3c",
"3d",
"3e",
"3f",
"40",
"41",
"42",
"43",
"44",
"45",
"46",
"47",
"48",
"49",
"4a",
"4b",
"4c",
"4d",
"4e",
"4f",
"50",
"51",
"52",
"53",
"54",
"55",
"56",
"57",
"58",
"59",
"5a",
"5b",
"5c",
"5d",
"5e",
"5f",
"60",
"61",
"62",
"63",
"64",
"65",
"66",
"67",
"68",
"69",
"6a",
"6b",
"6c",
"6d",
"6e",
"6f",
"70",
"71",
"72",
"73",
"74",
"75",
"76",
"77",
"78",
"79",
"7a",
"7b",
"7c",
"7d",
"7e",
"7f",
"80",
"81",
"82",
"83",
"84",
"85",
"86",
"87",
"88",
"89",
"8a",
"8b",
"8c",
"8d",
"8e",
"8f",
"90",
"91",
"92",
"93",
"94",
"95",
"96",
"97",
"98",
"99",
"9a",
"9b",
"9c",
"9d",
"9e",
"9f",
"a0",
"a1",
"a2",
"a3",
"a4",
"a5",
"a6",
"a7",
"a8",
"a9",
"aa",
"ab",
"ac",
"ad",
"ae",
"af",
"b0",
"b1",
"b2",
"b3",
"b4",
"b5",
"b6",
"b7",
"b8",
"b9",
"ba",
"bb",
"bc",
"bd",
"be",
"bf",
"c0",
"c1",
"c2",
"c3",
"c4",
"c5",
"c6",
"c7",
"c8",
"c9",
"ca",
"cb",
"cc",
"cd",
"ce",
"cf",
"d0",
"d1",
"d2",
"d3",
"d4",
"d5",
"d6",
"d7",
"d8",
"d9",
"da",
"db",
"dc",
"dd",
"de",
"df",
"e0",
"e1",
"e2",
"e3",
"e4",
"e5",
"e6",
"e7",
"e8",
"e9",
"ea",
"eb",
"ec",
"ed",
"ee",
"ef",
"f0",
"f1",
"f2",
"f3",
"f4",
"f5",
"f6",
"f7",
"f8",
"f9",
"fa",
"fb",
"fc",
"fd",
"fe",
"ff"
];
function arrayBuffersMatch(a, b) {
if (a.byteLength !== b.byteLength) {
return false;
} else {
switch (0) {
case a.byteLength % 4:
return typedArraysMatch(new Uint32Array(a), new Uint32Array(b));
case a.byteLength % 2:
return typedArraysMatch(new Uint16Array(a), new Uint16Array(b));
case a.byteLength % 1:
return typedArraysMatch(new Uint8Array(a), new Uint8Array(b));
default:
throw new Error("Unexpected Result, bad math?");
}
}
}
function typedArraysMatch(a, b) {
if (a.byteLength !== b.byteLength) {
return false;
} else {
return a.every((val, i) => val === b[i]);
}
}
class Merkletree {
/**
* @constructor Merkletree
* @protected
*/
constructor(r, p, sap, n, h, u, s) {
__publicField(this, "root");
__publicField(this, "source");
__publicField(this, "nodes");
__publicField(this, "hash");
__publicField(this, "salted");
__publicField(this, "sorted");
this.root = r;
this.source = p ? sap : [];
this.nodes = p ? n : [];
this.hash = h;
this.salted = u;
this.sorted = s;
}
/**
* @constructor Merkletree
* @param {IMerkletreeSource} input - Merkletree creation parameters.
* @returns {Promise<IMerkletree>}
*/
static async grow(input) {
let { seed, sapling, chunkSize, hashType = "sha3_512", useSalt = false, sort = false, preserve = true } = input;
if (sapling) ;
else if (seed && chunkSize) {
sapling = [];
for (let i = 0, ii = 0; i < seed.byteLength; i += chunkSize, ii++) {
const bufChunk = seed.slice(i, i + chunkSize);
const str = ii.toString() + bufferToHex(bufChunk);
const hashName = await crypto.subtle.digest("SHA-256", stringToUint8(str));
sapling.push(hashName);
}
} else {
throw new Error("No Data Provided!");
}
if (!Object.keys(branchHashOptions).includes(hashType)) {
throw new Error(`Unsupported hashType of "${hashType}"!`);
}
const hashFunc = branchHashOptions[hashType];
const branchesLen = calculateBranches(sapling.length);
const nodeLen = branchesLen + sapling.length + (branchesLen - sapling.length);
const nodes = Array(nodeLen).fill(new ArrayBuffer(64));
for (let i = 0; i < sapling.length; i++) {
nodes[branchesLen + i] = hashFunc(sapling[i]);
}
for (let i = branchesLen - 1; i > 0; i--) {
const left = nodes[i * 2];
const right = nodes[i * 2 + 1];
const concat = await new Blob([left, right]).arrayBuffer();
nodes[i] = hashFunc(concat);
}
return new Merkletree(nodes[1], preserve, sapling, nodes, hashType, useSalt, sort);
}
getRoot() {
return this.root;
}
getRootAsHex() {
return bufferToHex(this.root);
}
getSalt() {
return this.salted;
}
generatePollard(height) {
if (this.nodes.length === 0) {
throw new Error("Data was not preserved!");
} else {
return new Pollard(this.nodes.slice(1, Math.pow(2, height + 1)), this.hash, height);
}
}
generateProof(data, height) {
if (this.nodes.length === 0) {
throw new Error("Data was not preserved!");
} else {
const index = indexOf(data, this.source);
if (index === -1) {
throw new Error("Data is not present!");
} else {
const proofLen = Math.ceil(Math.log2(this.source.length)) - height;
const hashes = Array(proofLen).fill(new ArrayBuffer(64));
const it = index + this.nodes.length / 2;
const limit = Math.pow(2, height + 1) - 1;
for (let i = it, ii = 0; i > limit; i /= 2, ii++) {
hashes[ii] = this.nodes[i ^ 1];
}
return new Proof(hashes, index, this.hash, this.salted, this.generatePollard(height));
}
}
}
}
class MerkletreeCompact {
/**
* @constructor MerkletreeCompact
* @protected
*/
constructor(r) {
__publicField(this, "root");
this.root = r;
}
/**
* @constructor MerkletreeCompact
* @param {IMerkletreeCompactSource} input - Merkletree Compact creation parameters.
* @returns {Promise<IMerkletreeCompact>}
*/
static async grow(input) {
let { seed, sapling, chunkSize, hashType = "sha3_512" } = input;
if (sapling) ;
else if (seed && chunkSize) {
sapling = [];
for (let i = 0, ii = 0; i < seed.byteLength; i += chunkSize, ii++) {
const bufChunk = seed.slice(i, i + chunkSize);
const str = ii.toString() + bufferToHex(bufChunk);
const hashName = await crypto.subtle.digest("SHA-256", stringToUint8(str));
sapling.push(hashName);
}
} else {
throw new Error("No Data Provided!");
}
if (!Object.keys(branchHashOptions).includes(hashType)) {
throw new Error(`Unsupported hashType of "${hashType}"!`);
}
const hashFunc = branchHashOptions[hashType];
const branchesLen = calculateBranches(sapling.length);
let queue = Array(branchesLen).fill(new ArrayBuffer(64));
for (let i = 0; i < sapling.length; i++) {
queue[i] = hashFunc(sapling[i]);
}
while (queue.length > 1) {
const cycle = [];
for (let i = queue.length - 1; i >= 0; i--) {
cycle[i] = merkle(queue[i * 2], queue[i * 2 + 1], hashFunc);
}
queue = cycle;
}
return new MerkletreeCompact(queue[0]);
}
getRoot() {
return this.root;
}
getRootAsHex() {
return bufferToHex(this.root);
}
}
function merkle(left, right, hashFunc) {
const length = left.byteLength + right.byteLength;
const final = new Uint8Array(length);
const uLeft = new Uint8Array(left);
const uRight = new Uint8Array(right);
final.set(uLeft, 0);
final.set(uRight, left.byteLength);
return hashFunc(final);
}
function indexOf(needle, haystack) {
for (let i = 0; i < haystack.length; i++) {
if (arrayBuffersMatch(needle, haystack[i])) {
return i;
}
}
return -1;
}
class MultiProof {
constructor() {
}
async verify(data) {
console.log(data);
return false;
}
}
class Pollard {
/**
* @constructor Pollard
* @param {Array<ArrayBuffer>} hashes - Merkletree hashes.
* @param {TBranchHashOptionKeys} hashType - Merkletree hash type used.
* @param {number} height - Height in Merkletree.
*/
constructor(hashes, hashType, height) {
__publicField(this, "hashes");
__publicField(this, "hashType");
__publicField(this, "height");
this.hashes = hashes;
this.hashType = hashType;
this.height = height;
}
getHashes() {
return this.hashes;
}
getHeight() {
return this.height;
}
getLength() {
return this.hashes.length;
}
async verify() {
if (this.hashes.length === 1) {
return true;
} else {
const hashFunc = branchHashOptions[this.hashType];
for (let i = this.hashes.length / 2 - 1; i >= 0; i--) {
const left = this.hashes[i * 2 + 1];
const right = this.hashes[i * 2 + 2];
const concat = await new Blob([left, right]).arrayBuffer();
if (!arrayBuffersMatch(this.hashes[i], hashFunc(concat))) {
return false;
}
}
return true;
}
}
}
class Proof {
/**
* @constructor Proof
* @param {Array<ArrayBuffer>} hashes - Merkletree hashes.
* @param {number} index - Merkletree index.
* @param {TBranchHashOptionKeys} hashType - Merkletree hash type used.
* @param {boolean} salted - If merkletree was salted.
* @param {IPollard} pollard - Pollard instance to use as source.
*/
constructor(hashes, index, hashType, salted, pollard) {
__publicField(this, "hashes");
__publicField(this, "index");
__publicField(this, "hashType");
__publicField(this, "salted");
__publicField(this, "pollard");
this.hashes = hashes;
this.index = index;
this.hashType = hashType;
this.salted = salted;
this.pollard = pollard;
}
async verify(data) {
const hashFunc = branchHashOptions[this.hashType];
let proofHash = new ArrayBuffer(0);
if (this.salted) ;
else {
proofHash = hashFunc(data);
}
const ind = this.index + (1 << this.hashes.length);
for (let i = 0, ii = ind; i < this.hashes.length; i++, ii >>= 1) {
if (ii % 2 === 0) {
const concat = await new Blob([proofHash, this.hashes[i]]).arrayBuffer();
proofHash = hashFunc(concat);
} else {
const concat = await new Blob([this.hashes[i], proofHash]).arrayBuffer();
proofHash = hashFunc(concat);
}
}
const pHashes = this.pollard.getHashes();
for (let i = 0; i < pHashes.length / 2 + 1; i++) {
if (arrayBuffersMatch(pHashes[pHashes.length - 1 - i], proofHash)) {
return true;
}
}
return false;
}
}
exports.Merkletree = Merkletree;
exports.MerkletreeCompact = MerkletreeCompact;
exports.MultiProof = MultiProof;
exports.Pollard = Pollard;
exports.Proof = Proof;