Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions src/noir/lib/data-check/integrity/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};

Expand All @@ -24,7 +24,8 @@ pub fn get_dg2_hash_from_econtent<let HASH_SIZE: u32>(
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(
Expand Down Expand Up @@ -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(
Expand All @@ -104,7 +105,7 @@ pub fn check_signed_attributes_sha1<let SA_SIZE: u32>(

// 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",
);
}
Expand All @@ -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(
Expand All @@ -146,7 +147,7 @@ pub fn check_signed_attributes_sha224<let SA_SIZE: u32>(

// 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",
);
}
Expand All @@ -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(
Expand All @@ -188,7 +189,7 @@ pub fn check_signed_attributes_sha256<let SA_SIZE: u32>(

// 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",
);
}
Expand All @@ -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(
Expand All @@ -233,7 +234,7 @@ pub fn check_signed_attributes_sha384<let SA_SIZE: u32>(

// 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",
);
}
Expand All @@ -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(
Expand All @@ -278,7 +279,7 @@ pub fn check_signed_attributes_sha512<let SA_SIZE: u32>(

// 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",
);
}
4 changes: 2 additions & 2 deletions src/noir/lib/data-check/tbs-pubkey/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub fn verify_rsa_pubkey_in_tbs<let PUBKEY_SIZE: u32, let TBS_SIZE: u32>(
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");
}
Expand All @@ -17,7 +17,7 @@ pub fn verify_ecdsa_pubkey_in_tbs<let PUBKEY_SIZE: u32, let TBS_SIZE: u32>(
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
Expand Down
6 changes: 3 additions & 3 deletions src/noir/lib/facematch/src/android/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -16,7 +16,7 @@ pub fn get_app_id_from_credential_tbs<let TBS_MAX_LEN: u32, let APP_ID_MAX_LEN:
tbs: [u8; TBS_MAX_LEN],
) -> ([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",
Expand Down Expand Up @@ -91,7 +91,7 @@ pub fn get_app_id_from_credential_tbs<let TBS_MAX_LEN: u32, let APP_ID_MAX_LEN:

pub fn get_key_origin_from_credential_tbs<let TBS_MAX_LEN: u32>(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",
Expand Down
6 changes: 3 additions & 3 deletions src/noir/lib/facematch/src/ios/mod.nr
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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<let TBS_MAX_LEN: u32>(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;
Expand Down Expand Up @@ -44,7 +44,7 @@ pub fn get_app_id_from_credential_tbs<let TBS_MAX_LEN: u32, let APP_ID_MAX_LEN:
tbs: [u8; TBS_MAX_LEN],
) -> ([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)
Expand Down
46 changes: 22 additions & 24 deletions src/noir/lib/utils/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -405,50 +405,48 @@ pub unconstrained fn unsafe_get_asn1_element_length<let N: u32>(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<let NEEDLE_SIZE: u32, let HAYSTACK_SIZE: u32>(
/// 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<let NEEDLE_SIZE: u32, let HAYSTACK_SIZE: u32>(
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<let NEEDLE_SIZE: u32, let HAYSTACK_SIZE: u32>(
/// Require the needle to exist in the haystack
pub fn require_subarray_in_array<let NEEDLE_SIZE: u32, let HAYSTACK_SIZE: u32>(
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<let NEEDLE_SIZE: u32, let HAYSTACK_SIZE: u32>(
pub unconstrained fn fallible_find_subarray_index_unsafe<let NEEDLE_SIZE: u32, let HAYSTACK_SIZE: u32>(
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 {
Expand Down
122 changes: 60 additions & 62 deletions src/noir/lib/utils/src/tests.nr
Original file line number Diff line number Diff line change
@@ -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,
};
Expand Down Expand Up @@ -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<let NEEDLE_SIZE: u32, let HAYSTACK_SIZE: u32>(
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")
}

Expand Down