monero_mlsag_mirror/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![deny(missing_docs)]
4#![cfg_attr(not(feature = "std"), no_std)]
5#![allow(non_snake_case)]
6
7use std_shims::{
8  vec,
9  vec::Vec,
10  io::{self, Read, Write},
11};
12
13use zeroize::Zeroize;
14
15use curve25519_dalek::{traits::IsIdentity, Scalar, EdwardsPoint};
16
17use monero_io::*;
18use monero_generators::{H, hash_to_point};
19use monero_primitives::keccak256_to_scalar;
20
21/// Errors when working with MLSAGs.
22#[derive(Clone, Copy, PartialEq, Eq, Debug)]
23#[cfg_attr(feature = "std", derive(thiserror::Error))]
24pub enum MlsagError {
25  /// Invalid ring (such as too small or too large).
26  #[cfg_attr(feature = "std", error("invalid ring"))]
27  InvalidRing,
28  /// Invalid amount of key images.
29  #[cfg_attr(feature = "std", error("invalid amount of key images"))]
30  InvalidAmountOfKeyImages,
31  /// Invalid ss matrix.
32  #[cfg_attr(feature = "std", error("invalid ss"))]
33  InvalidSs,
34  /// Invalid key image.
35  #[cfg_attr(feature = "std", error("invalid key image"))]
36  InvalidKeyImage,
37  /// Invalid ci vector.
38  #[cfg_attr(feature = "std", error("invalid ci"))]
39  InvalidCi,
40}
41
42/// A vector of rings, forming a matrix, to verify the MLSAG with.
43#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
44pub struct RingMatrix {
45  matrix: Vec<Vec<EdwardsPoint>>,
46}
47
48impl RingMatrix {
49  /// Construct a ring matrix from an already formatted series of points.
50  fn new(matrix: Vec<Vec<EdwardsPoint>>) -> Result<Self, MlsagError> {
51    // Monero requires that there is more than one ring member for MLSAG signatures:
52    // https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/
53    // src/ringct/rctSigs.cpp#L462
54    if matrix.len() < 2 {
55      Err(MlsagError::InvalidRing)?;
56    }
57    for member in &matrix {
58      if member.is_empty() || (member.len() != matrix[0].len()) {
59        Err(MlsagError::InvalidRing)?;
60      }
61    }
62
63    Ok(RingMatrix { matrix })
64  }
65
66  /// Construct a ring matrix for an individual output.
67  pub fn individual(
68    ring: &[[EdwardsPoint; 2]],
69    pseudo_out: EdwardsPoint,
70  ) -> Result<Self, MlsagError> {
71    let mut matrix = Vec::with_capacity(ring.len());
72    for ring_member in ring {
73      matrix.push(vec![ring_member[0], ring_member[1] - pseudo_out]);
74    }
75    RingMatrix::new(matrix)
76  }
77
78  /// Iterate over the members of the matrix.
79  fn iter(&self) -> impl Iterator<Item = &[EdwardsPoint]> {
80    self.matrix.iter().map(AsRef::as_ref)
81  }
82
83  /// Get the amount of members in the ring.
84  pub fn members(&self) -> usize {
85    self.matrix.len()
86  }
87
88  /// Get the length of a ring member.
89  ///
90  /// A ring member is a vector of points for which the signer knows all of the discrete logarithms
91  /// of.
92  pub fn member_len(&self) -> usize {
93    // this is safe to do as the constructors don't allow empty rings
94    self.matrix[0].len()
95  }
96}
97
98/// The MLSAG linkable ring signature, as used in Monero.
99#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
100pub struct Mlsag {
101  ss: Vec<Vec<Scalar>>,
102  cc: Scalar,
103}
104
105impl Mlsag {
106  /// Write a MLSAG.
107  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
108    for ss in &self.ss {
109      write_raw_vec(write_scalar, ss, w)?;
110    }
111    write_scalar(&self.cc, w)
112  }
113
114  /// Read a MLSAG.
115  pub fn read<R: Read>(mixins: usize, ss_2_elements: usize, r: &mut R) -> io::Result<Mlsag> {
116    Ok(Mlsag {
117      ss: (0 .. mixins)
118        .map(|_| read_raw_vec(read_scalar, ss_2_elements, r))
119        .collect::<Result<_, _>>()?,
120      cc: read_scalar(r)?,
121    })
122  }
123
124  /// Verify a MLSAG.
125  pub fn verify(
126    &self,
127    msg: &[u8; 32],
128    ring: &RingMatrix,
129    key_images: &[EdwardsPoint],
130  ) -> Result<(), MlsagError> {
131    // Mlsag allows for layers to not need linkability, hence they don't need key images
132    // Monero requires that there is always only 1 non-linkable layer - the amount commitments.
133    if ring.member_len() != (key_images.len() + 1) {
134      Err(MlsagError::InvalidAmountOfKeyImages)?;
135    }
136
137    let mut buf = Vec::with_capacity(6 * 32);
138    buf.extend_from_slice(msg);
139
140    let mut ci = self.cc;
141
142    // This is an iterator over the key images as options with an added entry of `None` at the
143    // end for the non-linkable layer
144    let key_images_iter = key_images.iter().map(|ki| Some(*ki)).chain(core::iter::once(None));
145
146    if ring.matrix.len() != self.ss.len() {
147      Err(MlsagError::InvalidSs)?;
148    }
149
150    for (ring_member, ss) in ring.iter().zip(&self.ss) {
151      if ring_member.len() != ss.len() {
152        Err(MlsagError::InvalidSs)?;
153      }
154
155      for ((ring_member_entry, s), ki) in ring_member.iter().zip(ss).zip(key_images_iter.clone()) {
156        #[allow(non_snake_case)]
157        let L = EdwardsPoint::vartime_double_scalar_mul_basepoint(&ci, ring_member_entry, s);
158
159        let compressed_ring_member_entry = ring_member_entry.compress();
160        buf.extend_from_slice(compressed_ring_member_entry.as_bytes());
161        buf.extend_from_slice(L.compress().as_bytes());
162
163        // Not all dimensions need to be linkable, e.g. commitments, and only linkable layers need
164        // to have key images.
165        if let Some(ki) = ki {
166          if ki.is_identity() || (!ki.is_torsion_free()) {
167            Err(MlsagError::InvalidKeyImage)?;
168          }
169
170          #[allow(non_snake_case)]
171          let R = (s * hash_to_point(compressed_ring_member_entry.to_bytes())) + (ci * ki);
172          buf.extend_from_slice(R.compress().as_bytes());
173        }
174      }
175
176      ci = keccak256_to_scalar(&buf);
177      // keep the msg in the buffer.
178      buf.drain(msg.len() ..);
179    }
180
181    if ci != self.cc {
182      Err(MlsagError::InvalidCi)?
183    }
184    Ok(())
185  }
186}
187
188/// Builder for a RingMatrix when using an aggregate signature.
189///
190/// This handles the formatting as necessary.
191#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
192pub struct AggregateRingMatrixBuilder {
193  key_ring: Vec<Vec<EdwardsPoint>>,
194  amounts_ring: Vec<EdwardsPoint>,
195  sum_out: EdwardsPoint,
196}
197
198impl AggregateRingMatrixBuilder {
199  /// Create a new AggregateRingMatrixBuilder.
200  ///
201  /// This takes in the transaction's outputs' commitments and fee used.
202  pub fn new(commitments: &[EdwardsPoint], fee: u64) -> Self {
203    AggregateRingMatrixBuilder {
204      key_ring: vec![],
205      amounts_ring: vec![],
206      sum_out: commitments.iter().sum::<EdwardsPoint>() + (*H * Scalar::from(fee)),
207    }
208  }
209
210  /// Push a ring of [output key, commitment] to the matrix.
211  pub fn push_ring(&mut self, ring: &[[EdwardsPoint; 2]]) -> Result<(), MlsagError> {
212    if self.key_ring.is_empty() {
213      self.key_ring = vec![vec![]; ring.len()];
214      // Now that we know the length of the ring, fill the `amounts_ring`.
215      self.amounts_ring = vec![-self.sum_out; ring.len()];
216    }
217
218    if (self.amounts_ring.len() != ring.len()) || ring.is_empty() {
219      // All the rings in an aggregate matrix must be the same length.
220      return Err(MlsagError::InvalidRing);
221    }
222
223    for (i, ring_member) in ring.iter().enumerate() {
224      self.key_ring[i].push(ring_member[0]);
225      self.amounts_ring[i] += ring_member[1]
226    }
227
228    Ok(())
229  }
230
231  /// Build and return the [`RingMatrix`].
232  pub fn build(mut self) -> Result<RingMatrix, MlsagError> {
233    for (i, amount_commitment) in self.amounts_ring.drain(..).enumerate() {
234      self.key_ring[i].push(amount_commitment);
235    }
236    RingMatrix::new(self.key_ring)
237  }
238}