Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-altcha for openSUSE:Factory 
checked in at 2026-06-30 15:12:27
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-altcha (Old)
 and      /work/SRC/openSUSE:Factory/.python-altcha.new.11887 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-altcha"

Tue Jun 30 15:12:27 2026 rev:7 rq:1362529 version:2.0.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-altcha/python-altcha.changes      
2026-04-18 21:35:25.895256682 +0200
+++ /work/SRC/openSUSE:Factory/.python-altcha.new.11887/python-altcha.changes   
2026-06-30 15:12:58.559112544 +0200
@@ -1,0 +2,7 @@
+Mon Jun 29 20:33:23 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 2.0.2:
+  * fix: support bytes as hmac keys
+  * feat: payload.from_base64 and from_dict
+
+-------------------------------------------------------------------

Old:
----
  altcha-2.0.0.tar.gz

New:
----
  altcha-2.0.2.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-altcha.spec ++++++
--- /var/tmp/diff_new_pack.VB8d31/_old  2026-06-30 15:12:59.543145844 +0200
+++ /var/tmp/diff_new_pack.VB8d31/_new  2026-06-30 15:12:59.543145844 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python-altcha
-Version:        2.0.0
+Version:        2.0.2
 Release:        0
 Summary:        A library for creating and verifying challenges for ALTCHA
 License:        MIT

++++++ altcha-2.0.0.tar.gz -> altcha-2.0.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/altcha-2.0.0/PKG-INFO new/altcha-2.0.2/PKG-INFO
--- old/altcha-2.0.0/PKG-INFO   2026-04-07 13:56:00.222773300 +0200
+++ new/altcha-2.0.2/PKG-INFO   2026-06-20 12:15:40.391074000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: altcha
-Version: 2.0.0
+Version: 2.0.2
 Summary: A library for creating and verifying challenges for ALTCHA.
 Home-page: https://github.com/altcha-org/altcha-lib-py
 Author: Daniel Regeci
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/altcha-2.0.0/altcha/v1.py 
new/altcha-2.0.2/altcha/v1.py
--- old/altcha-2.0.0/altcha/v1.py       2026-04-07 13:55:56.000000000 +0200
+++ new/altcha-2.0.2/altcha/v1.py       2026-06-20 12:15:36.000000000 +0200
@@ -18,12 +18,15 @@
 AlgoType = Literal["SHA-1", "SHA-256", "SHA-512"]
 
 
-class PayloadType(TypedDict, total=False):
+class _PayloadTypeRequired(TypedDict):
     algorithm: AlgoType
     challenge: str
     number: int
     salt: str
     signature: str
+
+
+class PayloadType(_PayloadTypeRequired, total=False):
     verificationData: str
     verified: bool
 
@@ -53,7 +56,7 @@
         algorithm: AlgoType = DEFAULT_ALGORITHM,
         max_number: int = DEFAULT_MAX_NUMBER,
         salt_length: int = DEFAULT_SALT_LENGTH,
-        hmac_key: str = "",
+        hmac_key: str | bytes = "",
         salt: str = "",
         number: int | None = None,
         expires: datetime.datetime | None = None,
@@ -149,6 +152,41 @@
         """Convert the Payload to a base64 encoded JSON string."""
         return base64.b64encode(json.dumps(self.to_dict()).encode()).decode()
 
