feat: return promises
This commit is contained in:
parent
337ccf5459
commit
2c8ebaf225
6 changed files with 158 additions and 46 deletions
93
lnbits/extensions/cashu/core/b_dhke.py
Normal file
93
lnbits/extensions/cashu/core/b_dhke.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
# Don't trust me with cryptography.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Implementation of https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406
|
||||||
|
Alice:
|
||||||
|
A = a*G
|
||||||
|
return A
|
||||||
|
Bob:
|
||||||
|
Y = hash_to_point(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_point(secret_message)
|
||||||
|
C == a*Y
|
||||||
|
If true, C must have originated from Alice
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from secp256k1 import PrivateKey, PublicKey
|
||||||
|
|
||||||
|
|
||||||
|
def hash_to_point(secret_msg):
|
||||||
|
"""Generates x coordinate 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 = secret_msg
|
||||||
|
while point is None:
|
||||||
|
_hash = hashlib.sha256(msg).hexdigest().encode("utf-8")
|
||||||
|
try:
|
||||||
|
# We construct compressed pub which has x coordinate encoded with even y
|
||||||
|
_hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes
|
||||||
|
_hash[0] = 0x02 # set first byte to represent even y coord
|
||||||
|
_hash = bytes(_hash)
|
||||||
|
point = PublicKey(_hash, raw=True)
|
||||||
|
except:
|
||||||
|
msg = _hash
|
||||||
|
|
||||||
|
return point
|
||||||
|
|
||||||
|
|
||||||
|
def step1_alice(secret_msg):
|
||||||
|
secret_msg = secret_msg.encode("utf-8")
|
||||||
|
Y = hash_to_point(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_point(secret_msg.encode("utf-8"))
|
||||||
|
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
|
||||||
|
|
@ -93,7 +93,7 @@ async def delete_cashu(cashu_id) -> None:
|
||||||
##########################################
|
##########################################
|
||||||
|
|
||||||
|
|
||||||
async def store_promise(amount: int, B_: str, C_: str, cashu_id):
|
async def store_promise(amount: int, B_: str, C_: str, cashu_id: str):
|
||||||
promise_id = urlsafe_short_hash()
|
promise_id = urlsafe_short_hash()
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
from .mint_helper import derive_keys, derive_pubkeys
|
from typing import List
|
||||||
from .models import Cashu
|
|
||||||
|
from .core.b_dhke import step2_bob
|
||||||
|
from .core.base import BlindedSignature
|
||||||
|
from .core.secp import PublicKey
|
||||||
|
from .mint_helper import derive_key, derive_keys, derive_pubkeys
|
||||||
|
|
||||||
|
# todo: extract const
|
||||||
|
MAX_ORDER = 64
|
||||||
|
|
||||||
|
|
||||||
def get_pubkeys(xpriv: str):
|
def get_pubkeys(xpriv: str):
|
||||||
|
|
@ -11,22 +18,24 @@ def get_pubkeys(xpriv: str):
|
||||||
return {a: p.serialize().hex() for a, p in pub_keys.items()}
|
return {a: p.serialize().hex() for a, p in pub_keys.items()}
|
||||||
|
|
||||||
|
|
||||||
# async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None):
|
async def generate_promises(
|
||||||
# """Mints a promise for coins for B_."""
|
master_prvkey: str, amounts: List[int], B_s: List[PublicKey]
|
||||||
# # check if lightning invoice was paid
|
):
|
||||||
# if LIGHTNING:
|
"""Mints a promise for coins for B_."""
|
||||||
# try:
|
|
||||||
# paid = await self._check_lightning_invoice(payment_hash)
|
|
||||||
# except:
|
|
||||||
# raise Exception("could not check invoice.")
|
|
||||||
# if not paid:
|
|
||||||
# raise Exception("Lightning invoice not paid yet.")
|
|
||||||
|
|
||||||
# for amount in amounts:
|
for amount in amounts:
|
||||||
# if amount not in [2**i for i in range(MAX_ORDER)]:
|
if amount not in [2**i for i in range(MAX_ORDER)]:
|
||||||
# raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
|
raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
|
||||||
|
|
||||||
# promises = [
|
promises = [
|
||||||
# await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
|
await generate_promise(master_prvkey, amount, B_)
|
||||||
# ]
|
for B_, amount in zip(B_s, amounts)
|
||||||
# return promises
|
]
|
||||||
|
return promises
|
||||||
|
|
||||||
|
|
||||||
|
async def generate_promise(master_prvkey: str, amount: int, B_: PublicKey):
|
||||||
|
"""Generates a promise for given amount and returns a pair (amount, C')."""
|
||||||
|
secret_key = derive_key(master_prvkey, amount) # Get the correct key
|
||||||
|
C_ = step2_bob(B_, secret_key)
|
||||||
|
return BlindedSignature(amount=amount, C_=C_.serialize().hex())
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
from typing import List, Set
|
from typing import List
|
||||||
|
|
||||||
from .core.secp import PrivateKey, PublicKey
|
from .core.secp import PrivateKey
|
||||||
|
|
||||||
# todo: extract const
|
# todo: extract const
|
||||||
MAX_ORDER = 64
|
MAX_ORDER = 64
|
||||||
|
|
@ -9,16 +9,17 @@ MAX_ORDER = 64
|
||||||
|
|
||||||
def derive_keys(master_key: str):
|
def derive_keys(master_key: str):
|
||||||
"""Deterministic derivation of keys for 2^n values."""
|
"""Deterministic derivation of keys for 2^n values."""
|
||||||
return {
|
return {2**i: derive_key(master_key, i) for i in range(MAX_ORDER)}
|
||||||
2
|
|
||||||
** i: PrivateKey(
|
|
||||||
hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
|
def derive_key(master_key: str, i: int):
|
||||||
.hexdigest()
|
"""Deterministic derivation of keys for a particular value."""
|
||||||
.encode("utf-8")[:32],
|
return PrivateKey(
|
||||||
raw=True,
|
hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
|
||||||
)
|
.hexdigest()
|
||||||
for i in range(MAX_ORDER)
|
.encode("utf-8")[:32],
|
||||||
}
|
raw=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def derive_pubkeys(keys: List[PrivateKey]):
|
def derive_pubkeys(keys: List[PrivateKey]):
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,8 @@ class MeltPayload(BaseModel):
|
||||||
amount: int
|
amount: int
|
||||||
invoice: str
|
invoice: str
|
||||||
|
|
||||||
|
|
||||||
class CreateTokens(BaseModel):
|
class CreateTokens(BaseModel):
|
||||||
# cashu_id: str = Query(None)
|
# cashu_id: str = Query(None)
|
||||||
payloads: MintPayloads
|
payloads: MintPayloads
|
||||||
payment_hash: Union[str, None] = None
|
payment_hash: Union[str, None] = None
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,10 @@ from .crud import (
|
||||||
get_cashus,
|
get_cashus,
|
||||||
get_lightning_invoice,
|
get_lightning_invoice,
|
||||||
store_lightning_invoice,
|
store_lightning_invoice,
|
||||||
|
store_promise,
|
||||||
)
|
)
|
||||||
from .ledger import mint, request_mint
|
from .ledger import mint, request_mint
|
||||||
from .mint import get_pubkeys
|
from .mint import generate_promises, get_pubkeys
|
||||||
from .models import (
|
from .models import (
|
||||||
Cashu,
|
Cashu,
|
||||||
CheckPayload,
|
CheckPayload,
|
||||||
|
|
@ -280,22 +281,29 @@ async def mint_coins(
|
||||||
# if invoice.issued == True:
|
# if invoice.issued == True:
|
||||||
# todo: give old tokens?
|
# todo: give old tokens?
|
||||||
|
|
||||||
status: PaymentStatus = await check_transaction_status(cashu.wallet, data.payment_hash)
|
status: PaymentStatus = await check_transaction_status(
|
||||||
if status.paid != True:
|
cashu.wallet, data.payment_hash
|
||||||
|
)
|
||||||
|
# todo: revert to: status.paid != True:
|
||||||
|
if status.paid == False:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
|
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
|
||||||
)
|
)
|
||||||
|
|
||||||
# amounts = []
|
amounts = []
|
||||||
# B_s = []
|
B_s = []
|
||||||
# for payload in payloads.blinded_messages:
|
for payload in data.payloads.blinded_messages:
|
||||||
# amounts.append(payload.amount)
|
amounts.append(payload.amount)
|
||||||
# B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
|
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
|
||||||
# try:
|
print("### amounts", amounts)
|
||||||
# promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
|
print("### B_s", B_s)
|
||||||
# return promises
|
|
||||||
# except Exception as exc:
|
try:
|
||||||
# return CashuError(error=str(exc))
|
promises = await generate_promises(cashu.prvkey, amounts, B_s)
|
||||||
|
# await store_promise(amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), cashu_id)
|
||||||
|
return promises
|
||||||
|
except Exception as exc:
|
||||||
|
return CashuError(error=str(exc))
|
||||||
|
|
||||||
|
|
||||||
@cashu_ext.post("/melt")
|
@cashu_ext.post("/melt")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue