This commit is contained in:
callebtc
2022-10-17 11:03:39 +02:00
parent dafcfef5be
commit 161d49a450
6 changed files with 22 additions and 338 deletions

View File

@@ -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():

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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