+/// Allows `decode_next_hop` to return the next hop packet bytes for either payments or onion
+/// message forwards.
+pub(crate) trait NextPacketBytes: AsMut<[u8]> {
+ fn new(len: usize) -> Self;
+}
+
+impl NextPacketBytes for FixedSizeOnionPacket {
+ fn new(_len: usize) -> Self {
+ Self([0 as u8; ONION_DATA_LEN])
+ }
+}
+
+impl NextPacketBytes for Vec<u8> {
+ fn new(len: usize) -> Self {
+ vec![0 as u8; len]
+ }
+}
+
+/// Data decrypted from a payment's onion payload.
+pub(crate) enum Hop {
+ /// This onion payload was for us, not for forwarding to a next-hop. Contains information for
+ /// verifying the incoming payment.
+ Receive(msgs::InboundOnionPayload),
+ /// This onion payload needs to be forwarded to a next-hop.
+ Forward {
+ /// Onion payload data used in forwarding the payment.
+ next_hop_data: msgs::InboundOnionPayload,
+ /// HMAC of the next hop's onion packet.
+ next_hop_hmac: [u8; 32],
+ /// Bytes of the onion packet we're forwarding.
+ new_packet_bytes: [u8; ONION_DATA_LEN],
+ },
+}
+
+/// Error returned when we fail to decode the onion packet.
+#[derive(Debug)]
+pub(crate) enum OnionDecodeErr {
+ /// The HMAC of the onion packet did not match the hop data.
+ Malformed {
+ err_msg: &'static str,
+ err_code: u16,
+ },
+ /// We failed to decode the onion payload.
+ Relay {
+ err_msg: &'static str,
+ err_code: u16,
+ },
+}
+
+pub(crate) fn decode_next_payment_hop<NS: Deref>(
+ shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash,
+ node_signer: &NS,
+) -> Result<Hop, OnionDecodeErr> where NS::Target: NodeSigner {
+ match decode_next_hop(shared_secret, hop_data, hmac_bytes, Some(payment_hash), node_signer) {
+ Ok((next_hop_data, None)) => Ok(Hop::Receive(next_hop_data)),
+ Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => {
+ Ok(Hop::Forward {
+ next_hop_data,
+ next_hop_hmac,
+ new_packet_bytes
+ })
+ },
+ Err(e) => Err(e),
+ }
+}
+
+/// Build a payment onion, returning the first hop msat and cltv values as well.
+/// `cur_block_height` should be set to the best known block height + 1.
+pub fn create_payment_onion<T: secp256k1::Signing>(
+ secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, total_msat: u64,
+ recipient_onion: RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash,
+ keysend_preimage: &Option<PaymentPreimage>, prng_seed: [u8; 32]
+) -> Result<(msgs::OnionPacket, u64, u32), APIError> {
+ let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv)
+ .map_err(|_| APIError::InvalidRoute{
+ err: "Pubkey along hop was maliciously selected".to_owned()
+ })?;
+ let (onion_payloads, htlc_msat, htlc_cltv) = build_onion_payloads(
+ &path, total_msat, recipient_onion, cur_block_height, keysend_preimage
+ )?;
+ let onion_packet = construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash)
+ .map_err(|_| APIError::InvalidRoute{
+ err: "Route size too large considering onion data".to_owned()
+ })?;
+ Ok((onion_packet, htlc_msat, htlc_cltv))
+}
+
+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> {
+ decode_next_hop(shared_secret, hop_data, hmac_bytes, None, read_args)
+}
+
+fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: Option<PaymentHash>, read_args: T) -> Result<(R, Option<([u8; 32], N)>), OnionDecodeErr> {
+ let (rho, mu) = gen_rho_mu_from_shared_secret(&shared_secret);
+ let mut hmac = HmacEngine::<Sha256>::new(&mu);
+ hmac.input(hop_data);
+ if let Some(tag) = payment_hash {
+ hmac.input(&tag.0[..]);
+ }
+ if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &hmac_bytes) {
+ return Err(OnionDecodeErr::Malformed {
+ err_msg: "HMAC Check failed",
+ err_code: 0x8000 | 0x4000 | 5,
+ });
+ }
+
+ let mut chacha = ChaCha20::new(&rho, &[0u8; 8]);
+ let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&hop_data[..]) };
+ match R::read(&mut chacha_stream, read_args) {
+ Err(err) => {
+ let error_code = match err {
+ msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte
+ msgs::DecodeError::UnknownRequiredFeature|
+ msgs::DecodeError::InvalidValue|
+ msgs::DecodeError::ShortRead => 0x4000 | 22, // invalid_onion_payload
+ _ => 0x2000 | 2, // Should never happen
+ };
+ return Err(OnionDecodeErr::Relay {
+ err_msg: "Unable to decode our hop data",
+ err_code: error_code,
+ });
+ },
+ Ok(msg) => {
+ let mut hmac = [0; 32];
+ if let Err(_) = chacha_stream.read_exact(&mut hmac[..]) {
+ return Err(OnionDecodeErr::Relay {
+ err_msg: "Unable to decode our hop data",
+ err_code: 0x4000 | 22,
+ });
+ }
+ if hmac == [0; 32] {
+ #[cfg(test)]
+ {
+ // In tests, make sure that the initial onion packet data is, at least, non-0.
+ // We could do some fancy randomness test here, but, ehh, whatever.
+ // This checks for the issue where you can calculate the path length given the
+ // onion data as all the path entries that the originator sent will be here
+ // as-is (and were originally 0s).
+ // Of course reverse path calculation is still pretty easy given naive routing
+ // algorithms, but this fixes the most-obvious case.
+ let mut next_bytes = [0; 32];
+ chacha_stream.read_exact(&mut next_bytes).unwrap();
+ assert_ne!(next_bytes[..], [0; 32][..]);
+ chacha_stream.read_exact(&mut next_bytes).unwrap();
+ assert_ne!(next_bytes[..], [0; 32][..]);
+ }
+ return Ok((msg, None)); // We are the final destination for this packet
+ } else {
+ let mut new_packet_bytes = N::new(hop_data.len());
+ let read_pos = hop_data.len() - chacha_stream.read.position() as usize;
+ chacha_stream.read_exact(&mut new_packet_bytes.as_mut()[..read_pos]).unwrap();
+ #[cfg(debug_assertions)]
+ {
+ // Check two things:
+ // a) that the behavior of our stream here will return Ok(0) even if the TLV
+ // read above emptied out our buffer and the unwrap() wont needlessly panic
+ // b) that we didn't somehow magically end up with extra data.
+ let mut t = [0; 1];
+ debug_assert!(chacha_stream.read(&mut t).unwrap() == 0);
+ }
+ // Once we've emptied the set of bytes our peer gave us, encrypt 0 bytes until we
+ // fill the onion hop data we'll forward to our next-hop peer.
+ chacha_stream.chacha.process_in_place(&mut new_packet_bytes.as_mut()[read_pos..]);
+ return Ok((msg, Some((hmac, new_packet_bytes)))) // This packet needs forwarding
+ }
+ },
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::io;
+ use crate::prelude::*;
+ use crate::ln::PaymentHash;
+ use crate::ln::features::{ChannelFeatures, NodeFeatures};
+ use crate::routing::router::{Path, Route, RouteHop};
+ use crate::ln::msgs;
+ use crate::util::ser::{Writeable, Writer, VecWriter};
+
+ use bitcoin::hashes::hex::FromHex;
+ use bitcoin::secp256k1::Secp256k1;
+ use bitcoin::secp256k1::{PublicKey,SecretKey};