MerkleProof.test.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { StandardMerkleTree } = require('@openzeppelin/merkle-tree');
  5. const toElements = str => str.split('').map(e => [e]);
  6. const hashPair = (a, b) => ethers.keccak256(Buffer.concat([a, b].sort(Buffer.compare)));
  7. async function fixture() {
  8. const mock = await ethers.deployContract('$MerkleProof');
  9. return { mock };
  10. }
  11. describe('MerkleProof', function () {
  12. beforeEach(async function () {
  13. Object.assign(this, await loadFixture(fixture));
  14. });
  15. describe('verify', function () {
  16. it('returns true for a valid Merkle proof', async function () {
  17. const merkleTree = StandardMerkleTree.of(
  18. toElements('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='),
  19. ['string'],
  20. );
  21. const root = merkleTree.root;
  22. const hash = merkleTree.leafHash(['A']);
  23. const proof = merkleTree.getProof(['A']);
  24. expect(await this.mock.$verify(proof, root, hash)).to.be.true;
  25. expect(await this.mock.$verifyCalldata(proof, root, hash)).to.be.true;
  26. // For demonstration, it is also possible to create valid proofs for certain 64-byte values *not* in elements:
  27. const noSuchLeaf = hashPair(
  28. ethers.toBeArray(merkleTree.leafHash(['A'])),
  29. ethers.toBeArray(merkleTree.leafHash(['B'])),
  30. );
  31. expect(await this.mock.$verify(proof.slice(1), root, noSuchLeaf)).to.be.true;
  32. expect(await this.mock.$verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.be.true;
  33. });
  34. it('returns false for an invalid Merkle proof', async function () {
  35. const correctMerkleTree = StandardMerkleTree.of(toElements('abc'), ['string']);
  36. const otherMerkleTree = StandardMerkleTree.of(toElements('def'), ['string']);
  37. const root = correctMerkleTree.root;
  38. const hash = correctMerkleTree.leafHash(['a']);
  39. const proof = otherMerkleTree.getProof(['d']);
  40. expect(await this.mock.$verify(proof, root, hash)).to.be.false;
  41. expect(await this.mock.$verifyCalldata(proof, root, hash)).to.be.false;
  42. });
  43. it('returns false for a Merkle proof of invalid length', async function () {
  44. const merkleTree = StandardMerkleTree.of(toElements('abc'), ['string']);
  45. const root = merkleTree.root;
  46. const leaf = merkleTree.leafHash(['a']);
  47. const proof = merkleTree.getProof(['a']);
  48. const badProof = proof.slice(0, proof.length - 5);
  49. expect(await this.mock.$verify(badProof, root, leaf)).to.be.false;
  50. expect(await this.mock.$verifyCalldata(badProof, root, leaf)).to.be.false;
  51. });
  52. });
  53. describe('multiProofVerify', function () {
  54. it('returns true for a valid Merkle multi proof', async function () {
  55. const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']);
  56. const root = merkleTree.root;
  57. const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('bdf'));
  58. const hashes = leaves.map(e => merkleTree.leafHash(e));
  59. expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.true;
  60. expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.true;
  61. });
  62. it('returns false for an invalid Merkle multi proof', async function () {
  63. const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']);
  64. const otherMerkleTree = StandardMerkleTree.of(toElements('ghi'), ['string']);
  65. const root = merkleTree.root;
  66. const { proof, proofFlags, leaves } = otherMerkleTree.getMultiProof(toElements('ghi'));
  67. const hashes = leaves.map(e => merkleTree.leafHash(e));
  68. expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.false;
  69. expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.false;
  70. });
  71. it('revert with invalid multi proof #1', async function () {
  72. const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']);
  73. const root = merkleTree.root;
  74. const hashA = merkleTree.leafHash(['a']);
  75. const hashB = merkleTree.leafHash(['b']);
  76. const hashCD = hashPair(
  77. ethers.toBeArray(merkleTree.leafHash(['c'])),
  78. ethers.toBeArray(merkleTree.leafHash(['d'])),
  79. );
  80. const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree)
  81. const fill = ethers.randomBytes(32);
  82. await expect(
  83. this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]),
  84. ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
  85. await expect(
  86. this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]),
  87. ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
  88. });
  89. it('revert with invalid multi proof #2', async function () {
  90. const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']);
  91. const root = merkleTree.root;
  92. const hashA = merkleTree.leafHash(['a']);
  93. const hashB = merkleTree.leafHash(['b']);
  94. const hashCD = hashPair(
  95. ethers.toBeArray(merkleTree.leafHash(['c'])),
  96. ethers.toBeArray(merkleTree.leafHash(['d'])),
  97. );
  98. const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree)
  99. const fill = ethers.randomBytes(32);
  100. await expect(
  101. this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]),
  102. ).to.be.revertedWithPanic(0x32);
  103. await expect(
  104. this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]),
  105. ).to.be.revertedWithPanic(0x32);
  106. });
  107. it('limit case: works for tree containing a single leaf', async function () {
  108. const merkleTree = StandardMerkleTree.of(toElements('a'), ['string']);
  109. const root = merkleTree.root;
  110. const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('a'));
  111. const hashes = leaves.map(e => merkleTree.leafHash(e));
  112. expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.true;
  113. expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.true;
  114. });
  115. it('limit case: can prove empty leaves', async function () {
  116. const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']);
  117. const root = merkleTree.root;
  118. expect(await this.mock.$multiProofVerify([root], [], root, [])).to.be.true;
  119. expect(await this.mock.$multiProofVerifyCalldata([root], [], root, [])).to.be.true;
  120. });
  121. it('reverts processing manipulated proofs with a zero-value node at depth 1', async function () {
  122. // Create a merkle tree that contains a zero leaf at depth 1
  123. const leave = ethers.id('real leaf');
  124. const root = hashPair(ethers.toBeArray(leave), Buffer.alloc(32, 0));
  125. // Now we can pass any **malicious** fake leaves as valid!
  126. const maliciousLeaves = ['malicious', 'leaves'].map(ethers.id).map(ethers.toBeArray).sort(Buffer.compare);
  127. const maliciousProof = [leave, leave];
  128. const maliciousProofFlags = [true, true, false];
  129. await expect(
  130. this.mock.$multiProofVerify(maliciousProof, maliciousProofFlags, root, maliciousLeaves),
  131. ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
  132. await expect(
  133. this.mock.$multiProofVerifyCalldata(maliciousProof, maliciousProofFlags, root, maliciousLeaves),
  134. ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
  135. });
  136. });
  137. });