Wallets/unhashed_description (#870)

* new argument: unhashed_description

* accept in api

* set unhashed_description for memo case

* bolt11.py: dont be like CLN, accept the hash

* send hash to lnd in b64

* fix cln

* skip descr_hash for cln

* skip

* format
This commit is contained in:
calle 2022-08-13 14:29:04 +02:00 committed by GitHub
parent 3457ff101e
commit e5d8c500d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 101 additions and 21 deletions

View File

@ -216,7 +216,7 @@ def lnencode(addr, privkey):
expirybits = expirybits[5:]
data += tagged("x", expirybits)
elif k == "h":
data += tagged_bytes("h", hashlib.sha256(v.encode("utf-8")).digest())
data += tagged_bytes("h", v)
elif k == "n":
data += tagged_bytes("n", v)
else:

View File

@ -54,6 +54,7 @@ async def create_invoice(
amount: int, # in satoshis
memo: str,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
extra: Optional[Dict] = None,
webhook: Optional[str] = None,
internal: Optional[bool] = False,
@ -65,7 +66,10 @@ async def create_invoice(
wallet = FAKE_WALLET if internal else WALLET
ok, checking_id, payment_request, error_message = await wallet.create_invoice(
amount=amount, memo=invoice_memo, description_hash=description_hash
amount=amount,
memo=invoice_memo,
description_hash=description_hash,
unhashed_description=unhashed_description,
)
if not ok:
raise InvoiceFailure(error_message or "unexpected backend error.")

View File

@ -141,6 +141,7 @@ class CreateInvoiceData(BaseModel):
memo: Optional[str] = None
unit: Optional[str] = "sat"
description_hash: Optional[str] = None
unhashed_description: Optional[str] = None
lnurl_callback: Optional[str] = None
lnurl_balance_check: Optional[str] = None
extra: Optional[dict] = None
@ -152,9 +153,15 @@ class CreateInvoiceData(BaseModel):
async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
if data.description_hash:
description_hash = unhexlify(data.description_hash)
unhashed_description = b""
memo = ""
elif data.unhashed_description:
unhashed_description = unhexlify(data.unhashed_description)
description_hash = b""
memo = ""
else:
description_hash = b""
unhashed_description = b""
memo = data.memo or LNBITS_SITE_TITLE
if data.unit == "sat":
amount = int(data.amount)
@ -170,6 +177,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
amount=amount,
memo=memo,
description_hash=description_hash,
unhashed_description=unhashed_description,
extra=data.extra,
webhook=data.webhook,
internal=data.internal,

View File

@ -46,12 +46,19 @@ class ClicheWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
) -> InvoiceResponse:
if description_hash:
description_hash_hashed = hashlib.sha256(description_hash).hexdigest()
if unhashed_description or description_hash:
description_hash_str = (
description_hash.hex()
if description_hash
else hashlib.sha256(unhashed_description).hexdigest()
if unhashed_description
else None
)
ws = create_connection(self.endpoint)
ws.send(
f"create-invoice --msatoshi {amount*1000} --description_hash {description_hash_hashed}"
f"create-invoice --msatoshi {amount*1000} --description_hash {description_hash_str}"
)
r = ws.recv()
else:

View File

@ -82,21 +82,24 @@ class CoreLightningWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
) -> InvoiceResponse:
label = "lbl{}".format(random.random())
msat: int = int(amount * 1000)
try:
if description_hash and not self.supports_description_hash:
if description_hash:
raise Unsupported("description_hash")
if unhashed_description and not self.supports_description_hash:
raise Unsupported("unhashed_description")
r = self.ln.invoice(
msatoshi=msat,
label=label,
description=description_hash.decode("utf-8")
if description_hash
description=unhashed_description.decode("utf-8")
if unhashed_description
else memo,
exposeprivatechannels=True,
deschashonly=True
if description_hash
if unhashed_description
else False, # we can't pass None here
)

View File

@ -69,11 +69,14 @@ class EclairWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
) -> InvoiceResponse:
data: Dict = {"amountMsat": amount * 1000}
if description_hash:
data["description_hash"] = hashlib.sha256(description_hash).hexdigest()
data["description_hash"] = description_hash.hex()
elif unhashed_description:
data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest()
else:
data["description"] = memo or ""

View File

@ -35,6 +35,7 @@ class FakeWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
) -> InvoiceResponse:
# we set a default secret since FakeWallet is used for internal=True invoices
# and the user might not have configured a secret yet
@ -61,7 +62,10 @@ class FakeWallet(Wallet):
data["timestamp"] = datetime.now().timestamp()
if description_hash:
data["tags_set"] = ["h"]
data["description_hash"] = description_hash.decode("utf-8")
data["description_hash"] = description_hash
elif unhashed_description:
data["tags_set"] = ["d"]
data["description_hash"] = hashlib.sha256(unhashed_description).digest()
else:
data["tags_set"] = ["d"]
data["memo"] = memo

