monero_wallet/send/
eventuality.rs

1use std_shims::{vec::Vec, io};
2
3use zeroize::Zeroize;
4
5use crate::{
6  ringct::PrunedRctProofs,
7  transaction::{Input, Timelock, Pruned, Transaction},
8  send::SignableTransaction,
9};
10
11/// The eventual output of a SignableTransaction.
12///
13/// If a SignableTransaction is signed and published on-chain, it will create a Transaction
14/// identifiable to whoever else has the same SignableTransaction (with the same outgoing view
15/// key). This structure enables checking if a Transaction is in fact such an output, as it can.
16///
17/// Since Monero is a privacy coin without outgoing view keys, this only performs a fuzzy match.
18/// The fuzzy match executes over the outputs and associated data necessary to work with the
19/// outputs (the transaction randomness, ciphertexts). This transaction does not check if the
20/// inputs intended to be spent where actually the inputs spent (as infeasible).
21///
22/// The transaction randomness does bind to the inputs intended to be spent, so an on-chain
23/// transaction will not match for multiple `Eventuality`s unless the `SignableTransaction`s they
24/// were built from were in conflict (and their intended transactions cannot simultaneously exist
25/// on-chain).
26#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
27pub struct Eventuality(SignableTransaction);
28
29impl From<SignableTransaction> for Eventuality {
30  fn from(tx: SignableTransaction) -> Eventuality {
31    Eventuality(tx)
32  }
33}
34
35impl Eventuality {
36  /// Return the `extra` field any transaction following this intent would use.
37  ///
38  /// This enables building a HashMap of Extra -> Eventuality for efficiently fetching the
39  /// `Eventuality` an on-chain transaction may complete.
40  ///
41  /// This extra is cryptographically bound to the inputs intended to be spent. If the
42  /// `SignableTransaction`s the `Eventuality`s are built from are not in conflict (their intended
43  /// transactions can simultaneously exist on-chain), then each extra will only have a single
44  /// Eventuality associated (barring a cryptographic problem considered hard failing).
45  pub fn extra(&self) -> Vec<u8> {
46    self.0.extra()
47  }
48
49  /// Return if this TX matches the SignableTransaction this was created from.
50  ///
51  /// Matching the SignableTransaction means this transaction created the expected outputs, they're
52  /// scannable, they're not locked, and this transaction claims to use the intended inputs (though
53  /// this is not guaranteed). This 'claim' is evaluated by this transaction using the transaction
54  /// keys derived from the intended inputs. This ensures two SignableTransactions with the same
55  /// intended payments don't match for each other's `Eventuality`s (as they'll have distinct
56  /// inputs intended).
57  #[must_use]
58  pub fn matches(&self, tx: &Transaction<Pruned>) -> bool {
59    // Verify extra
60    if self.0.extra() != tx.prefix().extra {
61      return false;
62    }
63
64    // Also ensure no timelock was set
65    if tx.prefix().additional_timelock != Timelock::None {
66      return false;
67    }
68
69    // Check the amount of inputs aligns
70    if tx.prefix().inputs.len() != self.0.inputs.len() {
71      return false;
72    }
73    // Collect the key images used by this transaction
74    let Ok(key_images) = tx
75      .prefix()
76      .inputs
77      .iter()
78      .map(|input| match input {
79        Input::Gen(_) => Err(()),
80        Input::ToKey { key_image, .. } => Ok(*key_image),
81      })
82      .collect::<Result<Vec<_>, _>>()
83    else {
84      return false;
85    };
86
87    // Check the outputs
88    if self.0.outputs(&key_images) != tx.prefix().outputs {
89      return false;
90    }
91
92    // Check the encrypted amounts and commitments
93    let commitments_and_encrypted_amounts = self.0.commitments_and_encrypted_amounts(&key_images);
94    let Transaction::V2 { proofs: Some(PrunedRctProofs { ref base, .. }), .. } = tx else {
95      return false;
96    };
97    if base.commitments !=
98      commitments_and_encrypted_amounts
99        .iter()
100        .map(|(commitment, _)| commitment.calculate())
101        .collect::<Vec<_>>()
102    {
103      return false;
104    }
105    if base.encrypted_amounts !=
106      commitments_and_encrypted_amounts.into_iter().map(|(_, amount)| amount).collect::<Vec<_>>()
107    {
108      return false;
109    }
110
111    true
112  }
113
114  /// Write the Eventuality.
115  ///
116  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
117  /// defined serialization.
118  pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
119    self.0.write(w)
120  }
121
122  /// Serialize the Eventuality to a `Vec<u8>`.
123  ///
124  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
125  /// defined serialization.
126  pub fn serialize(&self) -> Vec<u8> {
127    self.0.serialize()
128  }
129
130  /// Read a Eventuality.
131  ///
132  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
133  /// defined serialization.
134  pub fn read<R: io::Read>(r: &mut R) -> io::Result<Eventuality> {
135    Ok(Eventuality(SignableTransaction::read(r)?))
136  }
137}