astrid-runtime/astrid · error

principal has no Ed25519 key for genesis identity

Error message

principal {alias} has no Ed25519 key for genesis identity

What it means

Thrown by genesis_public_key_bytes when the legacy principal profile contains no device public keys at all. The function picks the earliest-created device key (by created_at then key_id) to derive the genesis identity for the minted leftover; with an empty public_keys list there is nothing to derive from, so minting cannot proceed for that principal.

Solutions

  1. Regenerate or restore the profile so it contains at least one device public key with created_at and key_id.
  2. Skip/quarantine this principal instead of minting it, if the key genuinely cannot be recovered.
  3. Verify the legacy tool version that created the profile actually wrote public_keys; re-export from the legacy app if needed.
  4. Do not hand-edit profiles to remove keys before migration.

Example fix

// before
"auth": { "public_keys": [] }
// after
"auth": { "public_keys": [ { "key_id": "k1", "created_at": 1700000000, "pubkey": "<hex-ed25519>" } ] }
Defensive patterns

Strategy: validation

Validate before calling

fn profile_has_genesis_key(profile: &PrincipalProfile) -> bool {
    !profile.auth.public_keys.is_empty()
}

Try / catch

match mint_valid_leftover(entry) {
    Err(e) if e.to_string().contains("no Ed25519 key") => {
        // cannot derive genesis identity: quarantine instead of minting
        quarantine(entry)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: ensure_profile_with_genesis_key loads a profile whose auth.public_keys iterator yields no elements when min_by_key looks for the earliest device — i.e. the profile parsed fine but has zero registered keys.

Common situations: A legacy profile created by an old version that stored keys elsewhere; a hand-crafted or sanitized profile with the keys array emptied; keys pruned by rotation cleanup before migration; profile JSON edited manually dropping the keys field.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/ea9adb36fa5b5537. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-kernel/src/principal_home_migration/unbound.rs:216

            i64::try_from(crate::invite::now_epoch()).unwrap_or(0),
        ));
    }
    if !profile.auth.methods.contains(&AuthMethod::Keypair) {
        profile.auth.methods.push(AuthMethod::Keypair);
    }
    Ok(true)
}

fn genesis_public_key_bytes(home: &AstridHome, alias: &PrincipalId) -> io::Result<[u8; 32]> {
    let profile =
        PrincipalProfile::load_required(home, alias).map_err(|error| profile_io(&error))?;
    let device = profile
        .auth
        .public_keys
        .iter()
        .min_by_key(|device| (device.created_at, device.key_id.as_str()))
        .ok_or_else(|| {
            io::Error::new(
                io::ErrorKind::InvalidData,
                format!("principal {alias} has no Ed25519 key for genesis identity"),
            )
        })?;
    let public_key = astrid_crypto::PublicKey::from_hex(&device.pubkey).map_err(|error| {
        io::Error::new(
            io::ErrorKind::InvalidData,
            format!("principal {alias} has an invalid genesis public key: {error}"),
        )
    })?;
    Ok(public_key.into())
}

fn quarantine_entry(
    home: &AstridHome,
    source: &Path,
    file_name: &OsStr,
    reason: &str,

View on GitHub (pinned to affd8760f4)