diff --git a/coins/monero/rpc/src/lib.rs b/coins/monero/rpc/src/lib.rs index 27fe1fd7..daccac0c 100644 --- a/coins/monero/rpc/src/lib.rs +++ b/coins/monero/rpc/src/lib.rs @@ -270,7 +270,7 @@ pub trait Rpc: Sync + Clone + Debug { async fn get_protocol(&self) -> Result { #[derive(Deserialize, Debug)] struct ProtocolResponse { - major_version: u8, + hardfork_version: u8, } #[derive(Deserialize, Debug)] @@ -283,7 +283,7 @@ pub trait Rpc: Sync + Clone + Debug { .json_rpc_call::("get_last_block_header", None) .await? .block_header - .major_version, + .hardfork_version, ) } diff --git a/coins/monero/src/bin/reserialize_chain.rs b/coins/monero/src/bin/reserialize_chain.rs index 56748331..d133ed9e 100644 --- a/coins/monero/src/bin/reserialize_chain.rs +++ b/coins/monero/src/bin/reserialize_chain.rs @@ -114,7 +114,7 @@ mod binaries { ); assert_eq!(tx.hash(), tx_hash, "Transaction hash was different"); - if matches!(tx.rct_signatures.prunable, RctPrunable::Null) { + if matches!(tx.proofs.prunable, RctPrunable::Null) { assert_eq!(tx.prefix.version, 1); assert!(!tx.signatures.is_empty()); continue; @@ -126,7 +126,7 @@ mod binaries { // multisig explicitly calling verify as part of its signing process // Accordingly, making sure our signature_hash algorithm is correct is great, and further // making sure the verification functions are valid is appreciated - match tx.rct_signatures.prunable { + match tx.proofs.prunable { RctPrunable::Null | RctPrunable::AggregateMlsagBorromean { .. } | RctPrunable::MlsagBorromean { .. } => {} @@ -134,14 +134,14 @@ mod binaries { assert!(bulletproofs.batch_verify( &mut rand_core::OsRng, &mut batch, - &tx.rct_signatures.base.commitments + &tx.proofs.base.commitments )); } RctPrunable::Clsag { bulletproofs, clsags, pseudo_outs } => { assert!(bulletproofs.batch_verify( &mut rand_core::OsRng, &mut batch, - &tx.rct_signatures.base.commitments + &tx.proofs.base.commitments )); for (i, clsag) in clsags.into_iter().enumerate() { diff --git a/coins/monero/src/block.rs b/coins/monero/src/block.rs index caa1d74e..c93d45fd 100644 --- a/coins/monero/src/block.rs +++ b/coins/monero/src/block.rs @@ -18,10 +18,14 @@ const EXISTING_BLOCK_HASH_202612: [u8; 32] = /// A Monero block's header. #[derive(Clone, PartialEq, Eq, Debug)] pub struct BlockHeader { - /// The major version of the protocol, denoting the hard fork. - pub major_version: u8, - /// The minor version of the protocol. - pub minor_version: u8, + /// The hard fork of the protocol this block follows. + /// + /// Per the C++ codebase, this is the `major_version`. + pub hardfork_version: u8, + /// A signal for a proposed hard fork. + /// + /// Per the C++ codebase, this is the `minor_version`. + pub hardfork_signal: u8, /// Seconds since the epoch. pub timestamp: u64, /// The previous block's hash. @@ -36,8 +40,8 @@ pub struct BlockHeader { impl BlockHeader { /// Write the BlockHeader. pub fn write(&self, w: &mut W) -> io::Result<()> { - write_varint(&self.major_version, w)?; - write_varint(&self.minor_version, w)?; + write_varint(&self.hardfork_version, w)?; + write_varint(&self.hardfork_signal, w)?; write_varint(&self.timestamp, w)?; w.write_all(&self.previous)?; w.write_all(&self.nonce.to_le_bytes()) @@ -53,8 +57,8 @@ impl BlockHeader { /// Read a BlockHeader. pub fn read(r: &mut R) -> io::Result { Ok(BlockHeader { - major_version: read_varint(r)?, - minor_version: read_varint(r)?, + hardfork_version: read_varint(r)?, + hardfork_signal: read_varint(r)?, timestamp: read_varint(r)?, previous: read_bytes(r)?, nonce: read_bytes(r).map(u32::from_le_bytes)?, @@ -79,13 +83,15 @@ impl Block { /// This information comes from the Block's miner transaction. If the miner transaction isn't /// structed as expected, this will return None. pub fn number(&self) -> Option { - match self.miner_tx.prefix.inputs.first() { - Some(Input::Gen(number)) => Some(*number), - _ => None, + match &self.miner_tx { + Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => match prefix.inputs.first() { + Some(Input::Gen(number)) => Some(*number), + _ => None, + } } } - /// Write the BlockHeader. + /// Write the Block. pub fn write(&self, w: &mut W) -> io::Result<()> { self.header.write(w)?; self.miner_tx.write(w)?; @@ -96,7 +102,7 @@ impl Block { Ok(()) } - /// Serialize the BlockHeader to a Vec. + /// Serialize the Block to a Vec. pub fn serialize(&self) -> Vec { let mut serialized = vec![]; self.write(&mut serialized).unwrap(); @@ -116,10 +122,10 @@ impl Block { /// Get the hash of this block. pub fn hash(&self) -> [u8; 32] { - let mut hashable = self.serialize_hashable(); - // Monero pre-appends a VarInt of the block hashing blobs length before getting the block hash + let mut hashable = self.serialize_pow_hash(); + // Monero pre-appends a VarInt of the block-to-hash'ss length before getting the block hash, // but doesn't do this when getting the proof of work hash :) - let mut hashing_blob = Vec::with_capacity(8 + hashable.len()); + let mut hashing_blob = Vec::with_capacity(9 + hashable.len()); write_varint(&u64::try_from(hashable.len()).unwrap(), &mut hashing_blob).unwrap(); hashing_blob.append(&mut hashable); @@ -130,18 +136,11 @@ impl Block { hash } - /// Read a BlockHeader. + /// Read a Block. pub fn read(r: &mut R) -> io::Result { - let header = BlockHeader::read(r)?; - - let miner_tx = Transaction::read(r)?; - if !matches!(miner_tx.prefix.inputs.as_slice(), &[Input::Gen(_)]) { - Err(io::Error::other("Miner transaction has incorrect input type."))?; - } - Ok(Block { - header, - miner_tx, + header: BlockHeader::read(r)?, + miner_tx: Transaction::read(r)?, txs: (0_usize .. read_varint(r)?).map(|_| read_bytes(r)).collect::>()?, }) } diff --git a/coins/monero/src/lib.rs b/coins/monero/src/lib.rs index 0e0b2fcf..1bfcbba7 100644 --- a/coins/monero/src/lib.rs +++ b/coins/monero/src/lib.rs @@ -1,6 +1,6 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] -// #![deny(missing_docs)] // TODO +#![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] pub use monero_io as io; diff --git a/coins/monero/src/ringct.rs b/coins/monero/src/ringct.rs index b8a2826c..0893dd37 100644 --- a/coins/monero/src/ringct.rs +++ b/coins/monero/src/ringct.rs @@ -1,12 +1,11 @@ -use core::ops::Deref; use std_shims::{ vec::Vec, io::{self, Read, Write}, }; -use zeroize::{Zeroize, Zeroizing}; +use zeroize::Zeroize; -use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint}; +use curve25519_dalek::edwards::EdwardsPoint; pub use monero_mlsag as mlsag; pub use monero_clsag as clsag; @@ -15,15 +14,24 @@ pub use monero_bulletproofs as bulletproofs; use crate::{ io::*, - generators::hash_to_point, ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof}, }; /// An encrypted amount. #[derive(Clone, PartialEq, Eq, Debug)] pub enum EncryptedAmount { - Original { mask: [u8; 32], amount: [u8; 32] }, - Compact { amount: [u8; 8] }, + /// The original format for encrypted amounts. + Original { + /// A mask used with a mask derived from the shared secret to encrypt the amount. + mask: [u8; 32], + /// The amount, as a scalar, encrypted. + amount: [u8; 32], + }, + /// The "compact" format for encrypted amounts. + Compact { + /// The amount, as a u64, encrypted. + amount: [u8; 8], + }, } impl EncryptedAmount { @@ -51,44 +59,41 @@ impl EncryptedAmount { /// The type of the RingCT data. #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] pub enum RctType { - /// No RCT proofs. - Null, /// One MLSAG for multiple inputs and Borromean range proofs. /// - /// This lines up with RCTTypeFull. - MlsagAggregate, + /// This aligns with RCTTypeFull. + AggregateMlsagBorromean, // One MLSAG for each input and a Borromean range proof. /// - /// This lines up with RCTTypeSimple. - MlsagIndividual, + /// This aligns with RCTTypeSimple. + MlsagBorromean, // One MLSAG for each input and a Bulletproof. /// - /// This lines up with RCTTypeBulletproof. - Bulletproofs, + /// This aligns with RCTTypeBulletproof. + MlsagBulletproofs, /// One MLSAG for each input and a Bulletproof, yet using EncryptedAmount::Compact. /// - /// This lines up with RCTTypeBulletproof2. - BulletproofsCompactAmount, + /// This aligns with RCTTypeBulletproof2. + MlsagBulletproofsCompactAmount, /// One CLSAG for each input and a Bulletproof. /// - /// This lines up with RCTTypeCLSAG. - Clsag, + /// This aligns with RCTTypeCLSAG. + ClsagBulletproof, /// One CLSAG for each input and a Bulletproof+. /// - /// This lines up with RCTTypeBulletproofPlus. - BulletproofsPlus, + /// This aligns with RCTTypeBulletproofPlus. + ClsagBulletproofPlus, } impl From for u8 { fn from(kind: RctType) -> u8 { match kind { - RctType::Null => 0, - RctType::MlsagAggregate => 1, - RctType::MlsagIndividual => 2, - RctType::Bulletproofs => 3, - RctType::BulletproofsCompactAmount => 4, - RctType::Clsag => 5, - RctType::BulletproofsPlus => 6, + RctType::AggregateMlsagBorromean => 1, + RctType::MlsagBorromean => 2, + RctType::MlsagBulletproofs => 3, + RctType::MlsagBulletproofsCompactAmount => 4, + RctType::ClsagBulletproof => 5, + RctType::ClsagBulletproofPlus => 6, } } } @@ -97,27 +102,51 @@ impl TryFrom for RctType { type Error = (); fn try_from(byte: u8) -> Result { Ok(match byte { - 0 => RctType::Null, - 1 => RctType::MlsagAggregate, - 2 => RctType::MlsagIndividual, - 3 => RctType::Bulletproofs, - 4 => RctType::BulletproofsCompactAmount, - 5 => RctType::Clsag, - 6 => RctType::BulletproofsPlus, + 1 => RctType::AggregateMlsagBorromean, + 2 => RctType::MlsagBorromean, + 3 => RctType::MlsagBulletproofs, + 4 => RctType::MlsagBulletproofsCompactAmount, + 5 => RctType::ClsagBulletproof, + 6 => RctType::ClsagBulletproofPlus, _ => Err(())?, }) } } impl RctType { - /// Returns true if this RctType uses compact encrypted amounts, false otherwise. - pub fn compact_encrypted_amounts(&self) -> bool { + /// True if this RctType uses compact encrypted amounts, false otherwise. + fn compact_encrypted_amounts(&self) -> bool { match self { - RctType::Null | - RctType::MlsagAggregate | - RctType::MlsagIndividual | - RctType::Bulletproofs => false, - RctType::BulletproofsCompactAmount | RctType::Clsag | RctType::BulletproofsPlus => true, + RctType::AggregateMlsagBorromean | RctType::MlsagBorromean | RctType::MlsagBulletproofs => { + false + } + RctType::MlsagBulletproofsCompactAmount | + RctType::ClsagBulletproof | + RctType::ClsagBulletproofPlus => true, + } + } + + /// True if this RctType uses a Bulletproof, false otherwise. + pub(crate) fn bulletproof(&self) -> bool { + match self { + RctType::MlsagBulletproofs | + RctType::MlsagBulletproofsCompactAmount | + RctType::ClsagBulletproof => true, + RctType::AggregateMlsagBorromean | + RctType::MlsagBorromean | + RctType::ClsagBulletproofPlus => false, + } + } + + /// True if this RctType uses a Bulletproof+, false otherwise. + pub(crate) fn bulletproof_plus(&self) -> bool { + match self { + RctType::ClsagBulletproofPlus => true, + RctType::AggregateMlsagBorromean | + RctType::MlsagBorromean | + RctType::MlsagBulletproofs | + RctType::MlsagBulletproofsCompactAmount | + RctType::ClsagBulletproof => false, } } } @@ -137,7 +166,7 @@ pub struct RctBase { /// /// This field was deprecated and is empty for modern RctTypes. pub pseudo_outs: Vec, - /// The encrypted amounts for the recipient to decrypt. + /// The encrypted amounts for the recipients to decrypt. pub encrypted_amounts: Vec, /// The output commitments. pub commitments: Vec, @@ -153,32 +182,28 @@ impl RctBase { /// Write the RctBase. pub fn write(&self, w: &mut W, rct_type: RctType) -> io::Result<()> { w.write_all(&[u8::from(rct_type)])?; - match rct_type { - RctType::Null => Ok(()), - _ => { - write_varint(&self.fee, w)?; - if rct_type == RctType::MlsagIndividual { - write_raw_vec(write_point, &self.pseudo_outs, w)?; - } - for encrypted_amount in &self.encrypted_amounts { - encrypted_amount.write(w)?; - } - write_raw_vec(write_point, &self.commitments, w) - } + + write_varint(&self.fee, w)?; + if rct_type == RctType::MlsagBorromean { + write_raw_vec(write_point, &self.pseudo_outs, w)?; } + for encrypted_amount in &self.encrypted_amounts { + encrypted_amount.write(w)?; + } + write_raw_vec(write_point, &self.commitments, w) } /// Read a RctBase. pub fn read(inputs: usize, outputs: usize, r: &mut R) -> io::Result<(RctBase, RctType)> { let rct_type = - RctType::try_from(read_byte(r)?).map_err(|_| io::Error::other("invalid RCT type"))?; + RctType::try_from(read_byte(r)?).map_err(|()| io::Error::other("invalid RCT type"))?; match rct_type { - RctType::Null | RctType::MlsagAggregate | RctType::MlsagIndividual => {} - RctType::Bulletproofs | - RctType::BulletproofsCompactAmount | - RctType::Clsag | - RctType::BulletproofsPlus => { + RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => {} + RctType::MlsagBulletproofs | + RctType::MlsagBulletproofsCompactAmount | + RctType::ClsagBulletproof | + RctType::ClsagBulletproofPlus => { if outputs == 0 { // Because the Bulletproofs(+) layout must be canonical, there must be 1 Bulletproof if // Bulletproofs are in use @@ -191,23 +216,19 @@ impl RctBase { } Ok(( - if rct_type == RctType::Null { - RctBase { fee: 0, pseudo_outs: vec![], encrypted_amounts: vec![], commitments: vec![] } - } else { - RctBase { - fee: read_varint(r)?, - // Only read pseudo_outs if they have yet to be moved to RctPrunable - // TODO: Shouldn't this be any Mlsag*? - pseudo_outs: if rct_type == RctType::MlsagIndividual { - read_raw_vec(read_point, inputs, r)? - } else { - vec![] - }, - encrypted_amounts: (0 .. outputs) - .map(|_| EncryptedAmount::read(rct_type.compact_encrypted_amounts(), r)) - .collect::>()?, - commitments: read_raw_vec(read_point, outputs, r)?, - } + RctBase { + fee: read_varint(r)?, + // Only read pseudo_outs if they have yet to be moved to RctPrunable + // TODO: Shouldn't this be any Mlsag*? + pseudo_outs: if rct_type == RctType::MlsagBorromean { + read_raw_vec(read_point, inputs, r)? + } else { + vec![] + }, + encrypted_amounts: (0 .. outputs) + .map(|_| EncryptedAmount::read(rct_type.compact_encrypted_amounts(), r)) + .collect::>()?, + commitments: read_raw_vec(read_point, outputs, r)?, }, rct_type, )) @@ -217,20 +238,50 @@ impl RctBase { /// The prunable part of the RingCT data. #[derive(Clone, PartialEq, Eq, Debug)] pub enum RctPrunable { - /// Null. - Null, /// An aggregate MLSAG with Borromean range proofs. - AggregateMlsagBorromean { borromean: Vec, mlsag: Mlsag }, + AggregateMlsagBorromean { + /// The aggregate MLSAG ring signature. + mlsag: Mlsag, + /// The Borromean range proofs for each output. + borromean: Vec, + }, /// MLSAGs with Borromean range proofs. - MlsagBorromean { borromean: Vec, mlsags: Vec }, + MlsagBorromean { + /// The MLSAG ring signatures for each input. + mlsags: Vec, + /// The Borromean range proofs for each output. + borromean: Vec, + }, /// MLSAGs with Bulletproofs. MlsagBulletproofs { - bulletproofs: Bulletproof, + /// The MLSAG ring signatures for each input. mlsags: Vec, + /// The re-blinded commitments for the outputs being spent. pseudo_outs: Vec, + /// The aggregate Bulletproof, proving the outputs are within range. + bulletproof: Bulletproof, + }, + /// MLSAGs with Bulletproofs and compact encrypted amounts. + /// + /// This has an identical layout to MlsagBulletproofs and is interpreted the exact same way. It's + /// only differentiated to ensure discovery of the correct RctType. + MlsagBulletproofsCompactAmount { + /// The MLSAG ring signatures for each input. + mlsags: Vec, + /// The re-blinded commitments for the outputs being spent. + pseudo_outs: Vec, + /// The aggregate Bulletproof, proving the outputs are within range. + bulletproof: Bulletproof, }, /// CLSAGs with Bulletproofs(+). - Clsag { bulletproofs: Bulletproof, clsags: Vec, pseudo_outs: Vec }, + Clsag { + /// The CLSAGs for each input. + clsags: Vec, + /// The re-blinded commitments for the outputs being spent. + pseudo_outs: Vec, + /// The aggregate Bulletproof(+), proving the outputs are within range. + bulletproof: Bulletproof, + }, } impl RctPrunable { @@ -247,7 +298,6 @@ impl RctPrunable { /// Write the RctPrunable. pub fn write(&self, w: &mut W, rct_type: RctType) -> io::Result<()> { match self { - RctPrunable::Null => Ok(()), RctPrunable::AggregateMlsagBorromean { borromean, mlsag } => { write_raw_vec(BorromeanRange::write, borromean, w)?; mlsag.write(w) @@ -256,20 +306,21 @@ impl RctPrunable { write_raw_vec(BorromeanRange::write, borromean, w)?; write_raw_vec(Mlsag::write, mlsags, w) } - RctPrunable::MlsagBulletproofs { bulletproofs, mlsags, pseudo_outs } => { - if rct_type == RctType::Bulletproofs { + RctPrunable::MlsagBulletproofs { bulletproof, mlsags, pseudo_outs } | + RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, mlsags, pseudo_outs } => { + if rct_type == RctType::MlsagBulletproofs { w.write_all(&1u32.to_le_bytes())?; } else { w.write_all(&[1])?; } - bulletproofs.write(w)?; + bulletproof.write(w)?; write_raw_vec(Mlsag::write, mlsags, w)?; write_raw_vec(write_point, pseudo_outs, w) } - RctPrunable::Clsag { bulletproofs, clsags, pseudo_outs } => { + RctPrunable::Clsag { bulletproof, clsags, pseudo_outs } => { w.write_all(&[1])?; - bulletproofs.write(w)?; + bulletproof.write(w)?; write_raw_vec(Clsag::write, clsags, w)?; write_raw_vec(write_point, pseudo_outs, w) @@ -293,40 +344,46 @@ impl RctPrunable { r: &mut R, ) -> io::Result { Ok(match rct_type { - RctType::Null => RctPrunable::Null, - RctType::MlsagAggregate => RctPrunable::AggregateMlsagBorromean { + RctType::AggregateMlsagBorromean => RctPrunable::AggregateMlsagBorromean { borromean: read_raw_vec(BorromeanRange::read, outputs, r)?, mlsag: Mlsag::read(ring_length, inputs + 1, r)?, }, - RctType::MlsagIndividual => RctPrunable::MlsagBorromean { + RctType::MlsagBorromean => RctPrunable::MlsagBorromean { borromean: read_raw_vec(BorromeanRange::read, outputs, r)?, mlsags: (0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::>()?, }, - RctType::Bulletproofs | RctType::BulletproofsCompactAmount => { - RctPrunable::MlsagBulletproofs { - bulletproofs: { - if (if rct_type == RctType::Bulletproofs { - u64::from(read_u32(r)?) - } else { - read_varint(r)? - }) != 1 - { - Err(io::Error::other("n bulletproofs instead of one"))?; - } - Bulletproof::read(r)? - }, - mlsags: (0 .. inputs) - .map(|_| Mlsag::read(ring_length, 2, r)) - .collect::>()?, - pseudo_outs: read_raw_vec(read_point, inputs, r)?, + RctType::MlsagBulletproofs | RctType::MlsagBulletproofsCompactAmount => { + let bulletproof = { + if (if rct_type == RctType::MlsagBulletproofs { + u64::from(read_u32(r)?) + } else { + read_varint(r)? + }) != 1 + { + Err(io::Error::other("n bulletproofs instead of one"))?; + } + Bulletproof::read(r)? + }; + let mlsags = + (0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::>()?; + let pseudo_outs = read_raw_vec(read_point, inputs, r)?; + if rct_type == RctType::MlsagBulletproofs { + RctPrunable::MlsagBulletproofs { bulletproof, mlsags, pseudo_outs } + } else { + debug_assert_eq!(rct_type, RctType::MlsagBulletproofsCompactAmount); + RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, mlsags, pseudo_outs } } } - RctType::Clsag | RctType::BulletproofsPlus => RctPrunable::Clsag { - bulletproofs: { + RctType::ClsagBulletproof | RctType::ClsagBulletproofPlus => RctPrunable::Clsag { + bulletproof: { if read_varint::<_, u64>(r)? != 1 { Err(io::Error::other("n bulletproofs instead of one"))?; } - (if rct_type == RctType::Clsag { Bulletproof::read } else { Bulletproof::read_plus })(r)? + (if rct_type == RctType::ClsagBulletproof { + Bulletproof::read + } else { + Bulletproof::read_plus + })(r)? }, clsags: (0 .. inputs).map(|_| Clsag::read(ring_length, r)).collect::>()?, pseudo_outs: read_raw_vec(read_point, inputs, r)?, @@ -335,59 +392,51 @@ impl RctPrunable { } /// Write the RctPrunable as necessary for signing the signature. - /// - /// This function will return None if the object is `RctPrunable::Null` (and has no - /// representation here). - #[must_use] - pub(crate) fn signature_write(&self, w: &mut W) -> Option> { - Some(match self { - RctPrunable::Null => None?, + pub(crate) fn signature_write(&self, w: &mut W) -> io::Result<()> { + match self { RctPrunable::AggregateMlsagBorromean { borromean, .. } | RctPrunable::MlsagBorromean { borromean, .. } => { borromean.iter().try_for_each(|rs| rs.write(w)) } - RctPrunable::MlsagBulletproofs { bulletproofs, .. } | - RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w), - }) + RctPrunable::MlsagBulletproofs { bulletproof, .. } | + RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, .. } | + RctPrunable::Clsag { bulletproof, .. } => bulletproof.signature_write(w), + } } } +/// The RingCT proofs. +/// +/// This contains both the RctBase and RctPrunable structs. +/// +/// The C++ codebase refers to this as rct_signatures. #[derive(Clone, PartialEq, Eq, Debug)] -pub struct RctSignatures { +pub struct RctProofs { + /// The data necessary for handling this transaction. pub base: RctBase, + /// The data necessary for verifying this transaction. pub prunable: RctPrunable, } -impl RctSignatures { - /// RctType for a given RctSignatures struct. - /// - /// This is only guaranteed to return the type for a well-formed RctSignatures. For a malformed - /// RctSignatures, this will return either the presumed RctType (with no guarantee of compliance - /// with that type) or None. - #[must_use] - pub fn rct_type(&self) -> Option { - Some(match &self.prunable { - RctPrunable::Null => RctType::Null, - RctPrunable::AggregateMlsagBorromean { .. } => RctType::MlsagAggregate, - RctPrunable::MlsagBorromean { .. } => RctType::MlsagIndividual, - RctPrunable::MlsagBulletproofs { .. } => { - if matches!(self.base.encrypted_amounts.first()?, EncryptedAmount::Original { .. }) { - RctType::Bulletproofs +impl RctProofs { + /// RctType for a given RctProofs struct. + pub fn rct_type(&self) -> RctType { + match &self.prunable { + RctPrunable::AggregateMlsagBorromean { .. } => RctType::AggregateMlsagBorromean, + RctPrunable::MlsagBorromean { .. } => RctType::MlsagBorromean, + RctPrunable::MlsagBulletproofs { .. } => RctType::MlsagBulletproofs, + RctPrunable::MlsagBulletproofsCompactAmount { .. } => RctType::MlsagBulletproofsCompactAmount, + RctPrunable::Clsag { bulletproof, .. } => { + if matches!(bulletproof, Bulletproof::Original { .. }) { + RctType::ClsagBulletproof } else { - RctType::BulletproofsCompactAmount + RctType::ClsagBulletproofPlus } } - RctPrunable::Clsag { bulletproofs, .. } => { - if matches!(bulletproofs, Bulletproof::Original { .. }) { - RctType::Clsag - } else { - RctType::BulletproofsPlus - } - } - }) + } } - /// The weight of this RctSignatures as relevant for fees. + /// The weight of this RctProofs, as relevant for fees. pub fn fee_weight( bp_plus: bool, ring_len: usize, @@ -398,30 +447,29 @@ impl RctSignatures { RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(bp_plus, ring_len, inputs, outputs) } - #[must_use] - pub fn write(&self, w: &mut W) -> Option> { - let rct_type = self.rct_type()?; - if let Err(e) = self.base.write(w, rct_type) { - return Some(Err(e)); - }; - Some(self.prunable.write(w, rct_type)) + /// Write the RctProofs. + pub fn write(&self, w: &mut W) -> io::Result<()> { + let rct_type = self.rct_type(); + self.base.write(w, rct_type)?; + self.prunable.write(w, rct_type) } - #[must_use] - pub fn serialize(&self) -> Option> { + /// Serialize the RctProofs to a Vec. + pub fn serialize(&self) -> Vec { let mut serialized = vec![]; - self.write(&mut serialized)?.unwrap(); - Some(serialized) + self.write(&mut serialized).unwrap(); + serialized } + /// Read a RctProofs. pub fn read( ring_length: usize, inputs: usize, outputs: usize, r: &mut R, - ) -> io::Result { + ) -> io::Result { let base = RctBase::read(inputs, outputs, r)?; - Ok(RctSignatures { + Ok(RctProofs { base: base.0, prunable: RctPrunable::read(base.1, ring_length, inputs, outputs, r)?, }) diff --git a/coins/monero/src/transaction.rs b/coins/monero/src/transaction.rs index ae194906..3bbdbfa3 100644 --- a/coins/monero/src/transaction.rs +++ b/coins/monero/src/transaction.rs @@ -12,22 +12,34 @@ use crate::{ io::*, primitives::keccak256, ring_signatures::RingSignature, - ringct::{bulletproofs::Bulletproof, RctType, RctBase, RctPrunable, RctSignatures}, + ringct::{bulletproofs::Bulletproof, RctProofs}, }; +/// An input in the Monero protocol. #[derive(Clone, PartialEq, Eq, Debug)] pub enum Input { + /// An input for a miner transaction, which is generating new coins. Gen(u64), - ToKey { amount: Option, key_offsets: Vec, key_image: EdwardsPoint }, + /// An input spending an output on-chain. + ToKey { + /// The pool this input spends an output of. + amount: Option, + /// The decoys used by this input's ring, specified as their offset distance from each other. + key_offsets: Vec, + /// The key image (linking tag, nullifer) for the spent output. + key_image: EdwardsPoint, + }, } impl Input { + /// The weight of this Input, as relevant for fees. pub fn fee_weight(offsets_weight: usize) -> usize { // Uses 1 byte for the input type // Uses 1 byte for the VarInt amount due to amount being 0 1 + 1 + offsets_weight + 32 } + /// Write the Input. pub fn write(&self, w: &mut W) -> io::Result<()> { match self { Input::Gen(height) => { @@ -44,12 +56,14 @@ impl Input { } } + /// Serialize the Input to a Vec. pub fn serialize(&self) -> Vec { let mut res = vec![]; self.write(&mut res).unwrap(); res } + /// Read an Input. pub fn read(r: &mut R) -> io::Result { Ok(match read_byte(r)? { 255 => Input::Gen(read_varint(r)?), @@ -72,21 +86,26 @@ impl Input { } } -// Doesn't bother moving to an enum for the unused Script classes +/// An output in the Monero protocol. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Output { + /// The pool this output should be sorted into. pub amount: Option, + /// The key which can spend this output. pub key: CompressedEdwardsY, + /// The view tag for this output, as used to accelerate scanning. pub view_tag: Option, } impl Output { + /// The weight of this Output, as relevant for fees. pub fn fee_weight(view_tags: bool) -> usize { // Uses 1 byte for the output type // Uses 1 byte for the VarInt amount due to amount being 0 1 + 1 + 32 + if view_tags { 1 } else { 0 } } + /// Write the Output. pub fn write(&self, w: &mut W) -> io::Result<()> { write_varint(&self.amount.unwrap_or(0), w)?; w.write_all(&[2 + u8::from(self.view_tag.is_some())])?; @@ -97,12 +116,14 @@ impl Output { Ok(()) } + /// Write the Output to a Vec. pub fn serialize(&self) -> Vec { let mut res = Vec::with_capacity(8 + 1 + 32); self.write(&mut res).unwrap(); res } + /// Read an Output. pub fn read(rct: bool, r: &mut R) -> io::Result { let amount = read_varint(r)?; let amount = if rct { @@ -128,10 +149,17 @@ impl Output { } } +/// An additional timelock for a Monero transaction. +/// +/// Monero outputs are locked by a default timelock. If a timelock is explicitly specified, the +/// longer of the two will be the timelock used. #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] pub enum Timelock { + /// No timelock. None, + /// Locked until this block. Block(usize), + /// Locked until this many seconds since the epoch. Time(u64), } @@ -173,16 +201,27 @@ impl PartialOrd for Timelock { } } +/// The transaction prefix. +/// +/// This is common to all transaction versions and contains most parts of the transaction needed to +/// handle it. It excludes any proofs. #[derive(Clone, PartialEq, Eq, Debug)] pub struct TransactionPrefix { - pub version: u64, + /// The timelock this transaction uses. pub timelock: Timelock, + /// The inputs for this transaction. pub inputs: Vec, + /// The outputs for this transaction. pub outputs: Vec, + /// The additional data included within the transaction. + /// + /// This is an arbitrary data field, yet is used by wallets for containing the data necessary to + /// scan the transaction. pub extra: Vec, } impl TransactionPrefix { + /// The weight of this TransactionPrefix, as relevant for fees. pub fn fee_weight( decoy_weights: &[usize], outputs: usize, @@ -191,8 +230,7 @@ impl TransactionPrefix { ) -> usize { // Assumes Timelock::None since this library won't let you create a TX with a timelock // 1 input for every decoy weight - 1 + 1 + - varint_len(decoy_weights.len()) + + 1 + varint_len(decoy_weights.len()) + decoy_weights.iter().map(|&offsets_weight| Input::fee_weight(offsets_weight)).sum::() + varint_len(outputs) + (outputs * Output::fee_weight(view_tags)) + @@ -200,8 +238,10 @@ impl TransactionPrefix { extra } - pub fn write(&self, w: &mut W) -> io::Result<()> { - write_varint(&self.version, w)?; + /// Write a TransactionPrefix. + /// + /// This is distinct from Monero in that it won't write any version. + fn write(&self, w: &mut W) -> io::Result<()> { self.timelock.write(w)?; write_vec(Input::write, &self.inputs, w)?; write_vec(Output::write, &self.outputs, w)?; @@ -209,19 +249,11 @@ impl TransactionPrefix { w.write_all(&self.extra) } - pub fn serialize(&self) -> Vec { - let mut res = vec![]; - self.write(&mut res).unwrap(); - res - } - - pub fn read(r: &mut R) -> io::Result { - let version = read_varint(r)?; - // TODO: Create an enum out of version - if (version == 0) || (version > 2) { - Err(io::Error::other("unrecognized transaction version"))?; - } - + /// Read a TransactionPrefix. + /// + /// This is distinct from Monero in that it won't read the version. The version must be passed + /// in. + pub fn read(r: &mut R, version: u64) -> io::Result { let timelock = Timelock::from_raw(read_varint(r)?); let inputs = read_vec(|r| Input::read(r), r)?; @@ -231,7 +263,6 @@ impl TransactionPrefix { let is_miner_tx = matches!(inputs[0], Input::Gen { .. }); let mut prefix = TransactionPrefix { - version, timelock, inputs, outputs: read_vec(|r| Output::read((!is_miner_tx) && (version == 2), r), r)?, @@ -241,20 +272,36 @@ impl TransactionPrefix { Ok(prefix) } - pub fn hash(&self) -> [u8; 32] { - keccak256(self.serialize()) + fn hash(&self, version: u64) -> [u8; 32] { + let mut buf = vec![]; + write_varint(&version, &mut buf).unwrap(); + self.write(&mut buf).unwrap(); + keccak256(buf) } } -/// Monero transaction. For version 1, rct_signatures still contains an accurate fee value. +/// A Monero transaction. +#[allow(clippy::large_enum_variant)] #[derive(Clone, PartialEq, Eq, Debug)] -pub struct Transaction { - pub prefix: TransactionPrefix, - pub signatures: Vec, - pub rct_signatures: RctSignatures, +pub enum Transaction { + /// A version 1 transaction, used by the original Cryptonote codebase. + V1 { + /// The transaction's prefix. + prefix: TransactionPrefix, + /// The transaction's ring signatures. + signatures: Vec, + }, + /// A version 2 transaction, used by the RingCT protocol. + V2 { + /// The transaction's prefix. + prefix: TransactionPrefix, + /// The transaction's proofs. + proofs: Option, + }, } impl Transaction { + /// The weight of this Transaction, as relevant for fees. // TODO: Replace ring_len, decoy_weights for &[&[usize]], where the inner buf is the decoy // offsets pub fn fee_weight( @@ -266,88 +313,58 @@ impl Transaction { extra: usize, fee: u64, ) -> usize { - TransactionPrefix::fee_weight(decoy_weights, outputs, view_tags, extra) + - RctSignatures::fee_weight(bp_plus, ring_len, decoy_weights.len(), outputs, fee) + 1 + TransactionPrefix::fee_weight(decoy_weights, outputs, view_tags, extra) + + RctProofs::fee_weight(bp_plus, ring_len, decoy_weights.len(), outputs, fee) } - #[must_use] - pub fn write(&self, w: &mut W) -> Option> { - if let Err(e) = self.prefix.write(w) { - return Some(Err(e)); - }; - if self.prefix.version == 1 { - for ring_sig in &self.signatures { - if let Err(e) = ring_sig.write(w) { - return Some(Err(e)); - }; + /// Write the Transaction. + pub fn write(&self, w: &mut W) -> io::Result<()> { + match self { + Transaction::V1 { prefix, signatures } => { + write_varint(&1u8, w)?; + prefix.write(w)?; + for ring_sig in signatures { + ring_sig.write(w)?; + } } - Some(Ok(())) - } else if self.prefix.version == 2 { - if let Err(e) = self.rct_signatures.write(w)? { - return Some(Err(e)); + Transaction::V2 { prefix, proofs } => { + write_varint(&2u8, w)?; + prefix.write(w)?; + match proofs { + None => w.write_all(&[0])?, + Some(proofs) => proofs.write(w)?, + } } - Some(Ok(())) - } else { - Some(Err(io::Error::other("transaction had an unknown version"))) } + Ok(()) } - #[must_use] - pub fn serialize(&self) -> Option> { + /// Write the Transaction to a Vec. + pub fn serialize(&self) -> Vec { let mut res = Vec::with_capacity(2048); - self.write(&mut res)?.unwrap(); - Some(res) + self.write(&mut res).unwrap(); + res } + /// Read a Transaction. pub fn read(r: &mut R) -> io::Result { - let prefix = TransactionPrefix::read(r)?; - let mut signatures = vec![]; - let mut rct_signatures = RctSignatures { - base: RctBase { fee: 0, encrypted_amounts: vec![], pseudo_outs: vec![], commitments: vec![] }, - prunable: RctPrunable::Null, - }; + let version = read_varint(r)?; + let prefix = TransactionPrefix::read(r, version)?; - if prefix.version == 1 { - signatures = prefix + if version == 1 { + let signatures = prefix .inputs .iter() .filter_map(|input| match input { + // TODO: This allows mixing Gen and ToKey, which is likely undefined behavior? Input::ToKey { key_offsets, .. } => Some(RingSignature::read(key_offsets.len(), r)), _ => None, }) .collect::>()?; - if !matches!(prefix.inputs[0], Input::Gen(..)) { - let in_amount = prefix - .inputs - .iter() - .map(|input| match input { - Input::Gen(..) => Err(io::Error::other("Input::Gen present in non-coinbase v1 TX"))?, - // v1 TXs can burn v2 outputs - // dcff3fe4f914d6b6bd4a5b800cc4cca8f2fdd1bd73352f0700d463d36812f328 is one such TX - // It includes a pre-RCT signature for a RCT output, yet if you interpret the RCT - // output as being worth 0, it passes a sum check (guaranteed since no outputs are RCT) - Input::ToKey { amount, .. } => Ok(amount.unwrap_or(0)), - }) - .collect::>>()? - .into_iter() - .sum::(); - - let mut out = 0; - for output in &prefix.outputs { - if output.amount.is_none() { - Err(io::Error::other("v1 transaction had a 0-amount output"))?; - } - out += output.amount.unwrap(); - } - - if in_amount < out { - Err(io::Error::other("transaction spent more than it had as inputs"))?; - } - rct_signatures.base.fee = in_amount - out; - } - } else if prefix.version == 2 { - rct_signatures = RctSignatures::read( + Ok(Transaction::V1 { prefix, signatures }) + } else if version == 2 { + let proofs = Some(RctProofs::read( prefix.inputs.first().map_or(0, |input| match input { Input::Gen(_) => 0, Input::ToKey { key_offsets, .. } => key_offsets.len(), @@ -355,98 +372,109 @@ impl Transaction { prefix.inputs.len(), prefix.outputs.len(), r, - )?; - } else { - Err(io::Error::other("Tried to deserialize unknown version"))?; - } + )?); - Ok(Transaction { prefix, signatures, rct_signatures }) + Ok(Transaction::V2 { prefix, proofs }) + } else { + Err(io::Error::other("tried to deserialize unknown version")) + } } - #[must_use] - pub fn hash(&self) -> Option<[u8; 32]> { + /// The hash of the transaction. + pub fn hash(&self) -> [u8; 32] { let mut buf = Vec::with_capacity(2048); - if self.prefix.version == 1 { - self.write(&mut buf)?.unwrap(); - Some(keccak256(buf)) - } else { - let mut hashes = Vec::with_capacity(96); + match self { + Transaction::V1 { .. } => { + self.write(&mut buf).unwrap(); + keccak256(buf) + } + Transaction::V2 { prefix, proofs } => { + let mut hashes = Vec::with_capacity(96); - hashes.extend(self.prefix.hash()); + hashes.extend(prefix.hash(2)); - let rct_type = self.rct_signatures.rct_type()?; - self.rct_signatures.base.write(&mut buf, rct_type).unwrap(); - hashes.extend(keccak256(&buf)); - buf.clear(); + if let Some(proofs) = proofs { + let rct_type = proofs.rct_type(); + proofs.base.write(&mut buf, rct_type).unwrap(); + hashes.extend(keccak256(&buf)); + buf.clear(); - hashes.extend(&match self.rct_signatures.prunable { - RctPrunable::Null => [0; 32], - _ => { - self.rct_signatures.prunable.write(&mut buf, rct_type).unwrap(); - keccak256(buf) + proofs.prunable.write(&mut buf, rct_type).unwrap(); + hashes.extend(keccak256(buf)); + } else { + // Serialization of RctBase::Null + hashes.extend(keccak256([0])); + hashes.extend([0; 32]); } - }); - Some(keccak256(hashes)) + keccak256(hashes) + } } } /// Calculate the hash of this transaction as needed for signing it. - #[must_use] + /// + /// This returns None if the transaction is without signatures. pub fn signature_hash(&self) -> Option<[u8; 32]> { - if self.prefix.version == 1 { - return Some(self.prefix.hash()); + match self { + Transaction::V1 { prefix, .. } => Some(prefix.hash(1)), + Transaction::V2 { prefix, proofs } => { + let mut buf = Vec::with_capacity(2048); + let mut sig_hash = Vec::with_capacity(96); + + sig_hash.extend(prefix.hash(2)); + + let proofs = proofs.as_ref()?; + proofs.base.write(&mut buf, proofs.rct_type()).unwrap(); + sig_hash.extend(keccak256(&buf)); + buf.clear(); + + proofs.prunable.signature_write(&mut buf).unwrap(); + sig_hash.extend(keccak256(buf)); + + Some(keccak256(sig_hash)) + } } - - let mut buf = Vec::with_capacity(2048); - let mut sig_hash = Vec::with_capacity(96); - - sig_hash.extend(self.prefix.hash()); - - self.rct_signatures.base.write(&mut buf, self.rct_signatures.rct_type()?).unwrap(); - sig_hash.extend(keccak256(&buf)); - buf.clear(); - - self.rct_signatures.prunable.signature_write(&mut buf)?.unwrap(); - sig_hash.extend(keccak256(buf)); - - Some(keccak256(sig_hash)) } fn is_rct_bulletproof(&self) -> bool { - let Some(rct_type) = self.rct_signatures.rct_type() else { return false }; - match rct_type { - RctType::Bulletproofs | RctType::BulletproofsCompactAmount | RctType::Clsag => true, - RctType::Null | - RctType::MlsagAggregate | - RctType::MlsagIndividual | - RctType::BulletproofsPlus => false, + match self { + Transaction::V1 { .. } => false, + Transaction::V2 { proofs, .. } => { + let Some(proofs) = proofs else { return false }; + proofs.rct_type().bulletproof() + } } } fn is_rct_bulletproof_plus(&self) -> bool { - let Some(rct_type) = self.rct_signatures.rct_type() else { return false }; - match rct_type { - RctType::BulletproofsPlus => true, - RctType::Null | - RctType::MlsagAggregate | - RctType::MlsagIndividual | - RctType::Bulletproofs | - RctType::BulletproofsCompactAmount | - RctType::Clsag => false, + match self { + Transaction::V1 { .. } => false, + Transaction::V2 { proofs, .. } => { + let Some(proofs) = proofs else { return false }; + proofs.rct_type().bulletproof_plus() + } } } /// Calculate the transaction's weight. - pub fn weight(&self) -> Option { - let blob_size = self.serialize()?.len(); + pub fn weight(&self) -> usize { + let blob_size = self.serialize().len(); let bp = self.is_rct_bulletproof(); let bp_plus = self.is_rct_bulletproof_plus(); - Some(if !(bp || bp_plus) { + if !(bp || bp_plus) { blob_size } else { - blob_size + Bulletproof::calculate_bp_clawback(bp_plus, self.prefix.outputs.len()).0 - }) + blob_size + + Bulletproof::calculate_bp_clawback( + bp_plus, + match self { + Transaction::V1 { .. } => panic!("v1 transaction was BP(+)"), + Transaction::V2 { prefix, .. } => prefix.outputs.len(), + }, + ) + .0 + } } } diff --git a/coins/monero/wallet/src/scan.rs b/coins/monero/wallet/src/scan.rs index 2f4e0eb3..d816be8b 100644 --- a/coins/monero/wallet/src/scan.rs +++ b/coins/monero/wallet/src/scan.rs @@ -419,7 +419,7 @@ impl Scanner { commitment.amount = amount; // Regular transaction } else { - commitment = match tx.rct_signatures.base.encrypted_amounts.get(o) { + commitment = match tx.proofs.base.encrypted_amounts.get(o) { Some(amount) => amount.decrypt(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 @@ -428,7 +428,7 @@ impl Scanner { // If this is a malicious commitment, move to the next output // Any other R value will calculate to a different spend key and are therefore ignorable - if Some(&commitment.calculate()) != tx.rct_signatures.base.commitments.get(o) { + if Some(&commitment.calculate()) != tx.proofs.base.commitments.get(o) { break; } } diff --git a/coins/monero/wallet/src/send/mod.rs b/coins/monero/wallet/src/send/mod.rs index 6ae42e30..24dca949 100644 --- a/coins/monero/wallet/src/send/mod.rs +++ b/coins/monero/wallet/src/send/mod.rs @@ -30,7 +30,7 @@ use monero_serai::{ hash_to_point, clsag::{ClsagError, ClsagContext, Clsag}, bulletproofs::{MAX_COMMITMENTS, Bulletproof}, - RctBase, RctPrunable, RctSignatures, + RctBase, RctPrunable, RctProofs, }, transaction::{Input, Output, Timelock, TransactionPrefix, Transaction}, }; @@ -809,7 +809,7 @@ impl SignableTransaction { extra, }, signatures: vec![], - rct_signatures: RctSignatures { + proofs: RctProofs { base: RctBase { fee, encrypted_amounts, @@ -855,7 +855,7 @@ impl SignableTransaction { let clsag_pairs = Clsag::sign(rng, signable, mask_sum, tx.signature_hash()) .map_err(|_| TransactionError::WrongPrivateKey)?; - match tx.rct_signatures.prunable { + match tx.proofs.prunable { RctPrunable::Null => panic!("Signing for RctPrunable::Null"), RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. } => { clsags.append(&mut clsag_pairs.iter().map(|clsag| clsag.0.clone()).collect::>()); @@ -867,7 +867,7 @@ impl SignableTransaction { if self.has_change { debug_assert_eq!( self.fee_rate.calculate_fee_from_weight(tx.weight()), - tx.rct_signatures.base.fee, + tx.proofs.base.fee, "transaction used unexpected fee", ); } @@ -917,7 +917,7 @@ impl Eventuality { uniqueness(&tx.prefix.inputs), ); - let rct_type = tx.rct_signatures.rct_type(); + let rct_type = tx.proofs.rct_type(); if rct_type != self.protocol.optimal_rct_type() { return false; } @@ -935,9 +935,9 @@ impl Eventuality { key: expected.dest.compress(), view_tag: Some(expected.view_tag).filter(|_| self.protocol.view_tags()), } != actual) || - (Some(&expected.commitment.calculate()) != tx.rct_signatures.base.commitments.get(o)) || + (Some(&expected.commitment.calculate()) != tx.proofs.base.commitments.get(o)) || (Some(&EncryptedAmount::Compact { amount: expected.amount }) != - tx.rct_signatures.base.encrypted_amounts.get(o)) + tx.proofs.base.encrypted_amounts.get(o)) { return false; } diff --git a/coins/monero/wallet/src/send/multisig.rs b/coins/monero/wallet/src/send/multisig.rs index 3cc8e9bb..b3a5a493 100644 --- a/coins/monero/wallet/src/send/multisig.rs +++ b/coins/monero/wallet/src/send/multisig.rs @@ -390,7 +390,7 @@ impl SignatureMachine for TransactionSignatureMachine { shares: HashMap, ) -> Result { let mut tx = self.tx; - match tx.rct_signatures.prunable { + match tx.proofs.prunable { RctPrunable::Null => panic!("Signing for RctPrunable::Null"), RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. } => { for (c, clsag) in self.clsags.drain(..).enumerate() { diff --git a/coins/monero/wallet/tests/eventuality.rs b/coins/monero/wallet/tests/eventuality.rs index 14f255e5..d73c2665 100644 --- a/coins/monero/wallet/tests/eventuality.rs +++ b/coins/monero/wallet/tests/eventuality.rs @@ -70,7 +70,7 @@ test!( assert!(eventuality.matches(&tx)); // Mutate the TX - tx.rct_signatures.base.commitments[0] += ED25519_BASEPOINT_POINT; + tx.proofs.base.commitments[0] += ED25519_BASEPOINT_POINT; // Verify it no longer matches assert!(!eventuality.matches(&tx)); }, diff --git a/coins/monero/wallet/tests/runner.rs b/coins/monero/wallet/tests/runner.rs index 11dcf797..bb0eeea4 100644 --- a/coins/monero/wallet/tests/runner.rs +++ b/coins/monero/wallet/tests/runner.rs @@ -80,7 +80,7 @@ pub async fn get_miner_tx_output(rpc: &SimpleRequestRpc, view: &ViewPair) -> Spe /// Make sure the weight and fee match the expected calculation. pub fn check_weight_and_fee(tx: &Transaction, fee_rate: FeeRate) { - let fee = tx.rct_signatures.base.fee; + let fee = tx.proofs.base.fee; let weight = tx.weight(); let expected_weight = fee_rate.calculate_weight_from_fee(fee); diff --git a/coins/monero/wallet/tests/send.rs b/coins/monero/wallet/tests/send.rs index c6885398..51c5f081 100644 --- a/coins/monero/wallet/tests/send.rs +++ b/coins/monero/wallet/tests/send.rs @@ -306,7 +306,7 @@ test!( assert_eq!(outputs[1].commitment().amount, 50000); // The remainder should get shunted to fee, which is fingerprintable - assert_eq!(tx.rct_signatures.base.fee, 1000000000000 - 10000 - 50000); + assert_eq!(tx.proofs.base.fee, 1000000000000 - 10000 - 50000); }, ), );