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}