From 21e06b7065590bdca430bbba26fcda0fbf9fa2a8 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 13 Jun 2023 17:19:23 +0200 Subject: [PATCH] lnpeer: new payment secret, derived without preimage. (this is needed for hold invoices) --- electrum/lnpeer.py | 8 ++++++-- electrum/lnutil.py | 1 + electrum/lnworker.py | 10 ++++++---- electrum/tests/test_lnpeer.py | 2 ++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index 0b3b2561e..8dc0ebe0b 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -1826,8 +1826,12 @@ class Peer(Logger): raise exc_incorrect_or_unknown_pd preimage = self.lnworker.get_preimage(htlc.payment_hash) if payment_secret_from_onion: - if payment_secret_from_onion != derive_payment_secret_from_payment_preimage(preimage): - log_fail_reason(f'incorrect payment secret {payment_secret_from_onion.hex()} != {derive_payment_secret_from_payment_preimage(preimage).hex()}') + expected_payment_secrets = [self.lnworker.get_payment_secret(htlc.payment_hash)] + if preimage: + # legacy secret for old invoices + expected_payment_secrets.append(derive_payment_secret_from_payment_preimage(preimage)) + if payment_secret_from_onion not in expected_payment_secrets: + log_fail_reason(f'incorrect payment secret {payment_secret_from_onion.hex()} != {expected_payment_secrets[0].hex()}') raise exc_incorrect_or_unknown_pd invoice_msat = info.amount_msat if not (invoice_msat is None or invoice_msat <= total_msat <= 2 * invoice_msat): diff --git a/electrum/lnutil.py b/electrum/lnutil.py index 93ec18b1e..2c3b6b1bb 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -1538,6 +1538,7 @@ class LnKeyFamily(IntEnum): REVOCATION_ROOT = 5 | BIP32_PRIME NODE_KEY = 6 BACKUP_CIPHER = 7 | BIP32_PRIME + PAYMENT_SECRET_KEY = 8 | BIP32_PRIME def generate_keypair(node: BIP32Node, key_family: LnKeyFamily) -> Keypair: diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 8ece6a28e..16af82587 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -217,6 +217,7 @@ class LNWorker(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): self.lock = threading.RLock() self.node_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.NODE_KEY) self.backup_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.BACKUP_CIPHER).privkey + self.payment_secret_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.PAYMENT_SECRET_KEY).privkey self._peers = {} # type: Dict[bytes, Peer] # pubkey -> Peer # needs self.lock self.taskgroup = OldTaskGroup() self.listen_server = None # type: Optional[asyncio.AbstractServer] @@ -1824,9 +1825,7 @@ class LNWallet(LNWorker): routing_hints, trampoline_hints = self.calc_routing_hints_for_invoice(amount_msat, channels=channels) self.logger.info(f"creating bolt11 invoice with routing_hints: {routing_hints}") invoice_features = self.features.for_invoice() - payment_preimage = self.get_preimage(payment_hash) - if payment_preimage is None: # e.g. when export/importing requests between wallets - raise Exception("missing preimage for payment_hash") + payment_secret = self.get_payment_secret(payment_hash) amount_btc = amount_msat/Decimal(COIN*1000) if amount_msat else None if expiry == 0: expiry = LN_EXPIRY_NEVER @@ -1843,12 +1842,15 @@ class LNWallet(LNWorker): + routing_hints + trampoline_hints, date=timestamp, - payment_secret=derive_payment_secret_from_payment_preimage(payment_preimage)) + payment_secret=payment_secret) invoice = lnencode(lnaddr, self.node_keypair.privkey) pair = lnaddr, invoice self._bolt11_cache[payment_hash] = pair return pair + def get_payment_secret(self, payment_hash): + return sha256(sha256(self.payment_secret_key) + payment_hash) + def create_payment_info(self, *, amount_msat: Optional[int], write_to_disk=True) -> bytes: payment_preimage = os.urandom(32) payment_hash = sha256(payment_preimage) diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py index 99051c516..4fab6b85c 100644 --- a/electrum/tests/test_lnpeer.py +++ b/electrum/tests/test_lnpeer.py @@ -138,6 +138,7 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): Logger.__init__(self) NetworkRetryManager.__init__(self, max_retry_delay_normal=1, init_retry_delay_normal=1) self.node_keypair = local_keypair + self.payment_secret_key = os.urandom(256) # does not need to be deterministic in tests self._user_dir = tempfile.mkdtemp(prefix="electrum-lnpeer-test-") self.config = SimpleConfig({}, read_user_dir_function=lambda: self._user_dir) self.network = MockNetwork(tx_queue, config=self.config) @@ -239,6 +240,7 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): full_path=full_path)] get_payments = LNWallet.get_payments + get_payment_secret = LNWallet.get_payment_secret get_payment_info = LNWallet.get_payment_info save_payment_info = LNWallet.save_payment_info set_invoice_status = LNWallet.set_invoice_status