use core::mem::MaybeUninit;
use core::{self, fmt};
#[cfg(feature = "std")]
use std;
use crate::ffi::{self, CPtr};
#[cfg(doc)]
use crate::key;
use crate::{
from_hex, schnorr, Error, Keypair, PublicKey, Scalar, Secp256k1, SecretKey, XOnlyPublicKey,
};
pub const AGGNONCE_SERIALIZED_SIZE: usize = 66;
pub const PUBNONCE_SERIALIZED_SIZE: usize = 66;
pub const PART_SIG_SERIALIZED_SIZE: usize = 32;
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub enum ParseError {
MalformedArg,
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
ParseError::MalformedArg => write!(f, "Malformed parse argument"),
}
}
}
#[allow(missing_copy_implementations)]
pub struct SessionSecretRand([u8; 32]);
impl_non_secure_erase!(SessionSecretRand, 0, [0u8; 32]);
impl_display_secret!(SessionSecretRand);
impl SessionSecretRand {
#[cfg(feature = "rand")]
pub fn from_rng<R: rand::Rng + ?Sized>(rng: &mut R) -> Self {
let session_secrand = crate::random_32_bytes(rng);
SessionSecretRand(session_secrand)
}
pub fn assume_unique_per_nonce_gen(inner: [u8; 32], sk: &SecretKey) -> Self {
const MUSIG_AUX_TAG: &[u8] = b"MuSig/aux";
let mut mixed = [0u8; 32];
let ret = crate::with_global_context(
|secp: &Secp256k1<crate::AllPreallocated>| unsafe {
ffi::secp256k1_tagged_sha256(
secp.ctx.as_ptr(),
mixed.as_mut_ptr(),
MUSIG_AUX_TAG.as_ptr(),
MUSIG_AUX_TAG.len(),
inner.as_ptr(),
inner.len(),
)
},
None,
);
debug_assert_eq!(ret, 1);
for (this, that) in mixed.iter_mut().zip(sk.to_secret_bytes().iter()) {
*this ^= *that;
}
let mixed_or = mixed.iter().fold(0, |accum, x| accum | *x);
assert!(
unsafe { core::ptr::read_volatile(&mixed_or) != 0 },
"session secrets may not be all zero",
);
SessionSecretRand(mixed)
}
pub fn assume_uniformly_random(inner: [u8; 32]) -> Self {
let inner_or = inner.iter().fold(0, |accum, x| accum | *x);
assert!(
unsafe { core::ptr::read_volatile(&inner_or) != 0 },
"session secrets may not be all zero",
);
SessionSecretRand(inner)
}
pub fn to_secret_bytes(&self) -> [u8; 32] { self.0 }
pub fn as_secret_bytes(&self) -> &[u8; 32] { &self.0 }
fn as_mut_ptr(&mut self) -> *mut u8 { self.0.as_mut_ptr() }
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct KeyAggCache {
data: ffi::MusigKeyAggCache,
aggregated_xonly_public_key: XOnlyPublicKey,
}
impl CPtr for KeyAggCache {
type Target = ffi::MusigKeyAggCache;
fn as_c_ptr(&self) -> *const Self::Target { self.as_ptr() }
fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() }
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct InvalidTweakErr;
#[cfg(feature = "std")]
impl std::error::Error for InvalidTweakErr {}
impl fmt::Display for InvalidTweakErr {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "The tweak is negation of secret key")
}
}
pub fn new_nonce_pair(
mut session_secrand: SessionSecretRand,
key_agg_cache: Option<&KeyAggCache>,
sec_key: Option<SecretKey>,
pub_key: PublicKey,
msg: Option<&[u8; 32]>,
extra_rand: Option<[u8; 32]>,
) -> (SecretNonce, PublicNonce) {
let extra_ptr = extra_rand.as_ref().map(|e| e.as_ptr()).unwrap_or(core::ptr::null());
let sk_ptr = sec_key.as_ref().map(|e| e.as_c_ptr()).unwrap_or(core::ptr::null());
let msg_ptr = msg.as_ref().map(|e| e.as_c_ptr()).unwrap_or(core::ptr::null());
let cache_ptr = key_agg_cache.map(|e| e.as_ptr()).unwrap_or(core::ptr::null());
let mut seed = session_secrand.to_secret_bytes();
if let Some(bytes) = sec_key {
for (this, that) in seed.iter_mut().zip(bytes.to_secret_bytes().iter()) {
*this ^= *that;
}
}
if let Some(bytes) = extra_rand {
for (this, that) in seed.iter_mut().zip(bytes.iter()) {
*this ^= *that;
}
}
unsafe {
let mut sec_nonce = MaybeUninit::<ffi::MusigSecNonce>::uninit();
let mut pub_nonce = MaybeUninit::<ffi::MusigPubNonce>::uninit();
let ret = crate::with_global_context(
|secp: &Secp256k1<crate::AllPreallocated>| {
ffi::secp256k1_musig_nonce_gen(
secp.ctx.as_ptr(),
sec_nonce.as_mut_ptr(),
pub_nonce.as_mut_ptr(),
session_secrand.as_mut_ptr(),
sk_ptr,
pub_key.as_c_ptr(),
msg_ptr,
cache_ptr,
extra_ptr,
)
},
Some(&seed),
);
if ret == 0 {
panic!("A zero session secret was supplied")
} else {
let pub_nonce = PublicNonce(pub_nonce.assume_init());
let sec_nonce = SecretNonce(sec_nonce.assume_init());
(sec_nonce, pub_nonce)
}
}
}
pub fn new_nonce_pair_counter(
nonrepeating_cnt: u64,
key_agg_cache: Option<&KeyAggCache>,
keypair: &Keypair,
msg: Option<&[u8; 32]>,
extra_rand: Option<[u8; 32]>,
) -> (SecretNonce, PublicNonce) {
let extra_ptr = extra_rand.as_ref().map(|e| e.as_ptr()).unwrap_or(core::ptr::null());
let msg_ptr = msg.as_ref().map(|e| e.as_c_ptr()).unwrap_or(core::ptr::null());
let cache_ptr = key_agg_cache.map(|e| e.as_ptr()).unwrap_or(core::ptr::null());
let mut seed = keypair.to_secret_bytes();
if let Some(bytes) = extra_rand {
for (this, that) in seed.iter_mut().zip(bytes.iter()) {
*this ^= *that;
}
}
unsafe {
let mut sec_nonce = MaybeUninit::<ffi::MusigSecNonce>::uninit();
let mut pub_nonce = MaybeUninit::<ffi::MusigPubNonce>::uninit();
let ret = crate::with_global_context(
|secp: &Secp256k1<crate::AllPreallocated>| {
ffi::secp256k1_musig_nonce_gen_counter(
secp.ctx.as_ptr(),
sec_nonce.as_mut_ptr(),
pub_nonce.as_mut_ptr(),
nonrepeating_cnt,
keypair.as_c_ptr(),
msg_ptr,
cache_ptr,
extra_ptr,
)
},
Some(&seed),
);
if ret == 0 {
unreachable!("Arguments must be valid and well-typed")
} else {
let pub_nonce = PublicNonce(pub_nonce.assume_init());
let sec_nonce = SecretNonce(sec_nonce.assume_init());
(sec_nonce, pub_nonce)
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct PartialSignature(ffi::MusigPartialSignature);
impl CPtr for PartialSignature {
type Target = ffi::MusigPartialSignature;
fn as_c_ptr(&self) -> *const Self::Target { self.as_ptr() }
fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() }
}
impl fmt::LowerHex for PartialSignature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for b in self.serialize() {
write!(f, "{:02x}", b)?;
}
Ok(())
}
}
impl fmt::Display for PartialSignature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
}
impl core::str::FromStr for PartialSignature {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut res = [0u8; PART_SIG_SERIALIZED_SIZE];
match from_hex(s, &mut res) {
Ok(PART_SIG_SERIALIZED_SIZE) => PartialSignature::from_byte_array(&res),
_ => Err(ParseError::MalformedArg),
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for PartialSignature {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if s.is_human_readable() {
s.collect_str(self)
} else {
s.serialize_bytes(&self.serialize()[..])
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for PartialSignature {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
if d.is_human_readable() {
d.deserialize_str(super::serde_util::FromStrVisitor::new(
"a hex string representing a MuSig2 partial signature",
))
} else {
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
"a raw MuSig2 partial signature",
|slice| {
let bytes: &[u8; PART_SIG_SERIALIZED_SIZE] =
slice.try_into().map_err(|_| ParseError::MalformedArg)?;
Self::from_byte_array(bytes)
},
))
}
}
}
impl PartialSignature {
pub fn serialize(&self) -> [u8; PART_SIG_SERIALIZED_SIZE] {
let mut data = MaybeUninit::<[u8; PART_SIG_SERIALIZED_SIZE]>::uninit();
unsafe {
if ffi::secp256k1_musig_partial_sig_serialize(
ffi::secp256k1_context_static,
data.as_mut_ptr() as *mut u8,
self.as_ptr(),
) == 0
{
unreachable!("Serialization cannot fail")
} else {
data.assume_init()
}
}
}
pub fn from_byte_array(data: &[u8; PART_SIG_SERIALIZED_SIZE]) -> Result<Self, ParseError> {
let mut partial_sig = MaybeUninit::<ffi::MusigPartialSignature>::uninit();
unsafe {
if ffi::secp256k1_musig_partial_sig_parse(
ffi::secp256k1_context_static,
partial_sig.as_mut_ptr(),
data.as_ptr(),
) == 0
{
Err(ParseError::MalformedArg)
} else {
Ok(PartialSignature(partial_sig.assume_init()))
}
}
}
pub fn as_ptr(&self) -> *const ffi::MusigPartialSignature { &self.0 }
pub fn as_mut_ptr(&mut self) -> *mut ffi::MusigPartialSignature { &mut self.0 }
}
impl KeyAggCache {
pub fn new(pubkeys: &[&PublicKey]) -> Self {
if pubkeys.is_empty() {
panic!("Cannot aggregate an empty slice of pubkeys");
}
let mut key_agg_cache = MaybeUninit::<ffi::MusigKeyAggCache>::uninit();
let mut agg_pk = MaybeUninit::<ffi::XOnlyPublicKey>::uninit();
unsafe {
let pubkeys_ref = core::slice::from_raw_parts(
pubkeys.as_c_ptr().cast::<*const ffi::PublicKey>(),
pubkeys.len(),
);
let ret = crate::with_global_context(
|secp: &Secp256k1<crate::AllPreallocated>| {
ffi::secp256k1_musig_pubkey_agg(
secp.ctx.as_ptr(),
agg_pk.as_mut_ptr(),
key_agg_cache.as_mut_ptr(),
pubkeys_ref.as_ptr(),
pubkeys_ref.len(),
)
},
None,
);
if ret == 0 {
unreachable!("Invalid XOnlyPublicKey in input pubkeys")
} else {
let key_agg_cache = key_agg_cache.assume_init();
let agg_pk = XOnlyPublicKey::from(agg_pk.assume_init());
KeyAggCache { data: key_agg_cache, aggregated_xonly_public_key: agg_pk }
}
}
}
pub fn agg_pk(&self) -> XOnlyPublicKey { self.aggregated_xonly_public_key }
pub fn agg_pk_full(&self) -> PublicKey {
unsafe {
let mut pk = PublicKey::from(ffi::PublicKey::new());
if ffi::secp256k1_musig_pubkey_get(
ffi::secp256k1_context_static,
pk.as_mut_c_ptr(),
self.as_ptr(),
) == 0
{
unreachable!("All the arguments are valid")
} else {
pk
}
}
}
pub fn pubkey_ec_tweak_add(&mut self, tweak: &Scalar) -> Result<PublicKey, InvalidTweakErr> {
unsafe {
let mut out = PublicKey::from(ffi::PublicKey::new());
let ret = crate::with_global_context(
|secp: &Secp256k1<crate::AllPreallocated>| {
ffi::secp256k1_musig_pubkey_ec_tweak_add(
secp.ctx.as_ptr(),
out.as_mut_c_ptr(),
self.as_mut_ptr(),
tweak.as_c_ptr(),
)
},
None,
);
if ret == 0 {
Err(InvalidTweakErr)
} else {
self.aggregated_xonly_public_key = out.x_only_public_key().0;
Ok(out)
}
}
}
pub fn pubkey_xonly_tweak_add(&mut self, tweak: &Scalar) -> Result<PublicKey, InvalidTweakErr> {
unsafe {
let mut out = PublicKey::from(ffi::PublicKey::new());
let ret = crate::with_global_context(
|secp: &Secp256k1<crate::AllPreallocated>| {
ffi::secp256k1_musig_pubkey_xonly_tweak_add(
secp.ctx.as_ptr(),
out.as_mut_c_ptr(),
self.as_mut_ptr(),
tweak.as_c_ptr(),
)
},
None,
);
if ret == 0 {
Err(InvalidTweakErr)
} else {
self.aggregated_xonly_public_key = out.x_only_public_key().0;
Ok(out)
}
}
}
pub fn nonce_gen(
&self,
nonrepeating_cnt: u64,
keypair: &Keypair,
msg: &[u8; 32],
extra_rand: Option<[u8; 32]>,
) -> (SecretNonce, PublicNonce) {
new_nonce_pair_counter(nonrepeating_cnt, Some(self), keypair, Some(msg), extra_rand)
}
pub fn nonce_gen_with_uniform_randomness(
&self,
session_secrand: SessionSecretRand,
pub_key: PublicKey,
msg: &[u8; 32],
extra_rand: [u8; 32],
) -> (SecretNonce, PublicNonce) {
new_nonce_pair(session_secrand, Some(self), None, pub_key, Some(msg), Some(extra_rand))
}
pub fn as_ptr(&self) -> *const ffi::MusigKeyAggCache { &self.data }
pub fn as_mut_ptr(&mut self) -> *mut ffi::MusigKeyAggCache { &mut self.data }
}
#[allow(missing_copy_implementations)]
#[derive(Debug)]
pub struct SecretNonce(ffi::MusigSecNonce);
impl CPtr for SecretNonce {
type Target = ffi::MusigSecNonce;
fn as_c_ptr(&self) -> *const Self::Target { self.as_ptr() }
fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() }
}
impl SecretNonce {
pub fn as_ptr(&self) -> *const ffi::MusigSecNonce { &self.0 }
pub fn as_mut_ptr(&mut self) -> *mut ffi::MusigSecNonce { &mut self.0 }
pub fn dangerous_into_bytes(self) -> [u8; secp256k1_sys::MUSIG_SECNONCE_SIZE] {
self.0.dangerous_into_bytes()
}
pub fn dangerous_from_bytes(array: [u8; secp256k1_sys::MUSIG_SECNONCE_SIZE]) -> Self {
SecretNonce(ffi::MusigSecNonce::dangerous_from_bytes(array))
}
#[inline]
pub fn non_secure_erase(&mut self) { self.0.non_secure_erase(); }
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct PublicNonce(ffi::MusigPubNonce);
impl CPtr for PublicNonce {
type Target = ffi::MusigPubNonce;
fn as_c_ptr(&self) -> *const Self::Target { self.as_ptr() }
fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() }
}
impl fmt::LowerHex for PublicNonce {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for b in self.serialize() {
write!(f, "{:02x}", b)?;
}
Ok(())
}
}
impl fmt::Display for PublicNonce {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
}
impl core::str::FromStr for PublicNonce {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut res = [0u8; PUBNONCE_SERIALIZED_SIZE];
match from_hex(s, &mut res) {
Ok(PUBNONCE_SERIALIZED_SIZE) => PublicNonce::from_byte_array(&res),
_ => Err(ParseError::MalformedArg),
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for PublicNonce {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if s.is_human_readable() {
s.collect_str(self)
} else {
s.serialize_bytes(&self.serialize()[..])
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for PublicNonce {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
if d.is_human_readable() {
d.deserialize_str(super::serde_util::FromStrVisitor::new(
"a hex string representing a MuSig2 public nonce",
))
} else {
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
"a raw MuSig2 public nonce",
|slice| {
let bytes: &[u8; PUBNONCE_SERIALIZED_SIZE] =
slice.try_into().map_err(|_| ParseError::MalformedArg)?;
Self::from_byte_array(bytes)
},
))
}
}
}
impl PublicNonce {
pub fn serialize(&self) -> [u8; PUBNONCE_SERIALIZED_SIZE] {
let mut data = [0; PUBNONCE_SERIALIZED_SIZE];
unsafe {
if ffi::secp256k1_musig_pubnonce_serialize(
ffi::secp256k1_context_static,
data.as_mut_ptr(),
self.as_ptr(),
) == 0
{
unreachable!("Arguments must be valid and well-typed")
} else {
data
}
}
}
pub fn from_byte_array(data: &[u8; PUBNONCE_SERIALIZED_SIZE]) -> Result<Self, ParseError> {
let mut pub_nonce = MaybeUninit::<ffi::MusigPubNonce>::uninit();
unsafe {
if ffi::secp256k1_musig_pubnonce_parse(
ffi::secp256k1_context_static,
pub_nonce.as_mut_ptr(),
data.as_ptr(),
) == 0
{
Err(ParseError::MalformedArg)
} else {
Ok(PublicNonce(pub_nonce.assume_init()))
}
}
}
pub fn as_ptr(&self) -> *const ffi::MusigPubNonce { &self.0 }
pub fn as_mut_ptr(&mut self) -> *mut ffi::MusigPubNonce { &mut self.0 }
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AggregatedNonce(ffi::MusigAggNonce);
impl CPtr for AggregatedNonce {
type Target = ffi::MusigAggNonce;
fn as_c_ptr(&self) -> *const Self::Target { self.as_ptr() }
fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() }
}
impl fmt::LowerHex for AggregatedNonce {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for b in self.serialize() {
write!(f, "{:02x}", b)?;
}
Ok(())
}
}
impl fmt::Display for AggregatedNonce {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
}
impl core::str::FromStr for AggregatedNonce {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut res = [0u8; AGGNONCE_SERIALIZED_SIZE];
match from_hex(s, &mut res) {
Ok(AGGNONCE_SERIALIZED_SIZE) => AggregatedNonce::from_byte_array(&res),
_ => Err(ParseError::MalformedArg),
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for AggregatedNonce {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if s.is_human_readable() {
s.collect_str(self)
} else {
s.serialize_bytes(&self.serialize()[..])
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for AggregatedNonce {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
if d.is_human_readable() {
d.deserialize_str(super::serde_util::FromStrVisitor::new(
"a hex string representing a MuSig2 aggregated nonce",
))
} else {
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
"a raw MuSig2 aggregated nonce",
|slice| {
let bytes: &[u8; AGGNONCE_SERIALIZED_SIZE] =
slice.try_into().map_err(|_| ParseError::MalformedArg)?;
Self::from_byte_array(bytes)
},
))
}
}
}
impl AggregatedNonce {
pub fn new(nonces: &[&PublicNonce]) -> Self {
if nonces.is_empty() {
panic!("Cannot aggregate an empty slice of nonces");
}
let mut aggnonce = MaybeUninit::<ffi::MusigAggNonce>::uninit();
unsafe {
let pubnonces = core::slice::from_raw_parts(
nonces.as_c_ptr().cast::<*const ffi::MusigPubNonce>(),
nonces.len(),
);
let ret = crate::with_global_context(
|secp: &Secp256k1<crate::AllPreallocated>| {
ffi::secp256k1_musig_nonce_agg(
secp.ctx().as_ptr(),
aggnonce.as_mut_ptr(),
pubnonces.as_ptr(),
pubnonces.len(),
)
},
None,
);
if ret == 0 {
unreachable!("Public key nonces are well-formed and valid in rust typesystem")
} else {
AggregatedNonce(aggnonce.assume_init())
}
}
}
pub fn serialize(&self) -> [u8; AGGNONCE_SERIALIZED_SIZE] {
let mut data = [0; AGGNONCE_SERIALIZED_SIZE];
unsafe {
if ffi::secp256k1_musig_aggnonce_serialize(
ffi::secp256k1_context_static,
data.as_mut_ptr(),
self.as_ptr(),
) == 0
{
unreachable!("Arguments must be valid and well-typed")
} else {
data
}
}
}
pub fn from_byte_array(data: &[u8; AGGNONCE_SERIALIZED_SIZE]) -> Result<Self, ParseError> {
let mut aggnonce = MaybeUninit::<ffi::MusigAggNonce>::uninit();
unsafe {
if ffi::secp256k1_musig_aggnonce_parse(
ffi::secp256k1_context_static,
aggnonce.as_mut_ptr(),
data.as_ptr(),
) == 0
{
Err(ParseError::MalformedArg)
} else {
Ok(AggregatedNonce(aggnonce.assume_init()))
}
}
}
pub fn as_ptr(&self) -> *const ffi::MusigAggNonce { &self.0 }
pub fn as_mut_ptr(&mut self) -> *mut ffi::MusigAggNonce { &mut self.0 }
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AggregatedSignature([u8; 64]);
impl AggregatedSignature {
pub fn assume_valid(self) -> schnorr::Signature { schnorr::Signature::from_byte_array(self.0) }
pub fn verify(
self,
aggregate_key: &XOnlyPublicKey,
message: &[u8],
) -> Result<schnorr::Signature, Error> {
let sig = schnorr::Signature::from_byte_array(self.0);
schnorr::verify(&sig, message, aggregate_key)
.map(|_| sig)
.map_err(|_| Error::IncorrectSignature)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Session(ffi::MusigSession);
impl Session {
pub fn new(key_agg_cache: &KeyAggCache, agg_nonce: AggregatedNonce, msg: &[u8; 32]) -> Self {
let mut session = MaybeUninit::<ffi::MusigSession>::uninit();
unsafe {
let ret = crate::with_global_context(
|secp: &Secp256k1<crate::AllPreallocated>| {
ffi::secp256k1_musig_nonce_process(
secp.ctx().as_ptr(),
session.as_mut_ptr(),
agg_nonce.as_ptr(),
msg.as_c_ptr(),
key_agg_cache.as_ptr(),
)
},
None,
);
if ret == 0 {
unreachable!("Impossible to construct invalid arguments in safe rust.
Also reaches here if R1 + R2*b == point at infinity, but only occurs with 2^128 probability")
} else {
Session(session.assume_init())
}
}
}
pub fn partial_sign(
&self,
mut secnonce: SecretNonce,
keypair: &Keypair,
key_agg_cache: &KeyAggCache,
) -> PartialSignature {
unsafe {
let mut partial_sig = MaybeUninit::<ffi::MusigPartialSignature>::uninit();
let res = crate::with_global_context(
|secp: &Secp256k1<crate::AllPreallocated>| {
ffi::secp256k1_musig_partial_sign(
secp.ctx().as_ptr(),
partial_sig.as_mut_ptr(),
secnonce.as_mut_ptr(),
keypair.as_c_ptr(),
key_agg_cache.as_ptr(),
self.as_ptr(),
)
},
Some(&keypair.to_secret_bytes()),
);
assert_eq!(res, 1);
PartialSignature(partial_sig.assume_init())
}
}
pub fn partial_verify(
&self,
key_agg_cache: &KeyAggCache,
partial_sig: &PartialSignature,
pub_nonce: &PublicNonce,
pub_key: PublicKey,
) -> bool {
unsafe {
let ret = crate::with_global_context(
|secp: &Secp256k1<crate::AllPreallocated>| {
ffi::secp256k1_musig_partial_sig_verify(
secp.ctx.as_ptr(),
partial_sig.as_ptr(),
pub_nonce.as_ptr(),
pub_key.as_c_ptr(),
key_agg_cache.as_ptr(),
self.as_ptr(),
)
},
None,
);
ret == 1
}
}
pub fn partial_sig_agg(&self, partial_sigs: &[&PartialSignature]) -> AggregatedSignature {
if partial_sigs.is_empty() {
panic!("Cannot aggregate an empty slice of partial signatures");
}
let mut sig = [0u8; 64];
unsafe {
let partial_sigs_ref = core::slice::from_raw_parts(
partial_sigs.as_ptr().cast::<*const ffi::MusigPartialSignature>(),
partial_sigs.len(),
);
if ffi::secp256k1_musig_partial_sig_agg(
ffi::secp256k1_context_static,
sig.as_mut_ptr(),
self.as_ptr(),
partial_sigs_ref.as_ptr(),
partial_sigs_ref.len(),
) == 0
{
unreachable!("Impossible to construct invalid(not well-typed) partial signatures")
} else {
AggregatedSignature(sig)
}
}
}
pub fn as_ptr(&self) -> *const ffi::MusigSession { &self.0 }
pub fn as_mut_ptr(&mut self) -> *mut ffi::MusigSession { &mut self.0 }
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
use crate::PublicKey;
#[test]
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
fn session_secret_rand() {
let mut rng = rand::rng();
let session_secrand = SessionSecretRand::from_rng(&mut rng);
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
assert_ne!(session_secrand.to_secret_bytes(), [0; 32]); assert_ne!(session_secrand.to_secret_bytes(), session_secrand1.to_secret_bytes()); }
#[test]
fn session_secret_no_rand() {
let custom_bytes = [42u8; 32];
let sk = SecretKey::from_secret_bytes([0x11u8; 32]).unwrap();
let session_secrand = SessionSecretRand::assume_unique_per_nonce_gen(custom_bytes, &sk);
let mut expected = AUX_TAGGED_HASH_OF_42S;
for (this, that) in expected.iter_mut().zip(sk.to_secret_bytes().iter()) {
*this ^= *that;
}
assert_eq!(session_secrand.to_secret_bytes(), expected);
assert_eq!(session_secrand.as_secret_bytes(), &expected);
}
const AUX_TAGGED_HASH_OF_42S: [u8; 32] = [
0x83, 0x0c, 0xb6, 0xe8, 0x09, 0x4b, 0xef, 0x3c, 0x2a, 0xce, 0x9b, 0x9f, 0x43, 0x36, 0xc8,
0x74, 0x15, 0x80, 0x51, 0x7a, 0x5c, 0xb4, 0xe6, 0x91, 0xea, 0x5e, 0x5e, 0x32, 0x02, 0xe2,
0xd9, 0xf8,
];
#[test]
fn session_secret_assume_uniformly_random() {
let custom_bytes = [42u8; 32];
let session_secrand = SessionSecretRand::assume_uniformly_random(custom_bytes);
assert_eq!(session_secrand.to_secret_bytes(), custom_bytes);
assert_eq!(session_secrand.as_secret_bytes(), &custom_bytes);
}
#[test]
fn session_secret_rand_debug_output() {
let sk = SecretKey::from_secret_bytes([0x11u8; 32]).unwrap();
let mixed = SessionSecretRand::assume_unique_per_nonce_gen([42u8; 32], &sk);
assert_eq!(format!("{:?}", mixed), "SessionSecretRand(e1edfdd95b263a52)");
let uniform = SessionSecretRand::assume_uniformly_random([42u8; 32]);
assert_eq!(format!("{:?}", uniform), "SessionSecretRand(3e4c6e5318769b4a)");
}
#[test]
#[should_panic(expected = "session secrets may not be all zero")]
fn session_secret_rand_zero_panic() {
let zero_bytes = [0u8; 32];
let _session_secrand = SessionSecretRand::assume_uniformly_random(zero_bytes);
}
#[test]
#[should_panic(expected = "session secrets may not be all zero")]
fn session_secret_rand_mixed_zero_panic() {
let sk = SecretKey::from_secret_bytes(AUX_TAGGED_HASH_OF_42S).unwrap();
let _session_secrand = SessionSecretRand::assume_unique_per_nonce_gen([42u8; 32], &sk);
}
#[test]
#[cfg(not(secp256k1_fuzz))]
#[cfg(feature = "std")]
fn key_agg_cache() {
let (_seckey1, pubkey1) = crate::test_random_keypair();
let (_seckey2, pubkey2) = crate::test_random_keypair();
let pubkeys = [&pubkey1, &pubkey2];
let key_agg_cache = KeyAggCache::new(&pubkeys);
let agg_pk = key_agg_cache.agg_pk();
let agg_pk_full = key_agg_cache.agg_pk_full();
assert_eq!(agg_pk_full.x_only_public_key().0, agg_pk);
}
#[test]
#[cfg(not(secp256k1_fuzz))]
#[cfg(feature = "std")]
fn key_agg_cache_tweaking() {
let (_seckey1, pubkey1) = crate::test_random_keypair();
let (_seckey2, pubkey2) = crate::test_random_keypair();
let mut key_agg_cache = KeyAggCache::new(&[&pubkey1, &pubkey2]);
let key_agg_cache1 = KeyAggCache::new(&[&pubkey2, &pubkey1]);
let key_agg_cache2 = KeyAggCache::new(&[&pubkey1, &pubkey1]);
let key_agg_cache3 = KeyAggCache::new(&[&pubkey1, &pubkey1, &pubkey2]);
assert_ne!(key_agg_cache, key_agg_cache1); assert_ne!(key_agg_cache, key_agg_cache2); assert_ne!(key_agg_cache, key_agg_cache3); let original_agg_pk = key_agg_cache.agg_pk();
assert_ne!(key_agg_cache.agg_pk(), key_agg_cache1.agg_pk()); assert_ne!(key_agg_cache.agg_pk(), key_agg_cache2.agg_pk()); assert_ne!(key_agg_cache.agg_pk(), key_agg_cache3.agg_pk());
let plain_tweak: [u8; 32] = *b"this could be a BIP32 tweak....\0";
let plain_tweak = Scalar::from_be_bytes(plain_tweak).unwrap();
let tweaked_key = key_agg_cache.pubkey_ec_tweak_add(&plain_tweak).unwrap();
assert_ne!(key_agg_cache.agg_pk(), original_agg_pk);
assert_eq!(key_agg_cache.agg_pk(), tweaked_key.x_only_public_key().0);
let xonly_tweak: [u8; 32] = *b"this could be a Taproot tweak..\0";
let xonly_tweak = Scalar::from_be_bytes(xonly_tweak).unwrap();
let tweaked_agg_pk = key_agg_cache.pubkey_xonly_tweak_add(&xonly_tweak).unwrap();
assert_eq!(key_agg_cache.agg_pk(), tweaked_agg_pk.x_only_public_key().0);
}
#[test]
#[cfg(feature = "std")]
#[should_panic(expected = "Cannot aggregate an empty slice of pubkeys")]
fn key_agg_cache_empty_panic() { let _ = KeyAggCache::new(&[]); }
#[test]
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
fn nonce_generation() {
let mut rng = rand::rng();
let (_seckey1, pubkey1) = crate::test_random_keypair();
let (seckey2, pubkey2) = crate::test_random_keypair();
let key_agg_cache = KeyAggCache::new(&[&pubkey1, &pubkey2]);
let msg: &[u8; 32] = b"This message is exactly 32 bytes";
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
let (_sec_nonce1, pub_nonce1) = key_agg_cache.nonce_gen_with_uniform_randomness(
session_secrand1,
pubkey1,
msg,
[42u8; 32],
);
let session_secrand2 = SessionSecretRand::from_rng(&mut rng);
let extra_rand = Some([42u8; 32]);
let (_sec_nonce2, _pub_nonce2) = new_nonce_pair(
session_secrand2,
Some(&key_agg_cache),
Some(seckey2),
pubkey2,
Some(msg),
extra_rand,
);
let serialized_nonce = pub_nonce1.serialize();
let deserialized_nonce = PublicNonce::from_byte_array(&serialized_nonce).unwrap();
assert_eq!(pub_nonce1.serialize(), deserialized_nonce.serialize());
}
#[test]
#[cfg(not(secp256k1_fuzz))]
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
fn nonce_generation_counter() {
let (seckey1, pubkey1) = crate::test_random_keypair();
let (seckey2, pubkey2) = crate::test_random_keypair();
let key_agg_cache = KeyAggCache::new(&[&pubkey1, &pubkey2]);
let msg: &[u8; 32] = b"This message is exactly 32 bytes";
let keypair1 = Keypair::from_secret_key(&seckey1);
let (sec_nonce1, pub_nonce1) = key_agg_cache.nonce_gen(0, &keypair1, msg, None);
let keypair2 = Keypair::from_secret_key(&seckey2);
let extra_rand = Some([42u8; 32]);
let (sec_nonce2, pub_nonce2) =
new_nonce_pair_counter(0, Some(&key_agg_cache), &keypair2, Some(msg), extra_rand);
let agg_nonce = AggregatedNonce::new(&[&pub_nonce1, &pub_nonce2]);
let session = Session::new(&key_agg_cache, agg_nonce, msg);
let partial_sig1 = session.partial_sign(sec_nonce1, &keypair1, &key_agg_cache);
let partial_sig2 = session.partial_sign(sec_nonce2, &keypair2, &key_agg_cache);
assert!(session.partial_verify(&key_agg_cache, &partial_sig1, &pub_nonce1, pubkey1));
assert!(session.partial_verify(&key_agg_cache, &partial_sig2, &pub_nonce2, pubkey2));
let aggregated_signature = session.partial_sig_agg(&[&partial_sig1, &partial_sig2]);
aggregated_signature.verify(&key_agg_cache.agg_pk(), msg).unwrap();
}
#[test]
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
fn aggregated_nonce() {
let mut rng = rand::rng();
let (_seckey1, pubkey1) = crate::test_random_keypair();
let (_seckey2, pubkey2) = crate::test_random_keypair();
let key_agg_cache = KeyAggCache::new(&[&pubkey1, &pubkey2]);
let msg: &[u8; 32] = b"This message is exactly 32 bytes";
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
let (_, pub_nonce1) = key_agg_cache.nonce_gen_with_uniform_randomness(
session_secrand1,
pubkey1,
msg,
[42u8; 32],
);
let session_secrand2 = SessionSecretRand::from_rng(&mut rng);
let (_, pub_nonce2) = key_agg_cache.nonce_gen_with_uniform_randomness(
session_secrand2,
pubkey2,
msg,
[43u8; 32],
);
let agg_nonce = AggregatedNonce::new(&[&pub_nonce1, &pub_nonce2]);
let agg_nonce1 = AggregatedNonce::new(&[&pub_nonce2, &pub_nonce1]);
let agg_nonce2 = AggregatedNonce::new(&[&pub_nonce2, &pub_nonce2]);
let agg_nonce3 = AggregatedNonce::new(&[&pub_nonce2, &pub_nonce2]);
assert_eq!(agg_nonce, agg_nonce1); assert_ne!(agg_nonce, agg_nonce2); assert_ne!(agg_nonce, agg_nonce3);
let serialized_agg_nonce = agg_nonce.serialize();
let deserialized_agg_nonce =
AggregatedNonce::from_byte_array(&serialized_agg_nonce).unwrap();
assert_eq!(agg_nonce.serialize(), deserialized_agg_nonce.serialize());
}
#[test]
#[cfg(feature = "std")]
#[should_panic(expected = "Cannot aggregate an empty slice of nonces")]
fn aggregated_nonce_empty_panic() {
let empty_nonces: Vec<&PublicNonce> = vec![];
let _agg_nonce = AggregatedNonce::new(&empty_nonces);
}
#[test]
#[cfg(not(secp256k1_fuzz))]
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
fn session_and_partial_signing() {
let mut rng = rand::rng();
let (seckey1, pubkey1) = crate::test_random_keypair();
let (seckey2, pubkey2) = crate::test_random_keypair();
let pubkeys = [&pubkey1, &pubkey2];
let key_agg_cache = KeyAggCache::new(&pubkeys);
let msg: &[u8; 32] = b"This message is exactly 32 bytes";
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
let (sec_nonce1, pub_nonce1) = key_agg_cache.nonce_gen_with_uniform_randomness(
session_secrand1,
pubkey1,
msg,
[42u8; 32],
);
let session_secrand2 = SessionSecretRand::from_rng(&mut rng);
let (sec_nonce2, pub_nonce2) = key_agg_cache.nonce_gen_with_uniform_randomness(
session_secrand2,
pubkey2,
msg,
[43u8; 32],
);
let nonces = [&pub_nonce1, &pub_nonce2];
let agg_nonce = AggregatedNonce::new(&nonces);
let session = Session::new(&key_agg_cache, agg_nonce, msg);
let keypair1 = Keypair::from_secret_key(&seckey1);
let partial_sign1 = session.partial_sign(sec_nonce1, &keypair1, &key_agg_cache);
let keypair2 = Keypair::from_secret_key(&seckey2);
let partial_sign2 = session.partial_sign(sec_nonce2, &keypair2, &key_agg_cache);
assert!(session.partial_verify(&key_agg_cache, &partial_sign1, &pub_nonce1, pubkey1));
assert!(session.partial_verify(&key_agg_cache, &partial_sign2, &pub_nonce2, pubkey2));
assert!(!session.partial_verify(&key_agg_cache, &partial_sign2, &pub_nonce2, pubkey1));
assert!(!session.partial_verify(&key_agg_cache, &partial_sign2, &pub_nonce1, pubkey2));
assert!(!session.partial_verify(&key_agg_cache, &partial_sign2, &pub_nonce1, pubkey1));
let serialized_partial_sig = partial_sign1.serialize();
let deserialized_partial_sig =
PartialSignature::from_byte_array(&serialized_partial_sig).unwrap();
assert_eq!(partial_sign1.serialize(), deserialized_partial_sig.serialize());
}
#[test]
#[cfg(not(secp256k1_fuzz))]
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
fn signature_aggregation_and_verification() {
let mut rng = rand::rng();
let (seckey1, pubkey1) = crate::test_random_keypair();
let (seckey2, pubkey2) = crate::test_random_keypair();
let pubkeys = [&pubkey1, &pubkey2];
let key_agg_cache = KeyAggCache::new(&pubkeys);
let msg: &[u8; 32] = b"This message is exactly 32 bytes";
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
let (sec_nonce1, pub_nonce1) = key_agg_cache.nonce_gen_with_uniform_randomness(
session_secrand1,
pubkey1,
msg,
[42u8; 32],
);
let session_secrand2 = SessionSecretRand::from_rng(&mut rng);
let (sec_nonce2, pub_nonce2) = key_agg_cache.nonce_gen_with_uniform_randomness(
session_secrand2,
pubkey2,
msg,
[43u8; 32],
);
let nonces = [&pub_nonce1, &pub_nonce2];
let agg_nonce = AggregatedNonce::new(&nonces);
let session = Session::new(&key_agg_cache, agg_nonce, msg);
let keypair1 = Keypair::from_secret_key(&seckey1);
let partial_sign1 = session.partial_sign(sec_nonce1, &keypair1, &key_agg_cache);
let keypair2 = Keypair::from_secret_key(&seckey2);
let partial_sign2 = session.partial_sign(sec_nonce2, &keypair2, &key_agg_cache);
let aggregated_signature = session.partial_sig_agg(&[&partial_sign1, &partial_sign2]);
let agg_pk = key_agg_cache.agg_pk();
aggregated_signature.verify(&agg_pk, msg).unwrap();
let schnorr_sig = aggregated_signature.assume_valid();
schnorr::verify(&schnorr_sig, msg, &agg_pk).unwrap();
let aggregated_signature = session.partial_sig_agg(&[&partial_sign1, &partial_sign1]);
aggregated_signature.verify(&agg_pk, msg).unwrap_err();
let schnorr_sig = aggregated_signature.assume_valid();
schnorr::verify(&schnorr_sig, msg, &agg_pk).unwrap_err();
let aggregated_signature = session.partial_sig_agg(&[&partial_sign2, &partial_sign1]);
aggregated_signature.verify(&agg_pk, msg).unwrap();
let schnorr_sig = aggregated_signature.assume_valid();
schnorr::verify(&schnorr_sig, msg, &agg_pk).unwrap();
}
#[test]
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
#[should_panic(expected = "Cannot aggregate an empty slice of partial signatures")]
fn partial_sig_agg_empty_panic() {
let mut rng = rand::rng();
let (_seckey1, pubkey1) = crate::test_random_keypair();
let (_seckey2, pubkey2) = crate::test_random_keypair();
let pubkeys = [pubkey1, pubkey2];
let mut pubkeys_ref: Vec<&PublicKey> = pubkeys.iter().collect();
let pubkeys_ref = pubkeys_ref.as_mut_slice();
let key_agg_cache = KeyAggCache::new(pubkeys_ref);
let msg: &[u8; 32] = b"This message is exactly 32 bytes";
let session_secrand1 = SessionSecretRand::from_rng(&mut rng);
let (_, pub_nonce1) = key_agg_cache.nonce_gen_with_uniform_randomness(
session_secrand1,
pubkey1,
msg,
[42u8; 32],
);
let session_secrand2 = SessionSecretRand::from_rng(&mut rng);
let (_, pub_nonce2) = key_agg_cache.nonce_gen_with_uniform_randomness(
session_secrand2,
pubkey2,
msg,
[43u8; 32],
);
let nonces = [pub_nonce1, pub_nonce2];
let nonces_ref: Vec<&PublicNonce> = nonces.iter().collect();
let agg_nonce = AggregatedNonce::new(&nonces_ref);
let session = Session::new(&key_agg_cache, agg_nonce, msg);
let _agg_sig = session.partial_sig_agg(&[]);
}
#[test]
fn de_serialization() {
const MUSIG_PUBLIC_NONCE_HEX: &str = "03f4a361abd3d50535be08421dbc73b0a8f595654ae3238afcaf2599f94e25204c036ba174214433e21f5cd0fcb14b038eb40b05b7e7c820dd21aa568fdb0a9de4d7";
let pubnonce: PublicNonce = MUSIG_PUBLIC_NONCE_HEX.parse().unwrap();
assert_eq!(pubnonce.to_string(), MUSIG_PUBLIC_NONCE_HEX);
const MUSIG_AGGREGATED_NONCE_HEX: &str = "0218c30fe0f567a4a9c05eb4835e2735419cf30f834c9ce2fe3430f021ba4eacd503112e97bcf6a022d236d71a9357824a2b19515f980131b3970b087cadf94cc4a7";
let aggregated_nonce: AggregatedNonce = MUSIG_AGGREGATED_NONCE_HEX.parse().unwrap();
assert_eq!(aggregated_nonce.to_string(), MUSIG_AGGREGATED_NONCE_HEX);
const MUSIG_PARTIAL_SIGNATURE_HEX: &str =
"289eeb2f5efc314aa6d87bf58125043c96d15a007db4b6aaaac7d18086f49a99";
let partial_signature: PartialSignature = MUSIG_PARTIAL_SIGNATURE_HEX.parse().unwrap();
assert_eq!(partial_signature.to_string(), MUSIG_PARTIAL_SIGNATURE_HEX);
}
}