mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 03:59:22 +00:00
Remove the DecoySelection trait
This commit is contained in:
parent
a2c3aba82b
commit
d7f7f69738
18 changed files with 320 additions and 338 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4932,7 +4932,6 @@ dependencies = [
|
||||||
name = "monero-wallet"
|
name = "monero-wallet"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"dalek-ff-group",
|
"dalek-ff-group",
|
||||||
"flexible-transcript",
|
"flexible-transcript",
|
||||||
|
|
|
@ -137,13 +137,23 @@ impl Commitment {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decoy data, as used for producing Monero's ring signatures.
|
/// Decoy data, as used for producing Monero's ring signatures.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct Decoys {
|
pub struct Decoys {
|
||||||
offsets: Vec<u64>,
|
offsets: Vec<u64>,
|
||||||
signer_index: u8,
|
signer_index: u8,
|
||||||
ring: Vec<[EdwardsPoint; 2]>,
|
ring: Vec<[EdwardsPoint; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for Decoys {
|
||||||
|
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
||||||
|
fmt
|
||||||
|
.debug_struct("Decoys")
|
||||||
|
.field("offsets", &self.offsets)
|
||||||
|
.field("ring", &self.ring)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::len_without_is_empty)]
|
#[allow(clippy::len_without_is_empty)]
|
||||||
impl Decoys {
|
impl Decoys {
|
||||||
/// Create a new instance of decoy data.
|
/// Create a new instance of decoy data.
|
||||||
|
|
|
@ -18,7 +18,6 @@ workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false }
|
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 }
|
thiserror = { version = "1", default-features = false, optional = true }
|
||||||
|
|
||||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
// TODO: Clean this
|
// TODO: Clean this
|
||||||
|
|
||||||
use std_shims::{vec::Vec, collections::HashSet};
|
use std_shims::{io, vec::Vec, collections::HashSet};
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
use rand_distr::{Distribution, Gamma};
|
use rand_distr::{Distribution, Gamma};
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use rand_distr::num_traits::Float;
|
use rand_distr::num_traits::Float;
|
||||||
|
|
||||||
use curve25519_dalek::edwards::EdwardsPoint;
|
use curve25519_dalek::{Scalar, EdwardsPoint};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
DEFAULT_LOCK_WINDOW, COINBASE_LOCK_WINDOW, BLOCK_TIME,
|
DEFAULT_LOCK_WINDOW, COINBASE_LOCK_WINDOW, BLOCK_TIME,
|
||||||
|
primitives::Commitment,
|
||||||
rpc::{RpcError, Rpc},
|
rpc::{RpcError, Rpc},
|
||||||
|
output::OutputData,
|
||||||
WalletOutput,
|
WalletOutput,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -138,20 +140,16 @@ async fn select_decoys<R: RngCore + CryptoRng>(
|
||||||
rpc: &impl Rpc,
|
rpc: &impl Rpc,
|
||||||
ring_len: usize,
|
ring_len: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
inputs: &[WalletOutput],
|
input: &WalletOutput,
|
||||||
fingerprintable_canonical: bool,
|
fingerprintable_canonical: bool,
|
||||||
) -> Result<Vec<Decoys>, RpcError> {
|
) -> Result<Decoys, RpcError> {
|
||||||
let mut distribution = vec![];
|
let mut distribution = vec![];
|
||||||
|
|
||||||
let decoy_count = ring_len - 1;
|
let decoy_count = ring_len - 1;
|
||||||
|
|
||||||
// Convert the inputs in question to the raw output data
|
// Convert the inputs in question to the raw output data
|
||||||
let mut real = Vec::with_capacity(inputs.len());
|
let mut real = vec![input.relative_id.index_on_blockchain];
|
||||||
let mut outputs = Vec::with_capacity(inputs.len());
|
let output = (real[0], [input.key(), input.commitment().calculate()]);
|
||||||
for input in inputs {
|
|
||||||
real.push(input.relative_id.index_on_blockchain);
|
|
||||||
outputs.push((real[real.len() - 1], [input.key(), input.commitment().calculate()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if distribution.len() < height {
|
if distribution.len() < height {
|
||||||
// TODO: verify distribution elems are strictly increasing
|
// TODO: verify distribution elems are strictly increasing
|
||||||
|
@ -175,16 +173,14 @@ async fn select_decoys<R: RngCore + CryptoRng>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut used = HashSet::<u64>::new();
|
let mut used = HashSet::<u64>::new();
|
||||||
for o in &outputs {
|
used.insert(output.0);
|
||||||
used.insert(o.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Create a TX with less than the target amount, as allowed by the protocol
|
// TODO: Create a TX with less than the target amount, as allowed by the protocol
|
||||||
let high = distribution[distribution.len() - DEFAULT_LOCK_WINDOW];
|
let high = distribution[distribution.len() - DEFAULT_LOCK_WINDOW];
|
||||||
// This assumes that each miner TX had one output (as sane) and checks we have sufficient
|
// This assumes that each miner TX had one output (as sane) and checks we have sufficient
|
||||||
// outputs even when excluding them (due to their own timelock requirements)
|
// outputs even when excluding them (due to their own timelock requirements)
|
||||||
if high.saturating_sub(u64::try_from(COINBASE_LOCK_WINDOW).unwrap()) <
|
if high.saturating_sub(u64::try_from(COINBASE_LOCK_WINDOW).unwrap()) <
|
||||||
u64::try_from(inputs.len() * ring_len).unwrap()
|
u64::try_from(ring_len).unwrap()
|
||||||
{
|
{
|
||||||
Err(RpcError::InternalError("not enough decoy candidates".to_string()))?;
|
Err(RpcError::InternalError("not enough decoy candidates".to_string()))?;
|
||||||
}
|
}
|
||||||
|
@ -201,136 +197,163 @@ async fn select_decoys<R: RngCore + CryptoRng>(
|
||||||
per_second,
|
per_second,
|
||||||
&real,
|
&real,
|
||||||
&mut used,
|
&mut used,
|
||||||
inputs.len() * decoy_count,
|
decoy_count,
|
||||||
fingerprintable_canonical,
|
fingerprintable_canonical,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
real.zeroize();
|
real.zeroize();
|
||||||
|
|
||||||
let mut res = Vec::with_capacity(inputs.len());
|
// Grab the decoys for this specific output
|
||||||
for o in outputs {
|
let mut ring = decoys.drain((decoys.len() - decoy_count) ..).collect::<Vec<_>>();
|
||||||
// Grab the decoys for this specific output
|
ring.push(output);
|
||||||
let mut ring = decoys.drain((decoys.len() - decoy_count) ..).collect::<Vec<_>>();
|
ring.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
ring.push(o);
|
|
||||||
ring.sort_by(|a, b| a.0.cmp(&b.0));
|
|
||||||
|
|
||||||
// Sanity checks are only run when 1000 outputs are available in Monero
|
// Sanity checks are only run when 1000 outputs are available in Monero
|
||||||
// We run this check whenever the highest output index, which we acknowledge, is > 500
|
// We run this check whenever the highest output index, which we acknowledge, is > 500
|
||||||
// This means we assume (for presumably test blockchains) the height being used has not had
|
// This means we assume (for presumably test blockchains) the height being used has not had
|
||||||
// 500 outputs since while itself not being a sufficiently mature blockchain
|
// 500 outputs since while itself not being a sufficiently mature blockchain
|
||||||
// Considering Monero's p2p layer doesn't actually check transaction sanity, it should be
|
// Considering Monero's p2p layer doesn't actually check transaction sanity, it should be
|
||||||
// fine for us to not have perfectly matching rules, especially since this code will infinite
|
// fine for us to not have perfectly matching rules, especially since this code will infinite
|
||||||
// loop if it can't determine sanity, which is possible with sufficient inputs on
|
// loop if it can't determine sanity, which is possible with sufficient inputs on
|
||||||
// sufficiently small chains
|
// sufficiently small chains
|
||||||
if high > 500 {
|
if high > 500 {
|
||||||
// Make sure the TX passes the sanity check that the median output is within the last 40%
|
// Make sure the TX passes the sanity check that the median output is within the last 40%
|
||||||
let target_median = high * 3 / 5;
|
let target_median = high * 3 / 5;
|
||||||
while ring[ring_len / 2].0 < target_median {
|
while ring[ring_len / 2].0 < target_median {
|
||||||
// If it's not, update the bottom half with new values to ensure the median only moves up
|
// If it's not, update the bottom half with new values to ensure the median only moves up
|
||||||
for removed in ring.drain(0 .. (ring_len / 2)).collect::<Vec<_>>() {
|
for removed in ring.drain(0 .. (ring_len / 2)).collect::<Vec<_>>() {
|
||||||
// If we removed the real spend, add it back
|
// If we removed the real spend, add it back
|
||||||
if removed.0 == o.0 {
|
if removed.0 == output.0 {
|
||||||
ring.push(o);
|
ring.push(output);
|
||||||
} else {
|
} else {
|
||||||
// We could not remove this, saving CPU time and removing low values as
|
// We could not remove this, saving CPU time and removing low values as
|
||||||
// possibilities, yet it'd increase the amount of decoys required to create this
|
// possibilities, yet it'd increase the amount of decoys required to create this
|
||||||
// transaction and some removed outputs may be the best option (as we drop the first
|
// transaction and some removed outputs may be the best option (as we drop the first
|
||||||
// half, not just the bottom n)
|
// half, not just the bottom n)
|
||||||
used.remove(&removed.0);
|
used.remove(&removed.0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select new outputs until we have a full sized ring again
|
|
||||||
ring.extend(
|
|
||||||
select_n(
|
|
||||||
rng,
|
|
||||||
rpc,
|
|
||||||
&distribution,
|
|
||||||
height,
|
|
||||||
high,
|
|
||||||
per_second,
|
|
||||||
&[],
|
|
||||||
&mut used,
|
|
||||||
ring_len - ring.len(),
|
|
||||||
fingerprintable_canonical,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
ring.sort_by(|a, b| a.0.cmp(&b.0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The other sanity check rule is about duplicates, yet we already enforce unique ring
|
// Select new outputs until we have a full sized ring again
|
||||||
// members
|
ring.extend(
|
||||||
|
select_n(
|
||||||
|
rng,
|
||||||
|
rpc,
|
||||||
|
&distribution,
|
||||||
|
height,
|
||||||
|
high,
|
||||||
|
per_second,
|
||||||
|
&[],
|
||||||
|
&mut used,
|
||||||
|
ring_len - ring.len(),
|
||||||
|
fingerprintable_canonical,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
ring.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
res.push(
|
// The other sanity check rule is about duplicates, yet we already enforce unique ring
|
||||||
Decoys::new(
|
// members
|
||||||
offset(&ring.iter().map(|output| output.0).collect::<Vec<_>>()),
|
|
||||||
// Binary searches for the real spend since we don't know where it sorted to
|
|
||||||
u8::try_from(ring.partition_point(|x| x.0 < o.0)).unwrap(),
|
|
||||||
ring.iter().map(|output| output.1).collect(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res)
|
Ok(
|
||||||
|
Decoys::new(
|
||||||
|
offset(&ring.iter().map(|output| output.0).collect::<Vec<_>>()),
|
||||||
|
// Binary searches for the real spend since we don't know where it sorted to
|
||||||
|
u8::try_from(ring.partition_point(|x| x.0 < output.0)).unwrap(),
|
||||||
|
ring.iter().map(|output| output.1).collect(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use monero_serai::primitives::Decoys;
|
pub use monero_serai::primitives::Decoys;
|
||||||
|
|
||||||
// TODO: Remove this trait
|
/// An output with decoys selected.
|
||||||
/// TODO: Document
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
#[cfg(feature = "std")]
|
pub struct OutputWithDecoys {
|
||||||
#[async_trait::async_trait]
|
output: OutputData,
|
||||||
pub trait DecoySelection {
|
decoys: Decoys,
|
||||||
/// Select decoys using the same distribution as Monero. Relies on the monerod RPC
|
}
|
||||||
/// response for an output's unlocked status, minimizing trips to the daemon.
|
|
||||||
async fn select<R: Send + Sync + RngCore + CryptoRng>(
|
impl OutputWithDecoys {
|
||||||
rng: &mut R,
|
/// Select decoys for this output.
|
||||||
|
pub async fn new(
|
||||||
|
rng: &mut (impl Send + Sync + RngCore + CryptoRng),
|
||||||
rpc: &impl Rpc,
|
rpc: &impl Rpc,
|
||||||
ring_len: usize,
|
ring_len: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
inputs: &[WalletOutput],
|
output: WalletOutput,
|
||||||
) -> Result<Vec<Decoys>, RpcError>;
|
) -> Result<OutputWithDecoys, RpcError> {
|
||||||
|
let decoys = select_decoys(rng, rpc, ring_len, height, &output, false).await?;
|
||||||
|
Ok(OutputWithDecoys { output: output.data.clone(), decoys })
|
||||||
|
}
|
||||||
|
|
||||||
/// If no reorg has occurred and an honest RPC, any caller who passes the same height to this
|
/// Select a set of decoys for this output with a deterministic process.
|
||||||
/// function will use the same distribution to select decoys. It is fingerprintable
|
|
||||||
/// because a caller using this will not be able to select decoys that are timelocked
|
|
||||||
/// with a timestamp. Any transaction which includes timestamp timelocked decoys in its
|
|
||||||
/// rings could not be constructed using this function.
|
|
||||||
///
|
///
|
||||||
/// TODO: upstream change to monerod get_outs RPC to accept a height param for checking
|
/// This function will always output the same set of decoys when called with the same arguments.
|
||||||
/// output's unlocked status and remove all usage of fingerprintable_canonical
|
/// This makes it very useful in multisignature contexts, where instead of having one participant
|
||||||
async fn fingerprintable_canonical_select<R: Send + Sync + RngCore + CryptoRng>(
|
/// select the decoys, everyone can locally select the decoys while coming to the same result.
|
||||||
rng: &mut R,
|
///
|
||||||
|
/// The set of decoys selected may be fingerprintable as having been produced by this
|
||||||
|
/// methodology.
|
||||||
|
pub async fn fingerprintable_deterministic_new(
|
||||||
|
rng: &mut (impl Send + Sync + RngCore + CryptoRng),
|
||||||
rpc: &impl Rpc,
|
rpc: &impl Rpc,
|
||||||
ring_len: usize,
|
ring_len: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
inputs: &[WalletOutput],
|
output: WalletOutput,
|
||||||
) -> Result<Vec<Decoys>, RpcError>;
|
) -> Result<OutputWithDecoys, RpcError> {
|
||||||
}
|
let decoys = select_decoys(rng, rpc, ring_len, height, &output, true).await?;
|
||||||
|
Ok(OutputWithDecoys { output: output.data.clone(), decoys })
|
||||||
#[cfg(feature = "std")]
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl DecoySelection for Decoys {
|
|
||||||
async fn select<R: Send + Sync + RngCore + CryptoRng>(
|
|
||||||
rng: &mut R,
|
|
||||||
rpc: &impl Rpc,
|
|
||||||
ring_len: usize,
|
|
||||||
height: usize,
|
|
||||||
inputs: &[WalletOutput],
|
|
||||||
) -> Result<Vec<Decoys>, RpcError> {
|
|
||||||
select_decoys(rng, rpc, ring_len, height, inputs, false).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fingerprintable_canonical_select<R: Send + Sync + RngCore + CryptoRng>(
|
/// The key this output may be spent by.
|
||||||
rng: &mut R,
|
pub fn key(&self) -> EdwardsPoint {
|
||||||
rpc: &impl Rpc,
|
self.output.key()
|
||||||
ring_len: usize,
|
}
|
||||||
height: usize,
|
|
||||||
inputs: &[WalletOutput],
|
/// The scalar to add to the private spend key for it to be the discrete logarithm of this
|
||||||
) -> Result<Vec<Decoys>, RpcError> {
|
/// output's key.
|
||||||
select_decoys(rng, rpc, ring_len, height, inputs, true).await
|
pub fn key_offset(&self) -> Scalar {
|
||||||
|
self.output.key_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The commitment this output created.
|
||||||
|
pub fn commitment(&self) -> &Commitment {
|
||||||
|
&self.output.commitment
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The decoys this output selected.
|
||||||
|
pub fn decoys(&self) -> &Decoys {
|
||||||
|
&self.decoys
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the OutputWithDecoys.
|
||||||
|
///
|
||||||
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
|
/// defined serialization.
|
||||||
|
pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
|
self.output.write(w)?;
|
||||||
|
self.decoys.write(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize the OutputWithDecoys to a `Vec<u8>`.
|
||||||
|
///
|
||||||
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
|
/// defined serialization.
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut serialized = Vec::with_capacity(128);
|
||||||
|
self.write(&mut serialized).unwrap();
|
||||||
|
serialized
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read an OutputWithDecoys.
|
||||||
|
///
|
||||||
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
|
/// defined serialization.
|
||||||
|
pub fn read<R: io::Read>(r: &mut R) -> io::Result<Self> {
|
||||||
|
Ok(Self { output: OutputData::read(r)?, decoys: Decoys::read(r)? })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,15 +35,8 @@ pub use output::WalletOutput;
|
||||||
mod scan;
|
mod scan;
|
||||||
pub use scan::{Scanner, GuaranteedScanner};
|
pub use scan::{Scanner, GuaranteedScanner};
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
mod decoys;
|
mod decoys;
|
||||||
#[cfg(not(feature = "std"))]
|
pub use decoys::OutputWithDecoys;
|
||||||
mod decoys {
|
|
||||||
pub use monero_serai::primitives::Decoys;
|
|
||||||
/// TODO: Document/remove
|
|
||||||
pub trait DecoySelection {}
|
|
||||||
}
|
|
||||||
pub use decoys::{DecoySelection, Decoys};
|
|
||||||
|
|
||||||
/// Structs and functionality for sending transactions.
|
/// Structs and functionality for sending transactions.
|
||||||
pub mod send;
|
pub mod send;
|
||||||
|
|
|
@ -52,21 +52,15 @@ impl AbsoluteId {
|
||||||
|
|
||||||
/// An output's relative ID.
|
/// An output's relative ID.
|
||||||
///
|
///
|
||||||
/// This id defined as the block which contains the transaction creating the output and the
|
/// This is defined as the output's index on the blockchain.
|
||||||
/// output's index on the blockchain.
|
|
||||||
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||||
pub(crate) struct RelativeId {
|
pub(crate) struct RelativeId {
|
||||||
pub(crate) block: [u8; 32],
|
|
||||||
pub(crate) index_on_blockchain: u64,
|
pub(crate) index_on_blockchain: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Debug for RelativeId {
|
impl core::fmt::Debug for RelativeId {
|
||||||
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
||||||
fmt
|
fmt.debug_struct("RelativeId").field("index_on_blockchain", &self.index_on_blockchain).finish()
|
||||||
.debug_struct("RelativeId")
|
|
||||||
.field("block", &hex::encode(self.block))
|
|
||||||
.field("index_on_blockchain", &self.index_on_blockchain)
|
|
||||||
.finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +70,6 @@ impl RelativeId {
|
||||||
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
/// defined serialization.
|
/// defined serialization.
|
||||||
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
w.write_all(&self.block)?;
|
|
||||||
w.write_all(&self.index_on_blockchain.to_le_bytes())
|
w.write_all(&self.index_on_blockchain.to_le_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,18 +78,16 @@ impl RelativeId {
|
||||||
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
/// defined serialization.
|
/// defined serialization.
|
||||||
fn read<R: Read>(r: &mut R) -> io::Result<Self> {
|
fn read<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||||
Ok(RelativeId { block: read_bytes(r)?, index_on_blockchain: read_u64(r)? })
|
Ok(RelativeId { index_on_blockchain: read_u64(r)? })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The data within an output as necessary to spend an output, and the output's additional
|
/// The data within an output, as necessary to spend the output.
|
||||||
/// timelock.
|
|
||||||
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||||
pub(crate) struct OutputData {
|
pub(crate) struct OutputData {
|
||||||
pub(crate) key: EdwardsPoint,
|
pub(crate) key: EdwardsPoint,
|
||||||
pub(crate) key_offset: Scalar,
|
pub(crate) key_offset: Scalar,
|
||||||
pub(crate) commitment: Commitment,
|
pub(crate) commitment: Commitment,
|
||||||
pub(crate) additional_timelock: Timelock,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Debug for OutputData {
|
impl core::fmt::Debug for OutputData {
|
||||||
|
@ -106,33 +97,55 @@ impl core::fmt::Debug for OutputData {
|
||||||
.field("key", &hex::encode(self.key.compress().0))
|
.field("key", &hex::encode(self.key.compress().0))
|
||||||
.field("key_offset", &hex::encode(self.key_offset.to_bytes()))
|
.field("key_offset", &hex::encode(self.key_offset.to_bytes()))
|
||||||
.field("commitment", &self.commitment)
|
.field("commitment", &self.commitment)
|
||||||
.field("additional_timelock", &self.additional_timelock)
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutputData {
|
impl OutputData {
|
||||||
// Write the OutputData.
|
/// The key this output may be spent by.
|
||||||
|
pub(crate) fn key(&self) -> EdwardsPoint {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The scalar to add to the private spend key for it to be the discrete logarithm of this
|
||||||
|
/// output's key.
|
||||||
|
pub(crate) fn key_offset(&self) -> Scalar {
|
||||||
|
self.key_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The commitment this output created.
|
||||||
|
pub(crate) fn commitment(&self) -> &Commitment {
|
||||||
|
&self.commitment
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the OutputData.
|
||||||
///
|
///
|
||||||
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
/// defined serialization.
|
/// defined serialization.
|
||||||
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
pub(crate) fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
w.write_all(&self.key.compress().to_bytes())?;
|
w.write_all(&self.key.compress().to_bytes())?;
|
||||||
w.write_all(&self.key_offset.to_bytes())?;
|
w.write_all(&self.key_offset.to_bytes())?;
|
||||||
self.commitment.write(w)?;
|
self.commitment.write(w)
|
||||||
self.additional_timelock.write(w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
/// Serialize the OutputData to a `Vec<u8>`.
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut res = Vec::with_capacity(32 + 32 + 40);
|
||||||
|
self.write(&mut res).unwrap();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/// Read an OutputData.
|
/// Read an OutputData.
|
||||||
///
|
///
|
||||||
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
/// defined serialization.
|
/// defined serialization.
|
||||||
fn read<R: Read>(r: &mut R) -> io::Result<OutputData> {
|
pub(crate) fn read<R: Read>(r: &mut R) -> io::Result<OutputData> {
|
||||||
Ok(OutputData {
|
Ok(OutputData {
|
||||||
key: read_point(r)?,
|
key: read_point(r)?,
|
||||||
key_offset: read_scalar(r)?,
|
key_offset: read_scalar(r)?,
|
||||||
commitment: Commitment::read(r)?,
|
commitment: Commitment::read(r)?,
|
||||||
additional_timelock: Timelock::read(r)?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,6 +153,7 @@ impl OutputData {
|
||||||
/// The metadata for an output.
|
/// The metadata for an output.
|
||||||
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||||
pub(crate) struct Metadata {
|
pub(crate) struct Metadata {
|
||||||
|
pub(crate) additional_timelock: Timelock,
|
||||||
pub(crate) subaddress: Option<SubaddressIndex>,
|
pub(crate) subaddress: Option<SubaddressIndex>,
|
||||||
pub(crate) payment_id: Option<PaymentId>,
|
pub(crate) payment_id: Option<PaymentId>,
|
||||||
pub(crate) arbitrary_data: Vec<Vec<u8>>,
|
pub(crate) arbitrary_data: Vec<Vec<u8>>,
|
||||||
|
@ -149,6 +163,7 @@ impl core::fmt::Debug for Metadata {
|
||||||
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
||||||
fmt
|
fmt
|
||||||
.debug_struct("Metadata")
|
.debug_struct("Metadata")
|
||||||
|
.field("additional_timelock", &self.additional_timelock)
|
||||||
.field("subaddress", &self.subaddress)
|
.field("subaddress", &self.subaddress)
|
||||||
.field("payment_id", &self.payment_id)
|
.field("payment_id", &self.payment_id)
|
||||||
.field("arbitrary_data", &self.arbitrary_data.iter().map(hex::encode).collect::<Vec<_>>())
|
.field("arbitrary_data", &self.arbitrary_data.iter().map(hex::encode).collect::<Vec<_>>())
|
||||||
|
@ -162,6 +177,8 @@ impl Metadata {
|
||||||
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
/// defined serialization.
|
/// defined serialization.
|
||||||
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
|
self.additional_timelock.write(w)?;
|
||||||
|
|
||||||
if let Some(subaddress) = self.subaddress {
|
if let Some(subaddress) = self.subaddress {
|
||||||
w.write_all(&[1])?;
|
w.write_all(&[1])?;
|
||||||
w.write_all(&subaddress.account().to_le_bytes())?;
|
w.write_all(&subaddress.account().to_le_bytes())?;
|
||||||
|
@ -190,6 +207,8 @@ impl Metadata {
|
||||||
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
/// defined serialization.
|
/// defined serialization.
|
||||||
fn read<R: Read>(r: &mut R) -> io::Result<Metadata> {
|
fn read<R: Read>(r: &mut R) -> io::Result<Metadata> {
|
||||||
|
let additional_timelock = Timelock::read(r)?;
|
||||||
|
|
||||||
let subaddress = match read_byte(r)? {
|
let subaddress = match read_byte(r)? {
|
||||||
0 => None,
|
0 => None,
|
||||||
1 => Some(
|
1 => Some(
|
||||||
|
@ -200,6 +219,7 @@ impl Metadata {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Metadata {
|
Ok(Metadata {
|
||||||
|
additional_timelock,
|
||||||
subaddress,
|
subaddress,
|
||||||
payment_id: if read_byte(r)? == 1 { PaymentId::read(r).ok() } else { None },
|
payment_id: if read_byte(r)? == 1 { PaymentId::read(r).ok() } else { None },
|
||||||
arbitrary_data: {
|
arbitrary_data: {
|
||||||
|
@ -214,7 +234,7 @@ impl Metadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A received output.
|
/// A scanned output and all associated data.
|
||||||
///
|
///
|
||||||
/// This struct contains all data necessary to spend this output, or handle it as a payment.
|
/// This struct contains all data necessary to spend this output, or handle it as a payment.
|
||||||
///
|
///
|
||||||
|
@ -244,11 +264,6 @@ impl WalletOutput {
|
||||||
self.absolute_id.index_in_transaction
|
self.absolute_id.index_in_transaction
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The block containing the transaction which created this output.
|
|
||||||
pub fn block(&self) -> [u8; 32] {
|
|
||||||
self.relative_id.block
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The index of the output on the blockchain.
|
/// The index of the output on the blockchain.
|
||||||
pub fn index_on_blockchain(&self) -> u64 {
|
pub fn index_on_blockchain(&self) -> u64 {
|
||||||
self.relative_id.index_on_blockchain
|
self.relative_id.index_on_blockchain
|
||||||
|
@ -256,18 +271,18 @@ impl WalletOutput {
|
||||||
|
|
||||||
/// The key this output may be spent by.
|
/// The key this output may be spent by.
|
||||||
pub fn key(&self) -> EdwardsPoint {
|
pub fn key(&self) -> EdwardsPoint {
|
||||||
self.data.key
|
self.data.key()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The scalar to add to the private spend key for it to be the discrete logarithm of this
|
/// The scalar to add to the private spend key for it to be the discrete logarithm of this
|
||||||
/// output's key.
|
/// output's key.
|
||||||
pub fn key_offset(&self) -> Scalar {
|
pub fn key_offset(&self) -> Scalar {
|
||||||
self.data.key_offset
|
self.data.key_offset()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The commitment this output created.
|
/// The commitment this output created.
|
||||||
pub fn commitment(&self) -> &Commitment {
|
pub fn commitment(&self) -> &Commitment {
|
||||||
&self.data.commitment
|
self.data.commitment()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The additional timelock this output is subject to.
|
/// The additional timelock this output is subject to.
|
||||||
|
@ -276,7 +291,7 @@ impl WalletOutput {
|
||||||
/// on-chain during which they cannot be spent. Outputs may be additionally timelocked. This
|
/// on-chain during which they cannot be spent. Outputs may be additionally timelocked. This
|
||||||
/// function only returns the additional timelock.
|
/// function only returns the additional timelock.
|
||||||
pub fn additional_timelock(&self) -> Timelock {
|
pub fn additional_timelock(&self) -> Timelock {
|
||||||
self.data.additional_timelock
|
self.metadata.additional_timelock
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The index of the subaddress this output was identified as sent to.
|
/// The index of the subaddress this output was identified as sent to.
|
||||||
|
|
|
@ -107,7 +107,6 @@ impl InternalScanner {
|
||||||
|
|
||||||
fn scan_transaction(
|
fn scan_transaction(
|
||||||
&self,
|
&self,
|
||||||
block_hash: [u8; 32],
|
|
||||||
tx_start_index_on_blockchain: u64,
|
tx_start_index_on_blockchain: u64,
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
) -> Result<Timelocked, RpcError> {
|
) -> Result<Timelocked, RpcError> {
|
||||||
|
@ -224,16 +223,15 @@ impl InternalScanner {
|
||||||
index_in_transaction: o.try_into().unwrap(),
|
index_in_transaction: o.try_into().unwrap(),
|
||||||
},
|
},
|
||||||
relative_id: RelativeId {
|
relative_id: RelativeId {
|
||||||
block: block_hash,
|
|
||||||
index_on_blockchain: tx_start_index_on_blockchain + u64::try_from(o).unwrap(),
|
index_on_blockchain: tx_start_index_on_blockchain + u64::try_from(o).unwrap(),
|
||||||
},
|
},
|
||||||
data: OutputData {
|
data: OutputData { key: output_key, key_offset, commitment },
|
||||||
key: output_key,
|
metadata: Metadata {
|
||||||
key_offset,
|
|
||||||
commitment,
|
|
||||||
additional_timelock: tx.prefix().additional_timelock,
|
additional_timelock: tx.prefix().additional_timelock,
|
||||||
|
subaddress,
|
||||||
|
payment_id,
|
||||||
|
arbitrary_data: extra.data(),
|
||||||
},
|
},
|
||||||
metadata: Metadata { subaddress, payment_id, arbitrary_data: extra.data() },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Break to prevent public keys from being included multiple times, triggering multiple
|
// Break to prevent public keys from being included multiple times, triggering multiple
|
||||||
|
@ -253,8 +251,6 @@ impl InternalScanner {
|
||||||
)))?;
|
)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let block_hash = block.hash();
|
|
||||||
|
|
||||||
// We obtain all TXs in full
|
// We obtain all TXs in full
|
||||||
let mut txs = vec![block.miner_transaction.clone()];
|
let mut txs = vec![block.miner_transaction.clone()];
|
||||||
txs.extend(rpc.get_transactions(&block.transactions).await?);
|
txs.extend(rpc.get_transactions(&block.transactions).await?);
|
||||||
|
@ -327,7 +323,7 @@ impl InternalScanner {
|
||||||
{
|
{
|
||||||
let mut this_txs_outputs = vec![];
|
let mut this_txs_outputs = vec![];
|
||||||
core::mem::swap(
|
core::mem::swap(
|
||||||
&mut self.scan_transaction(block_hash, tx_start_index_on_blockchain, &tx)?.0,
|
&mut self.scan_transaction(tx_start_index_on_blockchain, &tx)?.0,
|
||||||
&mut this_txs_outputs,
|
&mut this_txs_outputs,
|
||||||
);
|
);
|
||||||
res.0.extend(this_txs_outputs);
|
res.0.extend(this_txs_outputs);
|
||||||
|
@ -379,27 +375,6 @@ impl Scanner {
|
||||||
self.0.register_subaddress(subaddress)
|
self.0.register_subaddress(subaddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Scan a transaction.
|
|
||||||
///
|
|
||||||
/// This takes in the block hash the transaction is contained in. This method is NOT recommended
|
|
||||||
/// and MUST be used carefully. The node will receive a request for the output indexes of the
|
|
||||||
/// specified transactions, which may de-anonymize which transactions belong to a user.
|
|
||||||
pub async fn scan_transaction(
|
|
||||||
&self,
|
|
||||||
rpc: &impl Rpc,
|
|
||||||
block_hash: [u8; 32],
|
|
||||||
tx: &Transaction,
|
|
||||||
) -> Result<Timelocked, RpcError> {
|
|
||||||
// This isn't technically illegal due to a lack of minimum output rules for a while
|
|
||||||
let Some(tx_start_index_on_blockchain) =
|
|
||||||
rpc.get_o_indexes(tx.hash()).await?.first().copied() else {
|
|
||||||
return Ok(Timelocked(vec![]))
|
|
||||||
};
|
|
||||||
self.0.scan_transaction(block_hash, tx_start_index_on_blockchain, tx)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Scan a block.
|
/// Scan a block.
|
||||||
pub async fn scan(&mut self, rpc: &impl Rpc, block: &Block) -> Result<Timelocked, RpcError> {
|
pub async fn scan(&mut self, rpc: &impl Rpc, block: &Block) -> Result<Timelocked, RpcError> {
|
||||||
self.0.scan(rpc, block).await
|
self.0.scan(rpc, block).await
|
||||||
|
@ -429,27 +404,6 @@ impl GuaranteedScanner {
|
||||||
self.0.register_subaddress(subaddress)
|
self.0.register_subaddress(subaddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Scan a transaction.
|
|
||||||
///
|
|
||||||
/// This takes in the block hash the transaction is contained in. This method is NOT recommended
|
|
||||||
/// and MUST be used carefully. The node will receive a request for the output indexes of the
|
|
||||||
/// specified transactions, which may de-anonymize which transactions belong to a user.
|
|
||||||
pub async fn scan_transaction(
|
|
||||||
&self,
|
|
||||||
rpc: &impl Rpc,
|
|
||||||
block_hash: [u8; 32],
|
|
||||||
tx: &Transaction,
|
|
||||||
) -> Result<Timelocked, RpcError> {
|
|
||||||
// This isn't technically illegal due to a lack of minimum output rules for a while
|
|
||||||
let Some(tx_start_index_on_blockchain) =
|
|
||||||
rpc.get_o_indexes(tx.hash()).await?.first().copied() else {
|
|
||||||
return Ok(Timelocked(vec![]))
|
|
||||||
};
|
|
||||||
self.0.scan_transaction(block_hash, tx_start_index_on_blockchain, tx)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Scan a block.
|
/// Scan a block.
|
||||||
pub async fn scan(&mut self, rpc: &impl Rpc, block: &Block) -> Result<Timelocked, RpcError> {
|
pub async fn scan(&mut self, rpc: &impl Rpc, block: &Block) -> Result<Timelocked, RpcError> {
|
||||||
self.0.scan(rpc, block).await
|
self.0.scan(rpc, block).await
|
||||||
|
|
|
@ -17,7 +17,6 @@ use frost::FrostError;
|
||||||
use crate::{
|
use crate::{
|
||||||
io::*,
|
io::*,
|
||||||
generators::{MAX_COMMITMENTS, hash_to_point},
|
generators::{MAX_COMMITMENTS, hash_to_point},
|
||||||
primitives::Decoys,
|
|
||||||
ringct::{
|
ringct::{
|
||||||
clsag::{ClsagError, ClsagContext, Clsag},
|
clsag::{ClsagError, ClsagContext, Clsag},
|
||||||
RctType, RctPrunable, RctProofs,
|
RctType, RctPrunable, RctProofs,
|
||||||
|
@ -26,7 +25,7 @@ use crate::{
|
||||||
extra::MAX_ARBITRARY_DATA_SIZE,
|
extra::MAX_ARBITRARY_DATA_SIZE,
|
||||||
address::{Network, MoneroAddress},
|
address::{Network, MoneroAddress},
|
||||||
rpc::FeeRate,
|
rpc::FeeRate,
|
||||||
ViewPair, GuaranteedViewPair, WalletOutput,
|
ViewPair, GuaranteedViewPair, OutputWithDecoys,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod tx_keys;
|
mod tx_keys;
|
||||||
|
@ -231,7 +230,7 @@ pub enum SendError {
|
||||||
pub struct SignableTransaction {
|
pub struct SignableTransaction {
|
||||||
rct_type: RctType,
|
rct_type: RctType,
|
||||||
outgoing_view_key: Zeroizing<[u8; 32]>,
|
outgoing_view_key: Zeroizing<[u8; 32]>,
|
||||||
inputs: Vec<(WalletOutput, Decoys)>,
|
inputs: Vec<OutputWithDecoys>,
|
||||||
payments: Vec<InternalPayment>,
|
payments: Vec<InternalPayment>,
|
||||||
data: Vec<Vec<u8>>,
|
data: Vec<Vec<u8>>,
|
||||||
fee_rate: FeeRate,
|
fee_rate: FeeRate,
|
||||||
|
@ -252,9 +251,9 @@ impl SignableTransaction {
|
||||||
if self.inputs.is_empty() {
|
if self.inputs.is_empty() {
|
||||||
Err(SendError::NoInputs)?;
|
Err(SendError::NoInputs)?;
|
||||||
}
|
}
|
||||||
for (_, decoys) in &self.inputs {
|
for input in &self.inputs {
|
||||||
// TODO: Add a function for the ring length
|
// TODO: Add a function for the ring length
|
||||||
if decoys.len() !=
|
if input.decoys().len() !=
|
||||||
match self.rct_type {
|
match self.rct_type {
|
||||||
RctType::ClsagBulletproof => 11,
|
RctType::ClsagBulletproof => 11,
|
||||||
RctType::ClsagBulletproofPlus => 16,
|
RctType::ClsagBulletproofPlus => 16,
|
||||||
|
@ -314,7 +313,7 @@ impl SignableTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we have enough funds
|
// Make sure we have enough funds
|
||||||
let in_amount = self.inputs.iter().map(|(input, _)| input.commitment().amount).sum::<u64>();
|
let in_amount = self.inputs.iter().map(|input| input.commitment().amount).sum::<u64>();
|
||||||
let payments_amount = self
|
let payments_amount = self
|
||||||
.payments
|
.payments
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -356,7 +355,7 @@ impl SignableTransaction {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
rct_type: RctType,
|
rct_type: RctType,
|
||||||
outgoing_view_key: Zeroizing<[u8; 32]>,
|
outgoing_view_key: Zeroizing<[u8; 32]>,
|
||||||
inputs: Vec<(WalletOutput, Decoys)>,
|
inputs: Vec<OutputWithDecoys>,
|
||||||
payments: Vec<(MoneroAddress, u64)>,
|
payments: Vec<(MoneroAddress, u64)>,
|
||||||
change: Change,
|
change: Change,
|
||||||
data: Vec<Vec<u8>>,
|
data: Vec<Vec<u8>>,
|
||||||
|
@ -406,11 +405,6 @@ impl SignableTransaction {
|
||||||
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
/// defined serialization.
|
/// defined serialization.
|
||||||
pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
fn write_input<W: io::Write>(input: &(WalletOutput, Decoys), w: &mut W) -> io::Result<()> {
|
|
||||||
input.0.write(w)?;
|
|
||||||
input.1.write(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_payment<W: io::Write>(payment: &InternalPayment, w: &mut W) -> io::Result<()> {
|
fn write_payment<W: io::Write>(payment: &InternalPayment, w: &mut W) -> io::Result<()> {
|
||||||
match payment {
|
match payment {
|
||||||
InternalPayment::Payment(addr, amount) => {
|
InternalPayment::Payment(addr, amount) => {
|
||||||
|
@ -433,7 +427,7 @@ impl SignableTransaction {
|
||||||
|
|
||||||
write_byte(&u8::from(self.rct_type), w)?;
|
write_byte(&u8::from(self.rct_type), w)?;
|
||||||
w.write_all(self.outgoing_view_key.as_slice())?;
|
w.write_all(self.outgoing_view_key.as_slice())?;
|
||||||
write_vec(write_input, &self.inputs, w)?;
|
write_vec(OutputWithDecoys::write, &self.inputs, w)?;
|
||||||
write_vec(write_payment, &self.payments, w)?;
|
write_vec(write_payment, &self.payments, w)?;
|
||||||
write_vec(|data, w| write_vec(write_byte, data, w), &self.data, w)?;
|
write_vec(|data, w| write_vec(write_byte, data, w), &self.data, w)?;
|
||||||
self.fee_rate.write(w)
|
self.fee_rate.write(w)
|
||||||
|
@ -454,10 +448,6 @@ impl SignableTransaction {
|
||||||
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||||
/// defined serialization.
|
/// defined serialization.
|
||||||
pub fn read<R: io::Read>(r: &mut R) -> io::Result<SignableTransaction> {
|
pub fn read<R: io::Read>(r: &mut R) -> io::Result<SignableTransaction> {
|
||||||
fn read_input(r: &mut impl io::Read) -> io::Result<(WalletOutput, Decoys)> {
|
|
||||||
Ok((WalletOutput::read(r)?, Decoys::read(r)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_address<R: io::Read>(r: &mut R) -> io::Result<MoneroAddress> {
|
fn read_address<R: io::Read>(r: &mut R) -> io::Result<MoneroAddress> {
|
||||||
String::from_utf8(read_vec(read_byte, r)?)
|
String::from_utf8(read_vec(read_byte, r)?)
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -484,7 +474,7 @@ impl SignableTransaction {
|
||||||
rct_type: RctType::try_from(read_byte(r)?)
|
rct_type: RctType::try_from(read_byte(r)?)
|
||||||
.map_err(|()| io::Error::other("unsupported/invalid RctType"))?,
|
.map_err(|()| io::Error::other("unsupported/invalid RctType"))?,
|
||||||
outgoing_view_key: Zeroizing::new(read_bytes(r)?),
|
outgoing_view_key: Zeroizing::new(read_bytes(r)?),
|
||||||
inputs: read_vec(read_input, r)?,
|
inputs: read_vec(OutputWithDecoys::read, r)?,
|
||||||
payments: read_vec(read_payment, r)?,
|
payments: read_vec(read_payment, r)?,
|
||||||
data: read_vec(|r| read_vec(read_byte, r), r)?,
|
data: read_vec(|r| read_vec(read_byte, r), r)?,
|
||||||
fee_rate: FeeRate::read(r)?,
|
fee_rate: FeeRate::read(r)?,
|
||||||
|
@ -522,7 +512,7 @@ impl SignableTransaction {
|
||||||
) -> Result<Transaction, SendError> {
|
) -> Result<Transaction, SendError> {
|
||||||
// Calculate the key images
|
// Calculate the key images
|
||||||
let mut key_images = vec![];
|
let mut key_images = vec![];
|
||||||
for (input, _) in &self.inputs {
|
for input in &self.inputs {
|
||||||
let input_key = Zeroizing::new(sender_spend_key.deref() + input.key_offset());
|
let input_key = Zeroizing::new(sender_spend_key.deref() + input.key_offset());
|
||||||
if (input_key.deref() * ED25519_BASEPOINT_TABLE) != input.key() {
|
if (input_key.deref() * ED25519_BASEPOINT_TABLE) != input.key() {
|
||||||
Err(SendError::WrongPrivateKey)?;
|
Err(SendError::WrongPrivateKey)?;
|
||||||
|
@ -536,12 +526,12 @@ impl SignableTransaction {
|
||||||
|
|
||||||
// Prepare the CLSAG signatures
|
// Prepare the CLSAG signatures
|
||||||
let mut clsag_signs = Vec::with_capacity(tx.intent.inputs.len());
|
let mut clsag_signs = Vec::with_capacity(tx.intent.inputs.len());
|
||||||
for (input, decoys) in &tx.intent.inputs {
|
for input in &tx.intent.inputs {
|
||||||
// Re-derive the input key as this will be in a different order
|
// Re-derive the input key as this will be in a different order
|
||||||
let input_key = Zeroizing::new(sender_spend_key.deref() + input.key_offset());
|
let input_key = Zeroizing::new(sender_spend_key.deref() + input.key_offset());
|
||||||
clsag_signs.push((
|
clsag_signs.push((
|
||||||
input_key,
|
input_key,
|
||||||
ClsagContext::new(decoys.clone(), input.commitment().clone())
|
ClsagContext::new(input.decoys().clone(), input.commitment().clone())
|
||||||
.map_err(SendError::ClsagError)?,
|
.map_err(SendError::ClsagError)?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,14 +65,14 @@ impl SignableTransaction {
|
||||||
let mut clsags = vec![];
|
let mut clsags = vec![];
|
||||||
|
|
||||||
let mut key_image_generators_and_offsets = vec![];
|
let mut key_image_generators_and_offsets = vec![];
|
||||||
for (i, (input, decoys)) in self.inputs.iter().enumerate() {
|
for input in &self.inputs {
|
||||||
// Check this is the right set of keys
|
// Check this is the right set of keys
|
||||||
let offset = keys.offset(dfg::Scalar(input.key_offset()));
|
let offset = keys.offset(dfg::Scalar(input.key_offset()));
|
||||||
if offset.group_key().0 != input.key() {
|
if offset.group_key().0 != input.key() {
|
||||||
Err(SendError::WrongPrivateKey)?;
|
Err(SendError::WrongPrivateKey)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = ClsagContext::new(decoys.clone(), input.commitment().clone())
|
let context = ClsagContext::new(input.decoys().clone(), input.commitment().clone())
|
||||||
.map_err(SendError::ClsagError)?;
|
.map_err(SendError::ClsagError)?;
|
||||||
let (clsag, clsag_mask_send) = ClsagMultisig::new(
|
let (clsag, clsag_mask_send) = ClsagMultisig::new(
|
||||||
RecommendedTranscript::new(b"Monero Multisignature Transaction"),
|
RecommendedTranscript::new(b"Monero Multisignature Transaction"),
|
||||||
|
@ -80,7 +80,7 @@ impl SignableTransaction {
|
||||||
);
|
);
|
||||||
key_image_generators_and_offsets.push((
|
key_image_generators_and_offsets.push((
|
||||||
clsag.key_image_generator(),
|
clsag.key_image_generator(),
|
||||||
keys.current_offset().unwrap_or(dfg::Scalar::ZERO).0 + self.inputs[i].0.key_offset(),
|
keys.current_offset().unwrap_or(dfg::Scalar::ZERO).0 + input.key_offset(),
|
||||||
));
|
));
|
||||||
clsags.push((clsag_mask_send, AlgorithmMachine::new(clsag, offset)));
|
clsags.push((clsag_mask_send, AlgorithmMachine::new(clsag, offset)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,10 @@ impl SignableTransaction {
|
||||||
debug_assert_eq!(self.inputs.len(), key_images.len());
|
debug_assert_eq!(self.inputs.len(), key_images.len());
|
||||||
|
|
||||||
let mut res = Vec::with_capacity(self.inputs.len());
|
let mut res = Vec::with_capacity(self.inputs.len());
|
||||||
for ((_, decoys), key_image) in self.inputs.iter().zip(key_images) {
|
for (input, key_image) in self.inputs.iter().zip(key_images) {
|
||||||
res.push(Input::ToKey {
|
res.push(Input::ToKey {
|
||||||
amount: None,
|
amount: None,
|
||||||
key_offsets: decoys.offsets().to_vec(),
|
key_offsets: input.decoys().offsets().to_vec(),
|
||||||
key_image: *key_image,
|
key_image: *key_image,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -299,7 +299,7 @@ impl SignableTransactionWithKeyImages {
|
||||||
} else {
|
} else {
|
||||||
// If we don't have a change output, the difference is the fee
|
// If we don't have a change output, the difference is the fee
|
||||||
let inputs =
|
let inputs =
|
||||||
self.intent.inputs.iter().map(|input| input.0.commitment().amount).sum::<u64>();
|
self.intent.inputs.iter().map(|input| input.commitment().amount).sum::<u64>();
|
||||||
let payments = self
|
let payments = self
|
||||||
.intent
|
.intent
|
||||||
.payments
|
.payments
|
||||||
|
|
|
@ -11,7 +11,7 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, Scalar, EdwardsPoint}
|
||||||
use crate::{
|
use crate::{
|
||||||
primitives::{keccak256, Commitment},
|
primitives::{keccak256, Commitment},
|
||||||
ringct::EncryptedAmount,
|
ringct::EncryptedAmount,
|
||||||
SharedKeyDerivations,
|
SharedKeyDerivations, OutputWithDecoys,
|
||||||
send::{InternalPayment, SignableTransaction, key_image_sort},
|
send::{InternalPayment, SignableTransaction, key_image_sort},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ impl SignableTransaction {
|
||||||
|
|
||||||
// Ensure uniqueness across transactions by binding to a use-once object
|
// Ensure uniqueness across transactions by binding to a use-once object
|
||||||
// The keys for the inputs is binding to their key images, making them use-once
|
// The keys for the inputs is binding to their key images, making them use-once
|
||||||
let mut input_keys = self.inputs.iter().map(|(input, _)| input.key()).collect::<Vec<_>>();
|
let mut input_keys = self.inputs.iter().map(OutputWithDecoys::key).collect::<Vec<_>>();
|
||||||
// We sort the inputs mid-way through TX construction, so apply our own sort to ensure a
|
// We sort the inputs mid-way through TX construction, so apply our own sort to ensure a
|
||||||
// consistent order
|
// consistent order
|
||||||
// We use the key image sort as it's applicable and well-defined, not because these are key
|
// We use the key image sort as it's applicable and well-defined, not because these are key
|
||||||
|
@ -208,7 +208,7 @@ impl SignableTransaction {
|
||||||
let amount = match payment {
|
let amount = match payment {
|
||||||
InternalPayment::Payment(_, amount) => *amount,
|
InternalPayment::Payment(_, amount) => *amount,
|
||||||
InternalPayment::Change(_, _) => {
|
InternalPayment::Change(_, _) => {
|
||||||
let inputs = self.inputs.iter().map(|input| input.0.commitment().amount).sum::<u64>();
|
let inputs = self.inputs.iter().map(|input| input.commitment().amount).sum::<u64>();
|
||||||
let payments = self
|
let payments = self
|
||||||
.payments
|
.payments
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -28,18 +28,17 @@ test!(
|
||||||
// Then make a second tx1
|
// Then make a second tx1
|
||||||
|rct_type: RctType, rpc: SimpleRequestRpc, mut builder: Builder, addr, state: _| async move {
|
|rct_type: RctType, rpc: SimpleRequestRpc, mut builder: Builder, addr, state: _| async move {
|
||||||
let output_tx0: WalletOutput = state;
|
let output_tx0: WalletOutput = state;
|
||||||
let decoys = Decoys::fingerprintable_canonical_select(
|
|
||||||
|
let input = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&rpc,
|
&rpc,
|
||||||
ring_len(rct_type),
|
ring_len(rct_type),
|
||||||
rpc.get_height().await.unwrap(),
|
rpc.get_height().await.unwrap(),
|
||||||
&[output_tx0.clone()],
|
output_tx0.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
builder.add_input(input);
|
||||||
let inputs = [output_tx0.clone()].into_iter().zip(decoys).collect::<Vec<_>>();
|
|
||||||
builder.add_inputs(&inputs);
|
|
||||||
builder.add_payment(addr, 1000000000000);
|
builder.add_payment(addr, 1000000000000);
|
||||||
|
|
||||||
(builder.build().unwrap(), (rct_type, output_tx0))
|
(builder.build().unwrap(), (rct_type, output_tx0))
|
||||||
|
@ -66,17 +65,19 @@ test!(
|
||||||
let mut selected_fresh_decoy = false;
|
let mut selected_fresh_decoy = false;
|
||||||
let mut attempts = 1000;
|
let mut attempts = 1000;
|
||||||
while !selected_fresh_decoy && attempts > 0 {
|
while !selected_fresh_decoy && attempts > 0 {
|
||||||
let decoys = Decoys::fingerprintable_canonical_select(
|
let decoys = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||||
&mut OsRng, // TODO: use a seeded RNG to consistently select the latest output
|
&mut OsRng, // TODO: use a seeded RNG to consistently select the latest output
|
||||||
&rpc,
|
&rpc,
|
||||||
ring_len(rct_type),
|
ring_len(rct_type),
|
||||||
height,
|
height,
|
||||||
&[output_tx0.clone()],
|
output_tx0.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap()
|
||||||
|
.decoys()
|
||||||
|
.clone();
|
||||||
|
|
||||||
selected_fresh_decoy = decoys[0].positions().contains(&most_recent_o_index);
|
selected_fresh_decoy = decoys.positions().contains(&most_recent_o_index);
|
||||||
attempts -= 1;
|
attempts -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,18 +108,16 @@ test!(
|
||||||
|rct_type: RctType, rpc, mut builder: Builder, addr, output_tx0: WalletOutput| async move {
|
|rct_type: RctType, rpc, mut builder: Builder, addr, output_tx0: WalletOutput| async move {
|
||||||
let rpc: SimpleRequestRpc = rpc;
|
let rpc: SimpleRequestRpc = rpc;
|
||||||
|
|
||||||
let decoys = Decoys::select(
|
let input = OutputWithDecoys::new(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&rpc,
|
&rpc,
|
||||||
ring_len(rct_type),
|
ring_len(rct_type),
|
||||||
rpc.get_height().await.unwrap(),
|
rpc.get_height().await.unwrap(),
|
||||||
&[output_tx0.clone()],
|
output_tx0.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
builder.add_input(input);
|
||||||
let inputs = [output_tx0.clone()].into_iter().zip(decoys).collect::<Vec<_>>();
|
|
||||||
builder.add_inputs(&inputs);
|
|
||||||
builder.add_payment(addr, 1000000000000);
|
builder.add_payment(addr, 1000000000000);
|
||||||
|
|
||||||
(builder.build().unwrap(), (rct_type, output_tx0))
|
(builder.build().unwrap(), (rct_type, output_tx0))
|
||||||
|
@ -145,17 +144,19 @@ test!(
|
||||||
let mut selected_fresh_decoy = false;
|
let mut selected_fresh_decoy = false;
|
||||||
let mut attempts = 1000;
|
let mut attempts = 1000;
|
||||||
while !selected_fresh_decoy && attempts > 0 {
|
while !selected_fresh_decoy && attempts > 0 {
|
||||||
let decoys = Decoys::select(
|
let decoys = OutputWithDecoys::new(
|
||||||
&mut OsRng, // TODO: use a seeded RNG to consistently select the latest output
|
&mut OsRng, // TODO: use a seeded RNG to consistently select the latest output
|
||||||
&rpc,
|
&rpc,
|
||||||
ring_len(rct_type),
|
ring_len(rct_type),
|
||||||
height,
|
height,
|
||||||
&[output_tx0.clone()],
|
output_tx0.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap()
|
||||||
|
.decoys()
|
||||||
|
.clone();
|
||||||
|
|
||||||
selected_fresh_decoy = decoys[0].positions().contains(&most_recent_o_index);
|
selected_fresh_decoy = decoys.positions().contains(&most_recent_o_index);
|
||||||
attempts -= 1;
|
attempts -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use zeroize::{Zeroize, Zeroizing};
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
|
|
||||||
use monero_wallet::{
|
use monero_wallet::{
|
||||||
primitives::Decoys,
|
|
||||||
ringct::RctType,
|
ringct::RctType,
|
||||||
rpc::FeeRate,
|
rpc::FeeRate,
|
||||||
address::MoneroAddress,
|
address::MoneroAddress,
|
||||||
WalletOutput,
|
OutputWithDecoys,
|
||||||
send::{Change, SendError, SignableTransaction},
|
send::{Change, SendError, SignableTransaction},
|
||||||
extra::MAX_ARBITRARY_DATA_SIZE,
|
extra::MAX_ARBITRARY_DATA_SIZE,
|
||||||
};
|
};
|
||||||
|
@ -15,7 +14,7 @@ use monero_wallet::{
|
||||||
pub struct SignableTransactionBuilder {
|
pub struct SignableTransactionBuilder {
|
||||||
rct_type: RctType,
|
rct_type: RctType,
|
||||||
outgoing_view_key: Zeroizing<[u8; 32]>,
|
outgoing_view_key: Zeroizing<[u8; 32]>,
|
||||||
inputs: Vec<(WalletOutput, Decoys)>,
|
inputs: Vec<OutputWithDecoys>,
|
||||||
payments: Vec<(MoneroAddress, u64)>,
|
payments: Vec<(MoneroAddress, u64)>,
|
||||||
change: Change,
|
change: Change,
|
||||||
data: Vec<Vec<u8>>,
|
data: Vec<Vec<u8>>,
|
||||||
|
@ -40,12 +39,12 @@ impl SignableTransactionBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_input(&mut self, input: (WalletOutput, Decoys)) -> &mut Self {
|
pub fn add_input(&mut self, input: OutputWithDecoys) -> &mut Self {
|
||||||
self.inputs.push(input);
|
self.inputs.push(input);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn add_inputs(&mut self, inputs: &[(WalletOutput, Decoys)]) -> &mut Self {
|
pub fn add_inputs(&mut self, inputs: &[OutputWithDecoys]) -> &mut Self {
|
||||||
self.inputs.extend(inputs.iter().cloned());
|
self.inputs.extend(inputs.iter().cloned());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,13 +198,10 @@ macro_rules! test {
|
||||||
};
|
};
|
||||||
|
|
||||||
use monero_wallet::{
|
use monero_wallet::{
|
||||||
primitives::Decoys,
|
|
||||||
ringct::RctType,
|
ringct::RctType,
|
||||||
rpc::FeePriority,
|
rpc::FeePriority,
|
||||||
address::Network,
|
address::Network,
|
||||||
ViewPair,
|
ViewPair, Scanner, OutputWithDecoys,
|
||||||
DecoySelection,
|
|
||||||
Scanner,
|
|
||||||
send::{Change, SignableTransaction, Eventuality},
|
send::{Change, SignableTransaction, Eventuality},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -300,16 +297,14 @@ macro_rules! test {
|
||||||
let temp = Box::new({
|
let temp = Box::new({
|
||||||
let mut builder = builder.clone();
|
let mut builder = builder.clone();
|
||||||
|
|
||||||
let decoys = Decoys::fingerprintable_canonical_select(
|
let input = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&rpc,
|
&rpc,
|
||||||
ring_len(rct_type),
|
ring_len(rct_type),
|
||||||
rpc.get_height().await.unwrap(),
|
rpc.get_height().await.unwrap(),
|
||||||
&[miner_tx.clone()],
|
miner_tx,
|
||||||
)
|
).await.unwrap();
|
||||||
.await
|
builder.add_input(input);
|
||||||
.unwrap();
|
|
||||||
builder.add_input((miner_tx, decoys.first().unwrap().clone()));
|
|
||||||
|
|
||||||
let (tx, state) = ($first_tx)(rpc.clone(), builder, next_addr).await;
|
let (tx, state) = ($first_tx)(rpc.clone(), builder, next_addr).await;
|
||||||
let fee_rate = tx.fee_rate().clone();
|
let fee_rate = tx.fee_rate().clone();
|
||||||
|
|
|
@ -4,8 +4,8 @@ use rand_core::OsRng;
|
||||||
|
|
||||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||||
use monero_wallet::{
|
use monero_wallet::{
|
||||||
primitives::Decoys, ringct::RctType, transaction::Transaction, rpc::Rpc,
|
ringct::RctType, transaction::Transaction, rpc::Rpc, address::SubaddressIndex, extra::Extra,
|
||||||
address::SubaddressIndex, extra::Extra, WalletOutput, DecoySelection,
|
WalletOutput, OutputWithDecoys,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod runner;
|
mod runner;
|
||||||
|
@ -18,19 +18,19 @@ async fn add_inputs(
|
||||||
outputs: Vec<WalletOutput>,
|
outputs: Vec<WalletOutput>,
|
||||||
builder: &mut SignableTransactionBuilder,
|
builder: &mut SignableTransactionBuilder,
|
||||||
) {
|
) {
|
||||||
let decoys = Decoys::fingerprintable_canonical_select(
|
for output in outputs {
|
||||||
&mut OsRng,
|
builder.add_input(
|
||||||
rpc,
|
OutputWithDecoys::fingerprintable_deterministic_new(
|
||||||
ring_len(rct_type),
|
&mut OsRng,
|
||||||
rpc.get_height().await.unwrap(),
|
rpc,
|
||||||
&outputs,
|
ring_len(rct_type),
|
||||||
)
|
rpc.get_height().await.unwrap(),
|
||||||
.await
|
output,
|
||||||
.unwrap();
|
)
|
||||||
|
.await
|
||||||
let inputs = outputs.into_iter().zip(decoys).collect::<Vec<_>>();
|
.unwrap(),
|
||||||
|
);
|
||||||
builder.add_inputs(&inputs);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
|
|
|
@ -20,7 +20,7 @@ use monero_wallet::{
|
||||||
block::Block,
|
block::Block,
|
||||||
rpc::{FeeRate, RpcError, Rpc},
|
rpc::{FeeRate, RpcError, Rpc},
|
||||||
address::{Network as MoneroNetwork, SubaddressIndex},
|
address::{Network as MoneroNetwork, SubaddressIndex},
|
||||||
ViewPair, GuaranteedViewPair, WalletOutput, GuaranteedScanner, DecoySelection, Decoys,
|
ViewPair, GuaranteedViewPair, WalletOutput, OutputWithDecoys, GuaranteedScanner,
|
||||||
send::{
|
send::{
|
||||||
SendError, Change, SignableTransaction as MSignableTransaction, Eventuality, TransactionMachine,
|
SendError, Change, SignableTransaction as MSignableTransaction, Eventuality, TransactionMachine,
|
||||||
},
|
},
|
||||||
|
@ -322,30 +322,31 @@ impl Monero {
|
||||||
_ => panic!("Monero hard forked and the processor wasn't updated for it"),
|
_ => panic!("Monero hard forked and the processor wasn't updated for it"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let spendable_outputs = inputs.iter().map(|input| input.0.clone()).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut transcript =
|
let mut transcript =
|
||||||
RecommendedTranscript::new(b"Serai Processor Monero Transaction Transcript");
|
RecommendedTranscript::new(b"Serai Processor Monero Transaction Transcript");
|
||||||
transcript.append_message(b"plan", plan_id);
|
transcript.append_message(b"plan", plan_id);
|
||||||
|
|
||||||
// All signers need to select the same decoys
|
// All signers need to select the same decoys
|
||||||
// All signers use the same height and a seeded RNG to make sure they do so.
|
// All signers use the same height and a seeded RNG to make sure they do so.
|
||||||
let decoys = Decoys::fingerprintable_canonical_select(
|
let mut inputs_actual = Vec::with_capacity(inputs.len());
|
||||||
&mut ChaCha20Rng::from_seed(transcript.rng_seed(b"decoys")),
|
for input in inputs {
|
||||||
&self.rpc,
|
inputs_actual.push(
|
||||||
// TODO: Have Decoys take RctType
|
OutputWithDecoys::fingerprintable_deterministic_new(
|
||||||
match rct_type {
|
&mut ChaCha20Rng::from_seed(transcript.rng_seed(b"decoys")),
|
||||||
RctType::ClsagBulletproof => 11,
|
&self.rpc,
|
||||||
RctType::ClsagBulletproofPlus => 16,
|
// TODO: Have Decoys take RctType
|
||||||
_ => panic!("selecting decoys for an unsupported RctType"),
|
match rct_type {
|
||||||
},
|
RctType::ClsagBulletproof => 11,
|
||||||
block_number + 1,
|
RctType::ClsagBulletproofPlus => 16,
|
||||||
&spendable_outputs,
|
_ => panic!("selecting decoys for an unsupported RctType"),
|
||||||
)
|
},
|
||||||
.await
|
block_number + 1,
|
||||||
.map_err(map_rpc_err)?;
|
input.0.clone(),
|
||||||
|
)
|
||||||
let inputs = spendable_outputs.into_iter().zip(decoys).collect::<Vec<_>>();
|
.await
|
||||||
|
.map_err(map_rpc_err)?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Monero requires at least two outputs
|
// Monero requires at least two outputs
|
||||||
// If we only have one output planned, add a dummy payment
|
// If we only have one output planned, add a dummy payment
|
||||||
|
@ -375,7 +376,7 @@ impl Monero {
|
||||||
rct_type,
|
rct_type,
|
||||||
// Use the plan ID as the outgoing view key
|
// Use the plan ID as the outgoing view key
|
||||||
Zeroizing::new(*plan_id),
|
Zeroizing::new(*plan_id),
|
||||||
inputs.clone(),
|
inputs_actual,
|
||||||
payments,
|
payments,
|
||||||
Change::fingerprintable(change.as_ref().map(|change| change.clone().into())),
|
Change::fingerprintable(change.as_ref().map(|change| change.clone().into())),
|
||||||
vec![],
|
vec![],
|
||||||
|
@ -400,7 +401,7 @@ impl Monero {
|
||||||
SendError::TooMuchArbitraryData |
|
SendError::TooMuchArbitraryData |
|
||||||
SendError::TooLargeTransaction |
|
SendError::TooLargeTransaction |
|
||||||
SendError::WrongPrivateKey => {
|
SendError::WrongPrivateKey => {
|
||||||
panic!("created an Monero invalid transaction: {e}");
|
panic!("created an invalid Monero transaction: {e}");
|
||||||
}
|
}
|
||||||
SendError::MultiplePaymentIds => {
|
SendError::MultiplePaymentIds => {
|
||||||
panic!("multiple payment IDs despite not supporting integrated addresses");
|
panic!("multiple payment IDs despite not supporting integrated addresses");
|
||||||
|
@ -736,10 +737,11 @@ impl Network for Monero {
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_block = self.rpc.get_block_by_number(new_block).await.unwrap();
|
let new_block = self.rpc.get_block_by_number(new_block).await.unwrap();
|
||||||
let outputs =
|
let mut outputs =
|
||||||
Self::test_scanner().scan(&self.rpc, &new_block).await.unwrap().ignore_additional_timelock();
|
Self::test_scanner().scan(&self.rpc, &new_block).await.unwrap().ignore_additional_timelock();
|
||||||
|
let output = outputs.swap_remove(0);
|
||||||
|
|
||||||
let amount = outputs[0].commitment().amount;
|
let amount = output.commitment().amount;
|
||||||
// The dust should always be sufficient for the fee
|
// The dust should always be sufficient for the fee
|
||||||
let fee = Monero::DUST;
|
let fee = Monero::DUST;
|
||||||
|
|
||||||
|
@ -749,7 +751,7 @@ impl Network for Monero {
|
||||||
_ => panic!("Monero hard forked and the processor wasn't updated for it"),
|
_ => panic!("Monero hard forked and the processor wasn't updated for it"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let decoys = Decoys::fingerprintable_canonical_select(
|
let output = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&self.rpc,
|
&self.rpc,
|
||||||
match rct_type {
|
match rct_type {
|
||||||
|
@ -758,19 +760,17 @@ impl Network for Monero {
|
||||||
_ => panic!("selecting decoys for an unsupported RctType"),
|
_ => panic!("selecting decoys for an unsupported RctType"),
|
||||||
},
|
},
|
||||||
self.rpc.get_height().await.unwrap(),
|
self.rpc.get_height().await.unwrap(),
|
||||||
&outputs,
|
output,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let inputs = outputs.into_iter().zip(decoys).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut outgoing_view_key = Zeroizing::new([0; 32]);
|
let mut outgoing_view_key = Zeroizing::new([0; 32]);
|
||||||
OsRng.fill_bytes(outgoing_view_key.as_mut());
|
OsRng.fill_bytes(outgoing_view_key.as_mut());
|
||||||
let tx = MSignableTransaction::new(
|
let tx = MSignableTransaction::new(
|
||||||
rct_type,
|
rct_type,
|
||||||
outgoing_view_key,
|
outgoing_view_key,
|
||||||
inputs,
|
vec![output],
|
||||||
vec![(address.into(), amount - fee)],
|
vec![(address.into(), amount - fee)],
|
||||||
Change::fingerprintable(Some(Self::test_address().into())),
|
Change::fingerprintable(Some(Self::test_address().into())),
|
||||||
vec![],
|
vec![],
|
||||||
|
|
|
@ -348,7 +348,7 @@ async fn mint_and_burn_test() {
|
||||||
ringct::RctType,
|
ringct::RctType,
|
||||||
rpc::{FeePriority, Rpc},
|
rpc::{FeePriority, Rpc},
|
||||||
address::{Network, AddressType, MoneroAddress},
|
address::{Network, AddressType, MoneroAddress},
|
||||||
ViewPair, Scanner, DecoySelection, Decoys,
|
ViewPair, Scanner, OutputWithDecoys,
|
||||||
send::{Change, SignableTransaction},
|
send::{Change, SignableTransaction},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -363,23 +363,22 @@ async fn mint_and_burn_test() {
|
||||||
.additional_timelock_satisfied_by(rpc.get_height().await.unwrap(), 0)
|
.additional_timelock_satisfied_by(rpc.get_height().await.unwrap(), 0)
|
||||||
.swap_remove(0);
|
.swap_remove(0);
|
||||||
|
|
||||||
let decoys = Decoys::fingerprintable_canonical_select(
|
let input = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&rpc,
|
&rpc,
|
||||||
16,
|
16,
|
||||||
rpc.get_height().await.unwrap(),
|
rpc.get_height().await.unwrap(),
|
||||||
&[output.clone()],
|
output.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.swap_remove(0);
|
|
||||||
|
|
||||||
let mut outgoing_view_key = Zeroizing::new([0; 32]);
|
let mut outgoing_view_key = Zeroizing::new([0; 32]);
|
||||||
OsRng.fill_bytes(outgoing_view_key.as_mut());
|
OsRng.fill_bytes(outgoing_view_key.as_mut());
|
||||||
let tx = SignableTransaction::new(
|
let tx = SignableTransaction::new(
|
||||||
RctType::ClsagBulletproofPlus,
|
RctType::ClsagBulletproofPlus,
|
||||||
outgoing_view_key,
|
outgoing_view_key,
|
||||||
vec![(output, decoys)],
|
vec![input],
|
||||||
vec![(
|
vec![(
|
||||||
MoneroAddress::new(
|
MoneroAddress::new(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
|
|
|
@ -412,7 +412,7 @@ impl Wallet {
|
||||||
ringct::RctType,
|
ringct::RctType,
|
||||||
rpc::{FeePriority, Rpc},
|
rpc::{FeePriority, Rpc},
|
||||||
address::{Network, AddressType, Address},
|
address::{Network, AddressType, Address},
|
||||||
Scanner, DecoySelection, Decoys,
|
Scanner, OutputWithDecoys,
|
||||||
send::{Change, SignableTransaction},
|
send::{Change, SignableTransaction},
|
||||||
};
|
};
|
||||||
use processor::{additional_key, networks::Monero};
|
use processor::{additional_key, networks::Monero};
|
||||||
|
@ -422,30 +422,35 @@ impl Wallet {
|
||||||
|
|
||||||
// Prepare inputs
|
// Prepare inputs
|
||||||
let current_height = rpc.get_height().await.unwrap();
|
let current_height = rpc.get_height().await.unwrap();
|
||||||
let mut inputs = vec![];
|
let mut outputs = vec![];
|
||||||
for block in last_tx.0 .. current_height {
|
for block in last_tx.0 .. current_height {
|
||||||
let block = rpc.get_block_by_number(block).await.unwrap();
|
let block = rpc.get_block_by_number(block).await.unwrap();
|
||||||
if (block.miner_transaction.hash() == last_tx.1) ||
|
if (block.miner_transaction.hash() == last_tx.1) ||
|
||||||
block.transactions.contains(&last_tx.1)
|
block.transactions.contains(&last_tx.1)
|
||||||
{
|
{
|
||||||
inputs = Scanner::new(view_pair.clone())
|
outputs = Scanner::new(view_pair.clone())
|
||||||
.scan(&rpc, &block)
|
.scan(&rpc, &block)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.ignore_additional_timelock();
|
.ignore_additional_timelock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert!(!inputs.is_empty());
|
assert!(!outputs.is_empty());
|
||||||
|
|
||||||
let mut decoys = Decoys::fingerprintable_canonical_select(
|
let mut inputs = Vec::with_capacity(outputs.len());
|
||||||
&mut OsRng,
|
for output in outputs {
|
||||||
&rpc,
|
inputs.push(
|
||||||
16,
|
OutputWithDecoys::fingerprintable_deterministic_new(
|
||||||
rpc.get_height().await.unwrap(),
|
&mut OsRng,
|
||||||
&inputs,
|
&rpc,
|
||||||
)
|
16,
|
||||||
.await
|
rpc.get_height().await.unwrap(),
|
||||||
.unwrap();
|
output,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let to_spend_key = decompress_point(<[u8; 32]>::try_from(to.as_ref()).unwrap()).unwrap();
|
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_view_key = additional_key::<Monero>(0);
|
||||||
|
@ -467,7 +472,7 @@ impl Wallet {
|
||||||
let tx = SignableTransaction::new(
|
let tx = SignableTransaction::new(
|
||||||
RctType::ClsagBulletproofPlus,
|
RctType::ClsagBulletproofPlus,
|
||||||
outgoing_view_key,
|
outgoing_view_key,
|
||||||
inputs.drain(..).zip(decoys.drain(..)).collect(),
|
inputs,
|
||||||
vec![(to_addr, AMOUNT)],
|
vec![(to_addr, AMOUNT)],
|
||||||
Change::new(view_pair),
|
Change::new(view_pair),
|
||||||
data,
|
data,
|
||||||
|
|
Loading…
Reference in a new issue