pot_o_mining/
neural_path.rs1use ai3_lib::tensor::Tensor;
4use pot_o_core::TribeResult;
5use sha2::{Digest, Sha256};
6
7pub struct NeuralPathValidator {
14 pub layer_widths: Vec<usize>,
16}
17
18impl Default for NeuralPathValidator {
19 fn default() -> Self {
20 Self {
21 layer_widths: vec![32, 16, 8],
22 }
23 }
24}
25
26impl NeuralPathValidator {
27 pub fn expected_path_signature(&self, challenge_hash: &str) -> Vec<u8> {
30 let hash_bytes = hex::decode(challenge_hash).unwrap_or_default();
31 let total_neurons: usize = self.layer_widths.iter().sum();
32 let mut sig = Vec::with_capacity(total_neurons);
33
34 let mut hasher = Sha256::new();
35 hasher.update(&hash_bytes);
36 let mut seed = hasher.finalize().to_vec();
37
38 for &width in &self.layer_widths {
39 for i in 0..width {
40 let byte_idx = i % seed.len();
41 let bit = (seed[byte_idx] >> (i % 8)) & 1;
42 sig.push(bit);
43 }
44 let mut h = Sha256::new();
46 h.update(&seed);
47 seed = h.finalize().to_vec();
48 }
49
50 sig
51 }
52
53 pub fn compute_actual_path(&self, tensor: &Tensor, nonce: u64) -> TribeResult<Vec<u8>> {
57 let mut activations = tensor.data.as_f32();
58 let mut path_bits = Vec::new();
59 let mut bit_idx: u32 = 0;
60
61 for &width in &self.layer_widths {
62 let mut layer_output = vec![0.0f32; width];
63
64 let stride = (activations.len() / width).max(1);
66 for (j, out) in layer_output.iter_mut().enumerate() {
67 if j >= width {
68 break;
69 }
70 let start = j * stride;
71 let end = (start + stride).min(activations.len());
72 let sum: f32 = activations[start..end].iter().sum();
73 let relu = sum.max(0.0);
75 *out = relu;
76
77 let base_bit = if relu > 0.0 { 1u8 } else { 0u8 };
78 let shift = (bit_idx % 64) as u64;
79 let nonce_bit = ((nonce >> shift) & 1) as u8;
80 let bit = base_bit ^ nonce_bit;
81
82 path_bits.push(bit);
83 bit_idx = bit_idx.wrapping_add(1);
84 }
85
86 activations = layer_output;
87 }
88
89 Ok(path_bits)
90 }
91
92 pub fn hamming_distance(a: &[u8], b: &[u8]) -> u32 {
94 a.iter()
95 .zip(b.iter())
96 .map(|(&x, &y)| if x != y { 1u32 } else { 0u32 })
97 .sum()
98 }
99
100 pub fn validate(&self, actual_path: &[u8], challenge_hash: &str, max_distance: u32) -> bool {
102 let expected = self.expected_path_signature(challenge_hash);
103 let min_len = actual_path.len().min(expected.len());
104 let distance = Self::hamming_distance(&actual_path[..min_len], &expected[..min_len]);
105 distance <= max_distance
106 }
107
108 pub fn path_to_hex(path: &[u8]) -> String {
110 let mut bytes = Vec::with_capacity(path.len().div_ceil(8));
111 for chunk in path.chunks(8) {
112 let mut byte = 0u8;
113 for (i, &bit) in chunk.iter().enumerate() {
114 if bit != 0 {
115 byte |= 1 << i;
116 }
117 }
118 bytes.push(byte);
119 }
120 hex::encode(bytes)
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use ai3_lib::tensor::{TensorData, TensorShape};
128
129 #[test]
130 fn test_expected_path_deterministic() {
131 let v = NeuralPathValidator::default();
132 let hash = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789";
133 let p1 = v.expected_path_signature(hash);
134 let p2 = v.expected_path_signature(hash);
135 assert_eq!(p1, p2);
136 }
137
138 #[test]
139 fn test_hamming_distance() {
140 assert_eq!(
141 NeuralPathValidator::hamming_distance(&[0, 1, 0], &[0, 1, 0]),
142 0
143 );
144 assert_eq!(
145 NeuralPathValidator::hamming_distance(&[0, 1, 0], &[1, 0, 1]),
146 3
147 );
148 assert_eq!(
149 NeuralPathValidator::hamming_distance(&[1, 1, 1], &[0, 1, 0]),
150 2
151 );
152 }
153
154 #[test]
155 fn test_actual_path_varies_with_nonce() {
156 let v = NeuralPathValidator::default();
157 let t = Tensor::new(TensorShape::new(vec![64]), TensorData::F32(vec![0.5; 64])).unwrap();
158 let p1 = v.compute_actual_path(&t, 0).unwrap();
159 let p2 = v.compute_actual_path(&t, 999_999).unwrap();
160 assert_ne!(p1, p2);
162 }
163
164 #[test]
165 fn test_path_hex_roundtrip() {
166 let path = vec![1, 0, 1, 1, 0, 0, 1, 0, 1];
167 let hex_str = NeuralPathValidator::path_to_hex(&path);
168 assert!(!hex_str.is_empty());
169 }
170}