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