{"record":{"id":"ae70ea7b3954a836","repo":"FuelLabs/fuels-ts","slug":"invalid-credentials","errorCode":"INVALID_CREDENTIALS","errorMessage":"Invalid credentials.","messagePattern":"Invalid credentials\\.","errorType":"exception","errorClass":"FuelError","httpStatus":null,"severity":"error","filePath":"packages/crypto/src/browser/aes-ctr.ts","lineNumber":81,"sourceCode":"  const iv = bufferFromString(keystore.iv);\n  const salt = bufferFromString(keystore.salt);\n  const secret = keyFromPassword(password, salt);\n  const encryptedText = bufferFromString(keystore.data);\n\n  const alg = {\n    name: ALGORITHM,\n    counter: iv,\n    length: 64,\n  };\n  const key = await crypto.subtle.importKey('raw', secret, alg, false, ['decrypt']);\n\n  const ptBuffer = await crypto.subtle.decrypt(alg, key, encryptedText);\n  const decryptedData = new TextDecoder().decode(ptBuffer);\n\n  try {\n    return JSON.parse(decryptedData);\n  } catch {\n    throw new FuelError(ErrorCode.INVALID_CREDENTIALS, 'Invalid credentials.');\n  }\n};\n","sourceCodeStart":63,"sourceCodeEnd":84,"githubUrl":"https://github.com/FuelLabs/fuels-ts/blob/b3f37c91aca4aa9d5e4c0d3967f66237190826ea/packages/crypto/src/browser/aes-ctr.ts#L63-L84","documentation":"Thrown by the browser decrypt() function (packages/crypto/src/browser/aes-ctr.ts:81) when JSON.parse(decryptedData) fails after AES-CTR decryption completes. The Web Crypto subtle.decrypt() call itself did not throw — AES-CTR has no integrity check, so a wrong password produces valid ciphertext output that is garbage. The error is only caught when that garbage cannot be parsed as JSON, indicating the password (and thus the derived key) was wrong.","triggerScenarios":"Calling decrypt(password, keystore) with the wrong password. The wrong password produces a wrong PBKDF2 key, which decrypts the ciphertext into garbage bytes, which fail JSON.parse. NOTE: there is a small probability (negligible but nonzero) that garbage happens to be valid JSON, in which case no error is thrown and incorrect data is returned.","commonSituations":"User enters the wrong password to unlock a keystore/wallet; keystore was encrypted with a different password than provided; keystore data or salt/iv is corrupted; migrating keystores between systems with different password encoding.","solutions":["Verify the password matches the one used during encrypt().","If the password is correct but the error persists, the keystore (data, iv, or salt fields) may be corrupted — re-create it.","Handle the error gracefully in the UI and prompt the user to re-enter credentials.","Note: AES-CTR lacks integrity verification; consider an authenticated mode if designing a new keystore format."],"exampleFix":"// before\nconst data = await decrypt(userPassword, keystore);\n\n// after\nlet data;\ntry {\n  data = await decrypt(userPassword, keystore);\n} catch (e) {\n  if (e.code === 'invalid-credentials') {\n    throw new Error('Wrong password. Please try again.');\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"async function safeDecrypt(password: string, keystore: Keystore) {\n  try {\n    return { ok: true, data: await decrypt(password, keystore) };\n  } catch (e) {\n    if (e instanceof FuelError && e.code === 'invalid-credentials') {\n      return { ok: false, error: 'Wrong password' };\n    }\n    throw e;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  const data = await decrypt(password, keystore);\n} catch (e) {\n  if (e instanceof FuelError && e.code === 'invalid-credentials') {\n    // Password is wrong, or keystore data/iv/salt is corrupted\n    // Prompt user to re-enter password\n  }\n  throw e;\n}","preventionTips":["Always handle INVALID_CREDENTIALS in the UI with a clear 'wrong password' message.","Note: AES-CTR has no integrity check, so there is a negligible probability that wrong-password garbage parses as valid JSON.","If the password is correct but the error persists, the keystore may be corrupted — re-create it.","Store the keystore fields (data, iv, salt) together and atomically to prevent partial corruption."],"tags":["crypto","browser","keystore","password","decryption","credentials"],"backgroundTag":null,"analyzedSha":"b3f37c91aca4aa9d5e4c0d3967f66237190826ea","analyzedAt":"2026-08-12T20:30:56.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}