There is an existing proposal for handling Chimeric wallets (which I will call the change=2
proposal) which imposes a limitation of a single staking key per account'
. This proposal lifts this restriction.
The restriction to only use account_index=0
in the change=2
problem is fundamentally to solve the following: every staking key should uniquely define the set of payment keys. As long as we only use account_index=0
, this holds.
However, this is slightly breaks the design of bip32: child keys should only depend on their parents (its derivation).
To see why this is the case, suppose we want to generate the address for m / 1852' / 1815' / 0' / 0 / 0
. The wallet software must do the following:
- Derive the public key
pay_pub
fromm / 1852' / 1815' / 0' / 0 / 0
- Check if
m / 1852' / 1815' / 0' / 2 / 0
(call itstake_pub
) exists in the wallet software - If yes, return
<pay_pub, stake_pub>
, otherwise return justpay_pub
.
As you can see, constructing an address required two DIFFERENT bip32 paths. If what we want is that the staking key uniquely defines the payment keys, a more proper solution would be to have the staking key be on the left (in the bip32 path).
It's important that if you ever reveal the staking_key
derivation level (which happens when included in a bech32 address), then you SHOULD strip the chaincode first. Otherwise, any external actor would be able to see all child keys (as per the bip32 specification). In general, you should avoid revealing the chaincode for any level (unless it is to interface with wallet software).
The Jormungandr codebase already expects staking keys to be submitted without a chaincode so this is not additional work.
we use the following derivation path (depending on the value for chain
, what comes next changes)
m / purpose' / coin_type' / account' / chain
For chain=0
or chain=1
this is a legacy Icarus wallet
m / purpose' / coin_type' / account' / 0 or 1 / account_index
For chain=2
this is a Chimeric Account
m / purpose' / coin_type' / account' / 2 / account-index
For chain=3
this is a Shelley UTXO
m / purpose' / coin_type' / account' / 3 / staking_key / change / address_index
Now the staking key can uniquely define the payment keys as we want.
We can easily handle legacy wallets either by changing purpose'
to something other than 44'
OR say that any chain=0
or chain=1
addresses indicates a legacy wallet.
If you only care about the chimeric account, you don’t need to pointlessly create a staking key.
The staking key is derived with m / purpose' / coin_type' / account' / 3 / staking_key
The payment key is generated by m / purpose' / coin_type' / account' / 3 / staking_key / change / address_index
.
Wallet software can choose to generate an address based just on the payment key OR display a grouped key by showing m / purpose' / coin_type' / account' / 3 / staking_key / change / address_index || m / purpose' / coin_type' / account' / 3 / staking_key
. In either case, the address generated depends only on a single BIP32 path (showing the full path down to the address_index
uniquely defines what the payment key and staking key. Up to the wallet to decide if a Single or Group address should be shown in the wallet UI)
With this proposal:
- The staking key now uniquely defines the payment keys
- Solves the privacy issue of the
change=2
proposal - Allows you to generate an address knowing only its single bip32 path (
change=2
can't do this)
- Solves the privacy issue of the
- We allow users to stake to multiple stake pools from the same account (
change=2
proposal doesn't allow for this)- This has a strong implication because it means
change=2
can NOT know if a new staking key was created on a different machine for your wallet unless you have the private key present.change=3
doesn't need this.
- This has a strong implication because it means