use crate::{
utils::{
OP_0, OP_1, OP_CHECKSIG, OP_DUP, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_PUSHBYTES_20,
OP_PUSHBYTES_32,
},
Error, Result,
};
use bitcoin_hashes::{hash160, Hash};
use secp256k1::{ecdh::shared_secret_point, Parity::Even, XOnlyPublicKey};
use secp256k1::{PublicKey, SecretKey};
use super::{hash::calculate_input_hash, COMPRESSED_PUBKEY_SIZE, NUMS_H};
pub fn calculate_tweak_data(
input_pub_keys: &[&PublicKey],
outpoints_data: &[(String, u32)],
) -> Result<PublicKey> {
let secp = secp256k1::Secp256k1::verification_only();
let A_sum = PublicKey::combine_keys(input_pub_keys)?;
let input_hash = calculate_input_hash(outpoints_data, A_sum)?;
Ok(A_sum.mul_tweak(&secp, &input_hash)?)
}
pub fn calculate_ecdh_shared_secret(tweak_data: &PublicKey, b_scan: &SecretKey) -> PublicKey {
let mut ss_bytes = [0u8; 65];
ss_bytes[0] = 0x04;
ss_bytes[1..].copy_from_slice(&shared_secret_point(&tweak_data, &b_scan));
PublicKey::from_slice(&ss_bytes).expect("guaranteed to be a point on the curve")
}
pub fn get_pubkey_from_input(
script_sig: &[u8],
txinwitness: &Vec<Vec<u8>>,
script_pub_key: &[u8],
) -> Result<Option<PublicKey>> {
if is_p2pkh(script_pub_key) {
match (txinwitness.is_empty(), script_sig.is_empty()) {
(true, false) => {
let spk_hash = &script_pub_key[3..23];
for i in (COMPRESSED_PUBKEY_SIZE..=script_sig.len()).rev() {
if let Some(pubkey_bytes) = script_sig.get(i - COMPRESSED_PUBKEY_SIZE..i) {
let pubkey_hash = hash160::Hash::hash(pubkey_bytes);
if pubkey_hash.to_byte_array() == spk_hash {
return Ok(Some(PublicKey::from_slice(pubkey_bytes)?));
}
} else {
return Ok(None);
}
}
}
(_, true) => {
return Err(Error::InvalidVin(
"Empty script_sig for spending a p2pkh".to_owned(),
))
}
(false, _) => {
return Err(Error::InvalidVin(
"non empty witness for spending a p2pkh".to_owned(),
))
}
}
} else if is_p2sh(script_pub_key) {
match (txinwitness.is_empty(), script_sig.is_empty()) {
(false, false) => {
let redeem_script = &script_sig[1..];
if is_p2wpkh(redeem_script) {
if let Some(value) = txinwitness.last() {
match (
PublicKey::from_slice(value),
value.len() == COMPRESSED_PUBKEY_SIZE,
) {
(Ok(pubkey), true) => {
return Ok(Some(pubkey));
}
(_, false) => {
return Ok(None);
}
(Err(_), _) => {
return Ok(None);
}
}
}
}
}
(_, true) => {
return Err(Error::InvalidVin(
"Empty script_sig for spending a p2sh".to_owned(),
))
}
(true, false) => return Ok(None),
}
} else if is_p2wpkh(script_pub_key) {
match (txinwitness.is_empty(), script_sig.is_empty()) {
(false, true) => {
if let Some(value) = txinwitness.last() {
match (
PublicKey::from_slice(value),
value.len() == COMPRESSED_PUBKEY_SIZE,
) {
(Ok(pubkey), true) => {
return Ok(Some(pubkey));
}
(_, false) => {
return Ok(None);
}
(Err(_), _) => {
return Ok(None);
}
}
} else {
return Err(Error::InvalidVin("Empty witness".to_owned()));
}
}
(_, false) => {
return Err(Error::InvalidVin(
"Non empty script sig for spending a segwit output".to_owned(),
))
}
(true, _) => {
return Err(Error::InvalidVin(
"Empty witness for spending a segwit output".to_owned(),
))
}
}
} else if is_p2tr(script_pub_key) {
match (txinwitness.is_empty(), script_sig.is_empty()) {
(false, true) => {
let annex = match txinwitness.last().and_then(|value| value.first()) {
Some(&0x50) => 1,
Some(_) => 0,
None => return Err(Error::InvalidVin("Empty or invalid witness".to_owned())),
};
let stack_size = txinwitness.len();
if stack_size > annex && txinwitness[stack_size - annex - 1][1..33] == NUMS_H {
return Ok(None);
}
return XOnlyPublicKey::from_slice(&script_pub_key[2..34])
.map_err(Error::Secp256k1Error)
.map(|x_only_public_key| {
Some(PublicKey::from_x_only_public_key(x_only_public_key, Even))
});
}
(_, false) => {
return Err(Error::InvalidVin(
"Non empty script sig for spending a segwit output".to_owned(),
))
}
(true, _) => {
return Err(Error::InvalidVin(
"Empty witness for spending a segwit output".to_owned(),
))
}
}
}
Ok(None)
}
pub fn is_p2tr(spk: &[u8]) -> bool {
matches!(spk, [OP_1, OP_PUSHBYTES_32, ..] if spk.len() == 34)
}
fn is_p2wpkh(spk: &[u8]) -> bool {
matches!(spk, [OP_0, OP_PUSHBYTES_20, ..] if spk.len() == 22)
}
fn is_p2sh(spk: &[u8]) -> bool {
matches!(spk, [OP_HASH160, OP_PUSHBYTES_20, .., OP_EQUAL] if spk.len() == 23)
}
fn is_p2pkh(spk: &[u8]) -> bool {
matches!(spk, [OP_DUP, OP_HASH160, OP_PUSHBYTES_20, .., OP_EQUALVERIFY, OP_CHECKSIG] if spk.len() == 25)
}