From 7dea6040cc338fd5567a017e350a6d54f853e5b8 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:15:08 +0100 Subject: [PATCH 01/57] add derivation paths to example (#389) --- .env.example | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index d3b11cc9..7cf46c0c 100644 --- a/.env.example +++ b/.env.example @@ -24,6 +24,9 @@ NOSTR_RELAYS=["wss://nostr-pub.wellorder.net"] # Wallet API port API_PORT=4448 +# Wallet default unit +WALLET_UNIT="sat" + # --------- MINT --------- # Network @@ -38,8 +41,17 @@ MINT_INFO_CONTACT=[["email","contact@me.com"], ["twitter","@me"], ["nostr", "np MINT_INFO_MOTD="Message to users" MINT_PRIVATE_KEY=supersecretprivatekey -# increment derivation path to rotate to a new keyset -MINT_DERIVATION_PATH="0/0/0/0" + +# Increment derivation path to rotate to a new keyset +# Example: m/0'/0'/0' -> m/0'/0'/1' +MINT_DERIVATION_PATH="m/0'/0'/0'" + +# Multiple derivation paths and units. Unit is parsed from the derivation path. +# m/0'/0'/0' is "sat" (default) +# m/0'/1'/0' is "msat" +# m/0'/2'/0' is "usd" +# In this example, we have 2 keysets for sat, 1 for msat and 1 for usd +# MINT_DERIVATION_PATH_LIST=["m/0'/0'/0'", "m/0'/0'/1'", "m/0'/1'/0'", "m/0'/2'/0'"] MINT_DATABASE=data/mint From a2709703f514c760cd653f3469b0b25915075e18 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 21 Jan 2024 20:45:40 +0100 Subject: [PATCH 02/57] skip change output amount verification during melt (#393) * skip change output amount verification during melt * make format --- cashu/mint/ledger.py | 2 +- cashu/mint/verification.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 78360984..7c61c458 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -625,7 +625,7 @@ async def melt( # make sure that the outputs (for fee return) are in the same unit as the quote if outputs: - await self._verify_outputs(outputs) + await self._verify_outputs(outputs, skip_amount_check=True) assert outputs[0].id, "output id not set" outputs_unit = self.keysets[outputs[0].id].unit assert melt_quote.unit == outputs_unit.name, ( diff --git a/cashu/mint/verification.py b/cashu/mint/verification.py index 06eb8b72..f1e93879 100644 --- a/cashu/mint/verification.py +++ b/cashu/mint/verification.py @@ -96,7 +96,9 @@ async def verify_inputs_and_outputs( if outputs and not self._verify_output_spending_conditions(proofs, outputs): raise TransactionError("validation of output spending conditions failed.") - async def _verify_outputs(self, outputs: List[BlindedMessage]): + async def _verify_outputs( + self, outputs: List[BlindedMessage], skip_amount_check=False + ): """Verify that the outputs are valid.""" logger.trace(f"Verifying {len(outputs)} outputs.") # Verify all outputs have the same keyset id @@ -108,7 +110,11 @@ async def _verify_outputs(self, outputs: List[BlindedMessage]): if not self.keysets[outputs[0].id].active: raise TransactionError("keyset id inactive.") # Verify amounts of outputs - if not all([self._verify_amount(o.amount) for o in outputs]): + # we skip the amount check for NUT-8 change outputs (which can have amount 0) + if ( + not all([self._verify_amount(o.amount) for o in outputs]) + and not skip_amount_check + ): raise TransactionError("invalid amount.") # verify that only unique outputs were used if not self._verify_no_duplicate_outputs(outputs): From 911886892dd1d53f278c90000c774e3c1a81bbca Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 21 Jan 2024 20:52:15 +0100 Subject: [PATCH 03/57] skip amount refactor (#394) --- cashu/mint/verification.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cashu/mint/verification.py b/cashu/mint/verification.py index f1e93879..48f08b3c 100644 --- a/cashu/mint/verification.py +++ b/cashu/mint/verification.py @@ -111,11 +111,9 @@ async def _verify_outputs( raise TransactionError("keyset id inactive.") # Verify amounts of outputs # we skip the amount check for NUT-8 change outputs (which can have amount 0) - if ( - not all([self._verify_amount(o.amount) for o in outputs]) - and not skip_amount_check - ): - raise TransactionError("invalid amount.") + if not skip_amount_check: + if not all([self._verify_amount(o.amount) for o in outputs]): + raise TransactionError("invalid amount.") # verify that only unique outputs were used if not self._verify_no_duplicate_outputs(outputs): raise TransactionError("duplicate outputs.") From 2645eb51bcf9b7e78eda94aedbf4e0f10ad5e750 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 21 Jan 2024 22:02:04 +0100 Subject: [PATCH 04/57] fix melt requiring outputs --- cashu/core/base.py | 4 ++-- tests/test_mint_api_deprecated.py | 38 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 8abc69f5..c3ab37e4 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -365,7 +365,7 @@ class PostMeltRequest(BaseModel): quote: str = Field(..., max_length=settings.mint_max_request_length) # quote id inputs: List[Proof] = Field(..., max_items=settings.mint_max_request_length) outputs: Union[List[BlindedMessage], None] = Field( - ..., max_items=settings.mint_max_request_length + None, max_items=settings.mint_max_request_length ) @@ -379,7 +379,7 @@ class PostMeltRequest_deprecated(BaseModel): proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length) pr: str = Field(..., max_length=settings.mint_max_request_length) outputs: Union[List[BlindedMessage], None] = Field( - ..., max_items=settings.mint_max_request_length + None, max_items=settings.mint_max_request_length ) diff --git a/tests/test_mint_api_deprecated.py b/tests/test_mint_api_deprecated.py index 12676ebc..fcdcf8cd 100644 --- a/tests/test_mint_api_deprecated.py +++ b/tests/test_mint_api_deprecated.py @@ -186,6 +186,44 @@ async def test_melt_internal(ledger: Ledger, wallet: Wallet): assert result["paid"] is True +@pytest.mark.asyncio +async def test_melt_internal_no_change_outputs(ledger: Ledger, wallet: Wallet): + # Clients without NUT-08 will not send change outputs + # internal invoice + invoice = await wallet.request_mint(64) + pay_if_regtest(invoice.bolt11) + await wallet.mint(64, id=invoice.id) + assert wallet.balance == 64 + + # create invoice to melt to + invoice = await wallet.request_mint(64) + + invoice_payment_request = invoice.bolt11 + + quote = await wallet.melt_quote(invoice_payment_request) + assert quote.amount == 64 + assert quote.fee_reserve == 0 + + inputs_payload = [p.to_dict() for p in wallet.proofs] + + # outputs for change + secrets, rs, derivation_paths = await wallet.generate_n_secrets(1) + outputs, rs = wallet._construct_outputs([2], secrets, rs) + + response = httpx.post( + f"{BASE_URL}/melt", + json={ + "pr": invoice_payment_request, + "proofs": inputs_payload, + }, + timeout=None, + ) + assert response.status_code == 200, f"{response.url} {response.status_code}" + result = response.json() + assert result.get("preimage") is not None + assert result["paid"] is True + + @pytest.mark.asyncio @pytest.mark.skipif( is_fake, From b307c4db792570e04e2d7abe78465f1eae33bd27 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 24 Jan 2024 22:02:25 +0100 Subject: [PATCH 05/57] Ngutech21-fix-get-quote-api (#397) * fix: v1 mint-quote api in router * fix get mint quote * test new endpoints --------- Co-authored-by: ngutech21 --- cashu/mint/router.py | 12 ++++++------ tests/test_mint_api.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 5cf303b6..6219b19a 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -190,7 +190,7 @@ async def mint_quote(payload: PostMintQuoteRequest) -> PostMintQuoteResponse: @router.get( - "/v1/mint/quote/{quote}", + "/v1/mint/quote/bolt11/{quote}", summary="Get mint quote", response_model=PostMintQuoteResponse, response_description="Get an existing mint quote to check its status.", @@ -199,7 +199,7 @@ async def get_mint_quote(quote: str) -> PostMintQuoteResponse: """ Get mint quote state. """ - logger.trace(f"> POST /v1/mint/quote/{quote}") + logger.trace(f"> GET /v1/mint/quote/bolt11/{quote}") mint_quote = await ledger.get_mint_quote(quote) resp = PostMintQuoteResponse( quote=mint_quote.quote, @@ -207,7 +207,7 @@ async def get_mint_quote(quote: str) -> PostMintQuoteResponse: paid=mint_quote.paid, expiry=mint_quote.expiry, ) - logger.trace(f"< POST /v1/mint/quote/{quote}") + logger.trace(f"< GET /v1/mint/quote/bolt11/{quote}") return resp @@ -253,7 +253,7 @@ async def get_melt_quote(payload: PostMeltQuoteRequest) -> PostMeltQuoteResponse @router.get( - "/v1/melt/quote/{quote}", + "/v1/melt/quote/bolt11/{quote}", summary="Get melt quote", response_model=PostMeltQuoteResponse, response_description="Get an existing melt quote to check its status.", @@ -262,7 +262,7 @@ async def melt_quote(quote: str) -> PostMeltQuoteResponse: """ Get melt quote state. """ - logger.trace(f"> POST /v1/melt/quote/{quote}") + logger.trace(f"> GET /v1/melt/quote/bolt11/{quote}") melt_quote = await ledger.get_melt_quote(quote) resp = PostMeltQuoteResponse( quote=melt_quote.quote, @@ -270,7 +270,7 @@ async def melt_quote(quote: str) -> PostMeltQuoteResponse: fee_reserve=melt_quote.fee_reserve, paid=melt_quote.paid, ) - logger.trace(f"< POST /v1/melt/quote/{quote}") + logger.trace(f"< GET /v1/melt/quote/bolt11/{quote}") return resp diff --git a/tests/test_mint_api.py b/tests/test_mint_api.py index 14de13b2..d46dd3e7 100644 --- a/tests/test_mint_api.py +++ b/tests/test_mint_api.py @@ -183,6 +183,14 @@ async def test_mint_quote(ledger: Ledger): invoice = bolt11.decode(result["request"]) assert invoice.amount_msat == 100 * 1000 + # get mint quote again from api + response = httpx.get( + f"{BASE_URL}/v1/mint/quote/bolt11/{result['quote']}", + ) + assert response.status_code == 200, f"{response.url} {response.status_code}" + result2 = response.json() + assert result2["quote"] == result["quote"] + @pytest.mark.asyncio @pytest.mark.skipif( @@ -236,6 +244,14 @@ async def test_melt_quote_internal(ledger: Ledger, wallet: Wallet): # TODO: internal invoice, fee should be 0 assert result["fee_reserve"] == 0 + # get melt quote again from api + response = httpx.get( + f"{BASE_URL}/v1/melt/quote/bolt11/{result['quote']}", + ) + assert response.status_code == 200, f"{response.url} {response.status_code}" + result2 = response.json() + assert result2["quote"] == result["quote"] + @pytest.mark.asyncio @pytest.mark.skipif( From 7b5192c27a6de0f6d0fb68fc89bc434d1b9221b4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:56:41 +0100 Subject: [PATCH 06/57] fix: do not serialize-deserialize secret for p2pk signature check (#398) --- cashu/core/p2pk.py | 6 +++--- cashu/mint/conditions.py | 2 +- cashu/wallet/p2pk.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cashu/core/p2pk.py b/cashu/core/p2pk.py index fab14967..f42a3a95 100644 --- a/cashu/core/p2pk.py +++ b/cashu/core/p2pk.py @@ -68,16 +68,16 @@ def n_sigs(self) -> Union[None, int]: return int(n_sigs) if n_sigs else None -def sign_p2pk_sign(message: bytes, private_key: PrivateKey): +def sign_p2pk_sign(message: bytes, private_key: PrivateKey) -> bytes: # ecdsa version # signature = private_key.ecdsa_serialize(private_key.ecdsa_sign(message)) signature = private_key.schnorr_sign( hashlib.sha256(message).digest(), None, raw=True ) - return signature.hex() + return signature -def verify_p2pk_signature(message: bytes, pubkey: PublicKey, signature: bytes): +def verify_p2pk_signature(message: bytes, pubkey: PublicKey, signature: bytes) -> bool: # ecdsa version # return pubkey.ecdsa_verify(message, pubkey.ecdsa_deserialize(signature)) return pubkey.schnorr_verify( diff --git a/cashu/mint/conditions.py b/cashu/mint/conditions.py index d48c06ef..e187ac06 100644 --- a/cashu/mint/conditions.py +++ b/cashu/mint/conditions.py @@ -83,7 +83,7 @@ def _verify_p2pk_spending_conditions(self, proof: Proof, secret: Secret) -> bool logger.trace(f"verifying signature {input_sig} by pubkey {pubkey}.") logger.trace(f"Message: {p2pk_secret.serialize().encode('utf-8')}") if verify_p2pk_signature( - message=p2pk_secret.serialize().encode("utf-8"), + message=proof.secret.encode("utf-8"), pubkey=PublicKey(bytes.fromhex(pubkey), raw=True), signature=bytes.fromhex(input_sig), ): diff --git a/cashu/wallet/p2pk.py b/cashu/wallet/p2pk.py index a2d824c7..e246409f 100644 --- a/cashu/wallet/p2pk.py +++ b/cashu/wallet/p2pk.py @@ -79,7 +79,7 @@ async def sign_p2pk_proofs(self, proofs: List[Proof]) -> List[str]: sign_p2pk_sign( message=proof.secret.encode("utf-8"), private_key=private_key, - ) + ).hex() for proof in proofs ] logger.debug(f"Signatures: {signatures}") @@ -95,7 +95,7 @@ async def sign_p2pk_outputs(self, outputs: List[BlindedMessage]) -> List[str]: sign_p2pk_sign( message=output.B_.encode("utf-8"), private_key=private_key, - ) + ).hex() for output in outputs ] From bc2b555c16a2e68097d6758549099e0231409ea4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:22:16 +0100 Subject: [PATCH 07/57] sign outputs as bytes (#399) --- cashu/mint/conditions.py | 6 +++--- cashu/wallet/p2pk.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cashu/mint/conditions.py b/cashu/mint/conditions.py index e187ac06..051947c3 100644 --- a/cashu/mint/conditions.py +++ b/cashu/mint/conditions.py @@ -154,7 +154,7 @@ def _verify_htlc_spending_conditions(self, proof: Proof, secret: Secret) -> bool assert signature, TransactionError("no HTLC refund signature provided") for pubkey in refund_pubkeys: if verify_p2pk_signature( - message=htlc_secret.serialize().encode("utf-8"), + message=proof.secret.encode("utf-8"), pubkey=PublicKey(bytes.fromhex(pubkey), raw=True), signature=bytes.fromhex(signature), ): @@ -181,7 +181,7 @@ def _verify_htlc_spending_conditions(self, proof: Proof, secret: Secret) -> bool assert signature, TransactionError("HTLC no hash lock signatures provided.") for pubkey in hashlock_pubkeys: if verify_p2pk_signature( - message=htlc_secret.serialize().encode("utf-8"), + message=proof.secret.encode("utf-8"), pubkey=PublicKey(bytes.fromhex(pubkey), raw=True), signature=bytes.fromhex(signature), ): @@ -305,7 +305,7 @@ def _verify_output_p2pk_spending_conditions( for sig in p2pksigs: for pubkey in pubkeys: if verify_p2pk_signature( - message=output.B_.encode("utf-8"), + message=bytes.fromhex(output.B_), pubkey=PublicKey(bytes.fromhex(pubkey), raw=True), signature=bytes.fromhex(sig), ): diff --git a/cashu/wallet/p2pk.py b/cashu/wallet/p2pk.py index e246409f..aff057c0 100644 --- a/cashu/wallet/p2pk.py +++ b/cashu/wallet/p2pk.py @@ -93,7 +93,7 @@ async def sign_p2pk_outputs(self, outputs: List[BlindedMessage]) -> List[str]: assert private_key.pubkey return [ sign_p2pk_sign( - message=output.B_.encode("utf-8"), + message=bytes.fromhex(output.B_), private_key=private_key, ).hex() for output in outputs From 6ddce571a071d81aec6f4e2a196c5ee9452ad394 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 2 Feb 2024 21:25:02 +0100 Subject: [PATCH 08/57] Keysets per seed and postgres (#400) * allow generation of keys per seed phrase * emit errors correctly * parse timestamps for melt and mint quotes correctly * error messages * adjust error message * postgres works * prepare postgres tests * timestamps refactor * add command to key activation * generate keys per seed * add keyset tests * keyest uniqueness constaint on (derivation_path, seed) * add tables ony if not exists * log leve --- .github/workflows/ci.yml | 2 +- cashu/core/base.py | 75 +++++++++++++++++++++++++++----- cashu/core/db.py | 23 +++++++++- cashu/core/errors.py | 18 ++++---- cashu/mint/crud.py | 44 +++++++++++-------- cashu/mint/ledger.py | 59 ++++++++++++------------- cashu/mint/migrations.py | 77 ++++++++++++++++++++++++++++----- cashu/mint/router.py | 23 ++++------ cashu/mint/router_deprecated.py | 10 ++--- cashu/mint/startup.py | 4 +- tests/test_mint_db.py | 63 +++++++++++++++++++++++++++ tests/test_mint_keysets.py | 57 ++++++++++++++++++++++++ 12 files changed, 352 insertions(+), 103 deletions(-) create mode 100644 tests/test_mint_db.py create mode 100644 tests/test_mint_keysets.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 873fce80..95be9061 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: poetry-version: ["1.7.1"] mint-cache-secrets: ["false", "true"] mint-only-deprecated: ["false", "true"] - # db-url: ["", "postgres://cashu:cashu@localhost:5432/test"] # TODO: Postgres test not working + # db-url: ["", "postgres://cashu:cashu@localhost:5432/cashu"] # TODO: Postgres test not working db-url: [""] backend-wallet-class: ["FakeWallet"] uses: ./.github/workflows/tests.yml diff --git a/cashu/core/base.py b/cashu/core/base.py index c3ab37e4..071fbf39 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -218,11 +218,37 @@ class MeltQuote(BaseModel): amount: int fee_reserve: int paid: bool - created_time: int = 0 - paid_time: int = 0 + created_time: Union[int, None] = None + paid_time: Union[int, None] = None fee_paid: int = 0 proof: str = "" + @classmethod + def from_row(cls, row: Row): + try: + created_time = int(row["created_time"]) if row["created_time"] else None + paid_time = int(row["paid_time"]) if row["paid_time"] else None + except Exception: + created_time = ( + int(row["created_time"].timestamp()) if row["created_time"] else None + ) + paid_time = int(row["paid_time"].timestamp()) if row["paid_time"] else None + + return cls( + quote=row["quote"], + method=row["method"], + request=row["request"], + checking_id=row["checking_id"], + unit=row["unit"], + amount=row["amount"], + fee_reserve=row["fee_reserve"], + paid=row["paid"], + created_time=created_time, + paid_time=paid_time, + fee_paid=row["fee_paid"], + proof=row["proof"], + ) + class MintQuote(BaseModel): quote: str @@ -233,10 +259,37 @@ class MintQuote(BaseModel): amount: int paid: bool issued: bool - created_time: int = 0 - paid_time: int = 0 + created_time: Union[int, None] = None + paid_time: Union[int, None] = None expiry: int = 0 + @classmethod + def from_row(cls, row: Row): + + try: + # SQLITE: row is timestamp (string) + created_time = int(row["created_time"]) if row["created_time"] else None + paid_time = int(row["paid_time"]) if row["paid_time"] else None + except Exception: + # POSTGRES: row is datetime.datetime + created_time = ( + int(row["created_time"].timestamp()) if row["created_time"] else None + ) + paid_time = int(row["paid_time"].timestamp()) if row["paid_time"] else None + return cls( + quote=row["quote"], + method=row["method"], + request=row["request"], + checking_id=row["checking_id"], + unit=row["unit"], + amount=row["amount"], + paid=row["paid"], + issued=row["issued"], + created_time=created_time, + paid_time=paid_time, + expiry=0, + ) + # ------- API ------- @@ -640,7 +693,7 @@ class MintKeyset: active: bool unit: Unit derivation_path: str - seed: Optional[str] = None + seed: str public_keys: Union[Dict[int, PublicKey], None] = None valid_from: Union[str, None] = None valid_to: Union[str, None] = None @@ -652,17 +705,17 @@ class MintKeyset: def __init__( self, *, + seed: str, + derivation_path: str, id="", valid_from=None, valid_to=None, first_seen=None, active=None, - seed: Optional[str] = None, - derivation_path: Optional[str] = None, unit: Optional[str] = None, version: str = "0", ): - self.derivation_path = derivation_path or "" + self.derivation_path = derivation_path self.seed = seed self.id = id self.valid_from = valid_from @@ -696,8 +749,10 @@ def __init__( self.unit = Unit[unit] # generate keys from seed - if self.seed and self.derivation_path: - self.generate_keys() + assert self.seed, "seed not set" + assert self.derivation_path, "derivation path not set" + + self.generate_keys() logger.debug(f"Keyset id: {self.id} ({self.unit.name})") diff --git a/cashu/core/db.py b/cashu/core/db.py index 1f96910a..1863c605 100644 --- a/cashu/core/db.py +++ b/cashu/core/db.py @@ -31,7 +31,8 @@ def timestamp_now(self) -> str: if self.type in {POSTGRES, COCKROACH}: return "now()" elif self.type == SQLITE: - return "(strftime('%s', 'now'))" + # return "(strftime('%s', 'now'))" + return str(int(time.time())) return "" @property @@ -204,6 +205,26 @@ def lock_table(db: Database, table: str) -> str: return "" +def timestamp_from_seconds( + db: Database, seconds: Union[int, float, None] +) -> Union[str, None]: + if seconds is None: + return None + seconds = int(seconds) + if db.type in {POSTGRES, COCKROACH}: + return datetime.datetime.fromtimestamp(seconds).strftime("%Y-%m-%d %H:%M:%S") + elif db.type == SQLITE: + return str(seconds) + return None + + +def timestamp_now(db: Database) -> str: + timestamp = timestamp_from_seconds(db, time.time()) + if timestamp is None: + raise Exception("Timestamp is None") + return timestamp + + @asynccontextmanager async def get_db_connection(db: Database, conn: Optional[Connection] = None): """Either yield the existing database connection or create a new one. diff --git a/cashu/core/errors.py b/cashu/core/errors.py index fa2ca4af..d36614a4 100644 --- a/cashu/core/errors.py +++ b/cashu/core/errors.py @@ -12,7 +12,7 @@ def __init__(self, detail, code=0): class NotAllowedError(CashuError): - detail = "Not allowed." + detail = "not allowed" code = 10000 def __init__(self, detail: Optional[str] = None, code: Optional[int] = None): @@ -20,7 +20,7 @@ def __init__(self, detail: Optional[str] = None, code: Optional[int] = None): class TransactionError(CashuError): - detail = "Transaction error." + detail = "transaction error" code = 11000 def __init__(self, detail: Optional[str] = None, code: Optional[int] = None): @@ -36,7 +36,7 @@ def __init__(self): class SecretTooLongError(TransactionError): - detail = "Secret too long." + detail = "secret too long" code = 11003 def __init__(self): @@ -44,7 +44,7 @@ def __init__(self): class NoSecretInProofsError(TransactionError): - detail = "No secret in proofs." + detail = "no secret in proofs" code = 11004 def __init__(self): @@ -52,7 +52,7 @@ def __init__(self): class KeysetError(CashuError): - detail = "Keyset error." + detail = "keyset error" code = 12000 def __init__(self, detail: Optional[str] = None, code: Optional[int] = None): @@ -60,7 +60,7 @@ def __init__(self, detail: Optional[str] = None, code: Optional[int] = None): class KeysetNotFoundError(KeysetError): - detail = "Keyset not found." + detail = "keyset not found" code = 12001 def __init__(self): @@ -68,15 +68,15 @@ def __init__(self): class LightningError(CashuError): - detail = "Lightning error." + detail = "Lightning error" code = 20000 def __init__(self, detail: Optional[str] = None, code: Optional[int] = None): super().__init__(detail or self.detail, code=code or self.code) -class InvoiceNotPaidError(CashuError): - detail = "Lightning invoice not paid yet." +class QuoteNotPaidError(CashuError): + detail = "quote not paid" code = 20001 def __init__(self): diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index 2d9660a3..06ce347a 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -1,4 +1,3 @@ -import time from abc import ABC, abstractmethod from typing import Any, List, Optional @@ -9,7 +8,13 @@ MintQuote, Proof, ) -from ..core.db import Connection, Database, table_with_schema +from ..core.db import ( + Connection, + Database, + table_with_schema, + timestamp_from_seconds, + timestamp_now, +) class LedgerCrud(ABC): @@ -27,6 +32,7 @@ async def get_keyset( db: Database, id: str = "", derivation_path: str = "", + seed: str = "", conn: Optional[Connection] = None, ) -> List[MintKeyset]: ... @@ -223,7 +229,7 @@ async def store_promise( e, s, id, - int(time.time()), + timestamp_now(db), ), ) @@ -274,7 +280,7 @@ async def invalidate_proof( proof.secret, proof.id, proof.witness, - int(time.time()), + timestamp_now(db), ), ) @@ -307,7 +313,7 @@ async def set_proof_pending( proof.amount, str(proof.C), str(proof.secret), - int(time.time()), + timestamp_now(db), ), ) @@ -348,8 +354,8 @@ async def store_mint_quote( quote.amount, quote.issued, quote.paid, - quote.created_time, - quote.paid_time, + timestamp_from_seconds(db, quote.created_time), + timestamp_from_seconds(db, quote.paid_time), ), ) @@ -367,7 +373,7 @@ async def get_mint_quote( """, (quote_id,), ) - return MintQuote(**dict(row)) if row else None + return MintQuote.from_row(row) if row else None async def get_mint_quote_by_checking_id( self, @@ -383,7 +389,7 @@ async def get_mint_quote_by_checking_id( """, (checking_id,), ) - return MintQuote(**dict(row)) if row else None + return MintQuote.from_row(row) if row else None async def update_mint_quote( self, @@ -398,7 +404,7 @@ async def update_mint_quote( ( quote.issued, quote.paid, - quote.paid_time, + timestamp_from_seconds(db, quote.paid_time), quote.quote, ), ) @@ -442,8 +448,8 @@ async def store_melt_quote( quote.amount, quote.fee_reserve or 0, quote.paid, - quote.created_time, - quote.paid_time, + timestamp_from_seconds(db, quote.created_time), + timestamp_from_seconds(db, quote.paid_time), quote.fee_paid, quote.proof, ), @@ -481,7 +487,7 @@ async def get_melt_quote( ) if row is None: return None - return MeltQuote(**dict(row)) if row else None + return MeltQuote.from_row(row) if row else None async def update_melt_quote( self, @@ -496,7 +502,7 @@ async def update_melt_quote( ( quote.paid, quote.fee_paid, - quote.paid_time, + timestamp_from_seconds(db, quote.paid_time), quote.proof, quote.quote, ), @@ -519,9 +525,9 @@ async def store_keyset( keyset.id, keyset.seed, keyset.derivation_path, - keyset.valid_from or int(time.time()), - keyset.valid_to or int(time.time()), - keyset.first_seen or int(time.time()), + keyset.valid_from or timestamp_now(db), + keyset.valid_to or timestamp_now(db), + keyset.first_seen or timestamp_now(db), True, keyset.version, keyset.unit.name, @@ -545,6 +551,7 @@ async def get_keyset( db: Database, id: Optional[str] = None, derivation_path: Optional[str] = None, + seed: Optional[str] = None, unit: Optional[str] = None, active: Optional[bool] = None, conn: Optional[Connection] = None, @@ -560,6 +567,9 @@ async def get_keyset( if derivation_path is not None: clauses.append("derivation_path = ?") values.append(derivation_path) + if seed is not None: + clauses.append("seed = ?") + values.append(seed) if unit is not None: clauses.append("unit = ?") values.append(unit) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 7c61c458..8b07d939 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -38,6 +38,7 @@ KeysetNotFoundError, LightningError, NotAllowedError, + QuoteNotPaidError, TransactionError, ) from ..core.helpers import sum_proofs @@ -81,7 +82,14 @@ def __init__( # ------- KEYS ------- - async def activate_keyset(self, derivation_path, autosave=True) -> MintKeyset: + async def activate_keyset( + self, + *, + derivation_path: str, + seed: Optional[str] = None, + version: Optional[str] = None, + autosave=True, + ) -> MintKeyset: """Load the keyset for a derivation path if it already exists. If not generate new one and store in the db. Args: @@ -91,29 +99,25 @@ async def activate_keyset(self, derivation_path, autosave=True) -> MintKeyset: Returns: MintKeyset: Keyset """ + assert derivation_path, "derivation path not set" + seed = seed or self.master_key logger.debug(f"Activating keyset for derivation path {derivation_path}") # load the keyset from db logger.trace(f"crud: loading keyset for {derivation_path}") tmp_keyset_local: List[MintKeyset] = await self.crud.get_keyset( - derivation_path=derivation_path, db=self.db + derivation_path=derivation_path, seed=seed, db=self.db ) logger.trace(f"crud: loaded {len(tmp_keyset_local)} keysets") if tmp_keyset_local: # we have a keyset with this derivation path in the database keyset = tmp_keyset_local[0] - # we keys are not stored in the database but only their derivation path - # so we might need to generate the keys for keysets loaded from the database - if not len(keyset.private_keys): - keyset.generate_keys() - else: - logger.trace(f"crud: no keyset for {derivation_path}") # no keyset for this derivation path yet # we create a new keyset (keys will be generated at instantiation) keyset = MintKeyset( - seed=self.master_key, + seed=seed or self.master_key, derivation_path=derivation_path, - version=settings.version, + version=version or settings.version, ) logger.debug(f"Generated new keyset {keyset.id}.") if autosave: @@ -144,33 +148,24 @@ async def init_keysets(self, autosave=True) -> None: not in the database yet. Will be passed to `self.activate_keyset` where it is generated from `self.derivation_path`. Defaults to True. """ - # load all past keysets from db + # load all past keysets from db, the keys will be generated at instantiation tmp_keysets: List[MintKeyset] = await self.crud.get_keyset(db=self.db) - logger.debug( - f"Loaded {len(tmp_keysets)} keysets from database. Generating keys..." - ) - # add keysets from db to current keysets + + # add keysets from db to memory for k in tmp_keysets: self.keysets[k.id] = k - # generate keys for all keysets in the database - for _, v in self.keysets.items(): - # if we already generated the keys for this keyset, skip - if v.id and v.public_keys and len(v.public_keys): - continue - logger.trace(f"Generating keys for keyset {v.id}") - v.seed = self.master_key - v.generate_keys() - - logger.info(f"Initialized {len(self.keysets)} keysets from the database.") - # activate the current keyset set by self.derivation_path - self.keyset = await self.activate_keyset(self.derivation_path, autosave) + if self.derivation_path: + self.keyset = await self.activate_keyset( + derivation_path=self.derivation_path, autosave=autosave + ) + logger.info(f"Current keyset: {self.keyset.id}") + logger.info( - "Activated keysets from database:" + f"Loaded {len(self.keysets)} keysets:" f" {[f'{k} ({v.unit.name})' for k, v in self.keysets.items()]}" ) - logger.info(f"Current keyset: {self.keyset.id}") # check that we have a least one active keyset assert any([k.active for k in self.keysets.values()]), "No active keyset found." @@ -189,7 +184,6 @@ async def init_keysets(self, autosave=True) -> None: self.keysets[keyset_copy.id] = keyset_copy # remember which keyset this keyset was duplicated from logger.debug(f"Duplicated keyset id {keyset.id} -> {keyset_copy.id}") - # END BACKWARDS COMPATIBILITY < 0.15.0 def get_keyset(self, keyset_id: Optional[str] = None) -> Dict[int, str]: @@ -295,6 +289,7 @@ async def mint_quote(self, quote_request: PostMintQuoteRequest) -> MintQuote: MintQuote: Mint quote object. """ logger.trace("called request_mint") + assert quote_request.amount > 0, "amount must be positive" if settings.mint_max_peg_in and quote_request.amount > settings.mint_max_peg_in: raise NotAllowedError( f"Maximum mint amount is {settings.mint_max_peg_in} sat." @@ -368,6 +363,7 @@ async def get_mint_quote(self, quote_id: str) -> MintQuote: if status.paid: logger.trace(f"Setting quote {quote_id} as paid") quote.paid = True + quote.paid_time = int(time.time()) await self.crud.update_mint_quote(quote=quote, db=self.db) return quote @@ -404,7 +400,7 @@ async def mint( ) # create a new lock if it doesn't exist async with self.locks[quote_id]: quote = await self.get_mint_quote(quote_id=quote_id) - assert quote.paid, "quote not paid" + assert quote.paid, QuoteNotPaidError() assert not quote.issued, "quote already issued" assert ( quote.amount == sum_amount_outputs @@ -593,6 +589,7 @@ async def melt_mint_settle_internally(self, melt_quote: MeltQuote) -> MeltQuote: await self.crud.update_melt_quote(quote=melt_quote, db=self.db) mint_quote.paid = True + mint_quote.paid_time = melt_quote.paid_time await self.crud.update_mint_quote(quote=mint_quote, db=self.db) return melt_quote diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 72160405..1f360f66 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -1,6 +1,4 @@ -import time - -from ..core.db import Connection, Database, table_with_schema +from ..core.db import SQLITE, Connection, Database, table_with_schema, timestamp_now from ..core.settings import settings @@ -224,18 +222,34 @@ async def m010_add_index_to_proofs_used(db: Database): async def m011_add_quote_tables(db: Database): + async def get_columns(db: Database, conn: Connection, table: str): + if db.type == SQLITE: + query = f"PRAGMA table_info({table})" + else: + query = ( + "SELECT column_name FROM information_schema.columns WHERE table_name =" + f" '{table}'" + ) + res = await conn.execute(query) + if db.type == SQLITE: + return [r["name"] async for r in res] + else: + return [r["column_name"] async for r in res] + async with db.connect() as conn: # add column "created" to tables invoices, promises, proofs_used, proofs_pending tables = ["invoices", "promises", "proofs_used", "proofs_pending"] for table in tables: - await conn.execute( - f"ALTER TABLE {table_with_schema(db, table)} ADD COLUMN created" - " TIMESTAMP" - ) - await conn.execute( - f"UPDATE {table_with_schema(db, table)} SET created =" - f" '{int(time.time())}'" - ) + columns = await get_columns(db, conn, table) + if "created" not in columns: + await conn.execute( + f"ALTER TABLE {table_with_schema(db, table)} ADD COLUMN created" + " TIMESTAMP" + ) + await conn.execute( + f"UPDATE {table_with_schema(db, table)} SET created =" + f" '{timestamp_now(db)}'" + ) # add column "witness" to table proofs_used await conn.execute( @@ -299,8 +313,47 @@ async def m011_add_quote_tables(db: Database): f"INSERT INTO {table_with_schema(db, 'mint_quotes')} (quote, method," " request, checking_id, unit, amount, paid, issued, created_time," " paid_time) SELECT id, 'bolt11', bolt11, payment_hash, 'sat', amount," - f" False, issued, created, 0 FROM {table_with_schema(db, 'invoices')} " + f" False, issued, created, NULL FROM {table_with_schema(db, 'invoices')} " ) # drop table invoices await conn.execute(f"DROP TABLE {table_with_schema(db, 'invoices')}") + + +async def m012_keysets_uniqueness_with_seed(db: Database): + # copy table keysets to keysets_old, create a new table keysets + # with the same columns but with a unique constraint on (seed, derivation_path) + # and copy the data from keysets_old to keysets, then drop keysets_old + async with db.connect() as conn: + await conn.execute( + f"DROP TABLE IF EXISTS {table_with_schema(db, 'keysets_old')}" + ) + await conn.execute( + f"CREATE TABLE {table_with_schema(db, 'keysets_old')} AS" + f" SELECT * FROM {table_with_schema(db, 'keysets')}" + ) + await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets')}") + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} ( + id TEXT NOT NULL, + derivation_path TEXT, + seed TEXT, + valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + active BOOL DEFAULT TRUE, + version TEXT, + unit TEXT, + + UNIQUE (seed, derivation_path) + + ); + """) + await conn.execute( + f"INSERT INTO {table_with_schema(db, 'keysets')} (id," + " derivation_path, valid_from, valid_to, first_seen," + " active, version, seed, unit) SELECT id, derivation_path," + " valid_from, valid_to, first_seen, active, version, seed," + f" unit FROM {table_with_schema(db, 'keysets_old')}" + ) + await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets_old')}") diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 6219b19a..02289bcc 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -1,6 +1,6 @@ from typing import Any, Dict, List -from fastapi import APIRouter, Request +from fastapi import APIRouter from loguru import logger from ..core.base import ( @@ -80,8 +80,7 @@ async def info() -> GetInfoResponse: name="Mint public keys", summary="Get the public keys of the newest mint keyset", response_description=( - "A dictionary of all supported token values of the mint and their associated" - " public key of the current keyset." + "All supported token values their associated public keys for all active keysets" ), response_model=KeysResponse, ) @@ -107,12 +106,12 @@ async def keys(): name="Keyset public keys", summary="Public keys of a specific keyset", response_description=( - "A dictionary of all supported token values of the mint and their associated" + "All supported token values of the mint and their associated" " public key for a specific keyset." ), response_model=KeysResponse, ) -async def keyset_keys(keyset_id: str, request: Request) -> KeysResponse: +async def keyset_keys(keyset_id: str) -> KeysResponse: """ Get the public keys of the mint from a specific keyset id. """ @@ -127,7 +126,7 @@ async def keyset_keys(keyset_id: str, request: Request) -> KeysResponse: keyset = ledger.keysets.get(keyset_id) if keyset is None: - raise CashuError(code=0, detail="Keyset not found.") + raise CashuError(code=0, detail="keyset not found") keyset_for_response = KeysResponseKeyset( id=keyset.id, @@ -172,12 +171,6 @@ async def mint_quote(payload: PostMintQuoteRequest) -> PostMintQuoteResponse: Call `POST /v1/mint/bolt11` after paying the invoice. """ logger.trace(f"> POST /v1/mint/quote/bolt11: payload={payload}") - amount = payload.amount - if amount > 21_000_000 * 100_000_000 or amount <= 0: - raise CashuError(code=0, detail="Amount must be a valid amount of sat.") - if settings.mint_peg_out_only: - raise CashuError(code=0, detail="Mint does not allow minting new tokens.") - quote = await ledger.mint_quote(payload) resp = PostMintQuoteResponse( request=quote.request, @@ -213,8 +206,8 @@ async def get_mint_quote(quote: str) -> PostMintQuoteResponse: @router.post( "/v1/mint/bolt11", - name="Mint tokens", - summary="Mint tokens in exchange for a Bitcoin payment that the user has made", + name="Mint tokens with a Lightning payment", + summary="Mint tokens by paying a bolt11 Lightning invoice.", response_model=PostMintResponse, response_description=( "A list of blinded signatures that can be used to create proofs." @@ -311,7 +304,7 @@ async def melt(payload: PostMeltRequest) -> PostMeltResponse: "An array of blinded signatures that can be used to create proofs." ), ) -async def split( +async def swap( payload: PostSplitRequest, ) -> PostSplitResponse: """ diff --git a/cashu/mint/router_deprecated.py b/cashu/mint/router_deprecated.py index b1d4be64..a2ac71b7 100644 --- a/cashu/mint/router_deprecated.py +++ b/cashu/mint/router_deprecated.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Dict, List, Optional from fastapi import APIRouter from loguru import logger @@ -70,7 +70,7 @@ async def info() -> GetInfoResponse_deprecated: response_model=KeysResponse_deprecated, deprecated=True, ) -async def keys_deprecated(): +async def keys_deprecated() -> Dict[str, str]: """This endpoint returns a dictionary of all supported token values of the mint and their associated public key.""" logger.trace("> GET /keys") keyset = ledger.get_keyset() @@ -86,10 +86,10 @@ async def keys_deprecated(): "A dictionary of all supported token values of the mint and their associated" " public key for a specific keyset." ), - response_model=KeysResponse_deprecated, + response_model=Dict[str, str], deprecated=True, ) -async def keyset_deprecated(idBase64Urlsafe: str): +async def keyset_deprecated(idBase64Urlsafe: str) -> Dict[str, str]: """ Get the public keys of the mint from a specific keyset id. The id is encoded in idBase64Urlsafe (by a wallet) and is converted back to @@ -323,7 +323,7 @@ async def split_deprecated( ), deprecated=True, ) -async def check_spendable( +async def check_spendable_deprecated( payload: CheckSpendableRequest_deprecated, ) -> CheckSpendableResponse_deprecated: """Check whether a secret has been spent already or not.""" diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index acc78559..84e31e89 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -56,7 +56,7 @@ async def rotate_keys(n_seconds=60): incremented_derivation_path = ( "/".join(ledger.derivation_path.split("/")[:-1]) + f"/{i}" ) - await ledger.activate_keyset(incremented_derivation_path) + await ledger.activate_keyset(derivation_path=incremented_derivation_path) logger.info(f"Current keyset: {ledger.keyset.id}") await asyncio.sleep(n_seconds) @@ -68,7 +68,7 @@ async def start_mint_init(): await ledger.init_keysets() for derivation_path in settings.mint_derivation_path_list: - await ledger.activate_keyset(derivation_path) + await ledger.activate_keyset(derivation_path=derivation_path) for method in ledger.backends: for unit in ledger.backends[method]: diff --git a/tests/test_mint_db.py b/tests/test_mint_db.py new file mode 100644 index 00000000..d45a4200 --- /dev/null +++ b/tests/test_mint_db.py @@ -0,0 +1,63 @@ +import pytest +import pytest_asyncio + +from cashu.core.base import PostMeltQuoteRequest +from cashu.mint.ledger import Ledger +from cashu.wallet.wallet import Wallet +from cashu.wallet.wallet import Wallet as Wallet1 +from tests.conftest import SERVER_ENDPOINT + + +async def assert_err(f, msg): + """Compute f() and expect an error message 'msg'.""" + try: + await f + except Exception as exc: + if msg not in str(exc.args[0]): + raise Exception(f"Expected error: {msg}, got: {exc.args[0]}") + return + raise Exception(f"Expected error: {msg}, got no error") + + +@pytest_asyncio.fixture(scope="function") +async def wallet1(ledger: Ledger): + wallet1 = await Wallet1.with_db( + url=SERVER_ENDPOINT, + db="test_data/wallet1", + name="wallet1", + ) + await wallet1.load_mint() + yield wallet1 + + +@pytest.mark.asyncio +async def test_mint_quote(wallet1: Wallet, ledger: Ledger): + invoice = await wallet1.request_mint(128) + assert invoice is not None + quote = await ledger.crud.get_mint_quote(quote_id=invoice.id, db=ledger.db) + assert quote is not None + assert quote.quote == invoice.id + assert quote.amount == 128 + assert quote.unit == "sat" + assert not quote.paid + assert quote.checking_id == invoice.payment_hash + assert quote.paid_time is None + assert quote.created_time + + +@pytest.mark.asyncio +async def test_melt_quote(wallet1: Wallet, ledger: Ledger): + invoice = await wallet1.request_mint(128) + assert invoice is not None + melt_quote = await ledger.melt_quote( + PostMeltQuoteRequest(request=invoice.bolt11, unit="sat") + ) + quote = await ledger.crud.get_melt_quote(quote_id=melt_quote.quote, db=ledger.db) + assert quote is not None + assert quote.quote == melt_quote.quote + assert quote.amount == 128 + assert quote.unit == "sat" + assert not quote.paid + assert quote.checking_id == invoice.payment_hash + assert quote.paid_time is None + assert quote.created_time diff --git a/tests/test_mint_keysets.py b/tests/test_mint_keysets.py new file mode 100644 index 00000000..6fad6aff --- /dev/null +++ b/tests/test_mint_keysets.py @@ -0,0 +1,57 @@ +import pytest + +from cashu.core.base import MintKeyset +from cashu.core.settings import settings + +SEED = "TEST_PRIVATE_KEY" +DERIVATION_PATH = "m/0'/0'/0'" + + +async def assert_err(f, msg): + """Compute f() and expect an error message 'msg'.""" + try: + await f + except Exception as exc: + if msg not in str(exc.args[0]): + raise Exception(f"Expected error: {msg}, got: {exc.args[0]}") + return + raise Exception(f"Expected error: {msg}, got no error") + + +@pytest.mark.asyncio +async def test_keyset_0_15_0(): + keyset = MintKeyset(seed=SEED, derivation_path=DERIVATION_PATH, version="0.15.0") + assert len(keyset.public_keys_hex) == settings.max_order + assert keyset.seed == "TEST_PRIVATE_KEY" + assert keyset.derivation_path == "m/0'/0'/0'" + assert ( + keyset.public_keys_hex[1] + == "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104" + ) + assert keyset.id == "009a1f293253e41e" + + +@pytest.mark.asyncio +async def test_keyset_0_14_0(): + keyset = MintKeyset(seed=SEED, derivation_path=DERIVATION_PATH, version="0.14.0") + assert len(keyset.public_keys_hex) == settings.max_order + assert keyset.seed == "TEST_PRIVATE_KEY" + assert keyset.derivation_path == "m/0'/0'/0'" + assert ( + keyset.public_keys_hex[1] + == "036d6f3adf897e88e16ece3bffb2ce57a0b635fa76f2e46dbe7c636a937cd3c2f2" + ) + assert keyset.id == "xnI+Y0j7cT1/" + + +@pytest.mark.asyncio +async def test_keyset_0_11_0(): + keyset = MintKeyset(seed=SEED, derivation_path=DERIVATION_PATH, version="0.11.0") + assert len(keyset.public_keys_hex) == settings.max_order + assert keyset.seed == "TEST_PRIVATE_KEY" + assert keyset.derivation_path == "m/0'/0'/0'" + assert ( + keyset.public_keys_hex[1] + == "026b714529f157d4c3de5a93e3a67618475711889b6434a497ae6ad8ace6682120" + ) + assert keyset.id == "Zkdws9zWxNc4" From d9f1ea0275d676cf02481d713fd380e29e4e1c0b Mon Sep 17 00:00:00 2001 From: sj-fisher <135287362+sj-fisher@users.noreply.github.com> Date: Fri, 2 Feb 2024 21:46:10 +0100 Subject: [PATCH 09/57] Update how to disable lightning for tests (#367) From 30b6e8aa565fec8251cae6b50cebaffea8ff3fbc Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 2 Feb 2024 22:09:06 +0100 Subject: [PATCH 10/57] not null constaint (#402) --- cashu/mint/migrations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 1f360f66..b5c373d0 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -338,9 +338,9 @@ async def m012_keysets_uniqueness_with_seed(db: Database): id TEXT NOT NULL, derivation_path TEXT, seed TEXT, - valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, - valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, - first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + valid_from TIMESTAMP, + valid_to TIMESTAMP, + first_seen TIMESTAMP, active BOOL DEFAULT TRUE, version TEXT, unit TEXT, From e02e4bbf49f222ae03616a4b4cad762afe347a8a Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 5 Feb 2024 16:22:53 +0100 Subject: [PATCH 11/57] mint: add seed decrypt (#403) * mint: add seed decrypt * add mint seed decryoption and migration tool --- cashu/core/base.py | 58 ++++++++------ cashu/core/crypto/aes.py | 65 ++++++++++++++++ cashu/core/settings.py | 3 +- cashu/mint/crud.py | 6 +- cashu/mint/decrypt.py | 151 +++++++++++++++++++++++++++++++++++++ cashu/mint/ledger.py | 33 ++++++-- cashu/mint/migrations.py | 50 ++++++++++++ cashu/mint/startup.py | 13 ++++ tests/conftest.py | 1 + tests/test_mint_init.py | 98 ++++++++++++++++++++++++ tests/test_mint_keysets.py | 22 +++++- 11 files changed, 465 insertions(+), 35 deletions(-) create mode 100644 cashu/core/crypto/aes.py create mode 100644 cashu/mint/decrypt.py create mode 100644 tests/test_mint_init.py diff --git a/cashu/core/base.py b/cashu/core/base.py index 071fbf39..03c4727e 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -9,6 +9,7 @@ from loguru import logger from pydantic import BaseModel, Field +from .crypto.aes import AESCipher from .crypto.keys import ( derive_keys, derive_keys_sha256, @@ -693,36 +694,51 @@ class MintKeyset: active: bool unit: Unit derivation_path: str - seed: str - public_keys: Union[Dict[int, PublicKey], None] = None - valid_from: Union[str, None] = None - valid_to: Union[str, None] = None - first_seen: Union[str, None] = None - version: Union[str, None] = None + seed: Optional[str] = None + encrypted_seed: Optional[str] = None + seed_encryption_method: Optional[str] = None + public_keys: Optional[Dict[int, PublicKey]] = None + valid_from: Optional[str] = None + valid_to: Optional[str] = None + first_seen: Optional[str] = None + version: Optional[str] = None duplicate_keyset_id: Optional[str] = None # BACKWARDS COMPATIBILITY < 0.15.0 def __init__( self, *, - seed: str, derivation_path: str, - id="", - valid_from=None, - valid_to=None, - first_seen=None, - active=None, + seed: Optional[str] = None, + encrypted_seed: Optional[str] = None, + seed_encryption_method: Optional[str] = None, + valid_from: Optional[str] = None, + valid_to: Optional[str] = None, + first_seen: Optional[str] = None, + active: Optional[bool] = None, unit: Optional[str] = None, - version: str = "0", + version: Optional[str] = None, + id: str = "", ): self.derivation_path = derivation_path - self.seed = seed + + if encrypted_seed and not settings.mint_seed_decryption_key: + raise Exception("MINT_SEED_DECRYPTION_KEY not set, but seed is encrypted.") + if settings.mint_seed_decryption_key and encrypted_seed: + self.seed = AESCipher(settings.mint_seed_decryption_key).decrypt( + encrypted_seed + ) + else: + self.seed = seed + + assert self.seed, "seed not set" + self.id = id self.valid_from = valid_from self.valid_to = valid_to self.first_seen = first_seen self.active = bool(active) if active is not None else False - self.version = version + self.version = version or settings.version self.version_tuple = tuple( [int(i) for i in self.version.split(".")] if self.version else [] @@ -730,7 +746,7 @@ def __init__( # infer unit from derivation path if not unit: - logger.warning( + logger.trace( f"Unit for keyset {self.derivation_path} not set – attempting to parse" " from derivation path" ) @@ -738,9 +754,9 @@ def __init__( self.unit = Unit( int(self.derivation_path.split("/")[2].replace("'", "")) ) - logger.warning(f"Inferred unit: {self.unit.name}") + logger.trace(f"Inferred unit: {self.unit.name}") except Exception: - logger.warning( + logger.trace( "Could not infer unit from derivation path" f" {self.derivation_path} – assuming 'sat'" ) @@ -754,7 +770,7 @@ def __init__( self.generate_keys() - logger.debug(f"Keyset id: {self.id} ({self.unit.name})") + logger.trace(f"Loaded keyset id: {self.id} ({self.unit.name})") @property def public_keys_hex(self) -> Dict[int, str]: @@ -775,14 +791,14 @@ def generate_keys(self): self.seed, self.derivation_path ) self.public_keys = derive_pubkeys(self.private_keys) # type: ignore - logger.warning( + logger.trace( f"WARNING: Using weak key derivation for keyset {self.id} (backwards" " compatibility < 0.12)" ) self.id = derive_keyset_id_deprecated(self.public_keys) # type: ignore elif self.version_tuple < (0, 15): self.private_keys = derive_keys_sha256(self.seed, self.derivation_path) - logger.warning( + logger.trace( f"WARNING: Using non-bip32 derivation for keyset {self.id} (backwards" " compatibility < 0.15)" ) diff --git a/cashu/core/crypto/aes.py b/cashu/core/crypto/aes.py new file mode 100644 index 00000000..7f856bf7 --- /dev/null +++ b/cashu/core/crypto/aes.py @@ -0,0 +1,65 @@ +import base64 +from hashlib import sha256 + +from Cryptodome import Random +from Cryptodome.Cipher import AES + +BLOCK_SIZE = 16 + + +class AESCipher: + """This class is compatible with crypto-js/aes.js + + Encrypt and decrypt in Javascript using: + import AES from "crypto-js/aes.js"; + import Utf8 from "crypto-js/enc-utf8.js"; + AES.encrypt(decrypted, password).toString() + AES.decrypt(encrypted, password).toString(Utf8); + + """ + + def __init__(self, key: str, description=""): + self.key: str = key + self.description = description + " " + + def pad(self, data): + length = BLOCK_SIZE - (len(data) % BLOCK_SIZE) + return data + (chr(length) * length).encode() + + def unpad(self, data): + return data[: -(data[-1] if isinstance(data[-1], int) else ord(data[-1]))] + + def bytes_to_key(self, data, salt, output=48): + # extended from https://gist.github.com/gsakkis/4546068 + assert len(salt) == 8, len(salt) + data += salt + key = sha256(data).digest() + final_key = key + while len(final_key) < output: + key = sha256(key + data).digest() + final_key += key + return final_key[:output] + + def decrypt(self, encrypted: str) -> str: # type: ignore + """Decrypts a string using AES-256-CBC.""" + encrypted = base64.urlsafe_b64decode(encrypted) # type: ignore + assert encrypted[0:8] == b"Salted__" + salt = encrypted[8:16] + key_iv = self.bytes_to_key(self.key.encode(), salt, 32 + 16) + key = key_iv[:32] + iv = key_iv[32:] + aes = AES.new(key, AES.MODE_CBC, iv) + try: + return self.unpad(aes.decrypt(encrypted[16:])).decode() # type: ignore + except UnicodeDecodeError: + raise ValueError("Wrong passphrase") + + def encrypt(self, message: bytes) -> str: + salt = Random.new().read(8) + key_iv = self.bytes_to_key(self.key.encode(), salt, 32 + 16) + key = key_iv[:32] + iv = key_iv[32:] + aes = AES.new(key, AES.MODE_CBC, iv) + return base64.urlsafe_b64encode( + b"Salted__" + salt + aes.encrypt(self.pad(message)) + ).decode() diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 689287ea..76064970 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -17,7 +17,7 @@ def find_env_file(): if not os.path.isfile(env_file): env_file = os.path.join(str(Path.home()), ".cashu", ".env") if os.path.isfile(env_file): - env.read_env(env_file) + env.read_env(env_file, recurse=False, override=True) else: env_file = "" return env_file @@ -49,6 +49,7 @@ class EnvSettings(CashuSettings): class MintSettings(CashuSettings): mint_private_key: str = Field(default=None) + mint_seed_decryption_key: str = Field(default=None) mint_derivation_path: str = Field(default="m/0'/0'/0'") mint_derivation_path_list: List[str] = Field(default=[]) mint_listen_host: str = Field(default="127.0.0.1") diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index 06ce347a..9174f72c 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -518,12 +518,14 @@ async def store_keyset( await (conn or db).execute( # type: ignore f""" INSERT INTO {table_with_schema(db, 'keysets')} - (id, seed, derivation_path, valid_from, valid_to, first_seen, active, version, unit) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + (id, seed, encrypted_seed, seed_encryption_method, derivation_path, valid_from, valid_to, first_seen, active, version, unit) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( keyset.id, keyset.seed, + keyset.encrypted_seed, + keyset.seed_encryption_method, keyset.derivation_path, keyset.valid_from or timestamp_now(db), keyset.valid_to or timestamp_now(db), diff --git a/cashu/mint/decrypt.py b/cashu/mint/decrypt.py new file mode 100644 index 00000000..71247bf6 --- /dev/null +++ b/cashu/mint/decrypt.py @@ -0,0 +1,151 @@ +import click + +try: + from ..core.crypto.aes import AESCipher +except ImportError: + # for the CLI to work + from cashu.core.crypto.aes import AESCipher +import asyncio +from functools import wraps + +from cashu.core.db import Database, table_with_schema +from cashu.core.migrations import migrate_databases +from cashu.core.settings import settings +from cashu.mint import migrations +from cashu.mint.crud import LedgerCrudSqlite +from cashu.mint.ledger import Ledger + + +# https://github.com/pallets/click/issues/85#issuecomment-503464628 +def coro(f): + @wraps(f) + def wrapper(*args, **kwargs): + return asyncio.run(f(*args, **kwargs)) + + return wrapper + + +@click.group() +def cli(): + """Ledger Decrypt CLI""" + pass + + +@cli.command() +@click.option("--message", prompt=True, help="The message to encrypt.") +@click.option( + "--key", + prompt=True, + hide_input=True, + confirmation_prompt=True, + help="The encryption key.", +) +def encrypt(message, key): + """Encrypt a message.""" + aes = AESCipher(key) + encrypted_message = aes.encrypt(message.encode()) + click.echo(f"Encrypted message: {encrypted_message}") + + +@cli.command() +@click.option("--encrypted", prompt=True, help="The encrypted message to decrypt.") +@click.option( + "--key", + prompt=True, + hide_input=True, + help="The decryption key.", +) +def decrypt(encrypted, key): + """Decrypt a message.""" + aes = AESCipher(key) + decrypted_message = aes.decrypt(encrypted) + click.echo(f"Decrypted message: {decrypted_message}") + + +# command to migrate the database to encrypted seeds +@cli.command() +@coro +@click.option("--no-dry-run", is_flag=True, help="Dry run.", default=False) +async def migrate(no_dry_run): + """Migrate the database to encrypted seeds.""" + ledger = Ledger( + db=Database("mint", settings.mint_database), + seed=settings.mint_private_key, + seed_decryption_key=settings.mint_seed_decryption_key, + derivation_path=settings.mint_derivation_path, + backends={}, + crud=LedgerCrudSqlite(), + ) + assert settings.mint_seed_decryption_key, "MINT_SEED_DECRYPTION_KEY not set." + assert ( + len(settings.mint_seed_decryption_key) > 12 + ), "MINT_SEED_DECRYPTION_KEY is too short, must be at least 12 characters." + click.echo( + "Decryption key:" + f" {settings.mint_seed_decryption_key[0]}{'*'*10}{settings.mint_seed_decryption_key[-1]}" + ) + + aes = AESCipher(settings.mint_seed_decryption_key) + + click.echo("Making sure that db is migrated to latest version first.") + await migrate_databases(ledger.db, migrations) + + # get all keysets + async with ledger.db.connect() as conn: + rows = await conn.fetchall( + f"SELECT * FROM {table_with_schema(ledger.db, 'keysets')} WHERE seed IS NOT" + " NULL" + ) + click.echo(f"Found {len(rows)} keysets in database.") + keysets_all = [dict(**row) for row in rows] + keysets_migrate = [] + # encrypt the seeds + for keyset_dict in keysets_all: + if keyset_dict["seed"] and not keyset_dict["encrypted_seed"]: + keyset_dict["encrypted_seed"] = aes.encrypt(keyset_dict["seed"].encode()) + keyset_dict["seed_encryption_method"] = "aes" + keysets_migrate.append(keyset_dict) + else: + click.echo(f"Skipping keyset {keyset_dict['id']}: already migrated.") + + click.echo(f"There are {len(keysets_migrate)} keysets to migrate.") + + for keyset_dict in keysets_migrate: + click.echo(f"Keyset {keyset_dict['id']}") + click.echo(f" Encrypted seed: {keyset_dict['encrypted_seed']}") + click.echo(f" Encryption method: {keyset_dict['seed_encryption_method']}") + decryption_success_str = ( + "✅" + if aes.decrypt(keyset_dict["encrypted_seed"]) == keyset_dict["seed"] + else "❌" + ) + click.echo(f" Seed decryption test: {decryption_success_str}") + + if not no_dry_run: + click.echo( + "This was a dry run. Use --no-dry-run to apply the changes to the database." + ) + if no_dry_run and keysets_migrate: + click.confirm( + "Are you sure you want to continue? Before you continue, make sure to have" + " a backup of your keysets database table.", + abort=True, + ) + click.echo("Updating keysets in the database.") + async with ledger.db.connect() as conn: + for keyset_dict in keysets_migrate: + click.echo(f"Updating keyset {keyset_dict['id']}") + await conn.execute( + f"UPDATE {table_with_schema(ledger.db, 'keysets')} SET seed=''," + " encrypted_seed = ?, seed_encryption_method = ? WHERE id = ?", + ( + keyset_dict["encrypted_seed"], + keyset_dict["seed_encryption_method"], + keyset_dict["id"], + ), + ) + click.echo("✅ Migration complete.") + + +if __name__ == "__main__": + cli() diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 8b07d939..24f11e0d 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -25,6 +25,7 @@ Unit, ) from ..core.crypto import b_dhke +from ..core.crypto.aes import AESCipher from ..core.crypto.keys import ( derive_keyset_id, derive_keyset_id_deprecated, @@ -68,10 +69,18 @@ def __init__( db: Database, seed: str, backends: Mapping[Method, Mapping[Unit, LightningBackend]], + seed_decryption_key: Optional[str] = None, derivation_path="", crud=LedgerCrudSqlite(), ): - self.master_key = seed + assert seed, "seed not set" + + # decrypt seed if seed_decryption_key is set + self.master_key = ( + AESCipher(seed_decryption_key).decrypt(seed) + if seed_decryption_key + else seed + ) self.derivation_path = derivation_path self.db = db @@ -101,16 +110,24 @@ async def activate_keyset( """ assert derivation_path, "derivation path not set" seed = seed or self.master_key - logger.debug(f"Activating keyset for derivation path {derivation_path}") + tmp_keyset_local = MintKeyset( + seed=seed, + derivation_path=derivation_path, + version=version or settings.version, + ) + logger.debug( + f"Activating keyset for derivation path {derivation_path} with id" + f" {tmp_keyset_local.id}." + ) # load the keyset from db logger.trace(f"crud: loading keyset for {derivation_path}") - tmp_keyset_local: List[MintKeyset] = await self.crud.get_keyset( - derivation_path=derivation_path, seed=seed, db=self.db + tmp_keysets_local: List[MintKeyset] = await self.crud.get_keyset( + id=tmp_keyset_local.id, db=self.db ) - logger.trace(f"crud: loaded {len(tmp_keyset_local)} keysets") - if tmp_keyset_local: + logger.trace(f"crud: loaded {len(tmp_keysets_local)} keysets") + if tmp_keysets_local: # we have a keyset with this derivation path in the database - keyset = tmp_keyset_local[0] + keyset = tmp_keysets_local[0] else: # no keyset for this derivation path yet # we create a new keyset (keys will be generated at instantiation) @@ -141,7 +158,7 @@ async def activate_keyset( async def init_keysets(self, autosave=True) -> None: """Initializes all keysets of the mint from the db. Loads all past keysets from db - and generate their keys. Then load the current keyset. + and generate their keys. Then activate the current keyset set by self.derivation_path. Args: autosave (bool, optional): Whether the current keyset should be saved if it is diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index b5c373d0..4e7f6133 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -357,3 +357,53 @@ async def m012_keysets_uniqueness_with_seed(db: Database): f" unit FROM {table_with_schema(db, 'keysets_old')}" ) await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets_old')}") + + +async def m013_keysets_add_encrypted_seed(db: Database): + async with db.connect() as conn: + # set keysets table unique constraint to id + # copy table keysets to keysets_old, create a new table keysets + # with the same columns but with a unique constraint on id + # and copy the data from keysets_old to keysets, then drop keysets_old + await conn.execute( + f"DROP TABLE IF EXISTS {table_with_schema(db, 'keysets_old')}" + ) + await conn.execute( + f"CREATE TABLE {table_with_schema(db, 'keysets_old')} AS" + f" SELECT * FROM {table_with_schema(db, 'keysets')}" + ) + await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets')}") + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} ( + id TEXT NOT NULL, + derivation_path TEXT, + seed TEXT, + valid_from TIMESTAMP, + valid_to TIMESTAMP, + first_seen TIMESTAMP, + active BOOL DEFAULT TRUE, + version TEXT, + unit TEXT, + + UNIQUE (id) + + ); + """) + await conn.execute( + f"INSERT INTO {table_with_schema(db, 'keysets')} (id," + " derivation_path, valid_from, valid_to, first_seen," + " active, version, seed, unit) SELECT id, derivation_path," + " valid_from, valid_to, first_seen, active, version, seed," + f" unit FROM {table_with_schema(db, 'keysets_old')}" + ) + await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets_old')}") + + # add columns encrypted_seed and seed_encryption_method to keysets + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN encrypted_seed" + " TEXT" + ) + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN" + " seed_encryption_method TEXT" + ) diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index 84e31e89..f094f97b 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -16,6 +16,18 @@ logger.debug("Enviroment Settings:") for key, value in settings.dict().items(): + if key in [ + "mint_private_key", + "mint_seed_decryption_key", + "nostr_private_key", + "mint_lnbits_key", + "mint_strike_key", + "mint_lnd_rest_macaroon", + "mint_lnd_rest_admin_macaroon", + "mint_lnd_rest_invoice_macaroon", + "mint_corelightning_rest_macaroon", + ]: + value = "********" if value is not None else None logger.debug(f"{key}: {value}") wallets_module = importlib.import_module("cashu.lightning") @@ -39,6 +51,7 @@ ledger = Ledger( db=Database("mint", settings.mint_database), seed=settings.mint_private_key, + seed_decryption_key=settings.mint_seed_decryption_key, derivation_path=settings.mint_derivation_path, backends=backends, crud=LedgerCrudSqlite(), diff --git a/tests/conftest.py b/tests/conftest.py index 82aa4ab0..cad7e07f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,6 +38,7 @@ settings.mint_derivation_path = "m/0'/0'/0'" settings.mint_derivation_path_list = [] settings.mint_private_key = "TEST_PRIVATE_KEY" +settings.mint_seed_decryption_key = "" settings.mint_max_balance = 0 assert "test" in settings.cashu_dir diff --git a/tests/test_mint_init.py b/tests/test_mint_init.py new file mode 100644 index 00000000..029a546e --- /dev/null +++ b/tests/test_mint_init.py @@ -0,0 +1,98 @@ +from typing import List + +import pytest + +from cashu.core.base import Proof +from cashu.core.crypto.aes import AESCipher +from cashu.core.db import Database +from cashu.core.settings import settings +from cashu.mint.crud import LedgerCrudSqlite +from cashu.mint.ledger import Ledger + +SEED = "TEST_PRIVATE_KEY" +DERIVATION_PATH = "m/0'/0'/0'" +DECRYPTON_KEY = "testdecryptionkey" +ENCRYPTED_SEED = "U2FsdGVkX1_7UU_-nVBMBWDy_9yDu4KeYb7MH8cJTYQGD4RWl82PALH8j-HKzTrI" + + +async def assert_err(f, msg): + """Compute f() and expect an error message 'msg'.""" + try: + await f + except Exception as exc: + assert exc.args[0] == msg, Exception( + f"Expected error: {msg}, got: {exc.args[0]}" + ) + + +def assert_amt(proofs: List[Proof], expected: int): + """Assert amounts the proofs contain.""" + assert [p.amount for p in proofs] == expected + + +@pytest.mark.asyncio +async def test_ledger_encrypt(): + aes = AESCipher(DECRYPTON_KEY) + encrypted = aes.encrypt(SEED.encode()) + assert aes.decrypt(encrypted) == SEED + + +@pytest.mark.asyncio +async def test_ledger_decrypt(): + aes = AESCipher(DECRYPTON_KEY) + assert aes.decrypt(ENCRYPTED_SEED) == SEED + + +@pytest.mark.asyncio +async def test_decrypt_seed(): + ledger = Ledger( + db=Database("mint", settings.mint_database), + seed=SEED, + seed_decryption_key=None, + derivation_path=DERIVATION_PATH, + backends={}, + crud=LedgerCrudSqlite(), + ) + await ledger.init_keysets() + assert ledger.keyset.seed == SEED + private_key_1 = ( + ledger.keysets[list(ledger.keysets.keys())[0]].private_keys[1].serialize() + ) + assert ( + private_key_1 + == "8300050453f08e6ead1296bb864e905bd46761beed22b81110fae0751d84604d" + ) + pubkeys = ledger.keysets[list(ledger.keysets.keys())[0]].public_keys + assert pubkeys + assert ( + pubkeys[1].serialize().hex() + == "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104" + ) + + ledger_encrypted = Ledger( + db=Database("mint", settings.mint_database), + seed=ENCRYPTED_SEED, + seed_decryption_key=DECRYPTON_KEY, + derivation_path=DERIVATION_PATH, + backends={}, + crud=LedgerCrudSqlite(), + ) + await ledger_encrypted.init_keysets() + assert ledger_encrypted.keyset.seed == SEED + private_key_1 = ( + ledger_encrypted.keysets[list(ledger_encrypted.keysets.keys())[0]] + .private_keys[1] + .serialize() + ) + assert ( + private_key_1 + == "8300050453f08e6ead1296bb864e905bd46761beed22b81110fae0751d84604d" + ) + pubkeys_encrypted = ledger_encrypted.keysets[ + list(ledger_encrypted.keysets.keys())[0] + ].public_keys + assert pubkeys_encrypted + assert ( + pubkeys_encrypted[1].serialize().hex() + == "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104" + ) diff --git a/tests/test_mint_keysets.py b/tests/test_mint_keysets.py index 6fad6aff..6085ea76 100644 --- a/tests/test_mint_keysets.py +++ b/tests/test_mint_keysets.py @@ -2,9 +2,7 @@ from cashu.core.base import MintKeyset from cashu.core.settings import settings - -SEED = "TEST_PRIVATE_KEY" -DERIVATION_PATH = "m/0'/0'/0'" +from tests.test_mint_init import DECRYPTON_KEY, DERIVATION_PATH, ENCRYPTED_SEED, SEED async def assert_err(f, msg): @@ -55,3 +53,21 @@ async def test_keyset_0_11_0(): == "026b714529f157d4c3de5a93e3a67618475711889b6434a497ae6ad8ace6682120" ) assert keyset.id == "Zkdws9zWxNc4" + + +@pytest.mark.asyncio +async def test_keyset_0_15_0_encrypted(): + settings.mint_seed_decryption_key = DECRYPTON_KEY + keyset = MintKeyset( + encrypted_seed=ENCRYPTED_SEED, + derivation_path=DERIVATION_PATH, + version="0.15.0", + ) + assert len(keyset.public_keys_hex) == settings.max_order + assert keyset.seed == "TEST_PRIVATE_KEY" + assert keyset.derivation_path == "m/0'/0'/0'" + assert ( + keyset.public_keys_hex[1] + == "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104" + ) + assert keyset.id == "009a1f293253e41e" From f8f167fe23987a962a67171ec7414d9b970d7f35 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:52:47 +0100 Subject: [PATCH 12/57] Mint: fix lnbits migration (#407) * fix lnbits migration * save without format * make format --- cashu/mint/migrations.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 4e7f6133..4d33c8da 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -312,8 +312,9 @@ async def get_columns(db: Database, conn: Connection, table: str): await conn.execute( f"INSERT INTO {table_with_schema(db, 'mint_quotes')} (quote, method," " request, checking_id, unit, amount, paid, issued, created_time," - " paid_time) SELECT id, 'bolt11', bolt11, payment_hash, 'sat', amount," - f" False, issued, created, NULL FROM {table_with_schema(db, 'invoices')} " + " paid_time) SELECT id, 'bolt11', bolt11, COALESCE(payment_hash, 'None')," + f" 'sat', amount, False, issued, COALESCE(created, '{timestamp_now(db)}')," + f" NULL FROM {table_with_schema(db, 'invoices')} " ) # drop table invoices From 087ee957a5624ed49e96e092a9ad2039544f625c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:43:11 +0100 Subject: [PATCH 13/57] remove column check during migration (#408) --- cashu/mint/migrations.py | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 4d33c8da..078dddd1 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -1,4 +1,4 @@ -from ..core.db import SQLITE, Connection, Database, table_with_schema, timestamp_now +from ..core.db import Connection, Database, table_with_schema, timestamp_now from ..core.settings import settings @@ -222,34 +222,18 @@ async def m010_add_index_to_proofs_used(db: Database): async def m011_add_quote_tables(db: Database): - async def get_columns(db: Database, conn: Connection, table: str): - if db.type == SQLITE: - query = f"PRAGMA table_info({table})" - else: - query = ( - "SELECT column_name FROM information_schema.columns WHERE table_name =" - f" '{table}'" - ) - res = await conn.execute(query) - if db.type == SQLITE: - return [r["name"] async for r in res] - else: - return [r["column_name"] async for r in res] - async with db.connect() as conn: # add column "created" to tables invoices, promises, proofs_used, proofs_pending tables = ["invoices", "promises", "proofs_used", "proofs_pending"] for table in tables: - columns = await get_columns(db, conn, table) - if "created" not in columns: - await conn.execute( - f"ALTER TABLE {table_with_schema(db, table)} ADD COLUMN created" - " TIMESTAMP" - ) - await conn.execute( - f"UPDATE {table_with_schema(db, table)} SET created =" - f" '{timestamp_now(db)}'" - ) + await conn.execute( + f"ALTER TABLE {table_with_schema(db, table)} ADD COLUMN created" + " TIMESTAMP" + ) + await conn.execute( + f"UPDATE {table_with_schema(db, table)} SET created =" + f" '{timestamp_now(db)}'" + ) # add column "witness" to table proofs_used await conn.execute( From 7574b132291850dca62f27ed7087855c6df4f8ad Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:19:31 +0100 Subject: [PATCH 14/57] Mint: option to not duplicate keysets (#409) * option to not duplicate keysets * test also settings --- cashu/core/settings.py | 38 ++++++++++++++++++++++++++++++++------ cashu/mint/ledger.py | 38 ++++++++++++++++++++++++-------------- tests/test_mint_init.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 20 deletions(-) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 76064970..fc054425 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -56,11 +56,37 @@ class MintSettings(CashuSettings): mint_listen_port: int = Field(default=3338) mint_lightning_backend: str = Field(default="LNbitsWallet") mint_database: str = Field(default="data/mint") - mint_peg_out_only: bool = Field(default=False) - mint_max_peg_in: int = Field(default=None) - mint_max_peg_out: int = Field(default=None) - mint_max_request_length: int = Field(default=1000) - mint_max_balance: int = Field(default=None) + mint_peg_out_only: bool = Field( + default=False, + title="Peg-out only", + description="Mint allows no mint operations.", + ) + mint_max_peg_in: int = Field( + default=None, + title="Maximum peg-in", + description="Maximum amount for a mint operation.", + ) + mint_max_peg_out: int = Field( + default=None, + title="Maximum peg-out", + description="Maximum amount for a melt operation.", + ) + mint_max_request_length: int = Field( + default=1000, + title="Maximum request length", + description="Maximum length of REST API request arrays.", + ) + mint_max_balance: int = Field( + default=None, title="Maximum mint balance", description="Maximum mint balance." + ) + mint_duplicate_keysets: bool = Field( + default=True, + title="Duplicate keysets", + description=( + "Whether to duplicate keysets for backwards compatibility before v1 API" + " (Nutshell 0.15.0)." + ), + ) mint_lnbits_endpoint: str = Field(default=None) mint_lnbits_key: str = Field(default=None) @@ -85,7 +111,7 @@ class MintInformation(CashuSettings): class WalletSettings(CashuSettings): - tor: bool = Field(default=True) + tor: bool = Field(default=False) socks_host: str = Field(default=None) # deprecated socks_port: int = Field(default=9050) # deprecated socks_proxy: str = Field(default=None) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 24f11e0d..116835ca 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -156,14 +156,19 @@ async def activate_keyset( logger.debug(f"Loaded keyset {keyset.id}") return keyset - async def init_keysets(self, autosave=True) -> None: + async def init_keysets( + self, autosave: bool = True, duplicate_keysets: Optional[bool] = None + ) -> None: """Initializes all keysets of the mint from the db. Loads all past keysets from db and generate their keys. Then activate the current keyset set by self.derivation_path. Args: autosave (bool, optional): Whether the current keyset should be saved if it is - not in the database yet. Will be passed to `self.activate_keyset` where it is - generated from `self.derivation_path`. Defaults to True. + not in the database yet. Will be passed to `self.activate_keyset` where it is + generated from `self.derivation_path`. Defaults to True. + duplicate_keysets (bool, optional): Whether to duplicate new keysets and compute + their old keyset id, and duplicate old keysets and compute their new keyset id. + Defaults to False. """ # load all past keysets from db, the keys will be generated at instantiation tmp_keysets: List[MintKeyset] = await self.crud.get_keyset(db=self.db) @@ -190,17 +195,22 @@ async def init_keysets(self, autosave=True) -> None: # BEGIN BACKWARDS COMPATIBILITY < 0.15.0 # we duplicate new keysets and compute their old keyset id, and # we duplicate old keysets and compute their new keyset id - for _, keyset in copy.copy(self.keysets).items(): - keyset_copy = copy.copy(keyset) - assert keyset_copy.public_keys - if keyset.version_tuple >= (0, 15): - keyset_copy.id = derive_keyset_id_deprecated(keyset_copy.public_keys) - else: - keyset_copy.id = derive_keyset_id(keyset_copy.public_keys) - keyset_copy.duplicate_keyset_id = keyset.id - self.keysets[keyset_copy.id] = keyset_copy - # remember which keyset this keyset was duplicated from - logger.debug(f"Duplicated keyset id {keyset.id} -> {keyset_copy.id}") + if ( + duplicate_keysets is None and settings.mint_duplicate_keysets + ) or duplicate_keysets: + for _, keyset in copy.copy(self.keysets).items(): + keyset_copy = copy.copy(keyset) + assert keyset_copy.public_keys + if keyset.version_tuple >= (0, 15): + keyset_copy.id = derive_keyset_id_deprecated( + keyset_copy.public_keys + ) + else: + keyset_copy.id = derive_keyset_id(keyset_copy.public_keys) + keyset_copy.duplicate_keyset_id = keyset.id + self.keysets[keyset_copy.id] = keyset_copy + # remember which keyset this keyset was duplicated from + logger.debug(f"Duplicated keyset id {keyset.id} -> {keyset_copy.id}") # END BACKWARDS COMPATIBILITY < 0.15.0 def get_keyset(self, keyset_id: Optional[str] = None) -> Dict[int, str]: diff --git a/tests/test_mint_init.py b/tests/test_mint_init.py index 029a546e..77f111b8 100644 --- a/tests/test_mint_init.py +++ b/tests/test_mint_init.py @@ -30,6 +30,36 @@ def assert_amt(proofs: List[Proof], expected: int): assert [p.amount for p in proofs] == expected +@pytest.mark.asyncio +async def test_init_keysets_with_duplicates(ledger: Ledger): + ledger.keysets = {} + await ledger.init_keysets(duplicate_keysets=True) + assert len(ledger.keysets) == 2 + + +@pytest.mark.asyncio +async def test_init_keysets_with_duplicates_via_settings(ledger: Ledger): + ledger.keysets = {} + settings.mint_duplicate_keysets = True + await ledger.init_keysets() + assert len(ledger.keysets) == 2 + + +@pytest.mark.asyncio +async def test_init_keysets_without_duplicates(ledger: Ledger): + ledger.keysets = {} + await ledger.init_keysets(duplicate_keysets=False) + assert len(ledger.keysets) == 1 + + +@pytest.mark.asyncio +async def test_init_keysets_without_duplicates_via_settings(ledger: Ledger): + ledger.keysets = {} + settings.mint_duplicate_keysets = False + await ledger.init_keysets() + assert len(ledger.keysets) == 1 + + @pytest.mark.asyncio async def test_ledger_encrypt(): aes = AESCipher(DECRYPTON_KEY) From 1de7abf0328cd9e2dcfc18b5fa07aa094528b47f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:23:52 +0100 Subject: [PATCH 15/57] mint: do not print all keysets (#410) --- cashu/mint/ledger.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 116835ca..4fb33c14 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -177,6 +177,8 @@ async def init_keysets( for k in tmp_keysets: self.keysets[k.id] = k + logger.info(f"Loaded {len(self.keysets)} keysets from database.") + # activate the current keyset set by self.derivation_path if self.derivation_path: self.keyset = await self.activate_keyset( @@ -184,11 +186,6 @@ async def init_keysets( ) logger.info(f"Current keyset: {self.keyset.id}") - logger.info( - f"Loaded {len(self.keysets)} keysets:" - f" {[f'{k} ({v.unit.name})' for k, v in self.keysets.items()]}" - ) - # check that we have a least one active keyset assert any([k.active for k in self.keysets.values()]), "No active keyset found." From 6db4604f998bc5499594cbc55f6c7c2dd9708710 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 10 Feb 2024 22:52:55 +0100 Subject: [PATCH 16/57] Mint: store Y in db (#412) * storage y db * for proofs_pending as well * pending check with Y * fix pending table * test_race_pending * skip race condition test on github * skip test on github actions * move test_cli.py -> test_wallet_cli.py * get full proof from memory * add domain separation wallet --- cashu/core/base.py | 10 ++- cashu/core/crypto/b_dhke.py | 46 +++++++++- cashu/core/settings.py | 2 +- cashu/mint/crud.py | 33 ++++--- cashu/mint/ledger.py | 39 +++++---- cashu/mint/migrations.py | 101 ++++++++++++++++++++++ cashu/mint/verification.py | 30 ++++--- cashu/wallet/wallet.py | 16 +++- tests/conftest.py | 6 +- tests/helpers.py | 1 + tests/test_wallet.py | 28 +++++- tests/{test_cli.py => test_wallet_cli.py} | 0 12 files changed, 264 insertions(+), 48 deletions(-) rename tests/{test_cli.py => test_wallet_cli.py} (100%) diff --git a/cashu/core/base.py b/cashu/core/base.py index 03c4727e..8ee0e43b 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -9,6 +9,8 @@ from loguru import logger from pydantic import BaseModel, Field +from cashu.core.crypto.b_dhke import hash_to_curve + from .crypto.aes import AESCipher from .crypto.keys import ( derive_keys, @@ -88,8 +90,9 @@ class Proof(BaseModel): id: Union[None, str] = "" amount: int = 0 secret: str = "" # secret or message to be blinded and signed + Y: str = "" # hash_to_curve(secret) C: str = "" # signature on secret, unblinded by wallet - dleq: Union[DLEQWallet, None] = None # DLEQ proof + dleq: Optional[DLEQWallet] = None # DLEQ proof witness: Union[None, str] = "" # witness for spending condition # whether this proof is reserved for sending, used for coin management in the wallet @@ -106,6 +109,11 @@ class Proof(BaseModel): None # holds the id of the melt operation that destroyed this proof ) + def __init__(self, **data): + super().__init__(**data) + if not self.Y: + self.Y = hash_to_curve(self.secret.encode("utf-8")).serialize().hex() + @classmethod def from_dict(cls, proof_dict: dict): if proof_dict.get("dleq") and isinstance(proof_dict["dleq"], str): diff --git a/cashu/core/crypto/b_dhke.py b/cashu/core/crypto/b_dhke.py index e8706239..df0bcccf 100644 --- a/cashu/core/crypto/b_dhke.py +++ b/cashu/core/crypto/b_dhke.py @@ -71,6 +71,26 @@ def hash_to_curve(message: bytes) -> PublicKey: return point +DOMAIN_SEPARATOR = b"Secp256k1_HashToCurve_" + + +def hash_to_curve_domain_separated(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 = DOMAIN_SEPARATOR + message + counter = 0 + while point is None: + _hash = hashlib.sha256(msg_to_hash + str(counter).encode()).digest() + try: + # will error if point does not lie on curve + point = PublicKey(b"\x02" + _hash, raw=True) + except Exception: + msg_to_hash = _hash + counter += 1 + return point + + def step1_alice( secret_msg: str, blinding_factor: Optional[PrivateKey] = None ) -> tuple[PublicKey, PrivateKey]: @@ -80,6 +100,15 @@ def step1_alice( return B_, r +def step1_alice_domain_separated( + secret_msg: str, blinding_factor: Optional[PrivateKey] = None +) -> tuple[PublicKey, PrivateKey]: + Y: PublicKey = hash_to_curve_domain_separated(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 @@ -94,7 +123,13 @@ def step3_alice(C_: PublicKey, r: PrivateKey, A: PublicKey) -> PublicKey: 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 + valid = C == Y.mult(a) # type: ignore + # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 + if not valid: + Y1: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) + return C == Y1.mult(a) # type: ignore + # END: BACKWARDS COMPATIBILITY < 0.15.1 + return valid def hash_e(*publickeys: PublicKey) -> bytes: @@ -149,7 +184,14 @@ 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) + valid = alice_verify_dleq(B_, C_, e, s, A) + # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 + if not valid: + Y1: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) + B_1: PublicKey = Y1 + r.pubkey # type: ignore + return alice_verify_dleq(B_1, C_, e, s, A) + # END: BACKWARDS COMPATIBILITY < 0.15.1 + return valid # Below is a test of a simple positive and negative case diff --git a/cashu/core/settings.py b/cashu/core/settings.py index fc054425..ace76940 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -121,7 +121,7 @@ class WalletSettings(CashuSettings): mint_port: int = Field(default=3338) wallet_name: str = Field(default="wallet") wallet_unit: str = Field(default="sat") - + wallet_domain_separation: bool = Field(default=False) api_port: int = Field(default=4448) api_host: str = Field(default="127.0.0.1") diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index 9174f72c..853ff094 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -47,8 +47,8 @@ async def get_spent_proofs( async def get_proof_used( self, *, + Y: str, db: Database, - secret: str, conn: Optional[Connection] = None, ) -> Optional[Proof]: ... @@ -65,6 +65,7 @@ async def invalidate_proof( async def get_proofs_pending( self, *, + proofs: List[Proof], db: Database, conn: Optional[Connection] = None, ) -> List[Proof]: ... @@ -271,13 +272,14 @@ async def invalidate_proof( await (conn or db).execute( f""" INSERT INTO {table_with_schema(db, 'proofs_used')} - (amount, C, secret, id, witness, created) - VALUES (?, ?, ?, ?, ?, ?) + (amount, C, secret, Y, id, witness, created) + VALUES (?, ?, ?, ?, ?, ?, ?) """, ( proof.amount, proof.C, proof.secret, + proof.Y, proof.id, proof.witness, timestamp_now(db), @@ -287,12 +289,17 @@ async def invalidate_proof( async def get_proofs_pending( self, *, + proofs: List[Proof], db: Database, conn: Optional[Connection] = None, ) -> List[Proof]: - rows = await (conn or db).fetchall(f""" + rows = await (conn or db).fetchall( + f""" SELECT * from {table_with_schema(db, 'proofs_pending')} - """) + WHERE Y IN ({','.join(['?']*len(proofs))}) + """, + tuple(proof.Y for proof in proofs), + ) return [Proof(**r) for r in rows] async def set_proof_pending( @@ -306,13 +313,14 @@ async def set_proof_pending( await (conn or db).execute( f""" INSERT INTO {table_with_schema(db, 'proofs_pending')} - (amount, C, secret, created) - VALUES (?, ?, ?, ?) + (amount, C, secret, Y, created) + VALUES (?, ?, ?, ?, ?) """, ( proof.amount, - str(proof.C), - str(proof.secret), + proof.C, + proof.secret, + proof.Y, timestamp_now(db), ), ) @@ -590,15 +598,16 @@ async def get_keyset( async def get_proof_used( self, + *, + Y: str, db: Database, - secret: str, conn: Optional[Connection] = None, ) -> Optional[Proof]: row = await (conn or db).fetchone( f""" SELECT * from {table_with_schema(db, 'proofs_used')} - WHERE secret = ? + WHERE Y = ? """, - (secret,), + (Y,), ) return Proof(**row) if row else None diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 4fb33c14..8d0be64b 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -233,7 +233,7 @@ async def _invalidate_proofs( proofs (List[Proof]): Proofs to add to known secret table. conn: (Optional[Connection], optional): Database connection to reuse. Will create a new one if not given. Defaults to None. """ - self.spent_proofs.update({p.secret: p for p in proofs}) + self.spent_proofs.update({p.Y: p for p in proofs}) async with get_db_connection(self.db, conn) as conn: # store in db for p in proofs: @@ -873,7 +873,7 @@ async def load_used_proofs(self) -> None: logger.debug("Loading used proofs into memory") spent_proofs_list = await self.crud.get_spent_proofs(db=self.db) or [] logger.debug(f"Loaded {len(spent_proofs_list)} used proofs") - self.spent_proofs = {p.secret: p for p in spent_proofs_list} + self.spent_proofs = {p.Y: p for p in spent_proofs_list} async def check_proofs_state(self, secrets: List[str]) -> List[ProofState]: """Checks if provided proofs are spend or are pending. @@ -891,19 +891,25 @@ async def check_proofs_state(self, secrets: List[str]) -> List[ProofState]: List[bool]: List of which proof are pending (True if pending, else False) """ states: List[ProofState] = [] - proofs_spent = await self._get_proofs_spent(secrets) - proofs_pending = await self._get_proofs_pending(secrets) + proofs_spent_idx_secret = await self._get_proofs_spent_idx_secret(secrets) + proofs_pending_idx_secret = await self._get_proofs_pending_idx_secret(secrets) for secret in secrets: - if secret not in proofs_spent and secret not in proofs_pending: + if ( + secret not in proofs_spent_idx_secret + and secret not in proofs_pending_idx_secret + ): states.append(ProofState(secret=secret, state=SpentState.unspent)) - elif secret not in proofs_spent and secret in proofs_pending: + elif ( + secret not in proofs_spent_idx_secret + and secret in proofs_pending_idx_secret + ): states.append(ProofState(secret=secret, state=SpentState.pending)) else: states.append( ProofState( secret=secret, state=SpentState.spent, - witness=proofs_spent[secret].witness, + witness=proofs_spent_idx_secret[secret].witness, ) ) return states @@ -922,13 +928,13 @@ async def _set_proofs_pending(self, proofs: List[Proof]) -> None: async with self.proofs_pending_lock: async with self.db.connect() as conn: await self._validate_proofs_pending(proofs, conn) - for p in proofs: - try: + try: + for p in proofs: await self.crud.set_proof_pending( proof=p, db=self.db, conn=conn ) - except Exception: - raise TransactionError("proofs already pending.") + except Exception: + raise TransactionError("Failed to set proofs pending.") async def _unset_proofs_pending(self, proofs: List[Proof]) -> None: """Deletes proofs from pending table. @@ -952,8 +958,9 @@ async def _validate_proofs_pending( Raises: Exception: At least one of the proofs is in the pending table. """ - proofs_pending = await self.crud.get_proofs_pending(db=self.db, conn=conn) - for p in proofs: - for pp in proofs_pending: - if p.secret == pp.secret: - raise TransactionError("proofs are pending.") + assert ( + len( + await self.crud.get_proofs_pending(proofs=proofs, db=self.db, conn=conn) + ) + == 0 + ), TransactionError("proofs are pending.") diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 078dddd1..79158bf3 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -1,3 +1,5 @@ +from cashu.core.base import Proof + from ..core.db import Connection, Database, table_with_schema, timestamp_now from ..core.settings import settings @@ -392,3 +394,102 @@ async def m013_keysets_add_encrypted_seed(db: Database): f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN" " seed_encryption_method TEXT" ) + + +async def m014_proofs_add_Y_column(db: Database): + # get all proofs_used and proofs_pending from the database and compute Y for each of them + async with db.connect() as conn: + rows = await conn.fetchall( + f"SELECT * FROM {table_with_schema(db, 'proofs_used')}" + ) + # Proof() will compute Y from secret upon initialization + proofs_used = [Proof(**r) for r in rows] + + rows = await conn.fetchall( + f"SELECT * FROM {table_with_schema(db, 'proofs_pending')}" + ) + proofs_pending = [Proof(**r) for r in rows] + async with db.connect() as conn: + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'proofs_used')} ADD COLUMN Y TEXT" + ) + for proof in proofs_used: + await conn.execute( + f"UPDATE {table_with_schema(db, 'proofs_used')} SET Y = '{proof.Y}'" + f" WHERE secret = '{proof.secret}'" + ) + # Copy proofs_used to proofs_used_old and create a new table proofs_used + # with the same columns but with a unique constraint on (Y) + # and copy the data from proofs_used_old to proofs_used, then drop proofs_used_old + await conn.execute( + f"DROP TABLE IF EXISTS {table_with_schema(db, 'proofs_used_old')}" + ) + await conn.execute( + f"CREATE TABLE {table_with_schema(db, 'proofs_used_old')} AS" + f" SELECT * FROM {table_with_schema(db, 'proofs_used')}" + ) + await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_used')}") + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_used')} ( + amount INTEGER NOT NULL, + C TEXT NOT NULL, + secret TEXT NOT NULL, + id TEXT, + Y TEXT, + created TIMESTAMP, + witness TEXT, + + UNIQUE (Y) + + ); + """) + await conn.execute( + f"INSERT INTO {table_with_schema(db, 'proofs_used')} (amount, C, " + "secret, id, Y, created, witness) SELECT amount, C, secret, id, Y," + f" created, witness FROM {table_with_schema(db, 'proofs_used_old')}" + ) + await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_used_old')}") + + # add column Y to proofs_pending + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'proofs_pending')} ADD COLUMN Y TEXT" + ) + for proof in proofs_pending: + await conn.execute( + f"UPDATE {table_with_schema(db, 'proofs_pending')} SET Y = '{proof.Y}'" + f" WHERE secret = '{proof.secret}'" + ) + + # Copy proofs_pending to proofs_pending_old and create a new table proofs_pending + # with the same columns but with a unique constraint on (Y) + # and copy the data from proofs_pending_old to proofs_pending, then drop proofs_pending_old + await conn.execute( + f"DROP TABLE IF EXISTS {table_with_schema(db, 'proofs_pending_old')}" + ) + + await conn.execute( + f"CREATE TABLE {table_with_schema(db, 'proofs_pending_old')} AS" + f" SELECT * FROM {table_with_schema(db, 'proofs_pending')}" + ) + + await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_pending')}") + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_pending')} ( + amount INTEGER NOT NULL, + C TEXT NOT NULL, + secret TEXT NOT NULL, + Y TEXT, + id TEXT, + created TIMESTAMP, + + UNIQUE (Y) + + ); + """) + await conn.execute( + f"INSERT INTO {table_with_schema(db, 'proofs_pending')} (amount, C, " + "secret, Y, id, created) SELECT amount, C, secret, Y, id, created" + f" FROM {table_with_schema(db, 'proofs_pending_old')}" + ) + + await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_pending_old')}") diff --git a/cashu/mint/verification.py b/cashu/mint/verification.py index 48f08b3c..e3da1d54 100644 --- a/cashu/mint/verification.py +++ b/cashu/mint/verification.py @@ -51,8 +51,10 @@ async def verify_inputs_and_outputs( """ # Verify inputs # Verify proofs are spendable - spent_proofs = await self._get_proofs_spent([p.secret for p in proofs]) - if not len(spent_proofs) == 0: + if ( + not len(await self._get_proofs_spent_idx_secret([p.secret for p in proofs])) + == 0 + ): raise TokenAlreadySpentError() # Verify amounts of inputs if not all([self._verify_amount(p.amount) for p in proofs]): @@ -141,27 +143,35 @@ async def _check_outputs_issued_before(self, outputs: List[BlindedMessage]): result.append(False if promise is None else True) return result - async def _get_proofs_pending(self, secrets: List[str]) -> Dict[str, Proof]: + async def _get_proofs_pending_idx_secret( + self, secrets: List[str] + ) -> Dict[str, Proof]: """Returns only those proofs that are pending.""" - all_proofs_pending = await self.crud.get_proofs_pending(db=self.db) + all_proofs_pending = await self.crud.get_proofs_pending( + proofs=[Proof(secret=s) for s in secrets], db=self.db + ) proofs_pending = list(filter(lambda p: p.secret in secrets, all_proofs_pending)) proofs_pending_dict = {p.secret: p for p in proofs_pending} return proofs_pending_dict - async def _get_proofs_spent(self, secrets: List[str]) -> Dict[str, Proof]: + async def _get_proofs_spent_idx_secret( + self, secrets: List[str] + ) -> Dict[str, Proof]: """Returns all proofs that are spent.""" + proofs = [Proof(secret=s) for s in secrets] proofs_spent: List[Proof] = [] if settings.mint_cache_secrets: # check used secrets in memory - for secret in secrets: - if secret in self.spent_proofs: - proofs_spent.append(self.spent_proofs[secret]) + for proof in proofs: + spent_proof = self.spent_proofs.get(proof.Y) + if spent_proof: + proofs_spent.append(spent_proof) else: # check used secrets in database async with self.db.connect() as conn: - for secret in secrets: + for proof in proofs: spent_proof = await self.crud.get_proof_used( - db=self.db, secret=secret, conn=conn + db=self.db, Y=proof.Y, conn=conn ) if spent_proof: proofs_spent.append(spent_proof) diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 8ace2478..314d3182 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -1120,7 +1120,14 @@ async def _construct_proofs( C = b_dhke.step3_alice( C_, r, self.keysets[promise.id].public_keys[promise.amount] ) - B_, r = b_dhke.step1_alice(secret, r) # recompute B_ for dleq proofs + # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 + if not settings.wallet_domain_separation: + B_, r = b_dhke.step1_alice(secret, r) # recompute B_ for dleq proofs + # END: BACKWARDS COMPATIBILITY < 0.15.1 + else: + B_, r = b_dhke.step1_alice_domain_separated( + secret, r + ) # recompute B_ for dleq proofs proof = Proof( id=promise.id, @@ -1183,7 +1190,12 @@ def _construct_outputs( rs_ = [None] * len(amounts) if not rs else rs rs_return: List[PrivateKey] = [] for secret, amount, r in zip(secrets, amounts, rs_): - B_, r = b_dhke.step1_alice(secret, r or None) + # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 + if not settings.wallet_domain_separation: + B_, r = b_dhke.step1_alice(secret, r or None) + # END: BACKWARDS COMPATIBILITY < 0.15.1 + else: + B_, r = b_dhke.step1_alice_domain_separated(secret, r or None) rs_return.append(r) output = BlindedMessage( amount=amount, B_=B_.serialize().hex(), id=self.keyset_id diff --git a/tests/conftest.py b/tests/conftest.py index cad7e07f..53a36c4a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import asyncio +import importlib import multiprocessing import os import shutil @@ -45,7 +46,7 @@ shutil.rmtree(settings.cashu_dir, ignore_errors=True) Path(settings.cashu_dir).mkdir(parents=True, exist_ok=True) -from cashu.mint.startup import lightning_backend # noqa +# from cashu.mint.startup import lightning_backend # noqa @pytest.fixture(scope="session") @@ -99,7 +100,8 @@ async def start_mint_init(ledger: Ledger): db_file = os.path.join(settings.mint_database, "mint.sqlite3") if os.path.exists(db_file): os.remove(db_file) - + wallets_module = importlib.import_module("cashu.lightning") + lightning_backend = getattr(wallets_module, settings.mint_lightning_backend)() backends = { Method.bolt11: {Unit.sat: lightning_backend}, } diff --git a/tests/helpers.py b/tests/helpers.py index 0cd36a9f..f121711d 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -30,6 +30,7 @@ async def get_random_invoice_data(): is_fake: bool = WALLET.__class__.__name__ == "FakeWallet" is_regtest: bool = not is_fake is_deprecated_api_only = settings.debug_mint_only_deprecated +is_github_actions = os.getenv("GITHUB_ACTIONS") == "true" docker_lightning_cli = [ "docker", diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 15c253dc..78195f6d 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -13,7 +13,13 @@ from cashu.wallet.wallet import Wallet as Wallet1 from cashu.wallet.wallet import Wallet as Wallet2 from tests.conftest import SERVER_ENDPOINT -from tests.helpers import get_real_invoice, is_fake, is_regtest, pay_if_regtest +from tests.helpers import ( + get_real_invoice, + is_fake, + is_github_actions, + is_regtest, + pay_if_regtest, +) async def assert_err(f, msg: Union[str, CashuError]): @@ -349,12 +355,30 @@ async def test_duplicate_proofs_double_spent(wallet1: Wallet): doublespend = await wallet1.mint(64, id=invoice.id) await assert_err( wallet1.split(wallet1.proofs + doublespend, 20), - "Mint Error: proofs already pending.", + "Mint Error: Failed to set proofs pending.", ) assert wallet1.balance == 64 assert wallet1.available_balance == 64 +@pytest.mark.asyncio +@pytest.mark.skipif(is_github_actions, reason="GITHUB_ACTIONS") +async def test_split_race_condition(wallet1: Wallet): + invoice = await wallet1.request_mint(64) + pay_if_regtest(invoice.bolt11) + await wallet1.mint(64, id=invoice.id) + # run two splits in parallel + import asyncio + + await assert_err( + asyncio.gather( + wallet1.split(wallet1.proofs, 20), + wallet1.split(wallet1.proofs, 20), + ), + "proofs are pending.", + ) + + @pytest.mark.asyncio async def test_send_and_redeem(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) diff --git a/tests/test_cli.py b/tests/test_wallet_cli.py similarity index 100% rename from tests/test_cli.py rename to tests/test_wallet_cli.py From a2b9ac8f14e1c13708e2f0f9c3bff051758c58c5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 11 Feb 2024 12:46:53 +0100 Subject: [PATCH 17/57] relative import (#414) --- cashu/core/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 8ee0e43b..7a9c29e0 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -9,9 +9,8 @@ from loguru import logger from pydantic import BaseModel, Field -from cashu.core.crypto.b_dhke import hash_to_curve - from .crypto.aes import AESCipher +from .crypto.b_dhke import hash_to_curve from .crypto.keys import ( derive_keys, derive_keys_sha256, From ac8e21a3a48f75f34996d22523fe78baa8d81b27 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 11 Feb 2024 12:51:44 +0100 Subject: [PATCH 18/57] relative import all (#415) --- cashu/mint/migrations.py | 3 +-- cashu/wallet/cli/cli.py | 3 +-- cashu/wallet/nostr.py | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 79158bf3..4093b211 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -1,5 +1,4 @@ -from cashu.core.base import Proof - +from ..core.base import Proof from ..core.db import Connection, Database, table_with_schema, timestamp_now from ..core.settings import settings diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 2d1d177c..80c5d6c4 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -14,10 +14,9 @@ from click import Context from loguru import logger -from cashu.core.logging import configure_logger - from ...core.base import TokenV3, Unit from ...core.helpers import sum_proofs +from ...core.logging import configure_logger from ...core.settings import settings from ...nostr.client.client import NostrClient from ...tor.tor import TorProxy diff --git a/cashu/wallet/nostr.py b/cashu/wallet/nostr.py index b6099cc9..56dfe73f 100644 --- a/cashu/wallet/nostr.py +++ b/cashu/wallet/nostr.py @@ -6,8 +6,7 @@ from httpx import ConnectError from loguru import logger -from cashu.core.base import TokenV3 - +from ..core.base import TokenV3 from ..core.settings import settings from ..nostr.client.client import NostrClient from ..nostr.event import Event From 78de84f3ebe03c1dc96765d8aca9d7a3587f3cfa Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 11 Feb 2024 13:49:28 +0100 Subject: [PATCH 19/57] drop balance views during migration (#416) --- cashu/mint/migrations.py | 83 +++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 4093b211..81a79f27 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -49,34 +49,46 @@ async def m001_initial(db: Database): """) -async def m002_add_balance_views(db: Database): - async with db.connect() as conn: - await conn.execute(f""" - CREATE VIEW {table_with_schema(db, 'balance_issued')} AS - SELECT COALESCE(SUM(s), 0) AS balance FROM ( - SELECT SUM(amount) AS s - FROM {table_with_schema(db, 'promises')} - WHERE amount > 0 - ) AS balance_issued; - """) +async def drop_balance_views(db: Database, conn: Connection): + await conn.execute(f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance')}") + await conn.execute(f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance_issued')}") + await conn.execute( + f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance_redeemed')}" + ) - await conn.execute(f""" - CREATE VIEW {table_with_schema(db, 'balance_redeemed')} AS - SELECT COALESCE(SUM(s), 0) AS balance FROM ( - SELECT SUM(amount) AS s - FROM {table_with_schema(db, 'proofs_used')} - WHERE amount > 0 - ) AS balance_redeemed; - """) - await conn.execute(f""" - CREATE VIEW {table_with_schema(db, 'balance')} AS - SELECT s_issued - s_used FROM ( - SELECT bi.balance AS s_issued, bu.balance AS s_used - FROM {table_with_schema(db, 'balance_issued')} bi - CROSS JOIN {table_with_schema(db, 'balance_redeemed')} bu - ) AS balance; - """) +async def create_balance_views(db: Database, conn: Connection): + await conn.execute(f""" + CREATE VIEW {table_with_schema(db, 'balance_issued')} AS + SELECT COALESCE(SUM(s), 0) AS balance FROM ( + SELECT SUM(amount) AS s + FROM {table_with_schema(db, 'promises')} + WHERE amount > 0 + ) AS balance_issued; + """) + + await conn.execute(f""" + CREATE VIEW {table_with_schema(db, 'balance_redeemed')} AS + SELECT COALESCE(SUM(s), 0) AS balance FROM ( + SELECT SUM(amount) AS s + FROM {table_with_schema(db, 'proofs_used')} + WHERE amount > 0 + ) AS balance_redeemed; + """) + + await conn.execute(f""" + CREATE VIEW {table_with_schema(db, 'balance')} AS + SELECT s_issued - s_used FROM ( + SELECT bi.balance AS s_issued, bu.balance AS s_used + FROM {table_with_schema(db, 'balance_issued')} bi + CROSS JOIN {table_with_schema(db, 'balance_redeemed')} bu + ) AS balance; + """) + + +async def m002_add_balance_views(db: Database): + async with db.connect() as conn: + await create_balance_views(db, conn) async def m003_mint_keysets(db: Database): @@ -184,15 +196,6 @@ async def m008_promises_dleq(db: Database): async def m009_add_out_to_invoices(db: Database): # column in invoices for marking whether the invoice is incoming (out=False) or outgoing (out=True) async with db.connect() as conn: - # we have to drop the balance views first and recreate them later - await conn.execute(f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance')}") - await conn.execute( - f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance_issued')}" - ) - await conn.execute( - f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance_redeemed')}" - ) - # rename column pr to bolt11 await conn.execute( f"ALTER TABLE {table_with_schema(db, 'invoices')} RENAME COLUMN pr TO" @@ -203,10 +206,6 @@ async def m009_add_out_to_invoices(db: Database): f"ALTER TABLE {table_with_schema(db, 'invoices')} RENAME COLUMN hash TO id" ) - # recreate balance views - await m002_add_balance_views(db) - - async with db.connect() as conn: await conn.execute( f"ALTER TABLE {table_with_schema(db, 'invoices')} ADD COLUMN out BOOL" ) @@ -409,6 +408,9 @@ async def m014_proofs_add_Y_column(db: Database): ) proofs_pending = [Proof(**r) for r in rows] async with db.connect() as conn: + # we have to drop the balance views first and recreate them later + await drop_balance_views(db, conn) + await conn.execute( f"ALTER TABLE {table_with_schema(db, 'proofs_used')} ADD COLUMN Y TEXT" ) @@ -492,3 +494,6 @@ async def m014_proofs_add_Y_column(db: Database): ) await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_pending_old')}") + + # recreate the balance views + await create_balance_views(db, conn) From f74f18c9ca4aebe4eada157dce8aa127fa46d03b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:52:02 +0100 Subject: [PATCH 20/57] Github CI with Postgres (#417) * with postgres * postgres test explicit * make format * start postgres only if needed * remove if again * print db * db in matrix * delete schema * use new env var MINT_TEST_DATABASE for tests * add db path to regtest --- .github/workflows/ci.yml | 6 ++++-- .github/workflows/regtest.yml | 18 ++++++++++++++++-- .github/workflows/tests.yml | 6 +++--- cashu/core/settings.py | 1 + tests/conftest.py | 9 ++++++++- tests/helpers.py | 1 + tests/test_mint_db.py | 8 ++++++++ 7 files changed, 41 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95be9061..53459421 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,8 +17,8 @@ jobs: poetry-version: ["1.7.1"] mint-cache-secrets: ["false", "true"] mint-only-deprecated: ["false", "true"] - # db-url: ["", "postgres://cashu:cashu@localhost:5432/cashu"] # TODO: Postgres test not working - db-url: [""] + mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"] + # mint-database: [""] backend-wallet-class: ["FakeWallet"] uses: ./.github/workflows/tests.yml with: @@ -27,6 +27,7 @@ jobs: poetry-version: ${{ matrix.poetry-version }} mint-cache-secrets: ${{ matrix.mint-cache-secrets }} mint-only-deprecated: ${{ matrix.mint-only-deprecated }} + mint-database: ${{ matrix.mint-database }} regtest: uses: ./.github/workflows/regtest.yml strategy: @@ -38,3 +39,4 @@ jobs: with: python-version: ${{ matrix.python-version }} backend-wallet-class: ${{ matrix.backend-wallet-class }} + mint-database: "./test_data/test_mint" diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index 48653736..46e2d8ed 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -12,7 +12,7 @@ on: os-version: default: "ubuntu-latest" type: string - db-url: + mint-database: default: "" type: string backend-wallet-class: @@ -23,6 +23,20 @@ jobs: regtest: runs-on: ${{ inputs.os-version }} timeout-minutes: 10 + services: + postgres: + image: postgres:latest + env: + POSTGRES_USER: cashu + POSTGRES_PASSWORD: cashu + POSTGRES_DB: cashu + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - uses: actions/checkout@v3 @@ -47,7 +61,7 @@ jobs: WALLET_NAME: test_wallet MINT_HOST: localhost MINT_PORT: 3337 - MINT_DATABASE: ${{ inputs.db-url }} + MINT_TEST_DATABASE: ${{ inputs.mint-database }} TOR: false MINT_LIGHTNING_BACKEND: ${{ inputs.backend-wallet-class }} MINT_LNBITS_ENDPOINT: http://localhost:5001 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6659b06a..cf1a176b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,7 @@ on: poetry-version: default: "1.7.1" type: string - db-url: + mint-database: default: "" type: string os: @@ -24,7 +24,7 @@ on: jobs: poetry: - name: Run (mint-cache-secrets ${{ inputs.mint-cache-secrets }}, mint-only-deprecated ${{ inputs.mint-only-deprecated }}) + name: Run (mint-cache-secrets ${{ inputs.mint-cache-secrets }}, mint-only-deprecated ${{ inputs.mint-only-deprecated }}, mint-database ${{ inputs.mint-database }}) runs-on: ${{ inputs.os }} services: postgres: @@ -53,7 +53,7 @@ jobs: WALLET_NAME: test_wallet MINT_HOST: localhost MINT_PORT: 3337 - MINT_DATABASE: ${{ inputs.db-url }} + MINT_TEST_DATABASE: ${{ inputs.mint-database }} MINT_CACHE_SECRETS: ${{ inputs.mint-cache-secrets }} DEBUG_MINT_ONLY_DEPRECATED: ${{ inputs.mint-only-deprecated }} TOR: false diff --git a/cashu/core/settings.py b/cashu/core/settings.py index ace76940..5bf13d4a 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -56,6 +56,7 @@ class MintSettings(CashuSettings): mint_listen_port: int = Field(default=3338) mint_lightning_backend: str = Field(default="LNbitsWallet") mint_database: str = Field(default="data/mint") + mint_test_database: str = Field(default="test_data/test_mint") mint_peg_out_only: bool = Field( default=False, title="Peg-out only", diff --git a/tests/conftest.py b/tests/conftest.py index 53a36c4a..231853eb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,7 +35,7 @@ settings.fakewallet_brr = True settings.fakewallet_delay_payment = False settings.fakewallet_stochastic_invoice = False -settings.mint_database = "./test_data/test_mint" +settings.mint_database = settings.mint_test_database settings.mint_derivation_path = "m/0'/0'/0'" settings.mint_derivation_path_list = [] settings.mint_private_key = "TEST_PRIVATE_KEY" @@ -100,6 +100,13 @@ async def start_mint_init(ledger: Ledger): db_file = os.path.join(settings.mint_database, "mint.sqlite3") if os.path.exists(db_file): os.remove(db_file) + else: + # clear postgres database + db = Database("mint", settings.mint_database) + async with db.connect() as conn: + await conn.execute("DROP SCHEMA public CASCADE;") + await conn.execute("CREATE SCHEMA public;") + wallets_module = importlib.import_module("cashu.lightning") lightning_backend = getattr(wallets_module, settings.mint_lightning_backend)() backends = { diff --git a/tests/helpers.py b/tests/helpers.py index f121711d..94fe729b 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -31,6 +31,7 @@ async def get_random_invoice_data(): is_regtest: bool = not is_fake is_deprecated_api_only = settings.debug_mint_only_deprecated is_github_actions = os.getenv("GITHUB_ACTIONS") == "true" +is_postgres = settings.mint_database.startswith("postgres") docker_lightning_cli = [ "docker", diff --git a/tests/test_mint_db.py b/tests/test_mint_db.py index d45a4200..1d006a2f 100644 --- a/tests/test_mint_db.py +++ b/tests/test_mint_db.py @@ -6,6 +6,7 @@ from cashu.wallet.wallet import Wallet from cashu.wallet.wallet import Wallet as Wallet1 from tests.conftest import SERVER_ENDPOINT +from tests.helpers import is_postgres async def assert_err(f, msg): @@ -61,3 +62,10 @@ async def test_melt_quote(wallet1: Wallet, ledger: Ledger): assert quote.checking_id == invoice.payment_hash assert quote.paid_time is None assert quote.created_time + + +@pytest.mark.asyncio +@pytest.mark.skipif(not is_postgres, reason="only works with Postgres") +async def test_postgres_working(): + assert is_postgres + assert True From 6fd66b9d7216b16fd8439dc654d1dd9ae1cd4316 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 11 Feb 2024 17:31:56 +0100 Subject: [PATCH 21/57] Mint: check that test db is different from main db (#419) * check that test db is different from main db * make format --- .github/workflows/ci.yml | 1 - tests/conftest.py | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53459421..82296e13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,6 @@ jobs: mint-cache-secrets: ["false", "true"] mint-only-deprecated: ["false", "true"] mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"] - # mint-database: [""] backend-wallet-class: ["FakeWallet"] uses: ./.github/workflows/tests.yml with: diff --git a/tests/conftest.py b/tests/conftest.py index 231853eb..f6887cb2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,9 @@ settings.fakewallet_brr = True settings.fakewallet_delay_payment = False settings.fakewallet_stochastic_invoice = False +assert ( + settings.mint_test_database != settings.mint_database +), "Test database is the same as the main database" settings.mint_database = settings.mint_test_database settings.mint_derivation_path = "m/0'/0'/0'" settings.mint_derivation_path_list = [] From 464c0e0ea4bb31ecb045dd5c38a4bd6e33e917c8 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 11 Feb 2024 20:23:43 +0100 Subject: [PATCH 22/57] DB backups before migrations (#420) * make backups before migrations * database tests * postgres db backup with location string * ignore version in pg_dump and throw warning * install latest pg_dump * install latest * pg update? * remove test from github * skip for postgres on github actions --- cashu/core/migrations.py | 51 +++++++++++++++++++++++++++++ cashu/core/settings.py | 1 + cashu/mint/migrations.py | 10 ++++++ tests/test_db.py | 69 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 tests/test_db.py diff --git a/cashu/core/migrations.py b/cashu/core/migrations.py index ce3e2bfe..0b862af0 100644 --- a/cashu/core/migrations.py +++ b/cashu/core/migrations.py @@ -1,8 +1,44 @@ +import os import re +import time from loguru import logger from ..core.db import COCKROACH, POSTGRES, SQLITE, Database, table_with_schema +from ..core.settings import settings + + +async def backup_database(db: Database, version: int = 0) -> str: + # for postgres: use pg_dump + # for sqlite: use sqlite3 + + # skip backups if db_backup_path is None + # and if version is 0 (fresh database) + if not settings.db_backup_path or not version: + return "" + + filename = f"backup_{db.name}_{int(time.time())}_v{version}" + try: + # create backup directory if it doesn't exist + os.makedirs(os.path.join(settings.db_backup_path), exist_ok=True) + except Exception as e: + logger.error( + f"Error creating backup directory: {e}. Run with BACKUP_DB_MIGRATION=False" + " to disable backups before database migrations." + ) + raise e + filepath = os.path.join(settings.db_backup_path, filename) + + if db.type == SQLITE: + filepath = f"{filepath}.sqlite3" + logger.info(f"Creating {db.type} backup of {db.name} db to {filepath}") + os.system(f"cp {db.path} {filepath}") + elif db.type in {POSTGRES, COCKROACH}: + filepath = f"{filepath}.dump" + logger.info(f"Creating {db.type} backup of {db.name} db to {filepath}") + os.system(f"pg_dump --dbname={db.db_location} --file={filepath}") + + return filepath async def migrate_databases(db: Database, migrations_module): @@ -19,6 +55,21 @@ async def set_migration_version(conn, db_name, version): async def run_migration(db, migrations_module): db_name = migrations_module.__name__.split(".")[-2] + # we first check whether any migration is needed and create a backup if so + migration_needed = False + for key, migrate in migrations_module.__dict__.items(): + match = matcher.match(key) + if match: + version = int(match.group(1)) + if version > current_versions.get(db_name, 0): + migration_needed = True + break + if migration_needed: + logger.debug(f"Creating backup of {db_name} db") + current_version = current_versions.get(db_name, 0) + await backup_database(db, current_version) + + # then we run the migrations for key, migrate in migrations_module.__dict__.items(): match = matcher.match(key) if match: diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 5bf13d4a..f65fd8b4 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -45,6 +45,7 @@ class EnvSettings(CashuSettings): cashu_dir: str = Field(default=os.path.join(str(Path.home()), ".cashu")) debug_profiling: bool = Field(default=False) debug_mint_only_deprecated: bool = Field(default=False) + db_backup_path: str = Field(default=False) class MintSettings(CashuSettings): diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 81a79f27..90323507 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -497,3 +497,13 @@ async def m014_proofs_add_Y_column(db: Database): # recreate the balance views await create_balance_views(db, conn) + + +async def m015_add_index_Y_to_proofs_used(db: Database): + # create index on proofs_used table for Y + async with db.connect() as conn: + await conn.execute( + "CREATE INDEX IF NOT EXISTS" + " proofs_used_Y_idx ON" + f" {table_with_schema(db, 'proofs_used')} (Y)" + ) diff --git a/tests/test_db.py b/tests/test_db.py new file mode 100644 index 00000000..3b2af374 --- /dev/null +++ b/tests/test_db.py @@ -0,0 +1,69 @@ +import datetime +import os +import time + +import pytest + +from cashu.core import db +from cashu.core.db import Connection, timestamp_now +from cashu.core.migrations import backup_database +from cashu.core.settings import settings +from cashu.mint.ledger import Ledger +from tests.helpers import is_github_actions, is_postgres + + +@pytest.mark.asyncio +@pytest.mark.skipif( + is_github_actions and is_postgres, + reason=( + "Fails on GitHub Actions because pg_dump is not the same version as postgres" + ), +) +async def test_backup_db_migration(ledger: Ledger): + settings.db_backup_path = "./test_data/backups/" + filepath = await backup_database(ledger.db, 999) + assert os.path.exists(filepath) + + +@pytest.mark.asyncio +async def test_timestamp_now(ledger: Ledger): + ts = timestamp_now(ledger.db) + if ledger.db.type == db.SQLITE: + assert isinstance(ts, str) + assert int(ts) <= time.time() + elif ledger.db.type in {db.POSTGRES, db.COCKROACH}: + assert isinstance(ts, str) + datetime.datetime.strptime(ts, "%Y-%m-%d %H:%M:%S") + + +@pytest.mark.asyncio +async def test_get_connection(ledger: Ledger): + async with ledger.db.connect() as conn: + assert isinstance(conn, Connection) + + +@pytest.mark.asyncio +async def test_db_tables(ledger: Ledger): + async with ledger.db.connect() as conn: + if ledger.db.type == db.SQLITE: + tables_res = await conn.execute( + "SELECT name FROM sqlite_master WHERE type='table';" + ) + elif ledger.db.type in {db.POSTGRES, db.COCKROACH}: + tables_res = await conn.execute( + "SELECT table_name FROM information_schema.tables WHERE table_schema =" + " 'public';" + ) + tables = [t[0] for t in await tables_res.fetchall()] + tables_expected = [ + "dbversions", + "keysets", + "proofs_used", + "proofs_pending", + "melt_quotes", + "mint_quotes", + "mint_pubkeys", + "promises", + ] + for table in tables_expected: + assert table in tables From 7c644e1835020d9e449478f88cc25de8e3f2cd81 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:45:45 +0100 Subject: [PATCH 23/57] Adjust new domain separator (#421) * adjust new domain separator * Update cashu/core/crypto/b_dhke.py Co-authored-by: Lagrang3 <32647090+Lagrang3@users.noreply.github.com> * slightly adjust comment --------- Co-authored-by: Lagrang3 <32647090+Lagrang3@users.noreply.github.com> --- cashu/core/crypto/b_dhke.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/cashu/core/crypto/b_dhke.py b/cashu/core/crypto/b_dhke.py index df0bcccf..4abf1b77 100644 --- a/cashu/core/crypto/b_dhke.py +++ b/cashu/core/crypto/b_dhke.py @@ -71,24 +71,34 @@ def hash_to_curve(message: bytes) -> PublicKey: return point -DOMAIN_SEPARATOR = b"Secp256k1_HashToCurve_" +DOMAIN_SEPARATOR = b"Secp256k1_HashToCurve_Cashu_" def hash_to_curve_domain_separated(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 = DOMAIN_SEPARATOR + message + """Generates a secp256k1 point from a message. + + The point is generated by hashing the message with a domain separator and then + iteratively trying to compute a point from the hash. An increasing uint32 counter + (byte order little endian) is appended to the hash until a point is found that lies on the curve. + + The chance of finding a valid point is 50% for every iteration. The maximum number of iterations + is 2**16. If no valid point is found after 2**16 iterations, a ValueError is raised (this should + never happen in practice). + + The domain separator is b"Secp256k1_HashToCurve_Cashu_" or + bytes.fromhex("536563703235366b315f48617368546f43757276655f43617368755f"). + """ + msg_to_hash = hashlib.sha256(DOMAIN_SEPARATOR + message).digest() counter = 0 - while point is None: - _hash = hashlib.sha256(msg_to_hash + str(counter).encode()).digest() + while counter < 2**16: + _hash = hashlib.sha256(msg_to_hash + counter.to_bytes(4, "little")).digest() try: # will error if point does not lie on curve - point = PublicKey(b"\x02" + _hash, raw=True) + return PublicKey(b"\x02" + _hash, raw=True) except Exception: - msg_to_hash = _hash counter += 1 - return point + # it should never reach this point + raise ValueError("No valid point found") def step1_alice( From d168b4b516bb419971403d1b5577e696e97b4e11 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:30:53 +0100 Subject: [PATCH 24/57] do not migrate in the beginning (#422) --- cashu/core/migrations.py | 2 +- cashu/core/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cashu/core/migrations.py b/cashu/core/migrations.py index 0b862af0..7bab5eeb 100644 --- a/cashu/core/migrations.py +++ b/cashu/core/migrations.py @@ -64,7 +64,7 @@ async def run_migration(db, migrations_module): if version > current_versions.get(db_name, 0): migration_needed = True break - if migration_needed: + if migration_needed and settings.db_backup_path: logger.debug(f"Creating backup of {db_name} db") current_version = current_versions.get(db_name, 0) await backup_database(db, current_version) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index f65fd8b4..6a58eeb4 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -45,7 +45,7 @@ class EnvSettings(CashuSettings): cashu_dir: str = Field(default=os.path.join(str(Path.home()), ".cashu")) debug_profiling: bool = Field(default=False) debug_mint_only_deprecated: bool = Field(default=False) - db_backup_path: str = Field(default=False) + db_backup_path: Optional[str] = Field(default=None) class MintSettings(CashuSettings): From e251e8a9ea85046b2bd10db8bed4cad82dddd4a4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:01:06 +0100 Subject: [PATCH 25/57] no backup (#423) --- cashu/core/migrations.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cashu/core/migrations.py b/cashu/core/migrations.py index 7bab5eeb..d92edafd 100644 --- a/cashu/core/migrations.py +++ b/cashu/core/migrations.py @@ -56,18 +56,12 @@ async def set_migration_version(conn, db_name, version): async def run_migration(db, migrations_module): db_name = migrations_module.__name__.split(".")[-2] # we first check whether any migration is needed and create a backup if so - migration_needed = False for key, migrate in migrations_module.__dict__.items(): match = matcher.match(key) if match: version = int(match.group(1)) if version > current_versions.get(db_name, 0): - migration_needed = True break - if migration_needed and settings.db_backup_path: - logger.debug(f"Creating backup of {db_name} db") - current_version = current_versions.get(db_name, 0) - await backup_database(db, current_version) # then we run the migrations for key, migrate in migrations_module.__dict__.items(): From ecad95715f7059545478a3c4f164729e65aa4eaf Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:58:40 +0100 Subject: [PATCH 26/57] readd db backup (#424) --- cashu/core/migrations.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cashu/core/migrations.py b/cashu/core/migrations.py index d92edafd..7bab5eeb 100644 --- a/cashu/core/migrations.py +++ b/cashu/core/migrations.py @@ -56,12 +56,18 @@ async def set_migration_version(conn, db_name, version): async def run_migration(db, migrations_module): db_name = migrations_module.__name__.split(".")[-2] # we first check whether any migration is needed and create a backup if so + migration_needed = False for key, migrate in migrations_module.__dict__.items(): match = matcher.match(key) if match: version = int(match.group(1)) if version > current_versions.get(db_name, 0): + migration_needed = True break + if migration_needed and settings.db_backup_path: + logger.debug(f"Creating backup of {db_name} db") + current_version = current_versions.get(db_name, 0) + await backup_database(db, current_version) # then we run the migrations for key, migrate in migrations_module.__dict__.items(): From 3f090c1691b0c674fcaa637432c531a5f3cf1e7d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 16 Feb 2024 11:40:46 +0100 Subject: [PATCH 27/57] Mint: add expiry to quotes, closes #385 (#429) * add expiry to quotes, closes #385 * fix wallet deprecated mock with expiry * expiry is an optional field --- cashu/core/base.py | 7 ++++--- cashu/lightning/fake.py | 3 +-- cashu/mint/ledger.py | 4 +++- cashu/mint/router.py | 1 + cashu/wallet/wallet.py | 1 + tests/test_mint_api.py | 3 +++ 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 7a9c29e0..b32933b2 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -230,6 +230,7 @@ class MeltQuote(BaseModel): paid_time: Union[int, None] = None fee_paid: int = 0 proof: str = "" + expiry: Optional[int] = None @classmethod def from_row(cls, row: Row): @@ -269,7 +270,7 @@ class MintQuote(BaseModel): issued: bool created_time: Union[int, None] = None paid_time: Union[int, None] = None - expiry: int = 0 + expiry: Optional[int] = None @classmethod def from_row(cls, row: Row): @@ -295,7 +296,6 @@ def from_row(cls, row: Row): issued=row["issued"], created_time=created_time, paid_time=paid_time, - expiry=0, ) @@ -370,7 +370,7 @@ class PostMintQuoteResponse(BaseModel): quote: str # quote id request: str # input payment request paid: bool # whether the request has been paid - expiry: int # expiry of the quote + expiry: Optional[int] # expiry of the quote # ------- API: MINT ------- @@ -417,6 +417,7 @@ class PostMeltQuoteResponse(BaseModel): amount: int # input amount fee_reserve: int # input fee reserve paid: bool # whether the request has been paid + expiry: Optional[int] # expiry of the quote # ------- API: MELT ------- diff --git a/cashu/lightning/fake.py b/cashu/lightning/fake.py index dc6a6d91..8da840ee 100644 --- a/cashu/lightning/fake.py +++ b/cashu/lightning/fake.py @@ -66,8 +66,7 @@ async def create_invoice( else: tags.add(TagChar.description, memo or "") - if expiry: - tags.add(TagChar.expire_time, expiry) + tags.add(TagChar.expire_time, expiry or 3600) if payment_secret: secret = payment_secret.hex() diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 8d0be64b..23502830 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -353,7 +353,7 @@ async def mint_quote(self, quote_request: PostMintQuoteRequest) -> MintQuote: issued=False, paid=False, created_time=int(time.time()), - expiry=invoice_obj.expiry or 0, + expiry=invoice_obj.expiry, ) await self.crud.store_mint_quote( quote=quote, @@ -507,6 +507,7 @@ async def melt_quote( paid=False, fee_reserve=payment_quote.fee.to(unit).amount, created_time=int(time.time()), + expiry=invoice_obj.expiry, ) await self.crud.store_melt_quote(quote=quote, db=self.db) return PostMeltQuoteResponse( @@ -514,6 +515,7 @@ async def melt_quote( amount=quote.amount, fee_reserve=quote.fee_reserve, paid=quote.paid, + expiry=quote.expiry, ) async def get_melt_quote(self, quote_id: str) -> MeltQuote: diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 02289bcc..d4deb948 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -262,6 +262,7 @@ async def melt_quote(quote: str) -> PostMeltQuoteResponse: amount=melt_quote.amount, fee_reserve=melt_quote.fee_reserve, paid=melt_quote.paid, + expiry=melt_quote.expiry, ) logger.trace(f"< GET /v1/melt/quote/bolt11/{quote}") return resp diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 314d3182..46d78ecc 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -543,6 +543,7 @@ async def melt_quote(self, payment_request: str) -> PostMeltQuoteResponse: amount=invoice_obj.amount_msat // 1000, fee_reserve=ret.fee or 0, paid=False, + expiry=invoice_obj.expiry, ) # END backwards compatibility < 0.15.0 self.raise_on_error_request(resp) diff --git a/tests/test_mint_api.py b/tests/test_mint_api.py index d46dd3e7..22d8e30c 100644 --- a/tests/test_mint_api.py +++ b/tests/test_mint_api.py @@ -182,6 +182,7 @@ async def test_mint_quote(ledger: Ledger): assert result["request"] invoice = bolt11.decode(result["request"]) assert invoice.amount_msat == 100 * 1000 + assert result["expiry"] == invoice.expiry # get mint quote again from api response = httpx.get( @@ -243,6 +244,8 @@ async def test_melt_quote_internal(ledger: Ledger, wallet: Wallet): assert result["amount"] == 64 # TODO: internal invoice, fee should be 0 assert result["fee_reserve"] == 0 + invoice_obj = bolt11.decode(request) + assert result["expiry"] == invoice_obj.expiry # get melt quote again from api response = httpx.get( From afbde83ae24ee09b0497550c66c24a9b08840508 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:11:39 +0100 Subject: [PATCH 28/57] wallet print on json deseriliaztion error and batch invalidation of proofs (#430) --- cashu/wallet/cli/cli.py | 4 +++- cashu/wallet/wallet.py | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 80c5d6c4..605ef240 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -576,7 +576,9 @@ async def burn(ctx: Context, token: str, all: bool, force: bool, delete: str): if delete: await wallet.invalidate(proofs) else: - await wallet.invalidate(proofs, check_spendable=True) + # batch check proofs + for _proofs in [proofs[i : i + 100] for i in range(0, len(proofs), 100)]: + await wallet.invalidate(_proofs, check_spendable=True) print_balance(ctx) diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 46d78ecc..aef3dc13 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -157,7 +157,12 @@ def raise_on_error_request( Raises: Exception: if the response contains an error """ - resp_dict = resp.json() + try: + resp_dict = resp.json() + except json.JSONDecodeError: + # if we can't decode the response, raise for status + resp.raise_for_status() + return if "detail" in resp_dict: logger.trace(f"Error from mint: {resp_dict}") error_message = f"Mint Error: {resp_dict['detail']}" From 1397c46ff479c3deb29674acaf4721efc605005b Mon Sep 17 00:00:00 2001 From: Steffen <99717310+ngutech21@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:07:44 +0100 Subject: [PATCH 29/57] add missing disabled-flag to nut4 (#426) * add missing disabled-flag to nut4 * Update cashu/mint/router.py --------- Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com> --- cashu/mint/router.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cashu/mint/router.py b/cashu/mint/router.py index d4deb948..d0cac90a 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -50,6 +50,7 @@ async def info() -> GetInfoResponse: mint_features: Dict[int, Dict[str, Any]] = { 4: dict( methods=method_unit_pairs, + disabled=False, ), 5: dict( methods=method_unit_pairs, From d1540ccb5b5d15a9050667d6d1a3410fdaa4929d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:22:41 +0100 Subject: [PATCH 30/57] wallet: add batch size setting (#431) --- cashu/core/settings.py | 1 + cashu/wallet/cli/cli.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 6a58eeb4..a7d126e3 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -139,6 +139,7 @@ class WalletSettings(CashuSettings): ) locktime_delta_seconds: int = Field(default=86400) # 1 day + proofs_batch_size: int = Field(default=1000) class LndRestFundingSource(MintSettings): diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 605ef240..b2010b86 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -576,8 +576,11 @@ async def burn(ctx: Context, token: str, all: bool, force: bool, delete: str): if delete: await wallet.invalidate(proofs) else: - # batch check proofs - for _proofs in [proofs[i : i + 100] for i in range(0, len(proofs), 100)]: + # invalidate proofs in batches + for _proofs in [ + proofs[i : i + settings.proofs_batch_size] + for i in range(0, len(proofs), settings.proofs_batch_size) + ]: await wallet.invalidate(_proofs, check_spendable=True) print_balance(ctx) From 94db0fc4c6b39e597d74636807c40187ff3d9893 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:02:26 +0100 Subject: [PATCH 31/57] Docker: use python 3.10 (#432) * Docker: use python 3.10 * Add Docker workflow * Build always * add all --- .github/workflows/docker.yaml | 38 +++++++++++++++++++++++++++++++++++ Dockerfile | 2 +- Makefile | 8 ++++++++ setup.py | 4 ++-- 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/docker.yaml diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 00000000..ded34be9 --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,38 @@ +name: Docker Build + +on: + push: + release: + types: [published] + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Determine Tag + id: get_tag + run: | + if [[ "${{ github.event_name }}" == "push" ]]; then + echo "::set-output name=tag::latest" + elif [[ "${{ github.event_name }}" == "release" ]]; then + echo "::set-output name=tag::${{ github.event.release.tag_name }}" + fi + + - name: Build and push on release + uses: docker/build-push-action@v5 + with: + context: . + push: ${{ github.event_name == 'release' }} + tags: ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}:${{ steps.get_tag.outputs.tag }} diff --git a/Dockerfile b/Dockerfile index 6d65364a..da3eb565 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9-slim +FROM python:3.10-slim RUN apt-get update RUN apt-get install -y curl python3-dev autoconf g++ RUN apt-get install -y libpq-dev diff --git a/Makefile b/Makefile index 7bc6d6e9..b9e79292 100644 --- a/Makefile +++ b/Makefile @@ -59,3 +59,11 @@ install-pre-commit-hook: pre-commit: poetry run pre-commit run --all-files + +docker-build: + rm -rf docker-build || true + mkdir -p docker-build + git clone . docker-build + cd docker-build + docker buildx build -f Dockerfile -t cashubtc/nutshell:0.15.0 --platform linux/amd64 . + # docker push cashubtc/nutshell:0.15.0 diff --git a/setup.py b/setup.py index d9d8e3d8..52574dc7 100644 --- a/setup.py +++ b/setup.py @@ -14,12 +14,12 @@ setuptools.setup( name="cashu", version="0.15.0", - description="Ecash wallet and mint for Bitcoin Lightning", + description="Ecash wallet and mint", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/cashubtc/cashu", author="Calle", - author_email="calle@protonmail.com", + author_email="callebtc@protonmail.com", license="MIT", packages=setuptools.find_namespace_packages(), classifiers=[ From c285d48edf3999b9c4278908495ef0a3883f49fe Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 17 Feb 2024 20:51:37 +0100 Subject: [PATCH 32/57] Add Blink backend (#433) * wip blink * Blink working * check fee correctly * add comment * unfix lnbits * unit test blink backend * make format * mypy * settings * add error if blink key not set --- .env.example | 8 +- cashu/core/settings.py | 2 +- cashu/lightning/__init__.py | 1 + cashu/lightning/blink.py | 343 +++++++++++++++++++++++++++++ cashu/lightning/lnbits.py | 2 +- cashu/mint/ledger.py | 7 +- poetry.lock | 16 +- pyproject.toml | 1 + tests/test_mint_lightning_blink.py | 137 ++++++++++++ 9 files changed, 510 insertions(+), 7 deletions(-) create mode 100644 cashu/lightning/blink.py create mode 100644 tests/test_mint_lightning_blink.py diff --git a/.env.example b/.env.example index 7cf46c0c..2a02071f 100644 --- a/.env.example +++ b/.env.example @@ -56,8 +56,8 @@ MINT_DERIVATION_PATH="m/0'/0'/0'" MINT_DATABASE=data/mint # Lightning -# Supported: LndRestWallet, LNbitsWallet, FakeWallet -MINT_LIGHTNING_BACKEND=LNbitsWallet +# Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, LNbitsWallet, BlinkWallet, StrikeWallet +MINT_LIGHTNING_BACKEND=FakeWallet # for use with LNbitsWallet MINT_LNBITS_ENDPOINT=https://legend.lnbits.com @@ -73,6 +73,10 @@ MINT_CORELIGHTNING_REST_URL=https://localhost:3001 MINT_CORELIGHTNING_REST_MACAROON="./clightning-rest/access.macaroon" MINT_CORELIGHTNING_REST_CERT="./clightning-2-rest/certificate.pem" +MINT_BLINK_KEY=blink_abcdefgh + +MINT_STRIKE_KEY=ABC123 + # fee to reserve in percent of the amount LIGHTNING_FEE_PERCENT=1.0 # minimum fee to reserve diff --git a/cashu/core/settings.py b/cashu/core/settings.py index a7d126e3..77bf6cb6 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -92,8 +92,8 @@ class MintSettings(CashuSettings): mint_lnbits_endpoint: str = Field(default=None) mint_lnbits_key: str = Field(default=None) - mint_strike_key: str = Field(default=None) + mint_blink_key: str = Field(default=None) class FakeWalletSettings(MintSettings): diff --git a/cashu/lightning/__init__.py b/cashu/lightning/__init__.py index 6d6cc3e0..89e46188 100644 --- a/cashu/lightning/__init__.py +++ b/cashu/lightning/__init__.py @@ -1,5 +1,6 @@ # type: ignore from ..core.settings import settings +from .blink import BlinkWallet # noqa: F401 from .corelightningrest import CoreLightningRestWallet # noqa: F401 from .fake import FakeWallet # noqa: F401 from .lnbits import LNbitsWallet # noqa: F401 diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py new file mode 100644 index 00000000..d4f11e80 --- /dev/null +++ b/cashu/lightning/blink.py @@ -0,0 +1,343 @@ +# type: ignore +import asyncio +import json +import math +from typing import Dict, Optional + +import bolt11 +import httpx +from bolt11 import ( + decode, +) +from loguru import logger + +from ..core.base import Amount, MeltQuote, Unit +from ..core.settings import settings +from .base import ( + InvoiceResponse, + LightningBackend, + PaymentQuoteResponse, + PaymentResponse, + PaymentStatus, + StatusResponse, +) + +# according to https://github.com/GaloyMoney/galoy/blob/7e79cc27304de9b9c2e7d7f4fdd3bac09df23aac/core/api/src/domain/bitcoin/index.ts#L59 +BLINK_MAX_FEE_PERCENT = 0.5 + + +class BlinkWallet(LightningBackend): + """https://dev.blink.sv/ + Create API Key at: https://dashboard.blink.sv/ + """ + + units = set([Unit.sat, Unit.usd]) + wallet_ids: Dict[Unit, str] = {} + endpoint = "https://api.blink.sv/graphql" + invoice_statuses = {"PENDING": None, "PAID": True, "EXPIRED": False} + payment_execution_statuses = {"SUCCESS": True, "ALREADY_PAID": None} + payment_statuses = {"SUCCESS": True, "PENDING": None, "FAILURE": False} + + def __init__(self): + assert settings.mint_blink_key, "MINT_BLINK_KEY not set" + self.client = httpx.AsyncClient( + verify=not settings.debug, + headers={ + "X-Api-Key": settings.mint_blink_key, + "Content-Type": "application/json", + }, + base_url=self.endpoint, + timeout=15, + ) + + async def status(self) -> StatusResponse: + try: + r = await self.client.post( + url=self.endpoint, + data=( + '{"query":"query me { me { defaultAccount { wallets { id' + ' walletCurrency balance }}}}", "variables":{}}' + ), + ) + r.raise_for_status() + except Exception as exc: + logger.error(f"Blink API error: {str(exc)}") + return StatusResponse( + error_message=f"Failed to connect to {self.endpoint} due to: {exc}", + balance=0, + ) + + try: + resp: dict = r.json() + except Exception: + return StatusResponse( + error_message=( + f"Received invalid response from {self.endpoint}: {r.text}" + ), + balance=0, + ) + + balance = 0 + for wallet_dict in resp["data"]["me"]["defaultAccount"]["wallets"]: + if wallet_dict["walletCurrency"] == "USD": + self.wallet_ids[Unit.usd] = wallet_dict["id"] + elif wallet_dict["walletCurrency"] == "BTC": + self.wallet_ids[Unit.sat] = wallet_dict["id"] + balance = wallet_dict["balance"] + + return StatusResponse(error_message=None, balance=balance) + + async def create_invoice( + self, + amount: Amount, + memo: Optional[str] = None, + description_hash: Optional[bytes] = None, + unhashed_description: Optional[bytes] = None, + ) -> InvoiceResponse: + self.assert_unit_supported(amount.unit) + + variables = { + "input": { + "amount": str(amount.to(Unit.sat).amount), + "recipientWalletId": self.wallet_ids[Unit.sat], + } + } + if description_hash: + variables["input"]["descriptionHash"] = description_hash.hex() + if memo: + variables["input"]["memo"] = memo + + data = { + "query": """ + mutation LnInvoiceCreateOnBehalfOfRecipient($input: LnInvoiceCreateOnBehalfOfRecipientInput!) { + lnInvoiceCreateOnBehalfOfRecipient(input: $input) { + invoice { + paymentRequest + paymentHash + paymentSecret + satoshis + } + errors { + message path code + } + } + } + """, + "variables": variables, + } + try: + r = await self.client.post( + url=self.endpoint, + data=json.dumps(data), + ) + r.raise_for_status() + except Exception as e: + logger.error(f"Blink API error: {str(e)}") + return InvoiceResponse(ok=False, error_message=str(e)) + + resp = r.json() + assert resp, "invalid response" + payment_request = resp["data"]["lnInvoiceCreateOnBehalfOfRecipient"]["invoice"][ + "paymentRequest" + ] + checking_id = payment_request + + return InvoiceResponse( + ok=True, + checking_id=checking_id, + payment_request=payment_request, + ) + + async def pay_invoice( + self, quote: MeltQuote, fee_limit_msat: int + ) -> PaymentResponse: + variables = { + "input": { + "paymentRequest": quote.request, + "walletId": self.wallet_ids[Unit.sat], + } + } + data = { + "query": """ + mutation lnInvoicePaymentSend($input: LnInvoicePaymentInput!) { + lnInvoicePaymentSend(input: $input) { + errors { + message path code + } + status + transaction { + settlementAmount settlementFee status + } + } + } + """, + "variables": variables, + } + + try: + r = await self.client.post( + url=self.endpoint, + data=json.dumps(data), + ) + r.raise_for_status() + except Exception as e: + logger.error(f"Blink API error: {str(e)}") + return PaymentResponse(ok=False, error_message=str(e)) + + resp: dict = r.json() + paid = self.payment_execution_statuses[ + resp["data"]["lnInvoicePaymentSend"]["status"] + ] + fee = resp["data"]["lnInvoicePaymentSend"]["transaction"]["settlementFee"] + checking_id = quote.request + + return PaymentResponse( + ok=paid, + checking_id=checking_id, + fee=Amount(Unit.sat, fee), + preimage=None, + error_message="Invoice already paid." if paid is None else None, + ) + + async def get_invoice_status(self, checking_id: str) -> PaymentStatus: + variables = {"input": {"paymentRequest": checking_id}} + data = { + "query": """ + query lnInvoicePaymentStatus($input: LnInvoicePaymentStatusInput!) { + lnInvoicePaymentStatus(input: $input) { + errors { + message path code + } + status + } + } + """, + "variables": variables, + } + try: + r = await self.client.post(url=self.endpoint, data=json.dumps(data)) + r.raise_for_status() + except Exception as e: + logger.error(f"Blink API error: {str(e)}") + return PaymentStatus(paid=None) + resp: dict = r.json() + if resp["data"]["lnInvoicePaymentStatus"]["errors"]: + logger.error( + "Blink Error", resp["data"]["lnInvoicePaymentStatus"]["errors"] + ) + return PaymentStatus(paid=None) + paid = self.invoice_statuses[resp["data"]["lnInvoicePaymentStatus"]["status"]] + return PaymentStatus(paid=paid) + + async def get_payment_status(self, checking_id: str) -> PaymentStatus: + # Checking ID is the payment request and blink wants the payment hash + payment_hash = bolt11.decode(checking_id).payment_hash + variables = { + "paymentHash": payment_hash, + "walletId": self.wallet_ids[Unit.sat], + } + data = { + "query": """ + query TransactionsByPaymentHash($paymentHash: PaymentHash!, $walletId: WalletId!) { + me { + defaultAccount { + walletById(walletId: $walletId) { + transactionsByPaymentHash(paymentHash: $paymentHash) { + status + direction + settlementFee + } + } + } + } + } + """, + "variables": variables, + } + + try: + r = await self.client.post( + url=self.endpoint, + data=json.dumps(data), + ) + r.raise_for_status() + except Exception as e: + logger.error(f"Blink API error: {str(e)}") + return PaymentResponse(ok=False, error_message=str(e)) + + resp: dict = r.json() + # no result found + if not resp["data"]["me"]["defaultAccount"]["walletById"][ + "transactionsByPaymentHash" + ]: + return PaymentStatus(paid=None) + + paid = self.payment_statuses[ + resp["data"]["me"]["defaultAccount"]["walletById"][ + "transactionsByPaymentHash" + ][0]["status"] + ] + fee = resp["data"]["me"]["defaultAccount"]["walletById"][ + "transactionsByPaymentHash" + ][0]["settlementFee"] + + return PaymentStatus( + paid=paid, + fee=Amount(Unit.sat, fee), + preimage=None, + ) + + async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: + variables = { + "input": { + "paymentRequest": bolt11, + "walletId": self.wallet_ids[Unit.sat], + } + } + data = { + "query": """ + mutation lnInvoiceFeeProbe($input: LnInvoiceFeeProbeInput!) { + lnInvoiceFeeProbe(input: $input) { + amount + errors { + message path code + } + } + } + """, + "variables": variables, + } + + try: + r = await self.client.post( + url=self.endpoint, + data=json.dumps(data), + ) + r.raise_for_status() + except Exception as e: + logger.error(f"Blink API error: {str(e)}") + return PaymentResponse(ok=False, error_message=str(e)) + resp: dict = r.json() + + invoice_obj = decode(bolt11) + assert invoice_obj.amount_msat, "invoice has no amount." + + amount_msat = int(invoice_obj.amount_msat) + + fees_response_msat = int(resp["data"]["lnInvoiceFeeProbe"]["amount"]) * 1000 + # we either take fee_msat_response or the BLINK_MAX_FEE_PERCENT, whichever is higher + fees_msat = max( + fees_response_msat, math.ceil(amount_msat * BLINK_MAX_FEE_PERCENT) + ) + + fees = Amount(unit=Unit.msat, amount=fees_msat) + amount = Amount(unit=Unit.msat, amount=amount_msat) + return PaymentQuoteResponse(checking_id=bolt11, fee=fees, amount=amount) + + +async def main(): + pass + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/cashu/lightning/lnbits.py b/cashu/lightning/lnbits.py index f47558b8..3896a4e1 100644 --- a/cashu/lightning/lnbits.py +++ b/cashu/lightning/lnbits.py @@ -46,7 +46,7 @@ async def status(self) -> StatusResponse: except Exception: return StatusResponse( error_message=( - f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'" + f"Received invalid response from {self.endpoint}: {r.text}" ), balance=0, ) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 23502830..1a973643 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -640,7 +640,7 @@ async def melt( Returns: Tuple[str, List[BlindedMessage]]: Proof of payment and signed outputs for returning overpaid fees to wallet. """ - # get melt quote and settle transaction internally if possible + # get melt quote and check if it is paid melt_quote = await self.get_melt_quote(quote_id=quote) method = Method[melt_quote.method] unit = Unit[melt_quote.unit] @@ -676,6 +676,7 @@ async def melt( # set proofs to pending to avoid race conditions await self._set_proofs_pending(proofs) try: + # settle the transaction internally if there is a mint quote with the same payment request melt_quote = await self.melt_mint_settle_internally(melt_quote) # quote not paid yet (not internal), pay it with the backend @@ -689,7 +690,9 @@ async def melt( f" fee: {payment.fee.str() if payment.fee else 0}" ) if not payment.ok: - raise LightningError("Lightning payment unsuccessful.") + raise LightningError( + f"Lightning payment unsuccessful. {payment.error_message}" + ) if payment.fee: melt_quote.fee_paid = payment.fee.to( to_unit=unit, round="up" diff --git a/poetry.lock b/poetry.lock index ace9c370..a901cc7e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1268,6 +1268,20 @@ six = ">=1.8.0" [package.extras] test = ["ipython", "mock", "pytest (>=3.0.5)"] +[[package]] +name = "respx" +version = "0.20.2" +description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." +optional = false +python-versions = ">=3.7" +files = [ + {file = "respx-0.20.2-py2.py3-none-any.whl", hash = "sha256:ab8e1cf6da28a5b2dd883ea617f8130f77f676736e6e9e4a25817ad116a172c9"}, + {file = "respx-0.20.2.tar.gz", hash = "sha256:07cf4108b1c88b82010f67d3c831dae33a375c7b436e54d87737c7f9f99be643"}, +] + +[package.dependencies] +httpx = ">=0.21.0" + [[package]] name = "ruff" version = "0.0.284" @@ -1598,4 +1612,4 @@ pgsql = ["psycopg2-binary"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "f7aa2919aca77aa4d1dfcba18c6fc9694a2cc1d5cfd60e7ec991a615251fa86e" +content-hash = "7ad5150f23bb8ba229e43b3d329b4ec747791622f5e83357a5fae8aee9315fdc" diff --git a/pyproject.toml b/pyproject.toml index b501ab75..4f689213 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ pytest = "^7.4.0" ruff = "^0.0.284" pre-commit = "^3.3.3" fastapi-profiler = "^1.2.0" +respx = "^0.20.2" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/test_mint_lightning_blink.py b/tests/test_mint_lightning_blink.py new file mode 100644 index 00000000..7339fc2b --- /dev/null +++ b/tests/test_mint_lightning_blink.py @@ -0,0 +1,137 @@ +import pytest +import respx +from httpx import Response + +from cashu.core.base import Amount, MeltQuote, Unit +from cashu.core.settings import settings +from cashu.lightning.blink import BlinkWallet + +settings.mint_blink_key = "123" +blink = BlinkWallet() +payment_request = ( + "lnbc10u1pjap7phpp50s9lzr3477j0tvacpfy2ucrs4q0q6cvn232ex7nt2zqxxxj8gxrsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzzsxqrrsss" + "p575z0n39w2j7zgnpqtdlrgz9rycner4eptjm3lz363dzylnrm3h4s9qyyssqfz8jglcshnlcf0zkw4qu8fyr564lg59x5al724kms3h6gpuhx9xrfv27tgx3l3u3cyf6" + "3r52u0xmac6max8mdupghfzh84t4hfsvrfsqwnuszf" +) + + +@respx.mock +@pytest.mark.asyncio +async def test_blink_status(): + mock_response = { + "data": { + "me": { + "defaultAccount": { + "wallets": [ + {"walletCurrency": "USD", "id": "123", "balance": 32142}, + { + "walletCurrency": "BTC", + "id": "456", + "balance": 100000, + }, + ] + } + } + } + } + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + status = await blink.status() + assert status.balance == 100000 + + +@respx.mock +@pytest.mark.asyncio +async def test_blink_create_invoice(): + mock_response = { + "data": { + "lnInvoiceCreateOnBehalfOfRecipient": { + "invoice": {"paymentRequest": payment_request} + } + } + } + + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + invoice = await blink.create_invoice(Amount(Unit.sat, 1000)) + assert invoice.checking_id == invoice.payment_request + assert invoice.ok + + +@respx.mock +@pytest.mark.asyncio +async def test_blink_pay_invoice(): + mock_response = { + "data": { + "lnInvoicePaymentSend": { + "status": "SUCCESS", + "transaction": {"settlementFee": 10}, + } + } + } + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + quote = MeltQuote( + request=payment_request, + quote="asd", + method="bolt11", + checking_id=payment_request, + unit="sat", + amount=100, + fee_reserve=12, + paid=False, + ) + payment = await blink.pay_invoice(quote, 1000) + assert payment.ok + assert payment.fee + assert payment.fee.amount == 10 + assert payment.error_message is None + assert payment.checking_id == payment_request + + +@respx.mock +@pytest.mark.asyncio +async def test_blink_get_invoice_status(): + mock_response = { + "data": { + "lnInvoicePaymentStatus": { + "status": "PAID", + "errors": [], + } + } + } + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + status = await blink.get_invoice_status("123") + assert status.paid + + +@respx.mock +@pytest.mark.asyncio +async def test_blink_get_payment_status(): + mock_response = { + "data": { + "me": { + "defaultAccount": { + "walletById": { + "transactionsByPaymentHash": [ + {"status": "SUCCESS", "settlementFee": 10} + ] + } + } + } + } + } + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + status = await blink.get_payment_status(payment_request) + assert status.paid + assert status.fee + assert status.fee.amount == 10 + assert status.preimage is None + + +@respx.mock +@pytest.mark.asyncio +async def test_blink_get_payment_quote(): + mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 10}}} + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + quote = await blink.get_payment_quote(payment_request) + assert quote.checking_id == payment_request + assert quote.amount == Amount(Unit.msat, 1000000) # msat + assert quote.fee == Amount(Unit.msat, 500000) # msat From fca2a6cb4b5a2ef0e89d366db3a3fae72ece2136 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 17 Feb 2024 21:22:07 +0100 Subject: [PATCH 33/57] Dev: Update ruff precommit hooks (#434) * update ruff * test * update ruff * only ruff check * CI rename --- .github/workflows/checks.yml | 6 +- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 12 +- Makefile | 10 +- cashu/core/crypto/b_dhke.py | 17 +- cashu/lightning/blink.py | 46 +-- poetry.lock | 684 ++++++++++++++++------------------- pyproject.toml | 16 +- tests/test_crypto.py | 173 +++------ 9 files changed, 410 insertions(+), 556 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index d044b185..7bb953d4 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -11,7 +11,7 @@ on: type: string jobs: - formatting: + format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -26,8 +26,8 @@ jobs: cache: "poetry" - name: Install packages run: poetry install - - name: Check black - run: make black-check + - name: Ruff check + run: make ruff-check mypy: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82296e13..2c50f6b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Nutshell CI +name: CI on: push: branches: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cad022a9..8a7d36b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: "^cashu/nostr/.*" repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -12,16 +12,12 @@ repos: - id: debug-statements - id: mixed-line-ending - id: check-case-conflict - # - repo: https://github.com/psf/black - # rev: 23.11.0 - # hooks: - # - id: black - # args: [--line-length=150] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.283 + rev: v0.2.1 hooks: - id: ruff - args: [--fix, --exit-non-zero-on-fix] + args: [--fix] + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.6.0 hooks: diff --git a/Makefile b/Makefile index b9e79292..f6cb065b 100644 --- a/Makefile +++ b/Makefile @@ -4,18 +4,12 @@ ruff: ruff-check: poetry run ruff check . -black: - poetry run black . - -black-check: - poetry run black . --check - mypy: poetry run mypy cashu --check-untyped-defs -format: black ruff +format: ruff -check: black-check ruff-check mypy +check: ruff-check mypy clean: rm -r cashu.egg-info/ || true diff --git a/cashu/core/crypto/b_dhke.py b/cashu/core/crypto/b_dhke.py index 4abf1b77..17634a0f 100644 --- a/cashu/core/crypto/b_dhke.py +++ b/cashu/core/crypto/b_dhke.py @@ -68,6 +68,7 @@ def hash_to_curve(message: bytes) -> PublicKey: point = PublicKey(b"\x02" + _hash, raw=True) except Exception: msg_to_hash = _hash + print(_hash) return point @@ -101,18 +102,14 @@ def hash_to_curve_domain_separated(message: bytes) -> PublicKey: raise ValueError("No valid point found") -def step1_alice( - secret_msg: str, blinding_factor: Optional[PrivateKey] = None -) -> tuple[PublicKey, PrivateKey]: +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 step1_alice_domain_separated( - secret_msg: str, blinding_factor: Optional[PrivateKey] = None -) -> tuple[PublicKey, PrivateKey]: +def step1_alice_domain_separated(secret_msg: str, blinding_factor: Optional[PrivateKey] = None) -> tuple[PublicKey, PrivateKey]: Y: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) r = blinding_factor or PrivateKey() B_: PublicKey = Y + r.pubkey # type: ignore @@ -151,9 +148,7 @@ def hash_e(*publickeys: PublicKey) -> bytes: return e -def step2_bob_dleq( - B_: PublicKey, a: PrivateKey, p_bytes: bytes = b"" -) -> Tuple[PrivateKey, PrivateKey]: +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) @@ -174,9 +169,7 @@ def step2_bob_dleq( return epk, spk -def alice_verify_dleq( - B_: PublicKey, C_: PublicKey, e: PrivateKey, s: PrivateKey, A: PublicKey -) -> bool: +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 diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index d4f11e80..3f6a618c 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -27,7 +27,8 @@ class BlinkWallet(LightningBackend): - """https://dev.blink.sv/ + """ + https://dev.blink.sv/ Create API Key at: https://dashboard.blink.sv/ """ @@ -54,10 +55,7 @@ async def status(self) -> StatusResponse: try: r = await self.client.post( url=self.endpoint, - data=( - '{"query":"query me { me { defaultAccount { wallets { id' - ' walletCurrency balance }}}}", "variables":{}}' - ), + data=('{"query":"query me { me { defaultAccount { wallets { id' ' walletCurrency balance }}}}", "variables":{}}'), ) r.raise_for_status() except Exception as exc: @@ -71,9 +69,7 @@ async def status(self) -> StatusResponse: resp: dict = r.json() except Exception: return StatusResponse( - error_message=( - f"Received invalid response from {self.endpoint}: {r.text}" - ), + error_message=(f"Received invalid response from {self.endpoint}: {r.text}"), balance=0, ) @@ -137,9 +133,7 @@ async def create_invoice( resp = r.json() assert resp, "invalid response" - payment_request = resp["data"]["lnInvoiceCreateOnBehalfOfRecipient"]["invoice"][ - "paymentRequest" - ] + payment_request = resp["data"]["lnInvoiceCreateOnBehalfOfRecipient"]["invoice"]["paymentRequest"] checking_id = payment_request return InvoiceResponse( @@ -148,9 +142,7 @@ async def create_invoice( payment_request=payment_request, ) - async def pay_invoice( - self, quote: MeltQuote, fee_limit_msat: int - ) -> PaymentResponse: + async def pay_invoice(self, quote: MeltQuote, fee_limit_msat: int) -> PaymentResponse: variables = { "input": { "paymentRequest": quote.request, @@ -185,9 +177,7 @@ async def pay_invoice( return PaymentResponse(ok=False, error_message=str(e)) resp: dict = r.json() - paid = self.payment_execution_statuses[ - resp["data"]["lnInvoicePaymentSend"]["status"] - ] + paid = self.payment_execution_statuses[resp["data"]["lnInvoicePaymentSend"]["status"]] fee = resp["data"]["lnInvoicePaymentSend"]["transaction"]["settlementFee"] checking_id = quote.request @@ -222,9 +212,7 @@ async def get_invoice_status(self, checking_id: str) -> PaymentStatus: return PaymentStatus(paid=None) resp: dict = r.json() if resp["data"]["lnInvoicePaymentStatus"]["errors"]: - logger.error( - "Blink Error", resp["data"]["lnInvoicePaymentStatus"]["errors"] - ) + logger.error("Blink Error", resp["data"]["lnInvoicePaymentStatus"]["errors"]) return PaymentStatus(paid=None) paid = self.invoice_statuses[resp["data"]["lnInvoicePaymentStatus"]["status"]] return PaymentStatus(paid=paid) @@ -267,19 +255,11 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus: resp: dict = r.json() # no result found - if not resp["data"]["me"]["defaultAccount"]["walletById"][ - "transactionsByPaymentHash" - ]: + if not resp["data"]["me"]["defaultAccount"]["walletById"]["transactionsByPaymentHash"]: return PaymentStatus(paid=None) - paid = self.payment_statuses[ - resp["data"]["me"]["defaultAccount"]["walletById"][ - "transactionsByPaymentHash" - ][0]["status"] - ] - fee = resp["data"]["me"]["defaultAccount"]["walletById"][ - "transactionsByPaymentHash" - ][0]["settlementFee"] + paid = self.payment_statuses[resp["data"]["me"]["defaultAccount"]["walletById"]["transactionsByPaymentHash"][0]["status"]] + fee = resp["data"]["me"]["defaultAccount"]["walletById"]["transactionsByPaymentHash"][0]["settlementFee"] return PaymentStatus( paid=paid, @@ -326,9 +306,7 @@ async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: fees_response_msat = int(resp["data"]["lnInvoiceFeeProbe"]["amount"]) * 1000 # we either take fee_msat_response or the BLINK_MAX_FEE_PERCENT, whichever is higher - fees_msat = max( - fees_response_msat, math.ceil(amount_msat * BLINK_MAX_FEE_PERCENT) - ) + fees_msat = max(fees_response_msat, math.ceil(amount_msat * BLINK_MAX_FEE_PERCENT)) fees = Amount(unit=Unit.msat, amount=fees_msat) amount = Amount(unit=Unit.msat, amount=amount_msat) diff --git a/poetry.lock b/poetry.lock index a901cc7e..ee07069b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -34,21 +34,22 @@ files = [ [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "base58" @@ -102,48 +103,6 @@ files = [ {file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"}, ] -[[package]] -name = "black" -version = "23.11.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, - {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, - {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, - {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, - {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, - {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, - {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, - {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, - {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, - {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, - {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, - {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, - {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, - {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, - {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, - {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, - {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, - {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "bolt11" version = "2.0.5" @@ -165,13 +124,13 @@ secp256k1 = "*" [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -331,63 +290,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.2" +version = "7.4.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, + {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, + {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, + {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, + {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, + {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, + {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, + {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, + {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, + {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, + {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, + {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, + {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, ] [package.dependencies] @@ -398,34 +357,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.5" +version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, - {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, - {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, - {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [package.dependencies] @@ -443,13 +402,13 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "distlib" -version = "0.3.7" +version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[package]] @@ -568,13 +527,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.2" +version = "1.0.3" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, + {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, + {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, ] [package.dependencies] @@ -585,7 +544,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] +trio = ["trio (>=0.22.0,<0.24.0)"] [[package]] name = "httpx" @@ -614,13 +573,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "identify" -version = "2.5.32" +version = "2.5.34" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.32-py2.py3-none-any.whl", hash = "sha256:0b7656ef6cba81664b783352c73f8c24b39cf82f926f78f4550eda928e5e0545"}, - {file = "identify-2.5.32.tar.gz", hash = "sha256:5d9979348ec1a21c768ae07e0a652924538e8bce67313a73cb0f681cf08ba407"}, + {file = "identify-2.5.34-py2.py3-none-any.whl", hash = "sha256:a4316013779e433d08b96e5eabb7f641e6c7942e4ab5d4c509ebd2e7a8994aed"}, + {file = "identify-2.5.34.tar.gz", hash = "sha256:ee17bc9d499899bc9eaec1ac7bf2dc9eedd480db9d88b96d123d3b64a9d34f5d"}, ] [package.extras] @@ -639,20 +598,20 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.8.0" +version = "6.11.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {file = "importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b"}, + {file = "importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] @@ -687,22 +646,22 @@ dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptio [[package]] name = "marshmallow" -version = "3.20.1" +version = "3.20.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, - {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, + {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, + {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] -dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] +dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["pre-commit (>=2.4,<4.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -718,38 +677,38 @@ files = [ [[package]] name = "mypy" -version = "1.7.1" +version = "1.8.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, - {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, - {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, - {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, - {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, - {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, - {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, - {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, - {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, - {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, - {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, - {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, - {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, - {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, - {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, - {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, - {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, - {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, - {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, + {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, + {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, + {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, + {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, + {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, + {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, ] [package.dependencies] @@ -813,41 +772,30 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] -[[package]] -name = "pathspec" -version = "0.11.2" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] - [[package]] name = "platformdirs" -version = "4.0.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, - {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -963,88 +911,88 @@ files = [ [[package]] name = "pycryptodomex" -version = "3.19.0" +version = "3.20.0" description = "Cryptographic library for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodomex-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff64fd720def623bf64d8776f8d0deada1cc1bf1ec3c1f9d6f5bb5bd098d034f"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:61056a1fd3254f6f863de94c233b30dd33bc02f8c935b2000269705f1eeeffa4"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:258c4233a3fe5a6341780306a36c6fb072ef38ce676a6d41eec3e591347919e8"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e45bb4635b3c4e0a00ca9df75ef6295838c85c2ac44ad882410cb631ed1eeaa"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a12144d785518f6491ad334c75ccdc6ad52ea49230b4237f319dbb7cef26f464"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:1789d89f61f70a4cd5483d4dfa8df7032efab1118f8b9894faae03c967707865"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:eb2fc0ec241bf5e5ef56c8fbec4a2634d631e4c4f616a59b567947a0f35ad83c"}, - {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c9a68a2f7bd091ccea54ad3be3e9d65eded813e6d79fdf4cc3604e26cdd6384f"}, - {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8df69e41f7e7015a90b94d1096ec3d8e0182e73449487306709ec27379fff761"}, - {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:917033016ecc23c8933205585a0ab73e20020fdf671b7cd1be788a5c4039840b"}, - {file = "pycryptodomex-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:e8e5ecbd4da4157889fce8ba49da74764dd86c891410bfd6b24969fa46edda51"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:a77b79852175064c822b047fee7cf5a1f434f06ad075cc9986aa1c19a0c53eb0"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5b883e1439ab63af976656446fb4839d566bb096f15fc3c06b5a99cde4927188"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3866d68e2fc345162b1b9b83ef80686acfe5cec0d134337f3b03950a0a8bf56"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74eb1f73f788facece7979ce91594dc177e1a9b5d5e3e64697dd58299e5cb4d"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cb51096a6a8d400724104db8a7e4f2206041a1f23e58924aa3d8d96bcb48338"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a588a1cb7781da9d5e1c84affd98c32aff9c89771eac8eaa659d2760666f7139"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:d4dd3b381ff5a5907a3eb98f5f6d32c64d319a840278ceea1dcfcc65063856f3"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:263de9a96d2fcbc9f5bd3a279f14ea0d5f072adb68ebd324987576ec25da084d"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-win32.whl", hash = "sha256:67c8eb79ab33d0fbcb56842992298ddb56eb6505a72369c20f60bc1d2b6fb002"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:09c9401dc06fb3d94cb1ec23b4ea067a25d1f4c6b7b118ff5631d0b5daaab3cc"}, - {file = "pycryptodomex-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:edbe083c299835de7e02c8aa0885cb904a75087d35e7bab75ebe5ed336e8c3e2"}, - {file = "pycryptodomex-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:136b284e9246b4ccf4f752d435c80f2c44fc2321c198505de1d43a95a3453b3c"}, - {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5d73e9fa3fe830e7b6b42afc49d8329b07a049a47d12e0ef9225f2fd220f19b2"}, - {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b2f1982c5bc311f0aab8c293524b861b485d76f7c9ab2c3ac9a25b6f7655975"}, - {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb040b5dda1dff1e197d2ef71927bd6b8bfcb9793bc4dfe0bb6df1e691eaacb"}, - {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:800a2b05cfb83654df80266692f7092eeefe2a314fa7901dcefab255934faeec"}, - {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c01678aee8ac0c1a461cbc38ad496f953f9efcb1fa19f5637cbeba7544792a53"}, - {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2126bc54beccbede6eade00e647106b4f4c21e5201d2b0a73e9e816a01c50905"}, - {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b801216c48c0886742abf286a9a6b117e248ca144d8ceec1f931ce2dd0c9cb40"}, - {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50cb18d4dd87571006fd2447ccec85e6cec0136632a550aa29226ba075c80644"}, - {file = "pycryptodomex-3.19.0.tar.gz", hash = "sha256:af83a554b3f077564229865c45af0791be008ac6469ef0098152139e6bd4b5b6"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, + {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, + {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, + {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, ] [[package]] name = "pydantic" -version = "1.10.13" +version = "1.10.14" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, - {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, - {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, - {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, - {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, - {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, - {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, - {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, - {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, - {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, + {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, + {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, + {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, + {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, + {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, + {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, + {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, + {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, ] [package.dependencies] @@ -1056,71 +1004,71 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pyinstrument" -version = "4.6.1" +version = "4.6.2" description = "Call stack profiler for Python. Shows you why your code is slow!" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstrument-4.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:73476e4bc6e467ac1b2c3c0dd1f0b71c9061d4de14626676adfdfbb14aa342b4"}, - {file = "pyinstrument-4.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4d1da8efd974cf9df52ee03edaee2d3875105ddd00de35aa542760f7c612bdf7"}, - {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:507be1ee2f2b0c9fba74d622a272640dd6d1b0c9ec3388b2cdeb97ad1e77125f"}, - {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cee6de08eb45754ef4f602ce52b640d1c535d934a6a8733a974daa095def37"}, - {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7873e8cec92321251fdf894a72b3c78f4c5c20afdd1fef0baf9042ec843bb04"}, - {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a242f6cac40bc83e1f3002b6b53681846dfba007f366971db0bf21e02dbb1903"}, - {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:97c9660cdb4bd2a43cf4f3ab52cffd22f3ac9a748d913b750178fb34e5e39e64"}, - {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e304cd0723e2b18ada5e63c187abf6d777949454c734f5974d64a0865859f0f4"}, - {file = "pyinstrument-4.6.1-cp310-cp310-win32.whl", hash = "sha256:cee21a2d78187dd8a80f72f5d0f1ddb767b2d9800f8bb4d94b6d11f217c22cdb"}, - {file = "pyinstrument-4.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:2000712f71d693fed2f8a1c1638d37b7919124f367b37976d07128d49f1445eb"}, - {file = "pyinstrument-4.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a366c6f3dfb11f1739bdc1dee75a01c1563ad0bf4047071e5e77598087df457f"}, - {file = "pyinstrument-4.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6be327be65d934796558aa9cb0f75ce62ebd207d49ad1854610c97b0579ad47"}, - {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e160d9c5d20d3e4ef82269e4e8b246ff09bdf37af5fb8cb8ccca97936d95ad6"}, - {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ffbf56605ef21c2fcb60de2fa74ff81f417d8be0c5002a407e414d6ef6dee43"}, - {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c92cc4924596d6e8f30a16182bbe90893b1572d847ae12652f72b34a9a17c24a"}, - {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f4b48a94d938cae981f6948d9ec603bab2087b178d2095d042d5a48aabaecaab"}, - {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7a386392275bdef4a1849712dc5b74f0023483fca14ef93d0ca27d453548982"}, - {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:871b131b83e9b1122f2325061c68ed1e861eebcb568c934d2fb193652f077f77"}, - {file = "pyinstrument-4.6.1-cp311-cp311-win32.whl", hash = "sha256:8d8515156dd91f5652d13b5fcc87e634f8fe1c07b68d1d0840348cdd50bf5ace"}, - {file = "pyinstrument-4.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb868fbe089036e9f32525a249f4c78b8dc46967612393f204b8234f439c9cc4"}, - {file = "pyinstrument-4.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a18cd234cce4f230f1733807f17a134e64a1f1acabf74a14d27f583cf2b183df"}, - {file = "pyinstrument-4.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:574cfca69150be4ce4461fb224712fbc0722a49b0dc02fa204d02807adf6b5a0"}, - {file = "pyinstrument-4.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e02cf505e932eb8ccf561b7527550a67ec14fcae1fe0e25319b09c9c166e914"}, - {file = "pyinstrument-4.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832fb2acef9d53701c1ab546564c45fb70a8770c816374f8dd11420d399103c9"}, - {file = "pyinstrument-4.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13cb57e9607545623ebe462345b3d0c4caee0125d2d02267043ece8aca8f4ea0"}, - {file = "pyinstrument-4.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9be89e7419bcfe8dd6abb0d959d6d9c439c613a4a873514c43d16b48dae697c9"}, - {file = "pyinstrument-4.6.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:476785cfbc44e8e1b1ad447398aa3deae81a8df4d37eb2d8bbb0c404eff979cd"}, - {file = "pyinstrument-4.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e9cebd90128a3d2fee36d3ccb665c1b9dce75261061b2046203e45c4a8012d54"}, - {file = "pyinstrument-4.6.1-cp312-cp312-win32.whl", hash = "sha256:1d0b76683df2ad5c40eff73607dc5c13828c92fbca36aff1ddf869a3c5a55fa6"}, - {file = "pyinstrument-4.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:c4b7af1d9d6a523cfbfedebcb69202242d5bd0cb89c4e094cc73d5d6e38279bd"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:79ae152f8c6a680a188fb3be5e0f360ac05db5bbf410169a6c40851dfaebcce9"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07cad2745964c174c65aa75f1bf68a4394d1b4d28f33894837cfd315d1e836f0"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb81f66f7f94045d723069cf317453d42375de9ff3c69089cf6466b078ac1db4"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab30ae75969da99e9a529e21ff497c18fdf958e822753db4ae7ed1e67094040"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f36cb5b644762fb3c86289324bbef17e95f91cd710603ac19444a47f638e8e96"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8b45075d9dbbc977dbc7007fb22bb0054c6990fbe91bf48dd80c0b96c6307ba7"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:475ac31477f6302e092463896d6a2055f3e6abcd293bad16ff94fc9185308a88"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-win32.whl", hash = "sha256:29172ab3d8609fdf821c3f2562dc61e14f1a8ff5306607c32ca743582d3a760e"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:bd176f297c99035127b264369d2bb97a65255f65f8d4e843836baf55ebb3cee4"}, - {file = "pyinstrument-4.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:23e9b4526978432e9999021da9a545992cf2ac3df5ee82db7beb6908fc4c978c"}, - {file = "pyinstrument-4.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2dbcaccc9f456ef95557ec501caeb292119c24446d768cb4fb43578b0f3d572c"}, - {file = "pyinstrument-4.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2097f63c66c2bc9678c826b9ff0c25acde3ed455590d9dcac21220673fe74fbf"}, - {file = "pyinstrument-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:205ac2e76bd65d61b9611a9ce03d5f6393e34ec5b41dd38808f25d54e6b3e067"}, - {file = "pyinstrument-4.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f414ddf1161976a40fc0a333000e6a4ad612719eac0b8c9bb73f47153187148"}, - {file = "pyinstrument-4.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:65e62ebfa2cd8fb57eda90006f4505ac4c70da00fc2f05b6d8337d776ea76d41"}, - {file = "pyinstrument-4.6.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d96309df4df10be7b4885797c5f69bb3a89414680ebaec0722d8156fde5268c3"}, - {file = "pyinstrument-4.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f3d1ad3bc8ebb4db925afa706aa865c4bfb40d52509f143491ac0df2440ee5d2"}, - {file = "pyinstrument-4.6.1-cp38-cp38-win32.whl", hash = "sha256:dc37cb988c8854eb42bda2e438aaf553536566657d157c4473cc8aad5692a779"}, - {file = "pyinstrument-4.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:2cd4ce750c34a0318fc2d6c727cc255e9658d12a5cf3f2d0473f1c27157bdaeb"}, - {file = "pyinstrument-4.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ca95b21f022e995e062b371d1f42d901452bcbedd2c02f036de677119503355"}, - {file = "pyinstrument-4.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ac1e1d7e1f1b64054c4eb04eb4869a7a5eef2261440e73943cc1b1bc3c828c18"}, - {file = "pyinstrument-4.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0711845e953fce6ab781221aacffa2a66dbc3289f8343e5babd7b2ea34da6c90"}, - {file = "pyinstrument-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7d28582017de35cb64eb4e4fa603e753095108ca03745f5d17295970ee631f"}, - {file = "pyinstrument-4.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7be57db08bd366a37db3aa3a6187941ee21196e8b14975db337ddc7d1490649d"}, - {file = "pyinstrument-4.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9a0ac0f56860398d2628ce389826ce83fb3a557d0c9a2351e8a2eac6eb869983"}, - {file = "pyinstrument-4.6.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a9045186ff13bc826fef16be53736a85029aae3c6adfe52e666cad00d7ca623b"}, - {file = "pyinstrument-4.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6c4c56b6eab9004e92ad8a48bb54913fdd71fc8a748ae42a27b9e26041646f8b"}, - {file = "pyinstrument-4.6.1-cp39-cp39-win32.whl", hash = "sha256:37e989c44b51839d0c97466fa2b623638b9470d56d79e329f359f0e8fa6d83db"}, - {file = "pyinstrument-4.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:5494c5a84fee4309d7d973366ca6b8b9f8ba1d6b254e93b7c506264ef74f2cef"}, - {file = "pyinstrument-4.6.1.tar.gz", hash = "sha256:f4731b27121350f5a983d358d2272fe3df2f538aed058f57217eef7801a89288"}, + {file = "pyinstrument-4.6.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a1b1cd768ea7ea9ab6f5490f7e74431321bcc463e9441dbc2f769617252d9e2"}, + {file = "pyinstrument-4.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8a386b9d09d167451fb2111eaf86aabf6e094fed42c15f62ec51d6980bce7d96"}, + {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23c3e3ca8553b9aac09bd978c73d21b9032c707ac6d803bae6a20ecc048df4a8"}, + {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f329f5534ca069420246f5ce57270d975229bcb92a3a3fd6b2ca086527d9764"}, + {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4dcdcc7ba224a0c5edfbd00b0f530f5aed2b26da5aaa2f9af5519d4aa8c7e41"}, + {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73db0c2c99119c65b075feee76e903b4ed82e59440fe8b5724acf5c7cb24721f"}, + {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:da58f265326f3cf3975366ccb8b39014f1e69ff8327958a089858d71c633d654"}, + {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:feebcf860f955401df30d029ec8de7a0c5515d24ea809736430fd1219686fe14"}, + {file = "pyinstrument-4.6.2-cp310-cp310-win32.whl", hash = "sha256:b2b66ff0b16c8ecf1ec22de001cfff46872b2c163c62429055105564eef50b2e"}, + {file = "pyinstrument-4.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:8d104b7a7899d5fa4c5bf1ceb0c1a070615a72c5dc17bc321b612467ad5c5d88"}, + {file = "pyinstrument-4.6.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:62f6014d2b928b181a52483e7c7b82f2c27e22c577417d1681153e5518f03317"}, + {file = "pyinstrument-4.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dcb5c8d763c5df55131670ba2a01a8aebd0d490a789904a55eb6a8b8d497f110"}, + {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ed4e8c6c84e0e6429ba7008a66e435ede2d8cb027794c20923c55669d9c5633"}, + {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c0f0e1d8f8c70faa90ff57f78ac0dda774b52ea0bfb2d9f0f41ce6f3e7c869e"}, + {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3c44cb037ad0d6e9d9a48c14d856254ada641fbd0ae9de40da045fc2226a2a"}, + {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:be9901f17ac2f527c352f2fdca3d717c1d7f2ce8a70bad5a490fc8cc5d2a6007"}, + {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a9791bf8916c1cf439c202fded32de93354b0f57328f303d71950b0027c7811"}, + {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d6162615e783c59e36f2d7caf903a7e3ecb6b32d4a4ae8907f2760b2ef395bf6"}, + {file = "pyinstrument-4.6.2-cp311-cp311-win32.whl", hash = "sha256:28af084aa84bbfd3620ebe71d5f9a0deca4451267f363738ca824f733de55056"}, + {file = "pyinstrument-4.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:dd6007d3c2e318e09e582435dd8d111cccf30d342af66886b783208813caf3d7"}, + {file = "pyinstrument-4.6.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e3813c8ecfab9d7d855c5f0f71f11793cf1507f40401aa33575c7fd613577c23"}, + {file = "pyinstrument-4.6.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c761372945e60fc1396b7a49f30592e8474e70a558f1a87346d27c8c4ce50f7"}, + {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fba3244e94c117bf4d9b30b8852bbdcd510e7329fdd5c7c8b3799e00a9215a8"}, + {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:803ac64e526473d64283f504df3b0d5c2c203ea9603cab428641538ffdc753a7"}, + {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2e554b1bb0df78f5ce8a92df75b664912ca93aa94208386102af454ec31b647"}, + {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7c671057fad22ee3ded897a6a361204ea2538e44c1233cad0e8e30f6d27f33db"}, + {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d02f31fa13a9e8dc702a113878419deba859563a32474c9f68e04619d43d6f01"}, + {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b55983a884f083f93f0fc6d12ff8df0acd1e2fb0580d2f4c7bfe6def33a84b58"}, + {file = "pyinstrument-4.6.2-cp312-cp312-win32.whl", hash = "sha256:fdc0a53b27e5d8e47147489c7dab596ddd1756b1e053217ef5bc6718567099ff"}, + {file = "pyinstrument-4.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:dd5c53a0159126b5ce7cbc4994433c9c671e057c85297ff32645166a06ad2c50"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b082df0bbf71251a7f4880a12ed28421dba84ea7110bb376e0533067a4eaff40"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90350533396071cb2543affe01e40bf534c35cb0d4b8fa9fdb0f052f9ca2cfe3"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67268bb0d579330cff40fd1c90b8510363ca1a0e7204225840614068658dab77"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20e15b4e1d29ba0b7fc81aac50351e0dc0d7e911e93771ebc3f408e864a2c93b"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e625fc6ffcd4fd420493edd8276179c3f784df207bef4c2192725c1b310534c"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:113d2fc534c9ca7b6b5661d6ada05515bf318f6eb34e8d05860fe49eb7cfe17e"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3098cd72b71a322a72dafeb4ba5c566465e193d2030adad4c09566bd2f89bf4f"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-win32.whl", hash = "sha256:08fdc7f88c989316fa47805234c37a40fafe7b614afd8ae863f0afa9d1707b37"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5ebeba952c0056dcc9b9355328c78c4b5c2a33b4b4276a9157a3ab589f3d1bac"}, + {file = "pyinstrument-4.6.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:34e59e91c88ec9ad5630c0964eca823949005e97736bfa838beb4789e94912a2"}, + {file = "pyinstrument-4.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cd0320c39e99e3c0a3129d1ed010ac41e5a7eb96fb79900d270080a97962e995"}, + {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46992e855d630575ec635eeca0068a8ddf423d4fd32ea0875a94e9f8688f0b95"}, + {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e474c56da636253dfdca7cd1998b240d6b39f7ed34777362db69224fcf053b1"}, + {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4b559322f30509ad8f082561792352d0805b3edfa508e492a36041fdc009259"}, + {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:06a8578b2943eb1dbbf281e1e59e44246acfefd79e1b06d4950f01b693de12af"}, + {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7bd3da31c46f1c1cb7ae89031725f6a1d1015c2041d9c753fe23980f5f9fd86c"}, + {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e63f4916001aa9c625976a50779282e0a5b5e9b17c52a50ef4c651e468ed5b88"}, + {file = "pyinstrument-4.6.2-cp38-cp38-win32.whl", hash = "sha256:32ec8db6896b94af790a530e1e0edad4d0f941a0ab8dd9073e5993e7ea46af7d"}, + {file = "pyinstrument-4.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:a59fc4f7db738a094823afe6422509fa5816a7bf74e768ce5a7a2ddd91af40ac"}, + {file = "pyinstrument-4.6.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3a165e0d2deb212d4cf439383982a831682009e1b08733c568cac88c89784e62"}, + {file = "pyinstrument-4.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ba858b3d6f6e5597c641edcc0e7e464f85aba86d71bc3b3592cb89897bf43f6"}, + {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fd8e547cf3df5f0ec6e4dffbe2e857f6b28eda51b71c3c0b5a2fc0646527835"}, + {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de2c1714a37a820033b19cf134ead43299a02662f1379140974a9ab733c5f3a"}, + {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01fc45dedceec3df81668d702bca6d400d956c8b8494abc206638c167c78dfd9"}, + {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5b6e161ef268d43ee6bbfae7fd2cdd0a52c099ddd21001c126ca1805dc906539"}, + {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6ba8e368d0421f15ba6366dfd60ec131c1b46505d021477e0f865d26cf35a605"}, + {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edca46f04a573ac2fb11a84b937844e6a109f38f80f4b422222fb5be8ecad8cb"}, + {file = "pyinstrument-4.6.2-cp39-cp39-win32.whl", hash = "sha256:baf375953b02fe94d00e716f060e60211ede73f49512b96687335f7071adb153"}, + {file = "pyinstrument-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:af1a953bce9fd530040895d01ff3de485e25e1576dccb014f76ba9131376fcad"}, + {file = "pyinstrument-4.6.2.tar.gz", hash = "sha256:0002ee517ed8502bbda6eb2bb1ba8f95a55492fcdf03811ba13d4806e50dd7f6"}, ] [package.extras] @@ -1132,13 +1080,13 @@ types = ["typing-extensions"] [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -1190,13 +1138,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "python-dotenv" -version = "1.0.0" +version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] [package.extras] @@ -1253,20 +1201,18 @@ files = [ [[package]] name = "represent" -version = "1.6.0.post0" +version = "2.1" description = "Create __repr__ automatically or declaratively." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"}, - {file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"}, + {file = "Represent-2.1-py3-none-any.whl", hash = "sha256:94fd22d7fec378240c598b20b233f80545ec7eb1131076e2d3d759cee9be2588"}, + {file = "Represent-2.1.tar.gz", hash = "sha256:0b2d015c14e7ba6b3b5e6a7ba131a952013fe944339ac538764ce728a75dbcac"}, ] -[package.dependencies] -six = ">=1.8.0" - [package.extras] -test = ["ipython", "mock", "pytest (>=3.0.5)"] +docstest = ["furo", "parver", "sphinx"] +test = ["ipython", "pytest", "rich"] [[package]] name = "respx" @@ -1284,28 +1230,28 @@ httpx = ">=0.21.0" [[package]] name = "ruff" -version = "0.0.284" -description = "An extremely fast Python linter, written in Rust." +version = "0.2.1" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.284-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:8b949084941232e2c27f8d12c78c5a6a010927d712ecff17231ee1a8371c205b"}, - {file = "ruff-0.0.284-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:a3930d66b35e4dc96197422381dff2a4e965e9278b5533e71ae8474ef202fab0"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d1f7096038961d8bc3b956ee69d73826843eb5b39a5fa4ee717ed473ed69c95"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bcaf85907fc905d838f46490ee15f04031927bbea44c478394b0bfdeadc27362"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3660b85a9d84162a055f1add334623ae2d8022a84dcd605d61c30a57b436c32"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0a3218458b140ea794da72b20ea09cbe13c4c1cdb7ac35e797370354628f4c05"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2fe880cff13fffd735387efbcad54ba0ff1272bceea07f86852a33ca71276f4"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1d098ea74d0ce31478765d1f8b4fbdbba2efc532397b5c5e8e5ea0c13d7e5ae"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c79ae3308e308b94635cd57a369d1e6f146d85019da2fbc63f55da183ee29b"}, - {file = "ruff-0.0.284-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f86b2b1e7033c00de45cc176cf26778650fb8804073a0495aca2f674797becbb"}, - {file = "ruff-0.0.284-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e37e086f4d623c05cd45a6fe5006e77a2b37d57773aad96b7802a6b8ecf9c910"}, - {file = "ruff-0.0.284-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d29dfbe314e1131aa53df213fdfea7ee874dd96ea0dd1471093d93b59498384d"}, - {file = "ruff-0.0.284-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:88295fd649d0aa1f1271441df75bf06266a199497afd239fd392abcfd75acd7e"}, - {file = "ruff-0.0.284-py3-none-win32.whl", hash = "sha256:735cd62fccc577032a367c31f6a9de7c1eb4c01fa9a2e60775067f44f3fc3091"}, - {file = "ruff-0.0.284-py3-none-win_amd64.whl", hash = "sha256:f67ed868d79fbcc61ad0fa034fe6eed2e8d438d32abce9c04b7c4c1464b2cf8e"}, - {file = "ruff-0.0.284-py3-none-win_arm64.whl", hash = "sha256:1292cfc764eeec3cde35b3a31eae3f661d86418b5e220f5d5dba1c27a6eccbb6"}, - {file = "ruff-0.0.284.tar.gz", hash = "sha256:ebd3cc55cd499d326aac17a331deaea29bea206e01c08862f9b5c6e93d77a491"}, + {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"}, + {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"}, + {file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"}, + {file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"}, + {file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"}, + {file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"}, + {file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"}, + {file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"}, + {file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"}, + {file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"}, ] [[package]] @@ -1499,13 +1445,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] @@ -1529,13 +1475,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "virtualenv" -version = "20.24.7" +version = "20.25.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, - {file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, ] [package.dependencies] @@ -1549,13 +1495,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "websocket-client" -version = "1.6.4" +version = "1.7.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"}, - {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"}, + {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, + {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, ] [package.extras] @@ -1612,4 +1558,4 @@ pgsql = ["psycopg2-binary"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "7ad5150f23bb8ba229e43b3d329b4ec747791622f5e83357a5fae8aee9315fdc" +content-hash = "94a66019b5c9fd191e33aa9c9a2a6a22a2a0db1d60110e858673738738ece902" diff --git a/pyproject.toml b/pyproject.toml index 4f689213..023e1ceb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,22 +31,20 @@ httpx = {extras = ["socks"], version = "^0.25.1"} bip32 = "^3.4" mnemonic = "^0.20" bolt11 = "^2.0.5" -black = "23.11.0" pre-commit = "^3.5.0" [tool.poetry.extras] pgsql = ["psycopg2-binary"] [tool.poetry.group.dev.dependencies] -mypy = "^1.5.1" -black = "^23.11.0" pytest-asyncio = "^0.21.1" pytest-cov = "^4.0.0" pytest = "^7.4.0" -ruff = "^0.0.284" pre-commit = "^3.3.3" fastapi-profiler = "^1.2.0" respx = "^0.20.2" +ruff = "^0.2.1" +mypy = "^1.8.0" [build-system] requires = ["poetry-core>=1.0.0"] @@ -69,10 +67,12 @@ preview = true [tool.ruff] # Same as Black. but black has a 10% overflow rule line-length = 150 +show-fixes = true +target-version = "py38" -# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. +[tool.ruff.lint] # (`I`) means isorting -select = ["E", "F", "I"] +extend-select = ["I"] ignore = [] # Allow autofix for all enabled rules (when `--fix`) is provided. @@ -81,7 +81,7 @@ unfixable = [] # Exclude a variety of commonly ignored directories. exclude = [ - "cashu/nostr", + "cashu/nostr/*", "cashu/core/bolt11.py", ".bzr", ".direnv", @@ -112,6 +112,6 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" # Assume Python 3.8 # target-version = "py38" -[tool.ruff.mccabe] +[tool.ruff.lint.mccabe] # Unlike Flake8, default to a complexity level of 10. max-complexity = 10 diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 59b94849..073e6204 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -12,38 +12,20 @@ def test_hash_to_curve(): - result = hash_to_curve( - bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000000" - ) - ) - assert ( - result.serialize().hex() - == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" - ) + result = hash_to_curve(bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000")) + assert result.serialize().hex() == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" - result = hash_to_curve( - bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) - ) - assert ( - result.serialize().hex() - == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" - ) + result = hash_to_curve(bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001")) + assert result.serialize().hex() == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" def test_hash_to_curve_iteration(): """This input causes multiple rounds of the hash_to_curve algorithm.""" - result = hash_to_curve( - bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000002" - ) - ) - assert ( - result.serialize().hex() - == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" - ) + result = hash_to_curve(bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000002")) + assert result.serialize().hex() == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" + + result2 = hash_to_curve(b"\x92g\xd3\xdb\xed\x80)AH?\x1a\xfa*k\xc6\x8d\xe5\xf6S\x12\x8a\xca\x9b\xf1F\x1c]\n:\xd3n\xd2") + assert result2.serialize().hex() == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" def test_step1(): @@ -51,57 +33,63 @@ def test_step1(): B_, blinding_factor = step1_alice( secret_msg, blinding_factor=PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) # 32 bytes + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") # 32 bytes ), ) - assert ( - B_.serialize().hex() - == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + assert B_.serialize().hex() == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + assert blinding_factor.private_key == bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") + + +def test_step1_collision(): + msg1 = "272120214382734463759987987430654107405" + B1, _ = step1_alice( + msg1, + blinding_factor=PrivateKey( + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") # 32 bytes + ), ) - assert blinding_factor.private_key == bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" + B1hex = B1.serialize().hex() + + msg2 = "\x02bae688ad7846ebf38ffa040627d4f12c" + B2, _ = step1_alice( + msg2, + blinding_factor=PrivateKey( + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") # 32 bytes + ), ) + B2hex = B2.serialize().hex() + assert B1hex != B2hex + + h2c1 = hash_to_curve(msg1.encode("utf-8")) + h2c2 = hash_to_curve(msg2.encode("utf-8")) + assert h2c1.serialize().hex() != h2c2.serialize().hex() + return def test_step2(): B_, _ = step1_alice( "test_message", blinding_factor=PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ), + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), raw=True, ), ) a = PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ), + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), raw=True, ) C_, e, s = step2_bob(B_, a) - assert ( - C_.serialize().hex() - == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" - ) + assert C_.serialize().hex() == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" def test_step3(): # C = C_ - A.mult(r) C_ = PublicKey( - bytes.fromhex( - "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" - ), + bytes.fromhex("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"), raw=True, ) - r = PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) - ) + r = PrivateKey(privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001")) A = PublicKey( pubkey=b"\x02" @@ -112,17 +100,12 @@ def test_step3(): ) C = step3_alice(C_, r, A) - assert ( - C.serialize().hex() - == "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" - ) + assert C.serialize().hex() == "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" def test_dleq_hash_e(): C_ = PublicKey( - bytes.fromhex( - "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" - ), + bytes.fromhex("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"), raw=True, ) K = PublicKey( @@ -154,69 +137,45 @@ def test_dleq_step2_bob_dleq(): B_, _ = step1_alice( "test_message", blinding_factor=PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ), + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), raw=True, ), ) a = PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ), + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), raw=True, ) - p_bytes = bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) # 32 bytes + p_bytes = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") # 32 bytes e, s = step2_bob_dleq(B_, a, p_bytes) + assert e.serialize() == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" assert ( - e.serialize() - == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" - ) - assert ( - s.serialize() - == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" + s.serialize() == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" ) # differs from e only in least significant byte because `a = 0x1` # change `a` a = PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000001111" - ), + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000001111"), raw=True, ) e, s = step2_bob_dleq(B_, a, p_bytes) - assert ( - e.serialize() - == "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e" - ) - assert ( - s.serialize() - == "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 = PrivateKey( - bytes.fromhex( - "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" - ), + bytes.fromhex("9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9"), raw=True, ) # s from test_step2_bob_dleq for a=0x1 s = PrivateKey( - bytes.fromhex( - "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" - ), + bytes.fromhex("9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da"), raw=True, ) a = PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ), + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), raw=True, ) A = a.pubkey @@ -229,9 +188,7 @@ def test_dleq_alice_verify_dleq(): # ), # 32 bytes # ) B_ = PublicKey( - bytes.fromhex( - "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" - ), + bytes.fromhex("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"), raw=True, ) @@ -245,9 +202,7 @@ def test_dleq_alice_verify_dleq(): # C_, e, s = step2_bob(B_, a) C_ = PublicKey( - bytes.fromhex( - "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" - ), + bytes.fromhex("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"), raw=True, ) @@ -257,9 +212,7 @@ def test_dleq_alice_verify_dleq(): def test_dleq_alice_direct_verify_dleq(): # ----- test again with B_ and C_ as per step1 and step2 a = PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ), + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), raw=True, ) A = a.pubkey @@ -267,9 +220,7 @@ def test_dleq_alice_direct_verify_dleq(): B_, _ = step1_alice( "test_message", blinding_factor=PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ), + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), raw=True, ), ) @@ -279,18 +230,14 @@ def test_dleq_alice_direct_verify_dleq(): def test_dleq_carol_varify_from_bob(): a = PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ), + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), raw=True, ) A = a.pubkey assert A secret_msg = "test_message" r = PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ), + privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), raw=True, ) B_, _ = step1_alice(secret_msg, r) From c4f4f3ccf22786403a9887e2baec6377d6d50232 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 17 Feb 2024 21:33:16 +0100 Subject: [PATCH 34/57] Revert "Dev: Update ruff precommit hooks (#434)" (#436) This reverts commit fca2a6cb4b5a2ef0e89d366db3a3fae72ece2136. --- .github/workflows/checks.yml | 6 +- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 12 +- Makefile | 10 +- cashu/core/crypto/b_dhke.py | 17 +- cashu/lightning/blink.py | 46 ++- poetry.lock | 684 +++++++++++++++++++---------------- pyproject.toml | 16 +- tests/test_crypto.py | 173 ++++++--- 9 files changed, 556 insertions(+), 410 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 7bb953d4..d044b185 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -11,7 +11,7 @@ on: type: string jobs: - format: + formatting: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -26,8 +26,8 @@ jobs: cache: "poetry" - name: Install packages run: poetry install - - name: Ruff check - run: make ruff-check + - name: Check black + run: make black-check mypy: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c50f6b2..82296e13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: Nutshell CI on: push: branches: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a7d36b2..cad022a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: "^cashu/nostr/.*" repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.3.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -12,12 +12,16 @@ repos: - id: debug-statements - id: mixed-line-ending - id: check-case-conflict + # - repo: https://github.com/psf/black + # rev: 23.11.0 + # hooks: + # - id: black + # args: [--line-length=150] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.1 + rev: v0.0.283 hooks: - id: ruff - args: [--fix] - - id: ruff-format + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.6.0 hooks: diff --git a/Makefile b/Makefile index f6cb065b..b9e79292 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,18 @@ ruff: ruff-check: poetry run ruff check . +black: + poetry run black . + +black-check: + poetry run black . --check + mypy: poetry run mypy cashu --check-untyped-defs -format: ruff +format: black ruff -check: ruff-check mypy +check: black-check ruff-check mypy clean: rm -r cashu.egg-info/ || true diff --git a/cashu/core/crypto/b_dhke.py b/cashu/core/crypto/b_dhke.py index 17634a0f..4abf1b77 100644 --- a/cashu/core/crypto/b_dhke.py +++ b/cashu/core/crypto/b_dhke.py @@ -68,7 +68,6 @@ def hash_to_curve(message: bytes) -> PublicKey: point = PublicKey(b"\x02" + _hash, raw=True) except Exception: msg_to_hash = _hash - print(_hash) return point @@ -102,14 +101,18 @@ def hash_to_curve_domain_separated(message: bytes) -> PublicKey: raise ValueError("No valid point found") -def step1_alice(secret_msg: str, blinding_factor: Optional[PrivateKey] = None) -> tuple[PublicKey, PrivateKey]: +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 step1_alice_domain_separated(secret_msg: str, blinding_factor: Optional[PrivateKey] = None) -> tuple[PublicKey, PrivateKey]: +def step1_alice_domain_separated( + secret_msg: str, blinding_factor: Optional[PrivateKey] = None +) -> tuple[PublicKey, PrivateKey]: Y: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) r = blinding_factor or PrivateKey() B_: PublicKey = Y + r.pubkey # type: ignore @@ -148,7 +151,9 @@ def hash_e(*publickeys: PublicKey) -> bytes: return e -def step2_bob_dleq(B_: PublicKey, a: PrivateKey, p_bytes: bytes = b"") -> Tuple[PrivateKey, PrivateKey]: +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) @@ -169,7 +174,9 @@ def step2_bob_dleq(B_: PublicKey, a: PrivateKey, p_bytes: bytes = b"") -> Tuple[ return epk, spk -def alice_verify_dleq(B_: PublicKey, C_: PublicKey, e: PrivateKey, s: PrivateKey, A: PublicKey) -> bool: +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 diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index 3f6a618c..d4f11e80 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -27,8 +27,7 @@ class BlinkWallet(LightningBackend): - """ - https://dev.blink.sv/ + """https://dev.blink.sv/ Create API Key at: https://dashboard.blink.sv/ """ @@ -55,7 +54,10 @@ async def status(self) -> StatusResponse: try: r = await self.client.post( url=self.endpoint, - data=('{"query":"query me { me { defaultAccount { wallets { id' ' walletCurrency balance }}}}", "variables":{}}'), + data=( + '{"query":"query me { me { defaultAccount { wallets { id' + ' walletCurrency balance }}}}", "variables":{}}' + ), ) r.raise_for_status() except Exception as exc: @@ -69,7 +71,9 @@ async def status(self) -> StatusResponse: resp: dict = r.json() except Exception: return StatusResponse( - error_message=(f"Received invalid response from {self.endpoint}: {r.text}"), + error_message=( + f"Received invalid response from {self.endpoint}: {r.text}" + ), balance=0, ) @@ -133,7 +137,9 @@ async def create_invoice( resp = r.json() assert resp, "invalid response" - payment_request = resp["data"]["lnInvoiceCreateOnBehalfOfRecipient"]["invoice"]["paymentRequest"] + payment_request = resp["data"]["lnInvoiceCreateOnBehalfOfRecipient"]["invoice"][ + "paymentRequest" + ] checking_id = payment_request return InvoiceResponse( @@ -142,7 +148,9 @@ async def create_invoice( payment_request=payment_request, ) - async def pay_invoice(self, quote: MeltQuote, fee_limit_msat: int) -> PaymentResponse: + async def pay_invoice( + self, quote: MeltQuote, fee_limit_msat: int + ) -> PaymentResponse: variables = { "input": { "paymentRequest": quote.request, @@ -177,7 +185,9 @@ async def pay_invoice(self, quote: MeltQuote, fee_limit_msat: int) -> PaymentRes return PaymentResponse(ok=False, error_message=str(e)) resp: dict = r.json() - paid = self.payment_execution_statuses[resp["data"]["lnInvoicePaymentSend"]["status"]] + paid = self.payment_execution_statuses[ + resp["data"]["lnInvoicePaymentSend"]["status"] + ] fee = resp["data"]["lnInvoicePaymentSend"]["transaction"]["settlementFee"] checking_id = quote.request @@ -212,7 +222,9 @@ async def get_invoice_status(self, checking_id: str) -> PaymentStatus: return PaymentStatus(paid=None) resp: dict = r.json() if resp["data"]["lnInvoicePaymentStatus"]["errors"]: - logger.error("Blink Error", resp["data"]["lnInvoicePaymentStatus"]["errors"]) + logger.error( + "Blink Error", resp["data"]["lnInvoicePaymentStatus"]["errors"] + ) return PaymentStatus(paid=None) paid = self.invoice_statuses[resp["data"]["lnInvoicePaymentStatus"]["status"]] return PaymentStatus(paid=paid) @@ -255,11 +267,19 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus: resp: dict = r.json() # no result found - if not resp["data"]["me"]["defaultAccount"]["walletById"]["transactionsByPaymentHash"]: + if not resp["data"]["me"]["defaultAccount"]["walletById"][ + "transactionsByPaymentHash" + ]: return PaymentStatus(paid=None) - paid = self.payment_statuses[resp["data"]["me"]["defaultAccount"]["walletById"]["transactionsByPaymentHash"][0]["status"]] - fee = resp["data"]["me"]["defaultAccount"]["walletById"]["transactionsByPaymentHash"][0]["settlementFee"] + paid = self.payment_statuses[ + resp["data"]["me"]["defaultAccount"]["walletById"][ + "transactionsByPaymentHash" + ][0]["status"] + ] + fee = resp["data"]["me"]["defaultAccount"]["walletById"][ + "transactionsByPaymentHash" + ][0]["settlementFee"] return PaymentStatus( paid=paid, @@ -306,7 +326,9 @@ async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: fees_response_msat = int(resp["data"]["lnInvoiceFeeProbe"]["amount"]) * 1000 # we either take fee_msat_response or the BLINK_MAX_FEE_PERCENT, whichever is higher - fees_msat = max(fees_response_msat, math.ceil(amount_msat * BLINK_MAX_FEE_PERCENT)) + fees_msat = max( + fees_response_msat, math.ceil(amount_msat * BLINK_MAX_FEE_PERCENT) + ) fees = Amount(unit=Unit.msat, amount=fees_msat) amount = Amount(unit=Unit.msat, amount=amount_msat) diff --git a/poetry.lock b/poetry.lock index ee07069b..a901cc7e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -34,22 +34,21 @@ files = [ [[package]] name = "attrs" -version = "23.2.0" +version = "23.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] +dev = ["attrs[docs,tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] name = "base58" @@ -103,6 +102,48 @@ files = [ {file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"}, ] +[[package]] +name = "black" +version = "23.11.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, + {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, + {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, + {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, + {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, + {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, + {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, + {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, + {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, + {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, + {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, + {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, + {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, + {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, + {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, + {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, + {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, + {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "bolt11" version = "2.0.5" @@ -124,13 +165,13 @@ secp256k1 = "*" [[package]] name = "certifi" -version = "2024.2.2" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] @@ -290,63 +331,63 @@ files = [ [[package]] name = "coverage" -version = "7.4.1" +version = "7.3.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, - {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, - {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, - {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, - {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, - {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, - {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, - {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, - {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, - {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, - {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, - {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, - {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, - {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, + {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, + {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, + {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, + {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, + {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, + {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, + {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, + {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, + {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, + {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, + {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, + {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] [package.dependencies] @@ -357,34 +398,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.7" +version = "41.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, - {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, - {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, - {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, + {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, + {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, + {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, + {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, + {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, + {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, + {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, ] [package.dependencies] @@ -402,13 +443,13 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "distlib" -version = "0.3.8" +version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] [[package]] @@ -527,13 +568,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.2" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, + {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, ] [package.dependencies] @@ -544,7 +585,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.23.0)"] [[package]] name = "httpx" @@ -573,13 +614,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "identify" -version = "2.5.34" +version = "2.5.32" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.34-py2.py3-none-any.whl", hash = "sha256:a4316013779e433d08b96e5eabb7f641e6c7942e4ab5d4c509ebd2e7a8994aed"}, - {file = "identify-2.5.34.tar.gz", hash = "sha256:ee17bc9d499899bc9eaec1ac7bf2dc9eedd480db9d88b96d123d3b64a9d34f5d"}, + {file = "identify-2.5.32-py2.py3-none-any.whl", hash = "sha256:0b7656ef6cba81664b783352c73f8c24b39cf82f926f78f4550eda928e5e0545"}, + {file = "identify-2.5.32.tar.gz", hash = "sha256:5d9979348ec1a21c768ae07e0a652924538e8bce67313a73cb0f681cf08ba407"}, ] [package.extras] @@ -598,20 +639,20 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.11.0" +version = "6.8.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b"}, - {file = "importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443"}, + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] @@ -646,22 +687,22 @@ dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptio [[package]] name = "marshmallow" -version = "3.20.2" +version = "3.20.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, - {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, + {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, + {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] -dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["pre-commit (>=2.4,<4.0)"] +dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -677,38 +718,38 @@ files = [ [[package]] name = "mypy" -version = "1.8.0" +version = "1.7.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, - {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, - {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, - {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, - {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, - {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, - {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, - {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, - {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, - {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, - {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, + {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, + {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, + {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, + {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, + {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, + {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, + {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, + {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, + {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, + {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, + {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, + {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, + {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, + {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, + {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, + {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, + {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, + {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, + {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, + {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, + {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, + {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, + {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, + {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, + {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, + {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, + {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, ] [package.dependencies] @@ -772,30 +813,41 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + [[package]] name = "platformdirs" -version = "4.2.0" +version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, + {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -911,88 +963,88 @@ files = [ [[package]] name = "pycryptodomex" -version = "3.20.0" +version = "3.19.0" description = "Cryptographic library for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, - {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, - {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, - {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff64fd720def623bf64d8776f8d0deada1cc1bf1ec3c1f9d6f5bb5bd098d034f"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:61056a1fd3254f6f863de94c233b30dd33bc02f8c935b2000269705f1eeeffa4"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:258c4233a3fe5a6341780306a36c6fb072ef38ce676a6d41eec3e591347919e8"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e45bb4635b3c4e0a00ca9df75ef6295838c85c2ac44ad882410cb631ed1eeaa"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a12144d785518f6491ad334c75ccdc6ad52ea49230b4237f319dbb7cef26f464"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:1789d89f61f70a4cd5483d4dfa8df7032efab1118f8b9894faae03c967707865"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:eb2fc0ec241bf5e5ef56c8fbec4a2634d631e4c4f616a59b567947a0f35ad83c"}, + {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c9a68a2f7bd091ccea54ad3be3e9d65eded813e6d79fdf4cc3604e26cdd6384f"}, + {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8df69e41f7e7015a90b94d1096ec3d8e0182e73449487306709ec27379fff761"}, + {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:917033016ecc23c8933205585a0ab73e20020fdf671b7cd1be788a5c4039840b"}, + {file = "pycryptodomex-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:e8e5ecbd4da4157889fce8ba49da74764dd86c891410bfd6b24969fa46edda51"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:a77b79852175064c822b047fee7cf5a1f434f06ad075cc9986aa1c19a0c53eb0"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5b883e1439ab63af976656446fb4839d566bb096f15fc3c06b5a99cde4927188"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3866d68e2fc345162b1b9b83ef80686acfe5cec0d134337f3b03950a0a8bf56"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74eb1f73f788facece7979ce91594dc177e1a9b5d5e3e64697dd58299e5cb4d"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cb51096a6a8d400724104db8a7e4f2206041a1f23e58924aa3d8d96bcb48338"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a588a1cb7781da9d5e1c84affd98c32aff9c89771eac8eaa659d2760666f7139"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:d4dd3b381ff5a5907a3eb98f5f6d32c64d319a840278ceea1dcfcc65063856f3"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:263de9a96d2fcbc9f5bd3a279f14ea0d5f072adb68ebd324987576ec25da084d"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-win32.whl", hash = "sha256:67c8eb79ab33d0fbcb56842992298ddb56eb6505a72369c20f60bc1d2b6fb002"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:09c9401dc06fb3d94cb1ec23b4ea067a25d1f4c6b7b118ff5631d0b5daaab3cc"}, + {file = "pycryptodomex-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:edbe083c299835de7e02c8aa0885cb904a75087d35e7bab75ebe5ed336e8c3e2"}, + {file = "pycryptodomex-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:136b284e9246b4ccf4f752d435c80f2c44fc2321c198505de1d43a95a3453b3c"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5d73e9fa3fe830e7b6b42afc49d8329b07a049a47d12e0ef9225f2fd220f19b2"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b2f1982c5bc311f0aab8c293524b861b485d76f7c9ab2c3ac9a25b6f7655975"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb040b5dda1dff1e197d2ef71927bd6b8bfcb9793bc4dfe0bb6df1e691eaacb"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:800a2b05cfb83654df80266692f7092eeefe2a314fa7901dcefab255934faeec"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c01678aee8ac0c1a461cbc38ad496f953f9efcb1fa19f5637cbeba7544792a53"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2126bc54beccbede6eade00e647106b4f4c21e5201d2b0a73e9e816a01c50905"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b801216c48c0886742abf286a9a6b117e248ca144d8ceec1f931ce2dd0c9cb40"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50cb18d4dd87571006fd2447ccec85e6cec0136632a550aa29226ba075c80644"}, + {file = "pycryptodomex-3.19.0.tar.gz", hash = "sha256:af83a554b3f077564229865c45af0791be008ac6469ef0098152139e6bd4b5b6"}, ] [[package]] name = "pydantic" -version = "1.10.14" +version = "1.10.13" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, - {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, - {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, - {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, - {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, - {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, - {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, - {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, - {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, - {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, + {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, + {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, + {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, + {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, + {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, + {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, + {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, + {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, + {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, + {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, + {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, + {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, + {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, + {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, + {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, + {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, + {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, + {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, + {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, + {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, + {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, + {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, + {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, + {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, + {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, + {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, + {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, + {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, + {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, + {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, + {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, + {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, + {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, + {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, + {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, + {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, ] [package.dependencies] @@ -1004,71 +1056,71 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pyinstrument" -version = "4.6.2" +version = "4.6.1" description = "Call stack profiler for Python. Shows you why your code is slow!" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstrument-4.6.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a1b1cd768ea7ea9ab6f5490f7e74431321bcc463e9441dbc2f769617252d9e2"}, - {file = "pyinstrument-4.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8a386b9d09d167451fb2111eaf86aabf6e094fed42c15f62ec51d6980bce7d96"}, - {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23c3e3ca8553b9aac09bd978c73d21b9032c707ac6d803bae6a20ecc048df4a8"}, - {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f329f5534ca069420246f5ce57270d975229bcb92a3a3fd6b2ca086527d9764"}, - {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4dcdcc7ba224a0c5edfbd00b0f530f5aed2b26da5aaa2f9af5519d4aa8c7e41"}, - {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73db0c2c99119c65b075feee76e903b4ed82e59440fe8b5724acf5c7cb24721f"}, - {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:da58f265326f3cf3975366ccb8b39014f1e69ff8327958a089858d71c633d654"}, - {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:feebcf860f955401df30d029ec8de7a0c5515d24ea809736430fd1219686fe14"}, - {file = "pyinstrument-4.6.2-cp310-cp310-win32.whl", hash = "sha256:b2b66ff0b16c8ecf1ec22de001cfff46872b2c163c62429055105564eef50b2e"}, - {file = "pyinstrument-4.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:8d104b7a7899d5fa4c5bf1ceb0c1a070615a72c5dc17bc321b612467ad5c5d88"}, - {file = "pyinstrument-4.6.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:62f6014d2b928b181a52483e7c7b82f2c27e22c577417d1681153e5518f03317"}, - {file = "pyinstrument-4.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dcb5c8d763c5df55131670ba2a01a8aebd0d490a789904a55eb6a8b8d497f110"}, - {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ed4e8c6c84e0e6429ba7008a66e435ede2d8cb027794c20923c55669d9c5633"}, - {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c0f0e1d8f8c70faa90ff57f78ac0dda774b52ea0bfb2d9f0f41ce6f3e7c869e"}, - {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3c44cb037ad0d6e9d9a48c14d856254ada641fbd0ae9de40da045fc2226a2a"}, - {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:be9901f17ac2f527c352f2fdca3d717c1d7f2ce8a70bad5a490fc8cc5d2a6007"}, - {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a9791bf8916c1cf439c202fded32de93354b0f57328f303d71950b0027c7811"}, - {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d6162615e783c59e36f2d7caf903a7e3ecb6b32d4a4ae8907f2760b2ef395bf6"}, - {file = "pyinstrument-4.6.2-cp311-cp311-win32.whl", hash = "sha256:28af084aa84bbfd3620ebe71d5f9a0deca4451267f363738ca824f733de55056"}, - {file = "pyinstrument-4.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:dd6007d3c2e318e09e582435dd8d111cccf30d342af66886b783208813caf3d7"}, - {file = "pyinstrument-4.6.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e3813c8ecfab9d7d855c5f0f71f11793cf1507f40401aa33575c7fd613577c23"}, - {file = "pyinstrument-4.6.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c761372945e60fc1396b7a49f30592e8474e70a558f1a87346d27c8c4ce50f7"}, - {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fba3244e94c117bf4d9b30b8852bbdcd510e7329fdd5c7c8b3799e00a9215a8"}, - {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:803ac64e526473d64283f504df3b0d5c2c203ea9603cab428641538ffdc753a7"}, - {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2e554b1bb0df78f5ce8a92df75b664912ca93aa94208386102af454ec31b647"}, - {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7c671057fad22ee3ded897a6a361204ea2538e44c1233cad0e8e30f6d27f33db"}, - {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d02f31fa13a9e8dc702a113878419deba859563a32474c9f68e04619d43d6f01"}, - {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b55983a884f083f93f0fc6d12ff8df0acd1e2fb0580d2f4c7bfe6def33a84b58"}, - {file = "pyinstrument-4.6.2-cp312-cp312-win32.whl", hash = "sha256:fdc0a53b27e5d8e47147489c7dab596ddd1756b1e053217ef5bc6718567099ff"}, - {file = "pyinstrument-4.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:dd5c53a0159126b5ce7cbc4994433c9c671e057c85297ff32645166a06ad2c50"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b082df0bbf71251a7f4880a12ed28421dba84ea7110bb376e0533067a4eaff40"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90350533396071cb2543affe01e40bf534c35cb0d4b8fa9fdb0f052f9ca2cfe3"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67268bb0d579330cff40fd1c90b8510363ca1a0e7204225840614068658dab77"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20e15b4e1d29ba0b7fc81aac50351e0dc0d7e911e93771ebc3f408e864a2c93b"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e625fc6ffcd4fd420493edd8276179c3f784df207bef4c2192725c1b310534c"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:113d2fc534c9ca7b6b5661d6ada05515bf318f6eb34e8d05860fe49eb7cfe17e"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3098cd72b71a322a72dafeb4ba5c566465e193d2030adad4c09566bd2f89bf4f"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-win32.whl", hash = "sha256:08fdc7f88c989316fa47805234c37a40fafe7b614afd8ae863f0afa9d1707b37"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5ebeba952c0056dcc9b9355328c78c4b5c2a33b4b4276a9157a3ab589f3d1bac"}, - {file = "pyinstrument-4.6.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:34e59e91c88ec9ad5630c0964eca823949005e97736bfa838beb4789e94912a2"}, - {file = "pyinstrument-4.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cd0320c39e99e3c0a3129d1ed010ac41e5a7eb96fb79900d270080a97962e995"}, - {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46992e855d630575ec635eeca0068a8ddf423d4fd32ea0875a94e9f8688f0b95"}, - {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e474c56da636253dfdca7cd1998b240d6b39f7ed34777362db69224fcf053b1"}, - {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4b559322f30509ad8f082561792352d0805b3edfa508e492a36041fdc009259"}, - {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:06a8578b2943eb1dbbf281e1e59e44246acfefd79e1b06d4950f01b693de12af"}, - {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7bd3da31c46f1c1cb7ae89031725f6a1d1015c2041d9c753fe23980f5f9fd86c"}, - {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e63f4916001aa9c625976a50779282e0a5b5e9b17c52a50ef4c651e468ed5b88"}, - {file = "pyinstrument-4.6.2-cp38-cp38-win32.whl", hash = "sha256:32ec8db6896b94af790a530e1e0edad4d0f941a0ab8dd9073e5993e7ea46af7d"}, - {file = "pyinstrument-4.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:a59fc4f7db738a094823afe6422509fa5816a7bf74e768ce5a7a2ddd91af40ac"}, - {file = "pyinstrument-4.6.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3a165e0d2deb212d4cf439383982a831682009e1b08733c568cac88c89784e62"}, - {file = "pyinstrument-4.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ba858b3d6f6e5597c641edcc0e7e464f85aba86d71bc3b3592cb89897bf43f6"}, - {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fd8e547cf3df5f0ec6e4dffbe2e857f6b28eda51b71c3c0b5a2fc0646527835"}, - {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de2c1714a37a820033b19cf134ead43299a02662f1379140974a9ab733c5f3a"}, - {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01fc45dedceec3df81668d702bca6d400d956c8b8494abc206638c167c78dfd9"}, - {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5b6e161ef268d43ee6bbfae7fd2cdd0a52c099ddd21001c126ca1805dc906539"}, - {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6ba8e368d0421f15ba6366dfd60ec131c1b46505d021477e0f865d26cf35a605"}, - {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edca46f04a573ac2fb11a84b937844e6a109f38f80f4b422222fb5be8ecad8cb"}, - {file = "pyinstrument-4.6.2-cp39-cp39-win32.whl", hash = "sha256:baf375953b02fe94d00e716f060e60211ede73f49512b96687335f7071adb153"}, - {file = "pyinstrument-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:af1a953bce9fd530040895d01ff3de485e25e1576dccb014f76ba9131376fcad"}, - {file = "pyinstrument-4.6.2.tar.gz", hash = "sha256:0002ee517ed8502bbda6eb2bb1ba8f95a55492fcdf03811ba13d4806e50dd7f6"}, + {file = "pyinstrument-4.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:73476e4bc6e467ac1b2c3c0dd1f0b71c9061d4de14626676adfdfbb14aa342b4"}, + {file = "pyinstrument-4.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4d1da8efd974cf9df52ee03edaee2d3875105ddd00de35aa542760f7c612bdf7"}, + {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:507be1ee2f2b0c9fba74d622a272640dd6d1b0c9ec3388b2cdeb97ad1e77125f"}, + {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cee6de08eb45754ef4f602ce52b640d1c535d934a6a8733a974daa095def37"}, + {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7873e8cec92321251fdf894a72b3c78f4c5c20afdd1fef0baf9042ec843bb04"}, + {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a242f6cac40bc83e1f3002b6b53681846dfba007f366971db0bf21e02dbb1903"}, + {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:97c9660cdb4bd2a43cf4f3ab52cffd22f3ac9a748d913b750178fb34e5e39e64"}, + {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e304cd0723e2b18ada5e63c187abf6d777949454c734f5974d64a0865859f0f4"}, + {file = "pyinstrument-4.6.1-cp310-cp310-win32.whl", hash = "sha256:cee21a2d78187dd8a80f72f5d0f1ddb767b2d9800f8bb4d94b6d11f217c22cdb"}, + {file = "pyinstrument-4.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:2000712f71d693fed2f8a1c1638d37b7919124f367b37976d07128d49f1445eb"}, + {file = "pyinstrument-4.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a366c6f3dfb11f1739bdc1dee75a01c1563ad0bf4047071e5e77598087df457f"}, + {file = "pyinstrument-4.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6be327be65d934796558aa9cb0f75ce62ebd207d49ad1854610c97b0579ad47"}, + {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e160d9c5d20d3e4ef82269e4e8b246ff09bdf37af5fb8cb8ccca97936d95ad6"}, + {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ffbf56605ef21c2fcb60de2fa74ff81f417d8be0c5002a407e414d6ef6dee43"}, + {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c92cc4924596d6e8f30a16182bbe90893b1572d847ae12652f72b34a9a17c24a"}, + {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f4b48a94d938cae981f6948d9ec603bab2087b178d2095d042d5a48aabaecaab"}, + {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7a386392275bdef4a1849712dc5b74f0023483fca14ef93d0ca27d453548982"}, + {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:871b131b83e9b1122f2325061c68ed1e861eebcb568c934d2fb193652f077f77"}, + {file = "pyinstrument-4.6.1-cp311-cp311-win32.whl", hash = "sha256:8d8515156dd91f5652d13b5fcc87e634f8fe1c07b68d1d0840348cdd50bf5ace"}, + {file = "pyinstrument-4.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb868fbe089036e9f32525a249f4c78b8dc46967612393f204b8234f439c9cc4"}, + {file = "pyinstrument-4.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a18cd234cce4f230f1733807f17a134e64a1f1acabf74a14d27f583cf2b183df"}, + {file = "pyinstrument-4.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:574cfca69150be4ce4461fb224712fbc0722a49b0dc02fa204d02807adf6b5a0"}, + {file = "pyinstrument-4.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e02cf505e932eb8ccf561b7527550a67ec14fcae1fe0e25319b09c9c166e914"}, + {file = "pyinstrument-4.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832fb2acef9d53701c1ab546564c45fb70a8770c816374f8dd11420d399103c9"}, + {file = "pyinstrument-4.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13cb57e9607545623ebe462345b3d0c4caee0125d2d02267043ece8aca8f4ea0"}, + {file = "pyinstrument-4.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9be89e7419bcfe8dd6abb0d959d6d9c439c613a4a873514c43d16b48dae697c9"}, + {file = "pyinstrument-4.6.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:476785cfbc44e8e1b1ad447398aa3deae81a8df4d37eb2d8bbb0c404eff979cd"}, + {file = "pyinstrument-4.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e9cebd90128a3d2fee36d3ccb665c1b9dce75261061b2046203e45c4a8012d54"}, + {file = "pyinstrument-4.6.1-cp312-cp312-win32.whl", hash = "sha256:1d0b76683df2ad5c40eff73607dc5c13828c92fbca36aff1ddf869a3c5a55fa6"}, + {file = "pyinstrument-4.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:c4b7af1d9d6a523cfbfedebcb69202242d5bd0cb89c4e094cc73d5d6e38279bd"}, + {file = "pyinstrument-4.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:79ae152f8c6a680a188fb3be5e0f360ac05db5bbf410169a6c40851dfaebcce9"}, + {file = "pyinstrument-4.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07cad2745964c174c65aa75f1bf68a4394d1b4d28f33894837cfd315d1e836f0"}, + {file = "pyinstrument-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb81f66f7f94045d723069cf317453d42375de9ff3c69089cf6466b078ac1db4"}, + {file = "pyinstrument-4.6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab30ae75969da99e9a529e21ff497c18fdf958e822753db4ae7ed1e67094040"}, + {file = "pyinstrument-4.6.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f36cb5b644762fb3c86289324bbef17e95f91cd710603ac19444a47f638e8e96"}, + {file = "pyinstrument-4.6.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8b45075d9dbbc977dbc7007fb22bb0054c6990fbe91bf48dd80c0b96c6307ba7"}, + {file = "pyinstrument-4.6.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:475ac31477f6302e092463896d6a2055f3e6abcd293bad16ff94fc9185308a88"}, + {file = "pyinstrument-4.6.1-cp37-cp37m-win32.whl", hash = "sha256:29172ab3d8609fdf821c3f2562dc61e14f1a8ff5306607c32ca743582d3a760e"}, + {file = "pyinstrument-4.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:bd176f297c99035127b264369d2bb97a65255f65f8d4e843836baf55ebb3cee4"}, + {file = "pyinstrument-4.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:23e9b4526978432e9999021da9a545992cf2ac3df5ee82db7beb6908fc4c978c"}, + {file = "pyinstrument-4.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2dbcaccc9f456ef95557ec501caeb292119c24446d768cb4fb43578b0f3d572c"}, + {file = "pyinstrument-4.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2097f63c66c2bc9678c826b9ff0c25acde3ed455590d9dcac21220673fe74fbf"}, + {file = "pyinstrument-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:205ac2e76bd65d61b9611a9ce03d5f6393e34ec5b41dd38808f25d54e6b3e067"}, + {file = "pyinstrument-4.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f414ddf1161976a40fc0a333000e6a4ad612719eac0b8c9bb73f47153187148"}, + {file = "pyinstrument-4.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:65e62ebfa2cd8fb57eda90006f4505ac4c70da00fc2f05b6d8337d776ea76d41"}, + {file = "pyinstrument-4.6.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d96309df4df10be7b4885797c5f69bb3a89414680ebaec0722d8156fde5268c3"}, + {file = "pyinstrument-4.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f3d1ad3bc8ebb4db925afa706aa865c4bfb40d52509f143491ac0df2440ee5d2"}, + {file = "pyinstrument-4.6.1-cp38-cp38-win32.whl", hash = "sha256:dc37cb988c8854eb42bda2e438aaf553536566657d157c4473cc8aad5692a779"}, + {file = "pyinstrument-4.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:2cd4ce750c34a0318fc2d6c727cc255e9658d12a5cf3f2d0473f1c27157bdaeb"}, + {file = "pyinstrument-4.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ca95b21f022e995e062b371d1f42d901452bcbedd2c02f036de677119503355"}, + {file = "pyinstrument-4.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ac1e1d7e1f1b64054c4eb04eb4869a7a5eef2261440e73943cc1b1bc3c828c18"}, + {file = "pyinstrument-4.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0711845e953fce6ab781221aacffa2a66dbc3289f8343e5babd7b2ea34da6c90"}, + {file = "pyinstrument-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7d28582017de35cb64eb4e4fa603e753095108ca03745f5d17295970ee631f"}, + {file = "pyinstrument-4.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7be57db08bd366a37db3aa3a6187941ee21196e8b14975db337ddc7d1490649d"}, + {file = "pyinstrument-4.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9a0ac0f56860398d2628ce389826ce83fb3a557d0c9a2351e8a2eac6eb869983"}, + {file = "pyinstrument-4.6.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a9045186ff13bc826fef16be53736a85029aae3c6adfe52e666cad00d7ca623b"}, + {file = "pyinstrument-4.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6c4c56b6eab9004e92ad8a48bb54913fdd71fc8a748ae42a27b9e26041646f8b"}, + {file = "pyinstrument-4.6.1-cp39-cp39-win32.whl", hash = "sha256:37e989c44b51839d0c97466fa2b623638b9470d56d79e329f359f0e8fa6d83db"}, + {file = "pyinstrument-4.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:5494c5a84fee4309d7d973366ca6b8b9f8ba1d6b254e93b7c506264ef74f2cef"}, + {file = "pyinstrument-4.6.1.tar.gz", hash = "sha256:f4731b27121350f5a983d358d2272fe3df2f538aed058f57217eef7801a89288"}, ] [package.extras] @@ -1080,13 +1132,13 @@ types = ["typing-extensions"] [[package]] name = "pytest" -version = "7.4.4" +version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] @@ -1138,13 +1190,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "python-dotenv" -version = "1.0.1" +version = "1.0.0" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, + {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, + {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, ] [package.extras] @@ -1201,18 +1253,20 @@ files = [ [[package]] name = "represent" -version = "2.1" +version = "1.6.0.post0" description = "Create __repr__ automatically or declaratively." optional = false -python-versions = ">=3.8" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "Represent-2.1-py3-none-any.whl", hash = "sha256:94fd22d7fec378240c598b20b233f80545ec7eb1131076e2d3d759cee9be2588"}, - {file = "Represent-2.1.tar.gz", hash = "sha256:0b2d015c14e7ba6b3b5e6a7ba131a952013fe944339ac538764ce728a75dbcac"}, + {file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"}, + {file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"}, ] +[package.dependencies] +six = ">=1.8.0" + [package.extras] -docstest = ["furo", "parver", "sphinx"] -test = ["ipython", "pytest", "rich"] +test = ["ipython", "mock", "pytest (>=3.0.5)"] [[package]] name = "respx" @@ -1230,28 +1284,28 @@ httpx = ">=0.21.0" [[package]] name = "ruff" -version = "0.2.1" -description = "An extremely fast Python linter and code formatter, written in Rust." +version = "0.0.284" +description = "An extremely fast Python linter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"}, - {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"}, - {file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"}, - {file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"}, - {file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"}, - {file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"}, + {file = "ruff-0.0.284-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:8b949084941232e2c27f8d12c78c5a6a010927d712ecff17231ee1a8371c205b"}, + {file = "ruff-0.0.284-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:a3930d66b35e4dc96197422381dff2a4e965e9278b5533e71ae8474ef202fab0"}, + {file = "ruff-0.0.284-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d1f7096038961d8bc3b956ee69d73826843eb5b39a5fa4ee717ed473ed69c95"}, + {file = "ruff-0.0.284-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bcaf85907fc905d838f46490ee15f04031927bbea44c478394b0bfdeadc27362"}, + {file = "ruff-0.0.284-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3660b85a9d84162a055f1add334623ae2d8022a84dcd605d61c30a57b436c32"}, + {file = "ruff-0.0.284-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0a3218458b140ea794da72b20ea09cbe13c4c1cdb7ac35e797370354628f4c05"}, + {file = "ruff-0.0.284-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2fe880cff13fffd735387efbcad54ba0ff1272bceea07f86852a33ca71276f4"}, + {file = "ruff-0.0.284-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1d098ea74d0ce31478765d1f8b4fbdbba2efc532397b5c5e8e5ea0c13d7e5ae"}, + {file = "ruff-0.0.284-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c79ae3308e308b94635cd57a369d1e6f146d85019da2fbc63f55da183ee29b"}, + {file = "ruff-0.0.284-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f86b2b1e7033c00de45cc176cf26778650fb8804073a0495aca2f674797becbb"}, + {file = "ruff-0.0.284-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e37e086f4d623c05cd45a6fe5006e77a2b37d57773aad96b7802a6b8ecf9c910"}, + {file = "ruff-0.0.284-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d29dfbe314e1131aa53df213fdfea7ee874dd96ea0dd1471093d93b59498384d"}, + {file = "ruff-0.0.284-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:88295fd649d0aa1f1271441df75bf06266a199497afd239fd392abcfd75acd7e"}, + {file = "ruff-0.0.284-py3-none-win32.whl", hash = "sha256:735cd62fccc577032a367c31f6a9de7c1eb4c01fa9a2e60775067f44f3fc3091"}, + {file = "ruff-0.0.284-py3-none-win_amd64.whl", hash = "sha256:f67ed868d79fbcc61ad0fa034fe6eed2e8d438d32abce9c04b7c4c1464b2cf8e"}, + {file = "ruff-0.0.284-py3-none-win_arm64.whl", hash = "sha256:1292cfc764eeec3cde35b3a31eae3f661d86418b5e220f5d5dba1c27a6eccbb6"}, + {file = "ruff-0.0.284.tar.gz", hash = "sha256:ebd3cc55cd499d326aac17a331deaea29bea206e01c08862f9b5c6e93d77a491"}, ] [[package]] @@ -1445,13 +1499,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] @@ -1475,13 +1529,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "virtualenv" -version = "20.25.0" +version = "20.24.7" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, + {file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, + {file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, ] [package.dependencies] @@ -1495,13 +1549,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "websocket-client" -version = "1.7.0" +version = "1.6.4" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, - {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, + {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"}, + {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"}, ] [package.extras] @@ -1558,4 +1612,4 @@ pgsql = ["psycopg2-binary"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "94a66019b5c9fd191e33aa9c9a2a6a22a2a0db1d60110e858673738738ece902" +content-hash = "7ad5150f23bb8ba229e43b3d329b4ec747791622f5e83357a5fae8aee9315fdc" diff --git a/pyproject.toml b/pyproject.toml index 023e1ceb..4f689213 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,20 +31,22 @@ httpx = {extras = ["socks"], version = "^0.25.1"} bip32 = "^3.4" mnemonic = "^0.20" bolt11 = "^2.0.5" +black = "23.11.0" pre-commit = "^3.5.0" [tool.poetry.extras] pgsql = ["psycopg2-binary"] [tool.poetry.group.dev.dependencies] +mypy = "^1.5.1" +black = "^23.11.0" pytest-asyncio = "^0.21.1" pytest-cov = "^4.0.0" pytest = "^7.4.0" +ruff = "^0.0.284" pre-commit = "^3.3.3" fastapi-profiler = "^1.2.0" respx = "^0.20.2" -ruff = "^0.2.1" -mypy = "^1.8.0" [build-system] requires = ["poetry-core>=1.0.0"] @@ -67,12 +69,10 @@ preview = true [tool.ruff] # Same as Black. but black has a 10% overflow rule line-length = 150 -show-fixes = true -target-version = "py38" -[tool.ruff.lint] +# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. # (`I`) means isorting -extend-select = ["I"] +select = ["E", "F", "I"] ignore = [] # Allow autofix for all enabled rules (when `--fix`) is provided. @@ -81,7 +81,7 @@ unfixable = [] # Exclude a variety of commonly ignored directories. exclude = [ - "cashu/nostr/*", + "cashu/nostr", "cashu/core/bolt11.py", ".bzr", ".direnv", @@ -112,6 +112,6 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" # Assume Python 3.8 # target-version = "py38" -[tool.ruff.lint.mccabe] +[tool.ruff.mccabe] # Unlike Flake8, default to a complexity level of 10. max-complexity = 10 diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 073e6204..59b94849 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -12,20 +12,38 @@ def test_hash_to_curve(): - result = hash_to_curve(bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000")) - assert result.serialize().hex() == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" + result = hash_to_curve( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000000" + ) + ) + assert ( + result.serialize().hex() + == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" + ) - result = hash_to_curve(bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001")) - assert result.serialize().hex() == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" + result = hash_to_curve( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + ) + assert ( + result.serialize().hex() + == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" + ) def test_hash_to_curve_iteration(): """This input causes multiple rounds of the hash_to_curve algorithm.""" - result = hash_to_curve(bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000002")) - assert result.serialize().hex() == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" - - result2 = hash_to_curve(b"\x92g\xd3\xdb\xed\x80)AH?\x1a\xfa*k\xc6\x8d\xe5\xf6S\x12\x8a\xca\x9b\xf1F\x1c]\n:\xd3n\xd2") - assert result2.serialize().hex() == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" + result = hash_to_curve( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000002" + ) + ) + assert ( + result.serialize().hex() + == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" + ) def test_step1(): @@ -33,63 +51,57 @@ def test_step1(): B_, blinding_factor = step1_alice( secret_msg, blinding_factor=PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") # 32 bytes + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) # 32 bytes ), ) - assert B_.serialize().hex() == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" - assert blinding_factor.private_key == bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") - - -def test_step1_collision(): - msg1 = "272120214382734463759987987430654107405" - B1, _ = step1_alice( - msg1, - blinding_factor=PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") # 32 bytes - ), + assert ( + B_.serialize().hex() + == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" ) - B1hex = B1.serialize().hex() - - msg2 = "\x02bae688ad7846ebf38ffa040627d4f12c" - B2, _ = step1_alice( - msg2, - blinding_factor=PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") # 32 bytes - ), + assert blinding_factor.private_key == bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" ) - B2hex = B2.serialize().hex() - assert B1hex != B2hex - - h2c1 = hash_to_curve(msg1.encode("utf-8")) - h2c2 = hash_to_curve(msg2.encode("utf-8")) - assert h2c1.serialize().hex() != h2c2.serialize().hex() - return def test_step2(): B_, _ = step1_alice( "test_message", blinding_factor=PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), raw=True, ), ) a = PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), raw=True, ) C_, e, s = step2_bob(B_, a) - assert C_.serialize().hex() == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + assert ( + C_.serialize().hex() + == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + ) def test_step3(): # C = C_ - A.mult(r) C_ = PublicKey( - bytes.fromhex("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"), + bytes.fromhex( + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + ), raw=True, ) - r = PrivateKey(privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001")) + r = PrivateKey( + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + ) A = PublicKey( pubkey=b"\x02" @@ -100,12 +112,17 @@ def test_step3(): ) C = step3_alice(C_, r, A) - assert C.serialize().hex() == "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" + assert ( + C.serialize().hex() + == "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" + ) def test_dleq_hash_e(): C_ = PublicKey( - bytes.fromhex("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"), + bytes.fromhex( + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + ), raw=True, ) K = PublicKey( @@ -137,45 +154,69 @@ def test_dleq_step2_bob_dleq(): B_, _ = step1_alice( "test_message", blinding_factor=PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), raw=True, ), ) a = PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), raw=True, ) - p_bytes = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") # 32 bytes + p_bytes = bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) # 32 bytes e, s = step2_bob_dleq(B_, a, p_bytes) - assert e.serialize() == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" assert ( - s.serialize() == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" + e.serialize() + == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" + ) + assert ( + s.serialize() + == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" ) # differs from e only in least significant byte because `a = 0x1` # change `a` a = PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000001111"), + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000001111" + ), raw=True, ) e, s = step2_bob_dleq(B_, a, p_bytes) - assert e.serialize() == "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e" - assert s.serialize() == "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 = PrivateKey( - bytes.fromhex("9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9"), + bytes.fromhex( + "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" + ), raw=True, ) # s from test_step2_bob_dleq for a=0x1 s = PrivateKey( - bytes.fromhex("9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da"), + bytes.fromhex( + "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" + ), raw=True, ) a = PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), raw=True, ) A = a.pubkey @@ -188,7 +229,9 @@ def test_dleq_alice_verify_dleq(): # ), # 32 bytes # ) B_ = PublicKey( - bytes.fromhex("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"), + bytes.fromhex( + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + ), raw=True, ) @@ -202,7 +245,9 @@ def test_dleq_alice_verify_dleq(): # C_, e, s = step2_bob(B_, a) C_ = PublicKey( - bytes.fromhex("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2"), + bytes.fromhex( + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + ), raw=True, ) @@ -212,7 +257,9 @@ def test_dleq_alice_verify_dleq(): def test_dleq_alice_direct_verify_dleq(): # ----- test again with B_ and C_ as per step1 and step2 a = PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), raw=True, ) A = a.pubkey @@ -220,7 +267,9 @@ def test_dleq_alice_direct_verify_dleq(): B_, _ = step1_alice( "test_message", blinding_factor=PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), raw=True, ), ) @@ -230,14 +279,18 @@ def test_dleq_alice_direct_verify_dleq(): def test_dleq_carol_varify_from_bob(): a = PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), raw=True, ) A = a.pubkey assert A secret_msg = "test_message" r = PrivateKey( - privkey=bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001"), + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), raw=True, ) B_, _ = step1_alice(secret_msg, r) From 48158cd497be9dc032e09bf4cf2e56790d9d6e02 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 17 Feb 2024 21:43:26 +0100 Subject: [PATCH 35/57] update ruff (#437) * update ruff * test * update ruff * update pyproject --- .github/workflows/checks.yml | 6 +- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 12 +- Makefile | 10 +- poetry.lock | 684 ++++++++++++++++------------------- pyproject.toml | 18 +- 6 files changed, 332 insertions(+), 400 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index d044b185..7bb953d4 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -11,7 +11,7 @@ on: type: string jobs: - formatting: + format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -26,8 +26,8 @@ jobs: cache: "poetry" - name: Install packages run: poetry install - - name: Check black - run: make black-check + - name: Ruff check + run: make ruff-check mypy: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82296e13..2c50f6b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Nutshell CI +name: CI on: push: branches: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cad022a9..8a7d36b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: "^cashu/nostr/.*" repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -12,16 +12,12 @@ repos: - id: debug-statements - id: mixed-line-ending - id: check-case-conflict - # - repo: https://github.com/psf/black - # rev: 23.11.0 - # hooks: - # - id: black - # args: [--line-length=150] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.283 + rev: v0.2.1 hooks: - id: ruff - args: [--fix, --exit-non-zero-on-fix] + args: [--fix] + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.6.0 hooks: diff --git a/Makefile b/Makefile index b9e79292..f6cb065b 100644 --- a/Makefile +++ b/Makefile @@ -4,18 +4,12 @@ ruff: ruff-check: poetry run ruff check . -black: - poetry run black . - -black-check: - poetry run black . --check - mypy: poetry run mypy cashu --check-untyped-defs -format: black ruff +format: ruff -check: black-check ruff-check mypy +check: ruff-check mypy clean: rm -r cashu.egg-info/ || true diff --git a/poetry.lock b/poetry.lock index a901cc7e..ee07069b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -34,21 +34,22 @@ files = [ [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "base58" @@ -102,48 +103,6 @@ files = [ {file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"}, ] -[[package]] -name = "black" -version = "23.11.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, - {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, - {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, - {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, - {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, - {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, - {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, - {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, - {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, - {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, - {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, - {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, - {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, - {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, - {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, - {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, - {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, - {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "bolt11" version = "2.0.5" @@ -165,13 +124,13 @@ secp256k1 = "*" [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -331,63 +290,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.2" +version = "7.4.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, + {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, + {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, + {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, + {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, + {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, + {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, + {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, + {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, + {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, + {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, + {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, + {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, ] [package.dependencies] @@ -398,34 +357,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.5" +version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, - {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, - {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, - {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [package.dependencies] @@ -443,13 +402,13 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "distlib" -version = "0.3.7" +version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[package]] @@ -568,13 +527,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.2" +version = "1.0.3" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, + {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, + {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, ] [package.dependencies] @@ -585,7 +544,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] +trio = ["trio (>=0.22.0,<0.24.0)"] [[package]] name = "httpx" @@ -614,13 +573,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "identify" -version = "2.5.32" +version = "2.5.34" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.32-py2.py3-none-any.whl", hash = "sha256:0b7656ef6cba81664b783352c73f8c24b39cf82f926f78f4550eda928e5e0545"}, - {file = "identify-2.5.32.tar.gz", hash = "sha256:5d9979348ec1a21c768ae07e0a652924538e8bce67313a73cb0f681cf08ba407"}, + {file = "identify-2.5.34-py2.py3-none-any.whl", hash = "sha256:a4316013779e433d08b96e5eabb7f641e6c7942e4ab5d4c509ebd2e7a8994aed"}, + {file = "identify-2.5.34.tar.gz", hash = "sha256:ee17bc9d499899bc9eaec1ac7bf2dc9eedd480db9d88b96d123d3b64a9d34f5d"}, ] [package.extras] @@ -639,20 +598,20 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.8.0" +version = "6.11.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {file = "importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b"}, + {file = "importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] @@ -687,22 +646,22 @@ dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptio [[package]] name = "marshmallow" -version = "3.20.1" +version = "3.20.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, - {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, + {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, + {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] -dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] +dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["pre-commit (>=2.4,<4.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -718,38 +677,38 @@ files = [ [[package]] name = "mypy" -version = "1.7.1" +version = "1.8.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, - {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, - {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, - {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, - {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, - {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, - {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, - {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, - {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, - {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, - {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, - {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, - {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, - {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, - {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, - {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, - {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, - {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, - {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, + {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, + {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, + {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, + {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, + {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, + {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, ] [package.dependencies] @@ -813,41 +772,30 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] -[[package]] -name = "pathspec" -version = "0.11.2" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] - [[package]] name = "platformdirs" -version = "4.0.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, - {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -963,88 +911,88 @@ files = [ [[package]] name = "pycryptodomex" -version = "3.19.0" +version = "3.20.0" description = "Cryptographic library for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodomex-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff64fd720def623bf64d8776f8d0deada1cc1bf1ec3c1f9d6f5bb5bd098d034f"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:61056a1fd3254f6f863de94c233b30dd33bc02f8c935b2000269705f1eeeffa4"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:258c4233a3fe5a6341780306a36c6fb072ef38ce676a6d41eec3e591347919e8"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e45bb4635b3c4e0a00ca9df75ef6295838c85c2ac44ad882410cb631ed1eeaa"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a12144d785518f6491ad334c75ccdc6ad52ea49230b4237f319dbb7cef26f464"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:1789d89f61f70a4cd5483d4dfa8df7032efab1118f8b9894faae03c967707865"}, - {file = "pycryptodomex-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:eb2fc0ec241bf5e5ef56c8fbec4a2634d631e4c4f616a59b567947a0f35ad83c"}, - {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c9a68a2f7bd091ccea54ad3be3e9d65eded813e6d79fdf4cc3604e26cdd6384f"}, - {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8df69e41f7e7015a90b94d1096ec3d8e0182e73449487306709ec27379fff761"}, - {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:917033016ecc23c8933205585a0ab73e20020fdf671b7cd1be788a5c4039840b"}, - {file = "pycryptodomex-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:e8e5ecbd4da4157889fce8ba49da74764dd86c891410bfd6b24969fa46edda51"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:a77b79852175064c822b047fee7cf5a1f434f06ad075cc9986aa1c19a0c53eb0"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5b883e1439ab63af976656446fb4839d566bb096f15fc3c06b5a99cde4927188"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3866d68e2fc345162b1b9b83ef80686acfe5cec0d134337f3b03950a0a8bf56"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74eb1f73f788facece7979ce91594dc177e1a9b5d5e3e64697dd58299e5cb4d"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cb51096a6a8d400724104db8a7e4f2206041a1f23e58924aa3d8d96bcb48338"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a588a1cb7781da9d5e1c84affd98c32aff9c89771eac8eaa659d2760666f7139"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:d4dd3b381ff5a5907a3eb98f5f6d32c64d319a840278ceea1dcfcc65063856f3"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:263de9a96d2fcbc9f5bd3a279f14ea0d5f072adb68ebd324987576ec25da084d"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-win32.whl", hash = "sha256:67c8eb79ab33d0fbcb56842992298ddb56eb6505a72369c20f60bc1d2b6fb002"}, - {file = "pycryptodomex-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:09c9401dc06fb3d94cb1ec23b4ea067a25d1f4c6b7b118ff5631d0b5daaab3cc"}, - {file = "pycryptodomex-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:edbe083c299835de7e02c8aa0885cb904a75087d35e7bab75ebe5ed336e8c3e2"}, - {file = "pycryptodomex-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:136b284e9246b4ccf4f752d435c80f2c44fc2321c198505de1d43a95a3453b3c"}, - {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5d73e9fa3fe830e7b6b42afc49d8329b07a049a47d12e0ef9225f2fd220f19b2"}, - {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b2f1982c5bc311f0aab8c293524b861b485d76f7c9ab2c3ac9a25b6f7655975"}, - {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb040b5dda1dff1e197d2ef71927bd6b8bfcb9793bc4dfe0bb6df1e691eaacb"}, - {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:800a2b05cfb83654df80266692f7092eeefe2a314fa7901dcefab255934faeec"}, - {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c01678aee8ac0c1a461cbc38ad496f953f9efcb1fa19f5637cbeba7544792a53"}, - {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2126bc54beccbede6eade00e647106b4f4c21e5201d2b0a73e9e816a01c50905"}, - {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b801216c48c0886742abf286a9a6b117e248ca144d8ceec1f931ce2dd0c9cb40"}, - {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50cb18d4dd87571006fd2447ccec85e6cec0136632a550aa29226ba075c80644"}, - {file = "pycryptodomex-3.19.0.tar.gz", hash = "sha256:af83a554b3f077564229865c45af0791be008ac6469ef0098152139e6bd4b5b6"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, + {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, + {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, + {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, ] [[package]] name = "pydantic" -version = "1.10.13" +version = "1.10.14" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, - {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, - {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, - {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, - {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, - {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, - {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, - {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, - {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, - {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, + {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, + {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, + {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, + {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, + {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, + {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, + {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, + {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, ] [package.dependencies] @@ -1056,71 +1004,71 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pyinstrument" -version = "4.6.1" +version = "4.6.2" description = "Call stack profiler for Python. Shows you why your code is slow!" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstrument-4.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:73476e4bc6e467ac1b2c3c0dd1f0b71c9061d4de14626676adfdfbb14aa342b4"}, - {file = "pyinstrument-4.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4d1da8efd974cf9df52ee03edaee2d3875105ddd00de35aa542760f7c612bdf7"}, - {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:507be1ee2f2b0c9fba74d622a272640dd6d1b0c9ec3388b2cdeb97ad1e77125f"}, - {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cee6de08eb45754ef4f602ce52b640d1c535d934a6a8733a974daa095def37"}, - {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7873e8cec92321251fdf894a72b3c78f4c5c20afdd1fef0baf9042ec843bb04"}, - {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a242f6cac40bc83e1f3002b6b53681846dfba007f366971db0bf21e02dbb1903"}, - {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:97c9660cdb4bd2a43cf4f3ab52cffd22f3ac9a748d913b750178fb34e5e39e64"}, - {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e304cd0723e2b18ada5e63c187abf6d777949454c734f5974d64a0865859f0f4"}, - {file = "pyinstrument-4.6.1-cp310-cp310-win32.whl", hash = "sha256:cee21a2d78187dd8a80f72f5d0f1ddb767b2d9800f8bb4d94b6d11f217c22cdb"}, - {file = "pyinstrument-4.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:2000712f71d693fed2f8a1c1638d37b7919124f367b37976d07128d49f1445eb"}, - {file = "pyinstrument-4.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a366c6f3dfb11f1739bdc1dee75a01c1563ad0bf4047071e5e77598087df457f"}, - {file = "pyinstrument-4.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6be327be65d934796558aa9cb0f75ce62ebd207d49ad1854610c97b0579ad47"}, - {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e160d9c5d20d3e4ef82269e4e8b246ff09bdf37af5fb8cb8ccca97936d95ad6"}, - {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ffbf56605ef21c2fcb60de2fa74ff81f417d8be0c5002a407e414d6ef6dee43"}, - {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c92cc4924596d6e8f30a16182bbe90893b1572d847ae12652f72b34a9a17c24a"}, - {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f4b48a94d938cae981f6948d9ec603bab2087b178d2095d042d5a48aabaecaab"}, - {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7a386392275bdef4a1849712dc5b74f0023483fca14ef93d0ca27d453548982"}, - {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:871b131b83e9b1122f2325061c68ed1e861eebcb568c934d2fb193652f077f77"}, - {file = "pyinstrument-4.6.1-cp311-cp311-win32.whl", hash = "sha256:8d8515156dd91f5652d13b5fcc87e634f8fe1c07b68d1d0840348cdd50bf5ace"}, - {file = "pyinstrument-4.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb868fbe089036e9f32525a249f4c78b8dc46967612393f204b8234f439c9cc4"}, - {file = "pyinstrument-4.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a18cd234cce4f230f1733807f17a134e64a1f1acabf74a14d27f583cf2b183df"}, - {file = "pyinstrument-4.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:574cfca69150be4ce4461fb224712fbc0722a49b0dc02fa204d02807adf6b5a0"}, - {file = "pyinstrument-4.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e02cf505e932eb8ccf561b7527550a67ec14fcae1fe0e25319b09c9c166e914"}, - {file = "pyinstrument-4.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832fb2acef9d53701c1ab546564c45fb70a8770c816374f8dd11420d399103c9"}, - {file = "pyinstrument-4.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13cb57e9607545623ebe462345b3d0c4caee0125d2d02267043ece8aca8f4ea0"}, - {file = "pyinstrument-4.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9be89e7419bcfe8dd6abb0d959d6d9c439c613a4a873514c43d16b48dae697c9"}, - {file = "pyinstrument-4.6.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:476785cfbc44e8e1b1ad447398aa3deae81a8df4d37eb2d8bbb0c404eff979cd"}, - {file = "pyinstrument-4.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e9cebd90128a3d2fee36d3ccb665c1b9dce75261061b2046203e45c4a8012d54"}, - {file = "pyinstrument-4.6.1-cp312-cp312-win32.whl", hash = "sha256:1d0b76683df2ad5c40eff73607dc5c13828c92fbca36aff1ddf869a3c5a55fa6"}, - {file = "pyinstrument-4.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:c4b7af1d9d6a523cfbfedebcb69202242d5bd0cb89c4e094cc73d5d6e38279bd"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:79ae152f8c6a680a188fb3be5e0f360ac05db5bbf410169a6c40851dfaebcce9"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07cad2745964c174c65aa75f1bf68a4394d1b4d28f33894837cfd315d1e836f0"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb81f66f7f94045d723069cf317453d42375de9ff3c69089cf6466b078ac1db4"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab30ae75969da99e9a529e21ff497c18fdf958e822753db4ae7ed1e67094040"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f36cb5b644762fb3c86289324bbef17e95f91cd710603ac19444a47f638e8e96"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8b45075d9dbbc977dbc7007fb22bb0054c6990fbe91bf48dd80c0b96c6307ba7"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:475ac31477f6302e092463896d6a2055f3e6abcd293bad16ff94fc9185308a88"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-win32.whl", hash = "sha256:29172ab3d8609fdf821c3f2562dc61e14f1a8ff5306607c32ca743582d3a760e"}, - {file = "pyinstrument-4.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:bd176f297c99035127b264369d2bb97a65255f65f8d4e843836baf55ebb3cee4"}, - {file = "pyinstrument-4.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:23e9b4526978432e9999021da9a545992cf2ac3df5ee82db7beb6908fc4c978c"}, - {file = "pyinstrument-4.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2dbcaccc9f456ef95557ec501caeb292119c24446d768cb4fb43578b0f3d572c"}, - {file = "pyinstrument-4.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2097f63c66c2bc9678c826b9ff0c25acde3ed455590d9dcac21220673fe74fbf"}, - {file = "pyinstrument-4.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:205ac2e76bd65d61b9611a9ce03d5f6393e34ec5b41dd38808f25d54e6b3e067"}, - {file = "pyinstrument-4.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f414ddf1161976a40fc0a333000e6a4ad612719eac0b8c9bb73f47153187148"}, - {file = "pyinstrument-4.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:65e62ebfa2cd8fb57eda90006f4505ac4c70da00fc2f05b6d8337d776ea76d41"}, - {file = "pyinstrument-4.6.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d96309df4df10be7b4885797c5f69bb3a89414680ebaec0722d8156fde5268c3"}, - {file = "pyinstrument-4.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f3d1ad3bc8ebb4db925afa706aa865c4bfb40d52509f143491ac0df2440ee5d2"}, - {file = "pyinstrument-4.6.1-cp38-cp38-win32.whl", hash = "sha256:dc37cb988c8854eb42bda2e438aaf553536566657d157c4473cc8aad5692a779"}, - {file = "pyinstrument-4.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:2cd4ce750c34a0318fc2d6c727cc255e9658d12a5cf3f2d0473f1c27157bdaeb"}, - {file = "pyinstrument-4.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ca95b21f022e995e062b371d1f42d901452bcbedd2c02f036de677119503355"}, - {file = "pyinstrument-4.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ac1e1d7e1f1b64054c4eb04eb4869a7a5eef2261440e73943cc1b1bc3c828c18"}, - {file = "pyinstrument-4.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0711845e953fce6ab781221aacffa2a66dbc3289f8343e5babd7b2ea34da6c90"}, - {file = "pyinstrument-4.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7d28582017de35cb64eb4e4fa603e753095108ca03745f5d17295970ee631f"}, - {file = "pyinstrument-4.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7be57db08bd366a37db3aa3a6187941ee21196e8b14975db337ddc7d1490649d"}, - {file = "pyinstrument-4.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9a0ac0f56860398d2628ce389826ce83fb3a557d0c9a2351e8a2eac6eb869983"}, - {file = "pyinstrument-4.6.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a9045186ff13bc826fef16be53736a85029aae3c6adfe52e666cad00d7ca623b"}, - {file = "pyinstrument-4.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6c4c56b6eab9004e92ad8a48bb54913fdd71fc8a748ae42a27b9e26041646f8b"}, - {file = "pyinstrument-4.6.1-cp39-cp39-win32.whl", hash = "sha256:37e989c44b51839d0c97466fa2b623638b9470d56d79e329f359f0e8fa6d83db"}, - {file = "pyinstrument-4.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:5494c5a84fee4309d7d973366ca6b8b9f8ba1d6b254e93b7c506264ef74f2cef"}, - {file = "pyinstrument-4.6.1.tar.gz", hash = "sha256:f4731b27121350f5a983d358d2272fe3df2f538aed058f57217eef7801a89288"}, + {file = "pyinstrument-4.6.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a1b1cd768ea7ea9ab6f5490f7e74431321bcc463e9441dbc2f769617252d9e2"}, + {file = "pyinstrument-4.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8a386b9d09d167451fb2111eaf86aabf6e094fed42c15f62ec51d6980bce7d96"}, + {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23c3e3ca8553b9aac09bd978c73d21b9032c707ac6d803bae6a20ecc048df4a8"}, + {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f329f5534ca069420246f5ce57270d975229bcb92a3a3fd6b2ca086527d9764"}, + {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4dcdcc7ba224a0c5edfbd00b0f530f5aed2b26da5aaa2f9af5519d4aa8c7e41"}, + {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73db0c2c99119c65b075feee76e903b4ed82e59440fe8b5724acf5c7cb24721f"}, + {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:da58f265326f3cf3975366ccb8b39014f1e69ff8327958a089858d71c633d654"}, + {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:feebcf860f955401df30d029ec8de7a0c5515d24ea809736430fd1219686fe14"}, + {file = "pyinstrument-4.6.2-cp310-cp310-win32.whl", hash = "sha256:b2b66ff0b16c8ecf1ec22de001cfff46872b2c163c62429055105564eef50b2e"}, + {file = "pyinstrument-4.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:8d104b7a7899d5fa4c5bf1ceb0c1a070615a72c5dc17bc321b612467ad5c5d88"}, + {file = "pyinstrument-4.6.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:62f6014d2b928b181a52483e7c7b82f2c27e22c577417d1681153e5518f03317"}, + {file = "pyinstrument-4.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dcb5c8d763c5df55131670ba2a01a8aebd0d490a789904a55eb6a8b8d497f110"}, + {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ed4e8c6c84e0e6429ba7008a66e435ede2d8cb027794c20923c55669d9c5633"}, + {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c0f0e1d8f8c70faa90ff57f78ac0dda774b52ea0bfb2d9f0f41ce6f3e7c869e"}, + {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3c44cb037ad0d6e9d9a48c14d856254ada641fbd0ae9de40da045fc2226a2a"}, + {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:be9901f17ac2f527c352f2fdca3d717c1d7f2ce8a70bad5a490fc8cc5d2a6007"}, + {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a9791bf8916c1cf439c202fded32de93354b0f57328f303d71950b0027c7811"}, + {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d6162615e783c59e36f2d7caf903a7e3ecb6b32d4a4ae8907f2760b2ef395bf6"}, + {file = "pyinstrument-4.6.2-cp311-cp311-win32.whl", hash = "sha256:28af084aa84bbfd3620ebe71d5f9a0deca4451267f363738ca824f733de55056"}, + {file = "pyinstrument-4.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:dd6007d3c2e318e09e582435dd8d111cccf30d342af66886b783208813caf3d7"}, + {file = "pyinstrument-4.6.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e3813c8ecfab9d7d855c5f0f71f11793cf1507f40401aa33575c7fd613577c23"}, + {file = "pyinstrument-4.6.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c761372945e60fc1396b7a49f30592e8474e70a558f1a87346d27c8c4ce50f7"}, + {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fba3244e94c117bf4d9b30b8852bbdcd510e7329fdd5c7c8b3799e00a9215a8"}, + {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:803ac64e526473d64283f504df3b0d5c2c203ea9603cab428641538ffdc753a7"}, + {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2e554b1bb0df78f5ce8a92df75b664912ca93aa94208386102af454ec31b647"}, + {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7c671057fad22ee3ded897a6a361204ea2538e44c1233cad0e8e30f6d27f33db"}, + {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d02f31fa13a9e8dc702a113878419deba859563a32474c9f68e04619d43d6f01"}, + {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b55983a884f083f93f0fc6d12ff8df0acd1e2fb0580d2f4c7bfe6def33a84b58"}, + {file = "pyinstrument-4.6.2-cp312-cp312-win32.whl", hash = "sha256:fdc0a53b27e5d8e47147489c7dab596ddd1756b1e053217ef5bc6718567099ff"}, + {file = "pyinstrument-4.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:dd5c53a0159126b5ce7cbc4994433c9c671e057c85297ff32645166a06ad2c50"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b082df0bbf71251a7f4880a12ed28421dba84ea7110bb376e0533067a4eaff40"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90350533396071cb2543affe01e40bf534c35cb0d4b8fa9fdb0f052f9ca2cfe3"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67268bb0d579330cff40fd1c90b8510363ca1a0e7204225840614068658dab77"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20e15b4e1d29ba0b7fc81aac50351e0dc0d7e911e93771ebc3f408e864a2c93b"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e625fc6ffcd4fd420493edd8276179c3f784df207bef4c2192725c1b310534c"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:113d2fc534c9ca7b6b5661d6ada05515bf318f6eb34e8d05860fe49eb7cfe17e"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3098cd72b71a322a72dafeb4ba5c566465e193d2030adad4c09566bd2f89bf4f"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-win32.whl", hash = "sha256:08fdc7f88c989316fa47805234c37a40fafe7b614afd8ae863f0afa9d1707b37"}, + {file = "pyinstrument-4.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5ebeba952c0056dcc9b9355328c78c4b5c2a33b4b4276a9157a3ab589f3d1bac"}, + {file = "pyinstrument-4.6.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:34e59e91c88ec9ad5630c0964eca823949005e97736bfa838beb4789e94912a2"}, + {file = "pyinstrument-4.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cd0320c39e99e3c0a3129d1ed010ac41e5a7eb96fb79900d270080a97962e995"}, + {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46992e855d630575ec635eeca0068a8ddf423d4fd32ea0875a94e9f8688f0b95"}, + {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e474c56da636253dfdca7cd1998b240d6b39f7ed34777362db69224fcf053b1"}, + {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4b559322f30509ad8f082561792352d0805b3edfa508e492a36041fdc009259"}, + {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:06a8578b2943eb1dbbf281e1e59e44246acfefd79e1b06d4950f01b693de12af"}, + {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7bd3da31c46f1c1cb7ae89031725f6a1d1015c2041d9c753fe23980f5f9fd86c"}, + {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e63f4916001aa9c625976a50779282e0a5b5e9b17c52a50ef4c651e468ed5b88"}, + {file = "pyinstrument-4.6.2-cp38-cp38-win32.whl", hash = "sha256:32ec8db6896b94af790a530e1e0edad4d0f941a0ab8dd9073e5993e7ea46af7d"}, + {file = "pyinstrument-4.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:a59fc4f7db738a094823afe6422509fa5816a7bf74e768ce5a7a2ddd91af40ac"}, + {file = "pyinstrument-4.6.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3a165e0d2deb212d4cf439383982a831682009e1b08733c568cac88c89784e62"}, + {file = "pyinstrument-4.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ba858b3d6f6e5597c641edcc0e7e464f85aba86d71bc3b3592cb89897bf43f6"}, + {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fd8e547cf3df5f0ec6e4dffbe2e857f6b28eda51b71c3c0b5a2fc0646527835"}, + {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de2c1714a37a820033b19cf134ead43299a02662f1379140974a9ab733c5f3a"}, + {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01fc45dedceec3df81668d702bca6d400d956c8b8494abc206638c167c78dfd9"}, + {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5b6e161ef268d43ee6bbfae7fd2cdd0a52c099ddd21001c126ca1805dc906539"}, + {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6ba8e368d0421f15ba6366dfd60ec131c1b46505d021477e0f865d26cf35a605"}, + {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edca46f04a573ac2fb11a84b937844e6a109f38f80f4b422222fb5be8ecad8cb"}, + {file = "pyinstrument-4.6.2-cp39-cp39-win32.whl", hash = "sha256:baf375953b02fe94d00e716f060e60211ede73f49512b96687335f7071adb153"}, + {file = "pyinstrument-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:af1a953bce9fd530040895d01ff3de485e25e1576dccb014f76ba9131376fcad"}, + {file = "pyinstrument-4.6.2.tar.gz", hash = "sha256:0002ee517ed8502bbda6eb2bb1ba8f95a55492fcdf03811ba13d4806e50dd7f6"}, ] [package.extras] @@ -1132,13 +1080,13 @@ types = ["typing-extensions"] [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -1190,13 +1138,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "python-dotenv" -version = "1.0.0" +version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] [package.extras] @@ -1253,20 +1201,18 @@ files = [ [[package]] name = "represent" -version = "1.6.0.post0" +version = "2.1" description = "Create __repr__ automatically or declaratively." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"}, - {file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"}, + {file = "Represent-2.1-py3-none-any.whl", hash = "sha256:94fd22d7fec378240c598b20b233f80545ec7eb1131076e2d3d759cee9be2588"}, + {file = "Represent-2.1.tar.gz", hash = "sha256:0b2d015c14e7ba6b3b5e6a7ba131a952013fe944339ac538764ce728a75dbcac"}, ] -[package.dependencies] -six = ">=1.8.0" - [package.extras] -test = ["ipython", "mock", "pytest (>=3.0.5)"] +docstest = ["furo", "parver", "sphinx"] +test = ["ipython", "pytest", "rich"] [[package]] name = "respx" @@ -1284,28 +1230,28 @@ httpx = ">=0.21.0" [[package]] name = "ruff" -version = "0.0.284" -description = "An extremely fast Python linter, written in Rust." +version = "0.2.1" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.284-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:8b949084941232e2c27f8d12c78c5a6a010927d712ecff17231ee1a8371c205b"}, - {file = "ruff-0.0.284-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:a3930d66b35e4dc96197422381dff2a4e965e9278b5533e71ae8474ef202fab0"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d1f7096038961d8bc3b956ee69d73826843eb5b39a5fa4ee717ed473ed69c95"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bcaf85907fc905d838f46490ee15f04031927bbea44c478394b0bfdeadc27362"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3660b85a9d84162a055f1add334623ae2d8022a84dcd605d61c30a57b436c32"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0a3218458b140ea794da72b20ea09cbe13c4c1cdb7ac35e797370354628f4c05"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2fe880cff13fffd735387efbcad54ba0ff1272bceea07f86852a33ca71276f4"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1d098ea74d0ce31478765d1f8b4fbdbba2efc532397b5c5e8e5ea0c13d7e5ae"}, - {file = "ruff-0.0.284-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c79ae3308e308b94635cd57a369d1e6f146d85019da2fbc63f55da183ee29b"}, - {file = "ruff-0.0.284-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f86b2b1e7033c00de45cc176cf26778650fb8804073a0495aca2f674797becbb"}, - {file = "ruff-0.0.284-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e37e086f4d623c05cd45a6fe5006e77a2b37d57773aad96b7802a6b8ecf9c910"}, - {file = "ruff-0.0.284-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d29dfbe314e1131aa53df213fdfea7ee874dd96ea0dd1471093d93b59498384d"}, - {file = "ruff-0.0.284-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:88295fd649d0aa1f1271441df75bf06266a199497afd239fd392abcfd75acd7e"}, - {file = "ruff-0.0.284-py3-none-win32.whl", hash = "sha256:735cd62fccc577032a367c31f6a9de7c1eb4c01fa9a2e60775067f44f3fc3091"}, - {file = "ruff-0.0.284-py3-none-win_amd64.whl", hash = "sha256:f67ed868d79fbcc61ad0fa034fe6eed2e8d438d32abce9c04b7c4c1464b2cf8e"}, - {file = "ruff-0.0.284-py3-none-win_arm64.whl", hash = "sha256:1292cfc764eeec3cde35b3a31eae3f661d86418b5e220f5d5dba1c27a6eccbb6"}, - {file = "ruff-0.0.284.tar.gz", hash = "sha256:ebd3cc55cd499d326aac17a331deaea29bea206e01c08862f9b5c6e93d77a491"}, + {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"}, + {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"}, + {file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"}, + {file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"}, + {file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"}, + {file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"}, + {file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"}, + {file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"}, + {file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"}, + {file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"}, + {file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"}, ] [[package]] @@ -1499,13 +1445,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] @@ -1529,13 +1475,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "virtualenv" -version = "20.24.7" +version = "20.25.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, - {file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, ] [package.dependencies] @@ -1549,13 +1495,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "websocket-client" -version = "1.6.4" +version = "1.7.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"}, - {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"}, + {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, + {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, ] [package.extras] @@ -1612,4 +1558,4 @@ pgsql = ["psycopg2-binary"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "7ad5150f23bb8ba229e43b3d329b4ec747791622f5e83357a5fae8aee9315fdc" +content-hash = "94a66019b5c9fd191e33aa9c9a2a6a22a2a0db1d60110e858673738738ece902" diff --git a/pyproject.toml b/pyproject.toml index 4f689213..71eb9b4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,22 +31,20 @@ httpx = {extras = ["socks"], version = "^0.25.1"} bip32 = "^3.4" mnemonic = "^0.20" bolt11 = "^2.0.5" -black = "23.11.0" pre-commit = "^3.5.0" [tool.poetry.extras] pgsql = ["psycopg2-binary"] [tool.poetry.group.dev.dependencies] -mypy = "^1.5.1" -black = "^23.11.0" pytest-asyncio = "^0.21.1" pytest-cov = "^4.0.0" pytest = "^7.4.0" -ruff = "^0.0.284" pre-commit = "^3.3.3" fastapi-profiler = "^1.2.0" respx = "^0.20.2" +ruff = "^0.2.1" +mypy = "^1.8.0" [build-system] requires = ["poetry-core>=1.0.0"] @@ -69,10 +67,11 @@ preview = true [tool.ruff] # Same as Black. but black has a 10% overflow rule line-length = 150 +show-fixes = true -# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. +[tool.ruff.lint] # (`I`) means isorting -select = ["E", "F", "I"] +extend-select = ["I"] ignore = [] # Allow autofix for all enabled rules (when `--fix`) is provided. @@ -81,7 +80,7 @@ unfixable = [] # Exclude a variety of commonly ignored directories. exclude = [ - "cashu/nostr", + "cashu/nostr/*", "cashu/core/bolt11.py", ".bzr", ".direnv", @@ -109,9 +108,6 @@ exclude = [ # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -# Assume Python 3.8 -# target-version = "py38" - -[tool.ruff.mccabe] +[tool.ruff.lint.mccabe] # Unlike Flake8, default to a complexity level of 10. max-complexity = 10 From ad906df788939ebe7ba1d12428c478cc366a1594 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 18 Feb 2024 12:24:41 +0100 Subject: [PATCH 36/57] Mint: blink fix fee estimation (#439) * blink: fix fee esimation * fix line length * fix line length * fix line length * remove noqa --- cashu/lightning/blink.py | 2 +- cashu/mint/ledger.py | 2 ++ tests/test_mint_lightning_blink.py | 12 ++++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index d4f11e80..24b223df 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -327,7 +327,7 @@ async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: fees_response_msat = int(resp["data"]["lnInvoiceFeeProbe"]["amount"]) * 1000 # we either take fee_msat_response or the BLINK_MAX_FEE_PERCENT, whichever is higher fees_msat = max( - fees_response_msat, math.ceil(amount_msat * BLINK_MAX_FEE_PERCENT) + fees_response_msat, math.ceil(amount_msat / 100 * BLINK_MAX_FEE_PERCENT) ) fees = Amount(unit=Unit.msat, amount=fees_msat) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 1a973643..3782b0cf 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -380,6 +380,7 @@ async def get_mint_quote(self, quote_id: str) -> MintQuote: method = Method[quote.method] if not quote.paid: + assert quote.checking_id, "quote has no checking id" logger.trace(f"Lightning: checking invoice {quote.checking_id}") status: PaymentStatus = await self.backends[method][ unit @@ -482,6 +483,7 @@ async def melt_quote( assert mint_quote.method == method.name, "methods do not match" assert not mint_quote.paid, "mint quote already paid" assert not mint_quote.issued, "mint quote already issued" + assert mint_quote.checking_id, "mint quote has no checking id" payment_quote = PaymentQuoteResponse( checking_id=mint_quote.checking_id, amount=Amount(unit, mint_quote.amount), diff --git a/tests/test_mint_lightning_blink.py b/tests/test_mint_lightning_blink.py index 7339fc2b..3071c69c 100644 --- a/tests/test_mint_lightning_blink.py +++ b/tests/test_mint_lightning_blink.py @@ -49,7 +49,6 @@ async def test_blink_create_invoice(): } } } - respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) invoice = await blink.create_invoice(Amount(Unit.sat, 1000)) assert invoice.checking_id == invoice.payment_request @@ -129,9 +128,18 @@ async def test_blink_get_payment_status(): @respx.mock @pytest.mark.asyncio async def test_blink_get_payment_quote(): + # response says 1 sat fees but invoice * 0.5% is 5 sat so we expect 5 sat + mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 1}}} + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + quote = await blink.get_payment_quote(payment_request) + assert quote.checking_id == payment_request + assert quote.amount == Amount(Unit.msat, 1000000) # msat + assert quote.fee == Amount(Unit.msat, 5000) # msat + + # response says 10 sat fees but invoice * 0.5% is 5 sat so we expect 10 sat mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 10}}} respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) quote = await blink.get_payment_quote(payment_request) assert quote.checking_id == payment_request assert quote.amount == Amount(Unit.msat, 1000000) # msat - assert quote.fee == Amount(Unit.msat, 500000) # msat + assert quote.fee == Amount(Unit.msat, 10000) # msat From 9c3949d197bd92133910e3bcda26c25e679e3d0c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 18 Feb 2024 19:26:10 +0100 Subject: [PATCH 37/57] Mint: fix Blink multiple failed attempts (#440) * fix: blink multiple failed attempts * fix line length again --- cashu/lightning/blink.py | 113 ++++++++++++++++++++++------- cashu/mint/ledger.py | 1 + tests/test_mint_lightning_blink.py | 6 +- 3 files changed, 92 insertions(+), 28 deletions(-) diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index 24b223df..961f4c64 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -24,6 +24,8 @@ # according to https://github.com/GaloyMoney/galoy/blob/7e79cc27304de9b9c2e7d7f4fdd3bac09df23aac/core/api/src/domain/bitcoin/index.ts#L59 BLINK_MAX_FEE_PERCENT = 0.5 +DIRECTION_SEND = "SEND" +DIRECTION_RECEIVE = "RECEIVE" class BlinkWallet(LightningBackend): @@ -35,7 +37,11 @@ class BlinkWallet(LightningBackend): wallet_ids: Dict[Unit, str] = {} endpoint = "https://api.blink.sv/graphql" invoice_statuses = {"PENDING": None, "PAID": True, "EXPIRED": False} - payment_execution_statuses = {"SUCCESS": True, "ALREADY_PAID": None} + payment_execution_statuses = { + "SUCCESS": True, + "ALREADY_PAID": None, + "FAILURE": False, + } payment_statuses = {"SUCCESS": True, "PENDING": None, "FAILURE": False} def __init__(self): @@ -78,10 +84,12 @@ async def status(self) -> StatusResponse: ) balance = 0 - for wallet_dict in resp["data"]["me"]["defaultAccount"]["wallets"]: - if wallet_dict["walletCurrency"] == "USD": + for wallet_dict in ( + resp.get("data", {}).get("me", {}).get("defaultAccount", {}).get("wallets") + ): + if wallet_dict.get("walletCurrency") == "USD": self.wallet_ids[Unit.usd] = wallet_dict["id"] - elif wallet_dict["walletCurrency"] == "BTC": + elif wallet_dict.get("walletCurrency") == "BTC": self.wallet_ids[Unit.sat] = wallet_dict["id"] balance = wallet_dict["balance"] @@ -137,9 +145,13 @@ async def create_invoice( resp = r.json() assert resp, "invalid response" - payment_request = resp["data"]["lnInvoiceCreateOnBehalfOfRecipient"]["invoice"][ - "paymentRequest" - ] + payment_request = ( + resp.get("data", {}) + .get("lnInvoiceCreateOnBehalfOfRecipient", {}) + .get("invoice", {}) + .get("paymentRequest") + ) + assert payment_request, "payment request not found" checking_id = payment_request return InvoiceResponse( @@ -178,6 +190,7 @@ async def pay_invoice( r = await self.client.post( url=self.endpoint, data=json.dumps(data), + timeout=None, ) r.raise_for_status() except Exception as e: @@ -186,9 +199,14 @@ async def pay_invoice( resp: dict = r.json() paid = self.payment_execution_statuses[ - resp["data"]["lnInvoicePaymentSend"]["status"] + resp.get("data", {}).get("lnInvoicePaymentSend", {}).get("status") ] - fee = resp["data"]["lnInvoicePaymentSend"]["transaction"]["settlementFee"] + fee = ( + resp.get("data", {}) + .get("lnInvoicePaymentSend", {}) + .get("transaction", {}) + .get("settlementFee") + ) checking_id = quote.request return PaymentResponse( @@ -221,12 +239,15 @@ async def get_invoice_status(self, checking_id: str) -> PaymentStatus: logger.error(f"Blink API error: {str(e)}") return PaymentStatus(paid=None) resp: dict = r.json() - if resp["data"]["lnInvoicePaymentStatus"]["errors"]: + if resp.get("data", {}).get("lnInvoicePaymentStatus", {}).get("errors"): logger.error( - "Blink Error", resp["data"]["lnInvoicePaymentStatus"]["errors"] + "Blink Error", + resp.get("data", {}).get("lnInvoicePaymentStatus", {}).get("errors"), ) return PaymentStatus(paid=None) - paid = self.invoice_statuses[resp["data"]["lnInvoicePaymentStatus"]["status"]] + paid = self.invoice_statuses[ + resp.get("data", {}).get("lnInvoicePaymentStatus", {}).get("status") + ] return PaymentStatus(paid=paid) async def get_payment_status(self, checking_id: str) -> PaymentStatus: @@ -267,19 +288,49 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus: resp: dict = r.json() # no result found - if not resp["data"]["me"]["defaultAccount"]["walletById"][ - "transactionsByPaymentHash" - ]: + if ( + not resp.get("data", {}) + .get("me", {}) + .get("defaultAccount", {}) + .get("walletById", {}) + .get("transactionsByPaymentHash") + ): return PaymentStatus(paid=None) - paid = self.payment_statuses[ - resp["data"]["me"]["defaultAccount"]["walletById"][ - "transactionsByPaymentHash" - ][0]["status"] - ] - fee = resp["data"]["me"]["defaultAccount"]["walletById"][ - "transactionsByPaymentHash" - ][0]["settlementFee"] + all_payments_with_this_hash = ( + resp.get("data", {}) + .get("me", {}) + .get("defaultAccount", {}) + .get("walletById", {}) + .get("transactionsByPaymentHash") + ) + + # Blink API edge case: for a failed payment attempt, it returns two payments with the same hash + # if there are two payments with the same hash with "direction" == "SEND" and "RECEIVE" + # it means that the payment previously failed and we can ignore the attempt and return + # PaymentStatus(paid=None) + if len(all_payments_with_this_hash) == 2 and all( + p["direction"] in [DIRECTION_SEND, DIRECTION_RECEIVE] + for p in all_payments_with_this_hash + ): + return PaymentStatus(paid=None) + + # if there is only one payment with the same hash, it means that the payment might have succeeded + # we only care about the payment with "direction" == "SEND" + payment = next( + ( + p + for p in all_payments_with_this_hash + if p.get("direction") == DIRECTION_SEND + ), + None, + ) + if not payment: + return PaymentStatus(paid=None) + + # we read the status of the payment + paid = self.payment_statuses[payment["status"]] + fee = payment["settlementFee"] return PaymentStatus( paid=paid, @@ -312,22 +363,30 @@ async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: r = await self.client.post( url=self.endpoint, data=json.dumps(data), + timeout=None, ) r.raise_for_status() except Exception as e: logger.error(f"Blink API error: {str(e)}") - return PaymentResponse(ok=False, error_message=str(e)) + raise e resp: dict = r.json() - + if resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("errors"): + raise Exception( + resp["data"]["lnInvoiceFeeProbe"]["errors"][0].get("message") + or "Unknown error" + ) invoice_obj = decode(bolt11) assert invoice_obj.amount_msat, "invoice has no amount." amount_msat = int(invoice_obj.amount_msat) - fees_response_msat = int(resp["data"]["lnInvoiceFeeProbe"]["amount"]) * 1000 + fees_response_msat = ( + int(resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("amount")) * 1000 + ) # we either take fee_msat_response or the BLINK_MAX_FEE_PERCENT, whichever is higher fees_msat = max( - fees_response_msat, math.ceil(amount_msat / 100 * BLINK_MAX_FEE_PERCENT) + fees_response_msat, + max(math.ceil(amount_msat / 100 * BLINK_MAX_FEE_PERCENT), 1000), ) fees = Amount(unit=Unit.msat, amount=fees_msat) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 3782b0cf..52eb25ad 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -498,6 +498,7 @@ async def melt_quote( payment_quote = await self.backends[method][unit].get_payment_quote( melt_quote.request ) + assert payment_quote.checking_id, "quote has no checking id" quote = MeltQuote( quote=random_hash(), diff --git a/tests/test_mint_lightning_blink.py b/tests/test_mint_lightning_blink.py index 3071c69c..393829ad 100644 --- a/tests/test_mint_lightning_blink.py +++ b/tests/test_mint_lightning_blink.py @@ -110,7 +110,11 @@ async def test_blink_get_payment_status(): "defaultAccount": { "walletById": { "transactionsByPaymentHash": [ - {"status": "SUCCESS", "settlementFee": 10} + { + "status": "SUCCESS", + "settlementFee": 10, + "direction": "SEND", + } ] } } From 03c7c61f45915726ea58ca93b229c8025ab499cd Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 18 Feb 2024 21:15:19 +0100 Subject: [PATCH 38/57] Ruff pre-commit max line length (#441) * test ruff * test * test * ruff * remove line break * remove black configuration --- pyproject.toml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 71eb9b4c..ae10cbe6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,18 +55,8 @@ mint = "cashu.mint.main:main" cashu = "cashu.wallet.cli.cli:cli" wallet-test = "tests.test_wallet:test" - -[tool.black] -line-length = 88 - -# previously experimental-string-processing = true -# this should autoformat string properly but does not work -preview = true - - [tool.ruff] -# Same as Black. but black has a 10% overflow rule -line-length = 150 +line-length = 88 show-fixes = true [tool.ruff.lint] From c4e8618cadbb9a5f5ddf0973ab403303dae3b3a0 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:20:44 +0100 Subject: [PATCH 39/57] Catch CashuErrors (#442) --- cashu/mint/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cashu/mint/app.py b/cashu/mint/app.py index 50058f21..fe7737bb 100644 --- a/cashu/mint/app.py +++ b/cashu/mint/app.py @@ -94,12 +94,13 @@ async def catch_exceptions(request: Request, call_next): except Exception: err_message = e.args[0] if e.args else "Unknown error" - if isinstance(e, CashuError): + if isinstance(e, CashuError) or isinstance(e.args[0], CashuError): logger.error(f"CashuError: {err_message}") + code = e.code if isinstance(e, CashuError) else e.args[0].code # return with cors headers return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, - content={"detail": err_message, "code": e.code}, + content={"detail": err_message, "code": code}, headers=CORS_HEADERS, ) logger.error(f"Exception: {err_message}") From a3145a93d521da53439d52538d0bab6a276bb833 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:26:55 +0100 Subject: [PATCH 40/57] do not chcek melt status before payment attempt (#443) --- cashu/lightning/blink.py | 5 +++-- cashu/mint/ledger.py | 15 +++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index 961f4c64..a26d3f6d 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -287,7 +287,8 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus: return PaymentResponse(ok=False, error_message=str(e)) resp: dict = r.json() - # no result found + + # no result found, this payment has not been attempted before if ( not resp.get("data", {}) .get("me", {}) @@ -305,7 +306,7 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus: .get("transactionsByPaymentHash") ) - # Blink API edge case: for a failed payment attempt, it returns two payments with the same hash + # Blink API edge case: for a previously failed payment attempt, it returns the two payments with the same hash # if there are two payments with the same hash with "direction" == "SEND" and "RECEIVE" # it means that the payment previously failed and we can ignore the attempt and return # PaymentStatus(paid=None) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 52eb25ad..81cb5c38 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -521,15 +521,18 @@ async def melt_quote( expiry=quote.expiry, ) - async def get_melt_quote(self, quote_id: str) -> MeltQuote: + async def get_melt_quote( + self, quote_id: str, check_quote_with_backend: bool = False + ) -> MeltQuote: """Returns a melt quote. - If melt quote is not paid yet, checks with the backend for the state of the payment request. - - If the quote has been paid, updates the melt quote in the database. + If melt quote is not paid yet and `check_quote_with_backend` is set to `True`, + checks with the backend for the state of the payment request. If the backend + says that the quote has been paid, updates the melt quote in the database. Args: quote_id (str): ID of the melt quote. + check_quote_with_backend (bool, optional): Whether to check the state of the payment request with the backend. Defaults to False. Raises: Exception: Quote not found. @@ -549,7 +552,7 @@ async def get_melt_quote(self, quote_id: str) -> MeltQuote: checking_id=melt_quote.checking_id, db=self.db ) - if not melt_quote.paid and not mint_quote: + if not melt_quote.paid and not mint_quote and check_quote_with_backend: logger.trace( "Lightning: checking outgoing Lightning payment" f" {melt_quote.checking_id}" @@ -643,7 +646,7 @@ async def melt( Returns: Tuple[str, List[BlindedMessage]]: Proof of payment and signed outputs for returning overpaid fees to wallet. """ - # get melt quote and check if it is paid + # get melt quote and check if it was already paid melt_quote = await self.get_melt_quote(quote_id=quote) method = Method[melt_quote.method] unit = Unit[melt_quote.unit] From 1e26f235d52b9a8beae328070d0b41c787163ca9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:47:21 +0100 Subject: [PATCH 41/57] Blink: timeout for LN probes (#444) * blink: timeout for ln probes * make MINIMUM_FEE_MSAT a const * format --- cashu/lightning/blink.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index a26d3f6d..eb78ed29 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -24,8 +24,11 @@ # according to https://github.com/GaloyMoney/galoy/blob/7e79cc27304de9b9c2e7d7f4fdd3bac09df23aac/core/api/src/domain/bitcoin/index.ts#L59 BLINK_MAX_FEE_PERCENT = 0.5 + DIRECTION_SEND = "SEND" DIRECTION_RECEIVE = "RECEIVE" +PROBE_FEE_TIMEOUT_SEC = 1 +MINIMUM_FEE_MSAT = 2000 class BlinkWallet(LightningBackend): @@ -360,34 +363,40 @@ async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: "variables": variables, } + fees_response_msat = 0 try: r = await self.client.post( url=self.endpoint, data=json.dumps(data), - timeout=None, + timeout=PROBE_FEE_TIMEOUT_SEC, ) r.raise_for_status() + resp: dict = r.json() + if resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("errors"): + raise Exception( + resp["data"]["lnInvoiceFeeProbe"]["errors"][0].get("message") + or "Unknown error" + ) + + fees_response_msat = ( + int(resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("amount")) + * 1000 + ) + except httpx.ReadTimeout: + pass except Exception as e: logger.error(f"Blink API error: {str(e)}") raise e - resp: dict = r.json() - if resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("errors"): - raise Exception( - resp["data"]["lnInvoiceFeeProbe"]["errors"][0].get("message") - or "Unknown error" - ) + invoice_obj = decode(bolt11) assert invoice_obj.amount_msat, "invoice has no amount." amount_msat = int(invoice_obj.amount_msat) - fees_response_msat = ( - int(resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("amount")) * 1000 - ) - # we either take fee_msat_response or the BLINK_MAX_FEE_PERCENT, whichever is higher + # we take the highest: fee_msat_response, or BLINK_MAX_FEE_PERCENT, or 2000 msat fees_msat = max( fees_response_msat, - max(math.ceil(amount_msat / 100 * BLINK_MAX_FEE_PERCENT), 1000), + max(math.ceil(amount_msat / 100 * BLINK_MAX_FEE_PERCENT), MINIMUM_FEE_MSAT), ) fees = Amount(unit=Unit.msat, amount=fees_msat) From 2189728771e318f6e8431303bc3c7af45ce2db11 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:30:44 +0100 Subject: [PATCH 42/57] blink: round up fees to next satoshi (#445) --- cashu/lightning/blink.py | 14 ++++++++--- tests/test_mint_lightning_blink.py | 39 +++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index eb78ed29..83348d5f 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -393,10 +393,18 @@ async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: amount_msat = int(invoice_obj.amount_msat) - # we take the highest: fee_msat_response, or BLINK_MAX_FEE_PERCENT, or 2000 msat - fees_msat = max( + # we take the highest: fee_msat_response, or BLINK_MAX_FEE_PERCENT, or MINIMUM_FEE_MSAT msat + # Note: fees with BLINK_MAX_FEE_PERCENT are rounded to the nearest 1000 msat + fees_amount_msat: int = ( + math.ceil(amount_msat / 100 * BLINK_MAX_FEE_PERCENT / 1000) * 1000 + ) + + fees_msat: int = max( fees_response_msat, - max(math.ceil(amount_msat / 100 * BLINK_MAX_FEE_PERCENT), MINIMUM_FEE_MSAT), + max( + fees_amount_msat, + MINIMUM_FEE_MSAT, + ), ) fees = Amount(unit=Unit.msat, amount=fees_msat) diff --git a/tests/test_mint_lightning_blink.py b/tests/test_mint_lightning_blink.py index 393829ad..64777d6e 100644 --- a/tests/test_mint_lightning_blink.py +++ b/tests/test_mint_lightning_blink.py @@ -4,7 +4,7 @@ from cashu.core.base import Amount, MeltQuote, Unit from cashu.core.settings import settings -from cashu.lightning.blink import BlinkWallet +from cashu.lightning.blink import MINIMUM_FEE_MSAT, BlinkWallet settings.mint_blink_key = "123" blink = BlinkWallet() @@ -12,6 +12,23 @@ "lnbc10u1pjap7phpp50s9lzr3477j0tvacpfy2ucrs4q0q6cvn232ex7nt2zqxxxj8gxrsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzzsxqrrsss" "p575z0n39w2j7zgnpqtdlrgz9rycner4eptjm3lz363dzylnrm3h4s9qyyssqfz8jglcshnlcf0zkw4qu8fyr564lg59x5al724kms3h6gpuhx9xrfv27tgx3l3u3cyf6" "3r52u0xmac6max8mdupghfzh84t4hfsvrfsqwnuszf" +) # 1000 sat + +payment_request_10k = ( + "lnbc100u1pjaxuyzpp5wn37d3mx38haqs7nd5he4j7pq4r806e6s83jdksxrd77pnanm3zqdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzzsxqrrss" + "sp5ayy0uuhwgy8hwphvy7ptzpg2dfn8vt3vlgsk53rsvj76jvafhujs9qyyssqc8aj03s5au3tgu6pj0rm0ws4a838s8ffe3y3qkj77esh7qmgsz7qlvdlzgj6dvx7tx7" + "zn6k352z85rvdqvlszrevvzakp96a4pvyn2cpgaaks6" +) + +payment_request_4973 = ( + "lnbc49730n1pjaxuxnpp5zw0ry2w2heyuv7wk4r6z38vvgnaudfst0hl2p5xnv0mjkxtavg2qdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzzsxqrr" + "sssp5x8tv2ka0m95hgek25kauw540m0dx727stqqr07l8h37v5283sn5q9qyyssqeevcs6vxcdnerk5w5mwfmntsf8nze7nxrf97dywmga7v0742vhmxtjrulgu3kah4f" + "2r6025j974jpjg4mkqhv2gdls5k7e5cvwdf4wcp3ytsvx" +) +payment_request_1 = ( + "lnbc10n1pjaxujrpp5sqehn6h5p8xpa0c0lvj5vy3a537gxfk5e7h2ate2alfw3y5cm6xqdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzzsxqrrsss" + "p5fkxsvyl0r32mvnhv9cws4rp986v0wjl2lp93zzl8jejnuwzvpynq9qyyssqqmsnatsz87qrgls98c97dfa6l2z3rzg2x6kxmrvpz886rwjylmd56y3qxzfulrq03kkh" + "hwk6r32wes6pjt2zykhnsjn30c6uhuk0wugp3x74al" ) @@ -132,7 +149,7 @@ async def test_blink_get_payment_status(): @respx.mock @pytest.mark.asyncio async def test_blink_get_payment_quote(): - # response says 1 sat fees but invoice * 0.5% is 5 sat so we expect 5 sat + # response says 1 sat fees but invoice (1000 sat) * 0.5% is 5 sat so we expect 5 sat mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 1}}} respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) quote = await blink.get_payment_quote(payment_request) @@ -140,10 +157,26 @@ async def test_blink_get_payment_quote(): assert quote.amount == Amount(Unit.msat, 1000000) # msat assert quote.fee == Amount(Unit.msat, 5000) # msat - # response says 10 sat fees but invoice * 0.5% is 5 sat so we expect 10 sat + # response says 10 sat fees but invoice (1000 sat) * 0.5% is 5 sat so we expect 10 sat mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 10}}} respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) quote = await blink.get_payment_quote(payment_request) assert quote.checking_id == payment_request assert quote.amount == Amount(Unit.msat, 1000000) # msat assert quote.fee == Amount(Unit.msat, 10000) # msat + + # response says 10 sat fees but invoice (4973 sat) * 0.5% is 24.865 sat so we expect 25 sat + mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 10}}} + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + quote = await blink.get_payment_quote(payment_request_4973) + assert quote.checking_id == payment_request_4973 + assert quote.amount == Amount(Unit.msat, 4973000) # msat + assert quote.fee == Amount(Unit.msat, 25000) # msat + + # response says 0 sat fees but invoice (1 sat) * 0.5% is 0.005 sat so we expect MINIMUM_FEE_MSAT/1000 sat + mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 0}}} + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + quote = await blink.get_payment_quote(payment_request_1) + assert quote.checking_id == payment_request_1 + assert quote.amount == Amount(Unit.msat, 1000) # msat + assert quote.fee == Amount(Unit.msat, MINIMUM_FEE_MSAT) # msat From 1c2c01ccfa4b6b728c3ac6341584f8c679251200 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 19 Feb 2024 19:42:06 +0100 Subject: [PATCH 43/57] test blink failed payment (#446) --- cashu/lightning/blink.py | 47 ++++++++++++++++++++---------- cashu/mint/ledger.py | 4 +-- tests/test_mint_lightning_blink.py | 44 ++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index 83348d5f..625419d8 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -2,7 +2,7 @@ import asyncio import json import math -from typing import Dict, Optional +from typing import Dict, Optional, Union import bolt11 import httpx @@ -201,23 +201,36 @@ async def pay_invoice( return PaymentResponse(ok=False, error_message=str(e)) resp: dict = r.json() + + error_message: Union[None, str] = None + fee: Union[None, int] = None + if resp.get("data", {}).get("lnInvoicePaymentSend", {}).get("errors"): + error_message = ( + resp["data"]["lnInvoicePaymentSend"]["errors"][0].get("message") + or "Unknown error" + ) + paid = self.payment_execution_statuses[ resp.get("data", {}).get("lnInvoicePaymentSend", {}).get("status") ] - fee = ( - resp.get("data", {}) - .get("lnInvoicePaymentSend", {}) - .get("transaction", {}) - .get("settlementFee") - ) + if paid is None: + error_message = "Invoice already paid." + + if resp.get("data", {}).get("lnInvoicePaymentSend", {}).get("transaction", {}): + fee = ( + resp.get("data", {}) + .get("lnInvoicePaymentSend", {}) + .get("transaction", {}) + .get("settlementFee") + ) checking_id = quote.request return PaymentResponse( ok=paid, checking_id=checking_id, - fee=Amount(Unit.sat, fee), + fee=Amount(Unit.sat, fee) if fee else None, preimage=None, - error_message="Invoice already paid." if paid is None else None, + error_message=error_message, ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: @@ -373,15 +386,17 @@ async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: r.raise_for_status() resp: dict = r.json() if resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("errors"): - raise Exception( - resp["data"]["lnInvoiceFeeProbe"]["errors"][0].get("message") - or "Unknown error" + # if there was an error, we simply ignore the response and decide the fees ourselves + fees_response_msat = 0 + logger.debug( + f"Blink probe error: {resp['data']['lnInvoiceFeeProbe']['errors'][0].get('message')}" ) - fees_response_msat = ( - int(resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("amount")) - * 1000 - ) + else: + fees_response_msat = ( + int(resp.get("data", {}).get("lnInvoiceFeeProbe", {}).get("amount")) + * 1000 + ) except httpx.ReadTimeout: pass except Exception as e: diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 81cb5c38..ce1ce7b1 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -692,8 +692,8 @@ async def melt( melt_quote, melt_quote.fee_reserve * 1000 ) logger.debug( - f"Melt status: {payment.ok}: preimage: {payment.preimage}," - f" fee: {payment.fee.str() if payment.fee else 0}" + f"Melt – Ok: {payment.ok}: preimage: {payment.preimage}," + f" fee: {payment.fee.str() if payment.fee is not None else 'None'}" ) if not payment.ok: raise LightningError( diff --git a/tests/test_mint_lightning_blink.py b/tests/test_mint_lightning_blink.py index 64777d6e..739af2ee 100644 --- a/tests/test_mint_lightning_blink.py +++ b/tests/test_mint_lightning_blink.py @@ -102,6 +102,38 @@ async def test_blink_pay_invoice(): assert payment.checking_id == payment_request +@respx.mock +@pytest.mark.asyncio +async def test_blink_pay_invoice_failure(): + mock_response = { + "data": { + "lnInvoicePaymentSend": { + "status": "FAILURE", + "errors": [ + {"message": "This is the error", "codee": "ROUTE_FINDING_ERROR"}, + ], + } + } + } + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + quote = MeltQuote( + request=payment_request, + quote="asd", + method="bolt11", + checking_id=payment_request, + unit="sat", + amount=100, + fee_reserve=12, + paid=False, + ) + payment = await blink.pay_invoice(quote, 1000) + assert not payment.ok + assert payment.fee is None + assert payment.error_message + assert "This is the error" in payment.error_message + assert payment.checking_id == payment_request + + @respx.mock @pytest.mark.asyncio async def test_blink_get_invoice_status(): @@ -180,3 +212,15 @@ async def test_blink_get_payment_quote(): assert quote.checking_id == payment_request_1 assert quote.amount == Amount(Unit.msat, 1000) # msat assert quote.fee == Amount(Unit.msat, MINIMUM_FEE_MSAT) # msat + + +@respx.mock +@pytest.mark.asyncio +async def test_blink_get_payment_quote_backend_error(): + # response says error but invoice (1000 sat) * 0.5% is 5 sat so we expect 10 sat + mock_response = {"data": {"lnInvoiceFeeProbe": {"errors": [{"message": "error"}]}}} + respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response)) + quote = await blink.get_payment_quote(payment_request) + assert quote.checking_id == payment_request + assert quote.amount == Amount(Unit.msat, 1000000) # msat + assert quote.fee == Amount(Unit.msat, 5000) # msat From c630fc8c40a0d01dd84d5c163edb41279799d929 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:17:04 +0100 Subject: [PATCH 44/57] blink: return preimage (#447) --- cashu/lightning/blink.py | 20 ++++++++++++++++++-- tests/test_mint_lightning_blink.py | 12 ++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index 625419d8..5379120b 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -223,13 +223,20 @@ async def pay_invoice( .get("transaction", {}) .get("settlementFee") ) + checking_id = quote.request + # we check the payment status to get the preimage + preimage: Union[None, str] = None + payment_status = await self.get_payment_status(checking_id) + if payment_status.paid: + preimage = payment_status.preimage + return PaymentResponse( ok=paid, checking_id=checking_id, fee=Amount(Unit.sat, fee) if fee else None, - preimage=None, + preimage=preimage, error_message=error_message, ) @@ -283,6 +290,14 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus: status direction settlementFee + settlementVia { + ... on SettlementViaIntraLedger { + preImage + } + ... on SettlementViaLn { + preImage + } + } } } } @@ -348,11 +363,12 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus: # we read the status of the payment paid = self.payment_statuses[payment["status"]] fee = payment["settlementFee"] + preimage = payment["settlementVia"].get("preImage") return PaymentStatus( paid=paid, fee=Amount(Unit.sat, fee), - preimage=None, + preimage=preimage, ) async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: diff --git a/tests/test_mint_lightning_blink.py b/tests/test_mint_lightning_blink.py index 739af2ee..224a6358 100644 --- a/tests/test_mint_lightning_blink.py +++ b/tests/test_mint_lightning_blink.py @@ -79,7 +79,12 @@ async def test_blink_pay_invoice(): "data": { "lnInvoicePaymentSend": { "status": "SUCCESS", - "transaction": {"settlementFee": 10}, + "transaction": { + "settlementFee": 10, + "settlementVia": { + "preImage": "123", + }, + }, } } } @@ -163,6 +168,9 @@ async def test_blink_get_payment_status(): "status": "SUCCESS", "settlementFee": 10, "direction": "SEND", + "settlementVia": { + "preImage": "123", + }, } ] } @@ -175,7 +183,7 @@ async def test_blink_get_payment_status(): assert status.paid assert status.fee assert status.fee.amount == 10 - assert status.preimage is None + assert status.preimage == "123" @respx.mock From e2c8f7f694c465b51662782238802c4227d3e56c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:10:50 +0100 Subject: [PATCH 45/57] Add tests for domain separated h2c (#451) * add tests for domain separated h2c * refactor b_dhke and add domain separated test --- cashu/core/crypto/b_dhke.py | 28 ++++++++++--- tests/test_crypto.py | 79 ++++++++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/cashu/core/crypto/b_dhke.py b/cashu/core/crypto/b_dhke.py index 4abf1b77..ad7ba1aa 100644 --- a/cashu/core/crypto/b_dhke.py +++ b/cashu/core/crypto/b_dhke.py @@ -136,12 +136,17 @@ def verify(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: valid = C == Y.mult(a) # type: ignore # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 if not valid: - Y1: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) - return C == Y1.mult(a) # type: ignore + return verify_domain_separated(a, C, secret_msg) # END: BACKWARDS COMPATIBILITY < 0.15.1 return valid +def verify_domain_separated(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: + Y: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) + valid = C == Y.mult(a) # type: ignore + return valid + + def hash_e(*publickeys: PublicKey) -> bytes: e_ = "" for p in publickeys: @@ -197,13 +202,26 @@ def carol_verify_dleq( valid = alice_verify_dleq(B_, C_, e, s, A) # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 if not valid: - Y1: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) - B_1: PublicKey = Y1 + r.pubkey # type: ignore - return alice_verify_dleq(B_1, C_, e, s, A) + return carol_verify_dleq_domain_separated(secret_msg, r, C, e, s, A) # END: BACKWARDS COMPATIBILITY < 0.15.1 return valid +def carol_verify_dleq_domain_separated( + secret_msg: str, + r: PrivateKey, + C: PublicKey, + e: PrivateKey, + s: PrivateKey, + A: PublicKey, +) -> bool: + Y: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) + C_: PublicKey = C + A.mult(r) # type: ignore + B_: PublicKey = Y + r.pubkey # type: ignore + valid = alice_verify_dleq(B_, C_, e, s, A) + return valid + + # Below is a test of a simple positive and negative case # # Alice's keys diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 59b94849..a74e8b56 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -1,9 +1,12 @@ from cashu.core.crypto.b_dhke import ( alice_verify_dleq, carol_verify_dleq, + carol_verify_dleq_domain_separated, hash_e, hash_to_curve, + hash_to_curve_domain_separated, step1_alice, + step1_alice_domain_separated, step2_bob, step2_bob_dleq, step3_alice, @@ -277,7 +280,7 @@ def test_dleq_alice_direct_verify_dleq(): assert alice_verify_dleq(B_, C_, e, s, A) -def test_dleq_carol_varify_from_bob(): +def test_dleq_carol_verify_from_bob(): a = PrivateKey( privkey=bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" @@ -300,3 +303,77 @@ def test_dleq_carol_varify_from_bob(): # 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) + + +# TESTS FOR DOMAIN SEPARATED HASH TO CURVE + + +def test_hash_to_curve_domain_separated(): + result = hash_to_curve_domain_separated( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000000" + ) + ) + assert ( + result.serialize().hex() + == "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725" + ) + + +def test_hash_to_curve_domain_separated_iterative(): + result = hash_to_curve_domain_separated( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + ) + assert ( + result.serialize().hex() + == "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf" + ) + + +def test_step1_domain_separated(): + secret_msg = "test_message" + B_, blinding_factor = step1_alice_domain_separated( + secret_msg, + blinding_factor=PrivateKey( + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) # 32 bytes + ), + ) + + assert ( + B_.serialize().hex() + == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + ) + assert blinding_factor.private_key == bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + + +def test_dleq_carol_verify_from_bob_domain_separated(): + 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_domain_separated(secret_msg, r) + C_, e, s = step2_bob(B_, a) + assert alice_verify_dleq(B_, C_, e, s, 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_domain_separated( + secret_msg=secret_msg, C=C, r=r, e=e, s=s, A=A + ) From ac0ddd57a169e961bf85698d2a4887d3adba0555 Mon Sep 17 00:00:00 2001 From: findingsov <113792100+findingsov@users.noreply.github.com> Date: Wed, 21 Feb 2024 05:13:34 -0500 Subject: [PATCH 46/57] Update .env.example (#450) * Update .env.example Minor comment add suggestions.. * Update .env.example * Update .env.example * Update .env.example * Update .env.example * Update .env.example --------- Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com> --- .env.example | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 2a02071f..e51c570c 100644 --- a/.env.example +++ b/.env.example @@ -56,25 +56,27 @@ MINT_DERIVATION_PATH="m/0'/0'/0'" MINT_DATABASE=data/mint # Lightning -# Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, LNbitsWallet, BlinkWallet, StrikeWallet +# Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, BlinkWallet, LNbitsWallet, StrikeWallet MINT_LIGHTNING_BACKEND=FakeWallet # for use with LNbitsWallet MINT_LNBITS_ENDPOINT=https://legend.lnbits.com MINT_LNBITS_KEY=yourkeyasdasdasd -# LndRestWallet +# Use with LndRestWallet MINT_LND_REST_ENDPOINT=https://127.0.0.1:8086 MINT_LND_REST_CERT="/home/lnd/.lnd/tls.cert" MINT_LND_REST_MACAROON="/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon" -# CoreLightningRestWallet +# Use with CoreLightningRestWallet MINT_CORELIGHTNING_REST_URL=https://localhost:3001 MINT_CORELIGHTNING_REST_MACAROON="./clightning-rest/access.macaroon" MINT_CORELIGHTNING_REST_CERT="./clightning-2-rest/certificate.pem" +# Use with BlinkWallet MINT_BLINK_KEY=blink_abcdefgh +# Use with StrikeWallet MINT_STRIKE_KEY=ABC123 # fee to reserve in percent of the amount From a546a246e2fec9d4fc98777203ea773d123f0868 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:26:46 +0100 Subject: [PATCH 47/57] Tests: add step2 domain separated test (#456) * add step2 domain separated test * add test3 derived from domain separated outputs * Fix comment --- cashu/core/crypto/b_dhke.py | 2 +- tests/test_crypto.py | 54 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/cashu/core/crypto/b_dhke.py b/cashu/core/crypto/b_dhke.py index ad7ba1aa..78b3510f 100644 --- a/cashu/core/crypto/b_dhke.py +++ b/cashu/core/crypto/b_dhke.py @@ -136,7 +136,7 @@ def verify(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: valid = C == Y.mult(a) # type: ignore # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 if not valid: - return verify_domain_separated(a, C, secret_msg) + valid = verify_domain_separated(a, C, secret_msg) # END: BACKWARDS COMPATIBILITY < 0.15.1 return valid diff --git a/tests/test_crypto.py b/tests/test_crypto.py index a74e8b56..279145a6 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -94,6 +94,7 @@ def test_step2(): def test_step3(): # C = C_ - A.mult(r) + # C_ from test_step2 C_ = PublicKey( bytes.fromhex( "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" @@ -352,6 +353,59 @@ def test_step1_domain_separated(): ) +def test_step2_domain_separated(): + B_, _ = step1_alice_domain_separated( + "test_message", + blinding_factor=PrivateKey( + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), + raw=True, + ), + ) + a = PrivateKey( + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), + raw=True, + ) + C_, e, s = step2_bob(B_, a) + assert ( + C_.serialize().hex() + == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + ) + + +def test_step3_domain_separated(): + # C = C_ - A.mult(r) + # C_ from test_step2 + C_ = PublicKey( + bytes.fromhex( + "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + ), + raw=True, + ) + r = PrivateKey( + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + ) + + A = PublicKey( + pubkey=b"\x02" + + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001", + ), + raw=True, + ) + C = step3_alice(C_, r, A) + + assert ( + C.serialize().hex() + == "0271bf0d702dbad86cbe0af3ab2bfba70a0338f22728e412d88a830ed0580b9de4" + ) + + def test_dleq_carol_verify_from_bob_domain_separated(): a = PrivateKey( privkey=bytes.fromhex( From b06d93c5ff3c5ac29937a5eb9c4a2033e446f12f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Feb 2024 00:24:58 +0100 Subject: [PATCH 48/57] Wallet: deprecate old hash to curve (#457) * wallet: deprecate old hash to curve * fix order --- cashu/core/crypto/b_dhke.py | 74 ++++++++++---------- cashu/core/settings.py | 2 +- cashu/wallet/wallet.py | 26 ++++--- tests/test_crypto.py | 133 ++++++++++++++++++++---------------- 4 files changed, 130 insertions(+), 105 deletions(-) diff --git a/cashu/core/crypto/b_dhke.py b/cashu/core/crypto/b_dhke.py index 78b3510f..d098c236 100644 --- a/cashu/core/crypto/b_dhke.py +++ b/cashu/core/crypto/b_dhke.py @@ -55,26 +55,10 @@ 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 - - DOMAIN_SEPARATOR = b"Secp256k1_HashToCurve_Cashu_" -def hash_to_curve_domain_separated(message: bytes) -> PublicKey: +def hash_to_curve(message: bytes) -> PublicKey: """Generates a secp256k1 point from a message. The point is generated by hashing the message with a domain separator and then @@ -110,15 +94,6 @@ def step1_alice( return B_, r -def step1_alice_domain_separated( - secret_msg: str, blinding_factor: Optional[PrivateKey] = None -) -> tuple[PublicKey, PrivateKey]: - Y: PublicKey = hash_to_curve_domain_separated(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 @@ -136,17 +111,11 @@ def verify(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: valid = C == Y.mult(a) # type: ignore # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 if not valid: - valid = verify_domain_separated(a, C, secret_msg) + valid = verify_deprecated(a, C, secret_msg) # END: BACKWARDS COMPATIBILITY < 0.15.1 return valid -def verify_domain_separated(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: - Y: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) - valid = C == Y.mult(a) # type: ignore - return valid - - def hash_e(*publickeys: PublicKey) -> bytes: e_ = "" for p in publickeys: @@ -202,12 +171,45 @@ def carol_verify_dleq( valid = alice_verify_dleq(B_, C_, e, s, A) # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 if not valid: - return carol_verify_dleq_domain_separated(secret_msg, r, C, e, s, A) + return carol_verify_dleq_deprecated(secret_msg, r, C, e, s, A) # END: BACKWARDS COMPATIBILITY < 0.15.1 return valid -def carol_verify_dleq_domain_separated( +# -------- Deprecated hash_to_curve before 0.15.0 -------- + + +def hash_to_curve_deprecated(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_deprecated( + secret_msg: str, blinding_factor: Optional[PrivateKey] = None +) -> tuple[PublicKey, PrivateKey]: + Y: PublicKey = hash_to_curve_deprecated(secret_msg.encode("utf-8")) + r = blinding_factor or PrivateKey() + B_: PublicKey = Y + r.pubkey # type: ignore + return B_, r + + +def verify_deprecated(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: + Y: PublicKey = hash_to_curve_deprecated(secret_msg.encode("utf-8")) + valid = C == Y.mult(a) # type: ignore + return valid + + +def carol_verify_dleq_deprecated( secret_msg: str, r: PrivateKey, C: PublicKey, @@ -215,7 +217,7 @@ def carol_verify_dleq_domain_separated( s: PrivateKey, A: PublicKey, ) -> bool: - Y: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) + Y: PublicKey = hash_to_curve_deprecated(secret_msg.encode("utf-8")) C_: PublicKey = C + A.mult(r) # type: ignore B_: PublicKey = Y + r.pubkey # type: ignore valid = alice_verify_dleq(B_, C_, e, s, A) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 77bf6cb6..d66d6ff7 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -123,7 +123,7 @@ class WalletSettings(CashuSettings): mint_port: int = Field(default=3338) wallet_name: str = Field(default="wallet") wallet_unit: str = Field(default="sat") - wallet_domain_separation: bool = Field(default=False) + wallet_use_deprecated_h2c: bool = Field(default=False) api_port: int = Field(default=4448) api_host: str = Field(default="127.0.0.1") diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index aef3dc13..9dd07b82 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -998,9 +998,11 @@ async def pay_lightning( # NUT-08, the mint will imprint these outputs with a value depending on the # amount of fees we overpaid. n_change_outputs = calculate_number_of_blank_outputs(fee_reserve_sat) - change_secrets, change_rs, change_derivation_paths = ( - await self.generate_n_secrets(n_change_outputs) - ) + ( + change_secrets, + change_rs, + change_derivation_paths, + ) = await self.generate_n_secrets(n_change_outputs) change_outputs, change_rs = self._construct_outputs( n_change_outputs * [1], change_secrets, change_rs ) @@ -1126,14 +1128,15 @@ async def _construct_proofs( C = b_dhke.step3_alice( C_, r, self.keysets[promise.id].public_keys[promise.amount] ) - # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 - if not settings.wallet_domain_separation: + + if not settings.wallet_use_deprecated_h2c: B_, r = b_dhke.step1_alice(secret, r) # recompute B_ for dleq proofs - # END: BACKWARDS COMPATIBILITY < 0.15.1 + # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 else: - B_, r = b_dhke.step1_alice_domain_separated( + B_, r = b_dhke.step1_alice_deprecated( secret, r ) # recompute B_ for dleq proofs + # END: BACKWARDS COMPATIBILITY < 0.15.1 proof = Proof( id=promise.id, @@ -1196,12 +1199,13 @@ def _construct_outputs( rs_ = [None] * len(amounts) if not rs else rs rs_return: List[PrivateKey] = [] for secret, amount, r in zip(secrets, amounts, rs_): - # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 - if not settings.wallet_domain_separation: + if not settings.wallet_use_deprecated_h2c: B_, r = b_dhke.step1_alice(secret, r or None) - # END: BACKWARDS COMPATIBILITY < 0.15.1 + # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 else: - B_, r = b_dhke.step1_alice_domain_separated(secret, r or None) + B_, r = b_dhke.step1_alice_deprecated(secret, r or None) + # END: BACKWARDS COMPATIBILITY < 0.15.1 + rs_return.append(r) output = BlindedMessage( amount=amount, B_=B_.serialize().hex(), id=self.keyset_id diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 279145a6..c031521f 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -1,12 +1,11 @@ from cashu.core.crypto.b_dhke import ( alice_verify_dleq, carol_verify_dleq, - carol_verify_dleq_domain_separated, hash_e, hash_to_curve, - hash_to_curve_domain_separated, + hash_to_curve_deprecated, step1_alice, - step1_alice_domain_separated, + step1_alice_deprecated, step2_bob, step2_bob_dleq, step3_alice, @@ -22,30 +21,19 @@ def test_hash_to_curve(): ) assert ( result.serialize().hex() - == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" - ) - - result = hash_to_curve( - bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) - ) - assert ( - result.serialize().hex() - == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" + == "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725" ) def test_hash_to_curve_iteration(): - """This input causes multiple rounds of the hash_to_curve algorithm.""" result = hash_to_curve( bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000001" ) ) assert ( result.serialize().hex() - == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" + == "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf" ) @@ -62,7 +50,7 @@ def test_step1(): assert ( B_.serialize().hex() - == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" ) assert blinding_factor.private_key == bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" @@ -88,7 +76,7 @@ def test_step2(): C_, e, s = step2_bob(B_, a) assert ( C_.serialize().hex() - == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" ) @@ -97,7 +85,7 @@ def test_step3(): # C_ from test_step2 C_ = PublicKey( bytes.fromhex( - "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" ), raw=True, ) @@ -118,7 +106,7 @@ def test_step3(): assert ( C.serialize().hex() - == "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" + == "0271bf0d702dbad86cbe0af3ab2bfba70a0338f22728e412d88a830ed0580b9de4" ) @@ -176,11 +164,11 @@ def test_dleq_step2_bob_dleq(): e, s = step2_bob_dleq(B_, a, p_bytes) assert ( e.serialize() - == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" + == "a608ae30a54c6d878c706240ee35d4289b68cfe99454bbfa6578b503bce2dbe1" ) assert ( s.serialize() - == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" + == "a608ae30a54c6d878c706240ee35d4289b68cfe99454bbfa6578b503bce2dbe2" ) # differs from e only in least significant byte because `a = 0x1` # change `a` @@ -193,11 +181,11 @@ def test_dleq_step2_bob_dleq(): e, s = step2_bob_dleq(B_, a, p_bytes) assert ( e.serialize() - == "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e" + == "076cbdda4f368053c33056c438df014d1875eb3c8b28120bece74b6d0e6381bb" ) assert ( s.serialize() - == "828404170c86f240c50ae0f5fc17bb6b82612d46b355e046d7cd84b0a3c934a0" + == "b6d41ac1e12415862bf8cace95e5355e9262eab8a11d201dadd3b6e41584ea6e" ) @@ -306,36 +294,47 @@ def test_dleq_carol_verify_from_bob(): assert carol_verify_dleq(secret_msg=secret_msg, C=C, r=r, e=e, s=s, A=A) -# TESTS FOR DOMAIN SEPARATED HASH TO CURVE +# TESTS FOR DEPRECATED HASH TO CURVE -def test_hash_to_curve_domain_separated(): - result = hash_to_curve_domain_separated( +def test_hash_to_curve_deprecated(): + result = hash_to_curve_deprecated( bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000000" ) ) assert ( result.serialize().hex() - == "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725" + == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" ) - -def test_hash_to_curve_domain_separated_iterative(): - result = hash_to_curve_domain_separated( + result = hash_to_curve_deprecated( bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" ) ) assert ( result.serialize().hex() - == "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf" + == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" + ) + + +def test_hash_to_curve_iteration_deprecated(): + """This input causes multiple rounds of the hash_to_curve algorithm.""" + result = hash_to_curve_deprecated( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000002" + ) + ) + assert ( + result.serialize().hex() + == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" ) -def test_step1_domain_separated(): +def test_step1_deprecated(): secret_msg = "test_message" - B_, blinding_factor = step1_alice_domain_separated( + B_, blinding_factor = step1_alice_deprecated( secret_msg, blinding_factor=PrivateKey( privkey=bytes.fromhex( @@ -346,15 +345,15 @@ def test_step1_domain_separated(): assert ( B_.serialize().hex() - == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" ) assert blinding_factor.private_key == bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" ) -def test_step2_domain_separated(): - B_, _ = step1_alice_domain_separated( +def test_step2_deprecated(): + B_, _ = step1_alice_deprecated( "test_message", blinding_factor=PrivateKey( privkey=bytes.fromhex( @@ -372,16 +371,16 @@ def test_step2_domain_separated(): C_, e, s = step2_bob(B_, a) assert ( C_.serialize().hex() - == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" ) -def test_step3_domain_separated(): +def test_step3_deprecated(): # C = C_ - A.mult(r) - # C_ from test_step2 + # C_ from test_step2_deprecated C_ = PublicKey( bytes.fromhex( - "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" ), raw=True, ) @@ -402,32 +401,52 @@ def test_step3_domain_separated(): assert ( C.serialize().hex() - == "0271bf0d702dbad86cbe0af3ab2bfba70a0338f22728e412d88a830ed0580b9de4" + == "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" ) -def test_dleq_carol_verify_from_bob_domain_separated(): +def test_dleq_step2_bob_dleq_deprecated(): + B_, _ = step1_alice_deprecated( + "test_message", + blinding_factor=PrivateKey( + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), + raw=True, + ), + ) a = PrivateKey( privkey=bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" ), raw=True, ) - A = a.pubkey - assert A - secret_msg = "test_message" - r = PrivateKey( + p_bytes = bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) # 32 bytes + e, s = step2_bob_dleq(B_, a, p_bytes) + assert ( + e.serialize() + == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" + ) + assert ( + s.serialize() + == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" + ) # differs from e only in least significant byte because `a = 0x1` + + # change `a` + a = PrivateKey( privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000001111" ), raw=True, ) - B_, _ = step1_alice_domain_separated(secret_msg, r) - C_, e, s = step2_bob(B_, a) - assert alice_verify_dleq(B_, C_, e, s, 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_domain_separated( - secret_msg=secret_msg, C=C, r=r, e=e, s=s, A=A + e, s = step2_bob_dleq(B_, a, p_bytes) + assert ( + e.serialize() + == "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e" + ) + assert ( + s.serialize() + == "828404170c86f240c50ae0f5fc17bb6b82612d46b355e046d7cd84b0a3c934a0" ) From be7a8a744fead90a87f71c1f9cac4b27c8ce5665 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Feb 2024 00:30:42 +0100 Subject: [PATCH 49/57] Revert "Wallet: deprecate old hash to curve (#457)" (#458) This reverts commit b06d93c5ff3c5ac29937a5eb9c4a2033e446f12f. --- cashu/core/crypto/b_dhke.py | 74 ++++++++++---------- cashu/core/settings.py | 2 +- cashu/wallet/wallet.py | 26 +++---- tests/test_crypto.py | 133 ++++++++++++++++-------------------- 4 files changed, 105 insertions(+), 130 deletions(-) diff --git a/cashu/core/crypto/b_dhke.py b/cashu/core/crypto/b_dhke.py index d098c236..78b3510f 100644 --- a/cashu/core/crypto/b_dhke.py +++ b/cashu/core/crypto/b_dhke.py @@ -55,10 +55,26 @@ 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 + + DOMAIN_SEPARATOR = b"Secp256k1_HashToCurve_Cashu_" -def hash_to_curve(message: bytes) -> PublicKey: +def hash_to_curve_domain_separated(message: bytes) -> PublicKey: """Generates a secp256k1 point from a message. The point is generated by hashing the message with a domain separator and then @@ -94,6 +110,15 @@ def step1_alice( return B_, r +def step1_alice_domain_separated( + secret_msg: str, blinding_factor: Optional[PrivateKey] = None +) -> tuple[PublicKey, PrivateKey]: + Y: PublicKey = hash_to_curve_domain_separated(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 @@ -111,11 +136,17 @@ def verify(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: valid = C == Y.mult(a) # type: ignore # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 if not valid: - valid = verify_deprecated(a, C, secret_msg) + valid = verify_domain_separated(a, C, secret_msg) # END: BACKWARDS COMPATIBILITY < 0.15.1 return valid +def verify_domain_separated(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: + Y: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) + valid = C == Y.mult(a) # type: ignore + return valid + + def hash_e(*publickeys: PublicKey) -> bytes: e_ = "" for p in publickeys: @@ -171,45 +202,12 @@ def carol_verify_dleq( valid = alice_verify_dleq(B_, C_, e, s, A) # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 if not valid: - return carol_verify_dleq_deprecated(secret_msg, r, C, e, s, A) + return carol_verify_dleq_domain_separated(secret_msg, r, C, e, s, A) # END: BACKWARDS COMPATIBILITY < 0.15.1 return valid -# -------- Deprecated hash_to_curve before 0.15.0 -------- - - -def hash_to_curve_deprecated(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_deprecated( - secret_msg: str, blinding_factor: Optional[PrivateKey] = None -) -> tuple[PublicKey, PrivateKey]: - Y: PublicKey = hash_to_curve_deprecated(secret_msg.encode("utf-8")) - r = blinding_factor or PrivateKey() - B_: PublicKey = Y + r.pubkey # type: ignore - return B_, r - - -def verify_deprecated(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: - Y: PublicKey = hash_to_curve_deprecated(secret_msg.encode("utf-8")) - valid = C == Y.mult(a) # type: ignore - return valid - - -def carol_verify_dleq_deprecated( +def carol_verify_dleq_domain_separated( secret_msg: str, r: PrivateKey, C: PublicKey, @@ -217,7 +215,7 @@ def carol_verify_dleq_deprecated( s: PrivateKey, A: PublicKey, ) -> bool: - Y: PublicKey = hash_to_curve_deprecated(secret_msg.encode("utf-8")) + Y: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) C_: PublicKey = C + A.mult(r) # type: ignore B_: PublicKey = Y + r.pubkey # type: ignore valid = alice_verify_dleq(B_, C_, e, s, A) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index d66d6ff7..77bf6cb6 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -123,7 +123,7 @@ class WalletSettings(CashuSettings): mint_port: int = Field(default=3338) wallet_name: str = Field(default="wallet") wallet_unit: str = Field(default="sat") - wallet_use_deprecated_h2c: bool = Field(default=False) + wallet_domain_separation: bool = Field(default=False) api_port: int = Field(default=4448) api_host: str = Field(default="127.0.0.1") diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 9dd07b82..aef3dc13 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -998,11 +998,9 @@ async def pay_lightning( # NUT-08, the mint will imprint these outputs with a value depending on the # amount of fees we overpaid. n_change_outputs = calculate_number_of_blank_outputs(fee_reserve_sat) - ( - change_secrets, - change_rs, - change_derivation_paths, - ) = await self.generate_n_secrets(n_change_outputs) + change_secrets, change_rs, change_derivation_paths = ( + await self.generate_n_secrets(n_change_outputs) + ) change_outputs, change_rs = self._construct_outputs( n_change_outputs * [1], change_secrets, change_rs ) @@ -1128,15 +1126,14 @@ async def _construct_proofs( C = b_dhke.step3_alice( C_, r, self.keysets[promise.id].public_keys[promise.amount] ) - - if not settings.wallet_use_deprecated_h2c: - B_, r = b_dhke.step1_alice(secret, r) # recompute B_ for dleq proofs # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 + if not settings.wallet_domain_separation: + B_, r = b_dhke.step1_alice(secret, r) # recompute B_ for dleq proofs + # END: BACKWARDS COMPATIBILITY < 0.15.1 else: - B_, r = b_dhke.step1_alice_deprecated( + B_, r = b_dhke.step1_alice_domain_separated( secret, r ) # recompute B_ for dleq proofs - # END: BACKWARDS COMPATIBILITY < 0.15.1 proof = Proof( id=promise.id, @@ -1199,13 +1196,12 @@ def _construct_outputs( rs_ = [None] * len(amounts) if not rs else rs rs_return: List[PrivateKey] = [] for secret, amount, r in zip(secrets, amounts, rs_): - if not settings.wallet_use_deprecated_h2c: - B_, r = b_dhke.step1_alice(secret, r or None) # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 - else: - B_, r = b_dhke.step1_alice_deprecated(secret, r or None) + if not settings.wallet_domain_separation: + B_, r = b_dhke.step1_alice(secret, r or None) # END: BACKWARDS COMPATIBILITY < 0.15.1 - + else: + B_, r = b_dhke.step1_alice_domain_separated(secret, r or None) rs_return.append(r) output = BlindedMessage( amount=amount, B_=B_.serialize().hex(), id=self.keyset_id diff --git a/tests/test_crypto.py b/tests/test_crypto.py index c031521f..279145a6 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -1,11 +1,12 @@ from cashu.core.crypto.b_dhke import ( alice_verify_dleq, carol_verify_dleq, + carol_verify_dleq_domain_separated, hash_e, hash_to_curve, - hash_to_curve_deprecated, + hash_to_curve_domain_separated, step1_alice, - step1_alice_deprecated, + step1_alice_domain_separated, step2_bob, step2_bob_dleq, step3_alice, @@ -21,19 +22,30 @@ def test_hash_to_curve(): ) assert ( result.serialize().hex() - == "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725" + == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" + ) + + result = hash_to_curve( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + ) + assert ( + result.serialize().hex() + == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" ) def test_hash_to_curve_iteration(): + """This input causes multiple rounds of the hash_to_curve algorithm.""" result = hash_to_curve( bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002" ) ) assert ( result.serialize().hex() - == "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf" + == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" ) @@ -50,7 +62,7 @@ def test_step1(): assert ( B_.serialize().hex() - == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" ) assert blinding_factor.private_key == bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" @@ -76,7 +88,7 @@ def test_step2(): C_, e, s = step2_bob(B_, a) assert ( C_.serialize().hex() - == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" ) @@ -85,7 +97,7 @@ def test_step3(): # C_ from test_step2 C_ = PublicKey( bytes.fromhex( - "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" ), raw=True, ) @@ -106,7 +118,7 @@ def test_step3(): assert ( C.serialize().hex() - == "0271bf0d702dbad86cbe0af3ab2bfba70a0338f22728e412d88a830ed0580b9de4" + == "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" ) @@ -164,11 +176,11 @@ def test_dleq_step2_bob_dleq(): e, s = step2_bob_dleq(B_, a, p_bytes) assert ( e.serialize() - == "a608ae30a54c6d878c706240ee35d4289b68cfe99454bbfa6578b503bce2dbe1" + == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" ) assert ( s.serialize() - == "a608ae30a54c6d878c706240ee35d4289b68cfe99454bbfa6578b503bce2dbe2" + == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" ) # differs from e only in least significant byte because `a = 0x1` # change `a` @@ -181,11 +193,11 @@ def test_dleq_step2_bob_dleq(): e, s = step2_bob_dleq(B_, a, p_bytes) assert ( e.serialize() - == "076cbdda4f368053c33056c438df014d1875eb3c8b28120bece74b6d0e6381bb" + == "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e" ) assert ( s.serialize() - == "b6d41ac1e12415862bf8cace95e5355e9262eab8a11d201dadd3b6e41584ea6e" + == "828404170c86f240c50ae0f5fc17bb6b82612d46b355e046d7cd84b0a3c934a0" ) @@ -294,47 +306,36 @@ def test_dleq_carol_verify_from_bob(): assert carol_verify_dleq(secret_msg=secret_msg, C=C, r=r, e=e, s=s, A=A) -# TESTS FOR DEPRECATED HASH TO CURVE +# TESTS FOR DOMAIN SEPARATED HASH TO CURVE -def test_hash_to_curve_deprecated(): - result = hash_to_curve_deprecated( +def test_hash_to_curve_domain_separated(): + result = hash_to_curve_domain_separated( bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000000" ) ) assert ( result.serialize().hex() - == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" - ) - - result = hash_to_curve_deprecated( - bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) - ) - assert ( - result.serialize().hex() - == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" + == "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725" ) -def test_hash_to_curve_iteration_deprecated(): - """This input causes multiple rounds of the hash_to_curve algorithm.""" - result = hash_to_curve_deprecated( +def test_hash_to_curve_domain_separated_iterative(): + result = hash_to_curve_domain_separated( bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000001" ) ) assert ( result.serialize().hex() - == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" + == "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf" ) -def test_step1_deprecated(): +def test_step1_domain_separated(): secret_msg = "test_message" - B_, blinding_factor = step1_alice_deprecated( + B_, blinding_factor = step1_alice_domain_separated( secret_msg, blinding_factor=PrivateKey( privkey=bytes.fromhex( @@ -345,15 +346,15 @@ def test_step1_deprecated(): assert ( B_.serialize().hex() - == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" ) assert blinding_factor.private_key == bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" ) -def test_step2_deprecated(): - B_, _ = step1_alice_deprecated( +def test_step2_domain_separated(): + B_, _ = step1_alice_domain_separated( "test_message", blinding_factor=PrivateKey( privkey=bytes.fromhex( @@ -371,16 +372,16 @@ def test_step2_deprecated(): C_, e, s = step2_bob(B_, a) assert ( C_.serialize().hex() - == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" ) -def test_step3_deprecated(): +def test_step3_domain_separated(): # C = C_ - A.mult(r) - # C_ from test_step2_deprecated + # C_ from test_step2 C_ = PublicKey( bytes.fromhex( - "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" ), raw=True, ) @@ -401,52 +402,32 @@ def test_step3_deprecated(): assert ( C.serialize().hex() - == "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" + == "0271bf0d702dbad86cbe0af3ab2bfba70a0338f22728e412d88a830ed0580b9de4" ) -def test_dleq_step2_bob_dleq_deprecated(): - B_, _ = step1_alice_deprecated( - "test_message", - blinding_factor=PrivateKey( - privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ), - raw=True, - ), - ) +def test_dleq_carol_verify_from_bob_domain_separated(): a = PrivateKey( privkey=bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" ), raw=True, ) - p_bytes = bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) # 32 bytes - e, s = step2_bob_dleq(B_, a, p_bytes) - assert ( - e.serialize() - == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" - ) - assert ( - s.serialize() - == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" - ) # differs from e only in least significant byte because `a = 0x1` - - # change `a` - a = PrivateKey( + A = a.pubkey + assert A + secret_msg = "test_message" + r = PrivateKey( privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000001111" + "0000000000000000000000000000000000000000000000000000000000000001" ), raw=True, ) - e, s = step2_bob_dleq(B_, a, p_bytes) - assert ( - e.serialize() - == "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e" - ) - assert ( - s.serialize() - == "828404170c86f240c50ae0f5fc17bb6b82612d46b355e046d7cd84b0a3c934a0" + B_, _ = step1_alice_domain_separated(secret_msg, r) + C_, e, s = step2_bob(B_, a) + assert alice_verify_dleq(B_, C_, e, s, 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_domain_separated( + secret_msg=secret_msg, C=C, r=r, e=e, s=s, A=A ) From a77b7dd5e81d65e7cc7cde4d86e9377c3b3eebb6 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Feb 2024 01:25:05 +0100 Subject: [PATCH 50/57] Wallet: fix nostr receive (#460) * fix wallet nostr receive * add log * add more relays and print info nicer --- cashu/core/settings.py | 8 +++++++- cashu/wallet/cli/cli.py | 45 ++++++++++++++++++++++++----------------- cashu/wallet/nostr.py | 10 ++++----- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 77bf6cb6..178eb437 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -132,9 +132,15 @@ class WalletSettings(CashuSettings): default=[ "wss://nostr-pub.wellorder.net", "wss://relay.damus.io", - "wss://nostr.zebedee.cloud", + "wss://nostr.mom", "wss://relay.snort.social", "wss://nostr.fmt.wiz.biz", + "wss://relay.minibits.cash", + "wss://nos.lol", + "wss://relay.nostr.band", + "wss://relay.bitcoiner.social", + "wss://140.f7z.io", + "wss://relayable.org", ] ) diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index b2010b86..b9b3ad19 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -512,6 +512,11 @@ async def receive_cli( await receive(wallet, tokenObj) elif nostr: await receive_nostr(wallet) + # exit on keypress + print("Press any key to exit.") + click.getchar() + print("Exiting.") + os._exit(0) elif all: reserved_proofs = await get_reserved_proofs(wallet.db) if len(reserved_proofs): @@ -762,25 +767,11 @@ async def info(ctx: Context, mint: bool, mnemonic: bool): if settings.debug: print(f"Debug: {settings.debug}") print(f"Cashu dir: {settings.cashu_dir}") - if settings.env_file: - print(f"Settings: {settings.env_file}") - if settings.tor: - print(f"Tor enabled: {settings.tor}") - if settings.nostr_private_key: - try: - client = NostrClient(private_key=settings.nostr_private_key, connect=False) - print(f"Nostr public key: {client.public_key.bech32()}") - print(f"Nostr relays: {settings.nostr_relays}") - except Exception: - print("Nostr: Error. Invalid key.") - if settings.socks_proxy: - print(f"Socks proxy: {settings.socks_proxy}") - if settings.http_proxy: - print(f"HTTP proxy: {settings.http_proxy}") mint_list = await list_mints(wallet) - print(f"Mint URLs: {mint_list}") - if mint: - for mint_url in mint_list: + print("Mints:") + for mint_url in mint_list: + print(f" - {mint_url}") + if mint: wallet.url = mint_url try: mint_info: dict = (await wallet._load_mint_info()).dict() @@ -805,13 +796,29 @@ async def info(ctx: Context, mint: bool, mnemonic: bool): "Supported NUTS:" f" {', '.join(['NUT-'+str(k) for k in mint_info['nuts'].keys()])}" ) + print("") except Exception as e: print("") print(f"Error fetching mint information for {mint_url}: {e}") if mnemonic: assert wallet.mnemonic - print(f"Mnemonic: {wallet.mnemonic}") + print(f"Mnemonic:\n - {wallet.mnemonic}") + if settings.env_file: + print(f"Settings: {settings.env_file}") + if settings.tor: + print(f"Tor enabled: {settings.tor}") + if settings.nostr_private_key: + try: + client = NostrClient(private_key=settings.nostr_private_key, connect=False) + print(f"Nostr public key: {client.public_key.bech32()}") + print(f"Nostr relays: {', '.join(settings.nostr_relays)}") + except Exception: + print("Nostr: Error. Invalid key.") + if settings.socks_proxy: + print(f"Socks proxy: {settings.socks_proxy}") + if settings.http_proxy: + print(f"HTTP proxy: {settings.http_proxy}") return diff --git a/cashu/wallet/nostr.py b/cashu/wallet/nostr.py index 56dfe73f..72b1a60e 100644 --- a/cashu/wallet/nostr.py +++ b/cashu/wallet/nostr.py @@ -111,7 +111,7 @@ async def receive_nostr( private_key=settings.nostr_private_key, relays=settings.nostr_relays ) print(f"Your nostr public key: {client.public_key.bech32()}") - # print(f"Your nostr private key (do not share!): {client.private_key.bech32()}") + await asyncio.sleep(2) def get_token_callback(event: Event, decrypted_content: str): @@ -126,10 +126,6 @@ def get_token_callback(event: Event, decrypted_content: str): words = decrypted_content.split(" ") for w in words: try: - logger.trace( - "Nostr: setting last check timestamp to" - f" {event.created_at} ({date_str})" - ) # call the receive method tokenObj: TokenV3 = deserialize_token_from_string(w) print( @@ -143,6 +139,10 @@ def get_token_callback(event: Event, decrypted_content: str): tokenObj, ) ) + logger.trace( + "Nostr: setting last check timestamp to" + f" {event.created_at} ({date_str})" + ) asyncio.run( set_nostr_last_check_timestamp( timestamp=event.created_at, db=wallet.db From cfebf70a0a446ba8dc8b3234d852ec0e777f2487 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Feb 2024 01:49:51 +0100 Subject: [PATCH 51/57] Wallet: fix nostr receive cli linebreak prints (#462) --- cashu/wallet/cli/cli.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index b9b3ad19..10251477 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -513,8 +513,7 @@ async def receive_cli( elif nostr: await receive_nostr(wallet) # exit on keypress - print("Press any key to exit.") - click.getchar() + input("Enter any text to exit.") print("Exiting.") os._exit(0) elif all: From 53cd8ff0169019a862da7891ab62d1062875f53e Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Feb 2024 01:50:37 +0100 Subject: [PATCH 52/57] bump version to 0.15.1 (#461) --- cashu/core/settings.py | 2 +- pyproject.toml | 2 +- requirements.txt | 49 +++++++++++++++++++++++++----------------- setup.py | 2 +- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 178eb437..fefe9b2f 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -8,7 +8,7 @@ env = Env() -VERSION = "0.15.0" +VERSION = "0.15.1" def find_env_file(): diff --git a/pyproject.toml b/pyproject.toml index ae10cbe6..917a8dba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cashu" -version = "0.15.0" +version = "0.15.1" description = "Ecash wallet and mint" authors = ["calle "] license = "MIT" diff --git a/requirements.txt b/requirements.txt index 610e9f62..d972e9dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,36 +1,44 @@ -anyio==4.0.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +anyio==3.7.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" asn1crypto==1.5.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -attrs==23.1.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +attrs==23.2.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" base58==2.1.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" bech32==1.2.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" bip32==3.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" bitstring==3.1.9 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" bolt11==2.0.5 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -certifi==2023.7.22 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +certifi==2024.2.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" cffi==1.16.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +cfgv==3.4.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" click==8.1.7 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" coincurve==18.0.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -colorama==0.4.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and platform_system == "Windows" or python_full_version >= "3.8.1" and python_full_version < "4.0.0" and sys_platform == "win32" -cryptography==41.0.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +colorama==0.4.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and (platform_system == "Windows" or sys_platform == "win32") +cryptography==41.0.7 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +distlib==0.3.8 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" ecdsa==0.18.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" environs==9.5.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -exceptiongroup==1.1.3 ; python_full_version >= "3.8.1" and python_version < "3.11" -fastapi==0.103.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +exceptiongroup==1.2.0 ; python_full_version >= "3.8.1" and python_version < "3.11" +fastapi==0.104.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +filelock==3.13.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" h11==0.14.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -httpcore==0.18.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -httpx[socks]==0.25.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -idna==3.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -importlib-metadata==6.8.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +httpcore==1.0.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +httpx[socks]==0.25.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +identify==2.5.34 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +idna==3.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +importlib-metadata==6.11.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" loguru==0.7.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -marshmallow==3.20.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +marshmallow==3.20.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" mnemonic==0.20 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -outcome==1.2.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +nodeenv==1.8.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +outcome==1.3.0.post0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" packaging==23.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +platformdirs==4.2.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +pre-commit==3.5.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" pycparser==2.21 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -pycryptodomex==3.19.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -pydantic==1.10.13 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -python-dotenv==1.0.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -represent==1.6.0.post0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +pycryptodomex==3.20.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +pydantic==1.10.14 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +python-dotenv==1.0.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +pyyaml==6.0.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +represent==2.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" secp256k1==0.14.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" setuptools==68.2.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" six==1.16.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" @@ -39,9 +47,10 @@ socksio==1.0.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0 sqlalchemy-aio==0.17.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" sqlalchemy==1.3.24 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" starlette==0.27.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -typing-extensions==4.8.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +typing-extensions==4.9.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" uvicorn==0.23.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -websocket-client==1.6.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" -wheel==0.41.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +virtualenv==20.25.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +websocket-client==1.7.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" +wheel==0.41.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" win32-setctime==1.1.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and sys_platform == "win32" zipp==3.17.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" diff --git a/setup.py b/setup.py index 52574dc7..f8bac914 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setuptools.setup( name="cashu", - version="0.15.0", + version="0.15.1", description="Ecash wallet and mint", long_description=long_description, long_description_content_type="text/markdown", From 29be002495d9a84926f5ff0e622fb5fd2c462bf8 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:07:13 +0100 Subject: [PATCH 53/57] Wallet: deprecate old h2c (#459) * wallet: deprecate old hash to curve * fix order * added migration: untested * recompute Y always --- cashu/core/base.py | 22 +++--- cashu/core/crypto/b_dhke.py | 74 ++++++++++---------- cashu/core/settings.py | 2 +- cashu/mint/migrations.py | 124 ++++++++++++++++++++++++--------- cashu/wallet/wallet.py | 26 ++++--- tests/test_crypto.py | 133 ++++++++++++++++++++---------------- 6 files changed, 232 insertions(+), 149 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index b32933b2..d9f1dc0a 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -101,17 +101,16 @@ class Proof(BaseModel): time_created: Union[None, str] = "" time_reserved: Union[None, str] = "" derivation_path: Union[None, str] = "" # derivation path of the proof - mint_id: Union[None, str] = ( - None # holds the id of the mint operation that created this proof - ) - melt_id: Union[None, str] = ( - None # holds the id of the melt operation that destroyed this proof - ) + mint_id: Union[ + None, str + ] = None # holds the id of the mint operation that created this proof + melt_id: Union[ + None, str + ] = None # holds the id of the melt operation that destroyed this proof def __init__(self, **data): super().__init__(**data) - if not self.Y: - self.Y = hash_to_curve(self.secret.encode("utf-8")).serialize().hex() + self.Y = hash_to_curve(self.secret.encode("utf-8")).serialize().hex() @classmethod def from_dict(cls, proof_dict: dict): @@ -274,7 +273,6 @@ class MintQuote(BaseModel): @classmethod def from_row(cls, row: Row): - try: # SQLITE: row is timestamp (string) created_time = int(row["created_time"]) if row["created_time"] else None @@ -664,9 +662,9 @@ def __init__( self.id = id def serialize(self): - return json.dumps({ - amount: key.serialize().hex() for amount, key in self.public_keys.items() - }) + return json.dumps( + {amount: key.serialize().hex() for amount, key in self.public_keys.items()} + ) @classmethod def from_row(cls, row: Row): diff --git a/cashu/core/crypto/b_dhke.py b/cashu/core/crypto/b_dhke.py index 78b3510f..d098c236 100644 --- a/cashu/core/crypto/b_dhke.py +++ b/cashu/core/crypto/b_dhke.py @@ -55,26 +55,10 @@ 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 - - DOMAIN_SEPARATOR = b"Secp256k1_HashToCurve_Cashu_" -def hash_to_curve_domain_separated(message: bytes) -> PublicKey: +def hash_to_curve(message: bytes) -> PublicKey: """Generates a secp256k1 point from a message. The point is generated by hashing the message with a domain separator and then @@ -110,15 +94,6 @@ def step1_alice( return B_, r -def step1_alice_domain_separated( - secret_msg: str, blinding_factor: Optional[PrivateKey] = None -) -> tuple[PublicKey, PrivateKey]: - Y: PublicKey = hash_to_curve_domain_separated(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 @@ -136,17 +111,11 @@ def verify(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: valid = C == Y.mult(a) # type: ignore # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 if not valid: - valid = verify_domain_separated(a, C, secret_msg) + valid = verify_deprecated(a, C, secret_msg) # END: BACKWARDS COMPATIBILITY < 0.15.1 return valid -def verify_domain_separated(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: - Y: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) - valid = C == Y.mult(a) # type: ignore - return valid - - def hash_e(*publickeys: PublicKey) -> bytes: e_ = "" for p in publickeys: @@ -202,12 +171,45 @@ def carol_verify_dleq( valid = alice_verify_dleq(B_, C_, e, s, A) # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 if not valid: - return carol_verify_dleq_domain_separated(secret_msg, r, C, e, s, A) + return carol_verify_dleq_deprecated(secret_msg, r, C, e, s, A) # END: BACKWARDS COMPATIBILITY < 0.15.1 return valid -def carol_verify_dleq_domain_separated( +# -------- Deprecated hash_to_curve before 0.15.0 -------- + + +def hash_to_curve_deprecated(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_deprecated( + secret_msg: str, blinding_factor: Optional[PrivateKey] = None +) -> tuple[PublicKey, PrivateKey]: + Y: PublicKey = hash_to_curve_deprecated(secret_msg.encode("utf-8")) + r = blinding_factor or PrivateKey() + B_: PublicKey = Y + r.pubkey # type: ignore + return B_, r + + +def verify_deprecated(a: PrivateKey, C: PublicKey, secret_msg: str) -> bool: + Y: PublicKey = hash_to_curve_deprecated(secret_msg.encode("utf-8")) + valid = C == Y.mult(a) # type: ignore + return valid + + +def carol_verify_dleq_deprecated( secret_msg: str, r: PrivateKey, C: PublicKey, @@ -215,7 +217,7 @@ def carol_verify_dleq_domain_separated( s: PrivateKey, A: PublicKey, ) -> bool: - Y: PublicKey = hash_to_curve_domain_separated(secret_msg.encode("utf-8")) + Y: PublicKey = hash_to_curve_deprecated(secret_msg.encode("utf-8")) C_: PublicKey = C + A.mult(r) # type: ignore B_: PublicKey = Y + r.pubkey # type: ignore valid = alice_verify_dleq(B_, C_, e, s, A) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index fefe9b2f..3fcda9df 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -123,7 +123,7 @@ class WalletSettings(CashuSettings): mint_port: int = Field(default=3338) wallet_name: str = Field(default="wallet") wallet_unit: str = Field(default="sat") - wallet_domain_separation: bool = Field(default=False) + wallet_use_deprecated_h2c: bool = Field(default=False) api_port: int = Field(default=4448) api_host: str = Field(default="127.0.0.1") diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 90323507..f215b2a2 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -4,17 +4,20 @@ async def m000_create_migrations_table(conn: Connection): - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(conn, 'dbversions')} ( db TEXT PRIMARY KEY, version INT NOT NULL ) - """) + """ + ) async def m001_initial(db: Database): async with db.connect() as conn: - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'promises')} ( amount {db.big_int} NOT NULL, B_b TEXT NOT NULL, @@ -23,9 +26,11 @@ async def m001_initial(db: Database): UNIQUE (B_b) ); - """) + """ + ) - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_used')} ( amount {db.big_int} NOT NULL, C TEXT NOT NULL, @@ -34,9 +39,11 @@ async def m001_initial(db: Database): UNIQUE (secret) ); - """) + """ + ) - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'invoices')} ( amount {db.big_int} NOT NULL, pr TEXT NOT NULL, @@ -46,7 +53,8 @@ async def m001_initial(db: Database): UNIQUE (hash) ); - """) + """ + ) async def drop_balance_views(db: Database, conn: Connection): @@ -58,32 +66,38 @@ async def drop_balance_views(db: Database, conn: Connection): async def create_balance_views(db: Database, conn: Connection): - await conn.execute(f""" + await conn.execute( + f""" CREATE VIEW {table_with_schema(db, 'balance_issued')} AS SELECT COALESCE(SUM(s), 0) AS balance FROM ( SELECT SUM(amount) AS s FROM {table_with_schema(db, 'promises')} WHERE amount > 0 ) AS balance_issued; - """) + """ + ) - await conn.execute(f""" + await conn.execute( + f""" CREATE VIEW {table_with_schema(db, 'balance_redeemed')} AS SELECT COALESCE(SUM(s), 0) AS balance FROM ( SELECT SUM(amount) AS s FROM {table_with_schema(db, 'proofs_used')} WHERE amount > 0 ) AS balance_redeemed; - """) + """ + ) - await conn.execute(f""" + await conn.execute( + f""" CREATE VIEW {table_with_schema(db, 'balance')} AS SELECT s_issued - s_used FROM ( SELECT bi.balance AS s_issued, bu.balance AS s_used FROM {table_with_schema(db, 'balance_issued')} bi CROSS JOIN {table_with_schema(db, 'balance_redeemed')} bu ) AS balance; - """) + """ + ) async def m002_add_balance_views(db: Database): @@ -96,7 +110,8 @@ async def m003_mint_keysets(db: Database): Stores mint keysets from different mints and epochs. """ async with db.connect() as conn: - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} ( id TEXT NOT NULL, derivation_path TEXT, @@ -108,8 +123,10 @@ async def m003_mint_keysets(db: Database): UNIQUE (derivation_path) ); - """) - await conn.execute(f""" + """ + ) + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'mint_pubkeys')} ( id TEXT NOT NULL, amount INTEGER NOT NULL, @@ -118,7 +135,8 @@ async def m003_mint_keysets(db: Database): UNIQUE (id, pubkey) ); - """) + """ + ) async def m004_keysets_add_version(db: Database): @@ -136,7 +154,8 @@ async def m005_pending_proofs_table(db: Database) -> None: Store pending proofs. """ async with db.connect() as conn: - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_pending')} ( amount INTEGER NOT NULL, C TEXT NOT NULL, @@ -145,7 +164,8 @@ async def m005_pending_proofs_table(db: Database) -> None: UNIQUE (secret) ); - """) + """ + ) async def m006_invoices_add_payment_hash(db: Database): @@ -255,7 +275,8 @@ async def m011_add_quote_tables(db: Database): f" '{settings.mint_private_key}', unit = 'sat'" ) - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'mint_quotes')} ( quote TEXT NOT NULL, method TEXT NOT NULL, @@ -271,9 +292,11 @@ async def m011_add_quote_tables(db: Database): UNIQUE (quote) ); - """) + """ + ) - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'melt_quotes')} ( quote TEXT NOT NULL, method TEXT NOT NULL, @@ -291,7 +314,8 @@ async def m011_add_quote_tables(db: Database): UNIQUE (quote) ); - """) + """ + ) await conn.execute( f"INSERT INTO {table_with_schema(db, 'mint_quotes')} (quote, method," @@ -318,7 +342,8 @@ async def m012_keysets_uniqueness_with_seed(db: Database): f" SELECT * FROM {table_with_schema(db, 'keysets')}" ) await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets')}") - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} ( id TEXT NOT NULL, derivation_path TEXT, @@ -333,7 +358,8 @@ async def m012_keysets_uniqueness_with_seed(db: Database): UNIQUE (seed, derivation_path) ); - """) + """ + ) await conn.execute( f"INSERT INTO {table_with_schema(db, 'keysets')} (id," " derivation_path, valid_from, valid_to, first_seen," @@ -358,7 +384,8 @@ async def m013_keysets_add_encrypted_seed(db: Database): f" SELECT * FROM {table_with_schema(db, 'keysets')}" ) await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets')}") - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} ( id TEXT NOT NULL, derivation_path TEXT, @@ -373,7 +400,8 @@ async def m013_keysets_add_encrypted_seed(db: Database): UNIQUE (id) ); - """) + """ + ) await conn.execute( f"INSERT INTO {table_with_schema(db, 'keysets')} (id," " derivation_path, valid_from, valid_to, first_seen," @@ -430,7 +458,8 @@ async def m014_proofs_add_Y_column(db: Database): f" SELECT * FROM {table_with_schema(db, 'proofs_used')}" ) await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_used')}") - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_used')} ( amount INTEGER NOT NULL, C TEXT NOT NULL, @@ -443,7 +472,8 @@ async def m014_proofs_add_Y_column(db: Database): UNIQUE (Y) ); - """) + """ + ) await conn.execute( f"INSERT INTO {table_with_schema(db, 'proofs_used')} (amount, C, " "secret, id, Y, created, witness) SELECT amount, C, secret, id, Y," @@ -474,7 +504,8 @@ async def m014_proofs_add_Y_column(db: Database): ) await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_pending')}") - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_pending')} ( amount INTEGER NOT NULL, C TEXT NOT NULL, @@ -486,7 +517,8 @@ async def m014_proofs_add_Y_column(db: Database): UNIQUE (Y) ); - """) + """ + ) await conn.execute( f"INSERT INTO {table_with_schema(db, 'proofs_pending')} (amount, C, " "secret, Y, id, created) SELECT amount, C, secret, Y, id, created" @@ -507,3 +539,31 @@ async def m015_add_index_Y_to_proofs_used(db: Database): " proofs_used_Y_idx ON" f" {table_with_schema(db, 'proofs_used')} (Y)" ) + + +async def m016_recompute_Y_with_new_h2c(db: Database): + # get all proofs_used and proofs_pending from the database and compute Y for each of them + async with db.connect() as conn: + rows = await conn.fetchall( + f"SELECT * FROM {table_with_schema(db, 'proofs_used')}" + ) + # Proof() will compute Y from secret upon initialization + proofs_used = [Proof(**r) for r in rows] + + rows = await conn.fetchall( + f"SELECT * FROM {table_with_schema(db, 'proofs_pending')}" + ) + proofs_pending = [Proof(**r) for r in rows] + + # overwrite the old Y columns with the new Y + async with db.connect() as conn: + for proof in proofs_used: + await conn.execute( + f"UPDATE {table_with_schema(db, 'proofs_used')} SET Y = '{proof.Y}'" + f" WHERE secret = '{proof.secret}'" + ) + for proof in proofs_pending: + await conn.execute( + f"UPDATE {table_with_schema(db, 'proofs_pending')} SET Y = '{proof.Y}'" + f" WHERE secret = '{proof.secret}'" + ) diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index aef3dc13..9dd07b82 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -998,9 +998,11 @@ async def pay_lightning( # NUT-08, the mint will imprint these outputs with a value depending on the # amount of fees we overpaid. n_change_outputs = calculate_number_of_blank_outputs(fee_reserve_sat) - change_secrets, change_rs, change_derivation_paths = ( - await self.generate_n_secrets(n_change_outputs) - ) + ( + change_secrets, + change_rs, + change_derivation_paths, + ) = await self.generate_n_secrets(n_change_outputs) change_outputs, change_rs = self._construct_outputs( n_change_outputs * [1], change_secrets, change_rs ) @@ -1126,14 +1128,15 @@ async def _construct_proofs( C = b_dhke.step3_alice( C_, r, self.keysets[promise.id].public_keys[promise.amount] ) - # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 - if not settings.wallet_domain_separation: + + if not settings.wallet_use_deprecated_h2c: B_, r = b_dhke.step1_alice(secret, r) # recompute B_ for dleq proofs - # END: BACKWARDS COMPATIBILITY < 0.15.1 + # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 else: - B_, r = b_dhke.step1_alice_domain_separated( + B_, r = b_dhke.step1_alice_deprecated( secret, r ) # recompute B_ for dleq proofs + # END: BACKWARDS COMPATIBILITY < 0.15.1 proof = Proof( id=promise.id, @@ -1196,12 +1199,13 @@ def _construct_outputs( rs_ = [None] * len(amounts) if not rs else rs rs_return: List[PrivateKey] = [] for secret, amount, r in zip(secrets, amounts, rs_): - # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 - if not settings.wallet_domain_separation: + if not settings.wallet_use_deprecated_h2c: B_, r = b_dhke.step1_alice(secret, r or None) - # END: BACKWARDS COMPATIBILITY < 0.15.1 + # BEGIN: BACKWARDS COMPATIBILITY < 0.15.1 else: - B_, r = b_dhke.step1_alice_domain_separated(secret, r or None) + B_, r = b_dhke.step1_alice_deprecated(secret, r or None) + # END: BACKWARDS COMPATIBILITY < 0.15.1 + rs_return.append(r) output = BlindedMessage( amount=amount, B_=B_.serialize().hex(), id=self.keyset_id diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 279145a6..c031521f 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -1,12 +1,11 @@ from cashu.core.crypto.b_dhke import ( alice_verify_dleq, carol_verify_dleq, - carol_verify_dleq_domain_separated, hash_e, hash_to_curve, - hash_to_curve_domain_separated, + hash_to_curve_deprecated, step1_alice, - step1_alice_domain_separated, + step1_alice_deprecated, step2_bob, step2_bob_dleq, step3_alice, @@ -22,30 +21,19 @@ def test_hash_to_curve(): ) assert ( result.serialize().hex() - == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" - ) - - result = hash_to_curve( - bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) - ) - assert ( - result.serialize().hex() - == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" + == "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725" ) def test_hash_to_curve_iteration(): - """This input causes multiple rounds of the hash_to_curve algorithm.""" result = hash_to_curve( bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000001" ) ) assert ( result.serialize().hex() - == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" + == "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf" ) @@ -62,7 +50,7 @@ def test_step1(): assert ( B_.serialize().hex() - == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" ) assert blinding_factor.private_key == bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" @@ -88,7 +76,7 @@ def test_step2(): C_, e, s = step2_bob(B_, a) assert ( C_.serialize().hex() - == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" ) @@ -97,7 +85,7 @@ def test_step3(): # C_ from test_step2 C_ = PublicKey( bytes.fromhex( - "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" + "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" ), raw=True, ) @@ -118,7 +106,7 @@ def test_step3(): assert ( C.serialize().hex() - == "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" + == "0271bf0d702dbad86cbe0af3ab2bfba70a0338f22728e412d88a830ed0580b9de4" ) @@ -176,11 +164,11 @@ def test_dleq_step2_bob_dleq(): e, s = step2_bob_dleq(B_, a, p_bytes) assert ( e.serialize() - == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" + == "a608ae30a54c6d878c706240ee35d4289b68cfe99454bbfa6578b503bce2dbe1" ) assert ( s.serialize() - == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" + == "a608ae30a54c6d878c706240ee35d4289b68cfe99454bbfa6578b503bce2dbe2" ) # differs from e only in least significant byte because `a = 0x1` # change `a` @@ -193,11 +181,11 @@ def test_dleq_step2_bob_dleq(): e, s = step2_bob_dleq(B_, a, p_bytes) assert ( e.serialize() - == "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e" + == "076cbdda4f368053c33056c438df014d1875eb3c8b28120bece74b6d0e6381bb" ) assert ( s.serialize() - == "828404170c86f240c50ae0f5fc17bb6b82612d46b355e046d7cd84b0a3c934a0" + == "b6d41ac1e12415862bf8cace95e5355e9262eab8a11d201dadd3b6e41584ea6e" ) @@ -306,36 +294,47 @@ def test_dleq_carol_verify_from_bob(): assert carol_verify_dleq(secret_msg=secret_msg, C=C, r=r, e=e, s=s, A=A) -# TESTS FOR DOMAIN SEPARATED HASH TO CURVE +# TESTS FOR DEPRECATED HASH TO CURVE -def test_hash_to_curve_domain_separated(): - result = hash_to_curve_domain_separated( +def test_hash_to_curve_deprecated(): + result = hash_to_curve_deprecated( bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000000" ) ) assert ( result.serialize().hex() - == "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725" + == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" ) - -def test_hash_to_curve_domain_separated_iterative(): - result = hash_to_curve_domain_separated( + result = hash_to_curve_deprecated( bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" ) ) assert ( result.serialize().hex() - == "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf" + == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" + ) + + +def test_hash_to_curve_iteration_deprecated(): + """This input causes multiple rounds of the hash_to_curve algorithm.""" + result = hash_to_curve_deprecated( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000002" + ) + ) + assert ( + result.serialize().hex() + == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" ) -def test_step1_domain_separated(): +def test_step1_deprecated(): secret_msg = "test_message" - B_, blinding_factor = step1_alice_domain_separated( + B_, blinding_factor = step1_alice_deprecated( secret_msg, blinding_factor=PrivateKey( privkey=bytes.fromhex( @@ -346,15 +345,15 @@ def test_step1_domain_separated(): assert ( B_.serialize().hex() - == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" ) assert blinding_factor.private_key == bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" ) -def test_step2_domain_separated(): - B_, _ = step1_alice_domain_separated( +def test_step2_deprecated(): + B_, _ = step1_alice_deprecated( "test_message", blinding_factor=PrivateKey( privkey=bytes.fromhex( @@ -372,16 +371,16 @@ def test_step2_domain_separated(): C_, e, s = step2_bob(B_, a) assert ( C_.serialize().hex() - == "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + == "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" ) -def test_step3_domain_separated(): +def test_step3_deprecated(): # C = C_ - A.mult(r) - # C_ from test_step2 + # C_ from test_step2_deprecated C_ = PublicKey( bytes.fromhex( - "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b" + "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2" ), raw=True, ) @@ -402,32 +401,52 @@ def test_step3_domain_separated(): assert ( C.serialize().hex() - == "0271bf0d702dbad86cbe0af3ab2bfba70a0338f22728e412d88a830ed0580b9de4" + == "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd" ) -def test_dleq_carol_verify_from_bob_domain_separated(): +def test_dleq_step2_bob_dleq_deprecated(): + B_, _ = step1_alice_deprecated( + "test_message", + blinding_factor=PrivateKey( + privkey=bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ), + raw=True, + ), + ) a = PrivateKey( privkey=bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" ), raw=True, ) - A = a.pubkey - assert A - secret_msg = "test_message" - r = PrivateKey( + p_bytes = bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) # 32 bytes + e, s = step2_bob_dleq(B_, a, p_bytes) + assert ( + e.serialize() + == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9" + ) + assert ( + s.serialize() + == "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da" + ) # differs from e only in least significant byte because `a = 0x1` + + # change `a` + a = PrivateKey( privkey=bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000001111" ), raw=True, ) - B_, _ = step1_alice_domain_separated(secret_msg, r) - C_, e, s = step2_bob(B_, a) - assert alice_verify_dleq(B_, C_, e, s, 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_domain_separated( - secret_msg=secret_msg, C=C, r=r, e=e, s=s, A=A + e, s = step2_bob_dleq(B_, a, p_bytes) + assert ( + e.serialize() + == "df1984d5c22f7e17afe33b8669f02f530f286ae3b00a1978edaf900f4721f65e" + ) + assert ( + s.serialize() + == "828404170c86f240c50ae0f5fc17bb6b82612d46b355e046d7cd84b0a3c934a0" ) From 72573865ad675d36ff1263cdd3004e79ef9c23fa Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 3 Mar 2024 12:20:59 +0100 Subject: [PATCH 54/57] Add checkout latest tag (#465) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4d9806b3..fb6aa6b4 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ source ~/.bashrc # install cashu git clone https://github.com/cashubtc/nutshell.git cashu cd cashu +git checkout pyenv local 3.10.4 poetry install ``` From ff1e7597d1777761dc5e9fb8efa732a919af3e0d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:34:51 +0100 Subject: [PATCH 55/57] Merge both h2c tests and add tests for deterministic blinding factor generation (#466) --- cashu/core/settings.py | 2 +- tests/test_crypto.py | 12 +++++++++--- tests/test_wallet_restore.py | 36 +++++++++++++++++++++++++++++------- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 3fcda9df..a27fddac 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -50,7 +50,7 @@ class EnvSettings(CashuSettings): class MintSettings(CashuSettings): mint_private_key: str = Field(default=None) - mint_seed_decryption_key: str = Field(default=None) + mint_seed_decryption_key: Optional[str] = Field(default=None) mint_derivation_path: str = Field(default="m/0'/0'/0'") mint_derivation_path_list: List[str] = Field(default=[]) mint_listen_host: str = Field(default="127.0.0.1") diff --git a/tests/test_crypto.py b/tests/test_crypto.py index c031521f..7a403a6b 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -23,9 +23,6 @@ def test_hash_to_curve(): result.serialize().hex() == "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725" ) - - -def test_hash_to_curve_iteration(): result = hash_to_curve( bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000001" @@ -35,6 +32,15 @@ def test_hash_to_curve_iteration(): result.serialize().hex() == "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf" ) + result = hash_to_curve( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000002" + ) + ) + assert ( + result.serialize().hex() + == "026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f" + ) def test_step1(): diff --git a/tests/test_wallet_restore.py b/tests/test_wallet_restore.py index 76b3c7e6..7670b5c2 100644 --- a/tests/test_wallet_restore.py +++ b/tests/test_wallet_restore.py @@ -110,6 +110,14 @@ async def test_bump_secret_derivation(wallet3: Wallet): "59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf", "576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0", ] + assert [r.private_key.hex() for r in rs1 if r.private_key] == [ + "ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679", + "967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248", + "b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899", + "fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29", + "5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9", + ] + for d in derivation_paths1: print('"' + d + '",') assert derivation_paths1 == [ @@ -189,7 +197,9 @@ async def test_restore_wallet_after_split_to_send(wallet3: Wallet): await wallet3.mint(64, id=invoice.id) assert wallet3.balance == 64 - _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore + _, spendable_proofs = await wallet3.split_to_send( + wallet3.proofs, 32, set_reserved=True + ) # type: ignore await reset_wallet_db(wallet3) await wallet3.load_proofs() @@ -212,7 +222,9 @@ async def test_restore_wallet_after_send_and_receive(wallet3: Wallet, wallet2: W await wallet3.mint(64, id=invoice.id) assert wallet3.balance == 64 - _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore + _, spendable_proofs = await wallet3.split_to_send( + wallet3.proofs, 32, set_reserved=True + ) # type: ignore await wallet2.redeem(spendable_proofs) @@ -253,7 +265,9 @@ async def test_restore_wallet_after_send_and_self_receive(wallet3: Wallet): await wallet3.mint(64, id=invoice.id) assert wallet3.balance == 64 - _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore + _, spendable_proofs = await wallet3.split_to_send( + wallet3.proofs, 32, set_reserved=True + ) # type: ignore await wallet3.redeem(spendable_proofs) @@ -281,7 +295,9 @@ async def test_restore_wallet_after_send_twice( box.add(wallet3.proofs) assert wallet3.balance == 2 - keep_proofs, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 1, set_reserved=True) # type: ignore + keep_proofs, spendable_proofs = await wallet3.split_to_send( + wallet3.proofs, 1, set_reserved=True + ) # type: ignore box.add(wallet3.proofs) assert wallet3.available_balance == 1 await wallet3.redeem(spendable_proofs) @@ -301,7 +317,9 @@ async def test_restore_wallet_after_send_twice( # again - _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 1, set_reserved=True) # type: ignore + _, spendable_proofs = await wallet3.split_to_send( + wallet3.proofs, 1, set_reserved=True + ) # type: ignore box.add(wallet3.proofs) assert wallet3.available_balance == 1 @@ -336,7 +354,9 @@ async def test_restore_wallet_after_send_and_self_receive_nonquadratic_value( box.add(wallet3.proofs) assert wallet3.balance == 64 - keep_proofs, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 10, set_reserved=True) # type: ignore + keep_proofs, spendable_proofs = await wallet3.split_to_send( + wallet3.proofs, 10, set_reserved=True + ) # type: ignore box.add(wallet3.proofs) assert wallet3.available_balance == 64 - 10 @@ -356,7 +376,9 @@ async def test_restore_wallet_after_send_and_self_receive_nonquadratic_value( # again - _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 12, set_reserved=True) # type: ignore + _, spendable_proofs = await wallet3.split_to_send( + wallet3.proofs, 12, set_reserved=True + ) # type: ignore assert wallet3.available_balance == 64 - 12 await wallet3.redeem(spendable_proofs) From 4f5297390874700ae9fbdf8c855199608d81f5c4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:52:20 +0100 Subject: [PATCH 56/57] Update PostRestoreResponse class to use "signatures" instead of "promises" (#467) * update nut-09 endpoint * add to deprecated router and wallet --- cashu/core/base.py | 8 +++++++- cashu/mint/router.py | 2 +- cashu/mint/router_deprecated.py | 2 +- cashu/wallet/wallet.py | 9 ++++++++- cashu/wallet/wallet_deprecated.py | 9 ++++++++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index d9f1dc0a..4ae524ef 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -530,7 +530,13 @@ class CheckFeesResponse_deprecated(BaseModel): class PostRestoreResponse(BaseModel): outputs: List[BlindedMessage] = [] - promises: List[BlindedSignature] = [] + signatures: List[BlindedSignature] = [] + promises: Optional[List[BlindedSignature]] = [] # deprecated since 0.15.1 + + # duplicate value of "signatures" for backwards compatibility with old clients < 0.15.1 + def __init__(self, **data): + super().__init__(**data) + self.promises = self.signatures # ------- KEYSETS ------- diff --git a/cashu/mint/router.py b/cashu/mint/router.py index d0cac90a..de3a07c7 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -355,4 +355,4 @@ async def check_state( async def restore(payload: PostMintRequest) -> PostRestoreResponse: assert payload.outputs, Exception("no outputs provided.") outputs, promises = await ledger.restore(payload.outputs) - return PostRestoreResponse(outputs=outputs, promises=promises) + return PostRestoreResponse(outputs=outputs, signatures=promises) diff --git a/cashu/mint/router_deprecated.py b/cashu/mint/router_deprecated.py index a2ac71b7..f2cb96c4 100644 --- a/cashu/mint/router_deprecated.py +++ b/cashu/mint/router_deprecated.py @@ -360,4 +360,4 @@ async def check_spendable_deprecated( async def restore(payload: PostMintRequest_deprecated) -> PostRestoreResponse: assert payload.outputs, Exception("no outputs provided.") outputs, promises = await ledger.restore(payload.outputs) - return PostRestoreResponse(outputs=outputs, promises=promises) + return PostRestoreResponse(outputs=outputs, signatures=promises) diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 9dd07b82..54735068 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -697,7 +697,14 @@ async def restore_promises( self.raise_on_error_request(resp) response_dict = resp.json() returnObj = PostRestoreResponse.parse_obj(response_dict) - return returnObj.outputs, returnObj.promises + + # BEGIN backwards compatibility < 0.15.1 + # if the mint returns promises, duplicate into signatures + if returnObj.promises: + returnObj.signatures = returnObj.promises + # END backwards compatibility < 0.15.1 + + return returnObj.outputs, returnObj.signatures class Wallet(LedgerAPI, WalletP2PK, WalletHTLC, WalletSecrets): diff --git a/cashu/wallet/wallet_deprecated.py b/cashu/wallet/wallet_deprecated.py index cca86fb3..db5e927a 100644 --- a/cashu/wallet/wallet_deprecated.py +++ b/cashu/wallet/wallet_deprecated.py @@ -408,7 +408,14 @@ async def restore_promises_deprecated( self.raise_on_error(resp) response_dict = resp.json() returnObj = PostRestoreResponse.parse_obj(response_dict) - return returnObj.outputs, returnObj.promises + + # BEGIN backwards compatibility < 0.15.1 + # if the mint returns promises, duplicate into signatures + if returnObj.promises: + returnObj.signatures = returnObj.promises + # END backwards compatibility < 0.15.1 + + return returnObj.outputs, returnObj.signatures @async_set_httpx_client @async_ensure_mint_loaded_deprecated From 150195d66aa90f62ae155eac67d208aa85ce94c8 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:53:18 +0100 Subject: [PATCH 57/57] Token state check with Y (#468) * Token state check with Y * remove backwards compat for v1 --- cashu/core/base.py | 4 +- cashu/mint/crud.py | 74 +++++++++++++++++++++------------ cashu/mint/ledger.py | 32 +++++++------- cashu/mint/router.py | 2 +- cashu/mint/router_deprecated.py | 2 +- cashu/mint/verification.py | 56 +++++++++++-------------- cashu/wallet/wallet.py | 8 ++-- tests/test_mint_api.py | 5 ++- tests/test_mint_operations.py | 4 +- 9 files changed, 99 insertions(+), 88 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 4ae524ef..d0a3eff3 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -486,7 +486,7 @@ class PostSplitResponse_Very_Deprecated(BaseModel): class PostCheckStateRequest(BaseModel): - secrets: List[str] = Field(..., max_items=settings.mint_max_request_length) + Ys: List[str] = Field(..., max_items=settings.mint_max_request_length) class SpentState(Enum): @@ -499,7 +499,7 @@ def __str__(self): class ProofState(BaseModel): - secret: str + Y: str state: SpentState witness: Optional[str] = None diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index 853ff094..9a1dd54c 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -34,7 +34,8 @@ async def get_keyset( derivation_path: str = "", seed: str = "", conn: Optional[Connection] = None, - ) -> List[MintKeyset]: ... + ) -> List[MintKeyset]: + ... @abstractmethod async def get_spent_proofs( @@ -42,7 +43,8 @@ async def get_spent_proofs( *, db: Database, conn: Optional[Connection] = None, - ) -> List[Proof]: ... + ) -> List[Proof]: + ... async def get_proof_used( self, @@ -50,7 +52,8 @@ async def get_proof_used( Y: str, db: Database, conn: Optional[Connection] = None, - ) -> Optional[Proof]: ... + ) -> Optional[Proof]: + ... @abstractmethod async def invalidate_proof( @@ -59,16 +62,18 @@ async def invalidate_proof( db: Database, proof: Proof, conn: Optional[Connection] = None, - ) -> None: ... + ) -> None: + ... @abstractmethod async def get_proofs_pending( self, *, - proofs: List[Proof], + Ys: List[str], db: Database, conn: Optional[Connection] = None, - ) -> List[Proof]: ... + ) -> List[Proof]: + ... @abstractmethod async def set_proof_pending( @@ -77,12 +82,14 @@ async def set_proof_pending( db: Database, proof: Proof, conn: Optional[Connection] = None, - ) -> None: ... + ) -> None: + ... @abstractmethod async def unset_proof_pending( self, *, proof: Proof, db: Database, conn: Optional[Connection] = None - ) -> None: ... + ) -> None: + ... @abstractmethod async def store_keyset( @@ -91,14 +98,16 @@ async def store_keyset( db: Database, keyset: MintKeyset, conn: Optional[Connection] = None, - ) -> None: ... + ) -> None: + ... @abstractmethod async def get_balance( self, db: Database, conn: Optional[Connection] = None, - ) -> int: ... + ) -> int: + ... @abstractmethod async def store_promise( @@ -112,7 +121,8 @@ async def store_promise( e: str = "", s: str = "", conn: Optional[Connection] = None, - ) -> None: ... + ) -> None: + ... @abstractmethod async def get_promise( @@ -121,7 +131,8 @@ async def get_promise( db: Database, B_: str, conn: Optional[Connection] = None, - ) -> Optional[BlindedSignature]: ... + ) -> Optional[BlindedSignature]: + ... @abstractmethod async def store_mint_quote( @@ -130,7 +141,8 @@ async def store_mint_quote( quote: MintQuote, db: Database, conn: Optional[Connection] = None, - ) -> None: ... + ) -> None: + ... @abstractmethod async def get_mint_quote( @@ -139,7 +151,8 @@ async def get_mint_quote( quote_id: str, db: Database, conn: Optional[Connection] = None, - ) -> Optional[MintQuote]: ... + ) -> Optional[MintQuote]: + ... @abstractmethod async def get_mint_quote_by_checking_id( @@ -148,7 +161,8 @@ async def get_mint_quote_by_checking_id( checking_id: str, db: Database, conn: Optional[Connection] = None, - ) -> Optional[MintQuote]: ... + ) -> Optional[MintQuote]: + ... @abstractmethod async def update_mint_quote( @@ -157,7 +171,8 @@ async def update_mint_quote( quote: MintQuote, db: Database, conn: Optional[Connection] = None, - ) -> None: ... + ) -> None: + ... # @abstractmethod # async def update_mint_quote_paid( @@ -176,7 +191,8 @@ async def store_melt_quote( quote: MeltQuote, db: Database, conn: Optional[Connection] = None, - ) -> None: ... + ) -> None: + ... @abstractmethod async def get_melt_quote( @@ -186,7 +202,8 @@ async def get_melt_quote( db: Database, checking_id: Optional[str] = None, conn: Optional[Connection] = None, - ) -> Optional[MeltQuote]: ... + ) -> Optional[MeltQuote]: + ... @abstractmethod async def update_melt_quote( @@ -195,7 +212,8 @@ async def update_melt_quote( quote: MeltQuote, db: Database, conn: Optional[Connection] = None, - ) -> None: ... + ) -> None: + ... class LedgerCrudSqlite(LedgerCrud): @@ -256,9 +274,11 @@ async def get_spent_proofs( db: Database, conn: Optional[Connection] = None, ) -> List[Proof]: - rows = await (conn or db).fetchall(f""" + rows = await (conn or db).fetchall( + f""" SELECT * from {table_with_schema(db, 'proofs_used')} - """) + """ + ) return [Proof(**r) for r in rows] if rows else [] async def invalidate_proof( @@ -289,16 +309,16 @@ async def invalidate_proof( async def get_proofs_pending( self, *, - proofs: List[Proof], + Ys: List[str], db: Database, conn: Optional[Connection] = None, ) -> List[Proof]: rows = await (conn or db).fetchall( f""" SELECT * from {table_with_schema(db, 'proofs_pending')} - WHERE Y IN ({','.join(['?']*len(proofs))}) + WHERE Y IN ({','.join(['?']*len(Ys))}) """, - tuple(proof.Y for proof in proofs), + tuple(Ys), ) return [Proof(**r) for r in rows] @@ -549,9 +569,11 @@ async def get_balance( db: Database, conn: Optional[Connection] = None, ) -> int: - row = await (conn or db).fetchone(f""" + row = await (conn or db).fetchone( + f""" SELECT * from {table_with_schema(db, 'balance')} - """) + """ + ) assert row, "Balance not found" return int(row[0]) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index ce1ce7b1..532df73f 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -886,7 +886,7 @@ async def load_used_proofs(self) -> None: logger.debug(f"Loaded {len(spent_proofs_list)} used proofs") self.spent_proofs = {p.Y: p for p in spent_proofs_list} - async def check_proofs_state(self, secrets: List[str]) -> List[ProofState]: + async def check_proofs_state(self, Ys: List[str]) -> List[ProofState]: """Checks if provided proofs are spend or are pending. Used by wallets to check if their proofs have been redeemed by a receiver or they are still in-flight in a transaction. @@ -895,32 +895,26 @@ async def check_proofs_state(self, secrets: List[str]) -> List[ProofState]: and which isn't. Args: - proofs (List[Proof]): List of proofs to check. + Ys (List[str]): List of Y's of proofs to check Returns: List[bool]: List of which proof is still spendable (True if still spendable, else False) List[bool]: List of which proof are pending (True if pending, else False) """ states: List[ProofState] = [] - proofs_spent_idx_secret = await self._get_proofs_spent_idx_secret(secrets) - proofs_pending_idx_secret = await self._get_proofs_pending_idx_secret(secrets) - for secret in secrets: - if ( - secret not in proofs_spent_idx_secret - and secret not in proofs_pending_idx_secret - ): - states.append(ProofState(secret=secret, state=SpentState.unspent)) - elif ( - secret not in proofs_spent_idx_secret - and secret in proofs_pending_idx_secret - ): - states.append(ProofState(secret=secret, state=SpentState.pending)) + proofs_spent = await self._get_proofs_spent(Ys) + proofs_pending = await self._get_proofs_pending(Ys) + for Y in Ys: + if Y not in proofs_spent and Y not in proofs_pending: + states.append(ProofState(Y=Y, state=SpentState.unspent)) + elif Y not in proofs_spent and Y in proofs_pending: + states.append(ProofState(Y=Y, state=SpentState.pending)) else: states.append( ProofState( - secret=secret, + Y=Y, state=SpentState.spent, - witness=proofs_spent_idx_secret[secret].witness, + witness=proofs_spent[Y].witness, ) ) return states @@ -971,7 +965,9 @@ async def _validate_proofs_pending( """ assert ( len( - await self.crud.get_proofs_pending(proofs=proofs, db=self.db, conn=conn) + await self.crud.get_proofs_pending( + Ys=[p.Y for p in proofs], db=self.db, conn=conn + ) ) == 0 ), TransactionError("proofs are pending.") diff --git a/cashu/mint/router.py b/cashu/mint/router.py index de3a07c7..4c96d162 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -338,7 +338,7 @@ async def check_state( ) -> PostCheckStateResponse: """Check whether a secret has been spent already or not.""" logger.trace(f"> POST /v1/checkstate: {payload}") - proof_states = await ledger.check_proofs_state(payload.secrets) + proof_states = await ledger.check_proofs_state(payload.Ys) return PostCheckStateResponse(states=proof_states) diff --git a/cashu/mint/router_deprecated.py b/cashu/mint/router_deprecated.py index f2cb96c4..f71a8a34 100644 --- a/cashu/mint/router_deprecated.py +++ b/cashu/mint/router_deprecated.py @@ -328,7 +328,7 @@ async def check_spendable_deprecated( ) -> CheckSpendableResponse_deprecated: """Check whether a secret has been spent already or not.""" logger.trace(f"> POST /check: {payload}") - proofs_state = await ledger.check_proofs_state([p.secret for p in payload.proofs]) + proofs_state = await ledger.check_proofs_state([p.Y for p in payload.proofs]) spendableList: List[bool] = [] pendingList: List[bool] = [] for proof_state in proofs_state: diff --git a/cashu/mint/verification.py b/cashu/mint/verification.py index e3da1d54..754c8b1c 100644 --- a/cashu/mint/verification.py +++ b/cashu/mint/verification.py @@ -51,10 +51,7 @@ async def verify_inputs_and_outputs( """ # Verify inputs # Verify proofs are spendable - if ( - not len(await self._get_proofs_spent_idx_secret([p.secret for p in proofs])) - == 0 - ): + if not len(await self._get_proofs_spent([p.Y for p in proofs])) == 0: raise TokenAlreadySpentError() # Verify amounts of inputs if not all([self._verify_amount(p.amount) for p in proofs]): @@ -87,11 +84,13 @@ async def verify_inputs_and_outputs( # Verify that input keyset units are the same as output keyset unit # We have previously verified that all outputs have the same keyset id in `_verify_outputs` assert outputs[0].id, "output id not set" - if not all([ - self.keysets[p.id].unit == self.keysets[outputs[0].id].unit - for p in proofs - if p.id - ]): + if not all( + [ + self.keysets[p.id].unit == self.keysets[outputs[0].id].unit + for p in proofs + if p.id + ] + ): raise TransactionError("input and output keysets have different units.") # Verify output spending conditions @@ -143,39 +142,34 @@ async def _check_outputs_issued_before(self, outputs: List[BlindedMessage]): result.append(False if promise is None else True) return result - async def _get_proofs_pending_idx_secret( - self, secrets: List[str] - ) -> Dict[str, Proof]: - """Returns only those proofs that are pending.""" - all_proofs_pending = await self.crud.get_proofs_pending( - proofs=[Proof(secret=s) for s in secrets], db=self.db - ) - proofs_pending = list(filter(lambda p: p.secret in secrets, all_proofs_pending)) - proofs_pending_dict = {p.secret: p for p in proofs_pending} + async def _get_proofs_pending(self, Ys: List[str]) -> Dict[str, Proof]: + """Returns a dictionary of only those proofs that are pending. + The key is the Y=h2c(secret) and the value is the proof. + """ + proofs_pending = await self.crud.get_proofs_pending(Ys=Ys, db=self.db) + proofs_pending_dict = {p.Y: p for p in proofs_pending} return proofs_pending_dict - async def _get_proofs_spent_idx_secret( - self, secrets: List[str] - ) -> Dict[str, Proof]: - """Returns all proofs that are spent.""" - proofs = [Proof(secret=s) for s in secrets] - proofs_spent: List[Proof] = [] + async def _get_proofs_spent(self, Ys: List[str]) -> Dict[str, Proof]: + """Returns a dictionary of all proofs that are spent. + The key is the Y=h2c(secret) and the value is the proof. + """ + proofs_spent_dict: Dict[str, Proof] = {} if settings.mint_cache_secrets: # check used secrets in memory - for proof in proofs: - spent_proof = self.spent_proofs.get(proof.Y) + for Y in Ys: + spent_proof = self.spent_proofs.get(Y) if spent_proof: - proofs_spent.append(spent_proof) + proofs_spent_dict[Y] = spent_proof else: # check used secrets in database async with self.db.connect() as conn: - for proof in proofs: + for Y in Ys: spent_proof = await self.crud.get_proof_used( - db=self.db, Y=proof.Y, conn=conn + db=self.db, Y=Y, conn=conn ) if spent_proof: - proofs_spent.append(spent_proof) - proofs_spent_dict = {p.secret: p for p in proofs_spent} + proofs_spent_dict[Y] = spent_proof return proofs_spent_dict def _verify_secret_criteria(self, proof: Proof) -> Literal[True]: diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 54735068..d4a5479c 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -654,7 +654,7 @@ async def check_proof_state(self, proofs: List[Proof]) -> PostCheckStateResponse """ Checks whether the secrets in proofs are already spent or not and returns a list of booleans. """ - payload = PostCheckStateRequest(secrets=[p.secret for p in proofs]) + payload = PostCheckStateRequest(Ys=[p.Y for p in proofs]) resp = await self.httpx.post( join(self.url, "/v1/checkstate"), json=payload.dict(), @@ -667,11 +667,11 @@ async def check_proof_state(self, proofs: List[Proof]) -> PostCheckStateResponse states: List[ProofState] = [] for spendable, pending, p in zip(ret.spendable, ret.pending, proofs): if spendable and not pending: - states.append(ProofState(secret=p.secret, state=SpentState.unspent)) + states.append(ProofState(Y=p.Y, state=SpentState.unspent)) elif spendable and pending: - states.append(ProofState(secret=p.secret, state=SpentState.pending)) + states.append(ProofState(Y=p.Y, state=SpentState.pending)) else: - states.append(ProofState(secret=p.secret, state=SpentState.spent)) + states.append(ProofState(Y=p.Y, state=SpentState.spent)) ret = PostCheckStateResponse(states=states) return ret # END backwards compatibility < 0.15.0 diff --git a/tests/test_mint_api.py b/tests/test_mint_api.py index 22d8e30c..6685af30 100644 --- a/tests/test_mint_api.py +++ b/tests/test_mint_api.py @@ -54,7 +54,8 @@ async def test_api_keys(ledger: Ledger): "id": keyset.id, "unit": keyset.unit.name, "keys": { - str(k): v.serialize().hex() for k, v in keyset.public_keys.items() # type: ignore + str(k): v.serialize().hex() + for k, v in keyset.public_keys.items() # type: ignore }, } for keyset in ledger.keysets.values() @@ -378,7 +379,7 @@ async def test_melt_external(ledger: Ledger, wallet: Wallet): reason="settings.debug_mint_only_deprecated is set", ) async def test_api_check_state(ledger: Ledger): - payload = PostCheckStateRequest(secrets=["asdasdasd", "asdasdasd1"]) + payload = PostCheckStateRequest(Ys=["asdasdasd", "asdasdasd1"]) response = httpx.post( f"{BASE_URL}/v1/checkstate", json=payload.dict(), diff --git a/tests/test_mint_operations.py b/tests/test_mint_operations.py index 46c05ec0..d5bca559 100644 --- a/tests/test_mint_operations.py +++ b/tests/test_mint_operations.py @@ -357,7 +357,5 @@ async def test_check_proof_state(wallet1: Wallet, ledger: Ledger): keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10) - proof_states = await ledger.check_proofs_state( - secrets=[p.secret for p in send_proofs] - ) + proof_states = await ledger.check_proofs_state(Ys=[p.Y for p in send_proofs]) assert all([p.state.value == "UNSPENT" for p in proof_states])