From e1c6e552577a62462b8316ee04573f282e83508f Mon Sep 17 00:00:00 2001 From: Evan Kaloudis Date: Sun, 15 Sep 2024 17:18:58 -0400 Subject: [PATCH 1/4] ZEUS-2371: LND v0.18.3: blinded paths for BOLT 11 --- backends/CLNRest.ts | 1 + backends/CLightningREST.ts | 1 + backends/Eclair.ts | 1 + backends/EmbeddedLND.ts | 2 ++ backends/LND.ts | 2 ++ backends/LightningNodeConnect.ts | 2 ++ backends/LndHub.ts | 1 + backends/Spark.ts | 1 + lndmobile/LndMobileInjection.ts | 2 ++ lndmobile/index.ts | 3 ++ locales/en.json | 3 ++ models/Invoice.ts | 1 + stores/InvoicesStore.ts | 4 +++ utils/BackendUtils.ts | 2 ++ views/Receive.tsx | 59 ++++++++++++++++++++++++++++++-- 15 files changed, 82 insertions(+), 3 deletions(-) diff --git a/backends/CLNRest.ts b/backends/CLNRest.ts index 914222350..a8fc933d0 100644 --- a/backends/CLNRest.ts +++ b/backends/CLNRest.ts @@ -416,6 +416,7 @@ export default class CLNRest { supportsChannelBatching = () => false; supportsLSPS1customMessage = () => false; supportsLSPS1rest = () => true; + supportsBolt11BlindedRoutes = () => false; supportsOffers = async () => { const { configs } = await this.postRequest('/v1/listconfigs'); diff --git a/backends/CLightningREST.ts b/backends/CLightningREST.ts index 6cdcb6755..1abb37d59 100644 --- a/backends/CLightningREST.ts +++ b/backends/CLightningREST.ts @@ -304,6 +304,7 @@ export default class CLightningREST extends LND { supportsChannelBatching = () => false; supportsLSPS1customMessage = () => false; supportsLSPS1rest = () => true; + supportsBolt11BlindedRoutes = () => false; supportsOffers = async () => { const res = await this.getRequest('/v1/utility/listConfigs'); const supportsOffers: boolean = res['experimental-offers'] || false; diff --git a/backends/Eclair.ts b/backends/Eclair.ts index 2297dd02f..45d7ccd2a 100644 --- a/backends/Eclair.ts +++ b/backends/Eclair.ts @@ -508,6 +508,7 @@ export default class Eclair { supportsChannelBatching = () => false; supportsLSPS1customMessage = () => false; supportsLSPS1rest = () => true; + supportsBolt11BlindedRoutes = () => false; supportsOffers = () => false; isLNDBased = () => false; } diff --git a/backends/EmbeddedLND.ts b/backends/EmbeddedLND.ts index e4d442704..fc30b98ef 100644 --- a/backends/EmbeddedLND.ts +++ b/backends/EmbeddedLND.ts @@ -85,6 +85,7 @@ export default class EmbeddedLND extends LND { memo: data.memo, expiry: data.expiry, is_amp: data.is_amp, + is_blinded: data.is_blinded, is_private: data.private, preimage: data.preimage, route_hints: data.route_hints @@ -299,5 +300,6 @@ export default class EmbeddedLND extends LND { supportsLSPS1customMessage = () => true; supportsLSPS1rest = () => false; supportsOffers = () => false; + supportsBolt11BlindedRoutes = () => this.supports('v0.18.3'); isLNDBased = () => true; } diff --git a/backends/LND.ts b/backends/LND.ts index 8711a6416..f81f0daa2 100644 --- a/backends/LND.ts +++ b/backends/LND.ts @@ -316,6 +316,7 @@ export default class LND { value_msat: data.value_msat || Number(data.value) * 1000, expiry: data.expiry, is_amp: data.is_amp, + is_blinded: data.is_blinded, private: data.private, r_preimage: data.preimage ? Base64Utils.hexToBase64(data.preimage) @@ -672,5 +673,6 @@ export default class LND { supportsLSPS1customMessage = () => true; supportsLSPS1rest = () => false; supportsOffers = (): Promise | boolean => false; + supportsBolt11BlindedRoutes = () => this.supports('v0.18.3'); isLNDBased = () => true; } diff --git a/backends/LightningNodeConnect.ts b/backends/LightningNodeConnect.ts index 0bad625e9..a092d3cb6 100644 --- a/backends/LightningNodeConnect.ts +++ b/backends/LightningNodeConnect.ts @@ -152,6 +152,7 @@ export default class LightningNodeConnect { value_msat: data.value_msat || Number(data.value) * 1000, expiry: data.expiry, is_amp: data.is_amp, + is_blinded: data.is_blinded, private: data.private, r_preimage: data.preimage ? Base64Utils.hexToBase64(data.preimage) @@ -494,5 +495,6 @@ export default class LightningNodeConnect { supportsLSPS1customMessage = () => true; supportsLSPS1rest = () => false; supportsOffers = () => false; + supportsBolt11BlindedRoutes = () => this.supports('v0.18.3'); isLNDBased = () => true; } diff --git a/backends/LndHub.ts b/backends/LndHub.ts index eea18b02b..87d1c4c6b 100644 --- a/backends/LndHub.ts +++ b/backends/LndHub.ts @@ -157,6 +157,7 @@ export default class LndHub extends LND { supportsChannelBatching = () => true; supportsLSPS1customMessage = () => false; supportsLSPS1rest = () => false; + supportsBolt11BlindedRoutes = () => false; supportsOffers = () => false; isLNDBased = () => false; } diff --git a/backends/Spark.ts b/backends/Spark.ts index e90bd5425..b10a52000 100644 --- a/backends/Spark.ts +++ b/backends/Spark.ts @@ -382,6 +382,7 @@ export default class Spark { supportsChannelBatching = () => true; supportsLSPS1customMessage = () => false; supportsLSPS1rest = () => true; + supportsBolt11BlindedRoutes = () => false; supportsOffers = () => false; isLNDBased = () => false; } diff --git a/lndmobile/LndMobileInjection.ts b/lndmobile/LndMobileInjection.ts index 932eb4f7d..39a7fcd6d 100644 --- a/lndmobile/LndMobileInjection.ts +++ b/lndmobile/LndMobileInjection.ts @@ -133,6 +133,7 @@ export interface ILndMobileInjections { memo, expiry, is_amp, + is_blinded, is_private, preimage, route_hints @@ -142,6 +143,7 @@ export interface ILndMobileInjections { memo: string; expiry?: number; is_amp?: boolean; + is_blinded?: boolean; is_private?: boolean; preimage?: string; route_hints?: lnrpc.IRouteHint[] | null; diff --git a/lndmobile/index.ts b/lndmobile/index.ts index d42137018..994c2208e 100644 --- a/lndmobile/index.ts +++ b/lndmobile/index.ts @@ -568,6 +568,7 @@ export const addInvoice = async ({ memo, expiry = 3600, is_amp, + is_blinded, is_private, preimage, route_hints @@ -577,6 +578,7 @@ export const addInvoice = async ({ memo: string; expiry: number; is_amp?: boolean; + is_blinded?: boolean; is_private?: boolean; preimage?: string; route_hints?: lnrpc.IRouteHint[] | null; @@ -597,6 +599,7 @@ export const addInvoice = async ({ private: is_private, min_hop_hints: is_private ? 6 : 0, is_amp, + is_blinded, r_preimage: preimage ? Base64Utils.hexToBytes(preimage) : undefined, route_hints } diff --git a/locales/en.json b/locales/en.json index cc97f9824..9e3d6ba23 100644 --- a/locales/en.json +++ b/locales/en.json @@ -606,6 +606,9 @@ "views.Receive.ampSwitchExplainer2": "Please note that AMP invoices are currently only compatible with LND nodes.", "views.Receive.lspZeroAmt": "The LSP is incompatible with zero amounts. An unwrapped invoice has been generated. Your node's public key will be exposed.", "views.Receive.createLightningAddress": "Create lightning address", + "views.Receive.blindedPaths": "Blinded paths", + "views.Receive.blindedPathsExplainer1": "Using blinded paths in your invoice is an advanced privacy technique that allows you to hide your node's' public key, which is typically revealed in most lightning invoices.", + "views.Receive.blindedPathsExplainer2": "Note that not all wallets support paying BOLT 11 invoices with blinded paths yet, and that the possiblity of payment success may decrease.", "views.Send.title": "Send", "views.Send.rPreimage": "R Preimage", "views.Send.lnPayment": "Lightning payment request", diff --git a/models/Invoice.ts b/models/Invoice.ts index 5b2558f49..5a9a618f1 100644 --- a/models/Invoice.ts +++ b/models/Invoice.ts @@ -52,6 +52,7 @@ export default class Invoice extends BaseModel { public cltv_expiry: string; public htlcs: Array; public is_amp?: boolean; + public is_blinded?: boolean; // c-lightning, eclair public bolt11: string; public label: string; diff --git a/stores/InvoicesStore.ts b/stores/InvoicesStore.ts index ea765d2a3..a06ad31db 100644 --- a/stores/InvoicesStore.ts +++ b/stores/InvoicesStore.ts @@ -126,6 +126,7 @@ export default class InvoicesStore { expiry = '3600', lnurl?: LNURLWithdrawParams, ampInvoice?: boolean, + blindedPaths?: boolean, routeHints?: boolean, routeHintChannels?: Channel[], addressType?: string, @@ -139,6 +140,7 @@ export default class InvoicesStore { expiry, lnurl, ampInvoice, + blindedPaths, routeHints, routeHintChannels, true, @@ -185,6 +187,7 @@ export default class InvoicesStore { expiry = '3600', lnurl?: LNURLWithdrawParams, ampInvoice?: boolean, + blindedPaths?: boolean, routeHints?: boolean, routeHintChannels?: Channel[], unified?: boolean, @@ -205,6 +208,7 @@ export default class InvoicesStore { }; if (ampInvoice) req.is_amp = true; + if (blindedPaths) req.is_blinded = true; if (routeHints) { if (routeHintChannels?.length) { const routeHints = []; diff --git a/utils/BackendUtils.ts b/utils/BackendUtils.ts index f0c80fc67..20a86b63e 100644 --- a/utils/BackendUtils.ts +++ b/utils/BackendUtils.ts @@ -169,6 +169,8 @@ class BackendUtils { supportsOnchainBatching = () => this.call('supportsOnchainBatching'); supportsChannelBatching = () => this.call('supportsChannelBatching'); supportsOffers = () => this.call('supportsOffers'); + supportsBolt11BlindedRoutes = () => + this.call('supportsBolt11BlindedRoutes'); isLNDBased = () => this.call('isLNDBased'); // LNC diff --git a/views/Receive.tsx b/views/Receive.tsx index d84114b61..0937ac6a8 100644 --- a/views/Receive.tsx +++ b/views/Receive.tsx @@ -134,6 +134,7 @@ interface ReceiveState { ampInvoice: boolean; routeHints: boolean; account: string; + blindedPaths: boolean; // POS orderId: string; orderTotal: string; @@ -188,6 +189,7 @@ export default class Receive extends React.Component< ampInvoice: false, routeHints: false, account: 'default', + blindedPaths: false, // POS orderId: '', orderTip: '', @@ -286,8 +288,13 @@ export default class Receive extends React.Component< }); } - const { expirySeconds, routeHints, ampInvoice, addressType } = - this.state; + const { + expirySeconds, + routeHints, + ampInvoice, + blindedPaths, + addressType + } = this.state; // POS const memo = route.params?.memo ?? this.state.memo; @@ -354,6 +361,7 @@ export default class Receive extends React.Component< expirySeconds, routeHints, ampInvoice, + blindedPaths, addressType ); } @@ -435,6 +443,7 @@ export default class Receive extends React.Component< expirySeconds?: string, routeHints?: boolean, ampInvoice?: boolean, + blindedPaths?: boolean, addressType?: string ) => { const { InvoicesStore } = this.props; @@ -447,6 +456,7 @@ export default class Receive extends React.Component< expirySeconds || '3600', undefined, lspIsActive ? false : ampInvoice || false, + lspIsActive ? false : blindedPaths || false, lspIsActive ? false : routeHints || false, undefined, BackendUtils.supportsAddressTypeSelection() @@ -568,6 +578,7 @@ export default class Receive extends React.Component< undefined, undefined, undefined, + undefined, !lspIsActive ) .then( @@ -1041,7 +1052,8 @@ export default class Receive extends React.Component< lspIsActive, lspNotConfigured, routeHintMode, - selectedRouteHintChannels + selectedRouteHintChannels, + blindedPaths } = this.state; const { fontScale } = Dimensions.get('window'); @@ -2609,6 +2621,43 @@ export default class Receive extends React.Component< )} + {BackendUtils.supportsBolt11BlindedRoutes() && + !lspIsActive && ( + <> + + {localeString( + 'views.Receive.blindedPaths' + )} + + + this.setState({ + blindedPaths: + !blindedPaths + }) + } + /> + + )} +