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

Cardano registration metadata changes and signature specification #75

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 63 additions & 19 deletions CIP-0015/CIP-0015.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,57 +33,88 @@ A voting key is simply an ED25519 key. How this key is created is up to the wall

### Associating stake with a voting key

This method has been used since Fund 2.
For future fund iterations, a new method making use of time-lock scripts may
be introduced as described [below][future-development].

Recall: Cardano uses the UTXO model so to completely associate a wallet's balance with a voting key (i.e. including enterprise addresses), we would need to associate every payment key to a voting key individually. Although there are attempts at this (see [CIP8](../CIP-0008/CIP-0008.md)), the resulting data structure is a little excessive for on-chain metadata (which we want to keep small)

Given the above, we choose to only associate staking keys with voting keys. Since most Cardano wallets only use base addresses for Shelley wallet types, in most cases this should perfectly match the user's wallet.

### Registration metadata format

A Catalyst registration transaction are a regular Cardano transaction with a specific transaction metadata associated with it
A Catalyst registration transaction is a regular Cardano transaction with a specific transaction metadata associated with it.

Notably, there should be three entries inside the metadata map:
Notably, there should be four entries inside the metadata map:

Voting key registration
Voting registration example:
```
61284: {
// voting_key - CBOR byte array
1: "0xa6a3c0447aeb9cc54cf6422ba32b294e5e1c3ef6d782f2acff4a70694c4d1663",
// stake_pub - CBOR byte array
2: "0xad4b948699193634a39dd56f779a2951a24779ad52aa7916f6912b8ec4702cee",
// address - CBOR byte array
3: "0x00588e8e1d18cba576a4d35758069fe94e53f638b6faf7c07b8abd2bc5c5cdee47b60edc7772855324c85033c638364214cbfc6627889f81c4"
// reward_address - CBOR byte array
3: "0x00588e8e1d18cba576a4d35758069fe94e53f638b6faf7c07b8abd2bc5c5cdee47b60edc7772855324c85033c638364214cbfc6627889f81c4",
// slot_number
4: 5479467
}
```

Signing the voting public key with the staking private key
The entries under keys 1, 2, 3, and 4 represent the Catalyst voting key,
the staking key on the Cardano network, the address to receive rewards,
and the current slot number, respectively. A registration with these metadata
will be considered valid if the following conditions hold:

- The slot number of the block including this transaction is equal to
Copy link
Collaborator

Choose a reason for hiding this comment

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

How could you possibly guarantee this? Slots in Cardano are only 2 seconds and not all of them have a block in it.
On top of that, it would allow any pool to censor Catalyst registration transactions by purposely skipping them rendering them invalid for future slots.
Additionally, signing a transaction for hardware wallets requires user steps, so by the time the user is done with all the steps the tx may very well no longer be valid.

Copy link
Contributor

@refi93 refi93 Mar 28, 2021

Choose a reason for hiding this comment

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

agree, the user may need even several minutes to sign the transaction on a hardware wallet (which needs to know the slot number in advance, making it de-facto impossible to sign a valid voting key registration). Could you @mzabaluev explain the rationale behind this constraint?

I gather that the motivation behind this constraint is likely preventing the users from submitting a key registration with too big of a value of the counter which would effectively make their key un-updatable - for that purpose a constraint that the slot number should be no higher than the slot number of the block in which the registration is submitted should do.

Another motivation could be avoiding state storage - Not sure how the current "active voting keys" are stored but they probably need to be stored somehow and storing a counter alongside them (to be able to compare it against subsequent re-registrations) seems more viable than forcing such strict consistency with the block number where the key registration would be submitted

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree that the two slot time window is too short, so either a larger validity timeout is needed or a different mechanism to provide a unique nonce.

it would allow any pool to censor Catalyst registration transactions by purposely skipping them rendering them invalid for future slots.

What number of slots would be sufficient to prevent such censorship, assuming an honest majority among the pools?
This time window defines how far apart successive re-registrations need to be for the same staking key, to make it impossible to nullify the newer registration by replaying the older.

