vta-sdk 0.17.0

SDK for Verifiable Trust Agents operating in Verifiable Trust Communities
Documentation
//! Key + seed management methods on [`VtaClient`].

use super::{
    CreateKeyRequest, CreateKeyResponse, GetKeySecretResponse, ImportKeyRequest, ImportKeyResponse,
    InvalidateKeyResponse, ListKeysResponse, ListSeedsResponse, RenameKeyRequest,
    RenameKeyResponse, RotateSeedRequest, RotateSeedResponse, SignResponse, Transport, VtaClient,
    WrappingKeyResponse, encode_path_segment,
};
use crate::error::VtaError;
use crate::keys::KeyRecord;
use crate::protocols::key_management::sign::SignAlgorithm;

#[cfg(feature = "client")]
use crate::protocols::{key_management, seed_management};

#[cfg(feature = "client")]
impl VtaClient {
    // ── Key methods ─────────────────────────────────────────────────

    pub async fn create_key(&self, req: CreateKeyRequest) -> Result<CreateKeyResponse, VtaError> {
        self.rpc(
            key_management::CREATE_KEY,
            serde_json::json!({
                "key_type": serde_json::to_value(&req.key_type)?,
                "derivation_path": req.derivation_path.as_deref().unwrap_or_default(),
                "mnemonic": req.mnemonic.as_deref(),
                "label": req.label.as_deref(),
                "context_id": req.context_id.as_deref(),
            }),
            key_management::CREATE_KEY_RESULT,
            30,
            |c, url| c.post(format!("{url}/keys")).json(&req),
        )
        .await
    }

    pub async fn list_keys(
        &self,
        offset: u64,
        limit: u64,
        status: Option<&str>,
        context_id: Option<&str>,
    ) -> Result<ListKeysResponse, VtaError> {
        self.rpc(
            key_management::LIST_KEYS,
            serde_json::json!({
                "offset": offset,
                "limit": limit,
                "status": status,
                "context_id": context_id,
            }),
            key_management::LIST_KEYS_RESULT,
            30,
            |c, url| {
                let mut u = format!("{url}/keys?offset={offset}&limit={limit}");
                if let Some(s) = status {
                    u.push_str(&format!("&status={s}"));
                }
                if let Some(ctx) = context_id {
                    u.push_str(&format!("&context_id={ctx}"));
                }
                c.get(u)
            },
        )
        .await
    }

    pub async fn get_key(&self, key_id: &str) -> Result<KeyRecord, VtaError> {
        self.rpc(
            key_management::GET_KEY,
            serde_json::json!({ "key_id": key_id }),
            key_management::GET_KEY_RESULT,
            30,
            |c, url| c.get(format!("{url}/keys/{}", encode_path_segment(key_id))),
        )
        .await
    }

    pub async fn get_key_secret(&self, key_id: &str) -> Result<GetKeySecretResponse, VtaError> {
        self.rpc(
            key_management::GET_KEY_SECRET,
            serde_json::json!({ "key_id": key_id }),
            key_management::GET_KEY_SECRET_RESULT,
            30,
            |c, url| c.get(format!("{url}/keys/{}/secret", encode_path_segment(key_id))),
        )
        .await
    }

    /// Sign a payload using a VTA-managed key.
    ///
    /// Sends the base64url-encoded payload to the VTA, which derives the key,
    /// signs in memory, and returns the signature. Key material never leaves VTA.
    pub async fn sign(
        &self,
        key_id: &str,
        payload: &[u8],
        algorithm: SignAlgorithm,
    ) -> Result<SignResponse, VtaError> {
        use base64::Engine;
        let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(payload);
        self.rpc(
            key_management::SIGN_REQUEST,
            serde_json::json!({
                "key_id": key_id,
                "payload": payload_b64,
                "algorithm": algorithm,
            }),
            key_management::SIGN_RESULT,
            30,
            |c, url| {
                c.post(format!("{url}/keys/{}/sign", encode_path_segment(key_id)))
                    .json(&serde_json::json!({
                        "payload": payload_b64,
                        "algorithm": algorithm,
                    }))
            },
        )
        .await
    }

    pub async fn invalidate_key(&self, key_id: &str) -> Result<InvalidateKeyResponse, VtaError> {
        self.rpc(
            key_management::REVOKE_KEY,
            serde_json::json!({ "key_id": key_id }),
            key_management::REVOKE_KEY_RESULT,
            30,
            |c, url| c.delete(format!("{url}/keys/{}", encode_path_segment(key_id))),
        )
        .await
    }

    pub async fn rename_key(
        &self,
        key_id: &str,
        new_key_id: &str,
    ) -> Result<RenameKeyResponse, VtaError> {
        self.rpc(
            key_management::RENAME_KEY,
            serde_json::json!({ "key_id": key_id, "new_key_id": new_key_id }),
            key_management::RENAME_KEY_RESULT,
            30,
            |c, url| {
                c.patch(format!("{url}/keys/{}", encode_path_segment(key_id)))
                    .json(&RenameKeyRequest {
                        key_id: new_key_id.to_string(),
                    })
            },
        )
        .await
    }

    // ── Import key methods ──────────────────────────────────────────

    /// Fetch an ephemeral wrapping key for REST key import.
    pub async fn get_wrapping_key(&self) -> Result<WrappingKeyResponse, VtaError> {
        match &self.transport {
            Transport::Rest {
                client,
                base_url,
                auth,
            } => {
                Self::ensure_token_valid(client, base_url, auth).await?;
                let token = auth.lock().await.token.clone();
                let req = client.get(format!("{base_url}/keys/import/wrapping-key"));
                let resp = Self::with_auth_token(req, &token).send().await?;
                Self::handle_response(resp).await
            }
            #[cfg(feature = "session")]
            Transport::DIDComm { .. } => Err(VtaError::UnsupportedTransport(
                "wrapping key not needed for DIDComm transport".into(),
            )),
        }
    }

    /// Import an externally-created private key into the VTA.
    pub async fn import_key(&self, req: ImportKeyRequest) -> Result<ImportKeyResponse, VtaError> {
        self.rpc(
            key_management::IMPORT_KEY,
            serde_json::to_value(&req)?,
            key_management::IMPORT_KEY_RESULT,
            30,
            |c, url| c.post(format!("{url}/keys/import")).json(&req),
        )
        .await
    }

    // ── Seed methods ────────────────────────────────────────────────

    pub async fn list_seeds(&self) -> Result<ListSeedsResponse, VtaError> {
        self.rpc(
            seed_management::LIST_SEEDS,
            serde_json::json!({}),
            seed_management::LIST_SEEDS_RESULT,
            30,
            |c, url| c.get(format!("{url}/keys/seeds")),
        )
        .await
    }

    pub async fn rotate_seed(
        &self,
        mnemonic: Option<String>,
    ) -> Result<RotateSeedResponse, VtaError> {
        let body = RotateSeedRequest {
            mnemonic: mnemonic.clone(),
        };
        self.rpc(
            seed_management::ROTATE_SEED,
            serde_json::json!({ "mnemonic": mnemonic }),
            seed_management::ROTATE_SEED_RESULT,
            30,
            |c, url| c.post(format!("{url}/keys/seeds/rotate")).json(&body),
        )
        .await
    }
}