1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
use core::{ops::Deref, fmt::Debug};
use std_shims::{
  sync::{Arc, Mutex},
  io::{self, Read, Write},
  collections::HashMap,
};

use rand_core::{RngCore, CryptoRng, SeedableRng};
use rand_chacha::ChaCha20Rng;

use zeroize::{Zeroize, Zeroizing};

use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};

use group::{
  ff::{Field, PrimeField},
  Group, GroupEncoding,
};

use transcript::{Transcript, RecommendedTranscript};
use dalek_ff_group as dfg;
use frost::{
  dkg::lagrange,
  curve::Ed25519,
  Participant, FrostError, ThresholdKeys, ThresholdView,
  algorithm::{WriteAddendum, Algorithm},
};

use monero_generators::hash_to_point;

use crate::{ClsagContext, Clsag};

impl ClsagContext {
  fn transcript<T: Transcript>(&self, transcript: &mut T) {
    // Doesn't domain separate as this is considered part of the larger CLSAG proof

    // Ring index
    transcript.append_message(b"signer_index", [self.decoys.signer_index()]);

    // Ring
    for (i, pair) in self.decoys.ring().iter().enumerate() {
      // Doesn't include global output indexes as CLSAG doesn't care/won't be affected by it
      // They're just a unreliable reference to this data which will be included in the message
      // if somehow relevant
      transcript.append_message(b"member", [u8::try_from(i).expect("ring size exceeded 255")]);
      // This also transcripts the key image generator since it's derived from this key
      transcript.append_message(b"key", pair[0].compress().to_bytes());
      transcript.append_message(b"commitment", pair[1].compress().to_bytes())
    }

    // Doesn't include the commitment's parts as the above ring + index includes the commitment
    // The only potential malleability would be if the G/H relationship is known, breaking the
    // discrete log problem, which breaks everything already
  }
}

/// A channel to send the mask to use for the pseudo-out (rerandomized commitment) with.
///
/// A mask must be sent along this channel before any preprocess addendums are handled. Breaking
/// this rule will cause a panic.
#[derive(Clone, Debug)]
pub struct ClsagMultisigMaskSender {
  buf: Arc<Mutex<Option<Scalar>>>,
}
#[derive(Clone, Debug)]
struct ClsagMultisigMaskReceiver {
  buf: Arc<Mutex<Option<Scalar>>>,
}
impl ClsagMultisigMaskSender {
  fn new() -> (ClsagMultisigMaskSender, ClsagMultisigMaskReceiver) {
    let buf = Arc::new(Mutex::new(None));
    (ClsagMultisigMaskSender { buf: buf.clone() }, ClsagMultisigMaskReceiver { buf })
  }

  /// Send a mask to a CLSAG multisig instance.
  pub fn send(self, mask: Scalar) {
    *self.buf.lock() = Some(mask);
  }
}
impl ClsagMultisigMaskReceiver {
  fn recv(self) -> Scalar {
    self.buf.lock().unwrap()
  }
}

/// Addendum produced during the signing process.
#[derive(Clone, PartialEq, Eq, Zeroize, Debug)]
pub struct ClsagAddendum {
  key_image_share: dfg::EdwardsPoint,
}

impl ClsagAddendum {
  /// The key image share within this addendum.
  pub fn key_image_share(&self) -> dfg::EdwardsPoint {
    self.key_image_share
  }
}

impl WriteAddendum for ClsagAddendum {
  fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
    writer.write_all(self.key_image_share.compress().to_bytes().as_ref())
  }
}

#[allow(non_snake_case)]
#[derive(Clone, PartialEq, Eq, Debug)]
struct Interim {
  p: Scalar,
  c: Scalar,

  clsag: Clsag,
  pseudo_out: EdwardsPoint,
}

/// FROST-inspired algorithm for producing a CLSAG signature.
///
/// Before this has its `process_addendum` called, a mask must be set. Else this will panic.
///
/// The message signed is expected to be a 32-byte value. Per Monero, it's the keccak256 hash of
/// the transaction data which is signed. This will panic if the message is not a 32-byte value.
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
pub struct ClsagMultisig {
  transcript: RecommendedTranscript,

  key_image_generator: EdwardsPoint,
  key_image_shares: HashMap<[u8; 32], dfg::EdwardsPoint>,
  image: Option<dfg::EdwardsPoint>,

  context: ClsagContext,

  mask_recv: Option<ClsagMultisigMaskReceiver>,
  mask: Option<Scalar>,

  msg: Option<[u8; 32]>,
  interim: Option<Interim>,
}

