update address public API design

This commit is contained in:
akildemir 2023-01-04 16:26:25 +03:00
parent d9fa88fa76
commit 3a319b229f
6 changed files with 97 additions and 101 deletions

View file

@ -80,6 +80,15 @@ impl<B: AddressBytes> Zeroize for AddressMeta<B> {
} }
} }
/// 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. /// Error when decoding an address.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
pub enum AddressError { pub enum AddressError {

View file

@ -16,7 +16,7 @@ pub(crate) use extra::{PaymentId, ExtraField, Extra};
/// Address encoding and decoding functionality. /// Address encoding and decoding functionality.
pub mod address; pub mod address;
use address::{Network, AddressType, AddressMeta, MoneroAddress}; use address::{Network, AddressType, AddressMeta, MoneroAddress, AddressSpec};
mod scan; mod scan;
pub use scan::{ReceivedOutput, SpendableOutput}; pub use scan::{ReceivedOutput, SpendableOutput};
@ -29,8 +29,6 @@ pub use send::{Fee, TransactionError, SignableTransaction, SignableTransactionBu
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
pub use send::TransactionMachine; pub use send::TransactionMachine;
use self::address::MoneroAddressBytes;
fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> std::cmp::Ordering { fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> std::cmp::Ordering {
x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse() x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse()
} }
@ -108,7 +106,7 @@ impl ViewPair {
ViewPair { spend, view } ViewPair { spend, view }
} }
pub(crate) fn subaddress(&self, index: (u32, u32)) -> Scalar { fn subaddress_keys(&self, index: (u32, u32)) -> Scalar {
if index == (0, 0) { if index == (0, 0) {
return Scalar::zero(); return Scalar::zero();
} }
@ -124,9 +122,45 @@ impl ViewPair {
)) ))
} }
/// Returns an address with the provided metadata. fn subaddress_derivation(&self, index: (u32, u32)) -> (EdwardsPoint, EdwardsPoint) {
pub fn address(&self, meta: AddressMeta<MoneroAddressBytes>) -> MoneroAddress { let scalar = self.subaddress_keys(index);
MoneroAddress::new(meta, self.spend, self.view.deref() * &ED25519_BASEPOINT_TABLE) 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 } Scanner { pair, network, subaddresses, burning_bug }
} }
/// Returns the main address for this view pair. /// Returns the specified address.
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.
/// ///
/// A `Scanner` will fail to scan this address unless it is appropriate to the `Scanner`. /// 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 /// 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 /// `burning_bug` upon construction, this must be a guaranteed address
/// in order to be scanned by it. /// in order to be scanned by it.
pub fn featured_address( pub fn address(&mut self, spec: AddressSpec) -> MoneroAddress {
&mut self, let addr = self.pair.address(self.network, spec);
subaddress: Option<(u32, u32)>,
payment_id: Option<[u8; 8]>,
guaranteed: bool,
) -> MoneroAddress {
let is_subaddress = subaddress.is_some() && (subaddress.unwrap() != (0, 0));
let mut spend = self.pair.spend; let mut index = (0, 0);
let mut view: EdwardsPoint = self.pair.view.deref() * &ED25519_BASEPOINT_TABLE; if let AddressSpec::Subaddress(i1, i2) = spec {
if is_subaddress { index = (i1, i2);
spend = } else if let AddressSpec::Featured(Some(index_inner), _, _) = spec {
self.pair.spend + (&self.pair.subaddress(subaddress.unwrap()) * &ED25519_BASEPOINT_TABLE); index = index_inner;
self.subaddresses.insert(spend.compress(), subaddress.unwrap());
view = self.pair.view.deref() * spend;
} }
self.subaddresses.insert(addr.spend.compress(), index);
let meta = addr
AddressMeta::new(self.network, AddressType::Featured(is_subaddress, payment_id, guaranteed));
MoneroAddress::new(meta, spend, view)
} }
} }

View file

