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/// The maximum length for a transaction's extra under current relay rules.
27// https://github.com/monero-project/monero
28//  /blob/8d4c625713e3419573dfcc7119c8848f47cabbaa/src/cryptonote_config.h#L217
29pub const MAX_EXTRA_SIZE_BY_RELAY_RULE: usize = 1060;
30
31/// A Payment ID.
32///
33/// This is a legacy method of identifying why Monero was sent to the receiver.
34#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
35pub enum PaymentId {
36  /// A deprecated form of payment ID which is no longer supported.
37  Unencrypted([u8; 32]),
38  /// An encrypted payment ID.
39  Encrypted([u8; 8]),
40}
41
42impl BitXor<[u8; 8]> for PaymentId {
43  type Output = PaymentId;
44
45  fn bitxor(self, bytes: [u8; 8]) -> PaymentId {
46    match self {
47      // Don't perform the xor since this isn't intended to be encrypted with xor
48      PaymentId::Unencrypted(_) => self,
49      PaymentId::Encrypted(id) => {
50        PaymentId::Encrypted((u64::from_le_bytes(id) ^ u64::from_le_bytes(bytes)).to_le_bytes())
51      }
52    }
53  }
54}
55
56impl PaymentId {
57  /// Write the PaymentId.
58  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
59    match self {
60      PaymentId::Unencrypted(id) => {
61        w.write_all(&[PAYMENT_ID_MARKER])?;
62        w.write_all(id)?;
63      }
64      PaymentId::Encrypted(id) => {
65        w.write_all(&[ENCRYPTED_PAYMENT_ID_MARKER])?;
66        w.write_all(id)?;
67      }
68    }
69    Ok(())
70  }
71
72  /// Serialize the PaymentId to a `Vec<u8>`.
73  pub fn serialize(&self) -> Vec<u8> {
74    let mut res = Vec::with_capacity(1 + 8);
75    self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
76    res
77  }
78
79  /// Read a PaymentId.
80  pub fn read<R: Read>(r: &mut R) -> io::Result<PaymentId> {
81    Ok(match read_byte(r)? {
82      0 => PaymentId::Unencrypted(read_bytes(r)?),
83      1 => PaymentId::Encrypted(read_bytes(r)?),
84      _ => Err(io::Error::other("unknown payment ID type"))?,
85    })
86  }
87}
88
89/// A field within the TX extra.
90#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
91pub enum ExtraField {
92  /// Padding.
93  ///
94  /// This is a block of zeroes within the TX extra.
95  Padding(usize),
96  /// The transaction key.
97  ///
98  /// This is a commitment to the randomness used for deriving outputs.
99  PublicKey(EdwardsPoint),
100  /// The nonce field.
101  ///
102  /// This is used for data, such as payment IDs.
103  Nonce(Vec<u8>),
104  /// The field for merge-mining.
105  ///
106  /// This is used within miner transactions who are merge-mining Monero to specify the foreign
107  /// block they mined.
108  MergeMining(u64, [u8; 32]),
109  /// The additional transaction keys.
110  ///
111  /// These are the per-output commitments to the randomness used for deriving outputs.
112  PublicKeys(Vec<EdwardsPoint>),
113  /// The 'mysterious' Minergate tag.
114  ///
115  /// This was used by a closed source entity without documentation. Support for parsing it was
116  /// added to reduce extra which couldn't be decoded.
117  MysteriousMinergate(Vec<u8>),
118}
119
120impl ExtraField {
121  /// Write the ExtraField.
122  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
123    match self {
124      ExtraField::Padding(size) => {
125        w.write_all(&[0])?;
126        for _ in 1 .. *size {
127          write_byte(&0u8, w)?;
128        }
129      }
130      ExtraField::PublicKey(key) => {
131        w.write_all(&[1])?;
132        w.write_all(&key.compress().to_bytes())?;
133      }
134      ExtraField::Nonce(data) => {
135        w.write_all(&[2])?;
136        write_vec(write_byte, data, w)?;
137      }
138      ExtraField::MergeMining(height, merkle) => {
139        w.write_all(&[3])?;
140        VarInt::write(height, w)?;
141        w.write_all(merkle)?;
142      }
143      ExtraField::PublicKeys(keys) => {
144        w.write_all(&[4])?;
145        write_vec(write_point, keys, w)?;
146      }
147      ExtraField::MysteriousMinergate(data) => {
148        w.write_all(&[0xDE])?;
149        write_vec(write_byte, data, w)?;
150      }
151    }
152    Ok(())
153  }
154
155  /// Serialize the ExtraField to a `Vec<u8>`.
156  pub fn serialize(&self) -> Vec<u8> {
157    let mut res = Vec::with_capacity(1 + 8);
158    self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
159    res
160  }
161
162  /// Read an ExtraField.
163  pub fn read<R: BufRead>(r: &mut R) -> io::Result<ExtraField> {
164    Ok(match read_byte(r)? {
165      0 => ExtraField::Padding({
166        // Read until either non-zero, max padding count, or end of buffer
167        let mut size: usize = 1;
168        loop {
169          let buf = r.fill_buf()?;
170          let mut n_consume = 0;
171          for v in buf {
172            if *v != 0u8 {
173              Err(io::Error::other("non-zero value after padding"))?
174            }
175            n_consume += 1;
176            size += 1;
177            if size > MAX_TX_EXTRA_PADDING_COUNT {
178              Err(io::Error::other("padding exceeded max count"))?
179            }
180          }
181          if n_consume == 0 {
182            break;
183          }
184          r.consume(n_consume);
185        }
186        size
187      }),
188      1 => ExtraField::PublicKey(read_point(r)?),
189      2 => ExtraField::Nonce(read_vec(read_byte, Some(MAX_TX_EXTRA_NONCE_SIZE), r)?),
190      3 => ExtraField::MergeMining(VarInt::read(r)?, read_bytes(r)?),
191      4 => ExtraField::PublicKeys(read_vec(read_point, None, r)?),
192      0xDE => ExtraField::MysteriousMinergate(read_vec(read_byte, None, r)?),
193      _ => Err(io::Error::other("unknown extra field"))?,
194    })
195  }
196}
197
198/// The result of decoding a transaction's extra field.
199#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
200pub struct Extra(pub(crate) Vec<ExtraField>);
201impl Extra {
202  /// The keys within this extra.
203  ///
204  /// This returns all keys specified with `PublicKey` and the first set of keys specified with
205  /// `PublicKeys`, so long as they're well-formed.
206  // https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c45
207  //   /src/wallet/wallet2.cpp#L2290-L2300
208  // https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
209  // /src/wallet/wallet2.cpp#L2337-L2340
210  pub fn keys(&self) -> Option<(Vec<EdwardsPoint>, Option<Vec<EdwardsPoint>>)> {
211    let mut keys = vec![];
212    let mut additional = None;
213    for field in &self.0 {
214      match field.clone() {
215        ExtraField::PublicKey(this_key) => keys.push(this_key),
216        ExtraField::PublicKeys(these_additional) => {
217          additional = additional.or(Some(these_additional))
218        }
219        _ => (),
220      }
221    }
222    // Don't return any keys if this was non-standard and didn't include the primary key
223    if keys.is_empty() {
224      None
225    } else {
226      Some((keys, additional))
227    }
228  }
229
230  /// The payment ID embedded within this extra.
231  // Monero finds the first nonce field and reads the payment ID from it:
232  // https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/
233  //   src/wallet/wallet2.cpp#L2709-L2752
234  pub fn payment_id(&self) -> Option<PaymentId> {
235    for field in &self.0 {
236      if let ExtraField::Nonce(data) = field {
237        let mut reader = data.as_slice();
238        let res = PaymentId::read(&mut reader).ok();
239        // https://github.com/monero-project/monero/blob/8d4c625713e3419573dfcc7119c8848f47cabbaa
240        //   /src/cryptonote_basic/cryptonote_format_utils.cpp#L801
241        //
242        //   /src/cryptonote_basic/cryptonote_format_utils.cpp#L811
243        if !reader.is_empty() {
244          None?;
245        }
246        return res;
247      }
248    }
249    None
250  }
251
252  /// The arbitrary data within this extra.
253  ///
254  /// This looks for all instances of `ExtraField::Nonce` with a marker byte of 0b0111_1111. This
255  /// is the largest possible value not interpretable as a VarInt, ensuring it's able to be
256  /// interpreted as a VarInt without issue, and that it's the most unlikely value to be used by
257  /// the Monero wallet protocol itself (which itself has assigned marker bytes incrementally). As
258  /// Monero itself does not support including arbitrary data with its wallet however, this was
259  /// first introduced by `monero-wallet` (under the monero-oxide project) and may be bespoke to
260  /// the ecosystem of monero-oxide and dependents of it.
261  ///
262  /// The data is stored without any padding or encryption applied. Applications MUST consider this
263  /// themselves. As Monero does not reserve any space for arbitrary data, the inclusion of _any_
264  /// arbitrary data will _always_ be a fingerprint even before considering what the data is.
265  /// Applications SHOULD include arbitrary data indistinguishable from random, of a popular length
266  /// (such as padded to the next power of two or the maximum length per chunk) IF arbitrary data
267  /// is included at all.
268  ///
269  /// For applications where indistinguishability from 'regular' Monero transactions is required,
270  /// steganography should be considered. Steganography is somewhat-frowned upon however due to it
271  /// bloating the Monero blockchain however and efficient methods are likely specific to
272  /// individual hard forks. They may also have their own privacy implications, which is why no
273  /// methods of stegnography are supported outright by `monero-wallet`.
274  pub fn arbitrary_data(&self) -> Vec<Vec<u8>> {
275    // Only parse arbitrary data from the amount of extra data accepted under the relay rule
276    let serialized = self.serialize();
277    let bounded_extra =
278      Self::read(&mut &serialized[.. serialized.len().min(MAX_EXTRA_SIZE_BY_RELAY_RULE)])
279        .expect("`Extra::read` only fails if the IO fails and `&[u8]` won't");
280
281    let mut res = vec![];
282    for field in &bounded_extra.0 {
283      if let ExtraField::Nonce(data) = field {
284        if data.first() == Some(&ARBITRARY_DATA_MARKER) {
285          res.push(data[1 ..].to_vec());
286        }
287      }
288    }
289    res
290  }
291
292  pub(crate) fn new(key: EdwardsPoint, additional: Vec<EdwardsPoint>) -> Extra {
293    let mut res = Extra(Vec::with_capacity(3));
294    // https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
295    //   /src/cryptonote_basic/cryptonote_format_utils.cpp#L627-L633
296    // We only support pushing nonces which come after these in the sort order
297    res.0.push(ExtraField::PublicKey(key));
298    if !additional.is_empty() {
299      res.0.push(ExtraField::PublicKeys(additional));
300    }
301    res
302  }
303
304  // TODO: This allows pushing a nonce of size greater than allowed. That's likely fine as it's
305  // internal, yet should be better?
306  pub(crate) fn push_nonce(&mut self, nonce: Vec<u8>) {
307    self.0.push(ExtraField::Nonce(nonce));
308  }
309
310  /// Write the Extra.
311  ///
312  /// This is not of deterministic length nor length-prefixed. It should only be written to a
313  /// buffer which will be delimited.
314  pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
315    for field in &self.0 {
316      field.write(w)?;
317    }
318    Ok(())
319  }
320
321  /// Serialize the Extra to a `Vec<u8>`.
322  pub fn serialize(&self) -> Vec<u8> {
323    let mut buf = vec![];
324    self.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
325    buf
326  }
327
328  /// Read an `Extra`.
329  ///
330  /// This is not of deterministic length nor length-prefixed. It should only be read from a buffer
331  /// already delimited.
332  #[allow(clippy::unnecessary_wraps)]
333  pub fn read<R: BufRead>(r: &mut R) -> io::Result<Extra> {
334    let mut res = Extra(vec![]);
335    // Extra reads until EOF
336    // We take a BufRead so we can detect when the buffer is empty
337    // `fill_buf` returns the current buffer, filled if empty, only empty if the reader is
338    // exhausted
339    while !r.fill_buf()?.is_empty() {
340      let Ok(field) = ExtraField::read(r) else { break };
341      res.0.push(field);
342    }
343    Ok(res)
344  }
345}