monero_wallet/send/
mod.rs

1use core::{ops::Deref as _, fmt};
2use std_shims::{
3  io, vec,
4  vec::Vec,
5  string::{String, ToString as _},
6  collections::HashSet,
7};
8
9use subtle::ConstantTimeEq as _;
10use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
11
12use rand_core::{RngCore, CryptoRng};
13use rand::seq::SliceRandom as _;
14
15#[cfg(feature = "compile-time-generators")]
16use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
17#[cfg(not(feature = "compile-time-generators"))]
18use curve25519_dalek::constants::ED25519_BASEPOINT_POINT as ED25519_BASEPOINT_TABLE;
19
20#[cfg(feature = "multisig")]
21use frost::FrostError;
22
23use crate::{
24  io::*,
25  ed25519::*,
26  ringct::{
27    clsag::{ClsagError, ClsagContext, Clsag},
28    bulletproofs::MAX_COMMITMENTS as MAX_BULLETPROOF_COMMITMENTS,
29    RctType, RctPrunable, RctProofs,
30  },
31  transaction::{TransactionPrefix, Transaction},
32  address::{Network, SubaddressIndex, MoneroAddress},
33  extra::{MAX_ARBITRARY_DATA_SIZE, MAX_EXTRA_SIZE_BY_RELAY_RULE},
34  interface::FeeRate,
35  ViewPair, GuaranteedViewPair, OutputWithDecoys,
36};
37
38mod tx_keys;
39pub use tx_keys::TransactionKeys;
40mod tx;
41mod eventuality;
42pub use eventuality::Eventuality;
43
44#[cfg(feature = "multisig")]
45mod multisig;
46#[cfg(feature = "multisig")]
47pub use multisig::{TransactionMachine, TransactionSignMachine, TransactionSignatureMachine};
48
49pub(crate) fn key_image_sort(x: &CompressedPoint, y: &CompressedPoint) -> core::cmp::Ordering {
50  x.cmp(y).reverse()
51}
52
53#[derive(Clone, Zeroize)]
54enum ChangeEnum {
55  AddressOnly(MoneroAddress),
56  Standard { view_pair: ViewPair, subaddress: Option<SubaddressIndex> },
57  Guaranteed { view_pair: GuaranteedViewPair, subaddress: Option<SubaddressIndex> },
58}
59
60impl PartialEq for ChangeEnum {
61  fn eq(&self, other: &Self) -> bool {
62    match (self, other) {
63      (ChangeEnum::AddressOnly(lhs), ChangeEnum::AddressOnly(rhs)) => lhs == rhs,
64      (
65        ChangeEnum::Standard { view_pair: lhs_vp, subaddress: lhs_s },
66        ChangeEnum::Standard { view_pair: rhs_vp, subaddress: rhs_s },
67      ) => {
68        bool::from(lhs_vp.spend.ct_eq(&rhs_vp.spend) & lhs_vp.view.ct_eq(&rhs_vp.view)) &
69          (lhs_s == rhs_s)
70      }
71      (
72        ChangeEnum::Guaranteed { view_pair: lhs_vp, subaddress: lhs_s },
73        ChangeEnum::Guaranteed { view_pair: rhs_vp, subaddress: rhs_s },
74      ) => {
75        bool::from(lhs_vp.0.spend.ct_eq(&rhs_vp.0.spend) & lhs_vp.0.view.ct_eq(&rhs_vp.0.view)) &
76          (lhs_s == rhs_s)
77      }
78      _ => false,
79    }
80  }
81}
82impl Eq for ChangeEnum {}
83
84impl ChangeEnum {
85  fn address(&self) -> MoneroAddress {
86    match self {
87      ChangeEnum::AddressOnly(addr) => *addr,
88      // Network::Mainnet as the network won't effect the derivations
89      ChangeEnum::Standard { view_pair, subaddress } => match subaddress {
90        Some(subaddress) => view_pair.subaddress(Network::Mainnet, *subaddress),
91        None => view_pair.legacy_address(Network::Mainnet),
92      },
93      ChangeEnum::Guaranteed { view_pair, subaddress } => {
94        view_pair.address(Network::Mainnet, *subaddress, None)
95      }
96    }
97  }
98}
99
100impl fmt::Debug for ChangeEnum {
101  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102    let kind = match self {
103      ChangeEnum::AddressOnly(addr) => {
104        return f.debug_struct("ChangeEnum::AddressOnly").field("0", &addr).finish();
105      }
106      ChangeEnum::Standard { .. } => "ChangeEnum::Standard",
107      ChangeEnum::Guaranteed { .. } => "ChangeEnum::Guaranteed",
108    };
109    f.debug_struct(kind).field("0", &self.address()).finish_non_exhaustive()
110  }
111}
112
113/// Specification for a change output.
114#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
115pub struct Change(Option<ChangeEnum>);
116
117impl Change {
118  /// Create a change output specification.
119  ///
120  /// This take the view key as Monero assumes it has the view key for change outputs. It optimizes
121  /// its wallet protocol accordingly.
122  pub fn new(view_pair: ViewPair, subaddress: Option<SubaddressIndex>) -> Change {
123    Change(Some(ChangeEnum::Standard { view_pair, subaddress }))
124  }
125
126  /// Create a change output specification for a guaranteed view pair.
127  ///
128  /// This take the view key as Monero assumes it has the view key for change outputs. It optimizes
129  /// its wallet protocol accordingly.
130  pub fn guaranteed(view_pair: GuaranteedViewPair, subaddress: Option<SubaddressIndex>) -> Change {
131    Change(Some(ChangeEnum::Guaranteed { view_pair, subaddress }))
132  }
133
134  /// Create a fingerprintable change output specification.
135  ///
136  /// You MUST assume this will harm your privacy. Only use this if you know what you're doing.
137  ///
138  /// If the change address is Some, this will be unable to optimize the transaction as the
139  /// Monero wallet protocol expects it can (due to presumably having the view key for the change
140  /// output). If a transaction should be optimized, and isn't, it will be fingerprintable.
141  ///
142  /// If the change address is None, there are two fingerprints:
143  ///
144  /// 1) The change in the TX is shunted to the fee (making it fingerprintable).
145  ///
146  /// 2) In two-output transactions, where the payment address doesn't have a payment ID, wallet2
147  ///    includes an encrypted dummy payment ID for the non-change output in order to not allow
148  ///    differentiating if transactions send to addresses with payment IDs or not. monero-wallet
149  ///    includes a dummy payment ID which at least one recipient will identify as not the expected
150  ///    dummy payment ID, revealing to the recipient(s) the sender is using non-wallet2 software.
151  ///
152  pub fn fingerprintable(address: Option<MoneroAddress>) -> Change {
153    Change(address.map(ChangeEnum::AddressOnly))
154  }
155}
156
157#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
158enum InternalPayment {
159  Payment(MoneroAddress, u64),
160  Change(ChangeEnum),
161}
162
163impl InternalPayment {
164  fn address(&self) -> MoneroAddress {
165    match self {
166      InternalPayment::Payment(addr, _) => *addr,
167      InternalPayment::Change(change) => change.address(),
168    }
169  }
170}
171
172/// An error while sending Monero.
173#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
174pub enum SendError {
175  /// The RingCT type to produce proofs for this transaction with weren't supported.
176  #[error("this library doesn't yet support that RctType")]
177  UnsupportedRctType,
178  /// The transaction had no inputs specified.
179  #[error("no inputs")]
180  NoInputs,
181  /// The provided inputs were invalid.
182  #[error("invalid inputs")]
183  InvalidInputs,
184  /// The decoy quantity was invalid for the specified RingCT type.
185  #[error("invalid number of decoys")]
186  InvalidDecoyQuantity,
187  /// The transaction had no outputs specified.
188  #[error("no outputs")]
189  NoOutputs,
190  /// The transaction had too many outputs specified.
191  #[error("too many outputs")]
192  TooManyOutputs,
193  /// The transaction did not have a change output, and did not have two outputs.
194  ///
195  /// Monero requires all transactions have at least two outputs, assuming one payment and one
196  /// change (or at least one dummy and one change). Accordingly, specifying no change and only
197  /// one payment prevents creating a valid transaction
198  #[error("only one output and no change address")]
199  NoChange,
200  /// Multiple addresses had payment IDs specified.
201  ///
202  /// Only one payment ID is allowed per transaction.
203  #[error("multiple addresses with payment IDs")]
204  MultiplePaymentIds,
205  /// Too much arbitrary data was specified.
206  #[error("too much data")]
207  TooMuchArbitraryData,
208  /// The created transaction was too large.
209  #[error("too large of a transaction")]
210  TooLargeTransaction,
211  /// The transactions' amounts could not be represented within a `u64`.
212  #[error("transaction amounts exceed u64::MAX (in {in_amount}, out {out_amount})")]
213  AmountsUnrepresentable {
214    /// The amount in (via inputs).
215    in_amount: u128,
216    /// The amount which would be out (between outputs and the fee).
217    out_amount: u128,
218  },
219  /// This transaction could not pay for itself.
220  #[error(
221    "not enough funds (inputs {inputs}, outputs {outputs}, necessary_fee {necessary_fee:?})"
222  )]
223  NotEnoughFunds {
224    /// The amount of funds the inputs contributed.
225    inputs: u64,
226    /// The amount of funds the outputs required.
227    outputs: u64,
228    /// The fee necessary to be paid on top.
229    ///
230    /// If this is None, it is because the fee was not calculated as the outputs alone caused this
231    /// error.
232    necessary_fee: Option<u64>,
233  },
234  /// This transaction is being signed with the wrong private key.
235  #[error("wrong spend private key")]
236  WrongPrivateKey,
237  /// This transaction was read from a bytestream which was malicious.
238  #[error("this SignableTransaction was created by deserializing a malicious serialization")]
239  MaliciousSerialization,
240  /// There was an error when working with the CLSAGs.
241  #[error("clsag error ({0})")]
242  ClsagError(ClsagError),
243  /// There was an error when working with FROST.
244  #[cfg(feature = "multisig")]
245  #[error("frost error {0}")]
246  FrostError(FrostError),
247}
248
249/// A signable transaction.
250#[derive(Clone, Zeroize, ZeroizeOnDrop)]
251pub struct SignableTransaction {
252  rct_type: RctType,
253  outgoing_view_key: Zeroizing<[u8; 32]>,
254  inputs: Vec<OutputWithDecoys>,
255  payments: Vec<InternalPayment>,
256  data: Vec<Vec<u8>>,
257  fee_rate: FeeRate,
258}
259
260impl PartialEq for SignableTransaction {
261  fn eq(&self, other: &Self) -> bool {
262    (self.rct_type == other.rct_type) &&
263      bool::from(self.outgoing_view_key.deref().ct_eq(other.outgoing_view_key.deref())) &&
264      (self.inputs == other.inputs) &&
265      (self.payments == other.payments) &&
266      (self.data == other.data) &&
267      (self.fee_rate == other.fee_rate)
268  }
269}
270impl Eq for SignableTransaction {}
271
272impl fmt::Debug for SignableTransaction {
273  /// This `Debug` implementation may run in variable time and reveal everything except the
274  /// `outgoing_view_key`.
275  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
276    f.debug_struct("SignableTransaction")
277      .field("rct_type", &self.rct_type)
278      .field("inputs", &self.inputs)
279      .field("payments", &self.payments)
280      .field("data", &self.data)
281      .field("fee_rate", &self.fee_rate)
282      .finish_non_exhaustive()
283  }
284}
285
286#[derive(Zeroize, ZeroizeOnDrop)]
287struct SignableTransactionWithKeyImages {
288  intent: SignableTransaction,
289  key_images: Vec<CompressedPoint>,
290}
291
292impl SignableTransaction {
293  fn validate(&self) -> Result<(), SendError> {
294    match self.rct_type {
295      RctType::ClsagBulletproof | RctType::ClsagBulletproofPlus => {}
296      RctType::AggregateMlsagBorromean |
297      RctType::MlsagBorromean |
298      RctType::MlsagBulletproofs |
299      RctType::MlsagBulletproofsCompactAmount => Err(SendError::UnsupportedRctType)?,
300    }
301
302    if self.inputs.is_empty() {
303      Err(SendError::NoInputs)?;
304    }
305    if self.inputs.iter().map(|input| input.key().compress()).collect::<HashSet<_>>().len() !=
306      self.inputs.len()
307    {
308      Err(SendError::InvalidInputs)?;
309    }
310    for input in &self.inputs {
311      // Checks for outputs we won't scan but are unusable if passed here
312      {
313        let key = input.key().into();
314        // Reject keys with torsion, which would need a bespoke signing algorithm to be complete
315        if !key.is_torsion_free() {
316          Err(SendError::InvalidInputs)?;
317        }
318        // Reject keys which are the identity and accordingly lack a usable a key image
319        use curve25519_dalek::traits::IsIdentity as _;
320        if key.is_identity() {
321          Err(SendError::InvalidInputs)?;
322        }
323      }
324
325      if input.decoys().len() !=
326        match self.rct_type {
327          RctType::ClsagBulletproof => 11,
328          RctType::ClsagBulletproofPlus => 16,
329          RctType::AggregateMlsagBorromean |
330          RctType::MlsagBorromean |
331          RctType::MlsagBulletproofs |
332          RctType::MlsagBulletproofsCompactAmount => panic!("unsupported RctType"),
333        }
334      {
335        Err(SendError::InvalidDecoyQuantity)?;
336      }
337    }
338
339    // Check we have at least one non-change output
340    if !self.payments.iter().any(|payment| matches!(payment, InternalPayment::Payment(_, _))) {
341      Err(SendError::NoOutputs)?;
342    }
343    // If we don't have at least two outputs, as required by Monero, error
344    if self.payments.len() < 2 {
345      Err(SendError::NoChange)?;
346    }
347    // Check we don't have multiple Change outputs due to decoding a malicious serialization
348    {
349      let mut change_count = 0;
350      for payment in &self.payments {
351        change_count += usize::from(u8::from(matches!(payment, InternalPayment::Change(_))));
352      }
353      if change_count > 1 {
354        Err(SendError::MaliciousSerialization)?;
355      }
356    }
357
358    // Make sure there's at most one payment ID
359    {
360      let mut payment_ids = 0;
361      for payment in &self.payments {
362        payment_ids += usize::from(u8::from(payment.address().payment_id().is_some()));
363      }
364      if payment_ids > 1 {
365        Err(SendError::MultiplePaymentIds)?;
366      }
367    }
368
369    if self.payments.len() > MAX_BULLETPROOF_COMMITMENTS {
370      Err(SendError::TooManyOutputs)?;
371    }
372
373    // Check the length of each arbitrary data
374    for part in &self.data {
375      if part.len() > MAX_ARBITRARY_DATA_SIZE {
376        Err(SendError::TooMuchArbitraryData)?;
377      }
378    }
379
380    // Check the length of TX extra
381    if self.extra().len() > MAX_EXTRA_SIZE_BY_RELAY_RULE {
382      Err(SendError::TooMuchArbitraryData)?;
383    }
384
385    // Make sure we have enough funds
386    let weight;
387    {
388      let in_amount: u128 =
389        self.inputs.iter().map(|input| u128::from(input.commitment().amount)).sum();
390      let payments_amount: u128 = self
391        .payments
392        .iter()
393        .filter_map(|payment| match payment {
394          InternalPayment::Payment(_, amount) => Some(u128::from(*amount)),
395          InternalPayment::Change(_) => None,
396        })
397        .sum();
398      let necessary_fee;
399      (weight, necessary_fee) = self.weight_and_necessary_fee();
400      let out_amount = payments_amount + u128::from(necessary_fee);
401      let in_out_amount = u64::try_from(in_amount)
402        .and_then(|in_amount| u64::try_from(out_amount).map(|out_amount| (in_amount, out_amount)));
403      let Ok((in_amount, out_amount)) = in_out_amount else {
404        Err(SendError::AmountsUnrepresentable { in_amount, out_amount })?
405      };
406      if in_amount < out_amount {
407        Err(SendError::NotEnoughFunds {
408          inputs: in_amount,
409          outputs: u64::try_from(payments_amount)
410            .expect("total out fit within u64 but not part of total out"),
411          necessary_fee: Some(necessary_fee),
412        })?;
413      }
414    }
415
416    // The limit is half the no-penalty block size
417    // https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
418    //   /src/wallet/wallet2.cpp#L11076-L11085
419    // https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
420    //   /src/cryptonote_config.h#L61
421    // https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
422    //   /src/cryptonote_config.h#L64
423    const MAX_TX_SIZE: usize = (300_000 / 2) - 600;
424    if weight >= MAX_TX_SIZE {
425      Err(SendError::TooLargeTransaction)?;
426    }
427
428    Ok(())
429  }
430
431  /// Create a new SignableTransaction.
432  ///
433  /// `outgoing_view_key` is used to seed the RNGs for this transaction. Anyone with knowledge of
434  /// the outgoing view key will be able to identify a transaction produced with this methodology,
435  /// and the data within it. Accordingly, it must be treated as a private key.
436  ///
437  /// If one `outgoing_view_key` is reused across two transactions which share keys in their
438  /// inputs, such transactions being mutually incompatible with each other on that premise, some
439  /// ephemeral secrets MAY be reused causing adverse effects. Do NOT reuse an `outgoing_view_key`
440  /// across incompatible transactions accordingly.
441  ///
442  /// `data` represents arbitrary data which will be embedded into the transaction's `extra` field.
443  /// Please see `Extra::arbitrary_data` for the full impacts of this.
444  ///
445  /// This will attempt to sign a transaction as constructed, even if the arguments are
446  /// inconsistent or invalid for some view of the Monero network. It is the caller's
447  /// responsibility to ensure their sanity.
448  ///
449  /// This function runs in time variable to the validity of the arguments and the public data.
450  pub fn new(
451    rct_type: RctType,
452    outgoing_view_key: Zeroizing<[u8; 32]>,
453    inputs: Vec<OutputWithDecoys>,
454    payments: Vec<(MoneroAddress, u64)>,
455    change: Change,
456    data: Vec<Vec<u8>>,
457    fee_rate: FeeRate,
458  ) -> Result<SignableTransaction, SendError> {
459    // Re-format the payments and change into a consolidated payments list
460    let mut payments = payments
461      .into_iter()
462      .map(|(addr, amount)| InternalPayment::Payment(addr, amount))
463      .collect::<Vec<_>>();
464
465    if let Some(change) = change.0 {
466      payments.push(InternalPayment::Change(change));
467    }
468
469    let mut res =
470      SignableTransaction { rct_type, outgoing_view_key, inputs, payments, data, fee_rate };
471    res.validate()?;
472
473    // Shuffle the payments
474    {
475      let mut rng = res.seeded_rng(b"shuffle_payments");
476      res.payments.shuffle(&mut rng);
477    }
478
479    Ok(res)
480  }
481
482  /// The fee rate this transaction uses.
483  pub fn fee_rate(&self) -> FeeRate {
484    self.fee_rate
485  }
486
487  /// The fee this transaction requires.
488  ///
489  /// This is distinct from the fee this transaction will use. If no change output is specified,
490  /// all unspent coins will be shunted to the fee.
491  pub fn necessary_fee(&self) -> u64 {
492    self.weight_and_necessary_fee().1
493  }
494
495  /// Write a SignableTransaction.
496  ///
497  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
498  /// defined serialization. This may run in time variable to its value.
499  pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
500    fn write_payment<W: io::Write>(payment: &InternalPayment, w: &mut W) -> io::Result<()> {
501      match payment {
502        InternalPayment::Payment(addr, amount) => {
503          w.write_all(&[0])?;
504          write_vec(write_byte, addr.to_string().as_bytes(), w)?;
505          w.write_all(&amount.to_le_bytes())
506        }
507        InternalPayment::Change(change) => match change {
508          ChangeEnum::AddressOnly(addr) => {
509            w.write_all(&[1])?;
510            write_vec(write_byte, addr.to_string().as_bytes(), w)
511          }
512          ChangeEnum::Standard { view_pair, subaddress } => {
513            w.write_all(&[2])?;
514            view_pair.spend().compress().write(w)?;
515            view_pair.view.write(w)?;
516            if let Some(subaddress) = subaddress {
517              w.write_all(&subaddress.account().to_le_bytes())?;
518              w.write_all(&subaddress.address().to_le_bytes())
519            } else {
520              w.write_all(&0u32.to_le_bytes())?;
521              w.write_all(&0u32.to_le_bytes())
522            }
523          }
524          ChangeEnum::Guaranteed { view_pair, subaddress } => {
525            w.write_all(&[3])?;
526            view_pair.spend().compress().write(w)?;
527            view_pair.0.view.write(w)?;
528            if let Some(subaddress) = subaddress {
529              w.write_all(&subaddress.account().to_le_bytes())?;
530              w.write_all(&subaddress.address().to_le_bytes())
531            } else {
532              w.write_all(&0u32.to_le_bytes())?;
533              w.write_all(&0u32.to_le_bytes())
534            }
535          }
536        },
537      }
538    }
539
540    write_byte(&u8::from(self.rct_type), w)?;
541    w.write_all(self.outgoing_view_key.as_slice())?;
542    write_vec(OutputWithDecoys::write, &self.inputs, w)?;
543    write_vec(write_payment, &self.payments, w)?;
544    write_vec(|data, w| write_vec(write_byte, data, w), &self.data, w)?;
545    self.fee_rate.write(w)
546  }
547
548  /// Serialize the SignableTransaction to a `Vec<u8>`.
549  ///
550  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
551  /// defined serialization. This may run in time variable to its value.
552  pub fn serialize(&self) -> Vec<u8> {
553    let mut buf = Vec::with_capacity(256);
554    self.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
555    buf
556  }
557
558  /// Read a `SignableTransaction`.
559  ///
560  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
561  /// defined serialization. This may run in time variable to its value.
562  pub fn read<R: io::Read>(r: &mut R) -> io::Result<SignableTransaction> {
563    fn read_address<R: io::Read>(r: &mut R) -> io::Result<MoneroAddress> {
564      String::from_utf8(read_vec(read_byte, Some(MoneroAddress::SIZE_UPPER_BOUND.0), r)?)
565        .ok()
566        .and_then(|str| MoneroAddress::from_str_with_unchecked_network(&str).ok())
567        .ok_or_else(|| io::Error::other("invalid address"))
568    }
569
570    fn read_payment<R: io::Read>(r: &mut R) -> io::Result<InternalPayment> {
571      Ok(match read_byte(r)? {
572        0 => InternalPayment::Payment(read_address(r)?, read_u64(r)?),
573        1 => InternalPayment::Change(ChangeEnum::AddressOnly(read_address(r)?)),
574        2 => InternalPayment::Change(ChangeEnum::Standard {
575          view_pair: ViewPair::new(
576            CompressedPoint::read(r)?
577              .decompress()
578              .ok_or_else(|| io::Error::other("`Change` payment had invalid public spend key"))?,
579            Zeroizing::new(Scalar::read(r)?),
580          )
581          .map_err(io::Error::other)?,
582          subaddress: SubaddressIndex::new(read_u32(r)?, read_u32(r)?),
583        }),
584        3 => InternalPayment::Change(ChangeEnum::Guaranteed {
585          view_pair: GuaranteedViewPair::new(
586            CompressedPoint::read(r)?.decompress().ok_or_else(|| {
587              io::Error::other("guaranteed `Change` payment had invalid public spend key")
588            })?,
589            Zeroizing::new(Scalar::read(r)?),
590          )
591          .map_err(io::Error::other)?,
592          subaddress: SubaddressIndex::new(read_u32(r)?, read_u32(r)?),
593        }),
594        _ => Err(io::Error::other("invalid payment"))?,
595      })
596    }
597
598    let res = SignableTransaction {
599      rct_type: RctType::try_from(read_byte(r)?)
600        .map_err(|()| io::Error::other("unsupported/invalid RctType"))?,
601      outgoing_view_key: Zeroizing::new(read_bytes(r)?),
602      inputs: read_vec(OutputWithDecoys::read, Some(TransactionPrefix::INPUTS_UPPER_BOUND.0), r)?,
603      payments: read_vec(read_payment, Some(MAX_BULLETPROOF_COMMITMENTS), r)?,
604      /*
605        This doesn't assert the _total_ length is `< MAX_EXTRA_SIZE_BY_RELAY_RULE`, yet the
606        following call to `validate` will.
607      */
608      data: read_vec(
609        |r| read_vec(read_byte, Some(MAX_ARBITRARY_DATA_SIZE), r),
610        Some(MAX_EXTRA_SIZE_BY_RELAY_RULE),
611        r,
612      )?,
613      fee_rate: FeeRate::read(r)?,
614    };
615    match res.validate() {
616      Ok(()) => {}
617      Err(e) => Err(io::Error::other(e))?,
618    }
619    Ok(res)
620  }
621
622  fn with_key_images(
623    mut self,
624    mut key_images: Vec<CompressedPoint>,
625  ) -> SignableTransactionWithKeyImages {
626    debug_assert_eq!(self.inputs.len(), key_images.len());
627
628    // Sort the inputs by their key images
629    let mut sorted_inputs = self.inputs.drain(..).zip(key_images.drain(..)).collect::<Vec<_>>();
630    sorted_inputs
631      .sort_by(|(_, key_image_a), (_, key_image_b)| key_image_sort(key_image_a, key_image_b));
632
633    for (input, key_image) in sorted_inputs {
634      self.inputs.push(input);
635      key_images.push(key_image);
636    }
637
638    SignableTransactionWithKeyImages { intent: self, key_images }
639  }
640
641  /// Fetch what the transaction will be, without its signatures (and associated fields).
642  ///
643  /// This returns `None` if an improper amount of key images is provided.
644  pub fn unsigned_transaction(self, key_images: Vec<CompressedPoint>) -> Option<Transaction> {
645    if self.inputs.len() != key_images.len() {
646      None?;
647    }
648    Some(self.with_key_images(key_images).transaction_without_signatures())
649  }
650
651  /// Sign this transaction.
652  ///
653  /// This function runs in time variable to the validity of the arguments and the public data.
654  pub fn sign(
655    self,
656    rng: &mut (impl RngCore + CryptoRng),
657    sender_spend_key: &Zeroizing<Scalar>,
658  ) -> Result<Transaction, SendError> {
659    let sender_spend_key = Zeroizing::new((**sender_spend_key).into());
660
661    // Calculate the key images
662    let mut key_images = vec![];
663    for input in &self.inputs {
664      let input_key = Zeroizing::new(sender_spend_key.deref() + input.key_offset().into());
665      if bool::from(!(input_key.deref() * ED25519_BASEPOINT_TABLE).ct_eq(&input.key().into())) {
666        Err(SendError::WrongPrivateKey)?;
667      }
668      let key_image = Point::from(
669        input_key.deref() * Point::biased_hash(input.key().compress().to_bytes()).into(),
670      );
671      key_images.push(key_image.compress());
672    }
673
674    // Convert to a SignableTransactionWithKeyImages
675    let tx = self.with_key_images(key_images);
676
677    // Prepare the CLSAG signatures
678    let mut clsag_signs = Vec::with_capacity(tx.intent.inputs.len());
679    for input in &tx.intent.inputs {
680      // Re-derive the input key as this will be in a different order
681      let input_key =
682        Zeroizing::new(Scalar::from(sender_spend_key.deref() + input.key_offset().into()));
683      clsag_signs.push((
684        input_key,
685        ClsagContext::new(input.decoys().clone(), input.commitment().clone())
686          .map_err(SendError::ClsagError)?,
687      ));
688    }
689
690    // Get the output commitments' mask sum
691    let mask_sum = tx.intent.sum_output_masks(&tx.key_images);
692
693    // Get the actual TX, just needing the CLSAGs
694    let mut tx = tx.transaction_without_signatures();
695
696    // Sign the CLSAGs
697    let clsags_and_pseudo_outs = Clsag::sign(
698      rng,
699      clsag_signs,
700      mask_sum,
701      tx.signature_hash().expect("signing a transaction which isn't signed?"),
702    )
703    .map_err(SendError::ClsagError)?;
704
705    // Fill in the CLSAGs/pseudo-outs
706    let inputs_len = tx.prefix().inputs.len();
707    let Transaction::V2 {
708      proofs:
709        Some(RctProofs {
710          prunable: RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. },
711          ..
712        }),
713      ..
714    } = tx
715    else {
716      panic!("not signing clsag?")
717    };
718    *clsags = Vec::with_capacity(inputs_len);
719    *pseudo_outs = Vec::with_capacity(inputs_len);
720    for (clsag, pseudo_out) in clsags_and_pseudo_outs {
721      clsags.push(clsag);
722      pseudo_outs.push(pseudo_out.compress());
723    }
724
725    // Return the signed TX
726    Ok(tx)
727  }
728}