mirror of
https://github.com/selfxyz/self.git
synced 2026-01-23 21:48:01 -05:00
234 lines
6.5 KiB
TypeScript
234 lines
6.5 KiB
TypeScript
import {DataHash, MrzInfo, PassportData} from '../types/passportData';
|
|
|
|
export function toUnsigned(byte: number) {
|
|
return byte & 0xff;
|
|
}
|
|
|
|
export function arraysAreEqual(array1: number[], array2: number[]) {
|
|
return (
|
|
array1.length === array2.length &&
|
|
array1.every((value, index) => value === array2[index])
|
|
);
|
|
}
|
|
|
|
export function toSigned(byte: number) {
|
|
return byte > 127 ? byte - 256 : byte;
|
|
}
|
|
|
|
export function dataHashesObjToArray(dataHashes: {
|
|
[key: string]: number[];
|
|
}): DataHash[] {
|
|
return Object.keys(dataHashes)
|
|
.map(key => {
|
|
const dataHash = dataHashes[key as keyof typeof dataHashes];
|
|
return [Number(key), dataHash];
|
|
})
|
|
.sort((a, b) => (a[0] as number) - (b[0] as number)) as DataHash[];
|
|
}
|
|
|
|
export function assembleMrz(mrzInfo: MrzInfo) {
|
|
return (
|
|
mrzInfo.documentCode +
|
|
'<' +
|
|
mrzInfo.issuingState +
|
|
mrzInfo.primaryIdentifier +
|
|
'<<' +
|
|
mrzInfo.secondaryIdentifier +
|
|
mrzInfo.documentNumber +
|
|
mrzInfo.documentNumberCheckDigit +
|
|
mrzInfo.nationality +
|
|
mrzInfo.dateOfBirth +
|
|
mrzInfo.dateOfBirthCheckDigit +
|
|
mrzInfo.gender.substring(0, 1) +
|
|
mrzInfo.dateOfExpiry +
|
|
mrzInfo.dateOfExpiryCheckDigit +
|
|
mrzInfo.optionalData1 +
|
|
mrzInfo.compositeCheckDigit
|
|
);
|
|
}
|
|
|
|
export function formatMrz(mrz: string) {
|
|
const mrzCharcodes = [...mrz].map(char => char.charCodeAt(0));
|
|
|
|
// console.log('mrzCharcodes:', mrzCharcodes);
|
|
|
|
mrzCharcodes.unshift(88); // the length of the mrz data
|
|
mrzCharcodes.unshift(95, 31); // the MRZ_INFO_TAG
|
|
mrzCharcodes.unshift(91); // the new length of the whole array
|
|
mrzCharcodes.unshift(97); // the tag for DG1
|
|
|
|
// console.log('mrzCharcodes with tags:', mrzCharcodes);
|
|
|
|
return mrzCharcodes;
|
|
}
|
|
|
|
// Example: [49, 15, 23, 13, 49, 57, 49, 50, 49, 54, 49, 55, 50, 50, 51, 56, 90]
|
|
// Is "191216172238Z" - 16th December 2019, 17:22:38 UTC
|
|
export function findTimeOfSignature(eContentDecomposed: any) {
|
|
const timeElement: any = eContentDecomposed.elements.find(
|
|
(element: any) => element.elements[0].identifier === '1.2.840.113549.1.9.5',
|
|
);
|
|
|
|
if (!timeElement) {
|
|
throw new Error('No time element found in eContentDecomposed');
|
|
}
|
|
|
|
const timeFound = timeElement.elements[1].elements[0].time;
|
|
|
|
// Adding the 4 bytes of the ASN.1 tag and length
|
|
// 49 : SET, 15 : LGT, 23 : UTCTIME, 13 : LGT
|
|
timeFound.unshift(...[49, 15, 23, 13]);
|
|
|
|
return timeFound;
|
|
}
|
|
|
|
export function parsePubKeyString(pubKeyString: string) {
|
|
const modulusMatch = pubKeyString.match(/modulus: ([\w\d]+)\s*public/);
|
|
const publicExponentMatch = pubKeyString.match(/public exponent: (\w+)/);
|
|
|
|
const modulus = modulusMatch ? modulusMatch[1] : null;
|
|
const exponent = publicExponentMatch ? publicExponentMatch[1] : null;
|
|
|
|
// console.log('Modulus:', modulus);
|
|
// console.log('Public Exponent:', exponent);
|
|
|
|
if (!modulus || !exponent) {
|
|
throw new Error('Could not parse public key string');
|
|
}
|
|
|
|
return {
|
|
modulus,
|
|
exponent,
|
|
};
|
|
}
|
|
|
|
export function formatAndConcatenateDataHashes(
|
|
mrzHash: number[],
|
|
dataHashes: DataHash[],
|
|
) {
|
|
// Let's replace the first array with the MRZ hash
|
|
dataHashes.shift();
|
|
dataHashes.unshift([1, mrzHash]);
|
|
|
|
let concat: number[] = []
|
|
|
|
// Starting sequence. Should be the same for everybody, but not sure
|
|
concat.push(...[
|
|
48, -126, 1, 37, 2, 1, 0, 48, 11, 6, 9, 96, -122, 72, 1, 101, 3, 4, 2, 1,
|
|
48, -126, 1, 17,
|
|
])
|
|
|
|
for(const dataHash of dataHashes) {
|
|
concat.push(...[48, 37, 2, 1, dataHash[0], 4, 32, ...dataHash[1]])
|
|
}
|
|
|
|
return concat;
|
|
}
|
|
|
|
export function assembleEContent(
|
|
messageDigest: number[],
|
|
timeOfSignature: number[],
|
|
) {
|
|
const constructedEContent = [];
|
|
|
|
// Detailed description is in private file r&d.ts for now
|
|
// First, the tag and length, assumed to be always the same
|
|
constructedEContent.push(...[49, 102]);
|
|
|
|
// 1.2.840.113549.1.9.3 is RFC_3369_CONTENT_TYPE_OID
|
|
constructedEContent.push(
|
|
...[48, 21, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 3],
|
|
);
|
|
// 2.23.136.1.1.1 is ldsSecurityObject
|
|
constructedEContent.push(...[49, 8, 6, 6, 103, -127, 8, 1, 1, 1]);
|
|
|
|
// 1.2.840.113549.1.9.5 is signing-time
|
|
constructedEContent.push(
|
|
...[48, 28, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 5],
|
|
);
|
|
// time of the signature
|
|
constructedEContent.push(...timeOfSignature);
|
|
// 1.2.840.113549.1.9.4 is RFC_3369_MESSAGE_DIGEST_OID
|
|
constructedEContent.push(
|
|
...[48, 47, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 4],
|
|
);
|
|
// TAG and length of the message digest
|
|
constructedEContent.push(...[49, 34, 4, 32]);
|
|
|
|
constructedEContent.push(...messageDigest);
|
|
return constructedEContent;
|
|
}
|
|
|
|
export const toBinaryString = (byte: any) => {
|
|
const binary = (parseInt(byte, 10) & 0xFF).toString(2).padStart(8, '0');
|
|
return binary;
|
|
};
|
|
|
|
export function splitToWords(
|
|
number: bigint,
|
|
wordsize: bigint,
|
|
numberElement: bigint
|
|
) {
|
|
let t = number
|
|
const words: string[] = []
|
|
for (let i = BigInt(0); i < numberElement; ++i) {
|
|
const baseTwo = BigInt(2)
|
|
|
|
words.push(`${t % BigInt(Math.pow(Number(baseTwo), Number(wordsize)))}`)
|
|
t = BigInt(t / BigInt(Math.pow(Number(BigInt(2)), Number(wordsize))))
|
|
}
|
|
if (!(t == BigInt(0))) {
|
|
throw `Number ${number} does not fit in ${(
|
|
wordsize * numberElement
|
|
).toString()} bits`
|
|
}
|
|
return words
|
|
}
|
|
|
|
export function bytesToBigDecimal(arr: number[]): string {
|
|
let result = BigInt(0);
|
|
for (let i = 0; i < arr.length; i++) {
|
|
result = result * BigInt(256) + BigInt(arr[i] & 0xff);
|
|
}
|
|
return result.toString();
|
|
}
|
|
|
|
export function hexToDecimal(hex: string): string {
|
|
return BigInt(`0x${hex}`).toString();
|
|
}
|
|
|
|
export function formatDuration(durationInMs: number) {
|
|
const durationInSeconds = durationInMs / 1000;
|
|
const minutes = Math.floor((durationInSeconds % 3600) / 60);
|
|
const seconds = Math.floor(durationInSeconds % 60);
|
|
|
|
return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
}
|
|
|
|
export function formatProof(proof: any) {
|
|
const formattedProof: { [key: string]: any } = {};
|
|
|
|
for (const key in proof) {
|
|
if (Object.hasOwnProperty.call(proof, key)) {
|
|
const element = proof[key];
|
|
|
|
if (key === 'b') {
|
|
// Special formatting for 'b'
|
|
formattedProof[key] = element.map((complex: string) => {
|
|
const matches = complex.match(/QuadExtField\(([^)]+)\)/);
|
|
if (matches && matches[1]) {
|
|
return matches[1].split(' + ').map(num => {
|
|
return num.replace(' * u', '').trim();
|
|
});
|
|
}
|
|
return [];
|
|
});
|
|
} else {
|
|
// Direct copy for 'a' and 'c'
|
|
formattedProof[key] = [...element];
|
|
}
|
|
}
|
|
}
|
|
|
|
return formattedProof;
|
|
} |