qudag_exchange_core/
immutable.rs

1//! Immutable Deployment System for QuDAG Exchange
2//!
3//! Provides optional immutable deployment mode where exchange configuration
4//! can be locked using quantum-resistant signatures, preventing further
5//! modifications and enabling governance-free operation.
6
7#[cfg(not(feature = "std"))]
8use alloc::{format, string::String, vec::Vec};
9
10use crate::{
11    fee_model::FeeModelParams,
12    types::{Hash, Timestamp},
13    Error, Result,
14};
15use serde::{Deserialize, Serialize};
16
17/// Quantum-resistant signature for immutable deployment
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct ImmutableSignature {
20    /// Algorithm used (e.g., "ML-DSA-87", "ML-DSA-65")
21    pub algorithm: String,
22
23    /// Public key of the signer
24    pub public_key: Vec<u8>,
25
26    /// The signature bytes
27    pub signature: Vec<u8>,
28
29    /// Hash of the signed configuration
30    pub config_hash: Hash,
31}
32
33/// Immutable deployment configuration
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ImmutableConfig {
36    /// Whether immutable mode is enabled
37    pub enabled: bool,
38
39    /// Timestamp when system was locked (None if not locked)
40    pub locked_at: Option<Timestamp>,
41
42    /// Quantum-resistant signature that locks the configuration
43    pub lock_signature: Option<ImmutableSignature>,
44
45    /// Optional governance override key (for emergency situations)
46    pub governance_key: Option<Vec<u8>>,
47
48    /// Hash of the configuration that was locked
49    pub locked_config_hash: Option<Hash>,
50
51    /// Grace period in seconds before lock takes effect
52    pub grace_period_seconds: u64,
53}
54
55impl Default for ImmutableConfig {
56    fn default() -> Self {
57        Self {
58            enabled: false,
59            locked_at: None,
60            lock_signature: None,
61            governance_key: None,
62            locked_config_hash: None,
63            grace_period_seconds: 24 * 60 * 60, // 24 hours default grace period
64        }
65    }
66}
67
68impl ImmutableConfig {
69    /// Create a new immutable config with default settings
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    /// Enable immutable mode (but don't lock yet)
75    pub fn enable(&mut self) {
76        self.enabled = true;
77    }
78
79    /// Disable immutable mode (only allowed if not locked)
80    pub fn disable(&mut self) -> Result<()> {
81        if self.is_locked() {
82            return Err(Error::Other(
83                "Cannot disable immutable mode: system is locked".into(),
84            ));
85        }
86        self.enabled = false;
87        Ok(())
88    }
89
90    /// Check if the system is currently locked
91    pub fn is_locked(&self) -> bool {
92        self.enabled && self.locked_at.is_some() && self.lock_signature.is_some()
93    }
94
95    /// Check if the system is in grace period (locked but not yet enforced)
96    pub fn is_in_grace_period(&self, current_time: Timestamp) -> bool {
97        if let Some(locked_at) = self.locked_at {
98            let grace_end = locked_at.value() + self.grace_period_seconds;
99            current_time.value() < grace_end
100        } else {
101            false
102        }
103    }
104
105    /// Check if immutable mode is actively enforced
106    pub fn is_enforced(&self, current_time: Timestamp) -> bool {
107        self.is_locked() && !self.is_in_grace_period(current_time)
108    }
109
110    /// Set grace period (only allowed if not locked)
111    pub fn set_grace_period(&mut self, seconds: u64) -> Result<()> {
112        if self.is_locked() {
113            return Err(Error::Other(
114                "Cannot change grace period: system is locked".into(),
115            ));
116        }
117        self.grace_period_seconds = seconds;
118        Ok(())
119    }
120
121    /// Set governance override key (only allowed if not locked)
122    pub fn set_governance_key(&mut self, key: Vec<u8>) -> Result<()> {
123        if self.is_locked() {
124            return Err(Error::Other(
125                "Cannot set governance key: system is locked".into(),
126            ));
127        }
128        self.governance_key = Some(key);
129        Ok(())
130    }
131}
132
133/// System configuration that can be made immutable
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct LockableConfig {
136    /// Fee model parameters
137    pub fee_params: FeeModelParams,
138
139    /// Maximum total supply allowed
140    pub max_total_supply: u64,
141
142    /// Minimum balance required for accounts
143    pub min_account_balance: u64,
144
145    /// Network chain ID
146    pub chain_id: u64,
147
148    /// Protocol version
149    pub protocol_version: u32,
150
151    /// Whether negative balances are allowed (for special accounts)
152    pub allow_negative_balances: bool,
153}
154
155impl Default for LockableConfig {
156    fn default() -> Self {
157        Self {
158            fee_params: FeeModelParams::default(),
159            max_total_supply: u64::MAX,
160            min_account_balance: 0,
161            chain_id: 1,
162            protocol_version: 1,
163            allow_negative_balances: false,
164        }
165    }
166}
167
168impl LockableConfig {
169    /// Calculate hash of the configuration for signing
170    pub fn hash(&self) -> Result<Hash> {
171        let bytes =
172            bincode::serialize(self).map_err(|e| Error::SerializationError(e.to_string()))?;
173        let hash = blake3::hash(&bytes);
174        Ok(Hash::from_bytes(*hash.as_bytes()))
175    }
176
177    /// Validate that configuration is valid
178    pub fn validate(&self) -> Result<()> {
179        self.fee_params.validate()?;
180
181        if self.max_total_supply == 0 {
182            return Err(Error::Other(
183                "max_total_supply must be greater than 0".into(),
184            ));
185        }
186
187        if self.chain_id == 0 {
188            return Err(Error::Other("chain_id must be greater than 0".into()));
189        }
190
191        if self.protocol_version == 0 {
192            return Err(Error::Other(
193                "protocol_version must be greater than 0".into(),
194            ));
195        }
196
197        Ok(())
198    }
199}
200
201/// Immutable deployment manager
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ImmutableDeployment {
204    /// Immutable configuration
205    pub config: ImmutableConfig,
206
207    /// System configuration that can be locked
208    pub system_config: LockableConfig,
209}
210
211impl ImmutableDeployment {
212    /// Create a new immutable deployment manager
213    pub fn new() -> Self {
214        Self {
215            config: ImmutableConfig::new(),
216            system_config: LockableConfig::default(),
217        }
218    }
219
220    /// Create with custom configuration
221    pub fn with_config(system_config: LockableConfig) -> Result<Self> {
222        system_config.validate()?;
223        Ok(Self {
224            config: ImmutableConfig::new(),
225            system_config,
226        })
227    }
228
229    /// Enable immutable mode
230    pub fn enable_immutable_mode(&mut self) -> Result<()> {
231        self.system_config.validate()?;
232        self.config.enable();
233        Ok(())
234    }
235
236    /// Lock the system with quantum-resistant signature
237    #[cfg(feature = "std")]
238    pub fn lock_system(
239        &mut self,
240        keypair: &qudag_crypto::MlDsaKeyPair,
241        current_time: Timestamp,
242    ) -> Result<()> {
243        if !self.config.enabled {
244            return Err(Error::Other("Immutable mode not enabled".into()));
245        }
246
247        if self.config.is_locked() {
248            return Err(Error::Other("System is already locked".into()));
249        }
250
251        // Validate configuration before locking
252        self.system_config.validate()?;
253
254        // Calculate configuration hash
255        let config_hash = self.system_config.hash()?;
256
257        // Prepare message to sign (config hash + timestamp)
258        let mut message = Vec::new();
259        message.extend_from_slice(config_hash.as_bytes());
260        message.extend_from_slice(&current_time.value().to_le_bytes());
261
262        // Sign the message
263        let signature = keypair
264            .sign(&message, &mut rand::thread_rng())
265            .map_err(|e| Error::Other(format!("Signing failed: {:?}", e)))?;
266
267        let public_key = keypair
268            .to_public_key()
269            .map_err(|e| Error::Other(format!("Public key extraction failed: {:?}", e)))?;
270
271        // Create immutable signature
272        let immutable_sig = ImmutableSignature {
273            algorithm: "ML-DSA-87".to_string(),
274            public_key: public_key.as_bytes().to_vec(),
275            signature,
276            config_hash,
277        };
278
279        // Lock the system
280        self.config.locked_at = Some(current_time);
281        self.config.lock_signature = Some(immutable_sig);
282        self.config.locked_config_hash = Some(config_hash);
283
284        Ok(())
285    }
286
287    /// Verify the lock signature
288    #[cfg(feature = "std")]
289    pub fn verify_lock_signature(&self, current_time: Timestamp) -> Result<bool> {
290        let sig_data = self
291            .config
292            .lock_signature
293            .as_ref()
294            .ok_or_else(|| Error::Other("No lock signature present".into()))?;
295
296        let locked_at = self
297            .config
298            .locked_at
299            .ok_or_else(|| Error::Other("No lock timestamp present".into()))?;
300
301        // Recreate the signed message
302        let mut message = Vec::new();
303        message.extend_from_slice(sig_data.config_hash.as_bytes());
304        message.extend_from_slice(&locked_at.value().to_le_bytes());
305
306        // Create public key from bytes
307        let public_key = qudag_crypto::MlDsaPublicKey::from_bytes(&sig_data.public_key)
308            .map_err(|e| Error::Other(format!("Invalid public key: {:?}", e)))?;
309
310        // Verify the signature
311        match public_key.verify(&message, &sig_data.signature) {
312            Ok(()) => {
313                // Also verify that the config hash matches current config
314                let current_hash = self.system_config.hash()?;
315                Ok(current_hash == sig_data.config_hash)
316            }
317            Err(_) => Ok(false),
318        }
319    }
320
321    /// Check if configuration changes are allowed
322    pub fn can_modify_config(&self, current_time: Timestamp) -> bool {
323        !self.config.is_enforced(current_time)
324    }
325
326    /// Update fee parameters (only allowed if not locked)
327    pub fn update_fee_params(
328        &mut self,
329        params: FeeModelParams,
330        current_time: Timestamp,
331    ) -> Result<()> {
332        if !self.can_modify_config(current_time) {
333            return Err(Error::Other(
334                "Cannot modify configuration: system is immutably locked".into(),
335            ));
336        }
337
338        params.validate()?;
339        self.system_config.fee_params = params;
340        Ok(())
341    }
342
343    /// Update system configuration (only allowed if not locked)
344    pub fn update_system_config(
345        &mut self,
346        config: LockableConfig,
347        current_time: Timestamp,
348    ) -> Result<()> {
349        if !self.can_modify_config(current_time) {
350            return Err(Error::Other(
351                "Cannot modify configuration: system is immutably locked".into(),
352            ));
353        }
354
355        config.validate()?;
356        self.system_config = config;
357        Ok(())
358    }
359
360    /// Get system status for display
361    pub fn get_status(&self, current_time: Timestamp) -> ImmutableStatus {
362        ImmutableStatus {
363            enabled: self.config.enabled,
364            locked: self.config.is_locked(),
365            enforced: self.config.is_enforced(current_time),
366            in_grace_period: self.config.is_in_grace_period(current_time),
367            locked_at: self.config.locked_at,
368            grace_period_seconds: self.config.grace_period_seconds,
369            config_hash: self.config.locked_config_hash,
370        }
371    }
372
373    /// Emergency governance override (only with governance key)
374    #[cfg(feature = "std")]
375    pub fn governance_override(
376        &mut self,
377        governance_keypair: &qudag_crypto::MlDsaKeyPair,
378        current_time: Timestamp,
379    ) -> Result<()> {
380        let governance_key = self
381            .config
382            .governance_key
383            .as_ref()
384            .ok_or_else(|| Error::Other("No governance key set".into()))?;
385
386        // Verify governance key matches
387        let public_key = governance_keypair
388            .to_public_key()
389            .map_err(|e| Error::Other(format!("Governance key extraction failed: {:?}", e)))?;
390
391        if public_key.as_bytes() != governance_key {
392            return Err(Error::Other("Invalid governance key".into()));
393        }
394
395        // Unlock the system (emergency only)
396        self.config.locked_at = None;
397        self.config.lock_signature = None;
398        self.config.locked_config_hash = None;
399
400        Ok(())
401    }
402}
403
404impl Default for ImmutableDeployment {
405    fn default() -> Self {
406        Self::new()
407    }
408}
409
410/// Status information for immutable deployment
411#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct ImmutableStatus {
413    /// Whether immutable mode is enabled
414    pub enabled: bool,
415    /// Whether system is locked
416    pub locked: bool,
417    /// Whether immutable restrictions are enforced
418    pub enforced: bool,
419    /// Whether system is in grace period
420    pub in_grace_period: bool,
421    /// When system was locked
422    pub locked_at: Option<Timestamp>,
423    /// Grace period duration
424    pub grace_period_seconds: u64,
425    /// Hash of locked configuration
426    pub config_hash: Option<Hash>,
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432
433    #[test]
434    fn test_immutable_config_lifecycle() {
435        let mut config = ImmutableConfig::new();
436
437        // Initially not enabled or locked
438        assert!(!config.enabled);
439        assert!(!config.is_locked());
440
441        // Enable immutable mode
442        config.enable();
443        assert!(config.enabled);
444        assert!(!config.is_locked()); // Not locked yet
445
446        // Can disable if not locked
447        config.disable().unwrap();
448        assert!(!config.enabled);
449    }
450
451    #[test]
452    fn test_grace_period() {
453        let mut config = ImmutableConfig::new();
454        config.enable();
455
456        let lock_time = Timestamp::new(1000);
457        config.locked_at = Some(lock_time);
458        config.lock_signature = Some(ImmutableSignature {
459            algorithm: "ML-DSA-87".to_string(),
460            public_key: vec![1, 2, 3],
461            signature: vec![4, 5, 6],
462            config_hash: Hash::from_bytes([0u8; 32]),
463        });
464
465        // During grace period
466        let grace_time = Timestamp::new(1000 + 12 * 60 * 60); // 12 hours later
467        assert!(config.is_locked());
468        assert!(config.is_in_grace_period(grace_time));
469        assert!(!config.is_enforced(grace_time));
470
471        // After grace period
472        let post_grace = Timestamp::new(1000 + 25 * 60 * 60); // 25 hours later
473        assert!(config.is_locked());
474        assert!(!config.is_in_grace_period(post_grace));
475        assert!(config.is_enforced(post_grace));
476    }
477
478    #[test]
479    fn test_lockable_config_validation() {
480        let mut config = LockableConfig::default();
481        assert!(config.validate().is_ok());
482
483        // Test invalid fee params
484        config.fee_params.f_min = -0.1;
485        assert!(config.validate().is_err());
486
487        // Reset and test other fields
488        config = LockableConfig::default();
489        config.max_total_supply = 0;
490        assert!(config.validate().is_err());
491
492        config = LockableConfig::default();
493        config.chain_id = 0;
494        assert!(config.validate().is_err());
495    }
496
497    #[test]
498    fn test_lockable_config_hash() {
499        let config1 = LockableConfig::default();
500        let config2 = LockableConfig::default();
501
502        // Same configs should have same hash
503        assert_eq!(config1.hash().unwrap(), config2.hash().unwrap());
504
505        // Different configs should have different hashes
506        let mut config3 = LockableConfig::default();
507        config3.chain_id = 42;
508        assert_ne!(config1.hash().unwrap(), config3.hash().unwrap());
509    }
510
511    #[test]
512    fn test_immutable_deployment_lifecycle() {
513        let mut deployment = ImmutableDeployment::new();
514        let current_time = Timestamp::new(1000);
515
516        // Initially can modify
517        assert!(deployment.can_modify_config(current_time));
518
519        // Enable immutable mode
520        deployment.enable_immutable_mode().unwrap();
521        assert!(deployment.can_modify_config(current_time)); // Still can modify until locked
522
523        // Update fee params should work before locking
524        let mut new_params = FeeModelParams::default();
525        new_params.f_min = 0.002;
526        deployment
527            .update_fee_params(new_params, current_time)
528            .unwrap();
529        assert_eq!(deployment.system_config.fee_params.f_min, 0.002);
530    }
531
532    #[test]
533    fn test_config_modification_restrictions() {
534        let mut deployment = ImmutableDeployment::new();
535        deployment.enable_immutable_mode().unwrap();
536
537        let current_time = Timestamp::new(1000);
538
539        // Simulate locked state (without actual signature)
540        deployment.config.locked_at = Some(current_time);
541        deployment.config.lock_signature = Some(ImmutableSignature {
542            algorithm: "ML-DSA-87".to_string(),
543            public_key: vec![1, 2, 3],
544            signature: vec![4, 5, 6],
545            config_hash: Hash::from_bytes([0u8; 32]),
546        });
547
548        // Should not be able to modify after grace period
549        let post_grace = Timestamp::new(current_time.value() + 25 * 60 * 60);
550        assert!(!deployment.can_modify_config(post_grace));
551
552        let new_params = FeeModelParams::default();
553        let result = deployment.update_fee_params(new_params, post_grace);
554        assert!(result.is_err());
555        assert!(result.unwrap_err().to_string().contains("immutably locked"));
556    }
557
558    #[test]
559    fn test_status_reporting() {
560        let mut deployment = ImmutableDeployment::new();
561        let current_time = Timestamp::new(1000);
562
563        // Initial status
564        let status = deployment.get_status(current_time);
565        assert!(!status.enabled);
566        assert!(!status.locked);
567        assert!(!status.enforced);
568
569        // After enabling
570        deployment.enable_immutable_mode().unwrap();
571        let status = deployment.get_status(current_time);
572        assert!(status.enabled);
573        assert!(!status.locked);
574        assert!(!status.enforced);
575    }
576}