Skip to content

Commit f203001

Browse files
LLFournclaude
authored andcommitted
feat: implement VRF-based certification for certpedpop
- Add CertificationScheme trait with Output associated type - Replace Certificate type alias with newtype struct - Implement VRF certification with randomness beacon - Add compute_randomness_beacon method to CertifiedKeygen - Update all references to use concrete signature types 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent ca623e5 commit f203001

File tree

1 file changed

+152
-92
lines changed

1 file changed

+152
-92
lines changed

schnorr_fun/src/frost/chilldkg.rs

Lines changed: 152 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,25 @@ pub trait CertificationScheme {
5555
/// The signature type produced by this scheme
5656
type Signature: Clone + core::fmt::Debug;
5757

58+
/// The output produced by successful verification
59+
type Output: Clone + core::fmt::Debug;
60+
5861
/// Sign the AggKeygenInput with the given keypair
5962
fn certify(&self, keypair: &KeyPair, agg_input: &encpedpop::AggKeygenInput) -> Self::Signature;
6063

61-
/// Verify a certification signature
64+
/// Verify a certification signature and return the output
6265
fn verify_cert(
6366
&self,
6467
cert_key: Point,
6568
agg_input: &encpedpop::AggKeygenInput,
6669
signature: &Self::Signature,
67-
) -> bool;
70+
) -> Option<Self::Output>;
6871
}
6972

