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:
Luke Parker 2023-06-29 04:14:29 -04:00 committed by GitHub
parent d25c668ee4
commit ac708b3b2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 487 additions and 261 deletions

23
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"]

View file

@ -1,4 +1,7 @@
use std::io::{self, Read, Write};
use std_shims::{
vec::Vec,
io::{self, Read, Write},
};
use crate::{
serialize::*,

View file

@ -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};

View file

@ -1,4 +1,4 @@
use std_shims::sync::OnceLock;
use std_shims::{vec::Vec, sync::OnceLock};
use rand_core::{RngCore, CryptoRng};

View file

@ -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};

View file

@ -1,4 +1,4 @@
use std_shims::sync::OnceLock;
use std_shims::{vec::Vec, sync::OnceLock};
use rand_core::{RngCore, CryptoRng};

View file

@ -1,4 +1,4 @@
use std_shims::sync::OnceLock;
use std_shims::{vec::Vec, sync::OnceLock};
use rand_core::{RngCore, CryptoRng};

View file

@ -1,4 +1,5 @@
use core::ops::{Add, Sub, Mul, Index};
use std_shims::vec::Vec;
use zeroize::{Zeroize, ZeroizeOnDrop};

View file

@ -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,
}

View file

@ -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;

View file

@ -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;

View 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(),
)
}
}

View file

@ -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,
}
*/
let indexes: OIndexes = self
.bin_call(
"get_o_indexes.bin",
monero_epee_bin_serde::to_bytes(&Request { txid: hash }).unwrap(),
)
.await?;
// Given the immaturity of Rust epee libraries, this is a homegrown one which is only validated
// to work against this specific function
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

View file

@ -1,4 +1,7 @@
use std::io::{self, Read, Write};
use std_shims::{
vec::Vec,
io::{self, Read, Write},
};
use curve25519_dalek::{
scalar::Scalar,

View file

@ -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;

View file

@ -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;

View file

@ -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,
}

View file

@ -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;

View file

@ -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;

View file

@ -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()
}

View file

@ -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};

View file

@ -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};

View file

@ -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,
}

View file

@ -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),
}

View file

@ -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;

View file

@ -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::*;
}

View file

@ -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> {
self.1.as_ref()
if !*self.0.lock() {
None
} else {
self.1.as_ref()
}
}
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 {
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()
}
}
}

View file

@ -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",
]

View file

@ -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 }