monero_wallet/
extra.rs

1use core::ops::BitXor;
2use std_shims::{
3  vec,
4  vec::Vec,
5  io::{self, Read, BufRead, Write},
6};
7
8use zeroize::Zeroize;
9
10use curve25519_dalek::edwards::EdwardsPoint;
11
12use monero_oxide::io::*;
13
14pub(crate) const MAX_TX_EXTRA_PADDING_COUNT: usize = 255;
15const MAX_TX_EXTRA_NONCE_SIZE: usize = 255;
16
17const PAYMENT_ID_MARKER: u8 = 0;
18const ENCRYPTED_PAYMENT_ID_MARKER: u8 = 1;
19// Used as it's the highest value not interpretable as a continued VarInt
20pub(crate) const ARBITRARY_DATA_MARKER: u8 = 127;
21
22/// The max amount of data which will fit within a blob of arbitrary data.
23// 1 byte is used for the marker
24pub const MAX_ARBITRARY_DATA_SIZE: usize = MAX_TX_EXTRA_NONCE_SIZE - 1;
25
26/// A Payment ID.
27///
28/// This is a legacy method of identifying why Monero was sent to the receiver.
29#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
30pub enum PaymentId {
31  /// A deprecated form of payment ID which is no longer supported.
32  Unencrypted([u8; 32]),
33  /// An encrypted payment ID.
34  Encrypted([u8; 8]),
35}
36
37impl BitXor<[u8; 8]> for PaymentId {
38  type Output = PaymentId;
39
40  fn bitxor(self, bytes: [u8; 8]) -> PaymentId {
41    match self {
42      // Don't perform the xor since this isn't intended to be encrypted with xor
43      PaymentId::Unencrypted(_) => self,
44      PaymentId::Encrypted(id) => {
45        PaymentId::Encrypted((u64::from_le_bytes(id) ^ u64::from_le_bytes(bytes)).to_le_bytes())
46      }
47    }
48  }
49}
50
51impl PaymentId {
52  /// Write the PaymentId.
53  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
54    match self {
55      PaymentId::Unencrypted(id) => {
56        w.write_all(&[PAYMENT_ID_MARKER])?;
57        w.write_all(id)?;
58      }
59      PaymentId::Encrypted(id) => {
60        w.write_all(&[ENCRYPTED_PAYMENT_ID_MARKER])?;
61        w.write_all(id)?;
62      }
63    }
64    Ok(())
65  }
66
67  /// Serialize the PaymentId to a `Vec<u8>`.
68  pub fn serialize(&self) -> Vec<u8> {
69    let mut res = Vec::with_capacity(1 + 8);
70    self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
71    res
72  }
73
74  /// Read a PaymentId.
75  pub fn read<R: Read>(r: &mut R) -> io::Result<PaymentId> {
76    Ok(match read_byte(r)? {
77      0 => PaymentId::Unencrypted(read_bytes(r)?),
78      1 => PaymentId::Encrypted(read_bytes(r)?),
79      _ => Err(io::Error::other("unknown payment ID type"))?,
80    })
81  }
82}
83
84/// A field within the TX extra.
85#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
86pub enum ExtraField {
87  /// Padding.
88  ///
89  /// This is a block of zeroes within the TX extra.
90  Padding(usize),
91  /// The transaction key.
92  ///
93  /// This is a commitment to the randomness used for deriving outputs.
94  PublicKey(EdwardsPoint),
95  /// The nonce field.
96  ///
97  /// This is used for data, such as payment IDs.
98  Nonce(Vec<u8>),
99  /// The field for merge-mining.
100  ///
101  /// This is used within miner transactions who are merge-mining Monero to specify the foreign
102  /// block they mined.
103  MergeMining(u64, [u8; 32]),
104  /// The additional transaction keys.
105  ///
106  /// These are the per-output commitments to the randomness used for deriving outputs.
107  PublicKeys(Vec<EdwardsPoint>),
108  /// The 'mysterious' Minergate tag.
109  ///
110  /// This was used by a closed source entity without documentation. Support for parsing it was
111  /// added to reduce extra which couldn't be decoded.
112  MysteriousMinergate(Vec<u8>),
113}
114
115impl ExtraField {
116  /// Write the ExtraField.
117  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
118    match self {
119      ExtraField::Padding(size) => {
120        w.write_all(&[0])?;
121        for _ in 1 .. *size {
122          write_byte(&0u8, w)?;
123        }
124      }
125      ExtraField::PublicKey(key) => {
126        w.write_all(&[1])?;
127        w.write_all(&key.compress().to_bytes())?;
128      }
129      ExtraField::Nonce(data) => {
130        w.write_all(&[2])?;
131        write_vec(write_byte, data, w)?;
132      }
133      ExtraField::MergeMining(height, merkle) => {
134        w.write_all(&[3])?;
135        write_varint(height, w)?;
136        w.write_all(merkle)?;
137      }
138      ExtraField::PublicKeys(keys) => {
139        w.write_all(&[4])?;
140        write_vec(write_point, keys, w)?;
141      }
142      ExtraField::MysteriousMinergate(data) => {
143        w.write_all(&[0xDE])?;
144        write_vec(write_byte, data, w)?;
145      }
146    }
147    Ok(())
148  }
149
150  /// Serialize the ExtraField to a `Vec<u8>`.
151  pub fn serialize(&self) -> Vec<u8> {
152    let mut res = Vec::with_capacity(1 + 8);
153    self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
154    res
155  }
156
157  /// Read an ExtraField.
158  pub fn read<R: BufRead>(r: &mut R) -> io::Result<ExtraField> {
159    Ok(match read_byte(r)? {
160      0 => ExtraField::Padding({
161        // Read until either non-zero, max padding count, or end of buffer
162        let mut size: usize = 1;
163        loop {
164          let buf = r.fill_buf()?;
165          let mut n_consume = 0;
166          for v in buf {
167            if *v != 0u8 {
168              Err(io::Error::other("non-zero value after padding"))?
169            }
170            n_consume += 1;
171            size += 1;
172            if size > MAX_TX_EXTRA_PADDING_COUNT {
173              Err(io::Error::other("padding exceeded max count"))?
174            }
175          }
176          if n_consume == 0 {
177            break;
178          }
179          r.consume(n_consume);
180        }
181        size
182      }),
183      1 => ExtraField::PublicKey(read_point(r)?),
184      2 => ExtraField::Nonce(read_vec(read_byte, Some(MAX_TX_EXTRA_NONCE_SIZE), r)?),
185      3 => ExtraField::MergeMining(read_varint(r)?, read_bytes(r)?),
186      4 => ExtraField::PublicKeys(read_vec(read_point, None, r)?),
187      0xDE => ExtraField::MysteriousMinergate(read_vec(read_byte, None, r)?),
188      _ => Err(io::Error::other("unknown extra field"))?,
189    })
190  }
191}
192
193/// The result of decoding a transaction's extra field.
194#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
195pub struct Extra(pub(crate) Vec<ExtraField>);
196impl Extra {
197  /// The keys within this extra.
198  ///
199  /// This returns all keys specified with `PublicKey` and the first set of keys specified with
200  /// `PublicKeys`, so long as they're well-formed.
201  // https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c45
202  //   /src/wallet/wallet2.cpp#L2290-L2300
203  // https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
204  // /src/wallet/wallet2.cpp#L2337-L2340
205  pub fn keys(&self) -> Option<(Vec<EdwardsPoint>, Option<Vec<EdwardsPoint>>)> {
206    let mut keys = vec![];
207    let mut additional = None;
208    for field in &self.0 {
209      match field.clone() {
210        ExtraField::PublicKey(this_key) => keys.push(this_key),
211        ExtraField::PublicKeys(these_additional) => {
212          additional = additional.or(Some(these_additional))
213        }
214        _ => (),
215      }
216    }
217    // Don't return any keys if this was non-standard and didn't include the primary key
218    if keys.is_empty() {
219      None
220    } else {
221      Some((keys, additional))
222    }
223  }
224
225  /// The payment ID embedded within this extra.
226  // Monero finds the first nonce field and reads the payment ID from it:
227  // https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/
228  //   src/wallet/wallet2.cpp#L2709-L2752
229  pub fn payment_id(&self) -> Option<PaymentId> {
230    for field in &self.0 {
231      if let ExtraField::Nonce(data) = field {
232        return PaymentId::read::<&[u8]>(&mut data.as_ref()).ok();
233      }
234    }
235    None
236  }
237
238  /// The arbitrary data within this extra.
239  ///
240  /// This uses a marker custom to monero-wallet.
241  pub fn data(&self) -> Vec<Vec<u8>> {
242    let mut res = vec![];
243    for field in &self.0 {
244      if let ExtraField::Nonce(data) = field {
245        if data[0] == ARBITRARY_DATA_MARKER {
246          res.push(data[1 ..].to_vec());
247        }
248      }
249    }
250    res
251  }
252
253  pub(crate) fn new(key: EdwardsPoint, additional: Vec<EdwardsPoint>) -> Extra {
254    let mut res = Extra(Vec::with_capacity(3));
255    // https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
256    //   /src/cryptonote_basic/cryptonote_format_utils.cpp#L627-L633
257    // We only support pushing nonces which come after these in the sort order
258    res.0.push(ExtraField::PublicKey(key));
259    if !additional.is_empty() {
260      res.0.push(ExtraField::PublicKeys(additional));
261    }
262    res
263  }
264
265  pub(crate) fn push_nonce(&mut self, nonce: Vec<u8>) {
266    self.0.push(ExtraField::Nonce(nonce));
267  }
268
269  /// Write the Extra.
270  ///
271  /// This is not of deterministic length nor length-prefixed. It should only be written to a
272  /// buffer which will be delimited.
273  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
274    for field in &self.0 {
275      field.write(w)?;
276    }
277    Ok(())
278  }
279
280  /// Serialize the Extra to a `Vec<u8>`.
281  pub fn serialize(&self) -> Vec<u8> {
282    let mut buf = vec![];
283    self.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
284    buf
285  }
286
287  /// Read an `Extra`.
288  ///
289  /// This is not of deterministic length nor length-prefixed. It should only be read from a buffer
290  /// already delimited.
291  #[allow(clippy::unnecessary_wraps)]
292  pub fn read<R: BufRead>(r: &mut R) -> io::Result<Extra> {
293    let mut res = Extra(vec![]);
294    // Extra reads until EOF
295    // We take a BufRead so we can detect when the buffer is empty
296    // `fill_buf` returns the current buffer, filled if empty, only empty if the reader is
297    // exhausted
298    while !r.fill_buf()?.is_empty() {
299      let Ok(field) = ExtraField::read(r) else { break };
300      res.0.push(field);
301    }
302    Ok(res)
303  }
304}