clone_solana_secp256k1_program/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2//! Instructions for the [secp256k1 native program][np].
3//!
4//! [np]: https://docs.solanalabs.com/runtime/programs#secp256k1-program
5//!
6//! _This module provides low-level cryptographic building blocks that must be
7//! used carefully to ensure proper security. Read this documentation and
8//! accompanying links thoroughly._
9//!
10//! The secp26k1 native program performs flexible verification of [secp256k1]
11//! ECDSA signatures, as used by Ethereum. It can verify up to 255 signatures on
12//! up to 255 messages, with those signatures, messages, and their public keys
13//! arbitrarily distributed across the instruction data of any instructions in
14//! the same transaction as the secp256k1 instruction.
15//!
16//! The secp256k1 native program ID is located in the [`secp256k1_program`] module.
17//!
18//! The instruction is designed for Ethereum interoperability, but may be useful
19//! for other purposes. It operates on Ethereum addresses, which are [`keccak`]
20//! hashes of secp256k1 public keys, and internally is implemented using the
21//! secp256k1 key recovery algorithm. Ethereum address can be created for
22//! secp256k1 public keys with the [`construct_eth_pubkey`] function.
23//!
24//! [`keccak`]: https://docs.rs/solana-sdk/latest/clone_solana_sdk/keccak/index.html
25//!
26//! This instruction does not directly allow for key recovery as in Ethereum's
27//! [`ecrecover`] precompile. For that Solana provides the [`secp256k1_recover`]
28//! syscall.
29//!
30//! [secp256k1]: https://en.bitcoin.it/wiki/Secp256k1
31//! [`secp256k1_program`]: https://docs.rs/solana-program/latest/clone_solana_program/secp256k1_program/index.html
32//! [`secp256k1_recover`]: https://docs.rs/solana-secp256k1-recover
33//! [`ecrecover`]: https://docs.soliditylang.org/en/v0.8.14/units-and-global-variables.html?highlight=ecrecover#mathematical-and-cryptographic-functions
34//!
35//! Use cases for the secp256k1 instruction include:
36//!
37//! - Verifying Ethereum transaction signatures.
38//! - Verifying Ethereum [EIP-712] signatures.
39//! - Verifying arbitrary secp256k1 signatures.
40//! - Signing a single message with multiple signatures.
41//!
42//! [EIP-712]: https://eips.ethereum.org/EIPS/eip-712
43//!
44//! The [`new_secp256k1_instruction`] function is suitable for building a
45//! secp256k1 program instruction for basic use cases where a single message must
46//! be signed by a known secret key. For other uses cases, including many
47//! Ethereum-integration use cases, construction of the secp256k1 instruction
48//! must be done manually.
49//!
50//! # How to use this program
51//!
52//! Transactions that use the secp256k1 native program will typically include
53//! at least two instructions: one for the secp256k1 program to verify the
54//! signatures, and one for a custom program that will check that the secp256k1
55//! instruction data matches what the program expects (using
56//! [`load_instruction_at_checked`] or [`get_instruction_relative`]). The
57//! signatures, messages, and Ethereum addresses being verified may reside in the
58//! instruction data of either of these instructions, or in the instruction data
59//! of one or more additional instructions, as long as those instructions are in
60//! the same transaction.
61//!
62//! [`load_instruction_at_checked`]: https://docs.rs/solana-program/latest/clone_solana_program/sysvar/instructions/fn.load_instruction_at_checked.html
63//! [`get_instruction_relative`]: https://docs.rs/solana-program/latest/clone_solana_program/sysvar/instructions/fn.get_instruction_relative.html
64//!
65//! Correct use of this program involves multiple steps, in client code and
66//! program code:
67//!
68//! - In the client:
69//!   - Sign the [`keccak`]-hashed messages with a secp256k1 ECDSA library,
70//!     like the [`libsecp256k1`] crate.
71//!   - Build any custom instruction data that contains signature, message, or
72//!     Ethereum address data that will be used by the secp256k1 instruction.
73//!   - Build the secp256k1 program instruction data, specifying the number of
74//!     signatures to verify, the instruction indexes within the transaction,
75//!     and offsets within those instruction's data, where the signatures,
76//!     messages, and Ethereum addresses are located.
77//!   - Build the custom instruction for the program that will check the results
78//!     of the secp256k1 native program.
79//!   - Package all instructions into a single transaction and submit them.
80//! - In the program:
81//!   - Load the secp256k1 instruction data with
82//!     [`load_instruction_at_checked`]. or [`get_instruction_relative`].
83//!   - Check that the secp256k1 program ID is equal to
84//!     [`secp256k1_program::ID`], so that the signature verification cannot be
85//!     faked with a malicious program.
86//!   - Check that the public keys and messages are the expected values per
87//!     the program's requirements.
88//!
89//! [`secp256k1_program::ID`]: https://docs.rs/solana-program/latest/clone_solana_program/secp256k1_program/constant.ID.html
90//!
91//! The signature, message, or Ethereum addresses may reside in the secp256k1
92//! instruction data itself as additional data, their bytes following the bytes
93//! of the protocol required by the secp256k1 instruction to locate the
94//! signature, message, and Ethereum address data. This is the technique used by
95//! `new_secp256k1_instruction` for simple signature verification.
96//!
97//! The `clone_solana_sdk` crate provides few APIs for building the instructions and
98//! transactions necessary for properly using the secp256k1 native program.
99//! Many steps must be done manually.
100//!
101//! The `clone_solana_program` crate provides no APIs to assist in interpreting
102//! the secp256k1 instruction data. It must be done manually.
103//!
104//! The secp256k1 program is implemented with the [`libsecp256k1`] crate,
105//! which clients may also want to use.
106//!
107//! [`libsecp256k1`]: https://docs.rs/libsecp256k1/latest/libsecp256k1
108//!
109//! # Layout and interpretation of the secp256k1 instruction data
110//!
111//! The secp256k1 instruction data contains:
112//!
113//! - 1 byte indicating the number of signatures to verify, 0 - 255,
114//! - A number of _signature offset_ structures that indicate where in the
115//!   transaction to locate each signature, message, and Ethereum address.
116//! - 0 or more bytes of arbitrary data, which may contain signatures,
117//!   messages or Ethereum addresses.
118//!
119//! The signature offset structure is defined by [`SecpSignatureOffsets`],
120//! and can be serialized to the correct format with [`bincode::serialize_into`].
121//! Note that the bincode format may not be stable,
122//! and callers should ensure they use the same version of `bincode` as the Solana SDK.
123//! This data structure is not provided to Solana programs,
124//! which are expected to interpret the signature offsets manually.
125//!
126//! [`bincode::serialize_into`]: https://docs.rs/bincode/1.3.3/bincode/fn.serialize_into.html
127//!
128//! The serialized signature offset structure has the following 11-byte layout,
129//! with data types in little-endian encoding.
130//!
131//! | index  | bytes | type  | description |
132//! |--------|-------|-------|-------------|
133//! | 0      | 2     | `u16` | `signature_offset` - offset to 64-byte signature plus 1-byte recovery ID. |
134//! | 2      | 1     | `u8`  | `signature_offset_instruction_index` - within the transaction, the index of the transaction whose instruction data contains the signature. |
135//! | 3      | 2     | `u16` | `eth_address_offset` - offset to 20-byte Ethereum address. |
136//! | 5      | 1     | `u8`  | `eth_address_instruction_index` - within the transaction, the index of the instruction whose instruction data contains the Ethereum address. |
137//! | 6      | 2     | `u16` | `message_data_offset` - Offset to start of message data. |
138//! | 8      | 2     | `u16` | `message_data_size` - Size of message data in bytes. |
139//! | 10     | 1     | `u8`  | `message_instruction_index` - Within the transaction, the index of the instruction whose instruction data contains the message data. |
140//!
141//! # Signature malleability
142//!
143//! With the ECDSA signature algorithm it is possible for any party, given a
144//! valid signature of some message, to create a second signature that is
145//! equally valid. This is known as _signature malleability_. In many cases this
146//! is not a concern, but in cases where applications rely on signatures to have
147//! a unique representation this can be the source of bugs, potentially with
148//! security implications.
149//!
150//! **The solana `secp256k1_recover` function does not prevent signature
151//! malleability**. This is in contrast to the Bitcoin secp256k1 library, which
152//! does prevent malleability by default. Solana accepts signatures with `S`
153//! values that are either in the _high order_ or in the _low order_, and it
154//! is trivial to produce one from the other.
155//!
156//! For more complete documentation of the subject, and techniques to prevent
157//! malleability, see the documentation for the [`secp256k1_recover`] syscall.
158//!
159//! # Additional security considerations
160//!
161//! Most programs will want to be conservative about the layout of the secp256k1 instruction
162//! to prevent unforeseen bugs. The following checks may be desirable:
163//!
164//! - That there are exactly the expected number of signatures.
165//! - That the three indexes, `signature_offset_instruction_index`,
166//!   `eth_address_instruction_index`, and `message_instruction_index` are as
167//!   expected, placing the signature, message and Ethereum address in the
168//!   expected instruction.
169//!
170//! Loading the secp256k1 instruction data within a program requires access to
171//! the [instructions sysvar][is], which must be passed to the program by its
172//! caller. Programs must verify the ID of this program to avoid calling an
173//! imposter program. This does not need to be done manually though, as long as
174//! it is only used through the [`load_instruction_at_checked`] or
175//! [`get_instruction_relative`] functions. Both of these functions check their
176//! sysvar argument to ensure it is the known instruction sysvar.
177//!
178//! [is]: https://docs.rs/solana-program/latest/clone_solana_program/sysvar/instructions/index.html
179//!
180//! Programs should _always_ verify that the secp256k1 program ID loaded through
181//! the instructions sysvar has the same value as in the [`secp256k1_program`]
182//! module. Again this prevents imposter programs.
183//!
184//! [`secp256k1_program`]: https://docs.rs/solana-program/latest/clone_solana_program/secp256k1_program/index.html
185//!
186//! # Errors
187//!
188//! The transaction will fail if any of the following are true:
189//!
190//! - Any signature was not created by the secret key corresponding to the
191//!   specified public key.
192//! - Any signature is invalid.
193//! - Any signature is "overflowing", a non-standard condition.
194//! - The instruction data is empty.
195//! - The first byte of instruction data is equal to 0 (indicating no signatures),
196//!   but the instruction data's length is greater than 1.
197//! - The instruction data is not long enough to hold the number of signature
198//!   offsets specified in the first byte.
199//! - Any instruction indexes specified in the signature offsets are greater or
200//!   equal to the number of instructions in the transaction.
201//! - Any bounds specified in the signature offsets exceed the bounds of the
202//!   instruction data to which they are indexed.
203//!
204//! # Examples
205//!
206//! Both of the following examples make use of the following module definition
207//! to parse the secp256k1 instruction data from within a Solana program.
208//!
209//! ```no_run
210//! mod secp256k1_defs {
211//!     use clone_solana_program_error::ProgramError;
212//!     use std::iter::Iterator;
213//!
214//!     pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
215//!     pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
216//!     pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
217//!
218//!     /// The structure encoded in the secp2256k1 instruction data.
219//!     pub struct SecpSignatureOffsets {
220//!         pub signature_offset: u16,
221//!         pub signature_instruction_index: u8,
222//!         pub eth_address_offset: u16,
223//!         pub eth_address_instruction_index: u8,
224//!         pub message_data_offset: u16,
225//!         pub message_data_size: u16,
226//!         pub message_instruction_index: u8,
227//!     }
228//!
229//!     pub fn iter_signature_offsets(
230//!        secp256k1_instr_data: &[u8],
231//!     ) -> Result<impl Iterator<Item = SecpSignatureOffsets> + '_, ProgramError> {
232//!         // First element is the number of `SecpSignatureOffsets`.
233//!         let num_structs = *secp256k1_instr_data
234//!             .get(0)
235//!             .ok_or(ProgramError::InvalidArgument)?;
236//!
237//!         let all_structs_size = SIGNATURE_OFFSETS_SERIALIZED_SIZE * num_structs as usize;
238//!         let all_structs_slice = secp256k1_instr_data
239//!             .get(1..all_structs_size + 1)
240//!             .ok_or(ProgramError::InvalidArgument)?;
241//!
242//!         fn decode_u16(chunk: &[u8], index: usize) -> u16 {
243//!             u16::from_le_bytes(<[u8; 2]>::try_from(&chunk[index..index + 2]).unwrap())
244//!         }
245//!
246//!         Ok(all_structs_slice
247//!             .chunks(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
248//!             .map(|chunk| SecpSignatureOffsets {
249//!                 signature_offset: decode_u16(chunk, 0),
250//!                 signature_instruction_index: chunk[2],
251//!                 eth_address_offset: decode_u16(chunk, 3),
252//!                 eth_address_instruction_index: chunk[5],
253//!                 message_data_offset: decode_u16(chunk, 6),
254//!                 message_data_size: decode_u16(chunk, 8),
255//!                 message_instruction_index: chunk[10],
256//!             }))
257//!     }
258//! }
259//! ```
260//!
261//! ## Example: Signing and verifying with `new_secp256k1_instruction`
262//!
263//! This example demonstrates the simplest way to use the secp256k1 program, by
264//! calling [`new_secp256k1_instruction`] to sign a single message and build the
265//! corresponding secp256k1 instruction.
266//!
267//! This example has two components: a Solana program, and an RPC client that
268//! sends a transaction to call it. The RPC client will sign a single message,
269//! and the Solana program will introspect the secp256k1 instruction to verify
270//! that the signer matches a known authorized public key.
271//!
272//! The Solana program. Note that it uses `libsecp256k1` version 0.7.0 to parse
273//! the secp256k1 signature to prevent malleability.
274//!
275//! ```no_run
276//! # mod secp256k1_defs {
277//! #     use clone_solana_program_error::ProgramError;
278//! #     use std::iter::Iterator;
279//! #
280//! #     pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
281//! #     pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
282//! #     pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
283//! #
284//! #     /// The structure encoded in the secp2256k1 instruction data.
285//! #     pub struct SecpSignatureOffsets {
286//! #         pub signature_offset: u16,
287//! #         pub signature_instruction_index: u8,
288//! #         pub eth_address_offset: u16,
289//! #         pub eth_address_instruction_index: u8,
290//! #         pub message_data_offset: u16,
291//! #         pub message_data_size: u16,
292//! #         pub message_instruction_index: u8,
293//! #     }
294//! #
295//! #     pub fn iter_signature_offsets(
296//! #        secp256k1_instr_data: &[u8],
297//! #     ) -> Result<impl Iterator<Item = SecpSignatureOffsets> + '_, ProgramError> {
298//! #         // First element is the number of `SecpSignatureOffsets`.
299//! #         let num_structs = *secp256k1_instr_data
300//! #             .get(0)
301//! #             .ok_or(ProgramError::InvalidArgument)?;
302//! #
303//! #         let all_structs_size = SIGNATURE_OFFSETS_SERIALIZED_SIZE * num_structs as usize;
304//! #         let all_structs_slice = secp256k1_instr_data
305//! #             .get(1..all_structs_size + 1)
306//! #             .ok_or(ProgramError::InvalidArgument)?;
307//! #
308//! #         fn decode_u16(chunk: &[u8], index: usize) -> u16 {
309//! #             u16::from_le_bytes(<[u8; 2]>::try_from(&chunk[index..index + 2]).unwrap())
310//! #         }
311//! #
312//! #         Ok(all_structs_slice
313//! #             .chunks(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
314//! #             .map(|chunk| SecpSignatureOffsets {
315//! #                 signature_offset: decode_u16(chunk, 0),
316//! #                 signature_instruction_index: chunk[2],
317//! #                 eth_address_offset: decode_u16(chunk, 3),
318//! #                 eth_address_instruction_index: chunk[5],
319//! #                 message_data_offset: decode_u16(chunk, 6),
320//! #                 message_data_size: decode_u16(chunk, 8),
321//! #                 message_instruction_index: chunk[10],
322//! #             }))
323//! #     }
324//! # }
325//! use clone_solana_account_info::{next_account_info, AccountInfo};
326//! use clone_solana_msg::msg;
327//! use clone_solana_program_error::{ProgramError, ProgramResult};
328//! use clone_solana_sdk_ids::secp256k1_program;
329//! use clone_solana_instructions_sysvar::load_instruction_at_checked;
330//!
331//! /// An Ethereum address corresponding to a secp256k1 secret key that is
332//! /// authorized to sign our messages.
333//! const AUTHORIZED_ETH_ADDRESS: [u8; 20] = [
334//!     0x18, 0x8a, 0x5c, 0xf2, 0x3b, 0x0e, 0xff, 0xe9, 0xa8, 0xe1, 0x42, 0x64, 0x5b, 0x82, 0x2f, 0x3a,
335//!     0x6b, 0x8b, 0x52, 0x35,
336//! ];
337//!
338//! /// Check the secp256k1 instruction to ensure it was signed by
339//! /// `AUTHORIZED_ETH_ADDRESS`s key.
340//! ///
341//! /// `accounts` is the slice of all accounts passed to the program
342//! /// entrypoint. The only account it should contain is the instructions sysvar.
343//! fn demo_secp256k1_verify_basic(
344//!    accounts: &[AccountInfo],
345//! ) -> ProgramResult {
346//!     let account_info_iter = &mut accounts.iter();
347//!
348//!     // The instructions sysvar gives access to the instructions in the transaction.
349//!     let instructions_sysvar_account = next_account_info(account_info_iter)?;
350//!     assert!(clone_solana_sdk_ids::sysvar::instructions::check_id(
351//!         instructions_sysvar_account.key
352//!     ));
353//!
354//!     // Load the secp256k1 instruction.
355//!     // `new_secp256k1_instruction` generates an instruction that must be at index 0.
356//!     let secp256k1_instr =
357//!         clone_solana_instructions_sysvar::load_instruction_at_checked(0, instructions_sysvar_account)?;
358//!
359//!     // Verify it is a secp256k1 instruction.
360//!     // This is security-critical - what if the transaction uses an imposter secp256k1 program?
361//!     assert!(secp256k1_program::check_id(&secp256k1_instr.program_id));
362//!
363//!     // There must be at least one byte. This is also verified by the runtime,
364//!     // and doesn't strictly need to be checked.
365//!     assert!(secp256k1_instr.data.len() > 1);
366//!
367//!     let num_signatures = secp256k1_instr.data[0];
368//!     // `new_secp256k1_instruction` generates an instruction that contains one signature.
369//!     assert_eq!(1, num_signatures);
370//!
371//!     // Load the first and only set of signature offsets.
372//!     let offsets: secp256k1_defs::SecpSignatureOffsets =
373//!         secp256k1_defs::iter_signature_offsets(&secp256k1_instr.data)?
374//!             .next()
375//!             .ok_or(ProgramError::InvalidArgument)?;
376//!
377//!     // `new_secp256k1_instruction` generates an instruction that only uses instruction index 0.
378//!     assert_eq!(0, offsets.signature_instruction_index);
379//!     assert_eq!(0, offsets.eth_address_instruction_index);
380//!     assert_eq!(0, offsets.message_instruction_index);
381//!
382//!     // Reject high-s value signatures to prevent malleability.
383//!     // Solana does not do this itself.
384//!     // This may or may not be necessary depending on use case.
385//!     {
386//!         let signature = &secp256k1_instr.data[offsets.signature_offset as usize
387//!             ..offsets.signature_offset as usize + secp256k1_defs::SIGNATURE_SERIALIZED_SIZE];
388//!         let signature = libsecp256k1::Signature::parse_standard_slice(signature)
389//!             .map_err(|_| ProgramError::InvalidArgument)?;
390//!
391//!         if signature.s.is_high() {
392//!             msg!("signature with high-s value");
393//!             return Err(ProgramError::InvalidArgument);
394//!         }
395//!     }
396//!
397//!     // There is likely at least one more verification step a real program needs
398//!     // to do here to ensure it trusts the secp256k1 instruction, e.g.:
399//!     //
400//!     // - verify the tx signer is authorized
401//!     // - verify the secp256k1 signer is authorized
402//!
403//!     // Here we are checking the secp256k1 pubkey against a known authorized pubkey.
404//!     let eth_address = &secp256k1_instr.data[offsets.eth_address_offset as usize
405//!         ..offsets.eth_address_offset as usize + secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE];
406//!
407//!     if eth_address != AUTHORIZED_ETH_ADDRESS {
408//!         return Err(ProgramError::InvalidArgument);
409//!     }
410//!
411//!     Ok(())
412//! }
413//! ```
414//!
415//! The client program:
416//!
417//! ```no_run
418//! # use clone_solana_sdk::example_mocks::clone_solana_rpc_client;
419//! use anyhow::Result;
420//! use clone_solana_instruction::{AccountMeta, Instruction};
421//! use clone_solana_keypair::Keypair;
422//! use clone_solana_rpc_client::rpc_client::RpcClient;
423//! use clone_solana_signer::Signer;
424//! use clone_solana_sdk::transaction::Transaction;
425//!
426//! fn demo_secp256k1_verify_basic(
427//!     payer_keypair: &Keypair,
428//!     secp256k1_secret_key: &libsecp256k1::SecretKey,
429//!     client: &RpcClient,
430//!     program_keypair: &Keypair,
431//! ) -> Result<()> {
432//!     // Internally to `new_secp256k1_instruction` and
433//!     // `secp256k_instruction::verify` (the secp256k1 program), this message is
434//!     // keccak-hashed before signing.
435//!     let msg = b"hello world";
436//!     let secp256k1_instr = clone_solana_secp256k1_program::new_secp256k1_instruction(&secp256k1_secret_key, msg);
437//!
438//!     let program_instr = Instruction::new_with_bytes(
439//!         program_keypair.pubkey(),
440//!         &[],
441//!         vec![
442//!             AccountMeta::new_readonly(clone_solana_sdk_ids::sysvar::instructions::ID, false)
443//!         ],
444//!     );
445//!
446//!     let blockhash = client.get_latest_blockhash()?;
447//!     let tx = Transaction::new_signed_with_payer(
448//!         &[secp256k1_instr, program_instr],
449//!         Some(&payer_keypair.pubkey()),
450//!         &[payer_keypair],
451//!         blockhash,
452//!     );
453//!
454//!     client.send_and_confirm_transaction(&tx)?;
455//!
456//!     Ok(())
457//! }
458//! ```
459//!
460//! ## Example: Verifying multiple signatures in one instruction
461//!
462//! This example demonstrates manually creating a secp256k1 instruction
463//! containing many signatures, and a Solana program that parses them all. This
464//! example on its own has no practical purpose. It simply demonstrates advanced
465//! use of the secp256k1 program.
466//!
467//! Recall that the secp256k1 program will accept signatures, messages, and
468//! Ethereum addresses that reside in any instruction contained in the same
469//! transaction. In the _previous_ example, the Solana program asserted that all
470//! signatures, messages, and addresses were stored in the instruction at 0. In
471//! this next example the Solana program supports signatures, messages, and
472//! addresses stored in any instruction. For simplicity the client still only
473//! stores signatures, messages, and addresses in a single instruction, the
474//! secp256k1 instruction. The code for storing this data across multiple
475//! instructions would be complex, and may not be necessary in practice.
476//!
477//! This example has two components: a Solana program, and an RPC client that
478//! sends a transaction to call it.
479//!
480//! The Solana program:
481//!
482//! ```no_run
483//! # mod secp256k1_defs {
484//! #     use clone_solana_program_error::ProgramError;
485//! #     use std::iter::Iterator;
486//! #
487//! #     pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
488//! #     pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
489//! #     pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
490//! #
491//! #     /// The structure encoded in the secp2256k1 instruction data.
492//! #     pub struct SecpSignatureOffsets {
493//! #         pub signature_offset: u16,
494//! #         pub signature_instruction_index: u8,
495//! #         pub eth_address_offset: u16,
496//! #         pub eth_address_instruction_index: u8,
497//! #         pub message_data_offset: u16,
498//! #         pub message_data_size: u16,
499//! #         pub message_instruction_index: u8,
500//! #     }
501//! #
502//! #     pub fn iter_signature_offsets(
503//! #        secp256k1_instr_data: &[u8],
504//! #     ) -> Result<impl Iterator<Item = SecpSignatureOffsets> + '_, ProgramError> {
505//! #         // First element is the number of `SecpSignatureOffsets`.
506//! #         let num_structs = *secp256k1_instr_data
507//! #             .get(0)
508//! #             .ok_or(ProgramError::InvalidArgument)?;
509//! #
510//! #         let all_structs_size = SIGNATURE_OFFSETS_SERIALIZED_SIZE * num_structs as usize;
511//! #         let all_structs_slice = secp256k1_instr_data
512//! #             .get(1..all_structs_size + 1)
513//! #             .ok_or(ProgramError::InvalidArgument)?;
514//! #
515//! #         fn decode_u16(chunk: &[u8], index: usize) -> u16 {
516//! #             u16::from_le_bytes(<[u8; 2]>::try_from(&chunk[index..index + 2]).unwrap())
517//! #         }
518//! #
519//! #         Ok(all_structs_slice
520//! #             .chunks(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
521//! #             .map(|chunk| SecpSignatureOffsets {
522//! #                 signature_offset: decode_u16(chunk, 0),
523//! #                 signature_instruction_index: chunk[2],
524//! #                 eth_address_offset: decode_u16(chunk, 3),
525//! #                 eth_address_instruction_index: chunk[5],
526//! #                 message_data_offset: decode_u16(chunk, 6),
527//! #                 message_data_size: decode_u16(chunk, 8),
528//! #                 message_instruction_index: chunk[10],
529//! #             }))
530//! #     }
531//! # }
532//! use clone_solana_account_info::{next_account_info, AccountInfo};
533//! use clone_solana_program_error::{ProgramError, ProgramResult};
534//! use clone_solana_msg::msg;
535//! use clone_solana_sdk_ids::secp256k1_program;
536//! use clone_solana_instructions_sysvar::{get_instruction_relative, load_instruction_at_checked};
537//!
538//! /// A struct to hold the values specified in the `SecpSignatureOffsets` struct.
539//! struct SecpSignature {
540//!     signature: [u8; secp256k1_defs::SIGNATURE_SERIALIZED_SIZE],
541//!     recovery_id: u8,
542//!     eth_address: [u8; secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE],
543//!     message: Vec<u8>,
544//! }
545//!
546//! /// Load all signatures indicated in the secp256k1 instruction.
547//! ///
548//! /// This function is quite inefficient for reloading the same instructions
549//! /// repeatedly and making copies and allocations.
550//! fn load_signatures(
551//!     secp256k1_instr_data: &[u8],
552//!     instructions_sysvar_account: &AccountInfo,
553//! ) -> Result<Vec<SecpSignature>, ProgramError> {
554//!     let mut sigs = vec![];
555//!     for offsets in secp256k1_defs::iter_signature_offsets(secp256k1_instr_data)? {
556//!         let signature_instr = load_instruction_at_checked(
557//!             offsets.signature_instruction_index as usize,
558//!             instructions_sysvar_account,
559//!         )?;
560//!         let eth_address_instr = load_instruction_at_checked(
561//!             offsets.eth_address_instruction_index as usize,
562//!             instructions_sysvar_account,
563//!         )?;
564//!         let message_instr = load_instruction_at_checked(
565//!             offsets.message_instruction_index as usize,
566//!             instructions_sysvar_account,
567//!         )?;
568//!
569//!         // These indexes must all be valid because the runtime already verified them.
570//!         let signature = &signature_instr.data[offsets.signature_offset as usize
571//!             ..offsets.signature_offset as usize + secp256k1_defs::SIGNATURE_SERIALIZED_SIZE];
572//!         let recovery_id = signature_instr.data
573//!             [offsets.signature_offset as usize + secp256k1_defs::SIGNATURE_SERIALIZED_SIZE];
574//!         let eth_address = &eth_address_instr.data[offsets.eth_address_offset as usize
575//!             ..offsets.eth_address_offset as usize + secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE];
576//!         let message = &message_instr.data[offsets.message_data_offset as usize
577//!             ..offsets.message_data_offset as usize + offsets.message_data_size as usize];
578//!
579//!         let signature =
580//!             <[u8; secp256k1_defs::SIGNATURE_SERIALIZED_SIZE]>::try_from(signature).unwrap();
581//!         let eth_address =
582//!             <[u8; secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE]>::try_from(eth_address).unwrap();
583//!         let message = Vec::from(message);
584//!
585//!         sigs.push(SecpSignature {
586//!             signature,
587//!             recovery_id,
588//!             eth_address,
589//!             message,
590//!         })
591//!     }
592//!     Ok(sigs)
593//! }
594//!
595//! fn demo_secp256k1_custom_many(
596//!     accounts: &[AccountInfo],
597//! ) -> ProgramResult {
598//!     let account_info_iter = &mut accounts.iter();
599//!
600//!     let instructions_sysvar_account = next_account_info(account_info_iter)?;
601//!     assert!(clone_solana_sdk_ids::sysvar::instructions::check_id(
602//!         instructions_sysvar_account.key
603//!     ));
604//!
605//!     let secp256k1_instr =
606//!         clone_solana_instructions_sysvar::get_instruction_relative(-1, instructions_sysvar_account)?;
607//!
608//!     assert!(secp256k1_program::check_id(&secp256k1_instr.program_id));
609//!
610//!     let signatures = load_signatures(&secp256k1_instr.data, instructions_sysvar_account)?;
611//!     for (idx, signature_bundle) in signatures.iter().enumerate() {
612//!         let signature = hex::encode(&signature_bundle.signature);
613//!         let eth_address = hex::encode(&signature_bundle.eth_address);
614//!         let message = hex::encode(&signature_bundle.message);
615//!         msg!("sig {}: {:?}", idx, signature);
616//!         msg!("recid: {}: {}", idx, signature_bundle.recovery_id);
617//!         msg!("eth address {}: {}", idx, eth_address);
618//!         msg!("message {}: {}", idx, message);
619//!     }
620//!
621//!     Ok(())
622//! }
623//! ```
624//!
625//! The client program:
626//!
627//! ```no_run
628//! # use clone_solana_sdk::example_mocks::clone_solana_rpc_client;
629//! use anyhow::Result;
630//! use clone_solana_instruction::{AccountMeta, Instruction};
631//! use clone_solana_rpc_client::rpc_client::RpcClient;
632//! use clone_solana_secp256k1_program::{
633//!     construct_eth_pubkey, SecpSignatureOffsets, HASHED_PUBKEY_SERIALIZED_SIZE,
634//!     SIGNATURE_OFFSETS_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE,
635//! };
636//! use clone_solana_signer::Signer;
637//! use clone_solana_keypair::Keypair;
638//! use clone_solana_sdk::transaction::Transaction;
639//!
640//! /// A struct to hold the values specified in the `SecpSignatureOffsets` struct.
641//! struct SecpSignature {
642//!     signature: [u8; SIGNATURE_SERIALIZED_SIZE],
643//!     recovery_id: u8,
644//!     eth_address: [u8; HASHED_PUBKEY_SERIALIZED_SIZE],
645//!     message: Vec<u8>,
646//! }
647//!
648//! /// Create the instruction data for a secp256k1 instruction.
649//! ///
650//! /// `instruction_index` is the index the secp256k1 instruction will appear
651//! /// within the transaction. For simplicity, this function only supports packing
652//! /// the signatures into the secp256k1 instruction data, and not into any other
653//! /// instructions within the transaction.
654//! fn make_secp256k1_instruction_data(
655//!     signatures: &[SecpSignature],
656//!     instruction_index: u8,
657//! ) -> Result<Vec<u8>> {
658//!     assert!(signatures.len() <= u8::MAX.into());
659//!
660//!     // We're going to pack all the signatures into the secp256k1 instruction data.
661//!     // Before our signatures though is the signature offset structures
662//!     // the secp256k1 program parses to find those signatures.
663//!     // This value represents the byte offset where the signatures begin.
664//!     let data_start = 1 + signatures.len() * SIGNATURE_OFFSETS_SERIALIZED_SIZE;
665//!
666//!     let mut signature_offsets = vec![];
667//!     let mut signature_buffer = vec![];
668//!
669//!     for signature_bundle in signatures {
670//!         let data_start = data_start
671//!             .checked_add(signature_buffer.len())
672//!             .expect("overflow");
673//!
674//!         let signature_offset = data_start;
675//!         let eth_address_offset = data_start
676//!             .checked_add(SIGNATURE_SERIALIZED_SIZE + 1)
677//!             .expect("overflow");
678//!         let message_data_offset = eth_address_offset
679//!             .checked_add(HASHED_PUBKEY_SERIALIZED_SIZE)
680//!             .expect("overflow");
681//!         let message_data_size = signature_bundle.message.len();
682//!
683//!         let signature_offset = u16::try_from(signature_offset)?;
684//!         let eth_address_offset = u16::try_from(eth_address_offset)?;
685//!         let message_data_offset = u16::try_from(message_data_offset)?;
686//!         let message_data_size = u16::try_from(message_data_size)?;
687//!
688//!         signature_offsets.push(SecpSignatureOffsets {
689//!             signature_offset,
690//!             signature_instruction_index: instruction_index,
691//!             eth_address_offset,
692//!             eth_address_instruction_index: instruction_index,
693//!             message_data_offset,
694//!             message_data_size,
695//!             message_instruction_index: instruction_index,
696//!         });
697//!
698//!         signature_buffer.extend(signature_bundle.signature);
699//!         signature_buffer.push(signature_bundle.recovery_id);
700//!         signature_buffer.extend(&signature_bundle.eth_address);
701//!         signature_buffer.extend(&signature_bundle.message);
702//!     }
703//!
704//!     let mut instr_data = vec![];
705//!     instr_data.push(signatures.len() as u8);
706//!
707//!     for offsets in signature_offsets {
708//!         let offsets = bincode::serialize(&offsets)?;
709//!         instr_data.extend(offsets);
710//!     }
711//!
712//!     instr_data.extend(signature_buffer);
713//!
714//!     Ok(instr_data)
715//! }
716//!
717//! fn demo_secp256k1_custom_many(
718//!     payer_keypair: &Keypair,
719//!     client: &RpcClient,
720//!     program_keypair: &Keypair,
721//! ) -> Result<()> {
722//!     // Sign some messages.
723//!     let mut signatures = vec![];
724//!     for idx in 0..2 {
725//!         let secret_key = libsecp256k1::SecretKey::random(&mut rand0_7::thread_rng());
726//!         let message = format!("hello world {}", idx).into_bytes();
727//!         let message_hash = {
728//!             let mut hasher = clone_solana_keccak_hasher::Hasher::default();
729//!             hasher.hash(&message);
730//!             hasher.result()
731//!         };
732//!         let secp_message = libsecp256k1::Message::parse(&message_hash.0);
733//!         let (signature, recovery_id) = libsecp256k1::sign(&secp_message, &secret_key);
734//!         let signature = signature.serialize();
735//!         let recovery_id = recovery_id.serialize();
736//!
737//!         let public_key = libsecp256k1::PublicKey::from_secret_key(&secret_key);
738//!         let eth_address = construct_eth_pubkey(&public_key);
739//!
740//!         signatures.push(SecpSignature {
741//!             signature,
742//!             recovery_id,
743//!             eth_address,
744//!             message,
745//!         });
746//!     }
747//!
748//!     let secp256k1_instr_data = make_secp256k1_instruction_data(&signatures, 0)?;
749//!     let secp256k1_instr = Instruction::new_with_bytes(
750//!         clone_solana_sdk_ids::secp256k1_program::ID,
751//!         &secp256k1_instr_data,
752//!         vec![],
753//!     );
754//!
755//!     let program_instr = Instruction::new_with_bytes(
756//!         program_keypair.pubkey(),
757//!         &[],
758//!         vec![
759//!             AccountMeta::new_readonly(clone_solana_sdk_ids::sysvar::instructions::ID, false)
760//!         ],
761//!     );
762//!
763//!     let blockhash = client.get_latest_blockhash()?;
764//!     let tx = Transaction::new_signed_with_payer(
765//!         &[secp256k1_instr, program_instr],
766//!         Some(&payer_keypair.pubkey()),
767//!         &[payer_keypair],
768//!         blockhash,
769//!     );
770//!
771//!     client.send_and_confirm_transaction(&tx)?;
772//!
773//!     Ok(())
774//! }
775//! ```
776
777use digest::Digest;
778#[cfg(feature = "serde")]
779use serde_derive::{Deserialize, Serialize};
780#[cfg(feature = "bincode")]
781use {clone_solana_instruction::Instruction, clone_solana_precompile_error::PrecompileError};
782
783pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
784pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
785pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
786pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + 1;
787
788/// Offsets of signature data within a secp256k1 instruction.
789///
790/// See the [module documentation][md] for a complete description.
791///
792/// [md]: self
793#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
794#[derive(Default, Debug, Eq, PartialEq)]
795pub struct SecpSignatureOffsets {
796    /// Offset to 64-byte signature plus 1-byte recovery ID.
797    pub signature_offset: u16,
798    /// Within the transaction, the index of the instruction whose instruction data contains the signature.
799    pub signature_instruction_index: u8,
800    /// Offset to 20-byte Ethereum address.
801    pub eth_address_offset: u16,
802    /// Within the transaction, the index of the instruction whose instruction data contains the address.
803    pub eth_address_instruction_index: u8,
804    /// Offset to start of message data.
805    pub message_data_offset: u16,
806    /// Size of message data in bytes.
807    pub message_data_size: u16,
808    /// Within the transaction, the index of the instruction whose instruction data contains the message.
809    pub message_instruction_index: u8,
810}
811
812/// Sign a message and create a secp256k1 program instruction to verify the signature.
813///
814/// This function is suitable for simple uses of the secp256k1 program.
815/// More complex uses must encode the secp256k1 instruction data manually.
816/// See the [module documentation][md] for examples.
817///
818/// [md]: self
819///
820/// The instruction generated by this function must be the first instruction
821/// included in a transaction or it will not verify. The
822/// [`SecpSignatureOffsets`] structure encoded in the instruction data specify
823/// the instruction indexes as 0.
824///
825/// `message_arr` is hashed with the [`keccak`] hash function prior to signing.
826///
827/// [`keccak`]: https://docs.rs/solana-sdk/latest/clone_solana_sdk/keccak/index.html
828#[cfg(feature = "bincode")]
829pub fn new_secp256k1_instruction(
830    priv_key: &libsecp256k1::SecretKey,
831    message_arr: &[u8],
832) -> Instruction {
833    let secp_pubkey = libsecp256k1::PublicKey::from_secret_key(priv_key);
834    let eth_pubkey = construct_eth_pubkey(&secp_pubkey);
835    let mut hasher = sha3::Keccak256::new();
836    hasher.update(message_arr);
837    let message_hash = hasher.finalize();
838    let mut message_hash_arr = [0u8; 32];
839    message_hash_arr.copy_from_slice(message_hash.as_slice());
840    let message = libsecp256k1::Message::parse(&message_hash_arr);
841    let (signature, recovery_id) = libsecp256k1::sign(&message, priv_key);
842    let signature_arr = signature.serialize();
843    assert_eq!(signature_arr.len(), SIGNATURE_SERIALIZED_SIZE);
844
845    let instruction_data_len = DATA_START
846        .saturating_add(eth_pubkey.len())
847        .saturating_add(signature_arr.len())
848        .saturating_add(message_arr.len())
849        .saturating_add(1);
850    let mut instruction_data = vec![0; instruction_data_len];
851
852    let eth_address_offset = DATA_START;
853    instruction_data[eth_address_offset..eth_address_offset.saturating_add(eth_pubkey.len())]
854        .copy_from_slice(&eth_pubkey);
855
856    let signature_offset = DATA_START.saturating_add(eth_pubkey.len());
857    instruction_data[signature_offset..signature_offset.saturating_add(signature_arr.len())]
858        .copy_from_slice(&signature_arr);
859
860    instruction_data[signature_offset.saturating_add(signature_arr.len())] =
861        recovery_id.serialize();
862
863    let message_data_offset = signature_offset
864        .saturating_add(signature_arr.len())
865        .saturating_add(1);
866    instruction_data[message_data_offset..].copy_from_slice(message_arr);
867
868    let num_signatures = 1;
869    instruction_data[0] = num_signatures;
870    let offsets = SecpSignatureOffsets {
871        signature_offset: signature_offset as u16,
872        signature_instruction_index: 0,
873        eth_address_offset: eth_address_offset as u16,
874        eth_address_instruction_index: 0,
875        message_data_offset: message_data_offset as u16,
876        message_data_size: message_arr.len() as u16,
877        message_instruction_index: 0,
878    };
879    let writer = std::io::Cursor::new(&mut instruction_data[1..DATA_START]);
880    bincode::serialize_into(writer, &offsets).unwrap();
881
882    Instruction {
883        program_id: clone_solana_sdk_ids::secp256k1_program::id(),
884        accounts: vec![],
885        data: instruction_data,
886    }
887}
888
889/// Creates an Ethereum address from a secp256k1 public key.
890pub fn construct_eth_pubkey(
891    pubkey: &libsecp256k1::PublicKey,
892) -> [u8; HASHED_PUBKEY_SERIALIZED_SIZE] {
893    let mut addr = [0u8; HASHED_PUBKEY_SERIALIZED_SIZE];
894    addr.copy_from_slice(&sha3::Keccak256::digest(&pubkey.serialize()[1..])[12..]);
895    assert_eq!(addr.len(), HASHED_PUBKEY_SERIALIZED_SIZE);
896    addr
897}
898
899/// Verifies the signatures specified in the secp256k1 instruction data.
900///
901/// This is the same as the verification routine executed by the runtime's secp256k1 native program,
902/// and is primarily of use to the runtime.
903///
904/// `data` is the secp256k1 program's instruction data. `instruction_datas` is
905/// the full slice of instruction datas for all instructions in the transaction,
906/// including the secp256k1 program's instruction data.
907///
908/// `feature_set` is the set of active Solana features. It is used to enable or
909/// disable a few minor additional checks that were activated on chain
910/// subsequent to the addition of the secp256k1 native program. For many
911/// purposes passing `FeatureSet::all_enabled()` is reasonable.
912#[cfg(feature = "bincode")]
913pub fn verify(
914    data: &[u8],
915    instruction_datas: &[&[u8]],
916    _feature_set: &clone_solana_feature_set::FeatureSet,
917) -> Result<(), PrecompileError> {
918    if data.is_empty() {
919        return Err(PrecompileError::InvalidInstructionDataSize);
920    }
921    let count = data[0] as usize;
922    if count == 0 && data.len() > 1 {
923        // count is zero but the instruction data indicates that is probably not
924        // correct, fail the instruction to catch probable invalid secp256k1
925        // instruction construction.
926        return Err(PrecompileError::InvalidInstructionDataSize);
927    }
928    let expected_data_size = count
929        .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
930        .saturating_add(1);
931    if data.len() < expected_data_size {
932        return Err(PrecompileError::InvalidInstructionDataSize);
933    }
934    for i in 0..count {
935        let start = i
936            .saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
937            .saturating_add(1);
938        let end = start.saturating_add(SIGNATURE_OFFSETS_SERIALIZED_SIZE);
939
940        let offsets: SecpSignatureOffsets = bincode::deserialize(&data[start..end])
941            .map_err(|_| PrecompileError::InvalidSignature)?;
942
943        // Parse out signature
944        let signature_index = offsets.signature_instruction_index as usize;
945        if signature_index >= instruction_datas.len() {
946            return Err(PrecompileError::InvalidInstructionDataSize);
947        }
948        let signature_instruction = instruction_datas[signature_index];
949        let sig_start = offsets.signature_offset as usize;
950        let sig_end = sig_start.saturating_add(SIGNATURE_SERIALIZED_SIZE);
951        if sig_end >= signature_instruction.len() {
952            return Err(PrecompileError::InvalidSignature);
953        }
954
955        let signature = libsecp256k1::Signature::parse_standard_slice(
956            &signature_instruction[sig_start..sig_end],
957        )
958        .map_err(|_| PrecompileError::InvalidSignature)?;
959
960        let recovery_id = libsecp256k1::RecoveryId::parse(signature_instruction[sig_end])
961            .map_err(|_| PrecompileError::InvalidRecoveryId)?;
962
963        // Parse out pubkey
964        let eth_address_slice = get_data_slice(
965            instruction_datas,
966            offsets.eth_address_instruction_index,
967            offsets.eth_address_offset,
968            HASHED_PUBKEY_SERIALIZED_SIZE,
969        )?;
970
971        // Parse out message
972        let message_slice = get_data_slice(
973            instruction_datas,
974            offsets.message_instruction_index,
975            offsets.message_data_offset,
976            offsets.message_data_size as usize,
977        )?;
978
979        let mut hasher = sha3::Keccak256::new();
980        hasher.update(message_slice);
981        let message_hash = hasher.finalize();
982
983        let pubkey = libsecp256k1::recover(
984            &libsecp256k1::Message::parse_slice(&message_hash).unwrap(),
985            &signature,
986            &recovery_id,
987        )
988        .map_err(|_| PrecompileError::InvalidSignature)?;
989        let eth_address = construct_eth_pubkey(&pubkey);
990
991        if eth_address_slice != eth_address {
992            return Err(PrecompileError::InvalidSignature);
993        }
994    }
995    Ok(())
996}
997
998#[cfg(feature = "bincode")]
999fn get_data_slice<'a>(
1000    instruction_datas: &'a [&[u8]],
1001    instruction_index: u8,
1002    offset_start: u16,
1003    size: usize,
1004) -> Result<&'a [u8], PrecompileError> {
1005    let signature_index = instruction_index as usize;
1006    if signature_index >= instruction_datas.len() {
1007        return Err(PrecompileError::InvalidDataOffsets);
1008    }
1009    let signature_instruction = &instruction_datas[signature_index];
1010    let start = offset_start as usize;
1011    let end = start.saturating_add(size);
1012    if end > signature_instruction.len() {
1013        return Err(PrecompileError::InvalidSignature);
1014    }
1015
1016    Ok(&instruction_datas[signature_index][start..end])
1017}
1018
1019#[cfg(test)]
1020pub mod test {
1021    use {
1022        super::*,
1023        clone_solana_feature_set::FeatureSet,
1024        clone_solana_hash::Hash,
1025        clone_solana_keccak_hasher as keccak,
1026        clone_solana_keypair::Keypair,
1027        clone_solana_sdk::transaction::Transaction,
1028        clone_solana_signer::Signer,
1029        rand0_7::{thread_rng, Rng},
1030    };
1031
1032    fn test_case(
1033        num_signatures: u8,
1034        offsets: &SecpSignatureOffsets,
1035    ) -> Result<(), PrecompileError> {
1036        let mut instruction_data = vec![0u8; DATA_START];
1037        instruction_data[0] = num_signatures;
1038        let writer = std::io::Cursor::new(&mut instruction_data[1..]);
1039        bincode::serialize_into(writer, &offsets).unwrap();
1040        let feature_set = FeatureSet::all_enabled();
1041        verify(&instruction_data, &[&[0u8; 100]], &feature_set)
1042    }
1043
1044    #[test]
1045    fn test_invalid_offsets() {
1046        clone_solana_logger::setup();
1047
1048        let mut instruction_data = vec![0u8; DATA_START];
1049        let offsets = SecpSignatureOffsets::default();
1050        instruction_data[0] = 1;
1051        let writer = std::io::Cursor::new(&mut instruction_data[1..]);
1052        bincode::serialize_into(writer, &offsets).unwrap();
1053        instruction_data.truncate(instruction_data.len() - 1);
1054        let feature_set = FeatureSet::all_enabled();
1055
1056        assert_eq!(
1057            verify(&instruction_data, &[&[0u8; 100]], &feature_set),
1058            Err(PrecompileError::InvalidInstructionDataSize)
1059        );
1060
1061        let offsets = SecpSignatureOffsets {
1062            signature_instruction_index: 1,
1063            ..SecpSignatureOffsets::default()
1064        };
1065        assert_eq!(
1066            test_case(1, &offsets),
1067            Err(PrecompileError::InvalidInstructionDataSize)
1068        );
1069
1070        let offsets = SecpSignatureOffsets {
1071            message_instruction_index: 1,
1072            ..SecpSignatureOffsets::default()
1073        };
1074        assert_eq!(
1075            test_case(1, &offsets),
1076            Err(PrecompileError::InvalidDataOffsets)
1077        );
1078
1079        let offsets = SecpSignatureOffsets {
1080            eth_address_instruction_index: 1,
1081            ..SecpSignatureOffsets::default()
1082        };
1083        assert_eq!(
1084            test_case(1, &offsets),
1085            Err(PrecompileError::InvalidDataOffsets)
1086        );
1087    }
1088
1089    #[test]
1090    fn test_message_data_offsets() {
1091        let offsets = SecpSignatureOffsets {
1092            message_data_offset: 99,
1093            message_data_size: 1,
1094            ..SecpSignatureOffsets::default()
1095        };
1096        assert_eq!(
1097            test_case(1, &offsets),
1098            Err(PrecompileError::InvalidSignature)
1099        );
1100
1101        let offsets = SecpSignatureOffsets {
1102            message_data_offset: 100,
1103            message_data_size: 1,
1104            ..SecpSignatureOffsets::default()
1105        };
1106        assert_eq!(
1107            test_case(1, &offsets),
1108            Err(PrecompileError::InvalidSignature)
1109        );
1110
1111        let offsets = SecpSignatureOffsets {
1112            message_data_offset: 100,
1113            message_data_size: 1000,
1114            ..SecpSignatureOffsets::default()
1115        };
1116        assert_eq!(
1117            test_case(1, &offsets),
1118            Err(PrecompileError::InvalidSignature)
1119        );
1120
1121        let offsets = SecpSignatureOffsets {
1122            message_data_offset: u16::MAX,
1123            message_data_size: u16::MAX,
1124            ..SecpSignatureOffsets::default()
1125        };
1126        assert_eq!(
1127            test_case(1, &offsets),
1128            Err(PrecompileError::InvalidSignature)
1129        );
1130    }
1131
1132    #[test]
1133    fn test_eth_offset() {
1134        let offsets = SecpSignatureOffsets {
1135            eth_address_offset: u16::MAX,
1136            ..SecpSignatureOffsets::default()
1137        };
1138        assert_eq!(
1139            test_case(1, &offsets),
1140            Err(PrecompileError::InvalidSignature)
1141        );
1142
1143        let offsets = SecpSignatureOffsets {
1144            eth_address_offset: 100 - HASHED_PUBKEY_SERIALIZED_SIZE as u16 + 1,
1145            ..SecpSignatureOffsets::default()
1146        };
1147        assert_eq!(
1148            test_case(1, &offsets),
1149            Err(PrecompileError::InvalidSignature)
1150        );
1151    }
1152
1153    #[test]
1154    fn test_signature_offset() {
1155        let offsets = SecpSignatureOffsets {
1156            signature_offset: u16::MAX,
1157            ..SecpSignatureOffsets::default()
1158        };
1159        assert_eq!(
1160            test_case(1, &offsets),
1161            Err(PrecompileError::InvalidSignature)
1162        );
1163
1164        let offsets = SecpSignatureOffsets {
1165            signature_offset: 100 - SIGNATURE_SERIALIZED_SIZE as u16 + 1,
1166            ..SecpSignatureOffsets::default()
1167        };
1168        assert_eq!(
1169            test_case(1, &offsets),
1170            Err(PrecompileError::InvalidSignature)
1171        );
1172    }
1173
1174    #[test]
1175    fn test_count_is_zero_but_sig_data_exists() {
1176        clone_solana_logger::setup();
1177
1178        let mut instruction_data = vec![0u8; DATA_START];
1179        let offsets = SecpSignatureOffsets::default();
1180        instruction_data[0] = 0;
1181        let writer = std::io::Cursor::new(&mut instruction_data[1..]);
1182        bincode::serialize_into(writer, &offsets).unwrap();
1183        let feature_set = FeatureSet::all_enabled();
1184
1185        assert_eq!(
1186            verify(&instruction_data, &[&[0u8; 100]], &feature_set),
1187            Err(PrecompileError::InvalidInstructionDataSize)
1188        );
1189    }
1190
1191    #[test]
1192    fn test_secp256k1() {
1193        clone_solana_logger::setup();
1194        let offsets = SecpSignatureOffsets::default();
1195        assert_eq!(
1196            bincode::serialized_size(&offsets).unwrap() as usize,
1197            SIGNATURE_OFFSETS_SERIALIZED_SIZE
1198        );
1199
1200        let secp_privkey = libsecp256k1::SecretKey::random(&mut thread_rng());
1201        let message_arr = b"hello";
1202        let mut secp_instruction = new_secp256k1_instruction(&secp_privkey, message_arr);
1203        let mint_keypair = Keypair::new();
1204        let feature_set = clone_solana_feature_set::FeatureSet::all_enabled();
1205
1206        let tx = Transaction::new_signed_with_payer(
1207            &[secp_instruction.clone()],
1208            Some(&mint_keypair.pubkey()),
1209            &[&mint_keypair],
1210            Hash::default(),
1211        );
1212
1213        assert!(tx.verify_precompiles(&feature_set).is_ok());
1214
1215        let index = thread_rng().gen_range(0, secp_instruction.data.len());
1216        secp_instruction.data[index] = secp_instruction.data[index].wrapping_add(12);
1217        let tx = Transaction::new_signed_with_payer(
1218            &[secp_instruction],
1219            Some(&mint_keypair.pubkey()),
1220            &[&mint_keypair],
1221            Hash::default(),
1222        );
1223        assert!(tx.verify_precompiles(&feature_set).is_err());
1224    }
1225
1226    // Signatures are malleable.
1227    #[test]
1228    fn test_malleability() {
1229        clone_solana_logger::setup();
1230
1231        let secret_key = libsecp256k1::SecretKey::random(&mut thread_rng());
1232        let public_key = libsecp256k1::PublicKey::from_secret_key(&secret_key);
1233        let eth_address = construct_eth_pubkey(&public_key);
1234
1235        let message = b"hello";
1236        let message_hash = {
1237            let mut hasher = keccak::Hasher::default();
1238            hasher.hash(message);
1239            hasher.result()
1240        };
1241
1242        let secp_message = libsecp256k1::Message::parse(&message_hash.0);
1243        let (signature, recovery_id) = libsecp256k1::sign(&secp_message, &secret_key);
1244
1245        // Flip the S value in the signature to make a different but valid signature.
1246        let mut alt_signature = signature;
1247        alt_signature.s = -alt_signature.s;
1248        let alt_recovery_id = libsecp256k1::RecoveryId::parse(recovery_id.serialize() ^ 1).unwrap();
1249
1250        let mut data: Vec<u8> = vec![];
1251        let mut both_offsets = vec![];
1252
1253        // Verify both signatures of the same message.
1254        let sigs = [(signature, recovery_id), (alt_signature, alt_recovery_id)];
1255        for (signature, recovery_id) in sigs.iter() {
1256            let signature_offset = data.len();
1257            data.extend(signature.serialize());
1258            data.push(recovery_id.serialize());
1259            let eth_address_offset = data.len();
1260            data.extend(eth_address);
1261            let message_data_offset = data.len();
1262            data.extend(message);
1263
1264            let data_start = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE * 2;
1265
1266            let offsets = SecpSignatureOffsets {
1267                signature_offset: (signature_offset + data_start) as u16,
1268                signature_instruction_index: 0,
1269                eth_address_offset: (eth_address_offset + data_start) as u16,
1270                eth_address_instruction_index: 0,
1271                message_data_offset: (message_data_offset + data_start) as u16,
1272                message_data_size: message.len() as u16,
1273                message_instruction_index: 0,
1274            };
1275
1276            both_offsets.push(offsets);
1277        }
1278
1279        let mut instruction_data: Vec<u8> = vec![2];
1280
1281        for offsets in both_offsets {
1282            let offsets = bincode::serialize(&offsets).unwrap();
1283            instruction_data.extend(offsets);
1284        }
1285
1286        instruction_data.extend(data);
1287
1288        verify(
1289            &instruction_data,
1290            &[&instruction_data],
1291            &FeatureSet::all_enabled(),
1292        )
1293        .unwrap();
1294    }
1295}