use core::{ops::BitXor, num::NonZero};
use std_shims::{
vec,
vec::Vec,
io::{self, Read, BufRead, Write},
};
use zeroize::Zeroize;
use monero_oxide::{
io::*,
ed25519::{CompressedPoint, Point},
};
const MAX_TX_EXTRA_NONCE_SIZE: usize = 255;
const PAYMENT_ID_MARKER: u8 = 0;
const ENCRYPTED_PAYMENT_ID_MARKER: u8 = 1;
pub(crate) const ARBITRARY_DATA_MARKER: u8 = 127;
pub const MAX_ARBITRARY_DATA_SIZE: usize = MAX_TX_EXTRA_NONCE_SIZE - 1;
pub const MAX_EXTRA_SIZE_BY_RELAY_RULE: usize = 1060;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub enum PaymentId {
Unencrypted([u8; 32]),
Encrypted([u8; 8]),
}
impl BitXor<[u8; 8]> for PaymentId {
type Output = PaymentId;
fn bitxor(self, bytes: [u8; 8]) -> PaymentId {
match self {
PaymentId::Unencrypted(_) => self,
PaymentId::Encrypted(id) => {
PaymentId::Encrypted((u64::from_le_bytes(id) ^ u64::from_le_bytes(bytes)).to_le_bytes())
}
}
}
}
impl PaymentId {
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self {
PaymentId::Unencrypted(id) => {
w.write_all(&[PAYMENT_ID_MARKER])?;
w.write_all(id)?;
}
PaymentId::Encrypted(id) => {
w.write_all(&[ENCRYPTED_PAYMENT_ID_MARKER])?;
w.write_all(id)?;
}
}
Ok(())
}
pub fn serialize(&self) -> Vec<u8> {
let mut res = Vec::with_capacity(1 + 8);
self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
res
}
pub fn read<R: Read>(r: &mut R) -> io::Result<PaymentId> {
Ok(match read_byte(r)? {
0 => PaymentId::Unencrypted(read_bytes(r)?),
1 => PaymentId::Encrypted(read_bytes(r)?),
_ => Err(io::Error::other("unknown payment ID type"))?,
})
}
}
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub enum ExtraField {
Padding(NonZero<u8>),
PublicKey(CompressedPoint),
Nonce(Vec<u8>),
MergeMining(u64, [u8; 32]),
PublicKeys(Vec<CompressedPoint>),
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 .. u8::from(*size) {
write_byte(&0u8, w)?;
}
}
ExtraField::PublicKey(key) => {
w.write_all(&[1])?;
key.write(w)?;
}
ExtraField::Nonce(data) => {
w.write_all(&[2])?;
write_vec(write_byte, data, w)?;
}
ExtraField::MergeMining(depth, merkle_root) => {
w.write_all(&[3])?;
VarInt::write(&(depth.varint_len() + merkle_root.len()), w)?;
VarInt::write(depth, w)?;
w.write_all(merkle_root)?;
}
ExtraField::PublicKeys(keys) => {
w.write_all(&[4])?;
write_vec(CompressedPoint::write, keys, w)?;
}
ExtraField::MysteriousMinergate(data) => {
w.write_all(&[0xDE])?;
write_vec(write_byte, data, w)?;
}
}
Ok(())
}
pub fn serialize(&self) -> Vec<u8> {
let mut res = Vec::with_capacity(1 + 8);
self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
res
}
pub fn read<R: BufRead>(r: &mut R) -> io::Result<ExtraField> {
Ok(match read_byte(r)? {
0 => ExtraField::Padding({
let mut size = 1u8;
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;
if size == u8::MAX {
Err(io::Error::other("padding exceeded max count"))?;
}
size += 1;
}
if n_consume == 0 {
break;
}
r.consume(n_consume);
}
NonZero::new(size).expect("size started at 1 but incremented to 0?")
}),
1 => ExtraField::PublicKey(CompressedPoint::read(r)?),
2 => ExtraField::Nonce(read_vec(read_byte, Some(MAX_TX_EXTRA_NONCE_SIZE), r)?),
3 => {
let field_len = <usize as VarInt>::read(r)?;
let depth = <u64 as VarInt>::read(r)?;
let merkle_root = read_bytes(r)?;
match field_len.checked_sub(depth.varint_len() + merkle_root.len()) {
Some(remaining) => {
for _ in 0 .. remaining {
read_byte(r)?;
}
}
None => Err(io::Error::other("`MergeMining` tag had a length smaller than its fields"))?,
}
ExtraField::MergeMining(depth, merkle_root)
}
4 => ExtraField::PublicKeys(read_vec(CompressedPoint::read, None, r)?),
0xDE => ExtraField::MysteriousMinergate(read_vec(read_byte, None, r)?),
_ => Err(io::Error::other("unknown extra field"))?,
})
}
}
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct Extra(pub(crate) Vec<ExtraField>);
impl Extra {
pub fn keys(&self) -> Option<(Vec<Point>, Option<Vec<Point>>)> {
let identity = {
use curve25519_dalek::{traits::Identity as _, EdwardsPoint};
Point::from(EdwardsPoint::identity())
};
let mut keys = vec![];
let mut additional = None;
for field in &self.0 {
match field.clone() {
ExtraField::PublicKey(key) => keys.push(key.decompress().unwrap_or(identity)),
ExtraField::PublicKeys(keys) => {
additional = additional
.or(Some(keys.into_iter().map(|key| key.decompress().unwrap_or(identity)).collect()));
}
ExtraField::Padding(_) |
ExtraField::Nonce(_) |
ExtraField::MergeMining(_, _) |
ExtraField::MysteriousMinergate(_) => (),
}
}
if keys.is_empty() {
None
} else {
Some((keys, additional))
}
}
pub fn payment_id(&self) -> Option<PaymentId> {
for field in &self.0 {
if let ExtraField::Nonce(data) = field {
let mut reader = data.as_slice();
let res = PaymentId::read(&mut reader).ok();
if !reader.is_empty() {
None?;
}
return res;
}
}
None
}
pub fn arbitrary_data(&self) -> Vec<Vec<u8>> {
let serialized = self.serialize();
let bounded_extra =
Self::read(&mut &serialized[.. serialized.len().min(MAX_EXTRA_SIZE_BY_RELAY_RULE)])
.expect("`Extra::read` only fails if the IO fails and `&[u8]` won't");
let mut res = vec![];
for field in &bounded_extra.0 {
if let ExtraField::Nonce(data) = field {
if data.first() == Some(&ARBITRARY_DATA_MARKER) {
res.push(data[1 ..].to_vec());
}
}
}
res
}
pub(crate) fn new(key: CompressedPoint, additional: Vec<CompressedPoint>) -> Extra {
let mut res = Extra(Vec::with_capacity(3));
res.0.push(ExtraField::PublicKey(key));
if !additional.is_empty() {
res.0.push(ExtraField::PublicKeys(additional));
}
res
}
pub(crate) fn push_nonce(&mut self, nonce: Vec<u8>) {
self.0.push(ExtraField::Nonce(nonce));
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
#[cfg(debug_assertions)]
let mut written = 0;
const SORT_ORDER: [fn(&ExtraField) -> bool; 6] = [
|field: &ExtraField| matches!(field, ExtraField::PublicKey(_)),
|field: &ExtraField| matches!(field, ExtraField::PublicKeys(_)),
|field: &ExtraField| matches!(field, ExtraField::Nonce(_)),
|field: &ExtraField| matches!(field, ExtraField::MergeMining(_, _)),
|field: &ExtraField| matches!(field, ExtraField::MysteriousMinergate(_)),
|field: &ExtraField| matches!(field, ExtraField::Padding(_)),
];
#[cfg(monero_oxide_rust_nightly)]
const _SORT_LEN: [(); 0 - core::mem::variant_count::<ExtraField>().abs_diff(SORT_ORDER.len())] =
[(); _];
for selection in SORT_ORDER {
for field in &self.0 {
if selection(field) {
field.write(w)?;
#[cfg(debug_assertions)]
{
written += 1;
}
}
}
}
#[cfg(debug_assertions)]
debug_assert_eq!(written, self.0.len());
Ok(())
}
pub fn serialize(&self) -> Vec<u8> {
let mut buf = vec![];
self.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
buf
}
pub fn read<R: BufRead>(r: &mut R) -> io::Result<Extra> {
let mut res = Extra(vec![]);
while !r.fill_buf()?.is_empty() {
let Ok(field) = ExtraField::read(r) else { break };
res.0.push(field);
}
Ok(res)
}
}