diff --git a/coins/monero/src/wallet/address.rs b/coins/monero/src/wallet/address.rs index 063c7e6f..c51dd73b 100644 --- a/coins/monero/src/wallet/address.rs +++ b/coins/monero/src/wallet/address.rs @@ -80,6 +80,15 @@ impl Zeroize for AddressMeta { } } +/// Specifies Address Properties +#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] +pub enum AddressSpec { + Standard, + Integrated([u8; 8]), + Subaddress(u32, u32), + Featured(Option<(u32, u32)>, Option<[u8; 8]>, bool), +} + /// Error when decoding an address. #[derive(Clone, Copy, PartialEq, Eq, Debug, Error)] pub enum AddressError { diff --git a/coins/monero/src/wallet/mod.rs b/coins/monero/src/wallet/mod.rs index 9037ad3e..9c4ed2b8 100644 --- a/coins/monero/src/wallet/mod.rs +++ b/coins/monero/src/wallet/mod.rs @@ -16,7 +16,7 @@ pub(crate) use extra::{PaymentId, ExtraField, Extra}; /// Address encoding and decoding functionality. pub mod address; -use address::{Network, AddressType, AddressMeta, MoneroAddress}; +use address::{Network, AddressType, AddressMeta, MoneroAddress, AddressSpec}; mod scan; pub use scan::{ReceivedOutput, SpendableOutput}; @@ -29,8 +29,6 @@ pub use send::{Fee, TransactionError, SignableTransaction, SignableTransactionBu #[cfg(feature = "multisig")] pub use send::TransactionMachine; -use self::address::MoneroAddressBytes; - fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> std::cmp::Ordering { x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse() } @@ -108,7 +106,7 @@ impl ViewPair { ViewPair { spend, view } } - pub(crate) fn subaddress(&self, index: (u32, u32)) -> Scalar { + fn subaddress_keys(&self, index: (u32, u32)) -> Scalar { if index == (0, 0) { return Scalar::zero(); } @@ -124,9 +122,45 @@ impl ViewPair { )) } - /// Returns an address with the provided metadata. - pub fn address(&self, meta: AddressMeta) -> MoneroAddress { - MoneroAddress::new(meta, self.spend, self.view.deref() * &ED25519_BASEPOINT_TABLE) + fn subaddress_derivation(&self, index: (u32, u32)) -> (EdwardsPoint, EdwardsPoint) { + let scalar = self.subaddress_keys(index); + let spend = self.spend + (&scalar * &ED25519_BASEPOINT_TABLE); + let view = self.view.deref() * spend; + (spend, view) + } + + /// Returns an address with the provided specification. + pub fn address(&self, network: Network, spec: AddressSpec) -> MoneroAddress { + let mut spend = self.spend; + let mut view: EdwardsPoint = self.view.deref() * &ED25519_BASEPOINT_TABLE; + + // construct the address meta + let meta = match spec { + AddressSpec::Standard => AddressMeta::new(network, AddressType::Standard), + AddressSpec::Integrated(payment_id) => { + AddressMeta::new(network, AddressType::Integrated(payment_id)) + } + AddressSpec::Subaddress(i1, i2) => { + if i1 == 0 && i2 == 0 { + AddressMeta::new(network, AddressType::Standard) + } else { + (spend, view) = self.subaddress_derivation((i1, i2)); + AddressMeta::new(network, AddressType::Subaddress) + } + } + AddressSpec::Featured(subaddress, payment_id, guaranteed) => { + let mut is_subaddress = false; + if let Some(index) = subaddress { + if index != (0, 0) { + (spend, view) = self.subaddress_derivation(index); + is_subaddress = true; + } + } + AddressMeta::new(network, AddressType::Featured(is_subaddress, payment_id, guaranteed)) + } + }; + + MoneroAddress::new(meta, spend, view) } } @@ -187,80 +221,22 @@ impl Scanner { Scanner { pair, network, subaddresses, burning_bug } } - /// Returns the main address for this view pair. - pub fn address(&self) -> MoneroAddress { - let meta = AddressMeta::new( - self.network, - if self.burning_bug.is_none() { - AddressType::Featured(false, None, true) - } else { - AddressType::Standard - }, - ); - self.pair.address(meta) - } - - /// Returns the integrated address for a given payment ID. - pub fn integrated_address(&self, payment_id: [u8; 8]) -> MoneroAddress { - let meta = AddressMeta::new( - self.network, - if self.burning_bug.is_none() { - AddressType::Featured(false, Some(payment_id), true) - } else { - AddressType::Integrated(payment_id) - }, - ); - self.pair.address(meta) - } - - /// Returns the specified subaddress for this view pair. - pub fn subaddress(&mut self, index: (u32, u32)) -> MoneroAddress { - if index == (0, 0) { - return self.address(); - } - - let spend = self.pair.spend + (&self.pair.subaddress(index) * &ED25519_BASEPOINT_TABLE); - self.subaddresses.insert(spend.compress(), index); - - MoneroAddress::new( - AddressMeta::new( - self.network, - if self.burning_bug.is_none() { - AddressType::Featured(true, None, true) - } else { - AddressType::Subaddress - }, - ), - spend, - self.pair.view.deref() * spend, - ) - } - - /// Returns a featured address. + /// Returns the specified address. /// /// A `Scanner` will fail to scan this address unless it is appropriate to the `Scanner`. /// If the `Scanner` was defined as not handling the burning bug, by passing `None` for /// `burning_bug` upon construction, this must be a guaranteed address /// in order to be scanned by it. - pub fn featured_address( - &mut self, - subaddress: Option<(u32, u32)>, - payment_id: Option<[u8; 8]>, - guaranteed: bool, - ) -> MoneroAddress { - let is_subaddress = subaddress.is_some() && (subaddress.unwrap() != (0, 0)); + pub fn address(&mut self, spec: AddressSpec) -> MoneroAddress { + let addr = self.pair.address(self.network, spec); - let mut spend = self.pair.spend; - let mut view: EdwardsPoint = self.pair.view.deref() * &ED25519_BASEPOINT_TABLE; - if is_subaddress { - spend = - self.pair.spend + (&self.pair.subaddress(subaddress.unwrap()) * &ED25519_BASEPOINT_TABLE); - self.subaddresses.insert(spend.compress(), subaddress.unwrap()); - view = self.pair.view.deref() * spend; + let mut index = (0, 0); + if let AddressSpec::Subaddress(i1, i2) = spec { + index = (i1, i2); + } else if let AddressSpec::Featured(Some(index_inner), _, _) = spec { + index = index_inner; } - - let meta = - AddressMeta::new(self.network, AddressType::Featured(is_subaddress, payment_id, guaranteed)); - MoneroAddress::new(meta, spend, view) + self.subaddresses.insert(addr.spend.compress(), index); + addr } } diff --git a/coins/monero/src/wallet/scan.rs b/coins/monero/src/wallet/scan.rs index 2dff5ed5..0abe3ab5 100644 --- a/coins/monero/src/wallet/scan.rs +++ b/coins/monero/src/wallet/scan.rs @@ -293,7 +293,7 @@ impl Scanner { // If we did though, it'd enable bypassing the included burning bug protection debug_assert!(output_key.is_torsion_free()); - let key_offset = shared_key + self.pair.subaddress(subaddress); + let key_offset = shared_key + self.pair.subaddress_keys(subaddress); // Since we've found an output to us, get its amount let mut commitment = Commitment::zero(); diff --git a/coins/monero/tests/runner.rs b/coins/monero/tests/runner.rs index 584b4e27..07faae05 100644 --- a/coins/monero/tests/runner.rs +++ b/coins/monero/tests/runner.rs @@ -14,7 +14,7 @@ use monero_serai::{ Protocol, random_scalar, wallet::{ ViewPair, Scanner, - address::{Network, AddressType, AddressMeta, MoneroAddress}, + address::{Network, AddressType, AddressMeta, MoneroAddress, AddressSpec}, SpendableOutput, }, rpc::Rpc, @@ -64,7 +64,7 @@ pub async fn get_miner_tx_output(rpc: &Rpc, view: &ViewPair) -> SpendableOutput // mine 60 blocks to unlock a miner tx let start = rpc.get_height().await.unwrap(); - rpc.generate_blocks(&scanner.address().to_string(), 60).await.unwrap(); + rpc.generate_blocks(&scanner.address(AddressSpec::Standard).to_string(), 60).await.unwrap(); let block = rpc.get_block(start).await.unwrap(); scanner.scan(rpc, &block).await.unwrap().swap_remove(0).ignore_timelock().swap_remove(0) @@ -151,7 +151,7 @@ macro_rules! test { use monero_serai::{ random_scalar, wallet::{ - address::{Network, AddressMeta, AddressType}, ViewPair, Scanner, SignableTransaction, + address::{Network, AddressSpec}, ViewPair, Scanner, SignableTransaction, SignableTransactionBuilder, }, }; @@ -185,7 +185,7 @@ macro_rules! test { let rpc = rpc().await; let view = ViewPair::new(spend_pub, Zeroizing::new(random_scalar(&mut OsRng))); - let addr = view.address(AddressMeta::new(Network::Mainnet, AddressType::Standard)); + let addr = view.address(Network::Mainnet, AddressSpec::Standard); let miner_tx = get_miner_tx_output(&rpc, &view).await; diff --git a/coins/monero/tests/scan.rs b/coins/monero/tests/scan.rs index d624505f..02a8165c 100644 --- a/coins/monero/tests/scan.rs +++ b/coins/monero/tests/scan.rs @@ -1,6 +1,6 @@ use rand::RngCore; -use monero_serai::{transaction::Transaction}; +use monero_serai::transaction::Transaction; mod runner; @@ -8,9 +8,9 @@ test!( scan_standard_address, ( |_, mut builder: Builder, _| async move { - let scanner = + let mut scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new())); - builder.add_payment(scanner.address(), 5); + builder.add_payment(scanner.address(AddressSpec::Standard), 5); (builder.build().unwrap(), (scanner,)) }, |_, tx: Transaction, _, mut state: (Scanner,)| async move { @@ -27,7 +27,10 @@ test!( let mut scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new())); let subaddress_index = (0, 1); - builder.add_payment(scanner.subaddress(subaddress_index), 5); + builder.add_payment( + scanner.address(AddressSpec::Subaddress(subaddress_index.0, subaddress_index.1)), + 5, + ); (builder.build().unwrap(), (scanner, subaddress_index)) }, |_, tx: Transaction, _, mut state: (Scanner, (u32, u32))| async move { @@ -42,12 +45,12 @@ test!( scan_integrated_address, ( |_, mut builder: Builder, _| async move { - let scanner = + let mut scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new())); let mut payment_id = [0u8; 8]; OsRng.fill_bytes(&mut payment_id); - builder.add_payment(scanner.integrated_address(payment_id), 5); + builder.add_payment(scanner.address(AddressSpec::Integrated(payment_id)), 5); (builder.build().unwrap(), (scanner, payment_id)) }, |_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move { @@ -64,7 +67,7 @@ test!( |_, mut builder: Builder, _| async move { let mut scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new())); - builder.add_payment(scanner.featured_address(None, None, false), 5); + builder.add_payment(scanner.address(AddressSpec::Featured(None, None, false)), 5); (builder.build().unwrap(), (scanner,)) }, |_, tx: Transaction, _, mut state: (Scanner,)| async move { @@ -81,7 +84,10 @@ test!( let mut scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new())); let subaddress_index = (0, 2); - builder.add_payment(scanner.featured_address(Some(subaddress_index), None, false), 5); + builder.add_payment( + scanner.address(AddressSpec::Featured(Some(subaddress_index), None, false)), + 5, + ); (builder.build().unwrap(), (scanner, subaddress_index)) }, |_, tx: Transaction, _, mut state: (Scanner, (u32, u32))| async move { @@ -101,7 +107,7 @@ test!( let mut payment_id = [0u8; 8]; OsRng.fill_bytes(&mut payment_id); - builder.add_payment(scanner.featured_address(None, Some(payment_id), false), 5); + builder.add_payment(scanner.address(AddressSpec::Featured(None, Some(payment_id), false)), 5); (builder.build().unwrap(), (scanner, payment_id)) }, |_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move { @@ -123,8 +129,10 @@ test!( let mut payment_id = [0u8; 8]; OsRng.fill_bytes(&mut payment_id); - builder - .add_payment(scanner.featured_address(Some(subaddress_index), Some(payment_id), false), 5); + builder.add_payment( + scanner.address(AddressSpec::Featured(Some(subaddress_index), Some(payment_id), false)), + 5, + ); (builder.build().unwrap(), (scanner, payment_id, subaddress_index)) }, |_, tx: Transaction, _, mut state: (Scanner, [u8; 8], (u32, u32))| async move { @@ -140,9 +148,9 @@ test!( scan_guaranteed_standard, ( |_, mut builder: Builder, _| async move { - let scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, None); + let mut scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, None); - builder.add_payment(scanner.address(), 5); + builder.add_payment(scanner.address(AddressSpec::Featured(None, None, true)), 5); (builder.build().unwrap(), (scanner,)) }, |_, tx: Transaction, _, mut state: (Scanner,)| async move { @@ -159,7 +167,8 @@ test!( let mut scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, None); let subaddress_index = (0, 1); - builder.add_payment(scanner.subaddress(subaddress_index), 5); + builder + .add_payment(scanner.address(AddressSpec::Featured(Some(subaddress_index), None, true)), 5); (builder.build().unwrap(), (scanner, subaddress_index)) }, |_, tx: Transaction, _, mut state: (Scanner, (u32, u32))| async move { @@ -174,11 +183,11 @@ test!( scan_guaranteed_integrated, ( |_, mut builder: Builder, _| async move { - let scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, None); + let mut scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, None); let mut payment_id = [0u8; 8]; OsRng.fill_bytes(&mut payment_id); - builder.add_payment(scanner.integrated_address(payment_id), 5); + builder.add_payment(scanner.address(AddressSpec::Featured(None, Some(payment_id), true)), 5); (builder.build().unwrap(), (scanner, payment_id)) }, |_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move { @@ -199,8 +208,10 @@ test!( let mut payment_id = [0u8; 8]; OsRng.fill_bytes(&mut payment_id); - builder - .add_payment(scanner.featured_address(Some(subaddress_index), Some(payment_id), true), 5); + builder.add_payment( + scanner.address(AddressSpec::Featured(Some(subaddress_index), Some(payment_id), true)), + 5, + ); (builder.build().unwrap(), (scanner, payment_id, subaddress_index)) }, |_, tx: Transaction, _, mut state: (Scanner, [u8; 8], (u32, u32))| async move { diff --git a/processor/src/coin/monero.rs b/processor/src/coin/monero.rs index b1656194..2e2cb14e 100644 --- a/processor/src/coin/monero.rs +++ b/processor/src/coin/monero.rs @@ -14,7 +14,7 @@ use monero_serai::{ rpc::Rpc, wallet::{ ViewPair, Scanner, - address::{Network, MoneroAddress}, + address::{Network, MoneroAddress, AddressSpec}, Fee, SpendableOutput, SignableTransaction as MSignableTransaction, TransactionMachine, }, }; @@ -91,7 +91,7 @@ impl Monero { #[cfg(test)] fn empty_address() -> MoneroAddress { - Self::empty_scanner().address() + Self::empty_scanner().address(AddressSpec::Standard) } } @@ -121,7 +121,7 @@ impl Coin for Monero { const MAX_OUTPUTS: usize = 16; fn address(&self, key: dfg::EdwardsPoint) -> Self::Address { - self.scanner(key).address() + self.scanner(key).address(AddressSpec::Featured(None, None, true)) } async fn get_latest_block_number(&self) -> Result {