Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b7c5bc7
feat: selfrica circuit and tests
Vishalkulkarni45 Oct 1, 2025
ed841d7
chore: remove unused code
Vishalkulkarni45 Oct 1, 2025
a91a955
feat: test for ofac,date and olderthan
Vishalkulkarni45 Oct 2, 2025
968a74c
fix: public signal constant
Vishalkulkarni45 Oct 2, 2025
e23322f
feat: add contract tests
Nesopie Oct 8, 2025
838bc49
feat: helper function to gen TEE input
Vishalkulkarni45 Oct 15, 2025
d51ce02
feat: gen circuit inputs with signature
Vishalkulkarni45 Oct 16, 2025
de37ae4
feat: seralized base64
Vishalkulkarni45 Oct 16, 2025
e5c445e
fix: DateIsLessFullYear componenet
Vishalkulkarni45 Oct 17, 2025
fd197fe
feat: register circuit for selfrica
Vishalkulkarni45 Oct 22, 2025
3e44266
feat: selfrica disclose circuit and test
Vishalkulkarni45 Oct 22, 2025
c290010
fix: common module error
Vishalkulkarni45 Oct 23, 2025
c39dc21
feat: add more test and fix constant
Vishalkulkarni45 Oct 23, 2025
6e5a4c7
fix: commitment calculation
Vishalkulkarni45 Oct 23, 2025
cd761a9
feat: selfrica contracts
Nesopie Oct 23, 2025
48ff517
test: selfrica register using unified circuit
Vishalkulkarni45 Oct 24, 2025
750f592
feat: register persona and selfrica circuit
Vishalkulkarni45 Oct 24, 2025
08fdae0
feat: selfrica circuit and tests
Vishalkulkarni45 Oct 1, 2025
c21543b
chore: remove unused code
Vishalkulkarni45 Oct 1, 2025
7910b87
feat: test for ofac,date and olderthan
Vishalkulkarni45 Oct 2, 2025
2804959
fix: public signal constant
Vishalkulkarni45 Oct 2, 2025
a13468a
feat: add contract tests
Nesopie Oct 8, 2025
bced45e
feat: helper function to gen TEE input
Vishalkulkarni45 Oct 15, 2025
ae6fb3a
feat: gen circuit inputs with signature
Vishalkulkarni45 Oct 16, 2025
fa03e1e
feat: seralized base64
Vishalkulkarni45 Oct 16, 2025
ceff539
fix: DateIsLessFullYear componenet
Vishalkulkarni45 Oct 17, 2025
c47769f
feat: register circuit for selfrica
Vishalkulkarni45 Oct 22, 2025
ec0e41d
feat: selfrica disclose circuit and test
Vishalkulkarni45 Oct 22, 2025
8397155
fix: common module error
Vishalkulkarni45 Oct 23, 2025
a2bc9c9
feat: add more test and fix constant
Vishalkulkarni45 Oct 23, 2025
dbcbd76
fix: commitment calculation
Vishalkulkarni45 Oct 23, 2025
be2f557
feat: selfrica contracts
Nesopie Oct 23, 2025
770bebe
test: selfrica register using unified circuit
Vishalkulkarni45 Oct 24, 2025
ae10ae4
feat: register persona and selfrica circuit
Vishalkulkarni45 Oct 24, 2025
043dd4e
Merge remote-tracking branch 'origin/feat/selfrica' into feat/selfrica
Tranquil-Flow Oct 28, 2025
74fb2f6
refactor: contract size reduction for IdentityVerificationHubImplV2
Tranquil-Flow Oct 28, 2025
dcea2b8
feat: disclose circuit for persona
Vishalkulkarni45 Nov 4, 2025
036646a
feat: update persona ofac trees
Vishalkulkarni45 Nov 4, 2025
2708539
feat; register circuit for selfper
Vishalkulkarni45 Nov 5, 2025
db1435b
feat: disclose test for selfper
Vishalkulkarni45 Nov 5, 2025
ed91479
chore: refactor
Vishalkulkarni45 Nov 7, 2025
9f767b0
chore : remove unused circuits
Vishalkulkarni45 Nov 7, 2025
7ff7d36
chore: rename selfper to kyc
Vishalkulkarni45 Nov 7, 2025
9c27329
Merge branch 'dev' into feat/selfrica
Nesopie Nov 11, 2025
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
124 changes: 124 additions & 0 deletions circuits/circuits/disclose/vc_and_disclose_kyc.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
pragma circom 2.1.9;

