Skip to main content

confium_tc_ecies_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).
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Share {
13    /// Party index (1-based).
14    pub x: u32,
15    /// Share value.
16    pub y: Scalar,
17}
18
19/// Split `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    let mut sum = Scalar::ZERO;
53    for s_i in shares {
54        let x_i = u32_to_scalar(s_i.x);
55        let mut numerator = Scalar::ONE;
56        let mut denominator = Scalar::ONE;
57        for s_j in shares {
58            if s_j.x == s_i.x {
59                continue;
60            }
61            let x_j = u32_to_scalar(s_j.x);
62            numerator = numerator.mul(&Scalar::ZERO.sub(&x_j));
63            denominator = denominator.mul(&x_i.sub(&x_j));
64        }
65        let denom_inv = invert(&denominator);
66        let lagrange = numerator.mul(&denom_inv);
67        let term = s_i.y.mul(&lagrange);
68        sum = sum.add(&term);
69    }
70    Ok(sum)
71}
72
73fn random_scalar() -> Scalar {
74    Scalar::random(&mut rand_core::UnwrapErr(getrandom::SysRng))
75}
76
77fn evaluate_polynomial(coeffs: &[Scalar], x: &Scalar) -> Scalar {
78    let mut result = Scalar::ZERO;
79    for c in coeffs.iter().rev() {
80        result = result.mul(x);
81        result = result.add(c);
82    }
83    result
84}
85
86/// Reduce 32 bytes to a scalar by rejection sampling with re-hash;
87/// never falls back to a constant.
88fn reduce_to_scalar(mut bytes: [u8; 32]) -> Scalar {
89    loop {
90        if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(bytes))) {
91            return s;
92        }
93        let mut h = Sha256::new();
94        h.update(b"confium-scalar-reduce-v1");
95        h.update(bytes);
96        bytes = h.finalize().into();
97    }
98}
99
100fn u32_to_scalar(v: u32) -> Scalar {
101    let mut arr = [0u8; 32];
102    arr[28..32].copy_from_slice(&v.to_be_bytes());
103    reduce_to_scalar(arr)
104}
105
106fn invert(s: &Scalar) -> Scalar {
107    // Garbage-in-garbage-out on zero input; protocol callers pass
108    // non-zero scalars (sweep ledger: SEC-audit-notes).
109    let ct: CtOption<Scalar> = s.invert();
110    Option::<Scalar>::from(ct).unwrap_or(Scalar::ZERO)
111}
112
113/// Shamir errors.
114#[derive(Debug, thiserror::Error)]
115pub enum ShamirError {
116    /// Fewer than T shares provided.
117    #[error("insufficient shares: have {have}, need {need}")]
118    InsufficientShares {
119        /// Count received.
120        have: usize,
121        /// Threshold.
122        need: u32,
123    },
124    /// Duplicate x-coordinates.
125    #[error("duplicate x: {0}")]
126    DuplicateX(u32),
127}