mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
Proving (#127)
Co-authored-by: turnoffthiscomputer <colin.remi07@gmail.com> Co-authored-by: thomas-senechal <thomas.senechal@pm.me>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user