diff --git a/coins/monero/src/clsag/mod.rs b/coins/monero/src/clsag/mod.rs index 98b32639..0c478ad2 100644 --- a/coins/monero/src/clsag/mod.rs +++ b/coins/monero/src/clsag/mod.rs @@ -195,8 +195,8 @@ pub fn sign( // Uses Monero's C verification function to ensure compatibility with Monero pub fn verify( clsag: &Clsag, - image: EdwardsPoint, msg: &[u8; 32], + image: EdwardsPoint, ring: &[[EdwardsPoint; 2]], pseudo_out: EdwardsPoint ) -> bool { diff --git a/coins/monero/src/clsag/multisig.rs b/coins/monero/src/clsag/multisig.rs index a86c7e4a..005b483d 100644 --- a/coins/monero/src/clsag/multisig.rs +++ b/coins/monero/src/clsag/multisig.rs @@ -15,19 +15,16 @@ use frost::{Curve, FrostError, algorithm::Algorithm, sign::ParamsView}; use monero::util::ringct::{Key, Clsag}; use crate::{ - SignError, hash_to_point, - frost::{Ed25519, DLEqProof}, - clsag::{SemiSignableRing, validate_sign_args, sign_core, verify} + frost::{MultisigError, Ed25519, DLEqProof}, + clsag::{SignableInput, sign_core, verify} }; #[allow(non_snake_case)] #[derive(Clone, Debug)] struct ClsagSignInterim { c: Scalar, - mu_C: Scalar, - z: Scalar, - mu_P: Scalar, + s: Scalar, clsag: Clsag, C_out: EdwardsPoint @@ -39,31 +36,24 @@ pub struct Multisig { b: Vec, AH: dfg::EdwardsPoint, - image: EdwardsPoint, - ssr: SemiSignableRing, msg: [u8; 32], + input: SignableInput, interim: Option } impl Multisig { pub fn new( - image: EdwardsPoint, msg: [u8; 32], - ring: Vec<[EdwardsPoint; 2]>, - i: u8, - randomness: &Scalar, - amount: u64 - ) -> Result { - let ssr = validate_sign_args(ring, i, None, randomness, amount)?; + input: SignableInput + ) -> Result { Ok( Multisig { b: vec![], AH: dfg::EdwardsPoint::identity(), - image, - ssr, msg, + input, interim: None } @@ -75,13 +65,9 @@ impl Algorithm for Multisig { type Signature = (Clsag, EdwardsPoint); fn context(&self) -> Vec { - let mut context = self.image.compress().to_bytes().to_vec(); - for pair in &self.ssr.ring { - context.extend(&pair[0].compress().to_bytes()); - context.extend(&pair[1].compress().to_bytes()); - } - context.extend(&u8::try_from(self.ssr.i).unwrap().to_le_bytes()); + let mut context = vec![]; context.extend(&self.msg); + context.extend(&self.input.context()); context } @@ -122,7 +108,7 @@ impl Algorithm for Multisig { Err(FrostError::InvalidCommitmentQuantity(l, 6, serialized.len() / 32))?; } - let alt = &hash_to_point(&self.ssr.ring[self.ssr.i][0]); + let alt = &hash_to_point(&self.input.ring[self.input.i][0]); let h0 = ::G_from_slice(&serialized[0 .. 32]).map_err(|_| FrostError::InvalidCommitment(l))?; DLEqProof::deserialize(&serialized[64 .. 128]).ok_or(FrostError::InvalidCommitment(l))?.verify( @@ -154,7 +140,7 @@ impl Algorithm for Multisig { ) -> dfg::Scalar { // Use everyone's commitments to derive a random source all signers can agree upon // Cannot be manipulated to effect and all signers must, and will, know this - let rand_source = Blake2b512::new() + let mut rand_source = Blake2b512::new() .chain("clsag_randomness") .chain(&self.b) .finalize() @@ -162,19 +148,22 @@ impl Algorithm for Multisig { .try_into() .unwrap(); + let mask = Scalar::from_bytes_mod_order_wide(&rand_source); + rand_source = Blake2b512::digest(&rand_source).as_slice().try_into().unwrap(); + #[allow(non_snake_case)] let (clsag, c, mu_C, z, mu_P, C_out) = sign_core( rand_source, - self.image, - &self.ssr, &self.msg, + &self.input, + mask, nonce_sum.0, self.AH.0 ); + self.interim = Some(ClsagSignInterim { c: c * mu_P, s: c * mu_C * z, clsag, C_out }); - let share = dfg::Scalar(nonce.0 - (c * (mu_P * view.secret_share().0))); + let share = dfg::Scalar(nonce.0 - (c * mu_P * view.secret_share().0)); - self.interim = Some(ClsagSignInterim { c, mu_C, z, mu_P, clsag, C_out }); share } @@ -186,12 +175,9 @@ impl Algorithm for Multisig { ) -> Option { let interim = self.interim.as_ref().unwrap(); - // Subtract the randomness's presence, which is done once and not fractionalized among shares - let s = sum.0 - (interim.c * (interim.mu_C * interim.z)); - let mut clsag = interim.clsag.clone(); - clsag.s[self.ssr.i] = Key { key: s.to_bytes() }; - if verify(&clsag, self.image, &self.msg, &self.ssr.ring, interim.C_out).is_ok() { + clsag.s[self.input.i] = Key { key: (sum.0 - interim.s).to_bytes() }; + if verify(&clsag, &self.msg, self.input.image, &self.input.ring, interim.C_out) { return Some((clsag, interim.C_out)); } return None; @@ -205,7 +191,7 @@ impl Algorithm for Multisig { ) -> bool { let interim = self.interim.as_ref().unwrap(); return (&share.0 * &ED25519_BASEPOINT_TABLE) == ( - nonce.0 - (interim.c * (interim.mu_P * verification_share.0)) + nonce.0 - (interim.c * verification_share.0) ); } } diff --git a/coins/monero/src/transaction/mod.rs b/coins/monero/src/transaction/mod.rs index 220a8598..afa9f793 100644 --- a/coins/monero/src/transaction/mod.rs +++ b/coins/monero/src/transaction/mod.rs @@ -156,6 +156,19 @@ impl SignableInput { Ok(SignableInput { image, mixins, ring, i, commitment }) } + + #[cfg(feature = "multisig")] + pub fn context(&self) -> Vec { + let mut context = self.image.compress().to_bytes().to_vec(); + for pair in &self.ring { + // Doesn't include mixins[i] as CLSAG doesn't care and won't be affected by it + context.extend(&pair[0].compress().to_bytes()); + context.extend(&pair[1].compress().to_bytes()); + } + context.extend(&u8::try_from(self.i).unwrap().to_le_bytes()); + // Doesn't include commitment as the above ring + index includes the commitment + context + } } #[allow(non_snake_case)] diff --git a/coins/monero/tests/clsag.rs b/coins/monero/tests/clsag.rs index 478310c2..83692a4b 100644 --- a/coins/monero/tests/clsag.rs +++ b/coins/monero/tests/clsag.rs @@ -2,7 +2,7 @@ use rand::{RngCore, rngs::OsRng}; use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar}; -use monero_serai::{random_scalar, Commitment, key_image, clsag, transaction::SignableInput}; +use monero_serai::{random_scalar, Commitment, frost::MultisigError, key_image, clsag, transaction::SignableInput}; #[cfg(feature = "multisig")] use ::frost::sign; @@ -56,12 +56,12 @@ fn test_single() { )], Scalar::zero() ).unwrap().swap_remove(0); - assert!(clsag::verify(&clsag, image, &msg, &ring, pseudo_out)); + assert!(clsag::verify(&clsag, &msg, image, &ring, pseudo_out)); } #[cfg(feature = "multisig")] #[test] -fn test_multisig() -> Result<(), SignError> { +fn test_multisig() -> Result<(), MultisigError> { let (keys, group_private) = generate_keys(THRESHOLD, PARTICIPANTS); let t = keys[0].params().t(); @@ -88,19 +88,18 @@ fn test_multisig() -> Result<(), SignError> { let mut ring = vec![]; for i in 0 .. RING_LEN { let dest; - let a; + let mask; let amount; if i != u64::from(RING_INDEX) { dest = random_scalar(&mut OsRng); - a = random_scalar(&mut OsRng); + mask = random_scalar(&mut OsRng); amount = OsRng.next_u64(); } else { dest = group_private.0; - a = randomness; + mask = randomness; amount = AMOUNT; } - let mask = commitment(&a, amount); - ring.push([&dest * &ED25519_BASEPOINT_TABLE, mask]); + ring.push([&dest * &ED25519_BASEPOINT_TABLE, Commitment::new(mask, amount).calculate()]); } let mut machines = vec![]; @@ -110,7 +109,10 @@ fn test_multisig() -> Result<(), SignError> { machines.push( sign::StateMachine::new( sign::Params::new( - clsag::Multisig::new(image, msg, ring.clone(), RING_INDEX, &randomness, AMOUNT).unwrap(), + clsag::Multisig::new( + msg, + SignableInput::new(image, vec![], ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap() + ).unwrap(), keys[i - 1].clone(), &(1 ..= t).collect::>() ).unwrap() diff --git a/coins/monero/tests/key_image.rs b/coins/monero/tests/key_image.rs index 9f69d485..6a041b6c 100644 --- a/coins/monero/tests/key_image.rs +++ b/coins/monero/tests/key_image.rs @@ -2,13 +2,13 @@ use rand::rngs::OsRng; -use monero_serai::{SignError, key_image}; +use monero_serai::{frost::MultisigError, key_image}; mod frost; use crate::frost::generate_keys; #[test] -fn test() -> Result<(), SignError> { +fn test() -> Result<(), MultisigError> { let (keys, group_private) = generate_keys(3, 5); let image = key_image::generate(&group_private);