Skip to main content

confium_tc_elgamal_p256/
shamir.rs

1//! Real Shamir secret sharing over the P-256 scalar field.
2
3use p256::elliptic_curve::rand_core;
4use p256::elliptic_curve::subtle::CtOption;
5use p256::elliptic_curve::{Field, PrimeField};
6use p256::{FieldBytes, Scalar};
7use sha2::{Digest as _, Sha256};
8use std::ops::{Add, Mul, Sub};
9
10/// A Shamir share: (x, y) where x is the party index and y is a scalar.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Share {
13    /// Party index (1-based).
14    pub x: u32,
15    /// The share value.
16    pub y: Scalar,
17}
18
19/// Split a `secret` into `n` shares with threshold `t`.
20pub fn split_secret(secret: &Scalar, t: u32, n: u32) -> Vec<Share> {
21    assert!(t >= 1);
22    assert!(n >= t);
23
24    let mut coeffs: Vec<Scalar> = Vec::with_capacity(t as usize);
25    coeffs.push(*secret);
26    for _ in 1..t {
27        coeffs.push(random_scalar());
28    }
29
30    (1..=n)
31        .map(|i| {
32            let x = u32_to_scalar(i);
33            Share {
34                x: i,
35                y: evaluate_polynomial(&coeffs, &x),
36            }
37        })
38        .collect()
39}
40
41/// Recover the secret via Lagrange interpolation at x=0.
42pub fn recover_secret(shares: &[&Share]) -> Result<Scalar, ShamirError> {
43    if shares.is_empty() {
44        return Err(ShamirError::InsufficientShares { have: 0, need: 1 });
45    }
46    let mut seen = std::collections::HashSet::new();
47    for s in shares {
48        if !seen.insert(s.x) {
49            return Err(ShamirError::DuplicateX(s.x));
50        }
51    }
52
53    let mut sum = Scalar::ZERO;
54    for s_i in shares {
55        let x_i = u32_to_scalar(s_i.x);
56        let mut numerator = Scalar::ONE;
57        let mut denominator = Scalar::ONE;
58        for s_j in shares {
59            if s_j.x == s_i.x {
60                continue;
61            }
62            let x_j = u32_to_scalar(s_j.x);
63            numerator = numerator.mul(&Scalar::ZERO.sub(&x_j));
64            denominator = denominator.mul(&x_i.sub(&x_j));
65        }
66        let denom_inv = invert(&denominator);
67        let lagrange = numerator.mul(&denom_inv);
68        let term = s_i.y.mul(&lagrange);
69        sum = sum.add(&term);
70    }
71    Ok(sum)
72}
73
74fn random_scalar() -> Scalar {
75    Scalar::random(&mut rand_core::UnwrapErr(getrandom::SysRng))
76}
77
78fn evaluate_polynomial(coeffs: &[Scalar], x: &Scalar) -> Scalar {
79    let mut result = Scalar::ZERO;
80    for c in coeffs.iter().rev() {
81        result = result.mul(x);
82        result = result.add(c);
83    }
84    result
85}
86
87/// Reduce 32 bytes to a scalar by rejection sampling with re-hash;
88/// never falls back to a constant.
89fn reduce_to_scalar(mut bytes: [u8; 32]) -> Scalar {
90    loop {
91        if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(bytes))) {
92            return s;
93        }
94        let mut h = Sha256::new();
95        h.update(b"confium-scalar-reduce-v1");
96        h.update(bytes);
97        bytes = h.finalize().into();
98    }
99}
100
101fn u32_to_scalar(v: u32) -> Scalar {
102    let mut arr = [0u8; 32];
103    arr[28..32].copy_from_slice(&v.to_be_bytes());
104    reduce_to_scalar(arr)
105}
106
107fn invert(s: &Scalar) -> Scalar {
108    // Garbage-in-garbage-out on zero input; protocol callers pass
109    // non-zero scalars (sweep ledger: SEC-audit-notes).
110    let ct: CtOption<Scalar> = s.invert();
111    Option::<Scalar>::from(ct).unwrap_or(Scalar::ZERO)
112}
113
114/// Errors during Shamir operations.
115#[derive(Debug, thiserror::Error)]
116pub enum ShamirError {
117    /// Fewer than T shares provided.
118    #[error("insufficient shares: have {have}, need {need}")]
119    InsufficientShares {
120        /// Count received.
121        have: usize,
122        /// Threshold.
123        need: u32,
124    },
125    /// Duplicate x-coordinates.
126    #[error("duplicate x: {0}")]
127    DuplicateX(u32),
128}