challenge.rs
1 // Copyright (c) 2025-2026 ACDC Network 2 // This file is part of the alphaos library. 3 // 4 // Alpha Chain | Delta Chain Protocol 5 // International Monetary Graphite. 6 // 7 // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com). 8 // They built world-class ZK infrastructure. We installed the EASY button. 9 // Their cryptography: elegant. Our modifications: bureaucracy-compatible. 10 // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours. 11 // 12 // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0 13 // All modifications and new work: CC0 1.0 Universal Public Domain Dedication. 14 // No rights reserved. No permission required. No warranty. No refunds. 15 // 16 // https://creativecommons.org/publicdomain/zero/1.0/ 17 // SPDX-License-Identifier: CC0-1.0 18 19 //! Challenge generation for CLP. 20 21 use super::{ChallengeId, ClpChallenge, ClpConfig}; 22 use std::{ 23 collections::hash_map::DefaultHasher, 24 hash::{Hash, Hasher}, 25 }; 26 use tracing::info; 27 28 /// Generates CLP challenges. 29 pub struct ChallengeGenerator { 30 config: ClpConfig, 31 } 32 33 impl ChallengeGenerator { 34 /// Create a new challenge generator. 35 pub fn new(config: ClpConfig) -> Self { 36 Self { config } 37 } 38 39 /// Generate a challenge for the current round. 40 /// 41 /// The challenge is deterministic based on: 42 /// - Block hash of the triggering block 43 /// - Current epoch number 44 /// - Current round number 45 pub fn generate(&self, block_hash: &[u8; 32], epoch: u64, round: u64, current_block: u64) -> ClpChallenge { 46 // Generate challenge value: hash(block_hash || epoch || round) 47 let value = self.compute_challenge_value(block_hash, epoch, round); 48 49 // Generate challenge ID from the value 50 let id = ChallengeId(self.hash_to_bytes(&value)); 51 52 // Calculate deadline based on block time (assuming ~10s blocks) 53 // Response window is 30s = ~3 blocks 54 let blocks_for_response = self.config.response_window_secs / 10; 55 let deadline_block = current_block + blocks_for_response; 56 57 let challenge = ClpChallenge { id, value, issued_at_block: current_block, epoch, round, deadline_block }; 58 59 info!( 60 "Generated CLP challenge {} for epoch {} round {} (deadline: block {})", 61 challenge.id, epoch, round, deadline_block 62 ); 63 64 challenge 65 } 66 67 /// Check if a new challenge should be generated based on block height. 68 /// 69 /// Challenges are generated every CLP_INTERVAL seconds. 70 /// With 10s blocks, that's every 6 blocks. 71 pub fn should_generate(&self, block_height: u64, last_challenge_block: u64) -> bool { 72 // Approximate blocks per interval (assuming 10s blocks) 73 let blocks_per_interval = self.config.interval_secs / 10; 74 block_height >= last_challenge_block + blocks_per_interval 75 } 76 77 /// Compute the challenge value from inputs. 78 fn compute_challenge_value(&self, block_hash: &[u8; 32], epoch: u64, round: u64) -> [u8; 32] { 79 let mut hasher = DefaultHasher::new(); 80 block_hash.hash(&mut hasher); 81 epoch.hash(&mut hasher); 82 round.hash(&mut hasher); 83 // Add domain separator 84 b"CLP_CHALLENGE_V1".hash(&mut hasher); 85 86 self.hash_to_bytes(&hasher.finish().to_le_bytes()) 87 } 88 89 /// Convert hash output to 32 bytes. 90 fn hash_to_bytes(&self, input: &[u8]) -> [u8; 32] { 91 let mut result = [0u8; 32]; 92 let mut hasher = DefaultHasher::new(); 93 input.hash(&mut hasher); 94 let h1 = hasher.finish(); 95 96 hasher = DefaultHasher::new(); 97 h1.hash(&mut hasher); 98 let h2 = hasher.finish(); 99 100 hasher = DefaultHasher::new(); 101 h2.hash(&mut hasher); 102 let h3 = hasher.finish(); 103 104 hasher = DefaultHasher::new(); 105 h3.hash(&mut hasher); 106 let h4 = hasher.finish(); 107 108 result[0..8].copy_from_slice(&h1.to_le_bytes()); 109 result[8..16].copy_from_slice(&h2.to_le_bytes()); 110 result[16..24].copy_from_slice(&h3.to_le_bytes()); 111 result[24..32].copy_from_slice(&h4.to_le_bytes()); 112 113 result 114 } 115 } 116 117 #[cfg(test)] 118 mod tests { 119 use super::*; 120 121 #[test] 122 fn test_challenge_generation() { 123 let config = ClpConfig::default(); 124 let generator = ChallengeGenerator::new(config); 125 126 let block_hash = [1u8; 32]; 127 let challenge = generator.generate(&block_hash, 1, 1, 100); 128 129 assert_eq!(challenge.issued_at_block, 100); 130 assert_eq!(challenge.epoch, 1); 131 assert_eq!(challenge.round, 1); 132 assert!(challenge.deadline_block > 100); 133 } 134 135 #[test] 136 fn test_challenge_determinism() { 137 let config = ClpConfig::default(); 138 let generator = ChallengeGenerator::new(config); 139 140 let block_hash = [42u8; 32]; 141 142 let c1 = generator.generate(&block_hash, 5, 10, 500); 143 let c2 = generator.generate(&block_hash, 5, 10, 500); 144 145 // Same inputs should produce same challenge value 146 assert_eq!(c1.value, c2.value); 147 assert_eq!(c1.id.0, c2.id.0); 148 } 149 150 #[test] 151 fn test_should_generate() { 152 let config = ClpConfig::default(); 153 let generator = ChallengeGenerator::new(config); 154 155 // With 60s interval and 10s blocks, should generate every 6 blocks 156 assert!(!generator.should_generate(100, 100)); // Same block 157 assert!(!generator.should_generate(105, 100)); // Only 5 blocks 158 assert!(generator.should_generate(106, 100)); // 6 blocks 159 assert!(generator.should_generate(200, 100)); // Way past 160 } 161 }