solana_secp256r1_program/
lib.rs

1//! Instructions for the
2//! [secp256r1 native program](https://docs.solana.com/developing/runtime-facilities/programs#secp256r1-program)
3//!
4//! Note on Signature Malleability:
5//! This precompile requires low-S values in signatures (s <= half_curve_order) to prevent signature malleability.
6//! Signature malleability means that for a valid signature (r,s), (r, order-s) is also valid for the
7//! same message and public key.
8//!
9//! This property can be problematic for developers who assume each signature is unique. Without enforcing
10//! low-S values, the same message and key can produce two different valid signatures, potentially breaking
11//! replay protection schemes that rely on signature uniqueness.
12use bytemuck::{Pod, Zeroable};
13pub use solana_sdk_ids::secp256r1_program::{check_id, id, ID};
14
15#[derive(Default, Debug, Copy, Clone, Zeroable, Pod, Eq, PartialEq)]
16#[repr(C)]
17pub struct Secp256r1SignatureOffsets {
18    /// Offset to compact secp256r1 signature of 64 bytes
19    pub signature_offset: u16,
20
21    /// Instruction index where the signature can be found
22    pub signature_instruction_index: u16,
23
24    /// Offset to compressed public key of 33 bytes
25    pub public_key_offset: u16,
26
27    /// Instruction index where the public key can be found
28    pub public_key_instruction_index: u16,
29
30    /// Offset to the start of message data
31    pub message_data_offset: u16,
32
33    /// Size of message data in bytes
34    pub message_data_size: u16,
35
36    /// Instruction index where the message data can be found
37    pub message_instruction_index: u16,
38}
39
40#[cfg(all(not(target_arch = "wasm32"), not(target_os = "solana")))]
41mod target_arch {
42    use {
43        crate::Secp256r1SignatureOffsets,
44        bytemuck::bytes_of,
45        openssl::{bn::BigNum, ec::EcKey, ecdsa::EcdsaSig, nid::Nid, pkey::PKey, sign::Signer},
46        solana_instruction::Instruction,
47    };
48
49    pub const COMPRESSED_PUBKEY_SERIALIZED_SIZE: usize = 33;
50    pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
51    pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 14;
52    pub const SIGNATURE_OFFSETS_START: usize = 2;
53    pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START;
54
55    // Order as defined in SEC2: 2.7.2 Recommended Parameters secp256r1
56    pub const SECP256R1_ORDER: [u8; FIELD_SIZE] = [
57        0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
58        0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63,
59        0x25, 0x51,
60    ];
61
62    // Computed SECP256R1_ORDER - 1
63    pub const SECP256R1_ORDER_MINUS_ONE: [u8; FIELD_SIZE] = [
64        0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
65        0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63,
66        0x25, 0x50,
67    ];
68
69    // Computed half order
70    pub const SECP256R1_HALF_ORDER: [u8; FIELD_SIZE] = [
71        0x7F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
72        0xFF, 0xDE, 0x73, 0x7D, 0x56, 0xD3, 0x8B, 0xCF, 0x42, 0x79, 0xDC, 0xE5, 0x61, 0x7E, 0x31,
73        0x92, 0xA8,
74    ];
75    // Field size in bytes
76    pub const FIELD_SIZE: usize = 32;
77
78    pub fn sign_message(
79        message: &[u8],
80        priv_key_bytes_der: &[u8],
81    ) -> Result<[u8; SIGNATURE_SERIALIZED_SIZE], Box<dyn core::error::Error>> {
82        let signing_key = EcKey::private_key_from_der(priv_key_bytes_der)?;
83        if signing_key.group().curve_name() != Some(Nid::X9_62_PRIME256V1) {
84            return Err(("Signing key must be on the secp256r1 curve".to_string()).into());
85        }
86
87        let signing_key_pkey = PKey::from_ec_key(signing_key)?;
88
89        let mut signer = Signer::new(openssl::hash::MessageDigest::sha256(), &signing_key_pkey)?;
90        signer.update(message)?;
91        let signature = signer.sign_to_vec()?;
92
93        let ecdsa_sig = EcdsaSig::from_der(&signature)?;
94        let r = ecdsa_sig.r().to_vec();
95        let s = ecdsa_sig.s().to_vec();
96        let mut signature = [0u8; SIGNATURE_SERIALIZED_SIZE];
97
98        // Incase of an r or s value of 31 bytes we need to pad it to 32 bytes
99        let mut padded_r = [0u8; FIELD_SIZE];
100        let mut padded_s = [0u8; FIELD_SIZE];
101        padded_r[FIELD_SIZE.saturating_sub(r.len())..].copy_from_slice(&r);
102        padded_s[FIELD_SIZE.saturating_sub(s.len())..].copy_from_slice(&s);
103
104        signature[..FIELD_SIZE].copy_from_slice(&padded_r);
105        signature[FIELD_SIZE..].copy_from_slice(&padded_s);
106
107        // Check if s > half_order, if so, compute s = order - s
108        let s_bignum = BigNum::from_slice(&s)?;
109        let half_order = BigNum::from_slice(&SECP256R1_HALF_ORDER)?;
110        let order = BigNum::from_slice(&SECP256R1_ORDER)?;
111        if s_bignum > half_order {
112            let mut new_s = BigNum::new()?;
113            new_s.checked_sub(&order, &s_bignum)?;
114            let new_s_bytes = new_s.to_vec();
115
116            // Incase the new s value is 31 bytes we need to pad it to 32 bytes
117            let mut new_padded_s = [0u8; FIELD_SIZE];
118            new_padded_s[FIELD_SIZE.saturating_sub(new_s_bytes.len())..]
119                .copy_from_slice(&new_s_bytes);
120
121            signature[FIELD_SIZE..].copy_from_slice(&new_padded_s);
122        }
123        Ok(signature)
124    }
125
126    pub fn new_secp256r1_instruction_with_signature(
127        message: &[u8],
128        signature: &[u8; SIGNATURE_SERIALIZED_SIZE],
129        pubkey: &[u8; COMPRESSED_PUBKEY_SERIALIZED_SIZE],
130    ) -> Instruction {
131        let mut instruction_data = Vec::with_capacity(
132            DATA_START
133                .saturating_add(SIGNATURE_SERIALIZED_SIZE)
134                .saturating_add(COMPRESSED_PUBKEY_SERIALIZED_SIZE)
135                .saturating_add(message.len()),
136        );
137
138        let num_signatures: u8 = 1;
139        let public_key_offset = DATA_START;
140        let signature_offset = public_key_offset.saturating_add(COMPRESSED_PUBKEY_SERIALIZED_SIZE);
141        let message_data_offset = signature_offset.saturating_add(SIGNATURE_SERIALIZED_SIZE);
142
143        instruction_data.extend_from_slice(bytes_of(&[num_signatures, 0]));
144
145        let offsets = Secp256r1SignatureOffsets {
146            signature_offset: signature_offset as u16,
147            signature_instruction_index: u16::MAX,
148            public_key_offset: public_key_offset as u16,
149            public_key_instruction_index: u16::MAX,
150            message_data_offset: message_data_offset as u16,
151            message_data_size: message.len() as u16,
152            message_instruction_index: u16::MAX,
153        };
154
155        instruction_data.extend_from_slice(bytes_of(&offsets));
156        instruction_data.extend_from_slice(pubkey);
157        instruction_data.extend_from_slice(signature);
158        instruction_data.extend_from_slice(message);
159
160        Instruction {
161            program_id: crate::id(),
162            accounts: vec![],
163            data: instruction_data,
164        }
165    }
166}
167
168pub use self::target_arch::*;