@@ -8,10 +8,114 @@ use serde::{Deserialize, Serialize};
8
8
9
9
use crate :: { Wots160PublicKey , Wots256PublicKey , WOTS_SINGLE } ;
10
10
11
- /// Winternitz One-Time Signature (WOTS) public key .
11
+ /// Winternitz One-Time Signature (WOTS) public keys shared in a deposit .
12
12
#[ derive( Clone , PartialEq , Eq , PartialOrd , Ord , Debug , Serialize , Deserialize ) ]
13
13
#[ cfg_attr( feature = "proptest" , derive( Arbitrary ) ) ]
14
14
pub struct WotsPublicKeys {
15
+ /// WOTS public key used for the Withdrawal Fulfillment transaction.
16
+ pub withdrawal_fulfillment : Wots256PublicKey ,
17
+
18
+ /// WOTS public keys used for the Assert transaction in the Groth16 proof.
19
+ pub groth16 : Groth16PublicKeys ,
20
+ }
21
+
22
+ impl WotsPublicKeys {
23
+ /// Creates a new [`WotsPublicKeys`] instance.
24
+ ///
25
+ /// # Examples
26
+ ///
27
+ /// ```
28
+ /// # use strata_p2p_types::{WotsPublicKeys, Wots256PublicKey, Wots160PublicKey, Groth16PublicKeys};
29
+ /// let withdrawal_key = Wots256PublicKey::new([[1u8; 20]; 68]);
30
+ ///
31
+ /// // Create a WotsPublicKeys with empty Groth16 parts
32
+ /// let empty_groth16_keys = WotsPublicKeys::new(
33
+ /// withdrawal_key.clone(),
34
+ /// vec![],
35
+ /// vec![],
36
+ /// vec![]
37
+ /// );
38
+ ///
39
+ /// // Create a WotsPublicKeys with some Groth16 parts
40
+ /// let public_inputs = Wots256PublicKey::new([[2u8; 20]; 68]);
41
+ /// let field_elements = Wots256PublicKey::new([[3u8; 20]; 68]);
42
+ /// let hashes = Wots160PublicKey::new([[4u8; 20]; 44]);
43
+ ///
44
+ /// let wots_keys = WotsPublicKeys::new(
45
+ /// withdrawal_key,
46
+ /// vec![public_inputs],
47
+ /// vec![field_elements],
48
+ /// vec![hashes],
49
+ /// );
50
+ /// ```
51
+ pub fn new (
52
+ withdrawal_fulfillment : Wots256PublicKey ,
53
+ public_inputs : Vec < Wots256PublicKey > ,
54
+ fqs : Vec < Wots256PublicKey > ,
55
+ hashes : Vec < Wots160PublicKey > ,
56
+ ) -> Self {
57
+ Self {
58
+ withdrawal_fulfillment,
59
+ groth16 : Groth16PublicKeys :: new ( public_inputs, fqs, hashes) ,
60
+ }
61
+ }
62
+
63
+ /// Creates a [`WotsPublicKeys`] from a flattened byte array.
64
+ ///
65
+ /// # Format
66
+ ///
67
+ /// The flattened byte array is structured as follows:
68
+ ///
69
+ /// - The first 3 bytes of the Groth16PublicKeys (containing counts)
70
+ /// - The next `WOTS_SINGLE * Wots256PublicKey::SIZE` bytes represent the withdrawal_fulfillment
71
+ /// key
72
+ /// - The remaining bytes represent the flattened Groth16PublicKeys as described in
73
+ /// [`Groth16PublicKeys::to_flattened_bytes`]
74
+ pub fn from_flattened_bytes ( bytes : & [ u8 ] ) -> Self {
75
+ // The withdrawal fulfillment key size in flattened form
76
+ let withdrawal_key_size = WOTS_SINGLE * Wots256PublicKey :: SIZE ;
77
+
78
+ // Parse the withdrawal fulfillment key
79
+ let withdrawal_fulfillment =
80
+ Wots256PublicKey :: from_flattened_bytes ( & bytes[ 0 ..withdrawal_key_size] ) ;
81
+
82
+ // Parse the Groth16 public keys from the remaining bytes
83
+ let groth16 = Groth16PublicKeys :: from_flattened_bytes ( & bytes[ withdrawal_key_size..] ) ;
84
+
85
+ Self {
86
+ withdrawal_fulfillment,
87
+ groth16,
88
+ }
89
+ }
90
+
91
+ /// Converts [`WotsPublicKeys`] to a flattened byte array.
92
+ ///
93
+ /// # Format
94
+ ///
95
+ /// The flattened byte array is structured as follows:
96
+ ///
97
+ /// - The first `WOTS_SINGLE * Wots256PublicKey::SIZE` bytes represent the
98
+ /// withdrawal_fulfillment key
99
+ /// - The remaining bytes represent the flattened Groth16PublicKeys as described in
100
+ /// [`Groth16PublicKeys::to_flattened_bytes`]
101
+ pub fn to_flattened_bytes ( & self ) -> Vec < u8 > {
102
+ let mut bytes = Vec :: new ( ) ;
103
+
104
+ // Add withdrawal fulfillment key bytes
105
+ bytes. extend ( self . withdrawal_fulfillment . to_flattened_bytes ( ) ) ;
106
+
107
+ // Add Groth16 public keys bytes
108
+ bytes. extend ( self . groth16 . to_flattened_bytes ( ) ) ;
109
+
110
+ bytes
111
+ }
112
+ }
113
+
114
+ /// Winternitz One-Time Signature (WOTS) public keys used for the Assert transaction
115
+ /// in the Groth16 proof.
116
+ #[ derive( Clone , PartialEq , Eq , PartialOrd , Ord , Debug , Serialize , Deserialize ) ]
117
+ #[ cfg_attr( feature = "proptest" , derive( Arbitrary ) ) ]
118
+ pub struct Groth16PublicKeys {
15
119
/// Number of public inputs.
16
120
pub n_public_inputs : u8 ,
17
121
@@ -31,8 +135,26 @@ pub struct WotsPublicKeys {
31
135
pub hashes : Vec < Wots160PublicKey > ,
32
136
}
33
137
34
- impl WotsPublicKeys {
35
- /// Creates a new [`WotsPublicKeys`] instance.
138
+ impl Groth16PublicKeys {
139
+ /// Creates a new [`Groth16PublicKeys`] instance.
140
+ ///
141
+ /// Note that you can create [`Groth16PublicKeys`] that contains no public inputs, field
142
+ /// elements, or hashes. For example:
143
+ ///
144
+ /// ```
145
+ /// # use strata_p2p_types::{Groth16PublicKeys, Wots256PublicKey, Wots160PublicKey};
146
+ /// let empty_wots = Groth16PublicKeys::new(vec![], vec![], vec![]);
147
+ /// # assert!(empty_wots.is_empty());
148
+ ///
149
+ /// let public_inputs = Wots256PublicKey::new([[1u8; 20]; 68]);
150
+ /// let just_public_inputs = Groth16PublicKeys::new(vec![public_inputs], vec![], vec![]);
151
+ ///
152
+ /// let field_elements = Wots256PublicKey::new([[2u8; 20]; 68]);
153
+ /// let just_field_elements = Groth16PublicKeys::new(vec![], vec![field_elements], vec![]);
154
+ ///
155
+ /// let hashes = Wots160PublicKey::new([[3u8; 20]; 44]);
156
+ /// let just_hashes = Groth16PublicKeys::new(vec![], vec![], vec![hashes]);
157
+ /// ```
36
158
pub fn new (
37
159
public_inputs : Vec < Wots256PublicKey > ,
38
160
fqs : Vec < Wots256PublicKey > ,
@@ -58,7 +180,7 @@ impl WotsPublicKeys {
58
180
self . n_public_inputs == 0 && self . n_field_elements == 0 && self . n_hashes == 0
59
181
}
60
182
61
- /// Converts [`WotsPublicKeys `] to a flattened byte array.
183
+ /// Converts [`Groth16PublicKeys `] to a flattened byte array.
62
184
///
63
185
/// # Format
64
186
///
@@ -175,9 +297,9 @@ mod tests {
175
297
use crate :: wots:: wots_total_digits;
176
298
177
299
#[ test]
178
- fn test_flattened_bytes_roundtrip ( ) {
300
+ fn groth16_wots_flattened_bytes_roundtrip ( ) {
179
301
// Create test data with known values
180
- let test_data = WotsPublicKeys :: new (
302
+ let test_data = Groth16PublicKeys :: new (
181
303
vec ! [ Wots256PublicKey :: new( [ [ 1u8 ; WOTS_SINGLE ] ; wots_total_digits( 32 ) ] ) ; 2 ] , /* 2 * 32 + 4 = 68 */
182
304
vec ! [ Wots256PublicKey :: new( [ [ 2u8 ; WOTS_SINGLE ] ; wots_total_digits( 32 ) ] ) ; 3 ] , /* 2 * 32 + 4 = 68 */
183
305
vec ! [ Wots160PublicKey :: new( [ [ 3u8 ; WOTS_SINGLE ] ; wots_total_digits( 20 ) ] ) ; 4 ] , /* 2 * 20 + 4 = 44 */
@@ -199,7 +321,7 @@ mod tests {
199
321
assert_eq ! ( flattened[ 2 ] , 4 ) ; // n_hashes
200
322
201
323
// Convert back from flattened bytes
202
- let reconstructed = WotsPublicKeys :: from_flattened_bytes ( & flattened) ;
324
+ let reconstructed = Groth16PublicKeys :: from_flattened_bytes ( & flattened) ;
203
325
204
326
// Verify all fields match
205
327
assert_eq ! ( test_data. n_public_inputs, reconstructed. n_public_inputs) ;
@@ -210,23 +332,87 @@ mod tests {
210
332
assert_eq ! ( test_data. hashes, reconstructed. hashes) ;
211
333
}
212
334
335
+ #[ test]
336
+ fn wots_public_keys_flattened_bytes_roundtrip ( ) {
337
+ // Create withdrawal fulfillment key
338
+ let withdrawal_fulfillment =
339
+ Wots256PublicKey :: new ( [ [ 4u8 ; WOTS_SINGLE ] ; wots_total_digits ( 32 ) ] ) ;
340
+
341
+ // Create Groth16 public keys
342
+ let groth16 = Groth16PublicKeys :: new (
343
+ vec ! [ Wots256PublicKey :: new( [ [ 1u8 ; WOTS_SINGLE ] ; wots_total_digits( 32 ) ] ) ; 2 ] ,
344
+ vec ! [ Wots256PublicKey :: new( [ [ 2u8 ; WOTS_SINGLE ] ; wots_total_digits( 32 ) ] ) ; 3 ] ,
345
+ vec ! [ Wots160PublicKey :: new( [ [ 3u8 ; WOTS_SINGLE ] ; wots_total_digits( 20 ) ] ) ; 4 ] ,
346
+ ) ;
347
+
348
+ // Create the WotsPublicKeys
349
+ let wots_keys = WotsPublicKeys :: new (
350
+ withdrawal_fulfillment,
351
+ groth16. public_inputs . clone ( ) ,
352
+ groth16. fqs . clone ( ) ,
353
+ groth16. hashes . clone ( ) ,
354
+ ) ;
355
+
356
+ // Convert to flattened bytes
357
+ let flattened = wots_keys. to_flattened_bytes ( ) ;
358
+
359
+ // Verify the length matches what we expect
360
+ let expected_len = ( WOTS_SINGLE * Wots256PublicKey :: SIZE ) + // withdrawal_fulfillment
361
+ 3 + // 3 bytes for counts
362
+ ( 2 * WOTS_SINGLE * Wots256PublicKey :: SIZE ) + // public inputs
363
+ ( 3 * WOTS_SINGLE * Wots256PublicKey :: SIZE ) + // field elements
364
+ ( 4 * WOTS_SINGLE * Wots160PublicKey :: SIZE ) ; // hashes
365
+ assert_eq ! ( flattened. len( ) , expected_len) ;
366
+
367
+ // Check that the first part is the withdrawal fulfillment key
368
+ let withdrawal_bytes = withdrawal_fulfillment. to_flattened_bytes ( ) ;
369
+ assert_eq ! ( & flattened[ 0 ..withdrawal_bytes. len( ) ] , & withdrawal_bytes[ ..] ) ;
370
+
371
+ // Convert back from flattened bytes
372
+ let reconstructed = WotsPublicKeys :: from_flattened_bytes ( & flattened) ;
373
+
374
+ // Verify all fields match
375
+ assert_eq ! (
376
+ wots_keys. withdrawal_fulfillment,
377
+ reconstructed. withdrawal_fulfillment
378
+ ) ;
379
+ assert_eq ! (
380
+ wots_keys. groth16. n_public_inputs,
381
+ reconstructed. groth16. n_public_inputs
382
+ ) ;
383
+ assert_eq ! (
384
+ wots_keys. groth16. n_field_elements,
385
+ reconstructed. groth16. n_field_elements
386
+ ) ;
387
+ assert_eq ! ( wots_keys. groth16. n_hashes, reconstructed. groth16. n_hashes) ;
388
+ assert_eq ! (
389
+ wots_keys. groth16. public_inputs,
390
+ reconstructed. groth16. public_inputs
391
+ ) ;
392
+ assert_eq ! ( wots_keys. groth16. fqs, reconstructed. groth16. fqs) ;
393
+ assert_eq ! ( wots_keys. groth16. hashes, reconstructed. groth16. hashes) ;
394
+
395
+ // Full equality check
396
+ assert_eq ! ( wots_keys, reconstructed) ;
397
+ }
398
+
213
399
#[ cfg( feature = "proptest" ) ]
214
400
proptest ! {
215
401
#[ test]
216
- fn proptest_flattened_bytes_roundtrip (
402
+ fn proptest_groth16_wots_flattened_bytes_roundtrip (
217
403
n_inputs in 0u8 ..5u8 ,
218
404
n_fqs in 0u8 ..5u8 ,
219
405
n_hashes in 0u8 ..5u8 ,
220
406
value in 0u8 ..255u8
221
407
) {
222
- let test_data = WotsPublicKeys :: new(
408
+ let test_data = Groth16PublicKeys :: new(
223
409
vec![ Wots256PublicKey :: new( [ [ value; WOTS_SINGLE ] ; Wots256PublicKey :: SIZE ] ) ; n_inputs as usize ] ,
224
410
vec![ Wots256PublicKey :: new( [ [ value; WOTS_SINGLE ] ; Wots256PublicKey :: SIZE ] ) ; n_fqs as usize ] ,
225
411
vec![ Wots160PublicKey :: new( [ [ value; WOTS_SINGLE ] ; Wots160PublicKey :: SIZE ] ) ; n_hashes as usize ] ,
226
412
) ;
227
413
228
414
let flattened = test_data. to_flattened_bytes( ) ;
229
- let reconstructed = WotsPublicKeys :: from_flattened_bytes( & flattened) ;
415
+ let reconstructed = Groth16PublicKeys :: from_flattened_bytes( & flattened) ;
230
416
231
417
prop_assert_eq!( test_data. n_public_inputs, reconstructed. n_public_inputs) ;
232
418
prop_assert_eq!( test_data. n_field_elements, reconstructed. n_field_elements) ;
@@ -235,5 +421,44 @@ mod tests {
235
421
prop_assert_eq!( test_data. fqs, reconstructed. fqs) ;
236
422
prop_assert_eq!( test_data. hashes, reconstructed. hashes) ;
237
423
}
424
+
425
+ #[ test]
426
+ fn proptest_wots_public_keys_flattened_bytes_roundtrip(
427
+ withdrawal_value in 0u8 ..255u8 ,
428
+ n_inputs in 0u8 ..3u8 ,
429
+ n_fqs in 0u8 ..3u8 ,
430
+ n_hashes in 0u8 ..3u8 ,
431
+ groth16_value in 0u8 ..255u8
432
+ ) {
433
+ // Create test data with different values for withdrawal and groth16
434
+ let withdrawal_fulfillment = Wots256PublicKey :: new(
435
+ [ [ withdrawal_value; WOTS_SINGLE ] ; Wots256PublicKey :: SIZE ]
436
+ ) ;
437
+
438
+ let groth16 = Groth16PublicKeys :: new(
439
+ vec![ Wots256PublicKey :: new( [ [ groth16_value; WOTS_SINGLE ] ; Wots256PublicKey :: SIZE ] ) ; n_inputs as usize ] ,
440
+ vec![ Wots256PublicKey :: new( [ [ groth16_value; WOTS_SINGLE ] ; Wots256PublicKey :: SIZE ] ) ; n_fqs as usize ] ,
441
+ vec![ Wots160PublicKey :: new( [ [ groth16_value; WOTS_SINGLE ] ; Wots160PublicKey :: SIZE ] ) ; n_hashes as usize ] ,
442
+ ) ;
443
+
444
+ let wots_keys = WotsPublicKeys :: new(
445
+ withdrawal_fulfillment,
446
+ groth16. public_inputs. clone( ) ,
447
+ groth16. fqs. clone( ) ,
448
+ groth16. hashes. clone( ) ,
449
+ ) ;
450
+
451
+ let flattened = wots_keys. to_flattened_bytes( ) ;
452
+ let reconstructed = WotsPublicKeys :: from_flattened_bytes( & flattened) ;
453
+
454
+ prop_assert_eq!( wots_keys. withdrawal_fulfillment, reconstructed. withdrawal_fulfillment) ;
455
+ prop_assert_eq!( wots_keys. groth16. n_public_inputs, reconstructed. groth16. n_public_inputs) ;
456
+ prop_assert_eq!( wots_keys. groth16. n_field_elements, reconstructed. groth16. n_field_elements) ;
457
+ prop_assert_eq!( wots_keys. groth16. n_hashes, reconstructed. groth16. n_hashes) ;
458
+ prop_assert_eq!( wots_keys. groth16. public_inputs. clone( ) , reconstructed. groth16. public_inputs. clone( ) ) ;
459
+ prop_assert_eq!( wots_keys. groth16. fqs. clone( ) , reconstructed. groth16. fqs. clone( ) ) ;
460
+ prop_assert_eq!( wots_keys. groth16. hashes. clone( ) , reconstructed. groth16. hashes. clone( ) ) ;
461
+ prop_assert_eq!( wots_keys, reconstructed) ;
462
+ }
238
463
}
239
464
}
0 commit comments