@@ -30,6 +30,29 @@ pub struct Ed25519SignatureOffsets {
30
30
message_instruction_index : u16 , // index of instruction data to get message data
31
31
}
32
32
33
+ /// Convenience function to convert signature offsets into a single ed25519 instruction
34
+ /// The caller can choose to extend the data buffer and write the verification data at
35
+ /// `DATA_START` or store the verification data in a different instruction.
36
+ pub fn offsets_to_ed25519_instruction ( offsets : & [ Ed25519SignatureOffsets ] ) -> Instruction {
37
+ let mut instruction_data = Vec :: with_capacity (
38
+ SIGNATURE_OFFSETS_START
39
+ . saturating_add ( SIGNATURE_OFFSETS_SERIALIZED_SIZE . saturating_mul ( offsets. len ( ) ) ) ,
40
+ ) ;
41
+
42
+ let num_signatures = offsets. len ( ) as u16 ;
43
+ instruction_data. extend_from_slice ( & num_signatures. to_le_bytes ( ) ) ;
44
+
45
+ for offsets in offsets {
46
+ instruction_data. extend_from_slice ( bytes_of ( offsets) ) ;
47
+ }
48
+
49
+ Instruction {
50
+ program_id : solana_sdk_ids:: ed25519_program:: id ( ) ,
51
+ accounts : vec ! [ ] ,
52
+ data : instruction_data,
53
+ }
54
+ }
55
+
33
56
pub fn new_ed25519_instruction ( keypair : & ed25519_dalek:: Keypair , message : & [ u8 ] ) -> Instruction {
34
57
let signature = keypair. sign ( message) . to_bytes ( ) ;
35
58
let pubkey = keypair. public . to_bytes ( ) ;
@@ -83,6 +106,59 @@ pub fn new_ed25519_instruction(keypair: &ed25519_dalek::Keypair, message: &[u8])
83
106
}
84
107
}
85
108
109
+ /// Creates an ed25519 instruction for verifying multiple messages signed by `keypair`
110
+ /// The verification is stored in the instruction data, so it is up to the caller to check
111
+ /// that the messages will fit within the maximum transaction size.
112
+ pub fn new_multi_ed25519_instruction (
113
+ keypair : & ed25519_dalek:: Keypair ,
114
+ messages : & [ & [ u8 ] ] ,
115
+ ) -> Instruction {
116
+ let data_start = messages
117
+ . len ( )
118
+ . saturating_mul ( SIGNATURE_OFFSETS_SERIALIZED_SIZE )
119
+ . saturating_add ( SIGNATURE_OFFSETS_START ) ;
120
+ let mut data_offset = data_start. saturating_add ( PUBKEY_SERIALIZED_SIZE ) ;
121
+ let ( offsets, signature_messages) : ( Vec < _ > , Vec < _ > ) = messages
122
+ . iter ( )
123
+ . map ( |message| {
124
+ let signature = keypair. sign ( message) . to_bytes ( ) ;
125
+
126
+ assert_eq ! ( signature. len( ) , SIGNATURE_SERIALIZED_SIZE ) ;
127
+
128
+ let signature_offset = data_offset;
129
+ let message_data_offset = signature_offset. saturating_add ( SIGNATURE_SERIALIZED_SIZE ) ;
130
+ data_offset = data_offset
131
+ . saturating_add ( SIGNATURE_SERIALIZED_SIZE )
132
+ . saturating_add ( message. len ( ) ) ;
133
+
134
+ let offsets = Ed25519SignatureOffsets {
135
+ signature_offset : signature_offset as u16 ,
136
+ signature_instruction_index : u16:: MAX ,
137
+ public_key_offset : data_start as u16 ,
138
+ public_key_instruction_index : u16:: MAX ,
139
+ message_data_offset : message_data_offset as u16 ,
140
+ message_data_size : message. len ( ) as u16 ,
141
+ message_instruction_index : u16:: MAX ,
142
+ } ;
143
+
144
+ ( offsets, ( signature, message) )
145
+ } )
146
+ . unzip ( ) ;
147
+
148
+ let mut instruction = offsets_to_ed25519_instruction ( & offsets) ;
149
+
150
+ let pubkey = keypair. public . as_ref ( ) ;
151
+ assert_eq ! ( pubkey. len( ) , PUBKEY_SERIALIZED_SIZE ) ;
152
+ instruction. data . extend_from_slice ( pubkey) ;
153
+
154
+ for ( signature, message) in signature_messages {
155
+ instruction. data . extend_from_slice ( & signature) ;
156
+ instruction. data . extend_from_slice ( message) ;
157
+ }
158
+
159
+ instruction
160
+ }
161
+
86
162
pub fn verify (
87
163
data : & [ u8 ] ,
88
164
instruction_datas : & [ & [ u8 ] ] ,
@@ -440,6 +516,43 @@ pub mod test {
440
516
assert ! ( tx. verify_precompiles( & feature_set) . is_err( ) ) ;
441
517
}
442
518
519
+ #[ test]
520
+ fn test_multi_ed25519 ( ) {
521
+ solana_logger:: setup ( ) ;
522
+
523
+ let privkey = ed25519_dalek:: Keypair :: generate ( & mut thread_rng ( ) ) ;
524
+ let messages: [ & [ u8 ] ; 3 ] = [ b"hello" , b"IBRL" , b"goodbye" ] ;
525
+ let mut instruction = new_multi_ed25519_instruction ( & privkey, & messages) ;
526
+ let mint_keypair = Keypair :: new ( ) ;
527
+ let feature_set = FeatureSet :: all_enabled ( ) ;
528
+
529
+ let tx = Transaction :: new_signed_with_payer (
530
+ & [ instruction. clone ( ) ] ,
531
+ Some ( & mint_keypair. pubkey ( ) ) ,
532
+ & [ & mint_keypair] ,
533
+ Hash :: default ( ) ,
534
+ ) ;
535
+
536
+ assert ! ( tx. verify_precompiles( & feature_set) . is_ok( ) ) ;
537
+
538
+ let index = loop {
539
+ let index = thread_rng ( ) . gen_range ( 0 , instruction. data . len ( ) ) ;
540
+ // byte 1 is not used, so this would not cause the verify to fail
541
+ if index != 1 {
542
+ break index;
543
+ }
544
+ } ;
545
+
546
+ instruction. data [ index] = instruction. data [ index] . wrapping_add ( 12 ) ;
547
+ let tx = Transaction :: new_signed_with_payer (
548
+ & [ instruction] ,
549
+ Some ( & mint_keypair. pubkey ( ) ) ,
550
+ & [ & mint_keypair] ,
551
+ Hash :: default ( ) ,
552
+ ) ;
553
+ assert ! ( tx. verify_precompiles( & feature_set) . is_err( ) ) ;
554
+ }
555
+
443
556
#[ test]
444
557
fn test_ed25519_malleability ( ) {
445
558
solana_logger:: setup ( ) ;
0 commit comments