From f114f283e5bfb73fb273371224ab8c230834ef29 Mon Sep 17 00:00:00 2001 From: ImmuneFOMO Date: Wed, 25 Feb 2026 19:06:19 +0000 Subject: [PATCH 1/2] feat: add CKD support with derivation_path in signing and ckdutil modul --- package-lock.json | 1349 +++++++++++++++++++++++++++++++++++++++--- package.json | 7 +- src/ckdutil.ts | 191 ++++++ src/client.ts | 2 + src/index.ts | 2 + src/types.ts | 1 + test/ckdutil.test.ts | 378 ++++++++++++ vitest.config.ts | 7 + 8 files changed, 1838 insertions(+), 99 deletions(-) create mode 100644 src/ckdutil.ts create mode 100644 test/ckdutil.test.ts create mode 100644 vitest.config.ts diff --git a/package-lock.json b/package-lock.json index c1bb331..beb475a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "@fystack/mpcium-ts", "version": "0.3.2", "dependencies": { + "@noble/curves": "^1.8.0", "@noble/ed25519": "^2.2.3", + "@noble/hashes": "^1.7.0", "age-encryption": "^0.2.3", "nats": "^2.29.3", "uuid": "^11.1.0" @@ -23,7 +25,8 @@ "bunchee": "^6.5.1", "ethers": "^6.13.7", "ts-node": "^10.9.2", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "vitest": "^3.2.4" } }, "node_modules/@adraffy/ens-normalize": { @@ -83,6 +86,448 @@ "node": ">=12" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@fastify/deepmerge": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz", @@ -1002,9 +1447,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", - "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -1016,9 +1461,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz", - "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -1030,9 +1475,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz", - "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -1044,9 +1489,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz", - "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -1058,9 +1503,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz", - "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -1072,9 +1517,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz", - "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -1086,9 +1531,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz", - "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -1100,9 +1545,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz", - "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -1114,9 +1559,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz", - "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -1128,9 +1573,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz", - "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -1141,10 +1586,24 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz", - "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -1155,10 +1614,24 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz", - "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -1170,9 +1643,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz", - "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -1184,9 +1657,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz", - "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -1198,9 +1671,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz", - "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -1212,9 +1685,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz", - "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -1226,9 +1699,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz", - "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -1239,10 +1712,38 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz", - "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -1254,9 +1755,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz", - "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -1267,10 +1768,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz", - "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -1732,6 +2247,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -1742,10 +2268,17 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -1783,6 +2316,131 @@ "@types/node": "*" } }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -1877,6 +2535,16 @@ "dev": true, "license": "MIT" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/base-x": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", @@ -2038,6 +2706,33 @@ } } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", @@ -2051,6 +2746,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -2225,6 +2930,16 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -2265,6 +2980,13 @@ "dev": true, "license": "MIT" }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -2282,6 +3004,48 @@ "es6-promise": "^4.0.3" } }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2385,6 +3149,16 @@ "dev": true, "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", @@ -2429,11 +3203,14 @@ } }, "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -2841,6 +3618,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -2925,6 +3709,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/nats": { "version": "2.29.3", "resolved": "https://registry.npmjs.org/nats/-/nats-2.29.3.tgz", @@ -3053,18 +3856,34 @@ "dev": true, "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -3074,6 +3893,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -3188,13 +4036,13 @@ } }, "node_modules/rollup": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", - "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -3204,26 +4052,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.1", - "@rollup/rollup-android-arm64": "4.40.1", - "@rollup/rollup-darwin-arm64": "4.40.1", - "@rollup/rollup-darwin-x64": "4.40.1", - "@rollup/rollup-freebsd-arm64": "4.40.1", - "@rollup/rollup-freebsd-x64": "4.40.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", - "@rollup/rollup-linux-arm-musleabihf": "4.40.1", - "@rollup/rollup-linux-arm64-gnu": "4.40.1", - "@rollup/rollup-linux-arm64-musl": "4.40.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", - "@rollup/rollup-linux-riscv64-gnu": "4.40.1", - "@rollup/rollup-linux-riscv64-musl": "4.40.1", - "@rollup/rollup-linux-s390x-gnu": "4.40.1", - "@rollup/rollup-linux-x64-gnu": "4.40.1", - "@rollup/rollup-linux-x64-musl": "4.40.1", - "@rollup/rollup-win32-arm64-msvc": "4.40.1", - "@rollup/rollup-win32-ia32-msvc": "4.40.1", - "@rollup/rollup-win32-x64-msvc": "4.40.1", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, @@ -3390,6 +4243,13 @@ "license": "MIT", "optional": true }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -3424,6 +4284,30 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -3488,6 +4372,26 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/superstruct": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", @@ -3517,6 +4421,67 @@ "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==", "dev": true }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3635,6 +4600,177 @@ "dev": true, "license": "MIT" }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -3663,6 +4799,23 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 4b80d61..9e4068e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ }, "scripts": { "build": "bunchee", + "test": "vitest run", + "test:watch": "vitest", "prepub": "npm run build", "pub": "npm publish --access public" }, @@ -24,10 +26,13 @@ "bunchee": "^6.5.1", "ethers": "^6.13.7", "ts-node": "^10.9.2", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "vitest": "^3.2.4" }, "dependencies": { + "@noble/curves": "^1.8.0", "@noble/ed25519": "^2.2.3", + "@noble/hashes": "^1.7.0", "age-encryption": "^0.2.3", "nats": "^2.29.3", "uuid": "^11.1.0" diff --git a/src/ckdutil.ts b/src/ckdutil.ts new file mode 100644 index 0000000..353440b --- /dev/null +++ b/src/ckdutil.ts @@ -0,0 +1,191 @@ +import { secp256k1 } from "@noble/curves/secp256k1"; +import { hmac } from "@noble/hashes/hmac"; +import { keccak_256 } from "@noble/hashes/sha3"; +import { sha512 } from "@noble/hashes/sha2"; +import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/hashes/utils"; + +const HARDENED_KEY_START = 0x80000000; +const CHAIN_CODE_BYTES = 32; +const COMPRESSED_PUBKEY_BYTES = 33; + +/** + * Derive a compressed child public key from a compressed master public key. + * Uses BIP-32 non-hardened derivation (public key only, no private key needed). + * + * @param masterPubKeyCompressed - 33-byte compressed secp256k1 public key + * @param chainCodeHex - 32-byte chain code as hex string (64 chars) + * @param path - derivation path as array of non-hardened indices + * @returns 33-byte compressed child public key + * @throws if any index >= 0x80000000 (hardened), or if derivation produces invalid key + */ +export function deriveSecp256k1ChildCompressed( + masterPubKeyCompressed: Uint8Array, + chainCodeHex: string, + path: number[] +): Uint8Array { + if (masterPubKeyCompressed.length !== COMPRESSED_PUBKEY_BYTES) { + throw new Error( + `invalid master pubkey length: ${masterPubKeyCompressed.length}` + ); + } + + let currentPubKey: Uint8Array; + try { + currentPubKey = secp256k1.ProjectivePoint.fromHex( + masterPubKeyCompressed + ).toRawBytes(true); + } catch (error) { + throw new Error(`decode master pubkey: ${toErrorMessage(error)}`); + } + + let currentChainCode = parseChainCode(chainCodeHex); + + for (let depth = 0; depth < path.length; depth += 1) { + const index = path[depth]; + validateChildIndex(index, depth); + + const data = new Uint8Array(COMPRESSED_PUBKEY_BYTES + 4); + data.set(currentPubKey, 0); + writeUint32BE(data, index, COMPRESSED_PUBKEY_BYTES); + + const ilr = hmac(sha512, currentChainCode, data); + const il = ilr.slice(0, CHAIN_CODE_BYTES); + const ir = ilr.slice(CHAIN_CODE_BYTES); + + const ilNum = bytesToBigInt(il); + if (ilNum === 0n || ilNum >= secp256k1.CURVE.n) { + throw new Error(`invalid IL for index ${index}`); + } + + const deltaPoint = secp256k1.ProjectivePoint.BASE.multiply(ilNum); + const parentPoint = secp256k1.ProjectivePoint.fromHex(currentPubKey); + const childPoint = parentPoint.add(deltaPoint); + + if (childPoint.equals(secp256k1.ProjectivePoint.ZERO)) { + throw new Error(`invalid child point at index ${index}`); + } + + currentPubKey = childPoint.toRawBytes(true); + currentChainCode = ir; + } + + return currentPubKey; +} + +/** + * Derive an Ethereum address from a compressed master public key + derivation path. + * Combines BIP-32 derivation with Ethereum address computation (keccak256 of uncompressed key). + * + * @returns Checksummed Ethereum address string (e.g. "0x742d35Cc...") + */ +export function deriveEthereumAddress( + masterPubKeyCompressed: Uint8Array, + chainCodeHex: string, + path: number[] +): string { + const childCompressed = deriveSecp256k1ChildCompressed( + masterPubKeyCompressed, + chainCodeHex, + path + ); + + const childPoint = secp256k1.ProjectivePoint.fromHex(childCompressed); + const childUncompressed = childPoint.toRawBytes(false); + const hash = keccak_256(childUncompressed.slice(1)); + const addressLowerHex = bytesToHex(hash.slice(-20)); + + return toChecksumAddress(addressLowerHex); +} + +/** + * Compress an uncompressed secp256k1 public key. + * @param uncompressed - 64 bytes (X||Y, no 04 prefix) or 65 bytes (with 04 prefix) + * @returns 33 bytes compressed (02/03 prefix + X) + */ +export function compressPublicKey(uncompressed: Uint8Array): Uint8Array { + let uncompressedWithPrefix: Uint8Array; + + if (uncompressed.length === 64) { + uncompressedWithPrefix = new Uint8Array(65); + uncompressedWithPrefix[0] = 0x04; + uncompressedWithPrefix.set(uncompressed, 1); + } else if (uncompressed.length === 65) { + if (uncompressed[0] !== 0x04) { + throw new Error( + `invalid uncompressed public key prefix: ${uncompressed[0]}` + ); + } + uncompressedWithPrefix = uncompressed; + } else { + throw new Error(`invalid uncompressed public key length: ${uncompressed.length}`); + } + + try { + return secp256k1.ProjectivePoint.fromHex(uncompressedWithPrefix).toRawBytes( + true + ); + } catch (error) { + throw new Error(`invalid secp256k1 public key: ${toErrorMessage(error)}`); + } +} + +function validateChildIndex(index: number, depth: number): void { + if (!Number.isInteger(index) || index < 0 || index > 0xffffffff) { + throw new Error(`invalid child index at path[${depth}]: ${index}`); + } + if (index >= HARDENED_KEY_START) { + throw new Error(`hardened derivation not supported: ${index}`); + } +} + +function parseChainCode(chainCodeHex: string): Uint8Array { + const normalized = chainCodeHex.startsWith("0x") + ? chainCodeHex.slice(2) + : chainCodeHex; + + let chainCode: Uint8Array; + try { + chainCode = hexToBytes(normalized); + } catch (error) { + throw new Error(`decode chain code: ${toErrorMessage(error)}`); + } + + if (chainCode.length !== CHAIN_CODE_BYTES) { + throw new Error(`invalid chain code length: ${chainCode.length}`); + } + + return chainCode; +} + +function bytesToBigInt(bytes: Uint8Array): bigint { + return BigInt(`0x${bytesToHex(bytes)}`); +} + +function writeUint32BE(target: Uint8Array, value: number, offset: number): void { + target[offset] = (value >>> 24) & 0xff; + target[offset + 1] = (value >>> 16) & 0xff; + target[offset + 2] = (value >>> 8) & 0xff; + target[offset + 3] = value & 0xff; +} + +function toChecksumAddress(addressLowerHex: string): string { + const address = addressLowerHex.toLowerCase().replace(/^0x/, ""); + if (address.length !== 40) { + throw new Error(`invalid Ethereum address length: ${address.length}`); + } + + const hash = keccak_256(utf8ToBytes(address)); + let checksummed = "0x"; + + for (let i = 0; i < address.length; i += 1) { + const hashByte = hash[Math.floor(i / 2)]; + const nibble = i % 2 === 0 ? hashByte >> 4 : hashByte & 0x0f; + checksummed += nibble >= 8 ? address[i].toUpperCase() : address[i]; + } + + return checksummed; +} + +function toErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/src/client.ts b/src/client.ts index b208cfa..1928cec 100644 --- a/src/client.ts +++ b/src/client.ts @@ -244,6 +244,7 @@ export class MpciumClient { keyType: KeyType; networkInternalCode: string; tx: string; + derivationPath?: number[]; }): Promise { const { nc } = this.options; @@ -256,6 +257,7 @@ export class MpciumClient { network_internal_code: params.networkInternalCode, tx_id: txId, tx: params.tx, + derivation_path: params.derivationPath, }; // Sign the message and convert Buffer to base64 string diff --git a/src/index.ts b/src/index.ts index e24c8d0..b40b5fe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,8 @@ export { signResharingMessage, } from "./utils"; +export * from "./ckdutil"; + // Export Polkadot/Substrate utilities export { buildSigningPayload, diff --git a/src/types.ts b/src/types.ts index c6cee6f..605279d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,6 +24,7 @@ export interface SignTxMessage { tx_id: string; tx: string; signature?: string; + derivation_path?: number[]; } export interface KeygenResultEvent { diff --git a/test/ckdutil.test.ts b/test/ckdutil.test.ts new file mode 100644 index 0000000..be3f8ee --- /dev/null +++ b/test/ckdutil.test.ts @@ -0,0 +1,378 @@ +import { secp256k1 } from "@noble/curves/secp256k1"; +import { hmac } from "@noble/hashes/hmac"; +import { sha256, sha512 } from "@noble/hashes/sha2"; +import { keccak_256 } from "@noble/hashes/sha3"; +import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/hashes/utils"; +import { describe, expect, it, vi } from "vitest"; +import { + compressPublicKey, + deriveEthereumAddress, + deriveSecp256k1ChildCompressed, +} from "../src/ckdutil"; +import { MpciumClient } from "../src/client"; +import { KeyType } from "../src/types"; + +const CHAIN_CODE_HEX = + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; +const MASTER_PRIVATE_KEY_HEX = + "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"; +const BASE58_ALPHABET = + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + +function writeUint32BE(target: Uint8Array, value: number, offset: number): void { + const view = new DataView(target.buffer, target.byteOffset, target.byteLength); + view.setUint32(offset, value, false); +} + +function referenceDeriveSecp256k1Child( + masterPubKeyCompressed: Uint8Array, + chainCodeHex: string, + path: number[] +): Uint8Array { + let currentChainCode = hexToBytes(chainCodeHex); + let currentPoint = secp256k1.ProjectivePoint.fromHex(masterPubKeyCompressed); + + for (const index of path) { + if (index >= 0x80000000) { + throw new Error(`hardened derivation not supported: ${index}`); + } + + const data = new Uint8Array(37); + data.set(currentPoint.toRawBytes(true), 0); + writeUint32BE(data, index, 33); + + const ilr = hmac(sha512, currentChainCode, data); + const il = ilr.slice(0, 32); + const ir = ilr.slice(32); + + const ilNum = BigInt(`0x${bytesToHex(il)}`); + if (ilNum === 0n || ilNum >= secp256k1.CURVE.n) { + throw new Error(`invalid IL for index ${index}`); + } + + const delta = secp256k1.ProjectivePoint.BASE.multiply(ilNum); + const child = currentPoint.add(delta); + if (child.equals(secp256k1.ProjectivePoint.ZERO)) { + throw new Error(`invalid child point at index ${index}`); + } + + currentPoint = child; + currentChainCode = ir; + } + + return currentPoint.toRawBytes(true); +} + +function toChecksumAddress(addressLowerHex: string): string { + const lower = addressLowerHex.toLowerCase().replace(/^0x/, ""); + const hash = keccak_256(utf8ToBytes(lower)); + let out = "0x"; + + for (let i = 0; i < lower.length; i += 1) { + const hashByte = hash[Math.floor(i / 2)]; + const nibble = i % 2 === 0 ? hashByte >> 4 : hashByte & 0x0f; + out += nibble >= 8 ? lower[i].toUpperCase() : lower[i]; + } + + return out; +} + +function referenceEthereumAddress(childCompressedPubKey: Uint8Array): string { + const childPoint = secp256k1.ProjectivePoint.fromHex(childCompressedPubKey); + const uncompressed = childPoint.toRawBytes(false); + const hash = keccak_256(uncompressed.slice(1)); + const addressLowerHex = bytesToHex(hash.slice(-20)); + return toChecksumAddress(addressLowerHex); +} + +function isChecksummedAddress(address: string): boolean { + if (!/^0x[0-9a-fA-F]{40}$/.test(address)) { + return false; + } + + const lower = address.slice(2).toLowerCase(); + return toChecksumAddress(lower) === address; +} + +function decodeXpub(xpub: string): { + chainCode: Uint8Array; + publicKey: Uint8Array; +} { + const decoded = base58Decode(xpub); + if (decoded.length !== 82) { + throw new Error(`invalid xpub length: ${decoded.length}`); + } + + const payload = decoded.slice(0, 78); + const checksum = decoded.slice(78); + const expectedChecksum = sha256(sha256(payload)).slice(0, 4); + if (bytesToHex(checksum) !== bytesToHex(expectedChecksum)) { + throw new Error("invalid xpub checksum"); + } + + const chainCode = payload.slice(13, 45); + const publicKey = payload.slice(45, 78); + return { chainCode, publicKey }; +} + +function base58Decode(input: string): Uint8Array { + let value = 0n; + for (const char of input) { + const digit = BASE58_ALPHABET.indexOf(char); + if (digit === -1) { + throw new Error(`invalid base58 character: ${char}`); + } + value = value * 58n + BigInt(digit); + } + + const decodedBytes: number[] = []; + while (value > 0n) { + decodedBytes.push(Number(value & 0xffn)); + value >>= 8n; + } + decodedBytes.reverse(); + + let leadingZeros = 0; + for (const char of input) { + if (char !== "1") { + break; + } + leadingZeros += 1; + } + + const out = new Uint8Array(leadingZeros + decodedBytes.length); + out.set(decodedBytes, leadingZeros); + return out; +} + +function createMockNatsConnection() { + const jetstreamPublish = vi.fn(async (_subject: string, _data: Uint8Array) => { + return { seq: 1n }; + }); + + const nc = { + status: async function* status() { + return; + }, + jetstream: () => ({ + publish: jetstreamPublish, + }), + publish: vi.fn(), + }; + + return { nc, jetstreamPublish }; +} + +type MockNc = ReturnType["nc"]; + +type SignTransactionClient = Pick & { + ensureStreamsExist: () => Promise; +}; + +function createSignTransactionClient( + nc: MockNc, + privateKeyByte: number +): SignTransactionClient { + const ClientCtor = MpciumClient as unknown as { + new ( + options: { nc: MockNc; keyPath: string }, + privateKey: Buffer + ): SignTransactionClient; + }; + + const client = new ClientCtor( + { nc, keyPath: "unused" }, + Buffer.alloc(32, privateKeyByte) + ); + + client.ensureStreamsExist = async () => {}; + return client; +} + +describe("ckdutil", () => { + const masterPrivateKey = hexToBytes(MASTER_PRIVATE_KEY_HEX); + const masterPubCompressed = secp256k1.getPublicKey(masterPrivateKey, true); + const masterPubUncompressed = secp256k1.getPublicKey(masterPrivateKey, false); + + it("A) derives expected child key via independent reference math", () => { + const path = [44, 60, 0, 0, 0]; + const actual = deriveSecp256k1ChildCompressed( + masterPubCompressed, + CHAIN_CODE_HEX, + path + ); + const expected = referenceDeriveSecp256k1Child( + masterPubCompressed, + CHAIN_CODE_HEX, + path + ); + + expect(bytesToHex(actual)).toBe(bytesToHex(expected)); + }); + + it("B) derives checksummed Ethereum address and matches manual computation", () => { + const path = [44, 60, 0, 0, 0]; + const derivedAddress = deriveEthereumAddress( + masterPubCompressed, + CHAIN_CODE_HEX, + path + ); + + const childCompressed = referenceDeriveSecp256k1Child( + masterPubCompressed, + CHAIN_CODE_HEX, + path + ); + const manualAddress = referenceEthereumAddress(childCompressed); + + expect(isChecksummedAddress(derivedAddress)).toBe(true); + expect(derivedAddress).toBe(manualAddress); + }); + + it("C) is deterministic and changes output with path/chain code changes", () => { + const path0 = [44, 60, 0, 0, 0]; + const path1 = [44, 60, 0, 0, 1]; + const alternateChainCode = + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + + const a1 = deriveEthereumAddress(masterPubCompressed, CHAIN_CODE_HEX, path0); + const a2 = deriveEthereumAddress(masterPubCompressed, CHAIN_CODE_HEX, path0); + const b = deriveEthereumAddress(masterPubCompressed, CHAIN_CODE_HEX, path1); + const c = deriveEthereumAddress( + masterPubCompressed, + alternateChainCode, + path0 + ); + + expect(a1).toBe(a2); + expect(a1).not.toBe(b); + expect(a1).not.toBe(c); + }); + + it("D) supports empty/single/multi-level paths and distinct final indices", () => { + const emptyPath = deriveSecp256k1ChildCompressed( + masterPubCompressed, + CHAIN_CODE_HEX, + [] + ); + expect(bytesToHex(emptyPath)).toBe(bytesToHex(masterPubCompressed)); + + const singleLevel = deriveSecp256k1ChildCompressed( + masterPubCompressed, + CHAIN_CODE_HEX, + [0] + ); + expect(singleLevel.length).toBe(33); + + const standard0 = deriveSecp256k1ChildCompressed( + masterPubCompressed, + CHAIN_CODE_HEX, + [44, 60, 0, 0, 0] + ); + const standard1 = deriveSecp256k1ChildCompressed( + masterPubCompressed, + CHAIN_CODE_HEX, + [44, 60, 0, 0, 1] + ); + + expect(bytesToHex(standard0)).not.toBe(bytesToHex(standard1)); + + const from64 = compressPublicKey(masterPubUncompressed.slice(1)); + const from65 = compressPublicKey(masterPubUncompressed); + expect(bytesToHex(from64)).toBe(bytesToHex(masterPubCompressed)); + expect(bytesToHex(from65)).toBe(bytesToHex(masterPubCompressed)); + }); + + it("E) validates hardened index, chain code, and invalid keys while allowing boundary indices", () => { + expect(() => + deriveSecp256k1ChildCompressed(masterPubCompressed, CHAIN_CODE_HEX, [ + 0x80000000, + ]) + ).toThrow(/hardened derivation not supported/i); + + expect(() => + deriveSecp256k1ChildCompressed(masterPubCompressed, "abcd", [0]) + ).toThrow(/invalid chain code length/i); + + expect(() => + deriveSecp256k1ChildCompressed(new Uint8Array(32), CHAIN_CODE_HEX, [0]) + ).toThrow(/invalid master pubkey length/i); + + const invalidPoint = hexToBytes( + "020000000000000000000000000000000000000000000000000000000000000007" + ); + expect(() => + deriveSecp256k1ChildCompressed(invalidPoint, CHAIN_CODE_HEX, [0]) + ).toThrow(/decode master pubkey|invalid/i); + + const indexZero = deriveSecp256k1ChildCompressed( + masterPubCompressed, + CHAIN_CODE_HEX, + [0] + ); + expect(indexZero.length).toBe(33); + + const largeIndex = deriveSecp256k1ChildCompressed( + masterPubCompressed, + CHAIN_CODE_HEX, + [0x7fffffff] + ); + expect(largeIndex.length).toBe(33); + }); + + it("F) matches BIP-32 vector for non-hardened derivation", () => { + // BIP-32 Test Vector 2: m -> m/0 (non-hardened) + const parentXpub = + "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"; + const childXpub = + "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"; + + const parent = decodeXpub(parentXpub); + const expectedChild = decodeXpub(childXpub); + + const derived = deriveSecp256k1ChildCompressed( + parent.publicKey, + bytesToHex(parent.chainCode), + [0] + ); + + expect(bytesToHex(derived)).toBe(bytesToHex(expectedChild.publicKey)); + }); + + it("G) includes derivation_path in SignTxMessage when provided", async () => { + const { nc, jetstreamPublish } = createMockNatsConnection(); + const client = createSignTransactionClient(nc, 7); + + await client.signTransaction({ + walletId: "wallet-1", + keyType: KeyType.Secp256k1, + networkInternalCode: "ethereum-mainnet", + tx: "AQID", + derivationPath: [44, 60, 0, 0, 0], + }); + + const publishedPayload = jetstreamPublish.mock.calls[0][1] as Uint8Array; + const decoded = JSON.parse(new TextDecoder().decode(publishedPayload)); + + expect(decoded.derivation_path).toEqual([44, 60, 0, 0, 0]); + }); + + it("G) omits derivation_path in SignTxMessage when not provided", async () => { + const { nc, jetstreamPublish } = createMockNatsConnection(); + const client = createSignTransactionClient(nc, 9); + + await client.signTransaction({ + walletId: "wallet-2", + keyType: KeyType.Ed25519, + networkInternalCode: "solana-devnet", + tx: "BAUG", + }); + + const publishedPayload = jetstreamPublish.mock.calls[0][1] as Uint8Array; + const decoded = JSON.parse(new TextDecoder().decode(publishedPayload)); + + expect(Object.prototype.hasOwnProperty.call(decoded, "derivation_path")).toBe( + false + ); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..e2ec332 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + }, +}); From 15ce11e34f671e7cffb907303ee4d1c8e7b2fca8 Mon Sep 17 00:00:00 2001 From: ImmuneFOMO Date: Wed, 25 Feb 2026 22:47:33 +0000 Subject: [PATCH 2/2] feat: add ed25519 CKD derivation with Go golden vectors and index edge tests --- src/ckdutil.ts | 82 +++++++++++++++ test/ckdutil.test.ts | 246 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 328 insertions(+) diff --git a/src/ckdutil.ts b/src/ckdutil.ts index 353440b..259ef97 100644 --- a/src/ckdutil.ts +++ b/src/ckdutil.ts @@ -1,3 +1,4 @@ +import { ed25519 } from "@noble/curves/ed25519"; import { secp256k1 } from "@noble/curves/secp256k1"; import { hmac } from "@noble/hashes/hmac"; import { keccak_256 } from "@noble/hashes/sha3"; @@ -7,6 +8,7 @@ import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/hashes/utils"; const HARDENED_KEY_START = 0x80000000; const CHAIN_CODE_BYTES = 32; const COMPRESSED_PUBKEY_BYTES = 33; +const ED25519_COMPRESSED_PUBKEY_BYTES = 32; /** * Derive a compressed child public key from a compressed master public key. @@ -72,6 +74,68 @@ export function deriveSecp256k1ChildCompressed( return currentPubKey; } +/** + * Derive a compressed child public key from a compressed master public key on ed25519. + * Uses BIP-32 non-hardened derivation (public key only, no private key needed). + * + * @param masterPubKeyCompressed - 32-byte compressed ed25519 public key + * @param chainCodeHex - 32-byte chain code as hex string (64 chars) + * @param path - derivation path as array of non-hardened indices + * @returns 32-byte compressed child public key (standard ed25519 format) + * @throws if any index >= 0x80000000 (hardened), or if derivation produces invalid key + */ +export function deriveEd25519ChildCompressed( + masterPubKeyCompressed: Uint8Array, + chainCodeHex: string, + path: number[] +): Uint8Array { + if (masterPubKeyCompressed.length !== ED25519_COMPRESSED_PUBKEY_BYTES) { + throw new Error( + `invalid master pubkey length: ${masterPubKeyCompressed.length}` + ); + } + + let currentPoint: InstanceType; + try { + currentPoint = ed25519.ExtendedPoint.fromHex(masterPubKeyCompressed); + } catch (error) { + throw new Error(`decode master pubkey: ${toErrorMessage(error)}`); + } + + let currentChainCode = parseChainCode(chainCodeHex); + + for (let depth = 0; depth < path.length; depth += 1) { + const index = path[depth]; + validateChildIndex(index, depth); + + const serialized = serializeEdwardsCompressed(currentPoint.x, currentPoint.y); + const data = new Uint8Array(COMPRESSED_PUBKEY_BYTES + 4); + data.set(serialized, 0); + writeUint32BE(data, index, COMPRESSED_PUBKEY_BYTES); + + const ilr = hmac(sha512, currentChainCode, data); + const il = ilr.slice(0, CHAIN_CODE_BYTES); + const ir = ilr.slice(CHAIN_CODE_BYTES); + + const ilNum = mod(bytesToBigInt(il), ed25519.CURVE.n); + if (ilNum === 0n) { + throw new Error(`invalid IL for index ${index}`); + } + + const deltaPoint = ed25519.ExtendedPoint.BASE.multiply(ilNum); + const childPoint = currentPoint.add(deltaPoint); + + if (childPoint.equals(ed25519.ExtendedPoint.ZERO)) { + throw new Error(`invalid child point at index ${index}`); + } + + currentPoint = childPoint; + currentChainCode = ir; + } + + return currentPoint.toRawBytes(); +} + /** * Derive an Ethereum address from a compressed master public key + derivation path. * Combines BIP-32 derivation with Ethereum address computation (keccak256 of uncompressed key). @@ -161,6 +225,24 @@ function bytesToBigInt(bytes: Uint8Array): bigint { return BigInt(`0x${bytesToHex(bytes)}`); } +function mod(a: bigint, n: bigint): bigint { + const result = a % n; + return result >= 0n ? result : result + n; +} + +/** + * Serialize an Edwards curve point as 33 bytes: 1-byte prefix (02/03 based on Y parity) + 32-byte X coordinate (big-endian). + * Matches the Go `serializeCompressed(x, y)` helper shared between secp256k1 and ed25519. + */ +function serializeEdwardsCompressed(x: bigint, y: bigint): Uint8Array { + const prefix = y & 1n ? 0x03 : 0x02; + const result = new Uint8Array(COMPRESSED_PUBKEY_BYTES); + result[0] = prefix; + const xHex = x.toString(16).padStart(64, "0"); + result.set(hexToBytes(xHex), 1); + return result; +} + function writeUint32BE(target: Uint8Array, value: number, offset: number): void { target[offset] = (value >>> 24) & 0xff; target[offset + 1] = (value >>> 16) & 0xff; diff --git a/test/ckdutil.test.ts b/test/ckdutil.test.ts index be3f8ee..cfb7304 100644 --- a/test/ckdutil.test.ts +++ b/test/ckdutil.test.ts @@ -1,3 +1,4 @@ +import { ed25519 } from "@noble/curves/ed25519"; import { secp256k1 } from "@noble/curves/secp256k1"; import { hmac } from "@noble/hashes/hmac"; import { sha256, sha512 } from "@noble/hashes/sha2"; @@ -6,6 +7,7 @@ import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/hashes/utils"; import { describe, expect, it, vi } from "vitest"; import { compressPublicKey, + deriveEd25519ChildCompressed, deriveEthereumAddress, deriveSecp256k1ChildCompressed, } from "../src/ckdutil"; @@ -376,3 +378,247 @@ describe("ckdutil", () => { ); }); }); + +/** + * Serialize an Edwards point as 33 bytes: prefix (02/03 by Y parity) + 32-byte X big-endian. + * Matches the Go `serializeCompressed(x, y)` helper used for HMAC input. + */ +function serializeEdwardsCompressed33(x: bigint, y: bigint): Uint8Array { + const prefix = y & 1n ? 0x03 : 0x02; + const result = new Uint8Array(33); + result[0] = prefix; + const xHex = x.toString(16).padStart(64, "0"); + result.set(hexToBytes(xHex), 1); + return result; +} + +function referenceDeriveEd25519Child( + masterPubKeyCompressed: Uint8Array, + chainCodeHex: string, + path: number[] +): Uint8Array { + let currentChainCode = hexToBytes(chainCodeHex); + let currentPoint = ed25519.ExtendedPoint.fromHex(masterPubKeyCompressed); + + for (const index of path) { + if (index >= 0x80000000) { + throw new Error(`hardened derivation not supported: ${index}`); + } + + const serialized = serializeEdwardsCompressed33(currentPoint.x, currentPoint.y); + const data = new Uint8Array(37); + data.set(serialized, 0); + writeUint32BE(data, index, 33); + + const ilr = hmac(sha512, currentChainCode, data); + const il = ilr.slice(0, 32); + const ir = ilr.slice(32); + + let ilNum = BigInt(`0x${bytesToHex(il)}`); + ilNum = ilNum % ed25519.CURVE.n; + if (ilNum === 0n) { + throw new Error(`invalid IL for index ${index}`); + } + + const delta = ed25519.ExtendedPoint.BASE.multiply(ilNum); + const child = currentPoint.add(delta); + if (child.equals(ed25519.ExtendedPoint.ZERO)) { + throw new Error(`invalid child point at index ${index}`); + } + + currentPoint = child; + currentChainCode = ir; + } + + return currentPoint.toRawBytes(); +} + +describe("ckdutil ed25519", () => { + const ED25519_MASTER_PRIVATE_KEY_HEX = + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"; + const ED25519_MASTER_PUBLIC_KEY_HEX = + "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"; + const GO_GOLDEN_CHILD_PATH_0_HEX = + "ceda176c847d179656ccb5da13f919d1fb5df16782f3742c77fdfe1d3b0f1ff9"; + const GO_GOLDEN_CHILD_PATH_44_501_0_0_0_HEX = + "6de30733f00a704796a727ec71508bbc6a6acb59564ffa4905530f7d480500c6"; + const ed25519MasterPub = ed25519.getPublicKey( + hexToBytes(ED25519_MASTER_PRIVATE_KEY_HEX) + ); + + it("H) derives expected ed25519 child key via independent reference math", () => { + const path = [44, 501, 0, 0, 0]; + const actual = deriveEd25519ChildCompressed( + ed25519MasterPub, + CHAIN_CODE_HEX, + path + ); + const expected = referenceDeriveEd25519Child( + ed25519MasterPub, + CHAIN_CODE_HEX, + path + ); + + expect(bytesToHex(actual)).toBe(bytesToHex(expected)); + }); + + it("H2) matches hardcoded Go golden vectors", () => { + expect(bytesToHex(ed25519MasterPub)).toBe(ED25519_MASTER_PUBLIC_KEY_HEX); + + const path0 = deriveEd25519ChildCompressed(ed25519MasterPub, CHAIN_CODE_HEX, [ + 0, + ]); + expect(bytesToHex(path0)).toBe(GO_GOLDEN_CHILD_PATH_0_HEX); + + const path44_501_0_0_0 = deriveEd25519ChildCompressed( + ed25519MasterPub, + CHAIN_CODE_HEX, + [44, 501, 0, 0, 0] + ); + expect(bytesToHex(path44_501_0_0_0)).toBe( + GO_GOLDEN_CHILD_PATH_44_501_0_0_0_HEX + ); + }); + + it("I) is deterministic: same inputs produce same output", () => { + const path = [44, 501, 0, 0, 0]; + const a1 = deriveEd25519ChildCompressed( + ed25519MasterPub, + CHAIN_CODE_HEX, + path + ); + const a2 = deriveEd25519ChildCompressed( + ed25519MasterPub, + CHAIN_CODE_HEX, + path + ); + + expect(bytesToHex(a1)).toBe(bytesToHex(a2)); + }); + + it("J) different paths produce different keys", () => { + const path0 = [44, 501, 0, 0, 0]; + const path1 = [44, 501, 0, 0, 1]; + + const a = deriveEd25519ChildCompressed( + ed25519MasterPub, + CHAIN_CODE_HEX, + path0 + ); + const b = deriveEd25519ChildCompressed( + ed25519MasterPub, + CHAIN_CODE_HEX, + path1 + ); + + expect(bytesToHex(a)).not.toBe(bytesToHex(b)); + }); + + it("K) different chain codes produce different keys", () => { + const path = [44, 501, 0, 0, 0]; + const alternateChainCode = + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + + const a = deriveEd25519ChildCompressed( + ed25519MasterPub, + CHAIN_CODE_HEX, + path + ); + const b = deriveEd25519ChildCompressed( + ed25519MasterPub, + alternateChainCode, + path + ); + + expect(bytesToHex(a)).not.toBe(bytesToHex(b)); + }); + + it("L) empty path returns master key in standard 32-byte ed25519 compressed form", () => { + const emptyPath = deriveEd25519ChildCompressed( + ed25519MasterPub, + CHAIN_CODE_HEX, + [] + ); + expect(emptyPath.length).toBe(32); + expect(bytesToHex(emptyPath)).toBe(bytesToHex(ed25519MasterPub)); + }); + + it("M) validates hardened index throws", () => { + expect(() => + deriveEd25519ChildCompressed(ed25519MasterPub, CHAIN_CODE_HEX, [ + 0x80000000, + ]) + ).toThrow(/hardened derivation not supported/i); + }); + + it("N) rejects negative child index", () => { + expect(() => + deriveEd25519ChildCompressed(ed25519MasterPub, CHAIN_CODE_HEX, [-1]) + ).toThrow(/invalid child index at path\[0\]/i); + }); + + it("O) rejects non-integer child index", () => { + expect(() => + deriveEd25519ChildCompressed(ed25519MasterPub, CHAIN_CODE_HEX, [1.5]) + ).toThrow(/invalid child index at path\[0\]/i); + }); + + it("P) rejects child index above uint32 max", () => { + expect(() => + deriveEd25519ChildCompressed(ed25519MasterPub, CHAIN_CODE_HEX, [ + 0x100000000, + ]) + ).toThrow(/invalid child index at path\[0\]/i); + }); + + it("Q) validates invalid inputs throw", () => { + // Wrong key length (33 bytes instead of 32) + expect(() => + deriveEd25519ChildCompressed(new Uint8Array(33), CHAIN_CODE_HEX, [0]) + ).toThrow(/invalid master pubkey length/i); + + // Wrong key length (31 bytes) + expect(() => + deriveEd25519ChildCompressed(new Uint8Array(31), CHAIN_CODE_HEX, [0]) + ).toThrow(/invalid master pubkey length/i); + + // Bad chain code (too short) + expect(() => + deriveEd25519ChildCompressed(ed25519MasterPub, "abcd", [0]) + ).toThrow(/invalid chain code length/i); + + // Invalid hex chain code + expect(() => + deriveEd25519ChildCompressed(ed25519MasterPub, "zzzz", [0]) + ).toThrow(/decode chain code/i); + + // Invalid ed25519 point (invalid y coordinate) + const invalidPoint = new Uint8Array(32); + invalidPoint[0] = 0xff; + invalidPoint[31] = 0xff; + expect(() => + deriveEd25519ChildCompressed(invalidPoint, CHAIN_CODE_HEX, [0]) + ).toThrow(/decode master pubkey|invalid/i); + }); + + it("R) supports single-level and multi-level derivation paths", () => { + const singleLevel = deriveEd25519ChildCompressed( + ed25519MasterPub, + CHAIN_CODE_HEX, + [0] + ); + expect(singleLevel.length).toBe(32); + + const multiLevel = deriveEd25519ChildCompressed( + ed25519MasterPub, + CHAIN_CODE_HEX, + [44, 501, 0, 0, 0] + ); + expect(multiLevel.length).toBe(32); + + // Result should be a valid ed25519 point + const parsed = ed25519.ExtendedPoint.fromHex(multiLevel); + expect(parsed.equals(ed25519.ExtendedPoint.ZERO)).toBe(false); + }); + +});