Policy and contribution

This commit is contained in:
Alekos Filini
2020-02-15 21:27:51 +01:00
parent 7df3b4844e
commit 2a7c7d5272
12 changed files with 476 additions and 127 deletions

View File

@@ -0,0 +1,64 @@
use std::iter::FromIterator;
use crate::descriptor::Error;
const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
const CHECKSUM_CHARSET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
fn poly_mod(mut c: u64, val: u64) -> u64 {
let c0 = c >> 35;
c = ((c & 0x7ffffffff) << 5) ^ val;
if c0 & 1 > 0 {
c ^= 0xf5dee51989
};
if c0 & 2 > 0 {
c ^= 0xa9fdca3312
};
if c0 & 4 > 0 {
c ^= 0x1bab10e32d
};
if c0 & 8 > 0 {
c ^= 0x3706b1677a
};
if c0 & 16 > 0 {
c ^= 0x644d626ffd
};
c
}
pub fn get_checksum(desc: &str) -> Result<String, Error> {
let mut c = 1;
let mut cls = 0;
let mut clscount = 0;
for ch in desc.chars() {
let pos = INPUT_CHARSET
.find(ch)
.ok_or(Error::InvalidDescriptorCharacter(ch))? as u64;
c = poly_mod(c, pos & 31);
cls = cls * 3 + (pos >> 5);
clscount += 1;
if clscount == 3 {
c = poly_mod(c, cls);
cls = 0;
clscount = 0;
}
}
if clscount > 0 {
c = poly_mod(c, cls);
}
(0..8).for_each(|_| c = poly_mod(c, 0));
c ^= 1;
let mut chars = Vec::with_capacity(8);
for j in 0..8 {
chars.push(
CHECKSUM_CHARSET
.chars()
.nth(((c >> (5 * (7 - j))) & 31) as usize)
.unwrap(),
);
}
Ok(String::from_iter(chars))
}

View File

