diff --git a/package-lock.json b/package-lock.json index 937a467..d0cf0a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.0", "license": "MIT", "dependencies": { + "@noble/curves": "^1.6.0", "@toruslabs/eccrypto": "^5.0.4", "@toruslabs/http-helpers": "^7.0.0", "bn.js": "^5.2.1", @@ -19,6 +20,7 @@ "web3-utils": "^4.3.1" }, "devDependencies": { + "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/register": "^7.24.6", "@rollup/plugin-replace": "^5.0.7", "@toruslabs/config": "^2.2.0", @@ -36,6 +38,7 @@ "prettier": "^3.3.3", "rimraf": "^6.0.1", "ts-node": "^10.9.2", + "tsx": "^4.19.1", "typescript": "^5.5.4" }, "engines": { @@ -1986,6 +1989,390 @@ "node": ">=10.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2489,12 +2876,25 @@ "license": "MIT" }, "node_modules/@noble/curves": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", - "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", - "license": "MIT", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", "dependencies": { - "@noble/hashes": "1.4.0" + "@noble/hashes": "1.5.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "engines": { + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -3138,6 +3538,17 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@scure/bip39": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", @@ -6291,6 +6702,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -7097,6 +7547,17 @@ "@scure/bip39": "1.3.0" } }, + "node_modules/ethereum-cryptography/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -13624,6 +14085,25 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz", + "integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==", + "dev": true, + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 11a9826..6974bc2 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "build": "torus-scripts build", "release": "torus-scripts release", "prepack": "npm run build", - "test:secp256k1": "CURVE=secp256k1 mocha", - "test:ed25519": "CURVE=ed25519 mocha", + "test:secp256k1": "CURVE=secp256k1 tsx --tsconfig tsconfig.test.json --test test/test.ts", + "test:ed25519": "CURVE=ed25519 tsx --tsconfig tsconfig.test.json --test test/test.ts", "test": "npm run test:secp256k1 && npm run test:ed25519", "lint:ts": "eslint --fix 'src/**/*.ts'", "prepare": "husky install" @@ -26,6 +26,7 @@ "@babel/runtime": "7.x" }, "dependencies": { + "@noble/curves": "^1.6.0", "@toruslabs/eccrypto": "^5.0.4", "@toruslabs/http-helpers": "^7.0.0", "bn.js": "^5.2.1", @@ -36,6 +37,7 @@ "web3-utils": "^4.3.1" }, "devDependencies": { + "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/register": "^7.24.6", "@rollup/plugin-replace": "^5.0.7", "@toruslabs/config": "^2.2.0", @@ -53,6 +55,7 @@ "prettier": "^3.3.3", "rimraf": "^6.0.1", "ts-node": "^10.9.2", + "tsx": "^4.19.1", "typescript": "^5.5.4" }, "lint-staged": { diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 0000000..1e4a840 --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,125 @@ +export function hexToBigInt(hex: string): bigint { + // Ensure the hex string starts with '0x' + const hexWithPrefix = hex.startsWith("0x") ? hex : `0x${hex}`; + return BigInt(hexWithPrefix); +} + +export function bufferToBigInt(buffer: Buffer): bigint { + return hexToBigInt(buffer.toString("hex")); +} + +export function bigIntUmod(a: bigint, m: bigint): bigint { + // return a % m; + return ((a % m) + m) % m; +} + +export function bigIntPointToHexPoint(point: { x: bigint; y: bigint }) { + return { + x: point.x.toString(16).padStart(64, "0"), + y: point.y.toString(16).padStart(64, "0"), + }; +} + +// You'll also need to implement a modular inverse function for BigInt +export function modularInverse(a: bigint, m: bigint): bigint { + let [old_r, r] = [a, m]; + let [old_s, s] = [BigInt(1), BigInt(0)]; + let [old_t, t] = [BigInt(0), BigInt(1)]; + + while (r !== BigInt(0)) { + const quotient = old_r / r; + [old_r, r] = [r, old_r - quotient * r]; + [old_s, s] = [s, old_s - quotient * s]; + [old_t, t] = [t, old_t - quotient * t]; + } + + if (old_r > BigInt(1)) { + throw new Error("Modular inverse does not exist"); + } + + if (old_s < BigInt(0)) { + old_s += m; + } + + return old_s; +} + +export function generatePolynomial(degree: number, yIntercept: bigint, randomElement: () => bigint): bigint[] { + const res: bigint[] = []; + let i = 0; + if (yIntercept !== undefined) { + res.push(yIntercept); + i++; + } + for (; i <= degree; i++) { + res.push(randomElement()); + } + return res; +} + +export function getShare(polynomial: bigint[], index: bigint, modulus: bigint) { + let res = BigInt(0); + for (let i = 0; i < polynomial.length; i++) { + const term = polynomial[i] * index ** BigInt(i); + res = bigIntUmod(term, modulus) + res; + } + return bigIntUmod(res, modulus); +} + +export function dotProduct(arr1: bigint[], arr2: bigint[], modulus?: bigint) { + if (arr1.length !== arr2.length) { + throw new Error("arrays of different lengths"); + } + let sum = BigInt(0); + for (let i = 0; i < arr1.length; i++) { + // eslint-disable-next-line prettier/prettier + sum = sum + ( arr1[i] * arr2[i] ); + if (modulus) { + sum = bigIntUmod(sum, modulus); + } + } + return sum; +} + +export function getLagrangeCoeff(_allIndexes: number[] | bigint[], _myIndex: number | bigint, _target: number | bigint, modulus: bigint) { + const allIndexes: bigint[] = _allIndexes.map((i) => BigInt(i)); + const myIndex: bigint = BigInt(_myIndex); + const target: bigint = BigInt(_target); + let upper = BigInt(1); + let lower = BigInt(1); + + for (let j = 0; j < allIndexes.length; j += 1) { + if (myIndex !== allIndexes[j]) { + let tempUpper = target - allIndexes[j]; + tempUpper = bigIntUmod(tempUpper, modulus); + upper = upper * tempUpper; + upper = bigIntUmod(upper, modulus); + let tempLower = myIndex - allIndexes[j]; + tempLower = bigIntUmod(tempLower, modulus); + lower = bigIntUmod(lower * tempLower, modulus); + } + } + return bigIntUmod(upper * modularInverse(lower, modulus), modulus); +} + +export function lagrangeInterpolation(shares: bigint[], nodeIndex: bigint[], modulus: bigint) { + if (shares.length !== nodeIndex.length) { + return null; + } + let secret = BigInt(0); + for (let i = 0; i < shares.length; i += 1) { + let upper = BigInt(1); + let lower = BigInt(1); + for (let j = 0; j < shares.length; j += 1) { + if (i !== j) { + upper = bigIntUmod(upper * BigInt(-1) * nodeIndex[j], modulus); + const temp = bigIntUmod(nodeIndex[i] - nodeIndex[j], modulus); + lower = bigIntUmod(lower * temp, modulus); + } + } + let delta = bigIntUmod(upper * modularInverse(lower, modulus), modulus); + delta = bigIntUmod(delta * shares[i], modulus); + secret = bigIntUmod(secret + delta, modulus); + } + return secret; +} diff --git a/src/importProcess.ts b/src/importProcess.ts new file mode 100644 index 0000000..da4d2e2 --- /dev/null +++ b/src/importProcess.ts @@ -0,0 +1,313 @@ +import { Group } from "@noble/curves/abstract/curve"; +import { ed25519 } from "@noble/curves/ed25519"; +import { secp256k1 } from "@noble/curves/secp256k1"; + +import { bigIntPointToHexPoint, bigIntUmod, generatePolynomial, getLagrangeCoeff, getShare, hexToBigInt } from "./helpers"; +import { IData, IMockServer, RSSRound1Response, ServersInfo } from "./rss"; +import { decrypt, encrypt, EncryptedMessage, PointHex } from "./utils"; + +export const importClientRound1Internal = async & { x: bigint; y: bigint }>(params: { + importKey: bigint; + targetIndexes: number[]; + serversInfo: ServersInfo; + tempPubKey: PointHex; + keyType: "secp256k1" | "ed25519"; + constructPoint: (p: { x: string; y: string } | { x: bigint; y: bigint }) => T; + nbCurve: typeof secp256k1 | typeof ed25519; +}) => { + const { importKey, targetIndexes, serversInfo, tempPubKey, nbCurve, constructPoint } = params; + + const curveN = nbCurve.CURVE.n; + const curveG = constructPoint({ x: nbCurve.CURVE.Gx, y: nbCurve.CURVE.Gy }); + + const randomBytes = nbCurve.utils.randomPrivateKey; + const generatePrivate = () => bigIntUmod(hexToBigInt(Buffer.from(randomBytes()).toString("hex")), curveN); + + // front end also generates hierarchical secret sharing + // - calculate lagrange coeffs + const _finalLagrangeCoeffs = targetIndexes.map((target) => bigIntUmod(getLagrangeCoeff([0, 1], 0, target, curveN), curveN)); + const _masterPolys = []; + const _masterPolyCommits = []; + const _serverPolys = []; + const _serverPolyCommits = []; + const generateRandomScalar = () => generatePrivate(); + + for (let i = 0; i < _finalLagrangeCoeffs.length; i++) { + const _lc = _finalLagrangeCoeffs[i]; + const _m = generatePolynomial(1, bigIntUmod(_lc * importKey, curveN), generateRandomScalar); + _masterPolys.push(_m); + _masterPolyCommits.push( + _m.map((coeff) => { + const _gCoeff = curveG.multiply(coeff); + return _gCoeff; + }) + ); + const _s = generatePolynomial(serversInfo.threshold - 1, getShare(_m, 1n, curveN), generateRandomScalar); + _serverPolys.push(_s); + _serverPolyCommits.push(_s.map((coeff) => curveG.multiply(coeff))); + } + const _serverEncs = []; + const _userEncs = []; + for (let i = 0; i < _masterPolys.length; i++) { + _serverEncs.push([]); // for each target_index, create an array of server encryptions + } + // - generate N + 1 shares + for (let i = 0; i < targetIndexes.length; i++) { + const _masterPoly = _masterPolys[i]; + _userEncs.push( + await encrypt( + Buffer.from(`04${tempPubKey.x.padStart(64, "0")}${tempPubKey.y.padStart(64, "0")}`, "hex"), + Buffer.from(getShare(_masterPoly, 99n, curveN).toString(16).padStart(64, "0"), "hex") + ) + ); + + const _serverPoly = _serverPolys[i]; + const _serverEnc: EncryptedMessage[] = _serverEncs[i]; + for (let j = 0; j < serversInfo.pubkeys.length; j++) { + const _pub = serversInfo.pubkeys[j]; + _serverEnc.push( + await encrypt( + Buffer.from(`04${_pub.x.padStart(64, "0")}${_pub.y.padStart(64, "0")}`, "hex"), + Buffer.from( + getShare(_serverPoly, BigInt(j + 1), curveN) + .toString(16) + .padStart(64, "0"), + "hex" + ) + ) + ); + } + } + const _data: IData = []; + for (let i = 0; i < targetIndexes.length; i++) { + _data.push({ + master_poly_commits: _masterPolyCommits[i].map((pt) => { + return bigIntPointToHexPoint(pt); + }), + server_poly_commits: _serverPolyCommits[i].map((pt) => { + return bigIntPointToHexPoint(pt); + }), + target_encryptions: { + user_enc: _userEncs[i], + server_encs: _serverEncs[i], + }, + }); + } + return _data; +}; + +export const importClientRound2Internal = async & { x: bigint; y: bigint }>(opts: { + targetIndexes: number[]; + rssRound1Responses: RSSRound1Response[]; + serverThreshold: number; + serverEndpoints: string[] | IMockServer[]; + factorPubs: PointHex[]; + tempPrivKey: bigint; + dkgNewPub: PointHex; + tssPubKey: PointHex; + keyType: "secp256k1" | "ed25519"; + constructPoint: (p: { x: string; y: string } | { x: bigint; y: bigint }) => T; + nbCurve: typeof secp256k1 | typeof ed25519; +}) => { + const { + rssRound1Responses, + targetIndexes, + serverThreshold, + serverEndpoints, + factorPubs, + tempPrivKey, + dkgNewPub, + tssPubKey, + constructPoint, + nbCurve, + } = opts; + + // const nbCurve = secp256k1; + const curveN = nbCurve.CURVE.n; + const curveG = constructPoint({ x: nbCurve.CURVE.Gx, y: nbCurve.CURVE.Gy }); + + type Point = T; + + // sum up all master poly commits and sum up all server poly commits + const sums = targetIndexes.map((_, i) => { + for (let j = 0; j < rssRound1Responses.length; j++) { + const rssRound1ResponseData = rssRound1Responses[j].data[i]; + const { master_poly_commits: masterPolyCommits, server_poly_commits: serverPolyCommits } = rssRound1ResponseData; + if (masterPolyCommits.length !== 2) throw new Error("incorrect number of coeffs for master poly commits"); + if (serverPolyCommits.length !== serverThreshold) throw new Error("incorrect number of coeffs for server poly commits"); + } + + let sumMasterPolyCommits: Point[] = []; + let sumServerPolyCommits: Point[] = []; + + for (let j = 0; j < rssRound1Responses.length; j++) { + const rssRound1ResponseData = rssRound1Responses[j].data[i]; + const { master_poly_commits: masterPolyCommits, server_poly_commits: serverPolyCommits } = rssRound1ResponseData; + if (sumMasterPolyCommits.length === 0 && sumServerPolyCommits.length === 0) { + sumMasterPolyCommits = masterPolyCommits.map((p) => constructPoint(p)); + sumServerPolyCommits = serverPolyCommits.map((p) => constructPoint(p)); + continue; + } + sumMasterPolyCommits = sumMasterPolyCommits.map((summedCommit, k) => { + return constructPoint(masterPolyCommits[k]).add(summedCommit); + }); + sumServerPolyCommits = sumServerPolyCommits.map((summedCommit, k) => { + return constructPoint(serverPolyCommits[k]).add(summedCommit); + }); + } + + return { + mc: sumMasterPolyCommits, + sc: sumServerPolyCommits, + }; + }); + + // front end checks + targetIndexes.map((target, i) => { + const { mc, sc } = sums[i]; + // check master poly commits are consistent with tssPubKey + const temp1 = constructPoint(dkgNewPub).multiply(getLagrangeCoeff([1, target], 1, 0, curveN)); + const temp2 = mc[0].multiply(getLagrangeCoeff([1, target], target, 0, curveN)); + const _tssPubKey = temp1.add(temp2); + if (!_tssPubKey.equals(constructPoint(tssPubKey))) throw new Error("master poly commits inconsistent with tssPubKey"); + + // check server poly commits are consistent with master poly commits + if (!mc[0].add(mc[1]).equals(sc[0])) throw new Error("server poly commits inconsistent with master poly commits"); + return null; + }); + + // front end checks if decrypted user shares are consistent with poly commits + const privKeyBuffer = Buffer.from(tempPrivKey.toString(16).padStart(64, "0"), "hex"); + const userShares = []; + for (let i = 0; i < targetIndexes.length; i++) { + const userEncs = rssRound1Responses.map((r) => r.data[i].target_encryptions.user_enc); + const userDecs = await Promise.all(userEncs.map((encMsg) => decrypt(privKeyBuffer, encMsg))); + const userShare = userDecs + .map((userDec) => BigInt(`0x${Buffer.from(userDec).toString("hex")}`)) + .reduce((acc, d) => bigIntUmod(acc + d, curveN), BigInt(0)); + const { mc } = sums[i]; + const gU = curveG.multiply(userShare); + const _gU = mc[0].add(mc[1].multiply(99n)); // master poly evaluated at x = 99 + if (!gU.equals(_gU)) throw new Error("decrypted user shares inconsistent with poly commits"); + userShares.push(userShare); + } + + const userFactorEncs = await Promise.all( + userShares.map((userShare, i) => { + const pub = factorPubs[i]; + return encrypt( + Buffer.from(`04${pub.x.padStart(64, "0")}${pub.y.padStart(64, "0")}`, "hex"), + Buffer.from(userShare.toString(16).padStart(64, "0"), "hex") + ); + }) + ); + + // rearrange received serverEncs before sending them to new servers + const serverEncs = targetIndexes.map((_, i) => { + const serverEncsReceived = rssRound1Responses.map((r) => r.data[i].target_encryptions.server_encs); + // flip the matrix + const serverEncsToSend = []; + for (let j = 0; j < serverEndpoints.length; j++) { + const serverEnc = []; + + // Import only has T servers and the user, so it's T + 1 + for (let k = 0; k < serverThreshold + 1; k++) { + serverEnc.push(serverEncsReceived[k][j]); + } + serverEncsToSend.push(serverEnc); + } + + return serverEncsToSend; + }); + + return { + sums: sums.map((s) => ({ + mc: s.mc.map((p) => bigIntPointToHexPoint(p)), + sc: s.sc.map((p) => bigIntPointToHexPoint(p)), + })), + userFactorEncs, + serverEncs, + }; +}; + +export const importClientRound1 = async (params: { + importKey: bigint; + targetIndexes: number[]; + serversInfo: ServersInfo; + tempPubKey: PointHex; + keyType: "secp256k1" | "ed25519"; +}) => { + if (params.keyType === "secp256k1") { + const nbCurve = secp256k1; + const constructPoint = (p: { x: string; y: string } | { x: bigint; y: bigint }) => { + if (typeof p.x === "bigint" && typeof p.y === "bigint") { + return secp256k1.ProjectivePoint.fromAffine({ x: p.x, y: p.y }); + } else if (typeof p.x === "string" && typeof p.y === "string") { + return secp256k1.ProjectivePoint.fromAffine({ x: hexToBigInt(p.x), y: hexToBigInt(p.y) }); + } + }; + return importClientRound1Internal({ + ...params, + constructPoint, + nbCurve, + }); + } else if (params.keyType === "ed25519") { + const nbCurve = ed25519; + const constructPoint = (p: { x: string; y: string } | { x: bigint; y: bigint }) => { + if (typeof p.x === "bigint" && typeof p.y === "bigint") { + return nbCurve.ExtendedPoint.fromAffine({ x: p.x, y: p.y }); + } else if (typeof p.x === "string" && typeof p.y === "string") { + return nbCurve.ExtendedPoint.fromAffine({ x: hexToBigInt(p.x), y: hexToBigInt(p.y) }); + } + }; + return importClientRound1Internal({ + ...params, + constructPoint, + nbCurve, + }); + } + throw new Error(`unknown key type: ${params.keyType}`); +}; + +export const importClientRound2 = async (opts: { + targetIndexes: number[]; + rssRound1Responses: RSSRound1Response[]; + serverThreshold: number; + serverEndpoints: string[] | IMockServer[]; + factorPubs: PointHex[]; + tempPrivKey: bigint; + dkgNewPub: PointHex; + tssPubKey: PointHex; + keyType: "secp256k1" | "ed25519"; +}) => { + if (opts.keyType === "secp256k1") { + const nbCurve = secp256k1; + const constructPoint = (p: { x: string; y: string } | { x: bigint; y: bigint }) => { + if (typeof p.x === "bigint" && typeof p.y === "bigint") { + return secp256k1.ProjectivePoint.fromAffine({ x: p.x, y: p.y }); + } else if (typeof p.x === "string" && typeof p.y === "string") { + return secp256k1.ProjectivePoint.fromAffine({ x: hexToBigInt(p.x), y: hexToBigInt(p.y) }); + } + }; + return importClientRound2Internal({ + ...opts, + constructPoint, + nbCurve, + }); + } else if (opts.keyType === "ed25519") { + const nbCurve = ed25519; + const constructPoint = (p: { x: string; y: string } | { x: bigint; y: bigint }) => { + if (typeof p.x === "bigint" && typeof p.y === "bigint") { + return nbCurve.ExtendedPoint.fromAffine({ x: p.x, y: p.y }); + } else if (typeof p.x === "string" && typeof p.y === "string") { + return nbCurve.ExtendedPoint.fromAffine({ x: hexToBigInt(p.x), y: hexToBigInt(p.y) }); + } + }; + return importClientRound2Internal({ + ...opts, + constructPoint, + nbCurve, + }); + } + throw new Error("Invalid keyType, only secp256k1 or ed25519 is supported"); +}; diff --git a/src/refreshProcess.ts b/src/refreshProcess.ts new file mode 100644 index 0000000..309f509 --- /dev/null +++ b/src/refreshProcess.ts @@ -0,0 +1,308 @@ +import { Group } from "@noble/curves/abstract/curve"; +import { AffinePoint } from "@noble/curves/abstract/weierstrass"; +import { ed25519 } from "@noble/curves/ed25519"; +import { secp256k1 } from "@noble/curves/secp256k1"; + +import { bigIntPointToHexPoint, bigIntUmod, generatePolynomial, getLagrangeCoeff, getShare, hexToBigInt } from "./helpers"; +import { IData, IMockServer, RSSRound1Response, ServersInfo } from "./rss"; +import { decrypt, encrypt, EncryptedMessage, PointHex } from "./utils"; + +export const toAffineHex = (affine: AffinePoint): AffinePoint => { + return { + x: affine.x.toString(16).padStart(64, "0"), + y: affine.y.toString(16).padStart(64, "0"), + }; +}; + +export const refreshClientRound1Internal = async & { x: bigint; y: bigint }>(params: { + inputIndex: number; + targetIndexes: number[]; + inputShare: bigint; + serversInfo: ServersInfo; + tempPubKey: Uint8Array; // uncompressed pubke + keyType: "secp256k1" | "ed25519"; + constructPoint: (p: { x: string; y: string } | { x: bigint; y: bigint }) => T; + nbCurve: typeof secp256k1 | typeof ed25519; +}) => { + const { inputIndex, targetIndexes, inputShare, serversInfo, tempPubKey, nbCurve, constructPoint } = params; + + // front end also generates hierarchical secret sharing + // - calculate lagrange coeffs + const curveN = nbCurve.CURVE.n; + const curveG = constructPoint({ x: nbCurve.CURVE.Gx, y: nbCurve.CURVE.Gy }); + + const randomBytes = nbCurve.utils.randomPrivateKey; + const generatePrivate = () => bigIntUmod(hexToBigInt(Buffer.from(randomBytes()).toString("hex")), curveN); + + const _L = getLagrangeCoeff([1, inputIndex], inputIndex, 0, curveN); + const _finalLagrangeCoeffs = targetIndexes.map((target) => _L * bigIntUmod(getLagrangeCoeff([0, 1], 0, target, curveN), curveN)); + const _masterPolys = []; + const _masterPolyCommits = []; + const _serverPolys = []; + const _serverPolyCommits = []; + const generateRandomScalar = () => generatePrivate(); + + for (let i = 0; i < _finalLagrangeCoeffs.length; i++) { + const _lc = _finalLagrangeCoeffs[i]; + const _m = generatePolynomial(1, bigIntUmod(_lc * inputShare, curveN), generateRandomScalar); + _masterPolys.push(_m); + _masterPolyCommits.push( + _m.map((coeff) => { + const _gCoeff = curveG.multiply(coeff); + return _gCoeff; + }) + ); + const _s = generatePolynomial(serversInfo.threshold - 1, getShare(_m, 1n, curveN), generateRandomScalar); + _serverPolys.push(_s); + _serverPolyCommits.push(_s.map((coeff) => curveG.multiply(coeff))); + } + const _serverEncs = []; + const _userEncs = []; + for (let i = 0; i < _masterPolys.length; i++) { + _serverEncs.push([]); // for each target_index, create an array of server encryptions + } + // - generate N + 1 shares + for (let i = 0; i < targetIndexes.length; i++) { + const _masterPoly = _masterPolys[i]; + _userEncs.push(await encrypt(Buffer.from(tempPubKey), Buffer.from(getShare(_masterPoly, 99n, curveN).toString(16).padStart(64, "0"), "hex"))); + + const _serverPoly = _serverPolys[i]; + const _serverEnc: EncryptedMessage[] = _serverEncs[i]; + for (let j = 0; j < serversInfo.pubkeys.length; j++) { + const _pub = serversInfo.pubkeys[j]; + _serverEnc.push( + await encrypt( + Buffer.from(`04${_pub.x.padStart(64, "0")}${_pub.y.padStart(64, "0")}`, "hex"), + Buffer.from( + getShare(_serverPoly, BigInt(j + 1), curveN) + .toString(16) + .padStart(64, "0"), + "hex" + ) + ) + ); + } + } + const _data: IData = []; + for (let i = 0; i < targetIndexes.length; i++) { + _data.push({ + master_poly_commits: _masterPolyCommits[i].map((pt) => { + return { x: pt.x.toString(16).padStart(64, "0"), y: pt.y.toString(16).padStart(64, "0") }; + }), + server_poly_commits: _serverPolyCommits[i].map((pt) => { + return { x: pt.x.toString(16).padStart(64, "0"), y: pt.y.toString(16).padStart(64, "0") }; + }), + target_encryptions: { + user_enc: _userEncs[i], + server_encs: _serverEncs[i], + }, + }); + } + return _data; +}; + +export const refreshClientRound2Internal = async & { x: bigint; y: bigint }>(opts: { + targetIndexes: number[]; + rssRound1Responses: RSSRound1Response[]; + serverThreshold: number; + serverEndpoints: string[] | IMockServer[]; + factorPubs: PointHex[]; + tempPrivKey: bigint; + dkgNewPub: PointHex; + tssPubKey: PointHex; + keyType: "secp256k1" | "ed25519"; + constructPoint: (p: { x: string; y: string } | { x: bigint; y: bigint }) => T; + nbCurve: typeof secp256k1 | typeof ed25519; +}) => { + const { + rssRound1Responses, + targetIndexes, + serverThreshold, + serverEndpoints, + factorPubs, + tempPrivKey, + dkgNewPub, + tssPubKey, + nbCurve, + constructPoint, + } = opts; + + const curveN = nbCurve.CURVE.n; + const curveG = constructPoint({ x: nbCurve.CURVE.Gx, y: nbCurve.CURVE.Gy }); + + type Point = T; + + // sum up all master poly commits and sum up all server poly commits + const sums = targetIndexes.map((_, i) => { + for (let j = 0; j < rssRound1Responses.length; j++) { + const rssRound1ResponseData = rssRound1Responses[j].data[i]; + const { master_poly_commits: masterPolyCommits, server_poly_commits: serverPolyCommits } = rssRound1ResponseData; + if (masterPolyCommits.length !== 2) throw new Error("incorrect number of coeffs for master poly commits"); + if (serverPolyCommits.length !== serverThreshold) throw new Error("incorrect number of coeffs for server poly commits"); + } + let sumMasterPolyCommits: Point[] = []; + let sumServerPolyCommits: Point[] = []; + + for (let j = 0; j < rssRound1Responses.length; j++) { + const rssRound1ResponseData = rssRound1Responses[j].data[i]; + const { master_poly_commits: masterPolyCommits, server_poly_commits: serverPolyCommits } = rssRound1ResponseData; + if (sumMasterPolyCommits.length === 0 && sumServerPolyCommits.length === 0) { + sumMasterPolyCommits = masterPolyCommits.map((p) => constructPoint(p)); + sumServerPolyCommits = serverPolyCommits.map((p) => constructPoint(p)); + continue; + } + sumMasterPolyCommits = sumMasterPolyCommits.map((summedCommit, k) => { + return constructPoint(masterPolyCommits[k]).add(summedCommit); + }); + sumServerPolyCommits = sumServerPolyCommits.map((summedCommit, k) => { + return constructPoint(serverPolyCommits[k]).add(summedCommit); + }); + } + + return { + mc: sumMasterPolyCommits, + sc: sumServerPolyCommits, + }; + }); + + // front end checks + targetIndexes.map((target, i) => { + const { mc, sc } = sums[i]; + // check master poly commits are consistent with tssPubKey + const temp1 = constructPoint(dkgNewPub).multiply(getLagrangeCoeff([1, target], 1, 0, curveN)); + const temp2 = mc[0].multiply(getLagrangeCoeff([1, target], target, 0, curveN)); + const _tssPubKey = temp1.add(temp2); + if (!_tssPubKey.equals(constructPoint(tssPubKey))) throw new Error("master poly commits inconsistent with tssPubKey"); + + // check server poly commits are consistent with master poly commits + if (!mc[0].add(mc[1]).equals(sc[0])) throw new Error("server poly commits inconsistent with master poly commits"); + return null; + }); + + // front end checks if decrypted user shares are consistent with poly commits + const privKeyBuffer = Buffer.from(tempPrivKey.toString(16).padStart(64, "0"), "hex"); + const userShares = []; + for (let i = 0; i < targetIndexes.length; i++) { + const userEncs = rssRound1Responses.map((r) => r.data[i].target_encryptions.user_enc); + const userDecs = await Promise.all(userEncs.map((encMsg) => decrypt(privKeyBuffer, encMsg))); + const userShare = userDecs.map((userDec) => hexToBigInt(userDec.toString("hex"))).reduce((acc, d) => bigIntUmod(acc + d, curveN), BigInt(0)); + const { mc } = sums[i]; + const gU = curveG.multiply(userShare); + const _gU = mc[0].add(mc[1].multiply(BigInt(99))); // master poly evaluated at x = 99 + if (!gU.equals(_gU)) throw new Error("decrypted user shares inconsistent with poly commits"); + userShares.push(userShare); + } + + const userFactorEncs = await Promise.all( + userShares.map((userShare, i) => { + const pub = factorPubs[i]; + return encrypt( + Buffer.from(`04${pub.x.padStart(64, "0")}${pub.y.padStart(64, "0")}`, "hex"), + Buffer.from(userShare.toString(16).padStart(64, "0"), "hex") + ); + }) + ); + + // rearrange received serverEncs before sending them to new servers + const serverEncs = targetIndexes.map((_, i) => { + const serverEncsReceived = rssRound1Responses.map((r) => r.data[i].target_encryptions.server_encs); + // flip the matrix + const serverEncsToSend = []; + for (let j = 0; j < serverEndpoints.length; j++) { + const serverEnc = []; + for (let k = 0; k < serverThreshold * 2 + 1; k++) { + serverEnc.push(serverEncsReceived[k][j]); + } + serverEncsToSend.push(serverEnc); + } + + return serverEncsToSend; + }); + + return { + sums: sums.map((s) => ({ + mc: s.mc.map((p) => bigIntPointToHexPoint(p)), + sc: s.sc.map((p) => bigIntPointToHexPoint(p)), + })), + serverEncs, + userFactorEncs, + }; +}; + +export const refreshClientRound1 = async (params: { + inputIndex: number; + targetIndexes: number[]; + inputShare: bigint; + serversInfo: ServersInfo; + tempPubKey: Uint8Array; // uncompressed pubke + keyType: "secp256k1" | "ed25519"; +}) => { + if (params.keyType === "secp256k1") { + const constructPoint = (p: { x: string; y: string } | { x: bigint; y: bigint }) => { + if (typeof p.x === "bigint" && typeof p.y === "bigint") { + return secp256k1.ProjectivePoint.fromAffine({ x: p.x, y: p.y }); + } else if (typeof p.x === "string" && typeof p.y === "string") { + return secp256k1.ProjectivePoint.fromAffine({ x: hexToBigInt(p.x), y: hexToBigInt(p.y) }); + } + throw new Error("Invalid point"); + }; + const nbCurve = secp256k1; + return refreshClientRound1Internal({ + ...params, + constructPoint, + nbCurve, + }); + } else if (params.keyType === "ed25519") { + const constructPoint = (p: { x: string; y: string } | { x: bigint; y: bigint }) => { + if (typeof p.x === "bigint" && typeof p.y === "bigint") { + return ed25519.ExtendedPoint.fromAffine({ x: p.x, y: p.y }); + } else if (typeof p.x === "string" && typeof p.y === "string") { + return ed25519.ExtendedPoint.fromAffine({ x: hexToBigInt(p.x), y: hexToBigInt(p.y) }); + } + throw new Error("Invalid point"); + }; + const nbCurve = ed25519; + return refreshClientRound1Internal({ + ...params, + constructPoint, + nbCurve, + }); + } + throw new Error("Invalid key type"); +}; + +export const refreshClientRound2 = async (opts: { + targetIndexes: number[]; + rssRound1Responses: RSSRound1Response[]; + serverThreshold: number; + serverEndpoints: string[] | IMockServer[]; + factorPubs: PointHex[]; + tempPrivKey: bigint; + dkgNewPub: PointHex; + tssPubKey: PointHex; + keyType: "secp256k1" | "ed25519"; +}) => { + if (opts.keyType === "secp256k1") { + const constructPoint = (p: { x: string; y: string } | { x: bigint; y: bigint }) => { + if (typeof p.x === "bigint" && typeof p.y === "bigint") { + return secp256k1.ProjectivePoint.fromAffine({ x: p.x, y: p.y }); + } else if (typeof p.x === "string" && typeof p.y === "string") { + return secp256k1.ProjectivePoint.fromAffine({ x: hexToBigInt(p.x), y: hexToBigInt(p.y) }); + } + throw new Error("Invalid point"); + }; + const nbCurve = secp256k1; + return refreshClientRound2Internal({ ...opts, constructPoint, nbCurve }); + } else if (opts.keyType === "ed25519") { + const constructPoint = (p: { x: string; y: string } | { x: bigint; y: bigint }) => { + if (typeof p.x === "bigint" && typeof p.y === "bigint") { + return ed25519.ExtendedPoint.fromAffine({ x: p.x, y: p.y }); + } else if (typeof p.x === "string" && typeof p.y === "string") { + return ed25519.ExtendedPoint.fromAffine({ x: hexToBigInt(p.x), y: hexToBigInt(p.y) }); + } + throw new Error("Invalid point"); + }; + const nbCurve = ed25519; + return refreshClientRound2Internal({ ...opts, constructPoint, nbCurve }); + } +}; diff --git a/src/rss.ts b/src/rss.ts index e523255..4cf4268 100644 --- a/src/rss.ts +++ b/src/rss.ts @@ -3,19 +3,10 @@ import BN from "bn.js"; import { curve, ec as EC } from "elliptic"; import log from "loglevel"; -import { - decrypt, - dotProduct, - ecCurveSecp256k1, - ecPoint, - encrypt, - EncryptedMessage, - generatePolynomial, - getLagrangeCoeff, - getShare, - hexPoint, - PointHex, -} from "./utils"; +import { hexToBigInt } from "./helpers"; +import { importClientRound1, importClientRound2 } from "./importProcess"; +import { refreshClientRound1, refreshClientRound2 } from "./refreshProcess"; +import { decrypt, dotProduct, ecCurveSecp256k1, ecPoint, EncryptedMessage, getLagrangeCoeff, hexPoint, PointHex } from "./utils"; export type KeyType = "secp256k1" | "ed25519"; @@ -201,66 +192,13 @@ export class RSSClient { }); }); - // front end also generates hierarchical secret sharing - // - calculate lagrange coeffs - const _finalLagrangeCoeffs = targetIndexes.map((target) => getLagrangeCoeff([0, 1], 0, target, this.ecCurve.n).umod(this.ecCurve.n)); - const _masterPolys = []; - const _masterPolyCommits = []; - const _serverPolys = []; - const _serverPolyCommits = []; - const generateRandomScalar = () => this.ecCurve.genKeyPair().getPrivate(); - for (let i = 0; i < _finalLagrangeCoeffs.length; i++) { - const _lc = _finalLagrangeCoeffs[i]; - const _m = generatePolynomial(1, _lc.mul(importKey).umod(this.ecCurve.n), generateRandomScalar); - _masterPolys.push(_m); - _masterPolyCommits.push( - _m.map((coeff) => { - const _gCoeff = this.ecCurve.g.mul(coeff); - return hexPoint(_gCoeff); - }) - ); - const _s = generatePolynomial(serversInfo.threshold - 1, getShare(_m, 1, this.ecCurve.n), generateRandomScalar); - _serverPolys.push(_s); - _serverPolyCommits.push(_s.map((coeff) => hexPoint(this.ecCurve.g.mul(coeff)))); - } - const _serverEncs = []; - const _userEncs = []; - for (let i = 0; i < _masterPolys.length; i++) { - _serverEncs.push([]); // for each target_index, create an array of server encryptions - } - // - generate N + 1 shares - for (let i = 0; i < targetIndexes.length; i++) { - const _masterPoly = _masterPolys[i]; - _userEncs.push( - await encrypt( - Buffer.from(`04${hexPoint(this.tempPubKey).x.padStart(64, "0")}${hexPoint(this.tempPubKey).y.padStart(64, "0")}`, "hex"), - Buffer.from(getShare(_masterPoly, 99, this.ecCurve.n).toString(16, 64), "hex") - ) - ); - - const _serverPoly = _serverPolys[i]; - const _serverEnc: EncryptedMessage[] = _serverEncs[i]; - for (let j = 0; j < serversInfo.pubkeys.length; j++) { - const _pub = serversInfo.pubkeys[j]; - _serverEnc.push( - await encrypt( - Buffer.from(`04${_pub.x.padStart(64, "0")}${_pub.y.padStart(64, "0")}`, "hex"), - Buffer.from(getShare(_serverPoly, j + 1, this.ecCurve.n).toString(16, 64), "hex") - ) - ); - } - } - const _data: IData = []; - for (let i = 0; i < targetIndexes.length; i++) { - _data.push({ - master_poly_commits: _masterPolyCommits[i], - server_poly_commits: _serverPolyCommits[i], - target_encryptions: { - user_enc: _userEncs[i], - server_encs: _serverEncs[i], - }, - }); - } + const _data = await importClientRound1({ + importKey: hexToBigInt(importKey.toString("hex")), + targetIndexes, + serversInfo, + tempPubKey: hexPoint(this.tempPubKey), + keyType: this.keyType, + }); // add front end generated hierarchical sharing to the list rssRound1Proms.push( @@ -275,91 +213,16 @@ export class RSSClient { // await responses const rssRound1Responses = (await Promise.all(rssRound1Proms)) as RSSRound1Response[]; - // sum up all master poly commits and sum up all server poly commits - const sums = targetIndexes.map((_, i) => { - for (let j = 0; j < rssRound1Responses.length; j++) { - const rssRound1ResponseData = rssRound1Responses[j].data[i]; - const { master_poly_commits: masterPolyCommits, server_poly_commits: serverPolyCommits } = rssRound1ResponseData; - if (masterPolyCommits.length !== 2) throw new Error("incorrect number of coeffs for master poly commits"); - if (serverPolyCommits.length !== this.serverThreshold) throw new Error("incorrect number of coeffs for server poly commits"); - } - - let sumMasterPolyCommits: curve.base.BasePoint[] = []; - let sumServerPolyCommits: curve.base.BasePoint[] = []; - - for (let j = 0; j < rssRound1Responses.length; j++) { - const rssRound1ResponseData = rssRound1Responses[j].data[i]; - const { master_poly_commits: masterPolyCommits, server_poly_commits: serverPolyCommits } = rssRound1ResponseData; - if (sumMasterPolyCommits.length === 0 && sumServerPolyCommits.length === 0) { - sumMasterPolyCommits = masterPolyCommits.map((p) => ecPoint(this.ecCurve, p)); - sumServerPolyCommits = serverPolyCommits.map((p) => ecPoint(this.ecCurve, p)); - continue; - } - sumMasterPolyCommits = sumMasterPolyCommits.map((summedCommit, k) => { - return ecPoint(this.ecCurve, masterPolyCommits[k]).add(summedCommit); - }); - sumServerPolyCommits = sumServerPolyCommits.map((summedCommit, k) => { - return ecPoint(this.ecCurve, serverPolyCommits[k]).add(summedCommit); - }); - } - - return { - mc: sumMasterPolyCommits, - sc: sumServerPolyCommits, - }; - }); - - // front end checks - targetIndexes.map((target, i) => { - const { mc, sc } = sums[i]; - // check master poly commits are consistent with tssPubKey - const temp1 = ecPoint(this.ecCurve, dkgNewPub).mul(getLagrangeCoeff([1, target], 1, 0, this.ecCurve.n)); - const temp2 = mc[0].mul(getLagrangeCoeff([1, target], target, 0, this.ecCurve.n)); - const _tssPubKey = temp1.add(temp2); - if (!_tssPubKey.eq(this.tssPubKey)) throw new Error("master poly commits inconsistent with tssPubKey"); - - // check server poly commits are consistent with master poly commits - if (!mc[0].add(mc[1]).eq(sc[0])) throw new Error("server poly commits inconsistent with master poly commits"); - return null; - }); - - // front end checks if decrypted user shares are consistent with poly commits - const privKeyBuffer = Buffer.from(this.tempPrivKey.toString(16, 64), "hex"); - const userShares = []; - for (let i = 0; i < targetIndexes.length; i++) { - const userEncs = rssRound1Responses.map((r) => r.data[i].target_encryptions.user_enc); - const userDecs = await Promise.all(userEncs.map((encMsg) => decrypt(privKeyBuffer, encMsg))); - const userShare = userDecs.map((userDec) => new BN(userDec)).reduce((acc, d) => acc.add(d).umod(this.ecCurve.n), new BN(0)); - const { mc } = sums[i]; - const gU = this.ecCurve.g.mul(userShare); - const _gU = mc[0].add(mc[1].mul(new BN(99))); // master poly evaluated at x = 99 - if (!gU.eq(_gU)) throw new Error("decrypted user shares inconsistent with poly commits"); - userShares.push(userShare); - } - - const userFactorEncs = await Promise.all( - userShares.map((userShare, i) => { - const pub = factorPubs[i]; - return encrypt(Buffer.from(`04${pub.x.padStart(64, "0")}${pub.y.padStart(64, "0")}`, "hex"), Buffer.from(userShare.toString(16, 64), "hex")); - }) - ); - - // rearrange received serverEncs before sending them to new servers - const serverEncs = targetIndexes.map((_, i) => { - const serverEncsReceived = rssRound1Responses.map((r) => r.data[i].target_encryptions.server_encs); - // flip the matrix - const serverEncsToSend = []; - for (let j = 0; j < this.serverEndpoints.length; j++) { - const serverEnc = []; - - // Import only has T servers and the user, so it's T + 1 - for (let k = 0; k < this.serverThreshold + 1; k++) { - serverEnc.push(serverEncsReceived[k][j]); - } - serverEncsToSend.push(serverEnc); - } - - return serverEncsToSend; + const { sums, serverEncs, userFactorEncs } = await importClientRound2({ + targetIndexes, + rssRound1Responses, + serverThreshold: this.serverThreshold, + serverEndpoints: this.serverEndpoints, + factorPubs, + tempPrivKey: hexToBigInt(this.tempPrivKey.toString("hex")), + dkgNewPub, + tssPubKey: hexPoint(this.tssPubKey), + keyType: this.keyType, }); // servers sum up their shares and encrypt it for factorPubs @@ -371,8 +234,8 @@ export class RSSClient { targetIndexes.map((_, i) => { const { mc, sc } = sums[i]; const round2RequestData = { - master_commits: mc.map(hexPoint), - server_commits: sc.map(hexPoint), + master_commits: mc, + server_commits: sc, server_encs: serverEncs[i][ind - 1], factor_pubkeys: [factorPubs[i]], // TODO: must we do it like this? }; @@ -454,67 +317,14 @@ export class RSSClient { }) ); - // front end also generates hierarchical secret sharing - // - calculate lagrange coeffs - const _L = getLagrangeCoeff([1, inputIndex], inputIndex, 0, this.ecCurve.n); - const _finalLagrangeCoeffs = targetIndexes.map((target) => _L.mul(getLagrangeCoeff([0, 1], 0, target, this.ecCurve.n)).umod(this.ecCurve.n)); - const _masterPolys = []; - const _masterPolyCommits = []; - const _serverPolys = []; - const _serverPolyCommits = []; - const generateRandomScalar = () => this.ecCurve.genKeyPair().getPrivate(); - for (let i = 0; i < _finalLagrangeCoeffs.length; i++) { - const _lc = _finalLagrangeCoeffs[i]; - const _m = generatePolynomial(1, _lc.mul(inputShare).umod(this.ecCurve.n), generateRandomScalar); - _masterPolys.push(_m); - _masterPolyCommits.push( - _m.map((coeff) => { - const _gCoeff = this.ecCurve.g.mul(coeff); - return hexPoint(_gCoeff); - }) - ); - const _s = generatePolynomial(serversInfo.threshold - 1, getShare(_m, 1, this.ecCurve.n), generateRandomScalar); - _serverPolys.push(_s); - _serverPolyCommits.push(_s.map((coeff) => hexPoint(this.ecCurve.g.mul(coeff)))); - } - const _serverEncs = []; - const _userEncs = []; - for (let i = 0; i < _masterPolys.length; i++) { - _serverEncs.push([]); // for each target_index, create an array of server encryptions - } - // - generate N + 1 shares - for (let i = 0; i < targetIndexes.length; i++) { - const _masterPoly = _masterPolys[i]; - _userEncs.push( - await encrypt( - Buffer.from(`04${hexPoint(this.tempPubKey).x.padStart(64, "0")}${hexPoint(this.tempPubKey).y.padStart(64, "0")}`, "hex"), - Buffer.from(getShare(_masterPoly, 99, this.ecCurve.n).toString(16, 64), "hex") - ) - ); - - const _serverPoly = _serverPolys[i]; - const _serverEnc: EncryptedMessage[] = _serverEncs[i]; - for (let j = 0; j < serversInfo.pubkeys.length; j++) { - const _pub = serversInfo.pubkeys[j]; - _serverEnc.push( - await encrypt( - Buffer.from(`04${_pub.x.padStart(64, "0")}${_pub.y.padStart(64, "0")}`, "hex"), - Buffer.from(getShare(_serverPoly, j + 1, this.ecCurve.n).toString(16, 64), "hex") - ) - ); - } - } - const _data: IData = []; - for (let i = 0; i < targetIndexes.length; i++) { - _data.push({ - master_poly_commits: _masterPolyCommits[i], - server_poly_commits: _serverPolyCommits[i], - target_encryptions: { - user_enc: _userEncs[i], - server_encs: _serverEncs[i], - }, - }); - } + const _data = await refreshClientRound1({ + inputIndex, + targetIndexes, + inputShare: hexToBigInt(inputShare.toString("hex")), + serversInfo, + tempPubKey: Buffer.from(this.tempPubKey.encode("hex", false), "hex"), + keyType: this.keyType, + }); // add front end generated hierarchical sharing to the list rssRound1Proms.push( @@ -529,89 +339,16 @@ export class RSSClient { // await responses const rssRound1Responses = (await Promise.all(rssRound1Proms)) as RSSRound1Response[]; - // sum up all master poly commits and sum up all server poly commits - const sums = targetIndexes.map((_, i) => { - for (let j = 0; j < rssRound1Responses.length; j++) { - const rssRound1ResponseData = rssRound1Responses[j].data[i]; - const { master_poly_commits: masterPolyCommits, server_poly_commits: serverPolyCommits } = rssRound1ResponseData; - if (masterPolyCommits.length !== 2) throw new Error("incorrect number of coeffs for master poly commits"); - if (serverPolyCommits.length !== this.serverThreshold) throw new Error("incorrect number of coeffs for server poly commits"); - } - - let sumMasterPolyCommits: curve.base.BasePoint[] = []; - let sumServerPolyCommits: curve.base.BasePoint[] = []; - - for (let j = 0; j < rssRound1Responses.length; j++) { - const rssRound1ResponseData = rssRound1Responses[j].data[i]; - const { master_poly_commits: masterPolyCommits, server_poly_commits: serverPolyCommits } = rssRound1ResponseData; - if (sumMasterPolyCommits.length === 0 && sumServerPolyCommits.length === 0) { - sumMasterPolyCommits = masterPolyCommits.map((p) => ecPoint(this.ecCurve, p)); - sumServerPolyCommits = serverPolyCommits.map((p) => ecPoint(this.ecCurve, p)); - continue; - } - sumMasterPolyCommits = sumMasterPolyCommits.map((summedCommit, k) => { - return ecPoint(this.ecCurve, masterPolyCommits[k]).add(summedCommit); - }); - sumServerPolyCommits = sumServerPolyCommits.map((summedCommit, k) => { - return ecPoint(this.ecCurve, serverPolyCommits[k]).add(summedCommit); - }); - } - - return { - mc: sumMasterPolyCommits, - sc: sumServerPolyCommits, - }; - }); - - // front end checks - targetIndexes.map((target, i) => { - const { mc, sc } = sums[i]; - // check master poly commits are consistent with tssPubKey - const temp1 = ecPoint(this.ecCurve, dkgNewPub).mul(getLagrangeCoeff([1, target], 1, 0, this.ecCurve.n)); - const temp2 = mc[0].mul(getLagrangeCoeff([1, target], target, 0, this.ecCurve.n)); - const _tssPubKey = temp1.add(temp2); - if (!_tssPubKey.eq(this.tssPubKey)) throw new Error("master poly commits inconsistent with tssPubKey"); - - // check server poly commits are consistent with master poly commits - if (!mc[0].add(mc[1]).eq(sc[0])) throw new Error("server poly commits inconsistent with master poly commits"); - return null; - }); - - // front end checks if decrypted user shares are consistent with poly commits - const privKeyBuffer = Buffer.from(this.tempPrivKey.toString(16, 64), "hex"); - const userShares = []; - for (let i = 0; i < targetIndexes.length; i++) { - const userEncs = rssRound1Responses.map((r) => r.data[i].target_encryptions.user_enc); - const userDecs = await Promise.all(userEncs.map((encMsg) => decrypt(privKeyBuffer, encMsg))); - const userShare = userDecs.map((userDec) => new BN(userDec)).reduce((acc, d) => acc.add(d).umod(this.ecCurve.n), new BN(0)); - const { mc } = sums[i]; - const gU = this.ecCurve.g.mul(userShare); - const _gU = mc[0].add(mc[1].mul(new BN(99))); // master poly evaluated at x = 99 - if (!gU.eq(_gU)) throw new Error("decrypted user shares inconsistent with poly commits"); - userShares.push(userShare); - } - - const userFactorEncs = await Promise.all( - userShares.map((userShare, i) => { - const pub = factorPubs[i]; - return encrypt(Buffer.from(`04${pub.x.padStart(64, "0")}${pub.y.padStart(64, "0")}`, "hex"), Buffer.from(userShare.toString(16, 64), "hex")); - }) - ); - - // rearrange received serverEncs before sending them to new servers - const serverEncs = targetIndexes.map((_, i) => { - const serverEncsReceived = rssRound1Responses.map((r) => r.data[i].target_encryptions.server_encs); - // flip the matrix - const serverEncsToSend = []; - for (let j = 0; j < this.serverEndpoints.length; j++) { - const serverEnc = []; - for (let k = 0; k < this.serverThreshold * 2 + 1; k++) { - serverEnc.push(serverEncsReceived[k][j]); - } - serverEncsToSend.push(serverEnc); - } - - return serverEncsToSend; + const { sums, serverEncs, userFactorEncs } = await refreshClientRound2({ + targetIndexes, + rssRound1Responses, + serverThreshold: this.serverThreshold, + serverEndpoints: this.serverEndpoints, + factorPubs, + tempPrivKey: hexToBigInt(this.tempPrivKey.toString("hex")), + dkgNewPub, + tssPubKey: hexPoint(this.tssPubKey), + keyType: this.keyType, }); // servers sum up their shares and encrypt it for factorPubs @@ -623,8 +360,8 @@ export class RSSClient { targetIndexes.map((_, i) => { const { mc, sc } = sums[i]; const round2RequestData = { - master_commits: mc.map(hexPoint), - server_commits: sc.map(hexPoint), + master_commits: mc, + server_commits: sc, server_encs: serverEncs[i][ind - 1], factor_pubkeys: [factorPubs[i]], // TODO: must we do it like this? }; diff --git a/test/test.ts b/test/test.ts index c622ee0..2fbe3ad 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,3 +1,5 @@ +import { describe, it } from "node:test"; + import assert from "assert"; import { ec as EC } from "elliptic"; import log from "loglevel"; @@ -33,7 +35,7 @@ describe("RSS Client", function () { // "http://localhost:7075", // ]; // }); - it("#should return correct values for import", async function () { + it("#should return correct values for import", {}, async function () { const testId = "test@test.com\u001cgoogle"; const factorKeyPairs = [ecCurveSecp256k1.genKeyPair(), ecCurveSecp256k1.genKeyPair()]; @@ -118,10 +120,10 @@ describe("RSS Client", function () { return null; }); - return true; + // return true; }); - it("#should return correct values for refresh", async function () { + it("#should return correct values for refresh", {}, async function () { const testId = "test@test.com\u001cgoogle"; const factorKeyPairs = [ecCurveSecp256k1.genKeyPair(), ecCurveSecp256k1.genKeyPair()]; @@ -224,6 +226,6 @@ describe("RSS Client", function () { return null; }); - return true; + // return true; }); }); diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..f892080 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,12 @@ +{ + "extends": "@toruslabs/config/tsconfig.default.json", + "include": ["src", "test"], + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + }, +}