monero_generators/
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
6use std_shims::{sync::LazyLock, vec::Vec};
7
8use sha3::{Digest, Keccak256};
9
10use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::EdwardsPoint};
11
12use monero_io::{write_varint, CompressedPoint};
13
14mod hash_to_point;
15pub use hash_to_point::biased_hash_to_point;
16
17#[cfg(test)]
18mod tests;
19
20fn keccak256(data: &[u8]) -> [u8; 32] {
21  Keccak256::digest(data).into()
22}
23
24/// Monero's `H` generator.
25///
26/// Contrary to convention (`G` for values, `H` for randomness), `H` is used by Monero for amounts
27/// within Pedersen commitments.
28#[allow(non_snake_case)]
29pub static H: LazyLock<EdwardsPoint> = LazyLock::new(|| {
30  CompressedPoint::from(keccak256(&ED25519_BASEPOINT_POINT.compress().to_bytes()))
31    .decompress()
32    .expect("known on-curve point wasn't on-curve")
33    .mul_by_cofactor()
34});
35
36static H_POW_2_CELL: LazyLock<[EdwardsPoint; 64]> = LazyLock::new(|| {
37  let mut res = [*H; 64];
38  for i in 1 .. 64 {
39    res[i] = res[i - 1] + res[i - 1];
40  }
41  res
42});
43/// Monero's `H` generator, multiplied by 2**i for i in 1 ..= 64.
44///
45/// This table is useful when working with amounts, which are u64s.
46#[allow(non_snake_case)]
47pub fn H_pow_2() -> &'static [EdwardsPoint; 64] {
48  &H_POW_2_CELL
49}
50
51/// The maximum amount of commitments provable for within a single Bulletproof(+).
52pub const MAX_BULLETPROOF_COMMITMENTS: usize = 16;
53/// The amount of bits a value within a commitment may use.
54pub const COMMITMENT_BITS: usize = 64;
55
56/// Container struct for Bulletproofs(+) generators.
57#[allow(non_snake_case)]
58pub struct Generators {
59  /// The G (bold) vector of generators.
60  pub G: Vec<EdwardsPoint>,
61  /// The H (bold) vector of generators.
62  pub H: Vec<EdwardsPoint>,
63}
64
65/// Generate generators as needed for Bulletproofs(+), as Monero does.
66///
67/// Consumers should not call this function ad-hoc, yet call it within a build script or use a
68/// once-initialized static.
69pub fn bulletproofs_generators(dst: &'static [u8]) -> Generators {
70  // The maximum amount of bits used within a single range proof.
71  const MAX_MN: usize = MAX_BULLETPROOF_COMMITMENTS * COMMITMENT_BITS;
72
73  let mut preimage = H.compress().to_bytes().to_vec();
74  preimage.extend(dst);
75
76  let mut res = Generators { G: Vec::with_capacity(MAX_MN), H: Vec::with_capacity(MAX_MN) };
77  for i in 0 .. MAX_MN {
78    // We generate a pair of generators per iteration
79    let i = 2 * i;
80
81    let mut even = preimage.clone();
82    write_varint(&i, &mut even).expect("write failed but <Vec as io::Write> doesn't fail");
83    res.H.push(biased_hash_to_point(keccak256(&even)));
84
85    let mut odd = preimage.clone();
86    write_varint(&(i + 1), &mut odd).expect("write failed but <Vec as io::Write> doesn't fail");
87    res.G.push(biased_hash_to_point(keccak256(&odd)));
88  }
89  res
90}