From 96390041a6ea10c63664bbcd61afc159a9d91dcb Mon Sep 17 00:00:00 2001 From: Nesopie <87437291+Nesopie@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:40:50 +0530 Subject: [PATCH 1/7] Revert "fix: ecdsa (#1625)" (#1671) This reverts commit 13d81c53bfb9b2514c178e59f7dec0e9b6cd1d24. --- .../crypto/bigInt/bigIntComparators.circom | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/circuits/circuits/utils/crypto/bigInt/bigIntComparators.circom b/circuits/circuits/utils/crypto/bigInt/bigIntComparators.circom index a90dafa26f..0d6c530032 100644 --- a/circuits/circuits/utils/crypto/bigInt/bigIntComparators.circom +++ b/circuits/circuits/utils/crypto/bigInt/bigIntComparators.circom @@ -17,13 +17,13 @@ include "../utils/switcher.circom"; // Can check for 2 bigints equality if in is sub of each chunk of those numbers template BigIntIsZero(CHUNK_SIZE, MAX_CHUNK_SIZE, CHUNK_NUMBER) { assert(CHUNK_NUMBER >= 2); - + var EPSILON = 3; - + assert(MAX_CHUNK_SIZE + EPSILON <= 253); - + signal input in[CHUNK_NUMBER]; - + signal carry[CHUNK_NUMBER - 1]; component carryRangeChecks[CHUNK_NUMBER - 1]; for (var i = 0; i < CHUNK_NUMBER - 1; i++){ @@ -45,9 +45,9 @@ template BigIntIsZero(CHUNK_SIZE, MAX_CHUNK_SIZE, CHUNK_NUMBER) { // Works with overflowed signed chunks // To handle megative values we use sign // Sign is var and can be changed, but it should be a problem -// Sign change means that we can calculate for -in instead of in, +// Sign change means that we can calculate for -in instead of in, // But if in % p == 0 means that -in % p == 0 too, so no exploit here -// Problem lies in other one: +// Problem lies in other one: // k - is result of div func, and can be anything (var) // we check k * p - in === 0 // k * p is result of big multiplication @@ -71,9 +71,9 @@ template BigIntIsZero(CHUNK_SIZE, MAX_CHUNK_SIZE, CHUNK_NUMBER) { template BigIntIsZeroModP(CHUNK_SIZE, MAX_CHUNK_SIZE, CHUNK_NUMBER, MAX_CHUNK_NUMBER, CHUNK_NUMBER_MODULUS){ signal input in[CHUNK_NUMBER]; signal input modulus[CHUNK_NUMBER_MODULUS]; - + var CHUNK_NUMBER_DIV = MAX_CHUNK_NUMBER - CHUNK_NUMBER_MODULUS + 1; - + var reduced[200] = reduce_overflow_signed_dl(CHUNK_SIZE, CHUNK_NUMBER, MAX_CHUNK_NUMBER, MAX_CHUNK_SIZE, in); var div_result[2][200] = long_div_dl(CHUNK_SIZE, CHUNK_NUMBER_MODULUS, CHUNK_NUMBER_DIV - 1, reduced, modulus); signal sign <-- reduced[199]; @@ -88,7 +88,7 @@ template BigIntIsZeroModP(CHUNK_SIZE, MAX_CHUNK_SIZE, CHUNK_NUMBER, MAX_CHUNK_NU for (var i = 0; i < CHUNK_NUMBER_DIV; i++){ k[i] <-- div_result[0][i]; kRangeChecks[i] = Num2Bits(CHUNK_SIZE); - kRangeChecks[i].in <== k[i]; + kRangeChecks[i].in <-- k[i]; } component mult; @@ -101,7 +101,7 @@ template BigIntIsZeroModP(CHUNK_SIZE, MAX_CHUNK_SIZE, CHUNK_NUMBER, MAX_CHUNK_NU mult.in1 <== modulus; mult.in2 <== k; } - + component swicher[CHUNK_NUMBER]; component isZero = BigIntIsZero(CHUNK_SIZE, MAX_CHUNK_SIZE, MAX_CHUNK_NUMBER); @@ -116,5 +116,5 @@ template BigIntIsZeroModP(CHUNK_SIZE, MAX_CHUNK_SIZE, CHUNK_NUMBER, MAX_CHUNK_NU for (var i = CHUNK_NUMBER; i < MAX_CHUNK_NUMBER; i++){ isZero.in[i] <== mult.out[i]; } - -} + +} \ No newline at end of file From cae937b8ba41490d29787c800cc930cd687271a9 Mon Sep 17 00:00:00 2001 From: Nesopie <87437291+Nesopie@users.noreply.github.com> Date: Thu, 29 Jan 2026 22:48:12 +0530 Subject: [PATCH 2/7] feat: add register_kyc and vc_and_disclose_kyc verifiers (#1672) --- .../disclose/Verifier_vc_and_disclose_kyc.sol | 364 ++++++++++++++++++ .../register_kyc/Verifier_register_kyc.sol | 189 +++++++++ 2 files changed, 553 insertions(+) create mode 100644 contracts/contracts/verifiers/disclose/Verifier_vc_and_disclose_kyc.sol create mode 100644 contracts/contracts/verifiers/register_kyc/Verifier_register_kyc.sol diff --git a/contracts/contracts/verifiers/disclose/Verifier_vc_and_disclose_kyc.sol b/contracts/contracts/verifiers/disclose/Verifier_vc_and_disclose_kyc.sol new file mode 100644 index 0000000000..b8de45e2cc --- /dev/null +++ b/contracts/contracts/verifiers/disclose/Verifier_vc_and_disclose_kyc.sol @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + +pragma solidity >=0.7.0 <0.9.0; + +contract Groth16Verifier { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679; + uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856; + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant deltax1 = 1022502948747070596300631872305350196366208813582081229292413330002410493735; + uint256 constant deltax2 = 8307404806875602039009979465400882149520343934575147532878670270259674144681; + uint256 constant deltay1 = 8725996148009629609617423651062395041554350094385944632997372312828608644955; + uint256 constant deltay2 = 19505227144542990355285832777856832082655385455315296491381347497982380087331; + + + uint256 constant IC0x = 16649376790350306128495410672000438222835355361873864679185308928608342391377; + uint256 constant IC0y = 1365830659239397567654193478106544803466926587095831397836882385286292210457; + + uint256 constant IC1x = 12768368041823022971486465099843313755353560181066686496309262693573983752166; + uint256 constant IC1y = 8959643464054312389755875312066576344157543684040889350558798123653714759323; + + uint256 constant IC2x = 8026951355325092256379108005740615512895662065129471323964253392093201472413; + uint256 constant IC2y = 17729685419344675830181571225504519401370157618831493299320871505193568194542; + + uint256 constant IC3x = 13865750614916211164740113517816425481179265306761612472818567385469595810190; + uint256 constant IC3y = 6210007189067774389269573600168370223250403017805496113623335642264819992738; + + uint256 constant IC4x = 16855313964021865460083277912281502340407051430688518561820294646056966683723; + uint256 constant IC4y = 15265407205922489364865678414919162208795257265772110915033785419192236363960; + + uint256 constant IC5x = 18598823774356508040525215881560556738983729535652356395586704599152692518280; + uint256 constant IC5y = 18145817576163407281749708126167770321482159783050035647989919114769433256079; + + uint256 constant IC6x = 7929686493832109041041190086485345905029205802382475316611421597823511641043; + uint256 constant IC6y = 19169046602940406351907027759303697432610627026407453208752335429425017694574; + + uint256 constant IC7x = 2605668546149689485076733864456601989800612639397730351435615085329568572059; + uint256 constant IC7y = 2242419572125099587271391127551951332349827207830958146376081280864531825864; + + uint256 constant IC8x = 17230061988111645534990582267868011734783232047326494254312685097544413153459; + uint256 constant IC8y = 10806577457667861555253433417098515955632524053970338643826272138544403320442; + + uint256 constant IC9x = 3751984630395628299497200107740113530312143585224331604497180428031979981854; + uint256 constant IC9y = 15676455188720477849218254715359881022685281346012746362600653176819367175994; + + uint256 constant IC10x = 9038868170600703467507268624782850799834426621476374278712452873055805013104; + uint256 constant IC10y = 9698587198888135369066906249654396893723648003242241945599284193157738042248; + + uint256 constant IC11x = 6050467884563375668249040797272149300003806466909114026944043296882360309360; + uint256 constant IC11y = 15900287959991498727296171595521639394049115178198151794906977584504380285297; + + uint256 constant IC12x = 11084322708760789175416300406920316493444723572225966905156819463716045081320; + uint256 constant IC12y = 11218515196222567596688687943809578734267033209068034707100619316839921252394; + + uint256 constant IC13x = 10645041863169277188776881369692412104739148582039109401067090622235062084156; + uint256 constant IC13y = 5266268354502390834581900591132009542571872858584466937449333517597831148030; + + uint256 constant IC14x = 12641747272597271663246870871466152965248117816492334493753291231347523232168; + uint256 constant IC14y = 19526003775802419962730302158408658198175393685733749794278416969198861577034; + + uint256 constant IC15x = 6139284918750361257008863566645097867991292622068199456332000872393801256773; + uint256 constant IC15y = 7099084867504428315337895159166860608559331005995192184490932820607010680845; + + uint256 constant IC16x = 9370432203154443644773178040475615441452364961035990256255996609230750218064; + uint256 constant IC16y = 17951757691776403072537537626795200133221243393670030429694486485017127221358; + + uint256 constant IC17x = 21581607541319264321515681298226106781535771321110191776762670932817827595844; + uint256 constant IC17y = 7631049069535860061742036261740730390300464507981117501570404056719958498930; + + uint256 constant IC18x = 16588935529361800732448688229721305142336631834288163321894359880448688608191; + uint256 constant IC18y = 4976649298929967469596409013742801233623738930274577396507275281714439091100; + + uint256 constant IC19x = 13336088316263130029440976636885322206279122461816212975585641922353453096719; + uint256 constant IC19y = 668527371723708514830022396101506352277923231593513590339198147917179128262; + + uint256 constant IC20x = 7911418535344866382682474453536883970529338904273675929069409842800763592456; + uint256 constant IC20y = 6722145715621557485364045815849938484983110008747946723738151730812429418202; + + uint256 constant IC21x = 610873214241184085635414594211441831430912772471117234461302269567691174096; + uint256 constant IC21y = 16969907768023728182903317862310886370963194429698287724301462949165910596854; + + uint256 constant IC22x = 659738555556673077218073955988504765951032248025470001896149485964044510568; + uint256 constant IC22y = 2124464077179769137643014583429957482256390408775774347541901875987080182668; + + uint256 constant IC23x = 11040330531093768074742977048495269267038172161278331102262692904222746927915; + uint256 constant IC23y = 20387648111599243028561521301140310714164415003338654058061856932087967245514; + + uint256 constant IC24x = 6937058621269922207815167233155518898032328662416059831807664411944661190679; + uint256 constant IC24y = 3779340684837021741207549471402298796167963069596080462551336236827030143602; + + uint256 constant IC25x = 20956067714892758188531163534075112952656779768842660715243328162174316184647; + uint256 constant IC25y = 9697689335367034906644638465039998846629732846527791686651080885302279721947; + + uint256 constant IC26x = 10803066158517027587330447158982829324243112587050865062666733319696533170000; + uint256 constant IC26y = 16966880529095588436103115659246637747363575619917237189424029126730846465979; + + uint256 constant IC27x = 12430600018955874842029331801839308658974272583893366935707885910189427842476; + uint256 constant IC27y = 14602780957678176966948503351865628319039612308733335242961008886115024541985; + + uint256 constant IC28x = 10923748125791784887614451982072899321420747436037959145471646494829305705731; + uint256 constant IC28y = 6050274667868774010280923182747429242888928748472706014853484883020658961073; + + uint256 constant IC29x = 1170885743391113947515531032472753161485583617637753865725092942330476093342; + uint256 constant IC29y = 19204742121781488340297839383055704899252648836617466985181418250802660585322; + + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[29] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, r)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) + + g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32))) + + g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64))) + + g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96))) + + g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128))) + + g1_mulAccC(_pVk, IC6x, IC6y, calldataload(add(pubSignals, 160))) + + g1_mulAccC(_pVk, IC7x, IC7y, calldataload(add(pubSignals, 192))) + + g1_mulAccC(_pVk, IC8x, IC8y, calldataload(add(pubSignals, 224))) + + g1_mulAccC(_pVk, IC9x, IC9y, calldataload(add(pubSignals, 256))) + + g1_mulAccC(_pVk, IC10x, IC10y, calldataload(add(pubSignals, 288))) + + g1_mulAccC(_pVk, IC11x, IC11y, calldataload(add(pubSignals, 320))) + + g1_mulAccC(_pVk, IC12x, IC12y, calldataload(add(pubSignals, 352))) + + g1_mulAccC(_pVk, IC13x, IC13y, calldataload(add(pubSignals, 384))) + + g1_mulAccC(_pVk, IC14x, IC14y, calldataload(add(pubSignals, 416))) + + g1_mulAccC(_pVk, IC15x, IC15y, calldataload(add(pubSignals, 448))) + + g1_mulAccC(_pVk, IC16x, IC16y, calldataload(add(pubSignals, 480))) + + g1_mulAccC(_pVk, IC17x, IC17y, calldataload(add(pubSignals, 512))) + + g1_mulAccC(_pVk, IC18x, IC18y, calldataload(add(pubSignals, 544))) + + g1_mulAccC(_pVk, IC19x, IC19y, calldataload(add(pubSignals, 576))) + + g1_mulAccC(_pVk, IC20x, IC20y, calldataload(add(pubSignals, 608))) + + g1_mulAccC(_pVk, IC21x, IC21y, calldataload(add(pubSignals, 640))) + + g1_mulAccC(_pVk, IC22x, IC22y, calldataload(add(pubSignals, 672))) + + g1_mulAccC(_pVk, IC23x, IC23y, calldataload(add(pubSignals, 704))) + + g1_mulAccC(_pVk, IC24x, IC24y, calldataload(add(pubSignals, 736))) + + g1_mulAccC(_pVk, IC25x, IC25y, calldataload(add(pubSignals, 768))) + + g1_mulAccC(_pVk, IC26x, IC26y, calldataload(add(pubSignals, 800))) + + g1_mulAccC(_pVk, IC27x, IC27y, calldataload(add(pubSignals, 832))) + + g1_mulAccC(_pVk, IC28x, IC28y, calldataload(add(pubSignals, 864))) + + g1_mulAccC(_pVk, IC29x, IC29y, calldataload(add(pubSignals, 896))) + + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + + checkField(calldataload(add(_pubSignals, 0))) + + checkField(calldataload(add(_pubSignals, 32))) + + checkField(calldataload(add(_pubSignals, 64))) + + checkField(calldataload(add(_pubSignals, 96))) + + checkField(calldataload(add(_pubSignals, 128))) + + checkField(calldataload(add(_pubSignals, 160))) + + checkField(calldataload(add(_pubSignals, 192))) + + checkField(calldataload(add(_pubSignals, 224))) + + checkField(calldataload(add(_pubSignals, 256))) + + checkField(calldataload(add(_pubSignals, 288))) + + checkField(calldataload(add(_pubSignals, 320))) + + checkField(calldataload(add(_pubSignals, 352))) + + checkField(calldataload(add(_pubSignals, 384))) + + checkField(calldataload(add(_pubSignals, 416))) + + checkField(calldataload(add(_pubSignals, 448))) + + checkField(calldataload(add(_pubSignals, 480))) + + checkField(calldataload(add(_pubSignals, 512))) + + checkField(calldataload(add(_pubSignals, 544))) + + checkField(calldataload(add(_pubSignals, 576))) + + checkField(calldataload(add(_pubSignals, 608))) + + checkField(calldataload(add(_pubSignals, 640))) + + checkField(calldataload(add(_pubSignals, 672))) + + checkField(calldataload(add(_pubSignals, 704))) + + checkField(calldataload(add(_pubSignals, 736))) + + checkField(calldataload(add(_pubSignals, 768))) + + checkField(calldataload(add(_pubSignals, 800))) + + checkField(calldataload(add(_pubSignals, 832))) + + checkField(calldataload(add(_pubSignals, 864))) + + checkField(calldataload(add(_pubSignals, 896))) + + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } diff --git a/contracts/contracts/verifiers/register_kyc/Verifier_register_kyc.sol b/contracts/contracts/verifiers/register_kyc/Verifier_register_kyc.sol new file mode 100644 index 0000000000..263f6efd9e --- /dev/null +++ b/contracts/contracts/verifiers/register_kyc/Verifier_register_kyc.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + +pragma solidity >=0.7.0 <0.9.0; + +contract Verifier_register_kyc { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679; + uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856; + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant deltax1 = 5096083179356499711134631633887324869705417987781707067448982643113793288629; + uint256 constant deltax2 = 21697837263794337150638011065730493662458737594964062811076864693347158601584; + uint256 constant deltay1 = 10401404284625717188368140886450294801087446278285114268746933223843924747393; + uint256 constant deltay2 = 21623976071772575613470418289568781837131470676146510317928308200173145329920; + + + uint256 constant IC0x = 3168135977548073774669686196671110956985263260631963004209946350111009871783; + uint256 constant IC0y = 19251271161827058925074199219712324559154387560340229136388386911360884273664; + + uint256 constant IC1x = 10113211405751296270501192543847397464767605934439509015058826831045146327835; + uint256 constant IC1y = 20906232714001423808044993672348326367907746369031125809295685889757083482955; + + uint256 constant IC2x = 6698755477482983343149024614634334433817620579582112164753380215391423709716; + uint256 constant IC2y = 19611748192038263311129103965451949878445716642076010695268731681711285170849; + + uint256 constant IC3x = 14337814476916517064830141950947112575746971807933737544800387322677759596630; + uint256 constant IC3y = 20134363192770038065525691357184427373049635942597185153353604022941231384818; + + uint256 constant IC4x = 11598465374717791235735036209864180918816853983932860910077820062417244512066; + uint256 constant IC4y = 10915386471964999341016166937952548568058036159601535214565672698374193076432; + + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[4] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, r)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) + + g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32))) + + g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64))) + + g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96))) + + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + + checkField(calldataload(add(_pubSignals, 0))) + + checkField(calldataload(add(_pubSignals, 32))) + + checkField(calldataload(add(_pubSignals, 64))) + + checkField(calldataload(add(_pubSignals, 96))) + + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } From 72c2b08334ef697f274517daf2e000acdf0af5b3 Mon Sep 17 00:00:00 2001 From: Nesopie <87437291+Nesopie@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:49:39 +0530 Subject: [PATCH 3/7] feat: add gcp jwt verifier (#1674) --- .../verifiers/gcp/Verifier_gcp_jwt.sol | 301 ++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 contracts/contracts/verifiers/gcp/Verifier_gcp_jwt.sol diff --git a/contracts/contracts/verifiers/gcp/Verifier_gcp_jwt.sol b/contracts/contracts/verifiers/gcp/Verifier_gcp_jwt.sol new file mode 100644 index 0000000000..4d36ea61cd --- /dev/null +++ b/contracts/contracts/verifiers/gcp/Verifier_gcp_jwt.sol @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + +pragma solidity >=0.7.0 <0.9.0; + +contract Verifier_gcp_jwt { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679; + uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856; + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant deltax1 = 1804222383802986733937376810902861143401033555807870231731929239915419049861; + uint256 constant deltax2 = 15902885537441599351050098769394227668772388058868388096316964244217496511682; + uint256 constant deltay1 = 4195707504005103778106485021796359604414786496137920116128130440872062477216; + uint256 constant deltay2 = 20513207510859042996645896574478474889840017920990203652675014165180462273668; + + + uint256 constant IC0x = 6972951741762339913362267428319005943611938060812676091174501911982947323821; + uint256 constant IC0y = 4968121098705797351946375443564156998686441710551907423285338106315203657372; + + uint256 constant IC1x = 3969479803545901558882616933276060341612655312403217371718193775571328202698; + uint256 constant IC1y = 10796516354190443333590906104065573186594421836191093099894208495600273943382; + + uint256 constant IC2x = 5282886783908067346990928387588210996099802199800176473402519317523182497411; + uint256 constant IC2y = 13420701105707643769706876856296866111708803407614711871170325095961081369695; + + uint256 constant IC3x = 14105950545034420261862110084277090993607573654064743638564927148396262651666; + uint256 constant IC3y = 13354956139782865997977495342720245140716772080136555810660173122394181127180; + + uint256 constant IC4x = 17223368406124250621460330134418760536341963146179581332507963390797809647912; + uint256 constant IC4y = 19015620010364835231555497011683709184643217460850880718542989960325995808017; + + uint256 constant IC5x = 11415362657438949221591074018468802007898322076011964898865456054649179831908; + uint256 constant IC5y = 17459573325598515038912928408360066384367356809087828399079121874232360528478; + + uint256 constant IC6x = 15574545936483334745596750909280550198515448424427848182054643607937078179213; + uint256 constant IC6y = 13006549512473282147197122913454973085866920937923147249375738521329287066222; + + uint256 constant IC7x = 14645989050046479540147134517500433000682841795623944679511623689017979403245; + uint256 constant IC7y = 16002146776744341769994596125501558460157837756621333957158039132600774201665; + + uint256 constant IC8x = 17447612904927318100653430764709204605475101707883725218472729377143326600248; + uint256 constant IC8y = 16892886274335002504909275077153679691684214526248560805118560019125943648821; + + uint256 constant IC9x = 17653661950237194880278154054792568909474176263902202958186273149474358670533; + uint256 constant IC9y = 11669219494719975955790450067861506164332870357879984076098486608481987018857; + + uint256 constant IC10x = 13289207501149959620194929372715676920560830325500657282490914929267428690980; + uint256 constant IC10y = 12465657438099014694334055521610703216229866770917539818266695642349007426072; + + uint256 constant IC11x = 18446654622136293276199162514838693836980616816456314636743905193625590745253; + uint256 constant IC11y = 12876916821064374752505779861869326377989533450827838519593872009453598320656; + + uint256 constant IC12x = 11001381773587677694421240176598022327285567125732057704900785068521955604564; + uint256 constant IC12y = 15721905323957520285870204323317542530315127175554829712351392669354944626115; + + uint256 constant IC13x = 19526090904722047042773905186611760547729403485756211734248157863388135796357; + uint256 constant IC13y = 6872421404352779784414693997079152972445035104903743503355279949152744176183; + + uint256 constant IC14x = 15194138441068760983236111544251338084740306295420897247383092303969333517280; + uint256 constant IC14y = 17571382599242644993857901274570230804168370452582601899367177574780143361956; + + uint256 constant IC15x = 584870595147362727880838486101127854955042037369856345600359023707849233383; + uint256 constant IC15y = 12343643073139461156226272211050331809098122200356986708169739203244290558425; + + uint256 constant IC16x = 14164891277783985284859197223195840777194061449283527719178608169082529731883; + uint256 constant IC16y = 5769361895392815047832493230313789373949187154386769492255962435984388734; + + uint256 constant IC17x = 5526583431755874525920531779957581117218605045719526246142282984128225259812; + uint256 constant IC17y = 15582261976988135470726322969910254124942972597198825965150134549937865280024; + + uint256 constant IC18x = 11933687532433713666089789805193821666211611847890385200532102102696090562695; + uint256 constant IC18y = 13768581020150988368938923899239734752213497676691170616636813895788587803927; + + uint256 constant IC19x = 21039243000302785560612608554208434709650210545299036143304628975668975303432; + uint256 constant IC19y = 3072044020424624557872621541718589400992098528118783904368755425332969903054; + + uint256 constant IC20x = 13029408846315391045768292892963336300734709802776968717851605403617397448869; + uint256 constant IC20y = 21441391199269244274037661931659719640029973634066921385003370500690694569608; + + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[20] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, r)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) + + g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32))) + + g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64))) + + g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96))) + + g1_mulAccC(_pVk, IC5x, IC5y, calldataload(add(pubSignals, 128))) + + g1_mulAccC(_pVk, IC6x, IC6y, calldataload(add(pubSignals, 160))) + + g1_mulAccC(_pVk, IC7x, IC7y, calldataload(add(pubSignals, 192))) + + g1_mulAccC(_pVk, IC8x, IC8y, calldataload(add(pubSignals, 224))) + + g1_mulAccC(_pVk, IC9x, IC9y, calldataload(add(pubSignals, 256))) + + g1_mulAccC(_pVk, IC10x, IC10y, calldataload(add(pubSignals, 288))) + + g1_mulAccC(_pVk, IC11x, IC11y, calldataload(add(pubSignals, 320))) + + g1_mulAccC(_pVk, IC12x, IC12y, calldataload(add(pubSignals, 352))) + + g1_mulAccC(_pVk, IC13x, IC13y, calldataload(add(pubSignals, 384))) + + g1_mulAccC(_pVk, IC14x, IC14y, calldataload(add(pubSignals, 416))) + + g1_mulAccC(_pVk, IC15x, IC15y, calldataload(add(pubSignals, 448))) + + g1_mulAccC(_pVk, IC16x, IC16y, calldataload(add(pubSignals, 480))) + + g1_mulAccC(_pVk, IC17x, IC17y, calldataload(add(pubSignals, 512))) + + g1_mulAccC(_pVk, IC18x, IC18y, calldataload(add(pubSignals, 544))) + + g1_mulAccC(_pVk, IC19x, IC19y, calldataload(add(pubSignals, 576))) + + g1_mulAccC(_pVk, IC20x, IC20y, calldataload(add(pubSignals, 608))) + + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + + checkField(calldataload(add(_pubSignals, 0))) + + checkField(calldataload(add(_pubSignals, 32))) + + checkField(calldataload(add(_pubSignals, 64))) + + checkField(calldataload(add(_pubSignals, 96))) + + checkField(calldataload(add(_pubSignals, 128))) + + checkField(calldataload(add(_pubSignals, 160))) + + checkField(calldataload(add(_pubSignals, 192))) + + checkField(calldataload(add(_pubSignals, 224))) + + checkField(calldataload(add(_pubSignals, 256))) + + checkField(calldataload(add(_pubSignals, 288))) + + checkField(calldataload(add(_pubSignals, 320))) + + checkField(calldataload(add(_pubSignals, 352))) + + checkField(calldataload(add(_pubSignals, 384))) + + checkField(calldataload(add(_pubSignals, 416))) + + checkField(calldataload(add(_pubSignals, 448))) + + checkField(calldataload(add(_pubSignals, 480))) + + checkField(calldataload(add(_pubSignals, 512))) + + checkField(calldataload(add(_pubSignals, 544))) + + checkField(calldataload(add(_pubSignals, 576))) + + checkField(calldataload(add(_pubSignals, 608))) + + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } From f11e8606594c89a5b3e735a62c26c1600893fa25 Mon Sep 17 00:00:00 2001 From: Nesopie <87437291+Nesopie@users.noreply.github.com> Date: Fri, 30 Jan 2026 20:45:56 +0530 Subject: [PATCH 4/7] fix: use pubsignals length of 20 (#1675) --- .../registry/IdentityRegistryKycImplV1.sol | 18 +++++++++--------- .../contracts/tests/MockGCPJWTVerifier.sol | 2 +- contracts/test/v2/registerKyc.test.ts | 9 +++++++++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/contracts/contracts/registry/IdentityRegistryKycImplV1.sol b/contracts/contracts/registry/IdentityRegistryKycImplV1.sol index 437925cd1d..feae4e1448 100644 --- a/contracts/contracts/registry/IdentityRegistryKycImplV1.sol +++ b/contracts/contracts/registry/IdentityRegistryKycImplV1.sol @@ -92,7 +92,7 @@ interface IGCPJWTVerifier { uint256[2] calldata pA, uint256[2][2] calldata pB, uint256[2] calldata pC, - uint256[19] calldata pubSignals + uint256[20] calldata pubSignals ) external view returns (bool); } @@ -450,7 +450,7 @@ contract IdentityRegistryKycImplV1 is IdentityRegistryKycStorageV1, IIdentityReg uint256[2] calldata pA, uint256[2][2] calldata pB, uint256[2] calldata pC, - uint256[19] calldata pubSignals + uint256[20] calldata pubSignals ) external onlyProxy onlyTEE { // Check if the proof is valid if (!IGCPJWTVerifier(_gcpJwtVerifier).verifyProof(pA, pB, pC, pubSignals)) revert INVALID_PROOF(); @@ -459,19 +459,19 @@ contract IdentityRegistryKycImplV1 is IdentityRegistryKycStorageV1, IIdentityReg if (pubSignals[0] != _gcpRootCAPubkeyHash) revert INVALID_ROOT_CA(); // Check if the TEE image hash is valid - bytes memory imageHash = GCPJWTHelper.unpackAndConvertImageHash(pubSignals[4], pubSignals[5], pubSignals[6]); + bytes memory imageHash = GCPJWTHelper.unpackAndConvertImageHash(pubSignals[5], pubSignals[6], pubSignals[7]); if (!IPCR0Manager(_PCR0Manager).isPCR0Set(imageHash)) revert INVALID_IMAGE(); // Unpack the pubkey and register it uint256 pubkeyCommitment = GCPJWTHelper.unpackAndDecodeHexPubkey(pubSignals[1], pubSignals[2], pubSignals[3]); _isRegisteredPubkeyCommitment[pubkeyCommitment] = true; - uint256 currentYear = 2000 + pubSignals[7] * 10 + pubSignals[8]; - uint256 currentMonth = pubSignals[9] * 10 + pubSignals[10]; - uint256 currentDay = pubSignals[11] * 10 + pubSignals[12]; - uint256 currentHour = pubSignals[13] * 10 + pubSignals[14]; - uint256 currentMinute = pubSignals[15] * 10 + pubSignals[16]; - uint256 currentSecond = pubSignals[17] * 10 + pubSignals[18]; + uint256 currentYear = 2000 + pubSignals[8] * 10 + pubSignals[9]; + uint256 currentMonth = pubSignals[10] * 10 + pubSignals[11]; + uint256 currentDay = pubSignals[12] * 10 + pubSignals[13]; + uint256 currentHour = pubSignals[14] * 10 + pubSignals[15]; + uint256 currentMinute = pubSignals[16] * 10 + pubSignals[17]; + uint256 currentSecond = pubSignals[18] * 10 + pubSignals[19]; uint256 currentTimestamp = Formatter.toTimeStampWithSeconds( currentYear, currentMonth, diff --git a/contracts/contracts/tests/MockGCPJWTVerifier.sol b/contracts/contracts/tests/MockGCPJWTVerifier.sol index 3183233521..81708a1857 100644 --- a/contracts/contracts/tests/MockGCPJWTVerifier.sol +++ b/contracts/contracts/tests/MockGCPJWTVerifier.sol @@ -29,7 +29,7 @@ contract MockGCPJWTVerifier { uint256[2] calldata pA, uint256[2][2] calldata pB, uint256[2] calldata pC, - uint256[19] calldata pubSignals + uint256[20] calldata pubSignals ) external view returns (bool) { // Silence unused variable warnings pA; diff --git a/contracts/test/v2/registerKyc.test.ts b/contracts/test/v2/registerKyc.test.ts index 96e3775bc3..42db8ba30e 100644 --- a/contracts/test/v2/registerKyc.test.ts +++ b/contracts/test/v2/registerKyc.test.ts @@ -128,6 +128,7 @@ describe("KYC Registration test", function () { p0, p1, p2, + 0n, testImageHash.p0, testImageHash.p1, testImageHash.p2, @@ -242,6 +243,7 @@ describe("KYC Registration test", function () { 1n, 2n, 3n, + 0n, 4n, 5n, 6n, @@ -273,6 +275,7 @@ describe("KYC Registration test", function () { 1n, 2n, 3n, + 0n, 4n, 5n, 6n, @@ -322,6 +325,7 @@ describe("KYC Registration test", function () { 1n, 2n, 3n, + 0n, 4n, 5n, 6n, @@ -356,6 +360,7 @@ describe("KYC Registration test", function () { p0, p1, p2, + 0n, 177384435506496807268973340845468654286294928521500580044819492874465981028n, 175298970718174405520284770870231222447414486446296682893283627688949855078n, 13360n, @@ -379,6 +384,7 @@ describe("KYC Registration test", function () { p0, p1, p2, + 0n, 177384435506496807268973340845468654286294928521500580044819492874465981028n, 175298970718174405520284770870231222447414486446296682893283627688949855078n, 13360n, @@ -417,6 +423,7 @@ describe("KYC Registration test", function () { 1n, 2n, 3n, + 0n, 4n, 5n, 6n, @@ -434,6 +441,7 @@ describe("KYC Registration test", function () { 1n, 2n, 3n, + 0n, 4n, 5n, 6n, @@ -451,6 +459,7 @@ describe("KYC Registration test", function () { 1n, 2n, 3n, + 0n, 4n, 5n, 6n, From a6c84d80f7a3e58905ec367cb9e25b576b8a0655 Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Fri, 30 Jan 2026 18:35:32 +0100 Subject: [PATCH 5/7] feat(kyc): register fcm token for sumsub verification (#1673) * feat(kyc): register fcm token for sumsub verification * fix tests * remove unused import * fix lint --- app/env.ts | 2 +- app/src/navigation/index.tsx | 7 +- app/src/providers/selfClientProvider.tsx | 4 +- app/src/screens/kyc/KycSuccessScreen.tsx | 47 ++++- .../notifications/notificationService.ts | 5 + .../src/screens/kyc/KycSuccessScreen.test.tsx | 196 +++++++++++++++++- 6 files changed, 240 insertions(+), 21 deletions(-) diff --git a/app/env.ts b/app/env.ts index c5e041280e..52eacf5063 100644 --- a/app/env.ts +++ b/app/env.ts @@ -27,6 +27,7 @@ export const IS_TEST_BUILD = process.env.IS_TEST_BUILD === 'true'; export const MIXPANEL_NFC_PROJECT_TOKEN = undefined; export const SEGMENT_KEY = process.env.SEGMENT_KEY; +export const SELF_UUID_NAMESPACE = process.env.SELF_UUID_NAMESPACE; export const SENTRY_DSN = process.env.SENTRY_DSN; export const SUMSUB_TEE_URL = process.env.SUMSUB_TEE_URL || 'http://localhost:8080'; @@ -34,6 +35,5 @@ export const SUMSUB_TEST_TOKEN = process.env.SUMSUB_TEST_TOKEN; export const TURNKEY_AUTH_PROXY_CONFIG_ID = process.env.TURNKEY_AUTH_PROXY_CONFIG_ID; - export const TURNKEY_GOOGLE_CLIENT_ID = process.env.TURNKEY_GOOGLE_CLIENT_ID; export const TURNKEY_ORGANIZATION_ID = process.env.TURNKEY_ORGANIZATION_ID; diff --git a/app/src/navigation/index.tsx b/app/src/navigation/index.tsx index 17f90209bc..d7dfb4ed56 100644 --- a/app/src/navigation/index.tsx +++ b/app/src/navigation/index.tsx @@ -79,6 +79,7 @@ export type RootStackParamList = Omit< | 'Home' | 'IDPicker' | 'IdDetails' + | 'KycSuccess' | 'RegistrationFallback' | 'Loading' | 'Modal' @@ -201,7 +202,11 @@ export type RootStackParamList = Omit< // Onboarding screens Disclaimer: undefined; - KycSuccess: undefined; + KycSuccess: + | { + userId?: string; + } + | undefined; // Dev screens CreateMock: undefined; diff --git a/app/src/providers/selfClientProvider.tsx b/app/src/providers/selfClientProvider.tsx index 28f9abe5fa..6bfbc9c2e4 100644 --- a/app/src/providers/selfClientProvider.tsx +++ b/app/src/providers/selfClientProvider.tsx @@ -380,7 +380,9 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => { // Success case: navigate to KYC success screen if (navigationRef.isReady()) { - navigationRef.navigate('KycSuccess'); + navigationRef.navigate('KycSuccess', { + userId: accessToken.userId, + }); } } catch (error) { const safeInitError = sanitizeErrorMessage( diff --git a/app/src/screens/kyc/KycSuccessScreen.tsx b/app/src/screens/kyc/KycSuccessScreen.tsx index 22849692cd..3269da141e 100644 --- a/app/src/screens/kyc/KycSuccessScreen.tsx +++ b/app/src/screens/kyc/KycSuccessScreen.tsx @@ -2,37 +2,70 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. -import React from 'react'; +import React, { useCallback } from 'react'; import { StyleSheet, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { YStack } from 'tamagui'; +import { v5 as uuidv5 } from 'uuid'; +import type { StaticScreenProps } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { DelayedLottieView } from '@selfxyz/mobile-sdk-alpha'; +import { DelayedLottieView, useSelfClient } from '@selfxyz/mobile-sdk-alpha'; import loadingAnimation from '@selfxyz/mobile-sdk-alpha/animations/loading/misc.json'; import { AbstractButton, Description, Title, } from '@selfxyz/mobile-sdk-alpha/components'; +import { ProofEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { black, white } from '@selfxyz/mobile-sdk-alpha/constants/colors'; import { buttonTap } from '@/integrations/haptics'; import type { RootStackParamList } from '@/navigation'; -import { requestNotificationPermission } from '@/services/notifications/notificationService'; +import { + getFCMToken, + getSelfUuidNamespace, + registerDeviceToken, + requestNotificationPermission, +} from '@/services/notifications/notificationService'; +import { useSettingStore } from '@/stores/settingStore'; + +type KycSuccessRouteParams = StaticScreenProps< + | { + userId?: string; + } + | undefined +>; -const KycSuccessScreen: React.FC = () => { +const KycSuccessScreen: React.FC = ({ + route: { params }, +}) => { const navigation = useNavigation>(); + const userId = params?.userId; const insets = useSafeAreaInsets(); + const setFcmToken = useSettingStore(state => state.setFcmToken); + const selfClient = useSelfClient(); + const { trackEvent } = selfClient; - const handleReceiveUpdates = async () => { + const handleReceiveUpdates = useCallback(async () => { buttonTap(); - await requestNotificationPermission(); + + if ((await requestNotificationPermission()) && userId) { + const token = await getFCMToken(); + if (token) { + setFcmToken(token); + trackEvent(ProofEvents.FCM_TOKEN_STORED); + + const sessionId = uuidv5(userId, getSelfUuidNamespace()); + await registerDeviceToken(sessionId, token); + } + } + // Navigate to Home regardless of permission result navigation.navigate('Home', {}); - }; + }, [navigation, setFcmToken, trackEvent, userId]); const handleCheckLater = () => { buttonTap(); diff --git a/app/src/services/notifications/notificationService.ts b/app/src/services/notifications/notificationService.ts index 87fe09ec60..159201e341 100644 --- a/app/src/services/notifications/notificationService.ts +++ b/app/src/services/notifications/notificationService.ts @@ -3,6 +3,7 @@ // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. import { PermissionsAndroid, Platform } from 'react-native'; +import { SELF_UUID_NAMESPACE } from '@env'; import type { FirebaseMessagingTypes } from '@react-native-firebase/messaging'; import messaging from '@react-native-firebase/messaging'; @@ -36,6 +37,10 @@ const error = (...args: unknown[]) => { if (!isTestEnv) console.error(...args); }; +export function getSelfUuidNamespace(): string { + return SELF_UUID_NAMESPACE ?? ''; +} + export { getStateMessage }; export async function isNotificationSystemReady(): Promise<{ diff --git a/app/tests/src/screens/kyc/KycSuccessScreen.test.tsx b/app/tests/src/screens/kyc/KycSuccessScreen.test.tsx index ea8cc6c72d..48d37a7d72 100644 --- a/app/tests/src/screens/kyc/KycSuccessScreen.test.tsx +++ b/app/tests/src/screens/kyc/KycSuccessScreen.test.tsx @@ -3,8 +3,9 @@ // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. import React from 'react'; +import { v5 as uuidv5 } from 'uuid'; import { useNavigation } from '@react-navigation/native'; -import { render } from '@testing-library/react-native'; +import { fireEvent, render, waitFor } from '@testing-library/react-native'; import ErrorBoundary from '@/components/ErrorBoundary'; import KycSuccessScreen from '@/screens/kyc/KycSuccessScreen'; @@ -46,10 +47,6 @@ jest.mock('tamagui', () => ({ Text: ({ children, ...props }: any) => {children}, })); -jest.mock('@selfxyz/mobile-sdk-alpha', () => ({ - DelayedLottieView: () => null, -})); - jest.mock('@selfxyz/mobile-sdk-alpha/constants/colors', () => ({ black: '#000000', white: '#FFFFFF', @@ -57,17 +54,17 @@ jest.mock('@selfxyz/mobile-sdk-alpha/constants/colors', () => ({ jest.mock('@selfxyz/mobile-sdk-alpha/components', () => ({ AbstractButton: ({ children, onPress }: any) => ( - ), PrimaryButton: ({ children, onPress }: any) => ( - ), SecondaryButton: ({ children, onPress }: any) => ( - ), @@ -77,12 +74,23 @@ jest.mock('@selfxyz/mobile-sdk-alpha/components', () => ({ ), })); +jest.mock('@selfxyz/mobile-sdk-alpha/constants/analytics', () => ({ + ProofEvents: { + FCM_TOKEN_STORED: 'FCM_TOKEN_STORED', + }, +})); + +jest.mock('@selfxyz/mobile-sdk-alpha/animations/loading/misc.json', () => ({})); + jest.mock('@/integrations/haptics', () => ({ buttonTap: jest.fn(), })); jest.mock('@/services/notifications/notificationService', () => ({ requestNotificationPermission: jest.fn(), + getFCMToken: jest.fn(), + registerDeviceToken: jest.fn(), + getSelfUuidNamespace: jest.fn(() => '1eebc0f5-eee9-45a4-9474-a0d103b9f20c'), })); jest.mock('@/config/sentry', () => ({ @@ -94,12 +102,36 @@ jest.mock('@/services/analytics', () => ({ trackNfcEvent: jest.fn(), })); +jest.mock('@selfxyz/mobile-sdk-alpha', () => ({ + DelayedLottieView: () => null, + useSelfClient: jest.fn(), +})); + +jest.mock('@/stores/settingStore', () => ({ + useSettingStore: jest.fn(), +})); + const mockUseNavigation = useNavigation as jest.MockedFunction< typeof useNavigation >; +// Import mocked modules +const { useSelfClient } = jest.requireMock('@selfxyz/mobile-sdk-alpha'); +const { useSettingStore } = jest.requireMock('@/stores/settingStore'); + +const MOCK_SELF_UUID_NAMESPACE = '1eebc0f5-eee9-45a4-9474-a0d103b9f20c'; + describe('KycSuccessScreen', () => { const mockNavigate = jest.fn(); + const mockTrackEvent = jest.fn(); + const mockSetFcmToken = jest.fn(); + const mockUserId = '19f21362-856a-4606-88e1-fa306036978f'; + const mockFcmToken = 'mock-fcm-token'; + const mockRoute = { + params: { + userId: mockUserId, + }, + }; beforeEach(() => { jest.clearAllMocks(); @@ -107,23 +139,165 @@ describe('KycSuccessScreen', () => { mockUseNavigation.mockReturnValue({ navigate: mockNavigate, } as any); + + useSelfClient.mockReturnValue({ + trackEvent: mockTrackEvent, + }); + + useSettingStore.mockReturnValue(mockSetFcmToken); + + ( + notificationService.requestNotificationPermission as jest.Mock + ).mockResolvedValue(true); + (notificationService.getFCMToken as jest.Mock).mockResolvedValue( + mockFcmToken, + ); + (notificationService.registerDeviceToken as jest.Mock).mockResolvedValue( + undefined, + ); }); it('should render the screen without errors', () => { - const { root } = render(); + const { root } = render(); expect(root).toBeTruthy(); }); it('should have navigation available', () => { - render(); + render(); expect(mockUseNavigation).toHaveBeenCalled(); }); it('should have notification service available', () => { - render(); + render(); expect(notificationService.requestNotificationPermission).toBeDefined(); }); + it('should fetch and register FCM token when "Receive live updates" is pressed', async () => { + const { root } = render(); + + const buttons = root.findAllByType('button'); + const receiveUpdatesButton = buttons[0]; // First button is "Receive live updates" + fireEvent.press(receiveUpdatesButton); + + await waitFor(() => { + // Verify notification permission was requested + expect( + notificationService.requestNotificationPermission, + ).toHaveBeenCalledTimes(1); + }); + + await waitFor(() => { + // Verify FCM token was fetched + expect(notificationService.getFCMToken).toHaveBeenCalledTimes(1); + }); + + await waitFor(() => { + // Verify FCM token was stored in settings store + expect(mockSetFcmToken).toHaveBeenCalledWith(mockFcmToken); + }); + + await waitFor(() => { + // Verify tracking event was sent + expect(mockTrackEvent).toHaveBeenCalledWith('FCM_TOKEN_STORED'); + }); + + await waitFor(() => { + // Verify device token was registered with deterministic session ID + expect(notificationService.registerDeviceToken).toHaveBeenCalledWith( + uuidv5(mockUserId, MOCK_SELF_UUID_NAMESPACE), + mockFcmToken, + ); + }); + + await waitFor(() => { + // Verify navigation to Home screen + expect(mockNavigate).toHaveBeenCalledWith('Home', {}); + }); + }); + + it('should navigate to Home without FCM token when permission is denied', async () => { + ( + notificationService.requestNotificationPermission as jest.Mock + ).mockResolvedValue(false); + + const { root } = render(); + + const buttons = root.findAllByType('button'); + const receiveUpdatesButton = buttons[0]; // First button is "Receive live updates" + fireEvent.press(receiveUpdatesButton); + + await waitFor(() => { + // Verify notification permission was requested + expect( + notificationService.requestNotificationPermission, + ).toHaveBeenCalledTimes(1); + }); + + // Verify FCM token was NOT fetched + expect(notificationService.getFCMToken).not.toHaveBeenCalled(); + + // Verify FCM token was NOT stored + expect(mockSetFcmToken).not.toHaveBeenCalled(); + + // Verify device token was NOT registered + expect(notificationService.registerDeviceToken).not.toHaveBeenCalled(); + + await waitFor(() => { + // Verify navigation to Home screen still happens + expect(mockNavigate).toHaveBeenCalledWith('Home', {}); + }); + }); + + it('should navigate to Home when "I will check back later" is pressed', () => { + const { root } = render(); + + const buttons = root.findAllByType('button'); + const checkLaterButton = buttons[1]; // Second button is "I will check back later" + fireEvent.press(checkLaterButton); + + // Verify navigation to Home screen + expect(mockNavigate).toHaveBeenCalledWith('Home', {}); + + // Verify FCM-related functions were NOT called + expect( + notificationService.requestNotificationPermission, + ).not.toHaveBeenCalled(); + expect(notificationService.getFCMToken).not.toHaveBeenCalled(); + expect(mockSetFcmToken).not.toHaveBeenCalled(); + expect(notificationService.registerDeviceToken).not.toHaveBeenCalled(); + }); + + it('should handle missing userId gracefully', async () => { + const routeWithoutUserId = { + params: {}, + }; + + ( + notificationService.requestNotificationPermission as jest.Mock + ).mockResolvedValue(true); + + const { root } = render(); + + const buttons = root.findAllByType('button'); + const receiveUpdatesButton = buttons[0]; // First button is "Receive live updates" + fireEvent.press(receiveUpdatesButton); + + await waitFor(() => { + // Verify notification permission was requested + expect( + notificationService.requestNotificationPermission, + ).toHaveBeenCalledTimes(1); + }); + + // Verify FCM token was NOT fetched (no userId) + expect(notificationService.getFCMToken).not.toHaveBeenCalled(); + + await waitFor(() => { + // Verify navigation to Home screen still happens + expect(mockNavigate).toHaveBeenCalledWith('Home', {}); + }); + }); + it('renders fallback on render error', () => { // Mock console.error to suppress error boundary error logs during test const consoleErrorSpy = jest From a96777d80a43b1dbfc8e415f91def378a537591f Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 30 Jan 2026 10:25:51 -0800 Subject: [PATCH 6/7] ux: add country document json info as static asset (#1670) * add country document json info as static asset * add staleness test * update test * formatting --- .../src/data/country-document-types.json | 256 ++++++++++++++++++ .../src/documents/useCountries.tsx | 37 +-- .../country-data-sync.integration.test.ts | 99 +++++++ 3 files changed, 361 insertions(+), 31 deletions(-) create mode 100644 packages/mobile-sdk-alpha/src/data/country-document-types.json create mode 100644 packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts diff --git a/packages/mobile-sdk-alpha/src/data/country-document-types.json b/packages/mobile-sdk-alpha/src/data/country-document-types.json new file mode 100644 index 0000000000..a535f1829a --- /dev/null +++ b/packages/mobile-sdk-alpha/src/data/country-document-types.json @@ -0,0 +1,256 @@ +{ + "ABW": ["p", "i"], + "AFG": ["p"], + "AGO": ["p", "i"], + "AIA": ["p", "i"], + "ALA": ["p", "i"], + "ALB": ["p", "i"], + "AND": ["p", "i"], + "ARE": ["p", "i"], + "ARG": ["p", "i"], + "ARM": ["p", "i"], + "ASM": ["p", "i"], + "ATA": ["p", "i"], + "ATF": ["p", "i"], + "ATG": ["p", "i"], + "AUS": ["p", "i"], + "AUT": ["p", "i"], + "AZE": ["p", "i"], + "BDI": ["p", "i"], + "BEL": ["p", "i"], + "BEN": ["p", "i"], + "BES": ["p", "i"], + "BFA": ["p", "i"], + "BGD": ["p", "i"], + "BGR": ["p", "i"], + "BHR": ["p", "i"], + "BHS": ["p", "i"], + "BIH": ["p", "i"], + "BLM": ["p", "i"], + "BLR": ["p", "i"], + "BLZ": ["p", "i"], + "BMU": ["p", "i"], + "BOL": ["p", "i"], + "BRA": ["p", "i"], + "BRB": ["p", "i"], + "BRN": ["p", "i"], + "BTN": ["p", "i"], + "BVT": ["p", "i"], + "BWA": ["p", "i"], + "CAF": ["p", "i"], + "CAN": ["p", "i"], + "CCK": ["p", "i"], + "CHE": ["p", "i"], + "CHL": ["p", "i"], + "CHN": ["p", "i"], + "CIV": ["p", "i"], + "CMR": ["p", "i"], + "COD": ["p", "i"], + "COG": ["p", "i"], + "COK": ["p", "i"], + "COL": ["p", "i"], + "COM": ["p", "i"], + "CPV": ["p", "i"], + "CRI": ["p", "i"], + "CUB": ["p", "i"], + "CUW": ["p", "i"], + "CXR": ["p", "i"], + "CYM": ["p", "i"], + "CYP": ["p", "i"], + "CZE": ["p", "i"], + "D<<": ["p", "i"], + "DJI": ["p", "i"], + "DMA": ["p", "i"], + "DNK": ["p", "i"], + "DOM": ["p", "i"], + "DZA": ["p", "i"], + "ECU": ["p", "i"], + "EGY": [], + "ERI": ["p", "i"], + "ESH": ["p", "i"], + "ESP": ["p", "i"], + "EST": ["p", "i"], + "ETH": ["p", "i"], + "EUE": ["p", "i"], + "FIN": ["p", "i"], + "FJI": ["p", "i"], + "FLK": ["p", "i"], + "FRA": ["p", "i"], + "FRO": ["p", "i"], + "FSM": ["p", "i"], + "GAB": ["p", "i"], + "GBR": ["p", "i"], + "GEO": ["p", "i"], + "GGY": ["p", "i"], + "GHA": ["p", "i"], + "GIB": ["p", "i"], + "GIN": ["p", "i"], + "GLP": ["p", "i"], + "GMB": ["p", "i"], + "GNB": ["p", "i"], + "GNQ": ["p", "i"], + "GRC": ["p", "i"], + "GRD": ["p", "i"], + "GRL": ["p", "i"], + "GTM": ["p", "i"], + "GUF": ["p", "i"], + "GUM": ["p", "i"], + "GUY": ["p", "i"], + "HKG": ["p", "i"], + "HMD": ["p", "i"], + "HND": ["p", "i"], + "HRV": ["p", "i"], + "HTI": ["p", "i"], + "HUN": ["p", "i"], + "IDN": ["p", "i"], + "IMN": ["p", "i"], + "IND": ["p", "a"], + "IOT": ["p", "i"], + "IRL": ["p", "i"], + "IRN": ["p", "i"], + "IRQ": ["p", "i"], + "ISL": ["p", "i"], + "ISR": ["p", "i"], + "ITA": ["p", "i"], + "JAM": ["p", "i"], + "JEY": ["p", "i"], + "JOR": ["p", "i"], + "JPN": ["p", "i"], + "KAZ": ["p", "i"], + "KEN": ["p", "i"], + "KGZ": ["p", "i"], + "KHM": ["p", "i"], + "KIR": ["p", "i"], + "KNA": ["p", "i"], + "KOR": ["p", "i"], + "KWT": ["p", "i"], + "LAO": ["p", "i"], + "LBN": ["p", "i"], + "LBR": ["p", "i"], + "LBY": ["p", "i"], + "LCA": ["p", "i"], + "LIE": ["p", "i"], + "LKA": ["p", "i"], + "LSO": ["p", "i"], + "LTU": ["p", "i"], + "LUX": ["p", "i"], + "LVA": ["p", "i"], + "MAC": ["p", "i"], + "MAF": ["p", "i"], + "MAR": ["p", "i"], + "MCO": ["p", "i"], + "MDA": ["p", "i"], + "MDG": ["p", "i"], + "MDV": ["p", "i"], + "MEX": ["p", "i"], + "MHL": ["p", "i"], + "MKD": ["p", "i"], + "MLI": ["p", "i"], + "MLT": ["p", "i"], + "MMR": ["p", "i"], + "MNE": ["p", "i"], + "MNG": ["p", "i"], + "MNP": ["p", "i"], + "MOZ": ["p", "i"], + "MRT": ["p", "i"], + "MSR": ["p", "i"], + "MTQ": ["p", "i"], + "MUS": ["p", "i"], + "MWI": ["p", "i"], + "MYS": ["p", "i"], + "MYT": ["p", "i"], + "NAM": ["p", "i"], + "NCL": ["p", "i"], + "NER": ["p", "i"], + "NFK": ["p", "i"], + "NGA": ["p", "i"], + "NIC": ["p", "i"], + "NIU": ["p", "i"], + "NLD": ["p", "i"], + "NOR": ["p", "i"], + "NPL": ["p", "i"], + "NRU": ["p", "i"], + "NZL": ["p", "i"], + "OMN": ["p", "i"], + "PAK": ["p", "i"], + "PAN": ["p", "i"], + "PCN": ["p", "i"], + "PER": ["p", "i"], + "PHL": ["p", "i"], + "PLW": ["p", "i"], + "PNG": ["p", "i"], + "POL": ["p", "i"], + "PRI": ["p", "i"], + "PRK": ["p", "i"], + "PRT": ["p", "i"], + "PRY": ["p", "i"], + "PSE": ["p", "i"], + "PYF": ["p", "i"], + "QAT": ["p", "i"], + "REU": ["p", "i"], + "ROU": ["p", "i"], + "RUS": ["p", "i"], + "RWA": ["p", "i"], + "SAU": ["p", "i"], + "SDN": ["p", "i"], + "SEN": ["p", "i"], + "SGP": ["p", "i"], + "SGS": ["p", "i"], + "SHN": ["p", "i"], + "SJM": ["p", "i"], + "SLB": ["p", "i"], + "SLE": ["p", "i"], + "SLV": ["p", "i"], + "SMR": ["p", "i"], + "SOM": ["p", "i"], + "SPM": ["p", "i"], + "SRB": ["p", "i"], + "SSD": ["p", "i"], + "STP": ["p", "i"], + "SUR": ["p", "i"], + "SVK": ["p", "i"], + "SVN": ["p", "i"], + "SWE": ["p", "i"], + "SWZ": ["p", "i"], + "SXM": ["p", "i"], + "SYC": ["p", "i"], + "SYR": ["p", "i"], + "TCA": ["p", "i"], + "TCD": ["p", "i"], + "TGO": ["p", "i"], + "THA": ["p", "i"], + "TJK": ["p", "i"], + "TKL": ["p", "i"], + "TKM": ["p", "i"], + "TLS": ["p", "i"], + "TON": ["p", "i"], + "TTO": ["p", "i"], + "TUN": ["p", "i"], + "TUR": ["p", "i"], + "TUV": ["p", "i"], + "TWN": ["p", "i"], + "TZA": ["p", "i"], + "UGA": ["p", "i"], + "UKR": ["p", "i"], + "UMI": ["p", "i"], + "UNO": ["p", "i"], + "URY": ["p", "i"], + "USA": ["p", "i"], + "UZB": ["p", "i"], + "VAT": ["p", "i"], + "VCT": ["p", "i"], + "VEN": ["p", "i"], + "VGB": ["p", "i"], + "VIR": ["p", "i"], + "VNM": ["p", "i"], + "VUT": ["p", "i"], + "WLF": ["p", "i"], + "WSM": ["p", "i"], + "XCE": ["p", "i"], + "XOM": ["p", "i"], + "XPO": ["p", "i"], + "YEM": ["p", "i"], + "ZAF": ["p", "i"], + "ZMB": ["p", "i"], + "ZWE": ["p", "i"] +} diff --git a/packages/mobile-sdk-alpha/src/documents/useCountries.tsx b/packages/mobile-sdk-alpha/src/documents/useCountries.tsx index f8adae9e2a..67acccc00d 100644 --- a/packages/mobile-sdk-alpha/src/documents/useCountries.tsx +++ b/packages/mobile-sdk-alpha/src/documents/useCountries.tsx @@ -2,12 +2,14 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. -import { useEffect, useMemo, useState } from 'react'; +import { useMemo } from 'react'; import { getCountry } from 'react-native-localize'; import { commonNames } from '@selfxyz/common'; import { alpha2ToAlpha3 } from '@selfxyz/common/constants/countries'; +import countryDocumentTypesData from '../data/country-document-types.json'; + export interface CountryData { [countryCode: string]: string[]; } @@ -29,38 +31,11 @@ function getUserCountryCode(): string | null { } return null; } + export function useCountries() { - const [countryData, setCountryData] = useState({}); - const [loading, setLoading] = useState(true); + const countryData = countryDocumentTypesData as CountryData; const userCountryCode = useMemo(getUserCountryCode, []); - useEffect(() => { - const controller = new AbortController(); - const fetchCountryData = async () => { - try { - const response = await fetch('https://api.staging.self.xyz/id-picker', { - signal: controller.signal, - }); - const result = await response.json(); - - if (result.status === 'success') { - setCountryData(result.data); - // if (__DEV__) { - // console.log('Set country data:', result.data); - // } - } else { - console.error('API returned non-success status:', result.status); - } - } catch (error) { - console.error('Error fetching country data:', error); - } finally { - setLoading(false); - } - }; - fetchCountryData(); - return () => controller.abort(); - }, []); - const countryList = useMemo(() => { const allCountries = Object.keys(countryData).map(countryCode => ({ key: countryCode, @@ -77,5 +52,5 @@ export function useCountries() { const showSuggestion = userCountryCode && countryData[userCountryCode]; - return { countryData, countryList, loading, userCountryCode, showSuggestion }; + return { countryData, countryList, loading: false, userCountryCode, showSuggestion }; } diff --git a/packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts b/packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts new file mode 100644 index 0000000000..76be2be586 --- /dev/null +++ b/packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +/** + * Integration test for country data synchronization. + * + * This test verifies that the bundled country-document-types.json matches + * the staging API response. It gracefully skips when network is unavailable + * to avoid CI flakiness from transient network issues. + * + * To run integration tests only: yarn test --grep="integration" + * To skip integration tests: yarn test --grep="^(?!.*integration)" + */ + +import { describe, expect, it } from 'vitest'; + +import countryDocumentTypesData from '../../src/data/country-document-types.json'; + +/** + * Helper to check if an error is a network-related error that should cause + * the test to skip rather than fail. + */ +function isNetworkError(error: unknown): boolean { + if (!(error instanceof Error)) return false; + + const networkErrorPatterns = [ + 'ENOTFOUND', // DNS resolution failed + 'ECONNREFUSED', // Connection refused + 'ECONNRESET', // Connection reset + 'ETIMEDOUT', // Connection timed out + 'EAI_AGAIN', // DNS temporary failure + 'ENETUNREACH', // Network unreachable + 'EHOSTUNREACH', // Host unreachable + 'fetch failed', // Generic fetch failure + 'network', // Generic network error + 'AbortError', // Request aborted (timeout) + ]; + + const errorMessage = error.message.toLowerCase(); + const errorName = error.name; + + return networkErrorPatterns.some( + pattern => + errorMessage.includes(pattern.toLowerCase()) || + errorName === pattern || + ('cause' in error && + error.cause instanceof Error && + error.cause.message.toLowerCase().includes(pattern.toLowerCase())), + ); +} + +describe('Country data synchronization [integration]', () => { + it('bundled data should match API response', async ({ skip }) => { + // Fetch current data from staging API with timeout + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + let response: Response; + try { + response = await fetch('https://api.staging.self.xyz/id-picker', { + signal: controller.signal, + }); + } catch (error) { + // Network errors should skip the test, not fail it + if (isNetworkError(error)) { + skip(); + return; + } + throw error; + } finally { + clearTimeout(timeoutId); + } + + // Non-2xx responses that aren't network errors should also skip + // (e.g., 503 Service Unavailable, 502 Bad Gateway) + if (!response.ok) { + if (response.status >= 500) { + skip(); + return; + } + // 4xx errors are likely real issues, so we let them fail + expect.fail(`API returned ${response.status}: ${response.statusText}`); + } + + const result = await response.json(); + expect(result.status).toBe('success'); + + const apiData = result.data; + const bundledData = countryDocumentTypesData; + + // Compare the data structures + expect(bundledData).toEqual(apiData); + + // If this test fails, it means the API has been updated with new countries + // or document types that aren't in the bundled data yet. + // To fix: Update src/data/country-document-types.json with the latest API data. + }, 10000); // 10s Vitest timeout +}); From 368799378078b4b18a58e1574b29c88a1c159eef Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 30 Jan 2026 13:06:08 -0800 Subject: [PATCH 7/7] fix failing test (#1677) --- .github/workflows/mobile-sdk-demo-e2e.yml | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/mobile-sdk-demo-e2e.yml b/.github/workflows/mobile-sdk-demo-e2e.yml index bc179734ab..c90b94af15 100644 --- a/.github/workflows/mobile-sdk-demo-e2e.yml +++ b/.github/workflows/mobile-sdk-demo-e2e.yml @@ -459,6 +459,37 @@ jobs: FORCE_BUNDLING=1 RCT_NO_LAUNCH_PACKAGER=1 \ xcodebuild -workspace "$WORKSPACE_PATH" -scheme ${{ env.IOS_PROJECT_SCHEME }} -configuration Debug -destination "id=${{ env.IOS_SIMULATOR_ID }}" -derivedDataPath packages/mobile-sdk-demo/ios/build -jobs "$(sysctl -n hw.ncpu)" -parallelizeTargets -quiet COMPILER_INDEX_STORE_ENABLE=NO ONLY_ACTIVE_ARCH=YES SWIFT_COMPILATION_MODE=wholemodule || { echo "❌ iOS build failed"; exit 1; } echo "✅ iOS build succeeded" + - name: Build iOS Release Archive (unsigned) + run: | + echo "Building iOS Release archive (unsigned) to validate Release configuration..." + WORKSPACE_PATH="${{ env.IOS_WORKSPACE_PATH }}" + + FORCE_BUNDLING=1 RCT_NO_LAUNCH_PACKAGER=1 \ + xcodebuild archive \ + -workspace "$WORKSPACE_PATH" \ + -scheme ${{ env.IOS_PROJECT_SCHEME }} \ + -configuration Release \ + -archivePath packages/mobile-sdk-demo/ios/build/SelfDemoApp.xcarchive \ + -destination "generic/platform=iOS" \ + -jobs "$(sysctl -n hw.ncpu)" \ + -parallelizeTargets \ + -quiet \ + COMPILER_INDEX_STORE_ENABLE=NO \ + SWIFT_COMPILATION_MODE=wholemodule \ + CODE_SIGN_IDENTITY="" \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGNING_ALLOWED=NO \ + AD_HOC_CODE_SIGNING_ALLOWED=NO \ + || { echo "❌ iOS Release archive build failed"; exit 1; } + echo "✅ iOS Release archive build succeeded (unsigned)" + + # Verify archive was created + if [ -d "packages/mobile-sdk-demo/ios/build/SelfDemoApp.xcarchive" ]; then + echo "📦 Archive created at packages/mobile-sdk-demo/ios/build/SelfDemoApp.xcarchive" + else + echo "❌ Archive not found" + exit 1 + fi - name: Install and Test on iOS run: | echo "Installing app on simulator..."