diff --git a/coins/monero/src/wallet/address.rs b/coins/monero/src/wallet/address.rs index 063c7e6f..d4ec589c 100644 --- a/coins/monero/src/wallet/address.rs +++ b/coins/monero/src/wallet/address.rs @@ -182,6 +182,14 @@ impl ToString for Address { } impl Address { + /// Generates an Address type according to the specification provided in the meta. + /// + /// WARNING: Specification on the meta are not "commands" and are + /// just "specifications" for the address. This function relies on the + /// caller to make sure all the passed parameters makes sense and assumes + /// specifications provided in the meta are correct and applicable for the passed in keys. + /// For example, passing `AddressType::Subaddress` type in the meta wont generate + /// a correct subaddress if the keys aren't already for a valid subaddress. pub fn new(meta: AddressMeta, spend: EdwardsPoint, view: EdwardsPoint) -> Self { Address { meta, spend, view } } diff --git a/coins/monero/src/wallet/mod.rs b/coins/monero/src/wallet/mod.rs index 5b776d2d..4ad0de2b 100644 --- a/coins/monero/src/wallet/mod.rs +++ b/coins/monero/src/wallet/mod.rs @@ -29,6 +29,8 @@ 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() } @@ -121,12 +123,19 @@ impl ViewPair { .concat(), )) } + + /// returns the MoneroAddress type according to specifications in the meta + pub fn address(&self, meta: AddressMeta) -> MoneroAddress { + MoneroAddress::new(meta, self.spend, self.view.deref() * &ED25519_BASEPOINT_TABLE) + } } /// Transaction scanner. -/// This scanner is capable of generating subaddresses, additionally scanning for them once they've -/// been explicitly generated. If the burning bug is attempted, any secondary outputs will be -/// ignored. +/// - This scanner is capable of generating subaddresses, additionally scanning for +/// them once they've been explicitly generated by using `subaddress()` member function. +/// It wont be able to track the outputs for subaddresses that aren't generated this way. +/// - If the burning bug is attempted, any secondary outputs will be ignored. +/// or standard address type if none passed. #[derive(Clone)] pub struct Scanner { pair: ViewPair, @@ -167,8 +176,13 @@ impl Scanner { /// burning_bug is a HashSet of used keys, intended to prevent key reuse which would burn funds. /// When an output is successfully scanned, the output key MUST be saved to disk. /// When a new scanner is created, ALL saved output keys must be passed in to be secure. - /// If None is passed, a modified shared key derivation is used which is immune to the burning - /// bug (specifically the Guaranteed feature from Featured Addresses). + /// If None is passed, a modified shared key derivation is used to generate new addresses + /// through this scanner which is immune to the burning bug (specifically + /// the Guaranteed feature from Featured Addresses). + /// + /// If you want to use guaranteed feature for both generating addresses and + /// decoding outputs with such addresses pass `None` to the `burning_bug`. if you + /// just wanna support standard monero addresses then pass `Some`. // TODO: Should this take in a DB access handle to ensure output keys are saved? pub fn from_view( pair: ViewPair, @@ -180,23 +194,33 @@ impl Scanner { Scanner { pair, network, subaddresses, burning_bug } } - /// Return the main address for this view pair. + /// Returns the main address for this view pair. 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.deref() * &ED25519_BASEPOINT_TABLE, - ) + let meta = AddressMeta::new( + self.network, + if self.burning_bug.is_none() { + AddressType::Featured(false, None, true) + } else { + AddressType::Standard + }, + ); + self.pair.address(meta) } - /// Return the specified subaddress for this view pair. + /// 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(); @@ -218,4 +242,32 @@ impl Scanner { self.pair.view.deref() * spend, ) } + + /// Returns a featured address. + /// + /// if you created the scanner with `Some` value for `burning_bug` be aware that + /// even though you can pass `true` for the `guaranteed` parameter and generate + /// a guaranteed address, scanner wont be able decode incoming outputs for this address. + /// If you want to support guaranteed addresses please create a scanner with `None` value to + /// `burning_bug`. Reverse of the case also holds. + pub fn featured_address( + &mut self, + index: Option<(u32, u32)>, + payment_id: Option<[u8; 8]>, + guaranteed: bool, + ) -> MoneroAddress { + let subaddress = index.is_some() && (index.unwrap() != (0, 0)); + + let mut spend = self.pair.spend; + let mut view: EdwardsPoint = self.pair.view.deref() * &ED25519_BASEPOINT_TABLE; + if subaddress { + spend = self.pair.spend + (&self.pair.subaddress(index.unwrap()) * &ED25519_BASEPOINT_TABLE); + self.subaddresses.insert(spend.compress(), index.unwrap()); + view = self.pair.view.deref() * spend; + } + + let meta = + AddressMeta::new(self.network, AddressType::Featured(subaddress, payment_id, guaranteed)); + MoneroAddress::new(meta, spend, view) + } }