• 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

85.71
/secret-sharing/src/churp/switch.rs
1
use std::sync::{Arc, Mutex};
2

3
use anyhow::Result;
4
use group::{Group, GroupEncoding};
5

6
use crate::{
7
    poly::{lagrange::lagrange, Polynomial},
8
    vss::{VerificationMatrix, VerificationVector},
9
};
10

11
use super::{Error, SecretShare, Shareholder, VerifiableSecretShare};
12

13
/// Dimension switch state.
14
enum DimensionSwitchState<G>
15
where
16
    G: Group + GroupEncoding,
17
{
18
    /// Represents the state where the dimension switch is waiting for
19
    /// the verification matrix from the previous switch, which is needed
20
    /// to verify switch points. Once the matrix is received, the state
21
    /// transitions to the Accumulating state.
22
    WaitingForVerificationMatrix,
23

24
    /// Represents the state where the switch points are being accumulated.
25
    /// Upon collection of enough points, the state transitions to the Merging
26
    /// state if proactivization is required, or directly to the Serving state.
27
    Accumulating(SwitchPoints<G>),
28

29
    /// Represents the state where the dimension switch is waiting
30
    /// for a shareholder to be proactivized with bivariate shares.
31
    /// The shareholder can be constructed from received switch points,
32
    /// transferred from a previous handoff, or omitted if we want
33
    /// to construct a new one.
34
    WaitingForShareholder,
35

36
    /// Represents the state where the dimension switch is merging
37
    /// bivariate shares. Once enough shares are collected, the shareholder
38
    /// is proactivized, and the state transitions to the Serving state.
39
    /// If no shareholder was given, the combined shares define a new one.
40
    Merging(BivariateShares<G>),
41

42
    /// Represents the state where the dimension switch is completed,
43
    /// and a new shareholder is available to serve requests.
44
    Serving(Arc<Shareholder<G>>),
45
}
46

47
/// A dimension switch based on a share resharing technique.
48
pub struct DimensionSwitch<G>
49
where
50
    G: Group + GroupEncoding,
51
{
52
    /// The degree of the secret-sharing polynomial.
53
    threshold: u8,
54

55
    /// Indicates whether bivariate shares should be derived from a zero-hole
56
    /// bivariate polynomial.
57
    zero_hole: bool,
58

59
    /// Indicates whether bivariate shares should be full or reduced shares.
60
    full_share: bool,
61

62
    /// The encoded identity.
63
    me: G::Scalar,
64

65
    /// The set of shareholders from which bivariate shares need to be fetched.
66
    /// If empty, proactivization is skipped.
67
    shareholders: Vec<G::Scalar>,
68

69
    /// Current state of the switch.
70
    state: Mutex<DimensionSwitchState<G>>,
71
}
72

73
impl<G> DimensionSwitch<G>
74
where
75
    G: Group + GroupEncoding,
