Skip to content

Commit

Permalink
monero: added tx extra variants padding and mysterious minergate (#510)
Browse files Browse the repository at this point in the history
* 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 <lukeparker5132@gmail.com>
  • Loading branch information
j-berman and kayabaNerve authored Feb 20, 2024
1 parent cda14ac commit 4f1f798
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 4 deletions.
158 changes: 158 additions & 0 deletions coins/monero/src/tests/extra.rs
Original file line number Diff line number Diff line change
@@ -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<u8> = vec![];
Extra::write(extra, &mut w).unwrap();
assert_eq!(buf, w);
}

#[test]
fn empty_extra() {
let buf: Vec<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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);
}
1 change: 1 addition & 0 deletions coins/monero/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ mod clsag;
mod bulletproofs;
mod address;
mod seed;
mod extra;
45 changes: 41 additions & 4 deletions coins/monero/src/wallet/extra.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<u8>),
MergeMining(usize, [u8; 32]),
PublicKeys(Vec<EdwardsPoint>),
MysteriousMinergate(Vec<u8>),
}

impl ExtraField {
pub fn write<W: 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())?;
Expand All @@ -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: Read>(r: &mut R) -> io::Result<ExtraField> {
pub fn read<R: BufRead>(r: &mut R) -> io::Result<ExtraField> {
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)?;
Expand All @@ -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<ExtraField>);
pub struct Extra(pub(crate) Vec<ExtraField>);
impl Extra {
pub fn keys(&self) -> Option<(Vec<EdwardsPoint>, Option<Vec<EdwardsPoint>>)> {
let mut keys = vec![];
Expand Down Expand Up @@ -204,7 +241,7 @@ impl Extra {
buf
}

pub fn read<R: Read>(r: &mut R) -> io::Result<Extra> {
pub fn read<R: BufRead>(r: &mut R) -> io::Result<Extra> {
let mut res = Extra(vec![]);
let mut field;
while {
Expand Down
14 changes: 14 additions & 0 deletions common/std-shims/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize>;
fn write_all(&mut self, buf: &[u8]) -> Result<()> {
Expand Down

0 comments on commit 4f1f798

Please sign in to comment.