Skip to content

Commit 5e4d49b

Browse files
committed
public onion utils, create/peel payment
1 parent d2242f6 commit 5e4d49b

File tree

4 files changed

+301
-7
lines changed

4 files changed

+301
-7
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

+290-2
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,180 @@ 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, best_block_height: u32, payment_hash: PaymentHash,
942+
keysend_preimage: Option<PaymentPreimage>, prng_seed: [u8; 32]
943+
) -> Result<(u64, u32, msgs::OnionPacket), ()>
944+
where
945+
T: secp256k1::Signing
946+
{
947+
let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv).map_err(|_| ())?;
948+
let (onion_payloads, htlc_msat, htlc_cltv) = build_onion_payloads(
949+
&path,
950+
total_msat,
951+
recipient_onion,
952+
best_block_height + 1,
953+
&keysend_preimage,
954+
).map_err(|_| ())?;
955+
let onion_packet = construct_onion_packet(
956+
onion_payloads, onion_keys, prng_seed, &payment_hash
957+
)?;
958+
Ok((htlc_msat, htlc_cltv, onion_packet))
959+
}
960+
961+
/// Forwarded Payment, including next hop details.
962+
pub struct ForwardedPayment {
963+
/// Short channel id of the next hop.
964+
pub short_channel_id: u64,
965+
/// The value, in msat, of the payment after this hop's fee is deducted.
966+
pub amt_to_forward: u64,
967+
/// Outgoing CLTV for the next hop.
968+
pub outgoing_cltv_value: u32,
969+
/// Onion packet for the next hop.
970+
pub onion_packet: msgs::OnionPacket,
971+
}
972+
973+
/// Received payment, of either regular or blinded type.
974+
pub enum ReceivedPayment {
975+
/// Regular (unblinded) payment.
976+
Unblinded {
977+
/// Payment_secret to authenticate sender to the receiver.
978+
payment_secret: Option<[u8; 32]>,
979+
/// The total value, in msat, of the payment as received by the ultimate recipient.
980+
total_msat: Option<u64>,
981+
/// Custom payment metadata included in the payment.
982+
payment_metadata: Option<Vec<u8>>,
983+
/// Preimage used in spontaneous payment.
984+
keysend_preimage: Option<[u8; 32]>,
985+
/// Custom TLV records included in the payment.
986+
custom_tlvs: Vec<(u64, Vec<u8>)>,
987+
/// Amount received.
988+
amt_msat: u64,
989+
/// CLTV expiration.
990+
cltv_expiry: u32,
991+
},
992+
/// Blinded payment
993+
Blinded {
994+
/// Amount received.
995+
amt_msat: u64,
996+
/// Amount received plus fees paid.
997+
total_msat: u64,
998+
/// CLTV expiration.
999+
cltv_expiry: u32,
1000+
/// Payment secret.
1001+
payment_secret: [u8; 32],
1002+
/// The maximum total CLTV that is acceptable when relaying a payment over this hop.
1003+
max_cltv_expiry: u32,
1004+
/// The minimum value, in msat, that may be accepted by the node corresponding to this hop.
1005+
htlc_minimum_msat: u64,
1006+
/// Blinding point from intro node.
1007+
intro_node_blinding_point: PublicKey,
1008+
}
1009+
}
1010+
1011+
impl core::convert::TryFrom<msgs::InboundOnionPayload> for ReceivedPayment {
1012+
type Error = ();
1013+
fn try_from(pld: msgs::InboundOnionPayload) -> Result<Self, Self::Error> {
1014+
match pld {
1015+
msgs::InboundOnionPayload::Forward {
1016+
short_channel_id: _, amt_to_forward: _, outgoing_cltv_value: _
1017+
} => {
1018+
Err(())
1019+
},
1020+
msgs::InboundOnionPayload::Receive {
1021+
payment_data, payment_metadata, keysend_preimage, custom_tlvs, amt_msat,
1022+
outgoing_cltv_value
1023+
} => {
1024+
let (payment_secret, total_msat) = match payment_data {
1025+
Some(p) => (Some(p.payment_secret.0), Some(p.total_msat)),
1026+
None => (None, None),
1027+
};
1028+
let keysend_preimage = keysend_preimage.map(|p| p.0);
1029+
Ok(Self::Unblinded {
1030+
payment_secret, total_msat, payment_metadata, keysend_preimage, custom_tlvs,
1031+
amt_msat, cltv_expiry: outgoing_cltv_value
1032+
})
1033+
},
1034+
msgs::InboundOnionPayload::BlindedReceive {
1035+
amt_msat, total_msat, outgoing_cltv_value, payment_secret, payment_constraints,
1036+
intro_node_blinding_point
1037+
} => {
1038+
let payment_secret = payment_secret.0;
1039+
let max_cltv_expiry = payment_constraints.max_cltv_expiry;
1040+
let htlc_minimum_msat = payment_constraints.htlc_minimum_msat;
1041+
Ok(Self::Blinded {
1042+
amt_msat, total_msat, cltv_expiry: outgoing_cltv_value, payment_secret,
1043+
max_cltv_expiry, htlc_minimum_msat, intro_node_blinding_point
1044+
})
1045+
}
1046+
}
1047+
}
1048+
}
1049+
1050+
/// Received and decrypted onion payment, either of type Receive (for us), or Forward.
1051+
pub enum PeeledPayment {
1052+
/// This onion payload was for us, not for forwarding to a next-hop.
1053+
Receive(ReceivedPayment),
1054+
/// This onion payload to be forwarded to next peer.
1055+
Forward(ForwardedPayment),
1056+
}
1057+
1058+
/// Unwrap one layer of an incoming HTLC, returning either or a received payment, or a another
1059+
/// onion to forward.
1060+
pub fn peel_payment_onion<NS: Deref>(
1061+
onion: &msgs::OnionPacket, payment_hash: PaymentHash, node_signer: &NS,
1062+
secp_ctx: &Secp256k1<secp256k1::All>
1063+
) -> Result<PeeledPayment, ()>
1064+
where
1065+
NS::Target: NodeSigner,
1066+
{
1067+
if onion.public_key.is_err() {
1068+
return Err(());
1069+
}
1070+
let shared_secret = node_signer
1071+
.ecdh(Recipient::Node, &onion.public_key.unwrap(), None)
1072+
.unwrap()
1073+
.secret_bytes();
1074+
1075+
let hop = decode_next_payment_hop(
1076+
shared_secret, &onion.hop_data[..], onion.hmac, payment_hash, node_signer
1077+
).map_err(|_| ())?;
1078+
let peeled = match hop {
1079+
Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => {
1080+
if let msgs::InboundOnionPayload::Forward{
1081+
short_channel_id, amt_to_forward, outgoing_cltv_value
1082+
} = next_hop_data {
1083+
1084+
let next_packet_pk = next_hop_pubkey(
1085+
secp_ctx, onion.public_key.unwrap(), &shared_secret
1086+
);
1087+
1088+
let onion_packet = msgs::OnionPacket {
1089+
version: 0,
1090+
public_key: next_packet_pk,
1091+
hop_data: new_packet_bytes,
1092+
hmac: next_hop_hmac,
1093+
};
1094+
PeeledPayment::Forward(ForwardedPayment{
1095+
short_channel_id,
1096+
amt_to_forward,
1097+
outgoing_cltv_value,
1098+
onion_packet,
1099+
})
1100+
} else {
1101+
return Err(());
1102+
}
1103+
},
1104+
Hop::Receive(inbound) => {
1105+
let payload: ReceivedPayment = inbound.try_into().map_err(|_| ())?;
1106+
PeeledPayment::Receive(payload)
1107+
},
1108+
};
1109+
Ok(peeled)
1110+
}
1111+
9381112
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> {
9391113
decode_next_hop(shared_secret, hop_data, hmac_bytes, None, read_args)
9401114
}
@@ -1019,7 +1193,8 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(shared_secret: [u8
10191193
#[cfg(test)]
10201194
mod tests {
10211195
use crate::io;
1022-
use crate::prelude::*;
1196+
use crate::ln::{PaymentPreimage, PaymentSecret};
1197+
use crate::prelude::*;
10231198
use crate::ln::PaymentHash;
10241199
use crate::ln::features::{ChannelFeatures, NodeFeatures};
10251200
use crate::routing::router::{Path, Route, RouteHop};
@@ -1235,4 +1410,117 @@ mod tests {
12351410
writer.write_all(&self.data[..])
12361411
}
12371412
}
1413+
1414+
use crate::ln::channelmanager::RecipientOnionFields;
1415+
use crate::sign::KeysManager;
1416+
use super::{create_payment_onion, peel_payment_onion};
1417+
use super::{PeeledPayment, ReceivedPayment};
1418+
1419+
fn payment_onion_args(hop_pk: PublicKey, recipient_pk: PublicKey) -> (
1420+
SecretKey, u64, u32, RecipientOnionFields, PaymentPreimage, PaymentHash, [u8; 32],
1421+
Vec<RouteHop>, u64, PaymentSecret,
1422+
) {
1423+
use core::convert::TryInto;
1424+
use super::{Sha256, Hash};
1425+
1426+
let session_priv_bytes = [42; 32];
1427+
let session_priv = SecretKey::from_slice(&session_priv_bytes).unwrap();
1428+
let total_amt_msat = 1000;
1429+
let cur_height = 1000;
1430+
let pay_secret = PaymentSecret([99; 32]);
1431+
let recipient_onion = RecipientOnionFields::secret_only(pay_secret);
1432+
let preimage_bytes = [43; 32];
1433+
let preimage = PaymentPreimage(preimage_bytes);
1434+
let rhash = Sha256::hash(&preimage_bytes).to_vec();
1435+
let rhash_bytes: [u8; 32] = rhash.try_into().unwrap();
1436+
let payment_hash = PaymentHash(rhash_bytes);
1437+
let prng_seed = [44; 32];
1438+
1439+
// make a route alice -> bob -> charlie
1440+
let hop_fee = 1;
1441+
let recipient_amount = total_amt_msat - hop_fee;
1442+
let hops = vec![
1443+
RouteHop {
1444+
pubkey: hop_pk,
1445+
fee_msat: hop_fee,
1446+
cltv_expiry_delta: 0,
1447+
short_channel_id: 1,
1448+
node_features: NodeFeatures::empty(),
1449+
channel_features: ChannelFeatures::empty(),
1450+
maybe_announced_channel: false,
1451+
},
1452+
RouteHop {
1453+
pubkey: recipient_pk,
1454+
fee_msat: recipient_amount,
1455+
cltv_expiry_delta: 0,
1456+
short_channel_id: 2,
1457+
node_features: NodeFeatures::empty(),
1458+
channel_features: ChannelFeatures::empty(),
1459+
maybe_announced_channel: false,
1460+
}
1461+
];
1462+
1463+
(session_priv, total_amt_msat, cur_height, recipient_onion, preimage, payment_hash,
1464+
prng_seed, hops, recipient_amount, pay_secret)
1465+
}
1466+
1467+
#[test]
1468+
fn create_and_peel_unblinded_payment() {
1469+
let secp_ctx = Secp256k1::new();
1470+
1471+
// let alice = make_keys_manager(&[1; 32]);
1472+
let bob = make_keys_manager(&[2; 32]);
1473+
let bob_pk = PublicKey::from_secret_key(&secp_ctx, &bob.get_node_secret_key());
1474+
let charlie = make_keys_manager(&[3; 32]);
1475+
let charlie_pk = PublicKey::from_secret_key(&secp_ctx, &charlie.get_node_secret_key());
1476+
1477+
let (session_priv, total_amt_msat, cur_height, recipient_onion, preimage, payment_hash,
1478+
prng_seed, hops, recipient_amount, pay_secret) = payment_onion_args(bob_pk, charlie_pk);
1479+
1480+
let path = Path {
1481+
hops: hops,
1482+
blinded_tail: None,
1483+
};
1484+
1485+
let (_htlc_msat, _htlc_cltv, onion) = create_payment_onion(
1486+
&secp_ctx, &path, &session_priv, total_amt_msat, recipient_onion, cur_height,
1487+
payment_hash, Some(preimage), prng_seed
1488+
).unwrap();
1489+
1490+
// bob peels to find another onion
1491+
let next_onion = match peel_payment_onion(&onion, payment_hash, &&bob, &secp_ctx).unwrap() {
1492+
PeeledPayment::Receive(_) => panic!("should not be Receive"),
1493+
PeeledPayment::Forward(forwarded) => forwarded.onion_packet,
1494+
};
1495+
1496+
// charlie peels to find a received payment
1497+
let received = match peel_payment_onion(
1498+
&next_onion, payment_hash, &&charlie, &secp_ctx
1499+
).unwrap() {
1500+
PeeledPayment::Receive(received) => received,
1501+
PeeledPayment::Forward(_) => panic!("should not be a Forward"),
1502+
};
1503+
1504+
match received {
1505+
ReceivedPayment::Unblinded{
1506+
amt_msat, keysend_preimage, payment_secret, total_msat, payment_metadata,
1507+
custom_tlvs, cltv_expiry
1508+
} => {
1509+
assert_eq!(amt_msat, recipient_amount);
1510+
assert_eq!(keysend_preimage, Some(preimage.0));
1511+
assert_eq!(payment_secret, Some(pay_secret.0));
1512+
assert_eq!(total_msat, Some(total_amt_msat));
1513+
assert_eq!(payment_metadata, None);
1514+
assert_eq!(custom_tlvs, Vec::new());
1515+
assert_eq!(cltv_expiry, cur_height + 1);
1516+
},
1517+
ReceivedPayment::Blinded { .. } => panic!("Should not be Blinded"),
1518+
}
1519+
1520+
}
1521+
1522+
fn make_keys_manager(seed: &[u8; 32]) -> KeysManager {
1523+
KeysManager::new(seed, 42, 42)
1524+
}
1525+
12381526
}

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};
31+
pub use self::messenger::{create_onion_message, peel_onion_message};
3132
#[cfg(not(c_bindings))]
3233
pub use self::messenger::{SimpleArcOnionMessenger, SimpleRefOnionMessenger};
3334
pub use self::offers::{OffersMessage, OffersMessageHandler};

0 commit comments

Comments
 (0)