1
- use ouroboros:: ledger:: { issuer_vkey_to_pool_id, PoolId , PoolInfo } ;
1
+ use ouroboros:: ledger:: { issuer_vkey_to_pool_id, LedgerState , PoolId } ;
2
2
use ouroboros:: validator:: Validator ;
3
3
use pallas_crypto:: hash:: { Hash , Hasher } ;
4
+ use pallas_crypto:: kes:: { KesPublicKey , KesSignature } ;
5
+ use pallas_crypto:: key:: ed25519:: { PublicKey , Signature } ;
4
6
use pallas_crypto:: vrf:: {
5
7
VrfProof , VrfProofBytes , VrfProofHashBytes , VrfPublicKey , VrfPublicKeyBytes ,
6
8
} ;
@@ -22,23 +24,23 @@ static CERTIFIED_NATURAL_MAX: LazyLock<FixedDecimal> = LazyLock::new(|| {
22
24
23
25
/// Validator for a block using praos consensus.
24
26
pub struct BlockValidator < ' b > {
25
- header : & ' b babbage:: Header ,
26
- pool_info : & ' b dyn PoolInfo ,
27
+ header : & ' b babbage:: MintedHeader < ' b > ,
28
+ ledger_state : & ' b dyn LedgerState ,
27
29
epoch_nonce : & ' b Hash < 32 > ,
28
30
// c is the ln(1-active_slots_coeff). Usually ln(1-0.05)
29
31
c : & ' b FixedDecimal ,
30
32
}
31
33
32
34
impl < ' b > BlockValidator < ' b > {
33
35
pub fn new (
34
- header : & ' b babbage:: Header ,
35
- pool_info : & ' b dyn PoolInfo ,
36
+ header : & ' b babbage:: MintedHeader ,
37
+ ledger_state : & ' b dyn LedgerState ,
36
38
epoch_nonce : & ' b Hash < 32 > ,
37
39
c : & ' b FixedDecimal ,
38
40
) -> Self {
39
41
Self {
40
42
header,
41
- pool_info ,
43
+ ledger_state ,
42
44
epoch_nonce,
43
45
c,
44
46
}
@@ -49,6 +51,7 @@ impl<'b> BlockValidator<'b> {
49
51
let _enter = span. enter ( ) ;
50
52
51
53
// Grab all the values we need to validate the block
54
+ let absolute_slot = self . header . header_body . slot ;
52
55
let issuer_vkey = & self . header . header_body . issuer_vkey ;
53
56
let pool_id: PoolId = issuer_vkey_to_pool_id ( issuer_vkey) ;
54
57
let vrf_vkey: VrfPublicKeyBytes = match ( & self . header . header_body . vrf_vkey ) . try_into ( ) {
@@ -58,11 +61,8 @@ impl<'b> BlockValidator<'b> {
58
61
return false ;
59
62
}
60
63
} ;
61
- if !self . ledger_matches_block_vrf_key_hash ( & pool_id, & vrf_vkey) {
62
- // Fail fast if the vrf key hash in the block does not match the ledger
63
- return false ;
64
- }
65
- let sigma: FixedDecimal = match self . pool_info . sigma ( & pool_id) {
64
+ let leader_vrf_output = & self . header . header_body . leader_vrf_output ( ) ;
65
+ let sigma: FixedDecimal = match self . ledger_state . pool_id_to_sigma ( & pool_id) {
66
66
Ok ( sigma) => {
67
67
FixedDecimal :: from ( sigma. numerator ) / FixedDecimal :: from ( sigma. denominator )
68
68
}
@@ -71,10 +71,6 @@ impl<'b> BlockValidator<'b> {
71
71
return false ;
72
72
}
73
73
} ;
74
- let absolute_slot = self . header . header_body . slot ;
75
-
76
- // Get the leader VRF output hash from the block vrf result
77
- let leader_vrf_output = & self . header . header_body . leader_vrf_output ( ) ;
78
74
79
75
let block_vrf_proof_hash: VrfProofHashBytes =
80
76
match ( & self . header . header_body . vrf_result . 0 ) . try_into ( ) {
@@ -109,13 +105,166 @@ impl<'b> BlockValidator<'b> {
109
105
) ;
110
106
trace ! ( "kes_signature: {}" , hex:: encode( kes_signature) ) ;
111
107
108
+ if !self . ledger_matches_block_vrf_key_hash ( & pool_id, & vrf_vkey) {
109
+ // Fail fast if the vrf key hash in the block does not match the ledger
110
+ error ! ( "VRF key hash validation failed" ) ;
111
+ return false ;
112
+ }
113
+ trace ! ( "VRF key hash validated" ) ;
114
+
115
+ if !self . validate_block_vrf (
116
+ absolute_slot,
117
+ & vrf_vkey,
118
+ leader_vrf_output,
119
+ & sigma,
120
+ block_vrf_proof_hash,
121
+ & block_vrf_proof,
122
+ ) {
123
+ // Fail if the block VRF validation has failed
124
+ error ! ( "Block VRF validation failed" ) ;
125
+ return false ;
126
+ }
127
+ trace ! ( "Block VRF validated" ) ;
128
+
129
+ if !self . validate_operational_certificate ( issuer_vkey. as_slice ( ) ) {
130
+ // Fail if the operational certificate validation has failed
131
+ error ! ( "Operational Certificate signature validation failed" ) ;
132
+ return false ;
133
+ } ;
134
+ trace ! ( "Operational Certificate signature validated" ) ;
135
+
136
+ if !self . validate_kes_signature ( absolute_slot, kes_signature) {
137
+ // Fail if the KES signature validation has failed
138
+ error ! ( "KES signature validation failed" ) ;
139
+ return false ;
140
+ }
141
+ trace ! ( "KES signature validated" ) ;
142
+ true
143
+ }
144
+
145
+ fn validate_kes_signature ( & self , absolute_slot : u64 , kes_signature : & [ u8 ] ) -> bool {
146
+ // Verify the KES signature
147
+ let kes_pk = match KesPublicKey :: from_bytes (
148
+ & self
149
+ . header
150
+ . header_body
151
+ . operational_cert
152
+ . operational_cert_hot_vkey ,
153
+ ) {
154
+ Ok ( kes_pk) => kes_pk,
155
+ Err ( error) => {
156
+ error ! ( "Could not convert kes_pk: {}" , error) ;
157
+ return false ;
158
+ }
159
+ } ;
160
+
161
+ // calculate the right KES period to verify the signature
162
+ let slot_kes_period = self . ledger_state . slot_to_kes_period ( absolute_slot) ;
163
+ let opcert_kes_period = self
164
+ . header
165
+ . header_body
166
+ . operational_cert
167
+ . operational_cert_kes_period ;
168
+
169
+ if opcert_kes_period > slot_kes_period {
170
+ error ! ( "Operational Certificate KES period is greater than the block slot KES period!" ) ;
171
+ return false ;
172
+ }
173
+ if slot_kes_period >= opcert_kes_period + self . ledger_state . max_kes_evolutions ( ) {
174
+ error ! ( "Operational Certificate KES period is too old!" ) ;
175
+ return false ;
176
+ }
177
+
178
+ let kes_period = ( slot_kes_period - opcert_kes_period) as u32 ;
179
+ trace ! ( "kes_period: {}" , kes_period) ;
180
+
181
+ // The header_body_cbor was signed by the KES private key. Verify this with the KES public key
182
+ let header_body_cbor = self . header . header_body . raw_cbor ( ) ;
183
+ let kes_signature = match KesSignature :: from_bytes ( kes_signature) {
184
+ Ok ( kes_signature) => kes_signature,
185
+ Err ( error) => {
186
+ error ! ( "Could not convert kes_signature: {}" , error) ;
187
+ return false ;
188
+ }
189
+ } ;
190
+
191
+ match kes_signature. verify ( kes_period, & kes_pk, header_body_cbor) {
192
+ Ok ( _) => true ,
193
+ Err ( error) => {
194
+ error ! ( "KES signature verification failed: {}" , error) ;
195
+ false
196
+ }
197
+ }
198
+ }
199
+
200
+ fn validate_operational_certificate ( & self , issuer_vkey : & [ u8 ] ) -> bool {
201
+ // Verify the Operational Certificate signature
202
+ let opcert_signature = match Signature :: try_from (
203
+ self . header
204
+ . header_body
205
+ . operational_cert
206
+ . operational_cert_sigma
207
+ . as_slice ( ) ,
208
+ ) {
209
+ Ok ( opcert_signature) => opcert_signature,
210
+ Err ( error) => {
211
+ error ! ( "Could not convert opcert_signature: {}" , error) ;
212
+ return false ;
213
+ }
214
+ } ;
215
+ let cold_pk = match PublicKey :: try_from ( issuer_vkey) {
216
+ Ok ( cold_pk) => cold_pk,
217
+ Err ( error) => {
218
+ error ! ( "Could not convert cold_pk: {}" , error) ;
219
+ return false ;
220
+ }
221
+ } ;
222
+
223
+ // The opcert message is a concatenation of the KES vkey, the counter, and the kes period
224
+ let mut opcert_message = Vec :: new ( ) ;
225
+ opcert_message. extend_from_slice (
226
+ & self
227
+ . header
228
+ . header_body
229
+ . operational_cert
230
+ . operational_cert_hot_vkey ,
231
+ ) ;
232
+ opcert_message. extend_from_slice (
233
+ & self
234
+ . header
235
+ . header_body
236
+ . operational_cert
237
+ . operational_cert_sequence_number
238
+ . to_be_bytes ( ) ,
239
+ ) ;
240
+ opcert_message. extend_from_slice (
241
+ & self
242
+ . header
243
+ . header_body
244
+ . operational_cert
245
+ . operational_cert_kes_period
246
+ . to_be_bytes ( ) ,
247
+ ) ;
248
+
249
+ cold_pk. verify ( & opcert_message, & opcert_signature)
250
+ }
251
+
252
+ fn validate_block_vrf (
253
+ & self ,
254
+ absolute_slot : u64 ,
255
+ vrf_vkey : & VrfPublicKeyBytes ,
256
+ leader_vrf_output : & Vec < u8 > ,
257
+ sigma : & FixedDecimal ,
258
+ block_vrf_proof_hash : VrfProofHashBytes ,
259
+ block_vrf_proof : & VrfProofBytes ,
260
+ ) -> bool {
112
261
// Calculate the VRF input seed so we can verify the VRF output against it.
113
262
let vrf_input_seed = self . mk_vrf_input ( absolute_slot, self . epoch_nonce . as_ref ( ) ) ;
114
263
trace ! ( "vrf_input_seed: {}" , vrf_input_seed) ;
115
264
116
265
// Verify the VRF proof
117
- let vrf_proof = VrfProof :: from ( & block_vrf_proof) ;
118
- let vrf_vkey = VrfPublicKey :: from ( & vrf_vkey) ;
266
+ let vrf_proof = VrfProof :: from ( block_vrf_proof) ;
267
+ let vrf_vkey = VrfPublicKey :: from ( vrf_vkey) ;
119
268
match vrf_proof. verify ( & vrf_vkey, vrf_input_seed. as_ref ( ) ) {
120
269
Ok ( proof_hash) => {
121
270
if proof_hash. as_slice ( ) != block_vrf_proof_hash. as_slice ( ) {
@@ -136,16 +285,11 @@ impl<'b> BlockValidator<'b> {
136
285
} else {
137
286
// The leader VRF output hash matches what was in the block
138
287
// Now we need to check if the pool had enough sigma stake to produce this block
139
- if self . pool_meets_delegation_threshold (
140
- & sigma,
288
+ self . pool_meets_delegation_threshold (
289
+ sigma,
141
290
absolute_slot,
142
291
leader_vrf_output. as_slice ( ) ,
143
- ) {
144
- // TODO: Validate the KES signature
145
- true
146
- } else {
147
- false
148
- }
292
+ )
149
293
}
150
294
}
151
295
}
@@ -186,11 +330,9 @@ impl<'b> BlockValidator<'b> {
186
330
true
187
331
}
188
332
_ => {
189
- trace ! (
333
+ error ! (
190
334
"Slot: {} - NOT Leader: {} >= {}" ,
191
- absolute_slot,
192
- recip_q,
193
- ordering. approx
335
+ absolute_slot, recip_q, ordering. approx
194
336
) ;
195
337
false
196
338
}
@@ -205,7 +347,7 @@ impl<'b> BlockValidator<'b> {
205
347
) -> bool {
206
348
let vrf_vkey_hash: Hash < 32 > = Hasher :: < 256 > :: hash ( vrf_vkey) ;
207
349
trace ! ( "block vrf_vkey_hash: {}" , hex:: encode( vrf_vkey_hash) ) ;
208
- let ledger_vrf_vkey_hash = match self . pool_info . vrf_vkey_hash ( pool_id) {
350
+ let ledger_vrf_vkey_hash = match self . ledger_state . vrf_vkey_hash ( pool_id) {
209
351
Ok ( ledger_vrf_vkey_hash) => ledger_vrf_vkey_hash,
210
352
Err ( error) => {
211
353
warn ! ( "{:?} - {:?}" , error, pool_id) ;
@@ -243,7 +385,7 @@ mod tests {
243
385
use crate :: consensus:: BlockValidator ;
244
386
use ctor:: ctor;
245
387
use mockall:: predicate:: eq;
246
- use ouroboros:: ledger:: { MockPoolInfo , PoolId , PoolSigma } ;
388
+ use ouroboros:: ledger:: { MockLedgerState , PoolId , PoolSigma } ;
247
389
use ouroboros:: validator:: Validator ;
248
390
use pallas_crypto:: hash:: Hash ;
249
391
use pallas_math:: math:: { FixedDecimal , FixedPrecision } ;
@@ -252,10 +394,9 @@ mod tests {
252
394
#[ ctor]
253
395
fn init ( ) {
254
396
// set rust log level to TRACE
255
- // std::env::set_var("RUST_LOG", "ouroboros-praos=trace");
256
-
397
+ // std::env::set_var("RUST_LOG", "trace");
257
398
// initialize tracing crate
258
- tracing_subscriber:: fmt:: init ( ) ;
399
+ // tracing_subscriber::fmt::init();
259
400
}
260
401
261
402
#[ test]
@@ -299,22 +440,29 @@ mod tests {
299
440
let babbage_header = multi_era_header. as_babbage ( ) . expect ( "Infallible" ) ;
300
441
assert_eq ! ( babbage_header. header_body. slot, 134402628u64 ) ;
301
442
302
- let mut pool_info = MockPoolInfo :: new ( ) ;
303
- pool_info
304
- . expect_sigma ( )
443
+ let mut ledger_state = MockLedgerState :: new ( ) ;
444
+ ledger_state
445
+ . expect_pool_id_to_sigma ( )
305
446
. with ( eq ( pool_id) )
306
447
. returning ( move |_| {
307
448
Ok ( PoolSigma {
308
449
numerator,
309
450
denominator,
310
451
} )
311
452
} ) ;
312
- pool_info
453
+ ledger_state
313
454
. expect_vrf_vkey_hash ( )
314
455
. with ( eq ( pool_id) )
315
456
. returning ( move |_| Ok ( vrf_vkey_hash) ) ;
457
+ ledger_state. expect_slot_to_kes_period ( ) . returning ( |slot| {
458
+ // hardcode some values from shelley-genesis.json for the mock implementation
459
+ let slots_per_kes_period: u64 = 129600 ; // from shelley-genesis.json (1.5 days in seconds)
460
+ slot / slots_per_kes_period
461
+ } ) ;
462
+ ledger_state. expect_max_kes_evolutions ( ) . returning ( || 62 ) ;
316
463
317
- let block_validator = BlockValidator :: new ( babbage_header, & pool_info, & epoch_nonce, & c) ;
464
+ let block_validator =
465
+ BlockValidator :: new ( babbage_header, & ledger_state, & epoch_nonce, & c) ;
318
466
assert_eq ! ( block_validator. validate( ) , expected) ;
319
467
}
320
468
}
0 commit comments