Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keysend #967

Merged
merged 11 commits into from
Jul 28, 2021
Merged

Keysend #967

merged 11 commits into from
Jul 28, 2021

Conversation

valentinewallace
Copy link
Contributor

@valentinewallace valentinewallace commented Jun 23, 2021

Based on #964

06/23/2021: Seeking concept ACKs for the API before adding tests

@codecov
Copy link

codecov bot commented Jun 23, 2021

Codecov Report

Merging #967 (6dd6289) into main (d37b1dd) will decrease coverage by 0.03%.
The diff coverage is 88.09%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #967      +/-   ##
==========================================
- Coverage   90.78%   90.75%   -0.04%     
==========================================
  Files          60       60              
  Lines       30926    31322     +396     
==========================================
+ Hits        28076    28426     +350     
- Misses       2850     2896      +46     
Impacted Files Coverage Δ
lightning/src/util/events.rs 15.13% <0.00%> (-2.04%) ⬇️
lightning/src/ln/chanmon_update_fail_tests.rs 97.62% <81.48%> (-0.33%) ⬇️
lightning/src/ln/channelmanager.rs 85.80% <92.85%> (+0.94%) ⬆️
lightning/src/ln/functional_tests.rs 97.33% <93.75%> (-0.11%) ⬇️
lightning/src/ln/features.rs 99.70% <100.00%> (+<0.01%) ⬆️
lightning/src/ln/functional_test_utils.rs 94.90% <100.00%> (+0.03%) ⬆️
lightning/src/ln/msgs.rs 88.69% <100.00%> (+0.04%) ⬆️
lightning/src/ln/onion_route_tests.rs 97.11% <100.00%> (ø)
lightning/src/ln/onion_utils.rs 94.91% <100.00%> (+0.01%) ⬆️
lightning/src/routing/router.rs 95.94% <100.00%> (+0.01%) ⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d37b1dd...6dd6289. Read the comment docs.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK the (public) api changes.

lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/msgs.rs Show resolved Hide resolved
Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

lightning/src/ln/channelmanager.rs Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/peer_handler.rs Outdated Show resolved Hide resolved
@valentinewallace valentinewallace force-pushed the 2021-06-keysend branch 4 times, most recently from a4fe071 to b825069 Compare June 25, 2021 21:58
@valentinewallace valentinewallace marked this pull request as ready for review June 25, 2021 22:03
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
@valentinewallace valentinewallace force-pushed the 2021-06-keysend branch 6 times, most recently from cf23fbf to 08a1cb3 Compare July 2, 2021 18:40
@valentinewallace valentinewallace force-pushed the 2021-06-keysend branch 2 times, most recently from 2cdf5fb to a003460 Compare July 4, 2021 22:00
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Structure all looks good IMO, one request for more tests and a comment or two.

lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Show resolved Hide resolved
impl_writeable_tlv_based!(ClaimableHTLC, {
(0, prev_hop, required),
(2, value, required),
(4, payment_data, required),
// XXX what to do here?
(4, claim_type, required),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, right, so to make this compatible we need to pull the impl_writeable_tlv_based apart and implement manually - if ClaimableType is Invoice we'll write the 4-type payment_data and if its Spontaneous we'll write an 8-type preimage. Its not ideal and we could just cheat here if we want, but IMO its not a ton of work here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I did this

@valentinewallace valentinewallace force-pushed the 2021-06-keysend branch 3 times, most recently from af5ea24 to 85950c0 Compare July 10, 2021 22:06
@TheBlueMatt TheBlueMatt linked an issue Jul 12, 2021 that may be closed by this pull request
if let Some(final_data) = payment_data {
if final_data.total_msat > MAX_VALUE_MSAT { panic!("We should never be sending infinite/overflow onion payments"); }
}
encode_varint_length_prefixed_tlv!(w, {
(2, HighZeroBytesDroppedVarInt(self.amt_to_forward), required),
(4, HighZeroBytesDroppedVarInt(self.outgoing_cltv_value), required),
(8, payment_data, option)
(8, payment_data, option),
(5482373484, keysend_preimage, option)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could you use a trailing comma so the blame layer for future additions don't affect this line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The macro gives me an error when I do this :/

lightning/src/util/events.rs Outdated Show resolved Hide resolved
Comment on lines -209 to +237
(2, payment_secret, required),
(2, payment_secret, option),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to check my understanding, going from required to optional is ok, but going from optional to required would necessitate increasing the version number?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's ways to make both directions work depending on if you're willing to add a special case in the code for backwards-compatibility? @TheBlueMatt would have to confirm

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think going from option to required is never allowed. If we move from option to required, and an old version "wrote" None, we'll fail to deserialize data written by an old node, which is not allowed unless we are deliberately breaking reading old versions (presumably after several sequential versions that always wrote Some so that you have to be reading very old data to fail).

(8, payment_preimage, option),
});
let purpose = match payment_secret {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If payment_secret were optional in InvoicePayment, would we be able to distinguish what the purpose is? In other words, should we encode this as an enum instead? Or would that break compatibility?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other words, should we encode this as an enum instead?

Could you clarify roughly what enum is being suggested? Didn't quite follow..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PaymentPurpose enum using impl_writeable_tlv_based_enum.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Believe that'd break compatibility

lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
Comment on lines 4624 to 4665
impl Readable for ClaimableHTLC {
fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError> {
init_tlv_field_var!(prev_hop, required);
init_tlv_field_var!(value, required);
init_tlv_field_var!(payment_data, required);
init_tlv_field_var!(cltv_expiry, required);
init_tlv_field_var!(keysend_preimage, option);
read_tlv_fields!
(reader,
{
(0, prev_hop, required), (2, value, required),
(4, payment_data, required), (6, cltv_expiry, required),
(8, keysend_preimage, option)
});
let claim_type = match keysend_preimage {
Some(p) => {
let data: msgs::FinalOnionHopData = payment_data.0.unwrap();
if data.payment_secret != PaymentSecret([0; 32]) || data.total_msat != 0 {
return Err(DecodeError::InvalidValue)
}
ClaimableType::Spontaneous(p)
},
None => ClaimableType::Invoice(payment_data.0.unwrap()),
};
Ok(Self {
prev_hop: init_tlv_based_struct_field!(prev_hop, required),
value: init_tlv_based_struct_field!(value, required),
claim_type,
cltv_expiry: init_tlv_based_struct_field!(cltv_expiry, required),
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like as we change our data format more, the more complex our (de)serialization logic becomes as we can no longer use impl_writeable_tlv_based. I'm wondering if it would be any better to bump the version but support reading older versions. Then maybe you could condition on the version read and use a macro for implementing each version we'd like to support? I might be missing something about this, though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I dont think bumping the version helps a ton, then we'd just have two copies of the deserialization logic - one for the old objects, and one for the new, and we'd have to switch between them based on the version. That just results in a code blowup that's almost equivalent, I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess then the versions would at least be fairly well delineated. Otherwise, if we refactor further there is more cognitive overhead in figuring out the various cases. But maybe at that point we'd just decided to bump versions. I don't feel strongly on this, but just want to point out the cost.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, if our version is only in topic-level structs (is it?) then we'd have to pass it through. So maybe not very feasible / clean?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I have a feeling sooner or later we'll adapt our read trait to pass a version number. I haven't bothered yet, but it may be required eventually. I agree we should be careful about growing bloat in read functions, maybe it makes sense to have a "we only support things written by versions as old as one year ago"-style policy.

Comment on lines 4512 to 4627
impl_writeable_tlv_based!(ClaimableHTLC, {
(0, prev_hop, required),
(2, value, required),
(4, payment_data, required),
(6, cltv_expiry, required),
});
impl Writeable for ClaimableHTLC {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
let payment_data = match &self.claim_type {
ClaimableType::Invoice(data) => data.clone(),
_ => msgs::FinalOnionHopData {
payment_secret: PaymentSecret([0; 32]),
total_msat: 0,
}
};
let keysend_preimage = match self.claim_type {
ClaimableType::Invoice(_) => None,
ClaimableType::Spontaneous(preimage) => Some(preimage.clone()),
};
write_tlv_fields!
(writer,
{
(0, self.prev_hop, required), (2, self.value, required),
(4, payment_data, required), (6, self.cltv_expiry, required),
(8, keysend_preimage, option),
});
Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do this because we introduced an enum where there wasn't one before? Would it make sense to bump the version and serialize enums using the macro for doing so?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah: #967 (comment)

Would it make sense to bump the version and serialize enums using the macro for doing so?

Is this the same idea as in this comment? I'm pretty eager to get this PR out the door to unblock sphinx, but would love to get @TheBlueMatt's input on this idea since it does sound potentially nicer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, similar concern.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't "just bump the version" since we want to at least try (I think) to be compatible where old versions can read new serialized data. Because some users will be synchronizing state across multiple applications that may upgrade at different times, I think we should generally try to at least have our data be readable by the previous version as long as you aren't using new features.

lightning/src/ln/msgs.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
Comment on lines -209 to +237
(2, payment_secret, required),
(2, payment_secret, option),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think going from option to required is never allowed. If we move from option to required, and an old version "wrote" None, we'll fail to deserialize data written by an old node, which is not allowed unless we are deliberately breaking reading old versions (presumably after several sequential versions that always wrote Some so that you have to be reading very old data to fail).

// final_expiry_too_soon
// We have to have some headroom to broadcast on chain if we have the preimage, so make sure we have at least
// HTLC_FAIL_BACK_BUFFER blocks to go.
// Also, ensure that, in the case of an unknown payment hash, our payment logic has enough time to fail the HTLC backward
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment could be clearer saying "an unknown preimage for the received payment hash" ?

I don't think this is true that we trigger a channel closure for an inbound HTLC of which the payment hash is unknown. See ChannelMonitor::should_broadcast_holder_commitment_txn, if the htlc is !htlc_outbound and we don't know a self.payment_preimages.contains_key(&htlc.payment_hash) so it doesn't matter for our payment logic has enough time to fail the HTLC backward ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment could be clearer saying "an unknown preimage for the received payment hash" ?

Added this!

@@ -1863,6 +1886,23 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
}
}

/// Send a spontaneous payment, which is a payment that does not require the recipient to have
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the recipient did generate an invoice with a given payment hash and signals even payment_secret feature bit ? If the recipient is a LDK node, I think it'll fail the keysend reception as we don't allow payment_secret + keysend_preimage ?

Maybe a bit edgy but point to be cleared up with keysend specification ? Like invoice reception implies MUST NOT pay with keysend?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I think the behavior here is right, we should expect a "real" payment, the spec should mention this.

});
},
hash_map::Entry::Occupied(_) => {
log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} for a duplicative payment hash", log_bytes!(payment_hash.0));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC this case handling, if we've a valid (payment_secret, payment_hash) pair in self.pending_inbound_payments and we receive a latter keysend with the same hash, we fail the keysend instead of claiming ?

If you're an intermediary hop Mallory and you relay HTLC 1 hash X to final hop Bob, I think this let Mallory send a "final-receiver probing" keysend hash X to Bob and observe the early failure (PendingHTLCsForwardable vs PaymentReceived processing times diff) ? If this is correct, I'm not sure if it binds well the requirement laid out above ("Further, we must not expose whether we have any other HTLCs associated with the same payment_hash pending or not')...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grr good catch. Going one step up the stack, I wonder if we're gonna have users screw this up reliably too. If you're a user and doing accounting based on the payment_hash when you issue invoices, if you receive a keysend I can see users looking up the payment hash in their accounting database. I wonder if we should avoid exposing the keysend payment hash/preimage entirely. I guess some users may want them for other reasons, but we should go to great pains to point out in documentation that you MUST NEVER EVER co-mingle payment hash lookups between keysend and non-keysend. Maybe at least we should name the field differently to call it "keysend preimage" instead of "payment preimage"?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid to return payment hash to avoid mangle with the accounting database ? I can foresee users willingly to know the preimage if it has any semantic sense like a pay-to-publish, privacy-preserving satoshi place. But within the keysend model I would say the payment hash should be ignored by the receiver there is no hash validation operation to realize against a database.

And if the receiver wants to prove delivery they can still manually hash the preimage. We can even return a prefixed preimage by concatening a "Lightning keysend" ?

You might even have worst collision, where a low-value keysend trigger the release of a virtual/physical good by your business logic, substituting to a regular high-value payment...?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, disregard, I had made that comment before I saw your suggestion at #967 (comment)

@@ -2304,15 +2380,27 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
let mut total_value = 0;
let htlcs = channel_state.claimable_htlcs.entry(payment_hash)
.or_insert(Vec::new());
if htlcs.len() == 1 {
if let OnionPayload::Spontaneous(_) = htlcs[0].onion_payload {
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as we already had an existing keysend HTLC with the same payment hash", log_bytes!(payment_hash.0));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I've a doubt if this doesn't introduce a weird payment censorship attack. Let's say an attacker observe that a payment invoice hash X has been issued by Alice. From now on, it will repeatedly send keysend X to Alice aiming to provoke a collision with honest HTLC X, thus triggering a reject.

Is the concern exposed sound ? I don't think the attacker even has to be on the honest HTLC payment path...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, but its solved by #967 (comment) as well, no?

incoming_cltv_expiry: msg.cltv_expiry,
}
} else if let Some(payment_preimage) = keysend_preimage {
PendingHTLCRouting::ReceiveKeysend {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about checking early that keysend preimage == HTLC hash and isn't pure junk, returning 0x4000|22 if it is ? May help with probing concerns expressed after.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! Yea, if we do that then we don't have to completely rewrite this whole PR to avoid the issues you noted at #967 (comment)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes i think that's okay to solve the issues. I had a concern with MPP payment where a preimage revealed could be replay as a probing keysend as long as the whole eMPP isn't settled but in fact we won't start the MPP settlement phase until all chunks have been received. And when we do so we remove the entry from pending_inbound_payments and as such the collision ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it doesn't matter either way - basically we would be using the payment preimage the same way we use the payment secret today - to authenticate that the sender of a given HTLC is the one authorized to send for that payment hash. We reliably check this authentication immediately upon HTLC receipt both for MPP and regular payments, and will just have to also do it for keysend.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about checking early that keysend preimage == HTLC hash and isn't pure junk, returning 0x4000|22 if it is ? May help with probing concerns expressed after.

Added this check! Thanks for the thoughtful review Antoine

lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as we didn't have a corresponding inbound payment.", log_bytes!(payment_hash.0));
fail_htlc!(claimable_htlc);
},
OnionPayload::Spontaneous(preimage) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear, this doesn't prevent honest keysend replay, where a buggy/hazardous payer sends twice a keysend with the same hash, even with different payment paths, and as such opens the payment to be claimed in-flight by an intermediary node ? Note, I think this is already a risk for any hashlock-based LN payment (variant of wormhole attacks...)

I would say we even care less that w.r.t to regular payments as we keysend the preimage doesn't have the proof-of-payment property plead most of the time ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yes it does seem like keysend requires more thought than normal payments for replay situations. I don't think it would open up buggy clients to in-flight claims by an intermediate node though, because wouldn't the buggy client run into issues once they started "replaying" the whole update_add dance, preventing the HTLC from being successfully added to the commitment tx to begin with?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the HTLC would appear under a new htlc_id. I'm thinking a situation similar to duplicate_htlc_test/test_duplicate_htlc_different_direction_onchain. Though nevermind, we don't prevent double-spend already for normal payments, see comment around send_payment.

lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
Copy link

@ariard ariard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my points about payment fault-injection and the lowered cost of hash-collision in our ChannelMonitor.

I wonder if we shouldn't introduce a no_keysend_receive config setting. Previously, we have a cleaner separation between routing and payment endpoint, and a routing hop no eager to receive payments could have just not issue invoices at all. I think this is blurred by keysend, and you might have weird interactions to care about with potential higher application logic. E.g a third-party replaying hash trying to provoke undefined behaviors. Likely going to be worst if we implement MPP keysend.

I think we can address this config setting question in a follow-up, otherwise I believe the PR to be pretty mature. Great work overall!

},
/// Because this is a spontaneous payment, the payer generated their own preimage rather than us
/// (the payee) providing a preimage.
SpontaneousPayment(PaymentPreimage),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're bouncing between mentions of "spontaenous" or "keysend" payments across this PR to designate the same feature. LDK-user wise, maybe we should bind to a consistent naming ?

Copy link
Contributor Author

@valentinewallace valentinewallace Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkczyz I was thinking that people might want to specifically send either a keysend spontaneous payment or a non-keysend spontaneous payment if they know that the payee only supports one or the other. So, I'm somewhat in favor of switching all Spontaneous variable names to Keysend (including s/send_spontaneous_payment/send_keysend_payment). Thoughts?

Copy link
Contributor

@jkczyz jkczyz Jul 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding was that Keysend is a specific type of spontaneous payment that is the predecessor to spontaneous AMP payments. They both seem either unintuitive or somewhat overloaded names and also are implementation details from a user's perspective.

On the receiving side (Event::PaymentReceived), does the user need to know what implementation was used to make the spontaneous payment? If not, I'd argue we shouldn't expose the implementation detail in our public API.

On the sending side (ChannelManager), how does the user know if the receiver accepts spontaneous payments and using which implementation? If via feature flags, could we chose which implementation to use based on the flags rather than making the user choose?

On the processing side, can these be generalized in any way?

I don't have definitive answers to these, but I'd imagine we could at least keep the first two generic (i.e., "spontaneous") and if needed make the third specific to the implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

w/r/t:

On the receiving side (Event:: PaymentReceived), does the user need to know what implementation was used to make the spontaneous payment?
If via feature flags, could we chose which implementation to use based on the flags rather than making the user choose?

Not sure, pinged Sphinx for their thoughts last night. Hopefully they get back to me soonish

how does the user know if the receiver accepts spontaneous payments and using which implementation?

It's a little annoying right now... C-Lightning and Eclair advertise feature bit 55 if they support keysend, but iiuc lnd doesn't advertise any feature bits for keysend. So, right now you know if the receiver doesn't support keysend if they fail back the payment, but there's no good way to tell if they do support keysend.

lightning/src/ln/channelmanager.rs Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Show resolved Hide resolved
if let OnionPayload::Invoice(ref data) = claimable_htlc.onion_payload {
data.clone()
} else {
log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", log_bytes!(payment_hash.0));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have concerns of fault-injection attacks against honest routing hops introduced with our keysend implementation ?

Let's say you have the following topology : A -> B -> C -> D -> E.
A is the payer, it draws a payment path to the payee E, going through B, C and D.
B receives a keysend X and from HTLC traffic A's habits assumes it's a keysend.
B knows a payment secret for C, which is also a merchant node and deliver on-demand.
B sends a regular payment X to C then A's keysend X.
C receives in-order the regular payment X then A's keysend X, provoking the failure of the keysend.
C returns an error to A as the source of the payment path failure.
A downgrades C's routing reputation and doesn't select it for future payment paths.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C returns an error to A as the source of the payment path failure.

C Should only fail if the payment is to C. It should (maybe could use a test for this) still happily forward an HTLC with the same payment hash onwards.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you're right, the relayed keysend payment shouldn't appear twice in pending_inbound_payments we won't generate a PaymentReceived.

// we have at least HTLC_FAIL_BACK_BUFFER blocks to go.
// Also, ensure that, in the case of an unknown preimage for the received payment hash, our
// payment logic has enough time to fail the HTLC backward before our onchain logic triggers a
// channel closure (see HTLC_FAIL_BACK_BUFFER rationale).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think keysend make it a bit easier to exploit our hash-collision in ChannelMonitor.payment_preimages.
Let's say you have the following topology : M1 -- A -- B -- M2
M1 and M2 are collusioning attackers.
M1 sends a keysend to A, populating its payment_preimages with H (ChannelManager::claim_funds_from_hop())
M1 sends a regular payment H to M2 via A and B
M2 breaks the chan, and pin the commitment, B will never update_fail_htlc to A before the forward payment is settled (is_resolving_htlc_output)
B goes onchain on the AB link as there is a pending inbound HTLC for which it knows the preimage.
M1 and M2 succeed to close a channel, of which they're not a counterparty
Attack can be made economically rational by batching, if B has neighboors C, D, E, ..., malicious HTLCs on their channels can be routed and made pending on the one M2's commitment
Previously, this attack would have require interactivity with B, like paying an invoice for each malicious HTLC

Ultimately, I don't think keysend is faultive, it's a known defect of our ChannelMonitor logic, so I don't believe there is anything to do on this PR...

Copy link
Collaborator

@TheBlueMatt TheBlueMatt Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a known defect of our ChannelMonitor logic

If the attack starts with "M2 prevents a channel-close transaction from going onchain because of pinning" I don't think we have a bug, lightning has a bug :). There isn't anything we can do about this short of fixing Bitcoin Core.

@valentinewallace valentinewallace force-pushed the 2021-06-keysend branch 2 times, most recently from e008dad to 4fdcda5 Compare July 23, 2021 22:21
@TheBlueMatt
Copy link
Collaborator

Needs rebase.

@ariard
Copy link

ariard commented Jul 27, 2021

Code Review ACK 6dd6289, good on my side!

Thanks to adding coverage and can't think of of other weirdness. W.r.t to naming we can fix it with follow-ups once we have feedbacks from users.

@valentinewallace
Copy link
Contributor Author

Code Review ACK 6dd6289, good on my side!

Thanks to adding coverage and can't think of of other weirdness. W.r.t to naming we can fix it with follow-ups once we have feedbacks from users.

Thanks, I agree, we can bikeshed on naming in follow-up

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good mod one comment. Gonna go ahead and merge so we can move forward, but would be good to follow up.

/// never reach the recipient.
///
/// Similar to regular payments, you MUST NOT reuse a `payment_preimage` value. See
/// [`send_payment`] for more information about the risks of duplicate preimage usage.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs to mention that you should see send_payment for more details on the return value. Further, this needs to mention that the Route must only have at most one path. Finally, we need to actually check that, but not here - as far as I can see we don't currently require that non-payment-secret-containing sends don't contain more than one path, which we should do in send_payment_internal.

@TheBlueMatt TheBlueMatt merged commit 0b4079d into lightningdevkit:main Jul 28, 2021
@jkczyz
Copy link
Contributor

jkczyz commented Jul 28, 2021

Thanks, I agree, we can bikeshed on naming in follow-up

SGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Spontaneous/keysend payments
4 participants