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#[derive(Clone, PartialEq, Eq, Debug)]
19pub enum EncryptedAmount {
20 Original {
22 mask: [u8; 32],
24 amount: [u8; 32],
26 },
27 Compact {
29 amount: [u8; 8],
31 },
32}
33
34impl EncryptedAmount {
35 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 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#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
58pub enum RctType {
59 AggregateMlsagBorromean,
63 MlsagBorromean,
67 MlsagBulletproofs,
71 MlsagBulletproofsCompactAmount,
75 ClsagBulletproof,
79 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 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 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 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#[derive(Clone, PartialEq, Eq, Debug)]
159pub struct RctBase {
160 pub fee: u64,
162 pub pseudo_outs: Vec<CompressedPoint>,
166 pub encrypted_amounts: Vec<EncryptedAmount>,
168 pub commitments: Vec<CompressedPoint>,
170}
171
172impl RctBase {
173 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 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 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 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#[derive(Clone, PartialEq, Eq, Debug)]
241pub enum RctPrunable {
242 AggregateMlsagBorromean {
244 mlsag: Mlsag,
246 borromean: Vec<BorromeanRange>,
248 },
249 MlsagBorromean {
251 mlsags: Vec<Mlsag>,
253 borromean: Vec<BorromeanRange>,
255 },
256 MlsagBulletproofs {
258 mlsags: Vec<Mlsag>,
260 pseudo_outs: Vec<CompressedPoint>,
262 bulletproof: Bulletproof,
264 },
265 MlsagBulletproofsCompactAmount {
270 mlsags: Vec<Mlsag>,
272 pseudo_outs: Vec<CompressedPoint>,
274 bulletproof: Bulletproof,
276 },
277 Clsag {
279 clsags: Vec<Clsag>,
281 pseudo_outs: Vec<CompressedPoint>,
283 bulletproof: Bulletproof,
285 },
286}
287
288impl RctPrunable {
289 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 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 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 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#[derive(Clone, PartialEq, Eq, Debug)]
413pub struct RctProofs {
414 pub base: RctBase,
416 pub prunable: RctPrunable,
418}
419
420impl RctProofs {
421 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 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 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 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#[derive(Clone, PartialEq, Eq, Debug)]
469pub struct PrunedRctProofs {
470 pub rct_type: RctType,
472 pub base: RctBase,
474}