]> git.codecow.com Git - libnemo.git/commitdiff
Require wallet type as additional metadata to decrypt.
authorChris Duncan <chris@zoso.dev>
Fri, 8 Aug 2025 20:53:20 +0000 (13:53 -0700)
committerChris Duncan <chris@zoso.dev>
Fri, 8 Aug 2025 20:53:20 +0000 (13:53 -0700)
src/lib/safe.ts
src/lib/wallet.ts

index 8a9712431537db05a6e3ba6cbdbb48d6f1912c36..6aecb1e1e007782470ae8e8ba4ca2b3fcde229b7 100644 (file)
@@ -69,7 +69,7 @@ export class Safe {
                                                break
                                        }
                                        case 'unlock': {
-                                               result = await this.unlock(key, iv, encrypted)
+                                               result = await this.unlock(type, key, iv, encrypted)
                                                break
                                        }
                                        case 'update': {
@@ -209,8 +209,11 @@ export class Safe {
        /**
        * Decrypts the input and sets the seed and, if it is included, the mnemonic.
        */
-       static async unlock (key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise<NamedData<boolean>> {
+       static async unlock (type?: string, key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise<NamedData<boolean>> {
                try {
+                       if (type == null) {
+                               throw new TypeError('Wallet type is required')
+                       }
                        if (key == null) {
                                throw new TypeError('Wallet password is required')
                        }
@@ -220,7 +223,7 @@ export class Safe {
                        if (encrypted == null) {
                                throw new TypeError('Wallet encrypted data is required')
                        }
-                       const { seed, mnemonic } = await this.#decryptWallet(key, iv, encrypted)
+                       const { seed, mnemonic } = await this.#decryptWallet(type, key, iv, encrypted)
                        if (!(seed instanceof ArrayBuffer)) {
                                throw new TypeError('Invalid seed')
                        }
@@ -312,11 +315,21 @@ export class Safe {
                return await crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose])
        }
 
-       static async #decryptWallet (key: CryptoKey, iv: ArrayBuffer, encrypted: ArrayBuffer): Promise<NamedData<string | ArrayBuffer>> {
-               const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encrypted)
-               const decoded = JSON.parse(bytes.toUtf8(new Uint8Array(decrypted)))
-               const seed = hex.toBuffer(decoded.seed)
-               const mnemonic = decoded.mnemonic
+       static async #decryptWallet (type: string, key: CryptoKey, iv: ArrayBuffer, encrypted: ArrayBuffer): Promise<NamedData<string | ArrayBuffer>> {
+               let decrypted, decoded, parsed
+
+               const additionalData = utf8.toBytes(type)
+               decrypted = new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv, additionalData }, key, encrypted))
+               decoded = bytes.toUtf8(decrypted)
+               bytes.erase(decrypted)
+               decrypted = undefined
+
+               parsed = JSON.parse(decoded)
+               decoded = undefined
+
+               const seed = hex.toBuffer(parsed.seed)
+               const mnemonic = parsed.mnemonic
+               parsed = parsed.seed = parsed.mnemonic = undefined
                return { seed, mnemonic }
        }
 
@@ -342,6 +355,9 @@ export class Safe {
        }
 
        static async #encryptWallet (key: CryptoKey): Promise<NamedData<ArrayBuffer>> {
+               if (this.#type == null) {
+                       throw new Error('Invalid wallet type')
+               }
                if (this.#seed == null) {
                        throw new Error('Wallet seed not found')
                }
@@ -352,8 +368,9 @@ export class Safe {
 
                // restrict iv to 96 bits per GCM best practice
                const iv = crypto.getRandomValues(new Uint8Array(12)).buffer
+               const additionalData = utf8.toBytes(this.#type)
                const encoded = utf8.toBytes(JSON.stringify(data))
-               const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded)
+               const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv, additionalData }, key, encoded)
                return { iv, encrypted }
        }
 
index 354fdc417c859c8ecb2a0ddc551c5ef4f78c0c49..bb5c12a649ff536644cc213d69640b91a48eccef 100644 (file)
@@ -404,6 +404,7 @@ export class Wallet {
                        const { iv, salt, encrypted } = await Wallet.#get(this.#id)\r
                        const { isUnlocked } = await this.#safe.request<boolean>({\r
                                action: 'unlock',\r
+                               type: this.#type,\r
                                password: utf8.toBuffer(password),\r
                                iv,\r
                                keySalt: salt,\r