1use core::{ops::Deref, fmt};
2use std_shims::{
3 io, vec,
4 vec::Vec,
5 string::{String, ToString},
6};
7
8use zeroize::{Zeroize, Zeroizing};
9
10use rand_core::{RngCore, CryptoRng};
11use rand::seq::SliceRandom;
12
13use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, Scalar, EdwardsPoint};
14#[cfg(feature = "multisig")]
15use frost::FrostError;
16
17use crate::{
18 io::*,
19 generators::{MAX_COMMITMENTS, hash_to_point},
20 ringct::{
21 clsag::{ClsagError, ClsagContext, Clsag},
22 RctType, RctPrunable, RctProofs,
23 },
24 transaction::Transaction,
25 address::{Network, SubaddressIndex, MoneroAddress},
26 extra::MAX_ARBITRARY_DATA_SIZE,
27 rpc::FeeRate,
28 ViewPair, GuaranteedViewPair, OutputWithDecoys,
29};
30
31mod tx_keys;
32pub use tx_keys::TransactionKeys;
33mod tx;
34mod eventuality;
35pub use eventuality::Eventuality;
36
37#[cfg(feature = "multisig")]
38mod multisig;
39#[cfg(feature = "multisig")]
40pub use multisig::{TransactionMachine, TransactionSignMachine, TransactionSignatureMachine};
41
42pub(crate) fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> core::cmp::Ordering {
43 x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse()
44}
45
46#[derive(Clone, PartialEq, Eq, Zeroize)]
47enum ChangeEnum {
48 AddressOnly(MoneroAddress),
49 Standard { view_pair: ViewPair, subaddress: Option<SubaddressIndex> },
50 Guaranteed { view_pair: GuaranteedViewPair, subaddress: Option<SubaddressIndex> },
51}
52
53impl fmt::Debug for ChangeEnum {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55 match self {
56 ChangeEnum::AddressOnly(addr) => {
57 f.debug_struct("ChangeEnum::AddressOnly").field("addr", &addr).finish()
58 }
59 ChangeEnum::Standard { subaddress, .. } => f
60 .debug_struct("ChangeEnum::Standard")
61 .field("subaddress", &subaddress)
62 .finish_non_exhaustive(),
63 ChangeEnum::Guaranteed { subaddress, .. } => f
64 .debug_struct("ChangeEnum::Guaranteed")
65 .field("subaddress", &subaddress)
66 .finish_non_exhaustive(),
67 }
68 }
69}
70
71#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
73pub struct Change(Option<ChangeEnum>);
74
75impl Change {
76 pub fn new(view_pair: ViewPair, subaddress: Option<SubaddressIndex>) -> Change {
81 Change(Some(ChangeEnum::Standard { view_pair, subaddress }))
82 }
83
84 pub fn guaranteed(view_pair: GuaranteedViewPair, subaddress: Option<SubaddressIndex>) -> Change {
89 Change(Some(ChangeEnum::Guaranteed { view_pair, subaddress }))
90 }
91
92 pub fn fingerprintable(address: Option<MoneroAddress>) -> Change {
110 if let Some(address) = address {
111 Change(Some(ChangeEnum::AddressOnly(address)))
112 } else {
113 Change(None)
114 }
115 }
116}
117
118#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
119enum InternalPayment {
120 Payment(MoneroAddress, u64),
121 Change(ChangeEnum),
122}
123
124impl InternalPayment {
125 fn address(&self) -> MoneroAddress {
126 match self {
127 InternalPayment::Payment(addr, _) => *addr,
128 InternalPayment::Change(change) => match change {
129 ChangeEnum::AddressOnly(addr) => *addr,
130 ChangeEnum::Standard { view_pair, subaddress } => match subaddress {
132 Some(subaddress) => view_pair.subaddress(Network::Mainnet, *subaddress),
133 None => view_pair.legacy_address(Network::Mainnet),
134 },
135 ChangeEnum::Guaranteed { view_pair, subaddress } => {
136 view_pair.address(Network::Mainnet, *subaddress, None)
137 }
138 },
139 }
140 }
141}
142
143#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
145pub enum SendError {
146 #[error("this library doesn't yet support that RctType")]
148 UnsupportedRctType,
149 #[error("no inputs")]
151 NoInputs,
152 #[error("invalid number of decoys")]
154 InvalidDecoyQuantity,
155 #[error("no outputs")]
157 NoOutputs,
158 #[error("too many outputs")]
160 TooManyOutputs,
161 #[error("only one output and no change address")]
167 NoChange,
168 #[error("multiple addresses with payment IDs")]
172 MultiplePaymentIds,
173 #[error("too much data")]
175 TooMuchArbitraryData,
176 #[error("too large of a transaction")]
178 TooLargeTransaction,
179 #[error("transaction amounts exceed u64::MAX (in {in_amount}, out {out_amount})")]
181 AmountsUnrepresentable {
182 in_amount: u128,
184 out_amount: u128,
186 },
187 #[error(
189 "not enough funds (inputs {inputs}, outputs {outputs}, necessary_fee {necessary_fee:?})"
190 )]
191 NotEnoughFunds {
192 inputs: u64,
194 outputs: u64,
196 necessary_fee: Option<u64>,
201 },
202 #[error("wrong spend private key")]
204 WrongPrivateKey,
205 #[error("this SignableTransaction was created by deserializing a malicious serialization")]
207 MaliciousSerialization,
208 #[error("clsag error ({0})")]
210 ClsagError(ClsagError),
211 #[cfg(feature = "multisig")]
213 #[error("frost error {0}")]
214 FrostError(FrostError),
215}
216
217#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
219pub struct SignableTransaction {
220 rct_type: RctType,
221 outgoing_view_key: Zeroizing<[u8; 32]>,
222 inputs: Vec<OutputWithDecoys>,
223 payments: Vec<InternalPayment>,
224 data: Vec<Vec<u8>>,
225 fee_rate: FeeRate,
226}
227
228struct SignableTransactionWithKeyImages {
229 intent: SignableTransaction,
230 key_images: Vec<EdwardsPoint>,
231}
232
233impl SignableTransaction {
234 fn validate(&self) -> Result<(), SendError> {
235 match self.rct_type {
236 RctType::ClsagBulletproof | RctType::ClsagBulletproofPlus => {}
237 _ => Err(SendError::UnsupportedRctType)?,
238 }
239
240 if self.inputs.is_empty() {
241 Err(SendError::NoInputs)?;
242 }
243 for input in &self.inputs {
244 if input.decoys().len() !=
245 match self.rct_type {
246 RctType::ClsagBulletproof => 11,
247 RctType::ClsagBulletproofPlus => 16,
248 _ => panic!("unsupported RctType"),
249 }
250 {
251 Err(SendError::InvalidDecoyQuantity)?;
252 }
253 }
254
255 if !self.payments.iter().any(|payment| matches!(payment, InternalPayment::Payment(_, _))) {
257 Err(SendError::NoOutputs)?;
258 }
259 if self.payments.len() < 2 {
261 Err(SendError::NoChange)?;
262 }
263 {
265 let mut change_count = 0;
266 for payment in &self.payments {
267 change_count += usize::from(u8::from(matches!(payment, InternalPayment::Change(_))));
268 }
269 if change_count > 1 {
270 Err(SendError::MaliciousSerialization)?;
271 }
272 }
273
274 {
276 let mut payment_ids = 0;
277 for payment in &self.payments {
278 payment_ids += usize::from(u8::from(payment.address().payment_id().is_some()));
279 }
280 if payment_ids > 1 {
281 Err(SendError::MultiplePaymentIds)?;
282 }
283 }
284
285 if self.payments.len() > MAX_COMMITMENTS {
286 Err(SendError::TooManyOutputs)?;
287 }
288
289 for part in &self.data {
291 if part.len() > MAX_ARBITRARY_DATA_SIZE {
292 Err(SendError::TooMuchArbitraryData)?;
293 }
294 }
295
296 const MAX_EXTRA_SIZE: usize = 1060;
299 if self.extra().len() > MAX_EXTRA_SIZE {
300 Err(SendError::TooMuchArbitraryData)?;
301 }
302
303 let weight;
305 {
306 let in_amount: u128 =
307 self.inputs.iter().map(|input| u128::from(input.commitment().amount)).sum();
308 let payments_amount: u128 = self
309 .payments
310 .iter()
311 .filter_map(|payment| match payment {
312 InternalPayment::Payment(_, amount) => Some(u128::from(*amount)),
313 InternalPayment::Change(_) => None,
314 })
315 .sum();
316 let necessary_fee;
317 (weight, necessary_fee) = self.weight_and_necessary_fee();
318 let out_amount = payments_amount + u128::from(necessary_fee);
319 let in_out_amount = u64::try_from(in_amount)
320 .and_then(|in_amount| u64::try_from(out_amount).map(|out_amount| (in_amount, out_amount)));
321 let Ok((in_amount, out_amount)) = in_out_amount else {
322 Err(SendError::AmountsUnrepresentable { in_amount, out_amount })?
323 };
324 if in_amount < out_amount {
325 Err(SendError::NotEnoughFunds {
326 inputs: in_amount,
327 outputs: u64::try_from(payments_amount)
328 .expect("total out fit within u64 but not part of total out"),
329 necessary_fee: Some(necessary_fee),
330 })?;
331 }
332 }
333
334 const MAX_TX_SIZE: usize = (300_000 / 2) - 600;
342 if weight >= MAX_TX_SIZE {
343 Err(SendError::TooLargeTransaction)?;
344 }
345
346 Ok(())
347 }
348
349 pub fn new(
359 rct_type: RctType,
360 outgoing_view_key: Zeroizing<[u8; 32]>,
361 inputs: Vec<OutputWithDecoys>,
362 payments: Vec<(MoneroAddress, u64)>,
363 change: Change,
364 data: Vec<Vec<u8>>,
365 fee_rate: FeeRate,
366 ) -> Result<SignableTransaction, SendError> {
367 let mut payments = payments
369 .into_iter()
370 .map(|(addr, amount)| InternalPayment::Payment(addr, amount))
371 .collect::<Vec<_>>();
372
373 if let Some(change) = change.0 {
374 payments.push(InternalPayment::Change(change));
375 }
376
377 let mut res =
378 SignableTransaction { rct_type, outgoing_view_key, inputs, payments, data, fee_rate };
379 res.validate()?;
380
381 {
383 let mut rng = res.seeded_rng(b"shuffle_payments");
384 res.payments.shuffle(&mut rng);
385 }
386
387 Ok(res)
388 }
389
390 pub fn fee_rate(&self) -> FeeRate {
392 self.fee_rate
393 }
394
395 pub fn necessary_fee(&self) -> u64 {
400 self.weight_and_necessary_fee().1
401 }
402
403 pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
408 fn write_payment<W: io::Write>(payment: &InternalPayment, w: &mut W) -> io::Result<()> {
409 match payment {
410 InternalPayment::Payment(addr, amount) => {
411 w.write_all(&[0])?;
412 write_vec(write_byte, addr.to_string().as_bytes(), w)?;
413 w.write_all(&amount.to_le_bytes())
414 }
415 InternalPayment::Change(change) => match change {
416 ChangeEnum::AddressOnly(addr) => {
417 w.write_all(&[1])?;
418 write_vec(write_byte, addr.to_string().as_bytes(), w)
419 }
420 ChangeEnum::Standard { view_pair, subaddress } => {
421 w.write_all(&[2])?;
422 write_point(&view_pair.spend(), w)?;
423 write_scalar(&view_pair.view, w)?;
424 if let Some(subaddress) = subaddress {
425 w.write_all(&subaddress.account().to_le_bytes())?;
426 w.write_all(&subaddress.address().to_le_bytes())
427 } else {
428 w.write_all(&0u32.to_le_bytes())?;
429 w.write_all(&0u32.to_le_bytes())
430 }
431 }
432 ChangeEnum::Guaranteed { view_pair, subaddress } => {
433 w.write_all(&[3])?;
434 write_point(&view_pair.spend(), w)?;
435 write_scalar(&view_pair.0.view, w)?;
436 if let Some(subaddress) = subaddress {
437 w.write_all(&subaddress.account().to_le_bytes())?;
438 w.write_all(&subaddress.address().to_le_bytes())
439 } else {
440 w.write_all(&0u32.to_le_bytes())?;
441 w.write_all(&0u32.to_le_bytes())
442 }
443 }
444 },
445 }
446 }
447
448 write_byte(&u8::from(self.rct_type), w)?;
449 w.write_all(self.outgoing_view_key.as_slice())?;
450 write_vec(OutputWithDecoys::write, &self.inputs, w)?;
451 write_vec(write_payment, &self.payments, w)?;
452 write_vec(|data, w| write_vec(write_byte, data, w), &self.data, w)?;
453 self.fee_rate.write(w)
454 }
455
456 pub fn serialize(&self) -> Vec<u8> {
461 let mut buf = Vec::with_capacity(256);
462 self.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
463 buf
464 }
465
466 pub fn read<R: io::Read>(r: &mut R) -> io::Result<SignableTransaction> {
471 fn read_address<R: io::Read>(r: &mut R) -> io::Result<MoneroAddress> {
472 String::from_utf8(read_vec(read_byte, None, r)?)
473 .ok()
474 .and_then(|str| MoneroAddress::from_str_with_unchecked_network(&str).ok())
475 .ok_or_else(|| io::Error::other("invalid address"))
476 }
477
478 fn read_payment<R: io::Read>(r: &mut R) -> io::Result<InternalPayment> {
479 Ok(match read_byte(r)? {
480 0 => InternalPayment::Payment(read_address(r)?, read_u64(r)?),
481 1 => InternalPayment::Change(ChangeEnum::AddressOnly(read_address(r)?)),
482 2 => InternalPayment::Change(ChangeEnum::Standard {
483 view_pair: ViewPair::new(read_point(r)?, Zeroizing::new(read_scalar(r)?))
484 .map_err(io::Error::other)?,
485 subaddress: SubaddressIndex::new(read_u32(r)?, read_u32(r)?),
486 }),
487 3 => InternalPayment::Change(ChangeEnum::Guaranteed {
488 view_pair: GuaranteedViewPair::new(read_point(r)?, Zeroizing::new(read_scalar(r)?))
489 .map_err(io::Error::other)?,
490 subaddress: SubaddressIndex::new(read_u32(r)?, read_u32(r)?),
491 }),
492 _ => Err(io::Error::other("invalid payment"))?,
493 })
494 }
495
496 let res = SignableTransaction {
497 rct_type: RctType::try_from(read_byte(r)?)
498 .map_err(|()| io::Error::other("unsupported/invalid RctType"))?,
499 outgoing_view_key: Zeroizing::new(read_bytes(r)?),
500 inputs: read_vec(OutputWithDecoys::read, None, r)?,
501 payments: read_vec(read_payment, None, r)?,
502 data: read_vec(|r| read_vec(read_byte, None, r), None, r)?,
503 fee_rate: FeeRate::read(r)?,
504 };
505 match res.validate() {
506 Ok(()) => {}
507 Err(e) => Err(io::Error::other(e))?,
508 }
509 Ok(res)
510 }
511
512 fn with_key_images(mut self, key_images: Vec<EdwardsPoint>) -> SignableTransactionWithKeyImages {
513 debug_assert_eq!(self.inputs.len(), key_images.len());
514
515 let mut sorted_inputs = self.inputs.into_iter().zip(key_images).collect::<Vec<_>>();
517 sorted_inputs
518 .sort_by(|(_, key_image_a), (_, key_image_b)| key_image_sort(key_image_a, key_image_b));
519
520 self.inputs = Vec::with_capacity(sorted_inputs.len());
521 let mut key_images = Vec::with_capacity(sorted_inputs.len());
522 for (input, key_image) in sorted_inputs {
523 self.inputs.push(input);
524 key_images.push(key_image);
525 }
526
527 SignableTransactionWithKeyImages { intent: self, key_images }
528 }
529
530 pub fn sign(
532 self,
533 rng: &mut (impl RngCore + CryptoRng),
534 sender_spend_key: &Zeroizing<Scalar>,
535 ) -> Result<Transaction, SendError> {
536 let mut key_images = vec![];
538 for input in &self.inputs {
539 let input_key = Zeroizing::new(sender_spend_key.deref() + input.key_offset());
540 if (input_key.deref() * ED25519_BASEPOINT_TABLE) != input.key() {
541 Err(SendError::WrongPrivateKey)?;
542 }
543 let key_image = input_key.deref() * hash_to_point(input.key().compress().to_bytes());
544 key_images.push(key_image);
545 }
546
547 let tx = self.with_key_images(key_images);
549
550 let mut clsag_signs = Vec::with_capacity(tx.intent.inputs.len());
552 for input in &tx.intent.inputs {
553 let input_key = Zeroizing::new(sender_spend_key.deref() + input.key_offset());
555 clsag_signs.push((
556 input_key,
557 ClsagContext::new(input.decoys().clone(), input.commitment().clone())
558 .map_err(SendError::ClsagError)?,
559 ));
560 }
561
562 let mask_sum = tx.intent.sum_output_masks(&tx.key_images);
564
565 let mut tx = tx.transaction_without_signatures();
567
568 let clsags_and_pseudo_outs = Clsag::sign(
570 rng,
571 clsag_signs,
572 mask_sum,
573 tx.signature_hash().expect("signing a transaction which isn't signed?"),
574 )
575 .map_err(SendError::ClsagError)?;
576
577 let inputs_len = tx.prefix().inputs.len();
579 let Transaction::V2 {
580 proofs:
581 Some(RctProofs {
582 prunable: RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. },
583 ..
584 }),
585 ..
586 } = tx
587 else {
588 panic!("not signing clsag?")
589 };
590 *clsags = Vec::with_capacity(inputs_len);
591 *pseudo_outs = Vec::with_capacity(inputs_len);
592 for (clsag, pseudo_out) in clsags_and_pseudo_outs {
593 clsags.push(clsag);
594 pseudo_outs.push(pseudo_out);
595 }
596
597 Ok(tx)
599 }
600}