76
{
77
    /// Creates a new share reduction dimension switch.
78
    ///
79
    /// In share reduction, shareholders switch from the degree-t dimension
80
    /// of the secret bivariate polynomial B(x,y) to the degree-2t dimension.
81
    /// As a result, each shareholders in the new committee obtains a reduced
82
    /// share B(x,j) and proactivizes it to B'(x,j).
83
    pub(crate) fn new_share_reduction(
1✔
84
        threshold: u8,
85
        zero_hole: bool,
86
        me: G::Scalar,
87
        shareholders: Vec<G::Scalar>,
88
    ) -> Result<Self> {
89
        Self::new(threshold, zero_hole, false, me, shareholders)
1✔
90
    }
91

92
    /// Creates a new full share distribution dimension switch.
93
    ///
94
    /// In full share distribution, new shares B'(i,y) are generated from
95
    /// proactive reduced shares, by switching back to the degree-t dimension
96
    /// of B'(x,y).
97
    pub(crate) fn new_full_share_distribution(
1✔
98
        threshold: u8,
99
        zero_hole: bool,
100
        me: G::Scalar,
101
        shareholders: Vec<G::Scalar>,
102
    ) -> Result<Self> {
103
        Self::new(threshold, zero_hole, true, me, shareholders)
1✔
104
    }
105

106
    /// Creates a new dimension switch.
107
    fn new(
1✔
108
        threshold: u8,
109
        zero_hole: bool,
110
        full_share: bool,
111
        me: G::Scalar,
112
        shareholders: Vec<G::Scalar>,
113
    ) -> Result<Self> {
114
        let state = Mutex::new(DimensionSwitchState::WaitingForVerificationMatrix);
1✔
115

116
        Ok(Self {
1✔
117
            threshold,
×
118
            zero_hole,
×
119
            full_share,
×
120
            me,
×
121
            shareholders,
1✔
122
            state,
×
123
        })
124
    }
125

126
    /// Checks if the switch is waiting for the verification matrix.
127
    pub(crate) fn is_waiting_for_verification_matrix(&self) -> bool {
1✔
128
        let state = self.state.lock().unwrap();
2✔
129
        matches!(&*state, DimensionSwitchState::WaitingForVerificationMatrix)
2✔
130
    }
131

132
    /// Skips the switch point accumulation.
133
    pub(crate) fn skip_accumulating(&self) -> Result<()> {
1✔
134
        let mut state = self.state.lock().unwrap();
2✔
135
        match &*state {
2✔
136
            DimensionSwitchState::WaitingForVerificationMatrix => (),
×
137
            _ => return Err(Error::InvalidState.into()),
×
138
        };
139

140
        *state = DimensionSwitchState::WaitingForShareholder;
1✔
141
        Ok(())
1✔
142
    }
143

144
    /// Starts accumulating switch points using the provided verification
145
    /// matrix for point verification.
146
    pub(crate) fn start_accumulating(&self, vm: VerificationMatrix<G>) -> Result<()> {
1✔
147
        let mut state = self.state.lock().unwrap();
2✔
148
        match *state {
2✔
149
            DimensionSwitchState::WaitingForVerificationMatrix => (),
×
150
            _ => return Err(Error::InvalidState.into()),
×
151
        }
152

153
        let sp = SwitchPoints::new(self.threshold, self.full_share, self.me, vm)?;
2✔
154
        *state = DimensionSwitchState::Accumulating(sp);
1✔
155

156
        Ok(())
1✔
157
    }
158

159
    /// Checks if a switch point is required from the given shareholder.
160
    pub(crate) fn needs_switch_point(&self, x: &G::Scalar) -> Result<bool> {
1✔
161
        let state = self.state.lock().unwrap();
2✔
162
        let sp = match &*state {
2✔
163
            DimensionSwitchState::WaitingForVerificationMatrix => return Ok(true),
×
164
            DimensionSwitchState::Accumulating(sp) => sp,
1✔
165
            _ => return Err(Error::InvalidState.into()),
2✔
166
        };
167

168
        let needs = sp.needs_point(x);
2✔
169
        Ok(needs)
1✔
170
    }
171

172
    /// Verifies and adds the given switch point.
173
    ///
174
    /// Returns true if enough points have been received and the switch
175
    /// transitioned to the next state.
176
    pub(crate) fn add_switch_point(&self, x: G::Scalar, bij: G::Scalar) -> Result<bool> {
1✔
177
        let mut state = self.state.lock().unwrap();
2✔
178
        let sp = match &mut *state {
2✔
179
            DimensionSwitchState::Accumulating(sp) => sp,
1✔
180
            _ => return Err(Error::InvalidState.into()),
×
181
        };
182

183
        let done = sp.add_point(x, bij)?;
2✔
184
        if done {
2✔
185
            let shareholder = sp.reconstruct_shareholder()?;
1✔
186
            let shareholder = Arc::new(shareholder);
2✔
187

188
            if self.shareholders.is_empty() {
3✔
189
                *state = DimensionSwitchState::Serving(shareholder);
1✔
190
            } else {
191
                let bs = BivariateShares::new(
192
                    self.threshold,
1✔
193
                    self.zero_hole,
1✔
194
                    self.full_share,
1✔
195
                    self.me,
1✔
196
                    self.shareholders.clone(),
1✔
197
                    Some(shareholder),
1✔
198
                )?;
199
                *state = DimensionSwitchState::Merging(bs);
1✔
200
            }
201
        }
202

203
        Ok(done)
1✔
204
    }
205

206
    /// Checks if the switch is waiting for a shareholder.
207
    pub(crate) fn is_waiting_for_shareholder(&self) -> bool {
1✔
208
        let state = self.state.lock().unwrap();
2✔
209
        matches!(&*state, DimensionSwitchState::WaitingForShareholder)
2✔
210
    }
211

212
    /// Starts merging bivariate shares to be used for proactivization
213
    /// of the provided shareholder.
214
    pub(crate) fn start_merging(&self, shareholder: Option<Arc<Shareholder<G>>>) -> Result<()> {
1✔
215
        let mut state = self.state.lock().unwrap();
2✔
216
        match &*state {
2✔
217
            DimensionSwitchState::WaitingForShareholder => (),
×
218
            _ => return Err(Error::InvalidState.into()),
×
219
        };
220

221
        let bs = BivariateShares::new(
222
            self.threshold,
1✔
223
            self.zero_hole,
1✔
224
            self.full_share,
1✔
225
            self.me,
1✔
226
            self.shareholders.clone(),
1✔
227
            shareholder,
1✔
228
        )?;
229
        *state = DimensionSwitchState::Merging(bs);
1✔
230

231
        Ok(())
1✔
232
    }
233

234
    /// Checks if a bivariate share is needed from the given shareholder.
235
    pub(crate) fn needs_bivariate_share(&self, x: &G::Scalar) -> Result<bool> {
1✔
236
        let state = self.state.lock().unwrap();
2✔
237
        let bs = match &*state {
2✔
238
            DimensionSwitchState::Merging(bs) => bs,
1✔
239
            _ => return Err(Error::InvalidState.into()),
2✔
240
        };
241

242
        let needs = bs.needs_bivariate_share(x);
2✔
243
        Ok(needs)
1✔
244
    }
245

246
    /// Verifies and adds the given bivariate share.
247
    ///
248
    /// Returns true if all shares have been received and the switch
249
    /// transitioned to the next state.
250
    pub(crate) fn add_bivariate_share(
1✔
251
        &self,
252
        x: &G::Scalar,
253
        verifiable_share: VerifiableSecretShare<G>,
254
    ) -> Result<bool> {
255
        let mut state = self.state.lock().unwrap();
2✔
256
        let shares = match &mut *state {
2✔
257
            DimensionSwitchState::Merging(bs) => bs,
1✔
258
            _ => return Err(Error::InvalidState.into()),
×
259
        };
260

261
        let done = shares.add_bivariate_share(x, verifiable_share)?;
2✔
262
        if done {
2✔
263
            let shareholder = shares.proactivize_shareholder()?;
1✔
264
            let shareholder = Arc::new(shareholder);
2✔
265
            *state = DimensionSwitchState::Serving(shareholder);
1✔
266
        }
267

268
        Ok(done)
1✔
269
    }
270

271
    /// Returns the shareholder if the switch has completed.
272
    pub(crate) fn get_shareholder(&self) -> Result<Arc<Shareholder<G>>> {
1✔
273
        let state = self.state.lock().unwrap();
2✔
274
        let shareholder = match &*state {
2✔
275
            DimensionSwitchState::Serving(p) => p.clone(),
2✔
276
            _ => return Err(Error::InvalidState.into()),
2✔
277
        };
278

279
        Ok(shareholder)
1✔
280
    }
281
}
282

