monero_oxide/
ring_signatures.rs

1use std_shims::{
2  io::{self, *},
3  vec::Vec,
4};
5
6use zeroize::Zeroize;
7
8use curve25519_dalek::{EdwardsPoint, Scalar};
9
10use crate::{io::*, generators::biased_hash_to_point, primitives::keccak256_to_scalar};
11
12#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
13pub(crate) struct Signature {
14  #[cfg(test)]
15  pub(crate) c: Scalar,
16  #[cfg(test)]
17  pub(crate) s: Scalar,
18  #[cfg(not(test))]
19  c: Scalar,
20  #[cfg(not(test))]
21  s: Scalar,
22}
23
24impl Signature {
25  fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
26    write_scalar(&self.c, w)?;
27    write_scalar(&self.s, w)?;
28    Ok(())
29  }
30
31  fn read<R: Read>(r: &mut R) -> io::Result<Signature> {
32    Ok(Signature { c: read_scalar(r)?, s: read_scalar(r)? })
33  }
34}
35
36/// A ring signature.
37///
38/// This was used by the original Cryptonote transaction protocol and was deprecated with RingCT.
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  /// Write the RingSignature.
49  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  /// Read a RingSignature.
57  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  /// Verify the ring signature.
62  pub fn verify(
63    &self,
64    msg: &[u8; 32],
65    ring: &[CompressedPoint],
66    key_image: &CompressedPoint,
67  ) -> bool {
68    if ring.len() != self.sigs.len() {
69      return false;
70    }
71
72    let Some(key_image) = key_image.decompress() else {
73      return false;
74    };
75
76    if !key_image.is_torsion_free() {
77      return false;
78    }
79
80    let mut buf = Vec::with_capacity(32 + (2 * 32 * ring.len()));
81    buf.extend_from_slice(msg);
82
83    let mut sum = Scalar::ZERO;
84    for (ring_member, sig) in ring.iter().zip(&self.sigs) {
85      /*
86        The traditional Schnorr signature is:
87          r = sample()
88          c = H(r G || m)
89          s = r - c x
90        Verified as:
91          s G + c A == R
92
93        Each ring member here performs a dual-Schnorr signature for:
94          s G + c A
95          s HtP(A) + c K
96        Where the transcript is pushed both these values, r G, r HtP(A) for the real spend.
97        This also serves as a DLEq proof between the key and the key image.
98
99        Checking sum(c) == H(transcript) acts a disjunction, where any one of the `c`s can be
100        modified to cause the intended sum, if and only if a corresponding `s` value is known.
101      */
102
103      let Some(decomp_ring_member) = ring_member.decompress() else {
104        return false;
105      };
106
107      #[allow(non_snake_case)]
108      let Li =
109        EdwardsPoint::vartime_double_scalar_mul_basepoint(&sig.c, &decomp_ring_member, &sig.s);
110      buf.extend_from_slice(Li.compress().as_bytes());
111      #[allow(non_snake_case)]
112      let Ri = (sig.s * biased_hash_to_point(ring_member.to_bytes())) + (sig.c * key_image);
113      buf.extend_from_slice(Ri.compress().as_bytes());
114
115      sum += sig.c;
116    }
117    sum == keccak256_to_scalar(buf)
118  }
119}