• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oasisprotocol / oasis-core / #5373

11 Oct 2024 12:48PM UTC coverage: 47.696% (+0.05%) from 47.643%
#5373

Pull #5897

peternose
secret-sharing/src/churp/switch: Verify combined bivariate polynomial

After all bivariate shares are collected and the switch either
creates a new shareholder or proactivates the share of an existing
one, the new share should be verified to ensure that the verification
matrix of the combined bivariate polynomial satisfies the non-zero
leading term requirements.
Pull Request #5897: secret-sharing/src/churp/switch: Verify combined bivariate polynomial

33 of 42 new or added lines in 5 files covered. (78.57%)

1 existing line in 1 file now uncovered.

4503 of 9441 relevant lines covered (47.7%)

1.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.5
/secret-sharing/src/churp/dealer.rs
1
//! CHURP dealer.
2

3
use anyhow::Result;
4
use group::{ff::Field, Group, GroupEncoding};
5
use rand_core::RngCore;
6

7
use crate::{poly::BivariatePolynomial, vss::VerificationMatrix};
8

9
use super::{Error, HandoffKind, SecretShare};
10

11
/// Dealer is responsible for generating a secret bivariate polynomial,
12
/// computing a verification matrix, and deriving secret shares for other
13
/// participants.
14
///
15
/// Shares must always be distributed over a secure channel and verified
16
/// against the matrix. Recovering the secret bivariate polynomial requires
17
/// obtaining more than a threshold number of shares from distinct participants.
18
pub struct Dealer<G: Group + GroupEncoding> {
19
    /// Secret bivariate polynomial.
20
    bp: BivariatePolynomial<G::Scalar>,
21

22
    /// Verification matrix.
23
    vm: VerificationMatrix<G>,
24
}
25

26
impl<G> Dealer<G>
27
where
28
    G: Group + GroupEncoding,
