Skip to main content

monero_interface/
provides_decoys.rs

1use core::{ops::RangeBounds, future::Future};
2use alloc::{borrow::ToOwned as _, format, vec::Vec};
3
4use monero_oxide::ed25519::Point;
5
6use crate::{InterfaceError, TransactionsError, ProvidesBlockchainMeta};
7
8/// How to evaluate if an output is unlocked.
9pub enum EvaluateUnlocked {
10  /// The normal method of evaluation.
11  Normal,
12  /// A deterministic method which only considers the view of the blockchain as of block
13  /// #`block_number`. This is fingerprintable as outputs locked with a time-based timelock will
14  /// always be considered locked and never be selected as decoys.
15  FingerprintableDeterministic {
16    /// The number of the block to premise the view upon.
17    block_number: usize,
18  },
19}
20
21/// Provides the necessary data to select decoys, without validating it.
22///
23/// This SHOULD be satisfied by a local store to prevent attack by malicious remote nodes.
24pub trait ProvidesUnvalidatedDecoys: ProvidesBlockchainMeta {
25  /// Get the distribution of RingCT outputs.
26  ///
27  /// `range` is in terms of block numbers. The result may be smaller than the requested range if
28  /// the range starts before RingCT outputs were created on-chain.
29  ///
30  /// No validation of the distribution is performed.
31  fn ringct_output_distribution(
32    &self,
33    range: impl Send + RangeBounds<usize>,
34  ) -> impl Send + Future<Output = Result<Vec<u64>, InterfaceError>>;
35
36  /// Get the specified RingCT outputs, but only return them if they're unlocked.
37  ///
38  /// No validation of the outputs is guaranteed to be performed.
39  fn unlocked_ringct_outputs(
40    &self,
41    indexes: &[u64],
42    evaluate_unlocked: EvaluateUnlocked,
43  ) -> impl Send + Future<Output = Result<Vec<Option<[Point; 2]>>, TransactionsError>>;
44}
45
46/// Provides the necessary data to select decoys.
47///
48/// This SHOULD be satisfied by a local store to prevent attack by malicious remote nodes.
49pub trait ProvidesDecoys: ProvidesBlockchainMeta {
50  /// Get the distribution of RingCT outputs.
51  ///
52  /// `range` is in terms of block numbers. The result may be smaller than the requested range if
53  /// the range starts before RingCT outputs were created on-chain.
54  ///
55  /// The distribution is checked to monotonically increase.
56  fn ringct_output_distribution(
57    &self,
58    range: impl Send + RangeBounds<usize>,
59  ) -> impl Send + Future<Output = Result<Vec<u64>, InterfaceError>>;
60
61  /// Get the specified RingCT outputs, but only return them if they're unlocked.
62  ///
63  /// No validation of the outputs is guaranteed to be performed other than confirming the correct
64  /// amount is returned.
65  fn unlocked_ringct_outputs(
66    &self,
67    indexes: &[u64],
68    evaluate_unlocked: EvaluateUnlocked,
69  ) -> impl Send + Future<Output = Result<Vec<Option<[Point; 2]>>, TransactionsError>>;
70}
71
72impl<P: ProvidesUnvalidatedDecoys> ProvidesDecoys for P {
73  fn ringct_output_distribution(
74    &self,
75    range: impl Send + RangeBounds<usize>,
76  ) -> impl Send + Future<Output = Result<Vec<u64>, InterfaceError>> {
77    async move {
78      let distribution =
79        <P as ProvidesUnvalidatedDecoys>::ringct_output_distribution(self, range).await?;
80
81      let mut monotonic = 0;
82      for d in &distribution {
83        if *d < monotonic {
84          Err(InterfaceError::InvalidInterface(
85            "received output distribution didn't increase monotonically".to_owned(),
86          ))?;
87        }
88        monotonic = *d;
89      }
90
91      Ok(distribution)
92    }
93  }
94
95  fn unlocked_ringct_outputs(
96    &self,
97    indexes: &[u64],
98    evaluate_unlocked: EvaluateUnlocked,
99  ) -> impl Send + Future<Output = Result<Vec<Option<[Point; 2]>>, TransactionsError>> {
100    async move {
101      let outputs =
102        <P as ProvidesUnvalidatedDecoys>::unlocked_ringct_outputs(self, indexes, evaluate_unlocked)
103          .await?;
104      if outputs.len() != indexes.len() {
105        Err(InterfaceError::InternalError(format!(
106          "`{}` returned {} outputs, expected {}",
107          "ProvidesUnvalidatedDecoys::unlocked_ringct_outputs",
108          outputs.len(),
109          indexes.len(),
110        )))?;
111      }
112      Ok(outputs)
113    }
114  }
115}