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 core::ops::Deref;
8use std_shims::{
9 vec,
10 vec::Vec,
11 io::{self, Read, Write},
12};
13
14use rand_core::{RngCore, CryptoRng};
15
16use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
17use subtle::{ConstantTimeEq, ConditionallySelectable};
18
19use curve25519_dalek::{
20 constants::{ED25519_BASEPOINT_TABLE, ED25519_BASEPOINT_POINT},
21 scalar::Scalar,
22 traits::{IsIdentity, MultiscalarMul, VartimePrecomputedMultiscalarMul},
23 edwards::{EdwardsPoint, VartimeEdwardsPrecomputation},
24};
25
26use monero_io::*;
27use monero_generators::hash_to_point;
28use monero_primitives::{INV_EIGHT, G_PRECOMP, Commitment, Decoys, keccak256_to_scalar};
29
30#[cfg(feature = "multisig")]
31mod multisig;
32#[cfg(feature = "multisig")]
33pub use multisig::{ClsagMultisigMaskSender, ClsagAddendum, ClsagMultisig};
34
35#[cfg(all(feature = "std", test))]
36mod tests;
37
38#[derive(Clone, Copy, PartialEq, Eq, Debug, thiserror::Error)]
40pub enum ClsagError {
41 #[error("invalid ring")]
43 InvalidRing,
44 #[error("invalid commitment")]
46 InvalidKey,
47 #[error("invalid commitment")]
49 InvalidCommitment,
50 #[error("invalid key image")]
52 InvalidImage,
53 #[error("invalid D")]
55 InvalidD,
56 #[error("invalid s")]
58 InvalidS,
59 #[error("invalid c1")]
61 InvalidC1,
62}
63
64#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
66pub struct ClsagContext {
67 commitment: Commitment,
69 decoys: Decoys,
71}
72
73impl ClsagContext {
74 pub fn new(decoys: Decoys, commitment: Commitment) -> Result<ClsagContext, ClsagError> {
76 if decoys.len() > u8::MAX.into() {
77 Err(ClsagError::InvalidRing)?;
78 }
79
80 if decoys.signer_ring_members()[1] != commitment.calculate() {
82 Err(ClsagError::InvalidCommitment)?;
83 }
84
85 Ok(ClsagContext { commitment, decoys })
86 }
87}
88
89#[allow(clippy::large_enum_variant)]
90enum Mode {
91 Sign { signer_index: u8, A: EdwardsPoint, AH: EdwardsPoint },
92 Verify { c1: Scalar, D_serialized: EdwardsPoint },
93}
94
95fn core(
99 ring: &[[EdwardsPoint; 2]],
100 I: &EdwardsPoint,
101 pseudo_out: &EdwardsPoint,
102 msg_hash: &[u8; 32],
103 D_torsion_free: &EdwardsPoint,
104 s: &[Scalar],
105 A_c1: &Mode,
106) -> ((EdwardsPoint, Scalar, Scalar), Scalar) {
107 let n = ring.len();
108
109 let images_precomp = match A_c1 {
110 Mode::Sign { .. } => None,
111 Mode::Verify { .. } => Some(VartimeEdwardsPrecomputation::new([I, D_torsion_free])),
112 };
113 let D_inv_eight = D_torsion_free * INV_EIGHT();
114
115 const PREFIX: &[u8] = b"CLSAG_";
118 #[rustfmt::skip]
119 const AGG_0: &[u8] = b"agg_0";
120 #[rustfmt::skip]
121 const ROUND: &[u8] = b"round";
122 const PREFIX_AGG_0_LEN: usize = PREFIX.len() + AGG_0.len();
123
124 let mut to_hash = Vec::with_capacity(((2 * n) + 5) * 32);
125 to_hash.extend(PREFIX);
126 to_hash.extend(AGG_0);
127 to_hash.extend([0; 32 - PREFIX_AGG_0_LEN]);
128
129 let mut P = Vec::with_capacity(n);
130 for member in ring {
131 P.push(member[0]);
132 to_hash.extend(member[0].compress().to_bytes());
133 }
134
135 let mut C = Vec::with_capacity(n);
136 for member in ring {
137 C.push(member[1] - pseudo_out);
138 to_hash.extend(member[1].compress().to_bytes());
139 }
140
141 to_hash.extend(I.compress().to_bytes());
142 match A_c1 {
143 Mode::Sign { .. } => {
144 to_hash.extend(D_inv_eight.compress().to_bytes());
145 }
146 Mode::Verify { D_serialized, .. } => {
147 to_hash.extend(D_serialized.compress().to_bytes());
148 }
149 }
150 to_hash.extend(pseudo_out.compress().to_bytes());
151 let mu_P = keccak256_to_scalar(&to_hash);
153 to_hash[PREFIX_AGG_0_LEN - 1] = b'1';
155 let mu_C = keccak256_to_scalar(&to_hash);
156
157 to_hash.truncate(((2 * n) + 1) * 32);
159 for i in 0 .. ROUND.len() {
160 to_hash[PREFIX.len() + i] = ROUND[i];
161 }
162 to_hash.extend(pseudo_out.compress().to_bytes());
165 to_hash.extend(msg_hash);
166
167 let start;
169 let end;
170 let mut c;
171 match A_c1 {
172 Mode::Sign { signer_index, A, AH } => {
173 let signer_index = usize::from(*signer_index);
174 start = signer_index + 1;
175 end = signer_index + n;
176 to_hash.extend(A.compress().to_bytes());
177 to_hash.extend(AH.compress().to_bytes());
178 c = keccak256_to_scalar(&to_hash);
179 }
180
181 Mode::Verify { c1, .. } => {
182 start = 0;
183 end = n;
184 c = *c1;
185 }
186 }
187
188 let mut c1 = c;
190 for i in (start .. end).map(|i| i % n) {
191 let c_p = mu_P * c;
192 let c_c = mu_C * c;
193
194 let L = match A_c1 {
196 Mode::Sign { .. } => {
197 EdwardsPoint::multiscalar_mul([s[i], c_p, c_c], [ED25519_BASEPOINT_POINT, P[i], C[i]])
198 }
199 Mode::Verify { .. } => {
200 G_PRECOMP().vartime_mixed_multiscalar_mul([s[i]], [c_p, c_c], [P[i], C[i]])
201 }
202 };
203
204 let PH = hash_to_point(P[i].compress().0);
205
206 let R = match A_c1 {
208 Mode::Sign { .. } => {
209 EdwardsPoint::multiscalar_mul([c_p, c_c, s[i]], [I, D_torsion_free, &PH])
210 }
211 Mode::Verify { .. } => images_precomp
212 .as_ref()
213 .expect("value populated when verifying wasn't populated")
214 .vartime_mixed_multiscalar_mul([c_p, c_c], [s[i]], [PH]),
215 };
216
217 to_hash.truncate(((2 * n) + 3) * 32);
218 to_hash.extend(L.compress().to_bytes());
219 to_hash.extend(R.compress().to_bytes());
220 c = keccak256_to_scalar(&to_hash);
221
222 c1.conditional_assign(&c, i.ct_eq(&(n - 1)));
226 }
227
228 ((D_inv_eight, c * mu_P, c * mu_C), c1)
230}
231
232#[derive(Clone, PartialEq, Eq, Debug)]
234pub struct Clsag {
235 pub D: EdwardsPoint,
237 pub s: Vec<Scalar>,
239 pub c1: Scalar,
241}
242
243struct ClsagSignCore {
244 incomplete_clsag: Clsag,
245 pseudo_out: EdwardsPoint,
246 key_challenge: Scalar,
247 challenged_mask: Scalar,
248}
249
250impl Clsag {
251 fn sign_core<R: RngCore + CryptoRng>(
254 rng: &mut R,
255 I: &EdwardsPoint,
256 input: &ClsagContext,
257 mask: Scalar,
258 msg_hash: &[u8; 32],
259 A: EdwardsPoint,
260 AH: EdwardsPoint,
261 ) -> ClsagSignCore {
262 let signer_index = input.decoys.signer_index();
263
264 let pseudo_out = Commitment::new(mask, input.commitment.amount).calculate();
265 let mask_delta = input.commitment.mask - mask;
266
267 let H = hash_to_point(input.decoys.ring()[usize::from(signer_index)][0].compress().0);
268 let D = H * mask_delta;
269 let mut s = Vec::with_capacity(input.decoys.ring().len());
270 for _ in 0 .. input.decoys.ring().len() {
271 s.push(Scalar::random(rng));
272 }
273 let ((D, c_p, c_c), c1) = core(
274 input.decoys.ring(),
275 I,
276 &pseudo_out,
277 msg_hash,
278 &D,
279 &s,
280 &Mode::Sign { signer_index, A, AH },
281 );
282
283 ClsagSignCore {
284 incomplete_clsag: Clsag { D, s, c1 },
285 pseudo_out,
286 key_challenge: c_p,
287 challenged_mask: c_c * mask_delta,
288 }
289 }
290
291 pub fn sign<R: RngCore + CryptoRng>(
313 rng: &mut R,
314 mut inputs: Vec<(Zeroizing<Scalar>, ClsagContext)>,
315 sum_outputs: Scalar,
316 msg_hash: [u8; 32],
317 ) -> Result<Vec<(Clsag, EdwardsPoint)>, ClsagError> {
318 let mut key_image_generators = vec![];
320 let mut key_images = vec![];
321 for input in &inputs {
322 let key = input.1.decoys.signer_ring_members()[0];
323
324 if (ED25519_BASEPOINT_TABLE * input.0.deref()) != key {
326 Err(ClsagError::InvalidKey)?;
327 }
328
329 let key_image_generator = hash_to_point(key.compress().0);
330 key_image_generators.push(key_image_generator);
331 key_images.push(key_image_generator * input.0.deref());
332 }
333
334 let mut res = Vec::with_capacity(inputs.len());
335 let mut sum_pseudo_outs = Scalar::ZERO;
336 for i in 0 .. inputs.len() {
337 let mask;
338 if i == (inputs.len() - 1) {
340 mask = sum_outputs - sum_pseudo_outs;
341 } else {
342 mask = Scalar::random(rng);
343 sum_pseudo_outs += mask;
344 }
345
346 let mut nonce = Zeroizing::new(Scalar::random(rng));
347 let ClsagSignCore { mut incomplete_clsag, pseudo_out, key_challenge, challenged_mask } =
348 Clsag::sign_core(
349 rng,
350 &key_images[i],
351 &inputs[i].1,
352 mask,
353 &msg_hash,
354 nonce.deref() * ED25519_BASEPOINT_TABLE,
355 nonce.deref() * key_image_generators[i],
356 );
357 incomplete_clsag.s[usize::from(inputs[i].1.decoys.signer_index())] =
361 nonce.deref() - ((key_challenge * inputs[i].0.deref()) + challenged_mask);
362 let clsag = incomplete_clsag;
363
364 inputs[i].0.zeroize();
366 nonce.zeroize();
367
368 debug_assert!(clsag
369 .verify(inputs[i].1.decoys.ring(), &key_images[i], &pseudo_out, &msg_hash)
370 .is_ok());
371
372 res.push((clsag, pseudo_out));
373 }
374
375 Ok(res)
376 }
377
378 pub fn verify(
384 &self,
385 ring: &[[EdwardsPoint; 2]],
386 I: &EdwardsPoint,
387 pseudo_out: &EdwardsPoint,
388 msg_hash: &[u8; 32],
389 ) -> Result<(), ClsagError> {
390 if ring.is_empty() {
393 Err(ClsagError::InvalidRing)?;
394 }
395 if ring.len() != self.s.len() {
396 Err(ClsagError::InvalidS)?;
397 }
398 if I.is_identity() || (!I.is_torsion_free()) {
399 Err(ClsagError::InvalidImage)?;
400 }
401
402 let D_torsion_free = self.D.mul_by_cofactor();
403 if D_torsion_free.is_identity() {
404 Err(ClsagError::InvalidD)?;
405 }
406
407 let (_, c1) = core(
408 ring,
409 I,
410 pseudo_out,
411 msg_hash,
412 &D_torsion_free,
413 &self.s,
414 &Mode::Verify { c1: self.c1, D_serialized: self.D },
415 );
416 if c1 != self.c1 {
417 Err(ClsagError::InvalidC1)?;
418 }
419 Ok(())
420 }
421
422 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
424 write_raw_vec(write_scalar, &self.s, w)?;
425 w.write_all(&self.c1.to_bytes())?;
426 write_point(&self.D, w)
427 }
428
429 pub fn read<R: Read>(decoys: usize, r: &mut R) -> io::Result<Clsag> {
431 Ok(Clsag { s: read_raw_vec(read_scalar, decoys, r)?, c1: read_scalar(r)?, D: read_point(r)? })
432 }
433}