Include subaddress and payment ID in SpendableOutput

This commit is contained in:
Luke Parker 2022-08-22 07:22:54 -04:00
parent f0b914c721
commit 19f5fd8fe9
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
3 changed files with 68 additions and 4 deletions

View file

@ -71,6 +71,10 @@ pub(crate) fn read_u64<R: io::Read>(r: &mut R) -> io::Result<u64> {
read_bytes(r).map(u64::from_le_bytes)
}
pub(crate) fn read_u32<R: io::Read>(r: &mut R) -> io::Result<u32> {
read_bytes(r).map(u32::from_le_bytes)
}
pub(crate) fn read_varint<R: io::Read>(r: &mut R) -> io::Result<u64> {
let mut bits = 0;
let mut res = 0;

View file

@ -1,3 +1,4 @@
use core::ops::BitXor;
use std::io::{self, Read, Write};
use zeroize::Zeroize;
@ -15,6 +16,19 @@ pub(crate) enum PaymentId {
Encrypted([u8; 8]),
}
impl BitXor<[u8; 8]> for PaymentId {
type Output = PaymentId;
fn bitxor(self, bytes: [u8; 8]) -> PaymentId {
match self {
PaymentId::Unencrypted(_) => self,
PaymentId::Encrypted(id) => {
PaymentId::Encrypted((u64::from_le_bytes(id) ^ u64::from_le_bytes(bytes)).to_le_bytes())
}
}
}
}
impl PaymentId {
fn serialize<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self {
@ -115,6 +129,15 @@ impl Extra {
keys
}
pub(crate) fn payment_id(&self) -> Option<PaymentId> {
for field in &self.0 {
if let ExtraField::PaymentId(id) = field {
return Some(*id);
}
}
None
}
pub(crate) fn data(&self) -> Option<Vec<u8>> {
for field in &self.0 {
if let ExtraField::Padding(data) = field {

View file

@ -6,18 +6,31 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwar
use crate::{
Commitment,
serialize::{read_byte, read_u64, read_bytes, read_scalar, read_point},
serialize::{read_byte, read_u32, read_u64, read_bytes, read_scalar, read_point},
transaction::{Timelock, Transaction},
wallet::{ViewPair, Extra, uniqueness, shared_key, amount_decryption, commitment_mask},
wallet::{
ViewPair, PaymentId, Extra, uniqueness, shared_key, amount_decryption, commitment_mask,
},
};
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
pub struct SpendableOutput {
pub tx: [u8; 32],
pub o: u8,
pub key: EdwardsPoint,
pub key_offset: Scalar,
pub commitment: Commitment,
// Does not have to be an Option since the 0 subaddress is the main address
pub subaddress: (u32, u32),
// Can be an Option, as extra doesn't necessarily have a payment ID, yet all Monero TXs should
// have this
// This will be gibberish if the payment ID wasn't intended for the recipient
// 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
// they should be randomly generated)
pub payment_id: [u8; 8],
}
#[derive(Zeroize, ZeroizeOnDrop)]
@ -48,12 +61,19 @@ impl Timelocked {
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.extend(self.subaddress.0.to_le_bytes());
res.extend(self.subaddress.1.to_le_bytes());
res.extend(self.payment_id);
res
}
@ -61,9 +81,13 @@ impl SpendableOutput {
Ok(SpendableOutput {
tx: read_bytes(r)?,
o: read_byte(r)?,
key: read_point(r)?,
key_offset: read_scalar(r)?,
commitment: Commitment::new(read_scalar(r)?, read_u64(r)?),
subaddress: (read_u32(r)?, read_u32(r)?),
payment_id: read_bytes(r)?,
})
}
}
@ -72,22 +96,31 @@ impl Transaction {
pub fn scan(&self, view: &ViewPair, guaranteed: bool) -> Timelocked {
let extra = Extra::deserialize(&mut Cursor::new(&self.prefix.extra));
let keys;
if let Ok(extra) = extra {
let extra = if let Ok(extra) = extra {
keys = extra.keys();
extra
} else {
return Timelocked(self.prefix.timelock, vec![]);
};
let payment_id = extra.payment_id();
let mut res = vec![];
for (o, output) in self.prefix.outputs.iter().enumerate() {
for key in &keys {
let (view_tag, key_offset, _) = shared_key(
let (view_tag, key_offset, payment_id_xor) = shared_key(
Some(uniqueness(&self.prefix.inputs)).filter(|_| guaranteed),
&view.view,
key,
o,
);
let payment_id =
if let Some(PaymentId::Encrypted(id)) = payment_id.map(|id| id ^ payment_id_xor) {
id
} else {
[0xff; 8]
};
if let Some(actual_view_tag) = output.view_tag {
if actual_view_tag != view_tag {
continue;
@ -127,9 +160,13 @@ impl Transaction {
res.push(SpendableOutput {
tx: self.hash(),
o: o.try_into().unwrap(),
key: output.key,
key_offset,
commitment,
subaddress: (0, 0),
payment_id,
});
}
// Break to prevent public keys from being included multiple times, triggering multiple