Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIP-0018: Multi Stake Keys Wallets #83

Merged
merged 4 commits into from
Aug 3, 2021

Conversation

KtorZ
Copy link
Member

@KtorZ KtorZ commented Apr 2, 2021

Multi-Stake-Keys Wallets

Abstract

This document describes how to evolve sequential wallets from Cardano to support multiple stake keys. This proposal is an extension of CIP-1852 and CIP-0011.

Motivation / Use-cases

Cardano wallets originally approached stake delegation by considering a single stake key per wallet. While this was beneficial in terms of ease of implementation and simplicity of reasoning, this is unsuitable for many users with large stakes. Indeed, the inability to split out the stake into multiple pools often leads to over-saturation of existing pools in case of delegation. The only workaround so far requires users to split their funds into multiple accounts and manage them independently. This can be quite cumbersome for a sufficiently large number of accounts.

Even for smaller actors, it can be interesting to delegate to multiple pools to limit risks. Pools may underperform or simply change their parameters from a day to another (for which wallets still do not warn users about). Delegating to more pools can reduce the impact of pool failure (for one or more epochs) or unattended changes in pool fees. It may as well be a matter of choice if users do want to delegate to several independent entities for various other reasons.

Another concern regards privacy leaks coming with the existing wallet scheme. Since the same stake key is associated with every single address of the wallet, it creates a kind of watermark that allows for tracing all funds belonging to the same wallet very easily (it suffices to look at the stake part of addresses). By allowing the wallet to hold multiple stake keys, rotating through them when creating changes does make the traceability a bit harder. One could imagine using a hundred stake keys delegated to the same pool.

Specification

Overview

The restriction from CIP-0011 regarding the derivation index reserved for stake key is rendered obsolete by this specification. That is, one is allowed to derive indexes beyond the first one (index = 0) to effectively administrate multi-stake keys accounts. The creation of new stake keys is however tightly coupled to the registration of associated stake keys to allow wallet software to automatically discover stake keys on-chain. In this design, stake key indexes form at all time a contiguous sequence with no gap.

Key registration

We introduce the concept of UTxO stake key pointer to reliably keep track of stake keys on the blockchain. The gist is to require that every key registration and/or deregistration consume and create a special UTxO which in itself is pointing to the next available stake key of the wallet. Such pointer allows piggybacking on the existing UTxO structure to cope with concurrency issues and rollbacks that are inherent to a distributed system such as Cardano. Plus, this mechanism only demands a low overhead for wallet software and may be recognized as a special spending pattern by hardware devices.

In more details, we require that beyond the first stake key (index = 0), all registrations must satisfy the following rules:

  1. Stake keys must be derived sequentially, from 0 and onwards.
  2. Every stake key registration must be accompanied by a matching delegation certificate.
  3. Every registration transaction must create a special output of exactly minUTxOValue[1] Ada such that its address is an enterprise address with a single payment part using the stake key hash of the next available stake key of the wallet to be registered after processing the registration transaction.
  4. Unless no key beyond the first one is registered, every registration transaction must consume the special UTxO stake key pointer corresponding to the previous key registration (resp. de-registration) for that wallet.
Example

For example, a wallet that has already registered stake keys 0, 1 and 2 have a UTxO for which the payment part is a hash of the stake key at index=3. If the wallet wants to register two new stake keys at index 3 and 4, it'll do so in a single transaction, by consuming the pointer UTxO and by creating a new one for which the payment part would be a hash of the stake key at index=5.

Note that this only requires two signatures from stake keys at indexes 3 and 4.

Key de-registration

Key de-registrations work symmetrically and require that:

  1. Stake keys are de-registered sequentially, from the highest and downwards.
  2. Unless the first key of the wallet is being de-registered, every de-registration transaction must create a special output of exactly minUtxOValue Ada such that its address is an enterprise address with a single payment part using the stake key hash of the next stake key of the wallet after processing the de-registration transaction.
  3. Every de-registration must consume the special UTxO stake key pointer corresponding to the previous key de-registration (resp. registration) for that wallet.

