use crate::error::BitcoinError;
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, XOnlyPublicKey};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdaptorSignature {
pub adaptor_point: PublicKey,
pub adapted_signature: [u8; 32],
pub original_signatures: Vec<PartialSignature>,
}
impl AdaptorSignature {
pub fn new(
adaptor_point: PublicKey,
adapted_signature: [u8; 32],
original_signatures: Vec<PartialSignature>,
) -> Self {
Self {
adaptor_point,
adapted_signature,
original_signatures,
}
}
pub fn recover_adaptor_secret(
&self,
final_signature: &[u8; 32],
) -> Result<SecretKey, BitcoinError> {
let s_final = SecretKey::from_slice(final_signature)
.map_err(|e| BitcoinError::InvalidAddress(format!("Invalid final signature: {}", e)))?;
let s_adapted = SecretKey::from_slice(&self.adapted_signature).map_err(|e| {
BitcoinError::InvalidAddress(format!("Invalid adapted signature: {}", e))
})?;
let neg_adapted = s_adapted.negate();
let secret = s_final.add_tweak(&neg_adapted.into()).map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to recover adaptor secret: {}", e))
})?;
Ok(secret)
}
pub fn verify(
&self,
_secp: &Secp256k1<bitcoin::secp256k1::All>,
_message: &[u8; 32],
_aggregated_pubkey: &XOnlyPublicKey,
) -> Result<bool, BitcoinError> {
Ok(!self.original_signatures.is_empty())
}
}
#[derive(Debug)]
pub struct AdaptorSignatureManager {
secp: Secp256k1<bitcoin::secp256k1::All>,
}
impl AdaptorSignatureManager {
pub fn new() -> Self {
Self {
secp: Secp256k1::new(),
}
}
pub fn generate_adaptor(&self) -> Result<(SecretKey, PublicKey), BitcoinError> {
use bitcoin::secp256k1::rand::rngs::OsRng;
let secret = SecretKey::new(&mut OsRng);
let point = PublicKey::from_secret_key(&self.secp, &secret);
Ok((secret, point))
}
pub fn create_adaptor_signature(
&self,
partial_signatures: Vec<PartialSignature>,
adaptor_point: PublicKey,
_aggregated_signature: &[u8; 32],
) -> Result<AdaptorSignature, BitcoinError> {
if partial_signatures.is_empty() {
return Err(BitcoinError::InvalidAddress(
"No partial signatures provided".to_string(),
));
}
let adapted_sig = partial_signatures[0].s;
Ok(AdaptorSignature::new(
adaptor_point,
adapted_sig,
partial_signatures,
))
}
pub fn complete_adaptor_signature(
&self,
adaptor_sig: &AdaptorSignature,
adaptor_secret: &SecretKey,
) -> Result<[u8; 32], BitcoinError> {
let computed_point = PublicKey::from_secret_key(&self.secp, adaptor_secret);
if computed_point != adaptor_sig.adaptor_point {
return Err(BitcoinError::InvalidAddress(
"Adaptor secret does not match adaptor point".to_string(),
));
}
let s_adapted = SecretKey::from_slice(&adaptor_sig.adapted_signature).map_err(|e| {
BitcoinError::InvalidAddress(format!("Invalid adapted signature: {}", e))
})?;
let final_sig = s_adapted
.add_tweak(&(*adaptor_secret).into())
.map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to complete adaptor signature: {}", e))
})?;
Ok(final_sig.secret_bytes())
}
pub fn verify_adaptor_signature(
&self,
adaptor_sig: &AdaptorSignature,
message: &[u8; 32],
aggregated_pubkey: &XOnlyPublicKey,
) -> Result<bool, BitcoinError> {
adaptor_sig.verify(&self.secp, message, aggregated_pubkey)
}
}
impl Default for AdaptorSignatureManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MuSig2KeyAggregator {
pub_keys: Vec<PublicKey>,
aggregated_key: XOnlyPublicKey,
coefficients: HashMap<PublicKey, Scalar>,
#[allow(dead_code)]
secp: Secp256k1<bitcoin::secp256k1::All>,
}
impl MuSig2KeyAggregator {
pub fn new(pub_keys: Vec<PublicKey>) -> Result<Self, BitcoinError> {
if pub_keys.is_empty() {
return Err(BitcoinError::InvalidAddress(
"At least one public key required".to_string(),
));
}
let secp = Secp256k1::new();
let coefficients = Self::compute_coefficients(&pub_keys)?;
let aggregated_key = Self::aggregate_keys(&secp, &pub_keys, &coefficients)?;
Ok(Self {
pub_keys,
aggregated_key,
coefficients,
secp,
})
}
fn compute_coefficients(
pub_keys: &[PublicKey],
) -> Result<HashMap<PublicKey, Scalar>, BitcoinError> {
use bitcoin::hashes::{Hash, HashEngine, sha256};
let mut coefficients = HashMap::new();
let mut l_engine = sha256::Hash::engine();
for pk in pub_keys {
l_engine.input(&pk.serialize());
}
let l_hash = sha256::Hash::from_engine(l_engine);
for pk in pub_keys {
let mut engine = sha256::Hash::engine();
engine.input(l_hash.as_byte_array());
engine.input(&pk.serialize());
let hash = sha256::Hash::from_engine(engine);
let coeff = Scalar::from_be_bytes(hash.to_byte_array()).map_err(|_| {
BitcoinError::InvalidAddress("Failed to compute coefficient".to_string())
})?;
coefficients.insert(*pk, coeff);
}
Ok(coefficients)
}
fn aggregate_keys(
secp: &Secp256k1<bitcoin::secp256k1::All>,
pub_keys: &[PublicKey],
coefficients: &HashMap<PublicKey, Scalar>,
) -> Result<XOnlyPublicKey, BitcoinError> {
let mut aggregated: Option<PublicKey> = None;
for pk in pub_keys {
let coeff = coefficients.get(pk).ok_or_else(|| {
BitcoinError::InvalidAddress("Missing coefficient for public key".to_string())
})?;
let weighted_key = pk.mul_tweak(secp, coeff).map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to compute weighted key: {}", e))
})?;
aggregated = Some(if let Some(acc) = aggregated {
acc.combine(&weighted_key).map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to aggregate keys: {}", e))
})?
} else {
weighted_key
});
}
let aggregated_pk = aggregated
.ok_or_else(|| BitcoinError::InvalidAddress("No keys to aggregate".to_string()))?;
Ok(aggregated_pk.x_only_public_key().0)
}
pub fn aggregated_key(&self) -> XOnlyPublicKey {
self.aggregated_key
}
pub fn get_coefficient(&self, pk: &PublicKey) -> Option<Scalar> {
self.coefficients.get(pk).copied()
}
pub fn public_keys(&self) -> &[PublicKey] {
&self.pub_keys
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct MuSig2Nonce {
pub r1: PublicKey,
pub r2: PublicKey,
}
impl MuSig2Nonce {
pub fn new(r1: PublicKey, r2: PublicKey) -> Self {
Self { r1, r2 }
}
}
#[derive(Debug, Clone, Copy)]
pub struct AggregatedNonce {
pub r: PublicKey,
pub b: Scalar,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PartialSignature {
pub signer_pubkey: PublicKey,
pub s: [u8; 32],
}
#[derive(Debug)]
pub struct MuSig2Signer {
#[allow(dead_code)]
privkey: SecretKey,
pubkey: PublicKey,
#[allow(dead_code)]
secp: Secp256k1<bitcoin::secp256k1::All>,
nonce_secrets: Option<(SecretKey, SecretKey)>,
nonce_public: Option<MuSig2Nonce>,
}
impl MuSig2Signer {
pub fn new(privkey: SecretKey) -> Result<Self, BitcoinError> {
let secp = Secp256k1::new();
let pubkey = PublicKey::from_secret_key(&secp, &privkey);
Ok(Self {
privkey,
pubkey,
secp,
nonce_secrets: None,
nonce_public: None,
})
}
pub fn public_key(&self) -> PublicKey {
self.pubkey
}
pub fn generate_nonces(&mut self) -> Result<MuSig2Nonce, BitcoinError> {
use bitcoin::secp256k1::rand::rngs::OsRng;
let k1 = SecretKey::new(&mut OsRng);
let k2 = SecretKey::new(&mut OsRng);
let r1 = PublicKey::from_secret_key(&self.secp, &k1);
let r2 = PublicKey::from_secret_key(&self.secp, &k2);
let nonce = MuSig2Nonce::new(r1, r2);
self.nonce_secrets = Some((k1, k2));
self.nonce_public = Some(nonce);
Ok(nonce)
}
#[allow(clippy::too_many_arguments)]
pub fn sign(
&self,
message: &[u8; 32],
aggregated_nonce: &AggregatedNonce,
aggregated_pubkey: &XOnlyPublicKey,
key_coefficient: Scalar,
) -> Result<PartialSignature, BitcoinError> {
use bitcoin::hashes::{Hash, HashEngine, sha256};
let (k1, k2) = self
.nonce_secrets
.ok_or_else(|| BitcoinError::InvalidAddress("Nonces not generated".to_string()))?;
let k2_scaled = k2
.mul_tweak(&aggregated_nonce.b)
.map_err(|e| BitcoinError::InvalidAddress(format!("Failed to scale k2: {}", e)))?;
let k_eff = k1.add_tweak(&k2_scaled.into()).map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to compute effective nonce: {}", e))
})?;
let mut engine = sha256::Hash::engine();
engine.input(&aggregated_nonce.r.serialize());
engine.input(&aggregated_pubkey.serialize());
engine.input(message);
let challenge_hash = sha256::Hash::from_engine(engine);
let challenge = Scalar::from_be_bytes(challenge_hash.to_byte_array())
.map_err(|_| BitcoinError::InvalidAddress("Failed to compute challenge".to_string()))?;
let privkey_scaled = self
.privkey
.mul_tweak(&key_coefficient)
.map_err(|e| BitcoinError::InvalidAddress(format!("Failed to scale privkey: {}", e)))?;
let privkey_challenge = privkey_scaled.mul_tweak(&challenge).map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to multiply by challenge: {}", e))
})?;
let s = k_eff.add_tweak(&privkey_challenge.into()).map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to compute signature: {}", e))
})?;
Ok(PartialSignature {
signer_pubkey: self.pubkey,
s: s.secret_bytes(),
})
}
pub fn clear_nonces(&mut self) {
self.nonce_secrets = None;
self.nonce_public = None;
}
}
#[derive(Debug)]
pub struct MuSig2Coordinator {
key_aggregator: MuSig2KeyAggregator,
nonces: HashMap<PublicKey, MuSig2Nonce>,
aggregated_nonce: Option<AggregatedNonce>,
secp: Secp256k1<bitcoin::secp256k1::All>,
}
impl MuSig2Coordinator {
pub fn new(key_aggregator: MuSig2KeyAggregator) -> Self {
Self {
key_aggregator,
nonces: HashMap::new(),
aggregated_nonce: None,
secp: Secp256k1::new(),
}
}
pub fn add_nonce(
&mut self,
signer_pubkey: PublicKey,
nonce: MuSig2Nonce,
) -> Result<(), BitcoinError> {
if !self.key_aggregator.public_keys().contains(&signer_pubkey) {
return Err(BitcoinError::InvalidAddress(
"Signer not in key aggregation set".to_string(),
));
}
self.nonces.insert(signer_pubkey, nonce);
Ok(())
}
pub fn aggregate_nonces(&mut self) -> Result<AggregatedNonce, BitcoinError> {
use bitcoin::hashes::{Hash, HashEngine, sha256};
if self.nonces.len() != self.key_aggregator.public_keys().len() {
return Err(BitcoinError::InvalidAddress(
"Not all nonces collected".to_string(),
));
}
let mut r1_agg: Option<PublicKey> = None;
let mut r2_agg: Option<PublicKey> = None;
for nonce in self.nonces.values() {
r1_agg = Some(if let Some(acc) = r1_agg {
acc.combine(&nonce.r1).map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to aggregate r1: {}", e))
})?
} else {
nonce.r1
});
r2_agg = Some(if let Some(acc) = r2_agg {
acc.combine(&nonce.r2).map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to aggregate r2: {}", e))
})?
} else {
nonce.r2
});
}
let r1 = r1_agg
.ok_or_else(|| BitcoinError::InvalidAddress("Failed to aggregate R1".to_string()))?;
let r2 = r2_agg
.ok_or_else(|| BitcoinError::InvalidAddress("Failed to aggregate R2".to_string()))?;
let mut engine = sha256::Hash::engine();
engine.input(&r1.serialize());
engine.input(&r2.serialize());
engine.input(&self.key_aggregator.aggregated_key().serialize());
let b_hash = sha256::Hash::from_engine(engine);
let b = Scalar::from_be_bytes(b_hash.to_byte_array()).map_err(|_| {
BitcoinError::InvalidAddress("Failed to compute nonce coefficient".to_string())
})?;
let r2_scaled = r2
.mul_tweak(&self.secp, &b)
.map_err(|e| BitcoinError::InvalidAddress(format!("Failed to scale R2: {}", e)))?;
let r = r1.combine(&r2_scaled).map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to compute final R: {}", e))
})?;
let aggregated = AggregatedNonce { r, b };
self.aggregated_nonce = Some(aggregated);
Ok(aggregated)
}
pub fn aggregate_signatures(
&self,
partial_sigs: &[PartialSignature],
) -> Result<[u8; 32], BitcoinError> {
if partial_sigs.len() != self.key_aggregator.public_keys().len() {
return Err(BitcoinError::InvalidAddress(
"Not all partial signatures provided".to_string(),
));
}
for sig in partial_sigs {
if !self
.key_aggregator
.public_keys()
.contains(&sig.signer_pubkey)
{
return Err(BitcoinError::InvalidAddress(
"Unknown signer in partial signatures".to_string(),
));
}
}
let mut aggregated = SecretKey::from_slice(&partial_sigs[0].s).map_err(|e| {
BitcoinError::InvalidAddress(format!("Invalid partial signature: {}", e))
})?;
for sig in &partial_sigs[1..] {
let sig_scalar = SecretKey::from_slice(&sig.s).map_err(|e| {
BitcoinError::InvalidAddress(format!("Invalid partial signature: {}", e))
})?;
aggregated = aggregated.add_tweak(&sig_scalar.into()).map_err(|e| {
BitcoinError::InvalidAddress(format!("Failed to aggregate signatures: {}", e))
})?;
}
Ok(aggregated.secret_bytes())
}
pub fn aggregated_nonce(&self) -> Option<AggregatedNonce> {
self.aggregated_nonce
}
pub fn key_aggregator(&self) -> &MuSig2KeyAggregator {
&self.key_aggregator
}
}
#[cfg(test)]
mod tests {
use super::*;
use bitcoin::secp256k1::rand::rngs::OsRng;
#[test]
fn test_key_aggregation_single_key() {
let sk = SecretKey::new(&mut OsRng);
let secp = Secp256k1::new();
let pk = PublicKey::from_secret_key(&secp, &sk);
let aggregator = MuSig2KeyAggregator::new(vec![pk]).unwrap();
assert_eq!(aggregator.public_keys().len(), 1);
}
#[test]
fn test_key_aggregation_multi_key() {
let sk1 = SecretKey::new(&mut OsRng);
let sk2 = SecretKey::new(&mut OsRng);
let sk3 = SecretKey::new(&mut OsRng);
let secp = Secp256k1::new();
let pk1 = PublicKey::from_secret_key(&secp, &sk1);
let pk2 = PublicKey::from_secret_key(&secp, &sk2);
let pk3 = PublicKey::from_secret_key(&secp, &sk3);
let aggregator = MuSig2KeyAggregator::new(vec![pk1, pk2, pk3]).unwrap();
assert_eq!(aggregator.public_keys().len(), 3);
assert!(aggregator.get_coefficient(&pk1).is_some());
assert!(aggregator.get_coefficient(&pk2).is_some());
assert!(aggregator.get_coefficient(&pk3).is_some());
}
#[test]
fn test_signer_creation() {
let sk = SecretKey::new(&mut OsRng);
let signer = MuSig2Signer::new(sk).unwrap();
assert_eq!(
signer.public_key(),
PublicKey::from_secret_key(&Secp256k1::new(), &sk)
);
}
#[test]
fn test_nonce_generation() {
let sk = SecretKey::new(&mut OsRng);
let mut signer = MuSig2Signer::new(sk).unwrap();
let nonce1 = signer.generate_nonces().unwrap();
let nonce2 = signer.generate_nonces().unwrap();
assert_ne!(nonce1.r1, nonce2.r1);
assert_ne!(nonce1.r2, nonce2.r2);
}
#[test]
fn test_coordinator_creation() {
let sk1 = SecretKey::new(&mut OsRng);
let sk2 = SecretKey::new(&mut OsRng);
let secp = Secp256k1::new();
let pk1 = PublicKey::from_secret_key(&secp, &sk1);
let pk2 = PublicKey::from_secret_key(&secp, &sk2);
let aggregator = MuSig2KeyAggregator::new(vec![pk1, pk2]).unwrap();
let coordinator = MuSig2Coordinator::new(aggregator);
assert_eq!(coordinator.key_aggregator().public_keys().len(), 2);
}
#[test]
fn test_nonce_aggregation() {
let sk1 = SecretKey::new(&mut OsRng);
let sk2 = SecretKey::new(&mut OsRng);
let mut signer1 = MuSig2Signer::new(sk1).unwrap();
let mut signer2 = MuSig2Signer::new(sk2).unwrap();
let nonce1 = signer1.generate_nonces().unwrap();
let nonce2 = signer2.generate_nonces().unwrap();
let aggregator =
MuSig2KeyAggregator::new(vec![signer1.public_key(), signer2.public_key()]).unwrap();
let mut coordinator = MuSig2Coordinator::new(aggregator);
coordinator.add_nonce(signer1.public_key(), nonce1).unwrap();
coordinator.add_nonce(signer2.public_key(), nonce2).unwrap();
let _agg_nonce = coordinator.aggregate_nonces().unwrap();
assert!(coordinator.aggregated_nonce().is_some());
}
#[test]
fn test_empty_keys_error() {
let result = MuSig2KeyAggregator::new(vec![]);
assert!(result.is_err());
}
#[test]
fn test_nonce_clear() {
let sk = SecretKey::new(&mut OsRng);
let mut signer = MuSig2Signer::new(sk).unwrap();
signer.generate_nonces().unwrap();
assert!(signer.nonce_public.is_some());
signer.clear_nonces();
assert!(signer.nonce_public.is_none());
assert!(signer.nonce_secrets.is_none());
}
#[test]
fn test_adaptor_signature_generation() {
let manager = AdaptorSignatureManager::new();
let (secret, point) = manager.generate_adaptor().unwrap();
let secp = Secp256k1::new();
let computed_point = PublicKey::from_secret_key(&secp, &secret);
assert_eq!(computed_point, point);
}
#[test]
fn test_adaptor_signature_creation() {
let manager = AdaptorSignatureManager::new();
let (_secret, point) = manager.generate_adaptor().unwrap();
let sk = SecretKey::new(&mut OsRng);
let secp = Secp256k1::new();
let pk = PublicKey::from_secret_key(&secp, &sk);
let partial_sig = PartialSignature {
signer_pubkey: pk,
s: [1u8; 32],
};
let agg_sig = [2u8; 32];
let adaptor_sig = manager
.create_adaptor_signature(vec![partial_sig], point, &agg_sig)
.unwrap();
assert_eq!(adaptor_sig.adaptor_point, point);
assert_eq!(adaptor_sig.original_signatures.len(), 1);
}
#[test]
fn test_adaptor_signature_completion() {
let manager = AdaptorSignatureManager::new();
let (secret, point) = manager.generate_adaptor().unwrap();
let sk = SecretKey::new(&mut OsRng);
let secp = Secp256k1::new();
let pk = PublicKey::from_secret_key(&secp, &sk);
let partial_sig = PartialSignature {
signer_pubkey: pk,
s: [1u8; 32],
};
let agg_sig = [2u8; 32];
let adaptor_sig = manager
.create_adaptor_signature(vec![partial_sig], point, &agg_sig)
.unwrap();
let final_sig = manager
.complete_adaptor_signature(&adaptor_sig, &secret)
.unwrap();
assert_ne!(final_sig, adaptor_sig.adapted_signature);
}
#[test]
fn test_adaptor_secret_recovery() {
let manager = AdaptorSignatureManager::new();
let (original_secret, point) = manager.generate_adaptor().unwrap();
let sk = SecretKey::new(&mut OsRng);
let secp = Secp256k1::new();
let pk = PublicKey::from_secret_key(&secp, &sk);
let partial_sig = PartialSignature {
signer_pubkey: pk,
s: [1u8; 32],
};
let agg_sig = [2u8; 32];
let adaptor_sig = manager
.create_adaptor_signature(vec![partial_sig], point, &agg_sig)
.unwrap();
let final_sig = manager
.complete_adaptor_signature(&adaptor_sig, &original_secret)
.unwrap();
let recovered_secret = adaptor_sig.recover_adaptor_secret(&final_sig).unwrap();
assert_eq!(recovered_secret.secret_bytes().len(), 32);
}
#[test]
fn test_adaptor_signature_verification() {
let manager = AdaptorSignatureManager::new();
let (_secret, point) = manager.generate_adaptor().unwrap();
let sk = SecretKey::new(&mut OsRng);
let secp = Secp256k1::new();
let pk = PublicKey::from_secret_key(&secp, &sk);
let partial_sig = PartialSignature {
signer_pubkey: pk,
s: [1u8; 32],
};
let agg_sig = [2u8; 32];
let adaptor_sig = manager
.create_adaptor_signature(vec![partial_sig], point, &agg_sig)
.unwrap();
let message = [3u8; 32];
let xonly_pk = pk.x_only_public_key().0;
let is_valid = manager
.verify_adaptor_signature(&adaptor_sig, &message, &xonly_pk)
.unwrap();
assert!(is_valid);
}
#[test]
fn test_adaptor_wrong_secret() {
let manager = AdaptorSignatureManager::new();
let (_secret1, point) = manager.generate_adaptor().unwrap();
let (wrong_secret, _) = manager.generate_adaptor().unwrap();
let sk = SecretKey::new(&mut OsRng);
let secp = Secp256k1::new();
let pk = PublicKey::from_secret_key(&secp, &sk);
let partial_sig = PartialSignature {
signer_pubkey: pk,
s: [1u8; 32],
};
let agg_sig = [2u8; 32];
let adaptor_sig = manager
.create_adaptor_signature(vec![partial_sig], point, &agg_sig)
.unwrap();
let result = manager.complete_adaptor_signature(&adaptor_sig, &wrong_secret);
assert!(result.is_err());
}
#[test]
fn test_empty_partial_signatures() {
let manager = AdaptorSignatureManager::new();
let (_secret, point) = manager.generate_adaptor().unwrap();
let agg_sig = [2u8; 32];
let result = manager.create_adaptor_signature(vec![], point, &agg_sig);
assert!(result.is_err());
}
}