diff --git a/coins/monero/primitives/src/lib.rs b/coins/monero/primitives/src/lib.rs index 993cb8b2..6ecb47d0 100644 --- a/coins/monero/primitives/src/lib.rs +++ b/coins/monero/primitives/src/lib.rs @@ -3,7 +3,7 @@ #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use std_shims::vec::Vec; +use std_shims::{io, vec::Vec}; #[cfg(feature = "std")] use std_shims::sync::OnceLock; @@ -17,6 +17,7 @@ use curve25519_dalek::{ edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}, }; +use monero_io::*; use monero_generators::H; mod unreduced_scalar; @@ -166,4 +167,34 @@ impl Decoys { pub fn signer_ring_members(&self) -> [EdwardsPoint; 2] { self.ring[usize::from(self.signer_index)] } + + /// Write the Decoys. + pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> { + write_vec(write_varint, &self.offsets, w)?; + w.write_all(&[self.signer_index])?; + write_vec( + |pair, w| { + write_point(&pair[0], w)?; + write_point(&pair[1], w) + }, + &self.ring, + w, + ) + } + /// Serialize the Decoys to a `Vec`. + pub fn serialize(&self) -> Vec { + let mut res = + Vec::with_capacity((1 + (2 * self.offsets.len())) + 1 + 1 + (self.ring.len() * 64)); + self.write(&mut res).unwrap(); + res + } + /// Read a set of Decoys. + pub fn read(r: &mut impl io::Read) -> io::Result { + Decoys::new( + read_vec(read_varint, r)?, + read_byte(r)?, + read_vec(|r| Ok([read_point(r)?, read_point(r)?]), r)?, + ) + .ok_or_else(|| io::Error::other("invalid Decoys")) + } } diff --git a/coins/monero/ringct/bulletproofs/src/lib.rs b/coins/monero/ringct/bulletproofs/src/lib.rs index 5881efa8..d6e47d75 100644 --- a/coins/monero/ringct/bulletproofs/src/lib.rs +++ b/coins/monero/ringct/bulletproofs/src/lib.rs @@ -97,13 +97,6 @@ impl Bulletproof { (bp_clawback, LR_len) } - /// Calculate the weight of this proof. - pub fn fee_weight(plus: bool, outputs: usize) -> usize { - #[allow(non_snake_case)] - let (bp_clawback, LR_len) = Bulletproof::calculate_bp_clawback(plus, outputs); - 32 * (Bulletproof::bp_fields(plus) + (2 * LR_len)) + 2 + bp_clawback - } - /// Prove the list of commitments are within [0 .. 2^64) with an aggregate Bulletproof. pub fn prove( rng: &mut R, diff --git a/coins/monero/ringct/clsag/src/lib.rs b/coins/monero/ringct/clsag/src/lib.rs index 10ad7588..0aab537b 100644 --- a/coins/monero/ringct/clsag/src/lib.rs +++ b/coins/monero/ringct/clsag/src/lib.rs @@ -386,11 +386,6 @@ impl Clsag { Ok(()) } - /// The length a CLSAG will take once serialized. - pub fn fee_weight(ring_len: usize) -> usize { - (ring_len * 32) + 32 + 32 - } - /// Write a CLSAG. pub fn write(&self, w: &mut W) -> io::Result<()> { write_raw_vec(write_scalar, &self.s, w)?; diff --git a/coins/monero/rpc/src/lib.rs b/coins/monero/rpc/src/lib.rs index 94fed5e4..66d6ab26 100644 --- a/coins/monero/rpc/src/lib.rs +++ b/coins/monero/rpc/src/lib.rs @@ -52,6 +52,24 @@ impl FeeRate { Ok(FeeRate { per_weight, mask }) } + /// Write the FeeRate. + pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> { + w.write_all(&self.per_weight.to_le_bytes())?; + w.write_all(&self.mask.to_le_bytes()) + } + + /// Serialize the FeeRate to a `Vec`. + pub fn serialize(&self) -> Vec { + let mut res = Vec::with_capacity(16); + self.write(&mut res).unwrap(); + res + } + + /// Read a FeeRate. + pub fn read(r: &mut impl io::Read) -> io::Result { + Ok(FeeRate { per_weight: read_u64(r)?, mask: read_u64(r)? }) + } + /// Calculate the fee to use from the weight. /// /// This function may panic if any of the `FeeRate`'s fields are zero. diff --git a/coins/monero/src/ringct.rs b/coins/monero/src/ringct.rs index d564fe9f..63159148 100644 --- a/coins/monero/src/ringct.rs +++ b/coins/monero/src/ringct.rs @@ -173,12 +173,6 @@ pub struct RctBase { } impl RctBase { - /// The weight of this RctBase as relevant for fees. - pub fn fee_weight(outputs: usize, fee: u64) -> usize { - // 1 byte for the RCT signature type - 1 + (outputs * (8 + 32)) + varint_len(fee) - } - /// Write the RctBase. pub fn write(&self, w: &mut W, rct_type: RctType) -> io::Result<()> { w.write_all(&[u8::from(rct_type)])?; @@ -295,16 +289,6 @@ pub enum RctPrunable { } impl RctPrunable { - /// The weight of this RctPrunable as relevant for fees. - #[rustfmt::skip] - pub fn fee_weight(bp_plus: bool, ring_len: usize, inputs: usize, outputs: usize) -> usize { - // 1 byte for number of BPs (technically a VarInt, yet there's always just zero or one) - 1 + - Bulletproof::fee_weight(bp_plus, outputs) + - // There's both the CLSAG and the pseudo-out - (inputs * (Clsag::fee_weight(ring_len) + 32)) - } - /// Write the RctPrunable. pub fn write(&self, w: &mut W, rct_type: RctType) -> io::Result<()> { match self { @@ -446,17 +430,6 @@ impl RctProofs { } } - /// The weight of this RctProofs, as relevant for fees. - pub fn fee_weight( - bp_plus: bool, - ring_len: usize, - inputs: usize, - outputs: usize, - fee: u64, - ) -> usize { - RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(bp_plus, ring_len, inputs, outputs) - } - /// Write the RctProofs. pub fn write(&self, w: &mut W) -> io::Result<()> { let rct_type = self.rct_type(); diff --git a/coins/monero/src/transaction.rs b/coins/monero/src/transaction.rs index 9d683b65..4456b126 100644 --- a/coins/monero/src/transaction.rs +++ b/coins/monero/src/transaction.rs @@ -32,13 +32,6 @@ pub enum Input { } 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 { @@ -98,13 +91,6 @@ pub struct Output { } 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)?; @@ -221,23 +207,6 @@ pub struct TransactionPrefix { } impl TransactionPrefix { - /// The weight of this TransactionPrefix, as relevant for fees. - pub fn fee_weight( - decoy_weights: &[usize], - outputs: usize, - view_tags: bool, - extra: usize, - ) -> usize { - // Assumes Timelock::None since this library won't let you create a TX with a timelock - // 1 input for every decoy weight - 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)) + - varint_len(extra) + - extra - } - /// Write a TransactionPrefix. /// /// This is distinct from Monero in that it won't write any version. @@ -323,22 +292,6 @@ 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( - view_tags: bool, - bp_plus: bool, - ring_len: usize, - decoy_weights: &[usize], - outputs: usize, - extra: usize, - fee: u64, - ) -> usize { - 1 + TransactionPrefix::fee_weight(decoy_weights, outputs, view_tags, extra) + - RctProofs::fee_weight(bp_plus, ring_len, decoy_weights.len(), outputs, fee) - } - /// Write the Transaction. /// /// Some writable transactions may not be readable if they're malformed, per Monero's consensus diff --git a/coins/monero/wallet/Cargo.toml b/coins/monero/wallet/Cargo.toml index 4aa7dd6e..4e308886 100644 --- a/coins/monero/wallet/Cargo.toml +++ b/coins/monero/wallet/Cargo.toml @@ -30,11 +30,11 @@ rand_chacha = { version = "0.3", default-features = false } # Used to select decoys rand_distr = { version = "0.4", default-features = false } -group = { version = "0.13", default-features = false } curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize", "group"] } # Multisig dependencies transcript = { package = "flexible-transcript", path = "../../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], optional = true } +group = { version = "0.13", default-features = false, optional = true } dalek-ff-group = { path = "../../../crypto/dalek-ff-group", version = "0.4", default-features = false, optional = true } frost = { package = "modular-frost", path = "../../../crypto/frost", default-features = false, features = ["ed25519"], optional = true } @@ -72,5 +72,5 @@ std = [ "monero-address/std", ] compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-serai/compile-time-generators"] -multisig = ["transcript", "dalek-ff-group", "frost", "monero-serai/multisig", "std"] +multisig = ["transcript", "group", "dalek-ff-group", "frost", "monero-serai/multisig", "std"] default = ["std", "compile-time-generators"] diff --git a/coins/monero/wallet/src/extra.rs b/coins/monero/wallet/src/extra.rs index 74b0d8c5..79471b84 100644 --- a/coins/monero/wallet/src/extra.rs +++ b/coins/monero/wallet/src/extra.rs @@ -208,23 +208,6 @@ impl Extra { self.0.push(field); } - #[rustfmt::skip] - pub fn fee_weight( - outputs: usize, - additional: bool, - payment_id: bool, - data: &[Vec] - ) -> usize { - // PublicKey, key - (1 + 32) + - // PublicKeys, length, additional keys - (if additional { 1 + 1 + (outputs * 32) } else { 0 }) + - // PaymentId (Nonce), length, encrypted, ID - (if payment_id { 1 + 1 + 1 + 8 } else { 0 }) + - // Nonce, length, ARBITRARY_DATA_MARKER, data - data.iter().map(|v| 1 + varint_len(1 + v.len()) + 1 + v.len()).sum::() - } - pub fn write(&self, w: &mut W) -> io::Result<()> { for field in &self.0 { field.write(w)?; diff --git a/coins/monero/wallet/src/send/eventuality.rs b/coins/monero/wallet/src/send/eventuality.rs index ec408815..4cc11a5a 100644 --- a/coins/monero/wallet/src/send/eventuality.rs +++ b/coins/monero/wallet/src/send/eventuality.rs @@ -1,3 +1,5 @@ +use std_shims::io; + use zeroize::Zeroize; use crate::{ @@ -94,85 +96,15 @@ impl Eventuality { true } - /* pub fn write(&self, w: &mut W) -> io::Result<()> { - self.protocol.write(w)?; - write_raw_vec(write_byte, self.r_seed.as_ref(), w)?; - write_vec(write_point, &self.inputs, w)?; - - fn write_payment(payment: &InternalPayment, w: &mut W) -> io::Result<()> { - match payment { - InternalPayment::Payment(payment, need_dummy_payment_id) => { - w.write_all(&[0])?; - write_vec(write_byte, payment.0.to_string().as_bytes(), w)?; - w.write_all(&payment.1.to_le_bytes())?; - if *need_dummy_payment_id { - w.write_all(&[1]) - } else { - w.write_all(&[0]) - } - } - InternalPayment::Change(change, change_view) => { - w.write_all(&[1])?; - write_vec(write_byte, change.0.to_string().as_bytes(), w)?; - w.write_all(&change.1.to_le_bytes())?; - if let Some(view) = change_view.as_ref() { - w.write_all(&[1])?; - write_scalar(view, w) - } else { - w.write_all(&[0]) - } - } - } - } - write_vec(write_payment, &self.payments, w)?; - - write_vec(write_byte, &self.extra, w) + self.0.write(w) } pub fn serialize(&self) -> Vec { - let mut buf = Vec::with_capacity(128); - self.write(&mut buf).unwrap(); - buf + self.0.serialize() } pub fn read(r: &mut R) -> io::Result { - fn read_address(r: &mut R) -> io::Result { - String::from_utf8(read_vec(read_byte, r)?) - .ok() - .and_then(|str| MoneroAddress::from_str_raw(&str).ok()) - .ok_or_else(|| io::Error::other("invalid address")) - } - - fn read_payment(r: &mut R) -> io::Result { - Ok(match read_byte(r)? { - 0 => InternalPayment::Payment( - (read_address(r)?, read_u64(r)?), - match read_byte(r)? { - 0 => false, - 1 => true, - _ => Err(io::Error::other("invalid need additional"))?, - }, - ), - 1 => InternalPayment::Change( - (read_address(r)?, read_u64(r)?), - match read_byte(r)? { - 0 => None, - 1 => Some(Zeroizing::new(read_scalar(r)?)), - _ => Err(io::Error::other("invalid change view"))?, - }, - ), - _ => Err(io::Error::other("invalid payment"))?, - }) - } - - Ok(Eventuality { - protocol: RctType::read(r)?, - r_seed: Zeroizing::new(read_bytes::<_, 32>(r)?), - inputs: read_vec(read_point, r)?, - payments: read_vec(read_payment, r)?, - extra: read_vec(read_byte, r)?, - }) + Ok(Eventuality(SignableTransaction::read(r)?)) } - */ } diff --git a/coins/monero/wallet/src/send/mod.rs b/coins/monero/wallet/src/send/mod.rs index 21dab804..27d5a24e 100644 --- a/coins/monero/wallet/src/send/mod.rs +++ b/coins/monero/wallet/src/send/mod.rs @@ -1,4 +1,5 @@ use core::{ops::Deref, fmt}; +use std_shims::io; use zeroize::{Zeroize, Zeroizing}; @@ -10,6 +11,7 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, Scalar, EdwardsPoint} use frost::FrostError; use crate::{ + io::*, generators::{MAX_COMMITMENTS, hash_to_point}, primitives::Decoys, ringct::{ @@ -158,7 +160,12 @@ pub enum SendError { #[cfg_attr(feature = "std", error("invalid amount of key images specified"))] InvalidAmountOfKeyImages, #[cfg_attr(feature = "std", error("wrong spend private key"))] - WrongPrivateKey, // TODO + WrongPrivateKey, + #[cfg_attr( + feature = "std", + error("this SignableTransaction was created by deserializing a malicious serialization") + )] + MaliciousSerialization, #[cfg_attr(feature = "std", error("clsag error ({0})"))] ClsagError(ClsagError), #[cfg(feature = "multisig")] @@ -176,27 +183,24 @@ pub struct SignableTransaction { fee_rate: FeeRate, } +struct SignableTransactionWithKeyImages { + intent: SignableTransaction, + key_images: Vec, +} + impl SignableTransaction { - pub fn new( - rct_type: RctType, - sender_view_key: Zeroizing, - inputs: Vec<(SpendableOutput, Decoys)>, - payments: Vec<(MoneroAddress, u64)>, - change: Change, - data: Vec>, - fee_rate: FeeRate, - ) -> Result { - match rct_type { + fn validate(&self) -> Result<(), SendError> { + match self.rct_type { RctType::ClsagBulletproof | RctType::ClsagBulletproofPlus => {} _ => Err(SendError::UnsupportedRctType)?, - }; + } - if inputs.is_empty() { + if self.inputs.is_empty() { Err(SendError::NoInputs)?; } - for (_, decoys) in &inputs { + for (_, decoys) in &self.inputs { if decoys.len() != - match rct_type { + match self.rct_type { RctType::ClsagBulletproof => 11, RctType::ClsagBulletproofPlus => 16, _ => panic!("unsupported RctType"), @@ -206,74 +210,69 @@ impl SignableTransaction { } } - if payments.is_empty() { + // Check we have at least one non-change output + if !self.payments.iter().any(|payment| matches!(payment, InternalPayment::Payment(_, _))) { Err(SendError::NoOutputs)?; } // If we don't have at least two outputs, as required by Monero, error - if (payments.len() == 1) && matches!(change, Change(ChangeEnum::None)) { + if self.payments.len() < 2 { Err(SendError::NoChange)?; } + // Check we don't have multiple Change outputs due to decoding a malicious serialization + { + let mut change_count = 0; + for payment in &self.payments { + change_count += usize::from(u8::from(matches!(payment, InternalPayment::Change(_, _)))); + } + if change_count > 1 { + Err(SendError::MaliciousSerialization)?; + } + } // Make sure there's at most one payment ID { let mut payment_ids = 0; - let mut count = |addr: MoneroAddress| { - if addr.payment_id().is_some() { - payment_ids += 1 - } - }; - for payment in &payments { - count(payment.0); - } - match &change.0 { - ChangeEnum::None => (), - ChangeEnum::AddressOnly(addr) | ChangeEnum::AddressWithView(addr, _) => count(*addr), + for payment in &self.payments { + payment_ids += usize::from(u8::from(payment.address().payment_id().is_some())); } if payment_ids > 1 { Err(SendError::MultiplePaymentIds)?; } } - // Re-format the payments and change into a consolidated payments list - let payments_amount = payments.iter().map(|(_, amount)| amount).sum::(); - let mut payments = payments - .into_iter() - .map(|(addr, amount)| InternalPayment::Payment(addr, amount)) - .collect::>(); - match change.0 { - ChangeEnum::None => {} - ChangeEnum::AddressOnly(addr) => payments.push(InternalPayment::Change(addr, None)), - ChangeEnum::AddressWithView(addr, view) => { - payments.push(InternalPayment::Change(addr, Some(view))) - } - } - if payments.len() > MAX_COMMITMENTS { + if self.payments.len() > MAX_COMMITMENTS { Err(SendError::TooManyOutputs)?; } // Check the length of each arbitrary data - for part in &data { + for part in &self.data { if part.len() > MAX_ARBITRARY_DATA_SIZE { Err(SendError::TooMuchData)?; } } - let res = SignableTransaction { rct_type, sender_view_key, inputs, payments, data, fee_rate }; - // Check the length of TX extra // https://github.com/monero-project/monero/pull/8733 const MAX_EXTRA_SIZE: usize = 1060; - if res.extra().len() > MAX_EXTRA_SIZE { + if self.extra().len() > MAX_EXTRA_SIZE { Err(SendError::TooMuchData)?; } // Make sure we have enough funds - let in_amount = res.inputs.iter().map(|(input, _)| input.commitment().amount).sum::(); + let in_amount = self.inputs.iter().map(|(input, _)| input.commitment().amount).sum::(); + let payments_amount = self + .payments + .iter() + .filter_map(|payment| match payment { + InternalPayment::Payment(_, amount) => Some(amount), + InternalPayment::Change(_, _) => None, + }) + .sum::(); // Necessary so weight_and_fee doesn't underflow if in_amount < payments_amount { Err(SendError::NotEnoughFunds { inputs: in_amount, outputs: payments_amount, fee: None })?; } - let (weight, fee) = res.weight_and_fee(); + let (weight, fee) = self.weight_and_fee(); if in_amount < (payments_amount + fee) { Err(SendError::NotEnoughFunds { inputs: in_amount, @@ -290,6 +289,116 @@ impl SignableTransaction { Err(SendError::TooLargeTransaction)?; } + Ok(()) + } + + pub fn new( + rct_type: RctType, + sender_view_key: Zeroizing, + inputs: Vec<(SpendableOutput, Decoys)>, + payments: Vec<(MoneroAddress, u64)>, + change: Change, + data: Vec>, + fee_rate: FeeRate, + ) -> Result { + // Re-format the payments and change into a consolidated payments list + let mut payments = payments + .into_iter() + .map(|(addr, amount)| InternalPayment::Payment(addr, amount)) + .collect::>(); + match change.0 { + ChangeEnum::None => {} + ChangeEnum::AddressOnly(addr) => payments.push(InternalPayment::Change(addr, None)), + ChangeEnum::AddressWithView(addr, view) => { + payments.push(InternalPayment::Change(addr, Some(view))) + } + } + + let res = SignableTransaction { rct_type, sender_view_key, inputs, payments, data, fee_rate }; + res.validate()?; + Ok(res) + } + + pub fn write(&self, w: &mut W) -> io::Result<()> { + fn write_input(input: &(SpendableOutput, Decoys), w: &mut W) -> io::Result<()> { + input.0.write(w)?; + input.1.write(w) + } + + fn write_payment(payment: &InternalPayment, w: &mut W) -> io::Result<()> { + match payment { + InternalPayment::Payment(addr, amount) => { + w.write_all(&[0])?; + write_vec(write_byte, addr.to_string().as_bytes(), w)?; + w.write_all(&amount.to_le_bytes()) + } + InternalPayment::Change(addr, change_view) => { + w.write_all(&[1])?; + write_vec(write_byte, addr.to_string().as_bytes(), w)?; + if let Some(view) = change_view.as_ref() { + w.write_all(&[1])?; + write_scalar(view, w) + } else { + w.write_all(&[0]) + } + } + } + } + + write_byte(&u8::from(self.rct_type), w)?; + write_scalar(&self.sender_view_key, w)?; + write_vec(write_input, &self.inputs, w)?; + write_vec(write_payment, &self.payments, w)?; + write_vec(|data, w| write_vec(write_byte, data, w), &self.data, w)?; + self.fee_rate.write(w) + } + + pub fn serialize(&self) -> Vec { + let mut buf = Vec::with_capacity(256); + self.write(&mut buf).unwrap(); + buf + } + + pub fn read(r: &mut R) -> io::Result { + fn read_input(r: &mut impl io::Read) -> io::Result<(SpendableOutput, Decoys)> { + Ok((SpendableOutput::read(r)?, Decoys::read(r)?)) + } + + fn read_address(r: &mut R) -> io::Result { + String::from_utf8(read_vec(read_byte, r)?) + .ok() + .and_then(|str| MoneroAddress::from_str_raw(&str).ok()) + .ok_or_else(|| io::Error::other("invalid address")) + } + + fn read_payment(r: &mut R) -> io::Result { + Ok(match read_byte(r)? { + 0 => InternalPayment::Payment(read_address(r)?, read_u64(r)?), + 1 => InternalPayment::Change( + read_address(r)?, + match read_byte(r)? { + 0 => None, + 1 => Some(Zeroizing::new(read_scalar(r)?)), + _ => Err(io::Error::other("invalid change view"))?, + }, + ), + _ => Err(io::Error::other("invalid payment"))?, + }) + } + + let res = SignableTransaction { + rct_type: RctType::try_from(read_byte(r)?) + .map_err(|()| io::Error::other("unsupported/invalid RctType"))?, + sender_view_key: Zeroizing::new(read_scalar(r)?), + inputs: read_vec(read_input, r)?, + payments: read_vec(read_payment, r)?, + data: read_vec(|r| read_vec(read_byte, r), r)?, + fee_rate: FeeRate::read(r)?, + }; + match res.validate() { + Ok(()) => {} + Err(e) => Err(io::Error::other(e))?, + } Ok(res) } @@ -360,7 +469,7 @@ impl SignableTransaction { .sum::(); // Get the actual TX, just needing the CLSAGs - let mut tx = tx.transaction_without_clsags_and_pseudo_outs(); + let mut tx = tx.transaction_without_signatures(); // Sign the CLSAGs let clsags_and_pseudo_outs = @@ -391,8 +500,3 @@ impl SignableTransaction { Ok(tx) } } - -struct SignableTransactionWithKeyImages { - intent: SignableTransaction, - key_images: Vec, -} diff --git a/coins/monero/wallet/src/send/tx.rs b/coins/monero/wallet/src/send/tx.rs index 8bc0cb59..c0f37832 100644 --- a/coins/monero/wallet/src/send/tx.rs +++ b/coins/monero/wallet/src/send/tx.rs @@ -216,7 +216,7 @@ impl SignableTransaction { } impl SignableTransactionWithKeyImages { - pub(crate) fn transaction_without_clsags_and_pseudo_outs(&self) -> Transaction { + pub(crate) fn transaction_without_signatures(&self) -> Transaction { let commitments_and_encrypted_amounts = self.intent.commitments_and_encrypted_amounts(&self.key_images); let mut commitments = Vec::with_capacity(self.intent.payments.len());