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}