use coins_core::{
hashes::{Digest, Hash160, Hash160Digest, Hash256Digest, MarkedDigestOutput, Sha256},
impl_hex_serde, impl_script_conversion,
types::tx::RecipientIdentifier,
wrap_prefixed_byte_vector,
};
pub trait BitcoinScript {}
wrap_prefixed_byte_vector!(
Script
);
wrap_prefixed_byte_vector!(
ScriptSig
);
wrap_prefixed_byte_vector!(
WitnessStackItem
);
wrap_prefixed_byte_vector!(
ScriptPubkey
);
impl BitcoinScript for Script {}
impl BitcoinScript for ScriptPubkey {}
impl BitcoinScript for ScriptSig {}
impl BitcoinScript for WitnessStackItem {}
impl_script_conversion!(Script, ScriptPubkey);
impl_script_conversion!(Script, ScriptSig);
impl_script_conversion!(Script, WitnessStackItem);
impl_script_conversion!(ScriptPubkey, ScriptSig);
impl_script_conversion!(ScriptPubkey, WitnessStackItem);
impl_script_conversion!(ScriptSig, WitnessStackItem);
impl RecipientIdentifier for ScriptPubkey {}
pub type Witness = Vec<WitnessStackItem>;
pub type TxWitness = Vec<Witness>;
impl ScriptPubkey {
pub fn p2pkh<K>(key: &K) -> Self
where
K: AsRef<coins_bip32::ecdsa::VerifyingKey>,
{
let digest = Hash160::digest(&key.as_ref().to_bytes());
let mut v: Vec<u8> = vec![0x76, 0xa9, 0x14]; v.extend(&digest);
v.extend(&[0x88, 0xac]); v.into()
}
pub fn p2wpkh<K>(key: &K) -> Self
where
K: AsRef<coins_bip32::ecdsa::VerifyingKey>,
{
let digest = Hash160::digest(&key.as_ref().to_bytes());
let mut v: Vec<u8> = vec![0x00, 0x14]; v.extend(&digest);
v.into()
}
pub fn p2sh(script: &Script) -> Self {
let mut v: Vec<u8> = vec![0xa9, 0x14]; v.extend(Hash160::digest(script.as_ref()).as_slice());
v.extend(&[0x87]); v.into()
}
pub fn p2wsh(script: &Script) -> Self {
let mut v: Vec<u8> = vec![0x00, 0x20]; v.extend(Sha256::digest(script.as_ref()));
v.into()
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum ScriptType {
Pkh(Hash160Digest),
Sh(Hash160Digest),
Wpkh(Hash160Digest),
Wsh(Hash256Digest),
OpReturn(Vec<u8>),
NonStandard,
}
impl ScriptPubkey {
pub fn extract_op_return_data(&self) -> Option<Vec<u8>> {
if self.len() < 2 {
return None;
}
if self[0] == 0x6a && self[1] <= 75 && self[1] as usize == (self.len() - 2) {
return Some(self.0[2..].to_vec());
}
None
}
pub fn standard_type(&self) -> ScriptType {
if let Some(data) = self.extract_op_return_data() {
return ScriptType::OpReturn(data);
}
let items = &self.0;
match self.0.len() {
0x19 => {
if items[0..3] == [0x76, 0xa9, 0x14] && items[0x17..] == [0x88, 0xac] {
let mut buf = Hash160Digest::default();
buf.as_mut_slice().copy_from_slice(&items[3..23]);
return ScriptType::Pkh(buf);
}
}
0x17 => {
if items[0..2] == [0xa9, 0x14] && items[0x16..] == [0x87] {
let mut buf = Hash160Digest::default();
buf.as_mut_slice().copy_from_slice(&items[2..22]);
return ScriptType::Sh(buf);
}
}
0x16 => {
if items[0..2] == [0x00, 0x14] {
let mut buf = Hash160Digest::default();
buf.as_mut_slice().copy_from_slice(&items[2..22]);
return ScriptType::Wpkh(buf);
}
}
0x22 => {
if items[0..2] == [0x00, 0x20] {
let mut buf = Hash256Digest::default();
buf.as_mut_slice().copy_from_slice(&items[2..34]);
return ScriptType::Wsh(buf);
}
}
_ => return ScriptType::NonStandard,
}
ScriptType::NonStandard
}
}
#[cfg(test)]
mod test {
use super::*;
use coins_core::ser::ByteFormat;
#[test]
fn it_serializes_and_derializes_scripts() {
let cases = [
(
Script::new(hex::decode("0014758ce550380d964051086798d6546bebdca27a73").unwrap()),
"160014758ce550380d964051086798d6546bebdca27a73",
22,
),
(Script::new(vec![]), "00", 0),
(Script::null(), "00", 0),
];
for case in cases.iter() {
let prevout_script = Script::deserialize_hex(case.1).unwrap();
assert_eq!(case.0.serialize_hex(), case.1);
assert_eq!(case.0.len(), case.2);
assert_eq!(case.0.is_empty(), case.2 == 0);
assert_eq!(prevout_script, case.0);
assert_eq!(prevout_script.serialize_hex(), case.1);
assert_eq!(prevout_script.len(), case.2);
assert_eq!(prevout_script.is_empty(), case.2 == 0);
}
}
#[test]
fn it_serializes_and_derializes_witness_stack_items() {
let cases = [
(
WitnessStackItem::new(
hex::decode("0014758ce550380d964051086798d6546bebdca27a73").unwrap(),
),
"160014758ce550380d964051086798d6546bebdca27a73",
22,
),
(WitnessStackItem::new(vec![]), "00", 0),
(WitnessStackItem::null(), "00", 0),
];
for case in cases.iter() {
let prevout_script = WitnessStackItem::deserialize_hex(case.1).unwrap();
assert_eq!(case.0.serialize_hex(), case.1);
assert_eq!(case.0.len(), case.2);
assert_eq!(case.0.is_empty(), case.2 == 0);
assert_eq!(prevout_script, case.0);
assert_eq!(prevout_script.serialize_hex(), case.1);
assert_eq!(prevout_script.len(), case.2);
assert_eq!(prevout_script.is_empty(), case.2 == 0);
}
}
#[test]
#[allow(unused_must_use)]
fn it_converts_between_bitcoin_script_types() {
let si = WitnessStackItem::new(
hex::decode("0014758ce550380d964051086798d6546bebdca27a73").unwrap(),
);
let sc = Script::from(si.items());
let spk = ScriptPubkey::from(si.items());
let ss = ScriptSig::from(si.items());
WitnessStackItem::from(si.items());
WitnessStackItem::from(&sc);
WitnessStackItem::from(&spk);
WitnessStackItem::from(&ss);
Script::from(&si);
Script::from(&spk);
Script::from(&ss);
ScriptPubkey::from(&si);
ScriptPubkey::from(&sc);
ScriptPubkey::from(&ss);
ScriptSig::from(&si);
ScriptSig::from(&sc);
ScriptSig::from(&spk);
}
#[test]
fn it_determines_script_pubkey_types_accurately() {
let cases = [
(ScriptPubkey::new(hex::decode("a914e88869b88866281ab166541ad8aafba8f8aba47a87").unwrap()), ScriptType::Sh([232, 136, 105, 184, 136, 102, 40, 26, 177, 102, 84, 26, 216, 170, 251, 168, 248, 171, 164, 122].into())),
(ScriptPubkey::new(hex::decode("a914e88869b88866281ab166541ad8aafba8f8aba47a89").unwrap()), ScriptType::NonStandard), (ScriptPubkey::new(hex::decode("aa14e88869b88866281ab166541ad8aafba8f8aba47a87").unwrap()), ScriptType::NonStandard), (ScriptPubkey::new(hex::decode("76a9140e5c3c8d420c7f11e88d76f7b860d471e6517a4488ac").unwrap()), ScriptType::Pkh([14, 92, 60, 141, 66, 12, 127, 17, 232, 141, 118, 247, 184, 96, 212, 113, 230, 81, 122, 68].into())),
(ScriptPubkey::new(hex::decode("76a9140e5c3c8d420c7f11e88d76f7b860d471e6517a4488ad").unwrap()), ScriptType::NonStandard), (ScriptPubkey::new(hex::decode("77a9140e5c3c8d420c7f11e88d76f7b860d471e6517a4488ac").unwrap()), ScriptType::NonStandard), (ScriptPubkey::new(hex::decode("00201bf8a1831db5443b42a44f30a121d1b616d011ab15df62b588722a845864cc99").unwrap()), ScriptType::Wsh([27, 248, 161, 131, 29, 181, 68, 59, 66, 164, 79, 48, 161, 33, 209, 182, 22, 208, 17, 171, 21, 223, 98, 181, 136, 114, 42, 132, 88, 100, 204, 153].into())),
(ScriptPubkey::new(hex::decode("01201bf8a1831db5443b42a44f30a121d1b616d011ab15df62b588722a845864cc99").unwrap()), ScriptType::NonStandard), (ScriptPubkey::new(hex::decode("00141bf8a1831db5443b42a44f30a121d1b616d011ab").unwrap()), ScriptType::Wpkh([27, 248, 161, 131, 29, 181, 68, 59, 66, 164, 79, 48, 161, 33, 209, 182, 22, 208, 17, 171].into())),
(ScriptPubkey::new(hex::decode("01141bf8a1831db5443b42a44f30a121d1b616d011ab").unwrap()), ScriptType::NonStandard), (ScriptPubkey::new(hex::decode("0011223344").unwrap()), ScriptType::NonStandard), (ScriptPubkey::new(hex::decode("deadbeefdeadbeefdeadbeefdeadbeef").unwrap()), ScriptType::NonStandard), (ScriptPubkey::new(hex::decode("02031bf8a1831db5443b42a44f30a121d1b616d011ab15df62b588722a845864cc99041bf8a1831db5443b42a44f30a121d1b616d011ab15df62b588722a845864cc9902af").unwrap()), ScriptType::NonStandard), ];
for case in cases.iter() {
let (script, t) = case;
assert_eq!(script.standard_type(), *t);
}
}
}