Concept
It is essential to allow validators to change (rotate) their public KIRA controller addresses as well as validator signing keys in the case where there exists a suspicion that the underlying private key or password which is securing the encrypted key-file was compromised. Since keys are often kept online alongside Tendermint signing keys the risk of being stuck with a compromised address is high, especially when scripting tools such as KM are used. Currently, if the keys are compromised validator has no other option but to create a new address and request governance to allow him to be a node again, this would result in the validator losing access to his staking pool as well as any other incentives accumulated, not to mention delegations and corresponding revenue.
In this proposal, we present a method for the secure migration of KIRA addresses that can be used by both validators, delegators, and users to secure their accounts. The method will utilize a “one-time recovery code” that can be stored offline and used in case of emergencies.
With KIP-76 we introduced a collaborative custody module that made it possible to limit transaction amounts, whitelist withdrawal addresses, password-protect transfers, and collaborative token transfers allowing for the implementation of 2FA. In this KIP we will utilize a very similar scheme where the double hash of a secret combined with nonce can be used to identify the real owner of the account in the case where the address needs to be rotated.
Governance
Parameters
Implementation
Recovery Module
The main task of the recovery registrar will be to persist the double hash of a recovery secret and if the hash of a secret is revealed - store information in regards to which account is the user's new KIRA recovery address. The recovery should only be possible if the user registered double-hashed “secret” S_H2 such that S_H2 == SHA256(SHA256(secret)) with the Recovery Registrar first and in the case of an emergency presented one-time hashed proof of knowing the secret P such that S_H2 == SHA256(P) by simply taking his original “secret” and hashing it once so P == SHA256(secret).
It is really important that the secret is sufficiently random and as strong as the mnemonic itself otherwise account and the funds can be compromised. To ensure that in the case where poor quality secrets are used or two users use the same exact recovery secret the value S_H2 stored on-chain is not the same for multiple accounts, we should make the Recovery Registrar store a nonce that can be mixed with the potentially unsafe secret, decreasing the chances of anyone being able to guess it by comparing their S_H2 with other people's S_H2. Our nonce should be simply a deterministic value nonce == SHA256(latest_block_hash + kira_public_key) and thus to secure user unsafe secret we can calculate a secure “secret” as secret == SHA256(unsafe_secret + nonce).
When the user sends register-recovery-secret-tx the Recovery Registrar must ensure that the same challenge and nonce values are NOT stored already for any other addresses in the Recovery Registrar. It is important that sekai fails the tx if the nonce is not correctly calculated by checking the last 10 block hashes. To make things simpler for the user CLI MUST automatically calculate nonce and hash the secret for the user. Finally, an optional field “proof” can be a part of a register-recovery-secret-tx making it possible to modify old recovery secrets with a new one. It MUST NOT be possible to modify challenge value S_H2 unless the user presents proof P.
Register Recovery Secret Tx
JSON
Copy
{
"address": "kiraXXX...XXX", // address registering recovery challenge
"challenge": "<sha256>", // recovery challenge S_H2, if the same S_H2 already exists then fail tx
"nonce": "<sha256>", // nonce must be a valid nonce
"proof": "<sha256>" // [OPTIONAL] proof of knowing the recovery secret to modify the old challenge
}
When the user sends recovery-rotate-address-tx full control over the old address benefits should be given to a new one. Once the new address is registered it should NOT be possible to modify it by sending consecutive rotate tx and it should NOT be possible to modify the recovery secret by using the old address. It is really important that the recovery address is unique and never used as recovery for any other account in the registrar, furthermore, the recovery-rotate-address-tx must only be possible to be sent by the recovery address itself. To make things simpler for the user CLI MUST automatically query nonce and combine it with user-supplied “string” secret before propagating tx.
Recovery Rotate Address Tx
JSON
Copy
{
"address": "kiraXXX...XXX", // old address that saved recovery challenge
"recovery": "kiraYYY...YYY", // new address that will take over control, must be different then old-address
"proof": "<sha256>" // proof of knowing the recovery secret, must hash to S_H2 otherwise fail tx. The proof is not needed if the account possesses a sufficient number of RR tokens, see Account Actions chaper.
}
Immediately after recovery-rotate-address-tx is sent the Recovery Module should ensure that all existing modules such as Identity Registrar, Staking, Governance, Collective, Spending Pools, and so on re-map values address -> recovery. If it happens that the address is a validator address then the node status should immediately change to jailed and the moniker re-mapped to the new recovery account. The new recovery address will have to send a claim validator seat tx again to be able to start producing blocks and newly re-mapped governance roles and permissions should enable that.
An alternative implementation would be to create a remap-tx for each module that will communicate with Recovery Registrar and allow a new recovery address to remap the old address into a new one.
Account Auctions
It is not really possible to prevent anyone from selling their validator nodes OTC after accumulating delegations, trust, and maintaining good statistics of the performance counters. This phenomenon takes place in the case of large data centers and validators who often sell their businesses. Since the sales executed OTC are not transparent and threaten the security of the network, we propose a model in which users can transparently sell their account to others by representing them as 10'000'000 shares of an rr_<denom> token that can be issued by the account owner through Recovery Registrar and where the <denom> is the lowercase mnemonic as defined in the Identity Registrar. The number of decimals for the RR tokens should be 6, the same as for KEX and the mnemonic should NOT be possible to be modified in IR afterward.
The account that possesses > 50% of the remaining “RR” tokens, MUST have the ability to rotate the account in the Recovery Registrar to his own without the need for proof of the recovery challenge. Furthermore, if RR coins are issued, recovery without the majority of RR tokens should NOT be possible. To make things simpler, the moment RR tokens are issued, the recovery challenge should be removed and not possible to be created unless all tokens are returned to redeem the underlying KEX. This mechanism enables the creation of a market for accounts that generate revenue through staking and other profit-bearing modules in the future.
To prevent spam, initially, only validator accounts should have the ability to issue RR tokens and the Network Properties MUST define a validator_recovery_bond property that will specify the amount of KEX that must be bonded to the Recovery Registrar by the validator in order for him to issue RR tokens (the default should be set to 300'000'000'000 ukex - 300k KEX).
Any user who holds the RR token should be able to redeem the remaining KEX bonded in the Recovery Registrar at the exact amount of total_locked_ukex * (amount_RR_returned/total_RR_supply). The RR tokens should be burned upon KEX redemption. Additionally, we must create a mechanism that will create incentives for people to acquire RR tokens. The simplest method is to modify the Staking Pool module to deposit all block and fee rewards when claimed to the Recovery Registrar, that way the amount of KEX backing RR token will grow over time and the only way to redeem the KEX will be to burn RR tokens. Only KEX should ever be deposited to the Recovery Registrar and no other tokens, even if fee rewards are paid in other assets; all of those other tokens should be redeemable by the validator and not by RR to make implementation simpler.
Recovery Registrar Example Structure
JSON
Copy
{
{ // example of recovery challenge record
"address": "kiraXXX...XXX", // address registering recovery secret
"challenge": "<sha256>", // S_H2
"nonce": "<sha256>" // nonce that the user must combine with his original secret to calculate proof
}, { // example of recovered account
"address": "kiraYYY...YYY", // old address registering recovery secret
"challenge": "<sha256>", // S_H2
"proof": "<sha256>" // P
"recovery": "kiraZZZ...ZZZ", // new address that can take full control and interact with other modules as if it was a
}, { // example of validator account controlled by RR tokens
"address": "kiraXXX...XXX", // address registering recovery secret
"bonded": <integer>, // ukex amount that can be redeemed by burning RR token
"supply": <integer>, // remaining amount of RR tokens
"denom": "rr_<moniker>" // token name controlling recovery
},
{ ... }, ...
}
Functions
recovery-register-secret - allow ANY user to register or modify existing recovery secret & verify if the nonce is correct
recovery-rotate-address - allow ANY KIRA address that knows the recovery secret or has a sufficient number of RR tokens to rotate the address
query-recovery-record - given KIRA public address as parameter return data from the recovery registrar
issue-recovery-tokens - mint rr_<moniker> tokens and deposit them to the validator account. This function will require putting up a bond in the amount of validator_recovery_bond otherwise should fail
burn-recovery-tokens - burn tokens and redeem KEX
Test Cases
Submit invalid nonce and ensure that recovery-register-secret-tx fails
Ensure that 2 accounts can’t have the same S_H2 values or the same nonces
Verify that S_H2 can be modified if the user presents proof otherwise ensure that tx fails
Ensure it is not possible to register a recovery address that was already registered for another account in the past
Ensure it is not possible to register recovery challenge twice for the same account
Ensure that recovery-rotate-address-tx fails if tx is NOT being sent by the recovery address itself (It must NOT be possible to steal the account even if someone guesses the recovery secret)
Test that recovery-rotate-address-tx fails if the recovery address was already used in by other modules (it must be a clean account), can not be a validator, can not have IR records etc.
Ensure that the new validator node can be launched
Verify that after issuing RR tokens validator can no longer use recovery secret to rotate the account
Guarantee that with > 50% of RR tokens account control can be taken over by another
Verify that monikers of validators who create RR tokens are a valid token denom (alphanumeric & digits, max 16 characters, no special chars.)
Prohibit creations of tokens with rr_ prefix, those should only be created by the RR module.
NOTES
Alternative Implementation for rewards distro
Let's imagine a situation where person has 1% of supply of RR tokens. If spending pool is configured to distribute all 100% of rewards over the period of X days (let's put default at 1 month so 2629800 seconds and make it configurable in network props) and the pool is dynamic so it recalculates how to distribute 100% of all rewards over the period of each month then you need to guarantee that each person indeed holds this many tokens RR over that time period.
I think weights for distribution were already implemented in KIP-83 so theoretically you can just say that if you have 1% and someone else has 2% then your weight is 0.5x the other person weight. So simply correlate % of RR you have to the weight when someone registers for claim.
The main problem here is that person can claim, send coins to another address, register another address and claim again thus draining the pool completely because module would not know owner changed.
To prevent this you would need to do here the same thing we do with staking module. So on transfer action of RR you would have to auto claim all outstanding rewards and modify the weight based on what person will have remaining after transfer. And if person ends up with 0 or less then some minimum you deregister them completely, so that pool doesn't have millions of 0 weight empty records as possible move those coins between wallets.
You also need minimum to to be able to register yourself to claim rewards to prevent spamming of that module. Maybe something configurable in the Network Props. Maybe 0.1% of supply, thus capping number of people who can claim to 1000.
Weights can be simply proportional to number of coins you have. So if you have 1% of all coins your weight is 0.01, if you have 2.5% it is 0.025 and so on to make things simpler.
FIXES
It was reported that UX for key rotation using secrets is not friendly, additionally the commit & reveal scheme was not implemented in the first release, causing potential issue with attacker monitoring the mempool and frontrunning the tx. Following changes will be implemented:
only recovery kira address can rotate account (in the case where RR tokens are not used)
once recovery kira address is set, to update or delete it you need to use recovery address itself
you can only issue RR if recovery address is not defined/removed