conxius-enclave-sdk 2.0.11

Hardware-backed security primitives for the broader Conxian ecosystem. Provides high-integrity root of trust for security-sensitive wallet, signing, attestation, and policy flows.
Documentation
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)
    }
}