/ node / consensus / src / clp / challenge.rs
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  }