chains_sdk/threshold/frost/
keygen.rs1use crate::error::SignerError;
8use k256::elliptic_curve::ops::Reduce;
9use k256::{AffinePoint, ProjectivePoint, Scalar};
10use zeroize::Zeroizing;
11
12#[derive(Clone)]
14pub struct KeyPackage {
15 pub identifier: u16,
17 pub(crate) secret_share: Zeroizing<Scalar>,
19 pub group_public_key: AffinePoint,
21 pub min_participants: u16,
23 pub max_participants: u16,
25}
26
27impl Drop for KeyPackage {
28 fn drop(&mut self) {
29 }
31}
32
33impl KeyPackage {
34 pub fn secret_share(&self) -> &Scalar {
36 &self.secret_share
37 }
38
39 pub fn public_key(&self) -> AffinePoint {
41 (ProjectivePoint::GENERATOR * *self.secret_share).to_affine()
42 }
43
44 pub fn secret_share_bytes(&self) -> Zeroizing<[u8; 32]> {
46 let mut bytes = Zeroizing::new([0u8; 32]);
47 bytes.copy_from_slice(&self.secret_share.to_bytes());
48 bytes
49 }
50}
51
52#[derive(Clone, Debug)]
57pub struct VssCommitments {
58 pub commitments: Vec<AffinePoint>,
60}
61
62impl VssCommitments {
63 pub fn verify_share(&self, identifier: u16, share: &Scalar) -> bool {
67 if identifier == 0 || self.commitments.is_empty() {
68 return false;
69 }
70
71 let i_scalar = Scalar::from(u64::from(identifier));
72 let lhs = ProjectivePoint::GENERATOR * *share;
73
74 let mut rhs = ProjectivePoint::IDENTITY;
76 for ck in self.commitments.iter().rev() {
77 rhs = rhs * i_scalar + ProjectivePoint::from(*ck);
78 }
79
80 lhs == rhs
81 }
82}
83
84pub struct KeyGenOutput {
86 pub key_packages: Vec<KeyPackage>,
88 pub vss_commitments: VssCommitments,
90 pub group_public_key: AffinePoint,
92}
93
94pub fn trusted_dealer_keygen(
108 group_secret: &[u8; 32],
109 min_participants: u16,
110 max_participants: u16,
111) -> Result<KeyGenOutput, SignerError> {
112 if min_participants < 2 {
113 return Err(SignerError::InvalidPrivateKey(
114 "min_participants must be >= 2".into(),
115 ));
116 }
117 if max_participants < min_participants {
118 return Err(SignerError::InvalidPrivateKey(
119 "max_participants must be >= min_participants".into(),
120 ));
121 }
122
123 let s = scalar_from_bytes(group_secret)?;
124 let group_public_key = (ProjectivePoint::GENERATOR * s).to_affine();
125
126 let mut coefficients = Vec::with_capacity(min_participants as usize);
128 coefficients.push(s); for _ in 1..min_participants {
131 let coeff = random_scalar()?;
132 coefficients.push(coeff);
133 }
134
135 let vss_commitments = VssCommitments {
137 commitments: coefficients
138 .iter()
139 .map(|a| (ProjectivePoint::GENERATOR * *a).to_affine())
140 .collect(),
141 };
142
143 let mut key_packages = Vec::with_capacity(max_participants as usize);
145 for i in 1..=max_participants {
146 let x = Scalar::from(u64::from(i));
147 let share = polynomial_evaluate(&x, &coefficients);
148
149 key_packages.push(KeyPackage {
150 identifier: i,
151 secret_share: Zeroizing::new(share),
152 group_public_key,
153 min_participants,
154 max_participants,
155 });
156 }
157
158 for c in coefficients.iter_mut() {
160 *c = Scalar::ZERO;
161 }
162
163 Ok(KeyGenOutput {
164 key_packages,
165 vss_commitments,
166 group_public_key,
167 })
168}
169
170pub fn polynomial_evaluate(x: &Scalar, coefficients: &[Scalar]) -> Scalar {
174 let mut result = Scalar::ZERO;
175 for c in coefficients.iter().rev() {
176 result = result * x + c;
177 }
178 result
179}
180
181pub fn derive_interpolating_value(
186 x_i: &Scalar,
187 participant_identifiers: &[Scalar],
188) -> Result<Scalar, SignerError> {
189 let mut numerator = Scalar::ONE;
190 let mut denominator = Scalar::ONE;
191
192 for x_j in participant_identifiers {
193 if x_j == x_i {
194 continue;
195 }
196 numerator *= x_j;
197 denominator *= *x_j - x_i;
198 }
199
200 if denominator == Scalar::ZERO {
202 return Err(SignerError::InvalidPrivateKey(
203 "duplicate participant identifiers".into(),
204 ));
205 }
206
207 Ok(numerator * denominator.invert().unwrap_or(Scalar::ZERO))
208}
209
210pub fn scalar_from_bytes(bytes: &[u8; 32]) -> Result<Scalar, SignerError> {
212 let wide = k256::U256::from_be_slice(bytes);
213 let scalar = <Scalar as Reduce<k256::U256>>::reduce(wide);
214 if scalar == Scalar::ZERO {
215 return Err(SignerError::InvalidPrivateKey("scalar is zero".into()));
216 }
217 Ok(scalar)
218}
219
220pub fn random_scalar() -> Result<Scalar, SignerError> {
222 let mut bytes = [0u8; 32];
223 crate::security::secure_random(&mut bytes)?;
224 let wide = k256::U256::from_be_slice(&bytes);
225 let scalar = <Scalar as Reduce<k256::U256>>::reduce(wide);
226 if scalar == Scalar::ZERO {
227 return random_scalar();
229 }
230 Ok(scalar)
231}
232
233#[cfg(test)]
234#[allow(clippy::unwrap_used, clippy::expect_used)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_polynomial_evaluate() {
240 let coeffs = [Scalar::from(3u64), Scalar::from(2u64), Scalar::from(1u64)];
242 let result = polynomial_evaluate(&Scalar::from(2u64), &coeffs);
243 assert_eq!(result, Scalar::from(11u64));
244 }
245
246 #[test]
247 fn test_trusted_dealer_keygen_2_of_3() {
248 let secret = [0x42u8; 32];
249 let out = trusted_dealer_keygen(&secret, 2, 3).unwrap();
250 assert_eq!(out.key_packages.len(), 3);
251 assert_eq!(out.vss_commitments.commitments.len(), 2);
252
253 for pkg in &out.key_packages {
255 assert!(out
256 .vss_commitments
257 .verify_share(pkg.identifier, pkg.secret_share()));
258 }
259 }
260
261 #[test]
262 fn test_share_reconstruction_lagrange() {
263 let secret = [0x42u8; 32];
264 let out = trusted_dealer_keygen(&secret, 2, 3).unwrap();
265
266 let ids = [Scalar::from(1u64), Scalar::from(3u64)];
268 let shares = [
269 *out.key_packages[0].secret_share(),
270 *out.key_packages[2].secret_share(),
271 ];
272
273 let mut reconstructed = Scalar::ZERO;
274 for (i, share) in shares.iter().enumerate() {
275 let lambda = derive_interpolating_value(&ids[i], &ids).unwrap();
276 reconstructed += lambda * share;
277 }
278
279 let original = scalar_from_bytes(&[0x42u8; 32]).unwrap();
280 assert_eq!(reconstructed, original);
281 }
282
283 #[test]
284 fn test_invalid_params() {
285 let secret = [0x42u8; 32];
286 assert!(trusted_dealer_keygen(&secret, 1, 3).is_err()); assert!(trusted_dealer_keygen(&secret, 3, 2).is_err()); }
289
290 #[test]
291 fn test_zero_secret_rejected() {
292 assert!(trusted_dealer_keygen(&[0u8; 32], 2, 3).is_err());
293 }
294
295 #[test]
296 fn test_all_shares_reconstruct_to_secret() {
297 let secret = [0x42u8; 32];
299 let out = trusted_dealer_keygen(&secret, 2, 3).unwrap();
300
301 let ids: Vec<Scalar> = (1u64..=3).map(Scalar::from).collect();
302 let mut reconstructed = Scalar::ZERO;
303 for (i, pkg) in out.key_packages.iter().enumerate() {
304 let lambda = derive_interpolating_value(&ids[i], &ids).unwrap();
305 reconstructed += lambda * *pkg.secret_share();
306 }
307 let original = scalar_from_bytes(&[0x42u8; 32]).unwrap();
308 assert_eq!(reconstructed, original);
309 }
310
311 #[test]
312 fn test_public_key_share() {
313 let secret = [0x42u8; 32];
314 let out = trusted_dealer_keygen(&secret, 2, 3).unwrap();
315 for pkg in &out.key_packages {
316 let pk = pkg.public_key();
317 assert_ne!(pk, AffinePoint::IDENTITY);
319 }
320 }
321
322 #[test]
323 fn test_keygen_3_of_5() {
324 let secret = [0x11u8; 32];
325 let out = trusted_dealer_keygen(&secret, 3, 5).unwrap();
326 assert_eq!(out.key_packages.len(), 5);
327 assert_eq!(out.vss_commitments.commitments.len(), 3); for pkg in &out.key_packages {
329 assert!(out
330 .vss_commitments
331 .verify_share(pkg.identifier, pkg.secret_share()));
332 }
333 }
334
335 #[test]
336 fn test_vss_verify_share_zero_identifier_rejected() {
337 let secret = [0x42u8; 32];
338 let out = trusted_dealer_keygen(&secret, 2, 3).unwrap();
339 assert!(!out
341 .vss_commitments
342 .verify_share(0, out.key_packages[0].secret_share()));
343 }
344}