29
{
30
    /// Creates a new dealer of secret bivariate shares, which can be used
31
    /// to recover a randomly selected shared secret.
32
    ///
33
    /// The dealer uses a random bivariate polynomial `B(x, y)` to generate
34
    /// full and reduced bivariate shares, i.e., `B(ID, y)` and `B(x, ID)`,
35
    /// where `ID` represents the identity of a participant, respectively.
36
    ///
37
    /// To ensure that the full and reduced bivariate shares form
38
    /// a (t, n)-sharing and a (2t, n)-sharing of the secret `B(0, 0)`,
39
    /// respectively, the bivariate polynomial is selected such that
40
    /// the polynomials `B(x, y)`, `B(x, 0)`, and `B(0, y)` have non-zero
41
    /// leading terms. This ensures that more than the threshold number
42
    /// of full shares, and more than twice the threshold number of reduced
43
    /// shares, are required to reconstruct the secret.
44
    ///
45
    /// Warning: If multiple dealers are used to generate the shared secret,
46
    /// it is essential to verify that the combined bivariate polynomial
47
    /// also satisfies the aforementioned non-zero leading term requirements.
48
    ///
49
    /// This function is not constant time because it uses rejection sampling.
50
    pub fn new(threshold: u8, rng: &mut impl RngCore) -> Result<Self> {
1✔
51
        let bp = Self::generate_bivariate_polynomial(threshold, rng)?;
1✔
52
        Ok(bp.into())
2✔
53
    }
54

55
    /// Creates a new dealer of secret proactive bivariate shares, which can
56
    /// be used to randomize a shared secret.
57
    ///
58
    /// The dealer uses a random zero-hole bivariate polynomial `B(x, y)`
59
    /// to generate full and reduced proactive bivariate shares, i.e.,
60
    /// `B(ID, y)` and `B(x, ID)`, where `ID` represents the identity
61
    /// of a participant. Since `B(0, 0) = 0`, adding a proactive share
62
    /// to an existing share does not change the shared secret.
63
    ///
64
    /// Warning: If one or more proactive dealers are used to randomize
65
    /// the shared secret, it is essential to verify that the combined
66
    /// bivariate polynomial still satisfies the non-zero leading term
67
    /// requirements.
68
    ///
69
    /// This function is not constant time because it uses rejection sampling.
70
    pub fn new_proactive(threshold: u8, rng: &mut impl RngCore) -> Result<Self> {
1✔
71
        let mut bp = Self::generate_bivariate_polynomial(threshold, rng)?;
1✔
72
        bp.to_zero_hole();
1✔
73
        Ok(bp.into())
1✔
74
    }
75

76
    /// Creates a new dealer of secret bivariate shares, which can be used
77
    /// to recover a predefined shared secret.
78
    ///
79
    /// This function is not constant time because it uses rejection sampling.
80
    #[cfg(test)]
81
    pub fn new_with_secret(
2✔
82
        threshold: u8,
83
        secret: G::Scalar,
84
        rng: &mut impl RngCore,
85
    ) -> Result<Self> {
86
        let mut bp = Self::generate_bivariate_polynomial(threshold, rng)?;
2✔
87
        let updated = bp.set_coefficient(0, 0, secret);
4✔
88
        debug_assert!(updated);
2✔
89
        Ok(bp.into())
2✔
90
    }
91

92
    /// Returns the secret bivariate polynomial.
93
    pub fn bivariate_polynomial(&self) -> &BivariatePolynomial<G::Scalar> {
2✔
94
        &self.bp
×
95
    }
96

97
    /// Returns the verification matrix.
98
    pub fn verification_matrix(&self) -> &VerificationMatrix<G> {
3✔
99
        &self.vm
3✔
100
    }
101

102
    /// Generates shares of the secret for the given shareholders.
103
    pub fn make_shares(
1✔
104
        &self,
105
        xs: Vec<G::Scalar>,
106
        kind: HandoffKind,
107
    ) -> Vec<SecretShare<G::Scalar>> {
108
        xs.into_iter().map(|x| self.make_share(x, kind)).collect()
3✔
109
    }
110

111
    /// Generates a share of the secret for the given shareholder.
112
    pub fn make_share(&self, x: G::Scalar, kind: HandoffKind) -> SecretShare<G::Scalar> {
1✔
113
        let p = match kind {
1✔
114
            HandoffKind::DealingPhase => self.bp.eval_x(&x),
1✔
115
            HandoffKind::CommitteeUnchanged => self.bp.eval_x(&x),
1✔
116
            HandoffKind::CommitteeChanged => self.bp.eval_y(&x),
1✔
117
        };
118

119
        SecretShare::new(x, p)
1✔
120
    }
121

122
    /// Generates a random bivariate polynomial `B(x, y)` such that
123
    /// the polynomials `B(x, y)`, `B(x, 0)`, and `B(0, y)` have non-zero
124
    /// leading term, and the secret `B(0, 0)` is non-zero.
125
    ///
126
    /// This function is not constant time because it uses rejection
127
    /// sampling to ensure that the polynomials have the maximum degree.
128
    /// Additionally, the underlying prime field implementation may also
129
    /// use rejection sampling to generate uniformly random elements.
130
    fn generate_bivariate_polynomial(
4✔
131
        threshold: u8,
132
        rng: &mut impl RngCore,
133
    ) -> Result<BivariatePolynomial<G::Scalar>> {
134
        let deg_x = threshold;
×
135
        let deg_y = threshold.checked_mul(2).ok_or(Error::ThresholdTooLarge)?;
3✔
136

137
        // When using a random RNG and a large prime field, this loop
138
        // should execute once with an extremely high probability,
139
        // so there is no need to optimize it by randomly selecting
140
        // only the problematic coefficients.
141
        loop {
5✔
142
            let bp = BivariatePolynomial::<G::Scalar>::random(deg_x, deg_y, rng);
4✔
143

144
            let i = deg_x as usize;
5✔
145
            let j = deg_y as usize;
4✔
146
            let is_zero_00 = bp.coefficient(0, 0).unwrap().is_zero();
9✔
147
            let is_zero_xy = bp.coefficient(i, j).unwrap().is_zero();
9✔
148
            let is_zero_x0 = bp.coefficient(i, 0).unwrap().is_zero();
9✔
149
            let is_zero_0y = bp.coefficient(0, j).unwrap().is_zero();
9✔
150

151
            if (is_zero_00 | is_zero_xy | is_zero_x0 | is_zero_0y).into() {
5✔
NEW
152
                continue;
×
153
            }
154

155
            return Ok(bp);
4✔
156
        }
157
    }
158
}
159