283
/// An accumulator for switch points.
284
#[derive(Debug)]
285
pub struct SwitchPoints<G>
286
where
287
    G: Group + GroupEncoding,
288
{
289
    /// The minimum number of distinct points required to reconstruct
290
    /// the polynomial.
291
    n: usize,
292

293
    /// Field element representing the identity of the shareholder.
294
    me: Option<G::Scalar>,
295

296
    /// The verification matrix for the bivariate polynomial of the source
297
    /// committee from the previous handoff.
298
    ///
299
    /// It is used to validate incoming switch points `B(node_id, me)`
300
    /// or `B(me, node_id)` during the share reduction or full share
301
    /// distribution phase.
302
    vm: Option<VerificationMatrix<G>>,
303

304
    /// The verification vector, derived from the verification matrix,
305
    /// is used to efficiently validate switch points.
306
    ///
307
    /// The vector can verify switch points from univariate polynomials
308
    /// `B(x, me)` or `B(me, y)` during the share reduction or full share
309
    /// distribution phase.
310
    vv: VerificationVector<G>,
311

312
    /// A list of encoded shareholders' identities whose points have been
313
    /// received.
314
    xs: Vec<G::Scalar>,
315

316
    /// A list of received switch points.
317
    bijs: Vec<G::Scalar>,
318
}
319

