mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
no-std support for monero-serai (#311)
* Move monero-serai from std to std-shims, where possible * no-std fixes * Make the HttpRpc its own feature, thiserror only on std * Drop monero-rs's epee for a homegrown one We only need it for a single function. While I tried jeffro's, it didn't work out of the box, had three unimplemented!s, and is no where near viable for no_std. Fixes #182, though should be further tested. * no-std monero-serai * Allow base58-monero via git * cargo fmt
This commit is contained in:
23
Cargo.lock
generated
23
Cargo.lock
generated
@@ -529,6 +529,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9d079cdf47e1ca75554200bb2f30bff5a5af16964cac4a566b18de9a5d48db2b"
|
checksum = "9d079cdf47e1ca75554200bb2f30bff5a5af16964cac4a566b18de9a5d48db2b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base58-monero"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "git+https://github.com/monero-rs/base58-monero?rev=5045e8d2b817b3b6c1190661f504e879bc769c29#5045e8d2b817b3b6c1190661f504e879bc769c29"
|
||||||
|
dependencies = [
|
||||||
"tiny-keccak",
|
"tiny-keccak",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5086,7 +5093,7 @@ version = "0.17.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "403883d12972e916dd9754cdb90c25441a9abcf435f8e09c3146de100150eeb0"
|
checksum = "403883d12972e916dd9754cdb90c25441a9abcf435f8e09c3146de100150eeb0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base58-monero",
|
"base58-monero 1.0.0",
|
||||||
"curve25519-dalek 3.2.0",
|
"curve25519-dalek 3.2.0",
|
||||||
"fixed-hash 0.7.0",
|
"fixed-hash 0.7.0",
|
||||||
"hex",
|
"hex",
|
||||||
@@ -5098,16 +5105,6 @@ dependencies = [
|
|||||||
"tiny-keccak",
|
"tiny-keccak",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "monero-epee-bin-serde"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1f8a3f7f7ef5bb1fd6c953be9187e48df8cc1a0ffc7d94f9fbabd4a23e37321e"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "monero-generators"
|
name = "monero-generators"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -5145,7 +5142,7 @@ name = "monero-serai"
|
|||||||
version = "0.1.4-alpha"
|
version = "0.1.4-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base58-monero",
|
"base58-monero 1.1.0",
|
||||||
"crc",
|
"crc",
|
||||||
"curve25519-dalek 3.2.0",
|
"curve25519-dalek 3.2.0",
|
||||||
"dalek-ff-group",
|
"dalek-ff-group",
|
||||||
@@ -5157,7 +5154,6 @@ dependencies = [
|
|||||||
"hex",
|
"hex",
|
||||||
"hex-literal 0.4.1",
|
"hex-literal 0.4.1",
|
||||||
"modular-frost",
|
"modular-frost",
|
||||||
"monero-epee-bin-serde",
|
|
||||||
"monero-generators",
|
"monero-generators",
|
||||||
"monero-rpc",
|
"monero-rpc",
|
||||||
"multiexp",
|
"multiexp",
|
||||||
@@ -8668,6 +8664,7 @@ dependencies = [
|
|||||||
"flexible-transcript",
|
"flexible-transcript",
|
||||||
"minimal-ed448",
|
"minimal-ed448",
|
||||||
"monero-generators",
|
"monero-generators",
|
||||||
|
"monero-serai",
|
||||||
"multiexp",
|
"multiexp",
|
||||||
"schnorr-signatures",
|
"schnorr-signatures",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,47 +14,51 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
std-shims = { path = "../../common/std-shims", version = "0.1", default-features = false }
|
std-shims = { path = "../../common/std-shims", version = "0.1", default-features = false }
|
||||||
|
|
||||||
futures = "0.3"
|
async-trait = { version = "0.1", default-features = false }
|
||||||
|
thiserror = { version = "1", optional = true }
|
||||||
|
|
||||||
async-trait = "0.1"
|
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||||
thiserror = "1"
|
subtle = { version = "^2.4", default-features = false }
|
||||||
|
|
||||||
rand_core = "0.6"
|
rand_core = { version = "0.6", default-features = false }
|
||||||
rand_chacha = "0.3"
|
# Used to send transactions
|
||||||
rand = "0.8"
|
rand = { version = "0.8", default-features = false }
|
||||||
rand_distr = "0.4"
|
rand_chacha = { version = "0.3", default-features = false }
|
||||||
|
# Used to select decoys
|
||||||
|
rand_distr = { version = "0.4", default-features = false }
|
||||||
|
|
||||||
zeroize = { version = "^1.5", features = ["zeroize_derive"] }
|
crc = { version = "3", default-features = false }
|
||||||
subtle = "^2.4"
|
sha3 = { version = "0.10", default-features = false }
|
||||||
|
|
||||||
crc = "3"
|
curve25519-dalek = { version = "^3.2", default-features = false }
|
||||||
sha3 = "0.10"
|
|
||||||
|
|
||||||
curve25519-dalek = { version = "^3.2", features = ["std"] }
|
# Used for the hash to curve, along with the more complicated proofs
|
||||||
|
group = { version = "0.13", default-features = false }
|
||||||
|
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3", default-features = false }
|
||||||
|
multiexp = { path = "../../crypto/multiexp", version = "0.3", default-features = false, features = ["batch"] }
|
||||||
|
|
||||||
group = "0.13"
|
# Needed for multisig
|
||||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3" }
|
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], optional = true }
|
||||||
multiexp = { path = "../../crypto/multiexp", version = "0.3", features = ["batch"] }
|
|
||||||
|
|
||||||
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.3", features = ["recommended"], optional = true }
|
|
||||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.7", features = ["ed25519"], optional = true }
|
|
||||||
dleq = { path = "../../crypto/dleq", version = "0.3", features = ["serialize"], optional = true }
|
dleq = { path = "../../crypto/dleq", version = "0.3", features = ["serialize"], optional = true }
|
||||||
|
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.7", features = ["ed25519"], optional = true }
|
||||||
|
|
||||||
monero-generators = { path = "generators", version = "0.3" }
|
monero-generators = { path = "generators", version = "0.3", default-features = false }
|
||||||
|
|
||||||
hex = "0.4"
|
futures = { version = "0.3", default-features = false, features = ["alloc"], optional = true }
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = "1"
|
|
||||||
|
|
||||||
base58-monero = "1"
|
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||||
monero-epee-bin-serde = "1"
|
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||||
|
serde_json = { version = "1", default-features = false, features = ["alloc"] }
|
||||||
|
|
||||||
digest_auth = "0.3"
|
base58-monero = { version = "1", git = "https://github.com/monero-rs/base58-monero", rev = "5045e8d2b817b3b6c1190661f504e879bc769c29", default-features = false, features = ["check"] }
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
|
||||||
|
# Used for the provided RPC
|
||||||
|
digest_auth = { version = "0.3", optional = true }
|
||||||
|
reqwest = { version = "0.11", features = ["json"], optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3" }
|
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3", default-features = false }
|
||||||
monero-generators = { path = "generators", version = "0.3" }
|
monero-generators = { path = "generators", version = "0.3", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.4"
|
hex-literal = "0.4"
|
||||||
@@ -65,4 +69,33 @@ monero-rpc = "0.3"
|
|||||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.7", features = ["tests"] }
|
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.7", features = ["tests"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
multisig = ["transcript", "frost", "dleq"]
|
std = [
|
||||||
|
"std-shims/std",
|
||||||
|
|
||||||
|
"thiserror",
|
||||||
|
|
||||||
|
"zeroize/std",
|
||||||
|
"subtle/std",
|
||||||
|
|
||||||
|
"rand_core/std",
|
||||||
|
"rand_chacha/std",
|
||||||
|
"rand/std",
|
||||||
|
"rand_distr/std",
|
||||||
|
|
||||||
|
"sha3/std",
|
||||||
|
|
||||||
|
"curve25519-dalek/std",
|
||||||
|
|
||||||
|
"multiexp/std",
|
||||||
|
|
||||||
|
"monero-generators/std",
|
||||||
|
|
||||||
|
"futures/std",
|
||||||
|
|
||||||
|
"hex/std",
|
||||||
|
"serde/std",
|
||||||
|
"serde_json/std",
|
||||||
|
]
|
||||||
|
http_rpc = ["digest_auth", "reqwest"]
|
||||||
|
multisig = ["transcript", "frost", "dleq", "std"]
|
||||||
|
default = ["std", "http_rpc"]
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
serialize::*,
|
serialize::*,
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
use std_shims::{sync::OnceLock, io};
|
use std_shims::{sync::OnceLock, io};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std_shims::sync::OnceLock;
|
use std_shims::{vec::Vec, sync::OnceLock};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std_shims::sync::OnceLock;
|
use std_shims::{vec::Vec, sync::OnceLock};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std_shims::sync::OnceLock;
|
use std_shims::{vec::Vec, sync::OnceLock};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use core::ops::{Add, Sub, Mul, Index};
|
use core::ops::{Add, Sub, Mul, Index};
|
||||||
|
use std_shims::vec::Vec;
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||||
@@ -29,23 +31,24 @@ pub use multisig::{ClsagDetails, ClsagAddendum, ClsagMultisig};
|
|||||||
pub(crate) use multisig::add_key_image_share;
|
pub(crate) use multisig::add_key_image_share;
|
||||||
|
|
||||||
/// Errors returned when CLSAG signing fails.
|
/// Errors returned when CLSAG signing fails.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum ClsagError {
|
pub enum ClsagError {
|
||||||
#[error("internal error ({0})")]
|
#[cfg_attr(feature = "std", error("internal error ({0})"))]
|
||||||
InternalError(&'static str),
|
InternalError(&'static str),
|
||||||
#[error("invalid ring")]
|
#[cfg_attr(feature = "std", error("invalid ring"))]
|
||||||
InvalidRing,
|
InvalidRing,
|
||||||
#[error("invalid ring member (member {0}, ring size {1})")]
|
#[cfg_attr(feature = "std", error("invalid ring member (member {0}, ring size {1})"))]
|
||||||
InvalidRingMember(u8, u8),
|
InvalidRingMember(u8, u8),
|
||||||
#[error("invalid commitment")]
|
#[cfg_attr(feature = "std", error("invalid commitment"))]
|
||||||
InvalidCommitment,
|
InvalidCommitment,
|
||||||
#[error("invalid key image")]
|
#[cfg_attr(feature = "std", error("invalid key image"))]
|
||||||
InvalidImage,
|
InvalidImage,
|
||||||
#[error("invalid D")]
|
#[cfg_attr(feature = "std", error("invalid D"))]
|
||||||
InvalidD,
|
InvalidD,
|
||||||
#[error("invalid s")]
|
#[cfg_attr(feature = "std", error("invalid s"))]
|
||||||
InvalidS,
|
InvalidS,
|
||||||
#[error("invalid c1")]
|
#[cfg_attr(feature = "std", error("invalid c1"))]
|
||||||
InvalidC1,
|
InvalidC1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use core::{ops::Deref, fmt::Debug};
|
use core::{ops::Deref, fmt::Debug};
|
||||||
use std::{
|
use std_shims::io::{self, Read, Write};
|
||||||
io::{self, Read, Write},
|
use std::sync::{Arc, RwLock};
|
||||||
sync::{Arc, RwLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||||
use rand_chacha::ChaCha20Rng;
|
use rand_chacha::ChaCha20Rng;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
|||||||
91
coins/monero/src/rpc/http.rs
Normal file
91
coins/monero/src/rpc/http.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use digest_auth::AuthContext;
|
||||||
|
use reqwest::Client;
|
||||||
|
|
||||||
|
use crate::rpc::{RpcError, RpcConnection, Rpc};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct HttpRpc {
|
||||||
|
client: Client,
|
||||||
|
userpass: Option<(String, String)>,
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpRpc {
|
||||||
|
/// Create a new HTTP(S) RPC connection.
|
||||||
|
///
|
||||||
|
/// A daemon requiring authentication can be used via including the username and password in the
|
||||||
|
/// URL.
|
||||||
|
pub fn new(mut url: String) -> Result<Rpc<HttpRpc>, RpcError> {
|
||||||
|
// Parse out the username and password
|
||||||
|
let userpass = if url.contains('@') {
|
||||||
|
let url_clone = url;
|
||||||
|
let split_url = url_clone.split('@').collect::<Vec<_>>();
|
||||||
|
if split_url.len() != 2 {
|
||||||
|
Err(RpcError::InvalidNode)?;
|
||||||
|
}
|
||||||
|
let mut userpass = split_url[0];
|
||||||
|
url = split_url[1].to_string();
|
||||||
|
|
||||||
|
// If there was additionally a protocol string, restore that to the daemon URL
|
||||||
|
if userpass.contains("://") {
|
||||||
|
let split_userpass = userpass.split("://").collect::<Vec<_>>();
|
||||||
|
if split_userpass.len() != 2 {
|
||||||
|
Err(RpcError::InvalidNode)?;
|
||||||
|
}
|
||||||
|
url = split_userpass[0].to_string() + "://" + &url;
|
||||||
|
userpass = split_userpass[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let split_userpass = userpass.split(':').collect::<Vec<_>>();
|
||||||
|
if split_userpass.len() != 2 {
|
||||||
|
Err(RpcError::InvalidNode)?;
|
||||||
|
}
|
||||||
|
Some((split_userpass[0].to_string(), split_userpass[1].to_string()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Rpc(HttpRpc { client: Client::new(), userpass, url }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl RpcConnection for HttpRpc {
|
||||||
|
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError> {
|
||||||
|
let mut builder = self.client.post(self.url.clone() + "/" + route).body(body);
|
||||||
|
|
||||||
|
if let Some((user, pass)) = &self.userpass {
|
||||||
|
let req = self.client.post(&self.url).send().await.map_err(|_| RpcError::InvalidNode)?;
|
||||||
|
// Only provide authentication if this daemon actually expects it
|
||||||
|
if let Some(header) = req.headers().get("www-authenticate") {
|
||||||
|
builder = builder.header(
|
||||||
|
"Authorization",
|
||||||
|
digest_auth::parse(header.to_str().map_err(|_| RpcError::InvalidNode)?)
|
||||||
|
.map_err(|_| RpcError::InvalidNode)?
|
||||||
|
.respond(&AuthContext::new_post::<_, _, _, &[u8]>(
|
||||||
|
user,
|
||||||
|
pass,
|
||||||
|
"/".to_string() + route,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
.map_err(|_| RpcError::InvalidNode)?
|
||||||
|
.to_header_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
builder
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|_| RpcError::ConnectionError)?
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|_| RpcError::ConnectionError)?
|
||||||
|
.slice(..)
|
||||||
|
.to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,32 @@
|
|||||||
use std::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io,
|
||||||
|
string::{String, ToString},
|
||||||
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
|
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
use digest_auth::AuthContext;
|
|
||||||
use reqwest::Client;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Protocol,
|
Protocol,
|
||||||
|
serialize::*,
|
||||||
transaction::{Input, Timelock, Transaction},
|
transaction::{Input, Timelock, Transaction},
|
||||||
block::Block,
|
block::Block,
|
||||||
wallet::Fee,
|
wallet::Fee,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "http_rpc")]
|
||||||
|
mod http;
|
||||||
|
#[cfg(feature = "http_rpc")]
|
||||||
|
pub use http::*;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct EmptyResponse {}
|
pub struct EmptyResponse {}
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
@@ -38,23 +47,24 @@ struct TransactionsResponse {
|
|||||||
txs: Vec<TransactionResponse>,
|
txs: Vec<TransactionResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum RpcError {
|
pub enum RpcError {
|
||||||
#[error("internal error ({0})")]
|
#[cfg_attr(feature = "std", error("internal error ({0})"))]
|
||||||
InternalError(&'static str),
|
InternalError(&'static str),
|
||||||
#[error("connection error")]
|
#[cfg_attr(feature = "std", error("connection error"))]
|
||||||
ConnectionError,
|
ConnectionError,
|
||||||
#[error("invalid node")]
|
#[cfg_attr(feature = "std", error("invalid node"))]
|
||||||
InvalidNode,
|
InvalidNode,
|
||||||
#[error("unsupported protocol version ({0})")]
|
#[cfg_attr(feature = "std", error("unsupported protocol version ({0})"))]
|
||||||
UnsupportedProtocol(usize),
|
UnsupportedProtocol(usize),
|
||||||
#[error("transactions not found")]
|
#[cfg_attr(feature = "std", error("transactions not found"))]
|
||||||
TransactionsNotFound(Vec<[u8; 32]>),
|
TransactionsNotFound(Vec<[u8; 32]>),
|
||||||
#[error("invalid point ({0})")]
|
#[cfg_attr(feature = "std", error("invalid point ({0})"))]
|
||||||
InvalidPoint(String),
|
InvalidPoint(String),
|
||||||
#[error("pruned transaction")]
|
#[cfg_attr(feature = "std", error("pruned transaction"))]
|
||||||
PrunedTransaction,
|
PrunedTransaction,
|
||||||
#[error("invalid transaction ({0:?})")]
|
#[cfg_attr(feature = "std", error("invalid transaction ({0:?})"))]
|
||||||
InvalidTransaction([u8; 32]),
|
InvalidTransaction([u8; 32]),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +84,23 @@ fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> {
|
|||||||
.ok_or_else(|| RpcError::InvalidPoint(point.to_string()))
|
.ok_or_else(|| RpcError::InvalidPoint(point.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read an EPEE VarInt, distinct from the VarInts used throughout the rest of the protocol
|
||||||
|
fn read_epee_vi<R: io::Read>(reader: &mut R) -> io::Result<u64> {
|
||||||
|
let vi_start = read_byte(reader)?;
|
||||||
|
let len = match vi_start & 0b11 {
|
||||||
|
0 => 1,
|
||||||
|
1 => 2,
|
||||||
|
2 => 4,
|
||||||
|
3 => 8,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let mut vi = u64::from(vi_start >> 2);
|
||||||
|
for i in 1 .. len {
|
||||||
|
vi |= u64::from(read_byte(reader)?) << (((i - 1) * 8) + 6);
|
||||||
|
}
|
||||||
|
Ok(vi)
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait RpcConnection: Clone + Debug {
|
pub trait RpcConnection: Clone + Debug {
|
||||||
/// Perform a POST request to the specified route with the specified body.
|
/// Perform a POST request to the specified route with the specified body.
|
||||||
@@ -82,91 +109,7 @@ pub trait RpcConnection: Clone + Debug {
|
|||||||
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError>;
|
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
// TODO: Make this provided methods for RpcConnection?
|
||||||
pub struct HttpRpc {
|
|
||||||
client: Client,
|
|
||||||
userpass: Option<(String, String)>,
|
|
||||||
url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HttpRpc {
|
|
||||||
/// Create a new HTTP(S) RPC connection.
|
|
||||||
///
|
|
||||||
/// A daemon requiring authentication can be used via including the username and password in the
|
|
||||||
/// URL.
|
|
||||||
pub fn new(mut url: String) -> Result<Rpc<HttpRpc>, RpcError> {
|
|
||||||
// Parse out the username and password
|
|
||||||
let userpass = if url.contains('@') {
|
|
||||||
let url_clone = url;
|
|
||||||
let split_url = url_clone.split('@').collect::<Vec<_>>();
|
|
||||||
if split_url.len() != 2 {
|
|
||||||
Err(RpcError::InvalidNode)?;
|
|
||||||
}
|
|
||||||
let mut userpass = split_url[0];
|
|
||||||
url = split_url[1].to_string();
|
|
||||||
|
|
||||||
// If there was additionally a protocol string, restore that to the daemon URL
|
|
||||||
if userpass.contains("://") {
|
|
||||||
let split_userpass = userpass.split("://").collect::<Vec<_>>();
|
|
||||||
if split_userpass.len() != 2 {
|
|
||||||
Err(RpcError::InvalidNode)?;
|
|
||||||
}
|
|
||||||
url = split_userpass[0].to_string() + "://" + &url;
|
|
||||||
userpass = split_userpass[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
let split_userpass = userpass.split(':').collect::<Vec<_>>();
|
|
||||||
if split_userpass.len() != 2 {
|
|
||||||
Err(RpcError::InvalidNode)?;
|
|
||||||
}
|
|
||||||
Some((split_userpass[0].to_string(), split_userpass[1].to_string()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Rpc(HttpRpc { client: Client::new(), userpass, url }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl RpcConnection for HttpRpc {
|
|
||||||
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError> {
|
|
||||||
let mut builder = self.client.post(self.url.clone() + "/" + route).body(body);
|
|
||||||
|
|
||||||
if let Some((user, pass)) = &self.userpass {
|
|
||||||
let req = self.client.post(&self.url).send().await.map_err(|_| RpcError::InvalidNode)?;
|
|
||||||
// Only provide authentication if this daemon actually expects it
|
|
||||||
if let Some(header) = req.headers().get("www-authenticate") {
|
|
||||||
builder = builder.header(
|
|
||||||
"Authorization",
|
|
||||||
digest_auth::parse(header.to_str().map_err(|_| RpcError::InvalidNode)?)
|
|
||||||
.map_err(|_| RpcError::InvalidNode)?
|
|
||||||
.respond(&AuthContext::new_post::<_, _, _, &[u8]>(
|
|
||||||
user,
|
|
||||||
pass,
|
|
||||||
"/".to_string() + route,
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
.map_err(|_| RpcError::InvalidNode)?
|
|
||||||
.to_header_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(
|
|
||||||
builder
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|_| RpcError::ConnectionError)?
|
|
||||||
.bytes()
|
|
||||||
.await
|
|
||||||
.map_err(|_| RpcError::ConnectionError)?
|
|
||||||
.slice(..)
|
|
||||||
.to_vec(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Rpc<R: RpcConnection>(R);
|
pub struct Rpc<R: RpcConnection>(R);
|
||||||
impl<R: RpcConnection> Rpc<R> {
|
impl<R: RpcConnection> Rpc<R> {
|
||||||
@@ -179,10 +122,9 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
route: &str,
|
route: &str,
|
||||||
params: Option<Params>,
|
params: Option<Params>,
|
||||||
) -> Result<Response, RpcError> {
|
) -> Result<Response, RpcError> {
|
||||||
self
|
serde_json::from_str(
|
||||||
.call_tail(
|
std_shims::str::from_utf8(
|
||||||
route,
|
&self
|
||||||
self
|
|
||||||
.0
|
.0
|
||||||
.post(
|
.post(
|
||||||
route,
|
route,
|
||||||
@@ -194,7 +136,9 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
)
|
)
|
||||||
.await
|
.map_err(|_| RpcError::InvalidNode)?,
|
||||||
|
)
|
||||||
|
.map_err(|_| RpcError::InternalError("Failed to parse JSON response"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a JSON-RPC call with the specified method with the provided parameters
|
/// Perform a JSON-RPC call with the specified method with the provided parameters
|
||||||
@@ -211,26 +155,8 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a binary call to the specified route with the provided parameters.
|
/// Perform a binary call to the specified route with the provided parameters.
|
||||||
pub async fn bin_call<Response: DeserializeOwned + Debug>(
|
pub async fn bin_call(&self, route: &str, params: Vec<u8>) -> Result<Vec<u8>, RpcError> {
|
||||||
&self,
|
self.0.post(route, params).await
|
||||||
route: &str,
|
|
||||||
params: Vec<u8>,
|
|
||||||
) -> Result<Response, RpcError> {
|
|
||||||
self.call_tail(route, self.0.post(route, params).await?).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn call_tail<Response: DeserializeOwned + Debug>(
|
|
||||||
&self,
|
|
||||||
route: &str,
|
|
||||||
res: Vec<u8>,
|
|
||||||
) -> Result<Response, RpcError> {
|
|
||||||
Ok(if !route.ends_with(".bin") {
|
|
||||||
serde_json::from_str(std::str::from_utf8(&res).map_err(|_| RpcError::InvalidNode)?)
|
|
||||||
.map_err(|_| RpcError::InternalError("Failed to parse JSON response"))?
|
|
||||||
} else {
|
|
||||||
monero_epee_bin_serde::from_bytes(&res)
|
|
||||||
.map_err(|_| RpcError::InternalError("Failed to parse binary response"))?
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the active blockchain protocol version.
|
/// Get the active blockchain protocol version.
|
||||||
@@ -391,6 +317,9 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
|
|
||||||
/// Get the output indexes of the specified transaction.
|
/// Get the output indexes of the specified transaction.
|
||||||
pub async fn get_o_indexes(&self, hash: [u8; 32]) -> Result<Vec<u64>, RpcError> {
|
pub async fn get_o_indexes(&self, hash: [u8; 32]) -> Result<Vec<u64>, RpcError> {
|
||||||
|
/*
|
||||||
|
TODO: Use these when a suitable epee serde lib exists
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
struct Request {
|
struct Request {
|
||||||
txid: [u8; 32],
|
txid: [u8; 32],
|
||||||
@@ -400,20 +329,125 @@ impl<R: RpcConnection> Rpc<R> {
|
|||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct OIndexes {
|
struct OIndexes {
|
||||||
o_indexes: Vec<u64>,
|
o_indexes: Vec<u64>,
|
||||||
status: String,
|
|
||||||
untrusted: bool,
|
|
||||||
credits: usize,
|
|
||||||
top_hash: String,
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
let indexes: OIndexes = self
|
// Given the immaturity of Rust epee libraries, this is a homegrown one which is only validated
|
||||||
.bin_call(
|
// to work against this specific function
|
||||||
"get_o_indexes.bin",
|
|
||||||
monero_epee_bin_serde::to_bytes(&Request { txid: hash }).unwrap(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(indexes.o_indexes)
|
// Header for EPEE, an 8-byte magic and a version
|
||||||
|
const EPEE_HEADER: &[u8] = b"\x01\x11\x01\x01\x01\x01\x02\x01\x01";
|
||||||
|
|
||||||
|
let mut request = EPEE_HEADER.to_vec();
|
||||||
|
// Number of fields (shifted over 2 bits as the 2 LSBs are reserved for metadata)
|
||||||
|
request.push(1 << 2);
|
||||||
|
// Length of field name
|
||||||
|
request.push(4);
|
||||||
|
// Field name
|
||||||
|
request.extend(b"txid");
|
||||||
|
// Type of field
|
||||||
|
request.push(10);
|
||||||
|
// Length of string, since this byte array is technically a string
|
||||||
|
request.push(32 << 2);
|
||||||
|
// The "string"
|
||||||
|
request.extend(hash);
|
||||||
|
|
||||||
|
let indexes_buf = self.bin_call("get_o_indexes.bin", request).await?;
|
||||||
|
let mut indexes: &[u8] = indexes_buf.as_ref();
|
||||||
|
|
||||||
|
(|| {
|
||||||
|
if read_bytes::<_, { EPEE_HEADER.len() }>(&mut indexes)? != EPEE_HEADER {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "invalid header"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let read_object = |reader: &mut &[u8]| {
|
||||||
|
let fields = read_byte(reader)? >> 2;
|
||||||
|
|
||||||
|
for _ in 0 .. fields {
|
||||||
|
let name_len = read_byte(reader)?;
|
||||||
|
let name = read_raw_vec(read_byte, name_len.into(), reader)?;
|
||||||
|
|
||||||
|
let type_with_array_flag = read_byte(reader)?;
|
||||||
|
let kind = type_with_array_flag & (!0x80);
|
||||||
|
|
||||||
|
let iters = if type_with_array_flag != kind { read_epee_vi(reader)? } else { 1 };
|
||||||
|
|
||||||
|
if (&name == b"o_indexes") && (kind != 5) {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "o_indexes weren't u64s"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let f = match kind {
|
||||||
|
// i64
|
||||||
|
1 => |reader: &mut &[u8]| read_raw_vec(read_byte, 8, reader),
|
||||||
|
// i32
|
||||||
|
2 => |reader: &mut &[u8]| read_raw_vec(read_byte, 4, reader),
|
||||||
|
// i16
|
||||||
|
3 => |reader: &mut &[u8]| read_raw_vec(read_byte, 2, reader),
|
||||||
|
// i8
|
||||||
|
4 => |reader: &mut &[u8]| read_raw_vec(read_byte, 1, reader),
|
||||||
|
// u64
|
||||||
|
5 => |reader: &mut &[u8]| read_raw_vec(read_byte, 8, reader),
|
||||||
|
// u32
|
||||||
|
6 => |reader: &mut &[u8]| read_raw_vec(read_byte, 4, reader),
|
||||||
|
// u16
|
||||||
|
7 => |reader: &mut &[u8]| read_raw_vec(read_byte, 2, reader),
|
||||||
|
// u8
|
||||||
|
8 => |reader: &mut &[u8]| read_raw_vec(read_byte, 1, reader),
|
||||||
|
// double
|
||||||
|
9 => |reader: &mut &[u8]| read_raw_vec(read_byte, 8, reader),
|
||||||
|
// string, or any collection of bytes
|
||||||
|
10 => |reader: &mut &[u8]| {
|
||||||
|
let len = read_epee_vi(reader)?;
|
||||||
|
read_raw_vec(
|
||||||
|
read_byte,
|
||||||
|
len
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| io::Error::new(io::ErrorKind::Other, "u64 length exceeded usize"))?,
|
||||||
|
reader,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// bool
|
||||||
|
11 => |reader: &mut &[u8]| read_raw_vec(read_byte, 1, reader),
|
||||||
|
// object, errors here as it shouldn't be used on this call
|
||||||
|
12 => |_: &mut &[u8]| {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"node used object in reply to get_o_indexes",
|
||||||
|
))
|
||||||
|
},
|
||||||
|
// array, so far unused
|
||||||
|
13 => |_: &mut &[u8]| {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "node used the unused array type"))
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
|_: &mut &[u8]| Err(io::Error::new(io::ErrorKind::Other, "node used an invalid type"))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
for _ in 0 .. iters {
|
||||||
|
res.push(f(reader)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut actual_res = Vec::with_capacity(res.len());
|
||||||
|
if &name == b"o_indexes" {
|
||||||
|
for o_index in res {
|
||||||
|
actual_res.push(u64::from_le_bytes(o_index.try_into().map_err(|_| {
|
||||||
|
io::Error::new(io::ErrorKind::Other, "node didn't provide 8 bytes for a u64")
|
||||||
|
})?));
|
||||||
|
}
|
||||||
|
return Ok(actual_res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Didn't return a response with o_indexes
|
||||||
|
// TODO: Check if this didn't have o_indexes because it's an error response
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "response didn't contain o_indexes"))
|
||||||
|
};
|
||||||
|
|
||||||
|
read_object(&mut indexes)
|
||||||
|
})()
|
||||||
|
.map_err(|_| RpcError::InvalidNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the output distribution, from the specified height to the specified height (both
|
/// Get the output distribution, from the specified height to the specified height (both
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use curve25519_dalek::{
|
use curve25519_dalek::{
|
||||||
scalar::Scalar,
|
scalar::Scalar,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use hex_literal::hex;
|
use hex_literal::hex;
|
||||||
use rand::rngs::OsRng;
|
use rand_core::OsRng;
|
||||||
|
|
||||||
use curve25519_dalek::{scalar::Scalar, edwards::CompressedEdwardsY};
|
use curve25519_dalek::{scalar::Scalar, edwards::CompressedEdwardsY};
|
||||||
use multiexp::BatchVerifier;
|
use multiexp::BatchVerifier;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use core::cmp::Ordering;
|
use core::cmp::Ordering;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use core::{marker::PhantomData, fmt::Debug};
|
use core::{marker::PhantomData, fmt::Debug};
|
||||||
use std::string::ToString;
|
use std_shims::string::{String, ToString};
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
@@ -114,19 +112,20 @@ impl<B: AddressBytes> Zeroize for AddressMeta<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Error when decoding an address.
|
/// Error when decoding an address.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum AddressError {
|
pub enum AddressError {
|
||||||
#[error("invalid address byte")]
|
#[cfg_attr(feature = "std", error("invalid address byte"))]
|
||||||
InvalidByte,
|
InvalidByte,
|
||||||
#[error("invalid address encoding")]
|
#[cfg_attr(feature = "std", error("invalid address encoding"))]
|
||||||
InvalidEncoding,
|
InvalidEncoding,
|
||||||
#[error("invalid length")]
|
#[cfg_attr(feature = "std", error("invalid length"))]
|
||||||
InvalidLength,
|
InvalidLength,
|
||||||
#[error("invalid key")]
|
#[cfg_attr(feature = "std", error("invalid key"))]
|
||||||
InvalidKey,
|
InvalidKey,
|
||||||
#[error("unknown features")]
|
#[cfg_attr(feature = "std", error("unknown features"))]
|
||||||
UnknownFeatures,
|
UnknownFeatures,
|
||||||
#[error("different network than expected")]
|
#[cfg_attr(feature = "std", error("different network than expected"))]
|
||||||
DifferentNetwork,
|
DifferentNetwork,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
use std_shims::{sync::OnceLock, collections::HashSet};
|
use std_shims::{sync::OnceLock, vec::Vec, collections::HashSet};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
use std_shims::sync::Mutex;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
|
|
||||||
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
use rand_distr::{Distribution, Gamma};
|
use rand_distr::{Distribution, Gamma};
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use rand_distr::num_traits::Float;
|
||||||
|
|
||||||
use curve25519_dalek::edwards::EdwardsPoint;
|
use curve25519_dalek::edwards::EdwardsPoint;
|
||||||
|
|
||||||
@@ -143,6 +148,9 @@ impl Decoys {
|
|||||||
height: usize,
|
height: usize,
|
||||||
inputs: &[SpendableOutput],
|
inputs: &[SpendableOutput],
|
||||||
) -> Result<Vec<Decoys>, RpcError> {
|
) -> Result<Vec<Decoys>, RpcError> {
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
let mut distribution = DISTRIBUTION().lock();
|
||||||
|
#[cfg(feature = "std")]
|
||||||
let mut distribution = DISTRIBUTION().lock().await;
|
let mut distribution = DISTRIBUTION().lock().await;
|
||||||
|
|
||||||
let decoy_count = ring_len - 1;
|
let decoy_count = ring_len - 1;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use core::ops::BitXor;
|
use core::ops::BitXor;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::collections::{HashSet, HashMap};
|
use std_shims::collections::{HashSet, HashMap};
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||||
|
|
||||||
@@ -28,15 +28,15 @@ pub(crate) mod decoys;
|
|||||||
pub(crate) use decoys::Decoys;
|
pub(crate) use decoys::Decoys;
|
||||||
|
|
||||||
mod send;
|
mod send;
|
||||||
pub use send::{
|
pub use send::{Fee, TransactionError, Change, SignableTransaction, Eventuality};
|
||||||
Fee, TransactionError, Change, SignableTransaction, SignableTransactionBuilder, Eventuality,
|
#[cfg(feature = "std")]
|
||||||
};
|
pub use send::SignableTransactionBuilder;
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
pub(crate) use send::InternalPayment;
|
pub(crate) use send::InternalPayment;
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
pub use send::TransactionMachine;
|
pub use send::TransactionMachine;
|
||||||
|
|
||||||
fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> std::cmp::Ordering {
|
fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> core::cmp::Ordering {
|
||||||
x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse()
|
x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::io::{self, Read, Write};
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std_shims::{sync::OnceLock, collections::HashMap};
|
use std_shims::{
|
||||||
|
sync::OnceLock,
|
||||||
|
vec::Vec,
|
||||||
|
string::{String, ToString},
|
||||||
|
collections::HashMap,
|
||||||
|
};
|
||||||
|
|
||||||
use zeroize::{Zeroize, Zeroizing};
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
use std_shims::string::String;
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub(crate) mod classic;
|
pub(crate) mod classic;
|
||||||
use classic::{CLASSIC_SEED_LENGTH, CLASSIC_SEED_LENGTH_WITH_CHECKSUM, ClassicSeed};
|
use classic::{CLASSIC_SEED_LENGTH, CLASSIC_SEED_LENGTH_WITH_CHECKSUM, ClassicSeed};
|
||||||
|
|
||||||
/// Error when decoding a seed.
|
/// Error when decoding a seed.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum SeedError {
|
pub enum SeedError {
|
||||||
#[error("invalid number of words in seed")]
|
#[cfg_attr(feature = "std", error("invalid number of words in seed"))]
|
||||||
InvalidSeedLength,
|
InvalidSeedLength,
|
||||||
#[error("unknown language")]
|
#[cfg_attr(feature = "std", error("unknown language"))]
|
||||||
UnknownLanguage,
|
UnknownLanguage,
|
||||||
#[error("invalid checksum")]
|
#[cfg_attr(feature = "std", error("invalid checksum"))]
|
||||||
InvalidChecksum,
|
InvalidChecksum,
|
||||||
#[error("english old seeds don't support checksums")]
|
#[cfg_attr(feature = "std", error("english old seeds don't support checksums"))]
|
||||||
EnglishOldWithChecksum,
|
EnglishOldWithChecksum,
|
||||||
#[error("invalid seed")]
|
#[cfg_attr(feature = "std", error("invalid seed"))]
|
||||||
InvalidSeed,
|
InvalidSeed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use core::{ops::Deref, fmt};
|
use core::{ops::Deref, fmt};
|
||||||
use std::io;
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
use thiserror::Error;
|
io,
|
||||||
|
string::{String, ToString},
|
||||||
|
};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||||
use rand_chacha::ChaCha20Rng;
|
use rand_chacha::ChaCha20Rng;
|
||||||
@@ -42,7 +44,9 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
mod builder;
|
mod builder;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
pub use builder::SignableTransactionBuilder;
|
pub use builder::SignableTransactionBuilder;
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
@@ -116,34 +120,35 @@ impl SendOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum TransactionError {
|
pub enum TransactionError {
|
||||||
#[error("multiple addresses with payment IDs")]
|
#[cfg_attr(feature = "std", error("multiple addresses with payment IDs"))]
|
||||||
MultiplePaymentIds,
|
MultiplePaymentIds,
|
||||||
#[error("no inputs")]
|
#[cfg_attr(feature = "std", error("no inputs"))]
|
||||||
NoInputs,
|
NoInputs,
|
||||||
#[error("no outputs")]
|
#[cfg_attr(feature = "std", error("no outputs"))]
|
||||||
NoOutputs,
|
NoOutputs,
|
||||||
#[error("only one output and no change address")]
|
#[cfg_attr(feature = "std", error("only one output and no change address"))]
|
||||||
NoChange,
|
NoChange,
|
||||||
#[error("too many outputs")]
|
#[cfg_attr(feature = "std", error("too many outputs"))]
|
||||||
TooManyOutputs,
|
TooManyOutputs,
|
||||||
#[error("too much data")]
|
#[cfg_attr(feature = "std", error("too much data"))]
|
||||||
TooMuchData,
|
TooMuchData,
|
||||||
#[error("too many inputs/too much arbitrary data")]
|
#[cfg_attr(feature = "std", error("too many inputs/too much arbitrary data"))]
|
||||||
TooLargeTransaction,
|
TooLargeTransaction,
|
||||||
#[error("not enough funds (in {0}, out {1})")]
|
#[cfg_attr(feature = "std", error("not enough funds (in {0}, out {1})"))]
|
||||||
NotEnoughFunds(u64, u64),
|
NotEnoughFunds(u64, u64),
|
||||||
#[error("wrong spend private key")]
|
#[cfg_attr(feature = "std", error("wrong spend private key"))]
|
||||||
WrongPrivateKey,
|
WrongPrivateKey,
|
||||||
#[error("rpc error ({0})")]
|
#[cfg_attr(feature = "std", error("rpc error ({0})"))]
|
||||||
RpcError(RpcError),
|
RpcError(RpcError),
|
||||||
#[error("clsag error ({0})")]
|
#[cfg_attr(feature = "std", error("clsag error ({0})"))]
|
||||||
ClsagError(ClsagError),
|
ClsagError(ClsagError),
|
||||||
#[error("invalid transaction ({0})")]
|
#[cfg_attr(feature = "std", error("invalid transaction ({0})"))]
|
||||||
InvalidTransaction(RpcError),
|
InvalidTransaction(RpcError),
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
#[error("frost error {0}")]
|
#[cfg_attr(feature = "std", error("frost error {0}"))]
|
||||||
FrostError(FrostError),
|
FrostError(FrostError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use std::{
|
use std_shims::{
|
||||||
|
vec::Vec,
|
||||||
io::{self, Read},
|
io::{self, Read},
|
||||||
sync::{Arc, RwLock},
|
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
};
|
};
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
@@ -10,6 +12,13 @@ pub mod sync;
|
|||||||
pub mod collections;
|
pub mod collections;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
|
||||||
|
pub mod vec {
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
pub use alloc::vec::*;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use std::vec::*;
|
||||||
|
}
|
||||||
|
|
||||||
pub mod str {
|
pub mod str {
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
pub use alloc::str::*;
|
pub use alloc::str::*;
|
||||||
@@ -17,9 +26,9 @@ pub mod str {
|
|||||||
pub use std::str::*;
|
pub use std::str::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod vec {
|
pub mod string {
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
pub use alloc::vec::*;
|
pub use alloc::string::*;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub use std::vec::*;
|
pub use std::string::*;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,28 +28,42 @@ pub use mutex_shim::{ShimMutex as Mutex, MutexGuard};
|
|||||||
pub use std::sync::OnceLock;
|
pub use std::sync::OnceLock;
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
mod oncelock_shim {
|
mod oncelock_shim {
|
||||||
pub struct OnceLock<T>(super::Mutex<()>, Option<T>);
|
use super::Mutex;
|
||||||
|
|
||||||
|
pub struct OnceLock<T>(Mutex<bool>, Option<T>);
|
||||||
impl<T> OnceLock<T> {
|
impl<T> OnceLock<T> {
|
||||||
pub const fn new() -> OnceLock<T> {
|
pub const fn new() -> OnceLock<T> {
|
||||||
OnceLock(Mutex::new(), None)
|
OnceLock(Mutex::new(false), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These return a distinct Option in case of None so another caller using get_or_init doesn't
|
||||||
|
// transform it from None to Some
|
||||||
pub fn get(&self) -> Option<&T> {
|
pub fn get(&self) -> Option<&T> {
|
||||||
self.1.as_ref()
|
if !*self.0.lock() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.1.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self) -> Option<&mut T> {
|
pub fn get_mut(&mut self) -> Option<&mut T> {
|
||||||
self.1.as_mut()
|
if !*self.0.lock() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.1.as_mut()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_or_init<F: FnOnce() -> T>(&self, f: F) -> &T {
|
pub fn get_or_init<F: FnOnce() -> T>(&self, f: F) -> &T {
|
||||||
let lock = self.0.lock();
|
let mut lock = self.0.lock();
|
||||||
if self.1.is_none() {
|
if !*lock {
|
||||||
self.1 = Some(f());
|
unsafe {
|
||||||
|
(core::ptr::addr_of!(self.1) as *mut Option<_>).write_unaligned(Some(f()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
*lock = true;
|
||||||
drop(lock);
|
drop(lock);
|
||||||
|
|
||||||
self.1.as_ref().unwrap()
|
self.get().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,4 +83,5 @@ allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
|||||||
allow-git = [
|
allow-git = [
|
||||||
"https://github.com/serai-dex/substrate-bip39",
|
"https://github.com/serai-dex/substrate-bip39",
|
||||||
"https://github.com/serai-dex/substrate",
|
"https://github.com/serai-dex/substrate",
|
||||||
|
"https://github.com/monero-rs/base58-monero",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -31,3 +31,4 @@ dkg = { path = "../../crypto/dkg", default-features = false }
|
|||||||
# frost-schnorrkel = { path = "../../crypto/schnorrkel", default-features = false }
|
# frost-schnorrkel = { path = "../../crypto/schnorrkel", default-features = false }
|
||||||
|
|
||||||
monero-generators = { path = "../../coins/monero/generators", default-features = false }
|
monero-generators = { path = "../../coins/monero/generators", default-features = false }
|
||||||
|
monero-serai = { path = "../../coins/monero", default-features = false }
|
||||||
|
|||||||
Reference in New Issue
Block a user