Skip to main content

confium_tc_gg18/
vss.rs

1//! Feldman verifiable secret sharing over P-256.
2//!
3//! A dealer samples a degree-`T-1` polynomial
4//! `f(x) = a_0 + a_1 x + ... + a_{T-1} x^{T-1}` with `a_0 = secret`,
5//! privately sends each party `i` the evaluation `y_i = f(i)`, and
6//! broadcasts Feldman commitments `C_j = g^{a_j}`. Recipients verify
7//! `g^{y_i} == \prod_j C_j^{i^j}` without learning the coefficients.
8//! `C_0 = g^{secret}` is the public key for the shared secret.
9
10use elliptic_curve::Field;
11use elliptic_curve::rand_core::CryptoRng;
12use elliptic_curve::sec1::ToSec1Point;
13use p256::{AffinePoint, ProjectivePoint, Scalar};
14
15/// One dealer's Feldman VSS output.
16#[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}