diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md new file mode 100644 index 00000000..7b9bd721 --- /dev/null +++ b/lnbits/extensions/boltcards/README.md @@ -0,0 +1,11 @@ +
+
+
+curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/boltcards -d '{"amount":"100","memo":"boltcards"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"
diff --git a/lnbits/extensions/boltcards/__init__.py b/lnbits/extensions/boltcards/__init__.py
new file mode 100644
index 00000000..69326708
--- /dev/null
+++ b/lnbits/extensions/boltcards/__init__.py
@@ -0,0 +1,19 @@
+from fastapi import APIRouter
+
+from lnbits.db import Database
+from lnbits.helpers import template_renderer
+
+db = Database("ext_boltcards")
+
+boltcards_ext: APIRouter = APIRouter(
+ prefix="/boltcards",
+ tags=["boltcards"]
+)
+
+
+def boltcards_renderer():
+ return template_renderer(["lnbits/extensions/boltcards/templates"])
+
+
+from .views import * # noqa
+from .views_api import * # noqa
diff --git a/lnbits/extensions/boltcards/config.json b/lnbits/extensions/boltcards/config.json
new file mode 100644
index 00000000..ef98a35a
--- /dev/null
+++ b/lnbits/extensions/boltcards/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Bolt Cards",
+ "short_description": "Self custody Bolt Cards with one time LNURLw",
+ "icon": "payment",
+ "contributors": ["iwarpbtc"]
+}
diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py
new file mode 100644
index 00000000..e8fb5477
--- /dev/null
+++ b/lnbits/extensions/boltcards/crud.py
@@ -0,0 +1,91 @@
+from optparse import Option
+from typing import List, Optional, Union
+from lnbits.helpers import urlsafe_short_hash
+
+from . import db
+from .models import Card, CreateCardData
+
+async def create_card(
+ data: CreateCardData, wallet_id: str
+) -> Card:
+ card_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO boltcards.cards (
+ id,
+ wallet,
+ card_name,
+ uid,
+ counter,
+ withdraw,
+ file_key,
+ meta_key
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ card_id,
+ wallet_id,
+ data.name,
+ data.uid,
+ data.counter,
+ data.withdraw,
+ data.file_key,
+ data.meta_key,
+ ),
+ )
+ link = await get_card(card_id, 0)
+ assert link, "Newly created card couldn't be retrieved"
+ return link
+
+async def update_card(card_id: str, **kwargs) -> Optional[Card]:
+ if "is_unique" in kwargs:
+ kwargs["is_unique"] = int(kwargs["is_unique"])
+ q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ await db.execute(
+ f"UPDATE boltcards.cards SET {q} WHERE id = ?",
+ (*kwargs.values(), card_id),
+ )
+ row = await db.fetchone(
+ "SELECT * FROM boltcards.cards WHERE id = ?", (card_id,)
+ )
+ return Card(**row) if row else None
+
+async def get_cards(wallet_ids: Union[str, List[str]]) -> List[Card]:
+ if isinstance(wallet_ids, str):
+ wallet_ids = [wallet_ids]
+
+ q = ",".join(["?"] * len(wallet_ids))
+ rows = await db.fetchall(
+ f"SELECT * FROM boltcards.cards WHERE wallet IN ({q})", (*wallet_ids,)
+ )
+
+ return [Card(**row) for row in rows]
+
+async def get_all_cards() -> List[Card]:
+ rows = await db.fetchall(
+ f"SELECT * FROM boltcards.cards"
+ )
+
+ return [Card(**row) for row in rows]
+
+async def get_card(card_id: str, id_is_uid: bool=False) -> Optional[Card]:
+ sql = "SELECT * FROM boltcards.cards WHERE {} = ?".format("uid" if id_is_uid else "id")
+ row = await db.fetchone(
+ sql, card_id,
+ )
+ if not row:
+ return None
+
+ card = dict(**row)
+
+ return Card.parse_obj(card)
+
+async def delete_card(card_id: str) -> None:
+ await db.execute("DELETE FROM boltcards.cards WHERE id = ?", (card_id,))
+
+async def update_card_counter(counter: int, id: str):
+ await db.execute(
+ "UPDATE boltcards.cards SET counter = ? WHERE id = ?",
+ (counter, id),
+ )
\ No newline at end of file
diff --git a/lnbits/extensions/boltcards/migrations.py b/lnbits/extensions/boltcards/migrations.py
new file mode 100644
index 00000000..eedbb5d3
--- /dev/null
+++ b/lnbits/extensions/boltcards/migrations.py
@@ -0,0 +1,20 @@
+from lnbits.helpers import urlsafe_short_hash
+
+async def m001_initial(db):
+ await db.execute(
+ """
+ CREATE TABLE boltcards.cards (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ card_name TEXT NOT NULL,
+ uid TEXT NOT NULL,
+ counter INT NOT NULL DEFAULT 0,
+ withdraw TEXT NOT NULL,
+ file_key TEXT NOT NULL DEFAULT '00000000000000000000000000000000',
+ meta_key TEXT NOT NULL DEFAULT '',
+ time TIMESTAMP NOT NULL DEFAULT """
+ + db.timestamp_now
+ + """
+ );
+ """
+ )
diff --git a/lnbits/extensions/boltcards/models.py b/lnbits/extensions/boltcards/models.py
new file mode 100644
index 00000000..6ef25d0c
--- /dev/null
+++ b/lnbits/extensions/boltcards/models.py
@@ -0,0 +1,21 @@
+from pydantic import BaseModel
+from fastapi.params import Query
+
+class Card(BaseModel):
+ id: str
+ wallet: str
+ card_name: str
+ uid: str
+ counter: int
+ withdraw: str
+ file_key: str
+ meta_key: str
+ time: int
+
+class CreateCardData(BaseModel):
+ card_name: str = Query(...)
+ uid: str = Query(...)
+ counter: str = Query(...)
+ withdraw: str = Query(...)
+ file_key: str = Query(...)
+ meta_key: str = Query(...)
\ No newline at end of file
diff --git a/lnbits/extensions/boltcards/nxp424.py b/lnbits/extensions/boltcards/nxp424.py
new file mode 100644
index 00000000..a67b896f
--- /dev/null
+++ b/lnbits/extensions/boltcards/nxp424.py
@@ -0,0 +1,31 @@
+from typing import Tuple
+from Cryptodome.Hash import CMAC
+from Cryptodome.Cipher import AES
+
+SV2 = "3CC300010080"
+
+def myCMAC(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
+
+ cipher = AES.new(key, AES.MODE_CBC, IVbytes)
+ sun_plain = cipher.decrypt(sun)
+
+ UID = sun_plain[1:8]
+ counter = sun_plain[8:11]
+
+ return UID, counter
+
+def getSunMAC(UID: bytes, counter: bytes, key: bytes) -> bytes:
+ sv2prefix = bytes.fromhex(SV2)
+ sv2bytes = sv2prefix + UID + counter
+
+ mac1 = myCMAC(key, sv2bytes)
+ mac2 = myCMAC(mac1)
+
+ return mac2[1::2]
diff --git a/lnbits/extensions/boltcards/templates/boltcards/_api_docs.html b/lnbits/extensions/boltcards/templates/boltcards/_api_docs.html
new file mode 100644
index 00000000..f4939255
--- /dev/null
+++ b/lnbits/extensions/boltcards/templates/boltcards/_api_docs.html
@@ -0,0 +1,27 @@
+
+ Manage your Bolt Cards self custodian way
+
+ More details
+
+
+ Created by,
+ iWarp
+