From 4f1f7984a6032703de0aeda39acba7cb2a42f349 Mon Sep 17 00:00:00 2001 From: Justin Berman Date: Mon, 19 Feb 2024 18:22:00 -0800 Subject: [PATCH] monero: added tx extra variants padding and mysterious minergate (#510) * monero: read/write tx extra padding * monero: read/write tx extra mysterious minergate variant * Clippy * monero: add tx extra test for minergate + pub key * BufRead --------- Co-authored-by: Luke Parker --- coins/monero/src/tests/extra.rs | 158 +++++++++++++++++++++++++++++++ coins/monero/src/tests/mod.rs | 1 + coins/monero/src/wallet/extra.rs | 45 ++++++++- common/std-shims/src/io.rs | 14 +++ 4 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 coins/monero/src/tests/extra.rs diff --git a/coins/monero/src/tests/extra.rs b/coins/monero/src/tests/extra.rs new file mode 100644 index 00000000..b727fe9d --- /dev/null +++ b/coins/monero/src/tests/extra.rs @@ -0,0 +1,158 @@ +use crate::{ + wallet::{ExtraField, Extra, extra::MAX_TX_EXTRA_PADDING_COUNT}, + serialize::write_varint, +}; + +use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY}; + +// Borrowed tests from +// https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/ +// tests/unit_tests/test_tx_utils.cpp + +const PUB_KEY_BYTES: [u8; 33] = [ + 1, 30, 208, 98, 162, 133, 64, 85, 83, 112, 91, 188, 89, 211, 24, 131, 39, 154, 22, 228, 80, 63, + 198, 141, 173, 111, 244, 183, 4, 149, 186, 140, 230, +]; + +fn pub_key() -> EdwardsPoint { + CompressedEdwardsY(PUB_KEY_BYTES[1 .. PUB_KEY_BYTES.len()].try_into().expect("invalid pub key")) + .decompress() + .unwrap() +} + +fn test_write_buf(extra: &Extra, buf: &[u8]) { + let mut w: Vec = vec![]; + Extra::write(extra, &mut w).unwrap(); + assert_eq!(buf, w); +} + +#[test] +fn empty_extra() { + let buf: Vec = vec![]; + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert!(extra.0.is_empty()); + test_write_buf(&extra, &buf); +} + +#[test] +fn padding_only_size_1() { + let buf: Vec = vec![0]; + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert_eq!(extra.0, vec![ExtraField::Padding(1)]); + test_write_buf(&extra, &buf); +} + +#[test] +fn padding_only_size_2() { + let buf: Vec = vec![0, 0]; + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert_eq!(extra.0, vec![ExtraField::Padding(2)]); + test_write_buf(&extra, &buf); +} + +#[test] +fn padding_only_max_size() { + let buf: Vec = vec![0; MAX_TX_EXTRA_PADDING_COUNT]; + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert_eq!(extra.0, vec![ExtraField::Padding(MAX_TX_EXTRA_PADDING_COUNT)]); + test_write_buf(&extra, &buf); +} + +#[test] +fn padding_only_exceed_max_size() { + let buf: Vec = vec![0; MAX_TX_EXTRA_PADDING_COUNT + 1]; + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert!(extra.0.is_empty()); +} + +#[test] +fn invalid_padding_only() { + let buf: Vec = vec![0, 42]; + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert!(extra.0.is_empty()); +} + +#[test] +fn pub_key_only() { + let buf: Vec = PUB_KEY_BYTES.to_vec(); + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert_eq!(extra.0, vec![ExtraField::PublicKey(pub_key())]); + test_write_buf(&extra, &buf); +} + +#[test] +fn extra_nonce_only() { + let buf: Vec = vec![2, 1, 42]; + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert_eq!(extra.0, vec![ExtraField::Nonce(vec![42])]); + test_write_buf(&extra, &buf); +} + +#[test] +fn extra_nonce_only_wrong_size() { + let mut buf: Vec = vec![0; 20]; + buf[0] = 2; + buf[1] = 255; + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert!(extra.0.is_empty()); +} + +#[test] +fn pub_key_and_padding() { + let mut buf: Vec = PUB_KEY_BYTES.to_vec(); + buf.extend([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert_eq!(extra.0, vec![ExtraField::PublicKey(pub_key()), ExtraField::Padding(76)]); + test_write_buf(&extra, &buf); +} + +#[test] +fn pub_key_and_invalid_padding() { + let mut buf: Vec = PUB_KEY_BYTES.to_vec(); + buf.extend([0, 1]); + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert_eq!(extra.0, vec![ExtraField::PublicKey(pub_key())]); +} + +#[test] +fn extra_mysterious_minergate_only() { + let buf: Vec = vec![222, 1, 42]; + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert_eq!(extra.0, vec![ExtraField::MysteriousMinergate(vec![42])]); + test_write_buf(&extra, &buf); +} + +#[test] +fn extra_mysterious_minergate_only_large() { + let mut buf: Vec = vec![222]; + write_varint(&512u64, &mut buf).unwrap(); + buf.extend_from_slice(&vec![0; 512]); + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert_eq!(extra.0, vec![ExtraField::MysteriousMinergate(vec![0; 512])]); + test_write_buf(&extra, &buf); +} + +#[test] +fn extra_mysterious_minergate_only_wrong_size() { + let mut buf: Vec = vec![0; 20]; + buf[0] = 222; + buf[1] = 255; + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert!(extra.0.is_empty()); +} + +#[test] +fn extra_mysterious_minergate_and_pub_key() { + let mut buf: Vec = vec![222, 1, 42]; + buf.extend(PUB_KEY_BYTES.to_vec()); + let extra = Extra::read::<&[u8]>(&mut buf.as_ref()).unwrap(); + assert_eq!( + extra.0, + vec![ExtraField::MysteriousMinergate(vec![42]), ExtraField::PublicKey(pub_key())] + ); + test_write_buf(&extra, &buf); +} diff --git a/coins/monero/src/tests/mod.rs b/coins/monero/src/tests/mod.rs index 64e72500..33d56f22 100644 --- a/coins/monero/src/tests/mod.rs +++ b/coins/monero/src/tests/mod.rs @@ -3,3 +3,4 @@ mod clsag; mod bulletproofs; mod address; mod seed; +mod extra; diff --git a/coins/monero/src/wallet/extra.rs b/coins/monero/src/wallet/extra.rs index 5f544a7f..deed8036 100644 --- a/coins/monero/src/wallet/extra.rs +++ b/coins/monero/src/wallet/extra.rs @@ -1,7 +1,7 @@ use core::ops::BitXor; use std_shims::{ vec::Vec, - io::{self, Read, Write}, + io::{self, Read, BufRead, Write}, }; use zeroize::Zeroize; @@ -13,6 +13,7 @@ use crate::serialize::{ write_point, write_vec, }; +pub const MAX_TX_EXTRA_PADDING_COUNT: usize = 255; pub const MAX_TX_EXTRA_NONCE_SIZE: usize = 255; pub const PAYMENT_ID_MARKER: u8 = 0; @@ -70,15 +71,23 @@ impl PaymentId { // Doesn't bother with padding nor MinerGate #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] pub enum ExtraField { + Padding(usize), PublicKey(EdwardsPoint), Nonce(Vec), MergeMining(usize, [u8; 32]), PublicKeys(Vec), + MysteriousMinergate(Vec), } impl ExtraField { pub fn write(&self, w: &mut W) -> io::Result<()> { match self { + ExtraField::Padding(size) => { + w.write_all(&[0])?; + for _ in 1 .. *size { + write_byte(&0u8, w)?; + } + } ExtraField::PublicKey(key) => { w.write_all(&[1])?; w.write_all(&key.compress().to_bytes())?; @@ -96,12 +105,39 @@ impl ExtraField { w.write_all(&[4])?; write_vec(write_point, keys, w)?; } + ExtraField::MysteriousMinergate(data) => { + w.write_all(&[0xDE])?; + write_vec(write_byte, data, w)?; + } } Ok(()) } - pub fn read(r: &mut R) -> io::Result { + pub fn read(r: &mut R) -> io::Result { Ok(match read_byte(r)? { + 0 => ExtraField::Padding({ + // Read until either non-zero, max padding count, or end of buffer + let mut size: usize = 1; + loop { + let buf = r.fill_buf()?; + let mut n_consume = 0; + for v in buf { + if *v != 0u8 { + Err(io::Error::other("non-zero value after padding"))? + } + n_consume += 1; + size += 1; + if size > MAX_TX_EXTRA_PADDING_COUNT { + Err(io::Error::other("padding exceeded max count"))? + } + } + if n_consume == 0 { + break; + } + r.consume(n_consume); + } + size + }), 1 => ExtraField::PublicKey(read_point(r)?), 2 => ExtraField::Nonce({ let nonce = read_vec(read_byte, r)?; @@ -112,13 +148,14 @@ impl ExtraField { }), 3 => ExtraField::MergeMining(read_varint(r)?, read_bytes(r)?), 4 => ExtraField::PublicKeys(read_vec(read_point, r)?), + 0xDE => ExtraField::MysteriousMinergate(read_vec(read_byte, r)?), _ => Err(io::Error::other("unknown extra field"))?, }) } } #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] -pub struct Extra(Vec); +pub struct Extra(pub(crate) Vec); impl Extra { pub fn keys(&self) -> Option<(Vec, Option>)> { let mut keys = vec![]; @@ -204,7 +241,7 @@ impl Extra { buf } - pub fn read(r: &mut R) -> io::Result { + pub fn read(r: &mut R) -> io::Result { let mut res = Extra(vec![]); let mut field; while { diff --git a/common/std-shims/src/io.rs b/common/std-shims/src/io.rs index 5950d6ee..3f049a46 100644 --- a/common/std-shims/src/io.rs +++ b/common/std-shims/src/io.rs @@ -64,6 +64,20 @@ mod shims { } } + pub trait BufRead: Read { + fn fill_buf(&mut self) -> Result<&[u8]>; + fn consume(&mut self, amt: usize); + } + + impl BufRead for &[u8] { + fn fill_buf(&mut self) -> Result<&[u8]> { + Ok(*self) + } + fn consume(&mut self, amt: usize) { + *self = &self[amt ..]; + } + } + pub trait Write { fn write(&mut self, buf: &[u8]) -> Result; fn write_all(&mut self, buf: &[u8]) -> Result<()> {