mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 04:09:23 +00:00
Clean the Monero lib for auditing (#577)
* Remove unsafe creation of dalek_ff_group::EdwardsPoint in BP+ * Rename Bulletproofs to Bulletproof, since they are a single Bulletproof Also bifurcates prove with prove_plus, and adds a few documentation items. * Make CLSAG signing private Also adds a bit more documentation and does a bit more tidying. * Remove the distribution cache It's a notable bandwidth/performance improvement, yet it's not ready. We need a dedicated Distribution struct which is managed by the wallet and passed in. While we can do that now, it's not currently worth the effort. * Tidy Borromean/MLSAG a tad * Remove experimental feature from monero-serai * Move amount_decryption into EncryptedAmount::decrypt * Various RingCT doc comments * Begin crate smashing * Further documentation, start shoring up API boundaries of existing crates * Document and clean clsag * Add a dedicated send/recv CLSAG mask struct Abstracts the types used internally. Also moves the tests from monero-serai to monero-clsag. * Smash out monero-bulletproofs Removes usage of dalek-ff-group/multiexp for curve25519-dalek. Makes compiling in the generators an optional feature. Adds a structured batch verifier which should be notably more performant. Documentation and clean up still necessary. * Correct no-std builds for monero-clsag and monero-bulletproofs * Tidy and document monero-bulletproofs I still don't like the impl of the original Bulletproofs... * Error if missing documentation * Smash out MLSAG * Smash out Borromean * Tidy up monero-serai as a meta crate * Smash out RPC, wallet * Document the RPC * Improve docs a bit * Move Protocol to monero-wallet * Incomplete work on using Option to remove panic cases * Finish documenting monero-serai * Remove TODO on reading pseudo_outs for AggregateMlsagBorromean * Only read transactions with one Input::Gen or all Input::ToKey Also adds a helper to fetch a transaction's prefix. * Smash out polyseed * Smash out seed * Get the repo to compile again * Smash out Monero addresses * Document cargo features Credit to @hinto-janai for adding such sections to their work on documenting monero-serai in #568. * Fix deserializing v2 miner transactions * Rewrite monero-wallet's send code I have yet to redo the multisig code and the builder. This should be much cleaner, albeit slower due to redoing work. This compiles with clippy --all-features. I have to finish the multisig/builder for --all-targets to work (and start updating the rest of Serai). * Add SignableTransaction Read/Write * Restore Monero multisig TX code * Correct invalid RPC type def in monero-rpc * Update monero-wallet tests to compile Some are _consistently_ failing due to the inputs we attempt to spend being too young. I'm unsure what's up with that. Most seem to pass _consistently_, implying it's not a random issue yet some configuration/env aspect. * Clean and document monero-address * Sync rest of repo with monero-serai changes * Represent height/block number as a u32 * Diversify ViewPair/Scanner into ViewPair/GuaranteedViewPair and Scanner/GuaranteedScanner Also cleans the Scanner impl. * Remove non-small-order view key bound Guaranteed addresses are in fact guaranteed even with this due to prefixing key images causing zeroing the ECDH to not zero the shared key. * Finish documenting monero-serai * Correct imports for no-std * Remove possible panic in monero-serai on systems < 32 bits This was done by requiring the system's usize can represent a certain number. * Restore the reserialize chain binary * fmt, machete, GH CI * Correct misc TODOs in monero-serai * Have Monero test runner evaluate an Eventuality for all signed TXs * Fix a pair of bugs in the decoy tests Unfortunately, this test is still failing. * Fix remaining bugs in monero-wallet tests * Reject torsioned spend keys to ensure we can spend the outputs we scan * Tidy inlined epee code in the RPC * Correct the accidental swap of stagenet/testnet address bytes * Remove unused dep from processor * Handle Monero fee logic properly in the processor * Document v2 TX/RCT output relation assumed when scanning * Adjust how we mine the initial blocks due to some CI test failures * Fix weight estimation for RctType::ClsagBulletproof TXs * Again increase the amount of blocks we mine prior to running tests * Correct the if check about when to mine blocks on start Finally fixes the lack of decoy candidates failures in CI. * Run Monero on Debian, even for internal testnets Change made due to a segfault incurred when locally testing. https://github.com/monero-project/monero/issues/9141 for the upstream. * Don't attempt running tests on the verify-chain binary Adds a minimum XMR fee to the processor and runs fmt. * Increase minimum Monero fee in processor I'm truly unsure why this is required right now. * Distinguish fee from necessary_fee in monero-wallet If there's no change, the fee is difference of the inputs to the outputs. The prior code wouldn't check that amount is greater than or equal to the necessary fee, and returning the would-be change amount as the fee isn't necessarily helpful. Now the fee is validated in such cases and the necessary fee is returned, enabling operating off of that. * Restore minimum Monero fee from develop
This commit is contained in:
@@ -27,7 +27,8 @@ rand_core = { version = "0.6", default-features = false }
|
||||
curve25519-dalek = { version = "4", features = ["rand_core"] }
|
||||
|
||||
bitcoin-serai = { path = "../../coins/bitcoin" }
|
||||
monero-serai = { path = "../../coins/monero" }
|
||||
monero-simple-request-rpc = { path = "../../coins/monero/rpc/simple-request" }
|
||||
monero-wallet = { path = "../../coins/monero/wallet" }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3" }
|
||||
serde = "1"
|
||||
|
||||
@@ -53,8 +53,9 @@ impl Handles {
|
||||
pub async fn monero(
|
||||
&self,
|
||||
ops: &DockerOperations,
|
||||
) -> monero_serai::rpc::Rpc<monero_serai::rpc::HttpRpc> {
|
||||
use monero_serai::rpc::HttpRpc;
|
||||
) -> monero_simple_request_rpc::SimpleRequestRpc {
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::rpc::Rpc;
|
||||
|
||||
let rpc = ops.handle(&self.monero.0).host_port(self.monero.1).unwrap();
|
||||
let rpc = format!("http://{RPC_USER}:{RPC_PASS}@{}:{}", rpc.0, rpc.1);
|
||||
@@ -62,7 +63,7 @@ impl Handles {
|
||||
// If the RPC server has yet to start, sleep for up to 60s until it does
|
||||
for _ in 0 .. 60 {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
let Ok(client) = HttpRpc::new(rpc.clone()).await else { continue };
|
||||
let Ok(client) = SimpleRequestRpc::new(rpc.clone()).await else { continue };
|
||||
if client.get_height().await.is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::{
|
||||
sync::{OnceLock, Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
collections::HashSet,
|
||||
};
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
@@ -88,14 +87,11 @@ async fn mint_and_burn_test() {
|
||||
// Mine a Monero block
|
||||
let monero_blocks = {
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
|
||||
use monero_serai::wallet::{
|
||||
ViewPair,
|
||||
address::{Network, AddressSpec},
|
||||
};
|
||||
use monero_wallet::{rpc::Rpc, ViewPair, address::Network};
|
||||
|
||||
let addr = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE))
|
||||
.address(Network::Mainnet, AddressSpec::Standard)
|
||||
.to_string();
|
||||
.unwrap()
|
||||
.legacy_address(Network::Mainnet);
|
||||
|
||||
let rpc = producer_handles.monero(ops).await;
|
||||
let mut res = Vec::with_capacity(count);
|
||||
@@ -103,8 +99,8 @@ async fn mint_and_burn_test() {
|
||||
let block =
|
||||
rpc.get_block(rpc.generate_blocks(&addr, 1).await.unwrap().0[0]).await.unwrap();
|
||||
|
||||
let mut txs = Vec::with_capacity(block.txs.len());
|
||||
for tx in &block.txs {
|
||||
let mut txs = Vec::with_capacity(block.transactions.len());
|
||||
for tx in &block.transactions {
|
||||
txs.push(rpc.get_transaction(*tx).await.unwrap());
|
||||
}
|
||||
res.push((serde_json::json!([hex::encode(block.serialize())]), txs));
|
||||
@@ -128,6 +124,8 @@ async fn mint_and_burn_test() {
|
||||
}
|
||||
|
||||
{
|
||||
use monero_wallet::rpc::Rpc;
|
||||
|
||||
let rpc = handles.monero(ops).await;
|
||||
|
||||
for (block, txs) in &monero_blocks {
|
||||
@@ -345,33 +343,30 @@ async fn mint_and_burn_test() {
|
||||
// Send in XMR
|
||||
{
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
|
||||
use monero_serai::{
|
||||
Protocol,
|
||||
transaction::Timelock,
|
||||
wallet::{
|
||||
ViewPair, Scanner, Decoys, Change, FeePriority, SignableTransaction,
|
||||
address::{Network, AddressType, AddressMeta, MoneroAddress},
|
||||
},
|
||||
decompress_point,
|
||||
use monero_wallet::{
|
||||
io::decompress_point,
|
||||
ringct::RctType,
|
||||
rpc::{FeePriority, Rpc},
|
||||
address::{Network, AddressType, MoneroAddress},
|
||||
ViewPair, Scanner, DecoySelection, Decoys,
|
||||
send::{Change, SignableTransaction},
|
||||
};
|
||||
|
||||
// Grab the first output on the chain
|
||||
let rpc = handles[0].monero(&ops).await;
|
||||
let view_pair = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE));
|
||||
let mut scanner = Scanner::from_view(view_pair.clone(), Some(HashSet::new()));
|
||||
let view_pair = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE)).unwrap();
|
||||
let mut scanner = Scanner::new(view_pair.clone());
|
||||
let output = scanner
|
||||
.scan(&rpc, &rpc.get_block_by_number(1).await.unwrap())
|
||||
.await
|
||||
.unwrap()
|
||||
.swap_remove(0)
|
||||
.unlocked(Timelock::Block(rpc.get_height().await.unwrap()))
|
||||
.unwrap()
|
||||
.additional_timelock_satisfied_by(rpc.get_height().await.unwrap(), 0)
|
||||
.swap_remove(0);
|
||||
|
||||
let decoys = Decoys::fingerprintable_canonical_select(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
Protocol::v16.ring_len(),
|
||||
16,
|
||||
rpc.get_height().await.unwrap(),
|
||||
&[output.clone()],
|
||||
)
|
||||
@@ -379,25 +374,25 @@ async fn mint_and_burn_test() {
|
||||
.unwrap()
|
||||
.swap_remove(0);
|
||||
|
||||
let mut outgoing_view_key = Zeroizing::new([0; 32]);
|
||||
OsRng.fill_bytes(outgoing_view_key.as_mut());
|
||||
let tx = SignableTransaction::new(
|
||||
Protocol::v16,
|
||||
None,
|
||||
RctType::ClsagBulletproofPlus,
|
||||
outgoing_view_key,
|
||||
vec![(output, decoys)],
|
||||
vec![(
|
||||
MoneroAddress::new(
|
||||
AddressMeta::new(
|
||||
Network::Mainnet,
|
||||
AddressType::Featured { guaranteed: true, subaddress: false, payment_id: None },
|
||||
),
|
||||
Network::Mainnet,
|
||||
AddressType::Featured { guaranteed: true, subaddress: false, payment_id: None },
|
||||
decompress_point(monero_key_pair.1.to_vec().try_into().unwrap()).unwrap(),
|
||||
ED25519_BASEPOINT_POINT *
|
||||
processor::additional_key::<processor::networks::monero::Monero>(0).0,
|
||||
),
|
||||
1_100_000_000_000,
|
||||
)],
|
||||
&Change::new(&view_pair, false),
|
||||
Change::new(&view_pair),
|
||||
vec![Shorthand::transfer(None, serai_addr).encode()],
|
||||
rpc.get_fee(Protocol::v16, FeePriority::Unimportant).await.unwrap(),
|
||||
rpc.get_fee_rate(FeePriority::Unimportant).await.unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
.sign(&mut OsRng, &Zeroizing::new(Scalar::ONE))
|
||||
@@ -473,9 +468,10 @@ async fn mint_and_burn_test() {
|
||||
let spend = ED25519_BASEPOINT_TABLE * &Scalar::random(&mut OsRng);
|
||||
let view = Scalar::random(&mut OsRng);
|
||||
|
||||
use monero_serai::wallet::address::{Network, AddressType, AddressMeta, MoneroAddress};
|
||||
use monero_wallet::address::{Network, AddressType, MoneroAddress};
|
||||
let addr = MoneroAddress::new(
|
||||
AddressMeta::new(Network::Mainnet, AddressType::Standard),
|
||||
Network::Mainnet,
|
||||
AddressType::Legacy,
|
||||
spend,
|
||||
ED25519_BASEPOINT_TABLE * &view,
|
||||
);
|
||||
@@ -486,7 +482,10 @@ async fn mint_and_burn_test() {
|
||||
// Get the current blocks
|
||||
let mut start_bitcoin_block =
|
||||
handles[0].bitcoin(&ops).await.get_latest_block_number().await.unwrap();
|
||||
let mut start_monero_block = handles[0].monero(&ops).await.get_height().await.unwrap();
|
||||
let mut start_monero_block = {
|
||||
use monero_wallet::rpc::Rpc;
|
||||
handles[0].monero(&ops).await.get_height().await.unwrap()
|
||||
};
|
||||
|
||||
// Burn the sriBTC/sriXMR
|
||||
{
|
||||
@@ -578,12 +577,10 @@ async fn mint_and_burn_test() {
|
||||
|
||||
// Verify the received Monero TX
|
||||
{
|
||||
use monero_serai::wallet::{ViewPair, Scanner};
|
||||
use monero_wallet::{transaction::Transaction, rpc::Rpc, ViewPair, Scanner};
|
||||
let rpc = handles[0].monero(&ops).await;
|
||||
let mut scanner = Scanner::from_view(
|
||||
ViewPair::new(monero_spend, Zeroizing::new(monero_view)),
|
||||
Some(HashSet::new()),
|
||||
);
|
||||
let mut scanner =
|
||||
Scanner::new(ViewPair::new(monero_spend, Zeroizing::new(monero_view)).unwrap());
|
||||
|
||||
// Check for up to 5 minutes
|
||||
let mut found = false;
|
||||
@@ -591,15 +588,16 @@ async fn mint_and_burn_test() {
|
||||
while i < (5 * 6) {
|
||||
if let Ok(block) = rpc.get_block_by_number(start_monero_block).await {
|
||||
start_monero_block += 1;
|
||||
let outputs = scanner.scan(&rpc, &block).await.unwrap();
|
||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
if !outputs.is_empty() {
|
||||
assert_eq!(outputs.len(), 1);
|
||||
let outputs = outputs[0].not_locked();
|
||||
assert_eq!(outputs.len(), 1);
|
||||
|
||||
assert_eq!(block.txs.len(), 1);
|
||||
let tx = rpc.get_transaction(block.txs[0]).await.unwrap();
|
||||
let tx_fee = tx.rct_signatures.base.fee;
|
||||
assert_eq!(block.transactions.len(), 1);
|
||||
let tx = rpc.get_transaction(block.transactions[0]).await.unwrap();
|
||||
let tx_fee = match &tx {
|
||||
Transaction::V2 { proofs: Some(proofs), .. } => proofs.base.fee,
|
||||
_ => panic!("fetched TX wasn't a signed V2 TX"),
|
||||
};
|
||||
|
||||
assert_eq!(outputs[0].commitment().amount, 1_000_000_000_000 - tx_fee);
|
||||
found = true;
|
||||
|
||||
@@ -35,5 +35,4 @@ dkg = { path = "../../crypto/dkg", default-features = false }
|
||||
|
||||
bitcoin-serai = { path = "../../coins/bitcoin", default-features = false, features = ["hazmat"] }
|
||||
|
||||
monero-generators = { path = "../../coins/monero/generators", default-features = false }
|
||||
monero-serai = { path = "../../coins/monero", default-features = false, features = ["cache-distribution"] }
|
||||
monero-wallet-util = { path = "../../coins/monero/wallet/util", default-features = false, features = ["compile-time-generators"] }
|
||||
|
||||
@@ -20,5 +20,4 @@ pub use frost_schnorrkel;
|
||||
|
||||
pub use bitcoin_serai;
|
||||
|
||||
pub use monero_generators;
|
||||
pub use monero_serai;
|
||||
pub use monero_wallet_util;
|
||||
|
||||
@@ -31,7 +31,8 @@ bitcoin-serai = { path = "../../coins/bitcoin" }
|
||||
k256 = "0.13"
|
||||
ethereum-serai = { path = "../../coins/ethereum" }
|
||||
|
||||
monero-serai = { path = "../../coins/monero" }
|
||||
monero-simple-request-rpc = { path = "../../coins/monero/rpc/simple-request" }
|
||||
monero-wallet = { path = "../../coins/monero/wallet" }
|
||||
|
||||
messages = { package = "serai-processor-messages", path = "../../processor/messages" }
|
||||
|
||||
|
||||
@@ -274,11 +274,12 @@ impl Coordinator {
|
||||
}
|
||||
}
|
||||
NetworkId::Monero => {
|
||||
use monero_serai::rpc::HttpRpc;
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::rpc::Rpc;
|
||||
|
||||
// Monero's won't, so call get_height
|
||||
if handle
|
||||
.block_on(HttpRpc::new(rpc_url.clone()))
|
||||
.block_on(SimpleRequestRpc::new(rpc_url.clone()))
|
||||
.ok()
|
||||
.and_then(|rpc| handle.block_on(rpc.get_height()).ok())
|
||||
.is_some()
|
||||
@@ -403,25 +404,16 @@ impl Coordinator {
|
||||
}
|
||||
NetworkId::Monero => {
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
|
||||
use monero_serai::{
|
||||
wallet::{
|
||||
ViewPair,
|
||||
address::{Network, AddressSpec},
|
||||
},
|
||||
rpc::HttpRpc,
|
||||
};
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::{rpc::Rpc, address::Network, ViewPair};
|
||||
|
||||
let rpc = HttpRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
|
||||
let _: EmptyResponse = rpc
|
||||
.json_rpc_call(
|
||||
"generateblocks",
|
||||
Some(serde_json::json!({
|
||||
"wallet_address": ViewPair::new(
|
||||
ED25519_BASEPOINT_POINT,
|
||||
Zeroizing::new(Scalar::ONE),
|
||||
).address(Network::Mainnet, AddressSpec::Standard).to_string(),
|
||||
"amount_of_blocks": 1,
|
||||
})),
|
||||
let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
|
||||
rpc
|
||||
.generate_blocks(
|
||||
&ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE))
|
||||
.unwrap()
|
||||
.legacy_address(Network::Mainnet),
|
||||
1,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -517,15 +509,19 @@ impl Coordinator {
|
||||
}
|
||||
}
|
||||
NetworkId::Monero => {
|
||||
use monero_serai::rpc::HttpRpc;
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::rpc::Rpc;
|
||||
|
||||
let rpc = HttpRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
|
||||
let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
|
||||
let to = rpc.get_height().await.unwrap();
|
||||
for coordinator in others {
|
||||
let other_rpc =
|
||||
HttpRpc::new(network_rpc(coordinator.network, ops, &coordinator.network_handle))
|
||||
.await
|
||||
.expect("couldn't connect to the Monero RPC");
|
||||
let other_rpc = SimpleRequestRpc::new(network_rpc(
|
||||
coordinator.network,
|
||||
ops,
|
||||
&coordinator.network_handle,
|
||||
))
|
||||
.await
|
||||
.expect("couldn't connect to the Monero RPC");
|
||||
|
||||
let from = other_rpc.get_height().await.unwrap();
|
||||
for b in from .. to {
|
||||
@@ -574,10 +570,12 @@ impl Coordinator {
|
||||
let _ = provider.send_raw_transaction(tx).await.unwrap();
|
||||
}
|
||||
NetworkId::Monero => {
|
||||
use monero_serai::{transaction::Transaction, rpc::HttpRpc};
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::{transaction::Transaction, rpc::Rpc};
|
||||
|
||||
let rpc =
|
||||
HttpRpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Monero RPC");
|
||||
let rpc = SimpleRequestRpc::new(rpc_url)
|
||||
.await
|
||||
.expect("couldn't connect to the coordinator's Monero RPC");
|
||||
rpc.publish_transaction(&Transaction::read(&mut &*tx).unwrap()).await.unwrap();
|
||||
}
|
||||
NetworkId::Serai => panic!("processor tests broadcasting block to Serai"),
|
||||
@@ -672,10 +670,12 @@ impl Coordinator {
|
||||
None
|
||||
}
|
||||
NetworkId::Monero => {
|
||||
use monero_serai::rpc::HttpRpc;
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::rpc::Rpc;
|
||||
|
||||
let rpc =
|
||||
HttpRpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Monero RPC");
|
||||
let rpc = SimpleRequestRpc::new(rpc_url)
|
||||
.await
|
||||
.expect("couldn't connect to the coordinator's Monero RPC");
|
||||
let mut hash = [0; 32];
|
||||
hash.copy_from_slice(tx);
|
||||
if let Ok(tx) = rpc.get_transaction(hash).await {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
@@ -103,8 +101,8 @@ pub enum Wallet {
|
||||
Monero {
|
||||
handle: String,
|
||||
spend_key: Zeroizing<curve25519_dalek::scalar::Scalar>,
|
||||
view_pair: monero_serai::wallet::ViewPair,
|
||||
inputs: Vec<monero_serai::wallet::ReceivedOutput>,
|
||||
view_pair: monero_wallet::ViewPair,
|
||||
last_tx: (usize, [u8; 32]),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -189,55 +187,27 @@ impl Wallet {
|
||||
|
||||
NetworkId::Monero => {
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
|
||||
use monero_serai::{
|
||||
wallet::{
|
||||
ViewPair, Scanner,
|
||||
address::{Network, AddressSpec},
|
||||
},
|
||||
rpc::HttpRpc,
|
||||
};
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::{rpc::Rpc, address::Network, ViewPair};
|
||||
|
||||
let mut bytes = [0; 64];
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
let spend_key = Scalar::from_bytes_mod_order_wide(&bytes);
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
let view_key = Scalar::from_bytes_mod_order_wide(&bytes);
|
||||
let spend_key = Scalar::random(&mut OsRng);
|
||||
let view_key = Scalar::random(&mut OsRng);
|
||||
|
||||
let view_pair =
|
||||
ViewPair::new(ED25519_BASEPOINT_POINT * spend_key, Zeroizing::new(view_key));
|
||||
ViewPair::new(ED25519_BASEPOINT_POINT * spend_key, Zeroizing::new(view_key)).unwrap();
|
||||
|
||||
let rpc = HttpRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
|
||||
let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
|
||||
|
||||
let height = rpc.get_height().await.unwrap();
|
||||
// Mines 200 blocks so sufficient decoys exist, as only 60 is needed for maturity
|
||||
let _: EmptyResponse = rpc
|
||||
.json_rpc_call(
|
||||
"generateblocks",
|
||||
Some(serde_json::json!({
|
||||
"wallet_address": view_pair.address(
|
||||
Network::Mainnet,
|
||||
AddressSpec::Standard
|
||||
).to_string(),
|
||||
"amount_of_blocks": 200,
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
rpc.generate_blocks(&view_pair.legacy_address(Network::Mainnet), 200).await.unwrap();
|
||||
let block = rpc.get_block(rpc.get_block_hash(height).await.unwrap()).await.unwrap();
|
||||
|
||||
let output = Scanner::from_view(view_pair.clone(), Some(HashSet::new()))
|
||||
.scan(&rpc, &block)
|
||||
.await
|
||||
.unwrap()
|
||||
.remove(0)
|
||||
.ignore_timelock()
|
||||
.remove(0);
|
||||
|
||||
Wallet::Monero {
|
||||
handle,
|
||||
spend_key: Zeroizing::new(spend_key),
|
||||
view_pair,
|
||||
inputs: vec![output.output.clone()],
|
||||
last_tx: (height, block.miner_transaction.hash()),
|
||||
}
|
||||
}
|
||||
NetworkId::Serai => panic!("creating a wallet for for Serai"),
|
||||
@@ -434,38 +404,45 @@ impl Wallet {
|
||||
)
|
||||
}
|
||||
|
||||
Wallet::Monero { handle, ref spend_key, ref view_pair, ref mut inputs } => {
|
||||
Wallet::Monero { handle, ref spend_key, ref view_pair, ref mut last_tx } => {
|
||||
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
|
||||
use monero_serai::{
|
||||
Protocol,
|
||||
wallet::{
|
||||
address::{Network, AddressType, AddressMeta, Address},
|
||||
SpendableOutput, Decoys, Change, FeePriority, Scanner, SignableTransaction,
|
||||
},
|
||||
rpc::HttpRpc,
|
||||
decompress_point,
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::{
|
||||
io::decompress_point,
|
||||
ringct::RctType,
|
||||
rpc::{FeePriority, Rpc},
|
||||
address::{Network, AddressType, Address},
|
||||
Scanner, DecoySelection, Decoys,
|
||||
send::{Change, SignableTransaction},
|
||||
};
|
||||
use processor::{additional_key, networks::Monero};
|
||||
|
||||
let rpc_url = network_rpc(NetworkId::Monero, ops, handle);
|
||||
let rpc = HttpRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
|
||||
let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
|
||||
|
||||
// Prepare inputs
|
||||
let outputs = std::mem::take(inputs);
|
||||
let mut these_inputs = vec![];
|
||||
for output in outputs {
|
||||
these_inputs.push(
|
||||
SpendableOutput::from(&rpc, output)
|
||||
let current_height = rpc.get_height().await.unwrap();
|
||||
let mut inputs = vec![];
|
||||
for block in last_tx.0 .. current_height {
|
||||
let block = rpc.get_block_by_number(block).await.unwrap();
|
||||
if (block.miner_transaction.hash() == last_tx.1) ||
|
||||
block.transactions.contains(&last_tx.1)
|
||||
{
|
||||
inputs = Scanner::new(view_pair.clone())
|
||||
.scan(&rpc, &block)
|
||||
.await
|
||||
.expect("prior transaction was never published"),
|
||||
);
|
||||
.unwrap()
|
||||
.ignore_additional_timelock();
|
||||
}
|
||||
}
|
||||
assert!(!inputs.is_empty());
|
||||
|
||||
let mut decoys = Decoys::fingerprintable_canonical_select(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
Protocol::v16.ring_len(),
|
||||
16,
|
||||
rpc.get_height().await.unwrap(),
|
||||
&these_inputs,
|
||||
&inputs,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -473,10 +450,8 @@ impl Wallet {
|
||||
let to_spend_key = decompress_point(<[u8; 32]>::try_from(to.as_ref()).unwrap()).unwrap();
|
||||
let to_view_key = additional_key::<Monero>(0);
|
||||
let to_addr = Address::new(
|
||||
AddressMeta::new(
|
||||
Network::Mainnet,
|
||||
AddressType::Featured { subaddress: false, payment_id: None, guaranteed: true },
|
||||
),
|
||||
Network::Mainnet,
|
||||
AddressType::Featured { subaddress: false, payment_id: None, guaranteed: true },
|
||||
to_spend_key,
|
||||
ED25519_BASEPOINT_POINT * to_view_key.0,
|
||||
);
|
||||
@@ -487,26 +462,24 @@ impl Wallet {
|
||||
if let Some(instruction) = instruction {
|
||||
data.push(Shorthand::Raw(RefundableInInstruction { origin: None, instruction }).encode());
|
||||
}
|
||||
let mut outgoing_view_key = Zeroizing::new([0; 32]);
|
||||
OsRng.fill_bytes(outgoing_view_key.as_mut());
|
||||
let tx = SignableTransaction::new(
|
||||
Protocol::v16,
|
||||
None,
|
||||
these_inputs.drain(..).zip(decoys.drain(..)).collect(),
|
||||
RctType::ClsagBulletproofPlus,
|
||||
outgoing_view_key,
|
||||
inputs.drain(..).zip(decoys.drain(..)).collect(),
|
||||
vec![(to_addr, AMOUNT)],
|
||||
&Change::new(view_pair, false),
|
||||
Change::new(view_pair),
|
||||
data,
|
||||
rpc.get_fee(Protocol::v16, FeePriority::Unimportant).await.unwrap(),
|
||||
rpc.get_fee_rate(FeePriority::Unimportant).await.unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
.sign(&mut OsRng, spend_key)
|
||||
.unwrap();
|
||||
|
||||
// Push the change output
|
||||
inputs.push(
|
||||
Scanner::from_view(view_pair.clone(), Some(HashSet::new()))
|
||||
.scan_transaction(&tx)
|
||||
.ignore_timelock()
|
||||
.remove(0),
|
||||
);
|
||||
// Update the last TX to track the change output
|
||||
last_tx.0 = current_height;
|
||||
last_tx.1 = tx.hash();
|
||||
|
||||
(tx.serialize(), Balance { coin: Coin::Monero, amount: Amount(AMOUNT) })
|
||||
}
|
||||
@@ -531,13 +504,11 @@ impl Wallet {
|
||||
)
|
||||
.unwrap(),
|
||||
Wallet::Monero { view_pair, .. } => {
|
||||
use monero_serai::wallet::address::{Network, AddressSpec};
|
||||
use monero_wallet::address::Network;
|
||||
ExternalAddress::new(
|
||||
networks::monero::Address::new(
|
||||
view_pair.address(Network::Mainnet, AddressSpec::Standard),
|
||||
)
|
||||
.unwrap()
|
||||
.into(),
|
||||
networks::monero::Address::new(view_pair.legacy_address(Network::Mainnet))
|
||||
.unwrap()
|
||||
.into(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user