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 = (key_package.identifier - 1) as usize;
105
106 for pkg in refresh_packages {
108 if my_idx >= pkg.deltas.len() {
109 return Err(SignerError::ParseError(
110 "refresh package missing delta".into(),
111 ));
112 }
113 let c0 = pkg.commitments.commitments[0];
115 if ProjectivePoint::from(c0) != ProjectivePoint::IDENTITY {
116 return Err(SignerError::SigningFailed(format!(
117 "refresh package from {} has non-zero constant term",
118 pkg.sender
119 )));
120 }
121 let valid = pkg
123 .commitments
124 .verify_share(key_package.identifier, &pkg.deltas[my_idx]);
125 if !valid {
126 return Err(SignerError::SigningFailed(format!(
127 "VSS verification failed for refresh from participant {}",
128 pkg.sender
129 )));
130 }
131 }
132
133 let mut delta_sum = Scalar::ZERO;
135 for pkg in refresh_packages {
136 delta_sum += pkg.deltas[my_idx].as_ref();
137 }
138
139 let new_share = *key_package.secret_share() + delta_sum;
141
142 Ok(KeyPackage {
143 identifier: key_package.identifier,
144 secret_share: Zeroizing::new(new_share),
145 group_public_key: key_package.group_public_key,
146 min_participants: key_package.min_participants,
147 max_participants: key_package.max_participants,
148 })
149}
150
151#[must_use]
156pub fn verify_refresh_package(pkg: &RefreshPackage) -> bool {
157 if pkg.commitments.commitments.is_empty() {
158 return false;
159 }
160 ProjectivePoint::from(pkg.commitments.commitments[0]) == ProjectivePoint::IDENTITY
162}
163
164#[cfg(test)]
169#[allow(clippy::unwrap_used, clippy::expect_used)]
170mod tests {
171 use super::*;
172 use crate::threshold::frost::signing;
173 use k256::elliptic_curve::sec1::ToEncodedPoint;
174
175 #[test]
176 fn test_refresh_preserves_group_key() {
177 let secret = [0x42u8; 32];
178 let kgen = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
179 let original_gpk = kgen.group_public_key;
180
181 let r1 = generate_refresh(2, 3, 1).unwrap();
183 let r2 = generate_refresh(2, 3, 2).unwrap();
184 let r3 = generate_refresh(2, 3, 3).unwrap();
185 let refresh_pkgs = vec![r1, r2, r3];
186
187 let new_kp1 = apply_refresh(&kgen.key_packages[0], &refresh_pkgs).unwrap();
189 let new_kp2 = apply_refresh(&kgen.key_packages[1], &refresh_pkgs).unwrap();
190 let new_kp3 = apply_refresh(&kgen.key_packages[2], &refresh_pkgs).unwrap();
191
192 assert_eq!(new_kp1.group_public_key, original_gpk);
194 assert_eq!(new_kp2.group_public_key, original_gpk);
195
196 assert_ne!(
198 *new_kp1.secret_share(),
199 *kgen.key_packages[0].secret_share()
200 );
201 }
202
203 #[test]
204 fn test_refreshed_shares_can_sign() {
205 let secret = [0x42u8; 32];
206 let kgen = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
207 let group_pk = kgen.group_public_key;
208
209 let r1 = generate_refresh(2, 3, 1).unwrap();
210 let r2 = generate_refresh(2, 3, 2).unwrap();
211 let r3 = generate_refresh(2, 3, 3).unwrap();
212 let refresh_pkgs = vec![r1, r2, r3];
213
214 let new_kp1 = apply_refresh(&kgen.key_packages[0], &refresh_pkgs).unwrap();
215 let new_kp2 = apply_refresh(&kgen.key_packages[1], &refresh_pkgs).unwrap();
216
217 let msg = b"signing with refreshed shares";
219 let n1 = signing::commit(&new_kp1).unwrap();
220 let n2 = signing::commit(&new_kp2).unwrap();
221 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
222 let s1 = signing::sign(&new_kp1, n1, &comms, msg).unwrap();
223 let s2 = signing::sign(&new_kp2, n2, &comms, msg).unwrap();
224 let sig = signing::aggregate(&comms, &[s1, s2], &group_pk, msg).unwrap();
225
226 assert!(
227 signing::verify(&sig, &group_pk, msg).unwrap(),
228 "refreshed shares must produce valid signatures"
229 );
230 }
231
232 #[test]
233 fn test_refresh_package_verification() {
234 let pkg = generate_refresh(2, 3, 1).unwrap();
235 assert!(verify_refresh_package(&pkg));
236 }
237
238 #[test]
239 fn test_refresh_invalid_params() {
240 assert!(generate_refresh(1, 3, 1).is_err());
241 assert!(generate_refresh(4, 3, 1).is_err());
242 }
243
244 #[test]
245 fn test_multiple_refreshes() {
246 let secret = [0x42u8; 32];
247 let kgen = keygen::trusted_dealer_keygen(&secret, 2, 3).unwrap();
248 let original_gpk = kgen.group_public_key;
249
250 let r1 = vec![
252 generate_refresh(2, 3, 1).unwrap(),
253 generate_refresh(2, 3, 2).unwrap(),
254 generate_refresh(2, 3, 3).unwrap(),
255 ];
256 let kp1_r1 = apply_refresh(&kgen.key_packages[0], &r1).unwrap();
257 let kp2_r1 = apply_refresh(&kgen.key_packages[1], &r1).unwrap();
258
259 let r2 = vec![
261 generate_refresh(2, 3, 1).unwrap(),
262 generate_refresh(2, 3, 2).unwrap(),
263 generate_refresh(2, 3, 3).unwrap(),
264 ];
265 let kp1_r2 = apply_refresh(&kp1_r1, &r2).unwrap();
266 let kp2_r2 = apply_refresh(&kp2_r1, &r2).unwrap();
267
268 assert_eq!(kp1_r2.group_public_key, original_gpk);
270
271 let msg = b"after two refreshes";
273 let n1 = signing::commit(&kp1_r2).unwrap();
274 let n2 = signing::commit(&kp2_r2).unwrap();
275 let comms = vec![n1.commitments.clone(), n2.commitments.clone()];
276 let s1 = signing::sign(&kp1_r2, n1, &comms, msg).unwrap();
277 let s2 = signing::sign(&kp2_r2, n2, &comms, msg).unwrap();
278 let sig = signing::aggregate(&comms, &[s1, s2], &original_gpk, msg).unwrap();
279 assert!(signing::verify(&sig, &original_gpk, msg).unwrap());
280 }
281}