diff --git a/cashu/core/base.py b/cashu/core/base.py index 089f65bc..1629134a 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -163,6 +163,18 @@ class DLEQ(BaseModel): s: str +class DLEQWallet(BaseModel): + """ + Discrete Log Equality (DLEQ) Proof + """ + + 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 + + class Proof(BaseModel): """ Value token @@ -171,10 +183,12 @@ class Proof(BaseModel): id: Union[ None, str ] = "" # NOTE: None for backwards compatibility for old clients that do not include the keyset id < 0.3 + amount: int = 0 secret: str = "" # secret or message to be blinded and signed C: str = "" # signature on secret, unblinded by wallet - dleq: Union[DLEQ, None] = None # DLEQ proof + dleq: Union[DLEQWallet, None] = None # DLEQ proof + p2pksigs: Union[List[str], None] = [] # P2PK signature p2shscript: Union[P2SHScript, None] = None # P2SH spending condition reserved: Union[ @@ -193,6 +207,7 @@ def to_dict(self, include_dleq=False): return dict(id=self.id, amount=self.amount, secret=self.secret, C=self.C) assert self.dleq, "DLEQ proof is missing" + print(self.dleq) return dict( id=self.id, amount=self.amount, diff --git a/cashu/core/crypto/b_dhke.py b/cashu/core/crypto/b_dhke.py index ad261028..6f30576e 100644 --- a/cashu/core/crypto/b_dhke.py +++ b/cashu/core/crypto/b_dhke.py @@ -80,7 +80,7 @@ def step1_alice( return B_, r -def step2_bob(B_: PublicKey, a: PrivateKey) -> Tuple[PublicKey, bytes, bytes]: +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) @@ -97,7 +97,7 @@ 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): +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() @@ -107,7 +107,9 @@ def hash_e(R1: PublicKey, R2: PublicKey, K: PublicKey, C_: PublicKey): return e -def step2_bob_dleq(B_: PublicKey, a: PrivateKey, p_bytes: bytes = b""): +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) @@ -117,23 +119,42 @@ def step2_bob_dleq(B_: PublicKey, a: PrivateKey, p_bytes: bytes = b""): 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_) + 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 - 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) + epk = PrivateKey(e, raw=True) + return epk, spk + + +def alice_verify_dleq( + e: PrivateKey, s: PrivateKey, A: PublicKey, B_: PublicKey, C_: PublicKey +): + 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, +): + 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(e, s, A, B_, C_) + # 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_) # Below is a test of a simple positive and negative case diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index c7cd5cc2..3519646c 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -191,8 +191,8 @@ async def _generate_promise( amount=amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), - e=e.hex(), - s=s.hex(), + e=e.serialize(), + s=s.serialize(), db=self.db, id=keyset.id, ) @@ -201,9 +201,7 @@ async def _generate_promise( id=keyset.id, amount=amount, C_=C_.serialize().hex(), - dleq=DLEQ( - e=e.hex(), s=s.hex() - ), + dleq=DLEQ(e=e.serialize(), s=s.serialize()), ) def _check_spendable(self, proof: Proof): diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index c08f4198..9ebb80c5 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -22,6 +22,7 @@ CheckFeesRequest, CheckSpendableRequest, CheckSpendableResponse, + DLEQWallet, GetInfoResponse, GetMeltResponse, GetMintResponse, @@ -148,12 +149,21 @@ def verify_proofs_dleq(self, proofs: List[Proof]): return logger.debug("Verifying DLEQ proof.") assert self.keys.public_keys - if not b_dhke.alice_verify_dleq( - bytes.fromhex(proof.dleq.e), - bytes.fromhex(proof.dleq.s), - self.keys.public_keys[proof.amount], - bytes.fromhex(proof.dleq.B_), - bytes.fromhex(proof.dleq.C_), + # 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( + secret_msg=proof.secret, + C=PublicKey(bytes.fromhex(proof.C), raw=True), + r=PrivateKey(bytes.fromhex(proof.dleq.r), raw=True), + 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.") @@ -188,17 +198,22 @@ def _construct_proofs( C_ = PublicKey(bytes.fromhex(promise.C_), raw=True) C = b_dhke.step3_alice(C_, r, self.public_keys[promise.amount]) + B_, r = b_dhke.step1_alice(secret, r) # recompute B_ for dleq proofs proof = Proof( id=promise.id, amount=promise.amount, C=C.serialize().hex(), secret=secret, - dleq=promise.dleq, derivation_path=path, ) - if proof.dleq: - proof.dleq.C_ = promise.C_ + + # if the mint returned a dleq proof, we add it to the proof + if promise.dleq: + proof.dleq = DLEQWallet( + e=promise.dleq.e, s=promise.dleq.s, r=r.serialize() + ) + proofs.append(proof) logger.trace( diff --git a/tests/test_cli.py b/tests/test_cli.py index c896a61f..1521e1c0 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -24,7 +24,6 @@ async def init_wallet(): return wallet - def test_info(cli_prefix): runner = CliRunner() result = runner.invoke( @@ -38,7 +37,6 @@ def test_info(cli_prefix): assert result.exit_code == 0 - def test_info_with_mint(cli_prefix): runner = CliRunner() result = runner.invoke( @@ -52,7 +50,6 @@ def test_info_with_mint(cli_prefix): assert result.exit_code == 0 - def test_info_with_mnemonic(cli_prefix): runner = CliRunner() result = runner.invoke( @@ -66,7 +63,6 @@ def test_info_with_mnemonic(cli_prefix): assert result.exit_code == 0 - def test_balance(cli_prefix): runner = CliRunner() result = runner.invoke( @@ -81,7 +77,6 @@ def test_balance(cli_prefix): assert result.exit_code == 0 - def test_invoice(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -97,7 +92,6 @@ def test_invoice(mint, cli_prefix): assert result.exit_code == 0 - def test_invoice_with_split(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -109,7 +103,6 @@ def test_invoice_with_split(mint, cli_prefix): # assert wallet.proof_amounts.count(1) >= 10 - def test_wallets(cli_prefix): runner = CliRunner() result = runner.invoke( @@ -124,7 +117,6 @@ def test_wallets(cli_prefix): assert result.exit_code == 0 - def test_send(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -139,7 +131,6 @@ def test_send(mint, cli_prefix): assert token.token[0].proofs[0].dleq is None, "dleq included" - def test_send_with_dleq(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -154,7 +145,6 @@ def test_send_with_dleq(mint, cli_prefix): assert token.token[0].proofs[0].dleq is not None, "no dleq included" - def test_send_legacy(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -168,7 +158,6 @@ def test_send_legacy(mint, cli_prefix): assert token_str.startswith("eyJwcm9v"), "output is not as expected" - def test_send_without_split(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -181,7 +170,6 @@ def test_send_without_split(mint, cli_prefix): assert "cashuA" in result.output, "output does not have a token" - def test_send_without_split_but_wrong_amount(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -191,7 +179,6 @@ def test_send_without_split_but_wrong_amount(mint, cli_prefix): assert "No proof with this amount found" in str(result.exception) - def test_receive_tokenv3(mint, cli_prefix): runner = CliRunner() token = ( @@ -213,7 +200,6 @@ def test_receive_tokenv3(mint, cli_prefix): print(result.output) - def test_receive_tokenv3_no_mint(mint, cli_prefix): # this test works only if the previous test succeeds because we simulate the case where the mint URL is not in the token # therefore, we need to know the mint keyset already and have the mint URL in the db @@ -237,7 +223,6 @@ def test_receive_tokenv3_no_mint(mint, cli_prefix): print(result.output) - def test_receive_tokenv2(mint, cli_prefix): runner = CliRunner() token = ( @@ -255,7 +240,6 @@ def test_receive_tokenv2(mint, cli_prefix): print(result.output) - def test_receive_tokenv1(mint, cli_prefix): runner = CliRunner() token = ( @@ -273,6 +257,8 @@ def test_receive_tokenv1(mint, cli_prefix): () + + def test_nostr_send(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -291,7 +277,6 @@ def test_nostr_send(mint, cli_prefix): print(result.output) - def test_pending(cli_prefix): runner = CliRunner() result = runner.invoke( diff --git a/tests/test_core.py b/tests/test_core.py index 89fbd866..e41df38c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -33,16 +33,23 @@ def test_tokenv3_get_proofs(): def test_tokenv3_deserialize_serialize_with_dleq(): token_str = ( - "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjI4ajhueVNMMU5kZCIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogInFvS01lMGk1LVgyQy0zc29mVUZUb0Ei" - "LCAiQyI6ICIwMmIzYWVlMjAxZDRmYzlhODJkYTJiZTVmOTQzODlmOWIxMzg4YzBkMDA3N2M2ODg1MDhjODIzZmU4OTMwY2YwYzYiLCAiZGxlcSI6IHsiZSI6ICJjMmJkNTM" - "zMTkyZDI2MzExMzM2MjU1OTUxZmQyZTI2OWEwY2E2ODViMzgxMDcwYTFhMzRiMDViYjVlYTBkNTAzIiwgInMiOiAiNzUxOTc5MDczZjk3YjcyMmYzNzJkMzRjZDhlNmY5MWR" - "lYzA0ODE4MTg5NjI3YjYwZWFmYjZjMTY3MjFiZjVmOCIsICJCXyI6ICIwMjVhZjliM2ViNmFkZjgxN2M0YzBjMzhhYWYwZjNhYmJjY2MxODFhY2VmMmNjZTQ4Mzg3MWIwYzE" - "yM2UwOTI1NDgiLCAiQ18iOiAiMDNlMDA2YTRiNGI1ODNlMzI5NTNmYzFmMWI4MmQyZTE1MmQyMjdlYTFhZTgyZTEyOWNiYzc3OWQ5NzYwMTc1Mzg4In19LCB7ImlkIjogIjI4" - "ajhueVNMMU5kZCIsICJhbW91bnQiOiA4LCAic2VjcmV0IjogIk1TcEdMMG9Qc3cyU2dFOTdvNmh3emciLCAiQyI6ICIwM2EyYjJmMTBjOGI2MjQ0ODRhYmNmYTc3MzUwYjhiN" - "WU1NDAwZWFhYmQxNjA0ZTRjYjliYWQyNjJkZmFhOThmYTgiLCAiZGxlcSI6IHsiZSI6ICJkODI0YzRjNGExNTBmZTQxM2JjM2YzOGNkMGE2NjAxZDU1NWVhYzhjOTNmNDMyOT" - "c0NzQxMGEwOGMzZmYyNTg4IiwgInMiOiAiMjZkZjdhZTk4NzdjN2YyZTE2N2FkZGI4MWRkYjFlNjg1NTE5NmY1NDE3ZDI3MDFiYTdkZmM0NDlkNjYwNTQyZiIsICJCXyI6ICI" - "wMmJlNzQ0NzllOTM0NWU3NWRhNWUzYzliYzcxYjBhMTZlNzZhNDJkMDVjMDA3M2ZjYjgzZmNlYTg3YWJmZGFhYTciLCAiQ18iOiAiMDNkYjA3MjdjYWNjMTQwYTljMzg0YjQx" - "ZjJhMjUyYTg3ODI5YWZhMWU4OWJjZTFlMGY4YWQyMGJkNGQ5Zjc4YjgyIn19XSwgIm1pbnQiOiAiaHR0cDovL2xvY2FsaG9zdDozMzM4In1dfQ==" + "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjFjQ05JQVoyWC93M" + "SIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIjZmZjFiY2VlOGUzMzk2NGE4ZDNjNGQ5NzYwNzdiZ" + "DI4ZGVkZWJkODYyMDU0MDQzNDY4ZjU5ZDFiZjI1OTQzN2QiLCAiQyI6ICIwM2I3ZD" + "lkMzIzYTAxOWJlNTE4NzRlOGE5OGY1NDViOTg3Y2JmNmU5MWUwMDc1YTFhZjQ3MjY2NDMxOGRlZ" + "TQzZTUiLCAiZGxlcSI6IHsiZSI6ICI1ZjkxMGQ4NTc0M2U0OTI0ZjRiNjlkNzhjM" + "jFjYTc1ZjEzNzg3Zjc3OTE1NWRmMjMzMjJmYTA1YjU5ODdhYzNmIiwgInMiOiAiZTc4Y2U0MzNiZ" + "WNlZTNjNGU1NzM4ZDdjMzRlNDQyZWQ0MmJkMzk0MjI0ZTc3MjE4OGFjMmI5MzZmM" + "jA2Y2QxYSIsICJyIjogIjI3MzM3ODNmOTQ4MWZlYzAxNzdlYmM4ZjBhOTI2OWVjOGFkNzU5MDU2ZT" + "k3MTRiMWEwYTEwMDQ3MmY2Y2Y5YzIifX0sIHsiaWQiOiAiMWNDTklBWjJYL3cxIi" + "wgImFtb3VudCI6IDgsICJzZWNyZXQiOiAiMmFkNDMyZDRkNTg2MzJiMmRlMzI0ZmQxYmE5OTcyZmE" + "4MDljNmU3ZGE1ZTkyZWVmYjBiNjYxMmQ5M2Q3ZTAwMCIsICJDIjogIjAzMmFmYjg" + "zOWQwMmRmMWNhOGY5ZGZjNTI1NzUxN2Q0MzY4YjdiMTc0MzgzM2JlYWUzZDQzNmExYmQwYmJkYjVk" + "OCIsICJkbGVxIjogeyJlIjogImY0NjM2MzU5YTUzZGQxNGEyNmUyNTMyMDQxZWIx" + "MDE2OTk1ZTg4NzgwODY0OWFlY2VlNTcwZTA5ZTk2NTU3YzIiLCAicyI6ICJmZWYzMGIzMDcwMDJkMW" + "VjNWZiZjg0ZGZhZmRkMGEwOTdkNDJlMDYxNTZiNzdiMTMzMmNjNGZjNGNjYWEyOD" + "JmIiwgInIiOiAiODQ5MjQxNzBlYzc3ZjhjMDNmZDRlZTkyZTA3MjdlMzYyNTliZjRhYTc4NTBjZTc2" + "NDExMDQ0MmNlNmVlM2FjYyJ9fV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0=" ) token = TokenV3.deserialize(token_str) assert token.serialize(include_dleq=True) == token_str @@ -50,9 +57,12 @@ def test_tokenv3_deserialize_serialize_with_dleq(): def test_tokenv3_deserialize_serialize(): token_str = ( - "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIkplaFpMVTZuQ3BSZCIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIjBFN2lDazRkVmxSZjVQRjFnNFpWMnci" - "LCAiQyI6ICIwM2FiNTgwYWQ5NTc3OGVkNTI5NmY4YmVlNjU1ZGJkN2Q2NDJmNWQzMmRlOGUyNDg0NzdlMGI0ZDZhYTg2M2ZjZDUifSwgeyJpZCI6ICJKZWhaTFU2bkNwUmQiLCAiYW" - "1vdW50IjogOCwgInNlY3JldCI6ICJzNklwZXh3SGNxcXVLZDZYbW9qTDJnIiwgIkMiOiAiMDIyZDAwNGY5ZWMxNmE1OGFkOTAxNGMyNTliNmQ2MTRlZDM2ODgyOWYwMmMzODc3M2M0" + "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIkplaFpMVTZuQ3BSZCIsICJh" + "bW91bnQiOiAyLCAic2VjcmV0IjogIjBFN2lDazRkVmxSZjVQRjFnNFpWMnci" + "LCAiQyI6ICIwM2FiNTgwYWQ5NTc3OGVkNTI5NmY4YmVlNjU1ZGJkN2Q2NDJmNWQzMmRlOG" + "UyNDg0NzdlMGI0ZDZhYTg2M2ZjZDUifSwgeyJpZCI6ICJKZWhaTFU2bkNwUmQiLCAiYW" + "1vdW50IjogOCwgInNlY3JldCI6ICJzNklwZXh3SGNxcXVLZDZYbW9qTDJnIiwgIkMiOiAiM" + "DIyZDAwNGY5ZWMxNmE1OGFkOTAxNGMyNTliNmQ2MTRlZDM2ODgyOWYwMmMzODc3M2M0" "NzIyMWY0OTYxY2UzZjIzIn1dLCAibWludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzgifV19" ) token = TokenV3.deserialize(token_str) @@ -61,24 +71,34 @@ def test_tokenv3_deserialize_serialize(): def test_tokenv3_deserialize_serialize_no_dleq(): token_str = ( - "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjI4ajhueVNMMU5kZCIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogInFvS01lMGk1LVgyQy0zc29mV" - "UZUb0EiLCAiQyI6ICIwMmIzYWVlMjAxZDRmYzlhODJkYTJiZTVmOTQzODlmOWIxMzg4YzBkMDA3N2M2ODg1MDhjODIzZmU4OTMwY2YwYzYiLCAiZGxlcSI6IHsiZ" - "SI6ICJjMmJkNTMzMTkyZDI2MzExMzM2MjU1OTUxZmQyZTI2OWEwY2E2ODViMzgxMDcwYTFhMzRiMDViYjVlYTBkNTAzIiwgInMiOiAiNzUxOTc5MDczZjk3YjcyM" - "mYzNzJkMzRjZDhlNmY5MWRlYzA0ODE4MTg5NjI3YjYwZWFmYjZjMTY3MjFiZjVmOCIsICJCXyI6ICIwMjVhZjliM2ViNmFkZjgxN2M0YzBjMzhhYWYwZjNhYmJjY" - "2MxODFhY2VmMmNjZTQ4Mzg3MWIwYzEyM2UwOTI1NDgiLCAiQ18iOiAiMDNlMDA2YTRiNGI1ODNlMzI5NTNmYzFmMWI4MmQyZTE1MmQyMjdlYTFhZTgyZTEyOWNiY" - "zc3OWQ5NzYwMTc1Mzg4In19LCB7ImlkIjogIjI4ajhueVNMMU5kZCIsICJhbW91bnQiOiA4LCAic2VjcmV0IjogIk1TcEdMMG9Qc3cyU2dFOTdvNmh3emciLCAiQ" - "yI6ICIwM2EyYjJmMTBjOGI2MjQ0ODRhYmNmYTc3MzUwYjhiNWU1NDAwZWFhYmQxNjA0ZTRjYjliYWQyNjJkZmFhOThmYTgiLCAiZGxlcSI6IHsiZSI6ICJkODI0Y" - "zRjNGExNTBmZTQxM2JjM2YzOGNkMGE2NjAxZDU1NWVhYzhjOTNmNDMyOTc0NzQxMGEwOGMzZmYyNTg4IiwgInMiOiAiMjZkZjdhZTk4NzdjN2YyZTE2N2FkZGI4M" - "WRkYjFlNjg1NTE5NmY1NDE3ZDI3MDFiYTdkZmM0NDlkNjYwNTQyZiIsICJCXyI6ICIwMmJlNzQ0NzllOTM0NWU3NWRhNWUzYzliYzcxYjBhMTZlNzZhNDJkMDVjM" - "DA3M2ZjYjgzZmNlYTg3YWJmZGFhYTciLCAiQ18iOiAiMDNkYjA3MjdjYWNjMTQwYTljMzg0YjQxZjJhMjUyYTg3ODI5YWZhMWU4OWJjZTFlMGY4YWQyMGJkNGQ5Z" - "jc4YjgyIn19XSwgIm1pbnQiOiAiaHR0cDovL2xvY2FsaG9zdDozMzM4In1dfQ==" + "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjFjQ05JQVoyWC93MSIsICJhb" + "W91bnQiOiAyLCAic2VjcmV0IjogIjZmZjFiY2VlOGUzMzk2NGE4ZDNjNGQ5NzYwNzdiZ" + "DI4ZGVkZWJkODYyMDU0MDQzNDY4ZjU5ZDFiZjI1OTQzN2QiLCAiQyI6ICIwM2I3ZDlkMzIzY" + "TAxOWJlNTE4NzRlOGE5OGY1NDViOTg3Y2JmNmU5MWUwMDc1YTFhZjQ3MjY2NDMxOGRlZ" + "TQzZTUiLCAiZGxlcSI6IHsiZSI6ICI1ZjkxMGQ4NTc0M2U0OTI0ZjRiNjlkNzhjMjFjYTc1Z" + "jEzNzg3Zjc3OTE1NWRmMjMzMjJmYTA1YjU5ODdhYzNmIiwgInMiOiAiZTc4Y2U0MzNiZ" + "WNlZTNjNGU1NzM4ZDdjMzRlNDQyZWQ0MmJkMzk0MjI0ZTc3MjE4OGFjMmI5MzZmMjA2Y2QxY" + "SIsICJyIjogIjI3MzM3ODNmOTQ4MWZlYzAxNzdlYmM4ZjBhOTI2OWVjOGFkNzU5MDU2ZT" + "k3MTRiMWEwYTEwMDQ3MmY2Y2Y5YzIifX0sIHsiaWQiOiAiMWNDTklBWjJYL3cxIiwgImFtb3" + "VudCI6IDgsICJzZWNyZXQiOiAiMmFkNDMyZDRkNTg2MzJiMmRlMzI0ZmQxYmE5OTcyZmE" + "4MDljNmU3ZGE1ZTkyZWVmYjBiNjYxMmQ5M2Q3ZTAwMCIsICJDIjogIjAzMmFmYjgzOWQwMmR" + "mMWNhOGY5ZGZjNTI1NzUxN2Q0MzY4YjdiMTc0MzgzM2JlYWUzZDQzNmExYmQwYmJkYjVk" + "OCIsICJkbGVxIjogeyJlIjogImY0NjM2MzU5YTUzZGQxNGEyNmUyNTMyMDQxZWIxMDE2OTk1" + "ZTg4NzgwODY0OWFlY2VlNTcwZTA5ZTk2NTU3YzIiLCAicyI6ICJmZWYzMGIzMDcwMDJkMW" + "VjNWZiZjg0ZGZhZmRkMGEwOTdkNDJlMDYxNTZiNzdiMTMzMmNjNGZjNGNjYWEyODJmIiwgIn" + "IiOiAiODQ5MjQxNzBlYzc3ZjhjMDNmZDRlZTkyZTA3MjdlMzYyNTliZjRhYTc4NTBjZTc2" + "NDExMDQ0MmNlNmVlM2FjYyJ9fV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0=" ) token_str_no_dleq = ( - "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjI4ajhueVNMMU5kZCIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogInFvS01lMG" - "k1LVgyQy0zc29mVUZUb0EiLCAiQyI6ICIwMmIzYWVlMjAxZDRmYzlhODJkYTJiZTVmOTQzODlmOWIxMzg4YzBkMDA3N2M2ODg1MDhjODIzZmU" - "4OTMwY2YwYzYifSwgeyJpZCI6ICIyOGo4bnlTTDFOZGQiLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICJNU3BHTDBvUHN3MlNnRTk3bzZod3pn" - "IiwgIkMiOiAiMDNhMmIyZjEwYzhiNjI0NDg0YWJjZmE3NzM1MGI4YjVlNTQwMGVhYWJkMTYwNGU0Y2I5YmFkMjYyZGZhYTk4ZmE4In1dLCAib" - "WludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzgifV19" + "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjFjQ05JQVoyWC93MSIsICJhbW91bn" + "QiOiAyLCAic2VjcmV0IjogIjZmZjFiY2VlOGUzMzk2NGE4ZDNjNGQ5NzYwNzdiZDI4" + "ZGVkZWJkODYyMDU0MDQzNDY4ZjU5ZDFiZjI1OTQzN2QiLCAiQyI6ICIwM2I3ZDlkMzIzYTAxOWJlN" + "TE4NzRlOGE5OGY1NDViOTg3Y2JmNmU5MWUwMDc1YTFhZjQ3MjY2NDMxOGRlZTQzZTU" + "ifSwgeyJpZCI6ICIxY0NOSUFaMlgvdzEiLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICIyYWQ0MzJkN" + "GQ1ODYzMmIyZGUzMjRmZDFiYTk5NzJmYTgwOWM2ZTdkYTVlOTJlZWZiMGI2NjEyZD" + "kzZDdlMDAwIiwgIkMiOiAiMDMyYWZiODM5ZDAyZGYxY2E4ZjlkZmM1MjU3NTE3ZDQzNjhiN2IxNzQz" + "ODMzYmVhZTNkNDM2YTFiZDBiYmRiNWQ4In1dLCAibWludCI6ICJodHRwOi8vbG9jY" + "Wxob3N0OjMzMzgifV19" ) token = TokenV3.deserialize(token_str) assert token.serialize(include_dleq=False) == token_str_no_dleq diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 053b8c10..f8e055db 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -1,5 +1,6 @@ from cashu.core.crypto.b_dhke import ( alice_verify_dleq, + carol_verify_dleq, hash_e, hash_to_curve, step1_alice, @@ -46,9 +47,9 @@ def test_hash_to_curve_iteration(): def test_step1(): - """""" + secret_msg = "test_message" B_, blinding_factor = step1_alice( - "test_message", + secret_msg, blinding_factor=PrivateKey( privkey=bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" @@ -169,9 +170,13 @@ def test_dleq_step2_bob_dleq(): "0000000000000000000000000000000000000000000000000000000000000001" ) # 32 bytes e, s = step2_bob_dleq(B_, a, p_bytes) - assert e.hex() == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" assert ( - s.hex() == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" + e.serialize() + == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" + ) + assert ( + s.serialize() + == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" ) # differs from e only in least significant byte because `a = 0x1` # change `a` @@ -182,27 +187,40 @@ def test_dleq_step2_bob_dleq(): raw=True, ) e, s = step2_bob_dleq(B_, a, p_bytes) - assert e.hex() == "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e" - assert s.hex() == "828404170c86f240c50ae0f5fc17bb6b82612d46b355e046d7cd84b0a3c934a0" + assert ( + e.serialize() + == "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e" + ) + assert ( + s.serialize() + == "828404170c86f240c50ae0f5fc17bb6b82612d46b355e046d7cd84b0a3c934a0" + ) def test_dleq_alice_verify_dleq(): # e from test_step2_bob_dleq for a=0x1 - e = bytes.fromhex( - "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" + e = PrivateKey( + bytes.fromhex( + "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" + ), + raw=True, ) # s from test_step2_bob_dleq for a=0x1 - s = bytes.fromhex( - "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" - ) - # pubkey of a=0x1 - K = PublicKey( + s = PrivateKey( bytes.fromhex( - "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" ), raw=True, ) + a = PrivateKey( + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), + raw=True, + ) + A = a.pubkey + assert A # B_ is the same as we did: # B_, _ = step1_alice( # "test_message", @@ -210,8 +228,11 @@ def test_dleq_alice_verify_dleq(): # "0000000000000000000000000000000000000000000000000000000000000001" # ), # 32 bytes # ) - B_ = bytes.fromhex( - "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + B_ = PublicKey( + bytes.fromhex( + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + ), + raw=True, ) # # C_ is the same as if we did: @@ -223,14 +244,26 @@ def test_dleq_alice_verify_dleq(): # ) # C_, e, s = step2_bob(B_, a) - C_ = bytes.fromhex( - "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + C_ = PublicKey( + bytes.fromhex( + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + ), + raw=True, ) - assert alice_verify_dleq(e, s, K, B_, C_) + assert alice_verify_dleq(e, s, A, B_, C_) - # ----- test again with B_ and C_ as per step1 and step2 +def test_dleq_alice_direct_verify_dleq(): + # ----- test again with B_ and C_ as per step1 and step2 + a = PrivateKey( + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), + raw=True, + ) + A = a.pubkey + assert A B_, _ = step1_alice( "test_message", blinding_factor=PrivateKey( @@ -240,11 +273,30 @@ def test_dleq_alice_verify_dleq(): raw=True, ), ) + C_, e, s = step2_bob(B_, a) + assert alice_verify_dleq(e, s, A, B_, C_) + + +def test_dleq_carol_varify_from_bob(): a = PrivateKey( privkey=bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" ), raw=True, ) + A = a.pubkey + assert A + secret_msg = "test_message" + r = PrivateKey( + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), + raw=True, + ) + B_, _ = step1_alice(secret_msg, r) C_, e, s = step2_bob(B_, a) - assert alice_verify_dleq(e, s, K, B_.serialize(), C_.serialize()) + assert alice_verify_dleq(e, s, A, B_, C_) + 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) diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 634c3e8f..c0df41bf 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -311,6 +311,7 @@ async def test_p2sh_receive_with_wrong_wallet(wallet1: Wallet, wallet2: Wallet): ) # sender side await assert_err(wallet2.redeem(send_proofs), "lock not found.") # wrong receiver + @pytest.mark.asyncio async def test_token_state(wallet1: Wallet): await wallet1.mint(64) @@ -319,6 +320,7 @@ async def test_token_state(wallet1: Wallet): assert resp.dict()["spendable"] assert resp.dict()["pending"] + @pytest.mark.asyncio async def test_bump_secret_derivation(wallet3: Wallet): await wallet3._init_private_key(