-
-
Notifications
You must be signed in to change notification settings - Fork 95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Wallet/Mint] DLEQ proofs #175
Changes from 32 commits
347de24
562ffa5
f4b5970
f73e0da
e0bd8bc
65be72b
9002824
a53bb0a
6e1171b
aa535a8
4c8622d
d9cc699
64c5070
80d40db
6d15300
5e5e958
4348d4f
e16010d
9a025fe
f676c70
d907d6c
bc5f478
8f3d1c2
8459105
55e406f
279e8f3
1050c49
ef99106
12be7b1
4af5008
3fb099c
13873b1
befdd12
0495bef
8321ca3
ab8a1f0
205cab3
00fd85f
5fccea3
4020273
43b04b6
8b337d9
7ab9539
22c5eef
4960635
068224e
e4c245b
749d5df
61b7ff4
5b5b8fa
809c52f
1a359a2
beca698
8aaf0f8
57f3967
877c164
e471c74
84d89ca
6451950
9ac144a
2fee249
23c2bbc
e1a7c88
42e447b
d186529
023fc81
35f9a2b
659a0e4
a89c230
b251c24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,26 @@ | |
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 | ||
|
@@ -37,43 +57,86 @@ | |
|
||
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, it tries computing a new point from the hash.""" | ||
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: | ||
_hash = hashlib.sha256(msg_to_hash).digest() | ||
# will error if point does not lie on curve | ||
point = PublicKey(b"\x02" + _hash, raw=True) | ||
except: | ||
msg_to_hash = _hash | ||
return point | ||
|
||
|
||
def step1_alice( | ||
secret_msg: str, blinding_factor: bytes = None | ||
secret_msg: str, blinding_factor: bytes = b"" | ||
) -> tuple[PublicKey, PrivateKey]: | ||
Y: PublicKey = hash_to_curve(secret_msg.encode("utf-8")) | ||
if blinding_factor: | ||
r = PrivateKey(privkey=blinding_factor, raw=True) | ||
else: | ||
r = PrivateKey() | ||
B_: PublicKey = Y + r.pubkey | ||
B_: PublicKey = Y + r.pubkey # type: ignore | ||
return B_, r | ||
|
||
|
||
def step2_bob(B_: PublicKey, a: PrivateKey) -> PublicKey: | ||
C_: PublicKey = B_.mult(a) | ||
return C_ | ||
def step2_bob(B_: PublicKey, a: PrivateKey): | ||
C_ = 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) | ||
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) | ||
return C == Y.mult(a) # type: ignore | ||
|
||
|
||
def hash_e(R1: PublicKey, R2: PublicKey, K: PublicKey, C_: PublicKey): | ||
_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_}" | ||
e = hashlib.sha256(e_.encode("utf-8")).digest() | ||
return e | ||
|
||
|
||
def step2_bob_dleq(B_: PublicKey, a: PrivateKey, p_bytes: bytes = b""): | ||
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 = B_.mult(p) # R2 = pB_ # type: ignore | ||
C_ = B_.mult(a) # C_ = aB_ # type: ignore | ||
K = a.pubkey | ||
assert K | ||
e = hash_e(R1, R2, K, C_) # e = hash(R1, R2, K, C_) | ||
s = p.tweak_add(a.tweak_mul(e)) # s = p + ek | ||
return e, s | ||
|
||
|
||
def alice_verify_dleq(e: bytes, s: bytes, K: PublicKey, B_: bytes, C_: bytes): | ||
epk = PrivateKey(e, raw=True) | ||
spk = PrivateKey(s, raw=True) | ||
bk = PublicKey(B_, raw=True) | ||
ck = PublicKey(C_, raw=True) | ||
R1 = spk.pubkey - K.mult(epk) # type: ignore | ||
R2 = bk.mult(spk) - ck.mult(epk) # type: ignore | ||
return e == hash_e(R1, R2, K, ck) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. new crypto functions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you know, i kinda wonder if |
||
|
||
|
||
### Below is a test of a simple positive and negative case | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,17 @@ class P2SHScript(BaseModel): | |
address: Union[str, None] = None | ||
|
||
|
||
class DLEQ(BaseModel): | ||
""" | ||
Discrete Log Equality (DLEQ) Proof | ||
""" | ||
|
||
e: str | ||
s: str | ||
B_: str | ||
C_: str | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. new |
||
|
||
|
||
class Proof(BaseModel): | ||
""" | ||
Value token | ||
|
@@ -35,6 +46,7 @@ class Proof(BaseModel): | |
secret: str = "" # secret or message to be blinded and signed | ||
C: str = "" # signature on secret, unblinded by wallet | ||
script: Union[P2SHScript, None] = None # P2SH spending condition | ||
dleq: Union[DLEQ, None] = None # DLEQ proof | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New entry in |
||
reserved: Union[ | ||
None, bool | ||
] = False # whether this proof is reserved for sending, used for coin management in the wallet | ||
|
@@ -44,7 +56,21 @@ class Proof(BaseModel): | |
time_created: Union[None, str] = "" | ||
time_reserved: Union[None, str] = "" | ||
|
||
def to_dict(self): | ||
def to_dict(self, include_dleq=True): | ||
# dictionary without the fields that don't need to be send to Carol | ||
if not include_dleq: | ||
return dict(id=self.id, amount=self.amount, secret=self.secret, C=self.C) | ||
|
||
assert self.dleq, "DLEQ proof is missing" | ||
return dict( | ||
id=self.id, | ||
amount=self.amount, | ||
secret=self.secret, | ||
C=self.C, | ||
dleq=self.dleq.dict(), | ||
) | ||
|
||
def to_dict_no_dleq(self): | ||
# dictionary without the fields that don't need to be send to Carol | ||
return dict(id=self.id, amount=self.amount, secret=self.secret, C=self.C) | ||
|
||
|
@@ -78,9 +104,10 @@ class BlindedSignature(BaseModel): | |
Blinded signature or "promise" which is the signature on a `BlindedMessage` | ||
""" | ||
|
||
id: Union[str, None] = None | ||
id: str | ||
amount: int | ||
C_: str # Hex-encoded signature | ||
dleq: Optional[DLEQ] = None # DLEQ proof | ||
|
||
|
||
class BlindedMessages(BaseModel): | ||
|
@@ -366,8 +393,8 @@ class TokenV3Token(BaseModel): | |
mint: Optional[str] = None | ||
proofs: List[Proof] | ||
|
||
def to_dict(self): | ||
return_dict = dict(proofs=[p.to_dict() for p in self.proofs]) | ||
def to_dict(self, include_dleq=True): | ||
return_dict = dict(proofs=[p.to_dict(include_dleq) for p in self.proofs]) | ||
if self.mint: | ||
return_dict.update(dict(mint=self.mint)) # type: ignore | ||
return return_dict | ||
|
@@ -381,8 +408,8 @@ class TokenV3(BaseModel): | |
token: List[TokenV3Token] = [] | ||
memo: Optional[str] = None | ||
|
||
def to_dict(self): | ||
return_dict = dict(token=[t.to_dict() for t in self.token]) | ||
def to_dict(self, include_dleq=True): | ||
return_dict = dict(token=[t.to_dict(include_dleq) for t in self.token]) | ||
if self.memo: | ||
return_dict.update(dict(memo=self.memo)) # type: ignore | ||
return return_dict | ||
|
@@ -409,14 +436,14 @@ def deserialize(cls, tokenv3_serialized: str): | |
token = json.loads(base64.urlsafe_b64decode(token_base64)) | ||
return cls.parse_obj(token) | ||
|
||
def serialize(self): | ||
def serialize(self, include_dleq=True): | ||
""" | ||
Takes a TokenV3 and serializes it as "cashuA<json_urlsafe_base64>. | ||
""" | ||
prefix = "cashuA" | ||
tokenv3_serialized = prefix | ||
# encode the token as a base64 string | ||
tokenv3_serialized += base64.urlsafe_b64encode( | ||
json.dumps(self.to_dict()).encode() | ||
json.dumps(self.to_dict(include_dleq)).encode() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
).decode() | ||
return tokenv3_serialized |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
from ..core import bolt11 as bolt11 | ||
from ..core import legacy as legacy | ||
from ..core.base import ( | ||
DLEQ, | ||
BlindedMessage, | ||
BlindedSignature, | ||
Invoice, | ||
|
@@ -141,11 +142,23 @@ async def _generate_promise( | |
keyset = keyset if keyset else self.keyset | ||
logger.trace(f"Generating promise with keyset {keyset.id}.") | ||
private_key_amount = keyset.private_keys[amount] | ||
C_ = b_dhke.step2_bob(B_, private_key_amount) | ||
C_, e, s = b_dhke.step2_bob(B_, private_key_amount) | ||
await self.crud.store_promise( | ||
amount=amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db | ||
amount=amount, | ||
B_=B_.serialize().hex(), | ||
C_=C_.serialize().hex(), | ||
e=e.hex(), | ||
s=s.hex(), | ||
db=self.db, | ||
) | ||
return BlindedSignature( | ||
id=keyset.id, | ||
amount=amount, | ||
C_=C_.serialize().hex(), | ||
dleq=DLEQ( | ||
e=e.hex(), s=s.hex(), B_=B_.serialize().hex(), C_=C_.serialize().hex() | ||
), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mint returns new |
||
) | ||
return BlindedSignature(id=keyset.id, amount=amount, C_=C_.serialize().hex()) | ||
|
||
def _check_spendable(self, proof: Proof): | ||
"""Checks whether the proof was already spent.""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new return for
step2_bob