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