Common#
Module for common objects.
This module provides classes to store the results of tests, common functions, and a custom Rich console to display content.
Test types#
- enum crypto_condor.primitives.common.TestType(value)#
The different types of test vectors.
Test vectors can be separated into three types:
VALID
tests represent the expected behaviour when dealing with correct inputs. For example, signing a message with ECDSA using a valid private key for the given elliptic curve.INVALID
tests use invalid inputs and the implementation is expected to to reject them or fail in some way. For example, when verifying signatures ECDSA, implementations are expected to reject a signature if it is equal to (0, 0).ACCEPTABLE
tests are inputs resulting from legacy implementations or weak parameters. Passing these tests is acceptable but failing them is expected, and thus not an actual failure.
- Member Type:
str
Valid values are as follows:
- VALID = <TestType.VALID: 'valid'>#
- INVALID = <TestType.INVALID: 'invalid'>#
- ACCEPTABLE = <TestType.ACCEPTABLE: 'acceptable'>#
Results#
Results for individual test vectors can be stored with the new TestInfo
class,
or the old DebugInfo
class. They define a common set of attributes that are
required for each individual test, such as the test type as described by
TestType
.
These individual results are grouped by primitive, function, and parameters in the
Results
class. To combine multiple variations of parameters or different
primitives, the ResultsDict
class should be used.
- class crypto_condor.primitives.common.ResultsDict(*args, **kwargs)#
A dictionary of Results.
This class extends the built-in dictionary to group
Results
as values. The keys are defined by the calling function. Currently key uniqueness is not enforced, the caller is responsible for not overwriting previous results. Seeadd()
for a suggestion.It provides the
check()
method to check for failed results over all of its values. It also defines a string representation with__str__()
, similar to that ofResults
.- __str__()#
Returns a summary of the results contained.
- add(res, arg_names=None)#
Adds Results with a deterministic key.
It generates the dictionary key from the attributes of the given Results as follows:
"module/function/value1+value2+..."
Where the values are those of the
arguments
field. If no values are available, the last section is replaced by “None”.- Parameters:
res (Results | None) – The results to add. If None, this method does nothing.
arg_names (list[str] | None) – An optional list of argument names to filter which ones should be used to create the key.
Notes
This method accepts None as the first argument to simplify its usage when a test function returns Results | None.
The values are stringified with
str()
.
- check()#
Returns True if all results return True.
- class crypto_condor.primitives.common.Results(module, function, description, arguments, valid, invalid, acceptable, notes, data, _flags, _tids)#
The results of a test.
Do not instantiate directly, create a new instance with
new()
.This class defines the essential information about a test to have a uniform interface across primitives. Usually corresponds to a test of a specific set of parameters with a single test vectors file.
The individual test results are recorded by test type (
valid
,invalid
, andacceptable
), and any debug data is stored. To display the results to the user, its__str__()
method is defined to provide a user-friendly representation that usesrich
’s markup to add colours.- Parameters:
module (str) – The name of the primitive module.
function (str) – The name of the function used.
description (str) – A description of the function.
arguments (dict[str, Any]) – The name of the arguments passed to the function and their values.
valid (PassedAndFailed) – A count of valid tests with their results and flags.
invalid (PassedAndFailed) – A count of invalid tests with their results and flags.
acceptable (PassedAndFailed) – A count of acceptable tests with their results and flags.
notes (dict[str, str]) – Notes explaining the meaning of the flags. Can contain notes of flags that are not used by the tests, they will be omitted from the string representation. Initialized with common flags.
data (dict[int, Any]) – Information about each individual test, indexed by test ID.
_flags – A set of all flags observed. This is used to skip notes associated with unused flags for the string representation.
_tids – A set of test IDs, used to ensure the uniqueness of the ID.
- __str__()#
Returns a string representation of the results.
It uses
rich
markup to add colours, so it should be printed by e.g. a rich console.
- classmethod new(desc, arg_names)#
Creates a new instance of Results for the calling function.
Uses
sys
stack frames to determine the calling function’s module and name to avoid having to manually instantiate those attributes.- Parameters:
desc (str) – The short description of the function, like the first sentence of the docstring.
arg_names (list[str]) – A list of names of arguments of the calling function to include. The frame contains all arguments and its values, this allows to select which ones should be included in the results. Notably, the argument used to pass the implementation should be skipped, as the value is a function pointer, which is not relevant to the results.
Notes
There is no reliable way of getting the docstring from the stack frame, which is why the description has to be manually provided.
Example
To create a new instance of Results to record the results of testing an implementation of AES encryption, we want to record arguments such as the mode of operation and key length.
>>> from crypto_condor.primitives.common import Results >>> results = Results.new( ... "Tests an implementation of AES encryption.", ["mode", "key_length"] ... )
- add(data)#
Adds a new result from the result data.
- Parameters:
data (TestInfo | Any) – A test info class. Either an instance of
TestInfo
or a data class that has an attribute calledinfo
which is an instance ofDebugInfo
.- Raises:
ValueError – If the test ID is already used by a recorded result, or if the
result
attribute of data is None (which is the default forTestInfo
.)
- add_notes(notes)#
Adds flag notes from a dictionary of notes.
- check(*, empty_as_fail=False)#
Checks if the results have passed.
- Keyword Arguments:
empty_as_fail – Whether to consider a lack of passed tests as a failure.
- Returns:
False if there are failed tests (valid or invalid), or if empty_as_fail is True and there are no passed tests; True otherwise.
- Return type:
bool
Notes
The existence of failed tests is checked before empty_as_fail.
- class crypto_condor.primitives.common.TestInfo(id, type, flags, result, comment, err_msg, data)#
Information about a single test.
This data class defines the set of common attributes each test result should have, such as an ID and the type of test vector used.
Do not instantiate directly: to create a new instance use
new()
. After calling the implementation, useok()
orfail()
.- Parameters:
id (int) – A numerical ID for this test. Should be unique among tests of the same
Results
instance. Uniqueness is enforced by the latter.type (TestType) – The type of test vector used.
flags (list[str]) – Tags that categorise test vectors.
result (bool | None) – Whether the test passed or failed. None means that the value has not been explicitly set yet.
comment (str | None) – An optional description of what the test vector is testing.
err_msg (str | None) – A message explaining why the test failed, None if the test passed.
data (Any | None) – The optional debug data class instance.
- classmethod new(id, type, flags=None, comment=None)#
Creates a new instance of TestInfo.
- Parameters:
- Returns:
A new instance of TestInfo with the
result
,err_msg
, anddata
fields set to None.
Example
Let’s create a simple test vector and create a new instance of TestInfo based on its information.
>>> from crypto_condor.primitives.common import TestInfo, TestType >>> test = {"id": 1, "type": TestType.VALID, "flags": ["User-defined"]} >>> # To create an instance with only the essential information. >>> info = TestInfo.new(test["id"], test["type"]) >>> # If all the tests have flags, we can add them easily. >>> info = TestInfo.new(test["id"], test["type"], test["flags"]) >>> # We can also add a comment about the test. >>> info = TestInfo.new(test["id"], test["type"], comment="Edge case")
- ok(data=None)#
Marks a test as passed.
- Parameters:
data (Any | None) – Optional test debug data to add.
- fail(err_msg=None, data=None)#
Marks a test as failed.
- Parameters:
err_msg (str | None) – An optional message explaining why the test failed.
data (Any | None) – Optional test debug data to add.
- class crypto_condor.primitives.common.DebugInfo(tid, test_type, flags, result=False, comment=None, error_msg=None)#
Information about a single test.
Note: for new primitives, prefer to use
TestInfo
.Each test is expected to have some common information such as its ID or
TestType
. This class provides a common interface for this information.Debug data classes in primitive modules are expected to have an instance of this class as their (first) argument called
info
. They can then define data specific to that test as the other arguments.This class has a custom
__str__()
method to provide a string representation for user display. Classes using DebugInfo should use it to display all the information available.- Parameters:
tid (int) – A unique ID used to identify the test and its result. Uniqueness should be enforced by the parent
Results
.flags (list[str]) – A list of flags that categorise the test.
result (bool) – Whether the operation was successful or not.
comment (str | None) – An optional comment about the test, usually explaining what the input values are testing. For example in AES-GCM “IV length different than 96 bits”.
error_msg (str | None) – An error message in case of operation failure. Usually the message of the exception caught or a message indicating what part of the operation failed (e.g. “MAC tag is invalid” in AEAD modes).
Example
>>> from crypto_condor.primitives.common import DebugInfo >>> import attrs
>>> @attrs.define ... class MyDebugData: ... info: DebugInfo ... key: bytes ... message: bytes ... signature: bytes ... def __str__(self): ... s = str(self.info) ... s += f"key = {self.key.hex()}\n" ... s += f"message = {self.message.hex()}\n" ... s += f"signature = {self.signature.hex()}\n" ... return s
- __str__()#
Returns a string representation.
- class crypto_condor.primitives.common.PassedAndFailed(passed=0, failed=0, passed_flags=_Nothing.NOTHING, failed_flags=_Nothing.NOTHING, passed_index=_Nothing.NOTHING, failed_index=_Nothing.NOTHING)#
Information about how many tests passed and failed.
The usual usage is to instantiate the class without passing any arguments, just using the default values.
- Parameters:
passed (int) – Counter for the number of tests passed.
failed (int) – Counter for the number of tests failed.
passed_flags (dict[str, int]) – A dictionary to count flags for passed tests.
failed_flags (dict[str, int]) – A dictionary to count flags for failed tests.
passed_index (set[int]) – A set containing the ID of tests that passed.
failed_index (set[int]) – A set containing the ID of tests that failed.
Functions#
For functions that have to persist application data, the get_appdata_dir()
function returns the path to use.
- crypto_condor.primitives.common.get_appdata_dir()#
Returns an OS-dependent application data directory.
Creates the directory and its parents if it doesn’t exist.
This directory is used to store application data such as the compiled internal implementation of AES.
Console#
- class crypto_condor.primitives.common.Console(file=None)#
Modified Rich console.
Adds a couple of methods for displaying and saving results.
- Parameters:
file – An optional file object where the console will write its output. None defaults to stdout.
- print_results(res)#
Prints the results string representation.
Disables Rich’s highlighting to only show colours defined by our classes.
- Parameters:
res (Results | ResultsDict) – Either an instance of Results or ResultsDict.
- process_results(res, filename='', no_save=False, debug_data=None)#
Displays and saves results.
Displays the results obtained with two possible panels (the Rich boxes):
The first panel contains the per-test results. It is shown only when there are failed tests or if the verbosity is greater than WARNING.
The second panel contains the summary of the results, with a brief description of the types of tests (valid, etc.)
Then, depending on
filename
andno_save
, it can prompt the user on whether to save the results to a file. This version always includes the per-test results, and does not use panels.- Parameters:
res (ResultsDict | Results) – The results to display.
filename (str | None) – An optional file. If a string is passed, the results are saved to that file. If an empty string is passed, the user is prompted. If None, the results are not saved and the user is not prompted.
no_save (bool) – If True, no results are saved and the user is not prompted. Overrides
filename
.debug_data (bool | None) – Controls whether to save debug data when saving the results to a file. If True, debug data is appended. If False, it is skipped. If None, when saving the results the user is prompted.
- Returns:
True if all tests passed, False otherwise (i.e. the boolean returned by the results’ check() method).
- Return type:
bool