onion_message: factor out get_blinded_paths_to_me from get_blinded_reply_paths.
the former also calculates payinfo information for payment scenarios. include payment_relay struct for payment blinded_paths.
This commit is contained in:
committed by
f321x
parent
65fb739584
commit
b7a512845f
+99
-15
@@ -31,10 +31,11 @@ import dataclasses
|
||||
from random import random
|
||||
from types import MappingProxyType
|
||||
|
||||
from typing import TYPE_CHECKING, Optional, Sequence, NamedTuple, List
|
||||
from typing import TYPE_CHECKING, Optional, Sequence, NamedTuple, Tuple
|
||||
|
||||
import electrum_ecc as ecc
|
||||
|
||||
from electrum.channel_db import get_mychannel_policy
|
||||
from electrum.lnrouter import PathEdge
|
||||
from electrum.logging import get_logger, Logger
|
||||
from electrum.crypto import sha256, get_ecdh
|
||||
@@ -42,7 +43,7 @@ from electrum.lnmsg import OnionWireSerializer
|
||||
from electrum.lnonion import (get_bolt04_onion_key, OnionPacket, process_onion_packet,
|
||||
OnionHopsDataSingle, decrypt_onionmsg_data_tlv, encrypt_onionmsg_data_tlv,
|
||||
get_shared_secrets_along_route, new_onion_packet, encrypt_hops_recipient_data)
|
||||
from electrum.lnutil import LnFeatures
|
||||
from electrum.lnutil import LnFeatures, MIN_FINAL_CLTV_DELTA_ACCEPTED, MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED
|
||||
from electrum.util import OldTaskGroup, log_exceptions
|
||||
|
||||
|
||||
@@ -61,6 +62,7 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
REQUEST_REPLY_PATHS_MAX = 3
|
||||
PAYMENT_PATHS_MAX = 3
|
||||
|
||||
|
||||
class NoRouteFound(Exception):
|
||||
@@ -93,6 +95,7 @@ def create_blinded_path(
|
||||
is_non_final_node = i < len(path) - 1
|
||||
|
||||
if is_non_final_node:
|
||||
# spec: alt: short_channel_id instead of next_node_id
|
||||
recipient_data = {
|
||||
# TODO: SHOULD add padding data to ensure all encrypted_data_tlv(i) have the same length
|
||||
'next_node_id': {'node_id': path[i+1]}
|
||||
@@ -122,6 +125,16 @@ def create_blinded_path(
|
||||
return blinded_path
|
||||
|
||||
|
||||
def encode_blinded_path(blinded_path: dict):
|
||||
with io.BytesIO() as blinded_path_fd:
|
||||
OnionWireSerializer.write_field(
|
||||
fd=blinded_path_fd,
|
||||
field_type='blinded_path',
|
||||
count=1,
|
||||
value=blinded_path)
|
||||
return blinded_path_fd.getvalue()
|
||||
|
||||
|
||||
def blinding_privkey(privkey: bytes, blinding: bytes) -> bytes:
|
||||
shared_secret = get_ecdh(privkey, blinding)
|
||||
b_hmac = get_bolt04_onion_key(b'blinded_node_id', shared_secret)
|
||||
@@ -371,35 +384,106 @@ def get_blinded_reply_paths(
|
||||
max_paths: int = REQUEST_REPLY_PATHS_MAX,
|
||||
preferred_node_id: bytes = None
|
||||
) -> Sequence[dict]:
|
||||
"""construct a list of blinded reply_paths.
|
||||
"""construct a list of blinded reply-paths for onion message.
|
||||
"""
|
||||
mydata = {'path_id': {'data': path_id}} # same path_id used in every reply path
|
||||
paths, payinfo = get_blinded_paths_to_me(lnwallet, mydata, max_paths=max_paths,
|
||||
preferred_node_id=preferred_node_id, onion_message=True)
|
||||
return paths
|
||||
|
||||
|
||||
def get_blinded_paths_to_me(
|
||||
lnwallet: 'LNWallet',
|
||||
final_recipient_data: dict,
|
||||
*,
|
||||
max_paths: int = PAYMENT_PATHS_MAX,
|
||||
preferred_node_id: bytes = None,
|
||||
onion_message: bool = False
|
||||
) -> Tuple[Sequence[dict], Sequence[dict]]:
|
||||
"""construct a list of blinded paths.
|
||||
current logic:
|
||||
- uses current onion_message capable channel peers if exist
|
||||
- otherwise, uses current onion_message capable peers
|
||||
- uses channels peers if not onion_message
|
||||
- uses current onion_message capable channel peers if exist and if onion_message
|
||||
- otherwise, uses current onion_message capable peers if onion_message
|
||||
- prefers preferred_node_id if given
|
||||
- reply_path introduction points are direct peers only (TODO: longer reply paths)"""
|
||||
- reply_path introduction points are direct peers only (TODO: longer paths)"""
|
||||
# TODO: build longer paths and/or add dummy hops to increase privacy
|
||||
my_active_channels = [chan for chan in lnwallet.channels.values() if chan.is_active()]
|
||||
my_onionmsg_channels = [chan for chan in my_active_channels if lnwallet.lnpeermgr.get_peer_by_pubkey(chan.node_id) and
|
||||
my_channels = my_active_channels
|
||||
if onion_message:
|
||||
my_channels = [chan for chan in my_active_channels if lnwallet.lnpeermgr.get_peer_by_pubkey(chan.node_id) and
|
||||
lnwallet.lnpeermgr.get_peer_by_pubkey(chan.node_id).their_features.supports(LnFeatures.OPTION_ONION_MESSAGE_OPT)]
|
||||
my_onionmsg_peers = [peer for peer in lnwallet.lnpeermgr.peers.values() if peer.their_features.supports(LnFeatures.OPTION_ONION_MESSAGE_OPT)]
|
||||
|
||||
result = []
|
||||
payinfo = []
|
||||
mynodeid = lnwallet.node_keypair.pubkey
|
||||
mydata = {'path_id': {'data': path_id}} # same path_id used in every reply path
|
||||
if len(my_onionmsg_channels):
|
||||
local_height = lnwallet.network.get_local_height()
|
||||
|
||||
if len(my_channels):
|
||||
# randomize list, but prefer preferred_node_id
|
||||
rchans = sorted(my_onionmsg_channels, key=lambda x: random() if x.node_id != preferred_node_id else 0)
|
||||
rchans = sorted(my_channels, key=lambda x: random() if x.node_id != preferred_node_id else 0)
|
||||
for chan in rchans[:max_paths]:
|
||||
blinded_path = create_blinded_path(os.urandom(32), [chan.node_id, mynodeid], mydata)
|
||||
hop_extras = None
|
||||
if not onion_message: # add hop_extras and payinfo, assumption: len(blinded_path) == 2 (us and peer)
|
||||
# get policy
|
||||
cp = get_mychannel_policy(chan.short_channel_id, chan.node_id, {chan.short_channel_id: chan})
|
||||
|
||||
dest_max_cltv_expiry = local_height + MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED
|
||||
|
||||
# TODO: for longer paths (>2), reverse traverse and calculate max_cltv_expiry at each intermediate hop
|
||||
# and determine the cltv delta sums and fee sums of the hops for the payinfo struct.
|
||||
# current assumption is len(blinded_path) == 2 (us and peer)
|
||||
sum_cltv_expiry_delta = cp.cltv_delta
|
||||
sum_fee_base_msat = cp.fee_base_msat
|
||||
sum_fee_proportional_millionths = cp.fee_proportional_millionths
|
||||
# path htlc limits
|
||||
blinded_path_min_htlc_msat = cp.htlc_minimum_msat
|
||||
blinded_path_max_htlc_msat = cp.htlc_maximum_msat
|
||||
|
||||
hop_extras = [{
|
||||
# spec: MUST include encrypted_data_tlv.payment_relay for each non-final node.
|
||||
'payment_relay': {
|
||||
'cltv_expiry_delta': cp.cltv_delta,
|
||||
'fee_base_msat': cp.fee_base_msat,
|
||||
'fee_proportional_millionths': cp.fee_proportional_millionths,
|
||||
},
|
||||
# spec: MUST set encrypted_data_tlv.payment_constraints for each non-final node and MAY set it for the final node:
|
||||
#
|
||||
# max_cltv_expiry to the largest block height at which the route is allowed to be used, starting
|
||||
# from the final node's chosen max_cltv_expiry height at which the route should expire, adding
|
||||
# the final node's min_final_cltv_expiry_delta and then adding
|
||||
# encrypted_data_tlv.payment_relay.cltv_expiry_delta at each hop.
|
||||
#
|
||||
# htlc_minimum_msat to the largest minimum HTLC value the nodes will allow.
|
||||
'payment_constraints': {
|
||||
'max_cltv_expiry': dest_max_cltv_expiry + cp.cltv_delta,
|
||||
'htlc_minimum_msat': blinded_path_min_htlc_msat
|
||||
}
|
||||
}]
|
||||
payinfo.append({
|
||||
'fee_base_msat': sum_fee_base_msat,
|
||||
'fee_proportional_millionths': sum_fee_proportional_millionths,
|
||||
'cltv_expiry_delta': sum_cltv_expiry_delta + MIN_FINAL_CLTV_DELTA_ACCEPTED,
|
||||
'htlc_minimum_msat': blinded_path_min_htlc_msat,
|
||||
'htlc_maximum_msat': blinded_path_max_htlc_msat,
|
||||
'flen': 0,
|
||||
'features': bytes(0)
|
||||
})
|
||||
blinded_path = create_blinded_path(os.urandom(32), [chan.node_id, mynodeid], final_recipient_data,
|
||||
hop_extras=hop_extras)
|
||||
result.append(blinded_path)
|
||||
elif len(my_onionmsg_peers):
|
||||
elif onion_message:
|
||||
# we can use peers even without channels for onion messages
|
||||
my_onionmsg_peers = [peer for peer in lnwallet.lnpeermgr.peers.values() if
|
||||
peer.their_features.supports(LnFeatures.OPTION_ONION_MESSAGE_OPT)]
|
||||
if len(my_onionmsg_peers):
|
||||
# randomize list, but prefer preferred_node_id
|
||||
rpeers = sorted(my_onionmsg_peers, key=lambda x: random() if x.pubkey != preferred_node_id else 0)
|
||||
for peer in rpeers[:max_paths]:
|
||||
blinded_path = create_blinded_path(os.urandom(32), [peer.pubkey, mynodeid], mydata)
|
||||
blinded_path = create_blinded_path(os.urandom(32), [peer.pubkey, mynodeid], final_recipient_data)
|
||||
result.append(blinded_path)
|
||||
|
||||
return result
|
||||
return result, payinfo
|
||||
|
||||
|
||||
class Timeout(Exception): pass
|
||||
|
||||
Reference in New Issue
Block a user