diff --git a/coins/monero/src/clsag/mod.rs b/coins/monero/src/clsag/mod.rs index 99ced700..52c4ab95 100644 --- a/coins/monero/src/clsag/mod.rs +++ b/coins/monero/src/clsag/mod.rs @@ -24,7 +24,7 @@ use crate::{ #[cfg(feature = "multisig")] mod multisig; #[cfg(feature = "multisig")] -pub use multisig::{TransactionData, Multisig, InputMultisig}; +pub use multisig::{TransactionData, Multisig}; #[derive(Error, Debug)] pub enum Error { diff --git a/coins/monero/src/clsag/multisig.rs b/coins/monero/src/clsag/multisig.rs index f1df8a51..4df3e8e1 100644 --- a/coins/monero/src/clsag/multisig.rs +++ b/coins/monero/src/clsag/multisig.rs @@ -43,12 +43,12 @@ struct ClsagSignInterim { #[allow(non_snake_case)] #[derive(Clone, Debug)] pub struct Multisig { - b: Vec, + entropy: Vec, AH: (dfg::EdwardsPoint, dfg::EdwardsPoint), input: Input, - image: Option, + image: EdwardsPoint, data: D, interim: Option @@ -61,30 +61,29 @@ impl Multisig { ) -> Result, MultisigError> { Ok( Multisig { - b: vec![], + entropy: vec![], AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()), input, - image: None, + image: EdwardsPoint::identity(), data, + interim: None } ) } - - pub fn set_image(&mut self, image: EdwardsPoint) { - self.image = Some(image); - } } impl Algorithm for Multisig { type Signature = (Clsag, EdwardsPoint); - // We arguably don't have to commit to at all thanks to xG and yG being committed to, both of - // those being proven to have the same scalar as xH and yH, yet it doesn't hurt + // We arguably don't have to commit to the nonces at all thanks to xG and yG being committed to, + // both of those being proven to have the same scalar as xH and yH, yet it doesn't hurt + // As for the image, that should be committed to by the msg from TransactionData, yet putting it + // here as well ensures the security bounds of this fn addendum_commit_len() -> usize { - 64 + 3 * 32 } fn preprocess_addendum( @@ -92,60 +91,74 @@ impl Algorithm for Multisig { view: &ParamsView, nonces: &[dfg::Scalar; 2] ) -> Vec { + let (mut serialized, proof) = key_image::generate_share(rng, view); + #[allow(non_snake_case)] let H = hash_to_point(&view.group_key().0); - let h0 = nonces[0].0 * H; - let h1 = nonces[1].0 * H; - let mut serialized = Vec::with_capacity(32 + 32 + 64 + 64); - serialized.extend(h0.compress().to_bytes()); - serialized.extend(h1.compress().to_bytes()); - serialized.extend(&DLEqProof::prove(rng, &nonces[0].0, &H, &h0).serialize()); - serialized.extend(&DLEqProof::prove(rng, &nonces[1].0, &H, &h1).serialize()); + #[allow(non_snake_case)] + let nH = (nonces[0].0 * H, nonces[1].0 * H); + + serialized.reserve_exact(3 * (32 + 64)); + serialized.extend(nH.0.compress().to_bytes()); + serialized.extend(nH.1.compress().to_bytes()); + serialized.extend(&DLEqProof::prove(rng, &nonces[0].0, &H, &nH.0).serialize()); + serialized.extend(&DLEqProof::prove(rng, &nonces[1].0, &H, &nH.1).serialize()); + serialized.extend(proof); serialized } fn process_addendum( &mut self, - _: &ParamsView, + view: &ParamsView, l: usize, commitments: &[dfg::EdwardsPoint; 2], serialized: &[u8] ) -> Result<(), FrostError> { - if serialized.len() != 192 { + if serialized.len() != (3 * (32 + 64)) { // Not an optimal error but... - Err(FrostError::InvalidCommitmentQuantity(l, 6, serialized.len() / 32))?; + Err(FrostError::InvalidCommitmentQuantity(l, 9, serialized.len() / 32))?; } + // 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 + self.entropy.extend(&l.to_le_bytes()); + self.entropy.extend(&serialized[0 .. (3 * 32)]); + + let (share, serialized) = key_image::verify_share(view, l, serialized).map_err(|_| FrostError::InvalidShare(l))?; + self.image += share; + 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))?; + #[allow(non_snake_case)] + let H = ( + ::G_from_slice(&serialized[0 .. 32]).map_err(|_| FrostError::InvalidCommitment(l))?, + ::G_from_slice(&serialized[32 .. 64]).map_err(|_| FrostError::InvalidCommitment(l))? + ); + DLEqProof::deserialize(&serialized[64 .. 128]).ok_or(FrostError::InvalidCommitment(l))?.verify( &alt, &commitments[0], - &h0 + &H.0 ).map_err(|_| FrostError::InvalidCommitment(l))?; - let h1 = ::G_from_slice(&serialized[32 .. 64]).map_err(|_| FrostError::InvalidCommitment(l))?; DLEqProof::deserialize(&serialized[128 .. 192]).ok_or(FrostError::InvalidCommitment(l))?.verify( &alt, &commitments[1], - &h1 + &H.1 ).map_err(|_| FrostError::InvalidCommitment(l))?; - self.b.extend(&l.to_le_bytes()); - self.b.extend(&serialized[0 .. 64]); - self.AH.0 += h0; - self.AH.1 += h1; + self.AH.0 += H.0; + self.AH.1 += H.1; Ok(()) } fn context(&self) -> Vec { let mut context = vec![]; - // This should be redundant as the image should be in the addendum if using InputMultisig and - // in msg if signing a Transaction, yet this ensures CLSAG takes responsibility for its own - // security boundaries - context.extend(&self.image.unwrap().compress().to_bytes()); + // This should be redundant as the image should be in the addendum if using Multisig and in msg + // if signing a Transaction, yet this ensures CLSAG takes responsibility for its own security + // boundaries + context.extend(&self.image.compress().to_bytes()); context.extend(&self.data.msg()); context.extend(&self.input.context()); context @@ -162,14 +175,12 @@ impl Algorithm for Multisig { // Apply the binding factor to the H variant of the nonce self.AH.0 += self.AH.1 * b; - // 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 - // Uses the context as well to prevent passive observers of messages from being able to break - // privacy, as the context includes the index of the output in the ring, which can only be - // known if you have the view key and know which of the wallet's TXOs is being spent + // Use the context with the entropy to prevent passive observers of messages from being able to + // break privacy, as the context includes the index of the output in the ring, which can only + // be known if you have the view key and know which of the wallet's TXOs is being spent let mut seed = b"CLSAG_randomness".to_vec(); seed.extend(&self.context()); - seed.extend(&self.b); + seed.extend(&self.entropy); let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap()); #[allow(non_snake_case)] @@ -177,7 +188,7 @@ impl Algorithm for Multisig { &mut rng, &self.data.msg(), &self.input, - &self.image.unwrap(), + &self.image, self.data.mask_sum(), nonce_sum.0, self.AH.0.0 @@ -199,7 +210,7 @@ impl Algorithm for Multisig { let mut clsag = interim.clsag.clone(); clsag.s[self.input.i] = Key { key: (sum.0 - interim.s).to_bytes() }; - if verify(&clsag, &self.data.msg(), self.image.unwrap(), &self.input.ring, interim.C_out) { + if verify(&clsag, &self.data.msg(), self.image, &self.input.ring, interim.C_out) { return Some((clsag, interim.C_out)); } return None; @@ -217,87 +228,3 @@ impl Algorithm for Multisig { ); } } - -#[allow(non_snake_case)] -#[derive(Clone, Debug)] -pub struct InputMultisig(EdwardsPoint, Multisig); - -impl InputMultisig { - pub fn new( - input: Input, - msg: D - ) -> Result, MultisigError> { - Ok(InputMultisig(EdwardsPoint::identity(), Multisig::new(input, msg)?)) - } - - pub fn image(&self) -> EdwardsPoint { - self.0 - } -} - -impl Algorithm for InputMultisig { - type Signature = (Clsag, EdwardsPoint); - - fn addendum_commit_len() -> usize { - 32 + Multisig::::addendum_commit_len() - } - - fn preprocess_addendum( - rng: &mut R, - view: &ParamsView, - nonces: &[dfg::Scalar; 2] - ) -> Vec { - let (mut serialized, end) = key_image::generate_share(rng, view); - serialized.extend(Multisig::::preprocess_addendum(rng, view, nonces)); - serialized.extend(end); - serialized - } - - fn process_addendum( - &mut self, - view: &ParamsView, - l: usize, - commitments: &[dfg::EdwardsPoint; 2], - serialized: &[u8] - ) -> Result<(), FrostError> { - let (image, serialized) = key_image::verify_share(view, l, serialized).map_err(|_| FrostError::InvalidShare(l))?; - self.0 += image; - if l == *view.included().last().unwrap() { - self.1.set_image(self.0); - } - self.1.process_addendum(view, l, commitments, &serialized) - } - - fn context(&self) -> Vec { - self.1.context() - } - - fn sign_share( - &mut self, - view: &ParamsView, - nonce_sum: dfg::EdwardsPoint, - b: dfg::Scalar, - nonce: dfg::Scalar, - msg: &[u8] - ) -> dfg::Scalar { - self.1.sign_share(view, nonce_sum, b, nonce, msg) - } - - fn verify( - &self, - group_key: dfg::EdwardsPoint, - nonce: dfg::EdwardsPoint, - sum: dfg::Scalar - ) -> Option { - self.1.verify(group_key, nonce, sum) - } - - fn verify_share( - &self, - verification_share: dfg::EdwardsPoint, - nonce: dfg::EdwardsPoint, - share: dfg::Scalar, - ) -> bool { - self.1.verify_share(verification_share, nonce, share) - } -} diff --git a/coins/monero/tests/clsag.rs b/coins/monero/tests/clsag.rs index 2d99b7e4..5f843fb4 100644 --- a/coins/monero/tests/clsag.rs +++ b/coins/monero/tests/clsag.rs @@ -92,7 +92,7 @@ fn test_multisig() -> Result<(), MultisigError> { for i in 1 ..= t { machines.push( sign::AlgorithmMachine::new( - clsag::InputMultisig::new( + clsag::Multisig::new( clsag::Input::new(ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap(), TransactionData ).unwrap(),