FuelLabs/fuels-ts · error · FuelError
INVALID_CREDENTIALS
INVALID_CREDENTIALS
Error message
Invalid credentials.
What it means
Thrown by the Node AES-256-CTR decrypt path when JSON.parse of the decrypted plaintext fails. A failed parse means the ciphertext did not decode to valid JSON, which happens when the derived key is wrong (wrong password) or the keystore blob is not the one originally encrypted. The SDK deliberately collapses both parse failure and authentication failure into a single INVALID_CREDENTIALS error so as not to leak which step failed.
Source
Thrown at packages/crypto/src/node/aes-ctr.ts:74
*/
export const decrypt: CryptoApi['decrypt'] = async <T>(
password: string,
keystore: Keystore
): Promise<T> => {
const iv = bufferFromString(keystore.iv);
const salt = bufferFromString(keystore.salt);
const secret = keyFromPassword(password, salt);
const encryptedText = bufferFromString(keystore.data);
const decipher = await crypto.createDecipheriv(ALGORITHM, secret, iv);
const decrypted = decipher.update(encryptedText);
const deBuff = Buffer.concat([decrypted, decipher.final()]);
const decryptedData = Buffer.from(deBuff).toString('utf-8');
try {
return JSON.parse(decryptedData);
} catch {
throw new FuelError(ErrorCode.INVALID_CREDENTIALS, 'Invalid credentials.');
}
};
View on GitHub (pinned to b3f37c91ac)
Solutions
- Re-enter the password carefully (the failure most often is a wrong password, not corruption).
- Confirm the keystore object is the exact one returned by `encrypt()` — match `data`, `iv`, and `salt`.
- If migrating from another tool, re-encrypt with `encrypt(password, data)` from this SDK first.
- Restore the keystore from a backup if the file was edited or partially overwritten.
Example fix
// before
const wallet = await decrypt(typedPassword, keystore);
// after — verify keystore shape and source first
if (!keystore.data || !keystore.iv || !keystore.salt) {
throw new Error('Keystore is incomplete');
}
try {
const wallet = await decrypt(typedPassword, keystore);
} catch (e) {
if (e instanceof FuelError && e.code === FuelError.CODES.INVALID_CREDENTIALS) {
// prompt the user to re-enter the password
}
} Defensive patterns
Strategy: try-catch
Validate before calling
import { encrypt } from '@fuel-ts/crypto';
// verify keystore shape and source before decrypt
function isKeystore(o: unknown): o is { data: string; iv: string; salt: string } {
return !!o && typeof o === 'object' &&
typeof (o as any).data === 'string' &&
typeof (o as any).iv === 'string' &&
typeof (o as any).salt === 'string';
} Type guard
import type { Keystore } from '@fuel-ts/crypto';
const isKeystore = (o: unknown): o is Keystore =>
!!o && typeof o === 'object' &&
['data', 'iv', 'salt'].every((k) => typeof (o as any)[k] === 'string'); Try / catch
try {
const data = await decrypt(password, keystore);
} catch (e) {
if (e instanceof FuelError && e.code === FuelError.CODES.INVALID_CREDENTIALS) {
// re-prompt for password; do NOT loop silently
}
throw e;
} Prevention
- Store the keystore exactly as returned by encrypt(); never edit its fields by hand.
- Round-trip test: encrypt then decrypt in the same session to confirm credentials.
- Keep backups of keystore + password together; a wrong password is indistinguishable from corruption by design.
When it happens
Trigger: Calling `decrypt(password, keystore)` from @fuel-ts/crypto with a password whose PBKDF2-derived key does not reproduce the original ciphertext, or with a keystore whose `data`, `iv`, or `salt` fields were altered.
Common situations: Typo in the wallet password, loading the wrong keystore file, a keystore generated by a different SDK/version or a non-Fuel tool, keystore truncated or corrupted on disk, or copy/paste of keystore fields that dropped characters.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of FuelLabs/fuels-ts@b3f37c91ac (2026-08-12).
Data as JSON: /api/errors/7ad49b24d4451964.
Report an issue: GitHub.