mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-22 19:49:22 +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:
parent
d25c668ee4
commit
ac708b3b2a
30 changed files with 487 additions and 261 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -529,6 +529,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9d079cdf47e1ca75554200bb2f30bff5a5af16964cac4a566b18de9a5d48db2b"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base58-monero"
|
||||
version = "1.1.0"
|
||||
source = "git+https://github.com/monero-rs/base58-monero?rev=5045e8d2b817b3b6c1190661f504e879bc769c29#5045e8d2b817b3b6c1190661f504e879bc769c29"
|
||||
dependencies = [
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
|
@ -5086,7 +5093,7 @@ version = "0.17.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "403883d12972e916dd9754cdb90c25441a9abcf435f8e09c3146de100150eeb0"
|
||||
dependencies = [
|
||||
"base58-monero",
|
||||
"base58-monero 1.0.0",
|
||||
"curve25519-dalek 3.2.0",
|
||||
"fixed-hash 0.7.0",
|
||||
"hex",
|
||||
|
@ -5098,16 +5105,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "monero-generators"
|
||||
version = "0.3.0"
|
||||
|
@ -5145,7 +5142,7 @@ name = "monero-serai"
|
|||
version = "0.1.4-alpha"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base58-monero",
|
||||
"base58-monero 1.1.0",
|
||||
"crc",
|
||||
"curve25519-dalek 3.2.0",
|
||||
"dalek-ff-group",
|
||||
|
@ -5157,7 +5154,6 @@ dependencies = [
|
|||
"hex",
|
||||
"hex-literal 0.4.1",
|
||||
"modular-frost",
|
||||
"monero-epee-bin-serde",
|
||||
"monero-generators",
|
||||
"monero-rpc",
|
||||
"multiexp",
|
||||
|
@ -8668,6 +8664,7 @@ dependencies = [
|
|||
"flexible-transcript",
|
||||
"minimal-ed448",
|
||||
"monero-generators",
|
||||
"monero-serai",
|
||||
"multiexp",
|
||||
"schnorr-signatures",
|
||||
]
|
||||
|
|
|
@ -14,47 +14,51 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||
[dependencies]
|
||||
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"
|
||||
thiserror = "1"
|
||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||
subtle = { version = "^2.4", default-features = false }
|
||||
|
||||
rand_core = "0.6"
|
||||
rand_chacha = "0.3"
|
||||
rand = "0.8"
|
||||
rand_distr = "0.4"
|
||||
rand_core = { version = "0.6", default-features = false }
|
||||
# Used to send transactions
|
||||
rand = { version = "0.8", default-features = false }
|
||||
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"] }
|
||||
subtle = "^2.4"
|
||||
crc = { version = "3", default-features = false }
|
||||
sha3 = { version = "0.10", default-features = false }
|
||||
|
||||
crc = "3"
|
||||
sha3 = "0.10"
|
||||
curve25519-dalek = { version = "^3.2", default-features = false }
|
||||
|
||||
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"
|
||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3" }
|
||||
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 }
|
||||
# Needed for multisig
|
||||
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], 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"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
futures = { version = "0.3", default-features = false, features = ["alloc"], optional = true }
|
||||
|
||||
base58-monero = "1"
|
||||
monero-epee-bin-serde = "1"
|
||||
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||
serde_json = { version = "1", default-features = false, features = ["alloc"] }
|
||||
|
||||
digest_auth = "0.3"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
base58-monero = { version = "1", git = "https://github.com/monero-rs/base58-monero", rev = "5045e8d2b817b3b6c1190661f504e879bc769c29", default-features = false, features = ["check"] }
|
||||
|
||||
# Used for the provided RPC
|
||||
digest_auth = { version = "0.3", optional = true }
|
||||
reqwest = { version = "0.11", features = ["json"], optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3" }
|
||||
monero-generators = { path = "generators", version = "0.3" }
|
||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3", default-features = false }
|
||||
monero-generators = { path = "generators", version = "0.3", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.4"
|
||||
|
@ -65,4 +69,33 @@ monero-rpc = "0.3"
|
|||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.7", features = ["tests"] }
|
||||
|
||||
[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::{
|
||||
serialize::*,
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![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};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std_shims::sync::OnceLock;
|
||||
use std_shims::{vec::Vec, sync::OnceLock};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
use std_shims::{
|
||||
vec::Vec,
|
||||
io::{self, Read, Write},
|
||||
};
|
||||
|
||||
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};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std_shims::sync::OnceLock;
|
||||
use std_shims::{vec::Vec, sync::OnceLock};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use core::ops::{Add, Sub, Mul, Index};
|
||||
use std_shims::vec::Vec;
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
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 zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
@ -29,23 +31,24 @@ pub use multisig::{ClsagDetails, ClsagAddendum, ClsagMultisig};
|
|||
pub(crate) use multisig::add_key_image_share;
|
||||
|
||||
/// 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 {
|
||||
#[error("internal error ({0})")]
|
||||
#[cfg_attr(feature = "std", error("internal error ({0})"))]
|
||||
InternalError(&'static str),
|
||||
#[error("invalid ring")]
|
||||
#[cfg_attr(feature = "std", error("invalid ring"))]
|
||||
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),
|
||||
#[error("invalid commitment")]
|
||||
#[cfg_attr(feature = "std", error("invalid commitment"))]
|
||||
InvalidCommitment,
|
||||
#[error("invalid key image")]
|
||||
#[cfg_attr(feature = "std", error("invalid key image"))]
|
||||
InvalidImage,
|
||||
#[error("invalid D")]
|
||||
#[cfg_attr(feature = "std", error("invalid D"))]
|
||||
InvalidD,
|
||||
#[error("invalid s")]
|
||||
#[cfg_attr(feature = "std", error("invalid s"))]
|
||||
InvalidS,
|
||||
#[error("invalid c1")]
|
||||
#[cfg_attr(feature = "std", error("invalid c1"))]
|
||||
InvalidC1,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
use core::{ops::Deref, fmt::Debug};
|
||||
use std::{
|
||||
io::{self, Read, Write},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use std_shims::io::{self, Read, Write};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use core::ops::Deref;
|
||||
use std::io::{self, Read, Write};
|
||||
use std_shims::{
|
||||
vec::Vec,
|
||||
io::{self, Read, Write},
|
||||
};
|
||||
|
||||
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 thiserror::Error;
|
||||
|
||||
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
|
||||
|
||||
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use digest_auth::AuthContext;
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::{
|
||||
Protocol,
|
||||
serialize::*,
|
||||
transaction::{Input, Timelock, Transaction},
|
||||
block::Block,
|
||||
wallet::Fee,
|
||||
};
|
||||
|
||||
#[cfg(feature = "http_rpc")]
|
||||
mod http;
|
||||
#[cfg(feature = "http_rpc")]
|
||||
pub use http::*;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EmptyResponse {}
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
@ -38,23 +47,24 @@ struct TransactionsResponse {
|
|||
txs: Vec<TransactionResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Error)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||
pub enum RpcError {
|
||||
#[error("internal error ({0})")]
|
||||
#[cfg_attr(feature = "std", error("internal error ({0})"))]
|
||||
InternalError(&'static str),
|
||||
#[error("connection error")]
|
||||
#[cfg_attr(feature = "std", error("connection error"))]
|
||||
ConnectionError,
|
||||
#[error("invalid node")]
|
||||
#[cfg_attr(feature = "std", error("invalid node"))]
|
||||
InvalidNode,
|
||||
#[error("unsupported protocol version ({0})")]
|
||||
#[cfg_attr(feature = "std", error("unsupported protocol version ({0})"))]
|
||||
UnsupportedProtocol(usize),
|
||||
#[error("transactions not found")]
|
||||
#[cfg_attr(feature = "std", error("transactions not found"))]
|
||||
TransactionsNotFound(Vec<[u8; 32]>),
|
||||
#[error("invalid point ({0})")]
|
||||
#[cfg_attr(feature = "std", error("invalid point ({0})"))]
|
||||
InvalidPoint(String),
|
||||
#[error("pruned transaction")]
|
||||
#[cfg_attr(feature = "std", error("pruned transaction"))]
|
||||
PrunedTransaction,
|
||||
#[error("invalid transaction ({0:?})")]
|
||||
#[cfg_attr(feature = "std", error("invalid transaction ({0:?})"))]
|
||||
InvalidTransaction([u8; 32]),
|
||||
}
|
||||
|
||||
|
@ -74,6 +84,23 @@ fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> {
|
|||
.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]
|
||||
pub trait RpcConnection: Clone + Debug {
|
||||
/// 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>;
|
||||
}
|
||||
|
||||
#[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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make this provided methods for RpcConnection?
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Rpc<R: RpcConnection>(R);
|
||||
impl<R: RpcConnection> Rpc<R> {
|
||||
|
@ -179,10 +122,9 @@ impl<R: RpcConnection> Rpc<R> {
|
|||
route: &str,
|
||||
params: Option<Params>,
|
||||
) -> Result<Response, RpcError> {
|
||||
self
|
||||
.call_tail(
|
||||
route,
|
||||
self
|
||||
serde_json::from_str(
|
||||
std_shims::str::from_utf8(
|
||||
&self
|
||||
.0
|
||||
.post(
|
||||
route,
|
||||
|
@ -194,7 +136,9 @@ impl<R: RpcConnection> Rpc<R> {
|
|||
)
|
||||
.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
|
||||
|
@ -211,26 +155,8 @@ impl<R: RpcConnection> Rpc<R> {
|
|||
}
|
||||
|
||||
/// Perform a binary call to the specified route with the provided parameters.
|
||||
pub async fn bin_call<Response: DeserializeOwned + Debug>(
|
||||
&self,
|
||||
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"))?
|
||||
})
|
||||
pub async fn bin_call(&self, route: &str, params: Vec<u8>) -> Result<Vec<u8>, RpcError> {
|
||||
self.0.post(route, params).await
|
||||
}
|
||||
|
||||
/// Get the active blockchain protocol version.
|
||||
|
@ -391,6 +317,9 @@ impl<R: RpcConnection> Rpc<R> {
|
|||
|
||||
/// Get the output indexes of the specified transaction.
|
||||
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)]
|
||||
struct Request {
|
||||
txid: [u8; 32],
|
||||
|
@ -400,20 +329,125 @@ impl<R: RpcConnection> Rpc<R> {
|
|||
#[derive(Deserialize, Debug)]
|
||||
struct OIndexes {
|
||||
o_indexes: Vec<u64>,
|
||||
status: String,
|
||||
untrusted: bool,
|
||||
credits: usize,
|
||||
top_hash: String,
|
||||
}
|
||||
*/
|
||||
|
||||
// Given the immaturity of Rust epee libraries, this is a homegrown one which is only validated
|
||||
// to work against this specific function
|
||||
|
||||
// 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 indexes: OIndexes = self
|
||||
.bin_call(
|
||||
"get_o_indexes.bin",
|
||||
monero_epee_bin_serde::to_bytes(&Request { txid: hash }).unwrap(),
|
||||
)
|
||||
.await?;
|
||||
let read_object = |reader: &mut &[u8]| {
|
||||
let fields = read_byte(reader)? >> 2;
|
||||
|
||||
Ok(indexes.o_indexes)
|
||||
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
|
|
@ -1,4 +1,7 @@
|
|||
use std::io::{self, Read, Write};
|
||||
use std_shims::{
|
||||
vec::Vec,
|
||||
io::{self, Read, Write},
|
||||
};
|
||||
|
||||
use curve25519_dalek::{
|
||||
scalar::Scalar,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use hex_literal::hex;
|
||||
use rand::rngs::OsRng;
|
||||
use rand_core::OsRng;
|
||||
|
||||
use curve25519_dalek::{scalar::Scalar, edwards::CompressedEdwardsY};
|
||||
use multiexp::BatchVerifier;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use core::cmp::Ordering;
|
||||
use std::io::{self, Read, Write};
|
||||
use std_shims::{
|
||||
vec::Vec,
|
||||
io::{self, Read, Write},
|
||||
};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use core::{marker::PhantomData, fmt::Debug};
|
||||
use std::string::ToString;
|
||||
|
||||
use thiserror::Error;
|
||||
use std_shims::string::{String, ToString};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
|
@ -114,19 +112,20 @@ impl<B: AddressBytes> Zeroize for AddressMeta<B> {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
#[error("invalid address byte")]
|
||||
#[cfg_attr(feature = "std", error("invalid address byte"))]
|
||||
InvalidByte,
|
||||
#[error("invalid address encoding")]
|
||||
#[cfg_attr(feature = "std", error("invalid address encoding"))]
|
||||
InvalidEncoding,
|
||||
#[error("invalid length")]
|
||||
#[cfg_attr(feature = "std", error("invalid length"))]
|
||||
InvalidLength,
|
||||
#[error("invalid key")]
|
||||
#[cfg_attr(feature = "std", error("invalid key"))]
|
||||
InvalidKey,
|
||||
#[error("unknown features")]
|
||||
#[cfg_attr(feature = "std", error("unknown features"))]
|
||||
UnknownFeatures,
|
||||
#[error("different network than expected")]
|
||||
#[cfg_attr(feature = "std", error("different network than expected"))]
|
||||
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 zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
use rand_distr::{Distribution, Gamma};
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use rand_distr::num_traits::Float;
|
||||
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
|
||||
|
@ -143,6 +148,9 @@ impl Decoys {
|
|||
height: usize,
|
||||
inputs: &[SpendableOutput],
|
||||
) -> Result<Vec<Decoys>, RpcError> {
|
||||
#[cfg(not(feature = "std"))]
|
||||
let mut distribution = DISTRIBUTION().lock();
|
||||
#[cfg(feature = "std")]
|
||||
let mut distribution = DISTRIBUTION().lock().await;
|
||||
|
||||
let decoy_count = ring_len - 1;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use core::ops::BitXor;
|
||||
use std::io::{self, Read, Write};
|
||||
use std_shims::{
|
||||
vec::Vec,
|
||||
io::{self, Read, Write},
|
||||
};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use core::ops::Deref;
|
||||
use std::collections::{HashSet, HashMap};
|
||||
use std_shims::collections::{HashSet, HashMap};
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
|
@ -28,15 +28,15 @@ pub(crate) mod decoys;
|
|||
pub(crate) use decoys::Decoys;
|
||||
|
||||
mod send;
|
||||
pub use send::{
|
||||
Fee, TransactionError, Change, SignableTransaction, SignableTransactionBuilder, Eventuality,
|
||||
};
|
||||
pub use send::{Fee, TransactionError, Change, SignableTransaction, Eventuality};
|
||||
#[cfg(feature = "std")]
|
||||
pub use send::SignableTransactionBuilder;
|
||||
#[cfg(feature = "multisig")]
|
||||
pub(crate) use send::InternalPayment;
|
||||
#[cfg(feature = "multisig")]
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use core::ops::Deref;
|
||||
use std::io::{self, Read, Write};
|
||||
use std_shims::{
|
||||
vec::Vec,
|
||||
io::{self, Read, Write},
|
||||
};
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
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 rand_core::{RngCore, CryptoRng};
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
use core::fmt;
|
||||
use std_shims::string::String;
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub(crate) mod classic;
|
||||
use classic::{CLASSIC_SEED_LENGTH, CLASSIC_SEED_LENGTH_WITH_CHECKSUM, ClassicSeed};
|
||||
|
||||
/// 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 {
|
||||
#[error("invalid number of words in seed")]
|
||||
#[cfg_attr(feature = "std", error("invalid number of words in seed"))]
|
||||
InvalidSeedLength,
|
||||
#[error("unknown language")]
|
||||
#[cfg_attr(feature = "std", error("unknown language"))]
|
||||
UnknownLanguage,
|
||||
#[error("invalid checksum")]
|
||||
#[cfg_attr(feature = "std", error("invalid checksum"))]
|
||||
InvalidChecksum,
|
||||
#[error("english old seeds don't support checksums")]
|
||||
#[cfg_attr(feature = "std", error("english old seeds don't support checksums"))]
|
||||
EnglishOldWithChecksum,
|
||||
#[error("invalid seed")]
|
||||
#[cfg_attr(feature = "std", error("invalid seed"))]
|
||||
InvalidSeed,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use core::{ops::Deref, fmt};
|
||||
use std::io;
|
||||
|
||||
use thiserror::Error;
|
||||
use std_shims::{
|
||||
vec::Vec,
|
||||
io,
|
||||
string::{String, ToString},
|
||||
};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
@ -42,7 +44,9 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod builder;
|
||||
#[cfg(feature = "std")]
|
||||
pub use builder::SignableTransactionBuilder;
|
||||
|
||||
#[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 {
|
||||
#[error("multiple addresses with payment IDs")]
|
||||
#[cfg_attr(feature = "std", error("multiple addresses with payment IDs"))]
|
||||
MultiplePaymentIds,
|
||||
#[error("no inputs")]
|
||||
#[cfg_attr(feature = "std", error("no inputs"))]
|
||||
NoInputs,
|
||||
#[error("no outputs")]
|
||||
#[cfg_attr(feature = "std", error("no outputs"))]
|
||||
NoOutputs,
|
||||
#[error("only one output and no change address")]
|
||||
#[cfg_attr(feature = "std", error("only one output and no change address"))]
|
||||
NoChange,
|
||||
#[error("too many outputs")]
|
||||
#[cfg_attr(feature = "std", error("too many outputs"))]
|
||||
TooManyOutputs,
|
||||
#[error("too much data")]
|
||||
#[cfg_attr(feature = "std", error("too much data"))]
|
||||
TooMuchData,
|
||||
#[error("too many inputs/too much arbitrary data")]
|
||||
#[cfg_attr(feature = "std", error("too many inputs/too much arbitrary data"))]
|
||||
TooLargeTransaction,
|
||||
#[error("not enough funds (in {0}, out {1})")]
|
||||
#[cfg_attr(feature = "std", error("not enough funds (in {0}, out {1})"))]
|
||||
NotEnoughFunds(u64, u64),
|
||||
#[error("wrong spend private key")]
|
||||
#[cfg_attr(feature = "std", error("wrong spend private key"))]
|
||||
WrongPrivateKey,
|
||||
#[error("rpc error ({0})")]
|
||||
#[cfg_attr(feature = "std", error("rpc error ({0})"))]
|
||||
RpcError(RpcError),
|
||||
#[error("clsag error ({0})")]
|
||||
#[cfg_attr(feature = "std", error("clsag error ({0})"))]
|
||||
ClsagError(ClsagError),
|
||||
#[error("invalid transaction ({0})")]
|
||||
#[cfg_attr(feature = "std", error("invalid transaction ({0})"))]
|
||||
InvalidTransaction(RpcError),
|
||||
#[cfg(feature = "multisig")]
|
||||
#[error("frost error {0}")]
|
||||
#[cfg_attr(feature = "std", error("frost error {0}"))]
|
||||
FrostError(FrostError),
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use std::{
|
||||
use std_shims::{
|
||||
vec::Vec,
|
||||
io::{self, Read},
|
||||
sync::{Arc, RwLock},
|
||||
collections::HashMap,
|
||||
};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
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(not(feature = "std"))]
|
||||
|
@ -10,6 +12,13 @@ pub mod sync;
|
|||
pub mod collections;
|
||||
pub mod io;
|
||||
|
||||
pub mod vec {
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub use alloc::vec::*;
|
||||
#[cfg(feature = "std")]
|
||||
pub use std::vec::*;
|
||||
}
|
||||
|
||||
pub mod str {
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub use alloc::str::*;
|
||||
|
@ -17,9 +26,9 @@ pub mod str {
|
|||
pub use std::str::*;
|
||||
}
|
||||
|
||||
pub mod vec {
|
||||
pub mod string {
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub use alloc::vec::*;
|
||||
pub use alloc::string::*;
|
||||
#[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;
|
||||
#[cfg(not(feature = "std"))]
|
||||
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> {
|
||||
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> {
|
||||
if !*self.0.lock() {
|
||||
None
|
||||
} else {
|
||||
self.1.as_ref()
|
||||
}
|
||||
|
||||
}
|
||||
pub fn get_mut(&mut self) -> Option<&mut T> {
|
||||
if !*self.0.lock() {
|
||||
None
|
||||
} else {
|
||||
self.1.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_or_init<F: FnOnce() -> T>(&self, f: F) -> &T {
|
||||
let lock = self.0.lock();
|
||||
if self.1.is_none() {
|
||||
self.1 = Some(f());
|
||||
let mut lock = self.0.lock();
|
||||
if !*lock {
|
||||
unsafe {
|
||||
(core::ptr::addr_of!(self.1) as *mut Option<_>).write_unaligned(Some(f()));
|
||||
}
|
||||
}
|
||||
*lock = true;
|
||||
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 = [
|
||||
"https://github.com/serai-dex/substrate-bip39",
|
||||
"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 }
|
||||
|
||||
monero-generators = { path = "../../coins/monero/generators", default-features = false }
|
||||
monero-serai = { path = "../../coins/monero", default-features = false }
|
||||
|
|
Loading…
Reference in a new issue