Skip to content

Commit d548ad8

Browse files
authored
Include group public key in binding value computation (#228)
* add helper functions and parameters to implement IETF from binding value computation pass group key when necessary to compute binding value use iter instead of into_iter when possible set aggregate public key in nonce tests; use aggregate public key not tweaked public key when computing binding values use RFC names for nonce commitments; add comments explaining what goes into the commitment lists; prefer let/else rather than if let/else replace R with aggregate_nonce * use descriptove variable name for aggregate nonce in frost coordinator * keep XMD message expansion
1 parent 7549148 commit d548ad8

6 files changed

Lines changed: 124 additions & 44 deletions

File tree

src/compute.rs

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,74 @@ use crate::{
1313
};
1414

1515
#[allow(non_snake_case)]
16-
/// Compute a binding value from the party ID, public nonces, and signed message using XMD-based expansion.
17-
pub fn binding(id: &Scalar, B: &[PublicNonce], msg: &[u8]) -> Scalar {
18-
let prefix = b"WSTS/binding";
16+
/// Compute the group commitment from the list of PartyIDs and nonce commitments using XMD-based expansion.
17+
pub fn group_commitment(commitment_list: &[(Scalar, PublicNonce)]) -> Scalar {
18+
let prefix = b"WSTS/group_commitment";
1919

20-
// Serialize all input into a buffer
2120
let mut buf = Vec::new();
22-
buf.extend_from_slice(&id.to_bytes());
23-
24-
for b in B {
25-
buf.extend_from_slice(b.D.compress().as_bytes());
26-
buf.extend_from_slice(b.E.compress().as_bytes());
21+
for (id, public_nonce) in commitment_list {
22+
buf.extend_from_slice(&id.to_bytes());
23+
buf.extend_from_slice(public_nonce.D.compress().as_bytes());
24+
buf.extend_from_slice(public_nonce.E.compress().as_bytes());
2725
}
2826

29-
buf.extend_from_slice(msg);
27+
expand_to_scalar(&buf, prefix)
28+
.expect("FATAL: DST is less than 256 bytes so operation should not fail")
29+
}
30+
31+
#[allow(non_snake_case)]
32+
/// Compute the group commitment from the list of PartyIDs and nonce commitments using XMD-based expansion.
33+
pub fn group_commitment_compressed(commitment_list: &[(Scalar, Compressed, Compressed)]) -> Scalar {
34+
let prefix = b"WSTS/group_commitment";
35+
36+
let mut buf = Vec::new();
37+
for (id, hiding_commitment, binding_commitment) in commitment_list {
38+
buf.extend_from_slice(&id.to_bytes());
39+
buf.extend_from_slice(hiding_commitment.as_bytes());
40+
buf.extend_from_slice(binding_commitment.as_bytes());
41+
}
3042

3143
expand_to_scalar(&buf, prefix)
3244
.expect("FATAL: DST is less than 256 bytes so operation should not fail")
3345
}
3446

3547
#[allow(non_snake_case)]
3648
/// Compute a binding value from the party ID, public nonces, and signed message using XMD-based expansion.
37-
pub fn binding_compressed(id: &Scalar, B: &[(Compressed, Compressed)], msg: &[u8]) -> Scalar {
49+
pub fn binding(
50+
id: &Scalar,
51+
group_public_key: Point,
52+
commitment_list: &[(Scalar, PublicNonce)],
53+
msg: &[u8],
54+
) -> Scalar {
3855
let prefix = b"WSTS/binding";
56+
let encoded_group_commitment = group_commitment(commitment_list);
3957

40-
// Serialize all input into a buffer
4158
let mut buf = Vec::new();
4259
buf.extend_from_slice(&id.to_bytes());
60+
buf.extend_from_slice(group_public_key.compress().as_bytes());
61+
buf.extend_from_slice(msg);
62+
buf.extend_from_slice(&encoded_group_commitment.to_bytes());
4363

44-
for (D, E) in B {
45-
buf.extend_from_slice(D.as_bytes());
46-
buf.extend_from_slice(E.as_bytes());
47-
}
64+
expand_to_scalar(&buf, prefix)
65+
.expect("FATAL: DST is less than 256 bytes so operation should not fail")
66+
}
67+
68+
#[allow(non_snake_case)]
69+
/// Compute a binding value from the party ID, public nonces, and signed message using XMD-based expansion.
70+
pub fn binding_compressed(
71+
id: &Scalar,
72+
group_public_key: Point,
73+
commitment_list: &[(Scalar, Compressed, Compressed)],
74+
msg: &[u8],
75+
) -> Scalar {
76+
let prefix = b"WSTS/binding";
77+
let encoded_group_commitment = group_commitment_compressed(commitment_list);
4878

79+
let mut buf = Vec::new();
80+
buf.extend_from_slice(&id.to_bytes());
81+
buf.extend_from_slice(group_public_key.compress().as_bytes());
4982
buf.extend_from_slice(msg);
83+
buf.extend_from_slice(&encoded_group_commitment.to_bytes());
5084

5185
expand_to_scalar(&buf, prefix)
5286
.expect("FATAL: DST is less than 256 bytes so operation should not fail")
@@ -82,10 +116,20 @@ pub fn lambda(i: u32, key_ids: &[u32]) -> Scalar {
82116
// Is this the best way to return these values?
83117
#[allow(non_snake_case)]
84118
/// Compute the intermediate values used in both the parties and the aggregator
85-
pub fn intermediate(msg: &[u8], party_ids: &[u32], nonces: &[PublicNonce]) -> (Vec<Point>, Point) {
119+
pub fn intermediate(
120+
msg: &[u8],
121+
group_key: Point,
122+
party_ids: &[u32],
123+
nonces: &[PublicNonce],
124+
) -> (Vec<Point>, Point) {
125+
let commitment_list: Vec<(Scalar, PublicNonce)> = party_ids
126+
.iter()
127+
.zip(nonces)
128+
.map(|(i, nonce)| (Scalar::from(*i), nonce.clone()))
129+
.collect();
86130
let rhos: Vec<Scalar> = party_ids
87131
.iter()
88-
.map(|&i| binding(&id(i), nonces, msg))
132+
.map(|i| binding(&id(*i), group_key, &commitment_list, msg))
89133
.collect();
90134
let R_vec: Vec<Point> = zip(nonces, rhos)
91135
.map(|(nonce, rho)| nonce.D + rho * nonce.E)
@@ -99,19 +143,21 @@ pub fn intermediate(msg: &[u8], party_ids: &[u32], nonces: &[PublicNonce]) -> (V
99143
/// Compute the aggregate nonce
100144
pub fn aggregate_nonce(
101145
msg: &[u8],
146+
group_key: Point,
102147
party_ids: &[u32],
103148
nonces: &[PublicNonce],
104149
) -> Result<Point, PointError> {
105-
let compressed_nonces: Vec<(Compressed, Compressed)> = nonces
150+
let commitment_list: Vec<(Scalar, Compressed, Compressed)> = party_ids
106151
.iter()
107-
.map(|nonce| (nonce.D.compress(), nonce.E.compress()))
152+
.zip(nonces)
153+
.map(|(id, nonce)| (Scalar::from(*id), nonce.D.compress(), nonce.E.compress()))
108154
.collect();
109155
let scalars: Vec<Scalar> = party_ids
110156
.iter()
111157
.flat_map(|&i| {
112158
[
113159
Scalar::from(1),
114-
binding_compressed(&id(i), &compressed_nonces, msg),
160+
binding_compressed(&id(i), group_key, &commitment_list, msg),
115161
]
116162
})
117163
.collect();

src/state_machine/coordinator/fire.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
10191019
if nonce_info.nonce_recv_key_ids.len() >= self.config.threshold as usize {
10201020
// We have a winning message!
10211021
self.message.clone_from(&nonce_response.message);
1022-
let aggregate_nonce = self.compute_aggregate_nonce();
1022+
let aggregate_nonce = self.compute_aggregate_nonce()?;
10231023
info!("Aggregate nonce: {aggregate_nonce}");
10241024

10251025
self.move_to(State::SigShareRequest(signature_type))?;
@@ -1248,7 +1248,7 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
12481248
}
12491249

12501250
#[allow(non_snake_case)]
1251-
fn compute_aggregate_nonce(&self) -> Point {
1251+
fn compute_aggregate_nonce(&self) -> Result<Point, Error> {
12521252
// XXX this needs to be key_ids for v1 and signer_ids for v2
12531253
let public_nonces = self
12541254
.message_nonces
@@ -1266,9 +1266,14 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
12661266
.cloned()
12671267
.flat_map(|pn| pn.nonces)
12681268
.collect::<Vec<PublicNonce>>();
1269-
let (_, R) = compute::intermediate(&self.message, &party_ids, &nonces);
12701269

1271-
R
1270+
let Some(group_key) = self.aggregate_public_key else {
1271+
return Err(Error::MissingAggregatePublicKey);
1272+
};
1273+
let (_, aggregate_nonce) =
1274+
compute::intermediate(&self.message, group_key, &party_ids, &nonces);
1275+
1276+
Ok(aggregate_nonce)
12721277
}
12731278

12741279
fn compute_num_key_ids<'a, I>(&self, signer_ids: I) -> Result<u32, Error>
@@ -1828,6 +1833,7 @@ pub mod test {
18281833
let signature_type = SignatureType::Frost;
18291834
let message = vec![0u8];
18301835
coordinator.state = State::NonceGather(signature_type);
1836+
coordinator.aggregate_public_key = Some(Point::from(Scalar::random(&mut rng)));
18311837

18321838
let nonce_response = NonceResponse {
18331839
dkg_id: 0,

src/state_machine/coordinator/frost.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
551551
);
552552
}
553553
if self.ids_to_await.is_empty() {
554-
let aggregate_nonce = self.compute_aggregate_nonce();
554+
let aggregate_nonce = self.compute_aggregate_nonce()?;
555555
info!(
556556
%aggregate_nonce,
557557
"Aggregate nonce"
@@ -733,7 +733,7 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
733733
}
734734

735735
#[allow(non_snake_case)]
736-
fn compute_aggregate_nonce(&self) -> Point {
736+
fn compute_aggregate_nonce(&self) -> Result<Point, Error> {
737737
// XXX this needs to be key_ids for v1 and signer_ids for v2
738738
let party_ids = self
739739
.public_nonces
@@ -745,9 +745,13 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
745745
.values()
746746
.flat_map(|pn| pn.nonces.clone())
747747
.collect::<Vec<PublicNonce>>();
748-
let (_, R) = compute::intermediate(&self.message, &party_ids, &nonces);
748+
let Some(group_key) = self.aggregate_public_key else {
749+
return Err(Error::MissingAggregatePublicKey);
750+
};
751+
let (_, aggregate_nonce) =
752+
compute::intermediate(&self.message, group_key, &party_ids, &nonces);
749753

750-
R
754+
Ok(aggregate_nonce)
751755
}
752756
}
753757

@@ -1284,6 +1288,7 @@ pub mod test {
12841288
let signature_type = SignatureType::Frost;
12851289
let message = vec![0u8];
12861290
coordinator.state = State::NonceGather(signature_type);
1291+
coordinator.aggregate_public_key = Some(Point::from(Scalar::random(&mut rng)));
12871292

12881293
let nonce_response = NonceResponse {
12891294
dkg_id: 0,

src/traits.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ pub trait Signer: Clone + Debug + PartialEq {
108108

109109
/// Compute intermediate values
110110
fn compute_intermediate(
111+
&self,
111112
msg: &[u8],
112113
signer_ids: &[u32],
113114
key_ids: &[u32],

src/v1.rs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,14 @@ impl Party {
219219

220220
/// Sign `msg` with this party's share of the group private key, using the set of `signers` and corresponding `nonces`
221221
pub fn sign(&self, msg: &[u8], signers: &[u32], nonces: &[PublicNonce]) -> SignatureShare {
222-
let (_, aggregate_nonce) = compute::intermediate(msg, signers, nonces);
223-
let mut z = &self.nonce.d + &self.nonce.e * compute::binding(&self.id(), nonces, msg);
222+
let (_, aggregate_nonce) = compute::intermediate(msg, self.group_key, signers, nonces);
223+
let commitment_list: Vec<(Scalar, PublicNonce)> = signers
224+
.iter()
225+
.zip(nonces)
226+
.map(|(id, nonce)| (Scalar::from(*id), nonce.clone()))
227+
.collect();
228+
let mut z = &self.nonce.d
229+
+ &self.nonce.e * compute::binding(&self.id(), self.group_key, &commitment_list, msg);
224230
z += compute::challenge(&self.group_key, &aggregate_nonce, msg)
225231
* &self.private_key
226232
* compute::lambda(self.id, signers);
@@ -255,7 +261,13 @@ impl Party {
255261
aggregate_nonce: &Point,
256262
tweak: Option<Scalar>,
257263
) -> SignatureShare {
258-
let mut r = &self.nonce.d + &self.nonce.e * compute::binding(&self.id(), nonces, msg);
264+
let commitment_list: Vec<(Scalar, PublicNonce)> = signers
265+
.iter()
266+
.zip(nonces)
267+
.map(|(id, nonce)| (Scalar::from(*id), nonce.clone()))
268+
.collect();
269+
let mut r = &self.nonce.d
270+
+ &self.nonce.e * compute::binding(&self.id(), self.group_key, &commitment_list, msg);
259271
if tweak.is_some() && !aggregate_nonce.has_even_y() {
260272
r = -r;
261273
}
@@ -327,7 +339,6 @@ impl Aggregator {
327339
}
328340

329341
let signers: Vec<u32> = sig_shares.iter().map(|ss| ss.id).collect();
330-
let (_Rs, R) = compute::intermediate(msg, &signers, nonces);
331342
let mut z = Scalar::zero();
332343
let mut cx_sign = Scalar::one();
333344
let aggregate_public_key = self.poly[0];
@@ -341,6 +352,7 @@ impl Aggregator {
341352
}
342353
_ => aggregate_public_key,
343354
};
355+
let (_Rs, R) = compute::intermediate(msg, aggregate_public_key, &signers, nonces);
344356
let c = compute::challenge(&tweaked_public_key, &R, msg);
345357

346358
for sig_share in sig_shares {
@@ -374,7 +386,6 @@ impl Aggregator {
374386
}
375387

376388
let signers: Vec<u32> = sig_shares.iter().map(|ss| ss.id).collect();
377-
let (Rs, R) = compute::intermediate(msg, &signers, nonces);
378389
let mut bad_party_keys = Vec::new();
379390
let mut bad_party_sigs = Vec::new();
380391
let aggregate_public_key = self.poly[0];
@@ -384,6 +395,7 @@ impl Aggregator {
384395
}
385396
_ => aggregate_public_key,
386397
};
398+
let (Rs, R) = compute::intermediate(msg, aggregate_public_key, &signers, nonces);
387399
let c = compute::challenge(&tweaked_public_key, &R, msg);
388400
let mut r_sign = Scalar::one();
389401
let mut cx_sign = Scalar::one();
@@ -689,12 +701,13 @@ impl traits::Signer for Signer {
689701
}
690702

691703
fn compute_intermediate(
704+
&self,
692705
msg: &[u8],
693706
_signer_ids: &[u32],
694707
key_ids: &[u32],
695708
nonces: &[PublicNonce],
696709
) -> (Vec<Point>, Point) {
697-
compute::intermediate(msg, key_ids, nonces)
710+
compute::intermediate(msg, self.group_key, key_ids, nonces)
698711
}
699712

700713
fn validate_party_id(
@@ -715,7 +728,8 @@ impl traits::Signer for Signer {
715728
key_ids: &[u32],
716729
nonces: &[PublicNonce],
717730
) -> Vec<SignatureShare> {
718-
let aggregate_nonce = compute::aggregate_nonce(msg, key_ids, nonces).unwrap();
731+
let aggregate_nonce =
732+
compute::aggregate_nonce(msg, self.group_key, key_ids, nonces).unwrap();
719733
self.parties
720734
.iter()
721735
.map(|p| p.sign_precomputed(msg, key_ids, nonces, &aggregate_nonce))
@@ -730,7 +744,8 @@ impl traits::Signer for Signer {
730744
nonces: &[PublicNonce],
731745
merkle_root: Option<[u8; 32]>,
732746
) -> Vec<SignatureShare> {
733-
let aggregate_nonce = compute::aggregate_nonce(msg, key_ids, nonces).unwrap();
747+
let aggregate_nonce =
748+
compute::aggregate_nonce(msg, self.group_key, key_ids, nonces).unwrap();
734749
let tweak = compute::tweak(&self.parties[0].group_key, merkle_root);
735750
self.parties
736751
.iter()
@@ -747,7 +762,8 @@ impl traits::Signer for Signer {
747762
key_ids: &[u32],
748763
nonces: &[PublicNonce],
749764
) -> Vec<SignatureShare> {
750-
let aggregate_nonce = compute::aggregate_nonce(msg, key_ids, nonces).unwrap();
765+
let aggregate_nonce =
766+
compute::aggregate_nonce(msg, self.group_key, key_ids, nonces).unwrap();
751767
self.parties
752768
.iter()
753769
.map(|p| {

src/v2.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -256,13 +256,18 @@ impl Party {
256256
} else {
257257
self.group_key
258258
};
259-
let (_, R) = compute::intermediate(msg, party_ids, nonces);
259+
let (_, R) = compute::intermediate(msg, self.group_key, party_ids, nonces);
260260
let c = compute::challenge(&tweaked_public_key, &R, msg);
261-
let mut r = &self.nonce.d + &self.nonce.e * compute::binding(&self.id(), nonces, msg);
261+
let commitment_list: Vec<(Scalar, PublicNonce)> = party_ids
262+
.iter()
263+
.zip(nonces)
264+
.map(|(id, nonce)| (Scalar::from(*id), nonce.clone()))
265+
.collect();
266+
let mut r = &self.nonce.d
267+
+ &self.nonce.e * compute::binding(&self.id(), self.group_key, &commitment_list, msg);
262268
if tweak.is_some() && !R.has_even_y() {
263269
r = -r;
264270
}
265-
266271
let mut cx = Scalar::zero();
267272
for key_id in self.key_ids.iter() {
268273
cx += c * &self.private_keys[key_id] * compute::lambda(*key_id, key_ids);
@@ -310,7 +315,7 @@ impl Aggregator {
310315
}
311316

312317
let party_ids: Vec<u32> = sig_shares.iter().map(|ss| ss.id).collect();
313-
let (_Rs, R) = compute::intermediate(msg, &party_ids, nonces);
318+
let (_Rs, R) = compute::intermediate(msg, self.poly[0], &party_ids, nonces);
314319
let mut z = Scalar::zero();
315320
let mut cx_sign = Scalar::one();
316321
let aggregate_public_key = self.poly[0];
@@ -361,7 +366,7 @@ impl Aggregator {
361366
}
362367

363368
let party_ids: Vec<u32> = sig_shares.iter().map(|ss| ss.id).collect();
364-
let (Rs, R) = compute::intermediate(msg, &party_ids, nonces);
369+
let (Rs, R) = compute::intermediate(msg, self.poly[0], &party_ids, nonces);
365370
let mut bad_party_keys = Vec::new();
366371
let mut bad_party_sigs = Vec::new();
367372
let aggregate_public_key = self.poly[0];
@@ -637,12 +642,13 @@ impl traits::Signer for Party {
637642
}
638643

639644
fn compute_intermediate(
645+
&self,
640646
msg: &[u8],
641647
signer_ids: &[u32],
642648
_key_ids: &[u32],
643649
nonces: &[PublicNonce],
644650
) -> (Vec<Point>, Point) {
645-
compute::intermediate(msg, signer_ids, nonces)
651+
compute::intermediate(msg, self.group_key, signer_ids, nonces)
646652
}
647653

648654
fn validate_party_id(

0 commit comments

Comments
 (0)