Update ws merged (#90)

Co-authored-by: thomas-senechal <thomas.senechal@pm.me>
This commit is contained in:
turnoffthiscomputer
2025-02-13 04:23:51 +01:00
committed by GitHub
parent 6d4c211c51
commit 54ffcb69e4
36 changed files with 907 additions and 2609 deletions

1
sdk/core/.yarnrc.yml Normal file
View File

@@ -0,0 +1 @@
nodeLinker: node-modules

View File

@@ -2,11 +2,13 @@ import { SelfVerifierReport } from './src/SelfVerifierReport';
import { SelfVerifier } from './src/SelfVerifier';
import { countryCodes } from '../../common/src/constants/constants';
import {
OpenPassportAttestation,
OpenPassportDynamicAttestation,
SelfAttestation,
// OpenPassportDynamicAttestation,
} from '../../common/src/utils/selfAttestation';
export {
SelfVerifier,
SelfVerifierReport,
countryCodes,
SelfAttestation,
};

View File

@@ -1,34 +1,14 @@
import {
countryNames,
} from '../../../common/src/constants/constants';
import type { SelfAttestation } from '../../../common/src/utils/selfAttestation';
import {
parsePublicSignalsDisclose,
} from '../../../common/src/utils/selfAttestation';
import {
formatForbiddenCountriesListFromCircuitOutput,
getAttributeFromUnpackedReveal,
unpackReveal,
} from '../../../common/src/utils/circuits/formatOutputs';
import { castToScope } from '../../../common/src/utils/circuits/uuid';
import { SelfVerifierReport } from './SelfVerifierReport';
import {
registryAbi
} from "./abi/IdentityRegistryImplV1";
import {
verifyAllAbi
} from "./abi/VerifyAll";
import { ethers } from 'ethers';
import { countryNames } from '../../../common/src/constants/constants';
import { SelfAttestation } from '../../../common/src/utils/selfAttestation';
import { SelfVerifierReport } from './SelfVerifierReport';
import { registryAbi } from './abi/IdentityRegistryImplV1';
import { verifyAllAbi } from './abi/VerifyAll';
// import type { VcAndDiscloseHubProofStruct } from "../../../common/src/utils/contracts/typechain-types/contracts/IdentityVerificationHubImplV1.sol/IdentityVerificationHubImplV1";
import {
groth16,
Groth16Proof,
PublicSignals
} from 'snarkjs';
import { groth16, Groth16Proof, PublicSignals } from 'snarkjs';
import { CIRCUIT_CONSTANTS, revealedDataTypes } from '../../../common/src/constants/constants';
export class AttestationVerifier {
protected devMode: boolean;
protected scope: string;
protected report: SelfVerifierReport;
@@ -61,11 +41,7 @@ export class AttestationVerifier {
}
public async verify(proof: Groth16Proof, publicSignals: PublicSignals): Promise<SelfAttestation> {
const solidityProof = await groth16.exportSolidityCallData(
proof,
publicSignals,
);
const solidityProof = await groth16.exportSolidityCallData(proof, publicSignals);
const vcAndDiscloseHubProof: any = {
olderThanEnabled: this.minimumAge.enabled,
@@ -73,8 +49,8 @@ export class AttestationVerifier {
forbiddenCountriesEnabled: this.excludedCountries.enabled,
forbiddenCountriesListPacked: BigInt(this.excludedCountries.value.length),
ofacEnabled: this.ofac,
vcAndDiscloseProof: solidityProof
}
vcAndDiscloseProof: solidityProof,
};
const result = await this.verifyAllContract.verifyAll(
this.targetRootTimestamp,
@@ -93,11 +69,11 @@ export class AttestationVerifier {
console.log(result);
const credentialSubject = {
userId: "",
userId: '',
application: this.scope,
merkle_root: "",
attestation_id: "",
current_date: "",
merkle_root: '',
attestation_id: '',
current_date: '',
issuing_state: result[0],
name: result[1],
passport_number: result[2],
@@ -108,7 +84,7 @@ export class AttestationVerifier {
older_than: result[7],
valid: result[8],
nullifier: publicSignals[CIRCUIT_CONSTANTS.REGISTER_NULLIFIER_INDEX],
}
};
const attestation: SelfAttestation = {
'@context': ['https://www.w3.org/2018/credentials/v1'],
@@ -117,18 +93,16 @@ export class AttestationVerifier {
issuanceDate: new Date().toISOString(),
credentialSubject: credentialSubject,
proof: {
type: "Groth16Proof",
verificationMethod: "Vc and Disclose",
type: 'Groth16Proof',
verificationMethod: 'Vc and Disclose',
value: {
proof: proof,
publicSignals: publicSignals,
},
vkey: "",
}
}
vkey: '',
},
};
return attestation;
}
}

View File

@@ -1,33 +1,27 @@
// import {
// ArgumentsDisclose,
// ArgumentsProveOffChain,
// ArgumentsProveOnChain,
// ArgumentsRegister,
// Mode,
// SelfAppPartial,
// SelfApp
// } from '../../../common/src/utils/appType';
import {
DEFAULT_RPC_URL,
countryNames,
} from '../../../common/src/constants/constants';
// import { UserIdType } from '../../../common/src/utils/circuits/uuid';
import { AttestationVerifier } from './AttestationVerifier';
export class SelfVerifier extends AttestationVerifier {
import msgpack from 'msgpack-lite';
import pako from 'pako';
import { DEFAULT_RPC_URL, TREE_TRACKER_URL, countryNames } from '../../../common/src/constants/constants';
import {
ArgumentsDisclose,
ArgumentsProveOffChain,
ArgumentsRegister,
DisclosureOptions,
SelfApp,
SelfAppPartial,
} from '../../../common/src/utils/appType';
import { UserIdType } from '../../../common/src/utils/circuits/uuid';
import { AttestationVerifier } from './AttestationVerifier';
export class SelfVerifier extends AttestationVerifier {
constructor(
scope: string,
scope: string,
devMode: boolean = false,
rpcUrl: string = DEFAULT_RPC_URL,
registryContractAddress: `0x${string}`,
hubContractAddress: `0x${string}`
) {
super(
devMode,
rpcUrl,
registryContractAddress,
hubContractAddress
);
super(devMode, rpcUrl, registryContractAddress, hubContractAddress);
this.scope = scope;
}
@@ -72,18 +66,20 @@ export class SelfVerifier extends AttestationVerifier {
return this;
}
toDisclosureOptions(): DisclosureOptions {
return [
{ key: 'minimumAge', ...this.minimumAge },
{ key: 'nationality', ...this.nationality },
{ key: 'excludedCountries', ...this.excludedCountries },
{ key: 'ofac', enabled: this.ofac },
];
}
// TODO: related to the qr code
getIntent(
appName: string,
userId: string,
userIdType: UserIdType,
sessionId: string,
websocketUrl: string = WEBSOCKET_URL
): string {
getIntent(appName: string, userId: string, userIdType: UserIdType, sessionId: string): string {
const intent_raw: SelfAppPartial = {
appName: appName,
scope: this.scope,
websocketUrl: websocketUrl,
sessionId: sessionId,
userId: userId,
userIdType: userIdType,
@@ -91,15 +87,11 @@ export class SelfVerifier extends AttestationVerifier {
};
let selfArguments: ArgumentsProveOffChain | ArgumentsRegister;
const argsVcAndDisclose: ArgumentsDisclose = {
disclosureOptions: {
minimumAge: this.minimumAge,
nationality: this.nationality,
excludedCountries: this.excludedCountries,
ofac: this.ofac,
};
selfArguments = argsVcAndDisclose;
}
const argsVcAndDisclose: ArgumentsDisclose = {
disclosureOptions: this.toDisclosureOptions(),
commitmentMerkleTreeUrl: TREE_TRACKER_URL,
};
selfArguments = argsVcAndDisclose;
const intent: SelfApp = {
...intent_raw,

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { OpenPassportAttestation, OpenPassportVerifier } from '@openpassport/core';
import { UserIdType } from '../../common/src/utils/utils';
import { SelfAttestation, SelfVerifier } from '@openpassport/core';
import { UserIdType } from '../../common/src/utils/circuits/uuid';
interface OpenPassportQRcodeProps {
appName: string;
userId: string;
userIdType: UserIdType;
openPassportVerifier: OpenPassportVerifier;
onSuccess: (attestation: OpenPassportAttestation) => void;
selfVerifier: SelfVerifier;
onSuccess: (attestation: SelfAttestation) => void;
websocketUrl?: string;
size?: number;
}

View File

@@ -1,28 +1,23 @@
import React, { useEffect, useState } from 'react';
import { OpenPassportAttestation } from '../../common/src/utils/openPassportAttestation';
import { OpenPassportVerifier } from '@openpassport/core';
import React, { useEffect, useState, useRef } from 'react';
import { BounceLoader } from 'react-spinners';
import Lottie from 'lottie-react';
import CHECK_ANIMATION from './animations/check_animation.json';
import X_ANIMATION from './animations/x_animation.json';
import LED from './components/LED';
import { WEBSOCKET_URL } from '../../common/src/constants/constants';
import { UserIdType } from '../../common/src/utils/utils';
import { REDIRECT_URL, WS_DB_RELAYER_NEW } from '../../common/src/constants/constants';
import { v4 as uuidv4 } from 'uuid';
import { QRcodeSteps } from './utils/utils';
import { containerStyle, ledContainerStyle, qrContainerStyle } from './utils/styles';
import dynamic from 'next/dynamic';
import { initWebSocket } from './utils/websocket';
import { SelfApp, SelfAppBuilder } from '../../common/src/utils/appType';
const QRCodeSVG = dynamic(() => import('qrcode.react').then((mod) => mod.QRCodeSVG), {
ssr: false,
});
interface OpenPassportQRcodeProps {
appName: string;
userId: string;
userIdType: UserIdType;
openPassportVerifier: OpenPassportVerifier;
onSuccess: (attestation: OpenPassportAttestation) => void;
selfApp: SelfApp;
onSuccess: () => void;
websocketUrl?: string;
size?: number;
}
@@ -30,7 +25,6 @@ interface OpenPassportQRcodeProps {
// Create a wrapper component that handles client-side rendering
const OpenPassportQRcodeWrapper: React.FC<OpenPassportQRcodeProps> = (props) => {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
@@ -38,40 +32,47 @@ const OpenPassportQRcodeWrapper: React.FC<OpenPassportQRcodeProps> = (props) =>
if (!isClient) {
return null;
}
return <OpenPassportQRcode {...props} />;
};
// Your existing OpenPassportQRcode component
const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps> = ({
appName,
userId,
userIdType,
openPassportVerifier,
selfApp,
onSuccess,
websocketUrl = WEBSOCKET_URL,
websocketUrl = WS_DB_RELAYER_NEW,
size = 300,
}) => {
const [proofStep, setProofStep] = useState(QRcodeSteps.WAITING_FOR_MOBILE);
const [proofVerified, setProofVerified] = useState(false);
const [sessionId, setSessionId] = useState(uuidv4());
const [sessionId] = useState(uuidv4());
const socketRef = useRef<ReturnType<typeof initWebSocket> | null>(null);
useEffect(() => {
initWebSocket(
websocketUrl,
sessionId,
setProofStep,
setProofVerified,
openPassportVerifier,
onSuccess
);
}, [sessionId, websocketUrl, openPassportVerifier]);
// Only initialize if we don't have a socket already
if (!socketRef.current) {
console.log('[QRCode] Initializing new WebSocket connection');
socketRef.current = initWebSocket(
websocketUrl,
sessionId,
selfApp,
setProofStep,
setProofVerified,
onSuccess
);
}
return () => {
console.log('[QRCode] Cleaning up WebSocket connection');
if (socketRef.current) {
socketRef.current();
socketRef.current = null;
}
};
}, [websocketUrl, sessionId, selfApp, onSuccess]);
const generateUniversalLink = () => {
const baseUrl = 'https://proofofpassport-merkle-tree.xyz';
const path = '/open-passport';
const data = openPassportVerifier.getIntent(appName, userId, userIdType, sessionId);
return `${baseUrl}${path}?data=${data}`;
const baseUrl = REDIRECT_URL;
return `${baseUrl}?sessionId=${sessionId}`;
};
const renderProofStatus = () => (
@@ -125,4 +126,4 @@ const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps> = ({
return <div style={containerStyle}>{renderProofStatus()}</div>;
};
export default OpenPassportQRcodeWrapper;
export { OpenPassportQRcodeWrapper, SelfApp, SelfAppBuilder };

View File

@@ -1,4 +1,6 @@
import OpenPassportQRcode from './OpenPassportQRcode';
import { WebAppInfo } from './utils/websocket';
export { OpenPassportQRcode };
export type { WebAppInfo };

View File

@@ -84,4 +84,4 @@
"publishConfig": {
"access": "public"
}
}
}

View File

@@ -1,90 +1,105 @@
import io, { Socket } from 'socket.io-client';
import { QRcodeSteps } from './utils';
import { OpenPassportVerifier } from '@openpassport/core';
import { OpenPassportAttestation } from '@openpassport/core';
import { SelfApp } from '../../../common/src/utils/appType';
const newSocket = (websocketUrl: string, sessionId: string) =>
io(websocketUrl, {
path: '/websocket',
export interface WebAppInfo {
appName: string;
userId: string;
logoBase64: string;
};
// Log once when this module loads
console.log('[WebSocket] Initializing websocket module.');
const newSocket = (websocketUrl: string, sessionId: string) => {
const fullUrl = `${websocketUrl}/websocket`;
console.log(`[WebSocket] Creating new socket. URL: ${fullUrl}, sessionId: ${sessionId}`);
return io(fullUrl, {
path: '/',
query: { sessionId, clientType: 'web' },
transports: ['websocket'],
});
};
const handleWebSocketMessage =
(
newSocket: Socket,
socket: Socket,
sessionId: string,
selfApp: SelfApp,
setProofStep: (step: number) => void,
setProofVerified: (proofVerified: boolean) => void,
openPassportVerifier: OpenPassportVerifier,
onSuccess: (proof: OpenPassportAttestation) => void
onSuccess: () => void
) =>
async (data) => {
console.log('received mobile status:', data.status);
async (data: any) => {
console.log('[WebSocket] Received mobile status:', data.status, 'for session:', sessionId);
switch (data.status) {
case 'mobile_connected':
console.log('[WebSocket] Mobile device connected. Emitting self_app event with payload:', selfApp);
setProofStep(QRcodeSteps.MOBILE_CONNECTED);
socket.emit('self_app', { ...selfApp, sessionId });
break;
case 'mobile_disconnected':
console.log('[WebSocket] Mobile device disconnected.');
setProofStep(QRcodeSteps.WAITING_FOR_MOBILE);
break;
case 'proof_generation_started':
console.log('[WebSocket] Proof generation started.');
setProofStep(QRcodeSteps.PROOF_GENERATION_STARTED);
break;
case 'proof_generated':
console.log('[WebSocket] Proof generated.');
setProofStep(QRcodeSteps.PROOF_GENERATED);
break;
case 'proof_generation_failed':
console.log('[WebSocket] Proof generation failed.');
setProofVerified(false);
setProofStep(QRcodeSteps.PROOF_VERIFIED);
console.log('Proof generation failed');
break;
}
if (data.proof) {
console.log(data.proof);
try {
const local_proofVerified = await openPassportVerifier.verify(data.proof);
setProofVerified(local_proofVerified.valid);
setProofStep(QRcodeSteps.PROOF_VERIFIED);
setTimeout(() => {
newSocket.emit('proof_verified', {
sessionId,
proofVerified: local_proofVerified.toString(),
});
if (local_proofVerified.valid) {
onSuccess(data.proof);
}
}, 1500); // wait for animation to finish before sending the proof to mobile
} catch (error) {
console.error('Error verifying proof:', error);
setProofVerified(false);
setProofStep(QRcodeSteps.PROOF_VERIFIED);
newSocket.emit('proof_verified', {
sessionId,
proofVerified: JSON.stringify({ valid: false, error: error.message }),
});
}
default:
console.log('[WebSocket] Unhandled mobile status:', data.status);
break;
}
};
export function initWebSocket(
websocketUrl: string,
sessionId: string,
selfApp: SelfApp,
setProofStep: (step: number) => void,
setProofVerified: (proofVerified: boolean) => void,
openPassportVerifier: OpenPassportVerifier,
onSuccess: (proof: OpenPassportAttestation) => void
onSuccess: () => void
) {
console.log(`[WebSocket] Initializing WebSocket connection for sessionId: ${sessionId}`);
const socket = newSocket(websocketUrl, sessionId);
socket.on(
'mobile_status',
socket.on('connect', () => {
console.log(`[WebSocket] Connected with id: ${socket.id}, transport: ${socket.io.engine.transport.name}`);
});
socket.on('connect_error', (error) => {
console.error('[WebSocket] Connection error:', error);
});
socket.on('mobile_status', (data) => {
console.log('[WebSocket] Raw mobile_status event received:', data);
handleWebSocketMessage(
socket,
sessionId,
selfApp,
setProofStep,
setProofVerified,
openPassportVerifier,
onSuccess
)
);
)(data);
});
socket.on('disconnect', (reason: string) => {
console.log(`[WebSocket] Disconnected. Reason: ${reason}, Last transport: ${socket.io.engine.transport?.name}`);
});
return () => {
console.log(`[WebSocket] Cleaning up connection for sessionId: ${sessionId}`);
if (socket) {
socket.disconnect();
}
};
}

View File

@@ -1,26 +1,18 @@
'use client';
import OpenPassportQRcode from '../../../../../qrcode/OpenPassportQRcode';
import { OpenPassportQRcodeWrapper, SelfApp, SelfAppBuilder } from '../../../../../qrcode/OpenPassportQRcode';
import { v4 as uuidv4 } from 'uuid';
import { OpenPassportVerifier } from '@openpassport/core';
import { COMMITMENT_TREE_TRACKER_URL } from '../../../../../../common/src/constants/constants';
export default function Prove() {
const userId = uuidv4();
const scope = 'scope';
const openPassportVerifierDisclose = new OpenPassportVerifier('vc_and_disclose', scope)
.setCommitmentMerkleTreeUrl(COMMITMENT_TREE_TRACKER_URL)
.excludeCountries('Albania')
.setMinimumAge(20)
.enableOFACCheck();
const selfApp = new SelfAppBuilder('Mock App2', 'scope').minimumAge(18).setUserId(userId).build();
return (
<div className="h-screen w-full bg-white flex flex-col items-center justify-center gap-4">
<OpenPassportQRcode
appName="Mock App"
userId={userId}
userIdType={'uuid'}
openPassportVerifier={openPassportVerifierDisclose}
onSuccess={(attestation) => { }}
<OpenPassportQRcodeWrapper
selfApp={selfApp}
onSuccess={() => { }}
/>
</div>
);

View File

@@ -2,19 +2,13 @@
export default function Home() {
return (
<div className="h-screen w-full bg-gray-100 flex flex-row items-center justify-center gap-12">
<div
className="flex flex-col items-center justify-center p-8 bg-white rounded-lg shadow-lg cursor-pointer transition-transform hover:scale-105"
onClick={() => (window.location.href = '/register')}
onClick={() => (window.location.href = '/disclose')}
>
<h2 className="text-2xl text-black font-bold mb-4">Register</h2>
<p className="text-gray-600">2 steps registration flow</p>
</div>
<div
className="flex flex-col items-center justify-center p-8 bg-white rounded-lg shadow-lg cursor-pointer transition-transform hover:scale-105"
onClick={() => (window.location.href = '/prove')}
>
<h2 className="text-2xl text-black font-bold mb-4">Prove</h2>
<p className="text-gray-600">1 step verification flow</p>
<h2 className="text-2xl text-black font-bold mb-4">Disclose</h2>
<p className="text-gray-600">1 step disclosure flow</p>
</div>
</div>
);

View File

@@ -1,31 +0,0 @@
'use client';
import { OpenPassportQRcode } from '@openpassport/qrcode';
// import { OpenPassportQRcode } from '../../../../../qrcode/index';
import { OpenPassportVerifier } from '@openpassport/core';
import { v4 as uuidv4 } from 'uuid';
// import { OpenPassportVerifier, OpenPassportDynamicAttestation } from '@openpassport/core';
// import { OpenPassportVerifier, OpenPassportDynamicAttestation } from '@openpassport/core';
export default function Prove() {
const userId = uuidv4();
const scope = 'scope';
const openPassportVerifier: OpenPassportVerifier = new OpenPassportVerifier('prove_offchain', scope)
.enableOFACCheck()
.excludeCountries('Iran (Islamic Republic of)').setMinimumAge(55).allowMockPassports();
return (
<div className="h-screen w-full bg-white flex flex-col items-center justify-center gap-4">
<OpenPassportQRcode
appName="Mock App"
userId={userId}
userIdType={'uuid'}
openPassportVerifier={openPassportVerifier}
onSuccess={(attestation) => {
// send the code to the backend server
}}
/>
</div>
);
}

View File

@@ -1,38 +0,0 @@
'use client';
import { v4 as uuidv4 } from 'uuid';
import { COMMITMENT_TREE_TRACKER_URL } from '../../../../../../common/src/constants/constants';
import { OpenPassportVerifier } from '@openpassport/core';
import OpenPassportQRcode from '../../../../../qrcode/OpenPassportQRcode';
import axios from 'axios';
export default function Prove() {
const userId = uuidv4();
const scope = 'scope';
const openPassportVerifier = new OpenPassportVerifier('register', scope)
.setCommitmentMerkleTreeUrl(COMMITMENT_TREE_TRACKER_URL);
return (
<div className="h-screen w-full bg-white flex flex-col items-center justify-center gap-4">
<OpenPassportQRcode
appName="Mock App"
userId={userId}
userIdType={'uuid'}
openPassportVerifier={openPassportVerifier}
onSuccess={(attestation) => {
axios
.post('https://proofofpassport-merkle-tree.xyz/api/verifier/register', attestation)
.then((response) => {
console.log('Registration response:', response);
})
.catch((error) => {
console.error('Error registering attestation:', error);
});
}}
/>
</div>
);
}

File diff suppressed because it is too large Load Diff