oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl

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.
Files changed (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1025 @@
1
+ """Differential Power Analysis (DPA) framework for side-channel attacks.
2
+
3
+ This module implements DPA, CPA (Correlation Power Analysis), and Template attacks
4
+ for recovering cryptographic keys from power consumption traces. Supports AES and DES
5
+ with multiple leakage models.
6
+
7
+ Key capabilities:
8
+ - DPA attack using difference of means
9
+ - CPA attack using Pearson correlation
10
+ - Template attack with profiling and matching phases
11
+ - Multiple leakage models (Hamming weight, Hamming distance, identity)
12
+ - AES S-box and DES operations
13
+ - Visualization of correlation traces and key rankings
14
+ - JSON export of attack results
15
+
16
+ Typical use cases:
17
+ - Break AES-128 implementations using power analysis
18
+ - Recover DES keys from embedded devices
19
+ - Evaluate cryptographic implementation security
20
+ - Generate attack visualizations for reports
21
+
22
+ Example:
23
+ >>> from oscura.side_channel.dpa import DPAAnalyzer, PowerTrace
24
+ >>> import numpy as np
25
+ >>> # Create analyzer
26
+ >>> analyzer = DPAAnalyzer(attack_type="cpa", leakage_model="hamming_weight")
27
+ >>> # Load power traces with plaintexts
28
+ >>> traces = [
29
+ ... PowerTrace(
30
+ ... timestamp=np.arange(1000),
31
+ ... power=np.random.randn(1000),
32
+ ... plaintext=bytes([i % 256 for i in range(16)])
33
+ ... )
34
+ ... for _ in range(100)
35
+ ... ]
36
+ >>> # Perform attack on first key byte
37
+ >>> result = analyzer.perform_attack(traces, target_byte=0, algorithm="aes128")
38
+ >>> print(f"Recovered key byte: 0x{result.recovered_key[0]:02X}")
39
+ >>> print(f"Confidence: {result.confidence:.2%}")
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ import json
45
+ import logging
46
+ from dataclasses import dataclass, field
47
+ from pathlib import Path
48
+ from typing import TYPE_CHECKING, Any, ClassVar
49
+
50
+ import numpy as np
51
+
52
+ if TYPE_CHECKING:
53
+ from collections.abc import Sequence
54
+
55
+ from numpy.typing import NDArray
56
+
57
+ logger = logging.getLogger(__name__)
58
+
59
+
60
+ # AES S-box (forward substitution box)
61
+ AES_SBOX = [
62
+ 0x63,
63
+ 0x7C,
64
+ 0x77,
65
+ 0x7B,
66
+ 0xF2,
67
+ 0x6B,
68
+ 0x6F,
69
+ 0xC5,
70
+ 0x30,
71
+ 0x01,
72
+ 0x67,
73
+ 0x2B,
74
+ 0xFE,
75
+ 0xD7,
76
+ 0xAB,
77
+ 0x76,
78
+ 0xCA,
79
+ 0x82,
80
+ 0xC9,
81
+ 0x7D,
82
+ 0xFA,
83
+ 0x59,
84
+ 0x47,
85
+ 0xF0,
86
+ 0xAD,
87
+ 0xD4,
88
+ 0xA2,
89
+ 0xAF,
90
+ 0x9C,
91
+ 0xA4,
92
+ 0x72,
93
+ 0xC0,
94
+ 0xB7,
95
+ 0xFD,
96
+ 0x93,
97
+ 0x26,
98
+ 0x36,
99
+ 0x3F,
100
+ 0xF7,
101
+ 0xCC,
102
+ 0x34,
103
+ 0xA5,
104
+ 0xE5,
105
+ 0xF1,
106
+ 0x71,
107
+ 0xD8,
108
+ 0x31,
109
+ 0x15,
110
+ 0x04,
111
+ 0xC7,
112
+ 0x23,
113
+ 0xC3,
114
+ 0x18,
115
+ 0x96,
116
+ 0x05,
117
+ 0x9A,
118
+ 0x07,
119
+ 0x12,
120
+ 0x80,
121
+ 0xE2,
122
+ 0xEB,
123
+ 0x27,
124
+ 0xB2,
125
+ 0x75,
126
+ 0x09,
127
+ 0x83,
128
+ 0x2C,
129
+ 0x1A,
130
+ 0x1B,
131
+ 0x6E,
132
+ 0x5A,
133
+ 0xA0,
134
+ 0x52,
135
+ 0x3B,
136
+ 0xD6,
137
+ 0xB3,
138
+ 0x29,
139
+ 0xE3,
140
+ 0x2F,
141
+ 0x84,
142
+ 0x53,
143
+ 0xD1,
144
+ 0x00,
145
+ 0xED,
146
+ 0x20,
147
+ 0xFC,
148
+ 0xB1,
149
+ 0x5B,
150
+ 0x6A,
151
+ 0xCB,
152
+ 0xBE,
153
+ 0x39,
154
+ 0x4A,
155
+ 0x4C,
156
+ 0x58,
157
+ 0xCF,
158
+ 0xD0,
159
+ 0xEF,
160
+ 0xAA,
161
+ 0xFB,
162
+ 0x43,
163
+ 0x4D,
164
+ 0x33,
165
+ 0x85,
166
+ 0x45,
167
+ 0xF9,
168
+ 0x02,
169
+ 0x7F,
170
+ 0x50,
171
+ 0x3C,
172
+ 0x9F,
173
+ 0xA8,
174
+ 0x51,
175
+ 0xA3,
176
+ 0x40,
177
+ 0x8F,
178
+ 0x92,
179
+ 0x9D,
180
+ 0x38,
181
+ 0xF5,
182
+ 0xBC,
183
+ 0xB6,
184
+ 0xDA,
185
+ 0x21,
186
+ 0x10,
187
+ 0xFF,
188
+ 0xF3,
189
+ 0xD2,
190
+ 0xCD,
191
+ 0x0C,
192
+ 0x13,
193
+ 0xEC,
194
+ 0x5F,
195
+ 0x97,
196
+ 0x44,
197
+ 0x17,
198
+ 0xC4,
199
+ 0xA7,
200
+ 0x7E,
201
+ 0x3D,
202
+ 0x64,
203
+ 0x5D,
204
+ 0x19,
205
+ 0x73,
206
+ 0x60,
207
+ 0x81,
208
+ 0x4F,
209
+ 0xDC,
210
+ 0x22,
211
+ 0x2A,
212
+ 0x90,
213
+ 0x88,
214
+ 0x46,
215
+ 0xEE,
216
+ 0xB8,
217
+ 0x14,
218
+ 0xDE,
219
+ 0x5E,
220
+ 0x0B,
221
+ 0xDB,
222
+ 0xE0,
223
+ 0x32,
224
+ 0x3A,
225
+ 0x0A,
226
+ 0x49,
227
+ 0x06,
228
+ 0x24,
229
+ 0x5C,
230
+ 0xC2,
231
+ 0xD3,
232
+ 0xAC,
233
+ 0x62,
234
+ 0x91,
235
+ 0x95,
236
+ 0xE4,
237
+ 0x79,
238
+ 0xE7,
239
+ 0xC8,
240
+ 0x37,
241
+ 0x6D,
242
+ 0x8D,
243
+ 0xD5,
244
+ 0x4E,
245
+ 0xA9,
246
+ 0x6C,
247
+ 0x56,
248
+ 0xF4,
249
+ 0xEA,
250
+ 0x65,
251
+ 0x7A,
252
+ 0xAE,
253
+ 0x08,
254
+ 0xBA,
255
+ 0x78,
256
+ 0x25,
257
+ 0x2E,
258
+ 0x1C,
259
+ 0xA6,
260
+ 0xB4,
261
+ 0xC6,
262
+ 0xE8,
263
+ 0xDD,
264
+ 0x74,
265
+ 0x1F,
266
+ 0x4B,
267
+ 0xBD,
268
+ 0x8B,
269
+ 0x8A,
270
+ 0x70,
271
+ 0x3E,
272
+ 0xB5,
273
+ 0x66,
274
+ 0x48,
275
+ 0x03,
276
+ 0xF6,
277
+ 0x0E,
278
+ 0x61,
279
+ 0x35,
280
+ 0x57,
281
+ 0xB9,
282
+ 0x86,
283
+ 0xC1,
284
+ 0x1D,
285
+ 0x9E,
286
+ 0xE1,
287
+ 0xF8,
288
+ 0x98,
289
+ 0x11,
290
+ 0x69,
291
+ 0xD9,
292
+ 0x8E,
293
+ 0x94,
294
+ 0x9B,
295
+ 0x1E,
296
+ 0x87,
297
+ 0xE9,
298
+ 0xCE,
299
+ 0x55,
300
+ 0x28,
301
+ 0xDF,
302
+ 0x8C,
303
+ 0xA1,
304
+ 0x89,
305
+ 0x0D,
306
+ 0xBF,
307
+ 0xE6,
308
+ 0x42,
309
+ 0x68,
310
+ 0x41,
311
+ 0x99,
312
+ 0x2D,
313
+ 0x0F,
314
+ 0xB0,
315
+ 0x54,
316
+ 0xBB,
317
+ 0x16,
318
+ ]
319
+
320
+
321
+ @dataclass
322
+ class PowerTrace:
323
+ """Power consumption trace for side-channel analysis.
324
+
325
+ Attributes:
326
+ timestamp: Time points for each power measurement (seconds or sample index).
327
+ power: Power consumption measurements (arbitrary units, e.g., voltage).
328
+ plaintext: Input plaintext for encryption (16 bytes for AES-128).
329
+ ciphertext: Output ciphertext from encryption (optional).
330
+ metadata: Additional trace information (device ID, temperature, etc.).
331
+
332
+ Example:
333
+ >>> import numpy as np
334
+ >>> trace = PowerTrace(
335
+ ... timestamp=np.linspace(0, 1e-6, 1000), # 1 microsecond
336
+ ... power=np.random.randn(1000),
337
+ ... plaintext=bytes(range(16)),
338
+ ... metadata={"device": "STM32", "temperature": 25.0}
339
+ ... )
340
+ """
341
+
342
+ timestamp: NDArray[np.float64]
343
+ power: NDArray[np.float64]
344
+ plaintext: bytes | None = None
345
+ ciphertext: bytes | None = None
346
+ metadata: dict[str, Any] = field(default_factory=dict)
347
+
348
+
349
+ @dataclass
350
+ class DPAResult:
351
+ """Result from DPA/CPA/Template attack.
352
+
353
+ Attributes:
354
+ recovered_key: Recovered key bytes (1 byte for single-byte attack).
355
+ key_ranks: Correlation or difference-of-means score for each key guess (0-255).
356
+ Higher values indicate more likely key bytes.
357
+ correlation_traces: Time x key_guess correlation matrix (optional).
358
+ Shape: (256, num_samples) for CPA attacks.
359
+ confidence: Attack confidence score (0.0-1.0).
360
+ Based on separation between best and second-best key guess.
361
+ successful: True if attack confidence exceeds threshold (>0.7).
362
+
363
+ Example:
364
+ >>> result = analyzer.perform_attack(traces, target_byte=0)
365
+ >>> if result.successful:
366
+ ... print(f"Key: 0x{result.recovered_key.hex()}")
367
+ ... print(f"Confidence: {result.confidence:.2%}")
368
+ ... else:
369
+ ... print("Attack failed - need more traces")
370
+ """
371
+
372
+ recovered_key: bytes
373
+ key_ranks: NDArray[np.float64]
374
+ correlation_traces: NDArray[np.float64] | None = None
375
+ confidence: float = 0.0
376
+ successful: bool = False
377
+
378
+
379
+ class DPAAnalyzer:
380
+ """Differential Power Analysis framework for cryptographic key recovery.
381
+
382
+ This class implements multiple power analysis attack methods for recovering
383
+ secret keys from power consumption traces. Supports DPA, CPA, and Template
384
+ attacks with various leakage models.
385
+
386
+ Attack Types:
387
+ dpa: Classic DPA using difference of means (Kocher et al., 1999)
388
+ cpa: Correlation Power Analysis (Brier et al., 2004)
389
+ template: Template attack with profiling phase (Chari et al., 2003)
390
+
391
+ Leakage Models:
392
+ hamming_weight: Power proportional to number of 1 bits (most common)
393
+ hamming_distance: Power proportional to bit transitions
394
+ identity: Direct intermediate value (linear leakage)
395
+
396
+ Supported Algorithms:
397
+ aes128: AES-128 encryption (16-byte key)
398
+ des: DES encryption (8-byte key, not implemented yet)
399
+
400
+ Example:
401
+ >>> # Basic CPA attack on AES
402
+ >>> analyzer = DPAAnalyzer(attack_type="cpa", leakage_model="hamming_weight")
403
+ >>> result = analyzer.perform_attack(traces, target_byte=0)
404
+ >>> # Template attack (profiling + matching)
405
+ >>> analyzer_template = DPAAnalyzer(attack_type="template")
406
+ >>> result = analyzer_template.template_attack(
407
+ ... profiling_traces=train_traces,
408
+ ... attack_traces=test_traces,
409
+ ... target_byte=0
410
+ ... )
411
+ """
412
+
413
+ LEAKAGE_MODELS: ClassVar[list[str]] = ["hamming_weight", "hamming_distance", "identity"]
414
+ ATTACK_TYPES: ClassVar[list[str]] = ["dpa", "cpa", "template"]
415
+
416
+ def __init__(
417
+ self,
418
+ attack_type: str = "cpa",
419
+ leakage_model: str = "hamming_weight",
420
+ ) -> None:
421
+ """Initialize DPA analyzer.
422
+
423
+ Args:
424
+ attack_type: Attack method ("dpa", "cpa", "template").
425
+ leakage_model: Power leakage model ("hamming_weight", "hamming_distance",
426
+ "identity").
427
+
428
+ Raises:
429
+ ValueError: If attack_type or leakage_model is invalid.
430
+
431
+ Example:
432
+ >>> analyzer = DPAAnalyzer(attack_type="cpa")
433
+ >>> analyzer = DPAAnalyzer(
434
+ ... attack_type="template",
435
+ ... leakage_model="hamming_distance"
436
+ ... )
437
+ """
438
+ if attack_type not in self.ATTACK_TYPES:
439
+ msg = f"Invalid attack_type: {attack_type}. Must be one of {self.ATTACK_TYPES}"
440
+ raise ValueError(msg)
441
+
442
+ if leakage_model not in self.LEAKAGE_MODELS:
443
+ msg = f"Invalid leakage_model: {leakage_model}. Must be one of {self.LEAKAGE_MODELS}"
444
+ raise ValueError(msg)
445
+
446
+ self.attack_type = attack_type
447
+ self.leakage_model = leakage_model
448
+ # Templates: key_byte -> (mean vector, covariance matrix)
449
+ self.templates: dict[int, tuple[NDArray[np.float64], NDArray[np.float64]]] = {}
450
+
451
+ def perform_attack(
452
+ self,
453
+ traces: list[PowerTrace],
454
+ target_byte: int = 0,
455
+ algorithm: str = "aes128",
456
+ ) -> DPAResult:
457
+ """Perform power analysis attack to recover key byte.
458
+
459
+ Dispatches to appropriate attack method based on attack_type.
460
+
461
+ Args:
462
+ traces: List of power traces with plaintexts.
463
+ target_byte: Target key byte position (0-15 for AES-128).
464
+ algorithm: Cryptographic algorithm ("aes128" or "des").
465
+
466
+ Returns:
467
+ DPAResult with recovered key and confidence metrics.
468
+
469
+ Raises:
470
+ ValueError: If traces is empty, target_byte invalid, or algorithm unsupported.
471
+
472
+ Example:
473
+ >>> result = analyzer.perform_attack(traces, target_byte=0)
474
+ >>> print(f"Key byte {0}: 0x{result.recovered_key[0]:02X}")
475
+ """
476
+ if not traces:
477
+ raise ValueError("No traces provided")
478
+
479
+ if target_byte < 0 or target_byte >= 16:
480
+ raise ValueError(f"Invalid target_byte: {target_byte}. Must be 0-15 for AES-128")
481
+
482
+ if algorithm != "aes128":
483
+ raise ValueError(f"Unsupported algorithm: {algorithm}. Only 'aes128' implemented")
484
+
485
+ if self.attack_type == "dpa":
486
+ return self.dpa_attack(traces, target_byte)
487
+ elif self.attack_type == "cpa":
488
+ return self.cpa_attack(traces, target_byte)
489
+ else:
490
+ msg = "Template attack requires separate profiling/attack traces"
491
+ raise ValueError(msg)
492
+
493
+ def dpa_attack(
494
+ self,
495
+ traces: list[PowerTrace],
496
+ target_byte: int,
497
+ ) -> DPAResult:
498
+ """Classic DPA attack using difference of means.
499
+
500
+ Separates traces into two groups based on hypothetical intermediate bit value,
501
+ then computes difference of mean power consumption at each time point.
502
+
503
+ Algorithm:
504
+ 1. For each key guess k in [0, 255]:
505
+ a. Compute intermediate value for each trace: v = SBOX[plaintext ^ k]
506
+ b. Partition traces by bit b of v (e.g., LSB)
507
+ c. Compute differential trace: mean(power | b=1) - mean(power | b=0)
508
+ d. Score = max absolute value in differential trace
509
+ 2. Return key with highest score
510
+
511
+ Args:
512
+ traces: List of power traces with plaintexts.
513
+ target_byte: Target key byte position.
514
+
515
+ Returns:
516
+ DPAResult with recovered key byte.
517
+
518
+ Example:
519
+ >>> analyzer = DPAAnalyzer(attack_type="dpa")
520
+ >>> result = analyzer.dpa_attack(traces, target_byte=0)
521
+ """
522
+ if not traces:
523
+ raise ValueError("No traces provided")
524
+
525
+ # Extract plaintexts and power matrix
526
+ plaintexts = [t.plaintext for t in traces if t.plaintext]
527
+ if not plaintexts:
528
+ raise ValueError("No plaintexts in traces")
529
+
530
+ power_matrix = np.array([t.power for t in traces]) # (num_traces, num_samples)
531
+
532
+ # Attack each key guess
533
+ max_diffs = np.zeros(256)
534
+
535
+ for key_guess in range(256):
536
+ # Compute intermediate values and selection bit
537
+ intermediates = []
538
+ for plaintext in plaintexts:
539
+ if plaintext is None or target_byte >= len(plaintext):
540
+ intermediates.append(0)
541
+ continue
542
+ intermediate = self._aes_sbox_output(plaintext[target_byte], key_guess)
543
+ intermediates.append(intermediate)
544
+
545
+ # Use LSB as selection bit
546
+ selection_bits = np.array([v & 1 for v in intermediates])
547
+
548
+ # Partition traces
549
+ group0_traces = power_matrix[selection_bits == 0]
550
+ group1_traces = power_matrix[selection_bits == 1]
551
+
552
+ if len(group0_traces) == 0 or len(group1_traces) == 0:
553
+ continue
554
+
555
+ # Compute differential trace
556
+ mean0 = np.mean(group0_traces, axis=0)
557
+ mean1 = np.mean(group1_traces, axis=0)
558
+ diff = mean1 - mean0
559
+
560
+ # Score is maximum absolute difference
561
+ max_diffs[key_guess] = np.max(np.abs(diff))
562
+
563
+ # Find best key guess
564
+ recovered_key_byte = int(np.argmax(max_diffs))
565
+ max_score = max_diffs[recovered_key_byte]
566
+
567
+ # Calculate confidence (separation from second-best)
568
+ sorted_scores = np.sort(max_diffs)
569
+ if sorted_scores[-2] > 0:
570
+ confidence = 1.0 - (sorted_scores[-2] / max_score)
571
+ else:
572
+ confidence = 1.0
573
+
574
+ successful = confidence > 0.7
575
+
576
+ return DPAResult(
577
+ recovered_key=bytes([recovered_key_byte]),
578
+ key_ranks=max_diffs,
579
+ correlation_traces=None,
580
+ confidence=float(confidence),
581
+ successful=successful,
582
+ )
583
+
584
+ def cpa_attack(
585
+ self,
586
+ traces: list[PowerTrace],
587
+ target_byte: int,
588
+ ) -> DPAResult:
589
+ """Correlation Power Analysis (CPA) attack.
590
+
591
+ Computes Pearson correlation between hypothetical power consumption (based on
592
+ leakage model) and measured power at each time point. Key with highest
593
+ correlation is most likely correct.
594
+
595
+ Algorithm:
596
+ 1. For each key guess k in [0, 255]:
597
+ a. Compute hypothetical power: h[i] = LeakageModel(SBOX[plaintext[i] ^ k])
598
+ b. For each time point t:
599
+ - Compute correlation: corr(h, power[:, t])
600
+ c. Score = max |correlation| across all time points
601
+ 2. Return key with highest score
602
+
603
+ Args:
604
+ traces: List of power traces with plaintexts.
605
+ target_byte: Target key byte position.
606
+
607
+ Returns:
608
+ DPAResult with recovered key byte and correlation traces.
609
+
610
+ Example:
611
+ >>> analyzer = DPAAnalyzer(attack_type="cpa", leakage_model="hamming_weight")
612
+ >>> result = analyzer.cpa_attack(traces, target_byte=0)
613
+ >>> print(f"Max correlation: {result.confidence:.3f}")
614
+ """
615
+ if not traces:
616
+ raise ValueError("No traces provided")
617
+
618
+ # Extract plaintexts and power matrix
619
+ plaintexts = [t.plaintext for t in traces if t.plaintext]
620
+ if not plaintexts:
621
+ raise ValueError("No plaintexts in traces")
622
+
623
+ power_matrix = np.array([t.power for t in traces]) # (num_traces, num_samples)
624
+ num_samples = power_matrix.shape[1]
625
+
626
+ # Attack each key guess
627
+ correlation_traces = np.zeros((256, num_samples))
628
+
629
+ for key_guess in range(256):
630
+ # Calculate hypothetical power consumption
631
+ hypothetical = self._calculate_hypothetical_power(
632
+ plaintexts,
633
+ key_guess,
634
+ target_byte,
635
+ )
636
+
637
+ # Calculate correlation at each time point
638
+ for sample_idx in range(num_samples):
639
+ measured = power_matrix[:, sample_idx]
640
+ # Use numpy's correlation coefficient
641
+ corr_matrix = np.corrcoef(hypothetical, measured)
642
+ correlation = corr_matrix[0, 1] if corr_matrix.shape == (2, 2) else 0.0
643
+ # Handle NaN from constant signals
644
+ if np.isnan(correlation):
645
+ correlation = 0.0
646
+ correlation_traces[key_guess, sample_idx] = abs(correlation)
647
+
648
+ # Find key guess with maximum correlation
649
+ max_correlations = np.max(correlation_traces, axis=1)
650
+ recovered_key_byte = int(np.argmax(max_correlations))
651
+ max_correlation = max_correlations[recovered_key_byte]
652
+
653
+ # Calculate confidence (separation from second-best)
654
+ sorted_corrs = np.sort(max_correlations)
655
+ if sorted_corrs[-2] > 0:
656
+ confidence = 1.0 - (sorted_corrs[-2] / max_correlation)
657
+ else:
658
+ confidence = 1.0
659
+
660
+ # Check if attack successful (correlation > threshold)
661
+ successful = max_correlation > 0.7
662
+
663
+ return DPAResult(
664
+ recovered_key=bytes([recovered_key_byte]),
665
+ key_ranks=max_correlations,
666
+ correlation_traces=correlation_traces,
667
+ confidence=float(confidence),
668
+ successful=successful,
669
+ )
670
+
671
+ def template_attack(
672
+ self,
673
+ profiling_traces: list[PowerTrace],
674
+ attack_traces: list[PowerTrace],
675
+ target_byte: int,
676
+ ) -> DPAResult:
677
+ """Template attack with profiling and matching phases.
678
+
679
+ Phase 1 (Profiling): Build templates (mean, covariance) for each key byte
680
+ using traces from controlled device with known key.
681
+ Phase 2 (Matching): Match attack traces to templates using multivariate
682
+ Gaussian probability.
683
+
684
+ Args:
685
+ profiling_traces: Traces from device with known key for template building.
686
+ attack_traces: Traces from target device with unknown key.
687
+ target_byte: Target key byte position.
688
+
689
+ Returns:
690
+ DPAResult with recovered key byte.
691
+
692
+ Raises:
693
+ ValueError: If profiling or attack traces are empty.
694
+
695
+ Example:
696
+ >>> analyzer = DPAAnalyzer(attack_type="template")
697
+ >>> # Build templates with known key
698
+ >>> result = analyzer.template_attack(
699
+ ... profiling_traces=train_traces,
700
+ ... attack_traces=test_traces,
701
+ ... target_byte=0
702
+ ... )
703
+ """
704
+ if not profiling_traces or not attack_traces:
705
+ raise ValueError("Both profiling and attack traces required")
706
+
707
+ # Phase 1: Build templates from profiling traces
708
+ self._build_templates(profiling_traces, target_byte)
709
+
710
+ # Phase 2: Match attack traces to templates
711
+ power_matrix = np.array([t.power for t in attack_traces])
712
+ num_samples = power_matrix.shape[1]
713
+
714
+ # Calculate probability for each key guess
715
+ probabilities = np.zeros(256)
716
+
717
+ for key_guess in range(256):
718
+ if key_guess not in self.templates:
719
+ continue
720
+
721
+ mean, cov = self.templates[key_guess]
722
+
723
+ # Use only points of interest (reduce dimensionality)
724
+ # For simplicity, use first min(100, num_samples) points
725
+ poi_count = min(100, num_samples)
726
+ mean_poi = mean[:poi_count]
727
+ cov_poi = cov[:poi_count, :poi_count]
728
+
729
+ # Add small regularization to covariance
730
+ cov_poi = cov_poi + np.eye(poi_count) * 1e-6
731
+
732
+ # Calculate log probability for each trace
733
+ log_prob = 0.0
734
+ for trace in power_matrix:
735
+ trace_poi = trace[:poi_count]
736
+ try:
737
+ # Multivariate Gaussian log probability
738
+ diff = trace_poi - mean_poi
739
+ inv_cov = np.linalg.inv(cov_poi)
740
+ log_prob += -0.5 * (diff @ inv_cov @ diff)
741
+ except np.linalg.LinAlgError:
742
+ # Singular covariance matrix
743
+ log_prob += -1e10
744
+
745
+ probabilities[key_guess] = log_prob
746
+
747
+ # Find best key guess
748
+ recovered_key_byte = int(np.argmax(probabilities))
749
+ max_prob = probabilities[recovered_key_byte]
750
+
751
+ # Calculate confidence
752
+ sorted_probs = np.sort(probabilities)
753
+ if sorted_probs[-2] != -np.inf and max_prob != -np.inf and max_prob != 0:
754
+ # Use absolute difference for log probabilities (negative values)
755
+ confidence = abs(max_prob - sorted_probs[-2]) / (abs(max_prob) + 1e-10)
756
+ confidence = min(confidence, 1.0)
757
+ else:
758
+ confidence = 0.0
759
+
760
+ successful = confidence > 0.7
761
+
762
+ return DPAResult(
763
+ recovered_key=bytes([recovered_key_byte]),
764
+ key_ranks=probabilities,
765
+ correlation_traces=None,
766
+ confidence=float(confidence),
767
+ successful=successful,
768
+ )
769
+
770
+ def _build_templates(
771
+ self,
772
+ traces: list[PowerTrace],
773
+ target_byte: int,
774
+ ) -> None:
775
+ """Build templates from profiling traces with known key.
776
+
777
+ Args:
778
+ traces: Profiling traces with known plaintexts and ciphertexts.
779
+ target_byte: Target key byte position.
780
+ """
781
+ # Group traces by intermediate value (assuming key byte = 0 for profiling)
782
+ # In real scenario, you'd know the profiling key
783
+ profiling_key_byte = 0 # Known key for profiling device
784
+
785
+ groups: dict[int, list[NDArray[np.float64]]] = {}
786
+
787
+ for trace in traces:
788
+ if trace.plaintext is None or target_byte >= len(trace.plaintext):
789
+ continue
790
+
791
+ intermediate = self._aes_sbox_output(
792
+ trace.plaintext[target_byte],
793
+ profiling_key_byte,
794
+ )
795
+
796
+ if intermediate not in groups:
797
+ groups[intermediate] = []
798
+ groups[intermediate].append(trace.power)
799
+
800
+ # Build template for each intermediate value
801
+ for intermediate, power_traces in groups.items():
802
+ if len(power_traces) < 2:
803
+ continue
804
+
805
+ power_array = np.array(power_traces)
806
+ mean = np.mean(power_array, axis=0)
807
+ cov = np.cov(power_array.T)
808
+
809
+ self.templates[intermediate] = (mean, cov)
810
+
811
+ def _hamming_weight(self, value: int) -> int:
812
+ """Calculate Hamming weight (population count).
813
+
814
+ Args:
815
+ value: Integer value (0-255).
816
+
817
+ Returns:
818
+ Number of 1 bits in binary representation (0-8).
819
+
820
+ Example:
821
+ >>> analyzer._hamming_weight(0x0F) # 0b00001111
822
+ 4
823
+ >>> analyzer._hamming_weight(0xFF) # 0b11111111
824
+ 8
825
+ """
826
+ count = 0
827
+ while value:
828
+ count += value & 1
829
+ value >>= 1
830
+ return count
831
+
832
+ def _hamming_distance(self, value1: int, value2: int) -> int:
833
+ """Calculate Hamming distance between two values.
834
+
835
+ Args:
836
+ value1: First integer value (0-255).
837
+ value2: Second integer value (0-255).
838
+
839
+ Returns:
840
+ Number of differing bits (0-8).
841
+
842
+ Example:
843
+ >>> analyzer._hamming_distance(0x00, 0xFF)
844
+ 8
845
+ >>> analyzer._hamming_distance(0x0F, 0x0E) # differ in 1 bit
846
+ 1
847
+ """
848
+ return self._hamming_weight(value1 ^ value2)
849
+
850
+ def _aes_sbox_output(self, plaintext_byte: int, key_guess: int) -> int:
851
+ """Calculate AES S-box output for given plaintext and key guess.
852
+
853
+ Args:
854
+ plaintext_byte: Single plaintext byte (0-255).
855
+ key_guess: Key byte guess (0-255).
856
+
857
+ Returns:
858
+ S-box output (0-255).
859
+
860
+ Example:
861
+ >>> analyzer._aes_sbox_output(0x00, 0x00)
862
+ 99 # SBOX[0x00] = 0x63
863
+ """
864
+ xored = plaintext_byte ^ key_guess
865
+ return AES_SBOX[xored]
866
+
867
+ def _calculate_hypothetical_power(
868
+ self,
869
+ plaintexts: Sequence[bytes | None],
870
+ key_guess: int,
871
+ target_byte: int,
872
+ ) -> NDArray[np.float64]:
873
+ """Calculate hypothetical power consumption for key guess.
874
+
875
+ Uses configured leakage model to convert intermediate values to
876
+ hypothetical power consumption.
877
+
878
+ Args:
879
+ plaintexts: List of plaintext bytes.
880
+ key_guess: Key byte guess (0-255).
881
+ target_byte: Target byte position.
882
+
883
+ Returns:
884
+ Array of hypothetical power values.
885
+
886
+ Example:
887
+ >>> hyp_power = analyzer._calculate_hypothetical_power(
888
+ ... plaintexts=[b'\\x00\\x01\\x02...'],
889
+ ... key_guess=0x42,
890
+ ... target_byte=0
891
+ ... )
892
+ """
893
+ hypothetical = []
894
+
895
+ for plaintext in plaintexts:
896
+ if plaintext is None or target_byte >= len(plaintext):
897
+ hypothetical.append(0.0)
898
+ continue
899
+
900
+ # Calculate intermediate value (AES S-box output)
901
+ intermediate = self._aes_sbox_output(plaintext[target_byte], key_guess)
902
+
903
+ # Apply leakage model
904
+ if self.leakage_model == "hamming_weight":
905
+ power = float(self._hamming_weight(intermediate))
906
+ elif self.leakage_model == "hamming_distance":
907
+ # Hamming distance from plaintext to intermediate
908
+ power = float(self._hamming_distance(plaintext[target_byte], intermediate))
909
+ else: # identity
910
+ power = float(intermediate)
911
+
912
+ hypothetical.append(power)
913
+
914
+ return np.array(hypothetical)
915
+
916
+ def visualize_attack(
917
+ self,
918
+ result: DPAResult,
919
+ output_path: Path,
920
+ ) -> None:
921
+ """Visualize CPA attack results with correlation traces and key rankings.
922
+
923
+ Creates two-panel plot:
924
+ - Top: Correlation traces for all key guesses (highlighted for recovered key)
925
+ - Bottom: Bar chart of maximum correlation per key guess
926
+
927
+ Args:
928
+ result: DPAResult from CPA attack.
929
+ output_path: Path to save plot image (PNG format).
930
+
931
+ Raises:
932
+ ValueError: If correlation_traces is None (not a CPA attack).
933
+ ImportError: If matplotlib is not installed.
934
+
935
+ Example:
936
+ >>> result = analyzer.cpa_attack(traces, target_byte=0)
937
+ >>> analyzer.visualize_attack(result, Path("attack_plot.png"))
938
+ """
939
+ if result.correlation_traces is None:
940
+ raise ValueError("Visualization requires correlation_traces (use CPA attack)")
941
+
942
+ try:
943
+ import matplotlib.pyplot as plt
944
+ except ImportError as e:
945
+ msg = "matplotlib required for visualization"
946
+ raise ImportError(msg) from e
947
+
948
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
949
+
950
+ # Plot 1: Correlation traces for all key guesses
951
+ for key_guess in range(256):
952
+ ax1.plot(
953
+ result.correlation_traces[key_guess],
954
+ alpha=0.1,
955
+ color="blue",
956
+ )
957
+
958
+ # Highlight correct key
959
+ recovered = result.recovered_key[0]
960
+ ax1.plot(
961
+ result.correlation_traces[recovered],
962
+ color="red",
963
+ linewidth=2,
964
+ label=f"Recovered key: 0x{recovered:02X}",
965
+ )
966
+
967
+ ax1.set_xlabel("Sample point")
968
+ ax1.set_ylabel("|Correlation|")
969
+ ax1.set_title("Correlation traces for all key guesses")
970
+ ax1.legend()
971
+ ax1.grid(True, alpha=0.3)
972
+
973
+ # Plot 2: Key ranking (max correlation per key)
974
+ ax2.bar(range(256), result.key_ranks, color="blue", alpha=0.6)
975
+ ax2.axvline(
976
+ result.recovered_key[0],
977
+ color="red",
978
+ linestyle="--",
979
+ linewidth=2,
980
+ label="Recovered key",
981
+ )
982
+ ax2.set_xlabel("Key guess")
983
+ ax2.set_ylabel("Max |Correlation|")
984
+ ax2.set_title("Key ranking")
985
+ ax2.legend()
986
+ ax2.grid(True, alpha=0.3)
987
+
988
+ plt.tight_layout()
989
+ plt.savefig(output_path, dpi=300, bbox_inches="tight")
990
+ plt.close()
991
+
992
+ logger.info(f"Attack visualization saved to {output_path}")
993
+
994
+ def export_results(
995
+ self,
996
+ result: DPAResult,
997
+ output_path: Path,
998
+ ) -> None:
999
+ """Export attack results to JSON file.
1000
+
1001
+ Args:
1002
+ result: DPAResult from attack.
1003
+ output_path: Path to save JSON file.
1004
+
1005
+ Example:
1006
+ >>> result = analyzer.perform_attack(traces, target_byte=0)
1007
+ >>> analyzer.export_results(result, Path("attack_results.json"))
1008
+ """
1009
+ data = {
1010
+ "recovered_key": result.recovered_key.hex(),
1011
+ "confidence": result.confidence,
1012
+ "successful": result.successful,
1013
+ "key_ranks": result.key_ranks.tolist(),
1014
+ "attack_type": self.attack_type,
1015
+ "leakage_model": self.leakage_model,
1016
+ }
1017
+
1018
+ if result.correlation_traces is not None:
1019
+ # Only export max correlations to reduce file size
1020
+ data["max_correlations"] = np.max(result.correlation_traces, axis=1).tolist()
1021
+
1022
+ with open(output_path, "w") as f:
1023
+ json.dump(data, f, indent=2)
1024
+
1025
+ logger.info(f"Attack results exported to {output_path}")