monero_serai/
ringct.rs

1use std_shims::{
2  vec,
3  vec::Vec,
4  io::{self, Read, Write},
5};
6
7use zeroize::Zeroize;
8
9use curve25519_dalek::edwards::CompressedEdwardsY;
10
11pub use monero_mlsag as mlsag;
12pub use monero_clsag as clsag;
13pub use monero_borromean as borromean;
14pub use monero_bulletproofs as bulletproofs;
15
16use crate::{
17  io::*,
18  ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof},
19};
20
21/// An encrypted amount.
22#[derive(Clone, PartialEq, Eq, Debug)]
23pub enum EncryptedAmount {
24  /// The original format for encrypted amounts.
25  Original {
26    /// A mask used with a mask derived from the shared secret to encrypt the amount.
27    mask: [u8; 32],
28    /// The amount, as a scalar, encrypted.
29    amount: [u8; 32],
30  },
31  /// The "compact" format for encrypted amounts.
32  Compact {
33    /// The amount, as a u64, encrypted.
34    amount: [u8; 8],
35  },
36}
37
38impl EncryptedAmount {
39  /// Read an EncryptedAmount from a reader.
40  pub fn read<R: Read>(compact: bool, r: &mut R) -> io::Result<EncryptedAmount> {
41    Ok(if !compact {
42      EncryptedAmount::Original { mask: read_bytes(r)?, amount: read_bytes(r)? }
43    } else {
44      EncryptedAmount::Compact { amount: read_bytes(r)? }
45    })
46  }
47
48  /// Write the EncryptedAmount to a writer.
49  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
50    match self {
51      EncryptedAmount::Original { mask, amount } => {
52        w.write_all(mask)?;
53        w.write_all(amount)
54      }
55      EncryptedAmount::Compact { amount } => w.write_all(amount),
56    }
57  }
58}
59
60/// The type of the RingCT data.
61#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
62pub enum RctType {
63  /// One MLSAG for multiple inputs and Borromean range proofs.
64  ///
65  /// This aligns with RCTTypeFull.
66  AggregateMlsagBorromean,
67  // One MLSAG for each input and a Borromean range proof.
68  ///
69  /// This aligns with RCTTypeSimple.
70  MlsagBorromean,
71  // One MLSAG for each input and a Bulletproof.
72  ///
73  /// This aligns with RCTTypeBulletproof.
74  MlsagBulletproofs,
75  /// One MLSAG for each input and a Bulletproof, yet using EncryptedAmount::Compact.
76  ///
77  /// This aligns with RCTTypeBulletproof2.
78  MlsagBulletproofsCompactAmount,
79  /// One CLSAG for each input and a Bulletproof.
80  ///
81  /// This aligns with RCTTypeCLSAG.
82  ClsagBulletproof,
83  /// One CLSAG for each input and a Bulletproof+.
84  ///
85  /// This aligns with RCTTypeBulletproofPlus.
86  ClsagBulletproofPlus,
87}
88
89impl From<RctType> for u8 {
90  fn from(rct_type: RctType) -> u8 {
91    match rct_type {
92      RctType::AggregateMlsagBorromean => 1,
93      RctType::MlsagBorromean => 2,
94      RctType::MlsagBulletproofs => 3,
95      RctType::MlsagBulletproofsCompactAmount => 4,
96      RctType::ClsagBulletproof => 5,
97      RctType::ClsagBulletproofPlus => 6,
98    }
99  }
100}
101
102impl TryFrom<u8> for RctType {
103  type Error = ();
104  fn try_from(byte: u8) -> Result<Self, ()> {
105    Ok(match byte {
106      1 => RctType::AggregateMlsagBorromean,
107      2 => RctType::MlsagBorromean,
108      3 => RctType::MlsagBulletproofs,
109      4 => RctType::MlsagBulletproofsCompactAmount,
110      5 => RctType::ClsagBulletproof,
111      6 => RctType::ClsagBulletproofPlus,
112      _ => Err(())?,
113    })
114  }
115}
116
117impl RctType {
118  /// True if this RctType uses compact encrypted amounts, false otherwise.
119  pub fn compact_encrypted_amounts(&self) -> bool {
120    match self {
121      RctType::AggregateMlsagBorromean | RctType::MlsagBorromean | RctType::MlsagBulletproofs => {
122        false
123      }
124      RctType::MlsagBulletproofsCompactAmount |
125      RctType::ClsagBulletproof |
126      RctType::ClsagBulletproofPlus => true,
127    }
128  }
129
130  /// True if this RctType uses a Bulletproof, false otherwise.
131  pub(crate) fn bulletproof(&self) -> bool {
132    match self {
133      RctType::MlsagBulletproofs |
134      RctType::MlsagBulletproofsCompactAmount |
135      RctType::ClsagBulletproof => true,
136      RctType::AggregateMlsagBorromean |
137      RctType::MlsagBorromean |
138      RctType::ClsagBulletproofPlus => false,
139    }
140  }
141
142  /// True if this RctType uses a Bulletproof+, false otherwise.
143  pub(crate) fn bulletproof_plus(&self) -> bool {
144    match self {
145      RctType::ClsagBulletproofPlus => true,
146      RctType::AggregateMlsagBorromean |
147      RctType::MlsagBorromean |
148      RctType::MlsagBulletproofs |
149      RctType::MlsagBulletproofsCompactAmount |
150      RctType::ClsagBulletproof => false,
151    }
152  }
153}
154
155/// The base of the RingCT data.
156///
157/// This excludes all proofs (which once initially verified do not need to be kept around) and
158/// solely keeps data which either impacts the effects of the transactions or is needed to scan it.
159///
160/// The one exception for this is `pseudo_outs`, which was originally present here yet moved to
161/// RctPrunable in a later hard fork (causing it to be present in both).
162#[derive(Clone, PartialEq, Eq, Debug)]
163pub struct RctBase {
164  /// The fee used by this transaction.
165  pub fee: u64,
166  /// The re-randomized amount commitments used within inputs.
167  ///
168  /// This field was deprecated and is empty for modern RctTypes.
169  pub pseudo_outs: Vec<CompressedEdwardsY>,
170  /// The encrypted amounts for the recipients to decrypt.
171  pub encrypted_amounts: Vec<EncryptedAmount>,
172  /// The output commitments.
173  pub commitments: Vec<CompressedEdwardsY>,
174}
175
176impl RctBase {
177  /// Write the RctBase.
178  pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
179    w.write_all(&[u8::from(rct_type)])?;
180
181    write_varint(&self.fee, w)?;
182    if rct_type == RctType::MlsagBorromean {
183      write_raw_vec(write_compressed_point, &self.pseudo_outs, w)?;
184    }
185    for encrypted_amount in &self.encrypted_amounts {
186      encrypted_amount.write(w)?;
187    }
188    write_raw_vec(write_compressed_point, &self.commitments, w)
189  }
190
191  /// Read a RctBase.
192  pub fn read<R: Read>(
193    inputs: usize,
194    outputs: usize,
195    r: &mut R,
196  ) -> io::Result<Option<(RctType, RctBase)>> {
197    let rct_type = read_byte(r)?;
198    if rct_type == 0 {
199      return Ok(None);
200    }
201    let rct_type =
202      RctType::try_from(rct_type).map_err(|()| io::Error::other("invalid RCT type"))?;
203
204    match rct_type {
205      RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => {}
206      RctType::MlsagBulletproofs |
207      RctType::MlsagBulletproofsCompactAmount |
208      RctType::ClsagBulletproof |
209      RctType::ClsagBulletproofPlus => {
210        if outputs == 0 {
211          // Because the Bulletproofs(+) layout must be canonical, there must be 1 Bulletproof if
212          // Bulletproofs are in use
213          // If there are Bulletproofs, there must be a matching amount of outputs, implicitly
214          // banning 0 outputs
215          // Since HF 12 (CLSAG being 13), a 2-output minimum has also been enforced
216          Err(io::Error::other("RCT with Bulletproofs(+) had 0 outputs"))?;
217        }
218      }
219    }
220
221    Ok(Some((
222      rct_type,
223      RctBase {
224        fee: read_varint(r)?,
225        // Only read pseudo_outs if they have yet to be moved to RctPrunable
226        // This would apply to AggregateMlsagBorromean and MlsagBorromean, except
227        // AggregateMlsagBorromean doesn't use pseudo_outs due to using the sum of the output
228        // commitments directly as the effective singular pseudo-out
229        pseudo_outs: if rct_type == RctType::MlsagBorromean {
230          read_raw_vec(read_compressed_point, inputs, r)?
231        } else {
232          vec![]
233        },
234        encrypted_amounts: (0 .. outputs)
235          .map(|_| EncryptedAmount::read(rct_type.compact_encrypted_amounts(), r))
236          .collect::<Result<_, _>>()?,
237        commitments: read_raw_vec(read_compressed_point, outputs, r)?,
238      },
239    )))
240  }
241}
242
243/// The prunable part of the RingCT data.
244#[derive(Clone, PartialEq, Eq, Debug)]
245pub enum RctPrunable {
246  /// An aggregate MLSAG with Borromean range proofs.
247  AggregateMlsagBorromean {
248    /// The aggregate MLSAG ring signature.
249    mlsag: Mlsag,
250    /// The Borromean range proofs for each output.
251    borromean: Vec<BorromeanRange>,
252  },
253  /// MLSAGs with Borromean range proofs.
254  MlsagBorromean {
255    /// The MLSAG ring signatures for each input.
256    mlsags: Vec<Mlsag>,
257    /// The Borromean range proofs for each output.
258    borromean: Vec<BorromeanRange>,
259  },
260  /// MLSAGs with Bulletproofs.
261  MlsagBulletproofs {
262    /// The MLSAG ring signatures for each input.
263    mlsags: Vec<Mlsag>,
264    /// The re-blinded commitments for the outputs being spent.
265    pseudo_outs: Vec<CompressedEdwardsY>,
266    /// The aggregate Bulletproof, proving the outputs are within range.
267    bulletproof: Bulletproof,
268  },
269  /// MLSAGs with Bulletproofs and compact encrypted amounts.
270  ///
271  /// This has an identical layout to MlsagBulletproofs and is interpreted the exact same way. It's
272  /// only differentiated to ensure discovery of the correct RctType.
273  MlsagBulletproofsCompactAmount {
274    /// The MLSAG ring signatures for each input.
275    mlsags: Vec<Mlsag>,
276    /// The re-blinded commitments for the outputs being spent.
277    pseudo_outs: Vec<CompressedEdwardsY>,
278    /// The aggregate Bulletproof, proving the outputs are within range.
279    bulletproof: Bulletproof,
280  },
281  /// CLSAGs with Bulletproofs(+).
282  Clsag {
283    /// The CLSAGs for each input.
284    clsags: Vec<Clsag>,
285    /// The re-blinded commitments for the outputs being spent.
286    pseudo_outs: Vec<CompressedEdwardsY>,
287    /// The aggregate Bulletproof(+), proving the outputs are within range.
288    bulletproof: Bulletproof,
289  },
290}
291
292impl RctPrunable {
293  /// Write the RctPrunable.
294  pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
295    match self {
296      RctPrunable::AggregateMlsagBorromean { borromean, mlsag } => {
297        write_raw_vec(BorromeanRange::write, borromean, w)?;
298        mlsag.write(w)
299      }
300      RctPrunable::MlsagBorromean { borromean, mlsags } => {
301        write_raw_vec(BorromeanRange::write, borromean, w)?;
302        write_raw_vec(Mlsag::write, mlsags, w)
303      }
304      RctPrunable::MlsagBulletproofs { bulletproof, mlsags, pseudo_outs } |
305      RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, mlsags, pseudo_outs } => {
306        if rct_type == RctType::MlsagBulletproofs {
307          w.write_all(&1u32.to_le_bytes())?;
308        } else {
309          w.write_all(&[1])?;
310        }
311        bulletproof.write(w)?;
312
313        write_raw_vec(Mlsag::write, mlsags, w)?;
314        write_raw_vec(write_compressed_point, pseudo_outs, w)
315      }
316      RctPrunable::Clsag { bulletproof, clsags, pseudo_outs } => {
317        w.write_all(&[1])?;
318        bulletproof.write(w)?;
319
320        write_raw_vec(Clsag::write, clsags, w)?;
321        write_raw_vec(write_compressed_point, pseudo_outs, w)
322      }
323    }
324  }
325
326  /// Serialize the RctPrunable to a `Vec<u8>`.
327  pub fn serialize(&self, rct_type: RctType) -> Vec<u8> {
328    let mut serialized = vec![];
329    self.write(&mut serialized, rct_type).unwrap();
330    serialized
331  }
332
333  /// Read a RctPrunable.
334  pub fn read<R: Read>(
335    rct_type: RctType,
336    ring_length: usize,
337    inputs: usize,
338    outputs: usize,
339    r: &mut R,
340  ) -> io::Result<RctPrunable> {
341    Ok(match rct_type {
342      RctType::AggregateMlsagBorromean => RctPrunable::AggregateMlsagBorromean {
343        borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
344        mlsag: Mlsag::read(ring_length, inputs + 1, r)?,
345      },
346      RctType::MlsagBorromean => RctPrunable::MlsagBorromean {
347        borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
348        mlsags: (0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?,
349      },
350      RctType::MlsagBulletproofs | RctType::MlsagBulletproofsCompactAmount => {
351        let bulletproof = {
352          if (if rct_type == RctType::MlsagBulletproofs {
353            u64::from(read_u32(r)?)
354          } else {
355            read_varint(r)?
356          }) != 1
357          {
358            Err(io::Error::other("n bulletproofs instead of one"))?;
359          }
360          Bulletproof::read(r)?
361        };
362        let mlsags =
363          (0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?;
364        let pseudo_outs = read_raw_vec(read_compressed_point, inputs, r)?;
365        if rct_type == RctType::MlsagBulletproofs {
366          RctPrunable::MlsagBulletproofs { bulletproof, mlsags, pseudo_outs }
367        } else {
368          debug_assert_eq!(rct_type, RctType::MlsagBulletproofsCompactAmount);
369          RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, mlsags, pseudo_outs }
370        }
371      }
372      RctType::ClsagBulletproof | RctType::ClsagBulletproofPlus => RctPrunable::Clsag {
373        bulletproof: {
374          if read_varint::<_, u64>(r)? != 1 {
375            Err(io::Error::other("n bulletproofs instead of one"))?;
376          }
377          (if rct_type == RctType::ClsagBulletproof {
378            Bulletproof::read
379          } else {
380            Bulletproof::read_plus
381          })(r)?
382        },
383        clsags: (0 .. inputs).map(|_| Clsag::read(ring_length, r)).collect::<Result<_, _>>()?,
384        pseudo_outs: read_raw_vec(read_compressed_point, inputs, r)?,
385      },
386    })
387  }
388
389  /// Write the RctPrunable as necessary for signing the signature.
390  pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
391    match self {
392      RctPrunable::AggregateMlsagBorromean { borromean, .. } |
393      RctPrunable::MlsagBorromean { borromean, .. } => {
394        borromean.iter().try_for_each(|rs| rs.write(w))
395      }
396      RctPrunable::MlsagBulletproofs { bulletproof, .. } |
397      RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, .. } |
398      RctPrunable::Clsag { bulletproof, .. } => bulletproof.signature_write(w),
399    }
400  }
401}
402
403/// The RingCT proofs.
404///
405/// This contains both the RctBase and RctPrunable structs.
406///
407/// The C++ codebase refers to this as rct_signatures.
408#[derive(Clone, PartialEq, Eq, Debug)]
409pub struct RctProofs {
410  /// The data necessary for handling this transaction.
411  pub base: RctBase,
412  /// The data necessary for verifying this transaction.
413  pub prunable: RctPrunable,
414}
415
416impl RctProofs {
417  /// RctType for a given RctProofs struct.
418  pub fn rct_type(&self) -> RctType {
419    match &self.prunable {
420      RctPrunable::AggregateMlsagBorromean { .. } => RctType::AggregateMlsagBorromean,
421      RctPrunable::MlsagBorromean { .. } => RctType::MlsagBorromean,
422      RctPrunable::MlsagBulletproofs { .. } => RctType::MlsagBulletproofs,
423      RctPrunable::MlsagBulletproofsCompactAmount { .. } => RctType::MlsagBulletproofsCompactAmount,
424      RctPrunable::Clsag { bulletproof, .. } => {
425        if matches!(bulletproof, Bulletproof::Original { .. }) {
426          RctType::ClsagBulletproof
427        } else {
428          RctType::ClsagBulletproofPlus
429        }
430      }
431    }
432  }
433
434  /// Write the RctProofs.
435  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
436    let rct_type = self.rct_type();
437    self.base.write(w, rct_type)?;
438    self.prunable.write(w, rct_type)
439  }
440
441  /// Serialize the RctProofs to a `Vec<u8>`.
442  pub fn serialize(&self) -> Vec<u8> {
443    let mut serialized = vec![];
444    self.write(&mut serialized).unwrap();
445    serialized
446  }
447
448  /// Read a RctProofs.
449  pub fn read<R: Read>(
450    ring_length: usize,
451    inputs: usize,
452    outputs: usize,
453    r: &mut R,
454  ) -> io::Result<Option<RctProofs>> {
455    let Some((rct_type, base)) = RctBase::read(inputs, outputs, r)? else { return Ok(None) };
456    Ok(Some(RctProofs {
457      base,
458      prunable: RctPrunable::read(rct_type, ring_length, inputs, outputs, r)?,
459    }))
460  }
461}
462
463/// A pruned set of RingCT proofs.
464#[derive(Clone, PartialEq, Eq, Debug)]
465pub struct PrunedRctProofs {
466  /// The type of RctProofs this used to be.
467  pub rct_type: RctType,
468  /// The data necessary for handling this transaction.
469  pub base: RctBase,
470}