use core::ops::Deref as _;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
#[cfg(feature = "compile-time-generators")]
use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
#[cfg(not(feature = "compile-time-generators"))]
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT as ED25519_BASEPOINT_TABLE;
use crate::{
ed25519::{Scalar, Point},
address::{Network, AddressType, SubaddressIndex, MoneroAddress},
};
#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
pub enum ViewPairError {
#[error("torsioned spend key")]
TorsionedSpendKey,
}
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct ViewPair {
pub(crate) spend: Point,
pub(crate) view: Zeroizing<Scalar>,
}
impl ViewPair {
pub fn new(spend: Point, view: Zeroizing<Scalar>) -> Result<Self, ViewPairError> {
if !spend.into().is_torsion_free() {
Err(ViewPairError::TorsionedSpendKey)?;
}
Ok(ViewPair { spend, view })
}
pub fn spend(&self) -> Point {
self.spend
}
pub fn view(&self) -> Point {
Point::from(Zeroizing::new((*self.view).into()).deref() * ED25519_BASEPOINT_TABLE)
}
pub(crate) fn subaddress_derivation(&self, index: SubaddressIndex) -> Scalar {
Scalar::hash(Zeroizing::new(
[
b"SubAddr\0".as_slice(),
Zeroizing::new(<[u8; 32]>::from(*self.view)).as_slice(),
&index.account().to_le_bytes(),
&index.address().to_le_bytes(),
]
.concat(),
))
}
pub(crate) fn subaddress_keys(&self, index: SubaddressIndex) -> (Point, Point) {
let scalar = self.subaddress_derivation(index);
let spend = self.spend.into() + (&scalar.into() * ED25519_BASEPOINT_TABLE);
let view = Zeroizing::new((*self.view).into()).deref() * spend;
(Point::from(spend), Point::from(view))
}
pub fn legacy_address(&self, network: Network) -> MoneroAddress {
MoneroAddress::new(network, AddressType::Legacy, self.spend, self.view())
}
pub fn legacy_integrated_address(&self, network: Network, payment_id: [u8; 8]) -> MoneroAddress {
MoneroAddress::new(network, AddressType::LegacyIntegrated(payment_id), self.spend, self.view())
}
pub fn subaddress(&self, network: Network, subaddress: SubaddressIndex) -> MoneroAddress {
let (spend, view) = self.subaddress_keys(subaddress);
MoneroAddress::new(network, AddressType::Subaddress, spend, view)
}
}
#[derive(Clone, PartialEq, Eq, Zeroize)] pub struct GuaranteedViewPair(pub(crate) ViewPair);
impl GuaranteedViewPair {
pub fn new(spend: Point, view: Zeroizing<Scalar>) -> Result<Self, ViewPairError> {
ViewPair::new(spend, view).map(GuaranteedViewPair)
}
pub fn spend(&self) -> Point {
self.0.spend()
}
pub fn view(&self) -> Point {
self.0.view()
}
pub fn address(
&self,
network: Network,
subaddress: Option<SubaddressIndex>,
payment_id: Option<[u8; 8]>,
) -> MoneroAddress {
let (spend, view) = if let Some(index) = subaddress {
self.0.subaddress_keys(index)
} else {
(self.spend(), self.view())
};
MoneroAddress::new(
network,
AddressType::Featured { subaddress: subaddress.is_some(), payment_id, guaranteed: true },
spend,
view,
)
}
}