Skip to content

Latest commit

 

History

History
162 lines (119 loc) · 7.23 KB

safe-liveness-checking.md

File metadata and controls

162 lines (119 loc) · 7.23 KB

Safe Liveness Checking

Table of Contents

Liveness checking Mechanism

The Security Security Council uses a specially extended Safe multisig contract to ensure that any loss of access to a signer's keys is identified and addressed within a predictable period of time.

This mechanism is intended only to be used to remove signers who have lost access to their keys, or are otherwise inactive. It is not intended to be used to remove signers who are acting in bad faith, or any other subjective criteria, such cases should be addressed by governance, and the removal handled via the standard Safe ownership management functionality.

Liveness checking methodology

This is achieved using two types of contracts which the Safe contract has built-in support for:

  1. Guard contracts: can execute pre- and post- transaction checks.
  2. Module contracts: a contract which is added to the Safe by the signers, and thenceforth is authorized to execute transactions via the Safe. This means the module must properly implement auth conditions internally.

The liveness guard

For implementing liveness checks a LivenessGuard is created which receives the signatures from each executed transaction, and tracks the latest time at which a transaction was signed by each signer. This time is made publicly available by calling a lastLive(address)(Timestamp) method.

Signers may also call the contract's showLiveness()() method directly in order to prove liveness.

The liveness module

A LivenessModule is also created which does the following:

  1. Has a function removeOwners() that anyone may call to specify one or more owners to be removed from the Safe.
  2. The Module would then check the LivenessGuard.lastLive() to determine if the signer is eligible for removal.
  3. If so, it will call the Safe's removeSigner() to remove the non-live signer, and if necessary reduce the threshold.
  4. When a member is removed, the signing parameters are modified such that M/N is the lowest ratio which remains greater than or equal to 75%. Using integer math, this can be expressed as M = (N * 75 + 99) / 100.

Owner removal call flow

The following diagram illustrates the flow for removing a single owner. The verifyFinalState box indicates calls to the Safe which ensure the final state is valid.

sequenceDiagram
    participant User
    participant LivenessModule
    participant LivenessGuard
    participant Safe
    User->>LivenessModule: removeOwners([previousOwner], [owner])
    LivenessModule->>LivenessGuard: lastLive(owner)
    LivenessModule->>Safe: getOwners()
    LivenessModule->>Safe: removeOwner(previousOwner, owner)

    alt verifyFinalState
    LivenessModule->>Safe: getOwners()
    LivenessModule->>Safe: getThreshold()
    LivenessModule->>Safe: getGuard()
    end
Loading

Shutdown

In the unlikely event that the signer set (N) is reduced below the allowed threshold, then (and only then) is a shutdown mechanism activated which removes the existing signers, and hands control of the multisig over to a predetermined entity.

Security Properties

The following security properties must be upheld:

  1. Signatures are assigned to the correct signer.
  2. Non-signers are unable to create a record of having signed.
  3. A signer cannot be censored or griefed such that their signing is not recorded.
  4. Signers may demonstrate liveness either by signing a transaction or by calling directly to the guard.
  5. The module only removes a signer if they have demonstrated liveness during the interval, or if necessary to convert the safe to a 1 of 1.
  6. The module sets the correct 75% threshold upon removing a signer.
  7. During a shutdown the module correctly removes all signers, and converts the safe to a 1 of 1.
  8. It must be impossible for the guard's checkTransaction or checkAfterExecution to permanently revert given any calldata and the current state.

Note: neither the module nor guard attempt to prevent a quorum of owners from removing either the liveness module or guard. There are legitimate reasons they might wish to do so. Moreover, if such a quorum of owners exists, there is no benefit to removing them, as they are defacto 'sufficiently live'.

Interdependency between the guard and module

The guard has no dependency on the module, and can be used independently to track liveness of Safe owners.

This means that the module can be removed or replaced without any affect on the guard.

The module however does have a dependency on the guard; if the guard is removed from the Safe, then the module will no longer be functional and calls to its removeOwners function will revert.

Deploying the liveness checking system

The module and guard are intended to be deployed and installed on the safe in the following sequence:

  1. Deploy the guard contract 2. The guard's constructor will read the Safe's owners and set a timestamp
  2. Deploy the module.
  3. Set the guard on the safe.
  4. Enable the module on the safe.

This order of operations is necessary to satisfy the constructor checks in the module, and is intended to prevent owners from being immediately removable.

Note that changes to the owners set should not be made between the time the module is deployed, and when it is enabled on the Safe, otherwise the checks made in the module's constructor may be invalidated. If such changes are made, a new module should be deployed.

Modify the liveness checking system

Changes to the liveness checking system should be done in the following manner:

Replacing the module

The module can safely be removed without affecting the operation of the guard. A new module can then be added.

Note: none of the module's parameters are modifiable. In order to update the security properties enforced by the module, it must be replaced.

Replacing the guard

The safe can only have one guard contract at a time, and if the guard is removed the module will cease to function. This does not affect the ability of the Safe to operate normally, however the module should be removed as a best practice.

If a new guard is added, eg. as a means of upgrading it, then a new module will also need to be deployed and enabled. Once both the guard and module have been removed, they can be replaced according to the steps in the Deployment section above.