I gather that the motivation behind this constraint is likely preventing the users from submitting a key registration with too big of a value of the counter which would effectively make their key un-updatable

It's rather so that old registrations cannot be replayed, reverting a possible newer registration of a different voting key (or a revocation, whenever that use case is supported) with the same staking key. But it could also prevent the problem you describe, if we apply the "larger slot number wins" rule to resolving conflicting registrations rather than the order of transactions (which may be problematic if the transactions happen to be in the same block and there is no UTxO-imposed order between them).

Not sure how the current "active voting keys" are stored but they probably need to be stored somehow and storing a counter alongside them (to be able to compare it against subsequent re-registrations) seems more viable

It's certainly doable for the validation logic on the snapshotting tool, but I'm not sure about all wallets.
A wallet would be expected to recover the last successful registration transaction for the selected public staking key to obtain the value of the counter. If this is compatible with constraints posed by HW wallets, we can change the specification to use this stateful scheme.

Copy link
Contributor

@refi93 refi93 Mar 29, 2021

Choose a reason for hiding this comment

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

A wallet would be expected to recover the last successful registration transaction for the selected public staking key to obtain the value of the counter.

I'm not sure why would a wallet need to recover the counter from a previous registration - if the counter is chosen as the current slot number, that's a global increasing value. If we are afraid about the counter getting bigger than it should be, Catalyst can just ignore registrations with a value of the counter higher than the slot where they have been submitted. However, if a user somehow signs by mistake a registration with a future slot number, somebody may abuse that to overwrite user's registration later on, but that's a problem that neither the currently proposed design addresses.

Copy link
Contributor

Choose a reason for hiding this comment

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

slot number is only a suggestion of what to use for the nonce. It's nice because it always increases, but fundamentally, it can be any number that never goes backwards. e.g. 1, 2, 3, 4, 3 would result in 4 being the one used and ignore the last. Note that this nonce is not implemented for fund4 (it's included, but snapshot ignores it). We want to get the schema correct so we can properly scope out adding this for fund 5, but also want to stabilize so teams aren't having to continuously make changes.

the slot number value under key 4 or its increment by one.
- The reward address is a Shelley address discriminated for the same network
this transaction is submitted to.

To produce the signature field, the CBOR representation of a map containing
a single entry with key 61284 and the registration metadata map in the
format above is formed, designated here as `sign_data`.
This data is signed with the staking key using one of the two methods,
discriminated by the key number in the signature metadata map:

1: `sign_data` is signed as is using Ed25519.
Copy link
Contributor

Choose a reason for hiding this comment

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

this is no longer supported in fund4, it has to be hashed first.


2: The blake2b-256 hash of `sign_data` is signed using Ed25519.

Signature example:
```
61285: {
// signature - ED25119 signature CBOR byte array
1: "0x8b508822ac89bacb1f9c3a3ef0dc62fd72a0bd3849e2381b17272b68a8f52ea8240dcc855f2264db29a8512bfcd522ab69b982cb011e5f43d0154e72f505f007"
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't the key be updated to "2"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The example is still valid, applications should be free to use scheme 1 if they prefer.

Copy link
Contributor

Choose a reason for hiding this comment

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

no, it should use 1. 2 will be ignored by the snapshot tools. We will not support both signature options.

}
```

This corresponds to the following CDDL definition
### Metadata schema

The format above can be described by the following CDDL definition:

