use crate::error::MutinyError;
use crate::labels::LabelStorage;
use crate::logging::MutinyLogger;
use crate::onchain::OnChainWallet;
use crate::storage::MutinyStorage;
use bdk::wallet::AddressIndex;
use bip39::Mnemonic;
use bitcoin::bech32::u5;
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
use bitcoin::secp256k1::ecdsa::Signature;
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, Signing};
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey};
use bitcoin::{PackedLockTime, Script, Transaction, TxOut};
use lightning::ln::msgs::{DecodeError, UnsignedGossipMessage};
use lightning::ln::script::ShutdownScript;
use lightning::log_warn;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::sign::{
EntropySource, InMemorySigner, KeyMaterial, NodeSigner,
PhantomKeysManager as LdkPhantomKeysManager, Recipient, SignerProvider,
SpendableOutputDescriptor,
};
use lightning::util::logger::Logger;
use std::sync::Arc;
pub struct PhantomKeysManager<S: MutinyStorage> {
inner: LdkPhantomKeysManager,
wallet: Arc<OnChainWallet<S>>,
logger: Arc<MutinyLogger>,
}
impl<S: MutinyStorage> PhantomKeysManager<S> {
pub fn new(
wallet: Arc<OnChainWallet<S>>,
seed: &[u8; 32],
starting_time_secs: u64,
starting_time_nanos: u32,
cross_node_seed: &[u8; 32],
logger: Arc<MutinyLogger>,
) -> Self {
let inner = LdkPhantomKeysManager::new(
seed,
starting_time_secs,
starting_time_nanos,
cross_node_seed,
);
Self {
inner,
wallet,
logger,
}
}
pub fn spend_spendable_outputs<C: Signing>(
&self,
descriptors: &[&SpendableOutputDescriptor],
outputs: Vec<TxOut>,
feerate_sat_per_1000_weight: u32,
locktime: Option<PackedLockTime>,
secp_ctx: &Secp256k1<C>,
) -> Result<Transaction, ()> {
let address = {
let mut wallet = self.wallet.wallet.try_write().map_err(|_| ())?;
wallet.get_internal_address(AddressIndex::New).address
};
let result = self.inner.spend_spendable_outputs(
descriptors,
outputs,
address.script_pubkey(),
feerate_sat_per_1000_weight,
locktime,
secp_ctx,
);
match result {
Ok(tx) => {
if let Err(e) = self
.wallet
.storage
.set_address_labels(address, vec!["Swept Force Close".to_string()])
{
log_warn!(
self.logger,
"Failed to set address label for spendable outputs: {e}"
)
}
Ok(tx)
}
Err(e) => Err(e),
}
}
}
impl<S: MutinyStorage> EntropySource for PhantomKeysManager<S> {
fn get_secure_random_bytes(&self) -> [u8; 32] {
self.inner.get_secure_random_bytes()
}
}
impl<S: MutinyStorage> NodeSigner for PhantomKeysManager<S> {
fn get_inbound_payment_key_material(&self) -> KeyMaterial {
self.inner.get_inbound_payment_key_material()
}
fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
self.inner.get_node_id(recipient)
}
fn ecdh(
&self,
recipient: Recipient,
other_key: &PublicKey,
tweak: Option<&Scalar>,
) -> Result<SharedSecret, ()> {
self.inner.ecdh(recipient, other_key, tweak)
}
fn sign_invoice(
&self,
hrp_bytes: &[u8],
invoice_data: &[u5],
recipient: Recipient,
) -> Result<RecoverableSignature, ()> {
self.inner.sign_invoice(hrp_bytes, invoice_data, recipient)
}
fn sign_bolt12_invoice_request(
&self,
invoice_request: &UnsignedInvoiceRequest,
) -> Result<bitcoin::secp256k1::schnorr::Signature, ()> {
self.inner.sign_bolt12_invoice_request(invoice_request)
}
fn sign_bolt12_invoice(
&self,
invoice: &UnsignedBolt12Invoice,
) -> Result<bitcoin::secp256k1::schnorr::Signature, ()> {
self.inner.sign_bolt12_invoice(invoice)
}
fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result<Signature, ()> {
self.inner.sign_gossip_message(msg)
}
}
impl<S: MutinyStorage> SignerProvider for PhantomKeysManager<S> {
type Signer = InMemorySigner;
fn generate_channel_keys_id(
&self,
inbound: bool,
channel_value_satoshis: u64,
user_channel_id: u128,
) -> [u8; 32] {
self.inner
.generate_channel_keys_id(inbound, channel_value_satoshis, user_channel_id)
}
fn derive_channel_signer(
&self,
channel_value_satoshis: u64,
channel_keys_id: [u8; 32],
) -> Self::Signer {
self.inner
.derive_channel_signer(channel_value_satoshis, channel_keys_id)
}
fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, DecodeError> {
self.inner.read_chan_signer(reader)
}
fn get_destination_script(&self) -> Result<Script, ()> {
let mut wallet = self.wallet.wallet.try_write().map_err(|_| ())?;
Ok(wallet
.get_address(AddressIndex::New)
.address
.script_pubkey())
}
fn get_shutdown_scriptpubkey(&self) -> Result<ShutdownScript, ()> {
let mut wallet = self.wallet.wallet.try_write().map_err(|_| ())?;
let script = wallet
.get_address(AddressIndex::New)
.address
.script_pubkey();
ShutdownScript::try_from(script).map_err(|_| ())
}
}
pub fn generate_seed(num_words: u8) -> Result<Mnemonic, MutinyError> {
let entropy_size = match num_words {
12 => 16,
24 => 32,
_ => return Err(MutinyError::SeedGenerationFailed),
};
let mut entropy = vec![0u8; entropy_size];
getrandom::getrandom(&mut entropy).map_err(|_| MutinyError::SeedGenerationFailed)?;
let mnemonic =
Mnemonic::from_entropy(&entropy).map_err(|_| MutinyError::SeedGenerationFailed)?;
Ok(mnemonic)
}
pub(crate) fn create_keys_manager<S: MutinyStorage>(
wallet: Arc<OnChainWallet<S>>,
xprivkey: ExtendedPrivKey,
child_index: u32,
logger: Arc<MutinyLogger>,
) -> Result<PhantomKeysManager<S>, MutinyError> {
let context = Secp256k1::new();
let shared_key = xprivkey.derive_priv(
&context,
&DerivationPath::from(vec![ChildNumber::from_hardened_idx(0)?]),
)?;
let xpriv = shared_key.derive_priv(
&context,
&DerivationPath::from(vec![ChildNumber::from_hardened_idx(child_index)?]),
)?;
let now = crate::utils::now();
Ok(PhantomKeysManager::new(
wallet,
&xpriv.private_key.secret_bytes(),
now.as_secs(),
now.as_nanos() as u32,
&shared_key.private_key.secret_bytes(),
logger,
))
}
pub(crate) fn pubkey_from_keys_manager<S: MutinyStorage>(
keys_manager: &PhantomKeysManager<S>,
) -> PublicKey {
keys_manager
.get_node_id(Recipient::Node)
.expect("cannot parse node id")
}
#[cfg(test)]
mod tests {
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
wasm_bindgen_test_configure!(run_in_browser);
use crate::{
encrypt::encryption_key_from_pass, keymanager::pubkey_from_keys_manager, test_utils::*,
};
use super::create_keys_manager;
use crate::fees::MutinyFeeEstimator;
use crate::logging::MutinyLogger;
use crate::multiesplora::MultiEsploraClient;
use crate::onchain::OnChainWallet;
use crate::storage::MemoryStorage;
use bip39::Mnemonic;
use bitcoin::util::bip32::ExtendedPrivKey;
use bitcoin::Network;
use esplora_client::Builder;
use std::str::FromStr;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
#[test]
async fn derive_pubkey_child_from_seed() {
let test_name = "derive_pubkey_child_from_seed";
log!("{}", test_name);
let mnemonic = Mnemonic::from_str("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").expect("could not generate");
let esplora = Arc::new(
Builder::new("https://blockstream.info/testnet/api/")
.build_async()
.unwrap(),
);
let esplora = Arc::new(MultiEsploraClient::new(vec![esplora]));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let db = MemoryStorage::new(Some(pass), Some(cipher), None);
let logger = Arc::new(MutinyLogger::default());
let fees = Arc::new(MutinyFeeEstimator::new(
db.clone(),
esplora.clone(),
logger.clone(),
));
let stop = Arc::new(AtomicBool::new(false));
let xpriv = ExtendedPrivKey::new_master(Network::Testnet, &mnemonic.to_seed("")).unwrap();
let wallet = Arc::new(
OnChainWallet::new(
xpriv,
db,
Network::Testnet,
esplora,
fees,
stop,
logger.clone(),
)
.unwrap(),
);
let km = create_keys_manager(wallet.clone(), xpriv, 1, logger.clone()).unwrap();
let pubkey = pubkey_from_keys_manager(&km);
assert_eq!(
"02cae09cf2c8842ace44068a5bf3117a494ebbf69a99e79712483c36f97cdb7b54",
pubkey.to_string()
);
let km = create_keys_manager(wallet.clone(), xpriv, 2, logger.clone()).unwrap();
let second_pubkey = pubkey_from_keys_manager(&km);
assert_eq!(
"03fcc9eaaf0b84946ea7935e3bc4f2b498893c2f53e5d2994d6877d149601ce553",
second_pubkey.to_string()
);
let km = create_keys_manager(wallet, xpriv, 2, logger).unwrap();
let second_pubkey_again = pubkey_from_keys_manager(&km);
assert_eq!(second_pubkey, second_pubkey_again);
}
}