monero_borromean/
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#![allow(non_snake_case)]
6
7use core::fmt::Debug;
8use std_shims::io::{self, Read, Write};
9
10use zeroize::Zeroize;
11
12use curve25519_dalek::{traits::Identity, Scalar, EdwardsPoint};
13
14use monero_io::*;
15use monero_generators::H_pow_2;
16use monero_primitives::{keccak256_to_scalar, UnreducedScalar};
17
18// 64 Borromean ring signatures, as needed for a 64-bit range proof.
19//
20// s0 and s1 are stored as `UnreducedScalar`s due to Monero not requiring they were reduced.
21// `UnreducedScalar` preserves their original byte encoding and implements a custom reduction
22// algorithm which was in use.
23#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
24struct BorromeanSignatures {
25  s0: [UnreducedScalar; 64],
26  s1: [UnreducedScalar; 64],
27  ee: Scalar,
28}
29
30impl BorromeanSignatures {
31  // Read a set of BorromeanSignatures.
32  fn read<R: Read>(r: &mut R) -> io::Result<BorromeanSignatures> {
33    Ok(BorromeanSignatures {
34      s0: read_array(UnreducedScalar::read, r)?,
35      s1: read_array(UnreducedScalar::read, r)?,
36      ee: read_scalar(r)?,
37    })
38  }
39
40  // Write the set of BorromeanSignatures.
41  fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
42    for s0 in &self.s0 {
43      s0.write(w)?;
44    }
45    for s1 in &self.s1 {
46      s1.write(w)?;
47    }
48    write_scalar(&self.ee, w)
49  }
50
51  fn verify(&self, keys_a: &[EdwardsPoint], keys_b: &[EdwardsPoint]) -> bool {
52    let mut transcript = [0; 2048];
53
54    for i in 0 .. 64 {
55      #[allow(non_snake_case)]
56      let LL = EdwardsPoint::vartime_double_scalar_mul_basepoint(
57        &self.ee,
58        &keys_a[i],
59        &self.s0[i].ref10_slide_scalar_vartime(),
60      );
61      #[allow(non_snake_case)]
62      let LV = EdwardsPoint::vartime_double_scalar_mul_basepoint(
63        &keccak256_to_scalar(LL.compress().as_bytes()),
64        &keys_b[i],
65        &self.s1[i].ref10_slide_scalar_vartime(),
66      );
67      transcript[(i * 32) .. ((i + 1) * 32)].copy_from_slice(LV.compress().as_bytes());
68    }
69
70    keccak256_to_scalar(transcript) == self.ee
71  }
72}
73
74/// A range proof premised on Borromean ring signatures.
75#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
76pub struct BorromeanRange {
77  sigs: BorromeanSignatures,
78  bit_commitments: [EdwardsPoint; 64],
79}
80
81impl BorromeanRange {
82  /// Read a BorromeanRange proof.
83  pub fn read<R: Read>(r: &mut R) -> io::Result<BorromeanRange> {
84    Ok(BorromeanRange {
85      sigs: BorromeanSignatures::read(r)?,
86      bit_commitments: read_array(read_point, r)?,
87    })
88  }
89
90  /// Write the BorromeanRange proof.
91  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
92    self.sigs.write(w)?;
93    write_raw_vec(write_point, &self.bit_commitments, w)
94  }
95
96  /// Verify the commitment contains a 64-bit value.
97  #[must_use]
98  pub fn verify(&self, commitment: &EdwardsPoint) -> bool {
99    if &self.bit_commitments.iter().sum::<EdwardsPoint>() != commitment {
100      return false;
101    }
102
103    #[allow(non_snake_case)]
104    let H_pow_2 = H_pow_2();
105    let mut commitments_sub_one = [EdwardsPoint::identity(); 64];
106    for i in 0 .. 64 {
107      commitments_sub_one[i] = self.bit_commitments[i] - H_pow_2[i];
108    }
109
110    self.sigs.verify(&self.bit_commitments, &commitments_sub_one)
111  }
112}