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::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: CompressedEdwardsY,
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 w.write_all(&key_image.0)
49 }
50 }
51 }
52
53 pub fn serialize(&self) -> Vec<u8> {
55 let mut res = vec![];
56 self.write(&mut res).unwrap();
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, r)?,
75 key_image: CompressedEdwardsY(read_bytes(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).unwrap();
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).unwrap();
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), 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), r)?,
254 extra: vec![],
255 };
256 prefix.extra = read_vec(read_byte, 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).unwrap();
263 self.write(&mut buf).unwrap();
264 keccak256(buf)
265 }
266}
267
268mod sealed {
269 use core::fmt::Debug;
270 use crate::ringct::*;
271 use super::*;
272
273 pub(crate) trait RingSignatures: Clone + PartialEq + Eq + Default + Debug {
274 fn signatures_to_write(&self) -> &[RingSignature];
275 fn read_signatures(inputs: &[Input], r: &mut impl Read) -> io::Result<Self>;
276 }
277
278 impl RingSignatures for Vec<RingSignature> {
279 fn signatures_to_write(&self) -> &[RingSignature] {
280 self
281 }
282 fn read_signatures(inputs: &[Input], r: &mut impl Read) -> io::Result<Self> {
283 let mut signatures = Vec::with_capacity(inputs.len());
284 for input in inputs {
285 match input {
286 Input::ToKey { key_offsets, .. } => {
287 signatures.push(RingSignature::read(key_offsets.len(), r)?)
288 }
289 _ => Err(io::Error::other("reading signatures for a transaction with non-ToKey inputs"))?,
290 }
291 }
292 Ok(signatures)
293 }
294 }
295
296 impl RingSignatures for () {
297 fn signatures_to_write(&self) -> &[RingSignature] {
298 &[]
299 }
300 fn read_signatures(_: &[Input], _: &mut impl Read) -> io::Result<Self> {
301 Ok(())
302 }
303 }
304
305 pub(crate) trait RctProofsTrait: Clone + PartialEq + Eq + Debug {
306 fn write(&self, w: &mut impl Write) -> io::Result<()>;
307 fn read(
308 ring_length: usize,
309 inputs: usize,
310 outputs: usize,
311 r: &mut impl Read,
312 ) -> io::Result<Option<Self>>;
313 fn rct_type(&self) -> RctType;
314 fn base(&self) -> &RctBase;
315 }
316
317 impl RctProofsTrait for RctProofs {
318 fn write(&self, w: &mut impl Write) -> io::Result<()> {
319 self.write(w)
320 }
321 fn read(
322 ring_length: usize,
323 inputs: usize,
324 outputs: usize,
325 r: &mut impl Read,
326 ) -> io::Result<Option<Self>> {
327 RctProofs::read(ring_length, inputs, outputs, r)
328 }
329 fn rct_type(&self) -> RctType {
330 self.rct_type()
331 }
332 fn base(&self) -> &RctBase {
333 &self.base
334 }
335 }
336
337 impl RctProofsTrait for PrunedRctProofs {
338 fn write(&self, w: &mut impl Write) -> io::Result<()> {
339 self.base.write(w, self.rct_type)
340 }
341 fn read(
342 _ring_length: usize,
343 inputs: usize,
344 outputs: usize,
345 r: &mut impl Read,
346 ) -> io::Result<Option<Self>> {
347 Ok(RctBase::read(inputs, outputs, r)?.map(|(rct_type, base)| Self { rct_type, base }))
348 }
349 fn rct_type(&self) -> RctType {
350 self.rct_type
351 }
352 fn base(&self) -> &RctBase {
353 &self.base
354 }
355 }
356
357 pub(crate) trait PotentiallyPruned {
358 type RingSignatures: RingSignatures;
359 type RctProofs: RctProofsTrait;
360 }
361 #[derive(Clone, PartialEq, Eq, Debug)]
363 pub struct NotPruned;
364 impl PotentiallyPruned for NotPruned {
365 type RingSignatures = Vec<RingSignature>;
366 type RctProofs = RctProofs;
367 }
368 #[derive(Clone, PartialEq, Eq, Debug)]
370 pub struct Pruned;
371 impl PotentiallyPruned for Pruned {
372 type RingSignatures = ();
373 type RctProofs = PrunedRctProofs;
374 }
375}
376pub use sealed::*;
377
378#[allow(private_bounds, private_interfaces, clippy::large_enum_variant)]
380#[derive(Clone, PartialEq, Eq, Debug)]
381pub enum Transaction<P: PotentiallyPruned = NotPruned> {
382 V1 {
384 prefix: TransactionPrefix,
386 signatures: P::RingSignatures,
388 },
389 V2 {
391 prefix: TransactionPrefix,
393 proofs: Option<P::RctProofs>,
395 },
396}
397
398enum PrunableHash<'a> {
399 V1(&'a [RingSignature]),
400 V2([u8; 32]),
401}
402
403#[allow(private_bounds)]
404impl<P: PotentiallyPruned> Transaction<P> {
405 pub fn version(&self) -> u8 {
407 match self {
408 Transaction::V1 { .. } => 1,
409 Transaction::V2 { .. } => 2,
410 }
411 }
412
413 pub fn prefix(&self) -> &TransactionPrefix {
415 match self {
416 Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => prefix,
417 }
418 }
419
420 pub fn prefix_mut(&mut self) -> &mut TransactionPrefix {
422 match self {
423 Transaction::V1 { prefix, .. } | Transaction::V2 { prefix, .. } => prefix,
424 }
425 }
426
427 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
432 write_varint(&self.version(), w)?;
433 match self {
434 Transaction::V1 { prefix, signatures } => {
435 prefix.write(w)?;
436 for ring_sig in signatures.signatures_to_write() {
437 ring_sig.write(w)?;
438 }
439 }
440 Transaction::V2 { prefix, proofs } => {
441 prefix.write(w)?;
442 match proofs {
443 None => w.write_all(&[0])?,
444 Some(proofs) => proofs.write(w)?,
445 }
446 }
447 }
448 Ok(())
449 }
450
451 pub fn serialize(&self) -> Vec<u8> {
453 let mut res = Vec::with_capacity(2048);
454 self.write(&mut res).unwrap();
455 res
456 }
457
458 pub fn read<R: Read>(r: &mut R) -> io::Result<Self> {
460 let version = read_varint(r)?;
461 let prefix = TransactionPrefix::read(r, version)?;
462
463 if version == 1 {
464 let signatures = if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
465 Default::default()
466 } else {
467 P::RingSignatures::read_signatures(&prefix.inputs, r)?
468 };
469
470 Ok(Transaction::V1 { prefix, signatures })
471 } else if version == 2 {
472 let proofs = P::RctProofs::read(
473 prefix.inputs.first().map_or(0, |input| match input {
474 Input::Gen(_) => 0,
475 Input::ToKey { key_offsets, .. } => key_offsets.len(),
476 }),
477 prefix.inputs.len(),
478 prefix.outputs.len(),
479 r,
480 )?;
481
482 Ok(Transaction::V2 { prefix, proofs })
483 } else {
484 Err(io::Error::other("tried to deserialize unknown version"))
485 }
486 }
487
488 #[allow(clippy::needless_pass_by_value)]
490 fn hash_with_prunable_hash(&self, prunable: PrunableHash<'_>) -> [u8; 32] {
491 match self {
492 Transaction::V1 { prefix, .. } => {
493 let mut buf = Vec::with_capacity(512);
494
495 write_varint(&self.version(), &mut buf).unwrap();
497 prefix.write(&mut buf).unwrap();
498
499 let PrunableHash::V1(signatures) = prunable else {
501 panic!("hashing v1 TX with non-v1 prunable data")
502 };
503 for signature in signatures {
504 signature.write(&mut buf).unwrap();
505 }
506
507 keccak256(buf)
508 }
509 Transaction::V2 { prefix, proofs } => {
510 let mut hashes = Vec::with_capacity(96);
511
512 hashes.extend(prefix.hash(2));
513
514 if let Some(proofs) = proofs {
515 let mut buf = Vec::with_capacity(512);
516 proofs.base().write(&mut buf, proofs.rct_type()).unwrap();
517 hashes.extend(keccak256(&buf));
518 } else {
519 hashes.extend(keccak256([0]));
521 }
522 let PrunableHash::V2(prunable_hash) = prunable else {
523 panic!("hashing v2 TX with non-v2 prunable data")
524 };
525 hashes.extend(prunable_hash);
526
527 keccak256(hashes)
528 }
529 }
530 }
531}
532
533impl Transaction<NotPruned> {
534 pub fn hash(&self) -> [u8; 32] {
536 match self {
537 Transaction::V1 { signatures, .. } => {
538 self.hash_with_prunable_hash(PrunableHash::V1(signatures))
539 }
540 Transaction::V2 { proofs, .. } => {
541 self.hash_with_prunable_hash(PrunableHash::V2(if let Some(proofs) = proofs {
542 let mut buf = Vec::with_capacity(1024);
543 proofs.prunable.write(&mut buf, proofs.rct_type()).unwrap();
544 keccak256(buf)
545 } else {
546 [0; 32]
547 }))
548 }
549 }
550 }
551
552 pub fn signature_hash(&self) -> Option<[u8; 32]> {
556 Some(match self {
557 Transaction::V1 { prefix, .. } => {
558 if (prefix.inputs.len() == 1) && matches!(prefix.inputs[0], Input::Gen(_)) {
559 None?;
560 }
561 self.hash_with_prunable_hash(PrunableHash::V1(&[]))
562 }
563 Transaction::V2 { proofs, .. } => self.hash_with_prunable_hash({
564 let Some(proofs) = proofs else { None? };
565 let mut buf = Vec::with_capacity(1024);
566 proofs.prunable.signature_write(&mut buf).unwrap();
567 PrunableHash::V2(keccak256(buf))
568 }),
569 })
570 }
571
572 fn is_rct_bulletproof(&self) -> bool {
573 match self {
574 Transaction::V1 { .. } => false,
575 Transaction::V2 { proofs, .. } => {
576 let Some(proofs) = proofs else { return false };
577 proofs.rct_type().bulletproof()
578 }
579 }
580 }
581
582 fn is_rct_bulletproof_plus(&self) -> bool {
583 match self {
584 Transaction::V1 { .. } => false,
585 Transaction::V2 { proofs, .. } => {
586 let Some(proofs) = proofs else { return false };
587 proofs.rct_type().bulletproof_plus()
588 }
589 }
590 }
591
592 pub fn weight(&self) -> usize {
594 let blob_size = self.serialize().len();
595
596 let bp = self.is_rct_bulletproof();
597 let bp_plus = self.is_rct_bulletproof_plus();
598 if !(bp || bp_plus) {
599 blob_size
600 } else {
601 blob_size +
602 Bulletproof::calculate_bp_clawback(
603 bp_plus,
604 match self {
605 Transaction::V1 { .. } => panic!("v1 transaction was BP(+)"),
606 Transaction::V2 { prefix, .. } => prefix.outputs.len(),
607 },
608 )
609 .0
610 }
611 }
612}
613
614impl From<Transaction<NotPruned>> for Transaction<Pruned> {
615 fn from(tx: Transaction<NotPruned>) -> Transaction<Pruned> {
616 match tx {
617 Transaction::V1 { prefix, .. } => Transaction::V1 { prefix, signatures: () },
618 Transaction::V2 { prefix, proofs } => Transaction::V2 {
619 prefix,
620 proofs: proofs
621 .map(|proofs| PrunedRctProofs { rct_type: proofs.rct_type(), base: proofs.base }),
622 },
623 }
624 }
625}