impl ClsagMultisig {
  /// Construct a new instance of multisignature CLSAG signing.
  pub fn new(
    transcript: RecommendedTranscript,
    context: ClsagContext,
  ) -> (ClsagMultisig, ClsagMultisigMaskSender) {
    let (mask_send, mask_recv) = ClsagMultisigMaskSender::new();
    (
      ClsagMultisig {
        transcript,

        key_image_generator: hash_to_point(context.decoys.signer_ring_members()[0].compress().0),
        key_image_shares: HashMap::new(),
        image: None,

        context,

        mask_recv: Some(mask_recv),
        mask: None,

        msg: None,
        interim: None,
      },
      mask_send,
    )
  }

  /// The key image generator used by the signer.
  pub fn key_image_generator(&self) -> EdwardsPoint {
    self.key_image_generator
  }
}

impl Algorithm<Ed25519> for ClsagMultisig {
  type Transcript = RecommendedTranscript;
  type Addendum = ClsagAddendum;
  // We output the CLSAG and the key image, which requires an interactive protocol to obtain
  type Signature = (Clsag, EdwardsPoint);

  // We need the nonce represented against both G and the key image generator
  fn nonces(&self) -> Vec<Vec<dfg::EdwardsPoint>> {
    vec![vec![dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(self.key_image_generator)]]
  }

  // We also publish our share of the key image
  fn preprocess_addendum<R: RngCore + CryptoRng>(
    &mut self,
    _rng: &mut R,
    keys: &ThresholdKeys<Ed25519>,
  ) -> ClsagAddendum {
    ClsagAddendum {
      key_image_share: dfg::EdwardsPoint(self.key_image_generator) * keys.secret_share().deref(),
    }
  }

  fn read_addendum<R: Read>(&self, reader: &mut R) -> io::Result<ClsagAddendum> {
    let mut bytes = [0; 32];
    reader.read_exact(&mut bytes)?;
    // dfg ensures the point is torsion free
    let xH = Option::<dfg::EdwardsPoint>::from(dfg::EdwardsPoint::from_bytes(&bytes))
      .ok_or_else(|| io::Error::other("invalid key image"))?;
    // Ensure this is a canonical point
    if xH.to_bytes() != bytes {
      Err(io::Error::other("non-canonical key image"))?;
    }

    Ok(ClsagAddendum { key_image_share: xH })
  }

  fn process_addendum(
    &mut self,
    view: &ThresholdView<Ed25519>,
    l: Participant,
    addendum: ClsagAddendum,
  ) -> Result<(), FrostError> {
    if self.image.is_none() {
      self.transcript.domain_separate(b"CLSAG");
      // Transcript the ring
      self.context.transcript(&mut self.transcript);
      // Fetch the mask from the Mutex
      // We set it to a variable to ensure our view of it is consistent
      // It was this or a mpsc channel... std doesn't have oneshot :/
      self.mask = Some(self.mask_recv.take().unwrap().recv());
      // Transcript the mask
      self.transcript.append_message(b"mask", self.mask.expect("mask wasn't set").to_bytes());

      // Init the image to the offset
      self.image = Some(dfg::EdwardsPoint(self.key_image_generator) * view.offset());
    }

    // Transcript this participant's contribution
    self.transcript.append_message(b"participant", l.to_bytes());
    self
      .transcript
      .append_message(b"key_image_share", addendum.key_image_share.compress().to_bytes());

    // Accumulate the interpolated share
    let interpolated_key_image_share =
      addendum.key_image_share * lagrange::<dfg::Scalar>(l, view.included());
    *self.image.as_mut().unwrap() += interpolated_key_image_share;

    self
      .key_image_shares
      .insert(view.verification_share(l).to_bytes(), interpolated_key_image_share);

    Ok(())
  }

  fn transcript(&mut self) -> &mut Self::Transcript {
    &mut self.transcript
  }

  fn sign_share(
    &mut self,
    view: &ThresholdView<Ed25519>,
    nonce_sums: &[Vec<dfg::EdwardsPoint>],
    nonces: Vec<Zeroizing<dfg::Scalar>>,
    msg: &[u8],
  ) -> dfg::Scalar {
    // Use the transcript to get a seeded random number generator
    //
    // The transcript contains private data, preventing passive adversaries from recreating this
    // process even if they have access to the commitments/key image share broadcast so far
    //
    // Specifically, the transcript contains the signer's index within the ring, along with the
    // opening of the commitment being re-randomized (and what it's re-randomized to)
    let mut rng = ChaCha20Rng::from_seed(self.transcript.rng_seed(b"decoy_responses"));

    self.msg = Some(msg.try_into().expect("CLSAG message should be 32-bytes"));

    let sign_core = Clsag::sign_core(
      &mut rng,
      &self.image.expect("verifying a share despite never processing any addendums").0,
      &self.context,
      self.mask.expect("mask wasn't set"),
      self.msg.as_ref().unwrap(),
      nonce_sums[0][0].0,
      nonce_sums[0][1].0,
    );
    self.interim = Some(Interim {
      p: sign_core.key_challenge,
      c: sign_core.challenged_mask,
      clsag: sign_core.incomplete_clsag,
      pseudo_out: sign_core.pseudo_out,
    });

    // r - p x, where p is the challenge for the keys
    *nonces[0] - dfg::Scalar(sign_core.key_challenge) * view.secret_share().deref()
  }

