Skip to content

Commit d45f12d

Browse files
committed
Implement WalletKeysManager
This implementation of `KeysInterface` allows to override the shutdown/destination scripts and forwards any other calls to an inner instance of `KeysManager` otherwise.
1 parent fc5dcff commit d45f12d

File tree

1 file changed

+146
-1
lines changed

1 file changed

+146
-1
lines changed

src/wallet.rs

+146-1
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
use crate::logger::{
22
log_error, log_given_level, log_internal, log_trace, FilesystemLogger, Logger,
33
};
4+
45
use crate::Error;
56

67
use lightning::chain::chaininterface::{
78
BroadcasterInterface, ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
89
};
910

11+
use lightning::chain::keysinterface::{
12+
EntropySource, InMemorySigner, KeyMaterial, KeysInterface, KeysManager, NodeSigner, Recipient,
13+
SignerProvider, SpendableOutputDescriptor,
14+
};
15+
use lightning::ln::msgs::DecodeError;
16+
use lightning::ln::script::ShutdownScript;
17+
1018
use bdk::blockchain::{Blockchain, EsploraBlockchain};
1119
use bdk::database::BatchDatabase;
1220
use bdk::wallet::AddressIndex;
1321
use bdk::{FeeRate, SignOptions, SyncOptions};
1422

15-
use bitcoin::{Script, Transaction};
23+
use bitcoin::bech32::u5;
24+
use bitcoin::secp256k1::ecdh::SharedSecret;
25+
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
26+
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing};
27+
use bitcoin::{Script, Transaction, TxOut};
1628

1729
use std::collections::HashMap;
1830
use std::sync::{Arc, Mutex, RwLock};
@@ -212,3 +224,136 @@ fn fallback_fee_from_conf_target(confirmation_target: ConfirmationTarget) -> Fee
212224

213225
FeeRate::from_sat_per_kwu(sats_kwu as f32)
214226
}
227+
228+
/// Similar to [`KeysManager`], but overrides the destination and shutdown scripts so they are
229+
/// directly spendable by the BDK wallet.
230+
pub struct WalletKeysManager<D>
231+
where
232+
D: BatchDatabase,
233+
{
234+
inner: KeysManager,
235+
wallet: Arc<Wallet<D>>,
236+
}
237+
238+
impl<D> WalletKeysManager<D>
239+
where
240+
D: BatchDatabase,
241+
{
242+
/// Constructs a `WalletKeysManager` that
243+
///
244+
/// See [`KeysManager::new`] for more information on `seed`, `starting_time_secs`, and
245+
/// `starting_time_nanos`.
246+
pub fn new(
247+
seed: &[u8; 32], starting_time_secs: u64, starting_time_nanos: u32, wallet: Arc<Wallet<D>>,
248+
) -> Self {
249+
let inner = KeysManager::new(seed, starting_time_secs, starting_time_nanos);
250+
Self { inner, wallet }
251+
}
252+
253+
/// See [`KeysManager::spend_spendable_outputs`] for documentation on this method.
254+
pub fn spend_spendable_outputs<C: Signing>(
255+
&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>,
256+
change_destination_script: Script, feerate_sat_per_1000_weight: u32,
257+
secp_ctx: &Secp256k1<C>,
258+
) -> Result<Transaction, ()> {
259+
let only_non_static = &descriptors
260+
.iter()
261+
.filter(|desc| {
262+
if let SpendableOutputDescriptor::StaticOutput { .. } = desc {
263+
false
264+
} else {
265+
true
266+
}
267+
})
268+
.copied()
269+
.collect::<Vec<_>>();
270+
self.inner.spend_spendable_outputs(
271+
only_non_static,
272+
outputs,
273+
change_destination_script,
274+
feerate_sat_per_1000_weight,
275+
secp_ctx,
276+
)
277+
}
278+
}
279+
280+
impl<D> NodeSigner for WalletKeysManager<D>
281+
where
282+
D: BatchDatabase,
283+
{
284+
fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()> {
285+
self.inner.get_node_secret(recipient)
286+
}
287+
288+
fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
289+
self.inner.get_node_id(recipient)
290+
}
291+
292+
fn ecdh(
293+
&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>,
294+
) -> Result<SharedSecret, ()> {
295+
self.inner.ecdh(recipient, other_key, tweak)
296+
}
297+
298+
fn get_inbound_payment_key_material(&self) -> KeyMaterial {
299+
self.inner.get_inbound_payment_key_material()
300+
}
301+
302+
fn sign_invoice(
303+
&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient,
304+
) -> Result<RecoverableSignature, ()> {
305+
self.inner.sign_invoice(hrp_bytes, invoice_data, recipient)
306+
}
307+
}
308+
309+
impl<D> KeysInterface for WalletKeysManager<D> where D: BatchDatabase {}
310+
311+
impl<D> EntropySource for WalletKeysManager<D>
312+
where
313+
D: BatchDatabase,
314+
{
315+
fn get_secure_random_bytes(&self) -> [u8; 32] {
316+
self.inner.get_secure_random_bytes()
317+
}
318+
}
319+
320+
impl<D> SignerProvider for WalletKeysManager<D>
321+
where
322+
D: BatchDatabase,
323+
{
324+
type Signer = InMemorySigner;
325+
326+
fn generate_channel_keys_id(
327+
&self, inbound: bool, channel_value_satoshis: u64, user_channel_id: u128,
328+
) -> [u8; 32] {
329+
self.inner.generate_channel_keys_id(inbound, channel_value_satoshis, user_channel_id)
330+
}
331+
332+
fn derive_channel_signer(
333+
&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32],
334+
) -> Self::Signer {
335+
self.inner.derive_channel_signer(channel_value_satoshis, channel_keys_id)
336+
}
337+
338+
fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, DecodeError> {
339+
self.inner.read_chan_signer(reader)
340+
}
341+
342+
fn get_destination_script(&self) -> Script {
343+
let address =
344+
self.wallet.get_new_address().expect("Failed to retrieve new address from wallet.");
345+
address.script_pubkey()
346+
}
347+
348+
fn get_shutdown_scriptpubkey(&self) -> ShutdownScript {
349+
let address =
350+
self.wallet.get_new_address().expect("Failed to retrieve new address from wallet.");
351+
match address.payload {
352+
bitcoin::util::address::Payload::WitnessProgram { version, program } => {
353+
return ShutdownScript::new_witness_program(version, &program)
354+
.expect("Invalid shutdown script.");
355+
}
356+
_ => panic!("Tried to use a non-witness address. This must not ever happen."),
357+
}
358+
}
359+
}

0 commit comments

Comments
 (0)