owenrusk.dev

casebench

test cases straight onto the board.

git clone https://owenrusk.dev/casebench.git

commit 1f4702d0b6bf1723a5435af631fec938240a47d9
parent 8ee9741288e34bbe0785a872acf8d982fec33b90
author Owen Rusk <owen@papermothgames.com>
date   2025-10-22 11:39:55 -0500
lock: cap the combinations at 64

four lines of four alternates took a few seconds to hash. that's 256; nobody needs that.
casebench/lock.py+7-0
tests/test_lock.py+4-0
diff --git a/casebench/lock.py b/casebench/lock.py
index c4d2ad8..475800c 100644
--- a/casebench/lock.py
+++ b/casebench/lock.py
@@ -1,11 +1,16 @@
 import hashlib
 import hmac
 import itertools
+import math
 import os
 from dataclasses import dataclass, replace
 from datetime import datetime, timedelta
 
 
+# each combination is a scrypt, at lock time. past this, write fewer alternates.
+MAX_COMBINATIONS = 64
+
+
 @dataclass(frozen=True)
 class Rest:
     after: int
@@ -49,6 +54,8 @@ def make(answers: list[str], rest: Rest, text: str = "locked.") -> Lock:
     choices = [[a for a in (normalize(part) for part in line.split("|")) if a] for line in answers]
     if not choices or not all(choices):
         raise ValueError("a lock needs an answer on every line")
+    if math.prod(len(choice) for choice in choices) > MAX_COMBINATIONS:
+        raise ValueError(f"more than {MAX_COMBINATIONS} combinations of answers")
     salt = os.urandom(16)
     hashes = sorted(digest(list(combination), salt) for combination in itertools.product(*choices))
     return Lock(text, len(answers), salt, tuple(hashes), rest)
diff --git a/tests/test_lock.py b/tests/test_lock.py
index 2eb2a4d..c97df15 100644
--- a/tests/test_lock.py
+++ b/tests/test_lock.py
@@ -67,6 +67,10 @@ class AttemptTest(unittest.TestCase):
             self.assertEqual(lock.attempt(either, typed, T0)[0], "open")
         self.assertEqual(lock.attempt(either, ["grey", "hat"], T0)[0], "wrong")
 
+    def test_too_many_combinations(self):
+        with self.assertRaisesRegex(ValueError, "combinations"):
+            lock.make(["a|b|c|d"] * 4, lock.Rest(3, 60))
+
     def test_empty_line(self):
         with self.assertRaises(ValueError):
             lock.make(["a", " | "], lock.Rest(3, 60))