chains_sdk/threshold/frost/
refresh.rs1use super::keygen::{self, KeyPackage, VssCommitments};
17use crate::error::SignerError;
18use k256::{ProjectivePoint, Scalar};
19use zeroize::Zeroizing;
20
21#[derive(Clone)]
23pub struct RefreshPackage {
24 pub sender: u16,
26 pub commitments: VssCommitments,
28 deltas: Vec<Zeroizing<Scalar>>,
30}
31
32impl Drop for RefreshPackage {
33 fn drop(&mut self) {
34 }
36}
37
38pub fn generate_refresh(
49 min_signers: u16,
50 max_signers: u16,
51 my_id: u16,
52) -> Result<RefreshPackage, SignerError> {
53 if min_signers < 2 || max_signers < min_signers {
54 return Err(SignerError::ParseError(
55 "refresh requires min >= 2, max >= min".into(),
56 ));
57 }
58
59 let mut coefficients = vec![Scalar::ZERO]; for _ in 1..min_signers {
63 coefficients.push(keygen::random_scalar()?);
64 }
65
66 let commitment_points = coefficients
68 .iter()
69 .map(|c| (ProjectivePoint::GENERATOR * c).to_affine())
70 .collect();
71
72 let mut deltas = Vec::with_capacity(max_signers as usize);
74 for i in 1..=max_signers {
75 let x = Scalar::from(u64::from(i));
76 let delta = keygen::polynomial_evaluate(&x, &coefficients);
77 deltas.push(Zeroizing::new(delta));
78 }
79
80 Ok(RefreshPackage {
81 sender: my_id,
82 commitments: VssCommitments {
83 commitments: commitment_points,
84 },
85 deltas,
86 })
87}
88
89pub fn apply_refresh(
101 key_package: &KeyPackage,
102 refresh_packages: &[RefreshPackage],
103) -> Result<KeyPackage, SignerError> {
104 let my_idx = match key_package.identifier.checked_sub(1) {
105 Some(idx) => idx as usize,
106 None => {
107 return Err(SignerError::ParseError(
108 "invalid key package identifier: must be >= 1".into(),
109 ))
110 }
111 };
112
113 for pkg in refresh_packages {
115 if my_idx >= pkg.deltas.len() {
116 return Err(SignerError::ParseError(
117 "refresh package missing delta".into(),
118 ));
119 }
120 if pkg.commitments.commitments.is_empty() {
121 return Err(SignerError::ParseError(
122 "refresh package has empty commitments".into(),
123 ));
124 }
125 let c0 = pkg.commitments.commitments[0];
127 if ProjectivePoint::from(c0) != ProjectivePoint::IDENTITY {
128 return Err(SignerError::SigningFailed(format!(
129 "refresh package from {} has non-zero constant term",
130 pkg.sender
131 )));
132 }
133 let valid = pkg
135 .commitments
136 .verify_share(key_package.identifier, &pkg.deltas[my_idx]);
137 if !valid {
138 return Err(SignerError::SigningFailed(format!(
139 "VSS verification failed for refresh from participant {}",
140 pkg.sender
141 )));
142 }
143 }
144
145 let mut delta_sum = Scalar::ZERO;
147 for pkg in refresh_packages {
148 delta_sum += pkg.deltas[my_idx].as_ref();
149 }
150
151 let new_share = *key_package.secret_share() + delta_sum;
153
154 Ok(KeyPackage {
155 identifier: key_package.identifier,
156 secret_share: Zeroizing::new(new_share),
157 group_public_key: key_package.group_public_key,
158 min_participants: key_package.min_participants,
159 max_participants: key_package.max_participants,
160 })
161}
162
163#[must_use]
168pub fn verify_refresh_package(pkg: &RefreshPackage) -> bool {
169 if pkg.commitments.commitments.is_empty() {
170 return false;
171 }
172 ProjectivePoint::from(pkg.commitments.commitments[0]) == ProjectivePoint::IDENTITY
174}
175
176#[cfg(test)]
181#[allow(clippy::unwrap_used, clippy::expect_used)]
182mod tests {
183 use super::*;
184 use crate::threshold::frost::signing;
185
186 #[test]
187 fn test_refresh_preserves_group_key() {
188 let secret = [0x42u8; 32];
189 let kgen = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
190 let original_gpk = kgen.group_public_key;
191
192 let r1 = generate_refresh(2, 3, 1).unwrap();
194 let r2 = generate_refresh(2, 3, 2).unwrap();
195 let r3 = generate_refresh(2, 3, 3).unwrap();
196 let refresh_pkgs = vec![r1, r2, r3];
197
198 let new_kp1 = apply_refresh(&kgen.key_packages[0], &refresh_pkgs).unwrap();
200 let new_kp2 = apply_refresh(&kgen.key_packages[1], &refresh_pkgs).unwrap();
201 let _new_kp3 = apply_refresh(&kgen.key_packages[2], &refresh_pkgs).unwrap();
202
203 assert_eq!(new_kp1.group_public_key, original_gpk);
205 assert_eq!(new_kp2.group_public_key, original_gpk);
206
207 assert_ne!(
209 *new_kp1.secret_share(),
210 *kgen.key_packages[0].secret_share()
211 );
212 }
213
214 #[test]
215 fn test_refreshed_shares_can_sign() {
216 let secret = [0x42u8; 32];
217 let kgen = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
218 let group_pk = kgen.group_public_key;
219
220 let r1 = generate_refresh(2, 3, 1).unwrap();
221 let r2 = generate_refresh(2, 3, 2).unwrap();
222 let r3 = generate_refresh(2, 3, 3).unwrap();
223 let refresh_pkgs = vec![r1, r2, r3];
224
225 let new_kp1 = apply_refresh(&kgen.key_packages[0], &refresh_pkgs).unwrap();
226 let new_kp2 = apply_refresh(&kgen.key_packages[1], &refresh_pkgs).unwrap();
227
228 let msg = b"signing with refreshed shares";
230 let n1 = signing::commit(&new_kp1).unwrap();
231 let n2 = signing::commit(&new_kp2).unwrap();
232 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
233 let s1 = signing::sign(&new_kp1, n1, &comms, msg).unwrap();
234 let s2 = signing::sign(&new_kp2, n2, &comms, msg).unwrap();
235 let sig = signing::aggregate(&comms, &[s1, s2], &group_pk, msg).unwrap();
236
237 assert!(
238 signing::verify(&sig, &group_pk, msg).unwrap(),
239 "refreshed shares must produce valid signatures"
240 );
241 }
242
243 #[test]
244 fn test_refresh_package_verification() {
245 let pkg = generate_refresh(2, 3, 1).unwrap();
246 assert!(verify_refresh_package(&pkg));
247 }
248
249 #[test]
250 fn test_refresh_invalid_params() {
251 assert!(generate_refresh(1, 3, 1).is_err());
252 assert!(generate_refresh(4, 3, 1).is_err());
253 }
254
255 #[test]
256 fn test_multiple_refreshes() {
257 let secret = [0x42u8; 32];
258 let kgen = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
259 let original_gpk = kgen.group_public_key;
260
261 let r1 = vec![
263 generate_refresh(2, 3, 1).unwrap(),
264 generate_refresh(2, 3, 2).unwrap(),
265 generate_refresh(2, 3, 3).unwrap(),
266 ];
267 let kp1_r1 = apply_refresh(&kgen.key_packages[0], &r1).unwrap();
268 let kp2_r1 = apply_refresh(&kgen.key_packages[1], &r1).unwrap();
269
270 let r2 = vec![
272 generate_refresh(2, 3, 1).unwrap(),
273 generate_refresh(2, 3, 2).unwrap(),
274 generate_refresh(2, 3, 3).unwrap(),
275 ];
276 let kp1_r2 = apply_refresh(&kp1_r1, &r2).unwrap();
277 let kp2_r2 = apply_refresh(&kp2_r1, &r2).unwrap();
278
279 assert_eq!(kp1_r2.group_public_key, original_gpk);
281
282 let msg = b"after two refreshes";
284 let n1 = signing::commit(&kp1_r2).unwrap();
285 let n2 = signing::commit(&kp2_r2).unwrap();
286 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
287 let s1 = signing::sign(&kp1_r2, n1, &comms, msg).unwrap();
288 let s2 = signing::sign(&kp2_r2, n2, &comms, msg).unwrap();
289 let sig = signing::aggregate(&comms, &[s1, s2], &original_gpk, msg).unwrap();
290 assert!(signing::verify(&sig, &original_gpk, msg).unwrap());
291 }
292}