Skip to content

Commit fc04e36

Browse files
committed
public onion utils, scid_utils, and utils for creating/peeling payment onions
1 parent be8797e commit fc04e36

File tree

6 files changed

+277
-10
lines changed

6 files changed

+277
-10
lines changed

lightning/src/ln/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub(crate) mod onion_utils;
3939
mod outbound_payment;
4040
pub mod wire;
4141

42+
pub use onion_utils::{ForwardedPayment, PeeledPayment, ReceivedPayment, create_payment_onion, peel_payment_onion};
4243
// Older rustc (which we support) refuses to let us call the get_payment_preimage_hash!() macro
4344
// without the node parameter being mut. This is incorrect, and thus newer rustcs will complain
4445
// about an unnecessary mut. Thus, we silence the unused_mut warning in two test modules below.

lightning/src/ln/msgs.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -1674,17 +1674,21 @@ pub use self::fuzzy_internal_msgs::*;
16741674
#[cfg(not(fuzzing))]
16751675
pub(crate) use self::fuzzy_internal_msgs::*;
16761676

1677+
/// Bolt04 OnionPacket including hop data for the next peer
16771678
#[derive(Clone)]
1678-
pub(crate) struct OnionPacket {
1679-
pub(crate) version: u8,
1679+
pub struct OnionPacket {
1680+
/// Bolt 04 version number
1681+
pub version: u8,
16801682
/// In order to ensure we always return an error on onion decode in compliance with [BOLT
16811683
/// #4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md), we have to
16821684
/// deserialize `OnionPacket`s contained in [`UpdateAddHTLC`] messages even if the ephemeral
16831685
/// public key (here) is bogus, so we hold a [`Result`] instead of a [`PublicKey`] as we'd
16841686
/// like.
1685-
pub(crate) public_key: Result<PublicKey, secp256k1::Error>,
1686-
pub(crate) hop_data: [u8; 20*65],
1687-
pub(crate) hmac: [u8; 32],
1687+
pub public_key: Result<PublicKey, secp256k1::Error>,
1688+
/// 1300 bytes encrypted payload for the next hop
1689+
pub hop_data: [u8; 20*65],
1690+
/// HMAC to verify the integrity of hop_data
1691+
pub hmac: [u8; 32],
16881692
}
16891693

16901694
impl onion_utils::Packet for OnionPacket {

lightning/src/ln/onion_utils.rs

+254-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::ln::msgs;
1313
use crate::ln::wire::Encode;
1414
use crate::routing::gossip::NetworkUpdate;
1515
use crate::routing::router::{BlindedTail, Path, RouteHop};
16-
use crate::sign::NodeSigner;
16+
use crate::sign::{NodeSigner, Recipient};
1717
use crate::util::chacha20::{ChaCha20, ChaChaReader};
1818
use crate::util::errors::{self, APIError};
1919
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, LengthCalculatingWriter};
@@ -935,6 +935,160 @@ pub(crate) fn decode_next_payment_hop<NS: Deref>(
935935
}
936936
}
937937

