monero_interface/
provides_decoys.rs

1use core::{ops::RangeBounds, future::Future};
2use alloc::{format, vec::Vec, string::ToString};
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 performed other than confirming the correct amount is
39  /// returned.
40  fn unlocked_ringct_outputs(
41    &self,
42    indexes: &[u64],
43    evaluate_unlocked: EvaluateUnlocked,
44  ) -> impl Send + Future<Output = Result<Vec<Option<[Point; 2]>>, TransactionsError>>;
45}
46
47/// Provides the necessary data to select decoys.
48///
49/// This SHOULD be satisfied by a local store to prevent attack by malicious remote nodes.
50pub trait ProvidesDecoys: ProvidesBlockchainMeta {
51  /// Get the distribution of RingCT outputs.
52  ///
53  /// `range` is in terms of block numbers. The result may be smaller than the requested range if
54  /// the range starts before RingCT outputs were created on-chain.
55  ///
56  /// The distribution is checked to monotonically increase.
57  fn ringct_output_distribution(
58    &self,
59    range: impl Send + RangeBounds<usize>,
60  ) -> impl Send + Future<Output = Result<Vec<u64>, InterfaceError>>;
61
62  /// Get the specified RingCT outputs, but only return them if they're unlocked.
63  fn unlocked_ringct_outputs(
64    &self,
65    indexes: &[u64],
66    evaluate_unlocked: EvaluateUnlocked,
67  ) -> impl Send + Future<Output = Result<Vec<Option<[Point; 2]>>, TransactionsError>>;
68}
69
70impl<P: ProvidesUnvalidatedDecoys> ProvidesDecoys for P {
71  fn ringct_output_distribution(
72    &self,
73    range: impl Send + RangeBounds<usize>,
74  ) -> impl Send + Future<Output = Result<Vec<u64>, InterfaceError>> {
75    async move {
76      let distribution =
77        <P as ProvidesUnvalidatedDecoys>::ringct_output_distribution(self, range).await?;
78
79      let mut monotonic = 0;
80      for d in &distribution {
81        if *d < monotonic {
82          Err(InterfaceError::InvalidInterface(
83            "received output distribution didn't increase monotonically".to_string(),
84          ))?;
85        }
86        monotonic = *d;
87      }
88
89      Ok(distribution)
90    }
91  }
92
93  fn unlocked_ringct_outputs(
94    &self,
95    indexes: &[u64],
96    evaluate_unlocked: EvaluateUnlocked,
97  ) -> impl Send + Future<Output = Result<Vec<Option<[Point; 2]>>, TransactionsError>> {
98    async move {
99      let outputs =
100        <P as ProvidesUnvalidatedDecoys>::unlocked_ringct_outputs(self, indexes, evaluate_unlocked)
101          .await?;
102      if outputs.len() != indexes.len() {
103        Err(InterfaceError::InternalError(format!(
104          "`{}` returned {} outputs, expected {}",
105          "ProvidesUnvalidatedDecoys::unlocked_ringct_outputs",
106          outputs.len(),
107          indexes.len(),
108        )))?;
109      }
110      Ok(outputs)
111    }
112  }
113}