320
impl<G> SwitchPoints<G>
321
where
322
    G: Group + GroupEncoding,
323
{
324
    /// Creates a new accumulator for switch points.
325
    fn new(
1✔
326
        threshold: u8,
327
        full_share: bool,
328
        me: G::Scalar,
329
        vm: VerificationMatrix<G>,
330
    ) -> Result<Self> {
331
        let threshold = threshold as usize;
1✔
332
        let rows = threshold + 1;
1✔
333
        let cols = 2 * threshold + 1;
2✔
334

335
        if vm.dimensions() != (rows, cols) {
2✔
336
            return Err(Error::VerificationMatrixDimensionMismatch.into());
×
337
        }
338

339
        // Precomputing the verification vector speeds up switch point
340
        // validation.
341
        let (n, vv) = match full_share {
2✔
342
            false => (rows, vm.verification_vector_for_x(&me)),
2✔
343
            true => (cols, vm.verification_vector_for_y(&me)),
2✔
344
        };
345

346
        // Wrap the identifier and the matrix in an option so that we can take
347
        // them when creating a shareholder.
348
        let me = Some(me);
1✔
349
        let vm = Some(vm);
1✔
350

351
        // We need at least n points to reconstruct the polynomial share.
352
        let xs = Vec::with_capacity(n);
1✔
353
        let bijs = Vec::with_capacity(n);
1✔
354

355
        Ok(Self {
1✔
356
            n,
×
357
            me,
×
358
            vm,
1✔
359
            vv,
1✔
360
            xs,
1✔
361
            bijs,
×
362
        })
363
    }
364

365
    /// Checks if a switch point is required from the given shareholder.
366
    fn needs_point(&self, x: &G::Scalar) -> bool {
1✔
367
        if self.xs.len() >= self.n {
1✔
368
            return false;
1✔
369
        }
370
        !self.xs.contains(x)
1✔
371
    }
372

373
    /// Verifies and adds the given switch point.
374
    ///
375
    /// Returns true if enough points have been received; otherwise,
376
    /// it returns false.
377
    fn add_point(&mut self, x: G::Scalar, bij: G::Scalar) -> Result<bool> {
1✔
378
        if self.xs.len() >= self.n {
1✔
379
            return Err(Error::TooManySwitchPoints.into());
1✔
380
        }
381
        if self.xs.contains(&x) {
1✔
382
            return Err(Error::DuplicateShareholder.into());
1✔
383
        }
384

385
        // The identity of the shareholder doesn't require verification.
386
        // If the point is valid, it doesn't matter if it came from a stranger.
387
        // However, since verification is costly, one could check if the point
388
        // came from a legitimate shareholder.
389
        if !self.vv.verify(&x, &bij) {
1✔
390
            return Err(Error::InvalidSwitchPoint.into());
1✔
391
        }
392

393
        self.xs.push(x);
1✔
394
        self.bijs.push(bij);
1✔
395

396
        let done = self.xs.len() >= self.n;
1✔
397

398
        Ok(done)
1✔
399
    }
400

401
    /// Reconstructs the shareholder from the received switch points.
402
    ///
403
    /// The shareholder can be reconstructed only once, which avoids copying
404
    /// the verification matrix.
405
    fn reconstruct_shareholder(&mut self) -> Result<Shareholder<G>> {
1✔
406
        if self.xs.len() < self.n {
1✔
407
            return Err(Error::NotEnoughSwitchPoints.into());
1✔
408
        }
409

410
        let xs = &self.xs[0..self.n];
1✔
411
        let ys = &self.bijs[0..self.n];
1✔
412
        let p = lagrange(xs, ys);
1✔
413

414
        if p.size() != self.n {
2✔
415
            return Err(Error::PolynomialDegreeMismatch.into());
×
416
        }
417

418
        let x = self.me.take().ok_or(Error::ShareholderIdentityRequired)?;
2✔
419
        let vm = self.vm.take().ok_or(Error::VerificationMatrixRequired)?;
2✔
420
        let share = SecretShare::new(x, p);
1✔
421
        let shareholder = Shareholder::new(share, vm);
1✔
422

423
        Ok(shareholder)
1✔
424
    }
425
}
426