@ -293,7 +293,7 @@ impl Scanner {
// If we did though, it'd enable bypassing the included burning bug protection // If we did though, it'd enable bypassing the included burning bug protection
debug_assert!(output_key.is_torsion_free()); 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 // Since we've found an output to us, get its amount
let mut commitment = Commitment::zero(); let mut commitment = Commitment::zero();

View file

@ -14,7 +14,7 @@ use monero_serai::{
Protocol, random_scalar, Protocol, random_scalar,
wallet::{ wallet::{
ViewPair, Scanner, ViewPair, Scanner,
address::{Network, AddressType, AddressMeta, MoneroAddress}, address::{Network, AddressType, AddressMeta, MoneroAddress, AddressSpec},
SpendableOutput, SpendableOutput,
}, },
rpc::Rpc, 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 // mine 60 blocks to unlock a miner tx
let start = rpc.get_height().await.unwrap(); 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(); let block = rpc.get_block(start).await.unwrap();
scanner.scan(rpc, &block).await.unwrap().swap_remove(0).ignore_timelock().swap_remove(0) scanner.scan(rpc, &block).await.unwrap().swap_remove(0).ignore_timelock().swap_remove(0)
@ -151,7 +151,7 @@ macro_rules! test {
use monero_serai::{ use monero_serai::{
random_scalar, random_scalar,
wallet::{ wallet::{
address::{Network, AddressMeta, AddressType}, ViewPair, Scanner, SignableTransaction, address::{Network, AddressSpec}, ViewPair, Scanner, SignableTransaction,
SignableTransactionBuilder, SignableTransactionBuilder,
}, },
}; };
@ -185,7 +185,7 @@ macro_rules! test {
let rpc = rpc().await; let rpc = rpc().await;
let view = ViewPair::new(spend_pub, Zeroizing::new(random_scalar(&mut OsRng))); 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; let miner_tx = get_miner_tx_output(&rpc, &view).await;

View file

@ -1,6 +1,6 @@
use rand::RngCore; use rand::RngCore;
use monero_serai::{transaction::Transaction}; use monero_serai::transaction::Transaction;
mod runner; mod runner;
@ -8,9 +8,9 @@ test!(
scan_standard_address, scan_standard_address,
( (
|_, mut builder: Builder, _| async move { |_, mut builder: Builder, _| async move {
let scanner = let mut scanner =
Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new())); 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,)) (builder.build().unwrap(), (scanner,))
}, },
|_, tx: Transaction, _, mut state: (Scanner,)| async move { |_, tx: Transaction, _, mut state: (Scanner,)| async move {
@ -27,7 +27,10 @@ test!(
let mut scanner = let mut scanner =
Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new())); Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new()));
let subaddress_index = (0, 1); 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)) (builder.build().unwrap(), (scanner, subaddress_index))
}, },
|_, tx: Transaction, _, mut state: (Scanner, (u32, u32))| async move { |_, tx: Transaction, _, mut state: (Scanner, (u32, u32))| async move {
@ -42,12 +45,12 @@ test!(
scan_integrated_address, scan_integrated_address,
( (
|_, mut builder: Builder, _| async move { |_, mut builder: Builder, _| async move {
let scanner = let mut scanner =
Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new())); Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new()));
let mut payment_id = [0u8; 8]; let mut payment_id = [0u8; 8];
OsRng.fill_bytes(&mut payment_id); 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)) (builder.build().unwrap(), (scanner, payment_id))
}, },
|_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move { |_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move {
@ -64,7 +67,7 @@ test!(
|_, mut builder: Builder, _| async move { |_, mut builder: Builder, _| async move {
let mut scanner = let mut scanner =
Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new())); 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,)) (builder.build().unwrap(), (scanner,))
}, },
|_, tx: Transaction, _, mut state: (Scanner,)| async move { |_, tx: Transaction, _, mut state: (Scanner,)| async move {
@ -81,7 +84,10 @@ test!(
let mut scanner = let mut scanner =
Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new())); Scanner::from_view(runner::random_address().1, Network::Mainnet, Some(HashSet::new()));
let subaddress_index = (0, 2); 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)) (builder.build().unwrap(), (scanner, subaddress_index))
}, },
|_, tx: Transaction, _, mut state: (Scanner, (u32, u32))| async move { |_, tx: Transaction, _, mut state: (Scanner, (u32, u32))| async move {
@ -101,7 +107,7 @@ test!(
let mut payment_id = [0u8; 8]; let mut payment_id = [0u8; 8];
OsRng.fill_bytes(&mut payment_id); 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)) (builder.build().unwrap(), (scanner, payment_id))
}, },
|_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move { |_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move {
@ -123,8 +129,10 @@ test!(
let mut payment_id = [0u8; 8]; let mut payment_id = [0u8; 8];
OsRng.fill_bytes(&mut payment_id); OsRng.fill_bytes(&mut payment_id);
builder builder.add_payment(
.add_payment(scanner.featured_address(Some(subaddress_index), Some(payment_id), false), 5); scanner.address(AddressSpec::Featured(Some(subaddress_index), Some(payment_id), false)),
5,
);
(builder.build().unwrap(), (scanner, payment_id, subaddress_index)) (builder.build().unwrap(), (scanner, payment_id, subaddress_index))
}, },
|_, tx: Transaction, _, mut state: (Scanner, [u8; 8], (u32, u32))| async move { |_, tx: Transaction, _, mut state: (Scanner, [u8; 8], (u32, u32))| async move {
@ -140,9 +148,9 @@ test!(
scan_guaranteed_standard, scan_guaranteed_standard,
( (
|_, mut builder: Builder, _| async move { |_, 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,)) (builder.build().unwrap(), (scanner,))
}, },
|_, tx: Transaction, _, mut state: (Scanner,)| async move { |_, 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 mut scanner = Scanner::from_view(runner::random_address().1, Network::Mainnet, None);
let subaddress_index = (0, 1); 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)) (builder.build().unwrap(), (scanner, subaddress_index))
}, },
|_, tx: Transaction, _, mut state: (Scanner, (u32, u32))| async move { |_, tx: Transaction, _, mut state: (Scanner, (u32, u32))| async move {
@ -174,11 +183,11 @@ test!(
scan_guaranteed_integrated, scan_guaranteed_integrated,
( (
|_, mut builder: Builder, _| async move { |_, 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]; let mut payment_id = [0u8; 8];
OsRng.fill_bytes(&mut payment_id); 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)) (builder.build().unwrap(), (scanner, payment_id))
}, },
|_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move { |_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move {
@ -199,8 +208,10 @@ test!(
let mut payment_id = [0u8; 8]; let mut payment_id = [0u8; 8];
OsRng.fill_bytes(&mut payment_id); OsRng.fill_bytes(&mut payment_id);
builder builder.add_payment(
.add_payment(scanner.featured_address(Some(subaddress_index), Some(payment_id), true), 5); scanner.address(AddressSpec::Featured(Some(subaddress_index), Some(payment_id), true)),
5,
);
(builder.build().unwrap(), (scanner, payment_id, subaddress_index)) (builder.build().unwrap(), (scanner, payment_id, subaddress_index))
}, },
|_, tx: Transaction, _, mut state: (Scanner, [u8; 8], (u32, u32))| async move { |_, tx: Transaction, _, mut state: (Scanner, [u8; 8], (u32, u32))| async move {

View file

@ -14,7 +14,7 @@ use monero_serai::{
rpc::Rpc, rpc::Rpc,
wallet::{ wallet::{
ViewPair, Scanner, ViewPair, Scanner,
address::{Network, MoneroAddress}, address::{Network, MoneroAddress, AddressSpec},
Fee, SpendableOutput, SignableTransaction as MSignableTransaction, TransactionMachine, Fee, SpendableOutput, SignableTransaction as MSignableTransaction, TransactionMachine,
}, },
}; };
@ -91,7 +91,7 @@ impl Monero {
#[cfg(test)] #[cfg(test)]
fn empty_address() -> MoneroAddress { 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; const MAX_OUTPUTS: usize = 16;
fn address(&self, key: dfg::EdwardsPoint) -> Self::Address { 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<usize, CoinError> { async fn get_latest_block_number(&self) -> Result<usize, CoinError> {