monero_wallet/send/
tx_keys.rs1use core::ops::Deref;
2use std_shims::{vec, vec::Vec};
3
4use zeroize::{Zeroize, Zeroizing};
5
6use rand_core::SeedableRng;
7use rand_chacha::ChaCha20Rng;
8
9use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, Scalar, EdwardsPoint};
10
11use crate::{
12 primitives::{keccak256, Commitment},
13 ringct::EncryptedAmount,
14 SharedKeyDerivations, OutputWithDecoys,
15 send::{ChangeEnum, InternalPayment, SignableTransaction, key_image_sort},
16};
17
18fn seeded_rng(
19 dst: &'static [u8],
20 outgoing_view_key: &[u8; 32],
21 mut input_keys: Vec<EdwardsPoint>,
22) -> ChaCha20Rng {
23 let mut transcript = Zeroizing::new(vec![
25 u8::try_from(dst.len()).expect("internal RNG with constant DST had a too-long DST specified")
26 ]);
27 transcript.extend(dst);
28
29 transcript.extend(outgoing_view_key);
31
32 input_keys.sort_by(key_image_sort);
36
37 for key in input_keys {
40 transcript.extend(key.compress().to_bytes());
41 }
42
43 let res = ChaCha20Rng::from_seed(keccak256(&transcript));
44 transcript.zeroize();
45 res
46}
47
48pub struct TransactionKeys(ChaCha20Rng);
53impl TransactionKeys {
54 pub fn new(outgoing_view_key: &Zeroizing<[u8; 32]>, input_keys: Vec<EdwardsPoint>) -> Self {
58 Self(seeded_rng(b"transaction_keys", outgoing_view_key, input_keys))
59 }
60}
61impl Iterator for TransactionKeys {
62 type Item = Zeroizing<Scalar>;
63 fn next(&mut self) -> Option<Self::Item> {
64 Some(Zeroizing::new(Scalar::random(&mut self.0)))
65 }
66}
67
68impl SignableTransaction {
69 fn input_keys(&self) -> Vec<EdwardsPoint> {
70 self.inputs.iter().map(OutputWithDecoys::key).collect()
71 }
72
73 pub(crate) fn seeded_rng(&self, dst: &'static [u8]) -> ChaCha20Rng {
74 seeded_rng(dst, &self.outgoing_view_key, self.input_keys())
75 }
76
77 fn has_payments_to_subaddresses(&self) -> bool {
78 self.payments.iter().any(|payment| match payment {
79 InternalPayment::Payment(addr, _) => addr.is_subaddress(),
80 InternalPayment::Change(change) => match change {
81 ChangeEnum::AddressOnly(addr) => addr.is_subaddress(),
82 ChangeEnum::Standard { .. } | ChangeEnum::Guaranteed { .. } => false,
86 },
87 })
88 }
89
90 fn should_use_additional_keys(&self) -> bool {
91 let has_payments_to_subaddresses = self.has_payments_to_subaddresses();
92 if !has_payments_to_subaddresses {
93 return false;
94 }
95
96 let has_change_view = self.payments.iter().any(|payment| match payment {
97 InternalPayment::Payment(_, _) => false,
98 InternalPayment::Change(change) => match change {
99 ChangeEnum::AddressOnly(_) => false,
100 ChangeEnum::Standard { .. } | ChangeEnum::Guaranteed { .. } => true,
101 },
102 });
103
104 has_payments_to_subaddresses && !((self.payments.len() == 2) && has_change_view)
115 }
116
117 fn transaction_keys(&self) -> (Zeroizing<Scalar>, Vec<Zeroizing<Scalar>>) {
119 let mut tx_keys = TransactionKeys::new(&self.outgoing_view_key, self.input_keys());
120
121 let tx_key = tx_keys.next().expect("TransactionKeys (never-ending) was exhausted");
122
123 let mut additional_keys = vec![];
124 if self.should_use_additional_keys() {
125 for _ in 0 .. self.payments.len() {
126 additional_keys.push(tx_keys.next().expect("TransactionKeys (never-ending) was exhausted"));
127 }
128 }
129 (tx_key, additional_keys)
130 }
131
132 fn ecdhs(&self) -> Vec<Zeroizing<EdwardsPoint>> {
133 let (tx_key, additional_keys) = self.transaction_keys();
134 debug_assert!(additional_keys.is_empty() || (additional_keys.len() == self.payments.len()));
135 let (tx_key_pub, additional_keys_pub) = self.transaction_keys_pub();
136 debug_assert_eq!(additional_keys_pub.len(), additional_keys.len());
137
138 let mut res = Vec::with_capacity(self.payments.len());
139 for (i, payment) in self.payments.iter().enumerate() {
140 let addr = payment.address();
141 let key_to_use =
142 if addr.is_subaddress() { additional_keys.get(i).unwrap_or(&tx_key) } else { &tx_key };
143
144 let ecdh = match payment {
145 InternalPayment::Payment(_, _) |
147 InternalPayment::Change(ChangeEnum::AddressOnly { .. }) => {
148 Zeroizing::new(key_to_use.deref() * addr.view())
149 }
150 InternalPayment::Change(ChangeEnum::Standard { view_pair, .. }) => {
152 Zeroizing::new(view_pair.view.deref() * tx_key_pub)
153 }
154 InternalPayment::Change(ChangeEnum::Guaranteed { view_pair, .. }) => {
155 Zeroizing::new(view_pair.0.view.deref() * tx_key_pub)
156 }
157 };
158
159 res.push(ecdh);
160 }
161 res
162 }
163
164 pub(crate) fn shared_key_derivations(
166 &self,
167 key_images: &[EdwardsPoint],
168 ) -> Vec<Zeroizing<SharedKeyDerivations>> {
169 let ecdhs = self.ecdhs();
170
171 let uniqueness = SharedKeyDerivations::uniqueness(&self.inputs(key_images));
172
173 let mut res = Vec::with_capacity(self.payments.len());
174 for (i, (payment, ecdh)) in self.payments.iter().zip(ecdhs).enumerate() {
175 let addr = payment.address();
176 res.push(SharedKeyDerivations::output_derivations(
177 addr.is_guaranteed().then_some(uniqueness),
178 ecdh,
179 i,
180 ));
181 }
182 res
183 }
184
185 pub(crate) fn payment_id_xors(&self) -> Vec<[u8; 8]> {
187 let mut res = Vec::with_capacity(self.payments.len());
188 for ecdh in self.ecdhs() {
189 res.push(SharedKeyDerivations::payment_id_xor(ecdh));
190 }
191 res
192 }
193
194 pub(crate) fn transaction_keys_pub(&self) -> (EdwardsPoint, Vec<EdwardsPoint>) {
199 let (tx_key, additional_keys) = self.transaction_keys();
200 debug_assert!(additional_keys.is_empty() || (additional_keys.len() == self.payments.len()));
201
202 let has_payments_to_subaddresses = self.has_payments_to_subaddresses();
204 let should_use_additional_keys = self.should_use_additional_keys();
205 if has_payments_to_subaddresses && (!should_use_additional_keys) {
206 debug_assert_eq!(additional_keys.len(), 0);
207
208 let InternalPayment::Payment(addr, _) = self
209 .payments
210 .iter()
211 .find(|payment| matches!(payment, InternalPayment::Payment(_, _)))
212 .expect("payment to subaddress yet no payment")
213 else {
214 panic!("filtered payment wasn't a payment")
215 };
216
217 return (tx_key.deref() * addr.spend(), vec![]);
218 }
219
220 if should_use_additional_keys {
221 let mut additional_keys_pub = vec![];
222 for (additional_key, payment) in additional_keys.into_iter().zip(&self.payments) {
223 let addr = payment.address();
224 if addr.is_subaddress() {
227 additional_keys_pub.push(additional_key.deref() * addr.spend());
228 } else {
229 additional_keys_pub.push(additional_key.deref() * ED25519_BASEPOINT_TABLE)
230 }
231 }
232 return (tx_key.deref() * ED25519_BASEPOINT_TABLE, additional_keys_pub);
233 }
234
235 debug_assert!(!has_payments_to_subaddresses);
236 debug_assert!(!should_use_additional_keys);
237 (tx_key.deref() * ED25519_BASEPOINT_TABLE, vec![])
238 }
239
240 pub(crate) fn commitments_and_encrypted_amounts(
241 &self,
242 key_images: &[EdwardsPoint],
243 ) -> Vec<(Commitment, EncryptedAmount)> {
244 let shared_key_derivations = self.shared_key_derivations(key_images);
245
246 let mut res = Vec::with_capacity(self.payments.len());
247 for (payment, shared_key_derivations) in self.payments.iter().zip(shared_key_derivations) {
248 let amount = match payment {
249 InternalPayment::Payment(_, amount) => *amount,
250 InternalPayment::Change(_) => {
251 let inputs = self.inputs.iter().map(|input| input.commitment().amount).sum::<u64>();
252 let payments = self
253 .payments
254 .iter()
255 .filter_map(|payment| match payment {
256 InternalPayment::Payment(_, amount) => Some(amount),
257 InternalPayment::Change(_) => None,
258 })
259 .sum::<u64>();
260 let necessary_fee = self.weight_and_necessary_fee().1;
261 inputs - (payments + necessary_fee)
263 }
264 };
265 let commitment = Commitment::new(shared_key_derivations.commitment_mask(), amount);
266 let encrypted_amount = EncryptedAmount::Compact {
267 amount: shared_key_derivations.compact_amount_encryption(amount),
268 };
269 res.push((commitment, encrypted_amount));
270 }
271 res
272 }
273
274 pub(crate) fn sum_output_masks(&self, key_images: &[EdwardsPoint]) -> Scalar {
275 self
276 .commitments_and_encrypted_amounts(key_images)
277 .into_iter()
278 .map(|(commitment, _)| commitment.mask)
279 .sum()
280 }
281}