confium_tc_frost_ed25519/
polynomial.rs1use curve25519_dalek::scalar::Scalar;
11use curve25519_dalek::traits::Identity;
12
13use crate::group;
14
15pub fn lagrange_coefficient(i: u32, participants: &[u32]) -> Scalar {
29 let mut num = Scalar::ONE;
30 let mut den = Scalar::ONE;
31 let i_scalar = Scalar::from(i);
32 for &j in participants {
33 if j == i {
34 continue;
35 }
36 let j_scalar = Scalar::from(j);
37 num *= j_scalar;
38 den *= j_scalar - i_scalar;
39 }
40 num * den.invert()
41}
42
43pub struct Polynomial {
46 coeff: Vec<Scalar>,
47}
48
49impl Polynomial {
50 pub fn from_coefficients(coeff: Vec<Scalar>) -> Self {
53 debug_assert!(!coeff.is_empty(), "polynomial must have at least one term");
54 Polynomial { coeff }
55 }
56
57 pub fn degree_plus_one(&self) -> usize {
60 self.coeff.len()
61 }
62
63 pub fn constant(&self) -> Scalar {
65 self.coeff[0]
66 }
67
68 pub fn coefficients(&self) -> &[Scalar] {
70 &self.coeff
71 }
72
73 pub fn evaluate(&self, x: u32) -> Scalar {
75 let x_scalar = Scalar::from(x);
76 let mut acc = *self.coeff.last().expect("non-empty polynomial");
77 for c in self.coeff[..self.coeff.len() - 1].iter().rev() {
78 acc = acc * x_scalar + c;
79 }
80 acc
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct CommitmentList {
91 commits: Vec<[u8; group::ELEMENT_BYTES]>,
93}
94
95impl CommitmentList {
96 pub fn from_bytes(commits: Vec<[u8; group::ELEMENT_BYTES]>) -> Self {
98 CommitmentList { commits }
99 }
100
101 pub fn commit(poly: &Polynomial) -> Self {
103 let commits = poly
104 .coefficients()
105 .iter()
106 .map(|a| group::point_to_bytes(&group::mul_base(a)))
107 .collect();
108 CommitmentList { commits }
109 }
110
111 pub fn public_key_bytes(&self) -> [u8; group::ELEMENT_BYTES] {
113 self.commits[0]
114 }
115
116 pub fn as_bytes(&self) -> &[[u8; group::ELEMENT_BYTES]] {
118 &self.commits
119 }
120
121 pub fn verify_share(&self, participant: u32, share: &Scalar) -> bool {
124 let lhs = group::mul_base(share);
125 let i_scalar = Scalar::from(participant);
128 let mut acc = curve25519_dalek::edwards::EdwardsPoint::identity();
129 for c_bytes in self.commits.iter().rev() {
130 let c = match group::point_from_bytes(c_bytes) {
131 Some(p) => p,
132 None => return false,
133 };
134 acc = acc * i_scalar + c;
135 }
136 acc == lhs
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use curve25519_dalek::scalar::Scalar;
144
145 #[test]
146 fn lagrange_of_single_party_is_one() {
147 let lambda = lagrange_coefficient(1, &[1]);
148 assert_eq!(lambda, Scalar::ONE);
149 }
150
151 #[test]
152 fn lagrange_coefficients_recover_secret() {
153 let a0 = Scalar::from(7u64);
155 let a1 = Scalar::from(3u64);
156 let poly = Polynomial::from_coefficients(vec![a0, a1]);
157 let s1 = poly.evaluate(1);
158 let s2 = poly.evaluate(2);
159 assert_eq!(s1, Scalar::from(10u64));
160 assert_eq!(s2, Scalar::from(13u64));
161 let l1 = lagrange_coefficient(1, &[1, 2]);
163 let l2 = lagrange_coefficient(2, &[1, 2]);
164 let recovered = s1 * l1 + s2 * l2;
165 assert_eq!(recovered, a0);
166 }
167
168 #[test]
169 fn polynomial_evaluate_matches_definition() {
170 let coeff = vec![Scalar::from(1u64), Scalar::from(2u64), Scalar::from(3u64)];
171 let poly = Polynomial::from_coefficients(coeff);
172 let got = poly.evaluate(5);
174 let want = Scalar::from(86u64);
175 assert_eq!(got, want);
176 }
177
178 #[test]
179 fn commitment_list_verifies_valid_share() {
180 let poly = Polynomial::from_coefficients(vec![
181 Scalar::from(11u64),
182 Scalar::from(7u64),
183 Scalar::from(3u64),
184 ]);
185 let cl = CommitmentList::commit(&poly);
186 let s2 = poly.evaluate(2);
188 assert!(cl.verify_share(2, &s2));
189 let bad = s2 + Scalar::ONE;
191 assert!(!cl.verify_share(2, &bad));
192 }
193
194 #[test]
195 fn commitment_list_public_key_matches_constant_term() {
196 let poly = Polynomial::from_coefficients(vec![Scalar::from(42u64), Scalar::from(9u64)]);
197 let cl = CommitmentList::commit(&poly);
198 let want = group::point_to_bytes(&group::mul_base(&Scalar::from(42u64)));
199 assert_eq!(cl.public_key_bytes(), want);
200 }
201}