πŸ›‘οΈTrustee

Key to Key Distribution Permissions

Design Ethos

The permissions and collateral providers enable safe key-level storage within a trust model. To collect their functionality, the Trustee contract acts as a Scribe to move funds between keys on the Ledger without moving the collateral itself at the provider.

Because it is a scribe, to enable the functionality a root key holder must trust the contract address at their Notary.

The Trustee contract has the following features:

  • Root Key Security. Only root key holders are authorized to create policies.

  • Permissioned Distribution. Configure which keys are allowed to move funds, and what keys they can move funds to.

  • Conditional Access. Use configured Events to only enable the distribution permission under certain circumstances.

This enables a few high level use cases:

  • Asset Recovery. Hold a second key that is otherwise inert. After a deadman's switch is triggered, the second key has access to all of the funds.

  • Air-locking Cold Storage. Build a warm key that can only move funds from the treasure key to a hot wallet. Separate access to funds from the ability to move it across private keys.

  • Corporate Distribution. The root key is a corporate treasury. Setting up tiered structures enables key holders to be decision makers without actually having access to the funds.

Additional Scribe and Event combinations can un-cover other use cases.

Storage

ILocksmith public locksmith;         // key validation
ITrustEventLog public trustEventLog; // event detection
ILedger public ledger;               // ledger manipulation

// A trust Policy enables a keyholder
// to distribute key trust funds.
struct Policy {
    bool enabled;               // is this policy enabled
    bytes32[] requiredEvents;   // the events needed to enable

    uint256 rootKeyId;          // the root key used to establish the policy
    uint256 sourceKeyId;        // where the funds can be moved from
    EnumerableSet.UintSet beneficiaries;    // where funds can be moved to
}

// maps trustee Keys, to the metadata.
// each key is limited to one policy in this implementation.
mapping(uint256 => Policy) private trustees;

// maps trusts to keys with policies on them
// this prevents UIs from having to spam the RPC
// with every key in the trust to get the set of policies.
 mapping(uint256 => EnumerableSet.UintSet) private trustPolicyKeys;

locksmith

The reference to the locksmith the contract uses to check key permissions.

trustEventLog

The reference to the trustEventLog the contract uses to. check for events.

ledger

The reference to the ledger that this scribe will work on.

struct Policy

enabled

Policies can be created before they are enabled. To create a policy that isn't enabled, add events to the required conditions.

requiredEvents

When a policy is created, it takes a list of bytes32 that signify the unique event hash in the log that is required for policy execution.

rootKeyId

This is the id of the root key which owns the policy. Root keys can only create policies for keys within their trust model.

sourceKeyId

This is the id of the key where the policy is allowed to move funds out of. The key must valid and within the trust's model.

beneficiaries

This is a list of key ids that the policy is authorized to distribute funds to. Each of these keys must be within the root key's trust model and cannot contain the sourceKeyId.

trustees

This is a global mapping of individual key IDs to their associated distribution policy. For simplicity and security, this implementation only allows one distribution policy per key. This encourages least privilege key design.

trustPolicyKeys

Another storage that is part of the trade-off of ensuring that UIs can be feasibly built on top of the smart contract. This contains a mapping of trust IDs, to all of the available policies on keys within the specified trust. This prevents front-ends from having to collect the keys from the trust, and then poll each key to see if there is a policy, then load each policy.

Operations

There are two introspection methods which will not be covered in detail, getPolicy(), and getTrustPolicyKeys(), which are essentially accessors to trustees and trustPolicyKeys.

setPolicy

This method is called by root key holders to configure a distribution policy. The caller must hold rootKeyId. All other specified keys need to exist within the trust model specified by the rootKeyId. Adding required events is optional, and will default to an enabled policy.

The method validates the trust model structure of the root key, trustee key, source key, and all of the beneficiaries.

removePolicy

A root key holder can call this method if they would like to remove completely a disitribution policy from a key.

distribute

This method is called by the trusteeKeyId holder, and if successful will move funds from a source key to a sub-set of the designated beneficiaries of their amount. In this implementation there is no restriction on assets, amounts, or frequency.

ensureEventActivation

This method is called when a trustee tries to distribute. It will check and register event firing. Because these events fire asynchronously, event activation is checked view-only in getPolicy(), and written to record upon distribute().

Last updated