mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Update CLSAG multisig to work again
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user