This commit is contained in:
benarc 2022-01-30 10:41:20 +00:00
parent b4d00a490b
commit c96e7068e5
4 changed files with 207 additions and 1 deletions

View file

@ -30,7 +30,7 @@ LNBITS_SITE_DESCRIPTION="Some description about your service, will display if ti
LNBITS_THEME_OPTIONS="mint, flamingo, classic, autumn, monochrome, salvador" LNBITS_THEME_OPTIONS="mint, flamingo, classic, autumn, monochrome, salvador"
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, LndWallet (gRPC), # Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, LndWallet (gRPC),
# LndRestWallet, CLightningWallet, LNbitsWallet, SparkWallet # LndRestWallet, CLightningWallet, LNbitsWallet, SparkWallet, FakeWallet
LNBITS_BACKEND_WALLET_CLASS=VoidWallet LNBITS_BACKEND_WALLET_CLASS=VoidWallet
# VoidWallet is just a fallback that works without any actual Lightning capabilities, # VoidWallet is just a fallback that works without any actual Lightning capabilities,
# just so you can see the UI before dealing with this file. # just so you can see the UI before dealing with this file.

View file

@ -116,6 +116,139 @@ def decode(pr: str) -> Invoice:
return invoice return invoice
def encode(options):
""" Convert options into LnAddr and pass it to the encoder
"""
addr = LnAddr()
addr.currency = options.currency
addr.fallback = options.fallback if options.fallback else None
if options.amount:
addr.amount = options.amount
if options.timestamp:
addr.date = int(options.timestamp)
addr.paymenthash = unhexlify(options.paymenthash)
if options.description:
addr.tags.append(('d', options.description))
if options.description_hashed:
addr.tags.append(('h', options.description_hashed))
if options.expires:
addr.tags.append(('x', options.expires))
if options.fallback:
addr.tags.append(('f', options.fallback))
for r in options.route:
splits = r.split('/')
route=[]
while len(splits) >= 5:
route.append((unhexlify(splits[0]),
unhexlify(splits[1]),
int(splits[2]),
int(splits[3]),
int(splits[4])))
splits = splits[5:]
assert(len(splits) == 0)
addr.tags.append(('r', route))
return lnencode(addr, options.privkey)
def lnencode(addr, privkey):
if addr.amount:
amount = Decimal(str(addr.amount))
# We can only send down to millisatoshi.
if amount * 10**12 % 10:
raise ValueError("Cannot encode {}: too many decimal places".format(
addr.amount))
amount = addr.currency + shorten_amount(amount)
else:
amount = addr.currency if addr.currency else ''
hrp = 'ln' + amount
# Start with the timestamp
data = bitstring.pack('uint:35', addr.date)
# Payment hash
data += tagged_bytes('p', addr.paymenthash)
tags_set = set()
for k, v in addr.tags:
# BOLT #11:
#
# A writer MUST NOT include more than one `d`, `h`, `n` or `x` fields,
if k in ('d', 'h', 'n', 'x'):
if k in tags_set:
raise ValueError("Duplicate '{}' tag".format(k))
if k == 'r':
route = bitstring.BitArray()
for step in v:
pubkey, channel, feebase, feerate, cltv = step
route.append(bitstring.BitArray(pubkey) + bitstring.BitArray(channel) + bitstring.pack('intbe:32', feebase) + bitstring.pack('intbe:32', feerate) + bitstring.pack('intbe:16', cltv))
data += tagged('r', route)
elif k == 'f':
data += encode_fallback(v, addr.currency)
elif k == 'd':
data += tagged_bytes('d', v.encode())
elif k == 'x':
# Get minimal length by trimming leading 5 bits at a time.
expirybits = bitstring.pack('intbe:64', v)[4:64]
while expirybits.startswith('0b00000'):
expirybits = expirybits[5:]
data += tagged('x', expirybits)
elif k == 'h':
data += tagged_bytes('h', hashlib.sha256(v.encode('utf-8')).digest())
elif k == 'n':
data += tagged_bytes('n', v)
else:
# FIXME: Support unknown tags?
raise ValueError("Unknown tag {}".format(k))
tags_set.add(k)
# BOLT #11:
#
# A writer MUST include either a `d` or `h` field, and MUST NOT include
# both.
if 'd' in tags_set and 'h' in tags_set:
raise ValueError("Cannot include both 'd' and 'h'")
if not 'd' in tags_set and not 'h' in tags_set:
raise ValueError("Must include either 'd' or 'h'")
# We actually sign the hrp, then data (padded to 8 bits with zeroes).
privkey = secp256k1.PrivateKey(bytes(unhexlify(privkey)))
sig = privkey.ecdsa_sign_recoverable(bytearray([ord(c) for c in hrp]) + data.tobytes())
# This doesn't actually serialize, but returns a pair of values :(
sig, recid = privkey.ecdsa_recoverable_serialize(sig)
data += bytes(sig) + bytes([recid])
return bech32_encode(hrp, bitarray_to_u5(data))
class LnAddr(object):
def __init__(self, paymenthash=None, amount=None, currency='bc', tags=None, date=None):
self.date = int(time.time()) if not date else int(date)
self.tags = [] if not tags else tags
self.unknown_tags = []
self.paymenthash=paymenthash
self.signature = None
self.pubkey = None
self.currency = currency
self.amount = amount
def __str__(self):
return "LnAddr[{}, amount={}{} tags=[{}]]".format(
hexlify(self.pubkey.serialize()).decode('utf-8'),
self.amount, self.currency,
", ".join([k + '=' + str(v) for k, v in self.tags])
)
def _unshorten_amount(amount: str) -> int: def _unshorten_amount(amount: str) -> int:
"""Given a shortened amount, return millisatoshis""" """Given a shortened amount, return millisatoshis"""
# BOLT #11: # BOLT #11:

