Skip to content

Commit

Permalink
Decrypt Trampoline onions
Browse files Browse the repository at this point in the history
In this commit, we start decrypting the inner onion we receive, and lay
some groundwork towards handling the subsequent routing or payment
receipt.
  • Loading branch information
arik-so committed Feb 27, 2025
1 parent 47e555f commit d22e0fe
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 8 deletions.
49 changes: 48 additions & 1 deletion lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4479,12 +4479,59 @@ where
Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
}
},
#[cfg(trampoline)]
onion_utils::Hop::TrampolineReceive { .. } => {
// OUR PAYMENT!
let current_height: u32 = self.best_block.read().unwrap().height;
match create_recv_pending_htlc_info(decoded_hop, shared_secret, msg.payment_hash,
msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat,
current_height)
{
Ok(info) => {
// Note that we could obviously respond immediately with an update_fulfill_htlc
// message, however that would leak that we are the recipient of this payment, so
// instead we stay symmetric with the forwarding case, only responding (after a
// delay) once they've sent us a commitment_signed!
PendingHTLCStatus::Forward(info)
},
Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
}
},
onion_utils::Hop::Forward { next_hop_hmac, new_packet_bytes, .. } | onion_utils::Hop::BlindedForward { next_hop_hmac, new_packet_bytes, .. } => {
match create_fwd_pending_htlc_info(msg, decoded_hop, next_hop_hmac,
new_packet_bytes, shared_secret, next_packet_pubkey_opt) {
Ok(info) => PendingHTLCStatus::Forward(info),
Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data)
}
},
#[cfg(trampoline)]
onion_utils::Hop::TrampolineForward { next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => {
debug_assert!(next_packet_pubkey_opt.is_some());
let next_trampoline_packet_pubkey = match next_packet_pubkey_opt {
Some(Ok(pubkey)) => pubkey,
_ => return_err!("Missing next Trampoline hop pubkey from intermediate Trampoline forwarding data ", 0x4000 | 22, Vec::new()),
};
let outgoing_packet = msgs::TrampolineOnionPacket {
version: 0,
public_key: next_trampoline_packet_pubkey,
hop_data: new_trampoline_packet_bytes,
hmac: next_trampoline_hop_hmac,
};
PendingHTLCStatus::Forward(PendingHTLCInfo {
routing: PendingHTLCRouting::TrampolineForward {
incoming_shared_secret: trampoline_shared_secret.secret_bytes(),
onion_packet: outgoing_packet,
node_id: next_trampoline_hop_data.outgoing_node_id,
incoming_cltv_expiry: msg.cltv_expiry,
blinded: None
},
payment_hash: msg.payment_hash,
incoming_shared_secret: shared_secret,
incoming_amt_msat: Some(msg.amount_msat),
outgoing_amt_msat: next_trampoline_hop_data.amt_to_forward,
outgoing_cltv_value: next_trampoline_hop_data.outgoing_cltv_value,
skimmed_fee_msat: None,
})
}
}
}
Expand Down Expand Up @@ -5896,7 +5943,7 @@ where
// of the onion.
failed_payment!(err_msg, err_code, sha256_of_onion.to_vec(), None);
},
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret }) => {
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret, .. }) => {
let phantom_shared_secret = shared_secret.secret_bytes();
failed_payment!(err_msg, err_code, Vec::new(), Some(phantom_shared_secret));
},
Expand Down
40 changes: 35 additions & 5 deletions lightning/src/ln/onion_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ pub(super) fn create_fwd_pending_htlc_info(
err_code: 0x4000 | 22,
err_data: Vec::new(),
}),
#[cfg(trampoline)]
onion_utils::Hop::TrampolineReceive { .. } =>
return Err(InboundHTLCErr {
msg: "Final Node OnionHopData provided for us as an intermediary node",
err_code: 0x4000 | 22,
err_data: Vec::new(),
}),
#[cfg(trampoline)]
onion_utils::Hop::TrampolineForward { .. } => panic!("Trampoline forwards are being handled outside of create_fwd_pending_htlc_info")
};

Ok(PendingHTLCInfo {
Expand Down Expand Up @@ -165,6 +174,8 @@ pub(super) fn create_recv_pending_htlc_info(
sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context),
intro_node_blinding_point.is_none(), true, invoice_request)
}
#[cfg(trampoline)]
onion_utils::Hop::TrampolineReceive { .. } => todo!(),
onion_utils::Hop::Forward { .. } => {
return Err(InboundHTLCErr {
err_code: 0x4000|22,
Expand All @@ -179,6 +190,14 @@ pub(super) fn create_recv_pending_htlc_info(
msg: "Got blinded non final data with an HMAC of 0",
})
},
#[cfg(trampoline)]
onion_utils::Hop::TrampolineForward { .. } => {
return Err(InboundHTLCErr {
err_code: 0x4000|22,
err_data: Vec::new(),
msg: "Got Trampoline non final data with an HMAC of 0",
})
},
};
// final_incorrect_cltv_expiry
if onion_cltv_expiry > cltv_expiry {
Expand Down Expand Up @@ -392,7 +411,7 @@ where
return_malformed_err!("Unknown onion packet version", 0x8000 | 0x4000 | 4);
}

