|
1 | | -// This script provides some useful methods for building MerkleTrees. It is essentially the uniswap implementation |
2 | | -// https://github.com/Uniswap/merkle-distributor/blob/master/src/merkle-tree.ts with some added convenience methods |
3 | | -// to take the leaves and conversion functions, so the user never has to work with buffers. |
4 | | -import { bufferToHex, keccak256 } from "ethereumjs-util"; |
5 | | - |
6 | | -export const EMPTY_MERKLE_ROOT = "0x0000000000000000000000000000000000000000000000000000000000000000"; |
7 | | -export class MerkleTree<T> { |
8 | | - private readonly elements: Buffer[]; |
9 | | - private readonly bufferElementPositionIndex: { [hexElement: string]: number }; |
10 | | - private readonly layers: Buffer[][]; |
11 | | - |
12 | | - constructor(leaves: T[], public readonly hashFn: (element: T) => string) { |
13 | | - this.elements = leaves.map((leaf) => this.leafToBuf(leaf)); |
14 | | - // Sort elements |
15 | | - this.elements.sort(Buffer.compare); |
16 | | - // Deduplicate elements |
17 | | - this.elements = MerkleTree.bufDedup(this.elements); |
18 | | - |
19 | | - this.bufferElementPositionIndex = this.elements.reduce<{ [hexElement: string]: number }>((memo, el, index) => { |
20 | | - memo[bufferToHex(el)] = index; |
21 | | - return memo; |
22 | | - }, {}); |
23 | | - |
24 | | - // Create layers |
25 | | - this.layers = this.getLayers(this.elements); |
26 | | - } |
27 | | - |
28 | | - isEmpty(): boolean { |
29 | | - return this.layers.length === 0; |
30 | | - } |
31 | | - |
32 | | - getLayers(elements: Buffer[]): Buffer[][] { |
33 | | - const layers: Buffer[][] = []; |
34 | | - if (elements.length === 0) return layers; |
35 | | - |
36 | | - layers.push(elements); |
37 | | - |
38 | | - // Get next layer until we reach the root |
39 | | - while (layers[layers.length - 1].length > 1) { |
40 | | - layers.push(this.getNextLayer(layers[layers.length - 1])); |
41 | | - } |
42 | | - |
43 | | - return layers; |
44 | | - } |
45 | | - |
46 | | - getNextLayer(elements: Buffer[]): Buffer[] { |
47 | | - return elements.reduce<Buffer[]>((layer, el, idx, arr) => { |
48 | | - if (idx % 2 === 0) { |
49 | | - // Hash the current element with its pair element |
50 | | - layer.push(MerkleTree.combinedHash(el, arr[idx + 1])); |
51 | | - } |
52 | | - |
53 | | - return layer; |
54 | | - }, []); |
55 | | - } |
56 | | - |
57 | | - static combinedHash(first: Buffer, second: Buffer): Buffer { |
58 | | - if (!first) { |
59 | | - return second; |
60 | | - } |
61 | | - if (!second) { |
62 | | - return first; |
63 | | - } |
64 | | - |
65 | | - return keccak256(MerkleTree.sortAndConcat(first, second)); |
66 | | - } |
67 | | - |
68 | | - getRoot(): Buffer { |
69 | | - return this.layers[this.layers.length - 1][0]; |
70 | | - } |
71 | | - |
72 | | - getHexRoot(): string { |
73 | | - if (this.isEmpty()) return EMPTY_MERKLE_ROOT; |
74 | | - return bufferToHex(this.getRoot()); |
75 | | - } |
76 | | - |
77 | | - getProof(leaf: T) { |
78 | | - return this.getProofRawBuf(this.leafToBuf(leaf)); |
79 | | - } |
80 | | - |
81 | | - getHexProof(leaf: T) { |
82 | | - return this.getHexProofRawBuf(this.leafToBuf(leaf)); |
83 | | - } |
84 | | - |
85 | | - leafToBuf(element: T): Buffer { |
86 | | - const hash = this.hashFn(element); |
87 | | - const hexString = hash.startsWith("0x") ? hash.substring(2) : hash; |
88 | | - return Buffer.from(hexString.toLowerCase(), "hex"); |
89 | | - } |
90 | | - |
91 | | - // Methods that take the raw buffers (hashes). |
92 | | - getProofRawBuf(element: Buffer) { |
93 | | - let idx = this.bufferElementPositionIndex[bufferToHex(element)]; |
94 | | - |
95 | | - if (typeof idx !== "number") { |
96 | | - throw new Error("Element does not exist in Merkle tree"); |
97 | | - } |
98 | | - |
99 | | - return this.layers.reduce((proof, layer) => { |
100 | | - const pairElement = MerkleTree.getPairElement(idx, layer); |
101 | | - |
102 | | - if (pairElement) { |
103 | | - proof.push(pairElement); |
104 | | - } |
105 | | - |
106 | | - idx = Math.floor(idx / 2); |
107 | | - |
108 | | - return proof; |
109 | | - }, []); |
110 | | - } |
111 | | - |
112 | | - getHexProofRawBuf(el: Buffer): string[] { |
113 | | - const proof = this.getProofRawBuf(el); |
114 | | - |
115 | | - return MerkleTree.bufArrToHexArr(proof); |
116 | | - } |
117 | | - |
118 | | - private static getPairElement(idx: number, layer: Buffer[]): Buffer | null { |
119 | | - const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; |
120 | | - |
121 | | - if (pairIdx < layer.length) { |
122 | | - return layer[pairIdx]; |
123 | | - } else { |
124 | | - return null; |
125 | | - } |
126 | | - } |
127 | | - |
128 | | - private static bufDedup(elements: Buffer[]): Buffer[] { |
129 | | - return elements.filter((el, idx) => { |
130 | | - return idx === 0 || !elements[idx - 1].equals(el); |
131 | | - }); |
132 | | - } |
133 | | - |
134 | | - private static bufArrToHexArr(arr: Buffer[]): string[] { |
135 | | - if (arr.some((el) => !Buffer.isBuffer(el))) { |
136 | | - throw new Error("Array is not an array of buffers"); |
137 | | - } |
138 | | - |
139 | | - return arr.map((el) => "0x" + el.toString("hex")); |
140 | | - } |
141 | | - |
142 | | - private static sortAndConcat(...args: Buffer[]): Buffer { |
143 | | - return Buffer.concat([...args].sort(Buffer.compare)); |
144 | | - } |
145 | | -} |
| 1 | +export { MerkleTree, EMPTY_MERKLE_ROOT } from "@uma/common"; |
0 commit comments