monero_wallet/
output.rs

1use std_shims::{
2  vec,
3  vec::Vec,
4  io::{self, Read, Write},
5};
6
7use zeroize::{Zeroize, ZeroizeOnDrop};
8
9use curve25519_dalek::{Scalar, edwards::EdwardsPoint};
10
11use crate::{
12  io::*,
13  primitives::Commitment,
14  transaction::Timelock,
15  address::SubaddressIndex,
16  extra::{MAX_ARBITRARY_DATA_SIZE, MAX_EXTRA_SIZE_BY_RELAY_RULE, PaymentId},
17};
18
19/// An absolute output ID, defined as its transaction hash and output index.
20///
21/// This is not the output's key as multiple outputs may share an output key.
22#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
23pub(crate) struct AbsoluteId {
24  pub(crate) transaction: [u8; 32],
25  pub(crate) index_in_transaction: u64,
26}
27
28impl core::fmt::Debug for AbsoluteId {
29  fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
30    fmt
31      .debug_struct("AbsoluteId")
32      .field("transaction", &hex::encode(self.transaction))
33      .field("index_in_transaction", &self.index_in_transaction)
34      .finish()
35  }
36}
37
38impl AbsoluteId {
39  /// Write the AbsoluteId.
40  ///
41  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
42  /// defined serialization.
43  fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
44    w.write_all(&self.transaction)?;
45    w.write_all(&self.index_in_transaction.to_le_bytes())
46  }
47
48  /// Read an AbsoluteId.
49  ///
50  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
51  /// defined serialization.
52  fn read<R: Read>(r: &mut R) -> io::Result<AbsoluteId> {
53    Ok(AbsoluteId { transaction: read_bytes(r)?, index_in_transaction: read_u64(r)? })
54  }
55}
56
57/// An output's relative ID.
58///
59/// This is defined as the output's index on the blockchain.
60#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
61pub(crate) struct RelativeId {
62  pub(crate) index_on_blockchain: u64,
63}
64
65impl core::fmt::Debug for RelativeId {
66  fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
67    fmt.debug_struct("RelativeId").field("index_on_blockchain", &self.index_on_blockchain).finish()
68  }
69}
70
71impl RelativeId {
72  /// Write the RelativeId.
73  ///
74  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
75  /// defined serialization.
76  fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
77    w.write_all(&self.index_on_blockchain.to_le_bytes())
78  }
79
80  /// Read an RelativeId.
81  ///
82  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
83  /// defined serialization.
84  fn read<R: Read>(r: &mut R) -> io::Result<Self> {
85    Ok(RelativeId { index_on_blockchain: read_u64(r)? })
86  }
87}
88
89/// The data within an output, as necessary to spend the output.
90#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
91pub(crate) struct OutputData {
92  pub(crate) key: EdwardsPoint,
93  pub(crate) key_offset: Scalar,
94  pub(crate) commitment: Commitment,
95}
96
97impl core::fmt::Debug for OutputData {
98  fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
99    fmt
100      .debug_struct("OutputData")
101      .field("key", &hex::encode(self.key.compress().0))
102      .field("commitment", &self.commitment)
103      .finish_non_exhaustive()
104  }
105}
106
107impl OutputData {
108  /// The key this output may be spent by.
109  pub(crate) fn key(&self) -> EdwardsPoint {
110    self.key
111  }
112
113  /// The scalar to add to the private spend key for it to be the discrete logarithm of this
114  /// output's key.
115  pub(crate) fn key_offset(&self) -> Scalar {
116    self.key_offset
117  }
118
119  /// The commitment this output created.
120  pub(crate) fn commitment(&self) -> &Commitment {
121    &self.commitment
122  }
123
124  /// Write the OutputData.
125  ///
126  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
127  /// defined serialization.
128  pub(crate) fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
129    w.write_all(&self.key.compress().to_bytes())?;
130    w.write_all(&self.key_offset.to_bytes())?;
131    self.commitment.write(w)
132  }
133
134  /* Commented as it's unused, due to self being private
135  /// Serialize the OutputData to a `Vec<u8>`.
136  pub fn serialize(&self) -> Vec<u8> {
137    let mut res = Vec::with_capacity(32 + 32 + 40);
138    self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
139    res
140  }
141  */
142
143  /// Read an OutputData.
144  ///
145  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
146  /// defined serialization.
147  pub(crate) fn read<R: Read>(r: &mut R) -> io::Result<OutputData> {
148    Ok(OutputData {
149      key: read_point(r)?,
150      key_offset: read_scalar(r)?,
151      commitment: Commitment::read(r)?,
152    })
153  }
154}
155
156/// The metadata for an output.
157#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
158pub(crate) struct Metadata {
159  pub(crate) additional_timelock: Timelock,
160  pub(crate) subaddress: Option<SubaddressIndex>,
161  pub(crate) payment_id: Option<PaymentId>,
162  pub(crate) arbitrary_data: Vec<Vec<u8>>,
163}
164
165impl core::fmt::Debug for Metadata {
166  fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
167    fmt
168      .debug_struct("Metadata")
169      .field("additional_timelock", &self.additional_timelock)
170      .field("subaddress", &self.subaddress)
171      .field("payment_id", &self.payment_id)
172      .field("arbitrary_data", &self.arbitrary_data.iter().map(hex::encode).collect::<Vec<_>>())
173      .finish()
174  }
175}
176
177impl Metadata {
178  /// Write the Metadata.
179  ///
180  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
181  /// defined serialization.
182  fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
183    self.additional_timelock.write(w)?;
184
185    if let Some(subaddress) = self.subaddress {
186      w.write_all(&[1])?;
187      w.write_all(&subaddress.account().to_le_bytes())?;
188      w.write_all(&subaddress.address().to_le_bytes())?;
189    } else {
190      w.write_all(&[0])?;
191    }
192
193    if let Some(payment_id) = self.payment_id {
194      w.write_all(&[1])?;
195      payment_id.write(w)?;
196    } else {
197      w.write_all(&[0])?;
198    }
199
200    VarInt::write(&self.arbitrary_data.len(), w)?;
201    for part in &self.arbitrary_data {
202      const _ASSERT_MAX_ARBITRARY_DATA_SIZE_FITS_WITHIN_U8: [();
203        (u8::MAX as usize) - MAX_ARBITRARY_DATA_SIZE] = [(); _];
204      w.write_all(&[
205        u8::try_from(part.len()).expect("piece of arbitrary data exceeded max length of u8::MAX")
206      ])?;
207      w.write_all(part)?;
208    }
209    Ok(())
210  }
211
212  /// Read a Metadata.
213  ///
214  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
215  /// defined serialization.
216  fn read<R: Read>(r: &mut R) -> io::Result<Metadata> {
217    let additional_timelock = Timelock::read(r)?;
218
219    let subaddress = match read_byte(r)? {
220      0 => None,
221      1 => Some(
222        SubaddressIndex::new(read_u32(r)?, read_u32(r)?)
223          .ok_or_else(|| io::Error::other("invalid subaddress in metadata"))?,
224      ),
225      _ => Err(io::Error::other("invalid subaddress is_some boolean in metadata"))?,
226    };
227
228    Ok(Metadata {
229      additional_timelock,
230      subaddress,
231      payment_id: if read_byte(r)? == 1 { PaymentId::read(r).ok() } else { None },
232      /*
233        This may technically read more `arbitrary_data` than can fit in actual transaction as it
234        only checks the arbitrary data, raw, will fit in an extra, with no other structure/fields.
235      */
236      arbitrary_data: {
237        let mut data = vec![];
238        let mut total_len = 0usize;
239        for _ in 0 .. <usize as VarInt>::read(r)? {
240          let len = read_byte(r)?;
241          let chunk = read_raw_vec(read_byte, usize::from(len), r)?;
242          total_len = total_len.wrapping_add(chunk.len());
243          if total_len > MAX_EXTRA_SIZE_BY_RELAY_RULE {
244            Err(io::Error::other("amount of arbitrary data exceeded amount allowed by policy"))?;
245          }
246          data.push(chunk);
247        }
248        data
249      },
250    })
251  }
252}
253
254/// A scanned output and all associated data.
255///
256/// This struct contains all data necessary to spend this output, or handle it as a payment.
257///
258/// This struct is bound to a specific instance of the blockchain. If the blockchain reorganizes
259/// the block this struct is bound to, it MUST be discarded. If any outputs are mutual to both
260/// blockchains, scanning the new blockchain will yield those outputs again.
261#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
262pub struct WalletOutput {
263  /// The absolute ID for this transaction.
264  pub(crate) absolute_id: AbsoluteId,
265  /// The ID for this transaction, relative to the blockchain.
266  pub(crate) relative_id: RelativeId,
267  /// The output's data.
268  pub(crate) data: OutputData,
269  /// Associated metadata relevant for handling it as a payment.
270  pub(crate) metadata: Metadata,
271}
272
273impl WalletOutput {
274  /// The hash of the transaction which created this output.
275  pub fn transaction(&self) -> [u8; 32] {
276    self.absolute_id.transaction
277  }
278
279  /// The index of the output within the transaction.
280  pub fn index_in_transaction(&self) -> u64 {
281    self.absolute_id.index_in_transaction
282  }
283
284  /// The index of the output on the blockchain.
285  pub fn index_on_blockchain(&self) -> u64 {
286    self.relative_id.index_on_blockchain
287  }
288
289  /// The key this output may be spent by.
290  pub fn key(&self) -> EdwardsPoint {
291    self.data.key()
292  }
293
294  /// The scalar to add to the private spend key for it to be the discrete logarithm of this
295  /// output's key.
296  pub fn key_offset(&self) -> Scalar {
297    self.data.key_offset()
298  }
299
300  /// The commitment this output created.
301  pub fn commitment(&self) -> &Commitment {
302    self.data.commitment()
303  }
304
305  /// The additional timelock this output is subject to.
306  ///
307  /// All outputs are subject to the '10-block lock', a 10-block window after their inclusion
308  /// on-chain during which they cannot be spent. Outputs may be additionally timelocked. This
309  /// function only returns the additional timelock.
310  pub fn additional_timelock(&self) -> Timelock {
311    self.metadata.additional_timelock
312  }
313
314  /// The index of the subaddress this output was identified as sent to.
315  pub fn subaddress(&self) -> Option<SubaddressIndex> {
316    self.metadata.subaddress
317  }
318
319  /// The payment ID included with this output.
320  ///
321  /// This field may be `Some` even if wallet2 would not return a payment ID. wallet2 will only
322  /// decrypt a payment ID if either:
323  ///
324  /// A) The transaction wasn't made by the wallet (via checking if any key images are recognized)
325  /// B) For the highest-indexed input with a recognized key image, it spends an output with
326  ///    subaddress account `(a, _)` which is distinct from this output's subaddress account
327  ///
328  /// Neither of these cases are handled by `monero-wallet` as scanning doesn't have the context
329  /// of key images.
330  //
331  // Identification of the subaddress account for the highest-indexed input with a recognized key
332  // image:
333  //   https://github.com/monero-project/monero/blob/a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623
334  //     /src/wallet/wallet2.cpp/#L2637-L2670
335  //
336  // Removal of 'transfers' received to this account:
337  //   https://github.com/monero-project/monero/blob/a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623
338  //     /src/wallet/wallet2.cpp/#L2782-L2794
339  //
340  // Payment IDs only being decrypted for the remaining transfers:
341  //   https://github.com/monero-project/monero/blob/a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623
342  //     /src/wallet/wallet2.cpp/#L2796-L2844
343  pub fn payment_id(&self) -> Option<PaymentId> {
344    self.metadata.payment_id
345  }
346
347  /// The arbitrary data from the `extra` field of the transaction which created this output.
348  pub fn arbitrary_data(&self) -> &[Vec<u8>] {
349    &self.metadata.arbitrary_data
350  }
351
352  /// Write the WalletOutput.
353  ///
354  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
355  /// defined serialization.
356  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
357    self.absolute_id.write(w)?;
358    self.relative_id.write(w)?;
359    self.data.write(w)?;
360    self.metadata.write(w)
361  }
362
363  /// Serialize the WalletOutput to a `Vec<u8>`.
364  ///
365  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
366  /// defined serialization.
367  pub fn serialize(&self) -> Vec<u8> {
368    let mut serialized = Vec::with_capacity(128);
369    self.write(&mut serialized).expect("write failed but <Vec as io::Write> doesn't fail");
370    serialized
371  }
372
373  /// Read a WalletOutput.
374  ///
375  /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
376  /// defined serialization.
377  pub fn read<R: Read>(r: &mut R) -> io::Result<WalletOutput> {
378    Ok(WalletOutput {
379      absolute_id: AbsoluteId::read(r)?,
380      relative_id: RelativeId::read(r)?,
381      data: OutputData::read(r)?,
382      metadata: Metadata::read(r)?,
383    })
384  }
385}