let encode_relay_error = |message: &str, err_code: u16, shared_secret: [u8; 32], data: &[u8]| {
let encode_relay_error = |message: &str, err_code: u16, shared_secret: [u8; 32], trampoline_shared_secret: Option<[u8; 32]>, data: &[u8]| {
if msg.blinding_point.is_some() {
return_malformed_err!(message, INVALID_ONION_BLINDING)
}
Expand All @@ -402,7 +421,7 @@ where
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
reason: HTLCFailReason::reason(err_code, data.to_vec())
.get_encrypted_failure_packet(&shared_secret, &None),
.get_encrypted_failure_packet(&shared_secret, &trampoline_shared_secret),
}));
};

Expand All @@ -414,8 +433,8 @@ where
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
return_malformed_err!(err_msg, err_code);
},
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret }) => {
return encode_relay_error(err_msg, err_code, shared_secret.secret_bytes(), &[0; 0]);
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret, trampoline_shared_secret }) => {
return encode_relay_error(err_msg, err_code, shared_secret.secret_bytes(), trampoline_shared_secret.map(|tss| tss.secret_bytes()), &[0; 0]);
},
};

Expand All @@ -435,7 +454,7 @@ where
Ok((amt, cltv)) => (amt, cltv),
Err(()) => {
return encode_relay_error("Underflow calculating outbound amount or cltv value for blinded forward",
INVALID_ONION_BLINDING, shared_secret.secret_bytes(), &[0; 32]);
INVALID_ONION_BLINDING, shared_secret.secret_bytes(), None, &[0; 32]);
}
};
let next_packet_pubkey = onion_utils::next_hop_pubkey(&secp_ctx,
Expand All @@ -445,6 +464,17 @@ where
outgoing_cltv_value
})
}
#[cfg(trampoline)]
onion_utils::Hop::TrampolineForward { next_trampoline_hop_data: msgs::InboundTrampolineForwardPayload { amt_to_forward, outgoing_cltv_value, outgoing_node_id }, trampoline_shared_secret, incoming_trampoline_public_key, .. } => {
let next_trampoline_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx,
incoming_trampoline_public_key, &trampoline_shared_secret.secret_bytes());
Some(NextPacketDetails {
next_packet_pubkey: next_trampoline_packet_pubkey,
outgoing_connector: HopConnector::Trampoline(outgoing_node_id),
outgoing_amt_msat: amt_to_forward,
outgoing_cltv_value,
})
}
_ => None
};

Expand Down
112 changes: 110 additions & 2 deletions lightning/src/ln/onion_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1431,6 +1431,19 @@ pub(crate) enum Hop {
/// Bytes of the onion packet we're forwarding.
new_packet_bytes: [u8; ONION_DATA_LEN],
},
/// This onion was received via Trampoline, and needs to be forwarded to a subsequent Trampoline
/// node.
#[cfg(trampoline)]
TrampolineForward {
#[allow(unused)]
outer_hop_data: msgs::InboundTrampolineEntrypointPayload,
outer_shared_secret: SharedSecret,
incoming_trampoline_public_key: PublicKey,
trampoline_shared_secret: SharedSecret,
next_trampoline_hop_data: msgs::InboundTrampolineForwardPayload,
next_trampoline_hop_hmac: [u8; 32],
new_trampoline_packet_bytes: Vec<u8>,
},
/// This onion payload needs to be forwarded to a next-hop.
BlindedForward {
/// Onion payload data used in forwarding the payment.
Expand Down Expand Up @@ -1458,6 +1471,16 @@ pub(crate) enum Hop {
/// Shared secret that was used to decrypt hop_data.
shared_secret: SharedSecret,
},
/// This onion payload was for us, not for forwarding to a next-hop, and it was sent to us via
/// Trampoline. Contains information for verifying the incoming payment.
#[allow(unused)]
#[cfg(trampoline)]
TrampolineReceive {
outer_hop_data: msgs::InboundTrampolineEntrypointPayload,
outer_shared_secret: SharedSecret,
trampoline_hop_data: msgs::InboundOnionReceivePayload,
trampoline_shared_secret: SharedSecret,
},
}

