use crate::state::AppState; use axum::{ Json, extract::State, http::StatusCode, response::{IntoResponse, Response}, }; use chrono::{Duration, Utc}; use k256::ecdsa::SigningKey; use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::{error, info}; const SECP256K1_MULTICODEC_PREFIX: [u8; 2] = [0xe7, 0x01]; fn public_key_to_did_key(signing_key: &SigningKey) -> String { let verifying_key = signing_key.verifying_key(); let compressed_pubkey = verifying_key.to_sec1_bytes(); let mut multicodec_key = Vec::with_capacity(2 + compressed_pubkey.len()); multicodec_key.extend_from_slice(&SECP256K1_MULTICODEC_PREFIX); multicodec_key.extend_from_slice(&compressed_pubkey); let encoded = multibase::encode(multibase::Base::Base58Btc, &multicodec_key); format!("did:key:{}", encoded) } #[derive(Deserialize)] pub struct ReserveSigningKeyInput { pub did: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct ReserveSigningKeyOutput { pub signing_key: String, } pub async fn reserve_signing_key( State(state): State, Json(input): Json, ) -> Response { let signing_key = SigningKey::random(&mut rand::thread_rng()); let private_key_bytes = signing_key.to_bytes(); let public_key_did_key = public_key_to_did_key(&signing_key); let expires_at = Utc::now() + Duration::hours(24); let private_bytes: &[u8] = &private_key_bytes; let result = sqlx::query!( r#" INSERT INTO reserved_signing_keys (did, public_key_did_key, private_key_bytes, expires_at) VALUES ($1, $2, $3, $4) RETURNING id "#, input.did, public_key_did_key, private_bytes, expires_at ) .fetch_one(&state.db) .await; match result { Ok(row) => { info!("Reserved signing key {} for did {:?}", row.id, input.did); ( StatusCode::OK, Json(ReserveSigningKeyOutput { signing_key: public_key_did_key, }), ) .into_response() } Err(e) => { error!("DB error in reserve_signing_key: {:?}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"})), ) .into_response() } } }