FuelLabs/fuel-core · error
invalid secret key
Error message
invalid secret key
What it means
keygen::parse_secret parses the input with fuel_crypto's SecretKey::from_str (a secp256k1 scalar). Any malformed input — wrong length, non-hex characters, a 0x prefix, surrounding whitespace, or a zero/out-of-range scalar — collapses into this single 'invalid secret key' message, so the underlying parse cause is not surfaced.
Source
Thrown at crates/keygen/src/lib.rs:123
let p2p_keypair = secp256k1::Keypair::from(p2p_secret);
let libp2p_keypair = Keypair::from(p2p_keypair);
let peer_id = PeerId::from_public_key(&libp2p_keypair.public());
NewKeyResponse {
secret,
address: None,
peer_id: Some(peer_id),
typ: key_type,
}
}
})
}
pub fn parse_secret(
key_type: KeyType,
secret: &str,
) -> anyhow::Result<ParseSecretResponse> {
let secret =
SecretKey::from_str(secret).map_err(|_| anyhow::anyhow!("invalid secret key"))?;
Ok(match key_type {
KeyType::BlockProduction => {
let address = Input::owner(&secret.public_key());
ParseSecretResponse {
address: Some(address),
peer_id: None,
typ: key_type,
}
}
KeyType::Peering => {
let mut bytes = *secret.deref();
let p2p_secret = secp256k1::SecretKey::try_from_bytes(&mut bytes)
.expect("Should be a valid private key");
let p2p_keypair = secp256k1::Keypair::from(p2p_secret);
let libp2p_keypair = Keypair::from(p2p_keypair);
let peer_id = PeerId::from_public_key(&libp2p_keypair.public());
ParseSecretResponse {
address: None,View on GitHub (pinned to b9d4d170da)
Solutions
- Normalize the input: exactly 64 hex characters, no 0x prefix, no whitespace or trailing newline (trim first).
- Validate the format before calling parse_secret: length 64 plus hex digits only.
- If the key may be wrong, generate a fresh one with keygen generate (SecretKey::random) and use its hex output verbatim.
- For KeyType::Peering, pass the secp256k1 secret from the libp2p keypair, not a public key or peer id.
Example fix
// before
let resp = parse_secret(KeyType::BlockProduction, "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")?;
// after: strip prefix/whitespace, 64 hex chars only
let raw = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
let resp = parse_secret(KeyType::BlockProduction, raw.trim().trim_start_matches("0x"))?; Defensive patterns
Strategy: validation
Validate before calling
fn is_valid_secret_hex(s: &str) -> bool {
s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit())
}
// normalize before calling keygen::parse_secret
let secret = secret.trim().trim_start_matches("0x");
assert!(
is_valid_secret_hex(secret),
"secret must be exactly 64 hex characters (32-byte secp256k1 scalar)"
); Type guard
fn is_valid_secret_hex(s: &str) -> bool {
s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit())
} Try / catch
match keygen::parse_secret(key_type, &secret) {
Err(e) if e.to_string() == "invalid secret key" => {
// re-check format: length 64, hex only, no 0x prefix or whitespace; regenerate if malformed
}
rest => rest,
} Prevention
- Store secrets as bare 64-char hex; trim whitespace and strip 0x before parsing.
- Validate secret format in config loaders at startup, not at first use.
- Do not mix key formats: secp256k1 secrets only for both KeyType variants.
When it happens
Trigger: Calling keygen parse (or any consumer of parse_secret) with a secret that is not exactly 64 hex characters encoding a valid non-zero secp256k1 scalar.
Common situations: Copying a key with a 0x prefix or surrounding quotes and whitespace from docs or env vars; truncated keys; pasting an ed25519 or libp2p key where a secp256k1 secret is expected; newline artifacts from files or CI secrets.
Related errors
- contract tx_pointer cannot be greater than genesis block
- message da_height cannot be greater than genesis da block he
- Not implemented yet
- Missing code for contract: {id}
- Missing utxo for contract: {id}
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/153391fcb1676afc.
Report an issue: GitHub.