diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb56197e..5c494e7c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T # Unreleased +## Added + +- New `@aptos-labs/aptos-keystore` package: encrypted private key storage standard based on Ethereum's Web3 Secret Storage Definition + - Separate package in `keystore/` — install with `npm install @aptos-labs/aptos-keystore` + - Supports all Aptos key types: Ed25519, Secp256k1, Secp256r1 + - AES-256-GCM authenticated encryption (no separate MAC needed) + - Argon2id KDF by default (when `hash-wasm` is installed), falls back to scrypt + - PBKDF2-HMAC-SHA256 also supported as an alternative KDF + - Password-based or key-file-based encryption + - Portable JSON format designed for cross-SDK compatibility (TypeScript, Rust, Python, Go, etc.) + - Exports: `encryptKeystore`, `decryptKeystore`, `AptosKeyStore`, `KeystorePrivateKey`, `KeystoreEncryptOptions` + ## Fixed - Fix simple function arguments for `Vector>` types: BCS-encoded values (e.g. `AccountAddress.ONE`) passed as elements of `vector>` are now automatically wrapped in `MoveOption` instead of throwing a type mismatch error diff --git a/keystore/package.json b/keystore/package.json new file mode 100644 index 000000000..9db932dad --- /dev/null +++ b/keystore/package.json @@ -0,0 +1,63 @@ +{ + "name": "@aptos-labs/aptos-keystore", + "description": "Encrypted private key storage for Aptos — AES-256-GCM + Argon2id/scrypt/PBKDF2", + "packageManager": "pnpm@10.30.3", + "license": "Apache-2.0", + "engines": { + "node": ">=20.0.0" + }, + "bugs": { + "url": "https://github.com/aptos-labs/aptos-ts-sdk/issues/new/choose" + }, + "homepage": "https://github.com/aptos-labs/aptos-ts-sdk/tree/main/keystore", + "version": "0.1.0", + "sideEffects": false, + "main": "dist/common/index.js", + "module": "dist/esm/index.mjs", + "exports": { + ".": { + "source": "./src/index.ts", + "require": { + "types": "./dist/common/index.d.ts", + "default": "./dist/common/index.js" + }, + "import": { + "types": "./dist/esm/index.d.mts", + "default": "./dist/esm/index.mjs" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ], + "scripts": { + "build:clean": "rm -rf dist", + "build": "pnpm build:clean && tsup", + "test": "vitest run", + "fmt": "biome format --write 'src/' 'tests/'", + "check": "biome check 'src/' 'tests/'" + }, + "peerDependencies": { + "@aptos-labs/ts-sdk": "^6.2.0", + "hash-wasm": "^4.12.0" + }, + "peerDependenciesMeta": { + "hash-wasm": { + "optional": true + } + }, + "dependencies": { + "@noble/hashes": "^1.5.0" + }, + "devDependencies": { + "@aptos-labs/ts-sdk": "link:..", + "@biomejs/biome": "^2.4.6", + "@types/node": "^24.3.1", + "hash-wasm": "^4.12.0", + "tsup": "^8.5.1", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + } +} diff --git a/keystore/pnpm-lock.yaml b/keystore/pnpm-lock.yaml new file mode 100644 index 000000000..ecdd8483e --- /dev/null +++ b/keystore/pnpm-lock.yaml @@ -0,0 +1,1713 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@noble/hashes': + specifier: ^1.5.0 + version: 1.8.0 + devDependencies: + '@aptos-labs/ts-sdk': + specifier: link:.. + version: link:.. + '@biomejs/biome': + specifier: ^2.4.6 + version: 2.4.8 + '@types/node': + specifier: ^24.3.1 + version: 24.12.0 + hash-wasm: + specifier: ^4.12.0 + version: 4.12.0 + tsup: + specifier: ^8.5.1 + version: 8.5.1(postcss@8.5.8)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.18 + version: 4.1.0(@types/node@24.12.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)) + +packages: + + '@biomejs/biome@2.4.8': + resolution: {integrity: sha512-ponn0oKOky1oRXBV+rlSaUlixUxf1aZvWC19Z41zBfUOUesthrQqL3OtiAlSB1EjFjyWpn98Q64DHelhA6jNlA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.8': + resolution: {integrity: sha512-ARx0tECE8I7S2C2yjnWYLNbBdDoPdq3oyNLhMglmuctThwUsuzFWRKrHmIGwIRWKz0Mat9DuzLEDp52hGnrxGQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.8': + resolution: {integrity: sha512-Jg9/PsB9vDCJlANE8uhG7qDhb5w0Ix69D7XIIc8IfZPUoiPrbLm33k2Ig3NOJ/7nb3UbesFz3D1aDKm9DvzjhQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.8': + resolution: {integrity: sha512-Zo9OhBQDJ3IBGPlqHiTISloo5H0+FBIpemqIJdW/0edJ+gEcLR+MZeZozcUyz3o1nXkVA7++DdRKQT0599j9jA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-arm64@2.4.8': + resolution: {integrity: sha512-5CdrsJct76XG2hpKFwXnEtlT1p+4g4yV+XvvwBpzKsTNLO9c6iLlAxwcae2BJ7ekPGWjNGw9j09T5KGPKKxQig==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-linux-x64-musl@2.4.8': + resolution: {integrity: sha512-Gi8quv8MEuDdKaPFtS2XjEnMqODPsRg6POT6KhoP+VrkNb+T2ywunVB+TvOU0LX1jAZzfBr+3V1mIbBhzAMKvw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-x64@2.4.8': + resolution: {integrity: sha512-PdKXspVEaMCQLjtZCn6vfSck/li4KX9KGwSDbZdgIqlrizJ2MnMcE3TvHa2tVfXNmbjMikzcfJpuPWH695yJrw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-win32-arm64@2.4.8': + resolution: {integrity: sha512-LoFatS0tnHv6KkCVpIy3qZCih+MxUMvdYiPWLHRri7mhi2vyOOs8OrbZBcLTUEWCS+ktO72nZMy4F96oMhkOHQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.8': + resolution: {integrity: sha512-vAn7iXDoUbqFXqVocuq1sMYAd33p8+mmurqJkWl6CtIhobd/O6moe4rY5AJvzbunn/qZCdiDVcveqtkFh1e7Hg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@emnapi/core@1.9.1': + resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + + '@emnapi/wasi-threads@1.2.0': + resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + + '@esbuild/aix-ppc64@0.27.4': + resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.4': + resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.4': + resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.4': + resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.4': + resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.4': + resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.4': + resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.4': + resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.4': + resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.4': + resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.4': + resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.4': + resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.4': + resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.4': + resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.4': + resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.4': + resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.4': + resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.4': + resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.4': + resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.4': + resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.4': + resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.4': + resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.4': + resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.4': + resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.4': + resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.4': + resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@oxc-project/types@0.120.0': + resolution: {integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==} + + '@rolldown/binding-android-arm64@1.0.0-rc.10': + resolution: {integrity: sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.10': + resolution: {integrity: sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.10': + resolution: {integrity: sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.10': + resolution: {integrity: sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10': + resolution: {integrity: sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10': + resolution: {integrity: sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.10': + resolution: {integrity: sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10': + resolution: {integrity: sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10': + resolution: {integrity: sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.10': + resolution: {integrity: sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.10': + resolution: {integrity: sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.10': + resolution: {integrity: sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.10': + resolution: {integrity: sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10': + resolution: {integrity: sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.10': + resolution: {integrity: sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.10': + resolution: {integrity: sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==} + + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@24.12.0': + resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} + + '@vitest/expect@4.1.0': + resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==} + + '@vitest/mocker@4.1.0': + resolution: {integrity: sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.0': + resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==} + + '@vitest/runner@4.1.0': + resolution: {integrity: sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==} + + '@vitest/snapshot@4.1.0': + resolution: {integrity: sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==} + + '@vitest/spy@4.1.0': + resolution: {integrity: sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==} + + '@vitest/utils@4.1.0': + resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + + esbuild@0.27.4: + resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + hash-wasm@4.12.0: + resolution: {integrity: sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + rolldown@1.0.0-rc.10: + resolution: {integrity: sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.0.0: + resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.0.4: + resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + vite@8.0.1: + resolution: {integrity: sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 + jiti: '>=1.21.0' + less: ^4.0.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 + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.0: + resolution: {integrity: sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.0 + '@vitest/browser-preview': 4.1.0 + '@vitest/browser-webdriverio': 4.1.0 + '@vitest/ui': 4.1.0 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + +snapshots: + + '@biomejs/biome@2.4.8': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.8 + '@biomejs/cli-darwin-x64': 2.4.8 + '@biomejs/cli-linux-arm64': 2.4.8 + '@biomejs/cli-linux-arm64-musl': 2.4.8 + '@biomejs/cli-linux-x64': 2.4.8 + '@biomejs/cli-linux-x64-musl': 2.4.8 + '@biomejs/cli-win32-arm64': 2.4.8 + '@biomejs/cli-win32-x64': 2.4.8 + + '@biomejs/cli-darwin-arm64@2.4.8': + optional: true + + '@biomejs/cli-darwin-x64@2.4.8': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.8': + optional: true + + '@biomejs/cli-linux-arm64@2.4.8': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.8': + optional: true + + '@biomejs/cli-linux-x64@2.4.8': + optional: true + + '@biomejs/cli-win32-arm64@2.4.8': + optional: true + + '@biomejs/cli-win32-x64@2.4.8': + optional: true + + '@emnapi/core@1.9.1': + dependencies: + '@emnapi/wasi-threads': 1.2.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.27.4': + optional: true + + '@esbuild/android-arm64@0.27.4': + optional: true + + '@esbuild/android-arm@0.27.4': + optional: true + + '@esbuild/android-x64@0.27.4': + optional: true + + '@esbuild/darwin-arm64@0.27.4': + optional: true + + '@esbuild/darwin-x64@0.27.4': + optional: true + + '@esbuild/freebsd-arm64@0.27.4': + optional: true + + '@esbuild/freebsd-x64@0.27.4': + optional: true + + '@esbuild/linux-arm64@0.27.4': + optional: true + + '@esbuild/linux-arm@0.27.4': + optional: true + + '@esbuild/linux-ia32@0.27.4': + optional: true + + '@esbuild/linux-loong64@0.27.4': + optional: true + + '@esbuild/linux-mips64el@0.27.4': + optional: true + + '@esbuild/linux-ppc64@0.27.4': + optional: true + + '@esbuild/linux-riscv64@0.27.4': + optional: true + + '@esbuild/linux-s390x@0.27.4': + optional: true + + '@esbuild/linux-x64@0.27.4': + optional: true + + '@esbuild/netbsd-arm64@0.27.4': + optional: true + + '@esbuild/netbsd-x64@0.27.4': + optional: true + + '@esbuild/openbsd-arm64@0.27.4': + optional: true + + '@esbuild/openbsd-x64@0.27.4': + optional: true + + '@esbuild/openharmony-arm64@0.27.4': + optional: true + + '@esbuild/sunos-x64@0.27.4': + optional: true + + '@esbuild/win32-arm64@0.27.4': + optional: true + + '@esbuild/win32-ia32@0.27.4': + optional: true + + '@esbuild/win32-x64@0.27.4': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.9.1 + '@emnapi/runtime': 1.9.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@noble/hashes@1.8.0': {} + + '@oxc-project/types@0.120.0': {} + + '@rolldown/binding-android-arm64@1.0.0-rc.10': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.10': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.10': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.10': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.10': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.10': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.10': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.10': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.10': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.10': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.10': {} + + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-x64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.59.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.59.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.59.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': + optional: true + + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/node@24.12.0': + dependencies: + undici-types: 7.16.0 + + '@vitest/expect@4.1.0': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.0 + '@vitest/utils': 4.1.0 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.0(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4))': + dependencies: + '@vitest/spy': 4.1.0 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4) + + '@vitest/pretty-format@4.1.0': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.0': + dependencies: + '@vitest/utils': 4.1.0 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.0': + dependencies: + '@vitest/pretty-format': 4.1.0 + '@vitest/utils': 4.1.0 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.0': {} + + '@vitest/utils@4.1.0': + dependencies: + '@vitest/pretty-format': 4.1.0 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + acorn@8.16.0: {} + + any-promise@1.3.0: {} + + assertion-error@2.0.1: {} + + bundle-require@5.1.0(esbuild@0.27.4): + dependencies: + esbuild: 0.27.4 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + chai@6.2.2: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + commander@4.1.1: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + convert-source-map@2.0.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + detect-libc@2.1.2: {} + + es-module-lexer@2.0.0: {} + + esbuild@0.27.4: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.4 + '@esbuild/android-arm': 0.27.4 + '@esbuild/android-arm64': 0.27.4 + '@esbuild/android-x64': 0.27.4 + '@esbuild/darwin-arm64': 0.27.4 + '@esbuild/darwin-x64': 0.27.4 + '@esbuild/freebsd-arm64': 0.27.4 + '@esbuild/freebsd-x64': 0.27.4 + '@esbuild/linux-arm': 0.27.4 + '@esbuild/linux-arm64': 0.27.4 + '@esbuild/linux-ia32': 0.27.4 + '@esbuild/linux-loong64': 0.27.4 + '@esbuild/linux-mips64el': 0.27.4 + '@esbuild/linux-ppc64': 0.27.4 + '@esbuild/linux-riscv64': 0.27.4 + '@esbuild/linux-s390x': 0.27.4 + '@esbuild/linux-x64': 0.27.4 + '@esbuild/netbsd-arm64': 0.27.4 + '@esbuild/netbsd-x64': 0.27.4 + '@esbuild/openbsd-arm64': 0.27.4 + '@esbuild/openbsd-x64': 0.27.4 + '@esbuild/openharmony-arm64': 0.27.4 + '@esbuild/sunos-x64': 0.27.4 + '@esbuild/win32-arm64': 0.27.4 + '@esbuild/win32-ia32': 0.27.4 + '@esbuild/win32-x64': 0.27.4 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.3.0: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.2 + rollup: 4.59.0 + + fsevents@2.3.3: + optional: true + + hash-wasm@4.12.0: {} + + joycon@3.1.1: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + object-assign@4.1.1: {} + + obug@2.1.1: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + postcss-load-config@6.0.1(postcss@8.5.8): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.8 + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + readdirp@4.1.2: {} + + resolve-from@5.0.0: {} + + rolldown@1.0.0-rc.10: + dependencies: + '@oxc-project/types': 0.120.0 + '@rolldown/pluginutils': 1.0.0-rc.10 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.10 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.10 + '@rolldown/binding-darwin-x64': 1.0.0-rc.10 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.10 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.10 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.10 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.10 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.10 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.10 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.10 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.10 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.10 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.10 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.10 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.10 + + rollup@4.59.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@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.3 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + stackback@0.0.2: {} + + std-env@4.0.0: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyexec@1.0.4: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.1.0: {} + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: + optional: true + + tsup@8.5.1(postcss@8.5.8)(typescript@5.9.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.4) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.4 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.8) + resolve-from: 5.0.0 + rollup: 4.59.0 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.8 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + undici-types@7.16.0: {} + + vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.3 + postcss: 8.5.8 + rolldown: 1.0.0-rc.10 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.12.0 + esbuild: 0.27.4 + fsevents: 2.3.3 + + vitest@4.1.0(@types/node@24.12.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)): + dependencies: + '@vitest/expect': 4.1.0 + '@vitest/mocker': 4.1.0(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)) + '@vitest/pretty-format': 4.1.0 + '@vitest/runner': 4.1.0 + '@vitest/snapshot': 4.1.0 + '@vitest/spy': 4.1.0 + '@vitest/utils': 4.1.0 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.0.4 + tinyglobby: 0.2.15 + tinyrainbow: 3.1.0 + vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.12.0 + transitivePeerDependencies: + - msw + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 diff --git a/keystore/src/index.ts b/keystore/src/index.ts new file mode 100644 index 000000000..ec1c34714 --- /dev/null +++ b/keystore/src/index.ts @@ -0,0 +1,4 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +export * from "./keystore"; diff --git a/keystore/src/keystore.ts b/keystore/src/keystore.ts new file mode 100644 index 000000000..9383f4e0a --- /dev/null +++ b/keystore/src/keystore.ts @@ -0,0 +1,473 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +/** + * Aptos Keystore — Encrypted private key storage standard. + * + * Based on the Ethereum Web3 Secret Storage Definition (keystore v3) and ERC-2335, + * adapted for Aptos key types. Supports encrypting Ed25519, Secp256k1, and Secp256r1 + * private keys with a password or key file using modern cryptography: + * + * - KDF: Argon2id (default when `hash-wasm` is installed), scrypt (fallback), or PBKDF2-HMAC-SHA256 + * - Cipher: AES-256-GCM (authenticated encryption) + * + * AES-256-GCM provides both confidentiality and integrity in a single operation, + * eliminating the need for a separate MAC. The GCM authentication tag verifies + * both the ciphertext integrity and the correctness of the password. + * + * The resulting JSON is portable across Aptos SDKs (TypeScript, Rust, Python, Go, etc.). + * + * @module + */ + +import { sha256 } from "@noble/hashes/sha256"; +import { scrypt as nobleScrypt } from "@noble/hashes/scrypt"; +import { pbkdf2 as noblePbkdf2 } from "@noble/hashes/pbkdf2"; +import { randomBytes, bytesToHex, hexToBytes } from "@noble/hashes/utils"; +import { Ed25519PrivateKey, Secp256k1PrivateKey, Secp256r1PrivateKey, PrivateKeyVariants } from "@aptos-labs/ts-sdk"; + +/** + * Union type of all private key types supported by the Aptos Keystore. + */ +export type KeystorePrivateKey = Ed25519PrivateKey | Secp256k1PrivateKey | Secp256r1PrivateKey; + +// region Types + +/** + * Argon2id KDF parameters stored in the keystore JSON. + */ +export interface Argon2idKdfParams { + /** Number of iterations (time cost). */ + iterations: number; + /** Degree of parallelism. */ + parallelism: number; + /** Memory size in kibibytes (1024 bytes). */ + memorySize: number; + /** Derived key length in bytes. */ + dklen: number; + /** Hex-encoded salt (no 0x prefix). */ + salt: string; +} + +/** + * Scrypt KDF parameters stored in the keystore JSON. + */ +export interface ScryptKdfParams { + /** CPU/memory cost parameter (must be a power of 2). */ + n: number; + /** Block size parameter. */ + r: number; + /** Parallelization parameter. */ + p: number; + /** Derived key length in bytes. */ + dklen: number; + /** Hex-encoded salt (no 0x prefix). */ + salt: string; +} + +/** + * PBKDF2 KDF parameters stored in the keystore JSON. + */ +export interface Pbkdf2KdfParams { + /** Iteration count. */ + c: number; + /** Derived key length in bytes. */ + dklen: number; + /** Pseudorandom function identifier. */ + prf: "hmac-sha256"; + /** Hex-encoded salt (no 0x prefix). */ + salt: string; +} + +/** Supported KDF algorithms. */ +export type KeystoreKdf = "argon2id" | "scrypt" | "pbkdf2"; + +/** Union of all KDF parameter types. */ +export type KeystoreKdfParams = Argon2idKdfParams | ScryptKdfParams | Pbkdf2KdfParams; + +/** + * The Aptos Keystore JSON structure for encrypted private key storage. + * + * Uses AES-256-GCM authenticated encryption: the GCM authentication tag provides + * both integrity and password verification, so no separate MAC field is needed. + * + * @example + * ```json + * { + * "version": 1, + * "id": "a7b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", + * "key_type": "ed25519", + * "address": "0x1234...abcd", + * "crypto": { + * "cipher": "aes-256-gcm", + * "cipherparams": { "iv": "...", "tag": "..." }, + * "ciphertext": "...", + * "kdf": "argon2id", + * "kdfparams": { "iterations": 3, "parallelism": 4, "memorySize": 65536, "dklen": 32, "salt": "..." } + * } + * } + * ``` + */ +export interface AptosKeyStore { + /** Keystore format version (always 1). */ + version: 1; + /** UUID v4 identifier for this keystore. */ + id: string; + /** Aptos key type stored in this keystore. */ + key_type: PrivateKeyVariants; + /** Optional Aptos account address associated with this key (0x-prefixed hex). */ + address?: string; + /** Cryptographic parameters and encrypted data. */ + crypto: { + /** Symmetric cipher algorithm. */ + cipher: "aes-256-gcm"; + /** Cipher parameters. */ + cipherparams: { + /** Hex-encoded 12-byte GCM nonce (no 0x prefix). */ + iv: string; + /** Hex-encoded 16-byte GCM authentication tag (no 0x prefix). */ + tag: string; + }; + /** Hex-encoded encrypted private key (no 0x prefix). */ + ciphertext: string; + /** Key derivation function identifier. */ + kdf: KeystoreKdf; + /** KDF-specific parameters. */ + kdfparams: KeystoreKdfParams; + }; +} + +/** + * Options for customizing keystore encryption. + */ +export interface KeystoreEncryptOptions { + /** KDF to use. Defaults to "argon2id" if `hash-wasm` is installed, otherwise "scrypt". */ + kdf?: KeystoreKdf; + /** Argon2id iterations (time cost). Defaults to 3. */ + argon2Iterations?: number; + /** Argon2id parallelism. Defaults to 4. */ + argon2Parallelism?: number; + /** Argon2id memory size in KiB. Defaults to 65536 (64 MiB). */ + argon2MemorySize?: number; + /** scrypt cost parameter N. Defaults to 131072 (2^17). Must be a power of 2. */ + scryptN?: number; + /** scrypt block size parameter r. Defaults to 8. */ + scryptR?: number; + /** scrypt parallelization parameter p. Defaults to 1. */ + scryptP?: number; + /** PBKDF2 iteration count. Defaults to 262144 (2^18). */ + pbkdf2C?: number; + /** Optional Aptos account address to include in the keystore. */ + address?: string; +} + +// endregion + +// region Internal helpers + +const DKLEN = 32; +const GCM_IV_LENGTH = 12; +const GCM_TAG_LENGTH = 16; +const SALT_LENGTH = 32; + +function passwordToBytes(password: string | Uint8Array): Uint8Array { + if (typeof password === "string") { + return new TextEncoder().encode(password); + } + return password; +} + +async function tryLoadArgon2id(): Promise { + try { + const mod = await import("hash-wasm"); + return mod.argon2id; + } catch { + return null; + } +} + +async function loadArgon2id(): Promise { + const fn = await tryLoadArgon2id(); + if (!fn) { + throw new Error('Argon2id KDF requires the "hash-wasm" package. Install it with: npm install hash-wasm'); + } + return fn; +} + +async function resolveDefaultKdf(): Promise { + const argon2 = await tryLoadArgon2id(); + return argon2 ? "argon2id" : "scrypt"; +} + +async function deriveKey(password: Uint8Array, kdf: KeystoreKdf, kdfparams: KeystoreKdfParams): Promise { + const salt = hexToBytes(kdfparams.salt); + + if (kdf === "argon2id") { + const argon2id = await loadArgon2id(); + const params = kdfparams as Argon2idKdfParams; + return argon2id({ + password, + salt, + iterations: params.iterations, + parallelism: params.parallelism, + memorySize: params.memorySize, + hashLength: params.dklen, + outputType: "binary", + }); + } + + if (kdf === "scrypt") { + const params = kdfparams as ScryptKdfParams; + return nobleScrypt(password, salt, { + N: params.n, + r: params.r, + p: params.p, + dkLen: params.dklen, + }); + } + + const params = kdfparams as Pbkdf2KdfParams; + return noblePbkdf2(sha256, password, salt, { + c: params.c, + dkLen: params.dklen, + }); +} + +function toArrayBuffer(data: Uint8Array): ArrayBuffer { + const buf = new ArrayBuffer(data.length); + new Uint8Array(buf).set(data); + return buf; +} + +async function aes256GcmEncrypt( + key: Uint8Array, + iv: Uint8Array, + plaintext: Uint8Array, +): Promise<{ ciphertext: Uint8Array; tag: Uint8Array }> { + const cryptoKey = await globalThis.crypto.subtle.importKey("raw", toArrayBuffer(key), { name: "AES-GCM" }, false, [ + "encrypt", + ]); + const result = await globalThis.crypto.subtle.encrypt( + { name: "AES-GCM", iv: toArrayBuffer(iv), tagLength: GCM_TAG_LENGTH * 8 }, + cryptoKey, + toArrayBuffer(plaintext), + ); + const resultBytes = new Uint8Array(result); + return { + ciphertext: resultBytes.slice(0, resultBytes.length - GCM_TAG_LENGTH), + tag: resultBytes.slice(resultBytes.length - GCM_TAG_LENGTH), + }; +} + +async function aes256GcmDecrypt( + key: Uint8Array, + iv: Uint8Array, + ciphertext: Uint8Array, + tag: Uint8Array, +): Promise { + const cryptoKey = await globalThis.crypto.subtle.importKey("raw", toArrayBuffer(key), { name: "AES-GCM" }, false, [ + "decrypt", + ]); + const combined = new Uint8Array(ciphertext.length + tag.length); + combined.set(ciphertext, 0); + combined.set(tag, ciphertext.length); + const result = await globalThis.crypto.subtle.decrypt( + { name: "AES-GCM", iv: toArrayBuffer(iv), tagLength: GCM_TAG_LENGTH * 8 }, + cryptoKey, + toArrayBuffer(combined), + ); + return new Uint8Array(result); +} + +function getKeyType(privateKey: KeystorePrivateKey): PrivateKeyVariants { + if (privateKey instanceof Ed25519PrivateKey) return PrivateKeyVariants.Ed25519; + if (privateKey instanceof Secp256k1PrivateKey) return PrivateKeyVariants.Secp256k1; + if (privateKey instanceof Secp256r1PrivateKey) return PrivateKeyVariants.Secp256r1; + throw new Error("Unsupported private key type for keystore encryption"); +} + +function createPrivateKey(bytes: Uint8Array, keyType: PrivateKeyVariants): KeystorePrivateKey { + switch (keyType) { + case PrivateKeyVariants.Ed25519: + return new Ed25519PrivateKey(bytes, false); + case PrivateKeyVariants.Secp256k1: + return new Secp256k1PrivateKey(bytes, false); + case PrivateKeyVariants.Secp256r1: + return new Secp256r1PrivateKey(bytes, false); + default: + throw new Error(`Unsupported key type in keystore: ${keyType}`); + } +} + +// endregion + +// region Public API + +/** + * Encrypt a private key into an Aptos Keystore JSON object. + * + * Supports all Aptos private key types (Ed25519, Secp256k1, Secp256r1). + * The password can be a string (passphrase) or raw bytes (e.g., contents of a key file). + * + * Uses AES-256-GCM authenticated encryption. The default KDF is Argon2id when the + * optional `hash-wasm` peer dependency is installed (`npm install hash-wasm`), + * falling back to scrypt otherwise. + * + * @param args.privateKey - The private key to encrypt. + * @param args.password - Password string or key-file bytes used to derive the encryption key. + * @param args.options - Optional encryption parameters (KDF, cost parameters, address). + * @returns A promise that resolves to the encrypted AptosKeyStore JSON object. + * + * @example + * ```typescript + * import { Ed25519PrivateKey } from "@aptos-labs/ts-sdk"; + * import { encryptKeystore } from "@aptos-labs/aptos-keystore"; + * + * // Uses argon2id if hash-wasm is installed, scrypt otherwise + * const privateKey = Ed25519PrivateKey.generate(); + * const keystore = await encryptKeystore({ + * privateKey, + * password: "my-secure-password", + * }); + * + * // Explicitly request a KDF + * const keystoreScrypt = await encryptKeystore({ + * privateKey, + * password: "my-secure-password", + * options: { kdf: "scrypt" }, + * }); + * ``` + */ +export async function encryptKeystore(args: { + privateKey: KeystorePrivateKey; + password: string | Uint8Array; + options?: KeystoreEncryptOptions; +}): Promise { + const { privateKey, password, options = {} } = args; + const defaultKdf = options.kdf ?? (await resolveDefaultKdf()); + const { + kdf = defaultKdf, + argon2Iterations = 3, + argon2Parallelism = 4, + argon2MemorySize = 65536, + scryptN = 131072, + scryptR = 8, + scryptP = 1, + pbkdf2C = 262144, + address, + } = options; + + const passwordBytes = passwordToBytes(password); + const salt = randomBytes(SALT_LENGTH); + const iv = randomBytes(GCM_IV_LENGTH); + const keyType = getKeyType(privateKey); + const plaintext = privateKey.toUint8Array(); + + let kdfparams: KeystoreKdfParams; + if (kdf === "argon2id") { + kdfparams = { + iterations: argon2Iterations, + parallelism: argon2Parallelism, + memorySize: argon2MemorySize, + dklen: DKLEN, + salt: bytesToHex(salt), + }; + } else if (kdf === "scrypt") { + kdfparams = { + n: scryptN, + r: scryptR, + p: scryptP, + dklen: DKLEN, + salt: bytesToHex(salt), + }; + } else { + kdfparams = { + c: pbkdf2C, + dklen: DKLEN, + prf: "hmac-sha256", + salt: bytesToHex(salt), + }; + } + + const dk = await deriveKey(passwordBytes, kdf, kdfparams); + const { ciphertext, tag } = await aes256GcmEncrypt(dk, iv, plaintext); + + const keystore: AptosKeyStore = { + version: 1, + id: globalThis.crypto.randomUUID(), + key_type: keyType, + crypto: { + cipher: "aes-256-gcm", + cipherparams: { + iv: bytesToHex(iv), + tag: bytesToHex(tag), + }, + ciphertext: bytesToHex(ciphertext), + kdf, + kdfparams, + }, + }; + + if (address !== undefined) { + keystore.address = address; + } + + return keystore; +} + +/** + * Decrypt an Aptos Keystore to recover the private key. + * + * Uses AES-256-GCM authenticated decryption: the GCM tag verifies both the + * ciphertext integrity and the correctness of the derived key (and therefore + * the password). If the password is wrong, decryption will fail with an error. + * + * @param args.keystore - The keystore object or a JSON string to decrypt. + * @param args.password - The password string or key-file bytes used during encryption. + * @returns A promise that resolves to the decrypted private key. + * @throws Error if the password is incorrect (GCM authentication fails). + * @throws Error if the keystore format is invalid. + * + * @example + * ```typescript + * import { decryptKeystore } from "@aptos-labs/aptos-keystore"; + * + * const privateKey = await decryptKeystore({ + * keystore: keystoreJson, + * password: "my-secure-password", + * }); + * ``` + */ +export async function decryptKeystore(args: { + keystore: AptosKeyStore | string; + password: string | Uint8Array; +}): Promise { + const { password } = args; + const keystore: AptosKeyStore = typeof args.keystore === "string" ? JSON.parse(args.keystore) : args.keystore; + + if (keystore.version !== 1) { + throw new Error(`Unsupported keystore version: ${keystore.version}`); + } + + const { crypto: cryptoData } = keystore; + if (cryptoData.cipher !== "aes-256-gcm") { + throw new Error(`Unsupported cipher: ${cryptoData.cipher}`); + } + + const passwordBytes = passwordToBytes(password); + const dk = await deriveKey(passwordBytes, cryptoData.kdf, cryptoData.kdfparams); + const ciphertext = hexToBytes(cryptoData.ciphertext); + const iv = hexToBytes(cryptoData.cipherparams.iv); + const tag = hexToBytes(cryptoData.cipherparams.tag); + + let plaintext: Uint8Array; + try { + plaintext = await aes256GcmDecrypt(dk, iv, ciphertext, tag); + } catch { + throw new Error("Invalid password: decryption failed (GCM authentication)"); + } + + return createPrivateKey(plaintext, keystore.key_type); +} + +// endregion diff --git a/keystore/tests/keystore.test.ts b/keystore/tests/keystore.test.ts new file mode 100644 index 000000000..30aaaac4a --- /dev/null +++ b/keystore/tests/keystore.test.ts @@ -0,0 +1,410 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import { Ed25519PrivateKey, Secp256k1PrivateKey, Secp256r1PrivateKey, PrivateKeyVariants } from "@aptos-labs/ts-sdk"; +import { encryptKeystore, decryptKeystore } from "../src"; +import type { AptosKeyStore } from "../src"; + +describe("AptosKeystore", () => { + const TEST_PASSWORD = "test-password-123"; + const FAST_ARGON2_OPTIONS = { + kdf: "argon2id" as const, + argon2Iterations: 2, + argon2Parallelism: 1, + argon2MemorySize: 1024, + }; + const FAST_SCRYPT_OPTIONS = { kdf: "scrypt" as const, scryptN: 1024, scryptR: 8, scryptP: 1 }; + const FAST_PBKDF2_OPTIONS = { kdf: "pbkdf2" as const, pbkdf2C: 1024 }; + + describe("encryptKeystore", () => { + it("should default to argon2id when hash-wasm is available", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: { argon2Iterations: 2, argon2Parallelism: 1, argon2MemorySize: 1024 }, + }); + + expect(keystore.version).toBe(1); + expect(keystore.key_type).toBe(PrivateKeyVariants.Ed25519); + expect(keystore.crypto.cipher).toBe("aes-256-gcm"); + expect(keystore.crypto.kdf).toBe("argon2id"); + expect(keystore.id).toBeDefined(); + expect(keystore.crypto.ciphertext).toBeDefined(); + expect(keystore.crypto.cipherparams.iv).toBeDefined(); + expect(keystore.crypto.cipherparams.tag).toBeDefined(); + expect(keystore.crypto.cipherparams.iv.length).toBe(24); // 12 bytes = 24 hex chars + expect(keystore.crypto.cipherparams.tag.length).toBe(32); // 16 bytes = 32 hex chars + }); + + it("should encrypt with scrypt when explicitly requested", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_SCRYPT_OPTIONS, + }); + + expect(keystore.crypto.kdf).toBe("scrypt"); + expect(keystore.crypto.cipher).toBe("aes-256-gcm"); + }); + + it("should encrypt with pbkdf2", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_PBKDF2_OPTIONS, + }); + + expect(keystore.crypto.kdf).toBe("pbkdf2"); + const params = keystore.crypto.kdfparams as { prf: string }; + expect(params.prf).toBe("hmac-sha256"); + }); + + it("should encrypt a Secp256k1 private key", async () => { + const privateKey = Secp256k1PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + expect(keystore.key_type).toBe(PrivateKeyVariants.Secp256k1); + }); + + it("should encrypt a Secp256r1 private key", async () => { + const privateKey = Secp256r1PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + expect(keystore.key_type).toBe(PrivateKeyVariants.Secp256r1); + }); + + it("should include address when provided", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const testAddress = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: { ...FAST_ARGON2_OPTIONS, address: testAddress }, + }); + + expect(keystore.address).toBe(testAddress); + }); + + it("should not include address when not provided", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + expect(keystore.address).toBeUndefined(); + }); + + it("should accept Uint8Array password (key file)", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keyFileBytes = new Uint8Array(32); + crypto.getRandomValues(keyFileBytes); + + const keystore = await encryptKeystore({ + privateKey, + password: keyFileBytes, + options: FAST_ARGON2_OPTIONS, + }); + + const decrypted = await decryptKeystore({ + keystore, + password: keyFileBytes, + }); + + expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + }); + + it("should produce unique ciphertext for same key with different passwords", async () => { + const privateKey = Ed25519PrivateKey.generate(); + + const keystore1 = await encryptKeystore({ + privateKey, + password: "password-one", + options: FAST_ARGON2_OPTIONS, + }); + const keystore2 = await encryptKeystore({ + privateKey, + password: "password-two", + options: FAST_ARGON2_OPTIONS, + }); + + expect(keystore1.crypto.ciphertext).not.toBe(keystore2.crypto.ciphertext); + }); + + it("should produce unique IDs for each keystore", async () => { + const privateKey = Ed25519PrivateKey.generate(); + + const keystore1 = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + const keystore2 = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + expect(keystore1.id).not.toBe(keystore2.id); + }); + + it("should produce valid JSON-serializable output", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const json = JSON.stringify(keystore); + const parsed = JSON.parse(json) as AptosKeyStore; + expect(parsed.version).toBe(1); + expect(parsed.crypto.cipher).toBe("aes-256-gcm"); + }); + }); + + describe("decryptKeystore", () => { + it("should round-trip an Ed25519 private key with argon2id", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); + expect(decrypted).toBeInstanceOf(Ed25519PrivateKey); + expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + }); + + it("should round-trip an Ed25519 private key with scrypt", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_SCRYPT_OPTIONS, + }); + + const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); + expect(decrypted).toBeInstanceOf(Ed25519PrivateKey); + expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + }); + + it("should round-trip an Ed25519 private key with pbkdf2", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_PBKDF2_OPTIONS, + }); + + const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); + expect(decrypted).toBeInstanceOf(Ed25519PrivateKey); + expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + }); + + it("should round-trip a Secp256k1 private key", async () => { + const privateKey = Secp256k1PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); + expect(decrypted).toBeInstanceOf(Secp256k1PrivateKey); + expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + }); + + it("should round-trip a Secp256r1 private key", async () => { + const privateKey = Secp256r1PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); + expect(decrypted).toBeInstanceOf(Secp256r1PrivateKey); + expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + }); + + it("should decrypt from a JSON string", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const json = JSON.stringify(keystore); + const decrypted = await decryptKeystore({ keystore: json, password: TEST_PASSWORD }); + expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + }); + + it("should throw on wrong password", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + await expect(decryptKeystore({ keystore, password: "wrong-password" })).rejects.toThrow( + "Invalid password: decryption failed (GCM authentication)", + ); + }); + + it("should throw on unsupported version", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const modified = { ...keystore, version: 99 as any }; + await expect(decryptKeystore({ keystore: modified, password: TEST_PASSWORD })).rejects.toThrow( + "Unsupported keystore version: 99", + ); + }); + + it("should throw on unsupported cipher", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const modified = { + ...keystore, + crypto: { ...keystore.crypto, cipher: "aes-128-ctr" as any }, + }; + await expect(decryptKeystore({ keystore: modified, password: TEST_PASSWORD })).rejects.toThrow( + "Unsupported cipher: aes-128-ctr", + ); + }); + + it("should throw on tampered ciphertext (GCM detects tampering)", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const chars = keystore.crypto.ciphertext.split(""); + chars[0] = chars[0] === "a" ? "b" : "a"; + const modified = { + ...keystore, + crypto: { ...keystore.crypto, ciphertext: chars.join("") }, + }; + await expect(decryptKeystore({ keystore: modified, password: TEST_PASSWORD })).rejects.toThrow( + "Invalid password: decryption failed (GCM authentication)", + ); + }); + + it("should throw on tampered tag", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const chars = keystore.crypto.cipherparams.tag.split(""); + chars[0] = chars[0] === "a" ? "b" : "a"; + const modified = { + ...keystore, + crypto: { + ...keystore.crypto, + cipherparams: { ...keystore.crypto.cipherparams, tag: chars.join("") }, + }, + }; + await expect(decryptKeystore({ keystore: modified, password: TEST_PASSWORD })).rejects.toThrow( + "Invalid password: decryption failed (GCM authentication)", + ); + }); + }); + + describe("cross-KDF compatibility", () => { + it("all three KDFs should produce valid keystores that decrypt correctly", async () => { + const privateKey = Ed25519PrivateKey.generate(); + + const argon2Ks = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + const scryptKs = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_SCRYPT_OPTIONS, + }); + const pbkdf2Ks = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_PBKDF2_OPTIONS, + }); + + expect(argon2Ks.crypto.kdf).toBe("argon2id"); + expect(scryptKs.crypto.kdf).toBe("scrypt"); + expect(pbkdf2Ks.crypto.kdf).toBe("pbkdf2"); + + const argon2Decrypted = await decryptKeystore({ keystore: argon2Ks, password: TEST_PASSWORD }); + const scryptDecrypted = await decryptKeystore({ keystore: scryptKs, password: TEST_PASSWORD }); + const pbkdf2Decrypted = await decryptKeystore({ keystore: pbkdf2Ks, password: TEST_PASSWORD }); + + expect(argon2Decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + expect(scryptDecrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + expect(pbkdf2Decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + }); + }); + + describe("derived key verification", () => { + it("the decrypted key should produce the same public key as the original", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const originalPubKey = privateKey.publicKey().toUint8Array(); + + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); + const decryptedPubKey = decrypted.publicKey().toUint8Array(); + + expect(decryptedPubKey).toEqual(originalPubKey); + }); + + it("the decrypted key should produce valid signatures", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const message = new TextEncoder().encode("Hello, Aptos!"); + + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + const decrypted = (await decryptKeystore({ keystore, password: TEST_PASSWORD })) as Ed25519PrivateKey; + const signature = decrypted.sign(message); + + expect(privateKey.publicKey().verifySignature({ message, signature })).toBe(true); + }); + }); +}); diff --git a/keystore/tsconfig.build.json b/keystore/tsconfig.build.json new file mode 100644 index 000000000..f86417dd0 --- /dev/null +++ b/keystore/tsconfig.build.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "baseUrl": "." + } +} diff --git a/keystore/tsup.config.ts b/keystore/tsup.config.ts new file mode 100644 index 000000000..5856d4fc5 --- /dev/null +++ b/keystore/tsup.config.ts @@ -0,0 +1,35 @@ +import { defineConfig } from "tsup"; +import type { Options, Format } from "tsup"; + +type MandatoryOptions = Options & { + outDir: string; + format: Format | Format[]; +}; + +const DEFAULT_CONFIG: Options = { + bundle: true, + clean: true, + dts: true, + minify: true, + entry: ["src/index.ts"], + skipNodeModulesBundle: true, + sourcemap: true, + splitting: true, + target: "es2020", + platform: "node", +}; + +const COMMON_CONFIG: MandatoryOptions = { + ...DEFAULT_CONFIG, + format: "cjs", + outDir: "dist/common", +}; + +const ESM_CONFIG: MandatoryOptions = { + ...DEFAULT_CONFIG, + entry: ["src/**/*.ts"], + format: "esm", + outDir: "dist/esm", +}; + +export default defineConfig([COMMON_CONFIG, ESM_CONFIG]); diff --git a/keystore/vitest.config.ts b/keystore/vitest.config.ts new file mode 100644 index 000000000..859640458 --- /dev/null +++ b/keystore/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + include: ["tests/**/*.test.ts"], + pool: "forks", + maxWorkers: 4, + testTimeout: 60000, + }, + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx", ".json"], + }, +});