Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add keygen output constructor and tests #478

Merged
merged 2 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions src/auxinfo/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::HashSet;

use crate::{
auxinfo::info::{AuxInfoPrivate, AuxInfoPublic},
errors::{CallerError, Result},
errors::{CallerError, InternalError, Result},
protocol::ParticipantIdentifier,
};
use tracing::error;
Expand Down Expand Up @@ -36,7 +36,7 @@ impl Output {
.map(AuxInfoPublic::participant)
.collect::<HashSet<_>>();
if pids.len() != public_auxinfo.len() {
error!("Tried to create a keygen output using a set of public material from non-unique participants");
error!("Tried to create an auxinfo output using a set of public material from non-unique participants");
Err(CallerError::BadInput)?
}

Expand Down Expand Up @@ -73,6 +73,21 @@ impl Output {
&self.public_auxinfo
}

pub(crate) fn private_pid(&self) -> Result<ParticipantIdentifier> {
let expected_public_key = self.private_auxinfo.encryption_key();
match self
.public_auxinfo
.iter()
.find(|auxinfo| &expected_public_key == auxinfo.pk())
{
Some(public_auxinfo) => Ok(public_auxinfo.participant()),
None => {
error!("Didn't find public aux info corresponding to the private aux info, but there should be one by construction");
Err(InternalError::InternalInvariantFailed)
}
}
}

pub(crate) fn find_public(&self, pid: ParticipantIdentifier) -> Option<&AuxInfoPublic> {
self.public_auxinfo
.iter()
Expand All @@ -93,10 +108,11 @@ mod tests {
};

impl Output {
/// Simulate the output of an auxinfo run with the given participants.
/// Simulate the valid output of an auxinfo run with the given
/// participants.
///
/// This should __never__ be called outside of tests! It does not
/// validate the PID input.
/// This should __never__ be called outside of tests! The given `pids`
/// must not contain duplicates.
pub(crate) fn simulate(
pids: &[ParticipantIdentifier],
rng: &mut (impl CryptoRng + RngCore),
Expand All @@ -118,43 +134,40 @@ mod tests {
})
.unzip();

Self {
private_auxinfo: private_auxinfo.pop().unwrap(),
public_auxinfo,
}
Self::from_parts(public_auxinfo, private_auxinfo.pop().unwrap()).unwrap()
}

/// Simulate a consistent output of an auxinfo run with the given
/// Simulate a consistent, valid output of an auxinfo run with the given
/// participants.
///
/// This produces output for every config in the provided set. The
/// config must have a non-zero length.
/// config must have a non-zero length and the given `pids` must not
/// contain duplicates.
pub(crate) fn simulate_set(
configs: &[ParticipantConfig],
rng: &mut (impl CryptoRng + RngCore),
) -> Vec<Self> {
let (private_auxinfo, public_auxinfo): (Vec<_>, Vec<_>) = configs
let (public_auxinfo, private_auxinfo): (Vec<_>, Vec<_>) = configs
.iter()
.map(|config| {
let (key, _, _) = DecryptionKey::new(rng).unwrap();
(
AuxInfoPrivate::from(key.clone()),
AuxInfoPublic::new(
&(),
config.id(),
key.encryption_key(),
VerifiedRingPedersen::extract(&key, &(), rng).unwrap(),
)
.unwrap(),
AuxInfoPrivate::from(key),
)
})
.unzip();

private_auxinfo
.into_iter()
.map(|private_auxinfo| Self {
private_auxinfo,
public_auxinfo: public_auxinfo.clone(),
.map(|private_auxinfo| {
Self::from_parts(public_auxinfo.clone(), private_auxinfo).unwrap()
})
.collect()
}
Expand All @@ -181,10 +194,24 @@ mod tests {
// Duplicate one of the PIDs
pids.push(pids[4]);

let output = Output::simulate(&pids, rng);
let (mut private_auxinfo, public_auxinfo): (Vec<_>, Vec<_>) = pids
.iter()
.map(|&pid| {
let (key, _, _) = DecryptionKey::new(rng).unwrap();
(
AuxInfoPrivate::from(key.clone()),
AuxInfoPublic::new(
&(),
pid,
key.encryption_key(),
VerifiedRingPedersen::extract(&key, &(), rng).unwrap(),
)
.unwrap(),
)
})
.unzip();

let (public, private) = output.into_parts();
assert!(Output::from_parts(public, private).is_err());
assert!(Output::from_parts(public_auxinfo, private_auxinfo.pop().unwrap()).is_err());
}

#[test]
Expand Down
4 changes: 3 additions & 1 deletion src/keygen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

mod keygen_commit;
mod keyshare;
mod output;
mod participant;

pub use keyshare::{KeySharePrivate, KeySharePublic};
pub use participant::{KeygenParticipant, Output};
pub use output::Output;
pub use participant::KeygenParticipant;
250 changes: 250 additions & 0 deletions src/keygen/output.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
// Copyright (c) 2022-2023 Bolt Labs Holdings, Inc
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.

use std::collections::HashSet;

use crate::{
errors::{CallerError, InternalError, Result},
keygen::keyshare::{KeySharePrivate, KeySharePublic},
utils::CurvePoint,
ParticipantIdentifier,
};

use k256::ecdsa::VerifyingKey;
use tracing::error;

/// Output type from key generation, including all parties' public key shares,
/// this party's private key share, and a bit of global randomness.
#[derive(Debug, Clone)]
pub struct Output {
public_key_shares: Vec<KeySharePublic>,
private_key_share: KeySharePrivate,
rid: [u8; 32],
}

impl Output {
/// Construct the generated public key.
pub fn public_key(&self) -> Result<VerifyingKey> {
// Add up all the key shares
let public_key_point = self
.public_key_shares
.iter()
.fold(CurvePoint::IDENTITY, |sum, share| sum + *share.as_ref());

VerifyingKey::from_encoded_point(&public_key_point.0.to_affine().into()).map_err(|_| {
error!("Keygen output does not produce a valid public key.");
InternalError::InternalInvariantFailed
})
}

pub(crate) fn public_key_shares(&self) -> &[KeySharePublic] {
&self.public_key_shares
}

pub(crate) fn private_key_share(&self) -> &KeySharePrivate {
&self.private_key_share
}

/// Get the [`ParticipantIdentifier`] corresponding to the
/// [`KeySharePrivate`].
pub(crate) fn private_pid(&self) -> Result<ParticipantIdentifier> {
let expected_public_share = self.private_key_share.public_share()?;
match self
.public_key_shares
.iter()
.find(|share| share.as_ref() == &expected_public_share)
{
Some(public_key_share) => Ok(public_key_share.participant()),
None => {
error!("Didn't find a public key share corresponding to the private key share, but there should be one by construction");
Err(InternalError::InternalInvariantFailed)
}
}
}

/// This could be made public if appropriate
#[cfg(test)]
pub(crate) fn rid(&self) -> &[u8; 32] {
&self.rid
}

/// Create a new `Output` from its constitutent parts.
///
/// This method should only be used with components that were previously
/// derived via the [`Output::into_parts()`] method; the calling application
/// should not try to form public and private key shares independently.
///
/// The provided components must satisfy the following properties:
/// - There is a valid key pair -- that is, the public key corresponding to
/// the private key share must be contained in the list of public shares.
/// - The public key shares must be from a unique set of participants
pub fn from_parts(
public_key_shares: Vec<KeySharePublic>,
private_key_share: KeySharePrivate,
rid: [u8; 32],
) -> Result<Self> {
let pids = public_key_shares
.iter()
.map(KeySharePublic::participant)
.collect::<HashSet<_>>();
if pids.len() != public_key_shares.len() {
error!("Tried to create a keygen output using a set of public material from non-unique participants");
Err(CallerError::BadInput)?
}

let expected_public_share = private_key_share.public_share()?;
if !public_key_shares
.iter()
.any(|share| share.as_ref() == &expected_public_share)
{
error!("Tried to create a keygen output using a private share with no corresponding public share");
Err(CallerError::BadInput)?
}

Ok(Self {
public_key_shares,
private_key_share,
rid,
})
}

/// Decompose the `Output` into its constituent parts.
///
/// # 🔒 Storage requirements
/// The [`KeySharePrivate`] must be stored securely by the calling
/// application, and a best effort should be made to drop it from memory
/// after it's securely stored.
///
/// The public components (including the byte array and the public key
/// shares) can be stored in the clear.
pub fn into_parts(self) -> (Vec<KeySharePublic>, KeySharePrivate, [u8; 32]) {
(self.public_key_shares, self.private_key_share, self.rid)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{utils::testing::init_testing, ParticipantConfig, ParticipantIdentifier};
use rand::{CryptoRng, Rng, RngCore};

impl Output {
/// Simulate the valid output of a keygen run with the given
/// participants.
///
/// This should __never__ be called outside of tests! The given `pids`
/// must not contain duplicates.
pub(crate) fn simulate(
pids: &[ParticipantIdentifier],
rng: &mut (impl CryptoRng + RngCore),
) -> Self {
let (mut private_key_shares, public_key_shares): (Vec<_>, Vec<_>) = pids
.iter()
.map(|&pid| {
// TODO #340: Replace with KeyShare methods once they exist.
let secret = KeySharePrivate::random(rng);
let public = secret.public_share().unwrap();
(secret, KeySharePublic::new(pid, public))
})
.unzip();

let rid = rng.gen();

Self::from_parts(public_key_shares, private_key_shares.pop().unwrap(), rid).unwrap()
}

/// Simulate a consistent, valid output of a keygen run with the given
/// participants.
///
/// This produces output for every config in the provided set. The
/// config must have a non-zero length, and the given `pids` must not
/// contain duplicates.
pub(crate) fn simulate_set(
configs: &[ParticipantConfig],
rng: &mut (impl CryptoRng + RngCore),
) -> Vec<Self> {
let (private_key_shares, public_key_shares): (Vec<_>, Vec<_>) = configs
.iter()
.map(|config| {
// TODO #340: Replace with KeyShare methods once they exist.
let secret = KeySharePrivate::random(rng);
let public = secret.public_share().unwrap();
(secret, KeySharePublic::new(config.id(), public))
})
.unzip();

let rid = rng.gen();

private_key_shares
.into_iter()
.map(|private_key_share| {
Self::from_parts(public_key_shares.clone(), private_key_share, rid).unwrap()
})
.collect()
}
}

#[test]
fn from_into_parts_works() {
let rng = &mut init_testing();
let pids = std::iter::repeat_with(|| ParticipantIdentifier::random(rng))
.take(5)
.collect::<Vec<_>>();
let output = Output::simulate(&pids, rng);

let (public, private, rid) = output.into_parts();
assert!(Output::from_parts(public, private, rid).is_ok());
}

#[test]
fn private_field_must_correspond_to_a_public() {
let rng = &mut init_testing();
let pids = std::iter::repeat_with(|| ParticipantIdentifier::random(rng))
.take(5)
.collect::<Vec<_>>();

// Use the simulate function to get a set of valid public components
let output = Output::simulate(&pids, rng);

// Create a random private share. It's legally possible for this to match one of
// the public keys but it's so unlikely that we won't check it.
let bad_private_key_share = KeySharePrivate::random(rng);

assert!(
Output::from_parts(output.public_key_shares, bad_private_key_share, output.rid)
.is_err()
)
}

#[test]
fn public_shares_must_not_have_duplicate_pids() {
let rng = &mut init_testing();
let mut pids = std::iter::repeat_with(|| ParticipantIdentifier::random(rng))
.take(5)
.collect::<Vec<_>>();

// Duplicate one of the PIDs
pids.push(pids[4]);

// Form output with the duplicated PID
let (mut private_key_shares, public_key_shares): (Vec<_>, Vec<_>) = pids
.iter()
.map(|&pid| {
// TODO #340: Replace with KeyShare methods once they exist.
let secret = KeySharePrivate::random(rng);
let public = secret.public_share().unwrap();
(secret, KeySharePublic::new(pid, public))
})
.unzip();

let rid = rng.gen();

assert!(
Output::from_parts(public_key_shares, private_key_shares.pop().unwrap(), rid).is_err()
);
}
}
Loading