monero_primitives/
lib.rs

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
6#[allow(unused_imports)]
7use std_shims::prelude::*;
8use std_shims::io;
9#[cfg(feature = "std")]
10use std_shims::sync::LazyLock;
11
12use zeroize::{Zeroize, ZeroizeOnDrop};
13
14use sha3::{Digest, Keccak256};
15use curve25519_dalek::{
16  constants::ED25519_BASEPOINT_POINT,
17  traits::{MultiscalarMul, VartimePrecomputedMultiscalarMul},
18  scalar::Scalar,
19  edwards::{EdwardsPoint, VartimeEdwardsPrecomputation},
20};
21
22use monero_io::*;
23use monero_generators::H;
24
25mod unreduced_scalar;
26pub use unreduced_scalar::UnreducedScalar;
27
28#[cfg(test)]
29mod tests;
30
31// On std, we cache some variables in statics.
32#[cfg(feature = "std")]
33static INV_EIGHT_CELL: LazyLock<Scalar> = LazyLock::new(|| Scalar::from(8u8).invert());
34/// The inverse of 8 over l, the prime factor of the order of Ed25519.
35#[cfg(feature = "std")]
36#[allow(non_snake_case)]
37pub fn INV_EIGHT() -> Scalar {
38  *INV_EIGHT_CELL
39}
40// In no-std environments, we prefer the reduced memory use and calculate it ad-hoc.
41/// The inverse of 8 over l, the prime factor of the order of Ed25519.
42#[cfg(not(feature = "std"))]
43#[allow(non_snake_case)]
44pub fn INV_EIGHT() -> Scalar {
45  Scalar::from(8u8).invert()
46}
47
48#[cfg(feature = "std")]
49static G_PRECOMP_CELL: LazyLock<VartimeEdwardsPrecomputation> =
50  LazyLock::new(|| VartimeEdwardsPrecomputation::new([ED25519_BASEPOINT_POINT]));
51/// A cached (if std) pre-computation of the Ed25519 generator, G.
52#[cfg(feature = "std")]
53#[allow(non_snake_case)]
54pub fn G_PRECOMP() -> &'static VartimeEdwardsPrecomputation {
55  &G_PRECOMP_CELL
56}
57/// A cached (if std) pre-computation of the Ed25519 generator, G.
58#[cfg(not(feature = "std"))]
59#[allow(non_snake_case)]
60pub fn G_PRECOMP() -> VartimeEdwardsPrecomputation {
61  VartimeEdwardsPrecomputation::new([ED25519_BASEPOINT_POINT])
62}
63
64/// The Keccak-256 hash function.
65pub fn keccak256(data: impl AsRef<[u8]>) -> [u8; 32] {
66  Keccak256::digest(data.as_ref()).into()
67}
68
69/// Hash the provided data to a scalar via keccak256(data) % l.
70///
71/// This function panics if it finds the Keccak-256 preimage for [0; 32].
72pub fn keccak256_to_scalar(data: impl AsRef<[u8]>) -> Scalar {
73  let scalar = Scalar::from_bytes_mod_order(keccak256(&data));
74  // Monero will explicitly error in this case
75  // This library acknowledges its practical impossibility of it occurring, and doesn't bother to
76  // code in logic to handle it. That said, if it ever occurs, something must happen in order to
77  // not generate/verify a proof we believe to be valid when it isn't
78  assert!(
79    scalar != Scalar::ZERO,
80    "keccak256(preimage) \\cong 0 \\mod l! Preimage: {:?}",
81    data.as_ref()
82  );
83  scalar
84}
85
86/// Transparent structure representing a Pedersen commitment's contents.
87#[allow(non_snake_case)]
88#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
89pub struct Commitment {
90  /// The mask for this commitment.
91  pub mask: Scalar,
92  /// The amount committed to by this commitment.
93  pub amount: u64,
94}
95
96impl core::fmt::Debug for Commitment {
97  fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
98    fmt.debug_struct("Commitment").field("amount", &self.amount).finish_non_exhaustive()
99  }
100}
101
102impl Commitment {
103  /// A commitment to zero, defined with a mask of 1 (as to not be the identity).
104  pub fn zero() -> Commitment {
105    Commitment { mask: Scalar::ONE, amount: 0 }
106  }
107
108  /// Create a new Commitment.
109  pub fn new(mask: Scalar, amount: u64) -> Commitment {
110    Commitment { mask, amount }
111  }
112
113  /// Calculate the Pedersen commitment, as a point, from this transparent structure.
114  pub fn calculate(&self) -> EdwardsPoint {
115    EdwardsPoint::multiscalar_mul([self.mask, self.amount.into()], [ED25519_BASEPOINT_POINT, *H])
116  }
117
118  /// Write the Commitment.
119  ///
120  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
121  /// defined serialization.
122  pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
123    w.write_all(&self.mask.to_bytes())?;
124    w.write_all(&self.amount.to_le_bytes())
125  }
126
127  /// Serialize the Commitment to a `Vec<u8>`.
128  ///
129  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
130  /// defined serialization.
131  pub fn serialize(&self) -> Vec<u8> {
132    let mut res = Vec::with_capacity(32 + 8);
133    self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
134    res
135  }
136
137  /// Read a Commitment.
138  ///
139  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
140  /// defined serialization.
141  pub fn read<R: io::Read>(r: &mut R) -> io::Result<Commitment> {
142    Ok(Commitment::new(read_scalar(r)?, read_u64(r)?))
143  }
144}
145
146/// Decoy data, as used for producing Monero's ring signatures.
147#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
148pub struct Decoys {
149  offsets: Vec<u64>,
150  signer_index: u8,
151  ring: Vec<[EdwardsPoint; 2]>,
152}
153
154impl core::fmt::Debug for Decoys {
155  fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
156    fmt
157      .debug_struct("Decoys")
158      .field("offsets", &self.offsets)
159      .field("ring", &self.ring)
160      .finish_non_exhaustive()
161  }
162}
163
164/*
165  The max ring size the monero-oxide libraries is programmed to support creating.
166
167  This exceeds the current Monero protocol's ring size of `16`, with the next hard fork planned to
168  remove rings entirely, making this without issue.
169*/
170const MAX_RING_SIZE: usize = u8::MAX as usize;
171
172#[allow(clippy::len_without_is_empty)]
173impl Decoys {
174  /// Create a new instance of decoy data.
175  ///
176  /// `offsets` are the positions of each ring member within the Monero blockchain, offset from the
177  /// prior member's position (with the initial ring member offset from 0).
178  pub fn new(offsets: Vec<u64>, signer_index: u8, ring: Vec<[EdwardsPoint; 2]>) -> Option<Self> {
179    if (offsets.len() > MAX_RING_SIZE) ||
180      (offsets.len() != ring.len()) ||
181      (usize::from(signer_index) >= ring.len())
182    {
183      None?;
184    }
185    // Check these offsets form representable positions
186    if offsets.iter().copied().try_fold(0, u64::checked_add).is_none() {
187      None?;
188    }
189    Some(Decoys { offsets, signer_index, ring })
190  }
191
192  /// The length of the ring.
193  pub fn len(&self) -> usize {
194    self.offsets.len()
195  }
196
197  /// The positions of the ring members within the Monero blockchain, as their offsets.
198  ///
199  /// The list is formatted as the position of the first ring member, then the offset from each
200  /// ring member to its prior.
201  pub fn offsets(&self) -> &[u64] {
202    &self.offsets
203  }
204
205  /// The positions of the ring members within the Monero blockchain.
206  pub fn positions(&self) -> Vec<u64> {
207    let mut res = Vec::with_capacity(self.len());
208    res.push(self.offsets[0]);
209    for m in 1 .. self.len() {
210      res.push(res[m - 1] + self.offsets[m]);
211    }
212    res
213  }
214
215  /// The index of the signer within the ring.
216  pub fn signer_index(&self) -> u8 {
217    self.signer_index
218  }
219
220  /// The ring.
221  pub fn ring(&self) -> &[[EdwardsPoint; 2]] {
222    &self.ring
223  }
224
225  /// The [key, commitment] pair of the signer.
226  pub fn signer_ring_members(&self) -> [EdwardsPoint; 2] {
227    self.ring[usize::from(self.signer_index)]
228  }
229
230  /// Write the Decoys.
231  ///
232  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
233  /// defined serialization.
234  pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
235    write_vec(VarInt::write, &self.offsets, w)?;
236    w.write_all(&[self.signer_index])?;
237    write_raw_vec(
238      |pair, w| {
239        write_point(&pair[0], w)?;
240        write_point(&pair[1], w)
241      },
242      &self.ring,
243      w,
244    )
245  }
246
247  /// Serialize the Decoys to a `Vec<u8>`.
248  ///
249  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
250  /// defined serialization.
251  pub fn serialize(&self) -> Vec<u8> {
252    let mut res =
253      Vec::with_capacity((1 + (2 * self.offsets.len())) + 1 + 1 + (self.ring.len() * 64));
254    self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
255    res
256  }
257
258  /// Read a set of Decoys.
259  ///
260  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
261  /// defined serialization.
262  pub fn read(r: &mut impl io::Read) -> io::Result<Decoys> {
263    let offsets = read_vec(VarInt::read, Some(MAX_RING_SIZE), r)?;
264    let len = offsets.len();
265    Decoys::new(
266      offsets,
267      read_byte(r)?,
268      read_raw_vec(|r| Ok([read_point(r)?, read_point(r)?]), len, r)?,
269    )
270    .ok_or_else(|| io::Error::other("invalid Decoys"))
271  }
272}