km-keybind 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
keybind/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from .keybind import Keybind
16
+
17
+ __all__ = ["Keybind"]
18
+
19
+
keybind/chunker.py ADDED
@@ -0,0 +1,30 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+
18
+ class ChunkManager:
19
+
20
+ def __init__(self, chunk_size: int = 1024 * 1024) -> None:
21
+ self.chunk_size = chunk_size
22
+
23
+ def split(self, payload: bytes) -> list[bytes]:
24
+ if payload is None:
25
+ return []
26
+ if len(payload) == 0:
27
+ return [b""]
28
+ return [payload[i : i + self.chunk_size] for i in range(0, len(payload), self.chunk_size)]
29
+
30
+
keybind/compressor.py ADDED
@@ -0,0 +1,41 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import zstandard as zstd
18
+
19
+ from .exceptions import CompressionError, raise_from
20
+ from .interfaces import Compressor
21
+
22
+
23
+ class ZstdCompressor(Compressor):
24
+
25
+ def __init__(self, level: int = 3) -> None:
26
+ self._compressor = zstd.ZstdCompressor(level=level)
27
+ self._decompressor = zstd.ZstdDecompressor()
28
+
29
+ def compress(self, payload: bytes) -> bytes:
30
+ try:
31
+ return self._compressor.compress(payload)
32
+ except Exception as exc:
33
+ raise_from(exc, CompressionError, "Compression failed")
34
+
35
+ def decompress(self, payload: bytes) -> bytes:
36
+ try:
37
+ return self._decompressor.decompress(payload)
38
+ except Exception as exc:
39
+ raise_from(exc, CompressionError, "Decompression failed")
40
+
41
+
keybind/config.py ADDED
@@ -0,0 +1,42 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass
18
+
19
+
20
+ @dataclass(slots=True)
21
+ class KeybindConfig:
22
+
23
+ version: str = "SC1"
24
+ chunk_size: int = 8 * 1024 * 1024
25
+ compression_threshold: int = 256
26
+ compression_level: int = 3
27
+ stream_block_size: int = 64 * 1024
28
+ enable_compression: bool = True
29
+ enable_signature: bool = False
30
+ purpose: str = "keybind"
31
+ hash_algorithm: str = "blake3"
32
+ nonce_size: int = 24
33
+ tag_size: int = 16
34
+ max_token_size: int = 8 * 1024 * 1024 * 1024
35
+ max_chain_depth: int = 128
36
+ default_expiration_seconds: int | None = None
37
+ default_issued_at: bool = True
38
+
39
+ def clone(self) -> "KeybindConfig":
40
+ return KeybindConfig(**self.__dict__)
41
+
42
+
keybind/constants.py ADDED
@@ -0,0 +1,24 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Final
18
+
19
+ MAGIC: Final[bytes] = b"KB1"
20
+ DEFAULT_PURPOSE: Final[str] = "keybind"
21
+ DEFAULT_VERSION: Final[str] = "KB1"
22
+ HEADER_VERSION: Final[int] = 1
23
+
24
+
keybind/crypto.py ADDED
@@ -0,0 +1,71 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from .exceptions import CryptoError, raise_from
18
+
19
+ try:
20
+ from cryptography.hazmat.primitives.ciphers.aead import XChaCha20Poly1305 as _CryptoXChaCha20Poly1305
21
+ except ImportError:
22
+ _CryptoXChaCha20Poly1305 = None
23
+
24
+ try:
25
+ from nacl.bindings import (
26
+ crypto_aead_xchacha20poly1305_ietf_decrypt as _lib_decrypt,
27
+ crypto_aead_xchacha20poly1305_ietf_encrypt as _lib_encrypt,
28
+ )
29
+ except ImportError:
30
+ _lib_encrypt = None
31
+ _lib_decrypt = None
32
+
33
+
34
+ class Encryptor:
35
+ def __init__(self, key: bytes) -> None:
36
+ if len(key) != 32:
37
+ raise CryptoError("Key must be 32 bytes")
38
+ self._key = key
39
+ self._cipher = _CryptoXChaCha20Poly1305(key) if _CryptoXChaCha20Poly1305 is not None else None
40
+
41
+ def encrypt(self, plaintext: bytes, nonce: bytes) -> bytes:
42
+ if len(nonce) != 24:
43
+ raise CryptoError("Nonce must be 24 bytes")
44
+ if self._cipher is not None:
45
+ try:
46
+ return self._cipher.encrypt(nonce, plaintext, None)
47
+ except Exception as exc:
48
+ raise_from(exc, CryptoError, "Encryption failed")
49
+ if _lib_encrypt is None:
50
+ raise CryptoError("No XChaCha20-Poly1305 backend is available")
51
+ try:
52
+ return _lib_encrypt(plaintext, b"", nonce, self._key)
53
+ except Exception as exc:
54
+ raise_from(exc, CryptoError, "Encryption failed")
55
+
56
+ def decrypt(self, ciphertext: bytes, nonce: bytes) -> bytes:
57
+ if len(nonce) != 24:
58
+ raise CryptoError("Nonce must be 24 bytes")
59
+ if self._cipher is not None:
60
+ try:
61
+ return self._cipher.decrypt(nonce, ciphertext, None)
62
+ except Exception as exc:
63
+ raise_from(exc, CryptoError, "Decryption failed")
64
+ if _lib_decrypt is None:
65
+ raise CryptoError("No XChaCha20-Poly1305 backend is available")
66
+ try:
67
+ return _lib_decrypt(ciphertext, b"", nonce, self._key)
68
+ except Exception as exc:
69
+ raise_from(exc, CryptoError, "Decryption failed")
70
+
71
+
keybind/enums.py ADDED
@@ -0,0 +1,29 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from enum import Enum
18
+
19
+
20
+ class CompressionMethod(str, Enum):
21
+ NONE = "none"
22
+ ZSTANDARD = "zstd"
23
+
24
+
25
+ class HashAlgorithm(str, Enum):
26
+ BLAKE3 = "blake3"
27
+ SHA3_256 = "sha3_256"
28
+
29
+
keybind/exceptions.py ADDED
@@ -0,0 +1,79 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+
18
+ class KeybindError(Exception):
19
+ """Base class for all Keybind errors."""
20
+
21
+ def __init__(self, message: str = "Keybind operation failed") -> None:
22
+ super().__init__(message)
23
+
24
+
25
+ class InvalidToken(KeybindError):
26
+ """Raised when a token has invalid structure or encoding."""
27
+
28
+
29
+ class ExpiredToken(KeybindError):
30
+ """Raised when the token validity window has elapsed."""
31
+
32
+
33
+ class TamperedToken(KeybindError):
34
+ """Raised when authentication or integrity checks fail."""
35
+
36
+
37
+ class IdentityMismatch(KeybindError):
38
+ """Raised when the identities used to decode do not match the token."""
39
+
40
+
41
+ class InvalidSignature(KeybindError):
42
+ """Raised when a signature cannot be verified."""
43
+
44
+
45
+ class CompressionError(KeybindError):
46
+ """Raised when compression or decompression fails."""
47
+
48
+
49
+ class SerializationError(KeybindError):
50
+ """Raised when serialization or deserialization fails."""
51
+
52
+
53
+ class ChunkError(KeybindError):
54
+ """Raised when chunk parsing or reconstruction fails."""
55
+
56
+
57
+ class MerkleError(KeybindError):
58
+ """Raised when Merkle verification fails."""
59
+
60
+
61
+ class CryptoError(KeybindError):
62
+ """Raised when cryptographic operations fail."""
63
+
64
+
65
+ class StorageError(KeybindError):
66
+ """Raised when persistence operations fail."""
67
+
68
+
69
+ def raise_from(exc: Exception, error_cls: type[KeybindError], message: str | None = None) -> None:
70
+ if isinstance(exc, KeybindError) and isinstance(exc, error_cls):
71
+ raise exc
72
+ raise error_cls(message or "Keybind operation failed") from exc
73
+
74
+
75
+ def ensure(condition: bool, error_cls: type[KeybindError], message: str) -> None:
76
+ if not condition:
77
+ raise error_cls(message)
78
+
79
+
keybind/hasher.py ADDED
@@ -0,0 +1,31 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+
19
+ import blake3
20
+
21
+
22
+ class HashManager:
23
+ @staticmethod
24
+ def blake3_digest(data: bytes) -> str:
25
+ return blake3.blake3(data).hexdigest()
26
+
27
+ @staticmethod
28
+ def sha3_256_digest(data: bytes) -> str:
29
+ return hashlib.sha3_256(data).hexdigest()
30
+
31
+
keybind/hkdf.py ADDED
@@ -0,0 +1,34 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from cryptography.hazmat.primitives import hashes
18
+ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
19
+
20
+
21
+ class KeyDeriver:
22
+ @staticmethod
23
+ def derive_key(
24
+ master_key: bytes,
25
+ primary_identity: str,
26
+ secondary_identity: str,
27
+ version: str,
28
+ purpose: str = "keybind",
29
+ ) -> bytes:
30
+ info = f"{version}:{purpose}:{primary_identity}:{secondary_identity}".encode("utf-8")
31
+ hkdf = HKDF(algorithm=hashes.SHA256(), length=32, salt=b"keybind", info=info)
32
+ return hkdf.derive(master_key)
33
+
34
+
keybind/identities.py ADDED
@@ -0,0 +1,23 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+
18
+ class IdentityManager:
19
+ @staticmethod
20
+ def normalize(value: str) -> str:
21
+ return value.strip().lower()
22
+
23
+
keybind/interfaces.py ADDED
@@ -0,0 +1,27 @@
1
+ # Copyright 2026 Pravanjan Roy
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from abc import ABC, abstractmethod
18
+
19
+
20
+ class Compressor(ABC):
21
+ @abstractmethod
22
+ def compress(self, payload: bytes) -> bytes:
23
+ raise NotImplementedError
24
+
25
+ @abstractmethod
26
+ def decompress(self, payload: bytes) -> bytes:
27
+ raise NotImplementedError