From 3282b1953653dca5c4771aec43b70544282feba9 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 21 May 2022 20:27:21 -0400 Subject: [PATCH] Transaction deserialization --- coins/monero/src/bulletproofs.rs | 4 +- coins/monero/src/clsag/mod.rs | 10 +++ coins/monero/src/rpc.rs | 20 ++--- coins/monero/src/serialize/mod.rs | 7 +- coins/monero/src/transaction.rs | 120 ++++++++++++++++++++++++++++-- 5 files changed, 140 insertions(+), 21 deletions(-) diff --git a/coins/monero/src/bulletproofs.rs b/coins/monero/src/bulletproofs.rs index f59f4ca4..e8e84bd3 100644 --- a/coins/monero/src/bulletproofs.rs +++ b/coins/monero/src/bulletproofs.rs @@ -104,8 +104,8 @@ impl Bulletproofs { T2: read_point(r)?, taux: read_scalar(r)?, mu: read_scalar(r)?, - L: read_vec(r, read_point)?, - R: read_vec(r, read_point)?, + L: read_vec(read_point, r)?, + R: read_vec(read_point, r)?, a: read_scalar(r)?, b: read_scalar(r)?, t: read_scalar(r)? diff --git a/coins/monero/src/clsag/mod.rs b/coins/monero/src/clsag/mod.rs index ae823e4e..427133da 100644 --- a/coins/monero/src/clsag/mod.rs +++ b/coins/monero/src/clsag/mod.rs @@ -292,6 +292,16 @@ impl Clsag { write_point(&self.D, w) } + pub fn deserialize(decoys: usize, r: &mut R) -> std::io::Result { + Ok( + Clsag { + s: read_raw_vec(read_scalar, decoys, r)?, + c1: read_scalar(r)?, + D: read_point(r)? + } + ) + } + pub fn verify( &self, ring: &[[EdwardsPoint; 2]], diff --git a/coins/monero/src/rpc.rs b/coins/monero/src/rpc.rs index e4845315..839cb291 100644 --- a/coins/monero/src/rpc.rs +++ b/coins/monero/src/rpc.rs @@ -9,7 +9,7 @@ use serde_json::json; use reqwest; -use crate::transaction::Transaction; +use crate::transaction::{Input, Transaction}; #[derive(Deserialize, Debug)] pub struct EmptyResponse {} @@ -115,24 +115,24 @@ impl Rpc { Err(RpcError::TransactionsNotFound(txs.txs.len(), hashes.len()))?; } - /* Ok( + // Ignores transactions we fail to parse txs.txs.iter().filter_map( - |tx| rpc_hex(if tx.as_hex.len() != 0 { &tx.as_hex } else { &tx.pruned_as_hex }).ok() - .and_then(|mut bytes| Transaction::deserialize(&mut bytes).ok()) + |res| rpc_hex(if res.as_hex.len() != 0 { &res.as_hex } else { &res.pruned_as_hex }).ok() + .and_then(|bytes| Transaction::deserialize(&mut std::io::Cursor::new(bytes)).ok()) // https://github.com/monero-project/monero/issues/8311 .filter( - if tx.as_hex.len() == 0 { - match res[res.len() - 1].prefix.inputs[0] { - Input::Gen { .. } => true, + |tx| if res.as_hex.len() == 0 { + match tx.prefix.inputs.get(0) { + Some(Input::Gen { .. }) => true, _ => false } + } else { + true } ) - ) + ).collect() ) - */ - Ok(vec![]) } /* diff --git a/coins/monero/src/serialize/mod.rs b/coins/monero/src/serialize/mod.rs index 62818eef..5a8eb0f0 100644 --- a/coins/monero/src/serialize/mod.rs +++ b/coins/monero/src/serialize/mod.rs @@ -85,8 +85,7 @@ pub fn read_point(r: &mut R) -> io::Result { ).decompress().filter(|point| point.is_torsion_free()).ok_or(io::Error::new(io::ErrorKind::Other, "invalid point")) } -pub fn read_vec io::Result>(r: &mut R, f: F) -> io::Result> { - let len = read_varint(r)?; +pub fn read_raw_vec io::Result>(f: F, len: usize, r: &mut R) -> io::Result> { let mut res = Vec::with_capacity( len.try_into().map_err(|_| io::Error::new(io::ErrorKind::Other, "length exceeds usize"))? ); @@ -95,3 +94,7 @@ pub fn read_vec io::Result>(r: &mut R, f: F) } Ok(res) } + +pub fn read_vec io::Result>(f: F, r: &mut R) -> io::Result> { + read_raw_vec(f, read_varint(r)?.try_into().unwrap(), r) +} diff --git a/coins/monero/src/transaction.rs b/coins/monero/src/transaction.rs index 4d6a807e..abe7c721 100644 --- a/coins/monero/src/transaction.rs +++ b/coins/monero/src/transaction.rs @@ -32,6 +32,22 @@ impl Input { } } } + + pub fn deserialize(r: &mut R) -> std::io::Result { + let mut variant = [0; 1]; + r.read_exact(&mut variant)?; + Ok( + match variant[0] { + 0 => Input::Gen(read_varint(r)?), + 2 => Input::ToKey { + amount: read_varint(r)?, + key_offsets: read_vec(read_varint, r)?, + key_image: read_point(r)? + }, + _ => Err(std::io::Error::new(std::io::ErrorKind::Other, "Tried to deserialize unknown/unused output type"))? + } + ) + } } // Doesn't bother moving to an enum for the unused Script classes @@ -51,6 +67,23 @@ impl Output { } Ok(()) } + + pub fn deserialize(r: &mut R) -> std::io::Result { + let amount = read_varint(r)?; + let mut tag = [0; 1]; + r.read_exact(&mut tag)?; + if (tag[0] != 2) && (tag[0] != 3) { + Err(std::io::Error::new(std::io::ErrorKind::Other, "Tried to deserialize unknown/unused output type"))?; + } + + Ok( + Output { + amount, + key: read_point(r)?, + tag: if tag[0] == 3 { r.read_exact(&mut tag)?; Some(tag[0]) } else { None } + } + ) + } } pub struct TransactionPrefix { @@ -70,6 +103,22 @@ impl TransactionPrefix { write_varint(&self.extra.len().try_into().unwrap(), w)?; w.write_all(&self.extra) } + + pub fn deserialize(r: &mut R) -> std::io::Result { + let mut prefix = TransactionPrefix { + version: read_varint(r)?, + unlock_time: read_varint(r)?, + inputs: read_vec(Input::deserialize, r)?, + outputs: read_vec(Output::deserialize, r)?, + extra: vec![] + }; + + let len = read_varint(r)?; + prefix.extra.resize(len.try_into().unwrap(), 0); + r.read_exact(&mut prefix.extra)?; + + Ok(prefix) + } } pub struct RctBase { @@ -87,6 +136,21 @@ impl RctBase { } write_raw_vec(write_point, &self.commitments, w) } + + pub fn deserialize(outputs: usize, r: &mut R) -> std::io::Result<(RctBase, u8)> { + let mut rct_type = [0]; + r.read_exact(&mut rct_type)?; + Ok(( + RctBase { + fee: read_varint(r)?, + ecdh_info: (0 .. outputs).map( + |_| { let mut ecdh = [0; 8]; r.read_exact(&mut ecdh).map(|_| ecdh) } + ).collect::>()?, + commitments: read_raw_vec(read_point, outputs, r)? + }, + rct_type[0] + )) + } } pub enum RctPrunable { @@ -106,13 +170,6 @@ impl RctPrunable { } } - pub fn signature_serialize(&self, w: &mut W) -> std::io::Result<()> { - match self { - RctPrunable::Null => panic!("Serializing RctPrunable::Null for a signature"), - RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.iter().map(|bp| bp.signature_serialize(w)).collect(), - } - } - pub fn serialize(&self, w: &mut W) -> std::io::Result<()> { match self { RctPrunable::Null => Ok(()), @@ -123,6 +180,33 @@ impl RctPrunable { } } } + + pub fn deserialize( + rct_type: u8, + decoys: &[usize], + outputs: usize, + r: &mut R + ) -> std::io::Result { + Ok( + match rct_type { + 0 => RctPrunable::Null, + 5 => RctPrunable::Clsag { + // TODO: Can the amount of outputs be calculated from the BPs for any validly formed TX? + bulletproofs: read_vec(Bulletproofs::deserialize, r)?, + clsags: (0 .. decoys.len()).map(|o| Clsag::deserialize(decoys[o], r)).collect::>()?, + pseudo_outs: read_raw_vec(read_point, outputs, r)? + }, + _ => Err(std::io::Error::new(std::io::ErrorKind::Other, "Tried to deserialize unknown RCT type"))? + } + ) + } + + pub fn signature_serialize(&self, w: &mut W) -> std::io::Result<()> { + match self { + RctPrunable::Null => panic!("Serializing RctPrunable::Null for a signature"), + RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.iter().map(|bp| bp.signature_serialize(w)).collect(), + } + } } pub struct RctSignatures { @@ -135,6 +219,11 @@ impl RctSignatures { self.base.serialize(w, self.prunable.rct_type())?; self.prunable.serialize(w) } + + pub fn deserialize(decoys: Vec, outputs: usize, r: &mut R) -> std::io::Result { + let base = RctBase::deserialize(outputs, r)?; + Ok(RctSignatures { base: base.0, prunable: RctPrunable::deserialize(base.1, &decoys, outputs, r)? }) + } } pub struct Transaction { @@ -148,6 +237,23 @@ impl Transaction { self.rct_signatures.serialize(w) } + pub fn deserialize(r: &mut R) -> std::io::Result { + let prefix = TransactionPrefix::deserialize(r)?; + Ok( + Transaction { + rct_signatures: RctSignatures::deserialize( + prefix.inputs.iter().map(|input| match input { + Input::Gen(_) => 0, + Input::ToKey { key_offsets, .. } => key_offsets.len() + }).collect(), + prefix.outputs.len(), + r + )?, + prefix + } + ) + } + pub fn hash(&self) -> [u8; 32] { let mut serialized = Vec::with_capacity(2048); if self.prefix.version == 1 {