|
1 | 1 | import { describe, expect, it } from "@jest/globals"; |
2 | | -import { MerkleTree } from "../index"; |
| 2 | +import { MerkleTree, MerkleTreeError, defaultHash } from "../index"; |
| 3 | +import { Buffer } from "buffer"; |
3 | 4 |
|
4 | 5 | describe("MerkleTree", () => { |
5 | | - it("should create a new Merkle tree", () => { |
6 | | - const data = ["a", "b", "c", "d"]; |
7 | | - const tree = new MerkleTree(data); |
8 | | - expect(tree).toBeDefined(); |
9 | | - expect(tree.root).toBeDefined(); |
| 6 | + describe("Constructor", () => { |
| 7 | + it("should create a tree with valid data", () => { |
| 8 | + const data = ["a", "b", "c", "d"]; |
| 9 | + const tree = new MerkleTree(data); |
| 10 | + expect(tree).toBeDefined(); |
| 11 | + expect(tree.root).toBeDefined(); |
| 12 | + expect(tree.leafCount).toBe(4); |
| 13 | + expect(tree.depth).toBe(3); |
| 14 | + }); |
| 15 | + |
| 16 | + it("should throw on empty array", () => { |
| 17 | + expect(() => new MerkleTree([])).toThrow(MerkleTreeError); |
| 18 | + }); |
| 19 | + |
| 20 | + it("should throw on invalid hash function", () => { |
| 21 | + expect(() => new MerkleTree(["a"], "not a function" as any)).toThrow(MerkleTreeError); |
| 22 | + }); |
| 23 | + |
| 24 | + it("should handle single leaf", () => { |
| 25 | + const tree = new MerkleTree(["a"]); |
| 26 | + expect(tree.leafCount).toBe(1); |
| 27 | + expect(tree.depth).toBe(1); |
| 28 | + expect(tree.root).toBeDefined(); |
| 29 | + }); |
| 30 | + |
| 31 | + it("should handle odd number of leaves", () => { |
| 32 | + const tree = new MerkleTree(["a", "b", "c"]); |
| 33 | + expect(tree.leafCount).toBe(3); |
| 34 | + expect(tree.depth).toBe(3); |
| 35 | + }); |
| 36 | + |
| 37 | + it("should handle large number of leaves", () => { |
| 38 | + const data = Array.from({ length: 1000 }, (_, i) => `data${i}`); |
| 39 | + const tree = new MerkleTree(data); |
| 40 | + expect(tree.leafCount).toBe(1000); |
| 41 | + expect(tree.depth).toBe(11); // log2(1000) rounded up |
| 42 | + }); |
| 43 | + |
| 44 | + it("should handle Buffer inputs", () => { |
| 45 | + const data = [Buffer.from("a"), Buffer.from("b")]; |
| 46 | + const tree = new MerkleTree(data); |
| 47 | + expect(tree.leafCount).toBe(2); |
| 48 | + }); |
| 49 | + |
| 50 | + it("should handle mixed string and Buffer inputs", () => { |
| 51 | + const data = ["a", Buffer.from("b")]; |
| 52 | + const tree = new MerkleTree(data); |
| 53 | + expect(tree.leafCount).toBe(2); |
| 54 | + }); |
10 | 55 | }); |
11 | 56 |
|
12 | | - it("should generate a valid proof", () => { |
13 | | - const data = ["a", "b", "c", "d"]; |
14 | | - const tree = new MerkleTree(data); |
15 | | - const proof = tree.getProof(2); // Proof for 'c' |
16 | | - expect(proof).toBeDefined(); |
17 | | - expect(Array.isArray(proof)).toBe(true); |
| 57 | + describe("Tree Structure", () => { |
| 58 | + it("should have correct tree levels", () => { |
| 59 | + const tree = new MerkleTree(["a", "b", "c", "d"]); |
| 60 | + expect(tree.tree.length).toBe(3); // 4 leaves -> 2 nodes -> 1 root |
| 61 | + expect(tree.tree[0].length).toBe(4); // leaves |
| 62 | + expect(tree.tree[1].length).toBe(2); // intermediate |
| 63 | + expect(tree.tree[2].length).toBe(1); // root |
| 64 | + }); |
| 65 | + |
| 66 | + it("should handle duplicate leaves", () => { |
| 67 | + const tree = new MerkleTree(["a", "a", "a", "a"]); |
| 68 | + const uniqueHashes = new Set(tree.getLeaves().map(h => h.toString("hex"))); |
| 69 | + expect(uniqueHashes.size).toBe(1); |
| 70 | + }); |
18 | 71 | }); |
19 | 72 |
|
20 | | - it("should verify a valid proof", () => { |
21 | | - const data = ["a", "b", "c", "d"]; |
22 | | - const tree = new MerkleTree(data); |
23 | | - const proof = tree.getProof(2); |
24 | | - const leafHash = tree.getLeaf(2); |
25 | | - const isValid = MerkleTree.verifyProof(leafHash, proof, tree.root); |
26 | | - expect(isValid).toBe(true); |
| 73 | + describe("Proof Generation", () => { |
| 74 | + it("should generate valid proof for first leaf", () => { |
| 75 | + const tree = new MerkleTree(["a", "b", "c", "d"]); |
| 76 | + const proof = tree.getProof(0); |
| 77 | + expect(proof.length).toBe(2); |
| 78 | + expect(proof[0].position).toBe("right"); |
| 79 | + }); |
| 80 | + |
| 81 | + it("should generate valid proof for last leaf", () => { |
| 82 | + const tree = new MerkleTree(["a", "b", "c", "d"]); |
| 83 | + const proof = tree.getProof(3); |
| 84 | + expect(proof.length).toBe(2); |
| 85 | + expect(proof[0].position).toBe("left"); |
| 86 | + }); |
| 87 | + |
| 88 | + it("should throw on invalid index", () => { |
| 89 | + const tree = new MerkleTree(["a", "b", "c", "d"]); |
| 90 | + expect(() => tree.getProof(-1)).toThrow(MerkleTreeError); |
| 91 | + expect(() => tree.getProof(4)).toThrow(MerkleTreeError); |
| 92 | + expect(() => tree.getProof(1.5)).toThrow(MerkleTreeError); |
| 93 | + }); |
| 94 | + |
| 95 | + it("should generate consistent proofs", () => { |
| 96 | + const tree = new MerkleTree(["a", "b", "c", "d"]); |
| 97 | + const proof1 = tree.getProof(1); |
| 98 | + const proof2 = tree.getProof(1); |
| 99 | + expect(proof1).toEqual(proof2); |
| 100 | + }); |
| 101 | + }); |
| 102 | + |
| 103 | + describe("Proof Verification", () => { |
| 104 | + it("should verify valid proof", () => { |
| 105 | + const tree = new MerkleTree(["a", "b", "c", "d"]); |
| 106 | + const proof = tree.getProof(2); |
| 107 | + const leafHash = tree.getLeaf(2); |
| 108 | + expect(MerkleTree.verifyProof(leafHash, proof, tree.root)).toBe(true); |
| 109 | + }); |
| 110 | + |
| 111 | + it("should reject invalid proof", () => { |
| 112 | + const tree = new MerkleTree(["a", "b", "c", "d"]); |
| 113 | + const proof = tree.getProof(2); |
| 114 | + const wrongLeafHash = tree.getLeaf(1); |
| 115 | + expect(MerkleTree.verifyProof(wrongLeafHash, proof, tree.root)).toBe(false); |
| 116 | + }); |
| 117 | + |
| 118 | + it("should throw on invalid proof format", () => { |
| 119 | + const tree = new MerkleTree(["a", "b"]); |
| 120 | + expect(() => MerkleTree.verifyProof( |
| 121 | + tree.getLeaf(0), |
| 122 | + [{ sibling: "invalid" as any, position: "right" }], |
| 123 | + tree.root |
| 124 | + )).toThrow(MerkleTreeError); |
| 125 | + }); |
| 126 | + }); |
| 127 | + |
| 128 | + describe("Serialization", () => { |
| 129 | + it("should serialize and deserialize correctly", () => { |
| 130 | + const original = new MerkleTree(["a", "b", "c", "d"]); |
| 131 | + const serialized = original.toJSON(); |
| 132 | + const reconstructed = MerkleTree.fromJSON(serialized); |
| 133 | + |
| 134 | + expect(reconstructed.root).toEqual(original.root); |
| 135 | + expect(reconstructed.leafCount).toBe(original.leafCount); |
| 136 | + expect(reconstructed.depth).toBe(original.depth); |
| 137 | + }); |
| 138 | + |
| 139 | + it("should throw on invalid JSON", () => { |
| 140 | + expect(() => MerkleTree.fromJSON("invalid json")).toThrow(MerkleTreeError); |
| 141 | + }); |
| 142 | + |
| 143 | + it("should throw on malformed tree data", () => { |
| 144 | + const invalidData = JSON.stringify({ |
| 145 | + leaves: ["invalid"], |
| 146 | + tree: [["invalid"]] |
| 147 | + }); |
| 148 | + expect(() => MerkleTree.fromJSON(invalidData)).toThrow(MerkleTreeError); |
| 149 | + }); |
| 150 | + }); |
| 151 | + |
| 152 | + describe("Custom Hash Function", () => { |
| 153 | + it("should work with custom hash function", () => { |
| 154 | + const customHash = (data: Buffer) => Buffer.from("custom" + data.toString()); |
| 155 | + const tree = new MerkleTree(["a", "b"], customHash); |
| 156 | + expect(tree.root).toBeDefined(); |
| 157 | + }); |
| 158 | + |
| 159 | + it("should maintain consistency with custom hash", () => { |
| 160 | + const customHash = (data: Buffer) => Buffer.from("custom" + data.toString()); |
| 161 | + const tree1 = new MerkleTree(["a", "b"], customHash); |
| 162 | + const tree2 = new MerkleTree(["a", "b"], customHash); |
| 163 | + expect(tree1.root).toEqual(tree2.root); |
| 164 | + }); |
| 165 | + }); |
| 166 | + |
| 167 | + describe("Edge Cases", () => { |
| 168 | + it("should handle very long input strings", () => { |
| 169 | + const longString = "a".repeat(10000); |
| 170 | + const tree = new MerkleTree([longString]); |
| 171 | + expect(tree.root).toBeDefined(); |
| 172 | + }); |
| 173 | + |
| 174 | + it("should handle special characters", () => { |
| 175 | + const data = ["!@#$%^&*()", "你好世界", "🌍"]; |
| 176 | + const tree = new MerkleTree(data); |
| 177 | + expect(tree.root).toBeDefined(); |
| 178 | + }); |
| 179 | + |
| 180 | + it("should handle empty strings", () => { |
| 181 | + const tree = new MerkleTree(["", "b"]); |
| 182 | + expect(tree.root).toBeDefined(); |
| 183 | + }); |
| 184 | + |
| 185 | + it("should handle repeated patterns", () => { |
| 186 | + const data = Array(100).fill("a"); |
| 187 | + const tree = new MerkleTree(data); |
| 188 | + expect(tree.root).toBeDefined(); |
| 189 | + }); |
27 | 190 | }); |
28 | 191 | }); |
0 commit comments