Browse Source

Remove merkleTree.js in favor of merkletreejs dependency (#2578)

Francisco Giordano 4 years ago
parent
commit
508a879ef0
4 changed files with 187 additions and 150 deletions
  1. 172 1
      package-lock.json
  2. 3 2
      package.json
  3. 0 135
      test/helpers/merkleTree.js
  4. 12 12
      test/utils/cryptography/MerkleProof.test.js

+ 172 - 1
package-lock.json

@@ -30,8 +30,10 @@
         "ethereumjs-wallet": "^1.0.1",
         "hardhat": "^2.0.6",
         "hardhat-gas-reporter": "^1.0.4",
+        "keccak256": "^1.0.2",
         "lodash.startcase": "^4.4.0",
         "lodash.zip": "^4.2.0",
+        "merkletreejs": "^0.2.13",
         "micromatch": "^4.0.2",
         "mocha": "^8.0.1",
         "rimraf": "^3.0.2",
@@ -4818,6 +4820,12 @@
       "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
       "dev": true
     },
+    "node_modules/buffer-reverse": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-reverse/-/buffer-reverse-1.0.1.tgz",
+      "integrity": "sha1-SSg8jvpvkBvAH6MwTQYCeXGuL2A=",
+      "dev": true
+    },
     "node_modules/buffer-to-arraybuffer": {
       "version": "0.0.5",
       "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz",
@@ -8869,7 +8877,104 @@
       "bundleDependencies": [
         "source-map-support",
         "yargs",
-        "ethereumjs-util"
+        "ethereumjs-util",
+        "@types/bn.js",
+        "@types/node",
+        "@types/pbkdf2",
+        "@types/secp256k1",
+        "ansi-regex",
+        "ansi-styles",
+        "base-x",
+        "blakejs",
+        "bn.js",
+        "brorand",
+        "browserify-aes",
+        "bs58",
+        "bs58check",
+        "buffer-from",
+        "buffer-xor",
+        "camelcase",
+        "cipher-base",
+        "cliui",
+        "color-convert",
+        "color-name",
+        "create-hash",
+        "create-hmac",
+        "cross-spawn",
+        "decamelize",
+        "elliptic",
+        "emoji-regex",
+        "end-of-stream",
+        "ethereum-cryptography",
+        "ethjs-util",
+        "evp_bytestokey",
+        "execa",
+        "find-up",
+        "get-caller-file",
+        "get-stream",
+        "hash-base",
+        "hash.js",
+        "hmac-drbg",
+        "inherits",
+        "invert-kv",
+        "is-fullwidth-code-point",
+        "is-hex-prefixed",
+        "is-stream",
+        "isexe",
+        "keccak",
+        "lcid",
+        "locate-path",
+        "map-age-cleaner",
+        "md5.js",
+        "mem",
+        "mimic-fn",
+        "minimalistic-assert",
+        "minimalistic-crypto-utils",
+        "nice-try",
+        "node-addon-api",
+        "node-gyp-build",
+        "npm-run-path",
+        "once",
+        "os-locale",
+        "p-defer",
+        "p-finally",
+        "p-is-promise",
+        "p-limit",
+        "p-locate",
+        "p-try",
+        "path-exists",
+        "path-key",
+        "pbkdf2",
+        "pump",
+        "randombytes",
+        "readable-stream",
+        "require-directory",
+        "require-main-filename",
+        "ripemd160",
+        "rlp",
+        "safe-buffer",
+        "scrypt-js",
+        "secp256k1",
+        "semver",
+        "set-blocking",
+        "setimmediate",
+        "sha.js",
+        "shebang-command",
+        "shebang-regex",
+        "signal-exit",
+        "source-map",
+        "string_decoder",
+        "string-width",
+        "strip-ansi",
+        "strip-eof",
+        "strip-hex-prefix",
+        "util-deprecate",
+        "which",
+        "which-module",
+        "wrap-ansi",
+        "wrappy",
+        "y18n",
+        "yargs-parser"
       ],
       "dev": true,
       "dependencies": {
@@ -12219,6 +12324,16 @@
         "node": ">=10.0.0"
       }
     },
+    "node_modules/keccak256": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.2.tgz",
+      "integrity": "sha512-f2EncSgmHmmQOkgxZ+/f2VaWTNkFL6f39VIrpoX+p8cEXJVyyCs/3h9GNz/ViHgwchxvv7oG5mjT2Tk4ZqInag==",
+      "dev": true,
+      "dependencies": {
+        "bn.js": "^4.11.8",
+        "keccak": "^3.0.1"
+      }
+    },
     "node_modules/keyv": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
@@ -13185,6 +13300,20 @@
         "safe-buffer": "^5.1.1"
       }
     },
