Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: code quality #39

Merged
merged 2 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: lint
on:
push:
branches:
- main
pull_request:

jobs:
lint:
uses: lnbits/lnbits/.github/workflows/lint.yml@dev
15 changes: 7 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- 'v[0-9]+.[0-9]+.[0-9]+'

jobs:

release:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -34,12 +33,12 @@ jobs:
- name: Create pull request in extensions repo
env:
GH_TOKEN: ${{ secrets.EXT_GITHUB }}
repo_name: "${{ github.event.repository.name }}"
tag: "${{ github.ref_name }}"
branch: "update-${{ github.event.repository.name }}-${{ github.ref_name }}"
title: "[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}"
body: "https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}"
archive: "https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip"
repo_name: '${{ github.event.repository.name }}'
tag: '${{ github.ref_name }}'
branch: 'update-${{ github.event.repository.name }}-${{ github.ref_name }}'
title: '[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}'
body: 'https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}'
archive: 'https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip'
run: |
cd lnbits-extensions
git checkout -b $branch
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
__pycache__
node_modules
.mypy_cache
.venv
12 changes: 12 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"semi": false,
"arrowParens": "avoid",
"insertPragma": false,
"printWidth": 80,
"proseWrap": "preserve",
"singleQuote": true,
"trailingComma": "none",
"useTabs": false,
"bracketSameLine": false,
"bracketSpacing": false
}
47 changes: 47 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
all: format check

format: prettier black ruff

check: mypy pyright checkblack checkruff checkprettier

prettier:
poetry run ./node_modules/.bin/prettier --write .
pyright:
poetry run ./node_modules/.bin/pyright

mypy:
poetry run mypy .

black:
poetry run black .

ruff:
poetry run ruff check . --fix

checkruff:
poetry run ruff check .

checkprettier:
poetry run ./node_modules/.bin/prettier --check .

checkblack:
poetry run black --check .

checkeditorconfig:
editorconfig-checker

test:
PYTHONUNBUFFERED=1 \
DEBUG=true \
poetry run pytest
install-pre-commit-hook:
@echo "Installing pre-commit hook to git"
@echo "Uninstall the hook with poetry run pre-commit uninstall"
poetry run pre-commit install

pre-commit:
poetry run pre-commit run --all-files