938+
/// Build a payment onion, returning the first hop msat and cltv values as well.
939+
pub fn create_payment_onion<T>(
940+
secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, total_msat: u64,
941+
recipient_onion: RecipientOnionFields, starting_htlc_offset: u32, payment_hash: PaymentHash,
942+
keysend_preimage: Option<PaymentPreimage>, prng_seed: [u8; 32]) -> Result<(u64, u32, msgs::OnionPacket), ()>
943+
where
944+
T: secp256k1::Signing
945+
{
946+
let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv).map_err(|_| ())?;
947+
let (onion_payloads, htlc_msat, htlc_cltv) = build_onion_payloads(
948+
&path,
949+
total_msat,
950+
recipient_onion,
951+
starting_htlc_offset,
952+
&keysend_preimage,
953+
).map_err(|_| ())?;
954+
let onion_packet = construct_onion_packet(onion_payloads, onion_keys, prng_seed, &payment_hash)?;
955+
Ok((htlc_msat, htlc_cltv, onion_packet))
956+
}
957+
958+
/// Forwarded Payment, including next hop details
959+
#[derive(Debug)]
960+
pub struct ForwardedPayment {
961+
/// short channel id of the next hop
962+
pub short_channel_id: u64,
963+
/// The value, in msat, of the payment after this hop's fee is deducted.
964+
pub amt_to_forward: u64,
965+
/// outgoing CLTV for the next hop
966+
pub outgoing_cltv_value: u32,
967+
/// onion packet for the next hop
968+
pub onion_packet: msgs::OnionPacket,
969+
}
970+
971+
/// Received payment, of either regular or blinded type
972+
#[derive(Debug)]
973+
pub enum ReceivedPayment {
974+
/// Regular (unblinded) payment
975+
Regular {
976+
/// payment_secret to authenticate sender to the receiver
977+
payment_secret: Option<[u8; 32]>,
978+
/// The total value, in msat, of the payment as received by the ultimate recipient
979+
total_msat: Option<u64>,
980+
/// custom payment metadata included in the payment
981+
payment_metadata: Option<Vec<u8>>,
982+
/// preimage used in spontaneous payment
983+
keysend_preimage: Option<[u8; 32]>,
984+
/// custom TLV records included in the payment
985+
custom_tlvs: Vec<(u64, Vec<u8>)>,
986+
/// amount received
987+
amt_msat: u64,
988+
/// outgoing ctlv
989+
outgoing_cltv_value: u32,
990+
},
991+
/// Blinded payment
992+
Blinded {
993+
/// amount received
994+
amt_msat: u64,
995+
/// amount received plus fees paid
996+
total_msat: u64,
997+
/// outgoing cltv
998+
outgoing_cltv_value: u32,
999+
/// Payment secret
1000+
payment_secret: [u8; 32],
1001+
/// The maximum total CLTV that is acceptable when relaying a payment over this hop
1002+
max_cltv_expiry: u32,
1003+
/// The minimum value, in msat, that may be accepted by the node corresponding to this hop
1004+
htlc_minimum_msat: u64,
1005+
/// Blinding point from intro node
1006+
intro_node_blinding_point: PublicKey,
1007+
}
1008+
}
1009+
1010+
impl std::convert::TryFrom<msgs::InboundOnionPayload> for ReceivedPayment {
1011+
type Error = ();
1012+
fn try_from(pld: msgs::InboundOnionPayload) -> Result<Self, Self::Error> {
1013+
match pld {
1014+
msgs::InboundOnionPayload::Forward { short_channel_id: _, amt_to_forward: _, outgoing_cltv_value: _ } => {
1015+
Err(())
1016+
},
1017+
msgs::InboundOnionPayload::Receive { payment_data, payment_metadata, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value } => {
1018+
let (payment_secret, total_msat) = match payment_data {
1019+
Some(p) => (Some(p.payment_secret.0), Some(p.total_msat)),
1020+
None => (None, None),
1021+
};
1022+
let keysend_preimage = keysend_preimage.map(|p| p.0);
1023+
Ok(Self::Regular { payment_secret, total_msat, payment_metadata, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value })
1024+
},
1025+
msgs::InboundOnionPayload::BlindedReceive { amt_msat, total_msat, outgoing_cltv_value, payment_secret, payment_constraints, intro_node_blinding_point } => {
1026+
let payment_secret = payment_secret.0;
1027+
let max_cltv_expiry = payment_constraints.max_cltv_expiry;
1028+
let htlc_minimum_msat = payment_constraints.htlc_minimum_msat;
1029+
Ok(Self::Blinded { amt_msat, total_msat, outgoing_cltv_value, payment_secret, max_cltv_expiry, htlc_minimum_msat, intro_node_blinding_point })
1030+
}
1031+
}
1032+
}
1033+
}
1034+
1035+
/// Received and decrypted onion payment, either of type Receive (for us), or Forward.
1036+
#[derive(Debug)]
1037+
pub enum PeeledPayment {
1038+
/// This onion payload was for us, not for forwarding to a next-hop.
1039+
Receive(ReceivedPayment),
1040+
/// This onion payload to be forwarded to next peer.
1041+
Forward(ForwardedPayment),
1042+
}
1043+
1044+
/// Unwrap one layer of an incoming HTLC, returning either or a received payment, or a another
1045+
/// onion to forward.
1046+
pub fn peel_payment_onion<NS: Deref>(
1047+
onion: &msgs::OnionPacket, payment_hash: PaymentHash, node_signer: &NS,
1048+
secp_ctx: &Secp256k1<secp256k1::All>
1049+
) -> Result<PeeledPayment, ()>
1050+
where
1051+
NS::Target: NodeSigner,
1052+
{
1053+
if onion.public_key.is_err() {
1054+
return Err(());
1055+
}
1056+
let shared_secret = node_signer
1057+
.ecdh(Recipient::Node, &onion.public_key.unwrap(), None)
1058+
.unwrap()
1059+
.secret_bytes();
1060+
1061+
let hop = decode_next_payment_hop(shared_secret, &onion.hop_data[..], onion.hmac, payment_hash, node_signer).map_err(|_| ())?;
1062+
let peeled = match hop {
1063+
Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => {
1064+
if let msgs::InboundOnionPayload::Forward{short_channel_id, amt_to_forward, outgoing_cltv_value} = next_hop_data {
1065+
1066+
let next_packet_pk = next_hop_pubkey(secp_ctx, onion.public_key.unwrap(), &shared_secret);
1067+
1068+
let onion_packet = msgs::OnionPacket {
1069+
version: 0,
1070+
public_key: next_packet_pk,
1071+
hop_data: new_packet_bytes,
1072+
hmac: next_hop_hmac,
1073+
};
1074+
PeeledPayment::Forward(ForwardedPayment{
1075+
short_channel_id,
1076+
amt_to_forward,
1077+
outgoing_cltv_value,
1078+
onion_packet,
1079+
})
1080+
} else {
1081+
return Err(());
1082+
}
1083+
},
1084+
Hop::Receive(inbound) => {
1085+
let payload: ReceivedPayment = inbound.try_into().map_err(|_| ())?;
1086+
PeeledPayment::Receive(payload)
1087+
},
1088+
};
1089+
Ok(peeled)
1090+
}
1091+
9381092
pub(crate) fn decode_next_untagged_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], read_args: T) -> Result<(R, Option<([u8; 32], N)>), OnionDecodeErr> {
9391093
decode_next_hop(shared_secret, hop_data, hmac_bytes, None, read_args)
9401094
}
@@ -1235,4 +1389,103 @@ mod tests {
12351389
writer.write_all(&self.data[..])
12361390
}
12371391
}
1392+
1393+
#[test]
1394+
fn create_and_peel_payment_onion() {
1395+
use crate::ln::channelmanager::RecipientOnionFields;
1396+
use crate::ln::PaymentPreimage;
1397+
use std::convert::TryInto;
1398+
use super::{create_payment_onion, peel_payment_onion};
1399+
use super::{Sha256, Hash, PeeledPayment, ReceivedPayment};
1400+
1401+
let secp_ctx = Secp256k1::new();
1402+
let recipient_onion = RecipientOnionFields::spontaneous_empty();
1403+
let session_priv_bytes = [42; 32];
1404+
let session_priv = SecretKey::from_slice(&session_priv_bytes).unwrap();
1405+
let amt_msat = 1000;
1406+
let cur_height = 1000;
1407+
let preimage_bytes = [43; 32];
1408+
let preimage = PaymentPreimage(preimage_bytes);
1409+
let rhash = Sha256::hash(&preimage_bytes).to_vec();
1410+
let rhash_bytes: [u8; 32] = rhash.try_into().unwrap();
1411+
let payment_hash = PaymentHash(rhash_bytes);
1412+
let prng_seed = [44; 32];
1413+
1414+
// let alice = make_keys_manager(&[1; 32]);
1415+
let bob = make_keys_manager(&[2; 32]);
1416+
let bob_pk = PublicKey::from_secret_key(&secp_ctx, &bob.get_node_secret_key());
1417+
let charlie = make_keys_manager(&[3; 32]);
1418+
let charlie_pk = PublicKey::from_secret_key(&secp_ctx, &charlie.get_node_secret_key());
1419+
1420+
// make a route alice -> bob -> charlie
1421+
let bob_fee = 1;
1422+
let recipient_amount = 999;
1423+
let hops = vec![
1424+
RouteHop {
1425+
pubkey: bob_pk,
1426+
fee_msat: bob_fee,
1427+
cltv_expiry_delta: 0,
1428+
short_channel_id: 1,
1429+
node_features: NodeFeatures::empty(),
1430+
channel_features: ChannelFeatures::empty(),
1431+
maybe_announced_channel: false,
1432+
},
1433+
RouteHop {
1434+
pubkey: charlie_pk,
1435+
fee_msat: recipient_amount,
1436+
cltv_expiry_delta: 0,
1437+
short_channel_id: 2,
1438+
node_features: NodeFeatures::empty(),
1439+
channel_features: ChannelFeatures::empty(),
1440+
maybe_announced_channel: false,
1441+
}
1442+
];
1443+
let path = Path {
1444+
hops: hops,
1445+
blinded_tail: None,
1446+
};
1447+
1448+
let (_htlc_msat, _htlc_cltv, onion) = create_payment_onion(
1449+
&secp_ctx, &path, &session_priv, amt_msat, recipient_onion, cur_height, payment_hash,
1450+
Some(preimage), prng_seed
1451+
).unwrap();
1452+
1453+
// bob peels to find another onion
1454+
let next_onion = match peel_payment_onion(&onion, payment_hash, &&bob, &secp_ctx).unwrap() {
1455+
PeeledPayment::Receive(_) => {
1456+
panic!("should not be a receive");
1457+
},
1458+
PeeledPayment::Forward(forwarded) => {
1459+
forwarded.onion_packet
1460+
},
1461+
};
1462+
1463+
// charlie peels to find a received payment
1464+
match peel_payment_onion(&next_onion, payment_hash, &&charlie, &secp_ctx).unwrap() {
1465+
PeeledPayment::Receive(received) => match received {
1466+
ReceivedPayment::Regular{amt_msat, keysend_preimage, ..} => {
1467+
assert_eq!(amt_msat, recipient_amount, "wrong received amount");
1468+
assert_eq!(Some(preimage_bytes), keysend_preimage, "wrong received preimage");
1469+
},
1470+
ReceivedPayment::Blinded{..} => {
1471+
panic!("should not be blinded");
1472+
}
1473+
},
1474+
PeeledPayment::Forward(_) => {
1475+
panic!("should not be a receive");
1476+
},
1477+
};
1478+
1479+
}
1480+
1481+
fn make_keys_manager(seed: &[u8; 32]) -> crate::sign::KeysManager {
1482+
use crate::sign::KeysManager;
1483+
use std::time::{SystemTime, UNIX_EPOCH};
1484+
let start = SystemTime::now();
1485+
let now = start
1486+
.duration_since(UNIX_EPOCH)
1487+
.expect("Time went backwards");
1488+
KeysManager::new(seed, now.as_secs(), now.subsec_nanos())
1489+
}
1490+
12381491
}

