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

This commit is contained in:
Francisco Giordano
2021-03-10 14:30:16 -03:00
committed by GitHub
parent 5171e46c47
commit 508a879ef0
4 changed files with 187 additions and 150 deletions

173
package-lock.json generated
View File

@ -30,8 +30,10 @@
"ethereumjs-wallet": "^1.0.1", "ethereumjs-wallet": "^1.0.1",
"hardhat": "^2.0.6", "hardhat": "^2.0.6",
"hardhat-gas-reporter": "^1.0.4", "hardhat-gas-reporter": "^1.0.4",
"keccak256": "^1.0.2",
"lodash.startcase": "^4.4.0", "lodash.startcase": "^4.4.0",
"lodash.zip": "^4.2.0", "lodash.zip": "^4.2.0",
"merkletreejs": "^0.2.13",
"micromatch": "^4.0.2", "micromatch": "^4.0.2",
"mocha": "^8.0.1", "mocha": "^8.0.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
@ -4818,6 +4820,12 @@
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true "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": { "node_modules/buffer-to-arraybuffer": {
"version": "0.0.5", "version": "0.0.5",
"resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz",
@ -8869,7 +8877,104 @@
"bundleDependencies": [ "bundleDependencies": [
"source-map-support", "source-map-support",
"yargs", "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, "dev": true,
"dependencies": { "dependencies": {
@ -12219,6 +12324,16 @@
"node": ">=10.0.0" "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": { "node_modules/keyv": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
@ -13185,6 +13300,20 @@
"safe-buffer": "^5.1.1" "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": { "node_modules/methods": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@ -17567,6 +17696,15 @@
"node": ">=6" "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": { "node_modules/true-case-path": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", "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==", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true "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": { "buffer-to-arraybuffer": {
"version": "0.0.5", "version": "0.0.5",
"resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", "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" "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": { "keyv": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", "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": { "methods": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "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": { "true-case-path": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz",

View File

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

View File

@ -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,
};

View File

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