cait_sith/
sign.rs

1use elliptic_curve::{ops::Invert, scalar::IsHigh, Field, Group, ScalarPrimitive};
2use subtle::ConditionallySelectable;
3
4use crate::{
5    compat::{self, CSCurve},
6    participants::{ParticipantCounter, ParticipantList},
7    protocol::{
8        internal::{make_protocol, Context, SharedChannel},
9        InitializationError, Participant, Protocol, ProtocolError,
10    },
11    PresignOutput,
12};
13
14/// Represents a signature with extra information, to support different variants of ECDSA.
15///
16/// An ECDSA signature is usually two scalars. The first scalar is derived from
17/// a point on the curve, and because this process is lossy, some other variants
18/// of ECDSA also include some extra information in order to recover this point.
19///
20/// Furthermore, some signature formats may disagree on how precisely to serialize
21/// different values as bytes.
22///
23/// To support these variants, this simply gives you a normal signature, along with the entire
24/// first point.
25#[derive(Clone)]
26pub struct FullSignature<C: CSCurve> {
27    /// This is the entire first point.
28    pub big_r: C::AffinePoint,
29    /// This is the second scalar, normalized to be in the lower range.
30    pub s: C::Scalar,
31}
32
33impl<C: CSCurve> FullSignature<C> {
34    #[must_use]
35    fn verify(&self, public_key: &C::AffinePoint, msg_hash: &C::Scalar) -> bool {
36        let r: C::Scalar = compat::x_coordinate::<C>(&self.big_r);
37        if r.is_zero().into() || self.s.is_zero().into() {
38            return false;
39        }
40        let s_inv = self.s.invert_vartime().unwrap();
41        let reproduced = (C::ProjectivePoint::generator() * (*msg_hash * s_inv))
42            + (C::ProjectivePoint::from(*public_key) * (r * s_inv));
43        compat::x_coordinate::<C>(&reproduced.into()) == r
44    }
45}
46
47async fn do_sign<C: CSCurve>(
48    mut chan: SharedChannel,
49    participants: ParticipantList,
50    me: Participant,
51    public_key: C::AffinePoint,
52    presignature: PresignOutput<C>,
53    msg_hash: C::Scalar,
54) -> Result<FullSignature<C>, ProtocolError> {
55    // Spec 1.1
56    let lambda = participants.lagrange::<C>(me);
57    let k_i = lambda * presignature.k;
58
59    // Spec 1.2
60    let sigma_i = lambda * presignature.sigma;
61
62    // Spec 1.3
63    let r = compat::x_coordinate::<C>(&presignature.big_r);
64    let s_i: C::Scalar = msg_hash * k_i + r * sigma_i;
65
66    // Spec 1.4
67    let wait0 = chan.next_waitpoint();
68    {
69        let s_i: ScalarPrimitive<C> = s_i.into();
70        chan.send_many(wait0, &s_i).await;
71    }
72
73    // Spec 2.1 + 2.2
74    let mut seen = ParticipantCounter::new(&participants);
75    let mut s: C::Scalar = s_i;
76    seen.put(me);
77    while !seen.full() {
78        let (from, s_j): (_, ScalarPrimitive<C>) = chan.recv(wait0).await?;
79        if !seen.put(from) {
80            continue;
81        }
82        s += C::Scalar::from(s_j)
83    }
84
85    // Spec 2.3
86    // Optionally, normalize s
87    s.conditional_assign(&(-s), s.is_high());
88    let sig = FullSignature {
89        big_r: presignature.big_r,
90        s,
91    };
92    if !sig.verify(&public_key, &msg_hash) {
93        return Err(ProtocolError::AssertionFailed(
94            "signature failed to verify".to_string(),
95        ));
96    }
97
98    // Spec 2.4
99    Ok(sig)
100}
101
102/// The signature protocol, allowing us to use a presignature to sign a message.
103///
104/// **WARNING** You must absolutely hash an actual message before passing it to
105/// this function. Allowing the signing of arbitrary scalars *is* a security risk,
106/// and this function only tolerates this risk to allow for genericity.
107pub fn sign<C: CSCurve>(
108    participants: &[Participant],
109    me: Participant,
110    public_key: C::AffinePoint,
111    presignature: PresignOutput<C>,
112    msg_hash: C::Scalar,
113) -> Result<impl Protocol<Output = FullSignature<C>>, InitializationError> {
114    if participants.len() < 2 {
115        return Err(InitializationError::BadParameters(format!(
116            "participant count cannot be < 2, found: {}",
117            participants.len()
118        )));
119    };
120
121    let participants = ParticipantList::new(participants).ok_or_else(|| {
122        InitializationError::BadParameters("participant list cannot contain duplicates".to_string())
123    })?;
124
125    let ctx = Context::new();
126    let fut = do_sign(
127        ctx.shared_channel(),
128        participants,
129        me,
130        public_key,
131        presignature,
132        msg_hash,
133    );
134    Ok(make_protocol(ctx, fut))
135}
136
137#[cfg(test)]
138mod test {
139    use std::error::Error;
140
141    use ecdsa::Signature;
142    use k256::{
143        ecdsa::signature::Verifier, ecdsa::VerifyingKey, ProjectivePoint, PublicKey, Scalar,
144        Secp256k1,
145    };
146    use rand_core::OsRng;
147
148    use crate::{compat::scalar_hash, math::Polynomial, protocol::run_protocol};
149
150    use super::*;
151
152    #[test]
153    fn test_sign() -> Result<(), Box<dyn Error>> {
154        let threshold = 2;
155        let msg = b"hello?";
156
157        // Run 4 times for flakiness reasons
158        for _ in 0..4 {
159            let f = Polynomial::<Secp256k1>::random(&mut OsRng, threshold);
160            let x = f.evaluate_zero();
161            let public_key = (ProjectivePoint::GENERATOR * x).to_affine();
162
163            let g = Polynomial::<Secp256k1>::random(&mut OsRng, threshold);
164
165            let k: Scalar = g.evaluate_zero();
166            let big_k = (ProjectivePoint::GENERATOR * k.invert().unwrap()).to_affine();
167
168            let sigma = k * x;
169
170            let h = Polynomial::<Secp256k1>::extend_random(&mut OsRng, threshold, &sigma);
171
172            let participants = vec![Participant::from(0u32), Participant::from(1u32)];
173            #[allow(clippy::type_complexity)]
174            let mut protocols: Vec<(
175                Participant,
176                Box<dyn Protocol<Output = FullSignature<Secp256k1>>>,
177            )> = Vec::with_capacity(participants.len());
178            for p in &participants {
179                let p_scalar = p.scalar::<Secp256k1>();
180                let presignature = PresignOutput {
181                    big_r: big_k,
182                    k: g.evaluate(&p_scalar),
183                    sigma: h.evaluate(&p_scalar),
184                };
185                let protocol = sign(
186                    &participants,
187                    *p,
188                    public_key,
189                    presignature,
190                    scalar_hash(msg),
191                )?;
192                protocols.push((*p, Box::new(protocol)));
193            }
194
195            let result = run_protocol(protocols)?;
196            let sig = result[0].1.clone();
197            let sig =
198                Signature::from_scalars(compat::x_coordinate::<Secp256k1>(&sig.big_r), sig.s)?;
199            VerifyingKey::from(&PublicKey::from_affine(public_key).unwrap())
200                .verify(&msg[..], &sig)?;
201        }
202        Ok(())
203    }
204}