solana_sdk/
ed25519_instruction.rs

1//! Instructions for the [ed25519 native program][np].
2//!
3//! [np]: https://docs.solanalabs.com/runtime/programs#ed25519-program
4
5#![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;
19// bytemuck requires structures to be aligned
20pub 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,             // offset to ed25519 signature of 64 bytes
27    signature_instruction_index: u16,  // instruction index to find signature
28    public_key_offset: u16,            // offset to public key of 32 bytes
29    public_key_instruction_index: u16, // instruction index to find public key
30    message_data_offset: u16,          // offset to start of message data
31    message_data_size: u16,            // size of message data
32    message_instruction_index: u16,    // index of instruction data to get message data
33}
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    // add padding byte so that offset structure is aligned
55    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    // We do not check or use the byte at data[1]
104    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        // bytemuck wants structures aligned
114        let offsets: &Ed25519SignatureOffsets = bytemuck::try_from_bytes(&data[start..end])
115            .map_err(|_| PrecompileError::InvalidDataOffsets)?;
116
117        // Parse out signature
118        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        // Parse out pubkey
130        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        // Parse out message
142        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        // add padding byte so that offset structure is aligned
225        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            // byte 1 is not used, so this would not cause the verify to fail
432            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        // sig created via ed25519_dalek: both pass
453        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        // malleable sig: verify_strict does NOT pass
470        // for example, test number 5:
471        // https://github.com/C2SP/CCTV/tree/main/ed25519
472        // R has low order (in fact R == 0)
473        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()); // verify_strict does NOT pass
491    }
492}