+    "node_modules/merkletreejs": {
+      "version": "0.2.13",
+      "resolved": "https://registry.npmjs.org/merkletreejs/-/merkletreejs-0.2.13.tgz",
+      "integrity": "sha512-hnM1XX0C+3yfAytRiX7FKC+bYg+GC83aQq7EytAp6nbcUBRdXU6/AVkmNdsAaJJ9IaKZt0w76r0QeWY/Fq+uFw==",
+      "dev": true,
+      "dependencies": {
+        "buffer-reverse": "^1.0.1",
+        "crypto-js": "^3.1.9-1",
+        "treeify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 7.6.0"
+      }
+    },
     "node_modules/methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -17567,6 +17696,15 @@
         "node": ">=6"
       }
     },
+    "node_modules/treeify": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz",
+      "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
     "node_modules/true-case-path": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz",
@@ -23031,6 +23169,12 @@
       "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
       "dev": true
     },
+    "buffer-reverse": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-reverse/-/buffer-reverse-1.0.1.tgz",
+      "integrity": "sha1-SSg8jvpvkBvAH6MwTQYCeXGuL2A=",
+      "dev": true
+    },
     "buffer-to-arraybuffer": {
       "version": "0.0.5",
       "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz",
@@ -28994,6 +29138,16 @@
         "node-gyp-build": "^4.2.0"
       }
     },
+    "keccak256": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.2.tgz",
+      "integrity": "sha512-f2EncSgmHmmQOkgxZ+/f2VaWTNkFL6f39VIrpoX+p8cEXJVyyCs/3h9GNz/ViHgwchxvv7oG5mjT2Tk4ZqInag==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.11.8",
+        "keccak": "^3.0.1"
+      }
+    },
     "keyv": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
@@ -29827,6 +29981,17 @@
         }
       }
     },
+    "merkletreejs": {
+      "version": "0.2.13",
+      "resolved": "https://registry.npmjs.org/merkletreejs/-/merkletreejs-0.2.13.tgz",
+      "integrity": "sha512-hnM1XX0C+3yfAytRiX7FKC+bYg+GC83aQq7EytAp6nbcUBRdXU6/AVkmNdsAaJJ9IaKZt0w76r0QeWY/Fq+uFw==",
+      "dev": true,
+      "requires": {
+        "buffer-reverse": "^1.0.1",
+        "crypto-js": "^3.1.9-1",
+        "treeify": "^1.1.0"
+      }
+    },
     "methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -33311,6 +33476,12 @@
         }
       }
     },
+    "treeify": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz",
+      "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==",
+      "dev": true
+    },
     "true-case-path": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz",

+ 3 - 2
package.json

@@ -66,8 +66,10 @@
     "ethereumjs-wallet": "^1.0.1",
     "hardhat": "^2.0.6",
     "hardhat-gas-reporter": "^1.0.4",
+    "keccak256": "^1.0.2",
     "lodash.startcase": "^4.4.0",
     "lodash.zip": "^4.2.0",
+    "merkletreejs": "^0.2.13",
     "micromatch": "^4.0.2",
     "mocha": "^8.0.1",
     "rimraf": "^3.0.2",
@@ -75,6 +77,5 @@
     "solidity-coverage": "^0.7.11",
     "solidity-docgen": "^0.5.3",
     "web3": "^1.3.0"
-  },
-  "dependencies": {}
+  }
 }

+ 0 - 135
test/helpers/merkleTree.js

