Co-authored-by: turnoffthiscomputer <colin.remi07@gmail.com>
Co-authored-by: thomas-senechal <thomas.senechal@pm.me>
This commit is contained in:
turboblitz
2025-02-16 19:20:45 -08:00
committed by GitHub
parent bf5f8ce166
commit fbce054f57
22 changed files with 6317 additions and 396 deletions

View File

@@ -33,15 +33,52 @@ export const hashAlgos = ['sha512', 'sha384', 'sha256', 'sha224', 'sha1'];
export const saltLengths = [64, 48, 32];
export const MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH = 10;
export const DEPLOYED_CIRCUITS_REGISTER = [
'register_sha1_sha1_sha1_rsa_65537_4096',
'register_sha1_sha256_sha256_rsa_65537_4096',
'register_sha224_sha224_sha224_ecdsa_brainpoolP224r1',
'register_sha256_sha224_sha224_ecdsa_secp224r1',
'register_sha256_sha256_sha256_ecdsa_brainpoolP256r1',
'register_sha256_sha256_sha256_ecdsa_brainpoolP384r1',
'register_sha256_sha256_sha256_ecdsa_secp256r1',
'register_sha256_sha256_sha256_ecdsa_secp384r1',
'register_sha256_sha256_sha256_rsa_3_4096',
'register_sha256_sha256_sha256_rsa_65537_4096',
'register_sha256_sha256_sha256_rsapss_3_32_2048',
'register_sha256_sha256_sha256_rsapss_65537_32_2048',
'register_sha256_sha256_sha256_rsapss_65537_32_3072',
'register_sha384_sha384_sha384_ecdsa_brainpoolP384r1',
'register_sha384_sha384_sha384_ecdsa_brainpoolP512r1',
'register_sha384_sha384_sha384_ecdsa_secp384r1',
'register_sha512_sha512_sha512_ecdsa_brainpoolP512r1',
'register_sha512_sha512_sha512_rsa_65537_4096',
'register_sha512_sha512_sha512_rsapss_65537_64_2048',
]
export const OFAC_TREE_LEVELS = 64;
export const DEPLOYED_CIRCUITS_DSC = [
'dsc_sha1_ecdsa_brainpoolP256r1',
'dsc_sha1_rsa_65537_4096',
'dsc_sha256_ecdsa_brainpoolP256r1',
'dsc_sha256_ecdsa_brainpoolP384r1',
'dsc_sha256_ecdsa_secp256r1',
'dsc_sha256_ecdsa_secp384r1',
'dsc_sha256_ecdsa_secp521r1',
'dsc_sha256_rsa_65537_4096',
'dsc_sha256_rsapss_3_32_3072',
'dsc_sha256_rsapss_65537_32_3072',
'dsc_sha256_rsapss_65537_32_4096',
'dsc_sha384_ecdsa_brainpoolP384r1',
'dsc_sha384_ecdsa_brainpoolP512r1',
'dsc_sha384_ecdsa_secp384r1',
'dsc_sha512_ecdsa_brainpoolP512r1',
'dsc_sha512_ecdsa_secp521r1',
'dsc_sha512_rsa_65537_4096',
'dsc_sha512_rsapss_65537_64_4096',
]
export const MAX_PADDED_ECONTENT_LEN: Partial<Record<(typeof hashAlgos)[number], number>> = {
sha1: 384,
sha224: 512,

View File

@@ -5,8 +5,7 @@ export type EndpointType = 'https' | 'celo';
import { v4 } from 'uuid';
// SelfAppType
export interface SelfAppPartial {
export interface SelfApp {
appName: string;
logoBase64: string;
endpointType: EndpointType;
@@ -17,169 +16,58 @@ export interface SelfAppPartial {
userId: string;
userIdType: UserIdType;
devMode: boolean;
disclosures: SelfAppDisclosureConfig;
}
export interface SelfApp extends SelfAppPartial {
args: ArgumentsDisclose;
export interface SelfAppDisclosureConfig {
// dg1
issuing_state?: boolean;
name?: boolean;
passport_number?: boolean;
nationality?: boolean;
date_of_birth?: boolean;
gender?: boolean;
expiry_date?: boolean;
// custom checks
ofac?: boolean;
excludedCountries?: string[];
minimumAge?: number;
}
export interface ArgumentsDisclose {
disclosureOptions: DisclosureOptions;
}
type DisclosureBoolKeys = 'ofac'
type DisclosureBoolOption = {
enabled: boolean;
key: DisclosureBoolKeys
}
type DisclosureMatchKeys = 'nationality' | 'minimumAge'
export interface DisclosureMatchOption<T = DisclosureMatchKeys> {
enabled: boolean;
key: T;
value: string;
}
type DisclosureListKeys = 'excludedCountries'
interface DisclosureListOption {
enabled: boolean;
key: DisclosureListKeys;
value: string[];
}
export type DisclosureOption = DisclosureBoolOption | DisclosureMatchOption | DisclosureListOption
export type DisclosureAttributes = DisclosureBoolKeys | DisclosureMatchKeys | DisclosureListKeys
export type DisclosureOptions = Array<DisclosureOption>
export type GetDisclosure<T extends DisclosureAttributes> = T extends DisclosureMatchKeys ? DisclosureMatchOption : T extends DisclosureListKeys ? DisclosureListOption : DisclosureBoolOption
// {"appName": "Mock App2", "args": {"disclosureOptions": [[Object]]}, "devMode": false, "endpoint": "https://mock-app2.com", "endpointType": "https", "header": "", "logoBase64": "", "scope": "scope", "sessionId": "05ce9b3f-cf20-4eca-8bf1-df2694967787", "userId": "06e946f1-485c-4af4-97c4-74a61cf47724", "userIdType": "uuid"}
export class SelfAppBuilder {
appName: string;
logoBase64: string;
scope: string;
sessionId: string;
userId: string;
userIdType: UserIdType;
devMode: boolean;
endpointType: EndpointType;
endpoint: string;
header: string;
args: ArgumentsDisclose;
private config: SelfApp;
constructor(appName: string, scope: string, endpoint: string) {
this.appName = appName;
this.scope = scope;
this.args = {
disclosureOptions: []
};
this.header = '';
this.endpoint = endpoint;
this.sessionId = v4();
this.logoBase64 = '';
this.userId = '';
this.userIdType = 'uuid';
this.devMode = false;
this.endpointType = 'https';
}
setLogoBase64(logoBase64: string) {
this.logoBase64 = logoBase64;
return this;
}
setUserId(userId: string) {
this.userId = userId;
return this;
}
setEndpointType(endpointType: EndpointType) {
this.endpointType = endpointType;
return this;
}
setEndpoint(endpoint: string) {
this.endpoint = endpoint;
return this;
}
setUserIdType(userIdType: UserIdType) {
this.userIdType = userIdType;
return this;
}
setDevMode(devMode: boolean) {
this.devMode = devMode;
return this;
}
minimumAge(age: number) {
this.args.disclosureOptions.push({
enabled: true,
key: 'minimumAge',
value: age.toString()
});
return this;
}
nationality(nationality: string) {
this.args.disclosureOptions.push({
enabled: true,
key: 'nationality',
value: nationality
});
return this;
}
ofac(ofac: boolean) {
this.args.disclosureOptions.push({
enabled: true,
key: 'ofac',
});
return this;
}
excludedCountries(countries: string[]) {
this.args.disclosureOptions.push({
enabled: true,
key: 'excludedCountries',
value: countries
});
return this;
constructor(config: Partial<SelfApp>) {
if (!config.appName) {
throw new Error('appName is required');
}
if (!config.scope) {
throw new Error('scope is required');
}
if (!config.endpoint) {
throw new Error('endpoint is required');
}
if (config.endpointType === 'https' && !config.endpoint.startsWith('https://')) {
throw new Error('endpoint must start with https://');
}
if (config.endpointType === 'celo' && !config.endpoint.startsWith('0x')) {
throw new Error('endpoint must be a valid address');
}
this.config = {
sessionId : v4(),
userIdType : 'uuid',
userId: "",
devMode : false,
endpointType : 'https',
header: "",
logoBase64: "",
disclosures: {},
...config,
} as SelfApp;
}
build(): SelfApp {
if (!this.appName) {
throw new Error('appName is required');
}
if (!this.scope) {
throw new Error('scope is required');
}
if (!this.sessionId) {
throw new Error('sessionId is required');
}
if (!this.endpoint) {
throw new Error('endpoint is required');
}
if (this.endpointType === 'https' && !this.endpoint.startsWith('https://')) {
throw new Error('endpoint must start with https://');
}
if (this.endpointType === 'celo' && !this.endpoint.startsWith('0x')) {
throw new Error('endpoint must be a valid address');
}
return {
appName: this.appName,
logoBase64: this.logoBase64,
scope: this.scope,
sessionId: this.sessionId,
userId: this.userId,
userIdType: this.userIdType,
devMode: this.devMode,
endpointType: this.endpointType,
endpoint: this.endpoint,
header: this.header,
args: this.args
};
return this.config;
}
}

View File

@@ -5,7 +5,6 @@ import {
PublicKeyDetailsRSA,
PublicKeyDetailsRSAPSS,
} from '../certificate_parsing/dataStructure';
import { parsePassportData } from '../passports/passport_parsing/parsePassportData';
export function getCircuitNameFromPassportData(passportData: PassportData, circuitType: 'register' | 'dsc') {
if (circuitType === 'register') {
@@ -16,11 +15,17 @@ export function getCircuitNameFromPassportData(passportData: PassportData, circu
}
function getDSCircuitNameFromPassportData(passportData: PassportData) {
const passportMetadata = parsePassportData(passportData);
if (!passportData.passportMetadata) {
throw new Error("Passport data are not parsed");
}
const passportMetadata = passportData.passportMetadata;
if (!passportMetadata.cscaFound) {
throw new Error("CSCA not found");
}
const parsedCSCA = passportData.csca_parsed;
const signatureAlgorithm = passportMetadata.cscaSignatureAlgorithm;
const hashFunction = passportMetadata.cscaHashFunction;
const parsedCSCA = parseCertificateSimple(passportData.passportMetadata.csca);
const bits = parsedCSCA.publicKeyDetails.bits;
if (signatureAlgorithm === 'ecdsa') {
const curve = (parsedCSCA.publicKeyDetails as PublicKeyDetailsECDSA).curve;
@@ -49,8 +54,15 @@ function getDSCircuitNameFromPassportData(passportData: PassportData) {
}
function getRegisterNameFromPassportData(passportData: PassportData) {
const passportMetadata = parsePassportData(passportData);
const parsedDsc = parseCertificateSimple(passportData.dsc);
if (!passportData.passportMetadata) {
throw new Error("Passport data are not parsed");
}
const passportMetadata = passportData.passportMetadata;
if (!passportMetadata.cscaFound) {
throw new Error("CSCA not found");
}
const parsedDsc = passportData.dsc_parsed;
const dgHashAlgo = passportMetadata.dg1HashFunction;
const eContentHashAlgo = passportMetadata.eContentHashFunction;
const signedAttrHashAlgo = passportMetadata.signedAttrHashFunction;

View File

@@ -201,7 +201,7 @@ export function generateCircuitInputsVCandDisclose(
// SMT - OFAC
const passportNo_leaf = getPassportNumberAndNationalityLeaf(formattedMrz.slice(49, 58), formattedMrz.slice(59, 62));
const namedob_leaf = getNameDobLeaf(formattedMrz.slice(10, 49), formattedMrz.slice(62, 68)); // [57-62] + 5 shift
const namedob_leaf = getNameDobLeaf(formattedMrz.slice(10, 49), formattedMrz.slice(62, 68));
const name_leaf = getNameYobLeaf(formattedMrz.slice(10, 49), formattedMrz.slice(62, 64));
const {

View File

@@ -1,10 +1,4 @@
import * as forge from 'node-forge';
import * as fs from 'fs';
import { MODAL_SERVER_ADDRESS } from '../constants/constants';
import axios from 'axios';
import { SKI_PEM, SKI_PEM_DEV } from '../constants/skiPem';
import { splitToWords } from './bytes';
import path from 'path';
export function findStartIndexEC(modulus: string, messagePadded: number[]): [number, number] {
const modulusNumArray = [];
@@ -135,8 +129,6 @@ export function findOIDPosition(
throw new Error('OID not found in message');
}
export function getCSCAFromSKI(ski: string, devMode: boolean): string {
const normalizedSki = ski.replace(/\s+/g, '').toLowerCase();
@@ -157,50 +149,4 @@ export function getCSCAFromSKI(ski: string, devMode: boolean): string {
}
return cscaPem;
}
export function getTBSHash(
cert: forge.pki.Certificate,
hashFunction: 'sha1' | 'sha256',
n: number,
k: number
): string[] {
const tbsCertAsn1 = forge.pki.certificateToAsn1(cert).value[0];
const tbsCertDer = forge.asn1.toDer(tbsCertAsn1 as any).getBytes();
const md = hashFunction === 'sha256' ? forge.md.sha256.create() : forge.md.sha1.create();
md.update(tbsCertDer);
const tbsCertificateHash = md.digest();
const tbsCertificateHashString = tbsCertificateHash.data;
const tbsCertificateHashHex = Buffer.from(tbsCertificateHashString, 'binary').toString('hex');
const tbsCertificateHashBigint = BigInt(`0x${tbsCertificateHashHex}`);
// console.log('tbsCertificateHashBigint', tbsCertificateHashBigint);
return splitToWords(tbsCertificateHashBigint, n, k);
}
export const sendCSCARequest = async (inputs_csca: any): Promise<any> => {
try {
const response = await axios.post(MODAL_SERVER_ADDRESS, inputs_csca, {
headers: {
'Content-Type': 'application/json',
},
});
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('Axios error:', error.message);
if (error.response) {
console.error('Response data:', error.response.data);
console.error('Response status:', error.response.status);
}
} else {
console.error('Unexpected error:', error);
}
throw error;
}
};
export const generateDscSecret = () => {
const secretBytes = forge.random.getBytesSync(31);
return BigInt(`0x${forge.util.bytesToHex(secretBytes)}`).toString();
};
}

View File

@@ -19,12 +19,27 @@ countries.registerLocale(en);
export async function getCSCATree(): Promise<string[][]> {
const response = await fetch(CSCA_TREE_URL);
return await response.json().then(data => data.data ? JSON.parse(data.data) : data);
const data = await response.json();
const status = data.status ? data.status : data;
if (status === 'error') {
throw new Error('Error fetching CSCA tree');
}
const tree = data.data ? JSON.parse(data.data) : data;
console.log('CSCA tree:', tree);
return tree;
}
export async function getDSCTree(): Promise<string> {
const response = await fetch(DSC_TREE_URL);
return await response.json().then(data => data.data ? data.data : data);
const data = await response.json();
const status = data.status ? data.status : data;
if (status === 'error') {
throw new Error('Error fetching DSC tree');
}
const tree = data.data ? data.data : data;
console.log('DSC tree:', tree);
return tree;
}
export async function getCommitmentTree(): Promise<string> {
@@ -47,12 +62,14 @@ export async function fetchTreeFromUrl(url: string): Promise<LeanIMT> {
export function getLeaf(parsed: CertificateData, type: 'dsc' | 'csca'): string {
if (type === 'dsc') {
// for now, we pad it for sha
const tbsArray = Object.keys(parsed.tbsBytes).map(key => parsed.tbsBytes[key]);
const [paddedTbsBytes, tbsBytesPaddedLength] = pad(parsed.hashAlgorithm)(
parsed.tbsBytes,
tbsArray,
max_dsc_bytes
);
const dsc_hash = packBytesAndPoseidon(Array.from(paddedTbsBytes));
return poseidon2([dsc_hash, parsed.tbsBytes.length]).toString();
return poseidon2([dsc_hash, tbsArray.length]).toString();
} else {
const tbsBytesArray = Array.from(parsed.tbsBytes);
const paddedTbsBytesArray = tbsBytesArray.concat(new Array(max_csca_bytes - tbsBytesArray.length).fill(0));
@@ -73,7 +90,9 @@ export function getLeafDscTreeFromParsedDsc(dscParsed: CertificateData): string
export function getLeafDscTree(dsc_parsed: CertificateData, csca_parsed: CertificateData): string {
const dscLeaf = getLeaf(dsc_parsed, 'dsc');
console.log('dscLeaf', dscLeaf);
const cscaLeaf = getLeaf(csca_parsed, 'csca');
console.log('cscaLeaf', cscaLeaf);
return poseidon2([dscLeaf, cscaLeaf]).toString();
}
@@ -95,6 +114,7 @@ export function getDscTreeInclusionProof(leaf: string, serialized_dsc_tree: stri
export function getCscaTreeInclusionProof(leaf: string, _serialized_csca_tree: any[][]) {
let tree = new IMT(poseidon2, CSCA_TREE_DEPTH, 0, 2);
console.log('serialized_csca_tree', _serialized_csca_tree);
tree.setNodes(_serialized_csca_tree);
const index = tree.indexOf(leaf);
if (index === -1) {