mod round1;
mod round2;
mod round3;
use super::*;
use elliptic_curve::group::GroupEncoding;
use elliptic_curve::subtle::ConditionallySelectable;
use elliptic_curve::{Field, Group};
use elliptic_curve_tools::{SumOfProducts, group, prime_field, prime_field_vec};
use rand_core::CryptoRng;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt::{self, Debug, Formatter};
use std::marker::PhantomData;
use vsss_rs::{
DefaultShare, IdentifierPrimeField, ShareElement, ShareVerifierGroup, ValueGroup,
ValuePrimeField, subtle::ConstantTimeEq,
};
pub type SecretParticipant<G> = Participant<SecretParticipantImpl<G>, G>;
pub type RefreshParticipant<G> = Participant<RefreshParticipantImpl<G>, G>;
pub type SecretShare<F> = DefaultShare<IdentifierPrimeField<F>, IdentifierPrimeField<F>>;
pub type FeldmanShareVerifier<G> = ShareVerifierGroup<G>;
#[derive(Copy, Clone, Debug)]
pub struct ReconstructionSet<'a, F: ScalarHash> {
identifiers: &'a [IdentifierPrimeField<F>],
}
impl<'a, F: ScalarHash> ReconstructionSet<'a, F> {
pub fn new(identifiers: &'a [IdentifierPrimeField<F>]) -> DkgResult<Self> {
if identifiers.len() < 2 {
return Err(Error::Initialization(
"A reconstruction set requires at least 2 participant identifiers".to_string(),
));
}
if identifiers.iter().any(|id| bool::from(id.is_zero())) {
return Err(Error::Initialization(
"Reconstruction participant identifiers cannot be zero".to_string(),
));
}
let unique_identifiers = identifiers.iter().copied().collect::<HashSet<_>>();
if unique_identifiers.len() != identifiers.len() {
return Err(Error::Initialization(
"Reconstruction participant identifiers must be unique".to_string(),
));
}
Ok(Self { identifiers })
}
pub fn identifiers(&self) -> &[IdentifierPrimeField<F>] {
self.identifiers
}
fn contains(&self, identifier: &IdentifierPrimeField<F>) -> bool {
self.identifiers.contains(identifier)
}
}
pub trait ParticipantImpl<G>
where
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
fn get_type(&self) -> ParticipantType;
fn random_value(rng: impl CryptoRng) -> G::Scalar;
fn check_feldman_verifier(verifier: G) -> bool;
}
#[derive(Serialize, Deserialize)]
pub struct Participant<I, G>
where
I: ParticipantImpl<G> + Default,
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
pub(crate) ordinal: usize,
#[serde(bound(
serialize = "IdentifierPrimeField<G::Scalar>: Serialize",
deserialize = "IdentifierPrimeField<G::Scalar>: Deserialize<'de>"
))]
pub(crate) id: IdentifierPrimeField<G::Scalar>,
pub(crate) threshold: usize,
pub(crate) limit: usize,
pub(crate) round: Round,
pub(crate) completed: bool,
#[serde(bound(
serialize = "SecretShare<G::Scalar>: Serialize",
deserialize = "SecretShare<G::Scalar>: Deserialize<'de>"
))]
pub(crate) secret_shares: Vec<SecretShare<G::Scalar>>,
#[serde(bound(
serialize = "ValueGroup<G>: Serialize",
deserialize = "ValueGroup<G>: Deserialize<'de>"
))]
pub(crate) feldman_verifiers: Vec<ValueGroup<G>>,
#[serde(with = "prime_field")]
pub(crate) original_secret: G::Scalar,
#[serde(with = "group")]
pub(crate) verifying_share: G,
#[serde(bound(
serialize = "SecretShare<G::Scalar>: Serialize",
deserialize = "SecretShare<G::Scalar>: Deserialize<'de>"
))]
pub(crate) secret_share: SecretShare<G::Scalar>,
#[serde(with = "group")]
pub(crate) message_generator: G,
#[serde(bound(
serialize = "ValueGroup<G>: Serialize",
deserialize = "ValueGroup<G>: Deserialize<'de>"
))]
pub(crate) public_key: ValueGroup<G>,
#[serde(with = "prime_field_vec")]
pub(crate) powers_of_i: Vec<G::Scalar>,
#[serde(bound(
serialize = "Round1Data<G>: Serialize",
deserialize = "Round1Data<G>: Deserialize<'de>"
))]
pub(crate) received_round1_data: Vec<Option<Round1Data<G>>>,
#[serde(bound(
serialize = "Round2Data<G::Scalar>: Serialize",
deserialize = "Round2Data<G::Scalar>: Deserialize<'de>"
))]
pub(crate) received_round2_data: Vec<Option<Round2Data<G::Scalar>>>,
#[serde(bound(
serialize = "IdentifierPrimeField<G::Scalar>: Serialize",
deserialize = "IdentifierPrimeField<G::Scalar>: Deserialize<'de>"
))]
pub(crate) all_participant_ids: Vec<IdentifierPrimeField<G::Scalar>>,
#[serde(bound(
serialize = "IdentifierPrimeField<G::Scalar>: Serialize",
deserialize = "IdentifierPrimeField<G::Scalar>: Deserialize<'de>"
))]
pub(crate) valid_participant_ids: Vec<Option<IdentifierPrimeField<G::Scalar>>>,
pub(crate) participant_impl: I,
}
impl<I, G> Debug for Participant<I, G>
where
I: ParticipantImpl<G> + Default,
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Participant")
.field("ordinal", &self.ordinal)
.field("id", &self.id)
.field("threshold", &self.threshold)
.field("limit", &self.limit)
.field("round", &self.round)
.field("completed", &self.completed)
.field("feldman_verifiers", &self.feldman_verifiers)
.field("public_key", &self.public_key)
.field("powers_of_i", &self.powers_of_i)
.finish()
}
}
impl<G> Participant<SecretParticipantImpl<G>, G>
where
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
pub fn new_secret(
id: IdentifierPrimeField<G::Scalar>,
parameters: &Parameters<G>,
) -> DkgResult<Self> {
let rng = rand::rng();
let secret = SecretParticipantImpl::<G>::random_value(rng);
Self::initialize(id, parameters, IdentifierPrimeField(secret), None)
}
pub fn with_secret(
new_identifier: IdentifierPrimeField<G::Scalar>,
old_share: &SecretShare<G::Scalar>,
parameters: &Parameters<G>,
reconstruction_set: &ReconstructionSet<'_, G::Scalar>,
) -> DkgResult<Self> {
if !reconstruction_set.contains(&old_share.identifier) {
return Err(Error::Initialization(
"The old share is not included in the reconstruction set".to_string(),
));
}
let secret = *old_share.value * *Self::lagrange(old_share, reconstruction_set);
Self::initialize(
new_identifier,
parameters,
IdentifierPrimeField(secret),
None,
)
}
}
impl<G> Participant<RefreshParticipantImpl<G>, G>
where
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
pub fn new_refresh(
id: IdentifierPrimeField<G::Scalar>,
existing_share: Option<&SecretShare<G::Scalar>>,
parameters: &Parameters<G>,
) -> DkgResult<Self> {
if existing_share.is_some_and(|share| share.identifier != id) {
return Err(Error::Initialization(
"The existing share identifier does not match the refresh participant".to_string(),
));
}
let secret = existing_share
.map(|share| share.value.0)
.unwrap_or_else(|| G::Scalar::random(&mut rand::rng()));
Self::initialize(
id,
parameters,
IdentifierPrimeField(secret),
Some(parameters.message_generator * secret),
)
}
}
impl<I, G> Participant<I, G>
where
I: ParticipantImpl<G> + Default,
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
fn initialize(
id: IdentifierPrimeField<G::Scalar>,
parameters: &Parameters<G>,
secret: ValuePrimeField<G::Scalar>,
verifying_share: Option<G>,
) -> DkgResult<Self> {
let rng = rand::rng();
let mut powers_of_i = vec![G::Scalar::ONE; parameters.threshold];
powers_of_i[1] = *id;
for i in 2..parameters.threshold {
powers_of_i[i] = powers_of_i[i - 1] * *id;
}
let participant_type = I::default().get_type();
let secret_to_split = match participant_type {
ParticipantType::Secret => secret,
ParticipantType::Refresh => IdentifierPrimeField(G::Scalar::ZERO),
};
let (shares, verifiers) = vsss_rs::feldman::split_secret_with_participant_generators::<
SecretShare<G::Scalar>,
ShareVerifierGroup<G>,
>(
parameters.threshold,
parameters.limit,
&secret_to_split,
Some(ValueGroup(parameters.message_generator)),
rng,
¶meters.participant_number_generators,
)?;
let verifiers = verifiers.iter().skip(1).copied().collect::<Vec<_>>();
let verifying_share = match participant_type {
ParticipantType::Secret => verifiers[0].0,
ParticipantType::Refresh => verifying_share.ok_or(Error::Initialization(
"Verifying share is required for refresh".to_string(),
))?,
};
if verifiers.iter().skip(1).any(|c| c.is_identity().into())
|| !I::check_feldman_verifier(*verifiers[0])
{
return Err(Error::Initialization(
"Invalid Feldman verifier".to_string(),
));
}
let ordinal = shares
.iter()
.position(|s| s.identifier == id)
.ok_or_else(|| {
Error::Initialization(format!(
"Invalid participant ID '{id}'; it is not in the generated set of shares"
))
})?;
let all_participant_ids = shares.iter().map(|share| share.identifier).collect();
Ok(Self {
ordinal,
id,
threshold: parameters.threshold,
limit: parameters.limit,
completed: false,
round: Round::One,
original_secret: secret.0,
verifying_share,
secret_shares: shares,
feldman_verifiers: verifiers,
secret_share: SecretShare::<G::Scalar>::default(),
message_generator: parameters.message_generator,
public_key: ValueGroup::<G>::identity(),
powers_of_i,
received_round1_data: std::iter::repeat_with(|| None)
.take(parameters.limit)
.collect(),
received_round2_data: std::iter::repeat_with(|| None)
.take(parameters.limit)
.collect(),
all_participant_ids,
valid_participant_ids: vec![None; parameters.limit],
participant_impl: Default::default(),
})
}
pub fn ordinal(&self) -> usize {
self.ordinal
}
pub fn id(&self) -> IdentifierPrimeField<G::Scalar> {
self.id
}
pub fn completed(&self) -> bool {
self.completed
}
pub fn round(&self) -> Round {
self.round
}
pub fn threshold(&self) -> usize {
self.threshold
}
pub fn limit(&self) -> usize {
self.limit
}
pub fn secret_share(&self) -> Option<SecretShare<G::Scalar>> {
if self.completed {
Some(self.secret_share)
} else {
None
}
}
pub fn public_key(&self) -> Option<G> {
if self.completed {
Some(*self.public_key)
} else {
None
}
}
pub fn all_participant_ids(&self) -> &[IdentifierPrimeField<G::Scalar>] {
&self.all_participant_ids
}
pub fn valid_participant_ids(&self) -> &[Option<IdentifierPrimeField<G::Scalar>>] {
&self.valid_participant_ids
}
pub fn feldman_verifiers(&self) -> &[ShareVerifierGroup<G>] {
&self.feldman_verifiers
}
pub fn received_round1_data(&self) -> &[Option<Round1Data<G>>] {
&self.received_round1_data
}
pub fn received_round2_data(&self) -> &[Option<Round2Data<G::Scalar>>] {
&self.received_round2_data
}
pub fn verifying_share(&self) -> G {
self.verifying_share
}
pub fn final_transcript_hash(&self) -> [u8; 32] {
get_final_transcript_hash(&self.received_round1_data, &self.received_round2_data)
}
pub fn into_output(self) -> DkgResult<DkgOutput<G>> {
if !self.completed {
return Err(Error::Round(
"Protocol is not complete; no output is available".to_string(),
));
}
let transcript_hash =
get_final_transcript_hash(&self.received_round1_data, &self.received_round2_data);
Ok(DkgOutput {
secret_share: self.secret_share,
public_key: self.public_key.0,
feldman_verifiers: self.feldman_verifiers,
participant_ids: self.valid_participant_ids,
transcript_hash,
})
}
#[deprecated(since = "0.6.0", note = "use `ordinal` instead")]
pub fn get_ordinal(&self) -> usize {
self.ordinal()
}
#[deprecated(since = "0.6.0", note = "use `id` instead")]
pub fn get_id(&self) -> IdentifierPrimeField<G::Scalar> {
self.id()
}
#[deprecated(since = "0.6.0", note = "use `round` instead")]
pub fn get_round(&self) -> Round {
self.round()
}
#[deprecated(since = "0.6.0", note = "use `threshold` instead")]
pub fn get_threshold(&self) -> usize {
self.threshold()
}
#[deprecated(since = "0.6.0", note = "use `limit` instead")]
pub fn get_limit(&self) -> usize {
self.limit()
}
#[deprecated(since = "0.6.0", note = "use `secret_share` instead")]
pub fn get_secret_share(&self) -> Option<SecretShare<G::Scalar>> {
self.secret_share()
}
#[deprecated(since = "0.6.0", note = "use `public_key` instead")]
pub fn get_public_key(&self) -> Option<G> {
self.public_key()
}
#[deprecated(since = "0.6.0", note = "use `all_participant_ids` instead")]
pub fn get_all_participant_ids(&self) -> &[IdentifierPrimeField<G::Scalar>] {
self.all_participant_ids()
}
#[deprecated(since = "0.6.0", note = "use `valid_participant_ids` instead")]
pub fn get_valid_participant_ids(&self) -> &[Option<IdentifierPrimeField<G::Scalar>>] {
self.valid_participant_ids()
}
#[deprecated(since = "0.6.0", note = "use `feldman_verifiers` instead")]
pub fn get_feldman_verifiers(&self) -> Vec<ShareVerifierGroup<G>> {
self.feldman_verifiers().to_vec()
}
#[deprecated(since = "0.6.0", note = "use `received_round1_data` instead")]
pub fn get_received_round1_data(&self) -> &[Option<Round1Data<G>>] {
self.received_round1_data()
}
#[deprecated(since = "0.6.0", note = "use `received_round2_data` instead")]
pub fn get_received_round2_data(&self) -> &[Option<Round2Data<G::Scalar>>] {
self.received_round2_data()
}
pub fn receive(&mut self, data: &[u8]) -> DkgResult<()> {
let (&round, payload) = data
.split_first()
.ok_or_else(|| Error::InvalidMessage("message is empty".to_string()))?;
let round = Round::try_from(round).map_err(Error::InvalidMessage)?;
match round {
Round::One => {
let round1_payload = postcard::from_bytes::<Round1Data<G>>(payload)?;
self.receive_round1data(round1_payload)
}
Round::Two => {
let round2_payload = postcard::from_bytes::<Round2Data<G::Scalar>>(payload)?;
self.receive_round2data(round2_payload)
}
_ => Err(Error::Round("Protocol is complete".to_string())),
}
}
pub fn run(&mut self) -> DkgResult<RoundOutputGenerator<G>> {
match self.round {
Round::One => self.round1(),
Round::Two => self.round2(),
Round::Three => self.round3(),
Round::Four => Err(Error::Round("Protocol is complete".to_string())),
}
}
pub fn advance(&mut self) -> DkgResult<AdvanceResult<G::Scalar>> {
match self.run()? {
RoundOutputGenerator::Round3 => Ok(AdvanceResult::Complete),
output => Ok(AdvanceResult::Messages(output.into_messages()?)),
}
}
pub(crate) fn check_sending_participant_id(
&self,
round: Round,
sender_ordinal: usize,
sender_id: IdentifierPrimeField<G::Scalar>,
) -> DkgResult<()> {
let id = self
.all_participant_ids
.get(sender_ordinal)
.ok_or_else(|| {
Error::Round(format!(
"Round {round}: Unknown sender ordinal, {sender_ordinal}"
))
})?;
if *id != sender_id {
return Err(Error::Round(format!(
"Round {round}: Sender id mismatch, expected '{id}', got '{sender_id}'"
)));
}
if sender_id.is_zero().into() {
return Err(Error::Round(format!("Round {round}: Sender id is zero")));
}
if self.id.ct_eq(&sender_id).into() {
return Err(Error::Round(format!(
"Round {round}: Sender id is equal to our id",
)));
}
Ok(())
}
pub(crate) fn lagrange(
share: &SecretShare<G::Scalar>,
reconstruction_set: &ReconstructionSet<'_, G::Scalar>,
) -> ValuePrimeField<G::Scalar> {
let mut num = G::Scalar::ONE;
let mut den = G::Scalar::ONE;
for &x_j in reconstruction_set.identifiers() {
if x_j == share.identifier {
continue;
}
num *= *x_j;
den *= *x_j - *share.identifier;
}
let den_inverse = den.invert().unwrap_or(G::Scalar::ZERO);
IdentifierPrimeField(num * den_inverse)
}
}
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct SecretParticipantImpl<G>(PhantomData<G>);
impl<G> ParticipantImpl<G> for SecretParticipantImpl<G>
where
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
fn get_type(&self) -> ParticipantType {
ParticipantType::Secret
}
fn random_value(mut rng: impl CryptoRng) -> <G as Group>::Scalar {
G::Scalar::random(&mut rng)
}
fn check_feldman_verifier(verifier: G) -> bool {
verifier.is_identity().unwrap_u8() == 0u8
}
}
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct RefreshParticipantImpl<G>(PhantomData<G>);
impl<G> ParticipantImpl<G> for RefreshParticipantImpl<G>
where
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
fn get_type(&self) -> ParticipantType {
ParticipantType::Refresh
}
fn random_value(_rng: impl CryptoRng) -> <G as Group>::Scalar {
G::Scalar::ZERO
}
fn check_feldman_verifier(verifier: G) -> bool {
verifier.is_identity().into()
}
}
pub trait AnyParticipant<G>: Send + Sync + Debug
where
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
fn ordinal(&self) -> usize;
fn id(&self) -> IdentifierPrimeField<G::Scalar>;
fn threshold(&self) -> usize;
fn limit(&self) -> usize;
fn round(&self) -> Round;
fn secret_share(&self) -> Option<SecretShare<G::Scalar>>;
fn public_key(&self) -> Option<G>;
fn valid_participant_ids(&self) -> &[Option<IdentifierPrimeField<G::Scalar>>];
fn all_participant_ids(&self) -> &[IdentifierPrimeField<G::Scalar>];
fn feldman_verifiers(&self) -> &[ShareVerifierGroup<G>];
fn received_round1_data(&self) -> &[Option<Round1Data<G>>];
fn received_round2_data(&self) -> &[Option<Round2Data<G::Scalar>>];
fn verifying_share(&self) -> G;
fn final_transcript_hash(&self) -> [u8; 32];
fn completed(&self) -> bool;
fn receive(&mut self, data: &[u8]) -> DkgResult<()>;
fn run(&mut self) -> DkgResult<RoundOutputGenerator<G>>;
fn advance(&mut self) -> DkgResult<AdvanceResult<G::Scalar>>;
fn into_output(self: Box<Self>) -> DkgResult<DkgOutput<G>>;
#[deprecated(since = "0.6.0", note = "use `ordinal` instead")]
fn get_ordinal(&self) -> usize {
self.ordinal()
}
#[deprecated(since = "0.6.0", note = "use `id` instead")]
fn get_id(&self) -> IdentifierPrimeField<G::Scalar> {
self.id()
}
#[deprecated(since = "0.6.0", note = "use `threshold` instead")]
fn get_threshold(&self) -> usize {
self.threshold()
}
#[deprecated(since = "0.6.0", note = "use `limit` instead")]
fn get_limit(&self) -> usize {
self.limit()
}
#[deprecated(since = "0.6.0", note = "use `round` instead")]
fn get_round(&self) -> Round {
self.round()
}
#[deprecated(since = "0.6.0", note = "use `secret_share` instead")]
fn get_secret_share(&self) -> Option<SecretShare<G::Scalar>> {
self.secret_share()
}
#[deprecated(since = "0.6.0", note = "use `public_key` instead")]
fn get_public_key(&self) -> Option<G> {
self.public_key()
}
#[deprecated(since = "0.6.0", note = "use `valid_participant_ids` instead")]
fn get_valid_participant_ids(&self) -> &[Option<IdentifierPrimeField<G::Scalar>>] {
self.valid_participant_ids()
}
#[deprecated(since = "0.6.0", note = "use `all_participant_ids` instead")]
fn get_all_participant_ids(&self) -> &[IdentifierPrimeField<G::Scalar>] {
self.all_participant_ids()
}
#[deprecated(since = "0.6.0", note = "use `feldman_verifiers` instead")]
fn get_feldman_verifiers(&self) -> Vec<ShareVerifierGroup<G>> {
self.feldman_verifiers().to_vec()
}
#[deprecated(since = "0.6.0", note = "use `received_round1_data` instead")]
fn get_received_round1_data(&self) -> &[Option<Round1Data<G>>] {
self.received_round1_data()
}
#[deprecated(since = "0.6.0", note = "use `received_round2_data` instead")]
fn get_received_round2_data(&self) -> &[Option<Round2Data<G::Scalar>>] {
self.received_round2_data()
}
#[deprecated(since = "0.6.0", note = "use `verifying_share` instead")]
fn get_verifying_share(&self) -> G {
self.verifying_share()
}
#[deprecated(since = "0.6.0", note = "use `final_transcript_hash` instead")]
fn get_final_transcript_hash(&self) -> [u8; 32] {
self.final_transcript_hash()
}
}
impl<I, G> AnyParticipant<G> for Participant<I, G>
where
I: ParticipantImpl<G> + Default + Send + Sync,
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
fn ordinal(&self) -> usize {
self.ordinal
}
fn id(&self) -> IdentifierPrimeField<G::Scalar> {
self.id
}
fn threshold(&self) -> usize {
self.threshold
}
fn limit(&self) -> usize {
self.limit
}
fn round(&self) -> Round {
self.round
}
fn secret_share(&self) -> Option<SecretShare<G::Scalar>> {
self.secret_share()
}
fn public_key(&self) -> Option<G> {
self.public_key()
}
fn valid_participant_ids(&self) -> &[Option<IdentifierPrimeField<G::Scalar>>] {
&self.valid_participant_ids
}
fn all_participant_ids(&self) -> &[IdentifierPrimeField<G::Scalar>] {
&self.all_participant_ids
}
fn feldman_verifiers(&self) -> &[ShareVerifierGroup<G>] {
&self.feldman_verifiers
}
fn received_round1_data(&self) -> &[Option<Round1Data<G>>] {
&self.received_round1_data
}
fn received_round2_data(&self) -> &[Option<Round2Data<G::Scalar>>] {
&self.received_round2_data
}
fn verifying_share(&self) -> G {
self.verifying_share
}
fn final_transcript_hash(&self) -> [u8; 32] {
get_final_transcript_hash(&self.received_round1_data, &self.received_round2_data)
}
fn completed(&self) -> bool {
self.completed()
}
fn receive(&mut self, data: &[u8]) -> DkgResult<()> {
self.receive(data)
}
fn run(&mut self) -> DkgResult<RoundOutputGenerator<G>> {
self.run()
}
fn advance(&mut self) -> DkgResult<AdvanceResult<G::Scalar>> {
self.advance()
}
fn into_output(self: Box<Self>) -> DkgResult<DkgOutput<G>> {
(*self).into_output()
}
}
fn get_final_transcript_hash<G>(
received_round1_data: &[Option<Round1Data<G>>],
received_round2_data: &[Option<Round2Data<G::Scalar>>],
) -> [u8; 32]
where
G: SumOfProducts + GroupEncoding + Default + ConditionallySelectable,
G::Scalar: ScalarHash,
{
let mut transcript = merlin::Transcript::new(b"Frost DKG - Final Transcript");
for round1data in received_round1_data.iter().flatten() {
round1data.add_to_transcript(&mut transcript);
}
for round2data in received_round2_data.iter().flatten() {
round2data.add_to_transcript(&mut transcript);
}
let mut transcript_hash = [0u8; 32];
transcript.challenge_bytes(b"final result", &mut transcript_hash);
transcript_hash
}
#[cfg(test)]
mod tests {
use super::*;
use k256::{ProjectivePoint, Scalar};
use std::num::NonZeroUsize;
use vsss_rs::Share;
#[test]
fn receive_rejects_empty_message() {
let parameters = Parameters::new(
NonZeroUsize::new(2).expect("threshold is non-zero"),
NonZeroUsize::new(2).expect("limit is non-zero"),
)
.expect("valid parameters");
let mut participant = SecretParticipant::<ProjectivePoint>::new_secret(
IdentifierPrimeField::ONE,
¶meters,
)
.expect("create participant");
let result = participant.receive(&[]);
assert!(
matches!(result, Err(Error::InvalidMessage(message)) if message == "message is empty")
);
}
#[test]
fn debug_redacts_secret_state() {
let parameters = Parameters::new(
NonZeroUsize::new(2).expect("threshold is non-zero"),
NonZeroUsize::new(2).expect("limit is non-zero"),
)
.expect("valid parameters");
let participant = SecretParticipant::<ProjectivePoint>::new_secret(
IdentifierPrimeField::ONE,
¶meters,
)
.expect("create participant");
assert_eq!(participant.feldman_verifiers().len(), 2);
let debug = format!("{participant:?}");
assert!(!debug.contains("original_secret"));
assert!(!debug.contains("secret_share"));
assert!(!debug.contains("secret_shares"));
assert!(!debug.contains("received_round2_data"));
}
#[test]
fn output_is_unavailable_before_completion() {
let parameters = Parameters::new(
NonZeroUsize::new(2).expect("threshold is non-zero"),
NonZeroUsize::new(2).expect("limit is non-zero"),
)
.expect("valid parameters");
let participant = SecretParticipant::<ProjectivePoint>::new_secret(
IdentifierPrimeField::ONE,
¶meters,
)
.expect("create participant");
let result = participant.into_output();
assert!(matches!(result, Err(Error::Round(message)) if message.contains("not complete")));
}
#[test]
fn reconstruction_set_rejects_duplicate_identifiers() {
let identifier = IdentifierPrimeField(Scalar::ONE);
let identifiers = [identifier, identifier];
let result = ReconstructionSet::new(&identifiers);
assert!(matches!(result, Err(Error::Initialization(_))));
}
#[test]
fn refresh_rejects_a_share_with_a_different_identifier() {
let parameters = Parameters::new(
NonZeroUsize::new(2).expect("threshold is non-zero"),
NonZeroUsize::new(2).expect("limit is non-zero"),
)
.expect("valid parameters");
let share = SecretShare::with_identifier_and_value(
IdentifierPrimeField(Scalar::ONE),
IdentifierPrimeField(Scalar::ONE),
);
let result = RefreshParticipant::<ProjectivePoint>::new_refresh(
IdentifierPrimeField(Scalar::from(2u64)),
Some(&share),
¶meters,
);
assert!(
matches!(result, Err(Error::Initialization(message)) if message.contains("does not match"))
);
}
#[test]
fn resharing_rejects_a_share_missing_from_the_reconstruction_set() {
let parameters = Parameters::new(
NonZeroUsize::new(2).expect("threshold is non-zero"),
NonZeroUsize::new(3).expect("limit is non-zero"),
)
.expect("valid parameters");
let share = SecretShare::with_identifier_and_value(
IdentifierPrimeField(Scalar::ONE),
IdentifierPrimeField(Scalar::ONE),
);
let identifiers = [
IdentifierPrimeField(Scalar::from(2u64)),
IdentifierPrimeField(Scalar::from(3u64)),
];
let reconstruction_set =
ReconstructionSet::new(&identifiers).expect("valid reconstruction set");
let result = SecretParticipant::<ProjectivePoint>::with_secret(
IdentifierPrimeField::ONE,
&share,
¶meters,
&reconstruction_set,
);
assert!(
matches!(result, Err(Error::Initialization(message)) if message.contains("not included"))
);
}
}