From ac30e3afaa9680bc084c1273f9e747905dae550e Mon Sep 17 00:00:00 2001 From: Boog900 <54e72d8a-345f-4599-bd90-c6b9bc7d0ec5@aleeas.com> Date: Fri, 23 Jun 2023 12:23:33 +0100 Subject: [PATCH] Add Borromean range proof verifying functionality --- coins/monero/generators/src/lib.rs | 15 +++++++ coins/monero/src/ringct/borromean.rs | 66 ++++++++++++++++++++++++---- coins/monero/src/serialize.rs | 8 ++++ coins/monero/src/transaction.rs | 19 ++++---- 4 files changed, 90 insertions(+), 18 deletions(-) diff --git a/coins/monero/generators/src/lib.rs b/coins/monero/generators/src/lib.rs index d3783e14..cba4666b 100644 --- a/coins/monero/generators/src/lib.rs +++ b/coins/monero/generators/src/lib.rs @@ -9,6 +9,7 @@ use lazy_static::lazy_static; use sha3::{Digest, Keccak256}; use curve25519_dalek::edwards::{EdwardsPoint as DalekPoint, CompressedEdwardsY}; +use curve25519_dalek::scalar::Scalar; use group::{Group, GroupEncoding}; use dalek_ff_group::EdwardsPoint; @@ -30,6 +31,20 @@ lazy_static! { .decompress() .unwrap() .mul_by_cofactor(); + + /// Monero's `H` generator multiplied 2^i for each index, i.e. H, 2H, 4H, 8H, ... + /// used in old range proofs. + /// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/src/ringct/rctTypes.h#L628 + pub static ref H2: [DalekPoint; 64] = generate_H2(); +} + +#[allow(non_snake_case)] +fn generate_H2() -> [DalekPoint; 64] { + let mut temp = Vec::with_capacity(64); + for i in 0..64 { + temp.push(Scalar::from(2_u128.pow(i)) * *H) + } + temp.try_into().unwrap() } const MAX_M: usize = 16; diff --git a/coins/monero/src/ringct/borromean.rs b/coins/monero/src/ringct/borromean.rs index ab81d212..084bbb91 100644 --- a/coins/monero/src/ringct/borromean.rs +++ b/coins/monero/src/ringct/borromean.rs @@ -3,18 +3,22 @@ use std::fmt::Debug; use std::io::{self, Read, Write}; -use curve25519_dalek::edwards::EdwardsPoint; +use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; use curve25519_dalek::scalar::Scalar; +use curve25519_dalek::traits::Identity; +use monero_generators::H2; + +use crate::hash_to_scalar; use crate::serialize::*; -fn read_64_array io::Result>( - f: F, - r: &mut R, -) -> io::Result<[T; 64]> { - (0 .. 64).map(|_| f(r)).collect::>>().map(|vec| vec.try_into().unwrap()) -} - +/// A Borromean signature. +/// +/// Note: This type keeps the data as raw bytes as Monero has +/// some transactions with unreduced scalars in this field, we +/// could use `from_bytes_mod_order` but then we would not be able +/// to encode this back into it's original form. +/// #[derive(Clone, PartialEq, Eq, Debug)] pub struct BorroSig { pub s0: [[u8; 32]; 64], @@ -55,4 +59,50 @@ impl RangeSig { self.asig.write(w)?; write_raw_vec(write_point, &self.Ci, w) } + + pub fn verify(&self, commitment: &EdwardsPoint) -> bool { + let mut P1 = Vec::with_capacity(64); + let mut P2 = Vec::with_capacity(64); + let mut bbs0 = Vec::with_capacity(64); + let mut bbs1 = Vec::with_capacity(64); + + let bbee = Scalar::from_bytes_mod_order(self.asig.ee); + + let mut C_temp = EdwardsPoint::identity(); + + for i in 0..64 { + bbs0.push(Scalar::from_bytes_mod_order(self.asig.s0[i])); + bbs1.push(Scalar::from_bytes_mod_order(self.asig.s1[i])); + + P1.push(self.Ci[i]); + P2.push(P1[i] - H2[i]); + + C_temp += P1[i]; + } + + if &C_temp != commitment { + false + } else { + verify_borromean(P1, P2, bbee, bbs0, bbs1) + } + + } +} + +fn verify_borromean(P1: Vec, P2: Vec, bbee: Scalar, bbs0: Vec, bbs1: Vec) -> bool { + let mut LV: Vec = Vec::with_capacity(2048); + for i in 0..64 { + let LL = EdwardsPoint::vartime_double_scalar_mul_basepoint(&bbee, &P1[i], &bbs0[i]); + let chash = hash_to_scalar(LL.compress().as_bytes()); + let LV_temp = EdwardsPoint::vartime_double_scalar_mul_basepoint(&chash, &P2[i], &bbs1[i]); + LV.extend(LV_temp.compress().as_bytes()); + } + let eecomp = hash_to_scalar(&LV); + + if !(eecomp == bbee) { + false + } + else { + true + } } diff --git a/coins/monero/src/serialize.rs b/coins/monero/src/serialize.rs index aec745bc..e1b7528d 100644 --- a/coins/monero/src/serialize.rs +++ b/coins/monero/src/serialize.rs @@ -1,3 +1,4 @@ +use std::fmt::Debug; use std::io::{self, Read, Write}; use curve25519_dalek::{ @@ -143,3 +144,10 @@ pub(crate) fn read_vec io::Result>( ) -> io::Result> { read_raw_vec(f, read_varint(r)?.try_into().unwrap(), r) } + +pub(crate) fn read_64_array io::Result>( + f: F, + r: &mut R, +) -> io::Result<[T; 64]> { + (0 .. 64).map(|_| f(r)).collect::>>().map(|vec| vec.try_into().unwrap()) +} diff --git a/coins/monero/src/transaction.rs b/coins/monero/src/transaction.rs index 4857b35a..1cfd0dcb 100644 --- a/coins/monero/src/transaction.rs +++ b/coins/monero/src/transaction.rs @@ -201,6 +201,10 @@ impl TransactionPrefix { prefix.extra = read_vec(read_byte, r)?; Ok(prefix) } + + pub fn hash(&self) -> [u8; 32] { + hash(&self.serialize()) + } } /// Monero transaction. For version 1, rct_signatures still contains an accurate fee value. @@ -254,18 +258,17 @@ impl Transaction { }; if prefix.version == 1 { - let read_sig = - |r: &mut R| -> io::Result<(Scalar, Scalar)> { Ok((read_scalar(r)?, read_scalar(r)?)) }; + signatures = prefix .inputs .iter() .filter_map(|input| match input { Input::ToKey { key_offsets, .. } => { - Some(key_offsets.iter().map(|_| (read_sig(r))).collect::, _>>()) + Some(key_offsets.iter().map(|_| Ok((read_scalar(r)?, read_scalar(r)?))).collect::>()) } _ => None, }) - .collect::>, _>>()?; + .collect::>()?; rct_signatures.base.fee = prefix .inputs @@ -304,9 +307,7 @@ impl Transaction { } else { let mut hashes = Vec::with_capacity(96); - self.prefix.write(&mut buf).unwrap(); - hashes.extend(hash(&buf)); - buf.clear(); + hashes.extend(self.prefix.hash()); self.rct_signatures.base.write(&mut buf, self.rct_signatures.prunable.rct_type()).unwrap(); hashes.extend(hash(&buf)); @@ -330,9 +331,7 @@ impl Transaction { let mut buf = Vec::with_capacity(2048); let mut sig_hash = Vec::with_capacity(96); - self.prefix.write(&mut buf).unwrap(); - sig_hash.extend(hash(&buf)); - buf.clear(); + sig_hash.extend(self.prefix.hash()); self.rct_signatures.base.write(&mut buf, self.rct_signatures.prunable.rct_type()).unwrap(); sig_hash.extend(hash(&buf));