-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Musig2 support #48
Musig2 support #48
Conversation
3eef309
to
6eb040e
Compare
7fcd5b6
to
651c0de
Compare
Marked as ready for review. |
|
9694636
to
a923782
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have only read through half the code yet but looks nice so far.
src/zkp/musig.rs
Outdated
/// | ||
/// MuSig differs from regular Schnorr signing in that implementers _must_ take | ||
/// special care to not reuse a nonce. If you cannot provide `session_id`, or `extra_rand` | ||
/// UNIFORMLY at random, refer to libsecp256k1-zkp documentation for additional considerations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fwiw extra_rand
generally does not need to be uniformly random which makes this sound a bit confusing. Also, what is missing is that in the mode that you're recommending here (session_id uniformly random), the session_id must also be kept secret.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also rename extra_rand
? the libsecp implement calls it extra_input
, the spec draft extra_in
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First review pass done. I wonder if it is worth having so much redundancy in the doc examples versus just having a single example that shows an entire signing session.
) == 0 | ||
{ | ||
// This can only crash if the individual nonces are invalid which is not possible is rust. | ||
// Note that even if aggregate nonce is point at infinity, the musig spec sets it as `G` | ||
unreachable!("Public key nonces are well-formed and valid in rust typesystem") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nonce_agg
will also fail if none_ptrs.len() == 0
. The reason is that there's no aggnonce to return in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I think it is okay to panic in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Followup: The term "Public key nonces" is confusing, perhaps just "nonces"?
- secp256k1_zkp is missing BlockstreamResearch/rust-secp256k1-zkp#48 - bitcoin_hashes is missing rust-bitcoin/bitcoin_hashes@f1084bf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay @jonasnick. Addressed the reviews.
) == 0 | ||
{ | ||
// This can only crash if the individual nonces are invalid which is not possible is rust. | ||
// Note that even if aggregate nonce is point at infinity, the musig spec sets it as `G` | ||
unreachable!("Public key nonces are well-formed and valid in rust typesystem") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I think it is okay to panic in this case.
288f52e
to
7ac3464
Compare
Thanks for providing this! I played a bit with it and I'm curious about the reason for having separate I also spotted quite some typos in the docs but not sure if it's the right time to be nitpicking (if it is let me know I can point them out). Otherwise it's really neat :)! |
@Tibo-lg Indeed. IMO, the return type of the function should only return the corresponding error variants that are expected from the function. If we collect all of them into a single enum, it is would impossible for the caller to know which variants are reachable for the corresponding function. This is handy for callers that want to act on an error instead of propagating forward. match key_agg_cache.pubkey_ec_tweak_add(&secp, tweak) {
Ok(_) => {},
Err(MusigTweakError::InvalidTweak) => panic!("not possible in bip32 tweaking"),
} If there is a single variant with everything, the caller has no choice but to propagate it forward. Unfortunately, there is a tradeoff that the implementors that want to forward the error instead of dealing with it would have more code to deal with. But overall, I think the tradeoff is clear as it clearly expresses the conditions the function errors with and allows sanely dealing with errors.
Corrections welcome. |
Exposing the inner bytes of
#[repr(C)]
#[derive(Copy, Clone)]
-pub struct MusigSecNonce([c_uchar; MUSIG_SECNONCE_LEN]);
+pub struct MusigSecNonce(pub [c_uchar; MUSIG_SECNONCE_LEN]);
impl_array_newtype!(MusigSecNonce, c_uchar, MUSIG_SECNONCE_LEN);
impl_raw_debug!(MusigSecNonce);
@@ -1008,7 +1008,7 @@ impl MusigAggNonce {
#[repr(C)]
#[derive(Copy, Clone)]
-pub struct MusigSession([c_uchar; MUSIG_SESSION_LEN]);
+pub struct MusigSession(pub [c_uchar; MUSIG_SESSION_LEN]);
impl_array_newtype!(MusigSession, c_uchar, MUSIG_SESSION_LEN);
impl_raw_debug!(MusigSession);
use crate::{Signing, Verification};
+use ffi::{MUSIG_SECNONCE_LEN, MUSIG_SESSION_LEN};
use secp256k1::Parity;
impl MusigSecNonce {
pub fn as_mut_ptr(&mut self) -> *mut ffi::MusigSecNonce {
&mut self.0
}
+
+ /// Function to return a copy of the internal array
+ pub fn serialize(&self) -> [u8; MUSIG_SECNONCE_LEN] {
+ let ffi::MusigSecNonce(array) = &self.0;
+ *array
+ }
+
+ /// Function to create a new MusigKeyAggCoef from a 32 byte array
+ pub fn from_slice(array: [u8; MUSIG_SECNONCE_LEN]) -> Self {
+ MusigSecNonce(ffi::MusigSecNonce(array))
+ }
}
impl CPtr for MusigSession {
type Target = ffi::MusigSession;
@@ -1671,6 +1695,17 @@ impl MusigSession {
pub fn as_mut_ptr(&mut self) -> *mut ffi::MusigSession {
&mut self.0
}
+
+ /// Function to return a copy of the internal array
+ pub fn serialize(&self) -> [u8; MUSIG_SESSION_LEN] {
+ let ffi::MusigSession(array) = &self.0;
+ *array
+ }
+
+ /// Function to create a new MusigKeyAggCoef from a 32 byte array
+ pub fn from_slice(array: [u8; MUSIG_SESSION_LEN]) -> Self {
+ MusigSession(ffi::MusigSession(array))
+ }
}
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The functions pubkey_xonly_tweak_add
and secp256k1_musig_pubkey_xonly_tweak_add
have the wrong parameter (XOnlyPublicKey
instead of PublicKey
).
This causes an error when verifying a signature of a tweaked key. Changing them to use the expected parameter fixes this.
secp256k1-zkp-sys/src/zkp.rs
Outdated
)] | ||
pub fn secp256k1_musig_pubkey_xonly_tweak_add( | ||
cx: *const Context, | ||
output_pubkey: *mut XOnlyPublicKey, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
secp256k1_musig_pubkey_xonly_tweak_add
expects a PublicKey
, not XOnlyPublicKey
.
This is causing signature verification to fail.
output_pubkey: *mut XOnlyPublicKey, | |
output_pubkey: *mut PublicKey, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.
158ff81
to
bcd5419
Compare
Hello everyone. Sorry for the delay in working on this. @ssantos21, there was a good security reason to disallow serialization APIs for SecNonce. I have prefix I suggest we review this PR for correctness, any incomplete serialization features can be added as a followup. Ready for review. cc @stevenroose @jonasnick @Ademan @ssantos21 @apoelstra |
b54aa8d
to
8b956f7
Compare
Signed-off-by: Sanket Kanjalkar <[email protected]>
/// Refer to libsecp256k1-zkp documentation for additional considerations. | ||
/// | ||
/// Musig2 nonces can be precomputed without knowing the aggregate public key, the message to sign. | ||
/// See the `new_nonce_pair` method that allows generating [`MusigSecNonce`] and [`MusigPubNonce`] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new_nonce_pair
method doesn't seem to exist?
Additionally, such a method should probably include the the optional extra_rand
field as in this case you might well want to use it for extra protection against accidental misuse?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh I see it's actually called new_musig_nonce_pair
now so just a doc-comment issue then
Not actively reviewing, but trying to use this. The
This makes me wonder, the name "SessionId" somehow made me thing it had to be shared between everyone in the same signing session. But from that sentence maybe it seems that it should be locally random and not shared? |
secp: &Secp256k1<C>, | ||
mut secnonce: MusigSecNonce, | ||
keypair: &Keypair, | ||
key_agg_cache: &MusigKeyAggCache, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need to be provided twice? It's already provided in the constructor..
/// # | ||
/// let key_agg_cache = MusigKeyAggCache::new(&secp, &[pub_key1, pub_key2]); | ||
/// // The session id must be sampled at random. Read documentation for more details. | ||
/// let session_id = MusigSessionId::new(&mut thread_rng()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method doesn't exist anymore and seems to be renamed to assume_unique_per_nonce_gen
.
pub fn nonce_gen<C: Signing>( | ||
&self, | ||
secp: &Secp256k1<C>, | ||
session_id: MusigSessionId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MusigSessionId
is not Copy
and it has to be kept around for partial signature generation, right? So this API should take a ref, I suppose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I seem to be mistaken. The session ID is not needed to create a MusigSession at all. So probably ignore this, but maybe double check :)
Are you planning serde serialization for the types? |
Suggestion: change the pub fn new<C: Verification>(secp: &Secp256k1<C>, pubkeys: &[PublicKey]) -> Self { EDIT: Same goes for nonces. I've been using this for a bit and it seems that a common situation is that you have "all other pubkeys" or "all other nonces", plus your own one and then you either have to re-allocate just to add your own, where with iterators, you could just So the argument would be |
@stevenroose, thanks for the testing this and providing valuable user feedback. Working on suggested changes. |
Is this PR still active? What can be done for the merge ? |
cc @jonasnick can you take a fresh look at this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went throught the PR and marked all comments resolved that appeared to be resolved. There are still a few unaddressed review comments (e.g., a panic, an incorrect type in the FFI, unfinished sentences). I think the PR also needs to be rebased to use the new v0_10_0 symbols.
This makes me wonder, the name "SessionId" somehow made me thing it had to be shared between everyone in the same signing session. But from that sentence maybe it seems that it should be locally random and not shared?
Argh, no, sorry. SessionId
is absotlutely not to be shared. It must be kept secret from other signers. We renamed this session_secrand
in the latest version of the module (PR'd to libsecp256k1, not in libsecp256k1-zkp yet).
/// UNIFORMLY RANDOM AND KEPT SECRET (even from other signers). | ||
/// Refer to libsecp256k1-zkp documentation for additional considerations. | ||
/// | ||
/// Musig2 nonces can be precomputed without knowing the aggregate public key, the message to sign. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Musig2 -> MuSig2, also below in the error enum doc. And sometimes it's just called MuSig. Should be MuSig2 consistently.
Damn we missing @sanket1729 here.. |
Given bitcoin-core/secp256k1#1479 was merged, does it make sense to focus on rust-bitcoin/rust-secp256k1#716 ? |
Closing this in favor of upstream PR linked in the comment above |
This was taken by #29 , but significantly changed. In particular, I was super strict about adding
unreachable!
to make the APIs not return a result when possible. Each API has a doc test with guidelines of using it.Things to pay special attention to:
unreachable!
in the FFI calls to make sure they cannot panic for any other reasons.session_id
and nonce re-use.Edit: I wanted to add a extra-rand with time of day example, but doing so in the current MSRV is non-trivial. Will add an example after MSRV update