427
/// An accumulator for bivariate shares.
428
struct BivariateShares<G>
429
where
430
    G: Group + GroupEncoding,
431
{
432
    /// The degree of the secret-sharing polynomial.
433
    threshold: u8,
434

435
    /// Indicates whether bivariate shares should be derived from a zero-hole
436
    /// bivariate polynomial.
437
    zero_hole: bool,
438

439
    /// Indicates whether bivariate shares should be full or reduced shares.
440
    full_share: bool,
441

442
    /// Field element representing the identity of the shareholder.
443
    me: G::Scalar,
444

445
    /// A set of shareholders providing bivariate shares.
446
    shareholders: Vec<G::Scalar>,
447

448
    /// A set of shareholders whose bivariate share still needs to be received.
449
    pending_shareholders: Vec<G::Scalar>,
450

451
    /// The shareholder to be proactivized with bivariate shares.
452
    shareholder: Option<Arc<Shareholder<G>>>,
453

454
    /// The sum of the received bivariate shares.
455
    p: Option<Polynomial<G::Scalar>>,
456

457
    /// The sum of the verification matrices of the received bivariate shares.
458
    vm: Option<VerificationMatrix<G>>,
459
}
460

461
impl<G> BivariateShares<G>
462
where
463
    G: Group + GroupEncoding,
