1use core::cmp::Ordering;
2use std_shims::{
3 vec,
4 vec::Vec,
5 io::{self, Read, Write},
6};
7
8use zeroize::Zeroize;
9
10use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
11
12use crate::{
13 io::*,
14 primitives::keccak256,
15 ring_signatures::RingSignature,
16 ringct::{bulletproofs::Bulletproof, PrunedRctProofs},
17};
18
19#[derive(Clone, PartialEq, Eq, Debug)]
21pub enum Input {
22 Gen(usize),
24 ToKey {
26 amount: Option<u64>,
28 key_offsets: Vec<u64>,
30 key_image: EdwardsPoint,
32 },
33}
34
35impl Input {
36 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
38 match self {
39 Input::Gen(height) => {
40 w.write_all(&[255])?;
41 write_varint(height, w)
42 }
43
44 Input::ToKey { amount, key_offsets, key_image } => {
45 w.write_all(&[2])?;
46 write_varint(&amount.unwrap_or(0), w)?;
47 write_vec(write_varint, key_offsets, w)?;
48 write_point(key_image, w)
49 }
50 }
51 }
52
53 pub fn serialize(&self) -> Vec<u8> {
55 let mut res = vec![];
56 self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
57 res
58 }
59
60 pub fn read<R: Read>(r: &mut R) -> io::Result<Input> {
62 Ok(match read_byte(r)? {
63 255 => Input::Gen(read_varint(r)?),
64 2 => {
65 let amount = read_varint(r)?;
66 let amount = if amount == 0 { None } else { Some(amount) };
72 Input::ToKey {
73 amount,
74 key_offsets: read_vec(read_varint, None, r)?,
75 key_image: read_torsion_free_point(r)?,
76 }
77 }
78 _ => Err(io::Error::other("Tried to deserialize unknown/unused input type"))?,
79 })
80 }
81}
82
83#[derive(Clone, PartialEq, Eq, Debug)]
85pub struct Output {
86 pub amount: Option<u64>,
88 pub key: CompressedEdwardsY,
90 pub view_tag: Option<u8>,
92}
93
94impl Output {
95 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
97 write_varint(&self.amount.unwrap_or(0), w)?;
98 w.write_all(&[2 + u8::from(self.view_tag.is_some())])?;
99 w.write_all(&self.key.to_bytes())?;
100 if let Some(view_tag) = self.view_tag {
101 w.write_all(&[view_tag])?;
102 }
103 Ok(())
104 }
105
106 pub fn serialize(&self) -> Vec<u8> {
108 let mut res = Vec::with_capacity(8 + 1 + 32);
109 self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
110 res
111 }
112
113 pub fn read<R: Read>(rct: bool, r: &mut R) -> io::Result<Output> {
115 let amount = read_varint(r)?;
116 let amount = if rct {
117 if amount != 0 {
118 Err(io::Error::other("RCT TX output wasn't 0"))?;
119 }
120 None
121 } else {
122 Some(amount)
123 };
124
125 let view_tag = match read_byte(r)? {
126 2 => false,
127 3 => true,
128 _ => Err(io::Error::other("Tried to deserialize unknown/unused output type"))?,
129 };
130
131 Ok(Output {
132 amount,
133 key: CompressedEdwardsY(read_bytes(r)?),
134 view_tag: if view_tag { Some(read_byte(r)?) } else { None },
135 })
136 }
137}
138
139#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
144pub enum Timelock {
145 None,
147 Block(usize),
149 Time(u64),
151}
152
153impl Timelock {
154 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
156 match self {
157 Timelock::None => write_varint(&0u8, w),
158 Timelock::Block(block) => write_varint(block, w),
159 Timelock::Time(time) => write_varint(time, w),
160 }
161 }
162
163 pub fn serialize(&self) -> Vec<u8> {
165 let mut res = Vec::with_capacity(1);
166 self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
167 res
168 }
169
170 pub fn read<R: Read>(r: &mut R) -> io::Result<Self> {
172 const TIMELOCK_BLOCK_THRESHOLD: usize = 500_000_000;
173
174 let raw = read_varint::<_, u64>(r)?;
175 Ok(if raw == 0 {
176 Timelock::None
177 } else if raw <
178 u64::try_from(TIMELOCK_BLOCK_THRESHOLD)
179 .expect("TIMELOCK_BLOCK_THRESHOLD didn't fit in a u64")
180 {
181 Timelock::Block(usize::try_from(raw).expect(
182 "timelock overflowed usize despite being less than a const representable with a usize",
183 ))
184 } else {
185 Timelock::Time(raw)
186 })
187 }
188}
189
190impl PartialOrd for Timelock {
191 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
192 match (self, other) {
193 (Timelock::None, Timelock::None) => Some(Ordering::Equal),
194 (Timelock::None, _) => Some(Ordering::Less),
195 (_, Timelock::None) => Some(Ordering::Greater),
196 (Timelock::Block(a), Timelock::Block(b)) => a.partial_cmp(b),
197 (Timelock::Time(a), Timelock::Time(b)) => a.partial_cmp(b),
198 _ => None,
199 }
200 }
201}
202
203#[derive(Clone, PartialEq, Eq, Debug)]
208pub struct TransactionPrefix {
209 pub additional_timelock: Timelock,
214 pub inputs: Vec<Input>,
216 pub outputs: Vec<Output>,
218 pub extra: Vec<u8>,
223}
224
225impl TransactionPrefix {
226 fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
230 self.additional_timelock.write(w)?;
231 write_vec(Input::write, &self.inputs, w)?;
232 write_vec(Output::write, &self.outputs, w)?;
233 write_varint(&self.extra.len(), w)?;
234 w.write_all(&self.extra)
235 }
236
237 pub fn read<R: Read>(r: &mut R, version: u64) -> io::Result<TransactionPrefix> {
242 let additional_timelock = Timelock::read(r)?;
243
244 let inputs = read_vec(|r| Input::read(r), None, r)?;
245 if inputs.is_empty() {
246 Err(io::Error::other("transaction had no inputs"))?;
247 }
248 let is_miner_tx = matches!(inputs[0], Input::Gen { .. });
249
250 let mut prefix = TransactionPrefix {
251 additional_timelock,
252 inputs,
253 outputs: read_vec(|r| Output::read((!is_miner_tx) && (version == 2), r), None, r)?,
254 extra: vec![],
255 };
256 prefix.extra = read_vec(read_byte, None, r)?;
257 Ok(prefix)
258 }
259
260 fn hash(&self, version: u64) -> [u8; 32] {
261 let mut buf = vec![];
262 write_varint(&version, &mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
263 self.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
264 keccak256(buf)
265 }
266}
267
268#[allow(private_bounds)]
269mod sealed {
270 use core::fmt::Debug;
271 use crate::ringct::*;
272 use super::*;
273
274 pub(crate) trait PotentiallyPrunedRingSignatures:
275 Clone + PartialEq + Eq + Default + Debug
276 {
277 fn signatures_to_write(&self) -> &[RingSignature];
278 fn read_signatures(inputs: &[Input], r: &mut impl Read) -> io::Result<Self>;
279 }
280
281 impl PotentiallyPrunedRingSignatures for Vec<RingSignature> {
282 fn signatures_to_write(&self) -> &[RingSignature] {
283 self
284 }
285 fn read_signatures(inputs: &[Input], r: &mut impl Read) -> io::Result<Self> {
286 let mut signatures = Vec::with_capacity(inputs.len());
287 for input in inputs {
288 match input {
289 Input::ToKey { key_offsets, .. } => {
290 signatures.push(RingSignature::read(key_offsets.len(), r)?)
291 }
292 _ => Err(io::Error::other("reading signatures for a transaction with non-ToKey inputs"))?,
293 }
294 }
295 Ok(signatures)
296 }
297 }
298
299 impl PotentiallyPrunedRingSignatures for () {
300 fn signatures_to_write(&self) -> &[RingSignature] {
301 &[]
302 }
303 fn read_signatures(_: &[Input], _: &mut impl Read) -> io::Result<Self> {
304 Ok(())
305 }
306 }
307
308 pub(crate) trait PotentiallyPrunedRctProofs: Clone + PartialEq + Eq + Debug {
309 fn write(&self, w: &mut impl Write) -> io::Result<()>;
310 fn read(
311 ring_length: usize,
312 inputs: usize,
313 outputs: usize,
314 r: &mut impl Read,
315 ) -> io::Result<Option<Self>>;
316 fn rct_type(&self) -> RctType;
317 fn base(&self) -> &RctBase;
318 }
319
320 impl PotentiallyPrunedRctProofs for RctProofs {
321 fn write(&self, w: &mut impl Write) -> io::Result<()> {
322 self.write(w)
323 }
324 fn read(
325 ring_length: usize,
326 inputs: usize,
327 outputs: usize,
328 r: &mut impl Read,
329 ) -> io::Result<Option<Self>> {
330 RctProofs::read(ring_length, inputs, outputs, r)
331 }
332 fn rct_type(&self) -> RctType {
333 self.rct_type()
334 }
335 fn base(&self) -> &RctBase {
336 &self.base
337 }
338 }
339
340 impl PotentiallyPrunedRctProofs for PrunedRctProofs {
341 fn write(&self, w: &mut impl Write) -> io::Result<()> {
342 self.base.write(w, self.rct_type)
343 }
344 fn read(
345 _ring_length: usize,
346 inputs: usize,
347 outputs: usize,
348 r: &mut impl Read,
349 ) -> io::Result<Option<Self>> {
350 Ok(RctBase::read(inputs, outputs, r)?.map(|(rct_type, base)| Self { rct_type, base }))
351 }
352 fn rct_type(&self) -> RctType {
353 self.rct_type
354 }
355 fn base(&self) -> &RctBase {
356 &self.base
357 }
358 }
359
360 trait Sealed {}
361
362 pub trait PotentiallyPruned: Sealed {
364 type RingSignatures: PotentiallyPrunedRingSignatures;
366 type RctProofs: PotentiallyPrunedRctProofs;
368 }
369 #[derive(Clone, PartialEq, Eq, Debug)]
371 pub struct NotPruned;
372 impl Sealed for NotPruned {}
373 impl PotentiallyPruned for NotPruned {
374 type RingSignatures = Vec<RingSignature>;
375 type RctProofs = RctProofs;
376 }
377 #[derive(Clone, PartialEq, Eq, Debug)]
379 pub struct Pruned;
380 impl Sealed for Pruned {}
381 impl PotentiallyPruned for Pruned {
382 type RingSignatures = ();
383 type RctProofs = PrunedRctProofs;
384 }
385}
386pub use sealed::*;
387
388#[allow(clippy::large_enum_variant)]
390#[derive(Clone, PartialEq, Eq, Debug)]
391pub enum Transaction<P: PotentiallyPruned = NotPruned> {
392 V1 {
394 prefix: TransactionPrefix,
396 signatures: P::RingSignatures,
398 },
399 V2 {
401 prefix: TransactionPrefix,
403 proofs: Option<P::RctProofs>,
405 },
406}
407
408enum PrunableHash<'a> {
409 V1(&'a [RingSignature]),
410 V2([u8; 32]),
411}
412
413#[allow(private_bounds)]
414impl<P: PotentiallyPruned> Transaction<P> {
415 pub fn version(&self) -> u8 {
417 match self {
418 Transaction::V1 { .. } => 1,
419 Transaction::V2 { .. } => 2,
420 }
421 }
422
423 pub fn prefix(&self) -> &TransactionPrefix {
425 match self {
426 Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => prefix,
427 }
428 }
429
430 pub fn prefix_mut(&mut self) -> &mut TransactionPrefix {
432 match self {
433 Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => prefix,
434 }
435 }
436
437 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
442 write_varint(&self.version(), w)?;
443 match self {
444 Transaction::V1 { prefix, signatures } => {
445 prefix.write(w)?;
446 for ring_sig in signatures.signatures_to_write() {
447 ring_sig.write(w)?;
448 }
449 }
450 Transaction::V2 { prefix, proofs } => {
451 prefix.write(w)?;
452 match proofs {
453 None => w.write_all(&[0])?,
454 Some(proofs) => proofs.write(w)?,
455 }
456 }
457 }
458 Ok(())
459 }
460
461 pub fn serialize(&self) -> Vec<u8> {
463 let mut res = Vec::with_capacity(2048);
464 self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
465 res
466 }
467
468 pub fn read<R: Read>(r: &mut R) -> io::Result<Self> {
470 let version = read_varint(r)?;
471 let prefix = TransactionPrefix::read(r, version)?;
472
473 if version == 1 {
474 let signatures = if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
475 Default::default()
476 } else {
477 P::RingSignatures::read_signatures(&prefix.inputs, r)?
478 };
479
480 Ok(Transaction::V1 { prefix, signatures })
481 } else if version == 2 {
482 let proofs = P::RctProofs::read(
483 prefix.inputs.first().map_or(0, |input| match input {
484 Input::Gen(_) => 0,
485 Input::ToKey { key_offsets, .. } => key_offsets.len(),
486 }),
487 prefix.inputs.len(),
488 prefix.outputs.len(),
489 r,
490 )?;
491
492 Ok(Transaction::V2 { prefix, proofs })
493 } else {
494 Err(io::Error::other("tried to deserialize unknown version"))
495 }
496 }
497
498 #[allow(clippy::needless_pass_by_value)]
500 fn hash_with_prunable_hash(&self, prunable: PrunableHash<'_>) -> [u8; 32] {
501 match self {
502 Transaction::V1 { prefix, .. } => {
503 let mut buf = Vec::with_capacity(512);
504
505 write_varint(&self.version(), &mut buf)
507 .expect("write failed but <Vec as io::Write> doesn't fail");
508 prefix.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
509
510 let PrunableHash::V1(signatures) = prunable else {
512 panic!("hashing v1 TX with non-v1 prunable data")
513 };
514 for signature in signatures {
515 signature.write(&mut buf).expect("write failed but <Vec as io::Write> doesn't fail");
516 }
517
518 keccak256(buf)
519 }
520 Transaction::V2 { prefix, proofs } => {
521 let mut hashes = Vec::with_capacity(96);
522
523 hashes.extend(prefix.hash(2));
524
525 if let Some(proofs) = proofs {
526 let mut buf = Vec::with_capacity(512);
527 proofs
528 .base()
529 .write(&mut buf, proofs.rct_type())
530 .expect("write failed but <Vec as io::Write> doesn't fail");
531 hashes.extend(keccak256(&buf));
532 } else {
533 hashes.extend(keccak256([0]));
535 }
536 let PrunableHash::V2(prunable_hash) = prunable else {
537 panic!("hashing v2 TX with non-v2 prunable data")
538 };
539 hashes.extend(prunable_hash);
540
541 keccak256(hashes)
542 }
543 }
544 }
545}
546
547impl Transaction<NotPruned> {
548 pub fn hash(&self) -> [u8; 32] {
550 match self {
551 Transaction::V1 { signatures, .. } => {
552 self.hash_with_prunable_hash(PrunableHash::V1(signatures))
553 }
554 Transaction::V2 { proofs, .. } => {
555 self.hash_with_prunable_hash(PrunableHash::V2(if let Some(proofs) = proofs {
556 let mut buf = Vec::with_capacity(1024);
557 proofs
558 .prunable
559 .write(&mut buf, proofs.rct_type())
560 .expect("write failed but <Vec as io::Write> doesn't fail");
561 keccak256(buf)
562 } else {
563 [0; 32]
564 }))
565 }
566 }
567 }
568
569 pub fn signature_hash(&self) -> Option<[u8; 32]> {
573 Some(match self {
574 Transaction::V1 { prefix, .. } => {
575 if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
576 None?;
577 }
578 self.hash_with_prunable_hash(PrunableHash::V1(&[]))
579 }
580 Transaction::V2 { proofs, .. } => self.hash_with_prunable_hash({
581 let Some(proofs) = proofs else { None? };
582 let mut buf = Vec::with_capacity(1024);
583 proofs
584 .prunable
585 .signature_write(&mut buf)
586 .expect("write failed but <Vec as io::Write> doesn't fail");
587 PrunableHash::V2(keccak256(buf))
588 }),
589 })
590 }
591
592 fn is_rct_bulletproof(&self) -> bool {
593 match self {
594 Transaction::V1 { .. } => false,
595 Transaction::V2 { proofs, .. } => {
596 let Some(proofs) = proofs else { return false };
597 proofs.rct_type().bulletproof()
598 }
599 }
600 }
601
602 fn is_rct_bulletproof_plus(&self) -> bool {
603 match self {
604 Transaction::V1 { .. } => false,
605 Transaction::V2 { proofs, .. } => {
606 let Some(proofs) = proofs else { return false };
607 proofs.rct_type().bulletproof_plus()
608 }
609 }
610 }
611
612 pub fn weight(&self) -> usize {
614 let blob_size = self.serialize().len();
615
616 let bp = self.is_rct_bulletproof();
617 let bp_plus = self.is_rct_bulletproof_plus();
618 if !(bp || bp_plus) {
619 blob_size
620 } else {
621 blob_size +
622 Bulletproof::calculate_clawback(
623 bp_plus,
624 match self {
625 Transaction::V1 { .. } => panic!("v1 transaction was BP(+)"),
626 Transaction::V2 { prefix, .. } => prefix.outputs.len(),
627 },
628 )
629 .0
630 }
631 }
632}
633
634impl From<Transaction<NotPruned>> for Transaction<Pruned> {
635 fn from(tx: Transaction<NotPruned>) -> Transaction<Pruned> {
636 match tx {
637 Transaction::V1 { prefix, .. } => Transaction::V1 { prefix, signatures: () },
638 Transaction::V2 { prefix, proofs } => Transaction::V2 {
639 prefix,
640 proofs: proofs
641 .map(|proofs| PrunedRctProofs { rct_type: proofs.rct_type(), base: proofs.base }),
642 },
643 }
644 }
645}