include "circomlib/circuits/bitify.circom";
include "circomlib/circuits/poseidon.circom";
include "@zk-kit/binary-merkle-root.circom/src/binary-merkle-root.circom";
include "@openpassport/zk-email-circuits/utils/bytes.circom";
include "../utils/passport/customHashers.circom";
include "../utils/kyc/disclose/disclose.circom";

template VC_AND_DISCLOSE_KYC(
MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH,
namedobTreeLevels,
nameyobTreeLevels,
n,
k,
Comment on lines +14 to +15
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unused RSA parameters n and k.

The template parameters n and k are declared but never used anywhere in the circuit. There's no RSA signature verification or any component that consumes these parameters.

 template VC_AND_DISCLOSE(
     MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH,
     namedobTreeLevels,
     nameyobTreeLevels,
-    n,
-    k,
     nLevels
 ) {

And update the main component instantiation:

-} = VC_AND_DISCLOSE(40, 64, 64, 121, 17, 33);
+} = VC_AND_DISCLOSE(40, 64, 64, 33);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
n,
k,
template VC_AND_DISCLOSE(
MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH,
namedobTreeLevels,
nameyobTreeLevels,
nLevels
) {
Suggested change
n,
k,
} = VC_AND_DISCLOSE(40, 64, 64, 33);
🤖 Prompt for AI Agents
In circuits/circuits/disclose/vc_and_disclose_selfrica.circom around lines
17-18, the template declares RSA parameters `n` and `k` that are not used
anywhere; remove `n, k` from the template parameter list and then update any
instantiation of that template (e.g., the main component) to stop passing those
two arguments so the constructor signature and all call sites are consistent.

nLevels
) {
var max_length = KYC_MAX_LENGTH();
var country_length = COUNTRY_LENGTH();
var id_number_length = ID_NUMBER_LENGTH();
var idNumberIdx = ID_NUMBER_INDEX();
var compressed_bit_len = max_length/2;

signal input data_padded[max_length];
signal input compressed_disclose_sel[2];

signal input scope;

signal input forbidden_countries_list[MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH * country_length];

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

signal input ofac_name_dob_smt_leaf_key;
signal input ofac_name_dob_smt_root;
signal input ofac_name_dob_smt_siblings[namedobTreeLevels];

signal input ofac_name_yob_smt_leaf_key;
signal input ofac_name_yob_smt_root;
signal input ofac_name_yob_smt_siblings[nameyobTreeLevels];

signal input selector_ofac;
signal input user_identifier;
signal input current_date[8];
Comment on lines +45 to +46
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bind the public user_identifier to the derived commitment.

user_identifier is declared as a public input but never constrained, letting a prover claim any value while still satisfying the circuit, which breaks the link between the proof and the user identity.

Add an equality constraint before exposing the commitment so the public value matches the hashed signature:

     signal output identity_commitment <== idCommCal.out;
+    user_identifier === idCommCal.out;

Also applies to: 156-157

🤖 Prompt for AI Agents
In circuits/circuits/disclose/vc_and_disclose_selfrica.circom around lines 43-44
(and similarly at 156-157), the public input signal user_identifier is declared
but never constrained; add an equality constraint that binds user_identifier to
the derived value (the hash/derived commitment of the signature or identifier
computed inside the circuit) before the commitment is exposed so the public
input cannot be arbitrarily chosen by the prover; place the constraint
immediately after computing the derived_hash/commitment (e.g., derivedHash ===
user_identifier) and before any signal is marked public or emitted.

signal input majority_age_ASCII[3];
signal input secret;

signal input attestation_id;

// Convert the two decimal inputs back to bit array
signal disclose_sel[max_length];

// Convert disclose_sel_low (first 133 bits) to bit array
component low_bits = Num2Bits(compressed_bit_len);
low_bits.in <== compressed_disclose_sel[0];

// Convert disclose_sel_high (next 133 bits) to bit array
component high_bits = Num2Bits(compressed_bit_len);
high_bits.in <== compressed_disclose_sel[1];

// Combine the bit arrays (little-endian format)
for(var i = 0; i < compressed_bit_len; i++){
disclose_sel[i] <== low_bits.out[i];
}
for(var i = 0; i < compressed_bit_len; i++){
disclose_sel[compressed_bit_len + i] <== high_bits.out[i];
}

component msg_hasher = PackBytesAndPoseidon(max_length);
for (var i = 0; i < max_length; i++) {
msg_hasher.in[i] <== data_padded[i];
}

signal leaf <== Poseidon(2)([secret, msg_hasher.out]);

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

signal id_num[id_number_length];
for (var i = 0; i < id_number_length; i++) {
id_num[i] <== data_padded[idNumberIdx + i];
}

component disclose_circuit = DISCLOSE_KYC(MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH, namedobTreeLevels, nameyobTreeLevels);

for (var i = 0; i < max_length; i++) {
disclose_circuit.data_padded[i] <== data_padded[i];
}
disclose_circuit.selector_data_padded <== disclose_sel;
disclose_circuit.forbidden_countries_list <== forbidden_countries_list;

disclose_circuit.ofac_name_dob_smt_leaf_key <== ofac_name_dob_smt_leaf_key;
disclose_circuit.ofac_name_dob_smt_root <== ofac_name_dob_smt_root;
disclose_circuit.ofac_name_dob_smt_siblings <== ofac_name_dob_smt_siblings;

disclose_circuit.ofac_name_yob_smt_leaf_key <== ofac_name_yob_smt_leaf_key;
disclose_circuit.ofac_name_yob_smt_root <== ofac_name_yob_smt_root;
disclose_circuit.ofac_name_yob_smt_siblings <== ofac_name_yob_smt_siblings;

disclose_circuit.selector_ofac <== selector_ofac;
disclose_circuit.current_date <== current_date;
disclose_circuit.majority_age_ASCII <== majority_age_ASCII;

var revealed_data_packed_chunk_length = computeIntChunkLength(max_length + 2 + 1);
signal output revealedData_packed[revealed_data_packed_chunk_length] <== disclose_circuit.revealedData_packed;

var forbidden_countries_list_packed_chunk_length = computeIntChunkLength(MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH * country_length);
signal output forbidden_countries_list_packed[forbidden_countries_list_packed_chunk_length] <== disclose_circuit.forbidden_countries_list_packed;
signal output nullifier <== Poseidon(2)([secret, scope]);
}