160
impl<G> From<BivariatePolynomial<G::Scalar>> for Dealer<G>
161
where
162
    G: Group + GroupEncoding,
163
{
164
    /// Creates a new dealer from the given bivariate polynomial.
165
    fn from(bp: BivariatePolynomial<G::Scalar>) -> Self {
1✔
166
        let vm = VerificationMatrix::from(&bp);
1✔
167
        Self { bp, vm }
168
    }
169
}
170

171
#[cfg(test)]
172
mod tests {
173
    use rand::{rngs::StdRng, Error, RngCore, SeedableRng};
174

175
    use super::{BivariatePolynomial, HandoffKind};
176

177
    type PrimeField = p384::Scalar;
178
    type Group = p384::ProjectivePoint;
179
    type Dealer = super::Dealer<Group>;
180

181
    #[test]
182
    fn test_new() {
183
        let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]);
184

185
        let test_cases = vec![
186
            (0, 0, 0, 1, 1), // Zero threshold.
187
            (2, 2, 4, 3, 5), // Non-zero threshold.
188
        ];
189

190
        for (threshold, deg_x, deg_y, rows, cols) in test_cases {
191
            let dealer = Dealer::new(threshold, &mut rng).unwrap();
192
            assert_eq!(dealer.bivariate_polynomial().deg_x, deg_x);
193
            assert_eq!(dealer.bivariate_polynomial().deg_y, deg_y);
194
            assert_eq!(dealer.verification_matrix().rows, rows);
195
            assert_eq!(dealer.verification_matrix().cols, cols);
196
            assert_ne!(
197
                dealer.bivariate_polynomial().coefficient(0, 0), // Zero with negligible probability.
198
                Some(&PrimeField::ZERO)
199
            );
200
        }
201
    }
202

203
    #[test]
204
    fn test_new_proactive() {
205
        let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]);
206

207
        let test_cases = vec![
208
            (0, 0, 0, 1, 1), // Zero threshold.
209
            (2, 2, 4, 3, 5), // Non-zero threshold.
210
        ];
211

212
        for (threshold, deg_x, deg_y, rows, cols) in test_cases {
213
            let dealer = Dealer::new_proactive(threshold, &mut rng).unwrap();
214
            assert_eq!(dealer.bivariate_polynomial().deg_x, deg_x);
215
            assert_eq!(dealer.bivariate_polynomial().deg_y, deg_y);
216
            assert_eq!(dealer.verification_matrix().rows, rows);
217
            assert_eq!(dealer.verification_matrix().cols, cols);
218
            assert_eq!(
219
                dealer.bivariate_polynomial().coefficient(0, 0),
220
                Some(&PrimeField::ZERO)
221
            );
222
        }
223
    }
224

225
    #[test]
226
    fn test_new_with_secret() {
227
        let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]);
228

229
        let test_cases = vec![
230
            (0, 0, 0, 1, 1, 0),   // Zero threshold.
231
            (2, 2, 4, 3, 5, 100), // Non-zero threshold.
232
        ];
233

