Skip to content

Add guardians protocol on-chain (only need a new ProxyType)

I thought about a way to define guardians on-chain, and actually it’s already possible with the existing proxy and multisig pallets; we just need to add one new ProxyType.

TL;DR: It only requires adding one ProxyType:

ProxyType::Guardians => {
    matches!(
        c,
        RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) if {
            matches!(calls.as_slice(), [
                RuntimeCall::UniversalDividend(pallet_universal_dividend::Call::claim_uds { .. }),
                RuntimeCall::Identity(pallet_identity::Call::change_owner_key { .. }),
                RuntimeCall::Balances(pallet_balances::Call::transfer_all { .. })
            ])
        }
    )
}

We can use the existing proxy and multisig pallets to create guardians that can restore an account if the user loses his private key.

Example scenario

  1. The user defines his guardians as a multisig account. For example, to allow 2/3 of guardians A, B, and C, the account ID is computed as:
    blake2_256(b"modlpy/utilisuba" | A | B | C | 3).
    See the multisig account ID computation here: Multisig account ID computation
  2. The user creates a proxy of type ProxyType::Guardians to allow his guardians to change the owner key of his member account and transfer funds (and claim UDs).
    Call: Proxy.add_proxy(guardiansMultisigAccountId, ProxyType::Guardians, DELAY_IN_BLOCKS)
  3. Some time later (potentially years later), the user loses his private key and doesn’t have his revocation document. In that case, he creates a new account and asks one guardian to call:
    Multisig.as_multi(threshold, otherGuardiansAccountIds, None, Proxy.annonce(oldUserAccountId, batchCallHash), MAX_WEIGHT)
    where batchCallHash is a blake2_256 hash of the SCALE-encoded batchCall defined as:
    Utility.batch_all([
        UniversalDividend.claim_uds(), 
        Identity.change_owner_key(newUserKey, newUserKeySig), 
        Balances.transfer_all(newUserKey, false)
    ])
  4. An indexer indexes the NewMultisig events and computes their related timepoint (block number and extrinsic position in the block).
  5. The remaining (threshold - 1) guardians each call:
    Multisig.as_multi(threshold, otherGuardiansAccountIds, Some(timepoint), Proxy.annonce(oldUserAccountId, batchCallHash), MAX_WEIGHT)
    where timepoint is a struct Timepoint { height: BlockNumber, index: u32 } that points to the on-chain extrinsic submitted in step 3 (Multisig.as_multi).
  6. After DELAY_IN_BLOCKS blocks, anyone (including a bot, since it’s permissionless) can call:
    Proxy.proxy_announced(guardiansMultisigAccountId, oldUserAccountId, batchCall)
    then the runtime will execute batchCall with origin oldUserAccountId.
  7. The user regains his identity, certifications, and funds under his new public key.

Important Notes

  • MAX_WEIGHT must be high enough for the execution of the whole batchCall. The best approach is probably to compute a value based on weight calculations and expose it as a constant in the metadata (for example, as an identity constant with a name like GUARDIANS_BATCH_CALL_MAX_WEIGHT).
  • The user can also create a proxy of type CancelProxy to allow a third party to reject the guardians’ announcement before the DELAY_IN_BLOCKS period expires.
  • DELAY_IN_BLOCKS should be long enough to allow the user (or a third party) to detect a malicious announcement from a subset of guardians.
  • This protocol is useful only if the user loses his private key; it is not useful if the user’s private key is stolen, since only the revocation mechanism would help in that case.
  • Guardians cannot revoke the user’s identity even if we allow calling identity.revoke_identity, because a multisig account is just a hash and therefore cannot create a valid signature. If we want to allow guardians to revoke the identity, we must create a new call Identity.revoke_identity_instant that does not require a signature (see [issue to be created]).
Edited by Éloïs
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information