From 7dcbf7c422aceab33090f5346c1f9de8024ea5fd Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 20 Mar 2026 01:12:13 +0000 Subject: [PATCH 1/5] feat: add Aptos Keystore encrypted private key storage standard Implement an encrypted credential management system based on Ethereum's Web3 Secret Storage Definition (keystore v3), adapted for all Aptos key types. The format is designed to be portable across Aptos SDKs. Key features: - Support for Ed25519, Secp256k1, and Secp256r1 private keys - Password-based or key-file-based encryption - scrypt (default) and PBKDF2-HMAC-SHA256 key derivation functions - AES-128-CTR symmetric cipher via Web Crypto API - SHA-256 MAC for password verification - Portable JSON format for cross-SDK interoperability New exports: - encryptKeystore(args): Encrypt a private key to keystore JSON - decryptKeystore(args): Decrypt keystore JSON to recover private key - AptosKeyStore: TypeScript interface for the keystore format - KeystorePrivateKey, KeystoreEncryptOptions: Supporting types Includes 23 unit tests covering all key types, both KDFs, round-trip encryption/decryption, error handling, and cryptographic verification. Co-authored-by: Greg Nazario --- CHANGELOG.md | 9 + src/core/crypto/index.ts | 1 + src/core/crypto/keystore.ts | 385 +++++++++++++++++++++++++++++++++++ tests/unit/keystore.test.ts | 386 ++++++++++++++++++++++++++++++++++++ 4 files changed, 781 insertions(+) create mode 100644 src/core/crypto/keystore.ts create mode 100644 tests/unit/keystore.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb56197e..014118ad6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T # Unreleased +## Added + +- Add Aptos Keystore: encrypted private key storage standard based on Ethereum's Web3 Secret Storage Definition (keystore v3) + - Supports all Aptos key types: Ed25519, Secp256k1, Secp256r1 + - Password-based or key-file-based encryption using scrypt (default) or PBKDF2-HMAC-SHA256 KDFs + - AES-128-CTR cipher with SHA-256 MAC for password verification + - Portable JSON format designed for cross-SDK compatibility (TypeScript, Rust, Python, Go, etc.) + - New 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/src/core/crypto/index.ts b/src/core/crypto/index.ts index 352ac8a31..9859f6043 100644 --- a/src/core/crypto/index.ts +++ b/src/core/crypto/index.ts @@ -5,6 +5,7 @@ export * from "./abstraction"; export * from "./ed25519"; export * from "./ephemeral"; export * from "./federatedKeyless"; +export * from "./keystore"; export * from "./hdKey"; export * from "./keyless"; export * from "./multiEd25519"; diff --git a/src/core/crypto/keystore.ts b/src/core/crypto/keystore.ts new file mode 100644 index 000000000..04595e8a7 --- /dev/null +++ b/src/core/crypto/keystore.ts @@ -0,0 +1,385 @@ +// 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 industry-standard cryptography: + * + * - KDF: scrypt (default) or PBKDF2-HMAC-SHA256 + * - Cipher: AES-128-CTR + * - MAC: SHA-256 + * + * 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 } from "./ed25519"; +import { Secp256k1PrivateKey } from "./secp256k1"; +import { Secp256r1PrivateKey } from "./secp256r1"; +import { PrivateKeyVariants } from "../../types"; + +/** + * Union type of all private key types supported by the Aptos Keystore. + */ +export type KeystorePrivateKey = Ed25519PrivateKey | Secp256k1PrivateKey | Secp256r1PrivateKey; + +// region Types + +/** + * 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; +} + +/** + * The Aptos Keystore JSON structure for encrypted private key storage. + * + * This format is based on the Ethereum Web3 Secret Storage Definition (v3), + * with additions for Aptos-specific key types. It is designed to be portable + * across all Aptos SDK implementations. + * + * @example + * ```json + * { + * "version": 1, + * "id": "a7b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", + * "key_type": "ed25519", + * "address": "0x1234...abcd", + * "crypto": { + * "cipher": "aes-128-ctr", + * "cipherparams": { "iv": "..." }, + * "ciphertext": "...", + * "kdf": "scrypt", + * "kdfparams": { "n": 131072, "r": 8, "p": 1, "dklen": 32, "salt": "..." }, + * "mac": "..." + * } + * } + * ``` + */ +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-128-ctr"; + /** Cipher parameters. */ + cipherparams: { + /** Hex-encoded initialization vector (no 0x prefix). */ + iv: string; + }; + /** Hex-encoded encrypted private key (no 0x prefix). */ + ciphertext: string; + /** Key derivation function identifier. */ + kdf: "scrypt" | "pbkdf2"; + /** KDF-specific parameters. */ + kdfparams: ScryptKdfParams | Pbkdf2KdfParams; + /** Hex-encoded MAC for password verification: SHA-256(DK[16..31] ++ ciphertext) (no 0x prefix). */ + mac: string; + }; +} + +/** + * Options for customizing keystore encryption. + */ +export interface KeystoreEncryptOptions { + /** KDF to use. Defaults to "scrypt". */ + kdf?: "scrypt" | "pbkdf2"; + /** 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 IV_LENGTH = 16; +const SALT_LENGTH = 32; + +function passwordToBytes(password: string | Uint8Array): Uint8Array { + if (typeof password === "string") { + return new TextEncoder().encode(password); + } + return password; +} + +function concat(a: Uint8Array, b: Uint8Array): Uint8Array { + const result = new Uint8Array(a.length + b.length); + result.set(a, 0); + result.set(b, a.length); + return result; +} + +function computeMac(derivedKey: Uint8Array, ciphertext: Uint8Array): Uint8Array { + const macBody = concat(derivedKey.slice(16, 32), ciphertext); + return sha256(macBody); +} + +function deriveKey( + password: Uint8Array, + kdf: "scrypt" | "pbkdf2", + kdfparams: ScryptKdfParams | Pbkdf2KdfParams, +): Uint8Array { + const salt = hexToBytes(kdfparams.salt); + + 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 aesCtr128Encrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Promise { + const cryptoKey = await globalThis.crypto.subtle.importKey("raw", toArrayBuffer(key), { name: "AES-CTR" }, false, [ + "encrypt", + ]); + const result = await globalThis.crypto.subtle.encrypt( + { name: "AES-CTR", counter: toArrayBuffer(iv), length: 128 }, + cryptoKey, + toArrayBuffer(plaintext), + ); + return new Uint8Array(result); +} + +async function aesCtr128Decrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Promise { + const cryptoKey = await globalThis.crypto.subtle.importKey("raw", toArrayBuffer(key), { name: "AES-CTR" }, false, [ + "decrypt", + ]); + const result = await globalThis.crypto.subtle.decrypt( + { name: "AES-CTR", counter: toArrayBuffer(iv), length: 128 }, + cryptoKey, + toArrayBuffer(ciphertext), + ); + return new Uint8Array(result); +} + +function generateUUID(): string { + return globalThis.crypto.randomUUID(); +} + +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). + * + * @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, encryptKeystore } from "@aptos-labs/ts-sdk"; + * + * const privateKey = Ed25519PrivateKey.generate(); + * const keystore = await encryptKeystore({ + * privateKey, + * password: "my-secure-password", + * }); + * console.log(JSON.stringify(keystore, null, 2)); + * ``` + */ +export async function encryptKeystore(args: { + privateKey: KeystorePrivateKey; + password: string | Uint8Array; + options?: KeystoreEncryptOptions; +}): Promise { + const { privateKey, password, options = {} } = args; + const { kdf = "scrypt", scryptN = 131072, scryptR = 8, scryptP = 1, pbkdf2C = 262144, address } = options; + + const passwordBytes = passwordToBytes(password); + const salt = randomBytes(SALT_LENGTH); + const iv = randomBytes(IV_LENGTH); + const keyType = getKeyType(privateKey); + const plaintext = privateKey.toUint8Array(); + + let kdfparams: ScryptKdfParams | Pbkdf2KdfParams; + 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 = deriveKey(passwordBytes, kdf, kdfparams); + const encryptionKey = dk.slice(0, 16); + const ciphertext = await aesCtr128Encrypt(encryptionKey, iv, plaintext); + const mac = computeMac(dk, ciphertext); + + const keystore: AptosKeyStore = { + version: 1, + id: generateUUID(), + key_type: keyType, + crypto: { + cipher: "aes-128-ctr", + cipherparams: { iv: bytesToHex(iv) }, + ciphertext: bytesToHex(ciphertext), + kdf, + kdfparams, + mac: bytesToHex(mac), + }, + }; + + if (address !== undefined) { + keystore.address = address; + } + + return keystore; +} + +/** + * Decrypt an Aptos Keystore to recover the private key. + * + * Verifies the MAC to ensure the password is correct before decrypting. + * Returns the appropriate private key type (Ed25519, Secp256k1, or Secp256r1) + * based on the `key_type` field in the keystore. + * + * @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 (MAC verification fails). + * @throws Error if the keystore format is invalid. + * + * @example + * ```typescript + * import { decryptKeystore } from "@aptos-labs/ts-sdk"; + * + * 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-128-ctr") { + throw new Error(`Unsupported cipher: ${cryptoData.cipher}`); + } + + const passwordBytes = passwordToBytes(password); + const dk = deriveKey(passwordBytes, cryptoData.kdf, cryptoData.kdfparams); + const ciphertext = hexToBytes(cryptoData.ciphertext); + + const expectedMac = computeMac(dk, ciphertext); + const storedMac = hexToBytes(cryptoData.mac); + + if (bytesToHex(expectedMac) !== bytesToHex(storedMac)) { + throw new Error("Invalid password: MAC verification failed"); + } + + const encryptionKey = dk.slice(0, 16); + const iv = hexToBytes(cryptoData.cipherparams.iv); + const plaintext = await aesCtr128Decrypt(encryptionKey, iv, ciphertext); + + return createPrivateKey(plaintext, keystore.key_type); +} + +// endregion diff --git a/tests/unit/keystore.test.ts b/tests/unit/keystore.test.ts new file mode 100644 index 000000000..f794cd41b --- /dev/null +++ b/tests/unit/keystore.test.ts @@ -0,0 +1,386 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import { + Ed25519PrivateKey, + Secp256k1PrivateKey, + Secp256r1PrivateKey, + encryptKeystore, + decryptKeystore, + PrivateKeyVariants, +} from "../../src"; +import type { AptosKeyStore } from "../../src"; + +describe("AptosKeystore", () => { + const TEST_PASSWORD = "test-password-123"; + const FAST_SCRYPT_OPTIONS = { scryptN: 1024, scryptR: 8, scryptP: 1 } as const; + const FAST_PBKDF2_OPTIONS = { kdf: "pbkdf2" as const, pbkdf2C: 1024 }; + + describe("encryptKeystore", () => { + it("should encrypt an Ed25519 private key with scrypt", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_SCRYPT_OPTIONS, + }); + + expect(keystore.version).toBe(1); + expect(keystore.key_type).toBe(PrivateKeyVariants.Ed25519); + expect(keystore.crypto.cipher).toBe("aes-128-ctr"); + expect(keystore.crypto.kdf).toBe("scrypt"); + expect(keystore.id).toBeDefined(); + expect(keystore.crypto.ciphertext).toBeDefined(); + expect(keystore.crypto.mac).toBeDefined(); + expect(keystore.crypto.cipherparams.iv).toBeDefined(); + }); + + it("should encrypt an Ed25519 private key with pbkdf2", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_PBKDF2_OPTIONS, + }); + + expect(keystore.version).toBe(1); + expect(keystore.key_type).toBe(PrivateKeyVariants.Ed25519); + 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_SCRYPT_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_SCRYPT_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_SCRYPT_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_SCRYPT_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_SCRYPT_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_SCRYPT_OPTIONS, + }); + const keystore2 = await encryptKeystore({ + privateKey, + password: "password-two", + options: FAST_SCRYPT_OPTIONS, + }); + + expect(keystore1.crypto.ciphertext).not.toBe(keystore2.crypto.ciphertext); + expect(keystore1.crypto.mac).not.toBe(keystore2.crypto.mac); + }); + + it("should produce unique IDs for each keystore", async () => { + const privateKey = Ed25519PrivateKey.generate(); + + const keystore1 = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_SCRYPT_OPTIONS, + }); + const keystore2 = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_SCRYPT_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_SCRYPT_OPTIONS, + }); + + const json = JSON.stringify(keystore); + const parsed = JSON.parse(json) as AptosKeyStore; + expect(parsed.version).toBe(1); + expect(parsed.crypto.cipher).toBe("aes-128-ctr"); + }); + }); + + describe("decryptKeystore", () => { + 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_SCRYPT_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_SCRYPT_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_SCRYPT_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_SCRYPT_OPTIONS, + }); + + await expect(decryptKeystore({ keystore, password: "wrong-password" })).rejects.toThrow( + "Invalid password: MAC verification failed", + ); + }); + + it("should throw on unsupported version", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_SCRYPT_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_SCRYPT_OPTIONS, + }); + + const modified = { + ...keystore, + crypto: { ...keystore.crypto, cipher: "aes-256-cbc" as any }, + }; + await expect(decryptKeystore({ keystore: modified, password: TEST_PASSWORD })).rejects.toThrow( + "Unsupported cipher: aes-256-cbc", + ); + }); + + it("should throw on tampered ciphertext", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_SCRYPT_OPTIONS, + }); + + const tamperedCiphertext = keystore.crypto.ciphertext.replace(/[0-9a-f]/, "0"); + if (tamperedCiphertext === keystore.crypto.ciphertext) { + 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: MAC verification failed", + ); + } else { + const modified = { + ...keystore, + crypto: { ...keystore.crypto, ciphertext: tamperedCiphertext }, + }; + await expect(decryptKeystore({ keystore: modified, password: TEST_PASSWORD })).rejects.toThrow( + "Invalid password: MAC verification failed", + ); + } + }); + }); + + describe("cross-KDF compatibility", () => { + it("should produce different keystore structures for scrypt vs pbkdf2", async () => { + const privateKey = Ed25519PrivateKey.generate(); + + 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(scryptKs.crypto.kdf).toBe("scrypt"); + expect(pbkdf2Ks.crypto.kdf).toBe("pbkdf2"); + + const scryptDecrypted = await decryptKeystore({ keystore: scryptKs, password: TEST_PASSWORD }); + const pbkdf2Decrypted = await decryptKeystore({ keystore: pbkdf2Ks, password: TEST_PASSWORD }); + + expect(scryptDecrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + expect(pbkdf2Decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + }); + }); + + describe("known test vector", () => { + it("should decrypt a known keystore correctly", async () => { + const knownPrivateKey = Ed25519PrivateKey.generate(); + const knownPassword = "aptos-keystore-test-vector"; + + const keystore = await encryptKeystore({ + privateKey: knownPrivateKey, + password: knownPassword, + options: { ...FAST_SCRYPT_OPTIONS, address: "0x1" }, + }); + + expect(keystore.address).toBe("0x1"); + expect(keystore.key_type).toBe("ed25519"); + + const recovered = await decryptKeystore({ keystore, password: knownPassword }); + expect(recovered.toUint8Array()).toEqual(knownPrivateKey.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_SCRYPT_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_SCRYPT_OPTIONS, + }); + + const decrypted = (await decryptKeystore({ keystore, password: TEST_PASSWORD })) as Ed25519PrivateKey; + const signature = decrypted.sign(message); + + expect(privateKey.publicKey().verifySignature({ message, signature })).toBe(true); + }); + }); +}); From 0bfa293800bd5c89f145c079c790dceeabd405c3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 20 Mar 2026 02:22:36 +0000 Subject: [PATCH 2/5] refactor: upgrade keystore to AES-256-GCM + Argon2id - Switch cipher from AES-128-CTR to AES-256-GCM authenticated encryption - Eliminates separate MAC field; GCM auth tag handles both integrity and password verification - Uses full 32-byte derived key (256-bit) instead of 16-byte (128-bit) - 12-byte GCM nonce + 16-byte auth tag stored in cipherparams - Add Argon2id as the default KDF (via hash-wasm, WASM-based) - Defaults: iterations=3, parallelism=4, memorySize=65536 KiB (64 MiB) - Winner of the Password Hashing Competition, OWASP recommended - Superior GPU/ASIC resistance compared to scrypt - Retain scrypt and PBKDF2 as alternative KDFs for compatibility - Update all 26 tests for new cipher and KDF parameters Co-authored-by: Greg Nazario --- CHANGELOG.md | 7 +- package.json | 1 + pnpm-lock.yaml | 8 ++ src/core/crypto/keystore.ts | 212 +++++++++++++++++++++++------------- tests/unit/keystore.test.ts | 186 ++++++++++++++++++------------- 5 files changed, 261 insertions(+), 153 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 014118ad6..d94cd3684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,11 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T ## Added -- Add Aptos Keystore: encrypted private key storage standard based on Ethereum's Web3 Secret Storage Definition (keystore v3) +- Add Aptos Keystore: encrypted private key storage standard based on Ethereum's Web3 Secret Storage Definition - Supports all Aptos key types: Ed25519, Secp256k1, Secp256r1 - - Password-based or key-file-based encryption using scrypt (default) or PBKDF2-HMAC-SHA256 KDFs - - AES-128-CTR cipher with SHA-256 MAC for password verification + - AES-256-GCM authenticated encryption (no separate MAC needed) + - Argon2id KDF by default (via `hash-wasm`), with scrypt and PBKDF2-HMAC-SHA256 as alternatives + - Password-based or key-file-based encryption - Portable JSON format designed for cross-SDK compatibility (TypeScript, Rust, Python, Go, etc.) - New exports: `encryptKeystore`, `decryptKeystore`, `AptosKeyStore`, `KeystorePrivateKey`, `KeystoreEncryptOptions` diff --git a/package.json b/package.json index 1376efb03..acc2925bd 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@scure/bip32": "^1.4.0", "@scure/bip39": "^1.3.0", "eventemitter3": "^5.0.4", + "hash-wasm": "^4.12.0", "js-base64": "^3.7.7", "jwt-decode": "^4.0.0", "poseidon-lite": "^0.2.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 562b8a7c5..d11119d88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,6 +36,9 @@ importers: eventemitter3: specifier: ^5.0.4 version: 5.0.4 + hash-wasm: + specifier: ^4.12.0 + version: 4.12.0 js-base64: specifier: ^3.7.7 version: 3.7.8 @@ -2002,6 +2005,9 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + hash-wasm@4.12.0: + resolution: {integrity: sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==} + header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} @@ -5286,6 +5292,8 @@ snapshots: has-flag@4.0.0: {} + hash-wasm@4.12.0: {} + header-case@2.0.4: dependencies: capital-case: 1.0.4 diff --git a/src/core/crypto/keystore.ts b/src/core/crypto/keystore.ts index 04595e8a7..1ad1626f1 100644 --- a/src/core/crypto/keystore.ts +++ b/src/core/crypto/keystore.ts @@ -6,11 +6,14 @@ * * 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 industry-standard cryptography: + * private keys with a password or key file using modern cryptography: * - * - KDF: scrypt (default) or PBKDF2-HMAC-SHA256 - * - Cipher: AES-128-CTR - * - MAC: SHA-256 + * - KDF: Argon2id (default), scrypt, 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.). * @@ -21,6 +24,7 @@ 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 { argon2id as hashWasmArgon2id } from "hash-wasm"; import { Ed25519PrivateKey } from "./ed25519"; import { Secp256k1PrivateKey } from "./secp256k1"; import { Secp256r1PrivateKey } from "./secp256r1"; @@ -33,6 +37,22 @@ export type KeystorePrivateKey = Ed25519PrivateKey | Secp256k1PrivateKey | Secp2 // 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. */ @@ -63,12 +83,17 @@ export interface Pbkdf2KdfParams { 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. * - * This format is based on the Ethereum Web3 Secret Storage Definition (v3), - * with additions for Aptos-specific key types. It is designed to be portable - * across all Aptos SDK implementations. + * 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 @@ -78,12 +103,11 @@ export interface Pbkdf2KdfParams { * "key_type": "ed25519", * "address": "0x1234...abcd", * "crypto": { - * "cipher": "aes-128-ctr", - * "cipherparams": { "iv": "..." }, + * "cipher": "aes-256-gcm", + * "cipherparams": { "iv": "...", "tag": "..." }, * "ciphertext": "...", - * "kdf": "scrypt", - * "kdfparams": { "n": 131072, "r": 8, "p": 1, "dklen": 32, "salt": "..." }, - * "mac": "..." + * "kdf": "argon2id", + * "kdfparams": { "iterations": 3, "parallelism": 4, "memorySize": 65536, "dklen": 32, "salt": "..." } * } * } * ``` @@ -100,20 +124,20 @@ export interface AptosKeyStore { /** Cryptographic parameters and encrypted data. */ crypto: { /** Symmetric cipher algorithm. */ - cipher: "aes-128-ctr"; + cipher: "aes-256-gcm"; /** Cipher parameters. */ cipherparams: { - /** Hex-encoded initialization vector (no 0x prefix). */ + /** 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: "scrypt" | "pbkdf2"; + kdf: KeystoreKdf; /** KDF-specific parameters. */ - kdfparams: ScryptKdfParams | Pbkdf2KdfParams; - /** Hex-encoded MAC for password verification: SHA-256(DK[16..31] ++ ciphertext) (no 0x prefix). */ - mac: string; + kdfparams: KeystoreKdfParams; }; } @@ -121,8 +145,14 @@ export interface AptosKeyStore { * Options for customizing keystore encryption. */ export interface KeystoreEncryptOptions { - /** KDF to use. Defaults to "scrypt". */ - kdf?: "scrypt" | "pbkdf2"; + /** KDF to use. Defaults to "argon2id". */ + 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. */ @@ -140,7 +170,8 @@ export interface KeystoreEncryptOptions { // region Internal helpers const DKLEN = 32; -const IV_LENGTH = 16; +const GCM_IV_LENGTH = 12; +const GCM_TAG_LENGTH = 16; const SALT_LENGTH = 32; function passwordToBytes(password: string | Uint8Array): Uint8Array { @@ -150,25 +181,22 @@ function passwordToBytes(password: string | Uint8Array): Uint8Array { return password; } -function concat(a: Uint8Array, b: Uint8Array): Uint8Array { - const result = new Uint8Array(a.length + b.length); - result.set(a, 0); - result.set(b, a.length); - return result; -} - -function computeMac(derivedKey: Uint8Array, ciphertext: Uint8Array): Uint8Array { - const macBody = concat(derivedKey.slice(16, 32), ciphertext); - return sha256(macBody); -} - -function deriveKey( - password: Uint8Array, - kdf: "scrypt" | "pbkdf2", - kdfparams: ScryptKdfParams | Pbkdf2KdfParams, -): Uint8Array { +async function deriveKey(password: Uint8Array, kdf: KeystoreKdf, kdfparams: KeystoreKdfParams): Promise { const salt = hexToBytes(kdfparams.salt); + if (kdf === "argon2id") { + const params = kdfparams as Argon2idKdfParams; + return hashWasmArgon2id({ + 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, { @@ -192,34 +220,46 @@ function toArrayBuffer(data: Uint8Array): ArrayBuffer { return buf; } -async function aesCtr128Encrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Promise { - const cryptoKey = await globalThis.crypto.subtle.importKey("raw", toArrayBuffer(key), { name: "AES-CTR" }, false, [ +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-CTR", counter: toArrayBuffer(iv), length: 128 }, + { name: "AES-GCM", iv: toArrayBuffer(iv), tagLength: GCM_TAG_LENGTH * 8 }, cryptoKey, toArrayBuffer(plaintext), ); - return new Uint8Array(result); + 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 aesCtr128Decrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Promise { - const cryptoKey = await globalThis.crypto.subtle.importKey("raw", toArrayBuffer(key), { name: "AES-CTR" }, false, [ +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-CTR", counter: toArrayBuffer(iv), length: 128 }, + { name: "AES-GCM", iv: toArrayBuffer(iv), tagLength: GCM_TAG_LENGTH * 8 }, cryptoKey, - toArrayBuffer(ciphertext), + toArrayBuffer(combined), ); return new Uint8Array(result); } -function generateUUID(): string { - return globalThis.crypto.randomUUID(); -} - function getKeyType(privateKey: KeystorePrivateKey): PrivateKeyVariants { if (privateKey instanceof Ed25519PrivateKey) return PrivateKeyVariants.Ed25519; if (privateKey instanceof Secp256k1PrivateKey) return PrivateKeyVariants.Secp256k1; @@ -250,6 +290,8 @@ function createPrivateKey(bytes: Uint8Array, keyType: PrivateKeyVariants): Keyst * 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 with Argon2id key derivation by default. + * * @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). @@ -273,16 +315,34 @@ export async function encryptKeystore(args: { options?: KeystoreEncryptOptions; }): Promise { const { privateKey, password, options = {} } = args; - const { kdf = "scrypt", scryptN = 131072, scryptR = 8, scryptP = 1, pbkdf2C = 262144, address } = options; + const { + kdf = "argon2id", + 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(IV_LENGTH); + const iv = randomBytes(GCM_IV_LENGTH); const keyType = getKeyType(privateKey); const plaintext = privateKey.toUint8Array(); - let kdfparams: ScryptKdfParams | Pbkdf2KdfParams; - if (kdf === "scrypt") { + 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, @@ -299,22 +359,22 @@ export async function encryptKeystore(args: { }; } - const dk = deriveKey(passwordBytes, kdf, kdfparams); - const encryptionKey = dk.slice(0, 16); - const ciphertext = await aesCtr128Encrypt(encryptionKey, iv, plaintext); - const mac = computeMac(dk, ciphertext); + const dk = await deriveKey(passwordBytes, kdf, kdfparams); + const { ciphertext, tag } = await aes256GcmEncrypt(dk, iv, plaintext); const keystore: AptosKeyStore = { version: 1, - id: generateUUID(), + id: globalThis.crypto.randomUUID(), key_type: keyType, crypto: { - cipher: "aes-128-ctr", - cipherparams: { iv: bytesToHex(iv) }, + cipher: "aes-256-gcm", + cipherparams: { + iv: bytesToHex(iv), + tag: bytesToHex(tag), + }, ciphertext: bytesToHex(ciphertext), kdf, kdfparams, - mac: bytesToHex(mac), }, }; @@ -328,14 +388,14 @@ export async function encryptKeystore(args: { /** * Decrypt an Aptos Keystore to recover the private key. * - * Verifies the MAC to ensure the password is correct before decrypting. - * Returns the appropriate private key type (Ed25519, Secp256k1, or Secp256r1) - * based on the `key_type` field in the keystore. + * 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 (MAC verification fails). + * @throws Error if the password is incorrect (GCM authentication fails). * @throws Error if the keystore format is invalid. * * @example @@ -360,25 +420,23 @@ export async function decryptKeystore(args: { } const { crypto: cryptoData } = keystore; - if (cryptoData.cipher !== "aes-128-ctr") { + if (cryptoData.cipher !== "aes-256-gcm") { throw new Error(`Unsupported cipher: ${cryptoData.cipher}`); } const passwordBytes = passwordToBytes(password); - const dk = deriveKey(passwordBytes, cryptoData.kdf, cryptoData.kdfparams); + 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); - const expectedMac = computeMac(dk, ciphertext); - const storedMac = hexToBytes(cryptoData.mac); - - if (bytesToHex(expectedMac) !== bytesToHex(storedMac)) { - throw new Error("Invalid password: MAC verification failed"); + let plaintext: Uint8Array; + try { + plaintext = await aes256GcmDecrypt(dk, iv, ciphertext, tag); + } catch { + throw new Error("Invalid password: decryption failed (GCM authentication)"); } - const encryptionKey = dk.slice(0, 16); - const iv = hexToBytes(cryptoData.cipherparams.iv); - const plaintext = await aesCtr128Decrypt(encryptionKey, iv, ciphertext); - return createPrivateKey(plaintext, keystore.key_type); } diff --git a/tests/unit/keystore.test.ts b/tests/unit/keystore.test.ts index f794cd41b..9948cdea1 100644 --- a/tests/unit/keystore.test.ts +++ b/tests/unit/keystore.test.ts @@ -13,26 +13,56 @@ import type { AptosKeyStore } from "../../src"; describe("AptosKeystore", () => { const TEST_PASSWORD = "test-password-123"; - const FAST_SCRYPT_OPTIONS = { scryptN: 1024, scryptR: 8, scryptP: 1 } as const; + const FAST_ARGON2_OPTIONS = { argon2Iterations: 2, argon2Parallelism: 1, argon2MemorySize: 1024 } as const; + 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 encrypt an Ed25519 private key with scrypt", async () => { + it("should encrypt an Ed25519 private key with argon2id (default)", async () => { const privateKey = Ed25519PrivateKey.generate(); const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore.version).toBe(1); expect(keystore.key_type).toBe(PrivateKeyVariants.Ed25519); - expect(keystore.crypto.cipher).toBe("aes-128-ctr"); - expect(keystore.crypto.kdf).toBe("scrypt"); + 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.mac).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 use argon2id as default KDF when no kdf option specified", async () => { + const privateKey = Ed25519PrivateKey.generate(); + const keystore = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); + + expect(keystore.crypto.kdf).toBe("argon2id"); + const params = keystore.crypto.kdfparams as { iterations: number; parallelism: number; memorySize: number }; + expect(params.iterations).toBe(2); + expect(params.parallelism).toBe(1); + expect(params.memorySize).toBe(1024); + }); + + it("should encrypt an Ed25519 private key with scrypt", 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 an Ed25519 private key with pbkdf2", async () => { @@ -43,8 +73,6 @@ describe("AptosKeystore", () => { options: FAST_PBKDF2_OPTIONS, }); - expect(keystore.version).toBe(1); - expect(keystore.key_type).toBe(PrivateKeyVariants.Ed25519); expect(keystore.crypto.kdf).toBe("pbkdf2"); const params = keystore.crypto.kdfparams as { prf: string }; expect(params.prf).toBe("hmac-sha256"); @@ -55,7 +83,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore.key_type).toBe(PrivateKeyVariants.Secp256k1); @@ -66,7 +94,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore.key_type).toBe(PrivateKeyVariants.Secp256r1); @@ -78,7 +106,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: { ...FAST_SCRYPT_OPTIONS, address: testAddress }, + options: { ...FAST_ARGON2_OPTIONS, address: testAddress }, }); expect(keystore.address).toBe(testAddress); @@ -89,7 +117,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore.address).toBeUndefined(); @@ -103,7 +131,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: keyFileBytes, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = await decryptKeystore({ @@ -120,16 +148,15 @@ describe("AptosKeystore", () => { const keystore1 = await encryptKeystore({ privateKey, password: "password-one", - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const keystore2 = await encryptKeystore({ privateKey, password: "password-two", - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore1.crypto.ciphertext).not.toBe(keystore2.crypto.ciphertext); - expect(keystore1.crypto.mac).not.toBe(keystore2.crypto.mac); }); it("should produce unique IDs for each keystore", async () => { @@ -138,12 +165,12 @@ describe("AptosKeystore", () => { const keystore1 = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const keystore2 = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore1.id).not.toBe(keystore2.id); @@ -154,17 +181,30 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + 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-128-ctr"); + 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({ @@ -196,7 +236,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -209,7 +249,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -222,7 +262,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const json = JSON.stringify(keystore); @@ -235,11 +275,11 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); await expect(decryptKeystore({ keystore, password: "wrong-password" })).rejects.toThrow( - "Invalid password: MAC verification failed", + "Invalid password: decryption failed (GCM authentication)", ); }); @@ -248,7 +288,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const modified = { ...keystore, version: 99 as any }; @@ -262,53 +302,69 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const modified = { ...keystore, - crypto: { ...keystore.crypto, cipher: "aes-256-cbc" as any }, + crypto: { ...keystore.crypto, cipher: "aes-128-ctr" as any }, }; await expect(decryptKeystore({ keystore: modified, password: TEST_PASSWORD })).rejects.toThrow( - "Unsupported cipher: aes-256-cbc", + "Unsupported cipher: aes-128-ctr", ); }); - it("should throw on tampered ciphertext", async () => { + it("should throw on tampered ciphertext (GCM detects tampering)", async () => { const privateKey = Ed25519PrivateKey.generate(); const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); - const tamperedCiphertext = keystore.crypto.ciphertext.replace(/[0-9a-f]/, "0"); - if (tamperedCiphertext === keystore.crypto.ciphertext) { - 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: MAC verification failed", - ); - } else { - const modified = { - ...keystore, - crypto: { ...keystore.crypto, ciphertext: tamperedCiphertext }, - }; - await expect(decryptKeystore({ keystore: modified, password: TEST_PASSWORD })).rejects.toThrow( - "Invalid password: MAC verification failed", - ); - } + 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("should produce different keystore structures for scrypt vs pbkdf2", async () => { + 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, @@ -320,36 +376,20 @@ describe("AptosKeystore", () => { 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("known test vector", () => { - it("should decrypt a known keystore correctly", async () => { - const knownPrivateKey = Ed25519PrivateKey.generate(); - const knownPassword = "aptos-keystore-test-vector"; - - const keystore = await encryptKeystore({ - privateKey: knownPrivateKey, - password: knownPassword, - options: { ...FAST_SCRYPT_OPTIONS, address: "0x1" }, - }); - - expect(keystore.address).toBe("0x1"); - expect(keystore.key_type).toBe("ed25519"); - - const recovered = await decryptKeystore({ keystore, password: knownPassword }); - expect(recovered.toUint8Array()).toEqual(knownPrivateKey.toUint8Array()); - }); - }); - describe("derived key verification", () => { it("the decrypted key should produce the same public key as the original", async () => { const privateKey = Ed25519PrivateKey.generate(); @@ -358,7 +398,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -374,7 +414,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = (await decryptKeystore({ keystore, password: TEST_PASSWORD })) as Ed25519PrivateKey; From cc1d128bae1ce04656efbd9f721b4fdde3772a9e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 20 Mar 2026 03:08:03 +0000 Subject: [PATCH 3/5] refactor: make hash-wasm optional, default KDF to scrypt Move hash-wasm from dependencies to an optional peerDependency to avoid bloating the bundle (~2.1 MB on disk, ~29 KB minified for argon2 alone) for users who don't need Argon2id. Changes: - hash-wasm is now an optional peer dependency, not a direct dependency - Default KDF changed from argon2id to scrypt (zero extra deps needed) - Argon2id loaded via dynamic import() only when explicitly requested - Clear error message if argon2id is requested without hash-wasm installed - Users who want argon2id: npm install hash-wasm, then { kdf: 'argon2id' } Bundle impact: zero for users who don't use argon2id. Co-authored-by: Greg Nazario --- CHANGELOG.md | 3 +- package.json | 9 ++- src/core/crypto/keystore.ts | 36 ++++++++--- tests/unit/keystore.test.ts | 115 +++++++++++++++++++----------------- 4 files changed, 97 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d94cd3684..d830c4a95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T - Add Aptos Keystore: encrypted private key storage standard based on Ethereum's Web3 Secret Storage Definition - Supports all Aptos key types: Ed25519, Secp256k1, Secp256r1 - AES-256-GCM authenticated encryption (no separate MAC needed) - - Argon2id KDF by default (via `hash-wasm`), with scrypt and PBKDF2-HMAC-SHA256 as alternatives + - scrypt KDF by default; Argon2id available via optional `hash-wasm` peer dependency + - 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.) - New exports: `encryptKeystore`, `decryptKeystore`, `AptosKeyStore`, `KeystorePrivateKey`, `KeystoreEncryptOptions` diff --git a/package.json b/package.json index acc2925bd..108870e69 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "@scure/bip32": "^1.4.0", "@scure/bip39": "^1.3.0", "eventemitter3": "^5.0.4", - "hash-wasm": "^4.12.0", "js-base64": "^3.7.7", "jwt-decode": "^4.0.0", "poseidon-lite": "^0.2.0" @@ -84,6 +83,14 @@ "typescript": "^5.9.3", "vitest": "^4.0.18" }, + "peerDependencies": { + "hash-wasm": "^4.12.0" + }, + "peerDependenciesMeta": { + "hash-wasm": { + "optional": true + } + }, "version": "6.2.0", "pnpm": { "overrides": { diff --git a/src/core/crypto/keystore.ts b/src/core/crypto/keystore.ts index 1ad1626f1..864ba76c4 100644 --- a/src/core/crypto/keystore.ts +++ b/src/core/crypto/keystore.ts @@ -8,7 +8,7 @@ * 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), scrypt, or PBKDF2-HMAC-SHA256 + * - KDF: scrypt (default), Argon2id (requires optional `hash-wasm` peer dependency), or PBKDF2-HMAC-SHA256 * - Cipher: AES-256-GCM (authenticated encryption) * * AES-256-GCM provides both confidentiality and integrity in a single operation, @@ -24,7 +24,6 @@ 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 { argon2id as hashWasmArgon2id } from "hash-wasm"; import { Ed25519PrivateKey } from "./ed25519"; import { Secp256k1PrivateKey } from "./secp256k1"; import { Secp256r1PrivateKey } from "./secp256r1"; @@ -106,8 +105,8 @@ export type KeystoreKdfParams = Argon2idKdfParams | ScryptKdfParams | Pbkdf2KdfP * "cipher": "aes-256-gcm", * "cipherparams": { "iv": "...", "tag": "..." }, * "ciphertext": "...", - * "kdf": "argon2id", - * "kdfparams": { "iterations": 3, "parallelism": 4, "memorySize": 65536, "dklen": 32, "salt": "..." } + * "kdf": "scrypt", + * "kdfparams": { "n": 131072, "r": 8, "p": 1, "dklen": 32, "salt": "..." } * } * } * ``` @@ -145,7 +144,7 @@ export interface AptosKeyStore { * Options for customizing keystore encryption. */ export interface KeystoreEncryptOptions { - /** KDF to use. Defaults to "argon2id". */ + /** KDF to use. Defaults to "scrypt". Use "argon2id" for stronger protection (requires `hash-wasm` peer dependency). */ kdf?: KeystoreKdf; /** Argon2id iterations (time cost). Defaults to 3. */ argon2Iterations?: number; @@ -181,12 +180,22 @@ function passwordToBytes(password: string | Uint8Array): Uint8Array { return password; } +async function loadArgon2id(): Promise { + try { + const mod = await import("hash-wasm"); + return mod.argon2id; + } catch { + throw new Error('Argon2id KDF requires the "hash-wasm" package. Install it with: npm install hash-wasm'); + } +} + 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 hashWasmArgon2id({ + return argon2id({ password, salt, iterations: params.iterations, @@ -290,7 +299,9 @@ function createPrivateKey(bytes: Uint8Array, keyType: PrivateKeyVariants): Keyst * 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 with Argon2id key derivation by default. + * Uses AES-256-GCM authenticated encryption with scrypt key derivation by default. + * For stronger password protection, use `kdf: "argon2id"` (requires the optional + * `hash-wasm` peer dependency: `npm install hash-wasm`). * * @param args.privateKey - The private key to encrypt. * @param args.password - Password string or key-file bytes used to derive the encryption key. @@ -301,12 +312,19 @@ function createPrivateKey(bytes: Uint8Array, keyType: PrivateKeyVariants): Keyst * ```typescript * import { Ed25519PrivateKey, encryptKeystore } from "@aptos-labs/ts-sdk"; * + * // Default (scrypt) — no extra dependencies * const privateKey = Ed25519PrivateKey.generate(); * const keystore = await encryptKeystore({ * privateKey, * password: "my-secure-password", * }); - * console.log(JSON.stringify(keystore, null, 2)); + * + * // Argon2id — requires: npm install hash-wasm + * const keystoreArgon2 = await encryptKeystore({ + * privateKey, + * password: "my-secure-password", + * options: { kdf: "argon2id" }, + * }); * ``` */ export async function encryptKeystore(args: { @@ -316,7 +334,7 @@ export async function encryptKeystore(args: { }): Promise { const { privateKey, password, options = {} } = args; const { - kdf = "argon2id", + kdf = "scrypt", argon2Iterations = 3, argon2Parallelism = 4, argon2MemorySize = 65536, diff --git a/tests/unit/keystore.test.ts b/tests/unit/keystore.test.ts index 9948cdea1..24de01167 100644 --- a/tests/unit/keystore.test.ts +++ b/tests/unit/keystore.test.ts @@ -13,23 +13,28 @@ import type { AptosKeyStore } from "../../src"; describe("AptosKeystore", () => { const TEST_PASSWORD = "test-password-123"; - const FAST_ARGON2_OPTIONS = { argon2Iterations: 2, argon2Parallelism: 1, argon2MemorySize: 1024 } as const; - const FAST_SCRYPT_OPTIONS = { kdf: "scrypt" as const, scryptN: 1024, scryptR: 8, scryptP: 1 }; + const FAST_SCRYPT_OPTIONS = { scryptN: 1024, scryptR: 8, scryptP: 1 } as const; const FAST_PBKDF2_OPTIONS = { kdf: "pbkdf2" as const, pbkdf2C: 1024 }; + const FAST_ARGON2_OPTIONS = { + kdf: "argon2id" as const, + argon2Iterations: 2, + argon2Parallelism: 1, + argon2MemorySize: 1024, + }; describe("encryptKeystore", () => { - it("should encrypt an Ed25519 private key with argon2id (default)", async () => { + it("should default to scrypt KDF with AES-256-GCM", async () => { const privateKey = Ed25519PrivateKey.generate(); const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); 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.crypto.kdf).toBe("scrypt"); expect(keystore.id).toBeDefined(); expect(keystore.crypto.ciphertext).toBeDefined(); expect(keystore.crypto.cipherparams.iv).toBeDefined(); @@ -38,44 +43,44 @@ describe("AptosKeystore", () => { expect(keystore.crypto.cipherparams.tag.length).toBe(32); // 16 bytes = 32 hex chars }); - it("should use argon2id as default KDF when no kdf option specified", async () => { + it("should use scrypt as default KDF when no kdf option specified", async () => { const privateKey = Ed25519PrivateKey.generate(); const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); - expect(keystore.crypto.kdf).toBe("argon2id"); - const params = keystore.crypto.kdfparams as { iterations: number; parallelism: number; memorySize: number }; - expect(params.iterations).toBe(2); - expect(params.parallelism).toBe(1); - expect(params.memorySize).toBe(1024); + expect(keystore.crypto.kdf).toBe("scrypt"); }); - it("should encrypt an Ed25519 private key with scrypt", async () => { + it("should encrypt with pbkdf2", async () => { const privateKey = Ed25519PrivateKey.generate(); const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_PBKDF2_OPTIONS, }); - expect(keystore.crypto.kdf).toBe("scrypt"); - expect(keystore.crypto.cipher).toBe("aes-256-gcm"); + expect(keystore.crypto.kdf).toBe("pbkdf2"); + const params = keystore.crypto.kdfparams as { prf: string }; + expect(params.prf).toBe("hmac-sha256"); }); - it("should encrypt an Ed25519 private key with pbkdf2", async () => { + it("should encrypt with argon2id when hash-wasm is available", async () => { const privateKey = Ed25519PrivateKey.generate(); const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_PBKDF2_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); - expect(keystore.crypto.kdf).toBe("pbkdf2"); - const params = keystore.crypto.kdfparams as { prf: string }; - expect(params.prf).toBe("hmac-sha256"); + expect(keystore.crypto.kdf).toBe("argon2id"); + expect(keystore.crypto.cipher).toBe("aes-256-gcm"); + const params = keystore.crypto.kdfparams as { iterations: number; parallelism: number; memorySize: number }; + expect(params.iterations).toBe(2); + expect(params.parallelism).toBe(1); + expect(params.memorySize).toBe(1024); }); it("should encrypt a Secp256k1 private key", async () => { @@ -83,7 +88,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); expect(keystore.key_type).toBe(PrivateKeyVariants.Secp256k1); @@ -94,7 +99,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); expect(keystore.key_type).toBe(PrivateKeyVariants.Secp256r1); @@ -106,7 +111,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: { ...FAST_ARGON2_OPTIONS, address: testAddress }, + options: { ...FAST_SCRYPT_OPTIONS, address: testAddress }, }); expect(keystore.address).toBe(testAddress); @@ -117,7 +122,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); expect(keystore.address).toBeUndefined(); @@ -131,7 +136,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: keyFileBytes, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const decrypted = await decryptKeystore({ @@ -148,12 +153,12 @@ describe("AptosKeystore", () => { const keystore1 = await encryptKeystore({ privateKey, password: "password-one", - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const keystore2 = await encryptKeystore({ privateKey, password: "password-two", - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); expect(keystore1.crypto.ciphertext).not.toBe(keystore2.crypto.ciphertext); @@ -165,12 +170,12 @@ describe("AptosKeystore", () => { const keystore1 = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const keystore2 = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); expect(keystore1.id).not.toBe(keystore2.id); @@ -181,7 +186,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const json = JSON.stringify(keystore); @@ -192,12 +197,12 @@ describe("AptosKeystore", () => { }); describe("decryptKeystore", () => { - it("should round-trip an Ed25519 private key with argon2id", async () => { + it("should round-trip an Ed25519 private key with scrypt (default)", async () => { const privateKey = Ed25519PrivateKey.generate(); const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -205,12 +210,12 @@ describe("AptosKeystore", () => { expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); }); - it("should round-trip an Ed25519 private key with scrypt", async () => { + 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_SCRYPT_OPTIONS, + options: FAST_PBKDF2_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -218,12 +223,12 @@ describe("AptosKeystore", () => { expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); }); - it("should round-trip an Ed25519 private key with pbkdf2", async () => { + 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_PBKDF2_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -236,7 +241,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -249,7 +254,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -262,7 +267,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const json = JSON.stringify(keystore); @@ -275,7 +280,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); await expect(decryptKeystore({ keystore, password: "wrong-password" })).rejects.toThrow( @@ -288,7 +293,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const modified = { ...keystore, version: 99 as any }; @@ -302,7 +307,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const modified = { @@ -319,7 +324,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const chars = keystore.crypto.ciphertext.split(""); @@ -338,7 +343,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const chars = keystore.crypto.cipherparams.tag.split(""); @@ -360,11 +365,6 @@ describe("AptosKeystore", () => { 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, @@ -375,18 +375,23 @@ describe("AptosKeystore", () => { password: TEST_PASSWORD, options: FAST_PBKDF2_OPTIONS, }); + const argon2Ks = await encryptKeystore({ + privateKey, + password: TEST_PASSWORD, + options: FAST_ARGON2_OPTIONS, + }); - expect(argon2Ks.crypto.kdf).toBe("argon2id"); expect(scryptKs.crypto.kdf).toBe("scrypt"); expect(pbkdf2Ks.crypto.kdf).toBe("pbkdf2"); + expect(argon2Ks.crypto.kdf).toBe("argon2id"); - 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 }); + const argon2Decrypted = await decryptKeystore({ keystore: argon2Ks, password: TEST_PASSWORD }); - expect(argon2Decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); expect(scryptDecrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); expect(pbkdf2Decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); + expect(argon2Decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); }); }); @@ -398,7 +403,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -414,7 +419,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const decrypted = (await decryptKeystore({ keystore, password: TEST_PASSWORD })) as Ed25519PrivateKey; From fe2a84efc1c14639317a17ab65c9fccc64f47c2c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 20 Mar 2026 03:34:47 +0000 Subject: [PATCH 4/5] fix: default to argon2id KDF, fall back to scrypt if hash-wasm unavailable The intended behavior is to always use Argon2id for password-based encryption. The KDF selection now works as: 1. If no KDF specified: try to load hash-wasm dynamically - If available: use argon2id (strongest option) - If not installed: fall back to scrypt (no extra deps needed) 2. If KDF explicitly specified: use that KDF - argon2id: requires hash-wasm, throws clear error if missing - scrypt/pbkdf2: always available, no extra deps This keeps hash-wasm as an optional peer dependency (zero bundle impact when not installed) while defaulting to the strongest KDF available. Co-authored-by: Greg Nazario --- CHANGELOG.md | 2 +- src/core/crypto/keystore.ts | 40 ++++++++++----- tests/unit/keystore.test.ts | 97 ++++++++++++++++--------------------- 3 files changed, 69 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d830c4a95..3e0eba847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T - Add Aptos Keystore: encrypted private key storage standard based on Ethereum's Web3 Secret Storage Definition - Supports all Aptos key types: Ed25519, Secp256k1, Secp256r1 - AES-256-GCM authenticated encryption (no separate MAC needed) - - scrypt KDF by default; Argon2id available via optional `hash-wasm` peer dependency + - Argon2id KDF by default when optional `hash-wasm` peer dependency 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.) diff --git a/src/core/crypto/keystore.ts b/src/core/crypto/keystore.ts index 864ba76c4..ac4bd897f 100644 --- a/src/core/crypto/keystore.ts +++ b/src/core/crypto/keystore.ts @@ -8,7 +8,7 @@ * adapted for Aptos key types. Supports encrypting Ed25519, Secp256k1, and Secp256r1 * private keys with a password or key file using modern cryptography: * - * - KDF: scrypt (default), Argon2id (requires optional `hash-wasm` peer dependency), or PBKDF2-HMAC-SHA256 + * - 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, @@ -105,8 +105,8 @@ export type KeystoreKdfParams = Argon2idKdfParams | ScryptKdfParams | Pbkdf2KdfP * "cipher": "aes-256-gcm", * "cipherparams": { "iv": "...", "tag": "..." }, * "ciphertext": "...", - * "kdf": "scrypt", - * "kdfparams": { "n": 131072, "r": 8, "p": 1, "dklen": 32, "salt": "..." } + * "kdf": "argon2id", + * "kdfparams": { "iterations": 3, "parallelism": 4, "memorySize": 65536, "dklen": 32, "salt": "..." } * } * } * ``` @@ -144,7 +144,7 @@ export interface AptosKeyStore { * Options for customizing keystore encryption. */ export interface KeystoreEncryptOptions { - /** KDF to use. Defaults to "scrypt". Use "argon2id" for stronger protection (requires `hash-wasm` peer dependency). */ + /** KDF to use. Defaults to "argon2id" if `hash-wasm` is installed, otherwise "scrypt". */ kdf?: KeystoreKdf; /** Argon2id iterations (time cost). Defaults to 3. */ argon2Iterations?: number; @@ -180,13 +180,26 @@ function passwordToBytes(password: string | Uint8Array): Uint8Array { return password; } -async function loadArgon2id(): Promise { +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 { @@ -299,9 +312,9 @@ function createPrivateKey(bytes: Uint8Array, keyType: PrivateKeyVariants): Keyst * 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 with scrypt key derivation by default. - * For stronger password protection, use `kdf: "argon2id"` (requires the optional - * `hash-wasm` peer dependency: `npm install hash-wasm`). + * 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. @@ -312,18 +325,18 @@ function createPrivateKey(bytes: Uint8Array, keyType: PrivateKeyVariants): Keyst * ```typescript * import { Ed25519PrivateKey, encryptKeystore } from "@aptos-labs/ts-sdk"; * - * // Default (scrypt) — no extra dependencies + * // Uses argon2id if hash-wasm is installed, scrypt otherwise * const privateKey = Ed25519PrivateKey.generate(); * const keystore = await encryptKeystore({ * privateKey, * password: "my-secure-password", * }); * - * // Argon2id — requires: npm install hash-wasm - * const keystoreArgon2 = await encryptKeystore({ + * // Explicitly request a KDF + * const keystoreScrypt = await encryptKeystore({ * privateKey, * password: "my-secure-password", - * options: { kdf: "argon2id" }, + * options: { kdf: "scrypt" }, * }); * ``` */ @@ -333,8 +346,9 @@ export async function encryptKeystore(args: { options?: KeystoreEncryptOptions; }): Promise { const { privateKey, password, options = {} } = args; + const defaultKdf = options.kdf ?? (await resolveDefaultKdf()); const { - kdf = "scrypt", + kdf = defaultKdf, argon2Iterations = 3, argon2Parallelism = 4, argon2MemorySize = 65536, diff --git a/tests/unit/keystore.test.ts b/tests/unit/keystore.test.ts index 24de01167..6c64f5f4d 100644 --- a/tests/unit/keystore.test.ts +++ b/tests/unit/keystore.test.ts @@ -13,28 +13,28 @@ import type { AptosKeyStore } from "../../src"; describe("AptosKeystore", () => { const TEST_PASSWORD = "test-password-123"; - const FAST_SCRYPT_OPTIONS = { scryptN: 1024, scryptR: 8, scryptP: 1 } as const; - const FAST_PBKDF2_OPTIONS = { kdf: "pbkdf2" as const, pbkdf2C: 1024 }; 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 scrypt KDF with AES-256-GCM", async () => { + it("should default to argon2id when hash-wasm is available", async () => { const privateKey = Ed25519PrivateKey.generate(); const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + 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("scrypt"); + expect(keystore.crypto.kdf).toBe("argon2id"); expect(keystore.id).toBeDefined(); expect(keystore.crypto.ciphertext).toBeDefined(); expect(keystore.crypto.cipherparams.iv).toBeDefined(); @@ -43,7 +43,7 @@ describe("AptosKeystore", () => { expect(keystore.crypto.cipherparams.tag.length).toBe(32); // 16 bytes = 32 hex chars }); - it("should use scrypt as default KDF when no kdf option specified", async () => { + it("should encrypt with scrypt when explicitly requested", async () => { const privateKey = Ed25519PrivateKey.generate(); const keystore = await encryptKeystore({ privateKey, @@ -52,6 +52,7 @@ describe("AptosKeystore", () => { }); expect(keystore.crypto.kdf).toBe("scrypt"); + expect(keystore.crypto.cipher).toBe("aes-256-gcm"); }); it("should encrypt with pbkdf2", async () => { @@ -67,28 +68,12 @@ describe("AptosKeystore", () => { expect(params.prf).toBe("hmac-sha256"); }); - it("should encrypt with argon2id when hash-wasm is available", async () => { - const privateKey = Ed25519PrivateKey.generate(); - const keystore = await encryptKeystore({ - privateKey, - password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, - }); - - expect(keystore.crypto.kdf).toBe("argon2id"); - expect(keystore.crypto.cipher).toBe("aes-256-gcm"); - const params = keystore.crypto.kdfparams as { iterations: number; parallelism: number; memorySize: number }; - expect(params.iterations).toBe(2); - expect(params.parallelism).toBe(1); - expect(params.memorySize).toBe(1024); - }); - it("should encrypt a Secp256k1 private key", async () => { const privateKey = Secp256k1PrivateKey.generate(); const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore.key_type).toBe(PrivateKeyVariants.Secp256k1); @@ -99,7 +84,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore.key_type).toBe(PrivateKeyVariants.Secp256r1); @@ -111,7 +96,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: { ...FAST_SCRYPT_OPTIONS, address: testAddress }, + options: { ...FAST_ARGON2_OPTIONS, address: testAddress }, }); expect(keystore.address).toBe(testAddress); @@ -122,7 +107,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore.address).toBeUndefined(); @@ -136,7 +121,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: keyFileBytes, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = await decryptKeystore({ @@ -153,12 +138,12 @@ describe("AptosKeystore", () => { const keystore1 = await encryptKeystore({ privateKey, password: "password-one", - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const keystore2 = await encryptKeystore({ privateKey, password: "password-two", - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore1.crypto.ciphertext).not.toBe(keystore2.crypto.ciphertext); @@ -170,12 +155,12 @@ describe("AptosKeystore", () => { const keystore1 = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const keystore2 = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); expect(keystore1.id).not.toBe(keystore2.id); @@ -186,7 +171,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const json = JSON.stringify(keystore); @@ -197,12 +182,12 @@ describe("AptosKeystore", () => { }); describe("decryptKeystore", () => { - it("should round-trip an Ed25519 private key with scrypt (default)", async () => { + 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_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -210,12 +195,12 @@ describe("AptosKeystore", () => { expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); }); - it("should round-trip an Ed25519 private key with pbkdf2", async () => { + 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_PBKDF2_OPTIONS, + options: FAST_SCRYPT_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -223,12 +208,12 @@ describe("AptosKeystore", () => { expect(decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); }); - it("should round-trip an Ed25519 private key with argon2id", async () => { + 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_ARGON2_OPTIONS, + options: FAST_PBKDF2_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -241,7 +226,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -254,7 +239,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -267,7 +252,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const json = JSON.stringify(keystore); @@ -280,7 +265,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); await expect(decryptKeystore({ keystore, password: "wrong-password" })).rejects.toThrow( @@ -293,7 +278,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const modified = { ...keystore, version: 99 as any }; @@ -307,7 +292,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const modified = { @@ -324,7 +309,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const chars = keystore.crypto.ciphertext.split(""); @@ -343,7 +328,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const chars = keystore.crypto.cipherparams.tag.split(""); @@ -365,6 +350,11 @@ describe("AptosKeystore", () => { 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, @@ -375,23 +365,18 @@ describe("AptosKeystore", () => { password: TEST_PASSWORD, options: FAST_PBKDF2_OPTIONS, }); - const argon2Ks = await encryptKeystore({ - privateKey, - password: TEST_PASSWORD, - options: FAST_ARGON2_OPTIONS, - }); + expect(argon2Ks.crypto.kdf).toBe("argon2id"); expect(scryptKs.crypto.kdf).toBe("scrypt"); expect(pbkdf2Ks.crypto.kdf).toBe("pbkdf2"); - expect(argon2Ks.crypto.kdf).toBe("argon2id"); + 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 }); - const argon2Decrypted = await decryptKeystore({ keystore: argon2Ks, password: TEST_PASSWORD }); + expect(argon2Decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); expect(scryptDecrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); expect(pbkdf2Decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); - expect(argon2Decrypted.toUint8Array()).toEqual(privateKey.toUint8Array()); }); }); @@ -403,7 +388,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = await decryptKeystore({ keystore, password: TEST_PASSWORD }); @@ -419,7 +404,7 @@ describe("AptosKeystore", () => { const keystore = await encryptKeystore({ privateKey, password: TEST_PASSWORD, - options: FAST_SCRYPT_OPTIONS, + options: FAST_ARGON2_OPTIONS, }); const decrypted = (await decryptKeystore({ keystore, password: TEST_PASSWORD })) as Ed25519PrivateKey; From 19e0dd8da8eca43774a040ff2969ad10d562efc4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 21 Mar 2026 04:37:43 +0000 Subject: [PATCH 5/5] refactor: extract keystore into standalone @aptos-labs/aptos-keystore package Move the encrypted keystore functionality out of the main ts-sdk into its own package at keystore/ to keep the SDK lean. New package: @aptos-labs/aptos-keystore - Peer-depends on @aptos-labs/ts-sdk ^6.2.0 - Optional peer dep on hash-wasm for Argon2id - Direct dep on @noble/hashes for scrypt/pbkdf2/sha256 - Own build (tsup), tests (vitest), and lint (biome) setup - Same API: encryptKeystore(), decryptKeystore() Main SDK changes: - Removed keystore.ts, its export, and hash-wasm peer dep - No new exports or dependencies added to ts-sdk - Zero impact on existing SDK users Co-authored-by: Greg Nazario --- CHANGELOG.md | 7 +- keystore/package.json | 63 + keystore/pnpm-lock.yaml | 1713 +++++++++++++++++ keystore/src/index.ts | 4 + {src/core/crypto => keystore/src}/keystore.ts | 10 +- .../unit => keystore/tests}/keystore.test.ts | 12 +- keystore/tsconfig.build.json | 6 + keystore/tsup.config.ts | 35 + keystore/vitest.config.ts | 15 + package.json | 8 - pnpm-lock.yaml | 8 - src/core/crypto/index.ts | 1 - 12 files changed, 1847 insertions(+), 35 deletions(-) create mode 100644 keystore/package.json create mode 100644 keystore/pnpm-lock.yaml create mode 100644 keystore/src/index.ts rename {src/core/crypto => keystore/src}/keystore.ts (97%) rename {tests/unit => keystore/tests}/keystore.test.ts (98%) create mode 100644 keystore/tsconfig.build.json create mode 100644 keystore/tsup.config.ts create mode 100644 keystore/vitest.config.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e0eba847..5c494e7c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,15 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T ## Added -- Add Aptos Keystore: encrypted private key storage standard based on Ethereum's Web3 Secret Storage Definition +- 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 optional `hash-wasm` peer dependency is installed, falls back to scrypt + - 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.) - - New exports: `encryptKeystore`, `decryptKeystore`, `AptosKeyStore`, `KeystorePrivateKey`, `KeystoreEncryptOptions` + - Exports: `encryptKeystore`, `decryptKeystore`, `AptosKeyStore`, `KeystorePrivateKey`, `KeystoreEncryptOptions` ## Fixed 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/src/core/crypto/keystore.ts b/keystore/src/keystore.ts similarity index 97% rename from src/core/crypto/keystore.ts rename to keystore/src/keystore.ts index ac4bd897f..9383f4e0a 100644 --- a/src/core/crypto/keystore.ts +++ b/keystore/src/keystore.ts @@ -24,10 +24,7 @@ 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 } from "./ed25519"; -import { Secp256k1PrivateKey } from "./secp256k1"; -import { Secp256r1PrivateKey } from "./secp256r1"; -import { PrivateKeyVariants } from "../../types"; +import { Ed25519PrivateKey, Secp256k1PrivateKey, Secp256r1PrivateKey, PrivateKeyVariants } from "@aptos-labs/ts-sdk"; /** * Union type of all private key types supported by the Aptos Keystore. @@ -323,7 +320,8 @@ function createPrivateKey(bytes: Uint8Array, keyType: PrivateKeyVariants): Keyst * * @example * ```typescript - * import { Ed25519PrivateKey, encryptKeystore } from "@aptos-labs/ts-sdk"; + * 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(); @@ -432,7 +430,7 @@ export async function encryptKeystore(args: { * * @example * ```typescript - * import { decryptKeystore } from "@aptos-labs/ts-sdk"; + * import { decryptKeystore } from "@aptos-labs/aptos-keystore"; * * const privateKey = await decryptKeystore({ * keystore: keystoreJson, diff --git a/tests/unit/keystore.test.ts b/keystore/tests/keystore.test.ts similarity index 98% rename from tests/unit/keystore.test.ts rename to keystore/tests/keystore.test.ts index 6c64f5f4d..30aaaac4a 100644 --- a/tests/unit/keystore.test.ts +++ b/keystore/tests/keystore.test.ts @@ -1,15 +1,9 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -import { - Ed25519PrivateKey, - Secp256k1PrivateKey, - Secp256r1PrivateKey, - encryptKeystore, - decryptKeystore, - PrivateKeyVariants, -} from "../../src"; -import type { AptosKeyStore } from "../../src"; +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"; 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"], + }, +}); diff --git a/package.json b/package.json index 108870e69..1376efb03 100644 --- a/package.json +++ b/package.json @@ -83,14 +83,6 @@ "typescript": "^5.9.3", "vitest": "^4.0.18" }, - "peerDependencies": { - "hash-wasm": "^4.12.0" - }, - "peerDependenciesMeta": { - "hash-wasm": { - "optional": true - } - }, "version": "6.2.0", "pnpm": { "overrides": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d11119d88..562b8a7c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,9 +36,6 @@ importers: eventemitter3: specifier: ^5.0.4 version: 5.0.4 - hash-wasm: - specifier: ^4.12.0 - version: 4.12.0 js-base64: specifier: ^3.7.7 version: 3.7.8 @@ -2005,9 +2002,6 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - hash-wasm@4.12.0: - resolution: {integrity: sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==} - header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} @@ -5292,8 +5286,6 @@ snapshots: has-flag@4.0.0: {} - hash-wasm@4.12.0: {} - header-case@2.0.4: dependencies: capital-case: 1.0.4 diff --git a/src/core/crypto/index.ts b/src/core/crypto/index.ts index 9859f6043..352ac8a31 100644 --- a/src/core/crypto/index.ts +++ b/src/core/crypto/index.ts @@ -5,7 +5,6 @@ export * from "./abstraction"; export * from "./ed25519"; export * from "./ephemeral"; export * from "./federatedKeyless"; -export * from "./keystore"; export * from "./hdKey"; export * from "./keyless"; export * from "./multiEd25519";