@@ -113,14 +113,12 @@ impl Protoleaf {
113113
114114impl SigsumSignature {
115115 pub fn from_ascii ( input : & str ) -> Result < Self > {
116- let parts: Vec < & str > = input. split ( "\n \n " ) . collect ( ) ;
117- if parts. len ( ) != 3 {
118- return Err ( ParseAsciiError ( format ! (
119- "expected 3 parts, got {}" ,
120- parts. len( )
121- ) ) ) ;
122- }
123- let mut p = Parser :: new ( parts[ 0 ] ) ;
116+ let mut parts = input. split ( "\n \n " ) ;
117+ let mut p = Parser :: new (
118+ parts
119+ . next ( )
120+ . expect ( "str.split() should return at least one part" ) ,
121+ ) ;
124122 let version: u64 = p. parse ( VERSION_FIELD ) ?;
125123 if version != 2 {
126124 return Err ( ParseAsciiError ( format ! ( "version {version} not supported" ) ) ) ;
@@ -132,8 +130,32 @@ impl SigsumSignature {
132130 "expected an empty line after 'leaf'" . into ( ) ,
133131 ) ) ;
134132 }
135- let sth = SignedTreeHead :: from_ascii ( parts[ 1 ] ) ?;
136- let proof = InclusionProof :: from_ascii ( parts[ 2 ] ) ?;
133+
134+ // Parsing the tree head part
135+ let Some ( sth_part) = parts. next ( ) else {
136+ return Err ( ParseAsciiError ( "missing tree head" . into ( ) ) ) ;
137+ } ;
138+ let sth = SignedTreeHead :: from_ascii ( sth_part) ?;
139+
140+ // Parsing the inclusion proof iff the tree size is not 1
141+ let proof;
142+ if sth. size > 1 {
143+ let Some ( proof_part) = parts. next ( ) else {
144+ return Err ( ParseAsciiError ( "missing inclusion proof" . into ( ) ) ) ;
145+ } ;
146+ proof = InclusionProof :: from_ascii ( proof_part) ?;
147+ } else if sth. size == 1 {
148+ proof = InclusionProof {
149+ leaf_index : 0 ,
150+ node_hashes : Vec :: new ( ) ,
151+ } ;
152+ } else {
153+ return Err ( ParseAsciiError ( "empty tree" . into ( ) ) ) ;
154+ }
155+ if parts. next ( ) . is_some ( ) {
156+ return Err ( ParseAsciiError ( "trailing data" . into ( ) ) ) ;
157+ } ;
158+
137159 Ok ( Self {
138160 log_keyhash,
139161 leaf_keyhash,
@@ -164,10 +186,12 @@ impl SigsumSignature {
164186 cosig. keyhash, cosig. timestamp, cosig. cosignature
165187 ) ;
166188 }
167- writeln ! ( ascii) ;
168- writeln ! ( ascii, "{LEAF_INDEX_FIELD}={}" , self . proof. leaf_index) ;
169- for h in self . proof . node_hashes . iter ( ) {
170- writeln ! ( ascii, "{NODE_HASH_FIELD}={h:x}" ) ;
189+ if self . sth . size > 1 {
190+ writeln ! ( ascii) ;
191+ writeln ! ( ascii, "{LEAF_INDEX_FIELD}={}" , self . proof. leaf_index) ;
192+ for h in self . proof . node_hashes . iter ( ) {
193+ writeln ! ( ascii, "{NODE_HASH_FIELD}={h:x}" ) ;
194+ }
171195 }
172196 ascii
173197 }
@@ -295,15 +319,70 @@ mod tests {
295319 ";
296320
297321 #[ test]
298- fn sigsumsig_from_ascii ( ) {
322+ fn ssig_from_ascii ( ) {
299323 assert_eq ! ( * SSIG , SigsumSignature :: from_ascii( SSIG_ASCII ) . unwrap( ) ) ;
300324 }
301325
302326 #[ test]
303- fn sigsumsig_to_ascii ( ) {
327+ fn ssig_to_ascii ( ) {
304328 assert_eq ! ( SSIG_ASCII , ( * SSIG ) . to_ascii( ) ) ;
305329 }
306330
331+ lazy_static ! {
332+ static ref SSIG_SIZE1 : SigsumSignature = SigsumSignature {
333+ log_keyhash: hex!( "4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d" )
334+ . into( ) ,
335+ leaf_keyhash: hex!( "16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245" ) . into( ) ,
336+ leaf_signature: hex!( "e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900" ) . into( ) ,
337+ sth: SignedTreeHead {
338+ size: 1 ,
339+ root_hash: hex!( "8100f29c0e9017a7512dab0911bf06a4b5b99cd77d8c710635307b5d217af1f6" ) . into( ) ,
340+ signature: hex!( "e327fe13e5c3d2043cbf69fe1b778f77cb10a8e14fc09309dd375c9af25903f9ec35906cfb2c36ab2d210329eb538a6673487d2d101800370c978634b6f9f70d" ) . into( ) ,
341+ cosignatures: vec![
342+ WitnessCosignature {
343+ keyhash: hex!( "1a450ecf1f49a4e4580c35e4d83316a74deda949dbb7d338e89d4315764d88de" ) . into( ) ,
344+ timestamp: 1687170591 ,
345+ cosignature: hex!( "cacc54d315609b796f72ac1d71d1bbc15667853ed980bd3e0f957de7a875b84bd2dcde6489fc3ed66428190ce588ac1061b0d5748e73cfb887ebf38d0b53060a" ) . into( ) ,
346+ } ,
347+ WitnessCosignature {
348+ keyhash: hex!( "73b6cbe5e3c8e679fb5967b78c59e95db2969a5c13b3423b5e69523e3d52f531" ) . into( ) ,
349+ timestamp: 1687170591 ,
350+ cosignature: hex!( "7f568da17c57ea322a9c2668ae9fc2c1d6ab5556d9a997e7bfa1cbc4dc5cf7b94e0cead42d481bf0d3d90ad2ee0d272e9e687f8f82fddf76d37d722c6815fe0f" ) . into( ) ,
351+ } ,
352+ ] ,
353+ } ,
354+ proof: InclusionProof {
355+ leaf_index: 0 ,
356+ node_hashes: vec![ ] ,
357+ } ,
358+ } ;
359+ }
360+
361+ const SSIG_SIZE1_ASCII : & str = "\
362+ version=2\n \
363+ log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n \
364+ leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n \
365+ \n \
366+ size=1\n \
367+ root_hash=8100f29c0e9017a7512dab0911bf06a4b5b99cd77d8c710635307b5d217af1f6\n \
368+ signature=e327fe13e5c3d2043cbf69fe1b778f77cb10a8e14fc09309dd375c9af25903f9ec35906cfb2c36ab2d210329eb538a6673487d2d101800370c978634b6f9f70d\n \
369+ cosignature=1a450ecf1f49a4e4580c35e4d83316a74deda949dbb7d338e89d4315764d88de 1687170591 cacc54d315609b796f72ac1d71d1bbc15667853ed980bd3e0f957de7a875b84bd2dcde6489fc3ed66428190ce588ac1061b0d5748e73cfb887ebf38d0b53060a\n \
370+ cosignature=73b6cbe5e3c8e679fb5967b78c59e95db2969a5c13b3423b5e69523e3d52f531 1687170591 7f568da17c57ea322a9c2668ae9fc2c1d6ab5556d9a997e7bfa1cbc4dc5cf7b94e0cead42d481bf0d3d90ad2ee0d272e9e687f8f82fddf76d37d722c6815fe0f\n \
371+ ";
372+
373+ #[ test]
374+ fn ssig_size1_from_ascii ( ) {
375+ assert_eq ! (
376+ * SSIG_SIZE1 ,
377+ SigsumSignature :: from_ascii( SSIG_SIZE1_ASCII ) . unwrap( )
378+ ) ;
379+ }
380+
381+ #[ test]
382+ fn ssig_size1_to_ascii ( ) {
383+ assert_eq ! ( SSIG_SIZE1_ASCII , ( * SSIG_SIZE1 ) . to_ascii( ) ) ;
384+ }
385+
307386 macro_rules! test_ssig_parse_error {
308387 ( $testname: ident, $errormsg: literal, $input: literal) => {
309388 #[ test]
@@ -316,9 +395,21 @@ mod tests {
316395 } ;
317396 }
318397
398+ test_ssig_parse_error ! { ssig_empty, "unexpected end of input" , "" }
399+
319400 test_ssig_parse_error ! {
320- ssig_missing_part,
321- "expected 3 parts, got 2" ,
401+ ssig_missing_sth,
402+ "missing tree head" ,
403+ "\
404+ version=2\n \
405+ log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n \
406+ leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n \
407+ "
408+ }
409+
410+ test_ssig_parse_error ! {
411+ ssig_missing_proof,
412+ "missing inclusion proof" ,
322413 "\
323414 version=2\n \
324415 log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n \
@@ -330,23 +421,55 @@ mod tests {
330421 "
331422 }
332423
333- test_ssig_parse_error ! { sigsum_signature_too_many_parts,
334- "expected 3 parts, got 4" ,
424+ test_ssig_parse_error ! {
425+ ssig_empty_tree,
426+ "empty tree" ,
335427 "\
336428 version=2\n \
337429 log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n \
338430 leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n \
339431 \n \
340- size=1\n \
432+ size=0\n \
433+ root_hash=5a2221ac3be281d2403abb44f9b6a8b1b1b2db97e5838d029a2df19db5e708bd\n \
434+ signature=67de0c13f8ae8a13ca2ad2aaab35326ba55186d240cbb48f4872a79847a41b5d2b43820dc8f4ae43030d432bbf753c365ba7a248fc12ea7be53f05bb72829409\n \
435+ "
436+ }
437+
438+ test_ssig_parse_error ! {
439+ ssig_trailing_data,
440+ "trailing data" ,
441+ "\
442+ version=2\n \
443+ log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n \
444+ leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n \
445+ \n \
446+ size=2\n \
341447 root_hash=5a2221ac3be281d2403abb44f9b6a8b1b1b2db97e5838d029a2df19db5e708bd\n \
342448 signature=67de0c13f8ae8a13ca2ad2aaab35326ba55186d240cbb48f4872a79847a41b5d2b43820dc8f4ae43030d432bbf753c365ba7a248fc12ea7be53f05bb72829409\n \
343449 \n \
344450 leaf_index=1\n \
451+ node_hash=35fd6eb70d46d60679775c346225688e6e84c02c3c7978e5c51daf8decc22d2f\n \
345452 \n \
346453 abc=123\n \
347454 "
348455 }
349456
457+ test_ssig_parse_error ! {
458+ ssig_size1_trailing_data,
459+ "trailing data" ,
460+ "\
461+ version=2\n \
462+ log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n \
463+ leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n \
464+ \n \
465+ size=1\n \
466+ root_hash=5a2221ac3be281d2403abb44f9b6a8b1b1b2db97e5838d029a2df19db5e708bd\n \
467+ signature=67de0c13f8ae8a13ca2ad2aaab35326ba55186d240cbb48f4872a79847a41b5d2b43820dc8f4ae43030d432bbf753c365ba7a248fc12ea7be53f05bb72829409\n \
468+ \n \
469+ leaf_index=0\n \
470+ "
471+ }
472+
350473 test_ssig_parse_error ! { sigsum_signature_version_not_supported,
351474 "version 666 not supported" ,
352475 "\
0 commit comments