1use 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#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum RingCtError {
43 InvalidRingSignature,
45 UnbalancedTransaction,
47 InvalidCommitment,
49 EmptyTransaction,
51 SerializationError,
53}
54
55pub type RingCtResult<T> = Result<T, RingCtError>;
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct RingCtTransaction {
65 pub input_commitments: Vec<PedersenCommitment>,
67 pub output_commitments: Vec<PedersenCommitment>,
69 pub ring_signature: RingSignature,
71 pub ring: Vec<PublicKey>,
73 pub fee_commitment: PedersenCommitment,
75}
76
77#[derive(Debug, Clone)]
79pub struct RingCtInput {
80 pub amount: u64,
82 pub blinding: Scalar,
84 pub commitment: PedersenCommitment,
86}
87
88#[derive(Debug, Clone)]
90pub struct RingCtOutput {
91 pub amount: u64,
93 pub blinding: Scalar,
95 pub commitment: PedersenCommitment,
97}
98
99pub struct RingCtBuilder {
101 inputs: Vec<RingCtInput>,
102 outputs: Vec<RingCtOutput>,
103 fee: u64,
104 decoy_public_keys: Vec<PublicKey>,
105}
106
107impl RingCtBuilder {
108 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 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 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 pub fn fee(mut self, fee: u64) -> Self {
144 self.fee = fee;
145 self
146 }
147
148 pub fn add_decoys(mut self, decoys: Vec<PublicKey>) -> Self {
166 self.decoy_public_keys = decoys;
167 self
168 }
169
170 pub fn add_decoy(mut self, decoy: PublicKey) -> Self {
172 self.decoy_public_keys.push(decoy);
173 self
174 }
175
176 pub fn calculate_last_output_blinding(&self) -> Option<Scalar> {
189 if self.inputs.is_empty() {
190 return None;
191 }
192
193 let total_input_blinding: Scalar = self.inputs.iter().map(|i| i.blinding).sum();
195
196 let existing_output_blinding: Scalar = self.outputs.iter().map(|o| o.blinding).sum();
198
199 Some(total_input_blinding - existing_output_blinding)
202 }
203
204 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 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 let total_input_blinding: Scalar = self.inputs.iter().map(|i| i.blinding).sum();
242
243 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 let blinding = total_input_blinding - existing_output_blinding;
253
254 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 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 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 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 if total_input_amount != total_output_amount + self.fee {
283 return Err(RingCtError::UnbalancedTransaction);
284 }
285
286 let zero_opening = PedersenOpening::from_bytes(Scalar::ZERO.to_bytes());
288 let fee_commitment = commit_with_blinding(self.fee, &zero_opening);
289
290 let _excess_blinding = total_input_blinding - total_output_blinding;
292
293 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 let mut ring_keys = vec![signer.public_key()];
302
303 ring_keys.extend(self.decoy_public_keys.iter().copied());
305
306 if ring_keys.len() < 2 {
308 ring_keys.push(KeyPair::generate().public_key());
309 }
310
311 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 pub fn verify(&self) -> RingCtResult<bool> {
344 if self.input_commitments.is_empty() || self.output_commitments.is_empty() {
346 return Err(RingCtError::EmptyTransaction);
347 }
348
349 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 if !self.verify_balance() {
366 return Ok(false);
367 }
368
369 Ok(true)
370 }
371
372 fn verify_balance(&self) -> bool {
376 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 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 sum_outputs = sum_outputs.add(&self.fee_commitment);
390
391 sum_inputs == sum_outputs
393 }
394
395 pub fn input_count(&self) -> usize {
397 self.input_commitments.len()
398 }
399
400 pub fn output_count(&self) -> usize {
402 self.output_commitments.len()
403 }
404}
405
406fn 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 for input in inputs {
417 hasher.update(input.as_bytes());
418 }
419
420 for output in outputs {
422 hasher.update(output.as_bytes());
423 }
424
425 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 let blinding = random_scalar();
449 let tx = RingCtBuilder::new()
450 .add_input(100, blinding)
451 .add_output(100, blinding) .fee(0)
453 .build(&signer)
454 .unwrap();
455
456 assert!(tx.verify().unwrap());
458 }
459
460 #[test]
461 fn test_transaction_with_fee() {
462 let signer = KeyPair::generate();
463
464 let blinding = random_scalar();
467 let tx = RingCtBuilder::new()
468 .add_input(100, blinding)
469 .add_output(90, blinding) .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 let r1 = random_scalar();
484 let r2 = random_scalar();
485 let r3 = random_scalar();
486 let r4 = r1 + r2 - r3; let tx = RingCtBuilder::new()
489 .add_input(100, r1)
490 .add_input(200, r2)
491 .add_output(150, r3)
492 .add_output(140, r4) .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 let result = RingCtBuilder::new()
506 .add_input(100, random_scalar())
507 .add_output(200, random_scalar()) .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 let result = RingCtBuilder::new()
520 .add_output(100, random_scalar())
521 .build(&signer);
522 assert_eq!(result.unwrap_err(), RingCtError::EmptyTransaction);
523
524 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 let blinding = random_scalar();
538
539 let tx = RingCtBuilder::new()
540 .add_input(100, blinding)
541 .add_output(100, blinding) .fee(0)
543 .build(&signer)
544 .unwrap();
545
546 assert!(tx.verify_balance());
548 }
549
550 #[test]
551 fn test_transaction_with_decoys() {
552 let signer = KeyPair::generate();
553
554 let blinding = random_scalar();
558 let tx = RingCtBuilder::new()
559 .add_input(100, blinding)
560 .add_output(100, blinding) .fee(0)
562 .build(&signer)
563 .unwrap();
564
565 assert!(tx.verify().unwrap());
567 }
568
569 #[test]
570 fn test_confidential_amounts() {
571 let signer = KeyPair::generate();
574
575 let r_in = random_scalar();
578 let r_out1 = random_scalar();
579 let r_out2 = r_in - r_out1; let tx = RingCtBuilder::new()
582 .add_input(1000, r_in)
583 .add_output(500, r_out1)
584 .add_output(500, r_out2) .fee(0)
586 .build(&signer)
587 .unwrap();
588
589 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 let blinding = random_scalar();
602 let tx = RingCtBuilder::new()
603 .add_input(100, blinding)
604 .add_output(90, blinding) .fee(10)
606 .build(&signer)
607 .unwrap();
608
609 let serialized = crate::codec::encode(&tx).unwrap();
611
612 let deserialized: RingCtTransaction = crate::codec::decode(&serialized).unwrap();
614
615 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 let mut builder = RingCtBuilder::new();
643
644 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 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 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 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 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 let last_blinding = builder.calculate_last_output_blinding().unwrap();
718
719 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 let tx = RingCtBuilder::new()
732 .add_input(1000, r1)
733 .add_output(700, r2)
734 .add_output_auto_balance(300) .build(&signer)
736 .unwrap();
737
738 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 let tx = RingCtBuilder::new()
751 .add_input(1000, r1)
752 .add_output(700, r2)
753 .add_output(300, r3) .rebalance_last_output() .build(&signer)
756 .unwrap();
757
758 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 let tx = RingCtBuilder::new()
770 .add_input(1000, r1)
771 .add_output(600, r2)
772 .add_output_auto_balance(350) .fee(50)
774 .build(&signer)
775 .unwrap();
776
777 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 let tx = RingCtBuilder::new()
791 .add_input(500, r1)
792 .add_input(500, r2)
793 .add_output(300, r3)
794 .add_output(700, r4) .rebalance_last_output() .build(&signer)
797 .unwrap();
798
799 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 let decoy1 = KeyPair::generate().public_key();
811 let decoy2 = KeyPair::generate().public_key();
812 let decoy3 = KeyPair::generate().public_key();
813
814 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 assert!(tx.verify().unwrap());
827
828 assert_eq!(tx.ring.len(), 4);
830
831 assert!(tx.ring.contains(&signer.public_key()));
833
834 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 let decoys: Vec<PublicKey> = (0..5).map(|_| KeyPair::generate().public_key()).collect();
847
848 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 assert!(tx.verify().unwrap());
858
859 assert_eq!(tx.ring.len(), 6);
861
862 for decoy in &decoys {
864 assert!(tx.ring.contains(decoy));
865 }
866 }
867}