Skip to content

Commit

Permalink
Compute aggregated BlindedPayInfo in path construction
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinewallace committed Aug 1, 2023
1 parent 7436da4 commit 1fa4ecd
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 6 deletions.
13 changes: 8 additions & 5 deletions lightning/src/blinded_path/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ pub(crate) mod utils;
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};

use crate::blinded_path::payment::BlindedPaymentTlvs;
use crate::sign::EntropySource;
use crate::offers::invoice::BlindedPayInfo;
use crate::ln::msgs::DecodeError;
use crate::sign::EntropySource;
use crate::util::ser::{Readable, Writeable, Writer};

use crate::io;
Expand Down Expand Up @@ -79,20 +80,22 @@ impl BlindedPath {
/// Create a blinded path for a payment, to be forwarded along `path`. The last node
/// in `path` will be the destination node.
///
/// Errors if `path` is empty or a node id in `path` is invalid.
/// Errors if `path` is empty, a node id in `path` is invalid, or [`BlindedPayInfo`] calculation
/// results in an integer overflow.
// TODO: make all payloads the same size with padding + add dummy hops
pub fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
path: &[(PublicKey, BlindedPaymentTlvs)], entropy_source: &ES, secp_ctx: &Secp256k1<T>
) -> Result<Self, ()> {
) -> Result<(BlindedPayInfo, Self), ()> {
if path.len() < 1 { return Err(()) }
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");

Ok(BlindedPath {
let blinded_payinfo = payment::compute_payinfo(path)?;
Ok((blinded_payinfo, BlindedPath {
introduction_node_id: path[0].0,
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
blinded_hops: payment::blinded_hops(secp_ctx, path, &blinding_secret).map_err(|_| ())?,
})
}))
}
}

Expand Down
130 changes: 129 additions & 1 deletion lightning/src/blinded_path/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
//! [`BlindedPath`]: crate::blinded_path::BlindedPath
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};

use core::convert::TryFrom;
use crate::blinded_path::BlindedHop;
use crate::blinded_path::utils;
use crate::io;
use crate::ln::PaymentSecret;
use crate::ln::features::BlindedHopFeatures;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice::BlindedPayInfo;
use crate::prelude::*;
use crate::util::ser::{Readable, Writeable, Writer};

Expand Down Expand Up @@ -45,6 +46,36 @@ pub enum BlindedPaymentTlvs {
},
}

impl BlindedPaymentTlvs {
// The fee used to get from the current hop to the next hop in the path.
fn fee_base_msat(&self) -> u32 {
match self {
Self::Forward { payment_relay, .. } => payment_relay.fee_base_msat,
_ => 0,
}
}
// The fee used to get from the current hop to the next hop in the path.
fn fee_proportional_millionths(&self) -> u32 {
match self {
Self::Forward { payment_relay, .. } => payment_relay.fee_proportional_millionths,
_ => 0,
}
}
// The delta used to get from the current hop to the next hop in the path.
fn cltv_expiry_delta(&self) -> u16 {
match self {
Self::Forward { payment_relay, .. } => payment_relay.cltv_expiry_delta,
_ => 0,
}
}
fn htlc_minimum_msat(&self) -> u64 {
match self {
Self::Forward { payment_constraints, .. } | Self::Receive { payment_constraints, .. } =>
payment_constraints.htlc_minimum_msat,
}
}
}

/// Parameters for relaying over a given [`BlindedHop`].
///
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
Expand Down Expand Up @@ -148,6 +179,34 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
Ok(blinded_hops)
}

