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

  1. Normalize the input: exactly 64 hex characters, no 0x prefix, no whitespace or trailing newline (trim first).
  2. Validate the format before calling parse_secret: length 64 plus hex digits only.
  3. If the key may be wrong, generate a fresh one with keygen generate (SecretKey::random) and use its hex output verbatim.
  4. 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

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


AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16). Data as JSON: /api/errors/153391fcb1676afc. Report an issue: GitHub.