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