use bitcoin::bip32;
use super::key_expression::KeyExpression;
use super::{KeyInfo, WalletPolicyError};
use crate::descriptor::Wildcard;
use crate::{BTreeSet, Descriptor, ToString, Vec};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WalletPolicy {
template: Descriptor<KeyExpression>,
key_info: Vec<KeyInfo>,
}
impl WalletPolicy {
pub(super) fn from_template(
template: Descriptor<KeyExpression>,
) -> Result<Self, WalletPolicyError> {
check_policy(&template, &[])?;
Ok(Self { template, key_info: vec![] })
}
pub(super) fn as_template(&self) -> &Descriptor<KeyExpression> { &self.template }
pub(super) fn into_template(self) -> Descriptor<KeyExpression> { self.template }
pub fn key_info(&self) -> &[KeyInfo] { &self.key_info }
pub fn set_key_info(&mut self, keys: Vec<KeyInfo>) -> Result<(), WalletPolicyError> {
if keys.is_empty() {
return Err(WalletPolicyError::WalletPolicyInvalidKeyInfo);
}
check_policy(&self.template, &keys)?;
self.key_info = keys;
Ok(())
}
}
fn check_keys_distinct(key_info: &[KeyInfo]) -> Result<(), WalletPolicyError> {
let mut seen = BTreeSet::new();
for key in key_info {
if !seen.insert(key.xkey.public_key) {
return Err(WalletPolicyError::KeyInfoDuplicateKey(key.to_string()));
}
}
Ok(())
}
fn check_placeholder_deriv(key: &KeyExpression) -> Result<(), WalletPolicyError> {
let paths = key.derivation_paths.paths();
let step = |p: &bip32::DerivationPath| match p.as_ref() {
[cn] if cn.is_normal() => Some(*cn),
_ => None,
};
if key.wildcard == Wildcard::Unhardened
&& paths.len() == 2
&& matches!((step(&paths[0]), step(&paths[1])), (Some(a), Some(b)) if a != b)
{
Ok(())
} else {
Err(WalletPolicyError::TemplateValidationInvalidPlaceholderDeriv)
}
}
fn check_policy(
template: &Descriptor<KeyExpression>,
key_info: &[KeyInfo],
) -> Result<(), WalletPolicyError> {
if matches!(template, Descriptor::Bare(_)) {
return Err(WalletPolicyError::TemplateValidationBareTopLevel);
}
let mut used: Vec<BTreeSet<_>> = vec![];
for key in template.iter_pk() {
check_placeholder_deriv(&key)?;
let paths = key.derivation_paths.paths();
let paths: BTreeSet<_> = paths.iter().flatten().copied().collect();
let i = key.index.0 as usize;
if i == used.len() {
used.push(paths);
} else if let Some(prev) = used.get_mut(i) {
if !prev.is_disjoint(&paths) {
return Err(WalletPolicyError::TemplateValidationNonDisjointPaths);
}
prev.extend(paths);
} else {
return Err(WalletPolicyError::TemplateValidationKeyIndexOutOfOrder);
}
}
if used.is_empty() {
return Err(WalletPolicyError::TemplateValidationNoKeyPlaceholder);
}
if !key_info.is_empty() && key_info.len() != used.len() {
return Err(WalletPolicyError::WalletPolicyInvalidKeyInfo);
}
check_keys_distinct(key_info)
}