use amplify::Wrapper;
use bitcoin::blockdata::script;
use bitcoin::blockdata::witness::Witness;
use bitcoin::{secp256k1, Script};
#[cfg(feature = "miniscript")]
use miniscript::descriptor::DescriptorType;
#[cfg(feature = "miniscript")]
use miniscript::{Descriptor, MiniscriptKey, ToPublicKey};
use crate::{LockScript, PubkeyScript, RedeemScript, ScriptSet, SigScript, WitnessScript};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Display, Hash)]
#[repr(u8)]
pub enum ConvertInfo {
#[display("bare")]
Bare,
#[display("hashed")]
Hashed,
#[display("nested")]
NestedV0,
#[display("segwit")]
SegWitV0,
#[display("taproot")]
Taproot,
}
#[cfg(feature = "miniscript")]
impl<Pk> From<&Descriptor<Pk>> for ConvertInfo
where
Pk: MiniscriptKey + ToPublicKey,
{
fn from(descriptor: &Descriptor<Pk>) -> Self {
match (descriptor.desc_type(), descriptor) {
(DescriptorType::Bare, _) => ConvertInfo::Bare,
(DescriptorType::Sh, _)
| (DescriptorType::ShSortedMulti, _)
| (DescriptorType::Pkh, _) => ConvertInfo::Hashed,
(DescriptorType::Wpkh, _)
| (DescriptorType::WshSortedMulti, _)
| (DescriptorType::Wsh, _) => ConvertInfo::SegWitV0,
(DescriptorType::ShWsh, _)
| (DescriptorType::ShWpkh, _)
| (DescriptorType::ShWshSortedMulti, _) => ConvertInfo::NestedV0,
(_, Descriptor::Tr(_)) => ConvertInfo::Taproot,
_ => unreachable!("taproot descriptor type for non-taproot descriptor"),
}
}
}
#[cfg(feature = "miniscript")]
impl<Pk> From<Descriptor<Pk>> for ConvertInfo
where
Pk: MiniscriptKey + ToPublicKey,
{
#[inline]
fn from(descriptor: Descriptor<Pk>) -> Self { Self::from(&descriptor) }
}
impl ConvertInfo {
#[inline]
pub fn is_segwit(self) -> bool { !matches!(self, ConvertInfo::Bare | ConvertInfo::Hashed) }
#[inline]
pub fn is_taproot(self) -> bool { !matches!(self, ConvertInfo::Taproot { .. }) }
}
#[derive(
Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error
)]
#[display(doc_comments)]
pub enum LockScriptError {
UncompressedPubkeyInWitness(bitcoin::PublicKey),
Taproot,
}
pub trait ToLockScript {
fn to_lock_script(&self, strategy: ConvertInfo) -> Result<LockScript, LockScriptError>;
}
pub trait ToPubkeyScript {
fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript>;
}
pub trait ToScripts
where
Self: ToPubkeyScript,
{
fn to_scripts(&self, strategy: ConvertInfo) -> Option<ScriptSet> {
Some(ScriptSet {
pubkey_script: self.to_pubkey_script(strategy)?,
sig_script: self.to_sig_script(strategy)?,
witness: self.to_witness(strategy),
})
}
fn to_sig_script(&self, strategy: ConvertInfo) -> Option<SigScript>;
fn to_witness(&self, strategy: ConvertInfo) -> Option<Witness>;
}
impl ToPubkeyScript for WitnessScript {
fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript> {
match strategy {
ConvertInfo::Bare => None,
ConvertInfo::Hashed => None,
ConvertInfo::NestedV0 => Some(RedeemScript::from(self.clone()).to_p2sh()),
ConvertInfo::SegWitV0 => Some(Script::new_v0_p2wsh(&self.script_hash()).into()),
ConvertInfo::Taproot => None,
}
}
}
impl ToPubkeyScript for RedeemScript {
fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript> {
match strategy {
ConvertInfo::Bare => None,
ConvertInfo::Hashed => Some(self.to_p2sh()),
ConvertInfo::NestedV0 => Some(self.to_p2sh()),
ConvertInfo::SegWitV0 => None,
ConvertInfo::Taproot => None,
}
}
}
impl ToPubkeyScript for LockScript {
fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript> {
Some(match strategy {
ConvertInfo::Bare => self.to_inner().into(),
ConvertInfo::Hashed => Script::new_p2sh(&self.script_hash()).into(),
ConvertInfo::SegWitV0 => Script::new_v0_p2wsh(&self.wscript_hash()).into(),
ConvertInfo::NestedV0 => WitnessScript::from(self.clone()).to_p2sh_wsh(),
ConvertInfo::Taproot => return None,
})
}
}
impl ToScripts for LockScript {
fn to_sig_script(&self, strategy: ConvertInfo) -> Option<SigScript> {
Some(match strategy {
ConvertInfo::Bare => SigScript::default(),
ConvertInfo::Hashed => script::Builder::new()
.push_slice(WitnessScript::from(self.clone()).as_bytes())
.into_script()
.into(),
ConvertInfo::NestedV0 => {
RedeemScript::from(WitnessScript::from(self.clone())).into()
}
_ => SigScript::default(),
})
}
fn to_witness(&self, strategy: ConvertInfo) -> Option<Witness> {
match strategy {
ConvertInfo::Bare | ConvertInfo::Hashed => None,
ConvertInfo::SegWitV0 | ConvertInfo::NestedV0 => {
let witness_script = WitnessScript::from(self.clone());
Some(Witness::from_vec(vec![witness_script.to_bytes()]))
}
ConvertInfo::Taproot => None,
}
}
}
impl ToPubkeyScript for bitcoin::PublicKey {
fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript> {
match strategy {
ConvertInfo::Bare => Some(Script::new_p2pk(self).into()),
ConvertInfo::Hashed => Some(Script::new_p2pkh(&self.pubkey_hash()).into()),
ConvertInfo::NestedV0 | ConvertInfo::SegWitV0 if !self.compressed => None,
ConvertInfo::NestedV0 | ConvertInfo::SegWitV0 => self.inner.to_pubkey_script(strategy),
ConvertInfo::Taproot => None,
}
}
}
impl ToScripts for bitcoin::PublicKey {
fn to_sig_script(&self, strategy: ConvertInfo) -> Option<SigScript> {
Some(match strategy {
ConvertInfo::Bare => SigScript::default(),
ConvertInfo::Hashed => script::Builder::new()
.push_slice(&self.to_bytes())
.into_script()
.into(),
ConvertInfo::NestedV0 => {
let redeem_script =
LockScript::from(self.to_pubkey_script(ConvertInfo::SegWitV0)?.into_inner());
script::Builder::new()
.push_slice(redeem_script.as_bytes())
.into_script()
.into()
}
_ => SigScript::default(),
})
}
fn to_witness(&self, strategy: ConvertInfo) -> Option<Witness> {
match strategy {
ConvertInfo::Bare | ConvertInfo::Hashed => None,
ConvertInfo::SegWitV0 | ConvertInfo::NestedV0 => {
Some(Witness::from_vec(vec![self.to_bytes()]))
}
ConvertInfo::Taproot => None,
}
}
}
impl ToPubkeyScript for secp256k1::PublicKey {
fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript> {
let pk = bitcoin::PublicKey::new(*self);
Some(
match strategy {
ConvertInfo::Bare => Script::new_p2pk(&pk),
ConvertInfo::Hashed => Script::new_p2pkh(&pk.pubkey_hash()),
ConvertInfo::SegWitV0 => Script::new_v0_p2wpkh(&pk.wpubkey_hash()?),
ConvertInfo::NestedV0 => {
let pubkey_script = Script::new_p2pkh(&pk.pubkey_hash());
let redeem_script = RedeemScript::from_inner(pubkey_script);
Script::new_p2sh(&redeem_script.script_hash())
}
ConvertInfo::Taproot => return None,
}
.into(),
)
}
}
impl ToScripts for secp256k1::PublicKey {
#[inline]
fn to_sig_script(&self, strategy: ConvertInfo) -> Option<SigScript> {
bitcoin::PublicKey::new(*self).to_sig_script(strategy)
}
#[inline]
fn to_witness(&self, strategy: ConvertInfo) -> Option<Witness> {
bitcoin::PublicKey::new(*self).to_witness(strategy)
}
}
pub trait ToP2pkh {
fn to_p2pkh(&self) -> Option<PubkeyScript>;
fn to_p2wpkh(&self) -> Option<PubkeyScript>;
fn to_p2sh_wpkh(&self) -> Option<PubkeyScript>;
}
#[cfg(feature = "miniscript")]
impl<T> ToP2pkh for T
where
T: ToPublicKey,
{
fn to_p2pkh(&self) -> Option<PubkeyScript> {
self.to_public_key().to_pubkey_script(ConvertInfo::Hashed)
}
fn to_p2wpkh(&self) -> Option<PubkeyScript> {
self.to_public_key().to_pubkey_script(ConvertInfo::SegWitV0)
}
fn to_p2sh_wpkh(&self) -> Option<PubkeyScript> {
self.to_public_key().to_pubkey_script(ConvertInfo::NestedV0)
}
}