mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-17 09:27:36 +00:00
Include subaddress and payment ID in SpendableOutput
This commit is contained in:
parent
f0b914c721
commit
19f5fd8fe9
3 changed files with 68 additions and 4 deletions
|
@ -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)
|
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> {
|
pub(crate) fn read_varint<R: io::Read>(r: &mut R) -> io::Result<u64> {
|
||||||
let mut bits = 0;
|
let mut bits = 0;
|
||||||
let mut res = 0;
|
let mut res = 0;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use core::ops::BitXor;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
@ -15,6 +16,19 @@ pub(crate) enum PaymentId {
|
||||||
Encrypted([u8; 8]),
|
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 {
|
impl PaymentId {
|
||||||
fn serialize<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
fn serialize<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -115,6 +129,15 @@ impl Extra {
|
||||||
keys
|
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>> {
|
pub(crate) fn data(&self) -> Option<Vec<u8>> {
|
||||||
for field in &self.0 {
|
for field in &self.0 {
|
||||||
if let ExtraField::Padding(data) = field {
|
if let ExtraField::Padding(data) = field {
|
||||||
|
|
|
@ -6,18 +6,31 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwar
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Commitment,
|
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},
|
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)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct SpendableOutput {
|
pub struct SpendableOutput {
|
||||||
pub tx: [u8; 32],
|
pub tx: [u8; 32],
|
||||||
pub o: u8,
|
pub o: u8,
|
||||||
|
|
||||||
pub key: EdwardsPoint,
|
pub key: EdwardsPoint,
|
||||||
pub key_offset: Scalar,
|
pub key_offset: Scalar,
|
||||||
pub commitment: Commitment,
|
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)]
|
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||||
|
@ -48,12 +61,19 @@ impl Timelocked {
|
||||||
impl SpendableOutput {
|
impl SpendableOutput {
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
let mut res = Vec::with_capacity(32 + 1 + 32 + 32 + 40);
|
let mut res = Vec::with_capacity(32 + 1 + 32 + 32 + 40);
|
||||||
|
|
||||||
res.extend(&self.tx);
|
res.extend(&self.tx);
|
||||||
res.push(self.o);
|
res.push(self.o);
|
||||||
|
|
||||||
res.extend(self.key.compress().to_bytes());
|
res.extend(self.key.compress().to_bytes());
|
||||||
res.extend(self.key_offset.to_bytes());
|
res.extend(self.key_offset.to_bytes());
|
||||||
res.extend(self.commitment.mask.to_bytes());
|
res.extend(self.commitment.mask.to_bytes());
|
||||||
res.extend(self.commitment.amount.to_le_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
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,9 +81,13 @@ impl SpendableOutput {
|
||||||
Ok(SpendableOutput {
|
Ok(SpendableOutput {
|
||||||
tx: read_bytes(r)?,
|
tx: read_bytes(r)?,
|
||||||
o: read_byte(r)?,
|
o: read_byte(r)?,
|
||||||
|
|
||||||
key: read_point(r)?,
|
key: read_point(r)?,
|
||||||
key_offset: read_scalar(r)?,
|
key_offset: read_scalar(r)?,
|
||||||
commitment: Commitment::new(read_scalar(r)?, read_u64(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 {
|
pub fn scan(&self, view: &ViewPair, guaranteed: bool) -> Timelocked {
|
||||||
let extra = Extra::deserialize(&mut Cursor::new(&self.prefix.extra));
|
let extra = Extra::deserialize(&mut Cursor::new(&self.prefix.extra));
|
||||||
let keys;
|
let keys;
|
||||||
if let Ok(extra) = extra {
|
let extra = if let Ok(extra) = extra {
|
||||||
keys = extra.keys();
|
keys = extra.keys();
|
||||||
|
extra
|
||||||
} else {
|
} else {
|
||||||
return Timelocked(self.prefix.timelock, vec![]);
|
return Timelocked(self.prefix.timelock, vec![]);
|
||||||
};
|
};
|
||||||
|
let payment_id = extra.payment_id();
|
||||||
|
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
for (o, output) in self.prefix.outputs.iter().enumerate() {
|
for (o, output) in self.prefix.outputs.iter().enumerate() {
|
||||||
for key in &keys {
|
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),
|
Some(uniqueness(&self.prefix.inputs)).filter(|_| guaranteed),
|
||||||
&view.view,
|
&view.view,
|
||||||
key,
|
key,
|
||||||
o,
|
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 let Some(actual_view_tag) = output.view_tag {
|
||||||
if actual_view_tag != view_tag {
|
if actual_view_tag != view_tag {
|
||||||
continue;
|
continue;
|
||||||
|
@ -127,9 +160,13 @@ impl Transaction {
|
||||||
res.push(SpendableOutput {
|
res.push(SpendableOutput {
|
||||||
tx: self.hash(),
|
tx: self.hash(),
|
||||||
o: o.try_into().unwrap(),
|
o: o.try_into().unwrap(),
|
||||||
|
|
||||||
key: output.key,
|
key: output.key,
|
||||||
key_offset,
|
key_offset,
|
||||||
commitment,
|
commitment,
|
||||||
|
|
||||||
|
subaddress: (0, 0),
|
||||||
|
payment_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Break to prevent public keys from being included multiple times, triggering multiple
|
// Break to prevent public keys from being included multiple times, triggering multiple
|
||||||
|
|
Loading…
Reference in a new issue