464
{
465
    /// Creates a new accumulator for bivariate shares.
466
    fn new(
1✔
467
        threshold: u8,
468
        zero_hole: bool,
469
        full_share: bool,
470
        me: G::Scalar,
471
        shareholders: Vec<G::Scalar>,
472
        shareholder: Option<Arc<Shareholder<G>>>,
473
    ) -> Result<Self> {
474
        if shareholders.is_empty() {
2✔
475
            return Err(Error::NotEnoughShareholders.into());
2✔
476
        }
477

478
        let pending_shareholders = shareholders.clone();
1✔
479

480
        Ok(Self {
1✔
481
            threshold,
×
482
            zero_hole,
×
483
            full_share,
×
484
            me,
×
485
            shareholders,
1✔
486
            pending_shareholders,
×
487
            shareholder,
1✔
488
            p: None,
1✔
489
            vm: None,
1✔
490
        })
491
    }
492

493
    /// Checks if a bivariate share can be received from the given shareholder.
494
    fn has_bivariate_share(&self, x: &G::Scalar) -> bool {
1✔
495
        self.shareholders.contains(x)
1✔
496
    }
497

498
    /// Checks if a bivariate share is needed from the given shareholder.
499
    fn needs_bivariate_share(&self, x: &G::Scalar) -> bool {
1✔
500
        self.pending_shareholders.contains(x)
1✔
501
    }
502

503
    /// Verifies and adds the given bivariate share.
504
    ///
505
    /// Returns true if all shares have been received; otherwise,
506
    /// it returns false.
507
    fn add_bivariate_share(
1✔
508
        &mut self,
509
        x: &G::Scalar,
510
        verifiable_share: VerifiableSecretShare<G>,
511
    ) -> Result<bool> {
512
        if !self.has_bivariate_share(x) {
2✔
513
            return Err(Error::UnknownShareholder.into());
2✔
514
        }
515
        if !self.needs_bivariate_share(x) {
2✔
516
            return Err(Error::DuplicateShareholder.into());
2✔
517
        }
518

519
        if verifiable_share.share.x != self.me {
2✔
520
            return Err(Error::ShareholderIdentityMismatch.into());
×
521
        }
522
        verifiable_share.verify(self.threshold, self.zero_hole, self.full_share)?;
5✔
523

524
        let p = match self.p.take() {
2✔
525
            Some(p) => p + verifiable_share.share.p,
2✔
526
            None => verifiable_share.share.p,
1✔
527
        };
528
        self.p = Some(p);
1✔
529

530
        let vm = match self.vm.take() {
2✔
531
            Some(vm) => vm + verifiable_share.vm,
2✔
532
            None => verifiable_share.vm,
1✔
533
        };
534
        self.vm = Some(vm);
1✔
535

536
        let index = self
3✔
537
            .pending_shareholders
×
538
            .iter()
539
            .position(|y| y == x)
2✔
540
            .unwrap();
541
        self.pending_shareholders.swap_remove(index);
1✔
542

543
        let done = self.pending_shareholders.is_empty();
1✔
544

545
        Ok(done)
1✔
546
    }
547

548
    /// Proactivizes the shareholder with the combined polynomial
549
    /// and verification matrix.
550
    fn proactivize_shareholder(&mut self) -> Result<Shareholder<G>> {
1✔
551
        if !self.pending_shareholders.is_empty() {
1✔
552
            return Err(Error::NotEnoughBivariateShares.into());
1✔
553
        }
554

555
        let p = self
3✔
NEW
556
            .p
×
557
            .take()
558
            .ok_or(Error::ShareholderProactivizationCompleted)?;
1✔
559
        let vm = self
2✔
NEW
560
            .vm
×
561
            .take()
562
            .ok_or(Error::ShareholderProactivizationCompleted)?;
1✔
563

564
        let shareholder = match &self.shareholder {
1✔
565
            Some(shareholder) => shareholder.proactivize(&p, &vm)?,
2✔
566
            None => {
×
567
                let share = SecretShare::new(self.me, p);
1✔
568
                Shareholder::new(share, vm)
1✔
569
            }
570
        };
571

572
        // Ensure that the combined bivariate polynomial satisfies
573
        // the non-zero leading term requirements.
574
        shareholder
4✔
575
            .verifiable_share()
576
            .verify(self.threshold, false, self.full_share)?;
1✔
577

578
        Ok(shareholder)
1✔
579
    }
580
}
581

582
#[cfg(test)]
583
mod tests {
584
    use anyhow::Result;
585
    use rand::{rngs::StdRng, SeedableRng};
586

587
    use crate::{
588
        churp::{SecretShare, VerifiableSecretShare},
589
        poly,
590
        suites::{self, p384},
591
        vss,
592
    };
593

594
    use super::{BivariateShares, Error, SwitchPoints};
595

596
    type Suite = p384::Sha3_384;
597
    type Group = <Suite as suites::Suite>::Group;
598
    type PrimeField = <Suite as suites::Suite>::PrimeField;
599
    type BivariatePolynomial = poly::BivariatePolynomial<<Suite as suites::Suite>::PrimeField>;
600
    type VerificationMatrix = vss::VerificationMatrix<<Suite as suites::Suite>::Group>;
601

602
    fn prepare_shareholder(id: u64) -> PrimeField {
603
        id.into()
604
    }
605

606
    fn prepare_shareholders(ids: &[u64]) -> Vec<PrimeField> {
607
        ids.into_iter().map(|&id| id.into()).collect()
608
    }
609

610
    fn add_point(
611
        me: u64,
612
        sh: u64,
613
        bp: &BivariatePolynomial,
614
        sp: &mut SwitchPoints<Group>,
615
        full_share: bool,
616
    ) -> Result<bool> {
617
        let x = prepare_shareholder(sh);
618
        let y = prepare_shareholder(me);
619
        let bij = match full_share {
620
            false => bp.eval(&x, &y),
621
            true => bp.eval(&y, &x),
622
        };
623
        let res = sp.add_point(x, bij);
624
        res
625
    }
626

627
    #[test]
628
    fn test_switch_point() {
629
        let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]);
