Secagg Manager

Interface with the component secure aggregation element database

Attributes

Classes

BaseSecaggManager

BaseSecaggManager(db_path)

Bases: ABC

Manage a component secagg element database

Parameters:

Name Type Description Default
db_path str

path to the component's secagg database

required

Raises:

Type Description
FedbiomedSecaggError

failed to access the database

Source code in fedbiomed/common/secagg_manager.py
def __init__(self, db_path: str):
    """Constructor of the class

    Args:
        db_path: path to the component's secagg database

    Raises:
        FedbiomedSecaggError: failed to access the database
    """
    try:
        self._db = TinyDB(db_path)
        self._db.table_class = DBTable
    except Exception as e:
        errmess = f'{ErrorNumbers.FB623.value}: failed to access the database with error: {e}'
        logger.error(errmess)
        raise FedbiomedSecaggError(errmess)

    self._query = Query()
    self._table = _SecaggTableSingleton(self._db).table

Functions

add abstractmethod
add(secagg_id, parties, context, experiment_id)

Add a new data entry in component secagg element database

Source code in fedbiomed/common/secagg_manager.py
@abstractmethod
def add(self, secagg_id: str, parties: List[str], context: Dict[str, int], experiment_id: Union[str, None]):
    """Add a new data entry in component secagg element database"""
get abstractmethod
get(secagg_id, experiment_id)

Search for a data entry in component secagg element database

Source code in fedbiomed/common/secagg_manager.py
@abstractmethod
def get(self, secagg_id: str, experiment_id: Union[str, None]):
    """Search for a data entry in component secagg element database"""
remove abstractmethod
remove(secagg_id, experiment_id)

Remove a data entry from component secagg element database

Source code in fedbiomed/common/secagg_manager.py
@abstractmethod
def remove(self, secagg_id: str, experiment_id: Union[str, None]) -> bool:
    """Remove a data entry from component secagg element database"""

SecaggBiprimeManager

SecaggBiprimeManager(db_path)

Bases: BaseSecaggManager

Manage the component biprime secagg element database table

Parameters:

Name Type Description Default
db_path str

path to the component's secagg database

required
Source code in fedbiomed/common/secagg_manager.py
def __init__(self, db_path: str):
    """Constructor of the class

    Args:
        db_path: path to the component's secagg database
    """
    super().__init__(db_path)

    self._v = Validator()

Functions

add
add(secagg_id, parties, context, experiment_id=None)

Add a new data entry for a context element in the secagg table

Check that no entry exists yet for this secagg_id in the table.

Parameters:

Name Type Description Default
secagg_id str

secure aggregation ID key of the entry

required
parties List[str]

list of parties participating in this secagg context element

required
context Dict[str, int]

the (full) biprime number shared with other parties

required
experiment_id None

unused argument

None
Source code in fedbiomed/common/secagg_manager.py
def add(
        self,
        secagg_id: str,
        parties: List[str],
        context: Dict[str, int],
        experiment_id: None = None
) -> None:
    """Add a new data entry for a context element in the secagg table

    Check that no entry exists yet for this `secagg_id` in the table.

    Args:
        secagg_id: secure aggregation ID key of the entry
        parties: list of parties participating in this secagg context element
        context: the (full) biprime number shared with other parties
        experiment_id: unused argument
    """
    # Trust argument type and value check from calling class (`SecaggSetup`, `Node`)
    self._add_generic(
        SecaggElementTypes.BIPRIME,
        secagg_id,
        parties,
        {
            'context': context,
            'type': BiprimeType.DYNAMIC.value
        }
    )
get
get(secagg_id, experiment_id=None)

Search for data entry with given secagg_id in the secagg table

Check that there is at most one entry with this unique secagg ID.

Parameters:

Name Type Description Default
secagg_id str

secure aggregation ID key to search

required
experiment_id None

unused argument.

None

Returns:

Type Description
Union[dict, None]

A dict containing all values for the secagg element for this secagg_id if it exists, or None if no element exists for this secagg_id