View file

@ -9,3 +9,4 @@ from .lnpay import LNPayWallet
from .lnbits import LNbitsWallet from .lnbits import LNbitsWallet
from .lndrest import LndRestWallet from .lndrest import LndRestWallet
from .spark import SparkWallet from .spark import SparkWallet
from .fake import FakeWallet

72
lnbits/wallets/fake.py Normal file
View file

@ -0,0 +1,72 @@
import asyncio
import json
import httpx
from os import getenv
from typing import Optional, Dict, AsyncGenerator
import hashlib
from ..bolt11 import encode
from .base import (
StatusResponse,
InvoiceResponse,
PaymentResponse,
PaymentStatus,
Wallet,
)
class FakeWallet(Wallet):
"""https://github.com/lnbits/lnbits"""
async def status(self) -> StatusResponse:
print("This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits.")
return StatusResponse(
None,
21000000000,
)
async def create_invoice(
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> InvoiceResponse:
options.amount = amount
options.timestamp = datetime.now().timestamp()
randomHash = hashlib.sha256(b"some random data").hexdigest()
options.payments_hash = hex(randomHash)
options.privkey = "v3qrevqrevm39qin0vq3r0ivmrewvmq3rimq03ig"
if description_hash:
options.description_hashed = description_hash
else:
options.memo = memo
payment_request = encode(options)
checking_id = randomHash
return InvoiceResponse(ok, checking_id, payment_request)
async def pay_invoice(self, bolt11: str) -> PaymentResponse:
return ""
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
return ""
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
return ""
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
url = f"{self.endpoint}/api/v1/payments/sse"
print("lost connection to lnbits /payments/sse, retrying in 5 seconds")
await asyncio.sleep(5)
#invoice = "lnbc"
#invoice += str(data.amount) + "m1"
#invoice += str(datetime.now().timestamp()).to_bytes(35, byteorder='big'))
#invoice += str(hashlib.sha256(b"some random data").hexdigest()) # hash of preimage, can be fake as invoice handled internally
#invoice += "dpl" # d then pl (p = 1, l = 31; 1 * 32 + 31 == 63)
#invoice += "2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq" #description, how do I encode this?
#invoice += str(hashlib.sha224("lnbc" + str(data.amount) + "m1").hexdigest())