diff --git a/coins/monero/tests/clsag.rs b/coins/monero/tests/clsag.rs index ba1dd4f1..13ed33d7 100644 --- a/coins/monero/tests/clsag.rs +++ b/coins/monero/tests/clsag.rs @@ -5,18 +5,10 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar}; use monero_serai::{random_scalar, Commitment, frost::MultisigError, key_image, clsag}; -#[cfg(feature = "multisig")] -use ::frost::sign; - #[cfg(feature = "multisig")] mod frost; #[cfg(feature = "multisig")] -use crate::frost::generate_keys; - -#[cfg(feature = "multisig")] -const THRESHOLD: usize = 5; -#[cfg(feature = "multisig")] -const PARTICIPANTS: usize = 8; +use crate::frost::{THRESHOLD, PARTICIPANTS, generate_keys, sign}; const RING_INDEX: u8 = 3; const RING_LEN: u64 = 11; @@ -62,7 +54,7 @@ fn test_single() { #[cfg(feature = "multisig")] #[test] fn test_multisig() -> Result<(), MultisigError> { - let (keys, group_private) = generate_keys(THRESHOLD, PARTICIPANTS); + let (keys, group_private) = generate_keys(); let t = keys[0].params().t(); let mut images = vec![]; @@ -102,59 +94,26 @@ fn test_multisig() -> Result<(), MultisigError> { ring.push([&dest * &ED25519_BASEPOINT_TABLE, Commitment::new(mask, amount).calculate()]); } - let mut machines = vec![]; - let mut commitments = Vec::with_capacity(PARTICIPANTS + 1); - commitments.resize(PARTICIPANTS + 1, None); - for i in 1 ..= t { - machines.push( - sign::StateMachine::new( - sign::Params::new( - clsag::Multisig::new( - &mut ChaCha12Rng::seed_from_u64(1), - msg, - clsag::Input::new(image, ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap() - ).unwrap(), - keys[i - 1].clone(), - &(1 ..= t).collect::>() - ).unwrap() - ) - ); - commitments[i] = Some(machines[i - 1].preprocess(&mut OsRng).unwrap()); - } - - let mut shares = Vec::with_capacity(PARTICIPANTS + 1); - shares.resize(PARTICIPANTS + 1, None); - for i in 1 ..= t { - shares[i] = Some( - machines[i - 1].sign( - &commitments - .iter() - .enumerate() - .map(|(idx, value)| if idx == i { None } else { value.to_owned() }) - .collect::>>>(), - &vec![] + let mut algorithms = Vec::with_capacity(t); + for _ in 1 ..= t { + algorithms.push( + clsag::Multisig::new( + &mut ChaCha12Rng::seed_from_u64(1), + msg, + clsag::Input::new(image, ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap() ).unwrap() ); } - let mut signature = None; - for i in 1 ..= t { - // Multisig does call verify to ensure integrity upon complete, before checking individual key - // shares. For FROST Schnorr, it's cheaper. For CLSAG, it may be more expensive? Yet it ensures - // we have usable signatures, not just signatures we think are usable - let sig = machines[i - 1].complete( - &shares - .iter() - .enumerate() - .map(|(idx, value)| if idx == i { None } else { value.to_owned() }) - .collect::>>>() - ).unwrap(); - if signature.is_none() { - signature = Some(sig.clone()); - } - // Check the commitment out and the non-decoy s scalar are identical to every other signature - assert_eq!(sig.1, signature.as_ref().unwrap().1); - assert_eq!(sig.0.s[RING_INDEX as usize], signature.as_ref().unwrap().0.s[RING_INDEX as usize]); + let mut signatures = sign(algorithms, keys); + let signature = signatures.swap_remove(0); + for s in 0 .. (t - 1) { + // Verify the commitments and the non-decoy s scalar are identical to every other signature + // FROST will already have called verify on the produced signature, before checking individual + // key shares. For FROST Schnorr, it's cheaper. For CLSAG, it may be more expensive? Yet it + // ensures we have usable signatures, not just signatures we think are usable + assert_eq!(signatures[s].1, signature.1); + assert_eq!(signatures[s].0.s[RING_INDEX as usize], signature.0.s[RING_INDEX as usize]); } Ok(()) diff --git a/coins/monero/tests/frost.rs b/coins/monero/tests/frost.rs index a951b84f..b731bd13 100644 --- a/coins/monero/tests/frost.rs +++ b/coins/monero/tests/frost.rs @@ -9,19 +9,21 @@ use dalek_ff_group::{ED25519_BASEPOINT_TABLE, Scalar}; use frost::{ MultisigParams, MultisigKeys, - key_gen, - sign::lagrange + key_gen, algorithm::Algorithm, sign::{self, lagrange} }; use monero_serai::frost::Ed25519; -pub fn generate_keys(t: usize, n: usize) -> (Vec>>, Scalar) { +pub const THRESHOLD: usize = 5; +pub const PARTICIPANTS: usize = 8; + +pub fn generate_keys() -> (Vec>>, Scalar) { let mut params = vec![]; let mut machines = vec![]; let mut commitments = vec![vec![]]; - for i in 1 ..= n { + for i in 1 ..= PARTICIPANTS { params.push( - MultisigParams::new(t, n, i).unwrap() + MultisigParams::new(THRESHOLD, PARTICIPANTS, i).unwrap() ); machines.push( key_gen::StateMachine::::new( @@ -33,7 +35,7 @@ pub fn generate_keys(t: usize, n: usize) -> (Vec>>, Sca } let mut secret_shares = vec![]; - for i in 1 ..= n { + for i in 1 ..= PARTICIPANTS { secret_shares.push( machines[i - 1].generate_secret_shares( &mut OsRng, @@ -47,7 +49,7 @@ pub fn generate_keys(t: usize, n: usize) -> (Vec>>, Sca } let mut keys = vec![]; - for i in 1 ..= n { + for i in 1 ..= PARTICIPANTS { let mut our_secret_shares = vec![vec![]]; our_secret_shares.extend( secret_shares.iter().map(|shares| shares[i].clone()).collect::>>() @@ -56,10 +58,67 @@ pub fn generate_keys(t: usize, n: usize) -> (Vec>>, Sca } let mut group_private = Scalar::zero(); - for i in 0 .. t { - group_private += keys[i].secret_share() * lagrange::(i + 1, &(1 ..= t).collect::>()); + for i in 1 ..= THRESHOLD { + group_private += keys[i - 1].secret_share() * lagrange::( + i, + &(1 ..= THRESHOLD).collect::>() + ); } assert_eq!(&ED25519_BASEPOINT_TABLE * group_private, keys[0].group_key()); (keys, group_private) } + +#[allow(dead_code)] // Currently has some false positive +pub fn sign>( + algorithms: Vec, + keys: Vec>> +) -> Vec { + assert!(algorithms.len() >= THRESHOLD); + assert!(keys.len() >= algorithms.len()); + + let mut machines = vec![]; + let mut commitments = Vec::with_capacity(PARTICIPANTS + 1); + commitments.resize(PARTICIPANTS + 1, None); + for i in 1 ..= THRESHOLD { + machines.push( + sign::StateMachine::new( + sign::Params::new( + algorithms[i - 1].clone(), + keys[i - 1].clone(), + &(1 ..= THRESHOLD).collect::>() + ).unwrap() + ) + ); + commitments[i] = Some(machines[i - 1].preprocess(&mut OsRng).unwrap()); + } + + let mut shares = Vec::with_capacity(PARTICIPANTS + 1); + shares.resize(PARTICIPANTS + 1, None); + for i in 1 ..= THRESHOLD { + shares[i] = Some( + machines[i - 1].sign( + &commitments + .iter() + .enumerate() + .map(|(idx, value)| if idx == i { None } else { value.to_owned() }) + .collect::>>>(), + &vec![] + ).unwrap() + ); + } + + let mut res = Vec::with_capacity(THRESHOLD); + for i in 1 ..= THRESHOLD { + res.push( + machines[i - 1].complete( + &shares + .iter() + .enumerate() + .map(|(idx, value)| if idx == i { None } else { value.to_owned() }) + .collect::>>>() + ).unwrap() + ); + } + res +} diff --git a/coins/monero/tests/key_image.rs b/coins/monero/tests/key_image.rs index 6a041b6c..2cf90d60 100644 --- a/coins/monero/tests/key_image.rs +++ b/coins/monero/tests/key_image.rs @@ -1,20 +1,25 @@ #![cfg(feature = "multisig")] -use rand::rngs::OsRng; +use rand::{RngCore, rngs::OsRng}; use monero_serai::{frost::MultisigError, key_image}; mod frost; -use crate::frost::generate_keys; +use crate::frost::{THRESHOLD, PARTICIPANTS, generate_keys}; #[test] fn test() -> Result<(), MultisigError> { - let (keys, group_private) = generate_keys(3, 5); + let (keys, group_private) = generate_keys(); let image = key_image::generate(&group_private); + let mut included = (1 ..= PARTICIPANTS).into_iter().collect::>(); + while included.len() > THRESHOLD { + included.swap_remove((OsRng.next_u64() as usize) % included.len()); + } + included.sort(); + let mut packages = vec![]; - packages.resize(5 + 1, None); - let included = vec![1, 3, 4]; + packages.resize(PARTICIPANTS + 1, None); for i in &included { let i = *i; packages[i] = Some(