Update CLSAG multisig to work again

This commit is contained in:
Luke Parker
2022-04-28 12:01:20 -04:00
parent f3a5e3c27e
commit 20f214c300
5 changed files with 48 additions and 47 deletions

View File

@@ -195,8 +195,8 @@ pub fn sign<R: RngCore + CryptoRng>(
// Uses Monero's C verification function to ensure compatibility with Monero // Uses Monero's C verification function to ensure compatibility with Monero
pub fn verify( pub fn verify(
clsag: &Clsag, clsag: &Clsag,
image: EdwardsPoint,
msg: &[u8; 32], msg: &[u8; 32],
image: EdwardsPoint,
ring: &[[EdwardsPoint; 2]], ring: &[[EdwardsPoint; 2]],
pseudo_out: EdwardsPoint pseudo_out: EdwardsPoint
) -> bool { ) -> bool {

View File

@@ -15,19 +15,16 @@ use frost::{Curve, FrostError, algorithm::Algorithm, sign::ParamsView};
use monero::util::ringct::{Key, Clsag}; use monero::util::ringct::{Key, Clsag};
use crate::{ use crate::{
SignError,
hash_to_point, hash_to_point,
frost::{Ed25519, DLEqProof}, frost::{MultisigError, Ed25519, DLEqProof},
clsag::{SemiSignableRing, validate_sign_args, sign_core, verify} clsag::{SignableInput, sign_core, verify}
}; };
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct ClsagSignInterim { struct ClsagSignInterim {
c: Scalar, c: Scalar,
mu_C: Scalar, s: Scalar,
z: Scalar,
mu_P: Scalar,
clsag: Clsag, clsag: Clsag,
C_out: EdwardsPoint C_out: EdwardsPoint
@@ -39,31 +36,24 @@ pub struct Multisig {
b: Vec<u8>, b: Vec<u8>,
AH: dfg::EdwardsPoint, AH: dfg::EdwardsPoint,
image: EdwardsPoint,
ssr: SemiSignableRing,
msg: [u8; 32], msg: [u8; 32],
input: SignableInput,
interim: Option<ClsagSignInterim> interim: Option<ClsagSignInterim>
} }
impl Multisig { impl Multisig {
pub fn new( pub fn new(
image: EdwardsPoint,
msg: [u8; 32], msg: [u8; 32],
ring: Vec<[EdwardsPoint; 2]>, input: SignableInput
i: u8, ) -> Result<Multisig, MultisigError> {
randomness: &Scalar,
amount: u64
) -> Result<Multisig, SignError> {
let ssr = validate_sign_args(ring, i, None, randomness, amount)?;
Ok( Ok(
Multisig { Multisig {
b: vec![], b: vec![],
AH: dfg::EdwardsPoint::identity(), AH: dfg::EdwardsPoint::identity(),
image,
ssr,
msg, msg,
input,
interim: None interim: None
} }
@@ -75,13 +65,9 @@ impl Algorithm<Ed25519> for Multisig {
type Signature = (Clsag, EdwardsPoint); type Signature = (Clsag, EdwardsPoint);
fn context(&self) -> Vec<u8> { fn context(&self) -> Vec<u8> {
let mut context = self.image.compress().to_bytes().to_vec(); let mut context = 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());
context.extend(&self.msg); context.extend(&self.msg);
context.extend(&self.input.context());
context context
} }
@@ -122,7 +108,7 @@ impl Algorithm<Ed25519> for Multisig {
Err(FrostError::InvalidCommitmentQuantity(l, 6, serialized.len() / 32))?; 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 = <Ed25519 as Curve>::G_from_slice(&serialized[0 .. 32]).map_err(|_| FrostError::InvalidCommitment(l))?; let h0 = <Ed25519 as Curve>::G_from_slice(&serialized[0 .. 32]).map_err(|_| FrostError::InvalidCommitment(l))?;
DLEqProof::deserialize(&serialized[64 .. 128]).ok_or(FrostError::InvalidCommitment(l))?.verify( DLEqProof::deserialize(&serialized[64 .. 128]).ok_or(FrostError::InvalidCommitment(l))?.verify(
@@ -154,7 +140,7 @@ impl Algorithm<Ed25519> for Multisig {
) -> dfg::Scalar { ) -> dfg::Scalar {
// Use everyone's commitments to derive a random source all signers can agree upon // 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 // 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("clsag_randomness")
.chain(&self.b) .chain(&self.b)
.finalize() .finalize()
@@ -162,19 +148,22 @@ impl Algorithm<Ed25519> for Multisig {
.try_into() .try_into()
.unwrap(); .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)] #[allow(non_snake_case)]
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core( let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
rand_source, rand_source,
self.image,
&self.ssr,
&self.msg, &self.msg,
&self.input,
mask,
nonce_sum.0, nonce_sum.0,
self.AH.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 share
} }
@@ -186,12 +175,9 @@ impl Algorithm<Ed25519> for Multisig {
) -> Option<Self::Signature> { ) -> Option<Self::Signature> {
let interim = self.interim.as_ref().unwrap(); 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(); let mut clsag = interim.clsag.clone();
clsag.s[self.ssr.i] = Key { key: s.to_bytes() }; clsag.s[self.input.i] = Key { key: (sum.0 - interim.s).to_bytes() };
if verify(&clsag, self.image, &self.msg, &self.ssr.ring, interim.C_out).is_ok() { if verify(&clsag, &self.msg, self.input.image, &self.input.ring, interim.C_out) {
return Some((clsag, interim.C_out)); return Some((clsag, interim.C_out));
} }
return None; return None;
@@ -205,7 +191,7 @@ impl Algorithm<Ed25519> for Multisig {
) -> bool { ) -> bool {
let interim = self.interim.as_ref().unwrap(); let interim = self.interim.as_ref().unwrap();
return (&share.0 * &ED25519_BASEPOINT_TABLE) == ( return (&share.0 * &ED25519_BASEPOINT_TABLE) == (
nonce.0 - (interim.c * (interim.mu_P * verification_share.0)) nonce.0 - (interim.c * verification_share.0)
); );
} }
} }

View File

@@ -156,6 +156,19 @@ impl SignableInput {
Ok(SignableInput { image, mixins, ring, i, commitment }) Ok(SignableInput { image, mixins, ring, i, commitment })
} }
#[cfg(feature = "multisig")]
pub fn context(&self) -> Vec<u8> {
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)] #[allow(non_snake_case)]

View File

@@ -2,7 +2,7 @@ use rand::{RngCore, rngs::OsRng};
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar}; 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")] #[cfg(feature = "multisig")]
use ::frost::sign; use ::frost::sign;
@@ -56,12 +56,12 @@ fn test_single() {
)], )],
Scalar::zero() Scalar::zero()
).unwrap().swap_remove(0); ).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")] #[cfg(feature = "multisig")]
#[test] #[test]
fn test_multisig() -> Result<(), SignError> { fn test_multisig() -> Result<(), MultisigError> {
let (keys, group_private) = generate_keys(THRESHOLD, PARTICIPANTS); let (keys, group_private) = generate_keys(THRESHOLD, PARTICIPANTS);
let t = keys[0].params().t(); let t = keys[0].params().t();
@@ -88,19 +88,18 @@ fn test_multisig() -> Result<(), SignError> {
let mut ring = vec![]; let mut ring = vec![];
for i in 0 .. RING_LEN { for i in 0 .. RING_LEN {
let dest; let dest;
let a; let mask;
let amount; let amount;
if i != u64::from(RING_INDEX) { if i != u64::from(RING_INDEX) {
dest = random_scalar(&mut OsRng); dest = random_scalar(&mut OsRng);
a = random_scalar(&mut OsRng); mask = random_scalar(&mut OsRng);
amount = OsRng.next_u64(); amount = OsRng.next_u64();
} else { } else {
dest = group_private.0; dest = group_private.0;
a = randomness; mask = randomness;
amount = AMOUNT; amount = AMOUNT;
} }
let mask = commitment(&a, amount); ring.push([&dest * &ED25519_BASEPOINT_TABLE, Commitment::new(mask, amount).calculate()]);
ring.push([&dest * &ED25519_BASEPOINT_TABLE, mask]);
} }
let mut machines = vec![]; let mut machines = vec![];
@@ -110,7 +109,10 @@ fn test_multisig() -> Result<(), SignError> {
machines.push( machines.push(
sign::StateMachine::new( sign::StateMachine::new(
sign::Params::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(), keys[i - 1].clone(),
&(1 ..= t).collect::<Vec<usize>>() &(1 ..= t).collect::<Vec<usize>>()
).unwrap() ).unwrap()

View File

@@ -2,13 +2,13 @@
use rand::rngs::OsRng; use rand::rngs::OsRng;
use monero_serai::{SignError, key_image}; use monero_serai::{frost::MultisigError, key_image};
mod frost; mod frost;
use crate::frost::generate_keys; use crate::frost::generate_keys;
#[test] #[test]
fn test() -> Result<(), SignError> { fn test() -> Result<(), MultisigError> {
let (keys, group_private) = generate_keys(3, 5); let (keys, group_private) = generate_keys(3, 5);
let image = key_image::generate(&group_private); let image = key_image::generate(&group_private);