2022-05-21 15:33:35 -04:00
|
|
|
use std::convert::TryFrom;
|
|
|
|
|
|
2022-07-15 01:26:07 -04:00
|
|
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint};
|
2022-05-21 15:33:35 -04:00
|
|
|
|
|
|
|
|
use monero::{consensus::deserialize, blockdata::transaction::ExtraField};
|
|
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
Commitment,
|
2022-05-26 03:51:27 -04:00
|
|
|
serialize::{write_varint, read_32, read_scalar, read_point},
|
2022-06-02 00:00:26 -04:00
|
|
|
transaction::{Timelock, Transaction},
|
2022-07-15 01:26:07 -04:00
|
|
|
wallet::{ViewPair, uniqueness, shared_key, amount_decryption, commitment_mask},
|
2022-05-21 15:33:35 -04:00
|
|
|
};
|
|
|
|
|
|
2022-07-22 02:34:36 -04:00
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
2022-05-21 15:33:35 -04:00
|
|
|
pub struct SpendableOutput {
|
|
|
|
|
pub tx: [u8; 32],
|
2022-05-26 03:51:27 -04:00
|
|
|
pub o: u8,
|
2022-05-21 15:33:35 -04:00
|
|
|
pub key: EdwardsPoint,
|
|
|
|
|
pub key_offset: Scalar,
|
2022-07-15 01:26:07 -04:00
|
|
|
pub commitment: Commitment,
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-09 18:53:52 -04:00
|
|
|
pub struct Timelocked(Timelock, Vec<SpendableOutput>);
|
|
|
|
|
impl Timelocked {
|
|
|
|
|
pub fn timelock(&self) -> Timelock {
|
|
|
|
|
self.0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn not_locked(&self) -> Vec<SpendableOutput> {
|
|
|
|
|
if self.0 == Timelock::None {
|
|
|
|
|
return self.1.clone();
|
|
|
|
|
}
|
|
|
|
|
vec![]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns None if the Timelocks aren't comparable. Returns Some(vec![]) if none are unlocked
|
|
|
|
|
pub fn unlocked(&self, timelock: Timelock) -> Option<Vec<SpendableOutput>> {
|
|
|
|
|
// If the Timelocks are comparable, return the outputs if they're now unlocked
|
|
|
|
|
self.0.partial_cmp(&timelock).filter(|_| self.0 <= timelock).map(|_| self.1.clone())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ignore_timelock(&self) -> Vec<SpendableOutput> {
|
|
|
|
|
self.1.clone()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-26 03:51:27 -04:00
|
|
|
impl SpendableOutput {
|
|
|
|
|
pub fn serialize(&self) -> Vec<u8> {
|
|
|
|
|
let mut res = Vec::with_capacity(32 + 1 + 32 + 32 + 40);
|
|
|
|
|
res.extend(&self.tx);
|
|
|
|
|
res.push(self.o);
|
|
|
|
|
res.extend(self.key.compress().to_bytes());
|
|
|
|
|
res.extend(self.key_offset.to_bytes());
|
|
|
|
|
res.extend(self.commitment.mask.to_bytes());
|
|
|
|
|
res.extend(self.commitment.amount.to_le_bytes());
|
|
|
|
|
res
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<SpendableOutput> {
|
2022-07-15 01:26:07 -04:00
|
|
|
Ok(SpendableOutput {
|
|
|
|
|
tx: read_32(r)?,
|
|
|
|
|
o: {
|
|
|
|
|
let mut o = [0; 1];
|
|
|
|
|
r.read_exact(&mut o)?;
|
|
|
|
|
o[0]
|
|
|
|
|
},
|
|
|
|
|
key: read_point(r)?,
|
|
|
|
|
key_offset: read_scalar(r)?,
|
|
|
|
|
commitment: Commitment::new(read_scalar(r)?, {
|
|
|
|
|
let mut amount = [0; 8];
|
|
|
|
|
r.read_exact(&mut amount)?;
|
|
|
|
|
u64::from_le_bytes(amount)
|
|
|
|
|
}),
|
|
|
|
|
})
|
2022-05-26 03:51:27 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-21 15:33:35 -04:00
|
|
|
impl Transaction {
|
2022-07-15 01:26:07 -04:00
|
|
|
pub fn scan(&self, view: ViewPair, guaranteed: bool) -> Timelocked {
|
2022-05-21 15:33:35 -04:00
|
|
|
let mut extra = vec![];
|
|
|
|
|
write_varint(&u64::try_from(self.prefix.extra.len()).unwrap(), &mut extra).unwrap();
|
|
|
|
|
extra.extend(&self.prefix.extra);
|
|
|
|
|
let extra = deserialize::<ExtraField>(&extra);
|
|
|
|
|
|
|
|
|
|
let pubkeys: Vec<EdwardsPoint>;
|
|
|
|
|
if let Ok(extra) = extra {
|
|
|
|
|
let mut m_pubkeys = vec![];
|
|
|
|
|
if let Some(key) = extra.tx_pubkey() {
|
|
|
|
|
m_pubkeys.push(key);
|
|
|
|
|
}
|
|
|
|
|
if let Some(keys) = extra.tx_additional_pubkeys() {
|
|
|
|
|
m_pubkeys.extend(&keys);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-22 02:34:36 -04:00
|
|
|
pubkeys = m_pubkeys.iter().filter_map(|key| key.point.decompress()).collect();
|
2022-05-21 15:33:35 -04:00
|
|
|
} else {
|
2022-07-09 18:53:52 -04:00
|
|
|
return Timelocked(self.prefix.timelock, vec![]);
|
2022-05-21 15:33:35 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut res = vec![];
|
|
|
|
|
for (o, output) in self.prefix.outputs.iter().enumerate() {
|
|
|
|
|
// TODO: This may be replaceable by pubkeys[o]
|
|
|
|
|
for pubkey in &pubkeys {
|
2022-06-28 00:01:20 -04:00
|
|
|
let key_offset = shared_key(
|
|
|
|
|
Some(uniqueness(&self.prefix.inputs)).filter(|_| guaranteed),
|
|
|
|
|
view.view,
|
|
|
|
|
pubkey,
|
2022-07-15 01:26:07 -04:00
|
|
|
o,
|
2022-06-28 00:01:20 -04:00
|
|
|
);
|
2022-05-21 15:33:35 -04:00
|
|
|
// P - shared == spend
|
2022-06-28 00:01:20 -04:00
|
|
|
if (output.key - (&key_offset * &ED25519_BASEPOINT_TABLE)) != view.spend {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-05-21 15:33:35 -04:00
|
|
|
|
2022-06-28 00:01:20 -04:00
|
|
|
// Since we've found an output to us, get its amount
|
|
|
|
|
let mut commitment = Commitment::zero();
|
2022-05-21 15:33:35 -04:00
|
|
|
|
2022-06-28 00:01:20 -04:00
|
|
|
// Miner transaction
|
|
|
|
|
if output.amount != 0 {
|
|
|
|
|
commitment.amount = output.amount;
|
|
|
|
|
// Regular transaction
|
|
|
|
|
} else {
|
|
|
|
|
let amount = match self.rct_signatures.base.ecdh_info.get(o) {
|
|
|
|
|
Some(amount) => amount_decryption(*amount, key_offset),
|
|
|
|
|
// This should never happen, yet it may be possible with miner transactions?
|
|
|
|
|
// Using get just decreases the possibility of a panic and lets us move on in that case
|
2022-07-15 01:26:07 -04:00
|
|
|
None => break,
|
2022-06-28 00:01:20 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Rebuild the commitment to verify it
|
|
|
|
|
commitment = Commitment::new(commitment_mask(key_offset), amount);
|
|
|
|
|
// If this is a malicious commitment, move to the next output
|
|
|
|
|
// Any other R value will calculate to a different spend key and are therefore ignorable
|
|
|
|
|
if Some(&commitment.calculate()) != self.rct_signatures.base.commitments.get(o) {
|
|
|
|
|
break;
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
2022-06-28 00:01:20 -04:00
|
|
|
}
|
2022-05-21 15:33:35 -04:00
|
|
|
|
2022-06-28 00:01:20 -04:00
|
|
|
if commitment.amount != 0 {
|
|
|
|
|
res.push(SpendableOutput {
|
|
|
|
|
tx: self.hash(),
|
|
|
|
|
o: o.try_into().unwrap(),
|
|
|
|
|
key: output.key,
|
|
|
|
|
key_offset,
|
2022-07-15 01:26:07 -04:00
|
|
|
commitment,
|
2022-06-28 00:01:20 -04:00
|
|
|
});
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
2022-06-28 00:01:20 -04:00
|
|
|
// Break to prevent public keys from being included multiple times, triggering multiple
|
|
|
|
|
// inclusions of the same output
|
|
|
|
|
break;
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
|
|
|
|
}
|
2022-06-02 00:00:26 -04:00
|
|
|
|
2022-07-09 18:53:52 -04:00
|
|
|
Timelocked(self.prefix.timelock, res)
|
2022-05-21 15:33:35 -04:00
|
|
|
}
|
|
|
|
|
}
|