diff --git a/coins/monero/Cargo.toml b/coins/monero/Cargo.toml index c0f41bd0..d5ef2e70 100644 --- a/coins/monero/Cargo.toml +++ b/coins/monero/Cargo.toml @@ -96,6 +96,9 @@ std = [ "serde/std", "serde_json/std", ] + http_rpc = ["digest_auth", "reqwest"] multisig = ["transcript", "frost", "dleq", "std"] +experimental = [] + default = ["std", "http_rpc"] diff --git a/coins/monero/generators/src/lib.rs b/coins/monero/generators/src/lib.rs index 742a8e0b..7f630f36 100644 --- a/coins/monero/generators/src/lib.rs +++ b/coins/monero/generators/src/lib.rs @@ -5,15 +5,12 @@ #![cfg_attr(not(feature = "std"), no_std)] -use core::cell::OnceCell; -use std_shims::sync::Mutex; +use std_shims::sync::OnceLock; use sha3::{Digest, Keccak256}; use curve25519_dalek::edwards::{EdwardsPoint as DalekPoint, CompressedEdwardsY}; -use curve25519_dalek::scalar::Scalar; -use std_shims::vec::Vec; use group::{Group, GroupEncoding}; use dalek_ff_group::EdwardsPoint; @@ -42,19 +39,16 @@ pub fn H() -> DalekPoint { static H_POW_2_CELL: OnceLock<[DalekPoint; 64]> = OnceLock::new(); /// Monero's alternate generator `H`, multiplied by 2**i for i in 1 ..= 64. #[allow(non_snake_case)] -pub fn H_pow_2() -> &[DalekPoint; 64] { +pub fn H_pow_2() -> &'static [DalekPoint; 64] { H_POW_2_CELL.get_or_init(|| { let mut res = [H(); 64]; for i in 1 .. 64 { - res[i] = res[i - 1].double(); + res[i] = res[i - 1] + res[i - 1]; } res }) } -#[allow(non_snake_case)] -fn generate_H2() -> [DalekPoint; 64] { - const MAX_M: usize = 16; const N: usize = 64; const MAX_MN: usize = MAX_M * N; diff --git a/coins/monero/src/ringct/borromean.rs b/coins/monero/src/ringct/borromean.rs index a3da7f65..c98a0068 100644 --- a/coins/monero/src/ringct/borromean.rs +++ b/coins/monero/src/ringct/borromean.rs @@ -1,39 +1,36 @@ #![allow(non_snake_case)] -use std::fmt::Debug; -use std::io::{self, Read, Write}; +use core::fmt::Debug; +use std_shims::io::{self, Read, Write}; -use curve25519_dalek::edwards::EdwardsPoint; -use curve25519_dalek::scalar::Scalar; -use curve25519_dalek::traits::Identity; +use curve25519_dalek::{traits::Identity, scalar::Scalar, edwards::EdwardsPoint}; -use monero_generators::H2; +use monero_generators::H_pow_2; +use crate::{hash_to_scalar, serialize::*}; -use crate::hash_to_scalar; -use crate::serialize::*; - -/// A Borromean signature. +/// 64 Borromean ring signatures, modified to be aggregated with a shared challenge. /// -/// 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. +/// This type keeps the data as raw bytes as Monero has some transactions with unreduced scalars in +/// this field. While we could use `from_bytes_mod_order`, we'd then not be able to encode this +/// back into it's original form. /// +/// Those scalars also have a custom reduction algorithm... #[derive(Clone, PartialEq, Eq, Debug)] -pub struct BorroSig { +pub struct BorromeanSignatures { pub s0: [[u8; 32]; 64], pub s1: [[u8; 32]; 64], pub ee: [u8; 32], } -impl BorroSig { - pub fn read(r: &mut R) -> io::Result { - Ok(BorroSig { +impl BorromeanSignatures { + pub fn read(r: &mut R) -> io::Result { + Ok(BorromeanSignatures { s0: read_array(read_bytes, r)?, s1: read_array(read_bytes, r)?, ee: read_bytes(r)?, }) } + pub fn write(&self, w: &mut W) -> io::Result<()> { for s0 in self.s0.iter() { w.write_all(s0)?; @@ -43,66 +40,63 @@ impl BorroSig { } w.write_all(&self.ee) } + + #[cfg(feature = "experimental")] + fn verify(&self, keys_a: &[EdwardsPoint], keys_b: &[EdwardsPoint]) -> bool { + let mut transcript = [0; 2048]; + for i in 0 .. 64 { + // TODO: These aren't the correct reduction + // TODO: Can either of these be tightened? + let LL = EdwardsPoint::vartime_double_scalar_mul_basepoint( + &Scalar::from_bytes_mod_order(self.ee), + &keys_a[i], + &Scalar::from_bytes_mod_order(self.s0[i]), + ); + let LV = EdwardsPoint::vartime_double_scalar_mul_basepoint( + &hash_to_scalar(LL.compress().as_bytes()), + &keys_b[i], + &Scalar::from_bytes_mod_order(self.s1[i]), + ); + transcript[i .. ((i + 1) * 32)].copy_from_slice(LV.compress().as_bytes()); + } + + // TODO: This isn't the correct reduction + // TODO: Can this be tightened to from_canonical_bytes? + hash_to_scalar(&transcript) == Scalar::from_bytes_mod_order(self.ee) + } } +/// A range proof premised on Borromean ring signatures. #[derive(Clone, PartialEq, Eq, Debug)] -pub struct RangeSig { - pub asig: BorroSig, - pub Ci: [EdwardsPoint; 64], +pub struct BorromeanRange { + pub sig: BorromeanSignatures, + pub bit_commitments: [EdwardsPoint; 64], } -impl RangeSig { - pub fn read(r: &mut R) -> io::Result { - Ok(RangeSig { asig: BorroSig::read(r)?, Ci: read_array(read_point, r)? }) +impl BorromeanRange { + pub fn read(r: &mut R) -> io::Result { + Ok(BorromeanRange { + sig: BorromeanSignatures::read(r)?, + bit_commitments: read_array(read_point, r)?, + }) } pub fn write(&self, w: &mut W) -> io::Result<()> { - self.asig.write(w)?; - write_raw_vec(write_point, &self.Ci, w) + self.sig.write(w)?; + write_raw_vec(write_point, &self.bit_commitments, w) } + #[cfg(feature = "experimental")] 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(); + if &self.bit_commitments.iter().sum::() != commitment { + return false; + } + let H_pow_2 = H_pow_2(); + let mut commitments_sub_one = [EdwardsPoint::identity(); 64]; 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]; + commitments_sub_one[i] = self.bit_commitments[i] - H_pow_2[i]; } - if &C_temp != commitment { - false - } else { - verify_borromean(P1, P2, bbee, bbs0, bbs1) - } + self.sig.verify(&self.bit_commitments, &commitments_sub_one) } } - -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); - - eecomp == bbee -} diff --git a/coins/monero/src/ringct/mod.rs b/coins/monero/src/ringct/mod.rs index 1c5ee081..8053020e 100644 --- a/coins/monero/src/ringct/mod.rs +++ b/coins/monero/src/ringct/mod.rs @@ -15,7 +15,7 @@ pub use hash_to_point::{raw_hash_to_point, hash_to_point}; pub mod clsag; /// MLSAG struct, along with verifying functionality. pub mod mlsag; -/// RangeSig struct, along with verifying functionality. +/// BorromeanRange struct, along with verifying functionality. pub mod borromean; /// Bulletproofs(+) structs, along with proving and verifying functionality. pub mod bulletproofs; @@ -23,7 +23,7 @@ pub mod bulletproofs; use crate::{ Protocol, serialize::*, - ringct::{clsag::Clsag, mlsag::MgSig, bulletproofs::Bulletproofs, borromean::RangeSig}, + ringct::{clsag::Clsag, mlsag::MgSig, bulletproofs::Bulletproofs, borromean::BorromeanRange}, }; /// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`. @@ -110,7 +110,7 @@ impl RctBase { pub enum RctPrunable { Null, Borromean { - range_sigs: Vec, + range_sigs: Vec, mlsags: Vec, simple: bool, }, @@ -165,7 +165,7 @@ impl RctPrunable { match self { RctPrunable::Null => Ok(()), RctPrunable::Borromean { range_sigs, mlsags, simple: _ } => { - write_raw_vec(RangeSig::write, range_sigs, w)?; + write_raw_vec(BorromeanRange::write, range_sigs, w)?; write_raw_vec(MgSig::write, mlsags, w) } RctPrunable::BulletProof { bulletproofs, mlsags, pseudo_outs, v2 } => { @@ -201,7 +201,7 @@ impl RctPrunable { Ok(match rct_type { 0 => RctPrunable::Null, 1 | 2 => RctPrunable::Borromean { - range_sigs: read_raw_vec(RangeSig::read, outputs, r)?, + range_sigs: read_raw_vec(BorromeanRange::read, outputs, r)?, mlsags: decoys.iter().map(|d| MgSig::read(*d, r)).collect::>()?, simple: rct_type == 2, }, diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index 79b80894..e7b92c24 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -708,9 +708,7 @@ impl SignableTransaction { clsags.append(&mut clsag_pairs.iter().map(|clsag| clsag.0.clone()).collect::>()); pseudo_outs.append(&mut clsag_pairs.iter().map(|clsag| clsag.1).collect::>()); } - _ => { - todo!() - } + _ => unreachable!("attempted to sign a TX which wasn't CLSAG"), } Ok(tx) } diff --git a/coins/monero/src/wallet/send/multisig.rs b/coins/monero/src/wallet/send/multisig.rs index b70e0770..3694d507 100644 --- a/coins/monero/src/wallet/send/multisig.rs +++ b/coins/monero/src/wallet/send/multisig.rs @@ -430,7 +430,7 @@ impl SignatureMachine for TransactionSignatureMachine { pseudo_outs.push(pseudo_out); } } - _ => todo!(""), + _ => unreachable!("attempted to sign a multisig TX which wasn't CLSAG"), } Ok(tx) }