mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-12 14:09:25 +00:00
Reorganize CLSAG sign flow
This commit is contained in:
@@ -8,23 +8,21 @@ use curve25519_dalek::{
|
||||
edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}
|
||||
};
|
||||
|
||||
use monero::{
|
||||
consensus::Encodable,
|
||||
util::ringct::{Key, Clsag}
|
||||
};
|
||||
use monero::{consensus::Encodable, util::ringct::{Key, Clsag}};
|
||||
|
||||
use crate::{
|
||||
Commitment,
|
||||
c_verify_clsag,
|
||||
transaction::decoys::Decoys,
|
||||
random_scalar,
|
||||
hash_to_scalar,
|
||||
hash_to_point
|
||||
hash_to_point,
|
||||
c_verify_clsag
|
||||
};
|
||||
|
||||
#[cfg(feature = "multisig")]
|
||||
mod multisig;
|
||||
#[cfg(feature = "multisig")]
|
||||
pub use multisig::Multisig;
|
||||
pub use multisig::{Details, Multisig};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
@@ -36,49 +34,48 @@ pub enum Error {
|
||||
InvalidCommitment
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Input {
|
||||
// Ring, the index we're signing for, and the actual commitment behind it
|
||||
pub ring: Vec<[EdwardsPoint; 2]>,
|
||||
pub i: u8,
|
||||
pub commitment: Commitment
|
||||
// The actual commitment for the true spend
|
||||
pub commitment: Commitment,
|
||||
// True spend index, offsets, and ring
|
||||
pub decoys: Decoys
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn new(
|
||||
ring: Vec<[EdwardsPoint; 2]>,
|
||||
i: u8,
|
||||
commitment: Commitment
|
||||
commitment: Commitment,
|
||||
decoys: Decoys
|
||||
) -> Result<Input, Error> {
|
||||
let n = ring.len();
|
||||
let n = decoys.len();
|
||||
if n > u8::MAX.into() {
|
||||
Err(Error::InternalError("max ring size in this library is u8 max".to_string()))?;
|
||||
}
|
||||
if i >= (n as u8) {
|
||||
Err(Error::InvalidRingMember(i, n as u8))?;
|
||||
if decoys.i >= (n as u8) {
|
||||
Err(Error::InvalidRingMember(decoys.i, n as u8))?;
|
||||
}
|
||||
|
||||
// Validate the commitment matches
|
||||
if ring[usize::from(i)][1] != commitment.calculate() {
|
||||
if decoys.ring[usize::from(decoys.i)][1] != commitment.calculate() {
|
||||
Err(Error::InvalidCommitment)?;
|
||||
}
|
||||
|
||||
Ok(Input { ring, i, commitment })
|
||||
Ok(Input { commitment, decoys })
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn sign_core<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
msg: &[u8; 32],
|
||||
input: &Input,
|
||||
image: &EdwardsPoint,
|
||||
input: &Input,
|
||||
mask: Scalar,
|
||||
msg: &[u8; 32],
|
||||
A: EdwardsPoint,
|
||||
AH: EdwardsPoint
|
||||
) -> (Clsag, Scalar, Scalar, Scalar, Scalar, EdwardsPoint) {
|
||||
let n = input.ring.len();
|
||||
let r: usize = input.i.into();
|
||||
let n = input.decoys.len();
|
||||
let r: usize = input.decoys.i.into();
|
||||
|
||||
let C_out;
|
||||
|
||||
@@ -94,7 +91,7 @@ pub(crate) fn sign_core<R: RngCore + CryptoRng>(
|
||||
{
|
||||
C_out = Commitment::new(mask, input.commitment.amount).calculate();
|
||||
|
||||
for member in &input.ring {
|
||||
for member in &input.decoys.ring {
|
||||
P.push(member[0]);
|
||||
C_non_zero.push(member[1]);
|
||||
C.push(C_non_zero[C_non_zero.len() - 1] - C_out);
|
||||
@@ -188,9 +185,9 @@ pub(crate) fn sign_core<R: RngCore + CryptoRng>(
|
||||
#[allow(non_snake_case)]
|
||||
pub fn sign<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
msg: [u8; 32],
|
||||
inputs: &[(Scalar, Input, EdwardsPoint)],
|
||||
sum_outputs: Scalar
|
||||
inputs: &[(Scalar, EdwardsPoint, Input)],
|
||||
sum_outputs: Scalar,
|
||||
msg: [u8; 32]
|
||||
) -> Option<Vec<(Clsag, EdwardsPoint)>> {
|
||||
if inputs.len() == 0 {
|
||||
return None;
|
||||
@@ -214,13 +211,14 @@ pub fn sign<R: RngCore + CryptoRng>(
|
||||
rng.fill_bytes(&mut rand_source);
|
||||
let (mut clsag, c, mu_C, z, mu_P, C_out) = sign_core(
|
||||
rng,
|
||||
&msg,
|
||||
&inputs[i].1,
|
||||
&inputs[i].2,
|
||||
mask,
|
||||
&nonce * &ED25519_BASEPOINT_TABLE, nonce * hash_to_point(&inputs[i].1.ring[usize::from(inputs[i].1.i)][0])
|
||||
&msg,
|
||||
&nonce * &ED25519_BASEPOINT_TABLE,
|
||||
nonce * hash_to_point(&inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0])
|
||||
);
|
||||
clsag.s[inputs[i].1.i as usize] = Key {
|
||||
clsag.s[inputs[i].2.decoys.i as usize] = Key {
|
||||
key: (nonce - (c * ((mu_C * z) + (mu_P * inputs[i].0)))).to_bytes()
|
||||
};
|
||||
|
||||
@@ -233,10 +231,10 @@ pub fn sign<R: RngCore + CryptoRng>(
|
||||
// Uses Monero's C verification function to ensure compatibility with Monero
|
||||
pub fn verify(
|
||||
clsag: &Clsag,
|
||||
msg: &[u8; 32],
|
||||
image: EdwardsPoint,
|
||||
ring: &[[EdwardsPoint; 2]],
|
||||
pseudo_out: EdwardsPoint
|
||||
pseudo_out: EdwardsPoint,
|
||||
msg: &[u8; 32]
|
||||
) -> bool {
|
||||
// Workaround for the fact monero-rs doesn't include the length of clsag.s in clsag encoding
|
||||
// despite it being part of clsag encoding. Reason for the patch version pin
|
||||
@@ -256,7 +254,7 @@ pub fn verify(
|
||||
unsafe {
|
||||
c_verify_clsag(
|
||||
serialized.len(), serialized.as_ptr(), image_bytes.as_ptr(),
|
||||
ring.len() as u8, ring_bytes.as_ptr(), msg.as_ptr(), pseudo_out_bytes.as_ptr()
|
||||
ring.len() as u8, ring_bytes.as_ptr(), pseudo_out_bytes.as_ptr(), msg.as_ptr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,13 +31,14 @@ impl Input {
|
||||
// Doesn't domain separate as this is considered part of the larger CLSAG proof
|
||||
|
||||
// Ring index
|
||||
transcript.append_message(b"ring_index", &[self.i]);
|
||||
transcript.append_message(b"ring_index", &[self.decoys.i]);
|
||||
|
||||
// Ring
|
||||
let mut ring = vec![];
|
||||
for pair in &self.ring {
|
||||
for pair in &self.decoys.ring {
|
||||
// Doesn't include global output indexes as CLSAG doesn't care and won't be affected by it
|
||||
// They're just a mutable reference to this data
|
||||
// They're just a unreliable reference to this data which will be included in the message
|
||||
// if in use
|
||||
ring.extend(&pair[0].compress().to_bytes());
|
||||
ring.extend(&pair[1].compress().to_bytes());
|
||||
}
|
||||
@@ -49,9 +50,24 @@ impl Input {
|
||||
}
|
||||
}
|
||||
|
||||
// pub to enable testing
|
||||
// While we could move the CLSAG test inside this crate, that'd require duplicating the FROST test
|
||||
// helper, and isn't worth doing right now when this is harmless enough (semver? TODO)
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Details {
|
||||
input: Input,
|
||||
mask: Scalar
|
||||
}
|
||||
|
||||
impl Details {
|
||||
pub fn new(input: Input, mask: Scalar) -> Details {
|
||||
Details { input, mask }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone, Debug)]
|
||||
struct ClsagSignInterim {
|
||||
struct Interim {
|
||||
c: Scalar,
|
||||
s: Scalar,
|
||||
|
||||
@@ -63,36 +79,33 @@ struct ClsagSignInterim {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Multisig {
|
||||
transcript: Transcript,
|
||||
input: Input,
|
||||
|
||||
image: EdwardsPoint,
|
||||
commitments_H: Vec<u8>,
|
||||
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
|
||||
|
||||
msg: Rc<RefCell<[u8; 32]>>,
|
||||
mask: Rc<RefCell<Scalar>>,
|
||||
details: Rc<RefCell<Option<Details>>>,
|
||||
msg: Rc<RefCell<Option<[u8; 32]>>>,
|
||||
|
||||
interim: Option<ClsagSignInterim>
|
||||
interim: Option<Interim>
|
||||
}
|
||||
|
||||
impl Multisig {
|
||||
pub fn new(
|
||||
transcript: Transcript,
|
||||
input: Input,
|
||||
msg: Rc<RefCell<[u8; 32]>>,
|
||||
mask: Rc<RefCell<Scalar>>,
|
||||
details: Rc<RefCell<Option<Details>>>,
|
||||
msg: Rc<RefCell<Option<[u8; 32]>>>,
|
||||
) -> Result<Multisig, MultisigError> {
|
||||
Ok(
|
||||
Multisig {
|
||||
transcript,
|
||||
input,
|
||||
|
||||
image: EdwardsPoint::identity(),
|
||||
commitments_H: vec![],
|
||||
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
|
||||
|
||||
details,
|
||||
msg,
|
||||
mask,
|
||||
|
||||
interim: None
|
||||
}
|
||||
@@ -102,6 +115,18 @@ impl Multisig {
|
||||
pub fn serialized_len() -> usize {
|
||||
3 * (32 + 64)
|
||||
}
|
||||
|
||||
fn input(&self) -> Input {
|
||||
self.details.borrow().as_ref().unwrap().input.clone()
|
||||
}
|
||||
|
||||
fn mask(&self) -> Scalar {
|
||||
self.details.borrow().as_ref().unwrap().mask
|
||||
}
|
||||
|
||||
fn msg(&self) -> [u8; 32] {
|
||||
*self.msg.borrow().as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Algorithm<Ed25519> for Multisig {
|
||||
@@ -144,9 +169,9 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
|
||||
if self.commitments_H.len() == 0 {
|
||||
self.transcript.domain_separate(b"CLSAG");
|
||||
self.input.transcript(&mut self.transcript);
|
||||
self.transcript.append_message(b"message", &*self.msg.borrow());
|
||||
self.transcript.append_message(b"mask", &self.mask.borrow().to_bytes());
|
||||
self.input().transcript(&mut self.transcript);
|
||||
self.transcript.append_message(b"mask", &self.mask().to_bytes());
|
||||
self.transcript.append_message(b"message", &self.msg());
|
||||
}
|
||||
|
||||
let (share, serialized) = key_image::verify_share(view, l, serialized).map_err(|_| FrostError::InvalidShare(l))?;
|
||||
@@ -156,7 +181,7 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
self.transcript.append_message(b"image_share", &share.compress().to_bytes());
|
||||
self.image += share;
|
||||
|
||||
let alt = &hash_to_point(&self.input.ring[usize::from(self.input.i)][0]);
|
||||
let alt = &hash_to_point(&view.group_key().0);
|
||||
|
||||
// Uses the same format FROST does for the expected commitments (nonce * G where this is nonce * H)
|
||||
// Given this is guaranteed to match commitments, which FROST commits to, this also technically
|
||||
@@ -214,14 +239,14 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
#[allow(non_snake_case)]
|
||||
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
|
||||
&mut rng,
|
||||
&self.msg.borrow(),
|
||||
&self.input,
|
||||
&self.image,
|
||||
*self.mask.borrow(),
|
||||
&self.input(),
|
||||
self.mask(),
|
||||
&self.msg(),
|
||||
nonce_sum.0,
|
||||
self.AH.0.0
|
||||
);
|
||||
self.interim = Some(ClsagSignInterim { c: c * mu_P, s: c * mu_C * z, clsag, C_out });
|
||||
self.interim = Some(Interim { 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));
|
||||
|
||||
@@ -237,8 +262,8 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
let interim = self.interim.as_ref().unwrap();
|
||||
|
||||
let mut clsag = interim.clsag.clone();
|
||||
clsag.s[usize::from(self.input.i)] = Key { key: (sum.0 - interim.s).to_bytes() };
|
||||
if verify(&clsag, &self.msg.borrow(), self.image, &self.input.ring, interim.C_out) {
|
||||
clsag.s[usize::from(self.input().decoys.i)] = Key { key: (sum.0 - interim.s).to_bytes() };
|
||||
if verify(&clsag, self.image, &self.input().decoys.ring, interim.C_out, &self.msg()) {
|
||||
return Some((clsag, interim.C_out));
|
||||
}
|
||||
return None;
|
||||
|
||||
Reference in New Issue
Block a user