monero_oxide/
ringct.rs

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