mod shared_key;
pub use shared_key::*;
mod share;
pub use share::*;
mod session;
pub use session::*;
pub mod chilldkg;
pub use crate::binonce::{Nonce, NonceKeyPair};
use crate::{Message, Schnorr, binonce};
use alloc::collections::{BTreeMap, BTreeSet};
use secp256kfun::{
KeyPair, derive_nonce_rng,
hash::{Hash32, HashAdd, Tag},
nonce::{self, NonceGen},
poly,
prelude::*,
rand_core::{RngCore, SeedableRng},
};
pub type ShareIndex = Scalar<Public, NonZero>;
#[derive(Clone, Debug, PartialEq)]
pub struct Frost<H, NG> {
pub schnorr: Schnorr<H, NG>,
binding_hash: H,
nonce_gen: NG,
}
impl<H, NG> Default for Frost<H, NG>
where
H: Hash32,
NG: Default + Tag + Clone,
{
fn default() -> Self {
Frost::new(Schnorr::default())
}
}
impl<H, NG> Frost<H, NG> {
pub fn gen_nonce<R: RngCore>(&self, nonce_rng: &mut R) -> NonceKeyPair {
NonceKeyPair::random(nonce_rng)
}
pub fn nonce_gen(&self) -> &NG {
&self.nonce_gen
}
pub fn create_share(
&self,
scalar_poly: &[Scalar],
share_index: Scalar<impl Secrecy>,
) -> Scalar<Secret, Zero> {
poly::scalar::eval(scalar_poly, share_index)
}
}
impl<H, NG> Frost<H, NG>
where
H: Tag + Default,
NG: Tag + Clone,
{
pub fn new(schnorr: Schnorr<H, NG>) -> Self {
Self {
binding_hash: H::default().tag(b"frost/binding"),
nonce_gen: schnorr.nonce_gen().clone().tag(b"frost"),
schnorr,
}
}
}
impl<H: Hash32, NG: NonceGen> Frost<H, NG> {
pub fn seed_nonce_rng<R: SeedableRng<Seed = [u8; 32]>>(
&self,
paired_secret_share: PairedSecretShare<impl Normalized>,
session_id: &[u8],
) -> R {
let sid_len = (session_id.len() as u64).to_be_bytes();
let pk_bytes = paired_secret_share.public_key().to_xonly_bytes();
let rng: R = derive_nonce_rng!(
nonce_gen => self.nonce_gen(),
secret => paired_secret_share.share(),
public => [pk_bytes, sid_len, session_id],
seedable_rng => R
);
rng
}
}
impl<H: Hash32, NG> Frost<H, NG> {
pub fn aggregate_binonces(
&self,
nonces: impl IntoIterator<Item = Nonce>,
) -> binonce::Nonce<Zero> {
binonce::Nonce::aggregate(nonces)
}
pub fn party_sign_session(
&self,
public_key: Point<EvenY>,
parties: BTreeSet<ShareIndex>,
agg_binonce: binonce::Nonce<Zero>,
message: Message,
) -> PartySignSession {
let binding_coeff = self.binding_coefficient(public_key, agg_binonce, message, &parties);
let (final_nonce, binonce_needs_negation) = agg_binonce.bind(binding_coeff);
let challenge = self.schnorr.challenge(&final_nonce, &public_key, message);
PartySignSession {
public_key,
parties,
binding_coeff,
challenge,
binonce_needs_negation,
final_nonce,
}
}
pub fn coordinator_sign_session(
&self,
shared_key: &SharedKey<EvenY>,
nonces: BTreeMap<ShareIndex, Nonce>,
message: Message,
) -> CoordinatorSignSession {
self.coordinator_sign_session_(shared_key, nonces, message, KeyPair::zero())
}
pub fn randomized_coordinator_sign_session(
&self,
shared_key: &SharedKey<EvenY>,
nonces: BTreeMap<ShareIndex, Nonce>,
message: Message,
rng: &mut impl RngCore,
) -> CoordinatorSignSession {
self.coordinator_sign_session_(
shared_key,
nonces,
message,
KeyPair::new(Scalar::random(rng)),
)
}
fn coordinator_sign_session_(
&self,
shared_key: &SharedKey<EvenY>,
mut nonces: BTreeMap<ShareIndex, Nonce>,
message: Message,
randomization: KeyPair<impl PointType, impl ZeroChoice>,
) -> CoordinatorSignSession {
if nonces.len() < shared_key.threshold() {
panic!("nonces' length was less than the threshold");
}
let (mut randomization_scalar, randomization_point) = randomization.as_tuple();
let agg_binonce =
binonce::Nonce::aggregate_and_add(nonces.values().cloned(), randomization_point);
let binding_coeff = self.binding_coefficient(
shared_key.public_key(),
agg_binonce,
message,
&nonces.keys().cloned().collect(),
);
let (final_nonce, binonce_needs_negation) = agg_binonce.bind(binding_coeff);
let challenge = self
.schnorr
.challenge(&final_nonce, &shared_key.public_key(), message);
for nonce in nonces.values_mut() {
nonce.conditional_negate(binonce_needs_negation);
}
randomization_scalar.conditional_negate(binonce_needs_negation);
CoordinatorSignSession {
randomization: randomization_scalar.mark_zero(),
binding_coeff,
agg_binonce,
final_nonce,
challenge,
nonces,
public_key: shared_key.public_key(),
}
}
fn binding_coefficient(
&self,
public_key: Point<EvenY>,
agg_binonce: Nonce<Zero>,
message: Message,
parties: &BTreeSet<ShareIndex>,
) -> Scalar<Public> {
Scalar::from_hash(
self.binding_hash
.clone()
.add(public_key)
.add((parties.len() as u32).to_be_bytes())
.add(parties)
.add(agg_binonce)
.add(message),
)
.public()
}
}
pub fn new_with_deterministic_nonces<H>() -> Frost<H, nonce::Deterministic<H>>
where
H: Hash32,
{
Frost::default()
}
pub fn new_with_synthetic_nonces<H, R>() -> Frost<H, nonce::Synthetic<H, nonce::GlobalRng<R>>>
where
H: Hash32,
R: RngCore + Default + Clone,
{
Frost::default()
}
pub fn new_without_nonce_generation<H>() -> Frost<H, nonce::NoNonces>
where
H: Hash32,
{
Frost::default()
}
pub type SignatureShare = Scalar<Public, Zero>;
#[cfg(test)]
mod test {
use super::*;
use chilldkg::simplepedpop;
use sha2::Sha256;
#[test]
fn zero_agg_nonce_results_in_G() {
let frost = new_with_deterministic_nonces::<Sha256>();
let (frost_poly, _shares) =
simplepedpop::simulate_keygen(&frost.schnorr, 2, 3, 3, &mut rand::thread_rng());
let nonce = NonceKeyPair::random(&mut rand::thread_rng()).public();
let mut malicious_nonce = nonce;
malicious_nonce.conditional_negate(true);
let session = frost.coordinator_sign_session(
&frost_poly.into_xonly(),
BTreeMap::from_iter([(s!(1).public(), nonce), (s!(2).public(), malicious_nonce)]),
Message::new("test", b"hello"),
);
assert_eq!(session.final_nonce(), *G);
}
}