diff --git a/docs/render_markdown.py b/docs/render_markdown.py index ddd1439..1884b11 100644 --- a/docs/render_markdown.py +++ b/docs/render_markdown.py @@ -2,7 +2,7 @@ from jinja2 import Environment, FileSystemLoader import os from src.nkode_api import NKodeAPI from src.models import NKodePolicy, KeypadSize, EncipheredNKode -from src.user_cipher_keys import UserCipherKeys +from src.user_cipher_keys import UserCipher from src.utils import list_to_matrix, matrix_transpose, xor_lists from secrets import choice from string import ascii_lowercase @@ -58,16 +58,16 @@ if __name__ == "__main__": ) keypad_size = KeypadSize( numb_of_keys=5, - attrs_per_key=6 # aka number of sets + props_per_key=6 # aka number of sets ) customer_id = api.create_new_customer(keypad_size, policy) customer = api.customers[customer_id] - set_vals = customer.attributes.set_vals - attr_vals = customer.attributes.attr_vals - customer_attr_view = list_to_matrix(attr_vals, keypad_size.attrs_per_key) + set_vals = customer.customer_cipher.set_key + attr_vals = customer.customer_cipher.prop_key + customer_attr_view = list_to_matrix(attr_vals, keypad_size.props_per_key) - attr_keypad_view = list_to_matrix(attr_vals, keypad_size.attrs_per_key) + attr_keypad_view = list_to_matrix(attr_vals, keypad_size.props_per_key) attr_set_view = matrix_transpose(attr_keypad_view) set_attribute_dict = dict(zip(set_vals, attr_set_view)) @@ -78,7 +78,7 @@ if __name__ == "__main__": passcode_len = 4 user_passcode = signup_interface[:passcode_len] selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.numb_of_keys) - server_side_attr = [customer.attributes.attr_vals[idx] for idx in user_passcode] + server_side_attr = [customer.customer_cipher.prop_key[idx] for idx in user_passcode] confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id) @@ -88,20 +88,20 @@ if __name__ == "__main__": success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id) assert success - passcode_server_attr = [customer.attributes.attr_vals[idx] for idx in user_passcode] - passcode_server_set = [customer.attributes.get_attr_set_val(attr) for attr in passcode_server_attr] + passcode_server_attr = [customer.customer_cipher.prop_key[idx] for idx in user_passcode] + passcode_server_set = [customer.customer_cipher.get_prop_set_val(attr) for attr in passcode_server_attr] user_keys = customer.users[username].user_keys - padded_passcode_server_set = user_keys.pad_user_mask(passcode_server_set, customer.attributes.set_vals) + padded_passcode_server_set = user_keys.pad_user_mask(passcode_server_set, customer.customer_cipher.set_key) - set_idx = [customer.attributes.get_set_index(set_val) for set_val in padded_passcode_server_set] + set_idx = [customer.customer_cipher.get_set_index(set_val) for set_val in padded_passcode_server_set] mask_set_keys = [user_keys.set_key[idx] for idx in set_idx] ciphered_mask = xor_lists(mask_set_keys, padded_passcode_server_set) ciphered_mask = xor_lists(ciphered_mask, user_keys.mask_key) mask = user_keys.encode_base64_str(ciphered_mask) - ciphered_customer_attrs = xor_lists(customer.attributes.attr_vals, user_keys.alpha_key) + ciphered_customer_attrs = xor_lists(customer.customer_cipher.prop_key, user_keys.alpha_key) passcode_ciphered_attrs = [ciphered_customer_attrs[idx] for idx in user_passcode] pad_len = customer.nkode_policy.max_nkode_len - passcode_len @@ -122,8 +122,8 @@ if __name__ == "__main__": USER LOGIN """ login_interface = api.get_login_interface(username, customer_id) - login_keypad = list_to_matrix(login_interface, keypad_size.attrs_per_key) - selected_keys_login = select_keys_with_passcode_values(user_passcode, login_interface, keypad_size.attrs_per_key) + login_keypad = list_to_matrix(login_interface, keypad_size.props_per_key) + selected_keys_login = select_keys_with_passcode_values(user_passcode, login_interface, keypad_size.props_per_key) success = api.login(customer_id, username, selected_keys_login) assert success @@ -133,7 +133,7 @@ if __name__ == "__main__": """ user = customer.users[username] - set_vals = customer.attributes.set_vals + set_vals = customer.customer_cipher.set_key user_keys = user.user_keys user_mask = user.enciphered_passcode.mask decoded_mask = user_keys.decode_base64_str(user_mask) @@ -148,7 +148,7 @@ if __name__ == "__main__": GET PRESUMED ATTRIBUTES """ - set_vals_idx = [customer.attributes.get_set_index(set_val) for set_val in login_passcode_sets] + set_vals_idx = [customer.customer_cipher.get_set_index(set_val) for set_val in login_passcode_sets] presumed_selected_attributes_idx = [] for idx in range(passcode_len): @@ -161,12 +161,12 @@ if __name__ == "__main__": RENEW KEYS """ - old_attrs = customer.attributes.attr_vals.copy() - old_sets = customer.attributes.set_vals.copy() - customer.attributes.renew() - new_attrs = customer.attributes.attr_vals - new_sets = customer.attributes.set_vals - customer_new_attr_view = list_to_matrix(new_attrs, keypad_size.attrs_per_key) + old_attrs = customer.customer_cipher.prop_key.copy() + old_sets = customer.customer_cipher.set_key.copy() + customer.customer_cipher.renew() + new_attrs = customer.customer_cipher.prop_key + new_sets = customer.customer_cipher.set_key + customer_new_attr_view = list_to_matrix(new_attrs, keypad_size.props_per_key) """ RENEW USER @@ -181,12 +181,12 @@ if __name__ == "__main__": """ REFRESH USER KEYS """ - user.user_keys = UserCipherKeys.create( - customer.attributes.keypad_size, - customer.attributes.set_vals, + user.user_keys = UserCipher.create( + customer.customer_cipher.keypad_size, + customer.customer_cipher.set_key, user.user_keys.max_nkode_len ) - user.enciphered_passcode = user.user_keys.encipher_nkode(presumed_selected_attributes_idx, customer.attributes) + user.enciphered_passcode = user.user_keys.encipher_nkode(presumed_selected_attributes_idx, customer.customer_cipher) user.renew = False # Define some data to pass to the template diff --git a/notebooks/dispersion_tutorial.ipynb b/notebooks/dispersion_tutorial.ipynb index 97ca9d8..2d08272 100644 --- a/notebooks/dispersion_tutorial.ipynb +++ b/notebooks/dispersion_tutorial.ipynb @@ -29,10 +29,10 @@ "from src.models import KeypadSize\n", "\n", "def keypad_md_table(interface: list[int], keypad_size: KeypadSize) -> str:\n", - " assert (keypad_size.numb_of_attrs == len(interface))\n", - " interface_keypad = list_to_matrix(interface, keypad_size.attrs_per_key)\n", - " table = \"|key|\" + \"\".join([f\"set{idx}|\" for idx in range(keypad_size.attrs_per_key)])\n", - " table += \"\\n|\" + \"\".join(\"-|\" for _ in range(keypad_size.attrs_per_key+1))\n", + " assert (keypad_size.numb_of_props == len(interface))\n", + " interface_keypad = list_to_matrix(interface, keypad_size.props_per_key)\n", + " table = \"|key|\" + \"\".join([f\"set{idx}|\" for idx in range(keypad_size.props_per_key)])\n", + " table += \"\\n|\" + \"\".join(\"-|\" for _ in range(keypad_size.props_per_key + 1))\n", "\n", " for key in range(keypad_size.numb_of_keys):\n", " table += f\"\\n|key{key+1}|\"\n", @@ -41,7 +41,7 @@ " return table\n", "\n", "\n", - "keypad_size = KeypadSize(numb_of_keys=5, attrs_per_key=4)\n", + "keypad_size = KeypadSize(numb_of_keys=5, props_per_key=4)\n", "attrs = [1, 10, 11, 100]\n", "keypad = []\n", "for key_numb in range(1,keypad_size.numb_of_keys+1):\n", @@ -73,7 +73,7 @@ } ], "source": [ - "demo_interface_matrix = list_to_matrix(demo_interface.keypad, demo_interface.keypad_size.attrs_per_key)\n", + "demo_interface_matrix = list_to_matrix(demo_interface.keypad, demo_interface.keypad_size.props_per_key)\n", "shuffled_keys = secure_fisher_yates_shuffle(demo_interface_matrix)\n", "shuffled_keys_list = matrix_to_list(shuffled_keys)\n", "display(Markdown(keypad_md_table(shuffled_keys_list, keypad_size)))\n" @@ -100,7 +100,7 @@ } ], "source": [ - "attr_rotation = secure_fisher_yates_shuffle(list(range(keypad_size.numb_of_keys)))[:keypad_size.attrs_per_key]\n", + "attr_rotation = secure_fisher_yates_shuffle(list(range(keypad_size.numb_of_keys)))[:keypad_size.props_per_key]\n", "dispersed_interface = UserKeypad.random_attribute_rotation(\n", " shuffled_keys,\n", " attr_rotation\n", diff --git a/src/customer.py b/src/customer.py index ee14c07..613a195 100644 --- a/src/customer.py +++ b/src/customer.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from uuid import UUID -from src.customer_attributes import CustomerAttributes +from src.customer_cipher import CustomerCipher from src.models import NKodePolicy from src.user import User from src.utils import xor_lists @@ -9,7 +9,7 @@ from src.utils import xor_lists class Customer: customer_id: UUID nkode_policy: NKodePolicy - attributes: CustomerAttributes + customer_cipher: CustomerCipher users: dict[str, User] # TODO: validate policy and keypad size don't conflict @@ -23,16 +23,16 @@ class Customer: if username not in self.users: raise ValueError(f"User '{username}' does not exist") - keypad_size = self.attributes.keypad_size.numb_of_keys - if not all(0 <= key_idx < keypad_size for key_idx in selected_keys): - raise ValueError(f"Invalid key indices. Must be between 0 and {keypad_size - 1}") + numb_of_keys = self.customer_cipher.keypad_size.numb_of_keys + if not all(0 <= key_idx < numb_of_keys for key_idx in selected_keys): + raise ValueError(f"Invalid key indices. Must be between 0 and {numb_of_keys - 1}") passcode_len = len(selected_keys) user = self.users[username] passcode_set_vals = user.user_keys.decipher_mask( - user.enciphered_passcode.mask, self.attributes.set_vals, passcode_len) - set_vals_idx = [self.attributes.get_set_index(set_val) for set_val in passcode_set_vals] + user.enciphered_passcode.mask, self.customer_cipher.set_key, passcode_len) + set_vals_idx = [self.customer_cipher.get_set_index(set_val) for set_val in passcode_set_vals] presumed_selected_attributes_idx = [] for idx in range(passcode_len): @@ -41,20 +41,20 @@ class Customer: selected_attr_idx = user.user_keypad.get_attr_idx_by_keynumb_setidx(key_numb, set_idx) presumed_selected_attributes_idx.append(selected_attr_idx) - enciphered_attr = user.user_keys.encipher_salt_hash_code(presumed_selected_attributes_idx, self.attributes) + enciphered_attr = user.user_keys.encipher_salt_hash_code(presumed_selected_attributes_idx, self.customer_cipher) if enciphered_attr != user.enciphered_passcode.code: return False if user.renew: - user.refresh_passcode(presumed_selected_attributes_idx, self.attributes) + user.refresh_passcode(presumed_selected_attributes_idx, self.customer_cipher) return True def renew_keys(self) -> bool: - old_attrs = self.attributes.attr_vals.copy() - old_sets = self.attributes.set_vals.copy() - self.attributes.renew() - new_attrs = self.attributes.attr_vals - new_sets = self.attributes.set_vals + old_attrs = self.customer_cipher.prop_key.copy() + old_sets = self.customer_cipher.set_key.copy() + self.customer_cipher.renew() + new_attrs = self.customer_cipher.prop_key + new_sets = self.customer_cipher.set_key attrs_xor = xor_lists(new_attrs, old_attrs) set_xor = xor_lists(new_sets, old_sets) @@ -66,7 +66,7 @@ class Customer: def valid_new_nkode(self, passcode_attr_idx: list[int]) -> bool: nkode_len = len(passcode_attr_idx) passcode_set_values = [ - self.attributes.get_attr_set_val(self.attributes.attr_vals[attr_idx]) for attr_idx in passcode_attr_idx + self.customer_cipher.get_prop_set_val(self.customer_cipher.prop_key[attr_idx]) for attr_idx in passcode_attr_idx ] distinct_sets = len(set(passcode_set_values)) distinct_attributes = len(set(passcode_attr_idx)) diff --git a/src/customer_attributes.py b/src/customer_attributes.py deleted file mode 100644 index 6680cbe..0000000 --- a/src/customer_attributes.py +++ /dev/null @@ -1,47 +0,0 @@ -from dataclasses import dataclass -from typing import ClassVar - -from src.models import KeypadSize -from src.utils import generate_random_nonrepeating_list - - -@dataclass -class CustomerAttributes: - attr_vals: list[int] - set_vals: list[int] - keypad_size: KeypadSize - MAX_KEYS: ClassVar[int] = 256 - MAX_ATTRS_PER_KEY: ClassVar[int] = 256 - - def __post_init__(self): - self.check_keys_vs_attrs() - - def check_keys_vs_attrs(self) -> None: - if self.keypad_size.is_dispersable: - raise ValueError("number of keys must be less than the number of " - "attributes per key to be dispersion resistant") - - @classmethod - def create(cls, keypad_size: KeypadSize) -> 'CustomerAttributes': - if keypad_size.numb_of_keys > cls.MAX_KEYS or keypad_size.attrs_per_key > cls.MAX_ATTRS_PER_KEY: - raise ValueError(f"Keys and attributes per key must not exceed {cls.MAX_KEYS}") - return cls( - attr_vals=generate_random_nonrepeating_list(keypad_size.numb_of_attrs), - set_vals=generate_random_nonrepeating_list(keypad_size.attrs_per_key), - keypad_size=keypad_size, - ) - - def renew(self): - self.attr_vals = generate_random_nonrepeating_list(self.keypad_size.numb_of_attrs) - self.set_vals = generate_random_nonrepeating_list(self.keypad_size.attrs_per_key) - - def get_attr_set_val(self, attr: int) -> int: - assert (attr in self.attr_vals) - attr_idx = self.attr_vals.index(attr) - set_idx = attr_idx % self.keypad_size.attrs_per_key - return self.set_vals[set_idx] - - def get_set_index(self, set_val: int) -> int: - if set_val not in self.set_vals: - raise ValueError(f"Set value {set_val} not found in set values") - return self.set_vals.index(set_val) \ No newline at end of file diff --git a/src/customer_cipher.py b/src/customer_cipher.py new file mode 100644 index 0000000..cfade71 --- /dev/null +++ b/src/customer_cipher.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass +from typing import ClassVar + +from src.models import KeypadSize +from src.utils import generate_random_nonrepeating_list + + +@dataclass +class CustomerCipher: + prop_key: list[int] + set_key: list[int] + keypad_size: KeypadSize + MAX_KEYS: ClassVar[int] = 256 + MAX_PROP_PER_KEY: ClassVar[int] = 256 + + def __post_init__(self): + self.check_keys_vs_props() + + def check_keys_vs_props(self) -> None: + if self.keypad_size.is_dispersable: + raise ValueError("number of keys must be less than the number of " + "properties per key to be dispersion resistant") + + @classmethod + def create(cls, keypad_size: KeypadSize) -> 'CustomerCipher': + if keypad_size.numb_of_keys > cls.MAX_KEYS or keypad_size.props_per_key > cls.MAX_PROP_PER_KEY: + raise ValueError(f"Keys and properties per key must not exceed {cls.MAX_KEYS}") + return cls( + prop_key=generate_random_nonrepeating_list(keypad_size.numb_of_props), + set_key=generate_random_nonrepeating_list(keypad_size.props_per_key), + keypad_size=keypad_size, + ) + + def renew(self): + self.prop_key = generate_random_nonrepeating_list(self.keypad_size.numb_of_props) + self.set_key = generate_random_nonrepeating_list(self.keypad_size.props_per_key) + + def get_prop_set_val(self, prop: int) -> int: + assert (prop in self.prop_key) + prop_idx = self.prop_key.index(prop) + set_idx = prop_idx % self.keypad_size.props_per_key + return self.set_key[set_idx] + + def get_set_index(self, set_val: int) -> int: + if set_val not in self.set_key: + raise ValueError(f"Set value {set_val} not found in set values") + return self.set_key.index(set_val) \ No newline at end of file diff --git a/src/models.py b/src/models.py index 9178196..39957ae 100644 --- a/src/models.py +++ b/src/models.py @@ -19,13 +19,13 @@ class NKodePolicy: @dataclass class KeypadSize: - attrs_per_key: int + props_per_key: int numb_of_keys: int @property - def numb_of_attrs(self) -> int: - return self.attrs_per_key * self.numb_of_keys + def numb_of_props(self) -> int: + return self.props_per_key * self.numb_of_keys @property def is_dispersable(self) -> bool: - return self.attrs_per_key <= self.numb_of_keys \ No newline at end of file + return self.props_per_key <= self.numb_of_keys \ No newline at end of file diff --git a/src/nkode_api.py b/src/nkode_api.py index a02e9ff..f647280 100644 --- a/src/nkode_api.py +++ b/src/nkode_api.py @@ -5,10 +5,10 @@ from typing import Dict, List, Tuple from src.customer import Customer from src.models import NKodePolicy, KeypadSize from src.user import User -from src.user_cipher_keys import UserCipherKeys +from src.user_cipher_keys import UserCipher from src.user_signup_session import UserSignupSession from src.user_keypad import UserKeypad -from src.customer_attributes import CustomerAttributes +from src.customer_cipher import CustomerCipher @dataclass @@ -19,7 +19,7 @@ class NKodeAPI: def create_new_customer(self, keypad_size: KeypadSize, nkode_policy: NKodePolicy) -> UUID: new_customer = Customer( customer_id=uuid4(), - attributes=CustomerAttributes.create(keypad_size), + customer_cipher=CustomerCipher.create(keypad_size), users={}, nkode_policy=nkode_policy ) @@ -30,7 +30,7 @@ class NKodeAPI: if customer_id not in self.customers.keys(): raise ValueError(f"Customer with ID '{customer_id}' does not exist") customer = self.customers[customer_id] - login_keypad = UserKeypad.create(customer.attributes.keypad_size) + login_keypad = UserKeypad.create(customer.customer_cipher.keypad_size) set_keypad = login_keypad.sign_up_keypad() new_session = UserSignupSession( session_id=uuid4(), @@ -75,12 +75,12 @@ class NKodeAPI: raise AssertionError(f"Username mismatch: {username} vs {session.username}") customer = self.customers[customer_id] passcode = self.signup_sessions[session_id].deduce_passcode(confirm_key_entry) - new_user_keys = UserCipherKeys.create( - customer.attributes.keypad_size, - customer.attributes.set_vals, + new_user_keys = UserCipher.create( + customer.customer_cipher.keypad_size, + customer.customer_cipher.set_key, customer.nkode_policy.max_nkode_len ) - enciphered_passcode = new_user_keys.encipher_nkode(passcode, customer.attributes) + enciphered_passcode = new_user_keys.encipher_nkode(passcode, customer.customer_cipher) new_user = User( username=username, enciphered_passcode=enciphered_passcode, diff --git a/src/user.py b/src/user.py index dad959c..7b3d21d 100644 --- a/src/user.py +++ b/src/user.py @@ -1,7 +1,7 @@ from dataclasses import dataclass, field from src.models import EncipheredNKode -from src.customer_attributes import CustomerAttributes -from src.user_cipher_keys import UserCipherKeys +from src.customer_cipher import CustomerCipher +from src.user_cipher_keys import UserCipher from src.user_keypad import UserKeypad from src.utils import xor_lists @@ -10,7 +10,7 @@ from src.utils import xor_lists class User: username: str enciphered_passcode: EncipheredNKode - user_keys: UserCipherKeys + user_keys: UserCipher user_keypad: UserKeypad renew: bool = field(default=False) @@ -19,10 +19,10 @@ class User: self.user_keys.set_key = xor_lists(self.user_keys.set_key, sets_xor) self.user_keys.alpha_key = xor_lists(self.user_keys.alpha_key, attrs_xor) - def refresh_passcode(self, passcode_attr_idx: list[int], customer_attributes: CustomerAttributes): - self.user_keys = UserCipherKeys.create( + def refresh_passcode(self, passcode_attr_idx: list[int], customer_attributes: CustomerCipher): + self.user_keys = UserCipher.create( customer_attributes.keypad_size, - customer_attributes.set_vals, + customer_attributes.set_key, self.user_keys.max_nkode_len ) self.enciphered_passcode = self.user_keys.encipher_nkode(passcode_attr_idx, customer_attributes) diff --git a/src/user_cipher_keys.py b/src/user_cipher_keys.py index a4a4d3f..d5390d6 100644 --- a/src/user_cipher_keys.py +++ b/src/user_cipher_keys.py @@ -4,11 +4,11 @@ from dataclasses import dataclass import bcrypt from secrets import choice from src.models import EncipheredNKode, KeypadSize -from src.customer_attributes import CustomerAttributes +from src.customer_cipher import CustomerCipher from src.utils import generate_random_nonrepeating_list, xor_lists, int_array_to_bytes @dataclass -class UserCipherKeys: +class UserCipher: alpha_key: list[int] set_key: list[int] pass_key: list[int] @@ -17,15 +17,15 @@ class UserCipherKeys: max_nkode_len: int @classmethod - def create(cls, keypad_size: KeypadSize, set_values: list[int], max_nkode_len: int) -> 'UserCipherKeys': - if len(set_values) != keypad_size.attrs_per_key: + def create(cls, keypad_size: KeypadSize, set_values: list[int], max_nkode_len: int) -> 'UserCipher': + if len(set_values) != keypad_size.props_per_key: raise ValueError("Invalid set values") - set_key = generate_random_nonrepeating_list(keypad_size.attrs_per_key) + set_key = generate_random_nonrepeating_list(keypad_size.props_per_key) set_key = xor_lists(set_key, set_values) - return UserCipherKeys( - alpha_key=generate_random_nonrepeating_list(keypad_size.attrs_per_key * keypad_size.numb_of_keys), + return UserCipher( + alpha_key=generate_random_nonrepeating_list(keypad_size.props_per_key * keypad_size.numb_of_keys), pass_key=generate_random_nonrepeating_list(max_nkode_len), mask_key=generate_random_nonrepeating_list(max_nkode_len), set_key=set_key, @@ -64,11 +64,11 @@ class UserCipherKeys: def encipher_nkode( self, passcode_attr_idx: list[int], - customer_attributes: CustomerAttributes + customer_attributes: CustomerCipher ) -> EncipheredNKode: - passcode_attrs = [customer_attributes.attr_vals[idx] for idx in passcode_attr_idx] - passcode_sets = [customer_attributes.get_attr_set_val(attr) for attr in passcode_attrs] + passcode_attrs = [customer_attributes.prop_key[idx] for idx in passcode_attr_idx] + passcode_sets = [customer_attributes.get_prop_set_val(attr) for attr in passcode_attrs] mask = self.encipher_mask(passcode_sets, customer_attributes) code = self.encipher_salt_hash_code(passcode_attr_idx, customer_attributes) return EncipheredNKode( @@ -79,10 +79,10 @@ class UserCipherKeys: def encipher_salt_hash_code( self, passcode_attr_idx: list[int], - customer_attributes: CustomerAttributes, + customer_attributes: CustomerCipher, ) -> str: passcode_len = len(passcode_attr_idx) - passcode_attrs = [customer_attributes.attr_vals[idx] for idx in passcode_attr_idx] + passcode_attrs = [customer_attributes.prop_key[idx] for idx in passcode_attr_idx] passcode_cipher = self.pass_key.copy() @@ -96,9 +96,9 @@ class UserCipherKeys: def encipher_mask( self, passcode_sets: list[int], - customer_attributes: CustomerAttributes + customer_attributes: CustomerCipher ) -> str: - padded_passcode_sets = self.pad_user_mask(passcode_sets, customer_attributes.set_vals) + padded_passcode_sets = self.pad_user_mask(passcode_sets, customer_attributes.set_key) set_idx = [customer_attributes.get_set_index(set_val) for set_val in padded_passcode_sets] mask_set_keys = [self.set_key[idx] for idx in set_idx] ciphered_mask = xor_lists(mask_set_keys, padded_passcode_sets) diff --git a/src/user_keypad.py b/src/user_keypad.py index 4efe5c6..bee1749 100644 --- a/src/user_keypad.py +++ b/src/user_keypad.py @@ -11,7 +11,7 @@ class UserKeypad: @classmethod def create(cls, keypad_size: KeypadSize) -> 'UserKeypad': keypad = UserKeypad( - keypad=list(range(keypad_size.numb_of_attrs)), + keypad=list(range(keypad_size.numb_of_props)), keypad_size=keypad_size ) keypad.random_keypad_shuffle() @@ -30,12 +30,12 @@ class UserKeypad: keypad=matrix_to_list(keypad_matrix), keypad_size=KeypadSize( numb_of_keys=self.keypad_size.numb_of_keys, - attrs_per_key=self.keypad_size.numb_of_keys + props_per_key=self.keypad_size.numb_of_keys ) ) def keypad_matrix(self) -> list[list[int]]: - return list_to_matrix(self.keypad, self.keypad_size.attrs_per_key) + return list_to_matrix(self.keypad, self.keypad_size.props_per_key) def random_keypad_shuffle(self): keypad_view = self.keypad_matrix() @@ -48,11 +48,11 @@ class UserKeypad: def disperse_keypad(self): if not self.keypad_size.is_dispersable: raise ValueError("Keypad size is not dispersable") - user_keypad_matrix = list_to_matrix(self.keypad, self.keypad_size.attrs_per_key) + user_keypad_matrix = list_to_matrix(self.keypad, self.keypad_size.props_per_key) shuffled_keys = secure_fisher_yates_shuffle(user_keypad_matrix) attr_rotation = secure_fisher_yates_shuffle(list(range(self.keypad_size.numb_of_keys)))[ - :self.keypad_size.attrs_per_key] + :self.keypad_size.props_per_key] dispersed_keypad = self.random_attribute_rotation( shuffled_keys, attr_rotation, @@ -61,10 +61,10 @@ class UserKeypad: def partial_keypad_shuffle(self): # TODO: this should be split shuffle - numb_of_selected_sets = self.keypad_size.attrs_per_key // 2 - # randomly shuffle half the sets. if attrs_per_key is odd, randomly add one 50% of the time - numb_of_selected_sets += choice([0, 1]) if (self.keypad_size.attrs_per_key & 1) == 1 else 0 - selected_sets = secure_fisher_yates_shuffle(list(range(self.keypad_size.attrs_per_key)))[:numb_of_selected_sets] + numb_of_selected_sets = self.keypad_size.props_per_key // 2 + # randomly shuffle half the sets. if props_per_key is odd, randomly add one 50% of the time + numb_of_selected_sets += choice([0, 1]) if (self.keypad_size.props_per_key & 1) == 1 else 0 + selected_sets = secure_fisher_yates_shuffle(list(range(self.keypad_size.props_per_key)))[:numb_of_selected_sets] user_keypad_matrix = self.keypad_matrix() shuffled_keys = secure_fisher_yates_shuffle(user_keypad_matrix) keypad_by_sets = [] @@ -100,7 +100,7 @@ class UserKeypad: def get_attr_idx_by_keynumb_setidx(self, key_numb: int, set_idx: int) -> int: if not (0 <= key_numb < self.keypad_size.numb_of_keys): raise ValueError(f"key_numb must be between 0 and {self.keypad_size.numb_of_keys - 1}") - if not (0 <= set_idx < self.keypad_size.attrs_per_key): - raise ValueError(f"set_idx must be between 0 and {self.keypad_size.attrs_per_key - 1}") + if not (0 <= set_idx < self.keypad_size.props_per_key): + raise ValueError(f"set_idx must be between 0 and {self.keypad_size.props_per_key - 1}") keypad_attr_idx = self.keypad_matrix() return keypad_attr_idx[key_numb][set_idx] diff --git a/src/user_signup_session.py b/src/user_signup_session.py index b9d14ed..ae277e5 100644 --- a/src/user_signup_session.py +++ b/src/user_signup_session.py @@ -19,7 +19,7 @@ class UserSignupSession(BaseModel): def deduce_passcode(self, confirm_key_entry: list[int]) -> list[int]: if not all(0 <= key <= self.keypad_size.numb_of_keys for key in confirm_key_entry): raise ValueError("Key values must be within valid range") - attrs_per_key = self.keypad_size.attrs_per_key + attrs_per_key = self.keypad_size.props_per_key set_key_entry = self.set_key_entry if len(set_key_entry) != len(confirm_key_entry): raise ValueError("Key entry lengths must match") diff --git a/test/test_nkode_api.py b/test/test_nkode_api.py index 13b2df3..128880d 100644 --- a/test/test_nkode_api.py +++ b/test/test_nkode_api.py @@ -9,8 +9,8 @@ def nkode_api() -> NKodeAPI: @pytest.mark.parametrize("keypad_size,passocode_len", [ - (KeypadSize(numb_of_keys=10, attrs_per_key=11), 4), - (KeypadSize(numb_of_keys=10, attrs_per_key=12), 5), + (KeypadSize(numb_of_keys=10, props_per_key=11), 4), + (KeypadSize(numb_of_keys=10, props_per_key=12), 5), ]) def test_create_new_user_and_renew_keys(nkode_api, keypad_size, passocode_len): username = "test_username" @@ -32,7 +32,7 @@ def test_create_new_user_and_renew_keys(nkode_api, keypad_size, passocode_len): ) assert successful_confirm - sign_in_key_selection = lambda keypad: [keypad.index(attr) // keypad_size.attrs_per_key for attr in user_passcode] + sign_in_key_selection = lambda keypad: [keypad.index(attr) // keypad_size.props_per_key for attr in user_passcode] login_keypad = nkode_api.get_login_keypad(username, customer_id) login_key_selection = sign_in_key_selection(login_keypad) successful_login = nkode_api.login(customer_id, username, login_key_selection) diff --git a/test/test_nkode_interface.py b/test/test_nkode_interface.py index 7998e77..b56908c 100644 --- a/test/test_nkode_interface.py +++ b/test/test_nkode_interface.py @@ -5,11 +5,11 @@ from src.models import KeypadSize @pytest.mark.parametrize( "keypad_size", - [KeypadSize(numb_of_keys=10, attrs_per_key=11)] + [KeypadSize(numb_of_keys=10, props_per_key=11)] ) def test_attr_set_idx(keypad_size): user_keypad = UserKeypad.create(keypad_size) - for attr_idx in range(keypad_size.numb_of_attrs): + for attr_idx in range(keypad_size.numb_of_props): user_keypad_idx = user_keypad.keypad[attr_idx] - assert (attr_idx % keypad_size.attrs_per_key == user_keypad_idx % keypad_size.attrs_per_key) + assert (attr_idx % keypad_size.props_per_key == user_keypad_idx % keypad_size.props_per_key) diff --git a/test/test_user_cipher_keys.py b/test/test_user_cipher_keys.py index 1b7e11f..e96adbd 100644 --- a/test/test_user_cipher_keys.py +++ b/test/test_user_cipher_keys.py @@ -1,7 +1,7 @@ import pytest from src.models import KeypadSize -from src.user_cipher_keys import UserCipherKeys, CustomerAttributes +from src.user_cipher_keys import UserCipher, CustomerCipher from src.utils import generate_random_nonrepeating_list @@ -13,8 +13,8 @@ from src.utils import generate_random_nonrepeating_list ) def test_encode_decode_base64(passcode_len): data = generate_random_nonrepeating_list(passcode_len) - encoded = UserCipherKeys.encode_base64_str(data) - decoded = UserCipherKeys.decode_base64_str(encoded) + encoded = UserCipher.encode_base64_str(data) + decoded = UserCipher.decode_base64_str(encoded) assert (len(data) == len(decoded)) assert (all(data[idx] == decoded[idx] for idx in range(passcode_len))) @@ -22,21 +22,21 @@ def test_encode_decode_base64(passcode_len): @pytest.mark.parametrize( "keypad_size,max_nkode_len", [ - (KeypadSize(numb_of_keys=10, attrs_per_key=11), 10), - (KeypadSize(numb_of_keys=9, attrs_per_key=11), 10), - (KeypadSize(numb_of_keys=8, attrs_per_key=11), 12), + (KeypadSize(numb_of_keys=10, props_per_key=11), 10), + (KeypadSize(numb_of_keys=9, props_per_key=11), 10), + (KeypadSize(numb_of_keys=8, props_per_key=11), 12), ]) def test_decode_mask(keypad_size, max_nkode_len): - customer = CustomerAttributes.create(keypad_size) + customer = CustomerCipher.create(keypad_size) passcode_entry = generate_random_nonrepeating_list( - keypad_size.numb_of_attrs, - max_val=keypad_size.numb_of_attrs)[:4] - passcode_values = [customer.attr_vals[idx] for idx in passcode_entry] - set_vals = customer.set_vals - user_keys = UserCipherKeys.create(keypad_size, set_vals, max_nkode_len) + keypad_size.numb_of_props, + max_val=keypad_size.numb_of_props)[:4] + passcode_values = [customer.prop_key[idx] for idx in passcode_entry] + set_vals = customer.set_key + user_keys = UserCipher.create(keypad_size, set_vals, max_nkode_len) passcode = user_keys.encipher_nkode(passcode_entry, customer) - orig_passcode_set_vals = [customer.get_attr_set_val(attr) for attr in passcode_values] + orig_passcode_set_vals = [customer.get_prop_set_val(attr) for attr in passcode_values] passcode_set_vals = user_keys.decipher_mask(passcode.mask, set_vals, len(passcode_entry)) assert (len(passcode_set_vals) == len(orig_passcode_set_vals)) assert (all(orig_passcode_set_vals[idx] == passcode_set_vals[idx] for idx in range(len(passcode_set_vals)))) diff --git a/test/test_user_keypad.py b/test/test_user_keypad.py index c1f2439..0929d65 100644 --- a/test/test_user_keypad.py +++ b/test/test_user_keypad.py @@ -5,7 +5,7 @@ from src.models import KeypadSize @pytest.fixture() def user_keypad(): - return UserKeypad.create(keypad_size=KeypadSize(attrs_per_key=7, numb_of_keys=10)) + return UserKeypad.create(keypad_size=KeypadSize(props_per_key=7, numb_of_keys=10)) def test_dispersion(user_keypad): @@ -22,7 +22,7 @@ def test_shuffle_attrs(user_keypad): expected statistical outcomes like: - every attribute gets to every key with a uniform distribution - every attribute is adjacent to every other attribute with uniform distribution - - the order in which the attributes move from key to key is random (i.e. the distance traveled is uniform) + - the order in which the customer_cipher move from key to key is random (i.e. the distance traveled is uniform) """ pre_shuffle_keypad = user_keypad.keypad user_keypad.partial_keypad_shuffle()