1+ use std:: collections:: HashSet ;
2+
13use acropolis_common:: {
24 crypto:: keyhash_224,
35 validation:: {
@@ -12,9 +14,13 @@ use crate::ouroboros::{kes, praos, tpraos};
1214
1315#[ derive( Copy , Clone ) ]
1416pub struct OperationalCertificate < ' a > {
17+ /// The operational hot key
1518 pub operational_cert_hot_vkey : & ' a [ u8 ] ,
19+ /// The sequence number of the operational certificate
1620 pub operational_cert_sequence_number : u64 ,
21+ /// The KES period of the operational certificate
1722 pub operational_cert_kes_period : u64 ,
23+ /// The signature of the operational certificate
1824 pub operational_cert_sigma : & ' a [ u8 ] ,
1925}
2026
@@ -88,6 +94,8 @@ pub fn validate_operational_certificate<'a>(
8894 }
8995
9096 // The opcert message is a concatenation of the KES vkey, the sequence number, and the kes period
97+ // Reference
98+ // https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/libs/cardano-protocol-tpraos/src/Cardano/Protocol/TPraos/OCert.hs#L144
9199 let mut message = Vec :: new ( ) ;
92100 message. extend_from_slice ( certificate. operational_cert_hot_vkey ) ;
93101 message. extend_from_slice ( & certificate. operational_cert_sequence_number . to_be_bytes ( ) ) ;
@@ -101,65 +109,79 @@ pub fn validate_operational_certificate<'a>(
101109 Ok ( ( ) )
102110}
103111
112+ /// This function check block header's KES signature and operational certificate
113+ /// return validation functions for KES signature and operational certificate
114+ /// and the pool id and declared sequence number (which will be used to update the operational certificate counter when validation is successful)
115+ /// Reference
116+ /// https://github.com/IntersectMBO/ouroboros-consensus/blob/e3c52b7c583bdb6708fac4fdaa8bf0b9588f5a88/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos.hs#L612
104117pub fn validate_block_kes < ' a > (
105118 header : & ' a MultiEraHeader ,
106119 ocert_counters : & ' a HashMap < PoolId , u64 > ,
107- active_spos : & ' a [ PoolId ] ,
120+ active_spos : & ' a HashSet < PoolId > ,
108121 genesis_delegs : & ' a GenesisDelegates ,
109122 slots_per_kes_period : u64 ,
110123 max_kes_evolutions : u64 ,
111- ) -> Result < Vec < KesValidation < ' a > > , Box < KesValidationError > > {
124+ ) -> Result < ( Vec < KesValidation < ' a > > , PoolId , u64 ) , Box < KesValidationError > > {
112125 let is_praos = matches ! ( header, MultiEraHeader :: BabbageCompatible ( _) ) ;
113126
114127 let issuer_vkey = header. issuer_vkey ( ) . ok_or ( Box :: new ( KesValidationError :: Other (
115- "Issuer Key is not set " . to_string ( ) ,
128+ "Block header missing issuer verification key " . to_string ( ) ,
116129 ) ) ) ?;
117130 let issuer = ed25519:: PublicKey :: from (
118- <[ u8 ; ed25519:: PublicKey :: SIZE ] >:: try_from ( issuer_vkey)
119- . map_err ( |_| Box :: new ( KesValidationError :: Other ( "Invalid issuer key" . to_string ( ) ) ) ) ?,
131+ <[ u8 ; ed25519:: PublicKey :: SIZE ] >:: try_from ( issuer_vkey) . map_err ( |_| {
132+ Box :: new ( KesValidationError :: Other (
133+ "Issuer verification key has invalid length (expected 32 bytes)" . to_string ( ) ,
134+ ) )
135+ } ) ?,
120136 ) ;
121137 let pool_id = PoolId :: from ( keyhash_224 ( issuer_vkey) ) ;
122138
123139 let slot_kes_period = header. slot ( ) / slots_per_kes_period;
124140 let cert = operational_cert ( header) . ok_or ( Box :: new ( KesValidationError :: Other (
125- "Operational certificate is not set " . to_string ( ) ,
141+ "Block header missing operational certificate " . to_string ( ) ,
126142 ) ) ) ?;
127143 let body_sig = body_signature ( header) . ok_or ( Box :: new ( KesValidationError :: Other (
128- "Body signature is not set " . to_string ( ) ,
144+ "Block header missing KES body signature " . to_string ( ) ,
129145 ) ) ) ?;
130146 let raw_header_body = header. header_body_cbor ( ) . ok_or ( Box :: new ( KesValidationError :: Other (
131- "Header body is not set " . to_string ( ) ,
147+ "Block header body CBOR not available " . to_string ( ) ,
132148 ) ) ) ?;
133149
150+ let declared_sequence_number = cert. operational_cert_sequence_number ;
134151 let latest_sequence_number = if is_praos {
135152 praos:: latest_issue_no_praos ( ocert_counters, active_spos, & pool_id)
136153 } else {
137154 tpraos:: latest_issue_no_tpraos ( ocert_counters, active_spos, genesis_delegs, & pool_id)
138155 }
139156 . ok_or ( Box :: new ( KesValidationError :: NoOCertCounter { pool_id } ) ) ?;
140157
141- Ok ( vec ! [
142- Box :: new( move || {
143- validate_kes_signature(
144- slot_kes_period,
145- cert. operational_cert_kes_period,
146- raw_header_body,
147- & kes:: PublicKey :: try_from( cert. operational_cert_hot_vkey) . map_err( |_| {
148- KesValidationError :: Other (
149- "Invalid operational certificate hot vkey" . to_string( ) ,
150- )
151- } ) ?,
152- & kes:: Signature :: try_from( body_sig)
153- . map_err( |_| KesValidationError :: Other ( "Invalid body signature" . to_string( ) ) ) ?,
154- max_kes_evolutions,
155- ) ?;
156- Ok ( ( ) )
157- } ) ,
158- Box :: new( move || {
159- validate_operational_certificate( cert, & issuer, latest_sequence_number, is_praos) ?;
160- Ok ( ( ) )
161- } ) ,
162- ] )
158+ Ok ( (
159+ vec ! [
160+ Box :: new( move || {
161+ validate_kes_signature(
162+ slot_kes_period,
163+ cert. operational_cert_kes_period,
164+ raw_header_body,
165+ & kes:: PublicKey :: try_from( cert. operational_cert_hot_vkey) . map_err( |_| {
166+ KesValidationError :: Other (
167+ "Invalid operational certificate hot vkey" . to_string( ) ,
168+ )
169+ } ) ?,
170+ & kes:: Signature :: try_from( body_sig) . map_err( |_| {
171+ KesValidationError :: Other ( "Invalid body signature" . to_string( ) )
172+ } ) ?,
173+ max_kes_evolutions,
174+ ) ?;
175+ Ok ( ( ) )
176+ } ) ,
177+ Box :: new( move || {
178+ validate_operational_certificate( cert, & issuer, latest_sequence_number, is_praos) ?;
179+ Ok ( ( ) )
180+ } ) ,
181+ ] ,
182+ pool_id,
183+ declared_sequence_number,
184+ ) )
163185}
164186
165187fn operational_cert < ' a > ( header : & ' a MultiEraHeader ) -> Option < OperationalCertificate < ' a > > {
@@ -213,7 +235,7 @@ mod tests {
213235 MultiEraHeader :: decode ( Era :: Shelley as u8 , None , & block_header_4490511) . unwrap ( ) ;
214236
215237 let ocert_counters = HashMap :: new ( ) ;
216- let active_spos = vec ! [ ] ;
238+ let active_spos = HashSet :: new ( ) ;
217239
218240 let result = validate_block_kes (
219241 & block_header,
@@ -223,10 +245,12 @@ mod tests {
223245 slots_per_kes_period,
224246 max_kes_evolutions,
225247 )
226- . and_then ( |kes_validations| {
227- kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) )
248+ . and_then ( |( kes_validations, pool_id, declared_sequence_number) | {
249+ kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) ) ?;
250+ Ok ( ( pool_id, declared_sequence_number) )
228251 } ) ;
229252 assert ! ( result. is_ok( ) ) ;
253+ assert_eq ! ( result. unwrap( ) . 1 , 0 ) ;
230254 }
231255
232256 #[ test]
@@ -245,11 +269,10 @@ mod tests {
245269 . unwrap ( ) ,
246270 1 ,
247271 ) ] ) ;
248- let active_spos =
249- vec ! [
250- PoolId :: from_bech32( "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy" )
251- . unwrap( ) ,
252- ] ;
272+ let active_spos = HashSet :: from_iter ( [ PoolId :: from_bech32 (
273+ "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy" ,
274+ )
275+ . unwrap ( ) ] ) ;
253276
254277 let result = validate_block_kes (
255278 & block_header,
@@ -259,10 +282,12 @@ mod tests {
259282 slots_per_kes_period,
260283 max_kes_evolutions,
261284 )
262- . and_then ( |kes_validations| {
263- kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) )
285+ . and_then ( |( kes_validations, pool_id, declared_sequence_number) | {
286+ kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) ) ?;
287+ Ok ( ( pool_id, declared_sequence_number) )
264288 } ) ;
265289 assert ! ( result. is_ok( ) ) ;
290+ assert_eq ! ( result. unwrap( ) . 1 , 1 ) ;
266291 }
267292
268293 #[ test]
@@ -281,11 +306,10 @@ mod tests {
281306 . unwrap ( ) ,
282307 2 ,
283308 ) ] ) ;
284- let active_spos =
285- vec ! [
286- PoolId :: from_bech32( "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy" )
287- . unwrap( ) ,
288- ] ;
309+ let active_spos = HashSet :: from_iter ( [ PoolId :: from_bech32 (
310+ "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy" ,
311+ )
312+ . unwrap ( ) ] ) ;
289313
290314 let result = validate_block_kes (
291315 & block_header,
@@ -295,8 +319,9 @@ mod tests {
295319 slots_per_kes_period,
296320 max_kes_evolutions,
297321 )
298- . and_then ( |kes_validations| {
299- kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) )
322+ . and_then ( |( kes_validations, pool_id, declared_sequence_number) | {
323+ kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) ) ?;
324+ Ok ( ( pool_id, declared_sequence_number) )
300325 } ) ;
301326 assert ! ( result. is_err( ) ) ;
302327 assert_eq ! (
@@ -322,7 +347,7 @@ mod tests {
322347 MultiEraHeader :: decode ( Era :: Shelley as u8 , None , & block_header_4556956) . unwrap ( ) ;
323348
324349 let ocert_counters = HashMap :: new ( ) ;
325- let active_spos = vec ! [ ] ;
350+ let active_spos = HashSet :: new ( ) ;
326351
327352 let result = validate_block_kes (
328353 & block_header,
@@ -332,9 +357,11 @@ mod tests {
332357 slots_per_kes_period,
333358 max_kes_evolutions,
334359 )
335- . and_then ( |kes_validations| {
336- kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) )
360+ . and_then ( |( kes_validations, pool_id, declared_sequence_number) | {
361+ kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) ) ?;
362+ Ok ( ( pool_id, declared_sequence_number) )
337363 } ) ;
364+
338365 assert ! ( result. is_err( ) ) ;
339366 assert_eq ! (
340367 result. unwrap_err( ) ,
@@ -363,11 +390,10 @@ mod tests {
363390 . unwrap ( ) ,
364391 11 ,
365392 ) ] ) ;
366- let active_spos =
367- vec ! [
368- PoolId :: from_bech32( "pool195gdnmj6smzuakm4etxsxw3fgh8asqc4awtcskpyfnkpcvh2v8t" )
369- . unwrap( ) ,
370- ] ;
393+ let active_spos = HashSet :: from_iter ( [ PoolId :: from_bech32 (
394+ "pool195gdnmj6smzuakm4etxsxw3fgh8asqc4awtcskpyfnkpcvh2v8t" ,
395+ )
396+ . unwrap ( ) ] ) ;
371397
372398 let result = validate_block_kes (
373399 & block_header,
@@ -377,8 +403,9 @@ mod tests {
377403 slots_per_kes_period,
378404 max_kes_evolutions,
379405 )
380- . and_then ( |kes_validations| {
381- kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) )
406+ . and_then ( |( kes_validations, pool_id, declared_sequence_number) | {
407+ kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) ) ?;
408+ Ok ( ( pool_id, declared_sequence_number) )
382409 } ) ;
383410 assert ! ( result. is_ok( ) ) ;
384411 }
@@ -402,11 +429,10 @@ mod tests {
402429 // now ocert counter is incremented by 2
403430 9 ,
404431 ) ] ) ;
405- let active_spos =
406- vec ! [
407- PoolId :: from_bech32( "pool195gdnmj6smzuakm4etxsxw3fgh8asqc4awtcskpyfnkpcvh2v8t" )
408- . unwrap( ) ,
409- ] ;
432+ let active_spos = HashSet :: from_iter ( [ PoolId :: from_bech32 (
433+ "pool195gdnmj6smzuakm4etxsxw3fgh8asqc4awtcskpyfnkpcvh2v8t" ,
434+ )
435+ . unwrap ( ) ] ) ;
410436
411437 let result = validate_block_kes (
412438 & block_header,
@@ -416,8 +442,9 @@ mod tests {
416442 slots_per_kes_period,
417443 max_kes_evolutions,
418444 )
419- . and_then ( |kes_validations| {
420- kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) )
445+ . and_then ( |( kes_validations, pool_id, declared_sequence_number) | {
446+ kes_validations. iter ( ) . try_for_each ( |assert| assert ( ) . map_err ( Box :: new) ) ?;
447+ Ok ( ( pool_id, declared_sequence_number) )
421448 } ) ;
422449 assert ! ( result. is_err( ) ) ;
423450 assert_eq ! (
0 commit comments