@@ -61,12 +61,18 @@ impl Secp256r1SignatureOffsets {
6161}
6262
6363/// Verify the secp256r1 instruction data contains the expected signature and
64- /// public key. This also validates that the secp256r1 precompile offsets point
65- /// to the expected locations, ensuring proper data alignment.
64+ /// public key. Also validates that the secp256r1 precompile offsets point to
65+ /// the expected locations, ensuring proper data alignment.
66+ ///
67+ /// The expected precompile message is passed as TWO slices — the
68+ /// authenticator_data and the clientDataJSON hash — which are concatenated
69+ /// by the on-chain secp256r1 precompile as its signed message. Accepting
70+ /// two slices here lets the caller skip a Vec allocation for the concat.
6671pub fn verify_secp256r1_instruction_data (
6772 instruction_data : & [ u8 ] ,
6873 expected_pubkey : & [ u8 ; 33 ] ,
69- expected_message : & [ u8 ] ,
74+ auth_data : & [ u8 ] ,
75+ client_data_hash : & [ u8 ; 32 ] ,
7076) -> Result < ( ) , ProgramError > {
7177 // Minimum check: must have at least the header and offsets
7278 if instruction_data. len ( ) < DATA_START {
@@ -111,25 +117,257 @@ pub fn verify_secp256r1_instruction_data(
111117 if offsets. message_data_offset as usize != MESSAGE_DATA_OFFSET {
112118 return Err ( AuthError :: InvalidInstruction . into ( ) ) ;
113119 }
114- if offsets. message_data_size as usize != expected_message. len ( ) {
120+ let expected_msg_len = auth_data. len ( ) + client_data_hash. len ( ) ;
121+ if offsets. message_data_size as usize != expected_msg_len {
115122 return Err ( AuthError :: InvalidInstruction . into ( ) ) ;
116123 }
117124
118125 // Dynamic length check: instruction must contain the full message
119- if instruction_data. len ( ) < MESSAGE_DATA_OFFSET + expected_message . len ( ) {
126+ if instruction_data. len ( ) < MESSAGE_DATA_OFFSET + expected_msg_len {
120127 return Err ( AuthError :: InvalidInstruction . into ( ) ) ;
121128 }
122129
123130 let pubkey_data = & instruction_data
124131 [ PUBKEY_DATA_OFFSET ..PUBKEY_DATA_OFFSET + COMPRESSED_PUBKEY_SERIALIZED_SIZE ] ;
125- let message_data =
126- & instruction_data[ MESSAGE_DATA_OFFSET ..MESSAGE_DATA_OFFSET + expected_message. len ( ) ] ;
127-
128132 if pubkey_data != expected_pubkey {
129133 return Err ( AuthError :: InvalidPubkey . into ( ) ) ;
130134 }
131- if message_data != expected_message {
135+
136+ // Compare the precompile's message area against the two caller-supplied
137+ // slices piecewise — no concat, no allocation.
138+ let msg_auth = & instruction_data[ MESSAGE_DATA_OFFSET ..MESSAGE_DATA_OFFSET + auth_data. len ( ) ] ;
139+ if msg_auth != auth_data {
140+ return Err ( AuthError :: InvalidMessageHash . into ( ) ) ;
141+ }
142+ let hash_start = MESSAGE_DATA_OFFSET + auth_data. len ( ) ;
143+ let msg_hash = & instruction_data[ hash_start..hash_start + client_data_hash. len ( ) ] ;
144+ if msg_hash != client_data_hash {
132145 return Err ( AuthError :: InvalidMessageHash . into ( ) ) ;
133146 }
134147 Ok ( ( ) )
135148}
149+
150+ #[ cfg( test) ]
151+ mod tests {
152+ use super :: * ;
153+
154+ /// Helper: build valid secp256r1 precompile instruction data with the standard layout.
155+ fn build_precompile_ix_data (
156+ pubkey : & [ u8 ; 33 ] ,
157+ signature : & [ u8 ; 64 ] ,
158+ message : & [ u8 ] ,
159+ ) -> Vec < u8 > {
160+ let total_len = DATA_START + 64 + 33 + 1 + message. len ( ) ;
161+ let mut data = vec ! [ 0u8 ; total_len] ;
162+
163+ // Header
164+ data[ 0 ] = 1 ; // num_signatures
165+ data[ 1 ] = 0 ; // padding
166+
167+ // Offsets (little-endian)
168+ data[ 2 ..4 ] . copy_from_slice ( & ( SIGNATURE_DATA_OFFSET as u16 ) . to_le_bytes ( ) ) ;
169+ data[ 4 ..6 ] . copy_from_slice ( & 0xFFFFu16 . to_le_bytes ( ) ) ; // sig ix index
170+ data[ 6 ..8 ] . copy_from_slice ( & ( PUBKEY_DATA_OFFSET as u16 ) . to_le_bytes ( ) ) ;
171+ data[ 8 ..10 ] . copy_from_slice ( & 0xFFFFu16 . to_le_bytes ( ) ) ; // pubkey ix index
172+ data[ 10 ..12 ] . copy_from_slice ( & ( MESSAGE_DATA_OFFSET as u16 ) . to_le_bytes ( ) ) ;
173+ data[ 12 ..14 ] . copy_from_slice ( & ( message. len ( ) as u16 ) . to_le_bytes ( ) ) ; // msg size
174+ data[ 14 ..16 ] . copy_from_slice ( & 0xFFFFu16 . to_le_bytes ( ) ) ; // msg ix index
175+
176+ // Data
177+ data[ SIGNATURE_DATA_OFFSET ..SIGNATURE_DATA_OFFSET + 64 ] . copy_from_slice ( signature) ;
178+ data[ PUBKEY_DATA_OFFSET ..PUBKEY_DATA_OFFSET + 33 ] . copy_from_slice ( pubkey) ;
179+ // Byte at offset 113 is alignment padding (zero)
180+ data[ MESSAGE_DATA_OFFSET ..MESSAGE_DATA_OFFSET + message. len ( ) ]
181+ . copy_from_slice ( message) ;
182+
183+ data
184+ }
185+
186+ #[ test]
187+ fn test_verify_valid_instruction_data ( ) {
188+ let pubkey = [ 0x02 ; 33 ] ;
189+ let signature = [ 0xAB ; 64 ] ;
190+ let message = [ 0x11 ; 32 ] ;
191+
192+ let ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
193+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_ok( ) ) ;
194+ }
195+
196+ #[ test]
197+ fn test_verify_variable_length_message ( ) {
198+ // Mode 1 messages are authenticatorData(37+) + clientDataJsonHash(32) = 69+ bytes.
199+ // We split into the two halves exactly like the caller does post-refactor.
200+ let pubkey = [ 0x03 ; 33 ] ;
201+ let signature = [ 0xCD ; 64 ] ;
202+ let message = [ 0x22 ; 69 ] ;
203+
204+ let ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
205+ let auth_data: & [ u8 ] = & message[ ..37 ] ;
206+ let client_data_hash: & [ u8 ; 32 ] = & message[ 37 ..] . try_into ( ) . unwrap ( ) ;
207+ assert ! (
208+ verify_secp256r1_instruction_data( & ix_data, & pubkey, auth_data, client_data_hash)
209+ . is_ok( )
210+ ) ;
211+ }
212+
213+ #[ test]
214+ fn test_verify_rejects_wrong_pubkey ( ) {
215+ let pubkey = [ 0x02 ; 33 ] ;
216+ let wrong_pubkey = [ 0x03 ; 33 ] ;
217+ let signature = [ 0xAB ; 64 ] ;
218+ let message = [ 0x11 ; 32 ] ;
219+
220+ let ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
221+ let err =
222+ verify_secp256r1_instruction_data ( & ix_data, & wrong_pubkey, & [ ] , & message) . unwrap_err ( ) ;
223+ assert_eq ! ( err, AuthError :: InvalidPubkey . into( ) ) ;
224+ }
225+
226+ #[ test]
227+ fn test_verify_rejects_wrong_message ( ) {
228+ let pubkey = [ 0x02 ; 33 ] ;
229+ let signature = [ 0xAB ; 64 ] ;
230+ let message = [ 0x11 ; 32 ] ;
231+ let wrong_message = [ 0x22 ; 32 ] ;
232+
233+ let ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
234+ let err =
235+ verify_secp256r1_instruction_data ( & ix_data, & pubkey, & [ ] , & wrong_message) . unwrap_err ( ) ;
236+ assert_eq ! ( err, AuthError :: InvalidMessageHash . into( ) ) ;
237+ }
238+
239+ #[ test]
240+ fn test_verify_rejects_zero_signatures ( ) {
241+ let pubkey = [ 0x02 ; 33 ] ;
242+ let signature = [ 0xAB ; 64 ] ;
243+ let message = [ 0x11 ; 32 ] ;
244+
245+ let mut ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
246+ ix_data[ 0 ] = 0 ; // zero signatures
247+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_err( ) ) ;
248+ }
249+
250+ #[ test]
251+ fn test_verify_rejects_multiple_signatures ( ) {
252+ let pubkey = [ 0x02 ; 33 ] ;
253+ let signature = [ 0xAB ; 64 ] ;
254+ let message = [ 0x11 ; 32 ] ;
255+
256+ let mut ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
257+ ix_data[ 0 ] = 2 ; // two signatures
258+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_err( ) ) ;
259+ }
260+
261+ #[ test]
262+ fn test_verify_rejects_cross_instruction_sig_index ( ) {
263+ let pubkey = [ 0x02 ; 33 ] ;
264+ let signature = [ 0xAB ; 64 ] ;
265+ let message = [ 0x11 ; 32 ] ;
266+
267+ let mut ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
268+ // Set signature_instruction_index to 0 instead of 0xFFFF
269+ ix_data[ 4 ..6 ] . copy_from_slice ( & 0u16 . to_le_bytes ( ) ) ;
270+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_err( ) ) ;
271+ }
272+
273+ #[ test]
274+ fn test_verify_rejects_cross_instruction_pubkey_index ( ) {
275+ let pubkey = [ 0x02 ; 33 ] ;
276+ let signature = [ 0xAB ; 64 ] ;
277+ let message = [ 0x11 ; 32 ] ;
278+
279+ let mut ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
280+ // Set public_key_instruction_index to 1 instead of 0xFFFF
281+ ix_data[ 8 ..10 ] . copy_from_slice ( & 1u16 . to_le_bytes ( ) ) ;
282+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_err( ) ) ;
283+ }
284+
285+ #[ test]
286+ fn test_verify_rejects_cross_instruction_msg_index ( ) {
287+ let pubkey = [ 0x02 ; 33 ] ;
288+ let signature = [ 0xAB ; 64 ] ;
289+ let message = [ 0x11 ; 32 ] ;
290+
291+ let mut ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
292+ // Set message_instruction_index to 0 instead of 0xFFFF
293+ ix_data[ 14 ..16 ] . copy_from_slice ( & 0u16 . to_le_bytes ( ) ) ;
294+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_err( ) ) ;
295+ }
296+
297+ #[ test]
298+ fn test_verify_rejects_wrong_pubkey_offset ( ) {
299+ let pubkey = [ 0x02 ; 33 ] ;
300+ let signature = [ 0xAB ; 64 ] ;
301+ let message = [ 0x11 ; 32 ] ;
302+
303+ let mut ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
304+ // Tamper pubkey_offset to point elsewhere
305+ ix_data[ 6 ..8 ] . copy_from_slice ( & 200u16 . to_le_bytes ( ) ) ;
306+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_err( ) ) ;
307+ }
308+
309+ #[ test]
310+ fn test_verify_rejects_wrong_message_offset ( ) {
311+ let pubkey = [ 0x02 ; 33 ] ;
312+ let signature = [ 0xAB ; 64 ] ;
313+ let message = [ 0x11 ; 32 ] ;
314+
315+ let mut ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
316+ // Tamper message_data_offset
317+ ix_data[ 10 ..12 ] . copy_from_slice ( & 50u16 . to_le_bytes ( ) ) ;
318+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_err( ) ) ;
319+ }
320+
321+ #[ test]
322+ fn test_verify_rejects_wrong_signature_offset ( ) {
323+ let pubkey = [ 0x02 ; 33 ] ;
324+ let signature = [ 0xAB ; 64 ] ;
325+ let message = [ 0x11 ; 32 ] ;
326+
327+ let mut ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
328+ // Tamper signature_offset
329+ ix_data[ 2 ..4 ] . copy_from_slice ( & 100u16 . to_le_bytes ( ) ) ;
330+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_err( ) ) ;
331+ }
332+
333+ #[ test]
334+ fn test_verify_rejects_message_size_mismatch ( ) {
335+ let pubkey = [ 0x02 ; 33 ] ;
336+ let signature = [ 0xAB ; 64 ] ;
337+ let message = [ 0x11 ; 32 ] ;
338+
339+ let mut ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
340+ // Set message_data_size to wrong value
341+ ix_data[ 12 ..14 ] . copy_from_slice ( & 64u16 . to_le_bytes ( ) ) ;
342+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_err( ) ) ;
343+ }
344+
345+ #[ test]
346+ fn test_verify_rejects_too_short_data ( ) {
347+ let pubkey = [ 0x02 ; 33 ] ;
348+ let message = [ 0x11 ; 32 ] ;
349+
350+ // Only 2 bytes — way too short
351+ assert ! ( verify_secp256r1_instruction_data( & [ 0x01 , 0x00 ] , & pubkey, & [ ] , & message) . is_err( ) ) ;
352+ }
353+
354+ #[ test]
355+ fn test_verify_rejects_truncated_message_area ( ) {
356+ let pubkey = [ 0x02 ; 33 ] ;
357+ let signature = [ 0xAB ; 64 ] ;
358+ let message = [ 0x11 ; 32 ] ;
359+
360+ let mut ix_data = build_precompile_ix_data ( & pubkey, & signature, & message) ;
361+ // Truncate — remove last 10 bytes so message area is incomplete
362+ ix_data. truncate ( ix_data. len ( ) - 10 ) ;
363+ assert ! ( verify_secp256r1_instruction_data( & ix_data, & pubkey, & [ ] , & message) . is_err( ) ) ;
364+ }
365+
366+ #[ test]
367+ fn test_offsets_constants_are_consistent ( ) {
368+ assert_eq ! ( DATA_START , 16 ) ; // 2 header + 14 offsets
369+ assert_eq ! ( SIGNATURE_DATA_OFFSET , 16 ) ;
370+ assert_eq ! ( PUBKEY_DATA_OFFSET , 16 + 64 ) ; // 80
371+ assert_eq ! ( MESSAGE_DATA_OFFSET , 80 + 33 + 1 ) ; // 114
372+ }
373+ }
0 commit comments