Remove Clone from ClsagMultisigMask{Sender, Receiver}

This had ill-defined properties on Clone, as a mask could be sent multiple times
(unintended) and multiple algorithms may receive the same mask from a singular
sender.

Requires removing the Clone bound within modular-frost and expanding the test
helpers accordingly.

This was not raised in the audit yet upon independent review.
This commit is contained in:
Luke Parker
2025-07-23 15:13:27 -04:00
parent feb18d64a7
commit 4f65a0b147
5 changed files with 75 additions and 34 deletions

View File

@@ -57,11 +57,11 @@ impl ClsagContext {
/// A channel to send the mask to use for the pseudo-out (rerandomized commitment) with.
///
/// A mask must be sent along this channel before any preprocess addendums are handled.
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct ClsagMultisigMaskSender {
buf: Arc<Mutex<Option<Scalar>>>,
}
#[derive(Clone, Debug)]
#[derive(Debug)]
struct ClsagMultisigMaskReceiver {
buf: Arc<Mutex<Option<Scalar>>>,
}
@@ -73,6 +73,8 @@ impl ClsagMultisigMaskSender {
/// Send a mask to a CLSAG multisig instance.
pub fn send(self, mask: Scalar) {
// There is no risk this was prior set as this consumes `self`, which does not implement
// `Clone`
*self.buf.lock() = Some(mask);
}
}
@@ -118,7 +120,7 @@ struct Interim {
/// The message signed is expected to be a 32-byte value. Per Monero, it's the keccak256 hash of
/// the transaction data which is signed. This will panic if the message is not a 32-byte value.
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct ClsagMultisig {
transcript: RecommendedTranscript,

View File

@@ -19,7 +19,8 @@ use crate::ClsagMultisig;
#[cfg(feature = "multisig")]
use frost::{
Participant,
tests::{key_gen, algorithm_machines, sign},
sign::AlgorithmMachine,
tests::{key_gen, algorithm_machines_without_clone, sign_without_clone},
};
const RING_LEN: u64 = 11;
@@ -99,21 +100,32 @@ fn clsag_multisig() {
ring.push([dest, Commitment::new(mask, amount).calculate()]);
}
let (algorithm, mask_send) = ClsagMultisig::new(
RecommendedTranscript::new(b"Monero Serai CLSAG Test"),
ClsagContext::new(
Decoys::new((1 ..= RING_LEN).collect(), RING_INDEX, ring.clone()).unwrap(),
Commitment::new(randomness, AMOUNT),
)
.unwrap(),
);
mask_send.send(Scalar::random(&mut OsRng));
let mask = Scalar::random(&mut OsRng);
let params = || {
let (algorithm, mask_send) = ClsagMultisig::new(
RecommendedTranscript::new(b"Monero Serai CLSAG Test"),
ClsagContext::new(
Decoys::new((1 ..= RING_LEN).collect(), RING_INDEX, ring.clone()).unwrap(),
Commitment::new(randomness, AMOUNT),
)
.unwrap(),
);
mask_send.send(mask);
algorithm
};
sign(
sign_without_clone(
&mut OsRng,
&algorithm,
keys.clone(),
algorithm_machines(&mut OsRng, &algorithm, &keys),
keys.values().map(|keys| (keys.params().i(), params())).collect(),
algorithm_machines_without_clone(
&mut OsRng,
&keys,
keys
.values()
.map(|keys| (keys.params().i(), AlgorithmMachine::new(params(), keys.clone())))
.collect(),
),
&[1; 32],
);
}