Skip to content

Implementation of bip-0353#246

Open
IgnacioPorte wants to merge 6 commits intolndk-org:masterfrom
IgnacioPorte:master
Open

Implementation of bip-0353#246
IgnacioPorte wants to merge 6 commits intolndk-org:masterfrom
IgnacioPorte:master

Conversation

@IgnacioPorte
Copy link
Contributor

@IgnacioPorte IgnacioPorte commented Oct 6, 2025

I’ve pushed a draft to validate the approach, later I will split it into clean commits and align it with our standards.

Per BIP-353, my understanding is we don’t need to run a resolver ourselves. We can send a DNSSECQuery over BLIP-32 to an external resolver node, wait for the response, and then locally verify the returned DNSSEC proof.

BIP-353 flow (short):

  1. User enters a name (e.g., alice.example).

  2. Build and send DNSSECQuery (via BLIP-32 to resolver peers, or self-resolve).

  3. Receive DNSSECProof over onion messages.

  4. Locally validate DNSSEC (required by BIP-353).

  5. Extract URI from TXT (typically lightning:).

  6. Extract offer and pay it

Ref: DNSResolverMessageHandler::handle_dnssec_query — https://docs.rs/lightning/latest/lightning/onion_message/dns_resolution/trait.DNSResolverMessageHandler.html#tymethod.handle_dnssec_query

You need a valid DNS name with a TXT record following BIP-353 to fully test the flow. You can use nacho@nachoporte.space to partially test: it has a valid BIP-353 TXT record, so resolution will succeed, but the payment attempt will fail because there’s no route to the node.

EDIT:
As disscused with @a-mpch the first implementation will be done with self resolve and then we will implement the blip-32

@codecov
Copy link

codecov bot commented Oct 6, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 6.88%. Comparing base (25ca60a) to head (9848db5).

Additional details and impacted files
@@          Coverage Diff           @@
##           master    #246   +/-   ##
======================================
  Coverage    6.88%   6.88%           
======================================
  Files           1       1           
  Lines         218     218           
======================================
  Hits           15      15           
  Misses        203     203           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@a-mpch a-mpch self-requested a review October 6, 2025 23:07
@IgnacioPorte IgnacioPorte force-pushed the master branch 2 times, most recently from 590c184 to a24ff2b Compare October 11, 2025 02:32
Add support for resolving human-readable names (e.g., satoshi@example.com)
to BOLT12 offers via DNSSEC-validated DNS lookups. The PayOfferRequest now
accepts an optional `name` field that the server resolves to an offer string
before processing payment.
Add dedicated error types for human-readable name parsing, DNS resolution
failures, and invalid payment URI formats to improve error diagnostics.
@IgnacioPorte IgnacioPorte marked this pull request as ready for review October 12, 2025 00:24
@IgnacioPorte IgnacioPorte force-pushed the master branch 2 times, most recently from 01250f1 to b3fd9c1 Compare October 12, 2025 02:32
@IgnacioPorte IgnacioPorte changed the title feat: draft implementation of bip-0353 Implementation of bip-0353 Oct 12, 2025
Copy link
Collaborator

@a-mpch a-mpch left a comment

Choose a reason for hiding this comment

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

solid start, let me know about the test related comments

resolver: R,
) {
if let Ok(mut guard) = lndk::dns_resolver::TEST_RESOLVER.write() {
*guard = Some(std::sync::Arc::new(resolver));
Copy link
Collaborator

Choose a reason for hiding this comment

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

This looks good, would it be easier using mockall crate that we use elsewhere?

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 because pay_name calls directly to LndkDNSResolverMessageHandler, you can't inject a mock here without the global static. To use easily mockall we would have to pass the dns_resolver as param to pay_name

}

#[tokio::test(flavor = "multi_thread")]
async fn test_pay_offer_with_name_and_dns() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for trying out actually doing the Server and then calling the client. We currently are using the server itself to trigger the messenger actions.
I'd stick to the same pattern (no so strongly, I'm happy to discuss it).

Anyway, I think we could have a different route to pay human_readable and not an option. In the future we could a have a "Pay" that just pay whatever it's sees.
Then we can test the PayHumanReadable flow not changing the gRPC of PayOffer. We could even have a "fetch_offer" in order to test that flow only.
Finally on the mocking side you can use the LDK node Offer to be resolved to the function so we can have a payment after the resolution

Copy link
Contributor Author

@IgnacioPorte IgnacioPorte Oct 15, 2025

Choose a reason for hiding this comment

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

To start I added a PayName endpoint

Add integration test validating end-to-end payment using human-readable names.
Uses mock DNS resolver to test server-side name resolution and payment flow.
Copy link
Collaborator

@a-mpch a-mpch 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! Haven't tested It yet.
Left some minor comments and we should be good to go

Extract BIP-353 human-readable name payment into dedicated PayName
RPC endpoint, separate from PayOffer. This improves API clarity by
distinguishing direct offer payments from name-based payments that
require DNS resolution.
@a-mpch
Copy link
Collaborator

a-mpch commented Oct 21, 2025

Sorry the delay, I'll review it ASAP


let resolution = self
.resolver
.resolve_hrn(&hrn_parsed)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it have a time out?

Copy link
Contributor Author

@IgnacioPorte IgnacioPorte Nov 5, 2025

Choose a reason for hiding this comment

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

No, now I added


/// Resolves a human-readable name (BIP-353) to an offer and pays it.
pub async fn pay_hrn(&self, cfg: PayHumanReadableAddressParams) -> Result<Payment, OfferError> {
let offer_str = LndkDNSResolverMessageHandler::new()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we could return an error if offer is not on the same network lndk is

Copy link
Contributor Author

@IgnacioPorte IgnacioPorte Nov 6, 2025

Choose a reason for hiding this comment

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

If the node that makes the resolution can't reach the node of the offer it will throw the respective error when trying to pay the offer. I think it's better to keep it like that

Copy link
Contributor

@f3r10 f3r10 left a comment

Choose a reason for hiding this comment

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

As indicated, I used nacho@nachoporte.space and the resolution was successful.

I tried to use other names, such: send.some@satsto.me or matt@mattcorallo.com, but the resolution fails.

Replaces global TEST_RESOLVER static with proper dependency injection
via a new with_dns_resolver() constructor. This makes the code more testable and eliminates potential
race conditions from shared mutable state.
Adds 20-second timeout to HTTP client used for DNS resolution to
prevent requests from hanging indefinitely on unresponsive servers.
Replaces manual URI query parsing with url crate for proper handling
of percent-encoded characters (%20, %3D, etc.) and robust parsing of
BIP-21 bitcoin URIs.
@IgnacioPorte IgnacioPorte requested a review from a-mpch November 6, 2025 00:47
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.

3 participants