mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-22 11:24:19 +01:00
* produce dleq * start working on verification * wip dleq * Use C_ instead of C in verify DLEQ! (#176) * Fix comments (DLEQ sign error) * Fix alice_verify_dleq in d_dhke.py * Fix_generate_promise in ledger.py * Fix verify_proofs_dleq in wallet.py * Fix: invalid public key (#182) * Use C_ instead of C in verify DLEQ! * Fix comments (DLEQ sign error) * Fix alice_verify_dleq in d_dhke.py * Fix_generate_promise in ledger.py * Fix verify_proofs_dleq in wallet.py * Fix: invalid public key * Exception: Mint Error: invalid public key * Update cashu/wallet/wallet.py --------- Co-authored-by: calle <93376500+callebtc@users.noreply.github.com> * Update cashu/core/b_dhke.py * Update tests/test_cli.py * verify all constructed proofs * dleq upon receive * serialize without dleq * all tests passing * make format * remove print * remove debug * option to send with dleq * add tests * fix test * deterministic p in step2_dleq and fix mypy error for hash_to_curve * test crypto/hash_e and crypto/step2_bob_dleq * rename A to K in b_dhke.py and test_alice_verify_dleq * rename tests * make format * store dleq in mint db (and readd balance view) * remove `r` from dleq in tests * add pending output * make format * works with pre-dleq mints * fix comments * make format * fix some tests * fix last test * test serialize dleq fix * flake * flake * keyset.id must be str * fix test decorators * start removing the duplicate fields from the dleq * format * remove print * cleanup * add type anotations to dleq functions * remove unnecessary fields from BlindedSignature * tests not working yet * spelling mistakes * spelling mistakes * fix more spelling mistakes * revert to normal * add comments * bdhke: generalize hash_e * remove P2PKSecret changes * revert tests for P2PKSecret * revert tests * revert test fully * revert p2pksecret changes * refactor proof invalidation * store dleq proofs in wallet db * make mypy happy --------- Co-authored-by: moonsettler <moonsettler@protonmail.com>
174 lines
4.4 KiB
Python
174 lines
4.4 KiB
Python
# Don't trust me with cryptography.
|
|
|
|
"""
|
|
Implementation of https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406
|
|
|
|
Bob (Mint):
|
|
A = a*G
|
|
return A
|
|
|
|
Alice (Client):
|
|
Y = hash_to_curve(secret_message)
|
|
r = random blinding factor
|
|
B'= Y + r*G
|
|
return B'
|
|
|
|
Bob:
|
|
C' = a*B'
|
|
(= a*Y + a*r*G)
|
|
return C'
|
|
|
|
Alice:
|
|
C = C' - r*A
|
|
(= C' - a*r*G)
|
|
(= a*Y)
|
|
return C, secret_message
|
|
|
|
Bob:
|
|
Y = hash_to_curve(secret_message)
|
|
C == a*Y
|
|
If true, C must have originated from Bob
|
|
|
|
|
|
# DLEQ Proof
|
|
|
|
(These steps occur once Bob returns C')
|
|
|
|
Bob:
|
|
r = random nonce
|
|
R1 = r*G
|
|
R2 = r*B'
|
|
e = hash(R1,R2,A,C')
|
|
s = r + e*a
|
|
return e, s
|
|
|
|
Alice:
|
|
R1 = s*G - e*A
|
|
R2 = s*B' - e*C'
|
|
e == hash(R1,R2,A,C')
|
|
|
|
If true, a in A = a*G must be equal to a in C' = a*B'
|
|
"""
|
|
|
|
import hashlib
|
|
from typing import Optional, Tuple
|
|
|
|
from secp256k1 import PrivateKey, PublicKey
|
|
|
|
|
|
def hash_to_curve(message: bytes) -> PublicKey:
|
|
"""Generates a point from the message hash and checks if the point lies on the curve.
|
|
If it does not, iteratively tries to compute a new point from the hash."""
|
|
point = None
|
|
msg_to_hash = message
|
|
while point is None:
|
|
_hash = hashlib.sha256(msg_to_hash).digest()
|
|
try:
|
|
# will error if point does not lie on curve
|
|
point = PublicKey(b"\x02" + _hash, raw=True)
|
|
except Exception:
|
|
msg_to_hash = _hash
|
|
return point
|
|
|
|
|
|
def step1_alice(
|
|
secret_msg: str, blinding_factor: Optional[PrivateKey] = None
|
|
) -> tuple[PublicKey, PrivateKey]:
|
|
Y: PublicKey = hash_to_curve(secret_msg.encode("utf-8"))
|
|
r = blinding_factor or PrivateKey()
|
|
B_: PublicKey = Y + r.pubkey # type: ignore
|
|
return B_, r
|
|
|
|
|
|
def step2_bob(B_: PublicKey, a: PrivateKey) -> Tuple[PublicKey, PrivateKey, PrivateKey]:
|
|
C_: PublicKey = B_.mult(a) # type: ignore
|
|
# produce dleq proof
|
|
e, s = step2_bob_dleq(B_, a)
|
|
return C_, e, s
|
|
|
|
|
|
def step3_alice(C_: PublicKey, r: PrivateKey, A: PublicKey) -> PublicKey:
|
|
C: PublicKey = C_ - A.mult(r) # type: ignore
|
|
return C
|
|
|
|
|
|
def verify(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool:
|
|
Y: PublicKey = hash_to_curve(secret_msg.encode("utf-8"))
|
|
return C == Y.mult(a) # type: ignore
|
|
|
|
|
|
def hash_e(*publickeys: PublicKey) -> bytes:
|
|
e_ = ""
|
|
for p in publickeys:
|
|
_p = p.serialize(compressed=False).hex()
|
|
e_ += str(_p)
|
|
e = hashlib.sha256(e_.encode("utf-8")).digest()
|
|
return e
|
|
|
|
|
|
def step2_bob_dleq(
|
|
B_: PublicKey, a: PrivateKey, p_bytes: bytes = b""
|
|
) -> Tuple[PrivateKey, PrivateKey]:
|
|
if p_bytes:
|
|
# deterministic p for testing
|
|
p = PrivateKey(privkey=p_bytes, raw=True)
|
|
else:
|
|
# normally, we generate a random p
|
|
p = PrivateKey()
|
|
|
|
R1 = p.pubkey # R1 = pG
|
|
assert R1
|
|
R2: PublicKey = B_.mult(p) # R2 = pB_ # type: ignore
|
|
C_: PublicKey = B_.mult(a) # C_ = aB_ # type: ignore
|
|
A = a.pubkey
|
|
assert A
|
|
e = hash_e(R1, R2, A, C_) # e = hash(R1, R2, A, C_)
|
|
s = p.tweak_add(a.tweak_mul(e)) # s = p + ek
|
|
spk = PrivateKey(s, raw=True)
|
|
epk = PrivateKey(e, raw=True)
|
|
return epk, spk
|
|
|
|
|
|
def alice_verify_dleq(
|
|
B_: PublicKey, C_: PublicKey, e: PrivateKey, s: PrivateKey, A: PublicKey
|
|
) -> bool:
|
|
R1 = s.pubkey - A.mult(e) # type: ignore
|
|
R2 = B_.mult(s) - C_.mult(e) # type: ignore
|
|
e_bytes = e.private_key
|
|
return e_bytes == hash_e(R1, R2, A, C_)
|
|
|
|
|
|
def carol_verify_dleq(
|
|
secret_msg: str,
|
|
r: PrivateKey,
|
|
C: PublicKey,
|
|
e: PrivateKey,
|
|
s: PrivateKey,
|
|
A: PublicKey,
|
|
) -> bool:
|
|
Y: PublicKey = hash_to_curve(secret_msg.encode("utf-8"))
|
|
C_: PublicKey = C + A.mult(r) # type: ignore
|
|
B_: PublicKey = Y + r.pubkey # type: ignore
|
|
return alice_verify_dleq(B_, C_, e, s, 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
|