use k256::{
elliptic_curve::{generic_array::GenericArray, PrimeField},
AffinePoint, ProjectivePoint, PublicKey, Scalar,
};
use rand::{rngs::OsRng, RngCore};
use std::error::Error;
fn scalar_to_pubkey(secret: &k256::Scalar) -> PublicKey {
let point = ProjectivePoint::GENERATOR * *secret;
PublicKey::from_affine(AffinePoint::from(point)).expect("invalid public key")
}
#[derive(Clone)]
pub struct Polynomial {
coefficients: Vec<Scalar>,
pub proofs: Vec<Vec<u8>>,
}
pub trait LagrangeInterpolatable {
fn get_index(&self) -> &Scalar;
fn get_share(&self) -> &Scalar;
fn get_threshold(&self) -> usize;
}
#[derive(Debug, Clone)]
pub struct SecretShare {
pub threshold: usize,
pub index: Scalar,
pub share: Scalar,
}
impl LagrangeInterpolatable for SecretShare {
fn get_index(&self) -> &Scalar {
&self.index
}
fn get_share(&self) -> &Scalar {
&self.share
}
fn get_threshold(&self) -> usize {
self.threshold
}
}
#[derive(Debug, Clone)]
pub struct VerifiableSecretShare {
pub secret_share: SecretShare,
pub proofs: Vec<Vec<u8>>,
}
impl VerifiableSecretShare {
pub fn marshal_proto(&self) -> spark_protos::spark::SecretShare {
::spark_protos::spark::SecretShare {
secret_share: self.secret_share.share.to_bytes().to_vec(),
proofs: self.proofs.clone(),
}
}
}
impl LagrangeInterpolatable for VerifiableSecretShare {
fn get_index(&self) -> &Scalar {
&self.secret_share.index
}
fn get_share(&self) -> &Scalar {
&self.secret_share.share
}
fn get_threshold(&self) -> usize {
self.secret_share.threshold
}
}
impl Polynomial {
pub fn evaluate(&self, x: &Scalar) -> Scalar {
let mut result = Scalar::ZERO;
let mut x_power = Scalar::ONE;
for coeff in self.coefficients.iter() {
result += *coeff * x_power;
x_power *= x;
}
result
}
}
fn field_div(
numerator: &k256::Scalar,
denominator: &k256::Scalar,
) -> Result<k256::Scalar, String> {
if bool::from(denominator.is_zero()) {
return Err("division by zero".to_string());
}
let inverse = denominator
.invert()
.into_option()
.ok_or("element not invertible".to_string())?;
Ok(*numerator * inverse)
}
pub fn compute_lagrange_coefficients<T: LagrangeInterpolatable>(
index: &Scalar,
points: &[T],
) -> Result<Scalar, String> {
let mut numerator = Scalar::ONE;
let mut denominator = Scalar::ONE;
for point in points {
if point.get_index() == index {
continue;
}
numerator *= point.get_index();
let value = point.get_index() - index;
denominator *= value;
}
field_div(&numerator, &denominator)
}
pub fn from_bytes_to_k256_scalar(bytes: &[u8]) -> Result<Scalar, String> {
if bytes.len() != 32 {
return Err(format!(
"Invalid byte length for scalar. Expected 32, got {}",
bytes.len()
));
}
let arr = GenericArray::clone_from_slice(bytes);
Scalar::from_repr_vartime(arr)
.ok_or_else(|| "Failed to parse Scalar (out of range)".to_string())
}
fn generate_polynomial_for_secret_sharing(
secret: &Scalar,
threshold: usize,
) -> Result<Polynomial, Box<dyn Error>> {
let mut coefficients = Vec::with_capacity(threshold + 1);
let mut proofs = Vec::with_capacity(threshold + 1);
coefficients.push(*secret);
proofs.push(scalar_to_pubkey(secret).to_sec1_bytes().to_vec());
for _ in 1..=threshold {
let mut random_bytes = [0u8; 32];
OsRng.fill_bytes(&mut random_bytes);
let random_scalar = from_bytes_to_k256_scalar(&random_bytes)?;
coefficients.push(random_scalar);
proofs.push(scalar_to_pubkey(&random_scalar).to_sec1_bytes().to_vec());
}
Ok(Polynomial {
coefficients,
proofs,
})
}
pub fn split_secret(
secret: &Scalar,
threshold: usize,
number_of_shares: usize,
) -> Result<Vec<SecretShare>, Box<dyn Error>> {
let polynomial = generate_polynomial_for_secret_sharing(secret, threshold - 1)?;
let mut shares = Vec::with_capacity(number_of_shares);
for i in 1..=number_of_shares {
let index = Scalar::from(i as u64);
let share = polynomial.evaluate(&index);
shares.push(SecretShare {
threshold,
index,
share,
});
}
Ok(shares)
}
fn scalar_modpow(base: &Scalar, exp: usize) -> Scalar {
if exp == 0 {
return Scalar::ONE;
}
let mut result = Scalar::ONE;
let mut base = *base;
let mut exp = exp;
while exp > 0 {
if exp & 1 == 1 {
result *= base;
}
base *= base;
exp >>= 1;
}
result
}
pub fn split_secret_with_proofs(
secret_scalar: &Scalar,
threshold: usize,
number_of_shares: usize,
) -> Result<Vec<VerifiableSecretShare>, Box<dyn Error>> {
if threshold == 0 || threshold > number_of_shares {
return Err("Invalid threshold".into());
}
let polynomial = generate_polynomial_for_secret_sharing(secret_scalar, threshold - 1)?;
let mut shares = Vec::with_capacity(number_of_shares);
for i in 1..=number_of_shares {
let index = Scalar::from(i as u64);
let share = polynomial.evaluate(&index);
shares.push(VerifiableSecretShare {
secret_share: SecretShare {
threshold,
index,
share,
},
proofs: polynomial.proofs.clone(),
});
}
Ok(shares)
}
pub fn recover_secret<T: LagrangeInterpolatable>(shares: &[T]) -> Result<Scalar, String> {
if shares.len() < shares[0].get_threshold() {
return Err("not enough shares to recover secret".to_string());
}
let mut result = Scalar::ZERO;
for share in shares {
let coeff = compute_lagrange_coefficients(share.get_index(), shares)?;
result += share.get_share() * &coeff;
}
Ok(result)
}
pub fn validate_share(share: &VerifiableSecretShare) -> Result<(), String> {
let target_pubkey = scalar_to_pubkey(&share.secret_share.share);
let mut result = ProjectivePoint::IDENTITY;
if let Some(base_proof) = share.proofs.first() {
let base_pubkey = PublicKey::from_sec1_bytes(base_proof).map_err(|e| e.to_string())?;
result += ProjectivePoint::from(base_pubkey.as_affine());
}
for (i, proof) in share.proofs.iter().enumerate().skip(1) {
let pubkey = PublicKey::from_sec1_bytes(proof).map_err(|e| e.to_string())?;
let exp = scalar_modpow(
&share.secret_share.index,
i,
);
result += ProjectivePoint::from(pubkey.as_affine()) * exp;
}
if AffinePoint::from(result) == *target_pubkey.as_affine() {
Ok(())
} else {
Err("Share validation failed".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn scalar_to_32_bytes(scalar: &Scalar) -> [u8; 32] {
scalar.to_bytes().into()
}
#[test]
fn test_secret_sharing_basic() -> Result<(), Box<dyn Error>> {
let mut secret_bytes = [0u8; 32];
secret_bytes[..5].copy_from_slice(&[1, 2, 3, 4, 5]);
let secret = from_bytes_to_k256_scalar(&secret_bytes)?;
let shares = split_secret(&secret, 3, 5)?;
let recovered = recover_secret(&shares[0..3])?;
assert_eq!(secret, recovered);
Ok(())
}
#[test]
fn test_verifiable_secret_sharing() -> Result<(), Box<dyn Error>> {
let mut secret_bytes = [0u8; 32];
secret_bytes[..5].copy_from_slice(&[1, 2, 3, 4, 5]);
let secret_scalar = from_bytes_to_k256_scalar(&secret_bytes)?;
let shares = split_secret_with_proofs(&secret_scalar, 3, 5)?;
for share in &shares {
validate_share(share)?;
}
let recovered = recover_secret(&shares[0..3])?;
assert_eq!(secret_scalar, recovered);
Ok(())
}
#[test]
fn test_share_bytes_compatibility() -> Result<(), Box<dyn Error>> {
let secret_scalar = from_bytes_to_k256_scalar(&[0x11; 32])?;
let shares = split_secret_with_proofs(&secret_scalar, 3, 5)?;
let original_share_bytes: Vec<Vec<u8>> = shares
.iter()
.map(|s| scalar_to_32_bytes(&s.secret_share.share).to_vec())
.collect();
for share in &shares {
validate_share(share)?;
}
let new_shares: Vec<VerifiableSecretShare> = shares
.iter()
.enumerate()
.map(|(i, original_share)| {
let share_scalar = from_bytes_to_k256_scalar(&original_share_bytes[i]).unwrap();
VerifiableSecretShare {
secret_share: SecretShare {
threshold: original_share.secret_share.threshold,
index: Scalar::from((i + 1) as u64),
share: share_scalar,
},
proofs: original_share.proofs.clone(),
}
})
.collect();
for share in &new_shares {
validate_share(share)?;
}
for (orig, new) in shares.iter().zip(new_shares.iter()) {
assert_eq!(
scalar_to_32_bytes(&orig.secret_share.share),
scalar_to_32_bytes(&new.secret_share.share),
"Share bytes don't match after reconstruction"
);
}
let recovered_orig = recover_secret(&shares[0..3])?;
let recovered_new = recover_secret(&new_shares[0..3])?;
assert_eq!(recovered_orig, recovered_new);
Ok(())
}
}