@@ -1,135 +0,0 @@
-const { keccak256, keccakFromString, bufferToHex } = require('ethereumjs-util');
-
-class MerkleTree {
-  constructor (elements) {
-    // Filter empty strings and hash elements
-    this.elements = elements.filter(el => el).map(el => keccakFromString(el));
-
-    // Sort elements
-    this.elements.sort(Buffer.compare);
-    // Deduplicate elements
-    this.elements = this.bufDedup(this.elements);
-
-    // Create layers
-    this.layers = this.getLayers(this.elements);
-  }
-
-  getLayers (elements) {
-    if (elements.length === 0) {
-      return [['']];
-    }
-
-    const layers = [];
-    layers.push(elements);
-
-    // Get next layer until we reach the root
-    while (layers[layers.length - 1].length > 1) {
-      layers.push(this.getNextLayer(layers[layers.length - 1]));
-    }
-
-    return layers;
-  }
-
-  getNextLayer (elements) {
-    return elements.reduce((layer, el, idx, arr) => {
-      if (idx % 2 === 0) {
-        // Hash the current element with its pair element
-        layer.push(this.combinedHash(el, arr[idx + 1]));
-      }
-
-      return layer;
-    }, []);
-  }
-
-  combinedHash (first, second) {
-    if (!first) { return second; }
-    if (!second) { return first; }
-
-    return keccak256(this.sortAndConcat(first, second));
-  }
-
-  getRoot () {
-    return this.layers[this.layers.length - 1][0];
-  }
-
-  getHexRoot () {
-    return bufferToHex(this.getRoot());
-  }
-
-  getProof (el) {
-    let idx = this.bufIndexOf(el, this.elements);
-
-    if (idx === -1) {
-      throw new Error('Element does not exist in Merkle tree');
-    }
-
-    return this.layers.reduce((proof, layer) => {
-      const pairElement = this.getPairElement(idx, layer);
-
-      if (pairElement) {
-        proof.push(pairElement);
-      }
-
-      idx = Math.floor(idx / 2);
-
-      return proof;
-    }, []);
-  }
-
-  getHexProof (el) {
-    const proof = this.getProof(el);
-
-    return this.bufArrToHexArr(proof);
-  }
-
-  getPairElement (idx, layer) {
-    const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1;
-
-    if (pairIdx < layer.length) {
-      return layer[pairIdx];
-    } else {
-      return null;
-    }
-  }
-
-  bufIndexOf (el, arr) {
-    let hash;
-
-    // Convert element to 32 byte hash if it is not one already
-    if (el.length !== 32 || !Buffer.isBuffer(el)) {
-      hash = keccakFromString(el);
-    } else {
-      hash = el;
-    }
-
-    for (let i = 0; i < arr.length; i++) {
-      if (hash.equals(arr[i])) {
-        return i;
-      }
-    }
-
-    return -1;
-  }
-
-  bufDedup (elements) {
-    return elements.filter((el, idx) => {
-      return idx === 0 || !elements[idx - 1].equals(el);
-    });
-  }
-
-  bufArrToHexArr (arr) {
-    if (arr.some(el => !Buffer.isBuffer(el))) {
-      throw new Error('Array is not an array of buffers');
-    }
-
-    return arr.map(el => '0x' + el.toString('hex'));
-  }
-
-  sortAndConcat (...args) {
-    return Buffer.concat([...args].sort(Buffer.compare));
-  }
-}
-
-module.exports = {
-  MerkleTree,
-};

+ 12 - 12
test/utils/cryptography/MerkleProof.test.js

@@ -1,7 +1,7 @@
 require('@openzeppelin/test-helpers');
 
-const { MerkleTree } = require('../../helpers/merkleTree.js');
-const { keccakFromString, bufferToHex } = require('ethereumjs-util');
+const { MerkleTree } = require('merkletreejs');
+const keccak256 = require('keccak256');
 
 const { expect } = require('chai');
 
@@ -15,43 +15,43 @@ contract('MerkleProof', function (accounts) {
   describe('verify', function () {
     it('returns true for a valid Merkle proof', async function () {
       const elements = ['a', 'b', 'c', 'd'];
-      const merkleTree = new MerkleTree(elements);
+      const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true });
 
       const root = merkleTree.getHexRoot();
 
-      const proof = merkleTree.getHexProof(elements[0]);
+      const leaf = keccak256(elements[0]);
 
-      const leaf = bufferToHex(keccakFromString(elements[0]));
+      const proof = merkleTree.getHexProof(leaf);
 
       expect(await this.merkleProof.verify(proof, root, leaf)).to.equal(true);
     });
 
     it('returns false for an invalid Merkle proof', async function () {
       const correctElements = ['a', 'b', 'c'];
-      const correctMerkleTree = new MerkleTree(correctElements);
+      const correctMerkleTree = new MerkleTree(correctElements, keccak256, { hashLeaves: true });
 
       const correctRoot = correctMerkleTree.getHexRoot();
 
-      const correctLeaf = bufferToHex(keccakFromString(correctElements[0]));
+      const correctLeaf = keccak256(correctElements[0]);
 
       const badElements = ['d', 'e', 'f'];
       const badMerkleTree = new MerkleTree(badElements);
 
-      const badProof = badMerkleTree.getHexProof(badElements[0]);
+      const badProof = badMerkleTree.getHexProof(badElements[0], keccak256, { hashLeaves: true });
 
       expect(await this.merkleProof.verify(badProof, correctRoot, correctLeaf)).to.equal(false);
     });
 
     it('returns false for a Merkle proof of invalid length', async function () {
       const elements = ['a', 'b', 'c'];
-      const merkleTree = new MerkleTree(elements);
+      const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true });
 
       const root = merkleTree.getHexRoot();
 
-      const proof = merkleTree.getHexProof(elements[0]);
-      const badProof = proof.slice(0, proof.length - 5);
+      const leaf = keccak256(elements[0]);
 
-      const leaf = bufferToHex(keccakFromString(elements[0]));
+      const proof = merkleTree.getHexProof(leaf);
+      const badProof = proof.slice(0, proof.length - 5);
 
       expect(await this.merkleProof.verify(badProof, root, leaf)).to.equal(false);
     });