wsts 14.0.2

Weighted Schnorr Threshold Signatures, based on FROST
Documentation
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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
use core::{
    fmt::{Debug, Display, Formatter, Result as FmtResult},
    ops::{Add, Mul},
};
use hashbrown::HashMap;
use num_traits::{One, Zero};
use rand_core::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

use crate::{
    compute::challenge,
    curve::{
        point::{Point, G},
        scalar::Scalar,
        traits::MultiMult,
    },
    schnorr::ID,
    util::hash_to_scalar,
};

/// A merkle root is a 256 bit hash
pub type MerkleRoot = [u8; 32];

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
/// A commitment to a polynonial, with a Schnorr proof of ownership bound to the ID
pub struct PolyCommitment {
    /// The party ID with a schnorr proof
    pub id: ID,
    /// The public polynomial which commits to the secret polynomial
    pub poly: Vec<Point>,
}

impl PolyCommitment {
    /// Verify the wrapped schnorr ID
    pub fn verify(&self) -> bool {
        self.id.verify(&self.poly[0])
    }
}

impl Display for PolyCommitment {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        write!(f, "{}", self.id.id)?;
        for p in &self.poly {
            write!(f, " {}", p)?;
        }
        Ok(())
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
/// A composite private nonce used as a random commitment in the protocol
pub struct Nonce {
    /// The first committed value
    pub d: Scalar,
    /// The second committed value
    pub e: Scalar,
}

impl Nonce {
    /// Construct a random nonce
    pub fn random<RNG: RngCore + CryptoRng>(rng: &mut RNG) -> Self {
        Self {
            d: Self::gen(rng),
            e: Self::gen(rng),
        }
    }

    /// Use the IETF nonce generation function from section 4.1 of
    ///   https://datatracker.ietf.org/doc/rfc9591
    fn gen<RNG: RngCore + CryptoRng>(rng: &mut RNG) -> Scalar {
        let mut bytes: [u8; 32] = [0; 32];
        rng.fill_bytes(&mut bytes);

        let s = Scalar::random(rng);

        let mut hasher = Sha256::new();

        hasher.update(bytes);
        hasher.update(s.to_bytes());

        hash_to_scalar(&mut hasher)
    }

    /// Check that the nonces are not zero since that can lead to attacks
    pub fn is_valid(&self) -> bool {
        !self.is_zero() && !self.is_one()
    }
}

impl Zero for Nonce {
    fn zero() -> Self {
        Self {
            d: Scalar::zero(),
            e: Scalar::zero(),
        }
    }

    fn is_zero(&self) -> bool {
        self.d == Scalar::zero() && self.e == Scalar::zero()
    }
}

impl One for Nonce {
    fn one() -> Self {
        Self {
            d: Scalar::one(),
            e: Scalar::one(),
        }
    }

    fn set_one(&mut self) {
        self.d = Scalar::one();
        self.e = Scalar::one();
    }

    fn is_one(&self) -> bool {
        self.d == Scalar::one() && self.e == Scalar::one()
    }
}

impl Add for Nonce {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            d: self.d + other.d,
            e: self.e + other.e,
        }
    }
}

impl Mul for Nonce {
    type Output = Self;

    fn mul(self, other: Self) -> Self {
        Self {
            d: self.d * other.d,
            e: self.e * other.e,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[allow(non_snake_case)]
/// A commitment to the private nonce
pub struct PublicNonce {
    /// A commitment to the private nonce's first value
    pub D: Point,
    /// A commitment to the private nonce's second value
    pub E: Point,
}

impl PublicNonce {
    /// Check that the nonces are not zero since that can lead to attacks
    pub fn is_valid(&self) -> bool {
        self.D != Point::identity() && self.E != Point::identity() && self.D != G && self.E != G
    }
}

impl Display for PublicNonce {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        write!(f, "{} {}", &self.D, &self.E)
    }
}

impl From<Nonce> for PublicNonce {
    fn from(nonce: Nonce) -> Self {
        Self {
            D: nonce.d * G,
            E: nonce.e * G,
        }
    }
}

impl From<&Nonce> for PublicNonce {
    fn from(nonce: &Nonce) -> Self {
        Self {
            D: nonce.d * G,
            E: nonce.e * G,
        }
    }
}

impl Add for PublicNonce {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            D: self.D + other.E,
            E: self.E + other.E,
        }
    }
}

impl Zero for PublicNonce {
    fn zero() -> Self {
        Self::from(Nonce::zero())
    }

    fn is_zero(&self) -> bool {
        self.D == Point::identity() && self.E == Point::identity()
    }
}

