use core::ops::Deref;
use std_shims::{vec, vec::Vec};
use zeroize::{Zeroize, Zeroizing};
use rand_core::SeedableRng;
use rand_chacha::ChaCha20Rng;
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, Scalar, EdwardsPoint};
use crate::{
primitives::{keccak256, Commitment},
ringct::EncryptedAmount,
SharedKeyDerivations, OutputWithDecoys,
send::{ChangeEnum, InternalPayment, SignableTransaction, key_image_sort},
};
fn seeded_rng(
dst: &'static [u8],
outgoing_view_key: &[u8; 32],
mut input_keys: Vec<EdwardsPoint>,
) -> ChaCha20Rng {
let mut transcript = Zeroizing::new(vec![u8::try_from(dst.len()).unwrap()]);
transcript.extend(dst);
transcript.extend(outgoing_view_key);
input_keys.sort_by(key_image_sort);
for key in input_keys {
transcript.extend(key.compress().to_bytes());
}
let res = ChaCha20Rng::from_seed(keccak256(&transcript));
transcript.zeroize();
res
}
pub struct TransactionKeys(ChaCha20Rng);
impl TransactionKeys {
pub fn new(outgoing_view_key: &Zeroizing<[u8; 32]>, input_keys: Vec<EdwardsPoint>) -> Self {
Self(seeded_rng(b"transaction_keys", outgoing_view_key, input_keys))
}
}
impl Iterator for TransactionKeys {
type Item = Zeroizing<Scalar>;
fn next(&mut self) -> Option<Self::Item> {
Some(Zeroizing::new(Scalar::random(&mut self.0)))
}
}
impl SignableTransaction {
fn input_keys(&self) -> Vec<EdwardsPoint> {
self.inputs.iter().map(OutputWithDecoys::key).collect()
}
pub(crate) fn seeded_rng(&self, dst: &'static [u8]) -> ChaCha20Rng {
seeded_rng(dst, &self.outgoing_view_key, self.input_keys())
}
fn has_payments_to_subaddresses(&self) -> bool {
self.payments.iter().any(|payment| match payment {
InternalPayment::Payment(addr, _) => addr.is_subaddress(),
InternalPayment::Change(change) => match change {
ChangeEnum::AddressOnly(addr) => addr.is_subaddress(),
ChangeEnum::Standard { .. } | ChangeEnum::Guaranteed { .. } => false,
},
})
}
fn should_use_additional_keys(&self) -> bool {
let has_payments_to_subaddresses = self.has_payments_to_subaddresses();
if !has_payments_to_subaddresses {
return false;
}
let has_change_view = self.payments.iter().any(|payment| match payment {
InternalPayment::Payment(_, _) => false,
InternalPayment::Change(change) => match change {
ChangeEnum::AddressOnly(_) => false,
ChangeEnum::Standard { .. } | ChangeEnum::Guaranteed { .. } => true,
},
});
has_payments_to_subaddresses && !((self.payments.len() == 2) && has_change_view)
}
fn transaction_keys(&self) -> (Zeroizing<Scalar>, Vec<Zeroizing<Scalar>>) {
let mut tx_keys = TransactionKeys::new(&self.outgoing_view_key, self.input_keys());
let tx_key = tx_keys.next().unwrap();
let mut additional_keys = vec![];
if self.should_use_additional_keys() {
for _ in 0 .. self.payments.len() {
additional_keys.push(tx_keys.next().unwrap());
}
}
(tx_key, additional_keys)
}
fn ecdhs(&self) -> Vec<Zeroizing<EdwardsPoint>> {
let (tx_key, additional_keys) = self.transaction_keys();
debug_assert!(additional_keys.is_empty() || (additional_keys.len() == self.payments.len()));
let (tx_key_pub, additional_keys_pub) = self.transaction_keys_pub();
debug_assert_eq!(additional_keys_pub.len(), additional_keys.len());
let mut res = Vec::with_capacity(self.payments.len());
for (i, payment) in self.payments.iter().enumerate() {
let addr = payment.address();
let key_to_use =
if addr.is_subaddress() { additional_keys.get(i).unwrap_or(&tx_key) } else { &tx_key };
let ecdh = match payment {
InternalPayment::Payment(_, _) |
InternalPayment::Change(ChangeEnum::AddressOnly { .. }) => {
Zeroizing::new(key_to_use.deref() * addr.view())
}
InternalPayment::Change(ChangeEnum::Standard { view_pair, .. }) => {
Zeroizing::new(view_pair.view.deref() * tx_key_pub)
}
InternalPayment::Change(ChangeEnum::Guaranteed { view_pair, .. }) => {
Zeroizing::new(view_pair.0.view.deref() * tx_key_pub)
}
};
res.push(ecdh);
}
res
}
pub(crate) fn shared_key_derivations(
&self,
key_images: &[EdwardsPoint],
) -> Vec<Zeroizing<SharedKeyDerivations>> {
let ecdhs = self.ecdhs();
let uniqueness = SharedKeyDerivations::uniqueness(&self.inputs(key_images));
let mut res = Vec::with_capacity(self.payments.len());
for (i, (payment, ecdh)) in self.payments.iter().zip(ecdhs).enumerate() {
let addr = payment.address();
res.push(SharedKeyDerivations::output_derivations(
addr.is_guaranteed().then_some(uniqueness),
ecdh,
i,
));
}
res
}
pub(crate) fn payment_id_xors(&self) -> Vec<[u8; 8]> {
let mut res = Vec::with_capacity(self.payments.len());
for ecdh in self.ecdhs() {
res.push(SharedKeyDerivations::payment_id_xor(ecdh));
}
res
}
pub(crate) fn transaction_keys_pub(&self) -> (EdwardsPoint, Vec<EdwardsPoint>) {
let (tx_key, additional_keys) = self.transaction_keys();
debug_assert!(additional_keys.is_empty() || (additional_keys.len() == self.payments.len()));
let has_payments_to_subaddresses = self.has_payments_to_subaddresses();
let should_use_additional_keys = self.should_use_additional_keys();
if has_payments_to_subaddresses && (!should_use_additional_keys) {
debug_assert_eq!(additional_keys.len(), 0);
let InternalPayment::Payment(addr, _) = self
.payments
.iter()
.find(|payment| matches!(payment, InternalPayment::Payment(_, _)))
.expect("payment to subaddress yet no payment")
else {
panic!("filtered payment wasn't a payment")
};
return (tx_key.deref() * addr.spend(), vec![]);
}
if should_use_additional_keys {
let mut additional_keys_pub = vec![];
for (additional_key, payment) in additional_keys.into_iter().zip(&self.payments) {
let addr = payment.address();
if addr.is_subaddress() {
additional_keys_pub.push(additional_key.deref() * addr.spend());
} else {
additional_keys_pub.push(additional_key.deref() * ED25519_BASEPOINT_TABLE)
}
}
return (tx_key.deref() * ED25519_BASEPOINT_TABLE, additional_keys_pub);
}
debug_assert!(!has_payments_to_subaddresses);
debug_assert!(!should_use_additional_keys);
(tx_key.deref() * ED25519_BASEPOINT_TABLE, vec![])
}
pub(crate) fn commitments_and_encrypted_amounts(
&self,
key_images: &[EdwardsPoint],
) -> Vec<(Commitment, EncryptedAmount)> {
let shared_key_derivations = self.shared_key_derivations(key_images);
let mut res = Vec::with_capacity(self.payments.len());
for (payment, shared_key_derivations) in self.payments.iter().zip(shared_key_derivations) {
let amount = match payment {
InternalPayment::Payment(_, amount) => *amount,
InternalPayment::Change(_) => {
let inputs = self.inputs.iter().map(|input| input.commitment().amount).sum::<u64>();
let payments = self
.payments
.iter()
.filter_map(|payment| match payment {
InternalPayment::Payment(_, amount) => Some(amount),
InternalPayment::Change(_) => None,
})
.sum::<u64>();
let necessary_fee = self.weight_and_necessary_fee().1;
inputs - (payments + necessary_fee)
}
};
let commitment = Commitment::new(shared_key_derivations.commitment_mask(), amount);
let encrypted_amount = EncryptedAmount::Compact {
amount: shared_key_derivations.compact_amount_encryption(amount),
};
res.push((commitment, encrypted_amount));
}
res
}
pub(crate) fn sum_output_masks(&self, key_images: &[EdwardsPoint]) -> Scalar {
self
.commitments_and_encrypted_amounts(key_images)
.into_iter()
.map(|(commitment, _)| commitment.mask)
.sum()
}
}