Source code in fedbiomed/common/secagg_manager.py
def get(self, secagg_id: str, experiment_id: None = None) -> Union[dict, None]:
    """Search for data entry with given `secagg_id` in the secagg table

    Check that there is at most one entry with this unique secagg ID.

    Args:
        secagg_id: secure aggregation ID key to search
        experiment_id: unused argument.

    Returns:
        A dict containing all values for the secagg element for this `secagg_id` if it exists,
            or None if no element exists for this `secagg_id`
    """
    # Trust argument type and value check from calling class (`SecaggSetup`, `Node`)
    element = self._get_generic(secagg_id)
    self._raise_error_incompatible_requested_entry(element,
                                                   SecaggElementTypes.BIPRIME,
                                                   secagg_id,
                                                   None,  # a biprime is not associated to a specific experiment
                                                   'getting')

    # type is internal to this class, need not transmit to caller
    if isinstance(element, dict) and 'type' in element:
        # `deepcopy` avoids  any risk of error related to database implementation
        element = copy.deepcopy(element)
        del element['type']

    return element
is_default_biprime
is_default_biprime(secagg_id)

Search for default (non dynamic) data entry with given secagg_id in the biprime table.

Parameters:

Name Type Description Default
secagg_id str

secure aggregation ID key to search

required

Returns:

Type Description
bool

True if a default biprime entry exists for this secagg_id, False if not

Source code in fedbiomed/common/secagg_manager.py
def is_default_biprime(self, secagg_id: str) -> bool:
    """Search for default (non dynamic) data entry with given `secagg_id` in the biprime table.

    Args:
        secagg_id: secure aggregation ID key to search

    Returns:
        True if a default biprime entry exists for this `secagg_id`, False if not
    """
    # Trust argument type and value check from calling class (`SecaggSetup`, `Node`)
    element = self._get_generic(secagg_id)
    return isinstance(element, dict) and 'type' in element and element['type'] == BiprimeType.DEFAULT.value
remove
remove(secagg_id, experiment_id=None)

Remove data entry for this secagg_id from the secagg table

Parameters:

Name Type Description Default
secagg_id str

secure aggregation ID key of the entry

required
experiment_id None

unused argument

None

Returns:

Type Description
bool

True if an entry existed (and was removed) for this secagg_id, False if no entry existed for this secagg_id

Raises:

Type Description
FedbiomedSecaggError

trying to remove a non-dynamic biprime

Source code in fedbiomed/common/secagg_manager.py
def remove(self, secagg_id: str, experiment_id: None = None) -> bool:
    """Remove data entry for this `secagg_id` from the secagg table

    Args:
        secagg_id: secure aggregation ID key of the entry
        experiment_id: unused argument

    Returns:
        True if an entry existed (and was removed) for this `secagg_id`,
            False if no entry existed for this `secagg_id`

    Raises:
        FedbiomedSecaggError: trying to remove a non-dynamic biprime

    """
    # Trust argument type and value check from calling class (`SecaggSetup`, `Node`)

    # Can only remove dynamic biprimes
    element = self._get_generic(secagg_id)
    if isinstance(element, dict) and ('type' not in element or element['type'] != BiprimeType.DYNAMIC.value):
        errmess = f'{ErrorNumbers.FB623.value}: not authorized to remove non-dynamic biprime "{secagg_id}"'
        logger.error(errmess)
        raise FedbiomedSecaggError(errmess)

    return self._remove_generic(secagg_id, SecaggElementTypes.BIPRIME)
update_default_biprimes
update_default_biprimes(allow_default_biprimes, default_biprimes_dir)

Update the default entries in the biprime table.

If allow_default_biprimes is True, then add or update the default biprimes from the *.json files in default_biprimes_dir directory.

In all cases, remove the other default biprimes existing in the biprime table.

Parameters:

Name Type Description Default
allow_default_biprimes bool

if True, then accept default biprimes from files

required
default_biprimes_dir str

directory containing the default biprimes files

required

Raises:

Type Description
FedbiomedSecaggError

cannot update default biprimes

