Skip to main content

auths_sdk/
keys.rs

1//! Key import and management operations.
2//!
3//! Provides SDK-level key management functions that wrap `auths-core` crypto
4//! primitives. These functions are the canonical entry point for key operations
5//! — the CLI is a thin wrapper that reads files and calls these.
6
7use auths_core::crypto::signer::encrypt_keypair;
8use auths_core::storage::keychain::{KeyAlias, KeyRole, KeyStorage};
9use auths_verifier::IdentityDID;
10use thiserror::Error;
11use zeroize::Zeroizing;
12
13/// Errors from key import operations.
14#[derive(Debug, Error)]
15#[non_exhaustive]
16pub enum KeyImportError {
17    /// The seed is not exactly 32 bytes.
18    #[error("seed must be exactly 32 bytes, got {0}")]
19    InvalidSeedLength(usize),
20
21    /// The alias string is empty.
22    #[error("key alias cannot be empty")]
23    EmptyAlias,
24
25    /// PKCS#8 DER encoding failed.
26    #[error("failed to generate PKCS#8 from seed: {0}")]
27    Pkcs8Generation(String),
28
29    /// Encryption of the private key failed.
30    #[error("failed to encrypt private key: {0}")]
31    Encryption(String),
32
33    /// Storing the encrypted key in the keychain failed.
34    #[error("failed to store key in keychain: {0}")]
35    KeychainStore(String),
36}
37
38/// Result of a successful key import.
39#[derive(Debug, Clone)]
40pub struct KeyImportResult {
41    /// The public key derived from the seed (32 bytes Ed25519, 33 bytes P-256 compressed).
42    pub public_key: Vec<u8>,
43    /// The curve of the imported key.
44    pub curve: auths_crypto::CurveType,
45    /// The alias under which the key was stored.
46    pub alias: String,
47}
48
49/// Imports a signing key from a raw 32-byte seed into the keychain.
50///
51/// Both Ed25519 and P-256 use 32-byte scalars; the curve tag determines
52/// which PKCS#8 shape is emitted and which public key is derived.
53///
54/// Args:
55/// * `seed`: The raw 32-byte signing seed, wrapped in `Zeroizing`.
56/// * `curve`: Curve for the imported key (P-256 default per workspace convention).
57/// * `passphrase`: The encryption passphrase, wrapped in `Zeroizing`.
58/// * `alias`: The local keychain alias for this key.
59/// * `controller_did`: The identity DID this key is associated with.
60/// * `keychain`: The keychain backend to store the encrypted key.
61///
62/// Usage:
63/// ```ignore
64/// let result = import_seed(
65///     &seed, CurveType::P256, &passphrase, "my_key",
66///     &IdentityDID::new_unchecked("did:keri:EXq5abc"),
67///     keychain.as_ref(),
68/// )?;
69/// ```
70pub fn import_seed(
71    seed: &Zeroizing<[u8; 32]>,
72    curve: auths_crypto::CurveType,
73    passphrase: &Zeroizing<String>,
74    alias: &str,
75    controller_did: &IdentityDID,
76    keychain: &dyn KeyStorage,
77) -> Result<KeyImportResult, KeyImportError> {
78    if alias.trim().is_empty() {
79        return Err(KeyImportError::EmptyAlias);
80    }
81
82    let typed_seed = match curve {
83        auths_crypto::CurveType::Ed25519 => auths_crypto::TypedSeed::Ed25519(**seed),
84        auths_crypto::CurveType::P256 => auths_crypto::TypedSeed::P256(**seed),
85    };
86    let signer = auths_crypto::TypedSignerKey::from_seed(typed_seed)
87        .map_err(|e| KeyImportError::Pkcs8Generation(format!("failed to derive keypair: {e}")))?;
88    let pkcs8 = signer
89        .to_pkcs8()
90        .map_err(|e| KeyImportError::Pkcs8Generation(e.to_string()))?;
91
92    let encrypted = encrypt_keypair(pkcs8.as_ref(), passphrase)
93        .map_err(|e| KeyImportError::Encryption(e.to_string()))?;
94
95    keychain
96        .store_key(
97            &KeyAlias::new_unchecked(alias),
98            controller_did,
99            KeyRole::Primary,
100            &encrypted,
101        )
102        .map_err(|e| KeyImportError::KeychainStore(e.to_string()))?;
103
104    Ok(KeyImportResult {
105        public_key: signer.public_key().to_vec(),
106        curve,
107        alias: alias.to_string(),
108    })
109}