630

631
        let threshold = 2;
632
        let deg_x = threshold;
633
        let deg_y = 2 * threshold;
634
        let bp = BivariatePolynomial::random(deg_x, deg_y, &mut rng);
635
        let vm = VerificationMatrix::from(&bp);
636
        let me = prepare_shareholder(1);
637

638
        for full_share in vec![false, true] {
639
            let mut sp = SwitchPoints::<Group>::new(threshold, full_share, me, vm.clone()).unwrap();
640
            let me = 1;
641
            let mut sh = 2;
642

643
            // Add invalid point (switch x and y).
644
            let res = add_point(sh, me, &bp, &mut sp, full_share);
645
            assert!(res.is_err());
646
            assert_eq!(
647
                res.unwrap_err().to_string(),
648
                Error::InvalidSwitchPoint.to_string()
649
            );
650

651
            // Add point.
652
            let res = add_point(me, me, &bp, &mut sp, full_share);
653
            assert!(res.is_ok());
654
            assert!(!res.unwrap());
655

656
            // Add another point twice.
657
            assert!(sp.needs_point(&prepare_shareholder(sh)));
658

659
            let res = add_point(me, sh, &bp, &mut sp, full_share);
660
            assert!(res.is_ok());
661
            assert!(!res.unwrap());
662

663
            let res = add_point(me, sh, &bp, &mut sp, full_share);
664
            assert!(res.is_err());
665
            assert_eq!(
666
                res.unwrap_err().to_string(),
667
                Error::DuplicateShareholder.to_string()
668
            );
669

670
            assert!(!sp.needs_point(&prepare_shareholder(sh)));
671
            sh += 1;
672

673
            // Try to reconstruct the polynomial.
674
            let res = sp.reconstruct_shareholder();
675
            assert!(res.is_err());
676
            unsafe {
677
                assert_eq!(
678
                    res.unwrap_err_unchecked().to_string(),
679
                    Error::NotEnoughSwitchPoints.to_string()
680
                );
681
            }
682

683
            // Full share distribution needs 2 * threshold points.
684
            if full_share {
685
                for _ in 0..threshold {
686
                    let res = add_point(me, sh, &bp, &mut sp, full_share);
687
                    assert!(res.is_ok());
688
                    assert!(!res.unwrap());
689
                    sh += 1;
690
                }
691
            }
692

693
            // Add the last point.
694
            let res = add_point(me, sh, &bp, &mut sp, full_share);
695
            assert!(res.is_ok());
696
            assert!(res.unwrap()); // Enough points.
697
            sh += 1;
698

699
            // No more points needed.
700
            assert!(!sp.needs_point(&prepare_shareholder(sh)));
701

702
            // Too many points.
703
            let res = add_point(me, sh, &bp, &mut sp, full_share);
704
            assert!(res.is_err());
705
            assert_eq!(
706
                res.unwrap_err().to_string(),
707
                Error::TooManySwitchPoints.to_string()
708
            );
709

710
            // Try to reconstruct the polynomial again.
711
            let res = sp.reconstruct_shareholder();
712
            assert!(res.is_ok());
713
        }
714
    }
715

716
    fn add_bivariate_shares(
717
        threshold: u8,
718
        zero_hole: bool,
719
        full_share: bool,
720
        me: u64,
721
        sh: u64,
722
        bs: &mut BivariateShares<Group>,
723
    ) -> Result<bool> {
724
        let deg_x = threshold;
725
        let deg_y = 2 * threshold;
726
        let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]);
