1#![cfg(feature = "full")]
2
3use {
4 crate::{feature_set::FeatureSet, instruction::Instruction, precompiles::PrecompileError},
5 bytemuck::{bytes_of, Pod, Zeroable},
6 ed25519_dalek::{ed25519::signature::Signature, Signer, Verifier},
7 std::sync::Arc,
8};
9
10pub const PUBKEY_SERIALIZED_SIZE: usize = 32;
11pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
12pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 14;
13pub const SIGNATURE_OFFSETS_START: usize = 2;
15pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START;
16
17#[derive(Default, Debug, Copy, Clone, Zeroable, Pod)]
18#[repr(C)]
19pub struct Ed25519SignatureOffsets {
20 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, }
28
29pub fn new_ed25519_instruction(keypair: &ed25519_dalek::Keypair, message: &[u8]) -> Instruction {
30 let signature = keypair.sign(message).to_bytes();
31 let pubkey = keypair.public.to_bytes();
32
33 assert_eq!(pubkey.len(), PUBKEY_SERIALIZED_SIZE);
34 assert_eq!(signature.len(), SIGNATURE_SERIALIZED_SIZE);
35
36 let mut instruction_data = Vec::with_capacity(
37 DATA_START
38 .saturating_add(SIGNATURE_SERIALIZED_SIZE)
39 .saturating_add(PUBKEY_SERIALIZED_SIZE)
40 .saturating_add(message.len()),
41 );
42
43 let num_signatures: u8 = 1;
44 let public_key_offset = DATA_START;
45 let signature_offset = public_key_offset.saturating_add(PUBKEY_SERIALIZED_SIZE);
46 let message_data_offset = signature_offset.saturating_add(SIGNATURE_SERIALIZED_SIZE);
47
48 instruction_data.extend_from_slice(bytes_of(&[num_signatures, 0]));
50
51 let offsets = Ed25519SignatureOffsets {
52 signature_offset: signature_offset as u16,
53 signature_instruction_index: u16::MAX,
54 public_key_offset: public_key_offset as u16,
55 public_key_instruction_index: u16::MAX,
56 message_data_offset: message_data_offset as u16,
57 message_data_size: message.len() as u16,
58 message_instruction_index: u16::MAX,
59 };
60
61 instruction_data.extend_from_slice(bytes_of(&offsets));
62
63 debug_assert_eq!(instruction_data.len(), public_key_offset);
64
65 instruction_data.extend_from_slice(&pubkey);
66
67 debug_assert_eq!(instruction_data.len(), signature_offset);
68
69 instruction_data.extend_from_slice(&signature);
70
71 debug_assert_eq!(instruction_data.len(), message_data_offset);
72
73 instruction_data.extend_from_slice(message);
74
75 Instruction {
76 program_id: solana_sdk::ed25519_program::id(),
77 accounts: vec![],
78 data: instruction_data,
79 }
80}
81
82pub fn verify(
83 data: &[u8],
84 instruction_datas: &[&[u8]],
85 _feature_set: &Arc<FeatureSet>,
86) -> Result<(), PrecompileError> {
87 if data.len() < SIGNATURE_OFFSETS_START {
88 return Err(PrecompileError::InvalidInstructionDataSize);
89 }
90 let num_signatures = data[0] as usize;
91 if num_signatures == 0 && data.len() > SIGNATURE_OFFSETS_START {
92 return Err(PrecompileError::InvalidInstructionDataSize);
93 }
94 let expected_data_size = num_signatures
95 .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
96 .saturating_add(SIGNATURE_OFFSETS_START);
97 if data.len() < expected_data_size {
99 return Err(PrecompileError::InvalidInstructionDataSize);
100 }
101 for i in 0..num_signatures {
102 let start = i
103 .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
104 .saturating_add(SIGNATURE_OFFSETS_START);
105 let end = start.saturating_add(SIGNATURE_OFFSETS_SERIALIZED_SIZE);
106
107 let offsets: &Ed25519SignatureOffsets = bytemuck::try_from_bytes(&data[start..end])
109 .map_err(|_| PrecompileError::InvalidDataOffsets)?;
110
111 let signature = get_data_slice(
113 data,
114 instruction_datas,
115 offsets.signature_instruction_index,
116 offsets.signature_offset,
117 SIGNATURE_SERIALIZED_SIZE,
118 )?;
119
120 let signature =
121 Signature::from_bytes(signature).map_err(|_| PrecompileError::InvalidSignature)?;
122
123 let pubkey = get_data_slice(
125 data,
126 instruction_datas,
127 offsets.public_key_instruction_index,
128 offsets.public_key_offset,
129 PUBKEY_SERIALIZED_SIZE,
130 )?;
131
132 let publickey = ed25519_dalek::PublicKey::from_bytes(pubkey)
133 .map_err(|_| PrecompileError::InvalidPublicKey)?;
134
135 let message = get_data_slice(
137 data,
138 instruction_datas,
139 offsets.message_instruction_index,
140 offsets.message_data_offset,
141 offsets.message_data_size as usize,
142 )?;
143
144 publickey
145 .verify(message, &signature)
146 .map_err(|_| PrecompileError::InvalidSignature)?;
147 }
148 Ok(())
149}
150
151fn get_data_slice<'a>(
152 data: &'a [u8],
153 instruction_datas: &'a [&[u8]],
154 instruction_index: u16,
155 offset_start: u16,
156 size: usize,
157) -> Result<&'a [u8], PrecompileError> {
158 let instruction = if instruction_index == u16::MAX {
159 data
160 } else {
161 let signature_index = instruction_index as usize;
162 if signature_index >= instruction_datas.len() {
163 return Err(PrecompileError::InvalidDataOffsets);
164 }
165 instruction_datas[signature_index]
166 };
167
168 let start = offset_start as usize;
169 let end = start.saturating_add(size);
170 if end > instruction.len() {
171 return Err(PrecompileError::InvalidDataOffsets);
172 }
173
174 Ok(&instruction[start..end])
175}
176
177#[cfg(test)]
178pub mod test {
179 use {
180 super::*,
181 crate::{
182 ed25519_instruction::new_ed25519_instruction,
183 feature_set::FeatureSet,
184 hash::Hash,
185 signature::{Keypair, Signer},
186 transaction::Transaction,
187 },
188 rand::{thread_rng, Rng},
189 std::sync::Arc,
190 };
191
192 fn test_case(
193 num_signatures: u16,
194 offsets: &Ed25519SignatureOffsets,
195 ) -> Result<(), PrecompileError> {
196 assert_eq!(
197 bytemuck::bytes_of(offsets).len(),
198 SIGNATURE_OFFSETS_SERIALIZED_SIZE
199 );
200
201 let mut instruction_data = vec![0u8; DATA_START];
202 instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&num_signatures));
203 instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(offsets));
204
205 verify(
206 &instruction_data,
207 &[&[0u8; 100]],
208 &Arc::new(FeatureSet::all_enabled()),
209 )
210 }
211
212 #[test]
213 fn test_invalid_offsets() {
214 solana_logger::setup();
215
216 let mut instruction_data = vec![0u8; DATA_START];
217 let offsets = Ed25519SignatureOffsets::default();
218 instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&1u16));
219 instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(&offsets));
220 instruction_data.truncate(instruction_data.len() - 1);
221
222 assert_eq!(
223 verify(
224 &instruction_data,
225 &[&[0u8; 100]],
226 &Arc::new(FeatureSet::all_enabled()),
227 ),
228 Err(PrecompileError::InvalidInstructionDataSize)
229 );
230
231 let offsets = Ed25519SignatureOffsets {
232 signature_instruction_index: 1,
233 ..Ed25519SignatureOffsets::default()
234 };
235 assert_eq!(
236 test_case(1, &offsets),
237 Err(PrecompileError::InvalidDataOffsets)
238 );
239
240 let offsets = Ed25519SignatureOffsets {
241 message_instruction_index: 1,
242 ..Ed25519SignatureOffsets::default()
243 };
244 assert_eq!(
245 test_case(1, &offsets),
246 Err(PrecompileError::InvalidDataOffsets)
247 );
248
249 let offsets = Ed25519SignatureOffsets {
250 public_key_instruction_index: 1,
251 ..Ed25519SignatureOffsets::default()
252 };
253 assert_eq!(
254 test_case(1, &offsets),
255 Err(PrecompileError::InvalidDataOffsets)
256 );
257 }
258
259 #[test]
260 fn test_message_data_offsets() {
261 let offsets = Ed25519SignatureOffsets {
262 message_data_offset: 99,
263 message_data_size: 1,
264 ..Ed25519SignatureOffsets::default()
265 };
266 assert_eq!(
267 test_case(1, &offsets),
268 Err(PrecompileError::InvalidSignature)
269 );
270
271 let offsets = Ed25519SignatureOffsets {
272 message_data_offset: 100,
273 message_data_size: 1,
274 ..Ed25519SignatureOffsets::default()
275 };
276 assert_eq!(
277 test_case(1, &offsets),
278 Err(PrecompileError::InvalidDataOffsets)
279 );
280
281 let offsets = Ed25519SignatureOffsets {
282 message_data_offset: 100,
283 message_data_size: 1000,
284 ..Ed25519SignatureOffsets::default()
285 };
286 assert_eq!(
287 test_case(1, &offsets),
288 Err(PrecompileError::InvalidDataOffsets)
289 );
290
291 let offsets = Ed25519SignatureOffsets {
292 message_data_offset: std::u16::MAX,
293 message_data_size: std::u16::MAX,
294 ..Ed25519SignatureOffsets::default()
295 };
296 assert_eq!(
297 test_case(1, &offsets),
298 Err(PrecompileError::InvalidDataOffsets)
299 );
300 }
301
302 #[test]
303 fn test_pubkey_offset() {
304 let offsets = Ed25519SignatureOffsets {
305 public_key_offset: std::u16::MAX,
306 ..Ed25519SignatureOffsets::default()
307 };
308 assert_eq!(
309 test_case(1, &offsets),
310 Err(PrecompileError::InvalidDataOffsets)
311 );
312
313 let offsets = Ed25519SignatureOffsets {
314 public_key_offset: 100 - PUBKEY_SERIALIZED_SIZE as u16 + 1,
315 ..Ed25519SignatureOffsets::default()
316 };
317 assert_eq!(
318 test_case(1, &offsets),
319 Err(PrecompileError::InvalidDataOffsets)
320 );
321 }
322
323 #[test]
324 fn test_signature_offset() {
325 let offsets = Ed25519SignatureOffsets {
326 signature_offset: std::u16::MAX,
327 ..Ed25519SignatureOffsets::default()
328 };
329 assert_eq!(
330 test_case(1, &offsets),
331 Err(PrecompileError::InvalidDataOffsets)
332 );
333
334 let offsets = Ed25519SignatureOffsets {
335 signature_offset: 100 - SIGNATURE_SERIALIZED_SIZE as u16 + 1,
336 ..Ed25519SignatureOffsets::default()
337 };
338 assert_eq!(
339 test_case(1, &offsets),
340 Err(PrecompileError::InvalidDataOffsets)
341 );
342 }
343
344 #[test]
345 fn test_ed25519() {
346 solana_logger::setup();
347
348 let privkey = ed25519_dalek::Keypair::generate(&mut thread_rng());
349 let message_arr = b"hello";
350 let mut instruction = new_ed25519_instruction(&privkey, message_arr);
351 let mint_keypair = Keypair::new();
352 let feature_set = Arc::new(FeatureSet::all_enabled());
353
354 let tx = Transaction::new_signed_with_payer(
355 &[instruction.clone()],
356 Some(&mint_keypair.pubkey()),
357 &[&mint_keypair],
358 Hash::default(),
359 );
360
361 assert!(tx.verify_precompiles(&feature_set).is_ok());
362
363 let index = loop {
364 let index = thread_rng().gen_range(0, instruction.data.len());
365 if index != 1 {
367 break index;
368 }
369 };
370
371 instruction.data[index] = instruction.data[index].wrapping_add(12);
372 let tx = Transaction::new_signed_with_payer(
373 &[instruction],
374 Some(&mint_keypair.pubkey()),
375 &[&mint_keypair],
376 Hash::default(),
377 );
378 assert!(tx.verify_precompiles(&feature_set).is_err());
379 }
380}