mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-25 07:07:48 +01:00
99 lines
3.1 KiB
Python
99 lines
3.1 KiB
Python
import hashlib
|
|
import base64
|
|
from typing import List, Set
|
|
|
|
from .core.b_dhke import verify
|
|
from .core.base import BlindedSignature
|
|
from .core.secp import PrivateKey, PublicKey
|
|
from .core.split import amount_split
|
|
from .models import BlindedMessage, Proof
|
|
|
|
# todo: extract const
|
|
MAX_ORDER = 64
|
|
|
|
|
|
def derive_keys(master_key: str):
|
|
"""Deterministic derivation of keys for 2^n values."""
|
|
return {
|
|
2
|
|
** i: PrivateKey(
|
|
hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
|
|
.hexdigest()
|
|
.encode("utf-8")[:32],
|
|
raw=True,
|
|
)
|
|
for i in range(MAX_ORDER)
|
|
}
|
|
|
|
|
|
def derive_pubkeys(keys: List[PrivateKey]):
|
|
return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
|
|
|
|
|
|
# async required?
|
|
async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
|
|
"""Verifies that the proof of promise was issued by this ledger."""
|
|
if proof.secret in proofs_used:
|
|
raise Exception(f"tokens already spent. Secret: {proof.secret}")
|
|
|
|
secret_key = derive_keys(master_prvkey)[
|
|
proof.amount
|
|
] # Get the correct key to check against
|
|
C = PublicKey(bytes.fromhex(proof.C), raw=True)
|
|
secret = base64.standard_b64decode(proof.secret)
|
|
print('### secret', secret)
|
|
validMintSig = verify(secret_key, C, secret)
|
|
if validMintSig != True:
|
|
raise Exception(f"tokens not valid. Secret: {proof.secret}")
|
|
|
|
|
|
|
|
|
|
def verify_split_amount(amount: int):
|
|
"""Split amount like output amount can't be negative or too big."""
|
|
try:
|
|
verify_amount(amount)
|
|
except:
|
|
# For better error message
|
|
raise Exception("invalid split amount: " + str(amount))
|
|
|
|
|
|
def verify_secret_criteria(proof: Proof):
|
|
if proof.secret is None or proof.secret == "":
|
|
raise Exception("no secret in proof.")
|
|
return True
|
|
|
|
|
|
def verify_no_duplicates(proofs: List[Proof], outputs: List[BlindedMessage]):
|
|
secrets = [p.secret for p in proofs]
|
|
if len(secrets) != len(list(set(secrets))):
|
|
return False
|
|
B_s = [od.B_ for od in outputs]
|
|
if len(B_s) != len(list(set(B_s))):
|
|
return False
|
|
return True
|
|
|
|
|
|
def verify_outputs(total: int, amount: int, outputs: List[BlindedMessage]):
|
|
"""Verifies the expected split was correctly computed"""
|
|
frst_amt, scnd_amt = total - amount, amount # we have two amounts to split to
|
|
frst_outputs = amount_split(frst_amt)
|
|
scnd_outputs = amount_split(scnd_amt)
|
|
expected = frst_outputs + scnd_outputs
|
|
given = [o.amount for o in outputs]
|
|
return given == expected
|
|
|
|
|
|
def verify_amount(amount: int):
|
|
"""Any amount used should be a positive integer not larger than 2^MAX_ORDER."""
|
|
valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
|
|
if not valid:
|
|
raise Exception("invalid amount: " + str(amount))
|
|
return amount
|
|
|
|
|
|
def verify_equation_balanced(proofs: List[Proof], outs: List[BlindedSignature]):
|
|
"""Verify that Σoutputs - Σinputs = 0."""
|
|
sum_inputs = sum(verify_amount(p.amount) for p in proofs)
|
|
sum_outputs = sum(verify_amount(p.amount) for p in outs)
|
|
assert sum_outputs - sum_inputs == 0
|