pub(super) fn compute_payinfo(
path: &[(PublicKey, BlindedPaymentTlvs)]
) -> Result<BlindedPayInfo, ()> {
let mut curr_base_fee: u128 = 0;
let mut curr_prop_mil: u128 = 0;
for (_, payment_tlvs) in path.iter().rev().skip(1) {
let next_base_fee = payment_tlvs.fee_base_msat() as u128;
let next_prop_mil = payment_tlvs.fee_proportional_millionths() as u128;
curr_base_fee =
((next_base_fee * 1_000_000 + (curr_base_fee * (1_000_000 + next_prop_mil))) + 1_000_000 - 1)
/ 1_000_000;
curr_prop_mil =
(((curr_prop_mil + next_prop_mil) * 1_000_000 + curr_prop_mil * next_prop_mil) + 1_000_000 - 1)
/ 1_000_000;
}
Ok(BlindedPayInfo {
fee_base_msat: u32::try_from(curr_base_fee).map_err(|_| ())?,
fee_proportional_millionths: u32::try_from(curr_prop_mil).map_err(|_| ())?,
cltv_expiry_delta: path.iter().map(|(_, tlvs)| tlvs.cltv_expiry_delta())
.try_fold(0u16, |acc, delta| acc.checked_add(delta)).ok_or(())?,
htlc_minimum_msat: path.iter().map(|(_, tlvs)| tlvs.htlc_minimum_msat()).max().unwrap_or(0),
// TODO: this field isn't present in route blinding encrypted data
htlc_maximum_msat: 21_000_000 * 100_000_000 * 1_000, // Total bitcoin supply
// TODO: when there are blinded hop features, take the subset of them here
features: BlindedHopFeatures::empty(),
})
}

impl_writeable_msg!(PaymentRelay, {
cltv_expiry_delta,
fee_proportional_millionths,
Expand All @@ -158,3 +217,72 @@ impl_writeable_msg!(PaymentConstraints, {
max_cltv_expiry,
htlc_minimum_msat
}, {});

#[cfg(test)]
mod tests {
use bitcoin::secp256k1::PublicKey;
use crate::blinded_path::payment::{BlindedPaymentTlvs, PaymentConstraints, PaymentRelay};
use crate::ln::PaymentSecret;
use crate::ln::features::BlindedHopFeatures;

#[test]
fn compute_payinfo() {
// Taken from the spec example for aggregating blinded payment info.
let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
let path = vec![(dummy_pk, BlindedPaymentTlvs::Forward {
short_channel_id: 0,
payment_relay: PaymentRelay {
cltv_expiry_delta: 144,
fee_proportional_millionths: 500,
fee_base_msat: 100,
},
payment_constraints: PaymentConstraints {
max_cltv_expiry: 0,
htlc_minimum_msat: 100,
},
features: BlindedHopFeatures::empty(),
}), (dummy_pk, BlindedPaymentTlvs::Forward {
short_channel_id: 0,
payment_relay: PaymentRelay {
cltv_expiry_delta: 144,
fee_proportional_millionths: 500,
fee_base_msat: 100,
},
payment_constraints: PaymentConstraints {
max_cltv_expiry: 0,
htlc_minimum_msat: 1_000,
},
features: BlindedHopFeatures::empty(),
}), (dummy_pk, BlindedPaymentTlvs::Receive {
payment_secret: PaymentSecret([0; 32]),
payment_constraints: PaymentConstraints {
max_cltv_expiry: 0,
htlc_minimum_msat: 1,
},
features: BlindedHopFeatures::empty(),
})];
let blinded_payinfo = super::compute_payinfo(&path[..]).unwrap();
assert_eq!(blinded_payinfo.fee_base_msat, 201);
assert_eq!(blinded_payinfo.fee_proportional_millionths, 1001);
assert_eq!(blinded_payinfo.cltv_expiry_delta, 288);
assert_eq!(blinded_payinfo.htlc_minimum_msat, 1_000);
}

#[test]
fn compute_payinfo_1_hop() {
let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
let path = vec![(dummy_pk, BlindedPaymentTlvs::Receive {
payment_secret: PaymentSecret([0; 32]),
payment_constraints: PaymentConstraints {
max_cltv_expiry: 0,
htlc_minimum_msat: 1,
},
features: BlindedHopFeatures::empty(),
})];
let blinded_payinfo = super::compute_payinfo(&path[..]).unwrap();
assert_eq!(blinded_payinfo.fee_base_msat, 0);
assert_eq!(blinded_payinfo.fee_proportional_millionths, 0);
assert_eq!(blinded_payinfo.cltv_expiry_delta, 0);
assert_eq!(blinded_payinfo.htlc_minimum_msat, 1);
}
}

0 comments on commit 1fa4ecd

Please sign in to comment.