1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(not(feature = "std"), no_std)]
4#![allow(non_snake_case)]
5
6use core::fmt::Debug;
7use std_shims::{
8 sync::LazyLock,
9 io::{self, Read, Write},
10 vec::Vec,
11};
12
13use zeroize::Zeroize;
14
15use curve25519_dalek::{traits::Identity as _, EdwardsPoint, edwards::CompressedEdwardsY};
16
17use monero_io::*;
18use monero_ed25519::{UnreducedScalar, Scalar, Point, CompressedPoint};
19
20static H_POW_2_CELL: LazyLock<[EdwardsPoint; 64]> = LazyLock::new(|| {
21 #[allow(non_snake_case)]
22 let H = CompressedEdwardsY(monero_ed25519::CompressedPoint::H.to_bytes()).decompress().unwrap();
23 let mut res = [H; 64];
24 for i in 1 .. 64 {
25 res[i] = res[i - 1] + res[i - 1];
26 }
27 res
28});
29#[allow(non_snake_case)]
33fn H_pow_2() -> &'static [EdwardsPoint; 64] {
34 &H_POW_2_CELL
35}
36
37#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
43struct BorromeanSignatures {
44 s0: [UnreducedScalar; 64],
45 s1: [UnreducedScalar; 64],
46 ee: Scalar,
47}
48
49impl BorromeanSignatures {
50 fn read<R: Read>(r: &mut R) -> io::Result<BorromeanSignatures> {
52 Ok(BorromeanSignatures {
53 s0: read_array(UnreducedScalar::read, r)?,
54 s1: read_array(UnreducedScalar::read, r)?,
55 ee: Scalar::read(r)?,
56 })
57 }
58
59 fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
61 for s0 in &self.s0 {
62 s0.write(w)?;
63 }
64 for s1 in &self.s1 {
65 s1.write(w)?;
66 }
67 self.ee.write(w)
68 }
69
70 fn verify(&self, keys_a: &[EdwardsPoint], keys_b: &[EdwardsPoint]) -> bool {
71 let mut transcript = [0; 2048];
72
73 for i in 0 .. 64 {
74 #[allow(non_snake_case)]
75 let LL = EdwardsPoint::vartime_double_scalar_mul_basepoint(
76 &self.ee.into(),
77 &keys_a[i],
78 &self.s0[i].ref10_slide_scalar_vartime().into(),
79 );
80 #[allow(non_snake_case)]
81 let LV = EdwardsPoint::vartime_double_scalar_mul_basepoint(
82 &Scalar::hash(LL.compress().to_bytes()).into(),
83 &keys_b[i],
84 &self.s1[i].ref10_slide_scalar_vartime().into(),
85 );
86 transcript[(i * 32) .. ((i + 1) * 32)].copy_from_slice(&LV.compress().to_bytes());
87 }
88
89 Scalar::hash(transcript) == self.ee
90 }
91}
92
93#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
95pub struct BorromeanRange {
96 sigs: BorromeanSignatures,
97 bit_commitments: [CompressedPoint; 64],
98}
99
100impl BorromeanRange {
101 pub fn read<R: Read>(r: &mut R) -> io::Result<BorromeanRange> {
103 Ok(BorromeanRange {
104 sigs: BorromeanSignatures::read(r)?,
105 bit_commitments: read_array(CompressedPoint::read, r)?,
106 })
107 }
108
109 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
111 self.sigs.write(w)?;
112 write_raw_vec(CompressedPoint::write, &self.bit_commitments, w)
113 }
114
115 #[must_use]
117 pub fn verify(&self, commitment: &CompressedPoint) -> bool {
118 let Some(bit_commitments) = self
119 .bit_commitments
120 .iter()
121 .map(|compressed| compressed.decompress().map(Point::into))
122 .collect::<Option<Vec<_>>>()
123 else {
124 return false;
125 };
126
127 if bit_commitments.iter().sum::<EdwardsPoint>().compress().0 != commitment.to_bytes() {
128 return false;
129 }
130
131 #[allow(non_snake_case)]
132 let H_pow_2 = H_pow_2();
133 let mut commitments_sub_one = [EdwardsPoint::identity(); 64];
134 for i in 0 .. 64 {
135 commitments_sub_one[i] = bit_commitments[i] - H_pow_2[i];
136 }
137
138 self.sigs.verify(&bit_commitments, &commitments_sub_one)
139 }
140}