astrid-runtime/astrid · error
invalid ed25519 secret key
Error message
invalid ed25519 secret key: {e} What it means
load_signing_key reads a signing key file, requires exactly 32 raw bytes, and then hands them to astrid_crypto::KeyPair::from_secret_key. If the cryptographic layer still rejects the bytes (not a valid ed25519 scalar), the underlying crypto error is wrapped into "invalid ed25519 secret key: {e}". The file exists and is the right length; its content just isn't a usable ed25519 secret key.
Solutions
- Regenerate the signing key with the tooling that emits 32 raw ed25519 secret-key bytes and point the seal command at the new file.
- If your key is stored hex- or base64-encoded, decode it to 32 raw bytes before writing it to the keyfile.
- Verify file contents: `wc -c` should report exactly 32 bytes and the bytes should be a valid ed25519 scalar (not the public key).
- Restore the keyfile from a known-good backup if it was corrupted in transit.
Example fix
// before: base64 text file fed to seal SigningKeyPath=/home/me/distro.key // contains 'dGhpcyBpcyBiYXNlNjQuLi4=' // after $ base64 -d distro.key.b64 > distro.key && wc -c distro.key # must be 32 SigningKeyPath=/home/me/distro.key
Defensive patterns
Strategy: validation
Validate before calling
let bytes = std::fs::read(path)?;
anyhow::ensure!(bytes.len() == 32, "signing key {} must be 32 raw bytes, got {}", path.display(), bytes.len());
anyhow::ensure!(bytes.iter().any(|&b| b != 0), "signing key must not be all zeros"); Try / catch
match load_signing_key(&path) {
Ok(kp) => kp,
Err(e) if e.to_string().starts_with("invalid ed25519 secret key") => {
eprintln!("keyfile content is not a raw ed25519 secret; regenerate or decode it first");
return Err(e);
}
Err(e) => return Err(e),
} Prevention
- Store signing keys as exactly 32 raw bytes — never hex/base64 text in the keyfile.
- Regenerate keys with the bundled tooling rather than external generators.
- Back up keyfiles and verify byte length after any transfer.
When it happens
Trigger: Running `seal` (run_seal) with a key path whose 32 bytes are rejected by ed25519 parsing — e.g. a base64/hex-encoded key stored raw-decoded incorrectly, an all-zero or out-of-range scalar, or a truncated/corrupted keyfile that coincidentally is 32 bytes. Also hit intentionally by the test load_signing_key_rejects_wrong_length.
Common situations: Generating the key with a different tool that stores it hex/base64 encoded while seal expects raw bytes; copying the wrong file (e.g. a public-key file or unrelated 32-byte blob) as the signing key; file corruption after a failed transfer.
Related errors
- invalid ed25519 public key
- decode public key hex
- fingerprint Ed25519 public key
- public key must be in 'ed25519
- invalid runtime key (replaces 'invalid signing key' in…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/4a612d98f3d51aa2.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/distro/seal.rs:205
}
bail!(
"seal requires a local Distro.toml path or its directory; {distro:?} is not a file or directory"
)
}
/// Load a 32-byte raw ed25519 secret key from `path`. Never logged.
fn load_signing_key(path: &Path) -> anyhow::Result<astrid_crypto::KeyPair> {
let bytes = std::fs::read(path)
.with_context(|| format!("failed to read signing key {}", path.display()))?;
if bytes.len() != 32 {
bail!(
"signing key {} must be exactly 32 raw bytes (got {})",
path.display(),
bytes.len()
);
}
astrid_crypto::KeyPair::from_secret_key(&bytes)
.map_err(|e| anyhow::anyhow!("invalid ed25519 secret key: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
struct CurrentDirGuard(PathBuf);
impl CurrentDirGuard {
fn set(path: &Path) -> Self {
let original = std::env::current_dir().unwrap();
std::env::set_current_dir(path).unwrap();
Self(original)
}
}
fn run_bare_cwd_child(child_name: &str, marker: &str) {
let result_dir = tempfile::tempdir().unwrap();View on GitHub (pinned to affd8760f4)