Skip to main content

veil_crypto/
rangeproof.rs

1//! Range Proofs - Pure Rust Implementation
2//!
3//! Range proofs prove that a committed value is within a valid range
4//! (e.g., non-negative and less than 2^64) without revealing the value.
5//!
6//! ## Algorithm
7//!
8//! Veil uses Borromean-based range proofs to:
9//! 1. Prove a committed value is in range [0, 2^64)
10//! 2. Hide the actual value while proving validity
11//! 3. Allow the recipient to decrypt the value with a nonce
12//!
13//! ## Implementation
14//!
15//! This is a **pure Rust** implementation using k256, NOT FFI bindings.
16//! This allows compilation to WASM for browser-based light wallets.
17//!
18//! ## Functions
19//!
20//! - **rangeproof_sign**: Create a range proof for a value
21//! - **rangeproof_verify**: Verify a range proof is valid
22//! - **rangeproof_rewind**: Decrypt a range proof to extract the value
23
24use crate::{Result, VeilCryptoError};
25use crate::borromean::borromean_sign;
26use crate::pedersen::{get_generator_h_point, pedersen_ecmult_point};
27use k256::{
28    elliptic_curve::{
29        group::Group,
30        sec1::{ToEncodedPoint, FromEncodedPoint},
31        ops::MulByGenerator,
32        PrimeField,
33    },
34    AffinePoint, ProjectivePoint, Scalar,
35};
36use sha2::{Sha256, Digest};
37use hmac::{Hmac, Mac};
38
39/// Maximum proof size (from Veil)
40pub const MAX_PROOF_SIZE: usize = 5134;
41
42/// Maximum rings in a range proof
43pub const MAX_RINGS: usize = 32;
44
45/// Maximum public keys in a range proof
46pub const MAX_NPUB: usize = 128;
47
48/// Maximum message size that can be embedded
49pub const MAX_MESSAGE_SIZE: usize = 256;
50
51//
52// ===== DATA STRUCTURES =====
53//
54
55/// Internal proof parameters
56#[derive(Debug, Clone)]
57struct RangeProofParams {
58    rings: usize,              // Number of rings
59    rsizes: Vec<usize>,        // Size of each ring [4, 4, ..., 2 or 4]
60    secidx: Vec<usize>,        // Secret index for each ring (which digit is real)
61    npub: usize,               // Total number of public keys
62    mantissa: usize,           // Number of bits to prove
63    #[allow(dead_code)]
64    min_value: u64,            // Minimum value in range
65    scale: u64,                // Scale factor (10^exp)
66    v: u64,                    // Encoded value (value - min_value) / scale
67}
68
69/// Result from signing a range proof
70#[derive(Debug, Clone)]
71pub struct RangeProofSignResult {
72    /// The generated range proof
73    pub proof: Vec<u8>,
74    /// The Pedersen commitment
75    pub commitment: Vec<u8>,
76    /// The blinding factor
77    pub blind: Vec<u8>,
78    /// The nonce used for encryption
79    pub nonce: Vec<u8>,
80}
81
82/// Result from verifying a range proof
83#[derive(Debug, Clone)]
84pub struct RangeProofVerifyResult {
85    /// Minimum value in the proven range
86    pub min_value: u64,
87    /// Maximum value in the proven range
88    pub max_value: u64,
89}
90
91/// Result from rewinding a range proof
92#[derive(Debug, Clone)]
93pub struct RangeProofRewindResult {
94    /// The recovered blinding factor
95    pub blind: Vec<u8>,
96    /// The recovered value
97    pub value: u64,
98    /// Minimum value in the proven range
99    pub min_value: u64,
100    /// Maximum value in the proven range
101    pub max_value: u64,
102    /// Any embedded message
103    pub message: Vec<u8>,
104}
105
106/// Result from extracting info from a range proof
107#[derive(Debug, Clone)]
108pub struct RangeProofInfo {
109    /// The exponent used in the proof
110    pub exp: i32,
111    /// The mantissa (number of bits proven)
112    pub mantissa: usize,
113    /// Minimum value in the proven range
114    pub min_value: u64,
115    /// Maximum value in the proven range
116    pub max_value: u64,
117}
118
119//
120// ===== HELPER FUNCTIONS =====
121//
122
123/// Serialize an affine point to 33 bytes (SEC1 format)
124///
125/// SEC1 format:
126/// - Byte 0: 0x02 if y is even, 0x03 if y is odd
127/// - Bytes 1-32: x-coordinate
128///
129/// Used for k256 interop and borromean functions
130fn serialize_point(point: &AffinePoint) -> [u8; 33] {
131    use k256::elliptic_curve::group::prime::PrimeCurveAffine;
132
133    let encoded = point.to_encoded_point(true);
134    let bytes = encoded.as_bytes();
135
136    // Safety check: ensure we have exactly 33 bytes (compressed point)
137    if bytes.len() != 33 {
138        eprintln!("\n========== SERIALIZE_POINT ERROR ==========");
139        eprintln!("ERROR: serialize_point got {} bytes instead of 33", bytes.len());
140        eprintln!("Point is identity: {}", bool::from(point.is_identity()));
141        eprintln!("Encoded bytes: {:02x?}", bytes);
142        eprintln!("==========================================\n");
143        panic!("Invalid point serialization: expected 33 bytes, got {}", bytes.len());
144    }
145
146    let mut result = [0u8; 33];
147    result.copy_from_slice(bytes);
148    result
149}
150
151/// Serialize an affine point to 33 bytes (custom format matching C code)
152///
153/// C custom format (NOT SEC1):
154/// - Byte 0: 0 if y is quadratic residue (even), 1 if not (odd)
155/// - Bytes 1-32: x-coordinate
156///
157/// This matches rustsecp256k1_v0_4_1_rangeproof_serialize_point in C code.
158/// Used ONLY for HMAC-DRBG seed construction to match C exactly.
159fn serialize_point_custom(point: &AffinePoint) -> [u8; 33] {
160    use k256::FieldElement;
161
162    let mut result = [0u8; 33];
163
164    // MATCH C++ EXACTLY: data[0] = !secp256k1_fe_is_quad_var(&point->y)
165    // Get y coordinate
166    let encoded = point.to_encoded_point(false); // Uncompressed to get y
167    let bytes = encoded.as_bytes();
168
169    // Extract y-coordinate (bytes 33..65 in uncompressed format)
170    let y_bytes: [u8; 32] = bytes[33..65].try_into().unwrap();
171    let y = FieldElement::from_bytes(&y_bytes.into()).unwrap();
172
173    // Check if y is a quadratic residue
174    // QR means: sqrt(y) exists (matching secp256k1's is_quad_var)
175    let y_has_sqrt = y.sqrt().is_some();
176    let y_is_qr: bool = y_has_sqrt.into();
177
178    // C++ code: data[0] = !is_quad_var(y)
179    // So: 0x00 if QR, 0x01 if NOT QR
180    result[0] = if y_is_qr { 0x00 } else { 0x01 };
181
182    // x-coordinate
183    result[1..33].copy_from_slice(&bytes[1..33]);
184
185    result
186}
187
188/// Count leading zeros in a u64 (for bit length calculation)
189fn clz64(x: u64) -> usize {
190    if x == 0 {
191        64
192    } else {
193        x.leading_zeros() as usize
194    }
195}
196
197/// RFC6979-style HMAC-SHA256 DRBG for deterministic random generation
198///
199/// This matches the C implementation in secp256k1/src/hash_impl.h
200pub struct HmacDrbg {
201    k: [u8; 32],
202    v: [u8; 32],
203    retry: bool,  // RFC 6979 3.2.h - update K on subsequent calls
204}
205
206impl HmacDrbg {
207    /// Initialize DRBG with a seed
208    ///
209    /// Implements RFC 6979 3.2.b-f
210    pub fn new(seed: &[u8]) -> Self {
211        let mut k = [0u8; 32];  // RFC 6979 3.2.c
212        let mut v = [1u8; 32];  // RFC 6979 3.2.b
213
214        eprintln!("\n=== HMAC-DRBG Initialization ===");
215        eprintln!("Initial K: {}", hex::encode(&k));
216        eprintln!("Initial V: {}", hex::encode(&v));
217        eprintln!("Seed ({} bytes): {}", seed.len(), hex::encode(seed));
218
219        type HmacSha256 = Hmac<Sha256>;
220
221        // RFC 6979 3.2.d: K = HMAC_K(V || 0x00 || seed)
222        let mut mac = HmacSha256::new_from_slice(&k).expect("HMAC init");
223        mac.update(&v);
224        mac.update(&[0x00]);
225        mac.update(seed);
226        k.copy_from_slice(&mac.finalize().into_bytes());
227        eprintln!("After step 3.2.d - K: {}", hex::encode(&k));
228
229        // V = HMAC_K(V)
230        let mut mac = HmacSha256::new_from_slice(&k).expect("HMAC init");
231        mac.update(&v);
232        v.copy_from_slice(&mac.finalize().into_bytes());
233        eprintln!("After step 3.2.e - V: {}", hex::encode(&v));
234
235        // RFC 6979 3.2.f: K = HMAC_K(V || 0x01 || seed)
236        let mut mac = HmacSha256::new_from_slice(&k).expect("HMAC init");
237        mac.update(&v);
238        mac.update(&[0x01]);
239        mac.update(seed);
240        k.copy_from_slice(&mac.finalize().into_bytes());
241        eprintln!("After step 3.2.f - K: {}", hex::encode(&k));
242
243        // V = HMAC_K(V)
244        let mut mac = HmacSha256::new_from_slice(&k).expect("HMAC init");
245        mac.update(&v);
246        v.copy_from_slice(&mac.finalize().into_bytes());
247        eprintln!("After step 3.2.g - V: {}", hex::encode(&v));
248        eprintln!("=================================\n");
249
250        Self { k, v, retry: false }  // retry starts at 0
251    }
252
253    /// Generate 32 bytes of random data
254    ///
255    /// Implements RFC 6979 3.2.h
256    /// Matches C code: rustsecp256k1_v0_4_1_rfc6979_hmac_sha256_generate
257    pub fn generate(&mut self) -> [u8; 32] {
258        static mut GENERATE_CALL_COUNT: usize = 0;
259        unsafe { GENERATE_CALL_COUNT += 1; }
260
261        type HmacSha256 = Hmac<Sha256>;
262
263        eprintln!("\n--- generate() call #{} ---", unsafe { GENERATE_CALL_COUNT });
264        eprintln!("Before: retry={}, K={}, V={}",
265                  self.retry, hex::encode(&self.k[..8]), hex::encode(&self.v[..8]));
266
267        // RFC 6979 3.2.h: On subsequent calls (retry == 1), update K first
268        if self.retry {
269            eprintln!("  Retry=true, updating K and V first");
270            // K = HMAC_K(V || 0x00)
271            let mut mac = HmacSha256::new_from_slice(&self.k).expect("HMAC init");
272            mac.update(&self.v);
273            mac.update(&[0x00]);
274            self.k.copy_from_slice(&mac.finalize().into_bytes());
275            eprintln!("  After K update: K={}", hex::encode(&self.k[..8]));
276
277            // V = HMAC_K(V)
278            let mut mac = HmacSha256::new_from_slice(&self.k).expect("HMAC init");
279            mac.update(&self.v);
280            self.v.copy_from_slice(&mac.finalize().into_bytes());
281            eprintln!("  After V update: V={}", hex::encode(&self.v[..8]));
282        }
283
284        // Always: V = HMAC_K(V)
285        let mut mac = HmacSha256::new_from_slice(&self.k).expect("HMAC init");
286        mac.update(&self.v);
287        self.v.copy_from_slice(&mac.finalize().into_bytes());
288
289        // Set retry for next call
290        self.retry = true;
291
292        eprintln!("After: V={} (output)", hex::encode(&self.v[..8]));
293        eprintln!("Full output: {}", hex::encode(&self.v));
294
295        self.v
296    }
297}
298
299/// Generate deterministic random values for range proof
300///
301/// This generates:
302/// - sec[rings]: Blinding factors for each ring (sum = 0 mod n)
303/// - s[npub]: Signature values for Borromean
304/// - Optionally encrypts message via XOR
305///
306/// The randomness is deterministic based on nonce, commitment, and proof header
307fn rangeproof_genrand(
308    sec: &mut [Scalar],
309    s: &mut [Scalar],
310    mut message: Option<&mut [u8]>,
311    rsizes: &[usize],
312    rings: usize,
313    nonce: &[u8],
314    commit_point: &AffinePoint,
315    genp: &AffinePoint,
316    proof_header: &[u8],
317) -> Result<bool> {
318    // Build seed: nonce || commit || genp || proof_header
319    // Use custom format to match C code exactly
320    let mut seed = Vec::with_capacity(32 + 33 + 33 + proof_header.len());
321    seed.extend_from_slice(nonce);
322
323    let commit_serialized = serialize_point_custom(commit_point);
324    let genp_serialized = serialize_point_custom(genp);
325
326    // DEBUG: Print seed components
327    eprintln!("\n=== HMAC-DRBG Seed Construction ===");
328    eprintln!("nonce ({} bytes): {}", nonce.len(), hex::encode(nonce));
329    eprintln!("commit_point: {:?}", commit_point);
330    eprintln!("commit_serialized ({} bytes): {}", commit_serialized.len(), hex::encode(&commit_serialized));
331    eprintln!("genp: {:?}", genp);
332    eprintln!("genp_serialized ({} bytes): {}", genp_serialized.len(), hex::encode(&genp_serialized));
333    eprintln!("proof_header ({} bytes): {}", proof_header.len(), hex::encode(proof_header));
334
335    seed.extend_from_slice(&commit_serialized);
336    seed.extend_from_slice(&genp_serialized);
337    seed.extend_from_slice(proof_header);
338
339    eprintln!("Total seed ({} bytes): {}", seed.len(), hex::encode(&seed));
340    eprintln!("=================================\n");
341
342    let mut drbg = HmacDrbg::new(&seed);
343    let mut acc = Scalar::ZERO;
344    let mut npub = 0;
345    let mut ret = true;
346
347    for i in 0..rings {
348        if i < rings - 1 {
349            // C code (line 85): Discard one generate() call BEFORE the do-while loop for each ring
350            let _ = drbg.generate();
351            eprintln!("Ring {}: Discarded first generate() call", i);
352
353            // Generate random scalar for sec[i] until valid (not zero, not overflow)
354            loop {
355                let bytes = drbg.generate();
356                eprintln!("Ring {}: Generated bytes: {}", i, hex::encode(&bytes));
357                let scalar_option = Option::<Scalar>::from(Scalar::from_repr(bytes.into()));
358                if let Some(scalar) = scalar_option {
359                    if !bool::from(scalar.is_zero()) {
360                        sec[i] = scalar;
361                        acc += scalar;
362                        eprintln!("Ring {}: Accepted scalar: {}", i, hex::encode(scalar.to_bytes()));
363                        break;
364                    }
365                }
366                eprintln!("Ring {}: Rejected (zero or overflow), retrying", i);
367            }
368        } else {
369            // Last sec is negated sum (ensures sum = 0)
370            sec[i] = -acc;
371            eprintln!("Ring {} (last): sec = -acc = {}", i, hex::encode(sec[i].to_bytes()));
372        }
373
374        // Generate s[j] for each element in ring
375        for j in 0..rsizes[i] {
376            let mut bytes = drbg.generate();
377
378            // XOR with message if provided (encrypts message)
379            if let Some(ref mut msg) = message {
380                let offset = (i * 4 + j) * 32;
381                if offset + 32 <= msg.len() {
382                    for b in 0..32 {
383                        bytes[b] ^= msg[offset + b];
384                        msg[offset + b] = bytes[b];
385                    }
386                }
387            }
388
389            // Convert to scalar
390            let scalar_option = Option::<Scalar>::from(Scalar::from_repr(bytes.into()));
391            if let Some(scalar) = scalar_option {
392                if bool::from(scalar.is_zero()) {
393                    ret = false;
394                }
395                s[npub] = scalar;
396            } else {
397                ret = false;
398            }
399            npub += 1;
400        }
401    }
402
403    Ok(ret)
404}
405
406/// Expand public keys for range proof rings
407///
408/// Given a base point and exponent, this generates all the public keys
409/// needed for each ring element. This implements the value encoding
410/// where each ring represents a digit in radix-4 (base 4).
411fn rangeproof_pub_expand(
412    pubs: &mut [ProjectivePoint],
413    exp: i32,
414    rsizes: &[usize],
415    rings: usize,
416    genp: &AffinePoint,
417) {
418    let mut base = ProjectivePoint::from(genp);
419    base = -base; // Negate
420
421    // Apply exponent (multiply by 10^exp)
422    for _ in 0..exp {
423        // Multiplication by 10 = 2 * (2 * 2 + 1)
424        let tmp = base.double();
425        base = tmp.double();
426        base = base.double();
427        base += tmp;
428    }
429
430    let mut npub = 0;
431    for i in 0..rings {
432        // Generate ring elements
433        for j in 1..rsizes[i] {
434            pubs[npub + j] = pubs[npub + j - 1] + base;
435        }
436
437        // For next ring, base *= 4 (shift 2 bits in radix-4)
438        if i < rings - 1 {
439            base = base.double();
440            base = base.double();
441        }
442        npub += rsizes[i];
443    }
444}
445
446/// Calculate range proof parameters from value and settings
447///
448/// This is the core function that determines:
449/// - How many rings we need
450/// - How many elements in each ring
451/// - Which element in each ring is the "real" one
452/// - The scale factor and encoded value
453fn range_proveparams(
454    min_value: &mut u64,
455    exp: &mut i32,
456    min_bits: &mut i32,
457    value: u64,
458) -> Result<RangeProofParams> {
459    let rings;
460    let mut rsizes;
461    let mut secidx;
462    let mut scale: u64 = 1;
463    let mut mantissa: usize;
464    let mut npub: usize = 0;
465
466    // If min_value is max, we cannot code a range
467    if *min_value == u64::MAX {
468        *exp = -1;
469    }
470
471    if *exp >= 0 {
472        let mut v2: u64;
473
474        // Overflow check
475        if (*min_value > 0 && value > i64::MAX as u64)
476            || (value > 0 && *min_value >= i64::MAX as u64)
477        {
478            return Err(VeilCryptoError::Other(
479                "Value or min_value too large (>= 2^63)".to_string(),
480            ));
481        }
482
483        // Calculate max_bits based on min_value
484        let max_bits = if *min_value > 0 {
485            clz64(*min_value)
486        } else {
487            64
488        };
489
490        if *min_bits > max_bits as i32 {
491            *min_bits = max_bits as i32;
492        }
493
494        // For very large numbers, disable exponent
495        if *min_bits > 61 || value > i64::MAX as u64 {
496            *exp = 0;
497        }
498
499        // Mask off least significant digits
500        let mut v = value.saturating_sub(*min_value);
501
502        // Adjust exponent based on min_bits
503        v2 = if *min_bits > 0 {
504            u64::MAX >> (64 - *min_bits)
505        } else {
506            0
507        };
508
509        let mut i = 0;
510        while i < *exp as usize && v2 <= u64::MAX / 10 {
511            v /= 10;
512            v2 *= 10;
513            i += 1;
514        }
515        *exp = i as i32;
516
517        v2 = v;
518        for _ in 0..*exp {
519            v2 *= 10;
520            scale *= 10;
521        }
522
523        // Compute public offset
524        *min_value = value - v2;
525
526        // How many bits do we need?
527        mantissa = if v > 0 { 64 - clz64(v) } else { 1 };
528
529        if (*min_bits as usize) > mantissa {
530            mantissa = *min_bits as usize;
531        }
532
533        // Digits in radix-4, except last digit if mantissa is odd
534        rings = (mantissa + 1) >> 1;
535        rsizes = Vec::with_capacity(rings);
536        secidx = Vec::with_capacity(rings);
537
538        for i in 0..rings {
539            // Each ring has 4 elements except possibly the last
540            let ring_size = if i < rings - 1 || (mantissa & 1) == 0 {
541                4
542            } else {
543                2
544            };
545            rsizes.push(ring_size);
546            npub += ring_size;
547
548            // Extract the secret index (which digit of the value)
549            secidx.push(((v >> (i * 2)) & 3) as usize);
550        }
551
552        // Validation
553        if v & !(u64::MAX >> (64 - mantissa)) != 0 {
554            return Err(VeilCryptoError::Other(
555                "Value encoding error: mantissa mismatch".to_string(),
556            ));
557        }
558
559        Ok(RangeProofParams {
560            rings,
561            rsizes,
562            secidx,
563            npub,
564            mantissa,
565            min_value: *min_value,
566            scale,
567            v,
568        })
569    } else {
570        // Exact value proof (exp = -1)
571        *exp = 0;
572        *min_value = value;
573
574        Ok(RangeProofParams {
575            rings: 1,
576            rsizes: vec![1],
577            secidx: vec![0],
578            npub: 1,  // Fixed: rings=1 with rsizes=[1] means only 1 public key total
579            mantissa: 0,
580            min_value: value,
581            scale: 1,
582            v: 0,
583        })
584    }
585}
586
587//
588// ===== MAIN FUNCTIONS (STUBS FOR NOW) =====
589//
590
591/// Sign a range proof
592///
593/// Creates a Borromean ring signature-based range proof that proves
594/// a committed value is in a valid range without revealing the value.
595///
596/// **STATUS**: 🚧 Under construction - Pure Rust implementation
597pub fn rangeproof_sign(
598    commitment: &[u8],
599    value: u64,
600    blind: &[u8],
601    nonce: &[u8],
602    message: Option<&[u8]>,
603    min_value: u64,
604    exp: i32,
605    min_bits: i32,
606) -> Result<RangeProofSignResult> {
607    use sha2::{Sha256, Digest};
608
609    // Validate inputs
610    if commitment.len() != 33 {
611        return Err(VeilCryptoError::Other(
612            "commitment must be 33 bytes".to_string(),
613        ));
614    }
615    if blind.len() != 32 {
616        return Err(VeilCryptoError::InvalidSecretKey);
617    }
618    if nonce.len() != 32 && nonce.len() != 33 {
619        return Err(VeilCryptoError::Other(
620            "nonce must be 32 or 33 bytes".to_string(),
621        ));
622    }
623    if exp < -1 || exp > 18 {
624        return Err(VeilCryptoError::Other(
625            "exp must be in range [-1, 18]".to_string(),
626        ));
627    }
628    if min_bits < 0 || min_bits > 64 {
629        return Err(VeilCryptoError::Other(
630            "min_bits must be in range [0, 64]".to_string(),
631        ));
632    }
633    if min_value > value {
634        return Err(VeilCryptoError::Other(
635            "min_value cannot be greater than value".to_string(),
636        ));
637    }
638
639    // Calculate proof parameters
640    let mut min_val_mut = min_value;
641    let mut exp_mut = exp;
642    let mut min_bits_mut = min_bits;
643
644    let params = range_proveparams(&mut min_val_mut, &mut exp_mut, &mut min_bits_mut, value)?;
645
646    // Parse commitment point using C++-matching loader
647    // This properly handles 0x08/0x09 format with canonical y + conditional negation
648    let commit_point = crate::mlsag::pedersen_commitment_load(commitment)?;
649    let commit_affine = commit_point.to_affine();
650
651    // Get generator H
652    let genp_proj = get_generator_h_point()?;  // Veil blockchain H generator (0x11)
653    let genp_affine = genp_proj.to_affine();
654
655    eprintln!("\n=== Generator H ===");
656    let genp_sec1 = serialize_point(&genp_affine);
657    let genp_custom = serialize_point_custom(&genp_affine);
658    eprintln!("SEC1 format: {}", hex::encode(&genp_sec1));
659    eprintln!("Custom format: {}", hex::encode(&genp_custom));
660
661    //
662    // ===== STEP 1: Build proof header =====
663    //
664    let mut proof = Vec::with_capacity(MAX_PROOF_SIZE);
665
666    // Flags byte: bit 6 = has mantissa, bit 5 = has min_value, bits 0-4 = exp
667    let has_mantissa = params.rsizes[0] > 1;
668    let has_min_value = min_val_mut > 0;
669    let flags = (if has_mantissa { 64 | exp_mut as u8 } else { 0 })
670        | (if has_min_value { 32 } else { 0 });
671    proof.push(flags);
672
673    // If mantissa exists, write mantissa-1
674    if has_mantissa {
675        proof.push((params.mantissa - 1) as u8);
676    }
677
678    // If min_value exists, write it as 8 bytes big-endian
679    if has_min_value {
680        for i in 0..8 {
681            proof.push((min_val_mut >> ((7 - i) * 8)) as u8);
682        }
683    }
684
685    //
686    // ===== STEP 2: Prepare message buffer =====
687    //
688    let msg_len = message.as_ref().map(|m| m.len()).unwrap_or(0);
689
690    // Check if we have room for the message
691    if msg_len > 0 && msg_len > 128 * (params.rings - 1) {
692        return Err(VeilCryptoError::Other(
693            format!("Message too large: {} bytes, max {} bytes",
694                msg_len, 128 * (params.rings - 1))
695        ));
696    }
697
698    // Prepare 4096-byte buffer for message encryption
699    let mut prep = vec![0u8; 4096];
700    if let Some(msg) = message {
701        prep[..msg.len()].copy_from_slice(msg);
702    }
703
704    // Special handling for value encoding sidechannel (line 295-309 in C code)
705    // The last ring encodes information about the value
706    if params.rsizes[params.rings - 1] > 1 {
707        let mut idx = params.rsizes[params.rings - 1] - 1;
708        // Avoid the secret index
709        if params.secidx[params.rings - 1] == idx {
710            idx -= 1;
711        }
712        let base_idx = ((params.rings - 1) * 4 + idx) * 32;
713
714        // Write value encoding
715        for i in 0..8 {
716            let byte = (params.v >> (56 - i * 8)) as u8;
717            prep[base_idx + 8 + i] = byte;
718            prep[base_idx + 16 + i] = byte;
719            prep[base_idx + 24 + i] = byte;
720            prep[base_idx + i] = 0;
721        }
722        prep[base_idx] = 128;
723    }
724
725    //
726    // ===== STEP 3: Generate random values (sec and s arrays) =====
727    //
728    let mut sec = vec![Scalar::ZERO; params.rings];
729    let mut s = vec![Scalar::ZERO; params.npub];
730
731    // Use nonce (first 32 bytes if 33-byte nonce)
732    let nonce_32 = if nonce.len() == 33 {
733        &nonce[..32]
734    } else {
735        nonce
736    };
737
738    let genrand_success = rangeproof_genrand(
739        &mut sec,
740        &mut s,
741        Some(&mut prep[..]),
742        &params.rsizes,
743        params.rings,
744        nonce_32,
745        &commit_affine,
746        &genp_affine,
747        &proof,
748    )?;
749
750    if !genrand_success {
751        return Err(VeilCryptoError::Other(
752            "rangeproof_genrand failed: invalid scalar generated".to_string()
753        ));
754    }
755
756    //
757    // ===== STEP 4: Move nonces and add blind factor =====
758    //
759    // Copy the "real" s values into k array (these become nonces for signing)
760    let mut k = vec![Scalar::ZERO; params.rings];
761    for i in 0..params.rings {
762        let idx = i * 4 + params.secidx[i];
763        k[i] = s[idx];
764        s[idx] = Scalar::ZERO; // Clear the real position
765    }
766
767    // Add the blind factor to the last sec value
768    // This makes sec[rings-1] = -sum(sec[0..rings-2]) + blind
769    let mut blind_bytes = [0u8; 32];
770    blind_bytes.copy_from_slice(blind);
771    let blind_scalar = Option::<Scalar>::from(Scalar::from_repr(blind_bytes.into()))
772        .ok_or_else(|| VeilCryptoError::InvalidSecretKey)?;
773
774    sec[params.rings - 1] += blind_scalar;
775
776    if bool::from(sec[params.rings - 1].is_zero()) {
777        return Err(VeilCryptoError::Other(
778            "Blinding factor resulted in zero secret".to_string()
779        ));
780    }
781
782    //
783    // ===== STEP 5: Reserve space for sign bits =====
784    //
785    let signs_offset = proof.len();
786    let signs_len = (params.rings + 6) >> 3; // Ceiling division by 8
787    for _ in 0..signs_len {
788        proof.push(0);
789    }
790
791    //
792    // ===== STEP 6: Create Pedersen commitments and serialize =====
793    //
794    let mut sha256_m = Sha256::new();
795
796    // Hash commitment and generator (use custom format to match C)
797    sha256_m.update(&serialize_point_custom(&commit_affine));
798    sha256_m.update(&serialize_point_custom(&genp_affine));
799
800    // Hash the proof header we've built so far
801    sha256_m.update(&proof[..signs_offset]);
802
803    let mut pubs = vec![ProjectivePoint::IDENTITY; params.npub];
804    let mut npub_idx = 0;
805
806    eprintln!("\n=== Building Commitments ===");
807    eprintln!("scale={}, rings={}", params.scale, params.rings);
808    eprintln!("secidx={:?}", params.secidx);
809
810    for i in 0..params.rings {
811        // Create Pedersen commitment: sec[i] * G + (secidx[i] * scale * 4^i) * H
812        let value_part = (params.secidx[i] as u64 * params.scale) << (i * 2);
813
814        if i == 0 {
815            eprintln!("\nRing 0 commitment details:");
816            eprintln!("  sec[0] = {}", hex::encode(sec[0].to_bytes()));
817            eprintln!("  secidx[0] = {}", params.secidx[0]);
818            eprintln!("  value_part = {}", value_part);
819        }
820
821        pubs[npub_idx] = pedersen_ecmult_point(&sec[i], value_part, &genp_proj)?;
822
823        if bool::from(pubs[npub_idx].is_identity()) {
824            return Err(VeilCryptoError::Other(
825                format!("Commitment at ring {} is infinity", i)
826            ));
827        }
828
829        // For all but the last ring, serialize the commitment and add to hash
830        if i < params.rings - 1 {
831            eprintln!("\n=== Ring {} commitment serialization ===", i);
832            eprintln!("npub_idx={}, pubs.len()={}", npub_idx, pubs.len());
833            eprintln!("pubs[{}] is_identity={}", npub_idx, bool::from(pubs[npub_idx].is_identity()));
834
835            let c_affine = pubs[npub_idx].to_affine();
836            // MATCH C++: Use custom format (QR-based 0x00/0x01), NOT SEC1 (0x02/0x03)
837            let serialized = serialize_point_custom(&c_affine);
838            let quadness = serialized[0]; // 0x00 if QR, 0x01 if NOT QR
839
840            eprintln!("Ring {}: commitment quadness={:#04x}, value_part={}",
841                     i, quadness, value_part);
842
843            // Hash tmpc (33 bytes): quadness + x-coordinate
844            // Matches C++: secp256k1_sha256_write(&sha256_m, tmpc, 33);
845            sha256_m.update(&serialized);
846
847            // Set quadness bit in signs array
848            // Matches C++: signs[i>>3] |= quadness << (i&7);
849            // quadness is 0x00 or 0x01, so the bit is 0 or 1
850            let sign_byte_idx = i >> 3;
851            let sign_bit_idx = i & 7;
852            proof[signs_offset + sign_byte_idx] |= (quadness & 1) << sign_bit_idx;
853
854            eprintln!("Ring {}: quadness={:#04x}, storing bit {} at signs[{}]",
855                i, quadness, quadness & 1, i);
856
857            // Write x-coordinate only (32 bytes)
858            eprintln!("Ring {}: Writing x-coord at offset {} (proof.len()={}), x={}",
859                i, proof.len(), proof.len(), hex::encode(&serialized[1..9]));
860            proof.extend_from_slice(&serialized[1..]);
861        }
862
863        npub_idx += params.rsizes[i];
864    }
865
866    //
867    // ===== STEP 7: Expand public keys for all ring elements =====
868    //
869    eprintln!("\n=== SIGN: PUBS BEFORE EXPANSION ===");
870    let mut npub_check = 0;
871    for i in 0..params.rings {
872        let affine = pubs[npub_check].to_affine();
873        let encoded = affine.to_encoded_point(true);
874        eprintln!("  pubs[{}] (ring {}): {}", npub_check, i, hex::encode(encoded.as_bytes()));
875        npub_check += params.rsizes[i];
876    }
877
878    rangeproof_pub_expand(&mut pubs, exp_mut, &params.rsizes, params.rings, &genp_affine);
879
880    //
881    // ===== STEP 8: Finalize message hash =====
882    //
883    let message_hash = sha256_m.finalize();
884    eprintln!("\n=== SIGN: Message hash for Borromean ===");
885    eprintln!("message_hash: {}", hex::encode(&message_hash));
886
887    eprintln!("\n========== DEBUG: PUBS BEFORE BORROMEAN SIGN ==========");
888    eprintln!("Total pubs elements: {}", pubs.len());
889    for (idx, p) in pubs.iter().enumerate().take(3) {
890        let affine = p.to_affine();
891        let encoded = affine.to_encoded_point(true);
892        eprintln!("  pubs[{}]: {}", idx, hex::encode(encoded.as_bytes()));
893    }
894    eprintln!("=====================================================\n");
895
896    //
897    // ===== STEP 9: Call Borromean sign =====
898    //
899    // Convert data to the format borromean_sign expects
900    let pubs_bytes: Vec<[u8; 33]> = pubs.iter()
901        .map(|p| serialize_point(&p.to_affine()))
902        .collect();
903
904    let k_bytes: Vec<[u8; 32]> = k.iter()
905        .map(|scalar| {
906            let mut bytes = [0u8; 32];
907            bytes.copy_from_slice(&scalar.to_bytes());
908            bytes
909        })
910        .collect();
911
912    let sec_bytes: Vec<[u8; 32]> = sec.iter()
913        .map(|scalar| {
914            let mut bytes = [0u8; 32];
915            bytes.copy_from_slice(&scalar.to_bytes());
916            bytes
917        })
918        .collect();
919
920    let s_bytes: Vec<[u8; 32]> = s.iter()
921        .map(|scalar| {
922            let mut bytes = [0u8; 32];
923            bytes.copy_from_slice(&scalar.to_bytes());
924            bytes
925        })
926        .collect();
927
928    let (e0, s_result) = borromean_sign(
929        &pubs_bytes,
930        &k_bytes,
931        &sec_bytes,
932        &s_bytes,
933        &params.rsizes,
934        &params.secidx,
935        &message_hash,
936    )?;
937
938    //
939    // ===== STEP 10: Serialize final proof =====
940    //
941    // Write e0 (32 bytes)
942    proof.extend_from_slice(&e0);
943
944    // Write all s values (32 bytes each)
945    for s_val in s_result {
946        proof.extend_from_slice(&s_val);
947    }
948
949    // Return result
950    Ok(RangeProofSignResult {
951        proof: proof.clone(),
952        commitment: commitment.to_vec(),
953        blind: blind.to_vec(),
954        nonce: nonce.to_vec(),
955    })
956}
957
958/// Parse range proof header
959///
960/// Extracts flags, exp, mantissa, scale, min_value, max_value from proof
961fn parse_proof_header(proof: &[u8]) -> Result<(usize, i32, usize, u64, u64, u64)> {
962    let mut offset = 0usize;
963
964    // Minimum proof size
965    if proof.len() < 65 {
966        return Err(VeilCryptoError::Other("Proof too short".to_string()));
967    }
968
969    // Check high bit is clear
970    if (proof[offset] & 128) != 0 {
971        return Err(VeilCryptoError::Other("Invalid proof flags".to_string()));
972    }
973
974    let has_nz_range = (proof[offset] & 64) != 0;
975    let has_min = (proof[offset] & 32) != 0;
976
977    let mut exp = -1i32;
978    let mut mantissa = 0usize;
979    let mut max_value = 0u64;
980
981    if has_nz_range {
982        exp = (proof[offset] & 31) as i32;
983        offset += 1;
984
985        if exp > 18 {
986            return Err(VeilCryptoError::Other("Invalid exponent".to_string()));
987        }
988
989        if offset >= proof.len() {
990            return Err(VeilCryptoError::Other("Proof truncated at mantissa".to_string()));
991        }
992
993        mantissa = proof[offset] as usize + 1;
994        if mantissa > 64 {
995            return Err(VeilCryptoError::Other("Invalid mantissa".to_string()));
996        }
997
998        max_value = u64::MAX >> (64 - mantissa);
999    }
1000
1001    offset += 1;
1002
1003    // Calculate scale = 10^exp
1004    let mut scale = 1u64;
1005    for _ in 0..exp {
1006        if max_value > u64::MAX / 10 {
1007            return Err(VeilCryptoError::Other("Max value overflow".to_string()));
1008        }
1009        max_value = max_value.wrapping_mul(10);
1010        scale = scale.wrapping_mul(10);
1011    }
1012
1013    // Parse min_value if present
1014    let mut min_value = 0u64;
1015    if has_min {
1016        if proof.len() - offset < 8 {
1017            return Err(VeilCryptoError::Other("Proof truncated at min_value".to_string()));
1018        }
1019
1020        for i in 0..8 {
1021            min_value = (min_value << 8) | (proof[offset + i] as u64);
1022        }
1023        offset += 8;
1024    }
1025
1026    // Check for overflow
1027    if max_value > u64::MAX - min_value {
1028        return Err(VeilCryptoError::Other("Value range overflow".to_string()));
1029    }
1030    max_value += min_value;
1031
1032    Ok((offset, exp, mantissa, scale, min_value, max_value))
1033}
1034
1035/// Verify a range proof
1036///
1037/// Verifies that a commitment contains a value in the proven range without
1038/// revealing the value itself.
1039pub fn rangeproof_verify(commitment: &[u8], proof: &[u8]) -> Result<RangeProofVerifyResult> {
1040    use sha2::{Sha256, Digest};
1041    use k256::elliptic_curve::sec1::FromEncodedPoint;
1042
1043    // Validate inputs
1044    if commitment.len() != 33 {
1045        return Err(VeilCryptoError::Other("commitment must be 33 bytes".to_string()));
1046    }
1047
1048    // Parse commitment point using C++-matching loader
1049    // This properly handles 0x08/0x09 format with canonical y + conditional negation
1050    let commit_point = crate::mlsag::pedersen_commitment_load(commitment)?;
1051    let commit_affine = commit_point.to_affine();
1052
1053    // Get generator H
1054    let genp_proj = get_generator_h_point()?;  // Veil blockchain H generator (0x11)
1055    let genp_affine = genp_proj.to_affine();
1056
1057    //
1058    // ===== STEP 1: Parse proof header =====
1059    //
1060    let (mut offset, exp, mantissa, _scale, min_value, max_value) = parse_proof_header(proof)?;
1061    let _offset_post_header = offset;
1062
1063    //
1064    // ===== STEP 2: Calculate rings and rsizes =====
1065    //
1066    let mut rings = 1usize;
1067    let mut rsizes = vec![1usize];
1068    let mut npub = 1usize;
1069
1070    if mantissa != 0 {
1071        rings = mantissa >> 1; // mantissa / 2
1072        rsizes = vec![4; rings];
1073        npub = rings << 2; // rings * 4
1074
1075        // If mantissa is odd, add a ring with 2 elements
1076        if (mantissa & 1) != 0 {
1077            rsizes.push(2);
1078            npub += 2;
1079            rings += 1;
1080        }
1081    }
1082
1083    if rings > MAX_RINGS {
1084        return Err(VeilCryptoError::Other("Too many rings".to_string()));
1085    }
1086
1087    //
1088    // ===== STEP 3: Validate proof length =====
1089    //
1090    let signs_len = (rings + 6) >> 3; // Ceiling division by 8
1091    let required_len = offset + signs_len + 32 * (npub + rings - 1) + 32;
1092    if proof.len() < required_len {
1093        return Err(VeilCryptoError::Other(
1094            format!("Proof too short: {} < {}", proof.len(), required_len)
1095        ));
1096    }
1097
1098    //
1099    // ===== STEP 4: Build SHA256 message hash =====
1100    //
1101    let mut sha256_m = Sha256::new();
1102    // Use custom format to match C code
1103    sha256_m.update(&serialize_point_custom(&commit_affine));
1104    sha256_m.update(&serialize_point_custom(&genp_affine));
1105    sha256_m.update(&proof[..offset]);
1106
1107    //
1108    // ===== STEP 5: Extract sign bits =====
1109    //
1110    let mut signs = vec![false; rings];
1111    for i in 0..(rings - 1) {
1112        let byte_idx = i >> 3;
1113        let bit_idx = i & 7;
1114        signs[i] = (proof[offset + byte_idx] & (1 << bit_idx)) != 0;
1115    }
1116
1117    eprintln!("\n=== VERIFY: Signs array ===");
1118    for i in 0..rings.min(4) {
1119        eprintln!("  signs[{}] = {}", i, signs[i]);
1120    }
1121
1122    offset += signs_len;
1123
1124    // Verify unused sign bits are zero (mutation check)
1125    if ((rings - 1) & 7) != 0 {
1126        let last_used_bit = (rings - 1) & 7;
1127        let unused_mask = 0xffu8 << last_used_bit;
1128        if (proof[offset - 1] & unused_mask) != 0 {
1129            return Err(VeilCryptoError::Other("Invalid unused sign bits".to_string()));
1130        }
1131    }
1132
1133    //
1134    // ===== STEP 6: Reconstruct public keys =====
1135    //
1136    let mut pubs = vec![ProjectivePoint::IDENTITY; npub];
1137    let mut accj = ProjectivePoint::IDENTITY;
1138
1139    // Add min_value * H to accumulator
1140    if min_value > 0 {
1141        accj = genp_proj * Scalar::from(min_value);
1142    }
1143
1144    // Process commitments for rings 0..(rings-1)
1145    let mut npub_idx = 0usize;
1146    for i in 0..(rings - 1) {
1147        // Parse x-coordinate from proof
1148        if offset + 32 > proof.len() {
1149            return Err(VeilCryptoError::Other("Proof truncated at commitment".to_string()));
1150        }
1151
1152        let mut x_bytes = [0u8; 32];
1153        x_bytes.copy_from_slice(&proof[offset..offset + 32]);
1154
1155        // Reconstruct point from x-coordinate and quadness bit
1156        // MATCH C++: Use canonical y + conditional negation (NOT SEC1 parity!)
1157        // C++ code:
1158        //   secp256k1_fe_set_b32(&fe, &proof[offset]);
1159        //   secp256k1_ge_set_xquad(&c, &fe);  // canonical y
1160        //   if (signs[i]) { secp256k1_ge_neg(&c, &c); }  // negate if NOT QR
1161
1162        use k256::FieldElement;
1163        let x = FieldElement::from_bytes(&x_bytes.into())
1164            .into_option()
1165            .ok_or_else(|| VeilCryptoError::Other("Invalid x-coordinate".to_string()))?;
1166
1167        // Compute y² = x³ + 7
1168        let x_cubed = x * x * x;
1169        let b = FieldElement::from(7u64);
1170        let y_squared = x_cubed + b;
1171
1172        // Compute sqrt to get ONE of the two roots
1173        let mut y = y_squared.sqrt()
1174            .into_option()
1175            .ok_or_else(|| VeilCryptoError::Other("Point not on curve".to_string()))?;
1176
1177        // CRITICAL: Match secp256k1's behavior
1178        // secp256k1's is_quad_var(y) checks if sqrt(y) exists
1179        // The sqrt(x³+7) gives us ONE root - check if IT has a square root
1180        // If sqrt(y) succeeds, y is a QR
1181        // If sqrt(y) fails, y is NOT a QR
1182        let y_has_sqrt = y.sqrt().is_some();
1183        let y_is_qr: bool = y_has_sqrt.into();
1184
1185        // If y is NOT a QR, use -y (which must be the QR)
1186        // Because exactly one of {y, -y} is a QR
1187        if !y_is_qr {
1188            y = -y;
1189        }
1190
1191        eprintln!("\n=== VERIFY: Ring {} sqrt selection ===", i);
1192        eprintln!("  x = {}", hex::encode(x.to_bytes()));
1193        eprintln!("  y (QR root) = {}", hex::encode(y.to_bytes()));
1194        eprintln!("  y_is_qr check = {}", y_is_qr);
1195
1196        // Create point with QR root y
1197        let y_bytes = y.to_bytes();
1198        let mut point_bytes = [0u8; 65];
1199        point_bytes[0] = 0x04; // Uncompressed
1200        point_bytes[1..33].copy_from_slice(&x_bytes);
1201        point_bytes[33..65].copy_from_slice(&y_bytes);
1202
1203        let encoded = k256::EncodedPoint::from_bytes(&point_bytes)
1204            .map_err(|_| VeilCryptoError::Other("Invalid point encoding".to_string()))?;
1205        let mut point_affine = AffinePoint::from_encoded_point(&encoded)
1206            .into_option()
1207            .ok_or_else(|| VeilCryptoError::Other("Invalid point".to_string()))?;
1208
1209        // Negate if signs[i] = 1 (NOT QR)
1210        // C++ stores: data[0] = !is_quad_var(y), so 0=QR, 1=NOT QR
1211        // C++ verify does: if (signs[i]) { negate }
1212        eprintln!("\n=== VERIFY: Ring {} reconstruction ===", i);
1213        eprintln!("  signs[{}] = {}", i, signs[i]);
1214        eprintln!("  Before negate: {}", hex::encode(point_affine.to_encoded_point(true).as_bytes()));
1215        if signs[i] {
1216            point_affine = -point_affine;
1217        }
1218        eprintln!("  After negate:  {}", hex::encode(point_affine.to_encoded_point(true).as_bytes()));
1219
1220        // Hash the commitment: sign bit || x-coordinate
1221        sha256_m.update(&[if signs[i] { 1u8 } else { 0u8 }]);
1222        sha256_m.update(&x_bytes);
1223
1224        pubs[npub_idx] = ProjectivePoint::from(point_affine);
1225        accj += point_affine;
1226
1227        offset += 32;
1228        npub_idx += rsizes[i];
1229    }
1230
1231    // Last ring: pubs[npub_idx] = commitment - accj
1232    accj = -accj;
1233    accj += commit_affine;
1234
1235    if bool::from(accj.is_identity()) {
1236        return Err(VeilCryptoError::Other("Last commitment is identity".to_string()));
1237    }
1238
1239    pubs[npub_idx] = accj;
1240
1241    //
1242    // ===== STEP 7: Expand public keys =====
1243    //
1244    eprintln!("\n=== VERIFY: PUBS BEFORE EXPANSION ===");
1245    let mut npub_check = 0;
1246    for i in 0..rings {
1247        let affine = pubs[npub_check].to_affine();
1248        let encoded = affine.to_encoded_point(true);
1249        eprintln!("  pubs[{}] (ring {}): {}", npub_check, i, hex::encode(encoded.as_bytes()));
1250        npub_check += rsizes[i];
1251    }
1252
1253    rangeproof_pub_expand(&mut pubs, exp, &rsizes, rings, &genp_affine);
1254
1255    eprintln!("\n========== DEBUG: PUBS BEFORE BORROMEAN VERIFY ==========");
1256    eprintln!("Total pubs elements: {}", pubs.len());
1257    for (idx, p) in pubs.iter().enumerate().take(3) {
1258        let affine = p.to_affine();
1259        let encoded = affine.to_encoded_point(true);
1260        eprintln!("  pubs[{}]: {}", idx, hex::encode(encoded.as_bytes()));
1261    }
1262    eprintln!("=====================================================\n");
1263
1264    //
1265    // ===== STEP 8: Parse e0 and s values =====
1266    //
1267    if offset + 32 + npub * 32 > proof.len() {
1268        return Err(VeilCryptoError::Other("Proof truncated at signatures".to_string()));
1269    }
1270
1271    let mut e0 = [0u8; 32];
1272    e0.copy_from_slice(&proof[offset..offset + 32]);
1273    offset += 32;
1274
1275    let mut s_values = Vec::with_capacity(npub);
1276    for _ in 0..npub {
1277        let mut s_bytes = [0u8; 32];
1278        s_bytes.copy_from_slice(&proof[offset..offset + 32]);
1279        s_values.push(s_bytes);
1280        offset += 32;
1281    }
1282
1283    // Verify we consumed the entire proof
1284    if offset != proof.len() {
1285        return Err(VeilCryptoError::Other("Extra data in proof".to_string()));
1286    }
1287
1288    //
1289    // ===== STEP 9: Finalize message hash =====
1290    //
1291    let message_hash = sha256_m.finalize();
1292    eprintln!("\n=== VERIFY: Message hash for Borromean ===");
1293    eprintln!("message_hash: {}", hex::encode(&message_hash));
1294
1295    //
1296    // ===== STEP 10: Call Borromean verify =====
1297    //
1298    // Convert pubs to byte format
1299    let pubs_bytes: Vec<[u8; 33]> = pubs.iter()
1300        .map(|p| serialize_point(&p.to_affine()))
1301        .collect();
1302
1303    // Verify Borromean signature
1304    use crate::borromean::borromean_verify;
1305    let verify_result = borromean_verify(
1306        &e0,
1307        &s_values,
1308        &pubs_bytes,
1309        &rsizes,
1310        &message_hash,
1311    )?;
1312
1313    if !verify_result {
1314        return Err(VeilCryptoError::Other("Borromean signature verification failed".to_string()));
1315    }
1316
1317    //
1318    // ===== STEP 11: Return result =====
1319    //
1320    Ok(RangeProofVerifyResult {
1321        min_value,
1322        max_value,
1323    })
1324}
1325
1326// ===== HELPER FUNCTIONS FOR REWIND =====
1327
1328/// Recover secret key x from Borromean signature
1329///
1330/// Formula: x = (k - s) / e = (k - s) * e^-1
1331///
1332/// Where:
1333/// - k = original nonce
1334/// - e = challenge
1335/// - s = signature
1336/// - x = secret key (what we're recovering)
1337fn rangeproof_recover_x(k: &Scalar, e: &Scalar, s: &Scalar) -> Scalar {
1338    // x = (k - s) * e^-1
1339    let k_minus_s = *k - s;
1340    let e_inv = e.invert().unwrap(); // e is never zero in valid signatures
1341    k_minus_s * e_inv
1342}
1343
1344/// Recover nonce k from Borromean signature
1345///
1346/// Formula: k = s + x*e
1347///
1348/// Where:
1349/// - x = secret key
1350/// - e = challenge
1351/// - s = signature
1352/// - k = nonce (what we're recovering)
1353fn rangeproof_recover_k(x: &Scalar, e: &Scalar, s: &Scalar) -> Scalar {
1354    // k = s + x*e
1355    s + (x * e)
1356}
1357
1358/// XOR two 32-byte arrays
1359fn xor_32(a: &mut [u8; 32], b: &[u8; 32]) {
1360    for i in 0..32 {
1361        a[i] ^= b[i];
1362    }
1363}
1364
1365// Note: rangeproof_genrand already exists above (line 252), we'll use that
1366
1367/// Rewind a range proof to extract value and blinding factor
1368///
1369/// Allows the recipient (who knows the nonce) to extract:
1370/// - The committed value
1371/// - The blinding factor
1372/// - Optional message from sender
1373pub fn rangeproof_rewind(
1374    nonce: &[u8],
1375    commitment: &[u8],
1376    proof: &[u8],
1377) -> Result<RangeProofRewindResult> {
1378    // Validate inputs
1379    if nonce.len() != 32 {
1380        return Err(VeilCryptoError::Other("Nonce must be 32 bytes".to_string()));
1381    }
1382    if commitment.len() != 33 {
1383        return Err(VeilCryptoError::Other("Commitment must be 33 bytes".to_string()));
1384    }
1385    if proof.len() < 11 {
1386        return Err(VeilCryptoError::InvalidRangeProof);
1387    }
1388
1389    // Parse proof header
1390    let (offset, exp, mantissa, scale, min_value, max_value) = parse_proof_header(proof)?;
1391    let offset_post_header = offset;
1392
1393    // Calculate ring structure (same as verify)
1394    let mut rings = 1usize;
1395    let mut rsizes = vec![1usize];
1396
1397    if mantissa != 0 {
1398        rings = mantissa >> 1; // mantissa / 2
1399        rsizes = vec![4; rings];
1400
1401        // If mantissa is odd, add a ring with 2 elements
1402        if (mantissa & 1) != 0 {
1403            rsizes.push(2);
1404            rings += 1;
1405        }
1406    }
1407
1408    // Get Generator H (Veil blockchain 0x11 format)
1409    let genp = crate::pedersen::get_generator_h_point()?.to_affine();
1410    eprintln!("DEBUG rewind: Using Veil H generator (0x11 format)");
1411    eprintln!("DEBUG rewind: H generator point: {:?}", genp);
1412
1413    // Parse commitment point using C++-matching loader
1414    eprintln!("DEBUG rewind: commitment[0]={:#04x}, len={}", commitment[0], commitment.len());
1415    let commit_proj = crate::mlsag::pedersen_commitment_load(commitment)?;
1416    let commit_point = commit_proj.to_affine();
1417
1418    // Allocate buffers for genrand
1419    let npub_total = rsizes.iter().sum();
1420    let mut sec = vec![Scalar::ZERO; rings];
1421    let mut s_orig = vec![Scalar::ZERO; npub_total];
1422    let mut message_buf = vec![0u8; 4096]; // Max message size
1423
1424    // Reconstruct prover's random values
1425    let proof_header = &proof[0..offset_post_header];
1426    rangeproof_genrand(
1427        &mut sec,
1428        &mut s_orig,
1429        Some(&mut message_buf),
1430        &rsizes,
1431        rings,
1432        nonce,
1433        &commit_point,
1434        &genp,
1435        proof_header,
1436    )?;
1437
1438    //
1439    // ===== Parse Borromean Signature from Proof =====
1440    //
1441    // Reset offset to start of proof body (after header)
1442    let mut offset = offset_post_header;
1443
1444    // Calculate proof lengths
1445    let signs_len = (rings + 6) >> 3;
1446    let required_len = offset + signs_len + 32 * (npub_total + rings - 1) + 32;
1447
1448    eprintln!("DEBUG: offset_post_header={}, signs_len={}, rings={}, npub_total={}",
1449        offset_post_header, signs_len, rings, npub_total);
1450    if proof.len() < required_len {
1451        return Err(VeilCryptoError::Other(
1452            format!("Proof too short for rewind: {} < {}", proof.len(), required_len)
1453        ));
1454    }
1455
1456    // Build SHA256 message hash (same as verify)
1457    let mut sha256_m = Sha256::new();
1458    sha256_m.update(&serialize_point_custom(&commit_point));
1459    sha256_m.update(&serialize_point_custom(&genp));
1460    sha256_m.update(&proof[..offset_post_header]);
1461
1462    // Extract sign bits
1463    let mut signs = vec![false; rings];
1464    for i in 0..(rings - 1) {
1465        let byte_idx = i >> 3;
1466        let bit_idx = i & 7;
1467        signs[i] = (proof[offset + byte_idx] & (1 << bit_idx)) != 0;
1468    }
1469    offset += signs_len;
1470
1471    // Reconstruct public keys and hash commitments
1472    let mut pubs = vec![ProjectivePoint::IDENTITY; npub_total];
1473    let mut accj = ProjectivePoint::IDENTITY;
1474
1475    // Add min_value * H to accumulator
1476    if min_value > 0 {
1477        accj = ProjectivePoint::from(&genp) * Scalar::from(min_value);
1478    }
1479
1480    // Process commitments for rings 0..(rings-1)
1481    let mut npub_idx = 0usize;
1482    for i in 0..(rings - 1) {
1483        // Parse x-coordinate from proof
1484        if offset + 32 > proof.len() {
1485            return Err(VeilCryptoError::Other("Proof truncated at commitment".to_string()));
1486        }
1487
1488        let mut x_bytes = [0u8; 32];
1489        x_bytes.copy_from_slice(&proof[offset..offset + 32]);
1490
1491        eprintln!("Ring {}: offset={}, signs[{}]={}, x_bytes={}",
1492            i, offset, i, signs[i], hex::encode(&x_bytes[..8]));
1493
1494        // Reconstruct point from x-coordinate using QR-based logic (NOT SEC1!)
1495        use k256::FieldElement;
1496        let x = FieldElement::from_bytes(&x_bytes.into())
1497            .into_option()
1498            .ok_or_else(|| VeilCryptoError::Other("Invalid x-coordinate".to_string()))?;
1499
1500        // Compute y² = x³ + 7
1501        let x_cubed = x * x * x;
1502        let b = FieldElement::from(7u64);
1503        let y_squared = x_cubed + b;
1504
1505        // Compute sqrt to get ONE of the two roots
1506        let mut y = y_squared.sqrt()
1507            .into_option()
1508            .ok_or_else(|| VeilCryptoError::Other("Point not on curve".to_string()))?;
1509
1510        // Match secp256k1: check if sqrt(y) exists to determine if y is a QR
1511        let y_has_sqrt = y.sqrt().is_some();
1512        let y_is_qr: bool = y_has_sqrt.into();
1513        if !y_is_qr {
1514            y = -y;
1515        }
1516
1517        // Create point with QR root y
1518        let y_bytes = y.to_bytes();
1519        let mut point_bytes = [0u8; 65];
1520        point_bytes[0] = 0x04; // Uncompressed
1521        point_bytes[1..33].copy_from_slice(&x_bytes);
1522        point_bytes[33..65].copy_from_slice(&y_bytes);
1523
1524        let point_encoded = k256::EncodedPoint::from_bytes(&point_bytes)
1525            .map_err(|_| VeilCryptoError::Other("Invalid point encoding".to_string()))?;
1526        let mut point_affine = AffinePoint::from_encoded_point(&point_encoded)
1527            .into_option()
1528            .ok_or_else(|| VeilCryptoError::Other(format!("Invalid commitment point at ring {}, offset {}", i, offset)))?;
1529
1530        // Negate if signs[i] = 1 (original y was NOT a QR)
1531        if signs[i] {
1532            point_affine = -point_affine;
1533        }
1534
1535        eprintln!("Ring {}: Reconstructed point={}", i, hex::encode(point_affine.to_encoded_point(true).as_bytes()));
1536
1537        // Hash the commitment: sign bit || x-coordinate
1538        sha256_m.update(&[if signs[i] { 1u8 } else { 0u8 }]);
1539        sha256_m.update(&x_bytes);
1540
1541        pubs[npub_idx] = ProjectivePoint::from(point_affine);
1542        accj += point_affine;
1543
1544        offset += 32;
1545        npub_idx += rsizes[i];
1546    }
1547
1548    // Last ring: pubs[npub_idx] = commitment - accj
1549    accj = -accj;
1550    accj += commit_point;
1551
1552    if bool::from(accj.is_identity()) {
1553        return Err(VeilCryptoError::Other("Last commitment is identity".to_string()));
1554    }
1555
1556    pubs[npub_idx] = accj;
1557
1558    // Expand public keys
1559    rangeproof_pub_expand(&mut pubs, exp, &rsizes, rings, &genp);
1560
1561    // Parse e0 and s values from proof
1562    if offset + 32 + npub_total * 32 > proof.len() {
1563        return Err(VeilCryptoError::Other("Proof truncated at signatures".to_string()));
1564    }
1565
1566    let mut e0 = [0u8; 32];
1567    e0.copy_from_slice(&proof[offset..offset + 32]);
1568    offset += 32;
1569
1570    let mut s = Vec::with_capacity(npub_total);
1571    for _ in 0..npub_total {
1572        let mut s_bytes = [0u8; 32];
1573        s_bytes.copy_from_slice(&proof[offset..offset + 32]);
1574        let s_scalar = Scalar::from_repr(s_bytes.into())
1575            .into_option()
1576            .ok_or_else(|| VeilCryptoError::Other("Invalid s value".to_string()))?;
1577        s.push(s_scalar);
1578        offset += 32;
1579    }
1580
1581    //
1582    // ===== Extract Value from Last Ring =====
1583    //
1584    // Special case: single proof (rings==1, rsizes[0]==1)
1585    // This is an exact value proof where the value is stored as min_value in the header
1586    let value: u64;
1587    let skip1: usize;
1588    let skip2: usize;
1589
1590    if rings == 1 && rsizes[0] == 1 {
1591        // Exact value proof - value is min_value from header
1592        value = min_value;
1593        skip1 = 0; // Not used for single ring
1594        skip2 = 0; // The only element is the real one
1595    } else {
1596        // Look for value encoding in last ring's signatures
1597        let npub_base = (rings - 1) * 4;
1598        let mut value_temp = u64::MAX;
1599        let mut skip1_temp = 0usize;
1600
1601        for j in 0..2 {
1602            let idx = npub_base + rsizes[rings - 1] - 1 - j;
1603            if idx >= s.len() {
1604                continue;
1605            }
1606
1607            // Get s[idx] as bytes (from parsed Borromean signature)
1608            let s_bytes = s[idx].to_bytes();
1609            let mut tmp: [u8; 32] = s_bytes.into();
1610
1611            // XOR with prep to recover (prep is stored in message_buf as 32-byte chunks)
1612            let prep_slice: [u8; 32] = message_buf[idx * 32..(idx + 1) * 32]
1613                .try_into()
1614                .unwrap();
1615            xor_32(&mut tmp, &prep_slice);
1616
1617            // Check for value encoding pattern:
1618            // tmp[0] & 0x80 and bytes 8-15, 16-23, 24-31 are identical
1619            if (tmp[0] & 0x80) != 0
1620                && tmp[16..24] == tmp[24..32]
1621                && tmp[8..16] == tmp[16..24]
1622            {
1623                // Extract value (big-endian from last 8 bytes)
1624                value_temp = u64::from_be_bytes(tmp[24..32].try_into().unwrap());
1625                skip1_temp = rsizes[rings - 1] - 1 - j;
1626                break;
1627            }
1628        }
1629
1630        if value_temp == u64::MAX {
1631            return Err(VeilCryptoError::Other(
1632                "Could not extract value from proof".to_string()
1633            ));
1634        }
1635
1636        value = value_temp;
1637        skip1 = skip1_temp;
1638
1639        // Determine which signature in last ring is real (not forged)
1640        skip2 = ((value >> ((rings - 1) * 2)) & 3) as usize;
1641        if skip1 == skip2 {
1642            return Err(VeilCryptoError::Other(
1643                "Value is in wrong position".to_string()
1644            ));
1645        }
1646    }
1647
1648    // Finalize message hash
1649    let message_hash = sha256_m.finalize();
1650
1651    // Compute challenge values (ev) by simulating Borromean verification
1652    let mut ev = Vec::with_capacity(npub_total);
1653    let mut count = 0;
1654
1655    for (i, &ring_size) in rsizes.iter().enumerate() {
1656        // Initial challenge for this ring: en = H(e0 || m || i || 0)
1657        let hash = crate::borromean::borromean_hash(&e0, &message_hash, i, 0);
1658        let mut en = Scalar::from_repr(hash.into())
1659            .into_option()
1660            .ok_or_else(|| VeilCryptoError::Other("Invalid scalar from hash".to_string()))?;
1661
1662        // Process each pubkey in the ring
1663        for j in 0..ring_size {
1664            // Store challenge value
1665            ev.push(en);
1666
1667            // Compute: r = s[count] * G + en * pubs[count]
1668            let s_times_g = ProjectivePoint::mul_by_generator(&s[count]);
1669            let en_times_pub = pubs[count] * en;
1670            let r = (s_times_g + en_times_pub).to_affine();
1671
1672            // Serialize r for hashing - USE SEC1 FORMAT to match Borromean signing!
1673            // The signing uses to_encoded_point(true) which is SEC1 compressed (0x02/0x03)
1674            let r_encoded = r.to_encoded_point(true);
1675            let r_serialized = r_encoded.as_bytes();
1676
1677            // Next challenge: en = H(r || m || i || j+1)
1678            let hash = crate::borromean::borromean_hash(r_serialized, &message_hash, i, j + 1);
1679            en = Scalar::from_repr(hash.into())
1680                .into_option()
1681                .ok_or_else(|| VeilCryptoError::Other("Invalid scalar from hash".to_string()))?;
1682
1683            count += 1;
1684        }
1685    }
1686
1687    //
1688    // ===== Recover Blinding Factor =====
1689    //
1690    // Find which position in last ring is the real signature
1691    let last_ring_start = npub_total - rsizes[rings - 1];
1692    let blind_idx = last_ring_start + skip2;
1693
1694    // Use recover_x to extract blind component
1695    let stmp = rangeproof_recover_x(&s_orig[blind_idx], &ev[blind_idx], &s[blind_idx]);
1696
1697    // Add back negated sec[rings-1] to get final blind
1698    let sec_last_neg = -sec[rings - 1];
1699    let blind_scalar = stmp + sec_last_neg;
1700    let blind_bytes = blind_scalar.to_bytes();
1701
1702    //
1703    // ===== Extract Message =====
1704    //
1705    let mut message_out = Vec::new();
1706    let mut npub_idx2 = 0;
1707
1708    for i in 0..rings {
1709        let idx = ((value >> (i * 2)) & 3) as usize;
1710        for j in 0..rsizes[i] {
1711            if npub_idx2 == blind_idx || npub_idx2 == last_ring_start + skip1 {
1712                // Skip positions used for value/blind encoding
1713                npub_idx2 += 1;
1714                continue;
1715            }
1716
1717            let stmp = if idx == j {
1718                // Real signature - recover nonce
1719                rangeproof_recover_k(&sec[i], &ev[npub_idx2], &s[npub_idx2])
1720            } else {
1721                // Forged signature - use s_orig
1722                s_orig[npub_idx2]
1723            };
1724
1725            // Extract message bytes
1726            let mut tmp: [u8; 32] = stmp.to_bytes().into();
1727            let prep_slice: [u8; 32] = message_buf[npub_idx2 * 32..(npub_idx2 + 1) * 32]
1728                .try_into()
1729                .unwrap();
1730            xor_32(&mut tmp, &prep_slice);
1731            message_out.extend_from_slice(&tmp);
1732            npub_idx2 += 1;
1733        }
1734    }
1735
1736    // Apply scale and min_value to get actual value
1737    // For single-ring proofs (rings==1, rsizes[0]==1), value is already the final value
1738    // For multi-ring proofs, value is an offset that needs scaling and min_value added
1739    let actual_value = if rings == 1 && rsizes[0] == 1 {
1740        value  // Single-ring: value is already min_value (the final value)
1741    } else {
1742        value * scale + min_value  // Multi-ring: value is offset from min_value
1743    };
1744
1745    Ok(RangeProofRewindResult {
1746        blind: blind_bytes.to_vec(),
1747        value: actual_value,
1748        min_value,
1749        max_value,
1750        message: message_out,
1751    })
1752}
1753
1754/// Extract metadata from a range proof without full verification
1755///
1756/// This is a lightweight function that only parses the proof header
1757/// to extract exp, mantissa, min_value, and max_value without performing
1758/// the full cryptographic verification.
1759///
1760/// # Arguments
1761///
1762/// * `proof` - The range proof to extract info from
1763///
1764/// # Returns
1765///
1766/// Returns `RangeProofInfo` containing the proof metadata
1767pub fn rangeproof_info(proof: &[u8]) -> Result<RangeProofInfo> {
1768    // Parse proof header
1769    let (_offset, exp, mantissa, _scale, min_value, max_value) = parse_proof_header(proof)?;
1770
1771    Ok(RangeProofInfo {
1772        exp,
1773        mantissa,
1774        min_value,
1775        max_value,
1776    })
1777}
1778
1779//
1780// ===== TESTS =====
1781//
1782
1783#[cfg(test)]
1784mod tests {
1785    use super::*;
1786    use hex;
1787
1788    #[test]
1789    fn test_hmac_drbg_simple() {
1790        // Test HMAC-DRBG with a simple known seed
1791        let seed = b"test seed";
1792        let mut drbg = HmacDrbg::new(seed);
1793
1794        let output1 = drbg.generate();
1795        let output2 = drbg.generate();
1796
1797        eprintln!("Simple HMAC-DRBG test:");
1798        eprintln!("Seed: {:?}", std::str::from_utf8(seed));
1799        eprintln!("Output 1: {}", hex::encode(&output1));
1800        eprintln!("Output 2: {}", hex::encode(&output2));
1801    }
1802
1803    #[test]
1804    fn test_range_proveparams_basic() {
1805        let value = 12345u64;
1806        let mut min_value = 0u64;
1807        let mut exp = 2i32;
1808        let mut min_bits = 32i32;
1809
1810        let params = range_proveparams(&mut min_value, &mut exp, &mut min_bits, value).unwrap();
1811
1812        println!("Test value: {}", value);
1813        println!("Params: rings={}, npub={}, mantissa={}", params.rings, params.npub, params.mantissa);
1814        println!("rsizes: {:?}", params.rsizes);
1815        println!("secidx: {:?}", params.secidx);
1816        println!("scale: {}, v: {}", params.scale, params.v);
1817
1818        // Basic validation
1819        assert!(params.rings > 0);
1820        assert!(params.rings <= MAX_RINGS);
1821        assert_eq!(params.rsizes.len(), params.rings);
1822        assert_eq!(params.secidx.len(), params.rings);
1823        assert!(params.npub <= MAX_NPUB);
1824    }
1825
1826    #[test]
1827    fn test_range_proveparams_small_value() {
1828        let value = 100u64;
1829        let mut min_value = 0u64;
1830        let mut exp = 2i32;
1831        let mut min_bits = 32i32;
1832
1833        let params = range_proveparams(&mut min_value, &mut exp, &mut min_bits, value).unwrap();
1834
1835        assert!(params.rings > 0);
1836        assert!(params.npub > 0);
1837    }
1838
1839    #[test]
1840    fn test_range_proveparams_large_value() {
1841        let value = 1000000000u64;
1842        let mut min_value = 0u64;
1843        let mut exp = 2i32;
1844        let mut min_bits = 32i32;
1845
1846        let params = range_proveparams(&mut min_value, &mut exp, &mut min_bits, value).unwrap();
1847
1848        assert!(params.rings > 0);
1849        assert!(params.npub > 0);
1850    }
1851
1852    #[test]
1853    fn test_serialize_point() {
1854        use k256::ProjectivePoint;
1855
1856        // Test with generator point
1857        let g = ProjectivePoint::GENERATOR;
1858        let g_affine = g.to_affine();
1859        let serialized = serialize_point(&g_affine);
1860
1861        // Should be 33 bytes (compressed)
1862        assert_eq!(serialized.len(), 33);
1863
1864        // First byte should be 0x02 or 0x03 (compressed format)
1865        assert!(serialized[0] == 0x02 || serialized[0] == 0x03);
1866    }
1867
1868    #[test]
1869    fn test_rangeproof_sign_basic() {
1870        use crate::pedersen::pedersen_commit;
1871
1872        // Test parameters (Veil-standard)
1873        let value = 12345u64;
1874        let blind_hex = "1111111111111111111111111111111111111111111111111111111111111111";
1875        let nonce_hex = "2222222222222222222222222222222222222222222222222222222222222222";
1876
1877        let blind = hex::decode(blind_hex).unwrap();
1878        let nonce = hex::decode(nonce_hex).unwrap();
1879
1880        // Create commitment in Veil format (0x08/0x09)
1881        let commitment_veil = pedersen_commit(value, &blind).unwrap();
1882
1883        // Convert to standard SEC1 format for rangeproof_sign
1884        // Veil: 0x08 = even y, 0x09 = odd y
1885        // SEC1: 0x02 = even y, 0x03 = odd y
1886        let mut commitment = commitment_veil.clone();
1887        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
1888
1889        println!("Testing rangeproof_sign with value={}", value);
1890        println!("Commitment (SEC1): {}", hex::encode(&commitment[..8]));
1891
1892        // Sign range proof
1893        let result = rangeproof_sign(
1894            &commitment,
1895            value,
1896            &blind,
1897            &nonce,
1898            None, // No message
1899            0,    // min_value
1900            2,    // exp (Veil standard)
1901            32,   // min_bits (Veil standard)
1902        );
1903
1904        match result {
1905            Ok(proof_result) => {
1906                println!("✅ Range proof generated successfully!");
1907                println!("Proof length: {} bytes", proof_result.proof.len());
1908                println!("First 32 bytes: {}", hex::encode(&proof_result.proof[..32.min(proof_result.proof.len())]));
1909                assert!(proof_result.proof.len() > 0);
1910                assert!(proof_result.proof.len() <= MAX_PROOF_SIZE);
1911            }
1912            Err(e) => {
1913                println!("❌ Range proof generation failed: {:?}", e);
1914                panic!("Range proof generation failed: {:?}", e);
1915            }
1916        }
1917    }
1918
1919    #[test]
1920    fn test_rangeproof_sign_large_value() {
1921        use crate::pedersen::pedersen_commit;
1922
1923        // Test with large value (1 billion)
1924        let value = 1000000000u64;
1925        let blind_hex = "1111111111111111111111111111111111111111111111111111111111111111";
1926        let nonce_hex = "2222222222222222222222222222222222222222222222222222222222222222";
1927
1928        let blind = hex::decode(blind_hex).unwrap();
1929        let nonce = hex::decode(nonce_hex).unwrap();
1930
1931        let commitment_veil = pedersen_commit(value, &blind).unwrap();
1932        let mut commitment = commitment_veil.clone();
1933        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
1934
1935        println!("Testing rangeproof_sign with large value={}", value);
1936
1937        let result = rangeproof_sign(
1938            &commitment,
1939            value,
1940            &blind,
1941            &nonce,
1942            Some(b"large value test"),
1943            0,
1944            2,
1945            32,
1946        );
1947
1948        assert!(result.is_ok(), "Large value proof failed: {:?}", result.err());
1949        let proof_result = result.unwrap();
1950        println!("✅ Large value proof: {} bytes", proof_result.proof.len());
1951        assert!(proof_result.proof.len() <= MAX_PROOF_SIZE);
1952    }
1953
1954    #[test]
1955    fn test_rangeproof_sign_small_value() {
1956        use crate::pedersen::pedersen_commit;
1957
1958        // Test with small value
1959        let value = 100u64;
1960        let blind_hex = "abababababababababababababababababababababababababababababababab";
1961        let nonce_hex = "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd";
1962
1963        let blind = hex::decode(blind_hex).unwrap();
1964        let nonce = hex::decode(nonce_hex).unwrap();
1965
1966        let commitment_veil = pedersen_commit(value, &blind).unwrap();
1967        let mut commitment = commitment_veil.clone();
1968        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
1969
1970        println!("Testing rangeproof_sign with small value={}", value);
1971
1972        let result = rangeproof_sign(
1973            &commitment,
1974            value,
1975            &blind,
1976            &nonce,
1977            Some(b"small"),
1978            0,
1979            2,
1980            32,
1981        );
1982
1983        assert!(result.is_ok(), "Small value proof failed: {:?}", result.err());
1984        let proof_result = result.unwrap();
1985        println!("✅ Small value proof: {} bytes", proof_result.proof.len());
1986        assert!(proof_result.proof.len() <= MAX_PROOF_SIZE);
1987    }
1988
1989    #[test]
1990    fn test_rangeproof_sign_medium_value() {
1991        use crate::pedersen::pedersen_commit;
1992
1993        // Test with medium value
1994        let value = 500000u64;
1995        let blind_hex = "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd";
1996        let nonce_hex = "efefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefef";
1997
1998        let blind = hex::decode(blind_hex).unwrap();
1999        let nonce = hex::decode(nonce_hex).unwrap();
2000
2001        let commitment_veil = pedersen_commit(value, &blind).unwrap();
2002        let mut commitment = commitment_veil.clone();
2003        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
2004
2005        println!("Testing rangeproof_sign with medium value={}", value);
2006
2007        let result = rangeproof_sign(
2008            &commitment,
2009            value,
2010            &blind,
2011            &nonce,
2012            Some(b"medium value"),
2013            0,
2014            2,
2015            32,
2016        );
2017
2018        assert!(result.is_ok(), "Medium value proof failed: {:?}", result.err());
2019        let proof_result = result.unwrap();
2020        println!("✅ Medium value proof: {} bytes", proof_result.proof.len());
2021        assert!(proof_result.proof.len() <= MAX_PROOF_SIZE);
2022    }
2023
2024    #[test]
2025    fn test_rangeproof_sign_minimal_value() {
2026        use crate::pedersen::pedersen_commit;
2027
2028        // Test with minimal value (1)
2029        let value = 1u64;
2030        let blind_hex = "0101010101010101010101010101010101010101010101010101010101010101";
2031        let nonce_hex = "0202020202020202020202020202020202020202020202020202020202020202";
2032
2033        let blind = hex::decode(blind_hex).unwrap();
2034        let nonce = hex::decode(nonce_hex).unwrap();
2035
2036        let commitment_veil = pedersen_commit(value, &blind).unwrap();
2037        let mut commitment = commitment_veil.clone();
2038        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
2039
2040        println!("Testing rangeproof_sign with minimal value={}", value);
2041
2042        let result = rangeproof_sign(
2043            &commitment,
2044            value,
2045            &blind,
2046            &nonce,
2047            Some(b"one"),
2048            0,
2049            2,
2050            32,
2051        );
2052
2053        assert!(result.is_ok(), "Minimal value proof failed: {:?}", result.err());
2054        let proof_result = result.unwrap();
2055        println!("✅ Minimal value proof: {} bytes", proof_result.proof.len());
2056        assert!(proof_result.proof.len() <= MAX_PROOF_SIZE);
2057    }
2058
2059    #[test]
2060    fn test_rangeproof_sign_no_message() {
2061        use crate::pedersen::pedersen_commit;
2062
2063        // Test without message
2064        let value = 99999u64;
2065        let blind_hex = "9999999999999999999999999999999999999999999999999999999999999999";
2066        let nonce_hex = "8888888888888888888888888888888888888888888888888888888888888888";
2067
2068        let blind = hex::decode(blind_hex).unwrap();
2069        let nonce = hex::decode(nonce_hex).unwrap();
2070
2071        let commitment_veil = pedersen_commit(value, &blind).unwrap();
2072        let mut commitment = commitment_veil.clone();
2073        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
2074
2075        println!("Testing rangeproof_sign without message, value={}", value);
2076
2077        let result = rangeproof_sign(
2078            &commitment,
2079            value,
2080            &blind,
2081            &nonce,
2082            None, // No message
2083            0,
2084            2,
2085            32,
2086        );
2087
2088        assert!(result.is_ok(), "No-message proof failed: {:?}", result.err());
2089        let proof_result = result.unwrap();
2090        println!("✅ No-message proof: {} bytes", proof_result.proof.len());
2091        assert!(proof_result.proof.len() <= MAX_PROOF_SIZE);
2092    }
2093
2094    #[test]
2095    fn test_rangeproof_verify_basic() {
2096        use crate::pedersen::pedersen_commit;
2097
2098        // Sign a proof
2099        let value = 12345u64;
2100        let blind_hex = "1111111111111111111111111111111111111111111111111111111111111111";
2101        let nonce_hex = "2222222222222222222222222222222222222222222222222222222222222222";
2102
2103        let blind = hex::decode(blind_hex).unwrap();
2104        let nonce = hex::decode(nonce_hex).unwrap();
2105
2106        let commitment = pedersen_commit(value, &blind).unwrap();
2107        // Use commitment in Veil format (0x08/0x09) - our functions now handle this properly
2108
2109        let sign_result = rangeproof_sign(
2110            &commitment,
2111            value,
2112            &blind,
2113            &nonce,
2114            None,
2115            0,
2116            2,
2117            32,
2118        ).unwrap();
2119
2120        println!("Verifying proof for value={}", value);
2121
2122        // Verify the proof
2123        let verify_result = rangeproof_verify(&commitment, &sign_result.proof);
2124
2125        assert!(verify_result.is_ok(), "Verification failed: {:?}", verify_result.err());
2126        let result = verify_result.unwrap();
2127
2128        println!("✅ Proof verified! min={}, max={}", result.min_value, result.max_value);
2129
2130        // Check the value is in the proven range
2131        assert!(value >= result.min_value, "value {} < min_value {}", value, result.min_value);
2132        assert!(value <= result.max_value, "value {} > max_value {}", value, result.max_value);
2133    }
2134
2135    #[test]
2136    fn test_rangeproof_verify_large_value() {
2137        use crate::pedersen::pedersen_commit;
2138
2139        let value = 1000000000u64;
2140        let blind_hex = "1111111111111111111111111111111111111111111111111111111111111111";
2141        let nonce_hex = "2222222222222222222222222222222222222222222222222222222222222222";
2142
2143        let blind = hex::decode(blind_hex).unwrap();
2144        let nonce = hex::decode(nonce_hex).unwrap();
2145
2146        let commitment = pedersen_commit(value, &blind).unwrap();
2147        // Use commitment in Veil format (0x08/0x09) - our functions now handle this properly
2148
2149        let sign_result = rangeproof_sign(
2150            &commitment,
2151            value,
2152            &blind,
2153            &nonce,
2154            Some(b"large value test"),
2155            0,
2156            2,
2157            32,
2158        ).unwrap();
2159
2160        let verify_result = rangeproof_verify(&commitment, &sign_result.proof).unwrap();
2161
2162        println!("✅ Large value verified! min={}, max={}", verify_result.min_value, verify_result.max_value);
2163
2164        // Check the value is in the proven range
2165        assert!(value >= verify_result.min_value, "value {} < min_value {}", value, verify_result.min_value);
2166        assert!(value <= verify_result.max_value, "value {} > max_value {}", value, verify_result.max_value);
2167    }
2168
2169    #[test]
2170    fn test_rangeproof_verify_minimal_value() {
2171        use crate::pedersen::pedersen_commit;
2172
2173        let value = 1u64;
2174        let blind_hex = "0101010101010101010101010101010101010101010101010101010101010101";
2175        let nonce_hex = "0202020202020202020202020202020202020202020202020202020202020202";
2176
2177        let blind = hex::decode(blind_hex).unwrap();
2178        let nonce = hex::decode(nonce_hex).unwrap();
2179
2180        let commitment = pedersen_commit(value, &blind).unwrap();
2181        // Use commitment in Veil format (0x08/0x09) - our functions now handle this properly
2182
2183        let sign_result = rangeproof_sign(
2184            &commitment,
2185            value,
2186            &blind,
2187            &nonce,
2188            Some(b"one"),
2189            0,
2190            2,
2191            32,
2192        ).unwrap();
2193
2194        let verify_result = rangeproof_verify(&commitment, &sign_result.proof).unwrap();
2195
2196        println!("✅ Minimal value verified! min={}, max={}", verify_result.min_value, verify_result.max_value);
2197
2198        assert!(value >= verify_result.min_value);
2199        assert!(value <= verify_result.max_value);
2200    }
2201
2202    #[test]
2203    fn test_rangeproof_verify_tampered_proof_fails() {
2204        use crate::pedersen::pedersen_commit;
2205
2206        let value = 12345u64;
2207        let blind_hex = "1111111111111111111111111111111111111111111111111111111111111111";
2208        let nonce_hex = "2222222222222222222222222222222222222222222222222222222222222222";
2209
2210        let blind = hex::decode(blind_hex).unwrap();
2211        let nonce = hex::decode(nonce_hex).unwrap();
2212
2213        let commitment = pedersen_commit(value, &blind).unwrap();
2214        // Use commitment in Veil format (0x08/0x09) - our functions now handle this properly
2215
2216        let sign_result = rangeproof_sign(
2217            &commitment,
2218            value,
2219            &blind,
2220            &nonce,
2221            None,
2222            0,
2223            2,
2224            32,
2225        ).unwrap();
2226
2227        // Tamper with the proof
2228        let mut tampered_proof = sign_result.proof.clone();
2229        tampered_proof[100] ^= 0xff; // Flip some bits
2230
2231        let verify_result = rangeproof_verify(&commitment, &tampered_proof);
2232
2233        println!("Tampered proof result: {:?}", verify_result);
2234        assert!(verify_result.is_err(), "Tampered proof should fail verification!");
2235    }
2236
2237    #[test]
2238    fn test_rangeproof_verify_wrong_commitment_fails() {
2239        use crate::pedersen::pedersen_commit;
2240
2241        let value = 12345u64;
2242        let blind_hex = "1111111111111111111111111111111111111111111111111111111111111111";
2243        let nonce_hex = "2222222222222222222222222222222222222222222222222222222222222222";
2244
2245        let blind = hex::decode(blind_hex).unwrap();
2246        let nonce = hex::decode(nonce_hex).unwrap();
2247
2248        let commitment = pedersen_commit(value, &blind).unwrap();
2249        // Use commitment in Veil format (0x08/0x09) - our functions now handle this properly
2250
2251        let sign_result = rangeproof_sign(
2252            &commitment,
2253            value,
2254            &blind,
2255            &nonce,
2256            None,
2257            0,
2258            2,
2259            32,
2260        ).unwrap();
2261
2262        // Use wrong commitment (different value)
2263        let wrong_value = 99999u64;
2264        let wrong_commitment_veil = pedersen_commit(wrong_value, &blind).unwrap();
2265        let mut wrong_commitment = wrong_commitment_veil.clone();
2266        wrong_commitment[0] = if wrong_commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
2267
2268        let verify_result = rangeproof_verify(&wrong_commitment, &sign_result.proof);
2269
2270        println!("Wrong commitment result: {:?}", verify_result);
2271        assert!(verify_result.is_err(), "Wrong commitment should fail verification!");
2272    }
2273
2274    #[test]
2275    fn test_rangeproof_sign_max_value_32bit() {
2276        use crate::pedersen::pedersen_commit;
2277
2278        // Test with max 32-bit value
2279        let value = 4294967295u64; // 2^32 - 1
2280        let blind_hex = "fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe";
2281        let nonce_hex = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
2282
2283        let blind = hex::decode(blind_hex).unwrap();
2284        let nonce = hex::decode(nonce_hex).unwrap();
2285
2286        let commitment_veil = pedersen_commit(value, &blind).unwrap();
2287        let mut commitment = commitment_veil.clone();
2288        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
2289
2290        println!("Testing rangeproof_sign with max 32-bit value={}", value);
2291
2292        let result = rangeproof_sign(
2293            &commitment,
2294            value,
2295            &blind,
2296            &nonce,
2297            Some(b"max32"),
2298            0,
2299            2,
2300            32,
2301        );
2302
2303        assert!(result.is_ok(), "Max 32-bit value proof failed: {:?}", result.err());
2304        let proof_result = result.unwrap();
2305        println!("✅ Max 32-bit value proof: {} bytes", proof_result.proof.len());
2306        assert!(proof_result.proof.len() <= MAX_PROOF_SIZE);
2307    }
2308
2309    #[test]
2310    fn test_rangeproof_sign_and_rewind() {
2311        use crate::pedersen::pedersen_commit;
2312
2313        println!("\n=== Test: Sign and Rewind ===");
2314
2315        // Test parameters
2316        let value = 12345u64;
2317        let blind = [0x42u8; 32];
2318        let nonce = [0x01u8; 32];
2319        let message = b"Hello, Veil!";
2320
2321        println!("Original value: {}", value);
2322        println!("Original blind: {}", hex::encode(&blind));
2323        println!("Nonce: {}", hex::encode(&nonce));
2324        println!("Message: {:?}", std::str::from_utf8(message).unwrap());
2325
2326        // Create commitment
2327        let commitment_veil = pedersen_commit(value, &blind).unwrap();
2328        let mut commitment = commitment_veil.clone();
2329        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
2330
2331        // Sign the range proof
2332        let sign_result = rangeproof_sign(
2333            &commitment,
2334            value,
2335            &blind,
2336            &nonce,
2337            Some(message),
2338            0,    // min_value
2339            2,    // exp
2340            32,   // min_bits
2341        );
2342
2343        assert!(sign_result.is_ok(), "Signing failed: {:?}", sign_result.err());
2344        let proof_data = sign_result.unwrap();
2345
2346        println!("Proof size: {} bytes", proof_data.proof.len());
2347        println!("Commitment: {}", hex::encode(&proof_data.commitment));
2348
2349        // Rewind the proof
2350        let rewind_result = rangeproof_rewind(
2351            &nonce,
2352            &proof_data.commitment,
2353            &proof_data.proof,
2354        );
2355
2356        assert!(rewind_result.is_ok(), "Rewind failed: {:?}", rewind_result.err());
2357        let rewind_data = rewind_result.unwrap();
2358
2359        println!("\n=== Rewind Results ===");
2360        println!("Recovered value: {}", rewind_data.value);
2361        println!("Recovered blind: {}", hex::encode(&rewind_data.blind));
2362        println!("Value range: [{}, {}]", rewind_data.min_value, rewind_data.max_value);
2363        println!("Message length: {} bytes", rewind_data.message.len());
2364
2365        // Verify value matches
2366        assert_eq!(rewind_data.value, value, "Value mismatch!");
2367
2368        // Verify blind matches
2369        assert_eq!(rewind_data.blind.len(), 32, "Blind should be 32 bytes");
2370        assert_eq!(&rewind_data.blind[..], &blind[..], "Blind mismatch!");
2371
2372        // Verify value is in range
2373        assert!(
2374            rewind_data.value >= rewind_data.min_value,
2375            "Value {} below min {}",
2376            rewind_data.value,
2377            rewind_data.min_value
2378        );
2379        assert!(
2380            rewind_data.value <= rewind_data.max_value,
2381            "Value {} above max {}",
2382            rewind_data.value,
2383            rewind_data.max_value
2384        );
2385
2386        // Verify message is recovered (may be padded with zeros)
2387        assert!(
2388            rewind_data.message.len() >= message.len(),
2389            "Message too short"
2390        );
2391
2392        // Check that message is somewhere in the recovered data
2393        let message_found = rewind_data.message
2394            .windows(message.len())
2395            .any(|window| window == message);
2396
2397        if !message_found {
2398            println!("Warning: Message not found in recovered data");
2399            println!("Expected: {:?}", std::str::from_utf8(message).unwrap());
2400            println!("Got first {} bytes: {:?}",
2401                message.len(),
2402                std::str::from_utf8(&rewind_data.message[..message.len().min(rewind_data.message.len())])
2403            );
2404        }
2405
2406        println!("✅ Sign and rewind test passed!");
2407    }
2408
2409    #[test]
2410    fn test_rangeproof_sign_and_rewind_no_message() {
2411        use crate::pedersen::pedersen_commit;
2412
2413        println!("\n=== Test: Sign and Rewind (No Message) ===");
2414
2415        let value = 99999u64;
2416        let blind = [0xAAu8; 32];
2417        let nonce = [0xBBu8; 32];
2418
2419        // Create commitment
2420        let commitment_veil = pedersen_commit(value, &blind).unwrap();
2421        let mut commitment = commitment_veil.clone();
2422        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
2423
2424        // Sign without message
2425        let sign_result = rangeproof_sign(
2426            &commitment,
2427            value,
2428            &blind,
2429            &nonce,
2430            None,  // No message
2431            0,
2432            2,
2433            32,
2434        );
2435
2436        assert!(sign_result.is_ok());
2437        let proof_data = sign_result.unwrap();
2438
2439        // Rewind
2440        let rewind_result = rangeproof_rewind(
2441            &nonce,
2442            &proof_data.commitment,
2443            &proof_data.proof,
2444        );
2445
2446        assert!(rewind_result.is_ok());
2447        let rewind_data = rewind_result.unwrap();
2448
2449        // Verify
2450        assert_eq!(rewind_data.value, value);
2451        assert_eq!(&rewind_data.blind[..], &blind[..]);
2452
2453        println!("✅ No-message rewind test passed!");
2454    }
2455
2456    #[test]
2457    fn test_rangeproof_rewind_wrong_nonce_fails() {
2458        use crate::pedersen::pedersen_commit;
2459
2460        println!("\n=== Test: Rewind with Wrong Nonce ===");
2461
2462        let value = 54321u64;
2463        let blind = [0x77u8; 32];
2464        let nonce = [0x11u8; 32];
2465        let wrong_nonce = [0x22u8; 32];
2466
2467        // Create commitment
2468        let commitment_veil = pedersen_commit(value, &blind).unwrap();
2469        let mut commitment = commitment_veil.clone();
2470        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
2471
2472        // Sign with correct nonce
2473        let sign_result = rangeproof_sign(
2474            &commitment,
2475            value,
2476            &blind,
2477            &nonce,
2478            Some(b"secret"),
2479            0,
2480            2,
2481            32,
2482        );
2483
2484        assert!(sign_result.is_ok());
2485        let proof_data = sign_result.unwrap();
2486
2487        // Try to rewind with wrong nonce
2488        let rewind_result = rangeproof_rewind(
2489            &wrong_nonce,  // Wrong nonce
2490            &proof_data.commitment,
2491            &proof_data.proof,
2492        );
2493
2494        // Should succeed but recover wrong values
2495        if let Ok(rewind_data) = rewind_result {
2496            // Values should not match
2497            assert_ne!(rewind_data.value, value, "Should not recover correct value with wrong nonce");
2498            assert_ne!(&rewind_data.blind[..], &blind[..], "Should not recover correct blind with wrong nonce");
2499            println!("✅ Wrong nonce produces wrong values as expected");
2500        } else {
2501            println!("✅ Wrong nonce causes rewind to fail (also acceptable)");
2502        }
2503    }
2504
2505    #[test]
2506    fn test_rangeproof_info() {
2507        use crate::pedersen::pedersen_commit;
2508
2509        println!("\n=== Test: Rangeproof Info ===");
2510
2511        let value = 54321u64;
2512        let blind = [0x88u8; 32];
2513        let nonce = [0x99u8; 32];
2514
2515        // Create commitment
2516        let commitment_veil = pedersen_commit(value, &blind).unwrap();
2517        let mut commitment = commitment_veil.clone();
2518        commitment[0] = if commitment_veil[0] == 0x08 { 0x02 } else { 0x03 };
2519
2520        // Sign proof
2521        let sign_result = rangeproof_sign(
2522            &commitment,
2523            value,
2524            &blind,
2525            &nonce,
2526            Some(b"info test"),
2527            0,
2528            2,
2529            32,
2530        );
2531
2532        assert!(sign_result.is_ok());
2533        let proof_data = sign_result.unwrap();
2534
2535        println!("Proof size: {} bytes", proof_data.proof.len());
2536
2537        // Extract info (lightweight, no verification)
2538        let info_result = rangeproof_info(&proof_data.proof);
2539
2540        assert!(info_result.is_ok(), "Failed to extract info: {:?}", info_result.err());
2541        let info = info_result.unwrap();
2542
2543        println!("Proof info:");
2544        println!("  exp: {}", info.exp);
2545        println!("  mantissa: {}", info.mantissa);
2546        println!("  min_value: {}", info.min_value);
2547        println!("  max_value: {}", info.max_value);
2548
2549        // Verify value is in range
2550        assert!(
2551            value >= info.min_value,
2552            "Value {} below min {}",
2553            value,
2554            info.min_value
2555        );
2556        assert!(
2557            value <= info.max_value,
2558            "Value {} above max {}",
2559            value,
2560            info.max_value
2561        );
2562
2563        // Verify exp is valid
2564        assert!(info.exp >= -1 && info.exp <= 18, "Invalid exp: {}", info.exp);
2565
2566        // Verify mantissa is reasonable
2567        assert!(info.mantissa <= 64, "Invalid mantissa: {}", info.mantissa);
2568
2569        println!("✅ Rangeproof info test passed!");
2570    }
2571}