Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::str::FromStr;

pub(crate) fn parse_startup_args() -> Result<LdkUserInfo, ()> {
if env::args().len() < 3 {
println!("ldk-tutorial-node requires at least 2 arguments: `cargo run [<bitcoind-rpc-username>:<bitcoind-rpc-password>@]<bitcoind-rpc-host>:<bitcoind-rpc-port> ldk_storage_directory_path [<ldk-incoming-peer-listening-port>] [bitcoin-network] [announced-node-name announced-listen-addr*]`");
println!("ldk-tutorial-node requires at least 2 arguments: `cargo run [<bitcoind-rpc-username>:<bitcoind-rpc-password>@]<bitcoind-rpc-host>:<bitcoind-rpc-port> ldk_storage_directory_path [<ldk-incoming-peer-listening-port>] [bitcoin-network] [channel-secrets] [announced-node-name announced-listen-addr*]`");
return Err(());
}
let bitcoind_rpc_info = env::args().skip(1).next().unwrap();
Expand Down Expand Up @@ -69,6 +69,17 @@ pub(crate) fn parse_startup_args() -> Result<LdkUserInfo, ()> {
return Err(());
};

let channel_secrets = match env::args().skip(arg_idx + 1).next() {
Some(s) => {
if s.len() != 389 {
panic!("Channel secrets should be seperated by '/' and total string length should be 389");
}
arg_idx += 1;
Some(s)
},
None => None,
};

let ldk_announced_node_name = match env::args().skip(arg_idx + 1).next().as_ref() {
Some(s) => {
if s.len() > 32 {
Expand Down Expand Up @@ -106,6 +117,7 @@ pub(crate) fn parse_startup_args() -> Result<LdkUserInfo, ()> {
ldk_announced_listen_addr,
ldk_announced_node_name,
network,
channel_secrets,
})
}

Expand Down
55 changes: 52 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::disk::{self, INBOUND_PAYMENTS_FNAME, OUTBOUND_PAYMENTS_FNAME};
use crate::hex_utils;
use crate::keys::MyKeysManager;
use crate::{
ChainMonitor, ChannelManager, HTLCStatus, InboundPaymentInfoStorage, MillisatAmount,
NetworkGraph, OutboundPaymentInfoStorage, PaymentInfo, PeerManager,
Expand All @@ -18,7 +19,7 @@ use lightning::ln::types::ChannelId;
use lightning::offers::offer::{self, Offer};
use lightning::routing::gossip::NodeId;
use lightning::routing::router::{PaymentParameters, RouteParameters};
use lightning::sign::{EntropySource, KeysManager};
use lightning::sign::EntropySource;
use lightning::types::payment::{PaymentHash, PaymentPreimage};
use lightning::util::config::{ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig};
use lightning::util::persist::KVStore;
Expand All @@ -43,11 +44,12 @@ pub(crate) struct LdkUserInfo {
pub(crate) ldk_announced_listen_addr: Vec<SocketAddress>,
pub(crate) ldk_announced_node_name: [u8; 32],
pub(crate) network: Network,
pub(crate) channel_secrets: Option<String>,
}

pub(crate) fn poll_for_user_input(
peer_manager: Arc<PeerManager>, channel_manager: Arc<ChannelManager>,
chain_monitor: Arc<ChainMonitor>, keys_manager: Arc<KeysManager>,
chain_monitor: Arc<ChainMonitor>, keys_manager: Arc<MyKeysManager>,
network_graph: Arc<NetworkGraph>, inbound_payments: Arc<Mutex<InboundPaymentInfoStorage>>,
outbound_payments: Arc<Mutex<OutboundPaymentInfoStorage>>, ldk_data_dir: String,
network: Network, logger: Arc<disk::FilesystemLogger>, fs_store: Arc<FilesystemStore>,
Expand All @@ -74,6 +76,53 @@ pub(crate) fn poll_for_user_input(
if let Some(word) = words.next() {
match word {
"help" => help(),
"openchannel_without_peer_addr" => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that we need a new cli command like openchannel_without_peer_addr, but probably it is required just to allow a less restrictive parser in the ldk-sample by assuming that if the address is not specified the peer is already connected (or exist inside the ldk gossip map already)

What do you think about it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that should be better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, lets definitely just update the existing openchannel.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding this, openchannel command should be followed by either pubkey@host:port or pubkey.

  • If it is just pubkey,
    • if that peer is already connected, open the fund channel
    • otherwise return error
  • if it is pubkey@host:port, proceed with the old behavior

Does this sound good?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, sounds great!

// opens a channel with a connected node
let peer_pubkey = words.next();
let channel_value_sat = words.next();
if peer_pubkey.is_none() || channel_value_sat.is_none() {
println!("ERROR: openchannel_without_peer_addr has 2 required arguments: `openchannel_without_peer_addr pubkey channel_amt_satoshis` [--public]");
continue;
}
let peer_pubkey = peer_pubkey.unwrap();
let chan_amt_sat: Result<u64, _> = channel_value_sat.unwrap().parse();
if chan_amt_sat.is_err() {
println!("ERROR: channel amount must be a number");
continue;
}

let (mut announce_channel, mut with_anchors) = (false, false);
while let Some(word) = words.next() {
match word {
"--public" | "--public=true" => announce_channel = true,
"--public=false" => announce_channel = false,
"--with-anchors" | "--with-anchors=true" => with_anchors = true,
"--with-anchors=false" => with_anchors = false,
_ => {
println!("ERROR: invalid boolean flag format. Valid formats: `--option`, `--option=true` `--option=false`");
continue;
},
}
}
let pubkey = peer_pubkey;
let pubkey = hex_utils::to_compressed_pubkey(pubkey);
if open_channel(
pubkey.unwrap(),
chan_amt_sat.unwrap(),
announce_channel,
with_anchors,
channel_manager.clone(),
)
.is_ok()
{
// let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone());
// let _ = disk::persist_channel_peer(
// Path::new(&peer_data_path),
// peer_pubkey_and_ip_addr,
// );
}
},

"openchannel" => {
let peer_pubkey_and_ip_addr = words.next();
let channel_value_sat = words.next();
Expand Down Expand Up @@ -848,7 +897,7 @@ fn keysend<E: EntropySource>(

fn get_invoice(
amt_msat: u64, inbound_payments: &mut InboundPaymentInfoStorage,
channel_manager: &ChannelManager, keys_manager: Arc<KeysManager>, network: Network,
channel_manager: &ChannelManager, keys_manager: Arc<MyKeysManager>, network: Network,
expiry_secs: u32, logger: Arc<disk::FilesystemLogger>,
) {
let currency = match network {
Expand Down
206 changes: 206 additions & 0 deletions src/keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
use std::sync::Arc;

use bitcoin::secp256k1::{Secp256k1, SecretKey};
use lightning::sign::{InMemorySigner, NodeSigner, OutputSpender, SignerProvider};

use lightning::sign::{EntropySource, KeysManager};
use lightning_invoice::RawBolt11Invoice;

pub struct MyKeys {
pub keys_manager: Arc<MyKeysManager>,
}

impl MyKeys {
pub fn new(seed: [u8; 32], starting_time_secs: u64, starting_time_nanos: u32) -> Self {
MyKeys {
keys_manager: Arc::new(MyKeysManager::new(
&seed,
starting_time_secs,
starting_time_nanos,
)),
}
}

pub fn with_channel_keys(
seed: [u8; 32], starting_time_secs: u64, starting_time_nanos: u32, channels_keys: String,
) -> Self {
let keys = channels_keys.split('/').collect::<Vec<_>>();

let mut manager = MyKeysManager::new(&seed, starting_time_secs, starting_time_nanos);
manager.set_channel_keys(
keys[0].to_string(),
keys[1].to_string(),
keys[2].to_string(),
keys[3].to_string(),
keys[4].to_string(),
keys[5].to_string(),
);
MyKeys { keys_manager: Arc::new(manager) }
}

pub fn inner(&self) -> Arc<MyKeysManager> {
self.keys_manager.clone()
}
}

/// MyKeysManager is a custom keysmanager that allows the use of custom channel secrets.
pub struct MyKeysManager {
pub(crate) inner: KeysManager,

funding_key: Option<SecretKey>,
revocation_base_secret: Option<SecretKey>,
payment_base_secret: Option<SecretKey>,
delayed_payment_base_secret: Option<SecretKey>,
htlc_base_secret: Option<SecretKey>,
shachain_seed: Option<[u8; 32]>,
}

impl MyKeysManager {
pub fn new(seed: &[u8; 32], starting_time_secs: u64, starting_time_nanos: u32) -> Self {
let inner = KeysManager::new(seed, starting_time_secs, starting_time_nanos);
Self {
inner,
funding_key: None,
revocation_base_secret: None,
payment_base_secret: None,
delayed_payment_base_secret: None,
htlc_base_secret: None,
shachain_seed: None,
}
}

pub fn get_node_secret_key(&self) -> SecretKey {
self.inner.get_node_secret_key()
}

pub fn set_channel_keys(
&mut self, funding_key: String, revocation_base_secret: String,
payment_base_secret: String, delayed_payment_base_secret: String, htlc_base_secret: String,
_shachain_seed: String,
) {
use std::str::FromStr;

self.funding_key = Some(SecretKey::from_str(&funding_key).unwrap());
self.revocation_base_secret = Some(SecretKey::from_str(&revocation_base_secret).unwrap());
self.payment_base_secret = Some(SecretKey::from_str(&payment_base_secret).unwrap());
self.delayed_payment_base_secret =
Some(SecretKey::from_str(&delayed_payment_base_secret).unwrap());
self.htlc_base_secret = Some(SecretKey::from_str(&htlc_base_secret).unwrap());
self.shachain_seed = Some(self.inner.get_secure_random_bytes())
}
}

impl EntropySource for MyKeysManager {
fn get_secure_random_bytes(&self) -> [u8; 32] {
self.inner.get_secure_random_bytes()
}
}

impl NodeSigner for MyKeysManager {
fn ecdh(
&self, recipient: lightning::sign::Recipient, other_key: &bitcoin::secp256k1::PublicKey,
tweak: Option<&bitcoin::secp256k1::Scalar>,
) -> Result<bitcoin::secp256k1::ecdh::SharedSecret, ()> {
self.inner.ecdh(recipient, other_key, tweak)
}

fn get_inbound_payment_key_material(&self) -> lightning::sign::KeyMaterial {
self.inner.get_inbound_payment_key_material()
}

fn get_node_id(
&self, recipient: lightning::sign::Recipient,
) -> Result<bitcoin::secp256k1::PublicKey, ()> {
self.inner.get_node_id(recipient)
}

fn sign_bolt12_invoice(
&self, invoice: &lightning::offers::invoice::UnsignedBolt12Invoice,
) -> Result<bitcoin::secp256k1::schnorr::Signature, ()> {
self.inner.sign_bolt12_invoice(invoice)
}

fn sign_bolt12_invoice_request(
&self, invoice_request: &lightning::offers::invoice_request::UnsignedInvoiceRequest,
) -> Result<bitcoin::secp256k1::schnorr::Signature, ()> {
self.inner.sign_bolt12_invoice_request(invoice_request)
}

fn sign_gossip_message(
&self, msg: lightning::ln::msgs::UnsignedGossipMessage,
) -> Result<bitcoin::secp256k1::ecdsa::Signature, ()> {
self.inner.sign_gossip_message(msg)
}

fn sign_invoice(
&self, invoice: &RawBolt11Invoice, recipient: lightning::sign::Recipient,
) -> Result<bitcoin::secp256k1::ecdsa::RecoverableSignature, ()> {
self.inner.sign_invoice(invoice, recipient)
}
}

impl OutputSpender for MyKeysManager {
fn spend_spendable_outputs<C: bitcoin::secp256k1::Signing>(
&self, descriptors: &[&lightning::sign::SpendableOutputDescriptor],
outputs: Vec<bitcoin::TxOut>, change_destination_script: bitcoin::ScriptBuf,
feerate_sat_per_1000_weight: u32, locktime: Option<bitcoin::absolute::LockTime>,
secp_ctx: &bitcoin::secp256k1::Secp256k1<C>,
) -> Result<bitcoin::Transaction, ()> {
self.inner.spend_spendable_outputs(
descriptors,
outputs,
change_destination_script,
feerate_sat_per_1000_weight,
locktime,
secp_ctx,
)
}
}

impl SignerProvider for MyKeysManager {
type EcdsaSigner = InMemorySigner;

fn derive_channel_signer(
&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32],
) -> Self::EcdsaSigner {
if self.funding_key.is_some() {
let commitment_seed = [
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
];
return InMemorySigner::new(
&Secp256k1::new(),
self.funding_key.unwrap(),
self.revocation_base_secret.unwrap(),
self.payment_base_secret.unwrap(),
self.delayed_payment_base_secret.unwrap(),
self.htlc_base_secret.unwrap(),
commitment_seed,
channel_value_satoshis,
channel_keys_id,
self.shachain_seed.unwrap(),
);
}
self.inner.derive_channel_signer(channel_value_satoshis, channel_keys_id)
}

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 get_destination_script(&self, channel_keys_id: [u8; 32]) -> Result<bitcoin::ScriptBuf, ()> {
self.inner.get_destination_script(channel_keys_id)
}

fn get_shutdown_scriptpubkey(&self) -> Result<lightning::ln::script::ShutdownScript, ()> {
self.inner.get_shutdown_scriptpubkey()
}

fn read_chan_signer(
&self, reader: &[u8],
) -> Result<Self::EcdsaSigner, lightning::ln::msgs::DecodeError> {
self.inner.read_chan_signer(reader)
}
}
Loading
Loading