234
        for (threshold, deg_x, deg_y, rows, cols, secret) in test_cases {
235
            let secret = PrimeField::from_u64(secret);
236
            let dealer = Dealer::new_with_secret(threshold, secret, &mut rng).unwrap();
237
            assert_eq!(dealer.bivariate_polynomial().deg_x, deg_x);
238
            assert_eq!(dealer.bivariate_polynomial().deg_y, deg_y);
239
            assert_eq!(dealer.verification_matrix().rows, rows);
240
            assert_eq!(dealer.verification_matrix().cols, cols);
241
            assert_eq!(
242
                dealer.bivariate_polynomial().coefficient(0, 0),
243
                Some(&secret)
244
            );
245
        }
246
    }
247

248
    #[test]
249
    fn test_make_share() {
250
        let threshold = 2;
251
        let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]);
252
        let dealer = Dealer::new(threshold, &mut rng).unwrap();
253
        let x = PrimeField::from_u64(2);
254

255
        let test_cases = vec![
256
            (HandoffKind::DealingPhase, 5),
257
            (HandoffKind::CommitteeUnchanged, 5),
258
            (HandoffKind::CommitteeChanged, 3),
259
        ];
260

261
        for (kind, size) in test_cases {
262
            let share = dealer.make_share(x, kind);
263
            assert_eq!(share.polynomial().size(), size);
264
        }
265
    }
266

267
    #[test]
268
    fn test_generate_bivariate_polynomial() {
269
        /// A custom RNG that fills the first few slices with zeros,
270
        /// and subsequent slices with ones.
271
        struct ZeroOneRng {
272
            /// Tracks how many times the RNG has been called.
273
            counter: usize,
274
            /// The number of slices that should be filled with zeros.
275
            limit: usize,
276
        }
277

278
        impl ZeroOneRng {
279
            /// Creates a new generator with the given limit.
280
            fn new(limit: usize) -> Self {
281
                Self { limit, counter: 0 }
282
            }
283

284
            // Returns the total number of times the generator has been invoked.
285
            fn total(&self) -> usize {
286
                self.counter
287
            }
288
        }
289

290
        impl RngCore for ZeroOneRng {
291
            fn next_u32(&mut self) -> u32 {
292
                panic!("not implemented")
293
            }
294

295
            fn next_u64(&mut self) -> u64 {
296
                panic!("not implemented")
297
            }
298

299
            fn try_fill_bytes(&mut self, _dest: &mut [u8]) -> Result<(), Error> {
300
                panic!("not implemented")
301
            }
302

303
            fn fill_bytes(&mut self, dest: &mut [u8]) {
304
                match self.counter < self.limit {
305
                    true => dest.fill(0),
306
                    false => dest.fill(1),
307
                }
308
                self.counter += 1;
309
            }
310
        }
311

312
        let test_cases = vec![0, 2, 4];
313

314
        for threshold in test_cases {
315
            // Prepare RNG that will generate the first two bivariate polynomials
316
            // with all coefficients set to zero.
317
            let num_terms = (threshold + 1) * (2 * threshold + 1);
318
            let mut rng = ZeroOneRng::new(2 * num_terms);
319

320
            // Generate a random bivariate polynomial and verify leading coefficients.
321
            let bp = Dealer::generate_bivariate_polynomial(threshold as u8, &mut rng).unwrap();
322
            let f = bp.eval_y(&PrimeField::ZERO);
323
            let g = bp.eval_x(&PrimeField::ZERO);
324
            let i = threshold;
325
            let j = 2 * threshold;
326

327
            assert!(!Into::<bool>::into(bp.coefficient(i, j).unwrap().is_zero()));
328
            assert!(!Into::<bool>::into(f.coefficient(i).unwrap().is_zero()));
329
            assert!(!Into::<bool>::into(g.coefficient(j).unwrap().is_zero()));
330

331
            // Verify that the RNG generated coefficients for three polynomials.
332
            assert_eq!(3 * num_terms, rng.total());
333
        }
334
    }
335

336
    #[test]
337
    fn test_from() {
338
        let bp = BivariatePolynomial::zero(2, 3);
339
        let _ = Dealer::from(bp);
340
    }
341
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc