#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] use core::fmt::Debug; use std_shims::io::{self, Read, Write}; use zeroize::Zeroize; use curve25519_dalek::{traits::Identity, Scalar, EdwardsPoint}; use monero_io::*; use monero_generators::H_pow_2; use monero_primitives::{keccak256_to_scalar, UnreducedScalar}; // 64 Borromean ring signatures, as needed for a 64-bit range proof. // // s0 and s1 are stored as `UnreducedScalar`s due to Monero not requiring they were reduced. // `UnreducedScalar` preserves their original byte encoding and implements a custom reduction // algorithm which was in use. #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] struct BorromeanSignatures { s0: [UnreducedScalar; 64], s1: [UnreducedScalar; 64], ee: Scalar, } impl BorromeanSignatures { // Read a set of BorromeanSignatures. fn read(r: &mut R) -> io::Result { Ok(BorromeanSignatures { s0: read_array(UnreducedScalar::read, r)?, s1: read_array(UnreducedScalar::read, r)?, ee: read_scalar(r)?, }) } // Write the set of BorromeanSignatures. fn write(&self, w: &mut W) -> io::Result<()> { for s0 in &self.s0 { s0.write(w)?; } for s1 in &self.s1 { s1.write(w)?; } write_scalar(&self.ee, w) } fn verify(&self, keys_a: &[EdwardsPoint], keys_b: &[EdwardsPoint]) -> bool { let mut transcript = [0; 2048]; for i in 0 .. 64 { #[allow(non_snake_case)] let LL = EdwardsPoint::vartime_double_scalar_mul_basepoint( &self.ee, &keys_a[i], &self.s0[i].recover_monero_slide_scalar(), ); #[allow(non_snake_case)] let LV = EdwardsPoint::vartime_double_scalar_mul_basepoint( &keccak256_to_scalar(LL.compress().as_bytes()), &keys_b[i], &self.s1[i].recover_monero_slide_scalar(), ); transcript[(i * 32) .. ((i + 1) * 32)].copy_from_slice(LV.compress().as_bytes()); } keccak256_to_scalar(transcript) == self.ee } } /// A range proof premised on Borromean ring signatures. #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] pub struct BorromeanRange { sigs: BorromeanSignatures, bit_commitments: [EdwardsPoint; 64], } impl BorromeanRange { /// Read a BorromeanRange proof. pub fn read(r: &mut R) -> io::Result { Ok(BorromeanRange { sigs: BorromeanSignatures::read(r)?, bit_commitments: read_array(read_point, r)?, }) } /// Write the BorromeanRange proof. pub fn write(&self, w: &mut W) -> io::Result<()> { self.sigs.write(w)?; write_raw_vec(write_point, &self.bit_commitments, w) } /// Verify the commitment contains a 64-bit value. #[must_use] pub fn verify(&self, commitment: &EdwardsPoint) -> bool { if &self.bit_commitments.iter().sum::() != commitment { return false; } #[allow(non_snake_case)] let H_pow_2 = H_pow_2(); let mut commitments_sub_one = [EdwardsPoint::identity(); 64]; for i in 0 .. 64 { commitments_sub_one[i] = self.bit_commitments[i] - H_pow_2[i]; } self.sigs.verify(&self.bit_commitments, &commitments_sub_one) } }