From f81f5c386d5a3e6a6838b0d3ac9ab32a05e8875d Mon Sep 17 00:00:00 2001 From: Boog900 <54e72d8a-345f-4599-bd90-c6b9bc7d0ec5@aleeas.com> Date: Wed, 31 May 2023 19:32:47 +0100 Subject: [PATCH] add borromean + fix mlsag --- coins/monero/src/ringct/borromean.rs | 54 ++++++++++++++ coins/monero/src/ringct/mlsag/mod.rs | 12 ++-- coins/monero/src/ringct/mod.rs | 91 ++++++++++++++++++++---- coins/monero/src/transaction.rs | 2 +- coins/monero/src/wallet/mod.rs | 21 +++++- coins/monero/src/wallet/scan.rs | 2 +- coins/monero/src/wallet/send/mod.rs | 9 ++- coins/monero/src/wallet/send/multisig.rs | 2 +- 8 files changed, 167 insertions(+), 26 deletions(-) create mode 100644 coins/monero/src/ringct/borromean.rs diff --git a/coins/monero/src/ringct/borromean.rs b/coins/monero/src/ringct/borromean.rs new file mode 100644 index 00000000..3c2240b3 --- /dev/null +++ b/coins/monero/src/ringct/borromean.rs @@ -0,0 +1,54 @@ +#![allow(non_snake_case)] + +use std::fmt::Debug; +use std::io::{self, Read, Write}; + +use curve25519_dalek::edwards::EdwardsPoint; +use curve25519_dalek::scalar::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()) +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct BorroSig { + pub s0: [Scalar; 64], + pub s1: [Scalar; 64], + pub ee: Scalar, +} + +impl BorroSig { + pub fn read(r: &mut R) -> io::Result { + Ok(BorroSig { + s0: read_64_array(read_scalar, r)?, + s1: read_64_array(read_scalar, r)?, + ee: read_scalar(r)?, + }) + } + pub fn write(&self, w: &mut W) -> io::Result<()> { + write_raw_vec(write_scalar, &self.s0, w)?; + write_raw_vec(write_scalar, &self.s1, w)?; + write_scalar(&self.ee, w) + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct RangeSig { + pub asig: BorroSig, + pub Ci: [EdwardsPoint; 64], +} + +impl RangeSig { + pub fn read(r: &mut R) -> io::Result { + Ok(RangeSig { asig: BorroSig::read(r)?, Ci: read_64_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) + } +} diff --git a/coins/monero/src/ringct/mlsag/mod.rs b/coins/monero/src/ringct/mlsag/mod.rs index d7711453..06ff3ed6 100644 --- a/coins/monero/src/ringct/mlsag/mod.rs +++ b/coins/monero/src/ringct/mlsag/mod.rs @@ -13,7 +13,7 @@ use crate::{ #[derive(Clone, PartialEq, Eq, Debug)] pub struct Mlsag { pub ss: Vec>, - pub cc: EdwardsPoint, + pub cc: Scalar, } impl Mlsag { @@ -21,15 +21,15 @@ impl Mlsag { for ss in self.ss.iter() { write_raw_vec(write_scalar, ss, w)?; } - write_point(&self.cc, w) + write_scalar(&self.cc, w) } - pub fn read(decoys: usize, elements: usize, r: &mut R) -> io::Result { + pub fn read(mixins: usize, ss2_elements: usize, r: &mut R) -> io::Result { Ok(Mlsag { - ss: (0 .. decoys) - .map(|_| read_raw_vec(read_scalar, elements, r)) + ss: (0 .. mixins) + .map(|_| read_raw_vec(read_scalar, ss2_elements, r)) .collect::>()?, - cc: read_point(r)?, + cc: read_scalar(r)?, }) } } diff --git a/coins/monero/src/ringct/mod.rs b/coins/monero/src/ringct/mod.rs index 2087ccc7..aaf7e77e 100644 --- a/coins/monero/src/ringct/mod.rs +++ b/coins/monero/src/ringct/mod.rs @@ -10,15 +10,17 @@ pub use hash_to_point::{raw_hash_to_point, hash_to_point}; /// CLSAG struct, along with signing and verifying functionality. pub mod clsag; -/// MLSAG struct, along with verifying functionality. +/// MLSAG struct. pub mod mlsag; +/// RangeSig struct. +pub mod borromean; /// Bulletproofs(+) structs, along with proving and verifying functionality. pub mod bulletproofs; use crate::{ Protocol, serialize::*, - ringct::{clsag::Clsag, mlsag::Mlsag, bulletproofs::Bulletproofs}, + ringct::{clsag::Clsag, mlsag::Mlsag, bulletproofs::Bulletproofs, borromean::RangeSig}, }; /// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`. @@ -26,10 +28,36 @@ pub fn generate_key_image(secret: &Zeroizing) -> EdwardsPoint { hash_to_point(&ED25519_BASEPOINT_TABLE * secret.deref()) * secret.deref() } +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum EcdhInfo { + Standard { mask: Scalar, amount: Scalar }, + Bulletproof { amount: [u8; 8] }, +} + +impl EcdhInfo { + pub fn read(rct_type: u8, r: &mut R) -> io::Result<(EcdhInfo)> { + Ok(match rct_type { + 0 ..= 3 => EcdhInfo::Standard { mask: read_scalar(r)?, amount: read_scalar(r)? }, + _ => EcdhInfo::Bulletproof { amount: read_bytes(r)? }, + }) + } + + pub fn write(&self, w: &mut W) -> io::Result<()> { + match self { + EcdhInfo::Standard { mask, amount } => { + write_scalar(mask, w)?; + write_scalar(amount, w) + } + EcdhInfo::Bulletproof { amount } => w.write_all(amount), + } + } +} + #[derive(Clone, PartialEq, Eq, Debug)] pub struct RctBase { pub fee: u64, - pub ecdh_info: Vec<[u8; 8]>, + pub ecdh_info: Vec, + pub pseudo_outs: Vec, pub commitments: Vec, } @@ -42,10 +70,13 @@ impl RctBase { w.write_all(&[rct_type])?; match rct_type { 0 => Ok(()), - 5 | 6 => { + _ => { write_varint(&self.fee, w)?; + if rct_type == 2 { + write_raw_vec(write_point, &self.pseudo_outs, w)?; + } for ecdh in &self.ecdh_info { - w.write_all(ecdh)?; + ecdh.write(w)?; } write_raw_vec(write_point, &self.commitments, w) } @@ -53,15 +84,18 @@ impl RctBase { } } - pub fn read(outputs: usize, r: &mut R) -> io::Result<(RctBase, u8)> { + pub fn read(inputs: usize, outputs: usize, r: &mut R) -> io::Result<(RctBase, u8)> { let rct_type = read_byte(r)?; Ok(( if rct_type == 0 { - RctBase { fee: 0, ecdh_info: vec![], commitments: vec![] } + RctBase { fee: 0, ecdh_info: vec![], pseudo_outs: vec![], commitments: vec![] } } else { RctBase { fee: read_varint(r)?, - ecdh_info: (0 .. outputs).map(|_| read_bytes(r)).collect::>()?, + pseudo_outs: if rct_type == 2 { read_raw_vec(read_point, inputs, r)? } else { vec![] }, + ecdh_info: (0 .. outputs) + .map(|_| EcdhInfo::read(rct_type, r)) + .collect::>()?, commitments: read_raw_vec(read_point, outputs, r)?, } }, @@ -73,6 +107,11 @@ impl RctBase { #[derive(Clone, PartialEq, Eq, Debug)] pub enum RctPrunable { Null, + Borromean { + range_sigs: Vec, + mlsags: Vec, + simple: bool, + }, BulletProof { bulletproofs: Vec, mlsags: Vec, @@ -91,6 +130,13 @@ impl RctPrunable { pub fn rct_type(&self) -> u8 { match self { RctPrunable::Null => 0, + RctPrunable::Borromean { simple, .. } => { + if !simple { + 1 + } else { + 2 + } + } RctPrunable::BulletProof { v2, .. } => { if !v2 { 3 @@ -116,6 +162,10 @@ impl RctPrunable { pub fn write(&self, w: &mut W) -> io::Result<()> { match self { RctPrunable::Null => Ok(()), + RctPrunable::Borromean { range_sigs, mlsags, simple: _ } => { + write_raw_vec(RangeSig::write, range_sigs, w)?; + write_raw_vec(Mlsag::write, mlsags, w) + } RctPrunable::BulletProof { bulletproofs, mlsags, pseudo_outs, v2 } => { if !v2 { w.write_all(&u32::try_from(bulletproofs.len()).unwrap().to_le_bytes())?; @@ -140,9 +190,26 @@ impl RctPrunable { serialized } - pub fn read(rct_type: u8, decoys: &[usize], r: &mut R) -> io::Result { + pub fn read( + rct_type: u8, + decoys: &[usize], + outputs: usize, + r: &mut R, + ) -> io::Result { Ok(match rct_type { 0 => RctPrunable::Null, + 1 => RctPrunable::Borromean { + range_sigs: read_raw_vec(RangeSig::read, outputs, r)?, + mlsags: vec![Mlsag::read(decoys[0], 1 + decoys.len(), r)?], + simple: false, + }, + 2 => RctPrunable::Borromean { + range_sigs: read_raw_vec(RangeSig::read, outputs, r)?, + mlsags: (0 .. decoys.len()) + .map(|o| Mlsag::read(decoys[o], 2, r)) + .collect::>()?, + simple: true, + }, 3 | 4 => RctPrunable::BulletProof { bulletproofs: read_raw_vec( Bulletproofs::read, @@ -172,10 +239,10 @@ impl RctPrunable { pub(crate) fn signature_write(&self, w: &mut W) -> io::Result<()> { match self { RctPrunable::Null => panic!("Serializing RctPrunable::Null for a signature"), - RctPrunable::BulletProof { .. } => todo!(), RctPrunable::Clsag { bulletproofs, .. } => { bulletproofs.iter().try_for_each(|bp| bp.signature_write(w)) } + _ => todo!(), } } } @@ -203,7 +270,7 @@ impl RctSignatures { } pub fn read(decoys: Vec, outputs: usize, r: &mut R) -> io::Result { - let base = RctBase::read(outputs, r)?; - Ok(RctSignatures { base: base.0, prunable: RctPrunable::read(base.1, &decoys, r)? }) + let base = RctBase::read(decoys.len(), outputs, r)?; + Ok(RctSignatures { base: base.0, prunable: RctPrunable::read(base.1, &decoys, outputs, r)? }) } } diff --git a/coins/monero/src/transaction.rs b/coins/monero/src/transaction.rs index 0053bec8..4857b35a 100644 --- a/coins/monero/src/transaction.rs +++ b/coins/monero/src/transaction.rs @@ -249,7 +249,7 @@ impl Transaction { let prefix = TransactionPrefix::read(r)?; let mut signatures = vec![]; let mut rct_signatures = RctSignatures { - base: RctBase { fee: 0, ecdh_info: vec![], commitments: vec![] }, + base: RctBase { fee: 0, ecdh_info: vec![], pseudo_outs: vec![], commitments: vec![] }, prunable: RctPrunable::Null, }; diff --git a/coins/monero/src/wallet/mod.rs b/coins/monero/src/wallet/mod.rs index b7322d84..2ac16cb3 100644 --- a/coins/monero/src/wallet/mod.rs +++ b/coins/monero/src/wallet/mod.rs @@ -35,6 +35,7 @@ pub use send::{ pub(crate) use send::InternalPayment; #[cfg(feature = "multisig")] pub use send::TransactionMachine; +use crate::ringct::EcdhInfo; fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> std::cmp::Ordering { x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse() @@ -92,8 +93,24 @@ pub(crate) fn amount_encryption(amount: u64, key: Scalar) -> [u8; 8] { (amount ^ u64::from_le_bytes(hash(&amount_mask)[.. 8].try_into().unwrap())).to_le_bytes() } -fn amount_decryption(amount: [u8; 8], key: Scalar) -> u64 { - u64::from_le_bytes(amount_encryption(u64::from_le_bytes(amount), key)) +fn amount_decryption(amount: &EcdhInfo, key: Scalar) -> u64 { + match amount { + EcdhInfo::Standard { mask, amount } => { + let shared_sec1 = hash(key.as_bytes()); + let shared_sec2 = hash(&shared_sec1); + let mask_scalar = mask - Scalar::from_bytes_mod_order(shared_sec1); + + let amount_scalar = amount - Scalar::from_bytes_mod_order(shared_sec2); + // get first 64 bits (d2b in rctTypes.cpp) + let amount_significant_bytes = + amount_scalar.to_bytes()[0 .. 8].try_into().expect("Can't fail"); + let amount = u64::from_le_bytes(amount_significant_bytes); + amount + } + EcdhInfo::Bulletproof { amount } => { + u64::from_le_bytes(amount_encryption(u64::from_le_bytes(*amount), key)) + } + } } pub(crate) fn commitment_mask(shared_key: Scalar) -> Scalar { diff --git a/coins/monero/src/wallet/scan.rs b/coins/monero/src/wallet/scan.rs index 9ae046ab..03bce1c0 100644 --- a/coins/monero/src/wallet/scan.rs +++ b/coins/monero/src/wallet/scan.rs @@ -372,7 +372,7 @@ impl Scanner { // Regular transaction } else { let amount = match tx.rct_signatures.base.ecdh_info.get(o) { - Some(amount) => amount_decryption(*amount, shared_key), + Some(amount) => amount_decryption(amount, shared_key), // This should never happen, yet it may be possible with miner transactions? // Using get just decreases the possibility of a panic and lets us move on in that case None => break, diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index c7f5c0c6..4eb780f1 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -49,6 +49,7 @@ pub use builder::SignableTransactionBuilder; mod multisig; #[cfg(feature = "multisig")] pub use multisig::TransactionMachine; +use crate::ringct::EcdhInfo; #[allow(non_snake_case)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] @@ -632,7 +633,7 @@ impl SignableTransaction { key: output.dest.compress(), view_tag: Some(output.view_tag).filter(|_| matches!(self.protocol, Protocol::v16)), }); - ecdh_info.push(output.amount); + ecdh_info.push(EcdhInfo::Bulletproof { amount: output.amount }); } ( @@ -649,6 +650,7 @@ impl SignableTransaction { base: RctBase { fee, ecdh_info, + pseudo_outs: vec![], commitments: commitments.iter().map(|commitment| commitment.calculate()).collect(), }, prunable: RctPrunable::Clsag { @@ -701,7 +703,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::>()); } - RctPrunable::BulletProof { .. } => { + _ => { todo!() } } @@ -753,7 +755,8 @@ impl Eventuality { view_tag: Some(expected.view_tag).filter(|_| matches!(self.protocol, Protocol::v16)), } != actual) || (Some(&expected.commitment.calculate()) != tx.rct_signatures.base.commitments.get(o)) || - (Some(&expected.amount) != tx.rct_signatures.base.ecdh_info.get(o)) + (Some(&EcdhInfo::Bulletproof { amount: expected.amount }) != + tx.rct_signatures.base.ecdh_info.get(o)) { return false; } diff --git a/coins/monero/src/wallet/send/multisig.rs b/coins/monero/src/wallet/send/multisig.rs index 86248c27..fa1187ac 100644 --- a/coins/monero/src/wallet/send/multisig.rs +++ b/coins/monero/src/wallet/send/multisig.rs @@ -429,7 +429,7 @@ impl SignatureMachine for TransactionSignatureMachine { pseudo_outs.push(pseudo_out); } } - RctPrunable::BulletProof { .. } => todo!(""), + _ => todo!(""), } Ok(tx) }