Source code in fedbiomed/common/secagg_manager.py
def update_default_biprimes(self, allow_default_biprimes: bool, default_biprimes_dir: str) -> None:
    """Update the default entries in the biprime table.

    If `allow_default_biprimes` is True, then add or update the default biprimes from the *.json
        files in `default_biprimes_dir` directory.

    In all cases, remove the other default biprimes existing in the biprime table.

    Args:
        allow_default_biprimes: if True, then accept default biprimes from files
        default_biprimes_dir: directory containing the default biprimes files

    Raises:
        FedbiomedSecaggError: cannot update default biprimes
    """
    # Read and check the new proposed default biprime values from files
    if allow_default_biprimes:
        default_biprimes_new = self._read_default_biprimes(default_biprimes_dir)
    else:
        default_biprimes_new = []

    # Read the existing default biprimes in DB
    try:
        default_biprimes_current = self._table.search(
            self._query.type.exists() &
            (self._query.type == BiprimeType.DEFAULT.value)
        )
    except Exception as e:
        errmess = f'{ErrorNumbers.FB623.value}: database search operation failed for default biprimes: {e}'
        logger.error(errmess)
        raise FedbiomedSecaggError(errmess)

    # Remove existing default biprimes not in the new proposed values
    bp_new_ids = set(bp['secagg_id'] for bp in default_biprimes_new)
    bp_current_ids = set(bp['secagg_id'] for bp in default_biprimes_current)
    bp_remove_ids = list(bp_current_ids - bp_new_ids)

    try:
        self._table.remove(self._query.secagg_id.one_of(bp_remove_ids))
    except Exception as e:
        errmess = f'{ErrorNumbers.FB623.value}: database remove operation failed for ' \
                  f'obsolete default biprimes {bp_remove_ids}: {e}'
        logger.error(errmess)
        raise FedbiomedSecaggError(errmess)

    # Save or update the new default biprimes
    for bp in default_biprimes_new:
        try:
            self._table.upsert(
                {
                    'secagg_version': bp['secagg_version'],
                    'secagg_id': bp['secagg_id'],
                    'parties': None,
                    'secagg_elem': SecaggElementTypes.BIPRIME.value,
                    'type': BiprimeType.DEFAULT.value,
                    'context': {
                        'biprime': bp['biprime'],
                        'max_keysize': bp['max_keysize']
                    },
                },
                self._query.secagg_id == bp['secagg_id']
            )
        except Exception as e:
            errmess = f'{ErrorNumbers.FB623.value}: database upsert operation failed for ' \
                      f'default biprime {bp["secagg_id"]}: {e}'
            logger.error(errmess)
            raise FedbiomedSecaggError(errmess)

SecaggDhManager

SecaggDhManager(db_path)

Bases: BaseSecaggManager

Manage the secagg table elements for Diffie Hellman components

Parameters:

Name Type Description Default
db_path str

path to the component's secagg database

required

Raises:

Type Description
FedbiomedSecaggError

failed to access the database

Source code in fedbiomed/common/secagg_manager.py
def __init__(self, db_path: str):
    """Constructor of the class

    Args:
        db_path: path to the component's secagg database

    Raises:
        FedbiomedSecaggError: failed to access the database
    """
    try:
        self._db = TinyDB(db_path)
        self._db.table_class = DBTable
    except Exception as e:
        errmess = f'{ErrorNumbers.FB623.value}: failed to access the database with error: {e}'
        logger.error(errmess)
        raise FedbiomedSecaggError(errmess)

    self._query = Query()
    self._table = _SecaggTableSingleton(self._db).table

Functions

add
add(secagg_id, parties, context, experiment_id)

Add a new data entry for a context element in the secagg table

Check that no entry exists yet for this secagg_id in the table.

Parameters:

Name Type Description Default
secagg_id str

secure aggregation ID key of the entry

required
parties List[str]

list of parties participating in this secagg context element

required
experiment_id str

ID of the experiment to which this secagg context element is attached

required
context Dict[str, bytes]

server key part held by this party