+    @classmethod
+    def from_dict(cls, data: PayloadType) -> "Payload":
+        """Create a Payload from a dictionary."""
+        return cls(
+            algorithm=cast(AlgoType, data["algorithm"]),
+            challenge=data["challenge"],
+            number=data["number"],
+            salt=data["salt"],
+            signature=data["signature"],
+        )
+
+    @classmethod
+    def from_base64(cls, encoded: str) -> "Payload":
+        """Create a Payload from a base64 encoded JSON string."""
+        data = cast(PayloadType, 
json.loads(base64.b64decode(encoded).decode()))
+        return cls.from_dict(data)
+
+    def parse_salt(self) -> tuple[str, float | None, dict[str, list[str]]]:
+        """Parse the salt into (hex_nonce, expires_timestamp, params).
+
+        Returns:
+            tuple: (hex_nonce, expires or None, dict of query params from salt)
+        """
+        parts = self.salt.split("?", 1)
+        hex_nonce = parts[0]
+        params = urllib.parse.parse_qs(parts[1]) if len(parts) > 1 else {}
+        expires_list = params.get("expires")
+        expires: float | None = None
+        if expires_list:
+            try:
+                expires = float(expires_list[0])
+            except ValueError:
+                pass
+        return hex_nonce, expires, params
+
 
 class ServerSignaturePayload:
     """
@@ -333,7 +371,7 @@
         raise ValueError(f"Unsupported algorithm: {algorithm}")
 
 
-def hmac_hex(algorithm: AlgoType, data: bytes, key: str) -> str:
+def hmac_hex(algorithm: AlgoType, data: bytes, key: str | bytes) -> str:
     """
     Computes the HMAC hexadecimal digest of the given data using the specified 
algorithm and key.
 
@@ -345,9 +383,9 @@
     Returns:
         str: Hexadecimal HMAC digest of the data.
     """
-    hmac_obj = hmac.new(
-        key.encode(), data, getattr(hashlib, algorithm.replace("-", 
"").lower())
-    )
+    if isinstance(key, str):
+        key = key.encode()
+    hmac_obj = hmac.new(key, data, getattr(hashlib, algorithm.replace("-", 
"").lower()))
     return hmac_obj.hexdigest()
 
 
@@ -359,7 +397,7 @@
     algorithm: AlgoType,
     max_number: int,
     salt_length: int,
-    hmac_key: str,
+    hmac_key: str | bytes,
     salt: str,
     number: int | None,
     expires: datetime.datetime | None,
@@ -373,7 +411,7 @@
     algorithm: AlgoType = DEFAULT_ALGORITHM,
     max_number: int = DEFAULT_MAX_NUMBER,
     salt_length: int = DEFAULT_SALT_LENGTH,
-    hmac_key: str = "",
+    hmac_key: str | bytes = "",
     salt: str = "",
     number: int | None = None,
     expires: datetime.datetime | None = None,
@@ -459,26 +497,26 @@
 @overload
 def verify_solution(
     payload: Payload,
-    hmac_key: str,
+    hmac_key: str | bytes,
     check_expires: bool = True,
 ) -> tuple[bool, str | None]: ...
 @overload
 def verify_solution(
     payload: str,
-    hmac_key: str,
+    hmac_key: str | bytes,
     check_expires: bool = True,
 ) -> tuple[bool, str | None]: ...
 @overload
 def verify_solution(
     payload: PayloadType,
-    hmac_key: str,
+    hmac_key: str | bytes,
     check_expires: bool = True,
 ) -> tuple[bool, str | None]: ...
 
 
 def verify_solution(
     payload: str | Payload | PayloadType,
-    hmac_key: str,
+    hmac_key: str | bytes,
     check_expires: bool = True,
 ) -> tuple[bool, str | None]:
     """
