FuelLabs/fuels-ts · error · FuelError

INVALID_CREDENTIALS

INVALID_CREDENTIALS

Error message

Invalid credentials.

What it means

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.

Source

Thrown at packages/crypto/src/browser/aes-ctr.ts:81

  const iv = bufferFromString(keystore.iv);
  const salt = bufferFromString(keystore.salt);
  const secret = keyFromPassword(password, salt);
  const encryptedText = bufferFromString(keystore.data);

  const alg = {
    name: ALGORITHM,
    counter: iv,
    length: 64,
  };
  const key = await crypto.subtle.importKey('raw', secret, alg, false, ['decrypt']);

  const ptBuffer = await crypto.subtle.decrypt(alg, key, encryptedText);
  const decryptedData = new TextDecoder().decode(ptBuffer);

  try {
    return JSON.parse(decryptedData);
  } catch {
    throw new FuelError(ErrorCode.INVALID_CREDENTIALS, 'Invalid credentials.');
  }
};

View on GitHub (pinned to b3f37c91ac)

Solutions

  1. Verify the password matches the one used during encrypt().
  2. If the password is correct but the error persists, the keystore (data, iv, or salt fields) may be corrupted — re-create it.
  3. Handle the error gracefully in the UI and prompt the user to re-enter credentials.
  4. Note: AES-CTR lacks integrity verification; consider an authenticated mode if designing a new keystore format.

Example fix

// before
const data = await decrypt(userPassword, keystore);

// after
let data;
try {
  data = await decrypt(userPassword, keystore);
} catch (e) {
  if (e.code === 'invalid-credentials') {
    throw new Error('Wrong password. Please try again.');
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

async function safeDecrypt(password: string, keystore: Keystore) {
  try {
    return { ok: true, data: await decrypt(password, keystore) };
  } catch (e) {
    if (e instanceof FuelError && e.code === 'invalid-credentials') {
      return { ok: false, error: 'Wrong password' };
    }
    throw e;
  }
}

Try / catch

try {
  const data = await decrypt(password, keystore);
} catch (e) {
  if (e instanceof FuelError && e.code === 'invalid-credentials') {
    // Password is wrong, or keystore data/iv/salt is corrupted
    // Prompt user to re-enter password
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of FuelLabs/fuels-ts@b3f37c91ac (2026-08-12). Data as JSON: /api/errors/ae70ea7b3954a836. Report an issue: GitHub.