required
Source code in fedbiomed/common/secagg_manager.py
def add(self, secagg_id: str, parties: List[str], context: Dict[str, bytes], experiment_id: str):
    """Add a new data entry for a context element in the secagg table

    Check that no entry exists yet for this `secagg_id` in the table.

    Args:
        secagg_id: secure aggregation ID key of the entry
        parties: list of parties participating in this secagg context element
        experiment_id: ID of the experiment to which this secagg context element is attached
        context: server key part held by this party
    """
    # Save key pairs as `str`` since it is the format support by JSON. Need to convert to `base64` first
    context_json = {node_id: str(base64.b64encode(key), 'utf-8') for node_id, key in context.items()}

    self._add_generic(
        SecaggElementTypes.DIFFIE_HELLMAN,
        secagg_id,
        parties,
        {'experiment_id': experiment_id, 'context': context_json}
    )
get
get(secagg_id, experiment_id)

Search for data entry with given secagg_id

Check that there is at most one entry with this unique secagg ID.

If there is an entry for this secagg_id, check it is associated with experiment experiment_id

Parameters:

Name Type Description Default
secagg_id str

secure aggregation ID key to search

required
experiment_id str

the experiment ID associated with the secagg entry

required

Returns:

Type Description
Union[dict, None]

A dict containing all values for the secagg element for this secagg_id if it exists, or None if no element exists for this secagg_id

Source code in fedbiomed/common/secagg_manager.py
def get(self, secagg_id: str, experiment_id: str) -> Union[dict, None]:
    """Search for data entry with given `secagg_id`

    Check that there is at most one entry with this unique secagg ID.

    If there is an entry for this `secagg_id`, check it is associated with experiment `experiment_id`

    Args:
        secagg_id: secure aggregation ID key to search
        experiment_id: the experiment ID associated with the secagg entry

    Returns:
        A dict containing all values for the secagg element for this `secagg_id` if it exists,
            or None if no element exists for this `secagg_id`
    """
    # Trust argument type and value check from calling class (`SecaggSetup`, `Node`)
    element = self._get_generic(secagg_id)
    self._raise_error_incompatible_requested_entry(element,
                                                   SecaggElementTypes.DIFFIE_HELLMAN,
                                                   secagg_id,
                                                   experiment_id,
                                                   'getting')

    if element:
        # Need to convert to keys as bytes
        context_bytes = {
            node_id: bytes(base64.b64decode(key)) \
                for node_id, key in element['context'].items()}
        element['context'] = context_bytes

    return element
remove
remove(secagg_id, experiment_id)

Remove data entry for this secagg_id from the secagg table

Check that the experiment ID for the table entry and the current experiment match

Parameters:

Name Type Description Default
secagg_id str

secure aggregation ID key of the entry

required
experiment_id str

experiment ID of the current experiment

required

Returns:

Type Description
bool

True if an entry existed (and was removed) for this secagg_id, False if no entry existed for this secagg_id

Raises:

Type Description
FedbiomedSecaggError

database entry does not belong to experiment_id

Source code in fedbiomed/common/secagg_manager.py
def remove(self, secagg_id: str, experiment_id: str) -> bool:
    """Remove data entry for this `secagg_id` from the secagg table

    Check that the experiment ID for the table entry and the current experiment match

    Args:
        secagg_id: secure aggregation ID key of the entry
        experiment_id: experiment ID of the current experiment

    Returns:
        True if an entry existed (and was removed) for this `secagg_id`,
            False if no entry existed for this `secagg_id`

    Raises:
        FedbiomedSecaggError: database entry does not belong to `experiment_id`
    """
    # Trust argument type and value check from calling class for `secagg_id` (`SecaggSetup`, but not `Node`)
    # Don't trust `Node` for `experiment_id` type (may give `None`) but this is not an issue
    element = self._get_generic(secagg_id)
    self._raise_error_incompatible_requested_entry(element,
                                                   SecaggElementTypes.DIFFIE_HELLMAN,
                                                   secagg_id,
                                                   experiment_id,
                                                   'removing')
    return self._remove_generic(secagg_id, SecaggElementTypes.DIFFIE_HELLMAN)

SecaggServkeyManager

SecaggServkeyManager(db_path)

Bases: BaseSecaggManager

Manage the component server key secagg element database table

Parameters:

Name Type Description Default
db_path str

path to the component's secagg database

required
Source code in fedbiomed/common/secagg_manager.py
def __init__(self, db_path: str):
    """Constructor of the class

    Args:
        db_path: path to the component's secagg database
    """
    super().__init__(db_path)

