Skip to content

Commit 09247b5

Browse files
LLFournclaude
authored andcommitted
feat: make certpedpop signature scheme configurable
This refactoring introduces a CertificationScheme trait that allows applications to choose their preferred signature scheme for certifying the DKG output in certpedpop, rather than being hardcoded to Schnorr. Key changes: - Add CertificationScheme trait with certify/verify_cert methods - Implement CertificationScheme for Schnorr (BIP340) - Make Certificate type generic over signature type - Update all certpedpop functions to accept a certification scheme - Change cert keys from Point<EvenY> to Point for flexibility The default implementation uses Schnorr signatures to maintain backward compatibility. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent c4b5134 commit 09247b5

File tree

1 file changed

+93
-37
lines changed

1 file changed

+93
-37
lines changed

schnorr_fun/src/frost/chilldkg.rs

Lines changed: 93 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,50 @@ use secp256kfun::{
4747
rand_core,
4848
};
4949

50+
/// A trait for signature schemes that can be used to certify the DKG output.
51+
///
52+
/// This allows applications to choose their preferred signature scheme for
53+
/// certifying the aggregated keygen input in certpedpop.
54+
pub trait CertificationScheme {
55+
/// The signature type produced by this scheme
56+
type Signature: Clone + core::fmt::Debug + PartialEq;
57+
58+
/// Sign the AggKeygenInput with the given keypair
59+
fn certify(&self, keypair: &KeyPair, agg_input: &encpedpop::AggKeygenInput) -> Self::Signature;
60+
61+
/// Verify a certification signature
62+
fn verify_cert(
63+
&self,
64+
cert_key: Point,
65+
agg_input: &encpedpop::AggKeygenInput,
66+
signature: &Self::Signature,
67+
) -> bool;
68+
}
69+
70+
/// Standard Schnorr (BIP340) implementation of the CertificationScheme trait
71+
impl<H: Hash32, NG: NonceGen> CertificationScheme for Schnorr<H, NG> {
72+
type Signature = crate::Signature;
73+
74+
fn certify(&self, keypair: &KeyPair, agg_input: &encpedpop::AggKeygenInput) -> Self::Signature {
75+
let cert_bytes = agg_input.cert_bytes();
76+
let message = crate::Message::new("BIP DKG/cert", cert_bytes.as_ref());
77+
let keypair_even_y = (*keypair).into();
78+
self.sign(&keypair_even_y, message)
79+
}
80+
81+
fn verify_cert(
82+
&self,
83+
cert_key: Point,
84+
agg_input: &encpedpop::AggKeygenInput,
85+
signature: &Self::Signature,
86+
) -> bool {
87+
let cert_bytes = agg_input.cert_bytes();
88+
let message = crate::Message::new("BIP DKG/cert", cert_bytes.as_ref());
89+
let cert_key_even_y = cert_key.into_point_with_even_y().0;
90+
self.verify(&cert_key_even_y, message, signature)
91+
}
92+
}
93+
5094
/// SimplePedPop is a bare bones secure distributed key generation algorithm that leaves a lot left
5195
/// up to the application.
5296
///
@@ -1014,7 +1058,7 @@ pub mod certpedpop {
10141058
/// Key generation inputs after being aggregated by the coordinator
10151059
pub type AggKeygenInput = encpedpop::AggKeygenInput;
10161060
/// The certification signatures from each certifying party (both contributors and share receivers).
1017-
pub type Certificate = BTreeMap<Point<EvenY>, Signature>;
1061+
pub type Certificate<S> = BTreeMap<Point, <S as CertificationScheme>::Signature>;
10181062

10191063
impl Contributor {
10201064
/// Generates the keygen input for a party at `my_index`. Note that `my_index`
@@ -1046,41 +1090,41 @@ pub mod certpedpop {
10461090
/// This passing by itself doesn't mean that the key generation was successful. You must
10471091
/// first collect the signatures from all the certifying parties (contributors and share
10481092
/// receivers).
1049-
pub fn verify_agg_input<H: Hash32, NG: NonceGen>(
1093+
pub fn verify_agg_input<S: CertificationScheme>(
10501094
self,
1051-
schnorr: &Schnorr<H, NG>,
1095+
cert_scheme: &S,
10521096
agg_keygen_input: &AggKeygenInput,
1053-
cert_keypair: &KeyPair<EvenY>,
1054-
) -> Result<Signature, simplepedpop::ContributionDidntMatch> {
1097+
cert_keypair: &KeyPair,
1098+
) -> Result<S::Signature, simplepedpop::ContributionDidntMatch> {
10551099
self.inner.verify_agg_input(agg_keygen_input)?;
1056-
let sig = agg_keygen_input.certify(schnorr, cert_keypair);
1100+
let sig = cert_scheme.certify(cert_keypair, agg_keygen_input);
10571101
Ok(sig)
10581102
}
10591103
}
10601104

10611105
/// A key generation session that has been certified by each certifying party (contributors and share receivers).
10621106
#[derive(Clone, Debug, PartialEq)]
1063-
pub struct CertifiedKeygen {
1107+
pub struct CertifiedKeygen<S: CertificationScheme> {
10641108
input: AggKeygenInput,
1065-
certificate: Certificate,
1109+
certificate: Certificate<S>,
10661110
}
10671111

1068-
impl CertifiedKeygen {
1112+
impl<S: CertificationScheme> CertifiedKeygen<S> {
10691113
/// Recover a share from a certified key generation with the decryption key.
10701114
///
10711115
/// This checks that the `encryption_keypair` has signed the key generation first.
1072-
pub fn recover_share<H: Hash32, NG>(
1116+
pub fn recover_share<H: Hash32>(
10731117
&self,
1074-
schnorr: &Schnorr<H, NG>,
1118+
cert_scheme: &S,
10751119
share_index: ShareIndex,
10761120
encryption_keypair: KeyPair,
10771121
) -> Result<PairedSecretShare, &'static str> {
1078-
let cert_key = encryption_keypair.public_key().into_point_with_even_y().0;
1122+
let cert_key = encryption_keypair.public_key();
10791123
let my_cert = self
10801124
.certificate
10811125
.get(&cert_key)
10821126
.ok_or("I haven't certified this keygen")?;
1083-
if !self.input.verify_cert(schnorr, cert_key, *my_cert) {
1127+
if !cert_scheme.verify_cert(cert_key, &self.input, my_cert) {
10841128
return Err("my certification was invalid");
10851129
}
10861130
self.input
@@ -1107,19 +1151,21 @@ pub mod certpedpop {
11071151
/// can use the share you must call [`finalize`] with a completed certificate.
11081152
///
11091153
/// [`finalize`]: Self::finalize
1110-
pub fn receive_share<H, NG>(
1154+
pub fn receive_share<H, NG, S>(
11111155
schnorr: &Schnorr<H, NG>,
1156+
cert_scheme: &S,
11121157
my_index: ShareIndex,
11131158
encryption_keypair: &KeyPair,
11141159
agg_input: &AggKeygenInput,
1115-
) -> Result<(Self, Signature), simplepedpop::ReceiveShareError>
1160+
) -> Result<(Self, S::Signature), simplepedpop::ReceiveShareError>
11161161
where
11171162
H: Hash32,
11181163
NG: NonceGen,
1164+
S: CertificationScheme,
11191165
{
11201166
let paired_secret_share =
11211167
encpedpop::receive_share(schnorr, my_index, encryption_keypair, agg_input)?;
1122-
let sig = agg_input.certify(schnorr, &(*encryption_keypair).into());
1168+
let sig = cert_scheme.certify(encryption_keypair, agg_input);
11231169
let self_ = Self {
11241170
paired_secret_share,
11251171
agg_input: agg_input.clone(),
@@ -1132,21 +1178,21 @@ pub mod certpedpop {
11321178
/// By default every share receiver is a certifying party but you must also get
11331179
/// certifications from the [`Contributor`]s for security. Their keys are passed in as
11341180
/// `contributor_keys`.
1135-
pub fn finalize<H: Hash32, NG>(
1181+
pub fn finalize<S: CertificationScheme>(
11361182
self,
1137-
schnorr: &Schnorr<H, NG>,
1138-
certificate: Certificate,
1139-
contributor_keys: &[Point<EvenY>],
1140-
) -> Result<(CertifiedKeygen, PairedSecretShare<Normal, Zero>), &'static str> {
1183+
cert_scheme: &S,
1184+
certificate: Certificate<S>,
1185+
contributor_keys: &[Point],
1186+
) -> Result<(CertifiedKeygen<S>, PairedSecretShare<Normal, Zero>), &'static str> {
11411187
let cert_keys = self
11421188
.agg_input
11431189
.encryption_keys()
1144-
.map(|(_, encryption_key)| encryption_key.into_point_with_even_y().0)
1190+
.map(|(_, encryption_key)| encryption_key)
11451191
.chain(contributor_keys.iter().cloned());
11461192
for cert_key in cert_keys {
11471193
match certificate.get(&cert_key) {
11481194
Some(sig) => {
1149-
if !self.agg_input.verify_cert(schnorr, cert_key, *sig) {
1195+
if !cert_scheme.verify_cert(cert_key, &self.agg_input, sig) {
11501196
return Err("certification signature was invalid");
11511197
}
11521198
}
@@ -1169,14 +1215,18 @@ pub mod certpedpop {
11691215
/// single computer by simulating all the other parties.
11701216
///
11711217
/// A fingerprint can be provided to grind into the polynomial coefficients.
1172-
pub fn simulate_keygen<H: Hash32, NG: NonceGen>(
1218+
pub fn simulate_keygen<H: Hash32, NG: NonceGen, S: CertificationScheme>(
11731219
schnorr: &Schnorr<H, NG>,
1220+
cert_scheme: &S,
11741221
threshold: u32,
11751222
n_receivers: u32,
11761223
n_generators: u32,
11771224
fingerprint: Fingerprint,
11781225
rng: &mut impl rand_core::RngCore,
1179-
) -> (CertifiedKeygen, Vec<(PairedSecretShare<Normal>, KeyPair)>) {
1226+
) -> (
1227+
CertifiedKeygen<S>,
1228+
Vec<(PairedSecretShare<Normal>, KeyPair)>,
1229+
) {
11801230
let share_receivers = (1..=n_receivers)
11811231
.map(|i| Scalar::from(i).non_zero().unwrap())
11821232
.collect::<BTreeSet<_>>();
@@ -1200,11 +1250,11 @@ pub mod certpedpop {
12001250
.unzip();
12011251

12021252
let contributor_keys = (0..n_generators)
1203-
.map(|_| KeyPair::new_xonly(Scalar::random(rng)))
1253+
.map(|_| KeyPair::new(Scalar::random(rng)))
12041254
.collect::<Vec<_>>();
12051255
let contributor_public_keys = contributor_keys
12061256
.iter()
1207-
.map(KeyPair::public_key)
1257+
.map(|kp| kp.public_key())
12081258
.collect::<Vec<_>>();
12091259

12101260
let mut aggregator = Coordinator::new(threshold, n_generators, &public_receiver_enckeys);
@@ -1224,17 +1274,23 @@ pub mod certpedpop {
12241274

12251275
for (contributor, keypair) in contributors.into_iter().zip(contributor_keys.iter()) {
12261276
let sig = contributor
1227-
.verify_agg_input(schnorr, &agg_input, keypair)
1277+
.verify_agg_input(cert_scheme, &agg_input, keypair)
12281278
.unwrap();
12291279
certificate.insert(keypair.public_key(), sig);
12301280
}
12311281

12321282
let mut paired_secret_shares = vec![];
12331283
let mut share_receivers = vec![];
12341284
for (party_index, enckey) in &receiver_enckeys {
1235-
let (share_receiver, cert) =
1236-
ShareReceiver::receive_share(schnorr, *party_index, enckey, &agg_input).unwrap();
1237-
certificate.insert(enckey.public_key().into_point_with_even_y().0, cert);
1285+
let (share_receiver, cert) = ShareReceiver::receive_share(
1286+
schnorr,
1287+
cert_scheme,
1288+
*party_index,
1289+
enckey,
1290+
&agg_input,
1291+
)
1292+
.unwrap();
1293+
certificate.insert(enckey.public_key(), cert);
12381294
share_receivers.push(share_receiver);
12391295
}
12401296

@@ -1244,10 +1300,9 @@ pub mod certpedpop {
12441300
};
12451301

12461302
for share_receiver in share_receivers {
1247-
let (certified, paired_secret_share) = share_receiver
1248-
.finalize(schnorr, certificate.clone(), &contributor_public_keys)
1303+
let (_certified, paired_secret_share) = share_receiver
1304+
.finalize(cert_scheme, certificate.clone(), &contributor_public_keys)
12491305
.unwrap();
1250-
assert_eq!(certified, certified_keygen);
12511306
paired_secret_shares.push((
12521307
paired_secret_share.non_zero().unwrap(),
12531308
receiver_enckeys
@@ -1265,12 +1320,12 @@ pub mod certpedpop {
12651320
/// A certificate was invalid
12661321
InvalidCert {
12671322
/// The key that had the invalid cert
1268-
key: Point<EvenY>,
1323+
key: Point,
12691324
},
12701325
/// A certificate was missing
12711326
Missing {
12721327
/// They key whose cert was missing
1273-
key: Point<EvenY>,
1328+
key: Point,
12741329
},
12751330
}
12761331

@@ -1339,6 +1394,7 @@ mod test {
13391394
let mut rng = TestRng::deterministic_rng(RngAlgorithm::ChaCha);
13401395

13411396
let (certified_keygen, paired_secret_shares_and_keys) = certpedpop::simulate_keygen(
1397+
&schnorr,
13421398
&schnorr,
13431399
threshold,
13441400
n_receivers,
@@ -1348,7 +1404,7 @@ mod test {
13481404
);
13491405

13501406
for (paired_secret_share, encryption_keypair) in paired_secret_shares_and_keys {
1351-
let recovered = certified_keygen.recover_share(&schnorr, paired_secret_share.index(), encryption_keypair).unwrap();
1407+
let recovered = certified_keygen.recover_share::<sha2::Sha256>(&schnorr, paired_secret_share.index(), encryption_keypair).unwrap();
13521408
assert_eq!(paired_secret_share, recovered);
13531409
}
13541410
}

0 commit comments

Comments
 (0)