monero_wallet/
view_pair.rs1use core::ops::Deref as _;
2
3use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
4
5#[cfg(feature = "compile-time-generators")]
6use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
7#[cfg(not(feature = "compile-time-generators"))]
8use curve25519_dalek::constants::ED25519_BASEPOINT_POINT as ED25519_BASEPOINT_TABLE;
9
10use crate::{
11 ed25519::{Scalar, Point},
12 address::{Network, AddressType, SubaddressIndex, MoneroAddress},
13};
14
15#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
17pub enum ViewPairError {
18 #[error("torsioned spend key")]
26 TorsionedSpendKey,
27}
28
29#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
33pub struct ViewPair {
34 pub(crate) spend: Point,
35 pub(crate) view: Zeroizing<Scalar>,
36}
37
38impl ViewPair {
39 pub fn new(spend: Point, view: Zeroizing<Scalar>) -> Result<Self, ViewPairError> {
43 if !spend.into().is_torsion_free() {
44 Err(ViewPairError::TorsionedSpendKey)?;
45 }
46 Ok(ViewPair { spend, view })
47 }
48
49 pub fn spend(&self) -> Point {
51 self.spend
52 }
53
54 pub fn view(&self) -> Point {
56 Point::from(Zeroizing::new((*self.view).into()).deref() * ED25519_BASEPOINT_TABLE)
57 }
58
59 pub(crate) fn subaddress_derivation(&self, index: SubaddressIndex) -> Scalar {
60 Scalar::hash(Zeroizing::new(
61 [
62 b"SubAddr\0".as_slice(),
63 Zeroizing::new(<[u8; 32]>::from(*self.view)).as_slice(),
64 &index.account().to_le_bytes(),
65 &index.address().to_le_bytes(),
66 ]
67 .concat(),
68 ))
69 }
70
71 pub(crate) fn subaddress_keys(&self, index: SubaddressIndex) -> (Point, Point) {
72 let scalar = self.subaddress_derivation(index);
73 let spend = self.spend.into() + (&scalar.into() * ED25519_BASEPOINT_TABLE);
74 let view = Zeroizing::new((*self.view).into()).deref() * spend;
75 (Point::from(spend), Point::from(view))
76 }
77
78 pub fn legacy_address(&self, network: Network) -> MoneroAddress {
82 MoneroAddress::new(network, AddressType::Legacy, self.spend, self.view())
83 }
84
85 pub fn legacy_integrated_address(&self, network: Network, payment_id: [u8; 8]) -> MoneroAddress {
89 MoneroAddress::new(network, AddressType::LegacyIntegrated(payment_id), self.spend, self.view())
90 }
91
92 pub fn subaddress(&self, network: Network, subaddress: SubaddressIndex) -> MoneroAddress {
94 let (spend, view) = self.subaddress_keys(subaddress);
95 MoneroAddress::new(network, AddressType::Subaddress, spend, view)
96 }
97}
98
99#[derive(Clone, PartialEq, Eq, Zeroize)] pub struct GuaranteedViewPair(pub(crate) ViewPair);
108
109impl GuaranteedViewPair {
110 pub fn new(spend: Point, view: Zeroizing<Scalar>) -> Result<Self, ViewPairError> {
112 ViewPair::new(spend, view).map(GuaranteedViewPair)
113 }
114
115 pub fn spend(&self) -> Point {
117 self.0.spend()
118 }
119
120 pub fn view(&self) -> Point {
122 self.0.view()
123 }
124
125 pub fn address(
130 &self,
131 network: Network,
132 subaddress: Option<SubaddressIndex>,
133 payment_id: Option<[u8; 8]>,
134 ) -> MoneroAddress {
135 let (spend, view) = if let Some(index) = subaddress {
136 self.0.subaddress_keys(index)
137 } else {
138 (self.spend(), self.view())
139 };
140
141 MoneroAddress::new(
142 network,
143 AddressType::Featured { subaddress: subaddress.is_some(), payment_id, guaranteed: true },
144 spend,
145 view,
146 )
147 }
148}