Functions

add
add(secagg_id, parties, context, experiment_id)

Add a new data entry for a context element in the secagg table

Check that no entry exists yet for this secagg_id in the table.

Parameters:

Name Type Description Default
secagg_id str

secure aggregation ID key of the entry

required
parties List[str]

list of parties participating in this secagg context element

required
context Dict[str, int]

server key part held by this party

required
experiment_id str

ID of the experiment to which this secagg context element is attached

required
Source code in fedbiomed/common/secagg_manager.py
def add(self, secagg_id: str, parties: List[str], context: Dict[str, int], experiment_id: str):
    """Add a new data entry for a context element in the secagg table

    Check that no entry exists yet for this `secagg_id` in the table.

    Args:
        secagg_id: secure aggregation ID key of the entry
        parties: list of parties participating in this secagg context element
        context: server key part held by this party
        experiment_id: ID of the experiment to which this secagg context element is attached
    """

    # Trust argument type and value check from calling class (`SecaggSetup`, but not `Node`)
    self._add_generic(
        SecaggElementTypes.SERVER_KEY,
        secagg_id,
        parties,
        {'experiment_id': experiment_id, 'context': context}
    )
get
get(secagg_id, experiment_id)

Search for data entry with given secagg_id

Check that there is at most one entry with this unique secagg ID.

If there is an entry for this secagg_id, check it is associated with experiment experiment_id

Parameters:

Name Type Description Default
secagg_id str

secure aggregation ID key to search

required
experiment_id str

the experiment ID associated with the secagg entry

required

Returns:

Type Description
Union[dict, None]

A dict containing all values for the secagg element for this secagg_id if it exists, or None if no element exists for this secagg_id

Source code in fedbiomed/common/secagg_manager.py
def get(self, secagg_id: str, experiment_id: str) -> Union[dict, None]:
    """Search for data entry with given `secagg_id`

    Check that there is at most one entry with this unique secagg ID.

    If there is an entry for this `secagg_id`, check it is associated with experiment `experiment_id`

    Args:
        secagg_id: secure aggregation ID key to search
        experiment_id: the experiment ID associated with the secagg entry

    Returns:
        A dict containing all values for the secagg element for this `secagg_id` if it exists,
            or None if no element exists for this `secagg_id`
    """

    # Trust argument type and value check from calling class (`SecaggSetup`, `Node`)
    element = self._get_generic(secagg_id)
    self._raise_error_incompatible_requested_entry(element,
                                                   SecaggElementTypes.SERVER_KEY,
                                                   secagg_id,
                                                   experiment_id,
                                                   'getting')

    return element
remove
remove(secagg_id, experiment_id)

Remove data entry for this secagg_id from the secagg table

Check that the experiment ID for the table entry and the current experiment match

Parameters:

Name Type Description Default
secagg_id str

secure aggregation ID key of the entry

required
experiment_id str

experiment ID of the current experiment

required

Returns:

Type Description
bool

True if an entry existed (and was removed) for this secagg_id, False if no entry existed for this secagg_id

Source code in fedbiomed/common/secagg_manager.py
def remove(self, secagg_id: str, experiment_id: str) -> bool:
    """Remove data entry for this `secagg_id` from the secagg table

    Check that the experiment ID for the table entry and the current experiment match

    Args:
        secagg_id: secure aggregation ID key of the entry
        experiment_id: experiment ID of the current experiment

    Returns:
        True if an entry existed (and was removed) for this `secagg_id`,
            False if no entry existed for this `secagg_id`
    """

    # Trust argument type and value check from calling class for `secagg_id` (`SecaggSetup`, but not `Node`)
    # Don't trust `Node` for `experiment_id` type (may give `None`) but this is not an issue
    element = self._get_generic(secagg_id)
    self._raise_error_incompatible_requested_entry(element,
                                                   SecaggElementTypes.SERVER_KEY,
                                                   secagg_id,
                                                   experiment_id,
                                                   'removing')
    return self._remove_generic(secagg_id, SecaggElementTypes.SERVER_KEY)

Functions