  #[must_use]
  fn verify(
    &self,
    _: dfg::EdwardsPoint,
    _: &[Vec<dfg::EdwardsPoint>],
    sum: dfg::Scalar,
  ) -> Option<Self::Signature> {
    let interim = self.interim.as_ref().unwrap();
    let mut clsag = interim.clsag.clone();
    // We produced shares as `r - p x`, yet the signature is actually `r - p x - c x`
    // Substract `c x` (saved as `c`) now
    clsag.s[usize::from(self.context.decoys.signer_index())] = sum.0 - interim.c;
    if clsag
      .verify(
        self.context.decoys.ring(),
        &self.image.expect("verifying a signature despite never processing any addendums").0,
        &interim.pseudo_out,
        self.msg.as_ref().unwrap(),
      )
      .is_ok()
    {
      return Some((clsag, interim.pseudo_out));
    }
    None
  }

  fn verify_share(
    &self,
    verification_share: dfg::EdwardsPoint,
    nonces: &[Vec<dfg::EdwardsPoint>],
    share: dfg::Scalar,
  ) -> Result<Vec<(dfg::Scalar, dfg::EdwardsPoint)>, ()> {
    let interim = self.interim.as_ref().unwrap();

    // For a share `r - p x`, the following two equalities should hold:
    // - `(r - p x)G == R.0 - pV`, where `V = xG`
    // - `(r - p x)H == R.1 - pK`, where `K = xH` (the key image share)
    //
    // This is effectively a discrete log equality proof for:
    // V, K over G, H
    // with nonces
    // R.0, R.1
    // and solution
    // s
    //
    // Which is a batch-verifiable rewrite of the traditional CP93 proof
    // (and also writable as Generalized Schnorr Protocol)
    //
    // That means that given a proper challenge, this alone can be certainly argued to prove the
    // key image share is well-formed and the provided signature so proves for that.

    // This is a bit funky as it doesn't prove the nonces are well-formed however. They're part of
    // the prover data/transcript for a CP93/GSP proof, not part of the statement. This practically
    // is fine, for a variety of reasons (given a consistent `x`, a consistent `r` can be
    // extracted, and the nonces as used in CLSAG are also part of its prover data/transcript).

    let key_image_share = self.key_image_shares[&verification_share.to_bytes()];

    // Hash every variable relevant here, using the hash output as the random weight
    let mut weight_transcript =
      RecommendedTranscript::new(b"monero-serai v0.1 ClsagMultisig::verify_share");
    weight_transcript.append_message(b"G", dfg::EdwardsPoint::generator().to_bytes());
    weight_transcript.append_message(b"H", self.key_image_generator.to_bytes());
    weight_transcript.append_message(b"xG", verification_share.to_bytes());
    weight_transcript.append_message(b"xH", key_image_share.to_bytes());
    weight_transcript.append_message(b"rG", nonces[0][0].to_bytes());
    weight_transcript.append_message(b"rH", nonces[0][1].to_bytes());
    weight_transcript.append_message(b"c", dfg::Scalar(interim.p).to_repr());
    weight_transcript.append_message(b"s", share.to_repr());
    let weight = weight_transcript.challenge(b"weight");
    let weight = dfg::Scalar(Scalar::from_bytes_mod_order_wide(&weight.into()));

    let part_one = vec![
      (share, dfg::EdwardsPoint::generator()),
      // -(R.0 - pV) == -R.0 + pV
      (-dfg::Scalar::ONE, nonces[0][0]),
      (dfg::Scalar(interim.p), verification_share),
    ];

    let mut part_two = vec![
      (weight * share, dfg::EdwardsPoint(self.key_image_generator)),
      // -(R.1 - pK) == -R.1 + pK
      (-weight, nonces[0][1]),
      (weight * dfg::Scalar(interim.p), key_image_share),
    ];

    let mut all = part_one;
    all.append(&mut part_two);
    Ok(all)
  }
}