mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
138 lines
4.9 KiB
Rust
138 lines
4.9 KiB
Rust
use std_shims::{vec::Vec, io};
|
|
|
|
use zeroize::Zeroize;
|
|
|
|
use crate::{
|
|
ringct::PrunedRctProofs,
|
|
transaction::{Input, Timelock, Pruned, Transaction},
|
|
send::SignableTransaction,
|
|
};
|
|
|
|
/// The eventual output of a SignableTransaction.
|
|
///
|
|
/// If a SignableTransaction is signed and published on-chain, it will create a Transaction
|
|
/// identifiable to whoever else has the same SignableTransaction (with the same outgoing view
|
|
/// key). This structure enables checking if a Transaction is in fact such an output, as it can.
|
|
///
|
|
/// Since Monero is a privacy coin without outgoing view keys, this only performs a fuzzy match.
|
|
/// The fuzzy match executes over the outputs and associated data necessary to work with the
|
|
/// outputs (the transaction randomness, ciphertexts). This transaction does not check if the
|
|
/// inputs intended to be spent where actually the inputs spent (as infeasible).
|
|
///
|
|
/// The transaction randomness does bind to the inputs intended to be spent, so an on-chain
|
|
/// transaction will not match for multiple `Eventuality`s unless the `SignableTransaction`s they
|
|
/// were built from were in conflict (and their intended transactions cannot simultaneously exist
|
|
/// on-chain).
|
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
|
pub struct Eventuality(SignableTransaction);
|
|
|
|
impl From<SignableTransaction> for Eventuality {
|
|
fn from(tx: SignableTransaction) -> Eventuality {
|
|
Eventuality(tx)
|
|
}
|
|
}
|
|
|
|
impl Eventuality {
|
|
/// Return the `extra` field any transaction following this intent would use.
|
|
///
|
|
/// This enables building a HashMap of Extra -> Eventuality for efficiently fetching the
|
|
/// `Eventuality` an on-chain transaction may complete.
|
|
///
|
|
/// This extra is cryptographically bound to the inputs intended to be spent. If the
|
|
/// `SignableTransaction`s the `Eventuality`s are built from are not in conflict (their intended
|
|
/// transactions can simultaneously exist on-chain), then each extra will only have a single
|
|
/// Eventuality associated (barring a cryptographic problem considered hard failing).
|
|
pub fn extra(&self) -> Vec<u8> {
|
|
self.0.extra()
|
|
}
|
|
|
|
/// Return if this TX matches the SignableTransaction this was created from.
|
|
///
|
|
/// Matching the SignableTransaction means this transaction created the expected outputs, they're
|
|
/// scannable, they're not locked, and this transaction claims to use the intended inputs (though
|
|
/// this is not guaranteed). This 'claim' is evaluated by this transaction using the transaction
|
|
/// keys derived from the intended inputs. This ensures two SignableTransactions with the same
|
|
/// intended payments don't match for each other's `Eventuality`s (as they'll have distinct
|
|
/// inputs intended).
|
|
#[must_use]
|
|
pub fn matches(&self, tx: &Transaction<Pruned>) -> bool {
|
|
// Verify extra
|
|
if self.0.extra() != tx.prefix().extra {
|
|
return false;
|
|
}
|
|
|
|
// Also ensure no timelock was set
|
|
if tx.prefix().additional_timelock != Timelock::None {
|
|
return false;
|
|
}
|
|
|
|
// Check the amount of inputs aligns
|
|
if tx.prefix().inputs.len() != self.0.inputs.len() {
|
|
return false;
|
|
}
|
|
// Collect the key images used by this transaction
|
|
let Ok(key_images) = tx
|
|
.prefix()
|
|
.inputs
|
|
.iter()
|
|
.map(|input| match input {
|
|
Input::Gen(_) => Err(()),
|
|
Input::ToKey { key_image, .. } => Ok(*key_image),
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()
|
|
else {
|
|
return false;
|
|
};
|
|
|
|
// Check the outputs
|
|
if self.0.outputs(&key_images) != tx.prefix().outputs {
|
|
return false;
|
|
}
|
|
|
|
// Check the encrypted amounts and commitments
|
|
let commitments_and_encrypted_amounts = self.0.commitments_and_encrypted_amounts(&key_images);
|
|
let Transaction::V2 { proofs: Some(PrunedRctProofs { ref base, .. }), .. } = tx else {
|
|
return false;
|
|
};
|
|
if base.commitments !=
|
|
commitments_and_encrypted_amounts
|
|
.iter()
|
|
.map(|(commitment, _)| commitment.calculate())
|
|
.collect::<Vec<_>>()
|
|
{
|
|
return false;
|
|
}
|
|
if base.encrypted_amounts !=
|
|
commitments_and_encrypted_amounts.into_iter().map(|(_, amount)| amount).collect::<Vec<_>>()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
true
|
|
}
|
|
|
|
/// Write the Eventuality.
|
|
///
|
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
|
/// defined serialization.
|
|
pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
|
self.0.write(w)
|
|
}
|
|
|
|
/// Serialize the Eventuality to a `Vec<u8>`.
|
|
///
|
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
|
/// defined serialization.
|
|
pub fn serialize(&self) -> Vec<u8> {
|
|
self.0.serialize()
|
|
}
|
|
|
|
/// Read a Eventuality.
|
|
///
|
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
|
/// defined serialization.
|
|
pub fn read<R: io::Read>(r: &mut R) -> io::Result<Eventuality> {
|
|
Ok(Eventuality(SignableTransaction::read(r)?))
|
|
}
|
|
}
|