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)]
56use std_shims::{io, vec::Vec};
7#[cfg(feature = "std")]
8use std_shims::sync::LazyLock;
910use zeroize::{Zeroize, ZeroizeOnDrop};
1112use sha3::{Digest, Keccak256};
13use curve25519_dalek::{
14 constants::ED25519_BASEPOINT_POINT,
15 traits::VartimePrecomputedMultiscalarMul,
16 scalar::Scalar,
17 edwards::{EdwardsPoint, VartimeEdwardsPrecomputation},
18};
1920use monero_io::*;
21use monero_generators::H;
2223mod unreduced_scalar;
24pub use unreduced_scalar::UnreducedScalar;
2526#[cfg(test)]
27mod tests;
2829// On std, we cache some variables in statics.
30#[cfg(feature = "std")]
31static INV_EIGHT_CELL: LazyLock<Scalar> = LazyLock::new(|| Scalar::from(8u8).invert());
32/// The inverse of 8 over l, the prime factor of the order of Ed25519.
33#[cfg(feature = "std")]
34#[allow(non_snake_case)]
35pub fn INV_EIGHT() -> Scalar {
36*INV_EIGHT_CELL
37}
38// In no-std environments, we prefer the reduced memory use and calculate it ad-hoc.
39/// The inverse of 8 over l, the prime factor of the order of Ed25519.
40#[cfg(not(feature = "std"))]
41#[allow(non_snake_case)]
42pub fn INV_EIGHT() -> Scalar {
43 Scalar::from(8u8).invert()
44}
4546#[cfg(feature = "std")]
47static G_PRECOMP_CELL: LazyLock<VartimeEdwardsPrecomputation> =
48 LazyLock::new(|| VartimeEdwardsPrecomputation::new([ED25519_BASEPOINT_POINT]));
49/// A cached (if std) pre-computation of the Ed25519 generator, G.
50#[cfg(feature = "std")]
51#[allow(non_snake_case)]
52pub fn G_PRECOMP() -> &'static VartimeEdwardsPrecomputation {
53&G_PRECOMP_CELL
54}
55/// A cached (if std) pre-computation of the Ed25519 generator, G.
56#[cfg(not(feature = "std"))]
57#[allow(non_snake_case)]
58pub fn G_PRECOMP() -> VartimeEdwardsPrecomputation {
59 VartimeEdwardsPrecomputation::new([ED25519_BASEPOINT_POINT])
60}
6162/// The Keccak-256 hash function.
63pub fn keccak256(data: impl AsRef<[u8]>) -> [u8; 32] {
64 Keccak256::digest(data.as_ref()).into()
65}
6667/// Hash the provided data to a scalar via keccak256(data) % l.
68///
69/// This function panics if it finds the Keccak-256 preimage for [0; 32].
70pub fn keccak256_to_scalar(data: impl AsRef<[u8]>) -> Scalar {
71let scalar = Scalar::from_bytes_mod_order(keccak256(data.as_ref()));
72// Monero will explicitly error in this case
73 // This library acknowledges its practical impossibility of it occurring, and doesn't bother to
74 // code in logic to handle it. That said, if it ever occurs, something must happen in order to
75 // not generate/verify a proof we believe to be valid when it isn't
76assert!(scalar != Scalar::ZERO, "ZERO HASH: {:?}", data.as_ref());
77 scalar
78}
7980/// Transparent structure representing a Pedersen commitment's contents.
81#[allow(non_snake_case)]
82#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
83pub struct Commitment {
84/// The mask for this commitment.
85pub mask: Scalar,
86/// The amount committed to by this commitment.
87pub amount: u64,
88}
8990impl core::fmt::Debug for Commitment {
91fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
92 fmt.debug_struct("Commitment").field("amount", &self.amount).finish_non_exhaustive()
93 }
94}
9596impl Commitment {
97/// A commitment to zero, defined with a mask of 1 (as to not be the identity).
98pub fn zero() -> Commitment {
99 Commitment { mask: Scalar::ONE, amount: 0 }
100 }
101102/// Create a new Commitment.
103pub fn new(mask: Scalar, amount: u64) -> Commitment {
104 Commitment { mask, amount }
105 }
106107/// Calculate the Pedersen commitment, as a point, from this transparent structure.
108pub fn calculate(&self) -> EdwardsPoint {
109 EdwardsPoint::vartime_double_scalar_mul_basepoint(&Scalar::from(self.amount), &H, &self.mask)
110 }
111112/// Write the Commitment.
113 ///
114 /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
115 /// defined serialization.
116pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
117 w.write_all(&self.mask.to_bytes())?;
118 w.write_all(&self.amount.to_le_bytes())
119 }
120121/// Serialize the Commitment to a `Vec<u8>`.
122 ///
123 /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
124 /// defined serialization.
125pub fn serialize(&self) -> Vec<u8> {
126let mut res = Vec::with_capacity(32 + 8);
127self.write(&mut res).unwrap();
128 res
129 }
130131/// Read a Commitment.
132 ///
133 /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
134 /// defined serialization.
135pub fn read<R: io::Read>(r: &mut R) -> io::Result<Commitment> {
136Ok(Commitment::new(read_scalar(r)?, read_u64(r)?))
137 }
138}
139140/// Decoy data, as used for producing Monero's ring signatures.
141#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
142pub struct Decoys {
143 offsets: Vec<u64>,
144 signer_index: u8,
145 ring: Vec<[EdwardsPoint; 2]>,
146}
147148impl core::fmt::Debug for Decoys {
149fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
150 fmt
151 .debug_struct("Decoys")
152 .field("offsets", &self.offsets)
153 .field("ring", &self.ring)
154 .finish_non_exhaustive()
155 }
156}
157158#[allow(clippy::len_without_is_empty)]
159impl Decoys {
160/// Create a new instance of decoy data.
161 ///
162 /// `offsets` are the positions of each ring member within the Monero blockchain, offset from the
163 /// prior member's position (with the initial ring member offset from 0).
164pub fn new(offsets: Vec<u64>, signer_index: u8, ring: Vec<[EdwardsPoint; 2]>) -> Option<Self> {
165if (offsets.len() != ring.len()) || (usize::from(signer_index) >= ring.len()) {
166None?;
167 }
168Some(Decoys { offsets, signer_index, ring })
169 }
170171/// The length of the ring.
172pub fn len(&self) -> usize {
173self.offsets.len()
174 }
175176/// The positions of the ring members within the Monero blockchain, as their offsets.
177 ///
178 /// The list is formatted as the position of the first ring member, then the offset from each
179 /// ring member to its prior.
180pub fn offsets(&self) -> &[u64] {
181&self.offsets
182 }
183184/// The positions of the ring members within the Monero blockchain.
185pub fn positions(&self) -> Vec<u64> {
186let mut res = Vec::with_capacity(self.len());
187 res.push(self.offsets[0]);
188for m in 1 .. self.len() {
189 res.push(res[m - 1] + self.offsets[m]);
190 }
191 res
192 }
193194/// The index of the signer within the ring.
195pub fn signer_index(&self) -> u8 {
196self.signer_index
197 }
198199/// The ring.
200pub fn ring(&self) -> &[[EdwardsPoint; 2]] {
201&self.ring
202 }
203204/// The [key, commitment] pair of the signer.
205pub fn signer_ring_members(&self) -> [EdwardsPoint; 2] {
206self.ring[usize::from(self.signer_index)]
207 }
208209/// Write the Decoys.
210 ///
211 /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
212 /// defined serialization.
213pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
214 write_vec(write_varint, &self.offsets, w)?;
215 w.write_all(&[self.signer_index])?;
216 write_vec(
217 |pair, w| {
218 write_point(&pair[0], w)?;
219 write_point(&pair[1], w)
220 },
221&self.ring,
222 w,
223 )
224 }
225226/// Serialize the Decoys to a `Vec<u8>`.
227 ///
228 /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
229 /// defined serialization.
230pub fn serialize(&self) -> Vec<u8> {
231let mut res =
232 Vec::with_capacity((1 + (2 * self.offsets.len())) + 1 + 1 + (self.ring.len() * 64));
233self.write(&mut res).unwrap();
234 res
235 }
236237/// Read a set of Decoys.
238 ///
239 /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
240 /// defined serialization.
241pub fn read(r: &mut impl io::Read) -> io::Result<Decoys> {
242 Decoys::new(
243 read_vec(read_varint, r)?,
244 read_byte(r)?,
245 read_vec(|r| Ok([read_point(r)?, read_point(r)?]), r)?,
246 )
247 .ok_or_else(|| io::Error::other("invalid Decoys"))
248 }
249}