Skip to main content

solana_native_sigverify/
lib.rs

1// solana-native-sigverify — Solana library for interacting with Solana native
2//                           signature verification programs and with
3//                           solana-sigverify program.
4// © 2024 by Composable Foundation
5// © 2025 by Michał Nazarewicz <mina86@mina86.com>
6//
7// This program is free software; you can redistribute it and/or modify it under
8// the terms of the GNU General Public License as published by the Free Software
9// Foundation; either version 2 of the License, or (at your option) any later
10// version.
11//
12// This program is distributed in the hope that it will be useful, but WITHOUT
13// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
15// details.
16//
17// You should have received a copy of the GNU General Public License along with
18// this program; if not, see <https://www.gnu.org/licenses/>.
19
20//! Utilities for creating and parsing native signature verification program
21//! instruction data.
22//!
23//! Solana runtime provides native programs for performing signature
24//! verification (henceforth referred to as native signature verification
25//! programs).  Unfortunately, interface for interfacing with those programs is
26//! rather lacking.
27//!
28//! This crate offers functions for creating instruction calling the native
29//! signature verification programs as well as parsing their instruction data.
30
31use solana_program::instruction::Instruction;
32use solana_program::pubkey::Pubkey;
33
34mod stdx;
35
36
37/// Offsets used in instruction data of native signature verification programs.
38///
39/// This is a low-level structure.  Typically you’d want to use higher level
40/// interface: [`new_instruction`] for creating instruction calling the native
41/// signature verification program or [`parse_data`] for parsing its instruction
42/// data.
43///
44/// All integers are stored as little-endian.
45// Copied from but we’re using
46// https://github.com/solana-labs/solana/blob/master/sdk/src/ed25519_instruction.rs
47#[derive(Copy, Clone, bytemuck::Zeroable, bytemuck::Pod)]
48#[repr(C)]
49pub struct SignatureOffsets {
50    pub signature_offset: u16, // offset to ed25519 signature of 64 bytes
51    pub signature_instruction_index: u16, // instruction index to find signature
52    pub pubkey_offset: u16,    // offset to public key of 32 bytes
53    pub pubkey_instruction_index: u16, // instruction index to find public key
54    pub message_offset: u16,   // offset to start of message data
55    pub message_size: u16,     // size of message data
56    pub message_instruction_index: u16, // index of instruction data to get message data
57}
58
59const OFF_SIZE: usize = core::mem::size_of::<SignatureOffsets>();
60
61
62/// A parse signature from the Ed25519 native program.
63#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub struct Entry<'a> {
65    pub signature: &'a [u8; 64],
66    pub pubkey: &'a [u8; 32],
67    pub message: &'a [u8],
68}
69
70
71/// Address of the Ed25519 native program.
72pub const ED25519_PROGRAM_ID: Pubkey = solana_program::ed25519_program::ID;
73/// Address of the Secp255k1 native program.
74pub const SECP256K1_PROGRAM_ID: Pubkey = solana_program::secp256k1_program::ID;
75/// Address of the Secp255r1 native program.
76// This isn’t defined in solana-program 1.18 but documentation lists it, see
77// <https://solana.com/docs/core/programs#secp256r1-program>.
78pub const SECP256R1_PROGRAM_ID: Pubkey =
79    solana_program::pubkey!("Secp256r1SigVerify1111111111111111111111111");
80
81
82/// Creates an instruction calling a native signature verification program.
83///
84/// `program_id` specifies the address of the signature verification program and
85/// typically is one of [`ED25519_PROGRAM_ID`], [`SECP256K1_PROGRAM_ID`] or
86/// [`SECP256R1_PROGRAM_ID`].  The function can be used for other signature
87/// verification programs so long as they use the same calling convention.
88///
89/// See [`new_instruction_data`] for possible error conditions and notes about
90/// space optimisation.
91pub fn new_instruction(
92    program_id: Pubkey,
93    entries: &[Entry],
94) -> Option<Instruction> {
95    let data = new_instruction_data(entries)?;
96    Some(Instruction { program_id, accounts: Vec::new(), data })
97}
98
99
100/// Creates instruction data for a call of a native signature verification
101/// program.
102///
103/// Returns `None` if there are more than 255 entries or message length of any
104/// entry is longer than 65535 bytes.  However, observe that Solana upper limit
105/// for instruction data is about 1100 (lower in practice).  This function does
106/// not check this size limit and may return instruction data which don’t fit in
107/// a Solana transaction.
108///
109/// Tries to conserve space by reusing messages and public keys if possible.  In
110/// current implementation this is done in two ways.  Firstly, if the same
111/// public key is used for multiple signatures, that public key is included in
112/// instruction data only once.  Secondly, if a later message is a prefix of an
113/// earlier one, the message isn’t included for the second time.
114///
115/// The second optimisation doesn’t work if signature for a prefix is earlier in
116/// the `entries` than the full message.  Depending on the nature of the
117/// entries, it may be useful to sort them by the message length (starting from
118/// the longest message) to maximise space optimisation potential.
119pub fn new_instruction_data(entries: &[Entry]) -> Option<Vec<u8>> {
120    u8::try_from(entries.len()).ok()?;
121
122    // Calculate the length of the instruction.  If we manage to deduplicate
123    // messages we may end up with something shorter.  This is the largest we
124    // may possibly use.
125    let mut capacity = (2 + (OFF_SIZE + 64 + 32) * entries.len()) as u16;
126    for entry in entries {
127        let len = u16::try_from(entry.message.len()).ok()?;
128        capacity = capacity.checked_add(len)?;
129    }
130
131    let mut data = Vec::with_capacity(usize::from(capacity));
132    let len = write_instruction_data(data.spare_capacity_mut(), entries);
133    // SAFETY: Per interface of write_instruction_data, all data up to len bytes
134    // have been initialised.
135    unsafe { data.set_len(len) };
136
137    Some(data)
138}
139
140fn write_instruction_data(
141    dst: &mut [core::mem::MaybeUninit<u8>],
142    entries: &[Entry],
143) -> usize {
144    // The structure of the instruction data is:
145    //   count:   u8
146    //   zero:    u8
147    //   entries: [SignatureOffsets; count]
148    //   data:    [u8]
149    dst[0].write(entries.len() as u8);
150    dst[1].write(0);
151
152    let mut len = 2 + entries.len() * OFF_SIZE;
153    let (head, mut dst) = dst.split_at_mut(len);
154    let (entries_dst, rest) =
155        stdx::as_chunks_mut::<{ OFF_SIZE }, _>(&mut head[2..]);
156    assert_eq!((entries.len(), 0), (entries_dst.len(), rest.len()));
157
158    macro_rules! append {
159        ($slice:expr) => {{
160            let (head, tail) = dst.split_at_mut($slice.len());
161            stdx::write_slice(head, $slice);
162            dst = tail;
163            let ret = len;
164            len += $slice.len();
165            ret as u16
166        }};
167    }
168
169    for idx in 0..entries.len() {
170        let Entry { signature, pubkey, message } = entries[idx];
171
172        // Append message but deduplicate if the message has already been used
173        // or the message is prefix of a message which has already been used.
174        let pos = entries[..idx]
175            .iter()
176            .position(|ent| ent.message.starts_with(message));
177        let message_offset = if let Some(pos) = pos {
178            let offsets = &entries_dst[pos];
179            // SAFETY: All offsets prior to idx have been initialised.
180            u16::from_le_bytes(unsafe {
181                [offsets[8].assume_init(), offsets[9].assume_init()]
182            })
183        } else {
184            append!(message)
185        };
186
187        // Append signature.
188        let signature_offset = append!(signature);
189
190        // Append pubkey, but deduplicate if the key has already been used.
191        let pos = entries[..idx].iter().position(|ent| ent.pubkey == pubkey);
192        let pubkey_offset = if let Some(pos) = pos {
193            let offsets = &entries_dst[pos];
194            // SAFETY: All offsets prior to idx have been initialised.
195            u16::from_le_bytes(unsafe {
196                [offsets[4].assume_init(), offsets[5].assume_init()]
197            })
198        } else {
199            append!(pubkey)
200        };
201
202        // Fill in the entry.
203        let offsets = SignatureOffsets {
204            signature_offset: u16::from_le(signature_offset),
205            signature_instruction_index: u16::MAX,
206            pubkey_offset: u16::from_le(pubkey_offset),
207            pubkey_instruction_index: u16::MAX,
208            message_offset: u16::from_le(message_offset),
209            message_size: message.len() as u16,
210            message_instruction_index: u16::MAX,
211        };
212        stdx::write_slice(&mut entries_dst[idx], bytemuck::bytes_of(&offsets));
213    }
214
215    len
216}
217
218
219/// Creates a new iterator over signatures in given native signature
220/// verification program instruction data.
221///
222/// `data` is the instruction data for the program call.  This is typically
223/// fetched from the instructions sysvar account.  The format of the data is:
224///
225/// ```ignore
226/// count:   u8
227/// unused:  u8
228/// offsets: [SignatureOffsets; count]
229/// rest:    [u8]
230/// ```
231///
232/// The way to parse the instruction data is to read count from the first byte,
233/// verify the second byte is zero and then iterate over the next count 14-byte
234/// blocks passing them to this method.
235///
236/// The iterator does *not* support fetching keys, signatures or messages from
237/// other instructions (which is something native signature verification
238/// programs support) and if that feature is used such entries will be reported
239/// as [`Error::UnsupportedFeature`] errors.
240///
241/// Returns [`Error::BadData`] if the data is malformed.
242pub fn parse_data<'a>(data: &'a [u8]) -> Result<Iter<'a>, BadData> {
243    match stdx::split_at::<2, u8>(data) {
244        Some(([count, 0], rest)) => {
245            stdx::as_chunks::<14, u8>(rest).0.get(..usize::from(*count))
246        }
247        _ => None,
248    }
249    .map(|entries| Iter { entries: entries.iter(), data })
250    .ok_or(BadData)
251}
252
253/// Iterator over signatures present in native signature verification program
254/// instruction data.
255#[derive(Clone, Debug)]
256pub struct Iter<'a> {
257    entries: core::slice::Iter<'a, [u8; 14]>,
258    data: &'a [u8],
259}
260
261impl<'a> core::iter::Iterator for Iter<'a> {
262    type Item = Result<Entry<'a>, Error>;
263
264    fn next(&mut self) -> Option<Self::Item> {
265        let entry = self.entries.next()?;
266        Some(decode_entry(self.data, entry))
267    }
268
269    fn last(self) -> Option<Self::Item> {
270        let entry = self.entries.last()?;
271        Some(decode_entry(self.data, entry))
272    }
273
274    fn nth(&mut self, n: usize) -> Option<Self::Item> {
275        let entry = self.entries.nth(n)?;
276        Some(decode_entry(self.data, entry))
277    }
278
279    fn size_hint(&self) -> (usize, Option<usize>) { self.entries.size_hint() }
280    fn count(self) -> usize { self.entries.count() }
281}
282
283impl core::iter::ExactSizeIterator for Iter<'_> {
284    fn len(&self) -> usize { self.entries.len() }
285}
286
287impl core::iter::DoubleEndedIterator for Iter<'_> {
288    fn next_back(&mut self) -> Option<Self::Item> {
289        let entry = self.entries.next_back()?;
290        Some(decode_entry(self.data, entry))
291    }
292
293    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
294        let entry = self.entries.nth_back(n)?;
295        Some(decode_entry(self.data, entry))
296    }
297}
298
299
300/// Error when parsing a signature.
301#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
302pub enum Error {
303    /// Signature entry references data from other instructions which is
304    /// currently unsupported.
305    UnsupportedFeature,
306
307    /// Signature entry is malformed.
308    ///
309    /// Such entries should cause the native signature verification program
310    /// instruction to fail so this should never happen when parsing past
311    /// instructions of current transaction.
312    BadData,
313}
314
315#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
316pub struct BadData;
317
318impl From<BadData> for Error {
319    fn from(_: BadData) -> Self { Self::BadData }
320}
321
322impl From<BadData> for solana_program::program_error::ProgramError {
323    fn from(_: BadData) -> Self { Self::InvalidInstructionData }
324}
325
326impl From<Error> for solana_program::program_error::ProgramError {
327    fn from(_: Error) -> Self { Self::InvalidInstructionData }
328}
329
330
331/// Decodes signature entry from the instruction data.
332///
333/// `data` is the entire instruction data for the native signature verification
334/// program call and `entry` is one of the signature offsets entry from that
335/// instruction data.
336fn decode_entry<'a>(
337    data: &'a [u8],
338    entry: &'a [u8; 14],
339) -> Result<Entry<'a>, Error> {
340    let entry: &[[u8; 2]; 7] = bytemuck::must_cast_ref(entry);
341    let entry = entry.map(u16::from_le_bytes);
342    let entry: SignatureOffsets = bytemuck::must_cast(entry);
343
344    if entry.signature_instruction_index != u16::MAX ||
345        entry.pubkey_instruction_index != u16::MAX ||
346        entry.message_instruction_index != u16::MAX
347    {
348        return Err(Error::UnsupportedFeature);
349    }
350
351    fn get_array<const N: usize>(data: &[u8], offset: u16) -> Option<&[u8; N]> {
352        Some(stdx::split_at::<N, u8>(data.get(usize::from(offset)..)?)?.0)
353    }
354
355    (|| {
356        let signature = get_array::<64>(data, entry.signature_offset)?;
357        let pubkey = get_array::<32>(data, entry.pubkey_offset)?;
358        let message = data
359            .get(usize::from(entry.message_offset)..)?
360            .get(..usize::from(entry.message_size))?;
361        Some(Entry { signature, pubkey, message })
362    })()
363    .ok_or(Error::BadData)
364}
365
366
367#[cfg(test)]
368mod test {
369    use ed25519_dalek::Signer;
370    use solana_ed25519_program::new_ed25519_instruction_with_signature;
371
372    use super::*;
373
374    macro_rules! make_test {
375        ($name:ident;
376         let $ctx:ident = $prepare:expr;
377         $make_data:expr;
378         $($entry:expr),* $(,)?
379        ) => {
380            mod $name {
381                use super::*;
382
383                #[test]
384                fn test_iter() {
385                    let $ctx = $prepare;
386                    let entries = [$($entry),*];
387                    let data = $make_data;
388                    let mut iter = parse_data(data.as_slice()).unwrap();
389                    for want in entries {
390                        assert_eq!(Some(Ok(want)), iter.next());
391                    }
392                    assert_eq!(None, iter.next());
393                }
394
395                #[test]
396                fn test_iter_new_instruction() {
397                    let $ctx = $prepare;
398                    let entries = [$($entry),*];
399                    let data = new_instruction_data(&entries).unwrap();
400
401                    let mut iter = parse_data(data.as_slice()).unwrap();
402                    for want in entries {
403                        assert_eq!(Some(Ok(want)), iter.next());
404                    }
405                    assert_eq!(None, iter.next());
406                }
407
408                #[test]
409                fn test_verify_new_instruction() {
410                    let $ctx = $prepare;
411                    let entries = [$($entry),*];
412                    let mut data = new_instruction_data(&entries).unwrap();
413
414                    // solana_sdk::ed25519_instruction::verify requires data to
415                    // be aligned to two bytes.  data is Vec<u8> so we can’t
416                    // control alignment but we can pad to get alignment we
417                    // need.
418                    let data = if data.as_ptr() as usize % 2 == 0 {
419                        data.as_slice()
420                    } else {
421                        data.insert(0, 0);
422                        &data[1..]
423                    };
424
425                    // Verify
426                    #[allow(deprecated)]
427                    solana_ed25519_program::verify(
428                        data,
429                        &[data],
430                        &Default::default(),
431                    ).unwrap();
432                }
433
434                #[test]
435                #[cfg(not(miri))]
436                fn test_new_instruction_snapshot() {
437                    let $ctx = $prepare;
438                    let entries = [$($entry),*];
439                    let data = new_instruction_data(&entries).unwrap();
440                    insta::assert_debug_snapshot!(data.as_slice());
441                }
442            }
443        }
444    }
445
446    const SECRETKEY1: [u8; 32] = [
447        99, 241, 33, 162, 28, 57, 15, 190, 246, 156, 30, 188, 100, 125, 110,
448        174, 37, 123, 198, 137, 90, 220, 247, 230, 191, 238, 71, 217, 207, 176,
449        67, 112,
450    ];
451
452    fn make_signature(
453        message: &[u8],
454        secretkey: &[u8; 32],
455    ) -> ([u8; 64], [u8; 32]) {
456        let secretkey = ed25519_dalek::SigningKey::from_bytes(secretkey);
457        let signature = secretkey.sign(message).to_bytes();
458        (signature, secretkey.verifying_key().to_bytes())
459    }
460
461    make_test! {
462        single_signature;
463        let ctx = make_signature(b"message", &SECRETKEY1);
464        new_ed25519_instruction_with_signature(b"message", &ctx.0, &ctx.1).data;
465        Entry { signature: &ctx.0, pubkey: &ctx.1, message: b"message" }
466    }
467
468    fn prepare_two_signatures_test(
469        msg1: &[u8],
470        msg2: &[u8],
471        secretkey2: &[u8; 32],
472    ) -> ([u8; 64], [u8; 32], [u8; 64], [u8; 32], Vec<u8>) {
473        const SIG_SIZE: u16 = 64;
474        const KEY_SIZE: u16 = 32;
475        const HEADER_SIZE: u16 = 2 + 2 * 14;
476        let first_offset = HEADER_SIZE;
477        let second_offset =
478            HEADER_SIZE + SIG_SIZE + KEY_SIZE + msg1.len() as u16;
479
480        #[rustfmt::skip]
481        let header = [
482            2,
483
484            /* sig offset: */ first_offset,
485            /* sig_ix_idx: */ u16::MAX,
486            /* key_offset: */ first_offset + SIG_SIZE,
487            /* key_ix_idx: */ u16::MAX,
488            /* msg_offset: */ first_offset + SIG_SIZE + KEY_SIZE,
489            /* msg_size:   */ msg1.len() as u16,
490            /* msg_ix_idx: */ u16::MAX,
491
492            /* sig offset: */ second_offset,
493            /* sig_ix_idx: */ u16::MAX,
494            /* key_offset: */ second_offset + SIG_SIZE,
495            /* key_ix_idx: */ u16::MAX,
496            /* msg_offset: */ second_offset + SIG_SIZE + KEY_SIZE,
497            /* msg_size:   */ msg2.len() as u16,
498            /* msg_ix_idx: */ u16::MAX,
499        ];
500
501        let (sig1, pubkey1) = make_signature(msg1, &SECRETKEY1);
502        let (sig2, pubkey2) = make_signature(msg2, secretkey2);
503
504        let data = [
505            bytemuck::bytes_of(&header),
506            sig1.as_ref(),
507            pubkey1.as_ref(),
508            msg1,
509            sig2.as_ref(),
510            pubkey2.as_ref(),
511            msg2,
512        ]
513        .concat();
514
515        (sig1, pubkey1, sig2, pubkey2, data)
516    }
517
518    make_test! {
519        two_signatures;
520        let ctx = prepare_two_signatures_test(b"foo", b"bar", &SECRETKEY1);
521        ctx.4;
522        Entry { signature: &ctx.0, pubkey: &ctx.1, message: b"foo" },
523        Entry { signature: &ctx.2, pubkey: &ctx.3, message: b"bar" }
524    }
525
526    make_test! {
527        two_signatures_same_message;
528        let ctx = prepare_two_signatures_test(b"foo", b"foo", &SECRETKEY1);
529        ctx.4;
530        Entry { signature: &ctx.0, pubkey: &ctx.1, message: b"foo" },
531        Entry { signature: &ctx.2, pubkey: &ctx.3, message: b"foo" }
532    }
533
534    make_test! {
535        two_signatures_prefix_message;
536        let ctx = prepare_two_signatures_test(b"foo", b"fo", &SECRETKEY1);
537        ctx.4;
538        Entry { signature: &ctx.0, pubkey: &ctx.1, message: b"foo" },
539        Entry { signature: &ctx.2, pubkey: &ctx.3, message: b"fo" }
540    }
541
542    const SECRETKEY2: [u8; 32] = [
543        157, 97, 177, 157, 239, 253, 90, 96, 186, 132, 74, 244, 146, 236, 44,
544        196, 68, 73, 197, 105, 123, 50, 105, 25, 112, 59, 172, 3, 28, 174, 127,
545        96,
546    ];
547
548    make_test! {
549        two_signatures_diff_keys;
550        let ctx = prepare_two_signatures_test(b"foo", b"bar", &SECRETKEY2);
551        ctx.4;
552        Entry { signature: &ctx.0, pubkey: &ctx.1, message: b"foo" },
553        Entry { signature: &ctx.2, pubkey: &ctx.3, message: b"bar" }
554    }
555
556    make_test! {
557        two_signatures_same_message_diff_keys;
558        let ctx = prepare_two_signatures_test(b"foo", b"foo", &SECRETKEY2);
559        ctx.4;
560        Entry { signature: &ctx.0, pubkey: &ctx.1, message: b"foo" },
561        Entry { signature: &ctx.2, pubkey: &ctx.3, message: b"foo" }
562    }
563
564    make_test! {
565        two_signatures_prefix_message_diff_keys;
566        let ctx = prepare_two_signatures_test(b"foo", b"fo", &SECRETKEY2);
567        ctx.4;
568        Entry { signature: &ctx.0, pubkey: &ctx.1, message: b"foo" },
569        Entry { signature: &ctx.2, pubkey: &ctx.3, message: b"fo" }
570    }
571}