1use core::cmp::Ordering;
2#[allow(unused_imports)]
3use std_shims::prelude::*;
4use std_shims::io::{self, Read, Write};
5
6use zeroize::Zeroize;
7
8use crate::{
9 io::*,
10 primitives::keccak256,
11 ring_signatures::RingSignature,
12 ringct::{bulletproofs::Bulletproof, PrunedRctProofs},
13};
14
15pub const MAX_NON_MINER_TRANSACTION_SIZE: usize = 1_000_000;
19
20const MAX_MINER_TRANSACTION_INPUTS: usize = 1;
21
22const NON_MINER_TRANSACTION_INPUT_SIZE_LOWER_BOUND: usize = 32;
23const NON_MINER_TRANSACTION_INPUTS_UPPER_BOUND: usize =
24 MAX_NON_MINER_TRANSACTION_SIZE / NON_MINER_TRANSACTION_INPUT_SIZE_LOWER_BOUND;
25
26const fn const_max(a: usize, b: usize) -> usize {
27 if a > b {
28 a
29 } else {
30 b
31 }
32}
33
34pub const INPUTS_UPPER_BOUND: usize =
40 const_max(MAX_MINER_TRANSACTION_INPUTS, NON_MINER_TRANSACTION_INPUTS_UPPER_BOUND);
41
42const NON_MINER_TRANSACTION_OUTPUT_SIZE_LOWER_BOUND: usize = 32;
43const MAX_NON_MINER_TRANSACTION_OUTPUTS: usize =
44 MAX_NON_MINER_TRANSACTION_SIZE / NON_MINER_TRANSACTION_OUTPUT_SIZE_LOWER_BOUND;
45
46#[derive(Clone, PartialEq, Eq, Debug)]
48pub enum Input {
49 Gen(usize),
51 ToKey {
53 amount: Option<u64>,
55 key_offsets: Vec<u64>,
57 key_image: CompressedPoint,
59 },
60}
61
62impl Input {
63 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
65 match self {
66 Input::Gen(height) => {
67 w.write_all(&[255])?;
68 VarInt::write(height, w)
69 }
70
71 Input::ToKey { amount, key_offsets, key_image } => {
72 w.write_all(&[2])?;
73 VarInt::write(&amount.unwrap_or(0), w)?;
74 write_vec(VarInt::write, key_offsets, w)?;
75 key_image.write(w)
76 }
77 }
78 }
79
80 pub fn serialize(&self) -> Vec<u8> {
82 let mut res = vec![];
83 self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
84 res
85 }
86
87 pub fn read<R: Read>(r: &mut R) -> io::Result<Input> {
89 Ok(match read_byte(r)? {
90 255 => Input::Gen(VarInt::read(r)?),
91 2 => {
92 let amount = VarInt::read(r)?;
93 let amount = if amount == 0 { None } else { Some(amount) };
99 Input::ToKey {
100 amount,
101 key_offsets: read_vec(VarInt::read, Some(MAX_NON_MINER_TRANSACTION_SIZE), r)?,
103 key_image: CompressedPoint::read(r)?,
104 }
105 }
106 _ => Err(io::Error::other("Tried to deserialize unknown/unused input type"))?,
107 })
108 }
109}
110
111#[derive(Clone, PartialEq, Eq, Debug)]
113pub struct Output {
114 pub amount: Option<u64>,
116 pub key: CompressedPoint,
118 pub view_tag: Option<u8>,
120}
121
122impl Output {
123 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
125 VarInt::write(&self.amount.unwrap_or(0), w)?;
126 w.write_all(&[2 + u8::from(self.view_tag.is_some())])?;
127 w.write_all(&self.key.to_bytes())?;
128 if let Some(view_tag) = self.view_tag {
129 w.write_all(&[view_tag])?;
130 }
131 Ok(())
132 }
133
134 pub fn serialize(&self) -> Vec<u8> {
136 let mut res = Vec::with_capacity(<u64 as VarInt>::UPPER_BOUND + 1 + 32 + 1);
137 self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
138 res
139 }
140
141 pub fn read<R: Read>(rct: bool, r: &mut R) -> io::Result<Output> {
143 let amount = VarInt::read(r)?;
144 let amount = if rct {
145 if amount != 0 {
146 Err(io::Error::other("RCT TX output wasn't 0"))?;
147 }
148 None
149 } else {
150 Some(amount)
151 };
152
153 let view_tag = match read_byte(r)? {
154 2 => false,
155 3 => true,
156 _ => Err(io::Error::other("Tried to deserialize unknown/unused output type"))?,
157 };
158
159 Ok(Output {
160 amount,
161 key: CompressedPoint::read(r)?,
162 view_tag: if view_tag { Some(read_byte(r)?) } else { None },
163 })
164 }
165}
166
167#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
172pub enum Timelock {
173 None,
175 Block(usize),
177 Time(u64),
179}
180
181impl Timelock {
182 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
184 match self {
185 Timelock::None => VarInt::write(&0u8, w),
186 Timelock::Block(block) => VarInt::write(block, w),
187 Timelock::Time(time) => VarInt::write(time, w),
188 }
189 }
190
191 pub fn serialize(&self) -> Vec<u8> {
193 let mut res = Vec::with_capacity(1);
194 self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
195 res
196 }
197
198 pub fn read<R: Read>(r: &mut R) -> io::Result<Self> {
200 const TIMELOCK_BLOCK_THRESHOLD: usize = 500_000_000;
201
202 let raw = <u64 as VarInt>::read(r)?;
203 Ok(if raw == 0 {
204 Timelock::None
205 } else if raw <
206 u64::try_from(TIMELOCK_BLOCK_THRESHOLD)
207 .expect("TIMELOCK_BLOCK_THRESHOLD didn't fit in a u64")
208 {
209 Timelock::Block(usize::try_from(raw).expect(
210 "timelock overflowed usize despite being less than a const representable with a usize",
211 ))
212 } else {
213 Timelock::Time(raw)
214 })
215 }
216}
217
218impl PartialOrd for Timelock {
219 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
220 match (self, other) {
221 (Timelock::None, Timelock::None) => Some(Ordering::Equal),
222 (Timelock::None, _) => Some(Ordering::Less),
223 (_, Timelock::None) => Some(Ordering::Greater),
224 (Timelock::Block(a), Timelock::Block(b)) => a.partial_cmp(b),
225 (Timelock::Time(a), Timelock::Time(b)) => a.partial_cmp(b),
226 _ => None,
227 }
228 }
229}
230
231#[derive(Clone, PartialEq, Eq, Debug)]
236pub struct TransactionPrefix {
237 pub additional_timelock: Timelock,
242 pub inputs: Vec<Input>,
244 pub outputs: Vec<Output>,
246 pub extra: Vec<u8>,
251}
252
253impl TransactionPrefix {
254 fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
258 self.additional_timelock.write(w)?;
259 write_vec(Input::write, &self.inputs, w)?;
260 write_vec(Output::write, &self.outputs, w)?;
261 VarInt::write(&self.extra.len(), w)?;
262 w.write_all(&self.extra)
263 }
264
265 pub fn read<R: Read>(r: &mut R, version: u64) -> io::Result<TransactionPrefix> {
274 let additional_timelock = Timelock::read(r)?;
275
276 let inputs = read_vec(|r| Input::read(r), Some(INPUTS_UPPER_BOUND), r)?;
277 if inputs.is_empty() {
278 Err(io::Error::other("transaction had no inputs"))?;
279 }
280 let is_miner_tx = matches!(inputs[0], Input::Gen { .. });
281
282 let max_outputs = if is_miner_tx { None } else { Some(MAX_NON_MINER_TRANSACTION_OUTPUTS) };
283 let mut prefix = TransactionPrefix {
284 additional_timelock,
285 inputs,
286 outputs: read_vec(|r| Output::read((!is_miner_tx) && (version == 2), r), max_outputs, r)?,
287 extra: vec![],
288 };
289 let max_extra = if is_miner_tx { None } else { Some(MAX_NON_MINER_TRANSACTION_SIZE) };
290 prefix.extra = read_vec(read_byte, max_extra, r)?;
291 Ok(prefix)
292 }
293
294 fn hash(&self, version: u64) -> [u8; 32] {
295 let mut buf = vec![];
296 VarInt::write(&version, &mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
297 self.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
298 keccak256(buf)
299 }
300}
301
302#[allow(private_bounds)]
303mod sealed {
304 use core::fmt::Debug;
305 use crate::ringct::*;
306 use super::*;
307
308 pub(crate) trait PotentiallyPrunedRingSignatures:
309 Clone + PartialEq + Eq + Default + Debug
310 {
311 fn signatures_to_write(&self) -> &[RingSignature];
312 fn read_signatures(inputs: &[Input], r: &mut impl Read) -> io::Result<Self>;
313 }
314
315 impl PotentiallyPrunedRingSignatures for Vec<RingSignature> {
316 fn signatures_to_write(&self) -> &[RingSignature] {
317 self
318 }
319 fn read_signatures(inputs: &[Input], r: &mut impl Read) -> io::Result<Self> {
320 let mut signatures = Vec::with_capacity(inputs.len());
321 for input in inputs {
322 match input {
323 Input::ToKey { key_offsets, .. } => {
324 signatures.push(RingSignature::read(key_offsets.len(), r)?)
325 }
326 _ => Err(io::Error::other("reading signatures for a transaction with non-ToKey inputs"))?,
327 }
328 }
329 Ok(signatures)
330 }
331 }
332
333 impl PotentiallyPrunedRingSignatures for () {
334 fn signatures_to_write(&self) -> &[RingSignature] {
335 &[]
336 }
337 fn read_signatures(_: &[Input], _: &mut impl Read) -> io::Result<Self> {
338 Ok(())
339 }
340 }
341
342 pub(crate) trait PotentiallyPrunedRctProofs: Clone + PartialEq + Eq + Debug {
343 fn write(&self, w: &mut impl Write) -> io::Result<()>;
344 fn read(
345 ring_length: usize,
346 inputs: usize,
347 outputs: usize,
348 r: &mut impl Read,
349 ) -> io::Result<Option<Self>>;
350 fn rct_type(&self) -> RctType;
351 fn base(&self) -> &RctBase;
352 }
353
354 impl PotentiallyPrunedRctProofs for RctProofs {
355 fn write(&self, w: &mut impl Write) -> io::Result<()> {
356 self.write(w)
357 }
358 fn read(
359 ring_length: usize,
360 inputs: usize,
361 outputs: usize,
362 r: &mut impl Read,
363 ) -> io::Result<Option<Self>> {
364 RctProofs::read(ring_length, inputs, outputs, r)
365 }
366 fn rct_type(&self) -> RctType {
367 self.rct_type()
368 }
369 fn base(&self) -> &RctBase {
370 &self.base
371 }
372 }
373
374 impl PotentiallyPrunedRctProofs for PrunedRctProofs {
375 fn write(&self, w: &mut impl Write) -> io::Result<()> {
376 self.base.write(w, self.rct_type)
377 }
378 fn read(
379 _ring_length: usize,
380 inputs: usize,
381 outputs: usize,
382 r: &mut impl Read,
383 ) -> io::Result<Option<Self>> {
384 Ok(RctBase::read(inputs, outputs, r)?.map(|(rct_type, base)| Self { rct_type, base }))
385 }
386 fn rct_type(&self) -> RctType {
387 self.rct_type
388 }
389 fn base(&self) -> &RctBase {
390 &self.base
391 }
392 }
393
394 trait Sealed {}
395
396 pub trait PotentiallyPruned: Sealed {
398 type RingSignatures: PotentiallyPrunedRingSignatures;
400 type RctProofs: PotentiallyPrunedRctProofs;
402 }
403 #[derive(Clone, PartialEq, Eq, Debug)]
405 pub struct NotPruned;
406 impl Sealed for NotPruned {}
407 impl PotentiallyPruned for NotPruned {
408 type RingSignatures = Vec<RingSignature>;
409 type RctProofs = RctProofs;
410 }
411 #[derive(Clone, PartialEq, Eq, Debug)]
413 pub struct Pruned;
414 impl Sealed for Pruned {}
415 impl PotentiallyPruned for Pruned {
416 type RingSignatures = ();
417 type RctProofs = PrunedRctProofs;
418 }
419}
420pub use sealed::*;
421
422#[allow(clippy::large_enum_variant)]
424#[derive(Clone, PartialEq, Eq, Debug)]
425pub enum Transaction<P: PotentiallyPruned = NotPruned> {
426 V1 {
428 prefix: TransactionPrefix,
430 signatures: P::RingSignatures,
432 },
433 V2 {
435 prefix: TransactionPrefix,
437 proofs: Option<P::RctProofs>,
439 },
440}
441
442enum PrunableHash<'a> {
443 V1(&'a [RingSignature]),
444 V2([u8; 32]),
445}
446
447#[allow(private_bounds)]
448impl<P: PotentiallyPruned> Transaction<P> {
449 pub fn version(&self) -> u8 {
451 match self {
452 Transaction::V1 { .. } => 1,
453 Transaction::V2 { .. } => 2,
454 }
455 }
456
457 pub fn prefix(&self) -> &TransactionPrefix {
459 match self {
460 Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => prefix,
461 }
462 }
463
464 pub fn prefix_mut(&mut self) -> &mut TransactionPrefix {
466 match self {
467 Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => prefix,
468 }
469 }
470
471 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
476 VarInt::write(&self.version(), w)?;
477 match self {
478 Transaction::V1 { prefix, signatures } => {
479 prefix.write(w)?;
480 for ring_sig in signatures.signatures_to_write() {
481 ring_sig.write(w)?;
482 }
483 }
484 Transaction::V2 { prefix, proofs } => {
485 prefix.write(w)?;
486 match proofs {
487 None => w.write_all(&[0])?,
488 Some(proofs) => proofs.write(w)?,
489 }
490 }
491 }
492 Ok(())
493 }
494
495 pub fn serialize(&self) -> Vec<u8> {
497 let mut res = Vec::with_capacity(2048);
498 self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
499 res
500 }
501
502 pub fn read<R: Read>(r: &mut R) -> io::Result<Self> {
508 let version = VarInt::read(r)?;
509 let prefix = TransactionPrefix::read(r, version)?;
510
511 if version == 1 {
512 let signatures = if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
513 Default::default()
514 } else {
515 P::RingSignatures::read_signatures(&prefix.inputs, r)?
516 };
517
518 Ok(Transaction::V1 { prefix, signatures })
519 } else if version == 2 {
520 let proofs = P::RctProofs::read(
521 prefix.inputs.first().map_or(0, |input| match input {
522 Input::Gen(_) => 0,
523 Input::ToKey { key_offsets, .. } => key_offsets.len(),
524 }),
525 prefix.inputs.len(),
526 prefix.outputs.len(),
527 r,
528 )?;
529
530 Ok(Transaction::V2 { prefix, proofs })
531 } else {
532 Err(io::Error::other("tried to deserialize unknown version"))
533 }
534 }
535
536 #[allow(clippy::needless_pass_by_value)]
538 fn hash_with_prunable_hash(&self, prunable: PrunableHash<'_>) -> [u8; 32] {
539 match self {
540 Transaction::V1 { prefix, .. } => {
541 let mut buf = Vec::with_capacity(512);
542
543 VarInt::write(&self.version(), &mut buf)
545 .expect("write failed but <Vec as io::Write> doesn't fail");
546 prefix.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
547
548 let PrunableHash::V1(signatures) = prunable else {
550 panic!("hashing v1 TX with non-v1 prunable data")
551 };
552 for signature in signatures {
553 signature.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
554 }
555
556 keccak256(buf)
557 }
558 Transaction::V2 { prefix, proofs } => {
559 let mut hashes = Vec::with_capacity(96);
560
561 hashes.extend(prefix.hash(2));
562
563 if let Some(proofs) = proofs {
564 let mut buf = Vec::with_capacity(512);
565 proofs
566 .base()
567 .write(&mut buf, proofs.rct_type())
568 .expect("write failed but <Vec as io::Write> doesn't fail");
569 hashes.extend(keccak256(&buf));
570 } else {
571 hashes.extend(keccak256([0]));
573 }
574 let PrunableHash::V2(prunable_hash) = prunable else {
575 panic!("hashing v2 TX with non-v2 prunable data")
576 };
577 hashes.extend(prunable_hash);
578
579 keccak256(hashes)
580 }
581 }
582 }
583}
584
585impl Transaction<NotPruned> {
586 pub fn hash(&self) -> [u8; 32] {
588 match self {
589 Transaction::V1 { signatures, .. } => {
590 self.hash_with_prunable_hash(PrunableHash::V1(signatures))
591 }
592 Transaction::V2 { proofs, .. } => {
593 self.hash_with_prunable_hash(PrunableHash::V2(if let Some(proofs) = proofs {
594 let mut buf = Vec::with_capacity(1024);
595 proofs
596 .prunable
597 .write(&mut buf, proofs.rct_type())
598 .expect("write failed but <Vec as io::Write> doesn't fail");
599 keccak256(buf)
600 } else {
601 [0; 32]
602 }))
603 }
604 }
605 }
606
607 pub fn signature_hash(&self) -> Option<[u8; 32]> {
611 Some(match self {
612 Transaction::V1 { prefix, .. } => {
613 if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
614 None?;
615 }
616 self.hash_with_prunable_hash(PrunableHash::V1(&[]))
617 }
618 Transaction::V2 { proofs, .. } => self.hash_with_prunable_hash({
619 let Some(proofs) = proofs else { None? };
620 let mut buf = Vec::with_capacity(1024);
621 proofs
622 .prunable
623 .signature_write(&mut buf)
624 .expect("write failed but <Vec as io::Write> doesn't fail");
625 PrunableHash::V2(keccak256(buf))
626 }),
627 })
628 }
629
630 fn is_rct_bulletproof(&self) -> bool {
631 match self {
632 Transaction::V1 { .. } => false,
633 Transaction::V2 { proofs, .. } => {
634 let Some(proofs) = proofs else { return false };
635 proofs.rct_type().bulletproof()
636 }
637 }
638 }
639
640 fn is_rct_bulletproof_plus(&self) -> bool {
641 match self {
642 Transaction::V1 { .. } => false,
643 Transaction::V2 { proofs, .. } => {
644 let Some(proofs) = proofs else { return false };
645 proofs.rct_type().bulletproof_plus()
646 }
647 }
648 }
649
650 pub fn weight(&self) -> usize {
652 let blob_size = self.serialize().len();
653
654 let bp = self.is_rct_bulletproof();
655 let bp_plus = self.is_rct_bulletproof_plus();
656 if !(bp || bp_plus) {
657 blob_size
658 } else {
659 blob_size +
660 Bulletproof::calculate_clawback(
661 bp_plus,
662 match self {
663 Transaction::V1 { .. } => panic!("v1 transaction was BP(+)"),
664 Transaction::V2 { prefix, .. } => prefix.outputs.len(),
665 },
666 )
667 .0
668 }
669 }
670}
671
672impl From<Transaction<NotPruned>> for Transaction<Pruned> {
673 fn from(tx: Transaction<NotPruned>) -> Transaction<Pruned> {
674 match tx {
675 Transaction::V1 { prefix, .. } => Transaction::V1 { prefix, signatures: () },
676 Transaction::V2 { prefix, proofs } => Transaction::V2 {
677 prefix,
678 proofs: proofs
679 .map(|proofs| PrunedRctProofs { rct_type: proofs.rct_type(), base: proofs.base }),
680 },
681 }
682 }
683}