use crate::{
enclave::{EnclaveManager, SignRequest, SigningAlgorithm},
ConclaveError, ConclaveResult,
};
use rand::Rng;
use secp256k1::{ecdsa::Signature, Message, PublicKey};
use serde::{Deserialize, Serialize};
use sha2::Digest;
use std::collections::HashMap;
use std::sync::RwLock;
use std::time::{SystemTime, UNIX_EPOCH};
fn unix_time_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BusinessAttribution {
pub business_id: String,
pub user_id: String,
pub timestamp: u64,
pub expiration: u64,
pub nonce: [u8; 16],
pub signature: String,
pub metadata: HashMap<String, String>,
}
impl BusinessAttribution {
pub fn get_hash(&self) -> Vec<u8> {
let mut hasher = sha2::Sha256::new();
hasher.update(self.business_id.as_bytes());
hasher.update(self.user_id.as_bytes());
hasher.update(self.timestamp.to_be_bytes());
hasher.update(self.expiration.to_be_bytes());
hasher.update(self.nonce);
let mut sorted_metadata: Vec<_> = self.metadata.iter().collect();
sorted_metadata.sort_by_key(|a| a.0);
for (k, v) in sorted_metadata {
hasher.update(k.as_bytes());
hasher.update(v.as_bytes());
}
hasher.finalize().to_vec()
}
pub fn verify(&self, public_key_hex: &str) -> ConclaveResult<()> {
let hash = self.get_hash();
let message_bytes: [u8; 32] = hash
.try_into()
.map_err(|_| ConclaveError::CryptoError("Invalid hash length".to_string()))?;
let message = Message::from_digest(message_bytes);
let public_key_bytes = hex::decode(public_key_hex)
.map_err(|_| ConclaveError::CryptoError("Invalid public key hex".to_string()))?;
let public_key = PublicKey::from_slice(&public_key_bytes)
.map_err(|_| ConclaveError::CryptoError("Invalid public key format".to_string()))?;
let signature_bytes = hex::decode(&self.signature)
.map_err(|_| ConclaveError::CryptoError("Invalid signature hex".to_string()))?;
let signature = Signature::from_compact(&signature_bytes)
.or_else(|_| Signature::from_der(&signature_bytes))
.map_err(|_| ConclaveError::CryptoError("Invalid signature format".to_string()))?;
if secp256k1::ecdsa::verify(&signature, message, &public_key).is_ok() {
Ok(())
} else {
Err(ConclaveError::CryptoError(
"Business attribution signature verification failed".to_string(),
))
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BusinessProfile {
pub id: String,
pub name: String,
pub public_key: String,
pub active: bool,
}
pub struct BusinessRegistry {
businesses: RwLock<HashMap<String, BusinessProfile>>,
}
impl Default for BusinessRegistry {
fn default() -> Self {
Self::new()
}
}
impl BusinessRegistry {
pub fn new() -> Self {
Self {
businesses: RwLock::new(HashMap::new()),
}
}
pub fn register_business(&self, profile: BusinessProfile) {
if let Ok(mut lock) = self.businesses.write() {
lock.insert(profile.id.clone(), profile);
}
}
pub fn get_business(&self, id: &str) -> Option<BusinessProfile> {
self.businesses.read().ok()?.get(id).cloned()
}
pub fn is_active(&self, id: &str) -> bool {
self.businesses
.read()
.ok()
.and_then(|lock| lock.get(id).map(|b| b.active))
.unwrap_or(false)
}
}
pub struct BusinessManager<'a> {
enclave: &'a dyn EnclaveManager,
registry: &'a BusinessRegistry,
}
impl<'a> BusinessManager<'a> {
pub fn new(enclave: &'a dyn EnclaveManager, registry: &'a BusinessRegistry) -> Self {
Self { enclave, registry }
}
pub fn generate_business_identity(
&self,
business_id: &str,
name: &str,
) -> ConclaveResult<BusinessProfile> {
let public_key = self
.enclave
.get_public_key(&format!("m/44'/5757'/0'/0/business/{}", business_id))?;
Ok(BusinessProfile {
id: business_id.to_string(),
name: name.to_string(),
public_key,
active: true,
})
}
pub fn generate_attribution(
&self,
business_id: &str,
user_id: &str,
metadata: HashMap<String, String>,
) -> ConclaveResult<BusinessAttribution> {
if !self.registry.is_active(business_id) {
return Err(ConclaveError::InvalidPayload);
}
let timestamp = unix_time_secs();
let ttl: u64 = 3600;
let expiration: u64 = timestamp + ttl;
let mut nonce = [0u8; 16];
rand::rng().fill_bytes(&mut nonce);
let mut attribution = BusinessAttribution {
business_id: business_id.to_string(),
user_id: user_id.to_string(),
timestamp,
expiration,
nonce,
signature: String::new(),
metadata,
};
let message_hash = attribution.get_hash();
let request = SignRequest {
algorithm: SigningAlgorithm::EcdsaSecp256k1,
message_hash,
derivation_path: format!("m/44'/5757'/0'/0/business/{}", business_id),
key_id: format!("business_{}", business_id),
taproot_tweak: None,
};
let response = self.enclave.sign(request)?;
attribution.signature = response.signature_hex;
Ok(attribution)
}
}