use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use amplify::Wrapper;
use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::script::Builder;
use bitcoin::secp256k1::{Secp256k1, Verification};
use bitcoin::Script;
use bitcoin_hd::account::DerivePublicKey;
use bitcoin_hd::{DerivePatternError, UnhardenedIndex};
use miniscript::MiniscriptKey;
#[cfg(feature = "serde")]
use serde_with::{hex::Hex, As, DisplayFromStr};
use strict_encoding::{StrictDecode, StrictEncode};
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename = "lowercase")
)]
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug, Hash, Display)]
#[derive(StrictEncode, StrictDecode)]
pub enum OpcodeTemplate<Pk>
where
Pk: MiniscriptKey + StrictEncode + StrictDecode + FromStr,
<Pk as FromStr>::Err: Display,
{
#[display("opcode({0})")]
OpCode(u8),
#[display("data({0:#x?})")]
Data(#[cfg_attr(feature = "serde", serde(with = "As::<Hex>"))] Box<[u8]>),
#[display("key({0})")]
Key(#[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))] Pk),
}
impl<Pk> OpcodeTemplate<Pk>
where
Pk: MiniscriptKey + DerivePublicKey + StrictEncode + StrictDecode + FromStr,
<Pk as FromStr>::Err: Display,
{
fn translate_pk<C: Verification>(
&self,
ctx: &Secp256k1<C>,
pat: impl IntoIterator<Item = impl Into<UnhardenedIndex>>,
) -> Result<OpcodeTemplate<bitcoin::PublicKey>, DerivePatternError> {
Ok(match self {
OpcodeTemplate::OpCode(code) => OpcodeTemplate::OpCode(*code),
OpcodeTemplate::Data(data) => OpcodeTemplate::Data(data.clone()),
OpcodeTemplate::Key(key) => {
OpcodeTemplate::Key(bitcoin::PublicKey::new(key.derive_public_key(ctx, pat)?))
}
})
}
}
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
#[derive(Wrapper, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
#[derive(StrictEncode, StrictDecode)]
#[wrap(Index, IndexMut, IndexFull, IndexFrom, IndexTo, IndexInclusive)]
pub struct ScriptTemplate<Pk>(Vec<OpcodeTemplate<Pk>>)
where
Pk: MiniscriptKey + StrictEncode + StrictDecode + FromStr,
<Pk as FromStr>::Err: Display;
impl<Pk> Display for ScriptTemplate<Pk>
where
Pk: MiniscriptKey + StrictEncode + StrictDecode + FromStr,
<Pk as FromStr>::Err: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
for instruction in &self.0 {
Display::fmt(instruction, f)?;
}
Ok(())
}
}
impl<Pk> ScriptTemplate<Pk>
where
Pk: MiniscriptKey + DerivePublicKey + StrictEncode + StrictDecode + FromStr,
<Pk as FromStr>::Err: Display,
{
pub fn translate_pk<C: Verification>(
&self,
ctx: &Secp256k1<C>,
pat: impl AsRef<[UnhardenedIndex]>,
) -> Result<ScriptTemplate<bitcoin::PublicKey>, DerivePatternError> {
let pat = pat.as_ref();
Ok(self
.0
.iter()
.map(|op| op.translate_pk(ctx, pat))
.collect::<Result<Vec<_>, _>>()?
.into())
}
}
impl From<ScriptTemplate<bitcoin::PublicKey>> for Script {
fn from(template: ScriptTemplate<bitcoin::PublicKey>) -> Self {
let mut builder = Builder::new();
for op in template.into_inner() {
builder = match op {
OpcodeTemplate::OpCode(code) => builder.push_opcode(opcodes::All::from(code)),
OpcodeTemplate::Data(data) => builder.push_slice(&data),
OpcodeTemplate::Key(key) => builder.push_key(&key),
};
}
builder.into_script()
}
}