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. See add() 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 of Results.

__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, and acceptable), 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 uses rich’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 called info which is an instance of DebugInfo.

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 for TestInfo.)

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, use ok() or fail().

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:
  • id (int) – The numerical ID of the test.

  • type (TestType) – The type of test vector, see TestType.

  • flags (list[str] | None) – An optional list of flags that categorize the test.

  • comment (str | None) – An optional comment describing what is being tested.

Returns:

A new instance of TestInfo with the result, err_msg, and data 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.

  • test_type (TestType) – The type of test, refer to TestType.

  • 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 and no_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