solana_sdk/
ed25519_instruction.rs

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;
13// bytemuck requires structures to be aligned
14pub 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,             // offset to ed25519 signature of 64 bytes
21    signature_instruction_index: u16,  // instruction index to find signature
22    public_key_offset: u16,            // offset to public key of 32 bytes
23    public_key_instruction_index: u16, // instruction index to find public key
24    message_data_offset: u16,          // offset to start of message data
25    message_data_size: u16,            // size of message data
26    message_instruction_index: u16,    // index of instruction data to get message data
27}
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    // add padding byte so that offset structure is aligned
49    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    // We do not check or use the byte at data[1]
98    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        // bytemuck wants structures aligned
108        let offsets: &Ed25519SignatureOffsets = bytemuck::try_from_bytes(&data[start..end])
109            .map_err(|_| PrecompileError::InvalidDataOffsets)?;
110
111        // Parse out signature
112        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        // Parse out pubkey
124        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        // Parse out message
136        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            // byte 1 is not used, so this would not cause the verify to fail
366            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}