View File

@ -57,10 +57,13 @@ class LNbitsWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
) -> InvoiceResponse:
data: Dict = {"out": False, "amount": amount}
if description_hash:
data["description_hash"] = hashlib.sha256(description_hash).hexdigest()
data["description_hash"] = description_hash.hex()
elif unhashed_description:
data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest()
else:
data["memo"] = memo or ""

View File

@ -134,14 +134,15 @@ class LndWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
) -> InvoiceResponse:
params: Dict = {"value": amount, "expiry": 600, "private": True}
if description_hash:
params["description_hash"] = description_hash
elif unhashed_description:
params["description_hash"] = hashlib.sha256(
description_hash
unhashed_description
).digest() # as bytes directly
else:
params["memo"] = memo or ""

View File

@ -73,11 +73,17 @@ class LndRestWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
) -> InvoiceResponse:
data: Dict = {"value": amount, "private": True}
if description_hash:
data["description_hash"] = base64.b64encode(description_hash).decode(
"ascii"
)
elif unhashed_description:
data["description_hash"] = base64.b64encode(
hashlib.sha256(description_hash).digest()
hashlib.sha256(unhashed_description).digest()
).decode("ascii")
else:
data["memo"] = memo or ""

View File

@ -52,10 +52,14 @@ class LNPayWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
) -> InvoiceResponse:
data: Dict = {"num_satoshis": f"{amount}"}
if description_hash:
data["description_hash"] = hashlib.sha256(description_hash).hexdigest()
data["description_hash"] = description_hash.hex()
elif unhashed_description:
data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest()
else:
data["memo"] = memo or ""

View File

@ -52,10 +52,14 @@ class LntxbotWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
) -> InvoiceResponse:
data: Dict = {"amt": str(amount)}
if description_hash:
data["description_hash"] = hashlib.sha256(description_hash).hexdigest()
data["description_hash"] = description_hash.hex()
elif unhashed_description:
data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest()
else:
data["memo"] = memo or ""

View File

@ -54,8 +54,10 @@ class OpenNodeWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
) -> InvoiceResponse:
if description_hash:
if description_hash or unhashed_description:
raise Unsupported("description_hash")
async with httpx.AsyncClient() as client:

View File

@ -93,6 +93,8 @@ class SparkWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
) -> InvoiceResponse:
label = "lbs{}".format(random.random())
checking_id = label
@ -102,7 +104,13 @@ class SparkWallet(Wallet):
r = await self.invoicewithdescriptionhash(
msatoshi=amount * 1000,
label=label,
description_hash=hashlib.sha256(description_hash).hexdigest(),
description_hash=description_hash.hex(),
)
elif unhashed_description:
r = await self.invoicewithdescriptionhash(
msatoshi=amount * 1000,
label=label,
description_hash=hashlib.sha256(unhashed_description).hexdigest(),
)
else:
r = await self.invoice(

View File

@ -18,6 +18,7 @@ class VoidWallet(Wallet):
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
**kwargs,
) -> InvoiceResponse:
raise Unsupported("")

View File

@ -11,6 +11,7 @@ from lnbits.core.views.api import (
api_payment,
api_payments_create_invoice,
)
from lnbits.settings import wallet_class
from ...helpers import get_random_invoice_data
@ -192,11 +193,32 @@ async def test_api_payment_with_key(invoice, inkey_headers_from):
# check POST /api/v1/payments: invoice creation with a description hash
@pytest.mark.skipif(
wallet_class.__name__ in ["CoreLightningWallet"],
reason="wallet does not support description_hash",
)
@pytest.mark.asyncio
async def test_create_invoice_with_description_hash(client, inkey_headers_to):
data = await get_random_invoice_data()
descr_hash = hashlib.sha256("asdasdasd".encode("utf-8")).hexdigest()
data["description_hash"] = "asdasdasd".encode("utf-8").hex()
data["description_hash"] = descr_hash
response = await client.post(
"/api/v1/payments", json=data, headers=inkey_headers_to
)
invoice = response.json()
invoice_bolt11 = bolt11.decode(invoice["payment_request"])
assert invoice_bolt11.description_hash == descr_hash
assert invoice_bolt11.description is None
return invoice
@pytest.mark.asyncio
async def test_create_invoice_with_unhashed_description(client, inkey_headers_to):
data = await get_random_invoice_data()
descr_hash = hashlib.sha256("asdasdasd".encode("utf-8")).hexdigest()
data["unhashed_description"] = "asdasdasd".encode("utf-8").hex()
response = await client.post(
"/api/v1/payments", json=data, headers=inkey_headers_to