@@ -577,23 +615,23 @@
 @overload
 def verify_server_signature(
     payload: str,
-    hmac_key: str,
+    hmac_key: str | bytes,
 ) -> tuple[bool, ServerSignatureVerificationData | None, str | None]: ...
 @overload
 def verify_server_signature(
     payload: ServerSignaturePayload,
-    hmac_key: str,
+    hmac_key: str | bytes,
 ) -> tuple[bool, ServerSignatureVerificationData | None, str | None]: ...
 @overload
 def verify_server_signature(
     payload: PayloadType,
-    hmac_key: str,
+    hmac_key: str | bytes,
 ) -> tuple[bool, ServerSignatureVerificationData | None, str | None]: ...
 
 
 def verify_server_signature(
     payload: str | ServerSignaturePayload | PayloadType,
-    hmac_key: str,
+    hmac_key: str | bytes,
 ) -> tuple[bool, ServerSignatureVerificationData | None, str | None]:
     """
     Verifies the server signature in the payload.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/altcha-2.0.0/altcha/v2.py 
new/altcha-2.0.2/altcha/v2.py
--- old/altcha-2.0.0/altcha/v2.py       2026-04-07 13:55:56.000000000 +0200
+++ new/altcha-2.0.2/altcha/v2.py       2026-06-20 12:15:36.000000000 +0200
@@ -197,13 +197,16 @@
         return base64.b64encode(json.dumps(self.to_dict()).encode()).decode()
 
     @classmethod
-    def from_base64(cls, data: str) -> Payload:
-        d = json.loads(base64.b64decode(data).decode())
+    def from_dict(cls, d: dict) -> Payload:
         return cls(
             challenge=Challenge.from_dict(d["challenge"]),
             solution=Solution.from_dict(d["solution"]),
         )
 
+    @classmethod
+    def from_base64(cls, data: str) -> Payload:
+        return cls.from_dict(json.loads(base64.b64decode(data).decode()))
+
 
 class VerifySolutionResult:
     """
@@ -265,12 +268,14 @@
     return json.dumps(_sort_keys(obj), separators=(",", ":"), 
ensure_ascii=False)
 
 
-def _hmac_v2(algorithm: str, data: str | bytes, key: str) -> bytes:
+def _hmac_v2(algorithm: str, data: str | bytes, key: str | bytes) -> bytes:
     """Compute HMAC and return raw bytes."""
     if isinstance(data, str):
         data = data.encode()
+    if isinstance(key, str):
+        key = key.encode()
     hash_name = algorithm.lower().replace("-", "")  # 'sha256', 'sha384', 
'sha512'
-    return _hmac_module.new(key.encode(), data, getattr(hashlib, 
hash_name)).digest()
+    return _hmac_module.new(key, data, getattr(hashlib, hash_name)).digest()
 
 
 def _constant_time_equal(a: str, b: str) -> bool:
@@ -281,8 +286,8 @@
     hmac_algorithm: str,
     parameters: ChallengeParameters,
     derived_key: bytes | None,
-    hmac_secret: str,
-    hmac_key_secret: str | None = None,
+    hmac_secret: str | bytes,
+    hmac_key_secret: str | bytes | None = None,
 ) -> Challenge:
     """Sign challenge parameters with HMAC, optionally also signing the 
derived key."""
     if derived_key is not None and hmac_key_secret is not None:
@@ -420,8 +425,8 @@
     parallelism: int | None = None,
     expires_at: int | datetime.datetime | None = None,
     data: dict | None = None,
-    hmac_secret: str | None = None,
-    hmac_key_secret: str | None = None,
+    hmac_secret: str | bytes | None = None,
+    hmac_key_secret: str | bytes | None = None,
     hmac_algorithm: HmacAlgorithmV2 = DEFAULT_HMAC_ALGORITHM,
 ) -> Challenge:
     """
@@ -556,10 +561,10 @@
 
 def verify_solution(
     payload: str | Payload,
-    hmac_secret: str,
+    hmac_secret: str | bytes,
     derive_key: DeriveKeyFunctionV2 | None = None,
     *,
-    hmac_key_secret: str | None = None,
+    hmac_key_secret: str | bytes | None = None,
     hmac_algorithm: HmacAlgorithmV2 = DEFAULT_HMAC_ALGORITHM,
 ) -> VerifySolutionResult:
     """
@@ -794,7 +799,7 @@
 
 def verify_server_signature(
     payload: str | ServerSignaturePayload,
-    hmac_secret: str,
+    hmac_secret: str | bytes,
     *,
     hmac_algorithm: HmacAlgorithmV2 = DEFAULT_HMAC_ALGORITHM,
 ) -> VerifyServerSignatureResult:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/altcha-2.0.0/altcha.egg-info/PKG-INFO 
new/altcha-2.0.2/altcha.egg-info/PKG-INFO
--- old/altcha-2.0.0/altcha.egg-info/PKG-INFO   2026-04-07 13:56:00.000000000 
+0200
+++ new/altcha-2.0.2/altcha.egg-info/PKG-INFO   2026-06-20 12:15:40.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: altcha
-Version: 2.0.0
+Version: 2.0.2
 Summary: A library for creating and verifying challenges for ALTCHA.
 Home-page: https://github.com/altcha-org/altcha-lib-py
 Author: Daniel Regeci
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/altcha-2.0.0/setup.py new/altcha-2.0.2/setup.py
--- old/altcha-2.0.0/setup.py   2026-04-07 13:55:56.000000000 +0200
+++ new/altcha-2.0.2/setup.py   2026-06-20 12:15:36.000000000 +0200
@@ -2,7 +2,7 @@
 
 setup(
     name="altcha",
-    version="2.0.0",
+    version="2.0.2",
     description="A library for creating and verifying challenges for ALTCHA.",
     long_description=open("README.md").read(),
     long_description_content_type="text/markdown",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/altcha-2.0.0/tests/test_altcha.py 
new/altcha-2.0.2/tests/test_altcha.py
--- old/altcha-2.0.0/tests/test_altcha.py       2026-04-07 13:55:56.000000000 
+0200
+++ new/altcha-2.0.2/tests/test_altcha.py       2026-06-20 12:15:36.000000000 
+0200
@@ -58,6 +58,31 @@
         result, _ = verify_solution(payload_encoded, self.hmac_key, 
check_expires=False)
         self.assertTrue(result)
 
+    def test_verify_solution_bytes_hmac_key(self):
+        # Non-UTF-8 binary key must be accepted (issue #20)
+        key = bytes(range(256))
+        options = ChallengeOptions(
+            algorithm="SHA-256",
+            max_number=1000,
+            salt_length=16,
+            hmac_key=key,
+            salt="somesalt",
+            number=123,
+        )
+        challenge = create_challenge(options)
+        payload = Payload(
+            algorithm="SHA-256",
+            challenge=challenge.challenge,
+            number=123,
+            salt="somesalt",
+            signature=challenge.signature,
+        )
+        payload_encoded = base64.b64encode(
+            json.dumps(payload.__dict__).encode()
+        ).decode()
+        result, _ = verify_solution(payload_encoded, key, check_expires=False)
+        self.assertTrue(result)
+
     def test_verify_solution_failure(self):
         options = ChallengeOptions(
             algorithm="SHA-256",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/altcha-2.0.0/tests/test_altcha_v2.py 
new/altcha-2.0.2/tests/test_altcha_v2.py
--- old/altcha-2.0.0/tests/test_altcha_v2.py    2026-04-07 13:55:56.000000000 
+0200
+++ new/altcha-2.0.2/tests/test_altcha_v2.py    2026-06-20 12:15:36.000000000 
+0200
@@ -144,6 +144,18 @@
         assert ch.signature is not None
         self.assertGreater(len(ch.signature), 0)
 
+    def test_bytes_hmac_secret(self):
+        # Non-UTF-8 binary key must be accepted (issue #20)
+        key = bytes(range(256))
+        ch = create_challenge("SHA-256", cost=1, counter=5, hmac_secret=key)
+        assert ch.signature is not None
+        sol = solve_challenge(ch)
+        assert sol is not None
+        payload = Payload(ch, sol).to_base64()
+        result = verify_solution(payload, key)
+        self.assertTrue(result.verified)
+        self.assertFalse(result.invalid_signature)
+
     def test_deterministic_counter(self):
         ch = create_challenge("SHA-256", cost=1, counter=42, 
hmac_secret=HMAC_KEY)
         # key_prefix should be the first 16 bytes (key_length//2) of the 
derived key

Reply via email to