#[derive(Clone, Deserialize, Serialize, PartialEq)]
/// A share of the party signature with related values
pub struct SignatureShare {
    /// The ID of the party
    pub id: u32,
    /// The party signature
    pub z_i: Scalar,
    /// The key IDs of the party
    pub key_ids: Vec<u32>,
}

impl Debug for SignatureShare {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.debug_struct("SignatureShare")
            .field("id", &self.id)
            .field("z_i", &self.z_i.to_string())
            .field("key_ids", &self.key_ids)
            .finish()
    }
}

#[allow(non_snake_case)]
/// An aggregated group signature
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Signature {
    /// The sum of the public nonces with commitments to the signed message
    pub R: Point,
    /// The sum of the party signatures
    pub z: Scalar,
}

impl Signature {
    #[allow(non_snake_case)]
    /// Verify the aggregated group signature
    pub fn verify(&self, public_key: &Point, msg: &[u8]) -> bool {
        let c = challenge(public_key, &self.R, msg);
        let R = &self.z * G + (-c) * public_key;

        R == self.R
    }
}

#[allow(non_snake_case)]
/// A Chaum-Pedersen proof that (G, A=a*G, B=b*G, K=(a*b)*G) is a DH tuple
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct TupleProof {
    /// R = r*G for a random scalar r
    pub R: Point,
    /// rB = r*B
    pub rB: Point,
    /// z = r + a*s where s = H(G,A,B,K,R) as per Fiat-Shamir
    pub z: Scalar,
}

impl TupleProof {
    #[allow(non_snake_case)]
    /// Construct a Chaum-Pedersen proof that (G, A, B, K) is a DH tuple
    pub fn new<RNG: RngCore + CryptoRng>(
        a: &Scalar,
        A: &Point,
        B: &Point,
        K: &Point,
        rng: &mut RNG,
    ) -> Self {
        let r = Scalar::random(rng);
        let R = r * G;
        let s = Self::challenge(A, B, K, &R);

        Self {
            R,
            rB: r * B,
            z: r + a * s,
        }
    }

    #[allow(non_snake_case)]
    /// Verify the proof using the transcript and public parameters
    pub fn verify(&self, A: &Point, B: &Point, K: &Point) -> bool {
        let s = Self::challenge(A, B, K, &self.R);

        (self.z * G == self.R + s * A) && (self.z * B == self.rB + s * K)
    }

    #[allow(non_snake_case)]
    fn challenge(A: &Point, B: &Point, K: &Point, R: &Point) -> Scalar {
        let mut hasher = Sha256::new();

        hasher.update("TUPLE_PROOF/".as_bytes());
        hasher.update(A.compress().as_bytes());
        hasher.update(B.compress().as_bytes());
        hasher.update(K.compress().as_bytes());
        hasher.update(R.compress().as_bytes());

        hash_to_scalar(&mut hasher)
    }
}

/// Check that the passed `signer_id` is valid
pub fn validate_signer_id(signer_id: u32, num_signers: u32) -> bool {
    signer_id < num_signers
}

/// Check that the passed `key_id` is valid
pub fn validate_key_id(key_id: u32, num_keys: u32) -> bool {
    key_id > 0 && key_id <= num_keys
}

/// Check that the PolyCommitment is properly signed and has the correct degree polynomial
pub fn check_public_shares(poly_comm: &PolyCommitment, threshold: usize) -> bool {
    poly_comm.verify() && poly_comm.poly.len() == threshold
}

/// An implementation of p256k1's MultiMult trait that allows fast checking of DKG private shares
/// We convert a set of checked polynomial evaluations into a single giant multimult
/// These evaluations take the form of s * G == \Sum{k=0}{T+1}(a_k * x^k) where the a vals are the coeffs of the polys
/// There is 1 share per poly, N polys, and each poly is degree T-1 (so T coeffs)
/// First we evaluate each poly, then we subtract each s * G
pub struct CheckPrivateShares {
    /// number of keys
    n: u32,
    /// threshold, where the degree of each poly is (t-1)
    t: u32,
    /// Powers of x, where x is the receiving key ID
    powers: Vec<Scalar>,
    /// Negated DKG private shares for the receiving key ID, indexed by sending key ID
    pub neg_shares: HashMap<u32, Scalar>,
    /// Polynomial commitments for each key ID
    polys: HashMap<u32, PolyCommitment>,
}

