1use std_shims::{vec, vec::Vec};
2
3#[cfg(feature = "compile-time-generators")]
4use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
5#[cfg(not(feature = "compile-time-generators"))]
6use curve25519_dalek::constants::ED25519_BASEPOINT_POINT as ED25519_BASEPOINT_TABLE;
7
8use crate::{
9 io::VarInt,
10 ed25519::*,
11 ringct::{
12 clsag::Clsag, bulletproofs::Bulletproof, EncryptedAmount, RctType, RctBase, RctPrunable,
13 RctProofs,
14 },
15 transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
16 extra::{ARBITRARY_DATA_MARKER, PaymentId, Extra},
17 send::{InternalPayment, SignableTransaction, SignableTransactionWithKeyImages},
18};
19
20impl SignableTransaction {
21 pub(crate) fn inputs(&self, key_images: &[CompressedPoint]) -> Vec<Input> {
23 debug_assert_eq!(self.inputs.len(), key_images.len());
24
25 let mut res = Vec::with_capacity(self.inputs.len());
26 for (input, key_image) in self.inputs.iter().zip(key_images) {
27 res.push(Input::ToKey {
28 amount: None,
29 key_offsets: input.decoys().offsets().to_vec(),
30 key_image: *key_image,
31 });
32 }
33 res
34 }
35
36 pub(crate) fn outputs(&self, key_images: &[CompressedPoint]) -> Vec<Output> {
38 let shared_key_derivations = self.shared_key_derivations(key_images);
39 debug_assert_eq!(self.payments.len(), shared_key_derivations.len());
40
41 let mut res = Vec::with_capacity(self.payments.len());
42 for (payment, shared_key_derivations) in self.payments.iter().zip(&shared_key_derivations) {
43 let key = (&shared_key_derivations.shared_key.into() * ED25519_BASEPOINT_TABLE) +
44 payment.address().spend().into();
45 res.push(Output {
46 key: Point::from(key).compress(),
47 amount: None,
48 view_tag: (match self.rct_type {
49 RctType::ClsagBulletproof => false,
50 RctType::ClsagBulletproofPlus => true,
51 RctType::AggregateMlsagBorromean |
52 RctType::MlsagBorromean |
53 RctType::MlsagBulletproofs |
54 RctType::MlsagBulletproofsCompactAmount => panic!("unsupported RctType"),
55 })
56 .then_some(shared_key_derivations.view_tag),
57 });
58 }
59 res
60 }
61
62 pub(crate) fn extra(&self) -> Vec<u8> {
64 let (tx_key, additional_keys) = self.transaction_keys_pub();
65 debug_assert!(additional_keys.is_empty() || (additional_keys.len() == self.payments.len()));
66 let payment_id_xors = self.payment_id_xors();
67 debug_assert_eq!(self.payments.len(), payment_id_xors.len());
68
69 let amount_of_keys = 1 + additional_keys.len();
70 let mut extra = Extra::new(tx_key.compress(), additional_keys);
71
72 if let Some((id, id_xor)) =
73 self.payments.iter().zip(&payment_id_xors).find_map(|(payment, payment_id_xor)| {
74 payment.address().payment_id().map(|id| (id, payment_id_xor))
75 })
76 {
77 let id = (u64::from_le_bytes(id) ^ u64::from_le_bytes(*id_xor)).to_le_bytes();
78 let mut id_vec = Vec::with_capacity(1 + 8);
79 PaymentId::Encrypted(id)
80 .write(&mut id_vec)
81 .expect("write failed but <Vec as io::Write> doesn't fail");
82 extra.push_nonce(id_vec);
83 } else {
84 if self.payments.len() == 2 {
96 let (_, payment_id_xor) = self
97 .payments
98 .iter()
99 .zip(&payment_id_xors)
100 .find(|(payment, _)| matches!(payment, InternalPayment::Payment(_, _)))
101 .expect("multiple change outputs?");
102 let mut id_vec = Vec::with_capacity(1 + 8);
103 PaymentId::Encrypted(*payment_id_xor)
105 .write(&mut id_vec)
106 .expect("write failed but <Vec as io::Write> doesn't fail");
107 extra.push_nonce(id_vec);
108 }
109 }
110
111 for part in &self.data {
113 let mut arb = vec![ARBITRARY_DATA_MARKER];
114 arb.extend(part);
115 extra.push_nonce(arb);
116 }
117
118 let mut serialized = Vec::with_capacity(32 * amount_of_keys);
119 extra.write(&mut serialized).expect("write failed but <Vec as io::Write> doesn't fail");
120 serialized
121 }
122
123 pub(crate) fn weight_and_necessary_fee(&self) -> (usize, u64) {
124 let base_weight = {
133 let mut key_images = Vec::with_capacity(self.inputs.len());
134 let mut clsags = Vec::with_capacity(self.inputs.len());
135 let mut pseudo_outs = Vec::with_capacity(self.inputs.len());
136 for _ in &self.inputs {
137 key_images.push(CompressedPoint::G);
138 clsags.push(Clsag {
139 D: CompressedPoint::G,
140 s: vec![
141 Scalar::ZERO;
142 match self.rct_type {
143 RctType::ClsagBulletproof => 11,
144 RctType::ClsagBulletproofPlus => 16,
145 RctType::AggregateMlsagBorromean |
146 RctType::MlsagBorromean |
147 RctType::MlsagBulletproofs |
148 RctType::MlsagBulletproofsCompactAmount => unreachable!("unsupported RCT type"),
149 }
150 ],
151 c1: Scalar::ZERO,
152 });
153 pseudo_outs.push(CompressedPoint::G);
154 }
155 let mut encrypted_amounts = Vec::with_capacity(self.payments.len());
156 let mut bp_commitments = Vec::with_capacity(self.payments.len());
157 let mut commitments = Vec::with_capacity(self.payments.len());
158 for _ in &self.payments {
159 encrypted_amounts.push(EncryptedAmount::Compact { amount: [0; 8] });
160 bp_commitments.push(Commitment::zero());
161 commitments.push(CompressedPoint::G);
162 }
163
164 let padded_log2 = {
165 let mut log2_find = 0;
166 while (1 << log2_find) < self.payments.len() {
167 log2_find += 1;
168 }
169 log2_find
170 };
171 let lr_len = 6 + padded_log2;
177
178 let bulletproof = match self.rct_type {
179 RctType::ClsagBulletproof => {
180 let mut bp = Vec::with_capacity(((9 + (2 * lr_len)) * 32) + 2);
181 let push_point = |bp: &mut Vec<u8>| {
182 bp.push(1);
183 bp.extend([0; 31]);
184 };
185 let push_scalar = |bp: &mut Vec<u8>| bp.extend([0; 32]);
186 for _ in 0 .. 4 {
187 push_point(&mut bp);
188 }
189 for _ in 0 .. 2 {
190 push_scalar(&mut bp);
191 }
192 for _ in 0 .. 2 {
193 VarInt::write(&lr_len, &mut bp)
194 .expect("write failed but <Vec as io::Write> doesn't fail");
195 for _ in 0 .. lr_len {
196 push_point(&mut bp);
197 }
198 }
199 for _ in 0 .. 3 {
200 push_scalar(&mut bp);
201 }
202 Bulletproof::read(&mut bp.as_slice()).expect("made an invalid dummy BP")
203 }
204 RctType::ClsagBulletproofPlus => {
205 let mut bp = Vec::with_capacity(((6 + (2 * lr_len)) * 32) + 2);
206 let push_point = |bp: &mut Vec<u8>| {
207 bp.push(1);
208 bp.extend([0; 31]);
209 };
210 let push_scalar = |bp: &mut Vec<u8>| bp.extend([0; 32]);
211 for _ in 0 .. 3 {
212 push_point(&mut bp);
213 }
214 for _ in 0 .. 3 {
215 push_scalar(&mut bp);
216 }
217 for _ in 0 .. 2 {
218 VarInt::write(&lr_len, &mut bp)
219 .expect("write failed but <Vec as io::Write> doesn't fail");
220 for _ in 0 .. lr_len {
221 push_point(&mut bp);
222 }
223 }
224 Bulletproof::read_plus(&mut bp.as_slice()).expect("made an invalid dummy BP+")
225 }
226 RctType::AggregateMlsagBorromean |
227 RctType::MlsagBorromean |
228 RctType::MlsagBulletproofs |
229 RctType::MlsagBulletproofsCompactAmount => panic!("unsupported RctType"),
230 };
231
232 Transaction::V2 {
234 prefix: TransactionPrefix {
235 additional_timelock: Timelock::None,
236 inputs: self.inputs(&key_images),
237 outputs: self.outputs(&key_images),
238 extra: self.extra(),
239 },
240 proofs: Some(RctProofs {
241 base: RctBase { fee: 0, encrypted_amounts, pseudo_outs: vec![], commitments },
242 prunable: RctPrunable::Clsag { bulletproof, clsags, pseudo_outs },
243 }),
244 }
245 .weight() -
246 1
247 };
248
249 let mut possible_weights = Vec::with_capacity(<u64 as VarInt>::UPPER_BOUND);
252 const _LOWER_BOUND_IS_LTE_ONE: [(); 1 - <u64 as VarInt>::LOWER_BOUND] = [(); _];
254 const _LOWER_BOUND_IS_GTE_ONE: [(); <u64 as VarInt>::LOWER_BOUND - 1] = [(); _];
255 for i in <u64 as VarInt>::LOWER_BOUND ..= <u64 as VarInt>::UPPER_BOUND {
256 possible_weights.push(base_weight + i);
257 }
258
259 let mut possible_fees = Vec::with_capacity(<u64 as VarInt>::UPPER_BOUND);
261 for weight in possible_weights {
262 possible_fees.push(self.fee_rate.calculate_fee_from_weight(weight));
263 }
264
265 let mut weight_and_fee = None;
267 for (fee_len, possible_fee) in possible_fees.into_iter().enumerate() {
268 let fee_len = 1 + fee_len;
270
271 if possible_fee.varint_len() <= fee_len {
276 weight_and_fee = Some((base_weight + fee_len, possible_fee));
277 break;
278 }
279 }
280 weight_and_fee
281 .expect("length of highest possible fee was greater than highest possible fee length")
282 }
283}
284
285impl SignableTransactionWithKeyImages {
286 pub(crate) fn transaction_without_signatures(&self) -> Transaction {
287 let commitments_and_encrypted_amounts =
288 self.intent.commitments_and_encrypted_amounts(&self.key_images);
289 let mut commitments = Vec::with_capacity(self.intent.payments.len());
290 let mut bp_commitments = Vec::with_capacity(self.intent.payments.len());
291 let mut encrypted_amounts = Vec::with_capacity(self.intent.payments.len());
292 for (commitment, encrypted_amount) in commitments_and_encrypted_amounts {
293 commitments.push(commitment.commit().compress());
294 bp_commitments.push(commitment);
295 encrypted_amounts.push(encrypted_amount);
296 }
297 let bulletproof = {
298 let mut bp_rng = self.intent.seeded_rng(b"bulletproof");
299 (match self.intent.rct_type {
300 RctType::ClsagBulletproof => Bulletproof::prove(&mut bp_rng, bp_commitments),
301 RctType::ClsagBulletproofPlus => Bulletproof::prove_plus(&mut bp_rng, bp_commitments),
302 RctType::AggregateMlsagBorromean |
303 RctType::MlsagBorromean |
304 RctType::MlsagBulletproofs |
305 RctType::MlsagBulletproofsCompactAmount => panic!("unsupported RctType"),
306 })
307 .expect("couldn't prove BP(+)s for this many payments despite checking in constructor?")
308 };
309
310 Transaction::V2 {
311 prefix: TransactionPrefix {
312 additional_timelock: Timelock::None,
313 inputs: self.intent.inputs(&self.key_images),
314 outputs: self.intent.outputs(&self.key_images),
315 extra: self.intent.extra(),
316 },
317 proofs: Some(RctProofs {
318 base: RctBase {
319 fee: if self
320 .intent
321 .payments
322 .iter()
323 .any(|payment| matches!(payment, InternalPayment::Change(_)))
324 {
325 self.intent.weight_and_necessary_fee().1
327 } else {
328 let inputs =
330 self.intent.inputs.iter().map(|input| input.commitment().amount).sum::<u64>();
331 let payments = self
332 .intent
333 .payments
334 .iter()
335 .filter_map(|payment| match payment {
336 InternalPayment::Payment(_, amount) => Some(amount),
337 InternalPayment::Change(_) => None,
338 })
339 .sum::<u64>();
340 inputs - payments
342 },
343 encrypted_amounts,
344 pseudo_outs: vec![],
345 commitments,
346 },
347 prunable: RctPrunable::Clsag { bulletproof, clsags: vec![], pseudo_outs: vec![] },
348 }),
349 }
350 }
351}