diff --git a/coins/monero/src/rpc.rs b/coins/monero/src/rpc.rs index c83b5997..bfd23ac5 100644 --- a/coins/monero/src/rpc.rs +++ b/coins/monero/src/rpc.rs @@ -195,6 +195,10 @@ impl Rpc { .collect() } + pub async fn get_transaction(&self, tx: [u8; 32]) -> Result { + self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) + } + pub async fn get_block(&self, height: usize) -> Result { #[derive(Deserialize, Debug)] struct BlockResponse { diff --git a/coins/monero/src/serialize.rs b/coins/monero/src/serialize.rs index 9f41bf50..0767d7f2 100644 --- a/coins/monero/src/serialize.rs +++ b/coins/monero/src/serialize.rs @@ -94,7 +94,12 @@ pub(crate) fn read_varint(r: &mut R) -> io::Result { Ok(res) } -// TODO: https://github.com/serai-dex/serai/issues/25 +// All scalar fields supported by monero-serai are checked to be canonical for valid transactions +// While from_bytes_mod_order would be more flexible, it's not currently needed and would be +// inaccurate to include now. While casting a wide net may be preferable, it'd also be inaccurate +// for now. There's also further edge cases as noted by +// https://github.com/monero-project/monero/issues/8438, where some scalars had an archaic +// reduction applied pub(crate) fn read_scalar(r: &mut R) -> io::Result { Scalar::from_canonical_bytes(read_bytes(r)?) .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "unreduced scalar")) diff --git a/coins/monero/src/transaction.rs b/coins/monero/src/transaction.rs index 6dd6c7b1..b3a5c598 100644 --- a/coins/monero/src/transaction.rs +++ b/coins/monero/src/transaction.rs @@ -2,12 +2,15 @@ use core::cmp::Ordering; use zeroize::Zeroize; -use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY}; +use curve25519_dalek::{ + scalar::Scalar, + edwards::{EdwardsPoint, CompressedEdwardsY}, +}; use crate::{ Protocol, hash, serialize::*, - ringct::{RctPrunable, RctSignatures}, + ringct::{RctBase, RctPrunable, RctSignatures}, }; #[derive(Clone, PartialEq, Eq, Debug)] @@ -189,6 +192,7 @@ impl TransactionPrefix { #[derive(Clone, PartialEq, Eq, Debug)] pub struct Transaction { pub prefix: TransactionPrefix, + pub signatures: Vec<(Scalar, Scalar)>, pub rct_signatures: RctSignatures, } @@ -205,13 +209,42 @@ impl Transaction { pub fn serialize(&self, w: &mut W) -> std::io::Result<()> { self.prefix.serialize(w)?; - self.rct_signatures.serialize(w) + if self.prefix.version == 1 { + for sig in &self.signatures { + write_scalar(&sig.0, w)?; + write_scalar(&sig.1, w)?; + } + Ok(()) + } else if self.prefix.version == 2 { + self.rct_signatures.serialize(w) + } else { + panic!("Serializing a transaction with an unknown version"); + } } pub fn deserialize(r: &mut R) -> std::io::Result { let prefix = TransactionPrefix::deserialize(r)?; - Ok(Transaction { - rct_signatures: RctSignatures::deserialize( + let mut signatures = vec![]; + let mut rct_signatures = RctSignatures { + base: RctBase { fee: 0, ecdh_info: vec![], commitments: vec![] }, + prunable: RctPrunable::Null, + }; + + if prefix.version == 1 { + for _ in 0 .. prefix.inputs.len() { + signatures.push((read_scalar(r)?, read_scalar(r)?)); + } + rct_signatures.base.fee = prefix + .inputs + .iter() + .map(|input| match input { + Input::Gen(..) => 0, + Input::ToKey { amount, .. } => *amount, + }) + .sum::() + .saturating_sub(prefix.outputs.iter().map(|output| output.amount).sum()); + } else if prefix.version == 2 { + rct_signatures = RctSignatures::deserialize( prefix .inputs .iter() @@ -222,9 +255,12 @@ impl Transaction { .collect(), prefix.outputs.len(), r, - )?, - prefix, - }) + )?; + } else { + Err(std::io::Error::new(std::io::ErrorKind::Other, "Tried to deserialize unknown version"))?; + } + + Ok(Transaction { prefix, signatures, rct_signatures }) } pub fn hash(&self) -> [u8; 32] { diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index 53cd9855..2e20998d 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -334,6 +334,7 @@ impl SignableTransaction { outputs: tx_outputs, extra, }, + signatures: vec![], rct_signatures: RctSignatures { base: RctBase { fee: self.fee,