mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-03 12:30:07 +00:00
make key derivation reasonable
no more hardcoded secrets, no more key-reuse
This commit is contained in:
parent
0a67872e83
commit
3b938160d1
5 changed files with 78 additions and 63 deletions
|
@ -766,7 +766,7 @@ class Commands:
|
|||
|
||||
@command('wn')
|
||||
def nodeid(self):
|
||||
return bh2u(self.wallet.lnworker.pubkey)
|
||||
return bh2u(self.wallet.lnworker.node_keypair.pubkey)
|
||||
|
||||
@command('wn')
|
||||
def listchannels(self):
|
||||
|
|
|
@ -82,7 +82,7 @@ class ChannelsList(MyTreeWidget):
|
|||
vbox = QVBoxLayout(d)
|
||||
h = QGridLayout()
|
||||
local_nodeid = QLineEdit()
|
||||
local_nodeid.setText(bh2u(lnworker.pubkey))
|
||||
local_nodeid.setText(bh2u(lnworker.node_keypair.pubkey))
|
||||
local_nodeid.setReadOnly(True)
|
||||
local_nodeid.setCursorPosition(0)
|
||||
remote_nodeid = QLineEdit()
|
||||
|
|
|
@ -4,38 +4,34 @@
|
|||
Derived from https://gist.github.com/AdamISZ/046d05c156aaeb56cc897f85eecb3eb8
|
||||
"""
|
||||
|
||||
from collections import namedtuple, defaultdict, OrderedDict, defaultdict
|
||||
from .lnutil import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore
|
||||
from .lnutil import sign_and_get_sig_string, funding_output_script, get_ecdh, get_per_commitment_secret_from_seed
|
||||
from .lnutil import secret_to_pubkey, LNPeerAddr, PaymentFailure
|
||||
from .lnutil import LOCAL, REMOTE, HTLCOwner
|
||||
from .bitcoin import COIN
|
||||
|
||||
from ecdsa.util import sigdecode_der, sigencode_string_canonize, sigdecode_string
|
||||
import queue
|
||||
from collections import OrderedDict, defaultdict
|
||||
import json
|
||||
import asyncio
|
||||
from concurrent.futures import FIRST_COMPLETED
|
||||
import os
|
||||
import time
|
||||
import binascii
|
||||
import hashlib
|
||||
import hmac
|
||||
from functools import partial
|
||||
|
||||
import cryptography.hazmat.primitives.ciphers.aead as AEAD
|
||||
import aiorpcx
|
||||
from functools import partial
|
||||
|
||||
from . import bitcoin
|
||||
from . import ecc
|
||||
from . import crypto
|
||||
from .ecc import sig_string_from_r_and_s, get_r_and_s_from_sig_string
|
||||
from .crypto import sha256
|
||||
from . import constants
|
||||
from . import transaction
|
||||
from .util import PrintError, bh2u, print_error, bfh, aiosafe
|
||||
from .transaction import opcodes, Transaction, TxOutput
|
||||
from .transaction import Transaction, TxOutput
|
||||
from .lnonion import new_onion_packet, OnionHopsDataSingle, OnionPerHop, decode_onion_error, ONION_FAILURE_CODE_MAP
|
||||
from .lnaddr import lndecode
|
||||
from .lnhtlc import HTLCStateMachine, RevokeAndAck
|
||||
from .lnutil import (Outpoint, ChannelConfig, LocalState,
|
||||
RemoteState, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore,
|
||||
funding_output_script, get_ecdh, get_per_commitment_secret_from_seed,
|
||||
secret_to_pubkey, LNPeerAddr, PaymentFailure,
|
||||
LOCAL, REMOTE, HTLCOwner, generate_keypair, LnKeyFamily)
|
||||
|
||||
|
||||
def channel_id_from_funding_tx(funding_txid, funding_index):
|
||||
funding_txid_bytes = bytes.fromhex(funding_txid)[::-1]
|
||||
|
@ -277,7 +273,7 @@ class Peer(PrintError):
|
|||
self.pubkey = pubkey
|
||||
self.peer_addr = LNPeerAddr(host, port, pubkey)
|
||||
self.lnworker = lnworker
|
||||
self.privkey = lnworker.privkey
|
||||
self.privkey = lnworker.node_keypair.privkey
|
||||
self.network = lnworker.network
|
||||
self.lnwatcher = lnworker.network.lnwatcher
|
||||
self.channel_db = lnworker.network.channel_db
|
||||
|
@ -484,50 +480,37 @@ class Peer(PrintError):
|
|||
chan.set_state('DISCONNECTED')
|
||||
self.network.trigger_callback('channel', chan)
|
||||
|
||||
def make_local_config(self, funding_sat, push_msat, initiator: HTLCOwner, password):
|
||||
# see lnd/keychain/derivation.go
|
||||
keyfamilymultisig = 0
|
||||
keyfamilyrevocationbase = 1
|
||||
keyfamilyhtlcbase = 2
|
||||
keyfamilypaymentbase = 3
|
||||
keyfamilydelaybase = 4
|
||||
keyfamilyrevocationroot = 5
|
||||
keyfamilynodekey = 6 # TODO currently unused
|
||||
def make_local_config(self, funding_sat, push_msat, initiator: HTLCOwner):
|
||||
# key derivation
|
||||
keypair_generator = lambda family, i: Keypair(*self.lnworker.wallet.keystore.get_keypair([family, i], password))
|
||||
channel_counter = self.lnworker.get_and_inc_counter_for_channel_keys()
|
||||
keypair_generator = lambda family: generate_keypair(self.lnworker.ln_keystore, family, channel_counter)
|
||||
if initiator == LOCAL:
|
||||
initial_msat = funding_sat * 1000 - push_msat
|
||||
else:
|
||||
initial_msat = push_msat
|
||||
local_config=ChannelConfig(
|
||||
payment_basepoint=keypair_generator(keyfamilypaymentbase, 0),
|
||||
multisig_key=keypair_generator(keyfamilymultisig, 0),
|
||||
htlc_basepoint=keypair_generator(keyfamilyhtlcbase, 0),
|
||||
delayed_basepoint=keypair_generator(keyfamilydelaybase, 0),
|
||||
revocation_basepoint=keypair_generator(keyfamilyrevocationbase, 0),
|
||||
payment_basepoint=keypair_generator(LnKeyFamily.PAYMENT_BASE),
|
||||
multisig_key=keypair_generator(LnKeyFamily.MULTISIG),
|
||||
htlc_basepoint=keypair_generator(LnKeyFamily.HTLC_BASE),
|
||||
delayed_basepoint=keypair_generator(LnKeyFamily.DELAY_BASE),
|
||||
revocation_basepoint=keypair_generator(LnKeyFamily.REVOCATION_BASE),
|
||||
to_self_delay=143,
|
||||
dust_limit_sat=546,
|
||||
max_htlc_value_in_flight_msat=0xffffffffffffffff,
|
||||
max_accepted_htlcs=5,
|
||||
initial_msat=initial_msat,
|
||||
)
|
||||
return local_config
|
||||
|
||||
def make_per_commitment_secret_seed(self):
|
||||
# TODO
|
||||
return 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100.to_bytes(32, 'big')
|
||||
per_commitment_secret_seed = keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey
|
||||
return local_config, per_commitment_secret_seed
|
||||
|
||||
@aiosafe
|
||||
async def channel_establishment_flow(self, password, funding_sat, push_msat, temp_channel_id, sweep_address):
|
||||
await self.initialized
|
||||
local_config = self.make_local_config(funding_sat, push_msat, LOCAL, password)
|
||||
local_config, per_commitment_secret_seed = self.make_local_config(funding_sat, push_msat, LOCAL)
|
||||
# amounts
|
||||
local_feerate = self.current_feerate_per_kw()
|
||||
# TODO derive this?
|
||||
per_commitment_secret_seed = self.make_per_commitment_secret_seed()
|
||||
per_commitment_secret_index = RevocationStore.START_INDEX
|
||||
# for the first commitment transaction
|
||||
per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, per_commitment_secret_index)
|
||||
per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, RevocationStore.START_INDEX)
|
||||
per_commitment_point_first = secret_to_pubkey(int.from_bytes(per_commitment_secret_first, 'big'))
|
||||
msg = gen_msg(
|
||||
"open_channel",
|
||||
|
@ -656,13 +639,10 @@ class Peer(PrintError):
|
|||
initial_msat=funding_sat * 1000 - push_msat,
|
||||
)
|
||||
temp_chan_id = payload['temporary_channel_id']
|
||||
password = None # TODO
|
||||
local_config = self.make_local_config(funding_sat * 1000, push_msat, REMOTE, password)
|
||||
local_config, per_commitment_secret_seed = self.make_local_config(funding_sat * 1000, push_msat, REMOTE)
|
||||
|
||||
per_commitment_secret_seed = self.make_per_commitment_secret_seed()
|
||||
per_commitment_secret_index = RevocationStore.START_INDEX
|
||||
# for the first commitment transaction
|
||||
per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, per_commitment_secret_index)
|
||||
per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, RevocationStore.START_INDEX)
|
||||
per_commitment_point_first = secret_to_pubkey(int.from_bytes(per_commitment_secret_first, 'big'))
|
||||
|
||||
min_depth = 3
|
||||
|
@ -884,7 +864,7 @@ class Peer(PrintError):
|
|||
chan.set_state("OPEN")
|
||||
self.network.trigger_callback('channel', chan)
|
||||
# add channel to database
|
||||
node_ids = [self.pubkey, self.lnworker.pubkey]
|
||||
node_ids = [self.pubkey, self.lnworker.node_keypair.pubkey]
|
||||
bitcoin_keys = [chan.local_config.multisig_key.pubkey, chan.remote_config.multisig_key.pubkey]
|
||||
sorted_node_ids = list(sorted(node_ids))
|
||||
if sorted_node_ids != node_ids:
|
||||
|
@ -931,8 +911,8 @@ class Peer(PrintError):
|
|||
)
|
||||
to_hash = chan_ann[256+2:]
|
||||
h = bitcoin.Hash(to_hash)
|
||||
bitcoin_signature = ecc.ECPrivkey(chan.local_config.multisig_key.privkey).sign(h, sigencode_string_canonize, sigdecode_string)
|
||||
node_signature = ecc.ECPrivkey(self.privkey).sign(h, sigencode_string_canonize, sigdecode_string)
|
||||
bitcoin_signature = ecc.ECPrivkey(chan.local_config.multisig_key.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string)
|
||||
node_signature = ecc.ECPrivkey(self.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string)
|
||||
self.send_message(gen_msg("announcement_signatures",
|
||||
channel_id=chan.channel_id,
|
||||
short_channel_id=chan.short_channel_id,
|
||||
|
@ -991,7 +971,7 @@ class Peer(PrintError):
|
|||
assert chan.get_state() == "OPEN", chan.get_state()
|
||||
assert amount_msat > 0, "amount_msat is not greater zero"
|
||||
height = self.network.get_local_height()
|
||||
route = self.network.path_finder.create_route_from_path(path, self.lnworker.pubkey)
|
||||
route = self.network.path_finder.create_route_from_path(path, self.lnworker.node_keypair.pubkey)
|
||||
hops_data = []
|
||||
sum_of_deltas = sum(route_edge.channel_policy.cltv_expiry_delta for route_edge in route[1:])
|
||||
total_fee = 0
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from enum import IntFlag
|
||||
from enum import IntFlag, IntEnum
|
||||
import json
|
||||
from collections import namedtuple
|
||||
from typing import NamedTuple, List, Tuple
|
||||
|
@ -14,6 +14,7 @@ from .bitcoin import push_script
|
|||
from . import segwit_addr
|
||||
from .i18n import _
|
||||
from .lnaddr import lndecode
|
||||
from .keystore import BIP32_KeyStore
|
||||
|
||||
HTLC_TIMEOUT_WEIGHT = 663
|
||||
HTLC_SUCCESS_WEIGHT = 703
|
||||
|
@ -526,3 +527,19 @@ def extract_nodeid(connect_contents: str) -> Tuple[bytes, str]:
|
|||
except:
|
||||
raise ConnStringFormatError(_('Invalid node ID, must be 33 bytes and hexadecimal'))
|
||||
return node_id, rest
|
||||
|
||||
|
||||
# key derivation
|
||||
# see lnd/keychain/derivation.go
|
||||
class LnKeyFamily(IntEnum):
|
||||
MULTISIG = 0
|
||||
REVOCATION_BASE = 1
|
||||
HTLC_BASE = 2
|
||||
PAYMENT_BASE = 3
|
||||
DELAY_BASE = 4
|
||||
REVOCATION_ROOT = 5
|
||||
NODE_KEY = 6
|
||||
|
||||
|
||||
def generate_keypair(ln_keystore: BIP32_KeyStore, key_family: LnKeyFamily, index: int) -> Keypair:
|
||||
return Keypair(*ln_keystore.get_keypair([key_family, 0, index], None))
|
||||
|
|
|
@ -12,6 +12,9 @@ import dns.resolver
|
|||
import dns.exception
|
||||
|
||||
from . import constants
|
||||
from . import keystore
|
||||
from . import bitcoin
|
||||
from .keystore import BIP32_KeyStore
|
||||
from .bitcoin import sha256, COIN
|
||||
from .util import bh2u, bfh, PrintError, InvoiceError, resolve_dns_srv, is_ip_address
|
||||
from .lnbase import Peer, privkey_to_pubkey, aiosafe
|
||||
|
@ -20,7 +23,8 @@ from .ecc import der_sig_from_sig_string
|
|||
from .lnhtlc import HTLCStateMachine
|
||||
from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr,
|
||||
get_compressed_pubkey_from_bech32, extract_nodeid,
|
||||
PaymentFailure, split_host_port, ConnStringFormatError)
|
||||
PaymentFailure, split_host_port, ConnStringFormatError,
|
||||
generate_keypair, LnKeyFamily)
|
||||
from electrum.lnaddr import lndecode
|
||||
from .i18n import _
|
||||
|
||||
|
@ -41,13 +45,8 @@ class LNWorker(PrintError):
|
|||
self.network = network
|
||||
self.channel_db = self.network.channel_db
|
||||
self.lock = threading.RLock()
|
||||
pk = wallet.storage.get('lightning_privkey')
|
||||
if pk is None:
|
||||
pk = bh2u(os.urandom(32))
|
||||
wallet.storage.put('lightning_privkey', pk)
|
||||
wallet.storage.write()
|
||||
self.privkey = bfh(pk)
|
||||
self.pubkey = privkey_to_pubkey(self.privkey)
|
||||
self.ln_keystore = self._read_ln_keystore()
|
||||
self.node_keypair = generate_keypair(self.ln_keystore, LnKeyFamily.NODE_KEY, 0)
|
||||
self.config = network.config
|
||||
self.peers = {} # pubkey -> Peer
|
||||
self.channels = {x.channel_id: x for x in map(HTLCStateMachine, wallet.storage.get("channels", []))}
|
||||
|
@ -62,6 +61,25 @@ class LNWorker(PrintError):
|
|||
self.network.register_callback(self.on_network_update, ['network_updated', 'verified', 'fee']) # thread safe
|
||||
asyncio.run_coroutine_threadsafe(self.network.main_taskgroup.spawn(self.main_loop()), self.network.asyncio_loop)
|
||||
|
||||
def _read_ln_keystore(self) -> BIP32_KeyStore:
|
||||
xprv = self.wallet.storage.get('lightning_privkey')
|
||||
if xprv is None:
|
||||
# TODO derive this deterministically from wallet.keystore at keystore generation time
|
||||
# probably along a hardened path ( lnd-equivalent would be m/1017'/coinType'/ )
|
||||
seed = os.urandom(32)
|
||||
xprv, xpub = bitcoin.bip32_root(seed, xtype='standard')
|
||||
self.wallet.storage.put('lightning_privkey', xprv)
|
||||
self.wallet.storage.write()
|
||||
return keystore.from_xprv(xprv)
|
||||
|
||||
def get_and_inc_counter_for_channel_keys(self):
|
||||
with self.lock:
|
||||
ctr = self.wallet.storage.get('lightning_channel_key_der_ctr', -1)
|
||||
ctr += 1
|
||||
self.wallet.storage.put('lightning_channel_key_der_ctr', ctr)
|
||||
self.wallet.storage.write()
|
||||
return ctr
|
||||
|
||||
def _add_peers_from_config(self):
|
||||
peer_list = self.config.get('lightning_peers', [])
|
||||
for host, port, pubkey in peer_list:
|
||||
|
@ -217,7 +235,7 @@ class LNWorker(PrintError):
|
|||
if amount_sat is None:
|
||||
raise InvoiceError(_("Missing amount"))
|
||||
amount_msat = int(amount_sat * 1000)
|
||||
path = self.network.path_finder.find_path_for_payment(self.pubkey, invoice_pubkey, amount_msat)
|
||||
path = self.network.path_finder.find_path_for_payment(self.node_keypair.pubkey, invoice_pubkey, amount_msat)
|
||||
if path is None:
|
||||
raise PaymentFailure(_("No path found"))
|
||||
node_id, short_channel_id = path[0]
|
||||
|
@ -236,7 +254,7 @@ class LNWorker(PrintError):
|
|||
payment_preimage = os.urandom(32)
|
||||
RHASH = sha256(payment_preimage)
|
||||
amount_btc = amount_sat/Decimal(COIN) if amount_sat else None
|
||||
pay_req = lnencode(LnAddr(RHASH, amount_btc, tags=[('d', message)]), self.privkey)
|
||||
pay_req = lnencode(LnAddr(RHASH, amount_btc, tags=[('d', message)]), self.node_keypair.privkey)
|
||||
self.invoices[bh2u(payment_preimage)] = pay_req
|
||||
self.wallet.storage.put('lightning_invoices', self.invoices)
|
||||
self.wallet.storage.write()
|
||||
|
|
Loading…
Add table
Reference in a new issue