diff --git a/docs/__init__.py b/docs/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/readme_template.md b/docs/readme_template.md
deleted file mode 100644
index 820f295..0000000
--- a/docs/readme_template.md
+++ /dev/null
@@ -1,422 +0,0 @@
-# README
-Play around with the code in /notebooks
-
-## Customer Creation
-Before creating a user, a customer generates random properties and set
-values. The customers manage users. They define an nKode policy, keypad's dimensions,
-properties/sets in the keypad, and the frequency of property renewal.
-### nKode Policy and Keypad Size
-An nKode policy defines:
-
- - the maximum length of a user's nKode
- - the minimum length of a user's nKode
- - the number of unique set values in a user's nKode
- - the number of unique values in a user's nKode
- - the number of bytes in an property and set
-
-
-The keypad size defines:
-
- - the number of keys in the keypad displayed to the user
- - properties per key
-
-
-To be [dispersion](nkode_concepts.md/#dispersion-resistant-interface) resistant, the number of properties must be greater than the number of keys.
-
-```
-api = NKodeAPI()
-
-policy = NKodePolicy(
- max_nkode_len=10,
- min_nkode_len=4,
- distinct_sets=0,
- distinct_properties=4,
- byte_len=2
-)
-
-keypad_size = KeypadSize(
- numb_of_keys = {{ keypad_size.numb_of_keys }},
- props_per_key = {{ keypad_size.props_per_key }} # aka number of sets
-)
-
-customer_id = api.create_new_customer(keypad_size, policy)
-customer = api.customers[customer_id]
-```
-### Customer properties and Sets
-A customer has users and defines the properties and set values for all its users.
-Since our customer has {{ keypad_size.numb_of_keys }} keys and {{ keypad_size.props_per_key }} properties per key,
-this gives a customer interface of {{ keypad_size.numb_of_props }} distinct properties and {{ keypad_size.props_per_key }} distinct property sets.
-Each property belongs to one of the {{ keypad_size.props_per_key }} sets. Each property and set value is a unique 2-byte integer in this example.
-
-```
-set_vals = customer.cipher.set_key
-
-Customer Sets: {{ customer_set_vals }}
-```
-
-```
-prop_vals = customer.cipher.prop_key
-keypad_view(prop_vals, keypad_size.props_per_key)
-
-Customer properties:
-{% for props in customer_prop_view -%}
-{{ props }}
-{% endfor %}
-```
-
-properties organized by set:
-```
-prop_set_view = matrix_transpose(prop_keypad_view)
-set_property_dict = dict(zip(set_vals, prop_set_view))
-
-Set to property Map:
-{% for set_val, props in set_property_dict.items() -%}
-{{ set_val }} : {{ props }}
-{% endfor %}
-```
-
-## User Signup
-Now that we have a customer, we can create users. To create a new user:
-
-1. Generate a random interface
-2. The user sets their nKode and sends their selection to the server
-3. The user confirms their nKode. If the user's nKode matches the policy, the server creates the user.
-### Random Interface Generation
-The user's interface must be dispersable so the server can determine the user's nkode.
-The server randomly drops property sets until
-the number of properties equals the number of keys, making the interface dispersable.
-In our case, the server randomly drops {{ keypad_size.props_per_key - keypad_size.numb_of_keys }} property {{ "sets" if keypad_size.props_per_key - keypad_size.numb_of_keys > 1 else "set" }}.
-to give us a {{ keypad_size.numb_of_keys }} X {{ keypad_size.numb_of_keys }} keypad with possible index values ranging from 0-{{ keypad_size.numb_of_props - 1 }}.
-Each value in the interface is the index value of a customer property.
-The user never learns what their "real" property is. They do not see the index value representing their nKode or
-the customer server-side value.
-
-```
-session_id, signup_interface = api.generate_index_interface(customer_id)
-signup_interface_keypad = list_to_matrix(signup_interface, keypad_size.props_per_key)
-
-Signup Keypad:
-{% for key in signup_keypad -%}
-Key {{ loop.index }}: {{ key }}
-{% endfor %}
-```
-
-### Set nKode
-The user identifies properties in the interface they want in their nkode. Each property has an index value.
-Below, the user has selected `{{ user_passcode }}`. These index values can be represented by anything in the GUI.
-The only requirement is that the GUI properties be associated with the same index every time the user logs in.
-If users want to change anything about their interface, they must also change their nkode.
-
-```
-username = {{ username }}
-user_passcode = {{ user_passcode }}
-selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.props_per_key)
-
-Selected Keys
-{{ selected_keys_set }}
-```
-
-The user's passcode server side properties are:
-```
-server_side_prop = [customer.cipher.prop_key[idx] for idx in user_passcode]
-
-User Passcode Server-side properties: {{ server_side_prop }}
-```
-
-### Confirm nKode
-The user submits the set interface to the server and receives the _confirm interface_ as a response.
-The user finds their nKode again.
-
-```
-confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id)
-keypad_view(confirm_interface, keypad_size.numb_of_keys)
-selected_keys_confirm = select_keys_with_passcode_values(user_passcode, confirm_interface, keypad_size.numb_of_keys)
-
-Confirm Keypad:
-{% for key in confirm_keypad -%}
-Key {{ loop.index }}: {{ key }}
-{% endfor %}
-Selected Keys:
-{{ selected_keys_confirm }}
-```
-
-The user submits their confirmation key selection and the user is created
-```
-success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id)
-```
-
-### Passcode Enciphering, Hashing, and Salting
-When a new user creates an nKode, the server caches its set and confirms the interface and the user's key selection.
-On the last api.confirm_nkode, the server:
-
-1. Deduces the user's properties
-2. Validates the Passcode against the nKodePolicy
-3. Creates new User Cipher Keys
-4. Enciphers the user's mask
-5. Enciphers, salts, and hashes the user's passcode
-
-Steps 1-2 are straightforward. For a better idea of how they work, see pyNKode.
-
-#### User Cipher Keys
-
-##### User Cipher Keys Data Structure
-```
-set_key = generate_random_nonrepeating_list(keypad_size.props_per_key, max_numb=2**(8*numb_of_bytes))
-set_key = xor_lists(set_key, customer_prop.set_vals)
-
-UserCipherKeys(
- prop_key=generate_random_nonrepeating_list(keypad_size.props_per_key * keypad_size.numb_of_keys, max_numb=2**(8*numb_of_bytes)),
- pass_key=generate_random_nonrepeating_list(max_nkode_len, max_numb=2**(8*numb_of_bytes)),
- mask_key=generate_random_nonrepeating_list(max_nkode_len, max_numb=2**(8*numb_of_bytes)),
- set_key=set_key,
- salt=bcrypt.gensalt(),
- max_nkode_len=max_nkode_len
-)
-```
-
-##### User Cipher Keys Values
-```
-user_cipher = UserCipherKeys(
- prop_key = {{ user_cipher.prop_key }},
- pass_key = {{ user_cipher.pass_key }},
- mask_key = {{ user_cipher.mask_key }},
- set_key = {{ user_cipher.set_key }},
- salt = {{ user_cipher.salt }},
- max_nkode_len = {{ user_cipher.max_nkode_len }}
-)
-```
-
-The method UserCipherKeys.encipher_nkode secures a user's nKode in the database. This method is called in api.confirm_nkode
-
-```
-class EncipheredNKode(BaseModel):
- code: str
- mask: str
-```
-
-#### Mask Enciphering
-
-Recall:
-
-- set_key_i = (set_rand_numb_i ^ set_val_i)
-- mask_key_i = mask_rand_numb_i
-- padded_passcode_server_set_i = set_val_i
-- len(set_key) == len(mask_key) == (padded_passcode_server_set) == max_nkode_len == 10
- where i is the index
-
-- mask_i = mask_key_i ^ padded_passcode_server_set_i ^ set_key_i
-- mask_i = mask_rand_num_i ^ set_val_i ^ set_rand_numb_i ^ set_val_i
-- mask_i = mask_rand_num_i ^ set_rand_numb_i # set_val_i is cancelled out
-
-
-```
-passcode = {{ user_passcode }}
-passcode_server_prop = [customer.cipher.prop_key[idx] for idx in passcode]
-passcode_server_set = [customer.cipher.get_prop_set_val(prop) for prop in passcode_server_prop]
-
-Passcode Set Vals: {{ passcode_server_prop }}
-Passcode prop Vals: {{ passcode_server_set }}
-```
-
-```
-padded_passcode_server_set = user_cipher.pad_user_mask(passcode_server_set, customer.nkode_policy.max_nkode_len)
-
-set_idx = [customer.cipher.get_set_index(set_val) for set_val in padded_passcode_server_set]
-mask_set_keys = [user_cipher.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_cipher.mask_key)
-
-mask = user_cipher.encode_base64_str(ciphered_mask)
-Mask: {{ enciphered_nkode.mask }}
-```
-
-#### Passcode Enciphering and Hashing
-
-- ciphered_customer_prop = prop_key ^ customer_prop
-- ciphered_passcode_i = pass_key_i ^ ciphered_customer_prop_i
-- code = hash(ciphered_passcode, salt)
-
-```
-ciphered_customer_props = xor_lists(customer.cipher.prop_key, user_cipher.prop_key)
-passcode_ciphered_props = [ciphered_customer_props[idx] for idx in passcode]
-pad_len = customer.nkode_policy.max_nkode_len - passcode_len
-
-passcode_ciphered_props.extend([0 for _ in range(pad_len)])
-
-ciphered_code = xor_lists(passcode_ciphered_props, user_cipher.pass_key)
-
-passcode_bytes = int_array_to_bytes(ciphered_code)
-passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest())
-hashed_data = bcrypt.hashpw(passcode_digest, user_cipher.salt)
-code = hashed_data.decode("utf-8")
-
-Code: {{ enciphered_nkode.code }}
-```
-
-## User Login
-To login, a user:
-
-1. Gets login interface
-2. Submits key entry
-
-### Get Login Interface
-The client requests the user's login interface.
-```
-login_interface = api.get_login_interface(username, customer_id)
-keypad_view(login_interface, keypad_size.props_per_key)
-```
-The server returns a randomly shuffled interface. Learn more about how the [User Interface Shuffle](nkode_concepts.md/#user-interface-shuffle) works
-```
-Login Interface Keypad View:
-{% for key in login_keypad -%}
-Key {{ loop.index }}: {{ key }}
-{% endfor %}
-```
-Recall the user's passcode is `user_passcode = {{ user_passcode }}` so the user selects keys ` selected_keys_login = {{ selected_login_keys }}`
-
-```
-success = api.login(customer_id, username, selected_keys_login)
-```
-
-### Validate Login Key Entry
-- decipher user mask and recover nkode set values
-- get presumed property from key selection and set values
-- encipher, salt, and hash presumed property values and compare them to the users hashed code
-
-#### Decipher Mask
-
-Recall:
-
-- set_key_i = (set_key_rand_numb_i ^ set_val_i)
-- mask_i = mask_key_rand_num_i ^ set_key_rand_numb_i
-
-Recover nKode set values:
-
-- decode mask from base64 to int
-- deciphered_mask = mask ^ mask_key
-- deciphered_mask_i = set_key_rand_numb # mask_key_rand_num_i is cancelled out
-- set_key_rand_component = set_key ^ set_values
-- deduce the set value
-
-```
-user = customer.users[username]
-user_cipher = user.user_cipher
-user_mask = user.enciphered_passcode.mask
-decoded_mask = user_cipher.decode_base64_str(user_mask)
-deciphered_mask = xor_lists(decoded_mask, user_cipher.mask_key)
-set_key_rand_component = xor_lists(set_vals, user_cipher.set_key)
-passcode_sets = []
-for set_cipher in deciphered_mask[:passcode_len]:
- set_idx = set_key_rand_component.index(set_cipher)
- passcode_sets.append(set_vals[set_idx])
-
-Passcode Sets: {{ login_passcode_sets }}
-```
-
-
-### Get Presumed properties
-```
-set_vals_idx = [customer.cipher.get_set_index(set_val) for set_val in passcode_sets]
-
-presumed_selected_properties_idx = []
-for idx in range(passcode_len):
- key_numb = selected_keys_login[idx]
- set_idx = set_vals_idx[idx]
- selected_prop_idx = customer.users[username].user_interface.get_prop_idx_by_keynumb_setidx(key_numb, set_idx)
- presumed_selected_properties_idx.append(selected_prop_idx)
-
-Presumped Passcode: {{ presumed_selected_properties_idx }}
-Recall User Passcode: {{ user_passcode }}
-```
-### Compare Enciphered Passcodes
-```
-enciphered_nkode = user_cipher.encipher_salt_hash_code(presumed_selected_properties_idx, customer.cipher)
-```
-If `enciphered_nkode == user.enciphered_passcode.code`, the user's key selection is valid, and the login is successful.
-
-## Renew properties
-properties renew is invoked with the renew_properties method: `api.renew_properties(customer_id)`
-The renew properties process has three steps:
-1. Renew Customer properties
-2. Renew User Keys
-3. Refresh User on Login
-
-When the customer calls the `renew_properties` method, the method replaces the customer's properties and set values. All its users go through an intermediate
-renewal step. The users fully renew after their first successful login. This first login refreshes their keys, salt, and hash with new values.
-
-
-### Customer Renew
-Old Customer properties and set values are cached and copied to variables before renewal.
-```
-old_sets = customer.cipher.set_key
-
-Customer Sets: {{ customer_set_vals }}
-```
-
-```
-old_prop = customer.cipher.prop_key
-
-Customer properties:
-{% for props in customer_prop_view -%}
-{{ props }}
-{% endfor %}
-```
-
-After the renewal, the customer properties and sets are new randomly generated values.
-```
-api.renew_properties(customer_id)
-
-set_vals = customer.cipher.set_key
-
-Customer Sets: {{ customer_new_set_vals }}
-```
-
-```
-prop_vals = customer.cipher.prop_key
-
-Customer properties:
-{% for props in customer_new_prop_view -%}
-{{ props }}
-{% endfor %}
-```
-
-### Renew User
-During the renewal, each user goes through a temporary transition period.
-```
-props_xor = xor_lists(new_props, old_props)
-sets_xor = xor_lists(new_sets, old_sets)
-for user in customer.users.values():
- user.renew = True
- user.user_cipher.set_key = xor_lists(user.user_cipher.set_key, sets_xor)
- user.user_cipher.prop_key = xor_lists(user.user_cipher.prop_key, props_xor)
-```
-##### User prop Key
-The user's prop key was a randomly generated list of length `numb_of_keys * prop_per_key`.
-Now each value in the prop key is `prop_key_i = old_prop_key_i ^ new_prop_i ^ old_prop_i`.
-Recall in the login process, `ciphered_customer_props = prop_key ^ customer_prop`.
-Since the customer_prop is now the new value, it gets canceled out, leaving:
-```
-new_prop_key = old_prop_key ^ old_prop ^ new_prop
-ciphered_customer_props = new_prop_key ^ new_prop
-ciphered_customer_props = old_prop_key ^ old_prop # since new_prop cancel out
-```
-Using the new customer properties, we can validate the user's login attempt with the same hash.
-
-##### User Set Key
-The user's set key was a randomly generated list of length `prop_per_key` xor `customer_set_vals`.
-The `old_set_vals` have been replaced with the new `new_set_vals`. The deciphering process described above
-remains the same.
-
-### User Refresh
-Once the user has a successful login, they get a new salt and cipher keys, and their `enciphered_passcode` is recomputed
-with the new values.
-```
-user.user_cipher = UserCipherKeys.new(
- customer.cipher.keypad_size,
- customer.cipher.set_key,
- user.user_cipher.max_nkode_len
-)
-user.enciphered_passcode = user.user_cipher.encipher_nkode(presumed_selected_properties_idx, customer.cipher)
-user.renew = False
-```
diff --git a/docs/render_readme.py b/docs/render_readme.py
deleted file mode 100644
index 92238e0..0000000
--- a/docs/render_readme.py
+++ /dev/null
@@ -1,204 +0,0 @@
-import numpy as np
-from jinja2 import Environment, FileSystemLoader
-import os
-from src.nkode_api import NKodeAPI
-from src.models import NKodePolicy, KeypadSize, EncipheredNKode
-from src.user_cipher import UserCipher
-from secrets import choice
-from string import ascii_lowercase
-import bcrypt
-import hashlib
-import base64
-
-
-def random_username() -> str:
- return "test_username" + "".join([choice(ascii_lowercase) for _ in range(6)])
-
-
-def select_keys_with_passcode_values(user_passcode: list[int], keypad: np.ndarray, props_per_key: int) -> list[int]:
- indices = [np.where(keypad == prop)[0][0] for prop in user_passcode]
- return [int(index // props_per_key) for index in indices]
-
-
-def visualize_keypad(keypad_list: np.ndarray, props_per_key: int):
- print("Keypad View")
- keypad_mat = keypad_list.reshape(-1, props_per_key)
- for idx, key_vals in enumerate(keypad_mat):
- print(f"Key {idx}: {key_vals}")
-
-
-def render_nkode_authentication(data: dict):
- # Set up the Jinja2 environment and template loader
- file_loader = FileSystemLoader('')
- env = Environment(loader=file_loader)
-
- # Load the template
- template = env.get_template('readme_template.md')
-
- print(os.getcwd())
- # Render the template with the data
- output = template.render(data)
-
- # Print or save the output
- # output_file = os.path.expanduser("~/Desktop/nkode_authentication.md")
- output_file = '../README.md'
- with open(output_file, 'w') as fp:
- fp.write(output)
- print("File written successfully")
-
-
-if __name__ == "__main__":
- api = NKodeAPI()
-
- policy = NKodePolicy(
- max_nkode_len=10,
- min_nkode_len=4,
- distinct_positions=0,
- distinct_properties=4,
- byte_len=2,
- )
- keypad_size = KeypadSize(
- numb_of_keys=5,
- 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.cipher.position_key
- prop_vals = customer.cipher.property_key
- customer_prop_view = prop_vals.reshape(-1, keypad_size.props_per_key)
-
- prop_keypad_view = prop_vals.reshape(-1, keypad_size.props_per_key)
- prop_set_view = prop_keypad_view.T
- set_property_dict = dict(zip(set_vals, prop_set_view))
-
- session_id, signup_interface = api.generate_signup_keypad(customer_id)
- signup_keypad = signup_interface.reshape(-1, keypad_size.numb_of_keys)
-
- username = random_username()
- passcode_len = 4
- user_passcode = signup_interface[:passcode_len].tolist()
- selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.numb_of_keys)
- server_side_prop = [customer.cipher.property_key[idx] for idx in user_passcode]
-
- confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id)
-
- confirm_keypad = confirm_interface.reshape(-1, keypad_size.numb_of_keys)
-
- selected_keys_confirm = select_keys_with_passcode_values(user_passcode, confirm_interface, keypad_size.numb_of_keys)
-
- success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id)
- assert success
- passcode_server_prop = [customer.cipher.property_key[idx] for idx in user_passcode]
- passcode_server_set = customer.cipher.get_props_position_vals(user_passcode)
- user_keys = customer.users[username].cipher
- # TODO: pad_user_mask is deprecated
- padded_passcode_server_set = user_keys.pad_user_mask(np.array(passcode_server_set), customer.cipher.position_key)
-
- set_idx = [customer.cipher.get_position_index(set_val) for set_val in padded_passcode_server_set]
- mask_set_keys = [user_keys.combined_position_key[idx] for idx in set_idx]
- ciphered_mask = mask_set_keys ^ padded_passcode_server_set ^ user_keys.mask_key
- mask = user_keys.encode_base64_str(ciphered_mask)
- ciphered_customer_props = customer.cipher.property_key ^ user_keys.property_key
- passcode_ciphered_props = [ciphered_customer_props[idx] for idx in user_passcode]
- pad_len = customer.nkode_policy.max_nkode_len - passcode_len
- passcode_ciphered_props.extend([0 for _ in range(pad_len)])
- ciphered_code = np.bitwise_xor(passcode_ciphered_props, user_keys.pass_key)
- passcode_bytes = ciphered_code.tobytes()
- passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest())
- hashed_data = bcrypt.hashpw(passcode_digest, bcrypt.gensalt(rounds=12))
- code = hashed_data.decode("utf-8")
-
- enciphered_nkode = EncipheredNKode(
- mask=mask,
- code=code,
- )
- """
- USER LOGIN
- """
- login_interface = api.get_login_keypad(username, customer_id)
- login_keypad = login_interface.reshape(-1, 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
-
- """
- VALIDATE LOGIN KEY ENTRY
- DECIPHER MASK
- """
-
- user = customer.users[username]
- set_vals = customer.cipher.position_key
- user_keys = user.cipher
- user_mask = user.enciphered_passcode.mask
- decoded_mask = user_keys.decode_base64_str(user_mask)
- deciphered_mask = np.bitwise_xor(decoded_mask, user_keys.mask_key)
- set_key_rand_component = np.bitwise_xor(set_vals, user_keys.combined_position_key)
- login_passcode_sets = []
- for set_cipher in deciphered_mask[:passcode_len]:
- set_idx = np.where(set_key_rand_component == set_cipher)[0][0]
- login_passcode_sets.append(int(set_vals[set_idx]))
-
- """
- GET PRESUMED properties
- """
-
- set_vals_idx = [customer.cipher.get_position_index(set_val) for set_val in login_passcode_sets]
- presumed_selected_properties_idx = customer.users[username].user_keypad.get_prop_idxs_by_keynumb_setidx(selected_keys_login, set_vals_idx)
- """
- RENEW KEYS
- """
-
- old_props = customer.cipher.property_key.copy()
- old_sets = customer.cipher.position_key.copy()
- customer.cipher.renew()
- new_props = customer.cipher.property_key
- new_sets = customer.cipher.position_key
- customer_new_prop_view = new_props.reshape(-1, keypad_size.props_per_key)
- """
- RENEW USER
- """
- props_xor = np.bitwise_xor(new_props, old_props)
- sets_xor = np.bitwise_xor(new_sets, old_sets)
- for user in customer.users.values():
- user.renew = True
- user.cipher.combined_position_key = np.bitwise_xor(user.cipher.combined_position_key, sets_xor)
- user.cipher.property_key = np.bitwise_xor(user.cipher.property_key, props_xor)
-
- """
- REFRESH USER KEYS
- """
- user.cipher = UserCipher.create(
- customer.cipher.keypad_size,
- customer.cipher.position_key,
- user.cipher.max_nkode_len
- )
- user.enciphered_passcode = user.cipher.encipher_nkode(presumed_selected_properties_idx, customer.cipher)
- user.renew = False
-
- # Define some data to pass to the template
- data = {
- 'keypad_size': keypad_size,
- 'customer_set_vals': set_vals,
- 'customer_prop_view': customer_prop_view,
- 'set_property_dict': set_property_dict,
- 'set_signup_keypad': signup_keypad,
- 'username': 'test_user',
- 'passcode_property_indices': user_passcode,
- 'selected_keys_set': selected_keys_set,
- 'server_side_prop': server_side_prop,
- 'confirm_keypad': confirm_keypad,
- 'selected_keys_confirm': selected_keys_confirm,
- 'user_cipher': user_keys,
- 'ordered_customer_prop_key': passcode_server_prop,
- 'ordered_customer_set_key': passcode_server_set,
- 'enciphered_nkode': enciphered_nkode,
- 'login_keypad': login_keypad,
- 'selected_login_keys': selected_keys_login,
- 'login_passcode_sets': login_passcode_sets,
- 'presumed_selected_properties_idx': presumed_selected_properties_idx,
- 'customer_new_prop_view': customer_new_prop_view,
- 'customer_new_set_vals': new_sets,
-
- }
- render_nkode_authentication(data)
\ No newline at end of file