solana_ed25519_program/
lib.rs

1//! Instructions for the [ed25519 native program][np].
2//!
3//! [np]: https://docs.solanalabs.com/runtime/programs#ed25519-program
4
5use {
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;
16// bytemuck requires structures to be aligned
17pub 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, // offset to ed25519 signature of 64 bytes
24    pub signature_instruction_index: u16, // instruction index to find signature
25    pub public_key_offset: u16, // offset to public key of 32 bytes
26    pub public_key_instruction_index: u16, // instruction index to find public key
27    pub message_data_offset: u16, // offset to start of message data
28    pub message_data_size: u16, // size of message data
29    pub message_instruction_index: u16, // index of instruction data to get message data
30}
31
32/// Encode just the signature offsets in a single ed25519 instruction.
33///
34/// This is a convenience function for rare cases where we wish to verify multiple messages in
35/// the same instruction. The verification data can be stored in a separate instruction specified
36/// by the `*_instruction_index` fields of `offsets`, or in this instruction by extending the data
37/// buffer.
38///
39/// Note: If the signer for these messages are the same, it is cheaper to concatenate the messages
40/// and have the signer sign the single buffer and use [`new_ed25519_instruction_with_signature`].
41pub 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    // add padding byte so that offset structure is aligned
86    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    // We do not check or use the byte at data[1]
140    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        // bytemuck wants structures aligned
150        let offsets: &Ed25519SignatureOffsets = bytemuck::try_from_bytes(&data[start..end])
151            .map_err(|_| PrecompileError::InvalidDataOffsets)?;
152
153        // Parse out signature
154        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        // Parse out pubkey
166        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        // Parse out message
178        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        // add padding byte so that offset structure is aligned
261        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            // byte 1 is not used, so this would not cause the verify to fail
471            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            // byte 1 is not used, so this would not cause the verify to fail
542            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        // sig created via ed25519_dalek: both pass
563        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        // malleable sig: verify_strict does NOT pass
580        // for example, test number 5:
581        // https://github.com/C2SP/CCTV/tree/main/ed25519
582        // R has low order (in fact R == 0)
583        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()); // verify_strict does NOT pass
601    }
602}