monero_clsag/
decoys.rs

1#[allow(unused_imports)]
2use std_shims::prelude::*;
3use std_shims::io;
4
5#[rustfmt::skip]
6use subtle::{Choice, ConstantTimeEq as _, ConstantTimeLess as _, ConstantTimeGreater as _, ConditionallySelectable as _};
7use zeroize::{Zeroize, ZeroizeOnDrop};
8
9use monero_io::*;
10use monero_ed25519::*;
11
12/// Decoy data, as used for producing a CLSAG.
13#[derive(Clone, Zeroize, ZeroizeOnDrop)]
14pub struct Decoys {
15  offsets: Vec<u64>,
16  signer_index: u8,
17  ring: Vec<[Point; 2]>,
18}
19
20impl core::fmt::Debug for Decoys {
21  /// This implementation of `fmt` reveals the ring but not the index of the signer.
22  fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
23    fmt
24      .debug_struct("Decoys")
25      .field("offsets", &self.offsets)
26      .field("ring", &self.ring)
27      .finish_non_exhaustive()
28  }
29}
30
31/*
32  The max ring size the `monero-oxide` libraries are programmed to support creating.
33
34  This exceeds the current Monero protocol's ring size of `16`, with the next hard fork planned to
35  remove rings entirely, making this without issue.
36*/
37pub(crate) const MAX_RING_SIZE: u8 = u8::MAX;
38
39#[allow(clippy::len_without_is_empty)]
40impl Decoys {
41  /// This equality runs in constant-time if the decoys are the same length.
42  ///
43  /// This is not a public function as it is not part of our API commitment.
44  #[doc(hidden)]
45  pub fn ct_eq(&self, other: &Self) -> Choice {
46    let ring = self.ring.len().ct_eq(&other.ring.len()) &
47      self.ring.iter().zip(&other.ring).fold(Choice::from(1u8), |accum, (lhs, rhs)| {
48        accum & lhs.as_slice().ct_eq(rhs.as_slice())
49      });
50    self.offsets.ct_eq(&other.offsets) & self.signer_index.ct_eq(&other.signer_index) & ring
51  }
52
53  /// Create a new instance of decoy data.
54  ///
55  /// `offsets` are the positions of each ring member within the Monero blockchain, offset from the
56  /// prior member's position (with the initial ring member offset from 0).
57  ///
58  /// This function runs in time variable to the length of the ring and the validity of the
59  /// arguments.
60  pub fn new(offsets: Vec<u64>, signer_index: u8, ring: Vec<[Point; 2]>) -> Option<Self> {
61    // We check the low eight bits are equal, then check the remaining bits are zero,
62    // due to the lack of `usize::ct_gt`
63    #[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
64    let ring_len_does_not_exceed_max =
65      (ring.len() >> 8).ct_eq(&0) & (!(ring.len() as u8).ct_gt(&MAX_RING_SIZE));
66    // This cast is safe `ring.len()` is checked to not exceed a `u8` constant
67    #[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
68    let signer_index_points_to_ring_member = signer_index.ct_lt(&(ring.len() as u8));
69    let offsets_align_with_ring = offsets.len().ct_eq(&ring.len());
70
71    // Check these offsets form representable positions
72    let mut offsets_representable = Choice::from(1u8);
73    {
74      let mut sum = 0u64;
75      for (i, offset) in offsets.iter().enumerate() {
76        let new_sum = sum.wrapping_add(*offset);
77        if i != 0 {
78          // This simultaneously checks we didn't underflow and that this offset was non-zero
79          offsets_representable &= new_sum.ct_gt(&sum);
80        }
81        sum = new_sum;
82      }
83    }
84
85    bool::from(
86      ring_len_does_not_exceed_max &
87        signer_index_points_to_ring_member &
88        offsets_align_with_ring &
89        offsets_representable,
90    )
91    .then_some(Decoys { offsets, signer_index, ring })
92  }
93
94  /// The length of the ring.
95  pub fn len(&self) -> usize {
96    self.offsets.len()
97  }
98
99  /// The positions of the ring members within the Monero blockchain, as their offsets.
100  ///
101  /// The list is formatted as the position of the first ring member, then the offset from each
102  /// ring member to its prior.
103  pub fn offsets(&self) -> &[u64] {
104    &self.offsets
105  }
106
107  /// The positions of the ring members within the Monero blockchain.
108  ///
109  /// This function is runs in time variable to the length of the ring.
110  pub fn positions(&self) -> Vec<u64> {
111    let mut res = Vec::with_capacity(self.len());
112    res.push(self.offsets[0]);
113    for m in 1 .. self.len() {
114      res.push(res[m - 1] + self.offsets[m]);
115    }
116    res
117  }
118
119  /// The index of the signer within the ring.
120  pub fn signer_index(&self) -> u8 {
121    self.signer_index
122  }
123
124  /// The ring.
125  pub fn ring(&self) -> &[[Point; 2]] {
126    &self.ring
127  }
128
129  /// The [key, commitment] pair of the signer.
130  ///
131  /// This function is runs in time variable to the length of the ring.
132  pub fn signer_ring_members(&self) -> [Point; 2] {
133    let mut result = self.ring[0];
134    for (i, member) in self.ring.iter().enumerate().skip(1) {
135      let select = i.ct_eq(&usize::from(self.signer_index));
136      result[0] = <_>::conditional_select(&result[0], &member[0], select);
137      result[1] = <_>::conditional_select(&result[1], &member[1], select);
138    }
139    result
140  }
141
142  /// Write the Decoys.
143  ///
144  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
145  /// defined serialization. This may run in time variable to its value.
146  pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
147    write_vec(VarInt::write, &self.offsets, w)?;
148    w.write_all(&[self.signer_index])?;
149    write_raw_vec(
150      |pair, w| {
151        pair[0].compress().write(w)?;
152        pair[1].compress().write(w)
153      },
154      &self.ring,
155      w,
156    )
157  }
158
159  /// Serialize the Decoys to a `Vec<u8>`.
160  ///
161  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
162  /// defined serialization. This may run in time variable to its value.
163  pub fn serialize(&self) -> Vec<u8> {
164    let mut res =
165      Vec::with_capacity((1 + (2 * self.offsets.len())) + 1 + 1 + (self.ring.len() * 64));
166    self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
167    res
168  }
169
170  /// Read a set of Decoys.
171  ///
172  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
173  /// defined serialization. This may run in time variable to its value.
174  pub fn read(r: &mut impl io::Read) -> io::Result<Decoys> {
175    let offsets = read_vec(VarInt::read, Some(usize::from(MAX_RING_SIZE)), r)?;
176    let len = offsets.len();
177    Decoys::new(
178      offsets,
179      read_byte(r)?,
180      read_raw_vec(
181        |r| {
182          Ok([
183            CompressedPoint::read(r)?
184              .decompress()
185              .ok_or(io::Error::other("Decoys had invalid key in ring"))?,
186            CompressedPoint::read(r)?
187              .decompress()
188              .ok_or(io::Error::other("Decoys had invalid commitment in ring"))?,
189          ])
190        },
191        len,
192        r,
193      )?,
194    )
195    .ok_or_else(|| io::Error::other("invalid Decoys"))
196  }
197}