Merge commit 'refs/pull/224/head' of github.com:bitcoindevkit/bdk

This commit is contained in:
Alekos Filini 2020-12-14 11:18:51 +01:00
commit 0ef0b45745
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
2 changed files with 146 additions and 17 deletions

View File

@ -966,6 +966,7 @@ mod test {
// 1 prv and 1 pub key descriptor, required 1 prv keys
#[test]
#[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
fn test_extract_policy_for_sh_multi_complete_1of2() {
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
@ -1058,6 +1059,7 @@ mod test {
// single key, 1 prv and 1 pub key descriptor, required 1 prv keys
#[test]
#[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
fn test_extract_policy_for_single_wsh_multi_complete_1of2() {
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
@ -1088,6 +1090,7 @@ mod test {
// test ExtractPolicy trait with descriptors containing timelocks in a thresh()
#[test]
#[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
fn test_extract_policy_for_wsh_multi_timelock() {
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR);
let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR);

View File

@ -91,9 +91,8 @@
//! ```
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::fmt;
use std::ops::Bound::Included;
use std::sync::Arc;
use bitcoin::blockdata::opcodes;
@ -296,7 +295,7 @@ impl Signer for PrivateKey {
/// The default value is `100`. Signers with an ordering above that will be called later,
/// and they will thus see the partial signatures added to the transaction once they get to sign
/// themselves.
#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
#[derive(Debug, Clone, Hash, PartialOrd, PartialEq, Ord, Eq)]
pub struct SignerOrdering(pub usize);
impl std::default::Default for SignerOrdering {
@ -305,7 +304,7 @@ impl std::default::Default for SignerOrdering {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct SignersContainerKey {
id: SignerId,
ordering: SignerOrdering,
@ -322,7 +321,7 @@ impl From<(SignerId, SignerOrdering)> for SignersContainerKey {
/// Container for multiple signers
#[derive(Debug, Default, Clone)]
pub struct SignersContainer(BTreeMap<SignersContainerKey, Arc<dyn Signer>>);
pub struct SignersContainer(HashMap<SignersContainerKey, Arc<dyn Signer>>);
impl SignersContainer {
pub fn as_key_map(&self, secp: &SecpCtx) -> KeyMap {
@ -370,7 +369,7 @@ impl SignersContainer {
}
/// Adds an external signer to the container for the specified id. Optionally returns the
/// signer that was previosuly in the container, if any
/// signer that was previously in the container, if any
pub fn add_external(
&mut self,
id: SignerId,
@ -395,18 +394,18 @@ impl SignersContainer {
/// Returns the list of signers in the container, sorted by lowest to highest `ordering`
pub fn signers(&self) -> Vec<&Arc<dyn Signer>> {
self.0.values().collect()
let mut items = self.0.iter().collect::<Vec<_>>();
items.sort_unstable_by_key(|(key, _)| *key);
items.into_iter().map(|(_, v)| v).collect()
}
/// Finds the signer with lowest ordering for a given id in the container.
pub fn find(&self, id: SignerId) -> Option<&Arc<dyn Signer>> {
self.0
.range((
Included(&(id.clone(), SignerOrdering(0)).into()),
Included(&(id, SignerOrdering(usize::MAX)).into()),
))
.iter()
.filter(|(key, _)| key.id == id)
.min_by(|(k_a, _), (k_b, _)| k_a.cmp(k_b))
.map(|(_, v)| v)
.next()
}
}
@ -526,10 +525,137 @@ impl Ord for SignersContainerKey {
}
}
impl PartialEq for SignersContainerKey {
fn eq(&self, other: &Self) -> bool {
self.ordering == other.ordering
#[cfg(test)]
mod signers_container_tests {
use super::*;
use crate::descriptor;
use crate::descriptor::ToWalletDescriptor;
use crate::keys::{DescriptorKey, ToDescriptorKey};
use bitcoin::secp256k1::All;
use bitcoin::util::bip32;
use bitcoin::util::psbt::PartiallySignedTransaction;
use bitcoin::Network;
use miniscript::ScriptContext;
use std::str::FromStr;
// Signers added with the same ordering (like `Ordering::default`) created from `KeyMap`
// should be preserved and not overwritten.
// This happens usually when a set of signers is created from a descriptor with private keys.
#[test]
fn signers_with_same_ordering() {
let (prvkey1, _, _) = setup_keys(TPRV0_STR);
let (prvkey2, _, _) = setup_keys(TPRV1_STR);
let desc = descriptor!(sh(multi 2, prvkey1, prvkey2)).unwrap();
let (_, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
let signers = SignersContainer::from(keymap);
assert_eq!(signers.ids().len(), 2);
let signers = signers.signers();
assert_eq!(signers.len(), 2);
}
#[test]
fn signers_sorted_by_ordering() {
let mut signers = SignersContainer::new();
let signer1 = Arc::new(DummySigner);
let signer2 = Arc::new(DummySigner);
let signer3 = Arc::new(DummySigner);
signers.add_external(
SignerId::Fingerprint(b"cafe"[..].into()),
SignerOrdering(1),
signer1.clone(),
);
signers.add_external(
SignerId::Fingerprint(b"babe"[..].into()),
SignerOrdering(2),
signer2.clone(),
);
signers.add_external(
SignerId::Fingerprint(b"feed"[..].into()),
SignerOrdering(3),
signer3.clone(),
);
// Check that signers are sorted from lowest to highest ordering
let signers = signers.signers();
assert_eq!(Arc::as_ptr(signers[0]), Arc::as_ptr(&signer1));
assert_eq!(Arc::as_ptr(signers[1]), Arc::as_ptr(&signer2));
assert_eq!(Arc::as_ptr(signers[2]), Arc::as_ptr(&signer3));
}
#[test]
fn find_signer_by_id() {
let mut signers = SignersContainer::new();
let signer1: Arc<dyn Signer> = Arc::new(DummySigner);
let signer2: Arc<dyn Signer> = Arc::new(DummySigner);
let signer3: Arc<dyn Signer> = Arc::new(DummySigner);
let signer4: Arc<dyn Signer> = Arc::new(DummySigner);
let id1 = SignerId::Fingerprint(b"cafe"[..].into());
let id2 = SignerId::Fingerprint(b"babe"[..].into());
let id3 = SignerId::Fingerprint(b"feed"[..].into());
let id_nonexistent = SignerId::Fingerprint(b"fefe"[..].into());
signers.add_external(id1.clone(), SignerOrdering(1), signer1.clone());
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
assert!(
matches!(signers.find(id1), Some(signer) if Arc::as_ptr(&signer1) == Arc::as_ptr(signer))
);
assert!(
matches!(signers.find(id2), Some(signer) if Arc::as_ptr(&signer2) == Arc::as_ptr(signer))
);
assert!(
matches!(signers.find(id3.clone()), Some(signer) if Arc::as_ptr(&signer3) == Arc::as_ptr(signer))
);
// The `signer4` has the same ID as `signer3` but lower ordering.
// It should be found by `id3` instead of `signer3`.
signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
assert!(
matches!(signers.find(id3), Some(signer) if Arc::as_ptr(&signer4) == Arc::as_ptr(signer))
);
// Can't find anything with ID that doesn't exist
assert!(matches!(signers.find(id_nonexistent), None));
}
#[derive(Debug)]
struct DummySigner;
impl Signer for DummySigner {
fn sign(
&self,
_psbt: &mut PartiallySignedTransaction,
_input_index: Option<usize>,
_secp: &SecpCtx,
) -> Result<(), SignerError> {
Ok(())
}
fn sign_whole_tx(&self) -> bool {
true
}
}
const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf";
const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N";
const PATH: &str = "m/44'/1'/0'/0";
fn setup_keys<Ctx: ScriptContext>(
tprv: &str,
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
let secp: Secp256k1<All> = Secp256k1::new();
let path = bip32::DerivationPath::from_str(PATH).unwrap();
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
let tpub = bip32::ExtendedPubKey::from_private(&secp, &tprv);
let fingerprint = tprv.fingerprint(&secp);
let prvkey = (tprv, path.clone()).to_descriptor_key().unwrap();
let pubkey = (tpub, path).to_descriptor_key().unwrap();
(prvkey, pubkey, fingerprint)
}
}
impl Eq for SignersContainerKey {}