Add SignableTransaction Read/Write

This commit is contained in:
Luke Parker
2024-06-28 05:25:02 -04:00
parent 70c36ed06c
commit abd48e9206
11 changed files with 216 additions and 234 deletions

View File

@@ -3,7 +3,7 @@
#![deny(missing_docs)] #![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
use std_shims::vec::Vec; use std_shims::{io, vec::Vec};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std_shims::sync::OnceLock; use std_shims::sync::OnceLock;
@@ -17,6 +17,7 @@ use curve25519_dalek::{
edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}, edwards::{EdwardsPoint, VartimeEdwardsPrecomputation},
}; };
use monero_io::*;
use monero_generators::H; use monero_generators::H;
mod unreduced_scalar; mod unreduced_scalar;
@@ -166,4 +167,34 @@ impl Decoys {
pub fn signer_ring_members(&self) -> [EdwardsPoint; 2] { pub fn signer_ring_members(&self) -> [EdwardsPoint; 2] {
self.ring[usize::from(self.signer_index)] self.ring[usize::from(self.signer_index)]
} }
/// Write the Decoys.
pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
write_vec(write_varint, &self.offsets, w)?;
w.write_all(&[self.signer_index])?;
write_vec(
|pair, w| {
write_point(&pair[0], w)?;
write_point(&pair[1], w)
},
&self.ring,
w,
)
}
/// Serialize the Decoys to a `Vec<u8>`.
pub fn serialize(&self) -> Vec<u8> {
let mut res =
Vec::with_capacity((1 + (2 * self.offsets.len())) + 1 + 1 + (self.ring.len() * 64));
self.write(&mut res).unwrap();
res
}
/// Read a set of Decoys.
pub fn read(r: &mut impl io::Read) -> io::Result<Decoys> {
Decoys::new(
read_vec(read_varint, r)?,
read_byte(r)?,
read_vec(|r| Ok([read_point(r)?, read_point(r)?]), r)?,
)
.ok_or_else(|| io::Error::other("invalid Decoys"))
}
} }

View File

@@ -97,13 +97,6 @@ impl Bulletproof {
(bp_clawback, LR_len) (bp_clawback, LR_len)
} }
/// Calculate the weight of this proof.
pub fn fee_weight(plus: bool, outputs: usize) -> usize {
#[allow(non_snake_case)]
let (bp_clawback, LR_len) = Bulletproof::calculate_bp_clawback(plus, outputs);
32 * (Bulletproof::bp_fields(plus) + (2 * LR_len)) + 2 + bp_clawback
}
/// Prove the list of commitments are within [0 .. 2^64) with an aggregate Bulletproof. /// Prove the list of commitments are within [0 .. 2^64) with an aggregate Bulletproof.
pub fn prove<R: RngCore + CryptoRng>( pub fn prove<R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,

View File

@@ -386,11 +386,6 @@ impl Clsag {
Ok(()) Ok(())
} }
/// The length a CLSAG will take once serialized.
pub fn fee_weight(ring_len: usize) -> usize {
(ring_len * 32) + 32 + 32
}
/// Write a CLSAG. /// Write a CLSAG.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> { pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_raw_vec(write_scalar, &self.s, w)?; write_raw_vec(write_scalar, &self.s, w)?;

View File

@@ -52,6 +52,24 @@ impl FeeRate {
Ok(FeeRate { per_weight, mask }) Ok(FeeRate { per_weight, mask })
} }
/// Write the FeeRate.
pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
w.write_all(&self.per_weight.to_le_bytes())?;
w.write_all(&self.mask.to_le_bytes())
}
/// Serialize the FeeRate to a `Vec<u8>`.
pub fn serialize(&self) -> Vec<u8> {
let mut res = Vec::with_capacity(16);
self.write(&mut res).unwrap();
res
}
/// Read a FeeRate.
pub fn read(r: &mut impl io::Read) -> io::Result<FeeRate> {
Ok(FeeRate { per_weight: read_u64(r)?, mask: read_u64(r)? })
}
/// Calculate the fee to use from the weight. /// Calculate the fee to use from the weight.
/// ///
/// This function may panic if any of the `FeeRate`'s fields are zero. /// This function may panic if any of the `FeeRate`'s fields are zero.

