use crate::address::MultisigWallet;
use crate::error::{MultisigError, Result};
use crate::script::opcodes;
use crate::signer::PartialSignature;
#[derive(Debug, Clone)]
pub struct CombinedSignatures {
pub signatures: Vec<Vec<u8>>,
pub script: Vec<u8>,
}
impl CombinedSignatures {
pub fn build_script_sig(&self) -> Vec<u8> {
let mut script_sig = Vec::new();
script_sig.push(opcodes::OP_0);
for sig in &self.signatures {
push_data(&mut script_sig, sig);
}
push_data(&mut script_sig, &self.script);
script_sig
}
pub fn build_witness(&self) -> Vec<Vec<u8>> {
let mut witness = Vec::new();
witness.push(Vec::new());
for sig in &self.signatures {
witness.push(sig.clone());
}
witness.push(self.script.clone());
witness
}
pub fn build_nested(&self, nested_redeem_script: &[u8]) -> (Vec<u8>, Vec<Vec<u8>>) {
let mut script_sig = Vec::new();
push_data(&mut script_sig, nested_redeem_script);
let witness = self.build_witness();
(script_sig, witness)
}
}
pub fn combine_signatures(
signatures: &[PartialSignature],
wallet: &MultisigWallet,
) -> Result<CombinedSignatures> {
let threshold = wallet.config.threshold() as usize;
if signatures.len() < threshold {
return Err(MultisigError::NotEnoughSignatures {
need: threshold,
got: signatures.len(),
});
}
for sig in signatures {
if !wallet.config.contains_key(&sig.pubkey) {
return Err(MultisigError::InvalidSignature {
index: sig.key_index,
reason: "Key not in multisig".to_string(),
});
}
}
let mut sorted_sigs: Vec<_> = signatures.iter().collect();
sorted_sigs.sort_by_key(|s| s.key_index);
let final_sigs: Vec<Vec<u8>> = sorted_sigs
.iter()
.take(threshold)
.map(|s| s.signature.clone())
.collect();
Ok(CombinedSignatures {
signatures: final_sigs,
script: wallet.redeem_script.clone(),
})
}
pub fn verify_signature_count(
signatures: &[PartialSignature],
wallet: &MultisigWallet,
) -> Result<()> {
let threshold = wallet.config.threshold() as usize;
let valid_count = signatures
.iter()
.filter(|s| wallet.config.contains_key(&s.pubkey))
.count();
if valid_count < threshold {
return Err(MultisigError::NotEnoughSignatures {
need: threshold,
got: valid_count,
});
}
Ok(())
}
fn push_data(script: &mut Vec<u8>, data: &[u8]) {
let len = data.len();
if len < 76 {
script.push(len as u8);
} else if len <= 255 {
script.push(0x4c); script.push(len as u8);
} else if len <= 65535 {
script.push(0x4d); script.extend_from_slice(&(len as u16).to_le_bytes());
} else {
script.push(0x4e); script.extend_from_slice(&(len as u32).to_le_bytes());
}
script.extend_from_slice(data);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::address::Network;
use crate::signer::sign_p2sh_multisig;
use rustywallet_keys::prelude::PrivateKey;
fn make_pubkey_from_privkey(privkey: &PrivateKey) -> [u8; 33] {
privkey.public_key().to_compressed()
}
#[test]
fn test_combine_2_of_3() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let key3 = PrivateKey::random();
let pubkeys = vec![
make_pubkey_from_privkey(&key1),
make_pubkey_from_privkey(&key2),
make_pubkey_from_privkey(&key3),
];
let wallet = MultisigWallet::from_pubkeys(2, pubkeys, Network::Mainnet).unwrap();
let sighash = [0xab; 32];
let sig1 = sign_p2sh_multisig(&sighash, &key1, &wallet).unwrap();
let sig2 = sign_p2sh_multisig(&sighash, &key2, &wallet).unwrap();
let combined = combine_signatures(&[sig1, sig2], &wallet).unwrap();
assert_eq!(combined.signatures.len(), 2);
}
#[test]
fn test_not_enough_signatures() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let key3 = PrivateKey::random();
let pubkeys = vec![
make_pubkey_from_privkey(&key1),
make_pubkey_from_privkey(&key2),
make_pubkey_from_privkey(&key3),
];
let wallet = MultisigWallet::from_pubkeys(2, pubkeys, Network::Mainnet).unwrap();
let sighash = [0xcd; 32];
let sig1 = sign_p2sh_multisig(&sighash, &key1, &wallet).unwrap();
let result = combine_signatures(&[sig1], &wallet);
assert!(matches!(result, Err(MultisigError::NotEnoughSignatures { .. })));
}
#[test]
fn test_build_script_sig() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let pubkeys = vec![
make_pubkey_from_privkey(&key1),
make_pubkey_from_privkey(&key2),
];
let wallet = MultisigWallet::from_pubkeys(2, pubkeys, Network::Mainnet).unwrap();
let sighash = [0xef; 32];
let sig1 = sign_p2sh_multisig(&sighash, &key1, &wallet).unwrap();
let sig2 = sign_p2sh_multisig(&sighash, &key2, &wallet).unwrap();
let combined = combine_signatures(&[sig1, sig2], &wallet).unwrap();
let script_sig = combined.build_script_sig();
assert_eq!(script_sig[0], opcodes::OP_0);
assert!(!script_sig.is_empty());
}
#[test]
fn test_build_witness() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let pubkeys = vec![
make_pubkey_from_privkey(&key1),
make_pubkey_from_privkey(&key2),
];
let wallet = MultisigWallet::from_pubkeys(2, pubkeys, Network::Mainnet).unwrap();
let sighash = [0x11; 32];
let sig1 = sign_p2sh_multisig(&sighash, &key1, &wallet).unwrap();
let sig2 = sign_p2sh_multisig(&sighash, &key2, &wallet).unwrap();
let combined = combine_signatures(&[sig1, sig2], &wallet).unwrap();
let witness = combined.build_witness();
assert_eq!(witness.len(), 4);
assert!(witness[0].is_empty()); }
}