use std::{fmt, marker::PhantomData, str::FromStr};
use bitcoin::blockdata::script;
use expression;
use miniscript::{
self, context::ScriptContext, decode::Terminal, limits::MAX_PUBKEYS_PER_MULTISIG,
};
use policy;
use script_num_size;
use {errstr, Error, ForEach, ForEachKey, Miniscript, MiniscriptKey, Satisfier, ToPublicKey};
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SortedMultiVec<Pk: MiniscriptKey, Ctx: ScriptContext> {
pub k: usize,
pub pks: Vec<Pk>,
pub(crate) phantom: PhantomData<Ctx>,
}
impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
pub fn new(k: usize, pks: Vec<Pk>) -> Result<Self, Error> {
if pks.len() > MAX_PUBKEYS_PER_MULTISIG {
Error::BadDescriptor("Too many public keys".to_string());
}
let term: miniscript::decode::Terminal<Pk, Ctx> = Terminal::Multi(k, pks.clone());
let ms = Miniscript::from_ast(term)?;
Ctx::check_local_validity(&ms)?;
Ok(Self {
k,
pks,
phantom: PhantomData,
})
}
pub fn from_tree(tree: &expression::Tree) -> Result<Self, Error>
where
Pk: FromStr,
<Pk as FromStr>::Err: ToString,
{
if tree.args.is_empty() {
return Err(errstr("no arguments given for sortedmulti"));
}
let k = expression::parse_num(tree.args[0].name)?;
if k > (tree.args.len() - 1) as u32 {
return Err(errstr(
"higher threshold than there were keys in sortedmulti",
));
}
let pks: Result<Vec<Pk>, _> = tree.args[1..]
.iter()
.map(|sub| expression::terminal(sub, Pk::from_str))
.collect();
pks.map(|pks| SortedMultiVec::new(k as usize, pks))?
}
pub fn translate_pk<FPk, Q, FuncError>(
&self,
translatefpk: &mut FPk,
) -> Result<SortedMultiVec<Q, Ctx>, FuncError>
where
FPk: FnMut(&Pk) -> Result<Q, FuncError>,
Q: MiniscriptKey,
{
let pks: Result<Vec<Q>, _> = self.pks.iter().map(&mut *translatefpk).collect();
Ok(SortedMultiVec {
k: self.k,
pks: pks?,
phantom: PhantomData,
})
}
}
impl<Pk: MiniscriptKey, Ctx: ScriptContext> ForEachKey<Pk> for SortedMultiVec<Pk, Ctx> {
fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, mut pred: F) -> bool
where
Pk: 'a,
Pk::Hash: 'a,
{
self.pks.iter().all(|key| pred(ForEach::Key(key)))
}
}
impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
pub fn sanity_check(&self) -> Result<(), Error> {
let ms: Miniscript<Pk, Ctx> =
Miniscript::from_ast(Terminal::Multi(self.k, self.pks.clone()))
.expect("Must typecheck");
ms.sanity_check()?;
Ok(())
}
}
impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
pub fn sorted_node(&self) -> Terminal<Pk, Ctx>
where
Pk: ToPublicKey,
{
let mut pks = self.pks.clone();
pks.sort_by(|a, b| {
a.to_public_key()
.inner
.serialize()
.partial_cmp(&b.to_public_key().inner.serialize())
.unwrap()
});
Terminal::Multi(self.k, pks)
}
pub fn encode(&self) -> script::Script
where
Pk: ToPublicKey,
{
self.sorted_node()
.encode(script::Builder::new())
.into_script()
}
pub fn satisfy<S>(&self, satisfier: S) -> Result<Vec<Vec<u8>>, Error>
where
Pk: ToPublicKey,
S: Satisfier<Pk>,
{
let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck");
ms.satisfy(satisfier)
}
pub fn script_size(&self) -> usize {
script_num_size(self.k)
+ 1
+ script_num_size(self.pks.len())
+ self.pks.iter().map(|pk| Ctx::pk_len(pk)).sum::<usize>()
}
pub fn max_satisfaction_witness_elements(&self) -> usize {
2 + self.k
}
pub fn max_satisfaction_size(&self) -> usize {
1 + 73 * self.k
}
}
impl<Pk: MiniscriptKey, Ctx: ScriptContext> policy::Liftable<Pk> for SortedMultiVec<Pk, Ctx> {
fn lift(&self) -> Result<policy::semantic::Policy<Pk>, Error> {
let ret = policy::semantic::Policy::Threshold(
self.k,
self.pks
.clone()
.into_iter()
.map(|k| policy::semantic::Policy::KeyHash(k.to_pubkeyhash()))
.collect(),
);
Ok(ret)
}
}
impl<Pk: MiniscriptKey, Ctx: ScriptContext> fmt::Debug for SortedMultiVec<Pk, Ctx> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<Pk: MiniscriptKey, Ctx: ScriptContext> fmt::Display for SortedMultiVec<Pk, Ctx> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "sortedmulti({}", self.k)?;
for k in &self.pks {
write!(f, ",{}", k)?;
}
f.write_str(")")
}
}