-
Notifications
You must be signed in to change notification settings - Fork 30
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
base: master
Are you sure you want to change the base?
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
/// let keypair2 = KeyPair::new(&secp, &mut thread_rng()); | ||
/// let pub_key2 = XOnlyPublicKey::from_keypair(&keypair2); | ||
/// | ||
/// let (_key_agg_cache, _agg_pk) = MusigKeyAggCache::new(&secp, &[pub_key1, pub_key2]); |
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.
There are situations when you don't need the _key_agg_cache
. I suppose this is the idiomatic way to do this in rust, but it looks a bit weird to call MusigKeyAggCache::new
just to get the _agg_pk
. An alternative API could just return the single _key_agg_cache
that has a getter for the aggregate key (see secp256k1_musig_pubkey_get
in the C code).
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.
It should not be necessary to call the FFI in this case right?
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 don't understand my suggestion anymore. I think I was mainly complaining about having to call something ominously named MusigKeyAggCache::new
if you just want an aggregate key. I don't see how my suggestion improves the situation.
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
.
src/zkp/musig.rs
Outdated
/// let sec_key = SecretKey::from_keypair(&keypair1); | ||
/// let msg = Message::from_slice(&[3; 32]).unwrap(); | ||
/// let mut extra_rand = [0u8; 32]; | ||
/// thread_rng().fill_bytes(&mut extra_rand); |
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.
extra_rand is there to help if session_id is a repeated value. In this example, if thread_rng is broken then extra_rand will not add anything. You may want to add system time, etc here but that seems like security theater. One legit thing to include as extra_rand is the other signer's nonces (if you know them already).
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.
Will add an example here that uses time
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.
src/zkp/musig.rs
Outdated
/// let keypair2 = KeyPair::new(&secp, &mut thread_rng()); | ||
/// let pub_key2 = XOnlyPublicKey::from_keypair(&keypair2); | ||
/// | ||
/// let (_key_agg_cache, _agg_pk) = MusigKeyAggCache::new(&secp, &[pub_key1, pub_key2]); |
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.
It should not be necessary to call the FFI in this case right?
src/zkp/musig.rs
Outdated
/// let sec_key = SecretKey::from_keypair(&keypair1); | ||
/// let msg = Message::from_slice(&[3; 32]).unwrap(); | ||
/// let mut extra_rand = [0u8; 32]; | ||
/// thread_rng().fill_bytes(&mut extra_rand); |
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.
Will add an example here that uses time
) == 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. |
0c87d00
to
c817be6
Compare
src/zkp/musig.rs
Outdated
/// | ||
/// Musig2 nonces can be precomputed without knowing the aggregate public key, the message to sign. | ||
/// However, for maximal mis-use resistance, this API requires user to have already | ||
/// have [`SecretKey`], [`Message`] and [`MusigKeyAggCache`]. See the `new_nonce_pair` method |
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.
have [
SecretKey
]
nonce_gen()
appears to take a public key, not a secret key.
Well that escalated quickly.. I'm super excited about this though, guys! |
f3bcb02
to
0de61c2
Compare
It would be nice to have serde serialization for pubnonces and partial signatures. (I may provide a patch for that) |
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.
src/zkp/musig.rs
Outdated
) -> Result<XOnlyPublicKey, MusigTweakErr> { | ||
let cx = secp.ctx().as_ptr(); | ||
unsafe { | ||
let mut out = XOnlyPublicKey::from(ffi::XOnlyPublicKey::new()); |
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.
) -> Result<XOnlyPublicKey, MusigTweakErr> { | |
let cx = secp.ctx().as_ptr(); | |
unsafe { | |
let mut out = XOnlyPublicKey::from(ffi::XOnlyPublicKey::new()); | |
) -> Result<PublicKey, MusigTweakErr> { | |
let cx = secp.ctx().as_ptr(); | |
unsafe { | |
let mut out = PublicKey::from(ffi::PublicKey::new()); |
src/zkp/musig.rs
Outdated
/// | ||
/// # Returns | ||
/// | ||
/// A pair ([`MusigKeyAggCache`], [`XOnlyPublicKey`]) where the first element is the `key_agg_cache`. |
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 is no longer true.
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 <sanketk@squareup.com>
/// 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. |
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