7 Commits

70 changed files with 1374 additions and 4489 deletions

View File

@@ -1,36 +1,90 @@
# Evil nKode
# Evilnkode Project
Simulated nKode Cracker
This README provides instructions for setting up and running the `evilnkode` project using Conda, activating the environment, and executing the provided CLI scripts. It also covers how to access help for command-line options.
## Installation
## Prerequisites
- Python version 3.10 or greater is required
- Install anaconda (or your preferred tool for environment management)
- **Conda**: Ensure you have Conda installed (Miniconda or Anaconda). Download from [conda.io](https://conda.io).
### Using conda
## Setting Up the Environment
To set up the project environment using the provided `environment.yaml` file, follow these steps:
1. **Install the environment**:
- Ensure you are in the project root directory where `environment.yaml` is located.
- Run the following command to create the `evilnkode` environment:
```bash
conda env create -f environment.yml
conda activate pynkode
conda env create -f environment.yaml
```
- This will install all dependencies specified in `environment.yaml`.
## Activating the Environment
To activate the `evilnkode` environment, run:
```bash
conda activate evilnkode
```
## Starting a Jupyter Notebook
Once activated, your terminal prompt should change to include `(evilnkode)`, indicating the environment is active.
## Running CLI Scripts
The project includes two main CLI scripts: `cli.visualnkode` and `cli.benchmark_histogram`. Below are instructions to run each.
### Running `cli.visualnkode`
To execute the `visualnkode` CLI script:
### Option 1: Using classic Jupyter Notebook
```bash
# Ensure your environment is activated
# For conda: conda activate pynkode
# For pyenv: (should be automatic if in the directory)
# Start the Jupyter Notebook server
jupyter notebook
python -m cli.visualnkode
```
### Option 2: Using JupyterLab
- This command runs the `visualnkode` module from the `cli` package.
- To view available options and arguments, use the `-help` flag:
```bash
# Ensure your environment is activated
# Start JupyterLab
jupyter lab
python -m cli.visualnkode -help
```
## Notebooks
- [evilnkode](notebooks/evilkode.ipynb)
### Running `cli.benchmark_histogram`
To execute the `benchmark_histogram` CLI script:
```bash
python -m cli.benchmark_histogram
```
- This command runs the `benchmark_histogram` module, which may generate output such as benchmark results or histograms. For example, it might produce output like:
```
File exists: output/slidingtowershufflekeypad-6-8-4-5-4-4-10000/benchmark/slidingtowershufflekeypad-6-8-4-5-4-4-10000.pkl
Bench SlidingTowerShuffle Break 5
Bench SlidingTowerShuffle Replay 5
```
- To view available options and arguments, use the `-help` flag:
```bash
python -m cli.benchmark_histogram -help
```
## Using the `-help` Flag
Both CLI scripts (`cli.visualnkode` and `cli.benchmark_histogram`) support a `-help` flag to display available command-line options and their descriptions. Run the following to explore options for each script:
```bash
python -m cli.visualnkode -help
python -m cli.benchmark_histogram -help
```
This will provide detailed information about parameters, flags, and usage for each script.
## Project Structure
Key files and directories in the project:
- `environment.yaml`: Conda environment configuration file.
- `cli/`: Contains CLI scripts (`visualnkode` and `benchmark_histogram`).
- `output/`: Directory where script outputs, such as benchmark results, are stored.
- `src/`: Source code for the project.
- `tests/`: Test scripts for the project.
- `requirements.txt`: Additional dependencies (if needed outside Conda).
For further details, explore the project documentation in the `docs/` directory.

0
cli/__init__.py Normal file
View File

115
cli/benchmark_histogram.py Normal file
View File

@@ -0,0 +1,115 @@
import argparse
from src.benchmark import benchmark
import matplotlib.pyplot as plt
from pathlib import Path
from statistics import mean
from src.keypad.keypad import (
RandomSplitShuffleKeypad,
RandomShuffleKeypad,
SlidingSplitShuffleKeypad,
SlidingTowerShuffleKeypad,
)
def bench_histogram(data, title, number_of_keys, properties_per_key, passcode_len, max_tries_before_lockout, complexity,
disparity, run_count, save_path: Path = None):
min_val = min(data)
max_val = max(data)
bins = range(min_val, max_val + 2)
plt.hist(data, bins=bins, edgecolor='black')
plt.title(title)
plt.xlabel('# of Login Observations')
plt.ylabel('Simulations')
text = (f"number_of_keys={number_of_keys}\n"
f"properties_per_key={properties_per_key}\n"
f"passcode_len={passcode_len}\n"
f"max_tries_before_lockout={max_tries_before_lockout}\n"
f"complexity={complexity}\n"
f"disparity={disparity}\n"
f"run_count={run_count}")
plt.text(0.95, 0.95, text, transform=plt.gca().transAxes, fontsize=10,
verticalalignment='top', horizontalalignment='right', bbox=dict(facecolor='white', alpha=0.5))
if save_path:
save_path = save_path / "histogram"
save_path.mkdir(parents=True, exist_ok=True)
filename = (f"{title.replace(' ', '_')}_keys{number_of_keys}_"
f"props{properties_per_key}_pass{passcode_len}_tries{max_tries_before_lockout}_"
f"comp{complexity}_disp{disparity}_runs{run_count}.png")
plt.savefig(save_path / filename, bbox_inches='tight', dpi=300)
plt.close()
def main():
parser = argparse.ArgumentParser(description='Benchmark Keypad Shuffle')
parser.add_argument('--shuffle_type', type=str,
choices=['RandomSplitShuffle', 'RandomShuffle', 'SlidingSplitShuffle', 'SlidingTowerShuffle'],
default='SlidingTowerShuffle', help='Type of keypad shuffle')
parser.add_argument('--number_of_keys', type=int, default=6, help='Number of keys')
parser.add_argument('--properties_per_key', type=int, default=8, help='Properties per key')
parser.add_argument('--passcode_len', type=int, default=4, help='Passcode length')
parser.add_argument('--max_tries_before_lockout', type=int, default=5, help='Max tries before lockout')
parser.add_argument('--complexity', type=int, default=4, help='Complexity')
parser.add_argument('--disparity', type=int, default=4, help='Disparity')
parser.add_argument('--run_count', type=int, default=10000, help='Number of runs')
parser.add_argument('--output_dir', type=str, default='./output',
help='Output directory for histograms')
args = parser.parse_args()
shuffle_classes = {
'RandomSplitShuffle': RandomSplitShuffleKeypad,
'RandomShuffle': RandomShuffleKeypad,
'SlidingSplitShuffle': SlidingSplitShuffleKeypad,
'SlidingTowerShuffle': SlidingTowerShuffleKeypad
}
keypad_class = shuffle_classes[args.shuffle_type]
keypad = keypad_class.new_keypad(args.number_of_keys, args.properties_per_key)
shuffle_type = str(type(keypad)).lower().split('.')[-1].replace("'>", "")
run_name = f"{shuffle_type}-{args.number_of_keys}-{args.properties_per_key}-{args.passcode_len}-{args.max_tries_before_lockout}-{args.complexity}-{args.disparity}-{args.run_count}"
save_path = Path(args.output_dir) / run_name
bench_result = benchmark(
number_of_keys=args.number_of_keys,
properties_per_key=args.properties_per_key,
passcode_len=args.passcode_len,
max_tries_before_lockout=args.max_tries_before_lockout,
run_count=args.run_count,
complexity=args.complexity,
disparity=args.disparity,
keypad=keypad,
file_path=save_path,
)
print(f"Bench {args.shuffle_type} Break {mean(bench_result.iterations_to_break)}")
print(f"Bench {args.shuffle_type} Replay {mean(bench_result.iterations_to_replay)}")
bench_histogram(
bench_result.iterations_to_break,
f"{args.shuffle_type} Break",
args.number_of_keys,
args.properties_per_key,
args.passcode_len,
args.max_tries_before_lockout,
args.complexity,
args.disparity,
args.run_count,
save_path
)
bench_histogram(
bench_result.iterations_to_replay,
f"{args.shuffle_type} Replay",
args.number_of_keys,
args.properties_per_key,
args.passcode_len,
args.max_tries_before_lockout,
args.complexity,
args.disparity,
args.run_count,
save_path,
)
if __name__ == "__main__":
main()

View File

@@ -1,45 +1,41 @@
import json
from typing import Iterable
from dataclasses import dataclass, asdict
from evilkode import Observation
from utils import observations, passcode_generator, ShuffleTypes
from src.evilnkode import Observation
from src.utils import observations, passcode_generator
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
from typing import Iterable
from src.keypad.keypad import (
BaseKeypad,
SlidingSplitShuffleKeypad,
SlidingTowerShuffleKeypad,
RandomShuffleKeypad,
RandomSplitShuffleKeypad,
)
import argparse
# Project root = parent of *this* file's directory
PROJECT_ROOT = Path(__file__).resolve().parent.parent
OUTPUT_DIR = PROJECT_ROOT / "example" / "obs_json"
PNG_DIR = PROJECT_ROOT / "example" / "obs_png"
@dataclass
class ObservationSequence:
target_passcode: list[int]
observations: list[Observation]
def new_observation_sequence(
number_of_keys: int,
properties_per_key: int,
keypad: BaseKeypad,
passcode_len: int,
complexity: int,
disparity: int,
numb_runs: int,
) -> ObservationSequence:
passcode = passcode_generator(number_of_keys, properties_per_key, passcode_len, complexity, disparity)
obs_seq = ObservationSequence(target_passcode=passcode, observations=[])
passcode = passcode_generator(keypad.k, keypad.p, passcode_len, complexity, disparity)
obs_gen = observations(
keypad=keypad,
target_passcode=passcode,
number_of_keys=number_of_keys,
properties_per_key=properties_per_key,
min_complexity=complexity,
min_disparity=disparity,
shuffle_type=ShuffleTypes.SPLIT_SHUFFLE,
number_of_observations=numb_runs,
)
for obs in obs_gen:
obs.keypad = obs.keypad.tolist()
obs_seq.observations.append(obs)
return ObservationSequence(target_passcode=passcode, observations=[obs for obs in obs_gen])
return obs_seq
def _next_json_filename(base_dir: Path) -> Path:
"""Find the next available observation_X.json file in base_dir."""
@@ -50,22 +46,15 @@ def _next_json_filename(base_dir: Path) -> Path:
return candidate
counter += 1
def save_observation_sequence_to_json(seq: ObservationSequence, filename: Path | None = None) -> None:
"""
Save ObservationSequence to JSON.
- If filename is None, put it under PROJECT_ROOT/output/obs_json/ as observation_{n}.json
- Creates directory if needed
"""
if filename is None:
base_dir = OUTPUT_DIR
base_dir.mkdir(parents=True, exist_ok=True)
filename = _next_json_filename(base_dir)
else:
def save_observation_sequence_to_json(seq: ObservationSequence,
filename: Path) -> None:
filename.parent.mkdir(parents=True, exist_ok=True)
with filename.open("w", encoding="utf-8") as f:
json.dump(asdict(seq), f, indent=4)
# ---------- Helpers ----------
def _load_font(preferred: str, size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
"""Try a preferred TTF, fall back to common monospace, then PIL default."""
@@ -83,14 +72,17 @@ def _load_font(preferred: str, size: int) -> ImageFont.FreeTypeFont | ImageFont.
continue
return ImageFont.load_default()
def _text_size(draw: ImageDraw.ImageDraw, text: str, font: ImageFont.ImageFont) -> tuple[int, int]:
"""Get (w, h) using font bbox for accurate layout."""
left, top, right, bottom = draw.textbbox((0, 0), text, font=font)
return right - left, bottom - top
return int(right - left), int(bottom - top)
def _join_nums(nums: Iterable[int]) -> str:
return " ".join(str(n) for n in nums)
def _next_available_path(path: Path) -> Path:
"""If path exists, append _1, _2, ..."""
if not path.exists():
@@ -103,6 +95,7 @@ def _next_available_path(path: Path) -> Path:
return candidate
i += 1
# ---------- Core rendering ----------
def render_observation_to_png(
target_passcode: list[int],
@@ -213,6 +206,7 @@ def render_observation_to_png(
img.save(out_path, format="PNG")
def _next_run_dir(base_dir: Path) -> Path:
"""Find the next available run directory under base_dir (run_001, run_002, ...)."""
counter = 1
@@ -223,21 +217,37 @@ def _next_run_dir(base_dir: Path) -> Path:
return run_dir
counter += 1
def render_sequence_to_pngs(seq: ObservationSequence, out_dir: Path | None = None) -> None:
"""
Render each observation to its own PNG inside a fresh run directory.
Default: PROJECT_ROOT/output/obs_png/run_XXX/observation_001.png
"""
base_dir = PNG_DIR if out_dir is None else out_dir
base_dir.mkdir(parents=True, exist_ok=True)
# Create a fresh run dir
run_dir = _next_run_dir(base_dir)
def render_sequence_to_pngs(seq: ObservationSequence, out_dir: Path) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
run_dir = _next_run_dir(out_dir)
for i, obs in enumerate(seq.observations, start=1):
filename = run_dir / f"observation_{i:03d}.png"
render_observation_to_png(seq.target_passcode, obs, filename)
if __name__ == "__main__":
obs_seq = new_observation_sequence(6, 9,4,0,0,numb_runs=50)
save_observation_sequence_to_json(obs_seq)
render_sequence_to_pngs(obs_seq)
shuffle_classes = {
'RandomSplitShuffle': RandomSplitShuffleKeypad,
'RandomShuffle': RandomShuffleKeypad,
'SlidingSplitShuffle': SlidingSplitShuffleKeypad,
'SlidingTowerShuffle': SlidingTowerShuffleKeypad
}
parser = argparse.ArgumentParser(description="Generate and save observation sequences with optional PNG rendering.")
parser.add_argument("--number-of-keys", type=int, default=6, help="Number of keys in the keypad (default: 6)")
parser.add_argument("--properties-per-key", type=int, default=9, help="Properties per key (default: 9)")
parser.add_argument("--passcode-length", type=int, default=4, help="Length of the passcode (default: 4)")
parser.add_argument("--complexity", type=int, default=0, help="Complexity of the passcode (default: 0)")
parser.add_argument("--disparity", type=int, default=0, help="Disparity of the passcode (default: 0)")
parser.add_argument("--num-runs", type=int, default=50, help="Number of observations to generate (default: 50)")
parser.add_argument("--shuffle-type", type=str, default="SlidingTowerShuffle", choices=list(shuffle_classes.keys()),
help="Keypad shuffle type: 'RandomShuffle' or 'SlidingTowerShuffle' (default: SlidingTowerShuffle)")
parser.add_argument("--output-dir", type=str, default="./output",
help="Custom output directory for JSON and PNG files")
args = parser.parse_args()
keypad = shuffle_classes[args.shuffle_type].new_keypad(6, 9)
obs_seq = new_observation_sequence(keypad, 4, 0, 0, numb_runs=50)
shuffle_type = str(type(keypad)).lower().split('.')[-1].replace("'>", "")
output_dir = Path(args.output_dir)
save_observation_sequence_to_json(obs_seq, output_dir / "obs.json")
render_sequence_to_pngs(obs_seq, output_dir / "obs_png")

544
environment.yaml Normal file
View File

@@ -0,0 +1,544 @@
name: base
channels:
- defaults
dependencies:
- _anaconda_depends=2024.10=py312_openblas_0
- aiobotocore=2.12.3=py312hca03da5_0
- aiohappyeyeballs=2.4.3=py312hca03da5_0
- aiohttp=3.10.5=py312h80987f9_0
- aioitertools=0.7.1=pyhd3eb1b0_0
- aiosignal=1.2.0=pyhd3eb1b0_0
- alabaster=0.7.16=py312hca03da5_0
- altair=5.0.1=py312hca03da5_0
- anaconda-anon-usage=0.5.0=py312hd6b623d_100
- anaconda-catalogs=0.2.0=py312hca03da5_1
- anaconda-cli-base=0.4.1=py312hca03da5_1
- anaconda-client=1.13.0=py312hca03da5_0
- anaconda-cloud-auth=0.7.2=py312hca03da5_0
- anaconda-navigator=2.6.4=py312hca03da5_0
- anaconda-project=0.11.1=py312hca03da5_0
- annotated-types=0.6.0=py312hca03da5_0
- anyio=4.6.2=py312hca03da5_0
- aom=3.6.0=h313beb8_0
- appdirs=1.4.4=pyhd3eb1b0_0
- applaunchservices=0.3.0=py312hca03da5_0
- appnope=0.1.3=py312hca03da5_1001
- appscript=1.2.5=py312h80987f9_0
- archspec=0.2.3=pyhd3eb1b0_0
- argon2-cffi=21.3.0=pyhd3eb1b0_0
- argon2-cffi-bindings=21.2.0=py312h80987f9_0
- arrow=1.3.0=py312hca03da5_0
- arrow-cpp=16.1.0=hbc20fb2_0
- astroid=3.2.4=py312hca03da5_0
- astropy=6.1.3=py312h80987f9_0
- astropy-iers-data=0.2024.9.2.0.33.23=py312hca03da5_0
- asttokens=2.0.5=pyhd3eb1b0_0
- async-lru=2.0.4=py312hca03da5_0
- asyncssh=2.17.0=py312hca03da5_0
- atomicwrites=1.4.0=py_0
- attrs=24.2.0=py312hca03da5_0
- automat=20.2.0=py_0
- autopep8=2.0.4=pyhd3eb1b0_0
- aws-c-auth=0.6.19=h80987f9_0
- aws-c-cal=0.5.20=h80987f9_0
- aws-c-common=0.8.5=h80987f9_0
- aws-c-compression=0.2.16=h80987f9_0
- aws-c-event-stream=0.2.15=h313beb8_0
- aws-c-http=0.6.25=h80987f9_0
- aws-c-io=0.13.10=h80987f9_0
- aws-c-mqtt=0.7.13=h80987f9_0
- aws-c-s3=0.1.51=h80987f9_0
- aws-c-sdkutils=0.1.6=h80987f9_0
- aws-checksums=0.1.13=h80987f9_0
- aws-crt-cpp=0.18.16=h313beb8_0
- aws-sdk-cpp=1.10.55=h313beb8_0
- babel=2.11.0=py312hca03da5_0
- backports=1.1=pyhd3eb1b0_1
- backports.functools_lru_cache=1.6.4=pyhd3eb1b0_0
- backports.tempfile=1.0=pyhd3eb1b0_1
- backports.weakref=1.0.post1=py_1
- bcrypt=3.2.0=py312h80987f9_1
- beautifulsoup4=4.12.3=py312hca03da5_0
- binaryornot=0.4.4=pyhd3eb1b0_1
- black=24.8.0=py312hca03da5_0
- blas=1.0=openblas
- bleach=6.2.0=py312hca03da5_0
- blinker=1.6.2=py312hca03da5_0
- blosc=1.21.3=h313beb8_0
- bokeh=3.6.0=py312hca03da5_0
- boltons=23.0.0=py312hca03da5_0
- boost-cpp=1.82.0=h48ca7d4_2
- botocore=1.34.69=py312hca03da5_0
- bottleneck=1.4.2=py312ha86b861_0
- brotli=1.0.9=h80987f9_8
- brotli-bin=1.0.9=h80987f9_8
- brotli-python=1.0.9=py312h313beb8_8
- brunsli=0.1=hc377ac9_1
- bzip2=1.0.8=h80987f9_6
- c-ares=1.19.1=h80987f9_0
- c-blosc2=2.12.0=h7df6c2f_0
- ca-certificates=2024.11.26=hca03da5_0
- cachetools=5.3.3=py312hca03da5_0
- cctools=949.0.1=hc179dcd_25
- cctools_osx-arm64=949.0.1=h332cad3_25
- certifi=2024.8.30=py312hca03da5_0
- cffi=1.17.1=py312h3eb5a62_0
- cfitsio=3.470=h7f6438f_7
- chardet=4.0.0=py312hca03da5_1003
- charls=2.2.0=hc377ac9_0
- charset-normalizer=3.3.2=pyhd3eb1b0_0
- click=8.1.7=py312hca03da5_0
- cloudpickle=3.0.0=py312hca03da5_0
- colorama=0.4.6=py312hca03da5_0
- colorcet=3.1.0=py312hca03da5_0
- comm=0.2.1=py312hca03da5_0
- conda=24.11.0=py312hca03da5_0
- conda-build=24.11.2=py312hca03da5_0
- conda-content-trust=0.2.0=py312hca03da5_1
- conda-index=0.5.0=py312hca03da5_0
- conda-libmamba-solver=24.9.0=pyhd3eb1b0_0
- conda-pack=0.6.0=pyhd3eb1b0_0
- conda-package-handling=2.4.0=py312hca03da5_0
- conda-package-streaming=0.11.0=py312hca03da5_0
- conda-repo-cli=1.0.114=py312hca03da5_0
- conda-token=0.4.0=pyhd3eb1b0_0
- conda-verify=3.4.2=py_1
- constantly=23.10.4=py312hca03da5_0
- contourpy=1.3.1=py312h48ca7d4_0
- cookiecutter=2.6.0=py312hca03da5_0
- cryptography=43.0.3=py312h8026fc7_1
- cssselect=1.2.0=py312hca03da5_0
- curl=8.9.1=h02f6b3c_0
- cycler=0.11.0=pyhd3eb1b0_0
- cyrus-sasl=2.1.28=h9131b1a_1
- cytoolz=0.12.2=py312h80987f9_0
- dask=2024.8.2=py312hca03da5_0
- dask-core=2024.8.2=py312hca03da5_0
- dask-expr=1.1.13=py312hca03da5_0
- datasets=2.19.1=py312hca03da5_0
- datashader=0.16.3=py312hca03da5_0
- dav1d=1.2.1=h80987f9_0
- debugpy=1.6.7=py312h313beb8_0
- decorator=5.1.1=pyhd3eb1b0_0
- defusedxml=0.7.1=pyhd3eb1b0_0
- deprecated=1.2.13=py312hca03da5_0
- diff-match-patch=20200713=pyhd3eb1b0_0
- dill=0.3.8=py312hca03da5_0
- distributed=2024.8.2=py312hca03da5_0
- distro=1.9.0=py312hca03da5_0
- dmglib=0.9.5=py312hca03da5_0
- docstring-to-markdown=0.11=py312hca03da5_0
- docutils=0.18.1=py312hca03da5_3
- et_xmlfile=1.1.0=py312hca03da5_1
- executing=0.8.3=pyhd3eb1b0_0
- expat=2.6.3=h313beb8_0
- filelock=3.13.1=py312hca03da5_0
- flake8=7.1.1=py312hca03da5_0
- flask=3.0.3=py312hca03da5_0
- fmt=9.1.0=h48ca7d4_1
- fonttools=4.51.0=py312h80987f9_0
- freetype=2.12.1=h1192e45_0
- frozendict=2.4.2=py312hca03da5_0
- frozenlist=1.5.0=py312h80987f9_0
- fsspec=2024.3.1=py312hca03da5_0
- future=1.0.0=py312hca03da5_0
- gensim=4.3.3=py312hd77ebd4_0
- gettext=0.21.0=hbdbcc25_2
- gflags=2.2.2=h313beb8_1
- giflib=5.2.2=h80987f9_0
- gitdb=4.0.7=pyhd3eb1b0_0
- gitpython=3.1.43=py312hca03da5_0
- glib=2.78.4=h313beb8_0
- glib-tools=2.78.4=h313beb8_0
- glog=0.5.0=h313beb8_1
- gmp=6.2.1=hc377ac9_3
- greenlet=3.0.1=py312h313beb8_0
- gst-plugins-base=1.14.1=h313beb8_1
- gstreamer=1.14.1=h80987f9_1
- h11=0.14.0=py312hca03da5_0
- h5py=3.12.1=py312h8456320_0
- hdf5=1.12.1=h05c076b_3
- heapdict=1.0.1=pyhd3eb1b0_0
- holoviews=1.20.0=py312hca03da5_0
- httpcore=1.0.2=py312hca03da5_0
- httpx=0.27.0=py312hca03da5_0
- huggingface_hub=0.24.6=py312hca03da5_0
- hvplot=0.11.1=py312hca03da5_0
- hyperlink=21.0.0=pyhd3eb1b0_0
- icu=73.1=h313beb8_0
- idna=3.7=py312hca03da5_0
- imagecodecs=2023.1.23=py312h75b721f_1
- imageio=2.33.1=py312hca03da5_0
- imagesize=1.4.1=py312hca03da5_0
- imbalanced-learn=0.12.3=py312hca03da5_1
- importlib-metadata=8.5.0=py312hca03da5_0
- importlib_metadata=8.5.0=hd3eb1b0_0
- incremental=22.10.0=pyhd3eb1b0_0
- inflection=0.5.1=py312hca03da5_1
- iniconfig=1.1.1=pyhd3eb1b0_0
- intake=2.0.7=py312hca03da5_0
- intervaltree=3.1.0=pyhd3eb1b0_0
- ipykernel=6.29.5=py312hca03da5_0
- ipython=8.27.0=py312hca03da5_0
- ipython_genutils=0.2.0=pyhd3eb1b0_1
- ipywidgets=7.8.1=py312hca03da5_0
- isort=5.13.2=py312hca03da5_0
- itemadapter=0.3.0=pyhd3eb1b0_0
- itemloaders=1.1.0=py312hca03da5_0
- itsdangerous=2.2.0=py312hca03da5_0
- jaraco.classes=3.2.1=pyhd3eb1b0_0
- jedi=0.19.1=py312hca03da5_0
- jellyfish=1.0.1=py312h1bd1ac0_1
- jinja2=3.1.4=py312hca03da5_1
- jmespath=1.0.1=py312hca03da5_0
- joblib=1.4.2=py312hca03da5_0
- jpeg=9e=h80987f9_3
- jq=1.7.1=h80987f9_0
- json5=0.9.25=py312hca03da5_0
- jsonpatch=1.33=py312hca03da5_1
- jsonpointer=2.1=pyhd3eb1b0_0
- jsonschema=4.23.0=py312hca03da5_0
- jsonschema-specifications=2023.7.1=py312hca03da5_0
- jupyter=1.0.0=py312hca03da5_9
- jupyter-lsp=2.2.0=py312hca03da5_0
- jupyter_client=8.6.0=py312hca03da5_0
- jupyter_console=6.6.3=py312hca03da5_1
- jupyter_core=5.7.2=py312hca03da5_0
- jupyter_events=0.10.0=py312hca03da5_0
- jupyter_server=2.14.1=py312hca03da5_0
- jupyter_server_terminals=0.4.4=py312hca03da5_1
- jupyterlab=4.2.5=py312hca03da5_0
- jupyterlab-variableinspector=3.1.0=py312hca03da5_0
- jupyterlab_pygments=0.1.2=py_0
- jupyterlab_server=2.27.3=py312hca03da5_0
- jupyterlab_widgets=1.0.0=pyhd3eb1b0_1
- jxrlib=1.1=h1a28f6b_2
- keyring=24.3.1=py312hca03da5_0
- kiwisolver=1.4.4=py312h313beb8_0
- krb5=1.20.1=hf3e1bf2_1
- lazy_loader=0.4=py312hca03da5_0
- lcms2=2.12=hba8e193_0
- ld64=530=hb29bf3f_25
- ld64_osx-arm64=530=h001ce53_25
- ldid=2.1.5=h20b2a84_3
- lerc=3.0=hc377ac9_0
- libabseil=20240116.2=cxx17_h313beb8_0
- libaec=1.0.4=hc377ac9_1
- libarchive=3.7.4=h8f13d7a_0
- libavif=0.11.1=h80987f9_0
- libboost=1.82.0=h0bc93f9_2
- libbrotlicommon=1.0.9=h80987f9_8
- libbrotlidec=1.0.9=h80987f9_8
- libbrotlienc=1.0.9=h80987f9_8
- libclang=14.0.6=default_h1b80db6_1
- libclang13=14.0.6=default_h24352ff_1
- libcurl=8.9.1=h3e2b118_0
- libcxx=14.0.6=h848a8c0_0
- libdeflate=1.17=h80987f9_1
- libedit=3.1.20230828=h80987f9_0
- libev=4.33=h1a28f6b_1
- libevent=2.1.12=h02f6b3c_1
- libffi=3.4.4=hca03da5_1
- libgfortran=5.0.0=11_3_0_hca03da5_28
- libgfortran5=11.3.0=h009349e_28
- libglib=2.78.4=h0a96307_0
- libgrpc=1.62.2=h62f6fdd_0
- libiconv=1.16=h80987f9_3
- liblief=0.12.3=h313beb8_0
- libllvm14=14.0.6=h19fdd8a_4
- libmamba=1.5.11=haeffa04_0
- libmambapy=1.5.11=py312h15e39b3_0
- libnghttp2=1.57.0=h62f6fdd_0
- libopenblas=0.3.21=h269037a_0
- libpng=1.6.39=h80987f9_0
- libpq=17.0=h02f6b3c_0
- libprotobuf=4.25.3=h514c7bf_0
- libsodium=1.0.18=h1a28f6b_0
- libsolv=0.7.24=h514c7bf_1
- libspatialindex=1.9.3=hc377ac9_0
- libssh2=1.11.1=h3e2b118_0
- libthrift=0.15.0=h73c2103_2
- libtiff=4.5.1=h313beb8_0
- libwebp-base=1.3.2=h80987f9_1
- libxml2=2.13.5=h0b34f26_0
- libxslt=1.1.41=hf4d3faa_0
- libzopfli=1.0.3=hc377ac9_0
- linkify-it-py=2.0.0=py312hca03da5_0
- llvm-openmp=14.0.6=hc6e5704_0
- llvmlite=0.43.0=py312h313beb8_0
- locket=1.0.0=py312hca03da5_0
- lxml=5.3.0=py312h1d4350b_0
- lz4=4.3.2=py312h80987f9_0
- lz4-c=1.9.4=h313beb8_1
- lzo=2.10=h1a28f6b_2
- markdown=3.4.1=py312hca03da5_0
- markdown-it-py=2.2.0=py312hca03da5_1
- markupsafe=2.1.3=py312h80987f9_0
- matplotlib=3.9.2=py312hca03da5_1
- matplotlib-base=3.9.2=py312h7ef442a_1
- matplotlib-inline=0.1.6=py312hca03da5_0
- mccabe=0.7.0=pyhd3eb1b0_0
- mdit-py-plugins=0.3.0=py312hca03da5_0
- mdurl=0.1.0=py312hca03da5_0
- menuinst=2.2.0=py312hca03da5_0
- mistune=2.0.4=py312hca03da5_0
- more-itertools=10.3.0=py312hca03da5_0
- mpc=1.1.0=h8c48613_1
- mpfr=4.0.2=h695f6f0_1
- mpmath=1.3.0=py312hca03da5_0
- msgpack-python=1.0.3=py312h48ca7d4_0
- multidict=6.1.0=py312h80987f9_0
- multipledispatch=0.6.0=py312hca03da5_0
- multiprocess=0.70.15=py312hca03da5_0
- mypy=1.11.2=py312h80987f9_0
- mypy_extensions=1.0.0=py312hca03da5_0
- mysql=8.4.0=h3a6587f_1
- navigator-updater=0.5.1=py312hca03da5_0
- nbclassic=1.1.0=py312hca03da5_0
- nbclient=0.8.0=py312hca03da5_0
- nbconvert=7.16.4=py312hca03da5_0
- nbformat=5.10.4=py312hca03da5_0
- ncurses=6.4=h313beb8_0
- nest-asyncio=1.6.0=py312hca03da5_0
- networkx=3.3=py312hca03da5_0
- nltk=3.9.1=py312hca03da5_0
- notebook=7.2.2=py312hca03da5_1
- notebook-shim=0.2.3=py312hca03da5_0
- numba=0.60.0=py312hd77ebd4_0
- numexpr=2.10.1=py312h5d9532f_0
- numpy=1.26.4=py312h7f4fdc5_0
- numpy-base=1.26.4=py312he047099_0
- numpydoc=1.7.0=py312hca03da5_0
- oniguruma=6.9.7.1=h1a28f6b_0
- openjpeg=2.5.2=h54b8e55_0
- openldap=2.6.4=he7ef289_0
- openpyxl=3.1.5=py312h80987f9_0
- openssl=3.0.15=h80987f9_0
- orc=2.0.1=h937ddfc_0
- overrides=7.4.0=py312hca03da5_0
- packaging=24.1=py312hca03da5_0
- pandas=2.2.3=py312hcf29cfe_0
- pandocfilters=1.5.0=pyhd3eb1b0_0
- panel=1.5.3=py312hca03da5_0
- param=2.1.1=py312hca03da5_0
- parsel=1.8.1=py312hca03da5_0
- parso=0.8.3=pyhd3eb1b0_0
- partd=1.4.1=py312hca03da5_0
- patch=2.7.6=h1a28f6b_1001
- pathspec=0.10.3=py312hca03da5_0
- patsy=0.5.6=py312hca03da5_0
- pcre2=10.42=hb066dcc_1
- pexpect=4.8.0=pyhd3eb1b0_3
- pickleshare=0.7.5=pyhd3eb1b0_1003
- pillow=11.0.0=py312hfaf4e14_0
- pip=24.2=py312hca03da5_0
- pkce=1.0.3=py312hca03da5_0
- pkginfo=1.11.2=py312hca03da5_0
- platformdirs=3.10.0=py312hca03da5_0
- plotly=5.24.1=py312h989b03a_0
- pluggy=1.5.0=py312hca03da5_0
- ply=3.11=py312hca03da5_1
- prometheus_client=0.21.0=py312hca03da5_0
- prompt-toolkit=3.0.43=py312hca03da5_0
- prompt_toolkit=3.0.43=hd3eb1b0_0
- propcache=0.2.0=py312h80987f9_0
- protego=0.1.16=py_0
- protobuf=4.25.3=py312h8472c4a_0
- psutil=5.9.0=py312h80987f9_0
- ptyprocess=0.7.0=pyhd3eb1b0_2
- pure_eval=0.2.2=pyhd3eb1b0_0
- py-cpuinfo=9.0.0=py312hca03da5_0
- py-lief=0.12.3=py312h313beb8_0
- pyarrow=16.1.0=py312hd77ebd4_0
- pyasn1=0.4.8=pyhd3eb1b0_0
- pyasn1-modules=0.2.8=py_0
- pybind11-abi=5=hd3eb1b0_0
- pycodestyle=2.12.1=py312hca03da5_0
- pycosat=0.6.6=py312h80987f9_1
- pycparser=2.21=pyhd3eb1b0_0
- pyct=0.5.0=py312hca03da5_0
- pycurl=7.45.3=py312h02f6b3c_0
- pydantic=2.8.2=py312hca03da5_0
- pydantic-core=2.20.1=py312hf0e4da2_0
- pydantic-settings=2.6.1=py312hca03da5_0
- pydeck=0.8.0=py312hca03da5_2
- pydispatcher=2.0.5=py312hca03da5_3
- pydocstyle=6.3.0=py312hca03da5_0
- pyerfa=2.0.1.4=py312ha86b861_0
- pyflakes=3.2.0=py312hca03da5_0
- pygithub=2.4.0=py312hca03da5_0
- pygments=2.15.1=py312hca03da5_1
- pyjwt=2.9.0=py312hca03da5_0
- pylint=3.2.7=py312hca03da5_0
- pylint-venv=3.0.3=py312hca03da5_0
- pyls-spyder=0.4.0=pyhd3eb1b0_0
- pynacl=1.5.0=py312h80987f9_0
- pyobjc-core=10.1=py312h80987f9_0
- pyobjc-framework-cocoa=10.1=py312hb094c41_0
- pyobjc-framework-coreservices=10.1=py312hdd8dd1f_0
- pyobjc-framework-fsevents=10.1=py312hca03da5_0
- pyodbc=5.1.0=py312h313beb8_0
- pyopenssl=24.2.1=py312hca03da5_0
- pyparsing=3.2.0=py312hca03da5_0
- pyqt=5.15.10=py312h313beb8_0
- pyqt5-sip=12.13.0=py312h80987f9_0
- pyqtwebengine=5.15.10=py312h313beb8_0
- pysocks=1.7.1=py312hca03da5_0
- pytables=3.10.1=py312h905a39b_0
- pytest=7.4.4=py312hca03da5_0
- python=3.12.7=h99e199e_0
- python-dateutil=2.9.0post0=py312hca03da5_2
- python-dotenv=0.21.0=py312hca03da5_0
- python-fastjsonschema=2.20.0=py312hca03da5_0
- python-json-logger=2.0.7=py312hca03da5_0
- python-libarchive-c=5.1=pyhd3eb1b0_0
- python-lmdb=1.4.1=py312h313beb8_0
- python-lsp-black=2.0.0=py312hca03da5_0
- python-lsp-jsonrpc=1.1.2=pyhd3eb1b0_0
- python-lsp-server=1.12.0=py312h989b03a_0
- python-slugify=5.0.2=pyhd3eb1b0_0
- python-tzdata=2023.3=pyhd3eb1b0_0
- python-xxhash=2.0.2=py312h80987f9_1
- python.app=3=py312h80987f9_1
- pytoolconfig=1.2.6=py312hca03da5_0
- pytz=2024.1=py312hca03da5_0
- pyuca=1.2=py312hca03da5_1
- pyviz_comms=3.0.2=py312hca03da5_0
- pywavelets=1.7.0=py312h80987f9_0
- pyyaml=6.0.2=py312h80987f9_0
- pyzmq=25.1.2=py312h313beb8_0
- qdarkstyle=3.2.3=pyhd3eb1b0_0
- qstylizer=0.2.2=py312hca03da5_0
- qt-main=5.15.2=h0917680_11
- qt-webengine=5.15.9=h2903aaf_7
- qtawesome=1.3.1=py312hca03da5_0
- qtconsole=5.6.0=py312hca03da5_0
- qtpy=2.4.1=py312hca03da5_0
- queuelib=1.6.2=py312hca03da5_0
- re2=2022.04.01=hc377ac9_0
- readchar=4.0.5=py312hca03da5_0
- readline=8.2=h1a28f6b_0
- referencing=0.30.2=py312hca03da5_0
- regex=2024.9.11=py312h80987f9_0
- reproc=14.2.4=h313beb8_2
- reproc-cpp=14.2.4=h313beb8_2
- requests=2.32.3=py312hca03da5_1
- requests-file=1.5.1=pyhd3eb1b0_0
- requests-toolbelt=1.0.0=py312hca03da5_0
- responses=0.13.3=pyhd3eb1b0_0
- rfc3339-validator=0.1.4=py312hca03da5_0
- rfc3986-validator=0.1.1=py312hca03da5_0
- rich=13.9.4=py312hca03da5_0
- rope=1.12.0=py312hca03da5_0
- rpds-py=0.10.6=py312h2aea54e_1
- rtree=1.0.1=py312hca03da5_0
- ruamel.yaml=0.18.6=py312h80987f9_0
- ruamel.yaml.clib=0.2.8=py312h80987f9_0
- ruamel_yaml=0.17.21=py312h80987f9_0
- s3fs=2024.3.1=py312hca03da5_0
- safetensors=0.4.5=py312h7805bc0_1
- scikit-image=0.24.0=py312hd77ebd4_0
- scikit-learn=1.5.1=py312hd77ebd4_0
- scipy=1.13.1=py312ha409365_0
- scrapy=2.12.0=py312hca03da5_0
- seaborn=0.13.2=py312hca03da5_0
- semver=3.0.2=py312hca03da5_0
- send2trash=1.8.2=py312hca03da5_0
- service_identity=18.1.0=pyhd3eb1b0_1
- setuptools=75.1.0=py312hca03da5_0
- shellingham=1.5.0=py312hca03da5_0
- sip=6.7.12=py312h313beb8_0
- six=1.16.0=pyhd3eb1b0_1
- smart_open=5.2.1=py312hca03da5_0
- smmap=4.0.0=pyhd3eb1b0_0
- snappy=1.2.1=h313beb8_0
- sniffio=1.3.0=py312hca03da5_0
- snowballstemmer=2.2.0=pyhd3eb1b0_0
- sortedcontainers=2.4.0=pyhd3eb1b0_0
- soupsieve=2.5=py312hca03da5_0
- sphinx=7.3.7=py312hca03da5_0
- sphinxcontrib-applehelp=1.0.2=pyhd3eb1b0_0
- sphinxcontrib-devhelp=1.0.2=pyhd3eb1b0_0
- sphinxcontrib-htmlhelp=2.0.0=pyhd3eb1b0_0
- sphinxcontrib-jsmath=1.0.1=pyhd3eb1b0_0
- sphinxcontrib-qthelp=1.0.3=pyhd3eb1b0_0
- sphinxcontrib-serializinghtml=1.1.10=py312hca03da5_0
- spyder=6.0.1=py312hca03da5_0
- spyder-kernels=3.0.0=py312h989b03a_0
- sqlalchemy=2.0.34=py312hbe2cdee_0
- sqlite=3.45.3=h80987f9_0
- stack_data=0.2.0=pyhd3eb1b0_0
- statsmodels=0.14.2=py312ha86b861_0
- streamlit=1.40.1=py312hca03da5_0
- superqt=0.6.7=py312h989b03a_0
- sympy=1.13.2=py312hca03da5_0
- tabulate=0.9.0=py312hca03da5_0
- tapi=1100.0.11=h8754e6a_1
- tbb=2021.8.0=h48ca7d4_0
- tblib=1.7.0=pyhd3eb1b0_0
- tenacity=9.0.0=py312hca03da5_0
- terminado=0.17.1=py312hca03da5_0
- text-unidecode=1.3=pyhd3eb1b0_0
- textdistance=4.6.3=py312h989b03a_0
- threadpoolctl=3.5.0=py312h989b03a_0
- three-merge=0.1.1=pyhd3eb1b0_0
- tifffile=2023.4.12=py312hca03da5_0
- tinycss2=1.2.1=py312hca03da5_0
- tk=8.6.14=h6ba3021_0
- tldextract=5.1.2=py312hca03da5_0
- tokenizers=0.20.1=py312he2d9c3e_1
- toml=0.10.2=pyhd3eb1b0_0
- tomli=2.0.1=py312hca03da5_1
- tomlkit=0.13.2=py312hca03da5_0
- toolz=0.12.0=py312hca03da5_0
- tornado=6.4.1=py312h80987f9_0
- tqdm=4.66.5=py312h989b03a_0
- traitlets=5.14.3=py312hca03da5_0
- transformers=4.45.2=py312hca03da5_0
- truststore=0.8.0=py312hca03da5_0
- twisted=23.10.0=py312hca03da5_0
- typer=0.9.0=py312hca03da5_0
- typing-extensions=4.11.0=py312hca03da5_0
- typing_extensions=4.11.0=py312hca03da5_0
- tzdata=2024b=h04d1e81_0
- uc-micro-py=1.0.1=py312hca03da5_0
- ujson=5.10.0=py312h313beb8_0
- unicodedata2=15.1.0=py312h80987f9_0
- unidecode=1.3.8=py312hca03da5_0
- unixodbc=2.3.11=h1a28f6b_0
- urllib3=2.2.3=py312hca03da5_0
- utf8proc=2.6.1=h80987f9_1
- w3lib=2.1.2=py312hca03da5_0
- watchdog=4.0.1=py312h80987f9_0
- wcwidth=0.2.5=pyhd3eb1b0_0
- webencodings=0.5.1=py312hca03da5_2
- websocket-client=1.8.0=py312hca03da5_0
- werkzeug=3.0.6=py312hca03da5_0
- whatthepatch=1.0.2=py312hca03da5_0
- wheel=0.44.0=py312hca03da5_0
- widgetsnbextension=3.6.6=py312hca03da5_0
- wrapt=1.14.1=py312h80987f9_0
- wurlitzer=3.0.2=py312hca03da5_0
- xarray=2023.6.0=py312hca03da5_0
- xlwings=0.32.1=py312hca03da5_0
- xxhash=0.8.0=h1a28f6b_3
- xyzservices=2022.9.0=py312hca03da5_1
- xz=5.4.6=h80987f9_1
- yaml=0.2.5=h1a28f6b_0
- yaml-cpp=0.8.0=h313beb8_1
- yapf=0.40.2=py312hca03da5_0
- yarl=1.18.0=py312h80987f9_0
- zeromq=4.3.5=h313beb8_0
- zfp=1.0.0=h313beb8_0
- zict=3.0.0=py312hca03da5_0
- zipp=3.21.0=py312hca03da5_0
- zlib=1.2.13=h18a0788_1
- zlib-ng=2.0.7=h80987f9_0
- zope=1.0=py312hca03da5_1
- zope.interface=7.1.1=py312h80987f9_0
- zstandard=0.23.0=py312h1a4646a_1
- zstd=1.5.6=hfb09047_0
- pip:
- svgpathtools==1.7.0
- svgwrite==1.4.3
prefix: /opt/homebrew/anaconda3

View File

@@ -1,125 +0,0 @@
name: evilnkode
channels:
- defaults
dependencies:
- anyio=4.6.2=py312hca03da5_0
- appnope=0.1.3=py312hca03da5_1001
- argon2-cffi=21.3.0=pyhd3eb1b0_0
- argon2-cffi-bindings=21.2.0=py312h80987f9_0
- asttokens=2.0.5=pyhd3eb1b0_0
- async-lru=2.0.4=py312hca03da5_0
- attrs=24.2.0=py312hca03da5_0
- babel=2.11.0=py312hca03da5_0
- beautifulsoup4=4.12.3=py312hca03da5_0
- bleach=6.2.0=py312hca03da5_0
- brotli-python=1.0.9=py312h313beb8_8
- bzip2=1.0.8=h80987f9_6
- ca-certificates=2024.12.31=hca03da5_0
- certifi=2024.12.14=py312hca03da5_0
- cffi=1.17.1=py312h3eb5a62_0
- charset-normalizer=3.3.2=pyhd3eb1b0_0
- comm=0.2.1=py312hca03da5_0
- debugpy=1.6.7=py312h313beb8_0
- decorator=5.1.1=pyhd3eb1b0_0
- defusedxml=0.7.1=pyhd3eb1b0_0
- executing=0.8.3=pyhd3eb1b0_0
- expat=2.6.3=h313beb8_0
- h11=0.14.0=py312hca03da5_0
- httpcore=1.0.2=py312hca03da5_0
- httpx=0.27.0=py312hca03da5_0
- idna=3.7=py312hca03da5_0
- ipykernel=6.29.5=py312hca03da5_0
- ipython=8.27.0=py312hca03da5_0
- jedi=0.19.1=py312hca03da5_0
- jinja2=3.1.4=py312hca03da5_1
- json5=0.9.25=py312hca03da5_0
- jsonschema=4.23.0=py312hca03da5_0
- jsonschema-specifications=2023.7.1=py312hca03da5_0
- jupyter-lsp=2.2.0=py312hca03da5_0
- jupyter_client=8.6.0=py312hca03da5_0
- jupyter_core=5.7.2=py312hca03da5_0
- jupyter_events=0.10.0=py312hca03da5_0
- jupyter_server=2.14.1=py312hca03da5_0
- jupyter_server_terminals=0.4.4=py312hca03da5_1
- jupyterlab=4.2.5=py312hca03da5_0
- jupyterlab_pygments=0.1.2=py_0
- jupyterlab_server=2.27.3=py312hca03da5_0
- libcxx=14.0.6=h848a8c0_0
- libffi=3.4.4=hca03da5_1
- libsodium=1.0.18=h1a28f6b_0
- markupsafe=2.1.3=py312h80987f9_0
- matplotlib-inline=0.1.6=py312hca03da5_0
- mistune=2.0.4=py312hca03da5_0
- nbclient=0.8.0=py312hca03da5_0
- nbconvert=7.16.4=py312hca03da5_0
- nbformat=5.10.4=py312hca03da5_0
- ncurses=6.4=h313beb8_0
- nest-asyncio=1.6.0=py312hca03da5_0
- notebook=7.2.2=py312hca03da5_1
- notebook-shim=0.2.3=py312hca03da5_0
- openssl=3.0.15=h80987f9_0
- overrides=7.4.0=py312hca03da5_0
- packaging=24.1=py312hca03da5_0
- pandocfilters=1.5.0=pyhd3eb1b0_0
- parso=0.8.3=pyhd3eb1b0_0
- pexpect=4.8.0=pyhd3eb1b0_3
- pip=24.2=py312hca03da5_0
- platformdirs=3.10.0=py312hca03da5_0
- prometheus_client=0.21.0=py312hca03da5_0
- prompt-toolkit=3.0.43=py312hca03da5_0
- prompt_toolkit=3.0.43=hd3eb1b0_0
- psutil=5.9.0=py312h80987f9_0
- ptyprocess=0.7.0=pyhd3eb1b0_2
- pure_eval=0.2.2=pyhd3eb1b0_0
- pycparser=2.21=pyhd3eb1b0_0
- pygments=2.15.1=py312hca03da5_1
- pysocks=1.7.1=py312hca03da5_0
- python=3.12.7=h99e199e_0
- python-dateutil=2.9.0post0=py312hca03da5_2
- python-fastjsonschema=2.20.0=py312hca03da5_0
- python-json-logger=2.0.7=py312hca03da5_0
- pytz=2024.1=py312hca03da5_0
- pyyaml=6.0.2=py312h80987f9_0
- pyzmq=25.1.2=py312h313beb8_0
- readline=8.2=h1a28f6b_0
- referencing=0.30.2=py312hca03da5_0
- requests=2.32.3=py312hca03da5_1
- rfc3339-validator=0.1.4=py312hca03da5_0
- rfc3986-validator=0.1.1=py312hca03da5_0
- rpds-py=0.10.6=py312h2aea54e_1
- send2trash=1.8.2=py312hca03da5_0
- setuptools=75.1.0=py312hca03da5_0
- six=1.16.0=pyhd3eb1b0_1
- sniffio=1.3.0=py312hca03da5_0
- soupsieve=2.5=py312hca03da5_0
- sqlite=3.45.3=h80987f9_0
- stack_data=0.2.0=pyhd3eb1b0_0
- terminado=0.17.1=py312hca03da5_0
- tinycss2=1.2.1=py312hca03da5_0
- tk=8.6.14=h6ba3021_0
- tornado=6.4.1=py312h80987f9_0
- traitlets=5.14.3=py312hca03da5_0
- typing-extensions=4.11.0=py312hca03da5_0
- typing_extensions=4.11.0=py312hca03da5_0
- tzdata=2024b=h04d1e81_0
- urllib3=2.2.3=py312hca03da5_0
- wcwidth=0.2.5=pyhd3eb1b0_0
- webencodings=0.5.1=py312hca03da5_2
- websocket-client=1.8.0=py312hca03da5_0
- wheel=0.44.0=py312hca03da5_0
- xz=5.4.6=h80987f9_1
- yaml=0.2.5=h1a28f6b_0
- zeromq=4.3.5=h313beb8_0
- zlib=1.2.13=h18a0788_1
- pip:
- contourpy==1.3.1
- cycler==0.12.1
- fonttools==4.55.3
- iniconfig==2.0.0
- kiwisolver==1.4.7
- matplotlib==3.9.3
- numpy==2.1.3
- pillow==11.0.0
- pluggy==1.5.0
- pyparsing==3.2.0
- pytest==8.3.4

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

199
notebooks/evilnkode.ipynb Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,19 +1,19 @@
from src.evilkode import Evilkode
from tqdm import tqdm
from src.evilnkode import EvilNKode
from dataclasses import dataclass
from statistics import mean, variance
from src.utils import observations, passcode_generator
from src.keypad.keypad import BaseKeypad
from pathlib import Path
from src.utils import ShuffleTypes, observations, passcode_generator
import pickle
@dataclass
class Benchmark:
mean: int
variance: int
runs: list[int]
iterations_to_break: list[int]
iterations_to_replay: list[int]
def shuffle_benchmark(
def benchmark(
number_of_keys: int,
properties_per_key: int,
passcode_len: int,
@@ -21,85 +21,44 @@ def shuffle_benchmark(
run_count: int,
complexity: int,
disparity: int,
shuffle_type: ShuffleTypes,
file_path: str = '../output',
keypad: BaseKeypad,
file_path: Path = '../output',
overwrite: bool = False
) -> Benchmark:
file_name = f"{shuffle_type.name.lower()}-{number_of_keys}-{properties_per_key}-{passcode_len}-{max_tries_before_lockout}-{complexity}-{disparity}-{run_count}.txt"
full_path = Path(file_path) / file_name
shuffle_type = str(type(keypad)).lower().split('.')[-1].replace("'>", "")
file_name = f"{shuffle_type}-{number_of_keys}-{properties_per_key}-{passcode_len}-{max_tries_before_lockout}-{complexity}-{disparity}-{run_count}.pkl"
full_path = Path(file_path) / "benchmark" / file_name
if not overwrite and full_path.exists():
print(f"file exists {file_path}")
with open(full_path, "r") as fp:
runs = fp.readline()
runs = runs.split(',')
runs = [int(i) for i in runs]
return Benchmark(
mean=mean(runs),
variance=variance(runs),
runs=runs
)
runs = []
for _ in range(run_count):
print(f"File exists: {full_path}")
with open(full_path, "rb") as fp:
return pickle.load(fp)
iterations_to_break = []
iterations_to_replay = []
for _ in tqdm(range(run_count)):
passcode = passcode_generator(number_of_keys, properties_per_key, passcode_len, complexity, disparity)
evilkode = Evilkode(
evilnkode = EvilNKode(
observations=observations(
target_passcode=passcode,
number_of_keys=number_of_keys,
properties_per_key=properties_per_key,
min_complexity=complexity,
min_disparity=disparity,
shuffle_type=shuffle_type,
keypad=keypad,
),
number_of_keys=number_of_keys,
properties_per_key=properties_per_key,
passcode_len=passcode_len,
max_tries_before_lockout=max_tries_before_lockout,
)
evilout = evilkode.run()
runs.append(evilout.iterations)
evilout = evilnkode.run()
iterations_to_break.append(evilout.iterations_to_break)
iterations_to_replay.append(evilout.iterations_to_replay)
benchmark_result = Benchmark(
iterations_to_break=iterations_to_break,
iterations_to_replay=iterations_to_replay
)
if file_path:
full_path.parent.mkdir(parents=True, exist_ok=True)
with open(full_path, "w") as fp:
fp.write(",".join([str(i) for i in runs])),
with open(full_path, "wb") as fp:
pickle.dump(benchmark_result, fp)
return Benchmark(
mean=mean(runs),
variance=variance(runs),
runs=runs
)
def full_shuffle_benchmark(
number_of_keys: int,
properties_per_key: int,
passcode_len: int,
max_tries_before_lockout: int,
run_count: int,
complexity: int,
disparity: int,
) -> Benchmark:
runs = []
for _ in range(run_count):
passcode = passcode_generator(number_of_keys, properties_per_key, passcode_len, complexity, disparity)
evilkode = Evilkode(
observations=observations(
target_passcode=passcode,
number_of_keys=number_of_keys,
properties_per_key=properties_per_key,
min_complexity=complexity,
min_disparity=disparity,
shuffle_type=ShuffleTypes.FULL_SHUFFLE,
),
number_of_keys=number_of_keys,
properties_per_key=properties_per_key,
passcode_len=passcode_len,
max_tries_before_lockout=max_tries_before_lockout,
)
evilout = evilkode.run()
runs.append(evilout.iterations)
return Benchmark(
mean=mean(runs),
variance=variance(runs),
runs=runs
)
return benchmark_result

View File

@@ -12,19 +12,19 @@ class Observation:
def property_list(self) -> list[set[int]]:
return [set(self.keypad[idx]) for idx in self.key_selection]
@property
def flat_keypad(self) -> list[int]:
return [num for row in self.keypad for num in row]
@dataclass
class EvilOutput:
possible_nkodes: list[list[int]]
iterations: int
@property
def number_of_possible_nkode(self):
return math.prod([len(el) for el in self.possible_nkodes])
iterations_to_break: int
iterations_to_replay: int
@dataclass
class Evilkode:
class EvilNKode:
observations: Iterator[Observation]
passcode_len: int
number_of_keys: int
@@ -32,16 +32,31 @@ class Evilkode:
max_tries_before_lockout: int = 5
possible_nkode = None
def initialize(self):
possible_values = set(range(self.number_of_keys * self.properties_per_key))
self.possible_nkode = [possible_values.copy() for _ in range(self.passcode_len)]
def run(self) -> EvilOutput:
self.initialize()
iterations_to_replay = None
for idx, obs in enumerate(self.observations):
if iterations_to_replay is None:
replay_possibilities = self.replay_attack(obs)
if replay_possibilities <= self.max_tries_before_lockout:
iterations_to_replay = idx + 1
if math.prod([len(el) for el in self.possible_nkode]) <= self.max_tries_before_lockout:
return EvilOutput(possible_nkodes=[list(el) for el in self.possible_nkode], iterations=idx+1)
assert iterations_to_replay <= idx + 1
return EvilOutput(
# possible_nkodes=[list(el) for el in self.possible_nkode],
iterations_to_break=idx + 1,
iterations_to_replay=iterations_to_replay
)
for jdx, props in enumerate(obs.property_list):
self.possible_nkode[jdx] = props.intersection(self.possible_nkode[jdx])
raise Exception("error in Evilkode, observations stopped yielding")
def replay_attack(self, obs: Observation) -> int:
possible_combos = 1
for el in self.possible_nkode:
possible_combos *= len({obs.flat_keypad.index(el2) // self.properties_per_key for el2 in el})
return possible_combos

View File

@@ -1,83 +0,0 @@
from dataclasses import dataclass
import numpy as np
@dataclass
class Keypad:
keypad: np.ndarray
k: int # number of keys
p: int # properties per key
keypad_cache: list #
max_cache_size: int = 100
@staticmethod
def new_keypad(k: int, p: int):
total_properties = k * p
array = np.arange(total_properties)
# Reshape into a 3x4 matrix
keypad = array.reshape(k, p)
set_view = keypad.T
for set_idx in set_view:
np.random.shuffle(set_idx)
return Keypad(keypad=set_view.T, k=k, p=p, keypad_cache=[])
def split_shuffle(self):
"""
This is a modified split shuffle.
It doesn't shuffle the keys only the properties in the keys.
Shuffling the keys makes it hard for people to guess an nKode not a machine.
This split shuffle includes a cache to prevent the same configuration from being used.
This cache is not in any other implementation.
Testing suggests it's not necessary.
Getting the same keypad twice over 100 shuffles is very unlikely.
"""
shuffled_sets = self._shuffle()
# Sort the shuffled sets by the first column
sorted_set = shuffled_sets[np.argsort(shuffled_sets[:, 0])]
while str(sorted_set) in self.keypad_cache:
# continue shuffling until we get a unique configuration
shuffled_sets = self._shuffle()
sorted_set = shuffled_sets[np.argsort(shuffled_sets[:, 0])]
self.keypad_cache.append(str(sorted_set))
self.keypad_cache = self.keypad_cache[:self.max_cache_size]
self.keypad = shuffled_sets
def _shuffle(self) -> np.ndarray:
column_permutation = np.random.permutation(self.p)
column_subset = column_permutation[:self.p // 2]
perm_indices = np.random.permutation(self.k)
shuffled_sets = self.keypad.copy()
shuffled_sets[:, column_subset] = shuffled_sets[perm_indices, :][:, column_subset]
return shuffled_sets
def full_shuffle(self):
shuffled_matrix = np.array([np.random.permutation(row) for row in self.keypad.T])
self.keypad = shuffled_matrix.T
def key_entry(self, target_passcode: list[int]) -> list[int]:
"""
Given target_values, return the row indices they are in.
Assert that each element is >= 0 and < self.k * self.p.
"""
# Convert the list to a NumPy array for vectorized checks
vals = np.array(target_passcode)
# Validate that each value is within the valid range
if np.any((vals < 0) | (vals >= self.k * self.p)):
raise ValueError("One or more values are out of the valid range.")
# Flatten the keypad to a 1D array
flat = self.keypad.flatten()
# Create an inverse mapping from value -> row index
inv_index = np.empty(self.k * self.p, dtype=int)
# Each value v is at position i in 'flat', so row = i // p
for i, v in enumerate(flat):
inv_index[v] = i // self.p
# Use the inverse mapping to get row indices for all target values
return inv_index[vals].tolist()

0
src/keypad/__init__.py Normal file
View File

123
src/keypad/keypad.py Normal file
View File

@@ -0,0 +1,123 @@
from dataclasses import dataclass
import numpy as np
from src.keypad.tower_shuffle import TowerShuffle
from abc import ABC, abstractmethod
from typing import Self
class BaseKeypad(ABC):
keypad: np.ndarray
k: int # number of keys
p: int # properties per key
@classmethod
def _build_keypad(cls, k: int, p: int) -> np.ndarray:
rng = np.random.default_rng()
total = k * p
array = np.arange(total)
keypad = array.reshape(k, p)
set_view = keypad.T.copy()
for set_idx in set_view:
rng.shuffle(set_idx)
return set_view.T
@classmethod
@abstractmethod
def new_keypad(cls, k: int, p: int) -> Self:
raise NotImplementedError
def key_entry(self, target_passcode: list[int]) -> list[int]:
vals = np.array(target_passcode)
if np.any((vals < 0) | (vals >= self.k * self.p)):
raise ValueError("One or more values are out of the valid range.")
flat = self.keypad.flatten()
inv_index = np.empty(self.k * self.p, dtype=int)
for i, v in enumerate(flat):
inv_index[v] = i // self.p
return inv_index[vals].tolist()
@abstractmethod
def shuffle(self):
pass
def keypad_mat(self) -> list[list[int]]:
return [el.tolist() for el in self.keypad]
@dataclass
class SlidingTowerShuffleKeypad(BaseKeypad):
keypad: np.ndarray
k: int # number of keys
p: int # properties per key
tower_shuffle: TowerShuffle
@classmethod
def new_keypad(cls, k: int, p: int) -> Self:
kp = cls._build_keypad(k, p)
return cls(keypad=kp, k=k, p=p, tower_shuffle=TowerShuffle.new(p))
def shuffle(self):
selected_positions = self.tower_shuffle.left_tower.tolist()
shift = np.random.randint(1, self.k) # random int in [1, k-1]
new_key_idxs = np.roll(np.arange(self.k), shift)
shuffled_sets = self.keypad.copy()
shuffled_sets[:, selected_positions] = shuffled_sets[new_key_idxs, :][:, selected_positions]
self.keypad = shuffled_sets
self.tower_shuffle.shuffle()
@dataclass
class RandomShuffleKeypad(BaseKeypad):
keypad: np.ndarray
k: int # number of keys
p: int # properties per key
@classmethod
def new_keypad(cls, k: int, p: int) -> Self:
kp = cls._build_keypad(k, p)
return cls(keypad=kp, k=k, p=p)
def shuffle(self):
shuffled_matrix = np.array([np.random.permutation(row) for row in self.keypad.T])
self.keypad = shuffled_matrix.T
@dataclass
class RandomSplitShuffleKeypad(BaseKeypad):
keypad: np.ndarray
k: int # number of keys
p: int # properties per key
@classmethod
def new_keypad(cls, k: int, p: int) -> Self:
kp = cls._build_keypad(k, p)
return cls(keypad=kp, k=k, p=p)
def shuffle(self):
column_permutation = np.random.permutation(self.p)
column_subset = column_permutation[:self.p // 2]
new_key_idxs = np.random.permutation(self.k)
shuffled_sets = self.keypad.copy()
shuffled_sets[:, column_subset] = shuffled_sets[new_key_idxs, :][:, column_subset]
self.keypad = shuffled_sets
@dataclass
class SlidingSplitShuffleKeypad(BaseKeypad):
keypad: np.ndarray
k: int # number of keys
p: int # properties per key
@classmethod
def new_keypad(cls, k: int, p: int) -> Self:
kp = cls._build_keypad(k, p)
return cls(keypad=kp, k=k, p=p)
def shuffle(self):
selected_positions = np.random.permutation(self.p)
column_subset = selected_positions[:self.p // 2]
shift = np.random.randint(1, self.k)
new_key_idxs = np.roll(np.arange(self.k), shift)
shuffled_sets = self.keypad.copy()
shuffled_sets[:, column_subset] = shuffled_sets[new_key_idxs, :][:, column_subset]
self.keypad = shuffled_sets

View File

@@ -0,0 +1,80 @@
from dataclasses import dataclass
import numpy as np
@dataclass
class Tower:
floors: list[np.ndarray]
def split_tower(self) -> tuple[np.ndarray, np.ndarray]:
discard = np.array([], dtype=int)
keep = np.array([], dtype=int)
balance = self.balance()
for idx, floor in enumerate(self.floors):
div = len(floor)//2 + balance[idx]
floor_shuffle = np.random.permutation(len(floor))
keep = np.concatenate((keep, floor[floor_shuffle[:div]]))
discard = np.concatenate((discard, floor[floor_shuffle[div:]]))
diff = len(discard) - len(keep)
assert 0 <= diff <= 1
return keep, discard
def balance(self) -> list[int]:
odd_floors = np.array([idx for idx, el in enumerate(self.floors) if len(el) & 1])
balance = np.zeros(len(self.floors), dtype=int)
if len(odd_floors) == 0:
return balance.tolist()
shuffle = np.random.permutation(len(odd_floors))[:len(odd_floors) // 2]
odd_floors = odd_floors[shuffle]
balance[odd_floors] = 1
return balance.tolist()
def update_tower(self, keep: np.ndarray, other_discard: np.ndarray):
new_floors = []
for floor in self.floors:
new_floor = np.intersect1d(floor, keep)
if len(new_floor):
new_floors.append(new_floor)
self.floors = new_floors
self.floors.insert(0, other_discard)
def __str__(self):
str_val = ""
floor_numb = [i for i in reversed(range(len(self.floors)))]
for idx, val in enumerate(reversed(self.floors)):
str_val += f"Floor {floor_numb[idx]}: {val.tolist()}\n"
return str_val
def tolist(self) -> list[int]:
tower = []
for floor in self.floors:
tower.extend(floor.tolist())
return tower
@dataclass
class TowerShuffle:
total_positions: int
left_tower: Tower
right_tower: Tower
@classmethod
def new(cls, total_pos:int):
assert total_pos >= 3
rand_pos = np.random.permutation(total_pos)
return TowerShuffle(
total_positions=total_pos,
left_tower=Tower(floors=[rand_pos[:total_pos//2]]),
right_tower=Tower(floors=[rand_pos[total_pos//2:]]),
)
def shuffle(self):
left_keep, left_discard = self.left_tower.split_tower()
right_keep, right_discard = self.right_tower.split_tower()
self.left_tower.update_tower(left_keep, right_discard)
self.right_tower.update_tower(right_keep, left_discard)
def __str__(self):
return f"""Left Tower:
{self.left_tower}
Right Tower:
{self.right_tower}"""

View File

@@ -1,43 +1,30 @@
import random
from enum import Enum
from math import factorial, comb
from src.evilkode import Observation
from src.keypad import Keypad
from src.evilnkode import Observation
from src.keypad.keypad import BaseKeypad
from typing import Iterator
def total_valid_nkode_states(k: int, p: int) -> int:
return factorial(k) ** (p - 1)
def total_shuffle_states(k: int, p: int) -> int:
return comb((p - 1), (p - 1) // 2) * factorial(k)
class ShuffleTypes(Enum):
FULL_SHUFFLE = "FULL_SHUFFLE"
SPLIT_SHUFFLE = "SPLIT_SHUFFLE"
def observations(target_passcode: list[int], number_of_keys:int, properties_per_key: int, min_complexity: int, min_disparity: int, shuffle_type: ShuffleTypes, number_of_observations: int = 100):
k = number_of_keys
p = properties_per_key
keypad = Keypad.new_keypad(k, p)
def obs_gen():
def observations(target_passcode: list[int], keypad: BaseKeypad, number_of_observations: int = 100) -> Iterator[
Observation]:
def obs():
for _ in range(number_of_observations):
yield Observation(
keypad=keypad.keypad.copy(),
keypad=keypad.keypad_mat(),
key_selection=keypad.key_entry(target_passcode=target_passcode)
)
match shuffle_type:
case ShuffleTypes.FULL_SHUFFLE:
keypad.full_shuffle()
case ShuffleTypes.SPLIT_SHUFFLE:
keypad.split_shuffle()
case _:
raise Exception(f"no shuffle type {shuffle_type}")
keypad.shuffle()
return obs_gen()
return obs()
def passcode_generator(k: int, p: int, n: int, c: int, d: int) -> list[int]:

View File

@@ -1,6 +1,14 @@
from src.utils import passcode_generator
from src.keypad.keypad import (
RandomShuffleKeypad,
RandomSplitShuffleKeypad,
SlidingTowerShuffleKeypad,
SlidingSplitShuffleKeypad
)
from src.benchmark import benchmark
import pytest
@pytest.mark.parametrize(
"k, p, n, c, d, runs",
[
@@ -13,3 +21,26 @@ def test_passcode_generator(k, p, n, c, d, runs):
passcode_sets = [el // p for el in passcode]
assert c <= len(set(passcode))
assert d <= len(set(passcode_sets))
@pytest.mark.parametrize(
"number_of_keys,properties_per_key,passcode_len,max_tries_before_lockout,complexity,disparity,run_count,keypad",
[
(6, 8, 4, 5, 4, 4, 100, RandomShuffleKeypad),
(6, 8, 4, 5, 4, 4, 100, RandomSplitShuffleKeypad),
(6, 8, 4, 5, 4, 4, 100, SlidingSplitShuffleKeypad),
(6, 8, 4, 5, 4, 4, 100, SlidingTowerShuffleKeypad),
]
)
def test_benchmark(number_of_keys, properties_per_key, passcode_len, max_tries_before_lockout, complexity, disparity,
run_count, keypad):
benchmark(
number_of_keys=number_of_keys,
properties_per_key=properties_per_key,
passcode_len=passcode_len,
max_tries_before_lockout=max_tries_before_lockout,
run_count=run_count,
complexity=complexity,
disparity=disparity,
keypad=keypad.new_keypad(number_of_keys, properties_per_key)
)

View File

@@ -1,45 +1,34 @@
import random
import pytest
from src.evilkode import Evilkode, Observation
from src.keypad import Keypad
from src.evilnkode import EvilNKode
from src.keypad.keypad import (
RandomShuffleKeypad,
)
from src.utils import observations
@pytest.fixture
def observations(number_of_keys, properties_per_key, passcode_len):
k = number_of_keys
p = properties_per_key
n = passcode_len
nkode = [random.randint(0, k*p-1) for _ in range(n)]
keypad = Keypad.new_keypad(k, p)
def obs_gen():
for _ in range(100): # finite number of yields
yield Observation(
keypad=keypad.keypad.copy(),
key_selection=keypad.key_entry(target_passcode=nkode)
)
keypad.split_shuffle()
return obs_gen()
def passcode(number_of_keys, properties_per_key, passcode_len):
return [random.randint(0, number_of_keys * properties_per_key - 1) for _ in range(passcode_len)]
@pytest.mark.parametrize(
"number_of_keys, properties_per_key, passcode_len",
[
(5, 3, 4), # Test case 1
(5, 4, 4), # Test case 1
(10, 5, 6), # Test case 2
(8, 4, 5), # Test case 3
]
)
def test_evilkode(number_of_keys, properties_per_key, passcode_len, observations):
evilkode = Evilkode(
observations=observations,
def test_evilkode(number_of_keys, properties_per_key, passcode_len, passcode):
keypad = RandomShuffleKeypad.new_keypad(number_of_keys, properties_per_key)
obs = observations(passcode, keypad)
evilkode = EvilNKode(
observations=obs,
number_of_keys=number_of_keys,
properties_per_key=properties_per_key,
passcode_len=passcode_len,
)
evilout = evilkode.run()
assert evilout.iterations > 1
assert evilout.iterations_to_break > 1

View File

@@ -1,31 +1,34 @@
import pytest
import numpy as np
from src.keypad.keypad import (
RandomSplitShuffleKeypad,
RandomShuffleKeypad,
SlidingSplitShuffleKeypad,
SlidingTowerShuffleKeypad,
)
from src.keypad import Keypad
def test_keypad():
keypad = Keypad(
def test_key_entry():
keypad = RandomShuffleKeypad(
keypad=np.array([
[8, 9, 10, 11],
[0, 5, 2, 3],
[4, 1, 6, 7]
]), k= 3, p=4, keypad_cache=[])
]), k=3, p=4)
assert keypad.key_entry([8, 5, 6, 11]) == [0, 1, 2, 0]
def test_split_shuffle():
p = 4 # properties_per_key
k = 3 # number_of_keys
keypad = Keypad.new_keypad(k, p)
print(keypad.keypad)
keypad.split_shuffle()
print(keypad.keypad)
def test_full_shuffle():
p = 4 # properties_per_key
k = 3 # number_of_keys
keypad = Keypad.new_keypad(k, p)
@pytest.mark.parametrize(
"keypad_type, number_of_keys, properties_per_key",
[
(RandomShuffleKeypad, 3, 4),
(RandomSplitShuffleKeypad, 3, 4),
(SlidingTowerShuffleKeypad, 3, 4),
(SlidingSplitShuffleKeypad, 3, 4),
]
)
def test_keypad_shuffle(keypad_type, number_of_keys, properties_per_key):
keypad = keypad_type.new_keypad(number_of_keys, properties_per_key)
print(keypad.keypad)
keypad.full_shuffle()
keypad.shuffle()
print(keypad.keypad)

View File

@@ -0,0 +1,8 @@
from src.keypad.tower_shuffle import TowerShuffle
def test_tower_shuffle():
tower = TowerShuffle.new(9)
print(tower)
for _ in range(100):
tower.shuffle()
print(tower)