View File

@@ -173,12 +173,6 @@ pub struct RctBase {
} }
impl RctBase { impl RctBase {
/// The weight of this RctBase as relevant for fees.
pub fn fee_weight(outputs: usize, fee: u64) -> usize {
// 1 byte for the RCT signature type
1 + (outputs * (8 + 32)) + varint_len(fee)
}
/// Write the RctBase. /// Write the RctBase.
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> { pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
w.write_all(&[u8::from(rct_type)])?; w.write_all(&[u8::from(rct_type)])?;
@@ -295,16 +289,6 @@ pub enum RctPrunable {
} }
impl RctPrunable { impl RctPrunable {
/// The weight of this RctPrunable as relevant for fees.
#[rustfmt::skip]
pub fn fee_weight(bp_plus: bool, ring_len: usize, inputs: usize, outputs: usize) -> usize {
// 1 byte for number of BPs (technically a VarInt, yet there's always just zero or one)
1 +
Bulletproof::fee_weight(bp_plus, outputs) +
// There's both the CLSAG and the pseudo-out
(inputs * (Clsag::fee_weight(ring_len) + 32))
}
/// Write the RctPrunable. /// Write the RctPrunable.
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> { pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
match self { match self {
@@ -446,17 +430,6 @@ impl RctProofs {
} }
} }
/// The weight of this RctProofs, as relevant for fees.
pub fn fee_weight(
bp_plus: bool,
ring_len: usize,
inputs: usize,
outputs: usize,
fee: u64,
) -> usize {
RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(bp_plus, ring_len, inputs, outputs)
}
/// Write the RctProofs. /// Write the RctProofs.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> { pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
let rct_type = self.rct_type(); let rct_type = self.rct_type();

View File

@@ -32,13 +32,6 @@ pub enum Input {
} }
impl Input { impl Input {
/// The weight of this Input, as relevant for fees.
pub fn fee_weight(offsets_weight: usize) -> usize {
// Uses 1 byte for the input type
// Uses 1 byte for the VarInt amount due to amount being 0
1 + 1 + offsets_weight + 32
}
/// Write the Input. /// Write the Input.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> { pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self { match self {
@@ -98,13 +91,6 @@ pub struct Output {
} }
impl Output { impl Output {
/// The weight of this Output, as relevant for fees.
pub fn fee_weight(view_tags: bool) -> usize {
// Uses 1 byte for the output type
// Uses 1 byte for the VarInt amount due to amount being 0
1 + 1 + 32 + if view_tags { 1 } else { 0 }
}
/// Write the Output. /// Write the Output.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> { pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_varint(&self.amount.unwrap_or(0), w)?; write_varint(&self.amount.unwrap_or(0), w)?;
@@ -221,23 +207,6 @@ pub struct TransactionPrefix {
} }
impl TransactionPrefix { impl TransactionPrefix {
/// The weight of this TransactionPrefix, as relevant for fees.
pub fn fee_weight(
decoy_weights: &[usize],
outputs: usize,
view_tags: bool,
extra: usize,
) -> usize {
// Assumes Timelock::None since this library won't let you create a TX with a timelock
// 1 input for every decoy weight
1 + varint_len(decoy_weights.len()) +
decoy_weights.iter().map(|&offsets_weight| Input::fee_weight(offsets_weight)).sum::<usize>() +
varint_len(outputs) +
(outputs * Output::fee_weight(view_tags)) +
varint_len(extra) +
extra
}
/// Write a TransactionPrefix. /// Write a TransactionPrefix.
/// ///
/// This is distinct from Monero in that it won't write any version. /// This is distinct from Monero in that it won't write any version.
@@ -323,22 +292,6 @@ impl Transaction {
} }
} }
/// The weight of this Transaction, as relevant for fees.
// TODO: Replace ring_len, decoy_weights for &[&[usize]], where the inner buf is the decoy
// offsets
pub fn fee_weight(
view_tags: bool,
bp_plus: bool,
ring_len: usize,
decoy_weights: &[usize],
outputs: usize,
extra: usize,
fee: u64,
) -> usize {
1 + TransactionPrefix::fee_weight(decoy_weights, outputs, view_tags, extra) +
RctProofs::fee_weight(bp_plus, ring_len, decoy_weights.len(), outputs, fee)
}
/// Write the Transaction. /// Write the Transaction.
/// ///
/// Some writable transactions may not be readable if they're malformed, per Monero's consensus /// Some writable transactions may not be readable if they're malformed, per Monero's consensus

View File

@@ -30,11 +30,11 @@ rand_chacha = { version = "0.3", default-features = false }
# Used to select decoys # Used to select decoys
rand_distr = { version = "0.4", default-features = false } rand_distr = { version = "0.4", default-features = false }
group = { version = "0.13", default-features = false }
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize", "group"] } curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize", "group"] }
# Multisig dependencies # Multisig dependencies
transcript = { package = "flexible-transcript", path = "../../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], optional = true } transcript = { package = "flexible-transcript", path = "../../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], optional = true }
group = { version = "0.13", default-features = false, optional = true }
dalek-ff-group = { path = "../../../crypto/dalek-ff-group", version = "0.4", default-features = false, optional = true } dalek-ff-group = { path = "../../../crypto/dalek-ff-group", version = "0.4", default-features = false, optional = true }
frost = { package = "modular-frost", path = "../../../crypto/frost", default-features = false, features = ["ed25519"], optional = true } frost = { package = "modular-frost", path = "../../../crypto/frost", default-features = false, features = ["ed25519"], optional = true }
@@ -72,5 +72,5 @@ std = [
"monero-address/std", "monero-address/std",
] ]
compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-serai/compile-time-generators"] compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-serai/compile-time-generators"]
multisig = ["transcript", "dalek-ff-group", "frost", "monero-serai/multisig", "std"] multisig = ["transcript", "group", "dalek-ff-group", "frost", "monero-serai/multisig", "std"]
default = ["std", "compile-time-generators"] default = ["std", "compile-time-generators"]

