nautechsystems/nautilus_trader · error

Invalid Ed25519 private key length

Error message

Invalid Ed25519 private key length

What it means

`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.

Source

Thrown at crates/cryptography/src/signing.rs:87

            &rng,
            data.as_bytes(),
            &mut signature,
        )
        .map_err(|_| anyhow::anyhow!("Failed to generate RSA signature"))?;

    Ok(BASE64_STANDARD.encode(signature))
}

/// Signs `data` using Ed25519 with the provided private key seed.
///
/// # Errors
///
/// Returns an error if the provided private key seed is invalid or signature creation fails.
pub fn ed25519_signature(private_key: &[u8], data: &str) -> anyhow::Result<String> {
    let signing_key = SigningKey::from_bytes(
        private_key
            .try_into()
            .map_err(|_| anyhow::anyhow!("Invalid Ed25519 private key length"))?,
    );
    let signature: Ed25519Signature = signing_key.sign(data.as_bytes());
    Ok(BASE64_STANDARD.encode(signature.to_bytes()))
}

#[cfg(test)]
mod tests {
    use rstest::rstest;

    use super::*;

    #[rstest]
    #[case(
        "mysecretkey",
        "data-to-sign",
        "19ed21a8b2a6b847d7d7aea059ab3134cd58f13c860cfbe89338c718685fe077"
    )]
    #[case(

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Decode the key material first: hex -> `bytes.fromhex(key)` or base64 -> `base64.b64decode(key)` so the result is exactly 32 bytes.
  2. If you have a 64-byte Ed25519 secret key, pass only the first 32 bytes: `secret[:32]`.
  3. Check the length in the caller before invoking: `len(key) == 32`.
  4. If using Python, ensure the argument is `bytes`, not `str`.

Example fix

// before
let key = hex_string.as_bytes(); // 64 bytes of ASCII hex -> error
let sig = ed25519_signature(&key, data)?;

// after
let key = hex::decode(hex_string)?; // 32 raw bytes
assert_eq!(key.len(), 32);
let sig = ed25519_signature(&key, data)?;
Defensive patterns

Strategy: validation

Validate before calling

// Python caller: decode and length-check the seed before calling the binding
import base64, binascii

def ed25519_seed(raw: str | bytes) -> bytes:
    if isinstance(raw, str):
        key = bytes.fromhex(raw) if all(c in "0123456789abcdefABCDEF" for c in raw) else base64.b64decode(raw)
    else:
        key = raw
    if len(key) != 32:
        raise ValueError(f"Ed25519 seed must be 32 bytes, got {len(key)}")
    return key[:32]

Type guard

// Rust caller
defn is_valid_seed(key: &[u8]) -> bool { key.len() == 32 }
// Python caller
def is_valid_seed(key) -> bool:
    return isinstance(key, (bytes, bytearray)) and len(key) == 32

Try / catch

try:
    sig = py_ed25519_signature(seed, data)
except (ValueError, RuntimeError) as e:
    if "Invalid Ed25519 private key length" in str(e):
        raise ValueError(f"seed must be 32 decoded bytes, got {len(seed)}") from e
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/3f78dd0cac198921. Report an issue: GitHub.