@@ -6,6 +6,14 @@ pub enum Error {
MalformedInput,
KeyParsingError(String),
InputIndexDoesntExist,
MissingPublicKey,
MissingDetails,
InvalidDescriptorCharacter(char),
CantDeriveWithMiniscript,
BIP32(bitcoin::util::bip32::Error),
Base58(bitcoin::util::base58::Error),
PK(bitcoin::util::key::Error),

View File

@@ -54,7 +54,13 @@ pub struct DescriptorExtendedKey {
impl DescriptorExtendedKey {
pub fn full_path(&self, index: u32) -> DerivationPath {
let mut final_path: Vec<ChildNumber> = self.path.clone().into();
let mut final_path: Vec<ChildNumber> = Vec::new();
if let Some(path) = &self.master_derivation {
let path_as_vec: Vec<ChildNumber> = path.clone().into();
final_path.extend_from_slice(&path_as_vec);
}
let our_path: Vec<ChildNumber> = self.path.clone().into();
final_path.extend_from_slice(&our_path);
let other_path: Vec<ChildNumber> = self.final_index.as_path(index).into();
final_path.extend_from_slice(&other_path);

View File

@@ -4,28 +4,36 @@ use std::convert::{Into, TryFrom};
use std::fmt;
use std::str::FromStr;
use bitcoin::blockdata::script::Script;
use bitcoin::hashes::{hash160, Hash};
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
use bitcoin::{PrivateKey, PublicKey};
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::{PrivateKey, PublicKey, Script};
pub use miniscript::descriptor::Descriptor;
pub use miniscript::{descriptor::Descriptor, Miniscript};
use serde::{Deserialize, Serialize};
use crate::psbt::utils::PSBTUtils;
pub mod checksum;
pub mod error;
pub mod extended_key;
pub mod policy;
pub use self::checksum::get_checksum;
pub use self::error::Error;
pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
pub use self::policy::{ExtractPolicy, Policy};
pub use self::policy::Policy;
trait MiniscriptExtractPolicy {
fn extract_policy(&self, lookup_map: &BTreeMap<String, Box<dyn Key>>) -> Option<Policy>;
}
pub trait ExtractPolicy {
fn extract_policy(&self) -> Option<Policy>;
}
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
struct DummyKey();
@@ -78,11 +86,7 @@ where
fn psbt_redeem_script(&self) -> Option<Script> {
match self {
Descriptor::ShWpkh(ref pk) => {
let addr =
bitcoin::Address::p2shwpkh(&pk.to_public_key(), bitcoin::Network::Bitcoin);
Some(addr.script_pubkey())
}
Descriptor::ShWpkh(_) => Some(self.witness_script()),
Descriptor::ShWsh(ref script) => Some(script.encode().to_v0_p2wsh()),
Descriptor::Sh(ref script) => Some(script.encode()),
_ => None,
@@ -105,6 +109,10 @@ trait Key: std::fmt::Debug {
fn xprv(&self) -> Option<ExtendedPrivKey>;
fn full_path(&self, index: u32) -> Option<DerivationPath>;
fn is_fixed(&self) -> bool;
fn has_secret(&self) -> bool {
self.xprv().is_some() || self.as_secret_key().is_some()
}
}
impl Key for PublicKey {
@@ -169,7 +177,11 @@ impl Key for PrivateKey {
impl Key for DescriptorExtendedKey {
fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint> {
Some(self.root_xpub(secp).fingerprint())
if let Some(fing) = self.master_fingerprint {
Some(fing.clone())
} else {
Some(self.root_xpub(secp).fingerprint())
}
}
fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error> {
@@ -255,6 +267,62 @@ impl ExtendedDescriptor {
})
}
pub fn derive_with_miniscript(
&self,
miniscript: Miniscript<PublicKey>,
) -> Result<DerivedDescriptor, Error> {
// TODO: make sure they are "equivalent"
match self.internal {
Descriptor::Bare(_) => Ok(Descriptor::Bare(miniscript)),
Descriptor::Sh(_) => Ok(Descriptor::Sh(miniscript)),
Descriptor::Wsh(_) => Ok(Descriptor::Wsh(miniscript)),
Descriptor::ShWsh(_) => Ok(Descriptor::ShWsh(miniscript)),
_ => Err(Error::CantDeriveWithMiniscript),
}
}
pub fn derive_from_psbt_input(
&self,
psbt: &PSBT,
input_index: usize,
) -> Result<DerivedDescriptor, Error> {
let get_pk_from_partial_sigs = || {
// here we need the public key.. since it's a single sig, there are only two
// options: we can either find it in the `partial_sigs`, or we can't. if we
// can't, it means that we can't even satisfy the input, so we can exit knowing
// that we did our best to try to find it.
psbt.inputs[input_index]
.partial_sigs
.keys()
.nth(0)
.ok_or(Error::MissingPublicKey)
};
if let Some(wit_script) = &psbt.inputs[input_index].witness_script {
self.derive_with_miniscript(Miniscript::parse(wit_script)?)
} else if let Some(p2sh_script) = &psbt.inputs[input_index].redeem_script {
if p2sh_script.is_v0_p2wpkh() {
// wrapped p2wpkh
get_pk_from_partial_sigs().map(|pk| Descriptor::ShWpkh(*pk))
} else {
self.derive_with_miniscript(Miniscript::parse(p2sh_script)?)
}
} else if let Some(utxo) = psbt.get_utxo_for(input_index) {
if utxo.script_pubkey.is_p2pkh() {
get_pk_from_partial_sigs().map(|pk| Descriptor::Pkh(*pk))
} else if utxo.script_pubkey.is_p2pk() {
get_pk_from_partial_sigs().map(|pk| Descriptor::Pk(*pk))
} else if utxo.script_pubkey.is_v0_p2wpkh() {
get_pk_from_partial_sigs().map(|pk| Descriptor::Wpkh(*pk))
} else {
// try as bare script
self.derive_with_miniscript(Miniscript::parse(&utxo.script_pubkey)?)
}
} else {
Err(Error::MissingDetails)
}
}
pub fn derive(&self, index: u32) -> Result<DerivedDescriptor, Error> {
let translatefpk = |xpub: &String| {
self.keys

View File

@@ -1,10 +1,11 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashSet};
use serde::Serialize;
use bitcoin::hashes::*;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32::Fingerprint;
use bitcoin::util::psbt;
use bitcoin::PublicKey;
use miniscript::{Descriptor, Miniscript, Terminal};
@@ -23,6 +24,7 @@ impl PKOrF {
fn from_key(k: &Box<dyn Key>) -> Self {
let secp = Secp256k1::gen_new();
let pubkey = k.as_public_key(&secp, None).unwrap();
if let Some(fing) = k.fingerprint(&secp) {
PKOrF {
fingerprint: Some(fing),
@@ -31,7 +33,7 @@ impl PKOrF {
} else {
PKOrF {
fingerprint: None,
pubkey: Some(k.as_public_key(&secp, None).unwrap()),
pubkey: Some(pubkey),
}
}
}
@@ -61,10 +63,10 @@ pub enum SatisfiableItem {
hash: hash160::Hash,
},
AbsoluteTimelock {
height: u32,
value: u32,
},
RelativeTimelock {
blocks: u32,
value: u32,
},
// Complex item
@@ -88,26 +90,107 @@ impl SatisfiableItem {
_ => true,
}
}
fn satisfy(&self, _input: &psbt::Input) -> Satisfaction {
Satisfaction::None
}
}
#[derive(Debug, Serialize)]
pub enum ItemSatisfier {
Us,
Other(Option<Fingerprint>),
Timelock(Option<u32>), // remaining blocks. TODO: time-based timelocks
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum Satisfaction {
Complete {
#[serde(skip_serializing_if = "PathRequirements::is_null")]
condition: PathRequirements,
},
Partial {
m: usize,
n: usize,
completed: HashSet<usize>,
},
None,
}
impl Satisfaction {
fn from_items_threshold(items: HashSet<usize>, threshold: usize) -> Satisfaction {
Satisfaction::Partial {
m: items.len(),
n: threshold,
completed: items,
}
}
}
impl<'a> std::ops::Add<&'a Satisfaction> for Satisfaction {
type Output = Satisfaction;
fn add(self, other: &'a Satisfaction) -> Satisfaction {
&self + other
}
}
impl<'a, 'b> std::ops::Add<&'b Satisfaction> for &'a Satisfaction {
type Output = Satisfaction;
fn add(self, other: &'b Satisfaction) -> Satisfaction {
match (self, other) {
// complete-complete
(
Satisfaction::Complete { condition: mut a },
Satisfaction::Complete { condition: b },
) => {
a.merge(&b).unwrap();
Satisfaction::Complete { condition: a }
}
// complete-<any>
(Satisfaction::Complete { condition }, _) => Satisfaction::Complete {
condition: *condition,
},
(_, Satisfaction::Complete { condition }) => Satisfaction::Complete {
condition: *condition,
},
// none-<any>
(Satisfaction::None, any) => any.clone(),
(any, Satisfaction::None) => any.clone(),
// partial-partial
(
Satisfaction::Partial {
m: _,
n: a_n,
completed: a_items,
},
Satisfaction::Partial {
m: _,
n: _,
completed: b_items,
},
) => {
let union: HashSet<_> = a_items.union(&b_items).cloned().collect();
Satisfaction::Partial {
m: union.len(),
n: *a_n,
completed: union,
}
}
}
}
}
#[derive(Debug, Serialize)]
pub struct Policy {
#[serde(flatten)]
item: SatisfiableItem,
#[serde(skip_serializing_if = "Option::is_none")]
satisfier: Option<ItemSatisfier>,
satisfaction: Satisfaction,
contribution: Satisfaction,
}
#[derive(Debug, Default)]
#[derive(Debug, Default, Eq, PartialEq, Clone, Copy, Serialize)]
pub struct PathRequirements {
#[serde(skip_serializing_if = "Option::is_none")]
pub csv: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timelock: Option<u32>,
}
@@ -126,6 +209,8 @@ impl PathRequirements {
}?;
match (self.timelock, other.timelock) {
// TODO: we could actually set the timelock to the highest of the two, but we would
// have to first check that they are both in the same "unit" (blocks vs time)
(Some(old), Some(new)) if old != new => Err(PolicyError::DifferentTimelock(old, new)),
_ => {
self.timelock = self.timelock.or(other.timelock);
@@ -154,7 +239,8 @@ impl Policy {
pub fn new(item: SatisfiableItem) -> Self {
Policy {
item,
satisfier: None,
satisfaction: Satisfaction::None,
contribution: Satisfaction::None,
}
}
@@ -162,13 +248,7 @@ impl Policy {
match (a, b) {
(None, None) => None,
(Some(x), None) | (None, Some(x)) => Some(x),
(Some(a), Some(b)) => Some(
SatisfiableItem::Thresh {
items: vec![a, b],
threshold: 2,
}
.into(),
),
(Some(a), Some(b)) => Self::make_thresh(vec![a, b], 2),
}
}
@@ -176,33 +256,49 @@ impl Policy {
match (a, b) {
(None, None) => None,
(Some(x), None) | (None, Some(x)) => Some(x),
(Some(a), Some(b)) => Some(
SatisfiableItem::Thresh {
items: vec![a, b],
threshold: 1,
}
.into(),
),
(Some(a), Some(b)) => Self::make_thresh(vec![a, b], 1),
}
}
pub fn make_thresh(items: Vec<Policy>, mut threshold: usize) -> Option<Policy> {
pub fn make_thresh(items: Vec<Policy>, threshold: usize) -> Option<Policy> {
if threshold == 0 {
return None;
}
if threshold > items.len() {
threshold = items.len();
}
Some(SatisfiableItem::Thresh { items, threshold }.into())
let contribution = items.iter().fold(
Satisfaction::Partial {
m: 0,
n: threshold,
completed: HashSet::new(),
},
|acc, x| acc + &x.contribution,
);
let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into();
policy.contribution = contribution;
Some(policy)
}
fn make_multisig(pubkeys: Vec<Option<&Box<dyn Key>>>, threshold: usize) -> Option<Policy> {
let keys = pubkeys
.into_iter()
.map(|k| PKOrF::from_key(k.unwrap()))
fn make_multisig(keys: Vec<Option<&Box<dyn Key>>>, threshold: usize) -> Option<Policy> {
let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k.unwrap())).collect();
let mut policy: Policy = SatisfiableItem::Multisig {
keys: parsed_keys,
threshold,
}
.into();
let our_keys = keys
.iter()
.enumerate()
.filter(|(_, x)| x.is_some() && x.unwrap().has_secret())
.map(|(k, _)| k)
.collect();
Some(SatisfiableItem::Multisig { keys, threshold }.into())
policy.contribution = Satisfaction::from_items_threshold(our_keys, threshold);
Some(policy)
}
pub fn satisfy(&mut self, input: &psbt::Input) {
self.satisfaction = self.item.satisfy(input);
}
pub fn requires_path(&self) -> bool {
@@ -267,12 +363,12 @@ impl Policy {
Ok(requirements)
}
_ if !selected.is_empty() => Err(PolicyError::TooManyItemsSelected(index)),
SatisfiableItem::AbsoluteTimelock { height } => Ok(PathRequirements {
SatisfiableItem::AbsoluteTimelock { value } => Ok(PathRequirements {
csv: None,
timelock: Some(*height),
timelock: Some(*value),
}),
SatisfiableItem::RelativeTimelock { blocks } => Ok(PathRequirements {
csv: Some(*blocks),
SatisfiableItem::RelativeTimelock { value } => Ok(PathRequirements {
csv: Some(*value),
timelock: None,
}),
_ => Ok(PathRequirements::default()),
@@ -286,19 +382,27 @@ impl From<SatisfiableItem> for Policy {
}
}
pub trait ExtractPolicy {
fn extract_policy(&self) -> Option<Policy>;
}
fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
key.map(|k| SatisfiableItem::Signature(PKOrF::from_key(k)).into())
key.map(|k| {
let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(k)).into();
policy.contribution = if k.has_secret() {
Satisfaction::Complete {
condition: Default::default(),
}
} else {
Satisfaction::None
};
policy
})
}
fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
let secp = Secp256k1::gen_new();
key.map(|k| {
if let Some(fing) = k.fingerprint(&secp) {
let pubkey = k.as_public_key(&secp, None).unwrap();
let mut policy: Policy = if let Some(fing) = k.fingerprint(&secp) {
SatisfiableItem::SignatureKey {
fingerprint: Some(fing),
pubkey_hash: None,
@@ -306,12 +410,19 @@ fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
} else {
SatisfiableItem::SignatureKey {
fingerprint: None,
pubkey_hash: Some(hash160::Hash::hash(
&k.as_public_key(&secp, None).unwrap().to_bytes(),
)),
pubkey_hash: Some(hash160::Hash::hash(&pubkey.to_bytes())),
}
}
.into()
.into();
policy.contribution = if k.has_secret() {
Satisfaction::Complete {
condition: Default::default(),
}
} else {
Satisfaction::None
};
policy
})
}
@@ -322,11 +433,27 @@ impl MiniscriptExtractPolicy for Miniscript<String> {
Terminal::True | Terminal::False => None,
Terminal::Pk(pubkey) => signature_from_string(lookup_map.get(pubkey)),
Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)),
Terminal::After(height) => {
Some(SatisfiableItem::AbsoluteTimelock { height: *height }.into())
Terminal::After(value) => {
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
policy.contribution = Satisfaction::Complete {
condition: PathRequirements {
csv: None,
timelock: Some(*value),
},
};
Some(policy)
}
Terminal::Older(blocks) => {
Some(SatisfiableItem::RelativeTimelock { blocks: *blocks }.into())
Terminal::Older(value) => {
let mut policy: Policy = SatisfiableItem::RelativeTimelock { value: *value }.into();
policy.contribution = Satisfaction::Complete {
condition: PathRequirements {
csv: Some(*value),
timelock: None,
},
};
Some(policy)
}
Terminal::Sha256(hash) => Some(SatisfiableItem::SHA256Preimage { hash: *hash }.into()),
Terminal::Hash256(hash) => {
@@ -338,6 +465,9 @@ impl MiniscriptExtractPolicy for Miniscript<String> {
Terminal::Hash160(hash) => {
Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into())
}
Terminal::ThreshM(k, pks) => {
Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)
}
// Identities
Terminal::Alt(inner)
| Terminal::Swap(inner)
@@ -376,9 +506,6 @@ impl MiniscriptExtractPolicy for Miniscript<String> {
Policy::make_thresh(mapped, threshold)
}
Terminal::ThreshM(k, pks) => {
Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)
}
}
}
}