View File

@@ -208,23 +208,6 @@ impl Extra {
self.0.push(field); self.0.push(field);
} }
#[rustfmt::skip]
pub fn fee_weight(
outputs: usize,
additional: bool,
payment_id: bool,
data: &[Vec<u8>]
) -> usize {
// PublicKey, key
(1 + 32) +
// PublicKeys, length, additional keys
(if additional { 1 + 1 + (outputs * 32) } else { 0 }) +
// PaymentId (Nonce), length, encrypted, ID
(if payment_id { 1 + 1 + 1 + 8 } else { 0 }) +
// Nonce, length, ARBITRARY_DATA_MARKER, data
data.iter().map(|v| 1 + varint_len(1 + v.len()) + 1 + v.len()).sum::<usize>()
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> { pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
for field in &self.0 { for field in &self.0 {
field.write(w)?; field.write(w)?;

View File

@@ -1,3 +1,5 @@
use std_shims::io;
use zeroize::Zeroize; use zeroize::Zeroize;
use crate::{ use crate::{
@@ -94,85 +96,15 @@ impl Eventuality {
true true
} }
/*
pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> { pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
self.protocol.write(w)?; self.0.write(w)
write_raw_vec(write_byte, self.r_seed.as_ref(), w)?;
write_vec(write_point, &self.inputs, w)?;
fn write_payment<W: io::Write>(payment: &InternalPayment, w: &mut W) -> io::Result<()> {
match payment {
InternalPayment::Payment(payment, need_dummy_payment_id) => {
w.write_all(&[0])?;
write_vec(write_byte, payment.0.to_string().as_bytes(), w)?;
w.write_all(&payment.1.to_le_bytes())?;
if *need_dummy_payment_id {
w.write_all(&[1])
} else {
w.write_all(&[0])
}
}
InternalPayment::Change(change, change_view) => {
w.write_all(&[1])?;
write_vec(write_byte, change.0.to_string().as_bytes(), w)?;
w.write_all(&change.1.to_le_bytes())?;
if let Some(view) = change_view.as_ref() {
w.write_all(&[1])?;
write_scalar(view, w)
} else {
w.write_all(&[0])
}
}
}
}
write_vec(write_payment, &self.payments, w)?;
write_vec(write_byte, &self.extra, w)
} }
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(128); self.0.serialize()
self.write(&mut buf).unwrap();
buf
} }
pub fn read<R: io::Read>(r: &mut R) -> io::Result<Eventuality> { pub fn read<R: io::Read>(r: &mut R) -> io::Result<Eventuality> {
fn read_address<R: io::Read>(r: &mut R) -> io::Result<MoneroAddress> { Ok(Eventuality(SignableTransaction::read(r)?))
String::from_utf8(read_vec(read_byte, r)?)
.ok()
.and_then(|str| MoneroAddress::from_str_raw(&str).ok())
.ok_or_else(|| io::Error::other("invalid address"))
} }
fn read_payment<R: io::Read>(r: &mut R) -> io::Result<InternalPayment> {
Ok(match read_byte(r)? {
0 => InternalPayment::Payment(
(read_address(r)?, read_u64(r)?),
match read_byte(r)? {
0 => false,
1 => true,
_ => Err(io::Error::other("invalid need additional"))?,
},
),
1 => InternalPayment::Change(
(read_address(r)?, read_u64(r)?),
match read_byte(r)? {
0 => None,
1 => Some(Zeroizing::new(read_scalar(r)?)),
_ => Err(io::Error::other("invalid change view"))?,
},
),
_ => Err(io::Error::other("invalid payment"))?,
})
}
Ok(Eventuality {
protocol: RctType::read(r)?,
r_seed: Zeroizing::new(read_bytes::<_, 32>(r)?),
inputs: read_vec(read_point, r)?,
payments: read_vec(read_payment, r)?,
extra: read_vec(read_byte, r)?,
})
}
*/
} }

View File

@@ -1,4 +1,5 @@
use core::{ops::Deref, fmt}; use core::{ops::Deref, fmt};
use std_shims::io;
use zeroize::{Zeroize, Zeroizing}; use zeroize::{Zeroize, Zeroizing};
@@ -10,6 +11,7 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, Scalar, EdwardsPoint}
use frost::FrostError; use frost::FrostError;
use crate::{ use crate::{
io::*,
generators::{MAX_COMMITMENTS, hash_to_point}, generators::{MAX_COMMITMENTS, hash_to_point},
primitives::Decoys, primitives::Decoys,
ringct::{ ringct::{
@@ -158,7 +160,12 @@ pub enum SendError {
#[cfg_attr(feature = "std", error("invalid amount of key images specified"))] #[cfg_attr(feature = "std", error("invalid amount of key images specified"))]
InvalidAmountOfKeyImages, InvalidAmountOfKeyImages,
#[cfg_attr(feature = "std", error("wrong spend private key"))] #[cfg_attr(feature = "std", error("wrong spend private key"))]
WrongPrivateKey, // TODO WrongPrivateKey,
#[cfg_attr(
feature = "std",
error("this SignableTransaction was created by deserializing a malicious serialization")
)]
MaliciousSerialization,
#[cfg_attr(feature = "std", error("clsag error ({0})"))] #[cfg_attr(feature = "std", error("clsag error ({0})"))]
ClsagError(ClsagError), ClsagError(ClsagError),
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
@@ -176,27 +183,24 @@ pub struct SignableTransaction {
fee_rate: FeeRate, fee_rate: FeeRate,
} }
struct SignableTransactionWithKeyImages {
intent: SignableTransaction,
key_images: Vec<EdwardsPoint>,
}
impl SignableTransaction { impl SignableTransaction {
pub fn new( fn validate(&self) -> Result<(), SendError> {
rct_type: RctType, match self.rct_type {
sender_view_key: Zeroizing<Scalar>,
inputs: Vec<(SpendableOutput, Decoys)>,
payments: Vec<(MoneroAddress, u64)>,
change: Change,
data: Vec<Vec<u8>>,
fee_rate: FeeRate,
) -> Result<SignableTransaction, SendError> {
match rct_type {
RctType::ClsagBulletproof | RctType::ClsagBulletproofPlus => {} RctType::ClsagBulletproof | RctType::ClsagBulletproofPlus => {}
_ => Err(SendError::UnsupportedRctType)?, _ => Err(SendError::UnsupportedRctType)?,
}; }
if inputs.is_empty() { if self.inputs.is_empty() {
Err(SendError::NoInputs)?; Err(SendError::NoInputs)?;
} }
for (_, decoys) in &inputs { for (_, decoys) in &self.inputs {
if decoys.len() != if decoys.len() !=
match rct_type { match self.rct_type {
RctType::ClsagBulletproof => 11, RctType::ClsagBulletproof => 11,
RctType::ClsagBulletproofPlus => 16, RctType::ClsagBulletproofPlus => 16,
_ => panic!("unsupported RctType"), _ => panic!("unsupported RctType"),
@@ -206,74 +210,69 @@ impl SignableTransaction {
} }
} }
if payments.is_empty() { // Check we have at least one non-change output
if !self.payments.iter().any(|payment| matches!(payment, InternalPayment::Payment(_, _))) {
Err(SendError::NoOutputs)?; Err(SendError::NoOutputs)?;
} }
// If we don't have at least two outputs, as required by Monero, error // If we don't have at least two outputs, as required by Monero, error
if (payments.len() == 1) && matches!(change, Change(ChangeEnum::None)) { if self.payments.len() < 2 {
Err(SendError::NoChange)?; Err(SendError::NoChange)?;
} }
// Check we don't have multiple Change outputs due to decoding a malicious serialization
{
let mut change_count = 0;
for payment in &self.payments {
change_count += usize::from(u8::from(matches!(payment, InternalPayment::Change(_, _))));
}
if change_count > 1 {
Err(SendError::MaliciousSerialization)?;
}
}
// Make sure there's at most one payment ID // Make sure there's at most one payment ID
{ {
let mut payment_ids = 0; let mut payment_ids = 0;
let mut count = |addr: MoneroAddress| { for payment in &self.payments {
if addr.payment_id().is_some() { payment_ids += usize::from(u8::from(payment.address().payment_id().is_some()));
payment_ids += 1
}
};
for payment in &payments {
count(payment.0);
}
match &change.0 {
ChangeEnum::None => (),
ChangeEnum::AddressOnly(addr) | ChangeEnum::AddressWithView(addr, _) => count(*addr),
} }
if payment_ids > 1 { if payment_ids > 1 {
Err(SendError::MultiplePaymentIds)?; Err(SendError::MultiplePaymentIds)?;
} }
} }
// Re-format the payments and change into a consolidated payments list if self.payments.len() > MAX_COMMITMENTS {
let payments_amount = payments.iter().map(|(_, amount)| amount).sum::<u64>();
let mut payments = payments
.into_iter()
.map(|(addr, amount)| InternalPayment::Payment(addr, amount))
.collect::<Vec<_>>();
match change.0 {
ChangeEnum::None => {}
ChangeEnum::AddressOnly(addr) => payments.push(InternalPayment::Change(addr, None)),
ChangeEnum::AddressWithView(addr, view) => {
payments.push(InternalPayment::Change(addr, Some(view)))
}
}
if payments.len() > MAX_COMMITMENTS {
Err(SendError::TooManyOutputs)?; Err(SendError::TooManyOutputs)?;
} }
// Check the length of each arbitrary data // Check the length of each arbitrary data
for part in &data { for part in &self.data {
if part.len() > MAX_ARBITRARY_DATA_SIZE { if part.len() > MAX_ARBITRARY_DATA_SIZE {
Err(SendError::TooMuchData)?; Err(SendError::TooMuchData)?;
} }
} }
let res = SignableTransaction { rct_type, sender_view_key, inputs, payments, data, fee_rate };
// Check the length of TX extra // Check the length of TX extra
// https://github.com/monero-project/monero/pull/8733 // https://github.com/monero-project/monero/pull/8733
const MAX_EXTRA_SIZE: usize = 1060; const MAX_EXTRA_SIZE: usize = 1060;
if res.extra().len() > MAX_EXTRA_SIZE { if self.extra().len() > MAX_EXTRA_SIZE {
Err(SendError::TooMuchData)?; Err(SendError::TooMuchData)?;
} }
// Make sure we have enough funds // Make sure we have enough funds
let in_amount = res.inputs.iter().map(|(input, _)| input.commitment().amount).sum::<u64>(); let in_amount = self.inputs.iter().map(|(input, _)| input.commitment().amount).sum::<u64>();
let payments_amount = self
.payments
.iter()
.filter_map(|payment| match payment {
InternalPayment::Payment(_, amount) => Some(amount),
InternalPayment::Change(_, _) => None,
})
.sum::<u64>();
// Necessary so weight_and_fee doesn't underflow // Necessary so weight_and_fee doesn't underflow
if in_amount < payments_amount { if in_amount < payments_amount {
Err(SendError::NotEnoughFunds { inputs: in_amount, outputs: payments_amount, fee: None })?; Err(SendError::NotEnoughFunds { inputs: in_amount, outputs: payments_amount, fee: None })?;
} }
let (weight, fee) = res.weight_and_fee(); let (weight, fee) = self.weight_and_fee();
if in_amount < (payments_amount + fee) { if in_amount < (payments_amount + fee) {
Err(SendError::NotEnoughFunds { Err(SendError::NotEnoughFunds {
inputs: in_amount, inputs: in_amount,
@@ -290,6 +289,116 @@ impl SignableTransaction {
Err(SendError::TooLargeTransaction)?; Err(SendError::TooLargeTransaction)?;
} }
Ok(())
}
pub fn new(
rct_type: RctType,
sender_view_key: Zeroizing<Scalar>,
inputs: Vec<(SpendableOutput, Decoys)>,
payments: Vec<(MoneroAddress, u64)>,
change: Change,
data: Vec<Vec<u8>>,
fee_rate: FeeRate,
) -> Result<SignableTransaction, SendError> {
// Re-format the payments and change into a consolidated payments list
let mut payments = payments
.into_iter()
.map(|(addr, amount)| InternalPayment::Payment(addr, amount))
.collect::<Vec<_>>();
match change.0 {
ChangeEnum::None => {}
ChangeEnum::AddressOnly(addr) => payments.push(InternalPayment::Change(addr, None)),
ChangeEnum::AddressWithView(addr, view) => {
payments.push(InternalPayment::Change(addr, Some(view)))
}
}
let res = SignableTransaction { rct_type, sender_view_key, inputs, payments, data, fee_rate };
res.validate()?;
Ok(res)
}
pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
fn write_input<W: io::Write>(input: &(SpendableOutput, Decoys), w: &mut W) -> io::Result<()> {
input.0.write(w)?;
input.1.write(w)
}
fn write_payment<W: io::Write>(payment: &InternalPayment, w: &mut W) -> io::Result<()> {
match payment {
InternalPayment::Payment(addr, amount) => {
w.write_all(&[0])?;
write_vec(write_byte, addr.to_string().as_bytes(), w)?;
w.write_all(&amount.to_le_bytes())
}
InternalPayment::Change(addr, change_view) => {
w.write_all(&[1])?;
write_vec(write_byte, addr.to_string().as_bytes(), w)?;
if let Some(view) = change_view.as_ref() {
w.write_all(&[1])?;
write_scalar(view, w)
} else {
w.write_all(&[0])
}
}
}
}
write_byte(&u8::from(self.rct_type), w)?;
write_scalar(&self.sender_view_key, w)?;
write_vec(write_input, &self.inputs, w)?;
write_vec(write_payment, &self.payments, w)?;
write_vec(|data, w| write_vec(write_byte, data, w), &self.data, w)?;
self.fee_rate.write(w)
}
pub fn serialize(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(256);
self.write(&mut buf).unwrap();
buf
}
pub fn read<R: io::Read>(r: &mut R) -> io::Result<SignableTransaction> {
fn read_input(r: &mut impl io::Read) -> io::Result<(SpendableOutput, Decoys)> {
Ok((SpendableOutput::read(r)?, Decoys::read(r)?))
}
fn read_address<R: io::Read>(r: &mut R) -> io::Result<MoneroAddress> {
String::from_utf8(read_vec(read_byte, r)?)
.ok()
.and_then(|str| MoneroAddress::from_str_raw(&str).ok())
.ok_or_else(|| io::Error::other("invalid address"))
}
fn read_payment<R: io::Read>(r: &mut R) -> io::Result<InternalPayment> {
Ok(match read_byte(r)? {
0 => InternalPayment::Payment(read_address(r)?, read_u64(r)?),
1 => InternalPayment::Change(
read_address(r)?,
match read_byte(r)? {
0 => None,
1 => Some(Zeroizing::new(read_scalar(r)?)),
_ => Err(io::Error::other("invalid change view"))?,
},
),
_ => Err(io::Error::other("invalid payment"))?,
})
}
let res = SignableTransaction {
rct_type: RctType::try_from(read_byte(r)?)
.map_err(|()| io::Error::other("unsupported/invalid RctType"))?,
sender_view_key: Zeroizing::new(read_scalar(r)?),
inputs: read_vec(read_input, r)?,
payments: read_vec(read_payment, r)?,
data: read_vec(|r| read_vec(read_byte, r), r)?,
fee_rate: FeeRate::read(r)?,
};
match res.validate() {
Ok(()) => {}
Err(e) => Err(io::Error::other(e))?,
}
Ok(res) Ok(res)
} }
@@ -360,7 +469,7 @@ impl SignableTransaction {
.sum::<Scalar>(); .sum::<Scalar>();
// Get the actual TX, just needing the CLSAGs // Get the actual TX, just needing the CLSAGs
let mut tx = tx.transaction_without_clsags_and_pseudo_outs(); let mut tx = tx.transaction_without_signatures();
// Sign the CLSAGs // Sign the CLSAGs
let clsags_and_pseudo_outs = let clsags_and_pseudo_outs =
@@ -391,8 +500,3 @@ impl SignableTransaction {
Ok(tx) Ok(tx)
} }
} }
struct SignableTransactionWithKeyImages {
intent: SignableTransaction,
key_images: Vec<EdwardsPoint>,
}

View File

@@ -216,7 +216,7 @@ impl SignableTransaction {
} }
impl SignableTransactionWithKeyImages { impl SignableTransactionWithKeyImages {
pub(crate) fn transaction_without_clsags_and_pseudo_outs(&self) -> Transaction { pub(crate) fn transaction_without_signatures(&self) -> Transaction {
let commitments_and_encrypted_amounts = let commitments_and_encrypted_amounts =
self.intent.commitments_and_encrypted_amounts(&self.key_images); self.intent.commitments_and_encrypted_amounts(&self.key_images);
let mut commitments = Vec::with_capacity(self.intent.payments.len()); let mut commitments = Vec::with_capacity(self.intent.payments.len());