use std_shims::{vec, vec::Vec};
use curve25519_dalek::{
constants::{ED25519_BASEPOINT_POINT, ED25519_BASEPOINT_TABLE},
Scalar, EdwardsPoint,
};
use crate::{
io::{varint_len, write_varint},
primitives::Commitment,
ringct::{
clsag::Clsag, bulletproofs::Bulletproof, EncryptedAmount, RctType, RctBase, RctPrunable,
RctProofs,
},
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
extra::{ARBITRARY_DATA_MARKER, PaymentId, Extra},
send::{InternalPayment, SignableTransaction, SignableTransactionWithKeyImages},
};
impl SignableTransaction {
pub(crate) fn inputs(&self, key_images: &[EdwardsPoint]) -> Vec<Input> {
debug_assert_eq!(self.inputs.len(), key_images.len());
let mut res = Vec::with_capacity(self.inputs.len());
for (input, key_image) in self.inputs.iter().zip(key_images) {
res.push(Input::ToKey {
amount: None,
key_offsets: input.decoys().offsets().to_vec(),
key_image: *key_image,
});
}
res
}
pub(crate) fn outputs(&self, key_images: &[EdwardsPoint]) -> Vec<Output> {
let shared_key_derivations = self.shared_key_derivations(key_images);
debug_assert_eq!(self.payments.len(), shared_key_derivations.len());
let mut res = Vec::with_capacity(self.payments.len());
for (payment, shared_key_derivations) in self.payments.iter().zip(&shared_key_derivations) {
let key =
(&shared_key_derivations.shared_key * ED25519_BASEPOINT_TABLE) + payment.address().spend();
res.push(Output {
key: key.compress(),
amount: None,
view_tag: (match self.rct_type {
RctType::ClsagBulletproof => false,
RctType::ClsagBulletproofPlus => true,
_ => panic!("unsupported RctType"),
})
.then_some(shared_key_derivations.view_tag),
});
}
res
}
pub(crate) fn extra(&self) -> Vec<u8> {
let (tx_key, additional_keys) = self.transaction_keys_pub();
debug_assert!(additional_keys.is_empty() || (additional_keys.len() == self.payments.len()));
let payment_id_xors = self.payment_id_xors();
debug_assert_eq!(self.payments.len(), payment_id_xors.len());
let amount_of_keys = 1 + additional_keys.len();
let mut extra = Extra::new(tx_key, additional_keys);
if let Some((id, id_xor)) =
self.payments.iter().zip(&payment_id_xors).find_map(|(payment, payment_id_xor)| {
payment.address().payment_id().map(|id| (id, payment_id_xor))
})
{
let id = (u64::from_le_bytes(id) ^ u64::from_le_bytes(*id_xor)).to_le_bytes();
let mut id_vec = Vec::with_capacity(1 + 8);
PaymentId::Encrypted(id).write(&mut id_vec).unwrap();
extra.push_nonce(id_vec);
} else {
if self.payments.len() == 2 {
let (_, payment_id_xor) = self
.payments
.iter()
.zip(&payment_id_xors)
.find(|(payment, _)| matches!(payment, InternalPayment::Payment(_, _)))
.expect("multiple change outputs?");
let mut id_vec = Vec::with_capacity(1 + 8);
PaymentId::Encrypted(*payment_id_xor).write(&mut id_vec).unwrap();
extra.push_nonce(id_vec);
}
}
for part in &self.data {
let mut arb = vec![ARBITRARY_DATA_MARKER];
arb.extend(part);
extra.push_nonce(arb);
}
let mut serialized = Vec::with_capacity(32 * amount_of_keys);
extra.write(&mut serialized).unwrap();
serialized
}
pub(crate) fn weight_and_necessary_fee(&self) -> (usize, u64) {
let base_weight = {
let mut key_images = Vec::with_capacity(self.inputs.len());
let mut clsags = Vec::with_capacity(self.inputs.len());
let mut pseudo_outs = Vec::with_capacity(self.inputs.len());
for _ in &self.inputs {
key_images.push(ED25519_BASEPOINT_POINT);
clsags.push(Clsag {
D: ED25519_BASEPOINT_POINT,
s: vec![
Scalar::ZERO;
match self.rct_type {
RctType::ClsagBulletproof => 11,
RctType::ClsagBulletproofPlus => 16,
_ => unreachable!("unsupported RCT type"),
}
],
c1: Scalar::ZERO,
});
pseudo_outs.push(ED25519_BASEPOINT_POINT);
}
let mut encrypted_amounts = Vec::with_capacity(self.payments.len());
let mut bp_commitments = Vec::with_capacity(self.payments.len());
let mut commitments = Vec::with_capacity(self.payments.len());
for _ in &self.payments {
encrypted_amounts.push(EncryptedAmount::Compact { amount: [0; 8] });
bp_commitments.push(Commitment::zero());
commitments.push(ED25519_BASEPOINT_POINT);
}
let padded_log2 = {
let mut log2_find = 0;
while (1 << log2_find) < self.payments.len() {
log2_find += 1;
}
log2_find
};
let lr_len = 6 + padded_log2;
let bulletproof = match self.rct_type {
RctType::ClsagBulletproof => {
let mut bp = Vec::with_capacity(((9 + (2 * lr_len)) * 32) + 2);
let push_point = |bp: &mut Vec<u8>| {
bp.push(1);
bp.extend([0; 31]);
};
let push_scalar = |bp: &mut Vec<u8>| bp.extend([0; 32]);
for _ in 0 .. 4 {
push_point(&mut bp);
}
for _ in 0 .. 2 {
push_scalar(&mut bp);
}
for _ in 0 .. 2 {
write_varint(&lr_len, &mut bp).unwrap();
for _ in 0 .. lr_len {
push_point(&mut bp);
}
}
for _ in 0 .. 3 {
push_scalar(&mut bp);
}
Bulletproof::read(&mut bp.as_slice()).expect("made an invalid dummy BP")
}
RctType::ClsagBulletproofPlus => {
let mut bp = Vec::with_capacity(((6 + (2 * lr_len)) * 32) + 2);
let push_point = |bp: &mut Vec<u8>| {
bp.push(1);
bp.extend([0; 31]);
};
let push_scalar = |bp: &mut Vec<u8>| bp.extend([0; 32]);
for _ in 0 .. 3 {
push_point(&mut bp);
}
for _ in 0 .. 3 {
push_scalar(&mut bp);
}
for _ in 0 .. 2 {
write_varint(&lr_len, &mut bp).unwrap();
for _ in 0 .. lr_len {
push_point(&mut bp);
}
}
Bulletproof::read_plus(&mut bp.as_slice()).expect("made an invalid dummy BP+")
}
_ => panic!("unsupported RctType"),
};
Transaction::V2 {
prefix: TransactionPrefix {
additional_timelock: Timelock::None,
inputs: self.inputs(&key_images),
outputs: self.outputs(&key_images),
extra: self.extra(),
},
proofs: Some(RctProofs {
base: RctBase { fee: 0, encrypted_amounts, pseudo_outs: vec![], commitments },
prunable: RctPrunable::Clsag { bulletproof, clsags, pseudo_outs },
}),
}
.weight() -
1
};
let mut possible_weights = Vec::with_capacity(9);
for i in 1 ..= 9 {
possible_weights.push(base_weight + i);
}
debug_assert_eq!(possible_weights.len(), 9);
let mut possible_fees = Vec::with_capacity(9);
for weight in possible_weights {
possible_fees.push(self.fee_rate.calculate_fee_from_weight(weight));
}
let mut weight_and_fee = None;
for (fee_len, possible_fee) in possible_fees.into_iter().enumerate() {
let fee_len = 1 + fee_len;
debug_assert!(1 <= fee_len);
debug_assert!(fee_len <= 9);
if varint_len(possible_fee) <= fee_len {
weight_and_fee = Some((base_weight + fee_len, possible_fee));
break;
}
}
weight_and_fee.unwrap()
}
}
impl SignableTransactionWithKeyImages {
pub(crate) fn transaction_without_signatures(&self) -> Transaction {
let commitments_and_encrypted_amounts =
self.intent.commitments_and_encrypted_amounts(&self.key_images);
let mut commitments = Vec::with_capacity(self.intent.payments.len());
let mut bp_commitments = Vec::with_capacity(self.intent.payments.len());
let mut encrypted_amounts = Vec::with_capacity(self.intent.payments.len());
for (commitment, encrypted_amount) in commitments_and_encrypted_amounts {
commitments.push(commitment.calculate());
bp_commitments.push(commitment);
encrypted_amounts.push(encrypted_amount);
}
let bulletproof = {
let mut bp_rng = self.intent.seeded_rng(b"bulletproof");
(match self.intent.rct_type {
RctType::ClsagBulletproof => Bulletproof::prove(&mut bp_rng, bp_commitments),
RctType::ClsagBulletproofPlus => Bulletproof::prove_plus(&mut bp_rng, bp_commitments),
_ => panic!("unsupported RctType"),
})
.expect("couldn't prove BP(+)s for this many payments despite checking in constructor?")
};
Transaction::V2 {
prefix: TransactionPrefix {
additional_timelock: Timelock::None,
inputs: self.intent.inputs(&self.key_images),
outputs: self.intent.outputs(&self.key_images),
extra: self.intent.extra(),
},
proofs: Some(RctProofs {
base: RctBase {
fee: if self
.intent
.payments
.iter()
.any(|payment| matches!(payment, InternalPayment::Change(_)))
{
self.intent.weight_and_necessary_fee().1
} else {
let inputs =
self.intent.inputs.iter().map(|input| input.commitment().amount).sum::<u64>();
let payments = self
.intent
.payments
.iter()
.filter_map(|payment| match payment {
InternalPayment::Payment(_, amount) => Some(amount),
InternalPayment::Change(_) => None,
})
.sum::<u64>();
inputs - payments
},
encrypted_amounts,
pseudo_outs: vec![],
commitments,
},
prunable: RctPrunable::Clsag { bulletproof, clsags: vec![], pseudo_outs: vec![] },
}),
}
}
}