checkbundle:
@echo "skipping checkbundle"
57 changes: 27 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,60 @@
# Bolt Cards - <small>[LNbits](https://github.com/lnbits/lnbits) extension</small>
<small>For more about LNBits extensions check [this tutorial](https://youtu.be/_sW7miqaXJc)</small>

<small>For more about LNBits extensions check [this tutorial](https://youtu.be/_sW7miqaXJc)</small>

This extension allows you to link your [Bolt Card](https://github.com/boltcard) on a NXP NTAG424 DNA tag with a LNbits hub that generated new links on each tab which allows a better privacy and security than a static LNURLw that you can also write to a NFC tag (fromon NTAG 213) in the withdraw-extension e.g. for one-time usage as a gift-card.
This extension allows you to link your [Bolt Card](https://github.com/boltcard) on a NXP NTAG424 DNA tag with a LNbits hub that generated new links on each tab which allows a better privacy and security than a static LNURLw that you can also write to a NFC tag (fromon NTAG 213) in the withdraw-extension e.g. for one-time usage as a gift-card.

<a class="text-secondary" href="https://youtu.be/_sW7miqaXJc">Video Tutorial</a>


**Disclaimer:** ***Use this only if you either know what you are doing or are a reckless lightning pioneer.
Only you are responsible for all your sats, cards and other devices. Always backup all your card keys!***

**Disclaimer:** **_Use this only if you either know what you are doing or are a reckless lightning pioneer.
Only you are responsible for all your sats, cards and other devices. Always backup all your card keys!_**

For the easy way you need:

* an LNbits instance in clearnet
* opened on Android in Chrome browser
* Boltcard extension installed for your LNbits wallet
* [Boltcard NFC Card Creator App](https://github.com/boltcard/bolt-nfc-android-app) from the [Apple-](https://apps.apple.com/us/app/boltcard-nfc-programmer/id6450968873) or [Play-Store](https://play.google.com/store/search?q=bolt+card+nfc+card+creator&c=apps) to write your keys to the tags once they were generated on LNbits
- an LNbits instance in clearnet
- opened on Android in Chrome browser
- Boltcard extension installed for your LNbits wallet
- [Boltcard NFC Card Creator App](https://github.com/boltcard/bolt-nfc-android-app) from the [Apple-](https://apps.apple.com/us/app/boltcard-nfc-programmer/id6450968873) or [Play-Store](https://play.google.com/store/search?q=bolt+card+nfc+card+creator&c=apps) to write your keys to the tags once they were generated on LNbits

If you want to gift a Boltcard, make sure to [include the following data](https://www.figma.com/proto/OH6aGCxH45vNpKsZ2nD96S/Untitled?node-id=6%3A37&scaling=min-zoom&page-id=0%3A1) in your present, so that the user is able to make full use of it.

***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!***

**_Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!_**

## Setting the card - Boltcard NFC Card Creator (easy way)

- Add new card in the extension.
- Set a max sats per transaction. Any transaction greater than this amount will be rejected. This is usually set higher than the funds in the wallet are to prevent accidential withdraws.
- Set a max sats per day. After the card spends this amount of sats in a day, additional transactions will be rejected.
- Set a card name. This is just for your reference inside LNbits.
- Set the card UID. This is the unique identifier of your NFC card and is 7 bytes.
- If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field.
- Otherwise read it with the Bolt-Card app (Read NFC) and paste it to the field.
- Advanced Options
- Card Keys (k0, k1, k2) will be automatically generated if not explicitly set.
- Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state (this is unsecure).
- GENERATE KEY button fill the keys randomly.
- Click CREATE CARD button
- Set a max sats per transaction. Any transaction greater than this amount will be rejected. This is usually set higher than the funds in the wallet are to prevent accidential withdraws.
- Set a max sats per day. After the card spends this amount of sats in a day, additional transactions will be rejected.
- Set a card name. This is just for your reference inside LNbits.
- Set the card UID. This is the unique identifier of your NFC card and is 7 bytes.
- If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field.
- Otherwise read it with the Bolt-Card app (Read NFC) and paste it to the field.
- Advanced Options
- Card Keys (k0, k1, k2) will be automatically generated if not explicitly set.
- Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state (this is unsecure).
- GENERATE KEY button fill the keys randomly.
- Click CREATE CARD button
- Click the QR code button next to a card to view its details. Backup the keys now! They'll be comfortable in your password manager.
- Now you can scan the QR code with the Boltcard app (Create Bolt Card -> SCAN QR CODE).
- Or the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. Then paste it into the Android app (Create Bolt Card -> PASTE AUTH URL).
- Now you can scan the QR code with the Boltcard app (Create Bolt Card -> SCAN QR CODE).
- Or the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. Then paste it into the Android app (Create Bolt Card -> PASTE AUTH URL).
- Click WRITE CARD NOW and approach the NFC card to set it up. DO NOT REMOVE THE CARD PREMATURELY!

## Erasing the card - Boltcard NFC Card Creator

Updated for v0.1.9

Since v0.1.2 of Boltcard NFC Card Creator it is possible not only to reset the keys but also to disable the SUN function and do the complete erase so the card can be used again as a static tag (or set as a new Bolt Card, ofc).

- In the Boltcard extension click the QR code button next to a card to view its details and select WIPE
- OR click the red cross icon on the right side to reach the same
- In the Boltcard app (Reset Keys)
- Click SCAN QR CODE to scan the QR
- Or click WIPE DATA in LNbits to copy and paste in to the app (PASTE KEY JSON)
- Click SCAN QR CODE to scan the QR
- Or click WIPE DATA in LNbits to copy and paste in to the app (PASTE KEY JSON)
- Click RESET CARD NOW and approach the NFC card to erase it. DO NOT REMOVE THE CARD PREMATURELY!
- Now if all is successful the card can be safely deleted from LNbits (but keep the keys backuped anyway; batter safe than brick).

If you somehow find yourself in some non-standard state (for instance only k3 and k4 remains filled after previous unsuccessful reset), then you need to edit the key fields manually (for instance leave k0-k2 to zeroes and provide the right k3 and k4).


## Setting the card (advanced)

A technology called [Secure Unique NFC](https://web.archive.org/web/20220706134959/https://mishka-scan.com/blog/secure-unique-nfc) is utilized in this workflow.
Expand All @@ -74,8 +71,8 @@ The key #00, K0 (also know as auth key) is used as authentification key. It is n

### The writing process

There's also a more [advanced guide](https://www.whitewolftech.com/articles/payment-card/) to set cards up manually with a card reader connected to your computer.
Writing can also be done (without setting the keys) via the [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) on Android.
There's also a more [advanced guide](https://www.whitewolftech.com/articles/payment-card/) to set cards up manually with a card reader connected to your computer.
Writing can also be done (without setting the keys) via the [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) on Android.

The URI should be `lnurlw://YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{YOUR_card_external_id}?p=00000000000000000000000000000000&c=0000000000000000`

Expand Down
33 changes: 18 additions & 15 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import asyncio
from loguru import logger

from fastapi import APIRouter
from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import create_permanent_unique_task
from loguru import logger

db = Database("ext_boltcards")
from .crud import db
from .tasks import wait_for_paid_invoices
from .views import boltcards_generic_router
from .views_api import boltcards_api_router
from .views_lnurl import boltcards_lnurl_router

boltcards_static_files = [
{
Expand All @@ -16,14 +17,9 @@
]

boltcards_ext: APIRouter = APIRouter(prefix="/boltcards", tags=["boltcards"])


def boltcards_renderer():
return template_renderer(["boltcards/templates"])


from .lnurl import * # noqa: F401,F403
from .tasks import * # noqa: F401,F403
boltcards_ext.include_router(boltcards_generic_router)
boltcards_ext.include_router(boltcards_api_router)
boltcards_ext.include_router(boltcards_lnurl_router)

scheduled_tasks: list[asyncio.Task] = []

Expand All @@ -37,9 +33,16 @@ def boltcards_stop():


def boltcards_start():
from lnbits.tasks import create_permanent_unique_task

task = create_permanent_unique_task("ext_boltcards", wait_for_paid_invoices)
scheduled_tasks.append(task)


from .views import * # noqa: F401,F403
from .views_api import * # noqa: F401,F403
__all__ = [
"db",
"boltcards_ext",
"boltcards_static_files",
"boltcards_start",
"boltcards_stop",
]
24 changes: 13 additions & 11 deletions crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
from datetime import datetime
from typing import List, Optional

from lnbits.db import Database
from lnbits.helpers import urlsafe_short_hash

from . import db
from .models import Card, CreateCardData, Hit, Refund

db = Database("ext_boltcards")


async def create_card(data: CreateCardData, wallet_id: str) -> Card:
card_id = urlsafe_short_hash().upper()
Expand Down Expand Up @@ -137,25 +139,25 @@ async def delete_card(card_id: str) -> None:
)


async def update_card_counter(counter: int, id: str):
async def update_card_counter(counter: int, card_id: str):
await db.execute(
"UPDATE boltcards.cards SET counter = ? WHERE id = ?",
(counter, id),
(counter, card_id),
)


async def enable_disable_card(enable: bool, id: str) -> Optional[Card]:
async def enable_disable_card(enable: bool, card_id: str) -> Optional[Card]:
await db.execute(
"UPDATE boltcards.cards SET enable = ? WHERE id = ?",
(enable, id),
(enable, card_id),
)
return await get_card(id)
return await get_card(card_id)


async def update_card_otp(otp: str, id: str):
async def update_card_otp(otp: str, card_id: str):
await db.execute(
"UPDATE boltcards.cards SET otp = ? WHERE id = ?",
(otp, id),
(otp, card_id),
)


Expand Down Expand Up @@ -194,12 +196,12 @@ async def get_hits_today(card_id: str) -> List[Hit]:
return [Hit(**row) for row in updatedrow]


async def spend_hit(id: str, amount: int):
async def spend_hit(card_id: str, amount: int):
await db.execute(
"UPDATE boltcards.hits SET spent = ?, amount = ? WHERE id = ?",
(True, amount, id),
(True, amount, card_id),
)
return await get_hit(id)
return await get_hit(card_id)


async def create_hit(card_id, ip, useragent, old_ctr, new_ctr) -> Hit:
Expand Down
8 changes: 4 additions & 4 deletions description.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ This extension enables you to link your Bolt Card to an NXP NTAG424 DNA tag via

For a simpler setup, you will need:

* An LNbits instance accessible over clearnet.
* Google Chrome browser opened on Android.
* Boltcard extension installed on your LNbits wallet.
* Boltcard NFC Card Creator App, available in the Apple or Play Store, to write your keys to the tags after they have been generated on LNbits.
- An LNbits instance accessible over clearnet.
- Google Chrome browser opened on Android.
- Boltcard extension installed on your LNbits wallet.
- Boltcard NFC Card Creator App, available in the Apple or Play Store, to write your keys to the tags after they have been generated on LNbits.
4 changes: 3 additions & 1 deletion models.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ def from_row(cls, row: Row) -> "Card":
return cls(**dict(row))

def lnurl(self, req: Request) -> Lnurl:
url = str(req.url_for("boltcard.lnurl_response", device_id=self.id, _external=True))
url = str(
req.url_for("boltcard.lnurl_response", device_id=self.id, _external=True)
)
return lnurl_encode(url)

async def lnurlpay_metadata(self) -> LnurlPayMetadata:
Expand Down
21 changes: 10 additions & 11 deletions nxp424.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
# https://www.nxp.com/docs/en/application-note/AN12196.pdf
from typing import Tuple

from Cryptodome.Cipher import AES
from Cryptodome.Hash import CMAC

SV2 = "3CC300010080"


def myCMAC(key: bytes, msg: bytes = b"") -> bytes:
def my_cmac(key: bytes, msg: bytes = b"") -> bytes:
cobj = CMAC.new(key, ciphermod=AES)
if msg != b"":
cobj.update(msg)
return cobj.digest()


def decryptSUN(sun: bytes, key: bytes) -> Tuple[bytes, bytes]:
IVbytes = b"\x00" * 16
def decrypt_sun(sun: bytes, key: bytes) -> tuple[bytes, bytes]:
ivbytes = b"\x00" * 16

cipher = AES.new(key, AES.MODE_CBC, IVbytes)
cipher = AES.new(key, AES.MODE_CBC, ivbytes)
sun_plain = cipher.decrypt(sun)

UID = sun_plain[1:8]
uid = sun_plain[1:8]
counter = sun_plain[8:11]

return UID, counter
return uid, counter


def getSunMAC(UID: bytes, counter: bytes, key: bytes) -> bytes:
def get_sun_mac(uid: bytes, counter: bytes, key: bytes) -> bytes:
sv2prefix = bytes.fromhex(SV2)
sv2bytes = sv2prefix + UID + counter
sv2bytes = sv2prefix + uid + counter

mac1 = myCMAC(key, sv2bytes)
mac2 = myCMAC(mac1)
mac1 = my_cmac(key, sv2bytes)
mac2 = my_cmac(mac1)

return mac2[1::2]
Loading