Smash out RPC, wallet

This commit is contained in:
Luke Parker
2024-06-16 18:40:15 -04:00
parent 3a1c6c7247
commit d740bd2924
76 changed files with 578 additions and 336 deletions

View File

@@ -18,32 +18,13 @@ workspace = true
[dependencies]
std-shims = { path = "../../common/std-shims", version = "^0.1.1", default-features = false }
async-trait = { version = "0.1", default-features = false }
thiserror = { version = "1", default-features = false, optional = true }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
subtle = { version = "^2.4", default-features = false }
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 }
sha3 = { version = "0.10", default-features = false }
pbkdf2 = { version = "0.12", features = ["simple"], default-features = false }
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
# 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.4", default-features = false }
# Needed for multisig
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], optional = true }
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.8", default-features = false, features = ["ed25519"], optional = true }
monero-io = { path = "io", version = "0.1", default-features = false }
monero-generators = { path = "generators", version = "0.4", default-features = false }
monero-primitives = { path = "primitives", version = "0.1", default-features = false }
@@ -57,22 +38,10 @@ hex = { version = "0.4", default-features = false, features = ["alloc"] }
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
serde_json = { version = "1", default-features = false, features = ["alloc"] }
base58-monero = { version = "2", default-features = false, features = ["check"] }
# Used for the provided HTTP RPC
digest_auth = { version = "0.3", default-features = false, optional = true }
simple-request = { path = "../../common/request", version = "0.1", default-features = false, features = ["tls"], optional = true }
tokio = { version = "1", default-features = false, optional = true }
[build-dependencies]
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.4", default-features = false }
monero-generators = { path = "generators", version = "0.4", default-features = false }
[dev-dependencies]
tokio = { version = "1", features = ["sync", "macros"] }
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["tests"] }
[features]
std = [
"std-shims/std",
@@ -80,17 +49,8 @@ std = [
"thiserror",
"zeroize/std",
"subtle/std",
"rand_core/std",
"rand/std",
"rand_chacha/std",
"rand_distr/std",
"sha3/std",
"pbkdf2/std",
"transcript/std",
"monero-io/std",
"monero-generators/std",
@@ -102,13 +62,9 @@ std = [
"hex/std",
"serde/std",
"serde_json/std",
"base58-monero/std",
]
compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-bulletproofs/compile-time-generators"]
http-rpc = ["digest_auth", "simple-request", "tokio"]
multisig = ["transcript", "frost", "monero-clsag/multisig", "std"]
binaries = ["tokio/rt-multi-thread", "tokio/macros", "http-rpc"]
default = ["std", "http-rpc"]
multisig = ["monero-clsag/multisig", "std"]
#binaries = ["tokio/rt-multi-thread", "tokio/macros", "http-rpc"]
default = ["std"]

View File

@@ -0,0 +1,46 @@
[package]
name = "monero-rpc"
version = "0.1.0"
description = "Trait for an RPC connection to a Monero daemon, built around monero-serai"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/rpc"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
edition = "2021"
rust-version = "1.79"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[dependencies]
std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false }
async-trait = { version = "0.1", default-features = false }
thiserror = { version = "1", default-features = false, optional = true }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
serde_json = { version = "1", default-features = false, features = ["alloc"] }
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
monero-serai = { path = "..", default-features = false }
[features]
std = [
"std-shims/std",
"thiserror",
"zeroize/std",
"hex/std",
"serde/std",
"serde_json/std",
"monero-serai/std",
]
default = ["std"]

21
coins/monero/rpc/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022-2024 Luke Parker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,6 @@
# Monero RPC
Trait for an RPC connection to a Monero daemon, built around monero-serai.
This library is usable under no-std when the `std` feature (on by default) is
disabled.

View File

@@ -0,0 +1,26 @@
[package]
name = "monero-simple-request-rpc"
version = "0.1.0"
description = "RPC connection to a Monero daemon via simple-request, built around monero-serai"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/rpc/simple-request"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
edition = "2021"
rust-version = "1.79"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[dependencies]
async-trait = { version = "0.1", default-features = false }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
digest_auth = { version = "0.3", default-features = false }
simple-request = { path = "../../../../common/request", version = "0.1", default-features = false, features = ["tls"] }
tokio = { version = "1", default-features = false }
monero-rpc = { path = "..", default-features = false, features = ["std"] }

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022-2024 Luke Parker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,3 @@
# Monero simple-request RPC
RPC connection to a Monero daemon via simple-request, built around monero-serai.

View File

@@ -1,3 +1,7 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
// #![deny(missing_docs)] // TODO
use std::{sync::Arc, io::Read, time::Duration};
use async_trait::async_trait;
@@ -10,7 +14,7 @@ use simple_request::{
Response, Client,
};
use crate::rpc::{RpcError, RpcConnection, Rpc};
use monero_rpc::{RpcError, RpcConnection, Rpc};
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
@@ -33,13 +37,13 @@ enum Authentication {
///
/// Requires tokio.
#[derive(Clone, Debug)]
pub struct HttpRpc {
pub struct SimpleRequestRpc {
authentication: Authentication,
url: String,
request_timeout: Duration,
}
impl HttpRpc {
impl SimpleRequestRpc {
fn digest_auth_challenge(
response: &Response,
) -> Result<Option<(WwwAuthenticateHeader, u64)>, RpcError> {
@@ -60,7 +64,7 @@ impl HttpRpc {
///
/// A daemon requiring authentication can be used via including the username and password in the
/// URL.
pub async fn new(url: String) -> Result<Rpc<HttpRpc>, RpcError> {
pub async fn new(url: String) -> Result<Rpc<SimpleRequestRpc>, RpcError> {
Self::with_custom_timeout(url, DEFAULT_TIMEOUT).await
}
@@ -71,7 +75,7 @@ impl HttpRpc {
pub async fn with_custom_timeout(
mut url: String,
request_timeout: Duration,
) -> Result<Rpc<HttpRpc>, RpcError> {
) -> Result<Rpc<SimpleRequestRpc>, RpcError> {
let authentication = if url.contains('@') {
// Parse out the username and password
let url_clone = url;
@@ -119,11 +123,11 @@ impl HttpRpc {
Authentication::Unauthenticated(Client::with_connection_pool())
};
Ok(Rpc(HttpRpc { authentication, url, request_timeout }))
Ok(Rpc::new(SimpleRequestRpc { authentication, url, request_timeout }))
}
}
impl HttpRpc {
impl SimpleRequestRpc {
async fn inner_post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError> {
let request_fn = |uri| {
Request::post(uri)
@@ -277,7 +281,7 @@ impl HttpRpc {
}
#[async_trait]
impl RpcConnection for HttpRpc {
impl RpcConnection for SimpleRequestRpc {
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError> {
tokio::time::timeout(self.request_timeout, self.inner_post(route, body))
.await

View File

@@ -1,3 +1,8 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
// #![deny(missing_docs)] // TODO
#![cfg_attr(not(feature = "std"), no_std)]
use core::fmt::Debug;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
@@ -7,33 +12,72 @@ use std_shims::{
string::{String, ToString},
};
use zeroize::Zeroize;
use async_trait::async_trait;
use curve25519_dalek::edwards::EdwardsPoint;
use monero_io::decompress_point;
use serde::{Serialize, Deserialize, de::DeserializeOwned};
use serde_json::{Value, json};
use crate::{
use monero_serai::{
io::*,
Protocol,
serialize::*,
transaction::{Input, Timelock, Transaction},
block::Block,
wallet::{FeePriority, Fee},
};
#[cfg(feature = "http-rpc")]
mod http;
#[cfg(feature = "http-rpc")]
pub use http::*;
// Number of blocks the fee estimate will be valid for
// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/
// src/wallet/wallet2.cpp#L121
const GRACE_BLOCKS_FOR_FEE_ESTIMATE: u64 = 10;
/// Fee struct, defined as a per-unit cost and a mask for rounding purposes.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Fee {
pub per_weight: u64,
pub mask: u64,
}
impl Fee {
pub fn calculate_fee_from_weight(&self, weight: usize) -> u64 {
let fee = (((self.per_weight * u64::try_from(weight).unwrap()) + self.mask - 1) / self.mask) *
self.mask;
debug_assert_eq!(weight, self.calculate_weight_from_fee(fee), "Miscalculated weight from fee");
fee
}
pub fn calculate_weight_from_fee(&self, fee: u64) -> usize {
usize::try_from(fee / self.per_weight).unwrap()
}
}
/// Fee priority, determining how quickly a transaction is included in a block.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[allow(non_camel_case_types)]
pub enum FeePriority {
Unimportant,
Normal,
Elevated,
Priority,
Custom { priority: u32 },
}
/// https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/
/// src/simplewallet/simplewallet.cpp#L161
impl FeePriority {
pub(crate) fn fee_priority(&self) -> u32 {
match self {
FeePriority::Unimportant => 1,
FeePriority::Normal => 2,
FeePriority::Elevated => 3,
FeePriority::Priority => 4,
FeePriority::Custom { priority, .. } => *priority,
}
}
}
#[derive(Deserialize, Debug)]
pub struct EmptyResponse {}
#[derive(Deserialize, Debug)]
@@ -132,6 +176,10 @@ pub trait RpcConnection: Clone + Debug {
#[derive(Clone, Debug)]
pub struct Rpc<R: RpcConnection>(R);
impl<R: RpcConnection> Rpc<R> {
pub fn new(connection: R) -> Self {
Self(connection)
}
/// Perform a RPC call to the specified route with the provided parameters.
///
/// This is NOT a JSON-RPC call. They use a route of "json_rpc" and are available via

View File

@@ -1,3 +1,4 @@
/* TODO
#[cfg(feature = "binaries")]
mod binaries {
pub(crate) use std::sync::Arc;
@@ -12,14 +13,14 @@ mod binaries {
ringct::{RctPrunable, bulletproofs::BatchVerifier},
transaction::{Input, Transaction},
block::Block,
rpc::{RpcError, Rpc, HttpRpc},
rpc::{RpcError, Rpc, SimpleRequestRpc},
};
pub(crate) use monero_io::decompress_point;
pub(crate) use tokio::task::JoinHandle;
pub(crate) async fn check_block(rpc: Arc<Rpc<HttpRpc>>, block_i: usize) {
pub(crate) async fn check_block(rpc: Arc<Rpc<SimpleRequestRpc>>, block_i: usize) {
let hash = loop {
match rpc.get_block_hash(block_i).await {
Ok(hash) => break hash,
@@ -157,7 +158,7 @@ mod binaries {
}
async fn get_outs(
rpc: &Rpc<HttpRpc>,
rpc: &Rpc<SimpleRequestRpc>,
amount: u64,
indexes: &[u64],
) -> Vec<[EdwardsPoint; 2]> {
@@ -268,9 +269,9 @@ async fn main() {
let nodes = if specified_nodes.is_empty() { default_nodes } else { specified_nodes };
let rpc = |url: String| async move {
HttpRpc::new(url.clone())
SimpleRequestRpc::new(url.clone())
.await
.unwrap_or_else(|_| panic!("couldn't create HttpRpc connected to {url}"))
.unwrap_or_else(|_| panic!("couldn't create SimpleRequestRpc connected to {url}"))
};
let main_rpc = rpc(nodes[0].clone()).await;
let mut rpcs = vec![];
@@ -315,3 +316,6 @@ async fn main() {
fn main() {
panic!("To run binaries, please build with `--feature binaries`.");
}
*/
fn main() {}

View File

@@ -27,14 +27,6 @@ pub mod transaction;
/// Block structs.
pub mod block;
/// Monero daemon RPC interface.
pub mod rpc;
/// Wallet functionality, enabling scanning and sending transactions.
pub mod wallet;
#[cfg(test)]
mod tests;
pub const DEFAULT_LOCK_WINDOW: usize = 10;
pub const COINBASE_LOCK_WINDOW: usize = 60;
pub const BLOCK_TIME: usize = 120;
@@ -106,7 +98,7 @@ impl Protocol {
}
}
pub(crate) fn write<W: stdio::Write>(&self, w: &mut W) -> stdio::Result<()> {
pub fn write<W: stdio::Write>(&self, w: &mut W) -> stdio::Result<()> {
match self {
Protocol::v14 => w.write_all(&[0, 14]),
Protocol::v16 => w.write_all(&[0, 16]),
@@ -122,7 +114,7 @@ impl Protocol {
}
}
pub(crate) fn read<R: stdio::Read>(r: &mut R) -> stdio::Result<Protocol> {
pub fn read<R: stdio::Read>(r: &mut R) -> stdio::Result<Protocol> {
Ok(match read_byte(r)? {
// Monero protocol
0 => match read_byte(r)? {

View File

@@ -134,7 +134,7 @@ pub struct RctBase {
}
impl RctBase {
pub(crate) fn fee_weight(outputs: usize, fee: u64) -> usize {
pub fn fee_weight(outputs: usize, fee: u64) -> usize {
// 1 byte for the RCT signature type
1 + (outputs * (8 + 32)) + varint_len(fee)
}
@@ -227,7 +227,7 @@ pub enum RctPrunable {
}
impl RctPrunable {
pub(crate) fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize) -> usize {
pub fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize) -> usize {
// 1 byte for number of BPs (technically a VarInt, yet there's always just zero or one)
1 + Bulletproof::fee_weight(protocol.bp_plus(), outputs) +
(inputs * (Clsag::fee_weight(protocol.ring_len()) + 32))
@@ -383,7 +383,7 @@ impl RctSignatures {
}
}
pub(crate) fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize, fee: u64) -> usize {
pub fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize, fee: u64) -> usize {
RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(protocol, inputs, outputs)
}

View File

@@ -23,7 +23,7 @@ pub enum Input {
}
impl Input {
pub(crate) fn fee_weight(offsets_weight: usize) -> usize {
pub fn fee_weight(offsets_weight: usize) -> usize {
// Uses 1 byte for the input type
// Uses 1 byte for the VarInt amount due to amount being 0
1 + 1 + offsets_weight + 32
@@ -82,7 +82,7 @@ pub struct Output {
}
impl Output {
pub(crate) fn fee_weight(view_tags: bool) -> usize {
pub fn fee_weight(view_tags: bool) -> usize {
// Uses 1 byte for the output type
// Uses 1 byte for the VarInt amount due to amount being 0
1 + 1 + 32 + if view_tags { 1 } else { 0 }
@@ -182,7 +182,7 @@ pub struct TransactionPrefix {
}
impl TransactionPrefix {
pub(crate) fn fee_weight(
pub fn fee_weight(
decoy_weights: &[usize],
outputs: usize,
view_tags: bool,
@@ -254,7 +254,7 @@ pub struct Transaction {
}
impl Transaction {
pub(crate) fn fee_weight(
pub fn fee_weight(
protocol: Protocol,
decoy_weights: &[usize],
outputs: usize,

View File

@@ -0,0 +1,88 @@
[package]
name = "monero-wallet"
version = "0.1.0"
description = "Wallet functionality for the Monero protocol, built around monero-serai"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/wallet"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
edition = "2021"
rust-version = "1.79"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[dependencies]
std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false }
async-trait = { version = "0.1", default-features = false }
thiserror = { version = "1", default-features = false, optional = true }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
subtle = { version = "^2.4", default-features = false }
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 }
sha3 = { version = "0.10", default-features = false }
pbkdf2 = { version = "0.12", features = ["simple"], default-features = false }
group = { version = "0.13", default-features = false }
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize", "group"] }
# Multisig dependencies
transcript = { package = "flexible-transcript", path = "../../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], optional = true }
dalek-ff-group = { path = "../../../crypto/dalek-ff-group", version = "0.4", default-features = false, optional = true }
frost = { package = "modular-frost", path = "../../../crypto/frost", default-features = false, features = ["ed25519"], optional = true }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
base58-monero = { version = "2", default-features = false, features = ["check"] }
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
serde_json = { version = "1", default-features = false, features = ["alloc"] }
monero-serai = { path = "..", default-features = false }
monero-rpc = { path = "../rpc", default-features = false }
[dev-dependencies]
hex-literal = "0.4"
frost = { package = "modular-frost", path = "../../../crypto/frost", default-features = false, features = ["ed25519", "tests"] }
tokio = { version = "1", features = ["sync", "macros"] }
monero-simple-request-rpc = { path = "../rpc/simple-request", default-features = false }
[features]
std = [
"std-shims/std",
"thiserror",
"zeroize/std",
"subtle/std",
"rand_core/std",
"rand/std",
"rand_chacha/std",
"rand_distr/std",
"sha3/std",
"pbkdf2/std",
"hex/std",
"base58-monero/std",
"serde/std",
"serde_json/std",
"monero-serai/std",
"monero-rpc/std",
]
multisig = ["transcript", "dalek-ff-group", "frost", "monero-serai/multisig", "std"]
default = ["std"]

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022-2024 Luke Parker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,6 @@
# Monero Wallet
Wallet functionality for the Monero protocol, built around monero-serai.
This library is usable under no-std when the `std` feature (on by default) is
disabled.

View File

@@ -5,7 +5,7 @@ use zeroize::Zeroize;
use curve25519_dalek::edwards::EdwardsPoint;
use crate::io::decompress_point;
use monero_serai::io::decompress_point;
use base58_monero::base58::{encode_check, decode_check};

View File

@@ -9,11 +9,9 @@ use rand_distr::num_traits::Float;
use curve25519_dalek::edwards::EdwardsPoint;
use crate::{
wallet::SpendableOutput,
rpc::{RpcError, RpcConnection, Rpc},
DEFAULT_LOCK_WINDOW, COINBASE_LOCK_WINDOW, BLOCK_TIME,
};
use monero_serai::{DEFAULT_LOCK_WINDOW, COINBASE_LOCK_WINDOW, BLOCK_TIME};
use monero_rpc::{RpcError, RpcConnection, Rpc};
use crate::SpendableOutput;
const RECENT_WINDOW: usize = 15;
const BLOCKS_PER_YEAR: usize = 365 * 24 * 60 * 60 / BLOCK_TIME;
@@ -274,7 +272,7 @@ async fn select_decoys<R: RngCore + CryptoRng, RPC: RpcConnection>(
Ok(res)
}
pub use monero_primitives::Decoys;
pub use monero_serai::primitives::Decoys;
// TODO: Remove this trait
#[cfg(feature = "std")]

View File

@@ -8,7 +8,7 @@ use zeroize::Zeroize;
use curve25519_dalek::edwards::EdwardsPoint;
use crate::io::*;
use monero_serai::io::*;
pub const MAX_TX_EXTRA_PADDING_COUNT: usize = 255;
pub const MAX_TX_EXTRA_NONCE_SIZE: usize = 255;
@@ -209,7 +209,7 @@ impl Extra {
}
#[rustfmt::skip]
pub(crate) fn fee_weight(
pub fn fee_weight(
outputs: usize,
additional: bool,
payment_id: bool,

View File

@@ -1,3 +1,8 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
// #![deny(missing_docs)] // TODO
#![cfg_attr(not(feature = "std"), no_std)]
use core::ops::Deref;
use std_shims::collections::{HashSet, HashMap};
@@ -9,12 +14,15 @@ use curve25519_dalek::{
edwards::{EdwardsPoint, CompressedEdwardsY},
};
use crate::{
use monero_serai::{
io::write_varint,
primitives::{Commitment, keccak256, keccak256_to_scalar},
ringct::EncryptedAmount,
transaction::Input,
};
pub use monero_serai as monero;
pub use monero_rpc as rpc;
pub mod extra;
pub(crate) use extra::{PaymentId, ExtraField, Extra};
@@ -33,7 +41,7 @@ pub use scan::{ReceivedOutput, SpendableOutput, Timelocked};
pub mod decoys;
#[cfg(not(feature = "std"))]
pub mod decoys {
pub use monero_primitives::Decoys;
pub use monero_serai::primitives::Decoys;
pub trait DecoySelection {}
}
pub use decoys::{DecoySelection, Decoys};
@@ -47,6 +55,9 @@ pub(crate) use send::InternalPayment;
#[cfg(feature = "multisig")]
pub use send::TransactionMachine;
#[cfg(test)]
mod tests;
fn key_image_sort(x: &EdwardsPoint, y: &EdwardsPoint) -> core::cmp::Ordering {
x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse()
}
@@ -109,12 +120,19 @@ pub(crate) fn compact_amount_encryption(amount: u64, key: Scalar) -> [u8; 8] {
(amount ^ u64::from_le_bytes(keccak256(amount_mask)[.. 8].try_into().unwrap())).to_le_bytes()
}
impl EncryptedAmount {
pub trait EncryptedAmountExt {
/// Decrypt an EncryptedAmount into the Commitment it encrypts.
///
/// The caller must verify the decrypted Commitment matches with the actual Commitment used
/// within in the Monero protocol.
pub fn decrypt(&self, key: Scalar) -> Commitment {
fn decrypt(&self, key: Scalar) -> Commitment;
}
impl EncryptedAmountExt for EncryptedAmount {
/// Decrypt an EncryptedAmount into the Commitment it encrypts.
///
/// The caller must verify the decrypted Commitment matches with the actual Commitment used
/// within in the Monero protocol.
fn decrypt(&self, key: Scalar) -> Commitment {
match self {
// TODO: Add a test vector for this
EncryptedAmount::Original { mask, amount } => {

View File

@@ -9,15 +9,15 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint};
use monero_io::decompress_point;
use crate::{
use monero_rpc::{RpcError, RpcConnection, Rpc};
use monero_serai::{
io::*,
primitives::Commitment,
transaction::{Input, Timelock, Transaction},
block::Block,
rpc::{RpcError, RpcConnection, Rpc},
wallet::{PaymentId, Extra, address::SubaddressIndex, Scanner, uniqueness, shared_key},
};
use crate::{
PaymentId, Extra, address::SubaddressIndex, Scanner, EncryptedAmountExt, uniqueness, shared_key,
};
/// An absolute output ID, defined as its transaction hash and output index.

View File

@@ -11,7 +11,7 @@ use rand_core::{RngCore, CryptoRng};
use curve25519_dalek::scalar::Scalar;
use crate::wallet::seed::SeedError;
use crate::seed::SeedError;
pub(crate) const CLASSIC_SEED_LENGTH: usize = 24;
pub(crate) const CLASSIC_SEED_LENGTH_WITH_CHECKSUM: usize = 25;

View File

@@ -2,12 +2,10 @@ use std::sync::{Arc, RwLock};
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
use monero_serai::Protocol;
use crate::{
Protocol,
wallet::{
address::MoneroAddress, Fee, SpendableOutput, Change, Decoys, SignableTransaction,
TransactionError, extra::MAX_ARBITRARY_DATA_SIZE,
},
address::MoneroAddress, Fee, SpendableOutput, Change, Decoys, SignableTransaction,
TransactionError, extra::MAX_ARBITRARY_DATA_SIZE,
};
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]

View File

@@ -17,14 +17,13 @@ use curve25519_dalek::{
scalar::Scalar,
edwards::EdwardsPoint,
};
use dalek_ff_group as dfg;
#[cfg(feature = "multisig")]
use frost::FrostError;
use monero_io::varint_len;
use crate::{
use monero_rpc::RpcError;
pub use monero_rpc::{Fee, FeePriority};
use monero_serai::{
io::*,
primitives::{Commitment, keccak256},
Protocol,
@@ -35,13 +34,12 @@ use crate::{
RctBase, RctPrunable, RctSignatures,
},
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
rpc::RpcError,
wallet::{
address::{Network, AddressSpec, MoneroAddress},
ViewPair, SpendableOutput, Decoys, PaymentId, ExtraField, Extra, key_image_sort, uniqueness,
shared_key, commitment_mask, compact_amount_encryption,
extra::{ARBITRARY_DATA_MARKER, MAX_ARBITRARY_DATA_SIZE},
},
};
use crate::{
address::{Network, AddressSpec, MoneroAddress},
ViewPair, SpendableOutput, Decoys, PaymentId, ExtraField, Extra, key_image_sort, uniqueness,
shared_key, commitment_mask, compact_amount_encryption,
extra::{ARBITRARY_DATA_MARKER, MAX_ARBITRARY_DATA_SIZE},
};
#[cfg(feature = "std")]
@@ -53,7 +51,7 @@ pub use builder::SignableTransactionBuilder;
mod multisig;
#[cfg(feature = "multisig")]
pub use multisig::TransactionMachine;
use crate::ringct::EncryptedAmount;
use monero_serai::ringct::EncryptedAmount;
#[allow(non_snake_case)]
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
@@ -264,51 +262,6 @@ fn calculate_weight_and_fee(
(weight, fee)
}
/// Fee struct, defined as a per-unit cost and a mask for rounding purposes.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Fee {
pub per_weight: u64,
pub mask: u64,
}
impl Fee {
pub fn calculate_fee_from_weight(&self, weight: usize) -> u64 {
let fee = (((self.per_weight * u64::try_from(weight).unwrap()) + self.mask - 1) / self.mask) *
self.mask;
debug_assert_eq!(weight, self.calculate_weight_from_fee(fee), "Miscalculated weight from fee");
fee
}
pub fn calculate_weight_from_fee(&self, fee: u64) -> usize {
usize::try_from(fee / self.per_weight).unwrap()
}
}
/// Fee priority, determining how quickly a transaction is included in a block.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[allow(non_camel_case_types)]
pub enum FeePriority {
Unimportant,
Normal,
Elevated,
Priority,
Custom { priority: u32 },
}
/// https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/
/// src/simplewallet/simplewallet.cpp#L161
impl FeePriority {
pub(crate) fn fee_priority(&self) -> u32 {
match self {
FeePriority::Unimportant => 1,
FeePriority::Normal => 2,
FeePriority::Elevated => 3,
FeePriority::Priority => 4,
FeePriority::Custom { priority, .. } => *priority,
}
}
}
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub(crate) enum InternalPayment {
Payment((MoneroAddress, u64), bool),
@@ -694,7 +647,7 @@ impl SignableTransaction {
} else {
// If this used tx_key, randomize its R
// This is so when extra is created, there's a distinct R for it to use
output.R = dfg::EdwardsPoint::random(&mut rng).0;
output.R = EdwardsPoint::random(&mut rng);
}
(output, payment_id)
}

View File

@@ -24,14 +24,14 @@ use frost::{
},
};
use crate::{
use monero_serai::{
ringct::{
clsag::{ClsagContext, ClsagMultisigMaskSender, ClsagAddendum, ClsagMultisig},
RctPrunable,
},
transaction::{Input, Transaction},
wallet::{TransactionError, InternalPayment, SignableTransaction, key_image_sort, uniqueness},
};
use crate::{TransactionError, InternalPayment, SignableTransaction, key_image_sort, uniqueness};
/// FROST signing machine to produce a signed transaction.
pub struct TransactionMachine {

View File

@@ -4,9 +4,9 @@ use rand_core::{RngCore, OsRng};
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
use monero_io::decompress_point;
use monero_serai::io::decompress_point;
use crate::wallet::address::{Network, AddressType, AddressMeta, MoneroAddress};
use crate::address::{Network, AddressType, AddressMeta, MoneroAddress};
const SPEND: [u8; 32] = hex!("f8631661f6ab4e6fda310c797330d86e23a682f20d5bc8cc27b18051191f16d7");
const VIEW: [u8; 32] = hex!("4a1535063ad1fee2dabbf909d4fd9a873e29541b401f0944754e17c9a41820ce");

View File

@@ -1,10 +1,8 @@
use crate::{
wallet::{ExtraField, Extra, extra::MAX_TX_EXTRA_PADDING_COUNT},
serialize::write_varint,
};
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
use monero_serai::io::write_varint;
use crate::{ExtraField, Extra, extra::MAX_TX_EXTRA_PADDING_COUNT};
// Borrowed tests from
// https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/
// tests/unit_tests/test_tx_utils.cpp

View File

@@ -4,9 +4,9 @@ use rand_core::OsRng;
use curve25519_dalek::scalar::Scalar;
use monero_primitives::keccak256;
use monero_serai::primitives::keccak256;
use crate::wallet::seed::{
use crate::seed::{
Seed, SeedType, SeedError,
classic::{self, trim_by_lang},
polyseed,

View File

@@ -1,7 +1,5 @@
use monero_serai::{
transaction::Transaction,
wallet::{TransactionError, extra::MAX_ARBITRARY_DATA_SIZE},
};
use monero_serai::transaction::Transaction;
use monero_wallet::{TransactionError, extra::MAX_ARBITRARY_DATA_SIZE};
mod runner;

View File

@@ -1,9 +1,6 @@
use monero_serai::{
transaction::Transaction,
wallet::SpendableOutput,
rpc::{Rpc, OutputResponse},
Protocol, DEFAULT_LOCK_WINDOW,
};
use monero_rpc::{Rpc, OutputResponse};
use monero_serai::{transaction::Transaction, Protocol, DEFAULT_LOCK_WINDOW};
use monero_wallet::SpendableOutput;
mod runner;

View File

@@ -1,11 +1,9 @@
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
use monero_serai::{
transaction::Transaction,
wallet::{
Eventuality,
address::{AddressType, AddressMeta, MoneroAddress},
},
use monero_serai::transaction::Transaction;
use monero_wallet::{
Eventuality,
address::{AddressType, AddressMeta, MoneroAddress},
};
mod runner;

View File

@@ -8,15 +8,13 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
use tokio::sync::Mutex;
use monero_serai::{
rpc::{HttpRpc, Rpc},
wallet::{
ViewPair, Scanner,
address::{Network, AddressType, AddressSpec, AddressMeta, MoneroAddress},
SpendableOutput, Fee,
},
transaction::Transaction,
DEFAULT_LOCK_WINDOW,
use monero_rpc::Rpc;
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_serai::{transaction::Transaction, DEFAULT_LOCK_WINDOW};
use monero_wallet::{
ViewPair, Scanner,
address::{Network, AddressType, AddressSpec, AddressMeta, MoneroAddress},
SpendableOutput, Fee,
};
pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) {
@@ -36,7 +34,7 @@ pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) {
// TODO: Support transactions already on-chain
// TODO: Don't have a side effect of mining blocks more blocks than needed under race conditions
pub async fn mine_until_unlocked(rpc: &Rpc<HttpRpc>, addr: &str, tx_hash: [u8; 32]) {
pub async fn mine_until_unlocked(rpc: &Rpc<SimpleRequestRpc>, addr: &str, tx_hash: [u8; 32]) {
// mine until tx is in a block
let mut height = rpc.get_height().await.unwrap();
let mut found = false;
@@ -66,7 +64,7 @@ pub async fn mine_until_unlocked(rpc: &Rpc<HttpRpc>, addr: &str, tx_hash: [u8; 3
// Mines 60 blocks and returns an unlocked miner TX output.
#[allow(dead_code)]
pub async fn get_miner_tx_output(rpc: &Rpc<HttpRpc>, view: &ViewPair) -> SpendableOutput {
pub async fn get_miner_tx_output(rpc: &Rpc<SimpleRequestRpc>, view: &ViewPair) -> SpendableOutput {
let mut scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
// Mine 60 blocks to unlock a miner TX
@@ -92,8 +90,9 @@ pub fn check_weight_and_fee(tx: &Transaction, fee_rate: Fee) {
assert_eq!(fee, expected_fee);
}
pub async fn rpc() -> Rpc<HttpRpc> {
let rpc = HttpRpc::new("http://serai:seraidex@127.0.0.1:18081".to_string()).await.unwrap();
pub async fn rpc() -> Rpc<SimpleRequestRpc> {
let rpc =
SimpleRequestRpc::new("http://serai:seraidex@127.0.0.1:18081".to_string()).await.unwrap();
// Only run once
if rpc.get_height().await.unwrap() != 1 {
@@ -171,12 +170,10 @@ macro_rules! test {
tests::{THRESHOLD, key_gen},
};
use monero_serai::{
wallet::{
address::{Network, AddressSpec},
ViewPair, Scanner, Change, DecoySelection, Decoys, FeePriority,
SignableTransaction, SignableTransactionBuilder,
},
use monero_wallet::{
address::{Network, AddressSpec},
ViewPair, Scanner, Change, DecoySelection, Decoys, FeePriority,
SignableTransaction, SignableTransactionBuilder,
};
use runner::{

View File

@@ -1,9 +1,7 @@
use rand::RngCore;
use rand_core::RngCore;
use monero_serai::{
transaction::Transaction,
wallet::{address::SubaddressIndex, extra::PaymentId},
};
use monero_serai::transaction::Transaction;
use monero_wallet::{address::SubaddressIndex, extra::PaymentId};
mod runner;

View File

@@ -1,13 +1,11 @@
use rand_core::OsRng;
use monero_serai::{
transaction::Transaction,
wallet::{
extra::Extra, address::SubaddressIndex, ReceivedOutput, SpendableOutput, DecoySelection,
Decoys, SignableTransactionBuilder,
},
rpc::{Rpc, HttpRpc},
Protocol,
use monero_serai::{transaction::Transaction, Protocol};
use monero_rpc::Rpc;
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{
extra::Extra, address::SubaddressIndex, ReceivedOutput, SpendableOutput, DecoySelection, Decoys,
SignableTransactionBuilder,
};
mod runner;
@@ -15,7 +13,7 @@ mod runner;
// Set up inputs, select decoys, then add them to the TX builder
async fn add_inputs(
protocol: Protocol,
rpc: &Rpc<HttpRpc>,
rpc: &Rpc<SimpleRequestRpc>,
outputs: Vec<ReceivedOutput>,
builder: &mut SignableTransactionBuilder,
) {
@@ -101,7 +99,7 @@ test!(
),
(
|protocol, rpc: Rpc<_>, _, _, outputs: Vec<ReceivedOutput>| async move {
use monero_serai::wallet::FeePriority;
use monero_wallet::FeePriority;
let change_view = ViewPair::new(
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
@@ -290,7 +288,7 @@ test!(
),
(
|protocol, rpc: Rpc<_>, _, addr, outputs: Vec<ReceivedOutput>| async move {
use monero_serai::wallet::FeePriority;
use monero_wallet::FeePriority;
let mut builder = SignableTransactionBuilder::new(
protocol,

View File

@@ -5,19 +5,18 @@ use rand_core::{OsRng, RngCore};
use serde::Deserialize;
use serde_json::json;
use monero_serai::{
transaction::Transaction,
rpc::{EmptyResponse, HttpRpc, Rpc},
wallet::{
address::{Network, AddressSpec, SubaddressIndex, MoneroAddress},
extra::{MAX_TX_EXTRA_NONCE_SIZE, Extra, PaymentId},
Scanner,
},
use monero_serai::transaction::Transaction;
use monero_rpc::{EmptyResponse, Rpc};
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{
address::{Network, AddressSpec, SubaddressIndex, MoneroAddress},
extra::{MAX_TX_EXTRA_NONCE_SIZE, Extra, PaymentId},
Scanner,
};
mod runner;
async fn make_integrated_address(rpc: &Rpc<HttpRpc>, payment_id: [u8; 8]) -> String {
async fn make_integrated_address(rpc: &Rpc<SimpleRequestRpc>, payment_id: [u8; 8]) -> String {
#[derive(Debug, Deserialize)]
struct IntegratedAddressResponse {
integrated_address: String,
@@ -34,8 +33,8 @@ async fn make_integrated_address(rpc: &Rpc<HttpRpc>, payment_id: [u8; 8]) -> Str
res.integrated_address
}
async fn initialize_rpcs() -> (Rpc<HttpRpc>, Rpc<HttpRpc>, String) {
let wallet_rpc = HttpRpc::new("http://127.0.0.1:18082".to_string()).await.unwrap();
async fn initialize_rpcs() -> (Rpc<SimpleRequestRpc>, Rpc<SimpleRequestRpc>, String) {
let wallet_rpc = SimpleRequestRpc::new("http://127.0.0.1:18082".to_string()).await.unwrap();
let daemon_rpc = runner::rpc().await;
#[derive(Debug, Deserialize)]
@@ -174,7 +173,7 @@ test!(
.add_payment(MoneroAddress::from_str(Network::Mainnet, &wallet_rpc_addr).unwrap(), 1000000);
(builder.build().unwrap(), wallet_rpc)
},
|_, tx: Transaction, _, data: Rpc<HttpRpc>| async move {
|_, tx: Transaction, _, data: Rpc<SimpleRequestRpc>| async move {
// confirm receipt
let _: EmptyResponse = data.json_rpc_call("refresh", None).await.unwrap();
let transfer: TransfersResponse = data
@@ -208,7 +207,7 @@ test!(
.add_payment(MoneroAddress::from_str(Network::Mainnet, &addr.address).unwrap(), 1000000);
(builder.build().unwrap(), (wallet_rpc, addr.account_index))
},
|_, tx: Transaction, _, data: (Rpc<HttpRpc>, u32)| async move {
|_, tx: Transaction, _, data: (Rpc<SimpleRequestRpc>, u32)| async move {
// confirm receipt
let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap();
let transfer: TransfersResponse = data
@@ -260,7 +259,7 @@ test!(
]);
(builder.build().unwrap(), (wallet_rpc, daemon_rpc, addrs.address_index))
},
|_, tx: Transaction, _, data: (Rpc<HttpRpc>, Rpc<HttpRpc>, u32)| async move {
|_, tx: Transaction, _, data: (Rpc<SimpleRequestRpc>, Rpc<SimpleRequestRpc>, u32)| async move {
// confirm receipt
let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap();
let transfer: TransfersResponse = data
@@ -305,7 +304,7 @@ test!(
builder.add_payment(MoneroAddress::from_str(Network::Mainnet, &addr).unwrap(), 1000000);
(builder.build().unwrap(), (wallet_rpc, payment_id))
},
|_, tx: Transaction, _, data: (Rpc<HttpRpc>, [u8; 8])| async move {
|_, tx: Transaction, _, data: (Rpc<SimpleRequestRpc>, [u8; 8])| async move {
// confirm receipt
let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap();
let transfer: TransfersResponse = data
@@ -340,7 +339,7 @@ test!(
(builder.build().unwrap(), wallet_rpc)
},
|_, tx: Transaction, _, data: Rpc<HttpRpc>| async move {
|_, tx: Transaction, _, data: Rpc<SimpleRequestRpc>| async move {
// confirm receipt
let _: EmptyResponse = data.json_rpc_call("refresh", None).await.unwrap();
let transfer: TransfersResponse = data