mirror of
https://github.com/selfxyz/self.git
synced 2026-01-09 14:48:06 -05:00
283 lines
10 KiB
TypeScript
283 lines
10 KiB
TypeScript
import {
|
|
PUBKEY_TREE_DEPTH,
|
|
DEFAULT_USER_ID_TYPE,
|
|
MAX_PADDED_ECONTENT_LEN,
|
|
MAX_PADDED_SIGNED_ATTR_LEN,
|
|
} from '../constants/constants';
|
|
import { assert, shaPad } from './shaPad';
|
|
import { PassportData } from './types';
|
|
import {
|
|
bytesToBigDecimal,
|
|
formatMrz,
|
|
hash,
|
|
splitToWords,
|
|
getCurrentDateYYMMDD,
|
|
generateMerkleProof,
|
|
generateSMTProof,
|
|
findSubarrayIndex,
|
|
hexToDecimal,
|
|
extractRSFromSignature,
|
|
castFromUUID,
|
|
castFromScope,
|
|
parseUIDToBigInt,
|
|
formatDg2Hash,
|
|
getNAndK,
|
|
stringToAsciiBigIntArray,
|
|
formatCountriesList,
|
|
} from './utils';
|
|
import { generateCommitment, getLeaf } from './pubkeyTree';
|
|
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
|
import { getCountryLeaf, getNameLeaf, getNameDobLeaf, getPassportNumberLeaf } from './smtTree';
|
|
import { packBytes } from '../utils/utils';
|
|
import { SMT } from '@openpassport/zk-kit-smt';
|
|
import { parseCertificate } from './certificates/handleCertificate';
|
|
|
|
export function generateCircuitInputsDisclose(
|
|
secret: string,
|
|
attestation_id: string,
|
|
passportData: PassportData,
|
|
scope: string,
|
|
selector_dg1: string[],
|
|
selector_older_than: string | number,
|
|
merkletree: LeanIMT,
|
|
majority: string,
|
|
name_smt: SMT,
|
|
selector_ofac: string | number,
|
|
forbidden_countries_list: string[],
|
|
user_identifier: string
|
|
) {
|
|
const pubkey_leaf = getLeaf(passportData.dsc);
|
|
const formattedMrz = formatMrz(passportData.mrz);
|
|
const mrz_bytes_packed = packBytes(formattedMrz);
|
|
|
|
const commitment = generateCommitment(
|
|
BigInt(secret).toString(),
|
|
BigInt(attestation_id).toString(),
|
|
BigInt(pubkey_leaf).toString(),
|
|
mrz_bytes_packed,
|
|
formatDg2Hash(passportData.dg2Hash)
|
|
);
|
|
console.log('\x1b[90mcommitment:\x1b[0m', commitment);
|
|
|
|
const index = findIndexInTree(merkletree, commitment);
|
|
|
|
const { merkleProofSiblings, merkleProofIndices, depthForThisOne } = generateMerkleProof(
|
|
merkletree,
|
|
index,
|
|
PUBKEY_TREE_DEPTH
|
|
);
|
|
const formattedMajority = majority.length === 1 ? `0${majority}` : majority;
|
|
const majority_ascii = formattedMajority.split('').map((char) => char.charCodeAt(0));
|
|
|
|
// SMT - OFAC
|
|
|
|
const name_leaf = getNameLeaf(formattedMrz.slice(10, 49)); // [6-44] + 5 shift
|
|
const {
|
|
root: smt_root,
|
|
closestleaf: smt_leaf_value,
|
|
siblings: smt_siblings,
|
|
} = generateSMTProof(name_smt, name_leaf);
|
|
|
|
return {
|
|
secret: formatInput(secret),
|
|
attestation_id: formatInput(attestation_id),
|
|
pubkey_leaf: formatInput(pubkey_leaf),
|
|
dg1: formatInput(formattedMrz),
|
|
dg2_hash: formatInput(formatDg2Hash(passportData.dg2Hash)),
|
|
merkle_root: formatInput(merkletree.root),
|
|
merkletree_size: formatInput(depthForThisOne),
|
|
path: formatInput(merkleProofIndices),
|
|
siblings: formatInput(merkleProofSiblings),
|
|
selector_dg1: formatInput(selector_dg1),
|
|
selector_older_than: formatInput(selector_older_than),
|
|
scope: formatInput(castFromScope(scope)),
|
|
current_date: formatInput(getCurrentDateYYMMDD()),
|
|
majority: formatInput(majority_ascii),
|
|
user_identifier: formatInput(castFromUUID(user_identifier)),
|
|
smt_root: formatInput(smt_root),
|
|
smt_leaf_value: formatInput(smt_leaf_value),
|
|
smt_siblings: formatInput(smt_siblings),
|
|
selector_ofac: formatInput(selector_ofac),
|
|
forbidden_countries_list: formatInput(formatCountriesList(forbidden_countries_list)),
|
|
};
|
|
}
|
|
|
|
export function generateCircuitInputsOfac(
|
|
passportData: PassportData,
|
|
sparsemerkletree: SMT,
|
|
proofLevel: number
|
|
) {
|
|
const mrz_bytes = formatMrz(passportData.mrz);
|
|
const passport_leaf = getPassportNumberLeaf(mrz_bytes.slice(49, 58));
|
|
const namedob_leaf = getNameDobLeaf(mrz_bytes.slice(10, 49), mrz_bytes.slice(62, 68)); // [57-62] + 5 shift
|
|
const name_leaf = getNameLeaf(mrz_bytes.slice(10, 49)); // [6-44] + 5 shift
|
|
|
|
let root, closestleaf, siblings;
|
|
if (proofLevel == 3) {
|
|
({ root, closestleaf, siblings } = generateSMTProof(sparsemerkletree, passport_leaf));
|
|
} else if (proofLevel == 2) {
|
|
({ root, closestleaf, siblings } = generateSMTProof(sparsemerkletree, namedob_leaf));
|
|
} else if (proofLevel == 1) {
|
|
({ root, closestleaf, siblings } = generateSMTProof(sparsemerkletree, name_leaf));
|
|
} else {
|
|
throw new Error('Invalid proof level');
|
|
}
|
|
|
|
return {
|
|
dg1: formatInput(mrz_bytes),
|
|
smt_leaf_value: formatInput(closestleaf),
|
|
smt_root: formatInput(root),
|
|
smt_siblings: formatInput(siblings),
|
|
};
|
|
}
|
|
|
|
export function generateCircuitInputsCountryVerifier(
|
|
passportData: PassportData,
|
|
sparsemerkletree: SMT
|
|
) {
|
|
const mrz_bytes = formatMrz(passportData.mrz);
|
|
const usa_ascii = stringToAsciiBigIntArray('USA');
|
|
const country_leaf = getCountryLeaf(usa_ascii, mrz_bytes.slice(7, 10));
|
|
const { root, closestleaf, siblings } = generateSMTProof(sparsemerkletree, country_leaf);
|
|
|
|
return {
|
|
dg1: formatInput(mrz_bytes),
|
|
hostCountry: formatInput(usa_ascii),
|
|
smt_leaf_value: formatInput(closestleaf),
|
|
smt_root: formatInput(root),
|
|
smt_siblings: formatInput(siblings),
|
|
};
|
|
}
|
|
|
|
// this get the commitment index whether it is a string or a bigint
|
|
// this is necessary rn because when the tree is send from the server in a serialized form,
|
|
// the bigints are converted to strings and I can't figure out how to use tree.import to load bigints there
|
|
export function findIndexInTree(tree: LeanIMT, commitment: bigint): number {
|
|
let index = tree.indexOf(commitment);
|
|
if (index === -1) {
|
|
index = tree.indexOf(commitment.toString() as unknown as bigint);
|
|
}
|
|
if (index === -1) {
|
|
throw new Error('This commitment was not found in the tree');
|
|
} else {
|
|
// console.log(`Index of commitment in the registry: ${index}`);
|
|
}
|
|
return index;
|
|
}
|
|
|
|
export function generateCircuitInputsProve(
|
|
selector_mode: number[] | string[],
|
|
secret: number | string,
|
|
dsc_secret: number | string,
|
|
passportData: PassportData,
|
|
scope: string,
|
|
selector_dg1: string[],
|
|
selector_older_than: string | number,
|
|
majority: string,
|
|
name_smt: SMT,
|
|
selector_ofac: string | number,
|
|
forbidden_countries_list: string[],
|
|
user_identifier: string,
|
|
user_identifier_type: 'uuid' | 'hex' | 'ascii' = DEFAULT_USER_ID_TYPE
|
|
) {
|
|
const { mrz, eContent, signedAttr, encryptedDigest, dsc, dg2Hash } = passportData;
|
|
const { signatureAlgorithm, hashFunction, hashLen, x, y, modulus, curve, exponent, bits } =
|
|
parseCertificate(passportData.dsc);
|
|
|
|
const signatureAlgorithmFullName = `${signatureAlgorithm}_${hashFunction}_${curve || exponent}_${bits}`;
|
|
let pubKey: any;
|
|
let signature: any;
|
|
|
|
const { n, k } = getNAndK(`${signatureAlgorithm}_${hashFunction}_${curve || exponent}_${bits}`);
|
|
|
|
if (signatureAlgorithm === 'ecdsa') {
|
|
const { r, s } = extractRSFromSignature(encryptedDigest);
|
|
const signature_r = splitToWords(BigInt(hexToDecimal(r)), n, k);
|
|
const signature_s = splitToWords(BigInt(hexToDecimal(s)), n, k);
|
|
signature = [...signature_r, ...signature_s];
|
|
const dsc_modulus_x = splitToWords(BigInt(hexToDecimal(x)), n, k);
|
|
const dsc_modulus_y = splitToWords(BigInt(hexToDecimal(y)), n, k);
|
|
pubKey = [...dsc_modulus_x, ...dsc_modulus_y];
|
|
} else {
|
|
signature = splitToWords(BigInt(bytesToBigDecimal(encryptedDigest)), n, k);
|
|
|
|
pubKey = splitToWords(BigInt(hexToDecimal(modulus)), n, k);
|
|
}
|
|
|
|
const formattedMrz = formatMrz(mrz);
|
|
const dg1Hash = hash(hashFunction, formattedMrz);
|
|
const dg1HashOffset = findSubarrayIndex(eContent, dg1Hash);
|
|
console.log('\x1b[90m%s\x1b[0m', 'dg1HashOffset', dg1HashOffset);
|
|
assert(dg1HashOffset !== -1, `DG1 hash ${dg1Hash} not found in eContent`);
|
|
|
|
const eContentHash = hash(hashFunction, eContent);
|
|
const eContentHashOffset = findSubarrayIndex(signedAttr, eContentHash);
|
|
console.log('\x1b[90m%s\x1b[0m', 'eContentHashOffset', eContentHashOffset);
|
|
assert(eContentHashOffset !== -1, `eContent hash ${eContentHash} not found in signedAttr`);
|
|
|
|
if (eContent.length > MAX_PADDED_ECONTENT_LEN[signatureAlgorithmFullName]) {
|
|
console.error(
|
|
`Data hashes too long (${eContent.length} bytes). Max length is ${MAX_PADDED_ECONTENT_LEN[signatureAlgorithmFullName]} bytes.`
|
|
);
|
|
throw new Error(
|
|
`This length of datagroups (${eContent.length} bytes) is currently unsupported. Please contact us so we add support!`
|
|
);
|
|
}
|
|
|
|
const [eContentPadded, eContentLen] = shaPad(
|
|
new Uint8Array(eContent),
|
|
MAX_PADDED_ECONTENT_LEN[signatureAlgorithmFullName]
|
|
);
|
|
|
|
const [signedAttrPadded, signedAttrPaddedLen] = shaPad(
|
|
new Uint8Array(signedAttr),
|
|
MAX_PADDED_SIGNED_ATTR_LEN[signatureAlgorithmFullName]
|
|
);
|
|
|
|
const formattedMajority = majority.length === 1 ? `0${majority}` : majority;
|
|
const majority_ascii = formattedMajority.split('').map((char) => char.charCodeAt(0));
|
|
|
|
// SMT - OFAC
|
|
const mrz_bytes = formatMrz(passportData.mrz);
|
|
const name_leaf = getNameLeaf(mrz_bytes.slice(10, 49)); // [6-44] + 5 shift
|
|
const {
|
|
root: smt_root,
|
|
closestleaf: smt_leaf_value,
|
|
siblings: smt_siblings,
|
|
} = generateSMTProof(name_smt, name_leaf);
|
|
return {
|
|
selector_mode: formatInput(selector_mode),
|
|
dg1: formatInput(formattedMrz),
|
|
dg1_hash_offset: formatInput(dg1HashOffset),
|
|
dg2_hash: formatInput(formatDg2Hash(dg2Hash)),
|
|
eContent: Array.from(eContentPadded).map((x) => x.toString()),
|
|
eContent_padded_length: formatInput(eContentLen),
|
|
signed_attr: Array.from(signedAttrPadded).map((x) => x.toString()),
|
|
signed_attr_padded_length: formatInput(signedAttrPaddedLen),
|
|
signed_attr_econtent_hash_offset: formatInput(eContentHashOffset),
|
|
signature: signature,
|
|
pubKey: pubKey,
|
|
current_date: formatInput(getCurrentDateYYMMDD()),
|
|
selector_dg1: formatInput(selector_dg1),
|
|
selector_older_than: formatInput(selector_older_than),
|
|
majority: formatInput(majority_ascii),
|
|
user_identifier: formatInput(parseUIDToBigInt(user_identifier, user_identifier_type)),
|
|
scope: formatInput(castFromScope(scope)),
|
|
secret: formatInput(secret),
|
|
dsc_secret: formatInput(dsc_secret),
|
|
smt_root: formatInput(smt_root),
|
|
smt_leaf_value: formatInput(smt_leaf_value),
|
|
smt_siblings: formatInput(smt_siblings),
|
|
selector_ofac: formatInput(selector_ofac),
|
|
forbidden_countries_list: formatInput(formatCountriesList(forbidden_countries_list)),
|
|
};
|
|
}
|
|
|
|
export function formatInput(input: any) {
|
|
if (Array.isArray(input)) {
|
|
return input.map((item) => BigInt(item).toString());
|
|
} else {
|
|
return [BigInt(input).toString()];
|
|
}
|
|
}
|