Implement serialization for SpendableOutput

Changes the output index to a u8. While it may expand to a u16 at some 
point, this can remain canonical using little endian serialization while 
dropping the latter byte if it's 0 (or simply only using u16 when it's 
actually possible).
This commit is contained in:
Luke Parker 2022-05-26 03:51:27 -04:00
parent d45473b2bd
commit 5ca0945cbf
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
3 changed files with 39 additions and 5 deletions

View file

@ -110,7 +110,7 @@ impl Decoys {
let mut outputs = Vec::with_capacity(inputs.len());
for input in inputs {
outputs.push((
rpc.get_o_indexes(input.tx).await?[input.o],
rpc.get_o_indexes(input.tx).await?[usize::from(input.o)],
[input.key, input.commitment.calculate()]
));
}

View file

@ -10,7 +10,7 @@ use monero::{consensus::deserialize, blockdata::transaction::ExtraField};
use crate::{
Commitment,
serialize::write_varint,
serialize::{write_varint, read_32, read_scalar, read_point},
transaction::Transaction,
wallet::{uniqueness, shared_key, amount_decryption, commitment_mask}
};
@ -18,12 +18,40 @@ use crate::{
#[derive(Clone, PartialEq, Debug)]
pub struct SpendableOutput {
pub tx: [u8; 32],
pub o: usize,
pub o: u8,
pub key: EdwardsPoint,
pub key_offset: Scalar,
pub commitment: Commitment
}
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> {
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) }
)
}
)
}
}
impl Transaction {
pub fn scan(
&self,
@ -88,7 +116,13 @@ impl Transaction {
}
if commitment.amount != 0 {
res.push(SpendableOutput { tx: self.hash(), o, key: output.key, key_offset, commitment });
res.push(SpendableOutput {
tx: self.hash(),
o: o.try_into().unwrap(),
key: output.key,
key_offset,
commitment
});
}
// Break to prevent public keys from being included multiple times, triggering multiple
// inclusions of the same output

View file

@ -71,7 +71,7 @@ impl SignableTransaction {
// These outputs can only be spent once. Therefore, it forces all RNGs derived from this
// transcript (such as the one used to create one time keys) to be unique
transcript.append_message(b"input_hash", &input.tx);
transcript.append_message(b"input_output_index", &u16::try_from(input.o).unwrap().to_le_bytes());
transcript.append_message(b"input_output_index", &[input.o]);
// Not including this, with a doxxed list of payments, would allow brute forcing the inputs
// to determine RNG seeds and therefore the true spends
transcript.append_message(b"input_shared_key", &input.key_offset.to_bytes());