727
        let mut bp = BivariatePolynomial::random(deg_x, deg_y, &mut rng);
728
        if zero_hole {
729
            bp.to_zero_hole();
730
        };
731
        let vm = VerificationMatrix::from(&bp);
732
        let x = prepare_shareholder(me);
733
        let p = match full_share {
734
            false => bp.eval_y(&x),
735
            true => bp.eval_x(&x),
736
        };
737
        let share = SecretShare::new(x, p);
738
        let verifiable_share = VerifiableSecretShare::new(share, vm);
739
        let x = prepare_shareholder(sh);
740
        bs.add_bivariate_share(&x, verifiable_share)
741
    }
742

743
    #[test]
744
    fn test_bivariate_shares() {
745
        let threshold = 2;
746

747
        let me = prepare_shareholder(1);
748
        let shareholders = prepare_shareholders(&[1, 2, 3]);
749

750
        // There should be at least 1 shareholder.
751
        let res = BivariateShares::<Group>::new(threshold, false, false, me, vec![], None);
752
        assert!(res.is_err());
753
        unsafe {
754
            assert_eq!(
755
                res.unwrap_err_unchecked().to_string(),
756
                Error::NotEnoughShareholders.to_string()
757
            );
758
        }
759

760
        // Happy path.
761
        for full_share in vec![false, true] {
762
            for zero_hole in vec![false, true] {
763
                let mut bs = BivariateShares::<Group>::new(
764
                    threshold,
765
                    zero_hole,
766
                    full_share,
767
                    me,
768
                    shareholders.clone(),
769
                    None,
770
                )
771
                .unwrap();
772

773
                let me = 1;
774
                let mut sh = 2;
775

776
                // Add invalid share (invalid threshold).
777
                let res =
778
                    add_bivariate_shares(threshold + 1, zero_hole, full_share, me, me, &mut bs);
779
                assert!(res.is_err());
780
                assert_eq!(
781
                    res.unwrap_err().to_string(),
782
                    Error::VerificationMatrixDimensionMismatch.to_string()
783
                );
784

785
                // Add share.
786
                let res = add_bivariate_shares(threshold, zero_hole, full_share, me, me, &mut bs);
787
                assert!(res.is_ok());
788
                assert!(!res.unwrap());
789

790
                // Add another share twice.
791
                assert!(bs.needs_bivariate_share(&prepare_shareholder(sh)));
792

793
                let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs);
794
                assert!(res.is_ok());
795
                assert!(!res.unwrap());
796

797
                let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs);
798
                assert!(res.is_err());
799
                assert_eq!(
800
                    res.unwrap_err().to_string(),
801
                    Error::DuplicateShareholder.to_string()
802
                );
803

804
                assert!(!bs.needs_bivariate_share(&prepare_shareholder(sh)));
805
                sh += 1;
806

807
                // Try to collect the polynomial and verification matrix.
808
                let res = bs.proactivize_shareholder();
809
                assert!(res.is_err());
810
                unsafe {
811
                    assert_eq!(
812
                        res.unwrap_err_unchecked().to_string(),
813
                        Error::NotEnoughBivariateShares.to_string()
814
                    );
815
                }
816

817
                // Add the last share.
818
                let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs);
819
                assert!(res.is_ok());
820
                assert!(res.unwrap()); // Enough shares.
821
                sh += 1;
822

823
                // Unknown shareholder.
824
                assert!(!bs.needs_bivariate_share(&prepare_shareholder(sh)));
825

826
                let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs);
827
                assert!(res.is_err());
828
                assert_eq!(
829
                    res.unwrap_err().to_string(),
830
                    Error::UnknownShareholder.to_string()
831
                );
832

833
                // Try to collect the polynomial and verification matrix again.
834
                let res = bs.proactivize_shareholder();
835
                match zero_hole {
836
                    true => assert!(res.is_err()), // The combined polynomial has zero secret (not allowed).
837
                    false => assert!(res.is_ok()), // The combined polynomial has non-zero secret.
838
                }
839
            }
840
        }
841
    }
842
}
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