impl CheckPrivateShares {
    /// Construct a new CheckPrivateShares object
    pub fn new(
        id: Scalar,
        shares: &HashMap<u32, Scalar>,
        polys: HashMap<u32, PolyCommitment>,
    ) -> Self {
        let mut l: usize = 0;
        if let Some((_id, comm)) = (&polys).into_iter().next() {
            l = comm.poly.len();
        }
        let n: u32 = shares.len().try_into().unwrap();
        let t: u32 = l.try_into().unwrap();
        let x = id;
        let mut powers = Vec::with_capacity(l);
        let mut pow = Scalar::one();

        for _ in 0..t {
            powers.push(pow);
            pow *= &x;
        }

        let mut neg_shares = HashMap::with_capacity(polys.len());
        for (i, s) in shares.iter() {
            neg_shares.insert(*i, -s);
        }

        Self {
            n,
            t,
            powers,
            neg_shares,
            polys,
        }
    }
}

impl MultiMult for CheckPrivateShares {
    /// The first n*t scalars will be powers, the last n will be the negation of shares
    fn get_scalar(&self, i: usize) -> &Scalar {
        let h: u32 = i.try_into().unwrap();
        let u: usize = self.t.try_into().unwrap();
        if h < self.n * self.t {
            &self.powers[i % u]
        } else {
            &self.neg_shares[&(h - (self.t * self.n) + 1)]
        }
    }

    /// The first n*t points will be poly coeffs, the last n will be G
    fn get_point(&self, i: usize) -> &Point {
        let h: u32 = i.try_into().unwrap();
        let u: usize = self.t.try_into().unwrap();
        if h < self.n * self.t {
            let j = i / u;
            let k = i % u;

            &self.polys[&((j + 1) as u32)].poly[k]
        } else {
            &G
        }
    }

    fn get_size(&self) -> usize {
        ((self.t + 1) * self.n).try_into().unwrap()
    }
}

/// Helper functions for tests
pub mod test_helpers {
    /// Generate a set of `k` vectors which divide `n` IDs evenly
    pub fn gen_signer_ids(n: u32, k: u32) -> Vec<Vec<u32>> {
        let mut ids = Vec::new();
        let m = n / k;

        for i in 0..k {
            let mut pids = Vec::new();
            for j in 1..m + 1 {
                pids.push(i * m + j);
            }
            ids.push(pids);
        }

        ids
    }
}

#[cfg(test)]
/// Test module for common functionality
pub mod test {
    use super::*;
    use crate::util::create_rng;

    use rand::prelude::*;
    use rand_chacha::ChaCha8Rng;

    #[test]
    #[allow(non_snake_case)]
    fn tuple_proof() {
        let mut rng = create_rng();
        let a = Scalar::random(&mut rng);
        let b = Scalar::random(&mut rng);
        let c = Scalar::random(&mut rng);

        let A = Point::from(a);
        let B = Point::from(b);

        let K = a * B;
        let tuple_proof = TupleProof::new(&a, &A, &B, &K, &mut rng);
        assert!(tuple_proof.verify(&A, &B, &K));
        assert!(!tuple_proof.verify(&B, &A, &K));

        let tuple_proof = TupleProof::new(&b, &A, &B, &K, &mut rng);
        assert!(!tuple_proof.verify(&A, &B, &K));
        assert!(!tuple_proof.verify(&B, &A, &K));

        let K = b * A;
        let tuple_proof = TupleProof::new(&b, &B, &A, &K, &mut rng);
        assert!(tuple_proof.verify(&B, &A, &K));
        assert!(!tuple_proof.verify(&A, &B, &K));

        let tuple_proof = TupleProof::new(&a, &B, &A, &K, &mut rng);
        assert!(!tuple_proof.verify(&B, &A, &K));
        assert!(!tuple_proof.verify(&A, &B, &K));

        let K = c * A;
        let tuple_proof = TupleProof::new(&a, &A, &B, &K, &mut rng);
        assert!(!tuple_proof.verify(&A, &B, &K));
        assert!(!tuple_proof.verify(&B, &A, &K));
    }

    #[test]
    /// Generating a nonce prior to IETF standard required two 32 byte random fills,
    /// each of which is directly used as the data buffer for a Scalar (then reduxced).
    /// Now it requires four fills, two of which are likewise used for Scalar data.
    ///
    /// So a reasonable test would be to call the seeded RNG four times to construct
    /// Scalars, reseed, then compare the output of Nonce::generation.  If none of those
    /// scalars match the nonce (d,e) values then we have succeeded in scrambling more.
    fn nonce_generation() {
        let mut rng = ChaCha8Rng::seed_from_u64(2);
        let test_scalars = (0..4)
            .map(|_| Scalar::random(&mut rng))
            .collect::<Vec<Scalar>>();

        let mut rng = ChaCha8Rng::seed_from_u64(2);
        let nonce = Nonce::random(&mut rng);

        for scalar in test_scalars {
            assert_ne!(scalar, nonce.d);
            assert_ne!(scalar, nonce.e);
        }
    }
}