1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![deny(missing_docs)]
4#![cfg_attr(not(feature = "std"), no_std)]
5
6use core::fmt::{self, Write};
7use std_shims::{
8 vec,
9 string::{String, ToString},
10};
11
12use zeroize::Zeroize;
13
14use curve25519_dalek::EdwardsPoint;
15
16use monero_io::*;
17
18mod base58check;
19use base58check::{encode_check, decode_check};
20
21#[cfg(test)]
22mod tests;
23
24#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
29pub enum AddressType {
30 Legacy,
32 LegacyIntegrated([u8; 8]),
34 Subaddress,
38 Featured {
47 subaddress: bool,
49 payment_id: Option<[u8; 8]>,
51 guaranteed: bool,
57 },
58}
59
60impl AddressType {
61 pub fn is_subaddress(&self) -> bool {
63 matches!(self, AddressType::Subaddress) ||
64 matches!(self, AddressType::Featured { subaddress: true, .. })
65 }
66
67 pub fn payment_id(&self) -> Option<[u8; 8]> {
69 if let AddressType::LegacyIntegrated(id) = self {
70 Some(*id)
71 } else if let AddressType::Featured { payment_id, .. } = self {
72 *payment_id
73 } else {
74 None
75 }
76 }
77
78 pub fn is_guaranteed(&self) -> bool {
84 matches!(self, AddressType::Featured { guaranteed: true, .. })
85 }
86}
87
88#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
92pub struct SubaddressIndex {
93 account: u32,
94 address: u32,
95}
96
97impl SubaddressIndex {
98 pub const fn new(account: u32, address: u32) -> Option<SubaddressIndex> {
100 if (account == 0) && (address == 0) {
101 return None;
102 }
103 Some(SubaddressIndex { account, address })
104 }
105
106 pub const fn account(&self) -> u32 {
108 self.account
109 }
110
111 pub const fn address(&self) -> u32 {
113 self.address
114 }
115}
116
117#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
121pub struct AddressBytes {
122 legacy: u8,
123 legacy_integrated: u8,
124 subaddress: u8,
125 featured: u8,
126}
127
128impl AddressBytes {
129 pub const fn new(
131 legacy: u8,
132 legacy_integrated: u8,
133 subaddress: u8,
134 featured: u8,
135 ) -> Option<Self> {
136 if (legacy == legacy_integrated) || (legacy == subaddress) || (legacy == featured) {
137 return None;
138 }
139 if (legacy_integrated == subaddress) || (legacy_integrated == featured) {
140 return None;
141 }
142 if subaddress == featured {
143 return None;
144 }
145 Some(AddressBytes { legacy, legacy_integrated, subaddress, featured })
146 }
147
148 const fn to_const_generic(self) -> u32 {
149 ((self.legacy as u32) << 24) +
150 ((self.legacy_integrated as u32) << 16) +
151 ((self.subaddress as u32) << 8) +
152 (self.featured as u32)
153 }
154
155 #[allow(clippy::cast_possible_truncation)]
156 const fn from_const_generic(const_generic: u32) -> Self {
157 let legacy = (const_generic >> 24) as u8;
158 let legacy_integrated = ((const_generic >> 16) & (u8::MAX as u32)) as u8;
159 let subaddress = ((const_generic >> 8) & (u8::MAX as u32)) as u8;
160 let featured = (const_generic & (u8::MAX as u32)) as u8;
161
162 AddressBytes { legacy, legacy_integrated, subaddress, featured }
163 }
164}
165
166const MONERO_MAINNET_BYTES: AddressBytes = match AddressBytes::new(18, 19, 42, 70) {
170 Some(bytes) => bytes,
171 None => panic!("mainnet byte constants conflicted"),
172};
173const MONERO_STAGENET_BYTES: AddressBytes = match AddressBytes::new(24, 25, 36, 86) {
176 Some(bytes) => bytes,
177 None => panic!("stagenet byte constants conflicted"),
178};
179const MONERO_TESTNET_BYTES: AddressBytes = match AddressBytes::new(53, 54, 63, 111) {
182 Some(bytes) => bytes,
183 None => panic!("testnet byte constants conflicted"),
184};
185
186#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
188pub enum Network {
189 Mainnet,
191 Stagenet,
195 Testnet,
199}
200
201#[derive(Clone, Copy, PartialEq, Eq, Debug)]
203#[cfg_attr(feature = "std", derive(thiserror::Error))]
204pub enum AddressError {
205 #[cfg_attr(feature = "std", error("invalid byte for the address's network/type ({0})"))]
207 InvalidTypeByte(u8),
208 #[cfg_attr(feature = "std", error("invalid address encoding"))]
210 InvalidEncoding,
211 #[cfg_attr(feature = "std", error("invalid length"))]
213 InvalidLength,
214 #[cfg_attr(feature = "std", error("invalid key"))]
216 InvalidKey,
217 #[cfg_attr(feature = "std", error("unknown features"))]
219 UnknownFeatures(u64),
220 #[cfg_attr(
222 feature = "std",
223 error("different network ({actual:?}) than expected ({expected:?})")
224 )]
225 DifferentNetwork {
226 expected: Network,
228 actual: Network,
230 },
231}
232
233#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
237pub struct NetworkedAddressBytes {
238 mainnet: AddressBytes,
239 stagenet: AddressBytes,
240 testnet: AddressBytes,
241}
242
243impl NetworkedAddressBytes {
244 pub const fn new(
246 mainnet: AddressBytes,
247 stagenet: AddressBytes,
248 testnet: AddressBytes,
249 ) -> Option<Self> {
250 let res = NetworkedAddressBytes { mainnet, stagenet, testnet };
251 let all_bytes = res.to_const_generic();
252
253 let mut i = 0;
254 while i < 12 {
255 let this_byte = (all_bytes >> (32 + (i * 8))) & (u8::MAX as u128);
256
257 let mut j = 0;
258 while j < 12 {
259 if i == j {
260 j += 1;
261 continue;
262 }
263 let other_byte = (all_bytes >> (32 + (j * 8))) & (u8::MAX as u128);
264 if this_byte == other_byte {
265 return None;
266 }
267
268 j += 1;
269 }
270
271 i += 1;
272 }
273
274 Some(res)
275 }
276
277 pub const fn to_const_generic(self) -> u128 {
281 ((self.mainnet.to_const_generic() as u128) << 96) +
282 ((self.stagenet.to_const_generic() as u128) << 64) +
283 ((self.testnet.to_const_generic() as u128) << 32)
284 }
285
286 #[allow(clippy::cast_possible_truncation)]
287 const fn from_const_generic(const_generic: u128) -> Self {
288 let mainnet = AddressBytes::from_const_generic((const_generic >> 96) as u32);
289 let stagenet =
290 AddressBytes::from_const_generic(((const_generic >> 64) & (u32::MAX as u128)) as u32);
291 let testnet =
292 AddressBytes::from_const_generic(((const_generic >> 32) & (u32::MAX as u128)) as u32);
293
294 NetworkedAddressBytes { mainnet, stagenet, testnet }
295 }
296
297 fn network(&self, network: Network) -> &AddressBytes {
298 match network {
299 Network::Mainnet => &self.mainnet,
300 Network::Stagenet => &self.stagenet,
301 Network::Testnet => &self.testnet,
302 }
303 }
304
305 fn byte(&self, network: Network, kind: AddressType) -> u8 {
306 let address_bytes = self.network(network);
307
308 match kind {
309 AddressType::Legacy => address_bytes.legacy,
310 AddressType::LegacyIntegrated(_) => address_bytes.legacy_integrated,
311 AddressType::Subaddress => address_bytes.subaddress,
312 AddressType::Featured { .. } => address_bytes.featured,
313 }
314 }
315
316 fn metadata_from_byte(&self, byte: u8) -> Result<(Network, AddressType), AddressError> {
318 let mut meta = None;
319 for network in [Network::Mainnet, Network::Testnet, Network::Stagenet] {
320 let address_bytes = self.network(network);
321 if let Some(kind) = match byte {
322 _ if byte == address_bytes.legacy => Some(AddressType::Legacy),
323 _ if byte == address_bytes.legacy_integrated => Some(AddressType::LegacyIntegrated([0; 8])),
324 _ if byte == address_bytes.subaddress => Some(AddressType::Subaddress),
325 _ if byte == address_bytes.featured => {
326 Some(AddressType::Featured { subaddress: false, payment_id: None, guaranteed: false })
327 }
328 _ => None,
329 } {
330 meta = Some((network, kind));
331 break;
332 }
333 }
334
335 meta.ok_or(AddressError::InvalidTypeByte(byte))
336 }
337}
338
339pub const MONERO_BYTES: NetworkedAddressBytes = match NetworkedAddressBytes::new(
341 MONERO_MAINNET_BYTES,
342 MONERO_STAGENET_BYTES,
343 MONERO_TESTNET_BYTES,
344) {
345 Some(bytes) => bytes,
346 None => panic!("Monero network byte constants conflicted"),
347};
348
349#[derive(Clone, Copy, PartialEq, Eq, Zeroize)]
351pub struct Address<const ADDRESS_BYTES: u128> {
352 network: Network,
353 kind: AddressType,
354 spend: EdwardsPoint,
355 view: EdwardsPoint,
356}
357
358impl<const ADDRESS_BYTES: u128> fmt::Debug for Address<ADDRESS_BYTES> {
359 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
360 let hex = |bytes: &[u8]| -> String {
361 let mut res = String::with_capacity(2 + (2 * bytes.len()));
362 res.push_str("0x");
363 for b in bytes {
364 write!(&mut res, "{b:02x}").unwrap();
365 }
366 res
367 };
368
369 fmt
370 .debug_struct("Address")
371 .field("network", &self.network)
372 .field("kind", &self.kind)
373 .field("spend", &hex(&self.spend.compress().to_bytes()))
374 .field("view", &hex(&self.view.compress().to_bytes()))
375 .field("(address)", &self.to_string())
377 .finish()
378 }
379}
380
381impl<const ADDRESS_BYTES: u128> fmt::Display for Address<ADDRESS_BYTES> {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 let address_bytes: NetworkedAddressBytes =
384 NetworkedAddressBytes::from_const_generic(ADDRESS_BYTES);
385
386 let mut data = vec![address_bytes.byte(self.network, self.kind)];
387 data.extend(self.spend.compress().to_bytes());
388 data.extend(self.view.compress().to_bytes());
389 if let AddressType::Featured { subaddress, payment_id, guaranteed } = self.kind {
390 let features_uint =
391 (u8::from(guaranteed) << 2) + (u8::from(payment_id.is_some()) << 1) + u8::from(subaddress);
392 write_varint(&features_uint, &mut data).unwrap();
393 }
394 if let Some(id) = self.kind.payment_id() {
395 data.extend(id);
396 }
397 write!(f, "{}", encode_check(data))
398 }
399}
400
401impl<const ADDRESS_BYTES: u128> Address<ADDRESS_BYTES> {
402 pub fn new(network: Network, kind: AddressType, spend: EdwardsPoint, view: EdwardsPoint) -> Self {
404 Address { network, kind, spend, view }
405 }
406
407 pub fn from_str_with_unchecked_network(s: &str) -> Result<Self, AddressError> {
409 let raw = decode_check(s).ok_or(AddressError::InvalidEncoding)?;
410 let mut raw = raw.as_slice();
411
412 let address_bytes: NetworkedAddressBytes =
413 NetworkedAddressBytes::from_const_generic(ADDRESS_BYTES);
414 let (network, mut kind) = address_bytes
415 .metadata_from_byte(read_byte(&mut raw).map_err(|_| AddressError::InvalidLength)?)?;
416 let spend = read_point(&mut raw).map_err(|_| AddressError::InvalidKey)?;
417 let view = read_point(&mut raw).map_err(|_| AddressError::InvalidKey)?;
418
419 if matches!(kind, AddressType::Featured { .. }) {
420 let features = read_varint::<_, u64>(&mut raw).map_err(|_| AddressError::InvalidLength)?;
421 if (features >> 3) != 0 {
422 Err(AddressError::UnknownFeatures(features))?;
423 }
424
425 let subaddress = (features & 1) == 1;
426 let integrated = ((features >> 1) & 1) == 1;
427 let guaranteed = ((features >> 2) & 1) == 1;
428
429 kind =
430 AddressType::Featured { subaddress, payment_id: integrated.then_some([0; 8]), guaranteed };
431 }
432
433 match kind {
435 AddressType::LegacyIntegrated(ref mut id) |
436 AddressType::Featured { payment_id: Some(ref mut id), .. } => {
437 *id = read_bytes(&mut raw).map_err(|_| AddressError::InvalidLength)?;
438 }
439 _ => {}
440 };
441
442 if !raw.is_empty() {
443 Err(AddressError::InvalidLength)?;
444 }
445
446 Ok(Address { network, kind, spend, view })
447 }
448
449 pub fn from_str(network: Network, s: &str) -> Result<Self, AddressError> {
454 Self::from_str_with_unchecked_network(s).and_then(|addr| {
455 if addr.network == network {
456 Ok(addr)
457 } else {
458 Err(AddressError::DifferentNetwork { actual: addr.network, expected: network })?
459 }
460 })
461 }
462
463 pub fn network(&self) -> Network {
465 self.network
466 }
467
468 pub fn kind(&self) -> &AddressType {
470 &self.kind
471 }
472
473 pub fn is_subaddress(&self) -> bool {
475 self.kind.is_subaddress()
476 }
477
478 pub fn payment_id(&self) -> Option<[u8; 8]> {
480 self.kind.payment_id()
481 }
482
483 pub fn is_guaranteed(&self) -> bool {
489 self.kind.is_guaranteed()
490 }
491
492 pub fn spend(&self) -> EdwardsPoint {
494 self.spend
495 }
496
497 pub fn view(&self) -> EdwardsPoint {
499 self.view
500 }
501}
502
503pub type MoneroAddress = Address<{ MONERO_BYTES.to_const_generic() }>;