mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 21:49:26 +00:00
Smash out Monero addresses
This commit is contained in:
30
Cargo.lock
generated
30
Cargo.lock
generated
@@ -880,16 +880,6 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base58-monero"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "978e81a45367d2409ecd33369a45dda2e9a3ca516153ec194de1fbda4b9fb79d"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror",
|
|
||||||
"tiny-keccak",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base58ck"
|
name = "base58ck"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -4751,6 +4741,23 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "monero-address"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"curve25519-dalek",
|
||||||
|
"hex",
|
||||||
|
"hex-literal",
|
||||||
|
"monero-io",
|
||||||
|
"monero-primitives",
|
||||||
|
"rand_core",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"std-shims",
|
||||||
|
"thiserror",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "monero-borromean"
|
name = "monero-borromean"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -4910,14 +4917,13 @@ name = "monero-wallet"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base58-monero",
|
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"dalek-ff-group",
|
"dalek-ff-group",
|
||||||
"flexible-transcript",
|
"flexible-transcript",
|
||||||
"group",
|
"group",
|
||||||
"hex",
|
"hex",
|
||||||
"hex-literal",
|
|
||||||
"modular-frost",
|
"modular-frost",
|
||||||
|
"monero-address",
|
||||||
"monero-rpc",
|
"monero-rpc",
|
||||||
"monero-serai",
|
"monero-serai",
|
||||||
"monero-simple-request-rpc",
|
"monero-simple-request-rpc",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ members = [
|
|||||||
"coins/monero",
|
"coins/monero",
|
||||||
"coins/monero/rpc",
|
"coins/monero/rpc",
|
||||||
"coins/monero/rpc/simple-request",
|
"coins/monero/rpc/simple-request",
|
||||||
|
"coins/monero/wallet/address",
|
||||||
"coins/monero/wallet",
|
"coins/monero/wallet",
|
||||||
"coins/monero/wallet/seed",
|
"coins/monero/wallet/seed",
|
||||||
"coins/monero/wallet/polyseed",
|
"coins/monero/wallet/polyseed",
|
||||||
|
|||||||
@@ -39,15 +39,14 @@ dalek-ff-group = { path = "../../../crypto/dalek-ff-group", version = "0.4", def
|
|||||||
frost = { package = "modular-frost", path = "../../../crypto/frost", default-features = false, features = ["ed25519"], optional = true }
|
frost = { package = "modular-frost", path = "../../../crypto/frost", default-features = false, features = ["ed25519"], optional = true }
|
||||||
|
|
||||||
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||||
base58-monero = { version = "2", default-features = false, features = ["check"] }
|
|
||||||
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
|
|
||||||
serde_json = { version = "1", default-features = false, features = ["alloc"] }
|
|
||||||
|
|
||||||
monero-serai = { path = "..", default-features = false }
|
monero-serai = { path = "..", default-features = false }
|
||||||
monero-rpc = { path = "../rpc", default-features = false }
|
monero-rpc = { path = "../rpc", default-features = false }
|
||||||
|
monero-address = { path = "./address", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.4"
|
serde = { version = "1", default-features = false, features = ["derive", "alloc", "std"] }
|
||||||
|
serde_json = { version = "1", default-features = false, features = ["alloc", "std"] }
|
||||||
|
|
||||||
frost = { package = "modular-frost", path = "../../../crypto/frost", default-features = false, features = ["ed25519", "tests"] }
|
frost = { package = "modular-frost", path = "../../../crypto/frost", default-features = false, features = ["ed25519", "tests"] }
|
||||||
|
|
||||||
@@ -68,13 +67,9 @@ std = [
|
|||||||
"rand_chacha/std",
|
"rand_chacha/std",
|
||||||
"rand_distr/std",
|
"rand_distr/std",
|
||||||
|
|
||||||
"hex/std",
|
|
||||||
"base58-monero/std",
|
|
||||||
"serde/std",
|
|
||||||
"serde_json/std",
|
|
||||||
|
|
||||||
"monero-serai/std",
|
"monero-serai/std",
|
||||||
"monero-rpc/std",
|
"monero-rpc/std",
|
||||||
|
"monero-address/std",
|
||||||
]
|
]
|
||||||
compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-serai/compile-time-generators"]
|
compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-serai/compile-time-generators"]
|
||||||
multisig = ["transcript", "dalek-ff-group", "frost", "monero-serai/multisig", "std"]
|
multisig = ["transcript", "dalek-ff-group", "frost", "monero-serai/multisig", "std"]
|
||||||
|
|||||||
47
coins/monero/wallet/address/Cargo.toml
Normal file
47
coins/monero/wallet/address/Cargo.toml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
[package]
|
||||||
|
name = "monero-address"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Monero addresses and associated functionality"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/wallet/address"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.79"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
std-shims = { path = "../../../../common/std-shims", version = "^0.1.1", default-features = false }
|
||||||
|
|
||||||
|
thiserror = { version = "1", default-features = false, optional = true }
|
||||||
|
|
||||||
|
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"] }
|
||||||
|
|
||||||
|
monero-io = { path = "../../io", default-features = false }
|
||||||
|
monero-primitives = { path = "../../primitives", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
hex-literal = { version = "0.4", default-features = false }
|
||||||
|
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
||||||
|
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
|
||||||
|
serde_json = { version = "1", default-features = false, features = ["alloc"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
std = [
|
||||||
|
"std-shims/std",
|
||||||
|
|
||||||
|
"thiserror",
|
||||||
|
|
||||||
|
"zeroize/std",
|
||||||
|
|
||||||
|
"monero-io/std",
|
||||||
|
]
|
||||||
|
default = ["std"]
|
||||||
21
coins/monero/wallet/address/LICENSE
Normal file
21
coins/monero/wallet/address/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022-2024 Luke Parker
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
6
coins/monero/wallet/address/README.md
Normal file
6
coins/monero/wallet/address/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Polyseed
|
||||||
|
|
||||||
|
Rust implementation of [Polyseed](https://github.com/tevador/polyseed).
|
||||||
|
|
||||||
|
This library is usable under no-std when the `std` feature (on by default) is
|
||||||
|
disabled.
|
||||||
104
coins/monero/wallet/address/src/base58check.rs
Normal file
104
coins/monero/wallet/address/src/base58check.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
use monero_primitives::keccak256;
|
||||||
|
|
||||||
|
const ALPHABET_LEN: u64 = 58;
|
||||||
|
const ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
pub(crate) const BLOCK_LEN: usize = 8;
|
||||||
|
const ENCODED_BLOCK_LEN: usize = 11;
|
||||||
|
|
||||||
|
const CHECKSUM_LEN: usize = 4;
|
||||||
|
|
||||||
|
// The maximum possible length of an encoding of this many bytes
|
||||||
|
//
|
||||||
|
// This is used for determining padding/how many bytes an encoding actually uses
|
||||||
|
pub(crate) fn encoded_len_for_bytes(bytes: usize) -> usize {
|
||||||
|
let bits = u64::try_from(bytes).expect("length exceeded 2**64") * 8;
|
||||||
|
let mut max = if bits == 64 { u64::MAX } else { (1 << bits) - 1 };
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while max != 0 {
|
||||||
|
max /= ALPHABET_LEN;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode an arbitrary-length stream of data
|
||||||
|
pub(crate) fn encode(bytes: &[u8]) -> String {
|
||||||
|
let mut res = String::with_capacity(bytes.len().div_ceil(BLOCK_LEN) * ENCODED_BLOCK_LEN);
|
||||||
|
|
||||||
|
for chunk in bytes.chunks(BLOCK_LEN) {
|
||||||
|
// Convert to a u64
|
||||||
|
let mut fixed_len_chunk = [0; BLOCK_LEN];
|
||||||
|
fixed_len_chunk[(BLOCK_LEN - chunk.len()) ..].copy_from_slice(chunk);
|
||||||
|
let mut val = u64::from_be_bytes(fixed_len_chunk);
|
||||||
|
|
||||||
|
// Convert to the base58 encoding
|
||||||
|
let mut chunk_str = [char::from(ALPHABET[0]); ENCODED_BLOCK_LEN];
|
||||||
|
let mut i = 0;
|
||||||
|
while val > 0 {
|
||||||
|
chunk_str[i] = ALPHABET[usize::try_from(val % ALPHABET_LEN)
|
||||||
|
.expect("ALPHABET_LEN exceeds usize despite being a usize")]
|
||||||
|
.into();
|
||||||
|
i += 1;
|
||||||
|
val /= ALPHABET_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only take used bytes, and since we put the LSBs in the first byte, reverse the byte order
|
||||||
|
for c in chunk_str.into_iter().take(encoded_len_for_bytes(chunk.len())).rev() {
|
||||||
|
res.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an arbitrary-length stream of data
|
||||||
|
pub(crate) fn decode(data: &str) -> Option<Vec<u8>> {
|
||||||
|
let mut res = Vec::with_capacity((data.len() / ENCODED_BLOCK_LEN) * BLOCK_LEN);
|
||||||
|
|
||||||
|
for chunk in data.as_bytes().chunks(ENCODED_BLOCK_LEN) {
|
||||||
|
// Convert the chunk back to a u64
|
||||||
|
let mut sum = 0u64;
|
||||||
|
for this_char in chunk {
|
||||||
|
sum = sum.checked_mul(ALPHABET_LEN)?;
|
||||||
|
sum += u64::try_from(ALPHABET.iter().position(|a| a == this_char)?)
|
||||||
|
.expect("alphabet len exceeded 2**64");
|
||||||
|
}
|
||||||
|
|
||||||
|
// From the size of the encoding, determine the size of the bytes
|
||||||
|
let mut used_bytes = None;
|
||||||
|
for i in 1 ..= BLOCK_LEN {
|
||||||
|
if encoded_len_for_bytes(i) == chunk.len() {
|
||||||
|
used_bytes = Some(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only push on the used bytes
|
||||||
|
res.extend(&sum.to_be_bytes()[(BLOCK_LEN - used_bytes.unwrap()) ..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode an arbitrary-length stream of data, with a checksum
|
||||||
|
pub(crate) fn encode_check(mut data: Vec<u8>) -> String {
|
||||||
|
let checksum = keccak256(&data);
|
||||||
|
data.extend(&checksum[.. CHECKSUM_LEN]);
|
||||||
|
encode(&data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an arbitrary-length stream of data, with a checksum
|
||||||
|
pub(crate) fn decode_check(data: &str) -> Option<Vec<u8>> {
|
||||||
|
if data.len() < CHECKSUM_LEN {
|
||||||
|
None?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = decode(data)?;
|
||||||
|
let checksum_pos = res.len() - CHECKSUM_LEN;
|
||||||
|
if keccak256(&res[.. checksum_pos])[.. CHECKSUM_LEN] != res[checksum_pos ..] {
|
||||||
|
None?;
|
||||||
|
}
|
||||||
|
res.truncate(checksum_pos);
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
// #![deny(missing_docs)] // TODO
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use core::{marker::PhantomData, fmt};
|
use core::{marker::PhantomData, fmt};
|
||||||
use std_shims::string::ToString;
|
use std_shims::string::ToString;
|
||||||
|
|
||||||
@@ -5,9 +10,13 @@ use zeroize::Zeroize;
|
|||||||
|
|
||||||
use curve25519_dalek::edwards::EdwardsPoint;
|
use curve25519_dalek::edwards::EdwardsPoint;
|
||||||
|
|
||||||
use monero_serai::io::decompress_point;
|
use monero_io::decompress_point;
|
||||||
|
|
||||||
use base58_monero::base58::{encode_check, decode_check};
|
mod base58check;
|
||||||
|
use base58check::{encode_check, decode_check};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
/// The network this address is for.
|
/// The network this address is for.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
@@ -226,7 +235,7 @@ impl<B: AddressBytes> fmt::Display for Address<B> {
|
|||||||
if let Some(id) = self.meta.kind.payment_id() {
|
if let Some(id) = self.meta.kind.payment_id() {
|
||||||
data.extend(id);
|
data.extend(id);
|
||||||
}
|
}
|
||||||
write!(f, "{}", encode_check(&data).unwrap())
|
write!(f, "{}", encode_check(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +245,7 @@ impl<B: AddressBytes> Address<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_str_raw(s: &str) -> Result<Self, AddressError> {
|
pub fn from_str_raw(s: &str) -> Result<Self, AddressError> {
|
||||||
let raw = decode_check(s).map_err(|_| AddressError::InvalidEncoding)?;
|
let raw = decode_check(s).ok_or(AddressError::InvalidEncoding)?;
|
||||||
if raw.len() < (1 + 32 + 32) {
|
if raw.len() < (1 + 32 + 32) {
|
||||||
Err(AddressError::InvalidLength)?;
|
Err(AddressError::InvalidLength)?;
|
||||||
}
|
}
|
||||||
@@ -4,9 +4,9 @@ use rand_core::{RngCore, OsRng};
|
|||||||
|
|
||||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
||||||
|
|
||||||
use monero_serai::io::decompress_point;
|
use monero_io::decompress_point;
|
||||||
|
|
||||||
use crate::address::{Network, AddressType, AddressMeta, MoneroAddress};
|
use crate::{Network, AddressType, AddressMeta, 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");
|
||||||
@@ -27,6 +27,41 @@ const SUBADDRESS: &str =
|
|||||||
|
|
||||||
const FEATURED_JSON: &str = include_str!("vectors/featured_addresses.json");
|
const FEATURED_JSON: &str = include_str!("vectors/featured_addresses.json");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encoded_len_for_bytes() {
|
||||||
|
// For an encoding of length `l`, we prune to the amount of bytes which encodes with length `l`
|
||||||
|
// This assumes length `l` -> amount of bytes has a singular answer, which is tested here
|
||||||
|
use crate::base58check::*;
|
||||||
|
let mut set = std::collections::HashSet::new();
|
||||||
|
for i in 0 .. BLOCK_LEN {
|
||||||
|
set.insert(encoded_len_for_bytes(i));
|
||||||
|
}
|
||||||
|
assert_eq!(set.len(), BLOCK_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base58check() {
|
||||||
|
use crate::base58check::*;
|
||||||
|
|
||||||
|
assert_eq!(encode(&[]), String::new());
|
||||||
|
assert!(decode("").unwrap().is_empty());
|
||||||
|
|
||||||
|
let full_block = &[1, 2, 3, 4, 5, 6, 7, 8];
|
||||||
|
assert_eq!(&decode(&encode(full_block)).unwrap(), full_block);
|
||||||
|
|
||||||
|
let partial_block = &[1, 2, 3];
|
||||||
|
assert_eq!(&decode(&encode(partial_block)).unwrap(), partial_block);
|
||||||
|
|
||||||
|
let max_encoded_block = &[u8::MAX; 8];
|
||||||
|
assert_eq!(&decode(&encode(max_encoded_block)).unwrap(), max_encoded_block);
|
||||||
|
|
||||||
|
let max_decoded_block = "zzzzzzzzzzz";
|
||||||
|
assert!(decode(max_decoded_block).is_none());
|
||||||
|
|
||||||
|
let full_and_partial_block = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||||
|
assert_eq!(&decode(&encode(full_and_partial_block)).unwrap(), full_and_partial_block);
|
||||||
|
}
|
||||||
|
|
||||||
#[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();
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use core::{ops::Deref, fmt};
|
use core::{ops::Deref, fmt};
|
||||||
use std_shims::{
|
use std_shims::{
|
||||||
sync::OnceLock,
|
sync::OnceLock,
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ pub use monero_rpc as rpc;
|
|||||||
pub mod extra;
|
pub mod extra;
|
||||||
pub(crate) use extra::{PaymentId, ExtraField, Extra};
|
pub(crate) use extra::{PaymentId, ExtraField, Extra};
|
||||||
|
|
||||||
/// Address encoding and decoding functionality.
|
pub use monero_address as address;
|
||||||
pub mod address;
|
|
||||||
use address::{Network, AddressType, SubaddressIndex, AddressSpec, AddressMeta, MoneroAddress};
|
use address::{Network, AddressType, SubaddressIndex, AddressSpec, AddressMeta, MoneroAddress};
|
||||||
|
|
||||||
mod scan;
|
mod scan;
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
mod address;
|
|
||||||
mod extra;
|
mod extra;
|
||||||
|
|||||||
Reference in New Issue
Block a user