diff options
| author | gabrix73 <gabriel1@frozenstar.info> | 2026-06-01 18:41:36 +0200 |
|---|---|---|
| committer | gabrix73 <gabriel1@frozenstar.info> | 2026-06-01 18:41:36 +0200 |
| commit | 9f5d864d533ce86459e654f5d78212933c0269ea (patch) | |
| tree | 4b71e8c7ab4e78da93e5f3164803da4de68c7031 /inheritance/src/contract.rs | |
| download | onioncoin-main.tar.gz onioncoin-main.tar.xz onioncoin-main.zip | |
OnionCoin is a privacy cryptocurrency that rewards Tor relay operators
through a unique Proof-of-Contribution consensus mechanism.
Core Features:
- Proof-of-Relay: 30% of block rewards go to Tor operators
- Native .onion node identity (no IP exposure)
- Temporal obfuscation protocols
- Dandelion++ over Tor propagation
- Native inheritance system with dead man's switch
Technical Stack:
- Rust workspace with 8 crates
- Ed25519/X25519 cryptography
- arti (Rust Tor client) integration planned
- 10 minute block time, 5-10 TPS design
Status: Prototype
- Consensus logic complete with passing tests (30/33)
- Network layer conceptual design complete
- Tor integration pending
- Testnet launch planned Q3 2026
License: MIT
Author: Gabriele Salati (virebent)
Contact: g48rix@gmail.com
Website: https://www.gabrielesalati.eu
Repository: https://git.virebent.art/virebent/onioncoin
Diffstat (limited to 'inheritance/src/contract.rs')
| -rw-r--r-- | inheritance/src/contract.rs | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/inheritance/src/contract.rs b/inheritance/src/contract.rs new file mode 100644 index 0000000..46d0ae0 --- /dev/null +++ b/inheritance/src/contract.rs @@ -0,0 +1,561 @@ +use serde::{Deserialize, Serialize}; +use crate::unlock::UnlockSchedule; +use blake3::Hasher; + +/// Time-locked inheritance contract for OnionCoin +/// WORLD'S FIRST native blockchain inheritance system! +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InheritanceContract { + /// Contract ID (hash of owner + creation time) + pub contract_id: [u8; 32], + + /// Owner's public key + pub owner: [u8; 32], + + /// Beneficiaries with their inheritance percentages + pub beneficiaries: Vec<Beneficiary>, + + /// Configuration + pub config: InheritanceConfig, + + /// Last heartbeat timestamp + pub last_heartbeat: i64, + + /// Contract creation time + pub created_at: i64, + + /// Current status + pub status: ContractStatus, + + /// Unlock schedule (optional for progressive unlock) + pub unlock_schedule: Option<UnlockSchedule>, + + /// Encrypted data (for privacy) + pub encrypted_metadata: Option<Vec<u8>>, + + /// Dispute flag (owner can dispute if alive) + pub dispute_active: bool, + + /// Total amount locked in contract + pub locked_amount: u64, +} + +/// A beneficiary who will receive inheritance +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Beneficiary { + /// Beneficiary's public key + pub pubkey: [u8; 32], + + /// Percentage of inheritance (0-100) + pub percentage: u8, + + /// Optional encrypted name/info + pub encrypted_info: Option<Vec<u8>>, + + /// Has this beneficiary been notified? + pub notified: bool, + + /// Amount unlocked so far + pub unlocked_amount: u64, +} + +impl Beneficiary { + pub fn new(pubkey: [u8; 32], percentage: u8) -> Result<Self, InheritanceError> { + if percentage > 100 { + return Err(InheritanceError::InvalidPercentage(percentage)); + } + + Ok(Self { + pubkey, + percentage, + encrypted_info: None, + notified: false, + unlocked_amount: 0, + }) + } + + /// Calculate absolute amount for this beneficiary + pub fn calculate_amount(&self, total: u64) -> u64 { + (total as f64 * (self.percentage as f64 / 100.0)) as u64 + } +} + +/// Contract configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InheritanceConfig { + /// Heartbeat interval (seconds) - how often owner must check in + /// Default: 90 days + pub heartbeat_interval: i64, + + /// Grace period after missed heartbeat (seconds) + /// Default: 30 days + pub grace_period: i64, + + /// Enable progressive unlock + pub progressive_unlock: bool, + + /// Require Tor-only access for heartbeat + pub tor_only: bool, + + /// Enable encrypted beneficiary list + pub encrypted_beneficiaries: bool, + + /// Require multi-signature for unlock + pub multi_sig_required: bool, + + /// Number of signatures required (if multi_sig enabled) + pub required_signatures: u32, + + /// Enable Shamir secret sharing + pub shamir_enabled: bool, + + /// Shamir threshold (M of N) + pub shamir_threshold: Option<(u8, u8)>, +} + +impl Default for InheritanceConfig { + fn default() -> Self { + Self { + heartbeat_interval: 90 * 24 * 3600, // 90 days + grace_period: 30 * 24 * 3600, // 30 days + progressive_unlock: true, // Default ON + tor_only: true, // Privacy first! + encrypted_beneficiaries: true, + multi_sig_required: false, + required_signatures: 0, + shamir_enabled: false, + shamir_threshold: None, + } + } +} + +impl InheritanceConfig { + /// Total time before unlock starts + pub fn total_timeout(&self) -> i64 { + self.heartbeat_interval + self.grace_period + } + + /// Create a secure config (maximum protections) + pub fn secure() -> Self { + Self { + heartbeat_interval: 180 * 24 * 3600, // 6 months + grace_period: 60 * 24 * 3600, // 2 months + progressive_unlock: true, + tor_only: true, + encrypted_beneficiaries: true, + multi_sig_required: true, + required_signatures: 2, + shamir_enabled: true, + shamir_threshold: Some((3, 5)), // 3 of 5 + } + } + + /// Create a quick config (for testing or urgent cases) + pub fn quick() -> Self { + Self { + heartbeat_interval: 30 * 24 * 3600, // 30 days + grace_period: 7 * 24 * 3600, // 7 days + progressive_unlock: true, + tor_only: false, + encrypted_beneficiaries: false, + multi_sig_required: false, + required_signatures: 0, + shamir_enabled: false, + shamir_threshold: None, + } + } +} + +/// Contract status +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum ContractStatus { + /// Contract is active, owner is alive + Active, + + /// Heartbeat missed, grace period active + GracePeriod, + + /// Grace period expired, unlocking in progress + Unlocking, + + /// Fully unlocked, inheritance distributed + Completed, + + /// Owner disputed (proved they're alive) + Disputed, + + /// Contract cancelled by owner + Cancelled, +} + +impl InheritanceContract { + /// Create a new inheritance contract + pub fn new( + owner: [u8; 32], + beneficiaries: Vec<Beneficiary>, + config: InheritanceConfig, + locked_amount: u64, + current_time: i64, + ) -> Result<Self, InheritanceError> { + // Validate beneficiaries + Self::validate_beneficiaries(&beneficiaries)?; + + // Generate contract ID + let contract_id = Self::generate_contract_id(&owner, current_time); + + Ok(Self { + contract_id, + owner, + beneficiaries, + config, + last_heartbeat: current_time, + created_at: current_time, + status: ContractStatus::Active, + unlock_schedule: None, + encrypted_metadata: None, + dispute_active: false, + locked_amount, + }) + } + + /// Generate unique contract ID + fn generate_contract_id(owner: &[u8; 32], created_at: i64) -> [u8; 32] { + let mut hasher = Hasher::new(); + hasher.update(owner); + hasher.update(&created_at.to_le_bytes()); + *hasher.finalize().as_bytes() + } + + /// Validate beneficiaries list + fn validate_beneficiaries(beneficiaries: &[Beneficiary]) -> Result<(), InheritanceError> { + if beneficiaries.is_empty() { + return Err(InheritanceError::NoBeneficiaries); + } + + // Check total percentage = 100% + let total: u32 = beneficiaries.iter().map(|b| b.percentage as u32).sum(); + if total != 100 { + return Err(InheritanceError::InvalidTotalPercentage(total)); + } + + // Check for duplicate beneficiaries + let mut seen = std::collections::HashSet::new(); + for b in beneficiaries { + if !seen.insert(b.pubkey) { + return Err(InheritanceError::DuplicateBeneficiary); + } + } + + Ok(()) + } + + /// Record a heartbeat (owner is alive) + pub fn heartbeat(&mut self, current_time: i64) -> Result<(), InheritanceError> { + // Only owner can heartbeat + if self.status == ContractStatus::Cancelled { + return Err(InheritanceError::ContractCancelled); + } + + if self.status == ContractStatus::Completed { + return Err(InheritanceError::ContractCompleted); + } + + self.last_heartbeat = current_time; + + // Reset status if was in grace period + if self.status == ContractStatus::GracePeriod { + self.status = ContractStatus::Active; + } + + Ok(()) + } + + /// Check if heartbeat timeout has been reached + pub fn is_timeout(&self, current_time: i64) -> bool { + let elapsed = current_time - self.last_heartbeat; + elapsed >= self.config.heartbeat_interval + } + + /// Check if grace period has expired + pub fn is_grace_expired(&self, current_time: i64) -> bool { + let elapsed = current_time - self.last_heartbeat; + elapsed >= self.config.total_timeout() + } + + /// Update contract status based on time + pub fn update_status(&mut self, current_time: i64) { + if self.is_grace_expired(current_time) { + if self.status != ContractStatus::Unlocking + && self.status != ContractStatus::Completed + && self.status != ContractStatus::Cancelled { + self.status = ContractStatus::Unlocking; + } + } else if self.is_timeout(current_time) { + if self.status == ContractStatus::Active { + self.status = ContractStatus::GracePeriod; + } + } + } + + /// File a dispute (owner proves they're alive) + pub fn file_dispute(&mut self, current_time: i64) -> Result<(), InheritanceError> { + if self.status == ContractStatus::Completed { + return Err(InheritanceError::ContractCompleted); + } + + self.dispute_active = true; + self.status = ContractStatus::Disputed; + self.last_heartbeat = current_time; // Reset heartbeat + + Ok(()) + } + + /// Cancel contract (owner decides to cancel) + pub fn cancel(&mut self) -> Result<u64, InheritanceError> { + if self.status == ContractStatus::Completed { + return Err(InheritanceError::ContractCompleted); + } + + self.status = ContractStatus::Cancelled; + + // Return locked amount to owner + let amount = self.locked_amount; + self.locked_amount = 0; + + Ok(amount) + } + + /// Calculate time until unlock + pub fn time_until_unlock(&self, current_time: i64) -> i64 { + let total_timeout = self.config.total_timeout(); + let elapsed = current_time - self.last_heartbeat; + (total_timeout - elapsed).max(0) + } + + /// Get human-readable status + pub fn status_description(&self, current_time: i64) -> String { + match self.status { + ContractStatus::Active => { + let days = self.time_until_unlock(current_time) / (24 * 3600); + format!("Active - {} days until timeout", days) + } + ContractStatus::GracePeriod => { + let days = self.time_until_unlock(current_time) / (24 * 3600); + format!("Grace Period - {} days remaining", days) + } + ContractStatus::Unlocking => "Unlocking in progress".to_string(), + ContractStatus::Completed => "Inheritance distributed".to_string(), + ContractStatus::Disputed => "Disputed by owner".to_string(), + ContractStatus::Cancelled => "Cancelled".to_string(), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum InheritanceError { + #[error("No beneficiaries specified")] + NoBeneficiaries, + + #[error("Invalid beneficiary percentage: {0} (must be 0-100)")] + InvalidPercentage(u8), + + #[error("Total percentage must be 100%, got {0}%")] + InvalidTotalPercentage(u32), + + #[error("Duplicate beneficiary detected")] + DuplicateBeneficiary, + + #[error("Contract already cancelled")] + ContractCancelled, + + #[error("Contract already completed")] + ContractCompleted, + + #[error("Heartbeat timeout not reached")] + TimeoutNotReached, + + #[error("Grace period not expired")] + GracePeriodNotExpired, + + #[error("Insufficient locked amount")] + InsufficientAmount, + + #[error("Unauthorized access")] + Unauthorized, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_beneficiary_creation() { + let beneficiary = Beneficiary::new([1u8; 32], 50).unwrap(); + assert_eq!(beneficiary.percentage, 50); + + let invalid = Beneficiary::new([1u8; 32], 101); + assert!(invalid.is_err()); + } + + #[test] + fn test_beneficiary_amount_calculation() { + let beneficiary = Beneficiary::new([1u8; 32], 25).unwrap(); + assert_eq!(beneficiary.calculate_amount(1000), 250); + } + + #[test] + fn test_contract_creation() { + let beneficiaries = vec![ + Beneficiary::new([1u8; 32], 60).unwrap(), + Beneficiary::new([2u8; 32], 40).unwrap(), + ]; + + let contract = InheritanceContract::new( + [0u8; 32], + beneficiaries, + InheritanceConfig::default(), + 10000, + 0, + ); + + assert!(contract.is_ok()); + } + + #[test] + fn test_invalid_total_percentage() { + let beneficiaries = vec![ + Beneficiary::new([1u8; 32], 60).unwrap(), + Beneficiary::new([2u8; 32], 30).unwrap(), // Total = 90%, invalid + ]; + + let contract = InheritanceContract::new( + [0u8; 32], + beneficiaries, + InheritanceConfig::default(), + 10000, + 0, + ); + + assert!(matches!( + contract.unwrap_err(), + InheritanceError::InvalidTotalPercentage(90) + )); + } + + #[test] + fn test_heartbeat() { + let beneficiaries = vec![Beneficiary::new([1u8; 32], 100).unwrap()]; + + let mut contract = InheritanceContract::new( + [0u8; 32], + beneficiaries, + InheritanceConfig::default(), + 10000, + 0, + ) + .unwrap(); + + // Record heartbeat + contract.heartbeat(100).unwrap(); + assert_eq!(contract.last_heartbeat, 100); + } + + #[test] + fn test_timeout_detection() { + let beneficiaries = vec![Beneficiary::new([1u8; 32], 100).unwrap()]; + + let config = InheritanceConfig { + heartbeat_interval: 1000, + grace_period: 500, + ..Default::default() + }; + + let contract = InheritanceContract::new([0u8; 32], beneficiaries, config, 10000, 0) + .unwrap(); + + assert!(!contract.is_timeout(500)); + assert!(contract.is_timeout(1001)); + } + + #[test] + fn test_grace_period() { + let beneficiaries = vec![Beneficiary::new([1u8; 32], 100).unwrap()]; + + let config = InheritanceConfig { + heartbeat_interval: 1000, + grace_period: 500, + ..Default::default() + }; + + let contract = InheritanceContract::new([0u8; 32], beneficiaries, config, 10000, 0) + .unwrap(); + + assert!(!contract.is_grace_expired(1400)); + assert!(contract.is_grace_expired(1501)); + } + + #[test] + fn test_status_updates() { + let beneficiaries = vec![Beneficiary::new([1u8; 32], 100).unwrap()]; + + let config = InheritanceConfig { + heartbeat_interval: 100, + grace_period: 50, + ..Default::default() + }; + + let mut contract = InheritanceContract::new([0u8; 32], beneficiaries, config, 10000, 0) + .unwrap(); + + // Initially active + assert_eq!(contract.status, ContractStatus::Active); + + // After timeout, should enter grace period + contract.update_status(101); + assert_eq!(contract.status, ContractStatus::GracePeriod); + + // After grace period, should start unlocking + contract.update_status(151); + assert_eq!(contract.status, ContractStatus::Unlocking); + } + + #[test] + fn test_dispute() { + let beneficiaries = vec![Beneficiary::new([1u8; 32], 100).unwrap()]; + + let mut contract = InheritanceContract::new( + [0u8; 32], + beneficiaries, + InheritanceConfig::default(), + 10000, + 0, + ) + .unwrap(); + + contract.file_dispute(1000).unwrap(); + + assert_eq!(contract.status, ContractStatus::Disputed); + assert!(contract.dispute_active); + assert_eq!(contract.last_heartbeat, 1000); + } + + #[test] + fn test_cancel_contract() { + let beneficiaries = vec![Beneficiary::new([1u8; 32], 100).unwrap()]; + + let mut contract = InheritanceContract::new( + [0u8; 32], + beneficiaries, + InheritanceConfig::default(), + 10000, + 0, + ) + .unwrap(); + + let returned = contract.cancel().unwrap(); + + assert_eq!(returned, 10000); + assert_eq!(contract.status, ContractStatus::Cancelled); + assert_eq!(contract.locked_amount, 0); + } +} |
