feat: add functions for disclosing aadhaar attributes (#1033)

* feat: add functions for disclosing aadhaar attributes

* format
This commit is contained in:
Nesopie
2025-09-17 01:58:33 +05:30
committed by GitHub
parent 045a8053da
commit 8e385cb857
2 changed files with 285 additions and 162 deletions

View File

@@ -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';

View File

@@ -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;
}