Skip to content

Commit

Permalink
Merge pull request #325 from zk-passport/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
remicolin authored Feb 3, 2025
2 parents 5f8ea4d + 004be44 commit 92753c3
Show file tree
Hide file tree
Showing 458 changed files with 20,712 additions and 18,198 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- name: Run Tests (Circuits)
working-directory: ./circuits
env:
FULL_TEST_SUITE: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/openpassportv2' }}
FULL_TEST_SUITE: false
run: yarn test

- name: Run Tests (Common)
Expand Down
4 changes: 2 additions & 2 deletions app/src/screens/ProveScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Progress, Spinner, Text, XStack, YStack } from 'tamagui';

import {
DEVELOPMENT_MODE,
max_cert_bytes,
max_dsc_bytes,
} from '../../../common/src/constants/constants';
import {
DisclosureOptions,
Expand Down Expand Up @@ -193,7 +193,7 @@ const ProveScreen: React.FC<ProveScreenProps> = ({
const cscaInputs = generateCircuitInputsDSC(
dscSecret as string,
passportData.dsc,
max_cert_bytes,
max_dsc_bytes,
selectedApp.devMode,
);
[dscProof, proof] = await Promise.all([
Expand Down
4 changes: 2 additions & 2 deletions app/src/utils/generateInputsInApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
OpenPassportApp,
} from '../../../common/src/utils/appType';
import {
generateCircuitInputsDisclose,
generateCircuitInputsVCandDisclose,
generateCircuitInputsProve,
} from '../../../common/src/utils/generateInputs';
import { fetchTreeFromUrl } from '../../../common/src/utils/pubkeyTree';
Expand Down Expand Up @@ -100,7 +100,7 @@ export const generateCircuitInputsInApp = async (
disclosureOptionsDisclose.excludedCountries.value.map(country =>
getCountryCode(country),
);
return generateCircuitInputsDisclose(
return generateCircuitInputsVCandDisclose(
secret,
PASSPORT_ATTESTATION_ID,
passportData,
Expand Down
3 changes: 2 additions & 1 deletion circuits/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
inputs
build
build/
!scripts/build/
node_modules/
err.log
.env
113 changes: 82 additions & 31 deletions circuits/circuits/disclose/vc_and_disclose.circom
Original file line number Diff line number Diff line change
@@ -1,64 +1,115 @@
pragma circom 2.1.9;

include "../utils/passport/disclose/verify_commitment.circom";
include "../utils/passport/disclose/disclose.circom";
include "../utils/passport/disclose/proveCountryIsNotInList.circom";
include "../utils/passport/ofac/ofac_name.circom";
include "../utils/passport/disclose/verify_commitment.circom";
include "../utils/passport/date/isValid.circom";

template VC_AND_DISCLOSE( nLevels,FORBIDDEN_COUNTRIES_LIST_LENGTH) {

/// @title VC_AND_DISCLOSE
/// @notice Verify user's commitment is part of the merkle tree and optionally disclose data from DG1
/// @param nLevels Maximum number of levels in the merkle tree
/// @param MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH Maximum number of countries present in the forbidden countries list
/// @input secret Secret of the user — used to reconstruct commitment and generate nullifier
/// @input attestation_id Attestation ID of the credential used to generate the commitment
/// @input dg1 Data group 1 of the passport
/// @input eContent_shaBytes_packed_hash Hash of the eContent packed
/// @input dsc_tree_leaf Leaf of the DSC tree, to keep a record of the full CSCA and DSC that were used
/// @input merkle_root Root of the commitment merkle tree
/// @input leaf_depth Actual size of the merkle tree
/// @input path Path of the commitment in the merkle tree
/// @input siblings Siblings of the commitment in the merkle tree
/// @input selector_dg1 bitmap used which bytes from the dg1 are revealed
/// @input majority Majority user wants to prove he is older than: YY — ASCII
/// @input current_date Current date: YYMMDD — number
/// @input selector_older_than bitmap used to reveal the majority
/// @input forbidden_countries_list Forbidden countries list user wants to prove he is not from
/// @input smt_leaf_key value of the leaf of the smt corresponding to his path
/// @input smt_root root of the smt
/// @input smt_siblings siblings of the smt
/// @input selector_ofac bitmap used to reveal the OFAC verification result
/// @input scope Scope of the application users generates the proof for
/// @input user_identifier User identifier — address or UUID
/// @output revealedData_packed Packed revealed data
/// @output forbidden_countries_list_packed Packed forbidden countries list
/// @output nullifier Scope nullifier - not deterministic on the passport data
template VC_AND_DISCLOSE(nLevels, MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH) {
signal input secret;
signal input attestation_id;
signal input pubkey_leaf;
signal input dg1[93];
signal input dg2_hash[64];
signal input eContent_shaBytes_packed_hash;
signal input dsc_tree_leaf;

signal input merkle_root;
signal input merkletree_size;
signal input leaf_depth;
signal input path[nLevels];
signal input siblings[nLevels];

signal input selector_dg1[88]; // 88 for MRZ
signal input selector_dg1[88];

signal input majority[2];
signal input current_date[6];
signal input selector_older_than;
signal input scope;
signal input current_date[6]; // YYMMDD - num
signal input majority[2]; // YY - ASCII
signal input user_identifier;

// ofac check
signal input forbidden_countries_list[MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH * 3];

signal input smt_leaf_key;
signal input smt_root;
signal input smt_siblings[256];
signal input selector_ofac;
// forbidden countries list
signal input forbidden_countries_list[FORBIDDEN_COUNTRIES_LIST_LENGTH * 3];

signal input scope;
signal input user_identifier;

// verify commitment is part of the merkle tree
VERIFY_COMMITMENT(nLevels)(secret, attestation_id, pubkey_leaf, dg1, dg2_hash, merkle_root, merkletree_size, path, siblings);
VERIFY_COMMITMENT(nLevels)(
secret,
attestation_id,
dg1,
eContent_shaBytes_packed_hash,
dsc_tree_leaf,
merkle_root,
leaf_depth,
path,
siblings
);

// verify passport validity and disclose optional data
component disclose = DISCLOSE();
// verify passport validity
signal validity_ASCII[6];
for (var i = 0; i < 6; i++) {
validity_ASCII[i] <== dg1[70 +i];
}

IsValid()(current_date,validity_ASCII);

// disclose optional data
component disclose = DISCLOSE(10);
disclose.dg1 <== dg1;
disclose.selector_dg1 <== selector_dg1;
disclose.selector_older_than <== selector_older_than;
disclose.current_date <== current_date;
disclose.majority <== majority;

// generate scope nullifier
component poseidon_nullifier = Poseidon(2);
poseidon_nullifier.inputs[0] <== secret;
poseidon_nullifier.inputs[1] <== scope;
signal output nullifier <== poseidon_nullifier.out;
disclose.smt_leaf_key <== smt_leaf_key;
disclose.smt_root <== smt_root;
disclose.smt_siblings <== smt_siblings;
disclose.selector_ofac <== selector_ofac;
disclose.forbidden_countries_list <== forbidden_countries_list;

signal output revealedData_packed[3] <== disclose.revealedData_packed;
signal output older_than[2] <== disclose.older_than;

// COUNTRY IS IN LIST
signal output forbidden_countries_list_packed_disclosed[2] <== ProveCountryIsNotInList(FORBIDDEN_COUNTRIES_LIST_LENGTH)(dg1, forbidden_countries_list);
var chunkLength = computeIntChunkLength(MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH * 3);
signal output forbidden_countries_list_packed[chunkLength] <== disclose.forbidden_countries_list_packed;

// OFAC
signal ofacCheckResult <== OFAC_NAME()(dg1,smt_leaf_key,smt_root,smt_siblings);
signal ofacIntermediaryOutput <== ofacCheckResult * selector_ofac;
signal output ofac_result <== ofacIntermediaryOutput;
signal output nullifier <== Poseidon(2)([secret, scope]);
}

component main { public [ merkle_root, smt_root, scope, user_identifier, current_date, attestation_id] } = VC_AND_DISCLOSE(16,20);
component main {
public [
merkle_root,
smt_root,
scope,
user_identifier,
current_date,
attestation_id
]
} = VC_AND_DISCLOSE(33, 10);
139 changes: 107 additions & 32 deletions circuits/circuits/dsc/dsc.circom
Original file line number Diff line number Diff line change
Expand Up @@ -9,57 +9,132 @@ include "@zk-kit/binary-merkle-root.circom/src/binary-merkle-root.circom";
include "../utils/passport/customHashers.circom";
include "../utils/passport/signatureAlgorithm.circom";
include "../utils/passport/signatureVerifier.circom";
include "@openpassport/zk-email-circuits/utils/bytes.circom";
include "../utils/passport/checkPubkeysEqual.circom";
include "../utils/passport/constants.circom";
include "../utils/crypto/bitify/bytes.circom";
include "../utils/passport/BytesToNum.circom";

/// @title DSC
/// @notice Circuit for verifying DSC certificate signature using CSCA certificate
/// @param signatureAlgorithm Algorithm used for DSC signature verification - contains the information about the final hash algorithm
/// @param n_csca Number of bits per chunk the CSCA key is split into
/// @param k_csca Number of chunks the CSCA key is split into
/// @input raw_csca Raw CSCA certificate data
/// @input raw_csca_actual_length Actual length of CSCA certificate
/// @input csca_pubKey_offset Offset of CSCA public key in certificate
/// @input csca_pubKey_actual_size Actual size of CSCA public key in bytes
/// @input raw_dsc Raw DSC certificate data
/// @input raw_dsc_padded_length Actual length of DSC certificate
/// @input csca_pubKey CSCA public key for signature verification
/// @input signature DSC signature
/// @input merkle_root Root of CSCA Merkle tree
/// @input path Path indices for CSCA Merkle proof
/// @input siblings Sibling hashes for CSCA Merkle proof
/// @output dsc_tree_leaf Leaf to be added to the DSC Merkle tree
template DSC(
signatureAlgorithm,
n_csca,
k_csca
) {
var MAX_CSCA_LENGTH = getMaxCSCALength();
var MAX_DSC_LENGTH = getMaxDSCLength();
var nLevels = getMaxCSCALevels();

template DSC(signatureAlgorithm, n_dsc, k_dsc, n_csca, k_csca, max_cert_bytes, dscPubkeyBytesLength, nLevels) {

// variables verification
assert(max_cert_bytes % 64 == 0);
assert(n_csca * k_csca > max_cert_bytes);
assert(MAX_CSCA_LENGTH % 64 == 0);
assert(MAX_DSC_LENGTH % 64 == 0);
// assert(n_csca * k_csca > max_dsc_bytes); // not sure what this is for
assert(n_csca <= (255 \ 2));

var hashLength = getHashLength(signatureAlgorithm);
var minKeyLength = getMinKeyLength(signatureAlgorithm);
var kLengthFactor = getKLengthFactor(signatureAlgorithm);
var kScaled = k_csca * kLengthFactor;
var hashLength = getHashLength(signatureAlgorithm);

var MAX_CSCA_PUBKEY_LENGTH = n_csca * kScaled / 8;

signal input raw_csca[MAX_CSCA_LENGTH];
signal input raw_csca_actual_length;
signal input csca_pubKey_offset;
signal input csca_pubKey_actual_size;

signal input raw_dsc[MAX_DSC_LENGTH];
signal input raw_dsc_padded_length;

signal input raw_dsc_cert[max_cert_bytes];
signal input raw_dsc_cert_padded_bytes;
signal input csca_pubKey[kScaled];
signal input signature[kScaled];
signal input dsc_pubKey[k_dsc];
signal input dsc_pubKey_offset;
signal input secret;

signal input merkle_root;
signal input path[nLevels];
signal input siblings[nLevels];

// first, compute raw_dsc_actual_length
// by getting the values of the last 4 bytes of the padded length
// cf sha padding
signal last_four_bytes_of_padded_length[4] <== SelectSubArray(MAX_DSC_LENGTH, 4)(raw_dsc, raw_dsc_padded_length - 4, 4);
signal computed_length_bits <== BytesToNum()(last_four_bytes_of_padded_length);
signal raw_dsc_actual_length <== computed_length_bits / 8;

// leaf
signal leaf <== LeafHasher(kScaled)(csca_pubKey, signatureAlgorithm);
// sanity check: raw_dsc[raw_dsc_actual_length] should be 128
signal raw_dsc_at_actual_length <== ItemAtIndex(MAX_DSC_LENGTH)(raw_dsc, raw_dsc_actual_length);
signal isByte128 <== IsEqual()([raw_dsc_at_actual_length, 128]);
isByte128 === 1;

signal computed_merkle_root <== BinaryMerkleRoot(nLevels)(leaf, nLevels, path, siblings);
merkle_root === computed_merkle_root;

// verify certificate signature
signal hashedCertificate[hashLength] <== ShaBytesDynamic(hashLength, max_cert_bytes)(raw_dsc_cert, raw_dsc_cert_padded_bytes);
// check that raw_dsc is padded with 0s after the sha padding
// this should guarantee the dsc commitment is unique for each commitment
component byte_checks[MAX_DSC_LENGTH];
for (var i = 0; i < MAX_DSC_LENGTH; i++) {
byte_checks[i] = GreaterThan(12);
byte_checks[i].in[0] <== i;
byte_checks[i].in[1] <== raw_dsc_padded_length;

// If i >= raw_dsc_padded_length, the byte must be 0
raw_dsc[i] * byte_checks[i].out === 0;
}

SignatureVerifier(signatureAlgorithm, n_csca, k_csca)(hashedCertificate, csca_pubKey, signature);
// check csca_pubKey_actual_size is at least the minimum key length
signal csca_pubKey_actual_size_in_range <== GreaterEqThan(12)([
csca_pubKey_actual_size,
minKeyLength * kLengthFactor / 8
]);
csca_pubKey_actual_size_in_range === 1;

// verify DSC csca_pubKey
component shiftLeft = VarShiftLeft(max_cert_bytes, dscPubkeyBytesLength); // use select subarray for dscPubKey variable length
shiftLeft.in <== raw_dsc_cert;
shiftLeft.shift <== dsc_pubKey_offset;
component spbt_1 = SplitBytesToWords(dscPubkeyBytesLength, n_dsc, k_dsc);
spbt_1.in <== shiftLeft.out;
for (var i = 0; i < k_dsc; i++) {
dsc_pubKey[i] === spbt_1.out[i];
}
// check offsets refer to valid ranges
signal csca_pubKey_offset_in_range <== LessEqThan(12)([
csca_pubKey_offset + csca_pubKey_actual_size,
raw_csca_actual_length
]);
csca_pubKey_offset_in_range === 1;

// compute leaf in the CSCA Merkle tree and verify inclusion
signal csca_hash <== PackBytesAndPoseidon(MAX_CSCA_LENGTH)(raw_csca);
signal csca_tree_leaf <== Poseidon(2)([csca_hash, raw_csca_actual_length]);
signal computed_merkle_root <== BinaryMerkleRoot(nLevels)(csca_tree_leaf, nLevels, path, siblings);
merkle_root === computed_merkle_root;

// blinded dsc commitment
signal pubkeyHash <== CustomHasher(k_dsc)(dsc_pubKey);
signal output blinded_dsc_commitment <== Poseidon(2)([secret, pubkeyHash]);
}
// get CSCA public key from the certificate
signal extracted_csca_pubKey[MAX_CSCA_PUBKEY_LENGTH] <== SelectSubArray(MAX_CSCA_LENGTH, MAX_CSCA_PUBKEY_LENGTH)(
raw_csca,
csca_pubKey_offset,
csca_pubKey_actual_size
);

// check if the CSCA public key is the same as the one in the certificate
// If we end up adding the pubkey in the CSCA leaf, we'll be able to remove this check
CheckPubkeysEqual(n_csca, kScaled, kLengthFactor, MAX_CSCA_PUBKEY_LENGTH)(
csca_pubKey,
extracted_csca_pubKey,
csca_pubKey_actual_size
);

// verify DSC signature
// raw_dsc_padded_length is constrained because an incorrect one
// would yield hashes that have not been signed
signal hashedCertificate[hashLength] <== ShaBytesDynamic(hashLength, MAX_DSC_LENGTH)(raw_dsc, raw_dsc_padded_length);
SignatureVerifier(signatureAlgorithm, n_csca, k_csca)(hashedCertificate, csca_pubKey, signature);

// generate DSC leaf as poseidon(dsc_hash_with_actual_length, csca_tree_leaf)
signal dsc_hash <== PackBytesAndPoseidon(MAX_DSC_LENGTH)(raw_dsc);
signal dsc_hash_with_actual_length <== Poseidon(2)([dsc_hash, raw_dsc_actual_length]);
signal output dsc_tree_leaf <== Poseidon(2)([dsc_hash_with_actual_length, csca_tree_leaf]);
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma circom 2.1.9;

include "../dsc.circom";

component main { public [ merkle_root] } = DSC(36, 64, 4);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma circom 2.1.9;

include "../dsc.circom";

component main { public [ merkle_root ] } = DSC(11, 120, 35);
Loading

0 comments on commit 92753c3

Please sign in to comment.