mirror of
https://github.com/selfxyz/self.git
synced 2026-01-09 14:48:06 -05:00
feat: add functions for disclosing aadhaar attributes (#1033)
* feat: add functions for disclosing aadhaar attributes * format
This commit is contained in:
@@ -84,6 +84,13 @@ export {
|
||||
stringToBigInt,
|
||||
} from './src/utils/index.js';
|
||||
|
||||
export {
|
||||
prepareAadhaarRegisterTestData,
|
||||
prepareAadhaarDiscloseTestData,
|
||||
prepareAadhaarRegisterData,
|
||||
prepareAadhaarDiscloseData,
|
||||
} from './src/utils/aadhaar/mockData.js';
|
||||
export { generateTestData, testCustomData } from './src/utils/aadhaar/utils.js';
|
||||
export { createSelector } from './src/utils/aadhaar/constants.js';
|
||||
// Hash utilities
|
||||
export {
|
||||
@@ -93,11 +100,3 @@ export {
|
||||
hash,
|
||||
packBytesAndPoseidon,
|
||||
} from './src/utils/hash.js';
|
||||
|
||||
export { generateTestData, testCustomData } from './src/utils/aadhaar/utils.js';
|
||||
|
||||
export {
|
||||
prepareAadhaarDiscloseTestData,
|
||||
prepareAadhaarRegisterData,
|
||||
prepareAadhaarRegisterTestData,
|
||||
} from './src/utils/aadhaar/mockData.js';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import forge from 'node-forge';
|
||||
import { poseidon5 } from 'poseidon-lite';
|
||||
|
||||
import { COMMITMENT_TREE_DEPTH } from '../../constants/constants.js';
|
||||
import { findIndexInTree, formatInput } from '../circuits/generateInputs.js';
|
||||
import { packBytesAndPoseidon } from '../hash.js';
|
||||
@@ -11,24 +10,20 @@ import {
|
||||
getNameYobLeafAahaar,
|
||||
} from '../trees.js';
|
||||
import { testQRData } from './assets/dataInput.js';
|
||||
import {
|
||||
calculateAge,
|
||||
extractQRDataFields,
|
||||
generateTestData,
|
||||
stringToAsciiArray,
|
||||
testCustomData,
|
||||
} from './utils.js';
|
||||
|
||||
import { calculateAge, generateTestData, stringToAsciiArray, testCustomData } from './utils.js';
|
||||
import { extractQRDataFields } from './utils.js';
|
||||
import { AadhaarField, createSelector } from './constants.js';
|
||||
import { formatCountriesList } from '../circuits/formatInputs.js';
|
||||
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
||||
import { SMT } from '@openpassport/zk-kit-smt';
|
||||
import {
|
||||
convertBigIntToByteArray,
|
||||
decompressByteArray,
|
||||
extractPhoto,
|
||||
splitToWords,
|
||||
} from '@anon-aadhaar/core';
|
||||
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
||||
import { SMT } from '@openpassport/zk-kit-smt';
|
||||
import { bufferToHex, Uint8ArrayToCharArray } from '@zk-email/helpers/dist/binary-format.js';
|
||||
import { sha256Pad } from '@zk-email/helpers/dist/sha-utils.js';
|
||||
import { bufferToHex, Uint8ArrayToCharArray } from '@zk-email/helpers/dist/binary-format.js';
|
||||
|
||||
// Helper function to compute padded name
|
||||
function computePaddedName(name: string): number[] {
|
||||
@@ -168,6 +163,162 @@ function processQRDataSimple(qrData: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareAadhaarRegisterTestData(
|
||||
privKeyPem: string,
|
||||
pubkeyPem: string,
|
||||
secret: string,
|
||||
name?: string,
|
||||
dateOfBirth?: string,
|
||||
gender?: string,
|
||||
pincode?: string,
|
||||
state?: string,
|
||||
timestamp?: string
|
||||
) {
|
||||
const sharedData = processQRData(
|
||||
privKeyPem,
|
||||
name,
|
||||
dateOfBirth,
|
||||
gender,
|
||||
pincode,
|
||||
state,
|
||||
timestamp
|
||||
);
|
||||
|
||||
const delimiterIndices: number[] = [];
|
||||
for (let i = 0; i < sharedData.qrDataPadded.length; i++) {
|
||||
if (sharedData.qrDataPadded[i] === 255) {
|
||||
delimiterIndices.push(i);
|
||||
}
|
||||
if (delimiterIndices.length === 18) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let photoEOI = 0;
|
||||
for (let i = delimiterIndices[17]; i < sharedData.qrDataPadded.length - 1; i++) {
|
||||
if (sharedData.qrDataPadded[i + 1] === 217 && sharedData.qrDataPadded[i] === 255) {
|
||||
photoEOI = i + 1;
|
||||
}
|
||||
}
|
||||
if (photoEOI === 0) {
|
||||
throw new Error('Photo EOI not found');
|
||||
}
|
||||
|
||||
const signatureBytes = sharedData.decodedData.slice(
|
||||
sharedData.decodedData.length - 256,
|
||||
sharedData.decodedData.length
|
||||
);
|
||||
const signature = BigInt('0x' + bufferToHex(Buffer.from(signatureBytes)).toString());
|
||||
|
||||
const publicKey = forge.pki.publicKeyFromPem(pubkeyPem);
|
||||
|
||||
const modulusHex = publicKey.n.toString(16);
|
||||
const pubKey = BigInt('0x' + modulusHex);
|
||||
|
||||
const nullifier = nullifierHash(sharedData.extractedFields);
|
||||
const packedCommitment = computePackedCommitment(sharedData.extractedFields);
|
||||
const commitment = computeCommitment(
|
||||
BigInt(secret),
|
||||
BigInt(sharedData.qrHash),
|
||||
nullifier,
|
||||
packedCommitment,
|
||||
BigInt(sharedData.photoHash)
|
||||
);
|
||||
|
||||
const inputs = {
|
||||
qrDataPadded: Uint8ArrayToCharArray(sharedData.qrDataPadded),
|
||||
qrDataPaddedLength: sharedData.qrDataPaddedLen,
|
||||
delimiterIndices: delimiterIndices,
|
||||
signature: splitToWords(signature, BigInt(121), BigInt(17)),
|
||||
pubKey: splitToWords(pubKey, BigInt(121), BigInt(17)),
|
||||
secret: secret,
|
||||
photoEOI: photoEOI,
|
||||
};
|
||||
|
||||
return {
|
||||
inputs,
|
||||
nullifier,
|
||||
commitment,
|
||||
};
|
||||
}
|
||||
|
||||
export async function prepareAadhaarRegisterData(qrData: string, secret: string, certs: string[]) {
|
||||
const sharedData = processQRDataSimple(qrData);
|
||||
const delimiterIndices: number[] = [];
|
||||
for (let i = 0; i < sharedData.qrDataPadded.length; i++) {
|
||||
if (sharedData.qrDataPadded[i] === 255) {
|
||||
delimiterIndices.push(i);
|
||||
}
|
||||
if (delimiterIndices.length === 18) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let photoEOI = 0;
|
||||
for (let i = delimiterIndices[17]; i < sharedData.qrDataPadded.length - 1; i++) {
|
||||
if (sharedData.qrDataPadded[i + 1] === 217 && sharedData.qrDataPadded[i] === 255) {
|
||||
photoEOI = i + 1;
|
||||
}
|
||||
}
|
||||
if (photoEOI === 0) {
|
||||
throw new Error('Photo EOI not found');
|
||||
}
|
||||
|
||||
const signatureBytes = sharedData.decodedData.slice(
|
||||
sharedData.decodedData.length - 256,
|
||||
sharedData.decodedData.length
|
||||
);
|
||||
const signature = BigInt('0x' + bufferToHex(Buffer.from(signatureBytes)).toString());
|
||||
|
||||
//do promise.all for all certs and pick the one that is valid
|
||||
const certificates = await Promise.all(
|
||||
certs.map(async (cert) => {
|
||||
const certificate = forge.pki.certificateFromPem(cert);
|
||||
const publicKey = certificate.publicKey as forge.pki.rsa.PublicKey;
|
||||
|
||||
try {
|
||||
const md = forge.md.sha256.create();
|
||||
md.update(forge.util.binary.raw.encode(sharedData.signedData));
|
||||
|
||||
const isValid = publicKey.verify(md.digest().getBytes(), signatureBytes);
|
||||
return isValid;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
//find the valid cert
|
||||
const validCert = certificates.indexOf(true);
|
||||
if (validCert === -1) {
|
||||
throw new Error('No valid certificate found');
|
||||
}
|
||||
const certPem = certs[validCert];
|
||||
const cert = forge.pki.certificateFromPem(certPem);
|
||||
const modulusHex = (cert.publicKey as forge.pki.rsa.PublicKey).n.toString(16);
|
||||
const pubKey = BigInt('0x' + modulusHex);
|
||||
|
||||
const nullifier = nullifierHash(sharedData.extractedFields);
|
||||
const packedCommitment = computePackedCommitment(sharedData.extractedFields);
|
||||
const commitment = computeCommitment(
|
||||
BigInt(secret),
|
||||
BigInt(sharedData.qrHash),
|
||||
nullifier,
|
||||
packedCommitment,
|
||||
BigInt(sharedData.photoHash)
|
||||
);
|
||||
|
||||
const inputs = {
|
||||
qrDataPadded: Uint8ArrayToCharArray(sharedData.qrDataPadded),
|
||||
qrDataPaddedLength: sharedData.qrDataPaddedLen,
|
||||
delimiterIndices: delimiterIndices,
|
||||
signature: splitToWords(signature, BigInt(121), BigInt(17)),
|
||||
pubKey: splitToWords(pubKey, BigInt(121), BigInt(17)),
|
||||
secret: secret,
|
||||
photoEOI: photoEOI,
|
||||
};
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
export function prepareAadhaarDiscloseTestData(
|
||||
privateKeyPem: string,
|
||||
merkletree: LeanIMT,
|
||||
@@ -201,7 +352,6 @@ export function prepareAadhaarDiscloseTestData(
|
||||
sharedData.extractedFields.yob
|
||||
);
|
||||
|
||||
const uppercaseName = computeUppercasePaddedName(sharedData.extractedFields.name);
|
||||
const genderAscii = stringToAsciiArray(sharedData.extractedFields.gender)[0];
|
||||
const nullifier = nullifierHash(sharedData.extractedFields);
|
||||
const packedCommitment = computePackedCommitment(sharedData.extractedFields);
|
||||
@@ -290,139 +440,34 @@ export function prepareAadhaarDiscloseTestData(
|
||||
};
|
||||
}
|
||||
|
||||
export async function prepareAadhaarRegisterData(qrData: string, secret: string, certs: string[]) {
|
||||
const sharedData = processQRDataSimple(qrData);
|
||||
const delimiterIndices: number[] = [];
|
||||
for (let i = 0; i < sharedData.qrDataPadded.length; i++) {
|
||||
if (sharedData.qrDataPadded[i] === 255) {
|
||||
delimiterIndices.push(i);
|
||||
}
|
||||
if (delimiterIndices.length === 18) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let photoEOI = 0;
|
||||
for (let i = delimiterIndices[17]; i < sharedData.qrDataPadded.length - 1; i++) {
|
||||
if (sharedData.qrDataPadded[i + 1] === 217 && sharedData.qrDataPadded[i] === 255) {
|
||||
photoEOI = i + 1;
|
||||
}
|
||||
}
|
||||
if (photoEOI === 0) {
|
||||
throw new Error('Photo EOI not found');
|
||||
}
|
||||
|
||||
const signatureBytes = sharedData.decodedData.slice(
|
||||
sharedData.decodedData.length - 256,
|
||||
sharedData.decodedData.length
|
||||
);
|
||||
const signature = BigInt('0x' + bufferToHex(Buffer.from(signatureBytes)).toString());
|
||||
|
||||
//do promise.all for all certs and pick the one that is valid
|
||||
const certificates = await Promise.all(
|
||||
certs.map(async (cert) => {
|
||||
const certificate = forge.pki.certificateFromPem(cert);
|
||||
const publicKey = certificate.publicKey as forge.pki.rsa.PublicKey;
|
||||
|
||||
try {
|
||||
const md = forge.md.sha256.create();
|
||||
md.update(forge.util.binary.raw.encode(sharedData.signedData));
|
||||
|
||||
const isValid = publicKey.verify(md.digest().getBytes(), signatureBytes);
|
||||
return isValid;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
//find the valid cert
|
||||
const validCert = certificates.indexOf(true);
|
||||
if (validCert === -1) {
|
||||
throw new Error('No valid certificate found');
|
||||
}
|
||||
const certPem = certs[validCert];
|
||||
const cert = forge.pki.certificateFromPem(certPem);
|
||||
const modulusHex = (cert.publicKey as forge.pki.rsa.PublicKey).n.toString(16);
|
||||
const pubKey = BigInt('0x' + modulusHex);
|
||||
|
||||
const nullifier = nullifierHash(sharedData.extractedFields);
|
||||
const packedCommitment = computePackedCommitment(sharedData.extractedFields);
|
||||
const commitment = computeCommitment(
|
||||
BigInt(secret),
|
||||
BigInt(sharedData.qrHash),
|
||||
nullifier,
|
||||
packedCommitment,
|
||||
BigInt(sharedData.photoHash)
|
||||
);
|
||||
|
||||
const inputs = {
|
||||
qrDataPadded: Uint8ArrayToCharArray(sharedData.qrDataPadded),
|
||||
qrDataPaddedLength: sharedData.qrDataPaddedLen,
|
||||
delimiterIndices: delimiterIndices,
|
||||
signature: splitToWords(signature, BigInt(121), BigInt(17)),
|
||||
pubKey: splitToWords(pubKey, BigInt(121), BigInt(17)),
|
||||
secret: secret,
|
||||
photoEOI: photoEOI,
|
||||
};
|
||||
|
||||
return {
|
||||
inputs,
|
||||
nullifier,
|
||||
commitment,
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareAadhaarRegisterTestData(
|
||||
privKeyPem: string,
|
||||
pubkeyPem: string,
|
||||
export function prepareAadhaarDiscloseData(
|
||||
qrData: string,
|
||||
identityTree: LeanIMT,
|
||||
nameAndDob_smt: SMT,
|
||||
nameAndYob_smt: SMT,
|
||||
scope: string,
|
||||
secret: string,
|
||||
name?: string,
|
||||
dateOfBirth?: string,
|
||||
gender?: string,
|
||||
pincode?: string,
|
||||
state?: string,
|
||||
timestamp?: string
|
||||
user_identifier: string,
|
||||
discloseAttributes: {
|
||||
dateOfBirth?: boolean;
|
||||
name?: boolean;
|
||||
gender?: boolean;
|
||||
idNumber?: boolean;
|
||||
issuingState?: boolean;
|
||||
minimumAge?: number;
|
||||
forbiddenCountriesListPacked?: string[];
|
||||
ofac?: boolean;
|
||||
}
|
||||
) {
|
||||
const sharedData = processQRData(
|
||||
privKeyPem,
|
||||
name,
|
||||
dateOfBirth,
|
||||
gender,
|
||||
pincode,
|
||||
state,
|
||||
timestamp
|
||||
const sharedData = processQRDataSimple(qrData);
|
||||
|
||||
const { currentYear, currentMonth, currentDay } = calculateAge(
|
||||
sharedData.extractedFields.dob,
|
||||
sharedData.extractedFields.mob,
|
||||
sharedData.extractedFields.yob
|
||||
);
|
||||
|
||||
const delimiterIndices: number[] = [];
|
||||
for (let i = 0; i < sharedData.qrDataPadded.length; i++) {
|
||||
if (sharedData.qrDataPadded[i] === 255) {
|
||||
delimiterIndices.push(i);
|
||||
}
|
||||
if (delimiterIndices.length === 18) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let photoEOI = 0;
|
||||
for (let i = delimiterIndices[17]; i < sharedData.qrDataPadded.length - 1; i++) {
|
||||
if (sharedData.qrDataPadded[i + 1] === 217 && sharedData.qrDataPadded[i] === 255) {
|
||||
photoEOI = i + 1;
|
||||
}
|
||||
}
|
||||
if (photoEOI === 0) {
|
||||
throw new Error('Photo EOI not found');
|
||||
}
|
||||
|
||||
const signatureBytes = sharedData.decodedData.slice(
|
||||
sharedData.decodedData.length - 256,
|
||||
sharedData.decodedData.length
|
||||
);
|
||||
const signature = BigInt('0x' + bufferToHex(Buffer.from(signatureBytes)).toString());
|
||||
|
||||
const publicKey = forge.pki.publicKeyFromPem(pubkeyPem);
|
||||
|
||||
const modulusHex = publicKey.n.toString(16);
|
||||
const pubKey = BigInt('0x' + modulusHex);
|
||||
|
||||
const genderAscii = stringToAsciiArray(sharedData.extractedFields.gender)[0];
|
||||
const nullifier = nullifierHash(sharedData.extractedFields);
|
||||
const packedCommitment = computePackedCommitment(sharedData.extractedFields);
|
||||
const commitment = computeCommitment(
|
||||
@@ -433,19 +478,98 @@ export function prepareAadhaarRegisterTestData(
|
||||
BigInt(sharedData.photoHash)
|
||||
);
|
||||
|
||||
const paddedName = computePaddedName(sharedData.extractedFields.name);
|
||||
|
||||
const index = findIndexInTree(identityTree, BigInt(commitment));
|
||||
const {
|
||||
siblings,
|
||||
path: merkle_path,
|
||||
leaf_depth,
|
||||
} = generateMerkleProof(identityTree, index, COMMITMENT_TREE_DEPTH);
|
||||
|
||||
const namedob_leaf = getNameDobLeafAadhaar(
|
||||
sharedData.extractedFields.name,
|
||||
sharedData.extractedFields.yob,
|
||||
sharedData.extractedFields.mob,
|
||||
sharedData.extractedFields.dob
|
||||
);
|
||||
const nameyob_leaf = getNameYobLeafAahaar(
|
||||
sharedData.extractedFields.name,
|
||||
sharedData.extractedFields.yob
|
||||
);
|
||||
|
||||
const {
|
||||
root: ofac_name_dob_smt_root,
|
||||
closestleaf: ofac_name_dob_smt_leaf_key,
|
||||
siblings: ofac_name_dob_smt_siblings,
|
||||
} = generateSMTProof(nameAndDob_smt, namedob_leaf);
|
||||
|
||||
const {
|
||||
root: ofac_name_yob_smt_root,
|
||||
closestleaf: ofac_name_yob_smt_leaf_key,
|
||||
siblings: ofac_name_yob_smt_siblings,
|
||||
} = generateSMTProof(nameAndYob_smt, nameyob_leaf);
|
||||
|
||||
const selectorArr: AadhaarField[] = [];
|
||||
if (discloseAttributes.dateOfBirth) {
|
||||
selectorArr.push('YEAR_OF_BIRTH');
|
||||
selectorArr.push('MONTH_OF_BIRTH');
|
||||
selectorArr.push('DAY_OF_BIRTH');
|
||||
}
|
||||
if (discloseAttributes.name) {
|
||||
selectorArr.push('NAME');
|
||||
}
|
||||
if (discloseAttributes.gender) {
|
||||
selectorArr.push('GENDER');
|
||||
}
|
||||
if (discloseAttributes.idNumber) {
|
||||
selectorArr.push('AADHAAR_LAST_4_DIGITS');
|
||||
}
|
||||
if (discloseAttributes.issuingState) {
|
||||
selectorArr.push('STATE');
|
||||
}
|
||||
if (discloseAttributes.ofac) {
|
||||
selectorArr.push('OFAC_NAME_DOB_CHECK');
|
||||
selectorArr.push('OFAC_NAME_YOB_CHECK');
|
||||
}
|
||||
|
||||
const selector = createSelector(selectorArr);
|
||||
|
||||
const inputs = {
|
||||
qrDataPadded: Uint8ArrayToCharArray(sharedData.qrDataPadded),
|
||||
qrDataPaddedLength: sharedData.qrDataPaddedLen,
|
||||
delimiterIndices: delimiterIndices,
|
||||
signature: splitToWords(signature, BigInt(121), BigInt(17)),
|
||||
pubKey: splitToWords(pubKey, BigInt(121), BigInt(17)),
|
||||
secret: secret,
|
||||
photoEOI: photoEOI,
|
||||
attestation_id: '3',
|
||||
secret,
|
||||
qrDataHash: BigInt(sharedData.qrHash).toString(),
|
||||
gender: genderAscii.toString(),
|
||||
yob: stringToAsciiArray(sharedData.extractedFields.yob),
|
||||
mob: stringToAsciiArray(sharedData.extractedFields.mob),
|
||||
dob: stringToAsciiArray(sharedData.extractedFields.dob),
|
||||
name: formatInput(paddedName),
|
||||
aadhaar_last_4digits: stringToAsciiArray(sharedData.extractedFields.aadhaarLast4Digits),
|
||||
pincode: stringToAsciiArray(sharedData.extractedFields.pincode),
|
||||
state: stringToAsciiArray(sharedData.extractedFields.state.padEnd(31, '\0')),
|
||||
ph_no_last_4digits: stringToAsciiArray(sharedData.extractedFields.phoneNoLast4Digits),
|
||||
photoHash: formatInput(BigInt(sharedData.photoHash)),
|
||||
merkle_root: formatInput(BigInt(identityTree.root)),
|
||||
leaf_depth: formatInput(leaf_depth),
|
||||
path: formatInput(merkle_path),
|
||||
siblings: formatInput(siblings),
|
||||
ofac_name_dob_smt_leaf_key: formatInput(BigInt(ofac_name_dob_smt_leaf_key)),
|
||||
ofac_name_dob_smt_root: formatInput(BigInt(ofac_name_dob_smt_root)),
|
||||
ofac_name_dob_smt_siblings: formatInput(ofac_name_dob_smt_siblings),
|
||||
ofac_name_yob_smt_leaf_key: formatInput(BigInt(ofac_name_yob_smt_leaf_key)),
|
||||
ofac_name_yob_smt_root: formatInput(BigInt(ofac_name_yob_smt_root)),
|
||||
ofac_name_yob_smt_siblings: formatInput(ofac_name_yob_smt_siblings),
|
||||
selector,
|
||||
minimumAge: formatInput(discloseAttributes.minimumAge ?? 0),
|
||||
currentYear: formatInput(currentYear),
|
||||
currentMonth: formatInput(currentMonth),
|
||||
currentDay: formatInput(currentDay),
|
||||
scope: formatInput(BigInt(scope)),
|
||||
user_identifier: formatInput(BigInt(user_identifier)),
|
||||
forbidden_countries_list: discloseAttributes.forbiddenCountriesListPacked
|
||||
? formatInput(formatCountriesList(discloseAttributes.forbiddenCountriesListPacked))
|
||||
: formatInput([...Array(120)].map((_) => '0')),
|
||||
};
|
||||
|
||||
return {
|
||||
inputs,
|
||||
nullifier,
|
||||
commitment,
|
||||
};
|
||||
return inputs;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user