Skip to main content

hopper_runtime/
crypto.rs

1//! Runtime cryptography helpers built on Solana syscalls and precompiles.
2
3use crate::{Address, ProgramError};
4
5pub type Sha256Hash = [u8; 32];
6pub type Keccak256Hash = [u8; 32];
7pub type Blake3Hash = [u8; 32];
8pub type Secp256k1Pubkey = [u8; 64];
9pub type EthereumAddress = [u8; 20];
10pub type CurvePoint = [u8; 32];
11pub type CurveScalar = [u8; 32];
12pub type PoseidonHash = [u8; 32];
13pub type AltBn128G1 = [u8; 64];
14pub type AltBn128G1Compressed = [u8; 32];
15pub type AltBn128G2 = [u8; 128];
16pub type AltBn128G2Compressed = [u8; 64];
17pub type AltBn128PairingResult = [u8; 32];
18
19pub const MAX_HASH_SEGMENTS: usize = 16;
20pub const CURVE25519_EDWARDS: u64 = 0;
21pub const CURVE25519_RISTRETTO: u64 = 1;
22pub const CURVE_GROUP_ADD: u64 = 0;
23pub const CURVE_GROUP_SUB: u64 = 1;
24pub const CURVE_GROUP_MUL: u64 = 2;
25pub const POSEIDON_BN254_X5: u64 = 0;
26pub const POSEIDON_BIG_ENDIAN: u64 = 0;
27pub const POSEIDON_LITTLE_ENDIAN: u64 = 1;
28pub const MAX_POSEIDON_INPUTS: usize = 12;
29pub const POSEIDON_INPUT_LEN: usize = 32;
30pub const ALT_BN128_LE_FLAG: u64 = 0x80;
31pub const ALT_BN128_FIELD_SIZE: usize = 32;
32pub const ALT_BN128_G1_POINT_SIZE: usize = 64;
33pub const ALT_BN128_G2_POINT_SIZE: usize = 128;
34pub const ALT_BN128_G1_ADDITION_INPUT_SIZE: usize = 128;
35pub const ALT_BN128_G2_ADDITION_INPUT_SIZE: usize = 256;
36pub const ALT_BN128_G1_MULTIPLICATION_INPUT_SIZE: usize = 96;
37pub const ALT_BN128_G2_MULTIPLICATION_INPUT_SIZE: usize = 160;
38pub const ALT_BN128_PAIRING_ELEMENT_SIZE: usize = 192;
39pub const ALT_BN128_G1_ADD_BE: u64 = 0;
40pub const ALT_BN128_G1_SUB_BE: u64 = 1;
41pub const ALT_BN128_G1_MUL_BE: u64 = 2;
42pub const ALT_BN128_PAIRING_BE: u64 = 3;
43pub const ALT_BN128_G2_ADD_BE: u64 = 4;
44pub const ALT_BN128_G2_SUB_BE: u64 = 5;
45pub const ALT_BN128_G2_MUL_BE: u64 = 6;
46pub const ALT_BN128_G1_ADD_LE: u64 = ALT_BN128_G1_ADD_BE | ALT_BN128_LE_FLAG;
47pub const ALT_BN128_G1_SUB_LE: u64 = ALT_BN128_G1_SUB_BE | ALT_BN128_LE_FLAG;
48pub const ALT_BN128_G1_MUL_LE: u64 = ALT_BN128_G1_MUL_BE | ALT_BN128_LE_FLAG;
49pub const ALT_BN128_PAIRING_LE: u64 = ALT_BN128_PAIRING_BE | ALT_BN128_LE_FLAG;
50pub const ALT_BN128_G2_ADD_LE: u64 = ALT_BN128_G2_ADD_BE | ALT_BN128_LE_FLAG;
51pub const ALT_BN128_G2_SUB_LE: u64 = ALT_BN128_G2_SUB_BE | ALT_BN128_LE_FLAG;
52pub const ALT_BN128_G2_MUL_LE: u64 = ALT_BN128_G2_MUL_BE | ALT_BN128_LE_FLAG;
53pub const ALT_BN128_G1_COMPRESS_BE: u64 = 0;
54pub const ALT_BN128_G1_DECOMPRESS_BE: u64 = 1;
55pub const ALT_BN128_G2_COMPRESS_BE: u64 = 2;
56pub const ALT_BN128_G2_DECOMPRESS_BE: u64 = 3;
57pub const ALT_BN128_G1_COMPRESS_LE: u64 = ALT_BN128_G1_COMPRESS_BE | ALT_BN128_LE_FLAG;
58pub const ALT_BN128_G1_DECOMPRESS_LE: u64 = ALT_BN128_G1_DECOMPRESS_BE | ALT_BN128_LE_FLAG;
59pub const ALT_BN128_G2_COMPRESS_LE: u64 = ALT_BN128_G2_COMPRESS_BE | ALT_BN128_LE_FLAG;
60pub const ALT_BN128_G2_DECOMPRESS_LE: u64 = ALT_BN128_G2_DECOMPRESS_BE | ALT_BN128_LE_FLAG;
61pub const MAX_INSTRUCTION_DATA_LEN: usize = 1232;
62pub const MAX_INSTRUCTION_ACCOUNTS_BYTES: usize = 2176;
63
64pub const ED25519_PROGRAM_ID: Address = Address::new_from_array(crate::__decode_base58_32(
65    "Ed25519SigVerify111111111111111111111111111",
66));
67
68pub const SECP256K1_PROGRAM_ID: Address = Address::new_from_array(crate::__decode_base58_32(
69    "KeccakSecp256k11111111111111111111111111111",
70));
71
72/// Secp256r1 (P-256) signature-verification precompile (SIMD-0075).
73/// This is the precompile passkey / WebAuthn flows verify against.
74pub const SECP256R1_PROGRAM_ID: Address = Address::new_from_array(crate::__decode_base58_32(
75    "Secp256r1SigVerify1111111111111111111111111",
76));
77
78#[derive(Clone, Debug)]
79pub struct ProcessedInstruction {
80    pub program_id: Address,
81    pub data: [u8; MAX_INSTRUCTION_DATA_LEN],
82    pub data_len: usize,
83    pub accounts_len: usize,
84}
85
86#[derive(Clone, Debug)]
87pub struct ProcessedInstructionData<const MAX_DATA: usize> {
88    pub program_id: Address,
89    pub data: [u8; MAX_DATA],
90    pub data_len: usize,
91}
92
93#[repr(C)]
94struct ProcessedInstructionMeta {
95    data_len: u64,
96    accounts_len: u64,
97}
98
99#[cfg(feature = "crypto-big-mod-exp")]
100#[repr(C)]
101struct BigModExpParams {
102    base: *const u8,
103    base_len: u64,
104    exponent: *const u8,
105    exponent_len: u64,
106    modulus: *const u8,
107    modulus_len: u64,
108}
109
110#[cfg(any(
111    feature = "crypto-curve",
112    feature = "crypto-poseidon",
113    feature = "crypto-bn254",
114    feature = "crypto-big-mod-exp"
115))]
116#[inline]
117fn syscall_error(status: u64) -> ProgramError {
118    if status <= u32::MAX as u64 {
119        ProgramError::Custom(status as u32)
120    } else {
121        ProgramError::InvalidArgument
122    }
123}
124
125#[inline]
126pub fn sha256(inputs: &[&[u8]]) -> Result<Sha256Hash, ProgramError> {
127    if inputs.len() > MAX_HASH_SEGMENTS {
128        return Err(ProgramError::InvalidArgument);
129    }
130
131    let mut result = [0u8; 32];
132    // SAFETY: `inputs` is a valid slice of slice descriptors and `result`
133    // points to exactly 32 writable output bytes.
134    unsafe {
135        crate::syscalls::sol_sha256(
136            inputs as *const _ as *const u8,
137            inputs.len() as u64,
138            result.as_mut_ptr(),
139        );
140    }
141    Ok(result)
142}
143
144#[inline]
145pub fn sha256_single(input: &[u8]) -> Result<Sha256Hash, ProgramError> {
146    sha256(&[input])
147}
148
149#[inline]
150pub fn keccak256(inputs: &[&[u8]]) -> Result<Keccak256Hash, ProgramError> {
151    if inputs.len() > MAX_HASH_SEGMENTS {
152        return Err(ProgramError::InvalidArgument);
153    }
154
155    let mut result = [0u8; 32];
156    // SAFETY: `inputs` is a valid slice of slice descriptors and `result`
157    // points to exactly 32 writable output bytes.
158    unsafe {
159        crate::syscalls::sol_keccak256(
160            inputs as *const _ as *const u8,
161            inputs.len() as u64,
162            result.as_mut_ptr(),
163        );
164    }
165    Ok(result)
166}
167
168#[inline]
169pub fn keccak256_single(input: &[u8]) -> Result<Keccak256Hash, ProgramError> {
170    keccak256(&[input])
171}
172
173#[inline]
174pub fn blake3(inputs: &[&[u8]]) -> Result<Blake3Hash, ProgramError> {
175    if inputs.len() > MAX_HASH_SEGMENTS {
176        return Err(ProgramError::InvalidArgument);
177    }
178
179    let mut result = [0u8; 32];
180    // SAFETY: `inputs` is a valid slice of slice descriptors and `result`
181    // points to exactly 32 writable output bytes.
182    let rc = unsafe {
183        crate::syscalls::sol_blake3(
184            inputs as *const _ as *const u8,
185            inputs.len() as u64,
186            result.as_mut_ptr(),
187        )
188    };
189    if rc != 0 {
190        return Err(ProgramError::InvalidArgument);
191    }
192    Ok(result)
193}
194
195#[inline]
196pub fn blake3_single(input: &[u8]) -> Result<Blake3Hash, ProgramError> {
197    blake3(&[input])
198}
199
200#[inline]
201pub fn secp256k1_recover(
202    message_hash: &[u8; 32],
203    recovery_id: u8,
204    signature: &[u8; 64],
205) -> Result<Secp256k1Pubkey, ProgramError> {
206    let mut result = [0u8; 64];
207    // SAFETY: all pointers refer to fixed-width buffers required by the
208    // Solana secp256k1 recover syscall.
209    let rc = unsafe {
210        crate::syscalls::sol_secp256k1_recover(
211            message_hash.as_ptr(),
212            recovery_id as u64,
213            signature.as_ptr(),
214            result.as_mut_ptr(),
215        )
216    };
217    if rc != 0 {
218        return Err(ProgramError::InvalidArgument);
219    }
220    Ok(result)
221}
222
223#[inline]
224pub fn recover_ethereum_address(
225    message_hash: &[u8; 32],
226    recovery_id: u8,
227    signature: &[u8; 64],
228) -> Result<EthereumAddress, ProgramError> {
229    let pubkey = secp256k1_recover(message_hash, recovery_id, signature)?;
230    let digest = keccak256(&[&pubkey])?;
231    let mut address = [0u8; 20];
232    address.copy_from_slice(&digest[12..32]);
233    Ok(address)
234}
235
236#[inline]
237pub fn curve_validate_point(curve_id: u64, point: &[u8; 32]) -> Result<bool, ProgramError> {
238    // SAFETY: `point` points to exactly 32 bytes; null output pointer requests
239    // validation-only behavior from Solana's curve syscall.
240    let rc = unsafe {
241        crate::syscalls::sol_curve_validate_point(curve_id, point.as_ptr(), core::ptr::null_mut())
242    };
243    Ok(rc == 0)
244}
245
246#[inline]
247pub fn curve25519_edwards_validate_point(point: &[u8; 32]) -> Result<bool, ProgramError> {
248    curve_validate_point(CURVE25519_EDWARDS, point)
249}
250
251#[cfg(feature = "crypto-curve")]
252#[inline]
253fn curve_group_op(
254    curve_id: u64,
255    group_op: u64,
256    left: &CurvePoint,
257    right: &CurvePoint,
258) -> Result<CurvePoint, ProgramError> {
259    let mut result = [0u8; 32];
260    // SAFETY: all operands and the output are fixed-width 32-byte curve buffers.
261    let rc = unsafe {
262        crate::syscalls::sol_curve_group_op(
263            curve_id,
264            group_op,
265            left.as_ptr(),
266            right.as_ptr(),
267            result.as_mut_ptr(),
268        )
269    };
270    if rc != 0 {
271        return Err(syscall_error(rc));
272    }
273    Ok(result)
274}
275
276#[cfg(feature = "crypto-curve")]
277#[inline]
278pub fn curve_group_add(
279    curve_id: u64,
280    left: &CurvePoint,
281    right: &CurvePoint,
282) -> Result<CurvePoint, ProgramError> {
283    curve_group_op(curve_id, CURVE_GROUP_ADD, left, right)
284}
285
286#[cfg(feature = "crypto-curve")]
287#[inline]
288pub fn curve_group_sub(
289    curve_id: u64,
290    left: &CurvePoint,
291    right: &CurvePoint,
292) -> Result<CurvePoint, ProgramError> {
293    curve_group_op(curve_id, CURVE_GROUP_SUB, left, right)
294}
295
296#[cfg(feature = "crypto-curve")]
297#[inline]
298pub fn curve_group_mul(
299    curve_id: u64,
300    scalar: &CurveScalar,
301    point: &CurvePoint,
302) -> Result<CurvePoint, ProgramError> {
303    let mut result = [0u8; 32];
304    // SAFETY: Solana's multiply op expects scalar left, point right, and a
305    // fixed-width 32-byte result buffer.
306    let rc = unsafe {
307        crate::syscalls::sol_curve_group_op(
308            curve_id,
309            CURVE_GROUP_MUL,
310            scalar.as_ptr(),
311            point.as_ptr(),
312            result.as_mut_ptr(),
313        )
314    };
315    if rc != 0 {
316        return Err(syscall_error(rc));
317    }
318    Ok(result)
319}
320
321#[cfg(feature = "crypto-curve")]
322#[inline]
323pub fn curve_multiscalar_mul(
324    curve_id: u64,
325    scalars: &[CurveScalar],
326    points: &[CurvePoint],
327) -> Result<CurvePoint, ProgramError> {
328    if scalars.len() != points.len() || points.is_empty() {
329        return Err(ProgramError::InvalidArgument);
330    }
331
332    let mut result = [0u8; 32];
333    // SAFETY: `scalars` and `points` are contiguous arrays of 32-byte encodings
334    // with matching lengths, and `result` is a 32-byte output buffer.
335    let rc = unsafe {
336        crate::syscalls::sol_curve_multiscalar_mul(
337            curve_id,
338            scalars.as_ptr() as *const u8,
339            points.as_ptr() as *const u8,
340            points.len() as u64,
341            result.as_mut_ptr(),
342        )
343    };
344    if rc != 0 {
345        return Err(syscall_error(rc));
346    }
347    Ok(result)
348}
349
350#[cfg(feature = "crypto-poseidon")]
351#[inline]
352pub fn poseidon_hashv(
353    parameters: u64,
354    endianness: u64,
355    inputs: &[&[u8]],
356) -> Result<PoseidonHash, ProgramError> {
357    if inputs.is_empty() || inputs.len() > MAX_POSEIDON_INPUTS {
358        return Err(ProgramError::InvalidArgument);
359    }
360    if inputs.iter().any(|input| input.len() != POSEIDON_INPUT_LEN) {
361        return Err(ProgramError::InvalidArgument);
362    }
363
364    let mut result = [0u8; 32];
365    // SAFETY: `inputs` is a valid slice-descriptor array and `result` points to
366    // exactly 32 writable bytes.
367    let rc = unsafe {
368        crate::syscalls::sol_poseidon(
369            parameters,
370            endianness,
371            inputs as *const _ as *const u8,
372            inputs.len() as u64,
373            result.as_mut_ptr(),
374        )
375    };
376    if rc != 0 {
377        return Err(syscall_error(rc));
378    }
379    Ok(result)
380}
381
382#[cfg(feature = "crypto-poseidon")]
383#[inline]
384pub fn poseidon_hash(
385    parameters: u64,
386    endianness: u64,
387    input: &[u8; 32],
388) -> Result<PoseidonHash, ProgramError> {
389    poseidon_hashv(parameters, endianness, &[input])
390}
391
392#[cfg(feature = "crypto-poseidon")]
393#[inline]
394pub fn poseidon_bn254_x5(inputs: &[&[u8]]) -> Result<PoseidonHash, ProgramError> {
395    poseidon_hashv(POSEIDON_BN254_X5, POSEIDON_BIG_ENDIAN, inputs)
396}
397
398#[cfg(feature = "crypto-bn254")]
399#[inline]
400fn alt_bn128_group_op<const OUT: usize>(
401    group_op: u64,
402    input: &[u8],
403) -> Result<[u8; OUT], ProgramError> {
404    let mut result = [0u8; OUT];
405    // SAFETY: callers choose `OUT` to match the selected BN254 syscall op.
406    let rc = unsafe {
407        crate::syscalls::sol_alt_bn128_group_op(
408            group_op,
409            input.as_ptr(),
410            input.len() as u64,
411            result.as_mut_ptr(),
412        )
413    };
414    if rc != 0 {
415        return Err(syscall_error(rc));
416    }
417    Ok(result)
418}
419
420#[cfg(feature = "crypto-bn254")]
421#[inline]
422fn alt_bn128_compression_op<const OUT: usize>(
423    op: u64,
424    input: &[u8],
425) -> Result<[u8; OUT], ProgramError> {
426    let mut result = [0u8; OUT];
427    // SAFETY: callers choose `OUT` to match the selected BN254 compression op.
428    let rc = unsafe {
429        crate::syscalls::sol_alt_bn128_compression(
430            op,
431            input.as_ptr(),
432            input.len() as u64,
433            result.as_mut_ptr(),
434        )
435    };
436    if rc != 0 {
437        return Err(syscall_error(rc));
438    }
439    Ok(result)
440}
441
442#[cfg(feature = "crypto-bn254")]
443#[inline]
444pub fn alt_bn128_g1_addition_be(input: &[u8]) -> Result<AltBn128G1, ProgramError> {
445    if input.len() > ALT_BN128_G1_ADDITION_INPUT_SIZE {
446        return Err(ProgramError::InvalidArgument);
447    }
448    alt_bn128_group_op::<ALT_BN128_G1_POINT_SIZE>(ALT_BN128_G1_ADD_BE, input)
449}
450
451#[cfg(feature = "crypto-bn254")]
452#[inline]
453pub fn alt_bn128_g1_multiplication_be(input: &[u8]) -> Result<AltBn128G1, ProgramError> {
454    if input.len() > ALT_BN128_G1_MULTIPLICATION_INPUT_SIZE {
455        return Err(ProgramError::InvalidArgument);
456    }
457    alt_bn128_group_op::<ALT_BN128_G1_POINT_SIZE>(ALT_BN128_G1_MUL_BE, input)
458}
459
460#[cfg(feature = "crypto-bn254")]
461#[inline]
462pub fn alt_bn128_pairing_be(input: &[u8]) -> Result<AltBn128PairingResult, ProgramError> {
463    if !input.len().is_multiple_of(ALT_BN128_PAIRING_ELEMENT_SIZE) {
464        return Err(ProgramError::InvalidArgument);
465    }
466    alt_bn128_group_op::<ALT_BN128_FIELD_SIZE>(ALT_BN128_PAIRING_BE, input)
467}
468
469#[cfg(feature = "crypto-bn254")]
470#[inline]
471pub fn alt_bn128_add(input: &[u8]) -> Result<AltBn128G1, ProgramError> {
472    alt_bn128_g1_addition_be(input)
473}
474
475#[cfg(feature = "crypto-bn254")]
476#[inline]
477pub fn alt_bn128_mul(input: &[u8]) -> Result<AltBn128G1, ProgramError> {
478    alt_bn128_g1_multiplication_be(input)
479}
480
481#[cfg(feature = "crypto-bn254")]
482#[inline]
483pub fn alt_bn128_pairing(input: &[u8]) -> Result<AltBn128PairingResult, ProgramError> {
484    alt_bn128_pairing_be(input)
485}
486
487#[cfg(feature = "crypto-bn254")]
488#[inline]
489pub fn alt_bn128_g1_compress_be(input: &AltBn128G1) -> Result<AltBn128G1Compressed, ProgramError> {
490    alt_bn128_compression_op::<ALT_BN128_FIELD_SIZE>(ALT_BN128_G1_COMPRESS_BE, input)
491}
492
493#[cfg(feature = "crypto-bn254")]
494#[inline]
495pub fn alt_bn128_g1_decompress_be(
496    input: &AltBn128G1Compressed,
497) -> Result<AltBn128G1, ProgramError> {
498    alt_bn128_compression_op::<ALT_BN128_G1_POINT_SIZE>(ALT_BN128_G1_DECOMPRESS_BE, input)
499}
500
501#[cfg(feature = "crypto-bn254")]
502#[inline]
503pub fn alt_bn128_g2_compress_be(input: &AltBn128G2) -> Result<AltBn128G2Compressed, ProgramError> {
504    alt_bn128_compression_op::<ALT_BN128_G1_POINT_SIZE>(ALT_BN128_G2_COMPRESS_BE, input)
505}
506
507#[cfg(feature = "crypto-bn254")]
508#[inline]
509pub fn alt_bn128_g2_decompress_be(
510    input: &AltBn128G2Compressed,
511) -> Result<AltBn128G2, ProgramError> {
512    alt_bn128_compression_op::<ALT_BN128_G2_POINT_SIZE>(ALT_BN128_G2_DECOMPRESS_BE, input)
513}
514
515#[cfg(feature = "crypto-big-mod-exp")]
516#[inline]
517pub fn big_mod_exp(
518    base: &[u8],
519    exponent: &[u8],
520    modulus: &[u8],
521    output: &mut [u8],
522) -> Result<(), ProgramError> {
523    if modulus.is_empty() || output.len() != modulus.len() {
524        return Err(ProgramError::InvalidArgument);
525    }
526
527    let params = BigModExpParams {
528        base: base.as_ptr(),
529        base_len: base.len() as u64,
530        exponent: exponent.as_ptr(),
531        exponent_len: exponent.len() as u64,
532        modulus: modulus.as_ptr(),
533        modulus_len: modulus.len() as u64,
534    };
535    // SAFETY: params has Solana's C layout and `output` has exactly modulus.len()
536    // writable bytes, which is the syscall's output size contract.
537    let rc = unsafe {
538        crate::syscalls::sol_big_mod_exp(
539            &params as *const BigModExpParams as *const u8,
540            output.as_mut_ptr(),
541        )
542    };
543    if rc != 0 {
544        return Err(syscall_error(rc));
545    }
546    Ok(())
547}
548
549#[inline(always)]
550pub fn get_stack_height() -> u64 {
551    crate::syscalls::sol_get_stack_height()
552}
553
554#[inline(always)]
555pub fn is_top_level() -> bool {
556    get_stack_height() <= 1
557}
558
559#[inline(always)]
560pub fn is_cpi() -> bool {
561    get_stack_height() > 1
562}
563
564#[inline(always)]
565pub fn require_top_level() -> Result<(), ProgramError> {
566    if is_top_level() {
567        Ok(())
568    } else {
569        Err(ProgramError::InvalidArgument)
570    }
571}
572
573#[inline]
574pub fn get_processed_instruction(index: u64) -> Option<ProcessedInstruction> {
575    let mut meta = ProcessedInstructionMeta {
576        data_len: MAX_INSTRUCTION_DATA_LEN as u64,
577        accounts_len: (MAX_INSTRUCTION_ACCOUNTS_BYTES / 34) as u64,
578    };
579    let mut program_id = Address::default();
580    let mut data = [0u8; MAX_INSTRUCTION_DATA_LEN];
581    let mut accounts = [0u8; MAX_INSTRUCTION_ACCOUNTS_BYTES];
582
583    // SAFETY: output pointers refer to writable stack buffers sized for the
584    // processed-sibling-instruction syscall contract.
585    let rc = unsafe {
586        crate::syscalls::sol_get_processed_sibling_instruction(
587            index,
588            &mut meta as *mut ProcessedInstructionMeta as *mut u8,
589            program_id.as_mut().as_mut_ptr(),
590            data.as_mut_ptr(),
591            accounts.as_mut_ptr(),
592        )
593    };
594    if rc != 0 {
595        return None;
596    }
597
598    Some(ProcessedInstruction {
599        program_id,
600        data,
601        data_len: meta.data_len as usize,
602        accounts_len: meta.accounts_len as usize,
603    })
604}
605
606#[inline]
607pub fn get_processed_instruction_data<const MAX_DATA: usize>(
608    index: u64,
609) -> Option<ProcessedInstructionData<MAX_DATA>> {
610    let mut meta = ProcessedInstructionMeta {
611        data_len: MAX_DATA as u64,
612        accounts_len: 0,
613    };
614    let mut program_id = Address::default();
615    let mut data = [0u8; MAX_DATA];
616    let mut accounts = [0u8; 0];
617
618    // SAFETY: output pointers refer to writable buffers. The account-meta
619    // capacity is advertised as zero because callers of this helper only need
620    // program id and instruction data.
621    let rc = unsafe {
622        crate::syscalls::sol_get_processed_sibling_instruction(
623            index,
624            &mut meta as *mut ProcessedInstructionMeta as *mut u8,
625            program_id.as_mut().as_mut_ptr(),
626            data.as_mut_ptr(),
627            accounts.as_mut_ptr(),
628        )
629    };
630    if rc != 0 {
631        return None;
632    }
633
634    Some(ProcessedInstructionData {
635        program_id,
636        data,
637        data_len: meta.data_len as usize,
638    })
639}
640
641#[inline]
642pub fn require_ed25519_instruction(
643    sibling_index: u64,
644) -> Result<ProcessedInstruction, ProgramError> {
645    let instruction =
646        get_processed_instruction(sibling_index).ok_or(ProgramError::InvalidArgument)?;
647    if instruction.program_id != ED25519_PROGRAM_ID {
648        return Err(ProgramError::IncorrectProgramId);
649    }
650    Ok(instruction)
651}
652
653#[inline]
654pub fn require_ed25519_instruction_data<const MAX_DATA: usize>(
655    sibling_index: u64,
656) -> Result<ProcessedInstructionData<MAX_DATA>, ProgramError> {
657    let instruction =
658        get_processed_instruction_data(sibling_index).ok_or(ProgramError::InvalidArgument)?;
659    if instruction.program_id != ED25519_PROGRAM_ID {
660        return Err(ProgramError::IncorrectProgramId);
661    }
662    Ok(instruction)
663}
664
665#[inline]
666pub fn require_secp256k1_instruction(
667    sibling_index: u64,
668) -> Result<ProcessedInstruction, ProgramError> {
669    let instruction =
670        get_processed_instruction(sibling_index).ok_or(ProgramError::InvalidArgument)?;
671    if instruction.program_id != SECP256K1_PROGRAM_ID {
672        return Err(ProgramError::IncorrectProgramId);
673    }
674    Ok(instruction)
675}
676
677/// Require that a sibling instruction targeted the Secp256r1 (P-256)
678/// precompile, the verification path for passkeys / WebAuthn. Returns
679/// the precompile instruction so the caller can bind the verified
680/// `(public_key, message, signature)` to its own authorization logic.
681#[inline]
682pub fn require_secp256r1_instruction(
683    sibling_index: u64,
684) -> Result<ProcessedInstruction, ProgramError> {
685    let instruction =
686        get_processed_instruction(sibling_index).ok_or(ProgramError::InvalidArgument)?;
687    if instruction.program_id != SECP256R1_PROGRAM_ID {
688        return Err(ProgramError::IncorrectProgramId);
689    }
690    Ok(instruction)
691}
692
693/// Variant of [`require_secp256r1_instruction`] returning the bounded
694/// instruction-data form for callers that parse the precompile payload.
695#[inline]
696pub fn require_secp256r1_instruction_data<const MAX_DATA: usize>(
697    sibling_index: u64,
698) -> Result<ProcessedInstructionData<MAX_DATA>, ProgramError> {
699    let instruction =
700        get_processed_instruction_data(sibling_index).ok_or(ProgramError::InvalidArgument)?;
701    if instruction.program_id != SECP256R1_PROGRAM_ID {
702        return Err(ProgramError::IncorrectProgramId);
703    }
704    Ok(instruction)
705}
706
707#[cfg(test)]
708mod tests {
709    use super::*;
710
711    const EMPTY: &[u8] = b"";
712
713    #[test]
714    fn hash_helpers_accept_sixteen_segments() {
715        let inputs = [EMPTY; MAX_HASH_SEGMENTS];
716
717        assert!(sha256(&inputs).is_ok());
718        assert!(keccak256(&inputs).is_ok());
719        assert!(blake3(&inputs).is_ok());
720    }
721
722    #[test]
723    fn hash_helpers_reject_more_than_sixteen_segments() {
724        let inputs = [EMPTY; MAX_HASH_SEGMENTS + 1];
725
726        assert_eq!(sha256(&inputs), Err(ProgramError::InvalidArgument));
727        assert_eq!(keccak256(&inputs), Err(ProgramError::InvalidArgument));
728        assert_eq!(blake3(&inputs), Err(ProgramError::InvalidArgument));
729    }
730
731    #[cfg(feature = "crypto-curve")]
732    #[test]
733    fn curve_msm_requires_matching_nonempty_inputs() {
734        let scalars = [[0u8; 32]; 2];
735        let points = [[0u8; 32]; 1];
736
737        assert_eq!(
738            curve_multiscalar_mul(CURVE25519_EDWARDS, &scalars, &points),
739            Err(ProgramError::InvalidArgument)
740        );
741        assert_eq!(
742            curve_multiscalar_mul(CURVE25519_EDWARDS, &[], &[]),
743            Err(ProgramError::InvalidArgument)
744        );
745    }
746
747    #[cfg(feature = "crypto-poseidon")]
748    #[test]
749    fn poseidon_rejects_bad_input_shape() {
750        let short = [0u8; 31];
751        let input = [0u8; 32];
752        let too_many = [&input[..]; MAX_POSEIDON_INPUTS + 1];
753
754        assert_eq!(
755            poseidon_hashv(POSEIDON_BN254_X5, POSEIDON_BIG_ENDIAN, &[]),
756            Err(ProgramError::InvalidArgument)
757        );
758        assert_eq!(
759            poseidon_hashv(POSEIDON_BN254_X5, POSEIDON_BIG_ENDIAN, &[&short]),
760            Err(ProgramError::InvalidArgument)
761        );
762        assert_eq!(
763            poseidon_hashv(POSEIDON_BN254_X5, POSEIDON_BIG_ENDIAN, &too_many),
764            Err(ProgramError::InvalidArgument)
765        );
766    }
767
768    #[cfg(feature = "crypto-bn254")]
769    #[test]
770    fn bn254_rejects_bad_input_lengths() {
771        let oversized_add = [0u8; ALT_BN128_G1_ADDITION_INPUT_SIZE + 1];
772        let bad_pairing = [0u8; ALT_BN128_PAIRING_ELEMENT_SIZE + 1];
773
774        assert_eq!(
775            alt_bn128_g1_addition_be(&oversized_add),
776            Err(ProgramError::InvalidArgument)
777        );
778        assert_eq!(
779            alt_bn128_pairing_be(&bad_pairing),
780            Err(ProgramError::InvalidArgument)
781        );
782    }
783
784    #[cfg(feature = "crypto-big-mod-exp")]
785    #[test]
786    fn big_mod_exp_requires_output_matching_modulus() {
787        let mut output = [0u8; 1];
788        let mut empty_output = [];
789
790        assert_eq!(
791            big_mod_exp(&[1], &[1], &[1, 2], &mut output),
792            Err(ProgramError::InvalidArgument)
793        );
794        assert_eq!(
795            big_mod_exp(&[1], &[1], &[], &mut empty_output),
796            Err(ProgramError::InvalidArgument)
797        );
798    }
799}