use crate::address::MultisigWallet;
use crate::error::{MultisigError, Result};
use rustywallet_keys::prelude::PrivateKey;
use secp256k1::{Secp256k1, Message, SecretKey};
use sha2::{Sha256, Digest};
pub mod sighash_type {
pub const ALL: u32 = 0x01;
pub const NONE: u32 = 0x02;
pub const SINGLE: u32 = 0x03;
pub const ANYONECANPAY: u32 = 0x80;
}
#[derive(Debug, Clone)]
pub struct PartialSignature {
pub pubkey: [u8; 33],
pub signature: Vec<u8>,
pub key_index: usize,
}
pub fn sign_p2sh_multisig(
sighash: &[u8; 32],
private_key: &PrivateKey,
wallet: &MultisigWallet,
) -> Result<PartialSignature> {
let pubkey = private_key.public_key().to_compressed();
let key_index = wallet
.config
.key_index(&pubkey)
.ok_or_else(|| MultisigError::InvalidPublicKey("Key not in multisig".to_string()))?;
let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(&private_key.to_bytes())
.map_err(|e| MultisigError::SigningFailed(e.to_string()))?;
let message = Message::from_digest(*sighash);
let signature = secp.sign_ecdsa(&message, &secret_key);
let mut sig_bytes = signature.serialize_der().to_vec();
sig_bytes.push(sighash_type::ALL as u8);
Ok(PartialSignature {
pubkey,
signature: sig_bytes,
key_index,
})
}
pub fn sign_p2wsh_multisig(
sighash: &[u8; 32],
private_key: &PrivateKey,
wallet: &MultisigWallet,
) -> Result<PartialSignature> {
sign_p2sh_multisig(sighash, private_key, wallet)
}
pub fn compute_p2sh_sighash(
tx_bytes: &[u8],
input_index: usize,
redeem_script: &[u8],
) -> [u8; 32] {
let mut data = Vec::new();
data.extend_from_slice(tx_bytes);
data.extend_from_slice(&(input_index as u32).to_le_bytes());
data.extend_from_slice(redeem_script);
data.extend_from_slice(&sighash_type::ALL.to_le_bytes());
double_sha256(&data)
}
pub struct Bip143SighashParams<'a> {
pub version: i32,
pub hash_prevouts: &'a [u8; 32],
pub hash_sequence: &'a [u8; 32],
pub outpoint: &'a [u8],
pub script_code: &'a [u8],
pub value: u64,
pub sequence: u32,
pub hash_outputs: &'a [u8; 32],
pub locktime: u32,
}
#[allow(clippy::too_many_arguments)]
pub fn compute_p2wsh_sighash(params: &Bip143SighashParams) -> [u8; 32] {
let mut data = Vec::new();
data.extend_from_slice(¶ms.version.to_le_bytes());
data.extend_from_slice(params.hash_prevouts);
data.extend_from_slice(params.hash_sequence);
data.extend_from_slice(params.outpoint);
encode_varint(&mut data, params.script_code.len() as u64);
data.extend_from_slice(params.script_code);
data.extend_from_slice(¶ms.value.to_le_bytes());
data.extend_from_slice(¶ms.sequence.to_le_bytes());
data.extend_from_slice(params.hash_outputs);
data.extend_from_slice(¶ms.locktime.to_le_bytes());
data.extend_from_slice(&sighash_type::ALL.to_le_bytes());
double_sha256(&data)
}
fn double_sha256(data: &[u8]) -> [u8; 32] {
let first = Sha256::digest(data);
let second = Sha256::digest(first);
let mut result = [0u8; 32];
result.copy_from_slice(&second);
result
}
fn encode_varint(buf: &mut Vec<u8>, n: u64) {
if n < 0xfd {
buf.push(n as u8);
} else if n <= 0xffff {
buf.push(0xfd);
buf.extend_from_slice(&(n as u16).to_le_bytes());
} else if n <= 0xffffffff {
buf.push(0xfe);
buf.extend_from_slice(&(n as u32).to_le_bytes());
} else {
buf.push(0xff);
buf.extend_from_slice(&n.to_le_bytes());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::address::Network;
fn make_pubkey_from_privkey(privkey: &PrivateKey) -> [u8; 33] {
privkey.public_key().to_compressed()
}
#[test]
fn test_sign_p2sh_multisig() {
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();
assert!(!sig1.signature.is_empty());
assert!(wallet.config.contains_key(&sig1.pubkey));
let sig2 = sign_p2sh_multisig(&sighash, &key2, &wallet).unwrap();
assert!(!sig2.signature.is_empty());
}
#[test]
fn test_sign_with_wrong_key() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let wrong_key = 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 = [0xcd; 32];
let result = sign_p2sh_multisig(&sighash, &wrong_key, &wallet);
assert!(result.is_err());
}
}