monero_oxide/
ring_signatures.rs1use std_shims::{
2 io::{self, *},
3 vec::Vec,
4};
5
6use zeroize::Zeroize;
7
8use crate::{io::*, ed25519::*};
9
10#[cfg_attr(not(test), expect(clippy::cfg_not_test))]
11#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
12pub(crate) struct Signature {
13 #[cfg(test)]
14 pub(crate) c: Scalar,
15 #[cfg(test)]
16 pub(crate) s: Scalar,
17 #[cfg(not(test))]
18 c: Scalar,
19 #[cfg(not(test))]
20 s: Scalar,
21}
22
23impl Signature {
24 fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
25 self.c.write(w)?;
26 self.s.write(w)?;
27 Ok(())
28 }
29
30 fn read<R: Read>(r: &mut R) -> io::Result<Signature> {
31 Ok(Signature { c: Scalar::read(r)?, s: Scalar::read(r)? })
32 }
33}
34
35#[cfg_attr(not(test), expect(clippy::cfg_not_test))]
39#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
40pub struct RingSignature {
41 #[cfg(test)]
42 pub(crate) sigs: Vec<Signature>,
43 #[cfg(not(test))]
44 sigs: Vec<Signature>,
45}
46
47impl RingSignature {
48 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
50 for sig in &self.sigs {
51 sig.write(w)?;
52 }
53 Ok(())
54 }
55
56 pub fn read<R: Read>(members: usize, r: &mut R) -> io::Result<RingSignature> {
58 Ok(RingSignature { sigs: read_raw_vec(Signature::read, members, r)? })
59 }
60
61 pub fn verify(
67 &self,
68 msg_hash: &[u8; 32],
69 ring: &[CompressedPoint],
70 key_image: &CompressedPoint,
71 ) -> bool {
72 if ring.len() != self.sigs.len() {
73 return false;
74 }
75
76 let Some(key_image) = key_image.decompress() else {
77 return false;
78 };
79 let Some(key_image) = key_image.key_image() else {
80 return false;
81 };
82
83 let mut buf = Vec::with_capacity(32 + (2 * 32 * ring.len()));
84 buf.extend_from_slice(msg_hash);
85
86 let mut sum = curve25519_dalek::Scalar::ZERO;
87 for (ring_member, sig) in ring.iter().zip(&self.sigs) {
88 let Some(decomp_ring_member) = ring_member.decompress() else {
107 return false;
108 };
109
110 #[expect(non_snake_case)]
111 let Li = curve25519_dalek::EdwardsPoint::vartime_double_scalar_mul_basepoint(
112 &sig.c.into(),
113 &decomp_ring_member.into(),
114 &sig.s.into(),
115 );
116 buf.extend_from_slice(Li.compress().as_bytes());
117 #[expect(non_snake_case)]
118 let Ri = (sig.s.into() * Point::biased_hash(ring_member.to_bytes()).into()) +
119 (sig.c.into() * key_image);
120 buf.extend_from_slice(Ri.compress().as_bytes());
121
122 sum += sig.c.into();
123 }
124 Scalar::from(sum) == Scalar::hash(buf)
125 }
126}