@osmura/merkletreejs 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/dist/Base.d.ts +212 -0
- package/dist/Base.js +379 -0
- package/dist/IncrementalMerkleTree.d.ts +36 -0
- package/dist/IncrementalMerkleTree.js +252 -0
- package/dist/MerkleMountainRange.d.ts +95 -0
- package/dist/MerkleMountainRange.js +436 -0
- package/dist/MerkleRadixTree.d.ts +33 -0
- package/dist/MerkleRadixTree.js +152 -0
- package/dist/MerkleSumTree.d.ts +37 -0
- package/dist/MerkleSumTree.js +138 -0
- package/dist/MerkleTree.d.ts +570 -0
- package/dist/MerkleTree.js +1326 -0
- package/dist/UnifiedBinaryTree.d.ts +454 -0
- package/dist/UnifiedBinaryTree.js +829 -0
- package/dist/functional.d.ts +255 -0
- package/dist/functional.js +360 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +30 -0
- package/merkletree.js +1 -0
- package/package.json +155 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MerkleRadixTree = void 0;
|
|
7
|
+
const Base_1 = __importDefault(require("./Base"));
|
|
8
|
+
class MerkleRadixNode {
|
|
9
|
+
constructor(key = '', value = null, hashFn) {
|
|
10
|
+
this.key = key;
|
|
11
|
+
this.value = value;
|
|
12
|
+
this.children = new Map();
|
|
13
|
+
this.hashFn = hashFn;
|
|
14
|
+
this.hash = this.computeHash();
|
|
15
|
+
}
|
|
16
|
+
computeHash() {
|
|
17
|
+
let hash = this.hashFn('');
|
|
18
|
+
hash = Buffer.concat([hash, Base_1.default.bufferify(this.key), this.value != null ? Base_1.default.bufferify(this.value) : Buffer.alloc(0)]);
|
|
19
|
+
for (const child of this.children.values()) {
|
|
20
|
+
hash = Buffer.concat([hash, child.hash]);
|
|
21
|
+
}
|
|
22
|
+
const result = this.hashFn(hash);
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
updateHash() {
|
|
26
|
+
this.hash = this.computeHash();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class MerkleRadixTree extends Base_1.default {
|
|
30
|
+
constructor(hashFn) {
|
|
31
|
+
super();
|
|
32
|
+
this.hashFn = this.bufferifyFn(hashFn);
|
|
33
|
+
this.root = new MerkleRadixNode('', null, this.hashFn);
|
|
34
|
+
}
|
|
35
|
+
insert(key, value) {
|
|
36
|
+
let node = this.root;
|
|
37
|
+
let commonPrefixLength = 0;
|
|
38
|
+
while (key.length > 0) {
|
|
39
|
+
const child = [...node.children.values()].find(child => key.startsWith(child.key));
|
|
40
|
+
if (!child) {
|
|
41
|
+
node.children.set(key, new MerkleRadixNode(key, value, this.hashFn));
|
|
42
|
+
node.updateHash(); // Update the hash of the current node
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
commonPrefixLength = this.commonPrefixLength(key, child.key);
|
|
46
|
+
if (commonPrefixLength === child.key.length) {
|
|
47
|
+
node = child;
|
|
48
|
+
key = key.slice(commonPrefixLength);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const commonPrefix = key.slice(0, commonPrefixLength);
|
|
52
|
+
const childSuffix = child.key.slice(commonPrefixLength);
|
|
53
|
+
const newNode = new MerkleRadixNode(commonPrefix, null, this.hashFn);
|
|
54
|
+
node.children.delete(child.key);
|
|
55
|
+
node.children.set(commonPrefix, newNode);
|
|
56
|
+
newNode.children.set(childSuffix, child);
|
|
57
|
+
child.key = childSuffix;
|
|
58
|
+
if (commonPrefixLength < key.length) {
|
|
59
|
+
const suffix = key.slice(commonPrefixLength);
|
|
60
|
+
newNode.children.set(suffix, new MerkleRadixNode(suffix, value, this.hashFn));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
newNode.value = value;
|
|
64
|
+
}
|
|
65
|
+
node.updateHash();
|
|
66
|
+
newNode.updateHash(); // Update the hash of the new node
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
node.value = value;
|
|
71
|
+
node.updateHash(); // Update the hash of the node where the value was inserted
|
|
72
|
+
}
|
|
73
|
+
lookup(key) {
|
|
74
|
+
let node = this.root;
|
|
75
|
+
while (key.length > 0) {
|
|
76
|
+
const child = [...node.children.values()].find(child => key.startsWith(child.key));
|
|
77
|
+
if (!child) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const commonPrefixLength = this.commonPrefixLength(key, child.key);
|
|
81
|
+
if (commonPrefixLength === child.key.length) {
|
|
82
|
+
node = child;
|
|
83
|
+
key = key.slice(commonPrefixLength);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return node.value;
|
|
90
|
+
}
|
|
91
|
+
commonPrefixLength(str1, str2) {
|
|
92
|
+
let length = 0;
|
|
93
|
+
while (length < str1.length && length < str2.length && str1[length] === str2[length]) {
|
|
94
|
+
length++;
|
|
95
|
+
}
|
|
96
|
+
return length;
|
|
97
|
+
}
|
|
98
|
+
generateProof(key) {
|
|
99
|
+
let node = this.root;
|
|
100
|
+
const proof = [];
|
|
101
|
+
while (key.length > 0) {
|
|
102
|
+
const siblings = [];
|
|
103
|
+
for (const child of node.children.values()) {
|
|
104
|
+
if (child.key !== key) {
|
|
105
|
+
siblings.push({
|
|
106
|
+
key: child.key,
|
|
107
|
+
hash: child.hash
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
proof.push({
|
|
112
|
+
key: node.key,
|
|
113
|
+
hash: node.hash,
|
|
114
|
+
siblings
|
|
115
|
+
});
|
|
116
|
+
const child = [...node.children.values()].find(child => key.startsWith(child.key));
|
|
117
|
+
if (!child) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const commonPrefixLength = this.commonPrefixLength(key, child.key);
|
|
121
|
+
if (commonPrefixLength === child.key.length) {
|
|
122
|
+
node = child;
|
|
123
|
+
key = key.slice(commonPrefixLength);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
proof.push({
|
|
130
|
+
key: node.key,
|
|
131
|
+
hash: node.hash,
|
|
132
|
+
siblings: []
|
|
133
|
+
});
|
|
134
|
+
return proof;
|
|
135
|
+
}
|
|
136
|
+
verifyProof(proof, rootHash) {
|
|
137
|
+
if (!proof || proof.length === 0) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
let currentHash = proof[proof.length - 1].hash;
|
|
141
|
+
for (let i = proof.length - 2; i >= 0; i--) {
|
|
142
|
+
const item = proof[i];
|
|
143
|
+
let concatenatedHash = Buffer.concat([this.hashFn(''), this.bufferify(item.key), currentHash]);
|
|
144
|
+
for (const sibling of item.siblings) {
|
|
145
|
+
concatenatedHash = Buffer.concat([concatenatedHash, sibling.hash]);
|
|
146
|
+
}
|
|
147
|
+
currentHash = this.hashFn(concatenatedHash);
|
|
148
|
+
}
|
|
149
|
+
return currentHash.equals(rootHash);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.MerkleRadixTree = MerkleRadixTree;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Base } from './Base';
|
|
3
|
+
declare type TValue = Buffer | BigInt | string | number | null | undefined;
|
|
4
|
+
declare type THashFn = (value: TValue) => Buffer;
|
|
5
|
+
export declare class Bucket {
|
|
6
|
+
size: BigInt;
|
|
7
|
+
hashed: Buffer;
|
|
8
|
+
parent: Bucket | null;
|
|
9
|
+
left: Bucket | null;
|
|
10
|
+
right: Bucket | null;
|
|
11
|
+
constructor(size: BigInt | number, hashed: Buffer);
|
|
12
|
+
}
|
|
13
|
+
export declare class Leaf {
|
|
14
|
+
hashFn: THashFn;
|
|
15
|
+
rng: BigInt[];
|
|
16
|
+
data: Buffer | null;
|
|
17
|
+
constructor(hashFn: THashFn, rng: (number | BigInt)[], data: Buffer | null);
|
|
18
|
+
getBucket(): Bucket;
|
|
19
|
+
}
|
|
20
|
+
export declare class ProofStep {
|
|
21
|
+
bucket: Bucket;
|
|
22
|
+
right: boolean;
|
|
23
|
+
constructor(bucket: Bucket, right: boolean);
|
|
24
|
+
}
|
|
25
|
+
export declare class MerkleSumTree extends Base {
|
|
26
|
+
hashFn: THashFn;
|
|
27
|
+
leaves: Leaf[];
|
|
28
|
+
buckets: Bucket[];
|
|
29
|
+
root: Bucket;
|
|
30
|
+
constructor(leaves: Leaf[], hashFn: THashFn);
|
|
31
|
+
sizeToBuffer(size: BigInt): Buffer;
|
|
32
|
+
static checkConsecutive(leaves: Leaf[]): void;
|
|
33
|
+
getProof(index: number | BigInt): any[];
|
|
34
|
+
sum(arr: BigInt[]): bigint;
|
|
35
|
+
verifyProof(root: Bucket, leaf: Leaf, proof: ProofStep[]): boolean;
|
|
36
|
+
}
|
|
37
|
+
export default MerkleSumTree;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MerkleSumTree = exports.ProofStep = exports.Leaf = exports.Bucket = void 0;
|
|
4
|
+
const Base_1 = require("./Base");
|
|
5
|
+
class Bucket {
|
|
6
|
+
constructor(size, hashed) {
|
|
7
|
+
this.size = BigInt(size);
|
|
8
|
+
this.hashed = hashed;
|
|
9
|
+
// each node in the tree can have a parent, and a left or right sibling
|
|
10
|
+
this.parent = null;
|
|
11
|
+
this.left = null;
|
|
12
|
+
this.right = null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.Bucket = Bucket;
|
|
16
|
+
class Leaf {
|
|
17
|
+
constructor(hashFn, rng, data) {
|
|
18
|
+
this.hashFn = hashFn;
|
|
19
|
+
this.rng = rng.map(x => BigInt(x));
|
|
20
|
+
this.data = data;
|
|
21
|
+
}
|
|
22
|
+
getBucket() {
|
|
23
|
+
let hashed;
|
|
24
|
+
if (this.data) {
|
|
25
|
+
hashed = this.hashFn(this.data);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
hashed = Buffer.alloc(32);
|
|
29
|
+
}
|
|
30
|
+
return new Bucket(BigInt(this.rng[1]) - BigInt(this.rng[0]), hashed);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.Leaf = Leaf;
|
|
34
|
+
class ProofStep {
|
|
35
|
+
constructor(bucket, right) {
|
|
36
|
+
this.bucket = bucket;
|
|
37
|
+
this.right = right; // whether the bucket hash should be appeded on the right side in this step (default is left
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.ProofStep = ProofStep;
|
|
41
|
+
class MerkleSumTree extends Base_1.Base {
|
|
42
|
+
constructor(leaves, hashFn) {
|
|
43
|
+
super();
|
|
44
|
+
this.leaves = leaves;
|
|
45
|
+
this.hashFn = hashFn;
|
|
46
|
+
MerkleSumTree.checkConsecutive(leaves);
|
|
47
|
+
this.buckets = [];
|
|
48
|
+
for (const l of leaves) {
|
|
49
|
+
this.buckets.push(l.getBucket());
|
|
50
|
+
}
|
|
51
|
+
let buckets = [];
|
|
52
|
+
for (const bucket of this.buckets) {
|
|
53
|
+
buckets.push(bucket);
|
|
54
|
+
}
|
|
55
|
+
while (buckets.length !== 1) {
|
|
56
|
+
const newBuckets = [];
|
|
57
|
+
while (buckets.length) {
|
|
58
|
+
if (buckets.length >= 2) {
|
|
59
|
+
const b1 = buckets.shift();
|
|
60
|
+
const b2 = buckets.shift();
|
|
61
|
+
const size = b1.size + b2.size;
|
|
62
|
+
const hashed = this.hashFn(Buffer.concat([this.sizeToBuffer(b1.size), this.bufferify(b1.hashed), this.sizeToBuffer(b2.size), this.bufferify(b2.hashed)]));
|
|
63
|
+
const b = new Bucket(size, hashed);
|
|
64
|
+
b2.parent = b;
|
|
65
|
+
b1.parent = b2.parent;
|
|
66
|
+
b1.right = b2;
|
|
67
|
+
b2.left = b1;
|
|
68
|
+
newBuckets.push(b);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
newBuckets.push(buckets.shift());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
buckets = newBuckets;
|
|
75
|
+
}
|
|
76
|
+
this.root = buckets[0];
|
|
77
|
+
}
|
|
78
|
+
sizeToBuffer(size) {
|
|
79
|
+
const buf = Buffer.alloc(8);
|
|
80
|
+
const view = new DataView(buf.buffer);
|
|
81
|
+
view.setBigInt64(0, BigInt(size), false); // true when little endian
|
|
82
|
+
return buf;
|
|
83
|
+
}
|
|
84
|
+
static checkConsecutive(leaves) {
|
|
85
|
+
let curr = BigInt(0);
|
|
86
|
+
for (const leaf of leaves) {
|
|
87
|
+
if (leaf.rng[0] !== curr) {
|
|
88
|
+
throw new Error('leaf ranges are invalid');
|
|
89
|
+
}
|
|
90
|
+
curr = BigInt(leaf.rng[1]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// gets inclusion/exclusion proof of a bucket in the specified index
|
|
94
|
+
getProof(index) {
|
|
95
|
+
let curr = this.buckets[Number(index)];
|
|
96
|
+
const proof = [];
|
|
97
|
+
while (curr && curr.parent) {
|
|
98
|
+
const right = !!curr.right;
|
|
99
|
+
const bucket = curr.right ? curr.right : curr.left;
|
|
100
|
+
curr = curr.parent;
|
|
101
|
+
proof.push(new ProofStep(bucket, right));
|
|
102
|
+
}
|
|
103
|
+
return proof;
|
|
104
|
+
}
|
|
105
|
+
sum(arr) {
|
|
106
|
+
let total = BigInt(0);
|
|
107
|
+
for (const value of arr) {
|
|
108
|
+
total += BigInt(value);
|
|
109
|
+
}
|
|
110
|
+
return total;
|
|
111
|
+
}
|
|
112
|
+
// validates the suppplied proof for a specified leaf according to the root bucket
|
|
113
|
+
verifyProof(root, leaf, proof) {
|
|
114
|
+
const rng = [this.sum(proof.filter(x => !x.right).map(x => x.bucket.size)), BigInt(root.size) - this.sum(proof.filter(x => x.right).map(x => x.bucket.size))];
|
|
115
|
+
if (!(rng[0] === leaf.rng[0] && rng[1] === leaf.rng[1])) {
|
|
116
|
+
// supplied steps are not routing to the range specified
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
let curr = leaf.getBucket();
|
|
120
|
+
let hashed;
|
|
121
|
+
for (const step of proof) {
|
|
122
|
+
if (step.right) {
|
|
123
|
+
hashed = this.hashFn(Buffer.concat([this.sizeToBuffer(curr.size), this.bufferify(curr.hashed), this.sizeToBuffer(step.bucket.size), this.bufferify(step.bucket.hashed)]));
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
hashed = this.hashFn(Buffer.concat([this.sizeToBuffer(step.bucket.size), this.bufferify(step.bucket.hashed), this.sizeToBuffer(curr.size), this.bufferify(curr.hashed)]));
|
|
127
|
+
}
|
|
128
|
+
curr = new Bucket(BigInt(curr.size) + BigInt(step.bucket.size), hashed);
|
|
129
|
+
}
|
|
130
|
+
return curr.size === root.size && curr.hashed.toString('hex') === root.hashed.toString('hex');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.MerkleSumTree = MerkleSumTree;
|
|
134
|
+
if (typeof window !== 'undefined') {
|
|
135
|
+
;
|
|
136
|
+
window.MerkleSumTree = MerkleSumTree;
|
|
137
|
+
}
|
|
138
|
+
exports.default = MerkleSumTree;
|