Rationale

  • Carrying an extra UTxO pointer makes it possible to not worry (too much) about concurrency issues and problems coming with either, multiple instances of the wallet (like many users do between a mobile and desktop wallet) or the usual rollbacks which may otherwise create gaps in the indexes. By forcing all registration (resp. deregistration) transactions to be chained together, we also enforce that any rollbacks do maintain consistency of the index state: if any intermediate transaction is rolled back, then transactions they depend on are also rolled back.

  • The first registration induces an extra cost for the end-user for the wallet needs to create a new UTxO with a minimum value. That UTxO is however passed from registration to registration afterwards without any extra cost. It can also be fully refunded upon de-registering the last stake key. So in practice, it works very much like a key deposit.

  • We do not allow mixing up key registration and key deregistration as part of the same transaction for it makes the calculation of the pointer trickier for wallet processing transactions. A single transaction either move the pointer up or down.

  • There's in principle nothing preventing someone from sending money to the special key-registration tracking address. Wallets should however only keep track of UTxOs created as part of transactions that register stake keys (and have therefore been authorized by the wallet itself). Applications are however encouraged to collect any funds sent to them in an ad-hoc manner on such keys.

Backwards Compatibility

As stated in the introduction, this proposal is built on top of CIP-0011 such that backward compatibility is preserved when a single key is used. In fact, The management of the first stake key at index 0 remains unchanged and does not require any pointer. This preserves backward compatibility with the existing design for a single stake key wallet and offers a design that can be implemented on top, retro-actively.

Nevertheless, we do assume that existing wallets following CIP-0011 are already fully capable of discovering addresses using stake keys not belonging to the wallet. Some may even report them as mangled[2]. As a result, this extension would not incapacitate existing wallets since the payment ownership is left untouched. However, wallets not supporting the extension may display addresses delegated to keys beyond the first one as mangled and may also fail to report rewards correctly across multiple keys.

We deem this to be an acceptable and fairly minor consequence but encourage existing software to raise awareness about this behaviour.

Reference Implementation

Coming soon.

Glossary

[1] Minimum Utxo Value

The minUTxOValue is fixed by the protocol. It is defined as part of the Shelley genesis and can be updated via on-chain protocol updates. At the moment, this equals 1 Ada on the mainnet.

[2] Mangled Addresses

An address is said mangled when it has a stake part, and the stake part isn't recognized as belonging to its associated wallet. That is, the payment part and the stake part appear to come from two different sources. This could be the case if the address has been purposely constructed in such a way (because the stake rights and funds are managed by separate entities), or because the stake part refers to a key hash which is no longer known of the wallet (because the associated key registration was rolled back).

Copyright

CC-BY-4.0

@KtorZ KtorZ self-assigned this Apr 2, 2021
@psychomb
Copy link

psychomb commented Apr 3, 2021

Delegating to more pools can reduce the impact of one pool going mad. Plus, it may be also a matter of will if users do want to support multiple projects at once.

I'd suggest a more neutral phrasing such as:

Delegating to more pools can reduce the impact of pool failure (for one or more epochs) or unattended changes in pool fees. It may as well be a matter of choice if users do want to delegate to several independent entities for various other reasons.

- **2.A)** It must exist an active<sup><a href="#1-active-stake-key">1</a></sup> stake key registration certificate on chain of the immediately previous stake key, located in the stable area<sup><a href="#2-stable-area">2</a></sup> of the chain.
- **2.B)** It must exist another stake key registration certificate of the immediately previous stake key in the same transaction which satisfies 2.A or 2.B.

Said differently, one can only register a new stake key if previous keys have already been registered and are immutable. We also allow registering keys in batch to alleviate the user experience for users wanting to register many stake keys.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it mean one will be able to make multi-delegation only at least every 36h?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and no. It means that you'll be to register new stake keys after at least 36h from the last registration. Yet, you can register many keys at once. So, if you have a portfolio of let's say 10 pools, you can multi-delegate in one go. But then, once done if you want to add a 11th pool, you'll have to wait 36h. However, you can change the delegation settings of any of these pools anytime you want. What's really constrained is the registration (and conversely, de-registration) of stake keys.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, thanks. Good, that's how I understood it.


Similarly, keys can only be de-registered in sequence, from the latest and downwards. Transaction de-registering stake keys must satisfy one of the following conditions to be valid:

