cait_sith/
keyshare.rs

1use elliptic_curve::{Field, Group, ScalarPrimitive};
2use magikitten::Transcript;
3use rand_core::OsRng;
4
5use crate::compat::CSCurve;
6use crate::crypto::{commit, hash, Digest};
7use crate::math::{GroupPolynomial, Polynomial};
8use crate::participants::{ParticipantCounter, ParticipantList, ParticipantMap};
9use crate::proofs::dlog;
10use crate::protocol::internal::{make_protocol, Context, SharedChannel};
11use crate::protocol::{InitializationError, Participant, Protocol, ProtocolError};
12use crate::serde::encode;
13
14const LABEL: &[u8] = b"cait-sith v0.8.0 keygen";
15
16async fn do_keyshare<C: CSCurve>(
17    mut chan: SharedChannel,
18    participants: ParticipantList,
19    me: Participant,
20    threshold: usize,
21    s_i: C::Scalar,
22    big_s: Option<C::ProjectivePoint>,
23) -> Result<(C::Scalar, C::AffinePoint), ProtocolError> {
24    let mut rng = OsRng;
25    let mut transcript = Transcript::new(LABEL);
26
27    // Spec 1.2
28    transcript.message(b"group", C::NAME);
29    transcript.message(b"participants", &encode(&participants));
30    // To allow interop between platforms where usize is different!
31    transcript.message(
32        b"threshold",
33        &u64::try_from(threshold).unwrap().to_be_bytes(),
34    );
35
36    // Spec 1.3
37    let f: Polynomial<C> = Polynomial::extend_random(&mut rng, threshold, &s_i);
38
39    // Spec 1.4
40    let mut big_f = f.commit();
41
42    // Spec 1.5
43    let (my_commitment, my_randomizer) = commit(&mut rng, &big_f);
44
45    // Spec 1.6
46    let wait0 = chan.next_waitpoint();
47    chan.send_many(wait0, &my_commitment).await;
48
49    // Spec 2.1
50    let mut all_commitments = ParticipantMap::new(&participants);
51    all_commitments.put(me, my_commitment);
52    while !all_commitments.full() {
53        let (from, commitment) = chan.recv(wait0).await?;
54        all_commitments.put(from, commitment);
55    }
56
57    // Spec 2.2
58    let my_confirmation = hash(&all_commitments);
59
60    // Spec 2.3
61    transcript.message(b"confirmation", my_confirmation.as_ref());
62
63    // Spec 2.4
64    let wait1 = chan.next_waitpoint();
65    chan.send_many(wait1, &my_confirmation).await;
66
67    // Spec 2.5
68    let statement = dlog::Statement::<C> {
69        public: &big_f.evaluate_zero(),
70    };
71    let witness = dlog::Witness::<C> {
72        x: &f.evaluate_zero(),
73    };
74    let my_phi_proof = dlog::prove(
75        &mut rng,
76        &mut transcript.forked(b"dlog0", &me.bytes()),
77        statement,
78        witness,
79    );
80
81    // Spec 2.6
82    let wait2 = chan.next_waitpoint();
83    chan.send_many(wait2, &(&big_f, &my_randomizer, my_phi_proof))
84        .await;
85
86    // Spec 2.7
87    let wait3 = chan.next_waitpoint();
88    for p in participants.others(me) {
89        let x_i_j: ScalarPrimitive<C> = f.evaluate(&p.scalar::<C>()).into();
90        chan.send_private(wait3, p, &x_i_j).await;
91    }
92    let mut x_i = f.evaluate(&me.scalar::<C>());
93
94    // Spec 3.1 + 3.2
95    let mut seen = ParticipantCounter::new(&participants);
96    seen.put(me);
97    while !seen.full() {
98        let (from, confirmation): (_, Digest) = chan.recv(wait1).await?;
99        if !seen.put(from) {
100            continue;
101        }
102        if confirmation != my_confirmation {
103            return Err(ProtocolError::AssertionFailed(format!(
104                "confirmation from {from:?} did not match expectation"
105            )));
106        }
107    }
108
109    // Spec 3.3 + 3.4, and also part of 3.6, for summing up the Fs.
110    seen.clear();
111    seen.put(me);
112    while !seen.full() {
113        let (from, (their_big_f, their_randomizer, their_phi_proof)): (
114            _,
115            (GroupPolynomial<C>, _, _),
116        ) = chan.recv(wait2).await?;
117        if !seen.put(from) {
118            continue;
119        }
120
121        if their_big_f.len() != threshold {
122            return Err(ProtocolError::AssertionFailed(format!(
123                "polynomial from {from:?} has the wrong length"
124            )));
125        }
126        if !all_commitments[from].check(&their_big_f, &their_randomizer) {
127            return Err(ProtocolError::AssertionFailed(format!(
128                "commitment from {from:?} did not match revealed F"
129            )));
130        }
131        let statement = dlog::Statement::<C> {
132            public: &their_big_f.evaluate_zero(),
133        };
134        if !dlog::verify(
135            &mut transcript.forked(b"dlog0", &from.bytes()),
136            statement,
137            &their_phi_proof,
138        ) {
139            return Err(ProtocolError::AssertionFailed(format!(
140                "dlog proof from {from:?} failed to verify"
141            )));
142        }
143        big_f += &their_big_f;
144    }
145
146    // Spec 3.5 + 3.6
147    seen.clear();
148    seen.put(me);
149    while !seen.full() {
150        let (from, x_j_i): (_, ScalarPrimitive<C>) = chan.recv(wait3).await?;
151        if !seen.put(from) {
152            continue;
153        }
154        x_i += C::Scalar::from(x_j_i);
155    }
156
157    // Spec 3.7
158    if big_f.evaluate(&me.scalar::<C>()) != C::ProjectivePoint::generator() * x_i {
159        return Err(ProtocolError::AssertionFailed(
160            "received bad private share".to_string(),
161        ));
162    }
163
164    // Spec 3.8
165    let big_x = big_f.evaluate_zero();
166    match big_s {
167        Some(big_s) if big_s != big_x => {
168            return Err(ProtocolError::AssertionFailed(
169                "new public key does not match old public key".to_string(),
170            ))
171        }
172        _ => {}
173    };
174
175    // Spec 3.9
176    Ok((x_i, big_x.into()))
177}
178
179/// Represents the output of the key generation protocol.
180///
181/// This contains our share of the private key, along with the public key.
182#[derive(Debug, Clone)]
183pub struct KeygenOutput<C: CSCurve> {
184    pub private_share: C::Scalar,
185    pub public_key: C::AffinePoint,
186}
187
188async fn do_keygen<C: CSCurve>(
189    chan: SharedChannel,
190    participants: ParticipantList,
191    me: Participant,
192    threshold: usize,
193) -> Result<KeygenOutput<C>, ProtocolError> {
194    let s_i = C::Scalar::random(&mut OsRng);
195    let (private_share, public_key) =
196        do_keyshare::<C>(chan, participants, me, threshold, s_i, None).await?;
197    Ok(KeygenOutput {
198        private_share,
199        public_key,
200    })
201}
202
203/// The key generation protocol, with a given threshold.
204///
205/// This produces a new key pair, such that any set of participants
206/// of size `>= threshold` can reconstruct the private key,
207/// but no smaller set can do the same.
208///
209/// This needs to be run once, before then being able to perform threshold
210/// signatures using the key.
211pub fn keygen<C: CSCurve>(
212    participants: &[Participant],
213    me: Participant,
214    threshold: usize,
215) -> Result<impl Protocol<Output = KeygenOutput<C>>, InitializationError> {
216    if participants.len() < 2 {
217        return Err(InitializationError::BadParameters(format!(
218            "participant count cannot be < 2, found: {}",
219            participants.len()
220        )));
221    };
222    // Spec 1.1
223    if threshold > participants.len() {
224        return Err(InitializationError::BadParameters(
225            "threshold must be <= participant count".to_string(),
226        ));
227    }
228
229    let participants = ParticipantList::new(participants).ok_or_else(|| {
230        InitializationError::BadParameters("participant list cannot contain duplicates".to_string())
231    })?;
232
233    if !participants.contains(me) {
234        return Err(InitializationError::BadParameters(
235            "participant list must contain this participant".to_string(),
236        ));
237    }
238
239    let ctx = Context::new();
240    let fut = do_keygen(ctx.shared_channel(), participants, me, threshold);
241    Ok(make_protocol(ctx, fut))
242}
243
244async fn do_reshare<C: CSCurve>(
245    chan: SharedChannel,
246    participants: ParticipantList,
247    old_subset: ParticipantList,
248    me: Participant,
249    threshold: usize,
250    my_share: Option<C::Scalar>,
251    public_key: C::AffinePoint,
252) -> Result<C::Scalar, ProtocolError> {
253    let s_i = my_share
254        .map(|x_i| old_subset.lagrange::<C>(me) * x_i)
255        .unwrap_or(C::Scalar::ZERO);
256    let big_s: C::ProjectivePoint = public_key.into();
257    let (private_share, _) =
258        do_keyshare::<C>(chan, participants, me, threshold, s_i, Some(big_s)).await?;
259    Ok(private_share)
260}
261
262/// The resharing protocol.
263///
264/// The purpose of this protocol is to take a key generated with one set of participants,
265/// and transfer it to another set of participants, potentially with a new threshold.
266///
267/// Not all participants must be present in the new set, but enough need to be present
268/// so that the old key can be reconstructed.
269///
270/// This protocol creates fresh shares for every party, without revealing the key,
271/// of course. The output of the protocol is the new share for this party.
272pub fn reshare<C: CSCurve>(
273    old_participants: &[Participant],
274    old_threshold: usize,
275    new_participants: &[Participant],
276    new_threshold: usize,
277    me: Participant,
278    my_share: Option<C::Scalar>,
279    public_key: C::AffinePoint,
280) -> Result<impl Protocol<Output = C::Scalar>, InitializationError> {
281    if new_participants.len() < 2 {
282        return Err(InitializationError::BadParameters(format!(
283            "participant count cannot be < 2, found: {}",
284            new_participants.len()
285        )));
286    };
287    // Spec 1.1
288    if new_threshold > new_participants.len() {
289        return Err(InitializationError::BadParameters(
290            "threshold must be <= participant count".to_string(),
291        ));
292    }
293
294    let new_participants = ParticipantList::new(new_participants).ok_or_else(|| {
295        InitializationError::BadParameters(
296            "new participant list cannot contain duplicates".to_string(),
297        )
298    })?;
299
300    if !new_participants.contains(me) {
301        return Err(InitializationError::BadParameters(
302            "new participant list must contain this participant".to_string(),
303        ));
304    }
305
306    let old_participants = ParticipantList::new(old_participants).ok_or_else(|| {
307        InitializationError::BadParameters(
308            "old participant list cannot contain duplicates".to_string(),
309        )
310    })?;
311
312    let old_subset = old_participants.intersection(&new_participants);
313    if old_subset.len() < old_threshold {
314        return Err(InitializationError::BadParameters(
315            "not enough old participants to reconstruct private key for resharing".to_string(),
316        ));
317    }
318
319    if old_subset.contains(me) && my_share.is_none() {
320        return Err(InitializationError::BadParameters(
321            "this party is present in the old participant list but provided no share".to_string(),
322        ));
323    }
324
325    let ctx = Context::new();
326    let fut = do_reshare::<C>(
327        ctx.shared_channel(),
328        new_participants,
329        old_subset,
330        me,
331        new_threshold,
332        my_share,
333        public_key,
334    );
335    Ok(make_protocol(ctx, fut))
336}
337
338/// The refresh protocol.
339///
340/// This is like resharing, but with extra constraints to ensure that the set
341/// of participants and threshold do not change.
342pub fn refresh<C: CSCurve>(
343    participants: &[Participant],
344    threshold: usize,
345    me: Participant,
346    my_share: C::Scalar,
347    public_key: C::AffinePoint,
348) -> Result<impl Protocol<Output = C::Scalar>, InitializationError> {
349    reshare::<C>(
350        participants,
351        threshold,
352        participants,
353        threshold,
354        me,
355        Some(my_share),
356        public_key,
357    )
358}
359
360#[cfg(test)]
361mod test {
362    use std::error::Error;
363
364    use k256::{ProjectivePoint, Scalar, Secp256k1};
365
366    use super::*;
367    use crate::protocol::{run_protocol, Participant};
368
369    #[allow(clippy::type_complexity)]
370    fn do_keygen(
371        participants: &[Participant],
372        threshold: usize,
373    ) -> Result<Vec<(Participant, KeygenOutput<Secp256k1>)>, Box<dyn Error>> {
374        let mut protocols: Vec<(
375            Participant,
376            Box<dyn Protocol<Output = KeygenOutput<Secp256k1>>>,
377        )> = Vec::with_capacity(participants.len());
378
379        for p in participants.iter() {
380            let protocol = keygen(participants, *p, threshold)?;
381            protocols.push((*p, Box::new(protocol)));
382        }
383
384        let result = run_protocol(protocols)?;
385        Ok(result)
386    }
387
388    #[test]
389    fn test_keygen() -> Result<(), Box<dyn Error>> {
390        let participants = vec![
391            Participant::from(0u32),
392            Participant::from(1u32),
393            Participant::from(2u32),
394        ];
395        let threshold = 3;
396
397        let result = do_keygen(&participants, threshold)?;
398        assert!(result.len() == participants.len());
399        assert_eq!(result[0].1.public_key, result[1].1.public_key);
400        assert_eq!(result[1].1.public_key, result[2].1.public_key);
401
402        let pub_key = result[2].1.public_key;
403
404        let participants = vec![result[0].0, result[1].0, result[2].0];
405        let shares = vec![
406            result[0].1.private_share,
407            result[1].1.private_share,
408            result[2].1.private_share,
409        ];
410        let p_list = ParticipantList::new(&participants).unwrap();
411        let x = p_list.lagrange::<Secp256k1>(participants[0]) * shares[0]
412            + p_list.lagrange::<Secp256k1>(participants[1]) * shares[1]
413            + p_list.lagrange::<Secp256k1>(participants[2]) * shares[2];
414        assert_eq!(ProjectivePoint::GENERATOR * x, pub_key);
415
416        Ok(())
417    }
418
419    #[test]
420    fn test_refresh() -> Result<(), Box<dyn Error>> {
421        let participants = vec![
422            Participant::from(0u32),
423            Participant::from(1u32),
424            Participant::from(2u32),
425        ];
426        let threshold = 3;
427
428        let result0 = do_keygen(&participants, threshold)?;
429
430        let pub_key = result0[2].1.public_key;
431
432        // Refresh
433        let mut protocols: Vec<(Participant, Box<dyn Protocol<Output = Scalar>>)> =
434            Vec::with_capacity(participants.len());
435
436        for (p, out) in result0.iter() {
437            let protocol = refresh::<Secp256k1>(
438                &participants,
439                threshold,
440                *p,
441                out.private_share,
442                out.public_key,
443            )?;
444            protocols.push((*p, Box::new(protocol)));
445        }
446
447        let result1 = run_protocol(protocols)?;
448
449        let participants = vec![result1[0].0, result1[1].0, result1[2].0];
450        let shares = vec![result1[0].1, result1[1].1, result1[2].1];
451        let p_list = ParticipantList::new(&participants).unwrap();
452        let x = p_list.lagrange::<Secp256k1>(participants[0]) * shares[0]
453            + p_list.lagrange::<Secp256k1>(participants[1]) * shares[1]
454            + p_list.lagrange::<Secp256k1>(participants[2]) * shares[2];
455        assert_eq!(ProjectivePoint::GENERATOR * x, pub_key);
456
457        Ok(())
458    }
459
460    #[test]
461    fn test_reshare() -> Result<(), Box<dyn Error>> {
462        let participants = vec![
463            Participant::from(0u32),
464            Participant::from(1u32),
465            Participant::from(2u32),
466            Participant::from(3u32),
467        ];
468        let threshold0 = 3;
469        let threshold1 = 4;
470
471        let result0 = do_keygen(&participants[..3], threshold0)?;
472
473        let pub_key = result0[2].1.public_key;
474
475        // Reshare
476        let mut setup: Vec<_> = result0
477            .into_iter()
478            .map(|(p, out)| (p, (Some(out.private_share), out.public_key)))
479            .collect();
480        setup.push((Participant::from(3u32), (None, pub_key)));
481
482        let mut protocols: Vec<(Participant, Box<dyn Protocol<Output = Scalar>>)> =
483            Vec::with_capacity(participants.len());
484
485        for (p, out) in setup.iter() {
486            let protocol = reshare::<Secp256k1>(
487                &participants[..3],
488                threshold0,
489                &participants,
490                threshold1,
491                *p,
492                out.0,
493                out.1,
494            )?;
495            protocols.push((*p, Box::new(protocol)));
496        }
497
498        let result1 = run_protocol(protocols)?;
499
500        let participants = vec![result1[0].0, result1[1].0, result1[2].0, result1[3].0];
501        let shares = vec![result1[0].1, result1[1].1, result1[2].1, result1[3].1];
502        let p_list = ParticipantList::new(&participants).unwrap();
503        let x = p_list.lagrange::<Secp256k1>(participants[0]) * shares[0]
504            + p_list.lagrange::<Secp256k1>(participants[1]) * shares[1]
505            + p_list.lagrange::<Secp256k1>(participants[2]) * shares[2]
506            + p_list.lagrange::<Secp256k1>(participants[3]) * shares[3];
507        assert_eq!(ProjectivePoint::GENERATOR * x, pub_key);
508
509        Ok(())
510    }
511}