component main {
public [
scope,
merkle_root,
ofac_name_dob_smt_root,
ofac_name_yob_smt_root,
user_identifier,
current_date,
attestation_id
]
} = VC_AND_DISCLOSE_KYC(40, 64, 64, 121, 17, 33);
75 changes: 75 additions & 0 deletions circuits/circuits/register/register_kyc.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
pragma circom 2.1.9;

include "circomlib/circuits/bitify.circom";
include "../utils/kyc/constants.circom";
include "../utils/passport/customHashers.circom";
include "../utils/kyc/verifySignature.circom";


template REGISTER_KYC() {

var max_length = KYC_MAX_LENGTH();
var country_length = COUNTRY_LENGTH();
var id_number_length = ID_NUMBER_LENGTH();
var idNumberIdx = ID_NUMBER_INDEX();

var compressed_bit_len = max_length/2;

signal input data_padded[max_length];

signal input s;
signal input Tx;
signal input Ty;
signal input pubKeyX;
signal input pubKeyY;
signal input r_inv[4];
signal input secret;
signal input attestation_id;

//Calculate msg_hash
component msg_hasher = PackBytesAndPoseidon(max_length);
for (var i = 0; i < max_length; i++) {
msg_hasher.in[i] <== data_padded[i];
}

//msg_hash bit decomposition
//TODO: should we add msg_hash_bits [254] & [255] == 0?
component bit_decompose = Num2Bits(256);
bit_decompose.in <== msg_hasher.out;
signal msg_hash_bits[256] <== bit_decompose.out;


signal msg_hash_limbs[4];
component bits2Num[4];

// Convert msg_hash_bits (little-endian) to 4 LE limbs
for (var i = 0; i < 4; i++) {
bits2Num[i] = Bits2Num(64);
for (var j = 0; j < 64; j++) {
bits2Num[i].in[j] <== msg_hash_bits[i * 64 + j];
}
msg_hash_limbs[i] <== bits2Num[i].out;
}


component verifyIdCommSig = VERIFY_KYC_SIGNATURE();
verifyIdCommSig.s <== s;
verifyIdCommSig.r_inv <== r_inv;
verifyIdCommSig.msg_hash_limbs <== msg_hash_limbs;
verifyIdCommSig.Tx <== Tx;
verifyIdCommSig.Ty <== Ty;
verifyIdCommSig.pubKeyX <== pubKeyX;
verifyIdCommSig.pubKeyY <== pubKeyY;

signal id_num[id_number_length];
for (var i = 0; i < id_number_length; i++) {
id_num[i] <== data_padded[idNumberIdx + i];
}
signal output nullifier <== PackBytesAndPoseidon(id_number_length)(id_num);
signal output commitment <== Poseidon(2)([secret, msg_hasher.out]);

signal output pubkey_hash <== Poseidon(2)([pubKeyX, pubKeyY]);

}

