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