mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 21:49:26 +00:00
Clean and document monero-address
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "monero-address"
|
name = "monero-address"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Monero addresses and associated functionality"
|
description = "Rust implementation of Monero addresses"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/wallet/address"
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/wallet/address"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
@@ -21,7 +21,6 @@ std-shims = { path = "../../../../common/std-shims", version = "^0.1.1", default
|
|||||||
thiserror = { version = "1", default-features = false, optional = true }
|
thiserror = { version = "1", default-features = false, optional = true }
|
||||||
|
|
||||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||||
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
|
||||||
|
|
||||||
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
|
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
|
||||||
|
|
||||||
@@ -29,8 +28,11 @@ monero-io = { path = "../../io", default-features = false }
|
|||||||
monero-primitives = { path = "../../primitives", default-features = false }
|
monero-primitives = { path = "../../primitives", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = { version = "0.4", default-features = false }
|
|
||||||
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
|
hex-literal = { version = "0.4", default-features = false }
|
||||||
|
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||||
|
|
||||||
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
|
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
|
||||||
serde_json = { version = "1", default-features = false, features = ["alloc"] }
|
serde_json = { version = "1", default-features = false, features = ["alloc"] }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Polyseed
|
# Monero Address
|
||||||
|
|
||||||
Rust implementation of [Polyseed](https://github.com/tevador/polyseed).
|
Rust implementation of Monero addresses.
|
||||||
|
|
||||||
This library is usable under no-std when the `std` feature (on by default) is
|
This library is usable under no-std when the `std` feature (on by default) is
|
||||||
disabled.
|
disabled.
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
// #![deny(missing_docs)] // TODO
|
#![deny(missing_docs)]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use core::{marker::PhantomData, fmt};
|
use core::fmt::{self, Write};
|
||||||
use std_shims::string::ToString;
|
use std_shims::string::ToString;
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use curve25519_dalek::edwards::EdwardsPoint;
|
use curve25519_dalek::edwards::EdwardsPoint;
|
||||||
|
|
||||||
use monero_io::decompress_point;
|
use monero_io::*;
|
||||||
|
|
||||||
mod base58check;
|
mod base58check;
|
||||||
use base58check::{encode_check, decode_check};
|
use base58check::{encode_check, decode_check};
|
||||||
@@ -18,65 +18,53 @@ use base58check::{encode_check, decode_check};
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
/// The network this address is for.
|
/// The address type.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
///
|
||||||
pub enum Network {
|
/// The officially specified addresses are supported, along with
|
||||||
Mainnet,
|
|
||||||
Testnet,
|
|
||||||
Stagenet,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The address type, supporting the officially documented addresses, along with
|
|
||||||
/// [Featured Addresses](https://gist.github.com/kayabaNerve/01c50bbc35441e0bbdcee63a9d823789).
|
/// [Featured Addresses](https://gist.github.com/kayabaNerve/01c50bbc35441e0bbdcee63a9d823789).
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub enum AddressType {
|
pub enum AddressType {
|
||||||
Standard,
|
/// A legacy address type.
|
||||||
Integrated([u8; 8]),
|
Legacy,
|
||||||
|
/// A legacy address with a payment ID embedded.
|
||||||
|
LegacyIntegrated([u8; 8]),
|
||||||
|
/// A subaddress.
|
||||||
|
///
|
||||||
|
/// This is what SHOULD be used if specific functionality isn't needed.
|
||||||
Subaddress,
|
Subaddress,
|
||||||
Featured { subaddress: bool, payment_id: Option<[u8; 8]>, guaranteed: bool },
|
/// A featured address.
|
||||||
}
|
///
|
||||||
|
/// Featured Addresses are an unofficial address specification which is meant to be extensible
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
/// and support a variety of functionality. This functionality includes being a subaddresses AND
|
||||||
pub struct SubaddressIndex {
|
/// having a payment ID, along with being immune to the burning bug.
|
||||||
pub(crate) account: u32,
|
///
|
||||||
pub(crate) address: u32,
|
/// At this time, support for featured addresses is limited to this crate. There should be no
|
||||||
}
|
/// expectation of interoperability.
|
||||||
|
Featured {
|
||||||
impl SubaddressIndex {
|
/// If this address is a subaddress.
|
||||||
pub const fn new(account: u32, address: u32) -> Option<SubaddressIndex> {
|
subaddress: bool,
|
||||||
if (account == 0) && (address == 0) {
|
/// The payment ID associated with this address.
|
||||||
return None;
|
payment_id: Option<[u8; 8]>,
|
||||||
}
|
/// If this address is guaranteed.
|
||||||
Some(SubaddressIndex { account, address })
|
///
|
||||||
}
|
/// A guaranteed address is one where any outputs scanned to it are guaranteed to be spendable
|
||||||
|
/// under the hardness of various cryptographic problems (which are assumed hard). This is via
|
||||||
pub fn account(&self) -> u32 {
|
/// a modified shared-key derivation which eliminates the burning bug.
|
||||||
self.account
|
guaranteed: bool,
|
||||||
}
|
},
|
||||||
|
|
||||||
pub fn address(&self) -> u32 {
|
|
||||||
self.address
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Address specification. Used internally to create addresses.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
|
||||||
pub enum AddressSpec {
|
|
||||||
Standard,
|
|
||||||
Integrated([u8; 8]),
|
|
||||||
Subaddress(SubaddressIndex),
|
|
||||||
Featured { subaddress: Option<SubaddressIndex>, payment_id: Option<[u8; 8]>, guaranteed: bool },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddressType {
|
impl AddressType {
|
||||||
|
/// If this address is a subaddress.
|
||||||
pub fn is_subaddress(&self) -> bool {
|
pub fn is_subaddress(&self) -> bool {
|
||||||
matches!(self, AddressType::Subaddress) ||
|
matches!(self, AddressType::Subaddress) ||
|
||||||
matches!(self, AddressType::Featured { subaddress: true, .. })
|
matches!(self, AddressType::Featured { subaddress: true, .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The payment ID within this address.
|
||||||
// TODO: wallet-core PaymentId? TX extra crate imported here?
|
// TODO: wallet-core PaymentId? TX extra crate imported here?
|
||||||
pub fn payment_id(&self) -> Option<[u8; 8]> {
|
pub fn payment_id(&self) -> Option<[u8; 8]> {
|
||||||
if let AddressType::Integrated(id) = self {
|
if let AddressType::LegacyIntegrated(id) = self {
|
||||||
Some(*id)
|
Some(*id)
|
||||||
} else if let AddressType::Featured { payment_id, .. } = self {
|
} else if let AddressType::Featured { payment_id, .. } = self {
|
||||||
*payment_id
|
*payment_id
|
||||||
@@ -85,251 +73,461 @@ impl AddressType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If this address is guaranteed.
|
||||||
|
///
|
||||||
|
/// A guaranteed address is one where any outputs scanned to it are guaranteed to be spendable
|
||||||
|
/// under the hardness of various cryptographic problems (which are assumed hard). This is via
|
||||||
|
/// a modified shared-key derivation which eliminates the burning bug.
|
||||||
pub fn is_guaranteed(&self) -> bool {
|
pub fn is_guaranteed(&self) -> bool {
|
||||||
matches!(self, AddressType::Featured { guaranteed: true, .. })
|
matches!(self, AddressType::Featured { guaranteed: true, .. })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type which returns the byte for a given address.
|
/// A subaddress index.
|
||||||
pub trait AddressBytes: Clone + Copy + PartialEq + Eq + fmt::Debug {
|
///
|
||||||
fn network_bytes(network: Network) -> (u8, u8, u8, u8);
|
/// Subaddresses are derived from a root using a `(account, address)` tuple as an index.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
|
pub struct SubaddressIndex {
|
||||||
|
account: u32,
|
||||||
|
address: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Address bytes for Monero.
|
impl SubaddressIndex {
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
/// Create a new SubaddressIndex.
|
||||||
pub struct MoneroAddressBytes;
|
pub const fn new(account: u32, address: u32) -> Option<SubaddressIndex> {
|
||||||
impl AddressBytes for MoneroAddressBytes {
|
if (account == 0) && (address == 0) {
|
||||||
fn network_bytes(network: Network) -> (u8, u8, u8, u8) {
|
return None;
|
||||||
match network {
|
|
||||||
Network::Mainnet => (18, 19, 42, 70),
|
|
||||||
Network::Testnet => (53, 54, 63, 111),
|
|
||||||
Network::Stagenet => (24, 25, 36, 86),
|
|
||||||
}
|
}
|
||||||
|
Some(SubaddressIndex { account, address })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the account this subaddress index is under.
|
||||||
|
pub const fn account(&self) -> u32 {
|
||||||
|
self.account
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the address this subaddress index is for, within its account.
|
||||||
|
pub const fn address(&self) -> u32 {
|
||||||
|
self.address
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Address metadata.
|
/// A specification for an address to be derived.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
///
|
||||||
pub struct AddressMeta<B: AddressBytes> {
|
/// This contains all the information an address will embed once derived.
|
||||||
_bytes: PhantomData<B>,
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub network: Network,
|
pub enum AddressSpec {
|
||||||
pub kind: AddressType,
|
/// A legacy address type.
|
||||||
|
Legacy,
|
||||||
|
/// A legacy address with a payment ID embedded.
|
||||||
|
LegacyIntegrated([u8; 8]),
|
||||||
|
/// A subaddress.
|
||||||
|
///
|
||||||
|
/// This is what SHOULD be used if specific functionality isn't needed.
|
||||||
|
Subaddress(SubaddressIndex),
|
||||||
|
/// A featured address.
|
||||||
|
///
|
||||||
|
/// Featured Addresses are an unofficial address specification which is meant to be extensible
|
||||||
|
/// and support a variety of functionality. This functionality includes being a subaddresses AND
|
||||||
|
/// having a payment ID, along with being immune to the burning bug.
|
||||||
|
///
|
||||||
|
/// At this time, support for featured addresses is limited to this crate. There should be no
|
||||||
|
/// expectation of interoperability.
|
||||||
|
Featured {
|
||||||
|
/// The subaddress index to derive this address with.
|
||||||
|
///
|
||||||
|
/// If None, no subaddress derivation occurs.
|
||||||
|
subaddress: Option<SubaddressIndex>,
|
||||||
|
/// The payment ID to embed in this address.
|
||||||
|
payment_id: Option<[u8; 8]>,
|
||||||
|
/// If this address should be guaranteed.
|
||||||
|
///
|
||||||
|
/// A guaranteed address is one where any outputs scanned to it are guaranteed to be spendable
|
||||||
|
/// under the hardness of various cryptographic problems (which are assumed hard). This is via
|
||||||
|
/// a modified shared-key derivation which eliminates the burning bug.
|
||||||
|
guaranteed: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: AddressBytes> Zeroize for AddressMeta<B> {
|
/// Bytes used as prefixes when encoding addresses.
|
||||||
fn zeroize(&mut self) {
|
///
|
||||||
self.network.zeroize();
|
/// These distinguish the address's type.
|
||||||
self.kind.zeroize();
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
|
pub struct AddressBytes {
|
||||||
|
legacy: u8,
|
||||||
|
legacy_integrated: u8,
|
||||||
|
subaddress: u8,
|
||||||
|
featured: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddressBytes {
|
||||||
|
/// Create a new set of address bytes, one for each address type.
|
||||||
|
pub const fn new(
|
||||||
|
legacy: u8,
|
||||||
|
legacy_integrated: u8,
|
||||||
|
subaddress: u8,
|
||||||
|
featured: u8,
|
||||||
|
) -> Option<Self> {
|
||||||
|
if (legacy == legacy_integrated) || (legacy == subaddress) || (legacy == featured) {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
if (legacy_integrated == subaddress) || (legacy_integrated == featured) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if subaddress == featured {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(AddressBytes { legacy, legacy_integrated, subaddress, featured })
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn to_const_generic(self) -> u32 {
|
||||||
|
((self.legacy as u32) << 24) +
|
||||||
|
((self.legacy_integrated as u32) << 16) +
|
||||||
|
((self.subaddress as u32) << 8) +
|
||||||
|
(self.featured as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
const fn from_const_generic(const_generic: u32) -> Self {
|
||||||
|
let legacy = (const_generic >> 24) as u8;
|
||||||
|
let legacy_integrated = ((const_generic >> 16) & (u8::MAX as u32)) as u8;
|
||||||
|
let subaddress = ((const_generic >> 8) & (u8::MAX as u32)) as u8;
|
||||||
|
let featured = (const_generic & (u8::MAX as u32)) as u8;
|
||||||
|
|
||||||
|
AddressBytes { legacy, legacy_integrated, subaddress, featured }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Cite origin
|
||||||
|
const MONERO_MAINNET_BYTES: AddressBytes = match AddressBytes::new(18, 19, 42, 70) {
|
||||||
|
Some(bytes) => bytes,
|
||||||
|
None => panic!("mainnet byte constants conflicted"),
|
||||||
|
};
|
||||||
|
const MONERO_STAGENET_BYTES: AddressBytes = match AddressBytes::new(53, 54, 63, 111) {
|
||||||
|
Some(bytes) => bytes,
|
||||||
|
None => panic!("stagenet byte constants conflicted"),
|
||||||
|
};
|
||||||
|
const MONERO_TESTNET_BYTES: AddressBytes = match AddressBytes::new(24, 25, 36, 86) {
|
||||||
|
Some(bytes) => bytes,
|
||||||
|
None => panic!("testnet byte constants conflicted"),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The network this address is for.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
|
pub enum Network {
|
||||||
|
/// A mainnet address.
|
||||||
|
Mainnet,
|
||||||
|
/// A stagenet address.
|
||||||
|
///
|
||||||
|
/// Stagenet maintains parity with mainnet and is useful for testing integrations accordingly.
|
||||||
|
Stagenet,
|
||||||
|
/// A testnet address.
|
||||||
|
///
|
||||||
|
/// Testnet is used to test new consensus rules and functionality.
|
||||||
|
Testnet,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error when decoding an address.
|
/// Error when decoding an address.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum AddressError {
|
pub enum AddressError {
|
||||||
#[cfg_attr(feature = "std", error("invalid address byte"))]
|
/// The address had an invalid (network, type) byte.
|
||||||
InvalidByte,
|
#[cfg_attr(feature = "std", error("invalid byte for the address's network/type ({0})"))]
|
||||||
|
InvalidTypeByte(u8),
|
||||||
|
/// The address wasn't a valid Base58Check (as defined by Monero) string.
|
||||||
#[cfg_attr(feature = "std", error("invalid address encoding"))]
|
#[cfg_attr(feature = "std", error("invalid address encoding"))]
|
||||||
InvalidEncoding,
|
InvalidEncoding,
|
||||||
|
/// The data encoded wasn't the proper length.
|
||||||
#[cfg_attr(feature = "std", error("invalid length"))]
|
#[cfg_attr(feature = "std", error("invalid length"))]
|
||||||
InvalidLength,
|
InvalidLength,
|
||||||
|
/// The address had an invalid key.
|
||||||
#[cfg_attr(feature = "std", error("invalid key"))]
|
#[cfg_attr(feature = "std", error("invalid key"))]
|
||||||
InvalidKey,
|
InvalidKey,
|
||||||
|
/// The address was featured with unrecognized features.
|
||||||
#[cfg_attr(feature = "std", error("unknown features"))]
|
#[cfg_attr(feature = "std", error("unknown features"))]
|
||||||
UnknownFeatures,
|
UnknownFeatures(u64),
|
||||||
#[cfg_attr(feature = "std", error("different network than expected"))]
|
/// The network was for a different network than expected.
|
||||||
DifferentNetwork,
|
#[cfg_attr(
|
||||||
|
feature = "std",
|
||||||
|
error("different network ({actual:?}) than expected ({expected:?})")
|
||||||
|
)]
|
||||||
|
DifferentNetwork {
|
||||||
|
/// The Network expected.
|
||||||
|
expected: Network,
|
||||||
|
/// The Network embedded within the Address.
|
||||||
|
actual: Network,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: AddressBytes> AddressMeta<B> {
|
/// Bytes used as prefixes when encoding addresses, variable to the network instance.
|
||||||
#[allow(clippy::wrong_self_convention)]
|
///
|
||||||
fn to_byte(&self) -> u8 {
|
/// These distinguish the address's network and type.
|
||||||
let bytes = B::network_bytes(self.network);
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
match self.kind {
|
pub struct NetworkedAddressBytes {
|
||||||
AddressType::Standard => bytes.0,
|
mainnet: AddressBytes,
|
||||||
AddressType::Integrated(_) => bytes.1,
|
stagenet: AddressBytes,
|
||||||
AddressType::Subaddress => bytes.2,
|
testnet: AddressBytes,
|
||||||
AddressType::Featured { .. } => bytes.3,
|
}
|
||||||
|
|
||||||
|
impl NetworkedAddressBytes {
|
||||||
|
/// Create a new set of address bytes, one for each network.
|
||||||
|
pub const fn new(
|
||||||
|
mainnet: AddressBytes,
|
||||||
|
stagenet: AddressBytes,
|
||||||
|
testnet: AddressBytes,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let res = NetworkedAddressBytes { mainnet, stagenet, testnet };
|
||||||
|
let all_bytes = res.to_const_generic();
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i < 12 {
|
||||||
|
let this_byte = (all_bytes >> (32 + (i * 8))) & (u8::MAX as u128);
|
||||||
|
|
||||||
|
let mut j = 0;
|
||||||
|
while j < 12 {
|
||||||
|
if i == j {
|
||||||
|
j += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let other_byte = (all_bytes >> (32 + (j * 8))) & (u8::MAX as u128);
|
||||||
|
if this_byte == other_byte {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert this set of address bytes to its representation as a u128.
|
||||||
|
///
|
||||||
|
/// We cannot use this struct directly as a const generic unfortunately.
|
||||||
|
pub const fn to_const_generic(self) -> u128 {
|
||||||
|
((self.mainnet.to_const_generic() as u128) << 96) +
|
||||||
|
((self.stagenet.to_const_generic() as u128) << 64) +
|
||||||
|
((self.testnet.to_const_generic() as u128) << 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
const fn from_const_generic(const_generic: u128) -> Self {
|
||||||
|
let mainnet = AddressBytes::from_const_generic((const_generic >> 96) as u32);
|
||||||
|
let stagenet =
|
||||||
|
AddressBytes::from_const_generic(((const_generic >> 64) & (u32::MAX as u128)) as u32);
|
||||||
|
let testnet =
|
||||||
|
AddressBytes::from_const_generic(((const_generic >> 32) & (u32::MAX as u128)) as u32);
|
||||||
|
|
||||||
|
NetworkedAddressBytes { mainnet, stagenet, testnet }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn network(&self, network: Network) -> &AddressBytes {
|
||||||
|
match network {
|
||||||
|
Network::Mainnet => &self.mainnet,
|
||||||
|
Network::Stagenet => &self.stagenet,
|
||||||
|
Network::Testnet => &self.testnet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an address's metadata.
|
fn byte(&self, network: Network, kind: AddressType) -> u8 {
|
||||||
pub fn new(network: Network, kind: AddressType) -> Self {
|
let address_bytes = self.network(network);
|
||||||
AddressMeta { _bytes: PhantomData, network, kind }
|
|
||||||
|
match kind {
|
||||||
|
AddressType::Legacy => address_bytes.legacy,
|
||||||
|
AddressType::LegacyIntegrated(_) => address_bytes.legacy_integrated,
|
||||||
|
AddressType::Subaddress => address_bytes.subaddress,
|
||||||
|
AddressType::Featured { .. } => address_bytes.featured,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an incomplete instantiation in the case of Integrated/Featured addresses
|
// This will return an incomplete AddressType for LegacyIntegrated/Featured.
|
||||||
fn from_byte(byte: u8) -> Result<Self, AddressError> {
|
fn metadata_from_byte(&self, byte: u8) -> Result<(Network, AddressType), AddressError> {
|
||||||
let mut meta = None;
|
let mut meta = None;
|
||||||
for network in [Network::Mainnet, Network::Testnet, Network::Stagenet] {
|
for network in [Network::Mainnet, Network::Testnet, Network::Stagenet] {
|
||||||
let (standard, integrated, subaddress, featured) = B::network_bytes(network);
|
let address_bytes = self.network(network);
|
||||||
if let Some(kind) = match byte {
|
if let Some(kind) = match byte {
|
||||||
_ if byte == standard => Some(AddressType::Standard),
|
_ if byte == address_bytes.legacy => Some(AddressType::Legacy),
|
||||||
_ if byte == integrated => Some(AddressType::Integrated([0; 8])),
|
_ if byte == address_bytes.legacy_integrated => Some(AddressType::LegacyIntegrated([0; 8])),
|
||||||
_ if byte == subaddress => Some(AddressType::Subaddress),
|
_ if byte == address_bytes.subaddress => Some(AddressType::Subaddress),
|
||||||
_ if byte == featured => {
|
_ if byte == address_bytes.featured => {
|
||||||
Some(AddressType::Featured { subaddress: false, payment_id: None, guaranteed: false })
|
Some(AddressType::Featured { subaddress: false, payment_id: None, guaranteed: false })
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
} {
|
} {
|
||||||
meta = Some(AddressMeta::new(network, kind));
|
meta = Some((network, kind));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.ok_or(AddressError::InvalidByte)
|
meta.ok_or(AddressError::InvalidTypeByte(byte))
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_subaddress(&self) -> bool {
|
|
||||||
self.kind.is_subaddress()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn payment_id(&self) -> Option<[u8; 8]> {
|
|
||||||
self.kind.payment_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_guaranteed(&self) -> bool {
|
|
||||||
self.kind.is_guaranteed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Monero address, composed of metadata and a spend/view key.
|
/// The bytes used for distinguishing Monero addresses.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
pub const MONERO_BYTES: NetworkedAddressBytes = match NetworkedAddressBytes::new(
|
||||||
pub struct Address<B: AddressBytes> {
|
MONERO_MAINNET_BYTES,
|
||||||
pub meta: AddressMeta<B>,
|
MONERO_STAGENET_BYTES,
|
||||||
pub spend: EdwardsPoint,
|
MONERO_TESTNET_BYTES,
|
||||||
pub view: EdwardsPoint,
|
) {
|
||||||
|
Some(bytes) => bytes,
|
||||||
|
None => panic!("Monero network byte constants conflicted"),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A Monero address.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Zeroize)]
|
||||||
|
pub struct Address<const ADDRESS_BYTES: u128> {
|
||||||
|
network: Network,
|
||||||
|
kind: AddressType,
|
||||||
|
spend: EdwardsPoint,
|
||||||
|
view: EdwardsPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: AddressBytes> fmt::Debug for Address<B> {
|
impl<const ADDRESS_BYTES: u128> fmt::Debug for Address<ADDRESS_BYTES> {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
|
let hex = |bytes: &[u8]| -> String {
|
||||||
|
let mut res = String::with_capacity(2 + (2 * bytes.len()));
|
||||||
|
res.push_str("0x");
|
||||||
|
for b in bytes {
|
||||||
|
write!(&mut res, "{b:02x}").unwrap();
|
||||||
|
}
|
||||||
|
res
|
||||||
|
};
|
||||||
|
|
||||||
fmt
|
fmt
|
||||||
.debug_struct("Address")
|
.debug_struct("Address")
|
||||||
.field("meta", &self.meta)
|
.field("network", &self.network)
|
||||||
.field("spend", &hex::encode(self.spend.compress().0))
|
.field("kind", &self.kind)
|
||||||
.field("view", &hex::encode(self.view.compress().0))
|
.field("spend", &hex(&self.spend.compress().to_bytes()))
|
||||||
|
.field("view", &hex(&self.view.compress().to_bytes()))
|
||||||
// This is not a real field yet is the most valuable thing to know when debugging
|
// This is not a real field yet is the most valuable thing to know when debugging
|
||||||
.field("(address)", &self.to_string())
|
.field("(address)", &self.to_string())
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: AddressBytes> Zeroize for Address<B> {
|
impl<const ADDRESS_BYTES: u128> fmt::Display for Address<ADDRESS_BYTES> {
|
||||||
fn zeroize(&mut self) {
|
|
||||||
self.meta.zeroize();
|
|
||||||
self.spend.zeroize();
|
|
||||||
self.view.zeroize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B: AddressBytes> fmt::Display for Address<B> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let mut data = vec![self.meta.to_byte()];
|
let address_bytes: NetworkedAddressBytes =
|
||||||
|
NetworkedAddressBytes::from_const_generic(ADDRESS_BYTES);
|
||||||
|
|
||||||
|
let mut data = vec![address_bytes.byte(self.network, self.kind)];
|
||||||
data.extend(self.spend.compress().to_bytes());
|
data.extend(self.spend.compress().to_bytes());
|
||||||
data.extend(self.view.compress().to_bytes());
|
data.extend(self.view.compress().to_bytes());
|
||||||
if let AddressType::Featured { subaddress, payment_id, guaranteed } = self.meta.kind {
|
if let AddressType::Featured { subaddress, payment_id, guaranteed } = self.kind {
|
||||||
// Technically should be a VarInt, yet we don't have enough features it's needed
|
let features_uint =
|
||||||
data.push(
|
(u8::from(guaranteed) << 2) + (u8::from(payment_id.is_some()) << 1) + u8::from(subaddress);
|
||||||
u8::from(subaddress) + (u8::from(payment_id.is_some()) << 1) + (u8::from(guaranteed) << 2),
|
write_varint(&features_uint, &mut data).unwrap();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if let Some(id) = self.meta.kind.payment_id() {
|
if let Some(id) = self.kind.payment_id() {
|
||||||
data.extend(id);
|
data.extend(id);
|
||||||
}
|
}
|
||||||
write!(f, "{}", encode_check(data))
|
write!(f, "{}", encode_check(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: AddressBytes> Address<B> {
|
impl<const ADDRESS_BYTES: u128> Address<ADDRESS_BYTES> {
|
||||||
pub fn new(meta: AddressMeta<B>, spend: EdwardsPoint, view: EdwardsPoint) -> Self {
|
/// Create a new address.
|
||||||
Address { meta, spend, view }
|
pub fn new(network: Network, kind: AddressType, spend: EdwardsPoint, view: EdwardsPoint) -> Self {
|
||||||
|
Address { network, kind, spend, view }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_str_raw(s: &str) -> Result<Self, AddressError> {
|
/// Parse an address from a String, accepting any network it is.
|
||||||
|
pub fn from_str_with_unchecked_network(s: &str) -> Result<Self, AddressError> {
|
||||||
let raw = decode_check(s).ok_or(AddressError::InvalidEncoding)?;
|
let raw = decode_check(s).ok_or(AddressError::InvalidEncoding)?;
|
||||||
if raw.len() < (1 + 32 + 32) {
|
let mut raw = raw.as_slice();
|
||||||
Err(AddressError::InvalidLength)?;
|
|
||||||
|
let address_bytes: NetworkedAddressBytes =
|
||||||
|
NetworkedAddressBytes::from_const_generic(ADDRESS_BYTES);
|
||||||
|
let (network, mut kind) = address_bytes
|
||||||
|
.metadata_from_byte(read_byte(&mut raw).map_err(|_| AddressError::InvalidLength)?)?;
|
||||||
|
let spend = read_point(&mut raw).map_err(|_| AddressError::InvalidKey)?;
|
||||||
|
let view = read_point(&mut raw).map_err(|_| AddressError::InvalidKey)?;
|
||||||
|
|
||||||
|
if matches!(kind, AddressType::Featured { .. }) {
|
||||||
|
let features = read_varint::<_, u64>(&mut raw).map_err(|_| AddressError::InvalidLength)?;
|
||||||
|
if (features >> 3) != 0 {
|
||||||
|
Err(AddressError::UnknownFeatures(features))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut meta = AddressMeta::from_byte(raw[0])?;
|
let subaddress = (features & 1) == 1;
|
||||||
let spend =
|
let integrated = ((features >> 1) & 1) == 1;
|
||||||
decompress_point(raw[1 .. 33].try_into().unwrap()).ok_or(AddressError::InvalidKey)?;
|
let guaranteed = ((features >> 2) & 1) == 1;
|
||||||
let view =
|
|
||||||
decompress_point(raw[33 .. 65].try_into().unwrap()).ok_or(AddressError::InvalidKey)?;
|
|
||||||
let mut read = 65;
|
|
||||||
|
|
||||||
if matches!(meta.kind, AddressType::Featured { .. }) {
|
kind =
|
||||||
if raw[read] >= (2 << 3) {
|
AddressType::Featured { subaddress, payment_id: integrated.then_some([0; 8]), guaranteed };
|
||||||
Err(AddressError::UnknownFeatures)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let subaddress = (raw[read] & 1) == 1;
|
// Read the payment ID, if there should be one
|
||||||
let integrated = ((raw[read] >> 1) & 1) == 1;
|
match kind {
|
||||||
let guaranteed = ((raw[read] >> 2) & 1) == 1;
|
AddressType::LegacyIntegrated(ref mut id) |
|
||||||
|
AddressType::Featured { payment_id: Some(ref mut id), .. } => {
|
||||||
meta.kind = AddressType::Featured {
|
*id = read_bytes(&mut raw).map_err(|_| AddressError::InvalidLength)?;
|
||||||
subaddress,
|
}
|
||||||
payment_id: Some([0; 8]).filter(|_| integrated),
|
_ => {}
|
||||||
guaranteed,
|
|
||||||
};
|
};
|
||||||
read += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update read early so we can verify the length
|
if !raw.is_empty() {
|
||||||
if meta.kind.payment_id().is_some() {
|
|
||||||
read += 8;
|
|
||||||
}
|
|
||||||
if raw.len() != read {
|
|
||||||
Err(AddressError::InvalidLength)?;
|
Err(AddressError::InvalidLength)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let AddressType::Integrated(ref mut id) = meta.kind {
|
Ok(Address { network, kind, spend, view })
|
||||||
id.copy_from_slice(&raw[(read - 8) .. read]);
|
|
||||||
}
|
|
||||||
if let AddressType::Featured { payment_id: Some(ref mut id), .. } = meta.kind {
|
|
||||||
id.copy_from_slice(&raw[(read - 8) .. read]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Address { meta, spend, view })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new address from a `&str`.
|
||||||
|
///
|
||||||
|
/// This takes in an argument for the expected network, erroring if a distinct network was used.
|
||||||
|
/// It also errors if the address is invalid (as expected).
|
||||||
pub fn from_str(network: Network, s: &str) -> Result<Self, AddressError> {
|
pub fn from_str(network: Network, s: &str) -> Result<Self, AddressError> {
|
||||||
Self::from_str_raw(s).and_then(|addr| {
|
Self::from_str_with_unchecked_network(s).and_then(|addr| {
|
||||||
if addr.meta.network == network {
|
if addr.network == network {
|
||||||
Ok(addr)
|
Ok(addr)
|
||||||
} else {
|
} else {
|
||||||
Err(AddressError::DifferentNetwork)?
|
Err(AddressError::DifferentNetwork { actual: addr.network, expected: network })?
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The network this address is intended for use on.
|
||||||
pub fn network(&self) -> Network {
|
pub fn network(&self) -> Network {
|
||||||
self.meta.network
|
self.network
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The type of address this is.
|
||||||
|
pub fn kind(&self) -> &AddressType {
|
||||||
|
&self.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is a subaddress.
|
||||||
pub fn is_subaddress(&self) -> bool {
|
pub fn is_subaddress(&self) -> bool {
|
||||||
self.meta.is_subaddress()
|
self.kind.is_subaddress()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The payment ID for this address.
|
||||||
pub fn payment_id(&self) -> Option<[u8; 8]> {
|
pub fn payment_id(&self) -> Option<[u8; 8]> {
|
||||||
self.meta.payment_id()
|
self.kind.payment_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If this address is guaranteed.
|
||||||
|
///
|
||||||
|
/// A guaranteed address is one where any outputs scanned to it are guaranteed to be spendable
|
||||||
|
/// under the hardness of various cryptographic problems (which are assumed hard). This is via
|
||||||
|
/// a modified shared-key derivation which eliminates the burning bug.
|
||||||
pub fn is_guaranteed(&self) -> bool {
|
pub fn is_guaranteed(&self) -> bool {
|
||||||
self.meta.is_guaranteed()
|
self.kind.is_guaranteed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The public spend key for this address.
|
||||||
|
pub fn spend(&self) -> EdwardsPoint {
|
||||||
|
self.spend
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The public view key for this address.
|
||||||
|
pub fn view(&self) -> EdwardsPoint {
|
||||||
|
self.view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instantiation of the Address type with Monero's network bytes.
|
/// Instantiation of the Address type with Monero's network bytes.
|
||||||
pub type MoneroAddress = Address<MoneroAddressBytes>;
|
pub type MoneroAddress = Address<{ MONERO_BYTES.to_const_generic() }>;
|
||||||
// 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<T> for T.
|
|
||||||
impl MoneroAddress {
|
|
||||||
pub fn from<B: AddressBytes>(address: Address<B>) -> MoneroAddress {
|
|
||||||
MoneroAddress::new(
|
|
||||||
AddressMeta::new(address.meta.network, address.meta.kind),
|
|
||||||
address.spend,
|
|
||||||
address.view,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
|||||||
|
|
||||||
use monero_io::decompress_point;
|
use monero_io::decompress_point;
|
||||||
|
|
||||||
use crate::{Network, AddressType, AddressMeta, MoneroAddress};
|
use crate::{Network, AddressType, MoneroAddress};
|
||||||
|
|
||||||
const SPEND: [u8; 32] = hex!("f8631661f6ab4e6fda310c797330d86e23a682f20d5bc8cc27b18051191f16d7");
|
const SPEND: [u8; 32] = hex!("f8631661f6ab4e6fda310c797330d86e23a682f20d5bc8cc27b18051191f16d7");
|
||||||
const VIEW: [u8; 32] = hex!("4a1535063ad1fee2dabbf909d4fd9a873e29541b401f0944754e17c9a41820ce");
|
const VIEW: [u8; 32] = hex!("4a1535063ad1fee2dabbf909d4fd9a873e29541b401f0944754e17c9a41820ce");
|
||||||
@@ -65,11 +65,11 @@ fn base58check() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn standard_address() {
|
fn standard_address() {
|
||||||
let addr = MoneroAddress::from_str(Network::Mainnet, STANDARD).unwrap();
|
let addr = MoneroAddress::from_str(Network::Mainnet, STANDARD).unwrap();
|
||||||
assert_eq!(addr.meta.network, Network::Mainnet);
|
assert_eq!(addr.network(), Network::Mainnet);
|
||||||
assert_eq!(addr.meta.kind, AddressType::Standard);
|
assert_eq!(addr.kind(), &AddressType::Legacy);
|
||||||
assert!(!addr.meta.kind.is_subaddress());
|
assert!(!addr.is_subaddress());
|
||||||
assert_eq!(addr.meta.kind.payment_id(), None);
|
assert_eq!(addr.payment_id(), None);
|
||||||
assert!(!addr.meta.kind.is_guaranteed());
|
assert!(!addr.is_guaranteed());
|
||||||
assert_eq!(addr.spend.compress().to_bytes(), SPEND);
|
assert_eq!(addr.spend.compress().to_bytes(), SPEND);
|
||||||
assert_eq!(addr.view.compress().to_bytes(), VIEW);
|
assert_eq!(addr.view.compress().to_bytes(), VIEW);
|
||||||
assert_eq!(addr.to_string(), STANDARD);
|
assert_eq!(addr.to_string(), STANDARD);
|
||||||
@@ -78,11 +78,11 @@ fn standard_address() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn integrated_address() {
|
fn integrated_address() {
|
||||||
let addr = MoneroAddress::from_str(Network::Mainnet, INTEGRATED).unwrap();
|
let addr = MoneroAddress::from_str(Network::Mainnet, INTEGRATED).unwrap();
|
||||||
assert_eq!(addr.meta.network, Network::Mainnet);
|
assert_eq!(addr.network(), Network::Mainnet);
|
||||||
assert_eq!(addr.meta.kind, AddressType::Integrated(PAYMENT_ID));
|
assert_eq!(addr.kind(), &AddressType::LegacyIntegrated(PAYMENT_ID));
|
||||||
assert!(!addr.meta.kind.is_subaddress());
|
assert!(!addr.is_subaddress());
|
||||||
assert_eq!(addr.meta.kind.payment_id(), Some(PAYMENT_ID));
|
assert_eq!(addr.payment_id(), Some(PAYMENT_ID));
|
||||||
assert!(!addr.meta.kind.is_guaranteed());
|
assert!(!addr.is_guaranteed());
|
||||||
assert_eq!(addr.spend.compress().to_bytes(), SPEND);
|
assert_eq!(addr.spend.compress().to_bytes(), SPEND);
|
||||||
assert_eq!(addr.view.compress().to_bytes(), VIEW);
|
assert_eq!(addr.view.compress().to_bytes(), VIEW);
|
||||||
assert_eq!(addr.to_string(), INTEGRATED);
|
assert_eq!(addr.to_string(), INTEGRATED);
|
||||||
@@ -91,11 +91,11 @@ fn integrated_address() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn subaddress() {
|
fn subaddress() {
|
||||||
let addr = MoneroAddress::from_str(Network::Mainnet, SUBADDRESS).unwrap();
|
let addr = MoneroAddress::from_str(Network::Mainnet, SUBADDRESS).unwrap();
|
||||||
assert_eq!(addr.meta.network, Network::Mainnet);
|
assert_eq!(addr.network(), Network::Mainnet);
|
||||||
assert_eq!(addr.meta.kind, AddressType::Subaddress);
|
assert_eq!(addr.kind(), &AddressType::Subaddress);
|
||||||
assert!(addr.meta.kind.is_subaddress());
|
assert!(addr.is_subaddress());
|
||||||
assert_eq!(addr.meta.kind.payment_id(), None);
|
assert_eq!(addr.payment_id(), None);
|
||||||
assert!(!addr.meta.kind.is_guaranteed());
|
assert!(!addr.is_guaranteed());
|
||||||
assert_eq!(addr.spend.compress().to_bytes(), SUB_SPEND);
|
assert_eq!(addr.spend.compress().to_bytes(), SUB_SPEND);
|
||||||
assert_eq!(addr.view.compress().to_bytes(), SUB_VIEW);
|
assert_eq!(addr.view.compress().to_bytes(), SUB_VIEW);
|
||||||
assert_eq!(addr.to_string(), SUBADDRESS);
|
assert_eq!(addr.to_string(), SUBADDRESS);
|
||||||
@@ -125,8 +125,7 @@ fn featured() {
|
|||||||
let guaranteed = (features & GUARANTEED_FEATURE_BIT) == GUARANTEED_FEATURE_BIT;
|
let guaranteed = (features & GUARANTEED_FEATURE_BIT) == GUARANTEED_FEATURE_BIT;
|
||||||
|
|
||||||
let kind = AddressType::Featured { subaddress, payment_id, guaranteed };
|
let kind = AddressType::Featured { subaddress, payment_id, guaranteed };
|
||||||
let meta = AddressMeta::new(network, kind);
|
let addr = MoneroAddress::new(network, kind, spend, view);
|
||||||
let addr = MoneroAddress::new(meta, spend, view);
|
|
||||||
|
|
||||||
assert_eq!(addr.to_string().chars().next().unwrap(), first);
|
assert_eq!(addr.to_string().chars().next().unwrap(), first);
|
||||||
assert_eq!(MoneroAddress::from_str(network, &addr.to_string()).unwrap(), addr);
|
assert_eq!(MoneroAddress::from_str(network, &addr.to_string()).unwrap(), addr);
|
||||||
@@ -190,14 +189,12 @@ fn featured_vectors() {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
MoneroAddress::new(
|
MoneroAddress::new(
|
||||||
AddressMeta::new(
|
|
||||||
network,
|
network,
|
||||||
AddressType::Featured {
|
AddressType::Featured {
|
||||||
subaddress: vector.subaddress,
|
subaddress: vector.subaddress,
|
||||||
payment_id: vector.payment_id,
|
payment_id: vector.payment_id,
|
||||||
guaranteed: vector.guaranteed
|
guaranteed: vector.guaranteed
|
||||||
}
|
},
|
||||||
),
|
|
||||||
spend,
|
spend,
|
||||||
view
|
view
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ pub mod extra;
|
|||||||
pub(crate) use extra::{PaymentId, Extra};
|
pub(crate) use extra::{PaymentId, Extra};
|
||||||
|
|
||||||
pub use monero_address as address;
|
pub use monero_address as address;
|
||||||
use address::{Network, AddressType, SubaddressIndex, AddressSpec, AddressMeta, MoneroAddress};
|
use address::{Network, AddressType, SubaddressIndex, AddressSpec, MoneroAddress};
|
||||||
|
|
||||||
pub mod scan;
|
pub mod scan;
|
||||||
|
|
||||||
@@ -88,28 +88,23 @@ impl ViewPair {
|
|||||||
let mut spend = self.spend;
|
let mut spend = self.spend;
|
||||||
let mut view: EdwardsPoint = self.view.deref() * ED25519_BASEPOINT_TABLE;
|
let mut view: EdwardsPoint = self.view.deref() * ED25519_BASEPOINT_TABLE;
|
||||||
|
|
||||||
// construct the address meta
|
// construct the address type
|
||||||
let meta = match spec {
|
let kind = match spec {
|
||||||
AddressSpec::Standard => AddressMeta::new(network, AddressType::Standard),
|
AddressSpec::Legacy => AddressType::Legacy,
|
||||||
AddressSpec::Integrated(payment_id) => {
|
AddressSpec::LegacyIntegrated(payment_id) => AddressType::LegacyIntegrated(payment_id),
|
||||||
AddressMeta::new(network, AddressType::Integrated(payment_id))
|
|
||||||
}
|
|
||||||
AddressSpec::Subaddress(index) => {
|
AddressSpec::Subaddress(index) => {
|
||||||
(spend, view) = self.subaddress_keys(index);
|
(spend, view) = self.subaddress_keys(index);
|
||||||
AddressMeta::new(network, AddressType::Subaddress)
|
AddressType::Subaddress
|
||||||
}
|
}
|
||||||
AddressSpec::Featured { subaddress, payment_id, guaranteed } => {
|
AddressSpec::Featured { subaddress, payment_id, guaranteed } => {
|
||||||
if let Some(index) = subaddress {
|
if let Some(index) = subaddress {
|
||||||
(spend, view) = self.subaddress_keys(index);
|
(spend, view) = self.subaddress_keys(index);
|
||||||
}
|
}
|
||||||
AddressMeta::new(
|
AddressType::Featured { subaddress: subaddress.is_some(), payment_id, guaranteed }
|
||||||
network,
|
|
||||||
AddressType::Featured { subaddress: subaddress.is_some(), payment_id, guaranteed },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MoneroAddress::new(meta, spend, view)
|
MoneroAddress::new(network, kind, spend, view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ impl Change {
|
|||||||
// Which network doesn't matter as the derivations will all be the same
|
// Which network doesn't matter as the derivations will all be the same
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
if !guaranteed {
|
if !guaranteed {
|
||||||
AddressSpec::Standard
|
AddressSpec::Legacy
|
||||||
} else {
|
} else {
|
||||||
AddressSpec::Featured { subaddress: None, payment_id: None, guaranteed: true }
|
AddressSpec::Featured { subaddress: None, payment_id: None, guaranteed: true }
|
||||||
},
|
},
|
||||||
@@ -390,7 +390,7 @@ impl SignableTransaction {
|
|||||||
fn read_address<R: io::Read>(r: &mut R) -> io::Result<MoneroAddress> {
|
fn read_address<R: io::Read>(r: &mut R) -> io::Result<MoneroAddress> {
|
||||||
String::from_utf8(read_vec(read_byte, r)?)
|
String::from_utf8(read_vec(read_byte, r)?)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|str| MoneroAddress::from_str_raw(&str).ok())
|
.and_then(|str| MoneroAddress::from_str_with_unchecked_network(&str).ok())
|
||||||
.ok_or_else(|| io::Error::other("invalid address"))
|
.ok_or_else(|| io::Error::other("invalid address"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ impl SignableTransaction {
|
|||||||
let mut res = Vec::with_capacity(self.payments.len());
|
let mut res = Vec::with_capacity(self.payments.len());
|
||||||
for (payment, shared_key_derivations) in self.payments.iter().zip(&shared_key_derivations) {
|
for (payment, shared_key_derivations) in self.payments.iter().zip(&shared_key_derivations) {
|
||||||
let key =
|
let key =
|
||||||
(&shared_key_derivations.shared_key * ED25519_BASEPOINT_TABLE) + payment.address().spend;
|
(&shared_key_derivations.shared_key * ED25519_BASEPOINT_TABLE) + payment.address().spend();
|
||||||
res.push(Output {
|
res.push(Output {
|
||||||
key: key.compress(),
|
key: key.compress(),
|
||||||
amount: None,
|
amount: None,
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ impl SignableTransaction {
|
|||||||
let ecdh = match payment {
|
let ecdh = match payment {
|
||||||
// If we don't have the view key, use the key dedicated for this address (r A)
|
// If we don't have the view key, use the key dedicated for this address (r A)
|
||||||
InternalPayment::Payment(_, _) | InternalPayment::Change(_, None) => {
|
InternalPayment::Payment(_, _) | InternalPayment::Change(_, None) => {
|
||||||
Zeroizing::new(key_to_use.deref() * addr.view)
|
Zeroizing::new(key_to_use.deref() * addr.view())
|
||||||
}
|
}
|
||||||
// If we do have the view key, use the commitment to the key (a R)
|
// If we do have the view key, use the commitment to the key (a R)
|
||||||
InternalPayment::Change(_, Some(view)) => Zeroizing::new(view.deref() * tx_key_pub),
|
InternalPayment::Change(_, Some(view)) => Zeroizing::new(view.deref() * tx_key_pub),
|
||||||
@@ -173,7 +173,7 @@ impl SignableTransaction {
|
|||||||
// TODO: Support subaddresses as change?
|
// TODO: Support subaddresses as change?
|
||||||
debug_assert!(addr.is_subaddress());
|
debug_assert!(addr.is_subaddress());
|
||||||
|
|
||||||
return (tx_key.deref() * addr.spend, vec![]);
|
return (tx_key.deref() * addr.spend(), vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_use_additional_keys {
|
if should_use_additional_keys {
|
||||||
@@ -182,7 +182,7 @@ impl SignableTransaction {
|
|||||||
let addr = payment.address();
|
let addr = payment.address();
|
||||||
// TODO: Double check this against wallet2
|
// TODO: Double check this against wallet2
|
||||||
if addr.is_subaddress() {
|
if addr.is_subaddress() {
|
||||||
additional_keys_pub.push(additional_key.deref() * addr.spend);
|
additional_keys_pub.push(additional_key.deref() * addr.spend());
|
||||||
} else {
|
} else {
|
||||||
additional_keys_pub.push(additional_key.deref() * ED25519_BASEPOINT_TABLE)
|
additional_keys_pub.push(additional_key.deref() * ED25519_BASEPOINT_TABLE)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
|
|||||||
use monero_serai::transaction::Transaction;
|
use monero_serai::transaction::Transaction;
|
||||||
use monero_wallet::{
|
use monero_wallet::{
|
||||||
rpc::Rpc,
|
rpc::Rpc,
|
||||||
address::{AddressType, AddressMeta, MoneroAddress},
|
address::{AddressType, MoneroAddress},
|
||||||
send::Eventuality,
|
send::Eventuality,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +17,8 @@ test!(
|
|||||||
// Each have their own slight implications to eventualities
|
// Each have their own slight implications to eventualities
|
||||||
builder.add_payment(
|
builder.add_payment(
|
||||||
MoneroAddress::new(
|
MoneroAddress::new(
|
||||||
AddressMeta::new(Network::Mainnet, AddressType::Standard),
|
Network::Mainnet,
|
||||||
|
AddressType::Legacy,
|
||||||
ED25519_BASEPOINT_POINT,
|
ED25519_BASEPOINT_POINT,
|
||||||
ED25519_BASEPOINT_POINT,
|
ED25519_BASEPOINT_POINT,
|
||||||
),
|
),
|
||||||
@@ -25,7 +26,8 @@ test!(
|
|||||||
);
|
);
|
||||||
builder.add_payment(
|
builder.add_payment(
|
||||||
MoneroAddress::new(
|
MoneroAddress::new(
|
||||||
AddressMeta::new(Network::Mainnet, AddressType::Integrated([0xaa; 8])),
|
Network::Mainnet,
|
||||||
|
AddressType::LegacyIntegrated([0xaa; 8]),
|
||||||
ED25519_BASEPOINT_POINT,
|
ED25519_BASEPOINT_POINT,
|
||||||
ED25519_BASEPOINT_POINT,
|
ED25519_BASEPOINT_POINT,
|
||||||
),
|
),
|
||||||
@@ -33,7 +35,8 @@ test!(
|
|||||||
);
|
);
|
||||||
builder.add_payment(
|
builder.add_payment(
|
||||||
MoneroAddress::new(
|
MoneroAddress::new(
|
||||||
AddressMeta::new(Network::Mainnet, AddressType::Subaddress),
|
Network::Mainnet,
|
||||||
|
AddressType::Subaddress,
|
||||||
ED25519_BASEPOINT_POINT,
|
ED25519_BASEPOINT_POINT,
|
||||||
ED25519_BASEPOINT_POINT,
|
ED25519_BASEPOINT_POINT,
|
||||||
),
|
),
|
||||||
@@ -41,10 +44,8 @@ test!(
|
|||||||
);
|
);
|
||||||
builder.add_payment(
|
builder.add_payment(
|
||||||
MoneroAddress::new(
|
MoneroAddress::new(
|
||||||
AddressMeta::new(
|
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
AddressType::Featured { subaddress: false, payment_id: None, guaranteed: true },
|
AddressType::Featured { subaddress: false, payment_id: None, guaranteed: true },
|
||||||
),
|
|
||||||
ED25519_BASEPOINT_POINT,
|
ED25519_BASEPOINT_POINT,
|
||||||
ED25519_BASEPOINT_POINT,
|
ED25519_BASEPOINT_POINT,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use monero_wallet::{
|
|||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
rpc::{Rpc, FeeRate},
|
rpc::{Rpc, FeeRate},
|
||||||
ViewPair,
|
ViewPair,
|
||||||
address::{Network, AddressType, AddressSpec, AddressMeta, MoneroAddress},
|
address::{Network, AddressType, AddressSpec, MoneroAddress},
|
||||||
scan::{SpendableOutput, Scanner},
|
scan::{SpendableOutput, Scanner},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,11 +36,12 @@ pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) {
|
|||||||
(
|
(
|
||||||
spend,
|
spend,
|
||||||
ViewPair::new(spend_pub, view.clone()),
|
ViewPair::new(spend_pub, view.clone()),
|
||||||
MoneroAddress {
|
MoneroAddress::new(
|
||||||
meta: AddressMeta::new(Network::Mainnet, AddressType::Standard),
|
Network::Mainnet,
|
||||||
spend: spend_pub,
|
AddressType::Legacy,
|
||||||
view: view.deref() * ED25519_BASEPOINT_TABLE,
|
spend_pub,
|
||||||
},
|
view.deref() * ED25519_BASEPOINT_TABLE,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ pub async fn get_miner_tx_output(rpc: &SimpleRequestRpc, view: &ViewPair) -> Spe
|
|||||||
// 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
|
rpc
|
||||||
.generate_blocks(&view.address(Network::Mainnet, AddressSpec::Standard).to_string(), 60)
|
.generate_blocks(&view.address(Network::Mainnet, AddressSpec::Legacy).to_string(), 60)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -112,11 +113,12 @@ pub async fn rpc() -> SimpleRequestRpc {
|
|||||||
return rpc;
|
return rpc;
|
||||||
}
|
}
|
||||||
|
|
||||||
let addr = MoneroAddress {
|
let addr = MoneroAddress::new(
|
||||||
meta: AddressMeta::new(Network::Mainnet, AddressType::Standard),
|
Network::Mainnet,
|
||||||
spend: &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
AddressType::Legacy,
|
||||||
view: &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
||||||
}
|
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
||||||
|
)
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
// Mine 40 blocks to ensure decoy availability
|
// Mine 40 blocks to ensure decoy availability
|
||||||
@@ -222,7 +224,7 @@ macro_rules! test {
|
|||||||
|
|
||||||
let view_priv = Zeroizing::new(Scalar::random(&mut OsRng));
|
let view_priv = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||||
let view = ViewPair::new(spend_pub, view_priv.clone());
|
let view = ViewPair::new(spend_pub, view_priv.clone());
|
||||||
let addr = view.address(Network::Mainnet, AddressSpec::Standard);
|
let addr = view.address(Network::Mainnet, AddressSpec::Legacy);
|
||||||
|
|
||||||
let miner_tx = get_miner_tx_output(&rpc, &view).await;
|
let miner_tx = get_miner_tx_output(&rpc, &view).await;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ test!(
|
|||||||
|_, mut builder: Builder, _| async move {
|
|_, mut builder: Builder, _| async move {
|
||||||
let view = runner::random_address().1;
|
let view = runner::random_address().1;
|
||||||
let scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
|
let scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
|
||||||
builder.add_payment(view.address(Network::Mainnet, AddressSpec::Standard), 5);
|
builder.add_payment(view.address(Network::Mainnet, AddressSpec::Legacy), 5);
|
||||||
(builder.build().unwrap(), scanner)
|
(builder.build().unwrap(), scanner)
|
||||||
},
|
},
|
||||||
|_, tx: Transaction, _, mut state: Scanner| async move {
|
|_, tx: Transaction, _, mut state: Scanner| async move {
|
||||||
@@ -54,7 +54,8 @@ 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(view.address(Network::Mainnet, AddressSpec::Integrated(payment_id)), 5);
|
builder
|
||||||
|
.add_payment(view.address(Network::Mainnet, AddressSpec::LegacyIntegrated(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 {
|
||||||
|
|||||||
@@ -117,11 +117,11 @@ async fn from_wallet_rpc_to_self(spec: AddressSpec) {
|
|||||||
assert_eq!(output.metadata.subaddress, Some(index));
|
assert_eq!(output.metadata.subaddress, Some(index));
|
||||||
assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted([0u8; 8])));
|
assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted([0u8; 8])));
|
||||||
}
|
}
|
||||||
AddressSpec::Integrated(payment_id) => {
|
AddressSpec::LegacyIntegrated(payment_id) => {
|
||||||
assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted(payment_id)));
|
assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted(payment_id)));
|
||||||
assert_eq!(output.metadata.subaddress, None);
|
assert_eq!(output.metadata.subaddress, None);
|
||||||
}
|
}
|
||||||
AddressSpec::Standard | AddressSpec::Featured { .. } => {
|
AddressSpec::Legacy | AddressSpec::Featured { .. } => {
|
||||||
assert_eq!(output.metadata.subaddress, None);
|
assert_eq!(output.metadata.subaddress, None);
|
||||||
assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted([0u8; 8])));
|
assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted([0u8; 8])));
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ async fn from_wallet_rpc_to_self(spec: AddressSpec) {
|
|||||||
|
|
||||||
async_sequential!(
|
async_sequential!(
|
||||||
async fn receipt_of_wallet_rpc_tx_standard() {
|
async fn receipt_of_wallet_rpc_tx_standard() {
|
||||||
from_wallet_rpc_to_self(AddressSpec::Standard).await;
|
from_wallet_rpc_to_self(AddressSpec::Legacy).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receipt_of_wallet_rpc_tx_subaddress() {
|
async fn receipt_of_wallet_rpc_tx_subaddress() {
|
||||||
@@ -141,7 +141,7 @@ async_sequential!(
|
|||||||
async fn receipt_of_wallet_rpc_tx_integrated() {
|
async fn receipt_of_wallet_rpc_tx_integrated() {
|
||||||
let mut payment_id = [0u8; 8];
|
let mut payment_id = [0u8; 8];
|
||||||
OsRng.fill_bytes(&mut payment_id);
|
OsRng.fill_bytes(&mut payment_id);
|
||||||
from_wallet_rpc_to_self(AddressSpec::Integrated(payment_id)).await;
|
from_wallet_rpc_to_self(AddressSpec::LegacyIntegrated(payment_id)).await;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use scale::{Encode, Decode};
|
|||||||
|
|
||||||
use ciphersuite::{Ciphersuite, Ed25519};
|
use ciphersuite::{Ciphersuite, Ed25519};
|
||||||
|
|
||||||
use monero_wallet::address::{AddressError, Network, AddressType, AddressMeta, MoneroAddress};
|
use monero_wallet::address::{AddressError, Network, AddressType, MoneroAddress};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Address(MoneroAddress);
|
pub struct Address(MoneroAddress);
|
||||||
@@ -33,7 +33,7 @@ impl fmt::Display for Address {
|
|||||||
// SCALE-encoded variant of Monero addresses.
|
// SCALE-encoded variant of Monero addresses.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||||
enum EncodedAddressType {
|
enum EncodedAddressType {
|
||||||
Standard,
|
Legacy,
|
||||||
Subaddress,
|
Subaddress,
|
||||||
Featured(u8),
|
Featured(u8),
|
||||||
}
|
}
|
||||||
@@ -52,10 +52,9 @@ impl TryFrom<Vec<u8>> for Address {
|
|||||||
let addr = EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())?;
|
let addr = EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())?;
|
||||||
// Convert over
|
// Convert over
|
||||||
Ok(Address(MoneroAddress::new(
|
Ok(Address(MoneroAddress::new(
|
||||||
AddressMeta::new(
|
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
match addr.kind {
|
match addr.kind {
|
||||||
EncodedAddressType::Standard => AddressType::Standard,
|
EncodedAddressType::Legacy => AddressType::Legacy,
|
||||||
EncodedAddressType::Subaddress => AddressType::Subaddress,
|
EncodedAddressType::Subaddress => AddressType::Subaddress,
|
||||||
EncodedAddressType::Featured(flags) => {
|
EncodedAddressType::Featured(flags) => {
|
||||||
let subaddress = (flags & 1) != 0;
|
let subaddress = (flags & 1) != 0;
|
||||||
@@ -67,7 +66,6 @@ impl TryFrom<Vec<u8>> for Address {
|
|||||||
AddressType::Featured { subaddress, payment_id: None, guaranteed }
|
AddressType::Featured { subaddress, payment_id: None, guaranteed }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
|
||||||
Ed25519::read_G::<&[u8]>(&mut addr.spend.as_ref()).map_err(|_| ())?.0,
|
Ed25519::read_G::<&[u8]>(&mut addr.spend.as_ref()).map_err(|_| ())?.0,
|
||||||
Ed25519::read_G::<&[u8]>(&mut addr.view.as_ref()).map_err(|_| ())?.0,
|
Ed25519::read_G::<&[u8]>(&mut addr.view.as_ref()).map_err(|_| ())?.0,
|
||||||
)))
|
)))
|
||||||
@@ -85,16 +83,19 @@ impl Into<MoneroAddress> for Address {
|
|||||||
impl Into<Vec<u8>> for Address {
|
impl Into<Vec<u8>> for Address {
|
||||||
fn into(self) -> Vec<u8> {
|
fn into(self) -> Vec<u8> {
|
||||||
EncodedAddress {
|
EncodedAddress {
|
||||||
kind: match self.0.meta.kind {
|
kind: match self.0.kind() {
|
||||||
AddressType::Standard => EncodedAddressType::Standard,
|
AddressType::Legacy => EncodedAddressType::Legacy,
|
||||||
|
AddressType::LegacyIntegrated(_) => {
|
||||||
|
panic!("integrated address became Serai Monero address")
|
||||||
|
}
|
||||||
AddressType::Subaddress => EncodedAddressType::Subaddress,
|
AddressType::Subaddress => EncodedAddressType::Subaddress,
|
||||||
AddressType::Integrated(_) => panic!("integrated address became Serai Monero address"),
|
AddressType::Featured { subaddress, payment_id, guaranteed } => {
|
||||||
AddressType::Featured { subaddress, payment_id: _, guaranteed } => {
|
debug_assert!(payment_id.is_none());
|
||||||
EncodedAddressType::Featured(u8::from(subaddress) + (u8::from(guaranteed) << 2))
|
EncodedAddressType::Featured(u8::from(*subaddress) + (u8::from(*guaranteed) << 2))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
spend: self.0.spend.compress().0,
|
spend: self.0.spend().compress().0,
|
||||||
view: self.0.view.compress().0,
|
view: self.0.view().compress().0,
|
||||||
}
|
}
|
||||||
.encode()
|
.encode()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user