commonware_cryptography/bls12381/dkg/
ops.rs

1//! Stateless operations useful in a DKG/Resharing procedure.
2
3use crate::bls12381::{
4    dkg::Error,
5    primitives::{
6        group::Share,
7        ops::msm_interpolate,
8        poly::{self, compute_weights},
9        variant::Variant,
10    },
11};
12use rand_core::CryptoRngCore;
13use rayon::{prelude::*, ThreadPoolBuilder};
14use std::collections::BTreeMap;
15
16/// Generate shares and a commitment.
17pub fn generate_shares<R: CryptoRngCore, V: Variant>(
18    rng: &mut R,
19    share: Option<Share>,
20    n: u32,
21    t: u32,
22) -> (poly::Public<V>, Vec<Share>) {
23    // Generate a secret polynomial and commit to it
24    let mut secret = poly::new_from(t - 1, rng);
25    if let Some(share) = share {
26        // Set the free coefficient of the secret polynomial to the secret
27        // of the previous DKG
28        secret.set(0, share.private);
29    }
30
31    // Commit to polynomial and generate shares
32    let commitment = poly::Public::<V>::commit(secret.clone());
33    let shares = (0..n)
34        .map(|i| {
35            let eval = secret.evaluate(i);
36            Share {
37                index: eval.index,
38                private: eval.value,
39            }
40        })
41        .collect::<Vec<_>>();
42    (commitment, shares)
43}
44
45/// Evaluates the polynomial at `n` indices.
46pub fn evaluate_all<V: Variant>(polynomial: &poly::Public<V>, n: u32) -> Vec<V::Public> {
47    let mut evals = Vec::with_capacity(n as usize);
48    for index in 0..n {
49        evals.push(polynomial.evaluate(index).value);
50    }
51    evals
52}
53
54/// Verify that a given commitment is valid for a dealer. If a previous
55/// polynomial is provided, verify that the commitment is on that polynomial.
56pub fn verify_commitment<V: Variant>(
57    previous: Option<&poly::Public<V>>,
58    commitment: &poly::Public<V>,
59    dealer: u32,
60    t: u32,
61) -> Result<(), Error> {
62    if let Some(previous) = previous {
63        let expected = previous.evaluate(dealer).value;
64        if expected != *commitment.constant() {
65            return Err(Error::UnexpectedPolynomial);
66        }
67    }
68    if commitment.degree() != t - 1 {
69        return Err(Error::CommitmentWrongDegree);
70    }
71    Ok(())
72}
73
74/// Verify that a given share is valid for a specified recipient.
75///
76/// # Warning
77///
78/// This function assumes the provided commitment has already been verified.
79pub fn verify_share<V: Variant>(
80    commitment: &poly::Public<V>,
81    recipient: u32,
82    share: &Share,
83) -> Result<(), Error> {
84    // Check if share is valid
85    if share.index != recipient {
86        return Err(Error::MisdirectedShare);
87    }
88    let expected = share.public::<V>();
89    let given = commitment.evaluate(share.index);
90    if given.value != expected {
91        return Err(Error::ShareWrongCommitment);
92    }
93    Ok(())
94}
95
96/// Construct a public polynomial by summing a vector of commitments.
97pub fn construct_public<'a, V: Variant>(
98    commitments: impl IntoIterator<Item = &'a poly::Public<V>>,
99    required: u32,
100) -> Result<poly::Public<V>, Error> {
101    // Compute new public polynomial by summing all commitments
102    let mut count = 0;
103    let mut public = poly::Public::<V>::zero();
104    for commitment in commitments.into_iter() {
105        public.add(commitment);
106        count += 1;
107    }
108
109    // Ensure we have enough commitments
110    if count < required {
111        return Err(Error::InsufficientDealings);
112    }
113    Ok(public)
114}
115
116/// Recover public polynomial by interpolating coefficient-wise all
117/// polynomials using precomputed Barycentric Weights.
118///
119/// It is assumed that the required number of commitments are provided.
120pub fn recover_public_with_weights<V: Variant>(
121    previous: &poly::Public<V>,
122    commitments: &BTreeMap<u32, poly::Public<V>>,
123    weights: &BTreeMap<u32, poly::Weight>,
124    threshold: u32,
125    concurrency: usize,
126) -> Result<poly::Public<V>, Error> {
127    // Ensure we have enough commitments to interpolate
128    let required = previous.required();
129    if commitments.len() < required as usize {
130        return Err(Error::InsufficientDealings);
131    }
132
133    // Construct pool to perform interpolation
134    let pool = ThreadPoolBuilder::new()
135        .num_threads(concurrency)
136        .build()
137        .expect("unable to build thread pool");
138
139    // Perform interpolation over each coefficient using the precomputed weights
140    let new = match pool.install(|| {
141        (0..threshold)
142            .into_par_iter()
143            .map(|coeff| {
144                // Extract evaluations for this coefficient from all commitments
145                let evals = commitments
146                    .iter()
147                    .map(|(dealer, commitment)| poly::Eval {
148                        index: *dealer,
149                        value: commitment.get(coeff),
150                    })
151                    .collect::<Vec<_>>();
152
153                // Use precomputed weights for interpolation
154                msm_interpolate(weights, &evals).map_err(|_| Error::PublicKeyInterpolationFailed)
155            })
156            .collect::<Result<Vec<_>, _>>()
157    }) {
158        Ok(points) => poly::Public::<V>::from(points),
159        Err(e) => return Err(e),
160    };
161
162    // Ensure public key matches
163    if previous.constant() != new.constant() {
164        return Err(Error::ReshareMismatch);
165    }
166    Ok(new)
167}
168
169/// Recover public polynomial by interpolating coefficient-wise all
170/// polynomials.
171///
172/// It is assumed that the required number of commitments are provided.
173pub fn recover_public<V: Variant>(
174    previous: &poly::Public<V>,
175    commitments: &BTreeMap<u32, poly::Public<V>>,
176    threshold: u32,
177    concurrency: usize,
178) -> Result<poly::Public<V>, Error> {
179    // Ensure we have enough commitments to interpolate
180    let required = previous.required();
181    if commitments.len() < required as usize {
182        return Err(Error::InsufficientDealings);
183    }
184
185    // Precompute Barycentric Weights for all coefficients
186    let indices: Vec<u32> = commitments.keys().cloned().collect();
187    let weights = compute_weights(indices).map_err(|_| Error::PublicKeyInterpolationFailed)?;
188
189    // Perform interpolation over each coefficient using the precomputed weights
190    recover_public_with_weights::<V>(previous, commitments, &weights, threshold, concurrency)
191}