Skip to content

Commit

Permalink
schnorr proofs for dleq
Browse files Browse the repository at this point in the history
  • Loading branch information
callebtc committed Sep 16, 2023
1 parent 749d5df commit c142113
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 35 deletions.
11 changes: 8 additions & 3 deletions cashu/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,16 @@ class DLEQWallet(BaseModel):
Discrete Log Equality (DLEQ) Proof
"""

# DLEQ proof of equality of a (mint private key)
e: str
s: str
r: str # blinding_factor, unknown to mint but sent from wallet to wallet for DLEQ proof
# B_: Union[str, None] = None # blinded message, sent to the mint by the wallet
# C_: Union[str, None] = None # blinded signature, received by the mint
# r: str # blinding_factor, unknown to mint but sent from wallet to wallet for DLEQ proof
B_: str # blinded message, sent to the mint by the wallet
C_: str # blinded signature, received by the mint

# schnorr proof of knowledge of r (blinding factor of Alice)
f: str
t: str


class Proof(BaseModel):
Expand Down
91 changes: 82 additions & 9 deletions cashu/core/crypto/b_dhke.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,11 @@ def verify(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool:
return C == Y.mult(a) # type: ignore


def hash_e(R1: PublicKey, R2: PublicKey, K: PublicKey, C_: PublicKey) -> bytes:
_R1 = R1.serialize(compressed=False).hex()
_R2 = R2.serialize(compressed=False).hex()
_K = K.serialize(compressed=False).hex()
_C_ = C_.serialize(compressed=False).hex()
e_ = f"{_R1}{_R2}{_K}{_C_}"
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

Expand Down Expand Up @@ -130,8 +129,13 @@ def step2_bob_dleq(
return epk, spk


def alice_verify_dleq(
B_: PublicKey, C_: PublicKey, e: PrivateKey, s: PrivateKey, A: PublicKey
def verify_dleq(
*,
B_: PublicKey,
C_: PublicKey,
e: PrivateKey,
s: PrivateKey,
A: PublicKey,
):
R1 = s.pubkey - A.mult(e) # type: ignore
R2 = B_.mult(s) - C_.mult(e) # type: ignore
Expand All @@ -140,6 +144,28 @@ def alice_verify_dleq(


def carol_verify_dleq(
*,
B_: PublicKey,
C_: PublicKey,
e: PrivateKey,
s: PrivateKey,
A: PublicKey,
f: PrivateKey,
t: PrivateKey,
C: PublicKey,
secret_msg: str,
):
# verify dleq proof that mint signature is valid
assert verify_dleq(B_=B_, C_=C_, e=e, s=s, A=A)
# verify schnorr proof that Alice sent us valid C_ and B_
assert carol_schnorr_r_verify(
A=A, B_=B_, secret_msg=secret_msg, C=C, C_=C_, f=f, t=t
)
return True


def alice_verify_dleq(
*,
secret_msg: str,
r: PrivateKey,
C: PublicKey,
Expand All @@ -150,7 +176,54 @@ def carol_verify_dleq(
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)
return verify_dleq(B_=B_, C_=C_, e=e, s=s, A=A)


def alice_schnorr_r(
r: PrivateKey,
A: PublicKey,
B_: PublicKey,
C_: PublicKey,
C: PublicKey,
secret_msg: str,
k_bytes: bytes = b"",
) -> Tuple[PrivateKey, PrivateKey]:
if k_bytes:
# deterministic k for testing
k = PrivateKey(privkey=k_bytes, raw=True)
else:
# normally, we generate a random p
k = PrivateKey()

K1 = k.pubkey # K1 = kG
assert K1
K2 = A.mult(k) # K2 = kA # type: ignore

Y: PublicKey = hash_to_curve(secret_msg.encode("utf-8"))
f = hash_e(K1, K2, A, B_, C_, Y, C)
t = k.tweak_add(r.tweak_mul(f)) # t = p + fk
tpk = PrivateKey(t, raw=True)
fpk = PrivateKey(f, raw=True)
return fpk, tpk


def carol_schnorr_r_verify(
*,
A: PublicKey,
B_: PublicKey,
secret_msg: str,
C: PublicKey,
C_: PublicKey,
f: PrivateKey,
t: PrivateKey,
):
Y: PublicKey = hash_to_curve(secret_msg.encode("utf-8"))
K1 = t.pubkey - B_.mult(f) + Y.mult(f) # type: ignore
K2 = A.mult(t) - C_.mult(f) + C.mult(f) # type: ignore

f_bytes = f.private_key

return f_bytes == hash_e(K1, K2, A, B_, C_, Y, C)


# Below is a test of a simple positive and negative case
Expand Down
65 changes: 47 additions & 18 deletions cashu/wallet/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,33 +142,48 @@ async def _init_s(self):

# ---------- DLEQ PROOFS ----------

def verify_proofs_dleq(self, proofs: List[Proof]):
def alice_verify_proofs_dleq(self, proofs: List[Proof], rs: List[PrivateKey]):
"""Verifies DLEQ proofs in proofs."""
for proof in proofs:
for r, proof in zip(rs, proofs):
if not proof.dleq:
logger.trace("No DLEQ proof in proof.")
return
logger.trace("Verifying DLEQ proof.")
logger.trace("Alice: Verifying DLEQ proof.")
assert self.keys.public_keys
# if not b_dhke.alice_verify_dleq(
# e=PrivateKey(bytes.fromhex(proof.dleq.e), raw=True),
# s=PrivateKey(bytes.fromhex(proof.dleq.s), raw=True),
# A=self.keys.public_keys[proof.amount],
# B_=PublicKey(bytes.fromhex(proof.B_), raw=True),
# C_=PublicKey(bytes.fromhex(proof.C_), raw=True),
# ):
# raise Exception("Alice: DLEQ proof invalid.")
if not b_dhke.carol_verify_dleq(
if not b_dhke.alice_verify_dleq(
secret_msg=proof.secret,
C=PublicKey(bytes.fromhex(proof.C), raw=True),
r=PrivateKey(bytes.fromhex(proof.dleq.r), raw=True),
r=r,
e=PrivateKey(bytes.fromhex(proof.dleq.e), raw=True),
s=PrivateKey(bytes.fromhex(proof.dleq.s), raw=True),
A=self.keys.public_keys[proof.amount],
):
raise Exception("DLEQ proof invalid.")
else:
logger.debug("DLEQ proof valid.")
logger.debug("Alice: DLEQ proof valid.")

def carol_verify_proofs_dleq(self, proofs: List[Proof]):
"""Verifies DLEQ proofs in proofs."""
for proof in proofs:
if not proof.dleq:
logger.debug("No DLEQ proof in proof.")
return
logger.trace("Carol: Verifying DLEQ proof.")
assert self.keys.public_keys
if not b_dhke.carol_verify_dleq(
e=PrivateKey(bytes.fromhex(proof.dleq.e), raw=True),
s=PrivateKey(bytes.fromhex(proof.dleq.s), raw=True),
A=self.keys.public_keys[proof.amount],
B_=PublicKey(bytes.fromhex(proof.dleq.B_), raw=True),
C_=PublicKey(bytes.fromhex(proof.dleq.C_), raw=True),
C=PublicKey(bytes.fromhex(proof.C), raw=True),
f=PrivateKey(bytes.fromhex(proof.dleq.f), raw=True),
t=PrivateKey(bytes.fromhex(proof.dleq.t), raw=True),
secret_msg=proof.secret,
):
raise Exception("Carol: DLEQ proof invalid.")
else:
logger.debug("Carol: DLEQ proof valid.")

def _construct_proofs(
self,
Expand Down Expand Up @@ -213,18 +228,32 @@ def _construct_proofs(

# if the mint returned a dleq proof, we add it to the proof
if promise.dleq:
# create schnorr proof for r
f, t = b_dhke.alice_schnorr_r(
r=r,
A=self.public_keys[promise.amount],
B_=B_,
C_=C_,
C=C,
secret_msg=secret,
)

proof.dleq = DLEQWallet(
e=promise.dleq.e, s=promise.dleq.s, r=r.serialize()
e=promise.dleq.e,
s=promise.dleq.s,
B_=B_.serialize().hex(),
C_=C_.serialize().hex(),
f=f.serialize(),
t=t.serialize(),
)

proofs.append(proof)

logger.trace(
f"Created proof: {proof}, r: {r.serialize()} out of promise {promise}"
)

# DLEQ verify
self.verify_proofs_dleq(proofs)
self.alice_verify_proofs_dleq(proofs, rs)

logger.trace(f"Constructed {len(proofs)} proofs.")
return proofs
Expand Down Expand Up @@ -1086,7 +1115,7 @@ async def redeem(
"""
# verify DLEQ of incoming proofs
logger.debug("Verifying DLEQ of incoming proofs.")
self.verify_proofs_dleq(proofs)
self.carol_verify_proofs_dleq(proofs)
logger.debug("DLEQ verified.")
return await self.split(proofs, sum_proofs(proofs))

