1use solana_program::instruction::Instruction;
32use solana_program::pubkey::Pubkey;
33
34mod stdx;
35
36
37#[derive(Copy, Clone, bytemuck::Zeroable, bytemuck::Pod)]
48#[repr(C)]
49pub struct SignatureOffsets {
50 pub signature_offset: u16, pub signature_instruction_index: u16, pub pubkey_offset: u16, pub pubkey_instruction_index: u16, pub message_offset: u16, pub message_size: u16, pub message_instruction_index: u16, }
58
59const OFF_SIZE: usize = core::mem::size_of::<SignatureOffsets>();
60
61
62#[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
71pub const ED25519_PROGRAM_ID: Pubkey = solana_program::ed25519_program::ID;
73pub const SECP256K1_PROGRAM_ID: Pubkey = solana_program::secp256k1_program::ID;
75pub const SECP256R1_PROGRAM_ID: Pubkey =
79 solana_program::pubkey!("Secp256r1SigVerify1111111111111111111111111");
80
81
82pub 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
100pub fn new_instruction_data(entries: &[Entry]) -> Option<Vec<u8>> {
120 u8::try_from(entries.len()).ok()?;
121
122 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 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 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 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 u16::from_le_bytes(unsafe {
181 [offsets[8].assume_init(), offsets[9].assume_init()]
182 })
183 } else {
184 append!(message)
185 };
186
187 let signature_offset = append!(signature);
189
190 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 u16::from_le_bytes(unsafe {
196 [offsets[4].assume_init(), offsets[5].assume_init()]
197 })
198 } else {
199 append!(pubkey)
200 };
201
202 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
219pub 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#[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#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
302pub enum Error {
303 UnsupportedFeature,
306
307 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
331fn 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 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 #[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 first_offset,
485 u16::MAX,
486 first_offset + SIG_SIZE,
487 u16::MAX,
488 first_offset + SIG_SIZE + KEY_SIZE,
489 msg1.len() as u16,
490 u16::MAX,
491
492 second_offset,
493 u16::MAX,
494 second_offset + SIG_SIZE,
495 u16::MAX,
496 second_offset + SIG_SIZE + KEY_SIZE,
497 msg2.len() as u16,
498 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}