Skip to content

Commit

Permalink
Introduce ShareIdx to use with threshold key shares
Browse files Browse the repository at this point in the history
  • Loading branch information
fjarri committed Jan 12, 2024
1 parent 1441e96 commit f2a8cff
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 90 deletions.
100 changes: 48 additions & 52 deletions synedrion/src/cggmp21/protocols/threshold.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use alloc::collections::BTreeMap;

use k256::ecdsa::VerifyingKey;
use rand_core::CryptoRngCore;
Expand All @@ -8,7 +8,9 @@ use serde::{Deserialize, Serialize};
use super::common::{make_aux_info, KeyShare, PartyIdx, PublicAuxInfo, SecretAuxInfo};
use crate::cggmp21::SchemeParams;
use crate::curve::{Point, Scalar};
use crate::tools::sss::{interpolation_coeff, shamir_evaluation_points, shamir_split};
use crate::tools::sss::{
interpolation_coeff, shamir_evaluation_points, shamir_join_points, shamir_split, ShareIdx,
};

/// A threshold variant of the key share, where any `threshold` shares our of the total number
/// is enough to perform signing.
Expand All @@ -20,12 +22,12 @@ use crate::tools::sss::{interpolation_coeff, shamir_evaluation_points, shamir_sp
#[serde(bound(deserialize = "SecretAuxInfo<P>: for <'x> Deserialize<'x>,
PublicAuxInfo<P>: for <'x> Deserialize<'x>"))]
pub struct ThresholdKeyShare<P: SchemeParams> {
pub(crate) index: PartyIdx,
pub(crate) threshold: u32, // TODO (#31): make typed? Can it be `ShareIdx`?
pub(crate) index: ShareIdx,
pub(crate) threshold: u32,
pub(crate) secret_share: Scalar,
pub(crate) public_shares: Box<[Point]>,
pub(crate) public_shares: BTreeMap<ShareIdx, Point>,
pub(crate) secret_aux: SecretAuxInfo<P>,
pub(crate) public_aux: Box<[PublicAuxInfo<P>]>,
pub(crate) public_aux: BTreeMap<ShareIdx, PublicAuxInfo<P>>,
}

impl<P: SchemeParams> ThresholdKeyShare<P> {
Expand All @@ -44,27 +46,30 @@ impl<P: SchemeParams> ThresholdKeyShare<P> {
Some(sk) => Scalar::from(sk.as_nonzero_scalar()),
};

let secret_shares = shamir_split(
rng,
&secret,
threshold,
&shamir_evaluation_points(num_parties),
);
let share_idxs = shamir_evaluation_points(num_parties);
let secret_shares = shamir_split(rng, &secret, threshold, &share_idxs);
let public_shares = secret_shares
.iter()
.map(|s| s.mul_by_generator())
.collect::<Box<_>>();
.map(|(idx, share)| (*idx, share.mul_by_generator()))
.collect::<BTreeMap<_, _>>();

let (secret_aux, public_aux) = make_aux_info(rng, num_parties);

let public_aux = public_aux
.into_vec()
.into_iter()
.enumerate()
.map(|(idx, public)| (share_idxs[idx], public))
.collect::<BTreeMap<_, _>>();

secret_aux
.into_vec()
.into_iter()
.enumerate()
.map(|(idx, secret_aux)| ThresholdKeyShare {
index: PartyIdx::from_usize(idx),
index: share_idxs[idx],
threshold: threshold as u32,
secret_share: secret_shares[idx],
secret_share: secret_shares[&share_idxs[idx]],
public_shares: public_shares.clone(),
secret_aux,
public_aux: public_aux.clone(),
Expand All @@ -73,12 +78,7 @@ impl<P: SchemeParams> ThresholdKeyShare<P> {
}

pub(crate) fn verifying_key_as_point(&self) -> Point {
let points = shamir_evaluation_points(self.num_parties());
self.public_shares[0..self.threshold as usize]
.iter()
.enumerate()
.map(|(idx, p)| p * &interpolation_coeff(&points[0..self.threshold as usize], idx))
.sum()
shamir_join_points(self.public_shares.iter().take(self.threshold as usize))
}

/// Return the verifying key to which this set of shares corresponds.
Expand All @@ -88,52 +88,42 @@ impl<P: SchemeParams> ThresholdKeyShare<P> {
self.verifying_key_as_point().to_verifying_key().unwrap()
}

/// Returns the number of parties in this set of shares.
pub fn num_parties(&self) -> usize {
// TODO (#31): technically it is `num_shares`, but for now we are equating the two,
// since we assume that one party has one share.
self.public_shares.len()
}

/// Returns the index of this share's party.
pub fn party_index(&self) -> usize {
// TODO (#31): technically it is the share index, but for now we are equating the two,
// since we assume that one party has one share.
self.index.as_usize()
pub fn index(&self) -> ShareIdx {
self.index
}

/// Converts a t-of-n key share into a t-of-t key share
/// (for the `t` parties supplied as `party_idxs`)
/// (for the `t` share indices supplied as `share_idxs`)
/// that can be used in the presigning/signing protocols.
pub fn to_key_share(&self, party_idxs: &[PartyIdx]) -> KeyShare<P> {
debug_assert!(party_idxs.len() == self.threshold as usize);
pub fn to_key_share(&self, share_idxs: &[ShareIdx]) -> KeyShare<P> {
debug_assert!(share_idxs.len() == self.threshold as usize);

Check warning on line 100 in synedrion/src/cggmp21/protocols/threshold.rs

View check run for this annotation

Codecov / codecov/patch

synedrion/src/cggmp21/protocols/threshold.rs#L100

Added line #L100 was not covered by tests
// TODO (#68): assert that all indices are distinct
let mapped_idx = party_idxs
let my_idx_position = share_idxs
.iter()
.position(|idx| idx == &self.index)
.unwrap();

let all_points = shamir_evaluation_points(self.num_parties());
let points = party_idxs
.iter()
.map(|idx| all_points[idx.as_usize()])
.collect::<Vec<_>>();

let secret_share = self.secret_share * interpolation_coeff(&points, mapped_idx);
let public_shares = party_idxs
let secret_share = self.secret_share * interpolation_coeff(share_idxs, my_idx_position);
let public_shares = share_idxs
.iter()
.enumerate()
.map(|(mapped_idx, idx)| {
&self.public_shares[idx.as_usize()] * &interpolation_coeff(&points, mapped_idx)
.map(|(position, share_idx)| {
&self.public_shares[share_idx] * &interpolation_coeff(share_idxs, position)
})
.collect();

let public_aux = share_idxs
.iter()
.map(|idx| self.public_aux[idx].clone())
.collect();

KeyShare {
index: PartyIdx::from_usize(mapped_idx),
index: PartyIdx::from_usize(my_idx_position),
secret_share,
public_shares,
secret_aux: self.secret_aux.clone(),
public_aux: self.public_aux.clone(),
public_aux,
}
}
}
Expand Down Expand Up @@ -164,17 +154,23 @@ mod tests {
use rand_core::OsRng;

use super::ThresholdKeyShare;
use crate::cggmp21::{PartyIdx, TestParams};
use crate::cggmp21::TestParams;
use crate::curve::Scalar;

#[test]
fn threshold_key_share_centralized() {
let sk = SigningKey::random(&mut OsRng);
let shares = ThresholdKeyShare::<TestParams>::new_centralized(&mut OsRng, 2, 3, Some(&sk));

assert_eq!(&shares[0].verifying_key(), sk.verifying_key());
assert_eq!(&shares[1].verifying_key(), sk.verifying_key());
assert_eq!(&shares[2].verifying_key(), sk.verifying_key());

assert_eq!(&shares[0].verifying_key(), sk.verifying_key());

let nt_share0 = shares[0].to_key_share(&[PartyIdx::from_usize(2), PartyIdx::from_usize(0)]);
let nt_share1 = shares[2].to_key_share(&[PartyIdx::from_usize(2), PartyIdx::from_usize(0)]);
let share_idxs = [shares[2].index(), shares[0].index()];
let nt_share0 = shares[0].to_key_share(&share_idxs);
let nt_share1 = shares[2].to_key_share(&share_idxs);

assert_eq!(&nt_share0.verifying_key(), sk.verifying_key());
assert_eq!(&nt_share1.verifying_key(), sk.verifying_key());
Expand Down
8 changes: 7 additions & 1 deletion synedrion/src/curve/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub(crate) type CompressedPointSize =

pub(crate) const ORDER: U256 = Secp256k1::ORDER;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PartialOrd, Ord)]
pub struct Scalar(BackendScalar);

impl Scalar {
Expand Down Expand Up @@ -230,6 +230,12 @@ impl From<u32> for Scalar {
}
}

impl From<usize> for Scalar {
fn from(val: usize) -> Self {
Self(BackendScalar::from(u64::try_from(val).unwrap()))
}

Check warning on line 236 in synedrion/src/curve/arithmetic.rs

View check run for this annotation

Codecov / codecov/patch

synedrion/src/curve/arithmetic.rs#L234-L236

Added lines #L234 - L236 were not covered by tests
}

impl Neg for Scalar {
type Output = Self;
fn neg(self) -> Self::Output {
Expand Down
114 changes: 77 additions & 37 deletions synedrion/src/tools/sss.rs
Original file line number Diff line number Diff line change
@@ -1,77 +1,117 @@
use alloc::collections::BTreeMap;
use alloc::vec::Vec;

use rand_core::CryptoRngCore;
use serde::{Deserialize, Serialize};

use crate::curve::Scalar;
use crate::curve::{Point, Scalar};

pub(crate) fn shamir_evaluation_points(num_shares: usize) -> Vec<Scalar> {
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct ShareIdx(Scalar);

impl ShareIdx {
pub fn new(idx: usize) -> Self {
Self(Scalar::from(idx))
}

Check warning on line 15 in synedrion/src/tools/sss.rs

View check run for this annotation

Codecov / codecov/patch

synedrion/src/tools/sss.rs#L13-L15

Added lines #L13 - L15 were not covered by tests
}

pub(crate) fn shamir_evaluation_points(num_shares: usize) -> Vec<ShareIdx> {
// For now we are hardcoding the points to be 1, 2, ..., n.
// TODO (#87): it should be still secure, right?
// Potentially we can derive them from Session ID.
(1..=u32::try_from(num_shares).expect("The number of shares cannot be over 2^32-1"))
.map(Scalar::from)
.map(|idx| ShareIdx(Scalar::from(idx)))
.collect()
}

pub(crate) struct Polynomial(Vec<Scalar>);

impl Polynomial {
pub fn random(rng: &mut impl CryptoRngCore, coeff0: &Scalar, degree: usize) -> Self {
let mut coeffs = Vec::with_capacity(degree);
coeffs.push(*coeff0);
for _ in 1..degree {
coeffs.push(Scalar::random_nonzero(rng));
}
Self(coeffs)
}

pub fn evaluate(&self, x: &ShareIdx) -> Scalar {
let mut res = self.0[0];
let mut xp = x.0;
for coeff in self.0[1..].iter() {
res = res + coeff * &xp;
xp = xp * x.0;
}
res
}
}

pub(crate) fn shamir_split(
rng: &mut impl CryptoRngCore,
secret: &Scalar,
threshold: usize,
points: &[Scalar],
) -> Vec<Scalar> {
let coeffs = (0..threshold - 1)
.map(|_| Scalar::random_nonzero(rng))
.collect::<Vec<_>>();
points
indices: &[ShareIdx],
) -> BTreeMap<ShareIdx, Scalar> {
let polynomial = Polynomial::random(rng, secret, threshold);
indices
.iter()
.map(|x| {
let mut res = *secret;
let mut xp = *x;
for coeff in coeffs.iter() {
res = res + coeff * &xp;
xp = &xp * x;
}
res
})
.map(|idx| (*idx, polynomial.evaluate(idx)))
.collect()
}

pub(crate) fn interpolation_coeff(points: &[Scalar], idx: usize) -> Scalar {
points
.iter()
pub(crate) fn interpolation_coeff(idxs: &[ShareIdx], exclude_idx: usize) -> Scalar {
// TODO: the inversions can be precalculated if we calculate multiple interpolation coeffs
// for the same set of shares.
idxs.iter()
.enumerate()
.filter(|(j, _)| j != &idx)
.map(|(_, x)| x * &(x - &points[idx]).invert().unwrap())
.filter(|(i, _)| i != &exclude_idx)
.map(|(_, idx)| idx.0 * (idx.0 - idxs[exclude_idx].0).invert().unwrap())
.product()
}

#[cfg(test)]
pub(crate) fn shamir_join_scalars<'a>(
pairs: impl Iterator<Item = (&'a ShareIdx, &'a Scalar)>,
) -> Scalar {
let (share_idxs, values): (Vec<_>, Vec<_>) = pairs.map(|(k, v)| (*k, *v)).unzip();
values
.iter()
.enumerate()
.map(|(i, val)| val * &interpolation_coeff(&share_idxs, i))
.sum()
}

pub(crate) fn shamir_join_points<'a>(
pairs: impl Iterator<Item = (&'a ShareIdx, &'a Point)>,
) -> Point {
let (share_idxs, values): (Vec<_>, Vec<_>) = pairs.map(|(k, v)| (*k, *v)).unzip();
values
.iter()
.enumerate()
.map(|(i, val)| val * &interpolation_coeff(&share_idxs, i))
.sum()
}

#[cfg(test)]
mod tests {
use rand_core::OsRng;

use super::{interpolation_coeff, shamir_evaluation_points, shamir_split};
use super::{shamir_evaluation_points, shamir_join_scalars, shamir_split};
use crate::curve::Scalar;

fn shamir_join(secrets: &[Scalar], points: &[Scalar]) -> Scalar {
secrets
.iter()
.enumerate()
.map(|(idx, secret)| secret * &interpolation_coeff(points, idx))
.sum()
}

#[test]
fn split_and_join() {
let threshold = 3;
let num_shares = 5;
let secret = Scalar::random(&mut OsRng);
let points = shamir_evaluation_points(num_shares);
let shares = shamir_split(&mut OsRng, &secret, threshold, &points);
let mut shares = shamir_split(&mut OsRng, &secret, threshold, &points);

shares.remove(&points[0]);
shares.remove(&points[3]);

let recovered_secret = shamir_join(
&[shares[1], shares[2], shares[4]],
&[points[1], points[2], points[4]],
);
let recovered_secret = shamir_join_scalars(shares.iter());
assert_eq!(recovered_secret, secret);
}
}

0 comments on commit f2a8cff

Please sign in to comment.