packet-tracer-skill 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +424 -42
- package/README.md +535 -250
- package/SKILL.md +337 -262
- package/bin/packet-tracer-skill.js +29 -2
- package/docs/automation-controller-proof.md +35 -0
- package/docs/curated-donor-registry.md +11 -0
- package/docs/generate-ready-pilot-design.md +30 -0
- package/docs/github-launch-ops-0.2.3.md +37 -0
- package/docs/github-metadata.md +6 -4
- package/docs/hero-demo-plan.md +1 -1
- package/docs/home-iot-donor-proof.md +4 -4
- package/docs/industrial-programming-proof.md +48 -0
- package/docs/ipv4-routing-management-proof.md +37 -0
- package/docs/l2-resiliency-bgp-proof.md +60 -0
- package/docs/l2-security-qos-proof.md +59 -0
- package/docs/packet-tracer-feature-gap-atlas.md +174 -17
- package/docs/post-launch-follow-up.md +9 -5
- package/docs/proof-readiness-dashboard.md +69 -0
- package/docs/publish-preview-roadmap.md +6 -5
- package/docs/release-checklist.md +27 -13
- package/docs/release-notes-0.2.2.md +1 -1
- package/docs/release-notes-0.2.3.md +59 -0
- package/docs/release-notes-0.2.4.md +20 -0
- package/docs/runtime-truth.md +33 -8
- package/docs/security-edge-deepening-proof.md +65 -0
- package/docs/voice-collaboration-proof.md +38 -0
- package/docs/wan-security-donor-proof.md +20 -3
- package/examples/README.md +98 -69
- package/examples/complex_campus_master_edit_v4.inventory.json +12 -2
- package/examples/gallery.md +94 -6
- package/examples/home_iot_cli_edit_v1.inventory.json +11 -2
- package/examples/index.json +932 -4
- package/examples/local-sample-evidence.json +24 -0
- package/examples/proof-cards.json +117 -0
- package/examples/service_heavy_cli_edit_v1.inventory.json +11 -2
- package/package.json +60 -44
- package/pytest.ini +9 -0
- package/references/packettracer-feature-atlas.json +67 -17
- package/references/packettracer-sample-catalog.json +45287 -4525
- package/references/packettracer-sample-catalog.md +599 -259
- package/references/proof-readiness-candidates.json +352 -0
- package/scripts/build_examples_index.py +228 -35
- package/scripts/build_sample_catalog.py +24 -44
- package/scripts/corpus_runner.py +430 -0
- package/scripts/coverage_matrix.py +1842 -1319
- package/scripts/donor_cache.py +354 -0
- package/scripts/donor_diagnostics.py +3 -1
- package/scripts/feature_atlas.py +65 -1
- package/scripts/generate_pkt.py +8762 -4070
- package/scripts/intent_parser.py +2242 -1138
- package/scripts/local_donors.py +340 -0
- package/scripts/packet_tracer_env.py +846 -391
- package/scripts/pkt_annotate.py +218 -0
- package/scripts/pkt_codec.py +420 -181
- package/scripts/pkt_editor.py +2405 -1226
- package/scripts/pkt_transformer.py +1072 -727
- package/scripts/pkt_verify.py +461 -0
- package/scripts/remote_search.py +197 -21
- package/scripts/runtime_doctor.py +80 -29
- package/scripts/sample_catalog.py +1372 -1195
- package/scripts/twofish_diagnostics.py +48 -31
- package/scripts/usage_ledger.py +218 -0
- package/scripts/vendor/README.md +44 -37
- package/scripts/vendor/twofish_pure.py +321 -0
- package/scripts/workspace_repair.py +548 -508
- package/templates/pt900/donors/README.md +15 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pure-Python Twofish block cipher.
|
|
3
|
+
|
|
4
|
+
Twofish was designed by Bruce Schneier, John Kelsey, Doug Whiting, David Wagner,
|
|
5
|
+
Chris Hall and Niels Ferguson. It is unpatented, uncopyrighted, and free for all
|
|
6
|
+
uses, so it can be vendored directly instead of binding to a compiled bridge.
|
|
7
|
+
|
|
8
|
+
This module exists so that `pkt_codec` has a runtime that works on a clean
|
|
9
|
+
checkout with no compiled artifacts and no environment variables. The ctypes
|
|
10
|
+
bridge in `twofish.py` remains supported as an optional accelerator.
|
|
11
|
+
|
|
12
|
+
Reference: "Twofish: A 128-Bit Block Cipher" (Schneier et al., 1998), and the
|
|
13
|
+
official test vectors reproduced in `self_test()` below.
|
|
14
|
+
|
|
15
|
+
The implementation uses the standard "full keying" optimisation: the key-dependent
|
|
16
|
+
S-box and the MDS matrix multiply are folded into four 256-entry word tables at
|
|
17
|
+
key-schedule time, so the hot loop is four table lookups and three XORs per g().
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
MASK32 = 0xFFFFFFFF
|
|
23
|
+
BLOCK_SIZE = 16
|
|
24
|
+
ROUNDS = 16
|
|
25
|
+
|
|
26
|
+
# --- q permutation construction (spec section 4.3.5) ------------------------
|
|
27
|
+
|
|
28
|
+
_Q0_T = (
|
|
29
|
+
(0x8, 0x1, 0x7, 0xD, 0x6, 0xF, 0x3, 0x2, 0x0, 0xB, 0x5, 0x9, 0xE, 0xC, 0xA, 0x4),
|
|
30
|
+
(0xE, 0xC, 0xB, 0x8, 0x1, 0x2, 0x3, 0x5, 0xF, 0x4, 0xA, 0x6, 0x7, 0x0, 0x9, 0xD),
|
|
31
|
+
(0xB, 0xA, 0x5, 0xE, 0x6, 0xD, 0x9, 0x0, 0xC, 0x8, 0xF, 0x3, 0x2, 0x4, 0x7, 0x1),
|
|
32
|
+
(0xD, 0x7, 0xF, 0x4, 0x1, 0x2, 0x6, 0xE, 0x9, 0xB, 0x3, 0x0, 0x8, 0x5, 0xC, 0xA),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
_Q1_T = (
|
|
36
|
+
(0x2, 0x8, 0xB, 0xD, 0xF, 0x7, 0x6, 0xE, 0x3, 0x1, 0x9, 0x4, 0x0, 0xA, 0xC, 0x5),
|
|
37
|
+
(0x1, 0xE, 0x2, 0xB, 0x4, 0xC, 0x3, 0x7, 0x6, 0xD, 0xA, 0x5, 0xF, 0x9, 0x0, 0x8),
|
|
38
|
+
(0x4, 0xC, 0x7, 0x5, 0x1, 0x6, 0x9, 0xA, 0x0, 0xE, 0xD, 0x8, 0x2, 0xB, 0x3, 0xF),
|
|
39
|
+
(0xB, 0x9, 0x5, 0x1, 0xC, 0x3, 0xD, 0xE, 0x6, 0x4, 0x7, 0xF, 0x2, 0x0, 0x8, 0xA),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _build_q(t: tuple[tuple[int, ...], ...]) -> tuple[int, ...]:
|
|
44
|
+
table = []
|
|
45
|
+
for x in range(256):
|
|
46
|
+
a0, b0 = x >> 4, x & 0xF
|
|
47
|
+
a1 = a0 ^ b0
|
|
48
|
+
b1 = (a0 ^ ((b0 >> 1) | ((b0 & 1) << 3)) ^ (a0 << 3)) & 0xF
|
|
49
|
+
a2, b2 = t[0][a1], t[1][b1]
|
|
50
|
+
a3 = a2 ^ b2
|
|
51
|
+
b3 = (a2 ^ ((b2 >> 1) | ((b2 & 1) << 3)) ^ (a2 << 3)) & 0xF
|
|
52
|
+
a4, b4 = t[2][a3], t[3][b3]
|
|
53
|
+
table.append((b4 << 4) | a4)
|
|
54
|
+
return tuple(table)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
Q0 = _build_q(_Q0_T)
|
|
58
|
+
Q1 = _build_q(_Q1_T)
|
|
59
|
+
|
|
60
|
+
# --- GF(2^8) helpers --------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
MDS_POLY = 0x169 # x^8 + x^6 + x^5 + x^3 + 1
|
|
63
|
+
RS_POLY = 0x14D # x^8 + x^6 + x^3 + x^2 + 1
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _gf_mul(a: int, b: int, poly: int) -> int:
|
|
67
|
+
result = 0
|
|
68
|
+
while b:
|
|
69
|
+
if b & 1:
|
|
70
|
+
result ^= a
|
|
71
|
+
b >>= 1
|
|
72
|
+
a <<= 1
|
|
73
|
+
if a & 0x100:
|
|
74
|
+
a ^= poly
|
|
75
|
+
return result & 0xFF
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# MDS matrix, by column: column i scaled by y_i contributes to z0..z3.
|
|
79
|
+
_MDS_COLUMNS = (
|
|
80
|
+
(0x01, 0x5B, 0xEF, 0xEF),
|
|
81
|
+
(0xEF, 0xEF, 0x5B, 0x01),
|
|
82
|
+
(0x5B, 0xEF, 0x01, 0xEF),
|
|
83
|
+
(0x5B, 0x01, 0xEF, 0x5B),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# mds_column_word[i][b] == the 32-bit little-endian word contributed by byte b
|
|
87
|
+
# sitting in position i of the MDS input vector.
|
|
88
|
+
_MDS_COLUMN_WORD: tuple[tuple[int, ...], ...] = tuple(
|
|
89
|
+
tuple(
|
|
90
|
+
(_gf_mul(column[0], b, MDS_POLY))
|
|
91
|
+
| (_gf_mul(column[1], b, MDS_POLY) << 8)
|
|
92
|
+
| (_gf_mul(column[2], b, MDS_POLY) << 16)
|
|
93
|
+
| (_gf_mul(column[3], b, MDS_POLY) << 24)
|
|
94
|
+
for b in range(256)
|
|
95
|
+
)
|
|
96
|
+
for column in _MDS_COLUMNS
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
_RS_MATRIX = (
|
|
100
|
+
(0x01, 0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E),
|
|
101
|
+
(0xA4, 0x56, 0x82, 0xF3, 0x1E, 0xC6, 0x68, 0xE5),
|
|
102
|
+
(0x02, 0xA1, 0xFC, 0xC1, 0x47, 0xAE, 0x3D, 0x19),
|
|
103
|
+
(0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E, 0x03),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _rs_encode(chunk: bytes) -> int:
|
|
108
|
+
"""Map 8 key bytes to one 32-bit S-vector word via the RS matrix."""
|
|
109
|
+
word = 0
|
|
110
|
+
for row_index, row in enumerate(_RS_MATRIX):
|
|
111
|
+
acc = 0
|
|
112
|
+
for coefficient, byte in zip(row, chunk):
|
|
113
|
+
acc ^= _gf_mul(coefficient, byte, RS_POLY)
|
|
114
|
+
word |= acc << (8 * row_index)
|
|
115
|
+
return word
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# --- rotations --------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _rol32(value: int, count: int) -> int:
|
|
122
|
+
count &= 31
|
|
123
|
+
return ((value << count) | (value >> (32 - count))) & MASK32
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _ror32(value: int, count: int) -> int:
|
|
127
|
+
count &= 31
|
|
128
|
+
return ((value >> count) | (value << (32 - count))) & MASK32
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# --- h function -------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _h_byte(position: int, byte: int, l_bytes: list[tuple[int, int, int, int]], k: int) -> int:
|
|
135
|
+
"""The q-permutation cascade of h() for one byte position, before MDS."""
|
|
136
|
+
y = byte
|
|
137
|
+
if k == 4:
|
|
138
|
+
y = (Q1, Q0, Q0, Q1)[position][y] ^ l_bytes[3][position]
|
|
139
|
+
if k >= 3:
|
|
140
|
+
y = (Q1, Q1, Q0, Q0)[position][y] ^ l_bytes[2][position]
|
|
141
|
+
y = (Q0, Q1, Q0, Q1)[position][y] ^ l_bytes[1][position]
|
|
142
|
+
y = (Q0, Q0, Q1, Q1)[position][y] ^ l_bytes[0][position]
|
|
143
|
+
return (Q1, Q0, Q1, Q0)[position][y]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _h(x: int, l_words: list[int], k: int) -> int:
|
|
147
|
+
l_bytes = [
|
|
148
|
+
(word & 0xFF, (word >> 8) & 0xFF, (word >> 16) & 0xFF, (word >> 24) & 0xFF)
|
|
149
|
+
for word in l_words
|
|
150
|
+
]
|
|
151
|
+
result = 0
|
|
152
|
+
for position in range(4):
|
|
153
|
+
byte = (x >> (8 * position)) & 0xFF
|
|
154
|
+
result ^= _MDS_COLUMN_WORD[position][_h_byte(position, byte, l_bytes, k)]
|
|
155
|
+
return result
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class Twofish:
|
|
159
|
+
"""Twofish in ECB single-block form, API-compatible with the ctypes bridge."""
|
|
160
|
+
|
|
161
|
+
def __init__(self, key: bytes) -> None:
|
|
162
|
+
if not isinstance(key, (bytes, bytearray)):
|
|
163
|
+
raise TypeError("Twofish key must be bytes")
|
|
164
|
+
key = bytes(key)
|
|
165
|
+
if not 0 < len(key) <= 32:
|
|
166
|
+
raise ValueError("invalid Twofish key length")
|
|
167
|
+
|
|
168
|
+
# Twofish is defined for 128/192/256-bit keys; shorter keys are
|
|
169
|
+
# zero-padded up to the next legal size, matching the reference library.
|
|
170
|
+
for legal in (16, 24, 32):
|
|
171
|
+
if len(key) <= legal:
|
|
172
|
+
key = key.ljust(legal, b"\x00")
|
|
173
|
+
break
|
|
174
|
+
|
|
175
|
+
k = len(key) // 8
|
|
176
|
+
words = [int.from_bytes(key[i : i + 4], "little") for i in range(0, len(key), 4)]
|
|
177
|
+
me = [words[i] for i in range(0, 2 * k, 2)]
|
|
178
|
+
mo = [words[i] for i in range(1, 2 * k, 2)]
|
|
179
|
+
s_words = [_rs_encode(key[8 * i : 8 * (i + 1)]) for i in range(k)]
|
|
180
|
+
s_words.reverse()
|
|
181
|
+
|
|
182
|
+
subkeys: list[int] = []
|
|
183
|
+
for i in range(20):
|
|
184
|
+
a = _h((2 * i) * 0x01010101, me, k)
|
|
185
|
+
b = _rol32(_h((2 * i + 1) * 0x01010101, mo, k), 8)
|
|
186
|
+
subkeys.append((a + b) & MASK32)
|
|
187
|
+
subkeys.append(_rol32((a + 2 * b) & MASK32, 9))
|
|
188
|
+
self._k = subkeys
|
|
189
|
+
|
|
190
|
+
# Fold the key-dependent S-box and the MDS multiply into four word
|
|
191
|
+
# tables, so g() costs four lookups instead of a full h() evaluation.
|
|
192
|
+
s_bytes = [
|
|
193
|
+
(word & 0xFF, (word >> 8) & 0xFF, (word >> 16) & 0xFF, (word >> 24) & 0xFF)
|
|
194
|
+
for word in s_words
|
|
195
|
+
]
|
|
196
|
+
self._sbox: list[tuple[int, ...]] = [
|
|
197
|
+
tuple(
|
|
198
|
+
_MDS_COLUMN_WORD[position][_h_byte(position, byte, s_bytes, k)]
|
|
199
|
+
for byte in range(256)
|
|
200
|
+
)
|
|
201
|
+
for position in range(4)
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
def _g(self, x: int) -> int:
|
|
205
|
+
sbox = self._sbox
|
|
206
|
+
return (
|
|
207
|
+
sbox[0][x & 0xFF]
|
|
208
|
+
^ sbox[1][(x >> 8) & 0xFF]
|
|
209
|
+
^ sbox[2][(x >> 16) & 0xFF]
|
|
210
|
+
^ sbox[3][(x >> 24) & 0xFF]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# The round function is written out longhand rather than calling `_g`,
|
|
214
|
+
# `_rol32` and `_ror32`. In CPython the call overhead dominates: inlining
|
|
215
|
+
# is worth roughly 2x, and these two methods are the whole cost of
|
|
216
|
+
# encoding or decoding a `.pkt`.
|
|
217
|
+
#
|
|
218
|
+
# `g(rol32(r1, 8))` is folded into the lookups: rotating left by 8 permutes
|
|
219
|
+
# the byte positions to (b3, b0, b1, b2), so the rotate never has to happen.
|
|
220
|
+
|
|
221
|
+
def encrypt(self, block: bytes) -> bytes:
|
|
222
|
+
if not isinstance(block, (bytes, bytearray)):
|
|
223
|
+
raise TypeError("block must be bytes")
|
|
224
|
+
if len(block) != BLOCK_SIZE:
|
|
225
|
+
raise ValueError("Twofish encrypt expects a 16-byte block")
|
|
226
|
+
|
|
227
|
+
k = self._k
|
|
228
|
+
s0, s1, s2, s3 = self._sbox
|
|
229
|
+
r0 = int.from_bytes(block[0:4], "little") ^ k[0]
|
|
230
|
+
r1 = int.from_bytes(block[4:8], "little") ^ k[1]
|
|
231
|
+
r2 = int.from_bytes(block[8:12], "little") ^ k[2]
|
|
232
|
+
r3 = int.from_bytes(block[12:16], "little") ^ k[3]
|
|
233
|
+
|
|
234
|
+
for index in range(8, 40, 2):
|
|
235
|
+
t0 = s0[r0 & 0xFF] ^ s1[(r0 >> 8) & 0xFF] ^ s2[(r0 >> 16) & 0xFF] ^ s3[r0 >> 24]
|
|
236
|
+
t1 = s0[r1 >> 24] ^ s1[r1 & 0xFF] ^ s2[(r1 >> 8) & 0xFF] ^ s3[(r1 >> 16) & 0xFF]
|
|
237
|
+
x = r2 ^ ((t0 + t1 + k[index]) & MASK32)
|
|
238
|
+
r0, r1, r2, r3 = (
|
|
239
|
+
(x >> 1) | ((x & 1) << 31),
|
|
240
|
+
(((r3 << 1) & MASK32) | (r3 >> 31)) ^ ((t0 + 2 * t1 + k[index + 1]) & MASK32),
|
|
241
|
+
r0,
|
|
242
|
+
r1,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# The final swap is undone by reading the registers back in 2,3,0,1 order.
|
|
246
|
+
return (
|
|
247
|
+
(r2 ^ k[4]).to_bytes(4, "little")
|
|
248
|
+
+ (r3 ^ k[5]).to_bytes(4, "little")
|
|
249
|
+
+ (r0 ^ k[6]).to_bytes(4, "little")
|
|
250
|
+
+ (r1 ^ k[7]).to_bytes(4, "little")
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def decrypt(self, block: bytes) -> bytes:
|
|
254
|
+
if not isinstance(block, (bytes, bytearray)):
|
|
255
|
+
raise TypeError("block must be bytes")
|
|
256
|
+
if len(block) != BLOCK_SIZE:
|
|
257
|
+
raise ValueError("Twofish decrypt expects a 16-byte block")
|
|
258
|
+
|
|
259
|
+
k = self._k
|
|
260
|
+
s0, s1, s2, s3 = self._sbox
|
|
261
|
+
r0 = int.from_bytes(block[0:4], "little") ^ k[4]
|
|
262
|
+
r1 = int.from_bytes(block[4:8], "little") ^ k[5]
|
|
263
|
+
r2 = int.from_bytes(block[8:12], "little") ^ k[6]
|
|
264
|
+
r3 = int.from_bytes(block[12:16], "little") ^ k[7]
|
|
265
|
+
|
|
266
|
+
for index in range(38, 6, -2):
|
|
267
|
+
t0 = s0[r0 & 0xFF] ^ s1[(r0 >> 8) & 0xFF] ^ s2[(r0 >> 16) & 0xFF] ^ s3[r0 >> 24]
|
|
268
|
+
t1 = s0[r1 >> 24] ^ s1[r1 & 0xFF] ^ s2[(r1 >> 8) & 0xFF] ^ s3[(r1 >> 16) & 0xFF]
|
|
269
|
+
x = r3 ^ ((t0 + 2 * t1 + k[index + 1]) & MASK32)
|
|
270
|
+
r0, r1, r2, r3 = (
|
|
271
|
+
(((r2 << 1) & MASK32) | (r2 >> 31)) ^ ((t0 + t1 + k[index]) & MASK32),
|
|
272
|
+
(x >> 1) | ((x & 1) << 31),
|
|
273
|
+
r0,
|
|
274
|
+
r1,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
(r2 ^ k[0]).to_bytes(4, "little")
|
|
279
|
+
+ (r3 ^ k[1]).to_bytes(4, "little")
|
|
280
|
+
+ (r0 ^ k[2]).to_bytes(4, "little")
|
|
281
|
+
+ (r1 ^ k[3]).to_bytes(4, "little")
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# Official Twofish test vectors: the I=3 (128-bit) and I=4 (192/256-bit) cases
|
|
286
|
+
# from section B.2 of the Twofish book.
|
|
287
|
+
TEST_VECTORS = (
|
|
288
|
+
(
|
|
289
|
+
"9F589F5CF6122C32B6BFEC2F2AE8C35A",
|
|
290
|
+
"D491DB16E7B1C39E86CB086B789F5419",
|
|
291
|
+
"019F9809DE1711858FAAC3A3BA20FBC3",
|
|
292
|
+
),
|
|
293
|
+
(
|
|
294
|
+
"88B2B2706B105E36B446BB6D731A1E88EFA71F788965BD44",
|
|
295
|
+
"39DA69D6BA4997D585B6DC073CA341B2",
|
|
296
|
+
"182B02D81497EA45F9DAACDC29193A65",
|
|
297
|
+
),
|
|
298
|
+
(
|
|
299
|
+
"D43BB7556EA32E46F2A282B7D45B4E0D57FF739D4DC92C1BD7FC01700CC8216F",
|
|
300
|
+
"90AFE91BB288544F2C32DC239B2635E6",
|
|
301
|
+
"6CB4561C40BF0A9705931CB6D408E7FA",
|
|
302
|
+
),
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def self_test() -> None:
|
|
307
|
+
"""Verify against the official vectors. Raises on any mismatch."""
|
|
308
|
+
for key_hex, plain_hex, cipher_hex in TEST_VECTORS:
|
|
309
|
+
key = bytes.fromhex(key_hex)
|
|
310
|
+
plain = bytes.fromhex(plain_hex)
|
|
311
|
+
cipher = bytes.fromhex(cipher_hex)
|
|
312
|
+
engine = Twofish(key)
|
|
313
|
+
if engine.encrypt(plain) != cipher:
|
|
314
|
+
raise ValueError(f"Twofish encrypt vector failed for {len(key) * 8}-bit key")
|
|
315
|
+
if engine.decrypt(cipher) != plain:
|
|
316
|
+
raise ValueError(f"Twofish decrypt vector failed for {len(key) * 8}-bit key")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
if __name__ == "__main__":
|
|
320
|
+
self_test()
|
|
321
|
+
print("pure-python twofish: all official vectors pass")
|