Skip to main content

confium_tc_cmp20/
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//!
10//! CMP20's non-interactive key generation folds the commitment
11//! broadcast and the per-peer share delivery into a single round: each
12//! party broadcasts its full commitment list and bundles every peer's
13//! evaluation into the same message, so no second round is needed.
14
15use elliptic_curve::Field;
16use elliptic_curve::rand_core::CryptoRng;
17use elliptic_curve::sec1::ToSec1Point;
18use p256::{AffinePoint, ProjectivePoint, Scalar};
19
20/// One dealer's Feldman VSS output.
21#[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}