Skip to content

Commit df52878

Browse files
authored
Merge pull request #113 from stejbac/integrate-multisig-module-into-end-to-end-protocol
Integrate `multisig` module into end-to-end protocol
2 parents f34b37f + fec69c7 commit df52878

3 files changed

Lines changed: 181 additions & 427 deletions

File tree

protocol/src/multisig.rs

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ impl KeyCtx {
8383
self.peers_key_share.as_ref().ok_or(MultisigErrorKind::MissingKeyShare)
8484
}
8585

86+
pub fn aggregated_key(&self) -> Result<&KeyPair> {
87+
self.aggregated_key.as_ref().ok_or(MultisigErrorKind::MissingAggPubKey)
88+
}
89+
8690
fn key_shares(&self) -> Result<[&KeyPair; 2]> {
8791
let mut shares = [self.my_key_share()?, self.peers_key_share()?];
8892
shares.sort_by_key(|p| p.pub_key());
@@ -91,7 +95,7 @@ impl KeyCtx {
9195

9296
pub fn aggregate_pub_key_shares(&mut self) -> Result<()> {
9397
let agg_ctx = KeyAggContext::new(self.key_shares()?.map(|p| *p.pub_key()))?;
94-
self.aggregated_key = Some(KeyPair::from_public(agg_ctx.aggregated_pubkey()));
98+
self.aggregated_key.get_or_insert(KeyPair::from_public(agg_ctx.aggregated_pubkey()));
9599
self.key_agg_ctx = Some(agg_ctx);
96100
Ok(())
97101
}
@@ -138,13 +142,17 @@ pub struct TweakedKeyCtx {
138142
}
139143

140144
impl TweakedKeyCtx {
141-
pub fn p2tr_address(&self, network: Network) -> Address {
145+
pub fn tweaked_public_key(&self) -> TweakedPublicKey {
142146
let pub_key: Point = self.key_agg_ctx.aggregated_pubkey();
143147
let pub_key = pub_key.to_public_key().into();
144148

145149
// This is safe, as `self` can only be constructed with a Taproot tweak applied to its
146150
// inner KeyAggContext (performed via the 'musig2::secp' crate):
147-
Address::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(pub_key), network)
151+
TweakedPublicKey::dangerous_assume_tweaked(pub_key)
152+
}
153+
154+
pub fn p2tr_address(&self, network: Network) -> Address {
155+
Address::p2tr_tweaked(self.tweaked_public_key(), network)
148156
}
149157
}
150158

@@ -174,6 +182,10 @@ impl SigCtx {
174182
Ok(&self.my_nonce_pair_share.as_ref().ok_or(MultisigErrorKind::MissingNonceShare)?.pub_nonce)
175183
}
176184

185+
fn peers_nonce_share(&self) -> Result<&PubNonce> {
186+
self.peers_nonce_share.as_ref().ok_or(MultisigErrorKind::MissingNonceShare)
187+
}
188+
177189
pub fn set_peers_nonce_share(&mut self, nonce_share: PubNonce) {
178190
self.peers_nonce_share.get_or_insert(nonce_share);
179191
}
@@ -182,12 +194,19 @@ impl SigCtx {
182194
self.my_partial_sig.as_ref().ok_or(MultisigErrorKind::MissingPartialSig)
183195
}
184196

197+
fn peers_partial_sig(&self) -> Result<&PartialSignature> {
198+
self.peers_partial_sig.as_ref().ok_or(MultisigErrorKind::MissingPartialSig)
199+
}
200+
185201
pub fn set_peers_partial_sig(&mut self, partial_signature: PartialSignature) -> &PartialSignature {
186202
self.peers_partial_sig.get_or_insert(partial_signature)
187203
}
188204

189205
pub fn set_adaptor_point(&mut self, adaptor_point: Point) -> Result<&Point> {
190-
if self.my_partial_sig.is_none() {
206+
// In order to have a better chance of provable security, don't allow the adaptor point to
207+
// be set after our local nonce share has already been initialized, as otherwise an attacker
208+
// may gain too much control over the challenge hash or final adapted signature nonce:
209+
if self.my_nonce_pair_share.is_none() {
191210
self.adaptor_point = adaptor_point.into();
192211
}
193212
match self.adaptor_point {
@@ -208,21 +227,8 @@ impl SigCtx {
208227
Ok(())
209228
}
210229

211-
fn get_nonce_shares(&self) -> Option<[&PubNonce; 2]> {
212-
Some([&self.my_nonce_pair_share.as_ref()?.pub_nonce, self.peers_nonce_share.as_ref()?])
213-
}
214-
215230
pub fn aggregate_nonce_shares(&mut self) -> Result<&AggNonce> {
216-
let agg_nonce = AggNonce::sum(self.get_nonce_shares()
217-
.ok_or(MultisigErrorKind::MissingNonceShare)?);
218-
if matches!((&agg_nonce.R1, &agg_nonce.R2), (MaybePoint::Infinity, MaybePoint::Infinity)) {
219-
// Fail early if the aggregated nonce is zero, since otherwise an attacker could force
220-
// the final signature nonce to be equal to the base point, G. While that might not be
221-
// a problem (for us), there would be an attack vector if such signatures were ever
222-
// deemed to be nonstandard. (Note that being able to assign blame later by allowing
223-
// this through is unimportant for a two-party protocol.)
224-
return Err(MultisigErrorKind::ZeroNonce);
225-
}
231+
let agg_nonce = AggNonce::sum([self.my_nonce_share()?, self.peers_nonce_share()?]);
226232
Ok(self.aggregated_nonce.insert(agg_nonce))
227233
}
228234

@@ -246,16 +252,11 @@ impl SigCtx {
246252
Ok(self.my_partial_sig.insert(sig))
247253
}
248254

249-
fn get_partial_signatures(&self) -> Option<[PartialSignature; 2]> {
250-
Some([self.my_partial_sig?, self.peers_partial_sig?])
251-
}
252-
253255
pub fn aggregate_partial_signatures(&mut self) -> Result<&AdaptorSignature> {
254256
let key_agg_ctx = &self.tweaked_key_ctx()?.key_agg_ctx;
255257
let aggregated_nonce = &self.aggregated_nonce.as_ref()
256258
.ok_or(MultisigErrorKind::MissingAggNonce)?;
257-
let partial_signatures = self.get_partial_signatures()
258-
.ok_or(MultisigErrorKind::MissingPartialSig)?;
259+
let partial_signatures = [*self.my_partial_sig()?, *self.peers_partial_sig()?];
259260
let message = self.message.as_ref()
260261
.ok_or(MultisigErrorKind::MissingPartialSig)?;
261262

@@ -267,6 +268,9 @@ impl SigCtx {
267268
pub fn compute_taproot_signature(&self, adaptor_secret: MaybeScalar) -> Result<Signature> {
268269
let adaptor_sig = self.aggregated_sig
269270
.ok_or(MultisigErrorKind::MissingAggSig)?;
271+
if self.adaptor_point != adaptor_secret.base_point_mul() {
272+
return Err(MultisigErrorKind::MismatchedKeyPair);
273+
}
270274
let sig_bytes: [u8; 64] = adaptor_sig.adapt(adaptor_secret)
271275
.ok_or(MultisigErrorKind::ZeroNonce)?;
272276
Ok(Signature::from_slice(&sig_bytes).expect("len = 64"))
@@ -288,7 +292,7 @@ pub trait PointExt {
288292
impl PointExt for Point {
289293
fn to_public_key(&self) -> PublicKey {
290294
// NOTE: We have to round-trip the public key because 'musig2' & 'bitcoin' currently use
291-
// different versions of the 'secp256k1' crate:
295+
// different versions of the 'secp256k1' crate. TODO: Consider unifying the versions.
292296
PublicKey::from_slice(&self.serialize_uncompressed())
293297
.expect("curve point should have a valid uncompressed DER encoding")
294298
}

0 commit comments

Comments
 (0)