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 [`eth_address_from_pubkey`] function.
23//!
24//! [`keccak`]: https://docs.rs/solana-sdk/latest/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/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_with_signature`] function is suitable for
45//! building a secp256k1 program instruction for basic use cases where a single
46//! message must be signed by a known secret key. For other uses cases, including
47//! many 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/solana_program/sysvar/instructions/fn.load_instruction_at_checked.html
63//! [`get_instruction_relative`]: https://docs.rs/solana-program/latest/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 [`k256`] 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/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_with_signature` for simple signature verification.
96//!
97//! The `solana_secp256k1_program` crate provides few APIs for building the
98//! instructions and transactions necessary for properly using the secp256k1
99//! native program. Many steps must be done manually.
100//!
101//! The `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//! but clients may want to use the [`k256`] crate.
106//!
107//! [`libsecp256k1`]: https://docs.rs/libsecp256k1/latest/libsecp256k1
108//! [`k256`]: https://docs.rs/k256/latest/k256
109//!
110//! # Layout and interpretation of the secp256k1 instruction data
111//!
112//! The secp256k1 instruction data contains:
113//!
114//! - 1 byte indicating the number of signatures to verify, 0 - 255,
115//! - A number of _signature offset_ structures that indicate where in the
116//! transaction to locate each signature, message, and Ethereum address.
117//! - 0 or more bytes of arbitrary data, which may contain signatures,
118//! messages or Ethereum addresses.
119//!
120//! The signature offset structure is defined by [`SecpSignatureOffsets`],
121//! and can be serialized to the correct format with [`bincode::serialize_into`].
122//! Note that the bincode format may not be stable,
123//! and callers should ensure they use the same version of `bincode` as the Solana SDK.
124//! This data structure is not provided to Solana programs,
125//! which are expected to interpret the signature offsets manually.
126//!
127//! [`bincode::serialize_into`]: https://docs.rs/bincode/1.3.3/bincode/fn.serialize_into.html
128//!
129//! The serialized signature offset structure has the following 11-byte layout,
130//! with data types in little-endian encoding.
131//!
132//! | index | bytes | type | description |
133//! |--------|-------|-------|-------------|
134//! | 0 | 2 | `u16` | `signature_offset` - offset to 64-byte signature plus 1-byte recovery ID. |
135//! | 2 | 1 | `u8` | `signature_offset_instruction_index` - within the transaction, the index of the transaction whose instruction data contains the signature. |
136//! | 3 | 2 | `u16` | `eth_address_offset` - offset to 20-byte Ethereum address. |
137//! | 5 | 1 | `u8` | `eth_address_instruction_index` - within the transaction, the index of the instruction whose instruction data contains the Ethereum address. |
138//! | 6 | 2 | `u16` | `message_data_offset` - Offset to start of message data. |
139//! | 8 | 2 | `u16` | `message_data_size` - Size of message data in bytes. |
140//! | 10 | 1 | `u8` | `message_instruction_index` - Within the transaction, the index of the instruction whose instruction data contains the message data. |
141//!
142//! # Signature malleability
143//!
144//! With the ECDSA signature algorithm it is possible for any party, given a
145//! valid signature of some message, to create a second signature that is
146//! equally valid. This is known as _signature malleability_. In many cases this
147//! is not a concern, but in cases where applications rely on signatures to have
148//! a unique representation this can be the source of bugs, potentially with
149//! security implications.
150//!
151//! **The solana `secp256k1_recover` function does not prevent signature
152//! malleability**. This is in contrast to the Bitcoin secp256k1 library, which
153//! does prevent malleability by default. Solana accepts signatures with `S`
154//! values that are either in the _high order_ or in the _low order_, and it
155//! is trivial to produce one from the other.
156//!
157//! For more complete documentation of the subject, and techniques to prevent
158//! malleability, see the documentation for the [`secp256k1_recover`] syscall.
159//!
160//! # Additional security considerations
161//!
162//! Most programs will want to be conservative about the layout of the secp256k1 instruction
163//! to prevent unforeseen bugs. The following checks may be desirable:
164//!
165//! - That there are exactly the expected number of signatures.
166//! - That the three indexes, `signature_offset_instruction_index`,
167//! `eth_address_instruction_index`, and `message_instruction_index` are as
168//! expected, placing the signature, message and Ethereum address in the
169//! expected instruction.
170//!
171//! Loading the secp256k1 instruction data within a program requires access to
172//! the [instructions sysvar][is], which must be passed to the program by its
173//! caller. Programs must verify the ID of this program to avoid calling an
174//! imposter program. This does not need to be done manually though, as long as
175//! it is only used through the [`load_instruction_at_checked`] or
176//! [`get_instruction_relative`] functions. Both of these functions check their
177//! sysvar argument to ensure it is the known instruction sysvar.
178//!
179//! [is]: https://docs.rs/solana-program/latest/solana_program/sysvar/instructions/index.html
180//!
181//! Programs should _always_ verify that the secp256k1 program ID loaded through
182//! the instructions sysvar has the same value as in the [`secp256k1_program`]
183//! module. Again this prevents imposter programs.
184//!
185//! [`secp256k1_program`]: https://docs.rs/solana-program/latest/solana_program/secp256k1_program/index.html
186//!
187//! # Errors
188//!
189//! The transaction will fail if any of the following are true:
190//!
191//! - Any signature was not created by the secret key corresponding to the
192//! specified public key.
193//! - Any signature is invalid.
194//! - Any signature is "overflowing", a non-standard condition.
195//! - The instruction data is empty.
196//! - The first byte of instruction data is equal to 0 (indicating no signatures),
197//! but the instruction data's length is greater than 1.
198//! - The instruction data is not long enough to hold the number of signature
199//! offsets specified in the first byte.
200//! - Any instruction indexes specified in the signature offsets are greater or
201//! equal to the number of instructions in the transaction.
202//! - Any bounds specified in the signature offsets exceed the bounds of the
203//! instruction data to which they are indexed.
204//!
205//! # Examples
206//!
207//! Both of the following examples make use of the following module definition
208//! to parse the secp256k1 instruction data from within a Solana program.
209//!
210//! ```no_run
211//! mod secp256k1_defs {
212//! use solana_program_error::ProgramError;
213//! use std::iter::Iterator;
214//!
215//! pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
216//! pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
217//! pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
218//!
219//! /// The structure encoded in the secp2256k1 instruction data.
220//! pub struct SecpSignatureOffsets {
221//! pub signature_offset: u16,
222//! pub signature_instruction_index: u8,
223//! pub eth_address_offset: u16,
224//! pub eth_address_instruction_index: u8,
225//! pub message_data_offset: u16,
226//! pub message_data_size: u16,
227//! pub message_instruction_index: u8,
228//! }
229//!
230//! pub fn iter_signature_offsets(
231//! secp256k1_instr_data: &[u8],
232//! ) -> Result<impl Iterator<Item = SecpSignatureOffsets> + '_, ProgramError> {
233//! // First element is the number of `SecpSignatureOffsets`.
234//! let num_structs = *secp256k1_instr_data
235//! .get(0)
236//! .ok_or(ProgramError::InvalidArgument)?;
237//!
238//! let all_structs_size = SIGNATURE_OFFSETS_SERIALIZED_SIZE * num_structs as usize;
239//! let all_structs_slice = secp256k1_instr_data
240//! .get(1..all_structs_size + 1)
241//! .ok_or(ProgramError::InvalidArgument)?;
242//!
243//! fn decode_u16(chunk: &[u8], index: usize) -> u16 {
244//! u16::from_le_bytes(<[u8; 2]>::try_from(&chunk[index..index + 2]).unwrap())
245//! }
246//!
247//! Ok(all_structs_slice
248//! .chunks(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
249//! .map(|chunk| SecpSignatureOffsets {
250//! signature_offset: decode_u16(chunk, 0),
251//! signature_instruction_index: chunk[2],
252//! eth_address_offset: decode_u16(chunk, 3),
253//! eth_address_instruction_index: chunk[5],
254//! message_data_offset: decode_u16(chunk, 6),
255//! message_data_size: decode_u16(chunk, 8),
256//! message_instruction_index: chunk[10],
257//! }))
258//! }
259//! }
260//! ```
261//!
262//! ## Example: Signing and verifying with `new_secp256k1_instruction_with_signature`
263//!
264//! This example demonstrates the simplest way to use the secp256k1 program, by
265//! calling [`new_secp256k1_instruction_with_signature`] to sign a single message
266//! and build the corresponding secp256k1 instruction.
267//!
268//! This example has two components: a Solana program, and an RPC client that
269//! sends a transaction to call it. The RPC client will sign a single message,
270//! and the Solana program will introspect the secp256k1 instruction to verify
271//! that the signer matches a known authorized public key.
272//!
273//! The Solana program. Note that it uses `k256` version 0.13.0 to parse the
274//! secp256k1 signature to prevent malleability.
275//!
276//! ```no_run
277//! # mod secp256k1_defs {
278//! # use solana_program_error::ProgramError;
279//! # use std::iter::Iterator;
280//! #
281//! # pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
282//! # pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
283//! # pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
284//! #
285//! # /// The structure encoded in the secp2256k1 instruction data.
286//! # pub struct SecpSignatureOffsets {
287//! # pub signature_offset: u16,
288//! # pub signature_instruction_index: u8,
289//! # pub eth_address_offset: u16,
290//! # pub eth_address_instruction_index: u8,
291//! # pub message_data_offset: u16,
292//! # pub message_data_size: u16,
293//! # pub message_instruction_index: u8,
294//! # }
295//! #
296//! # pub fn iter_signature_offsets(
297//! # secp256k1_instr_data: &[u8],
298//! # ) -> Result<impl Iterator<Item = SecpSignatureOffsets> + '_, ProgramError> {
299//! # // First element is the number of `SecpSignatureOffsets`.
300//! # let num_structs = *secp256k1_instr_data
301//! # .get(0)
302//! # .ok_or(ProgramError::InvalidArgument)?;
303//! #
304//! # let all_structs_size = SIGNATURE_OFFSETS_SERIALIZED_SIZE * num_structs as usize;
305//! # let all_structs_slice = secp256k1_instr_data
306//! # .get(1..all_structs_size + 1)
307//! # .ok_or(ProgramError::InvalidArgument)?;
308//! #
309//! # fn decode_u16(chunk: &[u8], index: usize) -> u16 {
310//! # u16::from_le_bytes(<[u8; 2]>::try_from(&chunk[index..index + 2]).unwrap())
311//! # }
312//! #
313//! # Ok(all_structs_slice
314//! # .chunks(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
315//! # .map(|chunk| SecpSignatureOffsets {
316//! # signature_offset: decode_u16(chunk, 0),
317//! # signature_instruction_index: chunk[2],
318//! # eth_address_offset: decode_u16(chunk, 3),
319//! # eth_address_instruction_index: chunk[5],
320//! # message_data_offset: decode_u16(chunk, 6),
321//! # message_data_size: decode_u16(chunk, 8),
322//! # message_instruction_index: chunk[10],
323//! # }))
324//! # }
325//! # }
326//! use k256::elliptic_curve::scalar::IsHigh;
327//! use solana_account_info::{next_account_info, AccountInfo};
328//! use solana_msg::msg;
329//! use solana_program_error::{ProgramError, ProgramResult};
330//! use solana_sdk_ids::secp256k1_program;
331//! use solana_instructions_sysvar::load_instruction_at_checked;
332//!
333//! /// An Ethereum address corresponding to a secp256k1 secret key that is
334//! /// authorized to sign our messages.
335//! const AUTHORIZED_ETH_ADDRESS: [u8; 20] = [
336//! 0x18, 0x8a, 0x5c, 0xf2, 0x3b, 0x0e, 0xff, 0xe9, 0xa8, 0xe1, 0x42, 0x64, 0x5b, 0x82, 0x2f, 0x3a,
337//! 0x6b, 0x8b, 0x52, 0x35,
338//! ];
339//!
340//! /// Check the secp256k1 instruction to ensure it was signed by
341//! /// `AUTHORIZED_ETH_ADDRESS`s key.
342//! ///
343//! /// `accounts` is the slice of all accounts passed to the program
344//! /// entrypoint. The only account it should contain is the instructions sysvar.
345//! fn demo_secp256k1_verify_basic(
346//! accounts: &[AccountInfo],
347//! ) -> ProgramResult {
348//! let account_info_iter = &mut accounts.iter();
349//!
350//! // The instructions sysvar gives access to the instructions in the transaction.
351//! let instructions_sysvar_account = next_account_info(account_info_iter)?;
352//! assert!(solana_sdk_ids::sysvar::instructions::check_id(
353//! instructions_sysvar_account.key
354//! ));
355//!
356//! // Load the secp256k1 instruction.
357//! // `new_secp256k1_instruction_with_signature` generates an instruction
358//! // that must be at index 0.
359//! let secp256k1_instr =
360//! solana_instructions_sysvar::load_instruction_at_checked(0, instructions_sysvar_account)?;
361//!
362//! // Verify it is a secp256k1 instruction.
363//! // This is security-critical - what if the transaction uses an imposter secp256k1 program?
364//! assert!(secp256k1_program::check_id(&secp256k1_instr.program_id));
365//!
366//! // There must be at least one byte. This is also verified by the runtime,
367//! // and doesn't strictly need to be checked.
368//! assert!(secp256k1_instr.data.len() > 1);
369//!
370//! let num_signatures = secp256k1_instr.data[0];
371//! // `new_secp256k1_instruction_with_signature` generates an instruction
372//! // that contains one signature.
373//! assert_eq!(1, num_signatures);
374//!
375//! // Load the first and only set of signature offsets.
376//! let offsets: secp256k1_defs::SecpSignatureOffsets =
377//! secp256k1_defs::iter_signature_offsets(&secp256k1_instr.data)?
378//! .next()
379//! .ok_or(ProgramError::InvalidArgument)?;
380//!
381//! // `new_secp256k1_instruction_with_signature` generates an instruction
382//! // that only uses instruction index 0.
383//! assert_eq!(0, offsets.signature_instruction_index);
384//! assert_eq!(0, offsets.eth_address_instruction_index);
385//! assert_eq!(0, offsets.message_instruction_index);
386//!
387//! // Reject high-s value signatures to prevent malleability.
388//! // Solana does not do this itself.
389//! // This may or may not be necessary depending on use case.
390//! {
391//! let signature = &secp256k1_instr.data[offsets.signature_offset as usize
392//! ..offsets.signature_offset as usize + secp256k1_defs::SIGNATURE_SERIALIZED_SIZE];
393//! let signature = k256::ecdsa::Signature::from_slice(signature)
394//! .map_err(|_| ProgramError::InvalidArgument)?;
395//!
396//! if bool::from(signature.s().is_high()) {
397//! msg!("signature with high-s value");
398//! return Err(ProgramError::InvalidArgument);
399//! }
400//! }
401//!
402//! // There is likely at least one more verification step a real program needs
403//! // to do here to ensure it trusts the secp256k1 instruction, e.g.:
404//! //
405//! // - verify the tx signer is authorized
406//! // - verify the secp256k1 signer is authorized
407//!
408//! // Here we are checking the secp256k1 pubkey against a known authorized pubkey.
409//! let eth_address = &secp256k1_instr.data[offsets.eth_address_offset as usize
410//! ..offsets.eth_address_offset as usize + secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE];
411//!
412//! if eth_address != AUTHORIZED_ETH_ADDRESS {
413//! return Err(ProgramError::InvalidArgument);
414//! }
415//!
416//! Ok(())
417//! }
418//! ```
419//!
420//! The client program:
421//!
422//! ```no_run
423//! # use solana_example_mocks::{solana_keypair, solana_rpc_client, solana_signer, solana_transaction};
424//! use anyhow::Result;
425//! use solana_instruction::{AccountMeta, Instruction};
426//! use solana_keypair::Keypair;
427//! use solana_rpc_client::rpc_client::RpcClient;
428//! use solana_signer::Signer;
429//! use solana_transaction::Transaction;
430//! use solana_secp256k1_program::{
431//! eth_address_from_pubkey, new_secp256k1_instruction_with_signature,
432//! sign_message,
433//! };
434//!
435//! fn demo_secp256k1_verify_basic(
436//! payer_keypair: &Keypair,
437//! secp256k1_secret_key: &k256::ecdsa::SigningKey,
438//! client: &RpcClient,
439//! program_keypair: &Keypair,
440//! ) -> Result<()> {
441//! // Internally to `sign_message` and `secp256k_instruction::verify`
442//! // (the secp256k1 program), this message is keccak-hashed before signing.
443//! let msg = b"hello world";
444//! let secp_pubkey = secp256k1_secret_key.verifying_key();
445//! let eth_address = eth_address_from_pubkey(&secp_pubkey.to_encoded_point(false).as_bytes()[1..].try_into().unwrap());
446//! let (signature, recovery_id) = sign_message(&secp256k1_secret_key.to_bytes().into(), msg).unwrap();
447//! let secp256k1_instr = new_secp256k1_instruction_with_signature(msg, &signature, recovery_id, ð_address);
448//!
449//! let program_instr = Instruction::new_with_bytes(
450//! program_keypair.pubkey(),
451//! &[],
452//! vec![
453//! AccountMeta::new_readonly(solana_sdk_ids::sysvar::instructions::ID, false)
454//! ],
455//! );
456//!
457//! let blockhash = client.get_latest_blockhash()?;
458//! let tx = Transaction::new_signed_with_payer(
459//! &[secp256k1_instr, program_instr],
460//! Some(&payer_keypair.pubkey()),
461//! &[payer_keypair],
462//! blockhash,
463//! );
464//!
465//! client.send_and_confirm_transaction(&tx)?;
466//!
467//! Ok(())
468//! }
469//! ```
470//!
471//! ## Example: Verifying multiple signatures in one instruction
472//!
473//! This example demonstrates manually creating a secp256k1 instruction
474//! containing many signatures, and a Solana program that parses them all. This
475//! example on its own has no practical purpose. It simply demonstrates advanced
476//! use of the secp256k1 program.
477//!
478//! Recall that the secp256k1 program will accept signatures, messages, and
479//! Ethereum addresses that reside in any instruction contained in the same
480//! transaction. In the _previous_ example, the Solana program asserted that all
481//! signatures, messages, and addresses were stored in the instruction at 0. In
482//! this next example the Solana program supports signatures, messages, and
483//! addresses stored in any instruction. For simplicity the client still only
484//! stores signatures, messages, and addresses in a single instruction, the
485//! secp256k1 instruction. The code for storing this data across multiple
486//! instructions would be complex, and may not be necessary in practice.
487//!
488//! This example has two components: a Solana program, and an RPC client that
489//! sends a transaction to call it.
490//!
491//! The Solana program:
492//!
493//! ```no_run
494//! # mod secp256k1_defs {
495//! # use solana_program_error::ProgramError;
496//! # use std::iter::Iterator;
497//! #
498//! # pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
499//! # pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
500//! # pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
501//! #
502//! # /// The structure encoded in the secp2256k1 instruction data.
503//! # pub struct SecpSignatureOffsets {
504//! # pub signature_offset: u16,
505//! # pub signature_instruction_index: u8,
506//! # pub eth_address_offset: u16,
507//! # pub eth_address_instruction_index: u8,
508//! # pub message_data_offset: u16,
509//! # pub message_data_size: u16,
510//! # pub message_instruction_index: u8,
511//! # }
512//! #
513//! # pub fn iter_signature_offsets(
514//! # secp256k1_instr_data: &[u8],
515//! # ) -> Result<impl Iterator<Item = SecpSignatureOffsets> + '_, ProgramError> {
516//! # // First element is the number of `SecpSignatureOffsets`.
517//! # let num_structs = *secp256k1_instr_data
518//! # .get(0)
519//! # .ok_or(ProgramError::InvalidArgument)?;
520//! #
521//! # let all_structs_size = SIGNATURE_OFFSETS_SERIALIZED_SIZE * num_structs as usize;
522//! # let all_structs_slice = secp256k1_instr_data
523//! # .get(1..all_structs_size + 1)
524//! # .ok_or(ProgramError::InvalidArgument)?;
525//! #
526//! # fn decode_u16(chunk: &[u8], index: usize) -> u16 {
527//! # u16::from_le_bytes(<[u8; 2]>::try_from(&chunk[index..index + 2]).unwrap())
528//! # }
529//! #
530//! # Ok(all_structs_slice
531//! # .chunks(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
532//! # .map(|chunk| SecpSignatureOffsets {
533//! # signature_offset: decode_u16(chunk, 0),
534//! # signature_instruction_index: chunk[2],
535//! # eth_address_offset: decode_u16(chunk, 3),
536//! # eth_address_instruction_index: chunk[5],
537//! # message_data_offset: decode_u16(chunk, 6),
538//! # message_data_size: decode_u16(chunk, 8),
539//! # message_instruction_index: chunk[10],
540//! # }))
541//! # }
542//! # }
543//! use solana_account_info::{next_account_info, AccountInfo};
544//! use solana_program_error::{ProgramError, ProgramResult};
545//! use solana_msg::msg;
546//! use solana_sdk_ids::secp256k1_program;
547//! use solana_instructions_sysvar::{get_instruction_relative, load_instruction_at_checked};
548//!
549//! /// A struct to hold the values specified in the `SecpSignatureOffsets` struct.
550//! struct SecpSignature {
551//! signature: [u8; secp256k1_defs::SIGNATURE_SERIALIZED_SIZE],
552//! recovery_id: u8,
553//! eth_address: [u8; secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE],
554//! message: Vec<u8>,
555//! }
556//!
557//! /// Load all signatures indicated in the secp256k1 instruction.
558//! ///
559//! /// This function is quite inefficient for reloading the same instructions
560//! /// repeatedly and making copies and allocations.
561//! fn load_signatures(
562//! secp256k1_instr_data: &[u8],
563//! instructions_sysvar_account: &AccountInfo,
564//! ) -> Result<Vec<SecpSignature>, ProgramError> {
565//! let mut sigs = vec![];
566//! for offsets in secp256k1_defs::iter_signature_offsets(secp256k1_instr_data)? {
567//! let signature_instr = load_instruction_at_checked(
568//! offsets.signature_instruction_index as usize,
569//! instructions_sysvar_account,
570//! )?;
571//! let eth_address_instr = load_instruction_at_checked(
572//! offsets.eth_address_instruction_index as usize,
573//! instructions_sysvar_account,
574//! )?;
575//! let message_instr = load_instruction_at_checked(
576//! offsets.message_instruction_index as usize,
577//! instructions_sysvar_account,
578//! )?;
579//!
580//! // These indexes must all be valid because the runtime already verified them.
581//! let signature = &signature_instr.data[offsets.signature_offset as usize
582//! ..offsets.signature_offset as usize + secp256k1_defs::SIGNATURE_SERIALIZED_SIZE];
583//! let recovery_id = signature_instr.data
584//! [offsets.signature_offset as usize + secp256k1_defs::SIGNATURE_SERIALIZED_SIZE];
585//! let eth_address = ð_address_instr.data[offsets.eth_address_offset as usize
586//! ..offsets.eth_address_offset as usize + secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE];
587//! let message = &message_instr.data[offsets.message_data_offset as usize
588//! ..offsets.message_data_offset as usize + offsets.message_data_size as usize];
589//!
590//! let signature =
591//! <[u8; secp256k1_defs::SIGNATURE_SERIALIZED_SIZE]>::try_from(signature).unwrap();
592//! let eth_address =
593//! <[u8; secp256k1_defs::HASHED_PUBKEY_SERIALIZED_SIZE]>::try_from(eth_address).unwrap();
594//! let message = Vec::from(message);
595//!
596//! sigs.push(SecpSignature {
597//! signature,
598//! recovery_id,
599//! eth_address,
600//! message,
601//! })
602//! }
603//! Ok(sigs)
604//! }
605//!
606//! fn demo_secp256k1_custom_many(
607//! accounts: &[AccountInfo],
608//! ) -> ProgramResult {
609//! let account_info_iter = &mut accounts.iter();
610//!
611//! let instructions_sysvar_account = next_account_info(account_info_iter)?;
612//! assert!(solana_sdk_ids::sysvar::instructions::check_id(
613//! instructions_sysvar_account.key
614//! ));
615//!
616//! let secp256k1_instr =
617//! solana_instructions_sysvar::get_instruction_relative(-1, instructions_sysvar_account)?;
618//!
619//! assert!(secp256k1_program::check_id(&secp256k1_instr.program_id));
620//!
621//! let signatures = load_signatures(&secp256k1_instr.data, instructions_sysvar_account)?;
622//! for (idx, signature_bundle) in signatures.iter().enumerate() {
623//! let signature = hex::encode(&signature_bundle.signature);
624//! let eth_address = hex::encode(&signature_bundle.eth_address);
625//! let message = hex::encode(&signature_bundle.message);
626//! msg!("sig {}: {:?}", idx, signature);
627//! msg!("recid: {}: {}", idx, signature_bundle.recovery_id);
628//! msg!("eth address {}: {}", idx, eth_address);
629//! msg!("message {}: {}", idx, message);
630//! }
631//!
632//! Ok(())
633//! }
634//! ```
635//!
636//! The client program:
637//!
638//! ```no_run
639//! # use solana_example_mocks::{solana_keypair, solana_rpc_client, solana_signer, solana_transaction};
640//! use anyhow::Result;
641//! use solana_instruction::{AccountMeta, Instruction};
642//! use solana_rpc_client::rpc_client::RpcClient;
643//! use solana_secp256k1_program::{
644//! eth_address_from_pubkey, SecpSignatureOffsets, HASHED_PUBKEY_SERIALIZED_SIZE,
645//! SIGNATURE_OFFSETS_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE,
646//! };
647//! use solana_signer::Signer;
648//! use solana_keypair::Keypair;
649//! use solana_transaction::Transaction;
650//!
651//! /// A struct to hold the values specified in the `SecpSignatureOffsets` struct.
652//! struct SecpSignature {
653//! signature: [u8; SIGNATURE_SERIALIZED_SIZE],
654//! recovery_id: u8,
655//! eth_address: [u8; HASHED_PUBKEY_SERIALIZED_SIZE],
656//! message: Vec<u8>,
657//! }
658//!
659//! /// Create the instruction data for a secp256k1 instruction.
660//! ///
661//! /// `instruction_index` is the index the secp256k1 instruction will appear
662//! /// within the transaction. For simplicity, this function only supports packing
663//! /// the signatures into the secp256k1 instruction data, and not into any other
664//! /// instructions within the transaction.
665//! fn make_secp256k1_instruction_data(
666//! signatures: &[SecpSignature],
667//! instruction_index: u8,
668//! ) -> Result<Vec<u8>> {
669//! assert!(signatures.len() <= u8::MAX.into());
670//!
671//! // We're going to pack all the signatures into the secp256k1 instruction data.
672//! // Before our signatures though is the signature offset structures
673//! // the secp256k1 program parses to find those signatures.
674//! // This value represents the byte offset where the signatures begin.
675//! let data_start = 1 + signatures.len() * SIGNATURE_OFFSETS_SERIALIZED_SIZE;
676//!
677//! let mut signature_offsets = vec![];
678//! let mut signature_buffer = vec![];
679//!
680//! for signature_bundle in signatures {
681//! let data_start = data_start
682//! .checked_add(signature_buffer.len())
683//! .expect("overflow");
684//!
685//! let signature_offset = data_start;
686//! let eth_address_offset = data_start
687//! .checked_add(SIGNATURE_SERIALIZED_SIZE + 1)
688//! .expect("overflow");
689//! let message_data_offset = eth_address_offset
690//! .checked_add(HASHED_PUBKEY_SERIALIZED_SIZE)
691//! .expect("overflow");
692//! let message_data_size = signature_bundle.message.len();
693//!
694//! let signature_offset = u16::try_from(signature_offset)?;
695//! let eth_address_offset = u16::try_from(eth_address_offset)?;
696//! let message_data_offset = u16::try_from(message_data_offset)?;
697//! let message_data_size = u16::try_from(message_data_size)?;
698//!
699//! signature_offsets.push(SecpSignatureOffsets {
700//! signature_offset,
701//! signature_instruction_index: instruction_index,
702//! eth_address_offset,
703//! eth_address_instruction_index: instruction_index,
704//! message_data_offset,
705//! message_data_size,
706//! message_instruction_index: instruction_index,
707//! });
708//!
709//! signature_buffer.extend(signature_bundle.signature);
710//! signature_buffer.push(signature_bundle.recovery_id);
711//! signature_buffer.extend(&signature_bundle.eth_address);
712//! signature_buffer.extend(&signature_bundle.message);
713//! }
714//!
715//! let mut instr_data = vec![];
716//! instr_data.push(signatures.len() as u8);
717//!
718//! for offsets in signature_offsets {
719//! let offsets = bincode::serialize(&offsets)?;
720//! instr_data.extend(offsets);
721//! }
722//!
723//! instr_data.extend(signature_buffer);
724//!
725//! Ok(instr_data)
726//! }
727//!
728//! fn demo_secp256k1_custom_many(
729//! payer_keypair: &Keypair,
730//! client: &RpcClient,
731//! program_keypair: &Keypair,
732//! ) -> Result<()> {
733//! // Sign some messages.
734//! let mut signatures = vec![];
735//! for idx in 0..2 {
736//! let secret_key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
737//! let message = format!("hello world {}", idx).into_bytes();
738//! let message_hash = {
739//! let mut hasher = solana_keccak_hasher::Hasher::default();
740//! hasher.hash(&message);
741//! hasher.result()
742//! };
743//! let (signature, recovery_id) =
744//! secret_key.sign_prehash_recoverable(message_hash.as_bytes()).unwrap();
745//! let signature = signature.to_bytes().into();
746//! let recovery_id = recovery_id.to_byte();
747//!
748//! let public_key = secret_key.verifying_key();
749//! let eth_address =
750//! eth_address_from_pubkey(&public_key.to_encoded_point(false).as_bytes()[1..].try_into().unwrap());
751//!
752//! signatures.push(SecpSignature {
753//! signature,
754//! recovery_id,
755//! eth_address,
756//! message,
757//! });
758//! }
759//!
760//! let secp256k1_instr_data = make_secp256k1_instruction_data(&signatures, 0)?;
761//! let secp256k1_instr = Instruction::new_with_bytes(
762//! solana_sdk_ids::secp256k1_program::ID,
763//! &secp256k1_instr_data,
764//! vec![],
765//! );
766//!
767//! let program_instr = Instruction::new_with_bytes(
768//! program_keypair.pubkey(),
769//! &[],
770//! vec![
771//! AccountMeta::new_readonly(solana_sdk_ids::sysvar::instructions::ID, false)
772//! ],
773//! );
774//!
775//! let blockhash = client.get_latest_blockhash()?;
776//! let tx = Transaction::new_signed_with_payer(
777//! &[secp256k1_instr, program_instr],
778//! Some(&payer_keypair.pubkey()),
779//! &[payer_keypair],
780//! blockhash,
781//! );
782//!
783//! client.send_and_confirm_transaction(&tx)?;
784//!
785//! Ok(())
786//! }
787//! ```
788
789#[cfg(feature = "serde")]
790use serde_derive::{Deserialize, Serialize};
791#[cfg(feature = "bincode")]
792use solana_instruction::Instruction;
793use {digest::Digest, solana_signature::error::Error};
794
795pub const SECP256K1_PUBKEY_SIZE: usize = 64;
796pub const SECP256K1_PRIVATE_KEY_SIZE: usize = 32;
797pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
798
799pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
800pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
801pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + 1;
802
803/// Offsets of signature data within a secp256k1 instruction.
804///
805/// See the [module documentation][md] for a complete description.
806///
807/// [md]: self
808#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
809#[derive(Default, Debug, Eq, PartialEq)]
810pub struct SecpSignatureOffsets {
811 /// Offset to 64-byte signature plus 1-byte recovery ID.
812 pub signature_offset: u16,
813 /// Within the transaction, the index of the instruction whose instruction data contains the signature.
814 pub signature_instruction_index: u8,
815 /// Offset to 20-byte Ethereum address.
816 pub eth_address_offset: u16,
817 /// Within the transaction, the index of the instruction whose instruction data contains the address.
818 pub eth_address_instruction_index: u8,
819 /// Offset to start of message data.
820 pub message_data_offset: u16,
821 /// Size of message data in bytes.
822 pub message_data_size: u16,
823 /// Within the transaction, the index of the instruction whose instruction data contains the message.
824 pub message_instruction_index: u8,
825}
826
827/// Signs a message from the given private key bytes
828pub fn sign_message(
829 priv_key_bytes: &[u8; SECP256K1_PRIVATE_KEY_SIZE],
830 message: &[u8],
831) -> Result<([u8; SIGNATURE_SERIALIZED_SIZE], u8), Error> {
832 let priv_key = k256::ecdsa::SigningKey::from_slice(priv_key_bytes)
833 .map_err(|e| Error::from_source(format!("{e}")))?;
834 let mut hasher = sha3::Keccak256::new();
835 hasher.update(message);
836 let message_hash = hasher.finalize();
837 let mut message_hash_arr = [0u8; 32];
838 message_hash_arr.copy_from_slice(message_hash.as_slice());
839 let (signature, recovery_id) = priv_key
840 .sign_prehash_recoverable(&message_hash_arr)
841 .map_err(|e| Error::from_source(format!("{e}")))?;
842 Ok((signature.to_bytes().into(), recovery_id.to_byte()))
843}
844
845#[cfg(feature = "bincode")]
846pub fn new_secp256k1_instruction_with_signature(
847 message_arr: &[u8],
848 signature: &[u8; SIGNATURE_SERIALIZED_SIZE],
849 recovery_id: u8,
850 eth_address: &[u8; HASHED_PUBKEY_SERIALIZED_SIZE],
851) -> Instruction {
852 let instruction_data_len = DATA_START
853 .saturating_add(eth_address.len())
854 .saturating_add(signature.len())
855 .saturating_add(message_arr.len())
856 .saturating_add(1);
857 let mut instruction_data = vec![0; instruction_data_len];
858
859 let eth_address_offset = DATA_START;
860 instruction_data[eth_address_offset..eth_address_offset.saturating_add(eth_address.len())]
861 .copy_from_slice(eth_address);
862
863 let signature_offset = DATA_START.saturating_add(eth_address.len());
864 instruction_data[signature_offset..signature_offset.saturating_add(signature.len())]
865 .copy_from_slice(signature);
866
867 instruction_data[signature_offset.saturating_add(signature.len())] = recovery_id;
868
869 let message_data_offset = signature_offset
870 .saturating_add(signature.len())
871 .saturating_add(1);
872 instruction_data[message_data_offset..].copy_from_slice(message_arr);
873
874 let num_signatures = 1;
875 instruction_data[0] = num_signatures;
876 let offsets = SecpSignatureOffsets {
877 signature_offset: signature_offset as u16,
878 signature_instruction_index: 0,
879 eth_address_offset: eth_address_offset as u16,
880 eth_address_instruction_index: 0,
881 message_data_offset: message_data_offset as u16,
882 message_data_size: message_arr.len() as u16,
883 message_instruction_index: 0,
884 };
885 let writer = std::io::Cursor::new(&mut instruction_data[1..DATA_START]);
886 bincode::serialize_into(writer, &offsets).unwrap();
887
888 Instruction {
889 program_id: solana_sdk_ids::secp256k1_program::id(),
890 accounts: vec![],
891 data: instruction_data,
892 }
893}
894
895/// Creates an Ethereum address from a secp256k1 public key.
896pub fn eth_address_from_pubkey(
897 pubkey: &[u8; SECP256K1_PUBKEY_SIZE],
898) -> [u8; HASHED_PUBKEY_SERIALIZED_SIZE] {
899 let mut addr = [0u8; HASHED_PUBKEY_SERIALIZED_SIZE];
900 addr.copy_from_slice(&sha3::Keccak256::digest(pubkey)[12..]);
901 assert_eq!(addr.len(), HASHED_PUBKEY_SERIALIZED_SIZE);
902 addr
903}