monero_wallet/
view_pair.rs

1use core::ops::Deref;
2
3use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
4
5use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, Scalar, EdwardsPoint};
6
7use crate::{
8  primitives::keccak256_to_scalar,
9  address::{Network, AddressType, SubaddressIndex, MoneroAddress},
10};
11
12/// An error while working with a ViewPair.
13#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
14pub enum ViewPairError {
15  /// The spend key was torsioned.
16  ///
17  /// Torsioned spend keys are of questionable spendability. This library avoids that question by
18  /// rejecting such ViewPairs.
19  // CLSAG seems to support it if the challenge does a torsion clear, FCMP++ should ship with a
20  // torsion clear, yet it's not worth it to modify CLSAG sign to generate challenges until the
21  // torsion clears and ensure spendability (nor can we reasonably guarantee that in the future)
22  #[error("torsioned spend key")]
23  TorsionedSpendKey,
24}
25
26/// The pair of keys necessary to scan transactions.
27///
28/// This is composed of the public spend key and the private view key.
29#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
30pub struct ViewPair {
31  spend: EdwardsPoint,
32  pub(crate) view: Zeroizing<Scalar>,
33}
34
35impl ViewPair {
36  /// Create a new ViewPair.
37  pub fn new(spend: EdwardsPoint, view: Zeroizing<Scalar>) -> Result<Self, ViewPairError> {
38    if !spend.is_torsion_free() {
39      Err(ViewPairError::TorsionedSpendKey)?;
40    }
41    Ok(ViewPair { spend, view })
42  }
43
44  /// The public spend key for this ViewPair.
45  pub fn spend(&self) -> EdwardsPoint {
46    self.spend
47  }
48
49  /// The public view key for this ViewPair.
50  pub fn view(&self) -> EdwardsPoint {
51    self.view.deref() * ED25519_BASEPOINT_TABLE
52  }
53
54  pub(crate) fn subaddress_derivation(&self, index: SubaddressIndex) -> Scalar {
55    keccak256_to_scalar(Zeroizing::new(
56      [
57        b"SubAddr\0".as_ref(),
58        Zeroizing::new(self.view.to_bytes()).as_ref(),
59        &index.account().to_le_bytes(),
60        &index.address().to_le_bytes(),
61      ]
62      .concat(),
63    ))
64  }
65
66  pub(crate) fn subaddress_keys(&self, index: SubaddressIndex) -> (EdwardsPoint, EdwardsPoint) {
67    let scalar = self.subaddress_derivation(index);
68    let spend = self.spend + (&scalar * ED25519_BASEPOINT_TABLE);
69    let view = self.view.deref() * spend;
70    (spend, view)
71  }
72
73  /// Derive a legacy address from this ViewPair.
74  ///
75  /// Subaddresses SHOULD be used instead.
76  pub fn legacy_address(&self, network: Network) -> MoneroAddress {
77    MoneroAddress::new(network, AddressType::Legacy, self.spend, self.view())
78  }
79
80  /// Derive a legacy integrated address from this ViewPair.
81  ///
82  /// Subaddresses SHOULD be used instead.
83  pub fn legacy_integrated_address(&self, network: Network, payment_id: [u8; 8]) -> MoneroAddress {
84    MoneroAddress::new(network, AddressType::LegacyIntegrated(payment_id), self.spend, self.view())
85  }
86
87  /// Derive a subaddress from this ViewPair.
88  pub fn subaddress(&self, network: Network, subaddress: SubaddressIndex) -> MoneroAddress {
89    let (spend, view) = self.subaddress_keys(subaddress);
90    MoneroAddress::new(network, AddressType::Subaddress, spend, view)
91  }
92}
93
94/// The pair of keys necessary to scan outputs immune to the burning bug.
95///
96/// This is composed of the public spend key and a non-zero private view key.
97///
98/// 'Guaranteed' outputs, or transactions outputs to the burning bug, are not officially specified
99/// by the Monero project. They should only be used if necessary. No support outside of
100/// monero-wallet is promised.
101#[derive(Clone, PartialEq, Eq, Zeroize)]
102pub struct GuaranteedViewPair(pub(crate) ViewPair);
103
104impl GuaranteedViewPair {
105  /// Create a new GuaranteedViewPair.
106  pub fn new(spend: EdwardsPoint, view: Zeroizing<Scalar>) -> Result<Self, ViewPairError> {
107    ViewPair::new(spend, view).map(GuaranteedViewPair)
108  }
109
110  /// The public spend key for this GuaranteedViewPair.
111  pub fn spend(&self) -> EdwardsPoint {
112    self.0.spend()
113  }
114
115  /// The public view key for this GuaranteedViewPair.
116  pub fn view(&self) -> EdwardsPoint {
117    self.0.view()
118  }
119
120  /// Returns an address with the provided specification.
121  ///
122  /// The returned address will be a featured address with the guaranteed flag set. These should
123  /// not be presumed to be interoperable with any other software.
124  pub fn address(
125    &self,
126    network: Network,
127    subaddress: Option<SubaddressIndex>,
128    payment_id: Option<[u8; 8]>,
129  ) -> MoneroAddress {
130    let (spend, view) = if let Some(index) = subaddress {
131      self.0.subaddress_keys(index)
132    } else {
133      (self.spend(), self.view())
134    };
135
136    MoneroAddress::new(
137      network,
138      AddressType::Featured { subaddress: subaddress.is_some(), payment_id, guaranteed: true },
139      spend,
140      view,
141    )
142  }
143}