Skip to content

Conversation

@Vishalkulkarni45
Copy link
Collaborator

@Vishalkulkarni45 Vishalkulkarni45 commented Oct 1, 2025

Note

Adds SelfRICA selective‑disclosure circuit with OFAC checks and age/date validation, supporting utilities (constants, input gen, RSA, trees, decoding) and comprehensive tests; minor Aadhaar test path fixes.

  • Circuits:
    • SelfRICA: circuits/disclose/vc_and_disclose_selfrica.circom with selective disclosure, identity commitment, and nullifier.
    • OFAC Subcircuits: utils/selfrica/disclose/ofac/ofac_name_dob_selfrica.circom, ofac_name_yob_selfrica.circom.
    • Date Validation: utils/selfrica/date/isValid.circom, isOlderThan.circom, dateIsLess.circom.
  • Common Utilities:
    • Constants/Types: common/src/utils/selfrica/constants.ts, types.ts.
    • Input/Decoding: generateInputs.ts (incl. RSA signing, disclose bits), decodePublicSignals.ts.
    • Crypto: rsa.ts (RSA-2048 PKCS#1 v1.5 sign/verify).
    • Trees: trees.ts additions for SelfRICA SMT building and leaf hashing (getNameDobLeafSelfrica, getNameYobLeafSelfrica).
  • Tests:
    • Disclosure: tests/disclose/vc_and_disclose_selfrica.test.ts (OFAC/non-OFAC, signatures, selectors).
    • Date: tests/utils/selfrica/date/* circom + TS tests.
    • OFAC: tests/utils/selfrica/ofac/* circom + TS tests with SMT fixtures.
    • Aadhaar: update paths/includes and JSON loading in vc_and_disclose_aadhaar.test.ts.

Written by Cursor Bugbot for commit 968a74c. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Selfrica ID-based identity verification with integrated OFAC sanctions screening
    • Implemented age verification and forbidden countries list validation
    • Enabled identity disclosure and registration workflows for Selfrica documents
  • Tests

    • Comprehensive test coverage for Selfrica verification flows and OFAC checks
    • Added date validation and signature verification tests
  • Chores

    • Updated build configuration and added required dependencies

transphorm and others added 3 commits September 30, 2025 15:40
chore: fix mobile deploy pipelines v2.6.8 rd2 (#1159)
chore: fix build version numbers for v2.6.8
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 1, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR introduces comprehensive Selfrica identity verification support alongside existing Aadhaar functionality. It adds new Circom circuits for Selfrica registration and disclosure with OFAC SMT verification, smart contract integration for verification flows and identity registry management, TypeScript utilities for input generation and ECDSA signing, and extensive test coverage across circuit and contract layers.

Changes

Cohort / File(s) Summary
Circom - Selfrica Disclosure & Registration Circuits
circuits/circuits/disclose/vc_and_disclose_selfrica.circom, circuits/circuits/register/register_selfrica_persona.circom, circuits/circuits/register/instances/register_selfrica.circom, circuits/circuits/register/instances/register_persona.circom
New Selfrica-specific circuit entry points for disclosure verification (with Merkle/OFAC proofs) and registration (with ECDSA signature verification); supports dual mode (Selfrica vs Persona).
Circom - Selfrica Utilities
circuits/circuits/utils/selfrica_persona/constants.circom, circuits/circuits/utils/selfrica_persona/persona_constants.circom, circuits/circuits/utils/selfrica_persona/babyEcdsa.circom, circuits/circuits/utils/selfrica_persona/date/*, circuits/circuits/utils/selfrica_persona/disclose/*, circuits/circuits/utils/selfrica_persona/verifySignature.circom
New field-layout constants, BabyJubJub ECDSA templates, date-validation circuits, OFAC name/DOB/YOB SMT verification, and signature-verification logic for Selfrica.
Circom - Selfrica Tests
circuits/tests/disclose/vc_and_disclose_selfrica.test.ts, circuits/tests/register/register_selfrica_persona.test.ts, circuits/tests/utils/selfrica/date/*, circuits/tests/utils/selfrica/ofac/*
End-to-end test suites validating Selfrica disclosure circuits, registration flows, date/age checks, and OFAC SMT verification.
Build Scripts
circuits/scripts/build/build_disclose_circuits.sh, circuits/scripts/build/build_register_selfrica.sh, circuits/scripts/build/common.sh
Added Selfrica circuit build entry; updated ptau download URL from S3 to GCS and circom include paths from relative to root-based.
TypeScript - Selfrica Utilities
common/src/utils/selfrica_persona/constants.ts, common/src/utils/selfrica_persona/types.ts, common/src/utils/selfrica_persona/generateInputs.ts, common/src/utils/selfrica_persona/persona_constants.ts, common/src/utils/selfrica_persona/ecdsa/*, common/src/utils/selfrica_persona/decodePublicSignals.ts
New Selfrica field metadata, SmileData/Persona types, mock input generation, ECDSA signing/verification, and public signal decoding.
Smart Contracts - Selfrica Verification & Registry
contracts/contracts/IdentityVerificationHubImplV2.sol, contracts/contracts/constants/AttestationId.sol, contracts/contracts/constants/CircuitConstantsV2.sol, contracts/contracts/registry/IdentityRegistrySelfricaImplV1.sol, contracts/contracts/libraries/CircuitAttributeHandlerV2.sol, contracts/contracts/libraries/CustomVerifier.sol, contracts/contracts/libraries/Formatter.sol, contracts/contracts/libraries/SelfStructs.sol
Added Selfrica-specific verifier routing, identity registry implementation with IMT and OFAC root management, output formatting, and attribute field extraction.
Smart Contract Interfaces
contracts/contracts/interfaces/IIdentityRegistrySelfricaV1.sol, contracts/contracts/interfaces/IVcAndDiscloseCircuitVerifier.sol, contracts/contracts/interfaces/IRegisterCircuitVerifier.sol
New Selfrica registry and circuit verifier interfaces for integration.
Smart Contract Tests
contracts/test/v2/discloseSelfrica.test.ts, contracts/test/v2/registerSelfrica.test.ts
Comprehensive test suites for Selfrica disclosure and registration verification flows, including error handling and cross-chain scenarios.
Test Infrastructure & Deployment
contracts/test/utils/deploymentV2.ts, contracts/test/utils/generateProof.ts, contracts/test/utils/types.ts, circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts
Extended deployment fixtures to include Selfrica registry and verifiers; added proof generation paths; updated test types; refactored Aadhaar test file path handling.
Configuration & Dependencies
circuits/package.json, common/package.json, contracts/package.json, contracts/hardhat.config.ts, common/index.ts, common/src/constants/constants.ts
Added test scripts, @zk-kit/baby-jubjub dependency, hardhat unlimited contract size config, and Selfrica utility exports.
Utilities - Trees & Types
common/src/utils/trees.ts, common/src/utils/types.ts, common/src/utils/circuits/registerInputs.ts
Extended SMT builder for Selfrica entries with name/DOB and name/YOB leaf derivation; added 'selfrica' to DocumentCategory union; added commented-out Selfrica disclose scaffolding.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CircomCircuit as Circom (VC & Disclose)
    participant SmartContract as Contract Hub
    participant Registry as Selfrica Registry
    participant Tree as Merkle Tree / SMT

    User->>CircomCircuit: SmileData, ECDSA sig, Merkle proof, scope, date
    CircomCircuit->>Tree: Verify Merkle root<br/>(secret + msg → leaf)
    Tree-->>CircomCircuit: Root valid
    CircomCircuit->>CircomCircuit: Verify OFAC SMT<br/>(name+DOB, name+YOB)
    CircomCircuit->>CircomCircuit: Validate age<br/>(IsOlderThan)
    CircomCircuit->>CircomCircuit: Pack revealed data<br/>(selector bits)
    CircomCircuit-->>User: Proof + public signals<br/>(nullifier, revealedData_packed, OFAC roots)
    
    User->>SmartContract: Submit proof
    SmartContract->>Registry: Check pubkey commitment
    Registry-->>SmartContract: Valid
    SmartContract->>SmartContract: Decode public signals
    SmartContract->>SmartContract: Verify OFAC/forbidden countries<br/>Verify age/expiry
    SmartContract->>Registry: Register commitment<br/>(nullifier, commitment)
    Registry->>Tree: Update IMT
    Tree-->>Registry: New root
    Registry-->>SmartContract: Registered
    SmartContract-->>User: VerificationCompleted event
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Rationale: This PR introduces substantial new cryptographic logic (ECDSA signing/verification, OFAC SMT verification, Baby Jubjub operations) across multiple layers (circuits, contracts, utilities). While the changes follow established patterns and remain cohesive in scope (Selfrica integration), the density of new logic—particularly around signature verification, field layout handling, and state management in the registry—requires careful scrutiny. The interaction between circuit outputs and contract verification paths, plus OFAC root management, demands thorough review.

Possibly related issues

  • The Powers of Tau download links are broken #1291: The changes to circuits/scripts/build/common.sh (ptau download URL migration from S3 to GCS, circom include path updates) directly resolve the infrastructure and build configuration issues described.

Possibly related PRs

  • Update example contracts to include EUID usage #656: Both PRs extend the verification stack (IdentityVerificationHubImplV2, CircuitConstantsV2, CustomVerifier, registry/verifier interfaces) to add new attestation types and circuit integrations (Selfrica/EUID).
  • fix: hub-v2 #1051: Both PRs modify IdentityVerificationHubImplV2 by expanding OFAC-related error handling and root validation checks.
  • Move proving inputs to the common package #937: Both PRs modify common proving-input utilities (registerInputs.ts, types.ts) to support stateless disclose generation and OFAC tree structures.

Suggested labels

codex, feature-selfrica, high-risk-crypto

Suggested reviewers

  • remicolin
  • aaronmgdr

🔐 Selfrica enters the stage, with ECDSA grace,
SMT-verified OFAC, and date checks in place.
Baby Jubjub signs with cryptographic might,
New identities bloom—selfrica shines bright!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.74% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: selfrica circuit and tests" is clear, concise, and directly related to a major component of the changeset. The title accurately identifies the addition of SelfRICA circuits and their comprehensive test suites, which form the core of this pull request. While the changeset also includes significant supporting infrastructure (common utilities, ECDSA crypto implementation, smart contracts, and build scripts), the title appropriately focuses on the primary deliverables without unnecessary detail, consistent with the principle that titles don't need to enumerate every aspect of the change.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

cursor[bot]

This comment was marked as outdated.

* Improve demo app safe area handling

* refactor: centralize mobile demo screen navigation

* update lock

* update podfile lock

* fix pipelines

* fix tests

* save wip polish

* polish app

* simplify and standardize screens

* small fixes

* fix tests

* Use SDK SelfClientProvider in demo (#1162)

* fix types

* Fix mobile SDK demo Jest mock

* force react-native-svg to 15.12.1

* fix tests

* add types script

* fix document list

* fix types and metro config

* add ignore files to speed up watchman and eslint

* save wip tweaks

* save mock doc screen wip

* use persistant document store

* save polish work in progress

* add polish to screens

* save wip secure storage

* allow cursor to examine react configs

* convert tests to vitest and fix

* fix tests

* prettier

* cr feedback

* fix tests and remove skipped
cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 23

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
circuits/package.json (1)

1-1: Fix formatting issues flagged by lint.

The pipeline indicates yarn lint failed. Run yarn format or prettier --write . to resolve formatting issues across the affected files.

circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts (3)

52-62: Mocha hook misuse: use before(), not this.beforeAll().

this.beforeAll is not a Mocha API. The suite setup may never run.

-  this.beforeAll(async function () {
+  before(async function () {
     this.timeout(0);
     circuit = await wasmTester(

119-123: Incorrect getOutput selection: requesting only [4] but reading [0]-[3].

You read revealData_packed[0..3] without fetching them, leading to undefined values.

-const revealedData = await circuit.getOutput(w, [`revealData_packed[4]`]);
+const revealedData = await circuit.getOutput(
+  w,
+  [`revealData_packed[0]`,`revealData_packed[1]`,`revealData_packed[2]`,`revealData_packed[3]`]
+);

164-168: Same issue: fetch all indices you consume.

You later read revealData_packed[0..4]; fetch them explicitly.

-const revealedData = await circuit.getOutput(w, [`revealData_packed[4]`, 'reveal_photoHash']);
+const revealedData = await circuit.getOutput(
+  w,
+  [
+    `revealData_packed[0]`,
+    `revealData_packed[1]`,
+    `revealData_packed[2]`,
+    `revealData_packed[3]`,
+    `revealData_packed[4]`,
+    'reveal_photoHash'
+  ]
+);
common/src/utils/trees.ts (2)

56-58: Remove or redact PII in logs (names, passport/country, DOB, arbitrary entry dumps)

Several console.log calls emit sensitive data (names, countries, raw entries). This violates logging guidelines and can leak PII in CI/telemetry.

Replace with opt‑in debug logs and redaction:

-      console.log('Processing', treetype, 'number', i, 'out of', field.length);
+      if (process.env.DEBUG_TREES === '1') {
+        console.debug('[SMT]', treetype, 'i=', i, '/', field.length);
+      }
...
-      console.log('This entry already exists in the tree, skipping...');
+      if (process.env.DEBUG_TREES === '1') console.debug('[SMT] duplicate leaf, skipping');
...
-  console.log('nationality and countryCode', nationality, countryCode);
+  if (process.env.DEBUG_TREES === '1') console.debug('[normalizeCountry]', 'alpha3 resolved');
...
-  console.log('arr', arr, 'arr.length', arr.length);
+  if (process.env.DEBUG_TREES === '1') console.debug('[name MRZ] len=', arr.length);
...
-    console.log('dob is null', i, entry);
+    console.warn('[Aadhaar] dob missing at i=', i);
...
-    console.log('year is null', i, entry);
+    console.warn('[Aadhaar] year missing at i=', i);
...
-    console.log('Error creating leaf value', i, country1, country2);
+    console.warn('[country leaf] invalid input at i=', i);

Also consider centralizing logging with a redact(name) helper (e.g., “J*** D***”). As per coding guidelines.

Also applies to: 70-73, 77-82, 336-337, 469-471, 620-623, 640-647, 518-523


693-708: Normalize error returns to BigInt(0) to prevent undefined propagation

getPassportNumberAndNationalityLeaf returns undefined on invalid lengths, while callers expect bigint. This can cause runtime/type errors.

Apply:

   if (passport.length !== 9) {
-    console.log('parsed passport length is not 9:', i, passport);
-    return;
+    console.warn('parsed passport length is not 9:', i);
+    return BigInt(0);
   }
   if (nationality.length !== 3) {
-    console.log('parsed nationality length is not 3:', i, nationality);
-    return;
+    console.warn('parsed nationality length is not 3:', i);
+    return BigInt(0);
   }
   try {
     const fullHash = poseidon12(passport.concat(nationality));
     return generateSmallKey(fullHash);
   } catch (err) {
-    console.log('err : passport', err, i, passport);
+    console.error('err : passport', err, i);
+    return BigInt(0);
   }
🧹 Nitpick comments (2)
common/src/utils/selfrica/decodePublicSignals.ts (1)

21-36: Add bounds validation for publicSignals array.

The function directly indexes publicSignals without verifying it has the expected length. If a malformed or truncated array is passed, this will throw an error. Consider adding a guard:

 export const decodePublicSignals = (publicSignals: string[]) => {
+    const expectedLength = SELFRICA_PUBLIC_SIGNALS_CURRENT_DATE + SELFRICA_PUBLIC_SIGNALS_CURRENT_DATE_LENGTH;
+    if (publicSignals.length < expectedLength) {
+        throw new Error(`Invalid publicSignals length: expected at least ${expectedLength}, got ${publicSignals.length}`);
+    }
+
     const revealedDataPacked = publicSignals.slice(SELFRICA_PUBLIC_SIGNALS_REVEALED_DATA_PACKED, ...);
common/src/utils/trees.ts (1)

710-744: Avoid noisy per-entry logs; guard with env flag to keep perf predictable

buildSelfricaSMT logs each iteration; this tanks throughput on large datasets and floods CI.

Wrap progress logs behind DEBUG_TREES and throttle to every N items (e.g., i % 1000 === 0).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 871890a and b7c5bc7.

📒 Files selected for processing (18)
  • circuits/circuits/disclose/vc_and_disclose_selfrica.circom (1 hunks)
  • circuits/circuits/utils/selfrica/constants.circom (1 hunks)
  • circuits/circuits/utils/selfrica/date/dateIsLess.circom (1 hunks)
  • circuits/circuits/utils/selfrica/date/isOlderThan.circom (1 hunks)
  • circuits/circuits/utils/selfrica/date/isValid.circom (1 hunks)
  • circuits/circuits/utils/selfrica/disclose/country_not_in_list.circom (1 hunks)
  • circuits/circuits/utils/selfrica/disclose/disclose.circom (1 hunks)
  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_dob_selfrica.circom (1 hunks)
  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_yob_selfrica.circom (1 hunks)
  • circuits/package.json (1 hunks)
  • circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts (2 hunks)
  • circuits/tests/disclose/vc_and_disclose_selfrica.test.ts (1 hunks)
  • common/src/utils/selfrica/constants.ts (1 hunks)
  • common/src/utils/selfrica/decodePublicSignals.ts (1 hunks)
  • common/src/utils/selfrica/generateInputs.ts (1 hunks)
  • common/src/utils/selfrica/rsa.ts (1 hunks)
  • common/src/utils/selfrica/types.ts (1 hunks)
  • common/src/utils/trees.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx,sol,nr}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,ts,tsx,jsx,sol,nr}: NEVER log sensitive data including PII (names, DOB, passport numbers, addresses), credentials, tokens, API keys, private keys, or session identifiers.
ALWAYS redact/mask sensitive fields in logs using consistent patterns (e.g., ***-***-1234 for passport numbers, J*** D*** for names).

Files:

  • common/src/utils/selfrica/decodePublicSignals.ts
  • common/src/utils/selfrica/types.ts
  • common/src/utils/selfrica/rsa.ts
  • circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts
  • common/src/utils/selfrica/generateInputs.ts
  • common/src/utils/trees.ts
  • common/src/utils/selfrica/constants.ts
  • circuits/tests/disclose/vc_and_disclose_selfrica.test.ts
common/src/**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

common/src/**/*.{ts,tsx,js,jsx}: Review shared utilities for:

  • Reusability and modular design
  • Type safety and error handling
  • Side-effect management
  • Documentation and naming clarity

Files:

  • common/src/utils/selfrica/decodePublicSignals.ts
  • common/src/utils/selfrica/types.ts
  • common/src/utils/selfrica/rsa.ts
  • common/src/utils/selfrica/generateInputs.ts
  • common/src/utils/trees.ts
  • common/src/utils/selfrica/constants.ts
circuits/**/*.circom

⚙️ CodeRabbit configuration file

circuits/**/*.circom: Review ZK circuit code for:

  • Circuit correctness and completeness
  • Constraint efficiency
  • Input validation
  • Security considerations for zero-knowledge proofs

Files:

  • circuits/circuits/utils/selfrica/disclose/country_not_in_list.circom
  • circuits/circuits/utils/selfrica/constants.circom
  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_yob_selfrica.circom
  • circuits/circuits/utils/selfrica/date/dateIsLess.circom
  • circuits/circuits/utils/selfrica/date/isOlderThan.circom
  • circuits/circuits/disclose/vc_and_disclose_selfrica.circom
  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_dob_selfrica.circom
  • circuits/circuits/utils/selfrica/date/isValid.circom
  • circuits/circuits/utils/selfrica/disclose/disclose.circom
**/*.{test,spec}.{ts,js,tsx,jsx}

⚙️ CodeRabbit configuration file

**/*.{test,spec}.{ts,js,tsx,jsx}: Review test files for:

  • Test coverage completeness
  • Test case quality and edge cases
  • Mock usage appropriateness
  • Test readability and maintainability

Files:

  • circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts
  • circuits/tests/disclose/vc_and_disclose_selfrica.test.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Integrate forbidden-country non-inclusion proof into the disclosure proof circuit
📚 Learning: 2025-08-24T18:53:11.221Z
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Integrate forbidden-country non-inclusion proof into the disclosure proof circuit

Applied to files:

  • circuits/circuits/utils/selfrica/disclose/country_not_in_list.circom
  • circuits/circuits/disclose/vc_and_disclose_selfrica.circom
  • circuits/circuits/utils/selfrica/disclose/disclose.circom
📚 Learning: 2025-09-22T11:10:22.019Z
Learnt from: CR
PR: selfxyz/self#0
File: .cursorrules:0-0
Timestamp: 2025-09-22T11:10:22.019Z
Learning: Applies to noir/crates/dg1/src/ofac/*.nr : OFAC Compliance System implements three-tier verification (name-based, name + DOB, passport number), and Merkle tree-based sanctions list verification.

Applied to files:

  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_yob_selfrica.circom
  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_dob_selfrica.circom
📚 Learning: 2025-08-24T18:53:11.221Z
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Ensure the circuit performs day-level comparison with correct date arithmetic

Applied to files:

  • circuits/circuits/utils/selfrica/date/dateIsLess.circom
📚 Learning: 2025-08-24T18:53:11.221Z
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Implement the age verification circuit at circuits/circuits/disclose/disclose.circom with private DOB input, public minimum-age input, and boolean output

Applied to files:

  • circuits/circuits/utils/selfrica/date/isOlderThan.circom
  • circuits/circuits/disclose/vc_and_disclose_selfrica.circom
  • circuits/circuits/utils/selfrica/disclose/disclose.circom
🧬 Code graph analysis (5)
common/src/utils/selfrica/decodePublicSignals.ts (1)
common/src/utils/selfrica/constants.ts (16)
  • SELFRICA_PUBLIC_SIGNALS_REVEALED_DATA_PACKED (42-42)
  • SELFRICA_PUBLIC_SIGNALS_REVEALED_DATA_PACKED_LENGTH (43-43)
  • SELFRICA_MAX_LENGTH (37-37)
  • SELFRICA_PUBLIC_SIGNALS_FORBIDDEN_COUNTRIES_PACKED (45-45)
  • SELFRICA_PUBLIC_SIGNALS_FORBIDDEN_COUNTRIES_PACKED_LENGTH (46-46)
  • SELFRICA_PUBLIC_SIGNALS_IDENTITY_COMMITMENT (48-48)
  • SELFRICA_PUBLIC_SIGNALS_NULLIFIER (49-49)
  • SELFRICA_PUBLIC_SIGNALS_PUBKEY_X (50-50)
  • SELFRICA_PUBLIC_SIGNALS_PUBKEY_Y (51-51)
  • SELFRICA_PUBLIC_SIGNALS_SCOPE (52-52)
  • SELFRICA_PUBLIC_SIGNALS_OFAC_NAME_DOB_SMT_ROOT (53-53)
  • SELFRICA_PUBLIC_SIGNALS_OFAC_NAME_YOB_SMT_ROOT (54-54)
  • SELFRICA_PUBLIC_SIGNALS_ATTESTATION_ID (55-55)
  • SELFRICA_PUBLIC_SIGNALS_CURRENT_DATE (57-57)
  • SELFRICA_PUBLIC_SIGNALS_CURRENT_DATE_LENGTH (58-58)
  • SELFRICA_PUBLIC_SIGNALS_USER_IDENTIFIER (56-56)
common/src/utils/selfrica/types.ts (1)
app/scripts/analyze-tree-shaking.cjs (1)
  • constants (164-180)
common/src/utils/selfrica/generateInputs.ts (6)
common/src/utils/selfrica/constants.ts (5)
  • SELFRICA_MAX_LENGTH (37-37)
  • SELFRICA_FULL_NAME_INDEX (16-16)
  • SELFRICA_FULL_NAME_LENGTH (17-17)
  • SELFRICA_DOB_INDEX (19-19)
  • SELFRICA_DOB_LENGTH (20-20)
common/src/utils/selfrica/types.ts (3)
  • SmileData (3-20)
  • serializeSmileData (22-99)
  • SelfricaCircuitInput (101-120)
common/src/utils/trees.ts (3)
  • getNameDobLeafSelfrica (767-772)
  • getNameYobLeafSelfrica (809-815)
  • generateSMTProof (176-225)
common/src/utils/circuits/generateInputs.ts (1)
  • formatInput (58-78)
common/src/utils/selfrica/rsa.ts (3)
  • generateRSAKeyPair (50-50)
  • signRSA (50-50)
  • verifyRSA (50-50)
common/src/utils/bytes.ts (1)
  • splitToWords (140-153)
common/src/utils/trees.ts (1)
common/src/utils/hash.ts (1)
  • packBytesAndPoseidon (185-188)
circuits/tests/disclose/vc_and_disclose_selfrica.test.ts (3)
common/src/utils/selfrica/generateInputs.ts (3)
  • generateCircuitInput (98-158)
  • NON_OFAC_DUMMY_INPUT (55-72)
  • OFAC_DUMMY_INPUT (36-53)
common/src/utils/selfrica/constants.ts (1)
  • SELFRICA_MAX_LENGTH (37-37)
common/src/utils/selfrica/types.ts (1)
  • serializeSmileData (22-99)
🪛 GitHub Actions: Circuits CI
circuits/tests/disclose/vc_and_disclose_selfrica.test.ts

[warning] 1-1: Code style issues found in file. Run 'Prettier --write' to fix.

🪛 GitHub Actions: Common CI
common/src/utils/selfrica/decodePublicSignals.ts

[warning] 1-1: Code style issues found in this file. Run 'Prettier --write' to fix.

common/src/utils/selfrica/types.ts

[warning] 1-1: Code style issues found in this file. Run 'Prettier --write' to fix.

common/src/utils/selfrica/rsa.ts

[warning] 1-1: Code style issues found in this file. Run 'Prettier --write' to fix.

common/src/utils/selfrica/generateInputs.ts

[warning] 1-1: Code style issues found in this file. Run 'Prettier --write' to fix.

common/src/utils/trees.ts

[warning] 1-1: Code style issues found in this file. Run 'Prettier --write' to fix.

common/src/utils/selfrica/constants.ts

[warning] 1-1: Code style issues found in this file. Run 'Prettier --write' to fix.

🪛 GitHub Actions: Workspace CI
common/src/utils/selfrica/decodePublicSignals.ts

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/circuits/utils/selfrica/disclose/country_not_in_list.circom

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/circuits/utils/selfrica/constants.circom

[error] 1-1: Command 'yarn lint' failed with exit code 1.

common/src/utils/selfrica/types.ts

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_yob_selfrica.circom

[error] 1-1: Command 'yarn lint' failed with exit code 1.

common/src/utils/selfrica/rsa.ts

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/circuits/utils/selfrica/date/dateIsLess.circom

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/package.json

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/circuits/utils/selfrica/date/isOlderThan.circom

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/circuits/disclose/vc_and_disclose_selfrica.circom

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_dob_selfrica.circom

[error] 1-1: Command 'yarn lint' failed with exit code 1.

common/src/utils/selfrica/generateInputs.ts

[error] 1-1: Command 'yarn lint' failed with exit code 1.

common/src/utils/trees.ts

[error] 1-1: Command 'yarn lint' failed with exit code 1.

common/src/utils/selfrica/constants.ts

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/circuits/utils/selfrica/date/isValid.circom

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/circuits/utils/selfrica/disclose/disclose.circom

[error] 1-1: Command 'yarn lint' failed with exit code 1.

circuits/tests/disclose/vc_and_disclose_selfrica.test.ts

[error] 1-1: Command 'yarn lint' failed with exit code 1.

⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: build-deps
🔇 Additional comments (8)
circuits/package.json (1)

24-24: LGTM! Script follows existing pattern.

The new test script is consistent with other disclose test scripts and correctly targets the selfrica test file.

circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_yob_selfrica.circom (2)

26-31: Verify YoB extraction assumes correct DOB format.

The code extracts the first 4 bytes of DOB as the year of birth, assuming DOB is in YYYYMMDD format. Ensure upstream validation (e.g., IsValidFullYear or data provider guarantees) enforces this format—otherwise, malformed dates could produce incorrect YoB hashes and bypass OFAC checks.


33-33: Hash input order is consistent in circuit and SMT builder.

circuits/circuits/utils/selfrica/date/dateIsLess.circom (2)

16-65: Component-wise comparison does not handle calendar arithmetic.

The circuit performs component-wise comparison (year, month, day) without accounting for varying month lengths or leap years. For example, 2024-02-30 < 2024-03-01 would evaluate as true even though Feb 30 is invalid. As per the learnings, ensure "day-level comparison with correct date arithmetic" by validating dates with IsValidFullYear before comparing, or document this limitation clearly.

Based on learnings


16-65: Add an assertion in isOlderThan.circom to enforce the age check.

isValid.circom correctly constrains DateIsLessFullYear.out via 1 === is_valid.out;, but isOlderThan.circom merely exports out <== is_older_than.out without asserting it. After the signal output out <== is_older_than.out; line, add:

1 === is_older_than.out;

to guarantee the circuit fails when the comparison is false.

⛔ Skipped due to learnings
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Ensure the circuit performs day-level comparison with correct date arithmetic
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Implement the age verification circuit at circuits/circuits/disclose/disclose.circom with private DOB input, public minimum-age input, and boolean output
circuits/circuits/utils/selfrica/constants.circom (1)

3-101: Constants alignment verified. All Circom index and length constants match their TypeScript counterparts.

common/src/utils/selfrica/generateInputs.ts (1)

132-139: SELFRICA_MAX_LENGTH matches expected 266 No action required.

common/src/utils/selfrica/constants.ts (1)

1-37: Add schema documentation and verify field lengths

  • SELFRICA_PHONE_NUMBER_LENGTH = 12 may not support full E.164 phone numbers (up to 15 digits).
  • SELFRICA_FULL_NAME_LENGTH = 40 could truncate longer international names—consider increasing to 60–80 characters.
  • No inline documentation describes each field’s encoding, length rationale, padding, or total layout.

Could you confirm your expected phone-number and full-name formats and update lengths or docs accordingly?

Comment on lines +43 to +44
signal input user_identifier;
signal input current_date[8];
Copy link
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.

Comment on lines 107 to 109
// component id_num_hasher = Sha256Bytes(SMILE_ID_PADDED());
id_num_hasher.paddedIn <== id_num;
id_num_hasher.paddedInLength <== SMILE_ID_PADDED();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Instantiate the ID number hasher.

id_num_hasher is referenced but never instantiated (the only declaration is commented out), so the circuit will not compile and the ID-number SHA-256 constraints never materialize.

Apply this diff so the hasher exists before wiring it:

-    // component id_num_hasher = Sha256Bytes(SMILE_ID_PADDED());
+    component id_num_hasher = Sha256Bytes(SMILE_ID_PADDED());
📝 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
// component id_num_hasher = Sha256Bytes(SMILE_ID_PADDED());
id_num_hasher.paddedIn <== id_num;
id_num_hasher.paddedInLength <== SMILE_ID_PADDED();
component id_num_hasher = Sha256Bytes(SMILE_ID_PADDED());
id_num_hasher.paddedIn <== id_num;
id_num_hasher.paddedInLength <== SMILE_ID_PADDED();
🤖 Prompt for AI Agents
In circuits/circuits/disclose/vc_and_disclose_selfrica.circom around lines 107
to 109, the id_num_hasher is wired but not instantiated (the component
declaration is commented out), so add back a component instantiation like:
declare id_num_hasher as Sha256Bytes(SMILE_ID_PADDED()) before assigning its
inputs, ensuring the component name matches the wiring (id_num_hasher) and
SMILE_ID_PADDED() is used for the constructor parameter so the SHA-256
constraints are generated.

Comment on lines 16 to 65
template DateIsLessFullYear() {
signal input day_1;
signal input day_2;

signal input month_1;
signal input month_2;

signal input year_1;
signal input year_2;

signal output out;

// ----
component year_less = LessThan(8);
year_less.in[0] <== year_1;
year_less.in[1] <== year_2;
signal is_year_less <== year_less.out;

component month_less = LessThan(8);
month_less.in[0] <== month_1;
month_less.in[1] <== month_2;
signal is_month_less <== month_less.out;

component day_less = LessThan(8);
day_less.in[0] <== day_1;
day_less.in[1] <== day_2;
signal is_day_less <== day_less.out;

// ----
component year_equal = IsEqual();
year_equal.in[0] <== year_1;
year_equal.in[1] <== year_2;
signal is_year_equal <== year_equal.out;

component month_equal = IsEqual();
month_equal.in[0] <== month_1;
month_equal.in[1] <== month_2;
signal is_month_equal <== month_equal.out;

// ----
signal is_year_equal_and_month_less <== (is_year_equal * is_month_less);
signal is_year_equal_and_month_equal <== (is_year_equal * is_month_equal);
signal is_year_equal_and_month_equal_and_day_less <== (is_year_equal_and_month_equal * is_day_less);

component greater_than = GreaterThan(3);
greater_than.in[0] <== is_year_less + is_year_equal_and_month_less + is_year_equal_and_month_equal_and_day_less;
greater_than.in[1] <== 0;

out <== greater_than.out;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

No input range validation for day, month, year.

The circuit does not constrain inputs to valid ranges (day 1-31, month 1-12, year > 0). Invalid dates like 2024-02-30 or 2024-13-01 could produce incorrect comparison results. Ensure the caller (e.g., IsValidFullYear) validates date components before invoking this template, or add range checks here.

Consider adding range constraints or verify that IsValidFullYear enforces them:

// Example range constraints (add to template)
component day1_valid = LessThan(8);
day1_valid.in[0] <== day_1;
day1_valid.in[1] <== 32;  // day must be < 32
day1_valid.out === 1;

component month1_valid = LessThan(8);
month1_valid.in[0] <== month_1;
month1_valid.in[1] <== 13;  // month must be < 13
month1_valid.out === 1;

// Similar for day_2, month_2
🤖 Prompt for AI Agents
In circuits/circuits/utils/selfrica/date/dateIsLess.circom around lines 16-65,
the template accepts day/month/year inputs without validating ranges; add
explicit range checks for both date inputs (day in 1..31, month in 1..12, year >
0) or call/require the existing IsValidFullYear verifier before using this
template. Implement these checks by adding comparator components (e.g.,
LessThan/GreaterThan or custom range check) for day_1, month_1, year_1 and
day_2, month_2, year_2 and assert their outputs (e.g., out === 1) so invalid
values fail the circuit; alternatively, document and enforce that callers must
run IsValidFullYear on both dates before invoking DateIsLessFullYear.

Comment on lines 21 to 23
for (var i = 0; i < 8; i++) {
validity_date_num[i] <== validity_date_ascii[i] - ASCII_rotation;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Constrain ASCII digits before date comparison

validity_date_ascii[i] - 48 is unconstrained, so a prover can feed values outside '0'..'9' (e.g., 48 + 500) and fabricate arbitrary years/months to bypass the expiry gate. Please enforce both ASCII range checks (48 ≤ char ≤ 57) and digit bounds (0 ≤ digit ≤ 9) before wiring into DateIsLessFullYear. Example patch:

     validity_date_num[i] <== validity_date_ascii[i] - ASCII_rotation;
+
+    component digit_lt_10 = LessThan(4);
+    digit_lt_10.in[0] <== validity_date_num[i];
+    digit_lt_10.in[1] <== TEN;
+    digit_lt_10.out === 1;
+
+    component ascii_ge_48 = GreaterEqThan(8);
+    ascii_ge_48.in[0] <== validity_date_ascii[i];
+    ascii_ge_48.in[1] <== ASCII_rotation;
+    ascii_ge_48.out === 1;
+
+    signal ASCII_upper_bound <== ASCII_rotation + TEN;
+    component ascii_lt_58 = LessThan(8);
+    ascii_lt_58.in[0] <== validity_date_ascii[i];
+    ascii_lt_58.in[1] <== ASCII_upper_bound;
+    ascii_lt_58.out === 1;

As per coding guidelines

📝 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
for (var i = 0; i < 8; i++) {
validity_date_num[i] <== validity_date_ascii[i] - ASCII_rotation;
}
for (var i = 0; i < 8; i++) {
// Convert ASCII codepoint to digit
validity_date_num[i] <== validity_date_ascii[i] - ASCII_rotation;
// Enforce digit is in [0..9]
component digit_lt_10 = LessThan(4);
digit_lt_10.in[0] <== validity_date_num[i];
digit_lt_10.in[1] <== TEN;
digit_lt_10.out === 1;
// Enforce ASCII code is ≥ '0' (48)
component ascii_ge_48 = GreaterEqThan(8);
ascii_ge_48.in[0] <== validity_date_ascii[i];
ascii_ge_48.in[1] <== ASCII_rotation;
ascii_ge_48.out === 1;
// Enforce ASCII code is < '0' + 10 (i.e. ≤ '9' (57))
signal ASCII_upper_bound <== ASCII_rotation + TEN;
component ascii_lt_58 = LessThan(8);
ascii_lt_58.in[0] <== validity_date_ascii[i];
ascii_lt_58.in[1] <== ASCII_upper_bound;
ascii_lt_58.out === 1;
}
🤖 Prompt for AI Agents
In circuits/circuits/utils/selfrica/date/isValid.circom around lines 21-23, the
subtraction validity_date_ascii[i] - 48 is currently unconstrained allowing
non-digit ASCII values; add explicit constraints that each
validity_date_ascii[i] is between 48 and 57 inclusive, then compute the digit
(validity_date_num[i]) and constrain that digit between 0 and 9 before passing
digits into DateIsLessFullYear. Concretely: for each i enforce
validity_date_ascii[i] >= 48 and <= 57, wire validity_date_num[i] <==
validity_date_ascii[i] - 48, and enforce validity_date_num[i] >= 0 and <= 9 (or
equivalent range constraints) prior to the date-comparison wiring.

Comment on lines 19 to 20
signal input selector_smile_data[selfrica_max_length];

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Enforce selector_smile_data as boolean bits to keep PackBytes sound and prevent malformed reveals

selector_smile_data isn’t constrained to {0,1} but is multiplied into bytes and later packed. Non‑boolean values can break byte bounds and weaken gating semantics. Constrain each selector bit.

Apply this diff near the selectors:

 signal input selector_smile_data[selfrica_max_length];
 
+// Ensure all selectors are boolean
+for (var i = 0; i < selfrica_max_length; i++) {
+    selector_smile_data[i] * (selector_smile_data[i] - 1) === 0;
+}

Also applies to: 74-76

🤖 Prompt for AI Agents
In circuits/circuits/utils/selfrica/disclose/disclose.circom around lines 19-20
(and similarly update lines 74-76), the signal input
selector_smile_data[selfrica_max_length] is not constrained to boolean values;
add constraints that force each selector bit to be 0 or 1 (e.g., for each i
enforce selector_smile_data[i] * (selector_smile_data[i] - 1) == 0) so packing
and byte multiplications remain sound and prevent malformed reveals. Ensure you
apply the same boolean constraint pattern to any other selector arrays mentioned
at lines 74-76.

Comment on lines 118 to 126
// Sign nullifier with RSA
const idNumber = Buffer.from(msgArray.slice(30, 30 + 20));

const id_num_rsaSig = signRSA(idNumber, privateKey);
console.assert(verifyRSA(idNumber, id_num_rsaSig, publicKey) == true, "Invalid nullifier RSA signature");

// Convert nullifier RSA signature to limbs
const nullifierSigBigInt = BigInt('0x' + id_num_rsaSig.toString('hex'));

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid hardcoded byte offsets when signing the nullifier; derive from constants

Slicing idNumber as msgArray.slice(30, 30 + 20) is brittle. If field ordering or sizes change, you’ll sign the wrong segment and break nullifier integrity.

Refactor to compute offsets from Selfrica constants:

-import { SELFRICA_DOB_INDEX, SELFRICA_DOB_LENGTH, SELFRICA_FULL_NAME_INDEX, SELFRICA_FULL_NAME_LENGTH, SELFRICA_MAX_LENGTH } from "./constants.js";
+import {
+  SELFRICA_DOB_INDEX, SELFRICA_DOB_LENGTH,
+  SELFRICA_FULL_NAME_INDEX, SELFRICA_FULL_NAME_LENGTH,
+  SELFRICA_ID_NUMBER_INDEX, SELFRICA_ID_NUMBER_LENGTH,
+  SELFRICA_MAX_LENGTH
+} from "./constants.js";
...
-    const idNumber = Buffer.from(msgArray.slice(30, 30 + 20));
+    const idNumber = Buffer.from(
+      msgArray.slice(SELFRICA_ID_NUMBER_INDEX, SELFRICA_ID_NUMBER_INDEX + SELFRICA_ID_NUMBER_LENGTH)
+    );

As per coding guidelines.

Also applies to: 185-192

🤖 Prompt for AI Agents
In common/src/utils/selfrica/generateInputs.ts around lines 118 to 126 (and
similarly lines 185 to 192), the code uses hardcoded slice indices
msgArray.slice(30, 30 + 20) to extract the idNumber; instead compute the start
and length from the Selfrica field-size and field-order constants (e.g.,
BASE_OFFSET + offsets.nullifier or equivalent), or use a helper that returns the
nullifier byte-range, then replace the hardcoded numbers with those computed
values so the slice always targets the correct bytes even if field sizes or
ordering change.

Comment on lines 22 to 37
export const serializeSmileData = (smileData: SmileData) => {
//ensure max length of each field
let serializedData = '';
smileData.country = smileData.country.toUpperCase().padEnd(constants.SELFRICA_COUNTRY_LENGTH, '\0');
smileData.idType = smileData.idType.toUpperCase().padEnd(constants.SELFRICA_ID_TYPE_LENGTH, '\0');
smileData.idNumber = smileData.idNumber.padEnd(constants.SELFRICA_ID_NUMBER_LENGTH, '\0');
smileData.issuanceDate = smileData.issuanceDate.padEnd(constants.SELFRICA_ISSUANCE_DATE_LENGTH, '\0');
smileData.expiryDate = smileData.expiryDate.padEnd(constants.SELFRICA_EXPIRY_DATE_LENGTH, '\0');
smileData.fullName = smileData.fullName.padEnd(constants.SELFRICA_FULL_NAME_LENGTH, '\0');
smileData.dob = smileData.dob.padEnd(constants.SELFRICA_DOB_LENGTH, '\0');
smileData.photoHash = smileData.photoHash.padEnd(constants.SELFRICA_PHOTO_HASH_LENGTH, '\0');
smileData.phoneNumber = smileData.phoneNumber.padEnd(constants.SELFRICA_PHONE_NUMBER_LENGTH, '\0');
smileData.document = smileData.document.padEnd(constants.SELFRICA_DOCUMENT_LENGTH, '\0');
smileData.gender = smileData.gender.padEnd(constants.SELFRICA_GENDER_LENGTH, '\0');
smileData.address = smileData.address.padEnd(constants.SELFRICA_ADDRESS_LENGTH, '\0');

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid mutating input in serializeSmileData to prevent cross-test/data pollution.

serializeSmileData pads/uppercases fields in-place on the passed object. This can corrupt shared constants (e.g., NON_OFAC_DUMMY_INPUT) used elsewhere.

 export const serializeSmileData = (smileData: SmileData) => {
-    //ensure max length of each field
-    let serializedData = '';
-    smileData.country = smileData.country.toUpperCase().padEnd(constants.SELFRICA_COUNTRY_LENGTH, '\0');
-    smileData.idType = smileData.idType.toUpperCase().padEnd(constants.SELFRICA_ID_TYPE_LENGTH, '\0');
+    // ensure max length of each field (no mutation)
+    let serializedData = '';
+    const country = smileData.country.toUpperCase().padEnd(constants.SELFRICA_COUNTRY_LENGTH, '\0');
+    const idType = smileData.idType.toUpperCase().padEnd(constants.SELFRICA_ID_TYPE_LENGTH, '\0');
     /* repeat for others as locals: idNumber, issuanceDate, ... address */
-    if (smileData.country.length !== constants.SELFRICA_COUNTRY_LENGTH) {
+    if (country.length !== constants.SELFRICA_COUNTRY_LENGTH) {
         throw new Error(`Country must be ${constants.SELFRICA_COUNTRY_LENGTH} characters`);
     }
-    serializedData += smileData.country;
+    serializedData += country;
-    if (smileData.idType.length !== constants.SELFRICA_ID_TYPE_LENGTH) {
+    if (idType.length !== constants.SELFRICA_ID_TYPE_LENGTH) {
         throw new Error(`ID type must be ${constants.SELFRICA_ID_TYPE_LENGTH} characters`);
     }
-    serializedData += smileData.idType;
+    serializedData += idType;
     // ...apply same pattern for all remaining fields

Also applies to: 41-97

🤖 Prompt for AI Agents
common/src/utils/selfrica/types.ts around lines 22-37 (also applies to 41-97):
the function currently mutates the input SmileData by uppercasing/padding fields
in-place; instead create a non-mutating local copy or read each field into local
variables and apply toUpperCase()/padEnd() there, build the serialized string
from those locals and return it without modifying the original object; ensure
all fields are normalized using the constants lengths and that the function does
not change smileData properties.

Comment on lines 122 to 129
export type SelfricaPublicInput = {
pubKeyX: string,
pubKeyY: string,
scope: string,
ofac_name_dob_smt_root: string[],
ofac_name_yob_smt_root: string[],
attestation_id: string[],
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Public input type/shape mismatch (pubKeyX/pubKeyY vs pubKey). Align the API.

SelfricaPublicInput declares pubKeyX/pubKeyY, but getPublicInput returns pubKey (RSA modulus limbs). This inconsistency will break consumers and is confusing.

Option A (RSA: keep limbs array):

-export type SelfricaPublicInput = {
-    pubKeyX: string,
-    pubKeyY: string,
-    scope: string,
-    ofac_name_dob_smt_root: string[],
-    ofac_name_yob_smt_root: string[],
-    attestation_id: string[],
-}
+export type SelfricaPublicInput = {
+    pubKey: string[],
+    msg_sig: string[],
+    id_num_sig: string[],
+    scope: string,
+    ofac_name_dob_smt_root: string[],
+    ofac_name_yob_smt_root: string[],
+    attestation_id: string[],
+}
 
-export const getPublicInput = (input: SelfricaCircuitInput) => {
+export const getPublicInput = (input: SelfricaCircuitInput): SelfricaPublicInput => {
     return {
         pubKey: input.pubKey,
         msg_sig: input.msg_sig,
         id_num_sig: input.id_num_sig,
         ofac_name_dob_smt_root: input.ofac_name_dob_smt_root,
         ofac_name_yob_smt_root: input.ofac_name_yob_smt_root,
         attestation_id: ['4'],
     }
 }

If elliptic keys are actually intended, split and rename the source accordingly. Based on learnings.

Also applies to: 131-140

🤖 Prompt for AI Agents
In common/src/utils/selfrica/types.ts around lines 122-129 (and also update
lines 131-140), the declared SelfricaPublicInput shape uses pubKeyX/pubKeyY but
the runtime/getPublicInput returns an RSA-style pubKey (array of limbs); change
the type to match the runtime by replacing pubKeyX and pubKeyY with a single
pubKey: string[] field (and update the related type entries at lines 131-140
accordingly), then update any references/tests to use input.pubKey array instead
of pubKeyX/pubKeyY.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

♻️ Duplicate comments (3)
common/src/utils/selfrica/types.ts (1)

122-139: Realign SelfricaPublicInput with the actual return payload

SelfricaPublicInput advertises pubKeyX, pubKeyY, and scope, but getPublicInput returns pubKey (array), omits scope, and adds signatures that are not part of the declared type. Downstream callers relying on the declared shape will either see runtime failures or silently drop the data they need. Please make the type and function agree (and include scope) so public-input derivation is reliable.

-export type SelfricaPublicInput = {
-    pubKeyX: string,
-    pubKeyY: string,
-    scope: string,
-    ofac_name_dob_smt_root: string[],
-    ofac_name_yob_smt_root: string[],
-    attestation_id: string[],
-}
-
-export const getPublicInput = (input: SelfricaCircuitInput) => {
-    return {
-        pubKey: input.pubKey,
-        msg_sig: input.msg_sig,
-        id_num_sig: input.id_num_sig,
-        ofac_name_dob_smt_root: input.ofac_name_dob_smt_root,
-        ofac_name_yob_smt_root: input.ofac_name_yob_smt_root,
-        attestation_id: ['4'],
-    }
-}
+export type SelfricaPublicInput = {
+    pubKey: string[],
+    msg_sig: string[],
+    id_num_sig: string[],
+    scope: string,
+    ofac_name_dob_smt_root: string[],
+    ofac_name_yob_smt_root: string[],
+    attestation_id: string[],
+};
+
+export const getPublicInput = (input: SelfricaCircuitInput): SelfricaPublicInput => ({
+    pubKey: input.pubKey,
+    msg_sig: input.msg_sig,
+    id_num_sig: input.id_num_sig,
+    scope: input.scope,
+    ofac_name_dob_smt_root: input.ofac_name_dob_smt_root,
+    ofac_name_yob_smt_root: input.ofac_name_yob_smt_root,
+    attestation_id: ['4'],
+});
common/src/utils/selfrica/generateInputs.ts (2)

137-155: Fix hardcoded majority age and current date.

current_date is still set to "20240101" and majority_age_ASCII encodes ASCII for “001”. Both values contradict the dummy payload (20250101 / age 20), so the circuit will fail date/age checks. Please update them to the correct values (or compute them dynamically).


200-220: Replace stale current_date in real-data path.

The real-data helper still emits "20240101" for current_date, so proofs generated on/after 2025-10-01 will fail the circuit’s freshness check. Align this with the actual current date (or thread it from the caller).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 871890a and ed841d7.

📒 Files selected for processing (17)
  • circuits/circuits/disclose/vc_and_disclose_selfrica.circom (1 hunks)
  • circuits/circuits/utils/selfrica/constants.circom (1 hunks)
  • circuits/circuits/utils/selfrica/date/dateIsLess.circom (1 hunks)
  • circuits/circuits/utils/selfrica/date/isOlderThan.circom (1 hunks)
  • circuits/circuits/utils/selfrica/date/isValid.circom (1 hunks)
  • circuits/circuits/utils/selfrica/disclose/disclose.circom (1 hunks)
  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_dob_selfrica.circom (1 hunks)
  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_yob_selfrica.circom (1 hunks)
  • circuits/package.json (1 hunks)
  • circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts (2 hunks)
  • circuits/tests/disclose/vc_and_disclose_selfrica.test.ts (1 hunks)
  • common/src/utils/selfrica/constants.ts (1 hunks)
  • common/src/utils/selfrica/decodePublicSignals.ts (1 hunks)
  • common/src/utils/selfrica/generateInputs.ts (1 hunks)
  • common/src/utils/selfrica/rsa.ts (1 hunks)
  • common/src/utils/selfrica/types.ts (1 hunks)
  • common/src/utils/trees.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx,sol,nr}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,ts,tsx,jsx,sol,nr}: NEVER log sensitive data including PII (names, DOB, passport numbers, addresses), credentials, tokens, API keys, private keys, or session identifiers.
ALWAYS redact/mask sensitive fields in logs using consistent patterns (e.g., ***-***-1234 for passport numbers, J*** D*** for names).

Files:

  • common/src/utils/selfrica/decodePublicSignals.ts
  • common/src/utils/selfrica/rsa.ts
  • common/src/utils/selfrica/generateInputs.ts
  • common/src/utils/selfrica/constants.ts
  • circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts
  • common/src/utils/trees.ts
  • common/src/utils/selfrica/types.ts
  • circuits/tests/disclose/vc_and_disclose_selfrica.test.ts
common/src/**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

common/src/**/*.{ts,tsx,js,jsx}: Review shared utilities for:

  • Reusability and modular design
  • Type safety and error handling
  • Side-effect management
  • Documentation and naming clarity

Files:

  • common/src/utils/selfrica/decodePublicSignals.ts
  • common/src/utils/selfrica/rsa.ts
  • common/src/utils/selfrica/generateInputs.ts
  • common/src/utils/selfrica/constants.ts
  • common/src/utils/trees.ts
  • common/src/utils/selfrica/types.ts
circuits/**/*.circom

⚙️ CodeRabbit configuration file

circuits/**/*.circom: Review ZK circuit code for:

  • Circuit correctness and completeness
  • Constraint efficiency
  • Input validation
  • Security considerations for zero-knowledge proofs

Files:

  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_yob_selfrica.circom
  • circuits/circuits/utils/selfrica/date/isValid.circom
  • circuits/circuits/utils/selfrica/date/dateIsLess.circom
  • circuits/circuits/utils/selfrica/date/isOlderThan.circom
  • circuits/circuits/utils/selfrica/disclose/disclose.circom
  • circuits/circuits/utils/selfrica/constants.circom
  • circuits/circuits/disclose/vc_and_disclose_selfrica.circom
  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_dob_selfrica.circom
**/*.{test,spec}.{ts,js,tsx,jsx}

⚙️ CodeRabbit configuration file

**/*.{test,spec}.{ts,js,tsx,jsx}: Review test files for:

  • Test coverage completeness
  • Test case quality and edge cases
  • Mock usage appropriateness
  • Test readability and maintainability

Files:

  • circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts
  • circuits/tests/disclose/vc_and_disclose_selfrica.test.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Integrate forbidden-country non-inclusion proof into the disclosure proof circuit
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Implement the age verification circuit at circuits/circuits/disclose/disclose.circom with private DOB input, public minimum-age input, and boolean output
📚 Learning: 2025-09-22T11:10:22.019Z
Learnt from: CR
PR: selfxyz/self#0
File: .cursorrules:0-0
Timestamp: 2025-09-22T11:10:22.019Z
Learning: Applies to noir/crates/dg1/src/ofac/*.nr : OFAC Compliance System implements three-tier verification (name-based, name + DOB, passport number), and Merkle tree-based sanctions list verification.

Applied to files:

  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_yob_selfrica.circom
  • circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_dob_selfrica.circom
📚 Learning: 2025-08-24T18:53:11.221Z
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Ensure the circuit performs day-level comparison with correct date arithmetic

Applied to files:

  • circuits/circuits/utils/selfrica/date/dateIsLess.circom
📚 Learning: 2025-08-24T18:53:11.221Z
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Integrate forbidden-country non-inclusion proof into the disclosure proof circuit

Applied to files:

  • circuits/circuits/utils/selfrica/disclose/disclose.circom
  • circuits/circuits/disclose/vc_and_disclose_selfrica.circom
📚 Learning: 2025-08-24T18:53:11.221Z
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/compliance-verification.mdc:0-0
Timestamp: 2025-08-24T18:53:11.221Z
Learning: Applies to circuits/circuits/disclose/disclose.circom : Implement the age verification circuit at circuits/circuits/disclose/disclose.circom with private DOB input, public minimum-age input, and boolean output

Applied to files:

  • circuits/circuits/utils/selfrica/disclose/disclose.circom
  • circuits/circuits/disclose/vc_and_disclose_selfrica.circom
🧬 Code graph analysis (4)
common/src/utils/selfrica/decodePublicSignals.ts (1)
common/src/utils/selfrica/constants.ts (16)
  • SELFRICA_PUBLIC_SIGNALS_REVEALED_DATA_PACKED (42-42)
  • SELFRICA_PUBLIC_SIGNALS_REVEALED_DATA_PACKED_LENGTH (43-43)
  • SELFRICA_MAX_LENGTH (37-37)
  • SELFRICA_PUBLIC_SIGNALS_FORBIDDEN_COUNTRIES_PACKED (45-45)
  • SELFRICA_PUBLIC_SIGNALS_FORBIDDEN_COUNTRIES_PACKED_LENGTH (46-46)
  • SELFRICA_PUBLIC_SIGNALS_IDENTITY_COMMITMENT (48-48)
  • SELFRICA_PUBLIC_SIGNALS_NULLIFIER (49-49)
  • SELFRICA_PUBLIC_SIGNALS_PUBKEY_X (50-50)
  • SELFRICA_PUBLIC_SIGNALS_PUBKEY_Y (51-51)
  • SELFRICA_PUBLIC_SIGNALS_SCOPE (52-52)
  • SELFRICA_PUBLIC_SIGNALS_OFAC_NAME_DOB_SMT_ROOT (53-53)
  • SELFRICA_PUBLIC_SIGNALS_OFAC_NAME_YOB_SMT_ROOT (54-54)
  • SELFRICA_PUBLIC_SIGNALS_ATTESTATION_ID (55-55)
  • SELFRICA_PUBLIC_SIGNALS_CURRENT_DATE (57-57)
  • SELFRICA_PUBLIC_SIGNALS_CURRENT_DATE_LENGTH (58-58)
  • SELFRICA_PUBLIC_SIGNALS_USER_IDENTIFIER (56-56)
common/src/utils/selfrica/generateInputs.ts (5)
common/src/utils/selfrica/constants.ts (5)
  • SELFRICA_MAX_LENGTH (37-37)
  • SELFRICA_FULL_NAME_INDEX (16-16)
  • SELFRICA_FULL_NAME_LENGTH (17-17)
  • SELFRICA_DOB_INDEX (19-19)
  • SELFRICA_DOB_LENGTH (20-20)
common/src/utils/selfrica/types.ts (3)
  • SmileData (3-20)
  • serializeSmileData (22-99)
  • SelfricaCircuitInput (101-120)
common/src/utils/trees.ts (3)
  • getNameDobLeafSelfrica (767-772)
  • getNameYobLeafSelfrica (809-815)
  • generateSMTProof (176-225)
common/src/utils/circuits/generateInputs.ts (1)
  • formatInput (58-78)
common/src/utils/bytes.ts (1)
  • splitToWords (140-153)
common/src/utils/trees.ts (1)
common/src/utils/hash.ts (1)
  • packBytesAndPoseidon (185-188)
circuits/tests/disclose/vc_and_disclose_selfrica.test.ts (3)
common/src/utils/selfrica/generateInputs.ts (3)
  • generateCircuitInput (98-158)
  • NON_OFAC_DUMMY_INPUT (55-72)
  • OFAC_DUMMY_INPUT (36-53)
common/src/utils/selfrica/constants.ts (1)
  • SELFRICA_MAX_LENGTH (37-37)
common/src/utils/selfrica/types.ts (1)
  • serializeSmileData (22-99)
🪛 GitHub Actions: Circuits CI
circuits/tests/disclose/vc_and_disclose_selfrica.test.ts

[warning] Code style issues found in this file. Run 'Prettier --write' to fix.

🪛 GitHub Actions: Common CI
common/src/utils/selfrica/decodePublicSignals.ts

[warning] 1-1: Code style issues found. Run Prettier with --write to fix.

common/src/utils/selfrica/rsa.ts

[warning] 1-1: Code style issues found. Run Prettier with --write to fix.

common/src/utils/selfrica/generateInputs.ts

[warning] 1-1: Code style issues found. Run Prettier with --write to fix.

common/src/utils/selfrica/constants.ts

[warning] 1-1: Code style issues found. Run Prettier with --write to fix.

common/src/utils/trees.ts

[warning] 1-1: Code style issues found. Run Prettier with --write to fix.

common/src/utils/selfrica/types.ts

[warning] 1-1: Code style issues found. Run Prettier with --write to fix.

⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test
  • GitHub Check: build-android
  • GitHub Check: build-ios
🔇 Additional comments (8)
circuits/package.json (1)

24-24: LGTM! Test script follows established patterns.

The new test script is consistent with existing disclose test scripts and properly configured.

circuits/circuits/utils/selfrica/date/dateIsLess.circom (1)

56-64: Date comparison logic is correct.

The three-tier comparison correctly implements date ordering: first by year, then by month (if years equal), then by day (if both year and month equal).

circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts (2)

15-22: LGTM! Path resolution modernized correctly.

The addition of __dirname and file loading updates properly handle ES module path resolution and make the test more maintainable.


59-59: Include path update is appropriate.

Adding 'node_modules' to the include array ensures circomlib circuits can be properly resolved during compilation.

circuits/circuits/utils/selfrica/date/isValid.circom (2)

29-47: Year/month/day construction is correct.

The digit-to-number conversion using powers of 10 correctly builds the full year, month, and day values from individual digits.


48-48: Validity constraint is properly enforced.

The constraint 1 === is_valid.out correctly ensures the passport is valid (current date is before validity date).

circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_dob_selfrica.circom (2)

19-34: Hash composition approach is sound for OFAC verification.

The two-tier hashing (separate name and DOB hashes, then combined) provides a clean structure that matches the OFAC name+DOB verification tier. Using Poseidon throughout is efficient for ZK circuits.


34-36: SMTVerify mode is correct for OFAC non-inclusion checks — all OFAC circuits consistently pass 0 (mode 0 = non-inclusion, mode 1 = inclusion) to assert the subject is not on the sanctions list.

Comment on lines 42 to 158
signal input selector_ofac;
signal input user_identifier;
signal input current_date[8];
signal input majority_age_ASCII[3];
signal input selector_older_than;

signal output attestation_id <== 4;

// Convert the two decimal inputs back to bit array
signal disclose_sel[selfrica_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];
}


//skiped paddedInLength to be within `ceil(log2(8 * maxByteLength))` bits bez we are using hardcoded values
component msg_hasher = Sha256Bytes(SMILE_DATA_PADDED());
msg_hasher.paddedIn <== SmileID_data_padded;
msg_hasher.paddedInLength <== SMILE_DATA_PADDED();

//verify Hash(smiledata) signatur
component msg_sig_verify = SignatureVerifier(1, n, k);
msg_sig_verify.hash <== msg_hasher.out;
msg_sig_verify.pubKey <== pubKey;
msg_sig_verify.signature <== msg_sig;


//Calculate IDNUMBER hash
signal id_num[SMILE_ID_PADDED()];
var idNumberIdx = ID_NUMBER_INDEX();

// Fill the first 20 bytes with actual ID number data
for (var i = 0; i < ID_NUMBER_LENGTH(); i++) {
id_num[i] <== SmileID_data_padded[idNumberIdx + i];
}

// Add SHA-256 padding for 20-byte message
// Add padding bit '1' (0x80)
id_num[ID_NUMBER_LENGTH()] <== 128; // 0x80 in decimal

// Fill with zeros up to position 56 (64 - 8 for length field)
for (var i = ID_NUMBER_LENGTH() + 1; i < SMILE_ID_PADDED() - 8; i++) {
id_num[i] <== 0;
}

// Add 64-bit length field (20 bytes * 8 = 160 bits)
// Length in bits as 64-bit big-endian integer
for (var i = SMILE_ID_PADDED() - 8; i < SMILE_ID_PADDED() - 1; i++) {
id_num[i] <== 0; // High bytes are 0 for small lengths
}
id_num[SMILE_ID_PADDED() - 1] <== 160; // 20 * 8 = 160 bits

component id_num_hasher = Sha256Bytes(SMILE_ID_PADDED());
id_num_hasher.paddedIn <== id_num;
id_num_hasher.paddedInLength <== SMILE_ID_PADDED();

//verify Hash(IdNumber) signature
component id_num_sig_verify = SignatureVerifier(1, n, k);
id_num_sig_verify.hash <== id_num_hasher.out;
id_num_sig_verify.pubKey <== pubKey;
id_num_sig_verify.signature <== id_num_sig;


// Identity Commitment = Hash( IdNumCommit sig )
component idCommCal = CustomHasher(k);
idCommCal.in <== msg_sig;

//Nullifier = HASH( nullifier sig , scope )
component nullifierCal = CustomHasher(k + 1);
for (var i = 0; i < k; i++) {
nullifierCal.in[i] <== id_num_sig[i];
}
nullifierCal.in[k] <== scope;

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

for (var i = 0; i < selfrica_length; i++) {
disclose_circuit.smile_data[i] <== SmileID_data_padded[i];
}
disclose_circuit.selector_smile_data <== 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;
disclose_circuit.selector_older_than <== selector_older_than;

var revealed_data_packed_chunk_length = computeIntChunkLength(selfrica_length + 2 + 3);
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 identity_commitment <== idCommCal.out;
signal output nullifier <== nullifierCal.out;

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bind user_identifier to the derived identity commitment

user_identifier is declared as a public input but never constrained, so a prover can emit any identifier while the circuit still verifies, defeating downstream identity binding. Tie it to idCommCal.out (or whichever commitment you expect verifiers to trust).

     component idCommCal = CustomHasher(k);
     idCommCal.in <== msg_sig;
+    user_identifier === idCommCal.out;
📝 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
signal input selector_ofac;
signal input user_identifier;
signal input current_date[8];
signal input majority_age_ASCII[3];
signal input selector_older_than;
signal output attestation_id <== 4;
// Convert the two decimal inputs back to bit array
signal disclose_sel[selfrica_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];
}
//skiped paddedInLength to be within `ceil(log2(8 * maxByteLength))` bits bez we are using hardcoded values
component msg_hasher = Sha256Bytes(SMILE_DATA_PADDED());
msg_hasher.paddedIn <== SmileID_data_padded;
msg_hasher.paddedInLength <== SMILE_DATA_PADDED();
//verify Hash(smiledata) signatur
component msg_sig_verify = SignatureVerifier(1, n, k);
msg_sig_verify.hash <== msg_hasher.out;
msg_sig_verify.pubKey <== pubKey;
msg_sig_verify.signature <== msg_sig;
//Calculate IDNUMBER hash
signal id_num[SMILE_ID_PADDED()];
var idNumberIdx = ID_NUMBER_INDEX();
// Fill the first 20 bytes with actual ID number data
for (var i = 0; i < ID_NUMBER_LENGTH(); i++) {
id_num[i] <== SmileID_data_padded[idNumberIdx + i];
}
// Add SHA-256 padding for 20-byte message
// Add padding bit '1' (0x80)
id_num[ID_NUMBER_LENGTH()] <== 128; // 0x80 in decimal
// Fill with zeros up to position 56 (64 - 8 for length field)
for (var i = ID_NUMBER_LENGTH() + 1; i < SMILE_ID_PADDED() - 8; i++) {
id_num[i] <== 0;
}
// Add 64-bit length field (20 bytes * 8 = 160 bits)
// Length in bits as 64-bit big-endian integer
for (var i = SMILE_ID_PADDED() - 8; i < SMILE_ID_PADDED() - 1; i++) {
id_num[i] <== 0; // High bytes are 0 for small lengths
}
id_num[SMILE_ID_PADDED() - 1] <== 160; // 20 * 8 = 160 bits
component id_num_hasher = Sha256Bytes(SMILE_ID_PADDED());
id_num_hasher.paddedIn <== id_num;
id_num_hasher.paddedInLength <== SMILE_ID_PADDED();
//verify Hash(IdNumber) signature
component id_num_sig_verify = SignatureVerifier(1, n, k);
id_num_sig_verify.hash <== id_num_hasher.out;
id_num_sig_verify.pubKey <== pubKey;
id_num_sig_verify.signature <== id_num_sig;
// Identity Commitment = Hash( IdNumCommit sig )
component idCommCal = CustomHasher(k);
idCommCal.in <== msg_sig;
//Nullifier = HASH( nullifier sig , scope )
component nullifierCal = CustomHasher(k + 1);
for (var i = 0; i < k; i++) {
nullifierCal.in[i] <== id_num_sig[i];
}
nullifierCal.in[k] <== scope;
component disclose_circuit = DISCLOSE_SELFRICA(MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH, namedobTreeLevels, nameyobTreeLevels);
for (var i = 0; i < selfrica_length; i++) {
disclose_circuit.smile_data[i] <== SmileID_data_padded[i];
}
disclose_circuit.selector_smile_data <== 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;
disclose_circuit.selector_older_than <== selector_older_than;
var revealed_data_packed_chunk_length = computeIntChunkLength(selfrica_length + 2 + 3);
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 identity_commitment <== idCommCal.out;
signal output nullifier <== nullifierCal.out;
// Identity Commitment = Hash( IdNumCommit sig )
component idCommCal = CustomHasher(k);
idCommCal.in <== msg_sig;
user_identifier === idCommCal.out;
🤖 Prompt for AI Agents
In circuits/circuits/disclose/vc_and_disclose_selfrica.circom around lines 42 to
158, user_identifier is declared as a public input but never constrained; add a
binding after idCommCal is computed to force the public input to equal the
derived identity commitment (i.e., constrain user_identifier to idCommCal.out).
Ensure the types/sizes match (convert or index if idCommCal.out is an array) so
the circuit enforces user_identifier <== idCommCal.out (or element-wise
equality) immediately after idCommCal is available.

/// @param year_1 is the year of the first date
/// @param year_2 is the year of the second date
/// @output out is the result of the comparison
/// @dev output is not constrained — verifier has to handle this check
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clarify constraint documentation.

The comment states "output is not constrained — verifier has to handle this check" but the output is actually constrained through the GreaterThan component at line 64. This documentation is misleading.

Update the comment to accurately reflect that the output is constrained:

-/// @dev output is not constrained — verifier has to handle this check
+/// @dev output is constrained via GreaterThan component
📝 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
/// @dev output is not constrained — verifier has to handle this check
/// @dev output is constrained via GreaterThan component
🤖 Prompt for AI Agents
In circuits/circuits/utils/selfrica/date/dateIsLess.circom around line 14, the
current comment "output is not constrained — verifier has to handle this check"
is misleading because the output is constrained by the GreaterThan component at
line 64; update the comment to accurately state that the output is constrained
(mentioning the GreaterThan check at line 64) and remove the claim that the
verifier must handle it, so the documentation reflects the actual constraint
enforcement.

Comment on lines +16 to +26
template DateIsLessFullYear() {
signal input day_1;
signal input day_2;

signal input month_1;
signal input month_2;

signal input year_1;
signal input year_2;

signal output out;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add input validation for date components.

The template accepts year/month/day inputs without validating they represent valid dates. Invalid values (e.g., month=13, day=32) will produce incorrect comparison results.

Based on learnings: Ensure the circuit performs day-level comparison with correct date arithmetic.

Consider adding range constraints:

// Add after signal declarations
component month_1_valid = LessThan(8);
month_1_valid.in[0] <== month_1;
month_1_valid.in[1] <== 13;
month_1_valid.out === 1;

component day_1_valid = LessThan(8);
day_1_valid.in[0] <== day_1;
day_1_valid.in[1] <== 32;
day_1_valid.out === 1;

// Repeat for month_2 and day_2

Comment on lines +23 to +40
for (var i=0; i<8; i++) {
birthdateNum[i] <== birthDateASCII[i] - ASCII_rotation;
}

signal TEN <== 10;
signal CENTURY <== 100;
signal MILLENIA <== 1000;

signal currDateMillenia <== currDate[0] * MILLENIA;
signal currDateCentury <== currDate[1] * CENTURY;
signal currDateDecade <== currDate[2] * TEN;
signal currDateYear <== currDateMillenia + currDateCentury + currDateDecade + currDate[3];

signal birthDateMillenia <== birthdateNum[0] * MILLENIA;
signal birthDateCentury <== birthdateNum[1] * CENTURY;
signal birthDateDecade <== birthdateNum[2] * TEN;
signal birthDateYear <== birthDateMillenia + birthDateCentury + birthDateDecade + birthdateNum[3];

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clamp birthDateASCII digits to ASCII range

We never constrain birthDateASCII[i] to '0'..'9'. A prover can drop non-digit field elements here, shape birthdateNum however they want, and bypass the age gate while still satisfying downstream constraints. Please mirror the guard you already apply to majorityASCII so the birth date derivation can’t be spoofed.

@@
-    for (var i=0; i<8; i++) {
+    component birth_ascii_lo[8];
+    component birth_ascii_hi[8];
+    signal birth_ascii_guard[8];
+    for (var i=0; i<8; i++) {
+        birth_ascii_lo[i] = LessThan(8);
+        birth_ascii_hi[i] = LessThan(8);
+        birth_ascii_lo[i].in[0] <== 47;
+        birth_ascii_lo[i].in[1] <== birthDateASCII[i];
+        birth_ascii_hi[i].in[0] <== birthDateASCII[i];
+        birth_ascii_hi[i].in[1] <== 58;
         birthdateNum[i] <== birthDateASCII[i] - ASCII_rotation;
+        birth_ascii_guard[i] <== birth_ascii_lo[i].out * birth_ascii_hi[i].out;
     }
+    for (var i=1; i<8; i++) {
+        birth_ascii_guard[i] <== birth_ascii_guard[i-1] * birth_ascii_guard[i];
+    }
+    birth_ascii_guard[7] === 1;
📝 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
for (var i=0; i<8; i++) {
birthdateNum[i] <== birthDateASCII[i] - ASCII_rotation;
}
signal TEN <== 10;
signal CENTURY <== 100;
signal MILLENIA <== 1000;
signal currDateMillenia <== currDate[0] * MILLENIA;
signal currDateCentury <== currDate[1] * CENTURY;
signal currDateDecade <== currDate[2] * TEN;
signal currDateYear <== currDateMillenia + currDateCentury + currDateDecade + currDate[3];
signal birthDateMillenia <== birthdateNum[0] * MILLENIA;
signal birthDateCentury <== birthdateNum[1] * CENTURY;
signal birthDateDecade <== birthdateNum[2] * TEN;
signal birthDateYear <== birthDateMillenia + birthDateCentury + birthDateDecade + birthdateNum[3];
// --- enforce ASCII '0'..'9' on each birthDateASCII digit ---
component birth_ascii_lo[8];
component birth_ascii_hi[8];
signal birth_ascii_guard[8];
for (var i = 0; i < 8; i++) {
birth_ascii_lo[i] = LessThan(8);
birth_ascii_hi[i] = LessThan(8);
// birthDateASCII[i] > 47 <==> ascii >= '0'
birth_ascii_lo[i].in[0] <== 47;
birth_ascii_lo[i].in[1] <== birthDateASCII[i];
// birthDateASCII[i] < 58 <==> ascii <= '9'
birth_ascii_hi[i].in[0] <== birthDateASCII[i];
birth_ascii_hi[i].in[1] <== 58;
// derive numeric digit
birthdateNum[i] <== birthDateASCII[i] - ASCII_rotation;
// both guards must pass
birth_ascii_guard[i] <== birth_ascii_lo[i].out * birth_ascii_hi[i].out;
}
// accumulate all guards and require them all to be true
for (var i = 1; i < 8; i++) {
birth_ascii_guard[i] <== birth_ascii_guard[i - 1] * birth_ascii_guard[i];
}
birth_ascii_guard[7] === 1;
// --- existing date‐to‐number logic remains unchanged ---
signal TEN <== 10;
signal CENTURY <== 100;
signal MILLENIA <== 1000;
signal currDateMillenia <== currDate[0] * MILLENIA;
signal currDateCentury <== currDate[1] * CENTURY;
signal currDateDecade <== currDate[2] * TEN;
signal currDateYear <== currDateMillenia + currDateCentury + currDateDecade + currDate[3];
signal birthDateMillenia <== birthdateNum[0] * MILLENIA;
signal birthDateCentury <== birthdateNum[1] * CENTURY;
signal birthDateDecade <== birthdateNum[2] * TEN;
signal birthDateYear <== birthDateMillenia + birthDateCentury + birthDateDecade + birthdateNum[3];
🤖 Prompt for AI Agents
In circuits/circuits/utils/selfrica/date/isOlderThan.circom around lines 23 to
40, the birthDateASCII digits are never constrained to the ASCII '0'..'9' range
so a prover can supply non-digit values; mirror the same guard used for
majorityASCII by adding constraints for each birthDateASCII[i] (i=0..7) that
enforce they are within the ASCII digit range (>= '0' and <= '9') using the same
comparator/boolean logic or range-check component already used for
majorityASCII, so birthdateNum derivation cannot be spoofed.

Comment on lines +19 to +23
signal ASCII_rotation <== 48;

for (var i = 0; i < 8; i++) {
validity_date_num[i] <== validity_date_ascii[i] - ASCII_rotation;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add validation for ASCII digit inputs.

The template assumes validity_date_ascii contains valid ASCII digits (48-57) but doesn't validate this. Invalid inputs could cause incorrect date calculations or constraint failures.

Add range constraints to ensure inputs are valid ASCII digits:

// Add after line 18
for (var i = 0; i < 8; i++) {
    component ascii_valid = LessThan(8);
    ascii_valid.in[0] <== validity_date_ascii[i];
    ascii_valid.in[1] <== 58; // ASCII '9' + 1
    ascii_valid.out === 1;
    
    component ascii_min = GreaterThan(8);
    ascii_min.in[0] <== validity_date_ascii[i];
    ascii_min.in[1] <== 47; // ASCII '0' - 1
    ascii_min.out === 1;
}
🤖 Prompt for AI Agents
In circuits/circuits/utils/selfrica/date/isValid.circom around lines 19 to 23,
the code subtracts ASCII_rotation from validity_date_ascii but doesn't validate
that each input is an ASCII digit (48-57); add per-byte range checks before the
subtraction by instantiating two comparator components for each i: a LessThan
comparing validity_date_ascii[i] < 58 with out===1 and a GreaterThan comparing
validity_date_ascii[i] > 47 with out===1, so together they enforce 48 <=
validity_date_ascii[i] <= 57 for i in 0..7, then proceed with the existing
ASCII_rotation subtraction.

Comment on lines 19 to 83
signal input selector_smile_data[selfrica_max_length];

signal input forbidden_countries_list[MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH * country_length];

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

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

signal input selector_ofac;
signal input current_date[8];
signal input majority_age_ASCII[3];
signal input selector_older_than;

selector_ofac * (selector_ofac - 1) === 0;
selector_older_than * (selector_older_than - 1) === 0;

signal validity_ASCII[8];
for (var i = 0; i < EXPIRATION_DATE_LENGTH(); i++) {
validity_ASCII[i] <== smile_data[EXPIRATION_DATE_INDEX() + i];
}
IsValidFullYear()(current_date, validity_ASCII);

signal birth_date_ASCII[8];
for (var i = 0; i < DOB_LENGTH(); i++) {
birth_date_ASCII[i] <== smile_data[DOB_INDEX() + i];
}

component is_older_than = IsOlderThan();
is_older_than.majorityASCII <== majority_age_ASCII;
is_older_than.currDate <== current_date;
is_older_than.birthDateASCII <== birth_date_ASCII;
signal is_older_than_result <== is_older_than.out;

component ofac_name_dob_circuit = OFAC_NAME_DOB_SELFRICA(name_dob_tree_levels);
ofac_name_dob_circuit.smile_data <== smile_data;
ofac_name_dob_circuit.smt_leaf_key <== ofac_name_dob_smt_leaf_key;
ofac_name_dob_circuit.smt_root <== ofac_name_dob_smt_root;
ofac_name_dob_circuit.smt_siblings <== ofac_name_dob_smt_siblings;

component ofac_name_yob_circuit = OFAC_NAME_YOB_SELFRICA(name_yob_tree_levels);
ofac_name_yob_circuit.smile_data <== smile_data;
ofac_name_yob_circuit.smt_leaf_key <== ofac_name_yob_smt_leaf_key;
ofac_name_yob_circuit.smt_root <== ofac_name_yob_smt_root;
ofac_name_yob_circuit.smt_siblings <== ofac_name_yob_smt_siblings;

signal older_than_verified[3];
older_than_verified[0] <== is_older_than_result * majority_age_ASCII[0];
older_than_verified[1] <== is_older_than_result * majority_age_ASCII[1];
older_than_verified[2] <== is_older_than_result * majority_age_ASCII[2];

signal revealed_data[selfrica_max_length + 2 + 3];
for (var i = 0; i < selfrica_max_length; i++) {
revealed_data[i] <== smile_data[i] * selector_smile_data[i];
}

revealed_data[selfrica_max_length] <== ofac_name_dob_circuit.ofacCheckResult * selector_ofac;
revealed_data[selfrica_max_length + 1] <== ofac_name_yob_circuit.ofacCheckResult * selector_ofac;
revealed_data[selfrica_max_length + 2] <== older_than_verified[0] * selector_older_than;
revealed_data[selfrica_max_length + 3] <== older_than_verified[1] * selector_older_than;
revealed_data[selfrica_max_length + 4] <== older_than_verified[2] * selector_older_than;

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Constrain selector_smile_data to boolean values

selector_smile_data drives which bytes are revealed, but nothing here forces each selector entry to be 0/1. A prover can pick arbitrary field elements (e.g., 2, p−1) and still satisfy the circuit while disclosing tampered values, breaking relying-party expectations. Please add a booleanity constraint per selector entry before using it as a mask.

     signal revealed_data[selfrica_max_length + 2 + 3];
-    for (var i = 0; i < selfrica_max_length; i++) {
+    for (var i = 0; i < selfrica_max_length; i++) {
+        selector_smile_data[i] * (selector_smile_data[i] - 1) === 0;
         revealed_data[i] <== smile_data[i] * selector_smile_data[i];
     }
📝 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
signal input selector_smile_data[selfrica_max_length];
signal input forbidden_countries_list[MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH * country_length];
signal input ofac_name_dob_smt_leaf_key;
signal input ofac_name_dob_smt_root;
signal input ofac_name_dob_smt_siblings[name_dob_tree_levels];
signal input ofac_name_yob_smt_leaf_key;
signal input ofac_name_yob_smt_root;
signal input ofac_name_yob_smt_siblings[name_yob_tree_levels];
signal input selector_ofac;
signal input current_date[8];
signal input majority_age_ASCII[3];
signal input selector_older_than;
selector_ofac * (selector_ofac - 1) === 0;
selector_older_than * (selector_older_than - 1) === 0;
signal validity_ASCII[8];
for (var i = 0; i < EXPIRATION_DATE_LENGTH(); i++) {
validity_ASCII[i] <== smile_data[EXPIRATION_DATE_INDEX() + i];
}
IsValidFullYear()(current_date, validity_ASCII);
signal birth_date_ASCII[8];
for (var i = 0; i < DOB_LENGTH(); i++) {
birth_date_ASCII[i] <== smile_data[DOB_INDEX() + i];
}
component is_older_than = IsOlderThan();
is_older_than.majorityASCII <== majority_age_ASCII;
is_older_than.currDate <== current_date;
is_older_than.birthDateASCII <== birth_date_ASCII;
signal is_older_than_result <== is_older_than.out;
component ofac_name_dob_circuit = OFAC_NAME_DOB_SELFRICA(name_dob_tree_levels);
ofac_name_dob_circuit.smile_data <== smile_data;
ofac_name_dob_circuit.smt_leaf_key <== ofac_name_dob_smt_leaf_key;
ofac_name_dob_circuit.smt_root <== ofac_name_dob_smt_root;
ofac_name_dob_circuit.smt_siblings <== ofac_name_dob_smt_siblings;
component ofac_name_yob_circuit = OFAC_NAME_YOB_SELFRICA(name_yob_tree_levels);
ofac_name_yob_circuit.smile_data <== smile_data;
ofac_name_yob_circuit.smt_leaf_key <== ofac_name_yob_smt_leaf_key;
ofac_name_yob_circuit.smt_root <== ofac_name_yob_smt_root;
ofac_name_yob_circuit.smt_siblings <== ofac_name_yob_smt_siblings;
signal older_than_verified[3];
older_than_verified[0] <== is_older_than_result * majority_age_ASCII[0];
older_than_verified[1] <== is_older_than_result * majority_age_ASCII[1];
older_than_verified[2] <== is_older_than_result * majority_age_ASCII[2];
signal revealed_data[selfrica_max_length + 2 + 3];
for (var i = 0; i < selfrica_max_length; i++) {
revealed_data[i] <== smile_data[i] * selector_smile_data[i];
}
revealed_data[selfrica_max_length] <== ofac_name_dob_circuit.ofacCheckResult * selector_ofac;
revealed_data[selfrica_max_length + 1] <== ofac_name_yob_circuit.ofacCheckResult * selector_ofac;
revealed_data[selfrica_max_length + 2] <== older_than_verified[0] * selector_older_than;
revealed_data[selfrica_max_length + 3] <== older_than_verified[1] * selector_older_than;
revealed_data[selfrica_max_length + 4] <== older_than_verified[2] * selector_older_than;
signal revealed_data[selfrica_max_length + 2 + 3];
for (var i = 0; i < selfrica_max_length; i++) {
// Ensure each selector is boolean (0 or 1)
selector_smile_data[i] * (selector_smile_data[i] - 1) === 0;
revealed_data[i] <== smile_data[i] * selector_smile_data[i];
}
🤖 Prompt for AI Agents
In circuits/circuits/utils/selfrica/disclose/disclose.circom around lines 19 to
83, each element of selector_smile_data is used as a mask but is not constrained
to be boolean; add a per-entry booleanity constraint to prevent non-{0,1} values
by asserting selector_smile_data[i] * (selector_smile_data[i] - 1) === 0 for
every i before the revealed_data loop (i.e., add a for loop over i from 0 to
selfrica_max_length applying that constraint).

Comment on lines +9 to +22
var selfrica_max_length = SELFRICA_MAX_LENGTH();
signal input smile_data[selfrica_max_length];

signal input smt_leaf_key;
signal input smt_root;
signal input smt_siblings[n_levels];

var name_length = FULL_NAME_LENGTH();
var name_index = FULL_NAME_INDEX();
//name hash
component name_hash = PackBytesAndPoseidon(name_length);
for (var i = name_index; i < name_index + name_length; i++) {
name_hash.in[i - name_index] <== smile_data[i];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add bounds validation for name extraction.

The template extracts name data from smile_data using constants but doesn't validate that the array access is within bounds. If constants are misconfigured, this could cause undefined behavior.

Add an assertion or constraint to validate array bounds:

// Add after line 17
signal name_end <== name_index + name_length;
// Ensure name_end <= selfrica_max_length
component bounds_check = LessThan(16);
bounds_check.in[0] <== name_end;
bounds_check.in[1] <== selfrica_max_length + 1;
bounds_check.out === 1;
🤖 Prompt for AI Agents
In circuits/circuits/utils/selfrica/disclose/ofac/ofac_name_dob_selfrica.circom
around lines 9 to 22, the code slices smile_data using name_index and
name_length without validating bounds; add a bounds check immediately after the
name_end calculation (name_end = name_index + name_length) and enforce name_end
<= selfrica_max_length by instantiating a LessThan comparator (compare name_end
and selfrica_max_length + 1) and asserting its output equals 1 so the circuit
fails when the extracted range would exceed smile_data.

Comment on lines +774 to +785
const processNameSelfrica = (firstName: string, lastName: string, i: number): bigint => {
firstName = firstName.replace(/'/g, '');
firstName = firstName.replace(/\./g, '');
firstName = firstName.replace(/[- ]/g, '<');
lastName = lastName.replace(/'/g, '');
lastName = lastName.replace(/[- ]/g, '<');
lastName = lastName.replace(/\./g, '');

//TODO: check if smile id does first name and last name || last name and first name
const nameArr = (lastName + ' ' + firstName).padEnd(40, '\0').split('').map(char => char.charCodeAt(0));
return BigInt(packBytesAndPoseidon(nameArr));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard Selfrica name inputs before string ops

processNameSelfrica calls .replace on firstName/lastName without verifying they’re non-empty strings. When the dataset has missing name fields (common in sanction feeds), this will throw and abort tree construction, blocking circuit input generation. Please short-circuit to BigInt(0) (or otherwise skip the record) when either name is absent.

-const processNameSelfrica = (firstName: string, lastName: string, i: number): bigint => {
+const processNameSelfrica = (firstName: string, lastName: string, i: number): bigint => {
+  if (typeof firstName !== 'string' || typeof lastName !== 'string') {
+    console.log('Name data missing for Selfrica entry', i, { firstName, lastName });
+    return BigInt(0);
+  }
   firstName = firstName.replace(/'/g, '');

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In common/src/utils/trees.ts around lines 774 to 785, processNameSelfrica
currently calls string methods on firstName/lastName without validation which
will throw if names are null/undefined; update the function to first guard that
both firstName and lastName are non-empty strings (e.g. typeof === 'string' and
trimmed length > 0) and immediately return BigInt(0) when either is
missing/invalid; otherwise proceed with the existing replace/pad/split/pack
logic. Ensure the early-return uses BigInt(0) to keep downstream tree
construction safe.

Vishalkulkarni45 and others added 11 commits October 1, 2025 15:31
* fix: ofac check

* fix: isOfacValid is false by default
* move fcm token from proving store to setting store for better separation of concerns.

* use analytics on adapter

* Revert "use analytics on adapter"

This reverts commit 4854d6f.

* prettier

* please be good

* ik heb oopsie wooopsie

* remove
* introduce usePrepareDocumentProof in mobile SDK

* revert haptic feedback + lint
* add new id picker flow

* refactor: update document management screen actions

- Renamed `handleScanDocument` to `handleAddDocument` for clarity.
- Updated navigation from 'DocumentOnboarding' to 'CountryPicker'.
- Removed unused `handleAddAadhaar` function and its associated button.

* address pr feedback

* address lint issues

* fix test

* fix typings and screen

* fix e2e button test

---------

Co-authored-by: Justin Hernandez <[email protected]>
* chore(common): format files with Prettier to satisfy CI lint

* security(demo): guard @noble/hashes resolver against path traversal in Metro
chore: address staging branch issues pr #1169 (#1178)
* update layout structure

* fix timeout

* feedback
cursor[bot]

This comment was marked as outdated.

aaronmgdr and others added 26 commits October 24, 2025 15:26
* setup publishing

* tag it as prerelease

* Apply suggestion from @coderabbitai[bot]

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Apply suggestion from @coderabbitai[bot]

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Apply suggestion from @coderabbitai[bot]

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* run workflow when msdk path changes

* build deps
* chore: bump mobile app version to 2.7.1

* fix last deployed timestamp

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Justin Hernandez <[email protected]>
* refactor: use singular ETHERSCAN_API_KEY in .env

Etherscan has unified all keys of associated explorers like Celoscan into a singular key rather than different keys for different networks.

* refactor: use one .env instead of separate .env.test + .env files

* refactor: deploy contracts with runs of 1000 instead of 200

Decreases gas cost of function calls on deployed contracts

* clean: remove duplicate/redundant deploy modules + scripts

* clean: cleanup empty script file

* refactor: cleanup default network of scripts

Read network from .env instead of using defaults of alfajores (outdated) or staging

* clean: remove references to Alfajores, replace with Sepolia

* chore: add default .env variables

* chore: update build-all script to include aardhaar circuit

* chore: update broken Powers of Tau download link (use iden3)

* chore: remove duplicate script

* fix: use stable version 18 for disclose circuits

* test: update test import paths to allow for .ts version of generateProof

* test: fix broken tests

* test: uncomment critical code for registration, change error names to updated names, fix broken import paths, update disclose tests for new scope generation/handling

* fix: broken import path

* test: fix Airdrop tests to use V2 logic

* docs: update docs for necessary prerequisite programs

* chore: yarn prettier formatting

* fix: CI errors occuring when deploying contracts as can't read .env

Using a dummy key for CI builds

* chore: yarn prettier

* refactor: change runs to 100000
@Vishalkulkarni45 Vishalkulkarni45 marked this pull request as draft October 28, 2025 03:35
export function logic to external libs, reduce compiler runs to 200, update deploy scripts to link new libs
@gitguardian
Copy link

gitguardian bot commented Oct 28, 2025

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
19414827 Triggered Generic Password 077dcc4 packages/mobile-sdk-alpha/ios/Frameworks/NFCPassportReader.xcframework/ios-arm64/NFCPassportReader.framework/Modules/NFCPassportReader.swiftmodule/arm64-apple-ios.private.swiftinterface View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants