nikivdev/code · error

invalid secret key length

Error message

invalid secret key length

What it means

decode_secret requires the base58-decoded bytes to be exactly 32 bytes (x25519 scalar). If decoding succeeds but the length is wrong, this error is thrown. The string looks like a valid secret but encodes the wrong amount of key material.

Source

Thrown at src/sealer_crypto.rs:89

    let nonce = derive_nonce(nonce_material);
    let cipher = XSalsa20Poly1305::new(&shared_secret.into());
    let plaintext = cipher
        .decrypt(&nonce.into(), sealed_message)
        .map_err(|_| anyhow::anyhow!("failed to unseal message"))?;
    Ok(plaintext)
}

fn decode_secret(value: &str) -> Result<[u8; 32]> {
    let encoded = value
        .strip_prefix(SECRET_PREFIX)
        .ok_or_else(|| anyhow::anyhow!("invalid sealer secret prefix"))?;
    let bytes = bs58::decode(encoded)
        .into_vec()
        .map_err(|e| anyhow::anyhow!("invalid base58 secret: {e}"))?;
    bytes
        .as_slice()
        .try_into()
        .map_err(|_| anyhow::anyhow!("invalid secret key length"))
}

fn decode_id(value: &str) -> Result<[u8; 32]> {
    let encoded = value
        .strip_prefix(ID_PREFIX)
        .ok_or_else(|| anyhow::anyhow!("invalid sealer id prefix"))?;
    let bytes = bs58::decode(encoded)
        .into_vec()
        .map_err(|e| anyhow::anyhow!("invalid base58 id: {e}"))?;
    bytes
        .as_slice()
        .try_into()
        .map_err(|_| anyhow::anyhow!("invalid public key length"))
}

fn derive_nonce(nonce_material: &[u8]) -> [u8; 24] {
    let hash = blake3::hash(nonce_material);
    let mut nonce = [0u8; 24];

View on GitHub (pinned to a747e741ae)

Solutions

  1. Provide the complete 32-byte secret; re-copy the full value.
  2. Regenerate via create_sealer_identity / new_x25519_private_key and re-seal any values.
  3. Re-encode exactly 32 bytes as "sealerSecret_z" + base58 if migrating.
  4. Pre-validate: decode and assert 32-byte length before calling seal/unseal.

Example fix

// before: decodes to 24 bytes
let k = decode_secret("sealerSecret_zTooShort")?;
// after: full 32-byte key
let k = decode_secret("sealerSecret_zFull32ByteBase58Body...")?;
Defensive patterns

Strategy: validation

Validate before calling

fn decode_secret_len_ok(s: &str) -> bool {
    s.strip_prefix("sealerSecret_z")
        .and_then(|b| bs58::decode(b).into_vec().ok())
        .map(|v| v.len() == 32)
        .unwrap_or(false)
}

Try / catch

match unseal(blob, recipient_secret, sender_id, nonce) {
    Err(e) if e.to_string().contains("invalid secret key length") => {
        eprintln!("recipient secret is not a 32-byte x25519 key; regenerate identity");
        return Err(e);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling seal or unseal with a prefixed secret whose decoded length is not 32 bytes — truncated keys, keys from other schemes, or accidental concatenation.

Common situations: Truncated paste into config, secrets generated by incompatible tooling, or editing the secret by hand.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/13adea282a41d01d. Report an issue: GitHub.