diff --git a/src/noir/lib/data-check/integrity/src/lib.nr b/src/noir/lib/data-check/integrity/src/lib.nr index 56281f04e..ed5666425 100644 --- a/src/noir/lib/data-check/integrity/src/lib.nr +++ b/src/noir/lib/data-check/integrity/src/lib.nr @@ -4,10 +4,10 @@ use sha512::{sha384, sha512}; use utils::{ check_zero_padding, constants::{ID_CARD_DG1_LENGTH, PASSPORT_DG1_LENGTH}, - find_subarray_index, + fallible_find_subarray_index, get_array_slice, is_id_card, - is_subarray_in_array, + require_subarray_in_array, types::{DG1Data, EContentData}, }; @@ -24,7 +24,8 @@ pub fn get_dg2_hash_from_econtent( e_content: EContentData, e_content_size: u32, ) -> [u8; HASH_SIZE] { - let dg2_hash_der_identifier_offset = find_subarray_index(DG2_HASH_DER_IDENTIFIER, e_content); + let dg2_hash_der_identifier_offset = + fallible_find_subarray_index(DG2_HASH_DER_IDENTIFIER, e_content); assert(dg2_hash_der_identifier_offset != e_content.len(), "DG2 hash not found in eContent"); assert_eq( @@ -80,7 +81,7 @@ pub fn check_dg1_sha1(dg1: DG1Data, e_content: EContentData, e_content_size: u32 let dg1_hash = sha1::sha1_var(dg1_bounded_vec); // Find the offset of the hash of dg1 in e_content - let dg1_offset_in_e_content = find_subarray_index(dg1_hash, e_content); + let dg1_offset_in_e_content = fallible_find_subarray_index(dg1_hash, e_content); // Check that the hash of dg1 is in the e_content, we use the actual size rather than generic size // of econtent to ignore the padding bytes at the end assert( @@ -104,7 +105,7 @@ pub fn check_signed_attributes_sha1( // Check that the computed final hash is in the signed attributes assert( - is_subarray_in_array(computed_final_hash, signed_attributes), + require_subarray_in_array(computed_final_hash, signed_attributes), "Computed final hash not found in signed attributes", ); } @@ -124,7 +125,7 @@ pub fn check_dg1_sha224(dg1: DG1Data, e_content: EContentData, e_content_size: u let dg1_hash = sha224_var(dg1, dg1_size); // Find the offset of the hash of dg1 in e_content - let dg1_offset_in_e_content = find_subarray_index(dg1_hash, e_content); + let dg1_offset_in_e_content = fallible_find_subarray_index(dg1_hash, e_content); // Check that the hash of dg1 is in the e_content, we use the actual size rather than generic size // of econtent to ignore the padding bytes at the end assert( @@ -146,7 +147,7 @@ pub fn check_signed_attributes_sha224( // Check that the computed final hash is in the signed attributes assert( - is_subarray_in_array(computed_final_hash, signed_attributes), + require_subarray_in_array(computed_final_hash, signed_attributes), "Computed final hash not found in signed attributes", ); } @@ -166,7 +167,7 @@ pub fn check_dg1_sha256(dg1: DG1Data, e_content: EContentData, e_content_size: u let dg1_hash = sha256_var(dg1, dg1_size); // Find the offset of the hash of dg1 in e_content - let dg1_offset_in_e_content = find_subarray_index(dg1_hash, e_content); + let dg1_offset_in_e_content = fallible_find_subarray_index(dg1_hash, e_content); // Check that the hash of dg1 is in the e_content, we use the actual size rather than generic size // of econtent to ignore the padding bytes at the end assert( @@ -188,7 +189,7 @@ pub fn check_signed_attributes_sha256( // Check that the computed final hash is in the signed attributes assert( - is_subarray_in_array(computed_final_hash, signed_attributes), + require_subarray_in_array(computed_final_hash, signed_attributes), "Computed final hash not found in signed attributes", ); } @@ -209,7 +210,7 @@ pub fn check_dg1_sha384(dg1: DG1Data, e_content: EContentData, e_content_size: u let dg1_hash = sha384::sha384_var(dg1_bounded_vec); // Find the offset of the hash of dg1 in e_content - let dg1_offset_in_e_content = find_subarray_index(dg1_hash, e_content); + let dg1_offset_in_e_content = fallible_find_subarray_index(dg1_hash, e_content); // Check that the hash of dg1 is in the e_content, we use the actual size rather than generic size // of econtent to ignore the padding bytes at the end assert( @@ -233,7 +234,7 @@ pub fn check_signed_attributes_sha384( // Check that the computed final hash is in the signed attributes assert( - is_subarray_in_array(computed_final_hash, signed_attributes), + require_subarray_in_array(computed_final_hash, signed_attributes), "Computed final hash not found in signed attributes", ); } @@ -254,7 +255,7 @@ pub fn check_dg1_sha512(dg1: DG1Data, e_content: EContentData, e_content_size: u let dg1_hash = sha512::sha512_var(dg1_bounded_vec); // Find the offset of the hash of dg1 in e_content - let dg1_offset_in_e_content = find_subarray_index(dg1_hash, e_content); + let dg1_offset_in_e_content = fallible_find_subarray_index(dg1_hash, e_content); // Check that the hash of dg1 is in the e_content, we use the actual size rather than generic size // of econtent to ignore the padding bytes at the end assert( @@ -278,7 +279,7 @@ pub fn check_signed_attributes_sha512( // Check that the computed final hash is in the signed attributes assert( - is_subarray_in_array(computed_final_hash, signed_attributes), + require_subarray_in_array(computed_final_hash, signed_attributes), "Computed final hash not found in signed attributes", ); } diff --git a/src/noir/lib/data-check/tbs-pubkey/src/lib.nr b/src/noir/lib/data-check/tbs-pubkey/src/lib.nr index dfa248905..eb86f9a8f 100644 --- a/src/noir/lib/data-check/tbs-pubkey/src/lib.nr +++ b/src/noir/lib/data-check/tbs-pubkey/src/lib.nr @@ -5,7 +5,7 @@ pub fn verify_rsa_pubkey_in_tbs( pubkey: [u8; PUBKEY_SIZE], tbs: [u8; TBS_SIZE], ) { - let pubkey_offset = utils::find_subarray_index(pubkey, tbs); + let pubkey_offset = utils::fallible_find_subarray_index(pubkey, tbs); for i in 0..PUBKEY_SIZE { assert(tbs[i + pubkey_offset] == pubkey[i], "Public key of DSC not found in TBS"); } @@ -17,7 +17,7 @@ pub fn verify_ecdsa_pubkey_in_tbs( pubkey_y: [u8; PUBKEY_SIZE], tbs: [u8; TBS_SIZE], ) { - let pubkey_offset = utils::find_subarray_index(pubkey_x, tbs); + let pubkey_offset = utils::fallible_find_subarray_index(pubkey_x, tbs); for i in 0..PUBKEY_SIZE { // We only need to check that the y coord is found after the x coord, since // find_substring_index() already guarantees that the x coord exists in the TBS diff --git a/src/noir/lib/facematch/src/android/mod.nr b/src/noir/lib/facematch/src/android/mod.nr index 235403637..c880a0e3f 100644 --- a/src/noir/lib/facematch/src/android/mod.nr +++ b/src/noir/lib/facematch/src/android/mod.nr @@ -4,7 +4,7 @@ use sha256; use sha512::sha384; use sig_check_ecdsa::{verify_nist_p256, verify_nist_p384}; use utils::{ - check_zero_padding, find_subarray_index, get_array_slice, poseidon2_hash_packed, + check_zero_padding, fallible_find_subarray_index, get_array_slice, poseidon2_hash_packed, unsafe_get_asn1_element_length, }; @@ -16,7 +16,7 @@ pub fn get_app_id_from_credential_tbs ([u8; APP_ID_MAX_LEN], u32) { // Find Android Key Attestation extension OID in the TBS certificate - let oid_offset = find_subarray_index(OID_ANDROID_KEY_ATTESTATION, tbs); + let oid_offset = fallible_find_subarray_index(OID_ANDROID_KEY_ATTESTATION, tbs); assert( oid_offset < TBS_MAX_LEN, "Android Key Attestation OID not found in credential certificate", @@ -91,7 +91,7 @@ pub fn get_app_id_from_credential_tbs(tbs: [u8; TBS_MAX_LEN]) -> u8 { // Find Android Key Attestation extension OID in the TBS certificate - let oid_offset = find_subarray_index(OID_ANDROID_KEY_ATTESTATION, tbs); + let oid_offset = fallible_find_subarray_index(OID_ANDROID_KEY_ATTESTATION, tbs); assert( oid_offset < TBS_MAX_LEN, "Android Key Attestation OID not found in credential certificate", diff --git a/src/noir/lib/facematch/src/ios/mod.nr b/src/noir/lib/facematch/src/ios/mod.nr index 13c3d5872..0a86d7d23 100644 --- a/src/noir/lib/facematch/src/ios/mod.nr +++ b/src/noir/lib/facematch/src/ios/mod.nr @@ -1,5 +1,5 @@ use crate::constants::{APP_ATTEST_ENV_DEVELOPMENT, APP_ATTEST_ENV_PRODUCTION, CLIENT_DATA_HASH_LEN}; -use utils::{find_subarray_index, get_array_slice, unsafe_get_asn1_element_length}; +use utils::{fallible_find_subarray_index, get_array_slice, unsafe_get_asn1_element_length}; pub mod constants; use crate::get_client_data_hash; use constants::{AAGUID_DEVELOPMENT, AAGUID_PRODUCTION, OID_APPLE_APP_ID, OID_APPLE_NONCE_ID}; @@ -10,7 +10,7 @@ pub mod tests; /// The nonce is stored in an ASN.1 OCTET STRING within the extension pub fn get_nonce_from_credential_tbs(tbs: [u8; TBS_MAX_LEN]) -> [u8; 32] { // Find Apple App Attest nonce extension OID in the TBS certificate - let oid_offset = find_subarray_index(OID_APPLE_NONCE_ID, tbs); + let oid_offset = fallible_find_subarray_index(OID_APPLE_NONCE_ID, tbs); assert(oid_offset < TBS_MAX_LEN, "Nonce OID not found in credential certificate"); // Look for OCTET STRING tag (04 20) within the next 10 bytes let NONCE_SEARCH_DISTANCE = 10; @@ -44,7 +44,7 @@ pub fn get_app_id_from_credential_tbs ([u8; APP_ID_MAX_LEN], u32) { // Find Apple App Attest app ID extension OID in the TBS certificate - let oid_offset = find_subarray_index(OID_APPLE_APP_ID, tbs); + let oid_offset = fallible_find_subarray_index(OID_APPLE_APP_ID, tbs); assert(oid_offset < TBS_MAX_LEN, "App ID OID not found in credential certificate"); // Look for the 6th item in the sequence (tagged with BF 89 34) diff --git a/src/noir/lib/utils/src/lib.nr b/src/noir/lib/utils/src/lib.nr index 74b3c3623..81a496fa5 100644 --- a/src/noir/lib/utils/src/lib.nr +++ b/src/noir/lib/utils/src/lib.nr @@ -405,50 +405,48 @@ pub unconstrained fn unsafe_get_asn1_element_length(asn1: [u8; N]) - } } -/// Find the index of the first occurrence of the needle in the haystack -/// Returns the index of the first occurrence of the needle in the haystack -/// Returns HAYSTACK_SIZE if the needle is not found -pub fn find_subarray_index( +/// Finds the offset of an occurance of `needle` in `haystack` or HAYSTACK_SIZE +// Note: This can return HAYSTACK_SIZE even if `needle` *does* exist in `haystack` due to using an unconstrained function +/// Returns the offset of a `needle` in `haystack` if it exists or HAYSTACK_SIZE +pub fn fallible_find_subarray_index( needle: [u8; NEEDLE_SIZE], haystack: [u8; HAYSTACK_SIZE], ) -> u32 { - // Safety: This is safe because the offset is only used as a starting point - // to verify the substring exists - let offset_unchecked = unsafe { find_subarray_index_unsafe(needle, haystack) }; - let mut offset = offset_unchecked; - // Check if offset is valid before attempting verification - if (offset_unchecked < HAYSTACK_SIZE) & (offset_unchecked + NEEDLE_SIZE <= HAYSTACK_SIZE) { + std::static_assert( + NEEDLE_SIZE <= HAYSTACK_SIZE, + "Needle must not be larger than haystack", + ); + std::static_assert(NEEDLE_SIZE != 0, "Needle must not be empty"); + // Safety: The offset is only used as a starting point to verify the subarray exists + let offset_unchecked = unsafe { fallible_find_subarray_index_unsafe(needle, haystack) }; + if (offset_unchecked + NEEDLE_SIZE <= HAYSTACK_SIZE) { + // Ensures the subarray exists for i in 0..NEEDLE_SIZE { - if haystack[i + offset_unchecked] != needle[i] { - offset = HAYSTACK_SIZE; - } + assert_eq(haystack[offset_unchecked + i], needle[i]); } } else { - // If offset is out of bounds, needle was not found - offset = HAYSTACK_SIZE; + assert(offset_unchecked == HAYSTACK_SIZE); } - offset + offset_unchecked } -pub fn is_subarray_in_array( +/// Require the needle to exist in the haystack +pub fn require_subarray_in_array( needle: [u8; NEEDLE_SIZE], haystack: [u8; HAYSTACK_SIZE], ) -> bool { - find_subarray_index(needle, haystack) < HAYSTACK_SIZE + fallible_find_subarray_index(needle, haystack) != HAYSTACK_SIZE } /// Safety: This is safe because the offset is only used as a starting point /// to verify the substring exists -pub unconstrained fn find_subarray_index_unsafe( +pub unconstrained fn fallible_find_subarray_index_unsafe( needle: [u8; NEEDLE_SIZE], haystack: [u8; HAYSTACK_SIZE], ) -> u32 { let mut result = HAYSTACK_SIZE; // Default to "not found" value - // Handle edge cases - if NEEDLE_SIZE == 0 { - result = 0; - } else if NEEDLE_SIZE <= HAYSTACK_SIZE { - // Search for the needle in the haystack + // Search for the needle in the haystack + if NEEDLE_SIZE <= HAYSTACK_SIZE { for i in 0..(HAYSTACK_SIZE - NEEDLE_SIZE + 1) { let mut found = true; for j in 0..NEEDLE_SIZE { diff --git a/src/noir/lib/utils/src/tests.nr b/src/noir/lib/utils/src/tests.nr index e1d106e03..7de29fa2c 100644 --- a/src/noir/lib/utils/src/tests.nr +++ b/src/noir/lib/utils/src/tests.nr @@ -1,5 +1,5 @@ use crate::{ - check_zero_padding, find_subarray_index, get_mrz_from_dg1, get_name_from_mrz, + check_zero_padding, fallible_find_subarray_index, get_mrz_from_dg1, get_name_from_mrz, pack_be_bytes_into_fields, pack_be_bytes_into_u128s, split_array, unsafe_get_asn1_element_length, }; @@ -345,73 +345,71 @@ fn test_unsafe_get_asn1_element_length() { /// Find subarray index functions #[test] -fn test_find_subarray_index_various_cases() { - // Test case 1: Basic substring at index 3 - let needle1 = [0x04, 0x05, 0x06]; - let haystack1 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a]; - let index1 = find_subarray_index(needle1, haystack1); - assert_eq(index1, 3); - - // Test case 2: Substring at the beginning - let needle2 = [0x01, 0x02]; - let haystack2 = [0x01, 0x02, 0x03, 0x04, 0x05]; - let index2 = find_subarray_index(needle2, haystack2); - assert_eq(index2, 0); - - // Test case 3: Substring at the end - let needle3 = [0x04, 0x05]; - let haystack3 = [0x01, 0x02, 0x03, 0x04, 0x05]; - let index3 = find_subarray_index(needle3, haystack3); - assert_eq(index3, 3); - - // Test case 4: Single byte needle - let needle4 = [0x03]; - let haystack4 = [0x01, 0x02, 0x03, 0x04, 0x05]; - let index4 = find_subarray_index(needle4, haystack4); - assert_eq(index4, 2); - - // Test case 5: Empty needle (should return 0) - let needle5: [u8; 0] = []; - let haystack5 = [0x01, 0x02, 0x03, 0x04, 0x05]; - let index5 = find_subarray_index(needle5, haystack5); - assert_eq(index5, 0); - - // Test case 6: Needle not found - completely different bytes - let needle6 = [0xff, 0xee, 0xdd]; - let haystack6 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a]; - let index6 = find_subarray_index(needle6, haystack6); - assert_eq(index6, haystack6.len()); // Should equal haystack size - - // Test case 7: Needle larger than haystack - let needle7 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]; - let haystack7 = [0x01, 0x02, 0x03]; - let index7 = find_subarray_index(needle7, haystack7); - assert_eq(index7, haystack7.len()); // Should equal haystack size - - // Test case 8: Partial match at the end but incomplete - let needle8 = [0x09, 0x0a, 0x0b]; - let haystack8 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a]; - let index8 = find_subarray_index(needle8, haystack8); - assert_eq(index8, haystack8.len()); // Should equal haystack size - - // Test case 9: Multiple false matches (partial matches) - let needle9 = [0x01, 0x01, 0x02]; - let haystack9 = [0x01, 0x03, 0x01, 0x04, 0x01, 0x01, 0x03, 0x05, 0x06, 0x07]; - let index9 = find_subarray_index(needle9, haystack9); - assert_eq(index9, haystack9.len()); // Should equal haystack size - - // Test case 10: Needle appears to match but fails verification - let needle10 = [0x02, 0x03, 0x04]; - let haystack10 = [0x01, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08, 0x09]; - let index10 = find_subarray_index(needle10, haystack10); - assert_eq(index10, haystack10.len()); // Should equal haystack size +fn test_fallible_find_subarray_index_various_cases() { + // Basic substring at index 3 + let needle = [0x04, 0x05, 0x06]; + let haystack = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a]; + let index = fallible_find_subarray_index(needle, haystack); + assert_eq(index, 3); + + // Substring at the beginning + let needle = [0x01, 0x02]; + let haystack = [0x01, 0x02, 0x03, 0x04, 0x05]; + let index = fallible_find_subarray_index(needle, haystack); + assert_eq(index, 0); + + // Substring at the end + let needle = [0x04, 0x05]; + let haystack = [0x01, 0x02, 0x03, 0x04, 0x05]; + let index = fallible_find_subarray_index(needle, haystack); + assert_eq(index, 3); + + // Single byte needle + let needle = [0x03]; + let haystack = [0x01, 0x02, 0x03, 0x04, 0x05]; + let index = fallible_find_subarray_index(needle, haystack); + assert_eq(index, 2); + + // Partial match at the beginning but incomplete + let needle = [0x01, 0x02, 0x03, 0x01]; + let haystack = [0x01, 0x02, 0x03, 0x04]; + let index = fallible_find_subarray_index(needle, haystack); + assert_eq(index, haystack.len()); // Should equal haystack size + + // Partial match at the end but incomplete + let needle = [0x02, 0x03, 0x04, 0x01]; + let haystack = [0x01, 0x02, 0x03, 0x04]; + let index = fallible_find_subarray_index(needle, haystack); + assert_eq(index, haystack.len()); // Should equal haystack size + + // Needle not found - completely different bytes + let needle = [0xff, 0xee, 0xdd]; + let haystack = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a]; + let index = fallible_find_subarray_index(needle, haystack); + assert_eq(index, haystack.len()); // Should equal haystack size +} + +#[test(should_fail_with = "Needle must not be empty")] +fn test_fallible_find_subarray_index_should_fail() { + // Empty needle + let needle: [u8; 0] = []; + let haystack = [0x01, 0x02, 0x03, 0x04, 0x05]; + let _ = fallible_find_subarray_index(needle, haystack); +} + +#[test(should_fail_with = "Needle must not be larger than haystack")] +fn test_fallible_find_subarray_index_should_fail_2() { + // Needle larger than haystack + let needle = [0x01, 0x02, 0x03, 0x04]; + let haystack = [0x01, 0x02, 0x03]; + let _ = fallible_find_subarray_index(needle, haystack); } pub fn verify_subarray_in_array( needle: [u8; NEEDLE_SIZE], haystack: [u8; HAYSTACK_SIZE], ) { - let offset = find_subarray_index(needle, haystack); + let offset = fallible_find_subarray_index(needle, haystack); assert(offset < HAYSTACK_SIZE, "Needle not found in haystack") }