@@ -50,33 +50,34 @@ def replace_digits(self, num: int) -> str:
5050 Convert numerical value to its character equivalent.
5151 """
5252 return self .key_string [num ]
53+
5354 def integer_determinant (self , matrix : np .ndarray ) -> int :
5455 """
5556 Calculate determinant of an integer matrix using exact arithmetic.
5657 """
5758 n = matrix .shape [0 ]
58-
59+
5960 # Base case for 1x1 matrix
6061 if n == 1 :
6162 return matrix [0 , 0 ]
62-
63+
6364 # Base case for 2x2 matrix
6465 if n == 2 :
6566 return matrix [0 , 0 ] * matrix [1 , 1 ] - matrix [0 , 1 ] * matrix [1 , 0 ]
66-
67+
6768 det = 0
6869 for j in range (n ):
6970 # Create minor matrix by removing first row and j-th column
7071 minor = matrix [1 :, :]
7172 minor = np .delete (minor , j , axis = 1 )
72-
73+
7374 # Recursively calculate determinant of minor
7475 minor_det = self .integer_determinant (minor )
75-
76+
7677 # Calculate cofactor with sign
7778 sign = (- 1 ) ** j
7879 det += sign * matrix [0 , j ] * minor_det
79-
80+
8081 return det
8182
8283 def check_determinant (self ) -> None :
@@ -89,7 +90,7 @@ def check_determinant(self) -> None:
8990 ValueError: If determinant is not coprime with 36
9091 """
9192 det = self .integer_determinant (self .encrypt_key )
92-
93+
9394 # Ensure positive modulo value
9495 det_mod = det % len (self .key_string )
9596 if det_mod == 0 :
@@ -107,14 +108,14 @@ def process_text(self, text: str) -> str:
107108 Prepare text for encryption/decryption.
108109 """
109110 chars = [char for char in text .upper () if char in self .key_string ]
110-
111+
111112 if not chars :
112113 return ""
113-
114+
114115 last = chars [- 1 ]
115116 while len (chars ) % self .break_key != 0 :
116117 chars .append (last )
117-
118+
118119 return "" .join (chars )
119120
120121 def encrypt (self , text : str ) -> str :
@@ -124,17 +125,17 @@ def encrypt(self, text: str) -> str:
124125 text = self .process_text (text .upper ())
125126 if not text :
126127 return ""
127-
128+
128129 encrypted = ""
129130
130131 for i in range (0 , len (text ), self .break_key ):
131- batch = text [i : i + self .break_key ]
132+ batch = text [i : i + self .break_key ]
132133 vec = [self .replace_letters (char ) for char in batch ]
133134 batch_vec = np .array (vec ).reshape (- 1 , 1 )
134-
135+
135136 product = self .encrypt_key @ batch_vec
136137 batch_encrypted = self .modulus (product ).flatten ().astype (int )
137-
138+
138139 encrypted_batch = "" .join (
139140 self .replace_digits (num ) for num in batch_encrypted
140141 )
@@ -149,7 +150,7 @@ def make_decrypt_key(self) -> np.ndarray:
149150 n = self .break_key
150151 det = self .integer_determinant (self .encrypt_key )
151152 modulus = len (self .key_string )
152-
153+
153154 # Find modular inverse of determinant
154155 det_mod = det % modulus
155156 det_inv = None
@@ -163,7 +164,7 @@ def make_decrypt_key(self) -> np.ndarray:
163164
164165 # Compute adjugate matrix
165166 adjugate = np .zeros ((n , n ), dtype = int )
166-
167+
167168 for i in range (n ):
168169 for j in range (n ):
169170 minor = np .delete (self .encrypt_key , i , axis = 0 )
@@ -183,18 +184,18 @@ def decrypt(self, text: str) -> str:
183184 text = self .process_text (text .upper ())
184185 if not text :
185186 return ""
186-
187+
187188 decrypt_key = self .make_decrypt_key ()
188189 decrypted = ""
189190
190191 for i in range (0 , len (text ), self .break_key ):
191- batch = text [i : i + self .break_key ]
192+ batch = text [i : i + self .break_key ]
192193 vec = [self .replace_letters (char ) for char in batch ]
193194 batch_vec = np .array (vec ).reshape (- 1 , 1 )
194-
195+
195196 product = decrypt_key @ batch_vec
196197 batch_decrypted = self .modulus (product ).flatten ().astype (int )
197-
198+
198199 decrypted_batch = "" .join (
199200 self .replace_digits (num ) for num in batch_decrypted
200201 )
@@ -203,7 +204,6 @@ def decrypt(self, text: str) -> str:
203204 return decrypted
204205
205206
206-
207207def main () -> None :
208208 """
209209 Command-line interface for Hill Cipher operations.
@@ -213,14 +213,14 @@ def main() -> None:
213213
214214 print ("Enter each row of the encryption key with space separated integers" )
215215 for i in range (n ):
216- row = [int (x ) for x in input (f"Row { i + 1 } : " ).split ()]
216+ row = [int (x ) for x in input (f"Row { i + 1 } : " ).split ()]
217217 hill_matrix .append (row )
218218
219219 hc = HillCipher (np .array (hill_matrix ))
220220
221221 print ("\n Would you like to encrypt or decrypt some text?" )
222222 option = input ("1. Encrypt\n 2. Decrypt\n Enter choice (1/2): " )
223-
223+
224224 if option == "1" :
225225 text = input ("\n Enter text to encrypt: " )
226226 print ("\n Encrypted text:" )
@@ -235,20 +235,21 @@ def main() -> None:
235235
236236if __name__ == "__main__" :
237237 import doctest
238+
238239 doctest .testmod ()
239-
240+
240241 print ("\n Running sample tests..." )
241242 key = np .array ([[2 , 5 ], [1 , 6 ]])
242243 cipher = HillCipher (key )
243-
244+
244245 # Test encryption/decryption round trip
245246 plaintext = "HELLO123"
246247 encrypted = cipher .encrypt (plaintext )
247248 decrypted = cipher .decrypt (encrypted )
248-
249+
249250 print (f"\n Original text: { plaintext } " )
250251 print (f"Encrypted text: { encrypted } " )
251252 print (f"Decrypted text: { decrypted } " )
252-
253+
253254 # Run CLI interface
254255 main ()
0 commit comments