{"record":{"id":"3f78dd0cac198921","repo":"nautechsystems/nautilus_trader","slug":"invalid-ed25519-private-key-length","errorCode":null,"errorMessage":"Invalid Ed25519 private key length","messagePattern":"Invalid Ed25519 private key length","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cryptography/src/signing.rs","lineNumber":87,"sourceCode":"            &rng,\n            data.as_bytes(),\n            &mut signature,\n        )\n        .map_err(|_| anyhow::anyhow!(\"Failed to generate RSA signature\"))?;\n\n    Ok(BASE64_STANDARD.encode(signature))\n}\n\n/// Signs `data` using Ed25519 with the provided private key seed.\n///\n/// # Errors\n///\n/// Returns an error if the provided private key seed is invalid or signature creation fails.\npub fn ed25519_signature(private_key: &[u8], data: &str) -> anyhow::Result<String> {\n    let signing_key = SigningKey::from_bytes(\n        private_key\n            .try_into()\n            .map_err(|_| anyhow::anyhow!(\"Invalid Ed25519 private key length\"))?,\n    );\n    let signature: Ed25519Signature = signing_key.sign(data.as_bytes());\n    Ok(BASE64_STANDARD.encode(signature.to_bytes()))\n}\n\n#[cfg(test)]\nmod tests {\n    use rstest::rstest;\n\n    use super::*;\n\n    #[rstest]\n    #[case(\n        \"mysecretkey\",\n        \"data-to-sign\",\n        \"19ed21a8b2a6b847d7d7aea059ab3134cd58f13c860cfbe89338c718685fe077\"\n    )]\n    #[case(","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/cryptography/src/signing.rs#L69-L105","documentation":"`ed25519_signature` requires the private key seed to be exactly 32 bytes (Ed25519 seed size). The seed slice is converted with `try_into()` to a `[u8; 32]`; if the length differs, this error is returned before any signing occurs.","triggerScenarios":"Calling `ed25519_signature(private_key, data)` (or the Python binding `py_ed25519_signature`) with a seed that is not exactly 32 bytes: hex/base64 string passed raw instead of decoded, a 64-byte public key or full 64-byte secret key passed instead of the 32-byte seed, or an empty/truncated byte array.","commonSituations":"Passing a hex-encoded string (64 chars = 64 bytes raw) without decoding; passing the 64-byte expanded secret from a keypair file; Python callers passing a `str` whose UTF-8 bytes are used directly instead of `bytes.fromhex`/`base64.b64decode`; off-by-one slicing when splitting a key file.","solutions":["Decode the key material first: hex -> `bytes.fromhex(key)` or base64 -> `base64.b64decode(key)` so the result is exactly 32 bytes.","If you have a 64-byte Ed25519 secret key, pass only the first 32 bytes: `secret[:32]`.","Check the length in the caller before invoking: `len(key) == 32`.","If using Python, ensure the argument is `bytes`, not `str`."],"exampleFix":"// before\nlet key = hex_string.as_bytes(); // 64 bytes of ASCII hex -> error\nlet sig = ed25519_signature(&key, data)?;\n\n// after\nlet key = hex::decode(hex_string)?; // 32 raw bytes\nassert_eq!(key.len(), 32);\nlet sig = ed25519_signature(&key, data)?;","handlingStrategy":"validation","validationCode":"// Python caller: decode and length-check the seed before calling the binding\nimport base64, binascii\n\ndef ed25519_seed(raw: str | bytes) -> bytes:\n    if isinstance(raw, str):\n        key = bytes.fromhex(raw) if all(c in \"0123456789abcdefABCDEF\" for c in raw) else base64.b64decode(raw)\n    else:\n        key = raw\n    if len(key) != 32:\n        raise ValueError(f\"Ed25519 seed must be 32 bytes, got {len(key)}\")\n    return key[:32]","typeGuard":"// Rust caller\ndefn is_valid_seed(key: &[u8]) -> bool { key.len() == 32 }\n// Python caller\ndef is_valid_seed(key) -> bool:\n    return isinstance(key, (bytes, bytearray)) and len(key) == 32","tryCatchPattern":"try:\n    sig = py_ed25519_signature(seed, data)\nexcept (ValueError, RuntimeError) as e:\n    if \"Invalid Ed25519 private key length\" in str(e):\n        raise ValueError(f\"seed must be 32 decoded bytes, got {len(seed)}\") from e\n    raise","preventionTips":["Always store/pass Ed25519 seeds as raw 32-byte arrays, never as hex/base64 strings without decoding at the boundary.","Slice 64-byte expanded secret keys down to `key[:32]` before use.","Assert `len(key) == 32` in unit tests for any code path that constructs keys.","In Python, enforce `bytes` typing on key parameters; never let `str` through."],"tags":["cryptography","ed25519","signing","key-length"],"backgroundTag":"invalid-argument-format","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}