diff --git a/coins/monero/src/tests/address.rs b/coins/monero/src/tests/address.rs index c01c30c8..d697fe67 100644 --- a/coins/monero/src/tests/address.rs +++ b/coins/monero/src/tests/address.rs @@ -6,7 +6,7 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, edwards::CompressedEd use crate::{ random_scalar, - wallet::address::{Network, AddressType, AddressMeta, Address}, + wallet::address::{Network, AddressType, AddressMeta, MoneroAddress}, }; const SPEND: [u8; 32] = hex!("f8631661f6ab4e6fda310c797330d86e23a682f20d5bc8cc27b18051191f16d7"); @@ -30,7 +30,7 @@ const FEATURED_JSON: &'static str = include_str!("vectors/featured_addresses.jso #[test] fn standard_address() { - let addr = Address::from_str(STANDARD, Network::Mainnet).unwrap(); + let addr = MoneroAddress::from_str(Network::Mainnet, STANDARD).unwrap(); assert_eq!(addr.meta.network, Network::Mainnet); assert_eq!(addr.meta.kind, AddressType::Standard); assert_eq!(addr.meta.kind.subaddress(), false); @@ -43,7 +43,7 @@ fn standard_address() { #[test] fn integrated_address() { - let addr = Address::from_str(INTEGRATED, Network::Mainnet).unwrap(); + let addr = MoneroAddress::from_str(Network::Mainnet, INTEGRATED).unwrap(); assert_eq!(addr.meta.network, Network::Mainnet); assert_eq!(addr.meta.kind, AddressType::Integrated(PAYMENT_ID)); assert_eq!(addr.meta.kind.subaddress(), false); @@ -56,7 +56,7 @@ fn integrated_address() { #[test] fn subaddress() { - let addr = Address::from_str(SUBADDRESS, Network::Mainnet).unwrap(); + let addr = MoneroAddress::from_str(Network::Mainnet, SUBADDRESS).unwrap(); assert_eq!(addr.meta.network, Network::Mainnet); assert_eq!(addr.meta.kind, AddressType::Subaddress); assert_eq!(addr.meta.kind.subaddress(), true); @@ -90,11 +90,11 @@ fn featured() { let guaranteed = (features & GUARANTEED_FEATURE_BIT) == GUARANTEED_FEATURE_BIT; let kind = AddressType::Featured(subaddress, id, guaranteed); - let meta = AddressMeta { network, kind }; - let addr = Address::new(meta, spend, view); + let meta = AddressMeta::new(network, kind); + let addr = MoneroAddress::new(meta, spend, view); assert_eq!(addr.to_string().chars().next().unwrap(), first); - assert_eq!(Address::from_str(&addr.to_string(), network).unwrap(), addr); + assert_eq!(MoneroAddress::from_str(network, &addr.to_string()).unwrap(), addr); assert_eq!(addr.spend, spend); assert_eq!(addr.view, view); @@ -146,7 +146,7 @@ fn featured_vectors() { let view = CompressedEdwardsY::from_slice(&hex::decode(vector.view).unwrap()).decompress().unwrap(); - let addr = Address::from_str(&vector.address, network).unwrap(); + let addr = MoneroAddress::from_str(network, &vector.address).unwrap(); assert_eq!(addr.spend, spend); assert_eq!(addr.view, view); @@ -156,11 +156,11 @@ fn featured_vectors() { assert_eq!(addr.guaranteed(), vector.guaranteed); assert_eq!( - Address::new( - AddressMeta { + MoneroAddress::new( + AddressMeta::new( network, - kind: AddressType::Featured(vector.subaddress, vector.payment_id, vector.guaranteed) - }, + AddressType::Featured(vector.subaddress, vector.payment_id, vector.guaranteed) + ), spend, view ) diff --git a/coins/monero/src/wallet/address.rs b/coins/monero/src/wallet/address.rs index c2cb9d55..574fabf4 100644 --- a/coins/monero/src/wallet/address.rs +++ b/coins/monero/src/wallet/address.rs @@ -1,3 +1,4 @@ +use core::{marker::PhantomData, fmt::Debug}; use std::string::ToString; use thiserror::Error; @@ -8,6 +9,7 @@ use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY}; use base58_monero::base58::{encode_check, decode_check}; +/// The network this address is for. #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] pub enum Network { Mainnet, @@ -26,14 +28,6 @@ pub enum AddressType { } impl AddressType { - fn network_bytes(network: Network) -> (u8, u8, u8, u8) { - match network { - Network::Mainnet => (18, 19, 42, 70), - Network::Testnet => (53, 54, 63, 111), - Network::Stagenet => (24, 25, 36, 86), - } - } - pub fn subaddress(&self) -> bool { matches!(self, AddressType::Subaddress) || matches!(self, AddressType::Featured(true, ..)) } @@ -53,12 +47,40 @@ impl AddressType { } } -#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] -pub struct AddressMeta { +/// A type which returns the byte for a given address. +pub trait AddressBytes: Clone + Copy + PartialEq + Eq + Debug { + fn network_bytes(network: Network) -> (u8, u8, u8, u8); +} + +/// Address bytes for Monero. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct MoneroAddressBytes; +impl AddressBytes for MoneroAddressBytes { + fn network_bytes(network: Network) -> (u8, u8, u8, u8) { + match network { + Network::Mainnet => (18, 19, 42, 70), + Network::Testnet => (53, 54, 63, 111), + Network::Stagenet => (24, 25, 36, 86), + } + } +} + +/// Address metadata. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct AddressMeta { + _bytes: PhantomData, pub network: Network, pub kind: AddressType, } +impl Zeroize for AddressMeta { + fn zeroize(&mut self) { + self.network.zeroize(); + self.kind.zeroize(); + } +} + +/// Error when decoding an address. #[derive(Clone, Error, Debug)] pub enum AddressError { #[error("invalid address byte")] @@ -75,10 +97,10 @@ pub enum AddressError { DifferentNetwork, } -impl AddressMeta { +impl AddressMeta { #[allow(clippy::wrong_self_convention)] fn to_byte(&self) -> u8 { - let bytes = AddressType::network_bytes(self.network); + let bytes = B::network_bytes(self.network); match self.kind { AddressType::Standard => bytes.0, AddressType::Integrated(_) => bytes.1, @@ -87,11 +109,16 @@ impl AddressMeta { } } + /// Create an address's metadata. + pub fn new(network: Network, kind: AddressType) -> Self { + AddressMeta { _bytes: PhantomData, network, kind } + } + // Returns an incomplete type in the case of Integrated/Featured addresses - fn from_byte(byte: u8) -> Result { + fn from_byte(byte: u8) -> Result { let mut meta = None; for network in [Network::Mainnet, Network::Testnet, Network::Stagenet] { - let (standard, integrated, subaddress, featured) = AddressType::network_bytes(network); + let (standard, integrated, subaddress, featured) = B::network_bytes(network); if let Some(kind) = match byte { _ if byte == standard => Some(AddressType::Standard), _ if byte == integrated => Some(AddressType::Integrated([0; 8])), @@ -99,7 +126,7 @@ impl AddressMeta { _ if byte == featured => Some(AddressType::Featured(false, None, false)), _ => None, } { - meta = Some(AddressMeta { network, kind }); + meta = Some(AddressMeta::new(network, kind)); break; } } @@ -120,14 +147,23 @@ impl AddressMeta { } } -#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] -pub struct Address { - pub meta: AddressMeta, +/// A Monero address, composed of metadata and a spend/view key. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Address { + pub meta: AddressMeta, pub spend: EdwardsPoint, pub view: EdwardsPoint, } -impl ToString for Address { +impl Zeroize for Address { + fn zeroize(&mut self) { + self.meta.zeroize(); + self.spend.zeroize(); + self.view.zeroize(); + } +} + +impl ToString for Address { fn to_string(&self) -> String { let mut data = vec![self.meta.to_byte()]; data.extend(self.spend.compress().to_bytes()); @@ -145,12 +181,12 @@ impl ToString for Address { } } -impl Address { - pub fn new(meta: AddressMeta, spend: EdwardsPoint, view: EdwardsPoint) -> Address { +impl Address { + pub fn new(meta: AddressMeta, spend: EdwardsPoint, view: EdwardsPoint) -> Self { Address { meta, spend, view } } - pub fn from_str_raw(s: &str) -> Result { + pub fn from_str_raw(s: &str) -> Result { let raw = decode_check(s).map_err(|_| AddressError::InvalidEncoding)?; if raw.len() < (1 + 32 + 32) { Err(AddressError::InvalidLength)?; @@ -197,7 +233,7 @@ impl Address { Ok(Address { meta, spend, view }) } - pub fn from_str(s: &str, network: Network) -> Result { + pub fn from_str(network: Network, s: &str) -> Result { Self::from_str_raw(s).and_then(|addr| { if addr.meta.network == network { Ok(addr) @@ -223,3 +259,17 @@ impl Address { self.meta.guaranteed() } } + +/// Instantiation of the Address type with Monero's network bytes. +pub type MoneroAddress = Address; +// Allow re-interpreting of an arbitrary address as a monero address so it can be used with the +// rest of this library. Doesn't use From as it was conflicting with From for T. +impl MoneroAddress { + pub fn from(address: Address) -> MoneroAddress { + MoneroAddress::new( + AddressMeta::new(address.meta.network, address.meta.kind), + address.spend, + address.view, + ) + } +} diff --git a/coins/monero/src/wallet/mod.rs b/coins/monero/src/wallet/mod.rs index af3bc807..6a17588d 100644 --- a/coins/monero/src/wallet/mod.rs +++ b/coins/monero/src/wallet/mod.rs @@ -15,7 +15,7 @@ pub(crate) use extra::{PaymentId, ExtraField, Extra}; /// Address encoding and decoding functionality. pub mod address; -use address::{Network, AddressType, AddressMeta, Address}; +use address::{Network, AddressType, AddressMeta, MoneroAddress}; mod scan; pub use scan::SpendableOutput; @@ -180,23 +180,23 @@ impl Scanner { } /// Return the main address for this view pair. - pub fn address(&self) -> Address { - Address::new( - AddressMeta { - network: self.network, - kind: if self.burning_bug.is_none() { + pub fn address(&self) -> MoneroAddress { + MoneroAddress::new( + AddressMeta::new( + self.network, + if self.burning_bug.is_none() { AddressType::Featured(false, None, true) } else { AddressType::Standard }, - }, + ), self.pair.spend, &self.pair.view * &ED25519_BASEPOINT_TABLE, ) } /// Return the specified subaddress for this view pair. - pub fn subaddress(&mut self, index: (u32, u32)) -> Address { + pub fn subaddress(&mut self, index: (u32, u32)) -> MoneroAddress { if index == (0, 0) { return self.address(); } @@ -204,15 +204,15 @@ impl Scanner { let spend = self.pair.spend + (&self.pair.subaddress(index) * &ED25519_BASEPOINT_TABLE); self.subaddresses.insert(spend.compress(), index); - Address::new( - AddressMeta { - network: self.network, - kind: if self.burning_bug.is_none() { + MoneroAddress::new( + AddressMeta::new( + self.network, + if self.burning_bug.is_none() { AddressType::Featured(true, None, true) } else { AddressType::Subaddress }, - }, + ), spend, self.pair.view * spend, ) diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index 9be83b9a..903d8c92 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -23,7 +23,7 @@ use crate::{ transaction::{Input, Output, Timelock, TransactionPrefix, Transaction}, rpc::{Rpc, RpcError}, wallet::{ - address::Address, SpendableOutput, Decoys, PaymentId, ExtraField, Extra, key_image_sort, + address::MoneroAddress, SpendableOutput, Decoys, PaymentId, ExtraField, Extra, key_image_sort, uniqueness, shared_key, commitment_mask, amount_encryption, }, }; @@ -47,7 +47,7 @@ impl SendOutput { fn new( rng: &mut R, unique: [u8; 32], - output: (usize, (Address, u64)), + output: (usize, (MoneroAddress, u64)), ) -> (SendOutput, Option<[u8; 8]>) { let o = output.0; let output = output.1; @@ -173,7 +173,7 @@ impl Fee { pub struct SignableTransaction { protocol: Protocol, inputs: Vec, - payments: Vec<(Address, u64)>, + payments: Vec<(MoneroAddress, u64)>, data: Option>, fee: u64, } @@ -186,15 +186,15 @@ impl SignableTransaction { pub fn new( protocol: Protocol, inputs: Vec, - mut payments: Vec<(Address, u64)>, - change_address: Option
, + mut payments: Vec<(MoneroAddress, u64)>, + change_address: Option, data: Option>, fee_rate: Fee, ) -> Result { // Make sure there's only one payment ID { let mut payment_ids = 0; - let mut count = |addr: Address| { + let mut count = |addr: MoneroAddress| { if addr.payment_id().is_some() { payment_ids += 1 } diff --git a/coins/monero/tests/rpc.rs b/coins/monero/tests/rpc.rs index bed1e337..e026f832 100644 --- a/coins/monero/tests/rpc.rs +++ b/coins/monero/tests/rpc.rs @@ -6,7 +6,7 @@ use serde_json::json; use monero_serai::{ Protocol, random_scalar, - wallet::address::{Network, AddressType, AddressMeta, Address}, + wallet::address::{Network, AddressType, AddressMeta, MoneroAddress}, rpc::{EmptyResponse, RpcError, Rpc}, }; @@ -18,8 +18,8 @@ pub async fn rpc() -> Rpc { return rpc; } - let addr = Address { - meta: AddressMeta { network: Network::Mainnet, kind: AddressType::Standard }, + let addr = MoneroAddress { + meta: AddressMeta::new(Network::Mainnet, AddressType::Standard), spend: &random_scalar(&mut OsRng) * &ED25519_BASEPOINT_TABLE, view: &random_scalar(&mut OsRng) * &ED25519_BASEPOINT_TABLE, } diff --git a/processor/src/coin/monero.rs b/processor/src/coin/monero.rs index fbca66ae..f0836f7e 100644 --- a/processor/src/coin/monero.rs +++ b/processor/src/coin/monero.rs @@ -12,7 +12,7 @@ use monero_serai::{ rpc::Rpc, wallet::{ ViewPair, Scanner, - address::{Network, Address}, + address::{Network, MoneroAddress}, Fee, SpendableOutput, SignableTransaction as MSignableTransaction, TransactionMachine, }, }; @@ -88,7 +88,7 @@ impl Monero { } #[cfg(test)] - fn empty_address() -> Address { + fn empty_address() -> MoneroAddress { Self::empty_scanner().address() } } @@ -105,7 +105,7 @@ impl Coin for Monero { type SignableTransaction = SignableTransaction; type TransactionMachine = TransactionMachine; - type Address = Address; + type Address = MoneroAddress; const ID: &'static [u8] = b"Monero"; const CONFIRMATIONS: usize = 10; @@ -161,7 +161,7 @@ impl Coin for Monero { transcript: RecommendedTranscript, block_number: usize, mut inputs: Vec, - payments: &[(Address, u64)], + payments: &[(MoneroAddress, u64)], fee: Fee, ) -> Result { let spend = keys.group_key();