use bitcoin::key::Keypair;
use bitcoin::secp256k1::{Secp256k1, rand};
use bitcoin::{Network, PrivateKey, PublicKey, ScriptBuf};
use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::str::FromStr;
use crate::error::{EscrowError, Result};
use crate::types::{EscrowParticipant, EscrowRole};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultisigConfig {
pub network: Network,
pub threshold: usize,
pub total: usize,
}
impl Default for MultisigConfig {
fn default() -> Self {
Self {
network: Network::Testnet,
threshold: 2,
total: 3,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultisigWallet {
pub config: MultisigConfig,
pub public_keys: BTreeMap<EscrowRole, String>,
pub descriptor: String,
}
impl MultisigWallet {
pub fn new(config: MultisigConfig, participants: &[EscrowParticipant]) -> Result<Self> {
if participants.len() != config.total {
return Err(EscrowError::Multisig(format!(
"Expected {} participants, got {}",
config.total,
participants.len()
)));
}
let mut public_keys = BTreeMap::new();
for participant in participants {
if let Some(pk) = &participant.public_key {
public_keys.insert(participant.role, pk.to_string());
}
}
if public_keys.len() != config.total {
return Err(EscrowError::Multisig(
"All participants must have public keys".to_string(),
));
}
let descriptor = Self::create_descriptor(config.threshold, &public_keys)?;
Ok(Self {
config,
public_keys,
descriptor,
})
}
fn create_descriptor(
threshold: usize,
public_keys: &BTreeMap<EscrowRole, String>,
) -> Result<String> {
let pk_strs: Vec<String> = public_keys.values().cloned().collect();
let descriptor_str = format!(
"wsh(sortedmulti({},{}))",
threshold,
pk_strs.join(",")
);
Ok(descriptor_str)
}
pub fn script_pubkey(&self) -> Result<ScriptBuf> {
Ok(ScriptBuf::new())
}
pub fn address(&self) -> Result<bitcoin::Address> {
let script = self.script_pubkey()?;
let address = bitcoin::Address::p2wsh(&script, self.config.network);
Ok(address)
}
}
pub fn generate_keypair(network: Network) -> Result<(PrivateKey, PublicKey)> {
let secp = Secp256k1::new();
let mut rng = rand::thread_rng();
let keypair = Keypair::new(&secp, &mut rng);
let secret_key = keypair.secret_key();
let public_key = keypair.public_key();
let private_key = PrivateKey::new(secret_key, network);
let bitcoin_pk = PublicKey::from_slice(&public_key.serialize())
.map_err(|e| EscrowError::Key(format!("Failed to create public key: {}", e)))?;
Ok((private_key, bitcoin_pk))
}
pub fn create_participant(
role: EscrowRole,
id: String,
network: Network,
) -> Result<(EscrowParticipant, PrivateKey)> {
let (private_key, public_key) = generate_keypair(network)?;
let participant = EscrowParticipant {
id,
role,
public_key: Some(public_key),
xonly_public_key: None,
address: None,
signed: false,
joined_at: chrono::Utc::now(),
};
Ok((participant, private_key))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultisigPSBT {
pub psbt_base64: String,
pub signed_by: Vec<EscrowRole>,
pub threshold: usize,
}
impl MultisigPSBT {
pub fn new(psbt_base64: String, threshold: usize) -> Self {
Self {
psbt_base64,
signed_by: Vec::new(),
threshold,
}
}
pub fn add_signature(&mut self, role: EscrowRole) -> Result<()> {
if self.signed_by.contains(&role) {
return Err(EscrowError::Signing(format!(
"Role {:?} has already signed",
role
)));
}
self.signed_by.push(role);
Ok(())
}
pub fn is_ready(&self) -> bool {
self.signed_by.len() >= self.threshold
}
pub fn signatures_needed(&self) -> usize {
self.threshold.saturating_sub(self.signed_by.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keypair_generation() {
let (priv_key, pub_key) = generate_keypair(Network::Testnet).unwrap();
assert!(!priv_key.to_bytes().is_empty());
assert!(!pub_key.to_bytes().is_empty());
}
#[test]
fn test_multisig_wallet_creation() {
let config = MultisigConfig::default();
let participants = vec![
create_participant(EscrowRole::Buyer, "buyer-1".to_string(), Network::Testnet).unwrap().0,
create_participant(EscrowRole::Seller, "seller-1".to_string(), Network::Testnet).unwrap().0,
create_participant(EscrowRole::Arbiter, "arbiter-1".to_string(), Network::Testnet).unwrap().0,
];
let wallet = MultisigWallet::new(config, &participants).unwrap();
assert!(!wallet.descriptor.is_empty());
}
#[test]
fn test_psbt_signatures() {
let mut psbt = MultisigPSBT::new("base64-psbt".to_string(), 2);
assert!(!psbt.is_ready());
assert_eq!(psbt.signatures_needed(), 2);
psbt.add_signature(EscrowRole::Buyer).unwrap();
assert!(!psbt.is_ready());
assert_eq!(psbt.signatures_needed(), 1);
psbt.add_signature(EscrowRole::Seller).unwrap();
assert!(psbt.is_ready());
assert_eq!(psbt.signatures_needed(), 0);
}
}