1use {
6 bytemuck::bytes_of,
7 bytemuck_derive::{Pod, Zeroable},
8 ed25519_dalek::{ed25519::signature::Signature, Signer, Verifier},
9 solana_feature_set::{ed25519_precompile_verify_strict, FeatureSet},
10 solana_instruction::Instruction,
11 solana_precompile_error::PrecompileError,
12};
13
14pub const PUBKEY_SERIALIZED_SIZE: usize = 32;
15pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
16pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 14;
17pub const SIGNATURE_OFFSETS_START: usize = 2;
19pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START;
20
21#[derive(Default, Debug, Copy, Clone, Zeroable, Pod, Eq, PartialEq)]
22#[repr(C)]
23pub struct Ed25519SignatureOffsets {
24 pub signature_offset: u16, pub signature_instruction_index: u16, pub public_key_offset: u16, pub public_key_instruction_index: u16, pub message_data_offset: u16, pub message_data_size: u16, pub message_instruction_index: u16, }
32
33pub fn offsets_to_ed25519_instruction(offsets: &[Ed25519SignatureOffsets]) -> Instruction {
43 let mut instruction_data = Vec::with_capacity(
44 SIGNATURE_OFFSETS_START
45 .saturating_add(SIGNATURE_OFFSETS_SERIALIZED_SIZE.saturating_mul(offsets.len())),
46 );
47
48 let num_signatures = offsets.len() as u16;
49 instruction_data.extend_from_slice(&num_signatures.to_le_bytes());
50
51 for offsets in offsets {
52 instruction_data.extend_from_slice(bytes_of(offsets));
53 }
54
55 Instruction {
56 program_id: solana_sdk_ids::ed25519_program::id(),
57 accounts: vec![],
58 data: instruction_data,
59 }
60}
61
62pub fn new_ed25519_instruction(keypair: &ed25519_dalek::Keypair, message: &[u8]) -> Instruction {
63 let signature = keypair.sign(message).to_bytes();
64 let pubkey = keypair.public.to_bytes();
65
66 assert_eq!(pubkey.len(), PUBKEY_SERIALIZED_SIZE);
67 assert_eq!(signature.len(), SIGNATURE_SERIALIZED_SIZE);
68
69 let mut instruction_data = Vec::with_capacity(
70 DATA_START
71 .saturating_add(SIGNATURE_SERIALIZED_SIZE)
72 .saturating_add(PUBKEY_SERIALIZED_SIZE)
73 .saturating_add(message.len()),
74 );
75
76 let num_signatures: u8 = 1;
77 let public_key_offset = DATA_START;
78 let signature_offset = public_key_offset.saturating_add(PUBKEY_SERIALIZED_SIZE);
79 let message_data_offset = signature_offset.saturating_add(SIGNATURE_SERIALIZED_SIZE);
80
81 instruction_data.extend_from_slice(bytes_of(&[num_signatures, 0]));
83
84 let offsets = Ed25519SignatureOffsets {
85 signature_offset: signature_offset as u16,
86 signature_instruction_index: u16::MAX,
87 public_key_offset: public_key_offset as u16,
88 public_key_instruction_index: u16::MAX,
89 message_data_offset: message_data_offset as u16,
90 message_data_size: message.len() as u16,
91 message_instruction_index: u16::MAX,
92 };
93
94 instruction_data.extend_from_slice(bytes_of(&offsets));
95
96 debug_assert_eq!(instruction_data.len(), public_key_offset);
97
98 instruction_data.extend_from_slice(&pubkey);
99
100 debug_assert_eq!(instruction_data.len(), signature_offset);
101
102 instruction_data.extend_from_slice(&signature);
103
104 debug_assert_eq!(instruction_data.len(), message_data_offset);
105
106 instruction_data.extend_from_slice(message);
107
108 Instruction {
109 program_id: solana_sdk_ids::ed25519_program::id(),
110 accounts: vec![],
111 data: instruction_data,
112 }
113}
114
115pub fn verify(
116 data: &[u8],
117 instruction_datas: &[&[u8]],
118 feature_set: &FeatureSet,
119) -> Result<(), PrecompileError> {
120 if data.len() < SIGNATURE_OFFSETS_START {
121 return Err(PrecompileError::InvalidInstructionDataSize);
122 }
123 let num_signatures = data[0] as usize;
124 if num_signatures == 0 && data.len() > SIGNATURE_OFFSETS_START {
125 return Err(PrecompileError::InvalidInstructionDataSize);
126 }
127 let expected_data_size = num_signatures
128 .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
129 .saturating_add(SIGNATURE_OFFSETS_START);
130 if data.len() < expected_data_size {
132 return Err(PrecompileError::InvalidInstructionDataSize);
133 }
134 for i in 0..num_signatures {
135 let start = i
136 .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
137 .saturating_add(SIGNATURE_OFFSETS_START);
138 let end = start.saturating_add(SIGNATURE_OFFSETS_SERIALIZED_SIZE);
139
140 let offsets: &Ed25519SignatureOffsets = bytemuck::try_from_bytes(&data[start..end])
142 .map_err(|_| PrecompileError::InvalidDataOffsets)?;
143
144 let signature = get_data_slice(
146 data,
147 instruction_datas,
148 offsets.signature_instruction_index,
149 offsets.signature_offset,
150 SIGNATURE_SERIALIZED_SIZE,
151 )?;
152
153 let signature =
154 Signature::from_bytes(signature).map_err(|_| PrecompileError::InvalidSignature)?;
155
156 let pubkey = get_data_slice(
158 data,
159 instruction_datas,
160 offsets.public_key_instruction_index,
161 offsets.public_key_offset,
162 PUBKEY_SERIALIZED_SIZE,
163 )?;
164
165 let publickey = ed25519_dalek::PublicKey::from_bytes(pubkey)
166 .map_err(|_| PrecompileError::InvalidPublicKey)?;
167
168 let message = get_data_slice(
170 data,
171 instruction_datas,
172 offsets.message_instruction_index,
173 offsets.message_data_offset,
174 offsets.message_data_size as usize,
175 )?;
176
177 if feature_set.is_active(&ed25519_precompile_verify_strict::id()) {
178 publickey
179 .verify_strict(message, &signature)
180 .map_err(|_| PrecompileError::InvalidSignature)?;
181 } else {
182 publickey
183 .verify(message, &signature)
184 .map_err(|_| PrecompileError::InvalidSignature)?;
185 }
186 }
187 Ok(())
188}
189
190fn get_data_slice<'a>(
191 data: &'a [u8],
192 instruction_datas: &'a [&[u8]],
193 instruction_index: u16,
194 offset_start: u16,
195 size: usize,
196) -> Result<&'a [u8], PrecompileError> {
197 let instruction = if instruction_index == u16::MAX {
198 data
199 } else {
200 let signature_index = instruction_index as usize;
201 if signature_index >= instruction_datas.len() {
202 return Err(PrecompileError::InvalidDataOffsets);
203 }
204 instruction_datas[signature_index]
205 };
206
207 let start = offset_start as usize;
208 let end = start.saturating_add(size);
209 if end > instruction.len() {
210 return Err(PrecompileError::InvalidDataOffsets);
211 }
212
213 Ok(&instruction[start..end])
214}
215
216#[cfg(test)]
217pub mod test {
218 use {
219 super::*,
220 ed25519_dalek::Signer as EdSigner,
221 hex,
222 rand0_7::{thread_rng, Rng},
223 solana_feature_set::FeatureSet,
224 solana_hash::Hash,
225 solana_keypair::Keypair,
226 solana_sdk::transaction::Transaction,
227 solana_signer::Signer,
228 };
229
230 pub fn new_ed25519_instruction_raw(
231 pubkey: &[u8],
232 signature: &[u8],
233 message: &[u8],
234 ) -> Instruction {
235 assert_eq!(pubkey.len(), PUBKEY_SERIALIZED_SIZE);
236 assert_eq!(signature.len(), SIGNATURE_SERIALIZED_SIZE);
237
238 let mut instruction_data = Vec::with_capacity(
239 DATA_START
240 .saturating_add(SIGNATURE_SERIALIZED_SIZE)
241 .saturating_add(PUBKEY_SERIALIZED_SIZE)
242 .saturating_add(message.len()),
243 );
244
245 let num_signatures: u8 = 1;
246 let public_key_offset = DATA_START;
247 let signature_offset = public_key_offset.saturating_add(PUBKEY_SERIALIZED_SIZE);
248 let message_data_offset = signature_offset.saturating_add(SIGNATURE_SERIALIZED_SIZE);
249
250 instruction_data.extend_from_slice(bytes_of(&[num_signatures, 0]));
252
253 let offsets = Ed25519SignatureOffsets {
254 signature_offset: signature_offset as u16,
255 signature_instruction_index: u16::MAX,
256 public_key_offset: public_key_offset as u16,
257 public_key_instruction_index: u16::MAX,
258 message_data_offset: message_data_offset as u16,
259 message_data_size: message.len() as u16,
260 message_instruction_index: u16::MAX,
261 };
262
263 instruction_data.extend_from_slice(bytes_of(&offsets));
264
265 debug_assert_eq!(instruction_data.len(), public_key_offset);
266
267 instruction_data.extend_from_slice(pubkey);
268
269 debug_assert_eq!(instruction_data.len(), signature_offset);
270
271 instruction_data.extend_from_slice(signature);
272
273 debug_assert_eq!(instruction_data.len(), message_data_offset);
274
275 instruction_data.extend_from_slice(message);
276
277 Instruction {
278 program_id: solana_sdk_ids::ed25519_program::id(),
279 accounts: vec![],
280 data: instruction_data,
281 }
282 }
283
284 fn test_case(
285 num_signatures: u16,
286 offsets: &Ed25519SignatureOffsets,
287 ) -> Result<(), PrecompileError> {
288 assert_eq!(
289 bytemuck::bytes_of(offsets).len(),
290 SIGNATURE_OFFSETS_SERIALIZED_SIZE
291 );
292
293 let mut instruction_data = vec![0u8; DATA_START];
294 instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&num_signatures));
295 instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(offsets));
296
297 verify(
298 &instruction_data,
299 &[&[0u8; 100]],
300 &FeatureSet::all_enabled(),
301 )
302 }
303
304 #[test]
305 fn test_invalid_offsets() {
306 solana_logger::setup();
307
308 let mut instruction_data = vec![0u8; DATA_START];
309 let offsets = Ed25519SignatureOffsets::default();
310 instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&1u16));
311 instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(&offsets));
312 instruction_data.truncate(instruction_data.len() - 1);
313
314 assert_eq!(
315 verify(
316 &instruction_data,
317 &[&[0u8; 100]],
318 &FeatureSet::all_enabled(),
319 ),
320 Err(PrecompileError::InvalidInstructionDataSize)
321 );
322
323 let offsets = Ed25519SignatureOffsets {
324 signature_instruction_index: 1,
325 ..Ed25519SignatureOffsets::default()
326 };
327 assert_eq!(
328 test_case(1, &offsets),
329 Err(PrecompileError::InvalidDataOffsets)
330 );
331
332 let offsets = Ed25519SignatureOffsets {
333 message_instruction_index: 1,
334 ..Ed25519SignatureOffsets::default()
335 };
336 assert_eq!(
337 test_case(1, &offsets),
338 Err(PrecompileError::InvalidDataOffsets)
339 );
340
341 let offsets = Ed25519SignatureOffsets {
342 public_key_instruction_index: 1,
343 ..Ed25519SignatureOffsets::default()
344 };
345 assert_eq!(
346 test_case(1, &offsets),
347 Err(PrecompileError::InvalidDataOffsets)
348 );
349 }
350
351 #[test]
352 fn test_message_data_offsets() {
353 let offsets = Ed25519SignatureOffsets {
354 message_data_offset: 99,
355 message_data_size: 1,
356 ..Ed25519SignatureOffsets::default()
357 };
358 assert_eq!(
359 test_case(1, &offsets),
360 Err(PrecompileError::InvalidSignature)
361 );
362
363 let offsets = Ed25519SignatureOffsets {
364 message_data_offset: 100,
365 message_data_size: 1,
366 ..Ed25519SignatureOffsets::default()
367 };
368 assert_eq!(
369 test_case(1, &offsets),
370 Err(PrecompileError::InvalidDataOffsets)
371 );
372
373 let offsets = Ed25519SignatureOffsets {
374 message_data_offset: 100,
375 message_data_size: 1000,
376 ..Ed25519SignatureOffsets::default()
377 };
378 assert_eq!(
379 test_case(1, &offsets),
380 Err(PrecompileError::InvalidDataOffsets)
381 );
382
383 let offsets = Ed25519SignatureOffsets {
384 message_data_offset: u16::MAX,
385 message_data_size: u16::MAX,
386 ..Ed25519SignatureOffsets::default()
387 };
388 assert_eq!(
389 test_case(1, &offsets),
390 Err(PrecompileError::InvalidDataOffsets)
391 );
392 }
393
394 #[test]
395 fn test_pubkey_offset() {
396 let offsets = Ed25519SignatureOffsets {
397 public_key_offset: u16::MAX,
398 ..Ed25519SignatureOffsets::default()
399 };
400 assert_eq!(
401 test_case(1, &offsets),
402 Err(PrecompileError::InvalidDataOffsets)
403 );
404
405 let offsets = Ed25519SignatureOffsets {
406 public_key_offset: 100 - PUBKEY_SERIALIZED_SIZE as u16 + 1,
407 ..Ed25519SignatureOffsets::default()
408 };
409 assert_eq!(
410 test_case(1, &offsets),
411 Err(PrecompileError::InvalidDataOffsets)
412 );
413 }
414
415 #[test]
416 fn test_signature_offset() {
417 let offsets = Ed25519SignatureOffsets {
418 signature_offset: u16::MAX,
419 ..Ed25519SignatureOffsets::default()
420 };
421 assert_eq!(
422 test_case(1, &offsets),
423 Err(PrecompileError::InvalidDataOffsets)
424 );
425
426 let offsets = Ed25519SignatureOffsets {
427 signature_offset: 100 - SIGNATURE_SERIALIZED_SIZE as u16 + 1,
428 ..Ed25519SignatureOffsets::default()
429 };
430 assert_eq!(
431 test_case(1, &offsets),
432 Err(PrecompileError::InvalidDataOffsets)
433 );
434 }
435
436 #[test]
437 fn test_ed25519() {
438 solana_logger::setup();
439
440 let privkey = ed25519_dalek::Keypair::generate(&mut thread_rng());
441 let message_arr = b"hello";
442 let mut instruction = new_ed25519_instruction(&privkey, message_arr);
443 let mint_keypair = Keypair::new();
444 let feature_set = FeatureSet::all_enabled();
445
446 let tx = Transaction::new_signed_with_payer(
447 &[instruction.clone()],
448 Some(&mint_keypair.pubkey()),
449 &[&mint_keypair],
450 Hash::default(),
451 );
452
453 assert!(tx.verify_precompiles(&feature_set).is_ok());
454
455 let index = loop {
456 let index = thread_rng().gen_range(0, instruction.data.len());
457 if index != 1 {
459 break index;
460 }
461 };
462
463 instruction.data[index] = instruction.data[index].wrapping_add(12);
464 let tx = Transaction::new_signed_with_payer(
465 &[instruction],
466 Some(&mint_keypair.pubkey()),
467 &[&mint_keypair],
468 Hash::default(),
469 );
470 assert!(tx.verify_precompiles(&feature_set).is_err());
471 }
472
473 #[test]
474 fn test_offsets_to_ed25519_instruction() {
475 solana_logger::setup();
476
477 let privkey = ed25519_dalek::Keypair::generate(&mut thread_rng());
478 let messages: [&[u8]; 3] = [b"hello", b"IBRL", b"goodbye"];
479 let data_start =
480 messages.len() * SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START;
481 let mut data_offset = data_start + PUBKEY_SERIALIZED_SIZE;
482 let (offsets, messages): (Vec<_>, Vec<_>) = messages
483 .into_iter()
484 .map(|message| {
485 let signature_offset = data_offset;
486 let message_data_offset = signature_offset + SIGNATURE_SERIALIZED_SIZE;
487 data_offset += SIGNATURE_SERIALIZED_SIZE + message.len();
488
489 let offsets = Ed25519SignatureOffsets {
490 signature_offset: signature_offset as u16,
491 signature_instruction_index: u16::MAX,
492 public_key_offset: data_start as u16,
493 public_key_instruction_index: u16::MAX,
494 message_data_offset: message_data_offset as u16,
495 message_data_size: message.len() as u16,
496 message_instruction_index: u16::MAX,
497 };
498
499 (offsets, message)
500 })
501 .unzip();
502
503 let mut instruction = offsets_to_ed25519_instruction(&offsets);
504
505 let pubkey = privkey.public.as_ref();
506 instruction.data.extend_from_slice(pubkey);
507
508 for message in messages {
509 let signature = privkey.sign(message).to_bytes();
510 instruction.data.extend_from_slice(&signature);
511 instruction.data.extend_from_slice(message);
512 }
513
514 let mint_keypair = Keypair::new();
515 let feature_set = FeatureSet::all_enabled();
516
517 let tx = Transaction::new_signed_with_payer(
518 &[instruction.clone()],
519 Some(&mint_keypair.pubkey()),
520 &[&mint_keypair],
521 Hash::default(),
522 );
523
524 assert!(tx.verify_precompiles(&feature_set).is_ok());
525
526 let index = loop {
527 let index = thread_rng().gen_range(0, instruction.data.len());
528 if index != 1 {
530 break index;
531 }
532 };
533
534 instruction.data[index] = instruction.data[index].wrapping_add(12);
535 let tx = Transaction::new_signed_with_payer(
536 &[instruction],
537 Some(&mint_keypair.pubkey()),
538 &[&mint_keypair],
539 Hash::default(),
540 );
541 assert!(tx.verify_precompiles(&feature_set).is_err());
542 }
543
544 #[test]
545 fn test_ed25519_malleability() {
546 solana_logger::setup();
547 let mint_keypair = Keypair::new();
548
549 let privkey = ed25519_dalek::Keypair::generate(&mut thread_rng());
551 let message_arr = b"hello";
552 let instruction = new_ed25519_instruction(&privkey, message_arr);
553 let tx = Transaction::new_signed_with_payer(
554 &[instruction.clone()],
555 Some(&mint_keypair.pubkey()),
556 &[&mint_keypair],
557 Hash::default(),
558 );
559
560 let feature_set = FeatureSet::default();
561 assert!(tx.verify_precompiles(&feature_set).is_ok());
562
563 let feature_set = FeatureSet::all_enabled();
564 assert!(tx.verify_precompiles(&feature_set).is_ok());
565
566 let pubkey =
571 &hex::decode("10eb7c3acfb2bed3e0d6ab89bf5a3d6afddd1176ce4812e38d9fd485058fdb1f")
572 .unwrap();
573 let signature = &hex::decode("00000000000000000000000000000000000000000000000000000000000000009472a69cd9a701a50d130ed52189e2455b23767db52cacb8716fb896ffeeac09").unwrap();
574 let message = b"ed25519vectors 3";
575 let instruction = new_ed25519_instruction_raw(pubkey, signature, message);
576 let tx = Transaction::new_signed_with_payer(
577 &[instruction.clone()],
578 Some(&mint_keypair.pubkey()),
579 &[&mint_keypair],
580 Hash::default(),
581 );
582
583 let feature_set = FeatureSet::default();
584 assert!(tx.verify_precompiles(&feature_set).is_ok());
585
586 let feature_set = FeatureSet::all_enabled();
587 assert!(tx.verify_precompiles(&feature_set).is_err()); }
589}