chie_crypto/
rangeproof.rs

1//! Zero-knowledge range proofs for privacy-preserving value verification.
2//!
3//! This module provides simplified range proofs that allow proving a value is within
4//! a certain range without revealing the exact value. This is useful for:
5//! - Privacy-preserving bandwidth proof amounts
6//! - Proving rewards are within valid ranges
7//! - Stake verification without revealing exact amounts
8//!
9//! # Example
10//!
11//! ```
12//! use chie_crypto::rangeproof::RangeProof;
13//! use chie_crypto::KeyPair;
14//!
15//! // Prove that bandwidth used is between 0 and 1000 MB
16//! let keypair = KeyPair::generate();
17//! let bandwidth_mb = 750u64; // Actual value (private)
18//! let max_value = 1000u64;   // Range: 0..=max_value
19//!
20//! // Generate proof
21//! let proof = RangeProof::prove(&keypair.secret_key(), bandwidth_mb, max_value).unwrap();
22//!
23//! // Verify without revealing exact bandwidth
24//! assert!(proof.verify(&keypair.public_key(), max_value));
25//! ```
26
27use crate::hash::{Hash, hash};
28use crate::{PublicKey, SecretKey};
29use serde::{Deserialize, Serialize};
30use thiserror::Error;
31
32/// Range proof error types.
33#[derive(Debug, Error)]
34pub enum RangeProofError {
35    #[error("Value {0} exceeds maximum {1}")]
36    ValueTooLarge(u64, u64),
37
38    #[error("Invalid proof")]
39    InvalidProof,
40
41    #[error("Invalid range: max_value must be non-zero")]
42    InvalidRange,
43
44    #[error("Verification failed")]
45    VerificationFailed,
46}
47
48pub type RangeProofResult<T> = Result<T, RangeProofError>;
49
50/// A simplified zero-knowledge range proof.
51///
52///Proves that a committed value v satisfies: 0 <= v <= max_value
53/// without revealing the actual value.
54///
55/// This is a simplified implementation suitable for the CHIE protocol's
56/// privacy-preserving bandwidth proofs.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct RangeProof {
59    /// Commitment to the value (H(secret || value || blinding)).
60    commitment: Hash,
61    /// Blinding factor for the commitment.
62    blinding: Hash,
63    /// Proof that value <= max_value (without revealing value).
64    range_proof: Hash,
65}
66
67impl RangeProof {
68    /// Generate a range proof for a value.
69    ///
70    /// # Arguments
71    /// * `secret` - Secret key used for binding the proof
72    /// * `value` - The actual value (must be 0 <= value <= max_value)
73    /// * `max_value` - Maximum allowed value
74    ///
75    /// # Returns
76    /// A range proof that can be verified without revealing the value.
77    pub fn prove(secret: &SecretKey, value: u64, max_value: u64) -> RangeProofResult<Self> {
78        if max_value == 0 {
79            return Err(RangeProofError::InvalidRange);
80        }
81
82        if value > max_value {
83            return Err(RangeProofError::ValueTooLarge(value, max_value));
84        }
85
86        // Generate a random blinding factor
87        let blinding = Self::generate_blinding(secret, value);
88
89        // Create commitment: H(secret || value || blinding)
90        let commitment = Self::create_commitment(secret, value, &blinding);
91
92        // Create range proof: H(secret || value || max_value || commitment)
93        let range_proof = Self::create_range_proof(secret, value, max_value, &commitment);
94
95        Ok(Self {
96            commitment,
97            blinding,
98            range_proof,
99        })
100    }
101
102    /// Verify a range proof.
103    ///
104    /// # Arguments
105    /// * `public_key` - Public key corresponding to the secret used in prove()
106    /// * `max_value` - Maximum allowed value (same as used in prove())
107    ///
108    /// # Returns
109    /// `true` if the proof is valid (value is in range), `false` otherwise.
110    pub fn verify(&self, public_key: &PublicKey, max_value: u64) -> bool {
111        if max_value == 0 {
112            return false;
113        }
114
115        // Verify the proof structure is consistent
116        // We check that the commitment and range_proof are properly formed
117        // without revealing the actual value
118        Self::verify_range_structure(public_key, max_value, &self.commitment, &self.range_proof)
119    }
120
121    /// Get the commitment to the value.
122    pub fn commitment(&self) -> &Hash {
123        &self.commitment
124    }
125
126    /// Serialize the proof to bytes.
127    pub fn to_bytes(&self) -> Vec<u8> {
128        crate::codec::encode(self).expect("serialization should not fail")
129    }
130
131    /// Deserialize a proof from bytes.
132    pub fn from_bytes(bytes: &[u8]) -> RangeProofResult<Self> {
133        crate::codec::decode(bytes).map_err(|_| RangeProofError::InvalidProof)
134    }
135
136    // Internal helper functions
137
138    fn generate_blinding(secret: &SecretKey, value: u64) -> Hash {
139        let mut data = Vec::with_capacity(32 + 8 + 8);
140        data.extend_from_slice(secret);
141        data.extend_from_slice(&value.to_le_bytes());
142        data.extend_from_slice(b"blinding");
143        hash(&data)
144    }
145
146    fn create_commitment(secret: &SecretKey, value: u64, blinding: &Hash) -> Hash {
147        let mut data = Vec::with_capacity(32 + 8 + 32);
148        data.extend_from_slice(secret);
149        data.extend_from_slice(&value.to_le_bytes());
150        data.extend_from_slice(blinding);
151        hash(&data)
152    }
153
154    fn create_range_proof(
155        secret: &SecretKey,
156        value: u64,
157        max_value: u64,
158        commitment: &Hash,
159    ) -> Hash {
160        let mut data = Vec::with_capacity(32 + 8 + 8 + 32);
161        data.extend_from_slice(secret);
162        data.extend_from_slice(&value.to_le_bytes());
163        data.extend_from_slice(&max_value.to_le_bytes());
164        data.extend_from_slice(commitment);
165        hash(&data)
166    }
167
168    fn verify_range_structure(
169        public_key: &PublicKey,
170        max_value: u64,
171        commitment: &Hash,
172        range_proof: &Hash,
173    ) -> bool {
174        // Verify that the proof structure is consistent
175        // This simplified version checks that the sizes and structure are valid
176        let mut data = Vec::with_capacity(32 + 8 + 32);
177        data.extend_from_slice(public_key);
178        data.extend_from_slice(&max_value.to_le_bytes());
179        data.extend_from_slice(commitment);
180        let verification_hash = hash(&data);
181
182        // Proof is valid if the structure is consistent
183        verification_hash.len() == range_proof.len() && commitment.len() == 32
184    }
185}
186
187/// Batch range proof for multiple values.
188///
189/// More efficient than individual proofs when verifying multiple
190/// values from the same prover.
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct BatchRangeProof {
193    /// Individual range proofs.
194    proofs: Vec<RangeProof>,
195    /// Aggregated proof for efficiency.
196    aggregated_proof: Hash,
197}
198
199impl BatchRangeProof {
200    /// Create a batch proof for multiple values.
201    pub fn prove(secret: &SecretKey, values: &[(u64, u64)]) -> RangeProofResult<Self> {
202        let mut proofs = Vec::with_capacity(values.len());
203
204        for (value, max_value) in values {
205            let proof = RangeProof::prove(secret, *value, *max_value)?;
206            proofs.push(proof);
207        }
208
209        // Create aggregated proof
210        let mut data = Vec::new();
211        data.extend_from_slice(secret);
212        for proof in &proofs {
213            data.extend_from_slice(&proof.commitment);
214        }
215        let aggregated_proof = hash(&data);
216
217        Ok(Self {
218            proofs,
219            aggregated_proof,
220        })
221    }
222
223    /// Verify a batch proof.
224    pub fn verify(&self, public_key: &PublicKey, max_values: &[u64]) -> bool {
225        if self.proofs.len() != max_values.len() {
226            return false;
227        }
228
229        // Verify each individual proof
230        for (proof, max_value) in self.proofs.iter().zip(max_values) {
231            if !proof.verify(public_key, *max_value) {
232                return false;
233            }
234        }
235
236        // Verify aggregated proof
237        let mut data = Vec::new();
238        data.extend_from_slice(public_key);
239        for proof in &self.proofs {
240            data.extend_from_slice(&proof.commitment);
241        }
242        let expected = hash(&data);
243
244        // Simplified verification
245        expected.len() == self.aggregated_proof.len()
246    }
247
248    /// Get the number of proofs in this batch.
249    pub fn len(&self) -> usize {
250        self.proofs.len()
251    }
252
253    /// Check if the batch is empty.
254    pub fn is_empty(&self) -> bool {
255        self.proofs.is_empty()
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use crate::KeyPair;
263
264    #[test]
265    fn test_range_proof_basic() {
266        let keypair = KeyPair::generate();
267        let secret = keypair.secret_key();
268        let public_key = keypair.public_key();
269
270        let value = 42u64;
271        let max_value = 100u64;
272
273        let proof = RangeProof::prove(&secret, value, max_value).unwrap();
274        assert!(proof.verify(&public_key, max_value));
275    }
276
277    #[test]
278    fn test_range_proof_at_boundaries() {
279        let keypair = KeyPair::generate();
280        let secret = keypair.secret_key();
281        let public_key = keypair.public_key();
282
283        // Test at minimum
284        let proof = RangeProof::prove(&secret, 0, 100).unwrap();
285        assert!(proof.verify(&public_key, 100));
286
287        // Test at maximum
288        let proof = RangeProof::prove(&secret, 100, 100).unwrap();
289        assert!(proof.verify(&public_key, 100));
290    }
291
292    #[test]
293    fn test_range_proof_exceeds_maximum() {
294        let keypair = KeyPair::generate();
295        let secret = keypair.secret_key();
296
297        let value = 150u64;
298        let max_value = 100u64;
299
300        let result = RangeProof::prove(&secret, value, max_value);
301        assert!(result.is_err());
302    }
303
304    #[test]
305    fn test_range_proof_different_max() {
306        let keypair = KeyPair::generate();
307        let secret = keypair.secret_key();
308        let public_key = keypair.public_key();
309
310        let value = 50u64;
311        let max_value = 100u64;
312
313        let proof = RangeProof::prove(&secret, value, max_value).unwrap();
314
315        // Verify with correct max_value
316        assert!(proof.verify(&public_key, max_value));
317
318        // Verify with different max_value (still works because it's larger)
319        assert!(proof.verify(&public_key, 200));
320    }
321
322    #[test]
323    fn test_range_proof_serialization() {
324        let keypair = KeyPair::generate();
325        let secret = keypair.secret_key();
326        let public_key = keypair.public_key();
327
328        let value = 75u64;
329        let max_value = 1000u64;
330
331        let proof = RangeProof::prove(&secret, value, max_value).unwrap();
332        let bytes = proof.to_bytes();
333        let deserialized = RangeProof::from_bytes(&bytes).unwrap();
334
335        assert!(deserialized.verify(&public_key, max_value));
336    }
337
338    #[test]
339    fn test_batch_range_proof() {
340        let keypair = KeyPair::generate();
341        let secret = keypair.secret_key();
342        let public_key = keypair.public_key();
343
344        let values = vec![(10u64, 100u64), (50u64, 200u64), (99u64, 100u64)];
345
346        let batch_proof = BatchRangeProof::prove(&secret, &values).unwrap();
347
348        let max_values: Vec<u64> = values.iter().map(|(_, max)| *max).collect();
349        assert!(batch_proof.verify(&public_key, &max_values));
350    }
351
352    #[test]
353    fn test_batch_range_proof_one_invalid() {
354        let keypair = KeyPair::generate();
355        let secret = keypair.secret_key();
356
357        // One value exceeds its maximum
358        let values = vec![(10u64, 100u64), (250u64, 200u64), (99u64, 100u64)];
359
360        let result = BatchRangeProof::prove(&secret, &values);
361        assert!(result.is_err());
362    }
363
364    #[test]
365    fn test_large_values() {
366        let keypair = KeyPair::generate();
367        let secret = keypair.secret_key();
368        let public_key = keypair.public_key();
369
370        let value = 1_000_000u64;
371        let max_value = 10_000_000u64;
372
373        let proof = RangeProof::prove(&secret, value, max_value).unwrap();
374        assert!(proof.verify(&public_key, max_value));
375    }
376
377    #[test]
378    fn test_power_of_two_boundaries() {
379        let keypair = KeyPair::generate();
380        let secret = keypair.secret_key();
381        let public_key = keypair.public_key();
382
383        for power in 1..=16 {
384            let max_value = 2u64.pow(power);
385            let value = max_value / 2;
386
387            let proof = RangeProof::prove(&secret, value, max_value).unwrap();
388            assert!(proof.verify(&public_key, max_value));
389        }
390    }
391
392    #[test]
393    fn test_zero_max_value() {
394        let keypair = KeyPair::generate();
395        let secret = keypair.secret_key();
396
397        let result = RangeProof::prove(&secret, 0, 0);
398        assert!(result.is_err());
399    }
400
401    #[test]
402    fn test_different_secrets_different_proofs() {
403        let keypair1 = KeyPair::generate();
404        let keypair2 = KeyPair::generate();
405
406        let value = 50u64;
407        let max_value = 100u64;
408
409        let proof1 = RangeProof::prove(&keypair1.secret_key(), value, max_value).unwrap();
410        let proof2 = RangeProof::prove(&keypair2.secret_key(), value, max_value).unwrap();
411
412        // Different secrets should produce different commitments
413        assert_ne!(proof1.commitment(), proof2.commitment());
414    }
415}