component main {public [attestation_id]} = REGISTER_KYC();
63 changes: 63 additions & 0 deletions circuits/circuits/utils/kyc/babyEcdsa.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// --------------------------------------------------
// Source: https://github.com/cursive-team/babyjubjub-ecdsa
// File: packages/circuits/baby-jubjub-ecdsa/baby_jubjub_ecdsa.circom
// License: MIT
// Author(s): cursive-team
// Changes: no changes
// --------------------------------------------------
pragma circom 2.1.9;

include "circomlib/circuits/babyjub.circom";
include "circomlib/circuits/bitify.circom";
include "circomlib/circuits/escalarmulany.circom";
/**
* BabyJubJubECDSA
* ====================
*
* Converts inputted efficient ECDSA signature to an public key. There is no
* public key validation included. Takes in points in Twisted Edwards form
* and uses Edwards addition and scalar multiplication. Returns computed
* public key in Edwards form.
*/
template BabyJubJubECDSA() {
var bits = 256;
signal input s;
signal input Tx; // T = r^-1 * R
signal input Ty; // T is represented in Twisted Edwards form
signal input Ux; // U = -(m * r^-1 * G)
signal input Uy; // U is represented in Twisted Edwards form

signal output pubKeyX; // Represented in Twisted Edwards form
signal output pubKeyY;

// bitify s
component sBits = Num2Bits(bits);
sBits.in <== s;

// check T, U are on curve
component checkT = BabyCheck();
checkT.x <== Tx;
checkT.y <== Ty;
component checkU = BabyCheck();
checkU.x <== Ux;
checkU.y <== Uy;

// sMultT = s * T
component sMultT = EscalarMulAny(bits);
var i;
for (i=0; i<bits; i++) {
sMultT.e[i] <== sBits.out[i];
}
sMultT.p[0] <== Tx;
sMultT.p[1] <== Ty;

// pubKey = sMultT + U
component pubKey = BabyAdd();
pubKey.x1 <== sMultT.out[0];
pubKey.y1 <== sMultT.out[1];
pubKey.x2 <== Ux;
pubKey.y2 <== Uy;

pubKeyX <== pubKey.xout;
pubKeyY <== pubKey.yout;
}
101 changes: 101 additions & 0 deletions circuits/circuits/utils/kyc/constants.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
pragma circom 2.1.9;

function COUNTRY_INDEX() {
return 0;
}

function COUNTRY_LENGTH() {
return 3;
}

function ID_TYPE_INDEX() {
return COUNTRY_INDEX() + COUNTRY_LENGTH();
}

function ID_TYPE_LENGTH() {
return 27;
}

function ID_NUMBER_INDEX() {
return ID_TYPE_INDEX() + ID_TYPE_LENGTH();
}

function ID_NUMBER_LENGTH() {
return 32;
}

function ISSUANCE_DATE_INDEX() {
return ID_NUMBER_INDEX() + ID_NUMBER_LENGTH();
}

function ISSUANCE_DATE_LENGTH() {
return 8;
}

function EXPIRATION_DATE_INDEX() {
return ISSUANCE_DATE_INDEX() + ISSUANCE_DATE_LENGTH();
}

function EXPIRATION_DATE_LENGTH() {
return 8;
}

function FULL_NAME_INDEX() {
return EXPIRATION_DATE_INDEX() + EXPIRATION_DATE_LENGTH();
}

function FULL_NAME_LENGTH() {
return 64;
}

function DOB_INDEX() {
return FULL_NAME_INDEX() + FULL_NAME_LENGTH();
}

function DOB_LENGTH() {
return 8;
}

function PHOTO_HASH_INDEX() {
return DOB_INDEX() + DOB_LENGTH();
}

function PHOTO_HASH_LENGTH() {
return 32;
}

function PHONE_NUMBER_INDEX() {
return PHOTO_HASH_INDEX() + PHOTO_HASH_LENGTH();
}

function PHONE_NUMBER_LENGTH() {
return 12;
}

function DOCUMENT_INDEX() {
return PHONE_NUMBER_INDEX() + PHONE_NUMBER_LENGTH();
}

function DOCUMENT_LENGTH() {
return 32;
}

function GENDER_INDEX() {
return DOCUMENT_INDEX() + DOCUMENT_LENGTH();
}

function GENDER_LENGTH() {
return 6;
}

function ADDRESS_INDEX() {
return GENDER_INDEX() + GENDER_LENGTH();
}

function ADDRESS_LENGTH() {
return 100;
}

function KYC_MAX_LENGTH() {
return ADDRESS_INDEX() + ADDRESS_LENGTH();
}
Loading
Loading