Common#
Module for common objects.
This module provides the Results
class to record test results in an uniform
manner. These can be grouped with the ResultsDict
class.
To have a common set of attributes each individual test should have, the
DebugInfo
and the new TestInfo
classes should be used. One of such
attributes is the type of test vector, which is defined by the TestType
enum.
For functions that have to persist application data, the get_appdata_dir()
function returns the path to use.
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#
- 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.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.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#
- 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.