```
registration_cbor = {
61284: key_registration
, 61285: registration_signature
61284: key_registration,
61285: registration_signature
}

$voting_pub_key /= bytes .size 32
$staking_pub_key /= bytes .size 32
$ed25519_signature /= bytes .size 64
$address /= bytes
$reward_address /= bytes
$slot_number /= uint
Copy link
Contributor

Choose a reason for hiding this comment

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

this does not have to be a slot number, it can be any int, but slot makes it easy to not have to understand previous state to update a registration.

Copy link
Contributor

@refi93 refi93 Mar 29, 2021

Choose a reason for hiding this comment

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

I see two potential issues with the slot number being an int without specific constraints:

  1. if the int is supposed to be a uint64, then it would be bounded and a user could end up with a unupdatable key registration if they submit it with the nonce set to MAX_UINT64. If the int is unbounded, this can be complicated to implement on hw wallets with limited support for numerical types.

  2. There doesn't seem to be a reasonably efficient way to check for the last active registration by wallets - db sync indexes metadata in a generic way and it's up to the wallet backend/frontend to filter out the valid voting key registrations which isn't a scalable approach. If each wallet chooses their own way of setting the nonce in a stateless manner, users may face issues re-registering their keys if they migrate to another walllet that does it differently.

If not enforced by the snapshotting tool, I would at least put in the CIP that standard wallet implementations shall choose as the nonce the current slot number to ensure users of standard wallets don't face issues re-registering their voting keys should they migrate to another wallet


key_registration = {
1 : $voting_pub_key
, 2 : $staking_pub_key
, 3 : $address
1 : $voting_pub_key,
2 : $staking_pub_key,
3 : $reward_address,
4 : $slot_number,
}

registration_signature = {
1 : $ed25519_signature
1 : $ed25519_signature //
Copy link
Contributor

@refi93 refi93 Mar 19, 2021

Choose a reason for hiding this comment

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

here should probably be a comment that this field is not used anymore as the signature scheme changed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea is that either of the schemes can be used interchangeably.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then I think the key should be prefixed with ? to mark its optionality (https://github.com/input-output-hk/cardano-ledger-specs/blob/2966737bbaf4952a2cf8cd185f253ea5b82d863f/shelley/chain-and-ledger/shelley-spec-ledger-test/cddl-files/shelley.cddl#L56) and explain with a comment that either key 1 or 2 should be specified, as I guess CDDL doesn't have that level of expressivity natively

Copy link
Contributor

@refi93 refi93 Mar 20, 2021

Choose a reason for hiding this comment

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

also, do we want to allow both keys at the same time? I can see that leading to hard-to-predict behavior/implementation missalignment across different parts of the system for the case when one signature would be valid and the other not, so I'd advocate for enfocing a strict "either key 1 is present or key 2, but not both"

2 : $ed25519_signature
}
```

Expand All @@ -93,17 +124,30 @@ Here an example using CBOR diagnostic notation
{
61284: {
1: h'8253C95609BC62C0443276FE2A1872B87CB11C06185FFDBB56C7CE8352EEF2A3',
2: h'345080C6DDFF7154B4ED4A622558AA0EAABD8CE7E2701C92B6858EA76DCECBCE'
2: h'AD4B948699193634A39DD56F779A2951A24779AD52AA7916F6912B8EC4702CEE',
3: h'00588E8E1D18CBA576A4D35758069FE94E53F638B6FAF7C07B8ABD2BC5C5CDEE47B60EDC7772855324C85033C638364214CBFC6627889F81C4',
4: 5479467
},
61285: {
1: h'7D88F34D778B7A4C76AA53FF5D9506DC5B92D25575B43AF75D66DC05082A2BCFF44FCEDDAB15DBA0C23C56A09A15367A9803E24A388AAFB8498EF72190407B0D'
}
}
```

### Future development

[future-development]: #future-development

A future change of the Catalyst system may make use of a time-lock script to commit ADA on the mainnet for the duration of a voting period. The voter registration metadata in this method will not need an association
with the staking key. Therefore, the `staking_pub_key` map entry
and the `registration_signature` payload with key 61285 will no longer
be required.

## Changelog

Fund 3 added the `address` inside the `key_registration` field.
Fund 3 added the `reward_address` inside the `key_registration` field.

Fund 4 added the `slot_number` field to prevent replay attacks.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd call this nonce rather than slot number


It was planned that since Fund 4, `registration_signature` and the `staking_pub_key` entry inside the `key_registration` field will be deprecated.
This has been deferred to a future revision of the protocol.

## Copyright

Expand Down