lightning/src/onion_message/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ mod functional_tests;
2828

2929
// Re-export structs so they can be imported with just the `onion_message::` module prefix.
3030
pub use self::messenger::{CustomOnionMessageHandler, DefaultMessageRouter, Destination, MessageRouter, OnionMessageContents, OnionMessagePath, OnionMessenger, PeeledOnion, PendingOnionMessage, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
31+
pub use self::messenger::{create_onion_message, peel_onion_message};
3132
pub use self::offers::{OffersMessage, OffersMessageHandler};
3233
pub use self::packet::{Packet, ParsedOnionMessageContents};
3334
pub(crate) use self::packet::ControlTlvs;

lightning/src/util/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub mod ser;
2020
pub mod message_signing;
2121
pub mod invoice;
2222
pub mod persist;
23+
pub mod scid_utils;
2324
pub mod string;
2425
pub mod wakers;
2526
#[cfg(fuzzing)]
@@ -34,7 +35,6 @@ pub(crate) mod chacha20;
3435
pub(crate) mod poly1305;
3536
pub(crate) mod chacha20poly1305rfc;
3637
pub(crate) mod transaction_utils;
37-
pub(crate) mod scid_utils;
3838
pub(crate) mod time;
3939

4040
pub mod indexed_map;

lightning/src/util/scid_utils.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// You may not use this file except in accordance with one or both of these
88
// licenses.
99

10+
//! Utils for constructing and parse short chan ids
11+
1012
/// Maximum block height that can be used in a `short_channel_id`. This
1113
/// value is based on the 3-bytes available for block height.
1214
pub const MAX_SCID_BLOCK: u64 = 0x00ffffff;
@@ -22,8 +24,11 @@ pub const MAX_SCID_VOUT_INDEX: u64 = 0xffff;
2224
/// A `short_channel_id` construction error
2325
#[derive(Debug, PartialEq, Eq)]
2426
pub enum ShortChannelIdError {
27+
/// Block too high
2528
BlockOverflow,
29+
/// TxIndex too high
2630
TxIndexOverflow,
31+
/// VoutIndex too high
2732
VoutIndexOverflow,
2833
}
2934

@@ -65,7 +70,7 @@ pub fn scid_from_parts(block: u64, tx_index: u64, vout_index: u64) -> Result<u64
6570
/// 2) phantom node payments, to get an scid for the phantom node's phantom channel
6671
/// 3) payments intended to be intercepted will route using a fake scid (this is typically used so
6772
/// the forwarding node can open a JIT channel to the next hop)
68-
pub(crate) mod fake_scid {
73+
pub mod fake_scid {
6974
use bitcoin::blockdata::constants::ChainHash;
7075
use bitcoin::network::constants::Network;
7176
use crate::sign::EntropySource;
@@ -90,9 +95,12 @@ pub(crate) mod fake_scid {
9095
/// receipt, and handle the HTLC accordingly. The namespace identifier is encrypted when encoded
9196
/// into the fake scid.
9297
#[derive(Copy, Clone)]
93-
pub(crate) enum Namespace {
98+
pub enum Namespace {
99+
/// Phantom short channel ids
94100
Phantom,
101+
/// Aliases for outbound channels
95102
OutboundAlias,
103+
/// Namespace for intercepted payments
96104
Intercept
97105
}
98106

@@ -101,7 +109,7 @@ pub(crate) mod fake_scid {
101109
/// between segwit activation and the current best known height, and the tx index and output
102110
/// index are also selected from a "reasonable" range. We add this logic because it makes it
103111
/// non-obvious at a glance that the scid is fake, e.g. if it appears in invoice route hints.
104-
pub(crate) fn get_fake_scid<ES: Deref>(&self, highest_seen_blockheight: u32, chain_hash: &ChainHash, fake_scid_rand_bytes: &[u8; 32], entropy_source: &ES) -> u64
112+
pub fn get_fake_scid<ES: Deref>(&self, highest_seen_blockheight: u32, chain_hash: &ChainHash, fake_scid_rand_bytes: &[u8; 32], entropy_source: &ES) -> u64
105113
where ES::Target: EntropySource,
106114
{
107115
// Ensure we haven't created a namespace that doesn't fit into the 3 bits we've allocated for

0 commit comments

Comments
 (0)