7073
/// Standard Schnorr (BIP340) implementation of the CertificationScheme trait
7174
impl<H: Hash32, NG: NonceGen> CertificationScheme for Schnorr<H, NG> {
7275
type Signature = crate::Signature;
76+
type Output = ();
7377

7478
fn certify(&self, keypair: &KeyPair, agg_input: &encpedpop::AggKeygenInput) -> Self::Signature {
7579
let cert_bytes = agg_input.cert_bytes();
@@ -83,11 +87,15 @@ impl<H: Hash32, NG: NonceGen> CertificationScheme for Schnorr<H, NG> {
8387
cert_key: Point,
8488
agg_input: &encpedpop::AggKeygenInput,
8589
signature: &Self::Signature,
86-
) -> bool {
90+
) -> Option<Self::Output> {
8791
let cert_bytes = agg_input.cert_bytes();
8892
let message = crate::Message::new("BIP DKG/cert", cert_bytes.as_ref());
8993
let cert_key_even_y = cert_key.into_point_with_even_y().0;
90-
self.verify(&cert_key_even_y, message, signature)
94+
if self.verify(&cert_key_even_y, message, signature) {
95+
Some(())
96+
} else {
97+
None
98+
}
9199
}
92100
}
93101

@@ -97,29 +105,20 @@ pub mod vrf_cert {
97105
use super::*;
98106
use vrf_fun::VrfProof;
99107

100-
/// A wrapper type for using VRF as a certification scheme
101-
pub struct VrfCertifier<V> {
102-
/// The underlying VRF instance
103-
pub vrf: V,
104-
}
105-
106-
/// Create a VRF certification scheme with the RFC 9381 SSWU suite using SHA256
107-
pub fn sswu_vrf_sha256() -> VrfCertifier<vrf_fun::Rfc9381SswuVrf<sha2::Sha256>> {
108-
VrfCertifier {
109-
vrf: vrf_fun::Rfc9381SswuVrf::default(),
110-
}
111-
}
108+
/// VRF certification scheme using SSWU VRF
109+
pub struct VrfCertifier;
112110

113-
/// Create a VRF certification scheme with the RFC 9381 TAI suite using SHA256
114-
pub fn tai_vrf_sha256() -> VrfCertifier<vrf_fun::Rfc9381TaiVrf<sha2::Sha256>> {
115-
VrfCertifier {
116-
vrf: vrf_fun::Rfc9381TaiVrf::default(),
117-
}
111+
/// The output from VRF verification containing the gamma point
112+
#[derive(Clone, Debug)]
113+
pub struct VrfOutput {
114+
/// The VRF output point (gamma)
115+
pub gamma: Point,
118116
}
119117

120-
/// Implement CertificationScheme for VrfCertifier wrapping the SSWU VRF
121-
impl CertificationScheme for VrfCertifier<vrf_fun::Rfc9381SswuVrf<sha2::Sha256>> {
118+
/// Implement CertificationScheme for VrfCertifier
119+
impl CertificationScheme for VrfCertifier {
122120
type Signature = VrfProof;
121+
type Output = VrfOutput;
123122

124123
fn certify(
125124
&self,
@@ -136,67 +135,15 @@ pub mod vrf_cert {
136135
cert_key: Point,
137136
agg_input: &encpedpop::AggKeygenInput,
138137
signature: &Self::Signature,
139-
) -> bool {
140-
// Use the certification bytes as the VRF input
141-
let cert_bytes = agg_input.cert_bytes();
142-
vrf_fun::rfc9381::sswu::verify::<sha2::Sha256>(cert_key, &cert_bytes, signature)
143-
.is_some()
144-
}
145-
}
146-
147-
/// Implement CertificationScheme for VrfCertifier wrapping the TAI VRF
148-
impl CertificationScheme for VrfCertifier<vrf_fun::Rfc9381TaiVrf<sha2::Sha256>> {
149-
type Signature = VrfProof;
150-
151-
fn certify(
152-
&self,
153-
keypair: &KeyPair,
154-
agg_input: &encpedpop::AggKeygenInput,
155-
) -> Self::Signature {
156-
// Use the certification bytes as the VRF input
157-
let cert_bytes = agg_input.cert_bytes();
158-
vrf_fun::rfc9381::tai::prove::<sha2::Sha256>(keypair, &cert_bytes)
159-
}
160-
161-
fn verify_cert(
162-
&self,
163-
cert_key: Point,
164-
agg_input: &encpedpop::AggKeygenInput,
165-
signature: &Self::Signature,
166-
) -> bool {
138+
) -> Option<Self::Output> {
167139
// Use the certification bytes as the VRF input
168140
let cert_bytes = agg_input.cert_bytes();
169-
vrf_fun::rfc9381::tai::verify::<sha2::Sha256>(cert_key, &cert_bytes, signature)
170-
.is_some()
171-
}
172-
}
173-
174-
/// Compute a randomness beacon from a set of VRF outputs
175-
///
176-
/// This function takes all the VRF outputs (gamma points) from the certificate
177-
/// and hashes them together to produce unpredictable randomness that no single
178-
/// party could have controlled (as long as at least one party is honest).
179-
pub fn compute_randomness_beacon<S>(certificate: &certpedpop::Certificate<S>) -> [u8; 32]
180-
where
181-
S: CertificationScheme,
182-
S::Signature: AsRef<VrfProof>,
183-
{
184-
use sha2::{Digest, Sha256};
185-
186-
let mut hasher = Sha256::new();
187-
188-
// Sort by public key to ensure deterministic ordering
189-
let mut sorted_entries: Vec<_> = certificate.iter().collect();
190-
sorted_entries.sort_by_key(|(pk, _)| pk.to_bytes());
191-
192-
// Hash all the VRF gamma points
193-
for (_, vrf_proof) in sorted_entries {
194-
let gamma = vrf_proof.as_ref().gamma;
195-
hasher.update(gamma.to_bytes());
141+
vrf_fun::rfc9381::sswu::verify::<sha2::Sha256>(cert_key, &cert_bytes, signature).map(
142+
|output| VrfOutput {
143+
gamma: output.gamma,
144+
},
145+
)
196146
}
197-
198-
// Get the hash output
199-
hasher.finalize().into()
200147
}
201148
}
202149

@@ -1166,8 +1113,37 @@ pub mod certpedpop {
11661113
pub type KeygenInput = encpedpop::KeygenInput;
11671114
/// Key generation inputs after being aggregated by the coordinator
11681115
pub type AggKeygenInput = encpedpop::AggKeygenInput;
1169-
/// The certification signatures from each certifying party (both contributors and share receivers).
1170-
pub type Certificate<S> = BTreeMap<Point, <S as CertificationScheme>::Signature>;
1116+
/// A certificate containing signatures or proofs from certifying parties
1117+
#[derive(Clone, Debug)]
1118+
pub struct Certificate<Sig>(BTreeMap<Point, Sig>);
1119+
1120+
impl<Sig> Default for Certificate<Sig> {
1121+
fn default() -> Self {
1122+
Self(BTreeMap::new())
1123+
}
1124+
}
1125+
1126+
impl<Sig> Certificate<Sig> {
1127+
/// Create a new empty certificate
1128+
pub fn new() -> Self {
1129+
Self::default()
1130+
}
1131+
1132+
/// Insert a signature/proof for a public key
1133+
pub fn insert(&mut self, key: Point, sig: Sig) {
1134+
self.0.insert(key, sig);
1135+
}
1136+
1137+
/// Get the signature/proof for a public key
1138+
pub fn get(&self, key: &Point) -> Option<&Sig> {
1139+
self.0.get(key)
1140+
}
1141+
1142+
/// Iterate over all entries in the certificate
1143+
pub fn iter(&self) -> impl Iterator<Item = (&Point, &Sig)> {
1144+
self.0.iter()
1145+
}
1146+
}
11711147

11721148
impl Contributor {
11731149
/// Generates the keygen input for a party at `my_index`. Note that `my_index`
@@ -1215,7 +1191,9 @@ pub mod certpedpop {
12151191
#[derive(Clone, Debug)]
12161192
pub struct CertifiedKeygen<S: CertificationScheme> {
12171193
input: AggKeygenInput,
1218-
certificate: Certificate<S>,
1194+
certificate: Certificate<S::Signature>,
1195+
/// The outputs from successful verification, indexed by certifying party's public key
1196+
outputs: BTreeMap<Point, S::Output>,
12191197
}
12201198

12211199
impl<S: CertificationScheme> CertifiedKeygen<S> {
@@ -1233,7 +1211,10 @@ pub mod certpedpop {
12331211
.certificate
12341212
.get(&cert_key)
12351213
.ok_or("I haven't certified this keygen")?;
1236-
if !cert_scheme.verify_cert(cert_key, &self.input, my_cert) {
1214+
if cert_scheme
1215+
.verify_cert(cert_key, &self.input, my_cert)
1216+
.is_none()
1217+
{
12371218
return Err("my certification was invalid");
12381219
}
12391220
self.input.recover_share::<H>(share_index, &keypair)
@@ -1243,6 +1224,37 @@ pub mod certpedpop {
12431224
pub fn inner(&self) -> &AggKeygenInput {
12441225
&self.input
12451226
}
1227+
1228+
/// Gets the certificate.
1229+
pub fn certificate(&self) -> &Certificate<S::Signature> {
1230+
&self.certificate
1231+
}
1232+
1233+
/// Gets the verification outputs.
1234+
pub fn outputs(&self) -> &BTreeMap<Point, S::Output> {
1235+
&self.outputs
1236+
}
1237+
}
1238+
1239+
#[cfg(feature = "vrf_cert_keygen")]
1240+
impl CertifiedKeygen<vrf_cert::VrfCertifier> {
1241+
/// Compute a randomness beacon from the VRF outputs
1242+
///
1243+
/// This function hashes all the VRF gamma points together to produce
1244+
/// unpredictable randomness that no single party could have controlled
1245+
/// (as long as at least one party is honest).
1246+
pub fn compute_randomness_beacon(&self) -> [u8; 32] {
1247+
use sha2::{Digest, Sha256};
1248+
1249+
let mut hasher = Sha256::new();
1250+
1251+
// BTreeMap already maintains sorted order by key
1252+
for output in self.outputs.values() {
1253+
hasher.update(output.gamma.to_bytes());
1254+
}
1255+
1256+
hasher.finalize().into()
1257+
}
12461258
}
12471259

12481260
pub use encpedpop::Coordinator;
@@ -1289,28 +1301,31 @@ pub mod certpedpop {
12891301
pub fn finalize<S: CertificationScheme>(
12901302
self,
12911303
cert_scheme: &S,
1292-
certificate: Certificate<S>,
1304+
certificate: Certificate<S::Signature>,
12931305
contributor_keys: &[Point],
12941306
) -> Result<(CertifiedKeygen<S>, PairedSecretShare<Normal, Zero>), &'static str> {
1307+
let mut outputs = BTreeMap::new();
12951308
let cert_keys = self
12961309
.agg_input
12971310
.encryption_keys()
12981311
.map(|(_, encryption_key)| encryption_key)
12991312
.chain(contributor_keys.iter().cloned());
13001313
for cert_key in cert_keys {
13011314
match certificate.get(&cert_key) {
1302-
Some(sig) => {
1303-
if !cert_scheme.verify_cert(cert_key, &self.agg_input, sig) {
1304-
return Err("certification signature was invalid");
1315+
Some(sig) => match cert_scheme.verify_cert(cert_key, &self.agg_input, sig) {
1316+
Some(output) => {
1317+
outputs.insert(cert_key, output);
13051318
}
1306-
}
1319+
None => return Err("certification signature was invalid"),
1320+
},
13071321
None => return Err("missing certification signature"),
13081322
}
13091323
}
13101324

13111325
let certified_keygen = CertifiedKeygen {
13121326
input: self.agg_input,
13131327
certificate,
1328+
outputs,
13141329
};
13151330

13161331
Ok((certified_keygen, self.paired_secret_share))
@@ -1377,8 +1392,7 @@ pub mod certpedpop {
13771392

13781393
// Apply fingerprint grinding
13791394
agg_input.grind_fingerprint::<H>(fingerprint);
1380-
1381-
let mut certificate = BTreeMap::default();
1395+
let mut certificate = Certificate::new();
13821396

13831397
for (contributor, keypair) in contributors.into_iter().zip(contributor_keys.iter()) {
13841398
let sig = contributor
@@ -1402,9 +1416,18 @@ pub mod certpedpop {
14021416
share_receivers.push(share_receiver);
14031417
}
14041418

1419+
// Collect outputs by verifying all certificates
1420+
let mut outputs = BTreeMap::new();
1421+
for (key, sig) in certificate.iter() {
1422+
if let Some(output) = cert_scheme.verify_cert(*key, &agg_input, sig) {
1423+
outputs.insert(*key, output);
1424+
}
1425+
}
1426+
14051427
let certified_keygen = CertifiedKeygen {
14061428
input: agg_input.clone(),
14071429
certificate: certificate.clone(),
1430+
outputs,
14081431
};
14091432

14101433
for share_receiver in share_receivers {
@@ -1549,4 +1572,41 @@ mod test {
15491572
assert!(shared_key.check_fingerprint::<sha2::Sha256>(&fingerprint), "fingerprint was grinded correctly");
15501573
}
15511574
}
1575+
1576+
#[test]
1577+
#[cfg(feature = "vrf_cert_keygen")]
1578+
fn vrf_certified_keygen_randomness_beacon() {
1579+
use proptest::test_runner::{RngAlgorithm, TestRng};
1580+
1581+
let schnorr = crate::new_with_deterministic_nonces::<sha2::Sha256>();
1582+
let vrf_certifier = vrf_cert::VrfCertifier;
1583+
let mut rng = TestRng::deterministic_rng(RngAlgorithm::ChaCha);
1584+
1585+
let threshold = 2;
1586+
let n_receivers = 3;
1587+
let n_generators = 2;
1588+
1589+
let (certified_keygen, _) = certpedpop::simulate_keygen(
1590+
&schnorr,
1591+
&vrf_certifier,
1592+
threshold,
1593+
n_receivers,
1594+
n_generators,
1595+
Fingerprint::none(),
1596+
&mut rng,
1597+
);
1598+
1599+
// Compute randomness beacon from the VRF outputs
1600+
let randomness = certified_keygen.compute_randomness_beacon();
1601+
1602+
// Verify the randomness is deterministic
1603+
let randomness2 = certified_keygen.compute_randomness_beacon();
1604+
assert_eq!(randomness, randomness2);
1605+
1606+
// Verify we have the expected number of VRF outputs
1607+
assert_eq!(
1608+
certified_keygen.outputs().len(),
1609+
(n_receivers + n_generators) as usize
1610+
);
1611+
}
15521612
}

0 commit comments

Comments
 (0)