-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Monero: fix decoy selection algo and add test for latest spendable
- DSA only selected coinbase outputs and didn't match the wallet2 implementation - Added test to make sure DSA will select a decoy output from the most recent unlocked block - Made usage of "height" in DSA consistent with other usage of "height" in Monero code (height == num blocks in chain) - Rely on monerod RPC response for output's unlocked status
- Loading branch information
Showing
9 changed files
with
155 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
use monero_serai::{ | ||
transaction::Transaction, | ||
wallet::{SpendableOutput, decoys::LOCK_WINDOW}, | ||
rpc::{Rpc, OutputResponse}, | ||
Protocol, | ||
}; | ||
|
||
mod runner; | ||
|
||
test!( | ||
select_latest_output_as_decoy, | ||
( | ||
// First make an initial tx0 | ||
|_, mut builder: Builder, addr| async move { | ||
builder.add_payment(addr, 2000000000000); | ||
(builder.build().unwrap(), ()) | ||
}, | ||
|rpc: Rpc<_>, tx: Transaction, mut scanner: Scanner, _| async move { | ||
let output = scanner.scan_transaction(&tx).not_locked().swap_remove(0); | ||
assert_eq!(output.commitment().amount, 2000000000000); | ||
SpendableOutput::from(&rpc, output).await.unwrap() | ||
}, | ||
), | ||
( | ||
// Then make a second tx1 | ||
|protocol: Protocol, rpc: Rpc<_>, mut builder: Builder, addr, state: _| async move { | ||
let output_tx0: SpendableOutput = state; | ||
let decoys = Decoys::select( | ||
&mut OsRng, | ||
&rpc, | ||
protocol.ring_len(), | ||
rpc.get_height().await.unwrap(), | ||
&[output_tx0.clone()], | ||
) | ||
.await | ||
.unwrap(); | ||
|
||
let inputs = [output_tx0.clone()].into_iter().zip(decoys).collect::<Vec<_>>(); | ||
builder.add_inputs(&inputs); | ||
builder.add_payment(addr, 1000000000000); | ||
|
||
(builder.build().unwrap(), (protocol, output_tx0)) | ||
}, | ||
// Then make sure DSA selects freshly unlocked output from tx1 as a decoy | ||
|rpc: Rpc<_>, tx: Transaction, mut scanner: Scanner, state: (_, _)| async move { | ||
use rand_core::OsRng; | ||
|
||
let height = rpc.get_height().await.unwrap(); | ||
|
||
let output_tx1 = | ||
SpendableOutput::from(&rpc, scanner.scan_transaction(&tx).not_locked().swap_remove(0)) | ||
.await | ||
.unwrap(); | ||
|
||
// Make sure output from tx1 is in the block in which it unlocks | ||
let out_tx1: OutputResponse = | ||
rpc.get_outs(&[output_tx1.global_index]).await.unwrap().swap_remove(0); | ||
assert_eq!(out_tx1.height, height - LOCK_WINDOW); | ||
assert!(out_tx1.unlocked); | ||
|
||
// Select decoys using spendable output from tx0 as the real, and make sure DSA selects | ||
// the freshly unlocked output from tx1 as a decoy | ||
let (protocol, output_tx0): (Protocol, SpendableOutput) = state; | ||
let mut selected_fresh_decoy = false; | ||
let mut attempts = 1000; | ||
while !selected_fresh_decoy && attempts > 0 { | ||
let decoys = Decoys::select( | ||
&mut OsRng, // TODO: use a seeded RNG to consistently select the latest output | ||
&rpc, | ||
protocol.ring_len(), | ||
height, | ||
&[output_tx0.clone()], | ||
) | ||
.await | ||
.unwrap(); | ||
|
||
selected_fresh_decoy = decoys[0].indexes().contains(&output_tx1.global_index); | ||
attempts -= 1; | ||
} | ||
|
||
assert!(selected_fresh_decoy); | ||
assert_eq!(height, rpc.get_height().await.unwrap()); | ||
}, | ||
), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters