From ca1aa9d26eea943ccbbb74f86cc4e578f680d9ed Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Fri, 22 Aug 2025 10:51:43 +0200 Subject: [PATCH 1/5] create ofactTree type to share --- app/src/stores/protocolStore.ts | 13 +++---------- app/src/utils/ofac.ts | 9 ++------- common/src/utils/types.ts | 7 +++++++ 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/app/src/stores/protocolStore.ts b/app/src/stores/protocolStore.ts index 99597a17e..53cb51a1b 100644 --- a/app/src/stores/protocolStore.ts +++ b/app/src/stores/protocolStore.ts @@ -18,6 +18,7 @@ import { IDENTITY_TREE_URL_STAGING, IDENTITY_TREE_URL_STAGING_ID_CARD, } from '@selfxyz/common/constants'; +import type { OfacTree } from '@selfxyz/common/utils/types'; import { fetchOfacTrees } from '@/utils/ofac'; @@ -29,11 +30,7 @@ interface ProtocolState { deployed_circuits: any; circuits_dns_mapping: any; alternative_csca: Record; - ofac_trees: { - passportNoAndNationality: any; - nameAndDob: any; - nameAndYob: any; - } | null; + ofac_trees: OfacTree | null; fetch_deployed_circuits: (environment: 'prod' | 'stg') => Promise; fetch_circuits_dns_mapping: (environment: 'prod' | 'stg') => Promise; fetch_csca_tree: (environment: 'prod' | 'stg') => Promise; @@ -53,11 +50,7 @@ interface ProtocolState { deployed_circuits: any; circuits_dns_mapping: any; alternative_csca: Record; - ofac_trees: { - passportNoAndNationality: any; - nameAndDob: any; - nameAndYob: any; - } | null; + ofac_trees: OfacTree | null; fetch_deployed_circuits: (environment: 'prod' | 'stg') => Promise; fetch_circuits_dns_mapping: (environment: 'prod' | 'stg') => Promise; fetch_csca_tree: (environment: 'prod' | 'stg') => Promise; diff --git a/app/src/utils/ofac.ts b/app/src/utils/ofac.ts index 8e8a9da09..72a0dcdfa 100644 --- a/app/src/utils/ofac.ts +++ b/app/src/utils/ofac.ts @@ -1,12 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import { TREE_URL, TREE_URL_STAGING } from '@selfxyz/common/constants'; - -export interface OfacTrees { - passportNoAndNationality: any; - nameAndDob: any; - nameAndYob: any; -} +import type { OfacTree } from '@selfxyz/common/utils/types'; export type OfacVariant = 'passport' | 'id_card'; @@ -31,7 +26,7 @@ const fetchTree = async (url: string): Promise => { export const fetchOfacTrees = async ( environment: 'prod' | 'stg', variant: OfacVariant = 'passport', -): Promise => { +): Promise => { const baseUrl = environment === 'prod' ? TREE_URL : TREE_URL_STAGING; const ppNoNatUrl = `${baseUrl}/ofac/passport-no-nationality`; diff --git a/common/src/utils/types.ts b/common/src/utils/types.ts index 5385f0a92..68fb8a838 100644 --- a/common/src/utils/types.ts +++ b/common/src/utils/types.ts @@ -4,6 +4,13 @@ import type { PassportMetadata } from './passports/passport_parsing/parsePasspor export type DocumentCategory = 'passport' | 'id_card'; export type DocumentType = 'passport' | 'id_card' | 'mock_passport' | 'mock_id_card'; + +export type OfacTree = { + passportNoAndNationality: any; + nameAndDob: any; + nameAndYob: any; +}; + export type PassportData = { mrz: string; dg1Hash?: number[]; From c653303e9d5a69b7b65eff23786e2b1346fda791 Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Fri, 22 Aug 2025 10:52:12 +0200 Subject: [PATCH 2/5] move proving inputs from app to register inputs in common --- app/src/utils/proving/provingInputs.ts | 165 +++----------------- common/src/utils/circuits/registerInputs.ts | 152 ++++++++++++++++++ 2 files changed, 170 insertions(+), 147 deletions(-) diff --git a/app/src/utils/proving/provingInputs.ts b/app/src/utils/proving/provingInputs.ts index 4b79ccd08..7a18047a7 100644 --- a/app/src/utils/proving/provingInputs.ts +++ b/app/src/utils/proving/provingInputs.ts @@ -1,163 +1,34 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 - -import { poseidon2 } from 'poseidon-lite'; -import { LeanIMT } from '@openpassport/zk-kit-lean-imt'; -import { SMT } from '@openpassport/zk-kit-smt'; - -import { - attributeToPosition, - attributeToPosition_ID, - DEFAULT_MAJORITY, - ID_CARD_ATTESTATION_ID, - PASSPORT_ATTESTATION_ID, -} from '@selfxyz/common/constants'; import type { DocumentCategory, PassportData } from '@selfxyz/common/types'; -import type { SelfApp, SelfAppDisclosureConfig } from '@selfxyz/common/utils'; +import type { SelfApp } from '@selfxyz/common/utils'; import { - calculateUserIdentifierHash, - generateCircuitInputsDSC, generateCircuitInputsRegister, - generateCircuitInputsVCandDisclose, - getCircuitNameFromPassportData, - hashEndpointWithScope, -} from '@selfxyz/common/utils'; + generateTEEInputsDiscloseStateless, + generateTEEInputsDSC, +} from '@selfxyz/common/utils/circuits/registerInputs'; import { useProtocolStore } from '@/stores/protocolStore'; -export function generateTEEInputsDSC( - passportData: PassportData, - cscaTree: string[][], - env: 'prod' | 'stg', -) { - const inputs = generateCircuitInputsDSC(passportData, cscaTree); - const circuitName = getCircuitNameFromPassportData(passportData, 'dsc'); - const endpointType = env === 'stg' ? 'staging_celo' : 'celo'; - const endpoint = 'https://self.xyz'; - return { inputs, circuitName, endpointType, endpoint }; -} - +export { generateCircuitInputsRegister, generateTEEInputsDSC }; export function generateTEEInputsDisclose( secret: string, passportData: PassportData, selfApp: SelfApp, ) { - const { scope, disclosures, endpoint, userId, userDefinedData, chainID } = - selfApp; - const userIdentifierHash = calculateUserIdentifierHash( - chainID, - userId, - userDefinedData, - ); - const scope_hash = hashEndpointWithScope(endpoint, scope); - const document: DocumentCategory = passportData.documentCategory; - - const selector_dg1 = getSelectorDg1(document, disclosures); - - const majority = disclosures.minimumAge - ? disclosures.minimumAge.toString() - : DEFAULT_MAJORITY; - const selector_older_than = disclosures.minimumAge ? '1' : '0'; - - const selector_ofac = disclosures.ofac ? 1 : 0; - - const ofac_trees = useProtocolStore.getState()[document].ofac_trees; - if (!ofac_trees) { - throw new Error('OFAC trees not loaded'); - } - let passportNoAndNationalitySMT: SMT | null = null; - const nameAndDobSMT = new SMT(poseidon2, true); - const nameAndYobSMT = new SMT(poseidon2, true); - if (document === 'passport') { - passportNoAndNationalitySMT = new SMT(poseidon2, true); - passportNoAndNationalitySMT.import(ofac_trees.passportNoAndNationality); - } - nameAndDobSMT.import(ofac_trees.nameAndDob); - nameAndYobSMT.import(ofac_trees.nameAndYob); - - const serialized_tree = useProtocolStore.getState()[document].commitment_tree; - const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serialized_tree); - const inputs = generateCircuitInputsVCandDisclose( + return generateTEEInputsDiscloseStateless( secret, - document === 'passport' ? PASSPORT_ATTESTATION_ID : ID_CARD_ATTESTATION_ID, passportData, - scope_hash, - selector_dg1, - selector_older_than, - tree, - majority, - passportNoAndNationalitySMT, - nameAndDobSMT, - nameAndYobSMT, - selector_ofac, - disclosures.excludedCountries ?? [], - userIdentifierHash.toString(), + selfApp, + (document: DocumentCategory, tree) => { + const protocolStore = useProtocolStore.getState(); + switch (tree) { + case 'ofac': + return protocolStore[document].ofac_trees; + case 'commitment': + return protocolStore[document].commitment_tree; + default: + throw new Error('Unknown tree type'); + } + }, ); - return { - inputs, - circuitName: - passportData.documentCategory === 'passport' - ? 'vc_and_disclose' - : 'vc_and_disclose_id', - endpointType: selfApp.endpointType, - endpoint: selfApp.endpoint, - }; -} - -export function generateTEEInputsRegister( - secret: string, - passportData: PassportData, - dscTree: string, - env: 'prod' | 'stg', -) { - const inputs = generateCircuitInputsRegister(secret, passportData, dscTree); - const circuitName = getCircuitNameFromPassportData(passportData, 'register'); - const endpointType = env === 'stg' ? 'staging_celo' : 'celo'; - const endpoint = 'https://self.xyz'; - return { inputs, circuitName, endpointType, endpoint }; -} - -/*** DISCLOSURE ***/ - -function getSelectorDg1( - document: DocumentCategory, - disclosures: SelfAppDisclosureConfig, -) { - switch (document) { - case 'passport': - return getSelectorDg1Passport(disclosures); - case 'id_card': - return getSelectorDg1IdCard(disclosures); - } -} - -function getSelectorDg1Passport(disclosures: SelfAppDisclosureConfig) { - const selector_dg1 = Array(88).fill('0'); - Object.entries(disclosures).forEach(([attribute, reveal]) => { - if (['ofac', 'excludedCountries', 'minimumAge'].includes(attribute)) { - return; - } - if (reveal) { - const [start, end] = - attributeToPosition[attribute as keyof typeof attributeToPosition]; - selector_dg1.fill('1', start, end + 1); - } - }); - return selector_dg1; -} - -function getSelectorDg1IdCard(disclosures: SelfAppDisclosureConfig) { - const selector_dg1 = Array(90).fill('0'); - Object.entries(disclosures).forEach(([attribute, reveal]) => { - if (['ofac', 'excludedCountries', 'minimumAge'].includes(attribute)) { - return; - } - if (reveal) { - const [start, end] = - attributeToPosition_ID[ - attribute as keyof typeof attributeToPosition_ID - ]; - selector_dg1.fill('1', start, end + 1); - } - }); - return selector_dg1; } diff --git a/common/src/utils/circuits/registerInputs.ts b/common/src/utils/circuits/registerInputs.ts index 1632d0dc3..ce992a482 100644 --- a/common/src/utils/circuits/registerInputs.ts +++ b/common/src/utils/circuits/registerInputs.ts @@ -1 +1,153 @@ +// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 + +import { poseidon2 } from 'poseidon-lite'; + +import { + attributeToPosition, + attributeToPosition_ID, + DEFAULT_MAJORITY, + ID_CARD_ATTESTATION_ID, + PASSPORT_ATTESTATION_ID, +} from '../../constants/constants.js'; +import type { DocumentCategory, PassportData } from '../../types/index.js'; +import type { SelfApp, SelfAppDisclosureConfig } from '../../utils/appType.js'; +import { + calculateUserIdentifierHash, + generateCircuitInputsDSC, + generateCircuitInputsRegister, + generateCircuitInputsVCandDisclose, + getCircuitNameFromPassportData, + hashEndpointWithScope, +} from '../../utils/index.js'; +import type { OfacTree } from '../../utils/types.js'; + +import { LeanIMT } from '@openpassport/zk-kit-lean-imt'; +import { SMT } from '@openpassport/zk-kit-smt'; + export { generateCircuitInputsRegister } from './generateInputs.js'; + +export function generateTEEInputsDSC( + passportData: PassportData, + cscaTree: string[][], + env: 'prod' | 'stg' +) { + const inputs = generateCircuitInputsDSC(passportData, cscaTree); + const circuitName = getCircuitNameFromPassportData(passportData, 'dsc'); + const endpointType = env === 'stg' ? 'staging_celo' : 'celo'; + const endpoint = 'https://self.xyz'; + return { inputs, circuitName, endpointType, endpoint }; +} + +export function generateTEEInputsDiscloseStateless( + secret: string, + passportData: PassportData, + selfApp: SelfApp, + getTree: ( + doc: DocumentCategory, + tree: T + ) => T extends 'ofac' ? OfacTree : any +) { + const { scope, disclosures, endpoint, userId, userDefinedData, chainID } = selfApp; + const userIdentifierHash = calculateUserIdentifierHash(chainID, userId, userDefinedData); + const scope_hash = hashEndpointWithScope(endpoint, scope); + const document: DocumentCategory = passportData.documentCategory; + + const selector_dg1 = getSelectorDg1(document, disclosures); + + const majority = disclosures.minimumAge ? disclosures.minimumAge.toString() : DEFAULT_MAJORITY; + const selector_older_than = disclosures.minimumAge ? '1' : '0'; + + const selector_ofac = disclosures.ofac ? 1 : 0; + + const ofac_trees = getTree(document, 'ofac'); + if (!ofac_trees) { + throw new Error('OFAC trees not loaded'); + } + let passportNoAndNationalitySMT: SMT | null = null; + const nameAndDobSMT = new SMT(poseidon2, true); + const nameAndYobSMT = new SMT(poseidon2, true); + if (document === 'passport') { + passportNoAndNationalitySMT = new SMT(poseidon2, true); + passportNoAndNationalitySMT.import(ofac_trees.passportNoAndNationality); + } + nameAndDobSMT.import(ofac_trees.nameAndDob); + nameAndYobSMT.import(ofac_trees.nameAndYob); + + const serialized_tree = getTree(document, 'commitment'); + const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serialized_tree); + const inputs = generateCircuitInputsVCandDisclose( + secret, + document === 'passport' ? PASSPORT_ATTESTATION_ID : ID_CARD_ATTESTATION_ID, + passportData, + scope_hash, + selector_dg1, + selector_older_than, + tree, + majority, + passportNoAndNationalitySMT, + nameAndDobSMT, + nameAndYobSMT, + selector_ofac, + disclosures.excludedCountries ?? [], + userIdentifierHash.toString() + ); + return { + inputs, + circuitName: + passportData.documentCategory === 'passport' ? 'vc_and_disclose' : 'vc_and_disclose_id', + endpointType: selfApp.endpointType, + endpoint: selfApp.endpoint, + }; +} + +export function generateTEEInputsRegister( + secret: string, + passportData: PassportData, + dscTree: string, + env: 'prod' | 'stg' +) { + const inputs = generateCircuitInputsRegister(secret, passportData, dscTree); + const circuitName = getCircuitNameFromPassportData(passportData, 'register'); + const endpointType = env === 'stg' ? 'staging_celo' : 'celo'; + const endpoint = 'https://self.xyz'; + return { inputs, circuitName, endpointType, endpoint }; +} + +/*** DISCLOSURE ***/ + +function getSelectorDg1(document: DocumentCategory, disclosures: SelfAppDisclosureConfig) { + switch (document) { + case 'passport': + return getSelectorDg1Passport(disclosures); + case 'id_card': + return getSelectorDg1IdCard(disclosures); + } +} + +function getSelectorDg1Passport(disclosures: SelfAppDisclosureConfig) { + const selector_dg1 = Array(88).fill('0'); + Object.entries(disclosures).forEach(([attribute, reveal]) => { + if (['ofac', 'excludedCountries', 'minimumAge'].includes(attribute)) { + return; + } + if (reveal) { + const [start, end] = attributeToPosition[attribute as keyof typeof attributeToPosition]; + selector_dg1.fill('1', start, end + 1); + } + }); + return selector_dg1; +} + +function getSelectorDg1IdCard(disclosures: SelfAppDisclosureConfig) { + const selector_dg1 = Array(90).fill('0'); + Object.entries(disclosures).forEach(([attribute, reveal]) => { + if (['ofac', 'excludedCountries', 'minimumAge'].includes(attribute)) { + return; + } + if (reveal) { + const [start, end] = attributeToPosition_ID[attribute as keyof typeof attributeToPosition_ID]; + selector_dg1.fill('1', start, end + 1); + } + }); + return selector_dg1; +} From 3f56bdc5addacde8a6fa6b752cfd65a3d542190b Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Fri, 22 Aug 2025 10:53:00 +0200 Subject: [PATCH 3/5] missed reexport --- app/src/utils/proving/provingInputs.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/utils/proving/provingInputs.ts b/app/src/utils/proving/provingInputs.ts index 7a18047a7..a1dce37d3 100644 --- a/app/src/utils/proving/provingInputs.ts +++ b/app/src/utils/proving/provingInputs.ts @@ -5,11 +5,16 @@ import { generateCircuitInputsRegister, generateTEEInputsDiscloseStateless, generateTEEInputsDSC, + generateTEEInputsRegister, } from '@selfxyz/common/utils/circuits/registerInputs'; import { useProtocolStore } from '@/stores/protocolStore'; -export { generateCircuitInputsRegister, generateTEEInputsDSC }; +export { + generateCircuitInputsRegister, + generateTEEInputsDSC, + generateTEEInputsRegister, +}; export function generateTEEInputsDisclose( secret: string, passportData: PassportData, From 7e34824cea620203e79e08777aaee24df9e7bb9b Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Fri, 22 Aug 2025 10:57:29 +0200 Subject: [PATCH 4/5] ok --- app/src/utils/proving/provingInputs.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/utils/proving/provingInputs.ts b/app/src/utils/proving/provingInputs.ts index a1dce37d3..2da8d30a3 100644 --- a/app/src/utils/proving/provingInputs.ts +++ b/app/src/utils/proving/provingInputs.ts @@ -1,4 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 + import type { DocumentCategory, PassportData } from '@selfxyz/common/types'; import type { SelfApp } from '@selfxyz/common/utils'; import { From d7a4821e056e052522fc007a8232df59fd44a2d8 Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Fri, 22 Aug 2025 12:16:39 +0200 Subject: [PATCH 5/5] add some validations as suggested by our ai overlords --- common/src/utils/circuits/registerInputs.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/src/utils/circuits/registerInputs.ts b/common/src/utils/circuits/registerInputs.ts index ce992a482..c99527b85 100644 --- a/common/src/utils/circuits/registerInputs.ts +++ b/common/src/utils/circuits/registerInputs.ts @@ -63,6 +63,15 @@ export function generateTEEInputsDiscloseStateless( if (!ofac_trees) { throw new Error('OFAC trees not loaded'); } + + // Validate OFAC tree structure + if (!ofac_trees.nameAndDob || !ofac_trees.nameAndYob) { + throw new Error('Invalid OFAC tree structure: missing required fields'); + } + if (document === 'passport' && !ofac_trees.passportNoAndNationality) { + throw new Error('Invalid OFAC tree structure: missing passportNoAndNationality for passport'); + } + let passportNoAndNationalitySMT: SMT | null = null; const nameAndDobSMT = new SMT(poseidon2, true); const nameAndYobSMT = new SMT(poseidon2, true);