Skip to content

Commit f08874f

Browse files
feat: add KeyRing type
1 parent 680e66a commit f08874f

File tree

3 files changed

+163
-0
lines changed

3 files changed

+163
-0
lines changed

wallet/src/keyring/changeset.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use crate::keyring::BTreeMap;
2+
3+
use bitcoin::Network;
4+
use chain::Merge;
5+
use miniscript::{Descriptor, DescriptorPublicKey};
6+
use serde::{Deserialize, Serialize};
7+
8+
/// Represents changes to the `KeyRing`.
9+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10+
pub struct ChangeSet<K: Ord> {
11+
/// Network.
12+
pub network: Option<Network>,
13+
/// Added descriptors.
14+
pub descriptors: BTreeMap<K, Descriptor<DescriptorPublicKey>>,
15+
/// Default keychain
16+
pub default_keychain: Option<K>,
17+
}
18+
19+
impl<K: Ord> Default for ChangeSet<K> {
20+
fn default() -> Self {
21+
Self {
22+
network: None,
23+
descriptors: Default::default(),
24+
default_keychain: None,
25+
}
26+
}
27+
}
28+
29+
impl<K: Ord> Merge for ChangeSet<K> {
30+
fn merge(&mut self, other: Self) {
31+
// merge network
32+
if other.network.is_some() && self.network.is_none() {
33+
self.network = other.network;
34+
}
35+
// merge descriptors
36+
self.descriptors.extend(other.descriptors);
37+
38+
// Note: if a new default keychain has been set, it will take precedence over the old one.
39+
if other.default_keychain.is_some() {
40+
self.default_keychain = other.default_keychain;
41+
}
42+
}
43+
44+
fn is_empty(&self) -> bool {
45+
self.network.is_none() && self.descriptors.is_empty()
46+
}
47+
}

wallet/src/keyring/mod.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Bitcoin Dev Kit
2+
// Written in 2020 by Alekos Filini <[email protected]>
3+
//
4+
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
5+
//
6+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
7+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
9+
// You may not use this file except in accordance with one or both of these
10+
// licenses.
11+
12+
//! The KeyRing is a utility type used to streamline the building of wallets that handle any number
13+
//! of descriptors. It ensures descriptors are usable together, consistent with a given network,
14+
//! and will work with a BDK `Wallet`.
15+
16+
mod changeset;
17+
18+
use crate::descriptor::IntoWalletDescriptor;
19+
use crate::keyring::changeset::ChangeSet;
20+
use bitcoin::secp256k1::{All, Secp256k1};
21+
use bitcoin::Network;
22+
use miniscript::{Descriptor, DescriptorPublicKey};
23+
use std::collections::BTreeMap;
24+
25+
/// KeyRing.
26+
#[derive(Debug, Clone)]
27+
pub struct KeyRing<K> {
28+
pub(crate) secp: Secp256k1<All>,
29+
pub(crate) network: Network,
30+
pub(crate) descriptors: BTreeMap<K, Descriptor<DescriptorPublicKey>>,
31+
pub(crate) default_keychain: K,
32+
}
33+
34+
impl<K> KeyRing<K>
35+
where
36+
K: Ord + Clone,
37+
{
38+
/// Construct a new [`KeyRing`] with the provided `network` and a descriptor. This descriptor
39+
/// will automatically become your default keychain. You can change your default keychain
40+
/// upon adding new ones with [`KeyRing::add_descriptor`]. Note that you cannot use a
41+
/// multipath descriptor here.
42+
pub fn new(network: Network, keychain: K, descriptor: impl IntoWalletDescriptor) -> Self {
43+
let secp = Secp256k1::new();
44+
let descriptor = descriptor
45+
.into_wallet_descriptor(&secp, network)
46+
.expect("err: invalid descriptor")
47+
.0;
48+
assert!(
49+
!descriptor.is_multipath(),
50+
"err: Use `add_multipath_descriptor` instead"
51+
);
52+
Self {
53+
secp: Secp256k1::new(),
54+
network,
55+
descriptors: BTreeMap::from([(keychain.clone(), descriptor)]),
56+
default_keychain: keychain.clone(),
57+
}
58+
}
59+
60+
/// Add a descriptor, must not be [multipath](miniscript::Descriptor::is_multipath).
61+
pub fn add_descriptor(
62+
&mut self,
63+
keychain: K,
64+
descriptor: impl IntoWalletDescriptor,
65+
default: bool,
66+
) {
67+
let descriptor = descriptor
68+
.into_wallet_descriptor(&self.secp, self.network)
69+
.expect("err: invalid descriptor")
70+
.0;
71+
assert!(
72+
!descriptor.is_multipath(),
73+
"err: Use `add_multipath_descriptor` instead"
74+
);
75+
76+
if default {
77+
self.default_keychain = keychain.clone();
78+
}
79+
self.descriptors.insert(keychain, descriptor);
80+
}
81+
82+
/// Returns the specified default keychain on the KeyRing.
83+
pub fn default_keychain(&self) -> K {
84+
self.default_keychain.clone()
85+
}
86+
87+
/// Change the default keychain on this `KeyRing`.
88+
pub fn set_default_keychain(&mut self, keychain: K) {
89+
self.default_keychain = keychain;
90+
}
91+
92+
/// Return all keychain identifiers `K`.
93+
pub fn list_keychains(&self) -> &BTreeMap<K, Descriptor<DescriptorPublicKey>> {
94+
&self.descriptors
95+
}
96+
97+
/// Initial changeset.
98+
pub fn initial_changeset(&self) -> ChangeSet<K> {
99+
ChangeSet {
100+
network: Some(self.network),
101+
descriptors: self.descriptors.clone(),
102+
default_keychain: Some(self.default_keychain.clone()),
103+
}
104+
}
105+
106+
/// Construct from changeset.
107+
pub fn from_changeset(changeset: ChangeSet<K>) -> Option<Self> {
108+
Some(Self {
109+
secp: Secp256k1::new(),
110+
network: changeset.network?,
111+
descriptors: changeset.descriptors,
112+
default_keychain: changeset.default_keychain?,
113+
})
114+
}
115+
}

wallet/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub extern crate serde;
2727
pub extern crate serde_json;
2828

2929
pub mod descriptor;
30+
pub mod keyring;
3031
pub mod keys;
3132
pub mod psbt;
3233
#[cfg(feature = "test-utils")]

0 commit comments

Comments
 (0)