confium_tc/reshare/refresh.rs
1//! Proactive refresh (Herzberg et al. 1995 pattern).
2//!
3//! Periodic share refresh invalidates previously-compromised shares
4//! without changing the public key. Each party generates a random
5//! polynomial with f_i(0) = 0; sum of all contributions at any point
6//! is zero, so adding refresh contributions to existing shares does
7//! not change the aggregate secret.
8
9use crate::reshare::lagrange::FieldElement;
10use serde::{Deserialize, Serialize};
11
12/// Parameters for a refresh round.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct RefreshParams {
15 /// Algorithm.
16 pub algorithm: String,
17 /// Number of parties.
18 pub num_parties: u32,
19 /// Threshold T (refresh preserves threshold).
20 pub threshold: u32,
21}
22
23/// A refresh contribution from party `i` to party `j`.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct RefreshContribution {
26 /// Source party index.
27 pub from_party: u32,
28 /// Destination party index.
29 pub to_party: u32,
30 /// Refresh bytes (the value f_i(j) where f_i is party i's random polynomial).
31 pub bytes: Vec<u8>,
32}
33
34/// Compute the new share given an old share and a set of refresh contributions.
35///
36/// new_share[j] = old_share[j] + sum_i(contribution[i->j])
37///
38/// For real algorithms, this addition happens in the field of the curve
39/// or group. This function provides the byte-level skeleton; algorithm
40/// crates provide field arithmetic.
41pub fn apply_refresh(
42 old_share: &FieldElement,
43 contributions: &[RefreshContribution],
44) -> FieldElement {
45 // Mock: XOR bytes together (real impl uses field addition).
46 let mut new_bytes = old_share.0.clone();
47 for c in contributions {
48 for (i, b) in c.bytes.iter().enumerate() {
49 if i < new_bytes.len() {
50 new_bytes[i] ^= b;
51 }
52 }
53 }
54 FieldElement::new(new_bytes)
55}
56
57/// Verify that a refresh round preserves the aggregate secret.
58///
59/// For correct refresh polynomials: sum_i f_i(0) == 0 for all parties.
60/// This check validates that invariant.
61pub fn verify_refresh_preserves_aggregate(
62 party_zero_contributions: &[RefreshContribution],
63) -> bool {
64 // Mock verification: sum of bytes mod 256 should be zero for any party's
65 // contribution evaluated at 0 (i.e., f_i(0) = 0 means all bytes are zero
66 // when summed appropriately). For real algorithms this uses field math.
67 let mut sum = [0u8; 32];
68 for c in party_zero_contributions {
69 for (i, b) in c.bytes.iter().enumerate() {
70 if i < sum.len() {
71 sum[i] = sum[i].wrapping_add(*b);
72 }
73 }
74 }
75 sum.iter().all(|b| *b == 0)
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn refresh_changes_share() {
84 let old = FieldElement::new(vec![0u8; 32]);
85 let contribs = vec![RefreshContribution {
86 from_party: 0,
87 to_party: 0,
88 bytes: vec![0xFF; 32],
89 }];
90 let new = apply_refresh(&old, &contribs);
91 assert_ne!(new.0, old.0);
92 }
93
94 #[test]
95 fn refresh_preserves_secret_when_balanced() {
96 let contribs = vec![
97 RefreshContribution {
98 from_party: 0,
99 to_party: 0,
100 bytes: vec![0x80; 32],
101 },
102 RefreshContribution {
103 from_party: 1,
104 to_party: 0,
105 bytes: vec![0x80; 32],
106 },
107 ];
108 // 0x80 + 0x80 = 0x00 (mod 256) — mock "preservation"
109 assert!(verify_refresh_preserves_aggregate(&contribs));
110 }
111}