From be9262a3a000d1c8d7d9c9158153fc7ca2971fd8 Mon Sep 17 00:00:00 2001 From: Artur Yurii Korchynskyi <42449190+akorchyn@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:24:17 +0300 Subject: [PATCH] decrypt and submit results (#24) --- relayer/package-lock.json | 540 +++++++++++++++++- relayer/package.json | 5 +- relayer/src/api/controllers/decryption.ts | 109 ++++ relayer/src/api/controllers/submitVote.ts | 1 - relayer/src/api/routes.ts | 4 + relayer/src/api/utils/near.ts | 87 ++- relayer/src/api/utils/secret.ts | 74 +++ .../cryptography/__tests__/encrypt_decrypt.ts | 39 +- relayer/src/cryptography/index.ts | 10 +- relayer/src/index.ts | 20 +- 10 files changed, 867 insertions(+), 22 deletions(-) create mode 100644 relayer/src/api/controllers/decryption.ts create mode 100644 relayer/src/api/utils/secret.ts diff --git a/relayer/package-lock.json b/relayer/package-lock.json index c64d7b9..4b226b5 100644 --- a/relayer/package-lock.json +++ b/relayer/package-lock.json @@ -8,12 +8,15 @@ "name": "server", "version": "0.0.1", "dependencies": { + "@supercharge/promise-pool": "^3.2.0", "bn.js": "^5.2.1", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", "miscreant": "^0.3.2", - "secp256k1": "^5.0.0" + "p-retry": "4.6.2", + "secp256k1": "^5.0.0", + "secretjs": "^1.12.5" }, "devDependencies": { "@types/cors": "^2.8.17", @@ -685,6 +688,29 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@cosmjs/encoding": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.27.1.tgz", + "integrity": "sha512-rayLsA0ojHeniaRfWWcqSsrE/T1rl1gl0OXVNtXlPwLJifKBeLEefGbOUiAQaT0wgJ8VNGBazVtAZBpJidfDhw==", + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "node_modules/@cosmjs/encoding/node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, + "node_modules/@cosmjs/math": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.27.1.tgz", + "integrity": "sha512-cHWVjmfIjtRc7f80n7x+J5k8pe+vTVTQ0lA82tIxUgqUvgS6rogPP/TmGtTiZ4+NxWxd11DUISY6gVpr18/VNQ==", + "dependencies": { + "bn.js": "^5.2.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1088,6 +1114,76 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@noble/hashes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.0.0.tgz", + "integrity": "sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg==" + }, + "node_modules/@noble/secp256k1": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.0.tgz", + "integrity": "sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1112,6 +1208,14 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@supercharge/promise-pool": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@supercharge/promise-pool/-/promise-pool-3.2.0.tgz", + "integrity": "sha512-pj0cAALblTZBPtMltWOlZTQSLT07jIaFNeM8TWoJD1cQMgDB9mcMlVMoetiB35OzNJpqQ2b+QEtwiR9f20mADg==", + "engines": { + "node": ">=8" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -1288,7 +1392,6 @@ "version": "20.12.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.3.tgz", "integrity": "sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -1305,6 +1408,11 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, "node_modules/@types/secp256k1": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.6.tgz", @@ -1589,6 +1697,54 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/bignumber.js": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", + "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1601,6 +1757,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bip32": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz", + "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", + "dependencies": { + "@types/node": "10.12.18", + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "tiny-secp256k1": "^1.1.3", + "typeforce": "^1.11.5", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bip32/node_modules/@types/node": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", + "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" + }, + "node_modules/bip39": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", + "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", + "dependencies": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, + "node_modules/bip39/node_modules/@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" + }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -1700,6 +1902,24 @@ "node": ">= 6" } }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -1864,6 +2084,15 @@ "node": ">=8" } }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/cjs-module-lexer": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", @@ -2025,6 +2254,31 @@ "node": ">= 0.10" } }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2052,6 +2306,14 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dependencies": { + "node-fetch": "2.6.7" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2066,6 +2328,11 @@ "node": ">= 8" } }, + "node_modules/curve25519-js": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/curve25519-js/-/curve25519-js-0.0.4.tgz", + "integrity": "sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==" + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -2416,6 +2683,11 @@ "bser": "2.1.1" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2600,6 +2872,11 @@ "node": ">=4" } }, + "node_modules/google-protobuf": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", + "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -2659,6 +2936,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -3654,6 +3944,11 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3696,6 +3991,16 @@ "tmpl": "1.0.5" } }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3807,6 +4112,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/nan": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3826,6 +4136,25 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-gyp-build": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", @@ -4028,6 +4357,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -4037,6 +4378,11 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -4101,6 +4447,21 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -4179,6 +4540,29 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4227,6 +4611,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4255,6 +4647,19 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4267,6 +4672,11 @@ "node": ">=8.10.0" } }, + "node_modules/readonly-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz", + "integrity": "sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==" + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -4329,6 +4739,23 @@ "node": ">=10" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -4376,6 +4803,34 @@ "node": ">=14.0.0" } }, + "node_modules/secretjs": { + "version": "1.12.5", + "resolved": "https://registry.npmjs.org/secretjs/-/secretjs-1.12.5.tgz", + "integrity": "sha512-JEnvJcfQRuJyu2QtsFQOZ3Vb0sQO8puKV1hK7rVsgROFWHE0s2wSmamTn9SV+hncgRwEtEtGht3Mu1WtQ5xfYA==", + "dependencies": { + "@cosmjs/encoding": "0.27.1", + "@cosmjs/math": "0.27.1", + "@noble/hashes": "1.0.0", + "@noble/secp256k1": "1.7.0", + "bech32": "2.0.0", + "big-integer": "1.6.51", + "bignumber.js": "9.0.2", + "bip32": "2.0.6", + "bip39": "3.0.4", + "cross-fetch": "3.1.5", + "curve25519-js": "0.0.4", + "google-protobuf": "^3.14.0", + "miscreant": "0.3.2", + "pako": "2.0.4", + "protobufjs": "7.2.5", + "secure-random": "1.1.2" + } + }, + "node_modules/secure-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/secure-random/-/secure-random-1.1.2.tgz", + "integrity": "sha512-H2bdSKERKdBV1SwoqYm6C0y+9EA94v6SUBOWO8kDndc4NoUih7Dv6Tsgma7zO1lv27wIvjlD0ZpMQk7um5dheQ==" + }, "node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -4454,6 +4909,18 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4585,6 +5052,14 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -4692,6 +5167,27 @@ "node": ">=8" } }, + "node_modules/tiny-secp256k1": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", + "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/tiny-secp256k1/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -4739,6 +5235,11 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -4873,6 +5374,11 @@ "node": ">= 0.6" } }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, "node_modules/typescript": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", @@ -4895,8 +5401,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unpipe": { "version": "1.0.0", @@ -4936,6 +5441,11 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -4991,6 +5501,20 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5006,6 +5530,14 @@ "node": ">= 8" } }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "dependencies": { + "bs58check": "<3.0.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/relayer/package.json b/relayer/package.json index f3ca842..d4c6fda 100644 --- a/relayer/package.json +++ b/relayer/package.json @@ -11,12 +11,15 @@ }, "author": "akorchyn", "dependencies": { + "@supercharge/promise-pool": "^3.2.0", "bn.js": "^5.2.1", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", "miscreant": "^0.3.2", - "secp256k1": "^5.0.0" + "p-retry": "4.6.2", + "secp256k1": "^5.0.0", + "secretjs": "^1.12.5" }, "devDependencies": { "@types/cors": "^2.8.17", diff --git a/relayer/src/api/controllers/decryption.ts b/relayer/src/api/controllers/decryption.ts new file mode 100644 index 0000000..dda5e30 --- /dev/null +++ b/relayer/src/api/controllers/decryption.ts @@ -0,0 +1,109 @@ +import { Request, Response } from "express"; +import { getSecretKeys } from "../utils/secret"; +import { base_encode } from "near-api-js/lib/utils/serialize"; +import { getVoterPublicKey, isNominee, getAllVotes, sendResultsToContract } from "../utils/near"; +import { decrypt, verifySignature } from "../../cryptography"; +import { VotingPackage } from "../../cryptography/types"; + +export const getPublicKey = async (_: Request, res: Response) => { + const secretKeys = await getSecretKeys(); + + if (secretKeys.error || !secretKeys.data) { + console.error("Error while getting secret keys", secretKeys.error); + return res.status(500).send({ message: "Error while getting secret keys" }); + } + + return res.status(200).send(base_encode(secretKeys.data.public)); +} + +export const postDecryption = async (req: Request, res: Response) => { + const secretKeys = await getSecretKeys(); + + if (secretKeys.error || !secretKeys.data) { + console.error("Error while getting secret keys", secretKeys.error); + return res.status(500).send({ message: "Error while getting secret keys" }); + } + + if (secretKeys.data.private === undefined) { + const endTime = new Date(secretKeys.data.end_time / 1_000_000).toUTCString() + return res.status(425).send({ message: `Secret key not available yet. Please come after ${endTime} UTC` }); + } + + const encryptedVotes = await getAllVotes(); + if (encryptedVotes.length === 0) { + return res.status(400).send({ message: "No votes to decrypt" }); + } + + const decryptedVotes = new Map(); + for (let i = 0; i < encryptedVotes.length; i++) { + const vote = encryptedVotes[i]; + let result = await decrypt(vote, secretKeys.data.private); + + if (result.error || !result.data) { + console.log(`Discard vote ${i}: ${result.error}`); + continue; + } + + let isValid = await validateVote(result.data, i); + if (!isValid) { + continue; + } + + if (decryptedVotes.has(result.data.accountId)) { + console.log(`Found new vote for ${result.data.accountId}. Replacing the old vote`); + } + decryptedVotes.set(result.data.accountId, result.data); + } + + const results = new Map(); + decryptedVotes.forEach((vote) => { + vote.votes.forEach((v) => { + results.set(v.candidate, (results.get(v.candidate) ?? 0) + v.weight); + }); + }); + + if (await sendResultsToContract(Array.from(results.entries()))) { + return res.status(200).send(decryptedVotes); + } + return res.status(500).send({ message: "Error while submitting results to the contract" }); +} + +const validateVote = async (vote: VotingPackage, voteNumber: number): Promise => { + const { accountId, votes, signature } = vote; + + const voterInfo = await getVoterPublicKey(accountId); + if (!voterInfo) { + console.log(`Discard vote ${voteNumber}: Voter is not registered`); + return false; + } + + const data = base_encode(JSON.stringify({ accountId, votes })); + if (!verifySignature(data, voterInfo.public_key, signature)) { + console.log(`Discard vote ${voteNumber}: Invalid user signature`); + return false; + } + + + let totalWeightUsed = 0; + for (let i = 0; i < votes.length; i++) { + let vote = votes[i]; + if (vote.weight < 0) { + console.log(`Discard vote ${voteNumber}: Invalid vote weight`); + return false; + } + totalWeightUsed += vote.weight; + + let status = await isNominee(vote.candidate); + if (!status) { + console.log(`Discard vote ${voteNumber}: Invalid candidate`); + return false; + } + } + + if (totalWeightUsed > voterInfo.vote_weight) { + console.log(`Discard vote ${voteNumber}: Vote weight exceeds the voter's total weight`); + return false; + } + + return true; +} diff --git a/relayer/src/api/controllers/submitVote.ts b/relayer/src/api/controllers/submitVote.ts index 98269e2..05b742e 100644 --- a/relayer/src/api/controllers/submitVote.ts +++ b/relayer/src/api/controllers/submitVote.ts @@ -36,7 +36,6 @@ export const postVote = async (req: Request, res: Response) => { } } - // Check if the user is a registered voter in the snapshot contract const voterInfo = await getVoterPublicKey(data.accountId); if (!voterInfo) { diff --git a/relayer/src/api/routes.ts b/relayer/src/api/routes.ts index 4f1402b..521aee2 100644 --- a/relayer/src/api/routes.ts +++ b/relayer/src/api/routes.ts @@ -1,8 +1,12 @@ import express from "express"; import { postVote } from "./controllers/submitVote"; +import { getPublicKey, postDecryption } from "./controllers/decryption"; const routes = express.Router(); +routes.get("/encryption-public-key", getPublicKey); + routes.post("/vote", postVote) +routes.post("/decrypt", postDecryption); export { routes }; diff --git a/relayer/src/api/utils/near.ts b/relayer/src/api/utils/near.ts index e62b46d..e6e9825 100644 --- a/relayer/src/api/utils/near.ts +++ b/relayer/src/api/utils/near.ts @@ -1,10 +1,12 @@ -import { connect, Contract, KeyPair, keyStores, ConnectConfig, Account, Near } from 'near-api-js'; +import { connect, Contract, keyStores, ConnectConfig, Account, Near } from 'near-api-js'; import { AccountId, EncryptedVotingPackage } from '../../cryptography/types'; import { NETWORK_ID, RELAYER_ACCOUNT, SNAPSHOT_CONTRACT, VOTING_CONTRACT } from '../..'; import os from 'os'; import path from 'path'; -import { PublicKey } from 'near-api-js/lib/utils'; import { parseNearAmount } from 'near-api-js/lib/utils/format'; +import PromisePool from '@supercharge/promise-pool'; +import pRetry, { FailedAttemptError } from "p-retry"; + // Load credentials; const homedir = os.homedir(); @@ -15,6 +17,7 @@ const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); // 300TGAS const GAS = "300000000000000"; const DEPOSIT = parseNearAmount("0.5"); +const RETRIES = 20; let connectionConfig: ConnectConfig; if (NETWORK_ID === "mainnet") { @@ -42,10 +45,15 @@ let votingContract: VotingContract; type SnapshotContract = Contract & { get_voter_information: (args: { voter: AccountId }) => Promise; + is_nominee: (args: { nominee: AccountId }) => Promise; }; type VotingContract = Contract & { send_encrypted_votes: (args: any) => Promise; + sumbit_results: (args: any) => Promise; + + get_total_votes: () => Promise; + get_votes: (args: { page: number, limit: number }) => Promise; }; export const initializeNear = async () => { @@ -55,14 +63,14 @@ export const initializeNear = async () => { relayer = await near.account(RELAYER_ACCOUNT!); snapshotContract = new Contract(relayer, SNAPSHOT_CONTRACT!, { - viewMethods: ["get_voter_information"], + viewMethods: ["get_voter_information", "is_nominee"], changeMethods: [], useLocalViewExecution: false, }) as SnapshotContract; votingContract = new Contract(relayer, VOTING_CONTRACT!, { - viewMethods: [], - changeMethods: ['send_encrypted_votes'], + viewMethods: ['get_total_votes', 'get_votes'], + changeMethods: ['send_encrypted_votes', 'sumbit_results'], useLocalViewExecution: false, }) as VotingContract; } catch (error) { @@ -81,8 +89,7 @@ export const getVoterPublicKey = async (accountId: AccountId): Promise => { + try { + await votingContract.sumbit_results({ args: { results }, gas: GAS, amount: DEPOSIT }); + return true; + } catch (error) { + console.error('Error submitting results to contract:', error); + return false; + } +} + +export const getAllVotes = async (): Promise => { + try { + const totalVotes = await votingContract.get_total_votes(); + const votes: EncryptedVotingPackage[] = []; + + const PAGE_SIZE = 2; + const totalPages = Math.ceil(totalVotes / PAGE_SIZE); + const pageNumbers = Array.from({ length: totalPages }, (_, i) => i); + + const onFailedAttempt = (error: FailedAttemptError) => { + console.warn(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`); + }; + + const { results: voteResults, errors: voteErrors } = await PromisePool + .withConcurrency(10) + .for(pageNumbers) + .useCorrespondingResults() + .process(async (page) => { + return pRetry(async () => { + const pageVotes = await votingContract.get_votes({ + page, + limit: PAGE_SIZE, + }); + + return pageVotes.map((vote: any) => ({ + encryptedData: vote.vote, + publicKey: vote.pubkey, + })); + }, { + retries: RETRIES, + onFailedAttempt, + }); + }); + + votes.push(...voteResults.flat()); + + if (voteErrors.length > 0) { + console.error('Errors occurred while loading votes:', voteErrors); + return []; + } + + return votes; + } catch (error) { + console.error('Error loading votes from contract:', error); + return []; + } +}; + +export const isNominee = async (accountId: AccountId): Promise => { + try { + return await snapshotContract.is_nominee({ nominee: accountId }); + } catch (_) { + return false; + } +} diff --git a/relayer/src/api/utils/secret.ts b/relayer/src/api/utils/secret.ts new file mode 100644 index 0000000..1ef7302 --- /dev/null +++ b/relayer/src/api/utils/secret.ts @@ -0,0 +1,74 @@ +import { SecretNetworkClient, Wallet } from "secretjs"; +import ecdsa from "secp256k1"; +import { NETWORK_ID, SECRET_CODE_HASH, SECRET_CONTRACT } from "../.."; +import { Result } from "../../cryptography"; + + +const wallet = new Wallet(); + +let secretjs: SecretNetworkClient; + +if (NETWORK_ID === "mainnet") { + secretjs = new SecretNetworkClient({ + chainId: "secret-4", + url: "https://rpc.ankr.com/http/scrt_cosmos", + wallet: wallet, + walletAddress: wallet.address, + }); +} +else { + secretjs = new SecretNetworkClient({ + chainId: "pulsar-3", + url: "https://lcd.pulsar-3.secretsaturn.net", + wallet: wallet, + walletAddress: wallet.address, + }); +} + +type SecretResponse = { + public: number[], + private: number[] | undefined, + end_time: number +} + +type Response = { + public: Uint8Array, + private: Uint8Array | undefined, + end_time: number +} + +type Request = { + get_keys: {} +} + +export async function getSecretKeys(): Promise> { + let query = await secretjs.query.compute.queryContract({ + contract_address: SECRET_CONTRACT!, + query: { + get_keys: {}, + }, + code_hash: SECRET_CODE_HASH, + }); + + if (query.private !== undefined && query.private.length > 0) { + const derivedPubKey = ecdsa.publicKeyCreate(Uint8Array.from(query.private), true); + const publicKey = query.public; + + if (!derivedPubKey.every((value, index) => value === publicKey[index])) { + return { + error: 'Secret key is invalid', + data: undefined + } + } + } + + return { + error: undefined, + data: { + public: Uint8Array.from(query.public), + private: query.private ? Uint8Array.from(query.private) : undefined, + end_time: query.end_time + } + } +}; + diff --git a/relayer/src/cryptography/__tests__/encrypt_decrypt.ts b/relayer/src/cryptography/__tests__/encrypt_decrypt.ts index 402f28b..e5ce54a 100644 --- a/relayer/src/cryptography/__tests__/encrypt_decrypt.ts +++ b/relayer/src/cryptography/__tests__/encrypt_decrypt.ts @@ -1,7 +1,8 @@ -import { publicKeyCreate, privateKeyVerify } from 'secp256k1'; +import { publicKeyCreate, privateKeyVerify, publicKeyVerify } from 'secp256k1'; import { encrypt, decrypt, createSignature } from '../index'; import { VotingPackage } from '../types'; import { randomBytes } from 'crypto'; +import { base_encode } from 'near-api-js/lib/utils/serialize'; describe('Encryption and Decryption', () => { let privateKey: Uint8Array; @@ -33,7 +34,7 @@ describe('Encryption and Decryption', () => { }); it('should encrypt and decrypt voting package', async () => { - const encrypted = await encrypt(votingPackage, privateKey, userPublicKey); + const encrypted = await encrypt(votingPackage, userPrivateKey, publicKey); expect(encrypted.error).toBeUndefined(); expect(encrypted.data).toBeDefined(); @@ -49,6 +50,7 @@ describe('Encryption and Decryption', () => { const [privateKey1, _] = generateKeyPair(); const decrypted = await decrypt(encrypted.data!, privateKey1); + console.log(decrypted); expect(decrypted.error).toBe('Could not decrypt data'); expect(decrypted.data).toBeUndefined(); }); @@ -69,4 +71,37 @@ describe('Encryption and Decryption', () => { expect(encrypted.error).toBe('Invalid key'); expect(encrypted.data).toBeUndefined(); }) + + it('data for testing', async () => { + const pk = "YOUR PK HERE"; + const votingPackageData = { + accountId: "yurtur.testnet", + votes: [ + { candidate: "one.near", weight: 1 }, + { candidate: "two.near", weight: 2 }, + ], + }; + const sig = await createSignature(base_encode(JSON.stringify(votingPackageData)), pk); + + const votingPackage: VotingPackage = { ...votingPackageData, signature: sig! }; + + const publicKey = Uint8Array.from([ + 3, 96, 130, 5, 147, 144, 11, 228, + 85, 204, 31, 187, 197, 66, 86, 254, + 95, 147, 16, 227, 252, 210, 205, 247, + 47, 174, 222, 147, 137, 125, 4, 90, + 23 + ]); + + expect(publicKeyVerify(publicKey)).toBe(true); + + const encrypted = await encrypt(votingPackage, privateKey, publicKey); + console.log(encrypted.data); + + expect(encrypted.error).toBeUndefined(); + expect(encrypted.data).toBeDefined(); + + const signature = await createSignature(encrypted.data!.encryptedData + encrypted.data!.publicKey, pk); + console.log(signature); + }) }); diff --git a/relayer/src/cryptography/index.ts b/relayer/src/cryptography/index.ts index 6d6b2e1..92cd48f 100644 --- a/relayer/src/cryptography/index.ts +++ b/relayer/src/cryptography/index.ts @@ -1,4 +1,4 @@ -import { ecdh, ecdsaSign, ecdsaVerify, privateKeyVerify, publicKeyVerify } from 'secp256k1'; +import { ecdh, ecdsaSign, ecdsaVerify, privateKeyVerify, publicKeyCreate, publicKeyVerify } from 'secp256k1'; import { SIV, PolyfillCryptoProvider } from 'miscreant'; import { EncryptedVotingPackage, VotingPackage } from './types'; import { KeyPair, PublicKey } from 'near-api-js/lib/utils'; @@ -6,7 +6,7 @@ import { base_decode, base_encode } from 'near-api-js/lib/utils/serialize'; const provider = new PolyfillCryptoProvider(); -type Result = { +export type Result = { error: string | undefined; data: T | undefined; } @@ -14,7 +14,7 @@ type Result = { const verifyKeys = (privateKey: Uint8Array, publicKey: Uint8Array): boolean => { try { return publicKeyVerify(publicKey) && privateKeyVerify(privateKey); - } catch (_) { + } catch (error) { return false; } } @@ -110,9 +110,9 @@ export const encrypt = async (vote: VotingPackage, privateKey: Uint8Array, publi data: undefined }; } + const publicKeyForExport = publicKeyCreate(privateKey, false); const sharedSecret = ecdh(publicKey, privateKey); - const siv = await SIV.importKey(sharedSecret, "AES-SIV", provider); const encryptedData = await siv.seal(Buffer.from(JSON.stringify(vote)), []); @@ -120,7 +120,7 @@ export const encrypt = async (vote: VotingPackage, privateKey: Uint8Array, publi error: undefined, data: { encryptedData: base_encode(encryptedData), - publicKey: base_encode(publicKey) + publicKey: base_encode(publicKeyForExport) } }; } diff --git a/relayer/src/index.ts b/relayer/src/index.ts index 46fc4af..6b2667d 100644 --- a/relayer/src/index.ts +++ b/relayer/src/index.ts @@ -11,19 +11,33 @@ export const SNAPSHOT_CONTRACT = process.env.SNAPSHOT_CONTRACT; export const NETWORK_ID = process.env.NETWORK_ID || 'testnet'; export const RELAYER_ACCOUNT = process.env.RELAYER_ACCOUNT; +export const SECRET_CONTRACT = process.env.SECRET_CONTRACT; +export const SECRET_CODE_HASH = process.env.SECRET_CODE_HASH; + app.listen(PORT, async () => { + let missingEnv = false; + if (!VOTING_CONTRACT || !SNAPSHOT_CONTRACT) { console.error('Please provide VOTING_CONTRACT and SNAPSHOT_CONTRACT in the environment variables'); - process.exit(1); + missingEnv = true; + } + + if (!SECRET_CONTRACT || !SECRET_CODE_HASH) { + console.error('Please provide SECRET_CONTRACT and SECRET_CODE_HASH in the environment variables'); + missingEnv = true; } if (!RELAYER_ACCOUNT) { console.error('Please provide RELAYER_ACCOUNT in the environment variables'); - process.exit(1); + missingEnv = true; } if (NETWORK_ID !== 'mainnet' && NETWORK_ID !== 'testnet') { console.error('NETWORK_ID should be either mainnet or testnet'); + missingEnv = true; + } + + if (missingEnv) { process.exit(1); } @@ -34,5 +48,7 @@ app.listen(PORT, async () => { console.log(`Snapshot Contract: ${SNAPSHOT_CONTRACT}`); console.log(`Network ID: ${NETWORK_ID}`); console.log(`Relayer Account: ${RELAYER_ACCOUNT}`); + console.log(`Secret Contract: ${SECRET_CONTRACT}`); + console.log(`Secret Code Hash: ${SECRET_CODE_HASH}`); console.log(`------------------`); });