1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(not(feature = "std"), no_std)]
4#![allow(non_snake_case)]
5
6use std_shims::{
7 prelude::*,
8 sync::LazyLock,
9 io::{self, Read, Write},
10};
11
12use zeroize::Zeroize;
13
14use curve25519_dalek::EdwardsPoint;
15
16use monero_io::*;
17use monero_ed25519::{Scalar, Point, CompressedPoint};
18
19static H: LazyLock<EdwardsPoint> = LazyLock::new(|| {
21 CompressedPoint::H.decompress().expect("couldn't decompress `CompressedPoint::H`").into()
22});
23
24#[derive(Clone, Copy, PartialEq, Eq, Debug, thiserror::Error)]
26pub enum MlsagError {
27 #[error("invalid ring")]
29 InvalidRing,
30 #[error("invalid amount of key images")]
32 InvalidAmountOfKeyImages,
33 #[error("invalid ss")]
35 InvalidSs,
36 #[error("invalid key image")]
38 InvalidKeyImage,
39 #[error("invalid ci")]
41 InvalidCi,
42}
43
44#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
46pub struct RingMatrix {
47 matrix: Vec<Vec<EdwardsPoint>>,
48}
49
50impl RingMatrix {
51 fn new(matrix: Vec<Vec<EdwardsPoint>>) -> Result<Self, MlsagError> {
53 if matrix.len() < 2 {
57 Err(MlsagError::InvalidRing)?;
58 }
59 for member in &matrix {
60 if member.is_empty() || (member.len() != matrix[0].len()) {
61 Err(MlsagError::InvalidRing)?;
62 }
63 }
64
65 Ok(RingMatrix { matrix })
66 }
67
68 pub fn individual(
70 ring: &[[CompressedPoint; 2]],
71 pseudo_out: CompressedPoint,
72 ) -> Result<Self, MlsagError> {
73 let mut matrix = Vec::with_capacity(ring.len());
74 for ring_member in ring {
75 let decomp = |p: CompressedPoint| Ok(p.decompress().ok_or(MlsagError::InvalidRing)?.into());
76
77 matrix.push(vec![decomp(ring_member[0])?, decomp(ring_member[1])? - decomp(pseudo_out)?]);
78 }
79 RingMatrix::new(matrix)
80 }
81
82 fn iter(&self) -> impl Iterator<Item = &[EdwardsPoint]> {
84 self.matrix.iter().map(AsRef::as_ref)
85 }
86
87 pub fn members(&self) -> usize {
89 self.matrix.len()
90 }
91
92 pub fn member_len(&self) -> usize {
97 self.matrix[0].len()
99 }
100}
101
102#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
104pub struct Mlsag {
105 ss: Vec<Vec<Scalar>>,
106 cc: Scalar,
107}
108
109impl Mlsag {
110 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
112 for ss in &self.ss {
113 write_raw_vec(Scalar::write, ss, w)?;
114 }
115 self.cc.write(w)
116 }
117
118 pub fn read<R: Read>(decoys: usize, ss_2_elements: usize, r: &mut R) -> io::Result<Mlsag> {
120 Ok(Mlsag {
121 ss: (0 .. decoys)
122 .map(|_| read_raw_vec(Scalar::read, ss_2_elements, r))
123 .collect::<Result<_, _>>()?,
124 cc: Scalar::read(r)?,
125 })
126 }
127
128 pub fn verify(
134 &self,
135 msg: &[u8; 32],
136 ring: &RingMatrix,
137 key_images: &[CompressedPoint],
138 ) -> Result<(), MlsagError> {
139 if ring.member_len() != (key_images.len() + 1) {
142 Err(MlsagError::InvalidAmountOfKeyImages)?;
143 }
144
145 let mut buf = Vec::with_capacity(6 * 32);
146 buf.extend_from_slice(msg);
147
148 let mut ci = self.cc.into();
149
150 let key_images_iter = key_images.iter().map(Some).chain(core::iter::once(None));
153
154 if ring.matrix.len() != self.ss.len() {
155 Err(MlsagError::InvalidSs)?;
156 }
157
158 for (ring_member, ss) in ring.iter().zip(&self.ss) {
159 if ring_member.len() != ss.len() {
160 Err(MlsagError::InvalidSs)?;
161 }
162
163 for ((ring_member_entry, s), ki) in ring_member.iter().zip(ss).zip(key_images_iter.clone()) {
164 let s = (*s).into();
165 #[allow(non_snake_case)]
166 let L = EdwardsPoint::vartime_double_scalar_mul_basepoint(&ci, ring_member_entry, &s);
167
168 let compressed_ring_member_entry = ring_member_entry.compress();
169 buf.extend_from_slice(compressed_ring_member_entry.as_bytes());
170 buf.extend_from_slice(L.compress().as_bytes());
171
172 if let Some(ki) = ki {
175 let Some(ki) = ki.decompress() else {
176 return Err(MlsagError::InvalidKeyImage);
177 };
178 let ki = ki.key_image().ok_or(MlsagError::InvalidKeyImage)?;
179
180 #[allow(non_snake_case)]
182 let R =
183 (s * Point::biased_hash(compressed_ring_member_entry.to_bytes()).into()) + (ci * ki);
184 buf.extend_from_slice(R.compress().as_bytes());
185 }
186 }
187
188 ci = Scalar::hash(&buf).into();
189 buf.drain(msg.len() ..);
191 }
192
193 if ci != self.cc.into() {
194 Err(MlsagError::InvalidCi)?;
195 }
196 Ok(())
197 }
198}
199
200#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
204pub struct AggregateRingMatrixBuilder {
205 key_ring: Vec<Vec<EdwardsPoint>>,
206 amounts_ring: Vec<EdwardsPoint>,
207 sum_out: EdwardsPoint,
208}
209
210impl AggregateRingMatrixBuilder {
211 pub fn new(commitments: &[CompressedPoint], fee: u64) -> Result<Self, MlsagError> {
215 Ok(AggregateRingMatrixBuilder {
217 key_ring: vec![],
218 amounts_ring: vec![],
219 sum_out: commitments
220 .iter()
221 .map(|compressed| compressed.decompress().map(Point::into))
222 .sum::<Option<EdwardsPoint>>()
223 .ok_or(MlsagError::InvalidRing)? +
224 (*H * curve25519_dalek::Scalar::from(fee)),
225 })
226 }
227
228 pub fn push_ring(&mut self, ring: &[[CompressedPoint; 2]]) -> Result<(), MlsagError> {
230 if self.key_ring.is_empty() {
231 self.key_ring = vec![vec![]; ring.len()];
232 self.amounts_ring = vec![-self.sum_out; ring.len()];
234 }
235
236 if (self.amounts_ring.len() != ring.len()) || ring.is_empty() {
237 return Err(MlsagError::InvalidRing);
239 }
240
241 for (i, ring_member) in ring.iter().enumerate() {
242 self.key_ring[i].push(ring_member[0].decompress().ok_or(MlsagError::InvalidRing)?.into());
243 self.amounts_ring[i] += ring_member[1].decompress().ok_or(MlsagError::InvalidRing)?.into();
244 }
245
246 Ok(())
247 }
248
249 pub fn build(mut self) -> Result<RingMatrix, MlsagError> {
251 for (i, amount_commitment) in self.amounts_ring.drain(..).enumerate() {
252 self.key_ring[i].push(amount_commitment);
253 }
254 RingMatrix::new(self.key_ring)
255 }
256}