Document and clean clsag

This commit is contained in:
Luke Parker
2024-06-14 16:17:51 -04:00
parent 9c217913e6
commit 865dee80e5
7 changed files with 209 additions and 161 deletions

View File

@@ -1,6 +1,5 @@
use core::ops::Deref;
#[cfg(feature = "multisig")]
use std::sync::{Arc, RwLock};
use std::sync::{Arc, Mutex};
use zeroize::Zeroizing;
use rand_core::{RngCore, OsRng};
@@ -17,11 +16,11 @@ use crate::{
wallet::Decoys,
ringct::{
generate_key_image,
clsag::{ClsagInput, Clsag},
clsag::{ClsagContext, Clsag},
},
};
#[cfg(feature = "multisig")]
use crate::ringct::clsag::{ClsagDetails, ClsagMultisig};
use crate::ringct::clsag::ClsagMultisig;
#[cfg(feature = "multisig")]
use frost::{
@@ -56,24 +55,24 @@ fn clsag() {
.push([dest.deref() * ED25519_BASEPOINT_TABLE, Commitment::new(mask, amount).calculate()]);
}
let image = generate_key_image(&secrets.0);
let (mut clsag, pseudo_out) = Clsag::sign(
&mut OsRng,
vec![(
secrets.0,
image,
ClsagInput::new(
Commitment::new(secrets.1, AMOUNT),
secrets.0.clone(),
ClsagContext::new(
Decoys::new((1 ..= RING_LEN).collect(), u8::try_from(real).unwrap(), ring.clone())
.unwrap(),
Commitment::new(secrets.1, AMOUNT),
)
.unwrap(),
)],
Scalar::random(&mut OsRng),
msg,
)
.unwrap()
.swap_remove(0);
let image = generate_key_image(&secrets.0);
clsag.verify(&ring, &image, &pseudo_out, &msg).unwrap();
// make sure verification fails if we throw a random `c1` at it.
@@ -105,18 +104,14 @@ fn clsag_multisig() {
ring.push([dest, Commitment::new(mask, amount).calculate()]);
}
let mask_sum = Scalar::random(&mut OsRng);
let algorithm = ClsagMultisig::new(
RecommendedTranscript::new(b"Monero Serai CLSAG Test"),
keys[&Participant::new(1).unwrap()].group_key().0,
Arc::new(RwLock::new(Some(ClsagDetails::new(
ClsagInput::new(
Commitment::new(randomness, AMOUNT),
Decoys::new((1 ..= RING_LEN).collect(), RING_INDEX, ring.clone()).unwrap(),
)
.unwrap(),
mask_sum,
)))),
ClsagContext::new(
Decoys::new((1 ..= RING_LEN).collect(), RING_INDEX, ring.clone()).unwrap(),
Commitment::new(randomness, AMOUNT),
)
.unwrap(),
Arc::new(Mutex::new(Some(Scalar::random(&mut OsRng)))),
);
sign(

View File

@@ -32,7 +32,7 @@ use crate::{
},
ringct::{
generate_key_image,
clsag::{ClsagError, ClsagInput, Clsag},
clsag::{ClsagError, ClsagContext, Clsag},
bulletproofs::{MAX_OUTPUTS, Bulletproof},
RctBase, RctPrunable, RctSignatures,
},
@@ -168,28 +168,34 @@ fn prepare_inputs(
inputs: &[(SpendableOutput, Decoys)],
spend: &Zeroizing<Scalar>,
tx: &mut Transaction,
) -> Result<Vec<(Zeroizing<Scalar>, EdwardsPoint, ClsagInput)>, TransactionError> {
) -> Result<Vec<(Zeroizing<Scalar>, ClsagContext)>, TransactionError> {
let mut signable = Vec::with_capacity(inputs.len());
for (i, (input, decoys)) in inputs.iter().enumerate() {
for (input, decoys) in inputs {
let input_spend = Zeroizing::new(input.key_offset() + spend.deref());
let image = generate_key_image(&input_spend);
signable.push((
input_spend,
image,
ClsagInput::new(input.commitment().clone(), decoys.clone())
ClsagContext::new(decoys.clone(), input.commitment().clone())
.map_err(TransactionError::ClsagError)?,
));
tx.prefix.inputs.push(Input::ToKey {
amount: None,
key_offsets: decoys.offsets().to_vec(),
key_image: signable[i].1,
key_image: image,
});
}
signable.sort_by(|x, y| x.1.compress().to_bytes().cmp(&y.1.compress().to_bytes()).reverse());
tx.prefix.inputs.sort_by(|x, y| {
// We now need to sort the inputs by their key image
// We take the transaction's inputs, temporarily
let mut tx_inputs = Vec::with_capacity(inputs.len());
std::mem::swap(&mut tx_inputs, &mut tx.prefix.inputs);
// Then we join them with their signable contexts
let mut joint = tx_inputs.into_iter().zip(signable).collect::<Vec<_>>();
// Perform the actual sort
joint.sort_by(|(x, _), (y, _)| {
if let (Input::ToKey { key_image: x, .. }, Input::ToKey { key_image: y, .. }) = (x, y) {
x.compress().to_bytes().cmp(&y.compress().to_bytes()).reverse()
} else {
@@ -197,6 +203,14 @@ fn prepare_inputs(
}
});
// We now re-create the consumed signable (tx.prefix.inputs already having an empty vector) and
// split the joint iterator back into two Vecs
let mut signable = Vec::with_capacity(inputs.len());
for (input, signable_i) in joint {
tx.prefix.inputs.push(input);
signable.push(signable_i);
}
Ok(signable)
}
@@ -875,7 +889,8 @@ impl SignableTransaction {
let signable = prepare_inputs(&self.inputs, spend, &mut tx)?;
let clsag_pairs = Clsag::sign(rng, signable, mask_sum, tx.signature_hash());
let clsag_pairs = Clsag::sign(rng, signable, mask_sum, tx.signature_hash())
.map_err(|_| TransactionError::WrongPrivateKey)?;
match tx.rct_signatures.prunable {
RctPrunable::Null => panic!("Signing for RctPrunable::Null"),
RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. } => {

View File

@@ -3,7 +3,7 @@ use std_shims::{
io::{self, Read},
collections::HashMap,
};
use std::sync::{Arc, RwLock};
use std::sync::{Arc, Mutex};
use zeroize::Zeroizing;
@@ -27,7 +27,7 @@ use frost::{
use crate::{
ringct::{
clsag::{ClsagInput, ClsagDetails, ClsagAddendum, ClsagMultisig},
clsag::{ClsagContext, ClsagAddendum, ClsagMultisig},
RctPrunable,
},
transaction::{Input, Transaction},
@@ -43,7 +43,7 @@ pub struct TransactionMachine {
// Hashed key and scalar offset
key_images: Vec<(EdwardsPoint, Scalar)>,
inputs: Vec<Arc<RwLock<Option<ClsagDetails>>>>,
clsag_mask_mutexes: Vec<Arc<Mutex<Option<Scalar>>>>,
clsags: Vec<AlgorithmMachine<Ed25519, ClsagMultisig>>,
}
@@ -54,7 +54,7 @@ pub struct TransactionSignMachine {
transcript: RecommendedTranscript,
key_images: Vec<(EdwardsPoint, Scalar)>,
inputs: Vec<Arc<RwLock<Option<ClsagDetails>>>>,
clsag_mask_mutexes: Vec<Arc<Mutex<Option<Scalar>>>>,
clsags: Vec<AlgorithmSignMachine<Ed25519, ClsagMultisig>>,
our_preprocess: Vec<Preprocess<Ed25519, ClsagAddendum>>,
@@ -73,10 +73,10 @@ impl SignableTransaction {
keys: &ThresholdKeys<Ed25519>,
mut transcript: RecommendedTranscript,
) -> Result<TransactionMachine, TransactionError> {
let mut inputs = vec![];
let mut clsag_mask_mutexes = vec![];
for _ in 0 .. self.inputs.len() {
// Doesn't resize as that will use a single Rc for the entire Vec
inputs.push(Arc::new(RwLock::new(None)));
clsag_mask_mutexes.push(Arc::new(Mutex::new(None)));
}
let mut clsags = vec![];
@@ -139,16 +139,18 @@ impl SignableTransaction {
}
let mut key_images = vec![];
for (i, (input, _)) in self.inputs.iter().enumerate() {
for (i, (input, decoys)) in self.inputs.iter().enumerate() {
// Check this the right set of keys
let offset = keys.offset(dfg::Scalar(input.key_offset()));
if offset.group_key().0 != input.key() {
Err(TransactionError::WrongPrivateKey)?;
}
let clsag = ClsagMultisig::new(transcript.clone(), input.key(), inputs[i].clone());
let context = ClsagContext::new(decoys.clone(), input.commitment())
.map_err(TransactionError::ClsagError)?;
let clsag = ClsagMultisig::new(transcript.clone(), context, clsag_mask_mutexes[i].clone());
key_images.push((
clsag.H,
clsag.key_image_generator(),
keys.current_offset().unwrap_or(dfg::Scalar::ZERO).0 + self.inputs[i].0.key_offset(),
));
clsags.push(AlgorithmMachine::new(clsag, offset));
@@ -156,12 +158,10 @@ impl SignableTransaction {
Ok(TransactionMachine {
signable: self,
i: keys.params().i(),
transcript,
key_images,
inputs,
clsag_mask_mutexes,
clsags,
})
}
@@ -206,7 +206,7 @@ impl PreprocessMachine for TransactionMachine {
transcript: self.transcript,
key_images: self.key_images,
inputs: self.inputs,
clsag_mask_mutexes: self.clsag_mask_mutexes,
clsags,
our_preprocess,
@@ -296,7 +296,8 @@ impl SignMachine<Transaction> for TransactionSignMachine {
// provides the easiest API overall, as this is where the TX is (which needs the key
// images in its message), along with where the outputs are determined (where our
// outputs may need these in order to guarantee uniqueness)
images[c] += preprocess.addendum.key_image.0 * lagrange::<dfg::Scalar>(*l, &included).0;
images[c] +=
preprocess.addendum.key_image_share().0 * lagrange::<dfg::Scalar>(*l, &included).0;
Ok((*l, preprocess))
})
@@ -330,12 +331,10 @@ impl SignMachine<Transaction> for TransactionSignMachine {
// Sort the inputs, as expected
let mut sorted = Vec::with_capacity(self.clsags.len());
while !self.clsags.is_empty() {
let (inputs, decoys) = self.signable.inputs.swap_remove(0);
sorted.push((
images.swap_remove(0),
inputs,
decoys,
self.inputs.swap_remove(0),
self.signable.inputs.swap_remove(0).1,
self.clsag_mask_mutexes.swap_remove(0),
self.clsags.swap_remove(0),
commitments.swap_remove(0),
));
@@ -353,22 +352,16 @@ impl SignMachine<Transaction> for TransactionSignMachine {
} else {
sum_pseudo_outs += mask;
}
*value.2.lock().unwrap() = Some(mask);
tx.prefix.inputs.push(Input::ToKey {
amount: None,
key_offsets: value.2.offsets().to_vec(),
key_offsets: value.1.offsets().to_vec(),
key_image: value.0,
});
*value.3.write().unwrap() = Some(ClsagDetails::new(
ClsagInput::new(value.1.commitment().clone(), value.2).map_err(|_| {
panic!("Signing an input which isn't present in the ring we created for it")
})?,
mask,
));
self.clsags.push(value.4);
commitments.push(value.5);
self.clsags.push(value.3);
commitments.push(value.4);
}
let msg = tx.signature_hash();