impl Hop {
Expand All @@ -1478,8 +1501,12 @@ impl Hop {
match self {
Hop::Forward { shared_secret, .. } => shared_secret,
Hop::BlindedForward { shared_secret, .. } => shared_secret,
#[cfg(trampoline)]
Hop::TrampolineForward { outer_shared_secret, .. } => outer_shared_secret,
Hop::Receive { shared_secret, .. } => shared_secret,
Hop::BlindedReceive { shared_secret, .. } => shared_secret,
#[cfg(trampoline)]
Hop::TrampolineReceive { outer_shared_secret, .. } => outer_shared_secret,
}
}
}
Expand All @@ -1490,7 +1517,15 @@ 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, shared_secret: SharedSecret },
///
/// If the payload we failed to decode belonged to a Trampoline onion, following the successful
/// decoding of the outer onion, the trampoline_shared_secret field should be set.
Relay {
err_msg: &'static str,
err_code: u16,
shared_secret: SharedSecret,
trampoline_shared_secret: Option<SharedSecret>,
},
}

pub(crate) fn decode_next_payment_hop<NS: Deref>(
Expand All @@ -1514,7 +1549,7 @@ where
hop_data,
hmac_bytes,
Some(payment_hash),
(blinding_point, node_signer),
(blinding_point, &(*node_signer)),
);
match decoded_hop {
Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => {
Expand Down Expand Up @@ -1545,6 +1580,7 @@ where
err_msg: "Final Node OnionHopData provided for us as an intermediary node",
err_code: 0x4000 | 22,
shared_secret,
trampoline_shared_secret: None,
})
},
}
Expand All @@ -1556,6 +1592,75 @@ where
msgs::InboundOnionPayload::BlindedReceive(hop_data) => {
Ok(Hop::BlindedReceive { shared_secret, hop_data })
},
#[cfg(trampoline)]
msgs::InboundOnionPayload::TrampolineEntrypoint(hop_data) => {
let incoming_trampoline_public_key = hop_data.trampoline_packet.public_key;
let trampoline_shared_secret = node_signer
.ecdh(
recipient,
&incoming_trampoline_public_key,
blinded_node_id_tweak.as_ref(),
)
.unwrap()
.secret_bytes();
let decoded_trampoline_hop: Result<
(msgs::InboundOnionPayload, Option<([u8; 32], Vec<u8>)>),
_,
> = decode_next_hop(
trampoline_shared_secret,
&hop_data.trampoline_packet.hop_data,
hop_data.trampoline_packet.hmac,
Some(payment_hash),
(blinding_point, node_signer),
);
match decoded_trampoline_hop {
Ok((
next_trampoline_hop_data,
Some((next_trampoline_hop_hmac, new_trampoline_packet_bytes)),
)) => {
match next_trampoline_hop_data {
msgs::InboundOnionPayload::TrampolineForward(trampoline_hop_data) => {
Ok(Hop::TrampolineForward {
outer_hop_data: hop_data,
outer_shared_secret: shared_secret,
incoming_trampoline_public_key,
trampoline_shared_secret: SharedSecret::from_bytes(
trampoline_shared_secret,
),
next_trampoline_hop_data: trampoline_hop_data,
next_trampoline_hop_hmac,
new_trampoline_packet_bytes,
})
},
_ => Err(OnionDecodeErr::Malformed {
err_msg: "Non-Trampoline onion data provided to us as inner onion",
// todo: find more suitable error code
err_code: 0x4000 | 22,
}),
}
},
Ok((trampoline_hop_data, None)) => {
match trampoline_hop_data {
msgs::InboundOnionPayload::Receive(trampoline_hop_data) => {
Ok(Hop::TrampolineReceive {
outer_hop_data: hop_data,
outer_shared_secret: shared_secret,
trampoline_hop_data,
trampoline_shared_secret: SharedSecret::from_bytes(
trampoline_shared_secret,
),
})
},
_ => Err(OnionDecodeErr::Malformed {
err_msg: "Non-Trampoline onion data provided to us as inner onion",
// todo: find more suitable error code
err_code: 0x4000 | 22,
}),
}
},
Err(e) => Err(e),
}
},
_ => {
if blinding_point.is_some() {
return Err(OnionDecodeErr::Malformed {
Expand All @@ -1567,6 +1672,7 @@ where
err_msg: "Intermediate Node OnionHopData provided for us as a final node",
err_code: 0x4000 | 22,
shared_secret,
trampoline_shared_secret: None,
})
},
},
Expand Down Expand Up @@ -1715,6 +1821,7 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(
err_msg: "Unable to decode our hop data",
err_code: error_code,
shared_secret: SharedSecret::from_bytes(shared_secret),
trampoline_shared_secret: None,
});
},
Ok(msg) => {
Expand All @@ -1724,6 +1831,7 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(
err_msg: "Unable to decode our hop data",
err_code: 0x4000 | 22,
shared_secret: SharedSecret::from_bytes(shared_secret),
trampoline_shared_secret: None,
});
}
if hmac == [0; 32] {
Expand Down

0 comments on commit d22e0fe

Please sign in to comment.