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 monero_oxide::io::CompressedPoint;
12
13use crate::{
14 primitives::{keccak256, Commitment},
15 ringct::EncryptedAmount,
16 SharedKeyDerivations,
17 send::{ChangeEnum, InternalPayment, SignableTransaction, key_image_sort},
18};
19
20fn seeded_rng(
21 dst: &'static [u8],
22 outgoing_view_key: &[u8; 32],
23 input_keys_and_commitments: Vec<(EdwardsPoint, EdwardsPoint)>,
24) -> ChaCha20Rng {
25 let mut transcript = Zeroizing::new(vec![
27 u8::try_from(dst.len()).expect("internal RNG with constant DST had a too-long DST specified")
28 ]);
29 transcript.extend(dst);
30
31 transcript.extend(outgoing_view_key);
33
34 let mut input_keys_and_commitments = input_keys_and_commitments
35 .into_iter()
36 .map(|(key, commitment)| {
37 (CompressedPoint::from(key.compress()), CompressedPoint::from(commitment.compress()))
38 })
39 .collect::<Vec<_>>();
40
41 input_keys_and_commitments
45 .sort_by(|(key_a, _commitment_a), (key_b, _commitment_b)| key_image_sort(key_a, key_b));
46
47 for (key, commitment) in input_keys_and_commitments {
50 transcript.extend(key.to_bytes());
51 transcript.extend(commitment.to_bytes());
52 }
53
54 let res = ChaCha20Rng::from_seed(keccak256(&transcript));
55 transcript.zeroize();
56 res
57}
58
59pub struct TransactionKeys(ChaCha20Rng);
64impl TransactionKeys {
65 pub fn new(
69 outgoing_view_key: &Zeroizing<[u8; 32]>,
70 input_keys_and_commitments: Vec<(EdwardsPoint, EdwardsPoint)>,
71 ) -> Self {
72 Self(seeded_rng(b"transaction_keys", outgoing_view_key, input_keys_and_commitments))
73 }
74}
75impl Iterator for TransactionKeys {
76 type Item = Zeroizing<Scalar>;
77 fn next(&mut self) -> Option<Self::Item> {
78 Some(Zeroizing::new(Scalar::random(&mut self.0)))
79 }
80}
81
82impl SignableTransaction {
83 fn input_keys_and_commitments(&self) -> Vec<(EdwardsPoint, EdwardsPoint)> {
84 self.inputs.iter().map(|output| (output.key(), output.commitment().calculate())).collect()
85 }
86
87 pub(crate) fn seeded_rng(&self, dst: &'static [u8]) -> ChaCha20Rng {
88 seeded_rng(dst, &self.outgoing_view_key, self.input_keys_and_commitments())
89 }
90
91 fn has_payments_to_subaddresses(&self) -> bool {
92 self.payments.iter().any(|payment| match payment {
93 InternalPayment::Payment(addr, _) => addr.is_subaddress(),
94 InternalPayment::Change(change) => match change {
95 ChangeEnum::AddressOnly(addr) => addr.is_subaddress(),
96 ChangeEnum::Standard { .. } | ChangeEnum::Guaranteed { .. } => false,
100 },
101 })
102 }
103
104 fn should_use_additional_keys(&self) -> bool {
105 let has_payments_to_subaddresses = self.has_payments_to_subaddresses();
106 if !has_payments_to_subaddresses {
107 return false;
108 }
109
110 let has_change_view = self.payments.iter().any(|payment| match payment {
111 InternalPayment::Payment(_, _) => false,
112 InternalPayment::Change(change) => match change {
113 ChangeEnum::AddressOnly(_) => false,
114 ChangeEnum::Standard { .. } | ChangeEnum::Guaranteed { .. } => true,
115 },
116 });
117
118 has_payments_to_subaddresses && !((self.payments.len() == 2) && has_change_view)
129 }
130
131 fn transaction_keys(&self) -> (Zeroizing<Scalar>, Vec<Zeroizing<Scalar>>) {
133 let mut tx_keys =
134 TransactionKeys::new(&self.outgoing_view_key, self.input_keys_and_commitments());
135
136 let tx_key = tx_keys.next().expect("TransactionKeys (never-ending) was exhausted");
137
138 let mut additional_keys = vec![];
139 if self.should_use_additional_keys() {
140 for _ in 0 .. self.payments.len() {
141 additional_keys.push(tx_keys.next().expect("TransactionKeys (never-ending) was exhausted"));
142 }
143 }
144 (tx_key, additional_keys)
145 }
146
147 fn ecdhs(&self) -> Vec<Zeroizing<EdwardsPoint>> {
148 let (tx_key, additional_keys) = self.transaction_keys();
149 debug_assert!(additional_keys.is_empty() || (additional_keys.len() == self.payments.len()));
150 let (tx_key_pub, additional_keys_pub) = self.transaction_keys_pub();
151 debug_assert_eq!(additional_keys_pub.len(), additional_keys.len());
152
153 let mut res = Vec::with_capacity(self.payments.len());
154 for (i, payment) in self.payments.iter().enumerate() {
155 let addr = payment.address();
156 let key_to_use =
157 if addr.is_subaddress() { additional_keys.get(i).unwrap_or(&tx_key) } else { &tx_key };
158
159 let ecdh = match payment {
160 InternalPayment::Payment(_, _) |
162 InternalPayment::Change(ChangeEnum::AddressOnly { .. }) => {
163 Zeroizing::new(key_to_use.deref() * addr.view())
164 }
165 InternalPayment::Change(ChangeEnum::Standard { view_pair, .. }) => {
167 Zeroizing::new(view_pair.view.deref() * tx_key_pub)
168 }
169 InternalPayment::Change(ChangeEnum::Guaranteed { view_pair, .. }) => {
170 Zeroizing::new(view_pair.0.view.deref() * tx_key_pub)
171 }
172 };
173
174 res.push(ecdh);
175 }
176 res
177 }
178
179 pub(crate) fn shared_key_derivations(
181 &self,
182 key_images: &[CompressedPoint],
183 ) -> Vec<Zeroizing<SharedKeyDerivations>> {
184 let ecdhs = self.ecdhs();
185
186 let uniqueness = SharedKeyDerivations::uniqueness(&self.inputs(key_images));
187
188 let mut res = Vec::with_capacity(self.payments.len());
189 for (i, (payment, ecdh)) in self.payments.iter().zip(ecdhs).enumerate() {
190 let addr = payment.address();
191 res.push(SharedKeyDerivations::output_derivations(
192 addr.is_guaranteed().then_some(uniqueness),
193 ecdh,
194 i,
195 ));
196 }
197 res
198 }
199
200 pub(crate) fn payment_id_xors(&self) -> Vec<[u8; 8]> {
202 let mut res = Vec::with_capacity(self.payments.len());
203 for ecdh in self.ecdhs() {
204 res.push(SharedKeyDerivations::payment_id_xor(ecdh));
205 }
206 res
207 }
208
209 pub(crate) fn transaction_keys_pub(&self) -> (EdwardsPoint, Vec<EdwardsPoint>) {
214 let (tx_key, additional_keys) = self.transaction_keys();
215 debug_assert!(additional_keys.is_empty() || (additional_keys.len() == self.payments.len()));
216
217 let has_payments_to_subaddresses = self.has_payments_to_subaddresses();
219 let should_use_additional_keys = self.should_use_additional_keys();
220 if has_payments_to_subaddresses && (!should_use_additional_keys) {
221 debug_assert_eq!(additional_keys.len(), 0);
222
223 let InternalPayment::Payment(addr, _) = self
224 .payments
225 .iter()
226 .find(|payment| matches!(payment, InternalPayment::Payment(_, _)))
227 .expect("payment to subaddress yet no payment")
228 else {
229 panic!("filtered payment wasn't a payment")
230 };
231
232 return (tx_key.deref() * addr.spend(), vec![]);
233 }
234
235 if should_use_additional_keys {
236 let mut additional_keys_pub = vec![];
237 for (additional_key, payment) in additional_keys.into_iter().zip(&self.payments) {
238 let addr = payment.address();
239 if addr.is_subaddress() {
242 additional_keys_pub.push(additional_key.deref() * addr.spend());
243 } else {
244 additional_keys_pub.push(additional_key.deref() * ED25519_BASEPOINT_TABLE)
245 }
246 }
247 return (tx_key.deref() * ED25519_BASEPOINT_TABLE, additional_keys_pub);
248 }
249
250 debug_assert!(!has_payments_to_subaddresses);
251 debug_assert!(!should_use_additional_keys);
252 (tx_key.deref() * ED25519_BASEPOINT_TABLE, vec![])
253 }
254
255 pub(crate) fn commitments_and_encrypted_amounts(
256 &self,
257 key_images: &[CompressedPoint],
258 ) -> Vec<(Commitment, EncryptedAmount)> {
259 let shared_key_derivations = self.shared_key_derivations(key_images);
260
261 let mut res = Vec::with_capacity(self.payments.len());
262 for (payment, shared_key_derivations) in self.payments.iter().zip(shared_key_derivations) {
263 let amount = match payment {
264 InternalPayment::Payment(_, amount) => *amount,
265 InternalPayment::Change(_) => {
266 let inputs = self.inputs.iter().map(|input| input.commitment().amount).sum::<u64>();
267 let payments = self
268 .payments
269 .iter()
270 .filter_map(|payment| match payment {
271 InternalPayment::Payment(_, amount) => Some(amount),
272 InternalPayment::Change(_) => None,
273 })
274 .sum::<u64>();
275 let necessary_fee = self.weight_and_necessary_fee().1;
276 inputs - (payments + necessary_fee)
278 }
279 };
280 let commitment = Commitment::new(shared_key_derivations.commitment_mask(), amount);
281 let encrypted_amount = EncryptedAmount::Compact {
282 amount: shared_key_derivations.compact_amount_encryption(amount),
283 };
284 res.push((commitment, encrypted_amount));
285 }
286 res
287 }
288
289 pub(crate) fn sum_output_masks(&self, key_images: &[CompressedPoint]) -> Scalar {
290 self
291 .commitments_and_encrypted_amounts(key_images)
292 .into_iter()
293 .map(|(commitment, _)| commitment.mask)
294 .sum()
295 }
296}