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