Expand Down
10 changes: 5 additions & 5 deletions tests/test_crypto.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from cashu.core.crypto.b_dhke import (
alice_verify_dleq,
carol_verify_dleq,
hash_e,
hash_to_curve,
step1_alice,
step2_bob,
step2_bob_dleq,
step3_alice,
verify_dleq,
)
from cashu.core.crypto.secp import PrivateKey, PublicKey

Expand Down Expand Up @@ -251,7 +251,7 @@ def test_dleq_alice_verify_dleq():
raw=True,
)

assert alice_verify_dleq(B_, C_, e, s, A)
assert verify_dleq(B_=B_, C_=C_, e=e, s=s, A=A)


def test_dleq_alice_direct_verify_dleq():
Expand All @@ -274,7 +274,7 @@ def test_dleq_alice_direct_verify_dleq():
),
)
C_, e, s = step2_bob(B_, a)
assert alice_verify_dleq(B_, C_, e, s, A)
assert verify_dleq(B_=B_, C_=C_, e=e, s=s, A=A)


def test_dleq_carol_varify_from_bob():
Expand All @@ -295,8 +295,8 @@ def test_dleq_carol_varify_from_bob():
)
B_, _ = step1_alice(secret_msg, r)
C_, e, s = step2_bob(B_, a)
assert alice_verify_dleq(B_, C_, e, s, A)
assert verify_dleq(B_=B_, C_=C_, e=e, s=s, A=A)
C = step3_alice(C_, r, A)

# carol does not know B_ and C_, but she receives C and r from Alice
assert carol_verify_dleq(secret_msg=secret_msg, C=C, r=r, e=e, s=s, A=A)
assert alice_verify_dleq(secret_msg=secret_msg, C=C, r=r, e=e, s=s, A=A)

0 comments on commit c142113

Please sign in to comment.