/
eip-3076.md
261 lines (206 loc) · 14.2 KB
/
eip-3076.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
---
eip: 3076
title: Slashing Protection Interchange Format
description: A JSON interchange format for proof of stake validators to migrate slashing protection data between clients.
author: Michael Sproul (@michaelsproul), Sacha Saint-Leger (@sachayves), Danny Ryan (@djrtwo)
discussions-to: https://ethereum-magicians.org/t/eip-3076-validator-client-interchange-format-slashing-protection/4883
status: Last Call
last-call-deadline: 2021-11-03
type: Standards Track
category: Interface
created: 2020-10-27
---
## Abstract
A standard format for transferring a key's signing history allows validators to easily switch between clients without the risk of signing conflicting messages. While a common keystore format provides part of the solution, it does not contain any information about a key's signing history. For a validator moving their keys from client A to client B, this could lead to scenarios in which client B inadvertently signs a message that conflicts with an earlier message signed with client A. The interchange format described here provides a solution to this problem.
## Motivation
The proof of stake (PoS) protocol penalises validators for voting in ways that could result in two different versions of the chain being finalised. These types of penalties are called slashings.
For a validator following the protocol correctly, there is, in principle, no risk of being slashed. However, changing clients (from client A to client B, say) can result in a slashing risk if client B is unaware of the blocks and attestations that were signed with client A.
This can occur if client A and client B do not agree on what the present time is. For example, say client A's time is accidentally set to a day in the future (225 epochs), and a validator switches from client A to client B without giving B a record of the blocks and attestations signed with A. The validator in question now runs the risk of attesting to two different blocks in the same epoch (a slashable offence) for the next 225 epochs (since they've already voted on these epochs with client A, and now stand to vote on them again with client B). Such time-skew bugs have been observed in the wild.
Another situation in which slashing protection is critical is in the case of re-orgs. During a re-org it is possible for a validator to be assigned new attestation duties for an epoch in which it has already signed an attestation. In this case it is essential that the record of the previous attestation is available, even if the validator just moved from one client to another in the space of a single epoch.
## Specification
### JSON Schema
A valid interchange file is one that adheres to the following JSON schema, and is interpreted according to the [Conditions](#conditions).
```json
{
"title": "Signing history",
"description": "This schema provides a record of the blocks and attestations signed by a set of validators",
"type": "object",
"properties": {
"metadata": {
"type": "object",
"properties": {
"interchange_format_version": {
"type": "string",
"description": "The version of the interchange format that this document adheres to"
},
"genesis_validators_root": {
"type": "string",
"description": "Calculated at Genesis time; serves to uniquely identify the chain"
}
},
"required": [
"interchange_format_version",
"genesis_validators_root"
]
},
"data": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"pubkey": {
"type": "string",
"description": "The BLS public key of the validator (encoded as a 0x-prefixed hex string)"
},
"signed_blocks": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"slot": {
"type": "string",
"description": "The slot number of the block that was signed"
},
"signing_root": {
"type": "string",
"description": "The output of compute_signing_root(block, domain)"
}
},
"required": [
"slot"
]
}
]
},
"signed_attestations": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"source_epoch": {
"type": "string",
"description": "The attestation.data.source.epoch of the signed attestation"
},
"target_epoch": {
"type": "string",
"description": "The attestation.data.target.epoch of the signed attestation"
},
"signing_root": {
"type": "string",
"description": "The output of compute_signing_root(attestation, domain)"
}
},
"required": [
"source_epoch",
"target_epoch"
]
}
]
}
},
"required": [
"pubkey",
"signed_blocks",
"signed_attestations"
]
}
]
}
},
"required": [
"metadata",
"data"
]
}
```
### Example JSON Instance
```json
{
"metadata": {
"interchange_format_version": "5",
"genesis_validators_root": "0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673"
},
"data": [
{
"pubkey": "0xb845089a1457f811bfc000588fbb4e713669be8ce060ea6be3c6ece09afc3794106c91ca73acda5e5457122d58723bed",
"signed_blocks": [
{
"slot": "81952",
"signing_root": "0x4ff6f743a43f3b4f95350831aeaf0a122a1a392922c45d804280284a69eb850b"
},
{
"slot": "81951"
}
],
"signed_attestations": [
{
"source_epoch": "2290",
"target_epoch": "3007",
"signing_root": "0x587d6a4f59a58fe24f406e0502413e77fe1babddee641fda30034ed37ecc884d"
},
{
"source_epoch": "2290",
"target_epoch": "3008"
}
]
}
]
}
```
### Conditions
After importing an interchange file with data field `data`, a signer must respect the following conditions:
1. Refuse to sign any block that is slashable with respect to the blocks contained in `data.signed_blocks`. For details of what constitutes a slashable block, see `process_proposer_slashing` (from `consensus-specs`). If the `signing_root` is absent from a block, a signer must assume that any new block with the same `slot` is slashable with respect to the imported block.
2. Refuse to sign any block with `slot <= min(b.slot for b in data.signed_blocks if b.pubkey == proposer_pubkey)`, except if it is a repeat signing as determined by the `signing_root`.
3. Refuse to sign any attestation that is slashable with respect to the attestations contained in `data.signed_attestations`. For details of what constitutes a slashable attestation, see `is_slashable_attestation_data`.
4. Refuse to sign any attestation with source epoch less than the minimum source epoch present in that signer's attestations (as seen in `data.signed_attestations`). In pseudocode:
```python3
source.epoch <
min(att.source_epoch
for att in data.signed_attestations
if att.pubkey == attester_pubkey)
```
{:start="5"}
5. Refuse to sign any attestation with target epoch less than or equal to the minimum target epoch present in that signer's attestations (as seen in `data.signed_attestations`), except if it is a repeat signing as determined by the `signing_root`. In pseudocode:
```python3
target_epoch <=
min(att.target_epoch
for att in data.signed_attestations
if att.pubkey == attester_pubkey)
```
### Additional Information
- The `interchange_format_version` version is set to 5.
- A signed block or attestation's `signing_root` refers to the message data (hash tree root) that gets signed with a BLS signature. It allows validators to re-sign and re-broadcast blocks or attestations if asked.
- The `signed_blocks` `signing_root`s are calculated using `compute_signing_root(block, domain)`: where `block` is the block (of type `BeaconBlock` or `BeaconBlockHeader`) that was signed, and `domain` is equal to `compute_domain(DOMAIN_BEACON_PROPOSER, fork, metadata.genesis_validators_root)`.
- The `signed_attestations` `signing_root`s are calculated using `compute_signing_root(attestation, domain)`: where `attestation` is the attestation (of type `AttestationData`) that was signed, and `domain` is equal to `compute_domain(DOMAIN_BEACON_ATTESTER, fork, metadata.genesis_validators_root)`.
## Rationale
### Supporting Different Strategies
The interchange format is designed to be flexible enough to support the full variety of slashing protection strategies that clients may implement, which may be categorised into two main types:
1. **Complete**: a database containing every message signed by each validator.
2. **Minimal**: a database containing only the latest messages signed by each validator.
The advantage of the minimal strategy is its simplicity and succinctness. Using only the latest messages for each validator, safe slashing protection can be achieved by refusing to sign messages for slots or epochs prior.
On the other hand, the complete strategy can provide safe slashing protection while also avoiding false positives (meaning that it only prevents a validator from signing if doing so would guarantee a slashing).
The two strategies are unified in the interchange format through the inclusion of [conditions](#conditions) (2), (4) and (5). This allows the interchange to transfer detailed or succinct information, as desired.
### Integer Representation
Most fields in the JSON schema are strings. For fields in which it is possible to encode the value as either a string or an integer, strings were chosen. This choice was made in order to avoid issues with different languages supporting different ranges of integers (specifically JavaScript, where the `number` type is a 64-bit float). If a validator is yet to sign a block or attestation, the relevant list is simply left empty.
### Versioning
The `interchange_format_version` is set to 5 because the specification went through several breaking changes during its design, incorporating feedback from implementers.
## Backwards Compatibility
This specification is not backwards-compatible with previous draft versions that used version numbers less than 5.
## Security Considerations
In order to minimise risk and complexity, the format has been designed to map cleanly onto the internal database formats used by implementers. Nevertheless, there are a few pitfalls worth illuminating.
### Advice for Complete Databases
For implementers who use a complete record of signed messages to implement their slashing protection database, we make the following recommendations:
- You MUST ensure that, in addition to importing all of the messages from an interchange, all the [conditions](#conditions) are enforced. In particular, conditions (2), (4) and (5) may not have been enforced by your implementation before adopting the interchange format. Our recommendation is to enforce these rules at all times, to keep the implementation clean and minimise the attack surface. For example: your slashing protection mechanism should not sign a block with a slot number less than, or equal to, the minimum slot number of a previously signed block, _irrespective_ of whether that minimum-slot block was imported from an interchange file, or inserted as part of your database's regular operation.
- If your database records the signing roots of messages in addition to their slot/epochs, you should ensure that imported messages without signing roots are assigned a suitable dummy signing root internally. We suggest using a special "null" value which is distinct from all other signing roots, although a value like `0x0` may be used instead (as it is extremely unlikely to collide with any real signing root).
- Care must be taken to avoid signing messages within a gap in the database (an area of unknown signing activity). This could occur if two interchanges were imported with a large gap between the last entry of the first and the first entry of the second. Signing in this gap is not safe, and would violate conditions (2), (4) and (5). It can be avoided by storing an explicit low watermark in addition to the actual messages of the slashing protection database, or by pruning on import so that the oldest messages from the interchange become the oldest messages in the database.
### Advice for Minimal Databases
For implementers who wish to implement their slashing protection database by storing only the latest block and attestation for each validator, we make the following recommendations:
- During import, make sure you take the _maximum_ slot block and _maximum_ source and target attestations for each validator. Although the [conditions](#conditions) require the minimums to be enforced, taking the maximums from an interchange file and merging them with any existing values in the database is the recommended approach. For example, if the interchange file includes blocks for validator `V` at slots 4, 98 and 243, then the latest signed block for validator `V` should be updated to the one from slot 243. However, if the database has already included a block for this validator at a slot greater than 243, for example, slot 351, then the database's existing value should remain unchanged.
### General Recommendations
- To avoid exporting an outdated interchange file -- an action which creates a slashing risk -- your implementation should only allow the slashing protection database to be exported when the validator client or signer is _stopped_ -- in other words, when the client or signer is no longer adding new messages to the database.
- Similarly, your implementation should only allow an interchange file to be imported when the validator client is stopped.
## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).