Remove Monero torsion-free requirement and make output keys 32 bytes

Maintains the torsion-free requirement in the one place it's used (key 
images).

In the modern day, the output keys are checked to be points, yet in 
older protocol versions they were allowed to be arbitrary bytes.

Closes https://github.com/serai-dex/serai/issues/23 and 
https://github.com/serai-dex/serai/issues/25.
This commit is contained in:
Luke Parker
2022-08-30 01:02:55 -04:00
parent ee6316b26b
commit d620231530
5 changed files with 28 additions and 20 deletions

View File

@@ -104,11 +104,15 @@ pub(crate) fn read_point<R: io::Read>(r: &mut R) -> io::Result<EdwardsPoint> {
let bytes = read_bytes(r)?; let bytes = read_bytes(r)?;
CompressedEdwardsY(bytes) CompressedEdwardsY(bytes)
.decompress() .decompress()
// Ban torsioned points, and points which are either unreduced or -0 // Ban points which are either unreduced or -0
.filter(|point| point.is_torsion_free() && (point.compress().to_bytes() == bytes)) .filter(|point| point.compress().to_bytes() == bytes)
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point")) .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point"))
} }
pub(crate) fn read_torsion_free_point<R: io::Read>(r: &mut R) -> io::Result<EdwardsPoint> {
read_point(r).ok().filter(|point| point.is_torsion_free()).ok_or(io::Error::new(io::ErrorKind::Other, "invalid point"))
}
pub(crate) fn read_raw_vec<R: io::Read, T, F: Fn(&mut R) -> io::Result<T>>( pub(crate) fn read_raw_vec<R: io::Read, T, F: Fn(&mut R) -> io::Result<T>>(
f: F, f: F,
len: usize, len: usize,

View File

@@ -2,7 +2,7 @@ use core::cmp::Ordering;
use zeroize::Zeroize; use zeroize::Zeroize;
use curve25519_dalek::edwards::EdwardsPoint; use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
use crate::{ use crate::{
Protocol, hash, Protocol, hash,
@@ -46,7 +46,7 @@ impl Input {
2 => Input::ToKey { 2 => Input::ToKey {
amount: read_varint(r)?, amount: read_varint(r)?,
key_offsets: read_vec(read_varint, r)?, key_offsets: read_vec(read_varint, r)?,
key_image: read_point(r)?, key_image: read_torsion_free_point(r)?,
}, },
_ => Err(std::io::Error::new( _ => Err(std::io::Error::new(
std::io::ErrorKind::Other, std::io::ErrorKind::Other,
@@ -60,7 +60,7 @@ impl Input {
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub struct Output { pub struct Output {
pub amount: u64, pub amount: u64,
pub key: EdwardsPoint, pub key: CompressedEdwardsY,
pub view_tag: Option<u8>, pub view_tag: Option<u8>,
} }
@@ -72,7 +72,7 @@ impl Output {
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> { pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
write_varint(&self.amount, w)?; write_varint(&self.amount, w)?;
w.write_all(&[2 + (if self.view_tag.is_some() { 1 } else { 0 })])?; w.write_all(&[2 + (if self.view_tag.is_some() { 1 } else { 0 })])?;
write_point(&self.key, w)?; w.write_all(&self.key.to_bytes())?;
if let Some(view_tag) = self.view_tag { if let Some(view_tag) = self.view_tag {
w.write_all(&[view_tag])?; w.write_all(&[view_tag])?;
} }
@@ -92,7 +92,7 @@ impl Output {
Ok(Output { Ok(Output {
amount, amount,
key: read_point(r)?, key: CompressedEdwardsY(read_bytes(r)?),
view_tag: if view_tag { Some(read_byte(r)?) } else { None }, view_tag: if view_tag { Some(read_byte(r)?) } else { None },
}) })
} }

View File

@@ -65,8 +65,7 @@ pub struct Metadata {
pub subaddress: (u32, u32), pub subaddress: (u32, u32),
// Can be an Option, as extra doesn't necessarily have a payment ID, yet all Monero TXs should // Can be an Option, as extra doesn't necessarily have a payment ID, yet all Monero TXs should
// have this // have this
// This will be gibberish if the payment ID wasn't intended for the recipient // This will be gibberish if the payment ID wasn't intended for the recipient or wasn't included
// This will be [0xff; 8] if the transaction didn't have a payment ID
// 0xff was chosen as it'd be distinct from [0; 8], enabling atomically incrementing IDs (though // 0xff was chosen as it'd be distinct from [0; 8], enabling atomically incrementing IDs (though
// they should be randomly generated) // they should be randomly generated)
pub payment_id: [u8; 8], pub payment_id: [u8; 8],
@@ -211,14 +210,19 @@ impl Scanner {
let mut res = vec![]; let mut res = vec![];
for (o, output) in tx.prefix.outputs.iter().enumerate() { for (o, output) in tx.prefix.outputs.iter().enumerate() {
// https://github.com/serai-dex/serai/issues/102 // https://github.com/serai-dex/serai/issues/106
let output_key_compressed = output.key.compress();
if let Some(burning_bug) = self.burning_bug.as_ref() { if let Some(burning_bug) = self.burning_bug.as_ref() {
if burning_bug.contains(&output_key_compressed) { if burning_bug.contains(&output.key) {
continue; continue;
} }
} }
let output_key = output.key.decompress();
if output_key.is_none() {
continue;
}
let output_key = output_key.unwrap();
for key in &keys { for key in &keys {
let (view_tag, shared_key, payment_id_xor) = shared_key( let (view_tag, shared_key, payment_id_xor) = shared_key(
if self.burning_bug.is_none() { Some(uniqueness(&tx.prefix.inputs)) } else { None }, if self.burning_bug.is_none() { Some(uniqueness(&tx.prefix.inputs)) } else { None },
@@ -231,7 +235,7 @@ impl Scanner {
if let Some(PaymentId::Encrypted(id)) = payment_id.map(|id| id ^ payment_id_xor) { if let Some(PaymentId::Encrypted(id)) = payment_id.map(|id| id ^ payment_id_xor) {
id id
} else { } else {
[0xff; 8] payment_id_xor
}; };
if let Some(actual_view_tag) = output.view_tag { if let Some(actual_view_tag) = output.view_tag {
@@ -243,15 +247,15 @@ impl Scanner {
// P - shared == spend // P - shared == spend
let subaddress = self let subaddress = self
.subaddresses .subaddresses
.get(&(output.key - (&shared_key * &ED25519_BASEPOINT_TABLE)).compress()); .get(&(output_key - (&shared_key * &ED25519_BASEPOINT_TABLE)).compress());
if subaddress.is_none() { if subaddress.is_none() {
continue; continue;
} }
// If it has torsion, it'll substract the non-torsioned shared key to a torsioned key // If it has torsion, it'll substract the non-torsioned shared key to a torsioned key
// We will not have a torsioned key in our HashMap of keys, so we wouldn't identify it as // We will not have a torsioned key in our HashMap of keys, so we wouldn't identify it as
// ours // ours
// If we did, it'd enable bypassing the included burning bug protection however // If we did though, it'd enable bypassing the included burning bug protection
debug_assert!(output.key.is_torsion_free()); debug_assert!(output_key.is_torsion_free());
let key_offset = shared_key + self.pair.subaddress(*subaddress.unwrap()); let key_offset = shared_key + self.pair.subaddress(*subaddress.unwrap());
// Since we've found an output to us, get its amount // Since we've found an output to us, get its amount
@@ -282,13 +286,13 @@ impl Scanner {
res.push(ReceivedOutput { res.push(ReceivedOutput {
absolute: AbsoluteId { tx: tx.hash(), o: o.try_into().unwrap() }, absolute: AbsoluteId { tx: tx.hash(), o: o.try_into().unwrap() },
data: OutputData { key: output.key, key_offset, commitment }, data: OutputData { key: output_key, key_offset, commitment },
metadata: Metadata { subaddress: (0, 0), payment_id }, metadata: Metadata { subaddress: (0, 0), payment_id },
}); });
if let Some(burning_bug) = self.burning_bug.as_mut() { if let Some(burning_bug) = self.burning_bug.as_mut() {
burning_bug.insert(output_key_compressed); burning_bug.insert(output.key);
} }
} }
// Break to prevent public keys from being included multiple times, triggering multiple // Break to prevent public keys from being included multiple times, triggering multiple

View File

@@ -305,7 +305,7 @@ impl SignableTransaction {
for output in &outputs { for output in &outputs {
tx_outputs.push(Output { tx_outputs.push(Output {
amount: 0, amount: 0,
key: output.dest, key: output.dest.compress(),
view_tag: Some(output.view_tag).filter(|_| matches!(self.protocol, Protocol::v16)), view_tag: Some(output.view_tag).filter(|_| matches!(self.protocol, Protocol::v16)),
}); });
ecdh_info.push(output.amount); ecdh_info.push(output.amount);

View File

@@ -199,7 +199,7 @@ impl Coin for Monero {
Ok(( Ok((
tx.hash().to_vec(), tx.hash().to_vec(),
tx.prefix.outputs.iter().map(|output| output.key.compress().to_bytes()).collect(), tx.prefix.outputs.iter().map(|output| output.key.to_bytes()).collect(),
)) ))
} }