confium_crypto_vss/
pedersen_vss.rs1use getrandom::SysRng;
4use p256::elliptic_curve::rand_core::UnwrapErr;
5use p256::elliptic_curve::sec1::{FromSec1Point, ToSec1Point};
6use p256::elliptic_curve::{Field, PrimeField};
7use p256::{AffinePoint, FieldBytes, ProjectivePoint, Scalar};
8use serde::{Deserialize, Serialize};
9use sha2::{Digest as _, Sha256};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct PedersenCommitment {
14 pub c_points_hex: Vec<String>,
15 pub d_points_hex: Vec<String>,
16}
17
18#[derive(Debug, Clone)]
21pub struct PedersenShare {
22 pub party_idx: u32,
23 pub value: Scalar,
24 pub randomness: Scalar,
25}
26
27impl Drop for PedersenShare {
28 fn drop(&mut self) {
29 use zeroize::Zeroize;
30 self.value.zeroize();
31 self.randomness.zeroize();
32 }
33}
34
35#[derive(Debug, Clone)]
37pub struct PedersenParams {
38 pub h: AffinePoint,
39}
40
41impl PedersenParams {
42 pub fn generate() -> Self {
44 let alpha = Scalar::random(&mut UnwrapErr(SysRng));
45 let h = (ProjectivePoint::GENERATOR * alpha).to_affine();
46 Self { h }
47 }
48}
49
50pub fn deal(
53 secret: &Scalar,
54 threshold: u32,
55 party_count: u32,
56 params: &PedersenParams,
57) -> (PedersenCommitment, Vec<PedersenShare>) {
58 let f_coeffs: Vec<Scalar> = (0..threshold)
60 .map(|i| {
61 if i == 0 {
62 *secret
63 } else {
64 Scalar::random(&mut UnwrapErr(SysRng))
65 }
66 })
67 .collect();
68 let r_coeffs: Vec<Scalar> = (0..threshold)
69 .map(|_| Scalar::random(&mut UnwrapErr(SysRng)))
70 .collect();
71
72 let mut c_points = Vec::with_capacity(threshold as usize);
74 let mut d_points = Vec::with_capacity(threshold as usize);
75 for i in 0..threshold as usize {
76 let g_fi = ProjectivePoint::GENERATOR * f_coeffs[i];
77 let h_ri = ProjectivePoint::from(params.h) * r_coeffs[i];
78 let c_i = (g_fi + h_ri).to_affine();
79 let d_i = h_ri.to_affine();
80 c_points.push(encode_point(&c_i));
81 d_points.push(encode_point(&d_i));
82 }
83
84 let shares: Vec<PedersenShare> = (1..=party_count)
86 .map(|j| PedersenShare {
87 party_idx: j,
88 value: eval_poly(&f_coeffs, j),
89 randomness: eval_poly(&r_coeffs, j),
90 })
91 .collect();
92
93 (
94 PedersenCommitment {
95 c_points_hex: c_points,
96 d_points_hex: d_points,
97 },
98 shares,
99 )
100}
101
102pub fn verify_share(
105 share: &PedersenShare,
106 commitment: &PedersenCommitment,
107 params: &PedersenParams,
108) -> bool {
109 let lhs = (ProjectivePoint::GENERATOR * share.value
111 + ProjectivePoint::from(params.h) * share.randomness)
112 .to_affine();
113
114 let mut rhs = ProjectivePoint::IDENTITY;
116 let j = share.party_idx;
117 let mut j_pow = Scalar::ONE;
118 for i in 0..commitment.c_points_hex.len() {
119 if let Some(c_i) = decode_point(&commitment.c_points_hex[i]) {
120 rhs += ProjectivePoint::from(c_i) * j_pow;
121 }
122 let j_scalar = u32_to_scalar(j);
123 j_pow *= j_scalar;
124 }
125
126 lhs == rhs.to_affine()
127}
128
129pub fn joint_public_key(commitment: &PedersenCommitment) -> Option<AffinePoint> {
131 if commitment.c_points_hex.is_empty() {
132 return None;
133 }
134 if commitment.d_points_hex.is_empty() {
138 return None;
139 }
140 let c0 = decode_point(&commitment.c_points_hex[0])?;
141 let d0 = decode_point(&commitment.d_points_hex[0])?;
142 let pk = ProjectivePoint::from(c0) - ProjectivePoint::from(d0);
143 Some(pk.to_affine())
144}
145
146fn eval_poly(coeffs: &[Scalar], x: u32) -> Scalar {
147 let x_scalar = u32_to_scalar(x);
148 let mut result = Scalar::ZERO;
149 let mut x_pow = Scalar::ONE;
150 for c in coeffs {
151 result += c * &x_pow;
152 x_pow *= x_scalar;
153 }
154 result
155}
156
157fn u32_to_scalar(v: u32) -> Scalar {
158 let mut arr = [0u8; 32];
159 arr[28..32].copy_from_slice(&v.to_be_bytes());
160 loop {
161 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(arr))) {
162 return s;
163 }
164 arr = {
165 let mut h = Sha256::new();
166 h.update(b"confium-scalar-reduce-v1");
167 h.update(arr);
168 h.finalize().into()
169 };
170 }
171}
172
173fn encode_point(p: &AffinePoint) -> String {
174 hex::encode(p.to_sec1_point(true).as_bytes())
175}
176
177fn decode_point(hex_str: &str) -> Option<AffinePoint> {
178 let bytes = hex::decode(hex_str).ok()?;
179 let encoded =
180 p256::elliptic_curve::sec1::Sec1Point::<p256::NistP256>::from_bytes(&bytes).ok()?;
181 Option::<AffinePoint>::from(AffinePoint::from_sec1_point(&encoded))
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn deal_and_verify() {
190 let params = PedersenParams::generate();
191 let secret = Scalar::random(&mut UnwrapErr(SysRng));
192 let (commitment, shares) = deal(&secret, 3, 5, ¶ms);
193 for share in &shares {
194 assert!(
195 verify_share(share, &commitment, ¶ms),
196 "party {}",
197 share.party_idx
198 );
199 }
200 }
201
202 #[test]
203 fn tampered_share_rejected() {
204 let params = PedersenParams::generate();
205 let secret = Scalar::random(&mut UnwrapErr(SysRng));
206 let (commitment, mut shares) = deal(&secret, 2, 3, ¶ms);
207 shares[0].value += Scalar::ONE;
208 assert!(!verify_share(&shares[0], &commitment, ¶ms));
209 }
210
211 #[test]
212 fn joint_public_key_extracted() {
213 let params = PedersenParams::generate();
214 let secret = Scalar::random(&mut UnwrapErr(SysRng));
215 let (commitment, _) = deal(&secret, 2, 3, ¶ms);
216 let pk = joint_public_key(&commitment).unwrap();
217 let expected = (ProjectivePoint::GENERATOR * secret).to_affine();
219 assert_eq!(pk, expected);
220 }
221
222 #[test]
223 fn different_secrets_different_commitments() {
224 let params = PedersenParams::generate();
225 let s1 = Scalar::random(&mut UnwrapErr(SysRng));
226 let s2 = Scalar::random(&mut UnwrapErr(SysRng));
227 let (c1, _) = deal(&s1, 2, 3, ¶ms);
228 let (c2, _) = deal(&s2, 2, 3, ¶ms);
229 assert_ne!(c1.c_points_hex[0], c2.c_points_hex[0]);
230 }
231
232 #[test]
233 fn threshold_one_works() {
234 let params = PedersenParams::generate();
235 let secret = Scalar::random(&mut UnwrapErr(SysRng));
236 let (commitment, shares) = deal(&secret, 1, 3, ¶ms);
237 assert_eq!(shares.len(), 3);
238 for share in &shares {
239 assert!(verify_share(share, &commitment, ¶ms));
240 }
241 }
242}