mirror of
https://github.com/lnbits/lnbits.git
synced 2025-08-03 07:22:28 +02:00
clean up
This commit is contained in:
@@ -18,7 +18,6 @@ cashu_static_files = [
|
||||
"name": "cashu_static",
|
||||
}
|
||||
]
|
||||
sys.path.append("/Users/cc/git/cashu")
|
||||
from cashu.mint.ledger import Ledger
|
||||
|
||||
ledger = Ledger(
|
||||
@@ -28,7 +27,7 @@ ledger = Ledger(
|
||||
derivation_path="0/0/0/1",
|
||||
)
|
||||
|
||||
cashu_ext: APIRouter = APIRouter(prefix="/api/v1/cashu", tags=["cashu"])
|
||||
cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
|
||||
|
||||
|
||||
def cashu_renderer():
|
||||
|
@@ -1,88 +0,0 @@
|
||||
# Don't trust me with cryptography.
|
||||
|
||||
"""
|
||||
Implementation of https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406
|
||||
Alice:
|
||||
A = a*G
|
||||
return A
|
||||
Bob:
|
||||
Y = hash_to_curve(secret_message)
|
||||
r = random blinding factor
|
||||
B'= Y + r*G
|
||||
return B'
|
||||
Alice:
|
||||
C' = a*B'
|
||||
(= a*Y + a*r*G)
|
||||
return C'
|
||||
Bob:
|
||||
C = C' - r*A
|
||||
(= C' - a*r*G)
|
||||
(= a*Y)
|
||||
return C, secret_message
|
||||
Alice:
|
||||
Y = hash_to_curve(secret_message)
|
||||
C == a*Y
|
||||
If true, C must have originated from Alice
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
|
||||
from secp256k1 import PrivateKey, PublicKey
|
||||
|
||||
|
||||
def hash_to_curve(message: bytes):
|
||||
"""Generates a point from the message hash and checks if the point lies on the curve.
|
||||
If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
|
||||
point = None
|
||||
msg_to_hash = message
|
||||
while point is None:
|
||||
try:
|
||||
_hash = hashlib.sha256(msg_to_hash).digest()
|
||||
point = PublicKey(b"\x02" + _hash, raw=True)
|
||||
except:
|
||||
msg_to_hash = _hash
|
||||
return point
|
||||
|
||||
|
||||
def step1_alice(secret_msg):
|
||||
secret_msg = secret_msg
|
||||
Y = hash_to_curve(secret_msg)
|
||||
r = PrivateKey()
|
||||
B_ = Y + r.pubkey
|
||||
return B_, r
|
||||
|
||||
|
||||
def step2_bob(B_, a):
|
||||
C_ = B_.mult(a)
|
||||
return C_
|
||||
|
||||
|
||||
def step3_alice(C_, r, A):
|
||||
C = C_ - A.mult(r)
|
||||
return C
|
||||
|
||||
|
||||
def verify(a, C, secret_msg):
|
||||
Y = hash_to_curve(secret_msg)
|
||||
return C == Y.mult(a)
|
||||
|
||||
|
||||
### Below is a test of a simple positive and negative case
|
||||
|
||||
# # Alice's keys
|
||||
# a = PrivateKey()
|
||||
# A = a.pubkey
|
||||
# secret_msg = "test"
|
||||
# B_, r = step1_alice(secret_msg)
|
||||
# C_ = step2_bob(B_, a)
|
||||
# C = step3_alice(C_, r, A)
|
||||
# print("C:{}, secret_msg:{}".format(C, secret_msg))
|
||||
# assert verify(a, C, secret_msg)
|
||||
# assert verify(a, C + C, secret_msg) == False # adding C twice shouldn't pass
|
||||
# assert verify(a, A, secret_msg) == False # A shouldn't pass
|
||||
|
||||
# # Test operations
|
||||
# b = PrivateKey()
|
||||
# B = b.pubkey
|
||||
# assert -A -A + A == -A # neg
|
||||
# assert B.mult(a) == A.mult(b) # a*B = A*b
|
@@ -1,168 +0,0 @@
|
||||
from sqlite3 import Row
|
||||
from typing import List, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class CashuError(BaseException):
|
||||
code = "000"
|
||||
error = "CashuError"
|
||||
|
||||
|
||||
class P2SHScript(BaseModel):
|
||||
script: str
|
||||
signature: str
|
||||
address: Union[str, None] = None
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
return cls(
|
||||
address=row[0],
|
||||
script=row[1],
|
||||
signature=row[2],
|
||||
used=row[3],
|
||||
)
|
||||
|
||||
|
||||
class Proof(BaseModel):
|
||||
amount: int
|
||||
secret: str = ""
|
||||
C: str
|
||||
script: Union[P2SHScript, None] = None
|
||||
reserved: bool = False # whether this proof is reserved for sending
|
||||
send_id: str = "" # unique ID of send attempt
|
||||
time_created: str = ""
|
||||
time_reserved: str = ""
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
return cls(
|
||||
amount=row[0],
|
||||
C=row[1],
|
||||
secret=row[2],
|
||||
reserved=row[3] or False,
|
||||
send_id=row[4] or "",
|
||||
time_created=row[5] or "",
|
||||
time_reserved=row[6] or "",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
assert "amount" in d, "no amount in proof"
|
||||
return cls(
|
||||
amount=d.get("amount"),
|
||||
C=d.get("C"),
|
||||
secret=d.get("secret") or "",
|
||||
reserved=d.get("reserved") or False,
|
||||
send_id=d.get("send_id") or "",
|
||||
time_created=d.get("time_created") or "",
|
||||
time_reserved=d.get("time_reserved") or "",
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
return dict(amount=self.amount, secret=self.secret, C=self.C)
|
||||
|
||||
def to_dict_no_secret(self):
|
||||
return dict(amount=self.amount, C=self.C)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__getattribute__(key)
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
self.__setattr__(key, val)
|
||||
|
||||
|
||||
class Proofs(BaseModel):
|
||||
"""TODO: Use this model"""
|
||||
|
||||
proofs: List[Proof]
|
||||
|
||||
|
||||
class Invoice(BaseModel):
|
||||
amount: int
|
||||
pr: str
|
||||
hash: str
|
||||
issued: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
return cls(
|
||||
cashu_id=str(row[0]),
|
||||
amount=int(row[1]),
|
||||
pr=str(row[2]),
|
||||
hash=str(row[3]),
|
||||
issued=bool(row[4]),
|
||||
)
|
||||
|
||||
|
||||
class BlindedMessage(BaseModel):
|
||||
amount: int
|
||||
B_: str
|
||||
|
||||
|
||||
class BlindedSignature(BaseModel):
|
||||
amount: int
|
||||
C_: str
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
return cls(
|
||||
amount=d["amount"],
|
||||
C_=d["C_"],
|
||||
)
|
||||
|
||||
|
||||
class MintRequest(BaseModel):
|
||||
blinded_messages: List[BlindedMessage] = []
|
||||
|
||||
|
||||
class GetMintResponse(BaseModel):
|
||||
pr: str
|
||||
hash: str
|
||||
|
||||
|
||||
class GetMeltResponse(BaseModel):
|
||||
paid: Union[bool, None]
|
||||
preimage: Union[str, None]
|
||||
|
||||
|
||||
class SplitRequest(BaseModel):
|
||||
proofs: List[Proof]
|
||||
amount: int
|
||||
output_data: Union[
|
||||
MintRequest, None
|
||||
] = None # backwards compatibility with clients < v0.2.2
|
||||
outputs: Union[MintRequest, None] = None
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
self.backwards_compatibility_v021()
|
||||
|
||||
def backwards_compatibility_v021(self):
|
||||
# before v0.2.2: output_data, after: outputs
|
||||
if self.output_data:
|
||||
self.outputs = self.output_data
|
||||
self.output_data = None
|
||||
|
||||
|
||||
class PostSplitResponse(BaseModel):
|
||||
fst: List[BlindedSignature]
|
||||
snd: List[BlindedSignature]
|
||||
|
||||
|
||||
class CheckRequest(BaseModel):
|
||||
proofs: List[Proof]
|
||||
|
||||
|
||||
class CheckFeesRequest(BaseModel):
|
||||
pr: str
|
||||
|
||||
|
||||
class CheckFeesResponse(BaseModel):
|
||||
fee: Union[int, None]
|
||||
|
||||
|
||||
class MeltRequest(BaseModel):
|
||||
proofs: List[Proof]
|
||||
amount: int = None # deprecated
|
||||
invoice: str
|
@@ -1,52 +0,0 @@
|
||||
from secp256k1 import PrivateKey, PublicKey
|
||||
|
||||
|
||||
# We extend the public key to define some operations on points
|
||||
# Picked from https://github.com/WTRMQDev/secp256k1-zkp-py/blob/master/secp256k1_zkp/__init__.py
|
||||
class PublicKeyExt(PublicKey):
|
||||
def __add__(self, pubkey2):
|
||||
if isinstance(pubkey2, PublicKey):
|
||||
new_pub = PublicKey()
|
||||
new_pub.combine([self.public_key, pubkey2.public_key])
|
||||
return new_pub
|
||||
else:
|
||||
raise TypeError("Cant add pubkey and %s" % pubkey2.__class__)
|
||||
|
||||
def __neg__(self):
|
||||
serialized = self.serialize()
|
||||
first_byte, remainder = serialized[:1], serialized[1:]
|
||||
# flip odd/even byte
|
||||
first_byte = {b"\x03": b"\x02", b"\x02": b"\x03"}[first_byte]
|
||||
return PublicKey(first_byte + remainder, raw=True)
|
||||
|
||||
def __sub__(self, pubkey2):
|
||||
if isinstance(pubkey2, PublicKey):
|
||||
return self + (-pubkey2)
|
||||
else:
|
||||
raise TypeError("Can't add pubkey and %s" % pubkey2.__class__)
|
||||
|
||||
def mult(self, privkey):
|
||||
if isinstance(privkey, PrivateKey):
|
||||
return self.tweak_mul(privkey.private_key)
|
||||
else:
|
||||
raise TypeError("Can't multiply with non privatekey")
|
||||
|
||||
def __eq__(self, pubkey2):
|
||||
if isinstance(pubkey2, PublicKey):
|
||||
seq1 = self.to_data()
|
||||
seq2 = pubkey2.to_data()
|
||||
return seq1 == seq2
|
||||
else:
|
||||
raise TypeError("Can't compare pubkey and %s" % pubkey2.__class__)
|
||||
|
||||
def to_data(self):
|
||||
return [self.public_key.data[i] for i in range(64)]
|
||||
|
||||
|
||||
# Horrible monkeypatching
|
||||
PublicKey.__add__ = PublicKeyExt.__add__
|
||||
PublicKey.__neg__ = PublicKeyExt.__neg__
|
||||
PublicKey.__sub__ = PublicKeyExt.__sub__
|
||||
PublicKey.mult = PublicKeyExt.mult
|
||||
PublicKey.__eq__ = PublicKeyExt.__eq__
|
||||
PublicKey.to_data = PublicKeyExt.to_data
|
@@ -1,8 +0,0 @@
|
||||
def amount_split(amount):
|
||||
"""Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
|
||||
bits_amt = bin(amount)[::-1][:-2]
|
||||
rv = []
|
||||
for (pos, bit) in enumerate(bits_amt):
|
||||
if bit == "1":
|
||||
rv.append(2**pos)
|
||||
return rv
|
@@ -64,7 +64,7 @@ LIGHTNING = False
|
||||
########################################
|
||||
|
||||
# todo: use /mints
|
||||
@cashu_ext.get("/cashus", status_code=HTTPStatus.OK)
|
||||
@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK)
|
||||
async def api_cashus(
|
||||
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
):
|
||||
@@ -75,7 +75,7 @@ async def api_cashus(
|
||||
return [cashu.dict() for cashu in await get_cashus(wallet_ids)]
|
||||
|
||||
|
||||
@cashu_ext.post("/cashus", status_code=HTTPStatus.CREATED)
|
||||
@cashu_ext.post("/api/v1/cashus", status_code=HTTPStatus.CREATED)
|
||||
async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
cashu_id = urlsafe_short_hash()
|
||||
# generate a new keyset in cashu
|
||||
@@ -93,7 +93,7 @@ async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key
|
||||
#######################################
|
||||
|
||||
|
||||
@cashu_ext.get("/{cashu_id}/keys", status_code=HTTPStatus.OK)
|
||||
@cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
|
||||
async def keys(cashu_id: str = Query(None)) -> dict[int, str]:
|
||||
"""Get the public keys of the mint"""
|
||||
cashu: Union[Cashu, None] = await get_cashu(cashu_id)
|
||||
@@ -106,7 +106,7 @@ async def keys(cashu_id: str = Query(None)) -> dict[int, str]:
|
||||
return ledger.get_keyset(keyset_id=cashu.keyset_id)
|
||||
|
||||
|
||||
@cashu_ext.get("/{cashu_id}/mint")
|
||||
@cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
|
||||
async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintResponse:
|
||||
"""
|
||||
Request minting of new tokens. The mint responds with a Lightning invoice.
|
||||
@@ -144,7 +144,7 @@ async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintR
|
||||
return resp
|
||||
|
||||
|
||||
@cashu_ext.post("/{cashu_id}/mint")
|
||||
@cashu_ext.post("/api/v1/cashu/{cashu_id}/mint")
|
||||
async def mint_coins(
|
||||
data: MintRequest,
|
||||
cashu_id: str = Query(None),
|
||||
@@ -203,7 +203,7 @@ async def mint_coins(
|
||||
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
|
||||
@cashu_ext.post("/{cashu_id}/melt")
|
||||
@cashu_ext.post("/api/v1/cashu/{cashu_id}/melt")
|
||||
async def melt_coins(
|
||||
payload: MeltRequest, cashu_id: str = Query(None)
|
||||
) -> GetMeltResponse:
|
||||
@@ -258,13 +258,13 @@ async def melt_coins(
|
||||
return GetMeltResponse(paid=status.paid, preimage=status.preimage)
|
||||
|
||||
|
||||
@cashu_ext.post("/check")
|
||||
@cashu_ext.post("/api/v1/check")
|
||||
async def check_spendable(payload: CheckRequest) -> Dict[int, bool]:
|
||||
"""Check whether a secret has been spent already or not."""
|
||||
return await ledger.check_spendable(payload.proofs)
|
||||
|
||||
|
||||
@cashu_ext.post("/checkfees")
|
||||
@cashu_ext.post("/api/v1/checkfees")
|
||||
async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
|
||||
"""
|
||||
Responds with the fees necessary to pay a Lightning invoice.
|
||||
@@ -281,7 +281,7 @@ async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
|
||||
return CheckFeesResponse(fee=fees_msat / 1000)
|
||||
|
||||
|
||||
@cashu_ext.post("/{cashu_id}/split")
|
||||
@cashu_ext.post("/api/v1/cashu/{cashu_id}/split")
|
||||
async def split(
|
||||
payload: SplitRequest, cashu_id: str = Query(None)
|
||||
) -> PostSplitResponse:
|
||||
@@ -299,6 +299,7 @@ async def split(
|
||||
outputs = payload.outputs.blinded_messages
|
||||
# backwards compatibility with clients < v0.2.2
|
||||
assert outputs, Exception("no outputs provided.")
|
||||
split_return = None
|
||||
try:
|
||||
split_return = await ledger.split(proofs, amount, outputs, cashu.keyset_id)
|
||||
except Exception as exc:
|
||||
@@ -316,7 +317,7 @@ async def split(
|
||||
return resp
|
||||
|
||||
|
||||
# @cashu_ext.post("/api/v1/cashus/upodatekeys", status_code=HTTPStatus.CREATED)
|
||||
# @cashu_ext.post("/api/v1s/upodatekeys", status_code=HTTPStatus.CREATED)
|
||||
# async def api_cashu_update_keys(
|
||||
# data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
# ):
|
||||
@@ -327,7 +328,7 @@ async def split(
|
||||
# return cashu.dict()
|
||||
|
||||
|
||||
# @cashu_ext.delete("/api/v1/cashus/{cashu_id}")
|
||||
# @cashu_ext.delete("/api/v1s/{cashu_id}")
|
||||
# async def api_cashu_delete(
|
||||
# cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||
# ):
|
||||
@@ -348,7 +349,7 @@ async def split(
|
||||
# ########################################
|
||||
# #################????###################
|
||||
# ########################################
|
||||
# @cashu_ext.post("/api/v1/cashus/{cashu_id}/invoices", status_code=HTTPStatus.CREATED)
|
||||
# @cashu_ext.post("/api/v1s/{cashu_id}/invoices", status_code=HTTPStatus.CREATED)
|
||||
# async def api_cashu_create_invoice(
|
||||
# amount: int = Query(..., ge=1), tipAmount: int = None, cashu_id: str = None
|
||||
# ):
|
||||
@@ -376,7 +377,7 @@ async def split(
|
||||
|
||||
|
||||
# @cashu_ext.post(
|
||||
# "/api/v1/cashus/{cashu_id}/invoices/{payment_request}/pay",
|
||||
# "/api/v1s/{cashu_id}/invoices/{payment_request}/pay",
|
||||
# status_code=HTTPStatus.OK,
|
||||
# )
|
||||
# async def api_cashu_pay_invoice(
|
||||
@@ -437,7 +438,7 @@ async def split(
|
||||
|
||||
|
||||
# @cashu_ext.get(
|
||||
# "/api/v1/cashus/{cashu_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK
|
||||
# "/api/v1s/{cashu_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK
|
||||
# )
|
||||
# async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
|
||||
# cashu = await get_cashu(cashu_id)
|
||||
@@ -459,7 +460,7 @@ async def split(
|
||||
# ########################################
|
||||
|
||||
|
||||
# # @cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
|
||||
# # @cashu_ext.get("/api/v1/{cashu_id}/keys", status_code=HTTPStatus.OK)
|
||||
# # async def keys(cashu_id: str = Query(False)):
|
||||
# # """Get the public keys of the mint"""
|
||||
# # mint = await get_cashu(cashu_id)
|
||||
@@ -470,7 +471,7 @@ async def split(
|
||||
# # return get_pubkeys(mint.prvkey)
|
||||
|
||||
|
||||
# @cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
|
||||
# @cashu_ext.get("/api/v1/{cashu_id}/mint")
|
||||
# async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
|
||||
# """Request minting of tokens. Server responds with a Lightning invoice."""
|
||||
|
||||
@@ -498,7 +499,7 @@ async def split(
|
||||
# return {"pr": payment_request, "hash": payment_hash}
|
||||
|
||||
|
||||
# @cashu_ext.post("/api/v1/cashu/{cashu_id}/mint")
|
||||
# @cashu_ext.post("/api/v1/{cashu_id}/mint")
|
||||
# async def mint_coins(
|
||||
# data: MintPayloads,
|
||||
# cashu_id: str = Query(None),
|
||||
@@ -560,7 +561,7 @@ async def split(
|
||||
# raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
|
||||
# @cashu_ext.post("/api/v1/cashu/{cashu_id}/melt")
|
||||
# @cashu_ext.post("/api/v1/{cashu_id}/melt")
|
||||
# async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
|
||||
# """Invalidates proofs and pays a Lightning invoice."""
|
||||
# cashu: Cashu = await get_cashu(cashu_id)
|
||||
@@ -576,12 +577,12 @@ async def split(
|
||||
# raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
|
||||
# @cashu_ext.post("/api/v1/cashu/{cashu_id}/check")
|
||||
# @cashu_ext.post("/api/v1/{cashu_id}/check")
|
||||
# async def check_spendable_coins(payload: CheckPayload, cashu_id: str = Query(None)):
|
||||
# return await check_spendable(payload.proofs, cashu_id)
|
||||
|
||||
|
||||
# @cashu_ext.post("/api/v1/cashu/{cashu_id}/split")
|
||||
# @cashu_ext.post("/api/v1/{cashu_id}/split")
|
||||
# async def split_proofs(payload: SplitRequest, cashu_id: str = Query(None)):
|
||||
# """
|
||||
# Requetst a set of tokens with amount "total" to be split into two
|
||||
|
Reference in New Issue
Block a user