-
Notifications
You must be signed in to change notification settings - Fork 31
feat: implement fragmented zkpassport age verification #262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Implements a fragmented zkPassport age verification flow that supports two circuit chains depending on TBS certificate size (single-pass signature verification for smaller TBS, and split SHA-256 + signature verification for larger TBS).
Changes:
- Introduces
SaltedValue<T>and new shared types/constants to support salted commitments and typed byte buffers. - Adds a new
partial_sha256library and new “fragmented_age_check” workspace with 4-circuit and 5-circuit pipelines. - Updates RSA signature utilities (PSS salt length parameter + pre-hashed RSA verification helper) and refactors commitments to include expiry date.
Reviewed changes
Copilot reviewed 57 out of 57 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| noir-examples/noir-passport-examples/zkpassport_libs/utils/src/types.nr | Adds new common types (dates, digests, buffers) and SaltedValue<T> with Poseidon2 hashing helpers. |
| noir-examples/noir-passport-examples/zkpassport_libs/utils/src/lib.nr | Adds little-endian byte packing and MRZ expiry-date extraction helpers. |
| noir-examples/noir-passport-examples/zkpassport_libs/utils/src/constants.nr | Introduces shared constants (DG1 lengths, nullifier type tags). |
| noir-examples/noir-passport-examples/zkpassport_libs/utils/Nargo.toml | Adds Poseidon dependency for the new salted hashing utilities. |
| noir-examples/noir-passport-examples/zkpassport_libs/sig-check/rsa/src/lib.nr | Adds PSS salt length parameter and a helper for verifying RSA signatures from a precomputed hash. |
| noir-examples/noir-passport-examples/zkpassport_libs/sig-check/rsa/Nargo.toml | Switches RSA dependency from local path to a pinned git tag. |
| noir-examples/noir-passport-examples/zkpassport_libs/partial-sha256/src/lib.nr | New partial SHA-256 implementation for splitting hashing across circuits + Poseidon state/data commitments. |
| noir-examples/noir-passport-examples/zkpassport_libs/partial-sha256/Nargo.toml | New package manifest for partial_sha256. |
| noir-examples/noir-passport-examples/zkpassport_libs/data-check/tbs-pubkey/src/lib.nr | Refactors ECDSA pubkey presence checks to search within TBS rather than requiring a provided offset. |
| noir-examples/noir-passport-examples/zkpassport_libs/data-check/integrity/src/lib.nr | Adds DG1 sizing helpers and SHA-256-based integrity checks used by fragmented circuits. |
| noir-examples/noir-passport-examples/zkpassport_libs/commitment/scoped-nullifier/src/lib.nr | Updates nullifier derivation to use salted commitments + adds nullifier “type” output. |
| noir-examples/noir-passport-examples/zkpassport_libs/commitment/integrity-to-disclosure/src/lib.nr | Updates commitment chaining to include salted expiry date and salted DG1/private-nullifier hashes. |
| noir-examples/noir-passport-examples/zkpassport_libs/commitment/common/src/lib.nr | Updates shared commitment primitives to use salted values and optionally salt scoped nullifiers. |
| noir-examples/noir-passport-examples/passport_validity_check/src/lib.nr | Updates calls to RSA verification to pass the new pss_salt_len argument. |
| noir-examples/noir-passport-examples/noir_rsa/src/types.nr | Removes local noir_rsa types module (migrating to external dependency). |
| noir-examples/noir-passport-examples/noir_rsa/src/lib.nr | Removes local noir_rsa library module. |
| noir-examples/noir-passport-examples/noir_rsa/README.md | Removes local noir_rsa readme. |
| noir-examples/noir-passport-examples/noir_rsa/Nargo.toml | Removes local noir_rsa package manifest. |
| noir-examples/noir-passport-examples/noir_native_sha256/src/tests.nr | Removes old native SHA-256 test harness. |
| noir-examples/noir-passport-examples/noir_native_sha256/src/ryan_sha256_noir.nr | Removes old SHA-256 implementation file. |
| noir-examples/noir-passport-examples/noir_native_sha256/src/ryan_sha256_constants.nr | Removes old SHA-256 constants file. |
| noir-examples/noir-passport-examples/noir_native_sha256/src/ryan_sha256_compression.nr | Removes old SHA-256 compression implementation file. |
| noir-examples/noir-passport-examples/noir_native_sha256/src/lib.nr | Removes old noir_native_sha256 library root. |
| noir-examples/noir-passport-examples/noir_native_sha256/Prover.toml | Removes old prover input fixture. |
| noir-examples/noir-passport-examples/noir_native_sha256/Nargo.toml | Removes old noir_native_sha256 package manifest. |
| noir-examples/noir-passport-examples/fragmented_age_check/sig_check_id_data_720/src/main.nr | New circuit: verifies DSC signature over signed attributes (720-byte TBS path) and commits to ID data. |
| noir-examples/noir-passport-examples/fragmented_age_check/sig_check_id_data_720/Nargo.toml | Package manifest for sig_check_id_data_720. |
| noir-examples/noir-passport-examples/fragmented_age_check/sig_check_id_data_1300/src/main.nr | New circuit: verifies DSC signature over signed attributes (1300-byte TBS path) and commits to ID data. |
| noir-examples/noir-passport-examples/fragmented_age_check/sig_check_id_data_1300/Nargo.toml | Package manifest for sig_check_id_data_1300. |
| noir-examples/noir-passport-examples/fragmented_age_check/sig_check_dsc_720/src/main.nr | New circuit: verifies CSCA signature over DSC certificate for small TBS path. |
| noir-examples/noir-passport-examples/fragmented_age_check/sig_check_dsc_720/Nargo.toml | Package manifest for sig_check_dsc_720. |
| noir-examples/noir-passport-examples/fragmented_age_check/sig_check_dsc_1300_verify/src/main.nr | New circuit: completes SHA-256 over large TBS and verifies CSCA RSA signature with pre-hashed verification. |
| noir-examples/noir-passport-examples/fragmented_age_check/sig_check_dsc_1300_verify/Nargo.toml | Package manifest for sig_check_dsc_1300_verify. |
| noir-examples/noir-passport-examples/fragmented_age_check/sig_check_dsc_1300_hash/src/main.nr | New circuit: processes first chunk of large TBS with SHA-256 start and commits to state+data. |
| noir-examples/noir-passport-examples/fragmented_age_check/sig_check_dsc_1300_hash/Nargo.toml | Package manifest for sig_check_dsc_1300_hash. |
| noir-examples/noir-passport-examples/fragmented_age_check/scripts/case2/prove-circuits.sh | Adds automation script to prove the 5-circuit chain. |
| noir-examples/noir-passport-examples/fragmented_age_check/scripts/case2/prepare-circuits.sh | Adds automation script to prepare keys for the 5-circuit chain. |
| noir-examples/noir-passport-examples/fragmented_age_check/scripts/case2/compile-circuits.sh | Adds automation script to compile circuits for the 5-circuit chain. |
| noir-examples/noir-passport-examples/fragmented_age_check/scripts/case1/prove-circuits.sh | Adds automation script to prove the 4-circuit chain. |
| noir-examples/noir-passport-examples/fragmented_age_check/scripts/case1/prepare-circuits.sh | Adds automation script to prepare keys for the 4-circuit chain. |
| noir-examples/noir-passport-examples/fragmented_age_check/scripts/case1/compile-circuits.sh | Adds automation script to compile circuits for the 4-circuit chain. |
| noir-examples/noir-passport-examples/fragmented_age_check/data_check_integrity_sa/src/main.nr | New circuit: checks DG1↔eContent↔SignedAttributes integrity and commits to disclosure inputs. |
| noir-examples/noir-passport-examples/fragmented_age_check/data_check_integrity_sa/Nargo.toml | Package manifest for data_check_integrity_sa. |
| noir-examples/noir-passport-examples/fragmented_age_check/compare_age/src/main.nr | New circuit: checks expiry, compares age bounds, and produces scoped nullifier + parameter commitment. |
| noir-examples/noir-passport-examples/fragmented_age_check/compare_age/Nargo.toml | Package manifest for compare_age circuit package. |
| noir-examples/noir-passport-examples/fragmented_age_check/benchmark-inputs/case2/sig_check_id_data_1300_prover.toml | Adds benchmark/prover inputs for case2 ID-data circuit. |
| noir-examples/noir-passport-examples/fragmented_age_check/benchmark-inputs/case2/sig_check_dsc_1300_verify_prover.toml | Adds benchmark/prover inputs for case2 DSC verification circuit. |
| noir-examples/noir-passport-examples/fragmented_age_check/benchmark-inputs/case2/sig_check_dsc_1300_hash_prover.toml | Adds benchmark/prover inputs for case2 DSC hash circuit. |
| noir-examples/noir-passport-examples/fragmented_age_check/benchmark-inputs/case2/data_check_integrity_sa_prover.toml | Adds benchmark/prover inputs for case2 integrity circuit. |
| noir-examples/noir-passport-examples/fragmented_age_check/benchmark-inputs/case2/compare_age_prover.toml | Adds benchmark/prover inputs for case2 compare-age circuit. |
| noir-examples/noir-passport-examples/fragmented_age_check/benchmark-inputs/case1/sig_check_id_data_720_prover.toml | Adds benchmark/prover inputs for case1 ID-data circuit. |
| noir-examples/noir-passport-examples/fragmented_age_check/benchmark-inputs/case1/sig_check_dsc_720_prover.toml | Adds benchmark/prover inputs for case1 DSC verification circuit. |
| noir-examples/noir-passport-examples/fragmented_age_check/benchmark-inputs/case1/data_check_integrity_sa_prover.toml | Adds benchmark/prover inputs for case1 integrity circuit. |
| noir-examples/noir-passport-examples/fragmented_age_check/benchmark-inputs/case1/compare_age_prover.toml | Adds benchmark/prover inputs for case1 compare-age circuit. |
| noir-examples/noir-passport-examples/fragmented_age_check/README.md | Documents the two circuit-chain approaches and when to use each. |
| noir-examples/noir-passport-examples/fragmented_age_check/Nargo.toml | Adds a workspace manifest for the fragmented age-check circuits. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [dependencies] | ||
| poseidon = { tag = "v0.1.2", git = "https://github.com/zkpassport/noir_poseidon2" } |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utils now depends on a different poseidon git repository/tag than the rest of the workspace (most other crates use https://github.com/noir-lang/poseidon tag v0.1.1). Because Poseidon2 hashes are part of cross-circuit commitments, mixing implementations risks producing incompatible hashes between crates. Consider standardizing all crates on the same poseidon dependency source/version (or renaming the dependency) to ensure consistent hashing across the proof chain.
| pub fn commit_to_disclosure<let SA_SIZE: u32, let ECONTENT_SIZE: u32>( | ||
| comm_in: Field, | ||
| salt_in: Field, | ||
| salt_out: Field, | ||
| dg1: [u8; 95], | ||
| salted_dg1: SaltedValue<[u8; 95]>, |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The top-of-file Circuit C header comment is now out of date (it still describes comm_out as H(salt, dg1, private_nullifier) and references inputs that no longer exist). Since this function now commits hash_salt_dg1_private_nullifier(salted_dg1_hash, salted_expiry_date_hash, salted_private_nullifier_hash), please update the documentation block accordingly so callers understand what is actually being proven/committed.
| let pubkey_x_offset = utils::find_subarray_index(pubkey_x, tbs); | ||
| let pubkey_y_offset = utils::find_subarray_index(pubkey_y, tbs); | ||
| for i in 0..PUBKEY_SIZE { | ||
| assert( | ||
| tbs[i + pubkey_x_offset] == pubkey_x[i], | ||
| "Public key x coord of DSC not found in TBS", | ||
| ); | ||
| } | ||
| for i in 0..PUBKEY_SIZE { | ||
| assert( | ||
| tbs[i + pubkey_y_offset] == pubkey_y[i], | ||
| "Public key y coord of DSC not found in TBS", |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
find_subarray_index returns TBS_SIZE when the subarray is not found (utils/src/lib.nr:688-710). The subsequent indexing tbs[i + pubkey_x_offset] / tbs[i + pubkey_y_offset] will go out of bounds in that case, causing a runtime failure instead of a clean assertion. Add an explicit check that the offsets are < TBS_SIZE (or use is_subarray_in_array) before indexing, and emit the intended error message when not found.
| // Part 2 RSA verification - takes pre-computed hash (used in fragmented circuits) | ||
| pub fn verify_rsa_signature<let SIG_BYTES: u32, let IS_PSS: u32, let DATA_TO_SIGN_MAX_LEN: u32, let HASH_BYTE_SIZE: u32>( | ||
| pubkey_bytes: [u8; SIG_BYTES], | ||
| sig_bytes: [u8; (((SIG_BYTES * 8) + 7) / 8)], | ||
| redc_param_bytes: [u8; SIG_BYTES + 1], | ||
| exponent: u32, | ||
| msg_hash: [u8; 32] // Pre-computed SHA256 hash | ||
| ) -> bool { | ||
| assert( | ||
| (SIG_BYTES == 768) | ||
| | (SIG_BYTES == 512) | ||
| | (SIG_BYTES == 384) | ||
| | (SIG_BYTES == 256) | ||
| | (SIG_BYTES == 128), | ||
| "Only modulus of bit size 1024, 2048, 3072, 4096 and 6144 are supported", | ||
| ); | ||
|
|
||
| let pubkey = | ||
| utils::pack_be_bytes_into_u128s::<SIG_BYTES, (SIG_BYTES + 14) / 15, 15>(pubkey_bytes); | ||
| let redc_param = utils::pack_be_bytes_into_u128s::<SIG_BYTES + 1, _, 15>(redc_param_bytes); | ||
| let params = BigNumParams::new(false, pubkey, redc_param); | ||
|
|
||
| let signature = RuntimeBigNum::from_be_bytes(params, sig_bytes); | ||
| verify_sha256_pkcs1v15::<_, SIG_BYTES * 8>(msg_hash, signature, exponent) | ||
| } |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
verify_rsa_signature is hard-coded to SHA-256 PKCS#1 v1.5 (it always calls verify_sha256_pkcs1v15 and takes msg_hash: [u8; 32]), but still exposes IS_PSS and HASH_BYTE_SIZE generics. This is misleading and can be misused. Consider removing the unused generics, or add asserts like assert(IS_PSS == 0) / assert(HASH_BYTE_SIZE == 32) to fail fast if instantiated incorrectly.
| pub fn check_dg1_hash_within_sod( | ||
| dg1: [u8; 95], | ||
| dg1_padded_length: u64, | ||
| econtent: [u8; 200], | ||
| econtent_len: u32, | ||
| dg1_hash_offset: u32, | ||
| ) { | ||
| // Check zero padding for econtent | ||
| check_zero_padding(econtent, econtent_len); | ||
|
|
||
| let dg1_hash = sha256_var(dg1, dg1_padded_length); | ||
|
|
||
| // The DG1 hash is located at the start of the SOD | ||
| // (offset is always 0 for DG1) | ||
| for i in 0..32 { | ||
| assert(dg1_hash[i] == econtent[dg1_hash_offset + i]); | ||
| } |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check_dg1_hash_within_sod indexes econtent[dg1_hash_offset + i] without validating that dg1_hash_offset + 32 <= econtent_len (or even <= econtent.len()). Since dg1_hash_offset is a witness input in data_check_integrity_sa, add an explicit bounds assertion before the loop to avoid out-of-range indexing / unexpected behavior.
Implements fragmented zkPassport age verification system with two different circuit chains based on TBS certificate size.