1use elliptic_curve::Field;
11use elliptic_curve::rand_core::CryptoRng;
12use elliptic_curve::sec1::ToSec1Point;
13use p256::{AffinePoint, ProjectivePoint, Scalar};
14
15#[derive(Clone, Debug)]
17pub struct FeldmanVss {
18 pub commitments: Vec<AffinePoint>,
19 pub shares: Vec<Scalar>,
20 pub secret: Scalar,
21}
22
23impl FeldmanVss {
24 pub fn deal(rng: &mut impl CryptoRng, n: usize, t: usize) -> Self {
25 debug_assert!(t >= 1 && t <= n);
26 let mut coeffs: Vec<Scalar> = (0..t).map(|_| Scalar::random(&mut *rng)).collect();
27 let secret = coeffs[0];
28 let g = ProjectivePoint::GENERATOR;
29 let commitments: Vec<AffinePoint> = coeffs.iter().map(|a| (g * a).to_affine()).collect();
30 let shares: Vec<Scalar> = (1..=n as u64)
31 .map(|i| {
32 let x = Scalar::from(i);
33 let mut acc = Scalar::ZERO;
34 for &a in coeffs.iter().rev() {
35 acc = acc * x + a;
36 }
37 acc
38 })
39 .collect();
40 coeffs.fill(Scalar::ZERO);
41 FeldmanVss {
42 commitments,
43 shares,
44 secret,
45 }
46 }
47
48 pub fn verify_share(commitments: &[AffinePoint], party_idx_1based: u64, share: Scalar) -> bool {
49 if commitments.is_empty() {
50 return false;
51 }
52 let g = ProjectivePoint::GENERATOR;
53 let lhs = g * share;
54 let i_scalar = Scalar::from(party_idx_1based);
55 let mut rhs = ProjectivePoint::IDENTITY;
56 let mut i_pow = Scalar::ONE;
57 for c in commitments {
58 rhs += ProjectivePoint::from(*c) * i_pow;
59 i_pow *= i_scalar;
60 }
61 lhs == rhs
62 }
63
64 pub fn encode_commitments(commitments: &[AffinePoint]) -> Vec<u8> {
65 let mut out = Vec::with_capacity(commitments.len() * 33);
66 for c in commitments {
67 out.extend_from_slice(c.to_sec1_point(true).as_bytes());
68 }
69 out
70 }
71
72 pub fn decode_commitments(bytes: &[u8]) -> Option<Vec<AffinePoint>> {
73 if bytes.len() % 33 != 0 {
74 return None;
75 }
76 use elliptic_curve::point::AffineCoordinates;
77 use elliptic_curve::sec1::FromSec1Point;
78 use p256::NistP256;
79 let mut out = Vec::with_capacity(bytes.len() / 33);
80 for chunk in bytes.chunks_exact(33) {
81 let enc = elliptic_curve::sec1::Sec1Point::<NistP256>::from_bytes(chunk).ok()?;
82 let pt: AffinePoint = Option::from(AffinePoint::from_sec1_point(&enc))?;
83 let _ = pt.x();
84 out.push(pt);
85 }
86 Some(out)
87 }
88
89 pub fn public_key(&self) -> AffinePoint {
90 self.commitments[0]
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use elliptic_curve::rand_core::UnwrapErr;
98 use getrandom::SysRng;
99
100 #[test]
101 fn feldman_share_verifies() {
102 let vss = FeldmanVss::deal(&mut UnwrapErr(SysRng), 5, 3);
103 for (i, &share) in vss.shares.iter().enumerate() {
104 assert!(
105 FeldmanVss::verify_share(&vss.commitments, (i + 1) as u64, share),
106 "share for party {} must verify",
107 i + 1
108 );
109 }
110 }
111
112 #[test]
113 fn feldman_rejects_tampered_share() {
114 let vss = FeldmanVss::deal(&mut UnwrapErr(SysRng), 5, 3);
115 let bad_share = vss.shares[0] + Scalar::from(1u64);
116 assert!(!FeldmanVss::verify_share(&vss.commitments, 1, bad_share));
117 }
118
119 #[test]
120 fn feldman_commitments_round_trip() {
121 let vss = FeldmanVss::deal(&mut UnwrapErr(SysRng), 5, 3);
122 let enc = FeldmanVss::encode_commitments(&vss.commitments);
123 let dec = FeldmanVss::decode_commitments(&enc).expect("decode");
124 assert_eq!(dec.len(), vss.commitments.len());
125 for (a, b) in vss.commitments.iter().zip(dec.iter()) {
126 let ab = a.to_sec1_point(true);
127 let bb = b.to_sec1_point(true);
128 assert_eq!(ab.as_bytes(), bb.as_bytes());
129 }
130 }
131
132 #[test]
133 fn feldman_decode_rejects_garbage() {
134 assert!(FeldmanVss::decode_commitments(&[0u8; 10]).is_none());
135 }
136}