diff --git a/package-lock.json b/package-lock.json index 045ad27..22f9850 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "@iden3/js-merkletree", "version": "1.5.1", "license": "MIT or Apache-2.0", + "dependencies": { + "@ethersproject/abi": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0" + }, "devDependencies": { "@cspell/eslint-plugin": "^8.14.2", "@iden3/eslint-config": "https://github.com/iden3/eslint-config", @@ -1210,6 +1214,398 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@ethersproject/abi": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.8.0.tgz", + "integrity": "sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz", + "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/networks": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/web": "^5.8.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz", + "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz", + "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/rlp": "^5.8.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz", + "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", + "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", + "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz", + "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz", + "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", + "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", + "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@ethersproject/networks": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz", + "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", + "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", + "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", + "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "bn.js": "^5.2.1", + "elliptic": "6.6.1", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", + "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz", + "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz", + "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -2697,6 +3093,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2721,6 +3123,12 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -3555,6 +3963,27 @@ "dev": true, "license": "MIT" }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -4475,6 +4904,16 @@ "node": ">=8" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4488,6 +4927,17 @@ "node": ">= 0.4" } }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/idb-keyval": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", @@ -4605,7 +5055,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -4895,6 +5344,12 @@ "node": ">=10" } }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5187,6 +5642,18 @@ "dom-walk": "^0.1.0" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", diff --git a/package.json b/package.json index c2587e1..f599485 100644 --- a/package.json +++ b/package.json @@ -59,17 +59,17 @@ "deps:check": "madge --warning --circular --extensions ts ./" }, "devDependencies": { - "@iden3/eslint-config": "https://github.com/iden3/eslint-config", - "@types/node": "^22.15.19", - "mock-local-storage": "^1.1.23", - "fake-indexeddb": "^6.0.1", "@cspell/eslint-plugin": "^8.14.2", + "@iden3/eslint-config": "https://github.com/iden3/eslint-config", "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-typescript": "^12.1.2", "@types/jest": "^29.5.14", + "@types/node": "^22.15.19", "esbuild": "^0.25.4", + "fake-indexeddb": "^6.0.1", "madge": "^8.0.0", + "mock-local-storage": "^1.1.23", "rimraf": "^6.0.1", "rollup": "^4.41.0", "ts-node": "^10.9.2", @@ -80,5 +80,9 @@ "peerDependencies": { "@iden3/js-crypto": "1.3.2", "idb-keyval": "6.2.2" + }, + "dependencies": { + "@ethersproject/abi": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0" } } diff --git a/src/lib/db/inMemory.ts b/src/lib/db/inMemory.ts index cb6071b..6d80209 100644 --- a/src/lib/db/inMemory.ts +++ b/src/lib/db/inMemory.ts @@ -2,19 +2,22 @@ import { Bytes, Node } from '../../types'; import { ITreeStorage } from '../../types/storage'; -import { Hash, ZERO_HASH } from '../hash/hash'; +import { Hash, ZERO_HASH, HashAlgorithm } from '../hash/hash'; export class InMemoryDB implements ITreeStorage { prefix: Bytes; + _algo: HashAlgorithm; + private _kvMap: { [k in string]: Node; }; private _currentRoot: Hash; - constructor(_prefix: Bytes) { + constructor(_prefix: Bytes, algo?: HashAlgorithm) { this.prefix = _prefix; this._kvMap = {}; this._currentRoot = ZERO_HASH; + this._algo = algo ?? HashAlgorithm.Poseidon; } async get(k: Bytes): Promise { @@ -35,4 +38,7 @@ export class InMemoryDB implements ITreeStorage { async setRoot(r: Hash): Promise { this._currentRoot = r; } + getHashAlgorithm(): HashAlgorithm { + return this._algo; + } } diff --git a/src/lib/db/indexedDB.ts b/src/lib/db/indexedDB.ts index 0b97e32..0c1cb63 100644 --- a/src/lib/db/indexedDB.ts +++ b/src/lib/db/indexedDB.ts @@ -1,6 +1,6 @@ import { Bytes, Node } from '../../types'; import { ITreeStorage } from '../../types/storage'; -import { Hash, ZERO_HASH } from '../hash/hash'; +import { Hash, ZERO_HASH, HashAlgorithm } from '../hash/hash'; import { bytes2Hex } from '../utils'; import { get, set, UseStore, createStore } from 'idb-keyval'; import { NODE_TYPE_EMPTY, NODE_TYPE_LEAF, NODE_TYPE_MIDDLE } from '../../constants'; @@ -13,14 +13,16 @@ export class IndexedDBStorage implements ITreeStorage { private readonly _store: UseStore; private _currentRoot: Hash; + private _algo: HashAlgorithm; - constructor(private readonly _prefix: Bytes, databaseName?: string) { + constructor(private readonly _prefix: Bytes, algo?: HashAlgorithm, databaseName?: string) { this._currentRoot = ZERO_HASH; this._prefixHash = bytes2Hex(_prefix); this._store = createStore( `${databaseName ?? IndexedDBStorage.storageName}-db`, IndexedDBStorage.storageName ); + this._algo = algo ?? HashAlgorithm.Poseidon; } async get(k: Bytes): Promise { @@ -36,13 +38,13 @@ export class IndexedDBStorage implements ITreeStorage { if (obj.type === NODE_TYPE_MIDDLE) { const cL = new Hash(Uint8Array.from(obj.childL.bytes)); const cR = new Hash(Uint8Array.from(obj.childR.bytes)); - return new NodeMiddle(cL, cR); + return new NodeMiddle(cL, cR, this._algo); } if (obj.type === NODE_TYPE_LEAF) { const k = new Hash(Uint8Array.from(obj.entry[0].bytes)); const v = new Hash(Uint8Array.from(obj.entry[1].bytes)); - return new NodeLeaf(k, v); + return new NodeLeaf(k, v, this._algo); } throw new Error(`error: value found for key ${key} is not of type Node`); } @@ -71,4 +73,7 @@ export class IndexedDBStorage implements ITreeStorage { await set(this._prefixHash, r, this._store); this._currentRoot = r; } + getHashAlgorithm(): HashAlgorithm { + return this._algo; + } } diff --git a/src/lib/db/localStorage.ts b/src/lib/db/localStorage.ts index a910777..7570b72 100644 --- a/src/lib/db/localStorage.ts +++ b/src/lib/db/localStorage.ts @@ -2,15 +2,16 @@ import { Bytes, Node } from '../../types'; import { ITreeStorage } from '../../types/storage'; -import { Hash, ZERO_HASH } from '../hash/hash'; +import { Hash, ZERO_HASH, HashAlgorithm } from '../hash/hash'; import { NODE_TYPE_EMPTY, NODE_TYPE_LEAF, NODE_TYPE_MIDDLE } from '../../constants'; import { NodeEmpty, NodeLeaf, NodeMiddle } from '../node/node'; import { bytes2Hex } from '../utils'; export class LocalStorageDB implements ITreeStorage { private _currentRoot: Hash; + private _algo: HashAlgorithm; - constructor(private readonly _prefix: Bytes) { + constructor(private readonly _prefix: Bytes, algo?: HashAlgorithm) { const rootStr = localStorage.getItem(bytes2Hex(_prefix)); if (rootStr) { const bytes: number[] = JSON.parse(rootStr); @@ -19,6 +20,7 @@ export class LocalStorageDB implements ITreeStorage { } else { this._currentRoot = ZERO_HASH; } + this._algo = algo ?? HashAlgorithm.Poseidon; } async get(k: Bytes): Promise { @@ -38,12 +40,12 @@ export class LocalStorageDB implements ITreeStorage { const cL = new Hash(Uint8Array.from(obj.childL)); const cR = new Hash(Uint8Array.from(obj.childR)); - return new NodeMiddle(cL, cR); + return new NodeMiddle(cL, cR, this._algo); case NODE_TYPE_LEAF: const k = new Hash(Uint8Array.from(obj.entry[0])); const v = new Hash(Uint8Array.from(obj.entry[1])); - return new NodeLeaf(k, v); + return new NodeLeaf(k, v, this._algo); } throw `error: value found for key ${bytes2Hex(kBytes)} is not of type Node`; @@ -73,4 +75,7 @@ export class LocalStorageDB implements ITreeStorage { this._currentRoot = r; localStorage.setItem(bytes2Hex(this._prefix), JSON.stringify(Array.from(r.bytes))); } + getHashAlgorithm(): HashAlgorithm { + return this._algo; + } } diff --git a/src/lib/entry/entry.ts b/src/lib/entry/entry.ts index 9166c3b..4179f26 100644 --- a/src/lib/entry/entry.ts +++ b/src/lib/entry/entry.ts @@ -1,18 +1,20 @@ import { Data } from '../entry/data'; -import { Hash, ZERO_HASH, hashElems } from '../hash/hash'; +import { Hash, ZERO_HASH, hashElems, HashAlgorithm } from '../hash/hash'; import { checkBigIntInField } from '../utils'; import { ElemBytes } from './elemBytes'; export class Entry { + private _algo: HashAlgorithm; private _data: Data; private _hIndex: Hash; private _hValue: Hash; - constructor(_data?: Data) { + constructor(_data?: Data, algo?: HashAlgorithm) { this._data = _data ? _data : new Data(); this._hIndex = ZERO_HASH; this._hValue = ZERO_HASH; + this._algo = algo ?? HashAlgorithm.Poseidon; } get data(): Data { @@ -27,16 +29,20 @@ export class Entry { return this._data.value.slice(4, 8); } + get algo(): HashAlgorithm { + return this._algo; + } + async hIndex(): Promise { if (this._hIndex === ZERO_HASH) { - return hashElems(elemBytesToBigInts(this.index)); + return hashElems(elemBytesToBigInts(this.index), this._algo); } return this._hIndex; } async hValue(): Promise { if (this._hValue === ZERO_HASH) { - return hashElems(elemBytesToBigInts(this.value)); + return hashElems(elemBytesToBigInts(this.value), this._algo); } return this._hValue; } @@ -58,7 +64,7 @@ export class Entry { } clone(): Entry { - return new Entry(this._data); + return new Entry(this._data, this._algo); } } @@ -75,7 +81,7 @@ export const checkEntryInField = (e: Entry): boolean => { let flag = true; bigInts.forEach((b) => { - if (!checkBigIntInField(b)) { + if (!e.algo.checkEntryInField(b)) { flag = false; } }); diff --git a/src/lib/hash/hash.ts b/src/lib/hash/hash.ts index 5c1bcf7..d5e5bcd 100644 --- a/src/lib/hash/hash.ts +++ b/src/lib/hash/hash.ts @@ -9,6 +9,32 @@ import { } from '../utils'; import { Bytes, IHash, Siblings } from '../../types'; import { Hex, poseidon } from '@iden3/js-crypto'; +import { keccak256 } from '@ethersproject/keccak256'; +import { AbiCoder } from '@ethersproject/abi'; + +export class HashAlgorithm { + static Poseidon: HashAlgorithm = new HashAlgorithm(0); + static Keccak256: HashAlgorithm = new HashAlgorithm(1); + + private _algo: number; + constructor(_algo: number) { + this._algo = _algo; + } + + checkEntryInField(entry: bigint): boolean { + switch (this._algo) { + case 0: + // Poseidon + return checkBigIntInField(entry); + case 1: + // Keccak256 + // no-op + return entry < BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); + default: + throw new Error(`Unsupported hash algorithm ${this._algo}`); + } + } +} export class Hash implements IHash { // little endian @@ -57,7 +83,8 @@ export class Hash implements IHash { static fromString(s: string): Hash { try { - return Hash.fromBigInt(BigInt(s)); + const bigIntValue = BigInt(s); + return Hash.fromBigInt(bigIntValue); } catch (e) { const deserializedHash = JSON.parse(s); const bytes = Uint8Array.from(Object.values(deserializedHash.bytes)); @@ -65,10 +92,6 @@ export class Hash implements IHash { } } static fromBigInt(i: bigint): Hash { - if (!checkBigIntInField(i)) { - throw new Error('NewBigIntFromHashBytes: Value not inside the Finite Field'); - } - const bytes = bigIntToUINT8Array(i); return new Hash(swapEndianness(bytes)); @@ -112,14 +135,32 @@ export const newHashFromString = (decimalString: string): Hash => { return Hash.fromString(decimalString); }; -export const hashElems = (e: Array): Hash => { - const hashBigInt = poseidon.hash(e); - return Hash.fromBigInt(hashBigInt); +export const hashElems = (e: Array, algo: HashAlgorithm): Hash => { + switch (algo) { + case HashAlgorithm.Poseidon: + const hashBigInt = poseidon.hash(e); + return Hash.fromBigInt(hashBigInt); + case HashAlgorithm.Keccak256: + const hashString = keccak256(new AbiCoder().encode(e.map(p => 'uint256'), e)); + return Hash.fromString(hashString); + default: + throw new Error(`Unsupported hash algorithm ${algo}`); + } }; -export const hashElemsKey = (k: bigint, e: Array): Hash => { - const hashBigInt = poseidon.hash([...e, k]); - return Hash.fromBigInt(hashBigInt); +export const hashElemsKey = (k: bigint, e: Array, algo: HashAlgorithm): Hash => { + switch (algo) { + case HashAlgorithm.Poseidon: + const hashBigInt = poseidon.hash([...e, k]); + return Hash.fromBigInt(hashBigInt); + case HashAlgorithm.Keccak256: + const params = [...e, k]; + const encoded = new AbiCoder().encode(params.map(p => 'uint256'), [...e, k]); + const hashString = keccak256(encoded); + return Hash.fromString(hashString); + default: + throw new Error(`Unsupported hash algorithm ${algo}`); + } }; export const circomSiblingsFromSiblings = (siblings: Siblings, levels: number): Siblings => { diff --git a/src/lib/merkletree/merkletree.ts b/src/lib/merkletree/merkletree.ts index 2352dd8..90c54dc 100644 --- a/src/lib/merkletree/merkletree.ts +++ b/src/lib/merkletree/merkletree.ts @@ -1,5 +1,5 @@ import { ITreeStorage } from '../../types/storage'; -import { Hash, ZERO_HASH, circomSiblingsFromSiblings } from '../hash/hash'; +import { Hash, ZERO_HASH, circomSiblingsFromSiblings, HashAlgorithm } from '../hash/hash'; import { Node } from '../../types'; import { NODE_TYPE_EMPTY, NODE_TYPE_LEAF, NODE_TYPE_MIDDLE } from '../../constants'; @@ -24,11 +24,13 @@ export class Merkletree { private _root!: Hash; private _writable: boolean; private _maxLevel: number; + private _algo: HashAlgorithm; constructor(_db: ITreeStorage, _writable: boolean, _maxLevels: number) { this._db = _db; this._writable = _writable; this._maxLevel = _maxLevels; + this._algo = _db.getHashAlgorithm(); } async root(): Promise { @@ -51,7 +53,7 @@ export class Merkletree { const kHash = Hash.fromBigInt(k); const vHash = Hash.fromBigInt(v); - const newNodeLeaf = new NodeLeaf(kHash, vHash); + const newNodeLeaf = new NodeLeaf(kHash, vHash, this._algo); const path = getPath(this.maxLevels, kHash.value); const newRootKey = await this.addLeaf(newNodeLeaf, this._root, 0, path); @@ -103,7 +105,7 @@ export class Merkletree { const hIndex = await e.hIndex(); const hValue = await e.hValue(); - const newNodeLeaf = new NodeLeaf(hIndex, hValue); + const newNodeLeaf = new NodeLeaf(hIndex, hValue, this._algo); const path = getPath(this.maxLevels, hIndex.value); const newRootKey = await this.addLeaf(newNodeLeaf, this._root, 0, path); @@ -127,9 +129,9 @@ export class Merkletree { if (pathNewLeaf[lvl] === pathOldLeaf[lvl]) { const nextKey = await this.pushLeaf(newLeaf, oldLeaf, lvl + 1, pathNewLeaf, pathOldLeaf); if (pathNewLeaf[lvl]) { - newNodeMiddle = new NodeMiddle(new Hash(), nextKey); + newNodeMiddle = new NodeMiddle(new Hash(), nextKey, this._algo); } else { - newNodeMiddle = new NodeMiddle(nextKey, new Hash()); + newNodeMiddle = new NodeMiddle(nextKey, new Hash(), this._algo); } return await this.addNode(newNodeMiddle); @@ -139,9 +141,9 @@ export class Merkletree { const newLeafKey = await newLeaf.getKey(); if (pathNewLeaf[lvl]) { - newNodeMiddle = new NodeMiddle(oldLeafKey, newLeafKey); + newNodeMiddle = new NodeMiddle(oldLeafKey, newLeafKey, this._algo); } else { - newNodeMiddle = new NodeMiddle(newLeafKey, oldLeafKey); + newNodeMiddle = new NodeMiddle(newLeafKey, oldLeafKey, this._algo); } await this.addNode(newLeaf); @@ -178,10 +180,10 @@ export class Merkletree { if (path[lvl]) { const nextKey = await this.addLeaf(newLeaf, (n as NodeMiddle).childR, lvl + 1, path); - newNodeMiddle = new NodeMiddle((n as NodeMiddle).childL, nextKey); + newNodeMiddle = new NodeMiddle((n as NodeMiddle).childL, nextKey, this._algo); } else { const nextKey = await this.addLeaf(newLeaf, (n as NodeMiddle).childL, lvl + 1, path); - newNodeMiddle = new NodeMiddle(nextKey, (n as NodeMiddle).childR); + newNodeMiddle = new NodeMiddle(nextKey, (n as NodeMiddle).childR, this._algo); } return this.addNode(newNodeMiddle); @@ -283,7 +285,7 @@ export class Merkletree { if (bytesEqual(kHash.value, (n as NodeLeaf).entry[0].value)) { cp.oldValue = (n as NodeLeaf).entry[1]; cp.siblings = circomSiblingsFromSiblings([...siblings], this.maxLevels); - const newNodeLeaf = new NodeLeaf(kHash, vHash); + const newNodeLeaf = new NodeLeaf(kHash, vHash, this._algo); await this.updateNode(newNodeLeaf); const newRootKey = await this.recalculatePathUntilRoot(path, newNodeLeaf, siblings); @@ -326,9 +328,9 @@ export class Merkletree { for (let i = siblings.length - 1; i >= 0; i -= 1) { const nodeKey = await node.getKey(); if (path[i]) { - node = new NodeMiddle(siblings[i], nodeKey); + node = new NodeMiddle(siblings[i], nodeKey, this._algo); } else { - node = new NodeMiddle(nodeKey, siblings[i]); + node = new NodeMiddle(nodeKey, siblings[i], this._algo); } await this.addNode(node); } @@ -406,9 +408,9 @@ export class Merkletree { if (nearestSibling?.type === NODE_TYPE_MIDDLE) { let newNode: Node; if (path[siblings.length - 1]) { - newNode = new NodeMiddle(toUpload, ZERO_HASH); + newNode = new NodeMiddle(toUpload, ZERO_HASH, this._algo); } else { - newNode = new NodeMiddle(ZERO_HASH, toUpload); + newNode = new NodeMiddle(ZERO_HASH, toUpload, this._algo); } await this.addNode(newNode); const newRootKey = await this.recalculatePathUntilRoot( @@ -425,9 +427,9 @@ export class Merkletree { if (!bytesEqual(siblings[i].value, ZERO_HASH.value)) { let newNode: Node; if (path[i]) { - newNode = new NodeMiddle(siblings[i], toUpload); + newNode = new NodeMiddle(siblings[i], toUpload, this._algo); } else { - newNode = new NodeMiddle(toUpload, siblings[i]); + newNode = new NodeMiddle(toUpload, siblings[i], this._algo); } await this.addNode(newNode); diff --git a/src/lib/merkletree/proof.ts b/src/lib/merkletree/proof.ts index e4436ec..5e2d74b 100644 --- a/src/lib/merkletree/proof.ts +++ b/src/lib/merkletree/proof.ts @@ -1,7 +1,7 @@ import { NodeAux, Siblings } from '../../types/merkletree'; import { ELEM_BYTES_LEN, NOT_EMPTIES_LEN, PROOF_FLAG_LEN } from '../../constants'; import { bytesEqual, getPath, setBitBigEndian, siblings2Bytes, testBitBigEndian } from '../utils'; -import { Hash, ZERO_HASH } from '../hash/hash'; +import { Hash, HashAlgorithm, ZERO_HASH } from '../hash/hash'; import { NodeMiddle } from '../node/node'; import { leafKey } from '../utils/node'; import { ErrNodeAuxNonExistAgainstHIndex } from '../errors/proof'; @@ -72,9 +72,9 @@ export class Proof { siblings: this.allSiblings().map((s) => s.toJSON()), node_aux: this.nodeAux ? { - key: this.nodeAux.key.toJSON(), - value: this.nodeAux.value.toJSON() - } + key: this.nodeAux.key.toJSON(), + value: this.nodeAux.value.toJSON() + } : undefined }; } @@ -150,10 +150,11 @@ export const verifyProof = async ( rootKey: Hash, proof: Proof, k: bigint, - v: bigint + v: bigint, + algo: HashAlgorithm = HashAlgorithm.Poseidon ): Promise => { try { - const rFromProof = await rootFromProof(proof, k, v); + const rFromProof = await rootFromProof(proof, k, v, algo); return bytesEqual(rootKey.value, rFromProof.value); } catch (err) { if (err === ErrNodeAuxNonExistAgainstHIndex) { @@ -163,13 +164,13 @@ export const verifyProof = async ( } }; -export const rootFromProof = async (proof: Proof, k: bigint, v: bigint): Promise => { +export const rootFromProof = async (proof: Proof, k: bigint, v: bigint, algo: HashAlgorithm): Promise => { const kHash = Hash.fromBigInt(k); const vHash = Hash.fromBigInt(v); let midKey: Hash; if (proof.existence) { - midKey = await leafKey(kHash, vHash); + midKey = await leafKey(kHash, vHash, algo); } else { if (typeof proof.nodeAux === 'undefined') { midKey = ZERO_HASH; @@ -178,7 +179,7 @@ export const rootFromProof = async (proof: Proof, k: bigint, v: bigint): Promise if (bytesEqual(kHash.value, nodeAux.key.value)) { throw ErrNodeAuxNonExistAgainstHIndex; } - midKey = await leafKey(nodeAux.key, nodeAux.value); + midKey = await leafKey(nodeAux.key, nodeAux.value, algo); } } @@ -188,9 +189,9 @@ export const rootFromProof = async (proof: Proof, k: bigint, v: bigint): Promise for (let i = siblings.length - 1; i >= 0; i -= 1) { if (path[i]) { - midKey = await new NodeMiddle(siblings[i], midKey).getKey(); + midKey = await new NodeMiddle(siblings[i], midKey, algo).getKey(); } else { - midKey = await new NodeMiddle(midKey, siblings[i]).getKey(); + midKey = await new NodeMiddle(midKey, siblings[i], algo).getKey(); } } diff --git a/src/lib/node/node.ts b/src/lib/node/node.ts index 6ea138f..97cfb3c 100644 --- a/src/lib/node/node.ts +++ b/src/lib/node/node.ts @@ -1,5 +1,5 @@ import { Bytes, Node, NodeType } from '../../types'; -import { Hash, ZERO_HASH, hashElems } from '../hash/hash'; +import { Hash, ZERO_HASH, hashElems, HashAlgorithm } from '../hash/hash'; import { EMPTY_NODE_STRING, @@ -15,16 +15,18 @@ export class NodeLeaf implements Node { entry: [Hash, Hash]; // cache used to avoid recalculating key private _key: Hash; + private _algo: HashAlgorithm = HashAlgorithm.Poseidon; - constructor(k: Hash, v: Hash) { + constructor(k: Hash, v: Hash, algo: HashAlgorithm) { this.type = NODE_TYPE_LEAF; this.entry = [k, v]; this._key = ZERO_HASH; + this._algo = algo; } async getKey(): Promise { if (this._key === ZERO_HASH) { - return await leafKey(this.entry[0], this.entry[1]); + return await leafKey(this.entry[0], this.entry[1], this._algo); } return this._key; } @@ -43,17 +45,19 @@ export class NodeMiddle implements Node { childL: Hash; childR: Hash; private _key: Hash; + private _algo: HashAlgorithm; - constructor(cL: Hash, cR: Hash) { + constructor(cL: Hash, cR: Hash, algo: HashAlgorithm) { this.type = NODE_TYPE_MIDDLE; this.childL = cL; this.childR = cR; this._key = ZERO_HASH; + this._algo = algo; } async getKey(): Promise { if (this._key === ZERO_HASH) { - return hashElems([this.childL.bigInt(), this.childR.bigInt()]); + return hashElems([this.childL.bigInt(), this.childR.bigInt()], this._algo); } return this._key; } diff --git a/src/lib/utils/node.ts b/src/lib/utils/node.ts index a3f3aaf..a40c729 100644 --- a/src/lib/utils/node.ts +++ b/src/lib/utils/node.ts @@ -1,13 +1,13 @@ // LeafKey computes the key of a leaf node given the hIndex and hValue of the // entry of the leaf. -import { Hash, hashElemsKey } from '../hash/hash'; +import { Hash, HashAlgorithm, hashElemsKey } from '../hash/hash'; import { NODE_VALUE_BYTE_ARR_LENGTH } from '../../constants'; import { bigIntToUINT8Array } from './bigint'; import { Bytes, NodeType } from '../../types'; -export const leafKey = async (k: Hash, v: Hash): Promise => { - return hashElemsKey(BigInt(1), [k.bigInt(), v.bigInt()]); +export const leafKey = async (k: Hash, v: Hash, algo: HashAlgorithm): Promise => { + return hashElemsKey(BigInt(1), [k.bigInt(), v.bigInt()], algo); }; export const nodeValue = (type: NodeType, a: Hash, b: Hash): Bytes => { diff --git a/src/types/node.ts b/src/types/node.ts index acfcfd0..2f3e771 100644 --- a/src/types/node.ts +++ b/src/types/node.ts @@ -1,4 +1,4 @@ -import { Hash } from '../lib/hash/hash'; +import { Hash, HashAlgorithm } from '../lib/hash/hash'; import { Bytes } from './bytes'; export type NodeType = 0 | 1 | 2; diff --git a/src/types/storage.ts b/src/types/storage.ts index 3b31006..77f8b70 100644 --- a/src/types/storage.ts +++ b/src/types/storage.ts @@ -1,5 +1,5 @@ import { Node } from './node'; -import { Hash } from '../lib/hash/hash'; +import { Hash, HashAlgorithm } from '../lib/hash/hash'; import { Bytes } from './bytes'; export interface ITreeStorage { @@ -7,6 +7,7 @@ export interface ITreeStorage { put: (k: Bytes, n: Node) => Promise; getRoot: () => Promise; setRoot: (r: Hash) => Promise; + getHashAlgorithm: () => HashAlgorithm; } export type KV = { diff --git a/tests/full.keccak256.test.ts b/tests/full.keccak256.test.ts new file mode 100644 index 0000000..8244ba7 --- /dev/null +++ b/tests/full.keccak256.test.ts @@ -0,0 +1,1261 @@ +import { UseStore, createStore, clear } from 'idb-keyval'; +import { HASH_BYTES_LENGTH, MAX_NUM_IN_FIELD, NODE_TYPE_LEAF } from '../src/constants'; +import { NodeLeaf, NodeMiddle } from '../src/lib/node/node'; +import { InMemoryDB, LocalStorageDB, IndexedDBStorage } from '../src/lib/db'; +import { bigIntToUINT8Array, bytes2Hex, bytesEqual, str2Bytes } from '../src/lib/utils'; +import { Hash, ZERO_HASH, HashAlgorithm } from '../src/lib/hash/hash'; +import { Merkletree, Proof, ProofJSON, verifyProof } from '../src/lib/merkletree'; +import { ErrEntryIndexAlreadyExists, ErrKeyNotFound, ErrReachedMaxLevel } from '../src/lib/errors'; +import { poseidon } from '@iden3/js-crypto'; + +import 'mock-local-storage'; +import 'fake-indexeddb/auto'; +import { Node } from '../src/types'; + +enum TreeStorageType { + LocalStorageDB = 'localStorage', + InMemoryDB = 'memoryStorage', + IndexedDB = 'indexedDB' +} + +const storages: TreeStorageType[] = [ + TreeStorageType.InMemoryDB, + TreeStorageType.LocalStorageDB, + TreeStorageType.IndexedDB +]; + +for (let index = 0; index < storages.length; index++) { + describe(`full test of the SMT library: ${storages[index].toString()}`, () => { + const store: UseStore = createStore( + `${IndexedDBStorage.storageName}-db`, + IndexedDBStorage.storageName + ); + + beforeEach(async () => { + localStorage.clear(); + await clear(store); + }); + + const getTreeStorage = (prefix = '') => { + if (storages[index] == TreeStorageType.LocalStorageDB) { + return new LocalStorageDB(str2Bytes(prefix), HashAlgorithm.Keccak256); + } else if (storages[index] == TreeStorageType.IndexedDB) { + return new IndexedDBStorage(str2Bytes(prefix), HashAlgorithm.Keccak256); + } else if (storages[index] == TreeStorageType.InMemoryDB) { + return new InMemoryDB(str2Bytes(prefix), HashAlgorithm.Keccak256); + } + throw new Error('error: unknown storage type'); + }; + + it('checks that the implementation of the db.Storage interface behaves as expected', async () => { + const sto = getTreeStorage(); + + const bytes = new Uint8Array(HASH_BYTES_LENGTH); + bytes[0] = 1; + const v = new Hash(bytes); + + const node = new NodeMiddle(v, v, HashAlgorithm.Keccak256); + const k = await node.getKey(); + await sto.put(k.value, node); + const val = await sto.get(k.value); + + expect(val).not.toBeUndefined(); + expect((val as NodeMiddle).childL.hex()).toEqual(v.hex()); + expect((val as NodeMiddle).childR.hex()).toEqual(v.hex()); + }); + + it('test new merkle tree', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + expect((await mt.root()).string()).toEqual('0'); + + await mt.add(BigInt('1'), BigInt('2')); + expect((await mt.root()).bigInt().toString(10)).toEqual( + '20349940423862035287868699599764962454537984981628200184279725786303353984557' + ); + + await mt.add(BigInt('33'), BigInt('44')); + expect((await mt.root()).bigInt().toString(10)).toEqual( + '76534138237239231515859035502772486263463178175980489503663557460094727691106' + ); + + await mt.add(BigInt('1234'), BigInt('9876')); + expect((await mt.root()).bigInt().toString(10)).toEqual( + '544861533666138023304524147783425313319718916836013588107382050253858247287' + ); + + expect((await sto.getRoot()).bigInt().toString()).toEqual( + (await mt.root()).bigInt().toString() + ); + + const { proof, value } = await mt.generateProof(BigInt('33')); + expect(value.toString()).toEqual('44'); + + expect(await verifyProof(await mt.root(), proof, BigInt('33'), BigInt('44'), HashAlgorithm.Keccak256)).toEqual(true); + + expect(await verifyProof(await mt.root(), proof, BigInt('33'), BigInt('45'), HashAlgorithm.Keccak256)).toEqual(false); + }); + + it('test tree with one node', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + expect(bytesEqual((await mt.root()).value, ZERO_HASH.value)).toEqual(true); + + await mt.add(BigInt('100'), BigInt('200')); + expect((await mt.root()).bigInt().toString(10)).toEqual( + '76684871214838026877932731174111849409910921034598023076966884922227909952638' + ); + const inputs = [BigInt('100'), BigInt('200'), BigInt('1')]; + expect((await mt.root()).bigInt().toString()).toEqual("76684871214838026877932731174111849409910921034598023076966884922227909952638"); + }); + + it('test add and different order', async () => { + const sto1 = getTreeStorage('tree1'); + const sto2 = getTreeStorage('tree2'); + const mt1 = new Merkletree(sto1, true, 140); + const mt2 = new Merkletree(sto2, true, 140); + + for (let i = 0; i < 16; i += 1) { + const k = BigInt(i); + const v = BigInt('0'); + await mt1.add(k, v); + } + + for (let i = 15; i >= 0; i -= 1) { + const k = BigInt(i); + const v = BigInt('0'); + await mt2.add(k, v); + } + + expect((await mt1.root()).string()).toEqual((await mt2.root()).string()); + expect((await mt1.root()).hex()).toEqual( + '71d5901d6ab3653ef7a8f24e4a16f4932752d9192a2c4bea8a1a7cadd93c11a9' + ); + }); + + it('test add repeated index', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 140); + + const k = BigInt('3'); + const v = BigInt('12'); + await mt.add(k, v); + + try { + await mt.add(k, v); + } catch (err) { + expect(err).toEqual(ErrEntryIndexAlreadyExists); + } + }); + + it('test get', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 140); + + for (let i = 0; i < 16; i += 1) { + const k = BigInt(i); + const v = BigInt(i * 2); + + await mt.add(k, v); + } + const { key: k1, value: v1 } = await mt.get(BigInt('10')); + expect(k1.toString(10)).toEqual('10'); + expect(v1.toString(10)).toEqual('20'); + + const { key: k2, value: v2 } = await mt.get(BigInt('15')); + expect(k2.toString(10)).toEqual('15'); + expect(v2.toString(10)).toEqual('30'); + + try { + await mt.get(BigInt('16')); + } catch (err) { + expect(err).toEqual(ErrKeyNotFound); + } + }); + + it('test update', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 140); + + for (let i = 0; i < 16; i += 1) { + const k = BigInt(i); + const v = BigInt(i * 2); + await mt.add(k, v); + } + + expect((await mt.get(BigInt('10'))).value.toString(10)).toEqual('20'); + + await mt.update(BigInt('10'), BigInt('1024')); + expect((await mt.get(BigInt('10'))).value.toString(10)).toEqual('1024'); + + try { + await mt.update(BigInt('10'), BigInt('1024')); + } catch (err) { + expect(err).toEqual(ErrKeyNotFound); + } + + const dbRoot = await sto.getRoot(); + expect(dbRoot.string()).toEqual((await mt.root()).string()); + }); + + it('test update 2', async () => { + const sto1 = getTreeStorage('tree1'); + const sto2 = getTreeStorage('tree2'); + const mt1 = new Merkletree(sto1, true, 140); + const mt2 = new Merkletree(sto2, true, 140); + + await mt1.add(BigInt('1'), BigInt('2')); + await mt1.add(BigInt('2'), BigInt('229')); + await mt1.add(BigInt('9876'), BigInt('6789')); + + await mt2.add(BigInt('1'), BigInt('11')); + await mt2.add(BigInt('2'), BigInt('22')); + await mt2.add(BigInt('9876'), BigInt('10')); + + await mt1.update(BigInt('1'), BigInt('11')); + await mt1.update(BigInt('2'), BigInt('22')); + await mt2.update(BigInt('9876'), BigInt('6789')); + + expect((await mt1.root()).string()).toEqual((await mt2.root()).string()); + }); + + it('test generate and verify proof 128', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 140); + + for (let i = 0; i < 128; i += 1) { + const k = BigInt(i); + const v = BigInt('0'); + + await mt.add(k, v); + } + + const { proof, value } = await mt.generateProof(BigInt('42')); + expect(value.toString()).toEqual('0'); + const verRes = await verifyProof(await mt.root(), proof, BigInt('42'), BigInt('0'), HashAlgorithm.Keccak256); + expect(verRes).toEqual(true); + }); + + it('test tree limit', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 5); + + for (let i = 0; i < 16; i += 1) { + await mt.add(BigInt(i), BigInt(i)); + } + + try { + await mt.add(BigInt('16'), BigInt('16')); + } catch (err) { + expect((err as Error).message).toEqual(ErrReachedMaxLevel); + } + }); + + it('test siblings from proof', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 140); + + for (let i = 0; i < 64; i += 1) { + const k = BigInt(i); + const v = BigInt('0'); + await mt.add(k, v); + } + + const { proof } = await mt.generateProof(BigInt('4')); + const siblings = proof.allSiblings(); + + expect(siblings.length).toEqual(6); + + expect(siblings[0].hex()).toEqual( + 'b2cee61650a6275e75b6bef5d338a14ac490db97614b1392ebfd1abccbb5725a' + ); + expect(siblings[1].hex()).toEqual( + 'c683ae7a8d9800bd5114ccc05ac673be7a20a5152c782305076166a8a27089ee' + ); + expect(siblings[2].hex()).toEqual( + '2e7090f730df0afda34acd203dfb44fa5c83a01480c4ab32f7af362e9fc66c2e' + ); + expect(siblings[3].hex()).toEqual( + '4f397ca4e191df962d78d7339cc83c8f2a5418e5801158bb576d02cca5ed893b' + ); + expect(siblings[4].hex()).toEqual( + 'd0b7a51724a7be4781dd97fb8e901e45b2e14f2587f3b933f8830d2e318ed5dd' + ); + expect(siblings[5].hex()).toEqual( + '0e029c03a12eadd7c268a105fbcb837c71ea26a762a429151db46192ae68483f' + ); + }); + + it('test and verify proof cases', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 140); + + for (let i = 0; i < 8; i += 1) { + await mt.add(BigInt(i), BigInt('0')); + } + + let { proof } = await mt.generateProof(BigInt('4')); + expect(proof.existence).toEqual(true); + expect(await verifyProof(await mt.root(), proof, BigInt('4'), BigInt('0'), HashAlgorithm.Keccak256)).toEqual(true); + expect(bytes2Hex(proof.bytes())).toEqual( + '00030000000000000000000000000000000000000000000000000000000000077baea77c663ade84f8375ee0caed270e1188c0b0715c48c4801d79ebc6a3bbbca47a068a9c0c91be00d31d79688c762a167036b67beae6ea06d6d522e752605ad9892c0d81c801830cd9216e6ff31da83fb8745b1365aa34fc600092aa4bfecb' + ); + + for (let i = 8; i < 32; i += 1) { + const { proof } = await mt.generateProof(BigInt(i)); + expect(proof.existence).toBeDefined(); + } + + // non-existence proof, node aux + proof = (await mt.generateProof(BigInt('12'))).proof; + expect(proof.existence).toEqual(false); + expect(proof.nodeAux).toBeDefined(); + expect(await verifyProof(await mt.root(), proof, BigInt('12'), BigInt('0'), HashAlgorithm.Keccak256)).toEqual(true); + expect(bytes2Hex(proof.bytes())).toEqual( + '03030000000000000000000000000000000000000000000000000000000000077baea77c663ade84f8375ee0caed270e1188c0b0715c48c4801d79ebc6a3bbbca47a068a9c0c91be00d31d79688c762a167036b67beae6ea06d6d522e752605ad9892c0d81c801830cd9216e6ff31da83fb8745b1365aa34fc600092aa4bfecb04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' //nolint:lll + ); + + // non-existence proof, node aux + proof = (await mt.generateProof(BigInt('10'))).proof; + expect(proof.existence).toEqual(false); + expect(proof.nodeAux).toBeDefined(); + expect(await verifyProof(await mt.root(), proof, BigInt('10'), BigInt('0'), HashAlgorithm.Keccak256)).toEqual(true); + expect(bytes2Hex(proof.bytes())).toEqual( + '03030000000000000000000000000000000000000000000000000000000000077baea77c663ade84f8375ee0caed270e1188c0b0715c48c4801d79ebc6a3bbbc73522996e2f99e663eb22db2b85ee06ec6eb91fef6e79fa27e04c7f64f96cae442247dfe8781b5b6cf155d9f12bc7235d8589002a7102dbd4068e60e4f9b74d702000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' //nolint:lll + ); + }); + + it('test and verify proof false', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 140); + + for (let i = 0; i < 8; i += 1) { + await mt.add(BigInt(i), BigInt('0')); + } + // Invalid existence proof (node used for verification doesn't + // correspond to node in the proof) + let { proof } = await mt.generateProof(BigInt('4')); + expect(proof.existence).toEqual(true); + expect(await verifyProof(await mt.root(), proof, BigInt('5'), BigInt('5'), HashAlgorithm.Keccak256)).toEqual(false); + + // Invalid non-existence proof (Non-existence proof, diff. node aux) + proof = (await mt.generateProof(BigInt('4'))).proof; + expect(proof.existence).toEqual(true); + proof.existence = false; + proof.nodeAux = { + key: Hash.fromBigInt(BigInt('4')), + value: Hash.fromBigInt(BigInt('4')) + }; + + expect(await verifyProof(await mt.root(), proof, BigInt('4'), BigInt('0'), HashAlgorithm.Keccak256)).toEqual(false); + }); + + it('test delete', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(BigInt('1'), BigInt('2')); + expect((await mt.root()).string()).toEqual( + '20349940423862035287868699599764962454537984981628200184279725786303353984557' + ); + + await mt.add(BigInt('33'), BigInt('44')); + expect((await mt.root()).string()).toEqual( + '76534138237239231515859035502772486263463178175980489503663557460094727691106' + ); + + await mt.add(BigInt('1234'), BigInt('9876')); + expect((await mt.root()).string()).toEqual( + '544861533666138023304524147783425313319718916836013588107382050253858247287' + ); + + await mt.delete(BigInt('33')); + expect((await mt.root()).string()).toEqual( + '59172665240949163570629625279231866406762637838923705579001199098945372573940' + ); + + await mt.delete(BigInt('1234')); + await mt.delete(BigInt('1')); + + expect((await mt.root()).string()).toEqual('0'); + expect((await mt.root()).string()).toEqual((await sto.getRoot()).string()); + }); + + it('test delete 2', async () => { + const sto1 = getTreeStorage('tree1'); + const sto2 = getTreeStorage('tree2'); + const mt1 = new Merkletree(sto1, true, 140); + const mt2 = new Merkletree(sto2, true, 140); + + for (let i = 0; i < 8; i += 1) { + const k = BigInt(i); + const v = BigInt('0'); + await mt1.add(k, v); + } + + const expectedRootStr = (await mt1.root()).string(); + + const k = BigInt('8'); + const v = BigInt('0'); + + await mt1.add(k, v); + await mt1.delete(k); + + expect(expectedRootStr).toEqual((await mt1.root()).string()); + + for (let i = 0; i < 8; i += 1) { + const k = BigInt(i); + const v = BigInt('0'); + await mt2.add(k, v); + } + + expect((await mt1.root()).string()).toEqual((await mt2.root()).string()); + }); + + it('test delete 3', async () => { + const sto1 = getTreeStorage('tree1'); + const sto2 = getTreeStorage('tree2'); + const mt1 = new Merkletree(sto1, true, 140); + const mt2 = new Merkletree(sto2, true, 140); + + await mt1.add(BigInt('1'), BigInt('1')); + await mt1.add(BigInt('2'), BigInt('2')); + + expect((await mt1.root()).string()).toEqual( + '108872779231739741211466661630182120869964350174968231253312446506137900102765' + ); + + await mt1.delete(BigInt('1')); + + expect((await mt1.root()).string()).toEqual( + '204679323412988774155807184358931572859960307382228217179160870267303983297' + ); + + await mt2.add(BigInt('2'), BigInt('2')); + expect((await mt1.root()).string()).toEqual((await mt2.root()).string()); + }); + + it('test delete 4', async () => { + const sto1 = getTreeStorage('tree1'); + const sto2 = getTreeStorage('tree2'); + const mt1 = new Merkletree(sto1, true, 140); + const mt2 = new Merkletree(sto2, true, 140); + + await mt1.add(BigInt('1'), BigInt('1')); + await mt1.add(BigInt('2'), BigInt('2')); + await mt1.add(BigInt('3'), BigInt('3')); + + expect((await mt1.root()).string()).toEqual( + '112589100365384563636828753186771502102479135063472969221605103709843483709964' + ); + + await mt1.delete(BigInt('1')); + + expect((await mt1.root()).string()).toEqual( + '14186295076042981090325393415823129915089483719993442784504350225538352965231' + ); + + await mt2.add(BigInt('2'), BigInt('2')); + await mt2.add(BigInt('3'), BigInt('3')); + expect((await mt1.root()).string()).toEqual((await mt2.root()).string()); + }); + + it('test delete 5', async () => { + const sto1 = getTreeStorage('tree1'); + const sto2 = getTreeStorage('tree2'); + const mt1 = new Merkletree(sto1, true, 140); + const mt2 = new Merkletree(sto2, true, 140); + + await mt1.add(BigInt('1'), BigInt('2')); + await mt1.add(BigInt('33'), BigInt('44')); + + expect((await mt1.root()).string()).toEqual( + '76534138237239231515859035502772486263463178175980489503663557460094727691106' + ); + + await mt1.delete(BigInt('1')); + + expect((await mt1.root()).string()).toEqual( + '10955310555638083816119775899206389561202556659568675876759181443512300421331' + ); + + await mt2.add(BigInt('33'), BigInt('44')); + expect((await mt1.root()).string()).toEqual((await mt2.root()).string()); + }); + + it('test delete not existing keys', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(BigInt('1'), BigInt('2')); + await mt.add(BigInt('33'), BigInt('44')); + + await mt.delete(BigInt('33')); + + try { + await mt.delete(BigInt('33')); + } catch (err) { + expect(err).toEqual(ErrKeyNotFound); + } + + await mt.delete(BigInt('1')); + expect((await mt.root()).string()).toEqual('0'); + + try { + await mt.delete(BigInt('33')); + } catch (err) { + expect(err).toEqual(ErrKeyNotFound); + } + }); + + it('test delete leaf near middle node. Right branch', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + const keys = [7n, 1n, 5n]; + + const expectedSiblings: { [id: string]: bigint[] } = { + '7': [], + '1': [0n, 58778245672362516760657188918491108074068739632281986516447865413987646804066n], + '5': [ + 0n, + 58778245672362516760657188918491108074068739632281986516447865413987646804066n, + 37245951031790617425846662166414203447230852981947190519491676860679629218468n + ] + }; + + for (const k of keys) { + await mt.add(k, k); + const existProof = await mt.generateProof(k, await mt.root()); + expect(existProof.proof.existence).toEqual(true); + compareSiblings(expectedSiblings[k.toString()], existProof.proof); + } + + const expectedSiblingsNonExist: { [id: string]: bigint[] } = { + '7': [0n, 90793012380542163121115292742720211788796606421058579751728012408711775415639n], + '1': [], + '5': [] + }; + + for (const k of keys) { + await mt.delete(k); + const existProof = await mt.generateProof(k, await mt.root()); + expect(existProof.proof.existence).toEqual(false); + compareSiblings(expectedSiblingsNonExist[k.toString()], existProof.proof); + } + }); + + it('test delete leaf near middle node. Right branch deep', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + const keys = [3n, 7n, 15n]; + + const expectedSiblings: { [id: string]: bigint[] } = { + '3': [], + '7': [ + 0n, + 0n, + 45906869880764156987885874863987484108432793389080846151397980307504325728862n + ], + '15': [ + 0n, + 0n, + 45906869880764156987885874863987484108432793389080846151397980307504325728862n, + 58778245672362516760657188918491108074068739632281986516447865413987646804066n + ] + }; + + for (const k of keys) { + await mt.add(k, k); + const existProof = await mt.generateProof(k, await mt.root()); + expect(existProof.proof.existence).toEqual(true); + compareSiblings(expectedSiblings[k.toString()], existProof.proof); + } + + const expectedSiblingsNonExist: { [id: string]: bigint[] } = { + '3': [ + 0n, + 0n, + 66983951388551681909755987368531576513037053734179480194567340249595337759173n + ], + '7': [], + '15': [] + }; + + for (const k of keys) { + await mt.delete(k); + const existProof = await mt.generateProof(k, await mt.root()); + expect(existProof.proof.existence).toEqual(false); + compareSiblings(expectedSiblingsNonExist[k.toString()], existProof.proof); + } + }); + + it('test delete leaf near middle node. Left branch', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + const keys = [6n, 4n, 2n]; + + const expectedSiblings: { [id: string]: bigint[] } = { + '6': [], + '4': [0n, 47167624024095951102360683516988377651272751399124548217514357033029495756702n], + '2': [ + 0n, + 75643218886135556618007590826502336434547303617941809863348288103535263487329n, + 47167624024095951102360683516988377651272751399124548217514357033029495756702n + ] + }; + + for (const k of keys) { + await mt.add(k, k); + const existProof = await mt.generateProof(k, await mt.root()); + expect(existProof.proof.existence).toEqual(true); + compareSiblings(expectedSiblings[k.toString()], existProof.proof); + } + + const expectedSiblingsNonExist: { [id: string]: bigint[] } = { + '6': [0n, 75643218886135556618007590826502336434547303617941809863348288103535263487329n], + '4': [], + '2': [] + }; + + for (const k of keys) { + await mt.delete(k); + const existProof = await mt.generateProof(k, await mt.root()); + expect(existProof.proof.existence).toEqual(false); + compareSiblings(expectedSiblingsNonExist[k.toString()], existProof.proof); + } + }); + + it('test delete leaf near middle node. Left branch deep', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + const keys = [4n, 8n, 16n]; + + const expectedSiblings: { [id: string]: bigint[] } = { + '4': [], + '8': [ + 0n, + 0n, + 75643218886135556618007590826502336434547303617941809863348288103535263487329n + ], + '16': [ + 0n, + 0n, + 75643218886135556618007590826502336434547303617941809863348288103535263487329n, + 52845948298258825660916624299504428202505418019704865991722346431233437049494n + ] + }; + + for (const k of keys) { + await mt.add(k, k); + const existProof = await mt.generateProof(k, await mt.root()); + expect(existProof.proof.existence).toEqual(true); + compareSiblings(expectedSiblings[k.toString()], existProof.proof); + } + + const expectedSiblingsNonExist: { [id: string]: bigint[] } = { + '4': [0n, 0n, 107404637154263281958799614031313875322282089948200739151367618334012768311826n], + '8': [], + '16': [] + }; + + for (const k of keys) { + await mt.delete(k); + const existProof = await mt.generateProof(k, await mt.root()); + expect(existProof.proof.existence).toEqual(false); + compareSiblings(expectedSiblingsNonExist[k.toString()], existProof.proof); + } + }); + + // Checking whether the last leaf will be moved to the root position + // + // root + // / \ + // 0 MiddleNode + // / \ + // 01 11 + // + // Up to: + // + // root(11) + it('test up to root after delete. Right branch', async () => { + const sto = getTreeStorage('right branch'); + const mt = new Merkletree(sto, true, 10); + + await mt.add(1n, 1n); + await mt.add(3n, 3n); + + await mt.delete(1n); + + const leaf = await mt.getNode(await mt.root()); + expect(leaf?.type).toEqual(NODE_TYPE_LEAF); + expect((leaf as NodeLeaf).entry[0].bigInt()).toEqual(3n); + }); + + // Checking whether the last leaf will be moved to the root position + // + // root + // / \ + // MiddleNode 0 + // / \ + // 100 010 + // + // Up to: + // + // root(100) + it('test up to root after delete. Left branch', async () => { + const sto = getTreeStorage('left branch'); + const mt = new Merkletree(sto, true, 10); + + await mt.add(2n, 2n); + await mt.add(4n, 4n); + + await mt.delete(2n); + + const leaf = await mt.getNode(await mt.root()); + expect(leaf?.type).toEqual(NODE_TYPE_LEAF); + expect((leaf as NodeLeaf).entry[0].bigInt()).toEqual(4n); + }); + + // Checking whether the new root will be calculated from to leafs + // + // root + // / \ + // 10 MiddleNode + // / \ + // 01 11 + // + // Up to: + // + // root + // / \ + // 10 11 + it('calculating of new root. Right branch', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(1n, 1n); + await mt.add(3n, 3n); + await mt.add(2n, 2n); + + await mt.delete(1n); + + const root = (await mt.getNode(await mt.root())) as NodeMiddle; + + const lleaf = (await mt.getNode(root.childL)) as NodeLeaf; + const rleaf = (await mt.getNode(root.childR)) as NodeLeaf; + + expect(lleaf.entry[0].bigInt()).toEqual(2n); + expect(rleaf.entry[0].bigInt()).toEqual(3n); + }); + + // Checking whether the new root will be calculated from to leafs + // + // root + // / \ + // MiddleNode 01 + // / \ + // 100 010 + // + // Up to: + // + // root + // / \ + // 100 001 + it('calculating of new root. Left branch', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(1n, 1n); + await mt.add(2n, 2n); + await mt.add(4n, 4n); + + await mt.delete(2n); + + const root = (await mt.getNode(await mt.root())) as NodeMiddle; + + const lleaf = (await mt.getNode(root.childL)) as NodeLeaf; + const rleaf = (await mt.getNode(root.childR)) as NodeLeaf; + + expect(lleaf.entry[0].bigInt()).toEqual(4n); + expect(rleaf.entry[0].bigInt()).toEqual(1n); + }); + + // https://github.com/iden3/go-merkletree-sql/issues/23 + it('test insert node after delete', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(1n, 1n); + await mt.add(5n, 5n); + await mt.add(7n, 7n); + + const expectedSiblings = [ + 0n, + 90793012380542163121115292742720211788796606421058579751728012408711775415639n + ]; + + await mt.delete(7n); + let proof = await mt.generateProof(7n, await mt.root()); + expect(proof.proof.existence).toEqual(false); + compareSiblings(expectedSiblings, proof.proof); + + await mt.add(7n, 7n); + proof = await mt.generateProof(7n, await mt.root()); + expect(proof.proof.existence).toEqual(true); + compareSiblings(expectedSiblings, proof.proof); + }); + + it('test insert deleted node then update it. Right branch', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(1n, 1n); + await mt.add(5n, 5n); + await mt.add(7n, 7n); + + const expectedSiblings = [ + 0n, + 90793012380542163121115292742720211788796606421058579751728012408711775415639n + ]; + + await mt.delete(7n); + let proof = await mt.generateProof(7n, await mt.root()); + expect(proof.proof.existence).toEqual(false); + compareSiblings(expectedSiblings, proof.proof); + + await mt.add(7n, 7n); + proof = await mt.generateProof(7n, await mt.root()); + expect(proof.proof.existence).toEqual(true); + compareSiblings(expectedSiblings, proof.proof); + + await mt.update(7n, 100n); + const updatedNode = await mt.get(7n); + expect(updatedNode.key).toEqual(7n); + expect(updatedNode.value).toEqual(100n); + }); + + it('test insert deleted node then update it. Left branch', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(6n, 6n); + await mt.add(2n, 2n); + await mt.add(4n, 4n); + + const expectedSiblings = [ + 0n, + 77032867444825930314963427098772579657571055799776141430952584153445051303225n + ]; + + await mt.delete(4n); + let proof = await mt.generateProof(4n, await mt.root()); + expect(proof.proof.existence).toEqual(false); + compareSiblings(expectedSiblings, proof.proof); + + await mt.add(4n, 4n); + proof = await mt.generateProof(4n, await mt.root()); + expect(proof.proof.existence).toEqual(true); + compareSiblings(expectedSiblings, proof.proof); + + await mt.update(4n, 100n); + const updatedNode = await mt.get(4n); + expect(updatedNode.key).toEqual(4n); + expect(updatedNode.value).toEqual(100n); + }); + + it('test push leaf already exists. Right branch', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(1n, 1n); + await mt.add(5n, 5n); + await mt.add(7n, 7n); + await mt.add(3n, 3n); + + const expectedSiblingsNonExist = [ + 0n, + 90793012380542163121115292742720211788796606421058579751728012408711775415639n + ]; + await mt.delete(3n); + let proof = await mt.generateProof(3n, await mt.root()); + expect(proof.proof.existence).toEqual(false); + compareSiblings(expectedSiblingsNonExist, proof.proof); + + const expectedSiblingsExist = [ + 0n, + 90793012380542163121115292742720211788796606421058579751728012408711775415639n, + 58778245672362516760657188918491108074068739632281986516447865413987646804066n + ]; + await mt.add(3n, 3n); + proof = await mt.generateProof(3n, await mt.root()); + expect(proof.proof.existence).toEqual(true); + compareSiblings(expectedSiblingsExist, proof.proof); + }); + + it('test push leaf already exists. Left branch', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(6n, 6n); + await mt.add(2n, 2n); + await mt.add(4n, 4n); + await mt.add(8n, 8n); + + const expectedSiblingsNonExist = [ + 0n, + 77032867444825930314963427098772579657571055799776141430952584153445051303225n + ]; + await mt.delete(8n); + let proof = await mt.generateProof(8n, await mt.root()); + expect(proof.proof.existence).toEqual(false); + compareSiblings(expectedSiblingsNonExist, proof.proof); + + const expectedSiblingsExist = [ + 0n, + 77032867444825930314963427098772579657571055799776141430952584153445051303225n, + 75643218886135556618007590826502336434547303617941809863348288103535263487329n + ]; + await mt.add(8n, 8n); + proof = await mt.generateProof(8n, await mt.root()); + expect(proof.proof.existence).toEqual(true); + compareSiblings(expectedSiblingsExist, proof.proof); + }); + + it('test up nodes to two levels. Right branch', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(1n, 1n); + await mt.add(7n, 7n); + await mt.add(15n, 15n); + await mt.delete(15n); + + const proof = await mt.generateProof(15n, await mt.root()); + expect(proof.proof.existence).toEqual(false); + compareSiblings( + [0n, 37245951031790617425846662166414203447230852981947190519491676860679629218468n], + proof.proof + ); + }); + + it('test up nodes to two levels. Left branch', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + await mt.add(2n, 2n); + await mt.add(8n, 8n); + await mt.add(16n, 16n); + await mt.delete(16n); + + const proof = await mt.generateProof(16n, await mt.root()); + expect(proof.proof.existence).toEqual(false); + compareSiblings( + [0n, 204679323412988774155807184358931572859960307382228217179160870267303983297n], + proof.proof + ); + }); + + it('test dump leafs and import leafs', async () => { + const sto1 = getTreeStorage('tree1'); + const sto2 = getTreeStorage('tree2'); + const mt1 = new Merkletree(sto1, true, 140); + const mt2 = new Merkletree(sto2, true, 140); + + for (let i = 0; i < 10; i += 1) { + let k = MAX_NUM_IN_FIELD - BigInt(i.toString()); + const v = BigInt('0'); + await mt1.add(k, v); + + k = BigInt(i); + await mt1.add(k, v); + } + }); + + it('test add and get circom proof', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + expect((await mt.root()).string()).toEqual('0'); + + let cp = await mt.addAndGetCircomProof(BigInt('1'), BigInt('2')); + + expect(cp.oldRoot.string()).toEqual('0'); + expect(cp.newRoot.string()).toEqual( + '20349940423862035287868699599764962454537984981628200184279725786303353984557' + ); + expect(cp.oldKey.string()).toEqual('0'); + expect(cp.oldValue.string()).toEqual('0'); + expect(cp.newKey.string()).toEqual('1'); + expect(cp.newValue.string()).toEqual('2'); + expect(cp.isOld0).toEqual(true); + cp.siblings.forEach((s) => { + expect(s.string()).toEqual('0'); + }); + expect(mt.maxLevels).toEqual(cp.siblings.length); + + cp = await mt.addAndGetCircomProof(BigInt('33'), BigInt('44')); + + expect(cp.oldRoot.string()).toEqual( + '20349940423862035287868699599764962454537984981628200184279725786303353984557' + ); + expect(cp.newRoot.string()).toEqual( + '76534138237239231515859035502772486263463178175980489503663557460094727691106' + ); + expect(cp.oldKey.string()).toEqual('1'); + expect(cp.oldValue.string()).toEqual('2'); + expect(cp.newKey.string()).toEqual('33'); + expect(cp.newValue.string()).toEqual('44'); + expect(cp.isOld0).toEqual(false); + cp.siblings.forEach((s) => { + expect(s.string()).toEqual('0'); + }); + expect(mt.maxLevels).toEqual(cp.siblings.length); + + cp = await mt.addAndGetCircomProof(BigInt('55'), BigInt('66')); + + expect(cp.oldRoot.string()).toEqual( + '76534138237239231515859035502772486263463178175980489503663557460094727691106' + ); + expect(cp.newRoot.string()).toEqual( + '100083525042858579469173067236420875189251047437244717808734980864550950841073' + ); + expect(cp.oldKey.string()).toEqual('0'); + expect(cp.oldValue.string()).toEqual('0'); + expect(cp.newKey.string()).toEqual('55'); + expect(cp.newValue.string()).toEqual('66'); + expect(cp.isOld0).toEqual(true); + cp.siblings.forEach((s, idx) => { + expect(s.string()).toEqual( + idx === 1 + ? '100147266556511215860644966030919568737539727316864555910145591818465462834425' + : '0' + ); + }); + expect(mt.maxLevels).toEqual(cp.siblings.length); + }); + + it('test update circom processor proof', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 10); + + for (let i = 0; i < 16; i += 1) { + const k = BigInt(i); + const v = BigInt(i * 2); + await mt.add(k, v); + } + + const { value } = await mt.get(BigInt('10')); + expect(value.toString(10)).toEqual('20'); + + const cp = await mt.update(BigInt('10'), BigInt('1024')); + expect(cp.oldRoot.string()).toEqual( + '70677940031105004292099401697546638864981638318384841379601260831501391203788' + ); + expect(cp.newRoot.string()).toEqual( + '48796142087158721811528672088219959178352830095639452772749500760280476852932' + ); + expect(cp.oldKey.string()).toEqual('10'); + expect(cp.oldValue.string()).toEqual('20'); + expect(cp.newKey.string()).toEqual('10'); + expect(cp.newValue.string()).toEqual('1024'); + expect(cp.isOld0).toEqual(false); + expect(cp.siblings[0].string()).toEqual( + '45247436445267862009183516361627205881717670673355502280321468321589941650846' + ); + expect(cp.siblings[1].string()).toEqual( + '76599317612364446295737122728756946145443571773351769593968842498322866493543' + ); + expect(cp.siblings[2].string()).toEqual( + '106508940255354195227377169991555575191708987078060771291990080645784153376100' + ); + expect(cp.siblings[3].string()).toEqual( + '99711606839544608379269298367305638623929069793968545743733961006129285845166' + ); + cp.siblings.slice(4).forEach((s) => { + expect(s.string()).toEqual('0'); + }); + }); + + it('expect tree.walk does not produce infinite loop', async () => { + const f = async (node: Node): Promise => { + return Promise.resolve(); + }; + const tree = new Merkletree(new InMemoryDB(str2Bytes(''), HashAlgorithm.Keccak256), true, 40); + + for (let i = 0; i < 5; i++) { + await tree.add(BigInt(i), BigInt(i)); + } + + await tree.walk(await tree.root(), (node: Node) => f(node)); + }); + + it('proof stringify (old format for node aux)', async () => { + const tree = new Merkletree(new InMemoryDB(str2Bytes(''), HashAlgorithm.Keccak256), true, 40); + + for (let i = 0; i < 5; i++) { + await tree.add(BigInt(i), BigInt(i)); + } + + const { proof, value } = await tree.generateProof(BigInt(9)); + + const proofModel = JSON.stringify(proof); + const p = JSON.parse(proofModel) as ProofJSON; + + p.nodeAux = p.node_aux; + p.node_aux = undefined; + + const proofFromJSON = Proof.fromJSON(JSON.parse(proofModel)); + + expect(JSON.stringify(proof.allSiblings())).toEqual( + JSON.stringify(proofFromJSON.allSiblings()) + ); + expect(proof.existence).toEqual(proofFromJSON.existence); + expect(proof.existence).toEqual(false); + expect(JSON.stringify(proof.nodeAux)).toEqual(JSON.stringify(proofFromJSON.nodeAux)); + }); + it('proof stringify (new format for node aux)', async () => { + const tree = new Merkletree(new InMemoryDB(str2Bytes(''), HashAlgorithm.Keccak256), true, 40); + + for (let i = 0; i < 5; i++) { + await tree.add(BigInt(i), BigInt(i)); + } + + const { proof, value } = await tree.generateProof(BigInt(9)); + + const proofModel = JSON.stringify(proof); + + const proofFromJSON = Proof.fromJSON(JSON.parse(proofModel)); + + expect(JSON.stringify(proof.allSiblings())).toEqual( + JSON.stringify(proofFromJSON.allSiblings()) + ); + expect(proof.existence).toEqual(proofFromJSON.existence); + expect(proof.existence).toEqual(false); + expect(JSON.stringify(proof.nodeAux)).toEqual(JSON.stringify(proofFromJSON.nodeAux)); + }); + it('should deserialize Old Hash properly', async () => { + const hash = new Hash( + bigIntToUINT8Array( + BigInt('5158240518874928563648144881543092238925265313977134167935552944620041388700') + ) + ); + + const oldSerializedHash = + '{"bytes":{"0":11,"1":103,"2":117,"3":238,"4":151,"5":230,"6":106,"7":85,"8":195,"9":138,"10":136,"11":160,"12":178,"13":153,"14":109,"15":13,"16":220,"17":95,"18":34,"19":180,"20":1,"21":227,"22":55,"23":246,"24":102,"25":115,"26":95,"27":214,"28":80,"29":163,"30":194,"31":156}}'; + // deserialize + const deserializedHash = JSON.parse(oldSerializedHash); + const bytes = Uint8Array.from(Object.values(deserializedHash.bytes)); + const hash2 = new Hash(bytes); + const hashFromOldStr = Hash.fromString(oldSerializedHash); + + expect(JSON.stringify(hash)).toEqual(JSON.stringify(hashFromOldStr.bigInt().toString())); + expect(JSON.stringify(hash.bytes)).toEqual(JSON.stringify(bytes)); + expect(hash.toJSON()).toEqual(hash2.bigInt().toString()); + expect(hash.bytes).toEqual(hash2.bytes); + + expect(hash.hex()).toEqual(Hash.fromHex(hash2.hex()).hex()); + }); + it('test smt verifier', async () => { + const sto = getTreeStorage(); + const mt = new Merkletree(sto, true, 4); + + await mt.add(BigInt('1'), BigInt('11')); + let cvp = await mt.generateSCVerifierProof(BigInt('1'), ZERO_HASH); + + expect(cvp.root.string()).toEqual( + '82784984500014977958877763851140688264267178109068954640885039848244046400394' + ); + expect(cvp.siblings.length).toEqual(0); + expect(cvp.oldKey.string()).toEqual('0'); + expect(cvp.oldValue.string()).toEqual('0'); + expect(cvp.isOld0).toEqual(false); + expect(cvp.key.string()).toEqual('1'); + expect(cvp.value.string()).toEqual('11'); + expect(cvp.fnc).toEqual(0); + + await mt.add(BigInt('2'), BigInt('22')); + await mt.add(BigInt('3'), BigInt('33')); + await mt.add(BigInt('4'), BigInt('44')); + + cvp = await mt.generateCircomVerifierProof(BigInt('2'), ZERO_HASH); + + expect(cvp.root.string()).toEqual( + '72673135994199316395211355754392715689773200652773094050830171707225485579516' + ); + expect(cvp.siblings.length).toEqual(4); + expect(cvp.siblings[0].string()).toEqual( + '95410369572552625669929443880336035845295666870039053310723367652976210217889' + ); + expect(cvp.siblings[1].string()).toEqual( + '108591810159780947021882411046895107657640634072271577060276219923125949566920' + ); + cvp.siblings.slice(3).forEach((s) => { + expect(s.string()).toEqual('0'); + }); + expect(cvp.oldKey.string()).toEqual('0'); + expect(cvp.oldValue.string()).toEqual('0'); + expect(cvp.isOld0).toEqual(false); + expect(cvp.key.string()).toEqual('2'); + expect(cvp.value.string()).toEqual('22'); + expect(cvp.fnc).toEqual(0); + }); + it('calculate depth for mtp', async () => { + const storage = getTreeStorage('calculatedepth'); + const mt = new Merkletree(storage, true, 40); + + await mt.add(BigInt('1'), BigInt('2')); + await mt.add(BigInt('3'), BigInt('8')); + await mt.add(BigInt('7'), BigInt('8')); + await mt.add(BigInt('9'), BigInt('8')); + + const { proof }: { proof: Proof } = await mt.generateProof(BigInt('11'), await mt.root()); + + const given = `{ "existence": false, "siblings": [ "0", "30257231137654976416713033869467714092857353299944484231479292418632657921939", "108174256589026683124305912205446370618204099420522125478345491452742619876089", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" ], "node_aux": { "key": "3", "value": "8" }}`; + const p = Proof.fromJSON(JSON.parse(given)); + + expect(proof.allSiblings()).toEqual(p.allSiblings()); + expect(proof.nodeAux).toEqual(p.nodeAux); + expect(proof.existence).toEqual(p.existence); + + let isValid = await verifyProof(await mt.root(), proof, BigInt('11'), BigInt('0'), HashAlgorithm.Keccak256); + expect(isValid).toEqual(true); + isValid = await verifyProof(await mt.root(), p, BigInt('11'), BigInt('0'), HashAlgorithm.Keccak256); + expect(isValid).toEqual(true); + }); + it('calculate depth for mtp (old format)', async () => { + const storage = getTreeStorage('calculatedepth'); + const mt = new Merkletree(storage, true, 40); + + await mt.add(BigInt('1'), BigInt('2')); + await mt.add(BigInt('3'), BigInt('8')); + await mt.add(BigInt('7'), BigInt('8')); + await mt.add(BigInt('9'), BigInt('8')); + + const { proof }: { proof: Proof } = await mt.generateProof(BigInt('11'), await mt.root()); + + const given = `{ "existence": false, "siblings": [ "0", "30257231137654976416713033869467714092857353299944484231479292418632657921939", "108174256589026683124305912205446370618204099420522125478345491452742619876089", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" ], "nodeAux": { "key": "3", "value": "8" }}`; + const p = Proof.fromJSON(JSON.parse(given)); + + expect(proof.allSiblings()).toEqual(p.allSiblings()); + expect(proof.nodeAux).toEqual(p.nodeAux); + expect(proof.existence).toEqual(p.existence); + + let isValid = await verifyProof(await mt.root(), proof, BigInt('11'), BigInt('0'), HashAlgorithm.Keccak256); + expect(isValid).toEqual(true); + isValid = await verifyProof(await mt.root(), p, BigInt('11'), BigInt('0'), HashAlgorithm.Keccak256); + expect(isValid).toEqual(true); + }); + }); +} + +const compareSiblings = (expectedSiblings: bigint[], p: Proof) => { + const actualSiblings = p.allSiblings(); + expect(actualSiblings.length).toEqual(expectedSiblings.length); + for (let i = 0; i < expectedSiblings.length; i++) { + expect(actualSiblings[i].bigInt()).toEqual(expectedSiblings[i]); + } +}; diff --git a/tests/full.test.ts b/tests/full.test.ts index 8c53baf..8db6971 100644 --- a/tests/full.test.ts +++ b/tests/full.test.ts @@ -3,7 +3,7 @@ import { HASH_BYTES_LENGTH, MAX_NUM_IN_FIELD, NODE_TYPE_LEAF } from '../src/cons import { NodeLeaf, NodeMiddle } from '../src/lib/node/node'; import { InMemoryDB, LocalStorageDB, IndexedDBStorage } from '../src/lib/db'; import { bigIntToUINT8Array, bytes2Hex, bytesEqual, str2Bytes } from '../src/lib/utils'; -import { Hash, ZERO_HASH } from '../src/lib/hash/hash'; +import { Hash, ZERO_HASH, HashAlgorithm } from '../src/lib/hash/hash'; import { Merkletree, Proof, ProofJSON, verifyProof } from '../src/lib/merkletree'; import { ErrEntryIndexAlreadyExists, ErrKeyNotFound, ErrReachedMaxLevel } from '../src/lib/errors'; import { poseidon } from '@iden3/js-crypto'; @@ -54,7 +54,7 @@ for (let index = 0; index < storages.length; index++) { bytes[0] = 1; const v = new Hash(bytes); - const node = new NodeMiddle(v, v); + const node = new NodeMiddle(v, v, HashAlgorithm.Poseidon); const k = await node.getKey(); await sto.put(k.value, node); const val = await sto.get(k.value);