- **3.A)** The index of the de-registered key must be equal to the index of the last active<sup><a href="#1-active-stake-key">1</a></sup> stake key on chain.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also note this can cause backwards compatibility issues since Yoroi supports de-registering your stake key (so if it doesn't support this spec, it will de-register only the 0-index key, leaving the other active keys untouched breaking this rule)

This comment was marked as off-topic.

@KtorZ KtorZ force-pushed the KtorZ/CIP-0018-multi-stake-keys-wallets branch from aa987ef to 44da520 Compare May 25, 2021 15:40
@KtorZ
Copy link
Member Author

KtorZ commented May 25, 2021

@SebastienGllmt we've made quite a large iteration on this design after running into various implementation and conceptual challenges, I've updated the proposal to the latest version which deviates quite a lot from the original idea. If you want to have look 👍


1. Stake keys must be derived sequentially, from 0 and onwards.
1. Every stake key registration must be accompanied by a matching delegation certificate.
1. Every registration transaction must create a special output of exactly `minUTxOValue`<a href="#glossary"><sup>[1]</sup></a> Ada such that its address is an enterprise address with a single **payment part** using the stake key hash of the next available stake key of the wallet to be registered after processing the registration transaction.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is an enterprise address

There should be no reason for requiring the pointer address to be an enterprise one, so it would be simpler just not to mention it? Arguably, one might as well have a stake key on the pointer utxo.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. This is needlessly restrictive 🤔

@MarcelKlammer
Copy link

Since the pointer would be a misused UTxO, would't any wallet not implementing this CIP be consuming that UTxO at some point?

@MarcelKlammer
Copy link

If a stake address was part of a base address that received funds (registered or not) wallets have to sync this stake key anyway (check regs/deregs/delegation/rewards/withdrawals). So why bother with implementing a pointer for registrations? Filling gaps makes sense, sure. But a wallet knows about the status of a stake key index and thus can re-register a stake key, if it previously was registered and had since been deregistered.

I try to understand what's the motivation from a wallet standpoint as I want to start implementing multi delegation in our wallet.

Portfolios:

If a portfolio changes and eg. removes a pool that was delegated to using let's say stake key index 3 of 5, with this CIP index 5 would need to be deregistered, index 3 re-delegated.

But wallets need to sync that stake key index anyway, a wallet knows the status of that index 3 as deregistered and that index can be reused (re-registering + delegating) if need be.

@KtorZ
Copy link
Member Author

KtorZ commented Jun 27, 2021

Since the pointer would be a misused UTxO would't any wallet not implementing this CIP be consuming that UTxO at some point?

There's actually little risk (unless the wallet it doing something abnormal, or not following CIP-1852) because the UTxO lives on a completely different derivation chain than others.

The pointer UTxO is specifically chosen at a path that does not conflict with existing derivation paths.

So why bother with implementing a pointer for registrations? Filling gaps makes sense, sure. But a wallet knows about the status of a stake key index

Actually, no. The wallet does not know the status of registrations. Or more exactly, it only knows it after a certain time (36h on mainnet and testnet). And this is because of two reasons:

  1. Wallets are inherently decentralized applications, and the internal "state" of a wallet only reflects its own local view of the chain. Many people do actually use multiple wallets (e.g. Daedalus on desktop, Yoroi on mobile). The same addresses, and UTxOs can be modified from multiple ends and your own local actions aren't necessarily reflecting the whole truth.

  2. Blocks are only truly immutable after a certain period. In ourorobos praos and with the current protocol parameters this is exactly 36h. Before that, there's a non zero chance that your own version of the chain gets replaced by a fork. Or said differently, a transaction sent by the wallet may gets invalidated even after it was inserted in "the ledger" (your local view of it).

Thus, you can't really expect that you'll be able to recover the state of your wallet from the chain unless you construct it in such a way that makes it possible.

Let's say that you do not use a pointer like in this proposal but simply rely on observing the next certificate on chain. Then comes Alice, who first registers a key K1. A bit later, she registers K2, but out of luck, the transaction regarding K1 is rolled back and never ends up on chain.

Now if she tries restoring her wallet on another machine, she may be surprised when her wallet fails to discover K2 because the wallet is only watching the chain for K1.

Like for sequential wallets, you could use a gap and not be too short sighted, but it only moves the problem by some offset. We've got some users who wants to register hundreds of stake keys, and while having a gap of 100 is possible, it would impact performances quite a bit and, will only be okay until someone comes with 200 keys to register.

Our first proposal to this was to simply forbid the creation of new stake keys before the immutability of the previous ones, which was unsatisfactory as it makes the UX worse.. This, another solution (this proposal) leverages the UTxO mechanism by artificially chaining all registrations and deregistrations together. By forcing a specific UTxO to be spent for every (de) registration, we avoid any concurrency issue regarding rollback or multi wallets. If a transaction is invalidated, then, any transaction coming after is also invalidated (since they depend on UTxOs which are only created by previous transactions!).

In brief, the pointer ain't much about tracking the state of the wallet, as you said, this can be figured out easily from certificates on chain. Rather, the pointer is a protection to ensure the wallet can not end up in a state where it is not fully recoverable from the chain. And it does so without limitations of numbers of keys or, time delays.

@MarcelKlammer
Copy link

Thank you for the explaination. What derivation path is planned for this proposal?

@KtorZ
Copy link
Member Author

KtorZ commented Jun 27, 2021

What derivation path is planned for this proposal?

It is said in the proposal, I reckon I could make that clearer then. We use the same path as the next stake key to be registered, but, use it to construct a payment address (so it's a bit like associating a UTxO to the reward account corresponding to the key).

@MarcelKlammer
Copy link

Ok, got it (I think).

Discover UTxOs on enterprise addr created by deriving m/1852'/1815'/x'/2/1 to n.

If a UTxO is present at m/1852'/1815'/x'/2/y, thus pointer available:

  • register/delegate z stake keys by consuming the UTxO at /y and creating a new pointer UTxO at /y+z.

If no UTxO was discovered:

  • check registrations of at least 20 (50?) stake keys to be reasonable sure, that no multi-delegation was performed before.
  • register/delegate z stake keys and creating a new pointer UTxO at /0+z.

Questions:

Single stake key delegation:

Do not create a pointer UTxO on /1, if the account did not perform multi-delegation before?

Remove pointer UTxO only if also /0 was deregistered?

@Anviking
Copy link
Member

The following diagram might be helpful, and maybe we should add something like it to the proposal: https://github.com/input-output-hk/cardano-wallet/blob/690e5a2bee1bd4cfbd760c65e8a7f20b71cbfad0/lib/core/src/Cardano/Wallet/Primitive/Delegation/State.hs#L109-L136

Do not create a pointer UTxO on /1, if the account did not perform multi-delegation before?

No, for consistency with existing single-delegation the pointer is never created for /1 when delegating with key /0.

Remove pointer UTxO only if also /0 was deregistered?

It ends up being only when /1 is deregisteried (see diagram).

@crptmppt
Copy link
Contributor

6/8 Editors meeting (24) briefly discussed this PR - see notes. (conversation is ahead of this - providing for reference)

=> Adding this PR as a "Review" for tomorrow's CIP Editors Meeting (25)

@crptmppt
Copy link
Contributor

crptmppt commented Jul 7, 2021

6/29 Editors meeting (25) discussed this PR - see notes. (conversation might be ahead of meeting notes at this point, this is a reference)

=> PR is now in 'Last Check': it will get another look at next week's public meeting (26) and should be merged in after that - consider attending the meeting if relevant to you.

Copy link
Contributor

@dcoutts dcoutts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, for draft CIP status.

@crptmppt crptmppt merged commit 1a0c973 into master Aug 3, 2021
@KtorZ KtorZ deleted the KtorZ/CIP-0018-multi-stake-keys-wallets branch August 10, 2021 12:05
rphair added a commit that referenced this pull request Mar 6, 2024
* promote PR #766 to CIP candidate 116

* added CBOR Registry CPS + CIPs to merge list

1. Stake keys must be derived sequentially, from 0 and onwards.
1. Every stake key registration must be accompanied by a matching delegation certificate.
1. Every registration transaction must create a special output of exactly `minUTxOValue`<a href="#glossary"><sup>[1]</sup></a> Ada such that its address is an enterprise address with a single **payment part** using the stake key hash of the next available stake key of the wallet to be registered after processing the registration transaction.

This comment was marked as off-topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants