mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-22 19:49: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"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"curve25519-dalek",
|
||||
"dalek-ff-group",
|
||||
"flexible-transcript",
|
||||
|
|
|
@ -137,13 +137,23 @@ impl Commitment {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
offsets: Vec<u64>,
|
||||
signer_index: u8,
|
||||
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)]
|
||||
impl Decoys {
|
||||
/// Create a new instance of decoy data.
|
||||
|
|
|
@ -18,7 +18,6 @@ workspace = true
|
|||
[dependencies]
|
||||
std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false }
|
||||
|
||||
async-trait = { version = "0.1", default-features = false }
|
||||
thiserror = { version = "1", default-features = false, optional = true }
|
||||
|
||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
// 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_distr::{Distribution, Gamma};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use rand_distr::num_traits::Float;
|
||||
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
use curve25519_dalek::{Scalar, EdwardsPoint};
|
||||
|
||||
use crate::{
|
||||
DEFAULT_LOCK_WINDOW, COINBASE_LOCK_WINDOW, BLOCK_TIME,
|
||||
primitives::Commitment,
|
||||
rpc::{RpcError, Rpc},
|
||||
output::OutputData,
|
||||
WalletOutput,
|
||||
};
|
||||
|
||||
|
@ -138,20 +140,16 @@ async fn select_decoys<R: RngCore + CryptoRng>(
|
|||
rpc: &impl Rpc,
|
||||
ring_len: usize,
|
||||
height: usize,
|
||||
inputs: &[WalletOutput],
|
||||
input: &WalletOutput,
|
||||
fingerprintable_canonical: bool,
|
||||
) -> Result<Vec<Decoys>, RpcError> {
|
||||
) -> Result<Decoys, RpcError> {
|
||||
let mut distribution = vec![];
|
||||
|
||||
let decoy_count = ring_len - 1;
|
||||
|
||||
// Convert the inputs in question to the raw output data
|
||||
let mut real = Vec::with_capacity(inputs.len());
|
||||
let mut outputs = Vec::with_capacity(inputs.len());
|
||||
for input in inputs {
|
||||
real.push(input.relative_id.index_on_blockchain);
|
||||
outputs.push((real[real.len() - 1], [input.key(), input.commitment().calculate()]));
|
||||
}
|
||||
let mut real = vec![input.relative_id.index_on_blockchain];
|
||||
let output = (real[0], [input.key(), input.commitment().calculate()]);
|
||||
|
||||
if distribution.len() < height {
|
||||
// TODO: verify distribution elems are strictly increasing
|
||||
|
@ -175,16 +173,14 @@ async fn select_decoys<R: RngCore + CryptoRng>(
|
|||
};
|
||||
|
||||
let mut used = HashSet::<u64>::new();
|
||||
for o in &outputs {
|
||||
used.insert(o.0);
|
||||
}
|
||||
used.insert(output.0);
|
||||
|
||||
// TODO: Create a TX with less than the target amount, as allowed by the protocol
|
||||
let high = distribution[distribution.len() - DEFAULT_LOCK_WINDOW];
|
||||
// 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)
|
||||
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()))?;
|
||||
}
|
||||
|
@ -201,17 +197,15 @@ async fn select_decoys<R: RngCore + CryptoRng>(
|
|||
per_second,
|
||||
&real,
|
||||
&mut used,
|
||||
inputs.len() * decoy_count,
|
||||
decoy_count,
|
||||
fingerprintable_canonical,
|
||||
)
|
||||
.await?;
|
||||
real.zeroize();
|
||||
|
||||
let mut res = Vec::with_capacity(inputs.len());
|
||||
for o in outputs {
|
||||
// Grab the decoys for this specific output
|
||||
let mut ring = decoys.drain((decoys.len() - decoy_count) ..).collect::<Vec<_>>();
|
||||
ring.push(o);
|
||||
ring.push(output);
|
||||
ring.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
// Sanity checks are only run when 1000 outputs are available in Monero
|
||||
|
@ -229,8 +223,8 @@ async fn select_decoys<R: RngCore + CryptoRng>(
|
|||
// 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<_>>() {
|
||||
// If we removed the real spend, add it back
|
||||
if removed.0 == o.0 {
|
||||
ring.push(o);
|
||||
if removed.0 == output.0 {
|
||||
ring.push(output);
|
||||
} else {
|
||||
// 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
|
||||
|
@ -263,74 +257,103 @@ async fn select_decoys<R: RngCore + CryptoRng>(
|
|||
// members
|
||||
}
|
||||
|
||||
res.push(
|
||||
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 < o.0)).unwrap(),
|
||||
u8::try_from(ring.partition_point(|x| x.0 < output.0)).unwrap(),
|
||||
ring.iter().map(|output| output.1).collect(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
)
|
||||
}
|
||||
|
||||
pub use monero_serai::primitives::Decoys;
|
||||
|
||||
// TODO: Remove this trait
|
||||
/// TODO: Document
|
||||
#[cfg(feature = "std")]
|
||||
#[async_trait::async_trait]
|
||||
pub trait DecoySelection {
|
||||
/// 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>(
|
||||
rng: &mut R,
|
||||
/// An output with decoys selected.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct OutputWithDecoys {
|
||||
output: OutputData,
|
||||
decoys: Decoys,
|
||||
}
|
||||
|
||||
impl OutputWithDecoys {
|
||||
/// Select decoys for this output.
|
||||
pub async fn new(
|
||||
rng: &mut (impl Send + Sync + RngCore + CryptoRng),
|
||||
rpc: &impl Rpc,
|
||||
ring_len: usize,
|
||||
height: usize,
|
||||
inputs: &[WalletOutput],
|
||||
) -> Result<Vec<Decoys>, RpcError>;
|
||||
output: WalletOutput,
|
||||
) -> 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
|
||||
/// 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.
|
||||
/// Select a set of decoys for this output with a deterministic process.
|
||||
///
|
||||
/// TODO: upstream change to monerod get_outs RPC to accept a height param for checking
|
||||
/// output's unlocked status and remove all usage of fingerprintable_canonical
|
||||
async fn fingerprintable_canonical_select<R: Send + Sync + RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
/// This function will always output the same set of decoys when called with the same arguments.
|
||||
/// This makes it very useful in multisignature contexts, where instead of having one participant
|
||||
/// select the decoys, everyone can locally select the decoys while coming to the same result.
|
||||
///
|
||||
/// 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,
|
||||
ring_len: usize,
|
||||
height: usize,
|
||||
inputs: &[WalletOutput],
|
||||
) -> Result<Vec<Decoys>, RpcError>;
|
||||
}
|
||||
|
||||
#[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
|
||||
output: WalletOutput,
|
||||
) -> Result<OutputWithDecoys, RpcError> {
|
||||
let decoys = select_decoys(rng, rpc, ring_len, height, &output, true).await?;
|
||||
Ok(OutputWithDecoys { output: output.data.clone(), decoys })
|
||||
}
|
||||
|
||||
async fn fingerprintable_canonical_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, true).await
|
||||
/// The key this output may be spent by.
|
||||
pub fn key(&self) -> EdwardsPoint {
|
||||
self.output.key()
|
||||
}
|
||||
|
||||
/// The scalar to add to the private spend key for it to be the discrete logarithm of this
|
||||
/// output's key.
|
||||
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;
|
||||
pub use scan::{Scanner, GuaranteedScanner};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod decoys;
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod decoys {
|
||||
pub use monero_serai::primitives::Decoys;
|
||||
/// TODO: Document/remove
|
||||
pub trait DecoySelection {}
|
||||
}
|
||||
pub use decoys::{DecoySelection, Decoys};
|
||||
pub use decoys::OutputWithDecoys;
|
||||
|
||||
/// Structs and functionality for sending transactions.
|
||||
pub mod send;
|
||||
|
|
|
@ -52,21 +52,15 @@ impl AbsoluteId {
|
|||
|
||||
/// An output's relative ID.
|
||||
///
|
||||
/// This id defined as the block which contains the transaction creating the output and the
|
||||
/// output's index on the blockchain.
|
||||
/// This is defined as the output's index on the blockchain.
|
||||
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
pub(crate) struct RelativeId {
|
||||
pub(crate) block: [u8; 32],
|
||||
pub(crate) index_on_blockchain: u64,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for RelativeId {
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
||||
fmt
|
||||
.debug_struct("RelativeId")
|
||||
.field("block", &hex::encode(self.block))
|
||||
.field("index_on_blockchain", &self.index_on_blockchain)
|
||||
.finish()
|
||||
fmt.debug_struct("RelativeId").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
|
||||
/// defined serialization.
|
||||
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())
|
||||
}
|
||||
|
||||
|
@ -85,18 +78,16 @@ impl RelativeId {
|
|||
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||
/// defined serialization.
|
||||
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
|
||||
/// timelock.
|
||||
/// The data within an output, as necessary to spend the output.
|
||||
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
pub(crate) struct OutputData {
|
||||
pub(crate) key: EdwardsPoint,
|
||||
pub(crate) key_offset: Scalar,
|
||||
pub(crate) commitment: Commitment,
|
||||
pub(crate) additional_timelock: Timelock,
|
||||
}
|
||||
|
||||
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_offset", &hex::encode(self.key_offset.to_bytes()))
|
||||
.field("commitment", &self.commitment)
|
||||
.field("additional_timelock", &self.additional_timelock)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
/// 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_offset.to_bytes())?;
|
||||
self.commitment.write(w)?;
|
||||
self.additional_timelock.write(w)
|
||||
self.commitment.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.
|
||||
///
|
||||
/// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
|
||||
/// 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 {
|
||||
key: read_point(r)?,
|
||||
key_offset: read_scalar(r)?,
|
||||
commitment: Commitment::read(r)?,
|
||||
additional_timelock: Timelock::read(r)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -140,6 +153,7 @@ impl OutputData {
|
|||
/// The metadata for an output.
|
||||
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
pub(crate) struct Metadata {
|
||||
pub(crate) additional_timelock: Timelock,
|
||||
pub(crate) subaddress: Option<SubaddressIndex>,
|
||||
pub(crate) payment_id: Option<PaymentId>,
|
||||
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> {
|
||||
fmt
|
||||
.debug_struct("Metadata")
|
||||
.field("additional_timelock", &self.additional_timelock)
|
||||
.field("subaddress", &self.subaddress)
|
||||
.field("payment_id", &self.payment_id)
|
||||
.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
|
||||
/// defined serialization.
|
||||
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
self.additional_timelock.write(w)?;
|
||||
|
||||
if let Some(subaddress) = self.subaddress {
|
||||
w.write_all(&[1])?;
|
||||
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
|
||||
/// defined serialization.
|
||||
fn read<R: Read>(r: &mut R) -> io::Result<Metadata> {
|
||||
let additional_timelock = Timelock::read(r)?;
|
||||
|
||||
let subaddress = match read_byte(r)? {
|
||||
0 => None,
|
||||
1 => Some(
|
||||
|
@ -200,6 +219,7 @@ impl Metadata {
|
|||
};
|
||||
|
||||
Ok(Metadata {
|
||||
additional_timelock,
|
||||
subaddress,
|
||||
payment_id: if read_byte(r)? == 1 { PaymentId::read(r).ok() } else { None },
|
||||
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.
|
||||
///
|
||||
|
@ -244,11 +264,6 @@ impl WalletOutput {
|
|||
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.
|
||||
pub fn index_on_blockchain(&self) -> u64 {
|
||||
self.relative_id.index_on_blockchain
|
||||
|
@ -256,18 +271,18 @@ impl WalletOutput {
|
|||
|
||||
/// The key this output may be spent by.
|
||||
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
|
||||
/// output's key.
|
||||
pub fn key_offset(&self) -> Scalar {
|
||||
self.data.key_offset
|
||||
self.data.key_offset()
|
||||
}
|
||||
|
||||
/// The commitment this output created.
|
||||
pub fn commitment(&self) -> &Commitment {
|
||||
&self.data.commitment
|
||||
self.data.commitment()
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// function only returns the additional 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.
|
||||
|
|
|
@ -107,7 +107,6 @@ impl InternalScanner {
|
|||
|
||||
fn scan_transaction(
|
||||
&self,
|
||||
block_hash: [u8; 32],
|
||||
tx_start_index_on_blockchain: u64,
|
||||
tx: &Transaction,
|
||||
) -> Result<Timelocked, RpcError> {
|
||||
|
@ -224,16 +223,15 @@ impl InternalScanner {
|
|||
index_in_transaction: o.try_into().unwrap(),
|
||||
},
|
||||
relative_id: RelativeId {
|
||||
block: block_hash,
|
||||
index_on_blockchain: tx_start_index_on_blockchain + u64::try_from(o).unwrap(),
|
||||
},
|
||||
data: OutputData {
|
||||
key: output_key,
|
||||
key_offset,
|
||||
commitment,
|
||||
data: OutputData { key: output_key, key_offset, commitment },
|
||||
metadata: Metadata {
|
||||
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
|
||||
|
@ -253,8 +251,6 @@ impl InternalScanner {
|
|||
)))?;
|
||||
}
|
||||
|
||||
let block_hash = block.hash();
|
||||
|
||||
// We obtain all TXs in full
|
||||
let mut txs = vec![block.miner_transaction.clone()];
|
||||
txs.extend(rpc.get_transactions(&block.transactions).await?);
|
||||
|
@ -327,7 +323,7 @@ impl InternalScanner {
|
|||
{
|
||||
let mut this_txs_outputs = vec![];
|
||||
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,
|
||||
);
|
||||
res.0.extend(this_txs_outputs);
|
||||
|
@ -379,27 +375,6 @@ impl Scanner {
|
|||
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.
|
||||
pub async fn scan(&mut self, rpc: &impl Rpc, block: &Block) -> Result<Timelocked, RpcError> {
|
||||
self.0.scan(rpc, block).await
|
||||
|
@ -429,27 +404,6 @@ impl GuaranteedScanner {
|
|||
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.
|
||||
pub async fn scan(&mut self, rpc: &impl Rpc, block: &Block) -> Result<Timelocked, RpcError> {
|
||||
self.0.scan(rpc, block).await
|
||||
|
|
|
@ -17,7 +17,6 @@ use frost::FrostError;
|
|||
use crate::{
|
||||
io::*,
|
||||
generators::{MAX_COMMITMENTS, hash_to_point},
|
||||
primitives::Decoys,
|
||||
ringct::{
|
||||
clsag::{ClsagError, ClsagContext, Clsag},
|
||||
RctType, RctPrunable, RctProofs,
|
||||
|
@ -26,7 +25,7 @@ use crate::{
|
|||
extra::MAX_ARBITRARY_DATA_SIZE,
|
||||
address::{Network, MoneroAddress},
|
||||
rpc::FeeRate,
|
||||
ViewPair, GuaranteedViewPair, WalletOutput,
|
||||
ViewPair, GuaranteedViewPair, OutputWithDecoys,
|
||||
};
|
||||
|
||||
mod tx_keys;
|
||||
|
@ -231,7 +230,7 @@ pub enum SendError {
|
|||
pub struct SignableTransaction {
|
||||
rct_type: RctType,
|
||||
outgoing_view_key: Zeroizing<[u8; 32]>,
|
||||
inputs: Vec<(WalletOutput, Decoys)>,
|
||||
inputs: Vec<OutputWithDecoys>,
|
||||
payments: Vec<InternalPayment>,
|
||||
data: Vec<Vec<u8>>,
|
||||
fee_rate: FeeRate,
|
||||
|
@ -252,9 +251,9 @@ impl SignableTransaction {
|
|||
if self.inputs.is_empty() {
|
||||
Err(SendError::NoInputs)?;
|
||||
}
|
||||
for (_, decoys) in &self.inputs {
|
||||
for input in &self.inputs {
|
||||
// TODO: Add a function for the ring length
|
||||
if decoys.len() !=
|
||||
if input.decoys().len() !=
|
||||
match self.rct_type {
|
||||
RctType::ClsagBulletproof => 11,
|
||||
RctType::ClsagBulletproofPlus => 16,
|
||||
|
@ -314,7 +313,7 @@ impl SignableTransaction {
|
|||
}
|
||||
|
||||
// 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
|
||||
.payments
|
||||
.iter()
|
||||
|
@ -356,7 +355,7 @@ impl SignableTransaction {
|
|||
pub fn new(
|
||||
rct_type: RctType,
|
||||
outgoing_view_key: Zeroizing<[u8; 32]>,
|
||||
inputs: Vec<(WalletOutput, Decoys)>,
|
||||
inputs: Vec<OutputWithDecoys>,
|
||||
payments: Vec<(MoneroAddress, u64)>,
|
||||
change: Change,
|
||||
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
|
||||
/// defined serialization.
|
||||
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<()> {
|
||||
match payment {
|
||||
InternalPayment::Payment(addr, amount) => {
|
||||
|
@ -433,7 +427,7 @@ impl SignableTransaction {
|
|||
|
||||
write_byte(&u8::from(self.rct_type), w)?;
|
||||
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(|data, w| write_vec(write_byte, data, w), &self.data, 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
|
||||
/// defined serialization.
|
||||
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> {
|
||||
String::from_utf8(read_vec(read_byte, r)?)
|
||||
.ok()
|
||||
|
@ -484,7 +474,7 @@ impl SignableTransaction {
|
|||
rct_type: RctType::try_from(read_byte(r)?)
|
||||
.map_err(|()| io::Error::other("unsupported/invalid RctType"))?,
|
||||
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)?,
|
||||
data: read_vec(|r| read_vec(read_byte, r), r)?,
|
||||
fee_rate: FeeRate::read(r)?,
|
||||
|
@ -522,7 +512,7 @@ impl SignableTransaction {
|
|||
) -> Result<Transaction, SendError> {
|
||||
// Calculate the key images
|
||||
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());
|
||||
if (input_key.deref() * ED25519_BASEPOINT_TABLE) != input.key() {
|
||||
Err(SendError::WrongPrivateKey)?;
|
||||
|
@ -536,12 +526,12 @@ impl SignableTransaction {
|
|||
|
||||
// Prepare the CLSAG signatures
|
||||
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
|
||||
let input_key = Zeroizing::new(sender_spend_key.deref() + input.key_offset());
|
||||
clsag_signs.push((
|
||||
input_key,
|
||||
ClsagContext::new(decoys.clone(), input.commitment().clone())
|
||||
ClsagContext::new(input.decoys().clone(), input.commitment().clone())
|
||||
.map_err(SendError::ClsagError)?,
|
||||
));
|
||||
}
|
||||
|
|
|
@ -65,14 +65,14 @@ impl SignableTransaction {
|
|||
let mut clsags = 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
|
||||
let offset = keys.offset(dfg::Scalar(input.key_offset()));
|
||||
if offset.group_key().0 != input.key() {
|
||||
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)?;
|
||||
let (clsag, clsag_mask_send) = ClsagMultisig::new(
|
||||
RecommendedTranscript::new(b"Monero Multisignature Transaction"),
|
||||
|
@ -80,7 +80,7 @@ impl SignableTransaction {
|
|||
);
|
||||
key_image_generators_and_offsets.push((
|
||||
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)));
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@ impl SignableTransaction {
|
|||
debug_assert_eq!(self.inputs.len(), key_images.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 {
|
||||
amount: None,
|
||||
key_offsets: decoys.offsets().to_vec(),
|
||||
key_offsets: input.decoys().offsets().to_vec(),
|
||||
key_image: *key_image,
|
||||
});
|
||||
}
|
||||
|
@ -299,7 +299,7 @@ impl SignableTransactionWithKeyImages {
|
|||
} else {
|
||||
// If we don't have a change output, the difference is the fee
|
||||
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
|
||||
.intent
|
||||
.payments
|
||||
|
|
|
@ -11,7 +11,7 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, Scalar, EdwardsPoint}
|
|||
use crate::{
|
||||
primitives::{keccak256, Commitment},
|
||||
ringct::EncryptedAmount,
|
||||
SharedKeyDerivations,
|
||||
SharedKeyDerivations, OutputWithDecoys,
|
||||
send::{InternalPayment, SignableTransaction, key_image_sort},
|
||||
};
|
||||
|
||||
|
@ -26,7 +26,7 @@ impl SignableTransaction {
|
|||
|
||||
// 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
|
||||
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
|
||||
// consistent order
|
||||
// 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 {
|
||||
InternalPayment::Payment(_, amount) => *amount,
|
||||
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
|
||||
.payments
|
||||
.iter()
|
||||
|
|
|
@ -28,18 +28,17 @@ test!(
|
|||
// Then make a second tx1
|
||||
|rct_type: RctType, rpc: SimpleRequestRpc, mut builder: Builder, addr, state: _| async move {
|
||||
let output_tx0: WalletOutput = state;
|
||||
let decoys = Decoys::fingerprintable_canonical_select(
|
||||
|
||||
let input = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
ring_len(rct_type),
|
||||
rpc.get_height().await.unwrap(),
|
||||
&[output_tx0.clone()],
|
||||
output_tx0.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let inputs = [output_tx0.clone()].into_iter().zip(decoys).collect::<Vec<_>>();
|
||||
builder.add_inputs(&inputs);
|
||||
builder.add_input(input);
|
||||
builder.add_payment(addr, 1000000000000);
|
||||
|
||||
(builder.build().unwrap(), (rct_type, output_tx0))
|
||||
|
@ -66,17 +65,19 @@ test!(
|
|||
let mut selected_fresh_decoy = false;
|
||||
let mut attempts = 1000;
|
||||
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
|
||||
&rpc,
|
||||
ring_len(rct_type),
|
||||
height,
|
||||
&[output_tx0.clone()],
|
||||
output_tx0.clone(),
|
||||
)
|
||||
.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;
|
||||
}
|
||||
|
||||
|
@ -107,18 +108,16 @@ test!(
|
|||
|rct_type: RctType, rpc, mut builder: Builder, addr, output_tx0: WalletOutput| async move {
|
||||
let rpc: SimpleRequestRpc = rpc;
|
||||
|
||||
let decoys = Decoys::select(
|
||||
let input = OutputWithDecoys::new(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
ring_len(rct_type),
|
||||
rpc.get_height().await.unwrap(),
|
||||
&[output_tx0.clone()],
|
||||
output_tx0.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let inputs = [output_tx0.clone()].into_iter().zip(decoys).collect::<Vec<_>>();
|
||||
builder.add_inputs(&inputs);
|
||||
builder.add_input(input);
|
||||
builder.add_payment(addr, 1000000000000);
|
||||
|
||||
(builder.build().unwrap(), (rct_type, output_tx0))
|
||||
|
@ -145,17 +144,19 @@ test!(
|
|||
let mut selected_fresh_decoy = false;
|
||||
let mut attempts = 1000;
|
||||
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
|
||||
&rpc,
|
||||
ring_len(rct_type),
|
||||
height,
|
||||
&[output_tx0.clone()],
|
||||
output_tx0.clone(),
|
||||
)
|
||||
.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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use monero_wallet::{
|
||||
primitives::Decoys,
|
||||
ringct::RctType,
|
||||
rpc::FeeRate,
|
||||
address::MoneroAddress,
|
||||
WalletOutput,
|
||||
OutputWithDecoys,
|
||||
send::{Change, SendError, SignableTransaction},
|
||||
extra::MAX_ARBITRARY_DATA_SIZE,
|
||||
};
|
||||
|
@ -15,7 +14,7 @@ use monero_wallet::{
|
|||
pub struct SignableTransactionBuilder {
|
||||
rct_type: RctType,
|
||||
outgoing_view_key: Zeroizing<[u8; 32]>,
|
||||
inputs: Vec<(WalletOutput, Decoys)>,
|
||||
inputs: Vec<OutputWithDecoys>,
|
||||
payments: Vec<(MoneroAddress, u64)>,
|
||||
change: Change,
|
||||
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
|
||||
}
|
||||
#[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
|
||||
}
|
||||
|
|
|
@ -198,13 +198,10 @@ macro_rules! test {
|
|||
};
|
||||
|
||||
use monero_wallet::{
|
||||
primitives::Decoys,
|
||||
ringct::RctType,
|
||||
rpc::FeePriority,
|
||||
address::Network,
|
||||
ViewPair,
|
||||
DecoySelection,
|
||||
Scanner,
|
||||
ViewPair, Scanner, OutputWithDecoys,
|
||||
send::{Change, SignableTransaction, Eventuality},
|
||||
};
|
||||
|
||||
|
@ -300,16 +297,14 @@ macro_rules! test {
|
|||
let temp = Box::new({
|
||||
let mut builder = builder.clone();
|
||||
|
||||
let decoys = Decoys::fingerprintable_canonical_select(
|
||||
let input = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
ring_len(rct_type),
|
||||
rpc.get_height().await.unwrap(),
|
||||
&[miner_tx.clone()],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
builder.add_input((miner_tx, decoys.first().unwrap().clone()));
|
||||
miner_tx,
|
||||
).await.unwrap();
|
||||
builder.add_input(input);
|
||||
|
||||
let (tx, state) = ($first_tx)(rpc.clone(), builder, next_addr).await;
|
||||
let fee_rate = tx.fee_rate().clone();
|
||||
|
|
|
@ -4,8 +4,8 @@ use rand_core::OsRng;
|
|||
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::{
|
||||
primitives::Decoys, ringct::RctType, transaction::Transaction, rpc::Rpc,
|
||||
address::SubaddressIndex, extra::Extra, WalletOutput, DecoySelection,
|
||||
ringct::RctType, transaction::Transaction, rpc::Rpc, address::SubaddressIndex, extra::Extra,
|
||||
WalletOutput, OutputWithDecoys,
|
||||
};
|
||||
|
||||
mod runner;
|
||||
|
@ -18,19 +18,19 @@ async fn add_inputs(
|
|||
outputs: Vec<WalletOutput>,
|
||||
builder: &mut SignableTransactionBuilder,
|
||||
) {
|
||||
let decoys = Decoys::fingerprintable_canonical_select(
|
||||
for output in outputs {
|
||||
builder.add_input(
|
||||
OutputWithDecoys::fingerprintable_deterministic_new(
|
||||
&mut OsRng,
|
||||
rpc,
|
||||
ring_len(rct_type),
|
||||
rpc.get_height().await.unwrap(),
|
||||
&outputs,
|
||||
output,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let inputs = outputs.into_iter().zip(decoys).collect::<Vec<_>>();
|
||||
|
||||
builder.add_inputs(&inputs);
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test!(
|
||||
|
|
|
@ -20,7 +20,7 @@ use monero_wallet::{
|
|||
block::Block,
|
||||
rpc::{FeeRate, RpcError, Rpc},
|
||||
address::{Network as MoneroNetwork, SubaddressIndex},
|
||||
ViewPair, GuaranteedViewPair, WalletOutput, GuaranteedScanner, DecoySelection, Decoys,
|
||||
ViewPair, GuaranteedViewPair, WalletOutput, OutputWithDecoys, GuaranteedScanner,
|
||||
send::{
|
||||
SendError, Change, SignableTransaction as MSignableTransaction, Eventuality, TransactionMachine,
|
||||
},
|
||||
|
@ -322,15 +322,16 @@ impl Monero {
|
|||
_ => 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 =
|
||||
RecommendedTranscript::new(b"Serai Processor Monero Transaction Transcript");
|
||||
transcript.append_message(b"plan", plan_id);
|
||||
|
||||
// All signers need to select the same decoys
|
||||
// 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());
|
||||
for input in inputs {
|
||||
inputs_actual.push(
|
||||
OutputWithDecoys::fingerprintable_deterministic_new(
|
||||
&mut ChaCha20Rng::from_seed(transcript.rng_seed(b"decoys")),
|
||||
&self.rpc,
|
||||
// TODO: Have Decoys take RctType
|
||||
|
@ -340,12 +341,12 @@ impl Monero {
|
|||
_ => panic!("selecting decoys for an unsupported RctType"),
|
||||
},
|
||||
block_number + 1,
|
||||
&spendable_outputs,
|
||||
input.0.clone(),
|
||||
)
|
||||
.await
|
||||
.map_err(map_rpc_err)?;
|
||||
|
||||
let inputs = spendable_outputs.into_iter().zip(decoys).collect::<Vec<_>>();
|
||||
.map_err(map_rpc_err)?,
|
||||
);
|
||||
}
|
||||
|
||||
// Monero requires at least two outputs
|
||||
// If we only have one output planned, add a dummy payment
|
||||
|
@ -375,7 +376,7 @@ impl Monero {
|
|||
rct_type,
|
||||
// Use the plan ID as the outgoing view key
|
||||
Zeroizing::new(*plan_id),
|
||||
inputs.clone(),
|
||||
inputs_actual,
|
||||
payments,
|
||||
Change::fingerprintable(change.as_ref().map(|change| change.clone().into())),
|
||||
vec![],
|
||||
|
@ -400,7 +401,7 @@ impl Monero {
|
|||
SendError::TooMuchArbitraryData |
|
||||
SendError::TooLargeTransaction |
|
||||
SendError::WrongPrivateKey => {
|
||||
panic!("created an Monero invalid transaction: {e}");
|
||||
panic!("created an invalid Monero transaction: {e}");
|
||||
}
|
||||
SendError::MultiplePaymentIds => {
|
||||
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 outputs =
|
||||
let mut outputs =
|
||||
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
|
||||
let fee = Monero::DUST;
|
||||
|
||||
|
@ -749,7 +751,7 @@ impl Network for Monero {
|
|||
_ => 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,
|
||||
&self.rpc,
|
||||
match rct_type {
|
||||
|
@ -758,19 +760,17 @@ impl Network for Monero {
|
|||
_ => panic!("selecting decoys for an unsupported RctType"),
|
||||
},
|
||||
self.rpc.get_height().await.unwrap(),
|
||||
&outputs,
|
||||
output,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let inputs = outputs.into_iter().zip(decoys).collect::<Vec<_>>();
|
||||
|
||||
let mut outgoing_view_key = Zeroizing::new([0; 32]);
|
||||
OsRng.fill_bytes(outgoing_view_key.as_mut());
|
||||
let tx = MSignableTransaction::new(
|
||||
rct_type,
|
||||
outgoing_view_key,
|
||||
inputs,
|
||||
vec![output],
|
||||
vec![(address.into(), amount - fee)],
|
||||
Change::fingerprintable(Some(Self::test_address().into())),
|
||||
vec![],
|
||||
|
|
|
@ -348,7 +348,7 @@ async fn mint_and_burn_test() {
|
|||
ringct::RctType,
|
||||
rpc::{FeePriority, Rpc},
|
||||
address::{Network, AddressType, MoneroAddress},
|
||||
ViewPair, Scanner, DecoySelection, Decoys,
|
||||
ViewPair, Scanner, OutputWithDecoys,
|
||||
send::{Change, SignableTransaction},
|
||||
};
|
||||
|
||||
|
@ -363,23 +363,22 @@ async fn mint_and_burn_test() {
|
|||
.additional_timelock_satisfied_by(rpc.get_height().await.unwrap(), 0)
|
||||
.swap_remove(0);
|
||||
|
||||
let decoys = Decoys::fingerprintable_canonical_select(
|
||||
let input = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
16,
|
||||
rpc.get_height().await.unwrap(),
|
||||
&[output.clone()],
|
||||
output.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.swap_remove(0);
|
||||
.unwrap();
|
||||
|
||||
let mut outgoing_view_key = Zeroizing::new([0; 32]);
|
||||
OsRng.fill_bytes(outgoing_view_key.as_mut());
|
||||
let tx = SignableTransaction::new(
|
||||
RctType::ClsagBulletproofPlus,
|
||||
outgoing_view_key,
|
||||
vec![(output, decoys)],
|
||||
vec![input],
|
||||
vec![(
|
||||
MoneroAddress::new(
|
||||
Network::Mainnet,
|
||||
|
|
|
@ -412,7 +412,7 @@ impl Wallet {
|
|||
ringct::RctType,
|
||||
rpc::{FeePriority, Rpc},
|
||||
address::{Network, AddressType, Address},
|
||||
Scanner, DecoySelection, Decoys,
|
||||
Scanner, OutputWithDecoys,
|
||||
send::{Change, SignableTransaction},
|
||||
};
|
||||
use processor::{additional_key, networks::Monero};
|
||||
|
@ -422,30 +422,35 @@ impl Wallet {
|
|||
|
||||
// Prepare inputs
|
||||
let current_height = rpc.get_height().await.unwrap();
|
||||
let mut inputs = vec![];
|
||||
let mut outputs = 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())
|
||||
outputs = Scanner::new(view_pair.clone())
|
||||
.scan(&rpc, &block)
|
||||
.await
|
||||
.unwrap()
|
||||
.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());
|
||||
for output in outputs {
|
||||
inputs.push(
|
||||
OutputWithDecoys::fingerprintable_deterministic_new(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
16,
|
||||
rpc.get_height().await.unwrap(),
|
||||
&inputs,
|
||||
output,
|
||||
)
|
||||
.await
|
||||
.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);
|
||||
|
@ -467,7 +472,7 @@ impl Wallet {
|
|||
let tx = SignableTransaction::new(
|
||||
RctType::ClsagBulletproofPlus,
|
||||
outgoing_view_key,
|
||||
inputs.drain(..).zip(decoys.drain(..)).collect(),
|
||||
inputs,
|
||||
vec![(to_addr, AMOUNT)],
|
||||
Change::new(view_pair),
|
||||
data,
|
||||
|
|
Loading…
Reference in a new issue