MerkleProof.test.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. const { expectRevert } = require('@openzeppelin/test-helpers');
  2. const { MerkleTree } = require('merkletreejs');
  3. const keccak256 = require('keccak256');
  4. const { expect } = require('chai');
  5. const { expectRevertCustomError } = require('../../helpers/customError');
  6. const MerkleProof = artifacts.require('$MerkleProof');
  7. contract('MerkleProof', function () {
  8. beforeEach(async function () {
  9. this.merkleProof = await MerkleProof.new();
  10. });
  11. describe('verify', function () {
  12. it('returns true for a valid Merkle proof', async function () {
  13. const elements = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split('');
  14. const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true, sortPairs: true });
  15. const root = merkleTree.getHexRoot();
  16. const leaf = keccak256(elements[0]);
  17. const proof = merkleTree.getHexProof(leaf);
  18. expect(await this.merkleProof.$verify(proof, root, leaf)).to.equal(true);
  19. expect(await this.merkleProof.$verifyCalldata(proof, root, leaf)).to.equal(true);
  20. // For demonstration, it is also possible to create valid proofs for certain 64-byte values *not* in elements:
  21. const noSuchLeaf = keccak256(
  22. Buffer.concat([keccak256(elements[0]), keccak256(elements[1])].sort(Buffer.compare)),
  23. );
  24. expect(await this.merkleProof.$verify(proof.slice(1), root, noSuchLeaf)).to.equal(true);
  25. expect(await this.merkleProof.$verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.equal(true);
  26. });
  27. it('returns false for an invalid Merkle proof', async function () {
  28. const correctElements = ['a', 'b', 'c'];
  29. const correctMerkleTree = new MerkleTree(correctElements, keccak256, { hashLeaves: true, sortPairs: true });
  30. const correctRoot = correctMerkleTree.getHexRoot();
  31. const correctLeaf = keccak256(correctElements[0]);
  32. const badElements = ['d', 'e', 'f'];
  33. const badMerkleTree = new MerkleTree(badElements);
  34. const badProof = badMerkleTree.getHexProof(badElements[0]);
  35. expect(await this.merkleProof.$verify(badProof, correctRoot, correctLeaf)).to.equal(false);
  36. expect(await this.merkleProof.$verifyCalldata(badProof, correctRoot, correctLeaf)).to.equal(false);
  37. });
  38. it('returns false for a Merkle proof of invalid length', async function () {
  39. const elements = ['a', 'b', 'c'];
  40. const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true, sortPairs: true });
  41. const root = merkleTree.getHexRoot();
  42. const leaf = keccak256(elements[0]);
  43. const proof = merkleTree.getHexProof(leaf);
  44. const badProof = proof.slice(0, proof.length - 5);
  45. expect(await this.merkleProof.$verify(badProof, root, leaf)).to.equal(false);
  46. expect(await this.merkleProof.$verifyCalldata(badProof, root, leaf)).to.equal(false);
  47. });
  48. });
  49. describe('multiProofVerify', function () {
  50. it('returns true for a valid Merkle multi proof', async function () {
  51. const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(keccak256).sort(Buffer.compare);
  52. const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
  53. const root = merkleTree.getRoot();
  54. const proofLeaves = ['b', 'f', 'd'].map(keccak256).sort(Buffer.compare);
  55. const proof = merkleTree.getMultiProof(proofLeaves);
  56. const proofFlags = merkleTree.getProofFlags(proofLeaves, proof);
  57. expect(await this.merkleProof.$multiProofVerify(proof, proofFlags, root, proofLeaves)).to.equal(true);
  58. expect(await this.merkleProof.$multiProofVerifyCalldata(proof, proofFlags, root, proofLeaves)).to.equal(true);
  59. });
  60. it('returns false for an invalid Merkle multi proof', async function () {
  61. const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(keccak256).sort(Buffer.compare);
  62. const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
  63. const root = merkleTree.getRoot();
  64. const badProofLeaves = ['g', 'h', 'i'].map(keccak256).sort(Buffer.compare);
  65. const badMerkleTree = new MerkleTree(badProofLeaves);
  66. const badProof = badMerkleTree.getMultiProof(badProofLeaves);
  67. const badProofFlags = badMerkleTree.getProofFlags(badProofLeaves, badProof);
  68. expect(await this.merkleProof.$multiProofVerify(badProof, badProofFlags, root, badProofLeaves)).to.equal(false);
  69. expect(await this.merkleProof.$multiProofVerifyCalldata(badProof, badProofFlags, root, badProofLeaves)).to.equal(
  70. false,
  71. );
  72. });
  73. it('revert with invalid multi proof #1', async function () {
  74. const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch
  75. const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
  76. const badLeaf = keccak256('e');
  77. const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
  78. const root = merkleTree.getRoot();
  79. await expectRevertCustomError(
  80. this.merkleProof.$multiProofVerify(
  81. [leaves[1], fill, merkleTree.layers[1][1]],
  82. [false, false, false],
  83. root,
  84. [leaves[0], badLeaf], // A, E
  85. ),
  86. 'MerkleProofInvalidMultiproof',
  87. [],
  88. );
  89. await expectRevertCustomError(
  90. this.merkleProof.$multiProofVerifyCalldata(
  91. [leaves[1], fill, merkleTree.layers[1][1]],
  92. [false, false, false],
  93. root,
  94. [leaves[0], badLeaf], // A, E
  95. ),
  96. 'MerkleProofInvalidMultiproof',
  97. [],
  98. );
  99. });
  100. it('revert with invalid multi proof #2', async function () {
  101. const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch
  102. const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
  103. const badLeaf = keccak256('e');
  104. const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
  105. const root = merkleTree.getRoot();
  106. await expectRevert(
  107. this.merkleProof.$multiProofVerify(
  108. [leaves[1], fill, merkleTree.layers[1][1]],
  109. [false, false, false, false],
  110. root,
  111. [badLeaf, leaves[0]], // A, E
  112. ),
  113. 'reverted with panic code 0x32',
  114. );
  115. await expectRevert(
  116. this.merkleProof.$multiProofVerifyCalldata(
  117. [leaves[1], fill, merkleTree.layers[1][1]],
  118. [false, false, false, false],
  119. root,
  120. [badLeaf, leaves[0]], // A, E
  121. ),
  122. 'reverted with panic code 0x32',
  123. );
  124. });
  125. it('limit case: works for tree containing a single leaf', async function () {
  126. const leaves = ['a'].map(keccak256).sort(Buffer.compare);
  127. const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
  128. const root = merkleTree.getRoot();
  129. const proofLeaves = ['a'].map(keccak256).sort(Buffer.compare);
  130. const proof = merkleTree.getMultiProof(proofLeaves);
  131. const proofFlags = merkleTree.getProofFlags(proofLeaves, proof);
  132. expect(await this.merkleProof.$multiProofVerify(proof, proofFlags, root, proofLeaves)).to.equal(true);
  133. expect(await this.merkleProof.$multiProofVerifyCalldata(proof, proofFlags, root, proofLeaves)).to.equal(true);
  134. });
  135. it('limit case: can prove empty leaves', async function () {
  136. const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
  137. const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });
  138. const root = merkleTree.getRoot();
  139. expect(await this.merkleProof.$multiProofVerify([root], [], root, [])).to.equal(true);
  140. expect(await this.merkleProof.$multiProofVerifyCalldata([root], [], root, [])).to.equal(true);
  141. });
  142. it('reverts processing manipulated proofs with a zero-value node at depth 1', async function () {
  143. // Create a merkle tree that contains a zero leaf at depth 1
  144. const leaves = [keccak256('real leaf'), Buffer.alloc(32, 0)];
  145. const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true });
  146. const root = merkleTree.getRoot();
  147. // Now we can pass any **malicious** fake leaves as valid!
  148. const maliciousLeaves = ['malicious', 'leaves'].map(keccak256).sort(Buffer.compare);
  149. const maliciousProof = [leaves[0], leaves[0]];
  150. const maliciousProofFlags = [true, true, false];
  151. await expectRevertCustomError(
  152. this.merkleProof.$multiProofVerify(maliciousProof, maliciousProofFlags, root, maliciousLeaves),
  153. 'MerkleProofInvalidMultiproof',
  154. [],
  155. );
  156. await expectRevertCustomError(
  157. this.merkleProof.$multiProofVerifyCalldata(maliciousProof, maliciousProofFlags, root, maliciousLeaves),
  158. 'MerkleProofInvalidMultiproof',
  159. [],
  160. );
  161. });
  162. });
  163. });