chie_crypto/
ringct.rs

1//! Ring Confidential Transactions (Ring CT)
2//!
3//! Combines ring signatures with confidential transactions to provide:
4//! - Sender anonymity (via ring signatures)
5//! - Amount confidentiality (via Pedersen commitments)
6//! - Balance verification without revealing amounts
7//!
8//! Perfect for privacy-preserving bandwidth credit transfers in CHIE protocol.
9//!
10//! ## Important Note on Blinding Factors
11//!
12//! For Ring CT transactions to verify correctly, the blinding factors must balance:
13//! `sum(input_blindings) = sum(output_blindings) + fee_blinding`
14//!
15//! Since fee uses zero blinding, this simplifies to:
16//! `sum(input_blindings) = sum(output_blindings)`
17//!
18//! In production, transaction builders should calculate the last output's blinding
19//! factor to ensure this balance.
20//!
21//! ## Implementation Notes
22//!
23//! - **Blinding Factor Balance**: Helper methods (`add_output_auto_balance`, `rebalance_last_output`,
24//!   `calculate_last_output_blinding`) are provided to automatically balance blinding factors,
25//!   ensuring transactions verify correctly.
26//!
27//! - **Decoy Support**: Full support for public key decoys via `add_decoys` and `add_decoy` methods.
28//!   In production, use real public keys from the blockchain for actual anonymity.
29//!
30//! - **Range Proofs**: Not currently implemented due to API limitations in the bulletproof module.
31//!   They will be added when bulletproofs support custom blinding factors. Without range proofs,
32//!   the system cannot prevent negative outputs, which is a security concern for production use.
33
34use crate::pedersen::{PedersenCommitment, PedersenOpening, commit_with_blinding};
35use crate::ring::{RingSignature, sign_ring, verify_ring};
36use crate::{KeyPair, PublicKey};
37use curve25519_dalek::scalar::Scalar;
38use serde::{Deserialize, Serialize};
39
40/// Errors that can occur in Ring CT operations
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum RingCtError {
43    /// Invalid ring signature
44    InvalidRingSignature,
45    /// Transaction is not balanced (inputs != outputs)
46    UnbalancedTransaction,
47    /// Invalid commitment
48    InvalidCommitment,
49    /// Empty inputs or outputs
50    EmptyTransaction,
51    /// Serialization error
52    SerializationError,
53}
54
55pub type RingCtResult<T> = Result<T, RingCtError>;
56
57/// A Ring CT transaction with hidden amounts and anonymous sender
58///
59/// Ring CT provides:
60/// - Sender anonymity via ring signatures
61/// - Amount confidentiality via Pedersen commitments
62/// - Transaction validity via balance checks
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct RingCtTransaction {
65    /// Commitments to input amounts: C_in = v*H + r*G
66    pub input_commitments: Vec<PedersenCommitment>,
67    /// Commitments to output amounts: C_out = v*H + r*G
68    pub output_commitments: Vec<PedersenCommitment>,
69    /// Ring signature proving ownership of one input without revealing which
70    pub ring_signature: RingSignature,
71    /// The ring of public keys used for the signature
72    pub ring: Vec<PublicKey>,
73    /// Transaction fee commitment (can be zero)
74    pub fee_commitment: PedersenCommitment,
75}
76
77/// Transaction input with secret opening
78#[derive(Debug, Clone)]
79pub struct RingCtInput {
80    /// The actual amount being spent
81    pub amount: u64,
82    /// The blinding factor for the commitment
83    pub blinding: Scalar,
84    /// The commitment to the amount: C = amount*H + blinding*G
85    pub commitment: PedersenCommitment,
86}
87
88/// Transaction output with commitment
89#[derive(Debug, Clone)]
90pub struct RingCtOutput {
91    /// The actual amount (known to creator, hidden in transaction)
92    pub amount: u64,
93    /// The blinding factor for the commitment
94    pub blinding: Scalar,
95    /// The commitment to the amount: C = amount*H + blinding*G
96    pub commitment: PedersenCommitment,
97}
98
99/// Builder for creating Ring CT transactions
100pub struct RingCtBuilder {
101    inputs: Vec<RingCtInput>,
102    outputs: Vec<RingCtOutput>,
103    fee: u64,
104    decoy_public_keys: Vec<PublicKey>,
105}
106
107impl RingCtBuilder {
108    /// Create a new Ring CT transaction builder
109    pub fn new() -> Self {
110        Self {
111            inputs: Vec::new(),
112            outputs: Vec::new(),
113            fee: 0,
114            decoy_public_keys: Vec::new(),
115        }
116    }
117
118    /// Add an input to the transaction
119    pub fn add_input(mut self, amount: u64, blinding: Scalar) -> Self {
120        let opening = PedersenOpening::from_bytes(blinding.to_bytes());
121        let commitment = commit_with_blinding(amount, &opening);
122        self.inputs.push(RingCtInput {
123            amount,
124            blinding,
125            commitment,
126        });
127        self
128    }
129
130    /// Add an output to the transaction
131    pub fn add_output(mut self, amount: u64, blinding: Scalar) -> Self {
132        let opening = PedersenOpening::from_bytes(blinding.to_bytes());
133        let commitment = commit_with_blinding(amount, &opening);
134        self.outputs.push(RingCtOutput {
135            amount,
136            blinding,
137            commitment,
138        });
139        self
140    }
141
142    /// Set transaction fee (in smallest units)
143    pub fn fee(mut self, fee: u64) -> Self {
144        self.fee = fee;
145        self
146    }
147
148    /// Add decoy public keys for ring signature (for anonymity)
149    ///
150    /// Decoy public keys are used to create a ring of possible signers, making it
151    /// impossible to determine which key actually signed the transaction.
152    ///
153    /// In a real Ring CT system, these should be real public keys from the blockchain
154    /// to provide actual anonymity. The more decoys, the greater the anonymity set.
155    ///
156    /// # Example
157    /// ```ignore
158    /// let decoys = vec![
159    ///     some_real_public_key_1,
160    ///     some_real_public_key_2,
161    ///     some_real_public_key_3,
162    /// ];
163    /// builder.add_decoys(decoys);
164    /// ```
165    pub fn add_decoys(mut self, decoys: Vec<PublicKey>) -> Self {
166        self.decoy_public_keys = decoys;
167        self
168    }
169
170    /// Add a single decoy public key for ring signature
171    pub fn add_decoy(mut self, decoy: PublicKey) -> Self {
172        self.decoy_public_keys.push(decoy);
173        self
174    }
175
176    /// Calculate the required blinding factor for a new output to balance the transaction
177    ///
178    /// For Ring CT transactions to verify correctly, the blinding factors must balance:
179    /// `sum(input_blindings) = sum(output_blindings) + fee_blinding`
180    ///
181    /// Since fee uses zero blinding, this simplifies to:
182    /// `sum(input_blindings) = sum(output_blindings)`
183    ///
184    /// This method calculates what the next output's blinding factor should be
185    /// to achieve this balance given all current inputs and outputs.
186    ///
187    /// Returns `None` if there are no inputs.
188    pub fn calculate_last_output_blinding(&self) -> Option<Scalar> {
189        if self.inputs.is_empty() {
190            return None;
191        }
192
193        // Calculate total input blinding
194        let total_input_blinding: Scalar = self.inputs.iter().map(|i| i.blinding).sum();
195
196        // Calculate blinding from ALL current outputs
197        let existing_output_blinding: Scalar = self.outputs.iter().map(|o| o.blinding).sum();
198
199        // Fee blinding is always zero, so we don't need to account for it
200        // Next output blinding = total_input_blinding - existing_output_blinding
201        Some(total_input_blinding - existing_output_blinding)
202    }
203
204    /// Add an output with automatic blinding factor calculation
205    ///
206    /// This method automatically calculates the blinding factor needed to balance
207    /// the transaction. It should only be used for the LAST output in a transaction.
208    ///
209    /// # Panics
210    /// Panics if there are no inputs (cannot calculate balance without inputs)
211    pub fn add_output_auto_balance(mut self, amount: u64) -> Self {
212        let blinding = self
213            .calculate_last_output_blinding()
214            .expect("Cannot auto-balance without inputs");
215
216        let opening = PedersenOpening::from_bytes(blinding.to_bytes());
217        let commitment = commit_with_blinding(amount, &opening);
218        self.outputs.push(RingCtOutput {
219            amount,
220            blinding,
221            commitment,
222        });
223        self
224    }
225
226    /// Rebalance the last output's blinding factor to ensure transaction balance
227    ///
228    /// This is useful if you've added all inputs and outputs but want to ensure
229    /// the transaction is properly balanced. It recalculates the last output's
230    /// commitment with a new blinding factor that ensures balance.
231    ///
232    /// Returns the builder for chaining.
233    ///
234    /// # Panics
235    /// Panics if there are no inputs or no outputs
236    pub fn rebalance_last_output(mut self) -> Self {
237        assert!(!self.inputs.is_empty(), "Cannot rebalance without inputs");
238        assert!(!self.outputs.is_empty(), "Cannot rebalance without outputs");
239
240        // Calculate total input blinding
241        let total_input_blinding: Scalar = self.inputs.iter().map(|i| i.blinding).sum();
242
243        // Calculate blinding from all outputs EXCEPT the last one
244        let existing_output_blinding: Scalar = self
245            .outputs
246            .iter()
247            .take(self.outputs.len() - 1)
248            .map(|o| o.blinding)
249            .sum();
250
251        // Calculate what the last output's blinding should be
252        let blinding = total_input_blinding - existing_output_blinding;
253
254        // Update the last output's blinding and recalculate commitment
255        if let Some(last_output) = self.outputs.last_mut() {
256            last_output.blinding = blinding;
257            let opening = PedersenOpening::from_bytes(blinding.to_bytes());
258            last_output.commitment = commit_with_blinding(last_output.amount, &opening);
259        }
260
261        self
262    }
263
264    /// Build and sign the transaction with the given keypair
265    ///
266    /// The keypair is used to create the ring signature proving ownership
267    /// of one of the inputs without revealing which one.
268    pub fn build(self, signer: &KeyPair) -> RingCtResult<RingCtTransaction> {
269        if self.inputs.is_empty() || self.outputs.is_empty() {
270            return Err(RingCtError::EmptyTransaction);
271        }
272
273        // Calculate total input amount and blinding
274        let total_input_amount: u64 = self.inputs.iter().map(|i| i.amount).sum();
275        let total_input_blinding: Scalar = self.inputs.iter().map(|i| i.blinding).sum();
276
277        // Calculate total output amount and blinding
278        let total_output_amount: u64 = self.outputs.iter().map(|o| o.amount).sum();
279        let total_output_blinding: Scalar = self.outputs.iter().map(|o| o.blinding).sum();
280
281        // Check balance: inputs = outputs + fee
282        if total_input_amount != total_output_amount + self.fee {
283            return Err(RingCtError::UnbalancedTransaction);
284        }
285
286        // Create fee commitment with zero blinding
287        let zero_opening = PedersenOpening::from_bytes(Scalar::ZERO.to_bytes());
288        let fee_commitment = commit_with_blinding(self.fee, &zero_opening);
289
290        // Calculate excess blinding: r_in - r_out - r_fee
291        let _excess_blinding = total_input_blinding - total_output_blinding;
292
293        // Create commitments lists
294        let input_commitments: Vec<PedersenCommitment> =
295            self.inputs.iter().map(|i| i.commitment).collect();
296        let output_commitments: Vec<PedersenCommitment> =
297            self.outputs.iter().map(|o| o.commitment).collect();
298
299        // Create ring for anonymity (signer's key + decoy keys)
300        // Ring signatures require at least 2 keys, so add a dummy decoy if none provided
301        let mut ring_keys = vec![signer.public_key()];
302
303        // Add all provided decoy public keys
304        ring_keys.extend(self.decoy_public_keys.iter().copied());
305
306        // If no decoys provided, add a dummy one (just for testing - in production, real decoys should be used)
307        if ring_keys.len() < 2 {
308            ring_keys.push(KeyPair::generate().public_key());
309        }
310
311        // Create ring signature over the transaction hash
312        let tx_hash =
313            compute_transaction_hash(&input_commitments, &output_commitments, &fee_commitment);
314
315        let ring_signature = sign_ring(signer, &ring_keys, &tx_hash)
316            .map_err(|_| RingCtError::InvalidRingSignature)?;
317
318        Ok(RingCtTransaction {
319            input_commitments,
320            output_commitments,
321            ring_signature,
322            ring: ring_keys,
323            fee_commitment,
324        })
325    }
326}
327
328impl Default for RingCtBuilder {
329    fn default() -> Self {
330        Self::new()
331    }
332}
333
334impl RingCtTransaction {
335    /// Verify the Ring CT transaction
336    ///
337    /// Checks:
338    /// 1. Ring signature is valid (proves sender is in the ring)
339    /// 2. Transaction is balanced: sum(inputs) = sum(outputs) + fee
340    ///
341    /// Note: Range proof verification is not currently implemented.
342    /// In production, range proofs should be added to prevent negative outputs.
343    pub fn verify(&self) -> RingCtResult<bool> {
344        // Check for empty transaction
345        if self.input_commitments.is_empty() || self.output_commitments.is_empty() {
346            return Err(RingCtError::EmptyTransaction);
347        }
348
349        // Verify ring signature using the stored ring
350        let tx_hash = compute_transaction_hash(
351            &self.input_commitments,
352            &self.output_commitments,
353            &self.fee_commitment,
354        );
355
356        let ring_valid = verify_ring(&self.ring, &tx_hash, &self.ring_signature)
357            .map_err(|_| RingCtError::InvalidRingSignature)?;
358
359        if !ring_valid {
360            return Ok(false);
361        }
362
363        // Verify balance: sum(C_in) = sum(C_out) + C_fee
364        // This works because Pedersen commitments are homomorphic
365        if !self.verify_balance() {
366            return Ok(false);
367        }
368
369        Ok(true)
370    }
371
372    /// Verify transaction balance using homomorphic property of commitments
373    ///
374    /// Checks that: sum(inputs) - sum(outputs) - fee = 0 (as commitments)
375    fn verify_balance(&self) -> bool {
376        // Sum all input commitments using homomorphic addition
377        let mut sum_inputs = self.input_commitments[0];
378        for input in &self.input_commitments[1..] {
379            sum_inputs = sum_inputs.add(input);
380        }
381
382        // Sum all output commitments
383        let mut sum_outputs = self.output_commitments[0];
384        for output in &self.output_commitments[1..] {
385            sum_outputs = sum_outputs.add(output);
386        }
387
388        // Add fee commitment
389        sum_outputs = sum_outputs.add(&self.fee_commitment);
390
391        // Check if inputs = outputs + fee
392        sum_inputs == sum_outputs
393    }
394
395    /// Get the total number of inputs
396    pub fn input_count(&self) -> usize {
397        self.input_commitments.len()
398    }
399
400    /// Get the total number of outputs
401    pub fn output_count(&self) -> usize {
402        self.output_commitments.len()
403    }
404}
405
406/// Compute a hash of the transaction for signing
407fn compute_transaction_hash(
408    inputs: &[PedersenCommitment],
409    outputs: &[PedersenCommitment],
410    fee: &PedersenCommitment,
411) -> Vec<u8> {
412    use blake3::Hasher;
413    let mut hasher = Hasher::new();
414
415    // Hash all input commitments
416    for input in inputs {
417        hasher.update(input.as_bytes());
418    }
419
420    // Hash all output commitments
421    for output in outputs {
422        hasher.update(output.as_bytes());
423    }
424
425    // Hash fee commitment
426    hasher.update(fee.as_bytes());
427
428    hasher.finalize().as_bytes().to_vec()
429}
430
431#[cfg(test)]
432mod tests {
433    use super::*;
434    use rand::Rng;
435
436    fn random_scalar() -> Scalar {
437        let mut bytes = [0u8; 32];
438        rand::thread_rng().fill(&mut bytes);
439        Scalar::from_bytes_mod_order(bytes)
440    }
441
442    #[test]
443    fn test_simple_ringct_transaction() {
444        let signer = KeyPair::generate();
445
446        // Create a simple 1-input, 1-output transaction
447        // IMPORTANT: Blinding factors must balance: r_in = r_out + r_fee
448        let blinding = random_scalar();
449        let tx = RingCtBuilder::new()
450            .add_input(100, blinding)
451            .add_output(100, blinding) // Same blinding since no fee
452            .fee(0)
453            .build(&signer)
454            .unwrap();
455
456        // Verify the transaction
457        assert!(tx.verify().unwrap());
458    }
459
460    #[test]
461    fn test_transaction_with_fee() {
462        let signer = KeyPair::generate();
463
464        // Create transaction with fee
465        // IMPORTANT: Blinding factors must balance: sum(r_in) = sum(r_out)
466        let blinding = random_scalar();
467        let tx = RingCtBuilder::new()
468            .add_input(100, blinding)
469            .add_output(90, blinding) // Same blinding since fee has zero blinding
470            .fee(10)
471            .build(&signer)
472            .unwrap();
473
474        assert!(tx.verify().unwrap());
475    }
476
477    #[test]
478    fn test_multiple_inputs_outputs() {
479        let signer = KeyPair::generate();
480
481        // Create transaction with multiple inputs and outputs
482        // IMPORTANT: Blinding factors must balance: sum(r_in) = sum(r_out)
483        let r1 = random_scalar();
484        let r2 = random_scalar();
485        let r3 = random_scalar();
486        let r4 = r1 + r2 - r3; // Calculate r4 to balance: r1 + r2 = r3 + r4
487
488        let tx = RingCtBuilder::new()
489            .add_input(100, r1)
490            .add_input(200, r2)
491            .add_output(150, r3)
492            .add_output(140, r4) // Calculated to ensure balance
493            .fee(10)
494            .build(&signer)
495            .unwrap();
496
497        assert!(tx.verify().unwrap());
498    }
499
500    #[test]
501    fn test_unbalanced_transaction_rejected() {
502        let signer = KeyPair::generate();
503
504        // Try to create unbalanced transaction
505        let result = RingCtBuilder::new()
506            .add_input(100, random_scalar())
507            .add_output(200, random_scalar()) // More output than input!
508            .fee(0)
509            .build(&signer);
510
511        assert_eq!(result.unwrap_err(), RingCtError::UnbalancedTransaction);
512    }
513
514    #[test]
515    fn test_empty_transaction_rejected() {
516        let signer = KeyPair::generate();
517
518        // No inputs
519        let result = RingCtBuilder::new()
520            .add_output(100, random_scalar())
521            .build(&signer);
522        assert_eq!(result.unwrap_err(), RingCtError::EmptyTransaction);
523
524        // No outputs
525        let result = RingCtBuilder::new()
526            .add_input(100, random_scalar())
527            .build(&signer);
528        assert_eq!(result.unwrap_err(), RingCtError::EmptyTransaction);
529    }
530
531    #[test]
532    fn test_homomorphic_balance_check() {
533        let signer = KeyPair::generate();
534
535        // Create transaction
536        // IMPORTANT: For balance to verify, blinding factors must match
537        let blinding = random_scalar();
538
539        let tx = RingCtBuilder::new()
540            .add_input(100, blinding)
541            .add_output(100, blinding) // Same blinding for balance
542            .fee(0)
543            .build(&signer)
544            .unwrap();
545
546        // Balance should verify correctly
547        assert!(tx.verify_balance());
548    }
549
550    #[test]
551    fn test_transaction_with_decoys() {
552        let signer = KeyPair::generate();
553
554        // Create transaction with decoys for ring signature
555        // IMPORTANT: Blinding factors must balance
556        // Note: The builder automatically generates dummy decoy keys for ring signature
557        let blinding = random_scalar();
558        let tx = RingCtBuilder::new()
559            .add_input(100, blinding)
560            .add_output(100, blinding) // Same blinding for balance
561            .fee(0)
562            .build(&signer)
563            .unwrap();
564
565        // Ring includes actual signer + decoy public keys (internally)
566        assert!(tx.verify().unwrap());
567    }
568
569    #[test]
570    fn test_confidential_amounts() {
571        // Commitments hide the actual amounts - only balance is verifiable
572
573        let signer = KeyPair::generate();
574
575        // Create transaction with valid amounts
576        // IMPORTANT: Blinding factors must balance: r_in = r_out1 + r_out2
577        let r_in = random_scalar();
578        let r_out1 = random_scalar();
579        let r_out2 = r_in - r_out1; // Calculate r_out2 to balance
580
581        let tx = RingCtBuilder::new()
582            .add_input(1000, r_in)
583            .add_output(500, r_out1)
584            .add_output(500, r_out2) // Calculated to ensure balance
585            .fee(0)
586            .build(&signer)
587            .unwrap();
588
589        // Commitments exist but amounts are hidden
590        assert_eq!(tx.output_commitments.len(), 2);
591        assert_eq!(tx.input_commitments.len(), 1);
592
593        assert!(tx.verify().unwrap());
594    }
595
596    #[test]
597    fn test_serialization() {
598        let signer = KeyPair::generate();
599
600        // IMPORTANT: Blinding factors must balance
601        let blinding = random_scalar();
602        let tx = RingCtBuilder::new()
603            .add_input(100, blinding)
604            .add_output(90, blinding) // Same blinding since fee has zero blinding
605            .fee(10)
606            .build(&signer)
607            .unwrap();
608
609        // Serialize
610        let serialized = crate::codec::encode(&tx).unwrap();
611
612        // Deserialize
613        let deserialized: RingCtTransaction = crate::codec::decode(&serialized).unwrap();
614
615        // Verify deserialized transaction
616        assert!(deserialized.verify().unwrap());
617    }
618
619    #[test]
620    fn test_transaction_counts() {
621        let signer = KeyPair::generate();
622
623        let tx = RingCtBuilder::new()
624            .add_input(100, random_scalar())
625            .add_input(200, random_scalar())
626            .add_output(150, random_scalar())
627            .add_output(140, random_scalar())
628            .fee(10)
629            .build(&signer)
630            .unwrap();
631
632        assert_eq!(tx.input_count(), 2);
633        assert_eq!(tx.output_count(), 2);
634    }
635
636    #[test]
637    fn test_large_transaction() {
638        let signer = KeyPair::generate();
639
640        // Create transaction with many inputs and outputs
641        // IMPORTANT: Blinding factors must balance: sum(r_in) = sum(r_out)
642        let mut builder = RingCtBuilder::new();
643
644        // Generate random blinding factors for inputs
645        let mut input_blindings = Vec::new();
646        let mut total_in = 0u64;
647        for _ in 0..10 {
648            let amount = 100;
649            total_in += amount;
650            let blinding = random_scalar();
651            input_blindings.push(blinding);
652            builder = builder.add_input(amount, blinding);
653        }
654
655        // Generate random blinding factors for outputs (except the last one)
656        let mut output_blindings = Vec::new();
657        let mut total_out = 0u64;
658        for _ in 0..8 {
659            let amount = 100;
660            total_out += amount;
661            let blinding = random_scalar();
662            output_blindings.push(blinding);
663            builder = builder.add_output(amount, blinding);
664        }
665
666        // Calculate the last output's blinding to ensure balance
667        let sum_input_blindings: Scalar = input_blindings.iter().sum();
668        let sum_output_blindings: Scalar = output_blindings.iter().sum();
669        let last_blinding = sum_input_blindings - sum_output_blindings;
670
671        let amount = 100;
672        total_out += amount;
673        builder = builder.add_output(amount, last_blinding);
674
675        let fee = total_in - total_out;
676        let tx = builder.fee(fee).build(&signer).unwrap();
677
678        assert_eq!(tx.input_count(), 10);
679        assert_eq!(tx.output_count(), 9);
680
681        assert!(tx.verify().unwrap());
682    }
683
684    #[test]
685    fn test_commitment_homomorphism() {
686        // Test that C(a) + C(b) = C(a+b)
687        let a = 100u64;
688        let b = 200u64;
689        let r1 = random_scalar();
690        let r2 = random_scalar();
691
692        let opening1 = PedersenOpening::from_bytes(r1.to_bytes());
693        let opening2 = PedersenOpening::from_bytes(r2.to_bytes());
694        let opening_sum = PedersenOpening::from_bytes((r1 + r2).to_bytes());
695
696        let c1 = commit_with_blinding(a, &opening1);
697        let c2 = commit_with_blinding(b, &opening2);
698        let c_sum = commit_with_blinding(a + b, &opening_sum);
699
700        // Homomorphic property: C(a) + C(b) = C(a+b)
701        let c_added = c1.add(&c2);
702        assert_eq!(c_added, c_sum);
703    }
704
705    #[test]
706    fn test_calculate_last_output_blinding() {
707        let r1 = random_scalar();
708        let r2 = random_scalar();
709        let r3 = random_scalar();
710
711        let builder = RingCtBuilder::new()
712            .add_input(100, r1)
713            .add_input(50, r2)
714            .add_output(80, r3);
715
716        // Calculate what the last output's blinding should be
717        let last_blinding = builder.calculate_last_output_blinding().unwrap();
718
719        // Should be: r1 + r2 - r3 (since we have 2 inputs and 1 output so far)
720        let expected = r1 + r2 - r3;
721        assert_eq!(last_blinding, expected);
722    }
723
724    #[test]
725    fn test_add_output_auto_balance() {
726        let signer = KeyPair::generate();
727        let r1 = random_scalar();
728        let r2 = random_scalar();
729
730        // Create a transaction with auto-balanced last output
731        let tx = RingCtBuilder::new()
732            .add_input(1000, r1)
733            .add_output(700, r2)
734            .add_output_auto_balance(300) // Should auto-calculate blinding
735            .build(&signer)
736            .unwrap();
737
738        // Transaction should verify because blinding is balanced
739        assert!(tx.verify().unwrap());
740    }
741
742    #[test]
743    fn test_rebalance_last_output() {
744        let signer = KeyPair::generate();
745        let r1 = random_scalar();
746        let r2 = random_scalar();
747        let r3 = random_scalar();
748
749        // Create a transaction with initially unbalanced outputs
750        let tx = RingCtBuilder::new()
751            .add_input(1000, r1)
752            .add_output(700, r2)
753            .add_output(300, r3) // Initially uses r3
754            .rebalance_last_output() // This will recalculate the last output's blinding
755            .build(&signer)
756            .unwrap();
757
758        // Transaction should verify because we rebalanced
759        assert!(tx.verify().unwrap());
760    }
761
762    #[test]
763    fn test_auto_balance_with_fee() {
764        let signer = KeyPair::generate();
765        let r1 = random_scalar();
766        let r2 = random_scalar();
767
768        // Create a transaction with fee
769        let tx = RingCtBuilder::new()
770            .add_input(1000, r1)
771            .add_output(600, r2)
772            .add_output_auto_balance(350) // 1000 - 600 - 50 (fee) = 350
773            .fee(50)
774            .build(&signer)
775            .unwrap();
776
777        // Transaction should verify
778        assert!(tx.verify().unwrap());
779    }
780
781    #[test]
782    fn test_multiple_inputs_auto_balance() {
783        let signer = KeyPair::generate();
784        let r1 = random_scalar();
785        let r2 = random_scalar();
786        let r3 = random_scalar();
787        let r4 = random_scalar();
788
789        // Create a transaction with multiple inputs and balanced amounts
790        let tx = RingCtBuilder::new()
791            .add_input(500, r1)
792            .add_input(500, r2)
793            .add_output(300, r3)
794            .add_output(700, r4) // 500 + 500 = 300 + 700
795            .rebalance_last_output() // Ensure blinding balance
796            .build(&signer)
797            .unwrap();
798
799        // Transaction should verify
800        assert!(tx.verify().unwrap());
801    }
802
803    #[test]
804    fn test_real_decoy_public_keys() {
805        let signer = KeyPair::generate();
806        let r1 = random_scalar();
807        let r2 = random_scalar();
808
809        // Create real decoy public keys (simulating other users' keys)
810        let decoy1 = KeyPair::generate().public_key();
811        let decoy2 = KeyPair::generate().public_key();
812        let decoy3 = KeyPair::generate().public_key();
813
814        // Create a transaction with real decoys
815        let tx = RingCtBuilder::new()
816            .add_input(1000, r1)
817            .add_output(600, r2)
818            .add_output_auto_balance(400)
819            .add_decoy(decoy1)
820            .add_decoy(decoy2)
821            .add_decoy(decoy3)
822            .build(&signer)
823            .unwrap();
824
825        // Transaction should verify
826        assert!(tx.verify().unwrap());
827
828        // Ring should contain signer + 3 decoys = 4 keys
829        assert_eq!(tx.ring.len(), 4);
830
831        // Ring should contain the signer's public key
832        assert!(tx.ring.contains(&signer.public_key()));
833
834        // Ring should contain all decoys
835        assert!(tx.ring.contains(&decoy1));
836        assert!(tx.ring.contains(&decoy2));
837        assert!(tx.ring.contains(&decoy3));
838    }
839
840    #[test]
841    fn test_bulk_decoys_addition() {
842        let signer = KeyPair::generate();
843        let r1 = random_scalar();
844
845        // Create a list of decoy public keys
846        let decoys: Vec<PublicKey> = (0..5).map(|_| KeyPair::generate().public_key()).collect();
847
848        // Create a transaction with bulk decoys
849        let tx = RingCtBuilder::new()
850            .add_input(1000, r1)
851            .add_output_auto_balance(1000)
852            .add_decoys(decoys.clone())
853            .build(&signer)
854            .unwrap();
855
856        // Transaction should verify
857        assert!(tx.verify().unwrap());
858
859        // Ring should contain signer + 5 decoys = 6 keys
860        assert_eq!(tx.ring.len(), 6);
861
862        // All decoys should be in the ring
863        for decoy in &decoys {
864            assert!(tx.ring.contains(decoy));
865        }
866    }
867}