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#[derive(Clone, Copy, PartialEq, Eq, Debug, thiserror::Error)]
23pub enum MlsagError {
24 #[error("invalid ring")]
26 InvalidRing,
27 #[error("invalid amount of key images")]
29 InvalidAmountOfKeyImages,
30 #[error("invalid ss")]
32 InvalidSs,
33 #[error("invalid key image")]
35 InvalidKeyImage,
36 #[error("invalid ci")]
38 InvalidCi,
39}
40
41#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
43pub struct RingMatrix {
44 matrix: Vec<Vec<EdwardsPoint>>,
45}
46
47impl RingMatrix {
48 fn new(matrix: Vec<Vec<EdwardsPoint>>) -> Result<Self, MlsagError> {
50 if matrix.len() < 2 {
54 Err(MlsagError::InvalidRing)?;
55 }
56 for member in &matrix {
57 if member.is_empty() || (member.len() != matrix[0].len()) {
58 Err(MlsagError::InvalidRing)?;
59 }
60 }
61
62 Ok(RingMatrix { matrix })
63 }
64
65 pub fn individual(
67 ring: &[[EdwardsPoint; 2]],
68 pseudo_out: EdwardsPoint,
69 ) -> Result<Self, MlsagError> {
70 let mut matrix = Vec::with_capacity(ring.len());
71 for ring_member in ring {
72 matrix.push(vec![ring_member[0], ring_member[1] - pseudo_out]);
73 }
74 RingMatrix::new(matrix)
75 }
76
77 fn iter(&self) -> impl Iterator<Item = &[EdwardsPoint]> {
79 self.matrix.iter().map(AsRef::as_ref)
80 }
81
82 pub fn members(&self) -> usize {
84 self.matrix.len()
85 }
86
87 pub fn member_len(&self) -> usize {
92 self.matrix[0].len()
94 }
95}
96
97#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
99pub struct Mlsag {
100 ss: Vec<Vec<Scalar>>,
101 cc: Scalar,
102}
103
104impl Mlsag {
105 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
107 for ss in &self.ss {
108 write_raw_vec(write_scalar, ss, w)?;
109 }
110 write_scalar(&self.cc, w)
111 }
112
113 pub fn read<R: Read>(mixins: usize, ss_2_elements: usize, r: &mut R) -> io::Result<Mlsag> {
115 Ok(Mlsag {
116 ss: (0 .. mixins)
117 .map(|_| read_raw_vec(read_scalar, ss_2_elements, r))
118 .collect::<Result<_, _>>()?,
119 cc: read_scalar(r)?,
120 })
121 }
122
123 pub fn verify(
129 &self,
130 msg: &[u8; 32],
131 ring: &RingMatrix,
132 key_images: &[EdwardsPoint],
133 ) -> Result<(), MlsagError> {
134 if ring.member_len() != (key_images.len() + 1) {
137 Err(MlsagError::InvalidAmountOfKeyImages)?;
138 }
139
140 let mut buf = Vec::with_capacity(6 * 32);
141 buf.extend_from_slice(msg);
142
143 let mut ci = self.cc;
144
145 let key_images_iter = key_images.iter().map(|ki| Some(*ki)).chain(core::iter::once(None));
148
149 if ring.matrix.len() != self.ss.len() {
150 Err(MlsagError::InvalidSs)?;
151 }
152
153 for (ring_member, ss) in ring.iter().zip(&self.ss) {
154 if ring_member.len() != ss.len() {
155 Err(MlsagError::InvalidSs)?;
156 }
157
158 for ((ring_member_entry, s), ki) in ring_member.iter().zip(ss).zip(key_images_iter.clone()) {
159 #[allow(non_snake_case)]
160 let L = EdwardsPoint::vartime_double_scalar_mul_basepoint(&ci, ring_member_entry, s);
161
162 let compressed_ring_member_entry = ring_member_entry.compress();
163 buf.extend_from_slice(compressed_ring_member_entry.as_bytes());
164 buf.extend_from_slice(L.compress().as_bytes());
165
166 if let Some(ki) = ki {
169 if ki.is_identity() || (!ki.is_torsion_free()) {
170 Err(MlsagError::InvalidKeyImage)?;
171 }
172
173 #[allow(non_snake_case)]
174 let R = (s * hash_to_point(compressed_ring_member_entry.to_bytes())) + (ci * ki);
175 buf.extend_from_slice(R.compress().as_bytes());
176 }
177 }
178
179 ci = keccak256_to_scalar(&buf);
180 buf.drain(msg.len() ..);
182 }
183
184 if ci != self.cc {
185 Err(MlsagError::InvalidCi)?
186 }
187 Ok(())
188 }
189}
190
191#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
195pub struct AggregateRingMatrixBuilder {
196 key_ring: Vec<Vec<EdwardsPoint>>,
197 amounts_ring: Vec<EdwardsPoint>,
198 sum_out: EdwardsPoint,
199}
200
201impl AggregateRingMatrixBuilder {
202 pub fn new(commitments: &[EdwardsPoint], fee: u64) -> Self {
206 AggregateRingMatrixBuilder {
207 key_ring: vec![],
208 amounts_ring: vec![],
209 sum_out: commitments.iter().sum::<EdwardsPoint>() + (*H * Scalar::from(fee)),
210 }
211 }
212
213 pub fn push_ring(&mut self, ring: &[[EdwardsPoint; 2]]) -> Result<(), MlsagError> {
215 if self.key_ring.is_empty() {
216 self.key_ring = vec![vec![]; ring.len()];
217 self.amounts_ring = vec![-self.sum_out; ring.len()];
219 }
220
221 if (self.amounts_ring.len() != ring.len()) || ring.is_empty() {
222 return Err(MlsagError::InvalidRing);
224 }
225
226 for (i, ring_member) in ring.iter().enumerate() {
227 self.key_ring[i].push(ring_member[0]);
228 self.amounts_ring[i] += ring_member[1]
229 }
230
231 Ok(())
232 }
233
234 pub fn build(mut self) -> Result<RingMatrix, MlsagError> {
236 for (i, amount_commitment) in self.amounts_ring.drain(..).enumerate() {
237 self.key_ring[i].push(amount_commitment);
238 }
239 RingMatrix::new(self.key_ring)
240 }
241}