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::RngCore;
13use rayon::{prelude::*, ThreadPoolBuilder};
14use std::collections::BTreeMap;
15
16/// Generate shares and a commitment.
17pub fn generate_shares<R: RngCore, 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    dealer: u32,
59    commitment: &poly::Public<V>,
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.
75pub fn verify_share<V: Variant>(
76    previous: Option<&poly::Public<V>>,
77    dealer: u32,
78    commitment: &poly::Public<V>,
79    t: u32,
80    recipient: u32,
81    share: &Share,
82) -> Result<(), Error> {
83    // Verify that commitment is on previous public polynomial (if provided)
84    verify_commitment::<V>(previous, dealer, commitment, t)?;
85
86    // Check if share is valid
87    if share.index != recipient {
88        return Err(Error::MisdirectedShare);
89    }
90    let expected = share.public::<V>();
91    let given = commitment.evaluate(share.index);
92    if given.value != expected {
93        return Err(Error::ShareWrongCommitment);
94    }
95    Ok(())
96}
97
98/// Construct a new public polynomial by summing all commitments.
99pub fn construct_public<V: Variant>(
100    commitments: Vec<poly::Public<V>>,
101    required: u32,
102) -> Result<poly::Public<V>, Error> {
103    if commitments.len() < required as usize {
104        return Err(Error::InsufficientDealings);
105    }
106    let mut public = poly::Public::<V>::zero();
107    for commitment in commitments {
108        public.add(&commitment);
109    }
110    Ok(public)
111}
112
113/// Recover public polynomial by interpolating coefficient-wise all
114/// polynomials using precomputed Barycentric Weights.
115///
116/// It is assumed that the required number of commitments are provided.
117pub fn recover_public_with_weights<V: Variant>(
118    previous: &poly::Public<V>,
119    commitments: BTreeMap<u32, poly::Public<V>>,
120    weights: &BTreeMap<u32, poly::Weight>,
121    threshold: u32,
122    concurrency: usize,
123) -> Result<poly::Public<V>, Error> {
124    // Ensure we have enough commitments to interpolate
125    let required = previous.required();
126    if commitments.len() < required as usize {
127        return Err(Error::InsufficientDealings);
128    }
129
130    // Construct pool to perform interpolation
131    let pool = ThreadPoolBuilder::new()
132        .num_threads(concurrency)
133        .build()
134        .expect("unable to build thread pool");
135
136    // Perform interpolation over each coefficient using the precomputed weights
137    let new = match pool.install(|| {
138        (0..threshold)
139            .into_par_iter()
140            .map(|coeff| {
141                // Extract evaluations for this coefficient from all commitments
142                let evals = commitments
143                    .iter()
144                    .map(|(dealer, commitment)| poly::Eval {
145                        index: *dealer,
146                        value: commitment.get(coeff),
147                    })
148                    .collect::<Vec<_>>();
149
150                // Use precomputed weights for interpolation
151                msm_interpolate(weights, &evals).map_err(|_| Error::PublicKeyInterpolationFailed)
152            })
153            .collect::<Result<Vec<_>, _>>()
154    }) {
155        Ok(points) => poly::Public::<V>::from(points),
156        Err(e) => return Err(e),
157    };
158
159    // Ensure public key matches
160    if previous.constant() != new.constant() {
161        return Err(Error::ReshareMismatch);
162    }
163    Ok(new)
164}
165
166/// Recover public polynomial by interpolating coefficient-wise all
167/// polynomials.
168///
169/// It is assumed that the required number of commitments are provided.
170pub fn recover_public<V: Variant>(
171    previous: &poly::Public<V>,
172    commitments: BTreeMap<u32, poly::Public<V>>,
173    threshold: u32,
174    concurrency: usize,
175) -> Result<poly::Public<V>, Error> {
176    // Ensure we have enough commitments to interpolate
177    let required = previous.required();
178    if commitments.len() < required as usize {
179        return Err(Error::InsufficientDealings);
180    }
181
182    // Precompute Barycentric Weights for all coefficients
183    let indices: Vec<u32> = commitments.keys().cloned().collect();
184    let weights = compute_weights(indices).map_err(|_| Error::PublicKeyInterpolationFailed)?;
185
186    // Perform interpolation over each coefficient using the precomputed weights
187    recover_public_with_weights::<V>(previous, commitments, &weights, threshold, concurrency)
188}