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"],
+ },
+});